@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 +22 -0
- package/package.json +1 -1
- package/src/advanced.js +5 -0
- package/src/api.js +4 -0
- package/src/artifacts.js +1 -1
- package/src/ci-policy.js +3 -1
- package/src/ci-summary.js +50 -3
- package/src/compatibility-report.js +39 -3
- package/src/config.js +35 -5
- package/src/contract-capture.js +1 -0
- package/src/contract-probes.js +25 -0
- package/src/fixture-summary.js +406 -0
- package/src/import-loop-profile.js +262 -8
- package/src/index.js +7 -0
- package/src/inspector.js +49 -4
- package/src/issues.js +66 -1
- package/src/openclaw-target.js +79 -2
- package/src/platform-probes.js +29 -6
- package/src/process-profile.js +40 -19
- package/src/prune-workspace-dev-deps-cli.js +23 -0
- package/src/report.js +19 -1
- package/src/runtime-profile.js +67 -15
- package/src/runtime-reconciliation.js +124 -0
- package/src/sdk-mock.js +64 -6
- package/src/synthetic-probes.js +51 -0
- package/src/workspace-plan.js +19 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { renderPaddedMarkdownTable, writeJsonMarkdownArtifacts } from "./artifacts.js";
|
|
@@ -24,22 +24,48 @@ export async function buildImportLoopProfile(options = {}) {
|
|
|
24
24
|
const entrypoint = options.entrypoint ?? defaultImportLoopProfileOptions.entrypoint;
|
|
25
25
|
assertRunCount(runs, 20);
|
|
26
26
|
|
|
27
|
+
const baseline = await buildBaselineProfile({ ...options, rootDir, runs });
|
|
27
28
|
const samples = [];
|
|
28
29
|
for (let index = 0; index < runs; index += 1) {
|
|
29
|
-
|
|
30
|
+
const sample = await runCaptureSample({ ...options, entrypoint, index, rootDir });
|
|
31
|
+
samples.push(applyBaselineAdjustment(sample, baseline));
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const wallMs = samples.map((sample) => sample.wallMs).sort((left, right) => left - right);
|
|
35
|
+
const pluginWallDeltaMs = samples.map((sample) => sample.pluginWallDeltaMs).sort((left, right) => left - right);
|
|
36
|
+
const openClawImportMs = openClawLifecycleMetric(samples, "importMs");
|
|
37
|
+
const openClawActivationMs = openClawLifecycleMetric(samples, "activationMs");
|
|
38
|
+
const rssSampleCount = samples.reduce((sum, sample) => sum + (sample.rssSampleCount ?? (sample.peakRssMb > 0 ? 1 : 0)), 0);
|
|
39
|
+
const cpuSampleCount = samples.reduce((sum, sample) => sum + (sample.cpuSampleCount ?? 0), 0);
|
|
40
|
+
const statSampleCount = samples.reduce((sum, sample) => sum + (sample.statSampleCount ?? 0), 0);
|
|
33
41
|
return {
|
|
34
42
|
generatedAt: options.generatedAt ?? defaultImportLoopProfileOptions.generatedAt,
|
|
35
|
-
mode: options.mode ?? "
|
|
43
|
+
mode: options.mode ?? "baseline-adjusted-cold-capture-loop",
|
|
36
44
|
entrypoint,
|
|
45
|
+
baseline,
|
|
37
46
|
summary: {
|
|
38
47
|
runs,
|
|
48
|
+
baselineRuns: baseline.runs,
|
|
49
|
+
baselineFailCount: baseline.failCount,
|
|
39
50
|
p50WallMs: percentile(wallMs, 0.5),
|
|
40
51
|
p95WallMs: percentile(wallMs, 0.95),
|
|
52
|
+
p50PluginWallDeltaMs: percentile(pluginWallDeltaMs, 0.5),
|
|
53
|
+
p95PluginWallDeltaMs: percentile(pluginWallDeltaMs, 0.95),
|
|
54
|
+
openClawLifecycleCount: openClawImportMs.length,
|
|
55
|
+
p50OpenClawImportMs: percentile(openClawImportMs, 0.5),
|
|
56
|
+
p95OpenClawImportMs: percentile(openClawImportMs, 0.95),
|
|
57
|
+
p50OpenClawActivationMs: percentile(openClawActivationMs, 0.5),
|
|
58
|
+
p95OpenClawActivationMs: percentile(openClawActivationMs, 0.95),
|
|
41
59
|
maxPeakRssMb: Math.max(0, ...samples.map((sample) => sample.peakRssMb)),
|
|
42
60
|
maxCpuMsEstimate: Math.max(0, ...samples.map((sample) => sample.cpuMsEstimate)),
|
|
61
|
+
maxPluginPeakRssDeltaMb: Math.max(0, ...samples.map((sample) => sample.pluginPeakRssDeltaMb)),
|
|
62
|
+
maxPluginCpuDeltaMsEstimate: Math.max(0, ...samples.map((sample) => sample.pluginCpuDeltaMsEstimate)),
|
|
63
|
+
baselineReferenceWallMs: baseline.reference.wallMs,
|
|
64
|
+
baselineReferencePeakRssMb: baseline.reference.peakRssMb,
|
|
65
|
+
baselineReferenceCpuMsEstimate: baseline.reference.cpuMsEstimate,
|
|
66
|
+
statSampleCount,
|
|
67
|
+
rssSampleCount,
|
|
68
|
+
cpuSampleCount,
|
|
43
69
|
capturedCount: samples.reduce((sum, sample) => sum + sample.capturedCount, 0),
|
|
44
70
|
failCount: samples.filter((sample) => sample.exitCode !== 0 || sample.status !== "captured").length,
|
|
45
71
|
},
|
|
@@ -52,6 +78,9 @@ export function validateImportLoopProfile(report) {
|
|
|
52
78
|
if (report.summary.failCount > 0) {
|
|
53
79
|
errors.push(`import loop has ${report.summary.failCount} failed sample(s)`);
|
|
54
80
|
}
|
|
81
|
+
if ((report.summary.baselineFailCount ?? report.baseline?.failCount ?? 0) > 0) {
|
|
82
|
+
errors.push("import loop baseline capture failed");
|
|
83
|
+
}
|
|
55
84
|
if (report.summary.capturedCount < report.summary.runs) {
|
|
56
85
|
errors.push("import loop did not capture at least one contract per run");
|
|
57
86
|
}
|
|
@@ -85,7 +114,11 @@ export function renderImportLoopProfileMarkdown(report, options = {}) {
|
|
|
85
114
|
"",
|
|
86
115
|
"## Summary",
|
|
87
116
|
"",
|
|
88
|
-
markdownTable(
|
|
117
|
+
markdownTable(summaryRows(report), ["Metric", "Value"]),
|
|
118
|
+
"",
|
|
119
|
+
"## Harness Baseline",
|
|
120
|
+
"",
|
|
121
|
+
markdownTable(baselineRows(report), ["Metric", "Value"]),
|
|
89
122
|
"",
|
|
90
123
|
"## Samples",
|
|
91
124
|
"",
|
|
@@ -94,22 +127,132 @@ export function renderImportLoopProfileMarkdown(report, options = {}) {
|
|
|
94
127
|
sample.index,
|
|
95
128
|
sample.status,
|
|
96
129
|
sample.capturedCount,
|
|
130
|
+
formatOpenClawLifecycleMetric(sample.openClawLifecycle?.importMs),
|
|
131
|
+
formatOpenClawLifecycleMetric(sample.openClawLifecycle?.activationMs),
|
|
132
|
+
formatOptionalMetric(sample.pluginWallDeltaMs, "ms"),
|
|
133
|
+
formatSampledMetric(sample.pluginPeakRssDeltaMb, sample.rssSampleCount),
|
|
134
|
+
formatSampledMetric(sample.pluginCpuDeltaMsEstimate, sample.cpuSampleCount, "ms"),
|
|
97
135
|
`${sample.wallMs} ms`,
|
|
98
|
-
|
|
99
|
-
|
|
136
|
+
formatSampledMetric(sample.peakRssMb, sample.rssSampleCount),
|
|
137
|
+
formatSampledMetric(sample.cpuMsEstimate, sample.cpuSampleCount, "ms"),
|
|
138
|
+
`${sample.rssSampleCount ?? 0}/${sample.cpuSampleCount ?? 0}`,
|
|
100
139
|
sample.exitCode,
|
|
101
140
|
]),
|
|
102
|
-
[
|
|
141
|
+
[
|
|
142
|
+
"Run",
|
|
143
|
+
"Status",
|
|
144
|
+
"Captured",
|
|
145
|
+
"OpenClaw Import",
|
|
146
|
+
"OpenClaw Activate",
|
|
147
|
+
"Plugin Wall Delta",
|
|
148
|
+
"Plugin RSS Delta",
|
|
149
|
+
"Plugin CPU Delta",
|
|
150
|
+
"Raw Wall",
|
|
151
|
+
"Raw Peak RSS",
|
|
152
|
+
"Raw CPU Estimate",
|
|
153
|
+
"RSS/CPU samples",
|
|
154
|
+
"Exit",
|
|
155
|
+
],
|
|
103
156
|
),
|
|
104
157
|
].join("\n");
|
|
105
158
|
}
|
|
106
159
|
|
|
160
|
+
async function buildBaselineProfile(options) {
|
|
161
|
+
const baselineRuns = options.baseline === false ? 0 : options.baselineRuns ?? Math.min(options.runs, 3);
|
|
162
|
+
if (baselineRuns <= 0) {
|
|
163
|
+
return emptyBaseline();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const entrypoint = await writeBaselineEntrypoint(options);
|
|
167
|
+
const samples = [];
|
|
168
|
+
for (let index = 0; index < baselineRuns; index += 1) {
|
|
169
|
+
samples.push(
|
|
170
|
+
await runCaptureSample({
|
|
171
|
+
...options,
|
|
172
|
+
entrypoint,
|
|
173
|
+
index,
|
|
174
|
+
sampleName: "baseline",
|
|
175
|
+
rootDir: options.rootDir,
|
|
176
|
+
}),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const wallMs = sortedMetric(samples, "wallMs");
|
|
181
|
+
const peakRssMb = sortedMetric(samples, "peakRssMb");
|
|
182
|
+
const cpuMsEstimate = sortedMetric(samples, "cpuMsEstimate");
|
|
183
|
+
return {
|
|
184
|
+
mode: "minimal-plugin-capture",
|
|
185
|
+
runs: baselineRuns,
|
|
186
|
+
entrypoint: path.relative(options.rootDir, entrypoint),
|
|
187
|
+
reference: {
|
|
188
|
+
wallMs: percentile(wallMs, 0.5),
|
|
189
|
+
peakRssMb: percentile(peakRssMb, 0.5),
|
|
190
|
+
cpuMsEstimate: percentile(cpuMsEstimate, 0.5),
|
|
191
|
+
},
|
|
192
|
+
max: {
|
|
193
|
+
wallMs: wallMs.at(-1) ?? 0,
|
|
194
|
+
peakRssMb: peakRssMb.at(-1) ?? 0,
|
|
195
|
+
cpuMsEstimate: cpuMsEstimate.at(-1) ?? 0,
|
|
196
|
+
},
|
|
197
|
+
statSampleCount: samples.reduce((sum, sample) => sum + (sample.statSampleCount ?? 0), 0),
|
|
198
|
+
rssSampleCount: samples.reduce((sum, sample) => sum + (sample.rssSampleCount ?? 0), 0),
|
|
199
|
+
cpuSampleCount: samples.reduce((sum, sample) => sum + (sample.cpuSampleCount ?? 0), 0),
|
|
200
|
+
failCount: samples.filter((sample) => sample.exitCode !== 0 || sample.status !== "captured").length,
|
|
201
|
+
samples,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function emptyBaseline() {
|
|
206
|
+
return {
|
|
207
|
+
mode: "disabled",
|
|
208
|
+
runs: 0,
|
|
209
|
+
entrypoint: null,
|
|
210
|
+
reference: {
|
|
211
|
+
wallMs: 0,
|
|
212
|
+
peakRssMb: 0,
|
|
213
|
+
cpuMsEstimate: 0,
|
|
214
|
+
},
|
|
215
|
+
max: {
|
|
216
|
+
wallMs: 0,
|
|
217
|
+
peakRssMb: 0,
|
|
218
|
+
cpuMsEstimate: 0,
|
|
219
|
+
},
|
|
220
|
+
statSampleCount: 0,
|
|
221
|
+
rssSampleCount: 0,
|
|
222
|
+
cpuSampleCount: 0,
|
|
223
|
+
failCount: 0,
|
|
224
|
+
samples: [],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function writeBaselineEntrypoint(options) {
|
|
229
|
+
const outputDir = resolveFromRoot(
|
|
230
|
+
options.rootDir,
|
|
231
|
+
options.outputDir ?? defaultImportLoopProfileOptions.outputDir,
|
|
232
|
+
);
|
|
233
|
+
const baselinePath = path.join(outputDir, "baseline-plugin.mjs");
|
|
234
|
+
await mkdir(path.dirname(baselinePath), { recursive: true });
|
|
235
|
+
await writeFile(
|
|
236
|
+
baselinePath,
|
|
237
|
+
[
|
|
238
|
+
"export default {",
|
|
239
|
+
" register(api) {",
|
|
240
|
+
" api.registerTool({ name: 'baseline_tool', inputSchema: { type: 'object' }, run() {} });",
|
|
241
|
+
" },",
|
|
242
|
+
"};",
|
|
243
|
+
"",
|
|
244
|
+
].join("\n"),
|
|
245
|
+
"utf8",
|
|
246
|
+
);
|
|
247
|
+
return baselinePath;
|
|
248
|
+
}
|
|
249
|
+
|
|
107
250
|
async function runCaptureSample(options) {
|
|
108
251
|
const outputDir = resolveFromRoot(
|
|
109
252
|
options.rootDir,
|
|
110
253
|
options.outputDir ?? defaultImportLoopProfileOptions.outputDir,
|
|
111
254
|
);
|
|
112
|
-
const outputPath = path.join(outputDir,
|
|
255
|
+
const outputPath = path.join(outputDir, `${options.sampleName ?? "capture"}-${options.index}.json`);
|
|
113
256
|
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
114
257
|
|
|
115
258
|
const command = buildCaptureCommand({ ...options, outputPath });
|
|
@@ -126,14 +269,125 @@ async function runCaptureSample(options) {
|
|
|
126
269
|
exitCode: profile.exitCode,
|
|
127
270
|
status: output?.status ?? "failed",
|
|
128
271
|
capturedCount: output?.captured?.length ?? 0,
|
|
272
|
+
openClawLifecycle: output?.openClawLifecycle ?? null,
|
|
129
273
|
wallMs: profile.wallMs,
|
|
130
274
|
peakRssMb: profile.peakRssMb,
|
|
131
275
|
peakCpuPercent: profile.peakCpuPercent,
|
|
132
276
|
cpuMsEstimate: profile.cpuMsEstimate,
|
|
277
|
+
statSampleCount: profile.statSampleCount,
|
|
278
|
+
rssSampleCount: profile.rssSampleCount,
|
|
279
|
+
cpuSampleCount: profile.cpuSampleCount,
|
|
133
280
|
stderrPreview: profile.stderrPreview,
|
|
134
281
|
};
|
|
135
282
|
}
|
|
136
283
|
|
|
284
|
+
function summaryRows(report) {
|
|
285
|
+
return [
|
|
286
|
+
["runs", report.summary.runs],
|
|
287
|
+
["baselineRuns", report.summary.baselineRuns ?? report.baseline?.runs ?? 0],
|
|
288
|
+
["baselineFailCount", report.summary.baselineFailCount ?? report.baseline?.failCount ?? 0],
|
|
289
|
+
["p50WallMs", report.summary.p50WallMs],
|
|
290
|
+
["p95WallMs", report.summary.p95WallMs],
|
|
291
|
+
...(Number.isFinite(report.summary.p50PluginWallDeltaMs)
|
|
292
|
+
? [
|
|
293
|
+
["p50PluginWallDeltaMs", report.summary.p50PluginWallDeltaMs],
|
|
294
|
+
["p95PluginWallDeltaMs", report.summary.p95PluginWallDeltaMs],
|
|
295
|
+
["maxPluginPeakRssDeltaMb", formatSampledMetric(report.summary.maxPluginPeakRssDeltaMb, report.summary.rssSampleCount)],
|
|
296
|
+
[
|
|
297
|
+
"maxPluginCpuDeltaMsEstimate",
|
|
298
|
+
formatSampledMetric(report.summary.maxPluginCpuDeltaMsEstimate, report.summary.cpuSampleCount, "ms"),
|
|
299
|
+
],
|
|
300
|
+
]
|
|
301
|
+
: []),
|
|
302
|
+
...((report.summary.openClawLifecycleCount ?? 0) > 0
|
|
303
|
+
? [
|
|
304
|
+
["openClawLifecycleCount", report.summary.openClawLifecycleCount],
|
|
305
|
+
["p50OpenClawImportMs", `${report.summary.p50OpenClawImportMs} ms`],
|
|
306
|
+
["p95OpenClawImportMs", `${report.summary.p95OpenClawImportMs} ms`],
|
|
307
|
+
["p50OpenClawActivationMs", `${report.summary.p50OpenClawActivationMs} ms`],
|
|
308
|
+
["p95OpenClawActivationMs", `${report.summary.p95OpenClawActivationMs} ms`],
|
|
309
|
+
]
|
|
310
|
+
: []),
|
|
311
|
+
["maxPeakRssMb", formatSampledMetric(report.summary.maxPeakRssMb, report.summary.rssSampleCount)],
|
|
312
|
+
["maxCpuMsEstimate", formatSampledMetric(report.summary.maxCpuMsEstimate, report.summary.cpuSampleCount, "ms")],
|
|
313
|
+
...(Number.isFinite(report.summary.baselineReferenceWallMs)
|
|
314
|
+
? [
|
|
315
|
+
["baselineReferenceWallMs", `${report.summary.baselineReferenceWallMs} ms`],
|
|
316
|
+
["baselineReferencePeakRssMb", formatSampledMetric(report.summary.baselineReferencePeakRssMb, report.baseline?.rssSampleCount ?? 0)],
|
|
317
|
+
[
|
|
318
|
+
"baselineReferenceCpuMsEstimate",
|
|
319
|
+
formatSampledMetric(report.summary.baselineReferenceCpuMsEstimate, report.baseline?.cpuSampleCount ?? 0, "ms"),
|
|
320
|
+
],
|
|
321
|
+
]
|
|
322
|
+
: []),
|
|
323
|
+
["statSampleCount", report.summary.statSampleCount ?? 0],
|
|
324
|
+
["rssSampleCount", report.summary.rssSampleCount ?? 0],
|
|
325
|
+
["cpuSampleCount", report.summary.cpuSampleCount ?? 0],
|
|
326
|
+
["capturedCount", report.summary.capturedCount],
|
|
327
|
+
["failCount", report.summary.failCount],
|
|
328
|
+
];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function baselineRows(report) {
|
|
332
|
+
const baseline = report.baseline ?? emptyBaseline();
|
|
333
|
+
return [
|
|
334
|
+
["mode", baseline.mode],
|
|
335
|
+
["runs", baseline.runs],
|
|
336
|
+
["entrypoint", baseline.entrypoint ?? "-"],
|
|
337
|
+
["referenceWallMs", `${baseline.reference?.wallMs ?? 0} ms`],
|
|
338
|
+
["referencePeakRssMb", formatSampledMetric(baseline.reference?.peakRssMb ?? 0, baseline.rssSampleCount)],
|
|
339
|
+
["referenceCpuMsEstimate", formatSampledMetric(baseline.reference?.cpuMsEstimate ?? 0, baseline.cpuSampleCount, "ms")],
|
|
340
|
+
["maxWallMs", `${baseline.max?.wallMs ?? 0} ms`],
|
|
341
|
+
["maxPeakRssMb", formatSampledMetric(baseline.max?.peakRssMb ?? 0, baseline.rssSampleCount)],
|
|
342
|
+
["maxCpuMsEstimate", formatSampledMetric(baseline.max?.cpuMsEstimate ?? 0, baseline.cpuSampleCount, "ms")],
|
|
343
|
+
["statSampleCount", baseline.statSampleCount ?? 0],
|
|
344
|
+
["failCount", baseline.failCount ?? 0],
|
|
345
|
+
];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function formatSampledMetric(value, count, unit = "MB") {
|
|
349
|
+
if ((count ?? 0) <= 0) {
|
|
350
|
+
return "n/a";
|
|
351
|
+
}
|
|
352
|
+
return `${value} ${unit}`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function formatOptionalMetric(value, unit) {
|
|
356
|
+
if (!Number.isFinite(value)) {
|
|
357
|
+
return "n/a";
|
|
358
|
+
}
|
|
359
|
+
return `${value} ${unit}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function formatOpenClawLifecycleMetric(value) {
|
|
363
|
+
return Number.isFinite(value) ? `${value} ms` : "n/a";
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function openClawLifecycleMetric(samples, field) {
|
|
367
|
+
return samples
|
|
368
|
+
.map((sample) => sample.openClawLifecycle?.[field])
|
|
369
|
+
.filter((value) => Number.isFinite(value))
|
|
370
|
+
.sort((left, right) => left - right);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function applyBaselineAdjustment(sample, baseline) {
|
|
374
|
+
return {
|
|
375
|
+
...sample,
|
|
376
|
+
pluginWallDeltaMs: roundNonNegative(sample.wallMs - baseline.reference.wallMs, 0),
|
|
377
|
+
pluginPeakRssDeltaMb: roundNonNegative(sample.peakRssMb - baseline.reference.peakRssMb, 1),
|
|
378
|
+
pluginCpuDeltaMsEstimate: roundNonNegative(sample.cpuMsEstimate - baseline.reference.cpuMsEstimate, 0),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function sortedMetric(samples, field) {
|
|
383
|
+
return samples.map((sample) => sample[field]).sort((left, right) => left - right);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function roundNonNegative(value, digits) {
|
|
387
|
+
const scale = 10 ** digits;
|
|
388
|
+
return Math.max(0, Math.round(value * scale) / scale);
|
|
389
|
+
}
|
|
390
|
+
|
|
137
391
|
function buildCaptureCommand(options) {
|
|
138
392
|
if (typeof options.captureCommand === "function") {
|
|
139
393
|
return options.captureCommand({
|
package/src/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import * as profileDiffApi from "./profile-diff.js";
|
|
|
13
13
|
import * as refDiffApi from "./ref-diff.js";
|
|
14
14
|
import * as reportApi from "./report.js";
|
|
15
15
|
import * as runtimeProfileApi from "./runtime-profile.js";
|
|
16
|
+
import * as runtimeReconciliationApi from "./runtime-reconciliation.js";
|
|
16
17
|
import * as syntheticProbeSuiteApi from "./synthetic-probe-suite.js";
|
|
17
18
|
import * as syntheticProbesApi from "./synthetic-probes.js";
|
|
18
19
|
|
|
@@ -111,6 +112,8 @@ export const runtime = Object.freeze({
|
|
|
111
112
|
writeImportLoopProfile: importLoopProfileApi.writeImportLoopProfile,
|
|
112
113
|
renderImportLoopProfile: importLoopProfileApi.renderImportLoopProfileMarkdown,
|
|
113
114
|
validateImportLoopProfile: importLoopProfileApi.validateImportLoopProfile,
|
|
115
|
+
applyExecutionCoverage: runtimeReconciliationApi.applyRuntimeExecutionCoverage,
|
|
116
|
+
buildExecutionCoverage: runtimeReconciliationApi.buildRuntimeExecutionCoverage,
|
|
114
117
|
});
|
|
115
118
|
|
|
116
119
|
export const synthetic = Object.freeze({
|
|
@@ -227,6 +230,10 @@ export {
|
|
|
227
230
|
validateRuntimeProfile,
|
|
228
231
|
writeRuntimeProfile,
|
|
229
232
|
} from "./runtime-profile.js";
|
|
233
|
+
export {
|
|
234
|
+
applyRuntimeExecutionCoverage,
|
|
235
|
+
buildRuntimeExecutionCoverage,
|
|
236
|
+
} from "./runtime-reconciliation.js";
|
|
230
237
|
export { buildSyntheticProbePlanFromReport } from "./synthetic-probe-suite.js";
|
|
231
238
|
export {
|
|
232
239
|
buildSyntheticProbePlan,
|
package/src/inspector.js
CHANGED
|
@@ -11,6 +11,9 @@ import { readOpenClawTargetSurface } from "./openclaw-target.js";
|
|
|
11
11
|
import { buildCompatibilityReport, buildReport } from "./report.js";
|
|
12
12
|
|
|
13
13
|
const execFileAsync = promisify(execFile);
|
|
14
|
+
const registrationEquivalents = new Map([
|
|
15
|
+
["registerChannel", new Set(["createChatChannelPlugin", "defineBundledChannelEntry", "defineChannelPluginEntry", "registerChannel"])],
|
|
16
|
+
]);
|
|
14
17
|
|
|
15
18
|
export async function inspectFixtureSet(config, options = {}) {
|
|
16
19
|
const { inspections, failures } = await inspectConfiguredFixtures(config, options);
|
|
@@ -32,6 +35,7 @@ export async function inspectCompatibilityFixtureSet(config, options = {}) {
|
|
|
32
35
|
inspections,
|
|
33
36
|
failures,
|
|
34
37
|
generatedAt: options.generatedAt,
|
|
38
|
+
executionResults: options.executionResults,
|
|
35
39
|
targetOpenClaw,
|
|
36
40
|
buildFixtureReport: ({ fixture, inspection }) =>
|
|
37
41
|
buildCompatibilityFixtureReport({
|
|
@@ -58,7 +62,7 @@ async function inspectConfiguredFixtures(config, options = {}) {
|
|
|
58
62
|
["manifestContracts", inspection.manifestContracts],
|
|
59
63
|
]) {
|
|
60
64
|
const expected = fixture.expect?.[key] ?? [];
|
|
61
|
-
const missing = expected.filter((value) => !
|
|
65
|
+
const missing = expected.filter((value) => !satisfiesExpectedSeam(key, value, observed));
|
|
62
66
|
if (missing.length > 0) {
|
|
63
67
|
failures.push(`${fixture.id}: missing ${key}: ${missing.join(", ")}`);
|
|
64
68
|
}
|
|
@@ -68,6 +72,17 @@ async function inspectConfiguredFixtures(config, options = {}) {
|
|
|
68
72
|
return { inspections, failures };
|
|
69
73
|
}
|
|
70
74
|
|
|
75
|
+
function satisfiesExpectedSeam(key, expected, observed) {
|
|
76
|
+
if (observed.includes(expected)) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
if (key !== "registrations") {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
const equivalents = registrationEquivalents.get(expected);
|
|
83
|
+
return Boolean(equivalents && observed.some((value) => equivalents.has(value)));
|
|
84
|
+
}
|
|
85
|
+
|
|
71
86
|
export async function inspectPlugin(fixture, options = {}) {
|
|
72
87
|
const config = options.config ?? { rootDir: options.rootDir ?? process.cwd() };
|
|
73
88
|
const checkoutPath = fixtureCheckoutPath(config, fixture);
|
|
@@ -132,6 +147,7 @@ export function inspectSourceText(text, filePath = "source.js") {
|
|
|
132
147
|
const hooks = collectDetailedMatches(searchableText, /\bapi\.on\(\s*["'`]([^"'`]+)["'`]/g, filePath, "name");
|
|
133
148
|
const registrations = [
|
|
134
149
|
...collectDetailedMatches(searchableText, /\bapi\.(register[A-Za-z0-9]+)\s*\(/g, filePath, "name"),
|
|
150
|
+
...collectDetailedMatches(searchableText, /\b(defineBundledChannelEntry)\s*\(/g, filePath, "name"),
|
|
135
151
|
...collectDetailedMatches(searchableText, /\b(defineChannelPluginEntry)\s*\(/g, filePath, "name"),
|
|
136
152
|
...collectDetailedMatches(searchableText, /\b(createChatChannelPlugin)\s*\(/g, filePath, "name"),
|
|
137
153
|
...collectDetailedMatches(searchableText, /\b(definePluginEntry)\s*\(/g, filePath, "name"),
|
|
@@ -457,9 +473,38 @@ function lineForOffset(text, offset) {
|
|
|
457
473
|
}
|
|
458
474
|
|
|
459
475
|
function stripComments(text) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
476
|
+
let result = "";
|
|
477
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
478
|
+
const char = text[index];
|
|
479
|
+
const next = text[index + 1];
|
|
480
|
+
if (char === "/" && next === "*") {
|
|
481
|
+
result += " ";
|
|
482
|
+
index += 2;
|
|
483
|
+
while (index < text.length && !(text[index] === "*" && text[index + 1] === "/")) {
|
|
484
|
+
result += blankCommentChar(text[index]);
|
|
485
|
+
index += 1;
|
|
486
|
+
}
|
|
487
|
+
if (index < text.length) {
|
|
488
|
+
result += " ";
|
|
489
|
+
index += 1;
|
|
490
|
+
}
|
|
491
|
+
} else if (char === "/" && next === "/") {
|
|
492
|
+
result += " ";
|
|
493
|
+
index += 2;
|
|
494
|
+
while (index < text.length && text[index] !== "\n" && text[index] !== "\r") {
|
|
495
|
+
result += " ";
|
|
496
|
+
index += 1;
|
|
497
|
+
}
|
|
498
|
+
index -= 1;
|
|
499
|
+
} else {
|
|
500
|
+
result += char;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function blankCommentChar(char) {
|
|
507
|
+
return char === "\n" || char === "\r" ? char : " ";
|
|
463
508
|
}
|
|
464
509
|
|
|
465
510
|
function sortDetails(details) {
|
package/src/issues.js
CHANGED
|
@@ -23,17 +23,25 @@ export const knownIssueCodes = new Set([
|
|
|
23
23
|
"package-build-artifact-entrypoint",
|
|
24
24
|
"package-dependency-install-required",
|
|
25
25
|
"package-entrypoint-missing",
|
|
26
|
+
"package-install-metadata-incomplete",
|
|
26
27
|
"package-json-missing",
|
|
27
28
|
"package-manifest-version-drift",
|
|
29
|
+
"package-min-host-version-drift",
|
|
30
|
+
"package-npm-pack-entrypoint-missing",
|
|
31
|
+
"package-npm-pack-metadata-missing",
|
|
32
|
+
"package-npm-pack-unavailable",
|
|
28
33
|
"package-openclaw-entry-missing",
|
|
29
34
|
"package-openclaw-metadata-missing",
|
|
35
|
+
"package-openclaw-unsupported-metadata",
|
|
30
36
|
"package-plugin-api-compat-missing",
|
|
31
37
|
"package-typescript-source-entrypoint",
|
|
32
38
|
"provider-auth-env-vars",
|
|
33
39
|
"registration-capture-gap",
|
|
34
40
|
"runtime-tool-capture",
|
|
35
41
|
"reserved-sdk-import",
|
|
42
|
+
"security-manifest-schema-unavailable",
|
|
36
43
|
"sdk-export-missing",
|
|
44
|
+
"unrecognized-security-manifest",
|
|
37
45
|
]);
|
|
38
46
|
|
|
39
47
|
export const issueMetadataByCode = {
|
|
@@ -85,6 +93,12 @@ export const issueMetadataByCode = {
|
|
|
85
93
|
decision: "plugin-upstream-fix",
|
|
86
94
|
title: "plugin imports reserved bundled-plugin SDK compatibility subpaths",
|
|
87
95
|
},
|
|
96
|
+
"security-manifest-schema-unavailable": {
|
|
97
|
+
severity: "P3",
|
|
98
|
+
owner: "plugin",
|
|
99
|
+
decision: "plugin-upstream-fix",
|
|
100
|
+
title: "plugin security manifest references an unavailable schema",
|
|
101
|
+
},
|
|
88
102
|
"missing-compat-record": {
|
|
89
103
|
severity: "P1",
|
|
90
104
|
owner: "core",
|
|
@@ -127,6 +141,12 @@ export const issueMetadataByCode = {
|
|
|
127
141
|
decision: "plugin-upstream-fix",
|
|
128
142
|
title: "OpenClaw package entrypoint is missing",
|
|
129
143
|
},
|
|
144
|
+
"package-install-metadata-incomplete": {
|
|
145
|
+
severity: "P2",
|
|
146
|
+
owner: "plugin",
|
|
147
|
+
decision: "plugin-upstream-fix",
|
|
148
|
+
title: "OpenClaw package install metadata is incomplete",
|
|
149
|
+
},
|
|
130
150
|
"package-json-missing": {
|
|
131
151
|
severity: "P2",
|
|
132
152
|
owner: "plugin",
|
|
@@ -139,6 +159,30 @@ export const issueMetadataByCode = {
|
|
|
139
159
|
decision: "plugin-upstream-fix",
|
|
140
160
|
title: "package and manifest versions drift",
|
|
141
161
|
},
|
|
162
|
+
"package-min-host-version-drift": {
|
|
163
|
+
severity: "P2",
|
|
164
|
+
owner: "plugin",
|
|
165
|
+
decision: "plugin-upstream-fix",
|
|
166
|
+
title: "OpenClaw package minimum host version drifts from build target",
|
|
167
|
+
},
|
|
168
|
+
"package-npm-pack-entrypoint-missing": {
|
|
169
|
+
severity: "P1",
|
|
170
|
+
owner: "plugin",
|
|
171
|
+
decision: "plugin-upstream-fix",
|
|
172
|
+
title: "advertised npm artifact is missing OpenClaw entrypoints",
|
|
173
|
+
},
|
|
174
|
+
"package-npm-pack-metadata-missing": {
|
|
175
|
+
severity: "P2",
|
|
176
|
+
owner: "plugin",
|
|
177
|
+
decision: "plugin-upstream-fix",
|
|
178
|
+
title: "advertised npm artifact is missing OpenClaw metadata",
|
|
179
|
+
},
|
|
180
|
+
"package-npm-pack-unavailable": {
|
|
181
|
+
severity: "P1",
|
|
182
|
+
owner: "plugin",
|
|
183
|
+
decision: "plugin-upstream-fix",
|
|
184
|
+
title: "advertised npm artifact cannot be packed",
|
|
185
|
+
},
|
|
142
186
|
"package-openclaw-entry-missing": {
|
|
143
187
|
severity: "P2",
|
|
144
188
|
owner: "plugin",
|
|
@@ -151,6 +195,12 @@ export const issueMetadataByCode = {
|
|
|
151
195
|
decision: "plugin-upstream-fix",
|
|
152
196
|
title: "OpenClaw package metadata is missing",
|
|
153
197
|
},
|
|
198
|
+
"package-openclaw-unsupported-metadata": {
|
|
199
|
+
severity: "P2",
|
|
200
|
+
owner: "plugin",
|
|
201
|
+
decision: "plugin-upstream-fix",
|
|
202
|
+
title: "package declares unsupported OpenClaw metadata",
|
|
203
|
+
},
|
|
154
204
|
"package-plugin-api-compat-missing": {
|
|
155
205
|
severity: "P2",
|
|
156
206
|
owner: "plugin",
|
|
@@ -193,6 +243,12 @@ export const issueMetadataByCode = {
|
|
|
193
243
|
decision: "core-compat-adapter",
|
|
194
244
|
title: "fixture calls a registrar missing from target OpenClaw",
|
|
195
245
|
},
|
|
246
|
+
"unrecognized-security-manifest": {
|
|
247
|
+
severity: "P3",
|
|
248
|
+
owner: "plugin",
|
|
249
|
+
decision: "plugin-upstream-fix",
|
|
250
|
+
title: "plugin ships an unsupported security manifest",
|
|
251
|
+
},
|
|
196
252
|
};
|
|
197
253
|
|
|
198
254
|
export function buildIssues({ breakages = [], warnings = [], suggestions = [], targetOpenClaw, idPrefix = "CRABPOT" }) {
|
|
@@ -212,7 +268,7 @@ export function buildIssues({ breakages = [], warnings = [], suggestions = [], t
|
|
|
212
268
|
owner: finding.owner,
|
|
213
269
|
code: finding.code,
|
|
214
270
|
decision: finding.decision,
|
|
215
|
-
status: finding.severity === "P0" || finding.level === "breakage" ? "blocking" : "open",
|
|
271
|
+
status: finding.status ?? (finding.severity === "P0" || finding.level === "breakage" ? "blocking" : "open"),
|
|
216
272
|
issueClass: finding.issueClass,
|
|
217
273
|
live: finding.live,
|
|
218
274
|
deprecated: finding.deprecated,
|
|
@@ -220,6 +276,7 @@ export function buildIssues({ breakages = [], warnings = [], suggestions = [], t
|
|
|
220
276
|
title: issueTitle(finding),
|
|
221
277
|
evidence: finding.evidence ?? [],
|
|
222
278
|
compatRecord: finding.compatRecord ?? null,
|
|
279
|
+
runtimeCoverage: finding.runtimeCoverage ?? null,
|
|
223
280
|
}));
|
|
224
281
|
}
|
|
225
282
|
|
|
@@ -314,10 +371,18 @@ function issueClassFor(code, options) {
|
|
|
314
371
|
"manifest-unknown-fields",
|
|
315
372
|
"package-json-missing",
|
|
316
373
|
"package-manifest-version-drift",
|
|
374
|
+
"package-min-host-version-drift",
|
|
375
|
+
"package-npm-pack-entrypoint-missing",
|
|
376
|
+
"package-npm-pack-metadata-missing",
|
|
377
|
+
"package-npm-pack-unavailable",
|
|
317
378
|
"package-openclaw-entry-missing",
|
|
318
379
|
"package-openclaw-metadata-missing",
|
|
380
|
+
"package-openclaw-unsupported-metadata",
|
|
319
381
|
"package-plugin-api-compat-missing",
|
|
382
|
+
"package-install-metadata-incomplete",
|
|
320
383
|
"reserved-sdk-import",
|
|
384
|
+
"security-manifest-schema-unavailable",
|
|
385
|
+
"unrecognized-security-manifest",
|
|
321
386
|
].includes(code)
|
|
322
387
|
) {
|
|
323
388
|
return "upstream-metadata";
|