@llblab/pi-kit 0.27.5 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/BACKLOG.md +3 -1
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +4 -4
  4. package/node_modules/@llblab/pi-state-flow/AGENTS.md +1 -1
  5. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +6 -2
  6. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +4 -1
  7. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +4 -3
  8. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +26 -13
  9. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  10. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +3 -1
  11. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +2 -2
  12. package/node_modules/@llblab/pi-state-flow/docs/usage.md +2 -1
  13. package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +4 -3
  14. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +25 -12
  15. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  16. package/node_modules/@llblab/pi-telegram/AGENTS.md +10 -8
  17. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -9
  18. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
  19. package/node_modules/@llblab/pi-telegram/LICENSE +21 -0
  20. package/node_modules/@llblab/pi-telegram/README.md +15 -5
  21. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
  22. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +5 -7
  23. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +5 -1
  24. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +6 -1
  25. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +4 -2
  26. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +54 -56
  27. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +206 -139
  28. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +21 -2
  29. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +181 -25
  30. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.d.ts +4 -1
  31. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +28 -4
  32. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +52 -56
  33. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +78 -246
  34. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +1 -3
  35. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +23 -26
  36. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +0 -5
  37. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +5 -5
  38. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +0 -23
  39. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +15 -15
  40. package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +0 -17
  41. package/node_modules/@llblab/pi-telegram/dist/lib/config.js +12 -14
  42. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +0 -4
  43. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +2 -5
  44. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +255 -61
  45. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -19
  46. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +0 -2
  47. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +2 -2
  48. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +177 -16
  49. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +1115 -249
  50. package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.d.ts +0 -2
  51. package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.js +2 -2
  52. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +1 -1
  53. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +100 -12
  54. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +380 -14
  55. package/node_modules/@llblab/pi-telegram/dist/lib/logging.d.ts +30 -12
  56. package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +129 -72
  57. package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +28 -5
  58. package/node_modules/@llblab/pi-telegram/dist/lib/media.js +26 -8
  59. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +0 -11
  60. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +8 -8
  61. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +0 -18
  62. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +18 -18
  63. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +4 -4
  64. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +12 -7
  65. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +1 -1
  66. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +1 -1
  67. package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +1 -0
  68. package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +4 -3
  69. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +0 -1
  70. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +1 -1
  71. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +1 -8
  72. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +1 -6
  73. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +4 -21
  74. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +0 -8
  75. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +5 -5
  76. package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +50 -7
  77. package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +155 -17
  78. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +1 -0
  79. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +2 -1
  80. package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +0 -5
  81. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +5 -5
  82. package/node_modules/@llblab/pi-telegram/dist/lib/preview.d.ts +0 -3
  83. package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +3 -3
  84. package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.d.ts +0 -1
  85. package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.js +1 -1
  86. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +3 -15
  87. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +69 -8
  88. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +29 -9
  89. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +104 -33
  90. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +0 -4
  91. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +1 -1
  92. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +73 -8
  93. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +2206 -307
  94. package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +0 -2
  95. package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +1 -1
  96. package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +44 -9
  97. package/node_modules/@llblab/pi-telegram/dist/lib/status.js +149 -27
  98. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +0 -5
  99. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +5 -4
  100. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +11 -3
  101. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +34 -23
  102. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +1 -0
  103. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +18 -21
  104. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +32 -5
  105. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +191 -7
  106. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +2 -0
  107. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +1 -1
  108. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +291 -59
  109. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +1647 -369
  110. package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +0 -1
  111. package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -1
  112. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +184 -28
  113. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +1032 -114
  114. package/node_modules/@llblab/pi-telegram/dist/lib/wire.d.ts +12 -0
  115. package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +21 -0
  116. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +25 -17
  117. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +95 -20
  118. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.d.ts +20 -0
  119. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +103 -0
  120. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +43 -8
  121. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +148 -52
  122. package/node_modules/@llblab/pi-telegram/dist/package.json +6 -6
  123. package/node_modules/@llblab/pi-telegram/dist/skills/generated-control-surface/SKILL.md +3 -3
  124. package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
  125. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
  126. package/node_modules/@llblab/pi-telegram/docs/activity.md +1 -1
  127. package/node_modules/@llblab/pi-telegram/docs/architecture.md +152 -34
  128. package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
  129. package/node_modules/@llblab/pi-telegram/docs/delivery.md +1 -1
  130. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +2 -2
  131. package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
  132. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +125 -19
  133. package/node_modules/@llblab/pi-telegram/docs/public-api.md +3 -1
  134. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +22 -8
  135. package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
  136. package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +5 -9
  137. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +6 -0
  138. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +5 -2
  139. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +243 -224
  140. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +178 -31
  141. package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +30 -8
  142. package/node_modules/@llblab/pi-telegram/lib/bus.ts +110 -334
  143. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +29 -29
  144. package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +5 -5
  145. package/node_modules/@llblab/pi-telegram/lib/commands.ts +15 -15
  146. package/node_modules/@llblab/pi-telegram/lib/config.ts +12 -17
  147. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +2 -8
  148. package/node_modules/@llblab/pi-telegram/lib/extension.ts +257 -72
  149. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +0 -22
  150. package/node_modules/@llblab/pi-telegram/lib/inbound.ts +2 -2
  151. package/node_modules/@llblab/pi-telegram/lib/journal.ts +1182 -326
  152. package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +2 -2
  153. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +1 -1
  154. package/node_modules/@llblab/pi-telegram/lib/locks.ts +402 -23
  155. package/node_modules/@llblab/pi-telegram/lib/logging.ts +151 -101
  156. package/node_modules/@llblab/pi-telegram/lib/media.ts +51 -9
  157. package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +8 -8
  158. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +18 -18
  159. package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +11 -0
  160. package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +2 -2
  161. package/node_modules/@llblab/pi-telegram/lib/menu.ts +5 -1
  162. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +1 -1
  163. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +1 -10
  164. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +5 -24
  165. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +5 -5
  166. package/node_modules/@llblab/pi-telegram/lib/paths.ts +177 -17
  167. package/node_modules/@llblab/pi-telegram/lib/pi.ts +5 -2
  168. package/node_modules/@llblab/pi-telegram/lib/polling.ts +5 -5
  169. package/node_modules/@llblab/pi-telegram/lib/preview.ts +3 -3
  170. package/node_modules/@llblab/pi-telegram/lib/prompt-templates.ts +1 -1
  171. package/node_modules/@llblab/pi-telegram/lib/queue.ts +67 -9
  172. package/node_modules/@llblab/pi-telegram/lib/recovery.ts +104 -44
  173. package/node_modules/@llblab/pi-telegram/lib/replies.ts +1 -1
  174. package/node_modules/@llblab/pi-telegram/lib/routing.ts +1955 -375
  175. package/node_modules/@llblab/pi-telegram/lib/sections.ts +1 -1
  176. package/node_modules/@llblab/pi-telegram/lib/status.ts +156 -36
  177. package/node_modules/@llblab/pi-telegram/lib/sync.ts +4 -4
  178. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +48 -33
  179. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +24 -21
  180. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +267 -9
  181. package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +3 -1
  182. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1697 -488
  183. package/node_modules/@llblab/pi-telegram/lib/turns.ts +1 -1
  184. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1072 -151
  185. package/node_modules/@llblab/pi-telegram/lib/wire.ts +28 -0
  186. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +97 -47
  187. package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +147 -0
  188. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +188 -89
  189. package/node_modules/@llblab/pi-telegram/package.json +6 -6
  190. package/node_modules/@llblab/pi-telegram/scripts/audit-exports.mjs +100 -0
  191. package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +80 -43
  192. package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +3 -3
  193. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/diagnosis.md +3 -3
  194. package/package.json +3 -3
@@ -20,7 +20,7 @@ myext:page:2
20
20
 
21
21
  - Use a stable extension-owned namespace, preferably the package or extension name without scope punctuation.
22
22
  - Keep the namespace lowercase ASCII: `a-z`, `0-9`, `_`, `-`.
23
- - Do not use `pi-telegram` owned prefixes: `compact:`, `new:`, `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `settings:`, `section:`. Current app navigation uses `menu:`; `status:` remains reserved for legacy/owned status callbacks but is not emitted by current UI. `compact:` and `new:` are owned by their destructive-action confirmation dialogs. `section:` is owned by the Extension Sections platform (0.10.0+), documented in [Extension Sections](./sections.md). `settings:` is owned for the built-in Settings submenu.
23
+ - Do not use `pi-telegram` owned prefixes: `compact:`, `new:`, `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `settings:`, `section:`, `reroute:`, `reroutemenu:`, `rerouteroot:`, `reroutenew:`, `rerouterestore:`, `reroutecancel:`. The `reroute*` family belongs to exact-source unbound Thread choosers; `reroutecancel:` retains an eligible owner's original before cancelling its routing authority. Its `review:` branch owns Status recovery for protected cancellation attempts; `history:` remains reserved only to reject retired historical-review controls. Action payloads carry a fresh bounded view nonce and an index, never caller-supplied journal paths or source authority. Current app navigation uses `menu:`; `status:` remains reserved for legacy/owned status callbacks but is not emitted by current UI. `compact:` and `new:` are owned by their destructive-action confirmation dialogs. `section:` is owned by the Extension Sections platform (0.10.0+), documented in [Extension Sections](./sections.md). `settings:` is owned for the built-in Settings submenu.
24
24
  - Keep the full `callback_data` within Telegram's 64-byte limit.
25
25
  - Put only opaque ids or small enum values in payloads; do not store secrets, full prompts, or large state.
26
26
  - Treat callbacks as untrusted input. Validate namespace, action, and payload before executing side effects.
@@ -53,7 +53,7 @@ export interface TelegramDeliveryView {
53
53
 
54
54
  `plain` is the default. Operational activity should prefer `plain` or explicit `html`. `markdown` exists for extension-authored content that naturally owns Markdown; the bridge converts it through the existing UI/compat Markdown-to-HTML renderer rather than entering the native assistant final-reply pipeline.
55
55
 
56
- `replyMarkup` accepts only structural keyboard data. Callback ownership stays with Sections or a registered raw update handler. The documented issue #126 consumer shape uses Sections for interactive Settings toggles and keeps delivered activity rows non-interactive, so a second managed callback registry would duplicate token, answer, edit, navigation, and cleanup ownership without a proven use case. Revisit only when a public-import-only consumer must generate managed callbacks independently of a registered Section context for arbitrary delivered messages.
56
+ `replyMarkup` accepts only structural keyboard data. Callback ownership stays with Sections or a registered raw update handler. The documented external consumer shape uses Sections for interactive Settings toggles and keeps delivered activity rows non-interactive, so a second managed callback registry would duplicate token, answer, edit, navigation, and cleanup ownership without a proven use case. Revisit only when a public-import-only consumer must generate managed callbacks independently of a registered Section context for arbitrary delivered messages.
57
57
 
58
58
  ### Target scopes
59
59
 
@@ -1,6 +1,6 @@
1
1
  # Generative Apps Runtime For Telegram
2
2
 
3
- _Status: incremental implementation. Canonical installation and explicit transactional replacement, agent-side method invocation, state/history commits, partial-tail recovery, cross-process transition locking with dead-owner recovery, installation-generation plus revision rejection for direct app-output controls, lifecycle-cancelled worker-isolated methods, the bounded non-shell process port, strict bound-action parsing, pre-model-queue `tgbtn` dispatch, new-message default views, opt-in in-place bound-action edits with explicit-action send fallback, and memory-only live dashboards with bounded scheduling, same-handle action rescheduling, Delivery failure classification, exact routed-target retention, unavailable-message invalidation, and lifecycle cancellation are implemented locally. Agent-mediated initial-surface revision capture, process-birth lock proof, voice delivery, and removal remain open in the backlog._
3
+ _Status: incremental implementation. Canonical installation and explicit transactional replacement, agent-side method invocation, state/history commits, partial-tail recovery, cross-process transition locking with dead-owner recovery, installation-generation plus revision rejection for direct app-output controls, lifecycle-cancelled worker-isolated methods, the bounded non-shell process port, strict bound-action parsing, pre-model-queue `tgbtn` dispatch, new-message default views, opt-in in-place bound-action edits with explicit-action send fallback, and memory-only live dashboards with bounded scheduling, same-handle action rescheduling, Delivery failure classification, exact routed-target retention, unavailable-message invalidation, and lifecycle cancellation are implemented locally. Agent-mediated initial-surface revision capture, process-birth lock proof, voice delivery, and removal are not implemented; no work is scheduled._
4
4
 
5
5
  ## Purpose
6
6
 
@@ -309,4 +309,4 @@ Implementation is not complete until evidence covers:
309
309
  - Live-view handle retention, unchanged-frame suppression, two-second minimum, non-overlap, coalescing, Telegram backoff, deletion invalidation, message-not-found handling, and lifecycle cancellation.
310
310
  - Poker-style internal state and media-style external-state reference applications.
311
311
 
312
- The canonical open implementation work remains in [`../BACKLOG.md`](../BACKLOG.md). This document owns the proposed subsystem contract and its architectural boundaries.
312
+ Future implementation work would be scheduled in [`../BACKLOG.md`](../BACKLOG.md). This document owns the proposed subsystem contract and its architectural boundaries.
@@ -131,6 +131,6 @@ If several programmatic inbound handlers are registered for a kind, they are tri
131
131
 
132
132
  ## Prompt Output
133
133
 
134
- Local attachments stay in the prompt under `[attachments] <directory>` with relative file entries. Successful media/file handler stdout is added under `[outputs]`. For composed media/file handlers, each step receives the previous step's stdout on stdin by default, and stdout from the last successful step is used as the handler output. Empty output and failed handler output are omitted from the prompt text.
134
+ Local attachments stay in the prompt under `[attachments] <directory>` with relative file entries. They live flat in `<agent-dir>/tmp/pi-telegram/attachments`, named `<kind>-<scope>-<messageId>[-<index>][-<sender file name>|.<ext>]`: `scope` is the bot `@username` (numeric id fallback) in a private chat and the public chat `@username` (unsigned numeric id fallback) in a group or channel; titles never name files. A message id is unique inside its chat, a forward gets a new id, and a repeated delivery of one message maps to the same path; files are published by rename so concurrent writers never expose partial content, and files older than 24 hours are removed by age alone. Successful media/file handler stdout is added under `[outputs]`. For composed media/file handlers, each step receives the previous step's stdout on stdin by default, and stdout from the last successful step is used as the handler output. Empty output and failed handler output are omitted from the prompt text.
135
135
 
136
136
  Text handler output replaces the prompt text directly and is not duplicated under `[outputs]`.
@@ -74,18 +74,59 @@ In Threaded Mode, the lock means "this instance is the current Telegram bus lead
74
74
  Classic ownership meaning:
75
75
 
76
76
  ```text
77
- tmp/telegram/owners.json / <profile-slot> -> polling/control owner
77
+ tmp/pi-telegram/state.json / profiles.<profile>.transport -> polling/control owner
78
78
  ```
79
79
 
80
80
  Threaded Mode meaning:
81
81
 
82
82
  ```text
83
- tmp/telegram/owners.json / <profile-slot> -> bus leader identity + heartbeat
83
+ tmp/pi-telegram/state.json / profiles.<profile>.transport -> bus leader identity + heartbeat
84
84
  ```
85
85
 
86
- Followers do not poll. They register with the leader and receive routed inbound updates from it. Followers still own their local queue, active-turn state, previews, final delivery planning, model switches, and Pi lifecycle. The leader owns only Telegram transport and update fanout. Pi session replacement (`new`) changes follower agent context, not bus membership: a registered follower preserves its registration and refreshes the live context instead of disconnecting. A new follower process also attempts a restore-only registration at session startup when the selected profile's local state contains a Workspace binding for the exact `cwd` and session ID. The recent-event log distinguishes missing session bindings, disabled Threaded Mode, leader-side restore refusal, and a restore that returned without connecting; none of these refusals silently provisions a new Thread. The Telegram bus belongs to the local set of cooperating visible Pi instances rather than to the first terminal session forever: if the visible terminal leader exits, a live registered follower can take over leadership.
86
+ Followers do not poll. They register with the leader and receive routed inbound updates from it. Followers still own their local queue, active-turn state, previews, final delivery planning, model switches, and Pi lifecycle. The leader owns only Telegram transport and update fanout. Same-session context refresh preserves bus membership; a changed Pi session ID suspends the old receiver/heartbeat and requires acknowledged successor registration before readiness, without an explicit Thread disconnect. A new follower process also attempts a restore-only registration at session startup when the selected profile's local state contains a Workspace binding for the exact `cwd` and session ID. The recent-event log distinguishes missing session bindings, disabled Threaded Mode, leader-side restore refusal, and a restore that returned without connecting; none of these refusals silently provisions a new Thread. The Telegram bus belongs to the local set of cooperating visible Pi instances rather than to the first terminal session forever: if the visible terminal leader exits, a live registered follower can take over leadership.
87
87
 
88
- The assembled follower receiver admits a registration generation only after its journal-binding preparation succeeds for the same generation and current Pi context. Registration identity is available during preparation so the worker can bind, but early inbound delivery receives a negative ACK rather than writing into a retained old journal. Readiness also binds the Pi session generation, so a reused context object cannot preserve it across session replacement. A registered follower's session refresh awaits binding preparation through `setContext` without re-registering or changing its bus generation. Preparation failure keeps inbound delivery unavailable and records a diagnostic; a later refresh can retry. Normal delivery retry proceeds after successful preparation. Registration requests also retain their original Pi session generation and local attempt authority across awaits. A late reply after session drift, stop or a newer request cannot publish success or tear down newer local authority; a context refresh likewise protects its retained membership from an older pending request. This refusal does not undo remote Thread provisioning or authorize deletion.
88
+ ### Session-Owned Journal Storage
89
+
90
+ Runtime storage lives under `<agent-dir>/tmp/pi-telegram`. The pre-0.52.0 `tmp/telegram` tree is neither written, migrated nor deleted. A live fresh owner there blocks competing polling as read-only evidence; its endpoint and secret grant no current authority.
91
+
92
+ The root keeps only `state.json` (per-profile `transport`, `workspace`, `admission` and observational `runtime` sections) and `logs.jsonl`; see [Consolidated Runtime Root](./architecture.md#consolidated-runtime-root). Journals have separate roles:
93
+
94
+ - Polling and local leader admission use the journal named by the profile's `transport` section. With a current Pi session ID, the first leader without a pointer or existing root custody hosts `sessions/<id>/inbox[.<profile>].json`.
95
+ - Follower recipient admission uses `sessions/<id>/journal.<recipient hash>[.<profile>].json`. Recipient binding identity and its existing 16-hex hash stay unchanged; equal hashes in different sessions remain distinct custody.
96
+ - Each journal owns its adjacent `.segments/` and `.retained/` family. Existing root `inbox` and explicit legacy `follower-inbox-<hash>` custody remain readable without file moves or migration.
97
+ - Slots `A`–`Z` never determine filenames. Canonical safe lowercase session names, including ordinary UUIDs, remain verbatim; unsafe/case-aliasing IDs use reversible UTF-8 percent encoding. No `session-` prefix, `unknown` folder or first-hash lookup exists.
98
+
99
+ The polling resolver prefers a valid owners-named path, then an existing profile root inbox, then the hosting session path. Without a session ID, the root inbox remains a compatibility fallback; ordinary session-aware startup does not create a new root inbox. Release keeps a pid-less `{ journalPath }` pointer, not a live owner. Successors continue the same cursor/custody; leader `/new` keeps polling into the original hosting folder. A missing named file does not select a new host: normal store creation may recreate the same path.
100
+
101
+ Workspace `journalSources` retains exact `{ sessionId, recipientBindingKey }` addresses (maximum 256 per binding), including predecessors across upsert and `/new` re-key. Returned tuples are detached; malformed/over-capacity publication and malformed cold loading refuse rather than erase evidence. Active follower paths require current Pi context and leader-acknowledged session ID agreement, including preparation before receiver readiness. Queue handoff requires the authenticated recipient registry's session ID before donor offer/removal. A dead-owner reclaimer without an exact session resolver refuses session-qualified custody; it never substitutes a flat same-hash journal.
102
+
103
+ Production protection uses scoped non-repairing historical reads, recipient-process writer checks and a fresh strict session namespace census even when binding addresses are complete. The catalog includes flat/session families and exactly one named polling source; other inboxes remain protected evidence. Missing resolution, corrupt/unclassified storage, uncommitted retention, missing committed originals or retained-only families without an exact snapshot leave protection unknown. Discovery/census is neither writer closure nor readiness/deletion permission.
104
+
105
+ #### Metadata-only Evidence Pruning
106
+
107
+ Production `createTelegramWorkspaceJournalEvidencePruner` is composed into the capacity-pressure runner; it is not periodic or startup cleanup. Only a failed fresh allocation with a verified full A–Z occupancy may call it for inactive bindings: the eligible retirement candidate when one exists, otherwise the protected-capacity candidates. `pruneTelegramWorkspaceJournalEvidence` acquires target admission before entering the shared Workspace gate and invoking a fresh source-capture callback. Its read-only protection observer reuses the same strict namespace census, exact resolvers, scoped references and writer checks as ordinary retirement protection. Legacy addresses and exact `(sessionId, recipientBindingKey)` pairs never collapse together. Every retained address and the shared source must have matching available evidence; incomplete/corrupt/wrong-scope capture refuses. Only empty addresses with positively quiescent writers are removed by subset CAS; busy/live/unknown-writer addresses stay retained, including alongside a removable subset. Completeness is never manufactured. Threads acknowledges the exact current metadata frame through `persistWorkspaceJournalEvidence`, checking the captured state path, transport authority and expected frame inside the snapshot file transaction before rename: refused publication is not reported as committed, and publication-result/authority loss never compensates an already published subset. Neither source files nor independent Restore/retirement/temporary roots are deleted or rewritten. A positive metadata ACK supplies the current binding frame to subsequent queue reclamation; stale/failed publication is never presented as pruning success. All ordinary protection and retirement-fence/deletion-permit checks remain independent and mandatory afterward. This is not a prerequisite or closure requirement for the separately approved optimistic session-family sweep.
108
+
109
+ Native filesystem fixtures cover address separation, late pending input, publication refusal/loss and stale retry. Full-capacity composition uses real binding resolvers, a strict session namespace, references and the shared gate; pending or independently delivery-protected custody still blocks deletion, corrupt namespaces remain untouched, epoch loss retains published metadata without deletion, and free capacity never calls the pruning port. Registry/writer/leader premises and the deletion transport are supplied; these are not actual Pi/Telegram or native-Windows acceptance.
110
+
111
+ Demand-driven pressure reclamation now resolves every retained session address through the same exact recipient resolver and existing reference scope. Whole unoffered queued receipts and death proof for every planned owner are mandatory before any terminal CAS; pending, unavailable/corrupt or alive/unverifiable custody refuses. Current binding/leader/local-work/delivery protection is rechecked before each journal-owned mutation, and fresh all-clear protection remains mandatory before ordinary retirement can delete a Thread. Partial terminal disposal is retained, never rolled back or replayed; retry handles only still-owned receipts. Native filesystem cases cover identical hashes in two sessions, source/authority refusal and interrupted second-journal publication; they do not prove live or native-Windows acceptance.
112
+
113
+ #### In-Process Follower Succession
114
+
115
+ After the predecessor worker stops and before the successor starts, lifecycle `prepareBinding` invokes `prepareActiveFollowerSuccession`. Its process-local tracker records the first prepared session without adoption. Only a changed session ID under the same recipient key adopts plain unclaimed `pending` entries; claims, provenance, routing-input custody, queue owners/receipts/handoffs, failures and exclusion vetoes prevent adoption. Cold process startup never adopts predecessor input.
116
+
117
+ Each eligible entry first commits away through private retention and a predecessor tombstone under `session-successor:<id>`, then appends to the successor journal. No file moves. Failure refuses readiness and retains the predecessor tracker for retry. A crash or failed append after commit leaves the original privately retained but non-executable; automatic transfer completion is not implemented, and retry never re-appends a committed source. Accepted queue custody stays with its predecessor owner. Restore cancellation proof accepts only `telegram-owner:` authority, so adoption cannot settle Restore.
118
+
119
+ Pending-entry deduplication does not prevent a generic delivery retry after the recipient entry completed and was removed; it can execute again, with or without adoption. Temporary-Thread Forward issuance is not a generic deduplication protocol. Native gateway/IPC fixtures supply Pi host hooks, not actual `newSession` ordering or live acceptance.
120
+
121
+ #### Optimistic Session-Family Sweeping
122
+
123
+ After a current follower-prune pass, the leader runs a best-effort sweep, throttled to once per 10 minutes by a process-local clock. All Workspace-bound sessions, live registrations and this process's own session are kept. In other canonical session folders it deletes only current-profile `journal.<hash>.json`, `.segments/` and `.retained/` families, including private originals, then removes the folder only if empty. Other profiles, unknown files and noncanonical folders remain. The matcher never selects any `inbox` family, so polling-host folders survive without a slot.
124
+
125
+ This operator-approved disposable-session policy is not Thread deletion, receipt settlement or whole-system reference closure. Historical Restore/retirement/temporary references into a swept family may stop resolving and remain protective; they grant no replay or fabricated completion. Strict protection reads themselves never repair or delete storage.
126
+
127
+ ### Follower Registration And Session Readiness
128
+
129
+ The assembled follower receiver admits a registration generation only after its journal-binding preparation succeeds for the same generation and current Pi context. Registration identity is available during preparation so the worker can bind, but early inbound delivery receives a negative ACK rather than writing into a retained old journal. Readiness also captures the Pi session ID before binding preparation and rechecks it after the await and at every readiness read, alongside the session generation. A reused context object cannot preserve readiness across session replacement, even if a host fails to advance its generation. The assembly's `getReadySessionId()` exposes only that still-current prepared ID, never the ID of an unfinished preparation. Same-session refresh awaits binding preparation through `setContext` without re-registering or changing its bus generation. A changed session ID now refuses refresh before binding preparation: local readiness must match the ID acknowledged by the current leader registration, not merely its bus generation. The existing suspended session-replacement handoff re-registers separately; only successful fresh registration can prepare the successor ID. Registration requests capture that exact ID before startup and recheck it across all awaits, so changed-ID late replies cannot finalize registration even when a reused context or unchanged host generation would otherwise conceal the switch. Refusal never removes old-session custody or rewrites the leader's retained binding. Preparation failure keeps inbound delivery unavailable and records a diagnostic; a later refresh can retry. Normal delivery retry proceeds after successful preparation. Registration requests also retain their original Pi session generation and local attempt authority across awaits. A late reply after session drift, stop or a newer request cannot publish success or tear down newer local authority; a context refresh likewise protects its retained membership from an older pending request. This refusal does not undo remote Thread provisioning or authorize deletion.
89
130
 
90
131
  A follower may route Bot API work or capability checks only after authenticated registration; an unregistered process that does not own the direct transport lock remains transport-passive. Follower request settlement remains owned by the existing local IPC operation budget rather than being inferred from Bot API method names; followers never invoke `getUpdates`.
91
132
 
@@ -151,7 +192,7 @@ A registered instance exposes:
151
192
 
152
193
  ## Approved Next Contract: Directory Names And Reclaimable Slots
153
194
 
154
- Status: approved design with a locally tested pure selection policy in `lib/workspace-slots.ts` and profile-isolated display preference persistence/default resolution in `lib/config.ts`. Workspace claims now reserve global letters before provisioning and preserve legacy binding keys. An exact claim assigns the first free letter to a missing-slot binding or the selected member of a duplicate-slot set, but persistence waits for successful target recovery; unresolved duplicates block unrelated fresh allocation. If authenticated follower re-registration finds that its retained target record carries another letter, the exact claim is canonical: registration repairs that record before binding commit and leaves the unrelated binding that owns the stale letter intact. Sticky suffix metadata and acknowledged `displayTitle` persist in Workspace bindings. `lib/thread-display.ts` provides the three-mode projection plus serialized title reconciliation wired into leader startup and follower registration. Heartbeat ACKs carry acknowledged display titles to followers and the current-thread/TUI projection uses them without changing restoration identity. Settings now exposes Letters (default), Names, and Directories; follower changes use the capability-gated leader-owned setting path. Live bot chooser/notice labels and cross-instance agent-target resolution use acknowledged titles without granting routing authority. The Thread store now persists the first proven `inactiveSinceMs` transition with exact confirmed target absence or fenced non-destructive detachment of a confirmed-dead follower or quiescent quitting leader whose tab is preserved; successful active provisioning clears it. Pressure selection, intents, mocked execution, and recovery are implemented. A 2/2 same-model independent post-fix quorum cleared the admission-composition blocker at 0.96 confidence per reviewer, and the operator has since authorized demand-driven rotation. Fresh allocation now invokes the existing retirement lifecycle under full capacity; `BACKLOG.md` owns remaining disposable-client acceptance.
195
+ Status: approved design with a locally tested pure selection policy in `lib/workspace-slots.ts` and profile-isolated display preference persistence/default resolution in `lib/config.ts`. Workspace claims now reserve global letters before provisioning and preserve legacy binding keys. An exact claim assigns the first free letter to a missing-slot binding or the selected member of a duplicate-slot set, but persistence waits for successful target recovery; unresolved duplicates block unrelated fresh allocation. If authenticated follower re-registration finds that its retained target record carries another letter, the exact claim is canonical: registration repairs that record before binding commit and leaves the unrelated binding that owns the stale letter intact. Sticky suffix metadata and acknowledged `displayTitle` persist in Workspace bindings. `lib/thread-display.ts` provides the three-mode projection plus serialized title reconciliation wired into leader startup and follower registration. Heartbeat ACKs carry acknowledged display titles to followers and the current-thread/TUI projection uses them without changing restoration identity. Settings now exposes Letters (default), Names, and Directories; follower changes use the capability-gated leader-owned setting path. Live bot chooser/notice labels and cross-instance agent-target resolution use acknowledged titles without granting routing authority. The Thread store now persists the first proven `inactiveSinceMs` transition with exact confirmed target absence or fenced non-destructive detachment of a confirmed-dead follower or quiescent quitting leader whose tab is preserved; successful active provisioning clears it. Pressure selection, intents, mocked execution, and recovery are implemented. A 2/2 same-model independent post-fix quorum cleared the admission-composition blocker at 0.96 confidence per reviewer, and the operator has since authorized demand-driven rotation. Fresh allocation now invokes the existing retirement lifecycle under full capacity; disposable-client behavior was accepted in the operator's 0.52.0 live smoke.
155
196
 
156
197
  The pure policy distinguishes a free letter, a proposed pressure-reclamation victim, and protected/invalid capacity. Its caller must supply a validated profile-wide snapshot, reservations, proven inactivity start, and explicit protection classification; duplicate legacy letters block selection. The policy performs no filesystem or Telegram operations and does not establish liveness or deletion authority. It proposes a victim only when every profile-wide letter is occupied or reserved; elapsed time alone never triggers retirement.
157
198
 
@@ -196,11 +237,11 @@ Production `confirmTargetAbsent` remains unwired until an approved observation c
196
237
 
197
238
  `sendChatAction/cancel` is accepted by upstream Bot API source but is not in the documented action set and is not a verified existence lookup. [TDLib action handling](https://github.com/tdlib/td/blob/ea97bcdd3a15523c58ddfe772b4547187cf5bbeb/td/telegram/DialogActionManager.cpp#L350-L437) can skip an unneeded action, and its [query handler](https://github.com/tdlib/td/blob/ea97bcdd3a15523c58ddfe772b4547187cf5bbeb/td/telegram/DialogActionManager.cpp#L37-L105) also maps a cancelled query to success. Do not interpret that success as Thread evidence or replace it with a typing indicator during recovery.
198
239
 
199
- An explicit operator-confirmed diagnostic send could provide a real target-bound API outcome, but it can leave a message when the Thread exists or the acknowledgement is lost. Approval of that recovery behavior and control is separate from automatic rotation; implementation and disposable live verification remain gated in `BACKLOG.md`. Until then, ambiguous or legacy `deletion-issued` attempts retain their fence. Never infer absence from cached client visibility, release because a target is present, retry an unknown deletion, or edit runtime files to force progress.
240
+ An explicit operator-confirmed diagnostic send could provide a real target-bound API outcome, but it can leave a message when the Thread exists or the acknowledgement is lost. Approval of that recovery behavior and control is separate from automatic rotation; it is not implemented and no work is scheduled. Until then, ambiguous or legacy `deletion-issued` attempts retain their fence. Never infer absence from cached client visibility, release because a target is present, retry an unknown deletion, or edit runtime files to force progress.
200
241
 
201
242
  ## Approved Next Contract: Session-Aware Workspace Identity
202
243
 
203
- Status: locally implemented and validated for the next minor release; final compatibility acceptance and the operator-authorized live smoke remain tracked in `BACKLOG.md`.
244
+ Status: shipped in 0.52.0 after local validation and the operator's live smoke.
204
245
 
205
246
  A durable Workspace binding is owned by the selected bot profile plus `{ cwd, sessionId? }`. `cwd` is the normalized exact full path. When present, `sessionId` is the exact non-empty bounded value returned by the public `ctx.sessionManager.getSessionId()` API. Session display names, selector positions, session file paths, process instance IDs, and lifecycle generations are metadata or transient fences, never substitutes for the stable session ID. Absence is a real identity value `{ cwd, ∅ }`, not a wildcard; malformed present values still fail closed.
206
247
 
@@ -230,9 +271,11 @@ Followers first try to re-register after leader reload or unknown-heartbeat resp
230
271
 
231
272
  The session-native local wire contract has protocol version `2`, independent from the npm package version. Follower registration and the leader acknowledgement carry `{ protocolVersion, runtimeBuild, capabilities }`. Capability names are canonical, unique, and sorted. A leader rejects missing or mismatched protocol identity before provisioning a target or publishing the follower into live routing; a strict follower likewise rejects an acknowledgement without compatible leader identity. Different package builds remain compatible when their protocol versions agree. `durable-follower-admission-v1` gates source forwarding, while `queue-handoff-v1` independently gates live semantic queue transfer; every participant in a routed handoff must advertise it. `workspace-follower-auto-connect-v1` gates restore-only startup admission. Session identity is part of protocol v2 rather than an optional capability. Every cwd-scoped v2 registration carries the exact bounded session ID; missing identity is rejected before provisioning or publication. Reconnect, replacement, and promotion preserve it. Protocol-v1 peers are rejected by the base version check, avoiding mixed semantic branches. `thread-display-mode-v1` gates exact-generation follower display-setting requests and Letters/Directories require compatible connected followers; returning to Names allows legacy peers. Registration checks the current display-mode requirement before provisioning and again before live publication. The leader serializes config persistence and title application. `workspace-thread-rename-v1` independently gates follower rename requests whose exact registration generation is checked before the leader mutates Telegram and persists the Workspace binding. `session-replacement-intent-v1` gates Telegram `/new` from a follower Thread: followers never write `state.json`, so `follower.publishSessionReplacement` asks the leader to CAS-publish the durable intent only when the live registration generation, leader epoch, Profile, exact `cwd`, source session, Thread target, and the leader's own Workspace binding slot/name all match, with a bounded unexpired TTL. The intent records the source runtime instance; only a successor registration from that instance or its authenticated same-process handoff (`previousInstanceId`) may re-key the binding, and `follower.settleSessionReplacement` claims it once only after the leader's store shows that successor session bound to the same target. A missing capability, stale generation, or mismatch fails the command closed.
232
273
 
274
+ The candidate advertises `workspace-restore-v1`; all participating peers and potential leader successors must be upgraded before operator-controlled use. The retired `leader.replaceFollowerTarget` envelope is no longer parsed or produced, even with current authentication and registration generation. Its `leader.workspaceRestore` envelope carries only an operation ID, exact recipient registration generation and `apply`/`inspect` mode; the receiver derives binding and destination from the retained intent, not caller-supplied target data. Authentication and an explicitly enabled receiver are mandatory. The controller checks both peers' capability and protocol compatibility, captures live registration/session/slot/endpoint authority, sends once without transport retry, then validates the request, operation, recipient, observed target, slot and boolean readiness against the current registration. A lost response leaves the durable issuance consumed; a separate read-only inspection can prove readiness without applying again. Neither the controller nor receiver settles source input or writes follower-owned canonical state. A successor registration/process may inspect only its actual same-session canonical binding and local target; readiness records the new observer separately without changing the original issuance. An old local target cannot be repaired through inspection, and successor apply is rejected. Interrupted startup, terminal source/cleanup settlement and live behavior were accepted in the operator's 0.52.0 live smoke.
275
+
233
276
  Negotiated identities remain on the live follower registry and appear in `/telegram-status --debug` plus the observational state snapshot. The Threaded Mode capability monitor owns one in-flight probe across lifecycle generations: stop/restart invalidates a late read, and a replacement monitor waits for the previous request to settle instead of creating overlapping transport transitions. Durable follower admission is authorized only when both peers advertise `durable-follower-admission-v1`, never inferred from package version: a capable runtime rejects missing support before provisioning, inbound routing, or election-roster eligibility. Authentication and exact registration generation remain mandatory independently of protocol compatibility. `follower.register` is the explicit bootstrap request and may provision; capability-gated `follower.restoreWorkspace` is startup-only and may claim, probe, or replace a remembered binding but returns `workspace-binding-unavailable` without allocating when no binding or claim-fenced legacy exact-`cwd` record exists. Both carry a fresh generation; every later request is exact-generation-fenced against the live registry entry. `bus.ack` is response-only and is rejected if submitted as a server request. Leader forwarding never synthesizes authority for an unknown recipient and preserves the follower's exact durable receipt end to end.
234
277
 
235
- Foreign update forwarding returns an explicit `accepted`, `retryable`, or `terminal-rejected` settlement. Acceptance requires an acknowledgement for the exact request whose receipt contains the expected stable `deliveryId` and source `update_id`; a callback error popup never substitutes for that receipt. Missing or negative acknowledgements, stale registrations, absent follower context, binding rejection, journal admission failure, and missing or mismatched receipts all retain the leader source. Message, edited-message, reaction, and callback paths share this contract.
278
+ Foreign update forwarding returns an explicit `accepted`, `retryable`, or `terminal-rejected` settlement. Acceptance requires an acknowledgement for the exact request whose receipt contains the expected stable `deliveryId` and source `update_id`; a callback error popup never substitutes for that receipt. Missing or negative acknowledgements, stale registrations, absent follower context, binding rejection, journal admission failure, and missing or mismatched receipts all retain the leader source. Message, edited-message, reaction, and callback paths share this contract. The follower remembers each admitted `deliveryId` in a process-local window (24 hours, at most 4,096 deliveries); a repeated delivery inside it, including after its journal entry completed and was removed, is acknowledged with the same receipt without another append or handler execution. This absorbs a leader retry after a lost acknowledgement; it is bounded at-most-once behavior, not exactly-once across recipient restart or window eviction.
236
279
 
237
280
  The delivery id excludes the replaceable runtime instance and registration generation: it derives from envelope kind, source `update_id`, and the stable manual-follower binding. Stored message ownership carries that binding and may rebind to the current authenticated registration after follower replacement, preserving one retry identity while still fencing each attempt by the current generation. A lost acknowledgement retries with the same delivery identity and becomes accepted only when the exact durable receipt returns. Journal deduplication covers retained entries, not lifetime completion history: after ordinary receipt completion removes an entry, cursorless admission can accept that source again. Stable delivery IDs therefore do not by themselves establish exactly-once execution. Owned local IPC evidence reproduces the gap: a proxy drops actual ACK bytes; real forwarding, authenticated receiving, paired admission, journals and admission workers leave the leader source in retry-wait after follower completion. The next attempt reaches an injected authorized callback handler twice in total, then both journals settle. A queued-message variant likewise re-admits the source under a fresh acquisition after a real worker receipt handoff. This proves repeated handler/queue-admission invocation in the composed local pipeline, not actual Pi queue dispatch, live Telegram effects or model execution. Delivery custody, including role-dependent local replay, must be reconciled before a consume-once guarantee can be claimed; the [design counterexamples](./architecture.md#busjournal-design-acceptance) rule out treating origin disappearance as proof of a specific handoff.
238
281
 
@@ -246,7 +289,7 @@ Implemented transport:
246
289
 
247
290
  ### Local IPC endpoint with bounded native paths
248
291
 
249
- Leader opens a local Node `net` endpoint: a Unix-domain socket under the agent temp directory on Unix-like platforms, or a deterministic Windows named pipe (`\\.\pipe\pi-telegram-...`) on native Windows. A filesystem-style endpoint supplied by legacy state or a transport harness normalizes deterministically to the same Windows pipe boundary before listen/connect. On Unix, an endpoint that would exceed conservative domain-socket pathname limits maps to a private user-scoped, hash-derived path under the OS temp directory; ordinary agent paths remain under `tmp/telegram`. On Unix, each server listens on a private generation socket and atomically publishes the stable profile path as a relative symlink; delayed shutdown closes only its private path and cannot remove a replacement generation's link. Followers register, heartbeat, and exchange routed events. The transport boundary owns endpoint derivation, socket-vs-pipe detection, bounded operation-aware retry policy, timeout/transient IPC error classification, endpoint reachability probes, and request-scoped transport events. Follower registration uses a longer registration-specific response timeout than ordinary heartbeat/forwarding calls because the leader may need to provision a Telegram thread before it can return the assigned target; timing out that handshake leaves a visible tab with no follower heartbeat. Keep this handshake to the true critical path: create/reuse the target, persist the live binding, and return it. Connected notices and replaced-thread reconciliation cleanup are non-critical and should run after registration so a follower becomes routable before Telegram client/server UI convergence work finishes.
292
+ Leader opens a local Node `net` endpoint: a Unix-domain socket under the agent temp directory on Unix-like platforms, or a deterministic Windows named pipe (`\\.\pipe\pi-telegram-...`) on native Windows. A filesystem-style endpoint supplied by legacy state or a transport harness normalizes deterministically to the same Windows pipe boundary before listen/connect. On Unix, an endpoint that would exceed conservative domain-socket pathname limits maps to a private user-scoped, hash-derived path under the OS temp directory; ordinary agent paths remain under `tmp/pi-telegram`. On Unix, each server listens on a private generation socket and atomically publishes the stable profile path as a relative symlink; delayed shutdown closes only its private path and cannot remove a replacement generation's link. Followers register, heartbeat, and exchange routed events. The transport boundary owns endpoint derivation, socket-vs-pipe detection, bounded operation-aware retry policy, timeout/transient IPC error classification, endpoint reachability probes, and request-scoped transport events. Follower registration uses a longer registration-specific response timeout than ordinary heartbeat/forwarding calls because the leader may need to provision a Telegram thread before it can return the assigned target; timing out that handshake leaves a visible tab with no follower heartbeat. Keep this handshake to the true critical path: create/reuse the target, persist the live binding, and return it. Connected notices and replaced-thread reconciliation cleanup are non-critical and should run after registration so a follower becomes routable before Telegram client/server UI convergence work finishes.
250
293
 
251
294
  Pros:
252
295
 
@@ -287,7 +330,7 @@ Native Windows support should not require WSL. The baseline transport uses Windo
287
330
  Manual smoke checklist:
288
331
 
289
332
  1. With Threaded Mode disabled, connect one Pi, attempt a second classic connection, confirm the ownership-handoff prompt, complete takeover, and verify the displaced process loses Bot API mutation authority without losing accepted local queue state.
290
- 2. Stop all owners, corrupt only a disposable `owners.json`/state snapshot, reconnect, and verify guarded stale recovery quarantines the damaged file while preserving `telegram.json`; inspect the replacement snapshots to confirm complete atomic JSON rather than partial writes.
333
+ 2. In an explicitly approved disposable agent directory, stop all owners, corrupt only `state.json`, then run `/telegram-connect`: verify one `state-reset` diagnostic, a fresh private envelope owned by the connecting leader, normal polling, and unchanged `telegram.json`/released `tmp/telegram`. Inspect replacement snapshots for complete atomic JSON.
291
334
  3. Enable Telegram private-chat Threaded Mode for the paired bot.
292
335
  4. Start Pi in one Windows terminal and run `/telegram-connect`; verify it becomes the leader, gets a named Telegram thread, and publishes a native `\\.\pipe\...` endpoint in explicit diagnostics.
293
336
  5. Start Pi in a second Windows terminal and run `/telegram-connect`; verify it registers as follower rather than offering takeover, creates/uses its assigned thread, terminal status shows `<ThreadName> Follower` while idle, and a follower prompt flips it to `<ThreadName> Active` while work is running.
@@ -296,9 +339,9 @@ Manual smoke checklist:
296
339
  8. Force a live Threaded Mode capability downgrade and verify the current leader keeps classic polling while followers disconnect instead of attempting takeover; restore capability and reconnect explicitly.
297
340
  9. With `🧹 Thread cleanup` enabled (default), quit the follower Pi normally without an explicit disconnect; verify graceful shutdown deletes its current tab through the leader before local suspension. Repeat with a double `Ctrl+C`; if Pi misses the graceful envelope, verify stale-heartbeat recovery deletes the same exact tab only after the OS confirms that follower PID has exited. Disable the setting, quit another follower normally or abruptly, and verify its tab remains as a restart hint. Reconnect, run `/telegram-disconnect`, confirm the prompt, and verify the leader confirms deletion before local polling stops.
298
341
  10. With `🧹 Thread cleanup` enabled, quit the leader normally and verify it deletes only its own tab before releasing transport; a remaining follower may then promote without recreating the deleted leader tab. If deletion is interrupted after intent persistence, start or promote a successor and verify it completes the exact pending cleanup before publishing its follower endpoint or provisioning its own tab; when the successor has the same stable leader profile and its old binding remains active, it must adopt that binding first and cancel the superseded cleanup without calling Telegram close, delete, or create APIs.
299
- 11. Generate enough diagnostics to cross the rotation threshold, reload the leader, and verify `logs.jsonl`/`logs._prev.jsonl` preserve complete ordered records. Confirm ordinary status hides raw pipe internals while explicit debug diagnostics show the active `pipe` endpoint and classified request failures.
342
+ 11. Generate enough diagnostics to cross the rotation threshold, reload the leader, and verify `logs.jsonl` and `logs/logs._prev.jsonl` preserve complete ordered records. Confirm ordinary status hides raw pipe internals while explicit debug diagnostics show the active `pipe` endpoint and classified request failures.
300
343
 
301
- If any step fails, capture `telegram-status --debug`, `tmp/telegram/state.json`, `tmp/telegram/logs.jsonl`, and, after a reload, `tmp/telegram/logs._prev.jsonl`. Debug status prints local leader/follower endpoints with their active transport kind (`pipe` or `socket`), while the runtime log records request-scoped transport failures with envelope kind, request id, retry attempt, endpoint, and classified IPC error. Reloads preserve the prior JSONL log as `logs._prev.jsonl` so the evidence that caused the reload is not immediately overwritten.
344
+ If any step fails, capture `telegram-status --debug`, `tmp/pi-telegram/state.json`, `tmp/pi-telegram/logs.jsonl`, and, after rotation, `tmp/pi-telegram/logs/logs._prev.jsonl`. Debug status prints local leader/follower endpoints with their active transport kind (`pipe` or `socket`), while the runtime log records request-scoped transport failures with envelope kind, request id, retry attempt, endpoint, and classified IPC error. Rotation preserves the prior mixed-profile segment as `logs/logs._prev.jsonl` so the evidence is not immediately overwritten.
302
345
 
303
346
  ### Native Windows Assumption Audit
304
347
 
@@ -460,11 +503,11 @@ Rules:
460
503
 
461
504
  Current state under the agent dir:
462
505
 
463
- - `tmp/telegram/owners.json`: authoritative extension-local transport owners keyed by `default` or named profile. Each owner contains the bus leader identity, capability secret, heartbeat, generation, and cleanup fencing epoch. Mutations serialize through `owners.json.transaction`; followers never write owner slots. The local bus endpoint is derived from the agent directory by default; legacy `busSocketPath` entry fields are tolerated inside current owner records but are not required.
464
- - `tmp/telegram/state.json`: volatile extension+bot observable/debug snapshot, not routing authority. It writes `source: "snapshot"` and `writtenAtMs` so consumers do not confuse it with an authoritative database. Every process on one Telegram profile reads this shared path, but only the active transport owner may persist it; followers become writers only after promotion. Status-only persistence refreshes disk-backed bindings before serialization so an already-loaded stale view cannot erase newer leader records. It mirrors `/telegram-status`-style projections: top-level `bot` stores bot-wide capability state such as `threadMode: "unknown" | "enabled" | "disabled"`; `runtime` identifies leader/follower role, lifecycle activity, and the exact polling phase/progress snapshot; `liveRoster` mirrors followers/current targets/reservations; `diagnostics` mirrors status/debug signals; `threads` stores current routeable bindings; `workspaceBindings` stores dormant exact-`cwd` target/name/slot reuse hints; `bot.lastSlot` stores the compact slot cursor used when all current threads are gone; and `reservations` records short-lived slot collision guards.
465
- - Local bus endpoints: Unix-like platforms expose stable `tmp/telegram/bus.sock` and `tmp/telegram/followers/*` symlinks backed by private generation sockets; native Windows uses deterministic named pipes under `\\.\pipe\pi-telegram-...`. These are transient IPC endpoints, not durable routing state.
506
+ - `tmp/pi-telegram/state.json` `profiles.<profile>.transport`: authoritative transport owner for `default` or a named profile, holding the bus leader identity, capability secret, heartbeat, generation, cleanup fencing epoch and polling-journal pointer. Mutations serialize through `runtime/state.json.transaction`; followers never write it. The local bus endpoint is derived from the agent directory; a `busSocketPath` field is tolerated but not required.
507
+ - `profiles.<profile>.workspace`: canonical Workspace/thread state; `profiles.<profile>.runtime`: volatile observable/debug projection, never routing authority, with `source: "snapshot"` and `writtenAtMs`. Every process on one profile reads this shared file, but only the active transport owner may publish either section; followers become writers only after promotion. Runtime publication never touches the canonical section, so a stale status writer cannot erase leader records. The `runtime` section mirrors `/telegram-status`-style projections: `runtime` identifies leader/follower role, lifecycle activity, and the exact polling phase/progress snapshot; `liveRoster` mirrors followers/current targets/reservations; `diagnostics` mirrors status/debug signals. The `workspace` section keeps top-level `bot` capability state such as `threadMode: "unknown" | "enabled" | "disabled"`; `threads` stores current routeable bindings; `workspaceBindings` stores dormant exact-`cwd` target/name/slot reuse hints; `bot.lastSlot` stores the compact slot cursor used when all current threads are gone; and `reservations` records short-lived slot collision guards.
508
+ - Local bus endpoints: Unix-like platforms expose stable `tmp/pi-telegram/runtime/bus.<hash>.sock` (leader) and `runtime/f.<hash>.sock` (follower) symlinks backed by colocated private generation sockets, with bounded private external shortening only for over-long paths; native Windows uses deterministic named pipes under `\\.\pipe\pi-telegram-...`. These are transient IPC endpoints, not durable routing state.
466
509
 
467
- If an unclean host shutdown truncates `owners.json`, a profile `state*.json`, or the ownership transaction guard, `/telegram-connect` classifies the damage before recovery. With no verifiable live owner, one cross-process recovery winner quarantines only those damaged disposable artifacts and startup retries once; followers or leaders appearing during the final guarded reread stop the reset. `telegram.json`, `logs*.jsonl`, other profiles' valid state, and unrelated extension data remain untouched. A blocked or failed reset reports which Pi must restart instead of emitting repeated raw parse/transaction errors.
510
+ If an unclean host shutdown damages `state.json`, the next non-election leader start replaces it with an empty envelope and continues (see [Damaged-State Reset](./architecture.md#damaged-state-reset-operator-approved)). Every profile's runtime continuity is deliberately lost; `telegram.json`, released `tmp/telegram` and unrelated extension data remain untouched. No section-scoped recovery is planned.
468
511
 
469
512
  The bridge must not keep a separate durable `telegram-targets.json` history. Profile-specific `state.json` retains exact-`cwd` Workspace bindings as restart hints, but they never authorize routing without a matching authenticated process claim and role-appropriate liveness/visibility proof. Stale/offline/failed observations are not reusable delivery authority. `sync` remains event-driven assumption reconciliation rather than a full Telegram bot-state mirror because Bot API exposes no complete thread listing surface. Non-current routeable thread bindings are pruned during load/persist; old session records must not be retained merely to drive allocation. Workspace allocation uses retained binding/claim reservations; `bot.lastSlot` remains a compatibility cursor only for provisioning without Workspace identity. Previous-process leader bindings are treated as occupied TTL-bounded reservations until Telegram confirms deletion: reload/startup may close/delete/probe the old thread, known reservations are retried proactively on leader startup, and if Telegram still accepts the old thread id, the new leader should provision the next free slot (`B`, `C`, …) rather than creating a duplicate same-letter tab or blocking startup on Telegram UI convergence. Routing must use live current threads/follower registry, never reservations. The bus leader provisions its own thread during bus startup/connect and provisions follower threads on `follower.register`; registered followers also live in the leader's in-memory registry and communicate over the local bus socket. The live follower registry can resolve a follower by exact `{ chatId, threadId? }`; the leader uses that target ownership to forward message and edited-message updates to followers, and the follower receiver accepts those updates in addition to callbacks and reactions. Terminal status and `[telegram|thread:name]` resolve the matching current-instance identity through the same target-aware path, preferring registered local metadata over stale shared bindings. Media album grouping and split-text coalescing keys include the thread target, queue reaction mutations can scope by chat/thread to avoid cross-target message-id collisions, active-turn target is exposed for lifecycle cleanup and local direct-tool defaults, transport reply dedup is chat/thread-scoped, stored menu state is keyed by chat/message so callback state lookup cannot collide across chats, and generated button turns plus section prompt/open actions preserve the callback thread target. `telegram_message` and immediate `telegram_attach` delivery can also carry an explicit `thread_id` with `chat_id`; when a follower is registered, their default direct-tool target is the assigned thread target and the bus-aware API runtime routes the send through the leader instead of calling Bot API transport locally.
470
513
 
@@ -502,7 +545,7 @@ All files containing routing, chat ids, thread ids, or process details use priva
502
545
 
503
546
  ### Stale thread delivery
504
547
 
505
- Local regression evidence covers continuity and cleanup authority; operator-coordinated live Restore verification remains tracked in [BACKLOG.md](../BACKLOG.md).
548
+ Local regression evidence covers continuity and cleanup authority; operator-coordinated live Restore verification passed in the operator's 0.52.0 live smoke.
506
549
 
507
550
  - Direct replies, menus, activity, target-aware edits, and multipart transport capture the request target and local authority before sending. Exact typed HTTP 400 stale-thread evidence stages invalidation of only the matching unchanged binding. The thread store rechecks leader/session/profile authority, binding identity, snapshot revisions, and destination path at the synchronous owner-fenced rename; no staged invalidation enters the live projection before durable commit, and a rejected commit preserves newer state.
508
551
  - Shared recovery marks topic, target-binding, and transport freshness suspect and schedules a diagnostic snapshot. Accepted local work and its active-turn target remain unchanged; a failed send is not replayed or redirected, and recovery does not create a replacement thread or probe on every send. Errors without a proven request target do not authorize invalidation.
@@ -520,7 +563,70 @@ The bounded pending reroute owns its original source target and the returned cho
520
563
 
521
564
  After dispatch, cleanup retries retain source identity but never redispatch accepted messages. Confirmed typed HTTP 400 `message to delete not found` completes message deletion idempotently; permission and transient failures remain errors. Reroute cleanup rechecks current bindings, live targets, reservations, and pending provisions before close, before delete, and on retry. Other destructive cleanup origins use the shared synchronous target-protection policy: explicit retirement permits only its unchanged departing binding, persisted shutdown intent permits only its original pre-intent binding, and reservation/provision cleanup permits only the corresponding unchanged claim. Protection checks also guard post-API local invalidation, reservation, and disconnect completion. A newly protected target cancels remaining cleanup. Already-issued remote operations cannot be undone by a later local ownership change; checks prevent subsequent effects, not retroactive cancellation.
522
565
 
523
- Follower Restore requires exact registration generation and expected old target both before and after awaited IPC acknowledgement/store loading. Completion also rechecks the restored target and generation after persistence before publishing status or acknowledging success. A mismatched, replaced, or same-target request cannot overwrite the current registration.
566
+ Follower Restore uses only the source-bound `leader.workspaceRestore` protocol described under [protocol identity and compatibility](#protocol-identity-and-compatibility). The follower validates the retained operation and exact registration/session authority under read-only canonical snapshot protection; it never persists leader-owned state. Lost apply replies require inspection, not target replacement or another apply grant. Legacy target-replacement requests are rejected before recipient effects.
567
+
568
+ ### Restore settlement ordering
569
+
570
+ **Approved ordering and conservative 0.52.0 policy; acceptance, scoped removal ACK, cold hint continuation and queued readiness composed, retention/interruption/startup acceptance still open:** Separate acceptance-proof publication from asynchronous cleanup. The worker still commits a queue receipt or removes a completed source before notifying routing of source disposition; routing then re-enters Workspace admission to publish the Restore settlement. Follower Restore now publishes positive forwarding acceptance before reporting source completion, then co-publishes an immutable acceptance-scoped ACK with hash-guarded journal removal. Failed acceptance publication retains the original; later disposition-publication failure leaves a queued receipt or retained forwarding acceptance and scoped removal ACK. Cold canonical-settlement recovery now handles exact forwarded, completed-command and queued acceptance scopes; queued readiness remains separate from receipt-owned terminal disposition. Native full-capacity `settlement-publication-interrupted` fixtures exercise both cases: cold journal/snapshot reads preserve the relocated binding and issued grant, worker-cache restart can republish its own queued receipt, and unrelated callback completion cannot turn retained evidence into settlement when the strict scope-reader port is absent. Those fixtures keep recovery unwired and prove conservative protection. Separate cold-store/native-journal/IPC fixtures below exercise the new scoped-ACK continuation.
571
+
572
+ The native Restore store now exposes `recordSourceAcceptance` on an already-issued routing grant. Its optional, nonempty `routing.acceptances` records one proof per original: exact journal binding/update ID, SHA-256 of the worker's captured journal entry, accepted recipient identity, and either positive local completion, authenticated forwarding delivery ID/binding, or committed queue receipt/kind plus SHA-256 of its exact owner. Runtime publishers must validate the positive result and compute these hashes from journal-owner evidence, never a routed message projection. Storage validates scope, hash/receipt shape, current recipient and full executor/operator CAS; it cannot establish the truth of a caller-supplied execution result. The sorted source-unique collection rejects duplicate delivery identities and conflicting same-receipt queue owners/kinds/recipients. An exact duplicate is read-only, including revision/timestamps. Before/after-rename fault fixtures prove retained sources and exact lost-reply observation. Executor adoption preserves proofs. Cold parsing rejects malformed or contradictory acceptance/settlement evidence without repair; the existing byte bounds cover this optional field.
573
+
574
+ Acceptance alone never counts as terminal source settlement, grants cleanup, removes journal input, publishes a follower-owned snapshot, or permits dispatch replay. Canonical `queued` settlement facts retain admission only and cannot grant cleanup or retirement, even after source absence or a clearance hint. Only `queue-completed` records positive receipt-owned disposition; it requires matching retained queued acceptance and exact receipt/kind. It may upgrade matching admission IDs while preserving unproven siblings, but cannot duplicate terminal facts, change receipts or downgrade completion. Cold parsing rejects queued admission with cleanup and terminal receipt facts without acceptance, retaining the file without repair. Older writers reject the new terminal tag; every possible peer/successor must upgrade before live Restore. `recordSourceSettlement` rejects outcome/receipt mismatches; legacy admission without acceptance remains nonterminal. A read-only follower view may observe an exact duplicate proof but cannot publish a new proof or source disposition. Follower Restore composes acceptance before its completion report and publishes terminal settlement only after its journal ACK. Leader command completion now uses the same pre-disposition publisher through a private routed carrier; receipt-owned terminal disposition acceptance remains open; the approved terminal proof lifetime is immutable bounded retention below. Exact forwarded/completed/queued source-disposition recovery is composed below.
575
+
576
+ The implementation must use this order:
577
+
578
+ 1. Validate the worker's exact original, journal binding, current execution claim and positive outcome. Retain the source while publishing a bounded acceptance proof into its already-issued Restore operation. Distinguish recipient acceptance or committed queue admission from acknowledgement of source removal; neither readiness nor a report alone is a removal ACK. No second sidecar or general custody migration is required.
579
+ 2. Dispose of the exact completed source through the journal owner only after that proof is durable. A queued source keeps its existing owner/receipt lifecycle. An interrupted publication leaves the original protected by the issued Restore grant; a lost publication reply reconciles only the exact retained proof. Do not repeat dispatch to obtain another result.
580
+ 3. Recover interrupted source disposal only from the exact retained proof plus strict source identity/transaction checks, without invoking the message handler. An absent source without that proof remains unknown. A changed source, foreign receipt owner, conflicting result or expired runtime authority must not be silently removed or marked complete.
581
+ 4. Continue cleanup separately, under fresh profile admission and existing accepted-work protection, after the journal owner acknowledges source disposition. The synchronous completion-report path must not await the existing cleanup observer while holding the dispatch Workspace gate: that observer reacquires the same gate. A queued receipt must not start downstream dispatch before its required Restore proof is published.
582
+
583
+ Follower forwarding now uses `updates.inspectTelegramDeferredSource` through the original source's hidden admission carrier. The live worker requires its exact owner/signal, journal binding, deferred claim, context and process/session identity, reads the journal afresh, compares the full captured entry, then returns its hash. A changed, queued, missing, reported or stopped source cannot supply proof; a routed message projection is never hashed. Routing validates the positive delivery ACK against that original ID and the exact current recipient binding/generation. It publishes acceptance under the still-held Restore dispatch admission, without reacquiring the gate, before calling `reportTelegramUpdateCompleted(original, expectedSource)`. Admission preserves a detached guard through immediate and late completion; a duplicate ordinary report cannot downgrade it. The worker rechecks journal binding, context and captured process/session identity, then calls the separate `journal.removeCompletedExact` capability. There is no ID-only fallback when that capability is unavailable. Under the existing source serialization and journal transaction, every supplied hash must match the full current parsed entry; missing/changed sources reject the entire batch before publication. Even an exactly hashed queued or failed entry retains its existing receipt/operator-disposition protection. Ordinary non-Restore completion still uses its existing path. A failed publication or mismatched response leaves the original pending and accepted recipient work intact. A lost rename reply reconciles only the identical retained proof under current authority. Source-removal/observer failure retains that proof; neither a re-click nor unrelated completion repeats forwarding or invents a removal ACK. Native full-capacity fixtures cover these publication/removal boundaries, including an unqueued original changed after durable acceptance: hash-CAS retains it without a settlement ACK, repeated forwarding or cleanup. Native source-family tests verify lock ordering, whole-batch rejection, cold reads and absence-as-conflict. This proves composed exact-source disposal and proof-before-report timing; successor registration and operator acceptance remain separate gates.
584
+
585
+ The prepared journal primitive now accepts an optional third `removeCompletedExact` argument: source completions `{updateId, sourceSha256, completionSha256}`. Each completion requires a matching exact source guard and is co-published with removal in the same journal revision; it is not a pre-removal intent. The opaque completion hash must bind the caller's immutable Restore request/operator/acceptance and exact source scope, not a mutable executor lease; journal storage cannot prove the truth of supplied execution acceptance. `sourceCompletions` is retained in the existing v1 journal snapshot/segments, without a sidecar. Cold segment replay validates that new ACKs match the removed predecessor entry in that revision, and retained ACKs cannot change or disappear. Compaction and ordinary publishers preserve them. Active/discarded-source contradictions, duplicate IDs/scopes and foreign hashes fail closed. Retained markers prevent admission replay and empty-entry identity rebinding, including same-bot token rotation. Count, byte and actual source-work bounds apply; capacity never evicts older ACKs or removes the new source.
586
+
587
+ The prepared `completeQueuedExact(receipts, completions)` is a separate strict v1 capability, not an overload silently ignored by ordinary/v3 adapters. It reuses whole-receipt owner/process validation, requires unoffered complete receipt groups and matches each requested marker against the current full **queued** entry hash before disposal. Matching scopes co-publish with removal in the same revision; any source/owner/group/hash/capacity conflict retains the entire batch. A scope subset does not permit partial receipt removal or grant evidence to unscoped siblings, whose ordinary semantics stay unchanged. Cold segment continuity permits queued ACKs only when every predecessor group member is removed without reintroduction, with matching owner/kind and no offer; grouped validation is cached once per receipt. Strict binding composition supplies this capability from the private serialized sibling, while the raw custody surface omits it. Lost replies are observed with `inspectSourceCompletion`, never by a second disposal. Ordinary `completeQueued` still publishes no scoped ACK. The worker's explicit `completeQueueReceipts(..., sourceCompletions)` API still requires every source in the requested batch. Cached prepared subsets may now cover fewer sources only after a captured strict private v1 inspector confirms the complete queued receipt/full owner and each scoped queued-entry digest before readiness. Receipt membership is immutable for that owner/acquisition, and native ACK publication/cold continuity requires complete group removal. Each requested scoped receipt needs an origin witness: matching retained queued-source proofs then acknowledge whole-receipt disposition, never source absence or a marker for an ordinary sibling. Its captured exact disposer and strict reader verify both the returned ACK collection and retained evidence, with binding/context/process/session checks before memory acknowledgement. Required scopes remain sticky after failure; an ordinary retry cannot downgrade them. Issuance is retained in that worker before the disposal call, so an uncertain reply permits only exact read-only reconciliation, not another disposal. Missing proof protects all local claims; changed batch authority and worker stop/start cannot borrow that attempt. Native grouped/multi-receipt fixtures cover these boundaries without handler replay. Production queued acceptance now returns those immutable scopes to the barrier, which detaches and retains them before readiness and rejects missing terminal capabilities. Lifecycle callers use the retained scopes without minting their own. A captured post-ACK receipt observer emits only a routing hint, never Pi dispatch; canonical terminal publication re-enters admission and rereads exact evidence. Completion-only mux owner selection permits an issued attempt's read-only reconciliation after readiness is lost, while foreign context/reason/owner/binding stays blocked. Subset scopes require confirmed whole-receipt queued origin before readiness; missing inspection, partial/foreign origin or changed authority holds all sources. Any issued scoped disposition, including a pre-write exception, closes execution readiness and permits only completion-only exact proof reconciliation. Mixed batches of independent whole receipts now settle a scoped group first and an ordinary group separately, rechecking current owner/context/process/session/binding between them. Positive component ACKs survive another component's failure; mux/runtime retries reuse exact acknowledged receipt objects for local cleanup only, without restoring readiness or repeating disposal. Ordinary sources gain no scoped marker and retain ordinary unknown-disposition protection. Scoped post-ACK authority loss suppresses a stale worker wake. Native grouped/discard/lost-ACK/readback/publication fixtures prove this composed boundary, with Pi lifecycle handoff supplied rather than executed. Native subset-origin/multi-receipt/mixed/captured-reader and uncertainty fixtures prove this boundary without replay; actual interrupted startup was accepted in the operator's 0.52.0 live smoke; proof consumption is outside 0.52.0.
588
+
589
+ `inspectSourceCompletion(expected)` is a read-only exact observation under existing serialization/transaction and strict private source-handle validation. It returns only matching retained evidence, throws on another scope, and never repairs, quarantines or treats source absence as completion. New stores can observe a lost-removal reply without invoking disposal again. Neither marker publication nor inspection is exposed on the prepared v3 custody surface. Follower Restore now derives `completionSha256` from a domain-tagged, recursively key-sorted immutable request/operator/retained acceptance frame; executor, revision, timestamps and mutable progress are excluded. Admission validates and detaches the optional scope hash, preserves it across ordinary duplicate reports, and rejects conflicting scopes. The worker snapshots exact disposal/inspection capabilities, requires a reader before issuance, passes detached markers as the third removal argument, validates the returned collection and strictly reads each retained ACK before notification. Binding/context/process/session authority is checked again after publication and inspection. Missing, altered or foreign replies/reads produce no observer ACK; lost replies never retry disposal. Production v1 binding composition serializes ordinary access and exposes a strict private completion sibling, preserving ordinary recovery semantics. Cold continuation now uses this same exact evidence under fresh admission; the approved terminal lifetime is bounded immutable retention, with no marker expiry or release.
590
+
591
+ Completion and authenticated recipient-heartbeat observations now select ready, unissued-cleanup follower Restores with retained forwarding acceptances. The hint itself never settles a source. Under fresh profile admission, the controller derives each immutable scope and queries the exact active journal through a held `operator-disposition` reference. Missing or foreign proof cannot even adopt the executor. Positive evidence permits exact executor adoption plus authenticated `inspect` on the current same-session/CWD/slot/target follower; no handler, forwarding, apply or disposal runs. A changed registration generation is recorded only after that read-only canonical observation. After the await, each proof is read again before publishing only its matching unsettled IDs. Partial evidence cannot release missing originals. The captured live-recipient fence remains active through cleanup awaits and publication; unknown/issued cleanup never replays. Interrupted canonical settlement publication resumes on a new hint from the same retained ACK, without source mutation. Native cases cover missing/conflicting/unreadable/foreign-binding evidence, ended authority, post-await read/registration changes and cleanup uncertainty. They supply successor registration and clear accepted-work protection as explicit preconditions, not actual startup or recipient-work clearance proof.
592
+
593
+ Leader command Restore now binds `updates.bindTelegramUpdateCompletionAcceptance` only to its routed messages, preserving their execution fence without mutating the original shared worker binding. Positive complete reports publish source-hashed `completed` acceptance under the held dispatch admission before the original completion report can reach disposal. The shared publisher rechecks current leader/session/target/source and exact retained proof; lost rename replies reconcile only that proof. The carrier detaches and memoizes its scoped evidence, rejects conflicts or missing scope, and does not run for deferred/queued outcomes. Native `/start` temporary-tab Restore proves once-only local execution, acceptance-before-removal, scoped ACK, retained source on failed publication, and unchanged tab/slot fate. Production All-command routing supplies the prepared stores and owned leader epoch; local fixtures do not establish actual Pi startup or Telegram-client acceptance. Ordinary commands including `/new` still use their existing post-removal lifecycle observers.
594
+
595
+ A source-journal completion hint now also selects a ready leader Restore with retained completed or queued acceptance and unissued cleanup. Fresh admission and strict scoped ACK queries precede executor adoption. A read-only canonical observation requires one exact active binding/session/CWD/slot/target and one current leader owner with matching instance, CWD and profile key; local target equality alone cannot re-key the operation. The existing inspect-only controller preserves the original recipient/grants and observes the same-session current leader without applying identity, invoking a handler or sending follower RPC. After its await, immutable proofs are read again before settling only their proven originals. Live fences remain in storage authority callbacks; canonical observations stay outside the snapshot transaction and are repeated through cleanup awaits and before retirement. An owner change after close retains issued cleanup rather than deleting or retiring. Exact already-retained local readiness needs no republication. Native cold cases cover same-instance/successor continuation, partial/missing/foreign evidence, wrong local session/CWD/target/owner, post-await proof/recipient changes, both canonical publication rename boundaries and unknown/issued cleanup, preserving source bytes throughout. Queued ACK continuation uses the same pre-adoption proof and canonical-owner fences. After recipient inspection it rereads unproven scopes and publishes `queue-completed`, grouping only matching receipts/kinds; admission-only, missing or partial proof cannot release another original. Native single-/multi-receipt cases preserve issued grants and source bytes across canonical publication faults, with or without a prior admission fact. Producer execution, successor startup/ownership and accepted-work clearance are distinct preconditions; these fixtures do not prove actual interrupted Pi startup. Scoped ACKs still never expire, evict or release.
596
+
597
+ The worker now has a prepared optional `beforeQueueReceiptPublished(receipt, owner, ctx, isCurrent)` barrier. Exact durable queue admission retains the original, but neither cached readiness nor its downstream observer publishes until the asynchronous acceptance publisher succeeds. Publication requires a captured exact receipt-inspection capability before and after the await, with current process/session/context/binding checks. Receipt and owner arguments are detached; grouped reports and concurrent snapshot refresh share one pending publication. Same-process cold receipt reconstruction uses that barrier without running the original handler. Callback failures hold queued work; readiness observers remain diagnostic after successful publication. Ordinary commit failures still release deferred claims synchronously. `waitForDrain` covers in-flight publication. Serialized production journal bindings now compose `inspectQueuedReceipt(expected)` with their existing strict private sibling and derive `isQueueReceiptCurrent` from the exact active recovery key. The read-only v1 observer requires every canonical ordered source ID, receipt/kind, full owner/acquisition and absence of a handoff offer. It returns detached source digests of the retained **queued** originals plus normalized-owner SHA-256; those digests are not pre-queue pending hashes or removal ACKs. Query mismatch yields no proof; malformed, corrupt, foreign or unprepared evidence throws without repair, rebinding or source mutation. Inspection uses lock-only source/config serialization, not writer admission, so observing exact accepted work does not borrow persistence permission. Native fixtures cover whole groups, owner/acquisition mismatch, offers, retained byte continuity, cold/detached proof, post-await completion/corruption and read-only authority. The production worker now wires routing's `beforeQueueReceiptPublished` publisher. It selects only retained operations overlapping that exact active-journal receipt, then reacquires fresh profile Workspace admission; ordinary receipts remain a no-op for Restore policy. A selected operation must be ready with its issued routing, exact current executor/operator and leader recipient. Canonical read-only binding/owner checks and live session/CWD/slot/target establish recipient authority before each `queued` acceptance. Exact receipt/source and owner-hash evidence is retained in the existing operation before readiness. Storage's authority predicate checks only live fences to avoid reacquiring its held snapshot transaction; canonical CAS plus pre/post snapshot reads preserve binding protection. Lost publication reply reconciles only an identical retained request/executor/operator/proof. After publication the publisher re-reads the whole receipt, and after admission returns it rechecks captured recipient guards; changed proof or authority suppresses readiness without rolling back accepted local queue work. No apply, prompt enqueue, deletion or cleanup is repeated by this publisher. Same-process worker reconstruction can republish only acceptance from its still-exact receipt; it is not actual interrupted Pi startup or a foreign-session successor grant.
598
+
599
+ Native full-capacity leader Restore cases exercise positive queued publication, both rename sides, a forged returned intent with no proof, missing/unreadable receipt or a changed source digest on proof reread, authority loss, recipient change before publication/after the proof reread/after admission, and same-process worker reconstruction. Each original stays queued under its durable owner, exactly one prompt is accepted, and uncertain acceptance never publishes readiness or triggers cleanup. A native two-original media-group producer now exercises the same contract through its real unbound-group debounce, shared chooser/Restore selection, journal and worker. At full A–Z capacity, it relocates the same binding/slot and enqueues one combined prompt with one full receipt. A failed or unproven second acceptance retains the first proof but holds the entire receipt; lost post-rename reply reconciles both. A changed second digest on reread or an actual native whole-group handoff offer withholds readiness without undoing accepted work. Same-process worker reconstruction completes partial proof, preserves the first acceptance byte-for-byte, and emits one readiness observation without journal mutation or another enqueue/apply. Duplicate acceptance publication leaves canonical bytes unchanged. These are grouped prompt fixtures, not all remaining control/local outcomes or actual Pi startup acceptance.
600
+
601
+ The private local-command carrier distinguishes queued admission from completed handling. Once it reports a valid queued outcome—even if its durable commit later fails—a trailing implicit command completion neither invokes the completed acceptance publisher nor reports source disposal. An explicit source-disposal guard in that state throws rather than downgrading receipt authority. The shared original binding and ordinary non-Restore `/new` lifecycle stay untouched. Native temporary-tab `/continue` tests use real Workspace admission, strict serialized journal observation and the prepared queue barrier: one control-lane continuation prompt remains receipt-owned, publication faults hold readiness and same-process reconstruction resumes only acceptance. Its tab remains bound and temporary source custody is retained until actual queue terminal settlement, not merely readiness. `/compact` is deliberately different: it opens the existing confirmation dialog, publishes completed acceptance before scoped removal, and cannot open another dialog on Restore re-click. The confirmation's later callback is an independent input, not the original Restore source.
602
+
603
+ #### Terminal Proof Lifetime Boundary
604
+
605
+ A scoped journal completion ACK has two independent consumers: exact Restore disposition readback and journal admission anti-replay. Canonical operation retirement does not consume either journal authority. The current lifetime is bounded, immutable retention: segment continuity rejects erasure/rewrite, compaction carries ACKs into the snapshot, matching old input remains duplicate, and even an empty executable journal cannot rebind its bot/profile identity while proof remains. Count, byte and source-work limits refuse new publication while preserving the original and older ACKs; they never expire or evict evidence. An accepted polling cursor is not a recipient replay barrier. Metadata-only address pruning does not alter source files, and independent Restore/retirement/temporary snapshots are not its consumption roots. Approved optimistic session sweeping and guarded damaged-state deletion are separate accepted storage-loss policies, not completion or reusable proof-release grants. The operator approved this existing bounded immutable retention policy for 0.52.0. Capacity exhaustion is an accepted availability limit: preserve the new original and older ACKs, refuse publication, and never reset proof or replay accepted work to regain capacity. No terminal-proof consumption API is composed or required for 0.52.0; a future release protocol must separately prove retained anti-replay protection. Actual startup was accepted in the operator's 0.52.0 live smoke. Existing journal tests cover compaction, replay, identity and capacity boundaries; supplied host/registration fixtures do not establish actual Pi startup.
606
+
607
+ #### Protected Historical Originals And Cleanup
608
+
609
+ The operator-approved 0.52.0 policy for protected unsupported/non-text historical originals is retain-only: keep exact bytes, do not invoke their handlers again, and do not dispose of them through generic Cancel or raw journal removal. A clean pending record or missing local receipt is not positive non-admission proof; recipient admission may precede a lost ACK. Classification uncertainty must not fall through to ordinary dispatch or grant source exemptions. These unresolved sources continue protecting affected cleanup. The removed Historical inputs UI stays removed; unsupported-source cancellation/release is deferred until a design proves the exact source, whole owner/group membership and a positive permissible disposition. The operator also approved retaining every eligible raw unsupported historical private-Thread original on bound Threads, not only sources with surviving Restore/temporary membership. New-world forgetting therefore cannot release those originals to startup dispatch: each worker generation installs a new retain-only claim without reconstructing forgotten intent or adding durable witnesses. Supported cold spending, live arrivals, business/forwarded and known owner-bearing paths, queued ownership and follower custody remain unchanged. Native regressions cover both membership paths after relocation/forgetting and ordinary bound unsupported startup originals; writer closure is not claimed, while actual startup and live cleanup were accepted in the operator's 0.52.0 live smoke.
610
+
611
+ Keep fresh canonical binding and all required predecessor/current journal references for cleanup; nonempty unclassified/shared, incomplete or unreadable evidence remains protective. The old-target observer unions both legacy keys and exact session/recipient address pairs from the immutable request and fresh canonical binding; equal recipient keys in different sessions are not duplicates. Strict journal reads carry physical presence separately from entry count: an absent required binding family is unknown, not present complete-empty proof, even when a fresh namespace census succeeds. Native follower fixtures recheck this union after close-await metadata pruning and preserve relocation, recipient custody and issued cleanup when predecessor evidence disappears or becomes nonempty/unreadable. Queue-owner witnesses publish real prompt/control receipts and whole-group offers in disposable native journals: binding-owned groups remain protected irrespective of source target, while nonempty shared/discovered groups remain unknown rather than exempt. Predecessor pruning and a new canonical successor cannot hide those groups; original bytes, owner/acquisition, offered custody, relocated binding/slot and independently accepted recipient work stay exact after later hints. These are local journal/router/worker and strict resolver/reference composition checks, not actual Pi startup, writer closure or follower-custody activation; actual-host behavior was accepted in the operator's 0.52.0 live smoke. The temporary-tab adapter uses the journal owner's bounded strict complete-empty namespace guard under source serialization and per-family references. It requires present exact current/historical source keys, inspects the owners-named polling source plus retained flat/session families, and grants no target-based exemption to nonempty shared or unclassified work. Native new-world and last-Cancel fixtures prove missing/corrupt/unclassified/reference-refused sources block cleanup, while present complete-empty evidence permits the existing one-shot path; cancellation keeps exact private originals and forgetting does not reconstruct intent. These fixtures and composition inspection do not prove writer closure, actual Pi startup or live deletion. Acceptance, exact journal-owner terminal disposition (whole receipt for queued work), and old-tab cleanup are separate results. A successful relocation can retain its binding while cleanup remains blocked; do not roll back or redeliver to force deletion. Warm continuation uses only the same retained intent, exact ACK and fresh canonical recipient identity: lost apply replies permit inspection, lost disposal replies permit exact readback, and issued/unknown cleanup never repeats. Recheck authority and evidence after every await. Cold restart, including reload that creates a new runtime instance, follows new-world forgetting without rollback, not warm intent reconstruction.
612
+
613
+ Native both-role/full-slot warm close-await checks now change context, session generation, epoch or canonical binding while `closeForumTopic` is awaited. A separate follower registration replacement exposed a source-hint gap: source settlement now also checks the acknowledged ready follower's exact generation/session/CWD/slot/target and negotiated capabilities before storage publication and after awaits, without borrowing heartbeat authority or reacquiring a snapshot lock inside storage CAS. The close may already be issued, but the next delete is refused; cleanup remains issued, accepted originals and an independent receipt stay exact, and regained authority, duplicate hints or re-clicks cannot repeat apply, delivery or cleanup. These are native journal/worker/router/IPC fixtures with supplied host and cleanup-clearance ports, not actual Pi startup, writer closure or live transport acceptance. The native both-role/full-capacity fixture now also interrupts disposal before publication or loses its post-publication reply, then continues with the same retained intent and a fresh same-runtime recipient generation. Leader whole-receipt completion and follower forwarded-source removal both preserve exact immutable scoped ACKs; continuation holds a source reference and fresh profile admission, inspects canonical successor identity and rereads proof after the await. Missing ACK or canonical identity blocks continuation; missing post-await proof or lost context withholds terminal settlement, and a later valid hint reads the same ACK without source mutation or another disposal. Re-click may independently inspect readiness without an ACK, but cannot turn readiness into disposition. Native independent whole receipts/originals keep cleanup protected, recipient custody and all other bindings remain exact, and apply/delivery/disposal never repeat. These fixture generations are not actual Pi startup/adoption or interrupted Restore host sequencing. Ordinary production follower-registration owner/epoch and context/generation races are locally proven separately; they do not close Restore's actual-host gate.
614
+
615
+ Current direct prompt Restore always passes through prompt queue admission; it has no separate unqueued completion producer to wrap. The local queued-owner protection checkpoint is verified for prompt/control/group/offered custody; native warm disposal/readback and close-await boundaries are locally proven, while actual interrupted Pi startup/registration and Restore host sequencing remain separately gated. Test interruptions before proof publication, after publication but before source disposition, and after source disposition but before cleanup; preserve independently accepted recipient work at every boundary. `/new` and ordinary non-Restore worker completion must retain their existing post-removal semantics. Live delivery was accepted in the operator's 0.52.0 live smoke.
616
+
617
+ #### Remaining Host Acceptance Boundary
618
+
619
+ Inspection of `lib/extension.ts` confirms that admission completion and receipt observers feed the existing routing settlement owner, follower registration/heartbeat observations supply recipient hints, and scoped proof reads hold `operator-disposition` references. The follower Restore receiver captures authenticated leader/profile and actual context/session-generation authority through `lib/bus-follower.ts`; it rechecks after admission and snapshot loading. `lib/lifecycle.ts` publishes the new context generation before composed queue/delivery/polling startup and follower refresh. Historical worker preparation separately invokes `forgetPreviousWorld` under captured transport authority. This is wiring evidence, not an executed host-ordering witness or a new runtime correction.
620
+
621
+ The production cold-start integration fixture seeds a previous-instance relocated intent, invokes supplied Pi hooks and proves forgetting without rollback. The both-role/full-capacity routing fixtures instead keep the same intent and supply warm generation/recipient changes around real journal/IPC effects. Ordinary production registration races hold creation, not Restore settlement. Combining those results does not establish actual Pi shutdown/startup ordering, successor preparation or inbound readiness. No additional supplied-hook permutation is selected as a substitute.
622
+
623
+ Close this remaining gate only with a separately authorized actual Pi host run on isolated storage; real followers are operator-started, never hidden replacement processes:
624
+
625
+ - `Identity and ordering`: Record exact build, platform, role/profile, runtime instance, Pi session/context generation, registration acknowledgement and journal binding. Correlate actual host lifecycle events with source preparation and the first ready recipient observation; a successful connect/send alone does not prove inbound readiness or Restore continuation.
626
+ - `Warm continuation`: Within the same retained runtime/intent, observe interruption and fresh successor readiness at the acceptance/disposition/cleanup boundary. Correlate immutable acceptance, exact scoped ACK readback, whole-receipt protection and fresh canonical references with no repeated apply/delivery/disposal or issued cleanup. Preserve independent accepted work; do not manufacture another effect to recover missing proof.
627
+ - `Cold replacement`: A new runtime instance must forget unfinished intent without rollback and retain protected originals/bindings; do not require adoption of the forgotten operation. Record this separately from warm continuation and from actual `newSession` storage succession. Both roles/all occupied slots, real multi-process/profile continuity and native Windows/macOS remain distinct acceptance dimensions; 0.52.0 covered them through the operator's live smoke and the release CI matrix.
628
+
629
+ This inspection authorizes no reload/restart, live-state mutation, fault injection or release. Before any separately approved live restart, let the queue drain or knowingly accept losing waiting work. Missing host access or approval leaves the gate open; existing native evidence remains valid within its stated scope.
524
630
 
525
631
  ### Split brain
526
632
 
@@ -551,7 +657,7 @@ Follower Restore requires exact registration generation and expected old target
551
657
  - [x] Queue, active turn, preview, reply deduplication, menu, section, button, reaction, and attachment state are scoped by instance/target. Queue reaction mutations and transport reply dedup are chat/thread-scoped; active-turn target is available to lifecycle cleanup; stored menu state is chat/message-keyed; generated button turns and section prompt/open actions preserve callback targets; preview and attachment delivery already carry targets.
552
658
  - [x] Authorization prevents arbitrary Telegram users or local processes from controlling agents or receiving artifacts.
553
659
 
554
- Live client and native Windows evidence gates are tracked in `BACKLOG.md`; this architecture document records the implemented contract, not the active smoke queue.
660
+ Open live client and native Windows evidence gates for future behavior belong in `BACKLOG.md`; this architecture document records the implemented contract, not the active smoke queue.
555
661
 
556
662
  ## Implemented Shape
557
663
 
@@ -62,11 +62,13 @@ Hidden compatibility shortcuts may open sections directly: `/help`, `/status`, `
62
62
 
63
63
  This command surface is a mobile companion subset, not a raw terminal-command bridge or session browser. A Telegram destination follows its assigned Pi instance and sends prompts into that instance's currently active session; it is not permanently bound to one session identity. Compaction and new-session replacement operate on the current session; resume, fork, tree navigation, session switching, TUI transcript clearing, and arbitrary slash-command dispatch stay out of the stable Telegram API unless Pi exposes safe public extension hooks for them.
64
64
 
65
+ An eligible single-text unbound Thread chooser offers **⛔️ Cancel routing** to the current paired owner. It retains the original privately without sending it to Pi or deleting its Telegram message/Thread. This is not `/abort` or `/stop`: already selected, partially forwarded, queued or running work cannot be cancelled here. Failed cancellation or chooser updates retain the same button for exact retry. When the current leader has protected attempts, **Status → ❌ Pending cancellations** opens owner-only review with up to five supported inputs per page. Finish cancellation preserves the original and starts no new delivery; previously accepted work may continue. Refresh, navigation, reload and ownership changes invalidate old controls. Unsupported source kinds are withheld, and corrupt retention evidence stays protected without implicit repair. Inputs already present in the worker's first validated startup snapshot cannot acquire fresh Cancel routing authority merely by recreating their chooser. Existing retained attempts remain recoverable. Historical unbound startup text is still held before default routing, but the Historical inputs menu entry and its review/Stop retrying submenu are removed. Previously published historical callbacks are answered as unavailable; they neither reopen the view nor authorize routing or abandonment. Hiding this technical state does not establish non-delivery or cancel independently accepted work. Confirmed eligible unbound prompt choosers now retain a fixed 60-minute deadline from first positive publication acknowledgement; refresh/restart does not renew it. Validated selection freezes expiry before effects; browsing or unavailable targets does not. Expiry retains the exact unselected original privately before pending-journal removal and invalidates stale controls, without Thread deletion, Pi stop or task-completion reporting. Selected/accepted/uncertain work and historical or unconfirmed clocks remain protected; no tab-disappearance signal is required. The journal lifetime capability and carrier hooks are package-private, not a new companion API. Live acceptance passed in the operator's 0.52.0 live smoke.
66
+
65
67
  ### Tools and assistant-authored actions
66
68
 
67
69
  Every assistant-authored HTML comment is transport-private on Telegram: previews and final replies remove `<!-- … -->` blocks regardless of Markdown nesting or which extension owns the comment, while only recognized top-level column-zero comments can activate voice or buttons. Unclosed comment tails are withheld and a comment-only result sends no text message; Pi's terminal transcript remains unchanged.
68
70
 
69
- - `telegram_bind({ app, script, argument? } | { app, method, argument? })` installs and initializes one canonical managed Generative App module under `<agent-dir>/genapps/<app>/<app>.mjs`, or invokes one named method on an installed app. Installation rejects silent replacement and noncanonical/symlink sources. Methods receive immutable JSON state, one optional JSON argument, cancellation, revision, and a bounded non-shell process port; successful state changes commit to `state.json` plus `states.jsonl`, while output-only methods leave history unchanged. After one-shot `tgbtn` resolution, a complete `app::method` or `app::method(<strict JSON>)` prompt invokes the installed app before Pi queue admission and sends its planned Markdown/buttons directly; malformed or failed bound actions never fall back to a model prompt. Direct app-output buttons retain hidden source revisions and stale actions fail before method execution; sibling processes serialize transitions and recover dead lock owners. Bound actions send a fresh message by default and retain the clicked button's selected state on its prior surface. A result may opt into `viewMode: "edit"` to replace the callback message and keyboard in place, with one fresh-send fallback only for that explicit action. Agent-mediated initial-surface revisions, process-birth lock proof, automatic refresh, and voice output remain open.
71
+ - `telegram_bind({ app, script, argument? } | { app, method, argument? })` installs and initializes one canonical managed Generative App module under `<agent-dir>/genapps/<app>/<app>.mjs`, or invokes one named method on an installed app. Installation rejects silent replacement and noncanonical/symlink sources. Methods receive immutable JSON state, one optional JSON argument, cancellation, revision, and a bounded non-shell process port; successful state changes commit to `state.json` plus `states.jsonl`, while output-only methods leave history unchanged. After one-shot `tgbtn` resolution, a complete `app::method` or `app::method(<strict JSON>)` prompt invokes the installed app before Pi queue admission and sends its planned Markdown/buttons directly; malformed or failed bound actions never fall back to a model prompt. Direct app-output buttons retain hidden source revisions and stale actions fail before method execution; sibling processes serialize transitions and recover dead lock owners. Bound actions send a fresh message by default and retain the clicked button's selected state on its prior surface. A result may opt into `viewMode: "edit"` to replace the callback message and keyboard in place, with one fresh-send fallback only for that explicit action. Agent-mediated initial-surface revisions, process-birth lock proof, automatic refresh, and voice output are not implemented.
70
72
  - `telegram_attach(paths, chat_id?, thread_id?, caption?)` is the stable artifact delivery tool for generated files. During Telegram turns it queues files before any separate final text; with `assistant.rendering: "rich"`, exactly one PNG/JPEG, MP4, or MP3 artifact plus non-empty final Markdown can become one reply-anchored Rich Message with media first. HTML mode, multiple/unsupported files, Guest Mode, and voice outputs retain their established paths. Outside Telegram turns the tool sends files directly to the paired/default chat, the registered follower's assigned thread, or an explicit `chat_id` plus optional `thread_id` when this Pi instance owns `/telegram-connect` or is registered with the multi-instance bus.
71
73
  - `telegram_channel_post(action, operation_id, markdown?)` edits or deletes one exact `published` record returned by `telegram_channel_posts`. Edit requires Markdown and delete forbids it. A media-post edit replaces the caption through `editMessageCaption`, while a text post uses `editMessageText`; both render Markdown formatting, including spoilers, as Telegram HTML. The direct leader fences the tool call as outcome-unknown before the mutation call, so ambiguous failures are never replayed automatically.
72
74
  - `telegram_channel_posts(chat_id?, limit?)` lists newest bounded records from the active profile's agent-owned post journal. It returns publication, edit/delete outcome-unknown, confirmed, and deleted local records only, including retained media kind/file name/size/SHA-256 identity; it never reads or claims completeness for Telegram channel history. This explicit successful listing is the only tool response that exposes retained authored Markdown; channel tool failures use fixed redacted messages, while pre-issuance media/caption validation errors stay actionable.
@@ -54,6 +54,7 @@ Use emoji as stable semantic markers, not decoration. Emoji carry transportable
54
54
  | `🔁` | Replace/restore mode | Thread replace/restore chooser entrypoints | Opens a second step for moving a Pi instance binding to the current source thread. |
55
55
  | `➡️` | Choose replacement target | Thread replace/restore target buttons that select which Pi instance should move to the current thread | Use inside the second replace/restore chooser, not for ordinary reroutes. |
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 must be stated in the chooser and bound to exact eligible target authority. |
57
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. |
58
59
  | `🗑` | Delete / defer removal | Destructive confirmations and removal reaction | In the queue menu, reversible Keep/Skip selectors replace immediate deletion. |
59
60
 
@@ -70,7 +71,7 @@ Use emoji as stable semantic markers, not decoration. Emoji carry transportable
70
71
 
71
72
  | Emoji | Meaning | Canonical surfaces | Notes |
72
73
  | --- | --- | --- | --- |
73
- | `🟢` | Current/active/enabled `On` | Current option in vertical lists, active state rows, active `On` toggle | One strong current marker per option list. |
74
+ | `🟢` | Current/active/enabled `On` | Current option in vertical lists, active state rows, active `On` toggle | One marker per selected list value; inactive list values stay unmarked. |
74
75
  | `🟡` | Active `Off` or elevated/filter state | Active `Off` toggle, Priority/Scoped active tab | Yellow means intentionally not-normal or off/default-caution, not error. |
75
76
  | `🔴` | Active destructive/deferred disposition | Active queue `Skip` selector | Red distinguishes a prompt that will be discarded at dispatch from reversible neutral or elevated state. |
76
77
  | `🟣` | Normal/default active tab | Normal priority tab, All/default scope tab, active page picker | Use for neutral active tabs. |
@@ -109,6 +110,18 @@ Some emoji are intentionally local examples or decorative variants, not global s
109
110
 
110
111
  Thread UI rule: when a message heading, chooser, or status line is specifically about Telegram/Pi threads or target thread selection, start the heading with `🧵`. Button labels for concrete thread targets should stay clean (`threadName` or slot fallback) and should not add `🧵` to every target button unless the row would otherwise be ambiguous.
111
112
 
113
+ ## Button Control Hierarchy
114
+
115
+ All inline controls are buttons at the transport level. The local UI kit gives them three visual roles:
116
+
117
+ - **Action button:** Performs an action or navigation, using its semantic emoji and Capitalized action text.
118
+ - **List-item button:** Represents a value in a collection. Use lowercase value labels; only selected values carry a state-circle emoji, while unselected values have no emoji. A list may support single or multiple selection.
119
+ - **Radio-style button:** Represents a labelled state choice. Use Capitalized labels and an indicator on every value: a semantic colored circle for active values, `⚫️` for inactive values. A radio group normally selects one value; checkbox-like binary controls and tabs reuse this same visual family rather than introducing separate label grammars.
120
+
121
+ Selection cardinality belongs to the control's domain, not to the visual family. Independent checkbox dimensions or tab groups may each have an active value. Do not confuse list and radio-style controls merely because both can select one item. Canonical names, identifiers and numeric/spatial tokens keep their own spelling; casing rules apply to authored value words.
122
+
123
+ These are button-based visual grammars, not claims that Telegram exposes native list, radio, checkbox or tab widgets. Selection markers project actual state; they never grant callback, mutation or routing authority. The specialized rules below define each reuse.
124
+
112
125
  ## Action Buttons
113
126
 
114
127
  Action buttons perform an operation.
@@ -143,7 +156,7 @@ Examples:
143
156
 
144
157
  ## Boolean Toggles
145
158
 
146
- Boolean settings use a horizontal `On` / `Off` pair.
159
+ Boolean settings are checkbox-like binary controls rendered with the radio-style grammar as a horizontal `On` / `Off` pair.
147
160
 
148
161
  Rules:
149
162
 
@@ -161,7 +174,7 @@ Examples:
161
174
 
162
175
  ## Horizontal Tabs
163
176
 
164
- Tabs or small mutually-exclusive scopes use a horizontal row.
177
+ Tabs or small mutually-exclusive scopes reuse the radio-style grammar in a horizontal row.
165
178
 
166
179
  Rules:
167
180
 
@@ -177,25 +190,25 @@ Examples:
177
190
 
178
191
  - `🟡 Scoped` / `⚫️ All`
179
192
  - `⚫️ Priority` / `🟣 Normal`
180
- - `1` / `🟣 2` / `3`
181
193
 
182
194
  ## Option Lists
183
195
 
184
- Option lists choose one value from a fixed set, for example model selection, thinking level, voice reply mode, or time injection mode.
196
+ Option lists represent values from a collection and may allow single or multiple selection. Current model selection, thinking level, voice reply mode and time injection mode are single-choice examples.
185
197
 
186
198
  Rules:
187
199
 
188
200
  - Put each option on its own row when labels are long, the set may grow, or scanning benefits from full width.
189
201
  - A fixed set of short, ordered peer values may use compact rows of up to three buttons.
190
202
  - Keep a semantically distinct value such as thinking `off` on its own full-width row before grouped intensity values.
191
- - Mark only the current value with `🟢`.
192
- - Leave non-current values without emoji.
193
- - Use lowercase labels when the option is a value.
203
+ - Mark only selected values with the appropriate state circle (`🟢` by default); a multi-select list marks each selected value.
204
+ - Leave unselected values without emoji, never with the radio family's `⚫️` placeholder.
205
+ - Use lowercase authored value labels; preserve canonical names, identifiers and numeric/spatial tokens.
194
206
 
195
207
  Examples:
196
208
 
197
209
  - Vertical: `hidden`, `🟢 mirror`, `always`.
198
210
  - Thinking: full-width `off`, then `minimal` / `low` / `🟢 medium`, then `high` / `xhigh` / `max`.
211
+ - Numeric page picker (list grammar, not radio-style tabs): `1` / `🟣 2` / `3`.
199
212
 
200
213
  ## Generated Prompt Buttons
201
214
 
@@ -211,6 +224,7 @@ Rules:
211
224
  - First-level submenus opened from the main inline menu start with `⬆️ Main menu`.
212
225
  - Deeper submenus start with `⬆️ Back`.
213
226
  - `Main menu` returns to the root inline menu.
227
+ - Choosing a Thinking level refreshes the same chooser and its current marker without leaving the submenu; only the top `Main menu` button returns to the root.
214
228
  - `Back` returns one level up, never directly to the root unless the parent is the root.
215
229
 
216
230
  Examples:
@@ -156,7 +156,7 @@ This means:
156
156
 
157
157
  ## Ownership semantics
158
158
 
159
- The handler registry is ownership-agnostic and does not interact with the extension-local transport owner slots documented in [Architecture](./architecture.md#configuration-and-ownership). When the polling runtime loses its `owners.json` slot and stops `getUpdates`, handlers stop receiving updates because no updates are being fetched; they are not unregistered.
159
+ The handler registry is ownership-agnostic and does not interact with the extension-local transport owner slots documented in [Architecture](./architecture.md#configuration-and-ownership). When the polling runtime loses its transport ownership and stops `getUpdates`, handlers stop receiving updates because no updates are being fetched; they are not unregistered.
160
160
 
161
161
  If a layered extension needs to react to ownership changes, it should observe `pi-telegram` lifecycle events through the standard pi extension hooks rather than through the handler registry.
162
162