@llblab/pi-telegram 0.22.1 → 0.23.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 +24 -13
- package/BACKLOG.md +1 -51
- package/CHANGELOG.md +41 -19
- package/README.md +3 -1
- package/docs/README.md +1 -1
- package/docs/activity.md +8 -0
- package/docs/architecture.md +16 -15
- package/docs/locks.md +21 -15
- package/docs/multi-instance-bus.md +17 -16
- package/docs/outbound.md +16 -0
- package/docs/public-api.md +9 -4
- package/index.ts +76 -65
- package/lib/activity.ts +100 -3
- package/lib/bindings.ts +86 -15
- package/lib/bus-follower.ts +205 -17
- package/lib/bus-leader.ts +333 -244
- package/lib/bus.ts +82 -19
- package/lib/commands.ts +28 -4
- package/lib/config.ts +44 -33
- package/lib/media.ts +102 -1
- package/lib/menu-settings.ts +4 -1
- package/lib/outbound-attachments.ts +150 -1
- package/lib/outbound.ts +106 -1
- package/lib/polling.ts +5 -0
- package/lib/queue.ts +41 -48
- package/lib/routing.ts +88 -1
- package/lib/sync.ts +40 -3
- package/lib/telegram-api.ts +77 -13
- package/lib/text-groups.ts +183 -16
- package/lib/thread-reconciler.ts +69 -93
- package/lib/threads.ts +131 -66
- package/lib/turns.ts +102 -13
- package/lib/updates.ts +2 -0
- package/package.json +3 -2
package/AGENTS.md
CHANGED
|
@@ -41,6 +41,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
41
41
|
- `/AGENTS.md`: Durable engineering and runtime conventions
|
|
42
42
|
- `/BACKLOG.md`: Canonical open work. Keep only open top-level tasks; when all subtasks under a top-level task are complete, remove that task from the backlog and record completed delivery in `CHANGELOG.md` if user-visible. Put detailed decomposition under the single owning top-level task with nested checkboxes and explicit done criteria instead of promoting completed slices into separate top-level backlog items.
|
|
43
43
|
- `/CHANGELOG.md`: Completed delivery history focused on the final released behavior and user/operator/developer impact. Prefer multiple domain-scoped bullets with an inline-code domain label followed by a colon, change, and impact—for example, `Lock Fencing`: change + impact—instead of accumulating unrelated changes into one long entry; never use square brackets for the domain label. Never include personal usernames, user/chat ids, message/thread ids, or operator-specific thread labels in changelog/docs evidence; use role placeholders such as `<remote-peer>`, `<paired-owner>`, or `assigned leader thread`. Do not record transient implementation churn such as "added then removed" mechanics, internal reversions, or cleanup of an abandoned intermediate path unless the final product surface exposes that as a meaningful migration/breaking change.
|
|
44
|
+
- `Pre-release changelog consolidation`: Treat the current version section as a working set during development, then consolidate it before the release commit, PR, tag, and GitHub Release. Merge repeated implementation and smoke chronology into the smallest truthful set of outcome-focused domain bullets; preserve distinct shipped behavior, safety/compatibility contracts, migrations, known limitations, and meaningful operator evidence. Remove superseded findings, repeated validation runs, and fixed-then-reworked mechanics. Do not rewrite older release sections during this gate.
|
|
44
45
|
|
|
45
46
|
## 4. Core Entities
|
|
46
47
|
|
|
@@ -61,8 +62,9 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
61
62
|
## 5.1 Flat Domain DAG Shape
|
|
62
63
|
|
|
63
64
|
- The project follows a `Flat Domain DAG`: cohesive bridge domains live as flat `/lib/*.ts` modules, and local imports must form a directed acyclic graph
|
|
64
|
-
- `index.ts` stays the single extension entrypoint and composition root for live Pi/Telegram ports, SDK adapters, and session
|
|
65
|
+
- `index.ts` stays the single extension entrypoint and composition root for live Pi/Telegram ports, SDK adapters, and session-state ports
|
|
65
66
|
- `index.ts` should compose high-level domain runtimes and ports, not own per-domain policies, loops, capability decisions, lifecycle recovery rules, or constants. When composition needs non-trivial branching over Threaded Mode, bus leader/follower state, sync slices, reconciliation, or session replacement, extract a named runtime/factory into the owning `/lib` domain and leave only port wiring in the entrypoint.
|
|
67
|
+
- `bindings` owns Pi-facing registration and cohesive cross-domain assembly such as assistant-output observation → authority → sender coordination. It may connect established domain ports and project failures, but policy, mutable sequencing, routing authority, rendering, and transport remain with their owning domains.
|
|
66
68
|
- Reusable runtime logic should be split into flat domain files under `/lib`
|
|
67
69
|
- Mutable counters, generations, target identity, sync state, provisioning activity, retry defaults, and lifecycle ordering are domain policy even when only the entrypoint currently consumes them. Put them behind a cohesive owner runtime with narrow getters/actions; do not leave them as composition-root closure state.
|
|
68
70
|
- Compression stops when `index.ts` contains high-level runtime construction and direct one-to-one live-port wiring. Do not create a one-use wrapper merely to reduce line count; extract only when the owner gains policy, state, sequencing, normalization, projection, or reusable adaptation.
|
|
@@ -79,14 +81,13 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
79
81
|
- Shared filesystem transactions publish a fully initialized private guard directory containing one exact generation-specific `owner.<generation>.json` through same-parent atomic rename. The owner filename and payload generation must match so stale observation cannot rename replacement metadata. Release verifies the owner and atomically renames the stable guard away before cleanup. Directory recovery first claims owner metadata inside the still-occupied guard so a crashed recoverer can itself be replaced without exposing the stable path; transient guard moves and rollback both retry, while inactive process-global claim generations remain recoverable after failed restoration. If secondary recovery cleanup fails after publishing a replacement main guard, release that exact replacement before propagating the failure. Legacy regular-file recovery remains separately serialized through recoverable directory guards. New guards must not depend on hard links, direct file rename-overwrite, incomplete create-then-write state, or age-only recovery. Legacy regular-file guards may be read only for bounded upgrade recovery; malformed or unsupported guards fail closed.
|
|
80
82
|
- Telegram profiles are parallel bot/runtime realities, not merely alternate credentials. Shared bridge settings remain global, but any lock, IPC endpoint, state snapshot, diagnostics log, thread/bus binding, runtime cache, ownership map, or operator-visible status surface that could mix bot identities must be deliberately classified as global, session-local, or profile-scoped. Preserve default-profile legacy paths/keys for compatibility, and give named profiles isolated observable surfaces unless there is an explicit top-level shared-config reason not to.
|
|
81
83
|
- Telegram private-chat Threaded Mode is the public switch for the local multi-instance bus. There is no separate public `telegram.json` bus switch: classic/private-chat singleton DM behavior is the base product mode, and when Telegram reports Threaded Mode available for the bot the bridge enables leader/follower bus behavior. If Telegram reports Threaded Mode unavailable or thread operations prove it unavailable, the bridge returns to classic singleton behavior as a first-class mode. During a live Threaded Mode downgrade, the current transport leader keeps the singleton classic polling role and followers disconnect instead of taking over or offering takeover while active thread bindings prove this is a downgrade from a live bus organism. Do not inject slot/thread rename guidance in classic mode. Blocked instances should not silently register as followers unless the effective runtime mode is Threaded Mode.
|
|
82
|
-
- In Threaded Mode, exactly one live leader owns `getUpdates` and direct Telegram Bot API calls for one bot token. `/telegram-connect` means join the local Telegram organism: if no live leader exists the instance becomes leader; if a live leader exists the terminal-visible instance registers as follower; ordinary connect must not offer manual takeover while the leader heartbeat is live. Leadership is an ephemeral transport role, not permanent ownership by the first terminal-visible Pi instance. Followers must be started by the operator as real Pi processes, register over the local bus, heartbeat, receive only their routed inbound updates, and send outbound replies/previews/files/chat-actions through the leader transport; Telegram must not spawn hidden Pi processes, launch follower subprocesses, or expose a slash command that creates a new local Pi instance. If a leader reloads while followers stay alive, followers must treat rejected/unknown/unauthorized heartbeats as a registration-loss signal and optimistically re-register with the currently active leader before considering promotion; after a short grace window without successful re-registration, a live follower may promote only through an atomic expected-owner transition after the exact observed leader lease becomes stale or inactive. An unreachable IPC endpoint does not authorize replacing a still-live lock owner; remain disconnected/retrying rather than creating split-brain polling. Terminal status must not remain `follower` while the leader registry can no longer route that follower's thread. Heartbeat pruning is liveness bookkeeping, not permission to delete, mark offline, or send a Telegram-visible disconnected notice for a follower's thread; preserve active follower tabs/bindings across transient reload/reconnect incidents and reserve destructive teardown/offline transitions for explicit disconnect or confirmed reconciliation cases. If the leader dies, a live follower should eventually be able to take over through stale-leader election/recovery so the local multi-instance organism keeps running. Explicit `/telegram-disconnect` is a manual thread teardown: a leader deletes its own bound thread before releasing transport ownership, while a follower
|
|
83
|
-
- Runtime routing binds to a live `instanceId
|
|
84
|
-
- Follower registration readiness should stay on the smallest critical path that makes the follower routable: create or reuse the Telegram target, persist the active binding, mark target-bindings fresh, and return the target to the follower. Manual follower identity must survive a Pi process reload in the same terminal so reload is not treated as a new follower/thread; do not key durable follower ownership only by the short-lived Pi process id. Telegram-visible connected notices, duplicate/replaced thread cleanup, and other reconciliation work are important but non-critical; run them after registration and record slow/failing background steps as runtime events rather than delaying follower usability.
|
|
85
|
-
- Instance slots are extension-owned ring-order metadata. Allocate fresh slots from the `bot.lastSlot` cursor as `A` → `B` → ... → `Z` → `A`, allowing the cursor to move to lower letters after wraparound. Only current live/recovering bindings plus unexpired pending provisions and explicit reservations occupy slots; historical records must not block allocation indefinitely. Preserve a slot on ordinary current binding/thread reuse without advancing or rewinding the fresh-allocation cursor. Follower bindings
|
|
84
|
+
- In Threaded Mode, exactly one live leader owns `getUpdates` and direct Telegram Bot API calls for one bot token. `/telegram-connect` means join the local Telegram organism: if no live leader exists the instance becomes leader; if a live leader exists the terminal-visible instance registers as follower; ordinary connect must not offer manual takeover while the leader heartbeat is live. Leadership is an ephemeral transport role, not permanent ownership by the first terminal-visible Pi instance. Heartbeat acknowledgements carry the authenticated live follower-slot roster: on leader loss, the lowest observed live slot attempts the atomic lock first, while higher slots defer for one bounded grace and then re-check so a missing lower follower cannot block recovery indefinitely. Followers must be started by the operator as real Pi processes, register over the local bus, heartbeat, receive only their routed inbound updates, and send outbound replies/previews/files/chat-actions through the leader transport; Telegram must not spawn hidden Pi processes, launch follower subprocesses, or expose a slash command that creates a new local Pi instance. If a leader reloads while followers stay alive, followers must treat rejected/unknown/unauthorized heartbeats as a registration-loss signal and optimistically re-register with the currently active leader before considering promotion; after a short grace window without successful re-registration, a live follower may promote only through an atomic expected-owner transition after the exact observed leader lease becomes stale or inactive. An unreachable IPC endpoint does not authorize replacing a still-live lock owner; remain disconnected/retrying rather than creating split-brain polling. Terminal status must not remain `follower` while the leader registry can no longer route that follower's thread. Heartbeat pruning is liveness bookkeeping, not permission to delete, mark offline, or send a Telegram-visible disconnected notice for a follower's thread; preserve active follower tabs/bindings across transient reload/reconnect incidents and reserve destructive teardown/offline transitions for explicit disconnect or confirmed reconciliation cases. If the leader dies, a live follower should eventually be able to take over through stale-leader election/recovery so the local multi-instance organism keeps running. Explicit `/telegram-disconnect` is a manual thread teardown: a leader deletes its own bound thread before releasing transport ownership, while a follower sends an authenticated exact-registration-generation disconnect envelope and waits for the leader to delete its assigned thread, mark the durable binding offline, and remove live routing authority before unregistering. Cleanup must be confirmed by successful deletion or explicit already-gone evidence; incomplete cleanup preserves binding/routing state, keeps the session open, and returns actionable retry guidance. A promoted leader authorizes inherited follower-thread cleanup by its current leader epoch rather than the historical owner label. Status is reactive to the effective runtime role and bot capability: classic/non-thread mode shows ordinary `telegram connected`, active bus leadership shows `telegram leader`, and registered followers show `telegram follower`; stable multi-instance truth belongs in the bus role (`leader`/`follower`), while lifecycle is reserved for transient recovery state such as `electing` and must not duplicate stable roles with labels like `leader-active`. When leadership or Threaded Mode availability changes at runtime, the status line and `/telegram-status` must converge promptly instead of preserving a stale role label.
|
|
85
|
+
- Runtime routing binds to a live `instanceId` plus its exact follower registration generation; current thread identity should use explicit owner records (`leader`, `manual-follower`, `pending-topic`) rather than hiding ownership direction in legacy string keys. Persisted manual-follower records may act as stable restart hints, but only an authenticated live registry entry authorizes inbound forwarding or outbound transport; stale session records must not be treated as live owners. A live instance owns at most one active bound thread; provisioning a new target for the same `instanceId` must remove older current-state bindings and close duplicate Telegram threads when possible. A reload creates a new runtime `instanceId`; if the previous leader slot is still known/alive, keep it occupied in reservations and assign the new leader the next free slot instead of creating a duplicate same-letter tab or blocking startup on Telegram UI convergence.
|
|
86
|
+
- Follower registration readiness should stay on the smallest critical path that makes the follower routable: create or reuse the Telegram target, persist the active binding, mark target-bindings fresh, and return the target to the follower. Registration requires a present exact generation. Cross-session target reuse and any carried target absent from current bindings require one synchronous visibility probe: explicit stale evidence may provision a replacement, while ambiguous/non-stale failure must reject registration without replay, binding invalidation, or speculative replacement. Persist an ambiguously observed absent target only as non-routable `probe-required` restoration evidence so targetless retries and leader reloads must probe the exact target again before activation or replacement. Registration and explicit disconnect mutations for one durable follower profile must serialize across awaited cleanup so an old runtime's disconnect cannot delete or unregister a replacement generation. Manual follower identity must survive a Pi process reload in the same terminal so reload is not treated as a new follower/thread; do not key durable follower ownership only by the short-lived Pi process id. Telegram-visible connected notices, duplicate/replaced thread cleanup, and other reconciliation work are important but non-critical; run them after registration and record slow/failing background steps as runtime events rather than delaying follower usability.
|
|
87
|
+
- Instance slots are extension-owned ring-order metadata. Allocate fresh slots from the `bot.lastSlot` cursor as `A` → `B` → ... → `Z` → `A`, allowing the cursor to move to lower letters after wraparound. Only current live/recovering bindings plus unexpired pending provisions and explicit reservations occupy slots; historical records must not block allocation indefinitely. Preserve a slot on ordinary current binding/thread reuse without advancing or rewinding the fresh-allocation cursor. Follower bindings survive process absence as stable restoration hints until explicit stale/deleted/offline evidence invalidates them. Successful target reuse must refresh the binding timestamp and replacement runtime identity; generic leader startup or heartbeat pruning must not erase the target merely because no follower registration is currently live. An authenticated live follower whose carried exact target is missing from current persisted bindings should recover that target instead of creating another Telegram thread; preserve a carried slot only when it does not collide with another current record. Preserve a cursor that belongs to a live or recoverable binding and continue skipping retained restart hints plus unexpired pending/reserved slots. Explicit stale/deleted/offline reconciliation may release that slot; generic process absence may not. The alphabet cap is a feature — compact single-letter tabs are scannable.
|
|
86
88
|
- Instance thread names are provisioner-authored identity metadata, not model-authored output. Fresh Threaded Mode threads select one of five curated compact 4-6 letter Latin names for the assigned slot letter using provisioning timestamp entropy, then create the Telegram thread with that thread name immediately. The title is the thread name itself, not `{slot} {threadName}` or `{slot} — {threadName}`. Do not register or reintroduce an agent-facing thread rename tool; removing the extra rename prompt/tool turn is an intentional simplicity constraint. Thread names must mirror into terminal status, live diagnostics, and `[telegram|thread:name]` through one target-aware current-instance identity resolver. Registered follower/leader metadata takes precedence over a stale shared record for the same target, with the record used only as fallback; do not hide a valid baked/custom thread name behind old slot-prefix validation. A follower that later becomes leader keeps its existing name; leadership changes are transport role changes, not identity resets.
|
|
87
|
-
- Telegram private-chat Threaded Mode treats `All` as an aggregate/control surface, not a durable `General` thread
|
|
88
|
-
-
|
|
89
|
-
- Telegram extension work should not hold Pi's core agent lifecycle hostage once Pi has produced the semantic result. For Telegram-originated turns, final Telegram delivery, attachment upload, and transport cleanup are extension-owned side effects; schedule them off the critical `agent_end` path when ordering can still be preserved, record failures as runtime events, and keep dispatch of the next Telegram turn behind the delivery task when needed to avoid interleaving previews/finals. Public activity handlers run through isolated asynchronous per-handler queues; lifecycle hooks enqueue normalized events and never await consumer work.
|
|
89
|
+
- Telegram private-chat Threaded Mode treats `All` as an aggregate/control surface, not a durable `General` thread or process launcher, and leader activation proactively creates or reuses its private-DM thread. When the owner writes in an unknown thread, the leader reclaims and routes the first one only if it lacks an active binding; later unknown threads remain intact and receive an in-thread chooser to reroute the captured prompt or restore a stale binding. Destructive cleanup requires explicit confirmation through `thread-reconciler`; another Pi instance still requires a manually started process followed by `/telegram-connect`. Without Threaded Mode, process the message through classic routing.
|
|
90
|
+
- Telegram extension work should not hold Pi's core agent lifecycle hostage once Pi has produced the semantic result. For Telegram-originated turns, final Telegram delivery, attachment upload, and transport cleanup are extension-owned side effects; schedule them off the critical `agent_end` path when ordering can still be preserved, record failures as runtime events, and keep dispatch of the next Telegram turn behind the delivery task when needed to avoid interleaving previews/finals. Public activity handlers run through isolated asynchronous per-handler queues; lifecycle hooks enqueue normalized events and never await consumer work. Proactive push defaults enabled and requires explicit `assistant.proactivePush: false` to opt out. While enabled, core delivery projects each completed Pi-visible assistant text block from local/autonomous work once and in order; bind admitted blocks to the exact target, profile/token transport stamp, direct leader epoch or follower registration generation, and session generation. Token deltas, hidden reasoning, tool traffic, stale authority, and Telegram-owned turns never enter that projection.
|
|
90
91
|
- Telegram runtime state should be treated as event-driven reconciliation of extension assumptions against observable Telegram signals, not as a full Telegram bot read-model and not as a reason to query Telegram on every action. Keep a local assumption model for bot identity/capabilities, pairing assumptions, thread support, known thread lifecycle, bound instance targets, reservations, and transport health. Invalidate and reconcile the relevant slice on meaningful events: startup/reload, lifecycle service messages, stale-send/API errors, setup/pairing changes, follower register/prune, explicit status/debug requests, and bounded low-frequency leader health ticks. `lib/sync.ts` owns sync slices, observation intake, invalidation triggers, status/debug freshness, and reconciliation scheduling; it must not promise complete bot-state mirroring because Bot API lacks a complete topic listing surface. `lib/thread-reconciler.ts` owns pure Threaded Mode lifecycle planning and should become the only policy authority for destructive thread cleanup decisions. `threads` owns current Telegram UI thread/tab binding primitives and thread-name helpers mapped to Bot API topic transport, `status` renders projections, and `index.ts` only wires ports. `tmp/telegram/logs.jsonl` is a session-local redacted runtime evidence stream for race debugging and resets on extension start / runtime scope changes; it is diagnostics only, not routing/provisioning authority. `state.json` should be an observable/debug snapshot aligned with `/telegram-status` (extension runtime, bot capabilities including `bot.lastSlot`, runtime role, live roster projection, reservations, diagnostics), not routing/provisioning authority. Because every process on one Telegram profile shares this file, only the active transport lock owner may persist it; followers read current state and gain write authority only after promotion. Status-only writes must refresh disk-backed bindings before serialization so a stale loaded snapshot cannot erase newer leader state. Live bus/runtime state is authoritative; file reservations and fresh capability observations may be startup hints/collision guards only; stale snapshots must re-probe before suppressing bus/topic behavior. Do not persist stale/offline/failed thread target history as source of truth.
|
|
91
92
|
- Thread bindings are bidirectional. From the Pi-instance side, an instance knows and preserves its target, slot, thread name, lifecycle state, and direct-delivery defaults. From the Telegram-client/bot side, the bridge observes thread creation/close/reopen/stale-send signals and reconciles them into instance binding state. Lifecycle transitions that affect operator understanding should be Telegram-visible when a live transport remains: every successful follower registration/re-registration gets a compact connected notice in the assigned thread, while heartbeat pruning stays silent because it is only liveness suspicion. These notices should use the instance thread name when known and fall back to the slot letter only while the thread is still unnamed. New unknown threads (owner writing in `All`) are preserved until the user explicitly chooses a reroute/restore/new-instance action that makes cleanup eligible; any destructive cleanup must go through `thread-reconciler`. Unknown `forum_topic_created` service events are observations, not destructive cleanup proof.
|
|
92
93
|
- The multi-instance bus uses private bot DMs with Telegram private-chat Threaded Mode enabled for the bot. No supergroup, group, or forum configuration is needed; the thread chat is always the private chat with the paired owner. Threaded capability checks must use bot/private-chat evidence such as `getMe.has_topics_enabled`, incoming `message_thread_id`, and topic operation success/failure; do not use group chat metadata as the control-plane truth for Telegram private-chat Threaded Mode.
|
|
@@ -102,7 +103,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
102
103
|
- UI emoji semantics live in `docs/ui-style.md` as the project-level emoji registry. Before adding a new UI emoji, reuse an existing registered meaning or update the registry in the same change. Use `🧵` consistently as the canonical Telegram/Pi thread marker in thread chooser headings, thread lifecycle copy, and target-routing status text; do not decorate every concrete thread-target button with it when the button label is already the thread name or slot. Operator-facing Threaded Mode routing copy should use the `Pi` glyph for Pi instance/thread/session concepts; reserve `Pi` spelling for product prose, package/API names, and surrounding docs where the wordmark is clearer.
|
|
103
104
|
- Telegram prompt context metadata uses `[tag|key:value|key:value]` without spaces around `:`. `[telegram...]` identifies the current inbound surface only: owner/current turns use `[telegram]` or `[telegram|thread:<name>]`, while guest-mode turns use `[telegram|guest:<group-title-or-peer-username-or-id>]`. In private guest replies authored by the owner, `guest:<...>` must name the replied guest peer, not the owner. Alternate/source authors belong on source-specific blocks such as `[reply|from:<username-or-id>]`, `[forward|from:<username-or-id>]`, and `[attachments|from:<username-or-id>]`; plain `[attachments]` remains current-turn/current-owner attachments and should render immediately after the `[telegram...]` current text before reply/forward/source attachment context.
|
|
104
105
|
- Telegram `reply_to_message` context is prompt-only and must not affect slash-command parsing; when Telegram includes `rich_message` blocks on a quoted rich reply, extract plain text from those blocks before falling back to raw `text`/`caption` so prompt context does not leak raw Rich Markdown source
|
|
105
|
-
- Long-lived timers, pollers, watchers, and deferred queue dispatch must be session-bound and avoid stale live Pi contexts after session replacement. Pi session replacement/reload changes agent context, so Threaded Mode followers must stop the old receiver/heartbeat and never retain a stale context or socket.
|
|
106
|
+
- Long-lived timers, pollers, watchers, and deferred queue dispatch must be session-bound and avoid stale live Pi contexts after session replacement. Pi session replacement/reload changes agent context, so Threaded Mode followers must stop the old receiver/heartbeat and never retain a stale context or socket. Retain a short-lived same-process handoff containing the assigned target identity immediately after follower promotion succeeds and refresh it before suspension; followers re-register through the live leader with that target, while leaders—including promoted followers—restore and persist the exact target, slot, and thread name under acquired leader authority before topic provisioning. During election and leader handoff, status identity may use the current instance's inactive manual-follower record as a display/restoration hint, but never as live routing authority. Bind leader handoffs to the exact Telegram profile owner key so session replacement cannot transfer a target across profiles. Preserve the existing Telegram thread during either handoff, show `Telegram disconnected` only while restoration is genuinely pending or failed, and require explicit `/telegram-connect` only when no fresh same-process handoff can be restored.
|
|
106
107
|
- Do not add Telegram commands that imitate Pi interactive session replacement, navigation, or TUI rendering through private internals, ANSI terminal clearing, raw TTY injection, or a shadow `pi` subprocess. Features such as a real Telegram `/new` require a public Pi API that runs the same session-replacement path as the terminal command.
|
|
107
108
|
- In-flight `/model` switching is limited to Telegram-owned active turns; if a tool call is active, abort is delayed until the tool finishes
|
|
108
109
|
|
|
@@ -114,6 +115,8 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
114
115
|
- Real code blocks must stay literal and escaped
|
|
115
116
|
- `telegram_attach` is the canonical outbound file-delivery path for Telegram-originated requests; outside active Telegram turns it may send immediately to the paired/default chat, a registered follower's assigned target, or explicit `chat_id` plus optional `thread_id` for explicit local/TUI delivery requests only when this Pi instance owns `/telegram-connect` or is registered with the Threaded Mode multi-instance bus. `telegram_message` is the first-class direct Telegram Markdown text tool for local/TUI prompts and follows the same ownership-or-bus-registration gate; neither direct tool replaces normal active-turn replies. It reuses top-level `telegram_button` comments for inline buttons; buttons must be attached to a text message, never sent as standalone actions
|
|
116
117
|
- Telegram prompt guidance is layered and context-frugal: unconfigured sessions receive no bridge suffix, configured sessions receive compact direct-delivery/help guidance, and Telegram-originated turns receive only the minimal per-turn contract. Detailed voice/button/direct-delivery/Threaded Mode/formatting/debug guidance belongs in the agent-facing `telegram_help` tool instead of being repeated in every prompt.
|
|
118
|
+
- Telegram inbound prompt topology keeps current user material contiguous: `[outputs]` produced from current attachments follows `[attachments]` immediately, then independent `[reply]` or forwarded-source context follows the complete current-user block. Forwarded Rich Message media belongs under source-attributed `[attachments|from:<identity>]` immediately after its `[forward|from:<identity>]` block and remains available as model image content. Do not place source context between an attachment and its transcription or handler output.
|
|
119
|
+
- Forward-comment grouping uses a bounded one-second candidate window for ordinary short human text so an adjacent same-sender/chat/thread forward can join across polling responses; a matching forward flushes immediately. Commands, bots, captions, media groups, target/sender mismatches, reversed ids, and distant message ids bypass grouping. Preserve the grouped candidate across session replacement and follower forwarding without weakening exact routing-generation authority.
|
|
117
120
|
- `telegram_voice` and `telegram_button` are not Pi tools; keep prompts/docs explicit that agents should author markup while voice synthesis provider extensions own TTS/OGG conversion, and pi-telegram owns button routing plus Telegram delivery
|
|
118
121
|
- Voice reply policy and prompt context are owned by pi-telegram's `telegram.json` `voice.replyMode`: missing/invalid config behaves as `manual` but does not add a `[voice]` prompt-context block; only an explicit valid `voice.replyMode` renders context. Render a single voice field as `[voice] reply mode: manual|mirror|always`, and render multiple fields as a `[voice]` list; place voice context after `[outputs]` when handler output exists, otherwise after `[attachments]`; provider prompt contributions are optional provider-specific additions, not the default policy channel
|
|
119
122
|
- Optional `telegram.json` `time` may add `[time] YYYY-MM-DD HH:mm:ss <timezone>` to Telegram-originated prompts for wall-clock context. It is hidden by default, uses `time.injectionMode` values `hidden|always|interval`, stores `time.interval` in milliseconds, uses the system timezone, and should render last after `[attachments]`, `[outputs]`, and `[voice]` sections. The Settings row `🕒 Time injection: hidden|always|interval` controls `time.injectionMode` only.
|
|
@@ -134,7 +137,15 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
134
137
|
- For non-trivial implementation, release, or architecture-audit work, run an `AGENTS.md` compliance pass: reread the durable principles and relevant project docs, map the changed behavior to the rules it touches, validate code/tests/docs against those rules, and explicitly surface any rule conflict, obsolete rule, or evidence-backed improvement. When the rules themselves drift from the product reality, update `AGENTS.md` or document a deliberate exception in the same pass instead of silently working around it.
|
|
135
138
|
- Treat Windows Telegram runtime error reports as high-signal evidence even when Linux is the primary development environment. Windows uses different filesystem/IPC behavior and is more likely to reveal lock, heartbeat, named-pipe, atomic-rename, and Threaded Mode edge cases; minimize each report into a regression or a documented platform caveat instead of dismissing it as environment noise.
|
|
136
139
|
|
|
137
|
-
## 6.2
|
|
140
|
+
## 6.2 Agent Context Discipline
|
|
141
|
+
|
|
142
|
+
- Keep successful command output compact at the tool boundary. Redirect full validation logs to a temporary file, report only the command status and a small summary, and inspect a bounded failure tail or targeted range only when the command fails. `npm test` uses the dot reporter for this default; use `npm run test:verbose` only when individual test names provide necessary diagnostics.
|
|
143
|
+
- Read large artifacts search-first and range-bounded. Prefer path/content search, exact line ranges, `git diff --stat`, and path-scoped diffs over whole large files or complete repository diffs; the vendored Bot API reference must follow its stricter skill-local lookup contract.
|
|
144
|
+
- Treat `CHANGELOG.md` as a large artifact: read only the first 50 lines for the latest release by default; for an older release, locate its heading first and read only that bounded section. Never read the complete changelog merely to inspect one release.
|
|
145
|
+
- During implementation, prefer focused typechecks and tests. Run the complete validation suite only at a meaningful stable gate such as final review, release preparation, or after a cross-cutting correction.
|
|
146
|
+
- Bound independent review context: use focused reviewers for unresolved risks, then one complete independent review after implementation stabilizes. Do not repeatedly rebuild multiple full-project reviewer contexts without new evidence.
|
|
147
|
+
|
|
148
|
+
## 6.3 Validation Hotspots
|
|
138
149
|
|
|
139
150
|
- Treat queue handling, compaction interaction, and lifecycle-hook state transitions as regression-prone areas; validate them after changing dispatch logic
|
|
140
151
|
- Keep the standard `npm run typecheck` gate strict about unused locals and parameters so stale imports and abandoned adapters fail validation instead of accumulating silently
|
|
@@ -145,7 +156,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
145
156
|
- Prefer width-efficient monospace table and list formatting for narrow clients, with table padding based on grapheme/display width rather than raw UTF-16 length where possible
|
|
146
157
|
- Flatten nested Markdown quotes into indented single-blockquote output because Telegram does not render nested blockquotes reliably
|
|
147
158
|
|
|
148
|
-
## 6.
|
|
159
|
+
## 6.4 File And Naming Style
|
|
149
160
|
|
|
150
161
|
- Keep comments and user-facing docs in English unless the surrounding file already follows another convention
|
|
151
162
|
- Each project `.ts` file should start with a short multi-line responsibility header comment that explains the file boundary to future maintainers; source-module headers must include `Zones:` tags for cross-cutting responsibility areas
|
|
@@ -156,7 +167,7 @@ The core product loop is mobile continuation: start or supervise work in the ter
|
|
|
156
167
|
- Keep composition wiring DRY with small local adapters or owning-domain contracts when repetition appears, but do not hide live mutable session state behind broad facades just to reduce repeated closures
|
|
157
168
|
- Keep interface contracts consistent for the same runtime entity: prefer the owning domain's exported contract when multiple modules mean the same entity, and use local structural `*Like`/view contracts only for deliberate narrow projections that avoid real coupling without duplicating source-of-truth shapes
|
|
158
169
|
|
|
159
|
-
## 6.
|
|
170
|
+
## 6.5 Current Domain Ownership Snapshot
|
|
160
171
|
|
|
161
172
|
The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/architecture.md). Keep this section as a compact agent-facing index, not a second copy of the full map.
|
|
162
173
|
|
|
@@ -167,7 +178,7 @@ The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/ar
|
|
|
167
178
|
- Extension platform: `sections` owns section registry, token mapping, callback dispatch, context building, and its globalThis bridge; `delivery` owns target-aware extension views, logical handles, target policy, ordering, lifecycle fencing, and its globalThis runtime membrane; `activity` owns normalized lifecycle registration, activity/source identity, non-blocking dispatch, delivery contexts, and its globalThis runtime membrane; `voice` owns the voice-provider registry and its globalThis bridge
|
|
168
179
|
- Pi SDK boundary: `pi` owns direct pi imports and bound extension API ports; `bindings` owns pi-facing command/tool/lifecycle registration wiring extracted from the entrypoint
|
|
169
180
|
|
|
170
|
-
## 6.
|
|
181
|
+
## 6.6 Entrypoint And Import Boundaries
|
|
171
182
|
|
|
172
183
|
- Keep the preview domain as a thin streaming lifecycle controller only: draft ids, safe-prefix selection for `sendRichMessageDraft`, voice suppression, serialized flushes, diagnostics, and finalization state. Do not reintroduce assistant preview rendering there; keep `rendering.ts` scoped to bridge-owned UI/compat regular-message rendering rather than assistant or guest Markdown delivery. Drafts must only send structurally closed Markdown prefixes; draft failures are not proof that drafts are globally unsupported, so record the failure and skip that preview frame. Do not add raw plain-message fallback previews for assistant Markdown
|
|
173
184
|
- Preview/final delivery ordering is release-critical: finalization waits for active preview flushes, persisted final delivery should not be followed by a post-final draft-clear call that creates transient draft UI, and regressions should cover in-flight draft flush serialization plus final reply ordering.
|
package/BACKLOG.md
CHANGED
|
@@ -1,44 +1,6 @@
|
|
|
1
1
|
# Project Backlog
|
|
2
2
|
|
|
3
|
-
_This backlog tracks only open release-relevant work: hotfixes, live runtime verification, evidence-gated Telegram client follow-ups, and upstream Pi API blockers. Completed outcomes and validation evidence belong in `CHANGELOG.md`, not in this queue._
|
|
4
|
-
|
|
5
|
-
## P0 — Termux-Compatible Filesystem Transactions (`0.22.1`)
|
|
6
|
-
|
|
7
|
-
Context: issue #131 proves that Android/Termux rejects the hard-link publication used by the `0.22.0` transaction guard, causing Pi to exit during JSONL initialization. A direct file-rename fallback is unsafe because rename replaces an existing destination and can admit multiple transaction owners. The hotfix keeps per-resource serialization but publishes a fully initialized, non-empty guard directory atomically.
|
|
8
|
-
|
|
9
|
-
Open work:
|
|
10
|
-
|
|
11
|
-
- [x] Replace staged-file/hard-link publication with a staged guard directory containing one private generation-specific owner file, then atomically rename that non-empty directory to the stable per-resource transaction path.
|
|
12
|
-
- [x] Keep collision-resistant owner generation plus PID and acquisition time, exact-owner verification, serialized stale recovery, and fail-closed malformed-guard behavior.
|
|
13
|
-
- [x] Release guards by exact-owner atomic rename away from the stable path before recursive cleanup; support stale legacy file guards left by `0.22.0` during upgrade.
|
|
14
|
-
- [x] Ensure JSONL append/rotation transaction failures remain diagnostics-only and cannot produce an unhandled rejection that terminates Pi.
|
|
15
|
-
- [x] Add deterministic regressions for directory publication, exact release, legacy and directory stale recovery, malformed guards, simultaneous acquisition, concurrent recovery, and swallowed diagnostics failures.
|
|
16
|
-
- [x] Update lock/diagnostics documentation and durable transaction contracts for the staged-directory protocol and fail-soft JSONL behavior.
|
|
17
|
-
- [x] Run implementation tests, typecheck, strict Domain DAG, ABCd, audit, package dry-run, invariants, and `git diff --check`.
|
|
18
|
-
- [x] Run an independent four-lens concurrency/filesystem review of directory publication, contention classification, stale recovery, exact release, legacy migration, and diagnostics containment; preserve its `NOT READY` evidence for the reproduced abandoned-recovery-guard deadlock.
|
|
19
|
-
- [x] Replace directory stale recovery with an internal exact reclaim marker that remains recoverable after claimant death; preserve live recovery guards and recover abandoned new-directory and legacy-file recovery guards with deterministic regressions.
|
|
20
|
-
- [x] Prove fail-soft diagnostics in a child process that emits one failing record under `--unhandled-rejections=strict`, independently from later queue recovery.
|
|
21
|
-
- [x] Run a clean follow-up review and preserve its `NOT READY` evidence for the reproduced same-process reclaim stall after a transient guard-rename failure.
|
|
22
|
-
- [x] Retry transient reclaim renames, roll the exact marker back on exhaustion, and treat inactive process-global reclaim generations as recoverable even when rollback itself fails; cover same-process reacquisition in both cases.
|
|
23
|
-
- [x] Run a final four-lens review and preserve its `NOT READY` evidence for peer-visible marker starvation and leaked replacement ownership after failed abandoned-recovery cleanup.
|
|
24
|
-
- [x] Retry rollback renames for peer-visible recovery, release exact newly recovered main ownership when secondary cleanup fails, and cover both failure sequences with deterministic same-process and child-process regressions.
|
|
25
|
-
- [x] Run one bounded final reviewer and preserve its `NOT READY` evidence for a reproduced stale-observation ABA race that let a delayed recoverer claim a replacement generation.
|
|
26
|
-
- [x] Bind each new directory owner generation into its unique `owner.<generation>.json` path and require filename/payload generation agreement, so delayed recovery receives `ENOENT` instead of renaming replacement metadata; cover the exact interleaving and repeat the child-process recovery barrier 20 times.
|
|
27
|
-
- [x] Obtain a final clean `READY` verdict and resolve every remaining release-blocking local finding.
|
|
28
|
-
- [x] Prepare `0.22.1` version metadata after review readiness, then rerun the complete release gate against the final package contents.
|
|
29
|
-
- [ ] Ask the issue #131 reporter to verify Termux startup, config/log writes, `/telegram-connect`, and reload after the hotfix becomes available; retain this as post-release environment evidence rather than a local release blocker.
|
|
30
|
-
|
|
31
|
-
Done when: pure Node filesystem operations provide exactly-one transaction ownership without hard links; old, replacement, and failed-recovery owners cannot delete or strand each other's guards; diagnostics failures cannot terminate Pi; independent review returns local `READY`; final release gates pass; and the post-release Termux verification request remains explicit until reporter evidence arrives.
|
|
32
|
-
|
|
33
|
-
## P1 — Promoted Follower Reload Evidence
|
|
34
|
-
|
|
35
|
-
Context: deterministic coverage protects promoted follower thread preservation, and the latest live Linux smoke closed reload routing, follower Active, and reroute/restore regressions. The exact promoted-leader reload path is deliberately outside the 0.20.1 profile IPC hotfix because it is unrelated to profile transport isolation; keep it as an evidence-gated follow-up rather than blocking that release.
|
|
36
|
-
|
|
37
|
-
Open work:
|
|
38
|
-
|
|
39
|
-
- [ ] Capture live evidence that leader → follower promotes → `/reload` preserves the promoted leader's Telegram thread identity.
|
|
40
|
-
|
|
41
|
-
Done when: promoted-follower reload identity has direct live Telegram evidence.
|
|
3
|
+
_This backlog tracks only open release-relevant work: hotfixes, bounded maintenance, live runtime verification, evidence-gated Telegram client follow-ups, and upstream Pi API blockers. Completed outcomes and validation evidence belong in `CHANGELOG.md`, not in this queue._
|
|
42
4
|
|
|
43
5
|
## P1 — Native Windows Threaded Mode Follow-Ups
|
|
44
6
|
|
|
@@ -64,18 +26,6 @@ Open work:
|
|
|
64
26
|
|
|
65
27
|
Done when: new client quirks are either fixed with targeted coverage or documented as evidence-backed exceptions, without keeping broad manual smoke matrices in the backlog.
|
|
66
28
|
|
|
67
|
-
## P1 — Evidence-Backed Rich Markdown Normalization
|
|
68
|
-
|
|
69
|
-
Context: Native Rich Markdown is the default assistant delivery path. Existing regressions cover known parser/client edges such as space-after-marker blockquotes, dollar-prefixed ticker atoms, list indentation, code fences, links, display math normalization, and long-message splitting. Further rewrites should be evidence-driven, not speculative.
|
|
70
|
-
|
|
71
|
-
Open work:
|
|
72
|
-
|
|
73
|
-
- [ ] Capture any new Telegram parser-breaking sequence from live/client evidence or a minimized fixture.
|
|
74
|
-
- [ ] Add a conservative normalization or safe-degradation rule only for confirmed sequences.
|
|
75
|
-
- [ ] Keep unconfirmed speculative rewrites out of the delivery path.
|
|
76
|
-
|
|
77
|
-
Done when: newly observed Rich Markdown failures have minimized fixtures and targeted regressions, while stable rendering behavior remains unchanged for unsupported guesses.
|
|
78
|
-
|
|
79
29
|
## Blocked — Same-Thread Telegram `/new`
|
|
80
30
|
|
|
81
31
|
Blocked: upstream Pi core API. Issue: https://github.com/earendil-works/pi/issues/5952
|
package/CHANGELOG.md
CHANGED
|
@@ -1,31 +1,53 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.23.1: Context Budget And Runtime Simplification
|
|
4
|
+
|
|
5
|
+
- `Bus Runtime Simplification`: Removed the no-op follower-binding recovery timer and grace configuration, the unused disconnected-announcement helper, and the duplicate follower-prune callback. Heartbeat pruning now emits one accurate diagnostic while preserving durable thread bindings. Impact: leader/follower recovery carries less dormant state and cannot imply offline cleanup that never occurs.
|
|
6
|
+
- `Thread Reconciliation Safety`: Removed unreachable reservation-probe/removal machinery and the uncalled heartbeat-prune destructive cleanup action. Explicit disconnect, replacement, previous-leader, and expired-provision cleanup retain their confirmation and leader-epoch fences. Impact: reconciliation matches the documented rule that heartbeat loss removes live routing authority without deleting a follower's recoverable Telegram thread.
|
|
7
|
+
- `Destination Resolution`: Collapsed proactive chat-id and target selection onto the target resolver's active-turn → assigned-thread → paired-chat priority, retaining a scalar adapter only where an API requires a chat id. Impact: proactive projection, activity typing, and Guest attachment staging cannot drift between duplicate destination sources.
|
|
8
|
+
- `Agent Context Budget`: Added compact successful-test output with an opt-in verbose reporter, bounded failure-log inspection, search-first large-artifact and Bot API lookup rules, scoped diff/review guidance, and stable-gate validation policy. Compressed repeated agent, architecture, and multi-instance contracts without weakening their safety meaning. Impact: ordinary validation and review consume substantially less model context while actionable diagnostics remain available in retained logs.
|
|
9
|
+
- `Validation`: Full typecheck/tests/audit/package dry-run, strict Domain DAG, ABCd context validation, repository hygiene checks, and an independent regression review all passed after cleanup. Impact: the behavior-preserving hotfix is release-ready with no identified blockers.
|
|
10
|
+
|
|
11
|
+
## 0.23.0: Telegram Bot API 10.2 Rich Output And Proactive Projection
|
|
12
|
+
|
|
13
|
+
- `Follower Visibility And Restoration`: Added one synchronous connected probe before cross-session target reuse or recovery of an exact target absent from current bindings. Success surfaces the existing tab; explicit stale-topic evidence provisions a monotonic replacement, while ambiguous failures preserve non-routable `probe-required` evidence and reject without replay or speculative replacement. Impact: followers cannot report invisible or deleted tabs as restored, and retries must resolve the exact target before activation.
|
|
14
|
+
- `Explicit Thread Disconnect`: Authenticated registration and disconnect with present exact generations, serialized both mutations per durable follower profile across awaited cleanup, and required thread-named confirmation plus successful deletion or explicit already-gone evidence before removing routing authority. Incomplete cleanup preserves leader/follower state with retry guidance, and promoted leaders authorize inherited cleanup through their current epoch. Impact: `/telegram-disconnect` cannot let an old runtime delete a replacement registration or falsely report success while its tab remains.
|
|
15
|
+
- `Follower Succession And Handoff`: Added the authenticated live slot roster to heartbeats so the lowest observed follower proceeds immediately on leader loss while higher slots defer for one bounded grace and then recheck exact atomic ownership. Promoted followers retain target, slot, thread name, profile ownership, and electing status through same-process session handoff; reload persists that identity under acquired leader authority before provisioning. Impact: failover prefers slot order without claiming consensus, dead lower candidates cannot block recovery indefinitely, and promoted reload reuses the existing tab instead of resetting identity.
|
|
16
|
+
- `Threaded Topology Evidence`: Two- and three-instance live smokes confirmed usable follower tabs, repeated bidirectional promotion/disconnect/re-registration, promoted-leader reload reuse, and healthy convergence without manual takeover. Occasional timing-dependent promotion order remains compatible with bounded grace because exact atomic ownership—not timing—fences leadership. Impact: visibility, restoration, and multi-instance recovery have direct operator evidence without overstating succession guarantees.
|
|
17
|
+
- `Composition Root Compression`: Reduced `index.ts` from 1,119 to 1,083 lines by moving assistant-output admission/authority/sender coordination into the existing Pi-binding orchestration domain, moving config-persist sync sequencing and stale-topic API recovery adapters into `sync`, and replacing local forwarding callbacks with direct live-port wiring where signatures already matched. Impact: the entrypoint retains visible runtime composition while low-level policy, failure projection, mutable sequencing, and reusable adapters stay with their owning domains.
|
|
18
|
+
- `Forwarded Input`: Added a bounded one-second candidate window that joins ordinary short comments with an adjacent same-sender/chat/thread forward across polling responses, including Rich Message text and follower-routed updates. Nested forwarded photo, video, animation, audio, and voice-note media now render under source-attributed attachments immediately after the forward block. A post-reload live smoke confirmed comment → forward → attachment ordering with the image available as model input. Impact: comments and forwards form one correctly attributed Pi turn without relying on incidental polling batches or mislabeling forwarded media as current-user attachments.
|
|
19
|
+
- `Follower Routing And Restart`: Propagated the exact live registration generation into every leader-forwarded envelope, recorded rejected acknowledgements, and preserved stable manual-follower bindings across process absence as restart hints only. Replacement registration reclaims the current recognizable thread and slot, while authenticated live registration remains mandatory routing authority and explicit stale/deleted/offline evidence invalidates reuse. Impact: follower prompts remain generation-fenced and diagnosable, and restarts avoid duplicate same-named tabs without routing to absent processes.
|
|
20
|
+
- `Inbound Prompt Topology`: Kept attachment-derived `[outputs]`, including voice transcription, immediately after current-user `[attachments]`, with independent reply/source context following the complete current-user block. A live voice reply to an image confirmed the same current-user-first ordering. Impact: attachment meaning remains connected to the input that produced it instead of being split by quoted-message context.
|
|
21
|
+
- `Portable Lock Standard`: Synchronized `docs/locks.md` bit-for-bit with the reusable cross-project standard and removed Telegram-specific runtime, bus, profile, and release-history policy from that portable artifact. Impact: the standard can be copied into another singleton-extension project as a complete implementation contract, while project-specific behavior remains owned by architecture documentation.
|
|
22
|
+
- `Proactive Configuration And Documentation`: Moved local/autonomous projection policy under `assistant.proactivePush` beside assistant rendering and draft-preview settings, enabled it when absent, retained nested explicit `false` as the canonical opt-out, and intentionally omitted a compatibility alias for the removed top-level key. Settings explain `on` (default) and `off`, while README, Activity, Outbound, Public API, architecture, and durable contracts document ordered public-block projection, exclusions, rendering, authority, follower routing, and no-replay behavior. Impact: configuration, operator copy, and maintainer contracts describe one coherent default-enabled feature with an explicit disable path.
|
|
23
|
+
- `Config Transactions`: Valid Telegram configuration snapshots now load directly from the atomically published file, while malformed-file recovery and every merge/write remain serialized by the cross-process transaction guard. Impact: ordinary `telegram.json` reads no longer create transaction-directory churn, without weakening concurrent persistence or destructive recovery safety.
|
|
24
|
+
- `Release Preflight`: Ran the complete 0.23.0 validation suite after follower visibility, bounded succession preference, explicit cross-role thread disconnect, cross-batch grouping, and forwarded Rich media fixes: 1,292 tests passed with one platform-only skip, typecheck passed, npm audit reported zero vulnerabilities, package dry-run produced `@llblab/pi-telegram@0.23.0`, and `git diff --check` stayed clean. Impact: deterministic, packaging, dependency, and repository hygiene gates are green, and the final independent release review reported SHIP with no blockers.
|
|
25
|
+
- `Proactive Projection`: Extended the existing Activity assistant-output path to project deduplicated completed public `text_end` segments from local and autonomous work through the configured Rich/HTML sender. Intermediate and final blocks share one ordered, non-blocking queue; Telegram-owned turns, hidden reasoning, tool traffic, empty text, disabled policy, and the removed final-only `agent_end` path remain excluded. Impact: public checkpoints and finals arrive once and in source order without a parallel projection domain or a Grow Loop dependency.
|
|
26
|
+
- `Proactive Authority And Ordering`: Bound every admitted block to its target, profile/token transport stamp, session generation, and exact direct leader epoch or follower registration generation, then revalidated authority immediately before each non-idempotent send/edit mutation. Direct and follower barriers prove queued stale-generation work drops, follower IPC preserves order, replacement transport receives no old mutation, and ambiguous transport outcomes retain no-replay behavior. Impact: asynchronous handlers, ownership changes, follower re-registration, and session replacement cannot redirect or duplicate delayed projection.
|
|
27
|
+
- `Proactive Projection Evidence`: Deterministic local-run, direct/follower ordering, privacy, and replacement-fence coverage plus a post-reload live smoke confirmed that one public commentary checkpoint and final arrived separately and in order in the assigned thread while internal tool traffic stayed silent. Impact: the normalized multi-block lifecycle has both regression and client evidence without duplicate output.
|
|
28
|
+
- `Rich Attachment Results`: Added one deterministic outbound plan that combines exactly one probe-confirmed PNG/JPEG photo, MP4 video, or MP3 audio artifact with normalized final assistant Markdown, reply/thread targeting, and an optional inline keyboard in one configured-Rich multipart result. Known-safe rejection returns to text plus attachment; `commit-unknown` or malformed success stops without replay. HTML mode, multiple or unsupported artifacts, empty text, Guest Mode, voice-only output, explicit voice markup, and OGG/Opus retain their established paths. Impact: supported requested media can arrive as one reply-anchored result without weakening rendering choice, voice policy, or ambiguity safety.
|
|
29
|
+
- `Rich Authority And Safety`: Recorded exact message ownership, authorized follower multipart upload only to its registered thread, and fenced scheduled or in-flight Rich finals across session replacement and ownership loss. Old generations cannot start multipart mutation or continue with preview cleanup, fallback delivery, or queue dispatch after authority changes. The outgoing Rich contract permits Markdown/HTML plus typed media but excludes Thinking blocks, with invariants across reply, preview, queue, and attachment paths. Impact: replacement sessions, cross-thread followers, and hidden reasoning cannot redirect or duplicate Rich delivery.
|
|
30
|
+
- `Rich Transport And Rendering`: Added narrow Bot API 10.2 media and voice-note types plus direct/follower JSON, cached-file, HTTPS, and single-file `attach://` multipart transport coverage. Structured Markdown regressions preserve tables, inline/display mathematics, code, details, lists, and quotations through normalization and final request construction without a parallel block renderer. Impact: supported Rich content crosses both transport roles with its typed shape and native structure intact.
|
|
31
|
+
- `Rich Client Evidence`: Post-reload direct-leader and follower Telegram-turn smokes confirmed one queued PNG plus final Markdown arrives as one reply-anchored composite result with no duplicate upload, attachment notice, or final. A separate live probe confirmed HTTPS photo/video/audio composites plus multipart and cached-`file_id` voice notes, with independently requested voice notes remaining separate messages. Impact: the bounded integration has deterministic transport coverage and direct client evidence across leader and follower paths.
|
|
32
|
+
- `Rich Output Contracts And Reference`: Documented single-artifact behavior, ownership, targeting, compatibility fallbacks, ambiguity no-replay, and evidence boundaries across operator and maintainer docs. Synchronized the vendored Bot API reference with 10.2 Rich Message blocks/media, voice notes, Ephemeral Messages, Communities, subscription updates, and refreshed lookup indexes while preserving its full-reference structure. Impact: implementation and review can resolve current behavior and API fields locally without overstating broader Bot API possibilities.
|
|
4
33
|
|
|
5
34
|
## 0.22.1: Termux-Compatible Filesystem Transactions
|
|
6
35
|
|
|
7
|
-
- `Filesystem Transactions`: Replaced hard-link guard publication with private staged directories containing complete exact-owner metadata, atomically published each non-empty guard by same-parent rename, and atomically renamed exact-owned guards away before recursive cleanup. Bound each owner generation into a unique filename so delayed stale observations cannot claim replacement metadata, retained exact stale recovery through recoverable internal claimant markers, retried reclaim and rollback moves without peer-visible stalls, released newly recovered ownership when secondary cleanup fails, kept fail-closed malformed-state handling, and bounded legacy regular-file guard recovery. Impact: shared Telegram state no longer requires the hard-link operation rejected by Android/Termux, while exactly-one cross-process ownership remains locally enforced;
|
|
36
|
+
- `Filesystem Transactions`: Replaced hard-link guard publication with private staged directories containing complete exact-owner metadata, atomically published each non-empty guard by same-parent rename, and atomically renamed exact-owned guards away before recursive cleanup. Bound each owner generation into a unique filename so delayed stale observations cannot claim replacement metadata, retained exact stale recovery through recoverable internal claimant markers, retried reclaim and rollback moves without peer-visible stalls, released newly recovered ownership when secondary cleanup fails, kept fail-closed malformed-state handling, and bounded legacy regular-file guard recovery. Impact: shared Telegram state no longer requires the hard-link operation rejected by Android/Termux, while exactly-one cross-process ownership remains locally enforced; subsequent reporter confirmation established that the released path works on Termux.
|
|
8
37
|
- `Diagnostics`: Contained synchronous and queued JSONL persistence failures inside the diagnostics boundary while preserving later queued records after a failed append or rotation. Impact: an unavailable diagnostics path cannot terminate Pi through an unhandled rejection or permanently poison subsequent runtime evidence.
|
|
9
38
|
|
|
10
39
|
## 0.22.0: Concurrency And Runtime Ownership Hardening
|
|
11
40
|
|
|
12
|
-
- `Composition Root`: Reduced `index.ts` from 1,534 to 1,077 lines by moving transport generations, Threaded Mode orchestration,
|
|
13
|
-
- `
|
|
14
|
-
- `
|
|
15
|
-
- `
|
|
16
|
-
- `
|
|
17
|
-
- `
|
|
18
|
-
- `
|
|
19
|
-
- `
|
|
20
|
-
- `
|
|
21
|
-
- `
|
|
22
|
-
- `Profile Handoff`: Preserved the selected profile identity across config reload, reordered profile activation so the old runtime stops before the new profile becomes visible, and stamped queue/final/Activity/Delivery work with immutable profile transport generations. Named-profile setup follows the same stop-old then commit-new order after validation. Impact: accepted same-profile work survives lock handoff, while old-profile work cannot use a replacement bot token or target.
|
|
23
|
-
- `Session Generation Fence`: Added one session generation registry across lifecycle contexts and guarded agent/tool/message, compaction, preview, final-delivery, control-dispatch, and shutdown paths against replacement contexts. Preview operations invalidate by generation, stale scheduled finals drop before delivery, shutdown rechecks after polling/preview awaits, and preview cleanup has a bounded wait. Impact: delayed callbacks from an old Pi session cannot clear or deliver through its replacement session.
|
|
24
|
-
- `Target And Ownership Scope`: Preserved immutable chat/thread targets on model-switch continuations, made public Delivery handles deeply frozen and privately bound, and scoped message ownership by bot profile plus exact follower registration generation. Forwarded traffic carries that generation, and follower edit/delete requires exact recorded message ownership. Impact: callers cannot retarget handles, and delayed or sibling follower traffic cannot mutate replacement-owned messages.
|
|
25
|
-
- `Idempotent Inbound Admission`: Retained successfully handled update ids until their polling offset commits, so config-write retries advance offsets without re-running update side effects. Deferred media albums and split-text groups now retain their exact message sets through asynchronous admission failures, suspend across session replacement, rebind to the replacement context, and retry until dispatch succeeds. Impact: polling/config retries and transient queue failures no longer duplicate accepted prompts or silently lose grouped input.
|
|
26
|
-
- `Method-Aware Bot API And Bus Retries`: Classified retry-safe Bot API methods separately from non-idempotent sends/uploads/topic creation. The local bus memoizes in-flight/completed request results, rejects request-id collisions, preserves structured ambiguity, and maps missing acknowledgements for non-idempotent follower calls to `TelegramApiCommitUnknownError`. Delivery exposes `commit-unknown` with any recoverable partial handle. Impact: response loss cannot silently authorize blind replay of messages, media, registrations, forwarded updates, or topic creation.
|
|
27
|
-
- `Secondary Shared State`: Revision-serialized same-process thread snapshots, serialized JSONL append/reset/rotation across processes, restricted destructive log reset to the exact transport owner, identity-checked malformed-config quarantine under the config transaction, and replaced bare PPID follower identity with process-birth identity (`/proc` start ticks where available, collision-resistant generation fallback elsewhere). Impact: delayed writers and PID reuse cannot erase newer state or reinterpret stale follower ownership.
|
|
28
|
-
- `Live Runtime Smoke`: After one clean process restart loaded the process-global generation fix, a local `/reload` retained the assigned leader thread, restored Telegram automatically, and accepted the next Telegram message without `/telegram-connect`, follower fallback, or takeover. A post-refactor private Guest Mode exchange then preserved `[telegram|guest:<remote-peer>]`, delivered a generated voice response, and delivered one requested text attachment through the one-result guest path. Impact: the stale self-lock belonged to the pre-fix runtime-generation collision rather than persistent state corruption, while the composition refactor preserves live thread, Guest identity, voice, and artifact delivery.
|
|
41
|
+
- `Composition Root And Validation`: Reduced `index.ts` from 1,534 to 1,077 lines by moving transport generations, Threaded Mode orchestration, request/ownership identity, synchronization, provisioning, lifecycle, diagnostics, Delivery policy, inbound authority, follower forwarding, and retry policy into their owning flat domains. Domain regressions and structural guards prohibit local runtime adapters, direct Node imports, dependency cycles, and leaf-domain drift. The release gate passed 1,229 tests with one platform-only skip, typecheck, strict Domain DAG, ABCd, invariants, package dry-run, zero-vulnerability audit, and `git diff --check`. Impact: the entrypoint retains high-level composition while release-critical policy remains independently testable and structurally enforced.
|
|
42
|
+
- `Lock Transactions And Fencing`: Serialized all `locks.json` acquisition, refresh, release, and dead-owner recovery through fail-closed cross-process transactions while preserving unrelated profile keys. Collision-resistant leader epochs, exact owner/profile retention, and time-monotonic same-process generations fence forced replacement, refresh, release, direct transport, state persistence, and reload handoff. Impact: concurrent or stale runtimes cannot both win ownership, reverse a replacement, mutate through replacement transport, or corrupt the shared registry.
|
|
43
|
+
- `Leader Startup And Follower Election`: Started exact-owner heartbeat refresh immediately after lock acquisition and retained it through binding handoff, provisioning, server startup, and polling; startup failure cleans up and releases ownership. Followers remain in re-registration recovery while an exact live lease exists, promote only after atomic stale/no-owner acquisition, and losers re-register with the winner using their carried target. Impact: slow startup, transient IPC loss, simultaneous followers, and session handoff cannot create split-brain polling, strand a follower, or duplicate its thread.
|
|
44
|
+
- `Reconciliation And Provisioning Fences`: Revalidated the stamped leader epoch before every destructive Bot API call, local deletion mutation, cleanup persistence, and provisioning boundary; missing ownership fails closed. Thread snapshots commit through exact lock transactions, pending creation intents survive displacement as serialized recovery evidence, and the next owner adopts successfully created targets without replaying creation. Impact: stale leaders cannot delete replacement-owned threads or publish authoritative bindings, while successful topic creation survives handoff without duplicates or deadlock.
|
|
45
|
+
- `Bus Endpoint Generations`: Bound each Unix bus server to a private generation socket and atomically published the stable profile endpoint as a relative symlink. Stop removes only its private path, replacement links remain intact, and startup waits for live legacy direct-socket servers before migration. Impact: delayed old-server teardown cannot unlink the replacement leader endpoint.
|
|
46
|
+
- `Configuration, Profiles, And Shared State`: Transactional `telegram.json` writers merge recursive deltas onto the latest snapshot, preserve monotonic profile offsets, and quarantine malformed config only behind identity-checked guards. Profile switching keeps the selected identity stable, stops old transport before exposing the new profile, and stamps queued/Activity/Delivery work with immutable profile generations. Revisioned thread snapshots, serialized JSONL rotation, owner-only destructive log reset, and process-birth follower identity protect secondary state. Impact: concurrent writers, delayed callbacks, profile replacement, and PID reuse cannot regress offsets, erase newer state, or reinterpret stale ownership.
|
|
47
|
+
- `Session, Target, And Message Ownership`: Added one generation registry across lifecycle, compaction, preview, final delivery, control dispatch, and shutdown so stale callbacks drop before touching replacement sessions. Model-switch continuations retain immutable targets; Delivery handles remain deeply frozen and privately bound; message ownership includes bot profile and exact follower registration generation. Impact: old sessions and sibling/replaced followers cannot retarget work, clear replacement state, or edit/delete messages they no longer own.
|
|
48
|
+
- `Idempotent Inbound Admission`: Retained handled update ids until polling-offset commit and preserved exact deferred album/split-text message sets across admission failure and session replacement. Suspended groups rebind to the replacement context and retry until dispatch succeeds. Impact: config retries and transient queue failures do not duplicate accepted prompts or silently lose grouped input.
|
|
49
|
+
- `Method-Aware Retry Safety`: Classified retry-safe Bot API methods separately from non-idempotent sends, uploads, and topic creation. The local bus memoizes request results, rejects id collisions, preserves ambiguity, maps missing non-idempotent acknowledgements to `TelegramApiCommitUnknownError`, and exposes `commit-unknown` with recoverable partial handles. Impact: response loss cannot authorize blind replay of messages, media, registrations, forwarded updates, or topic creation.
|
|
50
|
+
- `Live Runtime Evidence`: After a clean restart, local `/reload` retained the assigned leader thread, restored Telegram automatically, and accepted the next message without reconnect, follower fallback, or takeover. A private Guest Mode exchange preserved guest attribution and delivered generated voice plus a requested attachment through the one-result path. Impact: live evidence confirmed generation recovery, thread identity, Guest routing, voice, and artifact delivery after the hardening refactor.
|
|
29
51
|
|
|
30
52
|
## 0.21.1: Runtime And Session Semantics Hotfix
|
|
31
53
|
|
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
It is a **runtime adapter**, not a remote terminal. Start or supervise work in the Pi TUI, then continue from Telegram while away from the keyboard. Each Telegram destination follows a running Pi instance and sends prompts into that instance's currently active session; it is not permanently bound to one session file or session identity. The bridge preserves Pi session semantics instead of pretending Telegram is a PTY, shell, process launcher, or session browser. That boundary is the product: Telegram gets safe runtime handles, not raw terminal power.
|
|
10
10
|
|
|
11
|
+
Proactive push is enabled by default. `assistant.proactivePush` projects every completed public assistant text block from local or autonomous work—including visible checkpoints and the final answer—to the authorized Telegram target once and in order; set it explicitly to `false` to disable projection. It never mirrors local prompts, hidden reasoning, tool traffic, token deltas, Telegram-owned turns, or stale-generation work. See [Outbound](docs/outbound.md#proactive-public-output) and the [configuration reference](docs/public-api.md#configuration-api).
|
|
12
|
+
|
|
11
13
|
This repository is an actively maintained fork of [`badlogic/pi-telegram`](https://github.com/badlogic/pi-telegram). It started from upstream commit [`cb34008`](https://github.com/badlogic/pi-telegram/commit/cb34008460b6c1ca036d92322f69d87f626be0fc) and has since diverged substantially.
|
|
12
14
|
|
|
13
15
|
## Install
|
|
@@ -150,7 +152,7 @@ Run these inside Pi.
|
|
|
150
152
|
| `/telegram-setup <profile>` | Save or update a named-profile bot token |
|
|
151
153
|
| `/telegram-connect` | Activate the default profile and acquire its transport ownership |
|
|
152
154
|
| `/telegram-connect <profile>` | Activate a named profile and acquire its transport ownership |
|
|
153
|
-
| `/telegram-disconnect` | Stop polling and release ownership |
|
|
155
|
+
| `/telegram-disconnect` | Stop polling and release ownership; in Threaded Mode, confirm deletion of this instance's current Telegram thread |
|
|
154
156
|
| `/telegram-status` | Inspect connection, mode, queue, transport, and recent diagnostics |
|
|
155
157
|
|
|
156
158
|
Named profile identifiers contain only lowercase ASCII letters and digits (maximum 32 characters); `default`, `main`, and `active` remain reserved.
|
package/docs/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Living index of project documentation in `/docs`.
|
|
|
14
14
|
- [../.agents/skills/domain-dag/SKILL.md](../.agents/skills/domain-dag/SKILL.md) — Project-local Domain DAG architecture skill and validator guidance
|
|
15
15
|
- [command-templates.md](./command-templates.md) — Portable command-template standard core
|
|
16
16
|
- [inbound.md](./inbound.md) — Local `pi-telegram` inbound text/media handler bus, programmatic inbound handlers, registered STT provider fallbacks, legacy `attachmentHandlers` compatibility, placeholders, and fallbacks
|
|
17
|
-
- [outbound.md](./outbound.md) — Local `pi-telegram` outbound-handler config, text/voice/button behavior, voice synthesis provider fallback priority, artifact outputs, and callback routing
|
|
17
|
+
- [outbound.md](./outbound.md) — Local `pi-telegram` outbound-handler config, text/voice/button behavior, single-artifact Rich results, voice synthesis provider fallback priority, artifact outputs, and callback routing
|
|
18
18
|
- [callback-namespaces.md](./callback-namespaces.md) — Shared Telegram `callback_data` namespace standard for layered extensions
|
|
19
19
|
- [updates.md](./updates.md) — Update classification and runtime handler registry that lets layered extensions observe and consume Telegram updates without owning their own polling connection
|
|
20
20
|
- [multi-instance-bus.md](./multi-instance-bus.md) — Optional multi-instance Telegram bus architecture: profile-scoped transport, leader/follower routing, thread targets, instance slots, manual follower registration, and recovery semantics
|
package/docs/activity.md
CHANGED
|
@@ -234,6 +234,14 @@ Pi lifecycle must not wait for extension rendering or Telegram transport.
|
|
|
234
234
|
|
|
235
235
|
The Delivery API independently serializes concrete Telegram operations per target. Activity serialization preserves semantic event order; delivery serialization preserves transport order.
|
|
236
236
|
|
|
237
|
+
### Core proactive projection
|
|
238
|
+
|
|
239
|
+
When `assistant.proactivePush` is enabled, Activity's built-in assistant-output projection uses the same normalized `assistant-segment` boundary exposed to public handlers. It projects every completed public block from `local` or `autonomous` activity, including intermediate commentary/checkpoints and the final block. It never projects text token deltas, reasoning events, tool events or payloads, Telegram-owned activity, unknown-source activity, or empty text.
|
|
240
|
+
|
|
241
|
+
The projection does not delay Activity dispatch or Pi lifecycle. Its ordered admission tail deduplicates normalized event identity, while existing routing and outbound owners revalidate the immutable admission-time target, profile/token transport generation, direct leader epoch or follower registration generation, and session generation immediately before each send. A replacement or stale owner drops queued work rather than rerouting it. An already-started non-idempotent Bot API mutation follows the normal `commit-unknown` no-replay contract.
|
|
242
|
+
|
|
243
|
+
This projection consumes ordinary normalized Pi output. It has no runtime dependency on any workflow, loop, or companion extension that produced the public blocks.
|
|
244
|
+
|
|
237
245
|
## Lifecycle Mapping
|
|
238
246
|
|
|
239
247
|
The bridge maps Pi hooks as follows:
|