@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.
- package/CHANGELOG.md +18 -1
- 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 +16 -3
- package/src/compatibility-report.js +39 -3
- package/src/config.js +35 -5
- package/src/contract-probes.js +25 -1
- package/src/fixture-summary.js +406 -0
- package/src/import-loop-profile.js +227 -5
- package/src/index.js +7 -0
- package/src/inspector.js +35 -4
- package/src/issues.js +68 -3
- package/src/openclaw-target.js +79 -2
- package/src/platform-probes.js +29 -6
- package/src/prune-workspace-dev-deps-cli.js +23 -0
- package/src/report.js +19 -1
- package/src/runtime-reconciliation.js +124 -0
- package/src/sdk-mock.js +64 -6
- package/src/synthetic-probes.js +45 -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,25 +24,45 @@ 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");
|
|
33
38
|
const rssSampleCount = samples.reduce((sum, sample) => sum + (sample.rssSampleCount ?? (sample.peakRssMb > 0 ? 1 : 0)), 0);
|
|
34
39
|
const cpuSampleCount = samples.reduce((sum, sample) => sum + (sample.cpuSampleCount ?? 0), 0);
|
|
35
40
|
const statSampleCount = samples.reduce((sum, sample) => sum + (sample.statSampleCount ?? 0), 0);
|
|
36
41
|
return {
|
|
37
42
|
generatedAt: options.generatedAt ?? defaultImportLoopProfileOptions.generatedAt,
|
|
38
|
-
mode: options.mode ?? "
|
|
43
|
+
mode: options.mode ?? "baseline-adjusted-cold-capture-loop",
|
|
39
44
|
entrypoint,
|
|
45
|
+
baseline,
|
|
40
46
|
summary: {
|
|
41
47
|
runs,
|
|
48
|
+
baselineRuns: baseline.runs,
|
|
49
|
+
baselineFailCount: baseline.failCount,
|
|
42
50
|
p50WallMs: percentile(wallMs, 0.5),
|
|
43
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),
|
|
44
59
|
maxPeakRssMb: Math.max(0, ...samples.map((sample) => sample.peakRssMb)),
|
|
45
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,
|
|
46
66
|
statSampleCount,
|
|
47
67
|
rssSampleCount,
|
|
48
68
|
cpuSampleCount,
|
|
@@ -58,6 +78,9 @@ export function validateImportLoopProfile(report) {
|
|
|
58
78
|
if (report.summary.failCount > 0) {
|
|
59
79
|
errors.push(`import loop has ${report.summary.failCount} failed sample(s)`);
|
|
60
80
|
}
|
|
81
|
+
if ((report.summary.baselineFailCount ?? report.baseline?.failCount ?? 0) > 0) {
|
|
82
|
+
errors.push("import loop baseline capture failed");
|
|
83
|
+
}
|
|
61
84
|
if (report.summary.capturedCount < report.summary.runs) {
|
|
62
85
|
errors.push("import loop did not capture at least one contract per run");
|
|
63
86
|
}
|
|
@@ -93,6 +116,10 @@ export function renderImportLoopProfileMarkdown(report, options = {}) {
|
|
|
93
116
|
"",
|
|
94
117
|
markdownTable(summaryRows(report), ["Metric", "Value"]),
|
|
95
118
|
"",
|
|
119
|
+
"## Harness Baseline",
|
|
120
|
+
"",
|
|
121
|
+
markdownTable(baselineRows(report), ["Metric", "Value"]),
|
|
122
|
+
"",
|
|
96
123
|
"## Samples",
|
|
97
124
|
"",
|
|
98
125
|
markdownTable(
|
|
@@ -100,23 +127,132 @@ export function renderImportLoopProfileMarkdown(report, options = {}) {
|
|
|
100
127
|
sample.index,
|
|
101
128
|
sample.status,
|
|
102
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"),
|
|
103
135
|
`${sample.wallMs} ms`,
|
|
104
136
|
formatSampledMetric(sample.peakRssMb, sample.rssSampleCount),
|
|
105
137
|
formatSampledMetric(sample.cpuMsEstimate, sample.cpuSampleCount, "ms"),
|
|
106
138
|
`${sample.rssSampleCount ?? 0}/${sample.cpuSampleCount ?? 0}`,
|
|
107
139
|
sample.exitCode,
|
|
108
140
|
]),
|
|
109
|
-
[
|
|
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
|
+
],
|
|
110
156
|
),
|
|
111
157
|
].join("\n");
|
|
112
158
|
}
|
|
113
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
|
+
|
|
114
250
|
async function runCaptureSample(options) {
|
|
115
251
|
const outputDir = resolveFromRoot(
|
|
116
252
|
options.rootDir,
|
|
117
253
|
options.outputDir ?? defaultImportLoopProfileOptions.outputDir,
|
|
118
254
|
);
|
|
119
|
-
const outputPath = path.join(outputDir,
|
|
255
|
+
const outputPath = path.join(outputDir, `${options.sampleName ?? "capture"}-${options.index}.json`);
|
|
120
256
|
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
121
257
|
|
|
122
258
|
const command = buildCaptureCommand({ ...options, outputPath });
|
|
@@ -133,6 +269,7 @@ async function runCaptureSample(options) {
|
|
|
133
269
|
exitCode: profile.exitCode,
|
|
134
270
|
status: output?.status ?? "failed",
|
|
135
271
|
capturedCount: output?.captured?.length ?? 0,
|
|
272
|
+
openClawLifecycle: output?.openClawLifecycle ?? null,
|
|
136
273
|
wallMs: profile.wallMs,
|
|
137
274
|
peakRssMb: profile.peakRssMb,
|
|
138
275
|
peakCpuPercent: profile.peakCpuPercent,
|
|
@@ -147,10 +284,42 @@ async function runCaptureSample(options) {
|
|
|
147
284
|
function summaryRows(report) {
|
|
148
285
|
return [
|
|
149
286
|
["runs", report.summary.runs],
|
|
287
|
+
["baselineRuns", report.summary.baselineRuns ?? report.baseline?.runs ?? 0],
|
|
288
|
+
["baselineFailCount", report.summary.baselineFailCount ?? report.baseline?.failCount ?? 0],
|
|
150
289
|
["p50WallMs", report.summary.p50WallMs],
|
|
151
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
|
+
: []),
|
|
152
311
|
["maxPeakRssMb", formatSampledMetric(report.summary.maxPeakRssMb, report.summary.rssSampleCount)],
|
|
153
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
|
+
: []),
|
|
154
323
|
["statSampleCount", report.summary.statSampleCount ?? 0],
|
|
155
324
|
["rssSampleCount", report.summary.rssSampleCount ?? 0],
|
|
156
325
|
["cpuSampleCount", report.summary.cpuSampleCount ?? 0],
|
|
@@ -159,6 +328,23 @@ function summaryRows(report) {
|
|
|
159
328
|
];
|
|
160
329
|
}
|
|
161
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
|
+
|
|
162
348
|
function formatSampledMetric(value, count, unit = "MB") {
|
|
163
349
|
if ((count ?? 0) <= 0) {
|
|
164
350
|
return "n/a";
|
|
@@ -166,6 +352,42 @@ function formatSampledMetric(value, count, unit = "MB") {
|
|
|
166
352
|
return `${value} ${unit}`;
|
|
167
353
|
}
|
|
168
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
|
+
|
|
169
391
|
function buildCaptureCommand(options) {
|
|
170
392
|
if (typeof options.captureCommand === "function") {
|
|
171
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
|
@@ -12,7 +12,7 @@ import { buildCompatibilityReport, buildReport } from "./report.js";
|
|
|
12
12
|
|
|
13
13
|
const execFileAsync = promisify(execFile);
|
|
14
14
|
const registrationEquivalents = new Map([
|
|
15
|
-
["registerChannel", new Set(["createChatChannelPlugin", "defineChannelPluginEntry", "registerChannel"])],
|
|
15
|
+
["registerChannel", new Set(["createChatChannelPlugin", "defineBundledChannelEntry", "defineChannelPluginEntry", "registerChannel"])],
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
18
|
export async function inspectFixtureSet(config, options = {}) {
|
|
@@ -35,6 +35,7 @@ export async function inspectCompatibilityFixtureSet(config, options = {}) {
|
|
|
35
35
|
inspections,
|
|
36
36
|
failures,
|
|
37
37
|
generatedAt: options.generatedAt,
|
|
38
|
+
executionResults: options.executionResults,
|
|
38
39
|
targetOpenClaw,
|
|
39
40
|
buildFixtureReport: ({ fixture, inspection }) =>
|
|
40
41
|
buildCompatibilityFixtureReport({
|
|
@@ -146,6 +147,7 @@ export function inspectSourceText(text, filePath = "source.js") {
|
|
|
146
147
|
const hooks = collectDetailedMatches(searchableText, /\bapi\.on\(\s*["'`]([^"'`]+)["'`]/g, filePath, "name");
|
|
147
148
|
const registrations = [
|
|
148
149
|
...collectDetailedMatches(searchableText, /\bapi\.(register[A-Za-z0-9]+)\s*\(/g, filePath, "name"),
|
|
150
|
+
...collectDetailedMatches(searchableText, /\b(defineBundledChannelEntry)\s*\(/g, filePath, "name"),
|
|
149
151
|
...collectDetailedMatches(searchableText, /\b(defineChannelPluginEntry)\s*\(/g, filePath, "name"),
|
|
150
152
|
...collectDetailedMatches(searchableText, /\b(createChatChannelPlugin)\s*\(/g, filePath, "name"),
|
|
151
153
|
...collectDetailedMatches(searchableText, /\b(definePluginEntry)\s*\(/g, filePath, "name"),
|
|
@@ -471,9 +473,38 @@ function lineForOffset(text, offset) {
|
|
|
471
473
|
}
|
|
472
474
|
|
|
473
475
|
function stripComments(text) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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 : " ";
|
|
477
508
|
}
|
|
478
509
|
|
|
479
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",
|
|
@@ -170,10 +220,10 @@ export const issueMetadataByCode = {
|
|
|
170
220
|
title: "providerAuthEnvVars legacy manifest metadata must stay covered",
|
|
171
221
|
},
|
|
172
222
|
"registration-capture-gap": {
|
|
173
|
-
severity: "
|
|
223
|
+
severity: "P2",
|
|
174
224
|
owner: "inspector",
|
|
175
225
|
decision: "inspector-follow-up",
|
|
176
|
-
title: "runtime registrations need capture before contract judgment",
|
|
226
|
+
title: "runtime registrations need capture evidence before final contract judgment",
|
|
177
227
|
},
|
|
178
228
|
"runtime-tool-capture": {
|
|
179
229
|
severity: "P2",
|
|
@@ -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";
|
package/src/openclaw-target.js
CHANGED
|
@@ -106,12 +106,89 @@ export function openClawTargetPathCandidates(manifest, configuredPath) {
|
|
|
106
106
|
|
|
107
107
|
export function parseCompatRecordEntries(source) {
|
|
108
108
|
const entries = [];
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
let cursor = 0;
|
|
110
|
+
while (cursor < source.length) {
|
|
111
|
+
const codeProperty = readStringProperty(source, "code", cursor);
|
|
112
|
+
if (!codeProperty) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const statusProperty = readStringProperty(source, "status", codeProperty.end);
|
|
117
|
+
if (statusProperty) {
|
|
118
|
+
entries.push({ code: codeProperty.value, status: statusProperty.value });
|
|
119
|
+
cursor = statusProperty.end;
|
|
120
|
+
} else {
|
|
121
|
+
cursor = codeProperty.end;
|
|
122
|
+
}
|
|
111
123
|
}
|
|
112
124
|
return dedupeBy(entries, (entry) => entry.code).sort((left, right) => left.code.localeCompare(right.code));
|
|
113
125
|
}
|
|
114
126
|
|
|
127
|
+
function readStringProperty(source, property, fromIndex) {
|
|
128
|
+
const propertyIndex = findProperty(source, property, fromIndex);
|
|
129
|
+
if (propertyIndex === -1) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const colonIndex = source.indexOf(":", propertyIndex + property.length);
|
|
133
|
+
if (colonIndex === -1) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
let quoteIndex = colonIndex + 1;
|
|
137
|
+
while (quoteIndex < source.length && isWhitespace(source[quoteIndex])) {
|
|
138
|
+
quoteIndex += 1;
|
|
139
|
+
}
|
|
140
|
+
if (!isQuote(source[quoteIndex])) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return readQuotedValue(source, quoteIndex);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function findProperty(source, property, fromIndex) {
|
|
147
|
+
let index = source.indexOf(property, fromIndex);
|
|
148
|
+
while (index !== -1) {
|
|
149
|
+
const previous = index === 0 ? "" : source[index - 1];
|
|
150
|
+
const next = source[index + property.length] ?? "";
|
|
151
|
+
if (!isIdentifierChar(previous) && !isIdentifierChar(next)) {
|
|
152
|
+
return index;
|
|
153
|
+
}
|
|
154
|
+
index = source.indexOf(property, index + property.length);
|
|
155
|
+
}
|
|
156
|
+
return -1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function readQuotedValue(source, quoteIndex) {
|
|
160
|
+
const quote = source[quoteIndex];
|
|
161
|
+
let value = "";
|
|
162
|
+
for (let index = quoteIndex + 1; index < source.length; index += 1) {
|
|
163
|
+
const char = source[index];
|
|
164
|
+
if (char === "\\") {
|
|
165
|
+
value += source[index + 1] ?? "";
|
|
166
|
+
index += 1;
|
|
167
|
+
} else if (char === quote) {
|
|
168
|
+
return { value, end: index + 1 };
|
|
169
|
+
} else {
|
|
170
|
+
value += char;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isQuote(char) {
|
|
177
|
+
return char === '"' || char === "'" || char === "`";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function isIdentifierChar(char) {
|
|
181
|
+
if (char === "_" || char === "$") {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
const code = char.charCodeAt(0);
|
|
185
|
+
return (code >= 48 && code <= 57) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isWhitespace(char) {
|
|
189
|
+
return char === " " || char === "\n" || char === "\r" || char === "\t";
|
|
190
|
+
}
|
|
191
|
+
|
|
115
192
|
export function parsePluginSdkExports(packageJson) {
|
|
116
193
|
return Object.keys(packageJson.exports ?? {})
|
|
117
194
|
.filter((specifier) => specifier === "./plugin-sdk" || specifier.startsWith("./plugin-sdk/"))
|