@llblab/pi-telegram 0.54.4 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -2
- package/BACKLOG.md +1 -1
- package/CHANGELOG.md +11 -0
- package/README.md +5 -3
- package/dist/lib/activity.d.ts +5 -1
- package/dist/lib/activity.js +31 -2
- package/dist/lib/bindings.d.ts +3 -1
- package/dist/lib/bindings.js +8 -2
- package/dist/lib/bus-api.d.ts +2 -2
- package/dist/lib/bus-api.js +7 -2
- package/dist/lib/bus-follower.d.ts +49 -3
- package/dist/lib/bus-follower.js +313 -146
- package/dist/lib/bus-leader.d.ts +3 -0
- package/dist/lib/bus-leader.js +99 -18
- package/dist/lib/bus.d.ts +5 -3
- package/dist/lib/bus.js +13 -10
- package/dist/lib/delivery.d.ts +4 -0
- package/dist/lib/delivery.js +48 -15
- package/dist/lib/extension.js +48 -6
- package/dist/lib/lifecycle.d.ts +68 -0
- package/dist/lib/lifecycle.js +178 -58
- package/dist/lib/machine.d.ts +23 -0
- package/dist/lib/machine.js +37 -0
- package/dist/lib/media.d.ts +114 -28
- package/dist/lib/media.js +292 -119
- package/dist/lib/model.d.ts +54 -15
- package/dist/lib/model.js +98 -53
- package/dist/lib/polling.js +2 -0
- package/dist/lib/preview.d.ts +128 -113
- package/dist/lib/preview.js +448 -366
- package/dist/lib/queue.d.ts +2 -2
- package/dist/lib/queue.js +3 -3
- package/dist/lib/routing.d.ts +6 -1
- package/dist/lib/routing.js +334 -210
- package/dist/lib/runtime.d.ts +77 -11
- package/dist/lib/runtime.js +174 -92
- package/dist/lib/sections.js +0 -1
- package/dist/lib/telegram-api.d.ts +11 -9
- package/dist/lib/telegram-api.js +53 -4
- package/dist/lib/text-groups.d.ts +0 -26
- package/dist/lib/text-groups.js +51 -98
- package/dist/lib/thread-display.js +14 -6
- package/dist/lib/threads.d.ts +18 -5
- package/dist/lib/threads.js +107 -30
- package/dist/lib/turns.d.ts +2 -0
- package/dist/lib/turns.js +6 -1
- package/dist/lib/updates.d.ts +11 -4
- package/dist/lib/updates.js +54 -19
- package/dist/package.json +1 -1
- package/docs/activity.md +1 -1
- package/docs/architecture.md +12 -11
- package/docs/delivery.md +7 -5
- package/docs/multi-instance-bus.md +18 -8
- package/docs/public-api.md +2 -2
- package/docs/ui-style.md +2 -2
- package/lib/activity.ts +29 -5
- package/lib/bindings.ts +9 -1
- package/lib/bus-api.ts +11 -3
- package/lib/bus-follower.ts +417 -203
- package/lib/bus-leader.ts +88 -14
- package/lib/bus.ts +17 -15
- package/lib/delivery.ts +47 -19
- package/lib/extension.ts +51 -9
- package/lib/lifecycle.ts +229 -55
- package/lib/machine.ts +60 -0
- package/lib/media.ts +438 -172
- package/lib/model.ts +169 -74
- package/lib/polling.ts +2 -0
- package/lib/preview.ts +715 -566
- package/lib/queue.ts +5 -5
- package/lib/routing.ts +325 -238
- package/lib/runtime.ts +260 -104
- package/lib/sections.ts +0 -2
- package/lib/telegram-api.ts +61 -8
- package/lib/text-groups.ts +75 -136
- package/lib/thread-display.ts +15 -4
- package/lib/threads.ts +120 -36
- package/lib/turns.ts +8 -1
- package/lib/updates.ts +58 -21
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -60,6 +60,7 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
60
60
|
### 4.1 Flat Domain DAG
|
|
61
61
|
|
|
62
62
|
- Cohesive domains live as flat `/lib/*.ts` modules whose local import graph is acyclic.
|
|
63
|
+
- `In-memory lifecycles`: Use pure transitions and a domain-owned effect shell when competing asynchronous sources, generation/cancellation fencing or timers justify the pattern. The shared `machine` leaf owns runner mechanics only; state, events, effects and authority stay with the domain. Durable admission, custody, Workspace and IPC retain their transactional contracts.
|
|
63
64
|
- `lib/extension.ts` constructs high-level runtimes and wires live ports. Domain policy, mutable state, sequencing, identity, retries, normalization, and lifecycle recovery belong to the owning `/lib` module.
|
|
64
65
|
- During refactoring, add a domain only for demonstrated cross-domain reuse or a concrete dependency-direction/cycle problem. Prefer an existing cohesive owner; a closed helper cluster, independent tests or file length alone is insufficient. An acyclic validator result does not justify a new boundary. Do not atomize cohesive modules or create one-use wrappers merely to shrink `index.ts`.
|
|
65
66
|
- `bindings` owns Pi-facing registration and narrow cross-domain assembly; it may connect established ports but must not absorb routing, rendering, transport, or mutable policy.
|
|
@@ -85,7 +86,7 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
85
86
|
- Admission is journal-first: validate and persist the complete `getUpdates` response before one monotonic offset commit, then signal an independent worker without awaiting semantic execution. Missing cursor with a non-empty journal, malformed/foreign authority, or capacity exhaustion fails closed. “Durable” means process-crash recovery after atomic rename, not unflushed host/kernel/filesystem/device/power-loss survival.
|
|
86
87
|
- Storage cutovers must reconcile actual consumer locations before correcting path adapters. Never normalize a relative historical reference into new authority or treat equal reference strings / empty canonical storage as source completeness. Exact-path preflight is lexical only; physical identity, historical coverage, writer closure and migration remain separate proofs.
|
|
87
88
|
- Workspace mutations acquire cross-process admission before their shared process-local gate and hold it through asynchronous API work and durable settlement. Topic lifecycle, complete unbound/reroute target handling, manual disconnect, and session-restart cleanup use profile-wide scope; either retained retirement-fence phase rejects them before state access. Cleanup admission spans intent publication, target mutation, persistence, and successful transport release. Explicit manual disconnect may stop only its captured local transport after cleanup admission/effects fail; it must not bypass Thread mutation fences, release a replacement connection, claim unconfirmed deletion, or retry an uncertain stop. Session-restart cleanup retains its admission-required behavior. Detached reconciliation that mutates Thread state must reacquire fresh profile admission through the same gate; it cannot inherit a caller lease that ended before its timer runs. A live operation ID has one process-local caller: concurrent reuse is rejected before lease acquisition, while retry after the caller exits may resume exact durable authority.
|
|
88
|
-
- Workspace retirement is capacity-pressure-only. Elapsed time and heartbeat silence never trigger deletion; only complete `A`–`Z` exhaustion may propose the oldest continuously proven inactive, fully unprotected binding. Exact deletion, durable retirement, and fence completion precede slot reuse. Authorized demand-driven rotation retries one failed fresh allocation after retirement; restore-only follower startup never evicts. Release ordinary registration/provisioning leases before acquiring the destructive fence, retain the shared mutation gate across retirement, and validate the exact permit immediately before one non-retried deletion. Persist an exact method/target-matched rejection before withdrawing its intent; release that fence only after durable withdrawal, retaining the binding. A later attempt requires fresh operation authority. Protection reads must never repair, quarantine, or reset journals. Non-destructive owner detachment must atomically retain one exact Workspace binding and its letter while removing only its uniquely matched owner record and stamping first inactivity; it never manufactures Thread-deletion evidence or clears accepted work. During authenticated follower registration,
|
|
89
|
+
- Workspace retirement is capacity-pressure-only. Elapsed time and heartbeat silence never trigger deletion; only complete `A`–`Z` exhaustion may propose the oldest continuously proven inactive, fully unprotected binding. Exact deletion, durable retirement, and fence completion precede slot reuse. Authorized demand-driven rotation retries one failed fresh allocation after retirement; restore-only follower startup never evicts. Release ordinary registration/provisioning leases before acquiring the destructive fence, retain the shared mutation gate across retirement, and validate the exact permit immediately before one non-retried deletion. Persist an exact method/target-matched rejection before withdrawing its intent; release that fence only after durable withdrawal, retaining the binding. A later attempt requires fresh operation authority. Protection reads must never repair, quarantine, or reset journals. Non-destructive owner detachment must atomically retain one exact Workspace binding and its letter while removing only its uniquely matched owner record and stamping first inactivity; it never manufactures Thread-deletion evidence or clears accepted work. During authenticated follower registration, a mismatched retained target record is repaired to the exact Workspace claim before binding commit; if same-target commit adopts the retained binding's slot, its returned binding then owns the registration result and owner-record slot. Unrelated bindings that own a stale or provisional letter remain untouched. Same-target re-keying retains historical journal keys and exact source tuples without upgrading unknown completeness. Retained prune observations are bounded, non-routing and registration/profile/epoch/runtime-fenced. One unfinished preservation operation retains its admission identity across fresh-PID-proof retries; it can never become deletion authority. Leader quit requires completed delivery/polling/worker teardown under the captured session generation, profile and epoch; reload/new/resume/fork never establish inactivity. Unknown deletion outcomes retain their fence; only confirmed durable completion permits reuse.
|
|
89
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`.
|
|
90
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.
|
|
91
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.
|
|
@@ -102,8 +103,9 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
102
103
|
- `/stop`, `/abort`, `/next`, and `/continue` respectively reset+abort, abort while preserving queue, force the next turn, and enqueue a control-lane continuation. A negative reaction to an original standalone `/continue` settles and removes only its exact waiting source-addressed continuation; positive reactions do not change its control lane. Protect the Pi-owned pending head and already-started work, preserve other queue positions, and never address synthetic model continuations by their reply anchor. Settlement failure retains the continuation. Abort-history folding applies only to Telegram-owned active turns. A busy `/next` marks only its exact active Telegram turn: terminal abort settlement attempts one explicit abort notice before the exact selected queued prompt receives its dispatch notice. Notice failure is diagnostic and cannot block dispatch; a later `/abort` or `/stop` cancels both pending transition notices before taking ownership. Target command adapters must forward these transition ports. A successful dispatch notice keeps the queued prompt's one reply-header claim across agent start, so later messages in that turn never repeat it.
|
|
103
104
|
- A model selection from an authorized Telegram target may stop and continue any interruptible active agent run in that same Pi session, including local/TUI work. Queue the synthetic control-lane continuation before aborting; when no Telegram prompt owns the run, bind continuation and reply ownership to the exact model-menu chat/Thread/message. Delay abort until every active tool execution settles, and clear both selection and fallback target on cancellation, agent start, settlement, or session replacement.
|
|
104
105
|
- Telegram extension side effects must not hold Pi's core lifecycle hostage after semantic completion. Preserve ordering in extension-owned background work, record failures, and fence target/profile/transport/session authority.
|
|
106
|
+
- In Threaded Mode, All is service/input-only. New output, previews, typing and routing UI require a concrete Thread; neither implicit fallback nor explicit aggregate delivery may write to All. Unavailable routing authority retains input without an All notice or silent settlement. Classic DM and explicit channel/business destinations remain separate; historical All output is not auto-cleaned.
|
|
105
107
|
- Complete assistant/guest model answers use Telegram-native Rich Markdown. Harness-owned menus, status, diagnostics, thinking, and tool evidence remain explicit HTML/plain or their documented native surface. Before Telegram preview or final delivery, strip every assistant-authored HTML comment regardless of Markdown position while keeping action activation top-level-only; a comment-only result sends no text message. Preserve literal code outside comments and structurally safe chunking; never split invalid markup.
|
|
106
|
-
- `preview` owns streaming lifecycle only, not assistant rendering. Finalization waits for
|
|
108
|
+
- `preview` owns streaming lifecycle only, not assistant rendering. Finalization waits for the captured generation's preceding preview work, never later drafts blocked by that publication, and must not issue pre/post-final draft-clear calls that create transient Telegram draft UI. Reset/invalidation detaches predecessor dependencies; late settlements cannot mutate replacement state. Turns that already answer as one atomic reply (voice replies, Guest Mode queries) never stream previews.
|
|
107
109
|
- Native `sendChatAction(typing)` is the automatic activity signal for unsettled agent and compaction work while Telegram transport is authorized. Extension-owned blocking UI prompts pause it and completion resumes it while either work owner remains active. Compaction may stop only a typing loop it actually started; it must preserve a pre-existing agent-owned loop. Refresh only the assigned Thread on a conservative three-second cadence; do not mirror typing to aggregate `All` and multiply shared-chat flood pressure. Typing is best-effort presence only: a failed exact-target action remains a structured diagnostic and must not replace a healthy connected/leader/follower status with `error`. Do not invent extra in-chat work indicators or emit activity for startup/connect/reload/recovery alone.
|
|
108
110
|
- Public activity handlers and connected companion delivery are asynchronous, target-bound, generation-fenced surfaces. Connected companion projection has no independent opt-out: disconnect or authority loss is its boundary. Token deltas, hidden reasoning, unknown sources, and stale authority never enter public projection.
|
|
109
111
|
- Thread display defaults to the profile-scoped Letters strategy, with Names, Directory Snake, and Directory Title as the other automatic choices; Names projects the generated dictionary name for the slot. Unsupported retained display keys resolve to Letters without rewriting persisted configuration. A durable manual Thread display name retained on its Workspace binding overrides any automatic projection until exact reset; keep generated/recovery identity separate from manual and acknowledged display fields. After leader startup, automatic display contraction waits one follower-staleness window so election and follower re-registration cannot briefly remove and restore an acknowledged same-cwd suffix; stop/start generation cancels stale reconciliation. UI labels, emoji semantics, navigation, settings controls, callback namespaces, voice behavior, command templates, and assistant markup follow the linked `/docs` contracts. Generated human-readable action labels use `emoji + space + text`; emoji-free action text is only a reasoned no-semantic-marker fallback. State/value controls follow the [button control hierarchy](./docs/ui-style.md#button-control-hierarchy), not the action-label grammar. Non-spatial generated controls default to top-level vertical cells, with nested rows reserved for unmistakably compact peers. Do not restate other evolving UI details here.
|
package/BACKLOG.md
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.55.0: Native Restore and fenced lifecycles
|
|
8
|
+
|
|
9
|
+
- `Restore cleanup`: Live Restore binds the new tab and decides old-tab cleanup before native leader/follower inputs run. Protected work defers cleanup; session replacement carries no deletion grant, and unknown deletion never repeats. Follower execution targets the rebound tab without rewriting journal originals or quoting the former tab. Exact donor ACKs seal routing clocks and retire the menu. Reader/execution capabilities, source custody and detached replies stay separate; busy hints coalesce.
|
|
10
|
+
- `Runtime state`: In-memory lifecycles now use isolated pure TypeScript transitions and domain-owned effect shells. A shared leaf runner drives preview, typing, model switching, grouped input, compaction and follower recovery without adding a runtime dependency; durable admission, custody, Workspace and IPC retain their existing transactional owners.
|
|
11
|
+
- `Preview ordering`: A publication waits only for its preceding draft and publications, never a later draft that waits for it. New turns and sessions detach old preview dependencies, and late draft, timer or publication settlements cannot unblock or overwrite replacement work. Final replies still drain their own issued draft without post-final clearing.
|
|
12
|
+
- `Model switching`: Restarting or clearing a switch now consumes its staged model and continuation together, preventing an old tool settlement from aborting the continuation again. Synchronous nested controller calls retain the outer continuation context and abort handler.
|
|
13
|
+
- `Grouped input`: Albums and split text share one fenced pending-group lifecycle. Separate text batches end at a message identity rather than a mutable count, and a synchronous dispatcher failure retains the input and schedules retry instead of leaving the group permanently dispatching.
|
|
14
|
+
- `Typing ownership`: Queue dispatch preserves its exact Thread; Threaded Mode refuses missing targets and stops Classic chat-only loops on mode changes. Context/generation and loop-owner fences guard starts, queued HTTP/IPC issuance, ticks and awaited lifecycle continuations without letting stale work claim replacement typing. Typing never replays HTTP or transport fallback after failure; follower callbacks stay local. Compaction retains its own loop and preserves agent-owned activity.
|
|
15
|
+
- `Reconnect lifecycle`: Follower startup restores its exact session's retained Thread after proving the former runtime PID absent and no registered peer owns it; live/unknown predecessors stay protected. Connect notices use acknowledged display titles, not provisional generated names. Manual Connect/Restore retain prepared context, read-only checks, committed slots and journal history. Stale recovery cannot change a replacement connection; stopped leader startup cannot poll or publish its endpoint.
|
|
16
|
+
- `Thread surfaces`: All is input-only: sends, drafts, media, forwards and activity require a concrete Thread; implicit fallback and aggregate output are refused. Routing failures retain input, not All notices. Voice/files/albums enter one routing tab. Exact Linux/macOS home displays as Home/home with normal suffixes; manual names and other modes stay unchanged. Restore publishes acknowledged titles to heartbeat/TUI without changing generated identity; late old-target title ACKs are ignored.
|
|
17
|
+
|
|
7
18
|
## 0.54.4: Restored tabs take their Pi's name
|
|
8
19
|
|
|
9
20
|
- `Restore title`: Restoring a Pi into a routing tab ("move Pi into this tab") now renames the tab to that Pi's Thread display title, as Workspace Restore already did; previously a live-rebind Restore left the tab named `🚦 Routing`. A failed rename is recorded and never blocks the Restore.
|
package/README.md
CHANGED
|
@@ -143,7 +143,7 @@ Enable the optional capabilities the bridge needs in the [@BotFather](https://t.
|
|
|
143
143
|
| Generative Apps | Install or explicitly replace a reviewed `.mjs` application whose generated JSON button view may mix direct `app::method` actions with ordinary model prompts. | Repeated games, controls, tutors, and adapters compile routine interaction without losing selective model interpretation, explanation, or adaptation. |
|
|
144
144
|
| Callback routing | Route known callbacks to the owner extension and unknown callbacks back into Pi. | Companion extensions can build UI without polling Telegram themselves. |
|
|
145
145
|
| Threaded Mode | Run one leader plus visible follower Pi instances through named private-chat threads. | One bot can host a local multi-instance Pi organism without hidden process spawning. |
|
|
146
|
-
| Reroute and restore | Give
|
|
146
|
+
| Reroute and restore | Give text, voice, files and albums sent from `All`, plus unknown threads, explicit reroute and replace/restore choices. | Forward removes the temporary tab; restore rebinds it and may remove only the replaced old tab after positive cleanup proofs. Blocked cleanup preserves the relocation and protected work. |
|
|
147
147
|
| Extension sections | Add menu sections, commands, status rows, settings, callbacks, and delivery helpers from companion extensions. | `pi-telegram` becomes a platform surface for other Pi extensions. |
|
|
148
148
|
| Runtime diagnostics | Use `/telegram-status` and recent runtime events for connection, role, negotiated bus protocol/build/capabilities, separate polling and inbound-worker progress, journal depth, local/foreign queue ownership, automatic retry waits, transport, and failures. | Compatible build skew, foreign semantic authority, a healthy poller, durable backoff and an infrastructure-blocked worker remain distinguishable without hidden logs. |
|
|
149
149
|
| Safety and ownership | Pair one owner, lock transport, scope targets, and reject fake terminal behavior. | Remote access remains explicit, bounded, and understandable. |
|
|
@@ -196,6 +196,8 @@ Named profile identifiers contain only lowercase ASCII letters and digits (maxim
|
|
|
196
196
|
|
|
197
197
|
## Main Surfaces
|
|
198
198
|
|
|
199
|
+
In Threaded Mode, `All` is input-only: messages enter temporary routing tabs, and Pi replies stay in bound Threads. Missing routing or delivery targets fail closed instead of sending fallback messages to All. There is no automatic cleanup of old All output. Classic DM behavior is unchanged.
|
|
200
|
+
|
|
199
201
|
### Operator Menu
|
|
200
202
|
|
|
201
203
|
`/start` opens the Telegram-native control panel: status, prompt-template commands, model selection, thinking level, settings, queue controls, and extension sections. It is the primary Telegram UI; reaction shortcuts are secondary queue affordances.
|
|
@@ -248,14 +250,14 @@ Classic private DM mode is the base product mode. When Telegram private-chat Thr
|
|
|
248
250
|
- When all `A`–`Z` slots are reserved, a new connection may reclaim the oldest inactive, unprotected Thread. **This deletes that Thread and all its messages**, not Pi session history or project files. Active or protected work blocks reclamation; restoring a remembered session does not evict another binding.
|
|
249
251
|
- Telegram never launches hidden Pi processes.
|
|
250
252
|
|
|
251
|
-
In Threaded Mode, open Settings → **🧵 Thread display** to choose `letters` (default), `names`, `directory-title`, or `directory-snake` for this bot profile
|
|
253
|
+
In Threaded Mode, open Settings → **🧵 Thread display** to choose `letters` (default), `names`, `directory-title`, or `directory-snake` for this bot profile. Unsupported retained values, including the retired `directories` mode, fall back to `letters` without rewriting the saved configuration. Fresh tabs are created with the active mode's title instead of being visibly renamed afterward. Telegram tab titles, Pi terminal status, live Thread choosers/notices, prompt attribution, and named `telegram_message` targeting use the same acknowledged display name; target IDs and live registrations still own routing. Names shows the generated dictionary name chosen for the slot, such as `Anchor` for slot `A`; the directory formats produce `api_tools` or `Api Tools` and append `_a` or ` A` only while two or more authenticated live instances share that exact directory. On Linux/macOS, the current user's exact home directory displays as `home` or `Home`; subdirectories keep their own names, and Windows remains unchanged. Dormant retained bindings do not keep those suffixes visible. After leader election, automatic title contraction waits one follower-staleness window for live registrations to settle, avoiding a temporary suffix removal and restoration during startup. `/name` sets a manual Thread display name; **Reset to automatic** restores the selected automatic projection. Switching preserves Thread IDs, slots, generated recovery identity, and queue ownership. Partial application reports an error and can be retried without recreating Threads.
|
|
252
254
|
|
|
253
255
|
| Mode | Best for | Runtime shape |
|
|
254
256
|
| --- | --- | --- |
|
|
255
257
|
| Classic DM | One running Pi instance and its active session controlled from one private bot chat | One polling owner, one queue/runtime surface |
|
|
256
258
|
| Threaded Mode | Several visible Pi sessions sharing one bot | One leader owns transport; each private-chat Thread retains one session-qualified Workspace binding |
|
|
257
259
|
|
|
258
|
-
**Live rebinding
|
|
260
|
+
**Live rebinding:** Restore confirms the new binding, makes one old-tab cleanup decision, then releases the saved input through its native prompt, command or template owner. Negotiated followers use the same order without another queue. Earlier protected work defers cleanup; `/new` cannot carry an old-session deletion grant into its replacement. An unknown delete never repeats or blocks ordinary input on the confirmed new tab. Exact source/receipt custody remains separate from replies and model completion. Upgrade participating readers for the new metadata; older peers retain their supported legacy path. See [Multi-Instance Bus](./docs/multi-instance-bus.md#live-rebind-channel).
|
|
259
261
|
|
|
260
262
|
## Environment Configuration
|
|
261
263
|
|
package/dist/lib/activity.d.ts
CHANGED
|
@@ -101,11 +101,13 @@ export interface TelegramActivityDispatcher {
|
|
|
101
101
|
}
|
|
102
102
|
/** @internal */
|
|
103
103
|
export declare function createTelegramActivityDispatcher(deps?: {
|
|
104
|
+
captureDrain?: () => (() => void) | undefined;
|
|
104
105
|
recordFailure?: (handlerId: string, event: TelegramActivityEvent, error: unknown) => void;
|
|
105
106
|
}): TelegramActivityDispatcher;
|
|
106
107
|
/** @internal */
|
|
107
108
|
export declare function createTelegramActivityBridgeRuntime(deps: {
|
|
108
109
|
generation: string;
|
|
110
|
+
captureDrain?: () => (() => void) | undefined;
|
|
109
111
|
observeEvent?: (event: TelegramActivityEvent) => void;
|
|
110
112
|
recordFailure?: (handlerId: string, event: TelegramActivityEvent, error: unknown) => void;
|
|
111
113
|
now?: () => number;
|
|
@@ -207,7 +209,9 @@ export interface TelegramActivityPublicationRuntime {
|
|
|
207
209
|
hasUnconfirmed: () => boolean;
|
|
208
210
|
reset: () => void;
|
|
209
211
|
}
|
|
210
|
-
export declare function createTelegramActivityPublicationRuntime(
|
|
212
|
+
export declare function createTelegramActivityPublicationRuntime(deps?: {
|
|
213
|
+
captureDrain?: () => (() => void) | undefined;
|
|
214
|
+
}): TelegramActivityPublicationRuntime;
|
|
211
215
|
export interface TelegramAssistantOutputRuntime {
|
|
212
216
|
start: () => void;
|
|
213
217
|
beginTurn: () => void;
|
package/dist/lib/activity.js
CHANGED
|
@@ -116,10 +116,28 @@ function coalesceActivityEvents(previous, next) {
|
|
|
116
116
|
}
|
|
117
117
|
return next;
|
|
118
118
|
}
|
|
119
|
+
/** Optional observation must not change queue/transport settlement. */
|
|
120
|
+
function captureActivityDrainObserver(capture) {
|
|
121
|
+
try {
|
|
122
|
+
const notify = capture?.();
|
|
123
|
+
return () => {
|
|
124
|
+
try {
|
|
125
|
+
const emitted = notify?.();
|
|
126
|
+
if (emitted)
|
|
127
|
+
void Promise.resolve(emitted).catch(() => undefined);
|
|
128
|
+
}
|
|
129
|
+
catch { /* Observer failure is inert. */ }
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return () => { };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
119
136
|
/** @internal */
|
|
120
137
|
export function createTelegramActivityDispatcher(deps = {}) {
|
|
121
138
|
const queues = new Map();
|
|
122
139
|
let stopped = false;
|
|
140
|
+
const hasPending = () => Array.from(queues.values()).some(queue => queue.active && (queue.running || queue.events.length > 0));
|
|
123
141
|
const drain = async (queue) => {
|
|
124
142
|
if (queue.running || !queue.active)
|
|
125
143
|
return;
|
|
@@ -146,10 +164,12 @@ export function createTelegramActivityDispatcher(deps = {}) {
|
|
|
146
164
|
}
|
|
147
165
|
finally {
|
|
148
166
|
queue.running = false;
|
|
167
|
+
if (!stopped && !hasPending())
|
|
168
|
+
queue.notifyDrained?.();
|
|
149
169
|
}
|
|
150
170
|
};
|
|
151
171
|
return {
|
|
152
|
-
hasPending
|
|
172
|
+
hasPending,
|
|
153
173
|
dispatch(event) {
|
|
154
174
|
if (stopped)
|
|
155
175
|
return;
|
|
@@ -164,6 +184,8 @@ export function createTelegramActivityDispatcher(deps = {}) {
|
|
|
164
184
|
};
|
|
165
185
|
queues.set(registration.id, queue);
|
|
166
186
|
}
|
|
187
|
+
if (!queue.running && queue.events.length === 0)
|
|
188
|
+
queue.notifyDrained = captureActivityDrainObserver(deps.captureDrain);
|
|
167
189
|
const previous = queue.events.at(-1);
|
|
168
190
|
if (previous && canCoalesceActivityEvents(previous, event)) {
|
|
169
191
|
queue.events[queue.events.length - 1] = coalesceActivityEvents(previous, event);
|
|
@@ -198,6 +220,7 @@ export function createTelegramActivityBridgeRuntime(deps) {
|
|
|
198
220
|
runtime = createTelegramActivityRuntime({
|
|
199
221
|
generation: `${deps.generation}:${++generationSequence}`,
|
|
200
222
|
dispatcher: createTelegramActivityDispatcher({
|
|
223
|
+
captureDrain: deps.captureDrain,
|
|
201
224
|
recordFailure: deps.recordFailure,
|
|
202
225
|
}),
|
|
203
226
|
observeEvent: deps.observeEvent,
|
|
@@ -470,13 +493,14 @@ export function createTelegramActivityRuntime(deps) {
|
|
|
470
493
|
},
|
|
471
494
|
};
|
|
472
495
|
}
|
|
473
|
-
export function createTelegramActivityPublicationRuntime() {
|
|
496
|
+
export function createTelegramActivityPublicationRuntime(deps = {}) {
|
|
474
497
|
let generation = 0;
|
|
475
498
|
let tail = Promise.resolve();
|
|
476
499
|
let outstanding = 0, unconfirmed = false;
|
|
477
500
|
const pending = new Set();
|
|
478
501
|
const reserve = () => {
|
|
479
502
|
const admittedGeneration = generation;
|
|
503
|
+
const notifyDrained = captureActivityDrainObserver(deps.captureDrain);
|
|
480
504
|
let state = "pending";
|
|
481
505
|
let resolve;
|
|
482
506
|
const ready = new Promise((accept) => {
|
|
@@ -499,6 +523,8 @@ export function createTelegramActivityPublicationRuntime() {
|
|
|
499
523
|
})
|
|
500
524
|
.finally(() => {
|
|
501
525
|
outstanding -= 1;
|
|
526
|
+
if (admittedGeneration === generation && outstanding === 0 && !unconfirmed)
|
|
527
|
+
notifyDrained();
|
|
502
528
|
});
|
|
503
529
|
tail = result.catch(() => { });
|
|
504
530
|
return {
|
|
@@ -522,6 +548,7 @@ export function createTelegramActivityPublicationRuntime() {
|
|
|
522
548
|
hasUnconfirmed: () => unconfirmed,
|
|
523
549
|
beginWork() {
|
|
524
550
|
const admittedGeneration = generation;
|
|
551
|
+
const notifyDrained = captureActivityDrainObserver(deps.captureDrain);
|
|
525
552
|
let settled = false;
|
|
526
553
|
outstanding += 1;
|
|
527
554
|
return {
|
|
@@ -532,6 +559,8 @@ export function createTelegramActivityPublicationRuntime() {
|
|
|
532
559
|
outstanding -= 1;
|
|
533
560
|
if (outcome === "unconfirmed" && admittedGeneration === generation)
|
|
534
561
|
unconfirmed = true;
|
|
562
|
+
if (admittedGeneration === generation && outstanding === 0 && !unconfirmed)
|
|
563
|
+
notifyDrained();
|
|
535
564
|
},
|
|
536
565
|
};
|
|
537
566
|
},
|
package/dist/lib/bindings.d.ts
CHANGED
|
@@ -177,6 +177,7 @@ export interface TelegramActivityBindingRuntime {
|
|
|
177
177
|
/** Compose public activity fanout, verbosity, and assistant output ordering. */
|
|
178
178
|
export declare function createTelegramActivityBindingRuntime<TTransportStamp>(deps: {
|
|
179
179
|
generation: string;
|
|
180
|
+
captureDrain?: () => (() => void) | undefined;
|
|
180
181
|
assistantOutput: Omit<Parameters<typeof createTelegramAssistantOutputBindingRuntime<TTransportStamp>>[0], "waitForActivityIdle" | "enqueue">;
|
|
181
182
|
activityVerbosity: Omit<Parameters<typeof ActivityVerbosity.createTelegramActivityVerbosityRuntime<TelegramAssistantOutputAuthority<TTransportStamp>>>[0], "captureAuthority" | "isAuthorityActive" | "recordFailure" | "enqueue">;
|
|
182
183
|
}): TelegramActivityBindingRuntime;
|
|
@@ -288,11 +289,12 @@ interface TelegramLifecycleBindingDeps {
|
|
|
288
289
|
}) => void;
|
|
289
290
|
canSendAgentActivity: (ctx: Pi.ExtensionContext) => boolean;
|
|
290
291
|
isSessionContextActive: (ctx: Pi.ExtensionContext) => boolean;
|
|
292
|
+
getSessionGeneration?: () => number;
|
|
291
293
|
isTurnTransportActive?: (turn: Queue.PendingTelegramTurn) => boolean;
|
|
292
294
|
updateStatus: TelegramBridgeStatusUpdater;
|
|
293
295
|
recordRuntimeEvent: TelegramRuntimeEventRecorder;
|
|
294
296
|
/** Settled-session hint for owners that wait on agent idleness, such as old-tab cleanup after a live rebind. */
|
|
295
297
|
onSessionSettled?: (ctx: Pi.ExtensionContext) => void;
|
|
296
298
|
}
|
|
297
|
-
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, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive, isTurnTransportActive, updateStatus, recordRuntimeEvent, onSessionSettled, }: TelegramLifecycleBindingDeps): void;
|
|
299
|
+
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, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive, getSessionGeneration, isTurnTransportActive, updateStatus, recordRuntimeEvent, onSessionSettled, }: TelegramLifecycleBindingDeps): void;
|
|
298
300
|
export {};
|
package/dist/lib/bindings.js
CHANGED
|
@@ -354,7 +354,7 @@ export function createTelegramAssistantOutputBindingRuntime(deps) {
|
|
|
354
354
|
}
|
|
355
355
|
/** Compose public activity fanout, verbosity, and assistant output ordering. */
|
|
356
356
|
export function createTelegramActivityBindingRuntime(deps) {
|
|
357
|
-
const publication = Activity.createTelegramActivityPublicationRuntime();
|
|
357
|
+
const publication = Activity.createTelegramActivityPublicationRuntime({ captureDrain: deps.captureDrain });
|
|
358
358
|
const assistantOutputBinding = createTelegramAssistantOutputBindingRuntime({
|
|
359
359
|
...deps.assistantOutput,
|
|
360
360
|
enqueue: publication.enqueue,
|
|
@@ -374,6 +374,7 @@ export function createTelegramActivityBindingRuntime(deps) {
|
|
|
374
374
|
});
|
|
375
375
|
const activityRuntime = Activity.createTelegramActivityBridgeRuntime({
|
|
376
376
|
generation: deps.generation,
|
|
377
|
+
captureDrain: deps.captureDrain,
|
|
377
378
|
observeEvent(event) {
|
|
378
379
|
assistantOutputBinding.observeEvent(event);
|
|
379
380
|
activityVerbosityRuntime.accept(event);
|
|
@@ -626,7 +627,7 @@ export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, pe
|
|
|
626
627
|
},
|
|
627
628
|
});
|
|
628
629
|
}
|
|
629
|
-
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, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive = () => true, isTurnTransportActive, updateStatus, recordRuntimeEvent, onSessionSettled, }) {
|
|
630
|
+
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, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive = () => true, getSessionGeneration, isTurnTransportActive, updateStatus, recordRuntimeEvent, onSessionSettled, }) {
|
|
630
631
|
const agentEndResetter = Runtime.createTelegramAgentEndResetter({
|
|
631
632
|
abort,
|
|
632
633
|
typing,
|
|
@@ -864,6 +865,8 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
864
865
|
});
|
|
865
866
|
const messageActivityTypingHooks = Lifecycle.createTelegramMessageActivityTypingHooks({
|
|
866
867
|
hasActiveTurn: activeTurnRuntime.has,
|
|
868
|
+
isContextActive: isSessionContextActive,
|
|
869
|
+
getSessionGeneration,
|
|
867
870
|
startTypingLoop: startActiveTurnTypingLoop,
|
|
868
871
|
onMessageStart: previewRuntime.onMessageStart,
|
|
869
872
|
onMessageUpdate: previewRuntime.onMessageUpdate,
|
|
@@ -955,9 +958,12 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
955
958
|
async onAgentStart(event, ctx) {
|
|
956
959
|
if (!isSessionContextActive(ctx))
|
|
957
960
|
return;
|
|
961
|
+
const generation = getSessionGeneration?.();
|
|
958
962
|
agentWorkActive = true;
|
|
959
963
|
cancelPendingFinalPublication();
|
|
960
964
|
await agentStartWithDedupReset(event, ctx);
|
|
965
|
+
if (!isSessionContextActive(ctx) || (getSessionGeneration && !Object.is(getSessionGeneration(), generation)))
|
|
966
|
+
return;
|
|
961
967
|
const turn = activeTurnRuntime.get();
|
|
962
968
|
activityRuntime.onAgentStart(turn?.target, turn?.replyToMessageId);
|
|
963
969
|
startAgentActivityTypingLoop(ctx);
|
package/dist/lib/bus-api.d.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Wraps the direct Telegram Bot API runtime so follower instances can route outbound calls through the bus leader
|
|
5
5
|
*/
|
|
6
6
|
import { type TelegramBusSelectedMenuDeliveryObservation, type TelegramBusSelectedMenuTextEffect } from "./bus.ts";
|
|
7
|
-
import type { TelegramBridgeApiRuntime } from "./telegram-api.ts";
|
|
8
|
-
export type TelegramBusApiCall = (method: string, args: unknown[]) => Promise<unknown>;
|
|
7
|
+
import type { TelegramApiCallOptions, TelegramBridgeApiRuntime } from "./telegram-api.ts";
|
|
8
|
+
export type TelegramBusApiCall = (method: string, args: unknown[], localOptions?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<unknown>;
|
|
9
9
|
export interface TelegramBusAwareApiRuntimeDeps {
|
|
10
10
|
directRuntime: TelegramBridgeApiRuntime;
|
|
11
11
|
ownsDirect: () => boolean;
|
package/dist/lib/bus-api.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Wraps the direct Telegram Bot API runtime so follower instances can route outbound calls through the bus leader
|
|
5
5
|
*/
|
|
6
6
|
import { markTelegramBusCrossTargetDelivery, parseTelegramBusSelectedMenuTextEffect, stripTelegramBusApiMetadata, } from "./bus.js";
|
|
7
|
-
import { buildTelegramAnswerGuestQueryBody, isTelegramApiCommitUnknownError, isTelegramMessageNotModifiedError, TelegramApiAuthorityError, TelegramApiCommitUnknownError, } from "./telegram-api.js";
|
|
7
|
+
import { buildTelegramAnswerGuestQueryBody, assertTelegramApiCallAuthority, isTelegramApiCommitUnknownError, isTelegramMessageNotModifiedError, TelegramApiAuthorityError, TelegramApiCommitUnknownError, } from "./telegram-api.js";
|
|
8
8
|
function asRecord(value) {
|
|
9
9
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
10
10
|
? value
|
|
@@ -142,6 +142,7 @@ export function createTelegramSelectedMenuTextApi(deps) {
|
|
|
142
142
|
}
|
|
143
143
|
/** Followers route every chat action through the leader's generic `sendChatAction` call. */
|
|
144
144
|
function sendTelegramFollowerChatAction(deps, chatId, action, options) {
|
|
145
|
+
assertTelegramApiCallAuthority(options?.assertAuthority, false);
|
|
145
146
|
const body = withDefaultThreadTarget({
|
|
146
147
|
chat_id: chatId,
|
|
147
148
|
action,
|
|
@@ -149,7 +150,11 @@ function sendTelegramFollowerChatAction(deps, chatId, action, options) {
|
|
|
149
150
|
? { message_thread_id: options.message_thread_id }
|
|
150
151
|
: {}),
|
|
151
152
|
}, deps.getDefaultTarget?.());
|
|
152
|
-
|
|
153
|
+
// Only plain single-attempt policy crosses IPC; the captured assertion is a local issuance fence.
|
|
154
|
+
const args = ["sendChatAction", body, ...(action === "typing"
|
|
155
|
+
? [{ maxAttempts: 1, retrySafety: "non-idempotent" }] : [])];
|
|
156
|
+
return deps.callFollowerApi("call", args, options?.assertAuthority
|
|
157
|
+
? { assertAuthority: options.assertAuthority } : undefined).then(asBoolean);
|
|
153
158
|
}
|
|
154
159
|
export function createTelegramBusAwareApiRuntime(deps) {
|
|
155
160
|
return {
|
|
@@ -12,8 +12,10 @@ import { TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY, TELEGRAM_BUS_CAPABILITY_SELE
|
|
|
12
12
|
import type { TelegramConfigStore, TelegramThreadDisplayMode } from "./config.ts";
|
|
13
13
|
import { type TelegramUpdateJournalStoreOptions } from "./journal.ts";
|
|
14
14
|
import { type TelegramLockEntry, type TelegramLockState } from "./locks.ts";
|
|
15
|
+
import * as Machine from "./machine.ts";
|
|
15
16
|
import type { TelegramQueueHandoffStageResult } from "./queue.ts";
|
|
16
17
|
import { type TelegramTarget } from "./target.ts";
|
|
18
|
+
import { type TelegramApiCallOptions } from "./telegram-api.ts";
|
|
17
19
|
import * as Threads from "./threads.ts";
|
|
18
20
|
import { type TelegramWorkspaceAdmissionLedger } from "./workspace-admission.ts";
|
|
19
21
|
export declare const TELEGRAM_BUS_FOLLOWER_HEARTBEAT_TIMEOUT_MS = 8000;
|
|
@@ -343,7 +345,9 @@ export declare function createTelegramBusFollowerWorkspaceRestoreHandler<TContex
|
|
|
343
345
|
expectedTarget?: TelegramTarget & {
|
|
344
346
|
threadId: number;
|
|
345
347
|
};
|
|
346
|
-
release?: (canRelease: () => boolean
|
|
348
|
+
release?: (canRelease: () => boolean, target: TelegramTarget & {
|
|
349
|
+
threadId: number;
|
|
350
|
+
}) => void;
|
|
347
351
|
observeWork?: (oldTarget: TelegramTarget & {
|
|
348
352
|
threadId: number;
|
|
349
353
|
}) => void;
|
|
@@ -362,7 +366,7 @@ export declare function createTelegramBusFollowerWorkspaceRestoreHandler<TContex
|
|
|
362
366
|
};
|
|
363
367
|
export declare function createTelegramBusFollowerClientRuntime<TContext, TReactionUpdate, TCallbackQuery, TMessage = unknown>(deps: TelegramBusFollowerClientRuntimeDeps<TMessage>): {
|
|
364
368
|
createRequestId: () => string;
|
|
365
|
-
callApi: (method: string, args: unknown[]) => Promise<unknown>;
|
|
369
|
+
callApi: (method: string, args: unknown[], localOptions?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<unknown>;
|
|
366
370
|
agentMessages: {
|
|
367
371
|
resolveTarget: (selector: TelegramBusAgentTargetSelector) => Promise<TelegramTarget & {
|
|
368
372
|
threadId: number;
|
|
@@ -414,7 +418,7 @@ export declare function createTelegramBusFollowerSelectedMenuCaller<TContext>(de
|
|
|
414
418
|
effect: TelegramBusSelectedMenuTextEffect;
|
|
415
419
|
assertAuthority: () => void;
|
|
416
420
|
}) => Promise<TelegramBusSelectedMenuDeliveryObservation>;
|
|
417
|
-
export declare function createTelegramBusFollowerApiCaller(deps: TelegramBusFollowerApiCallerDeps): (method: string, args: unknown[]) => Promise<unknown>;
|
|
421
|
+
export declare function createTelegramBusFollowerApiCaller(deps: TelegramBusFollowerApiCallerDeps): (method: string, args: unknown[], localOptions?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<unknown>;
|
|
418
422
|
export declare function createTelegramBusFollowerSessionReplacementSuspender(deps: TelegramBusFollowerSessionReplacementSuspenderDeps): (preserveTarget?: boolean) => Promise<void>;
|
|
419
423
|
export declare function createTelegramBusFollowerSessionRefreshHook<TContext>(deps: TelegramBusFollowerSessionRefreshHookDeps<TContext>): (_event: unknown, ctx: TContext) => Promise<void>;
|
|
420
424
|
export declare function createTelegramBusFollowerControlState(): TelegramBusFollowerControlState;
|
|
@@ -426,6 +430,48 @@ export type TelegramBusFollowerHeartbeatRecoveryHandler<TContext> = ((error: unk
|
|
|
426
430
|
halt: () => void;
|
|
427
431
|
resume: () => void;
|
|
428
432
|
};
|
|
433
|
+
/** The whole recovery lifecycle state; only `transitionTelegramBusFollowerRecovery` produces a new one. */
|
|
434
|
+
export interface TelegramBusFollowerRecoveryState {
|
|
435
|
+
/** Bumped by halt and resume, so a recovery or retry from before an operator stop or connect is inert. */
|
|
436
|
+
readonly epoch: number;
|
|
437
|
+
readonly halted: boolean;
|
|
438
|
+
/** One recovery runs at a time; it belongs to the epoch it started in. */
|
|
439
|
+
readonly recovering: boolean;
|
|
440
|
+
readonly retryId: number | null;
|
|
441
|
+
readonly nextRetryId: number;
|
|
442
|
+
}
|
|
443
|
+
export type TelegramBusFollowerRecoveryEvent = {
|
|
444
|
+
type: "recoverRequested";
|
|
445
|
+
} | {
|
|
446
|
+
type: "recoverySettled";
|
|
447
|
+
epoch: number;
|
|
448
|
+
} | {
|
|
449
|
+
type: "retryRequested";
|
|
450
|
+
epoch: number;
|
|
451
|
+
} | {
|
|
452
|
+
type: "retryFired";
|
|
453
|
+
retryId: number;
|
|
454
|
+
} | {
|
|
455
|
+
type: "halted";
|
|
456
|
+
} | {
|
|
457
|
+
type: "resumed";
|
|
458
|
+
};
|
|
459
|
+
export type TelegramBusFollowerRecoveryEffect = {
|
|
460
|
+
type: "runRecovery";
|
|
461
|
+
epoch: number;
|
|
462
|
+
} | {
|
|
463
|
+
type: "armRetry";
|
|
464
|
+
retryId: number;
|
|
465
|
+
} | {
|
|
466
|
+
type: "cancelRetry";
|
|
467
|
+
retryId: number;
|
|
468
|
+
};
|
|
469
|
+
export declare function createTelegramBusFollowerRecoveryState(): TelegramBusFollowerRecoveryState;
|
|
470
|
+
/**
|
|
471
|
+
* Pure follower-recovery lifecycle. An operator stop halts it and a connect resumes it, each in a new epoch: work
|
|
472
|
+
* from the old epoch, a recovery still awaiting or a retry still armed, can no longer register or promote.
|
|
473
|
+
*/
|
|
474
|
+
export declare function transitionTelegramBusFollowerRecovery(state: TelegramBusFollowerRecoveryState, event: TelegramBusFollowerRecoveryEvent): Machine.TelegramMachineStep<TelegramBusFollowerRecoveryState, TelegramBusFollowerRecoveryEffect>;
|
|
429
475
|
export declare function createTelegramBusFollowerHeartbeatRecoveryHandler<TContext>(deps: TelegramBusFollowerHeartbeatRecoveryHandlerDeps<TContext>): TelegramBusFollowerHeartbeatRecoveryHandler<TContext>;
|
|
430
476
|
export declare function createTelegramBusFollowerRegistrationRuntime<TContext extends {
|
|
431
477
|
cwd?: string;
|