agentgui 1.0.912 → 1.0.914
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/AGENTS.md +20 -0
- package/lib/plugins/stream-plugin.js +1 -1
- package/lib/tool-spawner.js +26 -8
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -11,3 +11,23 @@ Fix: `bun run scripts/capture-screenshots.mjs` (not `node ...`).
|
|
|
11
11
|
Why it works: `process.execPath` becomes bun, so the spawned child server also runs under bun and loads `bun:sqlite` natively — no compiled binding needed.
|
|
12
12
|
|
|
13
13
|
Rule: any CI step that spawns the agentgui server (directly or via a script that inherits `process.execPath`) must invoke it with `bun`.
|
|
14
|
+
|
|
15
|
+
## Plugin Dependencies
|
|
16
|
+
|
|
17
|
+
**`lib/plugins/stream-plugin.js` must not import packages missing from package.json.**
|
|
18
|
+
|
|
19
|
+
The plugin loader runs at startup and logs failures silently if a plugin import fails. If stream plugin fails to load due to missing dependency (e.g., `uuid`), the error cascades: `agents` and `websocket` plugins both declare `stream` as a dependency and fail with "Plugin stream not found in registry", cascading to boot failure.
|
|
20
|
+
|
|
21
|
+
Fix for uuid: replaced `import { v4 as uuidv4 } from 'uuid'` with `import { randomUUID as uuidv4 } from 'crypto'` (Node.js built-in, zero deps).
|
|
22
|
+
|
|
23
|
+
Pattern: all imports in lib/plugins/* must be either built-in (crypto, fs, path, etc.) or already in package.json. No new npm packages should be added to plugins without adding them to dependencies.
|
|
24
|
+
|
|
25
|
+
## Plugin Tool Provisioning on Windows
|
|
26
|
+
|
|
27
|
+
**`lib/tool-spawner.js` must iterate bun → npx fallback and match cross-platform command-not-found errors.**
|
|
28
|
+
|
|
29
|
+
On Windows hosts without bun installed, the auto-provisioner on startup and 6h periodic update checker failed silently when spawnBunxProc tried `bun.cmd` directly. The missing command error came via process stderr+close, not the 'error' event, so simple ENOENT detection was insufficient. Additionally, the error message format differs by OS: Windows shows "'bun.cmd' is not recognized as an internal or external command", Linux/Mac show "command not found" or "cannot find".
|
|
30
|
+
|
|
31
|
+
Fix: `BUNX_RUNNERS` array iterates `['bun', 'npx']` and tries each in sequence. Error detection regex `isMissingCmdError` matches `/not recognized|ENOENT|command not found|cannot find/i` on both `error.message` and captured stdout+stderr. Only falls through to next runner when the `missing` flag is set.
|
|
32
|
+
|
|
33
|
+
Pattern: When a binary might not exist on all platforms, use a runner fallback strategy. Always capture and check both error.message and process output streams. Cross-platform error detection requires regex alternation on common message patterns.
|
package/lib/tool-spawner.js
CHANGED
|
@@ -29,17 +29,24 @@ const spawnNpmInstall = (pkg, onProgress) => new Promise((resolve) => {
|
|
|
29
29
|
proc.on('error', (err) => { clearTimeout(timer); if (!completed) { completed = true; resolve({ success: false, error: err.message }); } });
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
const
|
|
33
|
-
|
|
32
|
+
const BUNX_RUNNERS = [
|
|
33
|
+
{ cmd: isWindows ? 'bun.cmd' : 'bun', args: (pkg) => ['x', pkg] },
|
|
34
|
+
{ cmd: isWindows ? 'npx.cmd' : 'npx', args: (pkg) => ['-y', pkg] },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const isMissingCmdError = (s) => /not recognized|ENOENT|command not found|cannot find/i.test(s || '');
|
|
38
|
+
|
|
39
|
+
const spawnBunxOnce = (runner, pkg, onProgress) => new Promise((resolve) => {
|
|
40
|
+
const cmd = runner.cmd;
|
|
34
41
|
let completed = false, stderr = '', stdout = '';
|
|
35
42
|
let lastDataTime = Date.now();
|
|
36
43
|
let proc;
|
|
37
44
|
try {
|
|
38
|
-
proc = spawn(cmd,
|
|
45
|
+
proc = spawn(cmd, runner.args(pkg), { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows, windowsHide: true });
|
|
39
46
|
} catch (err) {
|
|
40
|
-
return resolve({ success: false, error: `Failed to spawn
|
|
47
|
+
return resolve({ success: false, error: `Failed to spawn ${cmd}: ${err.message}`, missing: isMissingCmdError(err.message) });
|
|
41
48
|
}
|
|
42
|
-
if (!proc) return resolve({ success: false, error:
|
|
49
|
+
if (!proc) return resolve({ success: false, error: `Failed to spawn ${cmd} process`, missing: true });
|
|
43
50
|
|
|
44
51
|
const timer = setTimeout(() => {
|
|
45
52
|
if (!completed) { completed = true; try { proc.kill('SIGKILL'); } catch (_) {} resolve({ success: false, error: 'Timeout (5min)' }); }
|
|
@@ -48,7 +55,7 @@ const spawnBunxProc = (pkg, onProgress) => new Promise((resolve) => {
|
|
|
48
55
|
const heartbeatTimer = setInterval(() => {
|
|
49
56
|
if (completed) { clearInterval(heartbeatTimer); return; }
|
|
50
57
|
const elapsed = Date.now() - lastDataTime;
|
|
51
|
-
if (elapsed > 30000) console.warn(`[tool-manager] No output from
|
|
58
|
+
if (elapsed > 30000) console.warn(`[tool-manager] No output from ${cmd} ${pkg} for ${elapsed}ms - process may be hung`);
|
|
52
59
|
}, 30000);
|
|
53
60
|
|
|
54
61
|
const onData = (d) => { lastDataTime = Date.now(); if (onProgress) onProgress({ type: 'progress', data: d.toString() }); };
|
|
@@ -64,16 +71,27 @@ const spawnBunxProc = (pkg, onProgress) => new Promise((resolve) => {
|
|
|
64
71
|
const ok = [code === 0, output.includes('upgraded'), output.includes('registered'),
|
|
65
72
|
output.includes('Hooks registered'), output.includes('successfully'),
|
|
66
73
|
output.includes('Done'), code === 0 && !output.includes('error')].some(Boolean);
|
|
67
|
-
resolve(ok ? { success: true, error: null, pkg } : { success: false, error: output.substring(0, 1000) || 'Failed' });
|
|
74
|
+
resolve(ok ? { success: true, error: null, pkg } : { success: false, error: output.substring(0, 1000) || 'Failed', missing: isMissingCmdError(output) });
|
|
68
75
|
});
|
|
69
76
|
|
|
70
77
|
proc.on('error', (err) => {
|
|
71
78
|
clearTimeout(timer);
|
|
72
79
|
clearInterval(heartbeatTimer);
|
|
73
|
-
if (!completed) { completed = true; resolve({ success: false, error: `Process error: ${err.message}
|
|
80
|
+
if (!completed) { completed = true; resolve({ success: false, error: `Process error: ${err.message}`, missing: isMissingCmdError(err.message) }); }
|
|
74
81
|
});
|
|
75
82
|
});
|
|
76
83
|
|
|
84
|
+
const spawnBunxProc = async (pkg, onProgress) => {
|
|
85
|
+
let lastResult = null;
|
|
86
|
+
for (const runner of BUNX_RUNNERS) {
|
|
87
|
+
const result = await spawnBunxOnce(runner, pkg, onProgress);
|
|
88
|
+
if (result.success) return result;
|
|
89
|
+
lastResult = result;
|
|
90
|
+
if (!result.missing) return result;
|
|
91
|
+
}
|
|
92
|
+
return lastResult || { success: false, error: 'No runner available' };
|
|
93
|
+
};
|
|
94
|
+
|
|
77
95
|
function spawnForTool(tool, onProgress) {
|
|
78
96
|
const pkg = tool.installPkg || tool.pkg;
|
|
79
97
|
return tool.category === 'cli' ? spawnNpmInstall(pkg, onProgress) : spawnBunxProc(pkg, onProgress);
|