@gonzih/cc-discord 0.2.37 → 0.2.38
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 +24 -15
- package/dist/meta-agent-manager.js +190 -108
- package/package.json +1 -1
|
@@ -4,12 +4,13 @@
|
|
|
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
|
-
*
|
|
7
|
+
* 3. ensureSession: spawn one persistent `claude --continue` process per namespace
|
|
8
|
+
* (no -p flag — messages go via stdin)
|
|
9
|
+
* 4. On new message: write "${message}\n" to stdin of the running process
|
|
10
|
+
* 5. On process exit: remove from sessions map; next message respawns
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* The polling loop now just drains any queued Redis messages into stdin.
|
|
13
|
+
* No more per-message process spawn — Claude receives messages in sequence.
|
|
13
14
|
*/
|
|
14
15
|
import type { createCcWire } from "@gonzih/cc-wire";
|
|
15
16
|
type Wire = ReturnType<typeof createCcWire>;
|
|
@@ -43,9 +44,12 @@ export declare function injectMcp(ns: string, wsPath: string, token: string): vo
|
|
|
43
44
|
export declare const metaStreamChannel: (ns: string) => string;
|
|
44
45
|
export declare const metaLogKey: (ns: string) => string;
|
|
45
46
|
/**
|
|
46
|
-
* Spawn `claude --continue -p
|
|
47
|
-
*
|
|
47
|
+
* Spawn `claude --continue -p "{message}" --dangerously-skip-permissions` in the
|
|
48
|
+
* namespace workspace. Pipes stdout line-by-line → wire.discord.publishOutgoing.
|
|
49
|
+
* Also streams each chunk to Redis: PUBLISH cca:meta:{ns}:stream and LPUSH cca:meta:{ns}:log.
|
|
48
50
|
* Returns a Promise that resolves when the process exits.
|
|
51
|
+
*
|
|
52
|
+
* Kept for backwards-compat and direct integration-test usage.
|
|
49
53
|
*/
|
|
50
54
|
export declare function spawnSession(ns: string, message: string, token: string, wire: Wire): Promise<void>;
|
|
51
55
|
export interface MetaAgentManager {
|
|
@@ -56,20 +60,25 @@ export interface MetaAgentManager {
|
|
|
56
60
|
repoUrl: string;
|
|
57
61
|
}>, instanceId?: string) => void;
|
|
58
62
|
stop: () => void;
|
|
59
|
-
/**
|
|
63
|
+
/** Kill and remove the persistent session for a namespace (e.g. on /clear). */
|
|
60
64
|
killSession: (ns: string) => void;
|
|
61
|
-
/**
|
|
65
|
+
/** Write a raw line to the stdin of the running session, if any. */
|
|
62
66
|
sendToSession: (ns: string, line: string) => void;
|
|
63
67
|
}
|
|
64
68
|
/**
|
|
65
|
-
* Create a MetaAgentManager that
|
|
69
|
+
* Create a MetaAgentManager that maintains one persistent Claude process per namespace.
|
|
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.
|
|
66
77
|
*
|
|
67
|
-
*
|
|
68
|
-
* then stdin is closed (EOF) so Claude processes and exits. --continue maintains
|
|
69
|
-
* conversation history via the JSONL session file in the workspace.
|
|
78
|
+
* On process exit: remove from sessions map. Next message triggers a respawn.
|
|
70
79
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
80
|
+
* The 3-second poll loop drains any messages that arrived while a session was
|
|
81
|
+
* starting up or temporarily unavailable.
|
|
73
82
|
*/
|
|
74
83
|
export declare function createMetaAgentManager(): MetaAgentManager;
|
|
75
84
|
/**
|
|
@@ -4,12 +4,13 @@
|
|
|
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
|
-
*
|
|
7
|
+
* 3. ensureSession: spawn one persistent `claude --continue` process per namespace
|
|
8
|
+
* (no -p flag — messages go via stdin)
|
|
9
|
+
* 4. On new message: write "${message}\n" to stdin of the running process
|
|
10
|
+
* 5. On process exit: remove from sessions map; next message respawns
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* The polling loop now just drains any queued Redis messages into stdin.
|
|
13
|
+
* No more per-message process spawn — Claude receives messages in sequence.
|
|
13
14
|
*/
|
|
14
15
|
import { spawn, execSync } from "child_process";
|
|
15
16
|
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
@@ -211,7 +212,19 @@ function wireStdoutToRedis(proc, ns, wire) {
|
|
|
211
212
|
result: resultText,
|
|
212
213
|
is_error: parsed.is_error ?? false,
|
|
213
214
|
};
|
|
214
|
-
|
|
215
|
+
if (resultText) {
|
|
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
|
+
}
|
|
215
228
|
}
|
|
216
229
|
else {
|
|
217
230
|
structuredEvent = parsed;
|
|
@@ -244,9 +257,12 @@ function wireStdoutToRedis(proc, ns, wire) {
|
|
|
244
257
|
});
|
|
245
258
|
}
|
|
246
259
|
/**
|
|
247
|
-
* Spawn `claude --continue -p
|
|
248
|
-
*
|
|
260
|
+
* Spawn `claude --continue -p "{message}" --dangerously-skip-permissions` in the
|
|
261
|
+
* namespace workspace. Pipes stdout line-by-line → wire.discord.publishOutgoing.
|
|
262
|
+
* Also streams each chunk to Redis: PUBLISH cca:meta:{ns}:stream and LPUSH cca:meta:{ns}:log.
|
|
249
263
|
* Returns a Promise that resolves when the process exits.
|
|
264
|
+
*
|
|
265
|
+
* Kept for backwards-compat and direct integration-test usage.
|
|
250
266
|
*/
|
|
251
267
|
export function spawnSession(ns, message, token, wire) {
|
|
252
268
|
return new Promise((resolve, reject) => {
|
|
@@ -255,15 +271,11 @@ export function spawnSession(ns, message, token, wire) {
|
|
|
255
271
|
const env = buildEnv(token);
|
|
256
272
|
const proc = spawn(claudeBin, [
|
|
257
273
|
"--continue",
|
|
258
|
-
"-p",
|
|
274
|
+
"-p", message,
|
|
259
275
|
"--output-format", "stream-json",
|
|
260
276
|
"--verbose",
|
|
261
277
|
"--dangerously-skip-permissions",
|
|
262
|
-
], { cwd: wsPath, env, stdio: ["
|
|
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
|
-
});
|
|
278
|
+
], { cwd: wsPath, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
267
279
|
wireStdoutToRedis(proc, ns, wire);
|
|
268
280
|
proc.on("exit", (code) => {
|
|
269
281
|
console.log(`[meta-agent-manager] session exited (ns=${ns}, code=${code})`);
|
|
@@ -276,75 +288,149 @@ export function spawnSession(ns, message, token, wire) {
|
|
|
276
288
|
});
|
|
277
289
|
}
|
|
278
290
|
/**
|
|
279
|
-
*
|
|
291
|
+
* Spawn a persistent `claude --continue` process for a namespace.
|
|
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.
|
|
280
334
|
*
|
|
281
|
-
*
|
|
282
|
-
* then stdin is closed (EOF) so Claude processes and exits. --continue maintains
|
|
283
|
-
* conversation history via the JSONL session file in the workspace.
|
|
335
|
+
* On process exit: remove from sessions map. Next message triggers a respawn.
|
|
284
336
|
*
|
|
285
|
-
*
|
|
286
|
-
*
|
|
337
|
+
* The 3-second poll loop drains any messages that arrived while a session was
|
|
338
|
+
* starting up or temporarily unavailable.
|
|
287
339
|
*/
|
|
288
340
|
export function createMetaAgentManager() {
|
|
289
341
|
let pollInterval = null;
|
|
290
|
-
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
const
|
|
294
|
-
// Cached session metadata (token, wire, repoUrl) per namespace
|
|
295
|
-
const sessionMeta = new Map();
|
|
342
|
+
/** One persistent ChildProcess per namespace. */
|
|
343
|
+
const sessions = new Map();
|
|
344
|
+
/** Namespaces currently being set up (workspace clone / first spawn). */
|
|
345
|
+
const startingUp = new Set();
|
|
296
346
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
347
|
+
* Write a line to the stdin of the persistent session for ns.
|
|
348
|
+
* Silently no-ops if no session exists.
|
|
299
349
|
*/
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const queue = queues.get(ns);
|
|
304
|
-
if (!queue || queue.length === 0)
|
|
305
|
-
return;
|
|
306
|
-
const meta = sessionMeta.get(ns);
|
|
307
|
-
if (!meta)
|
|
350
|
+
function writeToStdin(ns, line) {
|
|
351
|
+
const session = sessions.get(ns);
|
|
352
|
+
if (!session)
|
|
308
353
|
return;
|
|
309
|
-
processing.add(ns);
|
|
310
|
-
console.log(`[meta-agent-manager] processQueue started (ns=${ns})`);
|
|
311
354
|
try {
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
}
|
|
325
|
-
}
|
|
355
|
+
// Interactive mode: plain text line, Claude reads it as user input
|
|
356
|
+
session.proc.stdin.write(`${line}\n`);
|
|
326
357
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
358
|
+
catch (err) {
|
|
359
|
+
console.warn(`[meta-agent-manager] stdin write failed (ns=${ns}):`, err.message);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Drain all pending messages from the Redis input queue into the session's stdin.
|
|
364
|
+
*/
|
|
365
|
+
async function drainQueue(ns, wire) {
|
|
366
|
+
const inputKey = discordMetaInputKey(ns);
|
|
367
|
+
for (;;) {
|
|
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;
|
|
383
|
+
}
|
|
384
|
+
writeToStdin(ns, content);
|
|
330
385
|
}
|
|
331
386
|
}
|
|
332
387
|
/**
|
|
333
|
-
* Ensure
|
|
388
|
+
* Ensure a persistent session exists for ns.
|
|
389
|
+
* If one already exists, returns immediately.
|
|
390
|
+
* If not, spawns one, drains any queued messages, then writes `firstMessage` if provided.
|
|
334
391
|
*/
|
|
335
392
|
async function ensureSession(ns, repoUrl, token, wire, firstMessage) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
393
|
+
if (sessions.has(ns)) {
|
|
394
|
+
if (firstMessage !== undefined)
|
|
395
|
+
writeToStdin(ns, firstMessage);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (startingUp.has(ns)) {
|
|
399
|
+
// Already spinning up — queue the message so drainQueue picks it up
|
|
400
|
+
return;
|
|
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);
|
|
344
433
|
}
|
|
345
|
-
processQueue(ns).catch((err) => {
|
|
346
|
-
console.error(`[meta-agent-manager] processQueue error (ns=${ns}):`, err.message);
|
|
347
|
-
});
|
|
348
434
|
}
|
|
349
435
|
return {
|
|
350
436
|
async ensureWorkspace(ns, repoUrl) {
|
|
@@ -356,7 +442,7 @@ export function createMetaAgentManager() {
|
|
|
356
442
|
},
|
|
357
443
|
startPolling(wire, getNamespaces, instanceId) {
|
|
358
444
|
if (pollInterval)
|
|
359
|
-
return;
|
|
445
|
+
return; // already running
|
|
360
446
|
pollInterval = setInterval(() => {
|
|
361
447
|
const namespaces = getNamespaces();
|
|
362
448
|
if (namespaces.length === 0)
|
|
@@ -371,21 +457,25 @@ export function createMetaAgentManager() {
|
|
|
371
457
|
}
|
|
372
458
|
}).catch(() => { });
|
|
373
459
|
}
|
|
374
|
-
//
|
|
375
|
-
if (
|
|
460
|
+
// If session exists, drain any queued messages
|
|
461
|
+
if (sessions.has(ns)) {
|
|
462
|
+
drainQueue(ns, wire).catch((err) => {
|
|
463
|
+
console.warn(`[meta-agent-manager] drainQueue error (ns=${ns}):`, err.message);
|
|
464
|
+
});
|
|
376
465
|
continue;
|
|
377
|
-
|
|
466
|
+
}
|
|
467
|
+
// Check if anything is queued for this namespace
|
|
378
468
|
const inputKey = discordMetaInputKey(ns);
|
|
379
469
|
wire._redis.llen(inputKey).then(async (queueLen) => {
|
|
380
470
|
if (queueLen === 0)
|
|
381
471
|
return;
|
|
472
|
+
// Resolve token
|
|
382
473
|
let token;
|
|
383
474
|
try {
|
|
384
475
|
token = await wire.token.getMaster();
|
|
385
476
|
}
|
|
386
477
|
catch {
|
|
387
|
-
token =
|
|
388
|
-
?? process.env.CLAUDE_CODE_OAUTH_TOKEN
|
|
478
|
+
token = process.env.CLAUDE_CODE_OAUTH_TOKEN
|
|
389
479
|
?? process.env.CLAUDE_CODE_TOKEN
|
|
390
480
|
?? process.env.ANTHROPIC_API_KEY
|
|
391
481
|
?? "";
|
|
@@ -394,31 +484,8 @@ export function createMetaAgentManager() {
|
|
|
394
484
|
console.warn(`[meta-agent-manager] no token available, skipping session for ns=${ns}`);
|
|
395
485
|
return;
|
|
396
486
|
}
|
|
397
|
-
//
|
|
398
|
-
|
|
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
|
-
});
|
|
487
|
+
// ensureSession will drain the queue itself
|
|
488
|
+
await ensureSession(ns, repoUrl, token, wire);
|
|
422
489
|
}).catch((err) => {
|
|
423
490
|
console.warn(`[meta-agent-manager] llen error (ns=${ns}):`, err.message);
|
|
424
491
|
});
|
|
@@ -432,20 +499,35 @@ export function createMetaAgentManager() {
|
|
|
432
499
|
pollInterval = null;
|
|
433
500
|
console.log("[meta-agent-manager] polling stopped");
|
|
434
501
|
}
|
|
435
|
-
|
|
502
|
+
// Kill all active sessions
|
|
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
|
+
}
|
|
436
514
|
},
|
|
437
515
|
killSession(ns) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
516
|
+
const session = sessions.get(ns);
|
|
517
|
+
if (!session)
|
|
518
|
+
return;
|
|
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})`);
|
|
441
528
|
},
|
|
442
529
|
sendToSession(ns, line) {
|
|
443
|
-
|
|
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
|
-
});
|
|
530
|
+
writeToStdin(ns, line);
|
|
449
531
|
},
|
|
450
532
|
};
|
|
451
533
|
}
|