@friendlyrobot/discord-pi-agent 0.21.4 → 0.22.0
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,7 +1,14 @@
|
|
|
1
1
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { ImageContent } from "@earendil-works/pi-ai";
|
|
3
|
+
type ToolLifecycleEvent = {
|
|
4
|
+
toolName: string;
|
|
5
|
+
toolCallId: string;
|
|
6
|
+
isError?: boolean;
|
|
7
|
+
};
|
|
3
8
|
type CollectReplyOptions = {
|
|
4
9
|
images?: ImageContent[];
|
|
10
|
+
onToolStart?: (event: ToolLifecycleEvent) => void | Promise<void>;
|
|
11
|
+
onToolEnd?: (event: ToolLifecycleEvent) => void | Promise<void>;
|
|
5
12
|
};
|
|
6
13
|
export declare function runAgentTurn(session: AgentSession, prompt: string, options?: CollectReplyOptions): Promise<string>;
|
|
7
14
|
export {};
|
|
@@ -33,6 +33,10 @@ export async function runAgentTurn(session, prompt, options = {}) {
|
|
|
33
33
|
toolCount += 1;
|
|
34
34
|
const input = event.toolName === "bash" ? event.args.command : event.args;
|
|
35
35
|
toolInputsByCallId.set(event.toolCallId, input);
|
|
36
|
+
void options.onToolStart?.({
|
|
37
|
+
toolName: event.toolName,
|
|
38
|
+
toolCallId: event.toolCallId,
|
|
39
|
+
});
|
|
36
40
|
if (event.toolName === "bash") {
|
|
37
41
|
debugPrint(input, "CMD");
|
|
38
42
|
// logger.debug(
|
|
@@ -52,6 +56,11 @@ export async function runAgentTurn(session, prompt, options = {}) {
|
|
|
52
56
|
if (event.type === "tool_execution_end") {
|
|
53
57
|
const input = toolInputsByCallId.get(event.toolCallId);
|
|
54
58
|
toolInputsByCallId.delete(event.toolCallId);
|
|
59
|
+
void options.onToolEnd?.({
|
|
60
|
+
toolName: event.toolName,
|
|
61
|
+
toolCallId: event.toolCallId,
|
|
62
|
+
isError: event.isError,
|
|
63
|
+
});
|
|
55
64
|
if (event.toolName === "bash") {
|
|
56
65
|
debugPrint(extractToolOutput(event.result), event.isError ? "BASH TOOL ERROR OUTPUT" : "BASH TOOL OUTPUT");
|
|
57
66
|
// logger.debug(
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { readMediaAttachments, readTextAttachments, } from "./discord-attachments";
|
|
2
2
|
import { isAuthorizedMessage, resolveMessageScope } from "./discord-auth";
|
|
3
3
|
import { resolveMediaAttachmentsForPrompt } from "./discord-media-resolution";
|
|
4
|
-
import { addWorkingReaction, removeWorkingReaction, sendCommandReply, sendReply, } from "./discord-replies";
|
|
4
|
+
import { addReaction, addWorkingReaction, removeReaction, removeWorkingReaction, sendCommandReply, sendReply, } from "./discord-replies";
|
|
5
5
|
import { executeSessionCommand } from "./session-commands";
|
|
6
6
|
import { startTypingForChannel, stopTypingForChannel } from "./discord-typing";
|
|
7
7
|
import { createModuleLogger } from "./logger";
|
|
8
8
|
import { buildDiscordMessageMetadata, formatDiscordPromptTime, wrapXmlTag, } from "./prompt-context";
|
|
9
9
|
import { runAgentTurn } from "./agent-turn-runner";
|
|
10
10
|
const logger = createModuleLogger("discord-message-handler");
|
|
11
|
+
const TOOL_REACTION_EMOJIS = {
|
|
12
|
+
bash: "🖥️",
|
|
13
|
+
edit: "✏️",
|
|
14
|
+
read: "👀",
|
|
15
|
+
write: "📝",
|
|
16
|
+
};
|
|
17
|
+
const DEFAULT_TOOL_REACTION_EMOJI = "🔧";
|
|
11
18
|
export async function handleDiscordMessage(message, config, agentService, sessionRegistry, accessConfig) {
|
|
12
19
|
const preparedMessage = await prepareDiscordMessage(message, accessConfig);
|
|
13
20
|
if (!preparedMessage) {
|
|
@@ -134,6 +141,9 @@ async function archiveThreadSession(message, sessionRegistry, scope, commandResu
|
|
|
134
141
|
}
|
|
135
142
|
await sessionRegistry.remove(scope);
|
|
136
143
|
}
|
|
144
|
+
function resolveToolReactionEmoji(toolName) {
|
|
145
|
+
return TOOL_REACTION_EMOJIS[toolName] ?? DEFAULT_TOOL_REACTION_EMOJI;
|
|
146
|
+
}
|
|
137
147
|
async function processAgentPrompt(message, config, agentService, entry, preparedMessage, channelKey) {
|
|
138
148
|
if (!message.channel.isSendable()) {
|
|
139
149
|
stopTypingForChannel(channelKey);
|
|
@@ -142,17 +152,46 @@ async function processAgentPrompt(message, config, agentService, entry, prepared
|
|
|
142
152
|
}
|
|
143
153
|
await addWorkingReaction(message, entry.workingEmoji);
|
|
144
154
|
await notifyIfPromptQueued(message, entry.promptQueue.getSnapshot().pending);
|
|
155
|
+
const toolEmojiByCallId = new Map();
|
|
156
|
+
const activeToolCountByEmoji = new Map();
|
|
145
157
|
try {
|
|
146
158
|
const response = await entry.promptQueue.enqueue(async () => {
|
|
147
159
|
const promptInput = await buildPromptInput(message, config, agentService, entry, preparedMessage);
|
|
148
160
|
return runAgentTurn(entry.session, promptInput.prompt, {
|
|
149
161
|
images: promptInput.images,
|
|
162
|
+
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
|
+
}
|
|
170
|
+
},
|
|
171
|
+
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);
|
|
184
|
+
},
|
|
150
185
|
});
|
|
151
186
|
});
|
|
152
187
|
await sendReply(message, response);
|
|
153
188
|
}
|
|
154
189
|
finally {
|
|
155
190
|
stopTypingForChannel(channelKey);
|
|
191
|
+
const activeEmojis = Array.from(activeToolCountByEmoji.keys());
|
|
192
|
+
await Promise.all(activeEmojis.map(async (emoji) => {
|
|
193
|
+
await removeReaction(message, emoji);
|
|
194
|
+
}));
|
|
156
195
|
await removeWorkingReaction(message, entry.workingEmoji);
|
|
157
196
|
}
|
|
158
197
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Message } from "discord.js";
|
|
2
2
|
export declare const DEFAULT_WORKING_EMOJI = "\u2699\uFE0F";
|
|
3
|
+
export declare function addReaction(message: Message, emoji: string): Promise<void>;
|
|
4
|
+
export declare function removeReaction(message: Message, emoji: string): Promise<void>;
|
|
3
5
|
export declare function addWorkingReaction(message: Message, emoji?: string): Promise<void>;
|
|
4
6
|
export declare function removeWorkingReaction(message: Message, emoji?: string): Promise<void>;
|
|
5
7
|
export declare function sendReply(message: Message, text: string): Promise<void>;
|
package/dist/discord-replies.js
CHANGED
|
@@ -35,15 +35,15 @@ function chunkByLines(text, maxSize) {
|
|
|
35
35
|
return chunks;
|
|
36
36
|
}
|
|
37
37
|
export const DEFAULT_WORKING_EMOJI = "⚙️";
|
|
38
|
-
export async function
|
|
38
|
+
export async function addReaction(message, emoji) {
|
|
39
39
|
try {
|
|
40
40
|
await message.react(emoji);
|
|
41
41
|
}
|
|
42
42
|
catch (error) {
|
|
43
|
-
logger.debug({ messageId: message.id, error }, "failed to add
|
|
43
|
+
logger.debug({ messageId: message.id, emoji, error }, "failed to add reaction");
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
export async function
|
|
46
|
+
export async function removeReaction(message, emoji) {
|
|
47
47
|
try {
|
|
48
48
|
const reaction = message.reactions.cache.get(emoji);
|
|
49
49
|
if (reaction) {
|
|
@@ -51,9 +51,15 @@ export async function removeWorkingReaction(message, emoji = DEFAULT_WORKING_EMO
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
catch (error) {
|
|
54
|
-
logger.debug({ messageId: message.id, error }, "failed to remove
|
|
54
|
+
logger.debug({ messageId: message.id, emoji, error }, "failed to remove reaction");
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
+
export async function addWorkingReaction(message, emoji = DEFAULT_WORKING_EMOJI) {
|
|
58
|
+
await addReaction(message, emoji);
|
|
59
|
+
}
|
|
60
|
+
export async function removeWorkingReaction(message, emoji = DEFAULT_WORKING_EMOJI) {
|
|
61
|
+
await removeReaction(message, emoji);
|
|
62
|
+
}
|
|
57
63
|
export async function sendReply(message, text) {
|
|
58
64
|
const channel = message.channel;
|
|
59
65
|
if (!channel.isSendable()) {
|