@openclaw/plugin-inspector 0.3.5 → 0.3.7

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.
@@ -55,8 +55,11 @@ export function validatePlatformProbes(report, options = {}) {
55
55
  errors.push("all TypeScript loader entrypoints must track a Jiti fallback candidate");
56
56
  }
57
57
  for (const entrypoint of report.entrypoints) {
58
- if (entrypoint.loaderPrimary === "tsx" && (!entrypoint.captureUsesTsx || !entrypoint.syntheticUsesTsx)) {
59
- errors.push(`${entrypoint.id}: tsx loader strategy is not reflected in capture and synthetic commands`);
58
+ if (
59
+ entrypoint.loaderPrimary === "tsx" &&
60
+ (!entrypoint.captureUsesTypeScriptLoader || !entrypoint.syntheticUsesTypeScriptLoader)
61
+ ) {
62
+ errors.push(`${entrypoint.id}: TypeScript loader strategy is not reflected in capture and synthetic commands`);
60
63
  }
61
64
  }
62
65
  return errors;
@@ -94,9 +97,21 @@ export function renderPlatformProbesMarkdown(report, options = {}) {
94
97
  entrypoint.loaderAlternatives.join(", ") || "-",
95
98
  entrypoint.captureUsesTsx ? "yes" : "no",
96
99
  entrypoint.syntheticUsesTsx ? "yes" : "no",
100
+ entrypoint.captureUsesMockSdk ? "yes" : "no",
101
+ entrypoint.syntheticUsesMockSdk ? "yes" : "no",
97
102
  entrypoint.entrypoint,
98
103
  ]),
99
- ["Fixture", "Status", "Primary", "Alternatives", "Capture TSX", "Synthetic TSX", "Entrypoint"],
104
+ [
105
+ "Fixture",
106
+ "Status",
107
+ "Primary",
108
+ "Alternatives",
109
+ "Capture TSX",
110
+ "Synthetic TSX",
111
+ "Capture Mock SDK",
112
+ "Synthetic Mock SDK",
113
+ "Entrypoint",
114
+ ],
100
115
  ),
101
116
  "",
102
117
  "## Portability Findings",
@@ -137,6 +152,10 @@ export function renderPlatformProbesMarkdown(report, options = {}) {
137
152
  function summarizeEntrypoint(fixtureId, entrypoint) {
138
153
  const captureStep = entrypoint.steps.find((step) => step.kind === "capture");
139
154
  const syntheticStep = entrypoint.steps.find((step) => step.kind === "synthetic-probe");
155
+ const captureUsesTsx = Boolean(captureStep?.command.includes("--import tsx"));
156
+ const syntheticUsesTsx = Boolean(syntheticStep?.command.includes("--import tsx"));
157
+ const captureUsesMockSdk = Boolean(captureStep?.command.includes("--mock-sdk"));
158
+ const syntheticUsesMockSdk = Boolean(syntheticStep?.command.includes("--mock-sdk"));
140
159
  return {
141
160
  fixture: fixtureId,
142
161
  id: entrypoint.id,
@@ -148,8 +167,12 @@ function summarizeEntrypoint(fixtureId, entrypoint) {
148
167
  loaderAlternatives: entrypoint.loaderStrategy.alternatives,
149
168
  capturePlanned: Boolean(captureStep),
150
169
  syntheticProbePlanned: Boolean(syntheticStep),
151
- captureUsesTsx: Boolean(captureStep?.command.includes("--import tsx")),
152
- syntheticUsesTsx: Boolean(syntheticStep?.command.includes("--import tsx")),
170
+ captureUsesTsx,
171
+ syntheticUsesTsx,
172
+ captureUsesMockSdk,
173
+ syntheticUsesMockSdk,
174
+ captureUsesTypeScriptLoader: captureUsesTsx || captureUsesMockSdk,
175
+ syntheticUsesTypeScriptLoader: syntheticUsesTsx || syntheticUsesMockSdk,
153
176
  };
154
177
  }
155
178
 
@@ -260,7 +283,7 @@ function buildRecommendations(portabilityFindings, entrypoints) {
260
283
  if (entrypoints.some((entrypoint) => entrypoint.loaderPrimary === "tsx")) {
261
284
  recommendations.push({
262
285
  area: "loader",
263
- action: "keep tsx as the source-entrypoint smoke path, add a Jiti execution lane before treating TS plugin source compatibility as covered",
286
+ action: "keep mock-SDK TypeScript capture green, add a real host-loader/Jiti lane before treating TS plugin source compatibility as covered",
264
287
  });
265
288
  }
266
289
  if (portabilityFindings.some((finding) => finding.riskCodes.includes("rsync-required"))) {
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ const packageJsonPath = path.resolve(process.cwd(), "package.json");
6
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
7
+ let changed = false;
8
+
9
+ for (const [name, specifier] of Object.entries(packageJson.devDependencies ?? {})) {
10
+ if (typeof specifier === "string" && specifier.startsWith("workspace:")) {
11
+ delete packageJson.devDependencies[name];
12
+ changed = true;
13
+ }
14
+ }
15
+
16
+ if (packageJson.devDependencies && Object.keys(packageJson.devDependencies).length === 0) {
17
+ delete packageJson.devDependencies;
18
+ changed = true;
19
+ }
20
+
21
+ if (changed) {
22
+ await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
23
+ }
package/src/report.js CHANGED
@@ -5,6 +5,7 @@ import { buildContractProbes } from "./contract-probes.js";
5
5
  import { classifyCompatibilityFixture } from "./fixture-summary.js";
6
6
  import { buildIssues, summarizeIssueClasses } from "./issues.js";
7
7
  import { sanitizeReportArtifact } from "./report-sanitizer.js";
8
+ import { applyRuntimeExecutionCoverage } from "./runtime-reconciliation.js";
8
9
 
9
10
  export function buildReport({ config, inspections, failures = [], generatedAt = "deterministic" }) {
10
11
  const inspectionById = new Map(inspections.map((inspection) => [inspection.id, inspection]));
@@ -140,6 +141,10 @@ export async function buildCompatibilityReport(options = {}) {
140
141
  decisions,
141
142
  });
142
143
 
144
+ const runtimeCoverage = applyRuntimeExecutionCoverage({
145
+ findings: [...warnings, ...suggestions],
146
+ executionResults: options.executionResults,
147
+ });
143
148
  const issues = buildIssues({
144
149
  breakages,
145
150
  warnings,
@@ -149,6 +154,8 @@ export async function buildCompatibilityReport(options = {}) {
149
154
  });
150
155
  const contractProbes = buildContractProbes({ warnings, suggestions, fixtures: fixtureReports });
151
156
  const issueSummary = summarizeIssueClasses(issues);
157
+ const openIssues = issues.filter((issue) => issue.status !== "runtime-covered");
158
+ const openIssueSummary = summarizeIssueClasses(openIssues);
152
159
 
153
160
  return {
154
161
  generatedAt: options.generatedAt ?? "deterministic",
@@ -163,8 +170,11 @@ export async function buildCompatibilityReport(options = {}) {
163
170
  decisionCount: decisions.length,
164
171
  logCount: logs.length,
165
172
  issueCount: issues.length,
173
+ openIssueCount: openIssues.length,
166
174
  p0IssueCount: issues.filter((issue) => issue.severity === "P0").length,
167
175
  p1IssueCount: issues.filter((issue) => issue.severity === "P1").length,
176
+ openP0IssueCount: openIssues.filter((issue) => issue.severity === "P0").length,
177
+ openP1IssueCount: openIssues.filter((issue) => issue.severity === "P1").length,
168
178
  liveIssueCount: issueSummary["live-issue"],
169
179
  liveP0IssueCount: issues.filter((issue) => issue.issueClass === "live-issue" && issue.severity === "P0").length,
170
180
  compatGapCount: issueSummary["compat-gap"],
@@ -172,6 +182,10 @@ export async function buildCompatibilityReport(options = {}) {
172
182
  inspectorGapCount: issueSummary["inspector-gap"],
173
183
  upstreamIssueCount: issueSummary["upstream-metadata"],
174
184
  fixtureRegressionCount: issueSummary["fixture-regression"],
185
+ openInspectorGapCount: openIssueSummary["inspector-gap"],
186
+ runtimeCoveredIssueCount: runtimeCoverage.coveredFindingCount,
187
+ runtimePartiallyCoveredIssueCount: runtimeCoverage.partiallyCoveredFindingCount,
188
+ runtimeCoverageArtifactCount: runtimeCoverage.coverage.artifactCount,
175
189
  contractProbeCount: contractProbes.length,
176
190
  },
177
191
  fixtures: fixtureReports,
@@ -308,7 +322,11 @@ function topTextFindings(report, limit) {
308
322
  return [
309
323
  ...(report.breakages ?? []).map((finding) => formatTextFinding(finding, "breakage")),
310
324
  ...(report.issues ?? [])
311
- .filter((issue) => issue.status === "blocking" || issue.severity === "P0" || issue.severity === "P1")
325
+ .filter(
326
+ (issue) =>
327
+ issue.status !== "runtime-covered" &&
328
+ (issue.status === "blocking" || issue.severity === "P0" || issue.severity === "P1"),
329
+ )
312
330
  .map((issue) => formatTextFinding(issue, issue.severity ?? "issue")),
313
331
  ...(report.warnings ?? []).map((finding) => formatTextFinding(finding, "warning")),
314
332
  ].slice(0, limit);
@@ -0,0 +1,124 @@
1
+ export function applyRuntimeExecutionCoverage({ findings = [], executionResults } = {}) {
2
+ const coverage = buildRuntimeExecutionCoverage(executionResults);
3
+ let coveredFindingCount = 0;
4
+ let partiallyCoveredFindingCount = 0;
5
+
6
+ for (const finding of findings) {
7
+ const findingCoverage = runtimeCoverageForFinding(finding, coverage);
8
+ if (!findingCoverage) {
9
+ continue;
10
+ }
11
+
12
+ finding.runtimeCoverage = findingCoverage;
13
+ if (findingCoverage.status === "covered") {
14
+ finding.status = "runtime-covered";
15
+ coveredFindingCount += 1;
16
+ } else {
17
+ partiallyCoveredFindingCount += 1;
18
+ }
19
+ }
20
+
21
+ return {
22
+ coverage,
23
+ coveredFindingCount,
24
+ partiallyCoveredFindingCount,
25
+ };
26
+ }
27
+
28
+ export function buildRuntimeExecutionCoverage(executionResults) {
29
+ const fixtures = new Map();
30
+ for (const artifact of executionResults?.artifacts ?? []) {
31
+ if (artifact.kind !== "capture") {
32
+ continue;
33
+ }
34
+
35
+ const fixture = String(artifact.fixture ?? "unknown");
36
+ const fixtureCoverage = ensureFixtureCoverage(fixtures, fixture);
37
+ if (artifact.artifactPath) {
38
+ fixtureCoverage.artifacts.add(artifact.artifactPath);
39
+ }
40
+
41
+ for (const captured of normalizeCaptured(artifact.captured)) {
42
+ fixtureCoverage.captured.add(captured);
43
+ }
44
+ }
45
+
46
+ return {
47
+ fixtures,
48
+ artifactCount: [...fixtures.values()].reduce((sum, fixture) => sum + fixture.artifacts.size, 0),
49
+ };
50
+ }
51
+
52
+ function runtimeCoverageForFinding(finding, coverage) {
53
+ const fixtureCoverage = coverage.fixtures.get(finding.fixture);
54
+ if (!fixtureCoverage || fixtureCoverage.captured.size === 0) {
55
+ return null;
56
+ }
57
+
58
+ const expected = expectedRuntimeCaptureKeys(finding);
59
+ if (expected.length === 0) {
60
+ return null;
61
+ }
62
+
63
+ const captured = expected.filter((item) => fixtureCoverage.captured.has(item));
64
+ if (captured.length === 0) {
65
+ return null;
66
+ }
67
+
68
+ return {
69
+ status: captured.length === expected.length ? "covered" : "partial",
70
+ captured,
71
+ expected,
72
+ artifacts: [...fixtureCoverage.artifacts].sort(),
73
+ };
74
+ }
75
+
76
+ function expectedRuntimeCaptureKeys(finding) {
77
+ const names = evidenceNames(finding.evidence);
78
+ if (finding.code === "registration-capture-gap") {
79
+ return names.map((name) => `registration:${name}`);
80
+ }
81
+ if (finding.code === "runtime-tool-capture") {
82
+ return ["registration:registerTool"];
83
+ }
84
+ if (finding.code === "conversation-access-hook") {
85
+ return names.map((name) => `hook:${name}`);
86
+ }
87
+ return [];
88
+ }
89
+
90
+ function normalizeCaptured(captured) {
91
+ return (captured ?? [])
92
+ .map((item) => {
93
+ if (typeof item === "string") {
94
+ return item;
95
+ }
96
+ if (item && typeof item === "object" && item.kind && item.name) {
97
+ return `${item.kind}:${item.name}`;
98
+ }
99
+ return "";
100
+ })
101
+ .filter(Boolean);
102
+ }
103
+
104
+ function evidenceNames(evidence) {
105
+ return [
106
+ ...new Set(
107
+ (evidence ?? [])
108
+ .map((item) => String(item).split(" @ ")[0]?.trim())
109
+ .filter(Boolean),
110
+ ),
111
+ ];
112
+ }
113
+
114
+ function ensureFixtureCoverage(fixtures, fixture) {
115
+ let fixtureCoverage = fixtures.get(fixture);
116
+ if (!fixtureCoverage) {
117
+ fixtureCoverage = {
118
+ artifacts: new Set(),
119
+ captured: new Set(),
120
+ };
121
+ fixtures.set(fixture, fixtureCoverage);
122
+ }
123
+ return fixtureCoverage;
124
+ }
package/src/sdk-mock.js CHANGED
@@ -254,9 +254,20 @@ export async function createMockSdkPackage(rootDir, options = {}) {
254
254
  )}\n`,
255
255
  "utf8",
256
256
  );
257
- await writeFile(path.join(pluginSdkDir, "index.js"), mockSdkSource(), "utf8");
257
+ const rootExportNames = new Set([
258
+ ...mockSdkExportNames,
259
+ ...(imports.bySpecifier.get("openclaw/plugin-sdk") ?? []),
260
+ ]);
261
+ await writeFile(path.join(pluginSdkDir, "index.js"), mockSdkSource(rootExportNames), "utf8");
258
262
  for (const [subpath, exportNames] of Object.entries(mockSdkSubpathExports)) {
259
- await writeFile(path.join(pluginSdkDir, `${subpath}.js`), mockSdkSubpathSource(exportNames), "utf8");
263
+ const specifier = `openclaw/plugin-sdk/${subpath}`;
264
+ await writeFile(
265
+ path.join(pluginSdkDir, `${subpath}.js`),
266
+ mockSdkSubpathSource(exportNames, imports.bySpecifier.get(specifier) ?? new Set(), {
267
+ zod: subpath === "zod",
268
+ }),
269
+ "utf8",
270
+ );
260
271
  }
261
272
  for (const specifier of imports.openclawSdkSpecifiers) {
262
273
  if (specifier === "openclaw/plugin-sdk") {
@@ -428,6 +439,9 @@ export async function resolve(specifier, context, nextResolve) {
428
439
  const subpath = specifier.slice("openclaw/plugin-sdk/".length);
429
440
  return moduleUrl(path.join(pluginSdkDir, \`\${subpath}.js\`));
430
441
  }
442
+ if (externalMap.has(specifier)) {
443
+ return moduleUrl(externalMap.get(specifier));
444
+ }
431
445
  try {
432
446
  return await nextResolve(specifier, context);
433
447
  } catch (error) {
@@ -531,6 +545,12 @@ function genericExportStatement(name) {
531
545
  if (["createChatChannelPlugin", "createPlugin", "defineChannelPluginEntry", "definePlugin", "definePluginEntry", "defineSetupPluginEntry"].includes(name)) {
532
546
  return name === "definePluginEntry" ? "export { definePluginEntry };" : `export const ${name} = definePluginEntry;`;
533
547
  }
548
+ if (name === "defineBundledChannelEntry") {
549
+ return "export { defineBundledChannelEntry };";
550
+ }
551
+ if (name === "defineBundledChannelSetupEntry") {
552
+ return "export { defineBundledChannelSetupEntry };";
553
+ }
534
554
  if (/^[A-Z].*Schema$/u.test(name)) {
535
555
  return `export const ${name} = createSchema();`;
536
556
  }
@@ -547,9 +567,41 @@ function genericMockRuntimeSource(options = {}) {
547
567
  }
548
568
  return typeof entry === "function" ? { register: entry } : entry;
549
569
  }
570
+
571
+ function defineBundledChannelEntry(entry = {}) {
572
+ return {
573
+ ...entry,
574
+ kind: "bundled-channel-entry",
575
+ register(api) {
576
+ if (api?.registrationMode === "cli-metadata") {
577
+ return entry.registerCliMetadata?.(api);
578
+ }
579
+ if (api?.registrationMode !== "tool-discovery") {
580
+ api?.registerChannel?.({
581
+ id: entry.id,
582
+ name: entry.name,
583
+ description: entry.description,
584
+ plugin: { id: entry.id, name: entry.name },
585
+ });
586
+ }
587
+ entry.registerCliMetadata?.(api);
588
+ return entry.registerFull?.(api);
589
+ },
590
+ };
591
+ }
592
+
593
+ function defineBundledChannelSetupEntry(entry = {}) {
594
+ return {
595
+ ...entry,
596
+ kind: "bundled-channel-setup-entry",
597
+ };
598
+ }
550
599
  ` : ""}
551
600
  function createMockValue(name) {
552
601
  function fn(...args) {
602
+ if (name === "resolvePreferredOpenClawTmpDir") {
603
+ return process.env.TMPDIR || "/tmp";
604
+ }
553
605
  if (name.startsWith("normalize")) {
554
606
  return typeof args[0] === "string" ? args[0] : "";
555
607
  }
@@ -728,7 +780,8 @@ function createTypeNamespace() {
728
780
  `;
729
781
  }
730
782
 
731
- function mockSdkSource() {
783
+ function mockSdkSource(exportNames = mockSdkExportNames) {
784
+ const dynamicExportNames = [...exportNames].filter((name) => !mockSdkExportNames.includes(name));
732
785
  return `function normalizeEntry(entry) {
733
786
  return typeof entry === "function" ? { register: entry } : entry;
734
787
  }
@@ -1538,15 +1591,20 @@ export const OPENAI_RESPONSES_STREAM_HOOKS = buildProviderStreamFamilyHooks("ope
1538
1591
  export const OPENROUTER_THINKING_STREAM_HOOKS = buildProviderStreamFamilyHooks("openrouter-thinking");
1539
1592
  export const TOOL_STREAM_DEFAULT_ON_HOOKS = buildProviderStreamFamilyHooks("tool-stream-default");
1540
1593
  export const pluginSdkMock = true;
1594
+ ${dynamicExportNames.map(genericExportStatement).join("\n")}
1541
1595
 
1542
1596
  export default {
1543
- ${mockSdkExportNames.map((name) => ` ${name},`).join("\n")}
1597
+ ${[...exportNames].map((name) => ` ${name},`).join("\n")}
1544
1598
  };
1545
1599
  `;
1546
1600
  }
1547
1601
 
1548
- function mockSdkSubpathSource(exportNames) {
1549
- return `${exportNames.map((name) => `export { ${name} } from "./index.js";`).join("\n")}
1602
+ function mockSdkSubpathSource(staticExportNames, importedExportNames, options = {}) {
1603
+ const staticNames = new Set(staticExportNames);
1604
+ const dynamicNames = [...importedExportNames].filter((name) => !staticNames.has(name));
1605
+ return `${[...staticNames].map((name) => `export { ${name} } from "./index.js";`).join("\n")}
1606
+ ${dynamicNames.length > 0 ? genericMockRuntimeSource({ includeSdkRuntime: true, zod: options.zod }) : ""}
1607
+ ${dynamicNames.map(genericExportStatement).join("\n")}
1550
1608
  export { default } from "./index.js";
1551
1609
  `;
1552
1610
  }
@@ -11,6 +11,11 @@ export const syntheticRegistrationExecutionProfiles = {
11
11
  callableProperties: [],
12
12
  reason: "entry wrapper metadata is captured before channel runtime execution",
13
13
  },
14
+ defineBundledChannelEntry: {
15
+ mode: "metadata-only",
16
+ callableProperties: [],
17
+ reason: "bundled channel entry metadata is captured before channel runtime execution",
18
+ },
14
19
  definePluginEntry: {
15
20
  mode: "metadata-only",
16
21
  callableProperties: [],
@@ -26,6 +31,11 @@ export const syntheticRegistrationExecutionProfiles = {
26
31
  callableProperties: [],
27
32
  reason: "agent harness factories are captured as registration metadata; agent runtime execution remains isolated opt-in",
28
33
  },
34
+ registerAgentEventSubscription: {
35
+ mode: "metadata-only",
36
+ callableProperties: [],
37
+ reason: "agent event subscriptions are captured as registration metadata before agent event dispatch",
38
+ },
29
39
  registerAgentToolResultMiddleware: {
30
40
  mode: "metadata-only",
31
41
  callableProperties: [],
@@ -69,6 +79,11 @@ export const syntheticRegistrationExecutionProfiles = {
69
79
  callableProperties: [],
70
80
  reason: "context engine factories are captured as registration metadata; engine startup remains isolated opt-in",
71
81
  },
82
+ registerControlUiDescriptor: {
83
+ mode: "metadata-only",
84
+ callableProperties: [],
85
+ reason: "control UI descriptors are captured as registration metadata before UI composition",
86
+ },
72
87
  registerDetachedTaskRuntime: {
73
88
  mode: "metadata-only",
74
89
  callableProperties: [],
@@ -156,6 +171,11 @@ export const syntheticRegistrationExecutionProfiles = {
156
171
  callableProperties: [],
157
172
  reason: "node host commands are captured as registration metadata before host process execution",
158
173
  },
174
+ registerNodeInvokePolicy: {
175
+ mode: "metadata-only",
176
+ callableProperties: [],
177
+ reason: "node invoke policies are captured as registration metadata before host authorization checks",
178
+ },
159
179
  registerProvider: {
160
180
  mode: "metadata-only",
161
181
  callableProperties: [],
@@ -176,6 +196,11 @@ export const syntheticRegistrationExecutionProfiles = {
176
196
  callableProperties: [],
177
197
  reason: "reload handlers are captured as registration metadata before runtime reload execution",
178
198
  },
199
+ registerRuntimeLifecycle: {
200
+ mode: "metadata-only",
201
+ callableProperties: [],
202
+ reason: "runtime lifecycle handlers are captured as registration metadata before lifecycle dispatch",
203
+ },
179
204
  registerSecurityAuditCollector: {
180
205
  mode: "metadata-only",
181
206
  callableProperties: [],
@@ -186,6 +211,16 @@ export const syntheticRegistrationExecutionProfiles = {
186
211
  callableProperties: ["start", "stop", "dispose"],
187
212
  option: "includeLifecycle",
188
213
  },
214
+ registerSessionExtension: {
215
+ mode: "metadata-only",
216
+ callableProperties: [],
217
+ reason: "session extensions are captured as registration metadata before session runtime execution",
218
+ },
219
+ registerSessionSchedulerJob: {
220
+ mode: "metadata-only",
221
+ callableProperties: [],
222
+ reason: "session scheduler jobs are captured as registration metadata before scheduler execution",
223
+ },
189
224
  registerSpeechProvider: {
190
225
  mode: "provider-opt-in",
191
226
  callableProperties: ["speak", "synthesize", "tts"],
@@ -195,6 +230,11 @@ export const syntheticRegistrationExecutionProfiles = {
195
230
  mode: "direct",
196
231
  callableProperties: ["run", "handler", "execute"],
197
232
  },
233
+ registerToolMetadata: {
234
+ mode: "metadata-only",
235
+ callableProperties: [],
236
+ reason: "tool metadata descriptors are captured as registration metadata before tool runtime execution",
237
+ },
198
238
  registerTextTransforms: {
199
239
  mode: "metadata-only",
200
240
  callableProperties: [],
@@ -205,6 +245,11 @@ export const syntheticRegistrationExecutionProfiles = {
205
245
  callableProperties: [],
206
246
  reason: "video generation providers are captured as registration metadata before provider runtime execution",
207
247
  },
248
+ registerTrustedToolPolicy: {
249
+ mode: "metadata-only",
250
+ callableProperties: [],
251
+ reason: "trusted tool policies are captured as registration metadata before trust-policy enforcement",
252
+ },
208
253
  registerWebFetchProvider: {
209
254
  mode: "metadata-only",
210
255
  callableProperties: [],
@@ -71,6 +71,7 @@ export async function buildWorkspacePlan(options = {}) {
71
71
  installStepCount: allSteps.filter((step) => step.kind === "install").length,
72
72
  auditStepCount: allSteps.filter((step) => step.kind === "audit").length,
73
73
  buildStepCount: allSteps.filter((step) => step.kind === "build").length,
74
+ pruneDevWorkspaceDependencyStepCount: allSteps.filter((step) => step.kind === "prune-dev-workspace-deps").length,
74
75
  artifactStepCount: allSteps.filter((step) => step.kind === "prepare-artifacts").length,
75
76
  captureStepCount: allSteps.filter((step) => step.kind === "capture").length,
76
77
  syntheticProbeStepCount: allSteps.filter((step) => step.kind === "synthetic-probe").length,
@@ -179,6 +180,7 @@ export function renderWorkspacePlanMarkdown(plan, options = {}) {
179
180
  ["Artifact dirs", plan.summary.artifactStepCount],
180
181
  ["Install steps", plan.summary.installStepCount],
181
182
  ["Audit steps", plan.summary.auditStepCount],
183
+ ["Prune dev workspace dependency steps", plan.summary.pruneDevWorkspaceDependencyStepCount],
182
184
  ["Build steps", plan.summary.buildStepCount],
183
185
  ["Capture steps", plan.summary.captureStepCount],
184
186
  ["Synthetic probe steps", plan.summary.syntheticProbeStepCount],
@@ -248,6 +250,14 @@ async function buildEntrypointPlan({ fixtureId, entrypoint, packageSummary, pack
248
250
  }
249
251
 
250
252
  if (requiredCapabilities.includes("dependency-install")) {
253
+ if (hasWorkspaceProtocolDevDependencies(packageJson)) {
254
+ steps.push({
255
+ kind: "prune-dev-workspace-deps",
256
+ command: `node ${helperScript(settings, workspacePath, settings.pruneWorkspaceDevDepsScript, "prune-workspace-dev-deps-cli.js")}`,
257
+ cwd: workspacePath,
258
+ reason: "remove workspace: devDependencies from the isolated runtime install; the mock SDK supplies OpenClaw host imports",
259
+ });
260
+ }
251
261
  steps.push({
252
262
  kind: "install",
253
263
  command: installCommand(packageManager),
@@ -319,6 +329,7 @@ function workspaceSettings(options) {
319
329
  resultsRoot: repoRelative(options.resultsRoot ?? defaultWorkspacePlanOptions.resultsRoot),
320
330
  rootDir: path.resolve(options.rootDir ?? process.cwd()),
321
331
  syntheticProbeScript: options.syntheticProbeScript ?? defaultWorkspacePlanOptions.syntheticProbeScript,
332
+ pruneWorkspaceDevDepsScript: options.pruneWorkspaceDevDepsScript,
322
333
  workspaceRoot: repoRelative(options.workspaceRoot ?? defaultWorkspacePlanOptions.workspaceRoot),
323
334
  };
324
335
  }
@@ -377,6 +388,12 @@ function hasHostLinkedOpenClawDependency(packageSummary) {
377
388
  ].includes("openclaw");
378
389
  }
379
390
 
391
+ function hasWorkspaceProtocolDevDependencies(packageJson) {
392
+ return Object.values(packageJson.devDependencies ?? {}).some(
393
+ (value) => typeof value === "string" && value.startsWith("workspace:"),
394
+ );
395
+ }
396
+
380
397
  function detectPackageManager(rootDir, packageDir, packageJson) {
381
398
  const declared = typeof packageJson.packageManager === "string" ? packageJson.packageManager.split("@")[0] : null;
382
399
  if (declared) {
@@ -458,15 +475,13 @@ function runCommand(packageManager, script) {
458
475
  }
459
476
 
460
477
  function captureCommand(settings, fixtureId, entrypoint, workspacePath) {
461
- const loader = entrypoint.blockers.some((blocker) => blocker.code === "ts-loader-required") ? " --import tsx" : "";
462
478
  const script = helperScript(settings, workspacePath, settings.captureScript, "capture-cli.js");
463
- return `${settings.optInEnv} node${loader} ${script} ${entrypoint.specifier} --mock-sdk --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "capture")}`;
479
+ return `${settings.optInEnv} node ${script} ${entrypoint.specifier} --mock-sdk --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "capture")}`;
464
480
  }
465
481
 
466
482
  function syntheticProbeCommand(settings, fixtureId, entrypoint, workspacePath) {
467
- const loader = entrypoint.blockers.some((blocker) => blocker.code === "ts-loader-required") ? " --import tsx" : "";
468
483
  const script = helperScript(settings, workspacePath, settings.syntheticProbeScript, "synthetic-probes-cli.js");
469
- return `${settings.optInEnv} node${loader} ${script} --entrypoint ${entrypoint.specifier} --mock-sdk --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "synthetic")}`;
484
+ return `${settings.optInEnv} node ${script} --entrypoint ${entrypoint.specifier} --mock-sdk --output ${workspaceArtifactPath(settings, fixtureId, entrypoint, workspacePath, "synthetic")}`;
470
485
  }
471
486
 
472
487
  function helperScript(settings, workspacePath, configuredScript, helperFileName) {