@openclaw/plugin-inspector 0.3.4 → 0.3.6

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,28 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.6 - 2026-05-03
6
+
7
+ ### Changed
8
+
9
+ - Report import-loop RSS and CPU as baseline-adjusted plugin deltas alongside raw subprocess metrics so Crabpot dashboards do not treat harness import cost as plugin runtime cost.
10
+ - Include optional OpenClaw loader lifecycle timings for import and full activation when a capture runner provides them.
11
+
12
+ ### Fixed
13
+
14
+ - Accept plugin install minimum-host floors as supported package metadata.
15
+ - Flag unsupported legacy OpenClaw bundle metadata and advertised npm pack blockers.
16
+ - Reconcile runtime capture evidence and harden mocked capture paths for downstream fixture reports.
17
+
18
+ ## 0.3.5 - 2026-04-29
19
+
20
+ ### Fixed
21
+
22
+ - Add immediate/faster subprocess RSS and CPU sampling plus explicit sample counts so short import-loop reports do not silently publish fake zero-memory metrics.
23
+ - Classify `createChatChannelPlugin` as channel factory metadata in synthetic probe plans so channel-core plugins do not fail as unknown registrars.
24
+ - Treat `createChatChannelPlugin` and `defineChannelPluginEntry` as channel registration equivalents when validating fixture expectations.
25
+ - Label runtime profile wall-time summaries as command-median p95 and render missing sampled metrics as `n/a`.
26
+
5
27
  ## 0.3.4 - 2026-04-29
6
28
 
7
29
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/plugin-inspector",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "private": false,
5
5
  "description": "Offline compatibility inspector for OpenClaw plugins.",
6
6
  "type": "module",
package/src/advanced.js CHANGED
@@ -95,6 +95,7 @@ export {
95
95
  classifyTargetOpenClawCoverage,
96
96
  readPackageSummaries,
97
97
  readPluginManifests,
98
+ readSecurityManifests,
98
99
  summarizePackage,
99
100
  } from "./fixture-summary.js";
100
101
  export {
@@ -183,6 +184,10 @@ export {
183
184
  validateRuntimeProfile,
184
185
  writeRuntimeProfile,
185
186
  } from "./runtime-profile.js";
187
+ export {
188
+ applyRuntimeExecutionCoverage,
189
+ buildRuntimeExecutionCoverage,
190
+ } from "./runtime-reconciliation.js";
186
191
  export {
187
192
  buildRuntimeCaptureReport,
188
193
  renderRuntimeCaptureMarkdown,
package/src/api.js CHANGED
@@ -45,6 +45,7 @@ export async function inspectPluginRoot(options = {}) {
45
45
  return inspectCompatibilityFixtureSet(config, {
46
46
  generatedAt: options.generatedAt,
47
47
  openclawPath: options.openclawPath,
48
+ executionResults: options.executionResults,
48
49
  targetOpenClaw: options.targetOpenClaw,
49
50
  });
50
51
  }
@@ -59,6 +60,7 @@ export async function inspectCompatibilityFixtureSetConfig(options = {}) {
59
60
  return inspectCompatibilityFixtureSet(config, {
60
61
  generatedAt: options.generatedAt,
61
62
  openclawPath: options.openclawPath,
63
+ executionResults: options.executionResults,
62
64
  targetOpenClaw: options.targetOpenClaw,
63
65
  });
64
66
  }
@@ -112,6 +114,7 @@ export async function buildFixtureSetColdImportReadiness(options = {}) {
112
114
  (await inspectCompatibilityFixtureSet(config, {
113
115
  generatedAt: options.generatedAt,
114
116
  openclawPath: options.openclawPath,
117
+ executionResults: options.executionResults,
115
118
  targetOpenClaw: options.targetOpenClaw,
116
119
  }));
117
120
 
@@ -143,6 +146,7 @@ export async function buildFixtureSetWorkspacePlan(options = {}) {
143
146
  (await inspectCompatibilityFixtureSet(config, {
144
147
  generatedAt: options.generatedAt,
145
148
  openclawPath: options.openclawPath,
149
+ executionResults: options.executionResults,
146
150
  targetOpenClaw: options.targetOpenClaw,
147
151
  }));
148
152
  const rootDir = options.rootDir ?? config?.rootDir ?? options.cwd;
package/src/artifacts.js CHANGED
@@ -95,7 +95,7 @@ export function renderPaddedMarkdownTable(rows, headers, options = {}) {
95
95
  }
96
96
 
97
97
  export function escapeMarkdownTableCell(value) {
98
- return value.replace(/\|/g, "\\|").replace(/\n/g, "<br>");
98
+ return value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\n/g, "<br>");
99
99
  }
100
100
 
101
101
  async function assertFileMatches(filePath, expected) {
package/src/ci-policy.js CHANGED
@@ -241,7 +241,9 @@ function executionChecks(executionResults, policy, options) {
241
241
  }
242
242
 
243
243
  function findPolicyMatch(rules, item) {
244
- return rules.find((rule) => item.seam === rule.seam && item.reason?.includes(rule.reasonIncludes));
244
+ return rules.find(
245
+ (rule) => (rule.seam === "*" || item.seam === rule.seam) && item.reason?.includes(rule.reasonIncludes),
246
+ );
245
247
  }
246
248
 
247
249
  function failedExecutionEvidence(executionResults) {
package/src/ci-summary.js CHANGED
@@ -56,8 +56,14 @@ export async function buildCiSummary(options = {}) {
56
56
  loaderJitiCandidates: reports.platform?.summary?.jitiAlternativeCount ?? 0,
57
57
  importLoopP50Ms: reports.importLoop?.summary?.p50WallMs ?? 0,
58
58
  importLoopP95Ms: reports.importLoop?.summary?.p95WallMs ?? 0,
59
- importLoopMaxRssMb: reports.importLoop?.summary?.maxPeakRssMb ?? 0,
60
- importLoopMaxCpuMs: reports.importLoop?.summary?.maxCpuMsEstimate ?? 0,
59
+ importLoopOpenClawLifecycleCount: reports.importLoop?.summary?.openClawLifecycleCount ?? 0,
60
+ importLoopOpenClawImportP50Ms: reports.importLoop?.summary?.p50OpenClawImportMs ?? 0,
61
+ importLoopOpenClawActivationP50Ms: reports.importLoop?.summary?.p50OpenClawActivationMs ?? 0,
62
+ importLoopMetricBasis: reports.importLoop?.summary?.maxPluginPeakRssDeltaMb === undefined ? "raw" : "baseline-adjusted",
63
+ importLoopMaxRssMb: reports.importLoop?.summary?.maxPluginPeakRssDeltaMb ?? reports.importLoop?.summary?.maxPeakRssMb ?? 0,
64
+ importLoopMaxCpuMs: reports.importLoop?.summary?.maxPluginCpuDeltaMsEstimate ?? reports.importLoop?.summary?.maxCpuMsEstimate ?? 0,
65
+ importLoopRssSampleCount: metricSampleCount(reports.importLoop, "rss", "maxPeakRssMb"),
66
+ importLoopCpuSampleCount: metricSampleCount(reports.importLoop, "cpu", "maxCpuMsEstimate"),
61
67
  },
62
68
  topIssues: topIssues(reports.compatibility),
63
69
  refRegressions: (reports.refDiff?.regressions ?? []).slice(0, 20),
@@ -148,7 +154,7 @@ export function renderCiSummaryMarkdown(summary) {
148
154
  ["Jiti loader candidates", summary.summary.loaderJitiCandidates],
149
155
  [
150
156
  "Import loop",
151
- `p50 ${summary.summary.importLoopP50Ms} ms / p95 ${summary.summary.importLoopP95Ms} ms / max RSS ${summary.summary.importLoopMaxRssMb} MB / CPU ${summary.summary.importLoopMaxCpuMs} ms`,
157
+ importLoopSummaryLabel(summary.summary),
152
158
  ],
153
159
  ],
154
160
  ["Metric", "Value"],
@@ -221,3 +227,44 @@ function topIssues(report) {
221
227
  function markdownTable(rows, headers) {
222
228
  return renderPaddedMarkdownTable(rows, headers, { nullValue: "-" });
223
229
  }
230
+
231
+ function metricSampleCount(report, kind, maxMetric) {
232
+ const summaryKey = kind === "rss" ? "rssSampleCount" : "cpuSampleCount";
233
+ const summaryCount = report?.summary?.[summaryKey];
234
+ if (Number.isFinite(summaryCount)) {
235
+ return summaryCount;
236
+ }
237
+ const sampleCount = inferSampleCount(report?.samples, kind);
238
+ if (sampleCount > 0) {
239
+ return sampleCount;
240
+ }
241
+ return (report?.summary?.[maxMetric] ?? 0) > 0 ? 1 : 0;
242
+ }
243
+
244
+ function inferSampleCount(samples = [], kind) {
245
+ if (!Array.isArray(samples)) {
246
+ return 0;
247
+ }
248
+ return samples.reduce((sum, sample) => {
249
+ if (kind === "rss") {
250
+ return sum + (sample.rssSampleCount ?? (sample.peakRssMb > 0 ? 1 : 0));
251
+ }
252
+ return sum + (sample.cpuSampleCount ?? 0);
253
+ }, 0);
254
+ }
255
+
256
+ function importLoopSummaryLabel(summary) {
257
+ const metricLabel = summary.importLoopMetricBasis === "baseline-adjusted" ? "plugin delta" : "raw";
258
+ const lifecycle =
259
+ summary.importLoopOpenClawLifecycleCount > 0
260
+ ? ` / OpenClaw import ${summary.importLoopOpenClawImportP50Ms} ms / activate ${summary.importLoopOpenClawActivationP50Ms} ms`
261
+ : "";
262
+ return `p50 ${summary.importLoopP50Ms} ms / p95 ${summary.importLoopP95Ms} ms / ${metricLabel} RSS ${formatSampledMetric(summary.importLoopMaxRssMb, summary.importLoopRssSampleCount)} / ${metricLabel} CPU ${formatSampledMetric(summary.importLoopMaxCpuMs, summary.importLoopCpuSampleCount, "ms")}${lifecycle}`;
263
+ }
264
+
265
+ function formatSampledMetric(value, count, unit = "MB") {
266
+ if ((count ?? 0) <= 0) {
267
+ return "n/a";
268
+ }
269
+ return `${value} ${unit}`;
270
+ }
@@ -26,13 +26,20 @@ export function renderCompatibilityMarkdownReport(report, options = {}) {
26
26
  ["Warnings", report.summary.warningCount],
27
27
  ["Compatibility suggestions", report.summary.suggestionCount],
28
28
  ["Issue findings", report.summary.issueCount],
29
+ ["Open issue findings", report.summary.openIssueCount ?? report.summary.issueCount],
30
+ ["Runtime-covered findings", report.summary.runtimeCoveredIssueCount ?? 0],
31
+ ["Runtime-partial findings", report.summary.runtimePartiallyCoveredIssueCount ?? 0],
29
32
  ["P0 issues", report.summary.p0IssueCount],
30
33
  ["P1 issues", report.summary.p1IssueCount],
34
+ ["Open P0 issues", report.summary.openP0IssueCount ?? report.summary.p0IssueCount],
35
+ ["Open P1 issues", report.summary.openP1IssueCount ?? report.summary.p1IssueCount],
31
36
  ["Live issues", report.summary.liveIssueCount],
32
37
  ["Live P0 issues", report.summary.liveP0IssueCount],
33
38
  ["Compat gaps", report.summary.compatGapCount],
34
39
  ["Deprecation warnings", report.summary.deprecationWarningCount],
35
40
  ["Inspector gaps", report.summary.inspectorGapCount],
41
+ ["Open inspector gaps", report.summary.openInspectorGapCount ?? report.summary.inspectorGapCount],
42
+ ["Runtime coverage artifacts", report.summary.runtimeCoverageArtifactCount ?? 0],
36
43
  ["Upstream metadata", report.summary.upstreamIssueCount],
37
44
  ["Contract probes", report.summary.contractProbeCount],
38
45
  ["Decision rows", report.summary.decisionCount],
@@ -65,7 +72,11 @@ export function renderCompatibilityMarkdownReport(report, options = {}) {
65
72
  "",
66
73
  "## Inspector Proof Gaps",
67
74
  "",
68
- issuesTable(report.issues.filter((issue) => issue.issueClass === "inspector-gap"), options),
75
+ issuesTable(report.issues.filter((issue) => issue.issueClass === "inspector-gap" && issue.status !== "runtime-covered"), options),
76
+ "",
77
+ "## Runtime-Covered Inspector Gaps",
78
+ "",
79
+ issuesTable(report.issues.filter((issue) => issue.issueClass === "inspector-gap" && issue.status === "runtime-covered"), options),
69
80
  "",
70
81
  "## Upstream Metadata Issues",
71
82
  "",
@@ -141,13 +152,20 @@ export function renderCompatibilityIssuesReport(report, options = {}) {
141
152
  markdownTable(
142
153
  [
143
154
  ["Issue findings", report.summary.issueCount],
155
+ ["Open issue findings", report.summary.openIssueCount ?? report.summary.issueCount],
156
+ ["Runtime-covered findings", report.summary.runtimeCoveredIssueCount ?? 0],
157
+ ["Runtime-partial findings", report.summary.runtimePartiallyCoveredIssueCount ?? 0],
144
158
  [severityLabel("P0", options), report.summary.p0IssueCount],
145
159
  [severityLabel("P1", options), report.summary.p1IssueCount],
160
+ [`Open ${severityLabel("P0", options)}`, report.summary.openP0IssueCount ?? report.summary.p0IssueCount],
161
+ [`Open ${severityLabel("P1", options)}`, report.summary.openP1IssueCount ?? report.summary.p1IssueCount],
146
162
  ["Live issues", report.summary.liveIssueCount],
147
163
  ["Live P0 issues", report.summary.liveP0IssueCount],
148
164
  ["Compat gaps", report.summary.compatGapCount],
149
165
  ["Deprecation warnings", report.summary.deprecationWarningCount],
150
166
  ["Inspector gaps", report.summary.inspectorGapCount],
167
+ ["Open inspector gaps", report.summary.openInspectorGapCount ?? report.summary.inspectorGapCount],
168
+ ["Runtime coverage artifacts", report.summary.runtimeCoverageArtifactCount ?? 0],
151
169
  ["Upstream metadata", report.summary.upstreamIssueCount],
152
170
  ["Contract probes", report.summary.contractProbeCount],
153
171
  ],
@@ -179,7 +197,11 @@ export function renderCompatibilityIssuesReport(report, options = {}) {
179
197
  "",
180
198
  "## Inspector Proof Gaps",
181
199
  "",
182
- issuesTable(report.issues.filter((issue) => issue.issueClass === "inspector-gap"), options),
200
+ issuesTable(report.issues.filter((issue) => issue.issueClass === "inspector-gap" && issue.status !== "runtime-covered"), options),
201
+ "",
202
+ "## Runtime-Covered Inspector Gaps",
203
+ "",
204
+ issuesTable(report.issues.filter((issue) => issue.issueClass === "inspector-gap" && issue.status === "runtime-covered"), options),
183
205
  "",
184
206
  "## Upstream Metadata Issues",
185
207
  "",
@@ -228,6 +250,7 @@ function issueBlock(issue, options) {
228
250
  ` - state: ${issueState(issue)}`,
229
251
  " - evidence:",
230
252
  ...evidenceList(issue.evidence, options).map((item) => ` - ${item}`),
253
+ ...runtimeCoverageList(issue, options),
231
254
  ].join("\n");
232
255
  }
233
256
 
@@ -235,6 +258,7 @@ function issueState(issue) {
235
258
  const flags = [
236
259
  issue.status,
237
260
  `compat:${issue.compatStatus ?? "none"}`,
261
+ issue.runtimeCoverage?.status ? `runtime:${issue.runtimeCoverage.status}` : null,
238
262
  issue.live ? "live" : null,
239
263
  issue.deprecated ? "deprecated" : null,
240
264
  ].filter(Boolean);
@@ -266,7 +290,7 @@ function triageOverview(report) {
266
290
  "inspector-gap",
267
291
  report.summary.inspectorGapCount,
268
292
  "-",
269
- "Plugin Inspector needs stronger capture/probe evidence before making contract judgments.",
293
+ "Plugin Inspector needs stronger capture/probe evidence before making contract judgments. Runtime-covered rows are proof-backed and not open report work.",
270
294
  ],
271
295
  [
272
296
  "upstream-metadata",
@@ -357,3 +381,15 @@ function evidenceList(evidence, options) {
357
381
  const formatEvidence = options.formatEvidence ?? ((item) => item);
358
382
  return items.map((item) => formatEvidence(item));
359
383
  }
384
+
385
+ function runtimeCoverageList(issue, options) {
386
+ const runtimeCoverage = issue.runtimeCoverage;
387
+ if (!runtimeCoverage) {
388
+ return [];
389
+ }
390
+ return [
391
+ " - runtime coverage:",
392
+ ...evidenceList(runtimeCoverage.captured, options).map((item) => ` - captured ${item}`),
393
+ ...evidenceList(runtimeCoverage.artifacts, options).map((item) => ` - ${item}`),
394
+ ];
395
+ }
package/src/config.js CHANGED
@@ -202,13 +202,43 @@ export function packageId(packageName) {
202
202
  if (!packageName) {
203
203
  return null;
204
204
  }
205
- return packageName
205
+ const packageBase = packageName
206
206
  .split("/")
207
207
  .pop()
208
- .replace(/^openclaw-/, "")
209
- .replace(/[^a-zA-Z0-9]+/g, "-")
210
- .replace(/^-+|-+$/g, "")
211
- .toLowerCase();
208
+ .replace(/^openclaw-/, "");
209
+ return trimHyphenEdges(collapsePackageIdSeparators(packageBase)).toLowerCase();
210
+ }
211
+
212
+ function collapsePackageIdSeparators(value) {
213
+ let result = "";
214
+ let previousWasHyphen = false;
215
+ for (const char of value) {
216
+ if (isAsciiAlphaNumeric(char)) {
217
+ result += char;
218
+ previousWasHyphen = false;
219
+ } else if (!previousWasHyphen) {
220
+ result += "-";
221
+ previousWasHyphen = true;
222
+ }
223
+ }
224
+ return result;
225
+ }
226
+
227
+ function isAsciiAlphaNumeric(char) {
228
+ const code = char.charCodeAt(0);
229
+ return (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
230
+ }
231
+
232
+ function trimHyphenEdges(value) {
233
+ let start = 0;
234
+ let end = value.length;
235
+ while (start < end && value[start] === "-") {
236
+ start += 1;
237
+ }
238
+ while (end > start && value[end - 1] === "-") {
239
+ end -= 1;
240
+ }
241
+ return value.slice(start, end);
212
242
  }
213
243
 
214
244
  export function inferPluginSeams(pluginManifest, packageJson) {
@@ -7,6 +7,7 @@ import {
7
7
  } from "./synthetic-probes.js";
8
8
 
9
9
  export const defaultRegistrationAssertions = {
10
+ createChatChannelPlugin: ["channel plugin id is stable", "channel factory metadata is captured"],
10
11
  defineChannelPluginEntry: ["channel id is stable", "setup/config schema can be read", "message envelope metadata is preserved"],
11
12
  definePluginEntry: ["entrypoint register function is callable", "entrypoint metadata is preserved"],
12
13
  registerChannel: ["channel id is stable", "inbound/outbound envelope shape is captured", "sender metadata is preserved"],
@@ -74,6 +74,31 @@ export const contractProbeRules = {
74
74
  contract: "OpenClaw package entrypoints resolve to files in the published or built plugin package.",
75
75
  target: "package-loader",
76
76
  },
77
+ "package-install-metadata-incomplete": {
78
+ id: "package.metadata.install-release",
79
+ contract: "Release publishing metadata declares canonical ClawHub and npm install specs.",
80
+ target: "package-loader",
81
+ },
82
+ "package-min-host-version-drift": {
83
+ id: "package.metadata.min-host-version",
84
+ contract: "Install minimum host version matches the OpenClaw package surface targeted by the plugin.",
85
+ target: "package-loader",
86
+ },
87
+ "package-npm-pack-entrypoint-missing": {
88
+ id: "package.npm-pack.entrypoints",
89
+ contract: "Advertised npm artifacts include every declared OpenClaw package entrypoint.",
90
+ target: "package-loader",
91
+ },
92
+ "package-npm-pack-metadata-missing": {
93
+ id: "package.npm-pack.metadata",
94
+ contract: "Advertised npm artifacts include OpenClaw manifest and package metadata.",
95
+ target: "package-loader",
96
+ },
97
+ "package-npm-pack-unavailable": {
98
+ id: "package.npm-pack.available",
99
+ contract: "Packages that advertise npm install support can produce an npm pack artifact.",
100
+ target: "package-loader",
101
+ },
77
102
  "package-openclaw-entry-missing": {
78
103
  id: "package.entrypoint.openclaw-metadata",
79
104
  contract: "OpenClaw package metadata declares entrypoints for cold import and registration capture.",