@llblab/pi-kit 0.10.5 β 0.10.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.
- package/CHANGELOG.md +8 -0
- package/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -0
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +12 -0
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +2 -0
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +2 -2
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +9 -1
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +9 -0
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +4 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +13 -13
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +0 -1
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +41 -18
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +261 -4
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +23 -10
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +58 -10
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +40 -20
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@llblab/pi-kit` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.10.7 - 2026-09-12
|
|
6
|
+
|
|
7
|
+
- `Queue Enqueue`: Advances the exact Telegram pin to `0.45.7`, preventing asynchronous voice/file preparation from restoring consumed prompts or overwriting newer queue changes. Surviving history and receipts follow current queue state; an already-wedged in-memory queue still requires separately authorized recovery. The package set, resource inventory, and load order remain unchanged.
|
|
8
|
+
|
|
9
|
+
## 0.10.6 - 2026-09-12
|
|
10
|
+
|
|
11
|
+
- `Guest Placeholder And Delivery`: Advances the exact Telegram pin to `0.45.6`, so the guest ACK rotates the globe placeholder in whole cycles and holds `π Working on it...`, guest answers are still delivered when a companion extension suppresses the final assistant message, `429` retry waits are recorded as `api` runtime events, the Thread display settings card drops the manual `/name` hint, and empty-queue headings carry no trailing period. The package set, resource inventory, and explicit load order remain unchanged.
|
|
12
|
+
|
|
5
13
|
## 0.10.5 - 2026-09-11
|
|
6
14
|
|
|
7
15
|
- `State Flow Telegram Surface`: Advances the exact State Flow pin to `0.9.6`, so the submenu help line is exactly what State Flow records and when a new session resumes from the last committed point, and the state value (`off` or `#N`) renders in monospace. The package set, resource inventory, and explicit load order remain unchanged.
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Package links lead to the owning repositories for usage, documentation, issues,
|
|
|
15
15
|
| [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.9.4` | Compact Codex/Spark subscription-limit status |
|
|
16
16
|
| [`@llblab/pi-grow-loop`](https://github.com/llblab/pi-grow-loop) | `0.8.1` | Visible continuation scheduling and bounded worker Skills |
|
|
17
17
|
| [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.9.6` | Atomic scoped state cohorts with final-eligibility resolution |
|
|
18
|
-
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.45.
|
|
18
|
+
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.45.7` | Telegram companion, queues, files, voice, controls, and Generative Apps guidance |
|
|
19
19
|
| [`@llblab/skills`](https://github.com/llblab/skills) | `1.15.0` | Portable workflows for engineering, review, design, context maintenance, and other focused tasks |
|
|
20
20
|
|
|
21
21
|
Versions are exact by design. An upstream release does not change an installed kit until this repository explicitly advances the dependency and publishes a new kit version. Runtime defects and package-specific feature requests belong in the linked repository; package selection and kit installation issues belong here.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
_This file owns unresolved project work only. Completed behavior belongs in `CHANGELOG.md`; durable contracts belong in `AGENTS.md` and `/docs`._
|
|
4
4
|
|
|
5
|
+
- [ ] `Prompt enqueue hotfix` (`optional`, `operator-gated`): Optional nonblocking operator-authorized disposable-follower smoke: overlap delayed voice processing with turn completion and confirm one-time ordered consumption and truthful counts against the [queue contract](./docs/architecture.md#queue-and-dispatch-safety). Separately authorized supported recovery investigation remains open: prevention does not repair an already-wedged in-memory queue; establish the exact recovery path and preservation/discard consequences before mutation, otherwise report the blocker. No journal/ownership edits, replay of settled input, implicit queue clearing, or restart; live activation requires separate operator authorization.
|
|
5
6
|
- [ ] `Channel multimedia posts` (`0.45.1`, live-acceptance-gated): `telegram_message` channel delivery accepts one local `.jpg`/`.jpeg`/`.png`/`.webp` photo or `.mp4` video, uploads it through the multipart transport as `sendPhoto`/`sendVideo` with `text` as the HTML caption, validates kind and size (photo β€ 10 MiB, video β€ 50 MiB) plus β€ 1024 visible caption characters before issuance, and rejects unsupported types and albums instead of downgrading them to links. The channel-post journal binds kind/file name/byte size/SHA-256 and caption, so duplicate requests and lost acknowledgements never re-upload; media-post edits replace the caption through `editMessageCaption`, and Markdown spoilers render as `<tg-spoiler>`. Live image publication passed on `@llb_log`. Regressions cover confirmed publication, duplicate requests, lost ACK, pre-issuance rejection, caption edits, and reconnect replacement. Remaining: operator-authorized disposable-channel acceptance of rejected upload, duplicate request, and caption edit.
|
|
6
7
|
- [ ] `Manual Thread naming` (`gated-but-preparable`, release priority): Local bot-owned `/name Name` and bare `/name` flows avoid model dispatch. One expiring exact-target input dialog immediately accepts the next valid name, always offers cancel, and offers **Reset to automatic** only while a manual override exists; duplicate/stale callbacks cannot repeat mutation. Durable manual override supersedes every automatic display mode, reset is leader/follower generation- and target-fenced, and Letters remains the default without rewriting recovery identity. Local review findings are remediated, including Bot-API-wait target-replacement regressions for leader/follower rename and reset. Remaining: disposable live acceptance for command-menu ordering, dialog, invalid input, duplicate callbacks, leader/follower rename and reset.
|
|
7
8
|
- [ ] `OMP schema acceptance` ([#267](https://github.com/llblab/pi-telegram/issues/267), `human-/environment-gated`): Local emitted-schema, Pi process, and llama.cpp source checks now prove explicit recursive JSON values, root `$defs`, only supported local `#/...` references, and no bare boolean schema. Confirm one connected `telegram_bind` request through the reporter's OMP + llama-server build before closing interoperability acceptance; do not treat this progressive external check as a Pi release blocker.
|
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.45.7: Queue Enqueue Race Hotfix
|
|
8
|
+
|
|
9
|
+
- `Queue Enqueue`: Asynchronous voice/file preparation no longer restores consumed prompts or overwrites newer queue changes, preventing a settled phantom head from blocking accepted work. Final assembly uses current queue state and allocates order at commit; abort-history folding retains only surviving intended prompts and receipts while preserving a handed-off head until `agent_start`.
|
|
10
|
+
|
|
11
|
+
## 0.45.6: Guest Placeholder And Delivery Hotfix
|
|
12
|
+
|
|
13
|
+
- `Guest Placeholder`: The guest ACK answers with the first bold globe frame (`π Working on it.`) and rotates `π`/`π` once per second with the dots growing every two seconds. Rotation now completes whole 6-frame cycles and stops only after the 20 s minimum, so a pending answer holds the cycle's final frame (`π Working on it...`) instead of cutting mid-step; a 26 s safety bound stays clear of the ~28 s Telegram flood-control wall, and error backoff and pre-replacement cancellation are unchanged.
|
|
14
|
+
- `Guest Answer Delivery`: The guest replacement now falls back to the run's latest completed assistant text when Pi's final assistant message is empty, so an answer preserved by a companion extension's fallback turn (for example State Flow's suppressed final:true patch turn) is still edited into the guest message instead of being skipped as "no editable text".
|
|
15
|
+
- `Rate-Limit Visibility`: A `429` retry wait now records an `api` runtime event with the method, wait duration, attempt, and server `retry_after`, so a silent flood-control pause before the final guest replacement is visible in the runtime log instead of appearing only as a frozen frame.
|
|
16
|
+
- `Thread Display Hint`: The Thread display settings card no longer carries the manual `/name Name` override hint; the current-value line, the three mode descriptions, and the live chooser are unchanged.
|
|
17
|
+
- `Queue Empty Headings`: The default empty-queue line and every rotating refresh title drop the trailing period, so the fully bold queue headings read as headings; wording, emoji, rotation order, callbacks, and refresh behavior are unchanged.
|
|
18
|
+
|
|
7
19
|
## 0.45.5: Queue Refresh Icon Hotfix
|
|
8
20
|
|
|
9
21
|
- `Queue Refresh Icon`: The queue menu's Refresh row now uses `π`, the canonical refresh glyph, reserving `π` for the State Flow Telegram identity. The button label is the only change; queue refresh behavior, callbacks, and the rotating empty-queue notices are unchanged.
|
|
@@ -480,6 +480,8 @@ Menu and reaction controls share the same canonical queue state. A menu Keep can
|
|
|
480
480
|
|
|
481
481
|
Admission and planning validate lane contracts. Invalid lane/kind pairings fail predictably instead of being silently coerced.
|
|
482
482
|
|
|
483
|
+
Prompt preparation completes downloads, inbound handlers, and binary image reads before synchronous final assembly and queue commit. Enqueue captures only intended abort-history identities before preparation; afterward it reads the current queue, folds surviving selected prompts using their current text and receipts, allocates the new turn's order, and appends without another asynchronous boundary. The handed-off head remains in place until `agent_start` consumes it, even if preparation finishes first. Concurrent arrivals, removals, and lane/reaction edits remain intact; preparation failure or stale-generation completion leaves intervening queue changes untouched.
|
|
484
|
+
|
|
483
485
|
Dispatch requires:
|
|
484
486
|
|
|
485
487
|
- No active Telegram turn.
|
|
@@ -104,7 +104,7 @@ The menu may clear internal Skip but cannot remove a reaction created by the use
|
|
|
104
104
|
|
|
105
105
|
### Decorative Or Local-Example Emoji
|
|
106
106
|
|
|
107
|
-
Some emoji are intentionally local examples or decorative variants, not global semantics. Empty-queue rotating messages (`π«`, `π`, `π³`, `π¦`, `π`, `π§`, `πͺ`, `π§Ί`, `π`, `π«§`, `πΈ`) are copy flavor only and must not become controls. Example extension icons such as `π§ͺ`, `π§`, and `π` are documentation fixtures for companion extensions, not built-in pi-telegram meanings.
|
|
107
|
+
Some emoji are intentionally local examples or decorative variants, not global semantics. Empty-queue rotating messages (`π«`, `π`, `π³`, `π¦`, `π`, `π§`, `πͺ`, `π§Ί`, `π`, `π«§`, `πΈ`) are copy flavor only and must not become controls. The Guest Mode placeholder frames (`π`, `π`, `π` stepping every second with dots growing once every two seconds) are the same kind of decorative copy: they complete whole 6-frame cycles over at least a ~20 s rotation while a guest answer is pending and then hold the cycle's final frame (a 26 s safety bound caps slow streams), never become controls, and must not carry another meaning. Example extension icons such as `π§ͺ`, `π§`, and `π` are documentation fixtures for companion extensions, not built-in pi-telegram meanings.
|
|
108
108
|
|
|
109
109
|
Thread UI rule: when a message heading, chooser, or status line is specifically about Telegram/Pi threads or target thread selection, start the heading with `π§΅`. Button labels for concrete thread targets should stay clean (`threadName` or slot fallback) and should not add `π§΅` to every target button unless the row would otherwise be ambiguous.
|
|
110
110
|
|
|
@@ -224,7 +224,7 @@ Message cards and standalone informational notices sent by the bot should start
|
|
|
224
224
|
Rules:
|
|
225
225
|
|
|
226
226
|
- Start with a bold heading or, for dialogs, a bold question.
|
|
227
|
-
- Format standalone notices as one fully bold line: relevant emoji, one space, concise sentence, and terminal period. Menu or chooser headings use the same fully bold form but end in a colon when controls or detail follow.
|
|
227
|
+
- Format standalone notices as one fully bold line: relevant emoji, one space, concise sentence, and terminal period. Menu or chooser headings use the same fully bold form but end in a colon when controls or detail follow. Empty-queue headings are the deliberate exception: fully bold, with no trailing period or colon.
|
|
228
228
|
- Keep the emoji and complete sentence or heading inside the single bold span; do not bold only a fragment. A material name or phrase may receive nested italic emphasis without breaking the outer bold hierarchyβfor example `<b>π‘ Instance <i>Cedar</i> connected.</b>`.
|
|
229
229
|
- Apply the same hierarchy to success, progress, empty, busy, unavailable, cancellation, and failure notices.
|
|
230
230
|
- Callback alerts remain plain text because Telegram does not support rich text there, but still keep the relevant emoji and concise sentence.
|
|
@@ -823,6 +823,12 @@ interface TelegramLifecycleBindingDeps {
|
|
|
823
823
|
Pi.AgentEndEvent["messages"][number],
|
|
824
824
|
Keyboard.TelegramInlineKeyboardMarkup
|
|
825
825
|
>["editGuestReply"];
|
|
826
|
+
stopGuestPlaceholder?: Queue.TelegramAgentEndHookRuntimeDeps<
|
|
827
|
+
Queue.PendingTelegramTurn,
|
|
828
|
+
Pi.ExtensionContext,
|
|
829
|
+
Pi.AgentEndEvent["messages"][number],
|
|
830
|
+
Keyboard.TelegramInlineKeyboardMarkup
|
|
831
|
+
>["stopGuestPlaceholder"];
|
|
826
832
|
preparePreviewDelivery?: Queue.TelegramAgentEndRuntimeDeps<Queue.PendingTelegramTurn>["preparePreviewDelivery"];
|
|
827
833
|
finalizeMarkdownPreview: Queue.TelegramAgentEndHookRuntimeDeps<
|
|
828
834
|
Queue.PendingTelegramTurn,
|
|
@@ -876,6 +882,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
876
882
|
deleteMessage,
|
|
877
883
|
sendGuestReply,
|
|
878
884
|
editGuestReply,
|
|
885
|
+
stopGuestPlaceholder,
|
|
879
886
|
preparePreviewDelivery,
|
|
880
887
|
finalizeMarkdownPreview,
|
|
881
888
|
proactivePushTargetGetter,
|
|
@@ -1056,7 +1063,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1056
1063
|
updateStatus,
|
|
1057
1064
|
getActiveTurn: activeTurnRuntime.get,
|
|
1058
1065
|
loadConfig: configStore.load,
|
|
1059
|
-
extractAssistant: Replies.
|
|
1066
|
+
extractAssistant: Replies.extractRunAssistantMessage,
|
|
1060
1067
|
getFoldQueuedPromptsIntoHistory:
|
|
1061
1068
|
lifecycle.shouldFoldQueuedPromptsIntoHistory,
|
|
1062
1069
|
resetRuntimeState: agentEndResetter,
|
|
@@ -1090,6 +1097,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1090
1097
|
answerGuestQuery,
|
|
1091
1098
|
sendGuestReply,
|
|
1092
1099
|
editGuestReply,
|
|
1100
|
+
stopGuestPlaceholder,
|
|
1093
1101
|
sendGuestAttachment,
|
|
1094
1102
|
sendGuestVoiceReply,
|
|
1095
1103
|
planOutboundReply: outboundReplyPlanner,
|
|
@@ -1429,7 +1429,7 @@ export async function handleTelegramNextCommand(deps: {
|
|
|
1429
1429
|
deps.clearPendingModelSwitch();
|
|
1430
1430
|
if (!deps.hasQueuedItems()) {
|
|
1431
1431
|
await deps.sendTextReply(
|
|
1432
|
-
formatTelegramInformationHeading("β", "Queue is empty
|
|
1432
|
+
formatTelegramInformationHeading("β", "Queue is empty"),
|
|
1433
1433
|
{ parseMode: "HTML" },
|
|
1434
1434
|
);
|
|
1435
1435
|
return;
|
|
@@ -588,6 +588,12 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
588
588
|
const editGuestReply = Replies.createGuestMarkdownReplyEditor({
|
|
589
589
|
editGuestInlineMessage: telegramApiRuntime.editGuestInlineMessage,
|
|
590
590
|
});
|
|
591
|
+
// Rotate the guest placeholder frames until the final replacement stops them.
|
|
592
|
+
const guestPlaceholderRuntime =
|
|
593
|
+
Replies.createTelegramGuestPlaceholderRuntime({
|
|
594
|
+
editGuestInlineMessage: telegramApiRuntime.editGuestInlineMessage,
|
|
595
|
+
recordRuntimeEvent,
|
|
596
|
+
});
|
|
591
597
|
|
|
592
598
|
const promptDispatchRuntime = Runtime.createTelegramPromptDispatchRuntime({
|
|
593
599
|
lifecycle,
|
|
@@ -978,6 +984,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
978
984
|
deleteMessage: deleteTelegramMessage,
|
|
979
985
|
answerGuestQuery,
|
|
980
986
|
answerGuestQueryForInlineMessage,
|
|
987
|
+
startGuestPlaceholder: guestPlaceholderRuntime.start,
|
|
981
988
|
sendTextReply,
|
|
982
989
|
setMyCommands,
|
|
983
990
|
validateThreadName(threadName) {
|
|
@@ -1508,6 +1515,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
1508
1515
|
},
|
|
1509
1516
|
capabilityMonitor: telegramThreadCapabilityMonitor,
|
|
1510
1517
|
queueWatchdog: queueDispatchWatchdogRuntime,
|
|
1518
|
+
guestPlaceholder: { stopAll: guestPlaceholderRuntime.stopAll },
|
|
1511
1519
|
},
|
|
1512
1520
|
});
|
|
1513
1521
|
const sessionLifecycleRuntime =
|
|
@@ -1828,6 +1836,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
1828
1836
|
deleteMessage: deleteTelegramMessage,
|
|
1829
1837
|
sendGuestReply,
|
|
1830
1838
|
editGuestReply,
|
|
1839
|
+
stopGuestPlaceholder: guestPlaceholderRuntime.stop,
|
|
1831
1840
|
finalizeMarkdownPreview,
|
|
1832
1841
|
preparePreviewDelivery,
|
|
1833
1842
|
proactivePushTargetGetter,
|
|
@@ -265,6 +265,7 @@ export interface TelegramBridgeSessionServiceRuntime {
|
|
|
265
265
|
inboundWorker: { onSessionShutdown(): Promise<void> };
|
|
266
266
|
capabilityMonitor: { start(ctx: ExtensionContext): void; stop(): void };
|
|
267
267
|
queueWatchdog: { start(ctx: ExtensionContext): void; stop(): void };
|
|
268
|
+
guestPlaceholder?: { stopAll(): void };
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
export interface TelegramBridgeSessionLifecycleAssemblyDeps<
|
|
@@ -320,6 +321,7 @@ export interface TelegramBridgeSessionLifecyclePorts<
|
|
|
320
321
|
inboundWorker: TelegramBridgeSessionServiceRuntime["inboundWorker"];
|
|
321
322
|
capabilityMonitor: TelegramBridgeSessionServiceRuntime["capabilityMonitor"];
|
|
322
323
|
queueWatchdog: TelegramBridgeSessionServiceRuntime["queueWatchdog"];
|
|
324
|
+
guestPlaceholder?: TelegramBridgeSessionServiceRuntime["guestPlaceholder"];
|
|
323
325
|
};
|
|
324
326
|
}
|
|
325
327
|
|
|
@@ -347,6 +349,7 @@ export function createTelegramBridgeSessionLifecycleDeps<
|
|
|
347
349
|
inboundWorker: ports.services.inboundWorker,
|
|
348
350
|
capabilityMonitor: ports.services.capabilityMonitor,
|
|
349
351
|
queueWatchdog: ports.services.queueWatchdog,
|
|
352
|
+
guestPlaceholder: ports.services.guestPlaceholder,
|
|
350
353
|
},
|
|
351
354
|
};
|
|
352
355
|
}
|
|
@@ -383,6 +386,7 @@ export function createTelegramBridgeSessionLifecycleAssembly<
|
|
|
383
386
|
},
|
|
384
387
|
async onSessionShutdown(event, ctx) {
|
|
385
388
|
if (!isSessionActive(ctx)) return;
|
|
389
|
+
deps.services.guestPlaceholder?.stopAll();
|
|
386
390
|
await deps.services.delivery.onSessionShutdown();
|
|
387
391
|
if (!isSessionActive(ctx)) return;
|
|
388
392
|
deps.services.queueWatchdog.stop();
|
|
@@ -14,18 +14,18 @@ import * as Queue from "./queue.ts";
|
|
|
14
14
|
const QUEUE_ITEM_PROMPT_HTML_LIMIT = 3600;
|
|
15
15
|
const QUEUE_ITEM_PROMPT_TRUNCATION_SUFFIX = "\n⦠[truncated]";
|
|
16
16
|
const EMPTY_QUEUE_REFRESH_TITLES = [
|
|
17
|
-
"<b>β Queue is still empty
|
|
18
|
-
"<b>π« Still nothing in queue
|
|
19
|
-
"<b>π Queue remains empty
|
|
20
|
-
"<b>π³ Nothing queued yet
|
|
21
|
-
"<b>π¦ Queue crickets continue
|
|
22
|
-
"<b>π Queue is peacefully idle
|
|
23
|
-
"<b>π§ Nothing waiting. Very zen
|
|
24
|
-
"<b>πͺ Queue orbit is clear
|
|
25
|
-
"<b>π§Ί Basket is empty
|
|
26
|
-
"<b>π No prompts on the horizon
|
|
27
|
-
"<b>π«§ Queue bubbles: none
|
|
28
|
-
"<b>πΈ No queued signals detected
|
|
17
|
+
"<b>β Queue is still empty</b>",
|
|
18
|
+
"<b>π« Still nothing in queue</b>",
|
|
19
|
+
"<b>π Queue remains empty</b>",
|
|
20
|
+
"<b>π³ Nothing queued yet</b>",
|
|
21
|
+
"<b>π¦ Queue crickets continue</b>",
|
|
22
|
+
"<b>π Queue is peacefully idle</b>",
|
|
23
|
+
"<b>π§ Nothing waiting. Very zen</b>",
|
|
24
|
+
"<b>πͺ Queue orbit is clear</b>",
|
|
25
|
+
"<b>π§Ί Basket is empty</b>",
|
|
26
|
+
"<b>π No prompts on the horizon</b>",
|
|
27
|
+
"<b>π«§ Queue bubbles: none</b>",
|
|
28
|
+
"<b>πΈ No queued signals detected</b>",
|
|
29
29
|
] as const;
|
|
30
30
|
type TelegramQueueMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
31
31
|
interface TelegramQueueMenuItem {
|
|
@@ -340,7 +340,7 @@ function getTelegramQueueMenuListText(
|
|
|
340
340
|
emptyRefreshIndex?: number,
|
|
341
341
|
): string {
|
|
342
342
|
if (items.length > 0) return "<b>β³ Queue:</b>";
|
|
343
|
-
if (emptyRefreshIndex === undefined) return "<b>β Queue is empty
|
|
343
|
+
if (emptyRefreshIndex === undefined) return "<b>β Queue is empty</b>";
|
|
344
344
|
return EMPTY_QUEUE_REFRESH_TITLES[
|
|
345
345
|
emptyRefreshIndex % EMPTY_QUEUE_REFRESH_TITLES.length
|
|
346
346
|
];
|
|
@@ -184,7 +184,6 @@ export function buildThreadDisplaySettingsText(mode: TelegramThreadDisplayMode):
|
|
|
184
184
|
"<code>-</code> <code>letters</code> (default): show the unique slot, such as <b><i>A</i></b> or <b><i>B</i></b>.",
|
|
185
185
|
"<code>-</code> <code>names</code>: show the generated dictionary name for the slot, such as <b><i>Anchor</i></b> or <b><i>Briar</i></b>.",
|
|
186
186
|
"<code>-</code> <code>directories</code>: show the directory, such as <b><i>extensions</i></b>; shared Workspaces keep slot suffixes, such as <b><i>extensions_a</i></b> and <b><i>extensions_c</i></b>.",
|
|
187
|
-
"A manual <code>/name Name</code> overrides this Thread display name until reset.",
|
|
188
187
|
].join("\n");
|
|
189
188
|
}
|
|
190
189
|
|
|
@@ -1554,6 +1554,8 @@ export interface TelegramAgentEndRuntimeDeps<
|
|
|
1554
1554
|
sendGuestReply?: (guestQueryId: string, markdown: string) => Promise<void>;
|
|
1555
1555
|
/** Replaces the early guest ACK with the final text. */
|
|
1556
1556
|
editGuestReply?: (inlineMessageId: string, markdown: string) => Promise<void>;
|
|
1557
|
+
/** Cancels the animated guest placeholder before the final edit. */
|
|
1558
|
+
stopGuestPlaceholder?: (inlineMessageId: string) => Promise<void>;
|
|
1557
1559
|
sendGuestAttachment?: (
|
|
1558
1560
|
turn: TTurn,
|
|
1559
1561
|
attachment: QueuedAttachment,
|
|
@@ -1633,6 +1635,7 @@ export interface TelegramAgentEndHookRuntimeDeps<
|
|
|
1633
1635
|
answerGuestQuery?: TelegramAgentEndRuntimeDeps<TTurn>["answerGuestQuery"];
|
|
1634
1636
|
sendGuestReply?: TelegramAgentEndRuntimeDeps<TTurn>["sendGuestReply"];
|
|
1635
1637
|
editGuestReply?: TelegramAgentEndRuntimeDeps<TTurn>["editGuestReply"];
|
|
1638
|
+
stopGuestPlaceholder?: TelegramAgentEndRuntimeDeps<TTurn>["stopGuestPlaceholder"];
|
|
1636
1639
|
sendGuestAttachment?: TelegramAgentEndRuntimeDeps<TTurn>["sendGuestAttachment"];
|
|
1637
1640
|
sendGuestVoiceReply?: TelegramAgentEndRuntimeDeps<TTurn>["sendGuestVoiceReply"];
|
|
1638
1641
|
planOutboundReply?: TelegramAgentEndRuntimeDeps<
|
|
@@ -1774,6 +1777,7 @@ export function createTelegramAgentEndHook<
|
|
|
1774
1777
|
answerGuestQuery: deps.answerGuestQuery,
|
|
1775
1778
|
sendGuestReply: deps.sendGuestReply,
|
|
1776
1779
|
editGuestReply: deps.editGuestReply,
|
|
1780
|
+
stopGuestPlaceholder: deps.stopGuestPlaceholder,
|
|
1777
1781
|
sendGuestAttachment: deps.sendGuestAttachment,
|
|
1778
1782
|
sendGuestVoiceReply: deps.sendGuestVoiceReply,
|
|
1779
1783
|
planOutboundReply: deps.planOutboundReply,
|
|
@@ -1866,6 +1870,16 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
1866
1870
|
return;
|
|
1867
1871
|
}
|
|
1868
1872
|
if (turn.guestQueryId) {
|
|
1873
|
+
if (turn.guestInlineMessageId && deps.stopGuestPlaceholder) {
|
|
1874
|
+
try {
|
|
1875
|
+
await deps.stopGuestPlaceholder(turn.guestInlineMessageId);
|
|
1876
|
+
} catch (error) {
|
|
1877
|
+
deps.recordRuntimeEvent?.("delivery", error, {
|
|
1878
|
+
phase: "guest-placeholder-stop",
|
|
1879
|
+
guestQueryId: turn.guestQueryId,
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1869
1883
|
if (turn.guestInlineMessageId && deps.editGuestReply) {
|
|
1870
1884
|
const experimentText = assistant.errorMessage
|
|
1871
1885
|
? "Telegram bridge: Pi failed while processing the request."
|
|
@@ -1875,7 +1889,7 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
1875
1889
|
await deps.editGuestReply(turn.guestInlineMessageId, experimentText);
|
|
1876
1890
|
deps.recordRuntimeEvent?.(
|
|
1877
1891
|
"guest",
|
|
1878
|
-
new Error("Guest ACK
|
|
1892
|
+
new Error("Guest ACK edited the guest answer"),
|
|
1879
1893
|
{ phase: "guest-ack-edited", guestQueryId: turn.guestQueryId },
|
|
1880
1894
|
);
|
|
1881
1895
|
} catch (error) {
|
|
@@ -1887,7 +1901,7 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
1887
1901
|
} else {
|
|
1888
1902
|
deps.recordRuntimeEvent?.(
|
|
1889
1903
|
"delivery",
|
|
1890
|
-
new Error("Guest ACK
|
|
1904
|
+
new Error("Guest ACK turn produced no editable text"),
|
|
1891
1905
|
{ phase: "guest-ack-edit-empty", guestQueryId: turn.guestQueryId },
|
|
1892
1906
|
);
|
|
1893
1907
|
}
|
|
@@ -2335,16 +2349,18 @@ export interface TelegramControlQueueController<TContext> {
|
|
|
2335
2349
|
) => void;
|
|
2336
2350
|
}
|
|
2337
2351
|
|
|
2352
|
+
export type TelegramPreparedPromptTurn = (
|
|
2353
|
+
historyTurns: PendingTelegramTurn[],
|
|
2354
|
+
) => PendingTelegramTurn;
|
|
2355
|
+
|
|
2338
2356
|
export interface TelegramPromptEnqueueRuntimeDeps<
|
|
2339
2357
|
TMessage,
|
|
2340
2358
|
TContext = unknown,
|
|
2341
2359
|
> extends TelegramQueueStore<TContext> {
|
|
2360
|
+
hasPendingDispatch: () => boolean;
|
|
2342
2361
|
getFoldQueuedPromptsIntoHistory: () => boolean;
|
|
2343
2362
|
setFoldQueuedPromptsIntoHistory: (fold: boolean) => void;
|
|
2344
|
-
|
|
2345
|
-
messages: TMessage[],
|
|
2346
|
-
historyTurns: PendingTelegramTurn[],
|
|
2347
|
-
) => Promise<PendingTelegramTurn>;
|
|
2363
|
+
prepareTurn: (messages: TMessage[]) => Promise<TelegramPreparedPromptTurn>;
|
|
2348
2364
|
updateStatus: () => void;
|
|
2349
2365
|
dispatchNextQueuedTelegramTurn: () => void;
|
|
2350
2366
|
assertExecutionCurrent?: () => void;
|
|
@@ -2355,13 +2371,13 @@ export interface TelegramPromptEnqueueControllerDeps<
|
|
|
2355
2371
|
TMessage,
|
|
2356
2372
|
TContext = unknown,
|
|
2357
2373
|
> extends TelegramQueueStore<TContext> {
|
|
2374
|
+
hasPendingDispatch: () => boolean;
|
|
2358
2375
|
getFoldQueuedPromptsIntoHistory: () => boolean;
|
|
2359
2376
|
setFoldQueuedPromptsIntoHistory: (fold: boolean) => void;
|
|
2360
|
-
|
|
2377
|
+
prepareTurn: (
|
|
2361
2378
|
messages: TMessage[],
|
|
2362
|
-
historyTurns: PendingTelegramTurn[],
|
|
2363
2379
|
ctx: TContext,
|
|
2364
|
-
) => Promise<
|
|
2380
|
+
) => Promise<TelegramPreparedPromptTurn>;
|
|
2365
2381
|
updateStatus: (ctx: TContext) => void;
|
|
2366
2382
|
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
2367
2383
|
assertExecutionCurrent?: (messages: TMessage[]) => void;
|
|
@@ -2713,17 +2729,25 @@ export async function enqueueTelegramPromptTurnRuntime<
|
|
|
2713
2729
|
messages: TMessage[],
|
|
2714
2730
|
deps: TelegramPromptEnqueueRuntimeDeps<TMessage, TContext>,
|
|
2715
2731
|
): Promise<PendingTelegramTurn> {
|
|
2716
|
-
|
|
2732
|
+
deps.assertExecutionCurrent?.();
|
|
2733
|
+
const historyOrders = new Set(planTelegramPromptEnqueue(
|
|
2717
2734
|
deps.getQueuedItems(),
|
|
2718
2735
|
deps.getFoldQueuedPromptsIntoHistory(),
|
|
2719
|
-
);
|
|
2720
|
-
deps.assertExecutionCurrent?.();
|
|
2736
|
+
).historyTurns.map((turn) => turn.queueOrder));
|
|
2721
2737
|
deps.setFoldQueuedPromptsIntoHistory(false);
|
|
2722
|
-
const
|
|
2738
|
+
const buildTurn = await deps.prepareTurn(messages);
|
|
2723
2739
|
deps.assertExecutionCurrent?.();
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2740
|
+
// Preserve the Pi-owned head until agent_start, plus later arrivals and current edits/reactions.
|
|
2741
|
+
const pendingDispatch = deps.hasPendingDispatch();
|
|
2742
|
+
const historyTurns: PendingTelegramTurn[] = [];
|
|
2743
|
+
const remainingItems = deps.getQueuedItems().filter((item, index) => {
|
|
2744
|
+
if ((pendingDispatch && index === 0) || !isPendingTelegramTurn(item) ||
|
|
2745
|
+
!historyOrders.has(item.queueOrder)) return true;
|
|
2746
|
+
historyTurns.push(item);
|
|
2747
|
+
return false;
|
|
2748
|
+
});
|
|
2749
|
+
const turn = buildTurn(historyTurns);
|
|
2750
|
+
deps.setQueuedItems(appendTelegramQueueItem(remainingItems, turn));
|
|
2727
2751
|
deps.onQueued?.(turn);
|
|
2728
2752
|
deps.updateStatus();
|
|
2729
2753
|
deps.dispatchNextQueuedTelegramTurn();
|
|
@@ -2740,8 +2764,7 @@ export function createTelegramPromptEnqueueController<
|
|
|
2740
2764
|
enqueue: (messages, ctx, onQueued) =>
|
|
2741
2765
|
enqueueTelegramPromptTurnRuntime(messages, {
|
|
2742
2766
|
...deps,
|
|
2743
|
-
|
|
2744
|
-
deps.createTurn(nextMessages, historyTurns, ctx),
|
|
2767
|
+
prepareTurn: (nextMessages) => deps.prepareTurn(nextMessages, ctx),
|
|
2745
2768
|
updateStatus: () => deps.updateStatus(ctx),
|
|
2746
2769
|
dispatchNextQueuedTelegramTurn: () =>
|
|
2747
2770
|
deps.dispatchNextQueuedTelegramTurn(ctx),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Telegram reply delivery helpers
|
|
3
3
|
* Zones: telegram outbound, native rich markdown, UI/compat rendering transport
|
|
4
|
-
* Owns native assistant replies, rendered UI delivery, reply transport wiring, and plain text replies
|
|
4
|
+
* Owns native assistant replies, rendered UI delivery, guest placeholder rotation, reply transport wiring, and plain text replies
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { assertTelegramInlineKeyboardCallbackData } from "./keyboard.ts";
|
|
@@ -177,6 +177,47 @@ export function extractLatestAssistantMessageText(
|
|
|
177
177
|
return {};
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Extract the run's answer without trusting an empty final assistant message.
|
|
182
|
+
* A low-level run may end with a completed assistant message whose content was
|
|
183
|
+
* suppressed (a companion extension preserving an earlier draft, for example
|
|
184
|
+
* State Flow's fallback final:true patch turn). In that case the run's answer
|
|
185
|
+
* is the latest earlier completed assistant message that carries text;
|
|
186
|
+
* tool-use prefaces, errors, and aborts stay excluded.
|
|
187
|
+
*/
|
|
188
|
+
export function extractRunAssistantMessage(
|
|
189
|
+
messages: readonly unknown[],
|
|
190
|
+
): {
|
|
191
|
+
text?: string;
|
|
192
|
+
stopReason?: string;
|
|
193
|
+
errorMessage?: string;
|
|
194
|
+
} {
|
|
195
|
+
const latest = extractLatestAssistantMessageText(messages);
|
|
196
|
+
if (latest.text || latest.stopReason !== "stop") return latest;
|
|
197
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
198
|
+
const message = messages[i];
|
|
199
|
+
if (!message || !isAssistantAgentMessage(message)) continue;
|
|
200
|
+
const rawStopReason = getAgentMessageField(message, "stopReason");
|
|
201
|
+
if (
|
|
202
|
+
rawStopReason === "toolUse" ||
|
|
203
|
+
rawStopReason === "error" ||
|
|
204
|
+
rawStopReason === "aborted"
|
|
205
|
+
) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const text = getAgentMessageText(message);
|
|
209
|
+
if (!text) continue;
|
|
210
|
+
const rawErrorMessage = getAgentMessageField(message, "errorMessage");
|
|
211
|
+
return {
|
|
212
|
+
text,
|
|
213
|
+
stopReason: typeof rawStopReason === "string" ? rawStopReason : undefined,
|
|
214
|
+
errorMessage:
|
|
215
|
+
typeof rawErrorMessage === "string" ? rawErrorMessage : undefined,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return latest;
|
|
219
|
+
}
|
|
220
|
+
|
|
180
221
|
export interface TelegramReplyOwnershipRecorder {
|
|
181
222
|
record: (input: {
|
|
182
223
|
chatId: number;
|
|
@@ -900,9 +941,9 @@ export function createGuestMarkdownReplySender(deps: {
|
|
|
900
941
|
}
|
|
901
942
|
|
|
902
943
|
/**
|
|
903
|
-
* Guest reply editor: replaces
|
|
904
|
-
*
|
|
905
|
-
*
|
|
944
|
+
* Guest reply editor: replaces the early Guest Mode placeholder ACK with
|
|
945
|
+
* native Rich Markdown content addressed by `inline_message_id` instead of a
|
|
946
|
+
* chat/message pair.
|
|
906
947
|
*/
|
|
907
948
|
export function createGuestMarkdownReplyEditor(deps: {
|
|
908
949
|
editGuestInlineMessage: (
|
|
@@ -917,3 +958,219 @@ export function createGuestMarkdownReplyEditor(deps: {
|
|
|
917
958
|
});
|
|
918
959
|
};
|
|
919
960
|
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Guest Mode placeholder rotation: the early ACK is the first placeholder
|
|
964
|
+
* frame and the runtime steps the remaining frames once per interval, moving
|
|
965
|
+
* the globe every second while the trailing dots grow once every two seconds.
|
|
966
|
+
*
|
|
967
|
+
* Rotation completes whole six-frame cycles and only stops once at least
|
|
968
|
+
* `TELEGRAM_GUEST_PLACEHOLDER_MIN_MS` has elapsed, so a pending answer holds
|
|
969
|
+
* the finished cycle's last frame (`π Working on it...`) instead of whatever
|
|
970
|
+
* step a hard time cap happens to cut. The `TELEGRAM_GUEST_PLACEHOLDER_MAX_MS`
|
|
971
|
+
* safety bound keeps the edit stream clear of the first flood-control
|
|
972
|
+
* rejections measured in live guest runs (+26.5 s at ~53 edits, +27.8 s at
|
|
973
|
+
* ~28 edits): rotation caps at 23 edits and never starts a frame after 26 s.
|
|
974
|
+
*/
|
|
975
|
+
|
|
976
|
+
export const TELEGRAM_GUEST_PLACEHOLDER_FRAME_MS = 1_000;
|
|
977
|
+
|
|
978
|
+
export const TELEGRAM_GUEST_PLACEHOLDER_MIN_MS = 20_000;
|
|
979
|
+
|
|
980
|
+
export const TELEGRAM_GUEST_PLACEHOLDER_MAX_MS = 26_000;
|
|
981
|
+
|
|
982
|
+
export const TELEGRAM_GUEST_PLACEHOLDER_FRAMES = [
|
|
983
|
+
"<b>π Working on it.</b>",
|
|
984
|
+
"<b>π Working on it.</b>",
|
|
985
|
+
"<b>π Working on it..</b>",
|
|
986
|
+
"<b>π Working on it..</b>",
|
|
987
|
+
"<b>π Working on it...</b>",
|
|
988
|
+
"<b>π Working on it...</b>",
|
|
989
|
+
] as const;
|
|
990
|
+
|
|
991
|
+
export function buildTelegramGuestPlaceholderFrame(step: number): string {
|
|
992
|
+
const frames: readonly string[] = TELEGRAM_GUEST_PLACEHOLDER_FRAMES;
|
|
993
|
+
const index = ((step % frames.length) + frames.length) % frames.length;
|
|
994
|
+
return frames[index]!;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
export interface TelegramGuestPlaceholderRuntimeDeps {
|
|
998
|
+
editGuestInlineMessage: (
|
|
999
|
+
inlineMessageId: string,
|
|
1000
|
+
content: { text: string; parseMode: "HTML" },
|
|
1001
|
+
) => Promise<void>;
|
|
1002
|
+
recordRuntimeEvent?: (
|
|
1003
|
+
category: string,
|
|
1004
|
+
error: unknown,
|
|
1005
|
+
details?: Record<string, unknown>,
|
|
1006
|
+
) => void;
|
|
1007
|
+
intervalMs?: number;
|
|
1008
|
+
minMs?: number;
|
|
1009
|
+
maxMs?: number;
|
|
1010
|
+
now?: () => number;
|
|
1011
|
+
setTimer?: (
|
|
1012
|
+
callback: () => void,
|
|
1013
|
+
ms: number,
|
|
1014
|
+
) => ReturnType<typeof setTimeout>;
|
|
1015
|
+
clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
export interface TelegramGuestPlaceholderRuntime {
|
|
1019
|
+
/** Starts the frame loop on an answered guest inline message. */
|
|
1020
|
+
start: (inlineMessageId: string) => void;
|
|
1021
|
+
/** Cancels the loop and waits for any in-flight frame edit before returning. */
|
|
1022
|
+
stop: (inlineMessageId: string) => Promise<void>;
|
|
1023
|
+
/** Cancels every loop without waiting for in-flight edits (session shutdown). */
|
|
1024
|
+
stopAll: () => void;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
interface TelegramGuestPlaceholderSession {
|
|
1028
|
+
step: number;
|
|
1029
|
+
stopped: boolean;
|
|
1030
|
+
startedAtMs: number;
|
|
1031
|
+
finished: boolean;
|
|
1032
|
+
timer?: ReturnType<typeof setTimeout>;
|
|
1033
|
+
inflight?: Promise<void>;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function getTelegramGuestPlaceholderRetryDelayMs(
|
|
1037
|
+
error: unknown,
|
|
1038
|
+
fallbackMs: number,
|
|
1039
|
+
): number {
|
|
1040
|
+
const retryAfterSeconds = (
|
|
1041
|
+
error as { retryAfterSeconds?: unknown } | undefined
|
|
1042
|
+
)?.retryAfterSeconds;
|
|
1043
|
+
return typeof retryAfterSeconds === "number" && retryAfterSeconds > 0
|
|
1044
|
+
? Math.max(fallbackMs, retryAfterSeconds * 1_000)
|
|
1045
|
+
: fallbackMs;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
export function createTelegramGuestPlaceholderRuntime(
|
|
1049
|
+
deps: TelegramGuestPlaceholderRuntimeDeps,
|
|
1050
|
+
): TelegramGuestPlaceholderRuntime {
|
|
1051
|
+
const intervalMs = deps.intervalMs ?? TELEGRAM_GUEST_PLACEHOLDER_FRAME_MS;
|
|
1052
|
+
const minMs = deps.minMs ?? TELEGRAM_GUEST_PLACEHOLDER_MIN_MS;
|
|
1053
|
+
const maxMs = deps.maxMs ?? TELEGRAM_GUEST_PLACEHOLDER_MAX_MS;
|
|
1054
|
+
const now = deps.now ?? Date.now;
|
|
1055
|
+
const setTimer =
|
|
1056
|
+
deps.setTimer ??
|
|
1057
|
+
((callback: () => void, ms: number): ReturnType<typeof setTimeout> =>
|
|
1058
|
+
setTimeout(callback, ms));
|
|
1059
|
+
const clearTimer =
|
|
1060
|
+
deps.clearTimer ??
|
|
1061
|
+
((timer: ReturnType<typeof setTimeout>): void => clearTimeout(timer));
|
|
1062
|
+
const sessions = new Map<string, TelegramGuestPlaceholderSession>();
|
|
1063
|
+
|
|
1064
|
+
const finishRotation = (
|
|
1065
|
+
session: TelegramGuestPlaceholderSession,
|
|
1066
|
+
elapsedMs: number,
|
|
1067
|
+
): void => {
|
|
1068
|
+
if (session.finished) return;
|
|
1069
|
+
session.finished = true;
|
|
1070
|
+
deps.recordRuntimeEvent?.(
|
|
1071
|
+
"guest",
|
|
1072
|
+
"Guest placeholder rotation reached its bound",
|
|
1073
|
+
{
|
|
1074
|
+
phase: "guest-placeholder-capped",
|
|
1075
|
+
minMs,
|
|
1076
|
+
maxMs,
|
|
1077
|
+
elapsedMs,
|
|
1078
|
+
step: session.step,
|
|
1079
|
+
},
|
|
1080
|
+
);
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
const scheduleFrame = (
|
|
1084
|
+
inlineMessageId: string,
|
|
1085
|
+
session: TelegramGuestPlaceholderSession,
|
|
1086
|
+
delayMs: number,
|
|
1087
|
+
): void => {
|
|
1088
|
+
if (session.stopped) return;
|
|
1089
|
+
const elapsedMs = now() - session.startedAtMs;
|
|
1090
|
+
const frameCount = TELEGRAM_GUEST_PLACEHOLDER_FRAMES.length;
|
|
1091
|
+
const cycleComplete =
|
|
1092
|
+
session.step > 0 && session.step % frameCount === frameCount - 1;
|
|
1093
|
+
if (cycleComplete && elapsedMs >= minMs) {
|
|
1094
|
+
finishRotation(session, elapsedMs);
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
if (elapsedMs + delayMs > maxMs) {
|
|
1098
|
+
finishRotation(session, elapsedMs);
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
const timer = setTimer(() => {
|
|
1102
|
+
session.timer = undefined;
|
|
1103
|
+
runFrame(inlineMessageId, session);
|
|
1104
|
+
}, delayMs);
|
|
1105
|
+
timer.unref?.();
|
|
1106
|
+
session.timer = timer;
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
const runFrame = (
|
|
1110
|
+
inlineMessageId: string,
|
|
1111
|
+
session: TelegramGuestPlaceholderSession,
|
|
1112
|
+
): void => {
|
|
1113
|
+
if (session.stopped) return;
|
|
1114
|
+
session.step += 1;
|
|
1115
|
+
let nextDelayMs = intervalMs;
|
|
1116
|
+
session.inflight = (async () => {
|
|
1117
|
+
try {
|
|
1118
|
+
await deps.editGuestInlineMessage(inlineMessageId, {
|
|
1119
|
+
text: buildTelegramGuestPlaceholderFrame(session.step),
|
|
1120
|
+
parseMode: "HTML",
|
|
1121
|
+
});
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
nextDelayMs = getTelegramGuestPlaceholderRetryDelayMs(error, intervalMs);
|
|
1124
|
+
deps.recordRuntimeEvent?.("guest", error, {
|
|
1125
|
+
phase: "guest-placeholder-edit",
|
|
1126
|
+
retryAfterMs: nextDelayMs,
|
|
1127
|
+
});
|
|
1128
|
+
} finally {
|
|
1129
|
+
session.inflight = undefined;
|
|
1130
|
+
scheduleFrame(inlineMessageId, session, nextDelayMs);
|
|
1131
|
+
}
|
|
1132
|
+
})();
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
const stop = async (inlineMessageId: string): Promise<void> => {
|
|
1136
|
+
const session = sessions.get(inlineMessageId);
|
|
1137
|
+
if (!session) return;
|
|
1138
|
+
sessions.delete(inlineMessageId);
|
|
1139
|
+
session.stopped = true;
|
|
1140
|
+
if (session.timer !== undefined) {
|
|
1141
|
+
clearTimer(session.timer);
|
|
1142
|
+
session.timer = undefined;
|
|
1143
|
+
}
|
|
1144
|
+
try {
|
|
1145
|
+
await session.inflight;
|
|
1146
|
+
} catch {
|
|
1147
|
+
// Frame failures are reported as runtime events; stopping stays fail-open.
|
|
1148
|
+
}
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
return {
|
|
1152
|
+
start(inlineMessageId) {
|
|
1153
|
+
const existing = sessions.get(inlineMessageId);
|
|
1154
|
+
if (existing) {
|
|
1155
|
+
existing.stopped = true;
|
|
1156
|
+
if (existing.timer !== undefined) clearTimer(existing.timer);
|
|
1157
|
+
}
|
|
1158
|
+
const session: TelegramGuestPlaceholderSession = {
|
|
1159
|
+
step: 0,
|
|
1160
|
+
stopped: false,
|
|
1161
|
+
startedAtMs: now(),
|
|
1162
|
+
finished: false,
|
|
1163
|
+
};
|
|
1164
|
+
sessions.set(inlineMessageId, session);
|
|
1165
|
+
scheduleFrame(inlineMessageId, session, intervalMs);
|
|
1166
|
+
},
|
|
1167
|
+
stop,
|
|
1168
|
+
stopAll() {
|
|
1169
|
+
for (const session of sessions.values()) {
|
|
1170
|
+
session.stopped = true;
|
|
1171
|
+
if (session.timer !== undefined) clearTimer(session.timer);
|
|
1172
|
+
}
|
|
1173
|
+
sessions.clear();
|
|
1174
|
+
},
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
@@ -16,6 +16,7 @@ import * as Model from "./model.ts";
|
|
|
16
16
|
import * as OutboundHandlers from "./outbound.ts";
|
|
17
17
|
import * as PromptTemplates from "./prompt-templates.ts";
|
|
18
18
|
import * as Queue from "./queue.ts";
|
|
19
|
+
import * as Replies from "./replies.ts";
|
|
19
20
|
import type { TelegramBridgeRuntime } from "./runtime.ts";
|
|
20
21
|
import type { TelegramSectionRegistry } from "./sections.ts";
|
|
21
22
|
import * as TextGroups from "./text-groups.ts";
|
|
@@ -665,6 +666,8 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
665
666
|
text: string,
|
|
666
667
|
options?: { parseMode?: "HTML" },
|
|
667
668
|
) => Promise<string | undefined>;
|
|
669
|
+
/** Starts the animated placeholder on an answered guest inline message. */
|
|
670
|
+
startGuestPlaceholder?: (inlineMessageId: string) => void;
|
|
668
671
|
sendTextReply: (
|
|
669
672
|
chatId: number,
|
|
670
673
|
replyToMessageId: number,
|
|
@@ -2092,7 +2095,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2092
2095
|
}
|
|
2093
2096
|
await menuCallbackHandler(query, ctx);
|
|
2094
2097
|
};
|
|
2095
|
-
const
|
|
2098
|
+
const preparePromptTurn = Turns.createTelegramPromptTurnRuntimePreparer<
|
|
2096
2099
|
TMessage,
|
|
2097
2100
|
TContext
|
|
2098
2101
|
>({
|
|
@@ -2121,7 +2124,8 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2121
2124
|
text: "continue",
|
|
2122
2125
|
caption: undefined,
|
|
2123
2126
|
} as TMessage;
|
|
2124
|
-
const
|
|
2127
|
+
const buildTurn = await preparePromptTurn([continueMessage], ctx);
|
|
2128
|
+
const turn = buildTurn([]);
|
|
2125
2129
|
const continueTurn = {
|
|
2126
2130
|
...turn,
|
|
2127
2131
|
queueLane: "control" as const,
|
|
@@ -2259,15 +2263,19 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2259
2263
|
const promptEnqueueController =
|
|
2260
2264
|
Queue.createTelegramPromptEnqueueController<TMessage, TContext>({
|
|
2261
2265
|
...deps.telegramQueueStore,
|
|
2266
|
+
hasPendingDispatch: deps.bridgeRuntime.lifecycle.hasDispatchPending,
|
|
2262
2267
|
getFoldQueuedPromptsIntoHistory:
|
|
2263
2268
|
deps.bridgeRuntime.lifecycle.shouldFoldQueuedPromptsIntoHistory,
|
|
2264
2269
|
setFoldQueuedPromptsIntoHistory:
|
|
2265
2270
|
deps.bridgeRuntime.lifecycle.setFoldQueuedPromptsIntoHistory,
|
|
2266
|
-
|
|
2267
|
-
const
|
|
2268
|
-
return
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
+
prepareTurn: async (messages, turnCtx) => {
|
|
2272
|
+
const buildTurn = await preparePromptTurn(messages, turnCtx);
|
|
2273
|
+
return (historyTurns) => {
|
|
2274
|
+
const turn = buildTurn(historyTurns);
|
|
2275
|
+
return turn.replyToMessageId > 0
|
|
2276
|
+
? turn
|
|
2277
|
+
: { ...turn, replyToMessageId: 0 };
|
|
2278
|
+
};
|
|
2271
2279
|
},
|
|
2272
2280
|
updateStatus: deps.updateStatus,
|
|
2273
2281
|
dispatchNextQueuedTelegramTurn: requestDispatchNextQueuedTelegramTurn,
|
|
@@ -2662,8 +2670,10 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2662
2670
|
assertExecutionCurrent();
|
|
2663
2671
|
};
|
|
2664
2672
|
// Answer the guest query immediately so the agent-end edit can replace the
|
|
2665
|
-
// early ACK once the turn settles.
|
|
2666
|
-
|
|
2673
|
+
// early ACK once the turn settles. The ACK is the first placeholder frame and
|
|
2674
|
+
// the loop rotates through the remaining frames until the replacement.
|
|
2675
|
+
const TELEGRAM_GUEST_ACK_HTML =
|
|
2676
|
+
Replies.buildTelegramGuestPlaceholderFrame(0);
|
|
2667
2677
|
const handleAuthorizedTelegramGuestMessage = async (
|
|
2668
2678
|
guestMessage: Updates.TelegramGuestMessage & { from: TelegramUser },
|
|
2669
2679
|
ctx: TContext,
|
|
@@ -2679,9 +2689,12 @@ export function createTelegramInboundRouteRuntime<
|
|
|
2679
2689
|
TELEGRAM_GUEST_ACK_HTML,
|
|
2680
2690
|
{ parseMode: "HTML" },
|
|
2681
2691
|
);
|
|
2692
|
+
if (guestInlineMessageId) {
|
|
2693
|
+
deps.startGuestPlaceholder?.(guestInlineMessageId);
|
|
2694
|
+
}
|
|
2682
2695
|
deps.recordRuntimeEvent?.(
|
|
2683
2696
|
"guest",
|
|
2684
|
-
new Error("Guest ACK
|
|
2697
|
+
new Error("Guest ACK answered the guest query"),
|
|
2685
2698
|
{
|
|
2686
2699
|
phase: "guest-ack-sent",
|
|
2687
2700
|
guestQueryId: guestMessage.guest_query_id,
|
|
@@ -374,6 +374,13 @@ interface TelegramApiResponse<T> {
|
|
|
374
374
|
parameters?: { retry_after?: number };
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
+
export interface TelegramApiRetryWait {
|
|
378
|
+
method: string;
|
|
379
|
+
delayMs: number;
|
|
380
|
+
attempt: number;
|
|
381
|
+
retryAfterSeconds?: number;
|
|
382
|
+
}
|
|
383
|
+
|
|
377
384
|
export interface TelegramApiCallOptions {
|
|
378
385
|
signal?: AbortSignal;
|
|
379
386
|
maxAttempts?: number;
|
|
@@ -381,6 +388,8 @@ export interface TelegramApiCallOptions {
|
|
|
381
388
|
retrySafety?: "safe" | "non-idempotent";
|
|
382
389
|
retryBaseDelayMs?: number;
|
|
383
390
|
sleep?: (ms: number) => Promise<void>;
|
|
391
|
+
/** Observability hook fired before a 429 retry wait, never for 5xx waits. */
|
|
392
|
+
onRetryWait?: (wait: TelegramApiRetryWait) => void;
|
|
384
393
|
}
|
|
385
394
|
|
|
386
395
|
interface TelegramGetFileResult {
|
|
@@ -434,6 +443,7 @@ export interface TelegramAnswerGuestQueryOptions {
|
|
|
434
443
|
export interface TelegramEditGuestInlineMessageContent {
|
|
435
444
|
text?: string;
|
|
436
445
|
richMessage?: TelegramInputRichMessage;
|
|
446
|
+
parseMode?: "HTML";
|
|
437
447
|
}
|
|
438
448
|
|
|
439
449
|
export interface TelegramAnswerCallbackQueryOptions {
|
|
@@ -1380,7 +1390,25 @@ async function callTelegramWithRetry<TResponse>(
|
|
|
1380
1390
|
isTelegramApiMethodRetrySafe(method));
|
|
1381
1391
|
const maxAttempts = Math.max(1, options?.maxAttempts ?? 3);
|
|
1382
1392
|
const retryBaseDelayMs = options?.retryBaseDelayMs ?? 500;
|
|
1383
|
-
const waitBeforeRetry = async (
|
|
1393
|
+
const waitBeforeRetry = async (
|
|
1394
|
+
error: unknown,
|
|
1395
|
+
attempt: number,
|
|
1396
|
+
): Promise<void> => {
|
|
1397
|
+
const ms = getTelegramRetryDelayMs(error, attempt, retryBaseDelayMs);
|
|
1398
|
+
if (
|
|
1399
|
+
!options?.signal?.aborted &&
|
|
1400
|
+
error instanceof TelegramApiHttpError &&
|
|
1401
|
+
error.status === 429
|
|
1402
|
+
) {
|
|
1403
|
+
options?.onRetryWait?.({
|
|
1404
|
+
method,
|
|
1405
|
+
delayMs: ms,
|
|
1406
|
+
attempt,
|
|
1407
|
+
...(error.retryAfterSeconds !== undefined
|
|
1408
|
+
? { retryAfterSeconds: error.retryAfterSeconds }
|
|
1409
|
+
: {}),
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1384
1412
|
if (options?.sleep) await options.sleep(ms);
|
|
1385
1413
|
else await sleepTelegramRetry(ms, options?.signal);
|
|
1386
1414
|
throwIfTelegramApiCallAborted(options?.signal);
|
|
@@ -1406,9 +1434,7 @@ async function callTelegramWithRetry<TResponse>(
|
|
|
1406
1434
|
if (!retrySafe) {
|
|
1407
1435
|
if (error instanceof TelegramApiHttpError && error.status === 429) {
|
|
1408
1436
|
if (attempt >= maxAttempts - 1) throw error;
|
|
1409
|
-
await waitBeforeRetry(
|
|
1410
|
-
getTelegramRetryDelayMs(error, attempt, retryBaseDelayMs),
|
|
1411
|
-
);
|
|
1437
|
+
await waitBeforeRetry(error, attempt);
|
|
1412
1438
|
continue;
|
|
1413
1439
|
}
|
|
1414
1440
|
if (
|
|
@@ -1423,9 +1449,7 @@ async function callTelegramWithRetry<TResponse>(
|
|
|
1423
1449
|
throw error;
|
|
1424
1450
|
}
|
|
1425
1451
|
if (attempt >= maxAttempts - 1 || !retryable) throw error;
|
|
1426
|
-
await waitBeforeRetry(
|
|
1427
|
-
getTelegramRetryDelayMs(error, attempt, retryBaseDelayMs),
|
|
1428
|
-
);
|
|
1452
|
+
await waitBeforeRetry(error, attempt);
|
|
1429
1453
|
}
|
|
1430
1454
|
}
|
|
1431
1455
|
}
|
|
@@ -2095,6 +2119,7 @@ export function createTelegramBridgeApiRuntime(
|
|
|
2095
2119
|
...(content.richMessage
|
|
2096
2120
|
? { rich_message: content.richMessage }
|
|
2097
2121
|
: { text: content.text }),
|
|
2122
|
+
...(content.parseMode ? { parse_mode: content.parseMode } : {}),
|
|
2098
2123
|
});
|
|
2099
2124
|
},
|
|
2100
2125
|
prepareTempDir: () =>
|
|
@@ -2117,6 +2142,7 @@ export function createTelegramApiClient(
|
|
|
2117
2142
|
options: TelegramAnswerCallbackQueryOptions & { now?: () => number } = {},
|
|
2118
2143
|
): TelegramApiClient {
|
|
2119
2144
|
const now = options.now ?? Date.now;
|
|
2145
|
+
const recordRuntimeEvent = options.recordRuntimeEvent;
|
|
2120
2146
|
const draftRetryNotBeforeByTarget = new Map<string, number>();
|
|
2121
2147
|
return {
|
|
2122
2148
|
call: async <TResponse>(
|
|
@@ -2140,9 +2166,31 @@ export function createTelegramApiClient(
|
|
|
2140
2166
|
}
|
|
2141
2167
|
try {
|
|
2142
2168
|
// A draft is a replaceable snapshot, not a body to replay after backoff.
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2169
|
+
const retryWaitOptions: TelegramApiCallOptions = recordRuntimeEvent
|
|
2170
|
+
? {
|
|
2171
|
+
onRetryWait: (wait) => {
|
|
2172
|
+
recordRuntimeEvent(
|
|
2173
|
+
"api",
|
|
2174
|
+
new Error(
|
|
2175
|
+
`Telegram API rate limit: waiting ${wait.delayMs} ms before retrying ${wait.method}`,
|
|
2176
|
+
),
|
|
2177
|
+
{
|
|
2178
|
+
phase: "retry-wait",
|
|
2179
|
+
method: wait.method,
|
|
2180
|
+
waitMs: wait.delayMs,
|
|
2181
|
+
attempt: wait.attempt,
|
|
2182
|
+
...(wait.retryAfterSeconds !== undefined
|
|
2183
|
+
? { retryAfterSeconds: wait.retryAfterSeconds }
|
|
2184
|
+
: {}),
|
|
2185
|
+
},
|
|
2186
|
+
);
|
|
2187
|
+
},
|
|
2188
|
+
}
|
|
2189
|
+
: {};
|
|
2190
|
+
return await callTelegram<TResponse>(token, method, body, {
|
|
2191
|
+
...(isDraft ? { ...options, maxAttempts: 1 } : options),
|
|
2192
|
+
...retryWaitOptions,
|
|
2193
|
+
});
|
|
2146
2194
|
} catch (error) {
|
|
2147
2195
|
if (draftKey && isRetryableTelegramApiError(error)) {
|
|
2148
2196
|
draftRetryNotBeforeByTarget.set(draftKey, Math.max(
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
createTelegramQueueAdmissionReceipt,
|
|
27
27
|
truncateTelegramQueueSummary,
|
|
28
28
|
type PendingTelegramTurn,
|
|
29
|
+
type TelegramPreparedPromptTurn,
|
|
29
30
|
type TelegramPromptContent,
|
|
30
31
|
type TelegramQueueAdmissionReceipt,
|
|
31
32
|
type TelegramQueueItem,
|
|
@@ -454,17 +455,16 @@ export interface TelegramPromptTurnRuntimeBuilderDeps<
|
|
|
454
455
|
assertExecutionCurrent?: (message: TelegramTurnMessage) => void;
|
|
455
456
|
}
|
|
456
457
|
|
|
457
|
-
export function
|
|
458
|
+
export function createTelegramPromptTurnRuntimePreparer<
|
|
458
459
|
TMessage extends TelegramTurnMessage & TelegramMediaMessage,
|
|
459
460
|
TContext = unknown,
|
|
460
461
|
>(
|
|
461
462
|
deps: TelegramPromptTurnRuntimeBuilderDeps<TContext>,
|
|
462
463
|
): (
|
|
463
464
|
messages: TMessage[],
|
|
464
|
-
historyTurns?: PendingTelegramTurn[],
|
|
465
465
|
ctx?: TContext,
|
|
466
|
-
) => Promise<
|
|
467
|
-
return async (messages,
|
|
466
|
+
) => Promise<TelegramPreparedPromptTurn> {
|
|
467
|
+
return async (messages, ctx) => {
|
|
468
468
|
const rawText = extractTelegramMessagesText(messages);
|
|
469
469
|
const firstMessage = messages[0];
|
|
470
470
|
if (firstMessage) deps.assertExecutionCurrent?.(firstMessage);
|
|
@@ -577,11 +577,10 @@ export function createTelegramPromptTurnRuntimeBuilder<
|
|
|
577
577
|
thread: threadLabel,
|
|
578
578
|
"from-thread": firstMessage?.pi_telegram_agent_source_thread,
|
|
579
579
|
});
|
|
580
|
-
|
|
580
|
+
const buildTurn = await prepareTelegramPromptTurn({
|
|
581
581
|
telegramPrefix,
|
|
582
582
|
messages,
|
|
583
|
-
|
|
584
|
-
queueOrder: deps.allocateQueueOrder(),
|
|
583
|
+
readBinaryFile: readFile,
|
|
585
584
|
rawText: promptRawText,
|
|
586
585
|
sourceContext,
|
|
587
586
|
statusText: processed.rawText,
|
|
@@ -602,6 +601,10 @@ export function createTelegramPromptTurnRuntimeBuilder<
|
|
|
602
601
|
admissionScope: deps.getAdmissionScope?.(),
|
|
603
602
|
admissionJournalBinding: deps.getAdmissionJournalBinding?.(),
|
|
604
603
|
});
|
|
604
|
+
return (historyTurns) => {
|
|
605
|
+
if (firstMessage) deps.assertExecutionCurrent?.(firstMessage);
|
|
606
|
+
return buildTurn(deps.allocateQueueOrder(), historyTurns);
|
|
607
|
+
};
|
|
605
608
|
};
|
|
606
609
|
}
|
|
607
610
|
|
|
@@ -670,9 +673,29 @@ function collectTelegramTurnAdmissionReceipts(
|
|
|
670
673
|
return [...receipts.values()];
|
|
671
674
|
}
|
|
672
675
|
|
|
673
|
-
|
|
676
|
+
async function prepareTelegramPromptTurn(
|
|
677
|
+
options: Omit<BuildTelegramPromptTurnOptions, "queueOrder" | "historyTurns">,
|
|
678
|
+
): Promise<(queueOrder: number, historyTurns: PendingTelegramTurn[]) => PendingTelegramTurn> {
|
|
679
|
+
const images: TelegramPromptContent[] = [];
|
|
680
|
+
for (const file of options.files) {
|
|
681
|
+
if (!file.isImage) continue;
|
|
682
|
+
const mediaType = file.mimeType || options.inferImageMimeType(file.path);
|
|
683
|
+
if (!mediaType) continue;
|
|
684
|
+
const buffer = await options.readBinaryFile(file.path);
|
|
685
|
+
images.push({
|
|
686
|
+
type: "image",
|
|
687
|
+
data: Buffer.from(buffer).toString("base64"),
|
|
688
|
+
mimeType: mediaType,
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
return (queueOrder, historyTurns) =>
|
|
692
|
+
buildPreparedTelegramPromptTurn({ ...options, queueOrder, historyTurns }, images);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function buildPreparedTelegramPromptTurn(
|
|
674
696
|
options: BuildTelegramPromptTurnOptions,
|
|
675
|
-
|
|
697
|
+
images: TelegramPromptContent[],
|
|
698
|
+
): PendingTelegramTurn {
|
|
676
699
|
const firstMessage = options.messages[0];
|
|
677
700
|
if (!firstMessage) {
|
|
678
701
|
throw new Error("Missing Telegram message for turn creation");
|
|
@@ -703,18 +726,8 @@ export async function buildTelegramPromptTurn(
|
|
|
703
726
|
),
|
|
704
727
|
}),
|
|
705
728
|
},
|
|
729
|
+
...images,
|
|
706
730
|
];
|
|
707
|
-
for (const file of options.files) {
|
|
708
|
-
if (!file.isImage) continue;
|
|
709
|
-
const mediaType = file.mimeType || options.inferImageMimeType(file.path);
|
|
710
|
-
if (!mediaType) continue;
|
|
711
|
-
const buffer = await options.readBinaryFile(file.path);
|
|
712
|
-
content.push({
|
|
713
|
-
type: "image",
|
|
714
|
-
data: Buffer.from(buffer).toString("base64"),
|
|
715
|
-
mimeType: mediaType,
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
731
|
if (options.voicePromptContribution?.trim()) {
|
|
719
732
|
const textItem = content.find((c) => c.type === "text") as
|
|
720
733
|
{ type: "text"; text: string } | undefined;
|
|
@@ -759,6 +772,13 @@ export async function buildTelegramPromptTurn(
|
|
|
759
772
|
};
|
|
760
773
|
}
|
|
761
774
|
|
|
775
|
+
export async function buildTelegramPromptTurn(
|
|
776
|
+
options: BuildTelegramPromptTurnOptions,
|
|
777
|
+
): Promise<PendingTelegramTurn> {
|
|
778
|
+
const buildTurn = await prepareTelegramPromptTurn(options);
|
|
779
|
+
return buildTurn(options.queueOrder, options.historyTurns ?? []);
|
|
780
|
+
}
|
|
781
|
+
|
|
762
782
|
export async function buildTelegramPromptTurnRuntime(
|
|
763
783
|
options: BuildTelegramPromptTurnRuntimeOptions,
|
|
764
784
|
): Promise<PendingTelegramTurn> {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@llblab/pi-codex-usage": "0.9.4",
|
|
46
46
|
"@llblab/pi-grow-loop": "0.8.1",
|
|
47
47
|
"@llblab/pi-state-flow": "0.9.6",
|
|
48
|
-
"@llblab/pi-telegram": "0.45.
|
|
48
|
+
"@llblab/pi-telegram": "0.45.7",
|
|
49
49
|
"@llblab/skills": "1.15.0"
|
|
50
50
|
},
|
|
51
51
|
"bundledDependencies": [
|