@openclaw/plugin-inspector 0.1.1 → 0.1.3
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 +25 -0
- package/README.md +82 -41
- package/examples/github-actions-plugin-inspector.yml +2 -2
- package/examples/plugin-inspector.config.json +3 -0
- package/package.json +1 -1
- package/src/advanced.js +10 -0
- package/src/api.js +14 -8
- package/src/capture-api.js +180 -12
- package/src/capture-cli.js +61 -0
- package/src/ci-summary.js +1 -1
- package/src/cli.js +138 -7
- package/src/config.js +16 -2
- package/src/contract-probes.js +5 -0
- package/src/fixture-summary.js +40 -16
- package/src/index.js +1 -0
- package/src/init.js +150 -0
- package/src/inspector.js +89 -14
- package/src/issues.js +8 -0
- package/src/mock-sdk-capture-runner.js +110 -17
- package/src/openclaw-target.js +27 -0
- package/src/report.js +1 -0
- package/src/runtime-capture-report.js +11 -1
- package/src/sdk-mock.js +1266 -13
- package/src/synthetic-probes-cli.js +101 -0
- package/src/synthetic-probes.js +153 -9
- package/src/workspace-plan.js +16 -4
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { register } from "node:module";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
import { captureEntrypoint, runCapturedSyntheticProbes, writeArtifacts } from "./advanced.js";
|
|
8
|
+
import { createMockSdkPackage } from "./sdk-mock.js";
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
await run(args);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(error.message);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function run(commandArgs) {
|
|
20
|
+
const entrypoint = readFlag(commandArgs, "--entrypoint") ?? commandArgs.find((arg) => !arg.startsWith("-"));
|
|
21
|
+
const outputPath = readFlag(commandArgs, "--output");
|
|
22
|
+
const pluginRoot = readFlag(commandArgs, "--plugin-root");
|
|
23
|
+
const includeLifecycle = commandArgs.includes("--include-lifecycle");
|
|
24
|
+
const includeChannelRuntime = commandArgs.includes("--include-channel-runtime");
|
|
25
|
+
const includeProviderCapabilities = commandArgs.includes("--include-provider-capabilities");
|
|
26
|
+
const mockSdk = readMockSdkFlag(commandArgs) ?? true;
|
|
27
|
+
|
|
28
|
+
if (!entrypoint) {
|
|
29
|
+
throw new Error("synthetic probes require --entrypoint <path>");
|
|
30
|
+
}
|
|
31
|
+
if (process.env.PLUGIN_INSPECTOR_EXECUTE_ISOLATED !== "1") {
|
|
32
|
+
throw new Error("synthetic probes import plugin code; rerun with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 in an isolated workspace");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const capture = await captureForSyntheticProbes(entrypoint, {
|
|
36
|
+
mockSdk,
|
|
37
|
+
pluginRoot,
|
|
38
|
+
apiOptions: { retainHandlers: true },
|
|
39
|
+
});
|
|
40
|
+
const results = await runCapturedSyntheticProbes(capture, {
|
|
41
|
+
includeLifecycle,
|
|
42
|
+
includeChannelRuntime,
|
|
43
|
+
includeProviderCapabilities,
|
|
44
|
+
});
|
|
45
|
+
const json = `${JSON.stringify(results, null, 2)}\n`;
|
|
46
|
+
|
|
47
|
+
if (outputPath) {
|
|
48
|
+
await writeArtifacts([{ path: outputPath, content: json }]);
|
|
49
|
+
} else {
|
|
50
|
+
process.stdout.write(json);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function captureForSyntheticProbes(entrypoint, options) {
|
|
55
|
+
if (options.mockSdk !== true) {
|
|
56
|
+
return captureEntrypoint(entrypoint, options);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const resolvedEntrypoint = path.resolve(process.cwd(), entrypoint);
|
|
60
|
+
const pluginRoot = path.resolve(process.cwd(), options.pluginRoot ?? path.dirname(resolvedEntrypoint));
|
|
61
|
+
const workspace = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-synthetic-mock-sdk-"));
|
|
62
|
+
try {
|
|
63
|
+
const { loaderPath } = await createMockSdkPackage(workspace, { pluginRoot });
|
|
64
|
+
register(pathToFileURL(loaderPath));
|
|
65
|
+
return captureEntrypoint(entrypoint, {
|
|
66
|
+
...options,
|
|
67
|
+
mockSdk: false,
|
|
68
|
+
pluginRoot,
|
|
69
|
+
});
|
|
70
|
+
} finally {
|
|
71
|
+
await rm(workspace, { force: true, recursive: true });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function readFlag(commandArgs, name) {
|
|
76
|
+
const index = commandArgs.indexOf(name);
|
|
77
|
+
if (index === -1) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return commandArgs[index + 1] ?? null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readMockSdkFlag(commandArgs) {
|
|
84
|
+
const sdk = readFlag(commandArgs, "--sdk");
|
|
85
|
+
if (sdk === "mock") {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
if (sdk === "real") {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (sdk && !["mock", "real"].includes(sdk)) {
|
|
92
|
+
throw new Error("--sdk must be mock or real");
|
|
93
|
+
}
|
|
94
|
+
if (commandArgs.includes("--mock-sdk")) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (commandArgs.includes("--real-sdk")) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
package/src/synthetic-probes.js
CHANGED
|
@@ -203,6 +203,47 @@ export const defaultSyntheticRegistrationArguments = {
|
|
|
203
203
|
registerTool: [{ name: "fixture_tool", inputSchema: { type: "object", properties: {} }, run: "function" }],
|
|
204
204
|
};
|
|
205
205
|
|
|
206
|
+
export const defaultSyntheticRegistrationProbeInputs = {
|
|
207
|
+
registerCli: {
|
|
208
|
+
execute: commandProbeArgs,
|
|
209
|
+
handler: commandProbeArgs,
|
|
210
|
+
run: commandProbeArgs,
|
|
211
|
+
},
|
|
212
|
+
registerCommand: {
|
|
213
|
+
execute: commandProbeArgs,
|
|
214
|
+
handler: commandProbeArgs,
|
|
215
|
+
run: commandProbeArgs,
|
|
216
|
+
},
|
|
217
|
+
registerGatewayMethod: {
|
|
218
|
+
execute: gatewayProbeArgs,
|
|
219
|
+
handler: gatewayProbeArgs,
|
|
220
|
+
run: gatewayProbeArgs,
|
|
221
|
+
},
|
|
222
|
+
registerHttpRoute: {
|
|
223
|
+
execute: httpRouteProbeArgs,
|
|
224
|
+
handler: httpRouteProbeArgs,
|
|
225
|
+
run: httpRouteProbeArgs,
|
|
226
|
+
},
|
|
227
|
+
registerInteractiveHandler: {
|
|
228
|
+
execute: interactiveProbeArgs,
|
|
229
|
+
handler: interactiveProbeArgs,
|
|
230
|
+
run: interactiveProbeArgs,
|
|
231
|
+
},
|
|
232
|
+
registerService: {
|
|
233
|
+
start: lifecycleProbeArgs,
|
|
234
|
+
stop: lifecycleProbeArgs,
|
|
235
|
+
},
|
|
236
|
+
registerSpeechProvider: {
|
|
237
|
+
speak: speechProbeArgs,
|
|
238
|
+
synthesize: speechProbeArgs,
|
|
239
|
+
},
|
|
240
|
+
registerTool: {
|
|
241
|
+
execute: toolExecuteProbeArgs,
|
|
242
|
+
handler: toolRunProbeArgs,
|
|
243
|
+
run: toolRunProbeArgs,
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
|
|
206
247
|
export function buildSyntheticProbePlan(options = {}) {
|
|
207
248
|
if (!options.capture) {
|
|
208
249
|
throw new TypeError("buildSyntheticProbePlan requires a capture inventory");
|
|
@@ -411,7 +452,7 @@ async function runRegistrationProbes(entry, retainedEntry, captureIndex, options
|
|
|
411
452
|
return [metadataOnlyResult(entry, captureIndex, profile.reason)];
|
|
412
453
|
}
|
|
413
454
|
|
|
414
|
-
const descriptor = retainedEntry.arguments?.[0];
|
|
455
|
+
const descriptor = retainedEntry.arguments?.[0] ?? retainedEntry.returnValue;
|
|
415
456
|
if (!descriptor || typeof descriptor !== "object") {
|
|
416
457
|
return [blockedResult(entry, captureIndex, "captured registration has no object descriptor")];
|
|
417
458
|
}
|
|
@@ -419,7 +460,7 @@ async function runRegistrationProbes(entry, retainedEntry, captureIndex, options
|
|
|
419
460
|
return [blockedResult(entry, captureIndex, `captured registration requires ${profile.option}=true`)];
|
|
420
461
|
}
|
|
421
462
|
|
|
422
|
-
const invocations = registrationInvocations(entry.name, descriptor, profile, options);
|
|
463
|
+
const invocations = registrationInvocations(entry.name, descriptor, retainedEntry.returnValue, profile, options);
|
|
423
464
|
if (invocations.length === 0) {
|
|
424
465
|
return [blockedResult(entry, captureIndex, "captured registration has no supported callable probe")];
|
|
425
466
|
}
|
|
@@ -437,14 +478,21 @@ async function runRegistrationProbes(entry, retainedEntry, captureIndex, options
|
|
|
437
478
|
);
|
|
438
479
|
}
|
|
439
480
|
|
|
440
|
-
function registrationInvocations(registrar, descriptor, profile, options) {
|
|
481
|
+
function registrationInvocations(registrar, descriptor, returnValue, profile, options) {
|
|
441
482
|
const invocations = [];
|
|
483
|
+
const allowReturnValueFallback = descriptor === returnValue;
|
|
442
484
|
|
|
443
485
|
for (const property of profile.callableProperties) {
|
|
444
|
-
|
|
486
|
+
const callable =
|
|
487
|
+
typeof descriptor[property] === "function"
|
|
488
|
+
? descriptor[property]
|
|
489
|
+
: allowReturnValueFallback
|
|
490
|
+
? returnValue?.[property]
|
|
491
|
+
: undefined;
|
|
492
|
+
if (typeof callable === "function") {
|
|
445
493
|
invocations.push({
|
|
446
494
|
label: `${registrar}.${property}`,
|
|
447
|
-
invoke: () => invokeRegistrationCallable(
|
|
495
|
+
invoke: () => invokeRegistrationCallable(callable, registrar, property, options),
|
|
448
496
|
});
|
|
449
497
|
}
|
|
450
498
|
}
|
|
@@ -453,10 +501,9 @@ function registrationInvocations(registrar, descriptor, profile, options) {
|
|
|
453
501
|
|
|
454
502
|
function invokeRegistrationCallable(callable, registrar, property, options) {
|
|
455
503
|
const event = syntheticRegistrationEvent(registrar, property, options);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
return callable(event);
|
|
504
|
+
const inputFactory = options.registrationProbeInputs?.[registrar]?.[property] ?? defaultSyntheticRegistrationProbeInputs[registrar]?.[property];
|
|
505
|
+
const args = inputFactory ? inputFactory(event, options) : [event];
|
|
506
|
+
return callable(...args);
|
|
460
507
|
}
|
|
461
508
|
|
|
462
509
|
function syntheticRegistrationEvent(registrar, property, options) {
|
|
@@ -478,6 +525,103 @@ function syntheticRegistrationEvent(registrar, property, options) {
|
|
|
478
525
|
};
|
|
479
526
|
}
|
|
480
527
|
|
|
528
|
+
function toolRunProbeArgs(event) {
|
|
529
|
+
return [
|
|
530
|
+
event.params,
|
|
531
|
+
{
|
|
532
|
+
source: event.source,
|
|
533
|
+
toolName: event.toolName,
|
|
534
|
+
toolCallId: event.toolCall.id,
|
|
535
|
+
signal: new AbortController().signal,
|
|
536
|
+
logger: console,
|
|
537
|
+
},
|
|
538
|
+
];
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function toolExecuteProbeArgs(event) {
|
|
542
|
+
return [event.toolCall.id, event.params, new AbortController().signal, () => undefined];
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function httpRouteProbeArgs(event) {
|
|
546
|
+
return [
|
|
547
|
+
{
|
|
548
|
+
method: "POST",
|
|
549
|
+
path: "/fixture/probe",
|
|
550
|
+
url: "http://127.0.0.1/fixture/probe",
|
|
551
|
+
headers: event.headers,
|
|
552
|
+
body: event.body,
|
|
553
|
+
json: async () => event.body,
|
|
554
|
+
text: async () => JSON.stringify(event.body),
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
source: event.source,
|
|
558
|
+
params: event.params,
|
|
559
|
+
logger: console,
|
|
560
|
+
},
|
|
561
|
+
];
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function commandProbeArgs(event) {
|
|
565
|
+
return [
|
|
566
|
+
event.input,
|
|
567
|
+
{
|
|
568
|
+
source: event.source,
|
|
569
|
+
signal: new AbortController().signal,
|
|
570
|
+
logger: console,
|
|
571
|
+
},
|
|
572
|
+
];
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function gatewayProbeArgs(event) {
|
|
576
|
+
return [
|
|
577
|
+
{
|
|
578
|
+
params: event.params,
|
|
579
|
+
body: event.body,
|
|
580
|
+
headers: event.headers,
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
source: event.source,
|
|
584
|
+
logger: console,
|
|
585
|
+
},
|
|
586
|
+
];
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function interactiveProbeArgs(event) {
|
|
590
|
+
return [
|
|
591
|
+
{
|
|
592
|
+
id: "interaction-fixture",
|
|
593
|
+
payload: event.body,
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
source: event.source,
|
|
597
|
+
logger: console,
|
|
598
|
+
},
|
|
599
|
+
];
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function lifecycleProbeArgs(event) {
|
|
603
|
+
return [
|
|
604
|
+
{
|
|
605
|
+
source: event.source,
|
|
606
|
+
logger: console,
|
|
607
|
+
signal: new AbortController().signal,
|
|
608
|
+
},
|
|
609
|
+
];
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function speechProbeArgs(event) {
|
|
613
|
+
return [
|
|
614
|
+
{
|
|
615
|
+
text: "fixture speech request",
|
|
616
|
+
voice: "fixture",
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
source: event.source,
|
|
620
|
+
logger: console,
|
|
621
|
+
},
|
|
622
|
+
];
|
|
623
|
+
}
|
|
624
|
+
|
|
481
625
|
async function runProbe({ captureIndex, kind, seam, label, invoke }) {
|
|
482
626
|
try {
|
|
483
627
|
const output = await invoke();
|
package/src/workspace-plan.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { mkdir, readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { renderPaddedMarkdownTable, writeJsonMarkdownArtifacts } from "./artifacts.js";
|
|
5
6
|
import { buildColdImportReadiness } from "./cold-import-readiness.js";
|
|
6
7
|
import { normalizeRepoPath, posixJoin, slugForArtifact } from "./path-utils.js";
|
|
7
8
|
|
|
8
9
|
export const defaultWorkspacePlanOptions = {
|
|
9
|
-
captureScript:
|
|
10
|
+
captureScript: null,
|
|
10
11
|
optInEnv: "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1",
|
|
11
12
|
resultsRoot: ".plugin-inspector/results",
|
|
12
|
-
syntheticProbeScript:
|
|
13
|
+
syntheticProbeScript: null,
|
|
13
14
|
workspaceRoot: ".plugin-inspector/workspaces",
|
|
14
15
|
};
|
|
15
16
|
|
|
@@ -450,12 +451,23 @@ function runCommand(packageManager, script) {
|
|
|
450
451
|
|
|
451
452
|
function captureCommand(settings, fixtureId, entrypoint, workspacePath) {
|
|
452
453
|
const loader = entrypoint.blockers.some((blocker) => blocker.code === "ts-loader-required") ? " --import tsx" : "";
|
|
453
|
-
|
|
454
|
+
const script = helperScript(settings, workspacePath, settings.captureScript, "capture-cli.js");
|
|
455
|
+
return `${settings.optInEnv} node${loader} ${script} ${entrypoint.specifier} --mock-sdk --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "capture")}`;
|
|
454
456
|
}
|
|
455
457
|
|
|
456
458
|
function syntheticProbeCommand(settings, fixtureId, entrypoint, workspacePath) {
|
|
457
459
|
const loader = entrypoint.blockers.some((blocker) => blocker.code === "ts-loader-required") ? " --import tsx" : "";
|
|
458
|
-
|
|
460
|
+
const script = helperScript(settings, workspacePath, settings.syntheticProbeScript, "synthetic-probes-cli.js");
|
|
461
|
+
return `${settings.optInEnv} node${loader} ${script} --entrypoint ${entrypoint.specifier} --mock-sdk --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "synthetic")}`;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function helperScript(settings, workspacePath, configuredScript, helperFileName) {
|
|
465
|
+
if (configuredScript) {
|
|
466
|
+
return configuredScript;
|
|
467
|
+
}
|
|
468
|
+
const helperPath = fileURLToPath(new URL(`./${helperFileName}`, import.meta.url));
|
|
469
|
+
const workspaceFsPath = path.join(settings.rootDir, workspacePath);
|
|
470
|
+
return repoRelative(path.relative(workspaceFsPath, helperPath));
|
|
459
471
|
}
|
|
460
472
|
|
|
461
473
|
function targetOpenClawWorkspacePath(settings, fixtureId, targetOpenClawPath) {
|