@llblab/pi-kit 0.36.3 → 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +2 -2
  3. package/node_modules/@llblab/pi-telegram/AGENTS.md +4 -4
  4. package/node_modules/@llblab/pi-telegram/BACKLOG.md +9 -11
  5. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +19 -0
  6. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +2 -1
  7. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +2 -1
  8. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +5 -6
  9. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +36 -35
  10. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +2 -3
  11. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +20 -18
  12. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +6 -6
  13. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +23 -65
  14. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +3 -3
  15. package/node_modules/@llblab/pi-telegram/dist/lib/prompts.d.ts +1 -1
  16. package/node_modules/@llblab/pi-telegram/dist/lib/prompts.js +1 -1
  17. package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +1 -0
  18. package/node_modules/@llblab/pi-telegram/dist/lib/runtime.js +1 -0
  19. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +4 -2
  20. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +29 -20
  21. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  22. package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/SKILL.md +5 -5
  23. package/node_modules/@llblab/pi-telegram/docs/architecture.md +6 -6
  24. package/node_modules/@llblab/pi-telegram/docs/compact-matrix-literal.md +1 -1
  25. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
  26. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +1 -1
  27. package/node_modules/@llblab/pi-telegram/docs/outbound.md +8 -8
  28. package/node_modules/@llblab/pi-telegram/docs/public-api.md +5 -3
  29. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +4 -0
  30. package/node_modules/@llblab/pi-telegram/lib/commands.ts +53 -43
  31. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1 -3
  32. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +17 -15
  33. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +28 -68
  34. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +3 -3
  35. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +1 -1
  36. package/node_modules/@llblab/pi-telegram/lib/runtime.ts +2 -1
  37. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +31 -18
  38. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  39. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/SKILL.md +5 -5
  40. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to `@llblab/pi-kit` are documented here.
4
4
 
5
+ ## 0.37.1: Bundled Package Updates (2026-10-26)
6
+
7
+ - `Exact pins`: `@llblab/pi-telegram` 0.58.1 → 0.58.3.
8
+ - `Packaging`: Synchronizes the lockfile, bundle aliases and README inventory. Package membership and resource order remain unchanged; packed validation does not certify extension safety or installed-client behavior.
9
+
10
+ ## 0.37.0: Bundled Package Updates (2026-10-25)
11
+
12
+ - `Exact pins`: `@llblab/pi-telegram` 0.57.11 → 0.58.1.
13
+ - `Packaging`: Synchronizes the lockfile, bundle aliases and README inventory. Package membership and resource order remain unchanged; packed validation does not certify extension safety or installed-client behavior.
14
+
5
15
  ## 0.36.3: Bundled Package Updates (2026-10-24)
6
16
 
7
17
  - `Exact pins`: `@llblab/pi-telegram` 0.57.7 → 0.57.11.
package/README.md CHANGED
@@ -18,7 +18,7 @@ Package links lead to the owning repositories for usage, documentation, issues,
18
18
  | [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.13.2` | OpenAI/legacy Codex subscription quota and Business credit status, with persistent priority Fast toggle mirrored in Telegram |
19
19
  | [`@llblab/pi-grow-loop`](https://github.com/llblab/pi-grow-loop) | `0.9.0` | Visible continuation scheduling and bounded worker Skills through compiled, manifest-owned resources |
20
20
  | [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.26.5` | Scoped context/memory compiler with step-independent current memory, stable Passive/Active modes and visible blocked-state status |
21
- | [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.57.11` | Telegram companion with per-prompt Steer, queued controls, Reroute/Restore, recoverable connections, previews, follower Threads, files, and voice |
21
+ | [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.58.3` | Telegram companion with per-prompt Steer, queued controls, Reroute/Restore, recoverable connections, previews, follower Threads, files, and voice |
22
22
  | [`@llblab/skills`](https://github.com/llblab/skills) | `1.15.0` | Portable workflows for engineering, review, design, context maintenance, and other focused tasks |
23
23
 
24
24
  Versions are exact by design. An upstream release does not change an installed kit until this repository explicitly advances the dependency and publishes a new kit version. Runtime defects and package-specific feature requests belong in the linked repository; package selection and kit installation issues belong here.
@@ -27,7 +27,7 @@ Versions are exact by design. An upstream release does not change an installed k
27
27
 
28
28
  Requires **Pi 1.0.0+** and **Node.js 22.19.0+**. State Flow requires its canonical checkpoint/tail storage format and does not convert unsupported stores in place. Preserve existing stores and consult the [owning package's storage guidance](https://github.com/llblab/pi-state-flow/blob/v0.26.5/docs/usage.md#moving-a-store-and-supported-formats) before changing installations.
29
29
 
30
- From npm:
30
+ From NPM:
31
31
 
32
32
  ```bash
33
33
  pi install npm:@llblab/pi-kit
@@ -107,9 +107,9 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
107
107
  - A model selection from an authorized Telegram target may stop and continue any interruptible active agent run in that same Pi session, including local/TUI work. Queue the synthetic control-lane continuation before aborting; when no Telegram prompt owns the run, bind continuation and reply ownership to the exact model-menu chat/Thread/message. Delay abort until every active tool execution settles, and clear both selection and fallback target on cancellation, agent start, settlement, or session replacement.
108
108
  - Telegram extension side effects must not hold Pi's core lifecycle hostage after semantic completion. Preserve ordering in extension-owned background work, record failures, and fence target/profile/transport/session authority.
109
109
  - In Threaded Mode, All is service/input-only. New output, previews, typing and routing UI require a concrete Thread; neither implicit fallback nor explicit aggregate delivery may write to All. Unavailable routing authority retains input without an All notice or silent settlement. Classic DM and explicit channel/business destinations remain separate; historical All output is not auto-cleaned.
110
- - Complete assistant/guest model answers use Telegram-native Rich Markdown. Harness-owned menus, status, diagnostics, thinking, and tool evidence remain explicit HTML/plain or their documented native surface. Before Telegram preview or final delivery, strip every assistant-authored HTML comment regardless of Markdown position while keeping action activation top-level-only; a comment-only result sends no text message. Preserve literal code outside comments and structurally safe chunking; never split invalid markup.
110
+ - Complete assistant/guest model answers use Telegram-native Rich Markdown. Harness-owned menus, status, diagnostics, thinking, and tool evidence remain explicit HTML/plain or their documented native surface. Before Telegram preview or final delivery, strip every assistant-authored HTML comment regardless of Markdown position and recognize complete action comments by their delimiters and payload, independent of surrounding Markdown; a comment-only result sends no text message. Preserve literal code outside comments and structurally safe chunking; never split invalid markup.
111
111
  - `preview` owns streaming lifecycle only, not assistant rendering. Finalization waits for the captured generation's preceding preview work, never later drafts blocked by that publication, and must not issue pre/post-final draft-clear calls that create transient Telegram draft UI. Reset/invalidation detaches predecessor dependencies; late settlements cannot mutate replacement state. Turns that already answer as one atomic reply (voice replies, Guest Mode queries) never stream previews.
112
- - Native `sendChatAction(typing)` is the automatic activity signal for unsettled agent and compaction work while Telegram transport is authorized. Extension-owned blocking UI prompts pause it and completion resumes it while either work owner remains active. Compaction may stop only a typing loop it actually started; it must preserve a pre-existing agent-owned loop. Refresh only the assigned Thread on a conservative three-second cadence; do not mirror typing to aggregate `All` and multiply shared-chat flood pressure. Typing is best-effort presence only, never a Thread-existence or message-delivery probe: a failed exact-target action remains a structured diagnostic and must not replace a healthy connected/leader/follower status with `error`. Do not invent extra in-chat work indicators or emit activity for startup/connect/reload/recovery alone.
112
+ - Native `sendChatAction(typing)` is the automatic activity signal for unsettled agent and compaction work while Telegram transport is authorized. Extension-owned blocking UI prompts pause it and completion resumes it while either work owner remains active. Compaction may stop only a typing loop it actually started; it must preserve a pre-existing agent-owned loop. Refresh only the assigned Thread on a conservative three-second cadence; do not mirror typing to aggregate `All` and multiply shared-chat flood pressure. One Thread's pending action must not suppress another Thread's activity. Typing is best-effort presence only, never a Thread-existence or message-delivery probe: a failed exact-target action remains a structured diagnostic and must not replace a healthy connected/leader/follower status with `error`. Do not invent extra in-chat work indicators or emit activity for startup/connect/reload/recovery alone.
113
113
  - Public activity handlers and connected companion delivery are asynchronous, target-bound, generation-fenced surfaces. Connected companion projection has no independent opt-out: disconnect or authority loss is its boundary. Token deltas, hidden reasoning, unknown sources, and stale authority never enter public projection.
114
114
  - Profiles hold only bot identity and the paired owner; Settings-menu preferences live in shared top-level config. Thread display (`threads.displayMode`) defaults to the Letters strategy, with Names, Directory Snake, and Directory Title as the other automatic choices; Names projects the generated dictionary name for the slot. Unsupported retained display keys resolve to Letters without rewriting persisted configuration. A durable manual Thread display name retained on its Workspace binding overrides any automatic projection until exact reset; keep generated/recovery identity separate from manual and acknowledged display fields. After leader startup, automatic display contraction waits one follower-staleness window so election and follower re-registration cannot briefly remove and restore an acknowledged same-cwd suffix; stop/start generation cancels stale reconciliation. UI labels, emoji semantics, navigation, settings controls, callback namespaces, voice behavior, command templates, and assistant markup follow the linked `/docs` contracts. Generated human-readable action labels use `emoji + space + text`; emoji-free action text is only a reasoned no-semantic-marker fallback. State/value controls follow the [button control hierarchy](./docs/ui-style.md#button-control-hierarchy), not the action-label grammar. Non-spatial generated controls default to top-level vertical cells, with nested rows reserved for unmistakably compact peers. Do not restate other evolving UI details here.
115
115
 
@@ -134,7 +134,7 @@ The detailed map is canonical in [`docs/architecture.md`](./docs/architecture.md
134
134
  - Command templates remain compact and shell-free. Use string leaves or ordered `template` arrays; shell operators are not an execution contract. Examples use portable executable placeholders, never machine-local paths.
135
135
  - `telegram_attach` is the canonical file path and `telegram_message` the direct Markdown text/buttons path. Both require current direct or registered-follower authority and must not replace the normal active-turn reply.
136
136
  - Inbound handlers transform text/media before queueing; outbound handlers precede programmatic/provider fallbacks. Public contracts and ordering live in `docs/inbound.md`, `docs/outbound.md`, and `docs/public-api.md`.
137
- - Pi integration uses public hooks and APIs. Telegram `/new` is scheduled against the exact durable update, dispatched only after that update is removed from the journal, and then routed through the single `/telegram-internal` Pi gateway via `pi.sendUserMessage(..., { expandPromptTemplates: true })`; only a runtime-armed typed action may execute, manual invocation reports that the command cannot be run manually, and the handler receives the real `ExtensionCommandContext` before calling `ctx.newSession()`. Before replacement, CAS-publish one exact expiring handoff in the profile target snapshot. `workspace-thread` successors re-key the matching Workspace binding; `classic-chat` successors preserve Profile/CWD/session/chat continuity without creating a binding or invoking topic APIs. Both atomically claim the intent before one terminal result, and the old `withSession` path never publishes the same success. A registered follower cannot persist leader-owned state: its Workspace Thread intent is published and claimed by the leader over capability-gated exact-generation bus RPC after leader-side binding validation, and records its source runtime instance so only that follower lineage may re-key or claim it. Never replace the session while inbound authority is unsettled, store stale command contexts, accept an expired or mismatched handoff, inject terminal input, spawn a shadow Pi process, or mutate session files.
137
+ - Pi integration uses public hooks and APIs. Telegram `/new` is scheduled against the exact durable update, dispatched only after that update is removed from the journal, and then routed through the `/telegram-connect --internal new <token>` Pi gateway via `pi.sendUserMessage(..., { expandPromptTemplates: true })`; the internal branch never enters profile activation or connection/recovery, only a runtime-armed one-use typed action may execute, manual invocation reports that the action cannot be run manually, and the handler receives the real `ExtensionCommandContext` before calling `ctx.newSession()`. Before replacement, CAS-publish one exact expiring handoff in the profile target snapshot. `workspace-thread` successors re-key the matching Workspace binding; `classic-chat` successors preserve Profile/CWD/session/chat continuity without creating a binding or invoking topic APIs. Both atomically claim the intent before one terminal result, and the old `withSession` path never publishes the same success. A registered follower cannot persist leader-owned state: its Workspace Thread intent is published and claimed by the leader over capability-gated exact-generation bus RPC after leader-side binding validation, and records its source runtime instance so only that follower lineage may re-key or claim it. Never replace the session while inbound authority is unsettled, store stale command contexts, accept an expired or mismatched handoff, inject terminal input, spawn a shadow Pi process, or mutate session files.
138
138
 
139
139
  ## 7. Engineering Conventions
140
140
 
@@ -145,7 +145,7 @@ The detailed map is canonical in [`docs/architecture.md`](./docs/architecture.md
145
145
  - Treat every meaningful `lib/extension.ts` edit as a composition-pressure check, but keep one-off live adapter wiring there when extraction would only hide cross-domain state.
146
146
  - Follow [`docs/ui-style.md`](./docs/ui-style.md) for interface copy, emoji, buttons, menus, and dialogs. Update the registry before assigning a new UI emoji meaning. Standalone notices use one fully bold emoji-led sentence with a terminal period; menu or chooser headings use the same hierarchy with a terminal colon. Material names may add nested italic emphasis without breaking the outer bold span. Callback toasts are fleeting plain text with no emoji and no terminal sentence period (question/exclamation marks and ellipses remain), written in that final form at their call site; no delivery boundary rewrites punctuation, and navigation taps answer silently. In-chat notices and explicit raw API payloads keep their punctuation.
147
147
  - Markdown lists never contain blank lines between adjacent items; list items are not paragraphs. Use blank lines only between paragraphs or independently separated blocks. Markdown tables use compact source formatting with `---` separator cells and one surrounding space per cell. Preserve vendored references unchanged.
148
- - Treat Windows filesystem, named-pipe, lock, heartbeat, and atomic-rename reports as high-signal evidence; reduce them to regressions or explicit platform caveats.
148
+ - Treat Windows filesystem, named-pipe, lock, heartbeat, and atomic-rename reports as high-signal evidence; reduce them to regressions or explicit platform caveats. Process fixtures must distinguish bounded startup from post-readiness natural exit: arm the exit guard only after the declared checkpoint, retain a separate total cap, and ensure extra startup allowance cannot hide retained post-readiness handles.
149
149
  - Route significant runtime failures through the redacted recent-event recorder. Keep the compact TUI status at generic `error`; details belong in diagnostics.
150
150
 
151
151
  ## 8. Work Protocol
@@ -2,21 +2,19 @@
2
2
 
3
3
  _This file owns unresolved project work only. Completed behavior belongs in `CHANGELOG.md`; durable contracts belong in `AGENTS.md` and `/docs`._
4
4
 
5
- ## 0.57.11 publication checkpoint
6
-
7
- - [ ] Validate and publish only the pre-publication follower reply-target fix through the protected dev-to-main release path. Require fresh Ubuntu, Windows and both macOS shard checks before merge, exact candidate/merged-tree equality, one immutable version tag and the repository-owned release workflow.
8
- - [ ] Confirm the published GitHub Release and public npm version, tag/gitHead identity, release notes and packed runtime/skills before closing this checkpoint. Keep post-refusal recovery and disposable-tab live acceptance open; do not reload the running session.
9
-
10
5
  ## Disposable Thread live acceptance
11
6
 
12
7
  - [ ] After separate operator authorization, use disposable Telegram tabs to check leader Settings review/Back, one confirmed cleanup of an inactive slotless binding, preservation of its replacement letter holder, and visible outcome/recovery copy. Native filesystem and injected-transport tests do not prove live Telegram/client or native Windows acceptance. Never use production history as the disposable fixture.
13
8
  - [ ] On separately authorized disposable routing tabs, verify pending-choice protection, Cancel after the view becomes unavailable, expiry notice in the owned leader Thread and no duplicate deletion after an unknown outcome. Local native journal/API-decoder fixtures are not live client acceptance.
14
9
 
15
- ## Prompt-cache live confirmation
10
+ ## Typing authority during Telegram turns
11
+
12
+ Live report on package 0.58.2 and Pi 1.4.0: every Telegram turn logged `sendChatAction: Telegram API call authority is unavailable` in a connected, long-lived session. The reporter's leader/follower role and refusal stage are not yet known. Prompt-cache acceptance is complete; this is a separate typing-indicator investigation.
16
13
 
17
- The prompt-cache report remains open for the reporter's offered live Anthropic capture after the published fix. Native request/transcript checks do not replace cache-hit or signed-thinking measurements.
14
+ The native expired-loop diagnostic case is corrected: real typing loop/starter, direct API runtime and API client regressions cover mocked HTTP 200 after stop/same-target replacement, with unchanged session generation and transport epoch. A process-local ended-loop observation suppresses only the corresponding typed authority diagnostic; the request still rejects, active-owner refusals remain visible, and no callback crosses IPC. Real-client regressions retain the original HTTP/network failure when authority expires during error handling; a native follower caller rejects a delayed ACK after its local owner ends, without replay. Direct/follower and Classic/Threaded paths remain distinct from live acceptance. This does not establish the cause of the reporter's live failure or prove client typing.
18
15
 
19
- - [ ] Review the reporter's resume and mid-session tool-change results (versions, cache read/write counts and new thinking-drop events). Close the report if the live evidence confirms the fix, or narrow any residual failure into actionable follow-up. Do not reconstruct or claim recovery of previously lost thinking blocks.
16
+ - [ ] Correlate the live report with the corrected native case using only redacted leader/follower role, timing around loop end/replacement and refusal-stage evidence if available. Do not infer transport loss or widen authority from the error string alone; isolate any distinct current-owner refusal before another runtime change.
17
+ - [ ] After separately approved live validation, confirm client typing and absence of expired-loop API noise across Telegram turns and loop replacement. Preserve exact Thread targeting, independent Thread requests, best-effort presence and no unhealthy-status escalation; local native fixtures are not live acceptance.
20
18
 
21
19
  ## Stuck forwarded command after Thread re-creation
22
20
 
@@ -38,10 +36,10 @@ A target-scoped reply refusal cannot prove that a command made no prior effects.
38
36
  - [ ] Route an input that arrives in a tab bound to nothing through a fresh temporary routing tab (the All flow) instead of publishing the chooser in the orphan, deleting the orphan with it once nothing unspent remains. Needs its own durable-metadata design.
39
37
  - [ ] Live acceptance on disposable tabs only: Cancel on an orphan tab deletes it; a second unspent input keeps it until the last Cancel; a plain `std::map` option button is delivered as an ordinary prompt.
40
38
 
41
- ## macOS process-exit test flake
39
+ ## Passive-child cold-import follow-up
42
40
 
43
- The 0.57.8 tag validation failed `Child process sharing the agent dir does not poll while parent owns Telegram lock` even when that file ran alone: `Child process did not exit. stdout= stderr=` at its existing child deadline. The other seven process tests and the identical pre-merge tree passed. That historical failure has no child-stage trace, so cold import, startup and a retained runtime handle remain competing explanations. Bounded diagnostic sampling is exhausted; passing traces showed imports up to 4.37 seconds against the five-second non-Windows child budget and natural exit within 8–14 ms after the event-loop checkpoint. These traces do not identify the original cause.
44
- - [ ] Retain bounded child-stage/resource diagnostics for the next genuinely failing passive-child trace; the original timeout is still unexplained. Do not spend more unchanged-head diagnostic samples. The parent records explicit shutdown separately; do not add a shutdown call to the passive child to make it pass. Correct a fixture/runtime cause only when the failing stage evidence supports it, and require fresh platform CI before resuming release recovery. Do not widen deadlines or describe the runner as the proven cause without measurement.
41
+ The 0.57.8 tag validation failed `Child process sharing the agent dir does not poll while parent owns Telegram lock` even when that file ran alone: `Child process did not exit. stdout= stderr=` at its existing child deadline. The other seven process tests and the identical pre-merge tree passed. That historical failure has no child-stage trace, so cold import, startup and a retained runtime handle remain competing explanations. Bounded diagnostic sampling is exhausted; passing traces showed imports up to 4.37 seconds against the five-second non-Windows child budget and natural exit within 8–14 ms after the event-loop checkpoint. These traces do not identify the original cause. A later main-validation recurrence stopped after `eval` and `import-start`, with parent-clock `elapsedMs=5009` and no import-complete checkpoint. That trace locates this recurrence before import completion, not in natural exit or a retained post-checkpoint handle; the slow dependency/startup cause remains unknown. The minor release's independent tag-owned validation passed the same committed tree, which does not erase that historical failed main check. The initial hotfix candidate then showed the same pre-import-completion state on Ubuntu at `elapsedMs=5026`; the recurrence is not macOS-only. Slow dependency/startup cost remains unexplained, separate from the fixture's corrected startup/exit observation contract.
42
+ - [ ] On a genuinely failing cold-import trace under the phase-owned fixture, isolate dependency/startup cost using its actual overall/exit deadline and stage evidence before changing runtime imports or budgets. Preserve natural-exit/no-poll assertions and job budgets. Do not spend unchanged-head diagnostic samples or infer runner contention or the historical cause from passing runs.
45
43
 
46
44
  ## Windows Workspace-gate fixture timeout
47
45
 
@@ -4,6 +4,25 @@
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.58.3: Typing Cancellation Diagnostics
8
+
9
+ - `Typing diagnostics`: Ended typing loops no longer log cancellation-only authority refusals as API failures. Real HTTP/network failures retain their provenance and diagnostics even if authority expires before settlement. Requests remain rejected and single-attempt; callbacks stay local. Native late-success/failure and follower-ACK regressions cover this case, not the cause of the live report.
10
+ - `Prompt-cache acceptance`: Reporter-provided live Anthropic captures and connected-session measurements confirm the released fix across tool changes, resume, local/Telegram switches and reconnect, with no new thinking drops in the tested runs. The remaining live-confirmation gate is closed; connected wire capture and broader model/cache-expiry coverage remain outside this evidence. Already-lost thinking is not reconstructed.
11
+
12
+ ## 0.58.2: Flexible Assistant Action Markup
13
+
14
+ - `HTML actions`: Complete button/voice comments activate by their delimiters and accepted payload regardless of whitespace or surrounding Markdown, including code fences. Adjacent same-line comments are independent; unfinished comments do not activate. Escape the opening delimiter as `&lt;!--` for literal examples.
15
+ - `In-body buttons`: Fenced `telegram_button` blocks tolerate horizontal indentation, whitespace before the exact name, and three or more backticks or tildes. Closures require the same marker and at least the opening length on a separate line. Enclosing fenced examples remain literal; incomplete blocks, malformed JSON/CML and invalid rows still register no callbacks.
16
+
17
+ ## 0.58.1: Independent Thread Typing
18
+
19
+ - `Typing`: A pending activity request in one Thread no longer silently suppresses another Thread's typing. Exact chat/Thread/action calls still coalesce with their own minimum interval, while real Telegram 429 cooldowns remain shared across the chat, including late failures after a faster peer completes. Authority checks, one-attempt typing and bounded gate storage remain intact.
20
+ - `Process validation`: Passive-child checks distinguish cold startup from natural exit: a 15-second total cap retains the existing Windows bound, while a separate five-second guard starts after the event-loop checkpoint on every platform. Regressions reject both a missing checkpoint and retained post-checkpoint work; no polling assertions, natural-exit requirement or job budgets are weakened.
21
+
22
+ ## 0.58.0: Unified Connect Gateway
23
+
24
+ - `Pi commands`: Removes the standalone `/telegram-internal` gateway. Runtime-armed session replacement now dispatches `/telegram-connect --internal new <token>` after exact durable update completion. Internal calls bypass profile activation and connection/recovery; malformed, unarmed and replayed calls fail closed. `internal` remains a valid profile name.
25
+
7
26
  ## 0.57.11: Follower Reply Target
8
27
 
9
28
  - `Follower command replies`: Status/model/settings and text replies from an admitted follower command select its acknowledged Thread before publication, not the old input Thread. A changed Thread drops the reply anchor; delivery, menu state and ownership use one destination. The journaled original and explicit selected-menu grants stay intact; no refused-send recovery, handler replay or proxy widening is added.
@@ -201,6 +201,7 @@ export declare function createTelegramLiveTargetWorkObserver<TContext>(deps: {
201
201
  }, ctx: TContext) => TelegramBusLiveRebindWorkState;
202
202
  interface TelegramCommandsAndToolsBindingDeps {
203
203
  pi: Pi.ExtensionAPI;
204
+ handleInternalAction: Commands.TelegramSessionActionRuntime["handleInternalAction"];
204
205
  agentDir: string;
205
206
  configStore: Config.TelegramConfigStore;
206
207
  persistConfig: (config?: Config.TelegramConfig) => Promise<void>;
@@ -241,7 +242,7 @@ interface TelegramCommandsAndToolsBindingDeps {
241
242
  connectionIntent: NonNullable<Commands.TelegramBridgeCommandRegistrationDeps["connectionIntent"]>;
242
243
  recordRuntimeEvent: TelegramRuntimeEventRecorder;
243
244
  }
244
- export declare function registerTelegramCommandsAndTools({ pi, agentDir, configStore, persistConfig, setup, activeTurnRuntime, lockedPollingRuntime, stopPolling, getDisconnectThreadName, onTransportChanged, getStatusLines, buttonActionStore, sendMarkdownReply, sendChannelMarkdownMessage, sendChannelMediaMessage, listChannelPosts, mutateChannelPost, callMultipart, getDefaultChatId, getDefaultTarget, resolveAgentTarget, routeAgentMessage, canSendDirect, setGenerativeAppLiveSurfaceRuntime, recordRuntimeEvent, updateStatus, isContextCurrent, getSessionGeneration, connectionIntent, }: TelegramCommandsAndToolsBindingDeps): void;
245
+ export declare function registerTelegramCommandsAndTools({ pi, handleInternalAction, agentDir, configStore, persistConfig, setup, activeTurnRuntime, lockedPollingRuntime, stopPolling, getDisconnectThreadName, onTransportChanged, getStatusLines, buttonActionStore, sendMarkdownReply, sendChannelMarkdownMessage, sendChannelMediaMessage, listChannelPosts, mutateChannelPost, callMultipart, getDefaultChatId, getDefaultTarget, resolveAgentTarget, routeAgentMessage, canSendDirect, setGenerativeAppLiveSurfaceRuntime, recordRuntimeEvent, updateStatus, isContextCurrent, getSessionGeneration, connectionIntent, }: TelegramCommandsAndToolsBindingDeps): void;
245
246
  interface TelegramLifecycleBindingDeps {
246
247
  pi: Pi.ExtensionAPI;
247
248
  publicationRuntime: TelegramBridgePublicationRuntime;
@@ -497,7 +497,7 @@ export function createTelegramLiveTargetWorkObserver(deps) {
497
497
  };
498
498
  };
499
499
  }
500
- export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, persistConfig, setup, activeTurnRuntime, lockedPollingRuntime, stopPolling, getDisconnectThreadName, onTransportChanged, getStatusLines, buttonActionStore, sendMarkdownReply, sendChannelMarkdownMessage, sendChannelMediaMessage, listChannelPosts, mutateChannelPost, callMultipart, getDefaultChatId, getDefaultTarget, resolveAgentTarget, routeAgentMessage, canSendDirect, setGenerativeAppLiveSurfaceRuntime, recordRuntimeEvent, updateStatus, isContextCurrent, getSessionGeneration, connectionIntent, }) {
500
+ export function registerTelegramCommandsAndTools({ pi, handleInternalAction, agentDir, configStore, persistConfig, setup, activeTurnRuntime, lockedPollingRuntime, stopPolling, getDisconnectThreadName, onTransportChanged, getStatusLines, buttonActionStore, sendMarkdownReply, sendChannelMarkdownMessage, sendChannelMediaMessage, listChannelPosts, mutateChannelPost, callMultipart, getDefaultChatId, getDefaultTarget, resolveAgentTarget, routeAgentMessage, canSendDirect, setGenerativeAppLiveSurfaceRuntime, recordRuntimeEvent, updateStatus, isContextCurrent, getSessionGeneration, connectionIntent, }) {
501
501
  GenerativeApps.registerTelegramBindTool(pi, {
502
502
  agentDir,
503
503
  getActiveProfileName: configStore.getActiveProfileName,
@@ -551,6 +551,7 @@ export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, pe
551
551
  }, { deliverAs: "nextTurn" });
552
552
  };
553
553
  Commands.registerTelegramBridgeCommands(pi, {
554
+ handleInternalAction,
554
555
  promptForConfig: async (ctx, profileName) => {
555
556
  const nextProfileName = profileName ?? undefined;
556
557
  if (profileName && !Config.isValidTelegramProfileName(profileName)) {
@@ -166,6 +166,7 @@ export interface TelegramBridgeCommandStartPollingResult {
166
166
  owner?: string;
167
167
  }
168
168
  export interface TelegramBridgeCommandRegistrationDeps {
169
+ handleInternalAction?: TelegramSessionActionRuntime["handleInternalAction"];
169
170
  promptForConfig: (ctx: ExtensionCommandContext, profileName?: string) => Promise<void>;
170
171
  getStatusLines: (options?: TelegramBridgeStatusLineOptions) => string[];
171
172
  reloadConfig: () => Promise<void>;
@@ -661,11 +662,10 @@ export declare function createTelegramCommandHandler<TMessage extends TelegramCo
661
662
  prepareSelectedMenuCommand(command: ParsedTelegramCommand, messages: readonly TMessage[], ctx: TContext, admission: TelegramSelectedCommandAdmission): (() => Promise<boolean>) | undefined;
662
663
  };
663
664
  export declare function createTelegramCommandOrPromptDispatcher<TMessage, TContext>(deps: TelegramCommandOrPromptDispatcherDeps<TMessage, TContext>): (messages: TMessage[], ctx: TContext) => Promise<void>;
664
- export declare const TELEGRAM_INTERNAL_COMMAND_NAME = "telegram-internal";
665
- export declare const TELEGRAM_INTERNAL_COMMAND_DESCRIPTION = "Internal Telegram command cannot be run manually";
666
- export declare const TELEGRAM_INTERNAL_MANUAL_USE_MESSAGE = "This internal Telegram command cannot be run manually.";
665
+ export declare const TELEGRAM_CONNECT_COMMAND_NAME = "telegram-connect";
666
+ export declare const TELEGRAM_INTERNAL_COMMAND_FLAG = "--internal";
667
+ export declare const TELEGRAM_INTERNAL_MANUAL_USE_MESSAGE = "This internal Telegram action cannot be run manually.";
667
668
  export interface TelegramSessionActionRuntimeDeps {
668
- registerCommand: Pi.ExtensionAPI["registerCommand"];
669
669
  sendUserMessage: Pi.ExtensionAPI["sendUserMessage"];
670
670
  notifyResult: (target: {
671
671
  chatId: number;
@@ -697,7 +697,6 @@ export interface TelegramSessionReplacementSettlementDeps {
697
697
  }
698
698
  export declare function settleTelegramSessionReplacement(deps: TelegramSessionReplacementSettlementDeps): Promise<"none" | "settled" | "expired" | "failed" | "stale">;
699
699
  export interface TelegramSessionActionAssemblyDeps {
700
- registerCommand: Pi.ExtensionAPI["registerCommand"];
701
700
  sendUserMessage: Pi.ExtensionAPI["sendUserMessage"];
702
701
  store: {
703
702
  load: () => Promise<void>;
@@ -753,7 +752,7 @@ export declare function createTelegramSessionActionAssembly(deps: TelegramSessio
753
752
  };
754
753
  };
755
754
  export interface TelegramSessionActionRuntime {
756
- register: () => void;
755
+ handleInternalAction: (args: string, ctx: Pi.ExtensionCommandContext) => Promise<void>;
757
756
  scheduleAfterUpdate: (updateId: number, target: {
758
757
  chatId: number;
759
758
  threadId?: number;
@@ -405,6 +405,7 @@ function formatTelegramTakeoverPrompt(ctx, owner) {
405
405
  return `${action}\n\n${from} ${source}\n${to} ${ctx.cwd}`;
406
406
  }
407
407
  export function registerTelegramBridgeCommands(pi, deps) {
408
+ const handleInternalAction = deps.handleInternalAction;
408
409
  pi.registerCommand("telegram-setup", {
409
410
  description: "<profile> — Configure Telegram bot token",
410
411
  handler: async (args, ctx) => {
@@ -418,9 +419,19 @@ export function registerTelegramBridgeCommands(pi, deps) {
418
419
  ctx.ui.notify(deps.getStatusLines({ verbose }).join("\n"), "info");
419
420
  },
420
421
  });
421
- pi.registerCommand("telegram-connect", {
422
+ pi.registerCommand(TELEGRAM_CONNECT_COMMAND_NAME, {
422
423
  description: "<profile> — Start Telegram bridge",
423
424
  handler: async (args, ctx) => {
425
+ const words = args.trim().split(/\s+/);
426
+ // Service dispatch never inherits profile activation or explicit-connect recovery authority.
427
+ if (words.some(word => word.toLowerCase().startsWith(TELEGRAM_INTERNAL_COMMAND_FLAG))) {
428
+ if (words[0] !== TELEGRAM_INTERNAL_COMMAND_FLAG || !handleInternalAction) {
429
+ ctx.ui.notify(TELEGRAM_INTERNAL_MANUAL_USE_MESSAGE, "warning");
430
+ return;
431
+ }
432
+ await handleInternalAction(words.slice(1).join(" "), ctx);
433
+ return;
434
+ }
424
435
  const sessionGeneration = deps.getSessionGeneration?.();
425
436
  let intentId;
426
437
  // Pi context getters throw after replacement; check plain intent/generation first.
@@ -2415,9 +2426,9 @@ async function handleTelegramCommandRuntime(commandName, message, ctx, deps, com
2415
2426
  },
2416
2427
  }, commandArgs);
2417
2428
  }
2418
- export const TELEGRAM_INTERNAL_COMMAND_NAME = "telegram-internal";
2419
- export const TELEGRAM_INTERNAL_COMMAND_DESCRIPTION = "Internal Telegram command cannot be run manually";
2420
- export const TELEGRAM_INTERNAL_MANUAL_USE_MESSAGE = "This internal Telegram command cannot be run manually.";
2429
+ export const TELEGRAM_CONNECT_COMMAND_NAME = "telegram-connect";
2430
+ export const TELEGRAM_INTERNAL_COMMAND_FLAG = "--internal";
2431
+ export const TELEGRAM_INTERNAL_MANUAL_USE_MESSAGE = "This internal Telegram action cannot be run manually.";
2421
2432
  function delayTelegramSessionAction(delayMs) {
2422
2433
  return new Promise((resolve) => setTimeout(resolve, delayMs));
2423
2434
  }
@@ -2487,7 +2498,6 @@ export function createTelegramSessionActionAssembly(deps) {
2487
2498
  report(new Error("Telegram new-session result delivery failed."));
2488
2499
  };
2489
2500
  const action = createTelegramSessionActionRuntime({
2490
- registerCommand: deps.registerCommand,
2491
2501
  sendUserMessage: deps.sendUserMessage,
2492
2502
  notifyResult(target, result) {
2493
2503
  return sendTerminalResult(target, result);
@@ -2585,7 +2595,6 @@ export function createTelegramSessionActionRuntime(deps) {
2585
2595
  let pendingUpdateId;
2586
2596
  let pendingTarget;
2587
2597
  let pendingAction;
2588
- let registered = false;
2589
2598
  const reportFailure = (error) => {
2590
2599
  try {
2591
2600
  deps.recordRuntimeEvent?.("new-session", error);
@@ -2595,35 +2604,29 @@ export function createTelegramSessionActionRuntime(deps) {
2595
2604
  }
2596
2605
  };
2597
2606
  return {
2598
- register() {
2599
- if (registered)
2607
+ async handleInternalAction(args, ctx) {
2608
+ const action = pendingAction;
2609
+ const [operation, token, ...extra] = args.trim().split(/\s+/);
2610
+ if (!action || operation !== "new" ||
2611
+ token !== action.token || extra.length !== 0) {
2612
+ ctx.ui.notify(TELEGRAM_INTERNAL_MANUAL_USE_MESSAGE, "warning");
2600
2613
  return;
2601
- registered = true;
2602
- deps.registerCommand(TELEGRAM_INTERNAL_COMMAND_NAME, {
2603
- description: TELEGRAM_INTERNAL_COMMAND_DESCRIPTION,
2604
- handler: async (args, ctx) => {
2605
- const action = pendingAction;
2606
- if (!action || args.trim() !== action.token) {
2607
- ctx.ui.notify(TELEGRAM_INTERNAL_MANUAL_USE_MESSAGE, "warning");
2608
- return;
2614
+ }
2615
+ pendingAction = undefined;
2616
+ switch (action.kind) {
2617
+ case "replace-session":
2618
+ try {
2619
+ await deps.prepareReplacement?.(ctx, action.updateId, action.target);
2620
+ const result = await ctx.newSession();
2621
+ if (result.cancelled)
2622
+ await deps.notifyResult(action.target, "cancelled");
2609
2623
  }
2610
- pendingAction = undefined;
2611
- switch (action.kind) {
2612
- case "replace-session":
2613
- try {
2614
- await deps.prepareReplacement?.(ctx, action.updateId, action.target);
2615
- const result = await ctx.newSession();
2616
- if (result.cancelled)
2617
- await deps.notifyResult(action.target, "cancelled");
2618
- }
2619
- catch (error) {
2620
- reportFailure(error);
2621
- await deps.notifyResult(action.target, "failure");
2622
- }
2623
- return;
2624
+ catch (error) {
2625
+ reportFailure(error);
2626
+ await deps.notifyResult(action.target, "failure");
2624
2627
  }
2625
- },
2626
- });
2628
+ return;
2629
+ }
2627
2630
  },
2628
2631
  scheduleAfterUpdate(updateId, target) {
2629
2632
  if (pendingUpdateId !== undefined || pendingAction !== undefined)
@@ -2643,9 +2646,7 @@ export function createTelegramSessionActionRuntime(deps) {
2643
2646
  const token = randomUUID();
2644
2647
  pendingAction = { kind: "replace-session", token, updateId, target };
2645
2648
  void Promise.resolve()
2646
- .then(() => deps.sendUserMessage(`/${TELEGRAM_INTERNAL_COMMAND_NAME} ${token}`, {
2647
- expandPromptTemplates: true,
2648
- }))
2649
+ .then(() => deps.sendUserMessage(`/${TELEGRAM_CONNECT_COMMAND_NAME} ${TELEGRAM_INTERNAL_COMMAND_FLAG} new ${token}`, { expandPromptTemplates: true }))
2649
2650
  .catch((error) => {
2650
2651
  pendingAction = undefined;
2651
2652
  reportFailure(error);
@@ -80,7 +80,7 @@ const telegramBusProtocolIdentity = Bus.createTelegramCurrentBusProtocolIdentity
80
80
  export default function (pi) {
81
81
  Skills.registerTelegramSkillDiscovery(pi);
82
82
  const piRuntime = Pi.createExtensionApiRuntimePorts(pi);
83
- const { getActiveTools, getCommands, getThinkingLevel, sendUserMessage, registerCommand, setActiveTools, setModel, setThinkingLevel, } = piRuntime;
83
+ const { getActiveTools, getCommands, getThinkingLevel, sendUserMessage, setActiveTools, setModel, setThinkingLevel, } = piRuntime;
84
84
  const bridgeRuntime = Runtime.createTelegramBridgeRuntime();
85
85
  const runtimeDiagnostics = Logging.createTelegramRuntimeDiagnosticsRuntime({
86
86
  sharedFile: true,
@@ -242,7 +242,6 @@ export default function (pi) {
242
242
  captureAuthority: captureTelegramStateAuthority,
243
243
  });
244
244
  const sessionActionAssembly = Commands.createTelegramSessionActionAssembly({
245
- registerCommand,
246
245
  sendUserMessage,
247
246
  store: threadStore,
248
247
  getProfileName: configStore.getActiveProfileName,
@@ -2293,9 +2292,9 @@ export default function (pi) {
2293
2292
  }
2294
2293
  },
2295
2294
  }.reset);
2296
- sessionActionsRuntime.register();
2297
2295
  Bindings.registerTelegramCommandsAndTools({
2298
2296
  pi,
2297
+ handleInternalAction: sessionActionsRuntime.handleInternalAction,
2299
2298
  agentDir: Paths.resolveAgentDir(),
2300
2299
  configStore,
2301
2300
  persistConfig: persistTelegramConfigWithSync,
@@ -4,7 +4,7 @@
4
4
  * Owns assistant-authored telegram_button extraction, button action storage, callback handling, and prompt-turn construction
5
5
  */
6
6
  import { randomUUID } from "node:crypto";
7
- import { getTelegramActionString as getTelegramButtonString, parseTelegramActionPayloadRows, parseTelegramButtonPayloadRows, replaceTelegramButtonFences, replaceTopLevelHtmlComments, } from "./outbound-markup.js";
7
+ import { getTelegramActionString as getTelegramButtonString, parseTelegramActionPayloadRows, parseTelegramButtonPayloadRows, replaceTelegramButtonFences, replaceTelegramHtmlComments, } from "./outbound-markup.js";
8
8
  import { truncateTelegramQueueSummary, } from "./queue.js";
9
9
  const TELEGRAM_BUTTON_CALLBACK_PREFIX = "tgbtn";
10
10
  const TELEGRAM_BUTTON_ACTION_TTL_MS = 24 * 60 * 60 * 1000;
@@ -117,7 +117,23 @@ export function planTelegramButtonReply(markdown, deps) {
117
117
  }),
118
118
  }));
119
119
  };
120
- const withRichButtons = replaceTelegramButtonFences(markdown, (payload, closed) => {
120
+ const footerKeyboard = [];
121
+ const stripped = replaceTelegramHtmlComments(markdown, (comment) => {
122
+ const command = "telegram_button";
123
+ const normalizedContent = comment.content
124
+ .replace(/^\s+/, "")
125
+ .replace(/^!/, "");
126
+ if (!normalizedContent.startsWith(command))
127
+ return comment.raw;
128
+ const payloadRows = parseTelegramActionPayloadRows(comment, command);
129
+ if (!payloadRows)
130
+ return "";
131
+ const rows = buildRows(payloadRows, false);
132
+ if (rows)
133
+ footerKeyboard.push(...rows);
134
+ return "";
135
+ });
136
+ const withRichButtons = replaceTelegramButtonFences(stripped, (payload, closed) => {
121
137
  if (!closed)
122
138
  return "";
123
139
  const payloadRows = parseTelegramButtonPayloadRows(payload);
@@ -133,22 +149,8 @@ export function planTelegramButtonReply(markdown, deps) {
133
149
  }
134
150
  return `\n${rows.map(renderTelegramRichButtonRow).join("\n\n")}\n`;
135
151
  });
136
- const stripped = replaceTopLevelHtmlComments(withRichButtons, (comment) => {
137
- const command = "telegram_button";
138
- const normalizedContent = comment.content
139
- .replace(/^\s+/, "")
140
- .replace(/^!/, "");
141
- if (!normalizedContent.startsWith(command))
142
- return comment.raw;
143
- const payloadRows = parseTelegramActionPayloadRows(comment, command);
144
- if (!payloadRows)
145
- return "";
146
- const rows = buildRows(payloadRows, false);
147
- if (rows)
148
- keyboard.push(...rows);
149
- return "";
150
- });
151
- const visibleMarkdown = normalizeMarkdownAfterButtonExtraction(stripped);
152
+ keyboard.push(...footerKeyboard);
153
+ const visibleMarkdown = normalizeMarkdownAfterButtonExtraction(withRichButtons);
152
154
  return {
153
155
  markdown: keyboard.length > 0 && !visibleMarkdown
154
156
  ? DEFAULT_TELEGRAM_BUTTON_REPLY_MARKDOWN
@@ -1,22 +1,22 @@
1
1
  /**
2
2
  * Telegram outbound markup parsing helpers
3
3
  * Zones: telegram outbound, assistant markup
4
- * Owns top-level assistant action comment extraction, attribute parsing, and markup stripping shared by voice and outbound delivery
4
+ * Owns delimiter-based assistant action comment extraction, attribute parsing, and markup stripping shared by voice and outbound delivery
5
5
  */
6
- export interface TelegramTopLevelHtmlComment {
6
+ export interface TelegramHtmlComment {
7
7
  raw: string;
8
8
  content: string;
9
9
  start: number;
10
10
  end: number;
11
11
  }
12
- export declare function collectTopLevelHtmlComments(markdown: string): {
13
- comments: TelegramTopLevelHtmlComment[];
12
+ export declare function collectTelegramHtmlComments(markdown: string): {
13
+ comments: TelegramHtmlComment[];
14
14
  openCommentStart?: number;
15
15
  };
16
16
  export declare function replaceTelegramButtonFences(markdown: string, replace: (payload: string, closed: boolean) => string): string;
17
- export declare function replaceTopLevelHtmlComments(markdown: string, replacer: (comment: TelegramTopLevelHtmlComment) => string): string;
17
+ export declare function replaceTelegramHtmlComments(markdown: string, replacer: (comment: TelegramHtmlComment) => string): string;
18
18
  export declare function parseTelegramButtonPayloadRows(source: string): Record<string, unknown>[][] | undefined;
19
- export declare function parseTelegramActionPayloadRows(comment: TelegramTopLevelHtmlComment, command: string): Record<string, unknown>[][] | undefined;
19
+ export declare function parseTelegramActionPayloadRows(comment: TelegramHtmlComment, command: string): Record<string, unknown>[][] | undefined;
20
20
  export declare function stripTelegramCommentMarkupForPreview(markdown: string): string;
21
21
  export declare function stripTelegramCommentMarkupForDelivery(markdown: string): string;
22
22
  export interface TelegramVoiceReplyItem {