@openclaw/plugin-inspector 0.0.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +151 -2
- package/examples/github-actions-plugin-inspector.yml +24 -0
- package/examples/plugin-inspector.config.json +15 -0
- package/package.json +56 -3
- package/src/advanced.js +186 -0
- package/src/api.js +85 -0
- package/src/artifacts.js +113 -0
- package/src/capture-api.js +115 -0
- package/src/ci-policy.js +259 -0
- package/src/ci-summary.js +223 -0
- package/src/cli.js +113 -0
- package/src/cold-import-readiness.js +271 -0
- package/src/compatibility-report.js +356 -0
- package/src/config.js +182 -0
- package/src/contract-capture.js +288 -0
- package/src/contract-coverage.js +167 -0
- package/src/contract-probes.js +156 -0
- package/src/execution-results.js +297 -0
- package/src/fixture-summary.js +780 -0
- package/src/import-loop-profile.js +169 -0
- package/src/index.js +10 -0
- package/src/inspector.js +405 -0
- package/src/issues.js +366 -0
- package/src/json-file.js +10 -0
- package/src/mock-sdk-capture-runner.js +69 -0
- package/src/openclaw-target.js +179 -0
- package/src/path-utils.js +28 -0
- package/src/platform-probes.js +238 -0
- package/src/process-profile.js +117 -0
- package/src/profile-diff.js +222 -0
- package/src/ref-diff.js +335 -0
- package/src/report.js +435 -0
- package/src/runtime-capture-report.js +134 -0
- package/src/runtime-profile.js +289 -0
- package/src/sdk-mock.js +61 -0
- package/src/stats.js +13 -0
- package/src/synthetic-probes.js +544 -0
- package/src/workspace-plan.js +496 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import { renderPaddedMarkdownTable, writeJsonMarkdownArtifacts } from "./artifacts.js";
|
|
2
|
+
|
|
3
|
+
export const syntheticRegistrationExecutionProfiles = {
|
|
4
|
+
defineChannelPluginEntry: {
|
|
5
|
+
mode: "metadata-only",
|
|
6
|
+
callableProperties: [],
|
|
7
|
+
reason: "entry wrapper metadata is captured before channel runtime execution",
|
|
8
|
+
},
|
|
9
|
+
definePluginEntry: {
|
|
10
|
+
mode: "metadata-only",
|
|
11
|
+
callableProperties: [],
|
|
12
|
+
reason: "entry wrapper metadata is captured before plugin runtime execution",
|
|
13
|
+
},
|
|
14
|
+
registerChannel: {
|
|
15
|
+
mode: "channel-opt-in",
|
|
16
|
+
callableProperties: ["send", "receive"],
|
|
17
|
+
option: "includeChannelRuntime",
|
|
18
|
+
},
|
|
19
|
+
registerCli: {
|
|
20
|
+
mode: "direct",
|
|
21
|
+
callableProperties: ["handler", "run", "execute"],
|
|
22
|
+
},
|
|
23
|
+
registerCommand: {
|
|
24
|
+
mode: "direct",
|
|
25
|
+
callableProperties: ["handler", "run", "execute"],
|
|
26
|
+
},
|
|
27
|
+
registerContextEngine: {
|
|
28
|
+
mode: "metadata-only",
|
|
29
|
+
callableProperties: [],
|
|
30
|
+
reason: "context engine factories are captured as registration metadata; engine startup remains isolated opt-in",
|
|
31
|
+
},
|
|
32
|
+
registerGatewayMethod: {
|
|
33
|
+
mode: "direct",
|
|
34
|
+
callableProperties: ["handler", "run", "execute"],
|
|
35
|
+
},
|
|
36
|
+
registerHttpRoute: {
|
|
37
|
+
mode: "direct",
|
|
38
|
+
callableProperties: ["handler", "run", "execute"],
|
|
39
|
+
},
|
|
40
|
+
registerInteractiveHandler: {
|
|
41
|
+
mode: "direct",
|
|
42
|
+
callableProperties: ["handler", "run", "execute"],
|
|
43
|
+
},
|
|
44
|
+
registerHook: {
|
|
45
|
+
mode: "metadata-only",
|
|
46
|
+
callableProperties: [],
|
|
47
|
+
reason: "legacy hook registrar is captured as metadata; hook handlers are probed through hook events",
|
|
48
|
+
},
|
|
49
|
+
registerMemoryPromptSection: {
|
|
50
|
+
mode: "metadata-only",
|
|
51
|
+
callableProperties: [],
|
|
52
|
+
reason: "memory prompt section renderers are captured as metadata before prompt-runtime execution",
|
|
53
|
+
},
|
|
54
|
+
registerMemoryRuntime: {
|
|
55
|
+
mode: "metadata-only",
|
|
56
|
+
callableProperties: [],
|
|
57
|
+
reason: "memory runtime factories are captured as metadata; external memory startup remains isolated opt-in",
|
|
58
|
+
},
|
|
59
|
+
registerService: {
|
|
60
|
+
mode: "lifecycle-opt-in",
|
|
61
|
+
callableProperties: ["start", "stop"],
|
|
62
|
+
option: "includeLifecycle",
|
|
63
|
+
},
|
|
64
|
+
registerSpeechProvider: {
|
|
65
|
+
mode: "provider-opt-in",
|
|
66
|
+
callableProperties: ["speak", "synthesize"],
|
|
67
|
+
option: "includeProviderCapabilities",
|
|
68
|
+
},
|
|
69
|
+
registerTool: {
|
|
70
|
+
mode: "direct",
|
|
71
|
+
callableProperties: ["run", "handler", "execute"],
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const defaultSyntheticHookEvents = {
|
|
76
|
+
agent_end: {
|
|
77
|
+
runId: "run-fixture",
|
|
78
|
+
agentId: "agent-fixture",
|
|
79
|
+
conversationId: "conversation-fixture",
|
|
80
|
+
status: "completed",
|
|
81
|
+
transcript: [{ role: "assistant", content: "[redacted fixture output]" }],
|
|
82
|
+
},
|
|
83
|
+
before_agent_start: {
|
|
84
|
+
agentId: "agent-fixture",
|
|
85
|
+
runId: "run-fixture",
|
|
86
|
+
config: { source: "plugin-inspector" },
|
|
87
|
+
},
|
|
88
|
+
before_prompt_build: {
|
|
89
|
+
runId: "run-fixture",
|
|
90
|
+
agentId: "agent-fixture",
|
|
91
|
+
conversationId: "conversation-fixture",
|
|
92
|
+
messages: [{ role: "user", content: "fixture prompt" }],
|
|
93
|
+
metadata: { source: "plugin-inspector" },
|
|
94
|
+
},
|
|
95
|
+
before_tool_call: {
|
|
96
|
+
runId: "run-fixture",
|
|
97
|
+
toolName: "fixture_tool",
|
|
98
|
+
params: {},
|
|
99
|
+
toolCallId: "call-fixture",
|
|
100
|
+
},
|
|
101
|
+
inbound_claim: {
|
|
102
|
+
channelId: "fixture-channel",
|
|
103
|
+
source: { type: "external", id: "fixture-source" },
|
|
104
|
+
message: { id: "message-fixture", text: "claim this message" },
|
|
105
|
+
},
|
|
106
|
+
llm_input: {
|
|
107
|
+
agentId: "agent-fixture",
|
|
108
|
+
model: "gpt-5.4",
|
|
109
|
+
messages: [{ role: "user", content: "[redacted fixture input]" }],
|
|
110
|
+
},
|
|
111
|
+
llm_output: {
|
|
112
|
+
agentId: "agent-fixture",
|
|
113
|
+
model: "gpt-5.4",
|
|
114
|
+
output: { role: "assistant", content: "[redacted fixture output]" },
|
|
115
|
+
},
|
|
116
|
+
subagent_delivery_target: {
|
|
117
|
+
childSessionKey: "child-session",
|
|
118
|
+
agentId: "agent-child",
|
|
119
|
+
label: "fixture child",
|
|
120
|
+
mode: "run",
|
|
121
|
+
requester: { channel: "fixture-channel", accountId: "fixture-account" },
|
|
122
|
+
},
|
|
123
|
+
subagent_ended: {
|
|
124
|
+
childSessionKey: "child-session",
|
|
125
|
+
agentId: "agent-child",
|
|
126
|
+
status: "completed",
|
|
127
|
+
},
|
|
128
|
+
subagent_spawned: {
|
|
129
|
+
childSessionKey: "child-session",
|
|
130
|
+
agentId: "agent-child",
|
|
131
|
+
label: "fixture child",
|
|
132
|
+
mode: "run",
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const defaultSyntheticHookContexts = {
|
|
137
|
+
agent_end: {
|
|
138
|
+
runId: "run-fixture",
|
|
139
|
+
agentId: "agent-fixture",
|
|
140
|
+
sessionId: "session-fixture",
|
|
141
|
+
channelId: "fixture-channel",
|
|
142
|
+
},
|
|
143
|
+
before_agent_start: {
|
|
144
|
+
runId: "run-fixture",
|
|
145
|
+
agentId: "agent-fixture",
|
|
146
|
+
sessionId: "session-fixture",
|
|
147
|
+
},
|
|
148
|
+
before_prompt_build: {
|
|
149
|
+
runId: "run-fixture",
|
|
150
|
+
agentId: "agent-fixture",
|
|
151
|
+
sessionId: "session-fixture",
|
|
152
|
+
channelId: "fixture-channel",
|
|
153
|
+
},
|
|
154
|
+
before_tool_call: {
|
|
155
|
+
runId: "run-fixture",
|
|
156
|
+
agentId: "agent-fixture",
|
|
157
|
+
sessionId: "session-fixture",
|
|
158
|
+
toolName: "fixture_tool",
|
|
159
|
+
},
|
|
160
|
+
inbound_claim: {
|
|
161
|
+
channelId: "fixture-channel",
|
|
162
|
+
accountId: "fixture-account",
|
|
163
|
+
},
|
|
164
|
+
llm_input: {
|
|
165
|
+
runId: "run-fixture",
|
|
166
|
+
agentId: "agent-fixture",
|
|
167
|
+
sessionId: "session-fixture",
|
|
168
|
+
},
|
|
169
|
+
llm_output: {
|
|
170
|
+
runId: "run-fixture",
|
|
171
|
+
agentId: "agent-fixture",
|
|
172
|
+
sessionId: "session-fixture",
|
|
173
|
+
},
|
|
174
|
+
subagent_delivery_target: {
|
|
175
|
+
runId: "run-fixture",
|
|
176
|
+
parentAgentId: "agent-parent",
|
|
177
|
+
},
|
|
178
|
+
subagent_ended: {
|
|
179
|
+
runId: "run-fixture",
|
|
180
|
+
parentAgentId: "agent-parent",
|
|
181
|
+
},
|
|
182
|
+
subagent_spawned: {
|
|
183
|
+
runId: "run-fixture",
|
|
184
|
+
parentAgentId: "agent-parent",
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const defaultSyntheticRegistrationArguments = {
|
|
189
|
+
defineChannelPluginEntry: [{ id: "fixture-channel", setup: "function", receive: "function" }],
|
|
190
|
+
definePluginEntry: [{ id: "fixture-plugin", register: "function" }],
|
|
191
|
+
registerChannel: [{ id: "fixture-channel", send: "function", receive: "function" }],
|
|
192
|
+
registerCli: [{ name: "fixture-command", args: [{ name: "input", type: "string" }] }],
|
|
193
|
+
registerCommand: [{ name: "fixture-command", handler: "function" }],
|
|
194
|
+
registerContextEngine: [{ id: "fixture-context-engine", factory: "function" }],
|
|
195
|
+
registerGatewayMethod: [{ name: "fixture.gateway.method", inputSchema: { type: "object" }, handler: "function" }],
|
|
196
|
+
registerHook: ["before_prompt_build", "function"],
|
|
197
|
+
registerHttpRoute: [{ method: "POST", path: "/fixture/probe", handler: "function" }],
|
|
198
|
+
registerInteractiveHandler: [{ id: "fixture-interaction", handler: "function" }],
|
|
199
|
+
registerMemoryPromptSection: [{ id: "fixture-memory-section", render: "function" }],
|
|
200
|
+
registerMemoryRuntime: [{ id: "fixture-memory-runtime", create: "function" }],
|
|
201
|
+
registerService: [{ name: "fixture-service", start: "function", stop: "function" }],
|
|
202
|
+
registerSpeechProvider: [{ id: "fixture-speech", speak: "function" }],
|
|
203
|
+
registerTool: [{ name: "fixture_tool", inputSchema: { type: "object", properties: {} }, run: "function" }],
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export function buildSyntheticProbePlan(options = {}) {
|
|
207
|
+
if (!options.capture) {
|
|
208
|
+
throw new TypeError("buildSyntheticProbePlan requires a capture inventory");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const hookEvents = options.hookEvents ?? defaultSyntheticHookEvents;
|
|
212
|
+
const hookContexts = options.hookContexts ?? defaultSyntheticHookContexts;
|
|
213
|
+
const registrationArguments = options.registrationArguments ?? defaultSyntheticRegistrationArguments;
|
|
214
|
+
const capture = options.capture;
|
|
215
|
+
const hooks = capture.fixtures.flatMap((fixture) =>
|
|
216
|
+
fixture.hooks.map((hook) => ({
|
|
217
|
+
id: hook.id,
|
|
218
|
+
fixture: fixture.id,
|
|
219
|
+
kind: "hook",
|
|
220
|
+
seam: hook.hook,
|
|
221
|
+
status: hook.syntheticEvent && typeof hook.syntheticEvent === "object" ? "ready" : "blocked",
|
|
222
|
+
blocker: hook.syntheticEvent && typeof hook.syntheticEvent === "object" ? null : "missing synthetic event",
|
|
223
|
+
assertions: hook.assertions,
|
|
224
|
+
syntheticEvent: hook.syntheticEvent ?? hookEvents[hook.hook] ?? { hook: hook.hook },
|
|
225
|
+
syntheticContext: hook.syntheticContext ?? hookContexts[hook.hook] ?? { hook: hook.hook },
|
|
226
|
+
source: hook.ref,
|
|
227
|
+
})),
|
|
228
|
+
);
|
|
229
|
+
const registrations = capture.fixtures.flatMap((fixture) =>
|
|
230
|
+
fixture.registrations.map((registration) => {
|
|
231
|
+
const execution = registrationExecutionProfile(registration.registrar);
|
|
232
|
+
const hasSyntheticArguments = Array.isArray(registration.syntheticArguments);
|
|
233
|
+
const status = hasSyntheticArguments && execution.mode !== "unknown" ? "ready" : "blocked";
|
|
234
|
+
return {
|
|
235
|
+
id: registration.id,
|
|
236
|
+
fixture: fixture.id,
|
|
237
|
+
kind: "registration",
|
|
238
|
+
seam: registration.registrar,
|
|
239
|
+
status,
|
|
240
|
+
blocker: probeBlocker({ hasSyntheticArguments, execution }),
|
|
241
|
+
assertions: registration.assertions,
|
|
242
|
+
syntheticArguments: registration.syntheticArguments ?? registrationArguments[registration.registrar] ?? [{}],
|
|
243
|
+
execution,
|
|
244
|
+
source: registration.ref,
|
|
245
|
+
};
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
const probes = [...hooks, ...registrations];
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
generatedAt: capture.generatedAt,
|
|
252
|
+
summary: {
|
|
253
|
+
fixtureCount: capture.summary.fixtureCount,
|
|
254
|
+
probeCount: probes.length,
|
|
255
|
+
hookProbeCount: hooks.length,
|
|
256
|
+
registrationProbeCount: registrations.length,
|
|
257
|
+
readyCount: probes.filter((probe) => probe.status === "ready").length,
|
|
258
|
+
blockedCount: probes.filter((probe) => probe.status === "blocked").length,
|
|
259
|
+
directExecutionCount: registrations.filter((probe) => probe.execution.mode === "direct").length,
|
|
260
|
+
optInExecutionCount: registrations.filter((probe) => probe.execution.mode.endsWith("-opt-in")).length,
|
|
261
|
+
metadataOnlyCount: registrations.filter((probe) => probe.execution.mode === "metadata-only").length,
|
|
262
|
+
},
|
|
263
|
+
probes,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function validateSyntheticProbePlan(plan) {
|
|
268
|
+
const errors = [];
|
|
269
|
+
for (const probe of plan.probes) {
|
|
270
|
+
if (!probe.source) {
|
|
271
|
+
errors.push(`${probe.id}: missing source reference`);
|
|
272
|
+
}
|
|
273
|
+
if (!Array.isArray(probe.assertions) || probe.assertions.length === 0) {
|
|
274
|
+
errors.push(`${probe.id}: missing probe assertions`);
|
|
275
|
+
}
|
|
276
|
+
if (probe.status === "blocked") {
|
|
277
|
+
errors.push(`${probe.id}: ${probe.blocker}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return errors;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export async function writeSyntheticProbePlan(plan, options = {}) {
|
|
284
|
+
return writeJsonMarkdownArtifacts({
|
|
285
|
+
jsonPath: options.jsonPath,
|
|
286
|
+
markdownPath: options.markdownPath,
|
|
287
|
+
json: plan,
|
|
288
|
+
markdown: renderSyntheticProbeMarkdown(plan, options),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export async function runCapturedSyntheticProbes(capture, options = {}) {
|
|
293
|
+
const hookEvents = options.hookEvents ?? defaultSyntheticHookEvents;
|
|
294
|
+
const hookContexts = options.hookContexts ?? defaultSyntheticHookContexts;
|
|
295
|
+
const captured = capture.captured ?? [];
|
|
296
|
+
const retained = new Map((capture.retained ?? []).map((item) => [item.captureIndex, item]));
|
|
297
|
+
const results = [];
|
|
298
|
+
|
|
299
|
+
for (let index = 0; index < captured.length; index += 1) {
|
|
300
|
+
const entry = captured[index];
|
|
301
|
+
const retainedEntry = retained.get(index);
|
|
302
|
+
if (!retainedEntry) {
|
|
303
|
+
results.push(blockedResult(entry, index, "handler retention was not enabled"));
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (entry.kind === "hook") {
|
|
307
|
+
results.push(await runHookProbe(entry, retainedEntry, index, { hookEvents, hookContexts }));
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (entry.kind === "registration") {
|
|
311
|
+
results.push(...(await runRegistrationProbes(entry, retainedEntry, index, options)));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
entrypoint: capture.entrypoint,
|
|
317
|
+
status: capture.status,
|
|
318
|
+
summary: {
|
|
319
|
+
probeCount: results.length,
|
|
320
|
+
passCount: results.filter((result) => result.status === "pass").length,
|
|
321
|
+
failCount: results.filter((result) => result.status === "fail").length,
|
|
322
|
+
blockedCount: results.filter((result) => result.status === "blocked").length,
|
|
323
|
+
},
|
|
324
|
+
results,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function renderSyntheticProbeMarkdown(plan, options = {}) {
|
|
329
|
+
return [
|
|
330
|
+
`# ${options.title ?? "Plugin Inspector Synthetic Probes"}`,
|
|
331
|
+
"",
|
|
332
|
+
`Generated: ${plan.generatedAt}`,
|
|
333
|
+
"",
|
|
334
|
+
"## Summary",
|
|
335
|
+
"",
|
|
336
|
+
markdownTable(
|
|
337
|
+
[
|
|
338
|
+
["Fixtures", plan.summary.fixtureCount],
|
|
339
|
+
["Probes", plan.summary.probeCount],
|
|
340
|
+
["Hook probes", plan.summary.hookProbeCount],
|
|
341
|
+
["Registration probes", plan.summary.registrationProbeCount],
|
|
342
|
+
["Ready", plan.summary.readyCount],
|
|
343
|
+
["Blocked", plan.summary.blockedCount],
|
|
344
|
+
["Direct execution", plan.summary.directExecutionCount],
|
|
345
|
+
["Opt-in execution", plan.summary.optInExecutionCount],
|
|
346
|
+
["Metadata-only", plan.summary.metadataOnlyCount],
|
|
347
|
+
],
|
|
348
|
+
["Metric", "Value"],
|
|
349
|
+
),
|
|
350
|
+
"",
|
|
351
|
+
"## Probe Inventory",
|
|
352
|
+
"",
|
|
353
|
+
markdownTable(
|
|
354
|
+
plan.probes.map((probe) => [
|
|
355
|
+
probe.fixture,
|
|
356
|
+
probe.kind,
|
|
357
|
+
probe.seam,
|
|
358
|
+
probe.status,
|
|
359
|
+
probe.execution?.mode ?? "hook-direct",
|
|
360
|
+
probe.source,
|
|
361
|
+
probe.assertions.join("; "),
|
|
362
|
+
]),
|
|
363
|
+
["Fixture", "Kind", "Seam", "Status", "Execution", "Evidence", "Assertions"],
|
|
364
|
+
),
|
|
365
|
+
].join("\n");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function registrationExecutionProfile(registrar) {
|
|
369
|
+
return (
|
|
370
|
+
syntheticRegistrationExecutionProfiles[registrar] ?? {
|
|
371
|
+
mode: "unknown",
|
|
372
|
+
callableProperties: [],
|
|
373
|
+
reason: "registrar has not been classified for synthetic execution",
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function probeBlocker({ hasSyntheticArguments, execution }) {
|
|
379
|
+
if (!hasSyntheticArguments) {
|
|
380
|
+
return "missing synthetic arguments";
|
|
381
|
+
}
|
|
382
|
+
if (execution.mode === "unknown") {
|
|
383
|
+
return execution.reason;
|
|
384
|
+
}
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function runHookProbe(entry, retainedEntry, captureIndex, { hookEvents, hookContexts }) {
|
|
389
|
+
if (typeof retainedEntry.handler !== "function") {
|
|
390
|
+
return blockedResult(entry, captureIndex, "captured hook has no callable handler");
|
|
391
|
+
}
|
|
392
|
+
return runProbe({
|
|
393
|
+
captureIndex,
|
|
394
|
+
kind: "hook",
|
|
395
|
+
seam: entry.name,
|
|
396
|
+
label: entry.name,
|
|
397
|
+
invoke: () =>
|
|
398
|
+
retainedEntry.handler(
|
|
399
|
+
hookEvents[entry.name] ?? { hook: entry.name },
|
|
400
|
+
hookContexts[entry.name] ?? { hook: entry.name },
|
|
401
|
+
),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function runRegistrationProbes(entry, retainedEntry, captureIndex, options) {
|
|
406
|
+
const profile = registrationExecutionProfile(entry.name);
|
|
407
|
+
if (profile.mode === "unknown") {
|
|
408
|
+
return [blockedResult(entry, captureIndex, "captured registration has no execution profile")];
|
|
409
|
+
}
|
|
410
|
+
if (profile.mode === "metadata-only") {
|
|
411
|
+
return [metadataOnlyResult(entry, captureIndex, profile.reason)];
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const descriptor = retainedEntry.arguments?.[0];
|
|
415
|
+
if (!descriptor || typeof descriptor !== "object") {
|
|
416
|
+
return [blockedResult(entry, captureIndex, "captured registration has no object descriptor")];
|
|
417
|
+
}
|
|
418
|
+
if (profile.option && options[profile.option] !== true) {
|
|
419
|
+
return [blockedResult(entry, captureIndex, `captured registration requires ${profile.option}=true`)];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const invocations = registrationInvocations(entry.name, descriptor, profile, options);
|
|
423
|
+
if (invocations.length === 0) {
|
|
424
|
+
return [blockedResult(entry, captureIndex, "captured registration has no supported callable probe")];
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return Promise.all(
|
|
428
|
+
invocations.map((invocation) =>
|
|
429
|
+
runProbe({
|
|
430
|
+
captureIndex,
|
|
431
|
+
kind: "registration",
|
|
432
|
+
seam: entry.name,
|
|
433
|
+
label: invocation.label,
|
|
434
|
+
invoke: invocation.invoke,
|
|
435
|
+
}),
|
|
436
|
+
),
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function registrationInvocations(registrar, descriptor, profile, options) {
|
|
441
|
+
const invocations = [];
|
|
442
|
+
|
|
443
|
+
for (const property of profile.callableProperties) {
|
|
444
|
+
if (typeof descriptor[property] === "function") {
|
|
445
|
+
invocations.push({
|
|
446
|
+
label: `${registrar}.${property}`,
|
|
447
|
+
invoke: () => invokeRegistrationCallable(descriptor[property], registrar, property, options),
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return invocations;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function invokeRegistrationCallable(callable, registrar, property, options) {
|
|
455
|
+
const event = syntheticRegistrationEvent(registrar, property, options);
|
|
456
|
+
if (property === "execute") {
|
|
457
|
+
return callable("call-fixture", event.params, new AbortController().signal, () => undefined);
|
|
458
|
+
}
|
|
459
|
+
return callable(event);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function syntheticRegistrationEvent(registrar, property, options) {
|
|
463
|
+
const hookEvents = options.hookEvents ?? defaultSyntheticHookEvents;
|
|
464
|
+
const beforeToolCall = hookEvents.before_tool_call ?? defaultSyntheticHookEvents.before_tool_call;
|
|
465
|
+
return {
|
|
466
|
+
source: options.syntheticSource ?? "plugin-inspector.synthetic",
|
|
467
|
+
registrar,
|
|
468
|
+
property,
|
|
469
|
+
params: {},
|
|
470
|
+
input: {},
|
|
471
|
+
body: {},
|
|
472
|
+
headers: {},
|
|
473
|
+
toolName: beforeToolCall.toolName,
|
|
474
|
+
toolCall: {
|
|
475
|
+
id: beforeToolCall.toolCallId,
|
|
476
|
+
name: beforeToolCall.toolName,
|
|
477
|
+
},
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function runProbe({ captureIndex, kind, seam, label, invoke }) {
|
|
482
|
+
try {
|
|
483
|
+
const output = await invoke();
|
|
484
|
+
return {
|
|
485
|
+
captureIndex,
|
|
486
|
+
kind,
|
|
487
|
+
seam,
|
|
488
|
+
label,
|
|
489
|
+
status: "pass",
|
|
490
|
+
output: summarizeValue(output),
|
|
491
|
+
};
|
|
492
|
+
} catch (error) {
|
|
493
|
+
return {
|
|
494
|
+
captureIndex,
|
|
495
|
+
kind,
|
|
496
|
+
seam,
|
|
497
|
+
label,
|
|
498
|
+
status: "fail",
|
|
499
|
+
error: error instanceof Error ? error.message : String(error),
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function blockedResult(entry, captureIndex, reason) {
|
|
505
|
+
return {
|
|
506
|
+
captureIndex,
|
|
507
|
+
kind: entry.kind,
|
|
508
|
+
seam: entry.name,
|
|
509
|
+
label: entry.name,
|
|
510
|
+
status: "blocked",
|
|
511
|
+
reason,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function metadataOnlyResult(entry, captureIndex, reason) {
|
|
516
|
+
return {
|
|
517
|
+
captureIndex,
|
|
518
|
+
kind: entry.kind,
|
|
519
|
+
seam: entry.name,
|
|
520
|
+
label: entry.name,
|
|
521
|
+
status: "pass",
|
|
522
|
+
output: { mode: "metadata-only", reason },
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function summarizeValue(value) {
|
|
527
|
+
if (typeof value === "function") {
|
|
528
|
+
return { type: "function" };
|
|
529
|
+
}
|
|
530
|
+
if (Array.isArray(value)) {
|
|
531
|
+
return { type: "array", length: value.length };
|
|
532
|
+
}
|
|
533
|
+
if (value && typeof value === "object") {
|
|
534
|
+
return {
|
|
535
|
+
type: "object",
|
|
536
|
+
keys: Object.keys(value).sort(),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
return { type: typeof value, value };
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function markdownTable(rows, headers) {
|
|
543
|
+
return renderPaddedMarkdownTable(rows, headers);
|
|
544
|
+
}
|