diff --git a/src/capture-api.js b/src/capture-api.js index 24a5a86..cf1626b 100644 --- a/src/capture-api.js +++ b/src/capture-api.js @@ -82,6 +82,24 @@ export function createCaptureApi(options = {}) { } return api; }, + onConversationBindingResolved(handler) { + const captureIndex = + captured.push({ + kind: "hook", + name: "onConversationBindingResolved", + handlerType: typeof handler, + arguments: summarizeArguments([handler]), + }) - 1; + if (retainHandlers) { + retained.push({ + kind: "hook", + name: "onConversationBindingResolved", + handler, + captureIndex, + }); + } + return api; + }, }, { get(target, property) { diff --git a/test/capture-api.test.js b/test/capture-api.test.js index 1fb6127..175d5f9 100644 --- a/test/capture-api.test.js +++ b/test/capture-api.test.js @@ -50,6 +50,16 @@ test("capture API returns useful channel, gateway, and lifecycle descriptors", ( assert.equal(typeof service.dispose, "function"); }); +test("capture API records conversation binding resolved callbacks", () => { + const api = createCaptureApi(); + + assert.equal(api.onConversationBindingResolved(() => undefined), api); + assert.deepEqual( + api.getCapturedContracts().map((entry) => `${entry.kind}:${entry.name}`), + ["hook:onConversationBindingResolved"], + ); +}); + test("capture API accepts custom registrar return profiles", () => { const api = createCaptureApi({ registrarProfiles: { diff --git a/test/runtime-capture-report.test.js b/test/runtime-capture-report.test.js index 68d10a0..2c47fe2 100644 --- a/test/runtime-capture-report.test.js +++ b/test/runtime-capture-report.test.js @@ -61,6 +61,51 @@ test("runtime capture report imports plugin entrypoints with mocked SDK", async assert.match(await readFile(path.join(outDir, "capture.md"), "utf8"), /registerTool/); }); +test("runtime capture records conversation binding resolved callbacks", async () => { + const rootDir = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-runtime-binding-resolved-")); + await mkdir(path.join(rootDir, "src"), { recursive: true }); + await writeFile( + path.join(rootDir, "package.json"), + `${JSON.stringify( + { + name: "openclaw-binding-resolved", + version: "1.0.0", + type: "module", + openclaw: { + extensions: ["src/index.mjs"], + compat: { pluginApi: "^1.0.0" }, + }, + }, + null, + 2, + )}\n`, + "utf8", + ); + await writeFile( + path.join(rootDir, "src", "index.mjs"), + [ + 'import { definePluginEntry } from "openclaw/plugin-sdk";', + "", + "export default definePluginEntry((api) => {", + " api.onConversationBindingResolved(() => undefined);", + "});", + ].join("\n"), + "utf8", + ); + + const config = await loadPluginRootConfig(null, { cwd: rootDir }); + const compatibilityReport = await inspectCompatibilityFixtureSet(config, { openclawPath: false }); + const captureReport = await buildRuntimeCaptureReport({ report: compatibilityReport, rootDir }); + + assert.equal(captureReport.summary.failedCount, 0); + assert.equal(captureReport.summary.capturedCount, 1); + assert.equal(captureReport.summary.hookCount, 1); + assert.deepEqual( + captureReport.results[0].captured.map((entry) => `${entry.kind}:${entry.name}`), + ["hook:onConversationBindingResolved"], + ); +}); + test("runtime capture report classifies missing mocked SDK exports", async () => { const rootDir = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-runtime-capture-missing-export-")); await mkdir(path.join(rootDir, "src"), { recursive: true });