@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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +11 -0
- package/README.md +12 -9
- package/docs/architecture.md +16 -12
- package/index.ts +191 -246
- package/lib/api.ts +277 -42
- package/lib/commands.ts +87 -0
- package/lib/media.ts +70 -1
- package/lib/polling.ts +25 -5
- package/lib/preview.ts +31 -4
- package/lib/rendering.ts +105 -5
- package/lib/turns.ts +86 -0
- package/lib/types.ts +137 -0
- package/lib/updates.ts +64 -2
- package/package.json +1 -1
- package/tests/api.test.ts +243 -1
- package/tests/commands.test.ts +85 -0
- package/tests/media.test.ts +90 -1
- package/tests/polling.test.ts +73 -0
- package/tests/preview.test.ts +39 -0
- package/tests/rendering.test.ts +51 -0
- package/tests/turns.test.ts +115 -0
- package/tests/updates.test.ts +62 -3
package/tests/turns.test.ts
CHANGED
|
@@ -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]",
|
package/tests/updates.test.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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, [
|