@llblab/pi-kit 0.10.1 → 0.10.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/CHANGELOG.md +4 -0
- package/README.md +1 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-state-flow/README.md +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +15 -6
- package/node_modules/@llblab/pi-state-flow/lib/publication.ts +16 -3
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/package.json +2 -2
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.10.2 - 2026-09-11
|
|
6
|
+
|
|
7
|
+
- `State Flow Publication`: Advances the exact State Flow pin to `0.9.3`, syncing the publication-queue lineage-rewrite hotfix. A queued target orphaned by a reset or re-initialized journal now retargets the live lineage instead of failing every `turn_end`, and deferred remote publication is reported accurately. The package set, resource inventory, and explicit load order remain unchanged.
|
|
8
|
+
|
|
5
9
|
## 0.10.1 - 2026-09-11
|
|
6
10
|
|
|
7
11
|
- `State Flow Telegram Surface`: Advances the exact State Flow pin to `0.9.2`, so State Flow appears in Telegram exactly once: the pre-Settings section button carries the live status (`🌀 State Flow: #N`, `⚫️ State Flow` while off) and the duplicated Status-screen row is removed. The package set, resource inventory, and explicit load order remain unchanged.
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Package links lead to the owning repositories for usage, documentation, issues,
|
|
|
14
14
|
| [`@llblab/pi-clean-room`](https://github.com/llblab/pi-clean-room) | `0.1.1` | Isolated nested Pi TUI with explicitly selected extensions |
|
|
15
15
|
| [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.9.4` | Compact Codex/Spark subscription-limit status |
|
|
16
16
|
| [`@llblab/pi-grow-loop`](https://github.com/llblab/pi-grow-loop) | `0.8.1` | Visible continuation scheduling and bounded worker Skills |
|
|
17
|
-
| [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.9.
|
|
17
|
+
| [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.9.3` | Atomic scoped state cohorts with final-eligibility resolution |
|
|
18
18
|
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.45.5` | Telegram companion, queues, files, voice, controls, and Generative Apps guidance |
|
|
19
19
|
| [`@llblab/skills`](https://github.com/llblab/skills) | `1.15.0` | Portable workflows for engineering, review, design, context maintenance, and other focused tasks |
|
|
20
20
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- Read and write only regular non-symlink State Flow-owned files at those exact paths, publish each file by same-directory atomic rename, preserve arbitrary repository contents, and classify ownership exactly; never use repository-wide staging. Retain opaque source bytes for file identity and rollback, not decoded-text reconstructions. Use validated structural JSON equality for in-process semantic comparisons; reserve cryptographic hashes for compact identities that cross inference, persistence, process, or source-freshness boundaries. Serialize cooperating Git publications through a common-Git-directory publication lock; low-level file helpers require caller exclusion. Recheck prepared bases before each replacement/deletion and restore only bytes still matching the publisher's own output. Preserve detected concurrent changes and report unresolved rollback conflicts; do not claim kernel-atomic multi-file CAS against nonparticipating writers. Markdown discovery uses its independently configured source root, defaulting to `knowledge/` beneath Pi's agent directory; a storage-root override must not redirect source discovery.
|
|
23
23
|
- At instance initialization resolve the repository, CWD key, and session key; load each scope's anchored checkpoint and tail, materialize at the selected temporal boundary, then overlay `global → cwd → session`. Install cached view and publication basis atomically only after successful restoration/initialization; unavailable publication is an error, not a semantic no-op. Preserve the selected revision across transient restore failure so an explicit start can retry it. A genuinely new session gets an empty session layer and inherits only global/CWD values; it must never reuse another same-CWD session layer.
|
|
24
24
|
- Bind every loaded scope and active Pi branch/checkpoint to the corresponding State Flow Git revision. Resume and tree restoration must recover branch-correct runtime config and semantic layers from that revision without blindly importing Git `HEAD`; reading an older revision must use object-level Git reads and never reset or check out the shared repository worktree.
|
|
25
|
-
- Every accepted semantic change, including session-only and response-only changes, atomically updates affected checkpoint/tail pairs and temporal metadata and creates one immediate local Git commit when Git is available; each commit stages the complete non-ignored worktree delta (`git add -A` semantics, including manual deletions) before overlaying exact prepared State Flow outputs, respects `.gitignore`, keeps State Flow-owned active files under compare-and-swap protection, and synchronizes the caller-visible index to the committed tree. `remotePublication` is branch runtime policy: `turn-end` queues only the newest accepted target for asynchronous non-interactive push, `off` remains local-only, and `transition` preserves synchronous legacy behavior. Queue state is operational metadata beneath the Git common directory, not semantic history; use exact targets, destination identity, symlink-safe atomic CAS, descendant-
|
|
25
|
+
- Every accepted semantic change, including session-only and response-only changes, atomically updates affected checkpoint/tail pairs and temporal metadata and creates one immediate local Git commit when Git is available; each commit stages the complete non-ignored worktree delta (`git add -A` semantics, including manual deletions) before overlaying exact prepared State Flow outputs, respects `.gitignore`, keeps State Flow-owned active files under compare-and-swap protection, and synchronizes the caller-visible index to the committed tree. `remotePublication` is branch runtime policy: `turn-end` queues only the newest accepted target for asynchronous non-interactive push, `off` remains local-only, and `transition` preserves synchronous legacy behavior. Queue state is operational metadata beneath the Git common directory, not semantic history; use exact targets, destination identity, symlink-safe atomic CAS, descendant coalescing with lineage-rewrite recovery that retargets the live commit, cross-process worker leases, restart recovery, and truthful failure diagnostics. Remote failure never rolls back accepted state or regenerates an answer. A repository with no remote is intentionally local-only; only Git executable `ENOENT` authorizes file-only mode.
|
|
26
26
|
- Register `patch_state` as the sole model-authored semantic mutation protocol. It accepts optional fixed `global`, `cwd`, and `session` semantic patches plus optional `final:true`; require at least one scope or `final:true`, reject unknown fields, `final:false`, empty supplied scopes, material no-ops, and the retired `{scope, patch}` / `unchanged` grammar. Validate every supplied scope against one causal basis and publish it all-or-nothing with one identity, temporal boundary, and durable cohort. Never accept model-authored `response`.
|
|
27
27
|
- Treat every semantic `patch_state` as a strict inference barrier. Give it sequential execution mode, inspect Pi's synchronized current assistant message during tool preflight, require exactly one `patch_state` call in that response, and block every sibling tool call before execution. Every enabled iteration begins terminal-ineligible; only a successful call containing `final:true` latches eligibility for the next accepted `turn_end`, without stopping later reasoning, tools, or patches. If a terminal draft ends before eligibility, preserve it as the runtime-owned response at that `turn_end` and start at most two same-run fallback turns whose only purpose is the `final:true` patch; an eligible draft whose final validation fails after a later acquisition follows the same path. Fallback turns never enter `response`, and their only instruction is to apply `patch_state` with any durable changes and `final:true`, or `{final:true}` alone. A successful fallback closes resolution with the preserved answer intact; two failed fallbacks close the iteration with the preserved answer and current state plus one bounded warning and a finalization diagnostic. Failed patch calls do not consume fallback turns. A subsequent legal patch remains possible, and only an accepted ordinary answer is reconciled.
|
|
28
28
|
- Reconcile `response` only from the actually accepted ordinary assistant answer at `turn_end`; it remains runtime-owned. State Flow has no terminal HTML-comment mutation protocol and does not parse generic service comments. The first terminal draft that cannot be reconciled is preserved as the iteration response rather than discarded; fallback turns during pending resolution never reach `response`. Other extensions retain ownership of their own comments and output handling.
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
> Each release keeps at most 8 outcome records of at most 512 characters.
|
|
4
4
|
|
|
5
|
+
## 0.9.3: Publication queue lineage-rewrite hotfix
|
|
6
|
+
|
|
7
|
+
- `Publication`: Retarget a queued target orphaned by a journal reset or re-initialization to the live lineage and keep publishing, instead of failing every `turn_end` with "Publication queue targets diverge"; the retired commit stays in local Git history.
|
|
8
|
+
- `Diagnostics`: Record the retired target in opt-in diagnostics and report a deferred remote publication accurately instead of claiming the final response could not be reconciled.
|
|
9
|
+
|
|
5
10
|
## 0.9.2: Single Telegram surface
|
|
6
11
|
|
|
7
12
|
- `Interop`: State Flow now appears on Telegram exactly once. The inline main-menu section button before Settings carries the live status (`🌀 State Flow: #N`, `⚫️ State Flow` while off) and opens the existing start/stop submenu; the duplicated Status-screen row is removed, so the same value no longer renders in two places. Fail-open registration and shutdown disposal are unchanged.
|
|
@@ -175,7 +175,7 @@ Session `config.json` owns runtime behavior such as `enabled`. Session `meta.jso
|
|
|
175
175
|
|
|
176
176
|
Git-backed Pi checkpoint data is exactly `{"revision":"<full commit hash>"}`; file-only checkpoints use `{"revision":"file:<64 lowercase hex>"}` for an exact current-cohort reference. `{"disabled":true}` denotes an ordinary disabled branch without durable runtime. Configuration, counters, specification, lineage, and semantic state are not copied into these entries. Older config/meta and state-bearing checkpoints remain read-only compatibility input. Invalid immutable pointer targets fall back through the selected branch; transient publication-lock failure instead retains the selected revision for retry. Explicit start on a pre-runtime branch establishes an empty session origin, even if a later branch already created same-session files. Shared streams remain unchanged and the later session state remains recoverable from its Git revision; it is not imported into the new origin. Legacy checkpoint syntax and immutable targets undergo the same fallback checks, while an unanchored legacy semantic payload cannot be silently reduced to an ordinary-disabled marker. Stop on an unproven branch fails visibly instead of turning failed recovery into permission to replace existing runtime; select a valid checkpoint before retrying.
|
|
177
177
|
|
|
178
|
-
Every materially effective semantic transition, including response-only and session-only changes, immediately publishes affected checkpoint/tail pairs and necessary lineage metadata. In Git mode each accepted cohort gets its own consistent local commit; `turn-end` queues the newest target for asynchronous replication, `off` stays local, and compatibility `transition` attempts the push synchronously. Intermediate patches are not batched until turn end. With no remote configured, Git persistence is intentionally local-only, not a pending publication error. With Git absent, persistence remains file-only and does not report fake commits or pending pushes. One model-authored call may update global, CWD, and session together; every supplied scope shares one validation basis, transition identity, temporal boundary, and durable commit. Config-only changes may persist runtime state but never invent semantic transitions.
|
|
178
|
+
Every materially effective semantic transition, including response-only and session-only changes, immediately publishes affected checkpoint/tail pairs and necessary lineage metadata. In Git mode each accepted cohort gets its own consistent local commit; `turn-end` queues the newest target for asynchronous replication, `off` stays local, and compatibility `transition` attempts the push synchronously. A reset or re-initialized journal rewrites the lineage; the queue then retargets the live commit and keeps the retired one in local history instead of blocking later turns. Intermediate patches are not batched until turn end. With no remote configured, Git persistence is intentionally local-only, not a pending publication error. With Git absent, persistence remains file-only and does not report fake commits or pending pushes. One model-authored call may update global, CWD, and session together; every supplied scope shares one validation basis, transition identity, temporal boundary, and durable commit. Config-only changes may persist runtime state but never invent semantic transitions.
|
|
179
179
|
|
|
180
180
|
Writes use regular non-symlink owned files, same-directory atomic rename, isolated Git indexes, and compare-and-swap against the reconciled base. Cooperating State Flow Git publishers hold a common-Git-directory publication lock through capture, commit, and rollback. Raw bytes own file identity and recovery; non-UTF-8 explanatory journals are not reconstructed from decoded strings. Per-file publication checks catch changed bases. Both semantic and migration publishers retain exact prepared-output receipts: Git commits those bytes, not later worktree contents, and rollback requires the receipt to preserve detected external changes rather than replacing them. Low-level file helpers require caller exclusion; this is not kernel-atomic multi-file CAS against writers ignoring the protocol. An existing publication lock fails before writes and is not silently stolen; reconcile its active or interrupted owner before retrying. Scope keys mirror Pi's CWD/session directory names and retain separately verified canonical identity provenance inside owned state. Conflicts fail rather than silently selecting another scope or auto-merging semantic state. Every Git commit stages the complete non-ignored worktree delta — tracked edits, new files, and manual deletions, respecting `.gitignore` — then overlays the exact prepared State Flow outputs and synchronizes the caller-visible index to the committed tree, so no artificial staged or unstaged entries remain; unrelated user worktree changes may therefore be committed while ignored files stay untouched. With distinct roots, the Knowledge repository's files, index, HEAD, locks, and remote are not used for state publication; keep custom stores separate from sources to retain this isolation.
|
|
181
181
|
|
|
@@ -132,7 +132,7 @@ The persisted `remotePublication` policy is:
|
|
|
132
132
|
|
|
133
133
|
A destination is identified by canonical Git common directory, remote and full ref. Queue files live beneath the Git common directory and are not semantic history.
|
|
134
134
|
|
|
135
|
-
The queue uses exact commit targets, strict versioned JSON, symlink-safe atomic writes, CAS receipts and exclusive writer locks. A proven descendant may supersede an older target;
|
|
135
|
+
The queue uses exact commit targets, strict versioned JSON, symlink-safe atomic writes, CAS receipts and exclusive writer locks. A proven descendant may supersede an older target; a journal lineage rewrite retargets the live commit and records the retired target, while changed destinations fail closed.
|
|
136
136
|
|
|
137
137
|
After accepted response reconciliation, an asynchronous non-interactive worker pushes the newest target. Queue failure never rolls back semantic state or regenerates an answer. Failed and interrupted attempts remain retryable across restart. Destination-scoped worker leases prevent cross-process overlap, preserve live owners, recover proven-dead owners and use exact release tokens. Confirmation removes only the exact completed target; a newer descendant remains queued.
|
|
138
138
|
|
|
@@ -252,7 +252,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
252
252
|
if (mode !== "turn-end" || target === undefined || !/^[0-9a-f]{40,64}$/.test(target)) return;
|
|
253
253
|
turnPublicationTarget = target;
|
|
254
254
|
try {
|
|
255
|
-
enqueueTurnPublication();
|
|
255
|
+
enqueueTurnPublication(ctx);
|
|
256
256
|
launchPublicationWorker();
|
|
257
257
|
} catch (error) {
|
|
258
258
|
ctx.ui.notify(
|
|
@@ -288,7 +288,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
288
288
|
return true;
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
function enqueueTurnPublication(): void {
|
|
291
|
+
function enqueueTurnPublication(ctx: ExtensionContext): void {
|
|
292
292
|
const target = turnPublicationTarget;
|
|
293
293
|
turnPublicationTarget = undefined;
|
|
294
294
|
if (!target) return;
|
|
@@ -297,7 +297,12 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
297
297
|
const path = publicationQueuePath(destination);
|
|
298
298
|
const previous = loadPublicationQueue(path);
|
|
299
299
|
const next = previous
|
|
300
|
-
? coalescePublicationTarget(previous, destination, target, (ancestor, descendant) => isGitCommitAncestor(repositoryRoot, ancestor, descendant)
|
|
300
|
+
? coalescePublicationTarget(previous, destination, target, (ancestor, descendant) => isGitCommitAncestor(repositoryRoot, ancestor, descendant), {
|
|
301
|
+
onDivergedLineage: (dropped) => recordDiagnostic(
|
|
302
|
+
`Retired publication queue target ${dropped.target} after a journal lineage rewrite; retargeting to ${target}`,
|
|
303
|
+
"publication-conflict", ctx,
|
|
304
|
+
),
|
|
305
|
+
})
|
|
301
306
|
: createPublicationQueue(destination, target);
|
|
302
307
|
savePublicationQueue(path, next, previous);
|
|
303
308
|
}
|
|
@@ -365,7 +370,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
365
370
|
if (mode === "turn-end") {
|
|
366
371
|
turnPublicationTarget = target;
|
|
367
372
|
try {
|
|
368
|
-
enqueueTurnPublication();
|
|
373
|
+
enqueueTurnPublication(ctx);
|
|
369
374
|
launchPublicationWorker();
|
|
370
375
|
} catch (error) {
|
|
371
376
|
ctx.ui.notify(`State Flow retained local state; asynchronous publication recovery is deferred: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
@@ -1035,17 +1040,21 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1035
1040
|
updateUi(ctx);
|
|
1036
1041
|
return;
|
|
1037
1042
|
}
|
|
1043
|
+
let responseCommitted = false;
|
|
1038
1044
|
try {
|
|
1039
1045
|
const response = finalizedAssistantResponse(event.message);
|
|
1040
1046
|
const stage = stageScopedTransition(scopeStates, { transitions: [], response }, [], runtime!.causalBasis());
|
|
1041
1047
|
commitStage(stage, ctx, true);
|
|
1048
|
+
responseCommitted = true;
|
|
1042
1049
|
bootstrapContinuation = undefined;
|
|
1043
1050
|
rehydrationPhase = "step";
|
|
1044
|
-
enqueueTurnPublication();
|
|
1051
|
+
enqueueTurnPublication(ctx);
|
|
1045
1052
|
if (snapshot.meta.remotePublication?.mode === "turn-end") launchPublicationWorker();
|
|
1046
1053
|
} catch (error) {
|
|
1047
1054
|
recordDiagnostic(error instanceof Error ? error.message : String(error), "finalization", ctx);
|
|
1048
|
-
ctx.ui.notify(
|
|
1055
|
+
ctx.ui.notify(responseCommitted
|
|
1056
|
+
? `State Flow committed the final response; remote publication is deferred: ${error instanceof Error ? error.message : String(error)}`
|
|
1057
|
+
: `State Flow could not reconcile the final response: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1049
1058
|
} finally {
|
|
1050
1059
|
responseAwaitingReconciliation = false;
|
|
1051
1060
|
}
|
|
@@ -81,13 +81,26 @@ export function createPublicationQueue(destination: RemotePublicationDestination
|
|
|
81
81
|
return { version: 1, destination: structuredClone(destination), target, status: "pending", attempt: 0 };
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
export
|
|
84
|
+
export interface PublicationCoalesceObserver {
|
|
85
|
+
/** Called with the retired record when a journal lineage rewrite orphans its target. */
|
|
86
|
+
onDivergedLineage?: (previous: PublicationQueueState) => void;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function coalescePublicationTarget(state: PublicationQueueState, destination: RemotePublicationDestination, target: string, isAncestor: CommitAncestor, observer?: PublicationCoalesceObserver): PublicationQueueState {
|
|
85
90
|
validatePublicationQueue(state);
|
|
86
91
|
if (!COMMIT.test(target)) throw new Error("Publication queue target must be an exact commit");
|
|
87
92
|
if (remotePublicationDestinationKey(state.destination) !== remotePublicationDestinationKey(destination)) throw new Error("Publication queue destination changed");
|
|
88
93
|
if (target === state.target || isAncestor(target, state.target)) return structuredClone(state);
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
const previous = structuredClone(state);
|
|
95
|
+
if (!isAncestor(state.target, target)) {
|
|
96
|
+
// A reset or re-initialized journal rewrites the lineage. The queued commit can never
|
|
97
|
+
// fast-forward the remote again, so retarget the live lineage instead of wedging every
|
|
98
|
+
// later turn-end; the retired commit stays in the local Git object store.
|
|
99
|
+
observer?.onDivergedLineage?.(previous);
|
|
100
|
+
const { confirmed: _confirmed, error: _error, ...live } = previous;
|
|
101
|
+
return { ...live, target, status: "pending", attempt: 0 };
|
|
102
|
+
}
|
|
103
|
+
return { ...previous, target, status: "pending", attempt: 0, error: undefined };
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
export function parsePublicationQueue(content: string): PublicationQueueState {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@llblab/pi-clean-room": "0.1.1",
|
|
45
45
|
"@llblab/pi-codex-usage": "0.9.4",
|
|
46
46
|
"@llblab/pi-grow-loop": "0.8.1",
|
|
47
|
-
"@llblab/pi-state-flow": "0.9.
|
|
47
|
+
"@llblab/pi-state-flow": "0.9.3",
|
|
48
48
|
"@llblab/pi-telegram": "0.45.5",
|
|
49
49
|
"@llblab/skills": "1.15.0"
|
|
50
50
|
},
|