agentcord 2.1.0 → 2.1.1
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/README.md +3 -2
- package/dist/{bot-C6RYOLAL.js → bot-QXUF3LNK.js} +92 -13
- package/dist/cli.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# agentcord
|
|
2
2
|
|
|
3
|
-
Run and manage AI coding agent sessions on your machine through Discord.
|
|
3
|
+
Run and manage AI coding agent sessions on your machine through Discord. Supports [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and OpenAI Codex.
|
|
4
4
|
|
|
5
5
|
Each session gets a Discord channel for chatting with the agent and a tmux session for direct terminal access. Sessions are organized by project — create multiple sessions in the same codebase, each with their own channel.
|
|
6
6
|
|
|
@@ -20,6 +20,7 @@ The setup wizard walks you through creating a Discord app, configuring the bot t
|
|
|
20
20
|
- **Node.js 22.6+** (uses native TypeScript execution)
|
|
21
21
|
- **tmux** (for terminal session access)
|
|
22
22
|
- **Claude Code** installed on the machine (`@anthropic-ai/claude-agent-sdk`)
|
|
23
|
+
- **OpenAI Codex SDK** for Codex sessions (`@openai/codex-sdk`)
|
|
23
24
|
|
|
24
25
|
## How It Works
|
|
25
26
|
|
|
@@ -58,7 +59,7 @@ Discord Server
|
|
|
58
59
|
| `/claude attach` | Show tmux attach command for terminal access |
|
|
59
60
|
| `/claude model <model>` | Change model for the session |
|
|
60
61
|
| `/claude verbose` | Toggle tool call/result visibility |
|
|
61
|
-
| `/claude sync` | Reconnect orphaned tmux
|
|
62
|
+
| `/claude sync` | Reconnect orphaned sessions (tmux + existing provider channels, including Codex) |
|
|
62
63
|
|
|
63
64
|
### Shell
|
|
64
65
|
|
|
@@ -126,7 +126,7 @@ function getCommandDefinitions() {
|
|
|
126
126
|
{ name: "On request", value: "on-request" },
|
|
127
127
|
{ name: "On failure", value: "on-failure" },
|
|
128
128
|
{ name: "Untrusted", value: "untrusted" }
|
|
129
|
-
)).addBooleanOption((opt) => opt.setName("network-access").setDescription("Allow network in workspace-write sandbox (Codex only)")).addStringOption((opt) => opt.setName("directory").setDescription("Working directory (default: configured default)"))).addSubcommand((sub) => sub.setName("list").setDescription("List active sessions")).addSubcommand((sub) => sub.setName("end").setDescription("End the session in this channel")).addSubcommand((sub) => sub.setName("continue").setDescription("Continue the last conversation")).addSubcommand((sub) => sub.setName("stop").setDescription("Stop current generation")).addSubcommand((sub) => sub.setName("output").setDescription("Show recent conversation output").addIntegerOption((opt) => opt.setName("lines").setDescription("Number of lines (default 50)").setMinValue(1).setMaxValue(500))).addSubcommand((sub) => sub.setName("attach").setDescription("Show tmux attach command for terminal access")).addSubcommand((sub) => sub.setName("sync").setDescription("Reconnect orphaned tmux
|
|
129
|
+
)).addBooleanOption((opt) => opt.setName("network-access").setDescription("Allow network in workspace-write sandbox (Codex only)")).addStringOption((opt) => opt.setName("directory").setDescription("Working directory (default: configured default)"))).addSubcommand((sub) => sub.setName("list").setDescription("List active sessions")).addSubcommand((sub) => sub.setName("end").setDescription("End the session in this channel")).addSubcommand((sub) => sub.setName("continue").setDescription("Continue the last conversation")).addSubcommand((sub) => sub.setName("stop").setDescription("Stop current generation")).addSubcommand((sub) => sub.setName("output").setDescription("Show recent conversation output").addIntegerOption((opt) => opt.setName("lines").setDescription("Number of lines (default 50)").setMinValue(1).setMaxValue(500))).addSubcommand((sub) => sub.setName("attach").setDescription("Show tmux attach command for terminal access")).addSubcommand((sub) => sub.setName("sync").setDescription("Reconnect orphaned sessions (tmux + provider channels)")).addSubcommand((sub) => sub.setName("model").setDescription("Change the model for this session").addStringOption((opt) => opt.setName("model").setDescription("Model name (e.g. claude-sonnet-4-5-20250929, gpt-5.3-codex)").setRequired(true))).addSubcommand((sub) => sub.setName("id").setDescription("Show the provider session ID for this channel")).addSubcommand((sub) => sub.setName("verbose").setDescription("Toggle showing tool calls and results in this session")).addSubcommand((sub) => sub.setName("mode").setDescription("Set session mode (auto/plan/normal)").addStringOption((opt) => opt.setName("mode").setDescription("Session mode").setRequired(true).addChoices(
|
|
130
130
|
{ name: "Auto \u2014 full autonomy", value: "auto" },
|
|
131
131
|
{ name: "Plan \u2014 plan before executing", value: "plan" },
|
|
132
132
|
{ name: "Normal \u2014 ask before destructive ops", value: "normal" }
|
|
@@ -943,14 +943,27 @@ async function createSession(name, directory, channelId, projectName, provider =
|
|
|
943
943
|
const usesTmux = providerInstance.supports("tmux");
|
|
944
944
|
let id = sanitizeSessionName(name);
|
|
945
945
|
let tmuxName = usesTmux ? `${SESSION_PREFIX}${id}` : "";
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
946
|
+
if (options.recoverExisting) {
|
|
947
|
+
if (sessions.has(id)) {
|
|
948
|
+
throw new Error(`Session "${id}" already exists`);
|
|
949
|
+
}
|
|
950
|
+
} else {
|
|
951
|
+
let suffix = 1;
|
|
952
|
+
while (sessions.has(id) || usesTmux && await tmuxSessionExists(tmuxName)) {
|
|
953
|
+
suffix++;
|
|
954
|
+
id = sanitizeSessionName(`${name}-${suffix}`);
|
|
955
|
+
if (usesTmux) tmuxName = `${SESSION_PREFIX}${id}`;
|
|
956
|
+
}
|
|
951
957
|
}
|
|
952
958
|
if (usesTmux) {
|
|
953
|
-
|
|
959
|
+
if (options.recoverExisting) {
|
|
960
|
+
const existing = await tmuxSessionExists(tmuxName);
|
|
961
|
+
if (!existing) {
|
|
962
|
+
await tmux("new-session", "-d", "-s", tmuxName, "-c", resolvedDir);
|
|
963
|
+
}
|
|
964
|
+
} else {
|
|
965
|
+
await tmux("new-session", "-d", "-s", tmuxName, "-c", resolvedDir);
|
|
966
|
+
}
|
|
954
967
|
}
|
|
955
968
|
const session = {
|
|
956
969
|
id,
|
|
@@ -1867,6 +1880,17 @@ ${event.message}
|
|
|
1867
1880
|
break;
|
|
1868
1881
|
}
|
|
1869
1882
|
case "session_init": {
|
|
1883
|
+
const session = getSession(sessionId);
|
|
1884
|
+
const providerSessionId = event.providerSessionId || session?.providerSessionId;
|
|
1885
|
+
if (providerSessionId) {
|
|
1886
|
+
const currentTopic = channel.topic ?? "";
|
|
1887
|
+
const topicBase = currentTopic ? currentTopic.replace(/\s*\|\s*Provider Session:\s*[^\s|]+/i, "") : `${session?.provider === "codex" ? "OpenAI Codex" : "Claude Code"} session | Dir: ${session?.directory || "unknown"}`;
|
|
1888
|
+
const nextTopic = truncate(`${topicBase} | Provider Session: ${providerSessionId}`, 1024);
|
|
1889
|
+
if (nextTopic !== currentTopic) {
|
|
1890
|
+
await channel.setTopic(nextTopic).catch(() => {
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1870
1894
|
break;
|
|
1871
1895
|
}
|
|
1872
1896
|
}
|
|
@@ -2072,6 +2096,16 @@ function addCodexPolicyFields(fields, options) {
|
|
|
2072
2096
|
fields.push({ name: "Network Access", value: options.networkAccessEnabled ? "enabled" : "disabled", inline: true });
|
|
2073
2097
|
}
|
|
2074
2098
|
}
|
|
2099
|
+
function parseTopicDirectory(topic) {
|
|
2100
|
+
if (!topic) return null;
|
|
2101
|
+
const m = topic.match(/\bDir:\s*(.+?)(?:\s*\|\s*Provider Session:|$)/i);
|
|
2102
|
+
return m?.[1]?.trim() || null;
|
|
2103
|
+
}
|
|
2104
|
+
function parseTopicProviderSessionId(topic) {
|
|
2105
|
+
if (!topic) return void 0;
|
|
2106
|
+
const m = topic.match(/\bProvider Session:\s*([^\s|]+)/i);
|
|
2107
|
+
return m?.[1]?.trim() || void 0;
|
|
2108
|
+
}
|
|
2075
2109
|
async function handleSessionNew(interaction) {
|
|
2076
2110
|
const name = interaction.options.getString("name", true);
|
|
2077
2111
|
const provider = interaction.options.getString("provider") || "claude";
|
|
@@ -2279,7 +2313,7 @@ async function handleSessionResume(interaction) {
|
|
|
2279
2313
|
name: `${provider}-${session.id}`,
|
|
2280
2314
|
type: ChannelType.GuildText,
|
|
2281
2315
|
parent: category.id,
|
|
2282
|
-
topic: `${PROVIDER_LABELS[provider]} session (resumed) | Dir: ${directory}`
|
|
2316
|
+
topic: `${PROVIDER_LABELS[provider]} session (resumed) | Dir: ${directory} | Provider Session: ${providerSessionId}`
|
|
2283
2317
|
});
|
|
2284
2318
|
await linkChannel(session.id, channel.id);
|
|
2285
2319
|
const fields = [
|
|
@@ -2458,7 +2492,38 @@ async function handleSessionSync(interaction) {
|
|
|
2458
2492
|
const tmuxSessions = await listTmuxSessions();
|
|
2459
2493
|
const currentSessions = getAllSessions();
|
|
2460
2494
|
const currentIds = new Set(currentSessions.map((s) => s.id));
|
|
2461
|
-
|
|
2495
|
+
const currentChannelIds = new Set(currentSessions.map((s) => s.channelId));
|
|
2496
|
+
let syncedTmux = 0;
|
|
2497
|
+
let syncedChannels = 0;
|
|
2498
|
+
for (const ch of guild.channels.cache.values()) {
|
|
2499
|
+
if (ch.type !== ChannelType.GuildText) continue;
|
|
2500
|
+
if (currentChannelIds.has(ch.id)) continue;
|
|
2501
|
+
const m = ch.name.match(/^(claude|codex)-(.+)$/);
|
|
2502
|
+
if (!m) continue;
|
|
2503
|
+
const provider = m[1];
|
|
2504
|
+
const sessionName = m[2];
|
|
2505
|
+
const directory = parseTopicDirectory(ch.topic) || config.defaultDirectory;
|
|
2506
|
+
const providerSessionId = parseTopicProviderSessionId(ch.topic);
|
|
2507
|
+
const projectName = projectNameFromDir(directory);
|
|
2508
|
+
if (ch.parentId) {
|
|
2509
|
+
getOrCreateProject(projectName, directory, ch.parentId);
|
|
2510
|
+
}
|
|
2511
|
+
try {
|
|
2512
|
+
const recovered = await createSession(
|
|
2513
|
+
sessionName,
|
|
2514
|
+
directory,
|
|
2515
|
+
ch.id,
|
|
2516
|
+
projectName,
|
|
2517
|
+
provider,
|
|
2518
|
+
providerSessionId,
|
|
2519
|
+
{ recoverExisting: true }
|
|
2520
|
+
);
|
|
2521
|
+
syncedChannels++;
|
|
2522
|
+
currentIds.add(recovered.id);
|
|
2523
|
+
currentChannelIds.add(ch.id);
|
|
2524
|
+
} catch {
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2462
2527
|
for (const tmuxSession of tmuxSessions) {
|
|
2463
2528
|
if (currentIds.has(tmuxSession.id)) continue;
|
|
2464
2529
|
const projectName = projectNameFromDir(tmuxSession.directory);
|
|
@@ -2469,11 +2534,25 @@ async function handleSessionSync(interaction) {
|
|
|
2469
2534
|
parent: category.id,
|
|
2470
2535
|
topic: `Claude Code session (synced) | Dir: ${tmuxSession.directory}`
|
|
2471
2536
|
});
|
|
2472
|
-
await createSession(
|
|
2473
|
-
|
|
2474
|
-
|
|
2537
|
+
const recovered = await createSession(
|
|
2538
|
+
tmuxSession.id,
|
|
2539
|
+
tmuxSession.directory,
|
|
2540
|
+
channel.id,
|
|
2541
|
+
projectName,
|
|
2542
|
+
"claude",
|
|
2543
|
+
void 0,
|
|
2544
|
+
{ recoverExisting: true }
|
|
2545
|
+
);
|
|
2546
|
+
syncedTmux++;
|
|
2547
|
+
currentIds.add(recovered.id);
|
|
2548
|
+
currentChannelIds.add(channel.id);
|
|
2549
|
+
}
|
|
2550
|
+
const synced = syncedChannels + syncedTmux;
|
|
2551
|
+
const detail = [];
|
|
2552
|
+
if (syncedChannels > 0) detail.push(`${syncedChannels} channel`);
|
|
2553
|
+
if (syncedTmux > 0) detail.push(`${syncedTmux} tmux`);
|
|
2475
2554
|
await interaction.editReply(
|
|
2476
|
-
synced > 0 ? `Synced ${synced} orphaned session(s).` : "No orphaned sessions found."
|
|
2555
|
+
synced > 0 ? `Synced ${synced} orphaned session(s) (${detail.join(", ")}).` : "No orphaned sessions found."
|
|
2477
2556
|
);
|
|
2478
2557
|
}
|
|
2479
2558
|
async function handleSessionId(interaction) {
|
package/dist/cli.js
CHANGED
|
@@ -18,7 +18,7 @@ switch (command) {
|
|
|
18
18
|
console.log("Run \x1B[36magentcord setup\x1B[0m to configure.\n");
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
|
21
|
-
const { startBot } = await import("./bot-
|
|
21
|
+
const { startBot } = await import("./bot-QXUF3LNK.js");
|
|
22
22
|
console.log("agentcord starting...");
|
|
23
23
|
await startBot();
|
|
24
24
|
break;
|