agentgui 1.0.694 → 1.0.695
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/lib/acp-sdk-manager.js +16 -12
- package/lib/claude-runner.js +114 -169
- package/lib/ws-handlers-conv.js +37 -1
- package/package.json +6 -2
- package/server.js +3 -2
package/lib/acp-sdk-manager.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
+
import pRetry, { AbortError } from 'p-retry';
|
|
6
7
|
|
|
7
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const projectRoot = path.resolve(__dirname, '..');
|
|
@@ -67,13 +68,11 @@ function startProcess(tool) {
|
|
|
67
68
|
log(tool.id + ' exited code ' + code);
|
|
68
69
|
const window = Date.now() - RESTART_WINDOW_MS;
|
|
69
70
|
entry.restarts = entry.restarts.filter(t => t > window);
|
|
70
|
-
if (entry.restarts.length
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
log(tool.id + ' max restarts reached');
|
|
76
|
-
}
|
|
71
|
+
if (entry.restarts.length >= MAX_RESTARTS) { log(tool.id + ' max restarts reached'); return; }
|
|
72
|
+
const attempt = entry.restarts.length;
|
|
73
|
+
entry.restarts.push(Date.now());
|
|
74
|
+
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
|
|
75
|
+
setTimeout(() => { if (!shuttingDown) startProcess(tool); }, delay);
|
|
77
76
|
});
|
|
78
77
|
|
|
79
78
|
processes.set(tool.id, entry);
|
|
@@ -133,12 +132,17 @@ export async function ensureRunning(agentId) {
|
|
|
133
132
|
entry = startProcess(tool);
|
|
134
133
|
if (!entry) return null;
|
|
135
134
|
}
|
|
136
|
-
|
|
137
|
-
await
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
try {
|
|
136
|
+
await pRetry(async () => {
|
|
137
|
+
if (shuttingDown) throw new AbortError('shutting down');
|
|
138
|
+
await checkHealth(agentId);
|
|
139
|
+
if (!processes.get(agentId)?.healthy) throw new Error('not healthy yet');
|
|
140
|
+
}, { retries: 20, minTimeout: 500, maxTimeout: 500, factor: 1 });
|
|
141
|
+
resetIdleTimer(agentId);
|
|
142
|
+
return tool.port;
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
140
145
|
}
|
|
141
|
-
return null;
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
export function touch(agentId) {
|
package/lib/claude-runner.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import { execa } from 'execa';
|
|
2
3
|
|
|
3
4
|
const isWindows = process.platform === 'win32';
|
|
4
5
|
|
|
@@ -80,191 +81,135 @@ class AgentRunner {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
async runDirect(prompt, cwd, config = {}) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
} = config;
|
|
90
|
-
|
|
91
|
-
if (process.env.DEBUG === '1') {
|
|
92
|
-
const sp = config.systemPrompt;
|
|
93
|
-
console.error(`[prompt-trace] convId=${config.conversationId} promptType=${typeof prompt} promptLen=${String(prompt).length} prompt0=${String(prompt).slice(0, 100)} sysLen=${sp ? String(sp).length : 0}`);
|
|
94
|
-
}
|
|
95
|
-
const args = this.buildArgs(prompt, config);
|
|
96
|
-
const spawnOpts = getSpawnOptions(cwd);
|
|
97
|
-
if (Object.keys(this.spawnEnv).length > 0) {
|
|
98
|
-
spawnOpts.env = { ...spawnOpts.env, ...this.spawnEnv };
|
|
99
|
-
for (const [k, v] of Object.entries(this.spawnEnv)) {
|
|
100
|
-
if (v === undefined) delete spawnOpts.env[k];
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
// Tell hooks the actual project dir (used by mcp-thorns/codebasesearch)
|
|
104
|
-
if (cwd) spawnOpts.env.CLAUDE_PROJECT_DIR = cwd;
|
|
105
|
-
if (this.closeStdin) {
|
|
106
|
-
spawnOpts.stdio = ['ignore', 'pipe', 'pipe'];
|
|
107
|
-
}
|
|
108
|
-
const proc = spawn(this.command, args, spawnOpts);
|
|
109
|
-
console.log(`[${this.id}] Spawned PID ${proc.pid} closeStdin=${this.closeStdin}`);
|
|
110
|
-
|
|
111
|
-
if (config.onPid) {
|
|
112
|
-
try { config.onPid(proc.pid); } catch (e) {}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (config.onProcess) {
|
|
116
|
-
try { config.onProcess(proc); } catch (e) {}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
let jsonBuffer = '';
|
|
120
|
-
const outputs = [];
|
|
121
|
-
let timedOut = false;
|
|
122
|
-
let sessionId = null;
|
|
123
|
-
let rateLimited = false;
|
|
124
|
-
let retryAfterSec = 60;
|
|
125
|
-
let authError = false;
|
|
126
|
-
let authErrorMessage = '';
|
|
127
|
-
let stderrBuffer = '';
|
|
84
|
+
const {
|
|
85
|
+
timeout = 300000,
|
|
86
|
+
onEvent = null,
|
|
87
|
+
onError = null,
|
|
88
|
+
onRateLimit = null
|
|
89
|
+
} = config;
|
|
128
90
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}, timeout);
|
|
91
|
+
if (process.env.DEBUG === '1') {
|
|
92
|
+
const sp = config.systemPrompt;
|
|
93
|
+
console.error(`[prompt-trace] convId=${config.conversationId} promptType=${typeof prompt} promptLen=${String(prompt).length} prompt0=${String(prompt).slice(0, 100)} sysLen=${sp ? String(sp).length : 0}`);
|
|
94
|
+
}
|
|
134
95
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
96
|
+
const args = this.buildArgs(prompt, config);
|
|
97
|
+
const spawnOpts = getSpawnOptions(cwd);
|
|
98
|
+
if (Object.keys(this.spawnEnv).length > 0) {
|
|
99
|
+
spawnOpts.env = { ...spawnOpts.env, ...this.spawnEnv };
|
|
100
|
+
for (const [k, v] of Object.entries(this.spawnEnv)) {
|
|
101
|
+
if (v === undefined) delete spawnOpts.env[k];
|
|
139
102
|
}
|
|
103
|
+
}
|
|
104
|
+
if (cwd) spawnOpts.env.CLAUDE_PROJECT_DIR = cwd;
|
|
105
|
+
|
|
106
|
+
const proc = execa(this.command, args, {
|
|
107
|
+
cwd,
|
|
108
|
+
env: spawnOpts.env,
|
|
109
|
+
stdin: this.closeStdin ? 'ignore' : 'pipe',
|
|
110
|
+
stdout: 'pipe',
|
|
111
|
+
stderr: 'pipe',
|
|
112
|
+
reject: false,
|
|
113
|
+
timeout,
|
|
114
|
+
windowsHide: true,
|
|
115
|
+
shell: isWindows,
|
|
116
|
+
});
|
|
140
117
|
|
|
141
|
-
|
|
142
|
-
if (proc.stderr) proc.stderr.on('error', () => {});
|
|
143
|
-
proc.stdout.on('data', (chunk) => {
|
|
144
|
-
if (timedOut) return;
|
|
145
|
-
|
|
146
|
-
jsonBuffer += chunk.toString();
|
|
147
|
-
const lines = jsonBuffer.split('\n');
|
|
148
|
-
jsonBuffer = lines.pop();
|
|
149
|
-
|
|
150
|
-
for (const line of lines) {
|
|
151
|
-
if (line.trim()) {
|
|
152
|
-
const parsed = this.parseOutput(line);
|
|
153
|
-
if (!parsed) continue;
|
|
118
|
+
console.log(`[${this.id}] Spawned PID ${proc.pid} closeStdin=${this.closeStdin}`);
|
|
154
119
|
|
|
155
|
-
|
|
120
|
+
if (config.onPid) { try { config.onPid(proc.pid); } catch (e) {} }
|
|
121
|
+
if (config.onProcess) { try { config.onProcess(proc); } catch (e) {} }
|
|
156
122
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
123
|
+
if (this.supportsStdin && this.stdinPrompt && proc.stdin) {
|
|
124
|
+
proc.stdin.write(typeof prompt === 'string' ? prompt : String(prompt));
|
|
125
|
+
}
|
|
160
126
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
127
|
+
const outputs = [];
|
|
128
|
+
let sessionId = null;
|
|
129
|
+
let rateLimited = false;
|
|
130
|
+
let retryAfterSec = 60;
|
|
131
|
+
let authError = false;
|
|
132
|
+
let authErrorMessage = '';
|
|
133
|
+
let stderrBuffer = '';
|
|
134
|
+
|
|
135
|
+
proc.stderr.on('data', (chunk) => {
|
|
136
|
+
const errorText = chunk.toString();
|
|
137
|
+
stderrBuffer += errorText;
|
|
138
|
+
console.error(`[${this.id}] stderr:`, errorText);
|
|
139
|
+
const authMatch = errorText.match(/401|unauthorized|invalid.*auth|invalid.*token|auth.*failed|permission denied|access denied/i);
|
|
140
|
+
if (authMatch) { authError = true; authErrorMessage = errorText.trim(); }
|
|
141
|
+
const rateLimitMatch = errorText.match(/rate.?limit|429|too many requests|overloaded|throttl|hit your limit/i);
|
|
142
|
+
if (rateLimitMatch) {
|
|
143
|
+
rateLimited = true;
|
|
144
|
+
const retryMatch = errorText.match(/retry.?after[:\s]+(\d+)/i);
|
|
145
|
+
if (retryMatch) {
|
|
146
|
+
retryAfterSec = parseInt(retryMatch[1], 10) || 60;
|
|
147
|
+
} else {
|
|
148
|
+
const resetTimeMatch = errorText.match(/resets?\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*\(?(UTC|[A-Z]{2,4})\)?/i);
|
|
149
|
+
if (resetTimeMatch) {
|
|
150
|
+
let hours = parseInt(resetTimeMatch[1], 10);
|
|
151
|
+
const minutes = resetTimeMatch[2] ? parseInt(resetTimeMatch[2], 10) : 0;
|
|
152
|
+
const period = resetTimeMatch[3]?.toLowerCase();
|
|
153
|
+
if (period === 'pm' && hours !== 12) hours += 12;
|
|
154
|
+
if (period === 'am' && hours === 12) hours = 0;
|
|
155
|
+
const now = new Date();
|
|
156
|
+
const resetTime = new Date(now);
|
|
157
|
+
resetTime.setUTCHours(hours, minutes, 0, 0);
|
|
158
|
+
if (resetTime <= now) resetTime.setUTCDate(resetTime.getUTCDate() + 1);
|
|
159
|
+
retryAfterSec = Math.max(60, Math.ceil((resetTime.getTime() - now.getTime()) / 1000));
|
|
166
160
|
}
|
|
167
161
|
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const errorText = chunk.toString();
|
|
172
|
-
stderrBuffer += errorText;
|
|
173
|
-
console.error(`[${this.id}] stderr:`, errorText);
|
|
174
|
-
|
|
175
|
-
const authMatch = errorText.match(/401|unauthorized|invalid.*auth|invalid.*token|auth.*failed|permission denied|access denied/i);
|
|
176
|
-
if (authMatch) {
|
|
177
|
-
authError = true;
|
|
178
|
-
authErrorMessage = errorText.trim();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const rateLimitMatch = errorText.match(/rate.?limit|429|too many requests|overloaded|throttl|hit your limit/i);
|
|
182
|
-
if (rateLimitMatch) {
|
|
183
|
-
rateLimited = true;
|
|
184
|
-
const retryMatch = errorText.match(/retry.?after[:\s]+(\d+)/i);
|
|
185
|
-
if (retryMatch) {
|
|
186
|
-
retryAfterSec = parseInt(retryMatch[1], 10) || 60;
|
|
187
|
-
} else {
|
|
188
|
-
const resetTimeMatch = errorText.match(/resets?\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*\(?(UTC|[A-Z]{2,4})\)?/i);
|
|
189
|
-
if (resetTimeMatch) {
|
|
190
|
-
let hours = parseInt(resetTimeMatch[1], 10);
|
|
191
|
-
const minutes = resetTimeMatch[2] ? parseInt(resetTimeMatch[2], 10) : 0;
|
|
192
|
-
const period = resetTimeMatch[3]?.toLowerCase();
|
|
193
|
-
const tz = resetTimeMatch[4]?.toUpperCase() || 'UTC';
|
|
194
|
-
|
|
195
|
-
if (period === 'pm' && hours !== 12) hours += 12;
|
|
196
|
-
if (period === 'am' && hours === 12) hours = 0;
|
|
197
|
-
|
|
198
|
-
const now = new Date();
|
|
199
|
-
const resetTime = new Date(now);
|
|
200
|
-
resetTime.setUTCHours(hours, minutes, 0, 0);
|
|
201
|
-
|
|
202
|
-
if (resetTime <= now) {
|
|
203
|
-
resetTime.setUTCDate(resetTime.getUTCDate() + 1);
|
|
204
|
-
}
|
|
162
|
+
}
|
|
163
|
+
if (onError) { try { onError(errorText); } catch (e) {} }
|
|
164
|
+
});
|
|
205
165
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
166
|
+
let jsonBuffer = '';
|
|
167
|
+
proc.stdout.on('data', (chunk) => {
|
|
168
|
+
jsonBuffer += chunk.toString();
|
|
169
|
+
const lines = jsonBuffer.split('\n');
|
|
170
|
+
jsonBuffer = lines.pop();
|
|
171
|
+
for (const line of lines) {
|
|
172
|
+
if (!line.trim()) continue;
|
|
173
|
+
const parsed = this.parseOutput(line);
|
|
174
|
+
if (!parsed) continue;
|
|
175
|
+
outputs.push(parsed);
|
|
176
|
+
if (parsed.session_id) sessionId = parsed.session_id;
|
|
177
|
+
if (onEvent) { try { onEvent(parsed); } catch (e) { console.error(`[${this.id}] onEvent error: ${e.message}`); } }
|
|
178
|
+
}
|
|
179
|
+
});
|
|
210
180
|
|
|
211
|
-
|
|
212
|
-
try { onError(errorText); } catch (e) {}
|
|
213
|
-
}
|
|
214
|
-
});
|
|
181
|
+
const result = await proc;
|
|
215
182
|
|
|
216
|
-
|
|
217
|
-
clearTimeout(timeoutHandle);
|
|
218
|
-
// Close stdin when process exits - it was kept open for steering during execution
|
|
219
|
-
if (proc.stdin && !proc.stdin.destroyed) {
|
|
220
|
-
try { proc.stdin.end(); } catch (e) {}
|
|
221
|
-
}
|
|
222
|
-
if (timedOut) return;
|
|
183
|
+
if (proc.stdin && !proc.stdin.destroyed) { try { proc.stdin.end(); } catch (e) {} }
|
|
223
184
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
185
|
+
if (jsonBuffer.trim()) {
|
|
186
|
+
const parsed = this.parseOutput(jsonBuffer);
|
|
187
|
+
if (parsed) {
|
|
188
|
+
outputs.push(parsed);
|
|
189
|
+
if (parsed.session_id) sessionId = parsed.session_id;
|
|
190
|
+
if (onEvent) { try { onEvent(parsed); } catch (e) {} }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
231
193
|
|
|
232
|
-
|
|
233
|
-
const err = new Error(`Rate limited - retry after ${retryAfterSec}s`);
|
|
234
|
-
err.rateLimited = true;
|
|
235
|
-
err.retryAfterSec = retryAfterSec;
|
|
236
|
-
if (onRateLimit) {
|
|
237
|
-
try { onRateLimit({ retryAfterSec }); } catch (e) {}
|
|
238
|
-
}
|
|
239
|
-
reject(err);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
194
|
+
if (result.timedOut) throw new Error(`${this.name} timeout after ${timeout}ms`);
|
|
242
195
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (parsed.session_id) sessionId = parsed.session_id;
|
|
248
|
-
if (onEvent) {
|
|
249
|
-
try { onEvent(parsed); } catch (e) {}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
196
|
+
if (authError) {
|
|
197
|
+
const err = new Error(`Authentication failed: ${authErrorMessage || 'Invalid credentials or unauthorized access'}`);
|
|
198
|
+
err.authError = true; err.nonRetryable = true; throw err;
|
|
199
|
+
}
|
|
253
200
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
});
|
|
201
|
+
if (rateLimited) {
|
|
202
|
+
const err = new Error(`Rate limited - retry after ${retryAfterSec}s`);
|
|
203
|
+
err.rateLimited = true; err.retryAfterSec = retryAfterSec;
|
|
204
|
+
if (onRateLimit) { try { onRateLimit({ retryAfterSec }); } catch (e) {} }
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
262
207
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
});
|
|
208
|
+
const code = result.exitCode;
|
|
209
|
+
if (code === 0 || outputs.length > 0) return { outputs, sessionId };
|
|
210
|
+
const stderrHint = stderrBuffer.trim() ? `: ${stderrBuffer.trim().slice(0, 200)}` : '';
|
|
211
|
+
const codeHint = code === 143 ? ' (SIGTERM - process was killed)' : code === 137 ? ' (SIGKILL - out of memory or force-killed)' : '';
|
|
212
|
+
throw new Error(`${this.name} exited with code ${code}${codeHint}${stderrHint}`);
|
|
268
213
|
}
|
|
269
214
|
|
|
270
215
|
async runACP(prompt, cwd, config = {}, _retryCount = 0) {
|
package/lib/ws-handlers-conv.js
CHANGED
|
@@ -1,10 +1,43 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import os from 'os';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
|
|
4
5
|
function fail(code, message) { const e = new Error(message); e.code = code; throw e; }
|
|
5
6
|
function notFound(msg = 'Not found') { fail(404, msg); }
|
|
6
7
|
function expandTilde(p) { return p && p.startsWith('~') ? path.join(os.homedir(), p.slice(1)) : p; }
|
|
7
8
|
|
|
9
|
+
function validate(schema, params) {
|
|
10
|
+
const result = schema.safeParse(params);
|
|
11
|
+
if (!result.success) fail(400, result.error.issues.map(i => i.message).join('; '));
|
|
12
|
+
return result.data;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ConvNewSchema = z.object({
|
|
16
|
+
agentId: z.string().optional(),
|
|
17
|
+
title: z.string().optional(),
|
|
18
|
+
workingDirectory: z.string().optional(),
|
|
19
|
+
model: z.string().optional(),
|
|
20
|
+
subAgent: z.string().optional(),
|
|
21
|
+
}).passthrough();
|
|
22
|
+
|
|
23
|
+
const ConvUpdSchema = z.object({
|
|
24
|
+
id: z.string().min(1, 'id required'),
|
|
25
|
+
}).passthrough();
|
|
26
|
+
|
|
27
|
+
const MsgStreamSchema = z.object({
|
|
28
|
+
id: z.string().min(1, 'conversation id required'),
|
|
29
|
+
content: z.union([z.string(), z.any()]).optional(),
|
|
30
|
+
message: z.union([z.string(), z.any()]).optional(),
|
|
31
|
+
agentId: z.string().optional(),
|
|
32
|
+
model: z.string().optional(),
|
|
33
|
+
subAgent: z.string().optional(),
|
|
34
|
+
}).passthrough();
|
|
35
|
+
|
|
36
|
+
const ConvSteerSchema = z.object({
|
|
37
|
+
id: z.string().min(1, 'conversation id required'),
|
|
38
|
+
content: z.union([z.string(), z.record(z.any())]).refine(v => v !== undefined && v !== null && v !== '', { message: 'content required' }),
|
|
39
|
+
}).passthrough();
|
|
40
|
+
|
|
8
41
|
export function register(router, deps) {
|
|
9
42
|
const { queries, activeExecutions, messageQueues, rateLimitState,
|
|
10
43
|
broadcastSync, processMessageWithStreaming, cleanupExecution, logError = () => {} } = deps;
|
|
@@ -26,6 +59,7 @@ export function register(router, deps) {
|
|
|
26
59
|
});
|
|
27
60
|
|
|
28
61
|
router.handle('conv.new', (p) => {
|
|
62
|
+
p = validate(ConvNewSchema, p);
|
|
29
63
|
const wd = p.workingDirectory ? path.resolve(expandTilde(p.workingDirectory)) : null;
|
|
30
64
|
const conv = queries.createConversation(p.agentId, p.title, wd, p.model || null, p.subAgent || null);
|
|
31
65
|
queries.createEvent('conversation.created', { agentId: p.agentId, workingDirectory: conv.workingDirectory, model: conv.model, subAgent: conv.subAgent }, conv.id);
|
|
@@ -40,6 +74,7 @@ export function register(router, deps) {
|
|
|
40
74
|
});
|
|
41
75
|
|
|
42
76
|
router.handle('conv.upd', (p) => {
|
|
77
|
+
p = validate(ConvUpdSchema, p);
|
|
43
78
|
const { id, ...data } = p;
|
|
44
79
|
if (data.workingDirectory) data.workingDirectory = path.resolve(data.workingDirectory);
|
|
45
80
|
const conv = queries.updateConversation(id, data);
|
|
@@ -130,9 +165,9 @@ export function register(router, deps) {
|
|
|
130
165
|
});
|
|
131
166
|
|
|
132
167
|
router.handle('conv.steer', (p) => {
|
|
168
|
+
p = validate(ConvSteerSchema, p);
|
|
133
169
|
const conv = queries.getConversation(p.id);
|
|
134
170
|
if (!conv) notFound('Conversation not found');
|
|
135
|
-
if (!p.content) fail(400, 'Missing content');
|
|
136
171
|
|
|
137
172
|
const entry = activeExecutions.get(p.id);
|
|
138
173
|
if (!entry) fail(409, 'No active execution to steer');
|
|
@@ -226,6 +261,7 @@ export function register(router, deps) {
|
|
|
226
261
|
});
|
|
227
262
|
|
|
228
263
|
router.handle('msg.stream', (p) => {
|
|
264
|
+
p = validate(MsgStreamSchema, p);
|
|
229
265
|
const conv = queries.getConversation(p.id);
|
|
230
266
|
if (!conv) notFound('Conversation not found');
|
|
231
267
|
const rawContent = p.content || p.message;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentgui",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.695",
|
|
4
4
|
"description": "Multi-agent ACP client with real-time communication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -29,17 +29,21 @@
|
|
|
29
29
|
"audio-decode": "^2.2.3",
|
|
30
30
|
"better-sqlite3": "^12.6.2",
|
|
31
31
|
"busboy": "^1.6.0",
|
|
32
|
+
"execa": "^9.6.1",
|
|
32
33
|
"express": "^5.2.1",
|
|
33
34
|
"form-data": "^4.0.5",
|
|
34
35
|
"fsbrowse": "latest",
|
|
35
36
|
"google-auth-library": "^10.5.0",
|
|
37
|
+
"lru-cache": "^11.2.7",
|
|
36
38
|
"msgpackr": "^1.11.8",
|
|
37
39
|
"onnxruntime-node": "1.21.0",
|
|
38
40
|
"opencode-ai": "^1.2.15",
|
|
41
|
+
"p-retry": "^7.1.1",
|
|
39
42
|
"pm2": "^5.4.3",
|
|
40
43
|
"puppeteer-core": "^24.37.5",
|
|
41
44
|
"webtalk": "^1.0.31",
|
|
42
|
-
"ws": "^8.14.2"
|
|
45
|
+
"ws": "^8.14.2",
|
|
46
|
+
"zod": "^4.3.6"
|
|
43
47
|
},
|
|
44
48
|
"optionalDependencies": {
|
|
45
49
|
"node-pty": "^1.0.0"
|
package/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import crypto from 'crypto';
|
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { WebSocketServer } from 'ws';
|
|
9
9
|
import { execSync, spawn } from 'child_process';
|
|
10
|
+
import { LRUCache } from 'lru-cache';
|
|
10
11
|
import { createRequire } from 'module';
|
|
11
12
|
const PKG_VERSION = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf8')).version;
|
|
12
13
|
import { OAuth2Client } from 'google-auth-library';
|
|
@@ -3277,8 +3278,8 @@ function generateETag(stats) {
|
|
|
3277
3278
|
return `"${stats.mtimeMs.toString(36)}-${stats.size.toString(36)}"`;
|
|
3278
3279
|
}
|
|
3279
3280
|
|
|
3280
|
-
// In-memory cache: etag -> {
|
|
3281
|
-
const _assetCache = new
|
|
3281
|
+
// In-memory cache: etag -> { gz: Buffer, raw: Buffer } (bounded to 200 entries)
|
|
3282
|
+
const _assetCache = new LRUCache({ max: 200 });
|
|
3282
3283
|
// Cached processed HTML (invalidated on hot-reload or server restart)
|
|
3283
3284
|
let _htmlCache = null;
|
|
3284
3285
|
let _htmlCacheEtag = null;
|