@friendlyrobot/discord-pi-agent 0.22.0 → 0.22.2

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.
@@ -7,6 +7,7 @@ export async function runAgentTurn(session, prompt, options = {}) {
7
7
  let eventCount = 0;
8
8
  let toolCount = 0;
9
9
  const toolInputsByCallId = new Map();
10
+ const pendingToolLifecycleTasks = new Set();
10
11
  const model = session.model
11
12
  ? `${session.model.provider}/${session.model.id}`
12
13
  : "none";
@@ -33,9 +34,13 @@ export async function runAgentTurn(session, prompt, options = {}) {
33
34
  toolCount += 1;
34
35
  const input = event.toolName === "bash" ? event.args.command : event.args;
35
36
  toolInputsByCallId.set(event.toolCallId, input);
36
- void options.onToolStart?.({
37
+ const onToolStartTask = Promise.resolve(options.onToolStart?.({
37
38
  toolName: event.toolName,
38
39
  toolCallId: event.toolCallId,
40
+ })).then(() => undefined);
41
+ pendingToolLifecycleTasks.add(onToolStartTask);
42
+ void onToolStartTask.finally(() => {
43
+ pendingToolLifecycleTasks.delete(onToolStartTask);
39
44
  });
40
45
  if (event.toolName === "bash") {
41
46
  debugPrint(input, "CMD");
@@ -56,10 +61,14 @@ export async function runAgentTurn(session, prompt, options = {}) {
56
61
  if (event.type === "tool_execution_end") {
57
62
  const input = toolInputsByCallId.get(event.toolCallId);
58
63
  toolInputsByCallId.delete(event.toolCallId);
59
- void options.onToolEnd?.({
64
+ const onToolEndTask = Promise.resolve(options.onToolEnd?.({
60
65
  toolName: event.toolName,
61
66
  toolCallId: event.toolCallId,
62
67
  isError: event.isError,
68
+ })).then(() => undefined);
69
+ pendingToolLifecycleTasks.add(onToolEndTask);
70
+ void onToolEndTask.finally(() => {
71
+ pendingToolLifecycleTasks.delete(onToolEndTask);
63
72
  });
64
73
  if (event.toolName === "bash") {
65
74
  debugPrint(extractToolOutput(event.result), event.isError ? "BASH TOOL ERROR OUTPUT" : "BASH TOOL OUTPUT");
@@ -97,6 +106,7 @@ export async function runAgentTurn(session, prompt, options = {}) {
97
106
  });
98
107
  try {
99
108
  await session.prompt(prompt, { images: options.images });
109
+ await Promise.allSettled(Array.from(pendingToolLifecycleTasks));
100
110
  }
101
111
  finally {
102
112
  unsubscribe();
@@ -154,33 +154,40 @@ async function processAgentPrompt(message, config, agentService, entry, prepared
154
154
  await notifyIfPromptQueued(message, entry.promptQueue.getSnapshot().pending);
155
155
  const toolEmojiByCallId = new Map();
156
156
  const activeToolCountByEmoji = new Map();
157
+ let reactionOperationChain = Promise.resolve();
157
158
  try {
158
159
  const response = await entry.promptQueue.enqueue(async () => {
159
160
  const promptInput = await buildPromptInput(message, config, agentService, entry, preparedMessage);
160
161
  return runAgentTurn(entry.session, promptInput.prompt, {
161
162
  images: promptInput.images,
162
163
  onToolStart: async ({ toolName, toolCallId }) => {
163
- const emoji = resolveToolReactionEmoji(toolName);
164
- toolEmojiByCallId.set(toolCallId, emoji);
165
- const activeCount = activeToolCountByEmoji.get(emoji) ?? 0;
166
- activeToolCountByEmoji.set(emoji, activeCount + 1);
167
- if (activeCount === 0) {
168
- await addReaction(message, emoji);
169
- }
164
+ reactionOperationChain = reactionOperationChain.then(async () => {
165
+ const emoji = resolveToolReactionEmoji(toolName);
166
+ toolEmojiByCallId.set(toolCallId, emoji);
167
+ const activeCount = activeToolCountByEmoji.get(emoji) ?? 0;
168
+ activeToolCountByEmoji.set(emoji, activeCount + 1);
169
+ if (activeCount === 0) {
170
+ await addReaction(message, emoji);
171
+ }
172
+ });
173
+ await reactionOperationChain;
170
174
  },
171
175
  onToolEnd: async ({ toolCallId }) => {
172
- const emoji = toolEmojiByCallId.get(toolCallId);
173
- if (!emoji) {
174
- return;
175
- }
176
- toolEmojiByCallId.delete(toolCallId);
177
- const activeCount = activeToolCountByEmoji.get(emoji) ?? 0;
178
- if (activeCount <= 1) {
179
- activeToolCountByEmoji.delete(emoji);
180
- await removeReaction(message, emoji);
181
- return;
182
- }
183
- activeToolCountByEmoji.set(emoji, activeCount - 1);
176
+ reactionOperationChain = reactionOperationChain.then(async () => {
177
+ const emoji = toolEmojiByCallId.get(toolCallId);
178
+ if (!emoji) {
179
+ return;
180
+ }
181
+ toolEmojiByCallId.delete(toolCallId);
182
+ const activeCount = activeToolCountByEmoji.get(emoji) ?? 0;
183
+ if (activeCount <= 1) {
184
+ activeToolCountByEmoji.delete(emoji);
185
+ await removeReaction(message, emoji);
186
+ return;
187
+ }
188
+ activeToolCountByEmoji.set(emoji, activeCount - 1);
189
+ });
190
+ await reactionOperationChain;
184
191
  },
185
192
  });
186
193
  });
@@ -35,6 +35,34 @@ function chunkByLines(text, maxSize) {
35
35
  return chunks;
36
36
  }
37
37
  export const DEFAULT_WORKING_EMOJI = "⚙️";
38
+ function normalizeEmoji(value) {
39
+ return value.normalize("NFKC").replace(/\uFE0F/g, "");
40
+ }
41
+ function findReactionByEmoji(message, emoji) {
42
+ const directMatch = message.reactions.cache.get(emoji);
43
+ if (directMatch) {
44
+ return directMatch;
45
+ }
46
+ const normalizedEmoji = normalizeEmoji(emoji);
47
+ const normalizedMatch = Array.from(message.reactions.cache.entries()).find(([key, reaction]) => {
48
+ if (normalizeEmoji(String(key)) === normalizedEmoji) {
49
+ return true;
50
+ }
51
+ if (typeof reaction.emoji?.name === "string") {
52
+ return normalizeEmoji(reaction.emoji.name) === normalizedEmoji;
53
+ }
54
+ return false;
55
+ })?.[1];
56
+ if (!normalizedMatch) {
57
+ logger.debug({
58
+ messageId: message.id,
59
+ emoji,
60
+ normalizedEmoji,
61
+ reactionKeys: Array.from(message.reactions.cache.keys()),
62
+ }, "reaction not found in cache");
63
+ }
64
+ return normalizedMatch;
65
+ }
38
66
  export async function addReaction(message, emoji) {
39
67
  try {
40
68
  await message.react(emoji);
@@ -45,7 +73,7 @@ export async function addReaction(message, emoji) {
45
73
  }
46
74
  export async function removeReaction(message, emoji) {
47
75
  try {
48
- const reaction = message.reactions.cache.get(emoji);
76
+ const reaction = findReactionByEmoji(message, emoji);
49
77
  if (reaction) {
50
78
  await reaction.users.remove(message.client.user);
51
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@friendlyrobot/discord-pi-agent",
3
- "version": "0.22.0",
3
+ "version": "0.22.2",
4
4
  "description": "Reusable Discord gateway for persistent pi agent sessions",
5
5
  "license": "MIT",
6
6
  "type": "module",