@llblab/pi-telegram 0.2.9 → 0.2.10

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.
@@ -11,6 +11,7 @@ import {
11
11
  buildTelegramTurnPrompt,
12
12
  formatTelegramTurnStatusSummary,
13
13
  truncateTelegramQueueSummary,
14
+ updateTelegramPromptTurnText,
14
15
  } from "../lib/turns.ts";
15
16
 
16
17
  test("Turn helpers truncate queue summaries predictably", () => {
@@ -69,6 +70,120 @@ test("Turn helpers summarize text and attachment-only turns", () => {
69
70
  );
70
71
  });
71
72
 
73
+ test("Turn helpers update queued prompt text for edited Telegram messages", () => {
74
+ const turn = {
75
+ kind: "prompt" as const,
76
+ chatId: 99,
77
+ replyToMessageId: 10,
78
+ sourceMessageIds: [10],
79
+ queueOrder: 1,
80
+ queueLane: "default" as const,
81
+ laneOrder: 1,
82
+ queuedAttachments: [],
83
+ content: [{ type: "text" as const, text: "[telegram] old" }],
84
+ historyText: "old",
85
+ statusSummary: "old",
86
+ };
87
+ const updated = updateTelegramPromptTurnText({
88
+ turn,
89
+ telegramPrefix: "[telegram]",
90
+ rawText: "new edited message",
91
+ });
92
+ assert.equal(updated.content[0]?.type, "text");
93
+ assert.equal(
94
+ (updated.content[0] as { type: "text"; text: string }).text,
95
+ "[telegram] new edited message",
96
+ );
97
+ assert.equal(updated.historyText, "new edited message");
98
+ assert.equal(updated.statusSummary, "new edited message");
99
+ assert.notEqual(updated, turn);
100
+ });
101
+
102
+ test("Turn helpers preserve queued prompt attachments when captions are edited", () => {
103
+ const turn = {
104
+ kind: "prompt" as const,
105
+ chatId: 99,
106
+ replyToMessageId: 10,
107
+ sourceMessageIds: [10],
108
+ queueOrder: 1,
109
+ queueLane: "default" as const,
110
+ laneOrder: 1,
111
+ queuedAttachments: [],
112
+ content: [
113
+ {
114
+ type: "text" as const,
115
+ text:
116
+ "[telegram] old caption\n\n" +
117
+ "Telegram attachments were saved locally:\n" +
118
+ "- /tmp/demo.png\n" +
119
+ "- /tmp/report.txt",
120
+ },
121
+ { type: "image" as const, data: "abc", mimeType: "image/png" },
122
+ ],
123
+ historyText: "old caption\nAttachments:\n- /tmp/demo.png\n- /tmp/report.txt",
124
+ statusSummary: "old caption",
125
+ };
126
+ const updated = updateTelegramPromptTurnText({
127
+ turn,
128
+ telegramPrefix: "[telegram]",
129
+ rawText: "new caption",
130
+ });
131
+ assert.equal(
132
+ (updated.content[0] as { type: "text"; text: string }).text,
133
+ "[telegram] new caption\n\n" +
134
+ "Telegram attachments were saved locally:\n" +
135
+ "- /tmp/demo.png\n" +
136
+ "- /tmp/report.txt",
137
+ );
138
+ assert.deepEqual(updated.content[1], turn.content[1]);
139
+ assert.equal(
140
+ updated.historyText,
141
+ "new caption\nAttachments:\n- /tmp/demo.png\n- /tmp/report.txt",
142
+ );
143
+ assert.equal(updated.statusSummary, "new caption");
144
+ });
145
+
146
+ test("Turn helpers preserve abort-history prompt context when queued turns are edited", () => {
147
+ const turn = {
148
+ kind: "prompt" as const,
149
+ chatId: 99,
150
+ replyToMessageId: 10,
151
+ sourceMessageIds: [10],
152
+ queueOrder: 1,
153
+ queueLane: "default" as const,
154
+ laneOrder: 1,
155
+ queuedAttachments: [],
156
+ content: [
157
+ {
158
+ type: "text" as const,
159
+ text:
160
+ "[telegram]\n\n" +
161
+ "Earlier Telegram messages arrived after an aborted turn. " +
162
+ "Treat them as prior user messages, in order:\n\n" +
163
+ "1. older Current Telegram message: quote\n\n" +
164
+ "Current Telegram message:\nold current",
165
+ },
166
+ ],
167
+ historyText: "old current",
168
+ statusSummary: "old current",
169
+ };
170
+ const updated = updateTelegramPromptTurnText({
171
+ turn,
172
+ telegramPrefix: "[telegram]",
173
+ rawText: "new current",
174
+ });
175
+ assert.equal(
176
+ (updated.content[0] as { type: "text"; text: string }).text,
177
+ "[telegram]\n\n" +
178
+ "Earlier Telegram messages arrived after an aborted turn. " +
179
+ "Treat them as prior user messages, in order:\n\n" +
180
+ "1. older Current Telegram message: quote\n\n" +
181
+ "Current Telegram message:\nnew current",
182
+ );
183
+ assert.equal(updated.historyText, "new current");
184
+ assert.equal(updated.statusSummary, "new current");
185
+ });
186
+
72
187
  test("Turn helpers assemble prompt turns with text, ids, history, and image payloads", async () => {
73
188
  const turn = await buildTelegramPromptTurn({
74
189
  telegramPrefix: "[telegram]",
@@ -15,6 +15,7 @@ import {
15
15
  executeTelegramUpdatePlan,
16
16
  extractDeletedTelegramMessageIds,
17
17
  getAuthorizedTelegramCallbackQuery,
18
+ getAuthorizedTelegramEditedMessage,
18
19
  getAuthorizedTelegramMessage,
19
20
  getTelegramAuthorizationState,
20
21
  normalizeTelegramReactionEmoji,
@@ -74,7 +75,7 @@ test("Update routing extracts only private human callback queries", () => {
74
75
  assert.ok(query);
75
76
  });
76
77
 
77
- test("Update routing extracts private human messages from message or edited_message", () => {
78
+ test("Update routing extracts private human messages and edited messages separately", () => {
78
79
  assert.equal(
79
80
  getAuthorizedTelegramMessage({
80
81
  message: {
@@ -85,12 +86,19 @@ test("Update routing extracts private human messages from message or edited_mess
85
86
  undefined,
86
87
  );
87
88
  const directMessage = getAuthorizedTelegramMessage({
88
- edited_message: {
89
+ message: {
89
90
  chat: { type: "private" },
90
91
  from: { id: 1, is_bot: false },
91
92
  },
92
93
  });
93
94
  assert.ok(directMessage);
95
+ const editedMessage = getAuthorizedTelegramEditedMessage({
96
+ edited_message: {
97
+ chat: { type: "private" },
98
+ from: { id: 1, is_bot: false },
99
+ },
100
+ });
101
+ assert.ok(editedMessage);
94
102
  });
95
103
 
96
104
  test("Update flow prioritizes deleted business-message handling over other update kinds", () => {
@@ -107,7 +115,7 @@ test("Update flow prioritizes deleted business-message handling over other updat
107
115
  assert.deepEqual(action, { kind: "deleted", messageIds: [1, 2] });
108
116
  });
109
117
 
110
- test("Update flow returns authorized callback and message actions", () => {
118
+ test("Update flow returns authorized callback, message, and edit actions", () => {
111
119
  const callbackAction = buildTelegramUpdateFlowAction(
112
120
  {
113
121
  callback_query: {
@@ -135,6 +143,16 @@ test("Update flow returns authorized callback and message actions", () => {
135
143
  messageAction.kind === "message" ? messageAction.authorization : undefined,
136
144
  { kind: "pair", userId: 9 },
137
145
  );
146
+ const editAction = buildTelegramUpdateFlowAction(
147
+ {
148
+ edited_message: {
149
+ chat: { type: "private" },
150
+ from: { id: 9, is_bot: false },
151
+ },
152
+ },
153
+ 9,
154
+ );
155
+ assert.equal(editAction.kind, "edited-message");
138
156
  });
139
157
 
140
158
  test("Update flow ignores unauthorized transport shapes and preserves reaction events", () => {
@@ -239,6 +257,7 @@ test("Update runtime executes delete and reaction plans through the right side e
239
257
  handleAuthorizedTelegramCallbackQuery: async () => {},
240
258
  sendTextReply: async () => undefined,
241
259
  handleAuthorizedTelegramMessage: async () => {},
260
+ handleAuthorizedTelegramEditedMessage: async () => {},
242
261
  },
243
262
  );
244
263
  assert.deepEqual(events, ["media:1,2", "queue:1,2"]);
@@ -273,6 +292,9 @@ test("Update runtime can execute directly from raw updates", async () => {
273
292
  handleAuthorizedTelegramMessage: async () => {
274
293
  events.push("message");
275
294
  },
295
+ handleAuthorizedTelegramEditedMessage: async () => {
296
+ events.push("edited-message");
297
+ },
276
298
  },
277
299
  );
278
300
  assert.deepEqual(events, [
@@ -282,6 +304,37 @@ test("Update runtime can execute directly from raw updates", async () => {
282
304
  ]);
283
305
  });
284
306
 
307
+ test("Update runtime routes edited messages without creating normal message turns", async () => {
308
+ const events: string[] = [];
309
+ await executeTelegramUpdate(
310
+ {
311
+ edited_message: {
312
+ chat: { id: 10, type: "private" },
313
+ message_id: 20,
314
+ from: { id: 7, is_bot: false },
315
+ },
316
+ },
317
+ 7,
318
+ {
319
+ ctx: {} as never,
320
+ removePendingMediaGroupMessages: () => {},
321
+ removeQueuedTelegramTurnsByMessageIds: () => 0,
322
+ handleAuthorizedTelegramReactionUpdate: async () => {},
323
+ pairTelegramUserIfNeeded: async () => false,
324
+ answerCallbackQuery: async () => {},
325
+ handleAuthorizedTelegramCallbackQuery: async () => {},
326
+ sendTextReply: async () => undefined,
327
+ handleAuthorizedTelegramMessage: async () => {
328
+ events.push("message");
329
+ },
330
+ handleAuthorizedTelegramEditedMessage: async () => {
331
+ events.push("edited-message");
332
+ },
333
+ },
334
+ );
335
+ assert.deepEqual(events, ["edited-message"]);
336
+ });
337
+
285
338
  test("Update runtime handles callback deny and message pair flows", async () => {
286
339
  const events: string[] = [];
287
340
  await executeTelegramUpdatePlan(
@@ -317,6 +370,9 @@ test("Update runtime handles callback deny and message pair flows", async () =>
317
370
  handleAuthorizedTelegramMessage: async () => {
318
371
  events.push("message");
319
372
  },
373
+ handleAuthorizedTelegramEditedMessage: async () => {
374
+ events.push("edited-message");
375
+ },
320
376
  },
321
377
  );
322
378
  await executeTelegramUpdatePlan(
@@ -346,6 +402,9 @@ test("Update runtime handles callback deny and message pair flows", async () =>
346
402
  handleAuthorizedTelegramMessage: async () => {
347
403
  events.push("message");
348
404
  },
405
+ handleAuthorizedTelegramEditedMessage: async () => {
406
+ events.push("edited-message");
407
+ },
349
408
  },
350
409
  );
351
410
  assert.deepEqual(events, [