agentcord 0.1.6 → 0.1.7

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.
@@ -53,7 +53,11 @@ import {
53
53
  Routes
54
54
  } from "discord.js";
55
55
  function getCommandDefinitions() {
56
- const claude = new SlashCommandBuilder().setName("claude").setDescription("Manage Claude Code sessions").addSubcommand((sub) => sub.setName("new").setDescription("Create a new Claude Code session").addStringOption((opt) => opt.setName("name").setDescription("Session name").setRequired(true)).addStringOption((opt) => opt.setName("directory").setDescription("Working directory (default: configured default)"))).addSubcommand((sub) => sub.setName("resume").setDescription("Resume an existing Claude Code session from terminal").addStringOption((opt) => opt.setName("session-id").setDescription("Claude Code session UUID").setRequired(true).setAutocomplete(true)).addStringOption((opt) => opt.setName("name").setDescription("Name for the Discord channel").setRequired(true)).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 sessions")).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)").setRequired(true))).addSubcommand((sub) => sub.setName("verbose").setDescription("Toggle showing tool calls and results in this session"));
56
+ const claude = new SlashCommandBuilder().setName("claude").setDescription("Manage Claude Code sessions").addSubcommand((sub) => sub.setName("new").setDescription("Create a new Claude Code session").addStringOption((opt) => opt.setName("name").setDescription("Session name").setRequired(true)).addStringOption((opt) => opt.setName("directory").setDescription("Working directory (default: configured default)"))).addSubcommand((sub) => sub.setName("resume").setDescription("Resume an existing Claude Code session from terminal").addStringOption((opt) => opt.setName("session-id").setDescription("Claude Code session UUID").setRequired(true).setAutocomplete(true)).addStringOption((opt) => opt.setName("name").setDescription("Name for the Discord channel").setRequired(true)).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 sessions")).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)").setRequired(true))).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(
57
+ { name: "Auto \u2014 full autonomy", value: "auto" },
58
+ { name: "Plan \u2014 plan before executing", value: "plan" },
59
+ { name: "Normal \u2014 ask before destructive ops", value: "normal" }
60
+ )));
57
61
  const shell = new SlashCommandBuilder().setName("shell").setDescription("Run shell commands in the session directory").addSubcommand((sub) => sub.setName("run").setDescription("Execute a shell command").addStringOption((opt) => opt.setName("command").setDescription("Command to run").setRequired(true))).addSubcommand((sub) => sub.setName("processes").setDescription("List running processes")).addSubcommand((sub) => sub.setName("kill").setDescription("Kill a running process").addIntegerOption((opt) => opt.setName("pid").setDescription("Process ID to kill").setRequired(true)));
58
62
  const agent = new SlashCommandBuilder().setName("agent").setDescription("Manage agent personas").addSubcommand((sub) => sub.setName("use").setDescription("Switch to an agent persona").addStringOption((opt) => opt.setName("persona").setDescription("Agent persona name").setRequired(true).addChoices(
59
63
  { name: "Code Reviewer", value: "code-reviewer" },
@@ -446,6 +450,11 @@ function detectYesNoPrompt(text) {
446
450
 
447
451
  // src/session-manager.ts
448
452
  var SESSION_PREFIX = "claude-";
453
+ var MODE_PROMPTS = {
454
+ auto: "",
455
+ plan: "You MUST use EnterPlanMode at the start of every task. Present your plan for user approval before making any code changes. Do not write or edit files until the user approves the plan.",
456
+ normal: "Before performing destructive or significant operations (deleting files, running dangerous commands, making large refactors, writing to many files), use AskUserQuestion to confirm with the user first. Ask for explicit approval before proceeding with changes."
457
+ };
449
458
  var sessionStore = new Store("sessions.json");
450
459
  var sessions = /* @__PURE__ */ new Map();
451
460
  var channelToSession = /* @__PURE__ */ new Map();
@@ -473,6 +482,7 @@ async function loadSessions() {
473
482
  sessions.set(s.id, {
474
483
  ...s,
475
484
  verbose: s.verbose ?? false,
485
+ mode: s.mode ?? "auto",
476
486
  isGenerating: false
477
487
  });
478
488
  channelToSession.set(s.channelId, s.id);
@@ -499,6 +509,7 @@ async function saveSessions() {
499
509
  model: s.model,
500
510
  agentPersona: s.agentPersona,
501
511
  verbose: s.verbose || void 0,
512
+ mode: s.mode !== "auto" ? s.mode : void 0,
502
513
  createdAt: s.createdAt,
503
514
  lastActivity: s.lastActivity,
504
515
  messageCount: s.messageCount,
@@ -532,6 +543,7 @@ async function createSession(name, directory, channelId, projectName, claudeSess
532
543
  tmuxName,
533
544
  claudeSessionId,
534
545
  verbose: false,
546
+ mode: "auto",
535
547
  isGenerating: false,
536
548
  createdAt: Date.now(),
537
549
  lastActivity: Date.now(),
@@ -601,6 +613,13 @@ function setVerbose(sessionId, verbose) {
601
613
  saveSessions();
602
614
  }
603
615
  }
616
+ function setMode(sessionId, mode) {
617
+ const session = sessions.get(sessionId);
618
+ if (session) {
619
+ session.mode = mode;
620
+ saveSessions();
621
+ }
622
+ }
604
623
  function setAgentPersona(sessionId, persona) {
605
624
  const session = sessions.get(sessionId);
606
625
  if (session) {
@@ -616,6 +635,8 @@ function buildSystemPrompt(session) {
616
635
  const agent = getAgent(session.agentPersona);
617
636
  if (agent?.systemPrompt) parts.push(agent.systemPrompt);
618
637
  }
638
+ const modePrompt = MODE_PROMPTS[session.mode];
639
+ if (modePrompt) parts.push(modePrompt);
619
640
  if (parts.length > 0) {
620
641
  return { type: "preset", preset: "claude_code", append: parts.join("\n\n") };
621
642
  }
@@ -806,6 +827,20 @@ function makeOptionButtons(sessionId, options) {
806
827
  }
807
828
  return rows;
808
829
  }
830
+ function makeModeButtons(sessionId, currentMode) {
831
+ const modes = [
832
+ { id: "auto", label: "\u26A1 Auto" },
833
+ { id: "plan", label: "\u{1F4CB} Plan" },
834
+ { id: "normal", label: "\u{1F6E1}\uFE0F Normal" }
835
+ ];
836
+ const row = new ActionRowBuilder();
837
+ for (const m of modes) {
838
+ row.addComponents(
839
+ new ButtonBuilder().setCustomId(`mode:${sessionId}:${m.id}`).setLabel(m.label).setStyle(m.id === currentMode ? ButtonStyle.Primary : ButtonStyle.Secondary).setDisabled(m.id === currentMode)
840
+ );
841
+ }
842
+ return row;
843
+ }
809
844
  function makeYesNoButtons(sessionId) {
810
845
  return new ActionRowBuilder().addComponents(
811
846
  new ButtonBuilder().setCustomId(`confirm:${sessionId}:yes`).setLabel("Yes").setStyle(ButtonStyle.Success),
@@ -1021,7 +1056,7 @@ function renderTaskListEmbed(resultText) {
1021
1056
  }
1022
1057
  return new EmbedBuilder().setColor(10181046).setTitle("\u{1F4CB} Task Board").setDescription(truncate(formatted, 4e3));
1023
1058
  }
1024
- async function handleOutputStream(stream, channel, sessionId, verbose = false) {
1059
+ async function handleOutputStream(stream, channel, sessionId, verbose = false, mode = "auto") {
1025
1060
  const streamer = new MessageStreamer(channel, sessionId);
1026
1061
  let currentToolName = null;
1027
1062
  let currentToolInput = "";
@@ -1151,7 +1186,8 @@ ${displayResult}
1151
1186
  const embed = new EmbedBuilder().setColor(isSuccess ? 3066993 : 15158332).setTitle(isSuccess ? "Completed" : "Error").addFields(
1152
1187
  { name: "Cost", value: `$${cost}`, inline: true },
1153
1188
  { name: "Duration", value: duration, inline: true },
1154
- { name: "Turns", value: `${turns}`, inline: true }
1189
+ { name: "Turns", value: `${turns}`, inline: true },
1190
+ { name: "Mode", value: { auto: "\u26A1 Auto", plan: "\u{1F4CB} Plan", normal: "\u{1F6E1}\uFE0F Normal" }[mode] || "\u26A1 Auto", inline: true }
1155
1191
  );
1156
1192
  if (result.session_id) {
1157
1193
  embed.setFooter({ text: `Session: ${result.session_id}` });
@@ -1167,6 +1203,7 @@ ${displayResult}
1167
1203
  } else if (detectYesNoPrompt(checkText)) {
1168
1204
  components.push(makeYesNoButtons(sessionId));
1169
1205
  }
1206
+ components.push(makeModeButtons(sessionId, mode));
1170
1207
  components.push(makeCompletionButtons(sessionId));
1171
1208
  await channel.send({ embeds: [embed], components });
1172
1209
  }
@@ -1337,6 +1374,8 @@ async function handleClaude(interaction) {
1337
1374
  return handleClaudeModel(interaction);
1338
1375
  case "verbose":
1339
1376
  return handleClaudeVerbose(interaction);
1377
+ case "mode":
1378
+ return handleClaudeMode(interaction);
1340
1379
  default:
1341
1380
  await interaction.reply({ content: `Unknown subcommand: ${sub}`, ephemeral: true });
1342
1381
  }
@@ -1560,7 +1599,8 @@ async function handleClaudeList(interaction) {
1560
1599
  for (const [project, projectSessions] of grouped) {
1561
1600
  const lines = projectSessions.map((s) => {
1562
1601
  const status = s.isGenerating ? "\u{1F7E2} generating" : "\u26AA idle";
1563
- return `**${s.id}** \u2014 ${status} | ${formatUptime(s.createdAt)} uptime | ${s.messageCount} msgs | $${s.totalCost.toFixed(4)} | ${formatLastActivity(s.lastActivity)}`;
1602
+ const modeEmoji = { auto: "\u26A1", plan: "\u{1F4CB}", normal: "\u{1F6E1}\uFE0F" }[s.mode] || "\u26A1";
1603
+ return `**${s.id}** \u2014 ${status} ${modeEmoji} ${s.mode} | ${formatUptime(s.createdAt)} uptime | ${s.messageCount} msgs | $${s.totalCost.toFixed(4)} | ${formatLastActivity(s.lastActivity)}`;
1564
1604
  });
1565
1605
  embed.addFields({ name: `\u{1F4C1} ${project}`, value: lines.join("\n") });
1566
1606
  }
@@ -1596,7 +1636,7 @@ async function handleClaudeContinue(interaction) {
1596
1636
  const channel = interaction.channel;
1597
1637
  const stream = continueSession(session.id);
1598
1638
  await interaction.editReply("Continuing...");
1599
- await handleOutputStream(stream, channel, session.id, session.verbose);
1639
+ await handleOutputStream(stream, channel, session.id, session.verbose, session.mode);
1600
1640
  } catch (err) {
1601
1641
  await interaction.editReply(`Error: ${err.message}`);
1602
1642
  }
@@ -1697,6 +1737,24 @@ async function handleClaudeVerbose(interaction) {
1697
1737
  ephemeral: true
1698
1738
  });
1699
1739
  }
1740
+ var MODE_LABELS = {
1741
+ auto: "\u26A1 Auto \u2014 full autonomy, no confirmations",
1742
+ plan: "\u{1F4CB} Plan \u2014 always plans before executing changes",
1743
+ normal: "\u{1F6E1}\uFE0F Normal \u2014 asks before destructive operations"
1744
+ };
1745
+ async function handleClaudeMode(interaction) {
1746
+ const session = getSessionByChannel(interaction.channelId);
1747
+ if (!session) {
1748
+ await interaction.reply({ content: "No session in this channel.", ephemeral: true });
1749
+ return;
1750
+ }
1751
+ const mode = interaction.options.getString("mode", true);
1752
+ setMode(session.id, mode);
1753
+ await interaction.reply({
1754
+ content: `Mode set to **${MODE_LABELS[mode]}**`,
1755
+ ephemeral: true
1756
+ });
1757
+ }
1700
1758
  async function handleShell(interaction) {
1701
1759
  if (!isUserAllowed(interaction.user.id, config.allowedUsers, config.allowAllUsers)) {
1702
1760
  await interaction.reply({ content: "You are not authorized.", ephemeral: true });
@@ -1860,7 +1918,7 @@ ${list}`, ephemeral: true });
1860
1918
  const channel = interaction.channel;
1861
1919
  await interaction.editReply(`Running skill **${name}**...`);
1862
1920
  const stream = sendPrompt(session.id, expanded);
1863
- await handleOutputStream(stream, channel, session.id, session.verbose);
1921
+ await handleOutputStream(stream, channel, session.id, session.verbose, session.mode);
1864
1922
  } catch (err) {
1865
1923
  await interaction.editReply(`Error: ${err.message}`);
1866
1924
  }
@@ -1964,7 +2022,7 @@ async function handleMessage(message) {
1964
2022
  try {
1965
2023
  const channel = message.channel;
1966
2024
  const stream = sendPrompt(session.id, content);
1967
- await handleOutputStream(stream, channel, session.id, session.verbose);
2025
+ await handleOutputStream(stream, channel, session.id, session.verbose, session.mode);
1968
2026
  } catch (err) {
1969
2027
  await message.reply({
1970
2028
  content: `Error: ${err.message}`,
@@ -2005,7 +2063,7 @@ async function handleButton(interaction) {
2005
2063
  const channel = interaction.channel;
2006
2064
  const stream = continueSession(sessionId);
2007
2065
  await interaction.editReply("Continuing...");
2008
- await handleOutputStream(stream, channel, sessionId, session.verbose);
2066
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2009
2067
  } catch (err) {
2010
2068
  await interaction.editReply(`Error: ${err.message}`);
2011
2069
  }
@@ -2039,7 +2097,7 @@ ${display}
2039
2097
  const channel = interaction.channel;
2040
2098
  const stream = sendPrompt(sessionId, optionText);
2041
2099
  await interaction.editReply(`Selected option ${optionIndex + 1}`);
2042
- await handleOutputStream(stream, channel, sessionId, session.verbose);
2100
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2043
2101
  } catch (err) {
2044
2102
  await interaction.editReply(`Error: ${err.message}`);
2045
2103
  }
@@ -2059,7 +2117,7 @@ ${display}
2059
2117
  const channel = interaction.channel;
2060
2118
  const stream = sendPrompt(sessionId, answer);
2061
2119
  await interaction.editReply(`Answered: **${truncate(answer, 100)}**`);
2062
- await handleOutputStream(stream, channel, sessionId, session.verbose);
2120
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2063
2121
  } catch (err) {
2064
2122
  await interaction.editReply(`Error: ${err.message}`);
2065
2123
  }
@@ -2079,12 +2137,45 @@ ${display}
2079
2137
  const channel = interaction.channel;
2080
2138
  const stream = sendPrompt(sessionId, answer);
2081
2139
  await interaction.editReply(`Answered: ${answer}`);
2082
- await handleOutputStream(stream, channel, sessionId, session.verbose);
2140
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2083
2141
  } catch (err) {
2084
2142
  await interaction.editReply(`Error: ${err.message}`);
2085
2143
  }
2086
2144
  return;
2087
2145
  }
2146
+ if (customId.startsWith("mode:")) {
2147
+ const parts = customId.split(":");
2148
+ const sessionId = parts[1];
2149
+ const newMode = parts[2];
2150
+ const session = getSession(sessionId);
2151
+ if (!session) {
2152
+ await interaction.reply({ content: "Session not found.", ephemeral: true });
2153
+ return;
2154
+ }
2155
+ setMode(sessionId, newMode);
2156
+ const labels = {
2157
+ auto: "\u26A1 Auto \u2014 full autonomy",
2158
+ plan: "\u{1F4CB} Plan \u2014 plans before changes",
2159
+ normal: "\u{1F6E1}\uFE0F Normal \u2014 asks before destructive ops"
2160
+ };
2161
+ await interaction.reply({
2162
+ content: `Mode switched to **${labels[newMode]}**`,
2163
+ ephemeral: true
2164
+ });
2165
+ try {
2166
+ const original = interaction.message;
2167
+ const updatedComponents = original.components.map((row) => {
2168
+ const first = row.components?.[0];
2169
+ if (first?.customId?.startsWith("mode:")) {
2170
+ return makeModeButtons(sessionId, newMode);
2171
+ }
2172
+ return row;
2173
+ });
2174
+ await original.edit({ components: updatedComponents });
2175
+ } catch {
2176
+ }
2177
+ return;
2178
+ }
2088
2179
  await interaction.reply({ content: "Unknown button.", ephemeral: true });
2089
2180
  }
2090
2181
  async function handleSelectMenu(interaction) {
@@ -2106,7 +2197,7 @@ async function handleSelectMenu(interaction) {
2106
2197
  const channel = interaction.channel;
2107
2198
  const stream = sendPrompt(sessionId, selected);
2108
2199
  await interaction.editReply(`Answered: **${truncate(selected, 100)}**`);
2109
- await handleOutputStream(stream, channel, sessionId, session.verbose);
2200
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2110
2201
  } catch (err) {
2111
2202
  await interaction.editReply(`Error: ${err.message}`);
2112
2203
  }
@@ -2125,7 +2216,7 @@ async function handleSelectMenu(interaction) {
2125
2216
  const channel = interaction.channel;
2126
2217
  const stream = sendPrompt(sessionId, selected);
2127
2218
  await interaction.editReply(`Selected: ${truncate(selected, 100)}`);
2128
- await handleOutputStream(stream, channel, sessionId, session.verbose);
2219
+ await handleOutputStream(stream, channel, sessionId, session.verbose, session.mode);
2129
2220
  } catch (err) {
2130
2221
  await interaction.editReply(`Error: ${err.message}`);
2131
2222
  }
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-RT4CJNKG.js");
21
+ const { startBot } = await import("./bot-G6464LRS.js");
22
22
  console.log("agentcord starting...");
23
23
  await startBot();
24
24
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentcord",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "Discord bot for managing AI coding agent sessions (Claude Code, Codex, and more)",
6
6
  "bin": {