@llblab/pi-kit 0.27.5 → 0.27.6
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 +6 -1
- package/CHANGELOG.md +4 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +6 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +4 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +26 -13
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +3 -1
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +2 -1
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +25 -12
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/package.json +2 -2
package/BACKLOG.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# Backlog
|
|
2
2
|
|
|
3
|
-
The 0.27.
|
|
3
|
+
The 0.27.6 composition is recorded in [CHANGELOG.md](./CHANGELOG.md). Package pins, resource order and bundled runtime ownership remain authoritative in `package.json`.
|
|
4
|
+
|
|
5
|
+
## Release acceptance
|
|
6
|
+
|
|
7
|
+
- **Publication:** Once State Flow 0.25.5 is verified on npm, install its exact pin, validate the packed kit and run the exact-tag workflow; verify GitHub Release and npm identity.
|
|
4
8
|
|
|
5
9
|
## Carried checks
|
|
6
10
|
|
|
11
|
+
- **Installed 0.27.6 restoration/compaction smoke (operator-owned):** After separately authorized installation/reload, use disposable storage to confirm failed Active restoration blocks provider inference across tree/reload, explicit mode recovery remains available, and native Codemode values/deletions survive completed-history compaction. Packed validation does not certify installed clients.
|
|
7
12
|
- **Installed 0.27.5 cleanup smoke (operator-owned):** After separately authorized installation/reload, confirm State Flow missing-deletion hints, recursive empty-object cleanup, inherited fallback and preserved array slots in disposable storage. Packed validation does not certify installed clients.
|
|
8
13
|
- **Installed 0.27.4 inspection smoke (operator-owned):** After separately authorized installation/reload, inspect a large nested State Flow field in disposable storage. Confirm readable JSON layout, separate truncation notices and intact genuine string escapes. Packed validation does not certify installed Telegram clients.
|
|
9
14
|
- **Installed 0.27.3 single patch display smoke (operator-owned):** After separately authorized installation/reload, confirm one argument block followed by changed/no-op acknowledgements, compact configuration and visible rejected arguments/errors in disposable State Flow storage.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@llblab/pi-kit` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.27.6: State Flow Restoration Fence and Codemode Compaction
|
|
6
|
+
|
|
7
|
+
- `Restoration and Compaction`: Advances the exact State Flow pin to `0.25.5`. Failed Active restoration blocks inference before the provider rather than exposing native history; valid selection, accepted Start or explicit Passive/Off permits recovery. Native Codemode metadata no longer blocks completed-history compaction, while values/deletions survive compaction and resume. Unknown metadata and visible custom context stay protected. Other pins, resources and load order are unchanged.
|
|
8
|
+
|
|
5
9
|
## 0.27.5: State Flow Cleanup and Deletion Hints
|
|
6
10
|
|
|
7
11
|
- `Cleanup and Deletion Hints`: Advances the exact State Flow pin to `0.25.4`. Accepted scopes recursively drop empty object fields without shifting array slots; cleanup may reveal inherited values. Missing authored deletions report their target and first unavailable component without blocking useful writes or creating revisions. Exact history and untouched scopes stay unchanged. Other pins, resources and load order are unchanged.
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Package links lead to the owning repositories for usage, documentation, issues,
|
|
|
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
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.5` | Scoped context/memory compiler with intent-owned memory, failed Active restoration fencing and Codemode-safe compaction |
|
|
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.5/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.6` composition includes State Flow `0.25.5` with failed Active restoration fencing and Codemode-safe compaction; all other pins, resources and load order remain unchanged. `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
49
|
|
|
50
50
|
```bash
|
|
51
51
|
npm install
|
|
@@ -18,7 +18,7 @@ The [relocation ledger](docs/agent-contract-relocation.md) maps every pre-compac
|
|
|
18
18
|
|
|
19
19
|
- Treat canonical scope files as semantic authority and Git only as optional backup. Classify complete, wholly absent, partial and malformed cohorts before recovery; absent shared pairs may initialize only under accepted authority, while incomplete/private evidence fails closed. Never repair through ad-hoc writes. See [storage recovery](docs/usage.md#storage-and-recovery) and [transaction rule](docs/filesystem-recovery.md#transaction-rule).
|
|
20
20
|
- Keep exact regular-file, byte-CAS and lock-serialized publication with cancelable waits, single-use callback-scoped capabilities and guarded rollback. Never hold exclusion across inference, source acquisition or Git; do not steal interrupted locks, claim kernel-atomic multi-file publication or promise power-loss durability. See [asynchronous transaction](docs/architecture.md#asynchronous-storage-transaction) and [durability boundary](docs/filesystem-recovery.md#power-loss-durability).
|
|
21
|
-
- Attach Off branches through native policy bookkeeping only, deferring memory acquisition and recovery diagnostics until explicit Passive/Active. Preserve pending Off forks across cold reload with child-owned native markers, without reading the parent header/store until acquisition. When memory is selected, restore private retained boundaries over live shared scopes and copy exact proven source-session history into a fresh fork owner; never substitute current, empty, Git or another branch on expiry/failure. Explicit Start instead validates current same-session authority. Only accepted candidates install cache, checkpoint and mode. See [Pi lifecycle](docs/architecture.md#pi-lifecycle) and [fork contract](docs/fork-contract.md).
|
|
21
|
+
- Attach Off branches through native policy bookkeeping only, deferring memory acquisition and recovery diagnostics until explicit Passive/Active. Preserve pending Off forks across cold reload with child-owned native markers, without reading the parent header/store until acquisition. When memory is selected, restore private retained boundaries over live shared scopes and copy exact proven source-session history into a fresh fork owner; never substitute current, empty, Git or another branch on expiry/failure. Explicit Start instead validates current same-session authority. Only accepted candidates install cache, checkpoint and mode. Failed selected Active restoration fences live inference through public Abort until valid selection, accepted Start or explicit Passive/Off; never silently expose native history as the inactive fallback. See [Pi lifecycle](docs/architecture.md#pi-lifecycle) and [fork contract](docs/fork-contract.md).
|
|
22
22
|
- Persist each material semantic change and its affected revisions exactly once, including accepted Session responses. Optional settled-turn Git backup may capture only already-accepted owned files, leave unrelated index/worktree data intact and push without force or semantic side effects. Defer busy backup when the host provides no settlement operation signal rather than blocking Abort; never move it to `turn_end` or add a durable push queue. Off cancels owned captures/pushes and suppresses late reporting; normal agent-operation completion must not cancel an independently admitted push, and foreign callers' pushes retain ownership. See [optional Git backup](docs/architecture.md#optional-git-backup).
|
|
23
23
|
- Accept one atomic `patch_state` cohort across supplied scopes against current shared memory; reject empty scopes, unknown/retired grammar and model-authored `response`. Correct no-ops create no transition. Enforce the single-call inference barrier before sibling tools execute and retain conservative model-facing reconciliation when a result cannot be predicted. See [model tools](docs/architecture.md#model-tools) and [Pi lifecycle](docs/architecture.md#pi-lifecycle).
|
|
24
24
|
- Reconcile the actual accepted ordinary answer at `turn_end` with response-owned cancellation and one accepted lifecycle publication; do not request private repair inference, ceremonial finalization patches or roll back accepted memory after cancellation. See [Pi lifecycle](docs/architecture.md#pi-lifecycle).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Backlog
|
|
2
2
|
|
|
3
|
-
The **0.25.
|
|
3
|
+
The **0.25.5: Active Restoration Fence and Codemode Compaction** hotfix is prepared; outcomes belong in [CHANGELOG.md](CHANGELOG.md). This backlog retains publication, installed-client gates and deferred decisions.
|
|
4
4
|
|
|
5
5
|
## Out of scope
|
|
6
6
|
|
|
@@ -8,9 +8,13 @@ The **0.25.4: Empty-Object Cleanup and Deletion Hints** hotfix outcomes belong i
|
|
|
8
8
|
- Nested `lazy_navigation` and intent-ownership warnings, validation or rejection. Authored missing-deletion hints do not change cascade behavior.
|
|
9
9
|
- Any reduction of lifecycle state; the tagged-union question stays deferred.
|
|
10
10
|
|
|
11
|
+
## 0.25.5 acceptance
|
|
12
|
+
|
|
13
|
+
- **Publication.** Run the exact-tag workflow and verify GitHub Release/npm identity, then synchronize and release Pi Kit 0.27.6.
|
|
14
|
+
- **Installed restoration/compaction smoke (operator-owned).** After separately authorized reload, use disposable storage to confirm failed Active restoration blocks provider inference across tree/reload, explicit mode recovery remains available, and native Codemode values/deletions survive completed-history compaction. Local/native tests do not certify the installed client.
|
|
15
|
+
|
|
11
16
|
## Carried gates
|
|
12
17
|
|
|
13
|
-
- **0.25.4 publication and kit synchronization.** Verify the exact-tag workflow, GitHub Release and npm commit, then bundle and publish Pi Kit 0.27.5.
|
|
14
18
|
- **Installed 0.25.4 cleanup smoke (operator-owned).** After separately authorized installation/reload, use disposable storage to confirm missing-deletion hints, recursive empty-object cleanup, inherited fallback and preserved array slots. Local validation and publication do not certify installed clients.
|
|
15
19
|
- **Installed 0.25.3 inspection smoke (operator-owned).** After separately authorized installation/reload, inspect a large nested field: real layout newlines/quotes, separate omitted-character notice and intact genuine JSON string escapes. Use disposable storage; local adapter tests do not certify installed Telegram clients.
|
|
16
20
|
- **Installed 0.25.2 smoke (operator-owned).** Disposable store: confirm one patch argument block followed by changed/no-op acknowledgements, compact rows with `showSuccessfulPatches: false`, and visible rejected arguments/errors.
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
> Each release keeps at most 8 outcome records of at most 512 characters.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 0.25.5: Active Restoration Fence and Codemode Compaction
|
|
6
|
+
|
|
7
|
+
- `Active restoration fence`: Failed retained Active restoration aborts live inference instead of silently exposing native history under an inactive fallback. Expired tree/reload and contradictory-lineage native tests prove zero provider calls and unchanged memory. Valid selection, accepted Start or explicit Passive/Off releases the fence; historical authority is never substituted.
|
|
8
|
+
- `Codemode-safe compaction`: Exact native `codemode-store` metadata no longer blocks completed-history compaction. Full branch records remain intact; native Codemode reads prove values and deletions survive compaction, reload and resume. Unknown metadata and visible custom context remain protected.
|
|
6
9
|
|
|
7
10
|
## 0.25.4: Empty-Object Cleanup and Deletion Hints
|
|
8
11
|
|
|
@@ -3,10 +3,11 @@ import { estimateTokens } from "@earendil-works/pi-coding-agent";
|
|
|
3
3
|
export const STATE_FLOW_COMPACTION_SUMMARY = "State Flow accepted the completed work before this boundary. Current memory is restored from its retained semantic boundary and projected separately; use the retained native entries for subsequent work.";
|
|
4
4
|
/** A modest margin above Pi's default 20k retained suffix absorbs estimation drift. */
|
|
5
5
|
export const STATE_FLOW_COMPACTION_MIN_CONTEXT_TOKENS = 24_000;
|
|
6
|
-
function
|
|
6
|
+
function contextInvisibleEntry(entry) {
|
|
7
|
+
// Codemode reconstructs its store from the full native branch, not the compacted model context.
|
|
7
8
|
return entry.type === "custom"
|
|
8
9
|
&& typeof entry.customType === "string"
|
|
9
|
-
&& entry.customType.startsWith("state-flow-");
|
|
10
|
+
&& (entry.customType.startsWith("state-flow-") || entry.customType === "codemode-store");
|
|
10
11
|
}
|
|
11
12
|
export function shouldRequestStateFlowCompaction(usage) {
|
|
12
13
|
return typeof usage?.tokens === "number"
|
|
@@ -45,7 +46,7 @@ export function planStateFlowCompaction(entries, boundary, step, runAnchorTimest
|
|
|
45
46
|
const keep = entries.findIndex(isRunAnchor);
|
|
46
47
|
if (keep < 0 || keep > terminal || entries.findLastIndex(isRunAnchor) !== keep)
|
|
47
48
|
return undefined;
|
|
48
|
-
if (entries.slice(0, keep).some((entry) => entry.type === "custom_message" || (entry.type === "custom" && !
|
|
49
|
+
if (entries.slice(0, keep).some((entry) => entry.type === "custom_message" || (entry.type === "custom" && !contextInvisibleEntry(entry))))
|
|
49
50
|
return undefined;
|
|
50
51
|
const firstKeptEntryId = entries[keep]?.id;
|
|
51
52
|
const leafId = entries.at(-1)?.id;
|
|
@@ -42,7 +42,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
42
42
|
let scopeStates = { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
43
43
|
let effectiveState = {};
|
|
44
44
|
let branchStartsWithoutRuntime = false;
|
|
45
|
-
let
|
|
45
|
+
let selectedBoundaryError;
|
|
46
46
|
let modePersistenceError;
|
|
47
47
|
/** One pending inactive-mode persistence; later inactive choices coalesce until it publishes. */
|
|
48
48
|
const inactivePersistence = new OwnedOperationSlot();
|
|
@@ -390,7 +390,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
390
390
|
runtime = undefined;
|
|
391
391
|
snapshot = emptySnapshot("off");
|
|
392
392
|
branchStartsWithoutRuntime = withoutRuntime;
|
|
393
|
-
|
|
393
|
+
selectedBoundaryError = undefined;
|
|
394
394
|
modePersistenceError = persistenceError;
|
|
395
395
|
forkInitialization = reason === "fork";
|
|
396
396
|
installScopeStates();
|
|
@@ -408,7 +408,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
408
408
|
runtime = createRuntime(ctx);
|
|
409
409
|
installScopeStates();
|
|
410
410
|
branchStartsWithoutRuntime = false;
|
|
411
|
-
|
|
411
|
+
selectedBoundaryError = undefined;
|
|
412
412
|
forkInitialization = sessionStartReason === "fork";
|
|
413
413
|
let selection = { kind: "settled" };
|
|
414
414
|
let skipped = 0;
|
|
@@ -439,6 +439,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
439
439
|
? { ...selected.checkpoint, mode: sourceFence.mode ?? config.inactiveMode } : selected.checkpoint };
|
|
440
440
|
}
|
|
441
441
|
catch (error) {
|
|
442
|
+
selectedBoundaryError = { expired: error instanceof HistoryBoundaryExpiredError,
|
|
443
|
+
blockInference: selected.checkpoint.mode === "active" && requestedMode === undefined };
|
|
442
444
|
snapshot = selectedBoundaryFailure(diagnosticText(error), inactiveSelection(selected.checkpoint.mode));
|
|
443
445
|
}
|
|
444
446
|
}
|
|
@@ -500,6 +502,13 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
500
502
|
throw new Error("State Flow branch selection changed while awaiting publication");
|
|
501
503
|
};
|
|
502
504
|
let accepted = false;
|
|
505
|
+
const failSelection = (error) => {
|
|
506
|
+
selectedBoundaryError = { expired: error instanceof HistoryBoundaryExpiredError,
|
|
507
|
+
blockInference: pending.requestedMode === undefined && (selection.kind === "auto-start"
|
|
508
|
+
|| ((selection.kind === "restore" || selection.kind === "fork") && selection.checkpoint.mode === "active")) };
|
|
509
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), inactiveSelection(snapshot.config.mode));
|
|
510
|
+
installScopeStates();
|
|
511
|
+
};
|
|
503
512
|
// Install accepted memory before native writes; later ancillary failures cannot revert or replay it.
|
|
504
513
|
const accept = (candidate, next, nativeWrites) => {
|
|
505
514
|
accepted = true;
|
|
@@ -583,10 +592,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
583
592
|
}
|
|
584
593
|
if (!isCurrent())
|
|
585
594
|
return;
|
|
586
|
-
|
|
587
|
-
selectedHistoryExpired = true;
|
|
588
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error), snapshot.config.mode === "active" ? config.inactiveMode : snapshot.config.mode);
|
|
589
|
-
installScopeStates();
|
|
595
|
+
failSelection(error);
|
|
590
596
|
}
|
|
591
597
|
finally {
|
|
592
598
|
pending.awaitingAcceptance = false;
|
|
@@ -612,8 +618,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
612
618
|
// Host failures before acceptance leave the selection unavailable, never invented empty memory.
|
|
613
619
|
if (accepted || !isCurrent())
|
|
614
620
|
return;
|
|
615
|
-
|
|
616
|
-
installScopeStates();
|
|
621
|
+
failSelection(error);
|
|
617
622
|
}
|
|
618
623
|
finally {
|
|
619
624
|
pending.awaitingAcceptance = false;
|
|
@@ -642,7 +647,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
642
647
|
function settleSelection(ctx, reason, notifyRecovery, skipped, selectedMemory) {
|
|
643
648
|
const failure = !isActive() && snapshot.meta.validation?.attempt === 0 ? snapshot.meta.validation.error : undefined;
|
|
644
649
|
if (failure !== undefined && notifyRecovery && !modePersistenceError) {
|
|
645
|
-
if (
|
|
650
|
+
if (selectedBoundaryError?.expired)
|
|
646
651
|
notifyProblem(ctx, "State Flow history is outside the retained temporal window; /state-flow-active can use current session memory.", "warning");
|
|
647
652
|
else
|
|
648
653
|
notifyProblem(ctx, `State Flow restore failed: ${failure}`, "error");
|
|
@@ -919,14 +924,14 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
919
924
|
? findPassiveStopBoundary(ctx.sessionManager.getBranch(), owner, PASSIVE_STOP_ENTRY_TYPE) : undefined;
|
|
920
925
|
const bootstrap = hasPriorConversation(ctx.sessionManager.getBranch()) || passiveContinuation !== undefined || boundary !== undefined;
|
|
921
926
|
const activated = current ? resumeEpisode(current, bootstrap) : startEpisode(bootstrap);
|
|
922
|
-
const recoveredCurrent =
|
|
927
|
+
const recoveredCurrent = selectedBoundaryError?.expired;
|
|
923
928
|
publish(activated);
|
|
924
929
|
accepted = true;
|
|
925
930
|
inactivePersistence.cancel();
|
|
926
931
|
runtime = selected = activation;
|
|
927
932
|
snapshot = activated;
|
|
928
933
|
activeContext = ctx;
|
|
929
|
-
|
|
934
|
+
selectedBoundaryError = undefined;
|
|
930
935
|
modePersistenceError = undefined;
|
|
931
936
|
installScopeStates();
|
|
932
937
|
const continuation = passiveContinuation ?? bootstrapContinuation ?? (deferred ? selectedContinuation(ctx, deferred.reason) : undefined);
|
|
@@ -988,6 +993,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
988
993
|
/** Local policy, tools and UI change before any canonical wait. */
|
|
989
994
|
function applyInactiveMode(ctx, mode) {
|
|
990
995
|
snapshot = deactivateEpisode(snapshot, mode);
|
|
996
|
+
if (selectedBoundaryError)
|
|
997
|
+
selectedBoundaryError.blockInference = false;
|
|
991
998
|
clearRunTransient();
|
|
992
999
|
syncStateFlowTools();
|
|
993
1000
|
updateUi(ctx);
|
|
@@ -1074,7 +1081,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1074
1081
|
acquisition.reset();
|
|
1075
1082
|
runtime = undefined;
|
|
1076
1083
|
snapshot = emptySnapshot("off");
|
|
1077
|
-
|
|
1084
|
+
selectedBoundaryError = undefined;
|
|
1078
1085
|
branchStartsWithoutRuntime = preRuntime;
|
|
1079
1086
|
forkInitialization = pendingFork;
|
|
1080
1087
|
deferredBranch = { owner: address.key, cwd: ctx.cwd, reason: pendingFork ? "fork" : undefined };
|
|
@@ -1307,6 +1314,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1307
1314
|
return { messages: projection.project(source, view, () => runtimeContextHead(snapshot, view, projectRecentTransitionsWithLimit(config.historyLimit, runtime?.recent() ?? []))) };
|
|
1308
1315
|
}
|
|
1309
1316
|
function prepareContext(messages, ctx) {
|
|
1317
|
+
// Pi continues after hook errors; public Abort fences inference instead of exposing the native history fallback.
|
|
1318
|
+
if (selectedBoundaryError?.blockInference && ctx.signal && !shuttingDown) {
|
|
1319
|
+
ctx.abort();
|
|
1320
|
+
notifyProblem(ctx, "State Flow inference blocked: Active memory restoration failed. Select a valid branch or use /state-flow-active for current memory; /state-flow-passive or /state-flow-off explicitly permits native context.", "error");
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1310
1323
|
const pending = inferencePreparation.current;
|
|
1311
1324
|
const selected = runtime;
|
|
1312
1325
|
const operationSignal = ctx.signal;
|
|
@@ -328,7 +328,7 @@ The current user specification stays at user authority and appears only in synth
|
|
|
328
328
|
|
|
329
329
|
**Why no retain-none boundaries.** State Flow does not use retain-none boundary compactions. Completed canonical state omits the exact user prompt, and foreign custom context can legitimately occur inside the latest retained iteration; hiding both would make the projected semantic state a lossy substitute for native context.
|
|
330
330
|
|
|
331
|
-
**What prevents the boundary:** unknown or smaller usage, foreign custom metadata or native `custom_message` context in the prefix that would be removed, stale selection, Stop/bootstrap/error/abort, and pending input. User manual and native threshold/overflow compaction stay unmodified; unaccepted work stays under Pi's native compaction contract.
|
|
331
|
+
**What prevents the boundary:** unknown or smaller usage, foreign custom metadata or native `custom_message` context in the prefix that would be removed, stale selection, Stop/bootstrap/error/abort, and pending input. The exact native `custom` type `codemode-store` is exempt alongside State Flow metadata: it carries no model context, and Codemode reconstructs its values/deletions from the full append-only branch even after compaction. Unknown types, similarly named types and visible custom messages are not exempt. User manual and native threshold/overflow compaction stay unmodified; unaccepted work stays under Pi's native compaction contract.
|
|
332
332
|
|
|
333
333
|
### Context view and trajectory
|
|
334
334
|
|
|
@@ -510,6 +510,8 @@ Without removals on a complete accepted cohort, publication preserves semantic/p
|
|
|
510
510
|
3. **Acceptance.** The selected boundary, the exact-source fork, a truly new auto-start origin (`withStartTransaction` with creation authority, rechecking branch evidence after waiting) or failed-Stop read-only recovery then use the awaited runtime API. Bootstrap and policy are derived after waiting and published in that single acceptance.
|
|
511
511
|
4. **Installation.** Only the current lifetime installs memory, checkpoint, continuation, tools and UI before yielding; a later native-write failure only warns.
|
|
512
512
|
|
|
513
|
+
**Failed Active selection.** An unaccepted Active restoration/fork or automatic Start failure carries an inference fence independently of the inactive fallback mode. The live `context` hook calls public `ctx.abort()` before returning; merely throwing would let Pi continue with native history. Re-selecting/reloading the failed Active boundary reconstructs the fence. Accepted Start or a valid branch releases it; explicit Passive/Off permits native context but grants no missing historical/publication authority. Signal-less inspection does not cancel or clear the fence.
|
|
514
|
+
|
|
513
515
|
**Interaction with mode choices:**
|
|
514
516
|
|
|
515
517
|
- A new selection revokes older work; shutdown drains current and superseded restoration operations.
|
|
@@ -53,7 +53,7 @@ On the tested SDK, `before_agent_start` precedes the low-level agent's `prompt`;
|
|
|
53
53
|
2. The active `context` hook supplies the operation signal and awaits preparation/maintenance before provider inference.
|
|
54
54
|
3. If preparation fails, State Flow calls public `ctx.abort()`. Pi catches context-hook errors and may otherwise continue inference, so a thrown error alone is not a fence.
|
|
55
55
|
|
|
56
|
-
Native tests prove no provider call before coherent acceptance, cancellation while an independent writer remains held, rollback without draft installation and preservation of uncompiled native input.
|
|
56
|
+
Native tests prove no provider call before coherent acceptance, cancellation while an independent writer remains held, rollback without draft installation and preservation of uncompiled native input. Failed retained Active restoration also aborts live inference before the provider; native witnesses cover expired tree selection/reload and contradictory private lineage without changing canonical bytes. Explicit inactive policy releases that inference fence, not the write fence.
|
|
57
57
|
|
|
58
58
|
**Signals are not universal.** Idle commands and session events can lack them. Do not infer native Abort cancellation from an extension-owned shutdown signal or generalize active-run tests to idle waits.
|
|
59
59
|
|
|
@@ -126,7 +126,7 @@ This optional-backup policy does not apply to required semantic publication or r
|
|
|
126
126
|
**State Flow-owned compaction:**
|
|
127
127
|
|
|
128
128
|
- Requires known sufficient context usage and a proven retained run anchor.
|
|
129
|
-
- Preserves the complete accepted run, skips protected foreign context and never requests retain-none shortening.
|
|
129
|
+
- Preserves the complete accepted run, skips protected foreign context and never requests retain-none shortening. Exact native `codemode-store` custom metadata permits shortening; native tests preserve its branch records and verify `load()` values/deletions through the real Codemode extension after compaction/reload/resume.
|
|
130
130
|
- Leaves native manual/threshold/overflow compaction to Pi.
|
|
131
131
|
- Awaits native compaction completion or refusal in the settled handler, so deferred companion prompts do not race it.
|
|
132
132
|
- Skips compaction when usage is unknown or insufficient; a benign refusal permits a later attempt.
|
|
@@ -93,7 +93,7 @@ How each lifecycle event behaves:
|
|
|
93
93
|
- A missing or ambiguous native anchor skips compaction instead of choosing the last steering message.
|
|
94
94
|
- On resume, native `buildContextEntries()` and TUI rendering omit the older completed prefix.
|
|
95
95
|
- Unknown or smaller usage skips the request, and custom Pi retention settings may still decline it benignly.
|
|
96
|
-
- These prevent State Flow-owned shortening: foreign custom context in the prefix that would be removed, bootstrap/abort/error, Stop and pending input.
|
|
96
|
+
- These prevent State Flow-owned shortening: foreign custom context in the prefix that would be removed, bootstrap/abort/error, Stop and pending input. Pi's context-invisible `codemode-store` metadata does not block shortening: it stays on the full native branch, so `load()` and deletions survive compaction and resume. Unknown metadata and all `custom_message` entries remain protected.
|
|
97
97
|
- Obsolete or inactive owned requests are canceled before their hook can fall through to a model summary, and a late completion cannot clear a newer request.
|
|
98
98
|
- Ordinary manual/threshold/overflow compaction stays native and may preserve unfinished work not yet patched into memory.
|
|
99
99
|
|
|
@@ -281,6 +281,7 @@ A missing or expired private retained boundary is unavailable; State Flow does n
|
|
|
281
281
|
|
|
282
282
|
**After a selected-boundary failure:**
|
|
283
283
|
|
|
284
|
+
- If the selected Active boundary cannot be restored, live inference is aborted before the provider instead of silently sending native history under an inactive fallback. Reload/resume retains this fence. Select a valid branch or explicitly Start from current same-session memory; explicit Passive/Off permits native context without repairing historical authority. Signal-less inspection remains observational.
|
|
284
285
|
- Passive may still expose current global/CWD memory, but never the unavailable historical session layer or permission to publish an empty replacement.
|
|
285
286
|
- Historical session reads and every `patch_state` refuse without changing canonical files or appending substitute checkpoints.
|
|
286
287
|
- Passive/Off selection stays available and records the native policy/write fence described above. A later reload may expose validated current memory read-only, not the unavailable selected history.
|
|
@@ -26,10 +26,11 @@ type ActiveEntry = {
|
|
|
26
26
|
message?: { role?: unknown; stopReason?: unknown; content?: unknown; timestamp?: unknown };
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
function
|
|
29
|
+
function contextInvisibleEntry(entry: ActiveEntry): boolean {
|
|
30
|
+
// Codemode reconstructs its store from the full native branch, not the compacted model context.
|
|
30
31
|
return entry.type === "custom"
|
|
31
32
|
&& typeof entry.customType === "string"
|
|
32
|
-
&& entry.customType.startsWith("state-flow-");
|
|
33
|
+
&& (entry.customType.startsWith("state-flow-") || entry.customType === "codemode-store");
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
export function shouldRequestStateFlowCompaction(usage: { tokens: number | null } | undefined): boolean {
|
|
@@ -70,7 +71,7 @@ export function planStateFlowCompaction(
|
|
|
70
71
|
const isRunAnchor = (entry: ActiveEntry) => entry.type === "message" && entry.message?.role === "user" && entry.message.timestamp === runAnchorTimestamp;
|
|
71
72
|
const keep = entries.findIndex(isRunAnchor);
|
|
72
73
|
if (keep < 0 || keep > terminal || entries.findLastIndex(isRunAnchor) !== keep) return undefined;
|
|
73
|
-
if (entries.slice(0, keep).some((entry) => entry.type === "custom_message" || (entry.type === "custom" && !
|
|
74
|
+
if (entries.slice(0, keep).some((entry) => entry.type === "custom_message" || (entry.type === "custom" && !contextInvisibleEntry(entry)))) return undefined;
|
|
74
75
|
const firstKeptEntryId = entries[keep]?.id;
|
|
75
76
|
const leafId = entries.at(-1)?.id;
|
|
76
77
|
if (typeof firstKeptEntryId !== "string" || typeof leafId !== "string") return undefined;
|
|
@@ -93,7 +93,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
93
93
|
let scopeStates: ScopedStates = { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
94
94
|
let effectiveState: SemanticState = {};
|
|
95
95
|
let branchStartsWithoutRuntime = false;
|
|
96
|
-
let
|
|
96
|
+
let selectedBoundaryError: { expired: boolean; blockInference: boolean } | undefined;
|
|
97
97
|
let modePersistenceError: string | undefined;
|
|
98
98
|
/** One pending inactive-mode persistence; later inactive choices coalesce until it publishes. */
|
|
99
99
|
const inactivePersistence = new OwnedOperationSlot<InactivePersistence>();
|
|
@@ -448,7 +448,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
448
448
|
runtime = undefined;
|
|
449
449
|
snapshot = emptySnapshot("off");
|
|
450
450
|
branchStartsWithoutRuntime = withoutRuntime;
|
|
451
|
-
|
|
451
|
+
selectedBoundaryError = undefined;
|
|
452
452
|
modePersistenceError = persistenceError;
|
|
453
453
|
forkInitialization = reason === "fork";
|
|
454
454
|
installScopeStates();
|
|
@@ -464,7 +464,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
464
464
|
runtime = createRuntime(ctx);
|
|
465
465
|
installScopeStates();
|
|
466
466
|
branchStartsWithoutRuntime = false;
|
|
467
|
-
|
|
467
|
+
selectedBoundaryError = undefined;
|
|
468
468
|
forkInitialization = sessionStartReason === "fork";
|
|
469
469
|
let selection: BranchSelection = { kind: "settled" };
|
|
470
470
|
let skipped = 0;
|
|
@@ -492,6 +492,8 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
492
492
|
selection = { kind: "fork", source, checkpoint: sourceFence?.persistenceError !== undefined
|
|
493
493
|
? { ...selected.checkpoint, mode: sourceFence.mode ?? config.inactiveMode } : selected.checkpoint };
|
|
494
494
|
} catch (error) {
|
|
495
|
+
selectedBoundaryError = { expired: error instanceof HistoryBoundaryExpiredError,
|
|
496
|
+
blockInference: selected.checkpoint.mode === "active" && requestedMode === undefined };
|
|
495
497
|
snapshot = selectedBoundaryFailure(diagnosticText(error), inactiveSelection(selected.checkpoint.mode));
|
|
496
498
|
}
|
|
497
499
|
} else if (selected.kind === "boundary") {
|
|
@@ -548,6 +550,13 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
548
550
|
if (!isCurrent()) throw new Error("State Flow branch selection changed while awaiting publication");
|
|
549
551
|
};
|
|
550
552
|
let accepted = false;
|
|
553
|
+
const failSelection = (error: unknown): void => {
|
|
554
|
+
selectedBoundaryError = { expired: error instanceof HistoryBoundaryExpiredError,
|
|
555
|
+
blockInference: pending.requestedMode === undefined && (selection.kind === "auto-start"
|
|
556
|
+
|| ((selection.kind === "restore" || selection.kind === "fork") && selection.checkpoint.mode === "active")) };
|
|
557
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), inactiveSelection(snapshot.config.mode));
|
|
558
|
+
installScopeStates();
|
|
559
|
+
};
|
|
551
560
|
// Install accepted memory before native writes; later ancillary failures cannot revert or replay it.
|
|
552
561
|
const accept = (candidate: TemporalRuntime, next: Snapshot, nativeWrites: () => void): void => {
|
|
553
562
|
accepted = true;
|
|
@@ -616,9 +625,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
616
625
|
return;
|
|
617
626
|
}
|
|
618
627
|
if (!isCurrent()) return;
|
|
619
|
-
|
|
620
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error), snapshot.config.mode === "active" ? config.inactiveMode : snapshot.config.mode);
|
|
621
|
-
installScopeStates();
|
|
628
|
+
failSelection(error);
|
|
622
629
|
} finally {
|
|
623
630
|
pending.awaitingAcceptance = false;
|
|
624
631
|
}
|
|
@@ -637,8 +644,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
637
644
|
} catch (error) {
|
|
638
645
|
// Host failures before acceptance leave the selection unavailable, never invented empty memory.
|
|
639
646
|
if (accepted || !isCurrent()) return;
|
|
640
|
-
|
|
641
|
-
installScopeStates();
|
|
647
|
+
failSelection(error);
|
|
642
648
|
} finally {
|
|
643
649
|
pending.awaitingAcceptance = false;
|
|
644
650
|
branchRestoration.release(pending);
|
|
@@ -668,7 +674,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
668
674
|
function settleSelection(ctx: ExtensionContext, reason: unknown, notifyRecovery: boolean, skipped: number, selectedMemory: boolean): void {
|
|
669
675
|
const failure = !isActive() && snapshot.meta.validation?.attempt === 0 ? snapshot.meta.validation.error : undefined;
|
|
670
676
|
if (failure !== undefined && notifyRecovery && !modePersistenceError) {
|
|
671
|
-
if (
|
|
677
|
+
if (selectedBoundaryError?.expired) notifyProblem(ctx, "State Flow history is outside the retained temporal window; /state-flow-active can use current session memory.", "warning");
|
|
672
678
|
else notifyProblem(ctx, `State Flow restore failed: ${failure}`, "error");
|
|
673
679
|
}
|
|
674
680
|
const continuation = selectedMemory ? selectedContinuation(ctx, reason) : undefined;
|
|
@@ -909,14 +915,14 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
909
915
|
? findPassiveStopBoundary(ctx.sessionManager.getBranch(), owner, PASSIVE_STOP_ENTRY_TYPE) : undefined;
|
|
910
916
|
const bootstrap = hasPriorConversation(ctx.sessionManager.getBranch()) || passiveContinuation !== undefined || boundary !== undefined;
|
|
911
917
|
const activated = current ? resumeEpisode(current, bootstrap) : startEpisode(bootstrap);
|
|
912
|
-
const recoveredCurrent =
|
|
918
|
+
const recoveredCurrent = selectedBoundaryError?.expired;
|
|
913
919
|
publish(activated);
|
|
914
920
|
accepted = true;
|
|
915
921
|
inactivePersistence.cancel();
|
|
916
922
|
runtime = selected = activation;
|
|
917
923
|
snapshot = activated;
|
|
918
924
|
activeContext = ctx;
|
|
919
|
-
|
|
925
|
+
selectedBoundaryError = undefined;
|
|
920
926
|
modePersistenceError = undefined;
|
|
921
927
|
installScopeStates();
|
|
922
928
|
const continuation = passiveContinuation ?? bootstrapContinuation ?? (deferred ? selectedContinuation(ctx, deferred.reason) : undefined);
|
|
@@ -984,6 +990,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
984
990
|
/** Local policy, tools and UI change before any canonical wait. */
|
|
985
991
|
function applyInactiveMode(ctx: ExtensionContext, mode: InactiveMode): void {
|
|
986
992
|
snapshot = deactivateEpisode(snapshot, mode);
|
|
993
|
+
if (selectedBoundaryError) selectedBoundaryError.blockInference = false;
|
|
987
994
|
clearRunTransient();
|
|
988
995
|
syncStateFlowTools();
|
|
989
996
|
updateUi(ctx);
|
|
@@ -1064,7 +1071,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1064
1071
|
acquisition.reset();
|
|
1065
1072
|
runtime = undefined;
|
|
1066
1073
|
snapshot = emptySnapshot("off");
|
|
1067
|
-
|
|
1074
|
+
selectedBoundaryError = undefined;
|
|
1068
1075
|
branchStartsWithoutRuntime = preRuntime;
|
|
1069
1076
|
forkInitialization = pendingFork;
|
|
1070
1077
|
deferredBranch = { owner: address.key, cwd: ctx.cwd, reason: pendingFork ? "fork" : undefined };
|
|
@@ -1296,6 +1303,12 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1296
1303
|
}
|
|
1297
1304
|
|
|
1298
1305
|
function prepareContext(messages: AgentMessage[], ctx: ExtensionContext): ReturnType<typeof projectContext> | Promise<ReturnType<typeof projectContext>> {
|
|
1306
|
+
// Pi continues after hook errors; public Abort fences inference instead of exposing the native history fallback.
|
|
1307
|
+
if (selectedBoundaryError?.blockInference && ctx.signal && !shuttingDown) {
|
|
1308
|
+
ctx.abort();
|
|
1309
|
+
notifyProblem(ctx, "State Flow inference blocked: Active memory restoration failed. Select a valid branch or use /state-flow-active for current memory; /state-flow-passive or /state-flow-off explicitly permits native context.", "error");
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1299
1312
|
const pending = inferencePreparation.current;
|
|
1300
1313
|
const selected = runtime;
|
|
1301
1314
|
const operationSignal = ctx.signal;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@llblab/pi-clean-room": "0.3.0",
|
|
47
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.5",
|
|
50
50
|
"@llblab/pi-telegram": "0.51.6",
|
|
51
51
|
"@llblab/skills": "1.15.0"
|
|
52
52
|
},
|