@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.
@@ -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
+ }
@@ -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
- if (typeof descriptor[property] === "function") {
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(descriptor[property], registrar, property, options),
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
- if (property === "execute") {
457
- return callable("call-fixture", event.params, new AbortController().signal, () => undefined);
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();
@@ -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: "plugin-inspector-capture",
10
+ captureScript: null,
10
11
  optInEnv: "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1",
11
12
  resultsRoot: ".plugin-inspector/results",
12
- syntheticProbeScript: "plugin-inspector-synthetic-probes",
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
- return `${settings.optInEnv} node${loader} ${settings.captureScript} ${entrypoint.specifier} --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "capture")}`;
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
- return `${settings.optInEnv} node${loader} ${settings.syntheticProbeScript} --entrypoint ${entrypoint.specifier} --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "synthetic")}`;
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) {