@poncho-ai/messaging 0.3.0 → 0.5.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @poncho-ai/messaging@0.3.0 build /Users/cesar/Dev/latitude/poncho-ai/packages/messaging
2
+ > @poncho-ai/messaging@0.5.0 build /Users/cesar/Dev/latitude/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 44.67 KB
11
- ESM ⚡️ Build success in 55ms
10
+ ESM dist/index.js 45.94 KB
11
+ ESM ⚡️ Build success in 60ms
12
12
  DTS Build start
13
- DTS ⚡️ Build success in 3450ms
14
- DTS dist/index.d.ts 9.98 KB
13
+ DTS ⚡️ Build success in 2033ms
14
+ DTS dist/index.d.ts 10.13 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @poncho-ai/messaging
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`8ef9316`](https://github.com/cesr/poncho-ai/commit/8ef93165084b4df581e39a1581b1ead64b7b3f42) Thanks [@cesr](https://github.com/cesr)! - Add auto-continuation support for messaging adapters (Telegram, Slack, Resend) on serverless platforms. When `PONCHO_MAX_DURATION` is set, agent runs that hit the soft deadline now automatically resume with "Continue" messages, matching the web UI and client SDK behavior.
8
+
9
+ ## 0.4.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [`3216e80`](https://github.com/cesr/poncho-ai/commit/3216e8072027896dd1cc5f29b1a7b0eea9ee1ff5) Thanks [@cesr](https://github.com/cesr)! - Add `allowedUserIds` option to Telegram adapter for restricting bot access to specific users.
14
+
3
15
  ## 0.3.0
4
16
 
5
17
  ### Minor Changes
package/dist/index.d.ts CHANGED
@@ -83,6 +83,9 @@ interface AgentRunner {
83
83
  }): Promise<{
84
84
  response: string;
85
85
  files?: FileAttachment[];
86
+ continuation?: boolean;
87
+ steps?: number;
88
+ maxSteps?: number;
86
89
  }>;
87
90
  }
88
91
  interface AgentBridgeOptions {
@@ -192,6 +195,7 @@ declare class ResendAdapter implements MessagingAdapter {
192
195
  interface TelegramAdapterOptions {
193
196
  botTokenEnv?: string;
194
197
  webhookSecretEnv?: string;
198
+ allowedUserIds?: number[];
195
199
  }
196
200
  declare class TelegramAdapter implements MessagingAdapter {
197
201
  readonly platform: "telegram";
@@ -203,6 +207,7 @@ declare class TelegramAdapter implements MessagingAdapter {
203
207
  private botId;
204
208
  private readonly botTokenEnv;
205
209
  private readonly webhookSecretEnv;
210
+ private readonly allowedUserIds;
206
211
  private handler;
207
212
  private readonly sessionCounters;
208
213
  private lastUpdateId;
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ var AgentBridge = class {
33
33
  await this.adapter.initialize();
34
34
  }
35
35
  async handleMessage(message) {
36
+ const MAX_CONTINUATIONS = 10;
36
37
  let cleanup;
37
38
  this.adapter.resetRequestState?.();
38
39
  try {
@@ -46,33 +47,53 @@ var AgentBridge = class {
46
47
  const titleParts = [platformTag, senderLabel];
47
48
  if (message.subject) titleParts.push(message.subject);
48
49
  const title = titleParts.join(" ") || `${message.platform} thread`;
49
- const conversation = await this.runner.getOrCreateConversation(
50
- conversationId,
51
- {
52
- platform: message.platform,
53
- ownerId: this.ownerIdOverride ?? message.sender.id,
54
- title
55
- }
56
- );
57
50
  const senderLine = message.sender.name ? `From: ${message.sender.name} <${message.sender.id}>` : `From: ${message.sender.id}`;
58
51
  const subjectLine = message.subject ? `Subject: ${message.subject}` : "";
59
52
  const header = [senderLine, subjectLine].filter(Boolean).join("\n");
60
- const task = `${header}
53
+ let currentTask = `${header}
61
54
 
62
55
  ${message.text}`;
63
- const result = await this.runner.run(conversationId, {
64
- task,
65
- messages: conversation.messages,
66
- files: message.files,
67
- metadata: {
68
- platform: message.platform,
69
- sender: message.sender,
70
- threadId: message.threadRef.platformThreadId
56
+ let currentFiles = message.files;
57
+ let accumulatedResponse = "";
58
+ let accumulatedFiles = [];
59
+ let totalSteps = 0;
60
+ let stepBudget = 0;
61
+ let continuationCount = 0;
62
+ while (true) {
63
+ const conversation = await this.runner.getOrCreateConversation(
64
+ conversationId,
65
+ {
66
+ platform: message.platform,
67
+ ownerId: this.ownerIdOverride ?? message.sender.id,
68
+ title
69
+ }
70
+ );
71
+ const result = await this.runner.run(conversationId, {
72
+ task: currentTask,
73
+ messages: conversation.messages,
74
+ files: currentFiles,
75
+ metadata: {
76
+ platform: message.platform,
77
+ sender: message.sender,
78
+ threadId: message.threadRef.platformThreadId
79
+ }
80
+ });
81
+ accumulatedResponse += result.response;
82
+ if (result.files) accumulatedFiles.push(...result.files);
83
+ totalSteps += result.steps ?? 0;
84
+ if (typeof result.maxSteps === "number") stepBudget = result.maxSteps;
85
+ if (result.continuation && continuationCount < MAX_CONTINUATIONS && (stepBudget <= 0 || totalSteps < stepBudget)) {
86
+ continuationCount++;
87
+ console.log(`[agent-bridge] continuation ${continuationCount}/${MAX_CONTINUATIONS} for ${conversationId}`);
88
+ currentTask = "Continue";
89
+ currentFiles = void 0;
90
+ continue;
71
91
  }
72
- });
92
+ break;
93
+ }
73
94
  if (this.adapter.autoReply) {
74
- await this.adapter.sendReply(message.threadRef, result.response, {
75
- files: result.files
95
+ await this.adapter.sendReply(message.threadRef, accumulatedResponse, {
96
+ files: accumulatedFiles.length > 0 ? accumulatedFiles : void 0
76
97
  });
77
98
  } else if (!this.adapter.hasSentInCurrentRequest) {
78
99
  console.warn("[agent-bridge] tool mode completed without send_email being called; no reply sent");
@@ -1136,12 +1157,14 @@ var TelegramAdapter = class {
1136
1157
  botId = 0;
1137
1158
  botTokenEnv;
1138
1159
  webhookSecretEnv;
1160
+ allowedUserIds;
1139
1161
  handler;
1140
1162
  sessionCounters = /* @__PURE__ */ new Map();
1141
1163
  lastUpdateId = 0;
1142
1164
  constructor(options = {}) {
1143
1165
  this.botTokenEnv = options.botTokenEnv ?? "TELEGRAM_BOT_TOKEN";
1144
1166
  this.webhookSecretEnv = options.webhookSecretEnv ?? "TELEGRAM_WEBHOOK_SECRET";
1167
+ this.allowedUserIds = options.allowedUserIds && options.allowedUserIds.length > 0 ? options.allowedUserIds : void 0;
1145
1168
  }
1146
1169
  // -----------------------------------------------------------------------
1147
1170
  // MessagingAdapter implementation
@@ -1247,6 +1270,13 @@ var TelegramAdapter = class {
1247
1270
  res.end();
1248
1271
  return;
1249
1272
  }
1273
+ if (this.allowedUserIds && message.from) {
1274
+ if (!this.allowedUserIds.includes(message.from.id)) {
1275
+ res.writeHead(200);
1276
+ res.end();
1277
+ return;
1278
+ }
1279
+ }
1250
1280
  const chatId = String(message.chat.id);
1251
1281
  const chatType = message.chat.type;
1252
1282
  const isGroup = chatType === "group" || chatType === "supergroup";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/messaging",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Messaging platform adapters for Poncho agents (Slack, Telegram, etc.)",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,6 +29,7 @@ const NEW_COMMAND_RE = /^\/new(?:@(\S+))?$/i;
29
29
  export interface TelegramAdapterOptions {
30
30
  botTokenEnv?: string;
31
31
  webhookSecretEnv?: string;
32
+ allowedUserIds?: number[];
32
33
  }
33
34
 
34
35
  const collectBody = (req: http.IncomingMessage): Promise<string> =>
@@ -50,6 +51,7 @@ export class TelegramAdapter implements MessagingAdapter {
50
51
  private botId = 0;
51
52
  private readonly botTokenEnv: string;
52
53
  private readonly webhookSecretEnv: string;
54
+ private readonly allowedUserIds: number[] | undefined;
53
55
  private handler: IncomingMessageHandler | undefined;
54
56
  private readonly sessionCounters = new Map<string, number>();
55
57
  private lastUpdateId = 0;
@@ -58,6 +60,10 @@ export class TelegramAdapter implements MessagingAdapter {
58
60
  this.botTokenEnv = options.botTokenEnv ?? "TELEGRAM_BOT_TOKEN";
59
61
  this.webhookSecretEnv =
60
62
  options.webhookSecretEnv ?? "TELEGRAM_WEBHOOK_SECRET";
63
+ this.allowedUserIds =
64
+ options.allowedUserIds && options.allowedUserIds.length > 0
65
+ ? options.allowedUserIds
66
+ : undefined;
61
67
  }
62
68
 
63
69
  // -----------------------------------------------------------------------
@@ -197,6 +203,15 @@ export class TelegramAdapter implements MessagingAdapter {
197
203
  return;
198
204
  }
199
205
 
206
+ // -- User allowlist -----------------------------------------------------
207
+ if (this.allowedUserIds && message.from) {
208
+ if (!this.allowedUserIds.includes(message.from.id)) {
209
+ res.writeHead(200);
210
+ res.end();
211
+ return;
212
+ }
213
+ }
214
+
200
215
  const chatId = String(message.chat.id);
201
216
  const chatType = message.chat.type;
202
217
  const isGroup = chatType === "group" || chatType === "supergroup";
package/src/bridge.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import type {
3
3
  AgentBridgeOptions,
4
+ FileAttachment,
4
5
  IncomingMessage,
5
6
  MessagingAdapter,
6
7
  AgentRunner,
@@ -53,6 +54,7 @@ export class AgentBridge {
53
54
  }
54
55
 
55
56
  private async handleMessage(message: IncomingMessage): Promise<void> {
57
+ const MAX_CONTINUATIONS = 10;
56
58
  let cleanup: (() => Promise<void>) | undefined;
57
59
 
58
60
  this.adapter.resetRequestState?.();
@@ -71,36 +73,64 @@ export class AgentBridge {
71
73
  if (message.subject) titleParts.push(message.subject);
72
74
  const title = titleParts.join(" ") || `${message.platform} thread`;
73
75
 
74
- const conversation = await this.runner.getOrCreateConversation(
75
- conversationId,
76
- {
77
- platform: message.platform,
78
- ownerId: this.ownerIdOverride ?? message.sender.id,
79
- title,
80
- },
81
- );
82
-
83
76
  const senderLine = message.sender.name
84
77
  ? `From: ${message.sender.name} <${message.sender.id}>`
85
78
  : `From: ${message.sender.id}`;
86
79
  const subjectLine = message.subject ? `Subject: ${message.subject}` : "";
87
80
  const header = [senderLine, subjectLine].filter(Boolean).join("\n");
88
- const task = `${header}\n\n${message.text}`;
89
-
90
- const result = await this.runner.run(conversationId, {
91
- task,
92
- messages: conversation.messages,
93
- files: message.files,
94
- metadata: {
95
- platform: message.platform,
96
- sender: message.sender,
97
- threadId: message.threadRef.platformThreadId,
98
- },
99
- });
81
+
82
+ let currentTask = `${header}\n\n${message.text}`;
83
+ let currentFiles = message.files;
84
+ let accumulatedResponse = "";
85
+ let accumulatedFiles: FileAttachment[] = [];
86
+ let totalSteps = 0;
87
+ let stepBudget = 0;
88
+ let continuationCount = 0;
89
+
90
+ while (true) {
91
+ const conversation = await this.runner.getOrCreateConversation(
92
+ conversationId,
93
+ {
94
+ platform: message.platform,
95
+ ownerId: this.ownerIdOverride ?? message.sender.id,
96
+ title,
97
+ },
98
+ );
99
+
100
+ const result = await this.runner.run(conversationId, {
101
+ task: currentTask,
102
+ messages: conversation.messages,
103
+ files: currentFiles,
104
+ metadata: {
105
+ platform: message.platform,
106
+ sender: message.sender,
107
+ threadId: message.threadRef.platformThreadId,
108
+ },
109
+ });
110
+
111
+ accumulatedResponse += result.response;
112
+ if (result.files) accumulatedFiles.push(...result.files);
113
+ totalSteps += result.steps ?? 0;
114
+ if (typeof result.maxSteps === "number") stepBudget = result.maxSteps;
115
+
116
+ if (
117
+ result.continuation &&
118
+ continuationCount < MAX_CONTINUATIONS &&
119
+ (stepBudget <= 0 || totalSteps < stepBudget)
120
+ ) {
121
+ continuationCount++;
122
+ console.log(`[agent-bridge] continuation ${continuationCount}/${MAX_CONTINUATIONS} for ${conversationId}`);
123
+ currentTask = "Continue";
124
+ currentFiles = undefined;
125
+ continue;
126
+ }
127
+
128
+ break;
129
+ }
100
130
 
101
131
  if (this.adapter.autoReply) {
102
- await this.adapter.sendReply(message.threadRef, result.response, {
103
- files: result.files,
132
+ await this.adapter.sendReply(message.threadRef, accumulatedResponse, {
133
+ files: accumulatedFiles.length > 0 ? accumulatedFiles : undefined,
104
134
  });
105
135
  } else if (!this.adapter.hasSentInCurrentRequest) {
106
136
  console.warn("[agent-bridge] tool mode completed without send_email being called; no reply sent");
package/src/types.ts CHANGED
@@ -123,6 +123,9 @@ export interface AgentRunner {
123
123
  ): Promise<{
124
124
  response: string;
125
125
  files?: FileAttachment[];
126
+ continuation?: boolean;
127
+ steps?: number;
128
+ maxSteps?: number;
126
129
  }>;
127
130
  }
128
131