@openclaw/plugin-inspector 0.3.6 → 0.3.8
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 +12 -0
- package/package.json +1 -1
- package/src/capture-api.js +1 -0
- package/src/capture-config.js +126 -0
- package/src/contract-probes.js +0 -1
- package/src/inspector.js +8 -2
- package/src/issues.js +2 -2
- package/src/mock-sdk-capture-runner.js +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.3.8 - 2026-05-03
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Synthesize manifest config for isolated runtime capture so configured hooks can be observed without credentials.
|
|
10
|
+
|
|
11
|
+
## 0.3.7 - 2026-05-03
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Downgrade `registration-capture-gap` to advisory severity so missing capture evidence no longer reports as a P1 plugin contract risk.
|
|
16
|
+
|
|
5
17
|
## 0.3.6 - 2026-05-03
|
|
6
18
|
|
|
7
19
|
### Changed
|
package/package.json
CHANGED
package/src/capture-api.js
CHANGED
|
@@ -148,6 +148,7 @@ export function createCaptureContext(options = {}) {
|
|
|
148
148
|
config: options.config ?? {},
|
|
149
149
|
logger: options.logger ?? console,
|
|
150
150
|
pluginConfig: options.pluginConfig ?? {},
|
|
151
|
+
resolvePath: options.resolvePath ?? ((value) => value),
|
|
151
152
|
runtime: options.runtime ?? createRuntimeContext(options),
|
|
152
153
|
secrets: options.secrets ?? createSecretContext(options),
|
|
153
154
|
store: options.store ?? createStoreContext(options),
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export async function captureApiOptionsForPlugin(apiOptions = {}, options = {}) {
|
|
5
|
+
if (apiOptions.pluginConfig !== undefined || !options.pluginRoot) {
|
|
6
|
+
return apiOptions;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const pluginConfig = await readSamplePluginConfig(options.pluginRoot);
|
|
10
|
+
if (pluginConfig === undefined) {
|
|
11
|
+
return apiOptions;
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
...apiOptions,
|
|
15
|
+
pluginConfig,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function readSamplePluginConfig(pluginRoot) {
|
|
20
|
+
const manifestPath = path.join(pluginRoot, "openclaw.plugin.json");
|
|
21
|
+
let manifest;
|
|
22
|
+
try {
|
|
23
|
+
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
24
|
+
} catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const sample = sampleJsonSchema(manifest.configSchema, { key: "config" });
|
|
29
|
+
return isPlainObject(sample) && Object.keys(sample).length > 0 ? sample : undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function sampleJsonSchema(schema, context = {}) {
|
|
33
|
+
if (!isPlainObject(schema)) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) {
|
|
38
|
+
return schema.enum[0];
|
|
39
|
+
}
|
|
40
|
+
if (Object.prototype.hasOwnProperty.call(schema, "const")) {
|
|
41
|
+
return schema.const;
|
|
42
|
+
}
|
|
43
|
+
if (Object.prototype.hasOwnProperty.call(schema, "default")) {
|
|
44
|
+
return schema.default;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const type = Array.isArray(schema.type) ? schema.type.find((item) => item !== "null") : schema.type;
|
|
48
|
+
if (type === "object" || schema.properties) {
|
|
49
|
+
return sampleObjectSchema(schema);
|
|
50
|
+
}
|
|
51
|
+
if (type === "array") {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
if (type === "boolean") {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (type === "number" || type === "integer") {
|
|
58
|
+
return typeof schema.minimum === "number" ? schema.minimum : 1;
|
|
59
|
+
}
|
|
60
|
+
if (type === "string" || !type) {
|
|
61
|
+
return sampleString(context.key);
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function sampleObjectSchema(schema) {
|
|
67
|
+
const properties = isPlainObject(schema.properties) ? schema.properties : {};
|
|
68
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
69
|
+
const output = {};
|
|
70
|
+
|
|
71
|
+
for (const key of Object.keys(properties)) {
|
|
72
|
+
if (required.has(key)) {
|
|
73
|
+
const value = sampleJsonSchema(properties[key], { key });
|
|
74
|
+
if (value !== undefined) {
|
|
75
|
+
output[key] = value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const key of Object.keys(properties)) {
|
|
81
|
+
if (properties[key]?.type === "boolean") {
|
|
82
|
+
output[key] = false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (Object.keys(output).length === 0 && Number(schema.minProperties ?? 0) > 0) {
|
|
87
|
+
const key = preferredSamplePropertyKey(properties);
|
|
88
|
+
if (key) {
|
|
89
|
+
const value = sampleJsonSchema(properties[key], { key });
|
|
90
|
+
if (value !== undefined) {
|
|
91
|
+
output[key] = value;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return output;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function preferredSamplePropertyKey(properties) {
|
|
100
|
+
for (const key of ["provider", "model", "apiKey", "id", "name", ...Object.keys(properties)]) {
|
|
101
|
+
if (Object.prototype.hasOwnProperty.call(properties, key)) {
|
|
102
|
+
return key;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function sampleString(key = "") {
|
|
109
|
+
if (key === "provider") {
|
|
110
|
+
return "openai";
|
|
111
|
+
}
|
|
112
|
+
if (key === "model") {
|
|
113
|
+
return "text-embedding-3-small";
|
|
114
|
+
}
|
|
115
|
+
if (key === "apiKey") {
|
|
116
|
+
return "fixture-api-key";
|
|
117
|
+
}
|
|
118
|
+
if (key === "dbPath") {
|
|
119
|
+
return ".plugin-inspector/state/lancedb";
|
|
120
|
+
}
|
|
121
|
+
return "fixture";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isPlainObject(value) {
|
|
125
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
126
|
+
}
|
package/src/contract-probes.js
CHANGED
package/src/inspector.js
CHANGED
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
6
6
|
import { promisify } from "node:util";
|
|
7
7
|
import { createCaptureApi } from "./capture-api.js";
|
|
8
|
+
import { captureApiOptionsForPlugin } from "./capture-config.js";
|
|
8
9
|
import { fixtureCheckoutPath, fixtureSourceRoot } from "./config.js";
|
|
9
10
|
import { buildCompatibilityFixtureReport } from "./fixture-summary.js";
|
|
10
11
|
import { readOpenClawTargetSurface } from "./openclaw-target.js";
|
|
@@ -188,7 +189,12 @@ export async function captureEntrypoint(entrypoint, options = {}) {
|
|
|
188
189
|
};
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
const
|
|
192
|
+
const apiOptions = await captureApiOptionsForPlugin(options.apiOptions, {
|
|
193
|
+
pluginRoot: options.pluginRoot
|
|
194
|
+
? path.resolve(options.cwd ?? process.cwd(), options.pluginRoot)
|
|
195
|
+
: path.dirname(resolvedEntrypoint),
|
|
196
|
+
});
|
|
197
|
+
const api = createCaptureApi(apiOptions);
|
|
192
198
|
try {
|
|
193
199
|
await register(api);
|
|
194
200
|
} catch (error) {
|
|
@@ -199,7 +205,7 @@ export async function captureEntrypoint(entrypoint, options = {}) {
|
|
|
199
205
|
entrypoint: resolvedEntrypoint,
|
|
200
206
|
captured: api.getCapturedContracts(),
|
|
201
207
|
};
|
|
202
|
-
if (
|
|
208
|
+
if (apiOptions?.retainHandlers === true) {
|
|
203
209
|
result.retained = api.getRetainedContracts();
|
|
204
210
|
}
|
|
205
211
|
return result;
|
package/src/issues.js
CHANGED
|
@@ -220,10 +220,10 @@ export const issueMetadataByCode = {
|
|
|
220
220
|
title: "providerAuthEnvVars legacy manifest metadata must stay covered",
|
|
221
221
|
},
|
|
222
222
|
"registration-capture-gap": {
|
|
223
|
-
severity: "
|
|
223
|
+
severity: "P2",
|
|
224
224
|
owner: "inspector",
|
|
225
225
|
decision: "inspector-follow-up",
|
|
226
|
-
title: "runtime registrations need capture before contract judgment",
|
|
226
|
+
title: "runtime registrations need capture evidence before final contract judgment",
|
|
227
227
|
},
|
|
228
228
|
"runtime-tool-capture": {
|
|
229
229
|
severity: "P2",
|
|
@@ -6,6 +6,7 @@ import os from "node:os";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
import { createCaptureApi } from "./capture-api.js";
|
|
9
|
+
import { captureApiOptionsForPlugin } from "./capture-config.js";
|
|
9
10
|
import { createMockSdkPackage } from "./sdk-mock.js";
|
|
10
11
|
|
|
11
12
|
const options = JSON.parse(process.argv[2] ?? "{}");
|
|
@@ -30,7 +31,7 @@ async function run(options) {
|
|
|
30
31
|
cleanupTempDirOnExit(workspace);
|
|
31
32
|
const { loaderPath } = await createMockSdkPackage(workspace, { pluginRoot });
|
|
32
33
|
register(pathToFileURL(loaderPath));
|
|
33
|
-
return await captureLinkedEntrypoint(entrypoint, options);
|
|
34
|
+
return await captureLinkedEntrypoint(entrypoint, { ...options, pluginRoot });
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
function cleanupTempDirOnExit(dir) {
|
|
@@ -65,7 +66,10 @@ async function captureLinkedEntrypoint(entrypoint, options) {
|
|
|
65
66
|
);
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
const
|
|
69
|
+
const apiOptions = await captureApiOptionsForPlugin(options.apiOptions, {
|
|
70
|
+
pluginRoot: options.pluginRoot,
|
|
71
|
+
});
|
|
72
|
+
const api = createCaptureApi(apiOptions);
|
|
69
73
|
try {
|
|
70
74
|
await register(api);
|
|
71
75
|
} catch (error) {
|
|
@@ -80,7 +84,7 @@ async function captureLinkedEntrypoint(entrypoint, options) {
|
|
|
80
84
|
mockSdk: true,
|
|
81
85
|
captured: api.getCapturedContracts(),
|
|
82
86
|
};
|
|
83
|
-
if (
|
|
87
|
+
if (apiOptions?.retainHandlers === true) {
|
|
84
88
|
result.retained = api.getRetainedContracts();
|
|
85
89
|
}
|
|
86
90
|
return withProcessOutput(result, outputCapture);
|