@llblab/pi-telegram 0.54.3 → 0.54.4

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.
@@ -6093,9 +6093,11 @@ export function createTelegramCurrentThreadAssembly(deps) {
6093
6093
  deps.isFollowerRegistered()) {
6094
6094
  return deps.getFollowerDisplayTitle?.();
6095
6095
  }
6096
- return deps
6096
+ const binding = deps
6097
6097
  .listWorkspaceBindings?.()
6098
- .find((binding) => targetMatches(binding.target, target))?.displayTitle;
6098
+ .find((value) => targetMatches(value.target, target));
6099
+ return (binding?.displayTitle ??
6100
+ (binding ? deps.resolveAutomaticDisplayTitle?.(binding) : undefined));
6099
6101
  };
6100
6102
  const displayIdentity = (identity) => {
6101
6103
  const title = identity.target
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.54.3",
3
+ "version": "0.54.4",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -446,7 +446,7 @@ Fresh follower binding is manual and process-first: the operator starts another
446
446
 
447
447
  ### Unbound Thread Detection
448
448
 
449
- For ordinary unbound prompts outside the source-bound temporary lifecycle below, Threaded Mode can expose a new thread without an existing instance binding when the owner writes in `All`. The bridge detects this during update execution: if a message from the owner has a `message_thread_id` that no instance owns, the message is routed to the unbound-thread handler instead of the leader's normal message handler. In the default runtime, this handler first reclaims the thread for the leader when the leader has no active bound thread, assigns the current leader thread identity, persists the active binding, and serves the prompt locally. If the leader already has an active thread, the handler preserves the prompt in the source Telegram thread and shows the complete forward plus replace/restore chooser. Successful forward deletes the chooser and closes/deletes the confirmed temporary source through `thread-reconciler` proof-before-delete planning and stale-epoch fencing. Successful restore always deletes the chooser, rebinds the source thread to the selected Pi instance, and closes/deletes only that instance's replaced old thread. If foreign batch forwarding partially fails, retry sends only the remaining messages before cleanup. If Telegram cannot confirm thread or chooser deletion, the chooser becomes a cleanup-only or deletion-only retry control so already-routed content never dispatches twice and no visible button expires prematurely. Unknown `forum_topic_created` service events are recorded as observations and are not destructive cleanup proof, because Telegram can deliver creation events before local provisioning/binding writes become visible across reloads. If Threaded Mode is unavailable, the message is processed normally through classic routing.
449
+ For ordinary unbound prompts outside the source-bound temporary lifecycle below, Threaded Mode can expose a new thread without an existing instance binding when the owner writes in `All`. The bridge detects this during update execution: if a message from the owner has a `message_thread_id` that no instance owns, the message is routed to the unbound-thread handler instead of the leader's normal message handler. In the default runtime, this handler first reclaims the thread for the leader when the leader has no active bound thread, assigns the current leader thread identity, persists the active binding, and serves the prompt locally. When the leader session already owns a Workspace binding, that binding moves to the reclaimed tab with its slot and name (no second slot), loses its stale acknowledged title and inactivity mark, and the tab is renamed once to its display-mode title. A binding whose tab has no acknowledged title shows its display-mode title, never only its generated name. If the leader already has an active thread, the handler preserves the prompt in the source Telegram thread and shows the complete forward plus replace/restore chooser. Successful forward deletes the chooser and closes/deletes the confirmed temporary source through `thread-reconciler` proof-before-delete planning and stale-epoch fencing. Successful restore always deletes the chooser, rebinds the source thread to the selected Pi instance, and closes/deletes only that instance's replaced old thread. If foreign batch forwarding partially fails, retry sends only the remaining messages before cleanup. If Telegram cannot confirm thread or chooser deletion, the chooser becomes a cleanup-only or deletion-only retry control so already-routed content never dispatches twice and no visible button expires prematurely. Unknown `forum_topic_created` service events are recorded as observations and are not destructive cleanup proof, because Telegram can deliver creation events before local provisioning/binding writes become visible across reloads. If Threaded Mode is unavailable, the message is processed normally through classic routing.
450
450
 
451
451
  Unsettled plain-text choosers retain their route control while the journal source is deferred: creating a later chooser does not prune them after 30 minutes. Selected routes that still owe cleanup likewise keep their retry control. Age alone neither abandons nor dispatches a source; only the positively armed source-only lifetime below permits its exact expiry disposition. The existing 100-chooser capacity bound refuses additional allocation rather than evicting unresolved work. The production source-bound All-command path keeps its original deferred after chooser publication and uses the temporary lifecycle below. A compatibility chooser remains for callers without the required authority/store/transport composition; it retains its older publication-completion and command-expiry semantics, not the new temporary-tab guarantees. Historical orphaned originals stay held; no owner recovery path is scheduled.
452
452
 
@@ -476,15 +476,15 @@ With live bindings or a retained temporary entry, a known command or a fresh pri
476
476
 
477
477
  #### Temporary Thread Multi-Input Lifecycle (Approved Design)
478
478
 
479
- Every temporary routing tab carries one fixed routing name ([copy](./ui-style.md#route-chooser)), never its first input, because later prompts may join it: the bot creates its own tabs with that name and renames a native tab once, best-effort, when it adopts it. Commands and prompts share the same mode chooser. A command delivered with a Telegram-provided unbound private target uses the same chooser and fresh exact-source Cancel capability in that tab, without renaming it or creating another one. Mobile clients sending from `All` first create an implicitly named native tab and then deliver the input itself to `All`: an `All` input whose text starts with the name of such an observation from the same exact scope, created at most ten seconds earlier, registers that native tab as its temporary tab through `registerImplicitTemporaryThread` instead of creating a second one, and renames it once to the routing name. A received target or successful rename alone never manufactures temporary authority. A fresh owner-authenticated private `forum_topic_created` with literal `is_name_implicit: true` supplies native creation evidence: routing retains a bounded process-local observation tied to the exact context, session generation, admission scope, operator, journal binding and executor. The next live input group in that exact target, of any content kind and either one message or an album, registers it through Threads `registerImplicitTemporaryThread` under profile admission, with that group as the entry's first input (its smallest update ID is the source), without calling or fabricating `createForumTopic` (adoption renames it once, best-effort); the publication CAS refuses any binding, owner record, reservation, provisioning, cleanup or Restore conflict. Normal grouped membership, one-shot Forward, source disposition, last-Cancel quiet period and cleanup issuance then apply identically. Historical creation signals, context/epoch changes, manual names, string flags, foreign creators and missing observations grant nothing. The observation is consumed on registration, never reconstructed from a title or restart. Existing bindings/reservations and acknowledged temporary targets retain their protections. Their root chooser offers Reroute, Restore and eligible Cancel; each mode opens its own submenu of live thread targets without dispatching the held input. A submenu Back returns to the mode chooser with its exact original description, retained on that pending chooser; navigation never substitutes or recomputes the root copy. Copy, emoji and button layout live in [UI Style](./ui-style.md#route-chooser). Cancel appears only at the root. After cancellation, the immediate cleanup remains proof-gated. Private-chat temporary tabs skip the unsupported `closeForumTopic` call and issue only one `deleteForumTopic` attempt; literal positive deletion acknowledgement is still required, and uncertainty never authorizes a retry. A menu-picked command arrives threadless, so its original stays in All while the bot-created tab carries the chooser; once that input is consumed (Forward finalization, Restore dispatch, explicit or Restore-sibling Cancel, or chooser expiry, including after restart), the bot makes one best-effort `deleteMessage` of the All copy. Failure is recorded and never retried. Pending-cancellation recovery deletes nothing, and typed All inputs already live in their own tab and leave with it.
479
+ Every temporary routing tab carries one fixed routing name ([copy](./ui-style.md#route-chooser)), never its first input, because later prompts may join it: the bot creates its own tabs with that name and renames a native tab once, best-effort, when it adopts it. Commands and prompts share the same mode chooser. A command delivered with a Telegram-provided unbound private target uses the same chooser and fresh exact-source Cancel capability in that tab, without renaming it or creating another one. Mobile clients sending from `All` first create an implicitly named native tab and then deliver the input without a thread ID, although the client shows it inside that tab: an `All` input whose text starts with the name of such an observation (mobile names the tab after the input), or whose update immediately follows it (generic names such as desktop's `New Chat`), from the same exact scope and created at most ten seconds earlier, registers that native tab as its temporary tab through `registerImplicitTemporaryThread` instead of creating a second one, and renames it once to the routing name. A received target or successful rename alone never manufactures temporary authority. A fresh owner-authenticated private `forum_topic_created` with literal `is_name_implicit: true` supplies native creation evidence: routing retains a bounded process-local observation tied to the exact context, session generation, admission scope, operator, journal binding and executor. The next live input group in that exact target, of any content kind and either one message or an album, registers it through Threads `registerImplicitTemporaryThread` under profile admission, with that group as the entry's first input (its smallest update ID is the source), without calling or fabricating `createForumTopic` (adoption renames it once, best-effort); the publication CAS refuses any binding, owner record, reservation, provisioning, cleanup or Restore conflict. Normal grouped membership, one-shot Forward, source disposition, last-Cancel quiet period and cleanup issuance then apply identically. Historical creation signals, context/epoch changes, manual names, string flags, foreign creators and missing observations grant nothing. The observation is consumed on registration, never reconstructed from a title or restart. Existing bindings/reservations and acknowledged temporary targets retain their protections. Their root chooser offers Reroute, Restore and eligible Cancel; each mode opens its own submenu of live thread targets without dispatching the held input. A submenu Back returns to the mode chooser with its exact original description, retained on that pending chooser; navigation never substitutes or recomputes the root copy. Copy, emoji and button layout live in [UI Style](./ui-style.md#route-chooser). Cancel appears only at the root. After cancellation, the immediate cleanup remains proof-gated. Private-chat temporary tabs skip the unsupported `closeForumTopic` call and issue only one `deleteForumTopic` attempt; literal positive deletion acknowledgement is still required, and uncertainty never authorizes a retry. The Bot API files every threadless All input in All, a desktop menu-picked command as well as a mobile input its client first draws inside its new native tab. So a tab opened for such an input (bot-created or adopted, never on restart re-entry) first receives one silent best-effort `forwardMessage` of it, its chooser replies to that copy, and only a confirmed copy lets the bot delete the All original at once: the input moves into its tab on every client. A failed forward keeps the original in All; it is then deleted best-effort when the input is consumed by Forward, Cancel or expiry, which remove the tab too, but never by Restore, which keeps the tab as the Thread and must not leave it without its input. Failure is recorded and never retried. Pending-cancellation recovery deletes nothing, and typed All inputs already live in their own tab and leave with it.
480
480
 
481
481
  This multi-input lifecycle is implemented in the local candidate, not authority for live deletion. It supersedes unconditional per-chooser tab removal. The reject-only guard prevents cleanup/retirement while another known same-tab chooser remains, including unresolved cleanup-only work. `temporaryThreads.inputs` now records bounded append-only journal source groups through the unbound producer before chooser publication; duplicates are read-only, partial/foreign/overlapping groups refuse, and faults retain the source without a new chooser. Cold reads preserve this protection without process-local chooser memory. A group has no readiness or disposition flag, and an absent legacy field does not prove complete coverage. `cancelledInputs` records exact whole known donor groups after either same-journal/operator retained-cancellation evidence or the separate body-free expiry tombstone. Expiry recording may clear a group's unknown Forward issuance or its expired Restore intent, but never mutates a canonical binding, recipient queue or immutable scoped settlement proof. If Restore already bound the tab, expiry releases the temporary frame without deletion. Otherwise fully resolved groups enter the existing one-second quiet-period cleanup. A partially acknowledged known cohort may finish donor disposition when every member is absent from that exact source journal and at least one member has same-operator expiry proof; this is not recipient completion or non-delivery proof. Fresh strict namespace/protection evidence still gates deletion; independent work and bound Threads remain protected. Store publication and returned/retained acknowledgement recheck that proof and operator/executor authority; a lost reply reconciles the exact fact read-only, while missing/foreign/corrupt proof stays protective. The producer composes this into explicit Cancel before temporary cleanup or chooser retirement. Cancelled groups cannot Restore; independent sources remain untouched. More than one known group still blocks cleanup and raw retirement, including all-cancelled membership: these facts grant no deletion. Each successful Cancel retires only its own chooser. If durable membership is fully resolved by exact cancellation/completion facts and no chooser remains, a process-local cleanup is queued with no grace delay (it still waits behind the resolving operation's Workspace admission, which Cancel releases right after its notice, answering the toast and deleting any All copy alongside the cleanup; new input in the tab before it runs cancels it). Its single attempt, under profile admission, rechecks authority, whole-group cancellation/expiry evidence, exact entry CAS, a strict per-Thread journal census (every namespace source must read strictly; any retained update naming the Thread in any state, any of its own groups, and any custody-bearing, failed or non-plain entry anywhere still protect; only an unrelated waiting plain message, edit, callback or reaction elsewhere is exempt, so a chooser held in another tab cannot pin every temporary tab, and so is a plain button tap in this tab, since it carries no input and the in-flight Cancel itself may outlast the quiet period), absence of choosers and the normal reconciler protections. `issueTemporaryThreadCleanup` publishes canonical `cleanupIssued: true` only for an exact fully resolved entry with strict unbound canonical evidence checked inside the publication transaction; the normal target/source guards precede issuance and run again at API boundaries. The reconciler requires that exact retained issued frame and limits its plan to this tab; unrelated expired-provision cleanup never borrows the grant. Close/delete calls disable retries and transport fallback and require a positive acknowledgement. Confirmed current deletion alone permits entry retirement; skip, negative/lost reply, authority loss or post-rename publication interruption retains the marker. Cold reads classify already-issued entries as unknown, new membership refuses, and executor adoption never renews the grant. Missing evidence only refuses; an unpublished grant issues no API call and may still receive its first grant. The quiet period remains process-local, but issued uncertainty survives restart. The census cannot see updates in transit before journal append. Restore from a multi-input tab is allowed only when each other known group is durably cancelled, Forward-completed or a live unselected chooser that supports private abandonment. A previously completed Forward never supplies a new selection or cancellation grant. After positive settlement of every selected source, such siblings are abandoned with cancellation facts and their controls edited to cancelled, then `retireTemporaryThread(expected, authority, completed)` releases the entry (one newly completed selected group, all others durably cancelled or previously Forward-completed). Missing authority/proof or an unproven Restore leaves siblings pending and protected. On startup, a retained journal input recorded in a created temporary tab whose target a Workspace binding now owns is held as historical, never rerouted; an exact saved chooser clock may expire its unclaimed donor attempt without affecting the binding. Live pending sources and due retry-wait sources (including startup retries) use the same fresh membership/binding predicate through `shouldHoldPendingInput` before execution, with source/context/binding/transport checks after classification awaits. The hold grants neither cancellation nor delivery and does not block unrelated inputs. Recipient custody never uses this direct-source classifier. A native production witness admits a recorded command after the worker has already processed an unrelated command, proving it cannot fall through to ordinary bound dispatch. A production retry witness keeps recorded failure metadata intact without command execution, cancellation or deletion. Claimed retries cannot schedule repeated due-time wakes; independent due and future retries remain executable. Real-IPC Forward/Restore sibling witnesses now include recipient journal drain through the real worker and follower router: only the selected message executes, source disposal is observed, and repeated controls do not replay the command. Existing message-ownership composition keeps a locally published chooser's callbacks at its leader publisher after follower rebinding; the native fixture now records publication ownership rather than relying only on target ownership. Four native delivery-reply-loss witnesses cover both routing actions (Forward/Restore), before recipient execution and after worker disposal: the leader source remains unresolved without ACK, independent sibling Cancel preserves the selected group/tab, and a late reply or repeated issued Restore click cannot settle/replay it. Explicit Forward retry after a lost ACK reproduced command duplication when the recipient worker had already disposed its journal record; stable delivery identity alone was not terminal deduplication. The follower's bounded process-local delivery window now acknowledges such a retry without re-execution while it retains that delivery. Ordinary Forward from a temporary tab first publishes a durable per-group `forwardedInputs` issuance fact in the canonical Threads owner under current authority, before either local command handling/prompt preparation/queue admission or follower RPC. Only positively unpublished issuance may receive its first grant; a published fact (even if its reply is ambiguous) is never retried, including after reload, restart or leader replacement. Local repeated selection refuses the retained grant before handler or queue re-entry. Captured All-tab provenance and acknowledged typed-input membership remain protective if the metadata reader disappears; unavailable evidence never falls back to unguarded generic dispatch. Restore retains its separate canonical routing grant. Native local fixtures interrupt source completion before/after commit, prompt preparation and issuance publication/authority; issued sources stay nonterminal, independent siblings remain cancellable and cold restart never manufactures completion or another attempt. A process-local `forward-unknown` chooser phase gates the live chooser. An issued group is refused by repeat Forward, explicit Cancel and Restore (the store also rejects them); before its fixed chooser deadline it keeps sibling accounting protective, while body-free donor expiry or positive `completedInputs` may resolve it; independent siblings remain operable and a restarted chooser cannot resend. Issuance is not proof of delivery, and recipient-side deduplication is limited to the bounded process-local delivery window; the chooser deadline supplies a loss-accepting exit for unclaimed unknown issued groups; generic journal forwarding and non-chooser retry policy are unchanged. Canonical Restore keeps its existing durable grants. Mixed native witnesses prove that a completed Forward allows Restore of another group without replay/recancellation, while an unknown Forward prevents sibling Restore before an intent is created. Prompt Restore leaves a command sibling pending at queue admission, then privately cancels it only after exact scoped worker receipt disposition. Independently Forwarded queued input is neither unassigned nor cancellation-capable: either receipt may dispose first, the other retains custody, and only both positive dispositions release temporary membership while keeping the bound tab/slot. An ordinary queued ACK emits the existing source-completion hint only after whole acknowledged removal and fresh context, owner, generation and originating journal-binding checks; it creates no immutable Restore marker. A published independent Forward group fact wakes pure inspection of already settled Restore only after its Workspace admission releases. The store refuses both Forward facts and cancellation for any source overlapping a retained Restore grant, regardless of target; malformed partial selected groups stay protective. Native ordinary prompt Forward plus sibling Cancel also delays deletion until receipt disposition and fresh all-resolved cleanup. Pi handoff is supplied by the fixture, not observed model/task completion. The retained All-command fixture registers 84 scenarios across creation, Forward, Cancel, cleanup/membership and Restore, including full slots, publication faults, sibling protection, native Unix IPC and recipient worker drain through the real non-reentrant Workspace gate. Full-slot follower cases hold a real Unix response before apply, after apply, and after read-only inspection until client timeout. Fresh same-session registration/context generations reconcile only through inspection; late replies never publish readiness, repeat apply or dispatch, cancel siblings, release independent queued recipient work or delete the tab. Successful continuation retains exact scoped source-disposition proof. A separate cold-successor witness loads fresh stores and a journal binding after an issued command lost acceptance authority, then starts a fresh context/generation/epoch under supplied same-session owner publication. Original command and sibling stay pending, spent routing/canonical facts remain unchanged, and stale Restore/Forward/Cancel controls cannot replay or dispose them. No chooser is reconstructed: the protective hold is proven, not a usable cold recovery surface. Pi effects and startup registration remain supplied adapters/preconditions; actual Pi startup and live behavior were accepted in the operator's 0.52.0 live smoke. Forward completes only its own input: no Forward removes the tab, the worker's completion ACK records a durable `completedInputs` group (process-local accumulation across the group's sources), and when every known group is cancelled or completed the delayed, fully rechecked cleanup removes the tab once. Cancelled and completed groups are disjoint, cannot Restore, and a completed Forward queued prompt holds the tab until its receipt completes. The tab chooser discloses removal only when no other input remains; a failed removal no longer edits the original chooser. Source-only prompt Cancel remains unchanged. The candidate's temporary-tab chooser is the default under the owned leader epoch, not a configurable feature flag. Upgrade all prospective canonical readers/writers before live activation; the metadata (including `forwardProtocol`, `forwardedInputs` and `cleanupIssued`) is not downgrade-safe through older snapshot writers, which could drop issuance facts.
482
482
 
483
483
  - `Membership`: A positively source-linked disposable temporary Thread owns no Pi binding or A–Z slot and may contain multiple independent unassigned inputs, each with its own routing controls. Track exact journal source/group membership, including additional inputs, rather than treating the first chooser or a process-local count as the whole Thread. Serialize admission, selection, cancellation, Restore and cleanup through the existing Workspace admission and mutation owners; unreadable, incomplete, selected, accepted, running or unknown-issued evidence blocks deletion.
484
- - `Per-input Cancel`: Privately retain the exact original and positively commit source-only cancellation before acknowledging `Routing cancelled` and retiring its route controls. Other unassigned inputs retain their choosers and custody; cancelling one input neither delivers nor cancels siblings. No separate Cancel-and-delete action or extra confirmation dialog is required, but the chooser must disclose the conditional whole-tab deletion consequence.
485
- - `Last-resolution cleanup`: Only an explicit Cancel or positive completion of an explicitly issued Forward that resolves the final known group may schedule deletion of the entire still-temporary Telegram tab, including all its messages. The operator-approved grace is now zero, superseding the earlier 1-second and 2–3-second designs: deletion is attempted right after the cancellation notice. For Last Cancel, scheduling follows positively recorded whole-group cancellation and successful retirement of the final chooser, not the initial button tap. The timer establishes earliest eligibility for one proof-gated cleanup attempt, not an exact deletion time: Workspace admission, fresh protection checks and Bot API latency may delay or refuse it. Actual client timing was accepted in the operator's 0.52.0 live smoke. Mirrored native witnesses use shortened injected delays, not a production-default or Telegram-client timing measurement. This grace is presentation timing, not proof of emptiness or non-delivery. New input or a changed binding/authority cancels the scheduled cleanup. Immediately before issuance, reacquire current Workspace admission, recheck exact operator/profile/session/leader/source/target authority, positive disposable-tab provenance, complete source dispositions and all Thread protection, then publish the canonical one-shot temporary cleanup grant. A restored/bound Thread or any unresolved/unverifiable work remains protected. TTL, startup, silence and a zero chooser count do not manufacture this last-Cancel grant. Failed/unknown deletion cannot undo cancellation or cause automatic repeat issuance.
484
+ - `Per-input Cancel`: Privately retain the exact original and positively commit source-only cancellation before acknowledging `Routing cancelled` and deleting its one-shot chooser message. Other unassigned inputs retain their choosers and custody; cancelling one input neither delivers nor cancels siblings. No separate Cancel-and-delete action or extra confirmation dialog is required, but the chooser must disclose the conditional whole-tab deletion consequence.
485
+ - `Last-resolution cleanup`: Only an explicit Cancel or positive completion of an explicitly issued Forward that resolves the final known group may schedule deletion of the entire still-temporary Telegram tab, including all its messages. The operator-approved grace is now zero, superseding the earlier 1-second and 2–3-second designs: deletion is attempted right after the chooser is deleted. For Last Cancel, scheduling follows positively recorded whole-group cancellation and successful retirement of the final chooser, not the initial button tap. The timer establishes earliest eligibility for one proof-gated cleanup attempt, not an exact deletion time: Workspace admission, fresh protection checks and Bot API latency may delay or refuse it. Actual client timing was accepted in the operator's 0.52.0 live smoke. Mirrored native witnesses use shortened injected delays, not a production-default or Telegram-client timing measurement. This grace is presentation timing, not proof of emptiness or non-delivery. New input or a changed binding/authority cancels the scheduled cleanup. Immediately before issuance, reacquire current Workspace admission, recheck exact operator/profile/session/leader/source/target authority, positive disposable-tab provenance, complete source dispositions and all Thread protection, then publish the canonical one-shot temporary cleanup grant. A restored/bound Thread or any unresolved/unverifiable work remains protected. TTL, startup, silence and a zero chooser count do not manufacture this last-Cancel grant. Failed/unknown deletion cannot undo cancellation or cause automatic repeat issuance.
486
486
  - `Forward`: Forward only the selected input and preserve independently held siblings. Its existing source-tab cleanup cannot erase another unassigned or protected input; per-input completion is not whole-Thread deletion proof.
487
- - `Successful Restore`: Preserve this tab as the selected instance's bound Workspace Thread and keep its slot; only the selected input is delivered. Cancel the exact other still-unassigned inputs with private retention, positive source-only disposition and invalidated controls, without delivering them to Pi or physically removing the restored tab. Already selected, queued, running and unknown-issued work is outside sibling cancellation. Failed or unknown Restore does not cancel siblings. Composition must establish the exact sibling set under serialized membership and prevent held/partly-cancelled originals from falling through to normal bound-Thread execution after relocation; a retention/ACK fault preserves unresolved sources and cannot roll back independently accepted work.
487
+ - `Successful Restore`: Preserve this tab as the selected instance's bound Workspace Thread and keep its slot; only the selected input is delivered. Positive settlement of the selected input deletes its chooser message; protected old-Thread cleanup never holds the chooser back. Cancel the exact other still-unassigned inputs with private retention, positive source-only disposition and invalidated controls, without delivering them to Pi or physically removing the restored tab. Already selected, queued, running and unknown-issued work is outside sibling cancellation. Failed or unknown Restore does not cancel siblings. Composition must establish the exact sibling set under serialized membership and prevent held/partly-cancelled originals from falling through to normal bound-Thread execution after relocation; a retention/ACK fault preserves unresolved sources and cannot roll back independently accepted work.
488
488
  - `Validation and activation`: Native witnesses must cover two/three inputs, grouped sources, different cancellation orders, new admission during the grace, concurrent selection/Forward/Restore, stale controls, incomplete membership, retention/ACK faults, restart and one-shot unknown deletion. Operator-controlled live acceptance must explicitly authorize disposable test-tab deletion; it passed in the operator's 0.52.0 live smoke.
489
489
 
490
490
  #### Restart As A New World (Operator-Approved, Implemented Locally)
@@ -287,7 +287,7 @@ As part of staging, the exact recipient presents the token and atomically receiv
287
287
 
288
288
  ### Live-Rebind Channel
289
289
 
290
- The 0.53.0 channel preserved protocol v2 and negotiates live rebinding independently from package version; 0.54 moves the base protocol to v3 without changing these capabilities. Release 0.53.0 advertises the following capabilities; an instance still running an older build does not acquire them until it is reloaded. Compatible package versions alone cannot supply missing capabilities:
290
+ After the recipient confirms the applied rebind and before the source is released, the leader renames the tab once to the restored binding's display-mode title, the same title Workspace Restore applies; a failed rename is recorded (`live-rebind-title`) and never blocks Restore. The 0.53.0 channel preserved protocol v2 and negotiates live rebinding independently from package version; 0.54 moves the base protocol to v3 without changing these capabilities. Release 0.53.0 advertises the following capabilities; an instance still running an older build does not acquire them until it is reloaded. Compatible package versions alone cannot supply missing capabilities:
291
291
 
292
292
  - `live-thread-rebind-save-v1`, `live-thread-rebind-apply-v1`, `live-thread-rebind-settle-v1`: Exact recipient save/apply/release and source settlement through the shared channel.
293
293
  - `live-thread-rebind-command-set-v1`: One `selectedCommand: { name, target }` descriptor and `observe-command` / `command-observed` completion observation, rather than separate status/control wire forms. Retired descriptors and capability names refuse without a compatibility shim.
@@ -295,7 +295,7 @@ The 0.53.0 channel preserved protocol v2 and negotiates live rebinding independe
295
295
 
296
296
  Every participating peer must negotiate the required capabilities and retain current authentication, registration generation, Pi context/session, journal binding and exact recipient authority. The wire validates syntax; Commands' captured registry decides availability before hold/append. Supported built-ins and registered prompt templates reuse one plan set across compatible leader selections and held followers; extension commands/groups/legacy registrations do not gain follower execution. Continue keeps its control lane, while expanded templates are ordinary default-lane prompts even when their text starts with a slash.
297
297
 
298
- Semantic reports, queued receipts, completion observation and delivery remain separate. Native exact ACK and donor CAS still own source disposition; lost or mismatched evidence retains uncertainty without handler replay, another queue or fallback admission. This section owns the unified command-channel policy; [architecture](./architecture.md#live-rebind-module-map) owns the module map. Existing Workspace Restore receivers/readers and queue-handoff retain their custody contracts; the new loaded root prefers live rebinding rather than using legacy Restore as an uncertain-input fallback. Source-enabled bounded cleanup is not proof of an actual deletion or client acceptance. Windows runs the same strict paths, exercised by the CI matrix; actual Pi/client acceptance and release are not inferred from supplied IPC fixtures.
298
+ Semantic reports, queued receipts, completion observation and delivery remain separate. Native exact ACK and donor CAS still own source disposition; lost or mismatched evidence retains uncertainty without handler replay, another queue or fallback admission. This section owns the unified command-channel policy; [architecture](./architecture.md#live-rebind-module-map) owns the module map. Existing Workspace Restore receivers/readers and queue-handoff retain their custody contracts; the new loaded root prefers live rebinding rather than using legacy Restore as an uncertain-input fallback. Source-enabled bounded cleanup is not proof of an actual deletion or client acceptance. Old-tab cleanup waits for the recipient session to be idle, which the moved input itself usually keeps busy; its paced retries keep a whole first delay so a detached command reply still sees the released, cleanup-free row, but after that a leader's `agent_settled` wakes a waiting retry at once. The wake only re-samples every gate; it never grants cleanup. Windows runs the same strict paths, exercised by the CI matrix; actual Pi/client acceptance and release are not inferred from supplied IPC fixtures.
299
299
 
300
300
  ### Local IPC endpoint with bounded native paths
301
301
 
package/docs/ui-style.md CHANGED
@@ -52,9 +52,9 @@ Use emoji as stable semantic markers, not decoration. Emoji carry transportable
52
52
  | `🆕` | New session / fresh start | `/new`, session replacement notices | `/new` replaces the active Pi session while preserving the current classic chat or Thread target; use it only for a real session reset. |
53
53
  | `🔄` | Refresh | Queue refresh row and future refresh buttons | Re-fetch/re-render current surface, not transport reconnect. |
54
54
  | `🔀` | Reroute to an existing target | Root `🔀 Reroute: send it to a Pi thread` button, the `🔀 Reroute … to:` submenu heading | Crossing arrows mean the held message branches off to another live thread; its target buttons use `🧵`. |
55
- | `🔁` | Replace/restore mode | Root `🔁 Restore: move a Pi into this tab` button and the `🔁 Restore into this tab & send …:` submenu heading | Same marker for the action at both levels, like `🔀` for Reroute; its target buttons use `🧵`. |
55
+ | `🔁` | Replace/restore mode | Root `🔁 Restore: move a Pi into this tab` button and the `🔁 Restore a Pi into this tab:` submenu heading | Same marker for the action at both levels, like `🔀` for Reroute; its target buttons use `🧵`. |
56
56
  | `☑️` | Activate / choose this item | Model detail activation action, generated button-only choice heading | Positive selection cue; use `🟢 Active` for already-current state. |
57
- | `⛔️` | Cancel source routing | `⛔️ Cancel routing` chooser action and confirmed `⛔️ Routing cancelled.` feedback | Not an abort of active Pi work. Private retention remains mandatory; disposable-tab removal stays bound to exact eligible target authority and follows only once every input in the tab is resolved. |
57
+ | `⛔️` | Cancel source routing | `⛔️ Cancel routing` chooser action and the `Routing cancelled` toast | Not an abort of active Pi work. Private retention remains mandatory; disposable-tab removal stays bound to exact eligible target authority and follows only once every input in the tab is resolved. |
58
58
  | `❌` | No / cancel / terminal failure | Confirmation cancel buttons and terminal failure notices | Do not use for a recoverable operation failure that leaves session state intact. |
59
59
  | `🗑` | Delete / defer removal | Destructive confirmations and removal reaction | In the queue menu, reversible Keep/Skip selectors replace immediate deletion. |
60
60
 
@@ -238,8 +238,10 @@ A message (or All-tab command) held in an unbound or temporary tab gets one rout
238
238
 
239
239
  - Root text: bold heading `🚦 Route this message:` (or `🚦 Route <code>/start</code>:` for a command), a blank line, then the italic note `The choice expires in 60 minutes.` When Reroute is the only possible action (an All-tab command without a temporary tab), the italic note says `To restore a Pi instead, send a message in a new tab.` instead.
240
240
  - Root buttons, one per row and only when available: `🔀 Reroute: send it to a Pi thread`, `🔁 Restore: move a Pi into this tab`, `⛔️ Cancel routing`. The `🚦` heading echoes these three choices, with `⛔️` as the red stop.
241
- - Submenus start with `⬆️ Back`, which restores the root with its exact original text. Headings name the same subject as the root: bold `🔀 Reroute this message to:` / `🔀 Reroute <code>/start</code> to:`, and `🔁 Restore into this tab & send this message:` / `🔁 Restore into this tab & send <code>/start</code>:`. Every target button below is a thread, written `🧵 <display title>`.
242
- - Cancel appears only at the root. A confirmed cancellation replaces the chooser with bold `⛔️ Routing cancelled.` and an empty keyboard.
241
+ - Submenus start with `⬆️ Back`, which restores the root with its exact original text. Headings are bold: `🔀 Reroute this message to:` / `🔀 Reroute <code>/start</code> to:` name the root's subject, and `🔁 Restore a Pi into this tab:` names a command only when there is one (`🔁 Restore a Pi into this tab for <code>/start</code>:`). Every target button below is a thread, written `🧵 <display title>`.
242
+ - Cancel appears only at the root.
243
+ - An input sent from `All` moves into the routing tab opened for it: the tab starts with a silent forward of the input (Telegram shows it as forwarded from the operator), the chooser replies to it, and the original leaves `All` once the copy is confirmed. A restored Thread therefore keeps the prompt it answers.
244
+ - A chooser is one-shot: every final outcome deletes its message. A confirmed Cancel, a Forward or Reroute, and a successful Restore remove the menu; Restore keeps the tab as the Pi's Thread, while a temporary tab with no remaining unresolved input is removed. If Telegram refuses the delete, Cancel and Restore leave an inert bold notice (`⛔️ Routing cancelled.` / `✅ Message routed.`) with an empty keyboard, and Forward asks for one more tap to clear the menu.
243
245
  - Old or expired controls answer `⌛ Routing choice expired.`
244
246
  - Tab name: every temporary routing tab is named `🚦 Routing`, whether the bot created it (menu command or threadless prompt) or adopted the `New Chat` tab Telegram opened for a typed input. It is never renamed after its first input.
245
247
  - Status → `❌ Pending cancellations` lists one row per protected original: its text, or `📎` plus the caption, file name or the message field carrying Telegram's `file_id`.
package/lib/bindings.ts CHANGED
@@ -1199,6 +1199,8 @@ interface TelegramLifecycleBindingDeps {
1199
1199
  isTurnTransportActive?: (turn: Queue.PendingTelegramTurn) => boolean;
1200
1200
  updateStatus: TelegramBridgeStatusUpdater;
1201
1201
  recordRuntimeEvent: TelegramRuntimeEventRecorder;
1202
+ /** Settled-session hint for owners that wait on agent idleness, such as old-tab cleanup after a live rebind. */
1203
+ onSessionSettled?: (ctx: Pi.ExtensionContext) => void;
1202
1204
  }
1203
1205
 
1204
1206
  export function registerTelegramLifecycleRuntimeHooks({
@@ -1247,6 +1249,7 @@ export function registerTelegramLifecycleRuntimeHooks({
1247
1249
  isTurnTransportActive,
1248
1250
  updateStatus,
1249
1251
  recordRuntimeEvent,
1252
+ onSessionSettled,
1250
1253
  }: TelegramLifecycleBindingDeps): void {
1251
1254
  const agentEndResetter = Runtime.createTelegramAgentEndResetter({
1252
1255
  abort,
@@ -1753,6 +1756,7 @@ export function registerTelegramLifecycleRuntimeHooks({
1753
1756
  agentWorkActive = false;
1754
1757
  activityRuntime.onAgentSettled();
1755
1758
  modelContextAvailabilityRuntime.reconcile();
1759
+ onSessionSettled?.(ctx);
1756
1760
  },
1757
1761
  onBeforeAgentStart: Prompts.createTelegramProactiveBeforeAgentStartHook({
1758
1762
  reconcileAvailability: modelContextAvailabilityRuntime.reconcile,
package/lib/extension.ts CHANGED
@@ -632,6 +632,13 @@ export default function (pi: Pi.ExtensionAPI) {
632
632
  instanceId: telegramInstanceId,
633
633
  listRecords: threadStore.list,
634
634
  listWorkspaceBindings: threadStore.listWorkspaceBindings,
635
+ resolveAutomaticDisplayTitle(binding) {
636
+ return ThreadDisplay.resolveTelegramInitialWorkspaceDisplayName({
637
+ bindings: threadStore.listWorkspaceBindings(),
638
+ binding,
639
+ mode: Config.resolveTelegramThreadDisplayMode(configStore.get()),
640
+ });
641
+ },
635
642
  getFollowerDisplayTitle:
636
643
  telegramBusFollowerRegistrationState.getDisplayTitle,
637
644
  getActiveTurnTarget: activeTurnRuntime.getTarget,
@@ -3027,5 +3034,6 @@ export default function (pi: Pi.ExtensionAPI) {
3027
3034
  },
3028
3035
  updateStatus,
3029
3036
  recordRuntimeEvent,
3037
+ onSessionSettled: inboundRouteRuntime.onSessionSettled,
3030
3038
  });
3031
3039
  }