@llblab/pi-telegram 0.51.6 → 0.52.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 +15 -12
- package/BACKLOG.md +16 -9
- package/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +17 -6
- package/dist/lib/activity-verbosity.d.ts +1 -5
- package/dist/lib/activity-verbosity.js +5 -7
- package/dist/lib/bindings.d.ts +5 -1
- package/dist/lib/bindings.js +7 -1
- package/dist/lib/bus-api.js +8 -5
- package/dist/lib/bus-follower.d.ts +54 -56
- package/dist/lib/bus-follower.js +206 -139
- package/dist/lib/bus-leader.d.ts +21 -2
- package/dist/lib/bus-leader.js +181 -25
- package/dist/lib/bus-transport.d.ts +4 -1
- package/dist/lib/bus-transport.js +28 -4
- package/dist/lib/bus.d.ts +52 -56
- package/dist/lib/bus.js +78 -246
- package/dist/lib/channel-posts.d.ts +1 -3
- package/dist/lib/channel-posts.js +23 -26
- package/dist/lib/command-templates.d.ts +0 -5
- package/dist/lib/command-templates.js +5 -5
- package/dist/lib/commands.d.ts +0 -23
- package/dist/lib/commands.js +15 -15
- package/dist/lib/config.d.ts +0 -17
- package/dist/lib/config.js +12 -14
- package/dist/lib/delivery.d.ts +0 -4
- package/dist/lib/delivery.js +2 -5
- package/dist/lib/extension.js +258 -61
- package/dist/lib/generative-apps.d.ts +0 -19
- package/dist/lib/inbound.d.ts +0 -2
- package/dist/lib/inbound.js +2 -2
- package/dist/lib/journal.d.ts +183 -16
- package/dist/lib/journal.js +1175 -248
- package/dist/lib/keyboard.d.ts +0 -2
- package/dist/lib/keyboard.js +2 -2
- package/dist/lib/lifecycle.js +1 -1
- package/dist/lib/locks.d.ts +100 -12
- package/dist/lib/locks.js +380 -14
- package/dist/lib/logging.d.ts +30 -12
- package/dist/lib/logging.js +129 -72
- package/dist/lib/media.d.ts +28 -5
- package/dist/lib/media.js +26 -8
- package/dist/lib/menu-model.d.ts +0 -11
- package/dist/lib/menu-model.js +8 -8
- package/dist/lib/menu-settings.d.ts +0 -18
- package/dist/lib/menu-settings.js +18 -18
- package/dist/lib/menu-status.d.ts +4 -4
- package/dist/lib/menu-status.js +12 -7
- package/dist/lib/menu-thinking.d.ts +1 -1
- package/dist/lib/menu-thinking.js +1 -1
- package/dist/lib/menu.d.ts +1 -0
- package/dist/lib/menu.js +4 -3
- package/dist/lib/outbound-attachments.d.ts +0 -1
- package/dist/lib/outbound-attachments.js +1 -1
- package/dist/lib/outbound-buttons.js +1 -8
- package/dist/lib/outbound-markup.d.ts +1 -6
- package/dist/lib/outbound-markup.js +4 -21
- package/dist/lib/outbound.d.ts +0 -8
- package/dist/lib/outbound.js +5 -5
- package/dist/lib/paths.d.ts +50 -7
- package/dist/lib/paths.js +155 -17
- package/dist/lib/pi.d.ts +1 -0
- package/dist/lib/pi.js +2 -1
- package/dist/lib/polling.d.ts +0 -5
- package/dist/lib/polling.js +5 -5
- package/dist/lib/preview.d.ts +0 -3
- package/dist/lib/preview.js +3 -3
- package/dist/lib/prompt-templates.d.ts +0 -1
- package/dist/lib/prompt-templates.js +1 -1
- package/dist/lib/queue.d.ts +5 -15
- package/dist/lib/queue.js +94 -11
- package/dist/lib/recovery.d.ts +29 -9
- package/dist/lib/recovery.js +104 -33
- package/dist/lib/replies.d.ts +0 -4
- package/dist/lib/replies.js +1 -1
- package/dist/lib/routing.d.ts +74 -8
- package/dist/lib/routing.js +2280 -307
- package/dist/lib/sections.d.ts +0 -2
- package/dist/lib/sections.js +1 -1
- package/dist/lib/status.d.ts +44 -9
- package/dist/lib/status.js +149 -27
- package/dist/lib/sync.d.ts +0 -5
- package/dist/lib/sync.js +5 -4
- package/dist/lib/telegram-api.d.ts +13 -3
- package/dist/lib/telegram-api.js +38 -23
- package/dist/lib/thread-cleanup-manager.d.ts +1 -0
- package/dist/lib/thread-cleanup-manager.js +18 -21
- package/dist/lib/thread-naming.d.ts +32 -5
- package/dist/lib/thread-naming.js +191 -7
- package/dist/lib/thread-reconciler.d.ts +2 -0
- package/dist/lib/thread-reconciler.js +1 -1
- package/dist/lib/threads.d.ts +293 -59
- package/dist/lib/threads.js +1689 -369
- package/dist/lib/turns.d.ts +0 -1
- package/dist/lib/turns.js +1 -1
- package/dist/lib/updates.d.ts +191 -28
- package/dist/lib/updates.js +1048 -114
- package/dist/lib/wire.d.ts +12 -0
- package/dist/lib/wire.js +21 -0
- package/dist/lib/workspace-admission.d.ts +25 -17
- package/dist/lib/workspace-admission.js +95 -20
- package/dist/lib/workspace-identity.d.ts +20 -0
- package/dist/lib/workspace-identity.js +103 -0
- package/dist/lib/workspace-retirement.d.ts +43 -8
- package/dist/lib/workspace-retirement.js +148 -52
- package/dist/package.json +6 -6
- package/dist/skills/generated-control-surface/SKILL.md +3 -3
- package/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
- package/docs/README.md +1 -1
- package/docs/activity.md +1 -1
- package/docs/architecture.md +156 -36
- package/docs/callback-namespaces.md +1 -1
- package/docs/delivery.md +1 -1
- package/docs/generative-apps.md +2 -2
- package/docs/inbound.md +1 -1
- package/docs/multi-instance-bus.md +125 -19
- package/docs/public-api.md +3 -1
- package/docs/ui-style.md +23 -9
- package/docs/updates.md +1 -1
- package/lib/activity-verbosity.ts +5 -9
- package/lib/bindings.ts +7 -0
- package/lib/bus-api.ts +9 -4
- package/lib/bus-follower.ts +243 -224
- package/lib/bus-leader.ts +178 -31
- package/lib/bus-transport.ts +30 -8
- package/lib/bus.ts +110 -334
- package/lib/channel-posts.ts +29 -29
- package/lib/command-templates.ts +5 -5
- package/lib/commands.ts +15 -15
- package/lib/config.ts +12 -17
- package/lib/delivery.ts +2 -8
- package/lib/extension.ts +261 -72
- package/lib/generative-apps.ts +0 -22
- package/lib/inbound.ts +2 -2
- package/lib/journal.ts +1250 -325
- package/lib/keyboard.ts +2 -2
- package/lib/lifecycle.ts +1 -1
- package/lib/locks.ts +402 -23
- package/lib/logging.ts +151 -101
- package/lib/media.ts +51 -9
- package/lib/menu-model.ts +8 -8
- package/lib/menu-settings.ts +18 -18
- package/lib/menu-status.ts +11 -0
- package/lib/menu-thinking.ts +2 -2
- package/lib/menu.ts +5 -1
- package/lib/outbound-attachments.ts +1 -1
- package/lib/outbound-buttons.ts +1 -10
- package/lib/outbound-markup.ts +5 -24
- package/lib/outbound.ts +5 -5
- package/lib/paths.ts +177 -17
- package/lib/pi.ts +5 -2
- package/lib/polling.ts +5 -5
- package/lib/preview.ts +3 -3
- package/lib/prompt-templates.ts +1 -1
- package/lib/queue.ts +93 -12
- package/lib/recovery.ts +104 -44
- package/lib/replies.ts +1 -1
- package/lib/routing.ts +2010 -375
- package/lib/sections.ts +1 -1
- package/lib/status.ts +156 -36
- package/lib/sync.ts +4 -4
- package/lib/telegram-api.ts +53 -33
- package/lib/thread-cleanup-manager.ts +24 -21
- package/lib/thread-naming.ts +267 -9
- package/lib/thread-reconciler.ts +3 -1
- package/lib/threads.ts +1736 -488
- package/lib/turns.ts +1 -1
- package/lib/updates.ts +1094 -151
- package/lib/wire.ts +28 -0
- package/lib/workspace-admission.ts +97 -47
- package/lib/workspace-identity.ts +147 -0
- package/lib/workspace-retirement.ts +188 -89
- package/package.json +6 -6
- package/scripts/audit-exports.mjs +100 -0
- package/scripts/check-downgrade.mjs +80 -43
- package/skills/generated-control-surface/SKILL.md +3 -3
- package/skills/telegram-bridge/references/diagnosis.md +3 -3
package/AGENTS.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
- `Mobile companion boundary`: Telegram extends a running Pi session; it is not a remote terminal, PTY supervisor, process launcher, session browser, or replacement TUI. Never emulate Pi navigation through private internals, ANSI/TTY injection, or a shadow `pi` process.
|
|
6
6
|
- `Runtime safety`: Prefer explicit, fenced, recoverable behavior over shortcuts that can desynchronize Telegram transport, durable admission, local queue state, or Pi lifecycle state.
|
|
7
|
+
- `Pi baseline`: Require Pi ≥1.0.0 across coding-agent, agent-core and AI peers; keep locks and public API compatibility tests aligned without weakening live acceptance gates.
|
|
7
8
|
- `Pi-native extensibility`: Add capabilities through stable Pi and pi-telegram contracts. Do not fork polling, transport, menu ownership, or package-private runtime internals.
|
|
8
9
|
- `Bidirectional binding`: Treat Pi instance ↔ Telegram thread and bot ↔ client state as two-way relationships. Create, observe, repair, and reflect bindings on both surfaces.
|
|
9
10
|
- `Progressive enhancement`: Use richer Telegram/Pi capability when proven available and retain a useful fail-closed fallback when it is not.
|
|
@@ -35,6 +36,7 @@ Keep each fact in one authoritative layer:
|
|
|
35
36
|
- [`docs/public-api.md`](./docs/public-api.md): Canonical public commands, config, markup, package entrypoints, and compatibility contract.
|
|
36
37
|
- [`docs/multi-instance-bus.md`](./docs/multi-instance-bus.md): Canonical Threaded Mode, leader/follower, binding, election, and transport protocol.
|
|
37
38
|
- Other `/docs` files own their named subsystem contracts; keep them reachable from `docs/README.md`.
|
|
39
|
+
- `External-tracker agnosticism`: Only `CHANGELOG.md` may reference external issues or pull requests (to say what a release closed). Source, tests, docs, backlog, skills and `AGENTS.md` are self-sufficient and name no issue/PR numbers or tracker URLs; package metadata such as the npm `bugs` URL is the sole exception.
|
|
38
40
|
|
|
39
41
|
## 3. Repository Topology And Local Skills
|
|
40
42
|
|
|
@@ -59,7 +61,7 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
59
61
|
|
|
60
62
|
- Cohesive domains live as flat `/lib/*.ts` modules whose local import graph is acyclic.
|
|
61
63
|
- `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.
|
|
62
|
-
-
|
|
64
|
+
- 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`.
|
|
63
65
|
- `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.
|
|
64
66
|
- `pi` owns direct Pi SDK imports and concrete adapter contracts. Other domains use narrow ports; domains that register Pi hooks/tools/commands consume contracts through that adapter.
|
|
65
67
|
- Do not introduce shared buckets such as `lib/constants.ts`, `lib/types.ts`, `lib/globals.ts`, or broad global-augmentation modules. Keep state, constants, registry keys, and concrete transport shapes with their domain owner.
|
|
@@ -71,11 +73,11 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
71
73
|
- 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.
|
|
72
74
|
- 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).
|
|
73
75
|
- 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.
|
|
74
|
-
- `tmp/telegram
|
|
76
|
+
- 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.
|
|
75
77
|
- 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.
|
|
76
|
-
- 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.
|
|
78
|
+
- 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.
|
|
77
79
|
- 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` and capability-gated restore-only `follower.restoreWorkspace` 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.
|
|
78
|
-
- Long-lived timers, pollers, watchers, receivers, heartbeats, background delivery, and deferred dispatch are session-bound. Replacement stops stale activity and makes late work inert; teardown must recheck the captured session generation even when context identity is reused. Same-process handoff may preserve exact profile/target identity but never stale Pi context or cross-profile authority. Follower heartbeat response deadlines align with the eight-second leader stale-liveness window rather than the generic one-second local-RPC default; an ordinary multi-second Pi/TUI event-loop stall is not registration loss. Participating source observations must hold a reference for the actual read, including pending-mutation/count queries against a stopped worker's retained source; a scoped observation never restores receipt readiness or execution authority. A donor cancellation resumed after remote handoff awaits must also hold a source reference; only the existing exact journal CAS may cancel, never undo accepted recipient custody. Stable source keys do not certify captured callable lifetimes: snapshot prepared worker capabilities at construction and replace them on source-handle renewal without replaying unsettled input. Aborting a durable update generation does not release that `update_id`: replacement replay waits for its actual handler settlement, and effectful handlers use the shared execution fence immediately before commit and after awaited delegation. Admission also rechecks that fence after the default handler returns, before its outcome can settle custody; a stopped handler's ordinary return is not completion authority. Internal clones explicitly carry the hidden fence; reroute forwarding, thread-store mutation, cleanup, and Bot API boundaries retain the originating generation.
|
|
80
|
+
- Long-lived timers, pollers, watchers, receivers, heartbeats, background delivery, and deferred dispatch are session-bound. Shutdown cancels/drains diagnostics publications and fences deferred status reads before they can acquire guards or repair files. Replacement stops stale activity and makes late work inert; teardown must recheck the captured session generation even when context identity is reused. Same-process handoff may preserve exact profile/target identity but never stale Pi context or cross-profile authority. Follower heartbeat response deadlines align with the eight-second leader stale-liveness window rather than the generic one-second local-RPC default; an ordinary multi-second Pi/TUI event-loop stall is not registration loss. Participating source observations must hold a reference for the actual read, including pending-mutation/count queries against a stopped worker's retained source; a scoped observation never restores receipt readiness or execution authority. A donor cancellation resumed after remote handoff awaits must also hold a source reference; only the existing exact journal CAS may cancel, never undo accepted recipient custody. Stable source keys do not certify captured callable lifetimes: snapshot prepared worker capabilities at construction and replace them on source-handle renewal without replaying unsettled input. Aborting a durable update generation does not release that `update_id`: replacement replay waits for its actual handler settlement, and effectful handlers use the shared execution fence immediately before commit and after awaited delegation. Admission also rechecks that fence after the default handler returns, before its outcome can settle custody; a stopped handler's ordinary return is not completion authority. Internal clones explicitly carry the hidden fence; reroute forwarding, thread-store mutation, cleanup, and Bot API boundaries retain the originating generation.
|
|
79
81
|
- Runtime state is event-driven reconciliation of local assumptions against Telegram signals, not a complete bot read-model and not permission to query Telegram on every action. Destructive thread cleanup goes through `thread-reconciler` with current proof and leader fencing. Fresh Workspace Thread creation derives its initial Bot API title from the active display mode before issuance; the stable generated `threadName` remains separate from the acknowledged `displayTitle`.
|
|
80
82
|
|
|
81
83
|
### 4.3 Durable Admission And Settlement
|
|
@@ -84,26 +86,27 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
84
86
|
- 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.
|
|
85
87
|
- 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 scope spans intent publication, target mutation, persistence, and transport release. 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.
|
|
86
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, the exact Workspace claim owns the letter: a mismatched retained target record is repaired to that claim before binding commit, while any unrelated binding that owns the stale letter remains untouched. 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.
|
|
87
|
-
- 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.
|
|
89
|
+
- 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`.
|
|
88
90
|
- 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.
|
|
89
91
|
- 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.
|
|
90
92
|
- Startup and elapsed time are not owner-death proof; queued authority has no time lease. Dead-owner cleanup groups the complete receipt and transactionally rechecks pid liveness plus process-birth identity: only an absent PID or mismatched stable Linux/macOS birth proof discards all session-owned sources without replay; a matching proof is `alive`, while Windows or inaccessible birth metadata is `unverifiable`, and both non-dead outcomes keep authority queued. Under actual `A`–`Z` allocation pressure, retirement may invoke that journal-owned CAS only for a current inactive binding after complete strict source inspection, no local work/live owner/delivery authority, whole unoffered receipt groups, and a preflight proving every grouped owner dead. It must then recapture all protection before preparing deletion; partial progress never grants deletion authority. The live-transfer contract is authenticated offer → exact-generation bounded payload staging → recipient CAS acceptance → exact receipt-and-owner ACK → donor removal → recipient readiness. The offer freezes donor settlement/recovery; controls rebuild local closures; negative/mismatched pre-acceptance ACK cancels only an unaccepted offer and retains donor work; a lost post-acceptance ACK cannot cancel recipient authority and leaves donor memory frozen for explicit reconciliation.
|
|
91
|
-
- Execution failures persist bounded diagnostics and attempt state as `retry-wait`, except that an exact Telegram HTTP 400 stale/deleted-thread API failure with a proven `{chatId, threadId}` terminally settles the currently executing source after best-effort shared binding invalidation. Automatic retry continues indefinitely with exponential `1s → 2s → 4s → 8s → 16s → 32s → 60s` delay capped at 60 seconds; later independent updates continue draining, durable authority is never silently discarded, and legacy `failed` entries resume automatically at startup. Snapshot-plus-segment journals compact only after 256 unapplied revisions or 4 MiB; snapshot-first cleanup tolerates redundant segments, and empty authority may atomically rebind bot/profile identity. Missing snapshots left by the retired broad temp cleanup rebuild only from a complete provably empty segment chain, while revisionless snapshots may recover from a validated later segment predecessor; otherwise the
|
|
93
|
+
- Execution failures persist bounded diagnostics and attempt state as `retry-wait`, except that an exact Telegram HTTP 400 stale/deleted-thread API failure with a proven `{chatId, threadId}` terminally settles the currently executing source after best-effort shared binding invalidation. Automatic retry continues indefinitely with exponential `1s → 2s → 4s → 8s → 16s → 32s → 60s` delay capped at 60 seconds; later independent updates continue draining, durable authority is never silently discarded, and legacy `failed` entries resume automatically at startup. Snapshot-plus-segment journals compact only after 256 unapplied revisions or 4 MiB; snapshot-first cleanup tolerates redundant segments, and empty authority may atomically rebind bot/profile identity. Missing snapshots left by the retired broad temp cleanup rebuild only from a complete provably empty segment chain, while revisionless snapshots may recover from a validated later segment predecessor; otherwise the transaction-locked reset deletes damaged segments before publishing a fresh journal, with accepted input loss and informational evidence, not a quarantine copy. Unsupported versions remain untouched; strict protection reads never reset. Startup best-effort removes obsolete current-runtime root/session `recovery/` folders without touching pre-0.52.0 storage.
|
|
92
94
|
- Business connection chats are a separate namespace even when their chat/message IDs match bot-chat IDs. Default DM routing must never infer private-queue deletion intent from `deleted_business_messages`; raw companion handlers remain separate owners.
|
|
95
|
+
- Clock-bearing chooser sources survive cold spending until their fixed deadline; new-world forgetting preserves their positively unbound temporary frame without restoring routing intent. Expiry is a donor discard, not recipient cancellation or source execution completion. Confirmed expiry releases worker custody before UI/cleanup awaits; metadata retries retain no prompt bodies and cannot repeat an issued delete.
|
|
93
96
|
- An unresolved reaction delays only the exact governed queue item identified by chat/message sources, not unrelated queue work. An explicit immutable `preApprovalExcluded: true` cannot govern accepted work, even after re-pairing; missing or false exclusion evidence must not bypass the dependency guard. Prepared v3 drain may dispose of excluded pending input only through journal-owned `removeExcluded`, never generic raw completion; mixed requests containing non-excluded input must fail atomically. Queue receipt publication follows in-memory append and precedes dispatch request; receipt-bearing turns remain queued until every exact source commits.
|
|
94
97
|
- The detailed implementation and release gates live in [`docs/architecture.md`](./docs/architecture.md), [`docs/multi-instance-bus.md`](./docs/multi-instance-bus.md), and [`BACKLOG.md`](./BACKLOG.md).
|
|
95
98
|
|
|
96
99
|
### 4.4 Queue, Delivery, And User Surfaces
|
|
97
100
|
|
|
98
|
-
- Queue lane/kind admission is explicit. 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 `+N` suffix is a yellow count of executable prompts still waiting, excludes the dispatched head immediately, and never counts current agent work from any source. Each prompt is one object with one active lane and no reserved return slot. Normal and Priority are separate FIFO lanes: crossing lanes removes it from the source and appends it at the destination tail, while Keep/Skip and same-category emoji changes preserve lane position.
|
|
99
|
-
- `/stop`, `/abort`, `/next`, and `/continue` respectively reset+abort, abort while preserving queue, force the next turn, and enqueue a control-lane 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.
|
|
101
|
+
- Queue lane/kind admission is explicit. 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 `+N` suffix is a yellow count of executable prompts still waiting, excludes the dispatched head immediately, and never counts current agent work from any source. Each prompt is one object with one active lane and no reserved return slot. Normal and Priority are separate FIFO lanes: crossing lanes removes it from the source and appends it at the destination tail, while Keep/Skip and same-category emoji changes preserve lane position. For ordinary Normal/Priority prompts, complete reaction sets independently derive Priority from recognized positive emoji and Skip from recognized negative emoji; both 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.
|
|
102
|
+
- `/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.
|
|
100
103
|
- 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.
|
|
101
104
|
- 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.
|
|
102
105
|
- 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.
|
|
103
106
|
- `preview` owns streaming lifecycle only, not assistant rendering. Finalization waits for active preview flushes and must not issue pre/post-final draft-clear calls that create transient Telegram draft UI. Turns that already answer as one atomic reply (voice replies, Guest Mode queries) never stream previews.
|
|
104
107
|
- 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.
|
|
105
108
|
- 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.
|
|
106
|
-
- 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
|
|
109
|
+
- 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.
|
|
107
110
|
|
|
108
111
|
## 5. Domain Ownership Index
|
|
109
112
|
|
|
@@ -130,11 +133,11 @@ The detailed map is canonical in [`docs/architecture.md`](./docs/architecture.md
|
|
|
130
133
|
## 7. Engineering Conventions
|
|
131
134
|
|
|
132
135
|
- Keep comments and user-facing docs in English. Comment non-obvious rationale/contracts, not names or standard idioms.
|
|
133
|
-
- Name flat modules by bare domain (`queue.ts`, `queue.test.ts`); `telegram-api.ts` is the intentional transport exception. Tests primarily protect their mirrored module; shared fixtures require real cross-suite reuse.
|
|
136
|
+
- Name flat modules by bare domain (`queue.ts`, `queue.test.ts`); `telegram-api.ts` is the intentional transport exception. New modules extending Thread behavior use the `thread-` prefix, as in `thread-cleanup-manager` and `thread-reconciler`. Tests primarily protect their mirrored module; shared fixtures require real cross-suite reuse.
|
|
134
137
|
- Keep interfaces consistent with their owning exported contract. Use local structural `*Like`/view types only for deliberate narrow projections, not duplicate source-of-truth models.
|
|
135
|
-
- Remove dead code immediately. Reachability from composition roots, public exports, tests, registered surfaces, and documented APIs—not recent usefulness—determines whether code is live.
|
|
138
|
+
- Remove dead code immediately. Reachability from composition roots, public exports, tests, registered surfaces, and documented APIs—not recent usefulness—determines whether code is live. `node scripts/audit-exports.mjs` provides a read-only TypeScript-symbol inventory including aliases, JS consumers and public `api/` reexports (including namespaces); `namespaceEscapes` flags whole-module alias usage. No-external-reference candidates are not deletion proof. Review generated text, dynamic namespace access and public declaration reachability before removing or hiding a symbol.
|
|
136
139
|
- Treat every meaningful `lib/extension.ts` edit as a composition-pressure check, but keep one-off live adapter wiring there when extraction would only hide cross-domain state.
|
|
137
|
-
- Follow [`docs/ui-style.md`](./docs/ui-style.md) for interface copy, emoji, buttons, menus, and dialogs. Update the registry before assigning a new UI emoji meaning. Standalone notices use one fully bold emoji-led sentence with a terminal period; menu or chooser headings use the same hierarchy with a terminal colon. Material names may add nested italic emphasis without breaking the outer bold span.
|
|
140
|
+
- Follow [`docs/ui-style.md`](./docs/ui-style.md) for interface copy, emoji, buttons, menus, and dialogs. Update the registry before assigning a new UI emoji meaning. Standalone notices use one fully bold emoji-led sentence with a terminal period; menu or chooser headings use the same hierarchy with a terminal colon. Material names may add nested italic emphasis without breaking the outer bold span. Typed callback toasts preserve equivalent emoji-led plain text without a terminal sentence period; question/exclamation marks and ellipses remain. In-chat notices and explicit raw API payloads keep their punctuation.
|
|
138
141
|
- Markdown lists never contain blank lines between adjacent items; list items are not paragraphs. Use blank lines only between paragraphs or independently separated blocks. Markdown tables use compact source formatting with `---` separator cells and one surrounding space per cell. Preserve vendored references unchanged.
|
|
139
142
|
- Treat Windows filesystem, named-pipe, lock, heartbeat, and atomic-rename reports as high-signal evidence; reduce them to regressions or explicit platform caveats.
|
|
140
143
|
- Route significant runtime failures through the redacted recent-event recorder. Keep the compact TUI status at generic `error`; details belong in diagnostics.
|
package/BACKLOG.md
CHANGED
|
@@ -2,12 +2,19 @@
|
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- [ ]
|
|
13
|
-
- [ ]
|
|
5
|
+
## Routing Lifetime External Report
|
|
6
|
+
|
|
7
|
+
- [ ] Report the patch's precise shipped routing-lifetime scope externally only after separate authorization and verified release evidence. Do not claim a universal TTL or cleanup of historical originals lacking saved chooser clocks; native Windows destructive cleanup remains gated on strict evidence.
|
|
8
|
+
- Evidence boundary: The operator reports passing TTL live acceptance. New continuation-reaction and callback-copy behavior has native regression evidence, not a separate live-client claim. Neither establishes native Windows Restore/destructive-cleanup support. Completed implementation is recorded in `CHANGELOG.md`; durable contracts remain in `/docs`.
|
|
9
|
+
|
|
10
|
+
## Windows Reliability
|
|
11
|
+
|
|
12
|
+
- [ ] Strengthen native Windows reliability beyond the 0.52.0 ordinary-queue fix. Audit filesystem paths, named pipes, locking, owner fencing, heartbeat and atomic publication; cover interrupted writes, damaged state, ownership changes and lost-ACK retries with focused regressions.
|
|
13
|
+
- [ ] Investigate Windows-compatible strict journal evidence for Restore and cleanup without weakening source identity, scoped ACK or fail-closed protections. Inventory witnesses currently skipped on Windows and enable them only when equivalent native evidence exists.
|
|
14
|
+
- Acceptance: Local synthetic tests distinguish ordinary delivery from strict proofs; native Windows CI exercises supported paths and refusal boundaries. Linux emulation or a green matrix with skipped Restore witnesses does not establish Windows Restore acceptance.
|
|
15
|
+
|
|
16
|
+
## Consistent Callback Notification Copy Across Other Extensions
|
|
17
|
+
|
|
18
|
+
- [ ] Inventory Telegram button callback acknowledgements across all local extensions, including companion callbacks. Standardize the transient non-interactive notification bubbles (callback answers/toasts) to concise, consistent text with no trailing period.
|
|
19
|
+
- [ ] Distinguish transient callback bubbles from in-chat notices, chooser headings and blocking alerts; reconcile the owning UI-copy contracts before implementation rather than stripping punctuation from every message.
|
|
20
|
+
- Acceptance: All participating extensions follow the same callback-notification format, with focused copy checks for success, no-op and rejection outcomes; in-chat notice formatting and callback behavior remain unchanged.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.52.1: Finite chooser lifetime, continue cancellation and callback copy
|
|
8
|
+
|
|
9
|
+
- `Chooser expiry`: Fixed 60-minute clocks end unselected/unacknowledged donor routing without archives or replay. Fully resolved disposable tabs use fenced one-shot cleanup; accepted queues, independent work and restored Threads survive. Confirmed expiry clears worker custody even if cleanup fails; metadata retries use body-free proof and never repeat an issued delete. Cold startup preserves deadlines, and queue admission retains selected lifetime metadata.
|
|
10
|
+
- `Continue cancellation`: Negative reactions to an original standalone `/continue` now settle and remove its exact waiting continuation, even while other work runs. Other prompts, the Pi-owned dispatched head, active work and synthetic model continuations remain untouched. Positive reactions preserve the control lane; failed receipt settlement keeps the item waiting, and duplicate reactions cannot cancel twice.
|
|
11
|
+
- `Callback copy`: Shared typed callback toasts omit a terminal sentence period on direct and follower delivery. Questions, exclamations, ellipses, in-chat notices and explicit raw API payloads keep their punctuation.
|
|
12
|
+
|
|
13
|
+
## 0.52.0: Workspace Restore, routing lifetime and resilient runtime storage
|
|
14
|
+
|
|
15
|
+
- `Restore foundation`: One snapshot owns binding, slot, targets and one-shot grants. Publication and late creation receipts share a transaction; unnamed owners round-trip. Unknown or contradictory creation evidence blocks new Restore; absence and expiry never prove availability. Loading validates receipts before replacing working state, preserving disk, memory and source protection on conflict. Recovered targets survive expiry; conflicting creation stays blocked.
|
|
16
|
+
- `Follower routing`: Readiness binds the acknowledged session ID; stale registrations refuse. Originals settle only after exact durable ACK; chooser callbacks keep their publisher. Forward issuance is never repeated, cancelled or restored after restart. A follower acknowledges a repeated delivery within 24 h/4,096 deliveries without re-running it, even after completion. Full-slot lost apply/inspect replies resume by fresh inspection, preserving siblings and accepted work.
|
|
17
|
+
- `Restore composition`: `workspace-restore-v1` replaces legacy Restore/replacement IPC. Acceptance precedes scoped disposal; cold forwarded, command and queued ACKs recover without replay. Confirmed whole-receipt origin supports subset scopes without ordinary sibling markers. Lost replies read proof, never dispatch; issued disposal closes readiness. Queued admission cannot grant cleanup. Scoped ACKs use bounded immutable retention. Missing proof and unknown cleanup stay protected.
|
|
18
|
+
- `Restore protection`: Restart forgets unfinished Restore without rollback; unsupported historical originals stay retain-only, including on bound Threads. Cleanup unions predecessor/current references; absent or shared custody blocks it. Interrupted close, pre-disposal interruption and lost-ACK successor readback never repeat apply, delivery or disposal; missing proof, context or canonical identity holds settlement. Without POSIX no-follow journal handles (Windows), Restore fails closed.
|
|
19
|
+
- `Pi lifecycle and UI`: Requires Pi ≥1.0.0. Compaction observer expiry preserves late native success/failure/cancellation notices and Activity correlation. Thinking stays in its chooser; Main returns to root. `/next` keeps HTML/order/target. Video `.webm` / `video/webm` and animated `.tgs` / `application/x-tgsticker` stickers never enter image payloads; static WebP is unchanged, formats follow Bot API flags ([#311](https://github.com/llblab/pi-telegram/issues/311)).
|
|
20
|
+
- `Storage`: Runtime state moves to `tmp/pi-telegram` with only shared `state.json` and `logs.jsonl` root files, under the canonical agent directory even if symlinked; released `tmp/telegram` is never read, migrated or deleted. `/telegram-connect` replaces a damaged shared state with a fresh empty one and continues, forgetting every profile's runtime continuity; access errors still refuse. Owner/epoch and follower context/generation races block stale writes; a fresh connect reuses the remote binding.
|
|
21
|
+
- `Unbound routing lifetime`: Confirmed prompt choosers get one durable 60-minute choice deadline; refresh/restart never renew it. Valid selection within the hour freezes expiry before effects. Unselected originals are retained privately before journal removal, even without a visible tab. TTL never deletes Threads, stops Pi or cancels accepted/uncertain work ([#307](https://github.com/llblab/pi-telegram/issues/307)).
|
|
22
|
+
- `Retained input cancellation`: Commands and prompts share Reroute/Restore/Cancel. Titles: `/command` or `New chat`; Back restores original text, root-only Cancel retains originals. Fresh owner-authenticated implicit Telegram tabs use the same temporary lifecycle; titles and historical signals grant nothing. Last Cancel tries one delete after 1 quiet second; positive ACK is required, uncertainty never retries ([#307](https://github.com/llblab/pi-telegram/issues/307)).
|
|
23
|
+
|
|
24
|
+
.
|
|
25
|
+
|
|
7
26
|
## 0.51.6: Connection resume and Workspace recovery hotfix
|
|
8
27
|
|
|
9
28
|
- `Workspace slot recovery`: Confirmed pressure-retirement deletion invalidates the exact stale active-target record before binding removal. Same-process and successor retries finish a retained `commit-ready` fence without repeating Telegram deletion, preventing exhausted A–Z slots from deadlocking on `protection-changed`. Includes [#305](https://github.com/llblab/pi-telegram/pull/305).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 llblab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ pi install git:github.com/llblab/pi-telegram
|
|
|
28
28
|
|
|
29
29
|
Installed npm/git packages expose bundled Skills through their `pi.skills` manifest, so Pi package filters and package provenance remain authoritative. A checkout auto-discovered directly under Pi's user or project `extensions` directory contributes its adjacent source Skills even when Pi selects the checkout's compiled entrypoint; the two discovery paths are mutually exclusive.
|
|
30
30
|
|
|
31
|
-
The extension requires Pi `0.
|
|
31
|
+
The extension requires Pi `1.0.0` or newer, matching the package's peer dependencies. Its Activity API uses the public `agent_settled` lifecycle event to keep retries/continuations under one activity identity and release that identity only after the run fully settles.
|
|
32
32
|
|
|
33
33
|
Pi is the primary and only officially supported host. Narrow host-neutral adapters preserve ordered prompt blocks and normalize synchronous or asynchronous legacy/generic settings services for Pi-compatible hosts, but this is best-effort compatibility rather than an OMP support guarantee. Alternate-host shims must still reproduce required Pi lifecycle semantics—especially `agent_settled`—and their maintainers own ongoing validation.
|
|
34
34
|
|
|
@@ -59,7 +59,15 @@ Paste the bot token. If `~/.pi/agent/telegram.json` already contains a saved tok
|
|
|
59
59
|
|
|
60
60
|
The connected Pi instance owns Telegram polling. Use `/telegram-connect <profile>` to activate a named profile. Each profile is a parallel bot runtime with isolated polling, diagnostics, Threaded Mode state, and local bus transport; the `default` profile keeps unsuffixed runtime paths. In classic mode each profile uses a singleton lock. When Telegram private-chat Threaded Mode is available, one live instance becomes the profile's leader and later visible Pi instances register as followers. Reopening or resuming the same Pi session restores its remembered Thread at session startup. If `/resume` immediately follows `/telegram-connect`, or the bridge is already connected, connection intent follows the switch: the destination restores its own Thread/slot or receives a new binding, never the source session's Thread. Otherwise, a distinct unbound session still requires explicit `/telegram-connect`.
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
An eligible single-text unbound Thread chooser offers **⛔️ Cancel routing**: the original stays private and is not sent to Pi; its Telegram message and Thread are not deleted. This does not cancel already selected or accepted work. When protected cancellation attempts remain, **Status → ❌ Pending cancellations** lets the owner review and finish them, including after restart. Historical unbound startup text has no main-menu entry or historical-review submenu. Eligible raw unsupported command/media/unknown historical private-Thread originals use a separate retain-only path, including on bound Threads without surviving Restore/temporary membership. Each worker generation reclassifies them after cold forgetting, blocking handler replay, generic Cancel and cold spending. Ordinary bound startup dispatch stops for these originals; same-kind live arrivals and owner/custody paths remain unchanged. Old historical-review buttons are refused without routing or abandoning the original; removal of the UI does not cancel previously accepted work.
|
|
63
|
+
|
|
64
|
+
Eligible routing choosers and their donor sources share a **fixed 60-minute lifetime** from the first acknowledged publication, preserved across refresh/restart. Expiry drops unselected or unacknowledged routing attempts without privately archiving prompt bodies; nondelivery is accepted, so resend manually if needed. Old controls and late replies cannot replay the source. An expired disposable unbound tab is removed after all its owned groups resolve and fresh protection checks pass. Accepted queues, independent work and restored bound Threads remain intact. Confirmed expiry releases worker custody even if tab metadata fails; retries use only body-free tab/source proof, and uncertain deletion is never repeated. Explicit Cancel retains its separate private-original policy. Historical originals without a saved clock and unconfirmed publication receive no invented deadline. Local candidate checks do not certify live deletion.
|
|
65
|
+
|
|
66
|
+
`/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).
|
|
67
|
+
|
|
68
|
+
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.
|
|
69
|
+
|
|
70
|
+
Journal recovery is a separate existing loss policy, not shared-envelope recovery. A snapshot removed by older broad temp cleanup is rebuilt when its complete segment history proves an empty result, while a revisionless snapshot is repaired from the first surviving segment's exact predecessor when the reconstructed tail validates. Otherwise the damaged snapshot and segments are deleted (their inputs are lost), a fresh journal is published, and startup continues with an informational diagnostic. Unsupported journal versions block recovery without rewriting or quarantining retained files; use a compatible runtime rather than deleting journals. Saved `telegram.json` configuration and runtime diagnostics are outside that journal reset. Startup best-effort removes obsolete current-runtime root/session `recovery/` folders, so those directories are not a preservation location.
|
|
63
71
|
|
|
64
72
|
Persistent competing `getUpdates` clients cause a bounded transport stand-down rather than endless retries. Accepted local work remains queued/executable, but Telegram delivery stops. Inspect `/telegram-status --debug`, stop the competing client, then reconnect. See [Runtime Ownership](./docs/architecture.md#runtime-ownership).
|
|
65
73
|
|
|
@@ -139,7 +147,7 @@ Enable the optional capabilities the bridge needs in the [@BotFather](https://t.
|
|
|
139
147
|
| 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. |
|
|
140
148
|
| Callback routing | Route known callbacks to the owner extension and unknown callbacks back into Pi. | Companion extensions can build UI without polling Telegram themselves. |
|
|
141
149
|
| 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. |
|
|
142
|
-
| Reroute and restore | Give unknown and command-created temporary threads explicit forward and replace/restore choices. | Forward removes the temporary tab; restore rebinds it and
|
|
150
|
+
| Reroute and restore | Give unknown and command-created temporary threads explicit forward 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. |
|
|
143
151
|
| 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. |
|
|
144
152
|
| 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. |
|
|
145
153
|
| Safety and ownership | Pair one owner, lock transport, scope targets, and reject fake terminal behavior. | Remote access remains explicit, bounded, and understandable. |
|
|
@@ -206,11 +214,12 @@ Queue policy:
|
|
|
206
214
|
- Priority and Normal are separate FIFO lanes; Priority dispatches first.
|
|
207
215
|
- Moving `Normal → Priority` removes the prompt from Normal and places it at the Priority tail. Moving `Priority → Normal` removes it from Priority and places it at the Normal tail; no former position is restored.
|
|
208
216
|
- Keep/Skip never changes lane position. Skip preserves durable authority while waiting so Keep remains reversible, then settles that authority and drops the prompt without a model turn when dispatch reaches it. Skipped prompts stay visible at their physical queue position with a struck-through ordinal, but are excluded immediately from the executable queue count shown in both the Pi status bar and Telegram main menu. Graceful session shutdown discards all remaining queued authority, so a new session starts empty.
|
|
209
|
-
-
|
|
217
|
+
- For ordinary prompts, reactions control two independent dimensions; changing one category preserves the other:
|
|
210
218
|
- `Positive`: `👍`, `⚡️`, `❤️`, `🕊`, `🔥` — controls Priority.
|
|
211
219
|
- `Negative`: `👎`, `👻`, `💔`, `💩`, `🗑` — controls Skip.
|
|
212
220
|
- Priority and Skip can coexist—for example `👍 + 💩`. Skip wins at dispatch, regardless of which negative emoji is selected.
|
|
213
221
|
- Menu selectors and reactions share queue state, but the bot cannot remove a user's reaction; Keep may clear internal Skip while the user's emoji remains visible until they remove it.
|
|
222
|
+
- A negative reaction to your original standalone `/continue` immediately cancels its exact waiting continuation, even while other work is active. This is irreversible cancellation, not reversible Skip. Positive reactions do not change its control lane; already-dispatched or running work and synthetic model-switch continuations are untouched.
|
|
214
223
|
|
|
215
224
|
The detailed contract lives in [Priority, Reactions, Keep, and Skip](./docs/architecture.md#priority-reactions-keep-and-skip). If Pi automatically retries a transient provider failure, the active Telegram turn stays bound until the successful reply arrives or Pi confirms that the run has settled.
|
|
216
225
|
|
|
@@ -220,7 +229,7 @@ Rich Markdown is the default model-answer membrane. Complete assistant and guest
|
|
|
220
229
|
|
|
221
230
|
### Files And Artifacts
|
|
222
231
|
|
|
223
|
-
Inbound files land under `<agent-dir>/tmp/telegram` and default to a 50 MiB limit. `telegram_attach` is the canonical outbound file path. During Telegram-originated turns it attaches to the active reply; during explicit local/TUI delivery it can send to the paired/default chat or routed Threaded Mode target.
|
|
232
|
+
Inbound files land under `<agent-dir>/tmp/pi-telegram` and default to a 50 MiB limit. `telegram_attach` is the canonical outbound file path. During Telegram-originated turns it attaches to the active reply; during explicit local/TUI delivery it can send to the paired/default chat or routed Threaded Mode target.
|
|
224
233
|
|
|
225
234
|
### Voice And Media
|
|
226
235
|
|
|
@@ -263,7 +272,7 @@ Most controls live in Pi commands or the Telegram menu. Environment variables re
|
|
|
263
272
|
| Inbound file limit | `PI_TELEGRAM_INBOUND_FILE_MAX_BYTES`, `TELEGRAM_MAX_FILE_SIZE_BYTES` |
|
|
264
273
|
| Outbound attachment limit | `PI_TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES`, `TELEGRAM_MAX_ATTACHMENT_SIZE_BYTES` |
|
|
265
274
|
|
|
266
|
-
Defaults are chosen for ordinary private-bot use: saved config in `~/.pi/agent`, inbound temp files in `~/.pi/agent/tmp/telegram`, `assistant: { rendering: "rich", draftPreviews: true, activity: "verbose", timeInjection: "interval" }` for assistant output and activity, and native Telegram active status for long-running turns.
|
|
275
|
+
Defaults are chosen for ordinary private-bot use: saved config in `~/.pi/agent`, inbound temp files in `~/.pi/agent/tmp/pi-telegram`, `assistant: { rendering: "rich", draftPreviews: true, activity: "verbose", timeInjection: "interval" }` for assistant output and activity, and native Telegram active status for long-running turns.
|
|
267
276
|
|
|
268
277
|
## Extension Platform
|
|
269
278
|
|
|
@@ -285,6 +294,8 @@ Stable public entrypoints are documented in [Public API](./docs/public-api.md),
|
|
|
285
294
|
|
|
286
295
|
Durable inbound admission is a **process-crash recovery** guarantee. Atomic private-file replacement preserves acknowledged journal authority and its journal-owned `acceptedThroughUpdateId` polling cursor across ordinary process exit, crash, kill, and replacement, but the extension does not flush files or parent directories for host/kernel/filesystem/device/power-loss durability. `telegram.json` contains configuration only. Keep `~/.pi/agent` on appropriately managed storage and backups if that stronger operational guarantee is required. Before downgrading below `0.37.0`, run `node scripts/check-downgrade.mjs`; any retained cursor-schema journal blocks downgrade because an older runtime could repoll admitted updates. See [Durable Admission And Recovery](./docs/architecture.md#durable-admission-and-recovery).
|
|
287
296
|
|
|
297
|
+
Runtime files use `tmp/pi-telegram` with only `state.json` and `logs.jsonl` at its root; pre-0.52.0 `tmp/telegram` is left untouched and never migrated. The transport section of `state.json` names a polling journal hosted in a session folder; successor leaders continue it rather than moving its cursor. Follower journals are session-owned. Unbound session families are disposable under the approved sweep policy; in-process `/new` adopts eligible pending input, while cold startup does not. See [Session-Owned Journal Storage](./docs/multi-instance-bus.md#session-owned-journal-storage) for compatibility fallbacks, loss boundaries and pending acceptance.
|
|
298
|
+
|
|
288
299
|
`pi-telegram` intentionally does not:
|
|
289
300
|
|
|
290
301
|
- Spawn hidden Pi follower processes.
|
|
@@ -5,13 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { TelegramActivityEvent, TelegramActivityPublicationRuntime } from "./activity.ts";
|
|
7
7
|
import type { TelegramEditMessageTextBody, TelegramInputRichMessage, TelegramSendMessageBody, TelegramSendRichMessageBody, TelegramSentMessage } from "./telegram-api.ts";
|
|
8
|
-
import type
|
|
9
|
-
export declare const TELEGRAM_ACTIVITY_DETAIL_MAX_CHARS = 1200;
|
|
10
|
-
export declare const TELEGRAM_ACTIVITY_MESSAGE_MAX_CHARS = 3900;
|
|
8
|
+
import { type TelegramTarget } from "./target.ts";
|
|
11
9
|
export declare const TELEGRAM_ACTIVITY_MESSAGE_MAX_TOOLS = 6;
|
|
12
|
-
export declare const TELEGRAM_REASONING_MESSAGE_MAX_FRAMES = 24;
|
|
13
10
|
export declare const TELEGRAM_REASONING_BUFFER_MAX_CHARS = 1200;
|
|
14
|
-
export declare const TELEGRAM_REASONING_MIN_INTERVAL_MS = 2000;
|
|
15
11
|
export declare const TELEGRAM_TOOL_UPDATE_MAX_ENTRIES = 4;
|
|
16
12
|
interface ToolActivity {
|
|
17
13
|
id: string;
|
|
@@ -4,18 +4,16 @@
|
|
|
4
4
|
* Owns persistent bounded thinking and tool disclosures; excludes activity normalization, assistant answer rendering, and transport authority policy
|
|
5
5
|
*/
|
|
6
6
|
import { escapeHtml, renderTelegramInlineMarkdownHtml, } from "./rendering.js";
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
import { areTelegramTargetsEqual as targetEquals } from "./target.js";
|
|
8
|
+
const TELEGRAM_ACTIVITY_DETAIL_MAX_CHARS = 1_200;
|
|
9
|
+
const TELEGRAM_ACTIVITY_MESSAGE_MAX_CHARS = 3_900;
|
|
9
10
|
export const TELEGRAM_ACTIVITY_MESSAGE_MAX_TOOLS = 6;
|
|
10
|
-
|
|
11
|
+
const TELEGRAM_REASONING_MESSAGE_MAX_FRAMES = 24;
|
|
11
12
|
export const TELEGRAM_REASONING_BUFFER_MAX_CHARS = 1_200;
|
|
12
13
|
// Match native answer drafts: accumulate the opening frame for one full
|
|
13
14
|
// interval, then publish at most one updated frame per interval.
|
|
14
|
-
|
|
15
|
+
const TELEGRAM_REASONING_MIN_INTERVAL_MS = 2_000;
|
|
15
16
|
export const TELEGRAM_TOOL_UPDATE_MAX_ENTRIES = 4;
|
|
16
|
-
function targetEquals(left, right) {
|
|
17
|
-
return left.chatId === right.chatId && left.threadId === right.threadId;
|
|
18
|
-
}
|
|
19
17
|
function redactActivityText(text) {
|
|
20
18
|
return text
|
|
21
19
|
.replace(/\b\d{8,12}:[A-Za-z0-9_-]{30,}\b/g, "[REDACTED_BOT_TOKEN]")
|
package/dist/lib/bindings.d.ts
CHANGED
|
@@ -205,6 +205,10 @@ interface TelegramLifecycleBindingDeps {
|
|
|
205
205
|
publicationRuntime: TelegramBridgePublicationRuntime;
|
|
206
206
|
activityRuntime: Activity.TelegramActivityRuntime;
|
|
207
207
|
activityVerbosityRuntime?: ActivityVerbosity.TelegramActivityVerbosityRuntime;
|
|
208
|
+
diagnostics?: {
|
|
209
|
+
onSessionStart(): void;
|
|
210
|
+
onSessionShutdown(): Promise<void>;
|
|
211
|
+
};
|
|
208
212
|
assistantOutputRuntime: Pick<Activity.TelegramAssistantOutputRuntime, "start" | "beginTurn" | "hasAdmittedTelegramIntermediate" | "waitForIdle" | "stop">;
|
|
209
213
|
sessionLifecycleRuntime: Pick<Lifecycle.TelegramLifecycleRegistrationDeps, "onSessionStart" | "onSessionShutdown" | "onModelSelect">;
|
|
210
214
|
configStore: Pick<Config.TelegramConfigStore, "get" | "getOutboundHandlers" | "hasBotToken" | "load">;
|
|
@@ -250,5 +254,5 @@ interface TelegramLifecycleBindingDeps {
|
|
|
250
254
|
updateStatus: TelegramBridgeStatusUpdater;
|
|
251
255
|
recordRuntimeEvent: TelegramRuntimeEventRecorder;
|
|
252
256
|
}
|
|
253
|
-
export declare function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, 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, }: TelegramLifecycleBindingDeps): void;
|
|
257
|
+
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, }: TelegramLifecycleBindingDeps): void;
|
|
254
258
|
export {};
|
package/dist/lib/bindings.js
CHANGED
|
@@ -34,6 +34,7 @@ export function createTelegramQueueBindingRuntime(deps) {
|
|
|
34
34
|
};
|
|
35
35
|
const mutation = Queue.createTelegramQueueMutationController({
|
|
36
36
|
...deps.store,
|
|
37
|
+
hasPendingDispatch: deps.lifecycle.hasDispatchPending,
|
|
37
38
|
allocateLaneOrder: deps.queue.allocateItemOrder,
|
|
38
39
|
onItemsDiscarded(items, ctx) {
|
|
39
40
|
if (!settleDiscardedItems(items, ctx)) {
|
|
@@ -498,7 +499,7 @@ export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, pe
|
|
|
498
499
|
},
|
|
499
500
|
});
|
|
500
501
|
}
|
|
501
|
-
export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, 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, }) {
|
|
502
|
+
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, }) {
|
|
502
503
|
const agentEndResetter = Runtime.createTelegramAgentEndResetter({
|
|
503
504
|
abort,
|
|
504
505
|
typing,
|
|
@@ -744,6 +745,7 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
744
745
|
activityRuntime.recordInputSource(event.source ?? "unknown");
|
|
745
746
|
},
|
|
746
747
|
async onSessionStart(event, ctx) {
|
|
748
|
+
diagnostics?.onSessionStart();
|
|
747
749
|
cancelPendingFinalPublication();
|
|
748
750
|
previewRuntime.invalidate();
|
|
749
751
|
assistantOutputRuntime.start();
|
|
@@ -756,6 +758,7 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
756
758
|
async onSessionShutdown(event, ctx) {
|
|
757
759
|
if (!isSessionContextActive(ctx))
|
|
758
760
|
return;
|
|
761
|
+
const diagnosticsStopped = diagnostics?.onSessionShutdown();
|
|
759
762
|
shutdownGenerativeAppLiveSurfaces?.();
|
|
760
763
|
agentLifecycleHooks.clearRetainedAgentEnd();
|
|
761
764
|
activityRuntime.onSessionShutdown();
|
|
@@ -766,6 +769,9 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
766
769
|
cancelPendingFinalPublication();
|
|
767
770
|
uiPromptActive = false;
|
|
768
771
|
compactionObserver.onSessionShutdown();
|
|
772
|
+
await diagnosticsStopped;
|
|
773
|
+
if (!isSessionContextActive(ctx))
|
|
774
|
+
return;
|
|
769
775
|
if (event.reason === "quit" && disconnectOnQuit) {
|
|
770
776
|
try {
|
|
771
777
|
const automaticCleanupEnabled = (await resolveAutomaticThreadCleanupEnabled?.()) ?? true;
|
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, stripTelegramBusApiMetadata, } from "./bus.js";
|
|
7
|
-
import { buildTelegramAnswerGuestQueryBody, isTelegramMessageNotModifiedError, } from "./telegram-api.js";
|
|
7
|
+
import { buildTelegramAnswerGuestQueryBody, formatTelegramCallbackAnswerText, isTelegramMessageNotModifiedError, } from "./telegram-api.js";
|
|
8
8
|
function asRecord(value) {
|
|
9
9
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
10
10
|
? value
|
|
@@ -60,12 +60,14 @@ export function createTelegramBusAwareApiRuntime(deps) {
|
|
|
60
60
|
options,
|
|
61
61
|
]);
|
|
62
62
|
},
|
|
63
|
-
downloadFile(fileId, suggestedName) {
|
|
63
|
+
downloadFile(fileId, suggestedName, source) {
|
|
64
|
+
// The source names the file (kind-scope-message); dropping it falls back to the bare generated name.
|
|
64
65
|
return deps.ownsDirect()
|
|
65
|
-
? deps.directRuntime.downloadFile(fileId, suggestedName)
|
|
66
|
+
? deps.directRuntime.downloadFile(fileId, suggestedName, source)
|
|
66
67
|
: deps.callFollowerApi("downloadFile", [
|
|
67
68
|
fileId,
|
|
68
69
|
suggestedName,
|
|
70
|
+
...(source ? [source] : []),
|
|
69
71
|
]);
|
|
70
72
|
},
|
|
71
73
|
deleteWebhook(signal) {
|
|
@@ -199,15 +201,16 @@ export function createTelegramBusAwareApiRuntime(deps) {
|
|
|
199
201
|
]);
|
|
200
202
|
},
|
|
201
203
|
async answerCallbackQuery(callbackQueryId, text) {
|
|
204
|
+
const answer = formatTelegramCallbackAnswerText(text);
|
|
202
205
|
if (deps.ownsDirect()) {
|
|
203
|
-
await deps.directRuntime.answerCallbackQuery(callbackQueryId,
|
|
206
|
+
await deps.directRuntime.answerCallbackQuery(callbackQueryId, answer);
|
|
204
207
|
return;
|
|
205
208
|
}
|
|
206
209
|
await deps.callFollowerApi("call", [
|
|
207
210
|
"answerCallbackQuery",
|
|
208
211
|
{
|
|
209
212
|
callback_query_id: callbackQueryId,
|
|
210
|
-
...(
|
|
213
|
+
...(answer !== undefined ? { text: answer } : {}),
|
|
211
214
|
},
|
|
212
215
|
]);
|
|
213
216
|
},
|