@llblab/pi-kit 0.36.1 → 0.36.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +2 -2
- package/node_modules/@llblab/pi-claude-usage/lib/status-format.ts +2 -3
- package/node_modules/@llblab/pi-claude-usage/package.json +1 -1
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +1 -1
- package/node_modules/@llblab/pi-codex-usage/lib/status-format.ts +2 -3
- package/node_modules/@llblab/pi-codex-usage/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +2 -2
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +43 -5
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +45 -0
- package/node_modules/@llblab/pi-telegram/README.md +4 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +17 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +23 -11
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +24 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +7 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +7 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +22 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +52 -10
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.js +9 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +3 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +4 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/prompts.d.ts +6 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/prompts.js +50 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +3 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +120 -14
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +13 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +39 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +144 -16
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +15 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +14 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +122 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +9 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +4 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +52 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.d.ts +17 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +25 -0
- package/node_modules/@llblab/pi-telegram/dist/package.json +2 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +10 -10
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +23 -3
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +2 -0
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +24 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +30 -13
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +25 -8
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +11 -0
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +34 -8
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +54 -11
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +12 -0
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +2 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +4 -3
- package/node_modules/@llblab/pi-telegram/lib/menu.ts +4 -4
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +2 -0
- package/node_modules/@llblab/pi-telegram/lib/prompts.ts +79 -16
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +142 -26
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +18 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +173 -17
- package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +14 -8
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +166 -10
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +9 -6
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +8 -7
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +56 -2
- package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +44 -0
- package/node_modules/@llblab/pi-telegram/package.json +2 -1
- package/node_modules/@llblab/pi-telegram/scripts/run-macos-tests.mjs +43 -0
- package/package.json +4 -4
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.36.3: Bundled Package Updates (2026-10-24)
|
|
6
|
+
|
|
7
|
+
- `Exact pins`: `@llblab/pi-telegram` 0.57.7 → 0.57.11.
|
|
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.36.2: Bundled Package Updates (2026-10-23)
|
|
11
|
+
|
|
12
|
+
- `Exact pins`: `@llblab/pi-claude-usage` 0.2.2 → 0.2.3; `@llblab/pi-codex-usage` 0.13.1 → 0.13.2; `@llblab/pi-telegram` 0.57.3 → 0.57.7.
|
|
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.1: Bundled Package Updates (2026-10-22)
|
|
6
16
|
|
|
7
17
|
- `Exact pins`: `@llblab/pi-state-flow` 0.26.4 → 0.26.5.
|
package/README.md
CHANGED
|
@@ -13,12 +13,12 @@ Package links lead to the owning repositories for usage, documentation, issues,
|
|
|
13
13
|
| Package | Version | Purpose |
|
|
14
14
|
| --- | ---: | --- |
|
|
15
15
|
| [`@llblab/pi-actors`](https://github.com/llblab/pi-actors) | `0.55.0` | Inspectable local Runs, reusable Recipes, persistent tools, and delegation Skills |
|
|
16
|
-
| [`@llblab/pi-claude-usage`](https://github.com/llblab/pi-claude-usage) | `0.2.
|
|
16
|
+
| [`@llblab/pi-claude-usage`](https://github.com/llblab/pi-claude-usage) | `0.2.3` | Claude subscription quota status and shared per-model Fast toggle for Opus, mirrored in Telegram |
|
|
17
17
|
| [`@llblab/pi-clean-room`](https://github.com/llblab/pi-clean-room) | `0.3.0` | Isolated nested Pi TUI with named npm extensions and compatible model selection |
|
|
18
|
-
| [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.13.
|
|
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.
|
|
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 |
|
|
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.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.3: Empty Quota Bar Hotfix
|
|
4
|
+
|
|
5
|
+
- Aligned quota bars with rounded remaining percentages: when either window rounds to `0%`, its final segment disappears and the whole dual bar uses the error background, while the other half retains its quota. The 5-hour reset countdown follows the same zero boundary; near-full quota still rounds to the nearest 5% step.
|
|
6
|
+
|
|
3
7
|
## 0.2.2: Windows CI Validation
|
|
4
8
|
|
|
5
9
|
- Added Linux and Windows GitHub Actions validation that runs the full release check suite (typecheck, tests, extension import, dependency audit and package dry-run) on every push to `main` and pull request, so the shared-refresh SQLite mutex, atomic state writes and path handling are exercised on Windows. The extension runtime is unchanged from 0.2.1.
|
|
@@ -90,7 +90,7 @@ Pi 1.0.0 accepts the extra override but does not propagate `speed` to native req
|
|
|
90
90
|
claude ██████▀▀▀▀ 6d
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
The ten-character bar encodes two twenty-step limits at once: the top quadrants show the remaining 5-hour limit and the bottom quadrants show the remaining weekly limit.
|
|
93
|
+
The ten-character bar encodes two twenty-step limits at once: the top quadrants show the remaining 5-hour limit and the bottom quadrants show the remaining weekly limit. When either window's remaining percentage rounds to `0%`, that half becomes completely empty and the entire dual bar switches to the error background color; the other half still shows its remaining quota.
|
|
94
94
|
|
|
95
95
|
When the weekly reset time is available, it follows the bar. More than a day remains is shown in 144-minute day-tenth steps such as `7d`, `6.9d`, `1.1d`, rounded upward. At 24 hours and below it switches to upward-rounded hour-tenths such as `24h`, `23.7h`, `1.1h`, `1h`. Under an hour it shows floored minutes, then seconds. After the reset passes, `0s` is held until the next successful refresh.
|
|
96
96
|
|
|
@@ -100,7 +100,7 @@ When the 5-hour window is exhausted and exposes its reset time, the statusline a
|
|
|
100
100
|
claude ▄▄▄▄▄⠀⠀⠀⠀⠀ 5h/7d
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
If only one window is returned, the
|
|
103
|
+
If only one window is returned, the remaining percentage rounded to the nearest integer is shown:
|
|
104
104
|
|
|
105
105
|
```text
|
|
106
106
|
claude 67% 7d
|
|
@@ -262,9 +262,8 @@ function filledParts(
|
|
|
262
262
|
window: NormalizedRateLimitWindow | undefined,
|
|
263
263
|
totalParts: number,
|
|
264
264
|
): number {
|
|
265
|
-
if (!window) return 0;
|
|
265
|
+
if (!window || isQuotaWindowExhausted(window)) return 0;
|
|
266
266
|
const remaining = remainingPercent(window);
|
|
267
|
-
if (remaining <= 0) return 0;
|
|
268
267
|
return Math.max(1, Math.round(remaining / (100 / totalParts)));
|
|
269
268
|
}
|
|
270
269
|
|
|
@@ -275,7 +274,7 @@ function remainingPercent(window: NormalizedRateLimitWindow): number {
|
|
|
275
274
|
function isQuotaWindowExhausted(
|
|
276
275
|
window: NormalizedRateLimitWindow | undefined,
|
|
277
276
|
): boolean {
|
|
278
|
-
return window !== undefined && remainingPercent(window)
|
|
277
|
+
return window !== undefined && Math.round(remainingPercent(window)) === 0;
|
|
279
278
|
}
|
|
280
279
|
|
|
281
280
|
function hasExhaustedQuotaWindow(report: ClaudeUsageReport): boolean {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.2: Empty Quota Bar Hotfix
|
|
4
|
+
|
|
5
|
+
- Aligned quota bars with rounded remaining percentages: when either window rounds to `0%`, its final segment disappears and the whole dual bar uses the error background, while the other half retains its quota. The 5-hour reset countdown follows the same zero boundary; near-full quota still rounds to the nearest 5% step.
|
|
6
|
+
- Release verification now tolerates up to approximately 30 minutes of npm registry processing before checking the published package identity and creating the GitHub Release.
|
|
7
|
+
|
|
3
8
|
## 0.13.1: Windows Codex CLI Fallback
|
|
4
9
|
|
|
5
10
|
- The Codex CLI fallback now starts on Windows. When npm installs Codex behind a `codex.cmd` shim that a shell-free `spawn` cannot launch, the extension resolves the native `codex.exe` from PATH or from the npm platform package and starts it directly, so fallback quota stays available when the primary endpoint fails. Non-Windows platforms keep plain PATH lookup, and the helper window is hidden.
|
|
@@ -103,7 +103,7 @@ When OpenAI returns only the weekly window, the status shows the exact rounded r
|
|
|
103
103
|
codex 67% 7d
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
When both windows exist, the ten-character bar encodes two twenty-step limits at once: the top quadrants show the 5-hour limit and the bottom quadrants show the weekly limit, with each step representing 5%.
|
|
106
|
+
When both windows exist, the ten-character bar encodes two twenty-step limits at once: the top quadrants show the 5-hour limit and the bottom quadrants show the weekly limit, with each step representing 5%. When either window's remaining percentage rounds to `0%`, that half becomes completely empty and the entire dual bar switches to the error background color; the other half still shows its remaining quota.
|
|
107
107
|
|
|
108
108
|
Before the first usable Codex report arrives, two half-height markers move through the same fixed-width themed bar. The upper 5-hour marker travels opposite the lower weekly marker; both reverse smoothly at the ends, and each loader run randomly starts from one of the two mirrored endpoint phases. Their motion distinguishes loading from 100% remaining quota while preserving the normal bar background. A first report claiming both windows are completely unused is treated as provisional for 15 seconds and retried every second by the refreshing instance before it is published, because providers can briefly emit zeroed windows while initializing. Once a usable report exists, refresh requests preserve that last good bar.
|
|
109
109
|
|
|
@@ -312,9 +312,8 @@ function filledParts(
|
|
|
312
312
|
window: NormalizedRateLimitWindow | undefined,
|
|
313
313
|
totalParts: number,
|
|
314
314
|
): number {
|
|
315
|
-
if (!window) return 0;
|
|
315
|
+
if (!window || isQuotaWindowExhausted(window)) return 0;
|
|
316
316
|
const remaining = remainingPercent(window);
|
|
317
|
-
if (remaining <= 0) return 0;
|
|
318
317
|
return Math.max(1, Math.round(remaining / (100 / totalParts)));
|
|
319
318
|
}
|
|
320
319
|
|
|
@@ -325,7 +324,7 @@ function remainingPercent(window: NormalizedRateLimitWindow): number {
|
|
|
325
324
|
function isQuotaWindowExhausted(
|
|
326
325
|
window: NormalizedRateLimitWindow | undefined,
|
|
327
326
|
): boolean {
|
|
328
|
-
return window !== undefined && remainingPercent(window)
|
|
327
|
+
return window !== undefined && Math.round(remainingPercent(window)) === 0;
|
|
329
328
|
}
|
|
330
329
|
|
|
331
330
|
function hasExhaustedQuotaWindow(
|
|
@@ -80,7 +80,7 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
80
80
|
- Session identity, not Pi context object identity, decides authority. Pi builds a fresh context object for every event, command, and tool call, so compare sessions through their stored generation and ID, never by object, and treat a fresh object of the same session as the same authority. How an instance came online — explicit `/telegram-connect`, session auto-restore, election promotion, or a same-process handoff — may decide only that it is online and which existing binding, slot, and target it takes; every post-online behavior, including election eligibility, promotion, routing, delivery, status, and Thread display, must read the same acknowledged registration state and must not branch on that mechanism. Only the documented connect-time gates (remembered-binding restore admission, restore/recovery envelope flags, capability negotiation, and predecessor handoff) may observe it. An in-flight registration survives a fresh same-session context and is dropped only by a replaced session generation or ID.
|
|
81
81
|
- Protocol compatibility is independent from package version. Registration negotiates protocol version, runtime build, and canonical capabilities before target provisioning or live publication. `durable-follower-admission-v1` gates source forwarding; `queue-handoff-v1` independently gates semantic queue transfer for every participant and is advertised only with exact source/recipient journal-binding composition. `follower.register`, capability-gated restore-only `follower.restoreWorkspace` and side-effect-free liveness `bus.probe` are bootstrap requests; other requests require exact live-registry generation authority, and `bus.ack` is response-only. `thread-display-mode-v1` gates follower display-setting requests; the leader serializes the shared preference write and owns title application.
|
|
82
82
|
- Long-lived timers, pollers, watchers, receivers, heartbeats, background delivery, and deferred dispatch are session-bound. Shutdown cancels/drains diagnostics publications and fences deferred status reads before they can acquire guards or repair files. Replacement stops stale activity and makes late work inert; teardown must recheck the captured session generation even when context identity is reused. Same-process handoff may preserve exact profile/target identity but never stale Pi context or cross-profile authority. Follower heartbeat and leader-to-follower Restore/live-rebind control reply deadlines align with the eight-second leader stale-liveness window rather than the generic one-second local-RPC default; an ordinary multi-second Pi/TUI event-loop stall or a slower Windows named pipe is not registration or recipient loss. Participating source observations must hold a reference for the actual read, including pending-mutation/count queries against a stopped worker's retained source; a scoped observation never restores receipt readiness or execution authority. A donor cancellation resumed after remote handoff awaits must also hold a source reference; only the existing exact journal CAS may cancel, never undo accepted recipient custody. Stable source keys do not certify captured callable lifetimes: snapshot prepared worker capabilities at construction and replace them on source-handle renewal without replaying unsettled input. Aborting a durable update generation does not release that `update_id`: replacement replay waits for its actual handler settlement, and effectful handlers use the shared execution fence immediately before commit and after awaited delegation. Admission also rechecks that fence after the default handler returns, before its outcome can settle custody; a stopped handler's ordinary return is not completion authority. Internal clones explicitly carry the hidden fence; reroute forwarding, thread-store mutation, cleanup, and Bot API boundaries retain the originating generation.
|
|
83
|
-
- Repeated identical diagnostics in one phase collapse into one run record plus one outcome record; the logging domain owns that recording policy, and other domains receive it as a structural port rather than growing a second owner. Runtime state is event-driven reconciliation of local assumptions against Telegram signals, not a complete bot read-model and not permission to query Telegram on every action.
|
|
83
|
+
- Repeated identical diagnostics in one phase collapse into one run record plus one outcome record; the logging domain owns that recording policy, and other domains receive it as a structural port rather than growing a second owner. Runtime state is event-driven reconciliation of local assumptions against Telegram signals, not a complete bot read-model and not permission to query Telegram on every action. Lifecycle Thread cleanup goes through `thread-reconciler` with current proof and leader fencing. Pressure retirement and explicit manual inactive-Workspace cleanup instead require their exact named Workspace-admission fence and one-shot deletion permit; neither may call ordinary unfenced deletion transport. Fresh Workspace Thread creation derives its initial Bot API title from the active display mode before issuance; the stable generated `threadName` remains separate from the acknowledged `displayTitle`.
|
|
84
84
|
|
|
85
85
|
### 4.3 Durable Admission And Settlement
|
|
86
86
|
|
|
@@ -88,7 +88,7 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
88
88
|
- Storage cutovers must reconcile actual consumer locations before correcting path adapters. Never normalize a relative historical reference into new authority or treat equal reference strings / empty canonical storage as source completeness. Exact-path preflight is lexical only; physical identity, historical coverage, writer closure and migration remain separate proofs.
|
|
89
89
|
- Workspace mutations acquire cross-process admission before their shared process-local gate and hold it through asynchronous API work and durable settlement. Topic lifecycle, complete unbound/reroute target handling, manual disconnect, and session-restart cleanup use profile-wide scope; either retained retirement-fence phase rejects them before state access. Cleanup admission spans intent publication, target mutation, persistence, and successful transport release. Explicit manual disconnect may stop only its captured local transport after cleanup admission/effects fail; it must not bypass Thread mutation fences, release a replacement connection, claim unconfirmed deletion, or retry an uncertain stop. Session-restart cleanup retains its admission-required behavior. Detached reconciliation that mutates Thread state must reacquire fresh profile admission through the same gate; it cannot inherit a caller lease that ended before its timer runs. A live operation ID has one process-local caller: concurrent reuse is rejected before lease acquisition, while retry after the caller exits may resume exact durable authority.
|
|
90
90
|
- One Thread keeps one owner across session replacement. A session replaced inside the same process reuses its preserved handoff Thread instead of provisioning another. A new session in a folder whose previous session is provably gone (no live registration for its target, absent runtime PID) adopts that binding's exact target and slot, and the predecessor binding is re-keyed to the successor rather than left as an orphan active row. A live predecessor, an ambiguous candidate set, an unknown runtime, or a missing liveness probe keeps ordinary provisioning.
|
|
91
|
-
- Workspace retirement is capacity-pressure-only. Elapsed time and heartbeat silence never trigger deletion; only complete `A`–`Z` exhaustion may propose the oldest continuously proven inactive, fully unprotected binding. Exact deletion, durable retirement, and fence completion precede slot reuse. Authorized demand-driven rotation retries one failed fresh allocation after retirement; restore-only follower startup never evicts. Release ordinary registration/provisioning leases before acquiring the destructive fence, retain the shared mutation gate across retirement, and validate the exact permit immediately before one non-retried deletion. Persist an exact method/target-matched rejection before withdrawing its intent; release that fence only after durable withdrawal, retaining the binding. A later attempt requires fresh operation authority. Protection reads must never repair, quarantine, or reset journals. Non-destructive owner detachment must atomically retain one exact Workspace binding and its letter while removing only its uniquely matched owner record and stamping first inactivity; it never manufactures Thread-deletion evidence or clears accepted work. During authenticated follower registration, a mismatched retained target record is repaired to the exact Workspace claim before binding commit; if same-target commit adopts the retained binding's slot, its returned binding then owns the registration result and owner-record slot. Unrelated bindings that own a stale or provisional letter remain untouched. Same-target re-keying retains historical journal keys and exact source tuples without upgrading unknown completeness. Retained prune observations are bounded, non-routing and registration/profile/epoch/runtime-fenced. One unfinished preservation operation retains its admission identity across fresh-PID-proof retries; it can never become deletion authority. Leader quit requires completed delivery/polling/worker teardown under the captured session generation, profile and epoch; reload/new/resume/fork never establish inactivity. Unknown deletion outcomes retain their fence; only confirmed durable completion permits reuse.
|
|
91
|
+
- Workspace retirement is capacity-pressure-only. Elapsed time and heartbeat silence never trigger deletion; only complete `A`–`Z` exhaustion may propose the oldest continuously proven inactive, fully unprotected binding. Exact deletion, durable retirement, and fence completion precede slot reuse. Authorized demand-driven rotation retries one failed fresh allocation after retirement; restore-only follower startup never evicts. Release ordinary registration/provisioning leases before acquiring the destructive fence, retain the shared mutation gate across retirement, and validate the exact permit immediately before one non-retried deletion. Persist an exact method/target-matched rejection before withdrawing its intent; release that fence only after durable withdrawal, retaining the binding. A later attempt requires fresh operation authority. Protection reads must never repair, quarantine, or reset journals. Non-destructive owner detachment must atomically retain one exact Workspace binding and its letter while removing only its uniquely matched owner record and stamping first inactivity; it never manufactures Thread-deletion evidence or clears accepted work. During authenticated follower registration, a mismatched retained target record is repaired to the exact Workspace claim before binding commit; if same-target commit adopts the retained binding's slot, its returned binding then owns the registration result and owner-record slot. Unrelated bindings that own a stale or provisional letter remain untouched. Same-target re-keying retains historical journal keys and exact source tuples without upgrading unknown completeness. Retained prune observations are bounded, non-routing and registration/profile/epoch/runtime-fenced. One unfinished preservation operation retains its admission identity across fresh-PID-proof retries; it can never become deletion authority. Leader quit requires completed delivery/polling/worker teardown under the captured session generation, profile and epoch; reload/new/resume/fork never establish inactivity. Unknown deletion outcomes retain their fence; only confirmed durable completion permits reuse. When retirement finds no eligible binding, fresh allocation falls back to metadata-only reuse of the least recently used letter (detached by inactivity start, live owners last, then letter): strip `slot` from that binding and its current Thread record in one durable publication, then retry the claim. This deletes nothing, leaves Threads, journals, accepted work and routing identity untouched, skips letters held by claims, reservations, provisions, cleanups, retirements or external admission, and never runs for restore-only or background recovery registrations. A slotless binding holds no letter and is not a retirement candidate.
|
|
92
92
|
- Foreign forwarding settles as `accepted`, `retryable`, or `terminal-rejected`. Only an authenticated acknowledgement carrying the expected `deliveryId` and `sourceUpdateId` releases leader journal authority. Negative, missing, stale, mismatched, or capacity-failed settlement remains durable; callback error answers are side effects only. Exception: the operator-approved fixed chooser deadline may discard its exact unclaimed donor pending source without a body archive or delivery-success claim. Accepted recipient queue/work and bound Threads are untouched; old donor controls and late reports cannot replay it. The lifetime and temporary cleanup contract lives in `docs/architecture.md`.
|
|
93
93
|
- A forwarding delivery id is stable across registration replacement and derives from envelope kind, source `update_id`, and stable recipient binding. Runtime instance and registration generation remain separate attempt fences. Persisted message ownership carries the stable binding so replay can rebind only to its current authenticated registration.
|
|
94
94
|
- A queued receipt persists its acquiring runtime instance, OS pid/process-birth identity, session generation, acquisition id, and acquisition time. Only exact authority may settle or discard it. Cached presence is not current execution proof: prepared custody must revalidate the exact queued owner/group without recovery and refuse offers or uncertain reads. Completion requires an exact removal acknowledgement, never merely `!ready`; a retained acknowledgement permits local cleanup only, not replay. Same-process session replacement may reconstruct the claim and the original process may settle after transport ownership moves; a foreign process may neither replay nor settle it through generic removal or a copied acquisition id.
|
|
@@ -2,10 +2,48 @@
|
|
|
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
|
-
##
|
|
5
|
+
## 0.57.11 publication checkpoint
|
|
6
6
|
|
|
7
|
-
|
|
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.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- [ ]
|
|
10
|
+
## Disposable Thread live acceptance
|
|
11
|
+
|
|
12
|
+
- [ ] 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
|
+
- [ ] 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
|
+
|
|
15
|
+
## Prompt-cache live confirmation
|
|
16
|
+
|
|
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.
|
|
18
|
+
|
|
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.
|
|
20
|
+
|
|
21
|
+
## Stuck forwarded command after Thread re-creation
|
|
22
|
+
|
|
23
|
+
Evidence (2026-10-23 07:11–07:16): a `/start` sent in a former leader tab moved into a follower's journal as a raw original. After that follower's Thread was re-created by the restore probe, every execution attempt failed with `Telegram bus API call is not allowed for this follower` and retried every 60 s indefinitely, because the reply targeted a Thread the follower no longer owned. Terminal settlement alone is unsafe: the same rejection can be a benign race during a live-rebind pre-dispatch.
|
|
24
|
+
|
|
25
|
+
### Post-refusal reply-only recovery — deferred contract gate
|
|
26
|
+
|
|
27
|
+
Remaining scope is recovery after a reply is refused or recipient authority changes during publication, not initial destination selection. The latter is specified under the [reply ownership boundary](./docs/multi-instance-bus.md#reply-recovery-ownership-boundary); its native regression covers a stale source address, not the historical refusal itself or live Telegram restoration.
|
|
28
|
+
|
|
29
|
+
A target-scoped reply refusal cannot prove that a command made no prior effects. Keep the handler-replay containment while preparing a replacement: unresolved inputs must remain in ordinary durable retry state, not be silently completed or retargeted as whole commands. Ordinary journal execution remains at-least-once, not globally exactly-once.
|
|
30
|
+
|
|
31
|
+
- [ ] Obtain explicit approval for a post-refusal recipient-delivery contract, then implement recipient-scoped reply-only recovery under the [reply ownership boundary](./docs/multi-instance-bus.md#reply-recovery-ownership-boundary), outside the containment hotfix. No ordinary recipient-scoped send grant is composed today: the local execution fence and queue-owner identity are insufficient, and receipt/admission namespaces cannot substitute for the registered recipient profile key. Ordinary sends return only a message ID, so the menu/delivery owner must participate in any destination change. Reuse selected-menu delivery only with its exact retained live-rebind/source grant; a raw journaled command cannot borrow it. Capture and recheck recipient/profile/session/registration authority at both ends, with no widening of the ordinary proxy.
|
|
32
|
+
- [ ] Prove the reply-only path with the real follower API caller and worker: one replacement send before any reply chunk is acknowledged, consistent callback/message ownership and destination across every chunk, no stale reply anchor, no recovery for deletion/editing/unknown or lost ACKs, and no second send after authority changes. A command effect before a refused reply must execute once within that admission attempt; original journal evidence stays unchanged and diagnostics cannot change a successful outcome. Run typecheck and the full suite at the stable boundary. If these proofs cannot be supplied, retain the input without handler re-entry.
|
|
33
|
+
- [ ] Live acceptance after the reply-only recovery checkpoint: a forwarded command whose follower Thread was re-created is answered in the current Thread (disposable tabs only).
|
|
34
|
+
|
|
35
|
+
### Remaining routing investigation
|
|
36
|
+
|
|
37
|
+
- [ ] Find why the temporary tab's selected `/start` stayed unresolved until expiry, and why that Restore moved custody without applying the rebind.
|
|
38
|
+
- [ ] 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
|
+
- [ ] 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
|
+
|
|
41
|
+
## macOS process-exit test flake
|
|
42
|
+
|
|
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.
|
|
45
|
+
|
|
46
|
+
## Windows Workspace-gate fixture timeout
|
|
47
|
+
|
|
48
|
+
`Leader assembly serializes follower provisioning through its shared Workspace gate` once exceeded its generic one-second bus-response deadline. It sends `follower.register` directly, outside the changed API caller; production registration has a separate budget. The later native standalone witness acknowledged at about 90 ms, and the full suite passed, but the original failure has no gate/persistence trace. A synthetic delayed-write probe verifies diagnostic capture, not the native cause.
|
|
49
|
+
- [ ] Use the next genuinely failing native trace to distinguish gate wait, persistence and acknowledgement delay before correcting this fixture or runtime. Retain its bounded stage evidence and standalone/full-suite execution; do not spend unchanged-head reruns or infer a cause from passing checks. Any deadline-policy correction must be justified against the registration owner's contract, not a guessed larger timeout.
|
|
@@ -4,6 +4,51 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.57.11: Follower Reply Target
|
|
8
|
+
|
|
9
|
+
- `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.
|
|
10
|
+
|
|
11
|
+
## 0.57.10: Follower Request Identity
|
|
12
|
+
|
|
13
|
+
- `Follower request identity`: Follower API arguments use one JSON-wire snapshot before registration waits. Later caller mutation no longer changes the issued method/body or relabels a refused target and slow-call method. Ordinary and multipart calls retain wire JSON semantics and authority fences; refusal evidence still grants no resend or whole-command replay.
|
|
14
|
+
- `Windows CI diagnostics`: The Workspace-gate registration fixture records bounded gate and persistence stage timings, including them in failure context without paths or credentials. It also runs in the standalone Windows lifecycle probe before the full concurrent suite. Existing assertions, response deadline and job budgets are unchanged; instrumentation does not claim to solve the observed timeout.
|
|
15
|
+
|
|
16
|
+
## 0.57.9: Reply Safety and CI Recovery
|
|
17
|
+
|
|
18
|
+
- `Recipient safety`: Ships the containment prepared for unpublished 0.57.8: a refused follower reply no longer immediately reruns its whole command handler and repeats earlier effects. The original input stays unchanged in durable retry state, without silent completion. Automatic reply relocation remains withdrawn; stale-Thread commands may still retry and ordinary execution remains at-least-once.
|
|
19
|
+
- `Follower refusals`: Structured bus acknowledgements identify requests blocked before Telegram API delegation, without granting resend authority. Legacy error text, mismatched methods and lost acknowledgements cannot prove non-execution. Reply chunks keep their captured destination and ownership; refused chunks acquire no message ownership.
|
|
20
|
+
- `CI sharding`: macOS distributes its serialized test files across two deterministic jobs to avoid the measured whole-job overrun. A checked inventory includes every file exactly once, with real-process tests isolated and the local Pi CLI on PATH. Assertions, test deadlines and the 30-minute job budget are unchanged; publication requires every platform and shard to pass.
|
|
21
|
+
- `Process diagnostics`: Child-exit checks report bounded lifecycle timings and active resource types without logging fixture credentials or paths or adding referenced timers. Import, startup and natural-exit stages can now distinguish future failures. The original macOS child timeout remains unexplained; passing traces do not establish its cause.
|
|
22
|
+
|
|
23
|
+
## 0.57.8: Reply Recovery Safety
|
|
24
|
+
|
|
25
|
+
- `Recipient safety`: A refused follower reply no longer immediately re-runs the whole command handler, which could repeat changes made before the failed send. The original input stays unchanged in durable retry state, without silent completion. Automatic reply relocation is withdrawn pending transport-level safety proof; a stale-Thread command may still retry and ordinary execution remains at-least-once.
|
|
26
|
+
- `Follower refusals`: Structured bus acknowledgements distinguish requests blocked before Telegram API delegation from errors returned after execution. Legacy error text, mismatched methods and lost acknowledgements cannot certify non-execution or authorize a resend. Reply chunks retain their captured destination and ownership; refused chunks acquire no message ownership.
|
|
27
|
+
- `CI`: macOS runs real-process shutdown tests separately and the remaining test files one at a time, keeping the local Pi CLI on PATH. Assertions and deadlines are unchanged. This reduces observed runner-contention failures without claiming the process-exit flake is eliminated.
|
|
28
|
+
|
|
29
|
+
## 0.57.7: Routing Cleanup and Recipient Recovery
|
|
30
|
+
|
|
31
|
+
- `Recipient recovery`: A forwarded input journaled for a follower whose Thread was re-created no longer retries forever against the old Thread. After the leader refuses that reply, the input re-runs once against the follower's current Thread; a second refusal or any other failure keeps the durable retry state, and nothing is dropped.
|
|
32
|
+
- `Transit cleanup`: Cancel remains journal-first when its Telegram view is unavailable. A parsed exact-target deletion/absence response now finishes temporary-tab cleanup instead of retaining a stuck issued entry. Closed, denied, foreign, malformed and forged responses stay protected without retry. Native regressions also prove waiting-tab protection across cold reads and unavailable state, and refusal to cancel custody already handed to a queue.
|
|
33
|
+
- `Routing expiry`: Expired inputs now receive an explicit undelivered/unconfirmed notice even when the chooser message is gone. Cold expiry uses the journal's saved chooser; a disappearing or unavailable view falls back only to the leader's current owned Thread, never All. Selected/issued work is not falsely called undelivered, accepted recipient work continues, and stale authority or failed notices cannot replay input or authorize deletion.
|
|
34
|
+
- `Manual cleanup`: Leader Settings can review and separately confirm deletion of inactive Telegram tabs and their messages, including bindings without a letter. Pi history/project files and replacement letter holders stay untouched. Exact review, live-work protection, profile/session/leader fences and one-shot permits are rechecked; changed/protected tabs are skipped, unknown deletion never repeats and interrupted commits remain recoverable. Review and Back delete nothing.
|
|
35
|
+
- `Thread titles`: Reassigning a letter immediately attempts to retitle the displaced Thread with its retained name, preserving manual titles and all history/work. Slotless bindings participate in later display reconciliation without acquiring routing ownership. Failed or unacknowledged edits keep prior title evidence, record a diagnostic and never undo reuse or delete the Thread.
|
|
36
|
+
|
|
37
|
+
## 0.57.6: Stability Hotfix
|
|
38
|
+
|
|
39
|
+
- `Plain buttons`: A button without a Generative App binding stays an ordinary prompt unless it is a well-formed `app::method` for an installed app. Prompts containing `::` that fail that test (`std::map`, `cargo test a::b(c)`) no longer raise `Malformed Generative App bound action` or retry forever from the journal; bound buttons keep their fail-closed errors.
|
|
40
|
+
- `Unbound Cancel`: Cancelling the last unspent input in a tab that is bound to nothing now deletes that tab, like a routing tab, once no other chooser, queue, live owner, Restore record or retained journal arrival still names it. Missing proof or any failure keeps the tab with a diagnostic; the private original stays retained.
|
|
41
|
+
|
|
42
|
+
## 0.57.5: Workspace Slot Reuse
|
|
43
|
+
|
|
44
|
+
- `Slot reuse`: A full A–Z pool no longer refuses a new connection when every Thread is active or protected. After pressure retirement finds no candidate, the least recently used letter is reassigned as metadata: detached bindings first, live owners last. The previous holder keeps its Thread, history, journals and accepted work and only loses its letter label. Restore-only startup and background recovery never reassign a letter.
|
|
45
|
+
|
|
46
|
+
## 0.57.4: Prompt Cache Stability
|
|
47
|
+
|
|
48
|
+
- `Prompt composition`: Telegram guidance uses native Pi sections instead of forcing the request head, preserving its prefix across tool/context updates, connection changes and resume. Stable connection guidance is separate from hidden Telegram-turn messages; local/custom starts do not add new turn notes, while earlier notes remain in their original history. Refs #353.
|
|
49
|
+
- `Prompt compatibility`: An unchanged fallback produces no override. Hosts without structured sections, explicit custom hooks and an earlier extension-owned opaque prompt retain equality-guarded append/strip behavior for strings or ordered blocks without losing foreign instructions. Another extension's forced prompt remains outside the cache-stability guarantee.
|
|
50
|
+
- `CI`: macOS validation uses the supported macOS 26 Intel image to avoid hosted ARM runner-capacity failures, and runs its test files two at a time so wall-clock latency tests stop failing under contention. Full checks remain enabled on macOS, Linux and Windows, with unchanged test deadlines and assertions.
|
|
51
|
+
|
|
7
52
|
## 0.57.3: Session Thread Continuity
|
|
8
53
|
|
|
9
54
|
- `Session handoff`: A session replaced inside one Pi process now reuses its preserved Thread on every registration path, not only connect recovery. The successor re-keys onto the same tab and slot with the new session id, so `/new` no longer provisions a second tab and leaves the previous one without an owner.
|
|
@@ -257,10 +257,12 @@ Classic private DM mode is the base product mode. When Telegram private-chat Thr
|
|
|
257
257
|
- Each connected instance gets a Telegram thread target.
|
|
258
258
|
- Queued work for a live follower transfers through authenticated exact-journal handoff rather than replaying under the transport owner.
|
|
259
259
|
- Reopening, resuming, or replacing the process for the same Pi session restores its remembered Thread; distinct sessions in one directory keep independent bindings and letter slots.
|
|
260
|
-
- Unknown threads are preserved and offered explicit reroute/restore choices.
|
|
261
|
-
- When all `A`–`Z` slots are reserved, a new connection may reclaim the oldest inactive, unprotected Thread. **This deletes that Thread and all its messages**, not Pi session history or project files.
|
|
260
|
+
- Unknown threads are preserved and offered explicit reroute/restore choices. Retained temporary routing tabs stay protected while choices are pending. Cancel settles exact journal custody even if the Telegram view is unavailable; an unknown tab-deletion outcome never causes another deletion.
|
|
261
|
+
- When all `A`–`Z` slots are reserved, a new connection may reclaim the oldest inactive, unprotected Thread. **This deletes that Thread and all its messages**, not Pi session history or project files. When every slot is active or protected, nothing is deleted: the new connection takes the least recently used letter instead, and that Thread keeps its history and any accepted work. The leader immediately attempts to replace its old letter title with its retained name (manual titles stay); a failed edit is recorded for later display reconciliation, without undoing reuse or deleting anything. Restoring a remembered session never evicts another binding.
|
|
262
262
|
- Telegram never launches hidden Pi processes.
|
|
263
263
|
|
|
264
|
+
To remove retained inactive tabs, open Settings → **🧹 Thread cleanup** → **Review inactive tabs** on the leader instance. Review and Back delete nothing. The separate **Clean inactive tabs** action deletes eligible reviewed Telegram tabs **and all their messages**, not Pi session history or project files. Tabs without a letter are included when inactivity and clear protection are proven; live owners, accepted work and uncertain evidence remain protected. An unknown deletion outcome is retained without automatic retry.
|
|
265
|
+
|
|
264
266
|
In Settings → **🧵 Thread display**, choose Letters (default), Names, Directory Title or Directory Snake. The choice is shared across all profiles through `threads.displayMode`; profiles contain only bot identity and the paired owner. Use `/name` for a custom Thread name and **Reset to automatic** to restore the selected naming style. Renaming preserves the Thread and its queued work. See [Thread Display](./docs/architecture.md#threaded-mode-multi-instance-bus) for naming details.
|
|
265
267
|
|
|
266
268
|
| Mode | Best for | Runtime shape |
|
|
@@ -239,7 +239,23 @@ export function createTelegramGenerativeAppBoundButtonActionInvoker(deps) {
|
|
|
239
239
|
let boundAction;
|
|
240
240
|
let handedOff;
|
|
241
241
|
try {
|
|
242
|
-
|
|
242
|
+
// Only buttons emitted by a Generative App carry a binding. An unbound button is ordinary model text
|
|
243
|
+
// (`std::map`, `a::b(c)`): malformed or uninstalled-app prompts fall through to the normal prompt plane.
|
|
244
|
+
if (!action.binding) {
|
|
245
|
+
try {
|
|
246
|
+
boundAction = GenerativeApps.parseGenerativeAppBoundAction(action.prompt);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
if (boundAction &&
|
|
252
|
+
!(await GenerativeApps.isGenerativeAppInstalled(deps.agentDir, boundAction.app))) {
|
|
253
|
+
boundAction = undefined;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
boundAction = GenerativeApps.parseGenerativeAppBoundAction(action.prompt);
|
|
258
|
+
}
|
|
243
259
|
if (!boundAction)
|
|
244
260
|
return false;
|
|
245
261
|
deps.assertExecutionCurrent(query);
|
|
@@ -9,13 +9,13 @@ import { createHash } from "node:crypto";
|
|
|
9
9
|
import { basename } from "node:path";
|
|
10
10
|
import { isDeepStrictEqual } from "node:util";
|
|
11
11
|
import { getTelegramBusTransportRetryPolicy, TELEGRAM_BUS_REGISTRATION_RETRY, } from "./bus-transport.js";
|
|
12
|
-
import { createTelegramBusFollowerDeliveryIdentity, createTelegramBusForeignOwnedUpdateForwarder, createTelegramBusLocalServer, createTelegramBusRequestIdFactory, createUnauthorizedBusAck, rejectTelegramBusRequest, getTelegramBusProtocolCompatibility, getTelegramBusSocketPath, hasTelegramBusCapability, hasTelegramBusSteerQueueHandoffCapability, isTelegramBusEnvelopeAuthorized, parseTelegramBusEnvelope, resolveTelegramBusSocketPath, sendTelegramBusLocalEnvelope, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE, TELEGRAM_BUS_CAPABILITY_PRE_DISPATCH_REBIND_EXECUTION, TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY, TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE, TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME, TELEGRAM_BUS_HELD_COMMAND_CAPABILITIES, getTelegramBusHeldCommandCapabilities, TELEGRAM_BUS_LIVE_REBIND_CAPABILITIES, TelegramBusLocalAuthorityError, } from "./bus.js";
|
|
12
|
+
import { createTelegramBusFollowerDeliveryIdentity, createTelegramBusForeignOwnedUpdateForwarder, createTelegramBusLocalServer, createTelegramBusRequestIdFactory, createUnauthorizedBusAck, rejectTelegramBusRequest, getTelegramBusProtocolCompatibility, getTelegramBusSocketPath, hasTelegramBusCapability, hasTelegramBusSteerQueueHandoffCapability, isTelegramBusEnvelopeAuthorized, parseTelegramBusEnvelope, resolveTelegramBusSocketPath, sendTelegramBusLocalEnvelope, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_APPLY, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SAVE, TELEGRAM_BUS_CAPABILITY_LIVE_REBIND_SETTLE, TELEGRAM_BUS_CAPABILITY_PRE_DISPATCH_REBIND_EXECUTION, TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY, TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE, TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME, TELEGRAM_BUS_HELD_COMMAND_CAPABILITIES, getTelegramBusHeldCommandCapabilities, TELEGRAM_BUS_LIVE_REBIND_CAPABILITIES, TelegramBusLocalAuthorityError, TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE, } from "./bus.js";
|
|
13
13
|
import { parseTelegramUpdateJournalQueueOwner, } from "./journal.js";
|
|
14
14
|
import { TELEGRAM_BUS_LEADER_STALE_HEARTBEAT_MS, } from "./locks.js";
|
|
15
15
|
import * as Machine from "./machine.js";
|
|
16
16
|
import { isPiStaleContextError } from "./pi.js";
|
|
17
17
|
import { parseTelegramTarget as parseTarget, } from "./target.js";
|
|
18
|
-
import { isTelegramApiMethodRetrySafe, assertTelegramApiCallAuthority, TelegramApiAuthorityError, TelegramApiCommitUnknownError, TelegramApiStaleTargetError, } from "./telegram-api.js";
|
|
18
|
+
import { isTelegramApiMethodRetrySafe, assertTelegramApiCallAuthority, TelegramApiAuthorityError, TelegramApiCommitUnknownError, TelegramApiFollowerNotAllowedError, TelegramApiStaleTargetError, } from "./telegram-api.js";
|
|
19
19
|
import * as Threads from "./threads.js";
|
|
20
20
|
import { isWireRecord as isRecord } from "./wire.js";
|
|
21
21
|
import { createTelegramWorkspaceAdmissionOperationId, runWithTelegramWorkspaceAdmissionsAsync, } from "./workspace-admission.js";
|
|
@@ -955,6 +955,11 @@ export function createTelegramBusFollowerApiCaller(deps) {
|
|
|
955
955
|
const assertion = localOptions?.assertAuthority;
|
|
956
956
|
const assertCurrent = () => assertTelegramApiCallAuthority(assertion, false);
|
|
957
957
|
assertCurrent();
|
|
958
|
+
// Match IPC's JSON semantics once, before awaits; send and refusal metadata share this identity.
|
|
959
|
+
const requestArgs = JSON.parse(JSON.stringify(args));
|
|
960
|
+
const apiMethod = (method === "call" || method === "callMultipart") && typeof requestArgs[0] === "string"
|
|
961
|
+
? requestArgs[0] : method;
|
|
962
|
+
timing.apiMethod = apiMethod;
|
|
958
963
|
const registration = await resolveTelegramBusFollowerRegistration(deps, timeoutMs);
|
|
959
964
|
timing.registeredAtMs = getNowMs();
|
|
960
965
|
assertCurrent();
|
|
@@ -968,17 +973,13 @@ export function createTelegramBusFollowerApiCaller(deps) {
|
|
|
968
973
|
instanceId: deps.instanceId,
|
|
969
974
|
registrationGeneration: registration.generation,
|
|
970
975
|
method,
|
|
971
|
-
args,
|
|
976
|
+
args: requestArgs,
|
|
972
977
|
sentAtMs: getNowMs(),
|
|
973
978
|
}, assertion ? assertCurrent : undefined);
|
|
974
979
|
}
|
|
975
980
|
catch (error) {
|
|
976
981
|
if (error instanceof TelegramBusLocalAuthorityError)
|
|
977
982
|
throw new TelegramApiAuthorityError(error.requestIssued);
|
|
978
|
-
const apiMethod = (method === "call" || method === "callMultipart") &&
|
|
979
|
-
typeof args[0] === "string"
|
|
980
|
-
? args[0]
|
|
981
|
-
: method;
|
|
982
983
|
if (!isTelegramApiMethodRetrySafe(apiMethod)) {
|
|
983
984
|
throw new TelegramApiCommitUnknownError(apiMethod, error);
|
|
984
985
|
}
|
|
@@ -1002,6 +1003,20 @@ export function createTelegramBusFollowerApiCaller(deps) {
|
|
|
1002
1003
|
response.error?.code === "commit-unknown") {
|
|
1003
1004
|
throw new TelegramApiCommitUnknownError(response.error.method ?? method, new Error(message ?? "Telegram bus API call result is ambiguous."));
|
|
1004
1005
|
}
|
|
1006
|
+
const refusedMethod = response?.kind === "bus.ack" && !response.ok &&
|
|
1007
|
+
response.error?.code === "follower-api-not-allowed" &&
|
|
1008
|
+
response.error.method === apiMethod
|
|
1009
|
+
? apiMethod : undefined;
|
|
1010
|
+
if (refusedMethod || message === TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE) {
|
|
1011
|
+
const body = (method === "call" || method === "callMultipart") && isRecord(requestArgs[1])
|
|
1012
|
+
? requestArgs[1]
|
|
1013
|
+
: undefined;
|
|
1014
|
+
const chatId = Number(body?.chat_id);
|
|
1015
|
+
const threadId = Number(body?.message_thread_id);
|
|
1016
|
+
throw new TelegramApiFollowerNotAllowedError(message ?? TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE, Number.isSafeInteger(chatId) && Number.isSafeInteger(threadId)
|
|
1017
|
+
? { chatId, threadId }
|
|
1018
|
+
: undefined, refusedMethod);
|
|
1019
|
+
}
|
|
1005
1020
|
throw new Error(message ?? "Telegram bus API call failed.");
|
|
1006
1021
|
};
|
|
1007
1022
|
return async (method, args, localOptions) => {
|
|
@@ -1018,10 +1033,7 @@ export function createTelegramBusFollowerApiCaller(deps) {
|
|
|
1018
1033
|
finally {
|
|
1019
1034
|
const elapsedMs = getNowMs() - startedAtMs;
|
|
1020
1035
|
if (elapsedMs >= slowCallMs) {
|
|
1021
|
-
const apiMethod =
|
|
1022
|
-
typeof args[0] === "string"
|
|
1023
|
-
? args[0]
|
|
1024
|
-
: method;
|
|
1036
|
+
const apiMethod = timing.apiMethod ?? method;
|
|
1025
1037
|
try {
|
|
1026
1038
|
deps.recordRuntimeEvent?.("bus", `Telegram bus follower API call was slow (${elapsedMs}ms).`, {
|
|
1027
1039
|
phase: "follower-api-slow",
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { isDeepStrictEqual } from "node:util";
|
|
8
8
|
import { getTelegramBusTransportRetryPolicy } from "./bus-transport.js";
|
|
9
|
-
import { createTelegramBusLocalServer, createUnauthorizedBusAck, rejectTelegramBusRequest, getTelegramBusEnvelopeTrafficClass, getTelegramBusFollowerSocketPath, getTelegramBusProtocolCompatibility, hasTelegramBusCapability, isTelegramBusEnvelopeAuthorized, parseTelegramBusEnvelope, sendTelegramBusLocalEnvelope, stripTelegramBusApiMetadata, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION, TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF, TELEGRAM_BUS_CAPABILITY_CONNECT_RECOVERY, hasTelegramBusSteerQueueHandoffCapability, TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY, TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE, TELEGRAM_BUS_CAPABILITY_PRE_DISPATCH_REBIND, TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE, TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME, } from "./bus.js";
|
|
9
|
+
import { createTelegramBusLocalServer, createUnauthorizedBusAck, rejectTelegramBusRequest, getTelegramBusEnvelopeTrafficClass, getTelegramBusFollowerSocketPath, getTelegramBusProtocolCompatibility, hasTelegramBusCapability, isTelegramBusEnvelopeAuthorized, parseTelegramBusEnvelope, sendTelegramBusLocalEnvelope, stripTelegramBusApiMetadata, TELEGRAM_BUS_CAPABILITY_DIRECTORY_DISPLAY_FORMAT, TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION, TELEGRAM_BUS_CAPABILITY_QUEUE_HANDOFF, TELEGRAM_BUS_CAPABILITY_CONNECT_RECOVERY, hasTelegramBusSteerQueueHandoffCapability, TELEGRAM_BUS_CAPABILITY_SELECTED_MENU_DELIVERY, TELEGRAM_BUS_CAPABILITY_SESSION_REPLACEMENT_INTENT, TELEGRAM_BUS_CAPABILITY_THREAD_DISPLAY_MODE, TELEGRAM_BUS_CAPABILITY_PRE_DISPATCH_REBIND, TELEGRAM_BUS_CAPABILITY_WORKSPACE_FOLLOWER_AUTO_CONNECT, TELEGRAM_BUS_CAPABILITY_WORKSPACE_RESTORE, TELEGRAM_BUS_CAPABILITY_WORKSPACE_THREAD_RENAME, TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE, } from "./bus.js";
|
|
10
10
|
import { escapeHtml } from "./rendering.js";
|
|
11
11
|
import * as Sync from "./sync.js";
|
|
12
12
|
import { parseTelegramIntegerId as asInteger, } from "./target.js";
|
|
@@ -136,6 +136,9 @@ export function createTelegramBusLeaderRuntimeAssembly(deps) {
|
|
|
136
136
|
store: deps.topicTargetStore,
|
|
137
137
|
getLeaderEpoch: deps.getCurrentLeaderEpoch,
|
|
138
138
|
getExternalProtection: deps.captureWorkspaceExternalProtection,
|
|
139
|
+
onSlotReused: (bindingKey, isCurrent) => display
|
|
140
|
+
? reconcileThreadDisplay(bindingKey, isCurrent).then(() => undefined)
|
|
141
|
+
: Promise.resolve(),
|
|
139
142
|
recordEvent(message, details) {
|
|
140
143
|
deps.recordRuntimeEvent("bus", message, details);
|
|
141
144
|
},
|
|
@@ -174,6 +177,10 @@ export function createTelegramBusLeaderRuntimeAssembly(deps) {
|
|
|
174
177
|
getLeaderEpoch: () => deps.getCurrentLeaderEpoch?.(),
|
|
175
178
|
captureLiveBindingKeys,
|
|
176
179
|
captureBindingAuthority(binding) {
|
|
180
|
+
// Slotless retained bindings need display repair even without a live owner.
|
|
181
|
+
// The reconciler still fences the exact binding snapshot, profile, mode and epoch.
|
|
182
|
+
if (binding.slot === undefined)
|
|
183
|
+
return () => true;
|
|
177
184
|
const matches = (target) => target?.chatId === binding.target.chatId &&
|
|
178
185
|
target?.threadId === binding.target.threadId;
|
|
179
186
|
const leader = deps.topicTargetStore.getActiveByInstanceId(deps.instanceId);
|
|
@@ -194,16 +201,20 @@ export function createTelegramBusLeaderRuntimeAssembly(deps) {
|
|
|
194
201
|
callApi: deps.callApi,
|
|
195
202
|
})
|
|
196
203
|
: undefined;
|
|
197
|
-
const reconcileThreadDisplayOperation = async () => {
|
|
198
|
-
const result = await display.reconcile();
|
|
204
|
+
const reconcileThreadDisplayOperation = async (bindingKey) => {
|
|
205
|
+
const result = await display.reconcile(bindingKey);
|
|
199
206
|
deps.onThreadDisplayChanged?.();
|
|
200
207
|
return result;
|
|
201
208
|
};
|
|
202
|
-
const reconcileThreadDisplay = () => runWorkspaceOperation({
|
|
209
|
+
const reconcileThreadDisplay = (bindingKey, isCurrent = () => true) => runWorkspaceOperation({
|
|
203
210
|
operationId: createTelegramWorkspaceAdmissionOperationId(),
|
|
204
211
|
operationKind: "workspace.reconcile-display",
|
|
205
212
|
scopes: [{ kind: "profile" }],
|
|
206
|
-
},
|
|
213
|
+
}, () => {
|
|
214
|
+
if (!isCurrent())
|
|
215
|
+
throw new Error("Telegram Thread display update lost allocation authority.");
|
|
216
|
+
return reconcileThreadDisplayOperation(bindingKey);
|
|
217
|
+
});
|
|
207
218
|
let startupDisplayTimer;
|
|
208
219
|
let startupDisplayGeneration = 0;
|
|
209
220
|
let startupDisplaySettled = true;
|
|
@@ -2759,7 +2770,14 @@ async function handleFollowerApiCall(envelope, deps) {
|
|
|
2759
2770
|
method: envelope.method,
|
|
2760
2771
|
args: envelope.args,
|
|
2761
2772
|
})) {
|
|
2762
|
-
|
|
2773
|
+
const apiMethod = getFollowerApiMethodAndBody(envelope).apiMethod;
|
|
2774
|
+
return {
|
|
2775
|
+
...rejectTelegramBusRequest(envelope.requestId, TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE),
|
|
2776
|
+
// Only this pre-dispatch authorizer refusal certifies that the API method was not called.
|
|
2777
|
+
...(/^[A-Za-z][A-Za-z0-9]{0,63}$/.test(apiMethod)
|
|
2778
|
+
? { error: { code: "follower-api-not-allowed", method: apiMethod } }
|
|
2779
|
+
: {}),
|
|
2780
|
+
};
|
|
2763
2781
|
}
|
|
2764
2782
|
if (!deps.callApi) {
|
|
2765
2783
|
return rejectTelegramBusRequest(envelope.requestId, "Telegram bus leader does not expose API calling.");
|
|
@@ -548,7 +548,7 @@ export type TelegramBusEnvelope = ({
|
|
|
548
548
|
result?: unknown;
|
|
549
549
|
protocol?: TelegramBusProtocolIdentity;
|
|
550
550
|
error?: {
|
|
551
|
-
code: "commit-unknown" | "request-id-collision" | "ledger-overloaded" | "incompatible-protocol" | "stale-target" | "workspace-binding-unavailable";
|
|
551
|
+
code: "commit-unknown" | "request-id-collision" | "ledger-overloaded" | "incompatible-protocol" | "stale-target" | "follower-api-not-allowed" | "workspace-binding-unavailable";
|
|
552
552
|
method?: string;
|
|
553
553
|
chatId?: number;
|
|
554
554
|
threadId?: number;
|
|
@@ -744,6 +744,7 @@ export declare function createTelegramBusLiveRebindController(deps: {
|
|
|
744
744
|
})) => Promise<TelegramBusLiveRebindSaveObservation | TelegramBusLiveRebindApplyObservation | TelegramBusLiveRebindSettlementObservation | TelegramBusLiveRebindWorkObservation | TelegramBusLiveRebindCommandObservation | undefined>;
|
|
745
745
|
export declare function isTelegramBusEnvelopeAuthorized(envelope: TelegramBusEnvelope, secret: string | undefined): boolean;
|
|
746
746
|
/** Negative acknowledgement carrying only a diagnostic message. */
|
|
747
|
+
export declare const TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE = "Telegram bus API call is not allowed for this follower.";
|
|
747
748
|
export declare function rejectTelegramBusRequest(requestId: string, message: string): Extract<TelegramBusEnvelope, {
|
|
748
749
|
kind: "bus.ack";
|
|
749
750
|
}>;
|
|
@@ -1596,6 +1596,7 @@ export function isTelegramBusEnvelopeAuthorized(envelope, secret) {
|
|
|
1596
1596
|
return auth.length === expected.length && timingSafeEqual(auth, expected);
|
|
1597
1597
|
}
|
|
1598
1598
|
/** Negative acknowledgement carrying only a diagnostic message. */
|
|
1599
|
+
export const TELEGRAM_BUS_FOLLOWER_API_NOT_ALLOWED_MESSAGE = "Telegram bus API call is not allowed for this follower.";
|
|
1599
1600
|
export function rejectTelegramBusRequest(requestId, message) {
|
|
1600
1601
|
return { kind: "bus.ack", requestId, ok: false, message };
|
|
1601
1602
|
}
|
|
@@ -2656,9 +2657,15 @@ function parseAckEnvelope(value, requestId) {
|
|
|
2656
2657
|
code === "ledger-overloaded" ||
|
|
2657
2658
|
code === "incompatible-protocol" ||
|
|
2658
2659
|
code === "stale-target" ||
|
|
2660
|
+
code === "follower-api-not-allowed" ||
|
|
2659
2661
|
code === "workspace-binding-unavailable") {
|
|
2660
2662
|
const chatId = value.error.chatId;
|
|
2661
2663
|
const threadId = value.error.threadId;
|
|
2664
|
+
if (code === "follower-api-not-allowed" &&
|
|
2665
|
+
(value.ok || Object.hasOwn(value, "result") ||
|
|
2666
|
+
typeof value.error.method !== "string" ||
|
|
2667
|
+
!/^[A-Za-z][A-Za-z0-9]{0,63}$/.test(value.error.method)))
|
|
2668
|
+
return undefined;
|
|
2662
2669
|
if (code === "stale-target" &&
|
|
2663
2670
|
(!Number.isSafeInteger(chatId) || !Number.isSafeInteger(threadId))) {
|
|
2664
2671
|
return undefined;
|
|
@@ -11,6 +11,7 @@ import type { TelegramPromptTemplateCommand } from "./prompt-templates.ts";
|
|
|
11
11
|
import { createTelegramControlItemBuilder, type PendingTelegramControlItem, type TelegramControlQueueController, type TelegramControlQueueControllerDeps, type TelegramQueueAdmissionReceipt } from "./queue.ts";
|
|
12
12
|
import { type TelegramBridgeStatusLineOptions } from "./status.ts";
|
|
13
13
|
import { type TelegramApiCallOptions } from "./telegram-api.ts";
|
|
14
|
+
import type { TelegramTarget } from "./target.ts";
|
|
14
15
|
import type { TelegramSessionReplacementIntent } from "./threads.ts";
|
|
15
16
|
import { type TelegramDeferredSourceEvidence, type TelegramLiveSourceCompletionReadiness, type TelegramQueueAdmissionReceiptLike } from "./updates.ts";
|
|
16
17
|
export type TelegramHeldCommandName = "status" | "start" | "help" | "abort" | "stop" | "next" | "continue";
|
|
@@ -383,11 +384,13 @@ export interface TelegramCommandTargetRuntimeDeps<TContext> {
|
|
|
383
384
|
enqueueControlItem: (target: TelegramCommandMessageTarget, ctx: TContext, controlType: TelegramControlCommandType, statusSummary: string, execute: (ctx: TContext) => Promise<void>, admissionReceipts?: TelegramQueueAdmissionReceipt[], onQueued?: (item: PendingTelegramControlItem<TContext>) => void) => void;
|
|
384
385
|
getAdmissionScope?: () => string | undefined;
|
|
385
386
|
getAdmissionJournalBinding?: () => string | undefined;
|
|
387
|
+
/** Ordinary reply destination selected before publication, not resend authority. */
|
|
388
|
+
getReplyTarget?: (message: TelegramCommandRuntimeMessage) => TelegramTarget | undefined;
|
|
386
389
|
onControlQueued?: (message: TelegramCommandRuntimeMessage, receipt: TelegramQueueAdmissionReceipt) => void;
|
|
387
|
-
showStatus: (chatId: number, replyToMessageId: number, ctx: TContext, threadId?: number, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
|
|
388
|
-
openModelMenu: (chatId: number, replyToMessageId: number, ctx: TContext, threadId?: number, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
|
|
389
|
-
openSettingsMenu?: (chatId: number, replyToMessageId: number, ctx: TContext, threadId?: number, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
|
|
390
|
-
sendTextReply: (chatId: number, replyToMessageId: number, text: string, options?: {
|
|
390
|
+
showStatus: (chatId: number, replyToMessageId: number | undefined, ctx: TContext, threadId?: number, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
|
|
391
|
+
openModelMenu: (chatId: number, replyToMessageId: number | undefined, ctx: TContext, threadId?: number, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
|
|
392
|
+
openSettingsMenu?: (chatId: number, replyToMessageId: number | undefined, ctx: TContext, threadId?: number, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
|
|
393
|
+
sendTextReply: (chatId: number, replyToMessageId: number | undefined, text: string, options?: {
|
|
391
394
|
parseMode?: "HTML";
|
|
392
395
|
target?: {
|
|
393
396
|
chatId: number;
|