@openclaw/plugin-inspector 0.1.1 → 0.1.3

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.
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { mkdtemp, rm, symlink } from "node:fs/promises";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import { register } from "node:module";
3
4
  import os from "node:os";
4
5
  import path from "node:path";
5
6
  import { pathToFileURL } from "node:url";
@@ -7,12 +8,16 @@ import { createCaptureApi } from "./capture-api.js";
7
8
  import { createMockSdkPackage } from "./sdk-mock.js";
8
9
 
9
10
  const options = JSON.parse(process.argv[2] ?? "{}");
11
+ let activeOutputCapture = null;
10
12
 
11
13
  try {
12
14
  const result = await run(options);
13
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
15
+ writeRunnerStdout(`${JSON.stringify(result, null, 2)}\n`);
14
16
  } catch (error) {
15
- process.stderr.write(`${error.stack ?? error.message}\n`);
17
+ if (error.failureClass) {
18
+ writeRunnerStderr(`[plugin-inspector:${error.failureClass}]\n`);
19
+ }
20
+ writeRunnerStderr(`${error.stack ?? error.message}\n`);
16
21
  process.exitCode = 1;
17
22
  }
18
23
 
@@ -22,37 +27,79 @@ async function run(options) {
22
27
  const workspace = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-mock-sdk-"));
23
28
 
24
29
  try {
25
- await createMockSdkPackage(workspace);
26
- const linkedPluginRoot = path.join(workspace, "plugin");
27
- await symlink(pluginRoot, linkedPluginRoot, "junction");
28
- const linkedEntrypoint = path.join(linkedPluginRoot, path.relative(pluginRoot, entrypoint));
29
- return await captureLinkedEntrypoint(linkedEntrypoint, options);
30
+ const { loaderPath } = await createMockSdkPackage(workspace, { pluginRoot });
31
+ register(pathToFileURL(loaderPath));
32
+ return await captureLinkedEntrypoint(entrypoint, options);
30
33
  } finally {
31
34
  await rm(workspace, { force: true, recursive: true });
32
35
  }
33
36
  }
34
37
 
35
38
  async function captureLinkedEntrypoint(entrypoint, options) {
36
- const module = await import(pathToFileURL(entrypoint).href);
39
+ const outputCapture = installProcessOutputCapture();
40
+ activeOutputCapture = outputCapture;
41
+
42
+ let module;
43
+ try {
44
+ module = await import(pathToFileURL(entrypoint).href);
45
+ } catch (error) {
46
+ await drainAsyncOutput();
47
+ throw capturePhaseError(error, "entrypoint-import-error");
48
+ }
37
49
  const register = findRegisterExport(module);
38
50
 
39
51
  if (!register) {
40
- return {
41
- status: "no-register-export",
42
- entrypoint: options.entrypoint,
43
- mockSdk: true,
44
- captured: [],
45
- };
52
+ await drainAsyncOutput();
53
+ return withProcessOutput(
54
+ {
55
+ status: "no-register-export",
56
+ entrypoint: options.entrypoint,
57
+ mockSdk: true,
58
+ captured: [],
59
+ },
60
+ outputCapture,
61
+ );
46
62
  }
47
63
 
48
64
  const api = createCaptureApi(options.apiOptions);
49
- await register(api);
50
- return {
65
+ try {
66
+ await register(api);
67
+ } catch (error) {
68
+ await drainAsyncOutput();
69
+ throw capturePhaseError(error, "registration-execution-error");
70
+ }
71
+ await drainAsyncOutput();
72
+
73
+ const result = {
51
74
  status: "captured",
52
75
  entrypoint: options.entrypoint,
53
76
  mockSdk: true,
54
77
  captured: api.getCapturedContracts(),
55
78
  };
79
+ if (options.apiOptions?.retainHandlers === true) {
80
+ result.retained = api.getRetainedContracts();
81
+ }
82
+ return withProcessOutput(result, outputCapture);
83
+ }
84
+
85
+ function withProcessOutput(result, outputCapture) {
86
+ const stdout = outputCapture.stdout();
87
+ const stderr = outputCapture.stderr();
88
+ if (stdout.length === 0 && stderr.length === 0) {
89
+ return result;
90
+ }
91
+ return {
92
+ ...result,
93
+ processOutput: {
94
+ stdout,
95
+ stderr,
96
+ },
97
+ };
98
+ }
99
+
100
+ function capturePhaseError(error, failureClass) {
101
+ error.failureClass = failureClass;
102
+ return error;
56
103
  }
57
104
 
58
105
  function findRegisterExport(module) {
@@ -67,3 +114,49 @@ function findRegisterExport(module) {
67
114
  }
68
115
  return null;
69
116
  }
117
+
118
+ function installProcessOutputCapture() {
119
+ const stdoutChunks = [];
120
+ const stderrChunks = [];
121
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
122
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
123
+
124
+ process.stdout.write = (chunk, encoding, callback) => {
125
+ stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk));
126
+ invokeWriteCallback(encoding, callback);
127
+ return true;
128
+ };
129
+ process.stderr.write = (chunk, encoding, callback) => {
130
+ stderrChunks.push(Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk));
131
+ invokeWriteCallback(encoding, callback);
132
+ return true;
133
+ };
134
+
135
+ return {
136
+ originalStdoutWrite,
137
+ originalStderrWrite,
138
+ stdout: () => stdoutChunks.join(""),
139
+ stderr: () => stderrChunks.join(""),
140
+ };
141
+ }
142
+
143
+ function invokeWriteCallback(encoding, callback) {
144
+ if (typeof encoding === "function") {
145
+ encoding();
146
+ } else if (typeof callback === "function") {
147
+ callback();
148
+ }
149
+ }
150
+
151
+ async function drainAsyncOutput() {
152
+ await new Promise((resolve) => setTimeout(resolve, 0));
153
+ await new Promise((resolve) => setImmediate(resolve));
154
+ }
155
+
156
+ function writeRunnerStdout(text) {
157
+ (activeOutputCapture?.originalStdoutWrite ?? process.stdout.write.bind(process.stdout))(text);
158
+ }
159
+
160
+ function writeRunnerStderr(text) {
161
+ (activeOutputCapture?.originalStderrWrite ?? process.stderr.write.bind(process.stderr))(text);
162
+ }
@@ -31,6 +31,7 @@ export async function readOpenClawTargetSurface(options = {}) {
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
33
  const manifestTypesPath = path.join(resolvedPath, "src/plugins/manifest.ts");
34
+ const pluginSdkEntrypointsPath = path.join(resolvedPath, "src/plugin-sdk/entrypoints.ts");
34
35
  const packagePath = path.join(resolvedPath, "package.json");
35
36
 
36
37
  const registrySource = await readFile(registryPath, "utf8");
@@ -48,6 +49,18 @@ export async function readOpenClawTargetSurface(options = {}) {
48
49
  const sdkExports = existsSync(packagePath)
49
50
  ? parsePluginSdkExports(JSON.parse(await readFile(packagePath, "utf8")))
50
51
  : [];
52
+ const pluginSdkEntrypointsSource = existsSync(pluginSdkEntrypointsPath)
53
+ ? await readFile(pluginSdkEntrypointsPath, "utf8")
54
+ : "";
55
+ const reservedSdkExports = pluginSdkEntrypointsSource
56
+ ? parsePluginSdkEntrypointSpecifiers(pluginSdkEntrypointsSource, "reservedBundledPluginSdkEntrypoints")
57
+ : [];
58
+ const supportedFacadeSdkExports = pluginSdkEntrypointsSource
59
+ ? parsePluginSdkEntrypointSpecifiers(pluginSdkEntrypointsSource, "supportedBundledFacadeSdkEntrypoints")
60
+ : [];
61
+ const publicPluginOwnedSdkExports = pluginSdkEntrypointsSource
62
+ ? parsePluginSdkEntrypointSpecifiers(pluginSdkEntrypointsSource, "publicPluginOwnedSdkEntrypoints")
63
+ : [];
51
64
 
52
65
  return {
53
66
  configuredPath: requestedPath,
@@ -69,6 +82,13 @@ export async function readOpenClawTargetSurface(options = {}) {
69
82
  packagePath: existsSync(packagePath) ? relativePath(rootDir, packagePath) : null,
70
83
  sdkExportCount: sdkExports.length,
71
84
  sdkExports,
85
+ pluginSdkEntrypointsPath: existsSync(pluginSdkEntrypointsPath)
86
+ ? relativePath(rootDir, pluginSdkEntrypointsPath)
87
+ : null,
88
+ reservedSdkExportCount: reservedSdkExports.length,
89
+ reservedSdkExports,
90
+ supportedFacadeSdkExports,
91
+ publicPluginOwnedSdkExports,
72
92
  manifestTypesPath: existsSync(manifestTypesPath) ? relativePath(rootDir, manifestTypesPath) : null,
73
93
  manifestFieldCount: manifestFields.length,
74
94
  manifestFields,
@@ -149,11 +169,18 @@ function emptyTargetSurface({ configuredPath, searchedPaths = undefined, status
149
169
  apiRegistrars: [],
150
170
  capturedRegistrars: [],
151
171
  sdkExports: [],
172
+ reservedSdkExports: [],
173
+ supportedFacadeSdkExports: [],
174
+ publicPluginOwnedSdkExports: [],
152
175
  manifestFields: [],
153
176
  manifestContractFields: [],
154
177
  };
155
178
  }
156
179
 
180
+ export function parsePluginSdkEntrypointSpecifiers(source, exportName) {
181
+ return parseExportedStringArray(source, exportName).map((entrypoint) => `openclaw/plugin-sdk/${entrypoint}`).sort();
182
+ }
183
+
157
184
  function parseCapturedRegistrars(source) {
158
185
  return unique([...source.matchAll(/^\s*(register[A-Za-z0-9]+)\s*\(/gm)].map((match) => match[1])).sort();
159
186
  }
package/src/report.js CHANGED
@@ -160,6 +160,7 @@ export async function buildCompatibilityReport(options = {}) {
160
160
  warningCount: warnings.length,
161
161
  suggestionCount: suggestions.length,
162
162
  decisionCount: decisions.length,
163
+ logCount: logs.length,
163
164
  issueCount: issues.length,
164
165
  p0IssueCount: issues.filter((issue) => issue.severity === "P0").length,
165
166
  p1IssueCount: issues.filter((issue) => issue.severity === "P1").length,
@@ -71,13 +71,20 @@ export function renderRuntimeCaptureMarkdown(captureReport, options = {}) {
71
71
  result.fixture,
72
72
  result.status,
73
73
  result.entrypoint,
74
- (result.captured ?? []).map((item) => `${item.kind}:${item.name}`).join(", ") || result.error || "-",
74
+ (result.captured ?? []).map((item) => `${item.kind}:${item.name}`).join(", ") || formatCaptureError(result),
75
75
  ]),
76
76
  ["Fixture", "Status", "Entrypoint", "Captured"],
77
77
  ),
78
78
  ].join("\n");
79
79
  }
80
80
 
81
+ function formatCaptureError(result) {
82
+ if (!result.error) {
83
+ return "-";
84
+ }
85
+ return result.failureClass ? `${result.failureClass}: ${result.error}` : result.error;
86
+ }
87
+
81
88
  function captureTargets(fixture, rootDir) {
82
89
  return fixture.packages.flatMap((packageSummary) => {
83
90
  const packageRoot = path.dirname(path.resolve(rootDir, packageSummary.path));
@@ -124,6 +131,9 @@ async function captureTarget(target, options) {
124
131
  packagePath: target.packagePath,
125
132
  entrypoint: target.entrypoint.relativePath,
126
133
  error: error.message,
134
+ ...(error.failureClass ? { failureClass: error.failureClass } : {}),
135
+ ...(error.missingExport ? { missingExport: error.missingExport } : {}),
136
+ ...(error.missingModule ? { missingModule: error.missingModule } : {}),
127
137
  captured: [],
128
138
  };
129
139
  }