@openclaw/plugin-inspector 0.1.3 → 0.2.0
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 +14 -0
- package/README.md +56 -8
- package/examples/github-actions-plugin-inspector.yml +1 -2
- package/examples/package-json-plugin-inspector.json +21 -0
- package/package.json +1 -1
- package/src/advanced.js +9 -0
- package/src/api.js +2 -1
- package/src/capture-api.js +20 -3
- package/src/ci-outputs.js +235 -0
- package/src/cli.js +86 -7
- package/src/config.js +30 -3
- package/src/index.js +1 -0
- package/src/init.js +1 -2
- package/src/sdk-mock.js +50 -5
- package/src/synthetic-probes.js +6 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 - 2026-04-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add package.json `pluginInspector` config discovery for plugin-root checks.
|
|
8
|
+
- Add `plugin-inspector config` for resolved plugin-root config summaries.
|
|
9
|
+
- Add author-facing `plugin-inspector inspect` plugin-root flow.
|
|
10
|
+
- Add CI-native SARIF and JUnit outputs; `plugin-inspector ci` writes them by default.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Make generated CI workflows use one `plugin-inspector ci --no-openclaw --runtime --mock-sdk` command.
|
|
15
|
+
- Harden runtime capture for string handler registrations, parse-capable config schema helpers, and provider auth/catalog SDK mocks.
|
|
16
|
+
|
|
3
17
|
## 0.1.3 - 2026-04-27
|
|
4
18
|
|
|
5
19
|
### Added
|
package/README.md
CHANGED
|
@@ -35,17 +35,39 @@ npx plugin-inspector check
|
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
npx @openclaw/plugin-inspector check
|
|
38
|
+
npx @openclaw/plugin-inspector inspect
|
|
39
|
+
npx @openclaw/plugin-inspector ci --no-openclaw
|
|
40
|
+
npx @openclaw/plugin-inspector config
|
|
38
41
|
npx @openclaw/plugin-inspector check --plugin-root ./plugins/weather
|
|
39
42
|
npx @openclaw/plugin-inspector init --ci --package-manager pnpm
|
|
40
43
|
```
|
|
41
44
|
|
|
42
|
-
`check`
|
|
43
|
-
|
|
45
|
+
`check` and `inspect` read the current directory as one plugin unless
|
|
46
|
+
`--plugin-root` is set. `inspect` is the friendly author-facing alias; `check`
|
|
47
|
+
is kept for scripts. Both write:
|
|
44
48
|
|
|
45
49
|
- `reports/plugin-inspector-report.json`
|
|
46
50
|
- `reports/plugin-inspector-report.md`
|
|
47
51
|
- `reports/plugin-inspector-issues.md`
|
|
48
52
|
|
|
53
|
+
Use CI-native outputs when you want annotations or test-summary ingestion:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
plugin-inspector inspect --no-openclaw --sarif --junit
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
That also writes:
|
|
60
|
+
|
|
61
|
+
- `reports/plugin-inspector.sarif`
|
|
62
|
+
- `reports/plugin-inspector.junit.xml`
|
|
63
|
+
|
|
64
|
+
`config` prints the resolved plugin root, fixture id, seams, and capture
|
|
65
|
+
settings before CI runs:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
plugin-inspector config --json
|
|
69
|
+
```
|
|
70
|
+
|
|
49
71
|
Use `--no-openclaw` when CI should not compare against a local OpenClaw
|
|
50
72
|
checkout:
|
|
51
73
|
|
|
@@ -53,8 +75,9 @@ checkout:
|
|
|
53
75
|
plugin-inspector check --no-openclaw
|
|
54
76
|
```
|
|
55
77
|
|
|
56
|
-
Use `plugin-inspector.config.json`
|
|
57
|
-
|
|
78
|
+
Use either `plugin-inspector.config.json` or a `package.json`
|
|
79
|
+
`pluginInspector` block when CI needs stable fixture metadata, expected seams,
|
|
80
|
+
or runtime capture defaults:
|
|
58
81
|
|
|
59
82
|
```json
|
|
60
83
|
{
|
|
@@ -83,9 +106,32 @@ Then run:
|
|
|
83
106
|
plugin-inspector check --config plugin-inspector.config.json
|
|
84
107
|
```
|
|
85
108
|
|
|
109
|
+
For a single plugin package, the same config can live in `package.json`:
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"scripts": {
|
|
114
|
+
"plugin:check": "plugin-inspector inspect --no-openclaw"
|
|
115
|
+
},
|
|
116
|
+
"pluginInspector": {
|
|
117
|
+
"version": 1,
|
|
118
|
+
"plugin": {
|
|
119
|
+
"id": "weather",
|
|
120
|
+
"priority": "high",
|
|
121
|
+
"seams": ["dynamic-tool"],
|
|
122
|
+
"sourceRoot": "src"
|
|
123
|
+
},
|
|
124
|
+
"capture": {
|
|
125
|
+
"mockSdk": true
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
86
131
|
`init --ci` writes this shape for you, plus
|
|
87
132
|
`.github/workflows/plugin-inspector.yml`. Copy-ready examples also live in
|
|
88
133
|
`examples/plugin-inspector.config.json` and
|
|
134
|
+
`examples/package-json-plugin-inspector.json` and
|
|
89
135
|
`examples/github-actions-plugin-inspector.yml`.
|
|
90
136
|
|
|
91
137
|
## Runtime Capture
|
|
@@ -121,8 +167,8 @@ Minimal package scripts:
|
|
|
121
167
|
```json
|
|
122
168
|
{
|
|
123
169
|
"scripts": {
|
|
124
|
-
"plugin:check": "plugin-inspector
|
|
125
|
-
"plugin:
|
|
170
|
+
"plugin:check": "plugin-inspector inspect --no-openclaw",
|
|
171
|
+
"plugin:ci": "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector ci --no-openclaw --runtime --mock-sdk"
|
|
126
172
|
}
|
|
127
173
|
}
|
|
128
174
|
```
|
|
@@ -147,8 +193,7 @@ jobs:
|
|
|
147
193
|
node-version: 24
|
|
148
194
|
cache: npm
|
|
149
195
|
- run: npm ci
|
|
150
|
-
- run: npx @openclaw/plugin-inspector
|
|
151
|
-
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector check --no-openclaw --runtime --mock-sdk
|
|
196
|
+
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk
|
|
152
197
|
- uses: actions/upload-artifact@v5
|
|
153
198
|
if: always()
|
|
154
199
|
with:
|
|
@@ -156,6 +201,9 @@ jobs:
|
|
|
156
201
|
path: reports/plugin-inspector-*
|
|
157
202
|
```
|
|
158
203
|
|
|
204
|
+
`ci` writes the normal report, CI summary, SARIF, and JUnit files by default.
|
|
205
|
+
Pass `--no-sarif` or `--no-junit` only if your CI surface cannot consume them.
|
|
206
|
+
|
|
159
207
|
## Fixture Suites
|
|
160
208
|
|
|
161
209
|
Fixture-set configs are still supported for crabpot-style compatibility suites:
|
|
@@ -15,8 +15,7 @@ jobs:
|
|
|
15
15
|
node-version: 24
|
|
16
16
|
cache: npm
|
|
17
17
|
- run: npm ci
|
|
18
|
-
- run: npx @openclaw/plugin-inspector
|
|
19
|
-
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector check --no-openclaw --runtime --mock-sdk
|
|
18
|
+
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk
|
|
20
19
|
- uses: actions/upload-artifact@v5
|
|
21
20
|
if: always()
|
|
22
21
|
with:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scripts": {
|
|
3
|
+
"plugin:check": "plugin-inspector inspect --no-openclaw",
|
|
4
|
+
"plugin:ci": "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector ci --no-openclaw --runtime --mock-sdk"
|
|
5
|
+
},
|
|
6
|
+
"pluginInspector": {
|
|
7
|
+
"version": 1,
|
|
8
|
+
"plugin": {
|
|
9
|
+
"id": "weather",
|
|
10
|
+
"priority": "high",
|
|
11
|
+
"seams": ["dynamic-tool"],
|
|
12
|
+
"sourceRoot": "src",
|
|
13
|
+
"expect": {
|
|
14
|
+
"registrations": ["registerTool"]
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"capture": {
|
|
18
|
+
"mockSdk": true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
package/package.json
CHANGED
package/src/advanced.js
CHANGED
|
@@ -33,6 +33,14 @@ export {
|
|
|
33
33
|
renderCiSummaryMarkdown,
|
|
34
34
|
writeCiSummary,
|
|
35
35
|
} from "./ci-summary.js";
|
|
36
|
+
export {
|
|
37
|
+
buildSarifReport,
|
|
38
|
+
defaultJunitPath,
|
|
39
|
+
defaultSarifPath,
|
|
40
|
+
renderJunitXml,
|
|
41
|
+
reportFindings,
|
|
42
|
+
writeCiOutputArtifacts,
|
|
43
|
+
} from "./ci-outputs.js";
|
|
36
44
|
export {
|
|
37
45
|
buildContractProbes,
|
|
38
46
|
contractProbeRules,
|
|
@@ -122,6 +130,7 @@ export {
|
|
|
122
130
|
loadPluginRootConfig,
|
|
123
131
|
normalizeInspectorConfig,
|
|
124
132
|
normalizePluginRootConfig,
|
|
133
|
+
packageJsonConfigKeys,
|
|
125
134
|
packageId,
|
|
126
135
|
validateInspectorConfig,
|
|
127
136
|
} from "./config.js";
|
package/src/api.js
CHANGED
|
@@ -4,6 +4,7 @@ import { loadInspectorConfig, loadPluginRootConfig } from "./config.js";
|
|
|
4
4
|
import { writePluginInspectorInit } from "./init.js";
|
|
5
5
|
import { captureEntrypoint } from "./inspector.js";
|
|
6
6
|
import { renderTextSummary, writeCompatibilityReport } from "./report.js";
|
|
7
|
+
import { writeCiOutputArtifacts } from "./ci-outputs.js";
|
|
7
8
|
import { buildRuntimeCaptureReport, writeRuntimeCaptureReport } from "./runtime-capture-report.js";
|
|
8
9
|
import { inspectCompatibilityFixtureSet, inspectFixtureSet } from "./inspector.js";
|
|
9
10
|
|
|
@@ -88,4 +89,4 @@ export async function setupPluginInspector(options = {}) {
|
|
|
88
89
|
return writePluginInspectorInit(options);
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
export { createCaptureApi, renderTextSummary };
|
|
92
|
+
export { createCaptureApi, renderTextSummary, writeCiOutputArtifacts };
|
package/src/capture-api.js
CHANGED
|
@@ -223,6 +223,7 @@ function createStoreContext() {
|
|
|
223
223
|
|
|
224
224
|
function registrationObject(args, defaults) {
|
|
225
225
|
const first = args[0];
|
|
226
|
+
const callable = firstCallable(args);
|
|
226
227
|
if (first && typeof first === "object") {
|
|
227
228
|
return {
|
|
228
229
|
...defaults,
|
|
@@ -232,13 +233,29 @@ function registrationObject(args, defaults) {
|
|
|
232
233
|
};
|
|
233
234
|
}
|
|
234
235
|
if (typeof first === "string") {
|
|
235
|
-
return {
|
|
236
|
+
return withCallableDefaults({
|
|
236
237
|
...defaults,
|
|
237
238
|
name: first,
|
|
238
239
|
id: defaults.id,
|
|
239
|
-
};
|
|
240
|
+
}, callable);
|
|
240
241
|
}
|
|
241
|
-
return { ...defaults };
|
|
242
|
+
return withCallableDefaults({ ...defaults }, callable);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function firstCallable(args) {
|
|
246
|
+
return args.find((arg) => typeof arg === "function");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function withCallableDefaults(value, callable) {
|
|
250
|
+
if (!callable) {
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
...value,
|
|
255
|
+
handler: typeof value.handler === "function" ? value.handler : callable,
|
|
256
|
+
run: typeof value.run === "function" ? value.run : callable,
|
|
257
|
+
execute: typeof value.execute === "function" ? value.execute : callable,
|
|
258
|
+
};
|
|
242
259
|
}
|
|
243
260
|
|
|
244
261
|
function summarizeArguments(args) {
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { writeArtifacts } from "./artifacts.js";
|
|
3
|
+
|
|
4
|
+
export const defaultSarifPath = "plugin-inspector.sarif";
|
|
5
|
+
export const defaultJunitPath = "plugin-inspector.junit.xml";
|
|
6
|
+
|
|
7
|
+
export async function writeCiOutputArtifacts(report, options = {}) {
|
|
8
|
+
const outDir = path.resolve(options.cwd ?? process.cwd(), options.outDir ?? "reports");
|
|
9
|
+
const artifacts = [];
|
|
10
|
+
|
|
11
|
+
if (options.sarifPath) {
|
|
12
|
+
artifacts.push({
|
|
13
|
+
name: "sarifPath",
|
|
14
|
+
path: path.resolve(outDir, options.sarifPath),
|
|
15
|
+
json: buildSarifReport(report),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (options.junitPath) {
|
|
20
|
+
artifacts.push({
|
|
21
|
+
name: "junitPath",
|
|
22
|
+
path: path.resolve(outDir, options.junitPath),
|
|
23
|
+
content: renderJunitXml(report),
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (artifacts.length === 0) {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return writeArtifacts(artifacts, { check: options.check });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function buildSarifReport(report) {
|
|
35
|
+
const findings = reportFindings(report);
|
|
36
|
+
const rules = [...new Map(findings.map((finding) => [finding.code, sarifRule(finding)])).values()];
|
|
37
|
+
const fixtureById = new Map((report.fixtures ?? []).map((fixture) => [fixture.id, fixture]));
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
41
|
+
version: "2.1.0",
|
|
42
|
+
runs: [
|
|
43
|
+
{
|
|
44
|
+
tool: {
|
|
45
|
+
driver: {
|
|
46
|
+
name: "plugin-inspector",
|
|
47
|
+
informationUri: "https://github.com/openclaw/plugin-inspector",
|
|
48
|
+
rules,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
results: findings.map((finding) => sarifResult(finding, fixtureById)),
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function renderJunitXml(report) {
|
|
58
|
+
const findings = reportFindings(report);
|
|
59
|
+
const testcases = findings.length > 0 ? findings.map(junitFindingTestcase) : [junitPassingTestcase(report)];
|
|
60
|
+
const failures = findings.filter(isBlockingFinding).length;
|
|
61
|
+
const tests = testcases.length;
|
|
62
|
+
|
|
63
|
+
return [
|
|
64
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
65
|
+
`<testsuite name="plugin-inspector" tests="${tests}" failures="${failures}" errors="0" skipped="0">`,
|
|
66
|
+
...testcases,
|
|
67
|
+
"</testsuite>",
|
|
68
|
+
"",
|
|
69
|
+
].join("\n");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function reportFindings(report) {
|
|
73
|
+
const findings = new Map();
|
|
74
|
+
for (const finding of [...(report.breakages ?? []), ...(report.warnings ?? []), ...(report.suggestions ?? [])]) {
|
|
75
|
+
findings.set(findingKey(finding), finding);
|
|
76
|
+
}
|
|
77
|
+
for (const issue of report.issues ?? []) {
|
|
78
|
+
const finding = issueToFinding(issue);
|
|
79
|
+
findings.set(findingKey(finding), {
|
|
80
|
+
...findings.get(findingKey(finding)),
|
|
81
|
+
...finding,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return [...findings.values()];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function issueToFinding(issue) {
|
|
88
|
+
return {
|
|
89
|
+
fixture: issue.fixture,
|
|
90
|
+
code: issue.code,
|
|
91
|
+
level: issue.status === "blocking" ? "breakage" : "warning",
|
|
92
|
+
message: issue.title,
|
|
93
|
+
evidence: issue.evidence ?? [],
|
|
94
|
+
severity: issue.severity,
|
|
95
|
+
issueClass: issue.issueClass,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function findingKey(finding) {
|
|
100
|
+
return [
|
|
101
|
+
finding.fixture ?? "",
|
|
102
|
+
finding.code ?? "",
|
|
103
|
+
...normalizeEvidence(finding.evidence),
|
|
104
|
+
].join("\n");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function sarifRule(finding) {
|
|
108
|
+
return {
|
|
109
|
+
id: finding.code,
|
|
110
|
+
shortDescription: {
|
|
111
|
+
text: finding.code,
|
|
112
|
+
},
|
|
113
|
+
fullDescription: {
|
|
114
|
+
text: finding.message ?? finding.code,
|
|
115
|
+
},
|
|
116
|
+
defaultConfiguration: {
|
|
117
|
+
level: sarifLevel(finding),
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function sarifResult(finding, fixtureById) {
|
|
123
|
+
return {
|
|
124
|
+
ruleId: finding.code,
|
|
125
|
+
level: sarifLevel(finding),
|
|
126
|
+
message: {
|
|
127
|
+
text: finding.message ?? finding.code,
|
|
128
|
+
},
|
|
129
|
+
locations: [sarifLocation(finding, fixtureById)],
|
|
130
|
+
properties: {
|
|
131
|
+
fixture: finding.fixture,
|
|
132
|
+
severity: finding.severity ?? finding.level,
|
|
133
|
+
issueClass: finding.issueClass,
|
|
134
|
+
evidence: normalizeEvidence(finding.evidence),
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sarifLocation(finding, fixtureById) {
|
|
140
|
+
const parsed = parseEvidenceLocation(normalizeEvidence(finding.evidence)[0]);
|
|
141
|
+
const fixture = fixtureById.get(finding.fixture);
|
|
142
|
+
const uri = parsed?.uri ?? fixture?.path ?? ".";
|
|
143
|
+
return {
|
|
144
|
+
physicalLocation: {
|
|
145
|
+
artifactLocation: {
|
|
146
|
+
uri: normalizeUri(uri),
|
|
147
|
+
},
|
|
148
|
+
region: {
|
|
149
|
+
startLine: parsed?.line ?? 1,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function parseEvidenceLocation(evidence) {
|
|
156
|
+
if (!evidence) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const ref = evidence.includes(" @ ") ? evidence.split(" @ ").pop() : evidence;
|
|
161
|
+
const match = /^(?<uri>.+?):(?<line>\d+)(?::\d+)?$/.exec(ref);
|
|
162
|
+
if (!match?.groups?.uri) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
uri: match.groups.uri,
|
|
167
|
+
line: Number(match.groups.line),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function junitFindingTestcase(finding) {
|
|
172
|
+
const classname = `plugin-inspector.${xmlName(finding.fixture ?? "unknown")}`;
|
|
173
|
+
const name = `${finding.level ?? "finding"}:${finding.code}`;
|
|
174
|
+
const output = normalizeEvidence(finding.evidence).join("\n");
|
|
175
|
+
if (!isBlockingFinding(finding)) {
|
|
176
|
+
return [
|
|
177
|
+
` <testcase classname="${escapeXml(classname)}" name="${escapeXml(name)}">`,
|
|
178
|
+
output ? ` <system-out>${escapeXml(output)}</system-out>` : "",
|
|
179
|
+
" </testcase>",
|
|
180
|
+
]
|
|
181
|
+
.filter(Boolean)
|
|
182
|
+
.join("\n");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return [
|
|
186
|
+
` <testcase classname="${escapeXml(classname)}" name="${escapeXml(name)}">`,
|
|
187
|
+
` <failure message="${escapeXml(finding.message ?? finding.code)}">${escapeXml(output || finding.message || finding.code)}</failure>`,
|
|
188
|
+
" </testcase>",
|
|
189
|
+
].join("\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function junitPassingTestcase(report) {
|
|
193
|
+
return ` <testcase classname="plugin-inspector" name="status:${escapeXml(report.status ?? "pass")}"/>`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isBlockingFinding(finding) {
|
|
197
|
+
return finding.level === "breakage" || finding.status === "blocking" || finding.severity === "P0";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function sarifLevel(finding) {
|
|
201
|
+
if (isBlockingFinding(finding) || finding.severity === "P1") {
|
|
202
|
+
return "error";
|
|
203
|
+
}
|
|
204
|
+
if (finding.level === "warning" || finding.severity === "P2") {
|
|
205
|
+
return "warning";
|
|
206
|
+
}
|
|
207
|
+
return "note";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function normalizeEvidence(evidence) {
|
|
211
|
+
if (Array.isArray(evidence)) {
|
|
212
|
+
return evidence.map(String);
|
|
213
|
+
}
|
|
214
|
+
if (evidence == null) {
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
return [String(evidence)];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function normalizeUri(uri) {
|
|
221
|
+
return String(uri).replaceAll(path.sep, "/");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function xmlName(value) {
|
|
225
|
+
return String(value).replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function escapeXml(value) {
|
|
229
|
+
return String(value)
|
|
230
|
+
.replace(/&/g, "&")
|
|
231
|
+
.replace(/</g, "<")
|
|
232
|
+
.replace(/>/g, ">")
|
|
233
|
+
.replace(/"/g, """)
|
|
234
|
+
.replace(/'/g, "'");
|
|
235
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import {
|
|
4
|
+
loadPluginConfig,
|
|
4
5
|
renderTextSummary,
|
|
5
6
|
runPluginCheck,
|
|
6
7
|
} from "./index.js";
|
|
7
8
|
import {
|
|
8
9
|
buildCiSummary,
|
|
9
10
|
captureEntrypoint,
|
|
11
|
+
defaultJunitPath,
|
|
12
|
+
defaultSarifPath,
|
|
10
13
|
inspectCompatibilityFixtureSet,
|
|
11
14
|
inspectFixtureSet,
|
|
12
15
|
loadInspectorConfig,
|
|
16
|
+
writeCiOutputArtifacts,
|
|
13
17
|
writeCiSummary,
|
|
14
18
|
writeCompatibilityReport,
|
|
15
19
|
writePluginInspectorInit,
|
|
@@ -28,8 +32,14 @@ try {
|
|
|
28
32
|
await runCheck(commandArgs);
|
|
29
33
|
} else if (command === "init") {
|
|
30
34
|
await runInit(commandArgs);
|
|
35
|
+
} else if (command === "config") {
|
|
36
|
+
await runConfig(commandArgs);
|
|
31
37
|
} else if (command === "inspect" || command === "report") {
|
|
32
|
-
|
|
38
|
+
if (command === "inspect" && !commandArgs.includes("--config")) {
|
|
39
|
+
await runCheck(commandArgs);
|
|
40
|
+
} else {
|
|
41
|
+
await runReport(command, commandArgs);
|
|
42
|
+
}
|
|
33
43
|
} else if (command === "ci") {
|
|
34
44
|
await runCi(commandArgs);
|
|
35
45
|
} else if (command === "capture") {
|
|
@@ -42,6 +52,18 @@ try {
|
|
|
42
52
|
process.exitCode = 1;
|
|
43
53
|
}
|
|
44
54
|
|
|
55
|
+
async function runConfig(commandArgs) {
|
|
56
|
+
const configPath = readFlag(commandArgs, "--config");
|
|
57
|
+
const pluginRoot = readFlag(commandArgs, "--plugin-root") ?? readFlag(commandArgs, "--root");
|
|
58
|
+
const config = await loadPluginConfig({ configPath, pluginRoot });
|
|
59
|
+
|
|
60
|
+
if (commandArgs.includes("--json")) {
|
|
61
|
+
console.log(JSON.stringify(config, null, 2));
|
|
62
|
+
} else {
|
|
63
|
+
console.log(renderConfigTextSummary(config));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
45
67
|
async function runCheck(commandArgs) {
|
|
46
68
|
const configPath = readFlag(commandArgs, "--config");
|
|
47
69
|
const pluginRoot = readFlag(commandArgs, "--plugin-root") ?? readFlag(commandArgs, "--root");
|
|
@@ -50,7 +72,13 @@ async function runCheck(commandArgs) {
|
|
|
50
72
|
const json = commandArgs.includes("--json");
|
|
51
73
|
const capture = readRuntimeFlag(commandArgs);
|
|
52
74
|
const mockSdk = readMockSdkFlag(commandArgs);
|
|
53
|
-
const
|
|
75
|
+
const ciOutputs = readCiOutputFlags(commandArgs);
|
|
76
|
+
const { report, paths } = await runPluginCheck({ configPath, pluginRoot, outDir, openclawPath, capture, mockSdk });
|
|
77
|
+
await writeCiOutputArtifacts(report, {
|
|
78
|
+
...ciOutputs,
|
|
79
|
+
cwd: path.dirname(paths.jsonPath),
|
|
80
|
+
outDir: ".",
|
|
81
|
+
});
|
|
54
82
|
|
|
55
83
|
if (json) {
|
|
56
84
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -87,9 +115,15 @@ async function runReport(command, commandArgs) {
|
|
|
87
115
|
const outDir = readFlag(commandArgs, "--out") ?? "reports";
|
|
88
116
|
const check = commandArgs.includes("--check") || command === "ci";
|
|
89
117
|
const json = commandArgs.includes("--json");
|
|
118
|
+
const ciOutputs = readCiOutputFlags(commandArgs);
|
|
90
119
|
const config = await loadInspectorConfig(configPath);
|
|
91
120
|
const report = await inspectFixtureSet(config);
|
|
92
|
-
await writeReport(report, { outDir });
|
|
121
|
+
const paths = await writeReport(report, { outDir });
|
|
122
|
+
await writeCiOutputArtifacts(report, {
|
|
123
|
+
...ciOutputs,
|
|
124
|
+
cwd: path.dirname(paths.jsonPath),
|
|
125
|
+
outDir: ".",
|
|
126
|
+
});
|
|
93
127
|
|
|
94
128
|
if (json) {
|
|
95
129
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -108,8 +142,13 @@ async function runCi(commandArgs) {
|
|
|
108
142
|
const outDir = readFlag(commandArgs, "--out") ?? "reports";
|
|
109
143
|
const openclawPath = commandArgs.includes("--no-openclaw") ? false : readFlag(commandArgs, "--openclaw");
|
|
110
144
|
const json = commandArgs.includes("--json");
|
|
145
|
+
const capture = readRuntimeFlag(commandArgs);
|
|
146
|
+
const mockSdk = readMockSdkFlag(commandArgs);
|
|
147
|
+
const ciOutputs = readCiOutputFlags(commandArgs, { defaultEnabled: true });
|
|
111
148
|
const { report, reportDir } = await runCiCompatibilityReport({
|
|
149
|
+
capture,
|
|
112
150
|
configPath,
|
|
151
|
+
mockSdk,
|
|
113
152
|
openclawPath,
|
|
114
153
|
outDir,
|
|
115
154
|
pluginRoot,
|
|
@@ -128,6 +167,11 @@ async function runCi(commandArgs) {
|
|
|
128
167
|
jsonPath: path.join(reportDir, "plugin-inspector-ci-summary.json"),
|
|
129
168
|
markdownPath: path.join(reportDir, "plugin-inspector-ci-summary.md"),
|
|
130
169
|
});
|
|
170
|
+
await writeCiOutputArtifacts(report, {
|
|
171
|
+
...ciOutputs,
|
|
172
|
+
cwd: reportDir,
|
|
173
|
+
outDir: ".",
|
|
174
|
+
});
|
|
131
175
|
|
|
132
176
|
if (json) {
|
|
133
177
|
console.log(JSON.stringify(summary, null, 2));
|
|
@@ -140,7 +184,7 @@ async function runCi(commandArgs) {
|
|
|
140
184
|
}
|
|
141
185
|
}
|
|
142
186
|
|
|
143
|
-
async function runCiCompatibilityReport({ configPath, openclawPath, outDir, pluginRoot }) {
|
|
187
|
+
async function runCiCompatibilityReport({ capture, configPath, mockSdk, openclawPath, outDir, pluginRoot }) {
|
|
144
188
|
if (configPath) {
|
|
145
189
|
const config = await loadInspectorConfig(configPath, { cwd: pluginRoot });
|
|
146
190
|
const report = await inspectCompatibilityFixtureSet(config, { openclawPath });
|
|
@@ -151,7 +195,7 @@ async function runCiCompatibilityReport({ configPath, openclawPath, outDir, plug
|
|
|
151
195
|
};
|
|
152
196
|
}
|
|
153
197
|
|
|
154
|
-
const { report } = await runPluginCheck({ pluginRoot, outDir, openclawPath });
|
|
198
|
+
const { report } = await runPluginCheck({ pluginRoot, outDir, openclawPath, capture, mockSdk });
|
|
155
199
|
return {
|
|
156
200
|
report,
|
|
157
201
|
reportDir: path.resolve(pluginRoot ?? process.cwd(), outDir),
|
|
@@ -187,6 +231,26 @@ function readFlag(commandArgs, name) {
|
|
|
187
231
|
return commandArgs[index + 1] ?? null;
|
|
188
232
|
}
|
|
189
233
|
|
|
234
|
+
function readOptionalPathFlag(commandArgs, name, defaultPath) {
|
|
235
|
+
const index = commandArgs.indexOf(name);
|
|
236
|
+
if (index === -1) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const value = commandArgs[index + 1];
|
|
240
|
+
return value && !value.startsWith("-") ? value : defaultPath;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function readCiOutputFlags(commandArgs, options = {}) {
|
|
244
|
+
return {
|
|
245
|
+
sarifPath: commandArgs.includes("--no-sarif")
|
|
246
|
+
? null
|
|
247
|
+
: (readOptionalPathFlag(commandArgs, "--sarif", defaultSarifPath) ?? (options.defaultEnabled ? defaultSarifPath : null)),
|
|
248
|
+
junitPath: commandArgs.includes("--no-junit")
|
|
249
|
+
? null
|
|
250
|
+
: (readOptionalPathFlag(commandArgs, "--junit", defaultJunitPath) ?? (options.defaultEnabled ? defaultJunitPath : null)),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
190
254
|
function readRuntimeFlag(commandArgs) {
|
|
191
255
|
if (commandArgs.includes("--runtime") || commandArgs.includes("--capture")) {
|
|
192
256
|
return true;
|
|
@@ -226,19 +290,34 @@ function renderCiTextSummary(summary) {
|
|
|
226
290
|
].join("\n");
|
|
227
291
|
}
|
|
228
292
|
|
|
293
|
+
function renderConfigTextSummary(config) {
|
|
294
|
+
const fixture = config.fixtures[0];
|
|
295
|
+
return [
|
|
296
|
+
`Plugin: ${fixture.id}`,
|
|
297
|
+
`Root: ${config.rootDir}`,
|
|
298
|
+
`Config: ${config.configPath ?? "auto"}`,
|
|
299
|
+
`Priority: ${fixture.priority}`,
|
|
300
|
+
`Seams: ${fixture.seams.join(", ")}`,
|
|
301
|
+
`Runtime capture: ${config.capture?.runtime === true ? "on" : "off"}`,
|
|
302
|
+
`Mock SDK: ${config.capture?.mockSdk === false ? "off" : "on"}`,
|
|
303
|
+
].join("\n");
|
|
304
|
+
}
|
|
305
|
+
|
|
229
306
|
function printHelp() {
|
|
230
307
|
console.log(`plugin-inspector
|
|
231
308
|
|
|
232
309
|
Usage:
|
|
233
310
|
plugin-inspector
|
|
234
311
|
plugin-inspector check [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--json]
|
|
312
|
+
plugin-inspector config [--plugin-root <path>] [--config <path>] [--json]
|
|
235
313
|
plugin-inspector init [--plugin-root <path>] [--config <path>] [--ci] [--package-manager npm|pnpm|yarn|bun] [--force]
|
|
236
314
|
plugin-inspector report --config <path> [--out <dir>] [--check] [--json]
|
|
237
|
-
plugin-inspector inspect --config <path> [--out <dir>] [--check] [--json]
|
|
238
|
-
plugin-inspector ci [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--json]
|
|
315
|
+
plugin-inspector inspect [--plugin-root <path>] [--config <path>] [--out <dir>] [--check] [--json] [--sarif [path]] [--junit [path]]
|
|
316
|
+
plugin-inspector ci [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--json] [--no-sarif] [--no-junit]
|
|
239
317
|
PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector capture <entrypoint> [--mock-sdk|--real-sdk] [--plugin-root <path>] [--output <path>]
|
|
240
318
|
|
|
241
319
|
Default check runs from the current plugin root and writes reports/ unless --out is set.
|
|
320
|
+
CI writes SARIF and JUnit artifacts by default; check/inspect can write them with --sarif and --junit.
|
|
242
321
|
Runtime capture is opt-in because it imports plugin code; use --runtime with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1.
|
|
243
322
|
`);
|
|
244
323
|
}
|
package/src/config.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
export const npmPackagePayloadDir = ".crabpot-package";
|
|
6
6
|
export const defaultPluginRootConfigFiles = ["plugin-inspector.config.json", ".plugin-inspector.json"];
|
|
7
|
+
export const packageJsonConfigKeys = ["pluginInspector", "plugin-inspector"];
|
|
7
8
|
|
|
8
9
|
export async function loadInspectorConfig(configPath, options = {}) {
|
|
9
10
|
if (!configPath) {
|
|
@@ -24,16 +25,23 @@ export async function loadInspectorConfig(configPath, options = {}) {
|
|
|
24
25
|
export async function loadPluginRootConfig(configPath = null, options = {}) {
|
|
25
26
|
const rootDir = path.resolve(options.cwd ?? process.cwd());
|
|
26
27
|
const resolvedPath = configPath ? path.resolve(rootDir, configPath) : findPluginRootConfigPath(rootDir);
|
|
27
|
-
|
|
28
|
+
const packageJsonPath = path.join(rootDir, "package.json");
|
|
29
|
+
const packageJson = await readJsonIfExists(packageJsonPath);
|
|
30
|
+
const packageConfig = packageJsonConfig(packageJson);
|
|
31
|
+
|
|
32
|
+
if (!resolvedPath && !packageJson && !existsSync(path.join(rootDir, "openclaw.plugin.json"))) {
|
|
28
33
|
throw new Error("run from a plugin root with package.json/openclaw.plugin.json, or pass --config");
|
|
29
34
|
}
|
|
30
|
-
|
|
35
|
+
|
|
36
|
+
const config = resolvedPath
|
|
37
|
+
? JSON.parse(await readFile(resolvedPath, "utf8"))
|
|
38
|
+
: (packageConfig.config ?? { version: 1 });
|
|
31
39
|
const normalizedConfig = await normalizePluginRootConfig(config, { rootDir });
|
|
32
40
|
validateInspectorConfig(normalizedConfig);
|
|
33
41
|
return {
|
|
34
42
|
...normalizedConfig,
|
|
35
43
|
rootDir,
|
|
36
|
-
configPath: resolvedPath,
|
|
44
|
+
configPath: resolvedPath ?? packageConfig.configPath,
|
|
37
45
|
};
|
|
38
46
|
}
|
|
39
47
|
|
|
@@ -164,6 +172,25 @@ function findPluginRootConfigPath(rootDir) {
|
|
|
164
172
|
return defaultPluginRootConfigFiles.map((file) => path.join(rootDir, file)).find(existsSync) ?? null;
|
|
165
173
|
}
|
|
166
174
|
|
|
175
|
+
function packageJsonConfig(packageJson) {
|
|
176
|
+
if (!packageJson) {
|
|
177
|
+
return { config: null, configPath: null };
|
|
178
|
+
}
|
|
179
|
+
for (const key of packageJsonConfigKeys) {
|
|
180
|
+
if (packageJson[key] === undefined) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (!packageJson[key] || typeof packageJson[key] !== "object" || Array.isArray(packageJson[key])) {
|
|
184
|
+
throw new Error(`package.json ${key} must be an object`);
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
config: packageJson[key],
|
|
188
|
+
configPath: `package.json#${key}`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return { config: null, configPath: null };
|
|
192
|
+
}
|
|
193
|
+
|
|
167
194
|
async function readJsonIfExists(filePath) {
|
|
168
195
|
if (!existsSync(filePath)) {
|
|
169
196
|
return null;
|
package/src/index.js
CHANGED
package/src/init.js
CHANGED
|
@@ -79,8 +79,7 @@ jobs:
|
|
|
79
79
|
node-version: 24
|
|
80
80
|
cache: ${setup.cache}
|
|
81
81
|
${setup.corepack ? " - run: corepack enable\n" : ""} - run: ${setup.install}
|
|
82
|
-
- run: ${setup.exec} @openclaw/plugin-inspector
|
|
83
|
-
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 ${setup.exec} @openclaw/plugin-inspector check --no-openclaw --runtime --mock-sdk
|
|
82
|
+
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 ${setup.exec} @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk
|
|
84
83
|
- uses: actions/upload-artifact@v5
|
|
85
84
|
if: always()
|
|
86
85
|
with:
|
package/src/sdk-mock.js
CHANGED
|
@@ -718,6 +718,37 @@ function mockSdkSource() {
|
|
|
718
718
|
return typeof entry === "function" ? { register: entry } : entry;
|
|
719
719
|
}
|
|
720
720
|
|
|
721
|
+
function isPlainObject(value) {
|
|
722
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function parseWithSchema(schema, value) {
|
|
726
|
+
return schema && typeof schema.parse === "function" ? schema.parse(value) : value;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function createConfigSchema(schema = {}) {
|
|
730
|
+
if (schema && typeof schema.parse === "function") {
|
|
731
|
+
return schema;
|
|
732
|
+
}
|
|
733
|
+
const shape = isPlainObject(schema?.shape) ? schema.shape : isPlainObject(schema?.properties) ? schema.properties : schema;
|
|
734
|
+
return {
|
|
735
|
+
...schema,
|
|
736
|
+
parse(value = {}) {
|
|
737
|
+
if (!isPlainObject(shape)) {
|
|
738
|
+
return isPlainObject(value) ? value : {};
|
|
739
|
+
}
|
|
740
|
+
const source = isPlainObject(value) ? value : {};
|
|
741
|
+
const output = { ...source };
|
|
742
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
743
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
744
|
+
output[key] = parseWithSchema(fieldSchema, source[key]);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return output;
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
721
752
|
export function definePluginEntry(entry) {
|
|
722
753
|
return normalizeEntry(entry);
|
|
723
754
|
}
|
|
@@ -759,13 +790,13 @@ export function defineSingleProviderPluginEntry(options) {
|
|
|
759
790
|
}
|
|
760
791
|
|
|
761
792
|
export function buildPluginConfigSchema(schema = {}) {
|
|
762
|
-
return schema;
|
|
793
|
+
return createConfigSchema(schema);
|
|
763
794
|
}
|
|
764
795
|
|
|
765
|
-
export const emptyPluginConfigSchema = { type: "object", properties: {}, additionalProperties: false };
|
|
796
|
+
export const emptyPluginConfigSchema = createConfigSchema({ type: "object", properties: {}, additionalProperties: false });
|
|
766
797
|
|
|
767
798
|
export function buildChannelConfigSchema(schema = {}) {
|
|
768
|
-
return schema;
|
|
799
|
+
return createConfigSchema(schema);
|
|
769
800
|
}
|
|
770
801
|
|
|
771
802
|
export const emptyChannelConfigSchema = emptyPluginConfigSchema;
|
|
@@ -951,14 +982,28 @@ export function createAuthRateLimiter() {
|
|
|
951
982
|
}
|
|
952
983
|
|
|
953
984
|
export function createProviderApiKeyAuthMethod(options = {}) {
|
|
954
|
-
return {
|
|
985
|
+
return {
|
|
986
|
+
id: options.id ?? "apiKey",
|
|
987
|
+
type: "apiKey",
|
|
988
|
+
...options,
|
|
989
|
+
async resolve(ctx = {}) {
|
|
990
|
+
return ctx.apiKey ?? ctx.key ?? ctx.token ?? null;
|
|
991
|
+
},
|
|
992
|
+
};
|
|
955
993
|
}
|
|
956
994
|
|
|
957
995
|
export function buildSingleProviderApiKeyCatalog(options = {}) {
|
|
996
|
+
const auth = options.auth ?? createProviderApiKeyAuthMethod(options.authOptions);
|
|
958
997
|
return {
|
|
998
|
+
auth,
|
|
959
999
|
order: "simple",
|
|
960
1000
|
async run(ctx) {
|
|
961
|
-
|
|
1001
|
+
const provider = (await options.buildProvider?.(ctx)) ?? options.provider ?? { id: options.id ?? "provider", auth };
|
|
1002
|
+
return {
|
|
1003
|
+
provider,
|
|
1004
|
+
providers: [provider],
|
|
1005
|
+
models: (await options.buildModels?.(ctx)) ?? options.models ?? [],
|
|
1006
|
+
};
|
|
962
1007
|
},
|
|
963
1008
|
};
|
|
964
1009
|
}
|
package/src/synthetic-probes.js
CHANGED
|
@@ -13,7 +13,7 @@ export const syntheticRegistrationExecutionProfiles = {
|
|
|
13
13
|
},
|
|
14
14
|
registerChannel: {
|
|
15
15
|
mode: "channel-opt-in",
|
|
16
|
-
callableProperties: ["send", "receive"],
|
|
16
|
+
callableProperties: ["send", "sendMessage", "receive", "handleMessage"],
|
|
17
17
|
option: "includeChannelRuntime",
|
|
18
18
|
},
|
|
19
19
|
registerCli: {
|
|
@@ -31,7 +31,7 @@ export const syntheticRegistrationExecutionProfiles = {
|
|
|
31
31
|
},
|
|
32
32
|
registerGatewayMethod: {
|
|
33
33
|
mode: "direct",
|
|
34
|
-
callableProperties: ["handler", "run", "execute"],
|
|
34
|
+
callableProperties: ["handler", "run", "execute", "invoke"],
|
|
35
35
|
},
|
|
36
36
|
registerHttpRoute: {
|
|
37
37
|
mode: "direct",
|
|
@@ -58,12 +58,12 @@ export const syntheticRegistrationExecutionProfiles = {
|
|
|
58
58
|
},
|
|
59
59
|
registerService: {
|
|
60
60
|
mode: "lifecycle-opt-in",
|
|
61
|
-
callableProperties: ["start", "stop"],
|
|
61
|
+
callableProperties: ["start", "stop", "dispose"],
|
|
62
62
|
option: "includeLifecycle",
|
|
63
63
|
},
|
|
64
64
|
registerSpeechProvider: {
|
|
65
65
|
mode: "provider-opt-in",
|
|
66
|
-
callableProperties: ["speak", "synthesize"],
|
|
66
|
+
callableProperties: ["speak", "synthesize", "tts"],
|
|
67
67
|
option: "includeProviderCapabilities",
|
|
68
68
|
},
|
|
69
69
|
registerTool: {
|
|
@@ -452,7 +452,8 @@ async function runRegistrationProbes(entry, retainedEntry, captureIndex, options
|
|
|
452
452
|
return [metadataOnlyResult(entry, captureIndex, profile.reason)];
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
-
const descriptor =
|
|
455
|
+
const descriptor =
|
|
456
|
+
retainedEntry.arguments?.find((value) => value && typeof value === "object") ?? retainedEntry.returnValue;
|
|
456
457
|
if (!descriptor || typeof descriptor !== "object") {
|
|
457
458
|
return [blockedResult(entry, captureIndex, "captured registration has no object descriptor")];
|
|
458
459
|
}
|