@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 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,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
- 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
- console.log("[gateway] message received", {
1040
+ logger5.info({
1041
+ direction: "IN",
974
1042
  messageId: message.id,
975
1043
  authorId: message.author.id,
976
1044
  channelType: message.channel.type,
977
- 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
+ }
978
1055
  });
979
1056
  try {
980
1057
  await onMessage(message, config, agentService, sessionRegistry, authConfig);
981
1058
  } catch (error) {
982
- console.error("[gateway] message handling failed", error);
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
- console.log("[gateway] thread deleted", { threadId: thread.id, scope });
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
- console.log("[gateway] ignored bot message", { messageId: message.id });
1073
+ logger5.debug({ messageId: message.id }, "ignored bot message");
997
1074
  return;
998
1075
  }
999
1076
  if (message.system) {
1000
- console.log("[gateway] ignored system message", { messageId: message.id });
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
- console.log("[gateway] unsupported channel type, ignoring", {
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
- console.log("[gateway] unauthorized", {
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
- console.log("[gateway] ignored empty message", { messageId: message.id });
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
- console.log("[gateway] new thread session", {
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
- console.log("[gateway] archiving thread", { scope });
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
- console.error("[gateway] failed to archive thread", error);
1131
+ logger5.error({ error }, "failed to archive thread");
1055
1132
  }
1056
1133
  await sessionRegistry.remove(scope);
1057
1134
  return;
1058
1135
  }
1059
- console.log("[gateway] command handled", {
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
- console.log("[gateway] channel not sendable", { messageId: message.id });
1148
+ logger5.debug({ messageId: message.id }, "channel not sendable");
1072
1149
  return;
1073
1150
  }
1074
1151
  const queuePosition = promptQueue.getSnapshot().pending;
1075
- console.log("[queue] enqueue request", {
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
- console.log(`[queue] processing message ${message.id} in scope ${scope}`);
1161
+ logger5.debug({
1162
+ messageId: message.id,
1163
+ scope
1164
+ }, "processing message");
1085
1165
  const promptContent = buildDiscordPromptContent(message, scope, content, config);
1086
- console.log("[gateway:debug] prompt content", promptContent);
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
- console.log("[gateway] response ready", {
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
- console.log("[session-registry] scope registered", {
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
- console.log("[session-registry] removing scope", { scope });
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
- console.log("[session-registry] shutting down all scopes", this.scopes.size);
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
- console.log("[gateway] initializing agent service");
1311
+ logger7.info("initializing agent service");
1212
1312
  await agentService.initialize();
1213
- console.log("[gateway] agent ready", agentService.getStatus());
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
- console.log("[shutdown] stopping discord gateway", {
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
- console.log(`[shutdown] received ${signal}`);
1355
+ logger7.info({ signal }, "received signal");
1256
1356
  stop().finally(() => {
1257
- console.log("[shutdown] done");
1357
+ logger7.info("done");
1258
1358
  process.exit(0);
1259
1359
  });
1260
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.1",
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
  }