@poncho-ai/messaging 0.7.6 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
 
2
- > @poncho-ai/messaging@0.7.6 build /home/runner/work/poncho-ai/poncho-ai/packages/messaging
2
+ > @poncho-ai/messaging@0.7.7 build /home/runner/work/poncho-ai/poncho-ai/packages/messaging
3
3
  > tsup src/index.ts --format esm --dts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -7,8 +7,8 @@
7
7
  CLI tsup v8.5.1
8
8
  CLI Target: es2022
9
9
  ESM Build start
10
- ESM dist/index.js 51.09 KB
11
- ESM ⚡️ Build success in 102ms
10
+ ESM dist/index.js 52.41 KB
11
+ ESM ⚡️ Build success in 89ms
12
12
  DTS Build start
13
- DTS ⚡️ Build success in 4825ms
14
- DTS dist/index.d.ts 11.24 KB
13
+ DTS ⚡️ Build success in 5653ms
14
+ DTS dist/index.d.ts 11.72 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @poncho-ai/messaging
2
2
 
3
+ ## 0.7.7
4
+
5
+ ### Patch Changes
6
+
7
+ - [#61](https://github.com/cesr/poncho-ai/pull/61) [`0a51abe`](https://github.com/cesr/poncho-ai/commit/0a51abec12191397fd36ab1fd4feca7460489e33) Thanks [@cesr](https://github.com/cesr)! - Fix /new command on Telegram in serverless environments: persist conversation reset to the store so it survives cold starts.
8
+
3
9
  ## 0.7.6
4
10
 
5
11
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -26,6 +26,7 @@ interface IncomingMessage {
26
26
  raw: unknown;
27
27
  }
28
28
  type IncomingMessageHandler = (message: IncomingMessage) => Promise<void>;
29
+ type ResetHandler = (platform: string, threadRef: ThreadRef) => Promise<void>;
29
30
  type RouteHandler = (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>;
30
31
  type RouteRegistrar = (method: "GET" | "POST", path: string, handler: RouteHandler) => void;
31
32
  interface MessagingAdapter {
@@ -43,6 +44,8 @@ interface MessagingAdapter {
43
44
  initialize(): Promise<void>;
44
45
  /** Set the handler that processes incoming messages. */
45
46
  onMessage(handler: IncomingMessageHandler): void;
47
+ /** Set the handler called when a user resets the conversation (e.g. /new). */
48
+ onReset?(handler: ResetHandler): void;
46
49
  /** Post a reply back to the originating thread. */
47
50
  sendReply(threadRef: ThreadRef, content: string, options?: {
48
51
  files?: FileAttachment[];
@@ -89,6 +92,11 @@ interface AgentRunner {
89
92
  steps?: number;
90
93
  maxSteps?: number;
91
94
  }>;
95
+ /**
96
+ * Reset a conversation by clearing its messages, making the next
97
+ * interaction start fresh while keeping the same conversation ID.
98
+ */
99
+ resetConversation?(conversationId: string): Promise<void>;
92
100
  }
93
101
  interface AgentBridgeOptions {
94
102
  adapter: MessagingAdapter;
@@ -223,6 +231,7 @@ declare class TelegramAdapter implements MessagingAdapter {
223
231
  private readonly webhookSecretEnv;
224
232
  private readonly allowedUserIds;
225
233
  private handler;
234
+ private resetHandler;
226
235
  private approvalDecisionHandler;
227
236
  private readonly sessionCounters;
228
237
  private readonly approvalMessageIds;
@@ -230,6 +239,7 @@ declare class TelegramAdapter implements MessagingAdapter {
230
239
  constructor(options?: TelegramAdapterOptions);
231
240
  initialize(): Promise<void>;
232
241
  onMessage(handler: IncomingMessageHandler): void;
242
+ onReset(handler: ResetHandler): void;
233
243
  registerRoutes(router: RouteRegistrar): void;
234
244
  sendReply(threadRef: ThreadRef, content: string, options?: {
235
245
  files?: FileAttachment[];
package/dist/index.js CHANGED
@@ -30,6 +30,12 @@ var AgentBridge = class {
30
30
  this.waitUntil(processing);
31
31
  return processing;
32
32
  });
33
+ if (this.adapter.onReset && this.runner.resetConversation) {
34
+ this.adapter.onReset(async (platform, threadRef) => {
35
+ const conversationId = conversationIdFromThread(platform, threadRef);
36
+ await this.runner.resetConversation(conversationId);
37
+ });
38
+ }
33
39
  await this.adapter.initialize();
34
40
  }
35
41
  async handleMessage(message) {
@@ -1186,6 +1192,12 @@ var stripMention2 = (text, entities, botUsername, botId) => {
1186
1192
  // src/adapters/telegram/index.ts
1187
1193
  var TYPING_INTERVAL_MS = 4e3;
1188
1194
  var NEW_COMMAND_RE = /^\/new(?:@(\S+))?$/i;
1195
+ var parseMessageThreadId = (platformThreadId, chatId) => {
1196
+ const parts = platformThreadId.split(":");
1197
+ if (parts.length !== 3 || parts[0] !== chatId) return void 0;
1198
+ const threadId = Number(parts[1]);
1199
+ return Number.isInteger(threadId) ? threadId : void 0;
1200
+ };
1189
1201
  var collectBody3 = (req) => new Promise((resolve, reject) => {
1190
1202
  const chunks = [];
1191
1203
  req.on("data", (chunk) => chunks.push(chunk));
@@ -1204,6 +1216,7 @@ var TelegramAdapter = class {
1204
1216
  webhookSecretEnv;
1205
1217
  allowedUserIds;
1206
1218
  handler;
1219
+ resetHandler;
1207
1220
  approvalDecisionHandler;
1208
1221
  sessionCounters = /* @__PURE__ */ new Map();
1209
1222
  approvalMessageIds = /* @__PURE__ */ new Map();
@@ -1231,6 +1244,9 @@ var TelegramAdapter = class {
1231
1244
  onMessage(handler) {
1232
1245
  this.handler = handler;
1233
1246
  }
1247
+ onReset(handler) {
1248
+ this.resetHandler = handler;
1249
+ }
1234
1250
  registerRoutes(router) {
1235
1251
  router(
1236
1252
  "POST",
@@ -1241,11 +1257,16 @@ var TelegramAdapter = class {
1241
1257
  async sendReply(threadRef, content, options) {
1242
1258
  const chatId = threadRef.channelId;
1243
1259
  const replyTo = threadRef.messageId ? Number(threadRef.messageId) : void 0;
1260
+ const messageThreadId = parseMessageThreadId(
1261
+ threadRef.platformThreadId,
1262
+ chatId
1263
+ );
1244
1264
  if (content) {
1245
1265
  const chunks = splitMessage2(content);
1246
1266
  for (const chunk of chunks) {
1247
1267
  await sendMessage(this.botToken, chatId, chunk, {
1248
- reply_to_message_id: replyTo
1268
+ reply_to_message_id: replyTo,
1269
+ message_thread_id: messageThreadId
1249
1270
  });
1250
1271
  }
1251
1272
  }
@@ -1254,11 +1275,13 @@ var TelegramAdapter = class {
1254
1275
  if (file.mediaType.startsWith("image/")) {
1255
1276
  await sendPhoto(this.botToken, chatId, file.data, {
1256
1277
  reply_to_message_id: replyTo,
1278
+ message_thread_id: messageThreadId,
1257
1279
  filename: file.filename
1258
1280
  });
1259
1281
  } else {
1260
1282
  await sendDocument(this.botToken, chatId, file.data, {
1261
1283
  reply_to_message_id: replyTo,
1284
+ message_thread_id: messageThreadId,
1262
1285
  filename: file.filename,
1263
1286
  mediaType: file.mediaType
1264
1287
  });
@@ -1418,6 +1441,18 @@ ${inputSummary}` : "(no input)"
1418
1441
  this.sessionCounters.set(key2, current + 1);
1419
1442
  res.writeHead(200);
1420
1443
  res.end();
1444
+ if (this.resetHandler) {
1445
+ const topicId2 = message.message_thread_id;
1446
+ const prevThreadId = topicId2 ? `${chatId}:${topicId2}:${current}` : `${chatId}:${current}`;
1447
+ try {
1448
+ await this.resetHandler("telegram", {
1449
+ channelId: chatId,
1450
+ platformThreadId: prevThreadId
1451
+ });
1452
+ } catch (err) {
1453
+ console.error("[telegram-adapter] reset handler error:", err instanceof Error ? err.message : err);
1454
+ }
1455
+ }
1421
1456
  await sendMessage(
1422
1457
  this.botToken,
1423
1458
  chatId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/messaging",
3
- "version": "0.7.6",
3
+ "version": "0.7.7",
4
4
  "description": "Messaging platform adapters for Poncho agents (Slack, Telegram, etc.)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -4,6 +4,7 @@ import type {
4
4
  IncomingMessage as PonchoIncomingMessage,
5
5
  IncomingMessageHandler,
6
6
  MessagingAdapter,
7
+ ResetHandler,
7
8
  RouteRegistrar,
8
9
  ThreadRef,
9
10
  } from "../../types.js";
@@ -30,6 +31,19 @@ import {
30
31
  const TYPING_INTERVAL_MS = 4_000;
31
32
  const NEW_COMMAND_RE = /^\/new(?:@(\S+))?$/i;
32
33
 
34
+ const parseMessageThreadId = (
35
+ platformThreadId: string,
36
+ chatId: string,
37
+ ): number | undefined => {
38
+ const parts = platformThreadId.split(":");
39
+ // Telegram thread format:
40
+ // - non-topic chats: `${chatId}:${session}`
41
+ // - forum topics: `${chatId}:${message_thread_id}:${session}`
42
+ if (parts.length !== 3 || parts[0] !== chatId) return undefined;
43
+ const threadId = Number(parts[1]);
44
+ return Number.isInteger(threadId) ? threadId : undefined;
45
+ };
46
+
33
47
  export interface TelegramApprovalInfo {
34
48
  approvalId: string;
35
49
  tool: string;
@@ -69,6 +83,7 @@ export class TelegramAdapter implements MessagingAdapter {
69
83
  private readonly webhookSecretEnv: string;
70
84
  private readonly allowedUserIds: number[] | undefined;
71
85
  private handler: IncomingMessageHandler | undefined;
86
+ private resetHandler: ResetHandler | undefined;
72
87
  private approvalDecisionHandler: TelegramApprovalDecisionHandler | undefined;
73
88
  private readonly sessionCounters = new Map<string, number>();
74
89
  private readonly approvalMessageIds = new Map<string, { chatId: string; messageId: number }>();
@@ -107,6 +122,10 @@ export class TelegramAdapter implements MessagingAdapter {
107
122
  this.handler = handler;
108
123
  }
109
124
 
125
+ onReset(handler: ResetHandler): void {
126
+ this.resetHandler = handler;
127
+ }
128
+
110
129
  registerRoutes(router: RouteRegistrar): void {
111
130
  router("POST", "/api/messaging/telegram", (req, res) =>
112
131
  this.handleRequest(req, res),
@@ -122,12 +141,17 @@ export class TelegramAdapter implements MessagingAdapter {
122
141
  const replyTo = threadRef.messageId
123
142
  ? Number(threadRef.messageId)
124
143
  : undefined;
144
+ const messageThreadId = parseMessageThreadId(
145
+ threadRef.platformThreadId,
146
+ chatId,
147
+ );
125
148
 
126
149
  if (content) {
127
150
  const chunks = splitMessage(content);
128
151
  for (const chunk of chunks) {
129
152
  await sendMessage(this.botToken, chatId, chunk, {
130
153
  reply_to_message_id: replyTo,
154
+ message_thread_id: messageThreadId,
131
155
  });
132
156
  }
133
157
  }
@@ -137,11 +161,13 @@ export class TelegramAdapter implements MessagingAdapter {
137
161
  if (file.mediaType.startsWith("image/")) {
138
162
  await sendPhoto(this.botToken, chatId, file.data, {
139
163
  reply_to_message_id: replyTo,
164
+ message_thread_id: messageThreadId,
140
165
  filename: file.filename,
141
166
  });
142
167
  } else {
143
168
  await sendDocument(this.botToken, chatId, file.data, {
144
169
  reply_to_message_id: replyTo,
170
+ message_thread_id: messageThreadId,
145
171
  filename: file.filename,
146
172
  mediaType: file.mediaType,
147
173
  });
@@ -355,6 +381,24 @@ export class TelegramAdapter implements MessagingAdapter {
355
381
  res.writeHead(200);
356
382
  res.end();
357
383
 
384
+ // Persist the reset so it survives serverless cold starts.
385
+ // The in-memory counter handles long-running processes; the bridge
386
+ // clears messages in the conversation store for serverless.
387
+ if (this.resetHandler) {
388
+ const topicId = message.message_thread_id;
389
+ const prevThreadId = topicId
390
+ ? `${chatId}:${topicId}:${current}`
391
+ : `${chatId}:${current}`;
392
+ try {
393
+ await this.resetHandler("telegram", {
394
+ channelId: chatId,
395
+ platformThreadId: prevThreadId,
396
+ });
397
+ } catch (err) {
398
+ console.error("[telegram-adapter] reset handler error:", err instanceof Error ? err.message : err);
399
+ }
400
+ }
401
+
358
402
  await sendMessage(
359
403
  this.botToken,
360
404
  chatId,
package/src/bridge.ts CHANGED
@@ -50,6 +50,14 @@ export class AgentBridge {
50
50
  this.waitUntil(processing);
51
51
  return processing;
52
52
  });
53
+
54
+ if (this.adapter.onReset && this.runner.resetConversation) {
55
+ this.adapter.onReset(async (platform, threadRef) => {
56
+ const conversationId = conversationIdFromThread(platform, threadRef);
57
+ await this.runner.resetConversation!(conversationId);
58
+ });
59
+ }
60
+
53
61
  await this.adapter.initialize();
54
62
  }
55
63
 
package/src/types.ts CHANGED
@@ -33,6 +33,11 @@ export type IncomingMessageHandler = (
33
33
  message: IncomingMessage,
34
34
  ) => Promise<void>;
35
35
 
36
+ export type ResetHandler = (
37
+ platform: string,
38
+ threadRef: ThreadRef,
39
+ ) => Promise<void>;
40
+
36
41
  // ---------------------------------------------------------------------------
37
42
  // Route registration (adapter ↔ HTTP server contract)
38
43
  // ---------------------------------------------------------------------------
@@ -73,6 +78,9 @@ export interface MessagingAdapter {
73
78
  /** Set the handler that processes incoming messages. */
74
79
  onMessage(handler: IncomingMessageHandler): void;
75
80
 
81
+ /** Set the handler called when a user resets the conversation (e.g. /new). */
82
+ onReset?(handler: ResetHandler): void;
83
+
76
84
  /** Post a reply back to the originating thread. */
77
85
  sendReply(
78
86
  threadRef: ThreadRef,
@@ -133,6 +141,12 @@ export interface AgentRunner {
133
141
  steps?: number;
134
142
  maxSteps?: number;
135
143
  }>;
144
+
145
+ /**
146
+ * Reset a conversation by clearing its messages, making the next
147
+ * interaction start fresh while keeping the same conversation ID.
148
+ */
149
+ resetConversation?(conversationId: string): Promise<void>;
136
150
  }
137
151
 
138
152
  // ---------------------------------------------------------------------------