@llblab/pi-kit 0.35.0 → 0.35.1
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/BACKLOG.md +1 -0
- package/CHANGELOG.md +5 -0
- package/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +6 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +6 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +3 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +11 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +21 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +40 -45
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +7 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +80 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +33 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +6 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +46 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +137 -13
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +10 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +19 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +23 -11
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +5 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +3 -1
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +4 -2
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +13 -2
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +2 -1
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +21 -8
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +43 -55
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +80 -0
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +36 -3
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +43 -4
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +3 -2
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +140 -18
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +22 -5
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +25 -15
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +2 -2
package/BACKLOG.md
CHANGED
|
@@ -4,6 +4,7 @@ Package pins, resource order and bundled runtime ownership remain authoritative
|
|
|
4
4
|
|
|
5
5
|
## Carried checks
|
|
6
6
|
|
|
7
|
+
- **Installed 0.35.1 Telegram smoke (operator-owned):** After separately authorized kit installation/reload, verify Reroute removes an exhausted temporary tab and Restore keeps the new tab while deleting its unprotected predecessor. Confirm protected work survives. Guest PNG/JPEG, document and MP3 replacement plus staging cleanup remain explicitly deferred upstream live checks; verify unsupported OGG/Opus feedback separately. Upstream idle-leader acceptance and kit packaging checks do not certify this installation.
|
|
7
8
|
- **Installed 0.35.0 Telegram smoke (operator-owned):** After separately authorized installation/reload of both peers, verify same-process follower session replacement keeps the Thread, letter slot and title without a second connected notice. Check shared `threads.displayMode` through Settings across profiles and confirm the retired profile key is ignored without migration. Package validation does not certify installed clients.
|
|
8
9
|
- **Installed 0.34.3 typing smoke (operator-owned):** After separately authorized installation/reload of both peers, verify native typing during streaming, UI prompts and compaction without recurring local authorization churn. Check actual client versions, including iOS; successful HTTP issuance and synthetic tests do not prove client visibility. Use disposable sessions and preserve accepted work.
|
|
9
10
|
- **Installed 0.34.2 smoke (operator-owned):** After separately authorized installation/reload, verify Pi 1.2 custom-message completions no longer raise the Telegram prompt-hook error, adjacent button matrices retain their rows and callbacks, and native Windows Codex CLI fallback works on an actual Windows host. Use disposable settings; package validation and synthetic SDK probes do not certify installed clients or authorize live fault injection.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@llblab/pi-kit` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.35.1: Telegram Guest Media and Routing Cleanup
|
|
6
|
+
|
|
7
|
+
- `Telegram cleanup`: Advances only the exact Telegram pin to `0.57.1`. Exhausted Reroute tabs and Restore's old tab are no longer blocked by unrelated targetless creation attempts, while accepted work and uncertain deletion stay protected. Fresh local menu Reroute and supported Restore hold command effects through their cleanup decision. Other pins, package membership and resource order are unchanged.
|
|
8
|
+
- `Guest media`: Queued photos, documents and MP3 now replace an acknowledged Guest Mode placeholder instead of being silently skipped. Confirmed failures report errors, uncertain edits are not replayed, and unsupported OGG/Opus replacement is rejected explicitly. Guest media client acceptance remains a deferred upstream follow-up, not proof supplied by kit validation.
|
|
9
|
+
|
|
5
10
|
## 0.35.0: Telegram Thread Continuity and Shared Settings
|
|
6
11
|
|
|
7
12
|
- `Telegram continuity`: Advances only the exact Telegram pin to `0.57.0`. Same-process follower session replacement preserves the Thread, letter slot and acknowledged title without a second connected notice; cross-process handoff notices use the acknowledged title. Other pins, package membership and resource order are unchanged.
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Package links lead to the owning repositories for usage, documentation, issues,
|
|
|
18
18
|
| [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.13.1` | OpenAI/legacy Codex subscription quota and Business credit status, with persistent priority Fast toggle mirrored in Telegram |
|
|
19
19
|
| [`@llblab/pi-grow-loop`](https://github.com/llblab/pi-grow-loop) | `0.9.0` | Visible continuation scheduling and bounded worker Skills through compiled, manifest-owned resources |
|
|
20
20
|
| [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.26.4` | Scoped context/memory compiler with step-independent current memory, stable Passive/Active modes and visible blocked-state status |
|
|
21
|
-
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.57.
|
|
21
|
+
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.57.1` | Telegram companion with per-prompt Steer, queued controls, Reroute/Restore, recoverable connections, previews, follower Threads, files, and voice |
|
|
22
22
|
| [`@llblab/skills`](https://github.com/llblab/skills) | `1.15.0` | Portable workflows for engineering, review, design, context maintenance, and other focused tasks |
|
|
23
23
|
|
|
24
24
|
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.
|
|
@@ -90,7 +90,7 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
90
90
|
- Foreign forwarding settles as `accepted`, `retryable`, or `terminal-rejected`. Only an authenticated acknowledgement carrying the expected `deliveryId` and `sourceUpdateId` releases leader journal authority. Negative, missing, stale, mismatched, or capacity-failed settlement remains durable; callback error answers are side effects only. Exception: the operator-approved fixed chooser deadline may discard its exact unclaimed donor pending source without a body archive or delivery-success claim. Accepted recipient queue/work and bound Threads are untouched; old donor controls and late reports cannot replay it. The lifetime and temporary cleanup contract lives in `docs/architecture.md`.
|
|
91
91
|
- A forwarding delivery id is stable across registration replacement and derives from envelope kind, source `update_id`, and stable recipient binding. Runtime instance and registration generation remain separate attempt fences. Persisted message ownership carries the stable binding so replay can rebind only to its current authenticated registration.
|
|
92
92
|
- A queued receipt persists its acquiring runtime instance, OS pid/process-birth identity, session generation, acquisition id, and acquisition time. Only exact authority may settle or discard it. Cached presence is not current execution proof: prepared custody must revalidate the exact queued owner/group without recovery and refuse offers or uncertain reads. Completion requires an exact removal acknowledgement, never merely `!ready`; a retained acknowledgement permits local cleanup only, not replay. Same-process session replacement may reconstruct the claim and the original process may settle after transport ownership moves; a foreign process may neither replay nor settle it through generic removal or a copied acquisition id.
|
|
93
|
-
- Startup and elapsed time are not owner-death proof; queued authority has no time lease. Dead-owner cleanup groups the complete receipt and transactionally rechecks pid liveness plus process-birth identity: only an absent PID or mismatched stable Linux/macOS/Windows birth proof discards all session-owned sources without replay; a matching proof is `alive`, while inaccessible birth metadata is `unverifiable`, and both non-dead outcomes keep authority queued. Under actual `A`–`Z` allocation pressure, retirement may invoke that journal-owned CAS only for a current inactive binding after complete strict source inspection, no local work/live owner/delivery authority, whole unoffered receipt groups, and a preflight proving every grouped owner dead. It must then recapture all protection before preparing deletion; partial progress never grants deletion authority. The live-transfer contract is authenticated offer → exact-generation bounded payload staging → recipient CAS acceptance → exact receipt-and-owner ACK → donor removal → recipient readiness. The offer freezes donor settlement/recovery; controls rebuild local closures; negative/mismatched pre-acceptance ACK cancels only an unaccepted offer and retains donor work; a lost post-acceptance ACK cannot cancel recipient authority and leaves donor memory frozen for explicit reconciliation.
|
|
93
|
+
- Startup and elapsed time are not owner-death proof; queued authority has no time lease. Dead-owner cleanup groups the complete receipt and transactionally rechecks pid liveness plus process-birth identity: only an absent PID or mismatched stable Linux/macOS/Windows birth proof discards all session-owned sources without replay; a matching proof is `alive`, while inaccessible birth metadata is `unverifiable`, and both non-dead outcomes keep authority queued. Under actual `A`–`Z` allocation pressure, retirement may invoke that journal-owned CAS only for a current inactive binding after complete strict source inspection, no local work/live owner/delivery authority, whole unoffered receipt groups, and a preflight proving every grouped owner dead. It must then recapture all protection before preparing deletion; partial progress never grants deletion authority. Fully resolved, positively unbound temporary-tab cleanup may independently recover historical v1 queued receipts under its profile admission and journal source serialization: complete strict namespace/reference inspection, whole unoffered receipt groups, fresh process-death proof and the existing journal CAS are required. Alive/unverifiable owners and unsupported custody remain protected; this recovery never runs from read-only protection checks, and every ordinary target/source protection is recaptured before deletion. The live-transfer contract is authenticated offer → exact-generation bounded payload staging → recipient CAS acceptance → exact receipt-and-owner ACK → donor removal → recipient readiness. The offer freezes donor settlement/recovery; controls rebuild local closures; negative/mismatched pre-acceptance ACK cancels only an unaccepted offer and retains donor work; a lost post-acceptance ACK cannot cancel recipient authority and leaves donor memory frozen for explicit reconciliation.
|
|
94
94
|
- Execution failures persist bounded diagnostics and attempt state as `retry-wait`, except that an exact Telegram HTTP 400 stale/deleted-thread API failure with a proven `{chatId, threadId}` terminally settles the currently executing source after best-effort shared binding invalidation. Automatic retry continues indefinitely with exponential `1s → 2s → 4s → 8s → 16s → 32s → 60s` delay capped at 60 seconds; later independent updates continue draining, durable authority is never silently discarded, and legacy `failed` entries resume automatically at startup. Snapshot-plus-segment journals compact only after 256 unapplied revisions or 4 MiB; snapshot-first cleanup tolerates redundant segments, and empty authority may atomically rebind bot/profile identity. Missing snapshots left by the retired broad temp cleanup rebuild only from a complete provably empty segment chain, while revisionless snapshots may recover from a validated later segment predecessor; otherwise the transaction-locked reset deletes damaged segments before publishing a fresh journal, with accepted input loss and informational evidence, not a quarantine copy. Unsupported versions remain untouched; strict protection reads never reset. Startup best-effort removes obsolete current-runtime root/session `recovery/` folders without touching pre-0.52.0 storage.
|
|
95
95
|
- Business connection chats are a separate namespace even when their chat/message IDs match bot-chat IDs. Default DM routing must never infer private-queue deletion intent from `deleted_business_messages`; raw companion handlers remain separate owners.
|
|
96
96
|
- Clock-bearing chooser sources survive cold spending until their fixed deadline. A restarted owner revives one in place only while its saved phase is still `waiting`, its recorded chooser location is known, its tab stays unbound and live threads exist; that waiting phase is the proof no selection or effect ran. Selected, expired or bound sources keep the protective hold, and new-world forgetting preserves their positively unbound temporary frame. Expiry is a donor discard, not recipient cancellation or source execution completion. Confirmed expiry releases worker custody before UI/cleanup awaits; metadata retries retain no prompt bodies and cannot repeat an issued delete.
|
|
@@ -2,7 +2,12 @@
|
|
|
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
|
-
- [ ] **
|
|
5
|
+
- [ ] **Routing extensions and follow-up coverage.** Preserve live/unverifiable owners, offered receipts, exact source references, session/transport fences and one-shot deletion while extending beyond the current supported handoffs.
|
|
6
|
+
- [ ] **Follower menu handoff.** Establish whether deletion-before-execution warrants a separately negotiated Reroute acceptance/release contract. Ordinary follower ACK confirms durable custody, not held execution; existing held-command RPCs belong to Restore relocation and cannot silently change ordinary forwarding. Require exact recipient generation, bounded observation and no replay after uncertain release.
|
|
7
|
+
- [ ] **Non-menu and historical-source boundary.** Resolve the intended ordering for mutating/queue-producing commands and cold/revived originals before expanding the fresh local menu path. Queue receipt acceptance and historical retention must remain authoritative; never pre-complete sources, weaken fresh-source readiness or block accepted work solely to force deletion. Preserve current behavior until a supported handoff is proven.
|
|
8
|
+
- [ ] **Protected-work live coverage.** Exercise Reroute and Restore with protected earlier work to confirm cleanup waits without losing work. Idle leader acceptance and native regressions do not prove every busy-session client scenario. No manual journal edits or deletion of live tabs to obtain evidence.
|
|
9
|
+
- [ ] **Guest inline media live acceptance.** Explicitly deferred beyond 0.57.1 by operator release approval; not completed. After separately authorized reload, verify queued PNG/JPEG, documents and MP3 replace the acknowledged placeholder, with and without caption, and staging messages are removed. Confirm OGG/Opus attachment rejection and visible voice-markup explanation. Mocked Bot API coverage does not prove live-client rendering; use disposable artifacts and preserve accepted work.
|
|
10
|
+
- [ ] **Connection recovery live acceptance.** Verify operator-run leader/follower reconnect after interrupted creation on updated peers. Known target reuse and unknown-target abandonment have separate local regressions; do not infer live recovery from them. Also verify a long-deleted follower tab and follower `/start` Restore with negotiated menu-alias support. Record leftover-tab behavior, exact peer capabilities and diagnostics; local regressions and release authorization do not replace this live evidence.
|
|
6
11
|
- [ ] **Steer follow-up live acceptance.** Release authorization explicitly defers these checks beyond 0.56.0; do not treat automated coverage or the accepted busy-run smoke as live proof for unrun scenarios. Preserve the [queue and dispatch contract](./docs/architecture.md#queue-and-dispatch-safety).
|
|
7
12
|
- [ ] **Operator scenarios.** Verify final selector/counter rendering; idle packets, native pending input, compaction, model switching, abort/Continue, complete media groups, Reroute/Restore, negotiated leader/follower transfer, reply/preview ownership and session-generation replacement. Record actual Pi/OS/Telegram versions, peer capabilities and redacted outcomes. Reload and disposable live tests require operator intent; do not manipulate runtime files to obtain acceptance.
|
|
8
13
|
- [ ] **Restore recipient refusal after leadership change.** Diagnose a retained routing chooser refusing the selected current leader with unavailable recipient authority. Identify the exact context/session/Workspace mismatch before proposing recovery; do not treat an absent follower registry as the cause of a leader-target refusal, bypass fences or mutate live state.
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.57.1: Guest Media and Routing Cleanup
|
|
8
|
+
|
|
9
|
+
- `Guest attachments`: Queued photos, documents and MP3 audio now replace the acknowledged Guest Mode placeholder instead of being silently skipped. Upload failures and confirmed edit rejections report errors; uncertain edits are not replayed or overwritten. OGG/Opus attachments are rejected before queueing on this path, and voice markup explains the limitation. Live-client media acceptance remains a deferred follow-up. Fixes #346.
|
|
10
|
+
- `Routing cleanup`: Exhausted Reroute tabs and a restored instance's old tab are no longer blocked by unrelated creation attempts with unknown targets. Uncertain creation records stay intact; binding, slot, owner and concrete or recovered target conflicts still protect. Temporary cleanup can recover whole historical receipts from proven-dead owners; live/unverifiable owners, handoff offers and damaged sources remain protected. Idle leader Reroute and Restore passed operator live checks.
|
|
11
|
+
- `Command ordering`: Fresh local menu Reroute waits for exact source removal and a temporary-tab cleanup attempt before rendering. Restore confirms the new binding, decides old-tab cleanup, then releases held input. Busy work retains protection, and uncertain deletion never repeats. Other Reroute commands, follower delivery and sources without fresh completion readiness retain their existing paths.
|
|
12
|
+
|
|
7
13
|
## 0.57.0: Thread Continuity and Shared Settings
|
|
8
14
|
|
|
9
15
|
- `Config`: Thread display now lives in shared top-level `threads.displayMode`, beside the other Settings preferences; profiles hold only bot identity and the paired owner. The old `profiles.<name>.threadDisplayMode` key is not migrated and is ignored, so configs without the new key use the Letters default until Settings saves a choice.
|
|
@@ -269,7 +269,7 @@ interface TelegramLifecycleBindingDeps {
|
|
|
269
269
|
shutdownGenerativeAppLiveSurfaces?: () => void;
|
|
270
270
|
resolveAutomaticThreadCleanupEnabled?: () => boolean | Promise<boolean>;
|
|
271
271
|
buttonActionStore: OutboundHandlers.TelegramButtonActionStore;
|
|
272
|
-
callMultipart:
|
|
272
|
+
callMultipart: (...args: Parameters<TelegramApi.TelegramBridgeApiRuntime["callMultipart"]>) => Promise<unknown>;
|
|
273
273
|
sendChatAction: NonNullable<OutboundHandlers.TelegramVoiceReplySenderDeps["sendChatAction"]>;
|
|
274
274
|
sendRecordVoiceAction: NonNullable<OutboundHandlers.TelegramVoiceReplySenderDeps["sendRecordVoiceAction"]>;
|
|
275
275
|
sendMarkdownReply: Queue.TelegramAgentEndHookRuntimeDeps<Queue.PendingTelegramTurn, Pi.ExtensionContext, Pi.AgentEndEvent["messages"][number], Keyboard.TelegramInlineKeyboardMarkup>["sendMarkdownReply"];
|
|
@@ -278,6 +278,7 @@ interface TelegramLifecycleBindingDeps {
|
|
|
278
278
|
onSteerTurnEnd?: TelegramQueueBindingRuntime<Pi.ExtensionContext>["onTurnEnd"];
|
|
279
279
|
onPromptHandedOff?: (turn: Queue.PendingTelegramTurn, ctx: Pi.ExtensionContext) => void;
|
|
280
280
|
answerGuestQuery: TelegramApi.TelegramBridgeApiRuntime["answerGuestQuery"];
|
|
281
|
+
editGuestInlineMessage?: TelegramApi.TelegramBridgeApiRuntime["editGuestInlineMessage"];
|
|
281
282
|
deleteMessage: TelegramApi.TelegramBridgeApiRuntime["deleteMessage"];
|
|
282
283
|
sendGuestReply: NonNullable<Queue.TelegramAgentEndHookRuntimeDeps<Queue.PendingTelegramTurn, Pi.ExtensionContext, Pi.AgentEndEvent["messages"][number], Keyboard.TelegramInlineKeyboardMarkup>["sendGuestReply"]>;
|
|
283
284
|
editGuestReply?: Queue.TelegramAgentEndHookRuntimeDeps<Queue.PendingTelegramTurn, Pi.ExtensionContext, Pi.AgentEndEvent["messages"][number], Keyboard.TelegramInlineKeyboardMarkup>["editGuestReply"];
|
|
@@ -300,5 +301,5 @@ interface TelegramLifecycleBindingDeps {
|
|
|
300
301
|
/** Settled-session hint for owners that wait on agent idleness, such as old-tab cleanup after a live rebind. */
|
|
301
302
|
onSessionSettled?: (ctx: Pi.ExtensionContext) => void;
|
|
302
303
|
}
|
|
303
|
-
export declare function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, diagnostics, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, onSessionStarted, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onSteerTurnEnd, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive, getSessionGeneration, isTurnTransportActive, updateStatus, recordRuntimeEvent, onSessionSettled, }: TelegramLifecycleBindingDeps): void;
|
|
304
|
+
export declare function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, diagnostics, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, onSessionStarted, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onSteerTurnEnd, onPromptHandedOff, answerGuestQuery, editGuestInlineMessage, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive, getSessionGeneration, isTurnTransportActive, updateStatus, recordRuntimeEvent, onSessionSettled, }: TelegramLifecycleBindingDeps): void;
|
|
304
305
|
export {};
|
|
@@ -657,7 +657,7 @@ export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, pe
|
|
|
657
657
|
},
|
|
658
658
|
});
|
|
659
659
|
}
|
|
660
|
-
export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, diagnostics, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, onSessionStarted, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onSteerTurnEnd, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive = () => true, getSessionGeneration, isTurnTransportActive, updateStatus, recordRuntimeEvent, onSessionSettled, }) {
|
|
660
|
+
export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, diagnostics, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, onSessionStarted, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onSteerTurnEnd, onPromptHandedOff, answerGuestQuery, editGuestInlineMessage, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive = () => true, getSessionGeneration, isTurnTransportActive, updateStatus, recordRuntimeEvent, onSessionSettled, }) {
|
|
661
661
|
const agentEndResetter = Runtime.createTelegramAgentEndResetter({
|
|
662
662
|
abort,
|
|
663
663
|
typing,
|
|
@@ -677,7 +677,7 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
677
677
|
recordOwnership: recordMessageOwnership,
|
|
678
678
|
recordRuntimeEvent,
|
|
679
679
|
});
|
|
680
|
-
const sendGuestAttachment = async (turn, attachment, caption) => {
|
|
680
|
+
const sendGuestAttachment = async (turn, attachment, caption, options) => {
|
|
681
681
|
const stagingTarget = proactivePushTargetGetter();
|
|
682
682
|
const stagingChatId = stagingTarget?.chatId;
|
|
683
683
|
if (stagingChatId === undefined) {
|
|
@@ -685,11 +685,19 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
685
685
|
}
|
|
686
686
|
await OutboundAttachments.deliverTelegramGuestCachedAttachment({
|
|
687
687
|
guestQueryId: turn.guestQueryId,
|
|
688
|
+
guestInlineMessageId: turn.guestInlineMessageId,
|
|
689
|
+
editGuestInlineMessage,
|
|
690
|
+
isDeliveryActive: options?.isDeliveryActive,
|
|
688
691
|
stagingChatId,
|
|
689
692
|
stagingTarget,
|
|
690
693
|
attachment,
|
|
691
694
|
caption,
|
|
692
|
-
sendMultipart: callMultipart,
|
|
695
|
+
sendMultipart: (method, fields, fileField, path, name) => callMultipart(method, fields, fileField, path, name, {
|
|
696
|
+
assertAuthority() {
|
|
697
|
+
if (options?.isDeliveryActive?.() === false)
|
|
698
|
+
throw new Error("Guest attachment upload lost authority.");
|
|
699
|
+
},
|
|
700
|
+
}),
|
|
693
701
|
answerGuestQuery: (guestQueryId, result) => answerGuestQuery(guestQueryId, undefined, { result }),
|
|
694
702
|
answerGuestText: (guestQueryId, text) => answerGuestQuery(guestQueryId, text),
|
|
695
703
|
fallbackText: caption ||
|
|
@@ -339,9 +339,9 @@ export function createTelegramBusAwareApiRuntime(deps) {
|
|
|
339
339
|
? deps.directRuntime.answerGuestQueryForInlineMessage(guestQueryId, text, options)
|
|
340
340
|
: rejectTelegramDirectOwnership("answerGuestQueryForInlineMessage");
|
|
341
341
|
},
|
|
342
|
-
editGuestInlineMessage(inlineMessageId, content) {
|
|
342
|
+
editGuestInlineMessage(inlineMessageId, content, options) {
|
|
343
343
|
return deps.ownsDirect()
|
|
344
|
-
? deps.directRuntime.editGuestInlineMessage(inlineMessageId, content)
|
|
344
|
+
? deps.directRuntime.editGuestInlineMessage(inlineMessageId, content, options)
|
|
345
345
|
: rejectTelegramDirectOwnership("editGuestInlineMessage");
|
|
346
346
|
},
|
|
347
347
|
async deleteMessage(chatId, messageId) {
|
|
@@ -627,6 +627,8 @@ export interface TelegramSelectedCommandAdmission {
|
|
|
627
627
|
/** Independent exact recipient/target lifetime, including inside captured menu transport adapters. */
|
|
628
628
|
assertRecipientCurrent(): void;
|
|
629
629
|
reportCompleted(): boolean;
|
|
630
|
+
/** Optional recipient release barrier after acceptance; rejection suppresses effects without replay. */
|
|
631
|
+
beforeEffects?(): Promise<void>;
|
|
630
632
|
}
|
|
631
633
|
export declare function createTelegramCommandHandler<TMessage extends TelegramCommandRuntimeMessage, TContext>(deps: TelegramCommandRuntimeDeps<TMessage, TContext>): ((commandName: string | undefined, message: TMessage, ctx: TContext, commandArgs?: string) => Promise<boolean>) & {
|
|
632
634
|
/** Command-only producer execution; Routing must supply the captured plan and released exact-source authority. */
|
|
@@ -1667,7 +1667,7 @@ export function createTelegramCommandHandler(deps) {
|
|
|
1667
1667
|
const captured = { ...deps }, parsed = { ...command }, message = parsed.name === "status"
|
|
1668
1668
|
? copySelectedMenuCarrier(original)
|
|
1669
1669
|
: copySelectedReplyAnchor(original);
|
|
1670
|
-
const assertRecipientCurrent = admission.assertRecipientCurrent, report = admission.reportCompleted;
|
|
1670
|
+
const assertRecipientCurrent = admission.assertRecipientCurrent, report = admission.reportCompleted, beforeEffects = admission.beforeEffects;
|
|
1671
1671
|
const effects = parsed.name === "status"
|
|
1672
1672
|
? undefined
|
|
1673
1673
|
: {
|
|
@@ -1693,8 +1693,13 @@ export function createTelegramCommandHandler(deps) {
|
|
|
1693
1693
|
throw new Error(`Selected ${parsed.name} plan owner is unavailable.`);
|
|
1694
1694
|
};
|
|
1695
1695
|
return issueSelectedOnce(assertCurrent, async () => {
|
|
1696
|
-
if (plan.completion === "before-effect"
|
|
1697
|
-
|
|
1696
|
+
if (plan.completion === "before-effect") {
|
|
1697
|
+
if (!report.call(admission))
|
|
1698
|
+
return false;
|
|
1699
|
+
if (beforeEffects)
|
|
1700
|
+
await beforeEffects();
|
|
1701
|
+
assertRecipientCurrent();
|
|
1702
|
+
}
|
|
1698
1703
|
return prepared.run({
|
|
1699
1704
|
message,
|
|
1700
1705
|
command: parsed,
|
|
@@ -1720,7 +1725,7 @@ export function createTelegramCommandHandler(deps) {
|
|
|
1720
1725
|
...copySelectedReplyAnchor(original),
|
|
1721
1726
|
from: { ...original.from },
|
|
1722
1727
|
};
|
|
1723
|
-
const assertRecipientCurrent = admission.assertRecipientCurrent, reportCompleted = admission.reportCompleted;
|
|
1728
|
+
const assertRecipientCurrent = admission.assertRecipientCurrent, reportCompleted = admission.reportCompleted, beforeEffects = admission.beforeEffects;
|
|
1724
1729
|
const assertAdmissionCurrent = createSelectedSemanticGuard(admission, () => captured.assertExecutionCurrent?.(original), false);
|
|
1725
1730
|
const assertSemanticCurrent = () => {
|
|
1726
1731
|
assertAdmissionCurrent();
|
|
@@ -1732,7 +1737,7 @@ export function createTelegramCommandHandler(deps) {
|
|
|
1732
1737
|
persistAllowedUserId: async () => {
|
|
1733
1738
|
throw new Error(`Selected ${name} cannot acquire pairing authority.`);
|
|
1734
1739
|
},
|
|
1735
|
-
}, { assertSemanticCurrent, assertRecipientCurrent, reportCompleted }));
|
|
1740
|
+
}, { assertSemanticCurrent, assertRecipientCurrent, reportCompleted, beforeEffects }));
|
|
1736
1741
|
};
|
|
1737
1742
|
const prepareSelectedConfirmation = (name) => (command, messages, ctx, admission) => {
|
|
1738
1743
|
const original = messages[0], captured = { ...deps };
|
|
@@ -2036,7 +2041,7 @@ export function createTelegramCommandHandler(deps) {
|
|
|
2036
2041
|
typeof admission.reportCompleted !== "function")
|
|
2037
2042
|
return undefined;
|
|
2038
2043
|
const assertSourceCurrent = admission.assertSourceCurrent, assertRecipientCurrent = admission.assertRecipientCurrent;
|
|
2039
|
-
const reportCompleted = admission.reportCompleted, assertExecutionCurrent = deps.assertExecutionCurrent;
|
|
2044
|
+
const reportCompleted = admission.reportCompleted, beforeEffects = admission.beforeEffects, assertExecutionCurrent = deps.assertExecutionCurrent;
|
|
2040
2045
|
const assertIssuanceCurrent = () => {
|
|
2041
2046
|
assertSourceCurrent.call(admission);
|
|
2042
2047
|
assertExecutionCurrent?.call(deps, original);
|
|
@@ -2060,8 +2065,14 @@ export function createTelegramCommandHandler(deps) {
|
|
|
2060
2065
|
openSettingsMenu: menuDeps.openSettingsMenu &&
|
|
2061
2066
|
((value, context) => menuDeps.openSettingsMenu(value, context, recipientOptions)),
|
|
2062
2067
|
};
|
|
2063
|
-
return issueSelectedOnce(assertIssuanceCurrent, async () =>
|
|
2064
|
-
|
|
2068
|
+
return issueSelectedOnce(assertIssuanceCurrent, async () => {
|
|
2069
|
+
if (!reportCompleted.call(admission))
|
|
2070
|
+
return false;
|
|
2071
|
+
if (beforeEffects)
|
|
2072
|
+
await beforeEffects();
|
|
2073
|
+
assertRecipientCurrent();
|
|
2074
|
+
return handleTelegramCommandRuntime(name, message, ctx, recipientDeps, args);
|
|
2075
|
+
});
|
|
2065
2076
|
},
|
|
2066
2077
|
});
|
|
2067
2078
|
}
|
|
@@ -2157,6 +2168,8 @@ async function handleTelegramHelpCommand(commandName, message, ctx, deps, admiss
|
|
|
2157
2168
|
assertSemanticCurrent();
|
|
2158
2169
|
if (!admission.reportCompleted())
|
|
2159
2170
|
return false;
|
|
2171
|
+
if (admission.beforeEffects)
|
|
2172
|
+
await admission.beforeEffects();
|
|
2160
2173
|
admission.assertRecipientCurrent();
|
|
2161
2174
|
}
|
|
2162
2175
|
const assertEffectCurrent = admission?.assertRecipientCurrent ??
|
|
@@ -1033,6 +1033,29 @@ export default function (pi) {
|
|
|
1033
1033
|
delivery: deliveryLifecycleRuntime,
|
|
1034
1034
|
api: telegramApiTargetActivityRuntime,
|
|
1035
1035
|
});
|
|
1036
|
+
const prepareTemporaryThreadJournalInspection = function (target, requiredJournalBindingKeys, ownInputs) {
|
|
1037
|
+
const binding = resolveTelegramUpdateJournalBinding();
|
|
1038
|
+
const botToken = configStore.getBotToken();
|
|
1039
|
+
if (!binding || !botToken)
|
|
1040
|
+
return undefined;
|
|
1041
|
+
const profile = configStore.getActiveProfileName() ?? "default";
|
|
1042
|
+
const botIdentity = Journal.createTelegramUpdateJournalBotIdentity({ botToken, botId: getTelegramBotId() });
|
|
1043
|
+
return {
|
|
1044
|
+
directory: Paths.resolveTelegramTempDir(), profile, botIdentity,
|
|
1045
|
+
pollingPath: telegramLeaderJournalPath.resolve(configStore.getActiveProfileName()),
|
|
1046
|
+
requiredJournalBindingKeys: [binding.recoveryKey, ...requiredJournalBindingKeys],
|
|
1047
|
+
...(target.threadId !== undefined && ownInputs
|
|
1048
|
+
? { cleanup: { target: { chatId: target.chatId, threadId: target.threadId }, ownInputs } } : {}),
|
|
1049
|
+
limits: { maxDirectoryEntries: 10_000, maxFiles: 4096, maxBytes: 64 * 1024 * 1024,
|
|
1050
|
+
maxEntries: 10_000, maxWork: 100_000 },
|
|
1051
|
+
withSourceReference(path, operation) {
|
|
1052
|
+
return telegramJournalReferenceRegistry.withReference({
|
|
1053
|
+
referenceClass: "operator-disposition",
|
|
1054
|
+
recoveryKey: Journal.createTelegramUpdateJournalBindingKey({ path, profileName: profile, botIdentity }),
|
|
1055
|
+
}, operation);
|
|
1056
|
+
},
|
|
1057
|
+
};
|
|
1058
|
+
};
|
|
1036
1059
|
const inboundRouteRuntime = Routing.createTelegramInboundRouteRuntime({
|
|
1037
1060
|
configStore,
|
|
1038
1061
|
callApi: callTelegramApi,
|
|
@@ -1112,53 +1135,24 @@ export default function (pi) {
|
|
|
1112
1135
|
});
|
|
1113
1136
|
},
|
|
1114
1137
|
inspectTemporaryThreadSources(target, requiredJournalBindingKeys, ownInputs) {
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
if (!binding || !botToken)
|
|
1138
|
+
const input = prepareTemporaryThreadJournalInspection(target, requiredJournalBindingKeys, ownInputs);
|
|
1139
|
+
if (!input)
|
|
1118
1140
|
return undefined;
|
|
1119
|
-
const profile = configStore.getActiveProfileName() ?? "default";
|
|
1120
|
-
const botIdentity = Journal.createTelegramUpdateJournalBotIdentity({
|
|
1121
|
-
botToken,
|
|
1122
|
-
botId: getTelegramBotId(),
|
|
1123
|
-
});
|
|
1124
1141
|
return withTelegramJournalSourceSerialization(function () {
|
|
1125
|
-
return Journal.isTelegramThreadCleanupJournalNamespaceClear(
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
},
|
|
1140
|
-
}
|
|
1141
|
-
: {}),
|
|
1142
|
-
limits: {
|
|
1143
|
-
maxDirectoryEntries: 10_000,
|
|
1144
|
-
maxFiles: 4096,
|
|
1145
|
-
maxBytes: 64 * 1024 * 1024,
|
|
1146
|
-
maxEntries: 10_000,
|
|
1147
|
-
maxWork: 100_000,
|
|
1148
|
-
},
|
|
1149
|
-
withSourceReference(path, operation) {
|
|
1150
|
-
return telegramJournalReferenceRegistry.withReference({
|
|
1151
|
-
referenceClass: "operator-disposition",
|
|
1152
|
-
recoveryKey: Journal.createTelegramUpdateJournalBindingKey({
|
|
1153
|
-
path,
|
|
1154
|
-
profileName: profile,
|
|
1155
|
-
botIdentity,
|
|
1156
|
-
}),
|
|
1157
|
-
}, operation);
|
|
1158
|
-
},
|
|
1159
|
-
})
|
|
1160
|
-
? []
|
|
1161
|
-
: undefined;
|
|
1142
|
+
return Journal.isTelegramThreadCleanupJournalNamespaceClear(input) ? [] : undefined;
|
|
1143
|
+
});
|
|
1144
|
+
},
|
|
1145
|
+
recoverTemporaryThreadDeadQueueOwners(target, requiredJournalBindingKeys, ownInputs, isCurrent) {
|
|
1146
|
+
const input = prepareTemporaryThreadJournalInspection(target, requiredJournalBindingKeys, ownInputs);
|
|
1147
|
+
if (!input || !isCurrent())
|
|
1148
|
+
return 0;
|
|
1149
|
+
return Journal.recoverTelegramThreadCleanupDeadQueueOwners({
|
|
1150
|
+
...input, isCurrent, withSourceSerialization: withTelegramJournalSourceSerialization,
|
|
1151
|
+
recoveryOwner: {
|
|
1152
|
+
instanceId: telegramInstanceId, processId: telegramProcessId,
|
|
1153
|
+
processBirthId: telegramQueueProcessBirthId,
|
|
1154
|
+
sessionGeneration: telegramSessionContextStore.getGeneration(),
|
|
1155
|
+
},
|
|
1162
1156
|
});
|
|
1163
1157
|
},
|
|
1164
1158
|
hasWorkspaceRestoreAuthority() {
|
|
@@ -2485,6 +2479,7 @@ export default function (pi) {
|
|
|
2485
2479
|
deleteMessage: deleteTelegramMessage,
|
|
2486
2480
|
sendGuestReply,
|
|
2487
2481
|
editGuestReply,
|
|
2482
|
+
editGuestInlineMessage: telegramApiRuntime.editGuestInlineMessage,
|
|
2488
2483
|
stopGuestPlaceholder: guestPlaceholderRuntime.stop,
|
|
2489
2484
|
finalizeMarkdownPreview,
|
|
2490
2485
|
preparePreviewDelivery,
|
|
@@ -751,6 +751,13 @@ export declare function isTelegramThreadCleanupJournalNamespaceClear(input: Tele
|
|
|
751
751
|
}[];
|
|
752
752
|
};
|
|
753
753
|
}): boolean;
|
|
754
|
+
/** Demand-driven recovery only; caller holds temporary-cleanup admission. Never a deletion grant. */
|
|
755
|
+
export declare function recoverTelegramThreadCleanupDeadQueueOwners(input: Parameters<typeof isTelegramThreadCleanupJournalNamespaceClear>[0] & {
|
|
756
|
+
recoveryOwner: TelegramUpdateJournalQueueOwnerIdentity;
|
|
757
|
+
isCurrent: () => boolean;
|
|
758
|
+
withSourceSerialization: NonNullable<TelegramUpdateJournalStoreOptions["withSourceSerialization"]>;
|
|
759
|
+
getQueueProcessLiveness?: TelegramUpdateJournalStoreOptions["getQueueProcessLiveness"];
|
|
760
|
+
}): number;
|
|
754
761
|
export declare function publishTelegramUpdateJournalSegment(path: string, segment: TelegramUpdateJournalSegment): TelegramUpdateJournalSegmentPublicationResult;
|
|
755
762
|
export declare function createTelegramUpdateQueueHandoffToken(): string;
|
|
756
763
|
export declare function createTelegramUpdateJournalBotIdentity(input: {
|
|
@@ -1988,6 +1988,86 @@ export function isTelegramThreadCleanupJournalNamespaceClear(input) {
|
|
|
1988
1988
|
return namespace.sources.every((source) => source.evidence.kind === "absent" ||
|
|
1989
1989
|
source.evidence.file.entries.every((entry) => isUnrelatedPlainInput(entry, source.path)));
|
|
1990
1990
|
}
|
|
1991
|
+
/** Demand-driven recovery only; caller holds temporary-cleanup admission. Never a deletion grant. */
|
|
1992
|
+
export function recoverTelegramThreadCleanupDeadQueueOwners(input) {
|
|
1993
|
+
return input.withSourceSerialization(() => {
|
|
1994
|
+
const assertCurrent = () => {
|
|
1995
|
+
if (!input.isCurrent())
|
|
1996
|
+
throw new Error("Temporary cleanup queue recovery lost authority.");
|
|
1997
|
+
};
|
|
1998
|
+
assertCurrent();
|
|
1999
|
+
if (!input.cleanup)
|
|
2000
|
+
return 0;
|
|
2001
|
+
// Validate exact references and complete namespace coverage before considering any mutation.
|
|
2002
|
+
if (isTelegramThreadCleanupJournalNamespaceClear(input))
|
|
2003
|
+
return 0;
|
|
2004
|
+
const namespace = inspectTelegramSessionJournalNamespace(input);
|
|
2005
|
+
if (namespace.retainedInputs?.some(value => value.state !== "committed") ||
|
|
2006
|
+
input.requiredJournalBindingKeys.some(key => !namespace.sources.some(source => source.path === getTelegramUpdateJournalBindingPath(key) && source.evidence.kind === "present")))
|
|
2007
|
+
return 0;
|
|
2008
|
+
const plans = [];
|
|
2009
|
+
for (const source of namespace.sources) {
|
|
2010
|
+
if (source.evidence.kind !== "present")
|
|
2011
|
+
continue;
|
|
2012
|
+
const file = source.evidence.file;
|
|
2013
|
+
if (file.version !== TELEGRAM_UPDATE_JOURNAL_VERSION)
|
|
2014
|
+
continue;
|
|
2015
|
+
const groups = new Map();
|
|
2016
|
+
for (const entry of file.entries) {
|
|
2017
|
+
if (!entry.queueReceiptId)
|
|
2018
|
+
continue;
|
|
2019
|
+
const group = groups.get(entry.queueReceiptId) ?? [];
|
|
2020
|
+
group.push(entry);
|
|
2021
|
+
groups.set(entry.queueReceiptId, group);
|
|
2022
|
+
}
|
|
2023
|
+
for (const group of groups.values()) {
|
|
2024
|
+
const entry = group[0];
|
|
2025
|
+
if (entry.state !== "queued" || !entry.queueReceiptId || !entry.queueOwner ||
|
|
2026
|
+
!entry.queueKind || entry.queueHandoff || entry.inputClaim)
|
|
2027
|
+
continue;
|
|
2028
|
+
if (group.some(value => value.state !== "queued" || value.queueHandoff || value.inputClaim ||
|
|
2029
|
+
value.queueKind !== entry.queueKind || !isDeepStrictEqual(value.queueOwner, entry.queueOwner)))
|
|
2030
|
+
continue;
|
|
2031
|
+
plans.push({ path: source.path, recovery: {
|
|
2032
|
+
queueKind: entry.queueKind, receiptId: entry.queueReceiptId,
|
|
2033
|
+
sourceUpdateIds: group.map(value => value.updateId).sort((a, b) => a - b),
|
|
2034
|
+
deadOwner: cloneJournalQueueOwner(entry.queueOwner), recoveryOwner: { ...input.recoveryOwner },
|
|
2035
|
+
} });
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
const liveness = input.getQueueProcessLiveness ?? getTelegramProcessLiveness;
|
|
2039
|
+
const dead = plans.filter(plan => {
|
|
2040
|
+
assertCurrent();
|
|
2041
|
+
const result = liveness(plan.recovery.deadOwner);
|
|
2042
|
+
assertCurrent();
|
|
2043
|
+
return result === "dead";
|
|
2044
|
+
});
|
|
2045
|
+
let removed = 0;
|
|
2046
|
+
for (const plan of dead) {
|
|
2047
|
+
assertCurrent();
|
|
2048
|
+
removed += input.withSourceReference(plan.path, () => {
|
|
2049
|
+
const journal = createTelegramUpdateJournalStore({
|
|
2050
|
+
path: plan.path, profileName: input.profile, botIdentity: input.botIdentity,
|
|
2051
|
+
queueRuntimeIdentity: input.recoveryOwner,
|
|
2052
|
+
// This private synchronous store stays inside the cross-family serializer already held above.
|
|
2053
|
+
withSourceSerialization(operation) { assertCurrent(); return operation(); },
|
|
2054
|
+
sourceAccess: { directory: dirname(plan.path), limits: input.limits },
|
|
2055
|
+
getQueueProcessLiveness(owner) {
|
|
2056
|
+
assertCurrent();
|
|
2057
|
+
const result = liveness(owner);
|
|
2058
|
+
assertCurrent();
|
|
2059
|
+
return result;
|
|
2060
|
+
},
|
|
2061
|
+
withWriterAdmission(operation) { assertCurrent(); return operation(); },
|
|
2062
|
+
});
|
|
2063
|
+
// The journal transaction rechecks whole-group identity, absence of handoff and fresh process-death proof.
|
|
2064
|
+
return journal.recoverDeadQueueOwner(plan.recovery).recoveredUpdateIds.length;
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
assertCurrent();
|
|
2068
|
+
return removed;
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
1991
2071
|
function cloneEntry(entry) {
|
|
1992
2072
|
return {
|
|
1993
2073
|
...entry,
|
|
@@ -73,6 +73,7 @@ export interface TelegramQueuedOutboundAttachmentView {
|
|
|
73
73
|
export interface TelegramOutboundAttachmentQueueTargetView {
|
|
74
74
|
queuedAttachments: TelegramQueuedOutboundAttachmentView[];
|
|
75
75
|
guestQueryId?: string;
|
|
76
|
+
guestInlineMessageId?: string;
|
|
76
77
|
}
|
|
77
78
|
export interface TelegramQueuedOutboundAttachmentTurnView extends TelegramOutboundAttachmentQueueTargetView {
|
|
78
79
|
chatId: number;
|
|
@@ -161,6 +162,17 @@ export declare function queueTelegramOutboundAttachments(options: {
|
|
|
161
162
|
}): Promise<TelegramOutboundAttachmentToolResult>;
|
|
162
163
|
export declare function deliverTelegramGuestCachedAttachment(options: {
|
|
163
164
|
guestQueryId: string;
|
|
165
|
+
guestInlineMessageId?: string;
|
|
166
|
+
editGuestInlineMessage?: (inlineMessageId: string, content: {
|
|
167
|
+
media: {
|
|
168
|
+
type: "photo" | "document" | "audio";
|
|
169
|
+
media: string;
|
|
170
|
+
caption?: string;
|
|
171
|
+
};
|
|
172
|
+
}, options: {
|
|
173
|
+
assertAuthority: () => void;
|
|
174
|
+
}) => Promise<void>;
|
|
175
|
+
isDeliveryActive?: () => boolean;
|
|
164
176
|
stagingChatId: number;
|
|
165
177
|
stagingTarget?: TelegramTarget;
|
|
166
178
|
attachment: TelegramQueuedOutboundAttachmentView;
|
|
@@ -375,6 +375,9 @@ export async function queueTelegramOutboundAttachments(options) {
|
|
|
375
375
|
maxAttachmentSizeBytes: options.maxAttachmentSizeBytes,
|
|
376
376
|
statPath: options.statPath,
|
|
377
377
|
});
|
|
378
|
+
if (options.activeTurn.guestInlineMessageId && pendingAttachments.some((attachment) => getTelegramGuestAttachmentTransport(attachment.path).fileField === "voice")) {
|
|
379
|
+
throw new Error("Telegram Guest Mode cannot replace an inline placeholder with a voice note; no attachment was queued. Use MP3 audio or a normal bot chat.");
|
|
380
|
+
}
|
|
378
381
|
options.activeTurn.queuedAttachments.push(...pendingAttachments);
|
|
379
382
|
const added = pendingAttachments.map((attachment) => attachment.path);
|
|
380
383
|
return {
|
|
@@ -389,6 +392,16 @@ export async function queueTelegramOutboundAttachments(options) {
|
|
|
389
392
|
}
|
|
390
393
|
export async function deliverTelegramGuestCachedAttachment(options) {
|
|
391
394
|
const transport = getTelegramGuestAttachmentTransport(options.attachment.path);
|
|
395
|
+
const isActive = () => options.isDeliveryActive?.() !== false;
|
|
396
|
+
const assertActive = () => {
|
|
397
|
+
if (!isActive())
|
|
398
|
+
throw new Error("Guest attachment delivery lost authority.");
|
|
399
|
+
};
|
|
400
|
+
if (!isActive())
|
|
401
|
+
return;
|
|
402
|
+
if (options.guestInlineMessageId && (!options.editGuestInlineMessage || transport.fileField === "voice")) {
|
|
403
|
+
throw new Error("Guest inline attachment editing is unavailable for this media.");
|
|
404
|
+
}
|
|
392
405
|
let stagingMessageId;
|
|
393
406
|
let answerAttempted = false;
|
|
394
407
|
try {
|
|
@@ -397,6 +410,8 @@ export async function deliverTelegramGuestCachedAttachment(options) {
|
|
|
397
410
|
...getTelegramMultipartTargetFields(options.stagingTarget),
|
|
398
411
|
}, transport.fileField, options.attachment.path, options.attachment.fileName));
|
|
399
412
|
stagingMessageId = message.message_id;
|
|
413
|
+
if (!isActive())
|
|
414
|
+
return;
|
|
400
415
|
const caption = options.caption
|
|
401
416
|
? Array.from(options.caption).slice(0, 1024).join("")
|
|
402
417
|
: undefined;
|
|
@@ -446,11 +461,26 @@ export async function deliverTelegramGuestCachedAttachment(options) {
|
|
|
446
461
|
...(caption ? { caption } : {}),
|
|
447
462
|
};
|
|
448
463
|
}
|
|
464
|
+
if (!isActive())
|
|
465
|
+
return;
|
|
449
466
|
answerAttempted = true;
|
|
450
|
-
|
|
467
|
+
if (options.guestInlineMessageId && options.editGuestInlineMessage) {
|
|
468
|
+
if (result.type === "voice")
|
|
469
|
+
throw new Error("Voice notes cannot replace inline placeholders.");
|
|
470
|
+
const media = result.type === "photo" ? result.photo_file_id
|
|
471
|
+
: result.type === "audio" ? result.audio_file_id : result.document_file_id;
|
|
472
|
+
await options.editGuestInlineMessage(options.guestInlineMessageId, {
|
|
473
|
+
media: { type: result.type, media, ...(caption ? { caption } : {}) },
|
|
474
|
+
}, { assertAuthority: assertActive });
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
await options.answerGuestQuery(options.guestQueryId, result);
|
|
478
|
+
}
|
|
451
479
|
}
|
|
452
480
|
catch (error) {
|
|
453
|
-
if (!
|
|
481
|
+
if (!isActive())
|
|
482
|
+
return;
|
|
483
|
+
if (!options.guestInlineMessageId && !answerAttempted && options.answerGuestText && options.fallbackText) {
|
|
454
484
|
answerAttempted = true;
|
|
455
485
|
await options.answerGuestText(options.guestQueryId, options.fallbackText);
|
|
456
486
|
}
|
|
@@ -459,7 +489,7 @@ export async function deliverTelegramGuestCachedAttachment(options) {
|
|
|
459
489
|
}
|
|
460
490
|
}
|
|
461
491
|
finally {
|
|
462
|
-
if (stagingMessageId !== undefined) {
|
|
492
|
+
if (stagingMessageId !== undefined && isActive()) {
|
|
463
493
|
try {
|
|
464
494
|
await options.deleteMessage(options.stagingChatId, stagingMessageId);
|
|
465
495
|
}
|
|
@@ -506,10 +506,14 @@ export interface TelegramAgentEndRuntimeDeps<TTurn extends PendingTelegramTurn,
|
|
|
506
506
|
}) => Promise<void>;
|
|
507
507
|
sendGuestReply?: (guestQueryId: string, markdown: string) => Promise<void>;
|
|
508
508
|
/** Replaces the early guest ACK with the final text. */
|
|
509
|
-
editGuestReply?: (inlineMessageId: string, markdown: string
|
|
509
|
+
editGuestReply?: (inlineMessageId: string, markdown: string, options?: {
|
|
510
|
+
assertAuthority?: () => void;
|
|
511
|
+
}) => Promise<void>;
|
|
510
512
|
/** Cancels the animated guest placeholder before the final edit. */
|
|
511
513
|
stopGuestPlaceholder?: (inlineMessageId: string) => Promise<void>;
|
|
512
|
-
sendGuestAttachment?: (turn: TTurn, attachment: QueuedAttachment, caption?: string
|
|
514
|
+
sendGuestAttachment?: (turn: TTurn, attachment: QueuedAttachment, caption?: string, options?: {
|
|
515
|
+
isDeliveryActive?: () => boolean;
|
|
516
|
+
}) => Promise<void>;
|
|
513
517
|
sendGuestVoiceReply?: (turn: TTurn, plan: TelegramAgentEndOutboundReplyPlan<TReplyMarkup>, caption?: string) => Promise<void>;
|
|
514
518
|
planOutboundReply?: (markdown: string) => TelegramAgentEndOutboundReplyPlan<TReplyMarkup>;
|
|
515
519
|
sendOutboundReplyArtifacts?: (turn: TTurn, plan: TelegramAgentEndOutboundReplyPlan, options?: {
|