@llblab/pi-kit 0.27.0 → 0.27.2
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 +5 -1
- package/CHANGELOG.md +8 -0
- package/README.md +5 -5
- package/node_modules/@llblab/pi-claude-usage/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +3 -1
- package/node_modules/@llblab/pi-claude-usage/lib/status.ts +3 -8
- package/node_modules/@llblab/pi-claude-usage/lib/telegram.ts +15 -1
- package/node_modules/@llblab/pi-claude-usage/package.json +1 -1
- package/node_modules/@llblab/pi-codex-usage/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +2 -2
- package/node_modules/@llblab/pi-codex-usage/lib/status.ts +3 -11
- package/node_modules/@llblab/pi-codex-usage/lib/telegram.ts +15 -0
- package/node_modules/@llblab/pi-codex-usage/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +16 -20
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +6 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +4 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +7 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +3 -1
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +4 -3
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +3 -1
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +9 -2
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +6 -2
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +1 -1
- package/package.json +4 -4
package/BACKLOG.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# Backlog
|
|
2
2
|
|
|
3
|
-
The 0.27.
|
|
3
|
+
The 0.27.2 composition is recorded in [CHANGELOG.md](./CHANGELOG.md). Package pins, resource order and bundled runtime ownership remain authoritative in `package.json`.
|
|
4
4
|
|
|
5
5
|
## Carried checks
|
|
6
6
|
|
|
7
|
+
- **Installed 0.27.2 cascade receipt smoke (operator-owned):** After separately authorized installation/reload, close an intent owning a nested lazy key in disposable State Flow storage and confirm the receipt lists its owner path under `cascaded`, without the body. Packed validation does not certify installed clients.
|
|
8
|
+
|
|
9
|
+
- **Installed 0.27.1 Fast status smoke (operator-owned):** After separately authorized installation/reload, check Telegram menu Fast on/off and model switching for Codex and Claude Opus; unsupported Claude families must not show Fast. Use disposable settings and mocked quota where possible; do not mutate live stores or reconnect Telegram without separate authorization.
|
|
10
|
+
|
|
7
11
|
- **Installed 0.27.0 smoke (operator-owned):** After separately authorized installation/reload, check the exact released kit's terminal controls and optional Telegram rendering with disposable State Flow storage. Packed SDK validation does not certify the operator's running clients. Do not reconnect Telegram, change Pi settings or use live memory/usage/Recipe stores as fixtures without separate authorization.
|
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.27.2: State Flow Cascade Receipts
|
|
6
|
+
|
|
7
|
+
- `Cascade Receipts`: Advances the exact State Flow pin to published `0.25.1`. Accepted receipts now list every intent-cascaded owner path, including nested lazy keys, without exposing bodies or eliding the list as predictable. The lifecycle-state ceiling also counts const-bound operation slots and lifetimes. Other pins, resources, load order and storage semantics are unchanged.
|
|
8
|
+
|
|
9
|
+
## 0.27.1: Telegram Fast Status
|
|
10
|
+
|
|
11
|
+
- `Telegram Fast Status`: Advances Codex Usage to `0.12.1` and Claude Usage to `0.2.1`. Their Telegram rows now show the active model's Fast preference alongside quota, or alone when quota is unavailable, and reread it at menu render time. Claude retains Opus-only eligibility. Other pins, resource order and quota coordination are unchanged.
|
|
12
|
+
|
|
5
13
|
## 0.27.0: Intent-Owned State Flow Memory
|
|
6
14
|
|
|
7
15
|
- `Intent-Owned Memory`: Advances the exact State Flow pin to published `0.25.0`. Deleting an intent removes the same-scope `working`/`lazy` keys it owns through structured `$ref`, so agents that work from intents keep memory self-cleaning. `/state-flow-status` shows per-scope plane sizes and intent-owned shares; lifecycle internals were consolidated without behaviour changes.
|
package/README.md
CHANGED
|
@@ -13,11 +13,11 @@ 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.54.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.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.12.
|
|
18
|
+
| [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.12.1` | Shared Codex quota/Business credit status and 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
|
-
| [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.25.
|
|
20
|
+
| [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.25.1` | Scoped context/memory compiler with intent-owned self-cleaning memory, memory-inert Off and ownership status |
|
|
21
21
|
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.51.6` | Telegram companion with connection resume, Workspace slot recovery, follower Threads, filterable Skills, 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
|
|
|
@@ -25,7 +25,7 @@ Versions are exact by design. An upstream release does not change an installed k
|
|
|
25
25
|
|
|
26
26
|
## Install
|
|
27
27
|
|
|
28
|
-
Requires **Pi 1.0.0+** and **Node.js 22.19.0+**. State Flow requires its canonical checkpoint/tail storage format and does not convert unsupported stores in place. Preserve existing stores and consult the [owning package's storage guidance](https://github.com/llblab/pi-state-flow/blob/v0.25.
|
|
28
|
+
Requires **Pi 1.0.0+** and **Node.js 22.19.0+**. State Flow requires its canonical checkpoint/tail storage format and does not convert unsupported stores in place. Preserve existing stores and consult the [owning package's storage guidance](https://github.com/llblab/pi-state-flow/blob/v0.25.1/docs/usage.md#moving-a-store-and-supported-formats) before changing installations.
|
|
29
29
|
|
|
30
30
|
From npm:
|
|
31
31
|
|
|
@@ -45,7 +45,7 @@ Prefer the kit instead of separately loading the same packages. If you already u
|
|
|
45
45
|
|
|
46
46
|
## Development
|
|
47
47
|
|
|
48
|
-
The `0.27.
|
|
48
|
+
The `0.27.2` composition includes published State Flow `0.25.1` with complete cascade receipts, Codex Usage `0.12.1` and Claude Usage `0.2.1` with Telegram Fast status, alongside Telegram `0.51.6` and the Pi 1.0 cohort. The packed bundle loads all seven extensions on Pi 1.0.0 with no credentials or external requests. This does not certify installed-client rendering; carried checks remain in [Backlog](./BACKLOG.md).
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
npm install
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- `Pi baseline`: Every declared `@earendil-works/*` peer requires ≥1.0.0. Keep Pi peer lock identities aligned and validate against that host generation.
|
|
5
5
|
- `Statusline-first scope`: Own usage state + usage mode; keep quota reporting zero-configuration and optional Fast on the existing terminal status.
|
|
6
6
|
- Trigger: Considering commands, menus, persisted settings, or notification output.
|
|
7
|
-
- Action: Prefer deleting the surface unless it is required for the optimistic TUI status widget, optional `pi-telegram` `/start` status-line mirror, or the argument-free shared `/fast`. Register only the `anthropic` provider handler through `@llblab/pi-command-fast` on session_start; release on session_shutdown. Never register the command directly or gate it on quota auth. Claude Fast eligibility is consumer-owned: permit the `claude-opus-` family without a version allowlist; warn `Fast mode is supported only for Opus` for other families without writes. Family eligibility is not proof of backend support; preserve server capability/billing errors. For rejected models, ignore stale unsupported overrides in status/request adaptation, and leave Codex eligibility unchanged. The library owns session WeakMap/reload arbitration and generic JSONC; `lib/fast.ts` owns Claude semantics; require Pi ≥1.0.0 for its assembled-beta payload contract, rather than copying upstream beta defaults. ON is `speed: "fast"` in the current model override; OFF deletes the property. Preserve existing request speed/betas, append the required Fast beta to the native assembled list rather than replacing a header, and keep Fast out of quota state
|
|
7
|
+
- Action: Prefer deleting the surface unless it is required for the optimistic TUI status widget, optional `pi-telegram` `/start` status-line mirror, or the argument-free shared `/fast`. Register only the `anthropic` provider handler through `@llblab/pi-command-fast` on session_start; release on session_shutdown. Never register the command directly or gate it on quota auth. Claude Fast eligibility is consumer-owned: permit the `claude-opus-` family without a version allowlist; warn `Fast mode is supported only for Opus` for other families without writes. Family eligibility is not proof of backend support; preserve server capability/billing errors. For rejected models, ignore stale unsupported overrides in status/request adaptation, and leave Codex eligibility unchanged. The library owns session WeakMap/reload arbitration and generic JSONC; `lib/fast.ts` owns Claude semantics; require Pi ≥1.0.0 for its assembled-beta payload contract, rather than copying upstream beta defaults. ON is `speed: "fast"` in the current model override; OFF deletes the property. Preserve existing request speed/betas, append the required Fast beta to the native assembled list rather than replacing a header, and keep Fast out of quota state. The optional Telegram row reads the active model's eligible Fast preference at render time and appends plain-text ` fast` (or shows `fast` alone without quota). Toggle redraws through the final terminal boundary without a quota request or success notification; unreadable config fails closed for Fast only.
|
|
8
8
|
- `Domain boundaries`: `index.ts` is export-only; `lib/extension.ts` composes lifecycle/Fast registration and request hooks. `lib/status.ts` owns refresh orchestration and terminal timers; `usage-store.ts` owns claims/fencing/mutex, `query.ts` owns OAuth/HTTP, `usage.ts` owns quota normalization, `status-format.ts` owns presentation, and `telegram.ts` owns optional registration. Preserve mature quota/auth/leadership behavior, keep imports acyclic with no domain importing the entrypoint, and keep domain-focused tests in `tests/`. Do not merge usage extensions or redesign polling to add Fast.
|
|
9
9
|
- `Optimistic refresh`: Preserve the last good statusline bar during refresh and transient failures.
|
|
10
10
|
- Trigger: Updating quota polling or error handling.
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1: Telegram Fast Status
|
|
4
|
+
|
|
5
|
+
- The optional Telegram status row now appends lowercase ` fast` for the active eligible Opus model, or shows `fast` alone when quota is unavailable. It rereads the per-model preference at menu render time, so toggles and model changes do not wait for quota refresh. Unsupported Claude families ignore stale Fast overrides. Quota polling, auth, request adaptation and terminal behavior are unchanged; isolated tests cover eligibility, model/provider changes and unreadable configuration.
|
|
6
|
+
|
|
3
7
|
## 0.2.0: Shared Fast Mode and Pi 1.0 Baseline
|
|
4
8
|
|
|
5
9
|
- Split the monolithic extension into cohesive `lib/` domains matching Codex Usage: composition, status lifecycle, shared state, OAuth query, quota normalization, formatting, Telegram and Fast. `index.ts` now only re-exports the unchanged public API; tests follow domain owners. Quota/auth/leadership, mutex/fencing/backoff, status redraw and Fast behavior are preserved.
|
|
@@ -78,7 +78,7 @@ When Claude Usage and Codex Usage are loaded together, their shared library regi
|
|
|
78
78
|
claude ██████▀▀▀▀ 6d fast
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
Exactly lowercase ` fast` uses the existing dim/countdown role at the final terminal presentation boundary, including loading, single-window percentages, `n/a` and errors. Telegram
|
|
81
|
+
Exactly lowercase ` fast` uses the existing dim/countdown role at the final terminal presentation boundary, including loading, single-window percentages, `n/a` and errors. Telegram also appends plain-text ` fast` for the active eligible model; quota OAuth, polling, mutex, fencing and backoff are unchanged.
|
|
82
82
|
|
|
83
83
|
Native request adaptation adds `speed: "fast"` without overwriting an explicit speed. It adds `fast-mode-2026-02-01` to Pi's already-assembled request `betas`, preserving automatic OAuth/thinking/streaming and configured betas; the Anthropic SDK converts that list to the final `anthropic-beta` HTTP header. Setting a replacement header earlier would suppress Pi's automatic betas, so no header replacement or custom provider/transport is used. See [Anthropic Fast mode](https://platform.claude.com/docs/en/build-with-claude/fast-mode).
|
|
84
84
|
|
|
@@ -144,6 +144,8 @@ If `@llblab/pi-telegram` is loaded with the public status-line provider API, thi
|
|
|
144
144
|
claude: ██████▀▀▀▀ 6d
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
+
When Fast is enabled for the active Opus model, the row appends plain-text ` fast`; without a usable quota report it shows `claude: fast`. The preference is reread when the menu is rendered, so toggles and model changes do not depend on a quota refresh. Unsupported Claude families never show Fast, even with stale overrides.
|
|
148
|
+
|
|
147
149
|
If `pi-telegram` is absent or older, or the active model is not Anthropic, no Telegram row is added.
|
|
148
150
|
|
|
149
151
|
## Auth
|
|
@@ -4,9 +4,9 @@ import { type ExtensionAPI, type ExtensionContext, getAgentDir } from "@earendil
|
|
|
4
4
|
import { isFastEnabled } from "./fast.ts";
|
|
5
5
|
import { claimRefresh, isRefreshDue, nextRefreshAt, ownsRefreshClaim, publishRefresh, readState, MIN_ATTEMPT_GAP_MS, type SharedState } from "./usage-store.ts";
|
|
6
6
|
import { isAnthropicModel, type ClaudeUsageModel, type ClaudeUsageReport } from "./usage.ts";
|
|
7
|
-
import { DUAL_BAR_WIDTH,
|
|
7
|
+
import { DUAL_BAR_WIDTH, formatClaudeUsageBar, formatClaudeUsageStatusline, formatStatuslineLoading, formatStatuslineProblem, nextResetCountdownDelayMs } from "./status-format.ts";
|
|
8
8
|
import { queryUsage } from "./query.ts";
|
|
9
|
-
import { registerClaudeUsageTelegramStatusLine } from "./telegram.ts";
|
|
9
|
+
import { claudeUsageTelegramStatusLine, registerClaudeUsageTelegramStatusLine } from "./telegram.ts";
|
|
10
10
|
|
|
11
11
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
12
12
|
const SECOND_MS = 1000;
|
|
@@ -57,12 +57,7 @@ export function createClaudeUsageStatus(pi: ExtensionAPI) {
|
|
|
57
57
|
const ensureTelegramStatusLineRegistered = () => {
|
|
58
58
|
if (unregisterTelegramStatusLine || telegramStatusLineRegistration) return;
|
|
59
59
|
telegramStatusLineRegistration = registerClaudeUsageTelegramStatusLine(
|
|
60
|
-
({ activeModel }) =>
|
|
61
|
-
if (!isAnthropicModel(activeModel)) return undefined;
|
|
62
|
-
if (!shown.report) return undefined;
|
|
63
|
-
const value = formatClaudeUsageStatusValue(shown.report);
|
|
64
|
-
return value ? { label: STATUS_LABEL_TEXT, value } : undefined;
|
|
65
|
-
},
|
|
60
|
+
({ activeModel }) => claudeUsageTelegramStatusLine(shown.report, activeModel),
|
|
66
61
|
)
|
|
67
62
|
.then((unregister) => {
|
|
68
63
|
unregisterTelegramStatusLine = unregister;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Domain: Telegram status. Owns: optional status-provider discovery and registration. Excludes: quota state and terminal decoration. */
|
|
2
|
-
import
|
|
2
|
+
import { isFastEnabled } from "./fast.ts";
|
|
3
|
+
import { isAnthropicModel, type ClaudeUsageModel, type ClaudeUsageReport } from "./usage.ts";
|
|
4
|
+
import { formatClaudeUsageStatusValue, STATUS_LABEL_TEXT } from "./status-format.ts";
|
|
3
5
|
const CLAUDE_USAGE_EXTENSION_ID = "@llblab/pi-claude-usage";
|
|
4
6
|
const TELEGRAM_STATUS_IMPORT_SPECIFIERS = [
|
|
5
7
|
"@llblab/pi-telegram/status",
|
|
@@ -18,6 +20,18 @@ type TelegramStatusLineModule = {
|
|
|
18
20
|
) => () => void;
|
|
19
21
|
};
|
|
20
22
|
|
|
23
|
+
/** Read the selected model's preference at menu render time, not from the terminal cache. */
|
|
24
|
+
export function claudeUsageTelegramStatusLine(
|
|
25
|
+
report: ClaudeUsageReport | undefined,
|
|
26
|
+
activeModel: ClaudeUsageModel | undefined,
|
|
27
|
+
): TelegramStatusLineProviderResult {
|
|
28
|
+
if (!activeModel || !isAnthropicModel(activeModel)) return undefined;
|
|
29
|
+
const value = report ? formatClaudeUsageStatusValue(report) : undefined;
|
|
30
|
+
const fast = isFastEnabled(activeModel.id);
|
|
31
|
+
if (!value && !fast) return undefined;
|
|
32
|
+
return { label: STATUS_LABEL_TEXT, value: value ? `${value}${fast ? " fast" : ""}` : "fast" };
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
async function importTelegramStatusLineModule(): Promise<
|
|
22
36
|
TelegramStatusLineModule | undefined
|
|
23
37
|
> {
|
|
@@ -5,7 +5,7 @@
|
|
|
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 the `openai-codex` provider handler through `registerFastProvider` on session_start and release on session_shutdown; never register `/fast` directly or filter Fast by model ID/auth. Fast persists solely as `serviceTier: "priority"` in the current model override; OFF deletes only that property. Preserve JSONC/unrelated settings, existing wire tiers, and usage refresh/Telegram. Apply lowercase ` fast` through the final terminal boundary;
|
|
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 the `openai-codex` provider handler through `registerFastProvider` on session_start and release on session_shutdown; never register `/fast` directly or filter Fast by model ID/auth. Fast persists solely as `serviceTier: "priority"` in the current model override; 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
9
|
- `Optimistic refresh`: Preserve the last good statusline bar during refresh and transient failures.
|
|
10
10
|
- Trigger: Updating quota polling or error handling.
|
|
11
11
|
- 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.12.1: Telegram Fast Status
|
|
4
|
+
|
|
5
|
+
- 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.
|
|
6
|
+
|
|
3
7
|
## 0.12.0: Shared Fast Mode and Pi 1.0 Baseline
|
|
4
8
|
|
|
5
9
|
- Requires Pi ≥1.0.0 across the coding-agent, AI and agent-core peers; previous hosts are outside this release's compatibility contract.
|
|
@@ -83,7 +83,7 @@ Enabled native requests receive `service_tier: "priority"` only when the payload
|
|
|
83
83
|
codex ██████▀▀▀▀ 6d fast
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
-
The lowercase ` fast` suffix uses the existing dim/countdown theme role and is applied at the final terminal boundary, including loading, percentages/credits, `n/a`, and errors. Telegram
|
|
86
|
+
The lowercase ` fast` suffix uses the existing dim/countdown theme role and is applied at the final terminal boundary, including loading, percentages/credits, `n/a`, and errors. Telegram also appends plain-text ` fast` for the active model; quota polling/auth/leadership are unchanged.
|
|
87
87
|
|
|
88
88
|
Pi 1.0.0 accepts the extra override but does not propagate it to native request options, so a small `before_provider_request` adapter remains necessary; no replacement provider or transport is registered. Its public command API cannot hide/unregister commands by current model, so `/fast` stays listed and checks the provider at invocation. Backend capability and actual priority service are not guaranteed by a stored preference or suffix: an earlier authorized sample sent `priority` but received `default`. Priority service may have different provider pricing.
|
|
89
89
|
|
|
@@ -161,7 +161,7 @@ If `@llblab/pi-telegram` is loaded with the public status-line provider API, thi
|
|
|
161
161
|
codex: ██████▀▀▀▀ 6d
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
-
The value is the same compact quota bar plus weekly reset countdown used by the terminal statusline, always with the `codex` label. If `pi-telegram` is absent, older, or the active model is not a Codex subscription model, no Telegram row is added.
|
|
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 is not a Codex subscription model, no Telegram row is added.
|
|
165
165
|
|
|
166
166
|
## Auth
|
|
167
167
|
|
|
@@ -4,11 +4,10 @@ import { type ExtensionAPI, type ExtensionContext, getAgentDir } from "@earendil
|
|
|
4
4
|
import { isFastEnabled, isFastEligibleModel } from "./fast.ts";
|
|
5
5
|
import { claimRefresh, isRefreshDue, nextRefreshAt, ownsRefreshClaim, publishRefresh, readState, type SharedState, MIN_ATTEMPT_GAP_MS } from "./usage-store.ts";
|
|
6
6
|
import { canReuseCachedReport, isFullyAvailableReport, isOpenAICodexModel, isUsageUnavailable, type CodexUsageModel, type CodexUsageReport } from "./usage.ts";
|
|
7
|
-
import { appendFastStatus, formatReportBar, formatStatuslineLoading, formatStatuslineProblem,
|
|
7
|
+
import { appendFastStatus, formatReportBar, formatStatuslineLoading, formatStatuslineProblem, formatCodexUsageStatusline, nextResetCountdownDelayMs } from "./status-format.ts";
|
|
8
8
|
import { queryUsage, type QueryUsageResult } from "./query.ts";
|
|
9
|
-
import { registerCodexUsageTelegramStatusLine } from "./telegram.ts";
|
|
9
|
+
import { codexUsageTelegramStatusLine, registerCodexUsageTelegramStatusLine } from "./telegram.ts";
|
|
10
10
|
|
|
11
|
-
const DEFAULT_STATUS_LABEL_TEXT = "codex";
|
|
12
11
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
13
12
|
const SECOND_MS = 1000;
|
|
14
13
|
const MINUTE_MS = 60 * SECOND_MS;
|
|
@@ -64,14 +63,7 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
|
|
|
64
63
|
const ensureTelegramStatusLineRegistered = () => {
|
|
65
64
|
if (unregisterTelegramStatusLine || telegramStatusLineRegistration) return;
|
|
66
65
|
telegramStatusLineRegistration = registerCodexUsageTelegramStatusLine(
|
|
67
|
-
({ activeModel }) =>
|
|
68
|
-
if (!isOpenAICodexModel(activeModel)) return undefined;
|
|
69
|
-
if (!shown.report) return undefined;
|
|
70
|
-
const value = formatCodexUsageStatusValue(shown.report, activeModel);
|
|
71
|
-
return value
|
|
72
|
-
? { label: DEFAULT_STATUS_LABEL_TEXT, value }
|
|
73
|
-
: undefined;
|
|
74
|
-
},
|
|
66
|
+
({ activeModel }) => codexUsageTelegramStatusLine(shown.report, activeModel),
|
|
75
67
|
)
|
|
76
68
|
.then((unregister) => {
|
|
77
69
|
unregisterTelegramStatusLine = unregister;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** Domain: Telegram adapter. Owns: optional provider registration. Excludes: quota state and terminal formatting. */
|
|
2
2
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { isFastEnabled } from "./fast.ts";
|
|
4
|
+
import { isOpenAICodexModel, type CodexUsageReport } from "./usage.ts";
|
|
5
|
+
import { formatCodexUsageStatusValue } from "./status-format.ts";
|
|
3
6
|
const CODEX_USAGE_EXTENSION_ID = "@llblab/pi-codex-usage";
|
|
4
7
|
const TELEGRAM_STATUS_IMPORT_SPECIFIERS = [
|
|
5
8
|
"@llblab/pi-telegram/status",
|
|
@@ -19,6 +22,18 @@ type TelegramStatusLineModule = {
|
|
|
19
22
|
) => () => void;
|
|
20
23
|
};
|
|
21
24
|
|
|
25
|
+
/** Read the selected model's preference at menu render time, not from the terminal cache. */
|
|
26
|
+
export function codexUsageTelegramStatusLine(
|
|
27
|
+
report: CodexUsageReport | undefined,
|
|
28
|
+
activeModel: CodexUsageTelegramStatusModel | undefined,
|
|
29
|
+
): TelegramStatusLineProviderResult {
|
|
30
|
+
if (!activeModel || !isOpenAICodexModel(activeModel)) return undefined;
|
|
31
|
+
const value = report ? formatCodexUsageStatusValue(report, activeModel) : undefined;
|
|
32
|
+
const fast = isFastEnabled(activeModel.id);
|
|
33
|
+
if (!value && !fast) return undefined;
|
|
34
|
+
return { label: "codex", value: value ? `${value}${fast ? " fast" : ""}` : "fast" };
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
async function importTelegramStatusLineModule(): Promise<
|
|
23
38
|
TelegramStatusLineModule | undefined
|
|
24
39
|
> {
|
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
# Backlog
|
|
2
2
|
|
|
3
|
-
The **0.25.
|
|
3
|
+
The **0.25.1: Cascade Receipts** scope is implemented; outcomes belong in [CHANGELOG.md](CHANGELOG.md). This backlog retains release and installed-client gates and deferred decisions. Cascade semantics, canonical storage, stored patch records and lifecycle behaviour remain unchanged.
|
|
4
|
+
|
|
5
|
+
## Out of scope
|
|
6
|
+
|
|
7
|
+
- Changing what a cascade deletes, including writes into an owned target made by the closing patch. That stays deleted by design and already appears in the receipt.
|
|
8
|
+
- Nested `lazy_navigation`, warnings, validation or rejection of any kind.
|
|
9
|
+
- Any reduction of lifecycle state; the tagged-union question stays deferred.
|
|
4
10
|
|
|
5
11
|
## Carried gates
|
|
6
12
|
|
|
7
|
-
- **0.25.
|
|
8
|
-
- **Installed 0.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- **Installed 0.23.0 smoke (operator-owned):** After separately authorized installation/reload, confirm an unconfigured *new* session is Off without semantic writes, retained choices and explicit global modes survive, and Telegram shows one `Off | Passive | Active` radio row with the selected 🟡/🟣/🟢 marker and ⚫️ inactive markers, followed by four direct scope inspections. Isolate test storage; do not use the live store as a fixture. SDK tests alone do not certify installed-client rendering.
|
|
15
|
-
- **Installed 0.24.0 smoke (approval/operator-owned):** Local lifecycle, cancellation, background-work and callback/inspection acceptance is complete; real-client behavior remains separate evidence. After separately authorized installation/reload of the exact 0.24.0 release, use a disposable store to check Off attachment and pending-work cancellation, no late memory warnings, current read-only inspections without private placeholders, preserved deferred Passive/Active/fork acquisition, and unchanged terminal/Telegram mode controls. Do not use the live store, treat its reload as evidence for older published releases, or change unrelated operator sessions.
|
|
16
|
-
|
|
17
|
-
## Deferred beyond 0.25.0 (decision inputs, not commitments)
|
|
18
|
-
|
|
19
|
-
- **Convention uptake:** After some real use, read the `/state-flow-status` ownership shares; if most `working`/`lazy` entries stay unowned, revisit the protocol wording, not the mechanism.
|
|
20
|
-
- **Behavioural evaluation:** Live-model comparison of Active against native compaction on one long cyclic task. Needs a policy decision first; current benchmarks are synthetic and make no model calls.
|
|
21
|
-
- **Lifecycle real-use measurement (gated):** The operator install runs with `logging: true` and has no recorded `publication-conflict`, `finalization` or `barrier-block` entries; cooperating-writer waits, Stop fences and fork/restore contention are not instrumented. Removing any awaited layer for rarity first needs a decision to add opt-in counters.
|
|
22
|
-
- **Lifecycle state as one tagged union:** The 0.25.0 lifecycle review left 17 closure bindings in `lib/extension.ts`, each a justified selection, run or host fact. Remaining candidates: the seven branch-selection facts (`snapshot`, `runtime`, `branchStartsWithoutRuntime`, `selectedHistoryExpired`, `modePersistenceError`, `forkInitialization`, `deferredBranch`) as one Off-deferred/attached union, and `passiveContinuation`/`bootstrapContinuation` as one continuation slot once their exclusivity is proven. Decide only with a behavioural reason; no further mechanical moves are pending.
|
|
23
|
-
- **Skill compilation fidelity:** Hashes detect source change, not a lossy first compilation. Measure before adding anything.
|
|
24
|
-
- **Compaction threshold:** `STATE_FLOW_COMPACTION_MIN_CONTEXT_TOKENS = 24_000` is documented as a margin above Pi's default 20,000-token retained suffix. Make it configurable only if a real workload or non-default Pi retention settings demonstrate a mismatch.
|
|
13
|
+
- **Installed 0.25.1 smoke (operator-owned).** Disposable store: close an intent that owns a nested lazy key and confirm the receipt lists it under `cascaded`. May be combined with the open 0.25.0 smoke.
|
|
14
|
+
- **Installed 0.22.0, 0.23.0, 0.24.0 and 0.25.0 smokes** remain open as recorded.
|
|
15
|
+
|
|
16
|
+
## Deferred (decision inputs, not commitments)
|
|
17
|
+
|
|
18
|
+
- **Closing-patch losses.** In real sessions, look for closing patches that write into a target the same patch cascades. If frequent, revisit the closing sentence in the protocol, not the mechanism.
|
|
19
|
+
- Convention uptake, behavioural evaluation, lifecycle real-use measurement, lifecycle tagged union, Skill compilation fidelity and compaction threshold carry over unchanged from the 0.25.0 backlog.
|
|
25
20
|
|
|
26
21
|
## Release boundary
|
|
27
22
|
|
|
28
|
-
|
|
23
|
+
- **Publication (approval-gated).** After explicit authorization, commit the prepared 0.25.1 package, lockfile, changelog and rebuilt `dist/`, tag the exact commit `v0.25.1`, and push. Verify the exact-tag release workflow, non-draft GitHub Release and matching npm version/commit before closing this gate. Local readiness requires `npm run validate` and the context validator; it does not certify installed clients.
|
|
24
|
+
- **Installation/reload (operator-owned).** Requires separate authorization and disposable storage. Grow Loop preparation does not cross publication or live-client gates.
|
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
> Each release keeps at most 8 outcome records of at most 512 characters.
|
|
4
4
|
|
|
5
|
+
## 0.25.1: Cascade Receipts
|
|
6
|
+
|
|
7
|
+
- `Composition ceiling`: One invariant now counts mutable closure bindings plus `OwnedOperationSlot` and `RenewableLifetime` instances in `lib/extension.ts`, including const-bound holders, against the current total of 24 (17 + 5 + 2). Existing named owner assertions remain. Mutation checks reject an added binding, operation slot or lifetime; lifecycle source and behavior are unchanged.
|
|
8
|
+
- `Cascade receipts`: `state_updates.cascaded` lists every owner-scoped target removed by intent deletion, including nested lazy keys invisible to effective updates and top-level navigation. Paths only, never bodies; deterministic across scopes and never elided as predictable. Staging passes its existing cascade to the context projection without recomputing ownership or changing stored records. Native Active/Passive tests verify each scope, and the always-injected protocol did not grow.
|
|
9
|
+
|
|
5
10
|
## 0.25.0: Intent-Owned Memory
|
|
6
11
|
|
|
7
|
-
- `Intent-owned memory`: Deleting an intent
|
|
12
|
+
- `Intent-owned memory`: Deleting an intent deletes same-scope `working`/`lazy` keys its structured `{"$ref"}` values own, after authored operations in one atomic cohort/revision, unless a surviving intent references the target, an ancestor or descendant. Other targets and textual `$path` mentions are skipped silently; no rejection, warning or archive. Records store explicit deletions; receipts report effective changes and top-level lazy navigation, but omit nested lazy targets (fixed in 0.25.1).
|
|
8
13
|
- `Work from intents`: Protocol, `patch_state` description, both Skills, README and docs present `intents` as the queue of chosen actions and `working` as their temporary context. Structured refs inside intents own; textual `$path` mentions only use. Before closing an intent, save survivors to unowned paths, with abandonment reasons in `contract`. Unowned entries stay legal. The always-injected protocol did not grow; duplicated read-path and barrier wording now lives only in the tool definitions.
|
|
9
14
|
- `Ownership status`: `/state-flow-status` adds a per-scope `Scope memory:` block with UTF-8 sizes of present planes and the share of top-level `working`/`lazy` entries owned by an open intent. Operator-only; no notices, thresholds or model-facing effects.
|
|
10
15
|
- `Composition root step one`: Completed-history compaction request state moves into `StateFlowCompactionRequests` and settled-turn backup/push state into `SettledTurnBackup`, cutting `lib/extension.ts` mutable closure bindings from 34 to 26 and adding an invariant ceiling. No behaviour change.
|
|
@@ -3,8 +3,9 @@ import { type ArtifactInvalidationNotice, type ArtifactModelHints } from "./arti
|
|
|
3
3
|
import type { RecentTransitionWindow } from "./history.ts";
|
|
4
4
|
import { type JsonValue } from "./json.ts";
|
|
5
5
|
import type { Snapshot } from "./snapshot.ts";
|
|
6
|
+
import { type OwnedPath } from "./ownership.ts";
|
|
6
7
|
import type { RehydrationPhase } from "./rehydration.ts";
|
|
7
|
-
import { type AtomicScopePatches, type SemanticState } from "./state.ts";
|
|
8
|
+
import { type AtomicScopePatches, type SemanticState, type StateScope } from "./state.ts";
|
|
8
9
|
/** Refresh only our section; Pi owns system frames, tools and forced-prompt precedence. */
|
|
9
10
|
export declare function projectSystemProtocol(messages: AgentMessage[], protocol: string | undefined): AgentMessage[];
|
|
10
11
|
type LazyValueKind = "array" | "boolean" | "null" | "number" | "object" | "string";
|
|
@@ -48,7 +49,7 @@ export declare class ContextProjection {
|
|
|
48
49
|
private notices;
|
|
49
50
|
reset(): void;
|
|
50
51
|
/** Called only after successful publication and ancillary acceptance, immediately before returning the native result. */
|
|
51
|
-
acceptPatch(before: SemanticState, after: SemanticState, patches: AtomicScopePatches, hints: ArtifactModelHints): {
|
|
52
|
+
acceptPatch(before: SemanticState, after: SemanticState, patches: AtomicScopePatches, hints: ArtifactModelHints, cascades?: Partial<Record<StateScope, readonly OwnedPath[]>>): {
|
|
52
53
|
effective: ModelStateUpdate[];
|
|
53
54
|
lazy_navigation?: {
|
|
54
55
|
available: boolean;
|
|
@@ -56,6 +57,7 @@ export declare class ContextProjection {
|
|
|
56
57
|
keys?: Record<string, LazyValueKind>;
|
|
57
58
|
} | undefined;
|
|
58
59
|
projection: `${string}-${string}-${string}-${string}-${string}`;
|
|
60
|
+
cascaded?: string[] | undefined;
|
|
59
61
|
} | undefined;
|
|
60
62
|
project(messages: AgentMessage[], current: ContextView, makeHead: () => AgentMessage, initial?: ContextView): AgentMessage[];
|
|
61
63
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { projectArtifactForModel } from "./artifact.js";
|
|
3
3
|
import { applyPatch, isObject, presentationJson, sameJson } from "./json.js";
|
|
4
|
+
import { formatOwnedPath } from "./ownership.js";
|
|
4
5
|
import { projectSemanticPatch, projectStateForModel } from "./state.js";
|
|
5
6
|
/** Refresh only our section; Pi owns system frames, tools and forced-prompt precedence. */
|
|
6
7
|
export function projectSystemProtocol(messages, protocol) {
|
|
@@ -144,7 +145,7 @@ export class ContextProjection {
|
|
|
144
145
|
this.notices = [];
|
|
145
146
|
}
|
|
146
147
|
/** Called only after successful publication and ancillary acceptance, immediately before returning the native result. */
|
|
147
|
-
acceptPatch(before, after, patches, hints) {
|
|
148
|
+
acceptPatch(before, after, patches, hints, cascades = {}) {
|
|
148
149
|
const state = projectStateForModel(after, hints);
|
|
149
150
|
const navigation = lazyNavigationHint(after);
|
|
150
151
|
const beforeNavigation = this.view?.lazy_navigation ?? lazyNavigationHint(before);
|
|
@@ -252,7 +253,11 @@ export class ContextProjection {
|
|
|
252
253
|
}
|
|
253
254
|
if (this.view)
|
|
254
255
|
this.view = { ...this.view, state, lazy_navigation: navigation };
|
|
255
|
-
|
|
256
|
+
// Owner paths remain informative even when effective state/navigation is masked
|
|
257
|
+
// or the model could predict every direct write. Never expose target values.
|
|
258
|
+
const cascaded = ["global", "cwd", "session"].flatMap((scope) => (cascades[scope] ?? []).map((path) => formatOwnedPath(scope, path)));
|
|
259
|
+
return updates.effective.length || updates.lazy_navigation || cascaded.length
|
|
260
|
+
? { projection: this.identity, ...updates, ...(cascaded.length ? { cascaded } : {}) } : undefined;
|
|
256
261
|
}
|
|
257
262
|
project(messages, current, makeHead, initial) {
|
|
258
263
|
const identities = messages.map((message) => JSON.stringify([message.role, message.timestamp,
|
|
@@ -801,7 +801,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
801
801
|
}
|
|
802
802
|
clearAcceptedAcquisitions(new Set(acquiredArtifacts.map(({ path }) => path)));
|
|
803
803
|
updateUi(ctx);
|
|
804
|
-
const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, acquisition.hints);
|
|
804
|
+
const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, acquisition.hints, stage.cascades);
|
|
805
805
|
const acknowledgement = changed
|
|
806
806
|
? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
|
|
807
807
|
: "\nState already current.";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
2
2
|
export type { StateDocument } from "./state.ts";
|
|
3
|
-
export declare const PASSIVE_MEMORY_PROTOCOL = "State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns never trigger State Flow continuation or compaction. Missing paths/hints do not require history search. Choose targeted historical reads when useful to the task; no separate user permission is needed. Past values are evidence, not current state; never automatically restore deleted memory. Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply;
|
|
3
|
+
export declare const PASSIVE_MEMORY_PROTOCOL = "State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns never trigger State Flow continuation or compaction. Missing paths/hints do not require history search. Choose targeted historical reads when useful to the task; no separate user permission is needed. Past values are evidence, not current state; never automatically restore deleted memory. Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply; other IDs are history. Results/context notices carry state_updates: effective entries replace values at key/index-segment path arrays (deleted:true means absent); cascaded lists owner paths an intent deletion removed. Direct writes may elide; shared drift stays visible. Latest entries win over earlier state; lazy bodies stay omitted. Notices replace invalidations/rehydration, including []/null.";
|
|
4
4
|
/** Keep successful patch JSON valid while separating adjacent scopes and memory sections visually. */
|
|
5
5
|
export declare function formatPatchStateArguments(args: unknown): string;
|
|
6
6
|
/** Flatten causes before transport; native tool results need not retain Error.cause or AggregateError.errors. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const TASK_DRIVEN_HISTORY = "Missing paths/hints do not require history search. Choose targeted historical reads when useful to the task; no separate user permission is needed. Past values are evidence, not current state; never automatically restore deleted memory.";
|
|
2
|
-
const PATCH_RESULT_PROTOCOL = "Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply;
|
|
2
|
+
const PATCH_RESULT_PROTOCOL = "Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply; other IDs are history. Results/context notices carry state_updates: effective entries replace values at key/index-segment path arrays (deleted:true means absent); cascaded lists owner paths an intent deletion removed. Direct writes may elide; shared drift stays visible. Latest entries win over earlier state; lazy bodies stay omitted. Notices replace invalidations/rehydration, including []/null.";
|
|
3
3
|
export const PASSIVE_MEMORY_PROTOCOL = `State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns never trigger State Flow continuation or compaction. ${TASK_DRIVEN_HISTORY} ${PATCH_RESULT_PROTOCOL}`;
|
|
4
4
|
const PATCH_DISPLAY_SECTION_KEYS = new Set(["global", "cwd", "session", "intents", "contract", "working", "artifacts", "response", "lazy"]);
|
|
5
5
|
/** Keep successful patch JSON valid while separating adjacent scopes and memory sections visually. */
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { type ArtifactProvenance } from "./artifact.ts";
|
|
2
2
|
import type { SuccessfulArtifactRead } from "./acquisition.ts";
|
|
3
3
|
import { type AcceptedTransition } from "./history.ts";
|
|
4
|
+
import { type OwnedPath } from "./ownership.ts";
|
|
4
5
|
import { type SuccessfulSkillRead } from "./skills.ts";
|
|
5
6
|
import type { Snapshot } from "./snapshot.ts";
|
|
6
7
|
import type { AtomicScopePatches, ScopedSemanticStates, StateScope, TerminalTransition } from "./state.ts";
|
|
7
8
|
export interface StagedScopedTransition {
|
|
8
9
|
nextStates: ScopedSemanticStates;
|
|
10
|
+
/** Scope-local targets removed by the staged intent cascade, not replay input. */
|
|
11
|
+
cascades: Record<StateScope, OwnedPath[]>;
|
|
9
12
|
stateHashes: Record<StateScope, string>;
|
|
10
13
|
/** Fresh runtime-owned provenance for artifacts compiled in this transition. */
|
|
11
14
|
provenanceUpdates: Record<StateScope, Record<string, ArtifactProvenance>>;
|
|
@@ -136,12 +136,13 @@ function stageScopedSemanticTransition(currentStates, transition, successfulSkil
|
|
|
136
136
|
validateSkillCompilerTargets(patches, skillReads);
|
|
137
137
|
const nextStates = { ...currentStates };
|
|
138
138
|
const provenanceUpdates = { global: {}, cwd: {}, session: {} };
|
|
139
|
+
const cascades = { global: [], cwd: [], session: [] };
|
|
139
140
|
for (const scope of SCOPES) {
|
|
140
141
|
const authored = patches.get(scope) ?? {};
|
|
141
142
|
const patch = { ...authored, ...(scope === "session" && acceptedResponse !== undefined ? { response: acceptedResponse } : {}) };
|
|
142
143
|
let materialized = applyPatch({ ...emptyState(), ...currentStates[scope] }, patch);
|
|
143
144
|
// Authored operations first, then the same-scope intent ownership cascade.
|
|
144
|
-
const cascade = computeIntentCascade(scope, currentStates[scope], materialized);
|
|
145
|
+
const cascade = cascades[scope] = computeIntentCascade(scope, currentStates[scope], materialized);
|
|
145
146
|
if (cascade.length > 0)
|
|
146
147
|
materialized = applyPatch(materialized, cascadeDeletionPatch(cascade));
|
|
147
148
|
compileReadArtifacts(materialized, { artifacts: authored.artifacts ?? {} }, artifactReads.filter((read) => (read.scope ?? "global") === scope), provenanceUpdates[scope]);
|
|
@@ -157,6 +158,7 @@ function stageScopedSemanticTransition(currentStates, transition, successfulSkil
|
|
|
157
158
|
}
|
|
158
159
|
return {
|
|
159
160
|
nextStates,
|
|
161
|
+
cascades,
|
|
160
162
|
provenanceUpdates,
|
|
161
163
|
stateHashes: {
|
|
162
164
|
global: hashJson(currentStates.global),
|
|
@@ -32,7 +32,7 @@ Scopes overlay `global → cwd → session`: cross-project, project, branch/run.
|
|
|
32
32
|
|
|
33
33
|
## Read
|
|
34
34
|
|
|
35
|
-
Reuse sufficient visible state. The memory head and its recent transitions are frozen at projection start. Apply later `state_updates` only when their `projection` matches the head's `State Flow projection:` ID; older native results remain historical. Effective update paths are key/index arrays whose values replace that path, while `deleted: true` means absence. Current notices can replace invalidation lists or rehydration phase, including clearing them with `[]` or `null`; lazy bodies still require explicit reads.
|
|
35
|
+
Reuse sufficient visible state. The memory head and its recent transitions are frozen at projection start. Apply later `state_updates` only when their `projection` matches the head's `State Flow projection:` ID; older native results remain historical. Effective update paths are key/index arrays whose values replace that path, while `deleted: true` means absence. The optional `cascaded` array lists owner-scoped paths removed by intent deletion, including nested lazy keys; it contains no values, is never elided, and does not mean the effective path is absent if a broader-scope value remains. Current notices can replace invalidation lists or rehydration phase, including clearing them with `[]` or `null`; lazy bodies still require explicit reads.
|
|
36
36
|
|
|
37
37
|
`read_state` accepts `path` or `paths`, never both. Multi-path reads succeed or fail together. Projections: `value` (default), `keys` (structure), `patch` (intersecting change at the selected boundary).
|
|
38
38
|
|
|
@@ -122,7 +122,7 @@ checkpoint.json + patches.jsonl
|
|
|
122
122
|
|
|
123
123
|
### `state_updates` receipts
|
|
124
124
|
|
|
125
|
-
A successful native tool result
|
|
125
|
+
A successful native tool result may include a `state_updates` block, separate from the compact acknowledgement used by interactive rendering. The context domain owns this projection, not storage or the lifecycle composition root.
|
|
126
126
|
|
|
127
127
|
**Shape.** `effective` entries contain a `path` array of exact object keys/array indices, plus either a replacing `value` or `deleted: true`.
|
|
128
128
|
|
|
@@ -131,9 +131,10 @@ A successful native tool result includes a `state_updates` block, separate from
|
|
|
131
131
|
- Indexed-array patch selectors become numeric update paths only against a communicated in-bounds array basis. Object keys with the same spelling are literal.
|
|
132
132
|
- Artifact cards use the normal metadata filter, and touched cards replace their whole projected entry.
|
|
133
133
|
- Lazy bodies never enter this block; a changed bounded `lazy_navigation` may accompany it.
|
|
134
|
-
-
|
|
134
|
+
- An optional `cascaded` array lists owner-scoped paths removed by intent deletion, including nested lazy targets that do not change the top-level navigation. It contains paths only, never values, and is absent when no target cascaded.
|
|
135
|
+
- Unrelated unchanged branches are omitted. A result with nothing left to reconcile and no cascade has only the acknowledgement.
|
|
135
136
|
|
|
136
|
-
**What is always included.** Entries conservatively cover changed scope fallbacks, masked or overlapping multi-scope touches, and projected changes since the last communicated view, including shared refreshes before or during the transaction.
|
|
137
|
+
**What is always included.** Entries conservatively cover changed scope fallbacks, masked or overlapping multi-scope touches, and projected changes since the last communicated view, including shared refreshes before or during the transaction. `cascaded` lists every target computed during accepted staging, in deterministic per-scope order across Global, CWD and Session. It is never elided as predictable: a receipt with only `cascaded` and an empty `effective` array still reaches the model. The owner path does not imply effective absence; `effective` entries still describe any revealed fallback.
|
|
137
138
|
|
|
138
139
|
**What may be omitted as predictable:**
|
|
139
140
|
|
|
@@ -134,9 +134,11 @@ Intents can own the memory they create:
|
|
|
134
134
|
- missing targets;
|
|
135
135
|
- keys outside the `read_state` key grammar `[A-Za-z_$][A-Za-z0-9_$-]*`, for example keys with spaces, dots or non-Latin letters.
|
|
136
136
|
|
|
137
|
+
**Receipt.** `state_updates.cascaded` lists all owner-scoped targets removed by intent deletion, for example `["session.lazy.plans.x", "session.working.draft"]`. Paths only, never lazy bodies or deleted values. The list is deterministic (Global, CWD, Session, then each scope's cascade order), absent when no target cascaded, and never omitted as a predictable write. Nested lazy deletions are reported even when the top-level `lazy_navigation` catalog is unchanged.
|
|
138
|
+
|
|
137
139
|
**Edge cases:**
|
|
138
140
|
|
|
139
|
-
- Deletion is scope-local, so an effective read may afterwards show a same-path value inherited from a broader scope. The receipt then reports that value rather than `deleted: true
|
|
141
|
+
- Deletion is scope-local, so an effective read may afterwards show a same-path value inherited from a broader scope. The receipt's `effective` entry then reports that value rather than `deleted: true`; `cascaded` still identifies the deleted owner path.
|
|
140
142
|
- Concurrent shared writers keep last-accepted-wins behaviour: a later write into an intent another session already deleted simply recreates a partial intent.
|
|
141
143
|
|
|
142
144
|
**History and limits.** The accepted patch record stores cascaded keys as explicit deletions, so replay never re-derives them, and nothing is archived beyond ordinary retained history. Ownership adds no validation, unresolved-reference warning, cross-scope cascade, age-based cleanup, size budget, growth notice, archive of deleted entries or automatic hydration.
|
|
@@ -4,6 +4,7 @@ import { projectArtifactForModel, type ArtifactInvalidationNotice, type Artifact
|
|
|
4
4
|
import type { RecentTransitionWindow } from "./history.ts";
|
|
5
5
|
import { applyPatch, isObject, presentationJson, sameJson, type JsonObject, type JsonValue } from "./json.ts";
|
|
6
6
|
import type { Snapshot } from "./snapshot.ts";
|
|
7
|
+
import { formatOwnedPath, type OwnedPath } from "./ownership.ts";
|
|
7
8
|
import type { RehydrationPhase } from "./rehydration.ts";
|
|
8
9
|
import { projectSemanticPatch, projectStateForModel, type AtomicScopePatches, type SemanticState, type StateScope } from "./state.ts";
|
|
9
10
|
|
|
@@ -138,7 +139,8 @@ export class ContextProjection {
|
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
/** Called only after successful publication and ancillary acceptance, immediately before returning the native result. */
|
|
141
|
-
acceptPatch(before: SemanticState, after: SemanticState, patches: AtomicScopePatches, hints: ArtifactModelHints
|
|
142
|
+
acceptPatch(before: SemanticState, after: SemanticState, patches: AtomicScopePatches, hints: ArtifactModelHints,
|
|
143
|
+
cascades: Partial<Record<StateScope, readonly OwnedPath[]>> = {}) {
|
|
142
144
|
const state = projectStateForModel(after, hints);
|
|
143
145
|
const navigation = lazyNavigationHint(after);
|
|
144
146
|
const beforeNavigation = this.view?.lazy_navigation ?? lazyNavigationHint(before);
|
|
@@ -228,7 +230,12 @@ export class ContextProjection {
|
|
|
228
230
|
if (predictable && seen.size > 0 && sameJson(Object.fromEntries(expected), navigation.keys)) delete updates.lazy_navigation;
|
|
229
231
|
}
|
|
230
232
|
if (this.view) this.view = { ...this.view, state, lazy_navigation: navigation };
|
|
231
|
-
|
|
233
|
+
// Owner paths remain informative even when effective state/navigation is masked
|
|
234
|
+
// or the model could predict every direct write. Never expose target values.
|
|
235
|
+
const cascaded = (["global", "cwd", "session"] as const).flatMap((scope) =>
|
|
236
|
+
(cascades[scope] ?? []).map((path) => formatOwnedPath(scope, path)));
|
|
237
|
+
return updates.effective.length || updates.lazy_navigation || cascaded.length
|
|
238
|
+
? { projection: this.identity, ...updates, ...(cascaded.length ? { cascaded } : {}) } : undefined;
|
|
232
239
|
}
|
|
233
240
|
|
|
234
241
|
project(messages: AgentMessage[], current: ContextView, makeHead: () => AgentMessage, initial?: ContextView): AgentMessage[] {
|
|
@@ -807,7 +807,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
807
807
|
}
|
|
808
808
|
clearAcceptedAcquisitions(new Set(acquiredArtifacts.map(({ path }) => path)));
|
|
809
809
|
updateUi(ctx);
|
|
810
|
-
const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, acquisition.hints);
|
|
810
|
+
const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, acquisition.hints, stage.cascades);
|
|
811
811
|
const acknowledgement = changed
|
|
812
812
|
? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
|
|
813
813
|
: "\nState already current.";
|
|
@@ -4,7 +4,7 @@ export type { StateDocument } from "./state.ts";
|
|
|
4
4
|
|
|
5
5
|
const TASK_DRIVEN_HISTORY = "Missing paths/hints do not require history search. Choose targeted historical reads when useful to the task; no separate user permission is needed. Past values are evidence, not current state; never automatically restore deleted memory.";
|
|
6
6
|
|
|
7
|
-
const PATCH_RESULT_PROTOCOL = "Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply;
|
|
7
|
+
const PATCH_RESULT_PROTOCOL = "Head state/recent transitions are frozen at projection start. Only state_updates matching the head's State Flow projection ID apply; other IDs are history. Results/context notices carry state_updates: effective entries replace values at key/index-segment path arrays (deleted:true means absent); cascaded lists owner paths an intent deletion removed. Direct writes may elide; shared drift stays visible. Latest entries win over earlier state; lazy bodies stay omitted. Notices replace invalidations/rehydration, including []/null.";
|
|
8
8
|
|
|
9
9
|
export const PASSIVE_MEMORY_PROTOCOL = `State Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns never trigger State Flow continuation or compaction. ${TASK_DRIVEN_HISTORY} ${PATCH_RESULT_PROTOCOL}`;
|
|
10
10
|
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import type { SuccessfulArtifactRead } from "./acquisition.ts";
|
|
11
11
|
import { createAcceptedTransition, type AcceptedTransition } from "./history.ts";
|
|
12
12
|
import { applyPatch, containsNull, hashJson, isObject, validatePatch, type JsonObject } from "./json.ts";
|
|
13
|
-
import { cascadeDeletionPatch, computeIntentCascade } from "./ownership.ts";
|
|
13
|
+
import { cascadeDeletionPatch, computeIntentCascade, type OwnedPath } from "./ownership.ts";
|
|
14
14
|
import { hasCompiledSkillArtifact, SKILL_ARTIFACT_COMPILER, type SuccessfulSkillRead } from "./skills.ts";
|
|
15
15
|
import type { Snapshot } from "./snapshot.ts";
|
|
16
16
|
import { emptyState } from "./state.ts";
|
|
@@ -29,6 +29,8 @@ import type {
|
|
|
29
29
|
|
|
30
30
|
export interface StagedScopedTransition {
|
|
31
31
|
nextStates: ScopedSemanticStates;
|
|
32
|
+
/** Scope-local targets removed by the staged intent cascade, not replay input. */
|
|
33
|
+
cascades: Record<StateScope, OwnedPath[]>;
|
|
32
34
|
stateHashes: Record<StateScope, string>;
|
|
33
35
|
/** Fresh runtime-owned provenance for artifacts compiled in this transition. */
|
|
34
36
|
provenanceUpdates: Record<StateScope, Record<string, ArtifactProvenance>>;
|
|
@@ -186,12 +188,13 @@ function stageScopedSemanticTransition(
|
|
|
186
188
|
validateSkillCompilerTargets(patches, skillReads);
|
|
187
189
|
const nextStates = { ...currentStates };
|
|
188
190
|
const provenanceUpdates: Record<StateScope, Record<string, ArtifactProvenance>> = { global: {}, cwd: {}, session: {} };
|
|
191
|
+
const cascades: Record<StateScope, OwnedPath[]> = { global: [], cwd: [], session: [] };
|
|
189
192
|
for (const scope of SCOPES) {
|
|
190
193
|
const authored = patches.get(scope) ?? {};
|
|
191
194
|
const patch = { ...authored, ...(scope === "session" && acceptedResponse !== undefined ? { response: acceptedResponse } : {}) };
|
|
192
195
|
let materialized = applyPatch({ ...emptyState(), ...currentStates[scope] }, patch) as StateDocument;
|
|
193
196
|
// Authored operations first, then the same-scope intent ownership cascade.
|
|
194
|
-
const cascade = computeIntentCascade(scope, currentStates[scope], materialized);
|
|
197
|
+
const cascade = cascades[scope] = computeIntentCascade(scope, currentStates[scope], materialized);
|
|
195
198
|
if (cascade.length > 0) materialized = applyPatch(materialized, cascadeDeletionPatch(cascade)) as StateDocument;
|
|
196
199
|
compileReadArtifacts(
|
|
197
200
|
materialized,
|
|
@@ -210,6 +213,7 @@ function stageScopedSemanticTransition(
|
|
|
210
213
|
}
|
|
211
214
|
return {
|
|
212
215
|
nextStates,
|
|
216
|
+
cascades,
|
|
213
217
|
provenanceUpdates,
|
|
214
218
|
stateHashes: {
|
|
215
219
|
global: hashJson(currentStates.global),
|
|
@@ -32,7 +32,7 @@ Scopes overlay `global → cwd → session`: cross-project, project, branch/run.
|
|
|
32
32
|
|
|
33
33
|
## Read
|
|
34
34
|
|
|
35
|
-
Reuse sufficient visible state. The memory head and its recent transitions are frozen at projection start. Apply later `state_updates` only when their `projection` matches the head's `State Flow projection:` ID; older native results remain historical. Effective update paths are key/index arrays whose values replace that path, while `deleted: true` means absence. Current notices can replace invalidation lists or rehydration phase, including clearing them with `[]` or `null`; lazy bodies still require explicit reads.
|
|
35
|
+
Reuse sufficient visible state. The memory head and its recent transitions are frozen at projection start. Apply later `state_updates` only when their `projection` matches the head's `State Flow projection:` ID; older native results remain historical. Effective update paths are key/index arrays whose values replace that path, while `deleted: true` means absence. The optional `cascaded` array lists owner-scoped paths removed by intent deletion, including nested lazy keys; it contains no values, is never elided, and does not mean the effective path is absent if a broader-scope value remains. Current notices can replace invalidation lists or rehydration phase, including clearing them with `[]` or `null`; lazy bodies still require explicit reads.
|
|
36
36
|
|
|
37
37
|
`read_state` accepts `path` or `paths`, never both. Multi-path reads succeed or fail together. Projections: `value` (default), `keys` (structure), `patch` (intersecting change at the selected boundary).
|
|
38
38
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@llblab/pi-actors": "0.54.0",
|
|
45
|
-
"@llblab/pi-claude-usage": "0.2.
|
|
45
|
+
"@llblab/pi-claude-usage": "0.2.1",
|
|
46
46
|
"@llblab/pi-clean-room": "0.3.0",
|
|
47
|
-
"@llblab/pi-codex-usage": "0.12.
|
|
47
|
+
"@llblab/pi-codex-usage": "0.12.1",
|
|
48
48
|
"@llblab/pi-grow-loop": "0.9.0",
|
|
49
|
-
"@llblab/pi-state-flow": "0.25.
|
|
49
|
+
"@llblab/pi-state-flow": "0.25.1",
|
|
50
50
|
"@llblab/pi-telegram": "0.51.6",
|
|
51
51
|
"@llblab/skills": "1.15.0"
|
|
52
52
|
},
|