@openclaw/plugin-inspector 0.1.3 → 0.3.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 +34 -0
- package/README.md +80 -11
- package/examples/circleci-plugin-inspector.yml +19 -0
- package/examples/github-actions-code-scanning.yml +31 -0
- package/examples/github-actions-plugin-inspector.yml +1 -2
- package/examples/gitlab-ci-plugin-inspector.yml +11 -0
- package/examples/package-json-plugin-inspector.json +21 -0
- package/package.json +4 -1
- package/src/advanced.js +19 -0
- package/src/api.js +60 -3
- package/src/capture-api.js +60 -5
- package/src/ci-outputs.js +235 -0
- package/src/cli.js +129 -17
- package/src/config.js +30 -3
- package/src/index.js +6 -0
- package/src/init.js +90 -14
- package/src/mock-sdk-capture-runner.js +12 -8
- package/src/report.js +60 -8
- package/src/sdk-mock.js +252 -14
- package/src/synthetic-probe-suite.js +19 -0
- package/src/synthetic-probes-cli.js +16 -12
- package/src/synthetic-probes.js +61 -5
package/src/init.js
CHANGED
|
@@ -5,32 +5,66 @@ import { inferPluginSeams, packageId } from "./config.js";
|
|
|
5
5
|
|
|
6
6
|
export const defaultInitConfigPath = "plugin-inspector.config.json";
|
|
7
7
|
export const defaultInitWorkflowPath = ".github/workflows/plugin-inspector.yml";
|
|
8
|
+
export const defaultInitPackageScripts = {
|
|
9
|
+
"plugin:check": "plugin-inspector inspect --no-openclaw",
|
|
10
|
+
"plugin:ci": "plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute",
|
|
11
|
+
};
|
|
8
12
|
|
|
9
13
|
export async function writePluginInspectorInit(options = {}) {
|
|
10
14
|
const pluginRoot = path.resolve(options.pluginRoot ?? options.cwd ?? process.cwd());
|
|
11
15
|
const configPath = path.resolve(pluginRoot, options.configPath ?? defaultInitConfigPath);
|
|
16
|
+
const workflowPath = options.ci === true ? path.resolve(pluginRoot, options.workflowPath ?? defaultInitWorkflowPath) : null;
|
|
17
|
+
const packageManager = options.packageManager ?? (await detectPackageManager(pluginRoot));
|
|
18
|
+
const dryRun = options.dryRun === true;
|
|
12
19
|
const written = [];
|
|
13
20
|
|
|
14
|
-
if (existsSync(configPath) && options.force !== true) {
|
|
21
|
+
if (!dryRun && existsSync(configPath) && options.force !== true) {
|
|
15
22
|
throw new Error(`${path.relative(pluginRoot, configPath)} already exists; pass --force to overwrite it`);
|
|
16
23
|
}
|
|
24
|
+
if (!dryRun && workflowPath && existsSync(workflowPath) && options.force !== true) {
|
|
25
|
+
throw new Error(`${path.relative(pluginRoot, workflowPath)} already exists; pass --force to overwrite it`);
|
|
26
|
+
}
|
|
27
|
+
const packageJsonPath = path.join(pluginRoot, "package.json");
|
|
28
|
+
const packageJson = options.scripts === true ? await readJsonIfExists(packageJsonPath) : null;
|
|
29
|
+
if (options.scripts === true) {
|
|
30
|
+
if (!packageJson) {
|
|
31
|
+
throw new Error("package.json is required to write plugin-inspector package scripts");
|
|
32
|
+
}
|
|
33
|
+
for (const name of Object.keys(defaultInitPackageScripts)) {
|
|
34
|
+
if (!dryRun && packageJson.scripts?.[name] && options.force !== true) {
|
|
35
|
+
throw new Error(`package.json scripts.${name} already exists; pass --force to overwrite it`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
17
39
|
|
|
18
40
|
const config = await buildPluginInspectorConfig({ pluginRoot });
|
|
19
|
-
|
|
20
|
-
|
|
41
|
+
if (!dryRun) {
|
|
42
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
43
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
44
|
+
}
|
|
21
45
|
written.push(configPath);
|
|
22
46
|
|
|
23
|
-
if (
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
47
|
+
if (workflowPath) {
|
|
48
|
+
if (!dryRun) {
|
|
49
|
+
await mkdir(path.dirname(workflowPath), { recursive: true });
|
|
50
|
+
await writeFile(workflowPath, renderGithubActionsWorkflow({ packageManager }), "utf8");
|
|
27
51
|
}
|
|
28
|
-
await mkdir(path.dirname(workflowPath), { recursive: true });
|
|
29
|
-
await writeFile(workflowPath, renderGithubActionsWorkflow({ packageManager: options.packageManager }), "utf8");
|
|
30
52
|
written.push(workflowPath);
|
|
31
53
|
}
|
|
32
54
|
|
|
33
|
-
|
|
55
|
+
if (options.scripts === true) {
|
|
56
|
+
const existingScripts = packageJson.scripts ?? {};
|
|
57
|
+
packageJson.scripts = {
|
|
58
|
+
...existingScripts,
|
|
59
|
+
...defaultInitPackageScripts,
|
|
60
|
+
};
|
|
61
|
+
if (!dryRun) {
|
|
62
|
+
await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
|
|
63
|
+
}
|
|
64
|
+
written.push(packageJsonPath);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { pluginRoot, configPath, dryRun, packageManager, written };
|
|
34
68
|
}
|
|
35
69
|
|
|
36
70
|
export async function buildPluginInspectorConfig(options = {}) {
|
|
@@ -79,8 +113,7 @@ jobs:
|
|
|
79
113
|
node-version: 24
|
|
80
114
|
cache: ${setup.cache}
|
|
81
115
|
${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
|
|
116
|
+
- run: ${setup.exec} @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute
|
|
84
117
|
- uses: actions/upload-artifact@v5
|
|
85
118
|
if: always()
|
|
86
119
|
with:
|
|
@@ -89,19 +122,62 @@ ${setup.corepack ? " - run: corepack enable\n" : ""} - run: ${setup.in
|
|
|
89
122
|
`;
|
|
90
123
|
}
|
|
91
124
|
|
|
125
|
+
export async function detectPackageManager(pluginRoot) {
|
|
126
|
+
const root = path.resolve(pluginRoot ?? process.cwd());
|
|
127
|
+
const packageJson = await readJsonIfExists(path.join(root, "package.json"));
|
|
128
|
+
const packageManager = packageJson?.packageManager;
|
|
129
|
+
if (typeof packageManager === "string") {
|
|
130
|
+
const [name] = packageManager.split("@");
|
|
131
|
+
if (["npm", "pnpm", "yarn", "bun"].includes(name)) {
|
|
132
|
+
return name;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (existsSync(path.join(root, "pnpm-lock.yaml"))) {
|
|
137
|
+
return "pnpm";
|
|
138
|
+
}
|
|
139
|
+
if (existsSync(path.join(root, "yarn.lock"))) {
|
|
140
|
+
return "yarn";
|
|
141
|
+
}
|
|
142
|
+
if (existsSync(path.join(root, "bun.lockb")) || existsSync(path.join(root, "bun.lock"))) {
|
|
143
|
+
return "bun";
|
|
144
|
+
}
|
|
145
|
+
return "npm";
|
|
146
|
+
}
|
|
147
|
+
|
|
92
148
|
function inferSourceRoot(packageJson) {
|
|
93
149
|
const entrypoints = [
|
|
94
150
|
packageJson?.openclaw?.entrypoint,
|
|
95
151
|
...(packageJson?.openclaw?.extensions ?? []),
|
|
96
152
|
...(packageJson?.openclaw?.runtimeExtensions ?? []),
|
|
153
|
+
...entrypointStrings(packageJson?.exports?.["."]),
|
|
154
|
+
...entrypointStrings(packageJson?.exports),
|
|
155
|
+
packageJson?.module,
|
|
156
|
+
packageJson?.main,
|
|
97
157
|
].filter((value) => typeof value === "string");
|
|
98
|
-
const entrypoint = entrypoints[0] ??
|
|
99
|
-
if (
|
|
158
|
+
const entrypoint = entrypoints[0] ?? "src/index.js";
|
|
159
|
+
if (stripRelativePrefix(entrypoint).startsWith("src/")) {
|
|
100
160
|
return "src";
|
|
101
161
|
}
|
|
102
162
|
return ".";
|
|
103
163
|
}
|
|
104
164
|
|
|
165
|
+
function entrypointStrings(value) {
|
|
166
|
+
if (typeof value === "string") {
|
|
167
|
+
return [value];
|
|
168
|
+
}
|
|
169
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
return ["import", "default", "require", "node", "module"]
|
|
173
|
+
.map((key) => value[key])
|
|
174
|
+
.filter((item) => typeof item === "string");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function stripRelativePrefix(filePath) {
|
|
178
|
+
return filePath.replace(/^\.\//, "");
|
|
179
|
+
}
|
|
180
|
+
|
|
105
181
|
async function readJsonIfExists(filePath) {
|
|
106
182
|
if (!existsSync(filePath)) {
|
|
107
183
|
return null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { rmSync } from "node:fs";
|
|
3
|
+
import { mkdtemp } from "node:fs/promises";
|
|
3
4
|
import { register } from "node:module";
|
|
4
5
|
import os from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
@@ -26,13 +27,16 @@ async function run(options) {
|
|
|
26
27
|
const pluginRoot = path.resolve(options.cwd ?? process.cwd(), options.pluginRoot ?? path.dirname(entrypoint));
|
|
27
28
|
const workspace = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-mock-sdk-"));
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
cleanupTempDirOnExit(workspace);
|
|
31
|
+
const { loaderPath } = await createMockSdkPackage(workspace, { pluginRoot });
|
|
32
|
+
register(pathToFileURL(loaderPath));
|
|
33
|
+
return await captureLinkedEntrypoint(entrypoint, options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function cleanupTempDirOnExit(dir) {
|
|
37
|
+
process.once("exit", () => {
|
|
38
|
+
rmSync(dir, { force: true, recursive: true });
|
|
39
|
+
});
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
async function captureLinkedEntrypoint(entrypoint, options) {
|
package/src/report.js
CHANGED
|
@@ -246,27 +246,79 @@ export async function writeReport(report, options = {}) {
|
|
|
246
246
|
export async function writeCompatibilityReport(report, options = {}) {
|
|
247
247
|
const outDir = path.resolve(options.cwd ?? process.cwd(), options.outDir ?? "reports");
|
|
248
248
|
const basename = options.basename ?? "plugin-inspector-report";
|
|
249
|
-
const jsonPath = path.join(outDir, `${basename}.json`);
|
|
250
|
-
const markdownPath = path.join(outDir, `${basename}.md`);
|
|
251
|
-
const issuesPath = path.join(outDir, options.issuesBasename ?? "plugin-inspector-issues.md");
|
|
249
|
+
const jsonPath = options.jsonPath ?? path.join(outDir, `${basename}.json`);
|
|
250
|
+
const markdownPath = options.markdownPath ?? path.join(outDir, `${basename}.md`);
|
|
251
|
+
const issuesPath = options.issuesPath ?? path.join(outDir, options.issuesBasename ?? "plugin-inspector-issues.md");
|
|
252
|
+
const markdownOptions = compatibilityRenderOptions(options, {
|
|
253
|
+
title: options.markdownTitle ?? options.title,
|
|
254
|
+
...options.markdownOptions,
|
|
255
|
+
});
|
|
256
|
+
const issuesOptions = compatibilityRenderOptions(options, {
|
|
257
|
+
title: options.issuesTitle ?? options.title,
|
|
258
|
+
...options.issuesOptions,
|
|
259
|
+
});
|
|
252
260
|
|
|
253
261
|
return writeArtifacts(
|
|
254
262
|
[
|
|
255
263
|
{ name: "jsonPath", path: jsonPath, json: report },
|
|
256
|
-
{ name: "markdownPath", path: markdownPath, markdown: renderCompatibilityMarkdownReport(report) },
|
|
257
|
-
{ name: "issuesPath", path: issuesPath, markdown: renderCompatibilityIssuesReport(report) },
|
|
264
|
+
{ name: "markdownPath", path: markdownPath, markdown: renderCompatibilityMarkdownReport(report, markdownOptions) },
|
|
265
|
+
{ name: "issuesPath", path: issuesPath, markdown: renderCompatibilityIssuesReport(report, issuesOptions) },
|
|
258
266
|
],
|
|
259
267
|
{ check: options.check },
|
|
260
268
|
);
|
|
261
269
|
}
|
|
262
270
|
|
|
263
|
-
|
|
264
|
-
|
|
271
|
+
function compatibilityRenderOptions(options, overrides) {
|
|
272
|
+
const renderOptions = {
|
|
273
|
+
formatEvidence: options.formatEvidence,
|
|
274
|
+
severityLabels: options.severityLabels,
|
|
275
|
+
...overrides,
|
|
276
|
+
};
|
|
277
|
+
return Object.fromEntries(Object.entries(renderOptions).filter(([, value]) => value !== undefined));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function renderTextSummary(report, options = {}) {
|
|
281
|
+
const lines = [
|
|
265
282
|
`Status: ${report.status.toUpperCase()}`,
|
|
266
283
|
`Fixtures: ${report.summary.fixtureCount}`,
|
|
267
284
|
`Breakages: ${report.summary.breakageCount}`,
|
|
285
|
+
...(typeof report.summary.issueCount === "number" ? [`Issues: ${report.summary.issueCount}`] : []),
|
|
268
286
|
`Logs: ${report.summary.logCount}`,
|
|
269
|
-
]
|
|
287
|
+
];
|
|
288
|
+
const artifacts = Object.entries(options.artifacts ?? {}).filter(([, filePath]) => Boolean(filePath));
|
|
289
|
+
if (artifacts.length > 0) {
|
|
290
|
+
lines.push("", "Reports:", ...artifacts.map(([name, filePath]) => `- ${artifactLabel(name)}: ${filePath}`));
|
|
291
|
+
}
|
|
292
|
+
const findings = topTextFindings(report, options.topFindings ?? 3);
|
|
293
|
+
if (findings.length > 0) {
|
|
294
|
+
lines.push("", "Top findings:", ...findings.map((finding) => `- ${finding}`));
|
|
295
|
+
}
|
|
296
|
+
return lines.join("\n");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function topTextFindings(report, limit) {
|
|
300
|
+
if (report.status === "pass" || limit <= 0) {
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
return [
|
|
304
|
+
...(report.breakages ?? []).map((finding) => formatTextFinding(finding, "breakage")),
|
|
305
|
+
...(report.issues ?? [])
|
|
306
|
+
.filter((issue) => issue.status === "blocking" || issue.severity === "P0" || issue.severity === "P1")
|
|
307
|
+
.map((issue) => formatTextFinding(issue, issue.severity ?? "issue")),
|
|
308
|
+
...(report.warnings ?? []).map((finding) => formatTextFinding(finding, "warning")),
|
|
309
|
+
].slice(0, limit);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function formatTextFinding(finding, fallbackLevel) {
|
|
313
|
+
const level = finding.level ?? finding.severity ?? fallbackLevel;
|
|
314
|
+
const code = finding.code ? ` ${finding.code}` : "";
|
|
315
|
+
const message = finding.message ?? finding.title ?? "see report";
|
|
316
|
+
const evidence = Array.isArray(finding.evidence) && finding.evidence.length > 0 ? ` (${finding.evidence[0]})` : "";
|
|
317
|
+
return `${String(level).toUpperCase()} ${finding.fixture ?? "unknown"}${code}: ${message}${evidence}`;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function artifactLabel(name) {
|
|
321
|
+
return String(name).replace(/Path$/u, "");
|
|
270
322
|
}
|
|
271
323
|
|
|
272
324
|
export function renderMarkdownReport(report) {
|
package/src/sdk-mock.js
CHANGED
|
@@ -11,8 +11,11 @@ export const mockSdkSubpathExports = {
|
|
|
11
11
|
"emptyPluginConfigSchema",
|
|
12
12
|
],
|
|
13
13
|
core: [
|
|
14
|
+
"buildChannelOutboundSessionRoute",
|
|
14
15
|
"buildChannelConfigSchema",
|
|
15
16
|
"buildPluginConfigSchema",
|
|
17
|
+
"createActionGate",
|
|
18
|
+
"createChannelPluginBase",
|
|
16
19
|
"createChatChannelPlugin",
|
|
17
20
|
"createDedupeCache",
|
|
18
21
|
"defineChannelPluginEntry",
|
|
@@ -22,12 +25,24 @@ export const mockSdkSubpathExports = {
|
|
|
22
25
|
"emptyPluginConfigSchema",
|
|
23
26
|
"jsonResult",
|
|
24
27
|
"readNumberParam",
|
|
28
|
+
"readReactionParams",
|
|
29
|
+
"readStringArrayParam",
|
|
30
|
+
"readStringParam",
|
|
31
|
+
],
|
|
32
|
+
"channel-actions": [
|
|
33
|
+
"createActionGate",
|
|
34
|
+
"jsonResult",
|
|
35
|
+
"readNumberParam",
|
|
36
|
+
"readReactionParams",
|
|
37
|
+
"readStringArrayParam",
|
|
25
38
|
"readStringParam",
|
|
26
39
|
],
|
|
27
40
|
"channel-core": [
|
|
28
41
|
"buildChannelConfigSchema",
|
|
42
|
+
"buildChannelOutboundSessionRoute",
|
|
29
43
|
"buildThreadAwareOutboundSessionRoute",
|
|
30
44
|
"clearAccountEntryFields",
|
|
45
|
+
"createChannelPluginBase",
|
|
31
46
|
"createChatChannelPlugin",
|
|
32
47
|
"defineChannelPluginEntry",
|
|
33
48
|
"defineSetupPluginEntry",
|
|
@@ -718,20 +733,179 @@ function mockSdkSource() {
|
|
|
718
733
|
return typeof entry === "function" ? { register: entry } : entry;
|
|
719
734
|
}
|
|
720
735
|
|
|
736
|
+
function normalizeRegistrationMode(api) {
|
|
737
|
+
return api?.registrationMode ?? "full";
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function isPlainObject(value) {
|
|
741
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function parseWithSchema(schema, value) {
|
|
745
|
+
return schema && typeof schema.parse === "function" ? schema.parse(value) : value;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
function createConfigSchema(schema = {}) {
|
|
749
|
+
if (schema && typeof schema.parse === "function") {
|
|
750
|
+
return schema;
|
|
751
|
+
}
|
|
752
|
+
const shape = isPlainObject(schema?.shape) ? schema.shape : isPlainObject(schema?.properties) ? schema.properties : schema;
|
|
753
|
+
return {
|
|
754
|
+
...schema,
|
|
755
|
+
parse(value = {}) {
|
|
756
|
+
if (!isPlainObject(shape)) {
|
|
757
|
+
return isPlainObject(value) ? value : {};
|
|
758
|
+
}
|
|
759
|
+
const source = isPlainObject(value) ? value : {};
|
|
760
|
+
const output = { ...source };
|
|
761
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
762
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
763
|
+
output[key] = parseWithSchema(fieldSchema, source[key]);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return output;
|
|
767
|
+
},
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
721
771
|
export function definePluginEntry(entry) {
|
|
722
772
|
return normalizeEntry(entry);
|
|
723
773
|
}
|
|
724
774
|
|
|
725
775
|
export function defineChannelPluginEntry(entry) {
|
|
726
|
-
|
|
776
|
+
if (!isPlainObject(entry) || !entry.plugin) {
|
|
777
|
+
return normalizeEntry(entry);
|
|
778
|
+
}
|
|
779
|
+
const resolved = {
|
|
780
|
+
id: entry.id,
|
|
781
|
+
name: entry.name,
|
|
782
|
+
description: entry.description,
|
|
783
|
+
configSchema: createConfigSchema(entry.configSchema),
|
|
784
|
+
channelPlugin: entry.plugin,
|
|
785
|
+
register(api) {
|
|
786
|
+
const mode = normalizeRegistrationMode(api);
|
|
787
|
+
if (mode === "cli-metadata") {
|
|
788
|
+
entry.registerCliMetadata?.(api);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
api.registerChannel?.({ plugin: entry.plugin });
|
|
792
|
+
entry.setRuntime?.(api.runtime);
|
|
793
|
+
if (mode === "discovery") {
|
|
794
|
+
entry.registerCliMetadata?.(api);
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
if (mode !== "full") {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
entry.registerCliMetadata?.(api);
|
|
801
|
+
entry.registerFull?.(api);
|
|
802
|
+
},
|
|
803
|
+
};
|
|
804
|
+
if (entry.setRuntime) {
|
|
805
|
+
resolved.setChannelRuntime = entry.setRuntime;
|
|
806
|
+
}
|
|
807
|
+
return resolved;
|
|
727
808
|
}
|
|
728
809
|
|
|
729
810
|
export function defineSetupPluginEntry(entry) {
|
|
730
|
-
return
|
|
811
|
+
return isPlainObject(entry) && entry.plugin ? entry : { plugin: entry };
|
|
731
812
|
}
|
|
732
813
|
|
|
733
814
|
export function createChatChannelPlugin(entry) {
|
|
734
|
-
|
|
815
|
+
if (!isPlainObject(entry) || !entry.base) {
|
|
816
|
+
return normalizeEntry(entry);
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
...entry.base,
|
|
820
|
+
conversationBindings: {
|
|
821
|
+
supportsCurrentConversationBinding: true,
|
|
822
|
+
...(entry.base.conversationBindings ?? {}),
|
|
823
|
+
},
|
|
824
|
+
...(entry.security ? { security: resolveChannelSecurity(entry.security) } : {}),
|
|
825
|
+
...(entry.pairing ? { pairing: resolveChannelPairing(entry.pairing) } : {}),
|
|
826
|
+
...(entry.threading ? { threading: resolveChannelThreading(entry.threading) } : {}),
|
|
827
|
+
...(entry.outbound ? { outbound: resolveChannelOutbound(entry.outbound) } : {}),
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
export function createChannelPluginBase(params = {}) {
|
|
832
|
+
return {
|
|
833
|
+
id: params.id ?? "fixture-channel",
|
|
834
|
+
meta: { id: params.id ?? "fixture-channel", ...(params.meta ?? {}) },
|
|
835
|
+
...(params.setupWizard ? { setupWizard: params.setupWizard } : {}),
|
|
836
|
+
...(params.capabilities ? { capabilities: params.capabilities } : {}),
|
|
837
|
+
...(params.commands ? { commands: params.commands } : {}),
|
|
838
|
+
...(params.doctor ? { doctor: params.doctor } : {}),
|
|
839
|
+
...(params.agentPrompt ? { agentPrompt: params.agentPrompt } : {}),
|
|
840
|
+
...(params.streaming ? { streaming: params.streaming } : {}),
|
|
841
|
+
...(params.reload ? { reload: params.reload } : {}),
|
|
842
|
+
...(params.gatewayMethods ? { gatewayMethods: params.gatewayMethods } : {}),
|
|
843
|
+
...(params.configSchema ? { configSchema: createConfigSchema(params.configSchema) } : {}),
|
|
844
|
+
...(params.config ? { config: params.config } : {}),
|
|
845
|
+
...(params.security ? { security: params.security } : {}),
|
|
846
|
+
...(params.groups ? { groups: params.groups } : {}),
|
|
847
|
+
setup: params.setup ?? (() => ({})),
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function resolveChannelSecurity(security) {
|
|
852
|
+
if (!isPlainObject(security) || !security.dm) {
|
|
853
|
+
return security;
|
|
854
|
+
}
|
|
855
|
+
return {
|
|
856
|
+
resolveDmPolicy: ({ account } = {}) => ({
|
|
857
|
+
policy: security.dm.resolvePolicy?.(account ?? {}) ?? security.dm.defaultPolicy ?? "allow",
|
|
858
|
+
allowFrom: security.dm.resolveAllowFrom?.(account ?? {}) ?? [],
|
|
859
|
+
}),
|
|
860
|
+
...(security.collectWarnings ? { collectWarnings: security.collectWarnings } : {}),
|
|
861
|
+
...(security.collectAuditFindings ? { collectAuditFindings: security.collectAuditFindings } : {}),
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function resolveChannelPairing(pairing) {
|
|
866
|
+
if (!isPlainObject(pairing) || !pairing.text) {
|
|
867
|
+
return pairing;
|
|
868
|
+
}
|
|
869
|
+
return {
|
|
870
|
+
idLabel: pairing.text.idLabel,
|
|
871
|
+
normalizeAllowEntry: pairing.text.normalizeAllowEntry,
|
|
872
|
+
notifyApproval: (ctx) => pairing.text.notify?.({ ...ctx, message: pairing.text.message }),
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function resolveChannelThreading(threading) {
|
|
877
|
+
if (!isPlainObject(threading)) {
|
|
878
|
+
return threading;
|
|
879
|
+
}
|
|
880
|
+
if (threading.resolveReplyToMode) {
|
|
881
|
+
return threading;
|
|
882
|
+
}
|
|
883
|
+
return {
|
|
884
|
+
...threading,
|
|
885
|
+
resolveReplyToMode: () =>
|
|
886
|
+
threading.topLevelReplyToMode ??
|
|
887
|
+
threading.scopedAccountReplyToMode?.fallback ??
|
|
888
|
+
"thread",
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function resolveChannelOutbound(outbound) {
|
|
893
|
+
if (!isPlainObject(outbound) || !outbound.attachedResults) {
|
|
894
|
+
return outbound;
|
|
895
|
+
}
|
|
896
|
+
const { base = {}, attachedResults } = outbound;
|
|
897
|
+
return {
|
|
898
|
+
...base,
|
|
899
|
+
...(attachedResults.sendText
|
|
900
|
+
? { sendText: async (ctx) => ({ channel: attachedResults.channel, ...(await attachedResults.sendText(ctx)) }) }
|
|
901
|
+
: {}),
|
|
902
|
+
...(attachedResults.sendMedia
|
|
903
|
+
? { sendMedia: async (ctx) => ({ channel: attachedResults.channel, ...(await attachedResults.sendMedia(ctx)) }) }
|
|
904
|
+
: {}),
|
|
905
|
+
...(attachedResults.sendPoll
|
|
906
|
+
? { sendPoll: async (ctx) => ({ channel: attachedResults.channel, ...(await attachedResults.sendPoll(ctx)) }) }
|
|
907
|
+
: {}),
|
|
908
|
+
};
|
|
735
909
|
}
|
|
736
910
|
|
|
737
911
|
export function definePlugin(entry) {
|
|
@@ -759,13 +933,13 @@ export function defineSingleProviderPluginEntry(options) {
|
|
|
759
933
|
}
|
|
760
934
|
|
|
761
935
|
export function buildPluginConfigSchema(schema = {}) {
|
|
762
|
-
return schema;
|
|
936
|
+
return createConfigSchema(schema);
|
|
763
937
|
}
|
|
764
938
|
|
|
765
|
-
export const emptyPluginConfigSchema = { type: "object", properties: {}, additionalProperties: false };
|
|
939
|
+
export const emptyPluginConfigSchema = createConfigSchema({ type: "object", properties: {}, additionalProperties: false });
|
|
766
940
|
|
|
767
941
|
export function buildChannelConfigSchema(schema = {}) {
|
|
768
|
-
return schema;
|
|
942
|
+
return createConfigSchema(schema);
|
|
769
943
|
}
|
|
770
944
|
|
|
771
945
|
export const emptyChannelConfigSchema = emptyPluginConfigSchema;
|
|
@@ -774,13 +948,63 @@ export function jsonResult(value) {
|
|
|
774
948
|
return { content: [{ type: "text", text: JSON.stringify(value) }] };
|
|
775
949
|
}
|
|
776
950
|
|
|
777
|
-
export function readNumberParam(value,
|
|
778
|
-
const
|
|
779
|
-
|
|
951
|
+
export function readNumberParam(value, keyOrFallback = 0, options = {}) {
|
|
952
|
+
const raw = isPlainObject(value) ? value[keyOrFallback] : value;
|
|
953
|
+
const parsed = Number(raw);
|
|
954
|
+
if (Number.isFinite(parsed)) {
|
|
955
|
+
return options.integer ? Math.trunc(parsed) : parsed;
|
|
956
|
+
}
|
|
957
|
+
return isPlainObject(value) ? undefined : keyOrFallback;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
export function readStringParam(value, keyOrFallback = "") {
|
|
961
|
+
if (isPlainObject(value)) {
|
|
962
|
+
const raw = value[keyOrFallback];
|
|
963
|
+
return typeof raw === "string" ? raw : undefined;
|
|
964
|
+
}
|
|
965
|
+
return typeof value === "string" ? value : keyOrFallback;
|
|
780
966
|
}
|
|
781
967
|
|
|
782
|
-
export function
|
|
783
|
-
|
|
968
|
+
export function readStringArrayParam(value, key) {
|
|
969
|
+
const raw = isPlainObject(value) ? value[key] : value;
|
|
970
|
+
if (Array.isArray(raw)) {
|
|
971
|
+
return raw.map((entry) => String(entry));
|
|
972
|
+
}
|
|
973
|
+
return typeof raw === "string" && raw ? [raw] : [];
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
export function readReactionParams(value = {}) {
|
|
977
|
+
return {
|
|
978
|
+
messageId: value.messageId ?? value.id ?? "",
|
|
979
|
+
reaction: value.reaction ?? value.emoji ?? "",
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
export function createActionGate(actions = {}) {
|
|
984
|
+
return (key, defaultValue = true) => {
|
|
985
|
+
const value = actions?.[key];
|
|
986
|
+
return value === undefined ? defaultValue : value !== false;
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
export function buildChannelOutboundSessionRoute(params = {}) {
|
|
991
|
+
const peer = params.peer ?? { kind: params.chatType ?? "direct", id: params.to ?? "fixture-peer" };
|
|
992
|
+
const baseSessionKey = [
|
|
993
|
+
params.agentId ?? "agent",
|
|
994
|
+
params.channel ?? "channel",
|
|
995
|
+
params.accountId ?? "default",
|
|
996
|
+
peer.kind,
|
|
997
|
+
peer.id,
|
|
998
|
+
].filter(Boolean).join(":");
|
|
999
|
+
return {
|
|
1000
|
+
sessionKey: baseSessionKey,
|
|
1001
|
+
baseSessionKey,
|
|
1002
|
+
peer,
|
|
1003
|
+
chatType: params.chatType ?? peer.kind ?? "direct",
|
|
1004
|
+
from: params.from ?? "fixture-source",
|
|
1005
|
+
to: params.to ?? peer.id,
|
|
1006
|
+
...(params.threadId !== undefined ? { threadId: params.threadId } : {}),
|
|
1007
|
+
};
|
|
784
1008
|
}
|
|
785
1009
|
|
|
786
1010
|
export function createDedupeCache() {
|
|
@@ -951,14 +1175,28 @@ export function createAuthRateLimiter() {
|
|
|
951
1175
|
}
|
|
952
1176
|
|
|
953
1177
|
export function createProviderApiKeyAuthMethod(options = {}) {
|
|
954
|
-
return {
|
|
1178
|
+
return {
|
|
1179
|
+
id: options.id ?? "apiKey",
|
|
1180
|
+
type: "apiKey",
|
|
1181
|
+
...options,
|
|
1182
|
+
async resolve(ctx = {}) {
|
|
1183
|
+
return ctx.apiKey ?? ctx.key ?? ctx.token ?? null;
|
|
1184
|
+
},
|
|
1185
|
+
};
|
|
955
1186
|
}
|
|
956
1187
|
|
|
957
1188
|
export function buildSingleProviderApiKeyCatalog(options = {}) {
|
|
1189
|
+
const auth = options.auth ?? createProviderApiKeyAuthMethod(options.authOptions);
|
|
958
1190
|
return {
|
|
1191
|
+
auth,
|
|
959
1192
|
order: "simple",
|
|
960
1193
|
async run(ctx) {
|
|
961
|
-
|
|
1194
|
+
const provider = (await options.buildProvider?.(ctx)) ?? options.provider ?? { id: options.id ?? "provider", auth };
|
|
1195
|
+
return {
|
|
1196
|
+
provider,
|
|
1197
|
+
providers: [provider],
|
|
1198
|
+
models: (await options.buildModels?.(ctx)) ?? options.models ?? [],
|
|
1199
|
+
};
|
|
962
1200
|
},
|
|
963
1201
|
};
|
|
964
1202
|
}
|
|
@@ -1084,7 +1322,7 @@ export function createSubsystemLogger() {
|
|
|
1084
1322
|
}
|
|
1085
1323
|
|
|
1086
1324
|
export function buildThreadAwareOutboundSessionRoute(route = {}) {
|
|
1087
|
-
return route;
|
|
1325
|
+
return route.route ?? route;
|
|
1088
1326
|
}
|
|
1089
1327
|
|
|
1090
1328
|
export function clearAccountEntryFields(entry = {}) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { buildContractCapture } from "./contract-capture.js";
|
|
2
|
+
import { buildSyntheticProbePlan } from "./synthetic-probes.js";
|
|
3
|
+
|
|
4
|
+
export function buildSyntheticProbePlanFromReport(report, options = {}) {
|
|
5
|
+
const capture = options.capture ?? buildContractCapture({
|
|
6
|
+
report,
|
|
7
|
+
hookAssertions: options.hookAssertions,
|
|
8
|
+
hookContexts: options.hookContexts,
|
|
9
|
+
hookEvents: options.hookEvents,
|
|
10
|
+
registrationArguments: options.registrationArguments,
|
|
11
|
+
registrationAssertions: options.registrationAssertions,
|
|
12
|
+
});
|
|
13
|
+
return buildSyntheticProbePlan({
|
|
14
|
+
capture,
|
|
15
|
+
hookContexts: options.hookContexts,
|
|
16
|
+
hookEvents: options.hookEvents,
|
|
17
|
+
registrationArguments: options.registrationArguments,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { rmSync } from "node:fs";
|
|
3
|
+
import { mkdtemp } from "node:fs/promises";
|
|
3
4
|
import { register } from "node:module";
|
|
4
5
|
import os from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
@@ -59,17 +60,20 @@ async function captureForSyntheticProbes(entrypoint, options) {
|
|
|
59
60
|
const resolvedEntrypoint = path.resolve(process.cwd(), entrypoint);
|
|
60
61
|
const pluginRoot = path.resolve(process.cwd(), options.pluginRoot ?? path.dirname(resolvedEntrypoint));
|
|
61
62
|
const workspace = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-synthetic-mock-sdk-"));
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
cleanupTempDirOnExit(workspace);
|
|
64
|
+
const { loaderPath } = await createMockSdkPackage(workspace, { pluginRoot });
|
|
65
|
+
register(pathToFileURL(loaderPath));
|
|
66
|
+
return captureEntrypoint(entrypoint, {
|
|
67
|
+
...options,
|
|
68
|
+
mockSdk: false,
|
|
69
|
+
pluginRoot,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function cleanupTempDirOnExit(dir) {
|
|
74
|
+
process.once("exit", () => {
|
|
75
|
+
rmSync(dir, { force: true, recursive: true });
|
|
76
|
+
});
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
function readFlag(commandArgs, name) {
|