@llblab/pi-kit 0.22.2 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-grow-loop/AGENTS.md +10 -6
- package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +7 -0
- package/node_modules/@llblab/pi-grow-loop/README.md +2 -0
- package/node_modules/@llblab/pi-grow-loop/dist/index.d.ts +33 -0
- package/node_modules/@llblab/pi-grow-loop/dist/index.js +286 -0
- package/node_modules/@llblab/pi-grow-loop/dist/pi-grow-loop/index.d.ts +1 -0
- package/node_modules/@llblab/pi-grow-loop/dist/pi-grow-loop/index.js +1 -0
- package/node_modules/@llblab/pi-grow-loop/dist/skills/grow-loop/SKILL.md +117 -0
- package/node_modules/@llblab/pi-grow-loop/dist/skills/while-true/SKILL.md +233 -0
- package/node_modules/@llblab/pi-grow-loop/index.ts +67 -12
- package/node_modules/@llblab/pi-grow-loop/package.json +9 -8
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +23 -17
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +5 -3
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +11 -0
- package/node_modules/@llblab/pi-state-flow/README.md +18 -6
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/index.js +1 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +7 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.d.ts +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +54 -40
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +20 -20
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +755 -325
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +14 -17
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +4 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +65 -23
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +16 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +32 -15
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +28 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +276 -26
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +5 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +36 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +3 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +11 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +150 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +14 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +52 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +8 -8
- 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 +3 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +53 -7
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +84 -44
- package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +12 -2
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +6 -4
- package/node_modules/@llblab/pi-state-flow/docs/performance.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +28 -8
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +41 -14
- package/node_modules/@llblab/pi-state-flow/index.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +5 -5
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +8 -3
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +57 -40
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +20 -20
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +719 -316
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +16 -18
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +60 -24
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +34 -21
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +290 -25
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +37 -2
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +4 -1
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +141 -22
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +60 -19
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +8 -8
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +3 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-telegram/README.md +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/skills.d.ts +7 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/skills.js +32 -7
- package/node_modules/@llblab/pi-telegram/dist/package.json +3 -8
- package/node_modules/@llblab/pi-telegram/lib/skills.ts +43 -7
- package/node_modules/@llblab/pi-telegram/package.json +3 -8
- package/node_modules/@llblab/pi-telegram/scripts/build-dist.mjs +103 -32
- package/package.json +6 -6
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# Backlog
|
|
2
2
|
|
|
3
|
-
No open implementation
|
|
3
|
+
No open implementation items for the 0.19.0 release scope.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Release publication is owned by the version-tag workflow in `.github/workflows/release.yml`; a tag alone is not completion. Verify successful automation, the matching published GitHub Release and npm package before reporting the release complete.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Completed outcomes are recorded in [CHANGELOG.md](CHANGELOG.md). Maintained contracts and acceptance evidence belong to [docs](docs/README.md), including the accepted [persistence limitation](docs/filesystem-recovery.md#power-loss-durability) and [library API compatibility](docs/compatibility.md#state-flow-library-api-compatibility).
|
|
8
|
+
|
|
9
|
+
Live-host, provider and platform verification limits remain documented in [SDK compatibility](docs/compatibility.md). Production-store conversion and installed-host activation remain separate operator decisions; see [usage and recovery](docs/usage.md).
|
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
> Each release keeps at most 8 outcome records of at most 512 characters.
|
|
4
4
|
|
|
5
|
+
## 0.19.0: Awaited Memory and Lossless Mode Changes
|
|
6
|
+
|
|
7
|
+
- `Lossless context toggles`: Stop retains uncompiled conversation through Start/Stop, reload and restart without resurrecting compiled history. Interrupted runs keep their native anchor or conservatively retain available context. Uncheckpointed requests and interrupted boundary continuations survive idle Stop/reload even without a specification; successful bootstrap releases the extra context. Completed idle Stop stays bounded.
|
|
8
|
+
- `Awaited activation and restoration`: Startup/tree/fork/auto-start and fenced reload await owned selection. Start activates current same-session memory without claiming expired history. Restoration, auto-start initialization and fork copying survive Stop and cancelled Start waiters; their acceptance applies passive policy and permits configured passive patching. Selection/shutdown still revoke stale work. Acceptance installs memory; late failures never roll it back. Active Start is inert.
|
|
9
|
+
- `Awaited fail-operational Stop`: Local policy and passive context switch before cancelable publication waiting. Runtime-only acceptance preserves semantic/provenance files; repeats share the wait. Selection/shutdown/successful Start cancel obsolete work. Failed writes retain cached memory/context and a native fence until accepted Start. Telegram acknowledges early, drops stale results and keeps legacy ports. Forks inherit policy, not fences; fenced reload stays read-only.
|
|
10
|
+
- `Compact protocol and diagnostics`: Deduplicates injected plane, scope, response, stewardship and provenance rules without changing tools or memory semantics. Short errors retain operation, scope/path suffix and cause across tools, lifecycle, status and Telegram. Nested/aggregate causes survive transport; operand-first elision preserves Unicode and apostrophes. Opt-in logs retain full targets/input; failed Start emits one notice and tool headings keep a blank line.
|
|
11
|
+
- `Awaited memory transactions`: Patches preserve independent shared fields and acceptance order; repeats add no revisions. Setup/cards/evidence accept together under cancelable exclusion/CAS. Preparation/maintenance accept at native `context` or abort inference; runtime-only writes preserve history/provenance. Restore/fork await exact private authority over live shared streams; duplicate/competing Skill writes preserve ownership. Rejected drafts never install; private/selection fences remain.
|
|
12
|
+
- `Awaited response acceptance`: Final answers wait cancelably for current shared memory and publish response plus completed-run lifecycle together. Failed publication installs no draft or shared adoption. Native Abort, Stop, selection/run changes, shutdown and superseding answers withdraw obsolete waits without erasing newer work or write fences. Independent-process Pi tests prove waits beyond two seconds, private isolation, next-provider visibility and retained unfinished runs on cancellation.
|
|
13
|
+
- `Awaited inspection`: Telegram and continuation readers await coherent cohorts without publication or private substitution. Telegram pairs state/revisions, revokes stale receipts and keeps legacy ports; callbacks acknowledge early and escape late errors. Library inspection APIs now return Promises: callers must await results. Six legacy runtime adapters remain. Read-only current-memory recovery preserves private authority without enabling writes. Missing/invalid evidence never initializes memory.
|
|
14
|
+
- `Awaited backup capture`: Backup and canonical mutexes wait cancelably, capture one cohort and preserve replacement owners. Git runs outside canonical exclusion; branch/head drift is rechecked. Settlement awaits completion and shutdown cancels pending reads. Pi 0.87 lacks a settlement Abort signal, so contended backup explicitly defers instead of waiting uninterruptibly. Accepted memory/Stop fences survive; native and independent-writer tests cover ownership, cancellation and coherent commits.
|
|
15
|
+
|
|
5
16
|
## 0.18.1: Backup Reliability
|
|
6
17
|
|
|
7
18
|
- `Push lifecycle`: Bounds backup replication to one in-flight push per repository, skips overlap until a later accepted turn, and awaits process closure at session shutdown under the existing push timeout. Suppresses failure reporting after shutdown starts; test fixtures settle pushes before cleanup. Canonical acceptance stays independent of Git replication.
|
|
@@ -52,11 +52,19 @@ Enable active State Flow on the current branch:
|
|
|
52
52
|
|
|
53
53
|
Starting in an existing conversation retains its context for one complete bootstrap run so the agent can compile what matters.
|
|
54
54
|
|
|
55
|
-
- `/state-flow-start`: Enable active state
|
|
55
|
+
- `/state-flow-start`: Enable the active, state-driven iteration workflow and memory-based context projection; passive mode can already save explicit patches.
|
|
56
56
|
- `/state-flow-status`: Inspect effective state, retained history and known recovery issues without scanning sources or changing state.
|
|
57
|
-
- `/state-flow-stop`: End active episode semantics without deleting memory
|
|
57
|
+
- `/state-flow-stop`: End active episode semantics without deleting memory, even when canonical persistence fails. Interrupted work and uncompiled conversation remain available across toggles and reload; a failed write stays fenced until explicit Start safely reactivates current memory.
|
|
58
58
|
|
|
59
|
-
Active mode is **opt-in**. Passive memory projection and the memory tools are enabled by default: existing state can be read or explicitly patched without an active episode or State Flow compaction. Every materially changed owner advances its independent scope revision even in passive mode, without displaying an active-mode status. When active, terminal and Telegram status render the Effective revision vector as `G15/C8/S31`; passive Telegram shows `State Flow: off`. Requested Global, CWD and Session snapshots show their own `#revision`, while Effective shows the vector. Inspection
|
|
59
|
+
Active mode is **opt-in**. Passive memory projection and the memory tools are enabled by default: existing state can be read or explicitly patched without an active episode or State Flow compaction. Every materially changed owner advances its independent scope revision even in passive mode, without displaying an active-mode status. When active, terminal and Telegram status render the Effective revision vector as `G15/C8/S31`; passive Telegram shows `State Flow: off`. Requested Global, CWD and Session snapshots show their own `#revision`, while Effective shows the vector. Inspection waits cancelably for a coherent shared view, with matching data and revisions, without publishing state or advancing a counter. Stop returns to the configured passive behavior. `autoStart` can enable active mode for genuinely new sessions; resumed branches restore their own enablement. See [configuration](docs/usage.md#configuration).
|
|
60
|
+
|
|
61
|
+
### Active, passive, off
|
|
62
|
+
|
|
63
|
+
- **Active:** Memory tools are available; the agent consolidates necessary final state changes before completing an iteration. Subsequent iterations use accepted state and new input rather than completed prior reasoning.
|
|
64
|
+
- **Passive:** Memory tools and existing-state projection remain available by default. Patching is on demand, and ordinary conversation context continues without State Flow's active iteration reset.
|
|
65
|
+
- **Configured off:** With the branch inactive and both `passiveTools` and `passiveBootstrap` false, neither memory tool nor bootstrap/state projection is exposed to the model. `autoStart: false` alone does not turn memory off.
|
|
66
|
+
|
|
67
|
+
Modes select agent behavior, not a different disk persistence or fork-copy mechanism. Final consolidation does not require an empty ceremonial patch, and context projection does not delete native history. See [mode semantics and bootstrap terminology](docs/usage.md#active-passive-and-configured-off).
|
|
60
68
|
|
|
61
69
|
## State model
|
|
62
70
|
|
|
@@ -89,7 +97,7 @@ Registered Pi Skills may be compiled into source-addressed artifacts when durabl
|
|
|
89
97
|
|
|
90
98
|
## Incremental updates and history
|
|
91
99
|
|
|
92
|
-
`patch_state` updates one or more named scopes atomically.
|
|
100
|
+
`patch_state` updates one or more named scopes atomically. It waits cancelably for the store lock, then applies authored Global/CWD patches to current canonical values, preserving other writers' untouched fields. Overlapping assignments follow successful acceptance order; correct repeats succeed without new semantic revisions. Session remains private. Object patches merge recursively, and `null` deletes an object key rather than becoming stored data.
|
|
93
101
|
|
|
94
102
|
```json
|
|
95
103
|
{
|
|
@@ -124,9 +132,13 @@ Array ranges, structural `keys` reads and path-intersected `patch` projections s
|
|
|
124
132
|
|
|
125
133
|
The default store is `~/.pi/agent/state-flow/`, independent of registered source files. Canonical `checkpoint.json`, `patches.jsonl` and `meta.json` files hold each scope's state, retained changes and metadata; session configuration and runtime identity are stored separately.
|
|
126
134
|
|
|
127
|
-
**
|
|
135
|
+
**Persistence is optimistic across abrupt shutdown.** Short publication exclusion preserves independent shared fields between cooperating writers; the last accepted write wins on overlap. Per-file atomic replacement does not guarantee power-loss survival or crash-atomic recovery. That [accepted limitation](docs/filesystem-recovery.md#power-loss-durability) is not a release gate; no additional recovery journal or storage format is planned.
|
|
136
|
+
|
|
137
|
+
**Git backups are optional.** When the store is a configured Git repository, accepted active turns may create versioned backups of State Flow-owned files. A busy store explicitly defers backup when Pi provides no cancellable settlement wait (including SDK 0.87); a later accepted turn retries without changing memory or blocking native Abort. If the attached branch has an explicitly configured remote, State Flow then pushes the exact current backup commit there asynchronously and without force. Within one Pi process, only one push per repository can run at a time; overlapping attempts are skipped and a later accepted turn pushes the latest backup. Session shutdown waits for that repository's active push to close or time out. Commit failures warn locally; repeated push failures produce one concise warning until a successful push, with redacted Git detail kept in the local diagnostic log. Neither failure rejects or rolls back accepted memory. Backup needs a Git commit identity, but accepting and persisting state does not. Git history can be inspected separately, but it is not the authority for `read_state` or automatic restoration of expired semantic boundaries.
|
|
138
|
+
|
|
139
|
+
Resume and tree navigation restore the selected retained session boundary over current shared global/CWD memory. A new session gets its own session layer. Supported native forks copy selected session state into a new owner without changing the parent's private data. Expired, incomplete or contradictory boundaries never silently substitute newer private state during restoration. Explicit Start is a mode change, not historical restoration: it activates validated **current** memory of that same session, including private state, even after expired active/passive, interrupted or pre-runtime selections. Available aligned history and revisions survive; unavailable history is not recreated. Malformed storage, unsafe fork copying and concurrent writes remain fenced. See [fork support](docs/usage.md#fork-support-and-limits) and [storage recovery](docs/usage.md#storage-and-recovery).
|
|
128
140
|
|
|
129
|
-
|
|
141
|
+
SDK/launcher integrations can use [advisory continuation APIs](docs/architecture.md#session-continuation). Provenance inspection and candidate building are now asynchronous and accept host cancellation; they neither open native sessions nor install automatic resume. Run preparation and missing-artifact maintenance now wait cancelably before inference; embeddings can also use the [transaction APIs](docs/architecture.md#asynchronous-storage-transaction). Accepted-runtime Stop switches local policy/context immediately and awaits runtime-only persistence. Current-head Start also awaits coherent capture/acceptance before enabling mode; Stop or selection can cancel its wait. Telegram acknowledges controls early. Startup/recovery and exact restoration/fork still require migration, including Start's initial-attachment and unaccepted-fork paths.
|
|
130
142
|
|
|
131
143
|
The canonical storage-format boundary introduced in 0.17 still applies: current versions accept only the canonical store contract and provide no in-place predecessor converter. Preserve existing data and check the [format boundary](docs/usage.md#moving-a-store-and-the-017-format-boundary) before changing versions or moving a store.
|
|
132
144
|
|
|
@@ -13,6 +13,7 @@ export { planKnowledgeRehydration, type RehydrationOptions, type RehydrationPhas
|
|
|
13
13
|
export { TemporalRuntime } from "./lib/runtime.ts";
|
|
14
14
|
export { hasCompiledSkillArtifact, hashSkillSource, SKILL_ARTIFACT_COMPILER, type SuccessfulSkillRead } from "./lib/skills.ts";
|
|
15
15
|
export { emptyState, isMaterializedState, isStateDocument, overlayStates, projectStateForModel, updateMaterializedArtifacts, type AtomicScopePatches, type MaterializedState, type ScopedPatch, type ScopedStates, type ScopePatch, type SemanticTransition, type StateDocument, type StatePatch, type StateScope, type TerminalTransition } from "./lib/state.ts";
|
|
16
|
-
export {
|
|
16
|
+
export { PublicationBusyError } from "./lib/storage.ts";
|
|
17
|
+
export { buildStateFlowSectionView, createStateFlowTelegramAdapter, formatStateFlowSectionLabel, loadStateFlowTelegramModules, STATE_FLOW_TELEGRAM_ID, type StateFlowTelegramAdapter, type StateFlowTelegramButton, type StateFlowTelegramCallbackContext, type StateFlowTelegramControlResult, type StateFlowTelegramInspection, type StateFlowTelegramInspectionPort, type StateFlowTelegramLoader, type StateFlowTelegramModules, type StateFlowTelegramPort, type StateFlowTelegramSectionContext, type StateFlowTelegramSectionModule, type StateFlowTelegramSnapshot, type StateFlowTelegramView } from "./lib/telegram.ts";
|
|
17
18
|
export { advanceTemporalState, readTemporalState, temporalScopeRevisions, type ScopeRevisions, type TemporalState } from "./lib/temporal.ts";
|
|
18
19
|
export { parseStateReadPath, readStatePath, type StateReadQuery, type StateReadResult } from "./lib/query.ts";
|
|
@@ -12,6 +12,7 @@ export { planKnowledgeRehydration } from "./lib/rehydration.js";
|
|
|
12
12
|
export { TemporalRuntime } from "./lib/runtime.js";
|
|
13
13
|
export { hasCompiledSkillArtifact, hashSkillSource, SKILL_ARTIFACT_COMPILER } from "./lib/skills.js";
|
|
14
14
|
export { emptyState, isMaterializedState, isStateDocument, overlayStates, projectStateForModel, updateMaterializedArtifacts } from "./lib/state.js";
|
|
15
|
+
export { PublicationBusyError } from "./lib/storage.js";
|
|
15
16
|
export { buildStateFlowSectionView, createStateFlowTelegramAdapter, formatStateFlowSectionLabel, loadStateFlowTelegramModules, STATE_FLOW_TELEGRAM_ID } from "./lib/telegram.js";
|
|
16
17
|
export { advanceTemporalState, readTemporalState, temporalScopeRevisions } from "./lib/temporal.js";
|
|
17
18
|
export { parseStateReadPath, readStatePath } from "./lib/query.js";
|
|
@@ -100,7 +100,7 @@ export declare function hashArtifactSource(source: string | Uint8Array): string;
|
|
|
100
100
|
export declare function isArtifactHash(value: unknown): value is string;
|
|
101
101
|
export declare function validateArtifactMetadata(value: unknown, path?: string): asserts value is ArtifactMetadata;
|
|
102
102
|
export declare function isArtifactMetadata(value: unknown): value is ArtifactMetadata;
|
|
103
|
-
export declare function validateArtifactRegistry(value: unknown): asserts value is ArtifactRegistry;
|
|
103
|
+
export declare function validateArtifactRegistry(value: unknown, context?: string): asserts value is ArtifactRegistry;
|
|
104
104
|
export declare function selectArtifactsByTags(registry: ArtifactRegistry, tags: readonly string[], match?: "all" | "any"): string[];
|
|
105
105
|
export declare function isArtifactRegistry(value: unknown): value is ArtifactRegistry;
|
|
106
106
|
/** Parse one scope `meta.json` provenance registry; missing input means no recorded evidence. */
|
|
@@ -118,7 +118,7 @@ export declare function pruneArtifactProvenance(registry: Readonly<ArtifactProve
|
|
|
118
118
|
/** Validate and apply a whole compilation/removal cohort of model-visible artifacts. */
|
|
119
119
|
export declare function updateArtifactRegistry(registry: ArtifactRegistry, updates: readonly ArtifactCompilationUpdate[], removed?: readonly string[]): ArtifactRegistry;
|
|
120
120
|
/** Validate authored fields only: legacy retained evidence stays readable but cannot be model-edited. */
|
|
121
|
-
export declare function validateModelArtifactPatch(patch: JsonObject): void;
|
|
121
|
+
export declare function validateModelArtifactPatch(patch: JsonObject, context?: string): void;
|
|
122
122
|
/** Strip retained runtime bookkeeping from one model-visible artifact entry. */
|
|
123
123
|
export declare function projectArtifactForModel(entry: unknown): unknown;
|
|
124
124
|
export type ArtifactModelHints = Readonly<Record<string, string | readonly string[]>>;
|
|
@@ -99,13 +99,13 @@ export function isArtifactMetadata(value) {
|
|
|
99
99
|
return false;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
export function validateArtifactRegistry(value) {
|
|
102
|
+
export function validateArtifactRegistry(value, context = "artifacts") {
|
|
103
103
|
if (!isObject(value))
|
|
104
104
|
throw new Error("Artifacts must be a path-keyed JSON object");
|
|
105
105
|
for (const [path, metadata] of Object.entries(value)) {
|
|
106
106
|
if (path.trim().length === 0)
|
|
107
107
|
throw new Error("Artifact path keys must be non-empty");
|
|
108
|
-
validateArtifactMetadata(metadata, path);
|
|
108
|
+
validateArtifactMetadata(metadata, `${context}[${JSON.stringify(path)}]`);
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
export function selectArtifactsByTags(registry, tags, match = "all") {
|
|
@@ -261,7 +261,7 @@ export function compileArtifact(update) {
|
|
|
261
261
|
// Timestamps are runtime evidence; the model-visible entry never retains them.
|
|
262
262
|
const semantic = structuredClone(update.output);
|
|
263
263
|
delete semantic.compiled_at;
|
|
264
|
-
validateArtifactMetadata(semantic, update.source.path);
|
|
264
|
+
validateArtifactMetadata(semantic, `${update.source.scope ? `${update.source.scope}.` : ""}artifacts[${JSON.stringify(update.source.path)}]`);
|
|
265
265
|
return {
|
|
266
266
|
semantic,
|
|
267
267
|
provenance: {
|
|
@@ -325,13 +325,13 @@ export function updateArtifactRegistry(registry, updates, removed = []) {
|
|
|
325
325
|
/** Runtime-owned artifact fields that never belong in ordinary model context. */
|
|
326
326
|
const RUNTIME_ARTIFACT_FIELDS = [...MODEL_FORBIDDEN_PROVENANCE_FIELDS, "compiled_at", "hint"];
|
|
327
327
|
/** Validate authored fields only: legacy retained evidence stays readable but cannot be model-edited. */
|
|
328
|
-
export function validateModelArtifactPatch(patch) {
|
|
328
|
+
export function validateModelArtifactPatch(patch, context = "artifacts") {
|
|
329
329
|
for (const [path, entry] of Object.entries(patch)) {
|
|
330
330
|
if (!isObject(entry))
|
|
331
331
|
continue; // Whole-artifact deletion and materialized shape belong to the transition owner.
|
|
332
332
|
const field = RUNTIME_ARTIFACT_FIELDS.find((field) => Object.hasOwn(entry, field));
|
|
333
333
|
if (field !== undefined)
|
|
334
|
-
throw new Error(`Artifact patch at ${path} cannot set runtime-owned field ${field}`);
|
|
334
|
+
throw new Error(`Artifact patch at ${context}[${JSON.stringify(path)}] cannot set runtime-owned field ${field}`);
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
337
|
/** Strip retained runtime bookkeeping from one model-visible artifact entry. */
|
|
@@ -13,14 +13,15 @@ export declare function lazyNavigationHint(state: MaterializedState): {
|
|
|
13
13
|
path: string;
|
|
14
14
|
keys?: Record<string, LazyValueKind>;
|
|
15
15
|
};
|
|
16
|
-
/**
|
|
16
|
+
/** Context retained after semantic State Flow is stopped in this physical session. */
|
|
17
17
|
export interface PassiveContinuation {
|
|
18
18
|
startedAt: number;
|
|
19
19
|
activeRunStartedAt?: number;
|
|
20
|
+
preserveContext?: true;
|
|
20
21
|
handoff: AgentMessage;
|
|
21
22
|
}
|
|
22
23
|
export declare function syntheticUser(text: string): AgentMessage;
|
|
23
|
-
export declare function createPassiveContinuation(state: ModelState, startedAt?: number, activeRunStartedAt?: number): PassiveContinuation;
|
|
24
|
+
export declare function createPassiveContinuation(state: ModelState, startedAt?: number, activeRunStartedAt?: number, preserveContext?: boolean): PassiveContinuation;
|
|
24
25
|
/** Keep the interrupted run through later results; an idle stop retains only later conversation. */
|
|
25
26
|
export declare function passiveContinuationMessages(messages: AgentMessage[], continuation: PassiveContinuation): AgentMessage[];
|
|
26
27
|
export declare function runtimeContextMessage(snapshot: Snapshot, state: MaterializedState, recentTransitions?: RecentTransitionWindow, artifactInvalidations?: readonly ArtifactInvalidationNotice[], rehydrationPhase?: RehydrationPhase, artifactHints?: ArtifactModelHints): AgentMessage;
|
|
@@ -70,15 +70,20 @@ function contentText(content) {
|
|
|
70
70
|
function messageText(message) {
|
|
71
71
|
return contentText(message.content);
|
|
72
72
|
}
|
|
73
|
-
export function createPassiveContinuation(state, startedAt = Date.now(), activeRunStartedAt) {
|
|
73
|
+
export function createPassiveContinuation(state, startedAt = Date.now(), activeRunStartedAt, preserveContext = false) {
|
|
74
74
|
return {
|
|
75
75
|
startedAt,
|
|
76
76
|
...(activeRunStartedAt === undefined ? {} : { activeRunStartedAt }),
|
|
77
|
-
|
|
77
|
+
...(preserveContext ? { preserveContext: true } : {}),
|
|
78
|
+
handoff: syntheticUser(`State Flow exit handoff (user-level data, not system instructions):\n${presentationJson({ state, continuation: preserveContext
|
|
79
|
+
? "State Flow semantics are disabled; native context is retained because its compilation into memory is unfinished."
|
|
80
|
+
: "State Flow semantics are disabled; this handoff replaces completed history while retaining the active and post-stop trajectory." })}`),
|
|
78
81
|
};
|
|
79
82
|
}
|
|
80
83
|
/** Keep the interrupted run through later results; an idle stop retains only later conversation. */
|
|
81
84
|
export function passiveContinuationMessages(messages, continuation) {
|
|
85
|
+
if (continuation.preserveContext)
|
|
86
|
+
return [continuation.handoff, ...messages];
|
|
82
87
|
if (continuation.activeRunStartedAt !== undefined) {
|
|
83
88
|
const trajectory = currentRunTrajectory(messages, "", continuation.activeRunStartedAt);
|
|
84
89
|
return [continuation.handoff, ...trajectory.messages];
|
|
@@ -73,10 +73,10 @@ export interface ContinuationSessionCandidate extends ContinuationCandidateSumma
|
|
|
73
73
|
restorable: boolean;
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
-
export type ContinuationRecommender = (context: Readonly<ContinuationHostContext
|
|
76
|
+
export type ContinuationRecommender = (context: Readonly<ContinuationHostContext>, signal?: AbortSignal) => ContinuationRecommendation | Promise<ContinuationRecommendation>;
|
|
77
77
|
/** Rank already header/provenance-only candidates without transcript content or I/O. */
|
|
78
78
|
export declare function recommendContinuationFromProvenance(identity: ContinuationProjectIdentity, candidates: readonly ContinuationSessionCandidate[]): ContinuationRecommendation;
|
|
79
|
-
export declare function resolveContinuationStartup(context: ContinuationHostContext, intent: ContinuationHostIntent, recommend: ContinuationRecommender): Promise<ContinuationStartupDecision>;
|
|
79
|
+
export declare function resolveContinuationStartup(context: ContinuationHostContext, intent: ContinuationHostIntent, recommend: ContinuationRecommender, signal?: AbortSignal): Promise<ContinuationStartupDecision>;
|
|
80
80
|
export interface NativeSessionHeader {
|
|
81
81
|
file: string;
|
|
82
82
|
id: string;
|
|
@@ -106,6 +106,6 @@ export declare function discoverNativeSessionHeaders(sessionDir: string): {
|
|
|
106
106
|
error: string;
|
|
107
107
|
}>;
|
|
108
108
|
};
|
|
109
|
-
/**
|
|
110
|
-
export declare function inspectStateFlowContinuationProvenance(header: NativeSessionHeader
|
|
111
|
-
export declare function buildContinuationCandidates(headers: readonly NativeSessionHeader[], inspect: (header: Readonly<NativeSessionHeader
|
|
109
|
+
/** Await one exact canonical cohort; never read transcript bodies, initialize, or publish. */
|
|
110
|
+
export declare function inspectStateFlowContinuationProvenance(header: Readonly<NativeSessionHeader>, repositoryRoot: string, signal?: AbortSignal): Promise<Pick<ContinuationCandidateProvenance, "stateFlow" | "reason">>;
|
|
111
|
+
export declare function buildContinuationCandidates(headers: readonly NativeSessionHeader[], inspect: (header: Readonly<NativeSessionHeader>, signal?: AbortSignal) => ContinuationCandidateProvenance | undefined | Promise<ContinuationCandidateProvenance | undefined>, signal?: AbortSignal): Promise<ContinuationSessionCandidate[]>;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { closeSync, constants,
|
|
1
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { parseScopeStream, resolveSessionAddress, sessionRuntimePaths, temporalScopePaths } from "./durable.js";
|
|
4
4
|
import { MAX_HISTORY_LIMIT } from "./history.js";
|
|
5
|
+
import { diagnosticText } from "./protocol.js";
|
|
5
6
|
import { parseSessionRuntime } from "./snapshot.js";
|
|
7
|
+
import { withStorageTransaction } from "./storage.js";
|
|
6
8
|
import { validateScopeLineage } from "./temporal.js";
|
|
7
9
|
/**
|
|
8
10
|
* Preserve host-owned explicit session intent and consult State Flow only for
|
|
@@ -48,7 +50,8 @@ export function recommendContinuationFromProvenance(identity, candidates) {
|
|
|
48
50
|
return { action: "new", reason: "ineligible" };
|
|
49
51
|
return { action: "resume", sessionFile: candidate.sessionFile, sessionId: candidate.sessionId, reason: "latest-enabled-state-flow" };
|
|
50
52
|
}
|
|
51
|
-
export async function resolveContinuationStartup(context, intent, recommend) {
|
|
53
|
+
export async function resolveContinuationStartup(context, intent, recommend, signal) {
|
|
54
|
+
signal?.throwIfAborted();
|
|
52
55
|
switch (intent.kind) {
|
|
53
56
|
case "new":
|
|
54
57
|
return { action: "new", reason: "explicit-new" };
|
|
@@ -60,8 +63,11 @@ export async function resolveContinuationStartup(context, intent, recommend) {
|
|
|
60
63
|
return { action: "native", mode: "continue-recent" };
|
|
61
64
|
case "no-session":
|
|
62
65
|
return { action: "native", mode: "no-session" };
|
|
63
|
-
case "default":
|
|
64
|
-
|
|
66
|
+
case "default": {
|
|
67
|
+
const recommendation = await recommend(Object.freeze({ ...context }), signal);
|
|
68
|
+
signal?.throwIfAborted();
|
|
69
|
+
return recommendation;
|
|
70
|
+
}
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
73
|
const MAX_SESSION_HEADER_BYTES = 64 * 1024;
|
|
@@ -124,54 +130,62 @@ export function discoverNativeSessionHeaders(sessionDir) {
|
|
|
124
130
|
}
|
|
125
131
|
return { headers, invalid };
|
|
126
132
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
export function inspectStateFlowContinuationProvenance(header, repositoryRoot) {
|
|
136
|
-
const sessionKey = resolveSessionAddress(header.file, header.id, header.timestamp).key;
|
|
137
|
-
const paths = sessionRuntimePaths(header.cwd, header.id, repositoryRoot, sessionKey);
|
|
133
|
+
/** Await one exact canonical cohort; never read transcript bodies, initialize, or publish. */
|
|
134
|
+
export async function inspectStateFlowContinuationProvenance(header, repositoryRoot, signal) {
|
|
135
|
+
signal?.throwIfAborted();
|
|
136
|
+
const selected = { ...header };
|
|
137
|
+
const root = resolve(repositoryRoot);
|
|
138
|
+
const sessionKey = resolveSessionAddress(selected.file, selected.id, selected.timestamp).key;
|
|
139
|
+
const paths = sessionRuntimePaths(selected.cwd, selected.id, root, sessionKey);
|
|
140
|
+
const absent = { stateFlow: { enabled: false, restorable: true }, reason: "no State Flow session runtime" };
|
|
138
141
|
try {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
142
|
+
if (!lstatSync(root, { throwIfNoEntry: false }))
|
|
143
|
+
return absent;
|
|
144
|
+
const result = await withStorageTransaction(root, (tx) => {
|
|
145
|
+
const files = new Map(tx.capture(selected.cwd, selected.id, root, sessionKey).files.map((file) => [file.path, file.content]));
|
|
146
|
+
const privatePaths = temporalScopePaths(selected.cwd, selected.id, "session", root, sessionKey);
|
|
147
|
+
if ([paths.config, paths.runtime, privatePaths.meta, privatePaths.checkpoint, privatePaths.patches].every((path) => files.get(path) === undefined))
|
|
148
|
+
return absent;
|
|
149
|
+
const runtime = parseSessionRuntime(files.get(paths.config), files.get(paths.runtime), selected.cwd, selected.id);
|
|
150
|
+
if (!runtime)
|
|
151
|
+
throw new Error("incomplete canonical session runtime");
|
|
152
|
+
if (!runtime.config.enabled)
|
|
153
|
+
return { stateFlow: { enabled: false, restorable: true }, reason: "State Flow stopped on selected runtime" };
|
|
154
|
+
const streams = ["global", "cwd", "session"].map((scope) => {
|
|
155
|
+
const owned = temporalScopePaths(selected.cwd, selected.id, scope, root, sessionKey);
|
|
156
|
+
return parseScopeStream(files.get(owned.checkpoint), files.get(owned.patches), scope, scope === "cwd" ? selected.cwd : undefined, files.get(owned.meta));
|
|
157
|
+
});
|
|
158
|
+
if (streams.some((stream) => stream === undefined))
|
|
159
|
+
throw new Error("incomplete canonical temporal cohort");
|
|
160
|
+
// Shared streams remain current and independently valid; only the private stream binds to this lineage.
|
|
161
|
+
validateScopeLineage(streams[2], "session", runtime.meta.lineage, MAX_HISTORY_LIMIT);
|
|
162
|
+
return { stateFlow: { enabled: true, restorable: true }, reason: "canonical session lineage is valid beside current shared streams" };
|
|
163
|
+
}, signal);
|
|
164
|
+
signal?.throwIfAborted();
|
|
165
|
+
return result;
|
|
158
166
|
}
|
|
159
167
|
catch (error) {
|
|
160
|
-
|
|
168
|
+
signal?.throwIfAborted();
|
|
169
|
+
return { stateFlow: { enabled: true, restorable: false }, reason: `State Flow runtime is ineligible: ${diagnosticText(error)}` };
|
|
161
170
|
}
|
|
162
171
|
}
|
|
163
|
-
export function buildContinuationCandidates(headers, inspect) {
|
|
172
|
+
export async function buildContinuationCandidates(headers, inspect, signal) {
|
|
173
|
+
signal?.throwIfAborted();
|
|
174
|
+
const selected = headers.map((header) => Object.freeze({ ...header }));
|
|
164
175
|
const candidates = [];
|
|
165
|
-
for (const header of
|
|
166
|
-
|
|
176
|
+
for (const header of selected) {
|
|
177
|
+
signal?.throwIfAborted();
|
|
178
|
+
const provenance = await inspect(header, signal);
|
|
179
|
+
signal?.throwIfAborted();
|
|
167
180
|
if (!provenance)
|
|
168
181
|
continue;
|
|
169
182
|
candidates.push({
|
|
183
|
+
...provenance,
|
|
184
|
+
stateFlow: { ...provenance.stateFlow },
|
|
170
185
|
sessionFile: header.file,
|
|
171
186
|
sessionId: header.id,
|
|
172
187
|
lastActivity: header.lastActivity,
|
|
173
188
|
cwd: header.cwd,
|
|
174
|
-
...provenance,
|
|
175
189
|
});
|
|
176
190
|
}
|
|
177
191
|
return candidates;
|
|
@@ -22,7 +22,7 @@ export function hasCwdMaterialization(cwd, repositoryRoot) {
|
|
|
22
22
|
if (checkpoint.content !== undefined)
|
|
23
23
|
return parseScopeStream(checkpoint.content, patches.content, "cwd", cwd, meta.content) !== undefined;
|
|
24
24
|
if (patches.content !== undefined)
|
|
25
|
-
throw new Error(`State Flow tail has no provable checkpoint: ${directory}`);
|
|
25
|
+
throw new Error(`State Flow tail has no provable checkpoint: ${JSON.stringify(directory)}`);
|
|
26
26
|
return false;
|
|
27
27
|
}
|
|
28
28
|
/** Semantic files contain no runtime envelope; temporal boundaries and CWD ownership live in meta.json. */
|
|
@@ -124,7 +124,7 @@ export function sessionRuntimePaths(cwd, sessionId, repositoryRoot, sessionKey =
|
|
|
124
124
|
export function loadScopeStream(cwd, sessionId, scope, repositoryRoot, sessionKey = sessionId) {
|
|
125
125
|
const paths = temporalScopePaths(cwd, sessionId, scope, repositoryRoot, sessionKey);
|
|
126
126
|
if (readRegularBytes(join(paths.directory, STATE_FILE), repositoryRoot) !== undefined) {
|
|
127
|
-
throw new Error(`Unsupported State Flow storage format: ${paths.directory}`);
|
|
127
|
+
throw new Error(`Unsupported State Flow storage format: ${JSON.stringify(paths.directory)}`);
|
|
128
128
|
}
|
|
129
129
|
return parseScopeStream(readRegularFile(paths.checkpoint, repositoryRoot), readRegularFile(paths.patches, repositoryRoot), scope, scope === "cwd" ? cwd : undefined, readRegularFile(paths.meta, repositoryRoot));
|
|
130
130
|
}
|
|
@@ -147,7 +147,7 @@ export function temporalStateFileUpdates(cwd, sessionId, view, scopes, repositor
|
|
|
147
147
|
seen.add(scope);
|
|
148
148
|
const paths = temporalScopePaths(cwd, sessionId, scope, repositoryRoot, sessionKey);
|
|
149
149
|
if (readRegularBytes(join(paths.directory, STATE_FILE), repositoryRoot) !== undefined) {
|
|
150
|
-
throw new Error(`Unsupported State Flow storage format: ${paths.directory}`);
|
|
150
|
+
throw new Error(`Unsupported State Flow storage format: ${JSON.stringify(paths.directory)}`);
|
|
151
151
|
}
|
|
152
152
|
const sources = serializeScopeStream(view.scopes[scope], scope, scope === "cwd" ? cwd : undefined);
|
|
153
153
|
return [{ path: paths.checkpoint, content: sources.checkpoint }, { path: paths.patches, content: sources.patches }];
|
|
@@ -190,14 +190,14 @@ function parseMetadataDocument(source, label) {
|
|
|
190
190
|
}
|
|
191
191
|
/** Missing provenance is unavailable evidence, never corrupt state. Unknown metadata is preserved by writers. */
|
|
192
192
|
export function parseScopeProvenance(source, path) {
|
|
193
|
-
const value = parseMetadataDocument(source, `State Flow provenance file: ${path}`);
|
|
193
|
+
const value = parseMetadataDocument(source, `State Flow provenance file: ${JSON.stringify(path)}`);
|
|
194
194
|
if (Object.keys(value).length === 0)
|
|
195
195
|
return {};
|
|
196
196
|
if (value.version !== 1 && value.version !== 2)
|
|
197
|
-
throw new Error(`Invalid State Flow provenance document: ${path}`);
|
|
197
|
+
throw new Error(`Invalid State Flow provenance document: ${JSON.stringify(path)}`);
|
|
198
198
|
if (!Object.hasOwn(value, "artifacts"))
|
|
199
199
|
return {};
|
|
200
|
-
return parseArtifactProvenanceRegistry(value.artifacts, `State Flow provenance at ${path}`);
|
|
200
|
+
return parseArtifactProvenanceRegistry(value.artifacts, `State Flow provenance at ${JSON.stringify(path)}`);
|
|
201
201
|
}
|
|
202
202
|
/** Dedicated runtime storage, independent from Markdown source discovery. */
|
|
203
203
|
export function getDurableRepositoryRoot(agentDir = getAgentDir()) {
|
|
@@ -273,7 +273,7 @@ function missing(error) {
|
|
|
273
273
|
function assertWithinRepository(path, repositoryRoot) {
|
|
274
274
|
const child = relative(repositoryRoot, path);
|
|
275
275
|
if (child === "" || child === ".." || child.startsWith(`..${sep}`)) {
|
|
276
|
-
throw new Error(`Durable State Flow path escapes its repository: ${path}`);
|
|
276
|
+
throw new Error(`Durable State Flow path escapes its repository: ${JSON.stringify(path)}`);
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
/** Reject symlinked directory components instead of following them during reads or writes. */
|
|
@@ -295,7 +295,7 @@ function assertDirectoryChain(repositoryRoot, directory, create) {
|
|
|
295
295
|
try {
|
|
296
296
|
const metadata = lstatSync(current);
|
|
297
297
|
if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
|
|
298
|
-
throw new Error(`Durable State Flow directory is not a regular directory: ${current}`);
|
|
298
|
+
throw new Error(`Durable State Flow directory is not a regular directory: ${JSON.stringify(current)}`);
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
301
|
catch (error) {
|
|
@@ -315,7 +315,7 @@ function readRegularBytes(path, repositoryRoot) {
|
|
|
315
315
|
try {
|
|
316
316
|
descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
317
317
|
if (!fstatSync(descriptor).isFile()) {
|
|
318
|
-
throw new Error(`Durable State Flow path is not a regular file: ${path}`);
|
|
318
|
+
throw new Error(`Durable State Flow path is not a regular file: ${JSON.stringify(path)}`);
|
|
319
319
|
}
|
|
320
320
|
return readFileSync(descriptor);
|
|
321
321
|
}
|
|
@@ -325,7 +325,7 @@ function readRegularBytes(path, repositoryRoot) {
|
|
|
325
325
|
if (error instanceof Error
|
|
326
326
|
&& "code" in error
|
|
327
327
|
&& error.code === "ELOOP") {
|
|
328
|
-
throw new Error(`Durable State Flow path is not a regular file: ${path}`);
|
|
328
|
+
throw new Error(`Durable State Flow path is not a regular file: ${JSON.stringify(path)}`);
|
|
329
329
|
}
|
|
330
330
|
throw error;
|
|
331
331
|
}
|
|
@@ -348,7 +348,7 @@ function fileBase(path, repositoryRoot) {
|
|
|
348
348
|
}
|
|
349
349
|
function assertCurrentBytes(path, root, expected) {
|
|
350
350
|
if (byteIdentity(readRegularBytes(path, root)) !== byteIdentity(expected)) {
|
|
351
|
-
throw new Error(`State Flow file conflict at ${path};
|
|
351
|
+
throw new Error(`State Flow file conflict at ${JSON.stringify(path)}; concurrent bytes preserved`);
|
|
352
352
|
}
|
|
353
353
|
}
|
|
354
354
|
/** Capture exact owned bytes for one compare-and-swap publication cohort. */
|
|
@@ -356,7 +356,7 @@ export function captureOwnedFileBases(paths, repositoryRoot) {
|
|
|
356
356
|
const root = resolve(repositoryRoot);
|
|
357
357
|
return paths.map((path) => {
|
|
358
358
|
if (!isStateFlowOwnedPath(path, root))
|
|
359
|
-
throw new Error(`Cannot capture a non-State Flow path: ${path}`);
|
|
359
|
+
throw new Error(`Cannot capture a non-State Flow path: ${JSON.stringify(path)}`);
|
|
360
360
|
return fileBase(resolve(path), root);
|
|
361
361
|
});
|
|
362
362
|
}
|
|
@@ -365,7 +365,7 @@ function prepareFile(path, repositoryRoot, content, expected) {
|
|
|
365
365
|
assertDirectoryChain(repositoryRoot, dirname(path), true);
|
|
366
366
|
const original = readRegularBytes(path, repositoryRoot);
|
|
367
367
|
if (expected !== undefined && byteIdentity(original) !== expected.identity) {
|
|
368
|
-
throw new Error(`State Flow file conflict during preparation: ${path}`);
|
|
368
|
+
throw new Error(`State Flow file conflict during preparation: ${JSON.stringify(path)}`);
|
|
369
369
|
}
|
|
370
370
|
const temporary = join(dirname(path), `.${basename(path)}.${process.pid}.${randomUUID()}.tmp`);
|
|
371
371
|
const next = content === undefined ? undefined : Buffer.from(content);
|
|
@@ -425,7 +425,7 @@ export function assertOwnedFileUpdates(updates, repositoryRoot) {
|
|
|
425
425
|
const root = resolve(repositoryRoot);
|
|
426
426
|
for (const update of updates) {
|
|
427
427
|
if (!isStateFlowOwnedPath(update.path, root))
|
|
428
|
-
throw new Error(`Cannot inspect a non-State Flow path: ${update.path}`);
|
|
428
|
+
throw new Error(`Cannot inspect a non-State Flow path: ${JSON.stringify(update.path)}`);
|
|
429
429
|
assertCurrentBytes(update.path, root, update.content === undefined ? undefined : Buffer.from(update.content));
|
|
430
430
|
}
|
|
431
431
|
}
|
|
@@ -439,13 +439,13 @@ export function writeOwnedFileUpdates(updates, bases, repositoryRoot) {
|
|
|
439
439
|
for (const update of updates) {
|
|
440
440
|
const path = resolve(update.path);
|
|
441
441
|
if (!isStateFlowOwnedPath(path, root))
|
|
442
|
-
throw new Error(`Cannot update a non-State Flow path: ${path}`);
|
|
442
|
+
throw new Error(`Cannot update a non-State Flow path: ${JSON.stringify(path)}`);
|
|
443
443
|
if (seen.has(path))
|
|
444
|
-
throw new Error(`Duplicate State Flow file update: ${path}`);
|
|
444
|
+
throw new Error(`Duplicate State Flow file update: ${JSON.stringify(path)}`);
|
|
445
445
|
seen.add(path);
|
|
446
446
|
const base = byPath.get(path);
|
|
447
447
|
if (base === undefined)
|
|
448
|
-
throw new Error(`State Flow file update has no captured base: ${path}`);
|
|
448
|
+
throw new Error(`State Flow file update has no captured base: ${JSON.stringify(path)}`);
|
|
449
449
|
prepared.push(prepareFile(path, root, update.content, base));
|
|
450
450
|
}
|
|
451
451
|
}
|
|
@@ -464,13 +464,13 @@ export function restoreDurableFileBases(bases, repositoryRoot, expectedCurrent)
|
|
|
464
464
|
for (const base of bases) {
|
|
465
465
|
assertWithinRepository(base.path, root);
|
|
466
466
|
if (!isStateFlowOwnedPath(base.path, root)) {
|
|
467
|
-
throw new Error(`Cannot restore a non-State Flow path: ${base.path}`);
|
|
467
|
+
throw new Error(`Cannot restore a non-State Flow path: ${JSON.stringify(base.path)}`);
|
|
468
468
|
}
|
|
469
469
|
}
|
|
470
470
|
for (const base of bases) {
|
|
471
471
|
const update = expected.get(resolve(base.path));
|
|
472
472
|
if (update === undefined)
|
|
473
|
-
throw new Error(`Rollback has no published basis: ${base.path}`);
|
|
473
|
+
throw new Error(`Rollback has no published basis: ${JSON.stringify(base.path)}`);
|
|
474
474
|
assertCurrentBytes(base.path, root, update.content === undefined ? undefined : Buffer.from(update.content));
|
|
475
475
|
if (base.identity === "missing") {
|
|
476
476
|
rmSync(base.path, { force: true });
|
|
@@ -478,7 +478,7 @@ export function restoreDurableFileBases(bases, repositoryRoot, expectedCurrent)
|
|
|
478
478
|
}
|
|
479
479
|
const original = base.bytes ?? (base.content === undefined ? undefined : Buffer.from(base.content));
|
|
480
480
|
if (original === undefined)
|
|
481
|
-
throw new Error(`Durable State Flow base content is missing: ${base.path}`);
|
|
481
|
+
throw new Error(`Durable State Flow base content is missing: ${JSON.stringify(base.path)}`);
|
|
482
482
|
assertDirectoryChain(root, dirname(base.path), true);
|
|
483
483
|
const temporary = join(dirname(base.path), `.${basename(base.path)}.${process.pid}.${randomUUID()}.restore`);
|
|
484
484
|
try {
|