@gonzih/cc-discord 0.2.47 → 0.2.49
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/bot.d.ts +20 -11
- package/dist/bot.js +328 -74
- package/dist/index.d.ts +9 -5
- package/dist/index.js +17 -10
- package/dist/meta-agent-manager.d.ts +2 -2
- package/dist/meta-agent-manager.js +567 -81
- package/dist/notifier.d.ts +2 -2
- package/dist/notifier.js +80 -44
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +1 -1
- package/package.json +8 -5
package/dist/notifier.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* v0.2.0 channels (discord-scoped):
|
|
5
5
|
* cca:discord:notify:{ns} — job completion notifications → forward to Discord channel
|
|
6
6
|
* cca:chat:incoming:{ns} — messages from the web UI → echo to Discord + feed to meta-agent
|
|
7
|
-
* cca:discord:chat:outgoing:{ns} — meta-agent stdout lines (source=claude) → buffer+debounce → Discord
|
|
7
|
+
* cca:discord:chat:outgoing:{ns} — meta-agent stdout lines (source=claude/codex) → buffer+debounce → Discord
|
|
8
8
|
*
|
|
9
9
|
* All messages (Discord incoming, Claude responses) are also written to:
|
|
10
10
|
* cca:discord:chat:log:{ns} — LPUSH + LTRIM 0 499 (last 500 messages)
|
|
@@ -23,7 +23,7 @@ export interface EvalReport {
|
|
|
23
23
|
import type { CcDiscordBot } from "./bot.js";
|
|
24
24
|
export interface ChatMessage {
|
|
25
25
|
id: string;
|
|
26
|
-
source: "discord" | "ui" | "claude" | "cc-tg" | "cc-discord";
|
|
26
|
+
source: "discord" | "ui" | "claude" | "codex" | "cc-tg" | "cc-discord";
|
|
27
27
|
role: "user" | "assistant" | "tool";
|
|
28
28
|
content: string;
|
|
29
29
|
timestamp: string;
|
package/dist/notifier.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* v0.2.0 channels (discord-scoped):
|
|
5
5
|
* cca:discord:notify:{ns} — job completion notifications → forward to Discord channel
|
|
6
6
|
* cca:chat:incoming:{ns} — messages from the web UI → echo to Discord + feed to meta-agent
|
|
7
|
-
* cca:discord:chat:outgoing:{ns} — meta-agent stdout lines (source=claude) → buffer+debounce → Discord
|
|
7
|
+
* cca:discord:chat:outgoing:{ns} — meta-agent stdout lines (source=claude/codex) → buffer+debounce → Discord
|
|
8
8
|
*
|
|
9
9
|
* All messages (Discord incoming, Claude responses) are also written to:
|
|
10
10
|
* cca:discord:chat:log:{ns} — LPUSH + LTRIM 0 499 (last 500 messages)
|
|
@@ -270,15 +270,59 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
270
270
|
log("info", `psubscribed to ${outgoingPattern}`);
|
|
271
271
|
}
|
|
272
272
|
});
|
|
273
|
+
const TOOL_SLOT = "tools";
|
|
274
|
+
const RESPONSE_SLOT = "response";
|
|
273
275
|
const liveStates = new Map();
|
|
274
276
|
function getLiveState(ns, targetChannelId) {
|
|
275
277
|
let state = liveStates.get(ns);
|
|
276
278
|
if (!state) {
|
|
277
|
-
state = {
|
|
279
|
+
state = {
|
|
280
|
+
text: "",
|
|
281
|
+
toolLog: [],
|
|
282
|
+
activeTool: "",
|
|
283
|
+
targetChannelId,
|
|
284
|
+
responseStarting: false,
|
|
285
|
+
responseStarted: false,
|
|
286
|
+
toolStarting: false,
|
|
287
|
+
toolStarted: false,
|
|
288
|
+
finalTimer: null,
|
|
289
|
+
};
|
|
278
290
|
liveStates.set(ns, state);
|
|
279
291
|
}
|
|
280
292
|
return state;
|
|
281
293
|
}
|
|
294
|
+
function toolLogText(ns, state, live) {
|
|
295
|
+
const visible = state.toolLog.slice(0, 20);
|
|
296
|
+
const body = visible.length > 0 ? visible.join("\n") : "waiting for tool activity";
|
|
297
|
+
return `← [${ns}] tools\n${body}${live ? " ▋" : ""}`;
|
|
298
|
+
}
|
|
299
|
+
function ensureToolLogMessage(ns, deliverTo, state) {
|
|
300
|
+
if (state.toolStarted) {
|
|
301
|
+
bot.updateLiveMessage(deliverTo, toolLogText(ns, state, true), TOOL_SLOT);
|
|
302
|
+
return Promise.resolve();
|
|
303
|
+
}
|
|
304
|
+
if (state.toolStarting)
|
|
305
|
+
return Promise.resolve();
|
|
306
|
+
state.toolStarting = true;
|
|
307
|
+
return bot.startOrGetLiveMessage(deliverTo, toolLogText(ns, state, true), TOOL_SLOT).then((msg) => {
|
|
308
|
+
state.toolStarting = false;
|
|
309
|
+
if (msg) {
|
|
310
|
+
state.toolStarted = true;
|
|
311
|
+
bot.updateLiveMessage(deliverTo, toolLogText(ns, state, true), TOOL_SLOT);
|
|
312
|
+
}
|
|
313
|
+
}).catch(() => {
|
|
314
|
+
state.toolStarting = false;
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
function replaceNewestActiveTool(state, replacement) {
|
|
318
|
+
const idx = state.toolLog.findIndex((line) => line.startsWith("⚙️"));
|
|
319
|
+
if (idx >= 0) {
|
|
320
|
+
state.toolLog[idx] = replacement;
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
state.toolLog.unshift(replacement);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
282
326
|
async function finalizeState(ns, state) {
|
|
283
327
|
if (state.finalTimer) {
|
|
284
328
|
clearTimeout(state.finalTimer);
|
|
@@ -288,10 +332,18 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
288
332
|
const deliverTo = bot.getLoopThreadId(state.targetChannelId) ?? state.targetChannelId;
|
|
289
333
|
bot.stopMetaAgentTyping(deliverTo);
|
|
290
334
|
const trimmed = state.text.trim();
|
|
291
|
-
|
|
335
|
+
const toolText = state.toolLog.length > 0 ? toolLogText(ns, state, false) : "";
|
|
336
|
+
if (state.toolStarted || state.toolLog.length > 0) {
|
|
337
|
+
await bot.finalizeLiveMessage(deliverTo, toolText, TOOL_SLOT);
|
|
338
|
+
}
|
|
339
|
+
if (!trimmed && !state.responseStarted)
|
|
292
340
|
return;
|
|
293
341
|
const fullText = trimmed ? `← [${ns}]\n${stripAnsi(trimmed)}` : "";
|
|
294
|
-
|
|
342
|
+
if (fullText.length > 0 && fullText.length < 30) {
|
|
343
|
+
await bot.finalizeLiveMessage(deliverTo, "", RESPONSE_SLOT);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
await bot.finalizeLiveMessage(deliverTo, fullText, RESPONSE_SLOT);
|
|
295
347
|
}
|
|
296
348
|
function scheduleFinal(ns, state) {
|
|
297
349
|
if (state.finalTimer)
|
|
@@ -312,7 +364,7 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
312
364
|
catch {
|
|
313
365
|
return;
|
|
314
366
|
}
|
|
315
|
-
if (parsed?.source !== "claude")
|
|
367
|
+
if (parsed?.source !== "claude" && parsed?.source !== "codex")
|
|
316
368
|
return;
|
|
317
369
|
const targetChannelId = routedChannelIds.get(ns) ??
|
|
318
370
|
(ns === namespace ? (notifyChannelId ?? getActiveChannelId?.()) : undefined);
|
|
@@ -326,41 +378,29 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
326
378
|
// tool_start: show activity overlay, suspend finalize timer
|
|
327
379
|
if (event === "tool_start") {
|
|
328
380
|
const toolName = parsed.content || "tool";
|
|
329
|
-
state.
|
|
381
|
+
state.activeTool = toolName;
|
|
382
|
+
state.toolLog.unshift(`⚙️ \`${toolName}\`...`);
|
|
330
383
|
if (state.finalTimer) {
|
|
331
384
|
clearTimeout(state.finalTimer);
|
|
332
385
|
state.finalTimer = null;
|
|
333
386
|
}
|
|
334
|
-
|
|
335
|
-
const textPart = state.text.trim() ? `← [${ns}]\n${stripAnsi(state.text.trim())}\n\n` : "";
|
|
336
|
-
bot.updateLiveMessage(deliverTo, `${textPart}⚙️ \`${toolName}\`...`);
|
|
337
|
-
}
|
|
338
|
-
else if (!state.starting) {
|
|
339
|
-
state.starting = true;
|
|
340
|
-
bot.startOrGetLiveMessage(deliverTo, `⚙️ \`${toolName}\`...`).then((msg) => {
|
|
341
|
-
state.starting = false;
|
|
342
|
-
if (msg)
|
|
343
|
-
state.liveStarted = true;
|
|
344
|
-
}).catch(() => { state.starting = false; });
|
|
345
|
-
}
|
|
387
|
+
void ensureToolLogMessage(ns, deliverTo, state);
|
|
346
388
|
return;
|
|
347
389
|
}
|
|
348
390
|
// tool_end: clear overlay, resume finalize timer if text is pending
|
|
349
391
|
if (event === "tool_end") {
|
|
350
|
-
state.
|
|
392
|
+
const toolName = state.activeTool || "tool";
|
|
393
|
+
replaceNewestActiveTool(state, `✓ \`${toolName}\``);
|
|
394
|
+
state.activeTool = "";
|
|
395
|
+
if (state.toolStarted) {
|
|
396
|
+
bot.updateLiveMessage(deliverTo, toolLogText(ns, state, true), TOOL_SLOT);
|
|
397
|
+
}
|
|
351
398
|
if (state.text.trim())
|
|
352
399
|
scheduleFinal(ns, state);
|
|
353
400
|
return;
|
|
354
401
|
}
|
|
355
402
|
// done: immediate finalization (fired by meta-agent-manager after result event)
|
|
356
403
|
if (event === "done") {
|
|
357
|
-
if (state.starting) {
|
|
358
|
-
// startOrGetLiveMessage is still in flight — defer finalization to its .then() callback
|
|
359
|
-
// so we don't race: finalizeState runs before the Discord message exists → fallback
|
|
360
|
-
// sends a second plain message, leaving the live message orphaned with ▋.
|
|
361
|
-
state.pendingDone = true;
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
404
|
finalizeState(ns, state).catch((err) => {
|
|
365
405
|
log("warn", `meta-agent done finalize failed (ns=${ns}):`, err.message);
|
|
366
406
|
});
|
|
@@ -370,31 +410,27 @@ export function startNotifier(bot, notifyChannelId, namespace, redis, handleUser
|
|
|
370
410
|
const content = parsed.content;
|
|
371
411
|
if (!content)
|
|
372
412
|
return;
|
|
373
|
-
state.
|
|
374
|
-
|
|
413
|
+
state.text += parsed.source === "codex"
|
|
414
|
+
? content
|
|
415
|
+
: (state.text ? "\n" : "") + content;
|
|
375
416
|
const textDisplay = `← [${ns}]\n${stripAnsi(state.text)} ▋`;
|
|
376
|
-
if (!state.
|
|
377
|
-
if (!state.
|
|
378
|
-
state.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
417
|
+
if (!state.responseStarted) {
|
|
418
|
+
if (!state.responseStarting) {
|
|
419
|
+
state.responseStarting = true;
|
|
420
|
+
ensureToolLogMessage(ns, deliverTo, state).then(() => {
|
|
421
|
+
return bot.startOrGetLiveMessage(deliverTo, textDisplay, RESPONSE_SLOT);
|
|
422
|
+
}).then((msg) => {
|
|
423
|
+
state.responseStarting = false;
|
|
382
424
|
if (msg) {
|
|
383
|
-
state.
|
|
384
|
-
bot.updateLiveMessage(deliverTo, textDisplay);
|
|
385
|
-
}
|
|
386
|
-
// done arrived while we were creating the message — finalize now
|
|
387
|
-
if (state.pendingDone) {
|
|
388
|
-
finalizeState(ns, state).catch((err) => {
|
|
389
|
-
log("warn", `meta-agent deferred done finalize failed (ns=${ns}):`, err.message);
|
|
390
|
-
});
|
|
425
|
+
state.responseStarted = true;
|
|
426
|
+
bot.updateLiveMessage(deliverTo, textDisplay, RESPONSE_SLOT);
|
|
391
427
|
}
|
|
392
|
-
}).catch(() => { state.
|
|
428
|
+
}).catch(() => { state.responseStarting = false; });
|
|
393
429
|
}
|
|
394
430
|
// Text is buffered in state.text; applied when startOrGetLiveMessage resolves
|
|
395
431
|
}
|
|
396
432
|
else {
|
|
397
|
-
bot.updateLiveMessage(deliverTo, textDisplay);
|
|
433
|
+
bot.updateLiveMessage(deliverTo, textDisplay, RESPONSE_SLOT);
|
|
398
434
|
}
|
|
399
435
|
scheduleFinal(ns, state);
|
|
400
436
|
});
|
package/dist/tokens.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Supports CLAUDE_CODE_OAUTH_TOKENS (comma-separated list of tokens).
|
|
5
5
|
* Falls back to CLAUDE_CODE_OAUTH_TOKEN for single-token / backwards compat.
|
|
6
6
|
*
|
|
7
|
-
* cc-tg token pool rotates independently from cc-
|
|
7
|
+
* cc-tg token pool rotates independently from cc-discord's local session pool.
|
|
8
8
|
*/
|
|
9
9
|
/**
|
|
10
10
|
* Load tokens from env vars. Called on startup; also re-callable in tests.
|
package/dist/tokens.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Supports CLAUDE_CODE_OAUTH_TOKENS (comma-separated list of tokens).
|
|
5
5
|
* Falls back to CLAUDE_CODE_OAUTH_TOKEN for single-token / backwards compat.
|
|
6
6
|
*
|
|
7
|
-
* cc-tg token pool rotates independently from cc-
|
|
7
|
+
* cc-tg token pool rotates independently from cc-discord's local session pool.
|
|
8
8
|
*/
|
|
9
9
|
let tokens = [];
|
|
10
10
|
let currentIndex = 0;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gonzih/cc-discord",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.49",
|
|
4
|
+
"description": "Discord bot for routed Claude Code or Codex agents with ATC dispatch",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"cc-discord": "
|
|
7
|
+
"cc-discord": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "tsc && chmod +x dist/index.js",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"test": "vitest run",
|
|
14
14
|
"test:watch": "vitest",
|
|
15
15
|
"test:coverage": "vitest run --coverage",
|
|
16
|
-
"test:integration": "CLAUDE_BIN=./test/fixtures/mock-claude.js vitest run test/integration/"
|
|
16
|
+
"test:integration": "CLAUDE_BIN=./test/fixtures/mock-claude.js vitest run test/integration/",
|
|
17
|
+
"test:integration:codex": "CC_DISCORD_AGENT_DRIVER=codex CODEX_BIN=./test/fixtures/mock-codex.js vitest run test/integration/meta-agent.test.ts"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
20
|
"dist/"
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
},
|
|
34
35
|
"repository": {
|
|
35
36
|
"type": "git",
|
|
36
|
-
"url": "https://github.com/Gonzih/cc-discord.git"
|
|
37
|
+
"url": "git+https://github.com/Gonzih/cc-discord.git"
|
|
37
38
|
},
|
|
38
39
|
"homepage": "https://github.com/Gonzih/cc-discord",
|
|
39
40
|
"bugs": {
|
|
@@ -42,6 +43,8 @@
|
|
|
42
43
|
"keywords": [
|
|
43
44
|
"claude",
|
|
44
45
|
"claude-code",
|
|
46
|
+
"codex",
|
|
47
|
+
"atc",
|
|
45
48
|
"discord",
|
|
46
49
|
"bot",
|
|
47
50
|
"ai"
|