@friendlyrobot/discord-pi-agent 0.6.1 → 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 -81
- 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,30 +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
|
-
|
|
1040
|
+
logger5.info({
|
|
1041
|
+
direction: "IN",
|
|
974
1042
|
messageId: message.id,
|
|
975
1043
|
authorId: message.author.id,
|
|
976
1044
|
channelType: message.channel.type,
|
|
977
|
-
|
|
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
|
+
}
|
|
978
1055
|
});
|
|
979
1056
|
try {
|
|
980
1057
|
await onMessage(message, config, agentService, sessionRegistry, authConfig);
|
|
981
1058
|
} catch (error) {
|
|
982
|
-
|
|
1059
|
+
logger5.error({ error, direction: "IN" }, "message handling failed");
|
|
983
1060
|
await sendReply(message, "The bot hit an error while handling that message.");
|
|
984
1061
|
}
|
|
985
1062
|
});
|
|
986
1063
|
client.on(Events.ThreadDelete, async (thread) => {
|
|
987
1064
|
const scope = `thread:${thread.id}`;
|
|
988
|
-
|
|
1065
|
+
logger5.info({ threadId: thread.id, scope }, "thread deleted");
|
|
989
1066
|
await sessionRegistry.remove(scope);
|
|
990
1067
|
});
|
|
991
1068
|
await client.login(config.discordBotToken);
|
|
@@ -993,41 +1070,41 @@ async function startGatewayClient(config, agentService, sessionRegistry, authCon
|
|
|
993
1070
|
}
|
|
994
1071
|
async function onMessage(message, config, agentService, sessionRegistry, authConfig) {
|
|
995
1072
|
if (message.author.bot) {
|
|
996
|
-
|
|
1073
|
+
logger5.debug({ messageId: message.id }, "ignored bot message");
|
|
997
1074
|
return;
|
|
998
1075
|
}
|
|
999
1076
|
if (message.system) {
|
|
1000
|
-
|
|
1077
|
+
logger5.debug({ messageId: message.id }, "ignored system message");
|
|
1001
1078
|
return;
|
|
1002
1079
|
}
|
|
1003
1080
|
const scope = resolveScope(message);
|
|
1004
1081
|
if (scope === null) {
|
|
1005
|
-
|
|
1082
|
+
logger5.debug({
|
|
1006
1083
|
messageId: message.id,
|
|
1007
1084
|
channelType: message.channel.type
|
|
1008
|
-
});
|
|
1085
|
+
}, "unsupported channel type, ignoring");
|
|
1009
1086
|
return;
|
|
1010
1087
|
}
|
|
1011
1088
|
if (!isAuthorized(message, scope, authConfig)) {
|
|
1012
|
-
|
|
1089
|
+
logger5.debug({
|
|
1013
1090
|
messageId: message.id,
|
|
1014
1091
|
authorId: message.author.id,
|
|
1015
1092
|
scope
|
|
1016
|
-
});
|
|
1093
|
+
}, "unauthorized");
|
|
1017
1094
|
return;
|
|
1018
1095
|
}
|
|
1019
1096
|
const content = message.content.trim();
|
|
1020
1097
|
if (!content) {
|
|
1021
|
-
|
|
1098
|
+
logger5.debug({ messageId: message.id }, "ignored empty message");
|
|
1022
1099
|
return;
|
|
1023
1100
|
}
|
|
1024
1101
|
const { entry, created } = await sessionRegistry.getOrCreate(scope);
|
|
1025
1102
|
const { session, promptQueue } = entry;
|
|
1026
1103
|
if (created && scope.startsWith("thread:") && message.channel.isThread()) {
|
|
1027
|
-
|
|
1104
|
+
logger5.info({
|
|
1028
1105
|
scope,
|
|
1029
1106
|
threadName: message.channel.name
|
|
1030
|
-
});
|
|
1107
|
+
}, "new thread session");
|
|
1031
1108
|
}
|
|
1032
1109
|
let typingInterval = null;
|
|
1033
1110
|
if (message.channel.isSendable()) {
|
|
@@ -1041,7 +1118,7 @@ async function onMessage(message, config, agentService, sessionRegistry, authCon
|
|
|
1041
1118
|
if (commandResult.handled) {
|
|
1042
1119
|
stopTypingInterval(typingInterval);
|
|
1043
1120
|
if (commandResult.archive && scope.startsWith("thread:")) {
|
|
1044
|
-
|
|
1121
|
+
logger5.info({ scope }, "archiving thread");
|
|
1045
1122
|
const archiveChannel = message.channel;
|
|
1046
1123
|
if (archiveChannel.isSendable()) {
|
|
1047
1124
|
await archiveChannel.send(commandResult.response ?? "Archiving...");
|
|
@@ -1051,16 +1128,16 @@ async function onMessage(message, config, agentService, sessionRegistry, authCon
|
|
|
1051
1128
|
await archiveChannel.setArchived(true);
|
|
1052
1129
|
}
|
|
1053
1130
|
} catch (error) {
|
|
1054
|
-
|
|
1131
|
+
logger5.error({ error }, "failed to archive thread");
|
|
1055
1132
|
}
|
|
1056
1133
|
await sessionRegistry.remove(scope);
|
|
1057
1134
|
return;
|
|
1058
1135
|
}
|
|
1059
|
-
|
|
1136
|
+
logger5.info({
|
|
1060
1137
|
messageId: message.id,
|
|
1061
1138
|
command: content,
|
|
1062
1139
|
hasResponse: Boolean(commandResult.response)
|
|
1063
|
-
});
|
|
1140
|
+
}, "command handled");
|
|
1064
1141
|
if (commandResult.response) {
|
|
1065
1142
|
await sendReply(message, commandResult.response);
|
|
1066
1143
|
}
|
|
@@ -1068,33 +1145,54 @@ async function onMessage(message, config, agentService, sessionRegistry, authCon
|
|
|
1068
1145
|
}
|
|
1069
1146
|
if (!message.channel.isSendable()) {
|
|
1070
1147
|
stopTypingInterval(typingInterval);
|
|
1071
|
-
|
|
1148
|
+
logger5.debug({ messageId: message.id }, "channel not sendable");
|
|
1072
1149
|
return;
|
|
1073
1150
|
}
|
|
1074
1151
|
const queuePosition = promptQueue.getSnapshot().pending;
|
|
1075
|
-
|
|
1152
|
+
logger5.debug({
|
|
1076
1153
|
scope,
|
|
1077
1154
|
messageId: message.id,
|
|
1078
1155
|
queuePosition
|
|
1079
|
-
});
|
|
1156
|
+
}, "enqueue request");
|
|
1080
1157
|
if (queuePosition > 0) {
|
|
1081
1158
|
await sendReply(message, `Queued. ${queuePosition} request(s) ahead of this one.`);
|
|
1082
1159
|
}
|
|
1083
1160
|
const response = await promptQueue.enqueue(async () => {
|
|
1084
|
-
|
|
1161
|
+
logger5.debug({
|
|
1162
|
+
messageId: message.id,
|
|
1163
|
+
scope
|
|
1164
|
+
}, "processing message");
|
|
1085
1165
|
const promptContent = buildDiscordPromptContent(message, scope, content, config);
|
|
1086
|
-
|
|
1166
|
+
logPayload(logger5, {
|
|
1167
|
+
direction: "IN",
|
|
1168
|
+
label: "gateway prompt content",
|
|
1169
|
+
content: promptContent,
|
|
1170
|
+
context: {
|
|
1171
|
+
scope,
|
|
1172
|
+
messageId: message.id
|
|
1173
|
+
}
|
|
1174
|
+
});
|
|
1087
1175
|
const transformedPrompt = await config.promptTransform(promptContent);
|
|
1088
1176
|
return collectReply(session, transformedPrompt, {
|
|
1089
1177
|
logPrefix: `[agent:${session.sessionId}]`
|
|
1090
1178
|
});
|
|
1091
1179
|
});
|
|
1092
1180
|
stopTypingInterval(typingInterval);
|
|
1093
|
-
|
|
1181
|
+
logger5.info({
|
|
1182
|
+
direction: "OUT",
|
|
1094
1183
|
scope,
|
|
1095
1184
|
messageId: message.id,
|
|
1096
1185
|
responseLength: response.length,
|
|
1097
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
|
+
}
|
|
1098
1196
|
});
|
|
1099
1197
|
await sendReply(message, response);
|
|
1100
1198
|
}
|
|
@@ -1151,6 +1249,7 @@ function sessionDirForScope(agentDir, scope) {
|
|
|
1151
1249
|
}
|
|
1152
1250
|
throw new Error(`Unknown session scope: ${scope}`);
|
|
1153
1251
|
}
|
|
1252
|
+
var logger6 = createModuleLogger("session-registry");
|
|
1154
1253
|
|
|
1155
1254
|
class SessionRegistry {
|
|
1156
1255
|
scopes = new Map;
|
|
@@ -1172,11 +1271,11 @@ class SessionRegistry {
|
|
|
1172
1271
|
createdAt: new Date
|
|
1173
1272
|
};
|
|
1174
1273
|
this.scopes.set(scope, entry);
|
|
1175
|
-
|
|
1274
|
+
logger6.info({
|
|
1176
1275
|
scope,
|
|
1177
1276
|
sessionDir,
|
|
1178
1277
|
sessionId: session.sessionId
|
|
1179
|
-
});
|
|
1278
|
+
}, "scope registered");
|
|
1180
1279
|
return { entry, created: true };
|
|
1181
1280
|
}
|
|
1182
1281
|
async remove(scope) {
|
|
@@ -1184,7 +1283,7 @@ class SessionRegistry {
|
|
|
1184
1283
|
if (!entry) {
|
|
1185
1284
|
return;
|
|
1186
1285
|
}
|
|
1187
|
-
|
|
1286
|
+
logger6.info({ scope }, "removing scope");
|
|
1188
1287
|
await entry.session.abort();
|
|
1189
1288
|
entry.session.dispose();
|
|
1190
1289
|
this.scopes.delete(scope);
|
|
@@ -1196,7 +1295,7 @@ class SessionRegistry {
|
|
|
1196
1295
|
return Array.from(this.scopes.keys());
|
|
1197
1296
|
}
|
|
1198
1297
|
async shutdownAll() {
|
|
1199
|
-
|
|
1298
|
+
logger6.info({ count: this.scopes.size }, "shutting down all scopes");
|
|
1200
1299
|
const scopes = Array.from(this.scopes.keys());
|
|
1201
1300
|
for (const scope of scopes) {
|
|
1202
1301
|
await this.remove(scope);
|
|
@@ -1205,12 +1304,13 @@ class SessionRegistry {
|
|
|
1205
1304
|
}
|
|
1206
1305
|
|
|
1207
1306
|
// src/index.ts
|
|
1307
|
+
var logger7 = createModuleLogger("index");
|
|
1208
1308
|
async function startDiscordGateway(config) {
|
|
1209
1309
|
const resolvedConfig = resolveGatewayConfig(config);
|
|
1210
1310
|
const agentService = new AgentService(resolvedConfig);
|
|
1211
|
-
|
|
1311
|
+
logger7.info("initializing agent service");
|
|
1212
1312
|
await agentService.initialize();
|
|
1213
|
-
|
|
1313
|
+
logger7.info(agentService.getStatus(), "agent ready");
|
|
1214
1314
|
const authConfig = {
|
|
1215
1315
|
discordAllowedUserId: resolvedConfig.discordAllowedUserId,
|
|
1216
1316
|
discordAllowedForumChannelIds: resolvedConfig.discordAllowedForumChannelIds,
|
|
@@ -1241,10 +1341,10 @@ function createGatewayStopHandler(client, agentService, sessionRegistry, config)
|
|
|
1241
1341
|
return;
|
|
1242
1342
|
}
|
|
1243
1343
|
stopped = true;
|
|
1244
|
-
|
|
1344
|
+
logger7.info({
|
|
1245
1345
|
cwd: config.cwd,
|
|
1246
1346
|
agentDir: config.agentDir
|
|
1247
|
-
});
|
|
1347
|
+
}, "stopping discord gateway");
|
|
1248
1348
|
client.destroy();
|
|
1249
1349
|
await sessionRegistry.shutdownAll();
|
|
1250
1350
|
await agentService.shutdown();
|
|
@@ -1252,9 +1352,9 @@ function createGatewayStopHandler(client, agentService, sessionRegistry, config)
|
|
|
1252
1352
|
}
|
|
1253
1353
|
function registerSignalHandlers(stop) {
|
|
1254
1354
|
const handleSignal = (signal) => {
|
|
1255
|
-
|
|
1355
|
+
logger7.info({ signal }, "received signal");
|
|
1256
1356
|
stop().finally(() => {
|
|
1257
|
-
|
|
1357
|
+
logger7.info("done");
|
|
1258
1358
|
process.exit(0);
|
|
1259
1359
|
});
|
|
1260
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
|
}
|