@gonzih/cc-discord 0.2.35 → 0.2.37
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/dist/meta-agent-manager.d.ts +15 -24
- package/dist/meta-agent-manager.js +108 -190
- package/package.json +1 -1
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
* Flow per namespace:
|
|
5
5
|
* 1. ensureWorkspace: git clone repo to ~/cc-discord-workspace/{ns}
|
|
6
6
|
* 2. injectMcp: write .mcp.json so the claude subprocess has MCP tool access
|
|
7
|
-
* 3. ensureSession:
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 5. On process exit: remove from sessions map; next message respawns
|
|
7
|
+
* 3. ensureSession: add message to per-namespace queue, start processQueue
|
|
8
|
+
* 4. processQueue: spawn `claude --continue -p` (stdin+EOF) per message, one at a time
|
|
9
|
+
* 5. Context is maintained via --continue (JSONL session file in workspace)
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Spawn-per-message with --continue maintains full conversation history.
|
|
12
|
+
* Messages queue up and process sequentially per namespace.
|
|
14
13
|
*/
|
|
15
14
|
import type { createCcWire } from "@gonzih/cc-wire";
|
|
16
15
|
type Wire = ReturnType<typeof createCcWire>;
|
|
@@ -44,12 +43,9 @@ export declare function injectMcp(ns: string, wsPath: string, token: string): vo
|
|
|
44
43
|
export declare const metaStreamChannel: (ns: string) => string;
|
|
45
44
|
export declare const metaLogKey: (ns: string) => string;
|
|
46
45
|
/**
|
|
47
|
-
* Spawn `claude --continue -p
|
|
48
|
-
*
|
|
49
|
-
* Also streams each chunk to Redis: PUBLISH cca:meta:{ns}:stream and LPUSH cca:meta:{ns}:log.
|
|
46
|
+
* Spawn `claude --continue -p` with message written to stdin, then close stdin (EOF).
|
|
47
|
+
* Claude responds and exits. Context is maintained via --continue (JSONL session file).
|
|
50
48
|
* Returns a Promise that resolves when the process exits.
|
|
51
|
-
*
|
|
52
|
-
* Kept for backwards-compat and direct integration-test usage.
|
|
53
49
|
*/
|
|
54
50
|
export declare function spawnSession(ns: string, message: string, token: string, wire: Wire): Promise<void>;
|
|
55
51
|
export interface MetaAgentManager {
|
|
@@ -60,25 +56,20 @@ export interface MetaAgentManager {
|
|
|
60
56
|
repoUrl: string;
|
|
61
57
|
}>, instanceId?: string) => void;
|
|
62
58
|
stop: () => void;
|
|
63
|
-
/**
|
|
59
|
+
/** Clear the message queue for a namespace (e.g. on /clear). */
|
|
64
60
|
killSession: (ns: string) => void;
|
|
65
|
-
/**
|
|
61
|
+
/** Queue a message for a namespace. */
|
|
66
62
|
sendToSession: (ns: string, line: string) => void;
|
|
67
63
|
}
|
|
68
64
|
/**
|
|
69
|
-
* Create a MetaAgentManager that
|
|
70
|
-
*
|
|
71
|
-
* On first message for a namespace:
|
|
72
|
-
* 1. ensureWorkspace + injectMcp
|
|
73
|
-
* 2. Drain any pending Redis queue entries to stdin
|
|
74
|
-
* 3. Write the new message to stdin
|
|
75
|
-
*
|
|
76
|
-
* On subsequent messages: write directly to stdin of the running process.
|
|
65
|
+
* Create a MetaAgentManager that processes Discord messages via spawn-per-message.
|
|
77
66
|
*
|
|
78
|
-
*
|
|
67
|
+
* Each message spawns `claude --continue -p` with the message written to stdin,
|
|
68
|
+
* then stdin is closed (EOF) so Claude processes and exits. --continue maintains
|
|
69
|
+
* conversation history via the JSONL session file in the workspace.
|
|
79
70
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
71
|
+
* Messages queue per namespace and process sequentially — no concurrent spawns
|
|
72
|
+
* for the same namespace.
|
|
82
73
|
*/
|
|
83
74
|
export declare function createMetaAgentManager(): MetaAgentManager;
|
|
84
75
|
/**
|
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
* Flow per namespace:
|
|
5
5
|
* 1. ensureWorkspace: git clone repo to ~/cc-discord-workspace/{ns}
|
|
6
6
|
* 2. injectMcp: write .mcp.json so the claude subprocess has MCP tool access
|
|
7
|
-
* 3. ensureSession:
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 5. On process exit: remove from sessions map; next message respawns
|
|
7
|
+
* 3. ensureSession: add message to per-namespace queue, start processQueue
|
|
8
|
+
* 4. processQueue: spawn `claude --continue -p` (stdin+EOF) per message, one at a time
|
|
9
|
+
* 5. Context is maintained via --continue (JSONL session file in workspace)
|
|
11
10
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Spawn-per-message with --continue maintains full conversation history.
|
|
12
|
+
* Messages queue up and process sequentially per namespace.
|
|
14
13
|
*/
|
|
15
14
|
import { spawn, execSync } from "child_process";
|
|
16
15
|
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
@@ -212,19 +211,7 @@ function wireStdoutToRedis(proc, ns, wire) {
|
|
|
212
211
|
result: resultText,
|
|
213
212
|
is_error: parsed.is_error ?? false,
|
|
214
213
|
};
|
|
215
|
-
|
|
216
|
-
const msg = {
|
|
217
|
-
id: crypto.randomUUID(),
|
|
218
|
-
source: "claude",
|
|
219
|
-
role: "assistant",
|
|
220
|
-
content: resultText,
|
|
221
|
-
timestamp: new Date().toISOString(),
|
|
222
|
-
chatId: 0,
|
|
223
|
-
};
|
|
224
|
-
wire.discord.publishOutgoing(ns, msg).catch((err) => {
|
|
225
|
-
console.warn(`[meta-agent-manager] publishOutgoing (result) failed (ns=${ns}):`, err.message);
|
|
226
|
-
});
|
|
227
|
-
}
|
|
214
|
+
// Do not re-publish result text — assistant event already published it
|
|
228
215
|
}
|
|
229
216
|
else {
|
|
230
217
|
structuredEvent = parsed;
|
|
@@ -257,12 +244,9 @@ function wireStdoutToRedis(proc, ns, wire) {
|
|
|
257
244
|
});
|
|
258
245
|
}
|
|
259
246
|
/**
|
|
260
|
-
* Spawn `claude --continue -p
|
|
261
|
-
*
|
|
262
|
-
* Also streams each chunk to Redis: PUBLISH cca:meta:{ns}:stream and LPUSH cca:meta:{ns}:log.
|
|
247
|
+
* Spawn `claude --continue -p` with message written to stdin, then close stdin (EOF).
|
|
248
|
+
* Claude responds and exits. Context is maintained via --continue (JSONL session file).
|
|
263
249
|
* Returns a Promise that resolves when the process exits.
|
|
264
|
-
*
|
|
265
|
-
* Kept for backwards-compat and direct integration-test usage.
|
|
266
250
|
*/
|
|
267
251
|
export function spawnSession(ns, message, token, wire) {
|
|
268
252
|
return new Promise((resolve, reject) => {
|
|
@@ -271,11 +255,15 @@ export function spawnSession(ns, message, token, wire) {
|
|
|
271
255
|
const env = buildEnv(token);
|
|
272
256
|
const proc = spawn(claudeBin, [
|
|
273
257
|
"--continue",
|
|
274
|
-
"-p",
|
|
258
|
+
"-p",
|
|
275
259
|
"--output-format", "stream-json",
|
|
276
260
|
"--verbose",
|
|
277
261
|
"--dangerously-skip-permissions",
|
|
278
|
-
], { cwd: wsPath, env, stdio: ["
|
|
262
|
+
], { cwd: wsPath, env, stdio: ["pipe", "pipe", "pipe"] });
|
|
263
|
+
// Write message to stdin, then close (EOF triggers Claude to process and respond)
|
|
264
|
+
proc.stdin.write(message + "\n", () => {
|
|
265
|
+
proc.stdin.end();
|
|
266
|
+
});
|
|
279
267
|
wireStdoutToRedis(proc, ns, wire);
|
|
280
268
|
proc.on("exit", (code) => {
|
|
281
269
|
console.log(`[meta-agent-manager] session exited (ns=${ns}, code=${code})`);
|
|
@@ -288,149 +276,75 @@ export function spawnSession(ns, message, token, wire) {
|
|
|
288
276
|
});
|
|
289
277
|
}
|
|
290
278
|
/**
|
|
291
|
-
*
|
|
292
|
-
* No -p flag — messages are delivered via stdin.
|
|
293
|
-
* Stdout is wired to Redis via wireStdoutToRedis.
|
|
294
|
-
* Returns the ChildProcess.
|
|
295
|
-
*/
|
|
296
|
-
function spawnPersistentSession(ns, token, wire, onExit) {
|
|
297
|
-
const wsPath = workspacePath(ns);
|
|
298
|
-
const claudeBin = resolveClaude();
|
|
299
|
-
const env = buildEnv(token);
|
|
300
|
-
console.log(`[meta-agent-manager] spawning persistent session (ns=${ns})`);
|
|
301
|
-
// DO NOT ADD -p OR --input-format stream-json HERE.
|
|
302
|
-
// With -p + --input-format stream-json, Claude buffers all stdin until EOF before responding.
|
|
303
|
-
// Since the stdin pipe is never closed (it stays open for subsequent messages), Claude
|
|
304
|
-
// waits forever and never produces output. Interactive mode (no -p) reads one line at a
|
|
305
|
-
// time and responds immediately. Verified by direct test: `echo msg | claude --continue
|
|
306
|
-
// --output-format stream-json ...` responds correctly; the -p variant does not.
|
|
307
|
-
const proc = spawn(claudeBin, [
|
|
308
|
-
"--continue",
|
|
309
|
-
"--output-format", "stream-json",
|
|
310
|
-
"--verbose",
|
|
311
|
-
"--dangerously-skip-permissions",
|
|
312
|
-
], { cwd: wsPath, env, stdio: ["pipe", "pipe", "pipe"] });
|
|
313
|
-
proc.stdin.setDefaultEncoding("utf8");
|
|
314
|
-
wireStdoutToRedis(proc, ns, wire);
|
|
315
|
-
proc.on("exit", (code) => {
|
|
316
|
-
console.log(`[meta-agent-manager] persistent session exited (ns=${ns}, code=${code})`);
|
|
317
|
-
onExit();
|
|
318
|
-
});
|
|
319
|
-
proc.on("error", (err) => {
|
|
320
|
-
console.error(`[meta-agent-manager] persistent session spawn error (ns=${ns}):`, err.message);
|
|
321
|
-
onExit();
|
|
322
|
-
});
|
|
323
|
-
return proc;
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Create a MetaAgentManager that maintains one persistent Claude process per namespace.
|
|
327
|
-
*
|
|
328
|
-
* On first message for a namespace:
|
|
329
|
-
* 1. ensureWorkspace + injectMcp
|
|
330
|
-
* 2. Drain any pending Redis queue entries to stdin
|
|
331
|
-
* 3. Write the new message to stdin
|
|
332
|
-
*
|
|
333
|
-
* On subsequent messages: write directly to stdin of the running process.
|
|
279
|
+
* Create a MetaAgentManager that processes Discord messages via spawn-per-message.
|
|
334
280
|
*
|
|
335
|
-
*
|
|
281
|
+
* Each message spawns `claude --continue -p` with the message written to stdin,
|
|
282
|
+
* then stdin is closed (EOF) so Claude processes and exits. --continue maintains
|
|
283
|
+
* conversation history via the JSONL session file in the workspace.
|
|
336
284
|
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
285
|
+
* Messages queue per namespace and process sequentially — no concurrent spawns
|
|
286
|
+
* for the same namespace.
|
|
339
287
|
*/
|
|
340
288
|
export function createMetaAgentManager() {
|
|
341
289
|
let pollInterval = null;
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
const
|
|
290
|
+
// Per-namespace message queue (local, drained by processQueue)
|
|
291
|
+
const queues = new Map();
|
|
292
|
+
// Namespaces currently running a spawnSession (blocks concurrent spawns)
|
|
293
|
+
const processing = new Set();
|
|
294
|
+
// Cached session metadata (token, wire, repoUrl) per namespace
|
|
295
|
+
const sessionMeta = new Map();
|
|
346
296
|
/**
|
|
347
|
-
*
|
|
348
|
-
*
|
|
297
|
+
* Drain the local queue for a namespace, spawning one session per message.
|
|
298
|
+
* No-ops if already processing or queue is empty.
|
|
349
299
|
*/
|
|
350
|
-
function
|
|
351
|
-
|
|
352
|
-
|
|
300
|
+
async function processQueue(ns) {
|
|
301
|
+
if (processing.has(ns))
|
|
302
|
+
return;
|
|
303
|
+
const queue = queues.get(ns);
|
|
304
|
+
if (!queue || queue.length === 0)
|
|
305
|
+
return;
|
|
306
|
+
const meta = sessionMeta.get(ns);
|
|
307
|
+
if (!meta)
|
|
353
308
|
return;
|
|
309
|
+
processing.add(ns);
|
|
310
|
+
console.log(`[meta-agent-manager] processQueue started (ns=${ns})`);
|
|
354
311
|
try {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
let raw;
|
|
369
|
-
try {
|
|
370
|
-
raw = await wire._redis.lpop(inputKey);
|
|
371
|
-
}
|
|
372
|
-
catch {
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
375
|
-
if (!raw)
|
|
376
|
-
break;
|
|
377
|
-
let content;
|
|
378
|
-
try {
|
|
379
|
-
content = JSON.parse(raw).content ?? raw;
|
|
380
|
-
}
|
|
381
|
-
catch {
|
|
382
|
-
content = raw;
|
|
312
|
+
while (true) {
|
|
313
|
+
const q = queues.get(ns);
|
|
314
|
+
if (!q || q.length === 0)
|
|
315
|
+
break;
|
|
316
|
+
const message = q.shift();
|
|
317
|
+
const { token, wire } = sessionMeta.get(ns) ?? meta;
|
|
318
|
+
console.log(`[meta-agent-manager] spawning session for message (ns=${ns})`);
|
|
319
|
+
try {
|
|
320
|
+
await spawnSession(ns, message, token, wire);
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
console.error(`[meta-agent-manager] spawnSession error (ns=${ns}):`, err.message);
|
|
324
|
+
}
|
|
383
325
|
}
|
|
384
|
-
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
processing.delete(ns);
|
|
329
|
+
console.log(`[meta-agent-manager] processQueue done (ns=${ns})`);
|
|
385
330
|
}
|
|
386
331
|
}
|
|
387
332
|
/**
|
|
388
|
-
* Ensure
|
|
389
|
-
* If one already exists, returns immediately.
|
|
390
|
-
* If not, spawns one, drains any queued messages, then writes `firstMessage` if provided.
|
|
333
|
+
* Ensure workspace + MCP are ready, add message to queue, start processing.
|
|
391
334
|
*/
|
|
392
335
|
async function ensureSession(ns, repoUrl, token, wire, firstMessage) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
startingUp.add(ns);
|
|
403
|
-
try {
|
|
404
|
-
// Ensure workspace exists
|
|
405
|
-
const wsPath = workspacePath(ns);
|
|
406
|
-
await ensureWorkspace(ns, repoUrl);
|
|
407
|
-
injectMcp(ns, wsPath, token);
|
|
408
|
-
const proc = spawnPersistentSession(ns, token, wire, () => {
|
|
409
|
-
// On exit: remove from map so next message triggers a respawn
|
|
410
|
-
sessions.delete(ns);
|
|
411
|
-
console.log(`[meta-agent-manager] session removed from map (ns=${ns})`);
|
|
412
|
-
});
|
|
413
|
-
sessions.set(ns, { proc, ns });
|
|
414
|
-
// Drain any messages that arrived before this session started
|
|
415
|
-
await drainQueue(ns, wire);
|
|
416
|
-
// Write the triggering message last (after queue drain, in order)
|
|
417
|
-
if (firstMessage !== undefined)
|
|
418
|
-
writeToStdin(ns, firstMessage);
|
|
419
|
-
await wire.discord.setStatus(ns, {
|
|
420
|
-
namespace: ns,
|
|
421
|
-
status: "running",
|
|
422
|
-
isTyping: true,
|
|
423
|
-
turnCount: 0,
|
|
424
|
-
updatedAt: new Date().toISOString(),
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
catch (err) {
|
|
428
|
-
console.error(`[meta-agent-manager] ensureSession failed (ns=${ns}):`, err.message);
|
|
429
|
-
sessions.delete(ns);
|
|
430
|
-
}
|
|
431
|
-
finally {
|
|
432
|
-
startingUp.delete(ns);
|
|
336
|
+
sessionMeta.set(ns, { token, wire, repoUrl });
|
|
337
|
+
const wsPath = workspacePath(ns);
|
|
338
|
+
await ensureWorkspace(ns, repoUrl);
|
|
339
|
+
injectMcp(ns, wsPath, token);
|
|
340
|
+
if (!queues.has(ns))
|
|
341
|
+
queues.set(ns, []);
|
|
342
|
+
if (firstMessage !== undefined) {
|
|
343
|
+
queues.get(ns).push(firstMessage);
|
|
433
344
|
}
|
|
345
|
+
processQueue(ns).catch((err) => {
|
|
346
|
+
console.error(`[meta-agent-manager] processQueue error (ns=${ns}):`, err.message);
|
|
347
|
+
});
|
|
434
348
|
}
|
|
435
349
|
return {
|
|
436
350
|
async ensureWorkspace(ns, repoUrl) {
|
|
@@ -442,7 +356,7 @@ export function createMetaAgentManager() {
|
|
|
442
356
|
},
|
|
443
357
|
startPolling(wire, getNamespaces, instanceId) {
|
|
444
358
|
if (pollInterval)
|
|
445
|
-
return;
|
|
359
|
+
return;
|
|
446
360
|
pollInterval = setInterval(() => {
|
|
447
361
|
const namespaces = getNamespaces();
|
|
448
362
|
if (namespaces.length === 0)
|
|
@@ -457,25 +371,21 @@ export function createMetaAgentManager() {
|
|
|
457
371
|
}
|
|
458
372
|
}).catch(() => { });
|
|
459
373
|
}
|
|
460
|
-
//
|
|
461
|
-
if (
|
|
462
|
-
drainQueue(ns, wire).catch((err) => {
|
|
463
|
-
console.warn(`[meta-agent-manager] drainQueue error (ns=${ns}):`, err.message);
|
|
464
|
-
});
|
|
374
|
+
// Skip if already processing
|
|
375
|
+
if (processing.has(ns))
|
|
465
376
|
continue;
|
|
466
|
-
|
|
467
|
-
// Check if anything is queued for this namespace
|
|
377
|
+
// Check Redis queue for pending messages
|
|
468
378
|
const inputKey = discordMetaInputKey(ns);
|
|
469
379
|
wire._redis.llen(inputKey).then(async (queueLen) => {
|
|
470
380
|
if (queueLen === 0)
|
|
471
381
|
return;
|
|
472
|
-
// Resolve token
|
|
473
382
|
let token;
|
|
474
383
|
try {
|
|
475
384
|
token = await wire.token.getMaster();
|
|
476
385
|
}
|
|
477
386
|
catch {
|
|
478
|
-
token =
|
|
387
|
+
token = sessionMeta.get(ns)?.token
|
|
388
|
+
?? process.env.CLAUDE_CODE_OAUTH_TOKEN
|
|
479
389
|
?? process.env.CLAUDE_CODE_TOKEN
|
|
480
390
|
?? process.env.ANTHROPIC_API_KEY
|
|
481
391
|
?? "";
|
|
@@ -484,8 +394,31 @@ export function createMetaAgentManager() {
|
|
|
484
394
|
console.warn(`[meta-agent-manager] no token available, skipping session for ns=${ns}`);
|
|
485
395
|
return;
|
|
486
396
|
}
|
|
487
|
-
//
|
|
488
|
-
|
|
397
|
+
// Drain Redis queue into local queue
|
|
398
|
+
if (!queues.has(ns))
|
|
399
|
+
queues.set(ns, []);
|
|
400
|
+
for (;;) {
|
|
401
|
+
const raw = await wire._redis.lpop(inputKey).catch(() => null);
|
|
402
|
+
if (!raw)
|
|
403
|
+
break;
|
|
404
|
+
let content;
|
|
405
|
+
try {
|
|
406
|
+
content = JSON.parse(raw).content ?? raw;
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
content = raw;
|
|
410
|
+
}
|
|
411
|
+
queues.get(ns).push(content);
|
|
412
|
+
}
|
|
413
|
+
if (!sessionMeta.has(ns)) {
|
|
414
|
+
sessionMeta.set(ns, { token, wire, repoUrl });
|
|
415
|
+
}
|
|
416
|
+
const wsPath = workspacePath(ns);
|
|
417
|
+
await ensureWorkspace(ns, repoUrl);
|
|
418
|
+
injectMcp(ns, wsPath, token);
|
|
419
|
+
processQueue(ns).catch((err) => {
|
|
420
|
+
console.error(`[meta-agent-manager] processQueue error (ns=${ns}):`, err.message);
|
|
421
|
+
});
|
|
489
422
|
}).catch((err) => {
|
|
490
423
|
console.warn(`[meta-agent-manager] llen error (ns=${ns}):`, err.message);
|
|
491
424
|
});
|
|
@@ -499,35 +432,20 @@ export function createMetaAgentManager() {
|
|
|
499
432
|
pollInterval = null;
|
|
500
433
|
console.log("[meta-agent-manager] polling stopped");
|
|
501
434
|
}
|
|
502
|
-
|
|
503
|
-
for (const [ns, session] of sessions) {
|
|
504
|
-
try {
|
|
505
|
-
session.proc.stdin.end();
|
|
506
|
-
session.proc.kill();
|
|
507
|
-
}
|
|
508
|
-
catch {
|
|
509
|
-
// ignore errors during shutdown
|
|
510
|
-
}
|
|
511
|
-
sessions.delete(ns);
|
|
512
|
-
console.log(`[meta-agent-manager] killed session on stop (ns=${ns})`);
|
|
513
|
-
}
|
|
435
|
+
queues.clear();
|
|
514
436
|
},
|
|
515
437
|
killSession(ns) {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
try {
|
|
520
|
-
session.proc.stdin.end();
|
|
521
|
-
session.proc.kill();
|
|
522
|
-
}
|
|
523
|
-
catch {
|
|
524
|
-
// ignore
|
|
525
|
-
}
|
|
526
|
-
sessions.delete(ns);
|
|
527
|
-
console.log(`[meta-agent-manager] killed session (ns=${ns})`);
|
|
438
|
+
// Clear local queue — any in-flight spawn will complete naturally
|
|
439
|
+
queues.delete(ns);
|
|
440
|
+
console.log(`[meta-agent-manager] killed session queue (ns=${ns})`);
|
|
528
441
|
},
|
|
529
442
|
sendToSession(ns, line) {
|
|
530
|
-
|
|
443
|
+
if (!queues.has(ns))
|
|
444
|
+
queues.set(ns, []);
|
|
445
|
+
queues.get(ns).push(line);
|
|
446
|
+
processQueue(ns).catch((err) => {
|
|
447
|
+
console.error(`[meta-agent-manager] processQueue error (ns=${ns}):`, err.message);
|
|
448
|
+
});
|
|
531
449
|
},
|
|
532
450
|
};
|
|
533
451
|
}
|