@llblab/pi-kit 0.31.2 โ 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BACKLOG.md +1 -1
- package/CHANGELOG.md +8 -0
- package/README.md +5 -3
- package/node_modules/@llblab/pi-codex-usage/AGENTS.md +2 -1
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +11 -9
- package/node_modules/@llblab/pi-codex-usage/lib/extension.ts +13 -10
- package/node_modules/@llblab/pi-codex-usage/lib/fast.ts +6 -5
- package/node_modules/@llblab/pi-codex-usage/lib/query.ts +4 -4
- package/node_modules/@llblab/pi-codex-usage/lib/status.ts +10 -10
- package/node_modules/@llblab/pi-codex-usage/lib/telegram.ts +1 -1
- package/node_modules/@llblab/pi-codex-usage/lib/usage.ts +2 -1
- package/node_modules/@llblab/pi-codex-usage/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +4 -2
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +11 -0
- package/node_modules/@llblab/pi-telegram/README.md +5 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/activity.d.ts +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/activity.js +31 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +3 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +8 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.d.ts +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +7 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +49 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +313 -146
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +3 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +99 -18
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +5 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +13 -10
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +4 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +48 -15
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +48 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +68 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +178 -58
- package/node_modules/@llblab/pi-telegram/dist/lib/machine.d.ts +23 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/machine.js +37 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +114 -28
- package/node_modules/@llblab/pi-telegram/dist/lib/media.js +292 -119
- package/node_modules/@llblab/pi-telegram/dist/lib/model.d.ts +54 -15
- package/node_modules/@llblab/pi-telegram/dist/lib/model.js +98 -53
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/preview.d.ts +128 -113
- package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +448 -366
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +6 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +334 -210
- package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +77 -11
- package/node_modules/@llblab/pi-telegram/dist/lib/runtime.js +174 -92
- package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +11 -9
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +53 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/text-groups.d.ts +0 -26
- package/node_modules/@llblab/pi-telegram/dist/lib/text-groups.js +51 -98
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +14 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +18 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +107 -30
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +6 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +11 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +54 -19
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/activity.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +12 -11
- package/node_modules/@llblab/pi-telegram/docs/delivery.md +7 -5
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +18 -8
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +2 -2
- package/node_modules/@llblab/pi-telegram/lib/activity.ts +29 -5
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +9 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +11 -3
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +417 -203
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +88 -14
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +17 -15
- package/node_modules/@llblab/pi-telegram/lib/delivery.ts +47 -19
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +51 -9
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +229 -55
- package/node_modules/@llblab/pi-telegram/lib/machine.ts +60 -0
- package/node_modules/@llblab/pi-telegram/lib/media.ts +438 -172
- package/node_modules/@llblab/pi-telegram/lib/model.ts +169 -74
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +2 -0
- package/node_modules/@llblab/pi-telegram/lib/preview.ts +715 -566
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +5 -5
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +325 -238
- package/node_modules/@llblab/pi-telegram/lib/runtime.ts +260 -104
- package/node_modules/@llblab/pi-telegram/lib/sections.ts +0 -2
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +61 -8
- package/node_modules/@llblab/pi-telegram/lib/text-groups.ts +75 -136
- package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +15 -4
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +120 -36
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +8 -1
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +58 -21
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
package/BACKLOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Backlog
|
|
2
2
|
|
|
3
|
-
The 0.
|
|
3
|
+
The 0.33.0 composition advances Telegram to 0.55.0; package pins, resource order and bundled runtime ownership remain authoritative in `package.json`. Release outcomes belong in [CHANGELOG.md](./CHANGELOG.md).
|
|
4
4
|
|
|
5
5
|
## Carried checks
|
|
6
6
|
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@llblab/pi-kit` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.33.0: Telegram Native Restore and Lifecycle Fences
|
|
6
|
+
|
|
7
|
+
- `Telegram 0.55`: Advances the exact Telegram pin to `0.55.0`. Native Restore confirms its new binding and decides old-tab cleanup before retained input runs. All is input-only; preview, typing and reconnect reject stale lifecycle work. Exact Linux/macOS home displays as Home/home without changing routing identity. Compatible Restore capabilities are negotiated separately from bus protocol v3. Other pins, package membership, resources and load order are unchanged.
|
|
8
|
+
|
|
9
|
+
## 0.32.0: OpenAI Subscription Quota Support
|
|
10
|
+
|
|
11
|
+
- `OpenAI Subscription Support`: Advances Codex Usage to `0.13.0`. ChatGPT subscription OAuth under `openai` now fetches Codex quotas without the legacy `openai-codex` provider; ordinary API-key auth is skipped. Both providers support terminal and Telegram status plus `/fast`, with preferences isolated by provider/model. Other pins, package membership, resources and load order are unchanged.
|
|
12
|
+
|
|
5
13
|
## 0.31.2: Telegram Restore Flow
|
|
6
14
|
|
|
7
15
|
- `Telegram Restore Flow`: Advances the exact Telegram pin to `0.54.4`. Restoring a Pi into a routing tab renames the tab to that Pi's Thread title and keeps the prompt it answers, since an input sent from `All` now moves into its routing tab on desktop and mobile alike. Routing menus disappear after every final choice, every client ends with one routing tab per `All` input, and the old tab is removed right after Pi answers. Other pins, package membership, resources and load order are unchanged.
|
package/README.md
CHANGED
|
@@ -15,10 +15,10 @@ Package links lead to the owning repositories for usage, documentation, issues,
|
|
|
15
15
|
| [`@llblab/pi-actors`](https://github.com/llblab/pi-actors) | `0.54.0` | Inspectable local Runs, reusable Recipes, persistent tools, and delegation Skills |
|
|
16
16
|
| [`@llblab/pi-claude-usage`](https://github.com/llblab/pi-claude-usage) | `0.2.1` | 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.
|
|
18
|
+
| [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.13.0` | 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.4` | 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.
|
|
21
|
+
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.55.0` | Telegram companion with native pre-dispatch Restore, input-only All, fenced previews/typing/reconnect, follower Threads, Windows support, files, voice, and controls |
|
|
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.
|
|
@@ -45,7 +45,9 @@ Prefer the kit instead of separately loading the same packages. If you already u
|
|
|
45
45
|
|
|
46
46
|
## Development
|
|
47
47
|
|
|
48
|
-
The `0.
|
|
48
|
+
The `0.33.0` composition advances Telegram to `0.55.0`: native Restore confirms the new binding, decides old-tab cleanup, then releases retained input through its original prompt, command or template owner. Threaded Mode `All` is input-only; text, voice, files and albums enter routing Threads without output fallback to All. Preview, typing and reconnect lifecycles reject stale context/generation work, and exact Linux/macOS home directories display as Home/home. Classic DM remains supported. Other pins, resource paths and load order are unchanged.
|
|
49
|
+
|
|
50
|
+
Telegram keeps bus protocol v3: update every Pi process that runs pi-telegram together; 0.53.x followers cannot register with current leaders. Pre-dispatch Restore additionally negotiates compatible reader and execution capabilities; older supported paths remain distinct. Runtime storage stays in `tmp/pi-telegram`. Drain the prompt queue or accept losing waiting work before reload/restart; damaged-state reset discards all profiles' runtime continuity. `npm run validate` checks exact installed pins, declared resources, dependency audit and bundled inventory. It does not certify installed-client rendering; carried checks remain in [Backlog](./BACKLOG.md).
|
|
49
51
|
|
|
50
52
|
```bash
|
|
51
53
|
npm install
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
- `Domain DAG`: `index.ts` only re-exports the extension and public contracts; `lib/extension.ts` composes the Pi extension. `lib/usage-store.ts` owns persistence/leadership, `lib/query.ts` owns quota I/O, `lib/usage.ts` owns normalized reports, `lib/status.ts` and `lib/status-format.ts` own local UI orchestration and formatting, `lib/telegram.ts` owns optional registration, and `lib/fast.ts` owns Codex Fast semantics/request adaptation. `@llblab/pi-command-fast` owns command arbitration and generic JSONC override editing. Keep dependency direction acyclic; do not import `index.ts` from `lib/` or mix Fast persistence into quota coordination. Place domain tests in `tests/<domain>.test.ts` (or a domain-prefixed focused integration test); use independent names such as `invariants.test.ts` only for cross-domain invariants.
|
|
6
6
|
- `Statusline-first scope`: Own usage state + usage mode: keep quota reporting zero-configuration and the optional Fast toggle focused on the existing terminal status.
|
|
7
7
|
- Trigger: Considering new commands, menus, persisted settings, or notification output.
|
|
8
|
-
- Action: Prefer deleting the surface unless it is required for the optimistic TUI status widget, the optional `pi-telegram` `/start` status-line mirror, or the existing argument-free `/fast` command. Register
|
|
8
|
+
- Action: Prefer deleting the surface unless it is required for the optimistic TUI status widget, the optional `pi-telegram` `/start` status-line mirror, or the existing argument-free `/fast` command. Register both `openai-codex` and `openai` provider handlers through `registerFastProvider` on session_start and release both on session_shutdown; never register `/fast` directly or filter Fast by model ID/auth. Fast persists solely as `serviceTier: "priority"` in the current provider/model override (never share preferences across providers with the same model ID); OFF deletes only that property. Preserve JSONC/unrelated settings, existing wire tiers, and usage refresh/Telegram. Apply lowercase ` fast` through the final terminal boundary; the optional Telegram row reads the active model's Fast preference at render time and appends plain-text ` fast` (or shows `fast` alone without quota). Toggle redraws without quota fetch or success notification. The library owns session WeakMap/reload arbitration, not Fast state. A Fast config read failure must not block usage status.
|
|
9
|
+
- `OpenAI provider support`: Show the same Codex subscription quota for active `openai-codex` and `openai` models. Quota transport accepts `openai` subscription OAuth (guarded by `modelRegistry.isUsingOAuth`) and legacy `openai-codex` auth, with the Codex CLI fallback; never send `openai` API-key credentials to the ChatGPT quota endpoint or label Codex quotas as API billing limits.
|
|
9
10
|
- `Optimistic refresh`: Preserve the last good statusline bar during refresh and transient failures.
|
|
10
11
|
- Trigger: Updating quota polling or error handling.
|
|
11
12
|
- Action: Do not collapse the bar while a request is in flight; only show `n/a` or `error` after repeated failures or no usable quota.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0: OpenAI Subscription Provider Support
|
|
4
|
+
|
|
5
|
+
- Added `openai` alongside `openai-codex` for `/fast`, request priority adaptation, terminal status and the optional Telegram row. Fast preferences remain isolated by provider/model, including identical model IDs. Both providers share the existing Codex subscription quota stream. Pi subscription OAuth under `openai` now fetches quota directly without requiring the legacy provider; `openai` API-key auth is skipped and the Codex CLI fallback is preserved.
|
|
6
|
+
|
|
3
7
|
## 0.12.1: Telegram Fast Status
|
|
4
8
|
|
|
5
9
|
- The optional Telegram status row now appends lowercase ` fast` for the active Codex model, or shows `fast` alone when quota is unavailable. It rereads the per-model preference at menu render time, so toggles, model changes and manual edits do not wait for quota refresh. Quota polling, auth, request adaptation and terminal behavior are unchanged; isolated tests cover enabled/disabled state, model/provider changes and unreadable configuration.
|
|
@@ -19,16 +19,16 @@ This repository is a minimal fork of [`narumiruna/pi-extensions/extensions/pi-co
|
|
|
19
19
|
- Shows two counter-moving half-height markers in the statusline bar while Codex usage is loading, then keeps the bar fresh (the countdown ticks locally)
|
|
20
20
|
- Keeps the last usable bar visible during ordinary refreshes instead of replacing known quota with a loading state
|
|
21
21
|
- Statusline output adapts to the response: weekly-only limits show an explicit remaining percentage and reset countdown, while dual-window limits keep the compact themed bar
|
|
22
|
-
-
|
|
23
|
-
- When `pi-telegram` is available, the same compact value appears as `codex: <value>` in the `/start` menu status text for
|
|
22
|
+
- Models under either `openai-codex` or `openai` show the same primary Codex subscription quota and `codex` status label (not OpenAI API billing limits)
|
|
23
|
+
- When `pi-telegram` is available, the same compact value appears as `codex: <value>` in the `/start` menu status text for either supported provider
|
|
24
24
|
- Additional returned quota buckets are ignored
|
|
25
|
-
- Pi
|
|
25
|
+
- Pi subscription auth from `openai` or legacy `openai-codex` is used first
|
|
26
26
|
- Codex CLI app-server remains available as a fallback
|
|
27
27
|
- Missing auth, subscription, plan, or quota windows are shown as `n/a`, not as an error
|
|
28
28
|
- Successful updates briefly redraw the bar only when a 5% segment changes
|
|
29
29
|
- Network/provider failures keep the last good bar (up to an hour), then show `error`
|
|
30
30
|
- Any number of Pi instances share one request stream, see [Shared Refresh](#shared-refresh)
|
|
31
|
-
- Quota display requires no configuration; `/fast` optionally toggles priority service for the active
|
|
31
|
+
- Quota display requires no configuration; `/fast` optionally toggles priority service for the active `openai-codex` or `openai` model
|
|
32
32
|
|
|
33
33
|
## Install
|
|
34
34
|
|
|
@@ -59,7 +59,7 @@ For explicitly local library experiments, a temporary folder link reads the libr
|
|
|
59
59
|
|
|
60
60
|
## Fast mode
|
|
61
61
|
|
|
62
|
-
`/fast` takes no arguments and dispatches by the current **provider**, not model names or OAuth eligibility. For any `openai-codex` model, it stores only `serviceTier: "priority"` in Pi's canonical `models.json` (honoring `PI_CODING_AGENT_DIR`):
|
|
62
|
+
`/fast` takes no arguments and dispatches by the current **provider**, not model names or OAuth eligibility. For any `openai-codex` or `openai` model, it stores only `serviceTier: "priority"` in Pi's canonical `models.json` (honoring `PI_CODING_AGENT_DIR`):
|
|
63
63
|
|
|
64
64
|
```json
|
|
65
65
|
{
|
|
@@ -73,6 +73,8 @@ For explicitly local library experiments, a temporary folder link reads the libr
|
|
|
73
73
|
}
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
For an `openai` model, the same override goes under `providers.openai.modelOverrides`. Preferences are isolated by provider and model ID, so identical IDs in the two providers do not share Fast state.
|
|
77
|
+
|
|
76
78
|
OFF removes only `serviceTier`; it never writes `"default"`. JSONC comments and unrelated configuration survive. There is no separate Fast config or model allowlist. State follows provider/model selection and survives restart; manual edits are read on lifecycle refresh and each request.
|
|
77
79
|
|
|
78
80
|
When this and Claude Usage are loaded, their shared library registers **one** `/fast`, in either load order, even with separate physical dependency copies. A session-scoped WeakMap avoids cross-session dispatch; shutdown releases registrations for reload (Pi retains the session manager). Unrelated providers receive a concise unsupported-provider message. Invalid arguments show `Usage: /fast`; success is silent.
|
|
@@ -155,22 +157,22 @@ Coordination assumes a local filesystem and cooperating instances on the same ma
|
|
|
155
157
|
|
|
156
158
|
## Telegram Status Menu
|
|
157
159
|
|
|
158
|
-
If `@llblab/pi-telegram` is loaded with the public status-line provider API, this extension registers an optional `/start` menu status row. The row is shown only while the active model uses
|
|
160
|
+
If `@llblab/pi-telegram` is loaded with the public status-line provider API, this extension registers an optional `/start` menu status row. The row is shown only while the active model uses `openai-codex` or `openai`:
|
|
159
161
|
|
|
160
162
|
```text
|
|
161
163
|
codex: โโโโโโโโโโ 6d
|
|
162
164
|
```
|
|
163
165
|
|
|
164
|
-
The value is the same compact quota bar plus weekly reset countdown used by the terminal statusline, always with the `codex` label. When Fast is enabled for the active model, it appends plain-text ` fast`; without a usable quota report it shows `codex: fast`. The preference is reread when the menu is rendered, so toggles and model changes do not depend on a quota refresh. If `pi-telegram` is absent, older, or the active model
|
|
166
|
+
The value is the same compact quota bar plus weekly reset countdown used by the terminal statusline, always with the `codex` label. When Fast is enabled for the active model, it appends plain-text ` fast`; without a usable quota report it shows `codex: fast`. The preference is reread when the menu is rendered, so toggles and model changes do not depend on a quota refresh. If `pi-telegram` is absent, older, or the active model uses another provider, no Telegram row is added.
|
|
165
167
|
|
|
166
168
|
## Auth
|
|
167
169
|
|
|
168
170
|
The extension tries usage sources in this order:
|
|
169
171
|
|
|
170
|
-
1. Pi's `openai-codex`
|
|
172
|
+
1. Pi's subscription auth from `openai` or legacy `openai-codex` (active model first)
|
|
171
173
|
2. `codex app-server --listen stdio://`
|
|
172
174
|
|
|
173
|
-
OpenAI API keys are not ChatGPT
|
|
175
|
+
Selecting an `openai` model enables the status and Fast toggle, and its ChatGPT subscription OAuth credentials can fetch the quota directly; a legacy `openai-codex` provider is not required. The extension uses Pi's resolved auth headers/token, checks `modelRegistry.isUsingOAuth` before using `openai` credentials, and keeps the Codex CLI fallback. OpenAI API keys are not ChatGPT subscription auth and do not expose these quotas; `openai` API-key credentials are skipped, not sent to the ChatGPT quota endpoint. Without subscription auth/quota, the quota status is `n/a` when both sources report an unavailable state. Fast works independently of subscription auth.
|
|
174
176
|
|
|
175
177
|
## License
|
|
176
178
|
|
|
@@ -3,23 +3,26 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import { registerFastProvider } from "@llblab/pi-command-fast";
|
|
4
4
|
import { applyFastToRequest, isFastEnabled, isFastEligibleModel, toggleFast } from "./fast.ts";
|
|
5
5
|
import { createCodexUsageStatus } from "./status.ts";
|
|
6
|
+
import { OPENAI_USAGE_PROVIDERS } from "./usage.ts";
|
|
6
7
|
|
|
7
8
|
export default function codexUsage(pi: ExtensionAPI) {
|
|
8
|
-
let releaseFast: (
|
|
9
|
+
let releaseFast: Array<() => void> = [];
|
|
9
10
|
pi.on("session_start", (_event, ctx) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
for (const provider of OPENAI_USAGE_PROVIDERS) {
|
|
12
|
+
releaseFast.push(registerFastProvider(pi, ctx, {
|
|
13
|
+
provider,
|
|
14
|
+
toggle(commandCtx) {
|
|
15
|
+
toggleFast(commandCtx.model!);
|
|
16
|
+
status.fastChanged(commandCtx, commandCtx.model!);
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
17
20
|
});
|
|
18
|
-
pi.on("session_shutdown", () => { releaseFast
|
|
21
|
+
pi.on("session_shutdown", () => { releaseFast.forEach((release) => release()); releaseFast = []; });
|
|
19
22
|
const status = createCodexUsageStatus(pi);
|
|
20
23
|
pi.on("before_provider_request", (event, ctx) => {
|
|
21
24
|
const model = ctx.model;
|
|
22
25
|
if (!isFastEligibleModel(model)) return;
|
|
23
|
-
return applyFastToRequest(event.payload, model.id, isFastEnabled(model
|
|
26
|
+
return applyFastToRequest(event.payload, model.id, isFastEnabled(model));
|
|
24
27
|
});
|
|
25
28
|
}
|
|
@@ -3,16 +3,17 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
import { isModelOverrideValue, toggleModelOverrideValue } from "@llblab/pi-command-fast";
|
|
4
4
|
import { isOpenAICodexModel } from "./usage.ts";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
type FastModel = Pick<NonNullable<ExtensionContext["model"]>, "provider" | "id">;
|
|
7
|
+
const target = (model: FastModel) => ({ provider: model.provider, id: model.id, property: "serviceTier", enabledValue: "priority" });
|
|
7
8
|
|
|
8
9
|
export function isFastEligibleModel(model: ExtensionContext["model"]): model is NonNullable<ExtensionContext["model"]> {
|
|
9
10
|
return !!model && isOpenAICodexModel(model);
|
|
10
11
|
}
|
|
11
|
-
export function isFastEnabled(
|
|
12
|
-
return isModelOverrideValue(target(
|
|
12
|
+
export function isFastEnabled(model: FastModel, path?: string): boolean {
|
|
13
|
+
return isModelOverrideValue(target(model), path);
|
|
13
14
|
}
|
|
14
|
-
export function toggleFast(
|
|
15
|
-
return toggleModelOverrideValue(target(
|
|
15
|
+
export function toggleFast(model: FastModel, path?: string): boolean {
|
|
16
|
+
return toggleModelOverrideValue(target(model), path);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export function applyFastToRequest(payload: unknown, modelId: string, enabled: boolean): unknown {
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
|
|
3
3
|
import { createInterface } from "node:readline";
|
|
4
4
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import { assertObject, errorMessage, normalizeAppServerResponse, normalizeBackendPayload, parseJsonObject, selectUsageSnapshot, type AppServerRateLimitResponse, type CodexUsageModel, type CodexUsageReport, type RateLimitStatusPayload, type UsageQueryError } from "./usage.ts";
|
|
6
|
-
const CODEX_PROVIDER_ID = "openai-codex";
|
|
5
|
+
import { assertObject, errorMessage, isOpenAICodexModel, normalizeAppServerResponse, normalizeBackendPayload, parseJsonObject, selectUsageSnapshot, type AppServerRateLimitResponse, type CodexUsageModel, type CodexUsageReport, type RateLimitStatusPayload, type UsageQueryError } from "./usage.ts";
|
|
7
6
|
const CODEX_USAGE_LIMIT_ID = "codex";
|
|
8
7
|
const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
9
8
|
const MAX_ERROR_BODY_CHARS = 600;
|
|
@@ -64,7 +63,7 @@ async function queryViaPiAuth(
|
|
|
64
63
|
const auth = await resolvePiCodexAuth(ctx);
|
|
65
64
|
if (!auth) {
|
|
66
65
|
throw new Error(
|
|
67
|
-
"No Pi OpenAI Codex subscription auth was available. Use
|
|
66
|
+
"No Pi OpenAI Codex subscription auth was available. Use an openai or openai-codex subscription model or run /login for OpenAI ChatGPT.",
|
|
68
67
|
);
|
|
69
68
|
}
|
|
70
69
|
|
|
@@ -96,6 +95,7 @@ async function resolvePiCodexAuth(
|
|
|
96
95
|
const errors: string[] = [];
|
|
97
96
|
|
|
98
97
|
for (const model of models) {
|
|
98
|
+
if (model.provider === "openai" && !ctx.modelRegistry.isUsingOAuth(model)) continue;
|
|
99
99
|
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
100
100
|
if (!auth.ok) {
|
|
101
101
|
errors.push(auth.error);
|
|
@@ -127,7 +127,7 @@ function codexAuthCandidateModels(ctx: ExtensionContext): PiModel[] {
|
|
|
127
127
|
const candidates: PiModel[] = [];
|
|
128
128
|
const seen = new Set<string>();
|
|
129
129
|
const add = (model: PiModel | undefined) => {
|
|
130
|
-
if (!model || model
|
|
130
|
+
if (!model || !isOpenAICodexModel(model)) return;
|
|
131
131
|
const key = `${model.provider}/${model.id}`;
|
|
132
132
|
if (seen.has(key)) return;
|
|
133
133
|
seen.add(key);
|
|
@@ -41,20 +41,20 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
|
|
|
41
41
|
let statuslineRequestId = 0;
|
|
42
42
|
let unregisterTelegramStatusLine: (() => void) | undefined;
|
|
43
43
|
let telegramStatusLineRegistration: Promise<void> | undefined;
|
|
44
|
-
let
|
|
44
|
+
let fastModel: PiModel | undefined;
|
|
45
45
|
let fastEnabled = false;
|
|
46
46
|
let lastStatusText: string | undefined;
|
|
47
47
|
let loadingModel: CodexUsageModel | undefined;
|
|
48
48
|
|
|
49
|
-
const syncFast = (model: PiModel | undefined
|
|
50
|
-
|
|
51
|
-
fastEnabled =
|
|
49
|
+
const syncFast = (model: PiModel | undefined) => {
|
|
50
|
+
fastModel = isFastEligibleModel(model) ? model : undefined;
|
|
51
|
+
fastEnabled = fastModel !== undefined && isFastEnabled(fastModel);
|
|
52
52
|
};
|
|
53
53
|
const setCodexStatus = (ctx: ExtensionContext, text: string | undefined, model?: CodexUsageModel) => {
|
|
54
54
|
lastStatusText = text;
|
|
55
55
|
ctx.ui.setStatus(STATUS_KEY, text === undefined ? undefined : appendFastStatus(
|
|
56
56
|
ctx, text,
|
|
57
|
-
fastEnabled && model?.id ===
|
|
57
|
+
fastEnabled && model?.provider === fastModel?.provider && model?.id === fastModel?.id,
|
|
58
58
|
));
|
|
59
59
|
};
|
|
60
60
|
|
|
@@ -95,7 +95,7 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
|
|
|
95
95
|
ctx: ExtensionContext,
|
|
96
96
|
model: CodexUsageModel | undefined,
|
|
97
97
|
) => {
|
|
98
|
-
const modelChanged = loadingModel?.id !== model?.id;
|
|
98
|
+
const modelChanged = loadingModel?.provider !== model?.provider || loadingModel?.id !== model?.id;
|
|
99
99
|
loadingModel = model;
|
|
100
100
|
if (statuslineLoadingTimer) {
|
|
101
101
|
if (modelChanged && lastStatusText !== undefined) setCodexStatus(ctx, lastStatusText, model);
|
|
@@ -331,7 +331,7 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
|
|
|
331
331
|
try {
|
|
332
332
|
const activeModel = model ?? ctx.model;
|
|
333
333
|
const previousFast = fastEnabled;
|
|
334
|
-
syncFast(activeModel
|
|
334
|
+
syncFast(activeModel);
|
|
335
335
|
forceRender ||= previousFast !== fastEnabled;
|
|
336
336
|
if (!isOpenAICodexModel(activeModel)) {
|
|
337
337
|
clearUsageStatusline(ctx);
|
|
@@ -370,7 +370,7 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
|
|
|
370
370
|
ensureTelegramStatusLineRegistered();
|
|
371
371
|
|
|
372
372
|
pi.on("session_start", (_event, ctx) => {
|
|
373
|
-
syncFast(ctx.model
|
|
373
|
+
syncFast(ctx.model);
|
|
374
374
|
ensureTelegramStatusLineRegistered();
|
|
375
375
|
if (isOpenAICodexModel(ctx.model))
|
|
376
376
|
void refreshCurrentCodexUsageStatusline(ctx, true).catch(
|
|
@@ -388,7 +388,7 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
|
|
|
388
388
|
});
|
|
389
389
|
|
|
390
390
|
pi.on("model_select", (event, ctx) => {
|
|
391
|
-
syncFast(event.model
|
|
391
|
+
syncFast(event.model);
|
|
392
392
|
if (isOpenAICodexModel(event.model)) {
|
|
393
393
|
void refreshCurrentCodexUsageStatusline(ctx, true, event.model).catch(
|
|
394
394
|
handleAsyncTimerError,
|
|
@@ -405,7 +405,7 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
|
|
|
405
405
|
});
|
|
406
406
|
return {
|
|
407
407
|
fastChanged(ctx: ExtensionContext, model: PiModel) {
|
|
408
|
-
syncFast(model
|
|
408
|
+
syncFast(model);
|
|
409
409
|
if (lastStatusText !== undefined) setCodexStatus(ctx, lastStatusText, model);
|
|
410
410
|
renderState(ctx, loadState(), model, true);
|
|
411
411
|
},
|
|
@@ -29,7 +29,7 @@ export function codexUsageTelegramStatusLine(
|
|
|
29
29
|
): TelegramStatusLineProviderResult {
|
|
30
30
|
if (!activeModel || !isOpenAICodexModel(activeModel)) return undefined;
|
|
31
31
|
const value = report ? formatCodexUsageStatusValue(report, activeModel) : undefined;
|
|
32
|
-
const fast = isFastEnabled(activeModel
|
|
32
|
+
const fast = isFastEnabled(activeModel);
|
|
33
33
|
if (!value && !fast) return undefined;
|
|
34
34
|
return { label: "codex", value: value ? `${value}${fast ? " fast" : ""}` : "fast" };
|
|
35
35
|
}
|
|
@@ -4,8 +4,9 @@ const SECOND_MS = 1000;
|
|
|
4
4
|
const CODEX_USAGE_LIMIT_ID = "codex";
|
|
5
5
|
type PiModel = NonNullable<ExtensionContext["model"]>;
|
|
6
6
|
export type CodexUsageModel = Pick<PiModel, "id" | "name" | "provider">;
|
|
7
|
+
export const OPENAI_USAGE_PROVIDERS = ["openai-codex", "openai"] as const;
|
|
7
8
|
export function isOpenAICodexModel(model: Pick<PiModel, "provider"> | undefined): boolean {
|
|
8
|
-
return model?.provider ===
|
|
9
|
+
return OPENAI_USAGE_PROVIDERS.some((provider) => model?.provider === provider);
|
|
9
10
|
}
|
|
10
11
|
export type UsageSource = "pi-auth" | "codex-app-server";
|
|
11
12
|
export type UsageQueryError = { source: UsageSource; message: string; cause?: unknown };
|
|
@@ -60,6 +60,7 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
60
60
|
### 4.1 Flat Domain DAG
|
|
61
61
|
|
|
62
62
|
- Cohesive domains live as flat `/lib/*.ts` modules whose local import graph is acyclic.
|
|
63
|
+
- `In-memory lifecycles`: Use pure transitions and a domain-owned effect shell when competing asynchronous sources, generation/cancellation fencing or timers justify the pattern. The shared `machine` leaf owns runner mechanics only; state, events, effects and authority stay with the domain. Durable admission, custody, Workspace and IPC retain their transactional contracts.
|
|
63
64
|
- `lib/extension.ts` constructs high-level runtimes and wires live ports. Domain policy, mutable state, sequencing, identity, retries, normalization, and lifecycle recovery belong to the owning `/lib` module.
|
|
64
65
|
- During refactoring, add a domain only for demonstrated cross-domain reuse or a concrete dependency-direction/cycle problem. Prefer an existing cohesive owner; a closed helper cluster, independent tests or file length alone is insufficient. An acyclic validator result does not justify a new boundary. Do not atomize cohesive modules or create one-use wrappers merely to shrink `index.ts`.
|
|
65
66
|
- `bindings` owns Pi-facing registration and narrow cross-domain assembly; it may connect established ports but must not absorb routing, rendering, transport, or mutable policy.
|
|
@@ -85,7 +86,7 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
85
86
|
- Admission is journal-first: validate and persist the complete `getUpdates` response before one monotonic offset commit, then signal an independent worker without awaiting semantic execution. Missing cursor with a non-empty journal, malformed/foreign authority, or capacity exhaustion fails closed. โDurableโ means process-crash recovery after atomic rename, not unflushed host/kernel/filesystem/device/power-loss survival.
|
|
86
87
|
- 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.
|
|
87
88
|
- 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.
|
|
88
|
-
- 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,
|
|
89
|
+
- 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.
|
|
89
90
|
- 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`.
|
|
90
91
|
- 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.
|
|
91
92
|
- 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.
|
|
@@ -102,8 +103,9 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
102
103
|
- `/stop`, `/abort`, `/next`, and `/continue` respectively reset+abort, abort while preserving queue, force the next turn, and enqueue a control-lane continuation. A negative reaction to an original standalone `/continue` settles and removes only its exact waiting source-addressed continuation; positive reactions do not change its control lane. Protect the Pi-owned pending head and already-started work, preserve other queue positions, and never address synthetic model continuations by their reply anchor. Settlement failure retains the continuation. Abort-history folding applies only to Telegram-owned active turns. A busy `/next` marks only its exact active Telegram turn: terminal abort settlement attempts one explicit abort notice before the exact selected queued prompt receives its dispatch notice. Notice failure is diagnostic and cannot block dispatch; a later `/abort` or `/stop` cancels both pending transition notices before taking ownership. Target command adapters must forward these transition ports. A successful dispatch notice keeps the queued prompt's one reply-header claim across agent start, so later messages in that turn never repeat it.
|
|
103
104
|
- 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.
|
|
104
105
|
- 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.
|
|
106
|
+
- 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.
|
|
105
107
|
- 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.
|
|
106
|
-
- `preview` owns streaming lifecycle only, not assistant rendering. Finalization waits for
|
|
108
|
+
- `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.
|
|
107
109
|
- 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: 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.
|
|
108
110
|
- 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.
|
|
109
111
|
- Thread display defaults to the profile-scoped 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.
|
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.55.0: Native Restore and fenced lifecycles
|
|
8
|
+
|
|
9
|
+
- `Restore cleanup`: Live Restore binds the new tab and decides old-tab cleanup before native leader/follower inputs run. Protected work defers cleanup; session replacement carries no deletion grant, and unknown deletion never repeats. Follower execution targets the rebound tab without rewriting journal originals or quoting the former tab. Exact donor ACKs seal routing clocks and retire the menu. Reader/execution capabilities, source custody and detached replies stay separate; busy hints coalesce.
|
|
10
|
+
- `Runtime state`: In-memory lifecycles now use isolated pure TypeScript transitions and domain-owned effect shells. A shared leaf runner drives preview, typing, model switching, grouped input, compaction and follower recovery without adding a runtime dependency; durable admission, custody, Workspace and IPC retain their existing transactional owners.
|
|
11
|
+
- `Preview ordering`: A publication waits only for its preceding draft and publications, never a later draft that waits for it. New turns and sessions detach old preview dependencies, and late draft, timer or publication settlements cannot unblock or overwrite replacement work. Final replies still drain their own issued draft without post-final clearing.
|
|
12
|
+
- `Model switching`: Restarting or clearing a switch now consumes its staged model and continuation together, preventing an old tool settlement from aborting the continuation again. Synchronous nested controller calls retain the outer continuation context and abort handler.
|
|
13
|
+
- `Grouped input`: Albums and split text share one fenced pending-group lifecycle. Separate text batches end at a message identity rather than a mutable count, and a synchronous dispatcher failure retains the input and schedules retry instead of leaving the group permanently dispatching.
|
|
14
|
+
- `Typing ownership`: Queue dispatch preserves its exact Thread; Threaded Mode refuses missing targets and stops Classic chat-only loops on mode changes. Context/generation and loop-owner fences guard starts, queued HTTP/IPC issuance, ticks and awaited lifecycle continuations without letting stale work claim replacement typing. Typing never replays HTTP or transport fallback after failure; follower callbacks stay local. Compaction retains its own loop and preserves agent-owned activity.
|
|
15
|
+
- `Reconnect lifecycle`: Follower startup restores its exact session's retained Thread after proving the former runtime PID absent and no registered peer owns it; live/unknown predecessors stay protected. Connect notices use acknowledged display titles, not provisional generated names. Manual Connect/Restore retain prepared context, read-only checks, committed slots and journal history. Stale recovery cannot change a replacement connection; stopped leader startup cannot poll or publish its endpoint.
|
|
16
|
+
- `Thread surfaces`: All is input-only: sends, drafts, media, forwards and activity require a concrete Thread; implicit fallback and aggregate output are refused. Routing failures retain input, not All notices. Voice/files/albums enter one routing tab. Exact Linux/macOS home displays as Home/home with normal suffixes; manual names and other modes stay unchanged. Restore publishes acknowledged titles to heartbeat/TUI without changing generated identity; late old-target title ACKs are ignored.
|
|
17
|
+
|
|
7
18
|
## 0.54.4: Restored tabs take their Pi's name
|
|
8
19
|
|
|
9
20
|
- `Restore title`: Restoring a Pi into a routing tab ("move Pi into this tab") now renames the tab to that Pi's Thread display title, as Workspace Restore already did; previously a live-rebind Restore left the tab named `๐ฆ Routing`. A failed rename is recorded and never blocks the Restore.
|
|
@@ -143,7 +143,7 @@ Enable the optional capabilities the bridge needs in the [@BotFather](https://t.
|
|
|
143
143
|
| Generative Apps | Install or explicitly replace a reviewed `.mjs` application whose generated JSON button view may mix direct `app::method` actions with ordinary model prompts. | Repeated games, controls, tutors, and adapters compile routine interaction without losing selective model interpretation, explanation, or adaptation. |
|
|
144
144
|
| Callback routing | Route known callbacks to the owner extension and unknown callbacks back into Pi. | Companion extensions can build UI without polling Telegram themselves. |
|
|
145
145
|
| Threaded Mode | Run one leader plus visible follower Pi instances through named private-chat threads. | One bot can host a local multi-instance Pi organism without hidden process spawning. |
|
|
146
|
-
| Reroute and restore | Give
|
|
146
|
+
| Reroute and restore | Give text, voice, files and albums sent from `All`, plus unknown threads, explicit reroute and replace/restore choices. | Forward removes the temporary tab; restore rebinds it and may remove only the replaced old tab after positive cleanup proofs. Blocked cleanup preserves the relocation and protected work. |
|
|
147
147
|
| Extension sections | Add menu sections, commands, status rows, settings, callbacks, and delivery helpers from companion extensions. | `pi-telegram` becomes a platform surface for other Pi extensions. |
|
|
148
148
|
| Runtime diagnostics | Use `/telegram-status` and recent runtime events for connection, role, negotiated bus protocol/build/capabilities, separate polling and inbound-worker progress, journal depth, local/foreign queue ownership, automatic retry waits, transport, and failures. | Compatible build skew, foreign semantic authority, a healthy poller, durable backoff and an infrastructure-blocked worker remain distinguishable without hidden logs. |
|
|
149
149
|
| Safety and ownership | Pair one owner, lock transport, scope targets, and reject fake terminal behavior. | Remote access remains explicit, bounded, and understandable. |
|
|
@@ -196,6 +196,8 @@ Named profile identifiers contain only lowercase ASCII letters and digits (maxim
|
|
|
196
196
|
|
|
197
197
|
## Main Surfaces
|
|
198
198
|
|
|
199
|
+
In Threaded Mode, `All` is input-only: messages enter temporary routing tabs, and Pi replies stay in bound Threads. Missing routing or delivery targets fail closed instead of sending fallback messages to All. There is no automatic cleanup of old All output. Classic DM behavior is unchanged.
|
|
200
|
+
|
|
199
201
|
### Operator Menu
|
|
200
202
|
|
|
201
203
|
`/start` opens the Telegram-native control panel: status, prompt-template commands, model selection, thinking level, settings, queue controls, and extension sections. It is the primary Telegram UI; reaction shortcuts are secondary queue affordances.
|
|
@@ -248,14 +250,14 @@ Classic private DM mode is the base product mode. When Telegram private-chat Thr
|
|
|
248
250
|
- 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. Active or protected work blocks reclamation; restoring a remembered session does not evict another binding.
|
|
249
251
|
- Telegram never launches hidden Pi processes.
|
|
250
252
|
|
|
251
|
-
In Threaded Mode, open Settings โ **๐งต Thread display** to choose `letters` (default), `names`, `directory-title`, or `directory-snake` for this bot profile
|
|
253
|
+
In Threaded Mode, open Settings โ **๐งต Thread display** to choose `letters` (default), `names`, `directory-title`, or `directory-snake` for this bot profile. Unsupported retained values, including the retired `directories` mode, fall back to `letters` without rewriting the saved configuration. Fresh tabs are created with the active mode's title instead of being visibly renamed afterward. Telegram tab titles, Pi terminal status, live Thread choosers/notices, prompt attribution, and named `telegram_message` targeting use the same acknowledged display name; target IDs and live registrations still own routing. Names shows the generated dictionary name chosen for the slot, such as `Anchor` for slot `A`; the directory formats produce `api_tools` or `Api Tools` and append `_a` or ` A` only while two or more authenticated live instances share that exact directory. On Linux/macOS, the current user's exact home directory displays as `home` or `Home`; subdirectories keep their own names, and Windows remains unchanged. Dormant retained bindings do not keep those suffixes visible. After leader election, automatic title contraction waits one follower-staleness window for live registrations to settle, avoiding a temporary suffix removal and restoration during startup. `/name` sets a manual Thread display name; **Reset to automatic** restores the selected automatic projection. Switching preserves Thread IDs, slots, generated recovery identity, and queue ownership. Partial application reports an error and can be retried without recreating Threads.
|
|
252
254
|
|
|
253
255
|
| Mode | Best for | Runtime shape |
|
|
254
256
|
| --- | --- | --- |
|
|
255
257
|
| Classic DM | One running Pi instance and its active session controlled from one private bot chat | One polling owner, one queue/runtime surface |
|
|
256
258
|
| Threaded Mode | Several visible Pi sessions sharing one bot | One leader owns transport; each private-chat Thread retains one session-qualified Workspace binding |
|
|
257
259
|
|
|
258
|
-
**Live rebinding
|
|
260
|
+
**Live rebinding:** Restore confirms the new binding, makes one old-tab cleanup decision, then releases the saved input through its native prompt, command or template owner. Negotiated followers use the same order without another queue. Earlier protected work defers cleanup; `/new` cannot carry an old-session deletion grant into its replacement. An unknown delete never repeats or blocks ordinary input on the confirmed new tab. Exact source/receipt custody remains separate from replies and model completion. Upgrade participating readers for the new metadata; older peers retain their supported legacy path. See [Multi-Instance Bus](./docs/multi-instance-bus.md#live-rebind-channel).
|
|
259
261
|
|
|
260
262
|
## Environment Configuration
|
|
261
263
|
|
|
@@ -101,11 +101,13 @@ export interface TelegramActivityDispatcher {
|
|
|
101
101
|
}
|
|
102
102
|
/** @internal */
|
|
103
103
|
export declare function createTelegramActivityDispatcher(deps?: {
|
|
104
|
+
captureDrain?: () => (() => void) | undefined;
|
|
104
105
|
recordFailure?: (handlerId: string, event: TelegramActivityEvent, error: unknown) => void;
|
|
105
106
|
}): TelegramActivityDispatcher;
|
|
106
107
|
/** @internal */
|
|
107
108
|
export declare function createTelegramActivityBridgeRuntime(deps: {
|
|
108
109
|
generation: string;
|
|
110
|
+
captureDrain?: () => (() => void) | undefined;
|
|
109
111
|
observeEvent?: (event: TelegramActivityEvent) => void;
|
|
110
112
|
recordFailure?: (handlerId: string, event: TelegramActivityEvent, error: unknown) => void;
|
|
111
113
|
now?: () => number;
|
|
@@ -207,7 +209,9 @@ export interface TelegramActivityPublicationRuntime {
|
|
|
207
209
|
hasUnconfirmed: () => boolean;
|
|
208
210
|
reset: () => void;
|
|
209
211
|
}
|
|
210
|
-
export declare function createTelegramActivityPublicationRuntime(
|
|
212
|
+
export declare function createTelegramActivityPublicationRuntime(deps?: {
|
|
213
|
+
captureDrain?: () => (() => void) | undefined;
|
|
214
|
+
}): TelegramActivityPublicationRuntime;
|
|
211
215
|
export interface TelegramAssistantOutputRuntime {
|
|
212
216
|
start: () => void;
|
|
213
217
|
beginTurn: () => void;
|