@friendlyrobot/discord-pi-agent 0.6.0 → 0.7.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.
- package/README.md +32 -0
- package/dist/index.js +181 -93
- package/dist/logger.d.ts +11 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -96,6 +96,38 @@ The initial post body becomes the first prompt. Sessions survive restarts.
|
|
|
96
96
|
- `startupMessage` default: `Bot is online and ready.`
|
|
97
97
|
- `shutdownOnSignals` default: `true`
|
|
98
98
|
|
|
99
|
+
### Logging
|
|
100
|
+
|
|
101
|
+
The package uses `pino` for structured logs.
|
|
102
|
+
|
|
103
|
+
Behavior:
|
|
104
|
+
|
|
105
|
+
- when stdout is a TTY, logs use `pino-pretty` for readable local console output
|
|
106
|
+
- when stdout is not a TTY, logs stay as JSON
|
|
107
|
+
|
|
108
|
+
Log level env vars:
|
|
109
|
+
|
|
110
|
+
- `DISCORD_PI_AGENT_LOG_LEVEL`
|
|
111
|
+
- `LOG_LEVEL` fallback
|
|
112
|
+
|
|
113
|
+
Default level is `info`.
|
|
114
|
+
|
|
115
|
+
For detailed prompt and tool monitoring during local runs, use:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
DISCORD_PI_AGENT_LOG_LEVEL=debug
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Pretty console logs use:
|
|
122
|
+
|
|
123
|
+
- colors
|
|
124
|
+
- local timestamp (`SYS:standard`)
|
|
125
|
+
- level first
|
|
126
|
+
- hidden `pid` and `hostname`
|
|
127
|
+
- module-aware labels like `[discord-gateway]`
|
|
128
|
+
- direction markers like `IN` and `OUT`
|
|
129
|
+
- multi-line payload blocks for easier input/output inspection
|
|
130
|
+
|
|
99
131
|
### Forum channel options
|
|
100
132
|
|
|
101
133
|
- `discordAllowedForumChannelIds` — string array of forum channel IDs to respond in
|
package/dist/index.js
CHANGED
|
@@ -13,8 +13,46 @@ import {
|
|
|
13
13
|
SettingsManager
|
|
14
14
|
} from "@mariozechner/pi-coding-agent";
|
|
15
15
|
|
|
16
|
+
// src/logger.ts
|
|
17
|
+
import pino from "pino";
|
|
18
|
+
var loggerLevel = process.env.DISCORD_PI_AGENT_LOG_LEVEL || process.env.LOG_LEVEL || "info";
|
|
19
|
+
var usePrettyTransport = process.stdout.isTTY;
|
|
20
|
+
var baseOptions = {
|
|
21
|
+
level: loggerLevel
|
|
22
|
+
};
|
|
23
|
+
var logger = usePrettyTransport ? pino({
|
|
24
|
+
...baseOptions,
|
|
25
|
+
transport: {
|
|
26
|
+
target: "pino-pretty",
|
|
27
|
+
options: {
|
|
28
|
+
colorize: true,
|
|
29
|
+
colorizeObjects: true,
|
|
30
|
+
levelFirst: true,
|
|
31
|
+
translateTime: "SYS:standard",
|
|
32
|
+
ignore: "pid,hostname",
|
|
33
|
+
singleLine: false,
|
|
34
|
+
messageFormat: "[{module}] {if direction}{direction} {end}{msg}"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}) : pino(baseOptions);
|
|
38
|
+
function createModuleLogger(moduleName) {
|
|
39
|
+
return logger.child({ module: moduleName });
|
|
40
|
+
}
|
|
41
|
+
function logPayload(targetLogger, options) {
|
|
42
|
+
targetLogger.debug({
|
|
43
|
+
...options.context,
|
|
44
|
+
direction: options.direction,
|
|
45
|
+
payloadLength: options.content.length
|
|
46
|
+
}, formatPayloadBlock(options.label, options.content));
|
|
47
|
+
}
|
|
48
|
+
function formatPayloadBlock(label, content) {
|
|
49
|
+
return [label, "----- BEGIN -----", content, "----- END -----"].join(`
|
|
50
|
+
`);
|
|
51
|
+
}
|
|
52
|
+
|
|
16
53
|
// src/markdown-table-transformer.ts
|
|
17
54
|
import { Lexer } from "marked";
|
|
55
|
+
var logger2 = createModuleLogger("markdown-table-transformer");
|
|
18
56
|
var CODE_BLOCK_WRAPPER = "```\n{TABLE}\n```";
|
|
19
57
|
async function transformMarkdownTablesToCodeBlocks(text) {
|
|
20
58
|
const normalized = normalizeCodeFences(text);
|
|
@@ -58,19 +96,30 @@ async function formatWithPrettier(text) {
|
|
|
58
96
|
});
|
|
59
97
|
return formatted.trim();
|
|
60
98
|
} catch (error) {
|
|
61
|
-
|
|
99
|
+
logger2.error({
|
|
100
|
+
error
|
|
101
|
+
}, "Prettier formatting failed");
|
|
62
102
|
return text;
|
|
63
103
|
}
|
|
64
104
|
}
|
|
65
105
|
|
|
66
106
|
// src/reply-buffer.ts
|
|
107
|
+
var logger3 = createModuleLogger("reply-buffer");
|
|
67
108
|
async function collectReply(session, prompt, options = {}) {
|
|
68
109
|
const logPrefix = options.logPrefix ?? "[agent]";
|
|
69
110
|
let streamedText = "";
|
|
70
111
|
let eventCount = 0;
|
|
71
112
|
let toolCount = 0;
|
|
72
113
|
let sawAgentEnd = false;
|
|
73
|
-
|
|
114
|
+
logger3.debug({ logPrefix }, "prompt start");
|
|
115
|
+
logPayload(logger3, {
|
|
116
|
+
direction: "IN",
|
|
117
|
+
label: `${logPrefix} prompt content`,
|
|
118
|
+
content: prompt,
|
|
119
|
+
context: {
|
|
120
|
+
logPrefix
|
|
121
|
+
}
|
|
122
|
+
});
|
|
74
123
|
const unsubscribe = session.subscribe((event) => {
|
|
75
124
|
eventCount += 1;
|
|
76
125
|
if (event.type === "message_update") {
|
|
@@ -81,23 +130,26 @@ async function collectReply(session, prompt, options = {}) {
|
|
|
81
130
|
}
|
|
82
131
|
if (event.type === "tool_execution_start") {
|
|
83
132
|
toolCount += 1;
|
|
84
|
-
|
|
133
|
+
logger3.debug({
|
|
85
134
|
toolName: event.toolName,
|
|
86
|
-
input: truncateForLog(JSON.stringify(event.args))
|
|
87
|
-
|
|
135
|
+
input: truncateForLog(JSON.stringify(event.args)),
|
|
136
|
+
logPrefix
|
|
137
|
+
}, "tool start");
|
|
88
138
|
}
|
|
89
139
|
if (event.type === "tool_execution_end") {
|
|
90
|
-
|
|
140
|
+
logger3.debug({
|
|
91
141
|
toolName: event.toolName,
|
|
92
142
|
isError: event.isError,
|
|
93
|
-
output: truncateForLog(extractToolOutput(event.result))
|
|
94
|
-
|
|
143
|
+
output: truncateForLog(extractToolOutput(event.result)),
|
|
144
|
+
logPrefix
|
|
145
|
+
}, "tool end");
|
|
95
146
|
}
|
|
96
147
|
if (event.type === "agent_end") {
|
|
97
148
|
sawAgentEnd = true;
|
|
98
|
-
|
|
99
|
-
messageCount: event.messages.length
|
|
100
|
-
|
|
149
|
+
logger3.debug({
|
|
150
|
+
messageCount: event.messages.length,
|
|
151
|
+
logPrefix
|
|
152
|
+
}, "agent end");
|
|
101
153
|
}
|
|
102
154
|
});
|
|
103
155
|
try {
|
|
@@ -108,25 +160,36 @@ async function collectReply(session, prompt, options = {}) {
|
|
|
108
160
|
const errorMessage = session.agent.state.errorMessage?.trim();
|
|
109
161
|
const fallbackText = getLatestAssistantText(session.messages);
|
|
110
162
|
const finalText = streamedText.trim() || fallbackText.trim();
|
|
111
|
-
|
|
163
|
+
logger3.debug({
|
|
112
164
|
eventCount,
|
|
113
165
|
toolCount,
|
|
114
166
|
sawAgentEnd,
|
|
115
167
|
streamedTextLength: streamedText.trim().length,
|
|
116
168
|
fallbackTextLength: fallbackText.trim().length,
|
|
117
|
-
errorMessage
|
|
118
|
-
|
|
169
|
+
errorMessage,
|
|
170
|
+
logPrefix
|
|
171
|
+
}, "prompt done");
|
|
119
172
|
if (errorMessage) {
|
|
120
173
|
return errorMessage;
|
|
121
174
|
}
|
|
122
175
|
if (finalText) {
|
|
123
176
|
const transformed = await transformMarkdownTablesToCodeBlocks(finalText);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
177
|
+
logPayload(logger3, {
|
|
178
|
+
direction: "OUT",
|
|
179
|
+
label: `${logPrefix} assistant final text`,
|
|
180
|
+
content: finalText,
|
|
181
|
+
context: {
|
|
182
|
+
logPrefix
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
logPayload(logger3, {
|
|
186
|
+
direction: "OUT",
|
|
187
|
+
label: `${logPrefix} assistant transformed text`,
|
|
188
|
+
content: transformed,
|
|
189
|
+
context: {
|
|
190
|
+
logPrefix
|
|
191
|
+
}
|
|
192
|
+
});
|
|
130
193
|
return transformed;
|
|
131
194
|
}
|
|
132
195
|
return "No response generated.";
|
|
@@ -163,6 +226,8 @@ function getLatestAssistantText(messages) {
|
|
|
163
226
|
}
|
|
164
227
|
|
|
165
228
|
// src/agent-service.ts
|
|
229
|
+
var logger4 = createModuleLogger("agent-service");
|
|
230
|
+
|
|
166
231
|
class AgentService {
|
|
167
232
|
config;
|
|
168
233
|
authStorage;
|
|
@@ -184,19 +249,19 @@ class AgentService {
|
|
|
184
249
|
async initialize() {
|
|
185
250
|
await fs.mkdir(this.config.agentDir, { recursive: true });
|
|
186
251
|
await fs.mkdir(this.getSessionDir(), { recursive: true });
|
|
187
|
-
|
|
252
|
+
logger4.info({
|
|
188
253
|
cwd: this.config.cwd,
|
|
189
254
|
agentDir: this.config.agentDir,
|
|
190
255
|
sessionDir: this.getSessionDir(),
|
|
191
256
|
modelProvider: this.config.modelProvider,
|
|
192
257
|
modelId: this.config.modelId,
|
|
193
258
|
thinkingLevel: this.config.thinkingLevel
|
|
194
|
-
});
|
|
259
|
+
}, "config");
|
|
195
260
|
await this.resourceLoader.reload();
|
|
196
|
-
|
|
261
|
+
logger4.info({
|
|
197
262
|
extensions: this.resourceLoader.getExtensions().extensions.map((extension) => extension.path),
|
|
198
263
|
agentsFiles: this.resourceLoader.getAgentsFiles().agentsFiles.map((file) => file.path)
|
|
199
|
-
});
|
|
264
|
+
}, "resources loaded");
|
|
200
265
|
await this.createOrResumeSession();
|
|
201
266
|
await this.ensureConfiguredModel();
|
|
202
267
|
}
|
|
@@ -218,11 +283,11 @@ class AgentService {
|
|
|
218
283
|
sessionManager: SessionManager.continueRecent(this.config.cwd, sessionDir),
|
|
219
284
|
thinkingLevel: this.config.thinkingLevel
|
|
220
285
|
});
|
|
221
|
-
|
|
286
|
+
logger4.info({
|
|
222
287
|
sessionDir,
|
|
223
288
|
sessionId: session.sessionId,
|
|
224
289
|
sessionFile: session.sessionFile
|
|
225
|
-
});
|
|
290
|
+
}, "scoped session created");
|
|
226
291
|
await this.ensureModelForSession(session);
|
|
227
292
|
return session;
|
|
228
293
|
}
|
|
@@ -302,11 +367,11 @@ class AgentService {
|
|
|
302
367
|
thinkingLevel: this.config.thinkingLevel
|
|
303
368
|
});
|
|
304
369
|
this.session = session;
|
|
305
|
-
|
|
370
|
+
logger4.info({
|
|
306
371
|
sessionId: session.sessionId,
|
|
307
372
|
sessionFile: session.sessionFile,
|
|
308
373
|
restoredModel: session.model ? `${session.model.provider}/${session.model.id}` : null
|
|
309
|
-
});
|
|
374
|
+
}, "session ready");
|
|
310
375
|
}
|
|
311
376
|
async ensureConfiguredModel() {
|
|
312
377
|
await this.ensureModelForSession(this.requireSession());
|
|
@@ -314,25 +379,25 @@ class AgentService {
|
|
|
314
379
|
async ensureModelForSession(session) {
|
|
315
380
|
const desiredModel = this.modelRegistry.find(this.config.modelProvider, this.config.modelId);
|
|
316
381
|
const availableModels = await this.modelRegistry.getAvailable();
|
|
317
|
-
|
|
382
|
+
logger4.debug({
|
|
318
383
|
count: availableModels.length,
|
|
319
384
|
matches: availableModels.filter((model) => {
|
|
320
385
|
return model.provider === this.config.modelProvider;
|
|
321
386
|
}).map((model) => `${model.provider}/${model.id}`)
|
|
322
|
-
});
|
|
387
|
+
}, "available models");
|
|
323
388
|
if (!desiredModel) {
|
|
324
389
|
throw new Error(`Configured model not found: ${this.config.modelProvider}/${this.config.modelId}. Check your pi agent config and installed extensions.`);
|
|
325
390
|
}
|
|
326
391
|
if (isSameModel(session.model, desiredModel)) {
|
|
327
|
-
|
|
392
|
+
logger4.info({
|
|
328
393
|
model: `${desiredModel.provider}/${desiredModel.id}`
|
|
329
|
-
});
|
|
394
|
+
}, "model already selected");
|
|
330
395
|
return;
|
|
331
396
|
}
|
|
332
|
-
|
|
397
|
+
logger4.info({
|
|
333
398
|
from: session.model ? `${session.model.provider}/${session.model.id}` : null,
|
|
334
399
|
to: `${desiredModel.provider}/${desiredModel.id}`
|
|
335
|
-
});
|
|
400
|
+
}, "switching model");
|
|
336
401
|
await session.setModel(desiredModel);
|
|
337
402
|
await this.applyConfiguredThinkingLevelForSession(session);
|
|
338
403
|
}
|
|
@@ -350,14 +415,14 @@ class AgentService {
|
|
|
350
415
|
const available = session.getAvailableThinkingLevels();
|
|
351
416
|
if (available.includes(this.config.thinkingLevel)) {
|
|
352
417
|
session.setThinkingLevel(this.config.thinkingLevel);
|
|
353
|
-
|
|
418
|
+
logger4.info({
|
|
354
419
|
level: this.config.thinkingLevel
|
|
355
|
-
});
|
|
420
|
+
}, "thinking level applied");
|
|
356
421
|
} else {
|
|
357
|
-
|
|
422
|
+
logger4.debug({
|
|
358
423
|
requested: this.config.thinkingLevel,
|
|
359
424
|
available
|
|
360
|
-
});
|
|
425
|
+
}, "thinking level not available for model");
|
|
361
426
|
}
|
|
362
427
|
}
|
|
363
428
|
}
|
|
@@ -855,6 +920,7 @@ function normalizeContextValue(value) {
|
|
|
855
920
|
}
|
|
856
921
|
|
|
857
922
|
// src/discord-gateway-client.ts
|
|
923
|
+
var logger5 = createModuleLogger("discord-gateway");
|
|
858
924
|
function getAuthorDisplayName(message) {
|
|
859
925
|
return message.member?.displayName || message.author.globalName || message.author.username;
|
|
860
926
|
}
|
|
@@ -916,17 +982,18 @@ function stopTypingInterval(interval) {
|
|
|
916
982
|
async function sendReply(message, text) {
|
|
917
983
|
const channel = message.channel;
|
|
918
984
|
if (!channel.isSendable()) {
|
|
919
|
-
|
|
985
|
+
logger5.debug({
|
|
920
986
|
messageId: message.id
|
|
921
|
-
});
|
|
987
|
+
}, "reply skipped, channel not sendable");
|
|
922
988
|
return;
|
|
923
989
|
}
|
|
924
990
|
const chunks = chunkMessage(text);
|
|
925
|
-
|
|
991
|
+
logger5.info({
|
|
992
|
+
direction: "OUT",
|
|
926
993
|
messageId: message.id,
|
|
927
994
|
chunkCount: chunks.length,
|
|
928
995
|
textLength: text.length
|
|
929
|
-
});
|
|
996
|
+
}, "sending reply");
|
|
930
997
|
const [firstChunk, ...remainingChunks] = chunks;
|
|
931
998
|
if (!firstChunk) {
|
|
932
999
|
return;
|
|
@@ -937,10 +1004,10 @@ async function sendReply(message, text) {
|
|
|
937
1004
|
await channel.send(chunk);
|
|
938
1005
|
}
|
|
939
1006
|
} catch (error) {
|
|
940
|
-
|
|
1007
|
+
logger5.error({
|
|
941
1008
|
messageId: message.id,
|
|
942
1009
|
error
|
|
943
|
-
});
|
|
1010
|
+
}, "send reply failed");
|
|
944
1011
|
}
|
|
945
1012
|
}
|
|
946
1013
|
async function startGatewayClient(config, agentService, sessionRegistry, authConfig) {
|
|
@@ -954,7 +1021,7 @@ async function startGatewayClient(config, agentService, sessionRegistry, authCon
|
|
|
954
1021
|
partials: [Partials.Channel]
|
|
955
1022
|
});
|
|
956
1023
|
client.once(Events.ClientReady, async (readyClient) => {
|
|
957
|
-
|
|
1024
|
+
logger5.info({ userTag: readyClient.user.tag }, "logged in");
|
|
958
1025
|
if (!authConfig.startupMessage) {
|
|
959
1026
|
return;
|
|
960
1027
|
}
|
|
@@ -962,43 +1029,40 @@ async function startGatewayClient(config, agentService, sessionRegistry, authCon
|
|
|
962
1029
|
const user = await readyClient.users.fetch(authConfig.discordAllowedUserId);
|
|
963
1030
|
const dmChannel = await user.createDM();
|
|
964
1031
|
await dmChannel.send(authConfig.startupMessage);
|
|
965
|
-
|
|
1032
|
+
logger5.info({
|
|
966
1033
|
userId: authConfig.discordAllowedUserId
|
|
967
|
-
});
|
|
1034
|
+
}, "sent startup dm");
|
|
968
1035
|
} catch (error) {
|
|
969
|
-
|
|
1036
|
+
logger5.error({ error }, "failed to send startup dm");
|
|
970
1037
|
}
|
|
971
1038
|
});
|
|
972
1039
|
client.on(Events.MessageCreate, async (message) => {
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
messageId: message.id,
|
|
976
|
-
authorId: message.author.id,
|
|
977
|
-
authorTag: message.author.tag,
|
|
978
|
-
channelId: message.channel.id,
|
|
979
|
-
channelType: message.channel.type,
|
|
980
|
-
parentId: message.channel.parentId,
|
|
981
|
-
parentType: message.channel.parent?.type,
|
|
982
|
-
guildId: message.guild?.id,
|
|
983
|
-
content: message.content.slice(0, 500)
|
|
984
|
-
});
|
|
985
|
-
}
|
|
986
|
-
console.log("[gateway] message received", {
|
|
1040
|
+
logger5.info({
|
|
1041
|
+
direction: "IN",
|
|
987
1042
|
messageId: message.id,
|
|
988
1043
|
authorId: message.author.id,
|
|
989
1044
|
channelType: message.channel.type,
|
|
990
|
-
|
|
1045
|
+
preview: message.content.slice(0, 200)
|
|
1046
|
+
}, "message received");
|
|
1047
|
+
logPayload(logger5, {
|
|
1048
|
+
direction: "IN",
|
|
1049
|
+
label: "gateway message content",
|
|
1050
|
+
content: message.content,
|
|
1051
|
+
context: {
|
|
1052
|
+
messageId: message.id,
|
|
1053
|
+
authorId: message.author.id
|
|
1054
|
+
}
|
|
991
1055
|
});
|
|
992
1056
|
try {
|
|
993
1057
|
await onMessage(message, config, agentService, sessionRegistry, authConfig);
|
|
994
1058
|
} catch (error) {
|
|
995
|
-
|
|
1059
|
+
logger5.error({ error, direction: "IN" }, "message handling failed");
|
|
996
1060
|
await sendReply(message, "The bot hit an error while handling that message.");
|
|
997
1061
|
}
|
|
998
1062
|
});
|
|
999
1063
|
client.on(Events.ThreadDelete, async (thread) => {
|
|
1000
1064
|
const scope = `thread:${thread.id}`;
|
|
1001
|
-
|
|
1065
|
+
logger5.info({ threadId: thread.id, scope }, "thread deleted");
|
|
1002
1066
|
await sessionRegistry.remove(scope);
|
|
1003
1067
|
});
|
|
1004
1068
|
await client.login(config.discordBotToken);
|
|
@@ -1006,41 +1070,41 @@ async function startGatewayClient(config, agentService, sessionRegistry, authCon
|
|
|
1006
1070
|
}
|
|
1007
1071
|
async function onMessage(message, config, agentService, sessionRegistry, authConfig) {
|
|
1008
1072
|
if (message.author.bot) {
|
|
1009
|
-
|
|
1073
|
+
logger5.debug({ messageId: message.id }, "ignored bot message");
|
|
1010
1074
|
return;
|
|
1011
1075
|
}
|
|
1012
1076
|
if (message.system) {
|
|
1013
|
-
|
|
1077
|
+
logger5.debug({ messageId: message.id }, "ignored system message");
|
|
1014
1078
|
return;
|
|
1015
1079
|
}
|
|
1016
1080
|
const scope = resolveScope(message);
|
|
1017
1081
|
if (scope === null) {
|
|
1018
|
-
|
|
1082
|
+
logger5.debug({
|
|
1019
1083
|
messageId: message.id,
|
|
1020
1084
|
channelType: message.channel.type
|
|
1021
|
-
});
|
|
1085
|
+
}, "unsupported channel type, ignoring");
|
|
1022
1086
|
return;
|
|
1023
1087
|
}
|
|
1024
1088
|
if (!isAuthorized(message, scope, authConfig)) {
|
|
1025
|
-
|
|
1089
|
+
logger5.debug({
|
|
1026
1090
|
messageId: message.id,
|
|
1027
1091
|
authorId: message.author.id,
|
|
1028
1092
|
scope
|
|
1029
|
-
});
|
|
1093
|
+
}, "unauthorized");
|
|
1030
1094
|
return;
|
|
1031
1095
|
}
|
|
1032
1096
|
const content = message.content.trim();
|
|
1033
1097
|
if (!content) {
|
|
1034
|
-
|
|
1098
|
+
logger5.debug({ messageId: message.id }, "ignored empty message");
|
|
1035
1099
|
return;
|
|
1036
1100
|
}
|
|
1037
1101
|
const { entry, created } = await sessionRegistry.getOrCreate(scope);
|
|
1038
1102
|
const { session, promptQueue } = entry;
|
|
1039
1103
|
if (created && scope.startsWith("thread:") && message.channel.isThread()) {
|
|
1040
|
-
|
|
1104
|
+
logger5.info({
|
|
1041
1105
|
scope,
|
|
1042
1106
|
threadName: message.channel.name
|
|
1043
|
-
});
|
|
1107
|
+
}, "new thread session");
|
|
1044
1108
|
}
|
|
1045
1109
|
let typingInterval = null;
|
|
1046
1110
|
if (message.channel.isSendable()) {
|
|
@@ -1054,7 +1118,7 @@ async function onMessage(message, config, agentService, sessionRegistry, authCon
|
|
|
1054
1118
|
if (commandResult.handled) {
|
|
1055
1119
|
stopTypingInterval(typingInterval);
|
|
1056
1120
|
if (commandResult.archive && scope.startsWith("thread:")) {
|
|
1057
|
-
|
|
1121
|
+
logger5.info({ scope }, "archiving thread");
|
|
1058
1122
|
const archiveChannel = message.channel;
|
|
1059
1123
|
if (archiveChannel.isSendable()) {
|
|
1060
1124
|
await archiveChannel.send(commandResult.response ?? "Archiving...");
|
|
@@ -1064,16 +1128,16 @@ async function onMessage(message, config, agentService, sessionRegistry, authCon
|
|
|
1064
1128
|
await archiveChannel.setArchived(true);
|
|
1065
1129
|
}
|
|
1066
1130
|
} catch (error) {
|
|
1067
|
-
|
|
1131
|
+
logger5.error({ error }, "failed to archive thread");
|
|
1068
1132
|
}
|
|
1069
1133
|
await sessionRegistry.remove(scope);
|
|
1070
1134
|
return;
|
|
1071
1135
|
}
|
|
1072
|
-
|
|
1136
|
+
logger5.info({
|
|
1073
1137
|
messageId: message.id,
|
|
1074
1138
|
command: content,
|
|
1075
1139
|
hasResponse: Boolean(commandResult.response)
|
|
1076
|
-
});
|
|
1140
|
+
}, "command handled");
|
|
1077
1141
|
if (commandResult.response) {
|
|
1078
1142
|
await sendReply(message, commandResult.response);
|
|
1079
1143
|
}
|
|
@@ -1081,32 +1145,54 @@ async function onMessage(message, config, agentService, sessionRegistry, authCon
|
|
|
1081
1145
|
}
|
|
1082
1146
|
if (!message.channel.isSendable()) {
|
|
1083
1147
|
stopTypingInterval(typingInterval);
|
|
1084
|
-
|
|
1148
|
+
logger5.debug({ messageId: message.id }, "channel not sendable");
|
|
1085
1149
|
return;
|
|
1086
1150
|
}
|
|
1087
1151
|
const queuePosition = promptQueue.getSnapshot().pending;
|
|
1088
|
-
|
|
1152
|
+
logger5.debug({
|
|
1089
1153
|
scope,
|
|
1090
1154
|
messageId: message.id,
|
|
1091
1155
|
queuePosition
|
|
1092
|
-
});
|
|
1156
|
+
}, "enqueue request");
|
|
1093
1157
|
if (queuePosition > 0) {
|
|
1094
1158
|
await sendReply(message, `Queued. ${queuePosition} request(s) ahead of this one.`);
|
|
1095
1159
|
}
|
|
1096
1160
|
const response = await promptQueue.enqueue(async () => {
|
|
1097
|
-
|
|
1161
|
+
logger5.debug({
|
|
1162
|
+
messageId: message.id,
|
|
1163
|
+
scope
|
|
1164
|
+
}, "processing message");
|
|
1098
1165
|
const promptContent = buildDiscordPromptContent(message, scope, content, config);
|
|
1166
|
+
logPayload(logger5, {
|
|
1167
|
+
direction: "IN",
|
|
1168
|
+
label: "gateway prompt content",
|
|
1169
|
+
content: promptContent,
|
|
1170
|
+
context: {
|
|
1171
|
+
scope,
|
|
1172
|
+
messageId: message.id
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1099
1175
|
const transformedPrompt = await config.promptTransform(promptContent);
|
|
1100
1176
|
return collectReply(session, transformedPrompt, {
|
|
1101
1177
|
logPrefix: `[agent:${session.sessionId}]`
|
|
1102
1178
|
});
|
|
1103
1179
|
});
|
|
1104
1180
|
stopTypingInterval(typingInterval);
|
|
1105
|
-
|
|
1181
|
+
logger5.info({
|
|
1182
|
+
direction: "OUT",
|
|
1106
1183
|
scope,
|
|
1107
1184
|
messageId: message.id,
|
|
1108
1185
|
responseLength: response.length,
|
|
1109
1186
|
preview: response.slice(0, 200)
|
|
1187
|
+
}, "response ready");
|
|
1188
|
+
logPayload(logger5, {
|
|
1189
|
+
direction: "OUT",
|
|
1190
|
+
label: "gateway response content",
|
|
1191
|
+
content: response,
|
|
1192
|
+
context: {
|
|
1193
|
+
scope,
|
|
1194
|
+
messageId: message.id
|
|
1195
|
+
}
|
|
1110
1196
|
});
|
|
1111
1197
|
await sendReply(message, response);
|
|
1112
1198
|
}
|
|
@@ -1163,6 +1249,7 @@ function sessionDirForScope(agentDir, scope) {
|
|
|
1163
1249
|
}
|
|
1164
1250
|
throw new Error(`Unknown session scope: ${scope}`);
|
|
1165
1251
|
}
|
|
1252
|
+
var logger6 = createModuleLogger("session-registry");
|
|
1166
1253
|
|
|
1167
1254
|
class SessionRegistry {
|
|
1168
1255
|
scopes = new Map;
|
|
@@ -1184,11 +1271,11 @@ class SessionRegistry {
|
|
|
1184
1271
|
createdAt: new Date
|
|
1185
1272
|
};
|
|
1186
1273
|
this.scopes.set(scope, entry);
|
|
1187
|
-
|
|
1274
|
+
logger6.info({
|
|
1188
1275
|
scope,
|
|
1189
1276
|
sessionDir,
|
|
1190
1277
|
sessionId: session.sessionId
|
|
1191
|
-
});
|
|
1278
|
+
}, "scope registered");
|
|
1192
1279
|
return { entry, created: true };
|
|
1193
1280
|
}
|
|
1194
1281
|
async remove(scope) {
|
|
@@ -1196,7 +1283,7 @@ class SessionRegistry {
|
|
|
1196
1283
|
if (!entry) {
|
|
1197
1284
|
return;
|
|
1198
1285
|
}
|
|
1199
|
-
|
|
1286
|
+
logger6.info({ scope }, "removing scope");
|
|
1200
1287
|
await entry.session.abort();
|
|
1201
1288
|
entry.session.dispose();
|
|
1202
1289
|
this.scopes.delete(scope);
|
|
@@ -1208,7 +1295,7 @@ class SessionRegistry {
|
|
|
1208
1295
|
return Array.from(this.scopes.keys());
|
|
1209
1296
|
}
|
|
1210
1297
|
async shutdownAll() {
|
|
1211
|
-
|
|
1298
|
+
logger6.info({ count: this.scopes.size }, "shutting down all scopes");
|
|
1212
1299
|
const scopes = Array.from(this.scopes.keys());
|
|
1213
1300
|
for (const scope of scopes) {
|
|
1214
1301
|
await this.remove(scope);
|
|
@@ -1217,12 +1304,13 @@ class SessionRegistry {
|
|
|
1217
1304
|
}
|
|
1218
1305
|
|
|
1219
1306
|
// src/index.ts
|
|
1307
|
+
var logger7 = createModuleLogger("index");
|
|
1220
1308
|
async function startDiscordGateway(config) {
|
|
1221
1309
|
const resolvedConfig = resolveGatewayConfig(config);
|
|
1222
1310
|
const agentService = new AgentService(resolvedConfig);
|
|
1223
|
-
|
|
1311
|
+
logger7.info("initializing agent service");
|
|
1224
1312
|
await agentService.initialize();
|
|
1225
|
-
|
|
1313
|
+
logger7.info(agentService.getStatus(), "agent ready");
|
|
1226
1314
|
const authConfig = {
|
|
1227
1315
|
discordAllowedUserId: resolvedConfig.discordAllowedUserId,
|
|
1228
1316
|
discordAllowedForumChannelIds: resolvedConfig.discordAllowedForumChannelIds,
|
|
@@ -1253,10 +1341,10 @@ function createGatewayStopHandler(client, agentService, sessionRegistry, config)
|
|
|
1253
1341
|
return;
|
|
1254
1342
|
}
|
|
1255
1343
|
stopped = true;
|
|
1256
|
-
|
|
1344
|
+
logger7.info({
|
|
1257
1345
|
cwd: config.cwd,
|
|
1258
1346
|
agentDir: config.agentDir
|
|
1259
|
-
});
|
|
1347
|
+
}, "stopping discord gateway");
|
|
1260
1348
|
client.destroy();
|
|
1261
1349
|
await sessionRegistry.shutdownAll();
|
|
1262
1350
|
await agentService.shutdown();
|
|
@@ -1264,9 +1352,9 @@ function createGatewayStopHandler(client, agentService, sessionRegistry, config)
|
|
|
1264
1352
|
}
|
|
1265
1353
|
function registerSignalHandlers(stop) {
|
|
1266
1354
|
const handleSignal = (signal) => {
|
|
1267
|
-
|
|
1355
|
+
logger7.info({ signal }, "received signal");
|
|
1268
1356
|
stop().finally(() => {
|
|
1269
|
-
|
|
1357
|
+
logger7.info("done");
|
|
1270
1358
|
process.exit(0);
|
|
1271
1359
|
});
|
|
1272
1360
|
};
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Logger } from "pino";
|
|
2
|
+
export declare const logger: Logger<never, boolean>;
|
|
3
|
+
export declare function createModuleLogger(moduleName: string): Logger;
|
|
4
|
+
type PayloadLogOptions = {
|
|
5
|
+
direction: "IN" | "OUT";
|
|
6
|
+
label: string;
|
|
7
|
+
content: string;
|
|
8
|
+
context?: Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
export declare function logPayload(targetLogger: Logger, options: PayloadLogOptions): void;
|
|
11
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friendlyrobot/discord-pi-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Reusable Discord gateway bridge for persistent pi agent sessions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -40,11 +40,13 @@
|
|
|
40
40
|
"discord.js": "^14.26.4",
|
|
41
41
|
"dotenv": "^17.4.2",
|
|
42
42
|
"marked": "^18.0.3",
|
|
43
|
+
"pino": "^10.3.1",
|
|
44
|
+
"pino-pretty": "^13.1.3",
|
|
43
45
|
"prettier": "^3.8.3"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
46
48
|
"@types/node": "^25.6.0",
|
|
47
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
49
|
+
"@typescript/native-preview": "^7.0.0-dev.20260506.1",
|
|
48
50
|
"@vitest/ui": "^4.1.5",
|
|
49
51
|
"vitest": "^4.1.5"
|
|
50
52
|
}
|