@openclaw/plugin-inspector 0.3.2 → 0.3.4

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 CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.4 - 2026-04-29
6
+
7
+ ### Fixed
8
+
9
+ - Separate executor-covered platform portability findings from residual findings so downstream structured runners can keep reports blocking only on unhandled risks.
10
+ - Sanitize absolute target OpenClaw paths from generated report artifacts and JSON CLI output.
11
+ - Normalize the dependency-install inspector finding title to use isolated-workspace wording.
12
+ - Treat `openclaw` package dependencies as host-linked workspace inputs instead of isolated dependency-install blockers.
13
+
14
+ ## 0.3.3 - 2026-04-28
15
+
16
+ ### Fixed
17
+
18
+ - Classify generated kitchen-sink public registrar coverage in synthetic probe plans so new API-surface fixtures do not fail as unknown execution profiles.
19
+
5
20
  ## 0.3.2 - 2026-04-28
6
21
 
7
22
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/plugin-inspector",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "private": false,
5
5
  "description": "Offline compatibility inspector for OpenClaw plugins.",
6
6
  "type": "module",
package/src/advanced.js CHANGED
@@ -43,6 +43,7 @@ export {
43
43
  } from "./ci-outputs.js";
44
44
  export {
45
45
  buildContractProbes,
46
+ compatRecordForIssueCode,
46
47
  contractProbeRules,
47
48
  probePriority,
48
49
  } from "./contract-probes.js";
@@ -170,6 +171,7 @@ export {
170
171
  classifyCompatRecordCoverage,
171
172
  renderMarkdownReport,
172
173
  renderTextSummary,
174
+ sanitizeReportArtifact,
173
175
  writeCompatibilityReport,
174
176
  writeReport,
175
177
  } from "./report.js";
package/src/cli.js CHANGED
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import {
4
4
  loadPluginConfig,
5
5
  renderTextSummary,
6
+ sanitizeReportArtifact,
6
7
  runPluginCheck,
7
8
  } from "./index.js";
8
9
  import {
@@ -90,7 +91,7 @@ async function runCheck(commandArgs) {
90
91
  });
91
92
 
92
93
  if (json) {
93
- console.log(JSON.stringify(report, null, 2));
94
+ console.log(JSON.stringify(sanitizeReportArtifact(report), null, 2));
94
95
  } else {
95
96
  console.log(renderTextSummary(report, { artifacts: paths }));
96
97
  }
@@ -3,6 +3,8 @@ import path from "node:path";
3
3
  import { renderPaddedMarkdownTable, writeJsonMarkdownArtifacts } from "./artifacts.js";
4
4
  import { slugForArtifact } from "./path-utils.js";
5
5
 
6
+ const hostLinkedRuntimeDependencies = new Set(["openclaw"]);
7
+
6
8
  export function buildColdImportReadiness(options = {}) {
7
9
  const report = options.report;
8
10
  if (!report) {
@@ -182,7 +184,7 @@ function classifyEntrypointReadiness({ fixture, packageSummary, entrypoint, root
182
184
  ...(packageSummary.dependencies ?? []),
183
185
  ...(packageSummary.peerDependencies ?? []),
184
186
  ...(packageSummary.optionalDependencies ?? []),
185
- ]);
187
+ ]).filter((dependency) => !hostLinkedRuntimeDependencies.has(dependency));
186
188
  if (entrypoint.exists && runtimeDependencies.length > 0) {
187
189
  blockers.push({
188
190
  code: "dependency-install-required",
@@ -1,4 +1,5 @@
1
1
  import { renderPaddedMarkdownTable } from "./artifacts.js";
2
+ import { sanitizeReportArtifact } from "./report-sanitizer.js";
2
3
 
3
4
  const defaultSeverityLabels = {
4
5
  P0: "P0",
@@ -8,6 +9,7 @@ const defaultSeverityLabels = {
8
9
  };
9
10
 
10
11
  export function renderCompatibilityMarkdownReport(report, options = {}) {
12
+ report = sanitizeReportArtifact(report, options);
11
13
  return [
12
14
  `# ${options.title ?? "OpenClaw Plugin Compatibility Report"}`,
13
15
  "",
@@ -127,6 +129,7 @@ export function renderCompatibilityMarkdownReport(report, options = {}) {
127
129
  }
128
130
 
129
131
  export function renderCompatibilityIssuesReport(report, options = {}) {
132
+ report = sanitizeReportArtifact(report, options);
130
133
  return [
131
134
  `# ${options.title ?? "OpenClaw Plugin Issue Findings"}`,
132
135
  "",
@@ -106,6 +106,20 @@ export const contractProbeRules = {
106
106
  },
107
107
  };
108
108
 
109
+ const openClawOwnedProbeIssueCodes = new Set([
110
+ "before-tool-call-probe",
111
+ "channel-contract-probe",
112
+ "conversation-access-hook",
113
+ "registration-capture-gap",
114
+ ]);
115
+
116
+ export function compatRecordForIssueCode(code) {
117
+ if (!openClawOwnedProbeIssueCodes.has(code)) {
118
+ return undefined;
119
+ }
120
+ return contractProbeRules[code]?.id;
121
+ }
122
+
109
123
  export function buildContractProbes({ warnings = [], suggestions = [], fixtures = [] }) {
110
124
  const fixtureById = new Map(fixtures.map((fixture) => [fixture.id, fixture]));
111
125
  const probes = [];
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { readdir } from "node:fs/promises";
3
3
  import path from "node:path";
4
+ import { compatRecordForIssueCode } from "./contract-probes.js";
4
5
  import { readJsonFile } from "./json-file.js";
5
6
 
6
7
  const conversationAccessHooks = new Set(["agent_end", "llm_input", "llm_output"]);
@@ -17,6 +18,7 @@ const channelRegistrations = new Set([
17
18
  "defineChannelPluginEntry",
18
19
  "registerChannel",
19
20
  ]);
21
+ const hostLinkedRuntimeDependencies = new Set(["openclaw"]);
20
22
 
21
23
  export async function buildCompatibilityFixtureReport({ fixture, inspection, checkoutPath, sourceRoot, rootDir = process.cwd() }) {
22
24
  const pluginManifests = await readPluginManifests({ checkoutPath, sourceRoot, rootDir });
@@ -278,7 +280,7 @@ export function classifyPackageContracts({ fixture, inspection, fixtureReport })
278
280
  ...packageSummary.dependencies,
279
281
  ...packageSummary.peerDependencies,
280
282
  ...packageSummary.optionalDependencies,
281
- ]);
283
+ ]).filter((dependency) => !hostLinkedRuntimeDependencies.has(dependency));
282
284
  if (packageSummary.openclaw?.entrypoints.length > 0 && runtimeDependencies.length > 0) {
283
285
  suggestions.push({
284
286
  fixture: fixture.id,
@@ -399,6 +401,7 @@ export function classifyCompatibilityFixture({ fixture, inspection, fixtureRepor
399
401
  level: "warning",
400
402
  message: "fixture observes raw model or conversation content and needs privacy-boundary contract probes",
401
403
  evidence: detailEvidence(conversationHookDetails),
404
+ compatRecord: compatRecordForIssueCode("conversation-access-hook"),
402
405
  });
403
406
  decisions.push({
404
407
  fixture: fixture.id,
@@ -459,6 +462,7 @@ export function classifyCompatibilityFixture({ fixture, inspection, fixtureRepor
459
462
  level: "suggestion",
460
463
  message: "future inspector capture API should record lifecycle, route, gateway, command, and interactive registrations",
461
464
  evidence: detailEvidence(captureGapRegistrationDetails),
465
+ compatRecord: compatRecordForIssueCode("registration-capture-gap"),
462
466
  });
463
467
  decisions.push({
464
468
  fixture: fixture.id,
@@ -477,6 +481,7 @@ export function classifyCompatibilityFixture({ fixture, inspection, fixtureRepor
477
481
  level: "suggestion",
478
482
  message: "add contract probes for before_tool_call terminal, block, and approval semantics",
479
483
  evidence: detailEvidence(hookDetails),
484
+ compatRecord: compatRecordForIssueCode("before-tool-call-probe"),
480
485
  });
481
486
  decisions.push({
482
487
  fixture: fixture.id,
@@ -500,6 +505,7 @@ export function classifyCompatibilityFixture({ fixture, inspection, fixtureRepor
500
505
  level: "suggestion",
501
506
  message: "add channel envelope, config-schema, and runtime metadata probes",
502
507
  evidence: detailEvidence(channelRegistrationDetails),
508
+ compatRecord: compatRecordForIssueCode("channel-contract-probe"),
503
509
  });
504
510
  decisions.push({
505
511
  fixture: fixture.id,
package/src/index.js CHANGED
@@ -53,11 +53,13 @@ export const staticInspection = Object.freeze({
53
53
  export const reports = Object.freeze({
54
54
  renderMarkdown: reportApi.renderMarkdownReport,
55
55
  renderTextSummary: pluginApi.renderTextSummary,
56
+ sanitizeArtifact: reportApi.sanitizeReportArtifact,
56
57
  write: reportApi.writeReport,
57
58
  issueId: issuesApi.issueId,
58
59
  classifyIssueFinding: issuesApi.classifyIssueFinding,
59
60
  knownIssueCodes: issuesApi.knownIssueCodes,
60
61
  openClawTargetPathCandidates: openClawTargetApi.openClawTargetPathCandidates,
62
+ readOpenClawTargetSurface: openClawTargetApi.readOpenClawTargetSurface,
61
63
  });
62
64
 
63
65
  export const contracts = Object.freeze({
@@ -200,7 +202,7 @@ export {
200
202
  } from "./import-loop-profile.js";
201
203
  export { classifyIssueFinding, issueId, knownIssueCodes } from "./issues.js";
202
204
  export { inspectFixtureSet, inspectPlugin, inspectSourceText } from "./inspector.js";
203
- export { openClawTargetPathCandidates } from "./openclaw-target.js";
205
+ export { openClawTargetPathCandidates, readOpenClawTargetSurface } from "./openclaw-target.js";
204
206
  export {
205
207
  buildProfileDiff,
206
208
  defaultProfileDiffOptions,
@@ -216,7 +218,7 @@ export {
216
218
  validateRefDiff,
217
219
  writeRefDiff,
218
220
  } from "./ref-diff.js";
219
- export { renderMarkdownReport, writeReport } from "./report.js";
221
+ export { renderMarkdownReport, sanitizeReportArtifact, writeReport } from "./report.js";
220
222
  export {
221
223
  buildRuntimeProfile,
222
224
  defaultRuntimeProfileCommands,
package/src/issues.js CHANGED
@@ -119,7 +119,7 @@ export const issueMetadataByCode = {
119
119
  severity: "P2",
120
120
  owner: "inspector",
121
121
  decision: "inspector-follow-up",
122
- title: "cold import requires isolated dependency installation",
122
+ title: "cold import requires dependency installation in an isolated workspace",
123
123
  },
124
124
  "package-entrypoint-missing": {
125
125
  severity: "P1",
@@ -12,13 +12,11 @@ export function buildPlatformProbes(options = {}) {
12
12
  const entrypoints = plan.fixtures.flatMap((fixture) =>
13
13
  fixture.entrypoints.map((entrypoint) => summarizeEntrypoint(fixture.id, entrypoint)),
14
14
  );
15
- const portabilityFindings = plan.fixtures.flatMap((fixture) =>
16
- fixture.entrypoints.flatMap((entrypoint) =>
17
- entrypoint.steps
18
- .map((step) => summarizeStep(fixture.id, entrypoint, step))
19
- .filter((finding) => finding.riskCodes.length > 0),
20
- ),
15
+ const stepFindings = plan.fixtures.flatMap((fixture) =>
16
+ fixture.entrypoints.flatMap((entrypoint) => entrypoint.steps.map((step) => summarizeStep(fixture.id, entrypoint, step, options.stepCoverage))),
21
17
  );
18
+ const portabilityFindings = stepFindings.flatMap((finding) => (finding.residual ? [finding.residual] : []));
19
+ const coveredPortabilityFindings = stepFindings.flatMap((finding) => (finding.covered ? [finding.covered] : []));
22
20
 
23
21
  return {
24
22
  generatedAt: plan.generatedAt,
@@ -31,6 +29,7 @@ export function buildPlatformProbes(options = {}) {
31
29
  jitiAlternativeCount: entrypoints.filter((entrypoint) => entrypoint.loaderAlternatives.includes("jiti")).length,
32
30
  lazyImportProbeCount: entrypoints.filter((entrypoint) => entrypoint.capturePlanned && entrypoint.syntheticProbePlanned).length,
33
31
  portabilityFindingCount: portabilityFindings.length,
32
+ coveredPortabilityFindingCount: coveredPortabilityFindings.length,
34
33
  windowsRiskStepCount: portabilityFindings.filter((finding) => finding.platforms.includes("windows")).length,
35
34
  macosRiskStepCount: portabilityFindings.filter((finding) => finding.platforms.includes("macos")).length,
36
35
  linuxRiskStepCount: portabilityFindings.filter((finding) => finding.platforms.includes("linux")).length,
@@ -38,6 +37,7 @@ export function buildPlatformProbes(options = {}) {
38
37
  },
39
38
  entrypoints,
40
39
  portabilityFindings,
40
+ coveredPortabilityFindings,
41
41
  recommendations: buildRecommendations(portabilityFindings, entrypoints),
42
42
  };
43
43
  }
@@ -112,6 +112,19 @@ export function renderPlatformProbesMarkdown(report, options = {}) {
112
112
  ["Fixture", "Step", "Platforms", "Risks", "Mitigation"],
113
113
  ),
114
114
  "",
115
+ "## Covered Portability Findings",
116
+ "",
117
+ markdownTable(
118
+ (report.coveredPortabilityFindings ?? []).map((finding) => [
119
+ finding.fixture,
120
+ finding.kind,
121
+ finding.platforms.join(", ") || "-",
122
+ finding.riskCodes.join(", "),
123
+ finding.coverage,
124
+ ]),
125
+ ["Fixture", "Step", "Platforms", "Covered Risks", "Coverage"],
126
+ ),
127
+ "",
115
128
  "## Recommendations",
116
129
  "",
117
130
  markdownTable(
@@ -140,16 +153,48 @@ function summarizeEntrypoint(fixtureId, entrypoint) {
140
153
  };
141
154
  }
142
155
 
143
- function summarizeStep(fixtureId, entrypoint, step) {
156
+ function summarizeStep(fixtureId, entrypoint, step, stepCoverage) {
144
157
  const riskCodes = stepRiskCodes(step);
145
- return {
158
+ const coverage = normalizeStepCoverage(stepCoverage?.({ fixture: fixtureId, entrypoint, step, riskCodes }), riskCodes);
159
+ const residualRiskCodes = riskCodes.filter((code) => !coverage.riskCodes.includes(code));
160
+ const common = {
146
161
  fixture: fixtureId,
147
162
  entrypoint: entrypoint.id,
148
163
  kind: step.kind,
149
- platforms: platformsForRiskCodes(riskCodes),
150
- riskCodes,
151
164
  command: step.command,
152
- mitigation: mitigationForRiskCodes(riskCodes),
165
+ };
166
+ return {
167
+ residual:
168
+ residualRiskCodes.length > 0
169
+ ? {
170
+ ...common,
171
+ coveredRiskCodes: coverage.riskCodes,
172
+ platforms: platformsForRiskCodes(residualRiskCodes),
173
+ riskCodes: residualRiskCodes,
174
+ mitigation: mitigationForRiskCodes(residualRiskCodes),
175
+ }
176
+ : null,
177
+ covered:
178
+ coverage.riskCodes.length > 0
179
+ ? {
180
+ ...common,
181
+ coverage: coverage.reason,
182
+ platforms: platformsForRiskCodes(coverage.riskCodes),
183
+ riskCodes: coverage.riskCodes,
184
+ }
185
+ : null,
186
+ };
187
+ }
188
+
189
+ function normalizeStepCoverage(coverage, riskCodes) {
190
+ if (!coverage) {
191
+ return { reason: "", riskCodes: [] };
192
+ }
193
+ const requested = coverage === true ? riskCodes : coverage.coveredRiskCodes ?? coverage.handledRiskCodes ?? coverage.riskCodes ?? coverage;
194
+ const covered = Array.isArray(requested) ? requested : [];
195
+ return {
196
+ reason: typeof coverage.reason === "string" && coverage.reason ? coverage.reason : "covered by isolated workspace executor",
197
+ riskCodes: covered.filter((code) => riskCodes.includes(code)).sort(),
153
198
  };
154
199
  }
155
200
 
@@ -0,0 +1,42 @@
1
+ import path from "node:path";
2
+
3
+ export function sanitizeReportArtifact(report, options = {}) {
4
+ const sensitivePaths = sensitiveOpenClawPaths(report);
5
+ if (sensitivePaths.length === 0) {
6
+ return report;
7
+ }
8
+ const placeholder = options.openclawPathPlaceholder ?? "<OPENCLAW_PATH>";
9
+ return sanitizeValue(report, sensitivePaths, placeholder);
10
+ }
11
+
12
+ function sensitiveOpenClawPaths(report) {
13
+ const targetOpenClaw = report?.targetOpenClaw;
14
+ return unique(
15
+ [targetOpenClaw?.configuredPath, ...(targetOpenClaw?.searchedPaths ?? [])]
16
+ .filter((value) => typeof value === "string" && isAbsolutePath(value))
17
+ .sort((left, right) => right.length - left.length),
18
+ );
19
+ }
20
+
21
+ function sanitizeValue(value, sensitivePaths, placeholder) {
22
+ if (typeof value === "string") {
23
+ return sensitivePaths.reduce((result, sensitivePath) => result.replaceAll(sensitivePath, placeholder), value);
24
+ }
25
+ if (Array.isArray(value)) {
26
+ return value.map((item) => sanitizeValue(item, sensitivePaths, placeholder));
27
+ }
28
+ if (value && typeof value === "object") {
29
+ return Object.fromEntries(
30
+ Object.entries(value).map(([key, entryValue]) => [key, sanitizeValue(entryValue, sensitivePaths, placeholder)]),
31
+ );
32
+ }
33
+ return value;
34
+ }
35
+
36
+ function isAbsolutePath(value) {
37
+ return path.isAbsolute(value) || /^[A-Za-z]:[\\/]/u.test(value) || value.startsWith("\\\\");
38
+ }
39
+
40
+ function unique(values) {
41
+ return [...new Set(values)];
42
+ }
package/src/report.js CHANGED
@@ -4,6 +4,7 @@ import { renderCompatibilityIssuesReport, renderCompatibilityMarkdownReport } fr
4
4
  import { buildContractProbes } from "./contract-probes.js";
5
5
  import { classifyCompatibilityFixture } from "./fixture-summary.js";
6
6
  import { buildIssues, summarizeIssueClasses } from "./issues.js";
7
+ import { sanitizeReportArtifact } from "./report-sanitizer.js";
7
8
 
8
9
  export function buildReport({ config, inspections, failures = [], generatedAt = "deterministic" }) {
9
10
  const inspectionById = new Map(inspections.map((inspection) => [inspection.id, inspection]));
@@ -233,12 +234,13 @@ export async function writeReport(report, options = {}) {
233
234
  const basename = options.basename ?? "plugin-inspector-report";
234
235
  const jsonPath = path.join(outDir, `${basename}.json`);
235
236
  const markdownPath = path.join(outDir, `${basename}.md`);
237
+ const artifactReport = sanitizeReportArtifact(report, options);
236
238
 
237
239
  return writeJsonMarkdownArtifacts({
238
240
  jsonPath,
239
241
  markdownPath,
240
- json: report,
241
- markdown: renderMarkdownReport(report),
242
+ json: artifactReport,
243
+ markdown: renderMarkdownReport(artifactReport),
242
244
  check: options.check,
243
245
  });
244
246
  }
@@ -249,6 +251,7 @@ export async function writeCompatibilityReport(report, options = {}) {
249
251
  const jsonPath = options.jsonPath ?? path.join(outDir, `${basename}.json`);
250
252
  const markdownPath = options.markdownPath ?? path.join(outDir, `${basename}.md`);
251
253
  const issuesPath = options.issuesPath ?? path.join(outDir, options.issuesBasename ?? "plugin-inspector-issues.md");
254
+ const artifactReport = sanitizeReportArtifact(report, options);
252
255
  const markdownOptions = compatibilityRenderOptions(options, {
253
256
  title: options.markdownTitle ?? options.title,
254
257
  ...options.markdownOptions,
@@ -260,14 +263,16 @@ export async function writeCompatibilityReport(report, options = {}) {
260
263
 
261
264
  return writeArtifacts(
262
265
  [
263
- { name: "jsonPath", path: jsonPath, json: report },
264
- { name: "markdownPath", path: markdownPath, markdown: renderCompatibilityMarkdownReport(report, markdownOptions) },
265
- { name: "issuesPath", path: issuesPath, markdown: renderCompatibilityIssuesReport(report, issuesOptions) },
266
+ { name: "jsonPath", path: jsonPath, json: artifactReport },
267
+ { name: "markdownPath", path: markdownPath, markdown: renderCompatibilityMarkdownReport(artifactReport, markdownOptions) },
268
+ { name: "issuesPath", path: issuesPath, markdown: renderCompatibilityIssuesReport(artifactReport, issuesOptions) },
266
269
  ],
267
270
  { check: options.check },
268
271
  );
269
272
  }
270
273
 
274
+ export { sanitizeReportArtifact };
275
+
271
276
  function compatibilityRenderOptions(options, overrides) {
272
277
  const renderOptions = {
273
278
  formatEvidence: options.formatEvidence,
@@ -16,19 +16,64 @@ export const syntheticRegistrationExecutionProfiles = {
16
16
  callableProperties: ["send", "sendMessage", "receive", "handleMessage"],
17
17
  option: "includeChannelRuntime",
18
18
  },
19
+ registerAgentHarness: {
20
+ mode: "metadata-only",
21
+ callableProperties: [],
22
+ reason: "agent harness factories are captured as registration metadata; agent runtime execution remains isolated opt-in",
23
+ },
24
+ registerAgentToolResultMiddleware: {
25
+ mode: "metadata-only",
26
+ callableProperties: [],
27
+ reason: "agent tool-result middleware is captured as registration metadata before tool-result pipeline execution",
28
+ },
29
+ registerAutoEnableProbe: {
30
+ mode: "metadata-only",
31
+ callableProperties: [],
32
+ reason: "auto-enable probes are captured as registration metadata before runtime activation checks",
33
+ },
19
34
  registerCli: {
20
35
  mode: "direct",
21
36
  callableProperties: ["handler", "run", "execute"],
22
37
  },
38
+ registerCliBackend: {
39
+ mode: "metadata-only",
40
+ callableProperties: [],
41
+ reason: "CLI backend descriptors are captured as registration metadata before backend process execution",
42
+ },
23
43
  registerCommand: {
24
44
  mode: "direct",
25
45
  callableProperties: ["handler", "run", "execute"],
26
46
  },
47
+ registerCodexAppServerExtensionFactory: {
48
+ mode: "metadata-only",
49
+ callableProperties: [],
50
+ reason: "Codex app server extension factories are captured as registration metadata before host UI execution",
51
+ },
52
+ registerCompactionProvider: {
53
+ mode: "metadata-only",
54
+ callableProperties: [],
55
+ reason: "compaction providers are captured as registration metadata before compaction runtime execution",
56
+ },
57
+ registerConfigMigration: {
58
+ mode: "metadata-only",
59
+ callableProperties: [],
60
+ reason: "config migrations are captured as registration metadata before mutating stored plugin config",
61
+ },
27
62
  registerContextEngine: {
28
63
  mode: "metadata-only",
29
64
  callableProperties: [],
30
65
  reason: "context engine factories are captured as registration metadata; engine startup remains isolated opt-in",
31
66
  },
67
+ registerDetachedTaskRuntime: {
68
+ mode: "metadata-only",
69
+ callableProperties: [],
70
+ reason: "detached task runtimes are captured as registration metadata before async task execution",
71
+ },
72
+ registerGatewayDiscoveryService: {
73
+ mode: "metadata-only",
74
+ callableProperties: [],
75
+ reason: "gateway discovery services are captured as registration metadata before network discovery execution",
76
+ },
32
77
  registerGatewayMethod: {
33
78
  mode: "direct",
34
79
  callableProperties: ["handler", "run", "execute", "invoke"],
@@ -46,16 +91,91 @@ export const syntheticRegistrationExecutionProfiles = {
46
91
  callableProperties: [],
47
92
  reason: "legacy hook registrar is captured as metadata; hook handlers are probed through hook events",
48
93
  },
94
+ registerImageGenerationProvider: {
95
+ mode: "metadata-only",
96
+ callableProperties: [],
97
+ reason: "image generation providers are captured as registration metadata before provider runtime execution",
98
+ },
49
99
  registerMemoryPromptSection: {
50
100
  mode: "metadata-only",
51
101
  callableProperties: [],
52
102
  reason: "memory prompt section renderers are captured as metadata before prompt-runtime execution",
53
103
  },
104
+ registerMediaUnderstandingProvider: {
105
+ mode: "metadata-only",
106
+ callableProperties: [],
107
+ reason: "media understanding providers are captured as registration metadata before provider runtime execution",
108
+ },
109
+ registerMemoryCapability: {
110
+ mode: "metadata-only",
111
+ callableProperties: [],
112
+ reason: "memory capabilities are captured as registration metadata before memory runtime execution",
113
+ },
114
+ registerMemoryCorpusSupplement: {
115
+ mode: "metadata-only",
116
+ callableProperties: [],
117
+ reason: "memory corpus supplements are captured as registration metadata before memory runtime execution",
118
+ },
119
+ registerMemoryEmbeddingProvider: {
120
+ mode: "metadata-only",
121
+ callableProperties: [],
122
+ reason: "memory embedding providers are captured as registration metadata before provider runtime execution",
123
+ },
124
+ registerMemoryFlushPlan: {
125
+ mode: "metadata-only",
126
+ callableProperties: [],
127
+ reason: "memory flush plans are captured as registration metadata before memory runtime execution",
128
+ },
54
129
  registerMemoryRuntime: {
55
130
  mode: "metadata-only",
56
131
  callableProperties: [],
57
132
  reason: "memory runtime factories are captured as metadata; external memory startup remains isolated opt-in",
58
133
  },
134
+ registerMemoryPromptSupplement: {
135
+ mode: "metadata-only",
136
+ callableProperties: [],
137
+ reason: "memory prompt supplements are captured as registration metadata before prompt-runtime execution",
138
+ },
139
+ registerMigrationProvider: {
140
+ mode: "metadata-only",
141
+ callableProperties: [],
142
+ reason: "migration providers are captured as registration metadata before migration runtime execution",
143
+ },
144
+ registerMusicGenerationProvider: {
145
+ mode: "metadata-only",
146
+ callableProperties: [],
147
+ reason: "music generation providers are captured as registration metadata before provider runtime execution",
148
+ },
149
+ registerNodeHostCommand: {
150
+ mode: "metadata-only",
151
+ callableProperties: [],
152
+ reason: "node host commands are captured as registration metadata before host process execution",
153
+ },
154
+ registerProvider: {
155
+ mode: "metadata-only",
156
+ callableProperties: [],
157
+ reason: "provider descriptors are captured as registration metadata before provider runtime execution",
158
+ },
159
+ registerRealtimeTranscriptionProvider: {
160
+ mode: "metadata-only",
161
+ callableProperties: [],
162
+ reason: "realtime transcription providers are captured as registration metadata before provider runtime execution",
163
+ },
164
+ registerRealtimeVoiceProvider: {
165
+ mode: "metadata-only",
166
+ callableProperties: [],
167
+ reason: "realtime voice providers are captured as registration metadata before provider runtime execution",
168
+ },
169
+ registerReload: {
170
+ mode: "metadata-only",
171
+ callableProperties: [],
172
+ reason: "reload handlers are captured as registration metadata before runtime reload execution",
173
+ },
174
+ registerSecurityAuditCollector: {
175
+ mode: "metadata-only",
176
+ callableProperties: [],
177
+ reason: "security audit collectors are captured as registration metadata before filesystem or policy scans",
178
+ },
59
179
  registerService: {
60
180
  mode: "lifecycle-opt-in",
61
181
  callableProperties: ["start", "stop", "dispose"],
@@ -70,6 +190,26 @@ export const syntheticRegistrationExecutionProfiles = {
70
190
  mode: "direct",
71
191
  callableProperties: ["run", "handler", "execute"],
72
192
  },
193
+ registerTextTransforms: {
194
+ mode: "metadata-only",
195
+ callableProperties: [],
196
+ reason: "text transforms are captured as registration metadata before content mutation execution",
197
+ },
198
+ registerVideoGenerationProvider: {
199
+ mode: "metadata-only",
200
+ callableProperties: [],
201
+ reason: "video generation providers are captured as registration metadata before provider runtime execution",
202
+ },
203
+ registerWebFetchProvider: {
204
+ mode: "metadata-only",
205
+ callableProperties: [],
206
+ reason: "web fetch providers are captured as registration metadata before provider runtime execution",
207
+ },
208
+ registerWebSearchProvider: {
209
+ mode: "metadata-only",
210
+ callableProperties: [],
211
+ reason: "web search providers are captured as registration metadata before provider runtime execution",
212
+ },
73
213
  };
74
214
 
75
215
  export const defaultSyntheticHookEvents = {
@@ -218,7 +218,7 @@ async function buildEntrypointPlan({ fixtureId, entrypoint, packageSummary, pack
218
218
  const packageManager = detectPackageManager(settings.rootDir, packageDir, packageJson);
219
219
  const lockfile = findNearestLockfile(settings.rootDir, packageDir);
220
220
  const buildScript = packageJson.scripts?.build;
221
- const requiredCapabilities = requiredCapabilitiesFor(entrypoint);
221
+ const requiredCapabilities = requiredCapabilitiesFor(entrypoint, packageSummary);
222
222
  const loaderStrategy = loaderStrategyFor(entrypoint);
223
223
  const blockers = [...entrypoint.blockers];
224
224
  const workspacePath = posixJoin(settings.workspaceRoot, fixtureId);
@@ -342,7 +342,7 @@ function loaderStrategyFor(entrypoint) {
342
342
  };
343
343
  }
344
344
 
345
- function requiredCapabilitiesFor(entrypoint) {
345
+ function requiredCapabilitiesFor(entrypoint, packageSummary = {}) {
346
346
  const capabilities = new Set();
347
347
  for (const blocker of entrypoint.blockers) {
348
348
  if (blocker.code === "dependency-install-required") {
@@ -361,7 +361,7 @@ function requiredCapabilitiesFor(entrypoint) {
361
361
  capabilities.add("side-effect-sandbox");
362
362
  }
363
363
  }
364
- if (entrypoint.blockers.some((blocker) => /\bopenclaw\b/.test(blocker.evidence ?? ""))) {
364
+ if (hasHostLinkedOpenClawDependency(packageSummary)) {
365
365
  capabilities.add("target-openclaw-link");
366
366
  }
367
367
  capabilities.add("capture-shim");
@@ -369,6 +369,14 @@ function requiredCapabilitiesFor(entrypoint) {
369
369
  return [...capabilities].sort();
370
370
  }
371
371
 
372
+ function hasHostLinkedOpenClawDependency(packageSummary) {
373
+ return [
374
+ ...(packageSummary.dependencies ?? []),
375
+ ...(packageSummary.peerDependencies ?? []),
376
+ ...(packageSummary.optionalDependencies ?? []),
377
+ ].includes("openclaw");
378
+ }
379
+
372
380
  function detectPackageManager(rootDir, packageDir, packageJson) {
373
381
  const declared = typeof packageJson.packageManager === "string" ? packageJson.packageManager.split("@")[0] : null;
374
382
  if (declared) {