@friendlyrobot/discord-pi-agent 0.19.14 → 0.19.16

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.
@@ -1,6 +1,10 @@
1
1
  import type { Message } from "discord.js";
2
- export declare function addWorkingReaction(message: Message): Promise<void>;
3
- export declare function removeWorkingReaction(message: Message): Promise<void>;
4
- export declare function sendReply(message: Message, text: string, opts?: {
5
- codeFence?: boolean;
6
- }): Promise<void>;
2
+ export declare const DEFAULT_WORKING_EMOJI = "\u2699\uFE0F";
3
+ export declare function addWorkingReaction(message: Message, emoji?: string): Promise<void>;
4
+ export declare function removeWorkingReaction(message: Message, emoji?: string): Promise<void>;
5
+ export declare function sendReply(message: Message, text: string): Promise<void>;
6
+ /**
7
+ * Sends a command response wrapped in triple backticks.
8
+ * Splits by newlines so each chunk stays within Discord's 2000-char limit.
9
+ */
10
+ export declare function sendCommandReply(message: Message, text: string): Promise<void>;
package/dist/index.js CHANGED
@@ -727,6 +727,7 @@ async function handleHelpCommand(trimmedInput, context) {
727
727
  "!compact - compact the persistent session",
728
728
  "!reset-session - start a fresh persistent session",
729
729
  "!reload - reload resources (AGENTS.md, extensions, skills, etc.)",
730
+ "!reaction - show or set the working reaction emoji",
730
731
  extraCommands,
731
732
  "Any other text goes to the agent session."
732
733
  ].filter(Boolean).join(`
@@ -887,6 +888,32 @@ async function handleResetSessionCommand(trimmedInput, context) {
887
888
  })
888
889
  };
889
890
  }
891
+ async function handleReactionCommand(trimmedInput, _context) {
892
+ if (trimmedInput !== "!reaction" && !trimmedInput.startsWith("!reaction ")) {
893
+ return null;
894
+ }
895
+ const parts = trimmedInput.split(" ");
896
+ if (parts.length === 1) {
897
+ return {
898
+ handled: true,
899
+ response: `Current working reaction: ${_context.workingEmoji}
900
+ ` + `Usage: !reaction <emoji> to change it.
901
+ ` + `Examples: !reaction \uD83D\uDD04 or !reaction ⏳`
902
+ };
903
+ }
904
+ const emoji = parts.slice(1).join(" ").trim();
905
+ if (!emoji) {
906
+ return {
907
+ handled: true,
908
+ response: "Please provide an emoji. Example: !reaction \uD83D\uDD04"
909
+ };
910
+ }
911
+ return {
912
+ handled: true,
913
+ workingEmoji: emoji,
914
+ response: `Working reaction emoji set to ${emoji}`
915
+ };
916
+ }
890
917
  var commandHandlers = [
891
918
  handleHelpCommand,
892
919
  handleArchiveCommand,
@@ -895,7 +922,8 @@ var commandHandlers = [
895
922
  handleModelCommand,
896
923
  handleCompactCommand,
897
924
  handleReloadCommand,
898
- handleResetSessionCommand
925
+ handleResetSessionCommand,
926
+ handleReactionCommand
899
927
  ];
900
928
  async function executeSessionCommand(input, context) {
901
929
  const trimmedInput = input.trim();
@@ -1122,17 +1150,45 @@ function chunkMessage(text, maxChunkSize = SAFE_MESSAGE_LIMIT) {
1122
1150
 
1123
1151
  // src/discord-replies.ts
1124
1152
  var logger7 = createModuleLogger("discord-replies");
1125
- var WORKING_EMOJI = "⚙️";
1126
- async function addWorkingReaction(message) {
1153
+ var DISCORD_MESSAGE_LIMIT2 = 2000;
1154
+ var FENCE_OVERHEAD = 8;
1155
+ var MAX_CODE_FENCE_CONTENT = DISCORD_MESSAGE_LIMIT2 - FENCE_OVERHEAD;
1156
+ function chunkByLines(text, maxSize) {
1157
+ const lines = text.split(`
1158
+ `);
1159
+ const chunks = [];
1160
+ let current = "";
1161
+ for (const line of lines) {
1162
+ const candidate = current ? current + `
1163
+ ` + line : line;
1164
+ if (candidate.length > maxSize) {
1165
+ if (current) {
1166
+ chunks.push(current);
1167
+ current = line;
1168
+ } else {
1169
+ chunks.push(line.slice(0, maxSize));
1170
+ current = line.slice(maxSize);
1171
+ }
1172
+ } else {
1173
+ current = candidate;
1174
+ }
1175
+ }
1176
+ if (current) {
1177
+ chunks.push(current);
1178
+ }
1179
+ return chunks;
1180
+ }
1181
+ var DEFAULT_WORKING_EMOJI = "⚙️";
1182
+ async function addWorkingReaction(message, emoji = DEFAULT_WORKING_EMOJI) {
1127
1183
  try {
1128
- await message.react(WORKING_EMOJI);
1184
+ await message.react(emoji);
1129
1185
  } catch (error) {
1130
1186
  logger7.debug({ messageId: message.id, error }, "failed to add working reaction");
1131
1187
  }
1132
1188
  }
1133
- async function removeWorkingReaction(message) {
1189
+ async function removeWorkingReaction(message, emoji = DEFAULT_WORKING_EMOJI) {
1134
1190
  try {
1135
- const reaction = message.reactions.cache.get(WORKING_EMOJI);
1191
+ const reaction = message.reactions.cache.get(emoji);
1136
1192
  if (reaction) {
1137
1193
  await reaction.users.remove(message.client.user);
1138
1194
  }
@@ -1140,7 +1196,7 @@ async function removeWorkingReaction(message) {
1140
1196
  logger7.debug({ messageId: message.id, error }, "failed to remove working reaction");
1141
1197
  }
1142
1198
  }
1143
- async function sendReply(message, text, opts) {
1199
+ async function sendReply(message, text) {
1144
1200
  const channel = message.channel;
1145
1201
  if (!channel.isSendable()) {
1146
1202
  logger7.debug({
@@ -1148,10 +1204,7 @@ async function sendReply(message, text, opts) {
1148
1204
  }, "reply skipped, channel not sendable");
1149
1205
  return;
1150
1206
  }
1151
- const wrap = opts?.codeFence ? (c) => `\`\`\`
1152
- ${c}
1153
- \`\`\`` : (c) => c;
1154
- const chunks = chunkMessage(text).map(wrap);
1207
+ const chunks = chunkMessage(text);
1155
1208
  const [firstChunk, ...remainingChunks] = chunks;
1156
1209
  if (!firstChunk) {
1157
1210
  return;
@@ -1168,6 +1221,33 @@ ${c}
1168
1221
  }, "send reply failed");
1169
1222
  }
1170
1223
  }
1224
+ async function sendCommandReply(message, text) {
1225
+ const channel = message.channel;
1226
+ if (!channel.isSendable()) {
1227
+ logger7.debug({
1228
+ messageId: message.id
1229
+ }, "command reply skipped, channel not sendable");
1230
+ return;
1231
+ }
1232
+ const chunks = chunkByLines(text, MAX_CODE_FENCE_CONTENT).map((c) => `\`\`\`
1233
+ ${c}
1234
+ \`\`\``);
1235
+ const [firstChunk, ...remainingChunks] = chunks;
1236
+ if (!firstChunk) {
1237
+ return;
1238
+ }
1239
+ try {
1240
+ await message.reply(firstChunk);
1241
+ for (const chunk of remainingChunks) {
1242
+ await channel.send(chunk);
1243
+ }
1244
+ } catch (error) {
1245
+ logger7.error({
1246
+ messageId: message.id,
1247
+ error
1248
+ }, "send command reply failed");
1249
+ }
1250
+ }
1171
1251
 
1172
1252
  // src/media-description.ts
1173
1253
  var logger8 = createModuleLogger("media-description");
@@ -1533,10 +1613,15 @@ ${attachment.content}`;
1533
1613
  const commandResult = await executeSessionCommand(content, {
1534
1614
  agentService,
1535
1615
  promptQueue,
1536
- session
1616
+ session,
1617
+ workingEmoji: entry.workingEmoji
1537
1618
  });
1538
1619
  if (commandResult.handled) {
1539
1620
  stopTypingForChannel(channelKey);
1621
+ if (commandResult.workingEmoji) {
1622
+ entry.workingEmoji = commandResult.workingEmoji;
1623
+ logger11.info({ scope, emoji: commandResult.workingEmoji }, "working emoji updated");
1624
+ }
1540
1625
  if (commandResult.archive && scope.startsWith("thread:")) {
1541
1626
  logger11.info({ scope }, "archiving thread");
1542
1627
  const archiveChannel = message.channel;
@@ -1561,7 +1646,7 @@ ${commandResult.response ?? "Archiving..."}
1561
1646
  hasResponse: Boolean(commandResult.response)
1562
1647
  }, `command handled: ${content}`);
1563
1648
  if (commandResult.response) {
1564
- await sendReply(message, commandResult.response, { codeFence: true });
1649
+ await sendCommandReply(message, commandResult.response);
1565
1650
  }
1566
1651
  return;
1567
1652
  }
@@ -1570,7 +1655,7 @@ ${commandResult.response ?? "Archiving..."}
1570
1655
  logger11.debug({ messageId: message.id }, "channel not sendable");
1571
1656
  return;
1572
1657
  }
1573
- await addWorkingReaction(message);
1658
+ await addWorkingReaction(message, entry.workingEmoji);
1574
1659
  const queuePosition = promptQueue.getSnapshot().pending;
1575
1660
  if (queuePosition > 0) {
1576
1661
  await sendReply(message, `Queued. ${queuePosition} request(s) ahead of this one.`);
@@ -1595,7 +1680,7 @@ ${commandResult.response ?? "Archiving..."}
1595
1680
  });
1596
1681
  } finally {
1597
1682
  stopTypingForChannel(channelKey);
1598
- await removeWorkingReaction(message);
1683
+ await removeWorkingReaction(message, entry.workingEmoji);
1599
1684
  }
1600
1685
  await sendReply(message, response);
1601
1686
  }
@@ -1716,7 +1801,8 @@ class SessionRegistry {
1716
1801
  const entry = {
1717
1802
  session,
1718
1803
  promptQueue,
1719
- createdAt: new Date
1804
+ createdAt: new Date,
1805
+ workingEmoji: DEFAULT_WORKING_EMOJI
1720
1806
  };
1721
1807
  this.scopes.set(scope, entry);
1722
1808
  logger13.debug({
@@ -6,10 +6,14 @@ export type CommandResult = {
6
6
  response?: string;
7
7
  /** Set to true when the command wants to archive the current thread. */
8
8
  archive?: boolean;
9
+ /** When set, update the session's working reaction emoji. */
10
+ workingEmoji?: string;
9
11
  };
10
12
  export type CommandContext = {
11
13
  agentService: AgentService;
12
14
  promptQueue: PromptQueue;
13
15
  session?: AgentSession;
16
+ /** Current working reaction emoji for this session. */
17
+ workingEmoji: string;
14
18
  };
15
19
  export declare function executeSessionCommand(input: string, context: CommandContext): Promise<CommandResult>;
@@ -6,6 +6,7 @@ export type ScopedSessionEntry = {
6
6
  session: AgentSession;
7
7
  promptQueue: PromptQueue;
8
8
  createdAt: Date;
9
+ workingEmoji: string;
9
10
  };
10
11
  /**
11
12
  * Derive a deterministic session directory from a scope key.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@friendlyrobot/discord-pi-agent",
3
- "version": "0.19.14",
3
+ "version": "0.19.16",
4
4
  "description": "Reusable Discord gateway for persistent pi agent sessions",
5
5
  "license": "MIT",
6
6
  "type": "module",