@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 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
- console.error("[markdown-table-transformer] Prettier formatting failed:", error);
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
- console.log(`${logPrefix} prompt start`, { prompt });
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
- console.log(`${logPrefix} tool start`, {
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
- console.log(`${logPrefix} tool end`, {
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
- console.log(`${logPrefix} agent end`, {
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
- console.log(`${logPrefix} prompt done`, {
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
- console.log("[DEBUG] Table transformation comparison:", `
125
- === RAW finalText ===
126
- ` + finalText + `
127
- === TRANSFORMED ===
128
- ` + transformed + `
129
- === END ===`);
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
- console.log("[agent] config", {
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
- console.log("[agent] resources loaded", {
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
- console.log("[agent] scoped session created", {
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
- console.log("[agent] session ready", {
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
- console.log("[agent] available models", {
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
- console.log("[agent] model already selected", {
392
+ logger4.info({
328
393
  model: `${desiredModel.provider}/${desiredModel.id}`
329
- });
394
+ }, "model already selected");
330
395
  return;
331
396
  }
332
- console.log("[agent] switching model", {
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
- console.log("[agent] thinking level applied", {
418
+ logger4.info({
354
419
  level: this.config.thinkingLevel
355
- });
420
+ }, "thinking level applied");
356
421
  } else {
357
- console.log("[agent] thinking level not available for model", {
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
- console.log("[gateway] reply skipped, channel not sendable", {
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
- console.log("[gateway] sending reply", {
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
- console.error("[gateway] send reply failed", {
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
- console.log(`[gateway] logged in as ${readyClient.user.tag}`);
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
- console.log("[gateway] sent startup dm", {
1032
+ logger5.info({
966
1033
  userId: authConfig.discordAllowedUserId
967
- });
1034
+ }, "sent startup dm");
968
1035
  } catch (error) {
969
- console.error("[gateway] failed to send startup dm", error);
1036
+ logger5.error({ error }, "failed to send startup dm");
970
1037
  }
971
1038
  });
972
1039
  client.on(Events.MessageCreate, async (message) => {
973
- if (message.channel.isThread()) {
974
- console.log("[gateway:debug] thread message raw", {
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
- content: message.content.slice(0, 200)
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
- console.error("[gateway] message handling failed", error);
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
- console.log("[gateway] thread deleted", { threadId: thread.id, scope });
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
- console.log("[gateway] ignored bot message", { messageId: message.id });
1073
+ logger5.debug({ messageId: message.id }, "ignored bot message");
1010
1074
  return;
1011
1075
  }
1012
1076
  if (message.system) {
1013
- console.log("[gateway] ignored system message", { messageId: message.id });
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
- console.log("[gateway] unsupported channel type, ignoring", {
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
- console.log("[gateway] unauthorized", {
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
- console.log("[gateway] ignored empty message", { messageId: message.id });
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
- console.log("[gateway] new thread session", {
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
- console.log("[gateway] archiving thread", { scope });
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
- console.error("[gateway] failed to archive thread", error);
1131
+ logger5.error({ error }, "failed to archive thread");
1068
1132
  }
1069
1133
  await sessionRegistry.remove(scope);
1070
1134
  return;
1071
1135
  }
1072
- console.log("[gateway] command handled", {
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
- console.log("[gateway] channel not sendable", { messageId: message.id });
1148
+ logger5.debug({ messageId: message.id }, "channel not sendable");
1085
1149
  return;
1086
1150
  }
1087
1151
  const queuePosition = promptQueue.getSnapshot().pending;
1088
- console.log("[queue] enqueue request", {
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
- console.log(`[queue] processing message ${message.id} in scope ${scope}`);
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
- console.log("[gateway] response ready", {
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
- console.log("[session-registry] scope registered", {
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
- console.log("[session-registry] removing scope", { scope });
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
- console.log("[session-registry] shutting down all scopes", this.scopes.size);
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
- console.log("[gateway] initializing agent service");
1311
+ logger7.info("initializing agent service");
1224
1312
  await agentService.initialize();
1225
- console.log("[gateway] agent ready", agentService.getStatus());
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
- console.log("[shutdown] stopping discord gateway", {
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
- console.log(`[shutdown] received ${signal}`);
1355
+ logger7.info({ signal }, "received signal");
1268
1356
  stop().finally(() => {
1269
- console.log("[shutdown] done");
1357
+ logger7.info("done");
1270
1358
  process.exit(0);
1271
1359
  });
1272
1360
  };
@@ -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.6.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.20260504.1",
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
  }