@llblab/pi-telegram 0.55.1 → 0.56.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/AGENTS.md +5 -4
- package/BACKLOG.md +6 -1
- package/CHANGELOG.md +15 -0
- package/README.md +28 -17
- package/dist/lib/bindings.d.ts +8 -4
- package/dist/lib/bindings.js +41 -1
- package/dist/lib/bus-follower.d.ts +1 -0
- package/dist/lib/bus-follower.js +10 -5
- package/dist/lib/bus-leader.d.ts +2 -0
- package/dist/lib/bus-leader.js +34 -20
- package/dist/lib/bus.d.ts +8 -0
- package/dist/lib/bus.js +21 -7
- package/dist/lib/commands.d.ts +1 -1
- package/dist/lib/commands.js +33 -28
- package/dist/lib/extension.js +18 -3
- package/dist/lib/lifecycle.d.ts +2 -1
- package/dist/lib/lifecycle.js +28 -20
- package/dist/lib/locks.d.ts +6 -2
- package/dist/lib/locks.js +3 -3
- package/dist/lib/menu-queue.d.ts +1 -0
- package/dist/lib/menu-queue.js +102 -99
- package/dist/lib/pi.d.ts +3 -2
- package/dist/lib/pi.js +3 -0
- package/dist/lib/polling.d.ts +6 -2
- package/dist/lib/polling.js +5 -3
- package/dist/lib/queue.d.ts +57 -5
- package/dist/lib/queue.js +313 -25
- package/dist/lib/routing.d.ts +4 -0
- package/dist/lib/routing.js +62 -1
- package/dist/lib/status.d.ts +1 -2
- package/dist/lib/status.js +6 -7
- package/dist/lib/sync.js +11 -1
- package/dist/lib/threads.d.ts +11 -0
- package/dist/lib/threads.js +57 -6
- package/dist/lib/updates.d.ts +12 -7
- package/dist/lib/updates.js +44 -9
- package/dist/package.json +1 -1
- package/docs/README.md +1 -1
- package/docs/architecture.md +24 -13
- package/docs/callback-namespaces.md +4 -0
- package/docs/multi-instance-bus.md +6 -4
- package/docs/ui-style.md +14 -10
- package/lib/bindings.ts +52 -3
- package/lib/bus-follower.ts +13 -5
- package/lib/bus-leader.ts +37 -23
- package/lib/bus.ts +23 -7
- package/lib/commands.ts +52 -47
- package/lib/extension.ts +18 -2
- package/lib/lifecycle.ts +34 -28
- package/lib/locks.ts +5 -2
- package/lib/menu-queue.ts +116 -162
- package/lib/pi.ts +8 -0
- package/lib/polling.ts +7 -2
- package/lib/queue.ts +328 -33
- package/lib/routing.ts +51 -1
- package/lib/status.ts +7 -7
- package/lib/sync.ts +11 -0
- package/lib/threads.ts +57 -5
- package/lib/updates.ts +44 -15
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -27,7 +27,7 @@ Canonical terms:
|
|
|
27
27
|
|
|
28
28
|
Keep each fact in one authoritative layer:
|
|
29
29
|
|
|
30
|
-
- [`README.md`](./README.md): Public product entrypoint. Preserve the flow identity → install/connect → examples → product model → compact capabilities → controls/safety → docs. Balance strong positioning with a practical catalogue of capabilities, operator gestures and meaningful consequences; keep detailed routing proofs, recovery algorithms and action-markup grammar in indexed `/docs` instead of accumulating release-by-release paragraphs.
|
|
30
|
+
- [`README.md`](./README.md): Public product entrypoint. Preserve the flow identity → install/connect → examples → product model → compact capabilities → controls/safety → docs. Describe current behavior, not version-by-version changes or release progress; compatibility requirements belong here only when needed for installation or safe operation, while historical version details belong in CHANGELOG or indexed technical docs. Balance strong positioning with a practical catalogue of capabilities, operator gestures and meaningful consequences; keep detailed routing proofs, recovery algorithms and action-markup grammar in indexed `/docs` instead of accumulating release-by-release paragraphs.
|
|
31
31
|
- `AGENTS.md`: Stable engineering boundaries, recurring runtime invariants, and work protocol. Link to evolving subsystem contracts instead of copying them here.
|
|
32
32
|
- [`BACKLOG.md`](./BACKLOG.md): Canonical unresolved work. Keep only open top-level outcomes with nested decomposition and done criteria. Remove completed outcomes rather than retaining checked history.
|
|
33
33
|
- [`CHANGELOG.md`](./CHANGELOG.md): Completed user/operator/developer impact. A release has at most eight outcome bullets of at most 512 characters, each beginning with an inline-code domain label and colon. Exclude personal names and real user/chat/message/thread identifiers. Consolidate the current pre-release section before release; do not rewrite historical sections without an explicit retrospective request and evidence pass.
|
|
@@ -74,7 +74,7 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
74
74
|
- The bridge is session-local and paired to one allowed Telegram user. Preserve `{ chatId, threadId? }` through every inbound, queue, callback, reaction, media, preview, reply, menu, voice, attachment, and direct-delivery path.
|
|
75
75
|
- First-contact pairing grants in-memory authority only after profile/token/execution-fenced durable publication confirms that exact user; it never overwrites another configured owner. `profiles.<name>.botToken` may store an exact `$NAME`/`${NAME}` environment reference instead of a copied secret: resolve it only at validation or activation boundaries, fail closed with a redacted named-variable diagnostic when unresolved, and keep literal tokens compatible. Sender admission precedes user message/edit/callback/reaction delegation, including foreign ownership and unbound-Thread fallback paths. Reactions require an existing exact human owner; private chat type is not authorization. Queued config persistence must not replay observed authority as local grant edits or erase later local unpair. Setup and retry details belong in [`docs/architecture.md`](./docs/architecture.md#setup-flow).
|
|
76
76
|
- Telegram transport ownership is not semantic queue ownership. Losing the exact transport lock must not erase accepted local queue work or stop valid local Pi dispatch; direct Bot API mutations fail closed until exact direct or follower authority exists.
|
|
77
|
-
- Runtime files live under `<agent-dir>/tmp/pi-telegram` (releases before 0.52.0 used `tmp/telegram`, which a new release never writes, migrates or deletes). The older `owners.json` is read-only evidence: a live fresh owner there blocks acquisition so two release generations never poll one bot, and only its identity is exposed, never its bus endpoint or secret. Only `state.json` and `logs.jsonl` are persistent root files: the per-profile `transport` section of `state.json` is the sole transport-owner authority, `workspace` owns canonical Workspace/Restore evidence and `admission` its leases; the `runtime` section and `logs.jsonl` never grant routing authority. Each section changes only through the shared transaction under its own domain authority (nonleader admission remains process/birth-owned, not transport-owned); Workspace/runtime publication captures exact session authority, and acquisition, refresh, release, takeover, and irreversible leader work fence the exact owner/epoch. Ordinary reads and section mutations still refuse a damaged shared envelope without repair. Only a non-election leader start (`/telegram-connect` or session auto-start) runs the operator-approved optimistic reset: under the shared transaction guard it replaces damaged envelope/transport/Workspace/admission evidence with an empty envelope, accepting loss of every profile's runtime continuity; filesystem access errors still refuse (see [Damaged-State Reset](./docs/architecture.md#damaged-state-reset-operator-approved)). Use the [session journal contract](./docs/multi-instance-bus.md#session-owned-journal-storage) for owners-named polling continuity and the explicitly approved optimistic session-family sweep; that sweep is not Thread deletion or receipt settlement.
|
|
77
|
+
- Runtime files live under `<agent-dir>/tmp/pi-telegram` (releases before 0.52.0 used `tmp/telegram`, which a new release never writes, migrates or deletes). The older `owners.json` is read-only evidence: a live fresh owner there blocks acquisition so two release generations never poll one bot, and only its identity is exposed, never its bus endpoint or secret. Only `state.json` and `logs.jsonl` are persistent root files: the per-profile `transport` section of `state.json` is the sole transport-owner authority, `workspace` owns canonical Workspace/Restore evidence and `admission` its leases; the `runtime` section and `logs.jsonl` never grant routing authority. Each section changes only through the shared transaction under its own domain authority (nonleader admission remains process/birth-owned, not transport-owned); Workspace/runtime publication captures exact session authority, and acquisition, refresh, release, takeover, and irreversible leader work fence the exact owner/epoch. Ordinary reads and section mutations still refuse a damaged shared envelope without repair. Explicit `/telegram-connect` additionally grants one profile-admitted reset of matching untargeted unfinished Thread-creation metadata before Workspace identity allocation; it may leave an unknown Telegram tab behind, but never deletes a known target, Workspace binding or journal receipt. Automatic startup/re-registration and Restore-only admission cannot grant that reset. Known pending targets retain their exact binding key and slot; fresh provisioning uses unique operation IDs so late recovery evidence cannot attach to a replacement attempt. Follower reset intent uses authenticated `explicit-connect-recovery-v1` registration. Only a non-election leader start (`/telegram-connect` or session auto-start) runs the operator-approved optimistic reset: under the shared transaction guard it replaces damaged envelope/transport/Workspace/admission evidence with an empty envelope, accepting loss of every profile's runtime continuity; filesystem access errors still refuse (see [Damaged-State Reset](./docs/architecture.md#damaged-state-reset-operator-approved)). Use the [session journal contract](./docs/multi-instance-bus.md#session-owned-journal-storage) for owners-named polling continuity and the explicitly approved optimistic session-family sweep; that sweep is not Thread deletion or receipt settlement.
|
|
78
78
|
- Threaded Mode has exactly one live leader per bot profile. Followers are real operator-started Pi processes and must authenticate/register over local IPC; Telegram never spawns hidden Pi processes. A live but unreachable owner does not authorize split-brain polling.
|
|
79
79
|
- Local IPC is a trust boundary, not merely a private socket. Unknown, stale, mismatched-generation, or unauthorized requests must not inject prompts, callbacks, API sends, artifacts, liveness, or bindings. Follower registration identity may prepare a binding, but inbound generation authority is published only after successful preparation for the same generation and current context; pending or failed startup cannot append into a retained old journal. Readiness also binds the active Pi context and supplied session generation. Same-session refresh awaits binding preparation without re-registering; a changed session ID cannot publish readiness until its exact leader registration is acknowledged. Reusing a context object cannot carry readiness across a session-generation or ID change. Registration requests capture session authority before asynchronous startup and fence every publication/finalization against the current attempt. Stop or supersession invalidates that attempt; obsolete cleanup cannot erase a newer registration or a refreshed context.
|
|
80
80
|
- Protocol compatibility is independent from package version. Registration negotiates protocol version, runtime build, and canonical capabilities before target provisioning or live publication. `durable-follower-admission-v1` gates source forwarding; `queue-handoff-v1` independently gates semantic queue transfer for every participant and is advertised only with exact source/recipient journal-binding composition. `follower.register`, capability-gated restore-only `follower.restoreWorkspace` and side-effect-free liveness `bus.probe` are bootstrap requests; other requests require exact live-registry generation authority, and `bus.ack` is response-only. `thread-display-mode-v1` gates follower display-setting requests; the leader owns their serialized profile preference and title application.
|
|
@@ -99,14 +99,15 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
99
99
|
|
|
100
100
|
### 4.4 Queue, Delivery, And User Surfaces
|
|
101
101
|
|
|
102
|
-
- Queue lane/kind admission is explicit.
|
|
102
|
+
- Queue lane/kind admission is explicit. Ordinary new-task dispatch waits for active-turn, pending-dispatch, control, compaction, `ctx.isIdle()`, and Pi pending-message guards; a dispatched prompt stays queued until `agent_start` consumes it. The terminal shows one warning-yellow `+N` suffix for executable waiting work across all lanes (Normal/Priority/Steer/Control), omitted when zero. It excludes the dispatched head, frozen attempts and Skip, and never counts current agent work from any source. Each prompt is one object with one active lane and no reserved return slot. Control/Steer/Priority/Normal have explicit ranks. For ordinary prompts, crossing lanes removes it from the source and appends it at the destination tail; Keep/Skip and unchanged effective intent preserve lane position. Complete reaction sets derive Steer from fire, otherwise Priority from thumbs-up/heart/dove, and independent Skip from negative emoji. Fire dominates weaker positives; lightning is a Control marker, never a command classifier or Priority alias. Decorative changes cannot reset a menu lane choice; positive intent and Skip may coexist, suppressed turns retain durable receipts while waiting, and Skip settles them only when the prompt reaches dispatch before dropping it without inference. Suppressed turns remain visible at a struck-through physical ordinal without contributing to executable queue counters, while graceful session shutdown discards all remaining queue authority before clearing memory.
|
|
103
|
+
- Experimental Steer stays in the existing Queue/admission owner until one protected whole-packet attempt. Active insertion uses public `turn_end` after one response/full tool batch without resetting current Telegram reply/preview ownership; idle uses ordinary `sendUserMessage`. Recheck exact context/generation/source/native/compaction guards, freeze original objects before whole-receipt journal disposition, and never treat it as SDK/model-use proof. Partial/refused/unknown attempts remain non-executable and immutable without replay. Idle originals stay behind one pinned pending head until `agent_start` projects one active reply/voice/attachment owner. Keep identity fencing separate from same-session readiness refusal so old failures cannot clear replacement flags and unissued packets cannot contaminate native agent starts. Queue transfer allows unspent Steer only with `queue-steer-v1` plus baseline handoff support at donor/leader/recipient; missing capability refuses before offer, and frozen/pending work never transfers as unspent input.
|
|
103
104
|
- `/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.
|
|
104
105
|
- 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.
|
|
105
106
|
- 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
107
|
- 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.
|
|
107
108
|
- 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.
|
|
108
109
|
- `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.
|
|
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.
|
|
110
|
+
- 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, never a Thread-existence or message-delivery probe: 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.
|
|
110
111
|
- 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.
|
|
111
112
|
- 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.
|
|
112
113
|
|
package/BACKLOG.md
CHANGED
|
@@ -2,4 +2,9 @@
|
|
|
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
|
+
- [ ] **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, follower `/start` Restore with negotiated menu-alias support, and leader Restore confirmation on the first click plus separately proven old-tab cleanup. Record leftover-tab behavior, exact peer capabilities and diagnostics; local regressions and release authorization do not replace this live evidence.
|
|
6
|
+
- [ ] **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
|
+
- [ ] **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
|
+
- [ ] **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.
|
|
9
|
+
- [ ] **Post-reload diagnostics.** Investigate recurring typing-authority refusals and Workspace publication retries observed alongside successful leader/follower reconnection. Establish whether they are expected fenced stale work or affect current-session delivery; successful registration alone does not establish error-free operation.
|
|
10
|
+
- [ ] **Native Windows scenarios.** Verify named-pipe custody/ACK, journal/atomic rename, reused-context lifecycle and the phone scenarios on an actual Windows host. Record skipped tests and missing platform evidence explicitly; a hosted CI result does not establish phone or full live-runtime acceptance.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.56.1: Recover interrupted connections
|
|
8
|
+
|
|
9
|
+
- `Connection recovery`: Explicit `/telegram-connect` can abandon matching unknown Thread-creation attempts and connect afresh without deleting tabs or clearing accepted work. Known pending targets keep their Workspace key and slot; unique creation IDs prevent late replies from attaching to a new attempt. Follower recovery requires `explicit-connect-recovery-v1` on both peers. A lost API reply may leave an extra tab; automatic startup and Restore-only registration never reset it.
|
|
10
|
+
- `Restore`: Reconnecting followers verify retained tabs with a connected message rather than typing, replacing a proven deleted tab. Negotiated `held-menu-aliases-v1` lets `/start` and `/help` restore a follower and open its restricted menu without pairing or bootstrap. A leader Restore waits briefly for the exact command-removal ACK on the original click, independently of menu delivery; lost ACKs never replay input, and old-tab cleanup remains separately fenced.
|
|
11
|
+
- `Windows validation`: Follower control fixtures use fresh registration timestamps and the production Live Restore deadline; a delayed apply case retains exact command and receipt assertions. Commentary/Next integration tests await observed progress under a shared test deadline and always stop their runtime before restoring fixtures, including on failure. Runtime authority and retry policy are unchanged.
|
|
12
|
+
|
|
13
|
+
## 0.56.0: Steer from Telegram
|
|
14
|
+
|
|
15
|
+
- `Thread recovery`: A startup Telegram API timeout can leave the owner without a local bus despite retained Threaded Mode. A fresh positive capability probe now starts that owner's Threaded transport instead of treating the unchanged saved mode as readiness. Healthy buses stay running and followers cannot acquire leadership through this recovery.
|
|
16
|
+
- `Steer`: Send corrections into a busy Pi run with a fire reaction or the queue menu. Ready whole prompts form FIFO packets at public model-step boundaries; idle Steer starts one ordinary task. Waiting input stays in Telegram's queue, not Pi's native steering/follow-up queues. Native pending input and compaction defer attempts; delivery is not a transactional model-use acknowledgement, and uncertain attempts never automatically replay.
|
|
17
|
+
- `Queue controls`: Normal/Priority/Steer and independent Keep/Skip share menu and reaction state. Fire dominates Priority reactions; changing lanes appends at the destination tail. Blue Normal, purple Priority and yellow Steer selectors accompany one yellow terminal waiting-work count. Dispatched/frozen prompts are read-only; a dispatched guest cannot be removed by stale Skip controls or lose its reply owner.
|
|
18
|
+
- `Queue transfer`: Negotiated `queue-steer-v1` support preserves unspent Steer prompts, ordering, attachments and whole receipts across live handoff. Donor, leader and recipient must all support it alongside base queue handoff; incompatible peers, attempted work and pending packets refuse transfer before custody changes.
|
|
19
|
+
- `Session lifetime`: Queue startup/shutdown and follower refresh preserve captured session generations across asynchronous preparation and teardown. Reused context objects cannot let an obsolete operation discard a successor queue, publish stale registration success or start its monitors. Failed durable disposal retains work without replay.
|
|
20
|
+
- `Validation scope`: Operator smoke tests confirmed native and Telegram Steer, ordered text batching, voice-derived input and Normal work after completion. Broader live idle/native-pending, media/transfer/Restore, interruption and native-Windows scenarios remain follow-up acceptance work, not verified release guarantees.
|
|
21
|
+
|
|
7
22
|
## 0.55.1: Visible Reroute and follower cleanup
|
|
8
23
|
|
|
9
24
|
- `Reroute visibility`: Temporary Reroute forwards text, voice, files and albums into the selected Thread before local/follower admission, then anchors replies to confirmed copies. Source/recipient/generation fences reach HTTP and IPC write/ACK boundaries; one native grant prevents replay after partial/lost ACKs. Unknown cold All copies stay held with Cancel; real native-source recovery remains supported. Journal originals, receipt/group cleanup protections, Restore and cleanup timing are unchanged.
|
package/README.md
CHANGED
|
@@ -61,6 +61,8 @@ The connected Pi instance owns Telegram polling. Use `/telegram-connect <profile
|
|
|
61
61
|
|
|
62
62
|
`/telegram-connect` keeps working when the shared runtime `state.json` is damaged. If it is malformed, foreign or contains invalid evidence, the connecting leader replaces it with a fresh empty state and continues. Every profile then forgets old Thread bindings, slots, unfinished Restore/temporary work and polling continuity; `telegram.json`, Pi history and the released `tmp/telegram` are untouched. Permission errors still stop connection. See [Damaged-State Reset](./docs/architecture.md#damaged-state-reset-operator-approved).
|
|
63
63
|
|
|
64
|
+
If Thread creation was interrupted, an explicit `/telegram-connect` can reset that unfinished attempt and try a fresh connection without clearing queued prompts or taking over a live leader. A lost Telegram response may leave an extra tab behind; known tabs are reused rather than blindly recreated. Both peers need connection-recovery support. See [Connection Recovery](./docs/architecture.md#explicit-connection-recovery).
|
|
65
|
+
|
|
64
66
|
The prompt queue is session-local. Before a reload or restart, let it drain or knowingly accept losing waiting work and re-send what still matters afterwards; nothing is replayed automatically.
|
|
65
67
|
|
|
66
68
|
Ordinary network or admission failures use exponential retry pauses capped at 30 seconds, without permanently stopping after an arbitrary attempt count. The poller recovers when the outage clears. Explicit `/telegram-disconnect` still attempts to stop its captured local transport if Thread cleanup is unavailable; it reports unconfirmed cleanup rather than successful deletion, and an old disconnect cannot stop a new connection. Incomplete stop/release remains visible; check `/telegram-status --debug` and keep Pi open.
|
|
@@ -206,24 +208,33 @@ In Threaded Mode, `All` is input-only: messages enter temporary routing tabs, an
|
|
|
206
208
|
|
|
207
209
|
Messages sent while Pi is busy become queued turns. Queue controls let you inspect, prioritize, keep or skip, and dispatch work without touching the terminal.
|
|
208
210
|
|
|
209
|
-
|
|
211
|
+
Use reactions or the queue menu to control waiting prompts.
|
|
212
|
+
|
|
213
|
+
Priority — when the prompt runs:
|
|
214
|
+
- **Normal — no dedicated reaction emoji.** The default lane for ordinary queued work.
|
|
215
|
+
- **Priority — `👍`.** Moves waiting input ahead of Normal work, without interrupting the active task.
|
|
216
|
+
- **Steer — `🔥`.** Inserts ready input at a model-step boundary, or starts one ordinary task when idle. Fire takes precedence over Priority reactions; removing it falls back to remaining Priority intent or Normal.
|
|
210
217
|
|
|
211
|
-
|
|
212
|
-
-
|
|
213
|
-
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
-
|
|
219
|
-
-
|
|
220
|
-
-
|
|
218
|
+
Disposition — whether the prompt runs:
|
|
219
|
+
- **Keep — no dedicated reaction emoji.** The default disposition: the prompt remains eligible to run in its selected lane.
|
|
220
|
+
- **Skip — `👎`.** Suppresses the prompt without changing its lane or position. Priority and Skip can coexist—for example `👍 + 👎`; Skip wins at dispatch.
|
|
221
|
+
|
|
222
|
+
Queued commands and control actions use a separate **Control** lane ahead of Steer, Priority and Normal. They are marked with `⚡️`; this is a label, not a reaction shortcut.
|
|
223
|
+
|
|
224
|
+
Queue policy:
|
|
225
|
+
- Changing a prompt's lane moves it to the end of that lane; switching back does not restore its former position.
|
|
226
|
+
- Keep/Skip preserves position. Skip stays reversible until the prompt reaches dispatch, then drops it without a model turn. Skipped prompts remain visible with a struck-through number.
|
|
227
|
+
- One yellow terminal counter shows waiting work across all lanes, excluding current or already-dispatched work, Skip and frozen attempts.
|
|
228
|
+
- Dispatched or frozen prompts are inspection-only. An uncertain Steer attempt is not automatically retried.
|
|
229
|
+
- The bot cannot remove your Telegram reactions; changing a menu choice may leave the original reaction visible.
|
|
230
|
+
- A negative reaction to your waiting standalone `/continue` cancels it immediately rather than marking it for reversible Skip. Already-running work is unaffected.
|
|
231
|
+
- The queue is session-local. Let it drain before reload or shutdown, or accept losing waiting work.
|
|
221
232
|
|
|
222
|
-
|
|
233
|
+
See [Queue Behavior and Safety](./docs/architecture.md#priority-reactions-keep-and-skip) for reaction precedence, dispatch rules and recovery details.
|
|
223
234
|
|
|
224
235
|
### Native Rich Markdown
|
|
225
236
|
|
|
226
|
-
|
|
237
|
+
Answers use Telegram-native Rich Markdown, with clickable commands and links. In Settings, use **Draft previews** to control streaming previews, **Activity** to choose the amount of thinking and tool detail, and **Assistant rendering** to switch between Rich Markdown and HTML compatibility mode. See [Rendering and Delivery](./docs/architecture.md#rendering-and-delivery) and [Activity](./docs/activity.md).
|
|
227
238
|
|
|
228
239
|
### Files And Artifacts
|
|
229
240
|
|
|
@@ -250,14 +261,14 @@ Classic private DM mode is the base product mode. When Telegram private-chat Thr
|
|
|
250
261
|
- 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.
|
|
251
262
|
- Telegram never launches hidden Pi processes.
|
|
252
263
|
|
|
253
|
-
In
|
|
264
|
+
In Settings → **🧵 Thread display**, choose Letters (default), Names, Directory Title or Directory Snake. Use `/name` for a custom Thread name and **Reset to automatic** to restore the selected naming style. Renaming preserves the Thread and its queued work. See [Thread Display](./docs/architecture.md#threaded-mode-multi-instance-bus) for naming details.
|
|
254
265
|
|
|
255
266
|
| Mode | Best for | Runtime shape |
|
|
256
267
|
| --- | --- | --- |
|
|
257
268
|
| Classic DM | One running Pi instance and its active session controlled from one private bot chat | One polling owner, one queue/runtime surface |
|
|
258
269
|
| Threaded Mode | Several visible Pi sessions sharing one bot | One leader owns transport; each private-chat Thread retains one session-qualified Workspace binding |
|
|
259
270
|
|
|
260
|
-
**
|
|
271
|
+
**Restore** binds the selected live Pi session to the current Thread and releases the waiting input there. It may delete the session's former Thread and its messages; unfinished work can defer cleanup. An unconfirmed deletion is not automatically retried. See [Restore Behavior and Safety](./docs/multi-instance-bus.md#live-rebind-channel).
|
|
261
272
|
|
|
262
273
|
## Environment Configuration
|
|
263
274
|
|
|
@@ -292,9 +303,9 @@ Stable public entrypoints are documented in [Public API](./docs/public-api.md),
|
|
|
292
303
|
|
|
293
304
|
## Safety Boundaries
|
|
294
305
|
|
|
295
|
-
|
|
306
|
+
Inbound recovery protects against **process crashes**, not power loss or storage failure. Keep `~/.pi/agent` on reliable storage with appropriate backups. Do not downgrade to a runtime that cannot read the current journal format: it could process already admitted input again. See [Durable Admission and Recovery](./docs/architecture.md#durable-admission-and-recovery).
|
|
296
307
|
|
|
297
|
-
|
|
308
|
+
Configuration lives in `telegram.json`; runtime data lives under `<agent-dir>/tmp/pi-telegram`. Pending-input retention and cleanup rules are documented in [Session-Owned Journal Storage](./docs/multi-instance-bus.md#session-owned-journal-storage).
|
|
298
309
|
|
|
299
310
|
`pi-telegram` intentionally does not:
|
|
300
311
|
|
package/dist/lib/bindings.d.ts
CHANGED
|
@@ -57,17 +57,18 @@ export interface TelegramQueueBindingRuntime<TContext> {
|
|
|
57
57
|
requestNextDispatchAnnouncement: () => void;
|
|
58
58
|
cancelNextDispatchAnnouncement: () => void;
|
|
59
59
|
watchdog: Queue.TelegramQueueDispatchWatchdogRuntime<TContext>;
|
|
60
|
+
onTurnEnd: (event: Pick<Pi.TurnEndEvent, "turnIndex" | "outcome">, ctx: TContext) => Pi.TurnEndEventResult | undefined;
|
|
60
61
|
}
|
|
61
62
|
export declare function createTelegramQueueBindingRuntime<TContext>(deps: {
|
|
62
63
|
store: Queue.TelegramQueueStateStore<TContext>;
|
|
63
64
|
queue: Pick<Runtime.TelegramBridgeRuntime["queue"], "allocateItemOrder">;
|
|
64
|
-
lifecycle: Pick<Runtime.TelegramBridgeRuntime["lifecycle"], "isCompactionInProgress" | "hasDispatchPending"
|
|
65
|
-
activeTurn: Pick<Queue.TelegramActiveTurnStore, "has"
|
|
65
|
+
lifecycle: Pick<Runtime.TelegramBridgeRuntime["lifecycle"], "isCompactionInProgress" | "hasDispatchPending"> & Partial<Pick<Runtime.TelegramBridgeRuntime["lifecycle"], "getActiveToolExecutions">>;
|
|
66
|
+
activeTurn: Pick<Queue.TelegramActiveTurnStore, "has"> & Partial<Pick<Queue.TelegramActiveTurnStore, "get">>;
|
|
66
67
|
admission: {
|
|
67
68
|
getSettlement: () => {
|
|
68
69
|
onItemsDiscarded: (items: readonly Queue.TelegramQueueItem<TContext>[], ctx: TContext) => boolean;
|
|
69
70
|
isItemReady: (item: Queue.TelegramQueueItem<TContext>) => boolean;
|
|
70
|
-
onPromptHandedOff?: (item: Queue.PendingTelegramTurn, ctx: TContext) => boolean;
|
|
71
|
+
onPromptHandedOff?: (item: Pick<Queue.PendingTelegramTurn, "admissionReceipts">, ctx: TContext) => boolean;
|
|
71
72
|
onControlSettled: (item: Queue.PendingTelegramControlItem<TContext>, ctx: TContext) => void;
|
|
72
73
|
} | undefined;
|
|
73
74
|
hasPendingQueueMutationForItem: (item: Queue.TelegramQueueItem<TContext>) => boolean;
|
|
@@ -77,6 +78,8 @@ export declare function createTelegramQueueBindingRuntime<TContext>(deps: {
|
|
|
77
78
|
promptDispatch: Runtime.TelegramPromptDispatchRuntime<TContext>;
|
|
78
79
|
isIdle: (ctx: TContext) => boolean;
|
|
79
80
|
hasPendingMessages: (ctx: TContext) => boolean;
|
|
81
|
+
isContextCurrent?: (ctx: TContext) => boolean;
|
|
82
|
+
isContextAborted?: (ctx: TContext) => boolean;
|
|
80
83
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
81
84
|
sendTextReply: Queue.TelegramQueueDispatchRuntimeDeps<TContext>["sendTextReply"];
|
|
82
85
|
sendUserMessage: Queue.TelegramQueueDispatchRuntimeDeps<TContext>["sendUserMessage"];
|
|
@@ -272,6 +275,7 @@ interface TelegramLifecycleBindingDeps {
|
|
|
272
275
|
sendMarkdownReply: Queue.TelegramAgentEndHookRuntimeDeps<Queue.PendingTelegramTurn, Pi.ExtensionContext, Pi.AgentEndEvent["messages"][number], Keyboard.TelegramInlineKeyboardMarkup>["sendMarkdownReply"];
|
|
273
276
|
sendTextReply: Queue.TelegramAgentEndHookRuntimeDeps<Queue.PendingTelegramTurn, Pi.ExtensionContext, Pi.AgentEndEvent["messages"][number], Keyboard.TelegramInlineKeyboardMarkup>["sendTextReply"] & NonNullable<OutboundHandlers.TelegramVoiceReplySenderDeps["sendTextReply"]>;
|
|
274
277
|
dispatchNextQueuedTelegramTurn: (ctx: Pi.ExtensionContext) => void;
|
|
278
|
+
onSteerTurnEnd?: TelegramQueueBindingRuntime<Pi.ExtensionContext>["onTurnEnd"];
|
|
275
279
|
onPromptHandedOff?: (turn: Queue.PendingTelegramTurn, ctx: Pi.ExtensionContext) => void;
|
|
276
280
|
answerGuestQuery: TelegramApi.TelegramBridgeApiRuntime["answerGuestQuery"];
|
|
277
281
|
deleteMessage: TelegramApi.TelegramBridgeApiRuntime["deleteMessage"];
|
|
@@ -296,5 +300,5 @@ interface TelegramLifecycleBindingDeps {
|
|
|
296
300
|
/** Settled-session hint for owners that wait on agent idleness, such as old-tab cleanup after a live rebind. */
|
|
297
301
|
onSessionSettled?: (ctx: Pi.ExtensionContext) => void;
|
|
298
302
|
}
|
|
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;
|
|
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;
|
|
300
304
|
export {};
|
package/dist/lib/bindings.js
CHANGED
|
@@ -119,6 +119,10 @@ export function createTelegramQueueBindingRuntime(deps) {
|
|
|
119
119
|
return false;
|
|
120
120
|
return settlement.onItemsDiscarded(durableItems, ctx) === true;
|
|
121
121
|
};
|
|
122
|
+
const settlePromptPacket = (items, ctx) => {
|
|
123
|
+
const receipts = items.flatMap(item => item.admissionReceipts ?? []);
|
|
124
|
+
return receipts.length === 0 || deps.admission.getSettlement()?.onPromptHandedOff?.({ admissionReceipts: receipts }, ctx) === true;
|
|
125
|
+
};
|
|
122
126
|
const mutation = Queue.createTelegramQueueMutationController({
|
|
123
127
|
...deps.store,
|
|
124
128
|
hasPendingDispatch: deps.lifecycle.hasDispatchPending,
|
|
@@ -159,6 +163,13 @@ export function createTelegramQueueBindingRuntime(deps) {
|
|
|
159
163
|
return false;
|
|
160
164
|
return settlement.onPromptHandedOff(item, ctx) === true;
|
|
161
165
|
},
|
|
166
|
+
steerDispatch: {
|
|
167
|
+
commitPacket: settlePromptPacket,
|
|
168
|
+
isContextCurrent: (ctx) => deps.isContextCurrent?.(ctx) === true,
|
|
169
|
+
canSubmit: (ctx) => deps.isContextAborted?.(ctx) === false &&
|
|
170
|
+
deps.isIdle(ctx) && !deps.hasPendingMessages(ctx) && !deps.lifecycle.isCompactionInProgress() &&
|
|
171
|
+
!deps.activeTurn.has() && (deps.lifecycle.getActiveToolExecutions?.() ?? 0) === 0,
|
|
172
|
+
},
|
|
162
173
|
onControlSettled(item, ctx) {
|
|
163
174
|
deps.admission.getSettlement()?.onControlSettled(item, ctx);
|
|
164
175
|
},
|
|
@@ -172,8 +183,31 @@ export function createTelegramQueueBindingRuntime(deps) {
|
|
|
172
183
|
...deps.promptDispatch,
|
|
173
184
|
sendUserMessage: deps.sendUserMessage,
|
|
174
185
|
});
|
|
186
|
+
const steer = Queue.createTelegramSteerBoundaryRuntime({
|
|
187
|
+
...deps.store,
|
|
188
|
+
getGeneration: deps.deferredDispatch.getGeneration,
|
|
189
|
+
isContextCurrent: (ctx) => deps.isContextCurrent?.(ctx) === true,
|
|
190
|
+
isBlocked: (ctx) => deps.isContextAborted?.(ctx) !== false || !deps.deferredDispatch.isBound() ||
|
|
191
|
+
deps.isIdle(ctx) || deps.hasPendingMessages(ctx) ||
|
|
192
|
+
deps.lifecycle.isCompactionInProgress() || deps.lifecycle.hasDispatchPending() ||
|
|
193
|
+
(deps.lifecycle.getActiveToolExecutions?.() ?? 0) > 0 ||
|
|
194
|
+
(deps.activeTurn.has() && !deps.activeTurn.get?.()),
|
|
195
|
+
isItemReady(item) {
|
|
196
|
+
return deps.transportStamp.isActive(item.transportStamp) &&
|
|
197
|
+
!deps.admission.hasPendingQueueMutationForItem(item) &&
|
|
198
|
+
(deps.admission.getSettlement()?.isItemReady(item) ?? (item.admissionReceipts?.length ?? 0) === 0);
|
|
199
|
+
},
|
|
200
|
+
isItemCurrent: (item) => deps.transportStamp.isActive(item.transportStamp) &&
|
|
201
|
+
!deps.admission.hasPendingQueueMutationForItem(item),
|
|
202
|
+
getActiveTurn: () => deps.activeTurn.get?.(),
|
|
203
|
+
commitPacket: settlePromptPacket,
|
|
204
|
+
discardItem: (item, ctx) => settleDiscardedItems([item], ctx),
|
|
205
|
+
updateStatus: deps.updateStatus,
|
|
206
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
207
|
+
});
|
|
175
208
|
return {
|
|
176
209
|
mutation,
|
|
210
|
+
onTurnEnd: steer.onTurnEnd,
|
|
177
211
|
dispatchNext: dispatch.dispatchNext,
|
|
178
212
|
requestNextDispatchAnnouncement: dispatch.requestNextDispatchAnnouncement,
|
|
179
213
|
cancelNextDispatchAnnouncement: dispatch.cancelNextDispatchAnnouncement,
|
|
@@ -627,7 +661,7 @@ export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, pe
|
|
|
627
661
|
},
|
|
628
662
|
});
|
|
629
663
|
}
|
|
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, }) {
|
|
664
|
+
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, }) {
|
|
631
665
|
const agentEndResetter = Runtime.createTelegramAgentEndResetter({
|
|
632
666
|
abort,
|
|
633
667
|
typing,
|
|
@@ -877,6 +911,12 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
877
911
|
isSessionActive: isSessionContextActive,
|
|
878
912
|
...sessionLifecycleRuntime,
|
|
879
913
|
...agentLifecycleHooks,
|
|
914
|
+
...(onSteerTurnEnd ? {
|
|
915
|
+
onTurnEnd(event, ctx) {
|
|
916
|
+
const result = onSteerTurnEnd(event, ctx);
|
|
917
|
+
return result ? { ...result, entries: [...event.entries, ...(result.entries ?? [])] } : undefined;
|
|
918
|
+
},
|
|
919
|
+
} : {}),
|
|
880
920
|
onInput(event) {
|
|
881
921
|
activityRuntime.recordInputSource(event.source ?? "unknown");
|
|
882
922
|
},
|
|
@@ -37,6 +37,7 @@ export interface TelegramBusFollowerRegistrationRuntime<TContext> {
|
|
|
37
37
|
target?: TelegramTarget;
|
|
38
38
|
previousInstanceId?: string;
|
|
39
39
|
restoreWorkspace?: boolean;
|
|
40
|
+
recoverConnection?: boolean;
|
|
40
41
|
}) => Promise<boolean>;
|
|
41
42
|
setContext: (ctx: TContext) => void | Promise<void>;
|
|
42
43
|
disconnectFromLeader?: () => Promise<boolean>;
|
package/dist/lib/bus-follower.js
CHANGED
|
@@ -9,7 +9,7 @@ import { createHash } from "node:crypto";
|
|
|
9
9
|
import { basename } from "node:path";
|
|
10
10
|
import { isDeepStrictEqual } from "node:util";
|
|
11
11
|
import { getTelegramBusTransportRetryPolicy, TELEGRAM_BUS_REGISTRATION_RETRY, } from "./bus-transport.js";
|
|
12
|
-
import { createTelegramBusFollowerDeliveryIdentity, createTelegramBusForeignOwnedUpdateForwarder, createTelegramBusLocalServer, createTelegramBusRequestIdFactory, createUnauthorizedBusAck, rejectTelegramBusRequest, getTelegramBusProtocolCompatibility, getTelegramBusSocketPath, hasTelegramBusCapability, isTelegramBusEnvelopeAuthorized, parseTelegramBusEnvelope, resolveTelegramBusSocketPath, sendTelegramBusLocalEnvelope, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY,
|
|
12
|
+
import { createTelegramBusFollowerDeliveryIdentity, createTelegramBusForeignOwnedUpdateForwarder, createTelegramBusLocalServer, createTelegramBusRequestIdFactory, createUnauthorizedBusAck, rejectTelegramBusRequest, getTelegramBusProtocolCompatibility, getTelegramBusSocketPath, hasTelegramBusCapability, hasTelegramBusSteerQueueHandoffCapability, isTelegramBusEnvelopeAuthorized, parseTelegramBusEnvelope, resolveTelegramBusSocketPath, sendTelegramBusLocalEnvelope, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE, TELEGRAM_BUS_CAPABILITY_PRE_DISPATCH_REBIND_EXECUTION, TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY, TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE, TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME, TELEGRAM_BUS_HELD_COMMAND_CAPABILITIES, getTelegramBusHeldCommandCapabilities, TELEGRAM_BUS_LIVE_REBIND_CAPABILITIES, TelegramBusLocalAuthorityError, } from "./bus.js";
|
|
13
13
|
import { parseTelegramUpdateJournalQueueOwner, } from "./journal.js";
|
|
14
14
|
import { TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS, } from "./locks.js";
|
|
15
15
|
import * as Machine from "./machine.js";
|
|
@@ -1792,7 +1792,7 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
|
|
|
1792
1792
|
deps.setActiveAuthSecret?.(undefined);
|
|
1793
1793
|
await deps.stopReceiving?.();
|
|
1794
1794
|
};
|
|
1795
|
-
const pendingHandoff = options
|
|
1795
|
+
const pendingHandoff = options && !options.recoverConnection
|
|
1796
1796
|
? undefined
|
|
1797
1797
|
: getTelegramFollowerSessionHandoff();
|
|
1798
1798
|
const pendingHandoffOptions = isTelegramFollowerSessionHandoffFresh(pendingHandoff)
|
|
@@ -1801,7 +1801,7 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
|
|
|
1801
1801
|
previousInstanceId: pendingHandoff.instanceId,
|
|
1802
1802
|
}
|
|
1803
1803
|
: undefined;
|
|
1804
|
-
const registrationOptions = options
|
|
1804
|
+
const registrationOptions = options ? { ...pendingHandoffOptions, ...options } : pendingHandoffOptions;
|
|
1805
1805
|
const leaderSocketPath = leader.busSocketPath ??
|
|
1806
1806
|
deps.getLeaderSocketPath?.() ??
|
|
1807
1807
|
getTelegramBusSocketPath();
|
|
@@ -1821,6 +1821,8 @@ export function createTelegramBusFollowerRegistrationRuntime(deps) {
|
|
|
1821
1821
|
: "follower.register",
|
|
1822
1822
|
requestId: registrationGeneration,
|
|
1823
1823
|
auth: activeAuthSecret,
|
|
1824
|
+
...(registrationOptions?.recoverConnection && !registrationOptions.restoreWorkspace
|
|
1825
|
+
? { recoverConnection: true } : {}),
|
|
1824
1826
|
registration: {
|
|
1825
1827
|
instanceId: deps.instanceId,
|
|
1826
1828
|
...(registrationOptions?.previousInstanceId
|
|
@@ -2875,8 +2877,7 @@ export function createTelegramBusForwardedUpdateReceiverRuntime(deps) {
|
|
|
2875
2877
|
? [
|
|
2876
2878
|
TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY,
|
|
2877
2879
|
TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE,
|
|
2878
|
-
|
|
2879
|
-
TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY,
|
|
2880
|
+
...getTelegramBusHeldCommandCapabilities(commandInput.name),
|
|
2880
2881
|
]
|
|
2881
2882
|
: []),
|
|
2882
2883
|
];
|
|
@@ -3033,6 +3034,10 @@ export function createTelegramBusForwardedUpdateReceiverRuntime(deps) {
|
|
|
3033
3034
|
result: receipt,
|
|
3034
3035
|
};
|
|
3035
3036
|
}
|
|
3037
|
+
if (envelope.payload.queueLane === "steer" &&
|
|
3038
|
+
![deps.getLocalProtocol?.(), deps.getLeaderProtocol?.()].every(hasTelegramBusSteerQueueHandoffCapability)) {
|
|
3039
|
+
throw new Error("Steer queue handoff capability was not negotiated.");
|
|
3040
|
+
}
|
|
3036
3041
|
if (!deps.handleQueueHandoff) {
|
|
3037
3042
|
throw new Error("Telegram bus receiver cannot accept queue handoff payloads.");
|
|
3038
3043
|
}
|
package/dist/lib/bus-leader.d.ts
CHANGED
|
@@ -187,6 +187,7 @@ export declare function createTelegramBusInstanceLifecycleAnnouncement(input: {
|
|
|
187
187
|
}): TelegramBusFollowerLifecycleAnnouncement;
|
|
188
188
|
export declare function createTelegramBusFollowerTargetProvisioner(deps: TelegramBusFollowerTargetProvisionerDeps): (registration: TelegramBusInstanceRegistration, options?: {
|
|
189
189
|
existingWorkspaceBindingOnly?: boolean;
|
|
190
|
+
recoverConnection?: boolean;
|
|
190
191
|
}) => Promise<(TelegramTarget & {
|
|
191
192
|
slot?: string;
|
|
192
193
|
threadName?: string;
|
|
@@ -248,6 +249,7 @@ export interface TelegramBusLeaderEnvelopeHandlerDeps {
|
|
|
248
249
|
}>) => Promise<unknown> | unknown;
|
|
249
250
|
provisionFollowerTarget?: (registration: TelegramBusInstanceRegistration, options?: {
|
|
250
251
|
existingWorkspaceBindingOnly?: boolean;
|
|
252
|
+
recoverConnection?: boolean;
|
|
251
253
|
}) => Promise<(TelegramTarget & {
|
|
252
254
|
slot?: string;
|
|
253
255
|
threadName?: string;
|
package/dist/lib/bus-leader.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { isDeepStrictEqual } from "node:util";
|
|
8
8
|
import { getTelegramBusTransportRetryPolicy } from "./bus-transport.js";
|
|
9
|
-
import { createTelegramBusLocalServer, createUnauthorizedBusAck, rejectTelegramBusRequest, getTelegramBusEnvelopeTrafficClass, getTelegramBusFollowerSocketPath, getTelegramBusProtocolCompatibility, hasTelegramBusCapability, isTelegramBusEnvelopeAuthorized, parseTelegramBusEnvelope, sendTelegramBusLocalEnvelope, stripTelegramBusApiMetadata, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION, TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF, TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY, TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE, TELEGRAM_BUS_CAPABILITY_PRE_DISPATCH_REBIND, TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE, TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME, } from "./bus.js";
|
|
9
|
+
import { createTelegramBusLocalServer, createUnauthorizedBusAck, rejectTelegramBusRequest, getTelegramBusEnvelopeTrafficClass, getTelegramBusFollowerSocketPath, getTelegramBusProtocolCompatibility, hasTelegramBusCapability, isTelegramBusEnvelopeAuthorized, parseTelegramBusEnvelope, sendTelegramBusLocalEnvelope, stripTelegramBusApiMetadata, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION, TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF, TELEGRAM_BUS_CAPABILITY_CONNECT_RECOVERY, hasTelegramBusSteerQueueHandoffCapability, TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY, TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE, TELEGRAM_BUS_CAPABILITY_PRE_DISPATCH_REBIND, TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE, TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME, } from "./bus.js";
|
|
10
10
|
import { escapeHtml } from "./rendering.js";
|
|
11
11
|
import * as Sync from "./sync.js";
|
|
12
12
|
import { parseTelegramIntegerId as asInteger, } from "./target.js";
|
|
@@ -881,6 +881,16 @@ export function createTelegramBusFollowerTargetProvisioner(deps) {
|
|
|
881
881
|
if (typeof chatId !== "number")
|
|
882
882
|
return registration.target;
|
|
883
883
|
await deps.topicTargetStore.load();
|
|
884
|
+
if (options?.recoverConnection && !options.existingWorkspaceBindingOnly) {
|
|
885
|
+
const epoch = deps.getCurrentLeaderEpoch?.();
|
|
886
|
+
await Threads.resetTelegramConnectionProvisioning({
|
|
887
|
+
store: deps.topicTargetStore, owner: "manual-follower", instanceId: registration.instanceId,
|
|
888
|
+
profileKey: registration.profileKey ?? `manual:${registration.instanceId}`,
|
|
889
|
+
isCurrent: () => !deps.getCurrentLeaderEpoch ||
|
|
890
|
+
(epoch !== undefined && deps.getCurrentLeaderEpoch() === epoch),
|
|
891
|
+
recordEvent: deps.recordRuntimeEvent,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
884
894
|
let restoredPredecessor;
|
|
885
895
|
if (options?.existingWorkspaceBindingOnly && registration.cwd && registration.sessionId &&
|
|
886
896
|
!registration.previousInstanceId && deps.getLiveFollowers && deps.isFollowerProcessAlive) {
|
|
@@ -917,6 +927,7 @@ export function createTelegramBusFollowerTargetProvisioner(deps) {
|
|
|
917
927
|
? deps.topicTargetStore.claimWorkspaceIdentity(registration.cwd, registration.instanceId, registration.previousInstanceId ?? restoredPredecessor?.instanceId, {
|
|
918
928
|
existingBindingOnly: options?.existingWorkspaceBindingOnly === true,
|
|
919
929
|
sessionId: registration.sessionId,
|
|
930
|
+
provisionProfileKey: registration.profileKey ?? `manual:${registration.instanceId}`,
|
|
920
931
|
onCapacityUnavailable() {
|
|
921
932
|
capacityUnavailable = true;
|
|
922
933
|
},
|
|
@@ -1129,11 +1140,6 @@ export function createTelegramBusFollowerTargetProvisioner(deps) {
|
|
|
1129
1140
|
};
|
|
1130
1141
|
}
|
|
1131
1142
|
const probeRequiredRecord = reconnectRecord?.status === "probe-required";
|
|
1132
|
-
const exactSessionHandoff = crossSessionReuse &&
|
|
1133
|
-
!!requestedTarget &&
|
|
1134
|
-
registration.previousInstanceId === reconnectRecord?.instanceId &&
|
|
1135
|
-
requestedTarget.chatId === reconnectRecord.target.chatId &&
|
|
1136
|
-
requestedTarget.threadId === reconnectRecord.target.threadId;
|
|
1137
1143
|
const requiresVisibilityProbe = crossSessionReuse ||
|
|
1138
1144
|
probeRequiredRecord ||
|
|
1139
1145
|
recoverableTarget !== undefined;
|
|
@@ -1151,18 +1157,14 @@ export function createTelegramBusFollowerTargetProvisioner(deps) {
|
|
|
1151
1157
|
if (requiresVisibilityProbe && connectedAnnouncement) {
|
|
1152
1158
|
try {
|
|
1153
1159
|
assertRestoreRegistration();
|
|
1154
|
-
await deps.callApi(
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
:
|
|
1161
|
-
|
|
1162
|
-
message_thread_id: connectedAnnouncement.target.threadId,
|
|
1163
|
-
text: connectedAnnouncement.text,
|
|
1164
|
-
parse_mode: connectedAnnouncement.parseMode,
|
|
1165
|
-
});
|
|
1160
|
+
await deps.callApi(
|
|
1161
|
+
// Typing is presence, not evidence that a retained Thread still accepts messages.
|
|
1162
|
+
"sendMessage", {
|
|
1163
|
+
chat_id: connectedAnnouncement.target.chatId,
|
|
1164
|
+
message_thread_id: connectedAnnouncement.target.threadId,
|
|
1165
|
+
text: connectedAnnouncement.text,
|
|
1166
|
+
parse_mode: connectedAnnouncement.parseMode,
|
|
1167
|
+
});
|
|
1166
1168
|
assertRestoreRegistration();
|
|
1167
1169
|
if (recoverableTarget || probeRequiredRecord) {
|
|
1168
1170
|
const activatedRecord = deps.topicTargetStore.upsert({
|
|
@@ -2120,6 +2122,10 @@ export function createTelegramBusLeaderEnvelopeHandler(deps) {
|
|
|
2120
2122
|
!hasTelegramBusCapability(recipient?.protocol, TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF)) {
|
|
2121
2123
|
return rejectTelegramBusRequest(envelope.requestId, "Telegram queue handoff capability was not negotiated.");
|
|
2122
2124
|
}
|
|
2125
|
+
if (envelope.payload.queueLane === "steer" &&
|
|
2126
|
+
![deps.protocolIdentity, donor.protocol, recipient?.protocol].every(hasTelegramBusSteerQueueHandoffCapability)) {
|
|
2127
|
+
return rejectTelegramBusRequest(envelope.requestId, "Steer queue handoff capability was not negotiated.");
|
|
2128
|
+
}
|
|
2123
2129
|
if (!recipient?.registrationGeneration ||
|
|
2124
2130
|
envelope.recipientRegistrationGeneration !==
|
|
2125
2131
|
recipient.registrationGeneration) {
|
|
@@ -2257,6 +2263,9 @@ export function createTelegramBusLeaderEnvelopeHandler(deps) {
|
|
|
2257
2263
|
(hasTelegramBusCapability(deps.protocolIdentity, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE) &&
|
|
2258
2264
|
hasTelegramBusCapability(envelope.registration.protocol, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE));
|
|
2259
2265
|
const restoringWorkspace = envelope.kind === "follower.restoreWorkspace";
|
|
2266
|
+
const recoverConnection = envelope.kind === "follower.register" && envelope.recoverConnection === true;
|
|
2267
|
+
const supportsConnectionRecovery = [deps.protocolIdentity, envelope.registration.protocol]
|
|
2268
|
+
.every(identity => hasTelegramBusCapability(identity, TELEGRAM_BUS_CAPABILITY_CONNECT_RECOVERY));
|
|
2260
2269
|
const supportsWorkspaceAutoConnect = hasTelegramBusCapability(deps.protocolIdentity, TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT) &&
|
|
2261
2270
|
hasTelegramBusCapability(envelope.registration.protocol, TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT);
|
|
2262
2271
|
if (!compatibility.compatible ||
|
|
@@ -2264,7 +2273,8 @@ export function createTelegramBusLeaderEnvelopeHandler(deps) {
|
|
|
2264
2273
|
!readerCompatible() ||
|
|
2265
2274
|
(envelope.registration.cwd !== undefined &&
|
|
2266
2275
|
envelope.registration.sessionId === undefined) ||
|
|
2267
|
-
(restoringWorkspace && !supportsWorkspaceAutoConnect)
|
|
2276
|
+
(restoringWorkspace && !supportsWorkspaceAutoConnect) ||
|
|
2277
|
+
(recoverConnection && !supportsConnectionRecovery)) {
|
|
2268
2278
|
return {
|
|
2269
2279
|
kind: "bus.ack",
|
|
2270
2280
|
requestId: envelope.requestId,
|
|
@@ -2305,7 +2315,7 @@ export function createTelegramBusLeaderEnvelopeHandler(deps) {
|
|
|
2305
2315
|
if (hasTelegramBusCapability(deps.protocolIdentity, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE) &&
|
|
2306
2316
|
!deps.commitFollowerRegistration)
|
|
2307
2317
|
throw new Error("Workspace Restore registration publication protection is unavailable.");
|
|
2308
|
-
const target = await deps.provisionFollowerTarget?.(envelope.registration, { existingWorkspaceBindingOnly: restoringWorkspace });
|
|
2318
|
+
const target = await deps.provisionFollowerTarget?.(envelope.registration, { existingWorkspaceBindingOnly: restoringWorkspace, ...(recoverConnection ? { recoverConnection: true } : {}) });
|
|
2309
2319
|
if (restoringWorkspace && !target) {
|
|
2310
2320
|
return {
|
|
2311
2321
|
kind: "bus.ack",
|
|
@@ -3026,6 +3036,10 @@ export function createTelegramBusLeaderRuntime(deps) {
|
|
|
3026
3036
|
!hasTelegramBusCapability(recipient?.protocol, TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF)) {
|
|
3027
3037
|
return rejectTelegramBusRequest(input.requestId, "Telegram queue handoff capability was not negotiated.");
|
|
3028
3038
|
}
|
|
3039
|
+
if (input.payload.queueLane === "steer" &&
|
|
3040
|
+
![deps.protocolIdentity, recipient?.protocol].every(hasTelegramBusSteerQueueHandoffCapability)) {
|
|
3041
|
+
return rejectTelegramBusRequest(input.requestId, "Steer queue handoff capability was not negotiated.");
|
|
3042
|
+
}
|
|
3029
3043
|
if (!recipient?.registrationGeneration ||
|
|
3030
3044
|
input.recipientRegistrationGeneration !== recipient.registrationGeneration) {
|
|
3031
3045
|
return rejectTelegramBusRequest(input.requestId, "Stale Telegram queue handoff recipient registration generation.");
|
package/dist/lib/bus.d.ts
CHANGED
|
@@ -36,6 +36,9 @@ export declare function createTelegramBusProcessRuntime(input: {
|
|
|
36
36
|
export declare function createTelegramBusAuthSecret(): string;
|
|
37
37
|
export declare const TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION: "durable-follower-admission-v1";
|
|
38
38
|
export declare const TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF: "queue-handoff-v1";
|
|
39
|
+
/** Unspent Steer lane codec; independent from baseline receipt handoff support. */
|
|
40
|
+
export declare const TELEGRAM_BUS_CAPABILITY_QUEUE_STEER: "queue-steer-v1";
|
|
41
|
+
export declare const TELEGRAM_BUS_CAPABILITY_CONNECT_RECOVERY: "explicit-connect-recovery-v1";
|
|
39
42
|
export declare const TELEGRAM_BUS_CAPABILITY_INPUT_CUSTODY_REFERENCE: "input-custody-reference-v1";
|
|
40
43
|
export declare const TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME: "workspace-thread-rename-v1";
|
|
41
44
|
export declare const TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE: "thread-display-mode-v1";
|
|
@@ -56,6 +59,9 @@ export declare const TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_COMMAND_SET: "live-thre
|
|
|
56
59
|
/** Every live rebinding needs the save/apply/settle trio; held command selections additionally need these two. */
|
|
57
60
|
export declare const TELEGRAM_BUS_LIVE_REBIND_CAPABILITIES: readonly ["live-thread-rebind-save-v1", "live-thread-rebind-apply-v1", "live-thread-rebind-settle-v1"];
|
|
58
61
|
export declare const TELEGRAM_BUS_HELD_COMMAND_CAPABILITIES: readonly ["live-thread-rebind-command-set-v1", "selected-menu-delivery-v1"];
|
|
62
|
+
/** Start/help are restricted menu aliases, negotiated separately from the original held-command set. */
|
|
63
|
+
export declare const TELEGRAM_BUS_CAPABILITY_HELD_MENU_ALIASES: "held-menu-aliases-v1";
|
|
64
|
+
export declare function getTelegramBusHeldCommandCapabilities(name: string): readonly string[];
|
|
59
65
|
export interface TelegramBusSelectedCommandInput {
|
|
60
66
|
name: string;
|
|
61
67
|
target: TelegramTarget & {
|
|
@@ -192,6 +198,7 @@ export declare function createTelegramBusProtocolIdentity(input: {
|
|
|
192
198
|
}): TelegramBusProtocolIdentity;
|
|
193
199
|
export declare function createTelegramCurrentBusProtocolIdentity(capabilities?: readonly string[]): TelegramBusProtocolIdentity;
|
|
194
200
|
export declare function hasTelegramBusCapability(identity: TelegramBusProtocolIdentity | undefined, capability: string): boolean;
|
|
201
|
+
export declare function hasTelegramBusSteerQueueHandoffCapability(identity: TelegramBusProtocolIdentity | undefined): boolean;
|
|
195
202
|
export declare function getTelegramInputCustodyPeerReadiness(followers: readonly Pick<TelegramBusFollowerView, "registrationGeneration" | "protocol">[]): ("ready" | "legacy" | "unknown")[];
|
|
196
203
|
/** Both peers advertise every required capability over a compatible protocol; reads only the two identities. */
|
|
197
204
|
export declare function hasTelegramBusSharedCapabilities(local: TelegramBusProtocolIdentity, remote: TelegramBusProtocolIdentity | undefined, required: readonly string[]): boolean;
|
|
@@ -324,6 +331,7 @@ export declare function createTelegramBusFollowerSourceReferenceDeliveryIdentity
|
|
|
324
331
|
export type TelegramBusEnvelope = ({
|
|
325
332
|
kind: "follower.register";
|
|
326
333
|
requestId: string;
|
|
334
|
+
recoverConnection?: true;
|
|
327
335
|
registration: TelegramBusInstanceRegistration;
|
|
328
336
|
} | {
|
|
329
337
|
kind: "follower.restoreWorkspace";
|