@openclaw/plugin-inspector 0.3.16 → 0.3.18
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 +19 -1
- package/package.json +1 -1
- package/src/capture-api.js +140 -0
- package/src/fixture-summary.js +28 -3
- package/src/inspector.js +68 -13
- package/src/issues.js +60 -0
- package/src/link-openclaw-workspace-cli.js +32 -0
- package/src/openclaw-target.js +10 -1
- package/src/sdk-deprecation-rules.js +99 -36
- package/src/sdk-mock.js +2 -2
- package/src/synthetic-probes.js +39 -7
- package/src/workspace-plan.js +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.3.18 - 2026-07-21
|
|
4
|
+
|
|
5
|
+
### Highlights
|
|
6
|
+
|
|
7
|
+
- Keep compatibility capture current with OpenClaw's evolving hook, gateway, workspace-linking, blob-store, and Zod runtime surfaces.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Mock the current plugin blob-store runtime so Diffs and other trusted plugins can be captured without persistent state.
|
|
12
|
+
- Parse OpenClaw hook names after their source declaration became private and added a TypeScript `satisfies` constraint.
|
|
13
|
+
- Run synthetic gateway lifecycle hooks around ordinary probes while preserving capture indexes and report order, so `gateway_stop` teardown cannot invalidate later compatibility checks.
|
|
14
|
+
- Link isolated plugin workspaces to the OpenClaw checkout without npm normalizing duplicated dependency and peer declarations back to registry ranges.
|
|
15
|
+
- Keep mocked Zod schemas chainable through unsupported methods such as `pipe()` and `catch()`.
|
|
16
|
+
|
|
17
|
+
## 0.3.17 - 2026-06-29
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Detect deprecated session SDK read/write, file-path, and transcript helpers across source files, packaged `dist`/`build` artifacts, runtime session APIs, and dynamic SDK imports.
|
|
4
22
|
|
|
5
23
|
## 0.3.16 - 2026-06-23
|
|
6
24
|
|
package/package.json
CHANGED
package/src/capture-api.js
CHANGED
|
@@ -194,6 +194,8 @@ function registrationReturnValue(name, args, context) {
|
|
|
194
194
|
|
|
195
195
|
function createRuntimeContext(options) {
|
|
196
196
|
const runtime = options.runtime ?? {};
|
|
197
|
+
const blobStores = new Map();
|
|
198
|
+
const syncKeyedStores = new Map();
|
|
197
199
|
return {
|
|
198
200
|
...runtime,
|
|
199
201
|
agent: runtime.agent ?? {},
|
|
@@ -203,11 +205,149 @@ function createRuntimeContext(options) {
|
|
|
203
205
|
tts: runtime.tts ?? {},
|
|
204
206
|
state: {
|
|
205
207
|
resolveStateDir: () => options.stateDir ?? process.cwd(),
|
|
208
|
+
openBlobStore(storeOptions) {
|
|
209
|
+
const existing = blobStores.get(storeOptions.namespace);
|
|
210
|
+
if (existing) {
|
|
211
|
+
return existing;
|
|
212
|
+
}
|
|
213
|
+
const store = createBlobStoreContext(storeOptions);
|
|
214
|
+
blobStores.set(storeOptions.namespace, store);
|
|
215
|
+
return store;
|
|
216
|
+
},
|
|
217
|
+
openSyncKeyedStore({ namespace }) {
|
|
218
|
+
const existing = syncKeyedStores.get(namespace);
|
|
219
|
+
if (existing) {
|
|
220
|
+
return existing;
|
|
221
|
+
}
|
|
222
|
+
const store = createSyncKeyedStoreContext();
|
|
223
|
+
syncKeyedStores.set(namespace, store);
|
|
224
|
+
return store;
|
|
225
|
+
},
|
|
206
226
|
...(runtime.state ?? {}),
|
|
207
227
|
},
|
|
208
228
|
};
|
|
209
229
|
}
|
|
210
230
|
|
|
231
|
+
function createBlobStoreContext(options) {
|
|
232
|
+
const values = new Map();
|
|
233
|
+
const entryInfo = (key, entry) => ({
|
|
234
|
+
key,
|
|
235
|
+
metadata: entry.metadata,
|
|
236
|
+
sizeBytes: entry.bytes.byteLength,
|
|
237
|
+
createdAt: entry.createdAt,
|
|
238
|
+
...(entry.expiresAt === undefined ? {} : { expiresAt: entry.expiresAt }),
|
|
239
|
+
});
|
|
240
|
+
const read = (key) => {
|
|
241
|
+
const entry = values.get(key);
|
|
242
|
+
if (!entry) {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
return { ...entryInfo(key, entry), bytes: Uint8Array.from(entry.bytes) };
|
|
249
|
+
};
|
|
250
|
+
const register = async (key, bytes, metadata, registerOptions) => {
|
|
251
|
+
const createdAt = Date.now();
|
|
252
|
+
const ttlMs = registerOptions?.ttlMs ?? options.defaultTtlMs;
|
|
253
|
+
values.set(key, {
|
|
254
|
+
bytes: Uint8Array.from(bytes),
|
|
255
|
+
metadata,
|
|
256
|
+
createdAt,
|
|
257
|
+
...(ttlMs === undefined ? {} : { expiresAt: createdAt + ttlMs }),
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
return {
|
|
261
|
+
register,
|
|
262
|
+
async registerIfAbsent(key, bytes, metadata, registerOptions) {
|
|
263
|
+
if (values.has(key)) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
await register(key, bytes, metadata, registerOptions);
|
|
267
|
+
return true;
|
|
268
|
+
},
|
|
269
|
+
async lookup(key) {
|
|
270
|
+
return read(key);
|
|
271
|
+
},
|
|
272
|
+
async entries() {
|
|
273
|
+
return [...values.keys()].flatMap((key) => {
|
|
274
|
+
const entry = read(key);
|
|
275
|
+
if (!entry) {
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
278
|
+
const { bytes: _bytes, ...info } = entry;
|
|
279
|
+
return [info];
|
|
280
|
+
});
|
|
281
|
+
},
|
|
282
|
+
async delete(key) {
|
|
283
|
+
return values.delete(key);
|
|
284
|
+
},
|
|
285
|
+
async deleteExpiredKey(key) {
|
|
286
|
+
const entry = values.get(key);
|
|
287
|
+
if (!entry || entry.expiresAt === undefined || entry.expiresAt > Date.now()) {
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
values.delete(key);
|
|
291
|
+
return entryInfo(key, entry);
|
|
292
|
+
},
|
|
293
|
+
async deleteExpired() {
|
|
294
|
+
const expired = [];
|
|
295
|
+
for (const [key, entry] of values) {
|
|
296
|
+
if (entry.expiresAt === undefined || entry.expiresAt > Date.now()) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
values.delete(key);
|
|
300
|
+
expired.push(entryInfo(key, entry));
|
|
301
|
+
}
|
|
302
|
+
return expired;
|
|
303
|
+
},
|
|
304
|
+
async clear() {
|
|
305
|
+
values.clear();
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function createSyncKeyedStoreContext() {
|
|
311
|
+
const values = new Map();
|
|
312
|
+
return {
|
|
313
|
+
register(key, value) {
|
|
314
|
+
values.set(key, value);
|
|
315
|
+
},
|
|
316
|
+
registerIfAbsent(key, value) {
|
|
317
|
+
if (values.has(key)) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
values.set(key, value);
|
|
321
|
+
return true;
|
|
322
|
+
},
|
|
323
|
+
update(key, updateValue) {
|
|
324
|
+
const next = updateValue(values.get(key));
|
|
325
|
+
if (next === undefined) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
values.set(key, next);
|
|
329
|
+
return true;
|
|
330
|
+
},
|
|
331
|
+
lookup(key) {
|
|
332
|
+
return values.get(key);
|
|
333
|
+
},
|
|
334
|
+
consume(key) {
|
|
335
|
+
const value = values.get(key);
|
|
336
|
+
values.delete(key);
|
|
337
|
+
return value;
|
|
338
|
+
},
|
|
339
|
+
delete(key) {
|
|
340
|
+
return values.delete(key);
|
|
341
|
+
},
|
|
342
|
+
entries() {
|
|
343
|
+
return [...values].map(([key, value]) => ({ key, value, createdAt: 0 }));
|
|
344
|
+
},
|
|
345
|
+
clear() {
|
|
346
|
+
values.clear();
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
211
351
|
function createSecretContext(options) {
|
|
212
352
|
const secrets = new Map(Object.entries(options.secretValues ?? {}));
|
|
213
353
|
return {
|
package/src/fixture-summary.js
CHANGED
|
@@ -724,14 +724,39 @@ function classifySdkDeprecations({ fixture, inspection, fixtureReport, warnings,
|
|
|
724
724
|
decisions.push({
|
|
725
725
|
fixture: fixture.id,
|
|
726
726
|
decision: "core-compat-adapter",
|
|
727
|
-
seam:
|
|
728
|
-
action:
|
|
729
|
-
"Keep loadSessionStore compatibility active while plugin authors migrate to row-scoped session helpers.",
|
|
727
|
+
seam: sdkDeprecationSeamForCode(code),
|
|
728
|
+
action: sdkDeprecationActionForCode(code),
|
|
730
729
|
evidence: findings.map((finding) => finding.ref).join(", "),
|
|
731
730
|
});
|
|
732
731
|
}
|
|
733
732
|
}
|
|
734
733
|
|
|
734
|
+
function sdkDeprecationSeamForCode(code) {
|
|
735
|
+
if (code === "sdk-session-file-helper") {
|
|
736
|
+
return "session-file";
|
|
737
|
+
}
|
|
738
|
+
if (code === "sdk-session-transcript-file-target" || code === "sdk-session-transcript-low-level") {
|
|
739
|
+
return "session-transcript";
|
|
740
|
+
}
|
|
741
|
+
return "session-store";
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function sdkDeprecationActionForCode(code) {
|
|
745
|
+
if (code === "sdk-session-store-write") {
|
|
746
|
+
return "Keep whole-store session write compatibility active while plugin authors migrate to row-scoped session write helpers.";
|
|
747
|
+
}
|
|
748
|
+
if (code === "sdk-session-file-helper") {
|
|
749
|
+
return "Keep session file-path compatibility active while plugin authors migrate to session entry and transcript identity helpers.";
|
|
750
|
+
}
|
|
751
|
+
if (code === "sdk-session-transcript-file-target") {
|
|
752
|
+
return "Keep legacy transcript file target compatibility active while plugin authors migrate to structured transcript targets.";
|
|
753
|
+
}
|
|
754
|
+
if (code === "sdk-session-transcript-low-level") {
|
|
755
|
+
return "Keep low-level transcript write compatibility active while plugin authors migrate to structured transcript runtime helpers.";
|
|
756
|
+
}
|
|
757
|
+
return "Keep loadSessionStore compatibility active while plugin authors migrate to row-scoped session helpers.";
|
|
758
|
+
}
|
|
759
|
+
|
|
735
760
|
function classifySecurityManifestCoverage({ fixture, fixtureReport, warnings, decisions }) {
|
|
736
761
|
for (const securityManifest of fixtureReport.securityManifests ?? []) {
|
|
737
762
|
warnings.push({
|
package/src/inspector.js
CHANGED
|
@@ -95,10 +95,17 @@ export async function inspectPlugin(fixture, options = {}) {
|
|
|
95
95
|
return emptyInspection(fixture, "missing");
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
const
|
|
98
|
+
const packageInspection = await readPackageMetadata(config, checkoutPath, sourceRoot);
|
|
99
|
+
const includeBuildArtifacts = shouldScanBuildArtifacts(fixture, packageInspection);
|
|
100
|
+
const files = await listSourceFiles(sourceRoot, {
|
|
101
|
+
includeBuild: includeBuildArtifacts,
|
|
102
|
+
includeDist: includeBuildArtifacts,
|
|
103
|
+
});
|
|
99
104
|
if (sourceRoot !== checkoutPath) {
|
|
100
105
|
files.push(...(await listSourceFiles(checkoutPath, { shallowRootOnly: true })));
|
|
101
106
|
}
|
|
107
|
+
files.push(...packageInspection.entrypointFiles);
|
|
108
|
+
const sourceFiles = uniquePaths(files);
|
|
102
109
|
|
|
103
110
|
const hooks = new Set();
|
|
104
111
|
const registrations = new Set();
|
|
@@ -107,7 +114,7 @@ export async function inspectPlugin(fixture, options = {}) {
|
|
|
107
114
|
const sdkImportDetails = [];
|
|
108
115
|
const sdkDeprecationDetails = [];
|
|
109
116
|
|
|
110
|
-
for (const filePath of
|
|
117
|
+
for (const filePath of sourceFiles) {
|
|
111
118
|
const text = await readFile(filePath, "utf8");
|
|
112
119
|
const relativePath = path.relative(config.rootDir ?? process.cwd(), filePath);
|
|
113
120
|
const sourceInspection = inspectSourceText(text, relativePath);
|
|
@@ -129,8 +136,6 @@ export async function inspectPlugin(fixture, options = {}) {
|
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
const manifestInspection = await readManifestContracts(config, checkoutPath, sourceRoot);
|
|
132
|
-
const packageInspection = await readPackageMetadata(config, checkoutPath, sourceRoot);
|
|
133
|
-
|
|
134
139
|
return {
|
|
135
140
|
id: fixture.id,
|
|
136
141
|
status: "ok",
|
|
@@ -146,7 +151,7 @@ export async function inspectPlugin(fixture, options = {}) {
|
|
|
146
151
|
packageEntrypoints: packageInspection.entrypoints,
|
|
147
152
|
sdkImports: uniqueDetails(sdkImportDetails),
|
|
148
153
|
sdkDeprecations: uniqueSdkDeprecations(sdkDeprecationDetails),
|
|
149
|
-
sourceFiles:
|
|
154
|
+
sourceFiles: sourceFiles.map((filePath) => path.relative(config.rootDir ?? process.cwd(), filePath)).sort(),
|
|
150
155
|
};
|
|
151
156
|
}
|
|
152
157
|
|
|
@@ -418,18 +423,23 @@ async function readPackageMetadata(config, checkoutPath, sourceRoot) {
|
|
|
418
423
|
const files = [];
|
|
419
424
|
const errors = [];
|
|
420
425
|
const entrypoints = new Set();
|
|
426
|
+
const entrypointFiles = new Set();
|
|
421
427
|
|
|
422
428
|
for (const packageFile of packageFiles) {
|
|
423
429
|
const relativePath = path.relative(config.rootDir ?? process.cwd(), packageFile);
|
|
424
430
|
files.push(relativePath);
|
|
425
431
|
try {
|
|
426
432
|
const packageJson = JSON.parse(await readFile(packageFile, "utf8"));
|
|
427
|
-
|
|
428
|
-
collectEntrypoint(entrypoints, packageJson.
|
|
429
|
-
collectEntrypoint(entrypoints, packageJson.
|
|
430
|
-
collectEntrypoint(entrypoints, packageJson.openclaw?.
|
|
431
|
-
collectEntrypoint(entrypoints, packageJson.
|
|
432
|
-
collectEntrypoint(entrypoints, packageJson.
|
|
433
|
+
const packageDir = path.dirname(packageFile);
|
|
434
|
+
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.main);
|
|
435
|
+
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.module);
|
|
436
|
+
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.openclaw?.entry);
|
|
437
|
+
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.openclaw?.entrypoint);
|
|
438
|
+
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.openclaw?.setupEntry);
|
|
439
|
+
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.exports?.["."]?.import);
|
|
440
|
+
collectEntrypoint(entrypoints, entrypointFiles, packageDir, packageJson.exports?.["."]?.default);
|
|
441
|
+
collectEntrypoints(entrypoints, entrypointFiles, packageDir, packageJson.openclaw?.extensions);
|
|
442
|
+
collectEntrypoints(entrypoints, entrypointFiles, packageDir, packageJson.openclaw?.runtimeExtensions);
|
|
433
443
|
} catch {
|
|
434
444
|
errors.push(`${relativePath}: invalid JSON`);
|
|
435
445
|
}
|
|
@@ -439,13 +449,58 @@ async function readPackageMetadata(config, checkoutPath, sourceRoot) {
|
|
|
439
449
|
files: files.sort(),
|
|
440
450
|
errors,
|
|
441
451
|
entrypoints: [...entrypoints].sort(),
|
|
452
|
+
entrypointFiles: [...entrypointFiles].sort(),
|
|
442
453
|
};
|
|
443
454
|
}
|
|
444
455
|
|
|
445
|
-
function
|
|
456
|
+
function collectEntrypoints(entrypoints, entrypointFiles, packageDir, values) {
|
|
457
|
+
if (!Array.isArray(values)) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
for (const value of values) {
|
|
461
|
+
collectEntrypoint(entrypoints, entrypointFiles, packageDir, value);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function collectEntrypoint(entrypoints, entrypointFiles, packageDir, value) {
|
|
446
466
|
if (typeof value === "string" && value.length > 0) {
|
|
447
467
|
entrypoints.add(value);
|
|
468
|
+
for (const candidate of entrypointCandidates(packageDir, value)) {
|
|
469
|
+
if (existsSync(candidate) && isSourceFile(path.basename(candidate), candidate.split(path.sep).join("/"))) {
|
|
470
|
+
entrypointFiles.add(candidate);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function entrypointCandidates(packageDir, specifier) {
|
|
478
|
+
const resolved = path.resolve(packageDir, specifier);
|
|
479
|
+
if (path.extname(resolved)) {
|
|
480
|
+
return [resolved];
|
|
481
|
+
}
|
|
482
|
+
return [
|
|
483
|
+
resolved,
|
|
484
|
+
`${resolved}.js`,
|
|
485
|
+
`${resolved}.mjs`,
|
|
486
|
+
`${resolved}.cjs`,
|
|
487
|
+
`${resolved}.ts`,
|
|
488
|
+
path.join(resolved, "index.js"),
|
|
489
|
+
path.join(resolved, "index.mjs"),
|
|
490
|
+
path.join(resolved, "index.cjs"),
|
|
491
|
+
path.join(resolved, "index.ts"),
|
|
492
|
+
];
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function uniquePaths(paths) {
|
|
496
|
+
return [...new Set(paths)];
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function shouldScanBuildArtifacts(fixture, packageInspection) {
|
|
500
|
+
if (fixture.package) {
|
|
501
|
+
return true;
|
|
448
502
|
}
|
|
503
|
+
return packageInspection.entrypoints.some((entrypoint) => /(^|\/)(?:dist|build)\//.test(entrypoint));
|
|
449
504
|
}
|
|
450
505
|
|
|
451
506
|
async function listSourceFiles(root, options = {}) {
|
|
@@ -484,7 +539,7 @@ function shouldSkipDir(name, normalizedPath, options = {}) {
|
|
|
484
539
|
return (
|
|
485
540
|
name === "node_modules" ||
|
|
486
541
|
(!options.includeDist && name === "dist") ||
|
|
487
|
-
name === "build" ||
|
|
542
|
+
(!options.includeBuild && name === "build") ||
|
|
488
543
|
name === "coverage" ||
|
|
489
544
|
name === ".git" ||
|
|
490
545
|
name === "test" ||
|
package/src/issues.js
CHANGED
|
@@ -42,6 +42,10 @@ export const knownIssueCodes = new Set([
|
|
|
42
42
|
"reserved-sdk-import",
|
|
43
43
|
"security-manifest-schema-unavailable",
|
|
44
44
|
"sdk-load-session-store",
|
|
45
|
+
"sdk-session-file-helper",
|
|
46
|
+
"sdk-session-store-write",
|
|
47
|
+
"sdk-session-transcript-file-target",
|
|
48
|
+
"sdk-session-transcript-low-level",
|
|
45
49
|
"sdk-export-missing",
|
|
46
50
|
"unrecognized-security-manifest",
|
|
47
51
|
]);
|
|
@@ -124,6 +128,58 @@ export const issueMetadataByCode = {
|
|
|
124
128
|
],
|
|
125
129
|
),
|
|
126
130
|
},
|
|
131
|
+
"sdk-session-store-write": {
|
|
132
|
+
severity: "P2",
|
|
133
|
+
owner: "core",
|
|
134
|
+
decision: "core-compat-adapter",
|
|
135
|
+
title: "deprecated whole-store session write helper is still used",
|
|
136
|
+
authorRemediation: migrationRemediation(
|
|
137
|
+
"Replace deprecated whole-store session writes with row-scoped session helpers.",
|
|
138
|
+
[
|
|
139
|
+
"Use patchSessionEntry(...) when updating fields on an existing session entry.",
|
|
140
|
+
"Use upsertSessionEntry(...) when replacing or creating a session entry.",
|
|
141
|
+
],
|
|
142
|
+
),
|
|
143
|
+
},
|
|
144
|
+
"sdk-session-file-helper": {
|
|
145
|
+
severity: "P2",
|
|
146
|
+
owner: "core",
|
|
147
|
+
decision: "core-compat-adapter",
|
|
148
|
+
title: "deprecated session file-path helper is still used",
|
|
149
|
+
authorRemediation: migrationRemediation(
|
|
150
|
+
"Replace deprecated session file-path helpers with session entry and transcript identity APIs.",
|
|
151
|
+
[
|
|
152
|
+
"Use getSessionEntry(...) to read session metadata by agent/session identity.",
|
|
153
|
+
"Use patchSessionEntry(...) or upsertSessionEntry(...) to persist session metadata.",
|
|
154
|
+
],
|
|
155
|
+
),
|
|
156
|
+
},
|
|
157
|
+
"sdk-session-transcript-file-target": {
|
|
158
|
+
severity: "P2",
|
|
159
|
+
owner: "core",
|
|
160
|
+
decision: "core-compat-adapter",
|
|
161
|
+
title: "deprecated transcript file target helper is still used",
|
|
162
|
+
authorRemediation: migrationRemediation(
|
|
163
|
+
"Replace legacy transcript file targets with public transcript identity or target helpers.",
|
|
164
|
+
[
|
|
165
|
+
"Use resolveSessionTranscriptIdentity(...) when you only need public session identity.",
|
|
166
|
+
"Use resolveSessionTranscriptTarget(...) when you need a structured transcript operation target.",
|
|
167
|
+
],
|
|
168
|
+
),
|
|
169
|
+
},
|
|
170
|
+
"sdk-session-transcript-low-level": {
|
|
171
|
+
severity: "P2",
|
|
172
|
+
owner: "core",
|
|
173
|
+
decision: "core-compat-adapter",
|
|
174
|
+
title: "deprecated low-level transcript helper is still used",
|
|
175
|
+
authorRemediation: migrationRemediation(
|
|
176
|
+
"Replace low-level transcript writes with the structured transcript runtime helpers.",
|
|
177
|
+
[
|
|
178
|
+
"Use appendSessionTranscriptMessageByIdentity(...) for transcript appends.",
|
|
179
|
+
"Use publishSessionTranscriptUpdateByIdentity(...) for transcript update notifications.",
|
|
180
|
+
],
|
|
181
|
+
),
|
|
182
|
+
},
|
|
127
183
|
"sdk-export-missing": {
|
|
128
184
|
severity: "P1",
|
|
129
185
|
owner: "core",
|
|
@@ -568,6 +624,10 @@ function issueClassFor(code, options) {
|
|
|
568
624
|
"legacy-root-sdk-import",
|
|
569
625
|
"provider-auth-env-vars",
|
|
570
626
|
"sdk-load-session-store",
|
|
627
|
+
"sdk-session-file-helper",
|
|
628
|
+
"sdk-session-store-write",
|
|
629
|
+
"sdk-session-transcript-file-target",
|
|
630
|
+
"sdk-session-transcript-low-level",
|
|
571
631
|
].includes(code)
|
|
572
632
|
) {
|
|
573
633
|
return "deprecation-warning";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { writeJsonFileAtomic } from "./json-file.js";
|
|
6
|
+
|
|
7
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
8
|
+
const specifier = process.argv[2];
|
|
9
|
+
await linkOpenClawWorkspace(path.resolve(process.cwd(), "package.json"), specifier);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function linkOpenClawWorkspace(packageJsonPath, specifier) {
|
|
13
|
+
if (typeof specifier !== "string" || !specifier.startsWith("file:")) {
|
|
14
|
+
throw new TypeError("link-openclaw-workspace requires a file: dependency specifier");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
18
|
+
let linked = false;
|
|
19
|
+
for (const section of ["dependencies", "optionalDependencies"]) {
|
|
20
|
+
if (Object.hasOwn(packageJson[section] ?? {}, "openclaw")) {
|
|
21
|
+
packageJson[section].openclaw = specifier;
|
|
22
|
+
linked = true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!linked) {
|
|
27
|
+
packageJson.dependencies ??= {};
|
|
28
|
+
packageJson.dependencies.openclaw = specifier;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await writeJsonFileAtomic(packageJsonPath, packageJson);
|
|
32
|
+
}
|
package/src/openclaw-target.js
CHANGED
|
@@ -37,7 +37,7 @@ export async function readOpenClawTargetSurface(options = {}) {
|
|
|
37
37
|
const registrySource = await readFile(registryPath, "utf8");
|
|
38
38
|
const compatRecordEntries = parseCompatRecordEntries(registrySource);
|
|
39
39
|
const hookTypesSource = existsSync(hookTypesPath) ? await readFile(hookTypesPath, "utf8") : "";
|
|
40
|
-
const hookNames = hookTypesSource ?
|
|
40
|
+
const hookNames = hookTypesSource ? parseConstStringArray(hookTypesSource, "PLUGIN_HOOK_NAMES") : [];
|
|
41
41
|
const apiBuilderSource = existsSync(apiBuilderPath) ? await readFile(apiBuilderPath, "utf8") : "";
|
|
42
42
|
const apiRegistrars = apiBuilderSource ? parseApiRegistrars(apiBuilderSource) : [];
|
|
43
43
|
const manifestTypesSource = existsSync(manifestTypesPath) ? await readFile(manifestTypesPath, "utf8") : "";
|
|
@@ -198,6 +198,15 @@ export function parsePluginSdkExports(packageJson) {
|
|
|
198
198
|
|
|
199
199
|
export function parseExportedStringArray(source, exportName) {
|
|
200
200
|
const match = source.match(new RegExp(`export\\s+const\\s+${exportName}\\s*=\\s*\\[([\\s\\S]*?)\\]\\s+as\\s+const`));
|
|
201
|
+
return parseStringArrayMatch(match);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parseConstStringArray(source, constName) {
|
|
205
|
+
const match = source.match(new RegExp(`(?:export\\s+)?const\\s+${constName}\\s*=\\s*\\[([\\s\\S]*?)\\]\\s+as\\s+const`));
|
|
206
|
+
return parseStringArrayMatch(match);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function parseStringArrayMatch(match) {
|
|
201
210
|
if (!match) {
|
|
202
211
|
return [];
|
|
203
212
|
}
|
|
@@ -1,16 +1,53 @@
|
|
|
1
|
-
const
|
|
1
|
+
const sessionStoreReadReplacement =
|
|
2
2
|
"getSessionEntry(...) / listSessionEntries(...) for reads and patchSessionEntry(...) / upsertSessionEntry(...) for writes";
|
|
3
|
+
const sessionStoreWriteReplacement = "patchSessionEntry(...) / upsertSessionEntry(...) for row-scoped writes";
|
|
4
|
+
const sessionFileReplacement = "session entries and transcript identity helpers instead of persisted file paths";
|
|
5
|
+
const sessionTranscriptReplacement =
|
|
6
|
+
"resolveSessionTranscriptTarget(...), appendSessionTranscriptMessageByIdentity(...), and publishSessionTranscriptUpdateByIdentity(...)";
|
|
3
7
|
|
|
4
|
-
const
|
|
8
|
+
const sdkSessionSpecifiers = new Set([
|
|
5
9
|
"openclaw/plugin-sdk/config-runtime",
|
|
10
|
+
"openclaw/plugin-sdk/mattermost",
|
|
11
|
+
"openclaw/plugin-sdk/agent-harness-runtime",
|
|
6
12
|
"openclaw/plugin-sdk/session-store-runtime",
|
|
13
|
+
"openclaw/plugin-sdk/session-transcript-runtime",
|
|
7
14
|
]);
|
|
8
15
|
|
|
9
16
|
export const pluginSdkDeprecationRules = [
|
|
10
17
|
{
|
|
11
18
|
code: "sdk-load-session-store",
|
|
19
|
+
symbols: new Set(["loadSessionStore"]),
|
|
12
20
|
title: "deprecated whole-store session helper is still used",
|
|
13
|
-
replacement:
|
|
21
|
+
replacement: sessionStoreReadReplacement,
|
|
22
|
+
message: (symbol, replacement) => `${symbol} keeps the legacy whole-store session shape; use ${replacement}.`,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
code: "sdk-session-store-write",
|
|
26
|
+
symbols: new Set(["saveSessionStore", "updateSessionStore"]),
|
|
27
|
+
title: "deprecated whole-store session write helper is still used",
|
|
28
|
+
replacement: sessionStoreWriteReplacement,
|
|
29
|
+
message: (symbol, replacement) => `${symbol} writes the legacy whole-store session shape; use ${replacement}.`,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
code: "sdk-session-file-helper",
|
|
33
|
+
symbols: new Set(["resolveSessionFilePath", "resolveAndPersistSessionFile"]),
|
|
34
|
+
title: "deprecated session file-path helper is still used",
|
|
35
|
+
replacement: sessionFileReplacement,
|
|
36
|
+
message: (symbol, replacement) => `${symbol} depends on legacy session transcript file paths; use ${replacement}.`,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
code: "sdk-session-transcript-file-target",
|
|
40
|
+
symbols: new Set(["resolveSessionTranscriptLegacyFileTarget"]),
|
|
41
|
+
title: "deprecated transcript file target helper is still used",
|
|
42
|
+
replacement: "resolveSessionTranscriptTarget(...) or resolveSessionTranscriptIdentity(...)",
|
|
43
|
+
message: (symbol, replacement) => `${symbol} exposes legacy transcript file targets; use ${replacement}.`,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
code: "sdk-session-transcript-low-level",
|
|
47
|
+
symbols: new Set(["appendSessionTranscriptMessage", "emitSessionTranscriptUpdate"]),
|
|
48
|
+
title: "deprecated low-level transcript helper is still used",
|
|
49
|
+
replacement: sessionTranscriptReplacement,
|
|
50
|
+
message: (symbol, replacement) => `${symbol} bypasses the structured transcript runtime surface; use ${replacement}.`,
|
|
14
51
|
},
|
|
15
52
|
];
|
|
16
53
|
|
|
@@ -18,9 +55,7 @@ export function inspectSdkDeprecations(text, filePath = "source.js", rules = plu
|
|
|
18
55
|
const findings = [];
|
|
19
56
|
|
|
20
57
|
for (const rule of rules) {
|
|
21
|
-
|
|
22
|
-
collectLoadSessionStoreDeprecations(findings, { text, filePath, rule });
|
|
23
|
-
}
|
|
58
|
+
collectSdkHelperDeprecations(findings, { text, filePath, rule });
|
|
24
59
|
}
|
|
25
60
|
|
|
26
61
|
return uniqueFindings(findings)
|
|
@@ -28,12 +63,13 @@ export function inspectSdkDeprecations(text, filePath = "source.js", rules = plu
|
|
|
28
63
|
.map(({ offset, ...finding }) => finding);
|
|
29
64
|
}
|
|
30
65
|
|
|
31
|
-
function
|
|
66
|
+
function collectSdkHelperDeprecations(findings, context) {
|
|
32
67
|
collectNamedImportDeprecations(findings, context);
|
|
33
68
|
collectNamedReexportDeprecations(findings, context);
|
|
34
69
|
collectNamedRequireDeprecations(findings, context);
|
|
35
70
|
collectNamespaceUsageDeprecations(findings, context);
|
|
36
71
|
collectNamespaceRequireDeprecations(findings, context);
|
|
72
|
+
collectDynamicImportNamespaceDeprecations(findings, context);
|
|
37
73
|
collectRuntimeUsageDeprecations(findings, context);
|
|
38
74
|
collectRuntimeAliasUsageDeprecations(findings, context);
|
|
39
75
|
}
|
|
@@ -43,16 +79,17 @@ function collectNamedImportDeprecations(findings, context) {
|
|
|
43
79
|
/\bimport\s+(?:type\s+)?(?:[A-Za-z_$][\w$]*\s*,\s*)?{([^}]+)}\s*from\s*["'`]([^"'`]+)["'`]/g;
|
|
44
80
|
for (const match of context.text.matchAll(regex)) {
|
|
45
81
|
const specifier = match[2];
|
|
46
|
-
if (!
|
|
82
|
+
if (!sdkSessionSpecifiers.has(specifier)) {
|
|
47
83
|
continue;
|
|
48
84
|
}
|
|
49
85
|
for (const binding of parseNamedBindings(match[1])) {
|
|
50
|
-
if (binding.exported
|
|
86
|
+
if (!context.rule.symbols.has(binding.exported)) {
|
|
51
87
|
continue;
|
|
52
88
|
}
|
|
53
89
|
findings.push(
|
|
54
90
|
buildFinding(context.rule, {
|
|
55
|
-
surface: `${specifier} import`,
|
|
91
|
+
surface: `${specifier} ${binding.exported} import`,
|
|
92
|
+
symbol: binding.exported,
|
|
56
93
|
sourceText: context.text,
|
|
57
94
|
filePath: context.filePath,
|
|
58
95
|
offset: (match.index ?? 0) + match[0].lastIndexOf(binding.local),
|
|
@@ -66,16 +103,17 @@ function collectNamedReexportDeprecations(findings, context) {
|
|
|
66
103
|
const regex = /\bexport\s*{([^}]+)}\s*from\s*["'`]([^"'`]+)["'`]/g;
|
|
67
104
|
for (const match of context.text.matchAll(regex)) {
|
|
68
105
|
const specifier = match[2];
|
|
69
|
-
if (!
|
|
106
|
+
if (!sdkSessionSpecifiers.has(specifier)) {
|
|
70
107
|
continue;
|
|
71
108
|
}
|
|
72
109
|
for (const binding of parseNamedBindings(match[1])) {
|
|
73
|
-
if (binding.exported
|
|
110
|
+
if (!context.rule.symbols.has(binding.exported)) {
|
|
74
111
|
continue;
|
|
75
112
|
}
|
|
76
113
|
findings.push(
|
|
77
114
|
buildFinding(context.rule, {
|
|
78
|
-
surface: `${specifier} re-export`,
|
|
115
|
+
surface: `${specifier} ${binding.exported} re-export`,
|
|
116
|
+
symbol: binding.exported,
|
|
79
117
|
sourceText: context.text,
|
|
80
118
|
filePath: context.filePath,
|
|
81
119
|
offset: (match.index ?? 0) + match[0].lastIndexOf(binding.local),
|
|
@@ -89,16 +127,17 @@ function collectNamedRequireDeprecations(findings, context) {
|
|
|
89
127
|
const regex = /\b(?:const|let|var)\s+{([^}]+)}\s*=\s*require\(\s*["'`]([^"'`]+)["'`]\s*\)/g;
|
|
90
128
|
for (const match of context.text.matchAll(regex)) {
|
|
91
129
|
const specifier = match[2];
|
|
92
|
-
if (!
|
|
130
|
+
if (!sdkSessionSpecifiers.has(specifier)) {
|
|
93
131
|
continue;
|
|
94
132
|
}
|
|
95
133
|
for (const binding of parseNamedBindings(match[1], { aliasSeparator: ":" })) {
|
|
96
|
-
if (binding.exported
|
|
134
|
+
if (!context.rule.symbols.has(binding.exported)) {
|
|
97
135
|
continue;
|
|
98
136
|
}
|
|
99
137
|
findings.push(
|
|
100
138
|
buildFinding(context.rule, {
|
|
101
|
-
surface: `${specifier} require`,
|
|
139
|
+
surface: `${specifier} ${binding.exported} require`,
|
|
140
|
+
symbol: binding.exported,
|
|
102
141
|
sourceText: context.text,
|
|
103
142
|
filePath: context.filePath,
|
|
104
143
|
offset: (match.index ?? 0) + match[0].lastIndexOf(binding.local),
|
|
@@ -109,21 +148,24 @@ function collectNamedRequireDeprecations(findings, context) {
|
|
|
109
148
|
}
|
|
110
149
|
|
|
111
150
|
function collectMemberCallDeprecations(findings, context, options) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
151
|
+
for (const symbol of context.rule.symbols) {
|
|
152
|
+
forEachMethodCall(context.text, symbol, (offset) => {
|
|
153
|
+
// Normalize transparent parentheses and optional-chained member links before matching.
|
|
154
|
+
const receiver = readNormalizedCallReceiver(context.text, offset);
|
|
155
|
+
if (!receiver || !options.receiverMatcher(receiver)) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
findings.push(
|
|
159
|
+
buildFinding(context.rule, {
|
|
160
|
+
surface: `${options.surface} ${symbol}`,
|
|
161
|
+
symbol,
|
|
162
|
+
sourceText: context.text,
|
|
163
|
+
filePath: context.filePath,
|
|
164
|
+
offset,
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
127
169
|
}
|
|
128
170
|
|
|
129
171
|
function forEachMethodCall(text, methodName, visit) {
|
|
@@ -259,7 +301,11 @@ function isIdentifierBoundary(text, offset) {
|
|
|
259
301
|
}
|
|
260
302
|
|
|
261
303
|
function isRuntimeSessionReceiver(receiver) {
|
|
262
|
-
return
|
|
304
|
+
return (
|
|
305
|
+
/(?:^|\.)(?:[A-Za-z_$][A-Za-z0-9_$]*|this)\.runtime\.agent\.session$/.test(receiver) ||
|
|
306
|
+
/^(?:runtime|[A-Za-z_$][A-Za-z0-9_$]*Runtime)\.agent\.session$/.test(receiver) ||
|
|
307
|
+
/^(?:agentRuntime|[A-Za-z_$][A-Za-z0-9_$]*AgentRuntime)\.session$/.test(receiver)
|
|
308
|
+
);
|
|
263
309
|
}
|
|
264
310
|
|
|
265
311
|
function collectNamespaceUsageDeprecations(findings, context) {
|
|
@@ -267,7 +313,7 @@ function collectNamespaceUsageDeprecations(findings, context) {
|
|
|
267
313
|
for (const match of context.text.matchAll(regex)) {
|
|
268
314
|
const local = match[1];
|
|
269
315
|
const specifier = match[2];
|
|
270
|
-
if (!
|
|
316
|
+
if (!sdkSessionSpecifiers.has(specifier)) {
|
|
271
317
|
continue;
|
|
272
318
|
}
|
|
273
319
|
collectMemberCallDeprecations(findings, context, {
|
|
@@ -282,7 +328,7 @@ function collectNamespaceRequireDeprecations(findings, context) {
|
|
|
282
328
|
for (const match of context.text.matchAll(regex)) {
|
|
283
329
|
const local = match[1];
|
|
284
330
|
const specifier = match[2];
|
|
285
|
-
if (!
|
|
331
|
+
if (!sdkSessionSpecifiers.has(specifier)) {
|
|
286
332
|
continue;
|
|
287
333
|
}
|
|
288
334
|
collectMemberCallDeprecations(findings, context, {
|
|
@@ -292,6 +338,22 @@ function collectNamespaceRequireDeprecations(findings, context) {
|
|
|
292
338
|
}
|
|
293
339
|
}
|
|
294
340
|
|
|
341
|
+
function collectDynamicImportNamespaceDeprecations(findings, context) {
|
|
342
|
+
const regex =
|
|
343
|
+
/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:await\s+)?import\(\s*["'`]([^"'`]+)["'`]\s*\)/g;
|
|
344
|
+
for (const match of context.text.matchAll(regex)) {
|
|
345
|
+
const local = match[1];
|
|
346
|
+
const specifier = match[2];
|
|
347
|
+
if (!sdkSessionSpecifiers.has(specifier)) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
collectMemberCallDeprecations(findings, context, {
|
|
351
|
+
receiverMatcher: (receiver) => receiver === local,
|
|
352
|
+
surface: `${specifier} dynamic import namespace access`,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
295
357
|
function collectRuntimeUsageDeprecations(findings, context) {
|
|
296
358
|
collectMemberCallDeprecations(findings, context, {
|
|
297
359
|
receiverMatcher: isRuntimeSessionReceiver,
|
|
@@ -509,10 +571,11 @@ function buildFinding(rule, details) {
|
|
|
509
571
|
const refLine = lineForOffset(details.sourceText, details.offset);
|
|
510
572
|
return {
|
|
511
573
|
code: rule.code,
|
|
574
|
+
symbol: details.symbol,
|
|
512
575
|
surface: details.surface,
|
|
513
576
|
replacement: rule.replacement,
|
|
514
577
|
ref: `${details.filePath}:${refLine}`,
|
|
515
|
-
message:
|
|
578
|
+
message: rule.message(details.symbol, rule.replacement),
|
|
516
579
|
offset: details.offset,
|
|
517
580
|
};
|
|
518
581
|
}
|
|
@@ -520,7 +583,7 @@ function buildFinding(rule, details) {
|
|
|
520
583
|
function uniqueFindings(findings) {
|
|
521
584
|
const byKey = new Map();
|
|
522
585
|
for (const finding of findings) {
|
|
523
|
-
byKey.set(`${finding.code}:${finding.surface}:${finding.ref}`, finding);
|
|
586
|
+
byKey.set(`${finding.code}:${finding.symbol}:${finding.surface}:${finding.ref}`, finding);
|
|
524
587
|
}
|
|
525
588
|
return [...byKey.values()];
|
|
526
589
|
}
|
package/src/sdk-mock.js
CHANGED
|
@@ -968,11 +968,11 @@ function createSchema(defaultValue, shape) {
|
|
|
968
968
|
},
|
|
969
969
|
};
|
|
970
970
|
return new Proxy(schema, {
|
|
971
|
-
get(target, property) {
|
|
971
|
+
get(target, property, receiver) {
|
|
972
972
|
if (property in target) {
|
|
973
973
|
return target[property];
|
|
974
974
|
}
|
|
975
|
-
return () =>
|
|
975
|
+
return () => receiver;
|
|
976
976
|
},
|
|
977
977
|
});
|
|
978
978
|
}
|
package/src/synthetic-probes.js
CHANGED
|
@@ -236,6 +236,11 @@ export const syntheticRegistrationExecutionProfiles = {
|
|
|
236
236
|
callableProperties: [],
|
|
237
237
|
reason: "session actions are captured as registration metadata before session runtime execution",
|
|
238
238
|
},
|
|
239
|
+
registerSessionCatalog: {
|
|
240
|
+
mode: "metadata-only",
|
|
241
|
+
callableProperties: [],
|
|
242
|
+
reason: "session catalogs are captured as registration metadata before catalog runtime execution",
|
|
243
|
+
},
|
|
239
244
|
registerService: {
|
|
240
245
|
mode: "lifecycle-opt-in",
|
|
241
246
|
callableProperties: ["start", "stop", "dispose"],
|
|
@@ -591,23 +596,37 @@ export async function runCapturedSyntheticProbes(capture, options = {}) {
|
|
|
591
596
|
const hookContexts = options.hookContexts ?? defaultSyntheticHookContexts;
|
|
592
597
|
const captured = capture.captured ?? [];
|
|
593
598
|
const retained = new Map((capture.retained ?? []).map((item) => [item.captureIndex, item]));
|
|
594
|
-
const
|
|
599
|
+
const resultsByCaptureIndex = new Map();
|
|
600
|
+
const executionEntries = captured
|
|
601
|
+
.map((entry, captureIndex) => ({ entry, captureIndex }))
|
|
602
|
+
.sort(
|
|
603
|
+
(left, right) =>
|
|
604
|
+
syntheticLifecyclePhase(left.entry) - syntheticLifecyclePhase(right.entry) ||
|
|
605
|
+
left.captureIndex - right.captureIndex,
|
|
606
|
+
);
|
|
595
607
|
|
|
596
|
-
for (
|
|
597
|
-
const
|
|
598
|
-
const retainedEntry = retained.get(index);
|
|
608
|
+
for (const { entry, captureIndex } of executionEntries) {
|
|
609
|
+
const retainedEntry = retained.get(captureIndex);
|
|
599
610
|
if (!retainedEntry) {
|
|
600
|
-
|
|
611
|
+
resultsByCaptureIndex.set(captureIndex, [blockedResult(entry, captureIndex, "handler retention was not enabled")]);
|
|
601
612
|
continue;
|
|
602
613
|
}
|
|
603
614
|
if (entry.kind === "hook") {
|
|
604
|
-
|
|
615
|
+
resultsByCaptureIndex.set(captureIndex, [
|
|
616
|
+
await runHookProbe(entry, retainedEntry, captureIndex, { hookEvents, hookContexts }),
|
|
617
|
+
]);
|
|
605
618
|
continue;
|
|
606
619
|
}
|
|
607
620
|
if (entry.kind === "registration") {
|
|
608
|
-
|
|
621
|
+
resultsByCaptureIndex.set(
|
|
622
|
+
captureIndex,
|
|
623
|
+
await runRegistrationProbes(entry, retainedEntry, captureIndex, options),
|
|
624
|
+
);
|
|
609
625
|
}
|
|
610
626
|
}
|
|
627
|
+
// Execute in lifecycle order, but keep report rows and captureIndex values
|
|
628
|
+
// stable for downstream consumers that compare artifacts across runs.
|
|
629
|
+
const results = captured.flatMap((_, captureIndex) => resultsByCaptureIndex.get(captureIndex) ?? []);
|
|
611
630
|
|
|
612
631
|
return {
|
|
613
632
|
entrypoint: capture.entrypoint,
|
|
@@ -622,6 +641,19 @@ export async function runCapturedSyntheticProbes(capture, options = {}) {
|
|
|
622
641
|
};
|
|
623
642
|
}
|
|
624
643
|
|
|
644
|
+
function syntheticLifecyclePhase(entry) {
|
|
645
|
+
if (entry.kind !== "hook") {
|
|
646
|
+
return 1;
|
|
647
|
+
}
|
|
648
|
+
if (entry.name === "gateway_start") {
|
|
649
|
+
return 0;
|
|
650
|
+
}
|
|
651
|
+
if (entry.name === "gateway_stop") {
|
|
652
|
+
return 2;
|
|
653
|
+
}
|
|
654
|
+
return 1;
|
|
655
|
+
}
|
|
656
|
+
|
|
625
657
|
export function renderSyntheticProbeMarkdown(plan, options = {}) {
|
|
626
658
|
return [
|
|
627
659
|
`# ${options.title ?? "Plugin Inspector Synthetic Probes"}`,
|
package/src/workspace-plan.js
CHANGED
|
@@ -241,9 +241,10 @@ async function buildEntrypointPlan({ fixtureId, entrypoint, packageSummary, pack
|
|
|
241
241
|
});
|
|
242
242
|
|
|
243
243
|
if (requiredCapabilities.includes("target-openclaw-link")) {
|
|
244
|
+
const targetSpecifier = `file:${targetOpenClawWorkspacePath(settings, fixtureId, targetOpenClawPath)}`;
|
|
244
245
|
steps.push({
|
|
245
246
|
kind: "link-openclaw",
|
|
246
|
-
command:
|
|
247
|
+
command: `node ${shellQuote(helperScript(settings, workspacePath, settings.linkOpenClawWorkspaceScript, "link-openclaw-workspace-cli.js"))} ${shellQuote(targetSpecifier)}`,
|
|
247
248
|
cwd: workspacePath,
|
|
248
249
|
reason: "link the plugin's openclaw peer dependency to the target checkout under test",
|
|
249
250
|
});
|
|
@@ -325,6 +326,7 @@ function workspaceSettings(options) {
|
|
|
325
326
|
return {
|
|
326
327
|
captureScript: options.captureScript ?? defaultWorkspacePlanOptions.captureScript,
|
|
327
328
|
defaultTargetOpenClawWorkspacePath: options.defaultTargetOpenClawWorkspacePath ?? "../../../openclaw",
|
|
329
|
+
linkOpenClawWorkspaceScript: options.linkOpenClawWorkspaceScript,
|
|
328
330
|
optInEnv: options.optInEnv ?? defaultWorkspacePlanOptions.optInEnv,
|
|
329
331
|
resultsRoot: repoRelative(options.resultsRoot ?? defaultWorkspacePlanOptions.resultsRoot),
|
|
330
332
|
rootDir: path.resolve(options.rootDir ?? process.cwd()),
|