@openclaw/plugin-inspector 0.3.17 → 0.3.19
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 +23 -1
- package/README.md +3 -3
- package/examples/github-actions-code-scanning.yml +3 -3
- package/examples/github-actions-plugin-inspector.yml +3 -3
- package/package.json +1 -1
- package/src/capture-api.js +140 -0
- package/src/init.js +3 -3
- package/src/issues.js +0 -4
- package/src/link-openclaw-workspace-cli.js +32 -0
- package/src/openclaw-target.js +27 -5
- 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,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.3.19 - 2026-07-27
|
|
4
|
+
|
|
5
|
+
### Highlights
|
|
6
|
+
|
|
7
|
+
- Restore silently-lost manifest contract findings after OpenClaw moved its public manifest types into `manifest-types.ts`.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Refresh generated and repository GitHub Actions workflows to the current checkout, setup-node, upload-artifact, and pnpm/action-setup releases.
|
|
12
|
+
|
|
13
|
+
## 0.3.18 - 2026-07-21
|
|
14
|
+
|
|
15
|
+
### Highlights
|
|
16
|
+
|
|
17
|
+
- Keep compatibility capture current with OpenClaw's evolving hook, gateway, workspace-linking, blob-store, and Zod runtime surfaces.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Mock the current plugin blob-store runtime so Diffs and other trusted plugins can be captured without persistent state.
|
|
22
|
+
- Parse OpenClaw hook names after their source declaration became private and added a TypeScript `satisfies` constraint.
|
|
23
|
+
- Run synthetic gateway lifecycle hooks around ordinary probes while preserving capture indexes and report order, so `gateway_stop` teardown cannot invalidate later compatibility checks.
|
|
24
|
+
- Link isolated plugin workspaces to the OpenClaw checkout without npm normalizing duplicated dependency and peer declarations back to registry ranges.
|
|
25
|
+
- Keep mocked Zod schemas chainable through unsupported methods such as `pipe()` and `catch()`.
|
|
4
26
|
|
|
5
27
|
## 0.3.17 - 2026-06-29
|
|
6
28
|
|
package/README.md
CHANGED
|
@@ -256,14 +256,14 @@ jobs:
|
|
|
256
256
|
check:
|
|
257
257
|
runs-on: ubuntu-latest
|
|
258
258
|
steps:
|
|
259
|
-
- uses: actions/checkout@
|
|
260
|
-
- uses: actions/setup-node@
|
|
259
|
+
- uses: actions/checkout@v7
|
|
260
|
+
- uses: actions/setup-node@v7
|
|
261
261
|
with:
|
|
262
262
|
node-version: 24
|
|
263
263
|
cache: npm
|
|
264
264
|
- run: npm ci
|
|
265
265
|
- run: npx @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute
|
|
266
|
-
- uses: actions/upload-artifact@
|
|
266
|
+
- uses: actions/upload-artifact@v7
|
|
267
267
|
if: always()
|
|
268
268
|
with:
|
|
269
269
|
name: plugin-inspector-reports
|
|
@@ -13,8 +13,8 @@ jobs:
|
|
|
13
13
|
check:
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
steps:
|
|
16
|
-
- uses: actions/checkout@
|
|
17
|
-
- uses: actions/setup-node@
|
|
16
|
+
- uses: actions/checkout@v7
|
|
17
|
+
- uses: actions/setup-node@v7
|
|
18
18
|
with:
|
|
19
19
|
node-version: 24
|
|
20
20
|
cache: npm
|
|
@@ -24,7 +24,7 @@ jobs:
|
|
|
24
24
|
if: always()
|
|
25
25
|
with:
|
|
26
26
|
sarif_file: reports/plugin-inspector.sarif
|
|
27
|
-
- uses: actions/upload-artifact@
|
|
27
|
+
- uses: actions/upload-artifact@v7
|
|
28
28
|
if: always()
|
|
29
29
|
with:
|
|
30
30
|
name: plugin-inspector-reports
|
|
@@ -9,14 +9,14 @@ jobs:
|
|
|
9
9
|
check:
|
|
10
10
|
runs-on: ubuntu-latest
|
|
11
11
|
steps:
|
|
12
|
-
- uses: actions/checkout@
|
|
13
|
-
- uses: actions/setup-node@
|
|
12
|
+
- uses: actions/checkout@v7
|
|
13
|
+
- uses: actions/setup-node@v7
|
|
14
14
|
with:
|
|
15
15
|
node-version: 24
|
|
16
16
|
cache: npm
|
|
17
17
|
- run: npm ci
|
|
18
18
|
- run: npx @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute
|
|
19
|
-
- uses: actions/upload-artifact@
|
|
19
|
+
- uses: actions/upload-artifact@v7
|
|
20
20
|
if: always()
|
|
21
21
|
with:
|
|
22
22
|
name: plugin-inspector-reports
|
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/init.js
CHANGED
|
@@ -108,14 +108,14 @@ jobs:
|
|
|
108
108
|
check:
|
|
109
109
|
runs-on: ubuntu-latest
|
|
110
110
|
steps:
|
|
111
|
-
- uses: actions/checkout@
|
|
112
|
-
- uses: actions/setup-node@
|
|
111
|
+
- uses: actions/checkout@v7
|
|
112
|
+
- uses: actions/setup-node@v7
|
|
113
113
|
with:
|
|
114
114
|
node-version: 24
|
|
115
115
|
cache: ${setup.cache}
|
|
116
116
|
${setup.corepack ? " - run: corepack enable\n" : ""} - run: ${setup.install}
|
|
117
117
|
- run: ${setup.exec} @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute
|
|
118
|
-
- uses: actions/upload-artifact@
|
|
118
|
+
- uses: actions/upload-artifact@v7
|
|
119
119
|
if: always()
|
|
120
120
|
with:
|
|
121
121
|
name: plugin-inspector-reports
|
package/src/issues.js
CHANGED
|
@@ -583,10 +583,6 @@ export function classifyIssueFinding(finding, targetOpenClaw, metadata = {}) {
|
|
|
583
583
|
};
|
|
584
584
|
}
|
|
585
585
|
|
|
586
|
-
export function isInspectorGapFinding(finding, targetOpenClaw) {
|
|
587
|
-
return issueMetadata(finding, targetOpenClaw).issueClass === "inspector-gap";
|
|
588
|
-
}
|
|
589
|
-
|
|
590
586
|
export function isAuthorFacingFinding(finding, targetOpenClaw) {
|
|
591
587
|
return Boolean(issueMetadata(finding, targetOpenClaw).authorRemediation);
|
|
592
588
|
}
|
|
@@ -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
|
@@ -30,19 +30,32 @@ export async function readOpenClawTargetSurface(options = {}) {
|
|
|
30
30
|
const hookTypesPath = path.join(resolvedPath, "src/plugins/hook-types.ts");
|
|
31
31
|
const apiBuilderPath = path.join(resolvedPath, "src/plugins/api-builder.ts");
|
|
32
32
|
const capturedRegistrationPath = path.join(resolvedPath, "src/plugins/captured-registration.ts");
|
|
33
|
-
const
|
|
33
|
+
const currentManifestTypesPath = path.join(resolvedPath, "src/plugins/manifest-types.ts");
|
|
34
|
+
const legacyManifestTypesPath = path.join(resolvedPath, "src/plugins/manifest.ts");
|
|
34
35
|
const pluginSdkEntrypointsPath = path.join(resolvedPath, "src/plugin-sdk/entrypoints.ts");
|
|
35
36
|
const packagePath = path.join(resolvedPath, "package.json");
|
|
36
37
|
|
|
37
38
|
const registrySource = await readFile(registryPath, "utf8");
|
|
38
39
|
const compatRecordEntries = parseCompatRecordEntries(registrySource);
|
|
39
40
|
const hookTypesSource = existsSync(hookTypesPath) ? await readFile(hookTypesPath, "utf8") : "";
|
|
40
|
-
const hookNames = hookTypesSource ?
|
|
41
|
+
const hookNames = hookTypesSource ? parseConstStringArray(hookTypesSource, "PLUGIN_HOOK_NAMES") : [];
|
|
41
42
|
const apiBuilderSource = existsSync(apiBuilderPath) ? await readFile(apiBuilderPath, "utf8") : "";
|
|
42
43
|
const apiRegistrars = apiBuilderSource ? parseApiRegistrars(apiBuilderSource) : [];
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
const currentManifestTypesSource = existsSync(currentManifestTypesPath)
|
|
45
|
+
? await readFile(currentManifestTypesPath, "utf8")
|
|
46
|
+
: "";
|
|
47
|
+
const legacyManifestTypesSource = existsSync(legacyManifestTypesPath)
|
|
48
|
+
? await readFile(legacyManifestTypesPath, "utf8")
|
|
49
|
+
: "";
|
|
50
|
+
const currentManifestFields = parseTypeFields(currentManifestTypesSource, "PluginManifest");
|
|
51
|
+
const legacyManifestFields = parseTypeFields(legacyManifestTypesSource, "PluginManifest");
|
|
52
|
+
const currentManifestContractFields = parseTypeFields(currentManifestTypesSource, "PluginManifestContracts");
|
|
53
|
+
const legacyManifestContractFields = parseTypeFields(legacyManifestTypesSource, "PluginManifestContracts");
|
|
54
|
+
const useCurrentManifestTypes = currentManifestFields.length > 0;
|
|
55
|
+
const manifestTypesPath = useCurrentManifestTypes ? currentManifestTypesPath : legacyManifestTypesPath;
|
|
56
|
+
const manifestFields = useCurrentManifestTypes ? currentManifestFields : legacyManifestFields;
|
|
57
|
+
const manifestContractFields =
|
|
58
|
+
currentManifestContractFields.length > 0 ? currentManifestContractFields : legacyManifestContractFields;
|
|
46
59
|
const capturedRegistrars = existsSync(capturedRegistrationPath)
|
|
47
60
|
? parseCapturedRegistrars(await readFile(capturedRegistrationPath, "utf8"))
|
|
48
61
|
: [];
|
|
@@ -198,6 +211,15 @@ export function parsePluginSdkExports(packageJson) {
|
|
|
198
211
|
|
|
199
212
|
export function parseExportedStringArray(source, exportName) {
|
|
200
213
|
const match = source.match(new RegExp(`export\\s+const\\s+${exportName}\\s*=\\s*\\[([\\s\\S]*?)\\]\\s+as\\s+const`));
|
|
214
|
+
return parseStringArrayMatch(match);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function parseConstStringArray(source, constName) {
|
|
218
|
+
const match = source.match(new RegExp(`(?:export\\s+)?const\\s+${constName}\\s*=\\s*\\[([\\s\\S]*?)\\]\\s+as\\s+const`));
|
|
219
|
+
return parseStringArrayMatch(match);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function parseStringArrayMatch(match) {
|
|
201
223
|
if (!match) {
|
|
202
224
|
return [];
|
|
203
225
|
}
|
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()),
|