@llblab/pi-kit 0.22.2 → 0.23.1
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 +12 -0
- package/README.md +5 -5
- package/node_modules/@llblab/pi-actors/AGENTS.md +4 -1
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +10 -0
- package/node_modules/@llblab/pi-actors/README.md +2 -2
- package/node_modules/@llblab/pi-actors/dist/index.js +4 -1
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.js +2 -1
- package/node_modules/@llblab/pi-actors/dist/lib/paths.d.ts +6 -0
- package/node_modules/@llblab/pi-actors/dist/lib/paths.js +19 -1
- package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.d.ts +2 -0
- package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.js +11 -7
- package/node_modules/@llblab/pi-actors/dist/scripts/build-dist.mjs +94 -30
- package/node_modules/@llblab/pi-actors/docs/actor-inspector.md +1 -1
- package/node_modules/@llblab/pi-actors/index.ts +6 -3
- package/node_modules/@llblab/pi-actors/lib/inspector-overlay.ts +2 -1
- package/node_modules/@llblab/pi-actors/lib/paths.ts +27 -1
- package/node_modules/@llblab/pi-actors/lib/trace-projection.ts +13 -6
- package/node_modules/@llblab/pi-actors/package.json +3 -8
- package/node_modules/@llblab/pi-actors/scripts/build-dist.mjs +94 -30
- 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 +7 -7
|
@@ -3,12 +3,12 @@ import { lstatSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { classifyScopeStream, hasCwdMaterialization, parseScopeProvenance, parseScopeStream, sessionRuntimePaths, temporalScopePaths } from "./durable.js";
|
|
5
5
|
import { pruneArtifactProvenance } from "./artifact.js";
|
|
6
|
-
import { captureTemporalFileBase, initializeFileStore, publishTemporalStateToFiles } from "./storage.js";
|
|
6
|
+
import { assertTemporalFileBase, captureTemporalFileBase, initializeFileStore, publishTemporalStateToFiles, withStorageTransaction } from "./storage.js";
|
|
7
7
|
import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT } from "./history.js";
|
|
8
8
|
import { hashJson, sameJson } from "./json.js";
|
|
9
|
-
import { RevisionUnavailableError, createSessionRuntime, parseSessionRuntime, retainedBoundaryCheckpoint } from "./snapshot.js";
|
|
9
|
+
import { HistoryBoundaryExpiredError, RevisionUnavailableError, createSessionRuntime, parseSessionRuntime, retainedBoundaryCheckpoint } from "./snapshot.js";
|
|
10
10
|
import { emptyState } from "./state.js";
|
|
11
|
-
import { adoptTemporalStreams, advanceTemporalState, createTemporalState, readTemporalState, selectScopeStreamAtBoundary, validateScopeLineage } from "./temporal.js";
|
|
11
|
+
import { adoptTemporalStreams, advanceTemporalState, constrainTemporalState, createTemporalState, readTemporalState, selectScopeStreamAtBoundary, validateScopeLineage } from "./temporal.js";
|
|
12
12
|
const SCOPES = ["global", "cwd", "session"];
|
|
13
13
|
const SHARED_SCOPES = ["global", "cwd"];
|
|
14
14
|
function emptyProvenance() {
|
|
@@ -50,6 +50,7 @@ export class TemporalRuntime {
|
|
|
50
50
|
base;
|
|
51
51
|
semanticRevision;
|
|
52
52
|
savedRuntime;
|
|
53
|
+
transaction;
|
|
53
54
|
restoredOriginPending = false;
|
|
54
55
|
provenanceByScope = emptyProvenance();
|
|
55
56
|
/** Shared scopes whose wholly absent live basis was accepted after one stale-target refusal. */
|
|
@@ -76,7 +77,9 @@ export class TemporalRuntime {
|
|
|
76
77
|
loadPassive() {
|
|
77
78
|
if (!lstatSync(this.root, { throwIfNoEntry: false }))
|
|
78
79
|
return false;
|
|
79
|
-
|
|
80
|
+
return this.loadPassiveBase(captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey));
|
|
81
|
+
}
|
|
82
|
+
loadPassiveBase(base) {
|
|
80
83
|
const files = new Map(base.files.map((file) => [file.path, file.content]));
|
|
81
84
|
const shared = Object.fromEntries(SHARED_SCOPES.map((scope) => {
|
|
82
85
|
const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
|
|
@@ -88,13 +91,19 @@ export class TemporalRuntime {
|
|
|
88
91
|
throw new Error("Incomplete passive State Flow shared storage: CWD state exists without global state");
|
|
89
92
|
const fresh = createTemporalState({ global: emptyState(), cwd: emptyState(), session: emptyState() }, randomUUID(), this.historyLimit);
|
|
90
93
|
// Global memory is valid before this CWD has ever materialized its own scope.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.provenanceByScope = {
|
|
94
|
+
const view = adoptTemporalStreams({ global: shared.global, cwd: shared.cwd ?? fresh.scopes.cwd, session: fresh.scopes.session }, `passive:files:${randomUUID()}`, this.historyLimit);
|
|
95
|
+
const provenance = {
|
|
94
96
|
global: parseScopeProvenance(files.get(temporalScopePaths(this.cwd, this.sessionId, "global", this.root, this.sessionKey).meta), "State Flow global metadata"),
|
|
95
97
|
cwd: parseScopeProvenance(files.get(temporalScopePaths(this.cwd, this.sessionId, "cwd", this.root, this.sessionKey).meta), "State Flow CWD metadata"),
|
|
96
98
|
session: {},
|
|
97
99
|
};
|
|
100
|
+
this.view = view;
|
|
101
|
+
this.base = base;
|
|
102
|
+
this.provenanceByScope = provenance;
|
|
103
|
+
this.semanticRevision = undefined;
|
|
104
|
+
this.savedRuntime = undefined;
|
|
105
|
+
this.restoredOriginPending = false;
|
|
106
|
+
this.absentSharedScopes.clear();
|
|
98
107
|
return true;
|
|
99
108
|
}
|
|
100
109
|
/** Select canonical file acceptance even when Git is available; backup remains a later concern. */
|
|
@@ -143,7 +152,9 @@ export class TemporalRuntime {
|
|
|
143
152
|
prepareBoundaryRestore(checkpoint) {
|
|
144
153
|
if (!lstatSync(this.root, { throwIfNoEntry: false }))
|
|
145
154
|
throw new RevisionUnavailableError("Selected State Flow boundary storage is unavailable");
|
|
146
|
-
|
|
155
|
+
return this.prepareBoundaryRestoreBase(checkpoint, captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey));
|
|
156
|
+
}
|
|
157
|
+
prepareBoundaryRestoreBase(checkpoint, base) {
|
|
147
158
|
const files = new Map(base.files.map((file) => [file.path, file.content]));
|
|
148
159
|
const scopes = Object.fromEntries(SCOPES.map((scope) => {
|
|
149
160
|
const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
|
|
@@ -160,7 +171,7 @@ export class TemporalRuntime {
|
|
|
160
171
|
validateScopeLineage(scopes.session, "session", document.meta.lineage, MAX_HISTORY_LIMIT);
|
|
161
172
|
const boundary = document.meta.lineage.slice(-(this.historyLimit + 1)).find(({ id }) => id === checkpoint.boundary);
|
|
162
173
|
if (!boundary)
|
|
163
|
-
throw new
|
|
174
|
+
throw new HistoryBoundaryExpiredError("Selected State Flow history boundary is outside the retained temporal window");
|
|
164
175
|
const selectedSession = selectScopeStreamAtBoundary(scopes.session, "session", boundary, MAX_HISTORY_LIMIT);
|
|
165
176
|
const view = adoptTemporalStreams({
|
|
166
177
|
global: scopes.global,
|
|
@@ -214,6 +225,60 @@ export class TemporalRuntime {
|
|
|
214
225
|
const snapshot = prepared.restore();
|
|
215
226
|
return { snapshot, publication: this.acceptRestoredOrigin(snapshot) };
|
|
216
227
|
}
|
|
228
|
+
/** Await a coherent read-only recovery view; this neither activates policy nor accepts publication authority. */
|
|
229
|
+
async refreshCurrentMemory(signal) {
|
|
230
|
+
signal?.throwIfAborted();
|
|
231
|
+
if (!lstatSync(this.root, { throwIfNoEntry: false }))
|
|
232
|
+
return undefined;
|
|
233
|
+
return withStorageTransaction(this.root, (storage) => {
|
|
234
|
+
const base = storage.capture(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
235
|
+
signal?.throwIfAborted();
|
|
236
|
+
return this.loadCurrentMemoryBase(base);
|
|
237
|
+
}, signal);
|
|
238
|
+
}
|
|
239
|
+
loadCurrentMemoryBase(base) {
|
|
240
|
+
const files = new Map(base.files.map((file) => [file.path, file.content]));
|
|
241
|
+
const paths = sessionRuntimePaths(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
242
|
+
const session = temporalScopePaths(this.cwd, this.sessionId, "session", this.root, this.sessionKey);
|
|
243
|
+
if ([paths.config, paths.runtime, session.checkpoint, session.patches, session.meta].every((path) => files.get(path) === undefined))
|
|
244
|
+
return undefined;
|
|
245
|
+
const document = parseSessionRuntime(files.get(paths.config), files.get(paths.runtime), this.cwd, this.sessionId);
|
|
246
|
+
if (!document)
|
|
247
|
+
throw new RevisionUnavailableError("Current State Flow session runtime is unavailable");
|
|
248
|
+
const origin = `start:${randomUUID()}`;
|
|
249
|
+
const scopes = Object.fromEntries(SCOPES.map((scope) => {
|
|
250
|
+
const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
|
|
251
|
+
const stream = parseScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope, scope === "cwd" ? this.cwd : undefined, files.get(paths.meta));
|
|
252
|
+
if (!stream && scope === "session")
|
|
253
|
+
throw new RevisionUnavailableError("Current State Flow session memory is unavailable");
|
|
254
|
+
return [scope, stream ?? freshEmptyScopeStream(scope, origin, this.historyLimit)];
|
|
255
|
+
}));
|
|
256
|
+
validateScopeLineage(scopes.session, "session", document.meta.lineage, MAX_HISTORY_LIMIT);
|
|
257
|
+
let view;
|
|
258
|
+
try {
|
|
259
|
+
view = constrainTemporalState({ scopes, lineage: document.meta.lineage }, this.historyLimit);
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// Independently validated live shared streams can belong to another writer's lineage.
|
|
263
|
+
view = adoptTemporalStreams(scopes, origin, this.historyLimit);
|
|
264
|
+
}
|
|
265
|
+
const provenance = Object.fromEntries(SCOPES.map((scope) => {
|
|
266
|
+
const meta = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey).meta;
|
|
267
|
+
return [scope, parseScopeProvenance(files.get(meta), meta)];
|
|
268
|
+
}));
|
|
269
|
+
const snapshot = {
|
|
270
|
+
config: { enabled: false },
|
|
271
|
+
meta: { step: document.meta.step, ...(document.meta.bootstrap === true ? { bootstrap: true } : {}) },
|
|
272
|
+
};
|
|
273
|
+
this.view = view;
|
|
274
|
+
this.base = base;
|
|
275
|
+
this.provenanceByScope = provenance;
|
|
276
|
+
this.semanticRevision = undefined;
|
|
277
|
+
this.savedRuntime = undefined;
|
|
278
|
+
this.restoredOriginPending = false;
|
|
279
|
+
this.absentSharedScopes.clear();
|
|
280
|
+
return snapshot;
|
|
281
|
+
}
|
|
217
282
|
/** Copy one retained source-session boundary over the child's current shared scopes. */
|
|
218
283
|
prepareBoundaryFork(source, checkpoint) {
|
|
219
284
|
const parent = Object.freeze({ ...source });
|
|
@@ -285,7 +350,7 @@ export class TemporalRuntime {
|
|
|
285
350
|
const existingRuntime = parseSessionRuntime(files.get(paths.config), files.get(paths.runtime), this.cwd, this.sessionId);
|
|
286
351
|
if (existingRuntime && !newSessionOrigin)
|
|
287
352
|
throw new Error("Existing session runtime requires retained-boundary restoration");
|
|
288
|
-
//
|
|
353
|
+
// A pre-runtime branch may establish an empty origin, never import a later session layer.
|
|
289
354
|
if (newSessionOrigin)
|
|
290
355
|
streams.session = undefined;
|
|
291
356
|
if (copy)
|
|
@@ -316,7 +381,7 @@ export class TemporalRuntime {
|
|
|
316
381
|
/** Copy the private origin and apply configured retention folding, preserving live shared values/provenance. */
|
|
317
382
|
publishForkOrigin(snapshot) {
|
|
318
383
|
const runtime = createSessionRuntime(snapshot, this.cwd, this.sessionId, this.view.lineage, this.provenanceByScope.session);
|
|
319
|
-
const publication = publishTemporalStateToFiles(this.cwd, this.sessionId, this.view, SCOPES, this.base, this.root, runtime, this.sessionKey, this.provenanceByScope);
|
|
384
|
+
const publication = (this.transaction?.publish ?? publishTemporalStateToFiles)(this.cwd, this.sessionId, this.view, SCOPES, this.base, this.root, runtime, this.sessionKey, this.provenanceByScope);
|
|
320
385
|
this.base = publication.base;
|
|
321
386
|
this.semanticRevision = publication.revision;
|
|
322
387
|
this.savedRuntime = hashJson(runtime);
|
|
@@ -330,8 +395,8 @@ export class TemporalRuntime {
|
|
|
330
395
|
* actually changes remains a fail-closed write conflict. Divergence in non-adoptable
|
|
331
396
|
* session or runtime files also fails closed under the existing race rule.
|
|
332
397
|
*/
|
|
333
|
-
reconcileSharedDrift(changedScopes) {
|
|
334
|
-
const captured = captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
398
|
+
reconcileSharedDrift(changedScopes, current) {
|
|
399
|
+
const captured = current ?? captureTemporalFileBase(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
335
400
|
const liveFiles = new Map(captured.files.map((file) => [file.path, file]));
|
|
336
401
|
const priorFiles = new Map(this.base.files.map((file) => [file.path, file]));
|
|
337
402
|
const adoptable = new Set();
|
|
@@ -402,16 +467,201 @@ export class TemporalRuntime {
|
|
|
402
467
|
throw targetScopeConflict(targets);
|
|
403
468
|
return { view: reconciledView(), base: captured, provenance };
|
|
404
469
|
}
|
|
405
|
-
/**
|
|
406
|
-
refreshShared() {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
470
|
+
/** Await coherent shared inspection, lazily loading an empty private view only when none is selected. */
|
|
471
|
+
async refreshShared(signal) {
|
|
472
|
+
signal?.throwIfAborted();
|
|
473
|
+
if (!this.view && !lstatSync(this.root, { throwIfNoEntry: false }))
|
|
474
|
+
return false;
|
|
475
|
+
return withStorageTransaction(this.root, (storage) => {
|
|
476
|
+
const current = storage.capture(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
477
|
+
if (!this.view)
|
|
478
|
+
return this.loadPassiveBase(current);
|
|
479
|
+
if (!this.base)
|
|
480
|
+
throw new Error("State Flow shared refresh requires a selected temporal runtime");
|
|
481
|
+
const before = this.view;
|
|
482
|
+
const reconciled = this.reconcileSharedDrift(new Set(), current);
|
|
483
|
+
this.view = reconciled.view;
|
|
484
|
+
this.base = reconciled.base;
|
|
485
|
+
this.provenanceByScope = reconciled.provenance;
|
|
486
|
+
return before !== this.view;
|
|
487
|
+
}, signal);
|
|
488
|
+
}
|
|
489
|
+
/** Prepare current shared state while retaining the exact accepted private publication basis. */
|
|
490
|
+
publicationCandidate(storage, current) {
|
|
491
|
+
if (this.restoredOriginPending)
|
|
492
|
+
throw new RevisionUnavailableError("State Flow restored origin must be accepted before patching memory");
|
|
493
|
+
current ??= storage.capture(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
494
|
+
const candidate = new TemporalRuntime(this.cwd, this.session, this.root, undefined, this.historyLimit);
|
|
495
|
+
candidate.transaction = storage;
|
|
496
|
+
if (this.semanticRevision && this.view && this.base) {
|
|
497
|
+
candidate.view = this.view;
|
|
498
|
+
candidate.base = this.base;
|
|
499
|
+
candidate.provenanceByScope = this.provenanceByScope;
|
|
500
|
+
const reconciled = candidate.reconcileSharedDrift(new Set(), current);
|
|
501
|
+
candidate.view = reconciled.view;
|
|
502
|
+
candidate.base = reconciled.base;
|
|
503
|
+
candidate.provenanceByScope = reconciled.provenance;
|
|
504
|
+
return candidate;
|
|
505
|
+
}
|
|
506
|
+
const files = new Map(current.files.map((file) => [file.path, file.content]));
|
|
507
|
+
const paths = sessionRuntimePaths(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
508
|
+
const session = temporalScopePaths(this.cwd, this.sessionId, "session", this.root, this.sessionKey);
|
|
509
|
+
if ([paths.config, paths.runtime, session.checkpoint, session.patches, session.meta].some((path) => files.get(path) !== undefined)) {
|
|
510
|
+
const document = parseSessionRuntime(files.get(paths.config), files.get(paths.runtime), this.cwd, this.sessionId);
|
|
511
|
+
const stream = parseScopeStream(files.get(session.checkpoint), files.get(session.patches), "session", undefined, files.get(session.meta));
|
|
512
|
+
if (!document || !stream)
|
|
513
|
+
throw new RevisionUnavailableError("Current State Flow session memory is incomplete");
|
|
514
|
+
validateScopeLineage(stream, "session", document.meta.lineage, MAX_HISTORY_LIMIT);
|
|
515
|
+
throw new RevisionUnavailableError("Existing State Flow session memory is not selected; select an accepted boundary or use /state-flow-start");
|
|
516
|
+
}
|
|
517
|
+
const origin = `patch:${randomUUID()}`;
|
|
518
|
+
const streams = Object.fromEntries(SCOPES.map((scope) => {
|
|
519
|
+
const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
|
|
520
|
+
return [scope, parseScopeStream(files.get(paths.checkpoint), files.get(paths.patches), scope, scope === "cwd" ? this.cwd : undefined, files.get(paths.meta)) ?? freshEmptyScopeStream(scope, `${origin}:empty`, this.historyLimit)];
|
|
521
|
+
}));
|
|
522
|
+
candidate.view = adoptTemporalStreams(streams, origin, this.historyLimit);
|
|
523
|
+
candidate.base = current;
|
|
524
|
+
candidate.provenanceByScope = Object.fromEntries(SCOPES.map((scope) => {
|
|
525
|
+
const paths = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey);
|
|
526
|
+
// Absent semantic pairs cannot confer compilation evidence on a new registration.
|
|
527
|
+
return [scope, files.get(paths.checkpoint) === undefined ? {} : parseScopeProvenance(files.get(paths.meta), paths.meta)];
|
|
528
|
+
}));
|
|
529
|
+
return candidate;
|
|
530
|
+
}
|
|
531
|
+
/** Stage and accept synchronously inside an awaited lock; expose neither selection nor raw storage operations. */
|
|
532
|
+
async withPatchTransaction(action, signal) {
|
|
533
|
+
return this.withPublicationTransaction((candidate, publish) => action({
|
|
534
|
+
states: candidate.states(),
|
|
535
|
+
causalBasis: candidate.causalBasis(),
|
|
536
|
+
provenance: structuredClone(candidate.provenanceByScope),
|
|
537
|
+
publish,
|
|
538
|
+
}), { kind: "patch" }, signal);
|
|
539
|
+
}
|
|
540
|
+
/** Activate current owned memory; the caller must authorize a wholly absent private origin after waiting. */
|
|
541
|
+
async withStartTransaction(action, signal, allowCreateOrigin = false) {
|
|
542
|
+
return this.withPublicationTransaction((_candidate, publish, current) => action(current, (snapshot) => publish(snapshot)), { kind: "start", allowCreateOrigin }, signal);
|
|
543
|
+
}
|
|
544
|
+
/** Select one retained private boundary beside current shared streams, then accept only after caller policy is rechecked. */
|
|
545
|
+
async withRestoreTransaction(checkpoint, action, signal) {
|
|
546
|
+
signal?.throwIfAborted();
|
|
547
|
+
return this.withPublicationTransaction((_candidate, publish, selected) => action(selected, (snapshot) => publish(snapshot)), { kind: "restore", checkpoint: structuredClone(checkpoint) }, signal);
|
|
548
|
+
}
|
|
549
|
+
/** Copy exact retained parent authority into an unoccupied child; the caller rechecks native selection after waiting. */
|
|
550
|
+
async withForkTransaction(source, checkpoint, action, signal) {
|
|
551
|
+
signal?.throwIfAborted();
|
|
552
|
+
return this.withPublicationTransaction((_candidate, publish, selected) => action(selected, (snapshot) => publish(snapshot)), { kind: "fork", source: { ...source }, checkpoint: structuredClone(checkpoint) }, signal);
|
|
553
|
+
}
|
|
554
|
+
prepareForkCandidate(storage, source, checkpoint) {
|
|
555
|
+
const sourceBase = storage.capture(this.cwd, source.id, this.root, source.key);
|
|
556
|
+
const parent = new TemporalRuntime(this.cwd, source, this.root, undefined, this.historyLimit);
|
|
557
|
+
const selected = parent.prepareBoundaryRestoreBase(checkpoint, sourceBase).restore();
|
|
558
|
+
const base = storage.capture(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
559
|
+
const files = new Map(base.files.map((file) => [file.path, file.content]));
|
|
560
|
+
const paths = temporalScopePaths(this.cwd, this.sessionId, "session", this.root, this.sessionKey);
|
|
561
|
+
if ([paths.checkpoint, paths.patches, paths.meta, join(paths.directory, "config.json"), join(paths.directory, "runtime.json")].some((path) => files.get(path) !== undefined)) {
|
|
562
|
+
throw new Error("State Flow fork target already has session storage");
|
|
563
|
+
}
|
|
564
|
+
if (SCOPES.some((scope) => lstatSync(join(temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey).directory, "state.json"), { throwIfNoEntry: false }))) {
|
|
565
|
+
throw new Error("Unsupported State Flow storage exists; preserve or convert it before initialization");
|
|
566
|
+
}
|
|
567
|
+
const candidate = new TemporalRuntime(this.cwd, this.session, this.root, undefined, this.historyLimit);
|
|
568
|
+
candidate.transaction = storage;
|
|
569
|
+
candidate.base = base;
|
|
570
|
+
candidate.view = adoptTemporalStreams(parent.view.scopes, `files:${randomUUID()}`, this.historyLimit);
|
|
571
|
+
candidate.provenanceByScope = { ...parent.provenanceByScope };
|
|
572
|
+
for (const scope of SHARED_SCOPES) {
|
|
573
|
+
const meta = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey).meta;
|
|
574
|
+
candidate.provenanceByScope[scope] = parseScopeProvenance(files.get(meta), meta);
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
candidate,
|
|
578
|
+
current: { config: selected.config, meta: { step: 0, ...(selected.meta.bootstrap === undefined ? {} : { bootstrap: selected.meta.bootstrap }) } },
|
|
579
|
+
assertSource: () => assertTemporalFileBase(sourceBase, storage.capture(this.cwd, source.id, this.root, source.key)),
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
/** Recheck caller policy after waiting, then accept only config/runtime over an already accepted private basis. */
|
|
583
|
+
async withLifecycleTransaction(action, signal) {
|
|
584
|
+
return this.withPublicationTransaction((_candidate, publish) => action((snapshot) => publish(snapshot)), { kind: "lifecycle" }, signal);
|
|
585
|
+
}
|
|
586
|
+
async withPublicationTransaction(action, selection, signal) {
|
|
587
|
+
const { kind } = selection;
|
|
588
|
+
const allowCreateOrigin = selection.kind === "start" && selection.allowCreateOrigin;
|
|
589
|
+
const semantic = kind !== "lifecycle";
|
|
590
|
+
const assertAuthority = () => {
|
|
591
|
+
if (selection.kind === "fork") {
|
|
592
|
+
if (selection.source.id === this.sessionId || selection.source.key === this.sessionKey)
|
|
593
|
+
throw new Error("State Flow fork requires a distinct session identity and key");
|
|
594
|
+
if (this.view)
|
|
595
|
+
throw new Error("State Flow fork target already has session storage");
|
|
596
|
+
}
|
|
597
|
+
if (!semantic && (!this.semanticRevision || !this.view || !this.base || this.restoredOriginPending)) {
|
|
598
|
+
throw new Error("State Flow lifecycle transaction requires an accepted runtime");
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
signal?.throwIfAborted();
|
|
602
|
+
assertAuthority();
|
|
603
|
+
if (kind === "patch" || allowCreateOrigin)
|
|
604
|
+
initializeFileStore(this.root);
|
|
605
|
+
if ((kind === "start" || kind === "restore" || kind === "fork") && !lstatSync(this.root, { throwIfNoEntry: false })) {
|
|
606
|
+
throw new RevisionUnavailableError(kind !== "start" ? "Selected State Flow boundary storage is unavailable" : "Current State Flow session storage is unavailable");
|
|
607
|
+
}
|
|
608
|
+
return withStorageTransaction(this.root, (storage) => {
|
|
609
|
+
assertAuthority();
|
|
610
|
+
let current;
|
|
611
|
+
let candidate;
|
|
612
|
+
let assertSource;
|
|
613
|
+
if (selection.kind === "fork") {
|
|
614
|
+
({ candidate, current, assertSource } = this.prepareForkCandidate(storage, selection.source, selection.checkpoint));
|
|
615
|
+
}
|
|
616
|
+
else if (selection.kind === "restore" || selection.kind === "start") {
|
|
617
|
+
candidate = new TemporalRuntime(this.cwd, this.session, this.root, undefined, this.historyLimit);
|
|
618
|
+
candidate.transaction = storage;
|
|
619
|
+
const base = storage.capture(this.cwd, this.sessionId, this.root, this.sessionKey);
|
|
620
|
+
current = selection.kind === "restore"
|
|
621
|
+
? candidate.prepareBoundaryRestoreBase(selection.checkpoint, base).restore()
|
|
622
|
+
: candidate.loadCurrentMemoryBase(base);
|
|
623
|
+
if (!current) {
|
|
624
|
+
if (!allowCreateOrigin)
|
|
625
|
+
throw new RevisionUnavailableError("Current State Flow session memory is unavailable");
|
|
626
|
+
if (SCOPES.some((scope) => lstatSync(join(temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey).directory, "state.json"), { throwIfNoEntry: false }))) {
|
|
627
|
+
throw new Error("Unsupported State Flow storage exists; preserve or convert it before initialization");
|
|
628
|
+
}
|
|
629
|
+
candidate = candidate.publicationCandidate(storage, base);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
else
|
|
633
|
+
candidate = this.publicationCandidate(storage);
|
|
634
|
+
let consumed = false;
|
|
635
|
+
let published = false;
|
|
636
|
+
const result = action(candidate, (snapshot, accepted, provenance) => {
|
|
637
|
+
if (consumed)
|
|
638
|
+
throw new Error(`State Flow ${kind} transaction publication was already consumed`);
|
|
639
|
+
consumed = true;
|
|
640
|
+
signal?.throwIfAborted();
|
|
641
|
+
assertSource?.();
|
|
642
|
+
// Patch preparation without semantic work preserves complete accepted cohorts; selection accepts origins with configured folding.
|
|
643
|
+
// New authority or wholly absent shared pairs still need normal atomic initialization; partial evidence already failed.
|
|
644
|
+
const writeSemantic = semantic && (kind === "start" || kind === "restore" || kind === "fork" || accepted !== undefined || !this.semanticRevision
|
|
645
|
+
|| SCOPES.some((scope) => Object.keys(provenance?.[scope] ?? {}).length > 0)
|
|
646
|
+
|| candidate.base.files.some((file) => file.content === undefined));
|
|
647
|
+
// Every capability acceptance validates its captured cohort, including semantic no-ops.
|
|
648
|
+
const publication = kind === "fork" ? candidate.publishForkOrigin(snapshot) : candidate.publish(snapshot, writeSemantic, accepted, { provenance });
|
|
649
|
+
if (!publication)
|
|
650
|
+
throw new Error(`State Flow ${kind} transaction produced no canonical publication`);
|
|
651
|
+
this.view = candidate.view;
|
|
652
|
+
this.base = candidate.base;
|
|
653
|
+
this.provenanceByScope = candidate.provenanceByScope;
|
|
654
|
+
this.semanticRevision = candidate.semanticRevision;
|
|
655
|
+
this.savedRuntime = candidate.savedRuntime;
|
|
656
|
+
this.restoredOriginPending = false;
|
|
657
|
+
this.absentSharedScopes.clear();
|
|
658
|
+
published = true;
|
|
659
|
+
return publication;
|
|
660
|
+
}, current);
|
|
661
|
+
if (!published)
|
|
662
|
+
throw new Error(`State Flow ${kind} transaction requires one synchronous publication`);
|
|
663
|
+
return result;
|
|
664
|
+
}, signal);
|
|
415
665
|
}
|
|
416
666
|
/** Canonically accept a prepared retained-boundary origin before lifecycle-only persistence. */
|
|
417
667
|
acceptRestoredOrigin(snapshot) {
|
|
@@ -434,8 +684,8 @@ export class TemporalRuntime {
|
|
|
434
684
|
let basis = this.view;
|
|
435
685
|
let base = this.base;
|
|
436
686
|
let basisProvenance = this.provenanceByScope;
|
|
437
|
-
//
|
|
438
|
-
if (this.semanticRevision || accepted !== undefined || provenanceScopes.length > 0) {
|
|
687
|
+
// A transaction already selected its current basis under exclusion; raw replay callers still guard their older basis.
|
|
688
|
+
if (!this.transaction && (this.semanticRevision || accepted !== undefined || provenanceScopes.length > 0)) {
|
|
439
689
|
const changedScopes = new Set([
|
|
440
690
|
...(accepted?.transitions ?? []).map(({ scope }) => scope),
|
|
441
691
|
...provenanceScopes,
|
|
@@ -462,7 +712,7 @@ export class TemporalRuntime {
|
|
|
462
712
|
const fingerprint = hashJson(runtime);
|
|
463
713
|
if (!semantic && fingerprint === this.savedRuntime && !provenanceChanged)
|
|
464
714
|
return undefined;
|
|
465
|
-
const result = publishTemporalStateToFiles(this.cwd, this.sessionId, next, runtimeOnly ? [] : SCOPES, base, this.root, runtime, this.sessionKey, runtimeOnly ? undefined : nextProvenance, runtimeOnly);
|
|
715
|
+
const result = (this.transaction?.publish ?? publishTemporalStateToFiles)(this.cwd, this.sessionId, next, runtimeOnly ? [] : SCOPES, base, this.root, runtime, this.sessionKey, runtimeOnly ? undefined : nextProvenance, runtimeOnly);
|
|
466
716
|
this.base = result.base;
|
|
467
717
|
this.view = next;
|
|
468
718
|
this.provenanceByScope = nextProvenance;
|
|
@@ -20,6 +20,9 @@ export interface SessionEntryLookup {
|
|
|
20
20
|
export interface PassiveStopBoundary {
|
|
21
21
|
at: number;
|
|
22
22
|
from?: number;
|
|
23
|
+
preserveContext?: true;
|
|
24
|
+
/** A same-owner failed Stop remains a write fence until a later accepted checkpoint. */
|
|
25
|
+
persistenceError?: string;
|
|
23
26
|
}
|
|
24
27
|
export interface SnapshotDiscovery {
|
|
25
28
|
candidates: unknown[];
|
|
@@ -30,6 +33,8 @@ export declare function discoverSnapshotData(branch: readonly BranchEntry[]): Sn
|
|
|
30
33
|
export declare function snapshotDataNewestFirst(branch: readonly BranchEntry[]): unknown[];
|
|
31
34
|
export declare function latestSnapshotData(branch: readonly BranchEntry[]): unknown;
|
|
32
35
|
export declare function hasPriorConversation(branch: readonly BranchEntry[]): boolean;
|
|
36
|
+
/** Native conversation after the latest valid checkpoint may contain uncompiled work, not a new semantic authority. */
|
|
37
|
+
export declare function hasUncheckpointedConversation(branch: readonly BranchEntry[]): boolean;
|
|
33
38
|
/** Auto-start eligibility is session identity/lifecycle, not the presence of CWD materialization. */
|
|
34
39
|
export declare function isNewSession(reason: unknown, branch: readonly BranchEntry[]): boolean;
|
|
35
40
|
export declare function findAssistantToolBatch(session: SessionEntryLookup, toolCallId: string): string[] | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseRetainedPiCheckpoint } from "./snapshot.js";
|
|
1
2
|
export const SNAPSHOT_ENTRY_TYPE = "state-flow-snapshot";
|
|
2
3
|
/** Enumerate active-branch snapshots newest-first while containing hostile entries. */
|
|
3
4
|
export function discoverSnapshotData(branch) {
|
|
@@ -36,6 +37,27 @@ export function hasPriorConversation(branch) {
|
|
|
36
37
|
}
|
|
37
38
|
return false;
|
|
38
39
|
}
|
|
40
|
+
/** Native conversation after the latest valid checkpoint may contain uncompiled work, not a new semantic authority. */
|
|
41
|
+
export function hasUncheckpointedConversation(branch) {
|
|
42
|
+
for (let index = branch.length - 1; index >= 0; index--) {
|
|
43
|
+
try {
|
|
44
|
+
const entry = branch[index];
|
|
45
|
+
if (entry?.type === "message") {
|
|
46
|
+
const role = entry.message?.role;
|
|
47
|
+
if (role === "user" || role === "assistant" || role === "toolResult")
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (entry?.type === "custom" && entry.customType === SNAPSHOT_ENTRY_TYPE) {
|
|
51
|
+
parseRetainedPiCheckpoint(entry.data);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Malformed entries cannot prove that pending input was checkpointed.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
39
61
|
/** Auto-start eligibility is session identity/lifecycle, not the presence of CWD materialization. */
|
|
40
62
|
export function isNewSession(reason, branch) {
|
|
41
63
|
if (reason === "new")
|
|
@@ -58,17 +80,29 @@ export function findAssistantToolBatch(session, toolCallId) {
|
|
|
58
80
|
return undefined;
|
|
59
81
|
}
|
|
60
82
|
export function findPassiveStopBoundary(branch, sessionId, entryType) {
|
|
83
|
+
let checkpointSeen = false;
|
|
61
84
|
for (const entry of [...branch].reverse()) {
|
|
62
85
|
try {
|
|
63
|
-
if (entry?.type !== "custom"
|
|
86
|
+
if (entry?.type !== "custom")
|
|
64
87
|
continue;
|
|
65
|
-
|
|
88
|
+
if (entry.customType === SNAPSHOT_ENTRY_TYPE) {
|
|
89
|
+
parseRetainedPiCheckpoint(entry.data);
|
|
90
|
+
checkpointSeen = true;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (entry.customType !== entryType)
|
|
94
|
+
continue;
|
|
95
|
+
const { at, from, reset, owner, preserveContext, persistenceError } = entry.data ?? {};
|
|
66
96
|
if (reset === true && owner === sessionId)
|
|
67
97
|
return undefined;
|
|
98
|
+
if (owner !== undefined && owner !== sessionId)
|
|
99
|
+
continue;
|
|
68
100
|
if (typeof at === "number" && Number.isSafeInteger(at) && at >= 0)
|
|
69
101
|
return {
|
|
70
102
|
at,
|
|
71
103
|
...(typeof from === "number" && Number.isSafeInteger(from) && from >= 0 ? { from } : {}),
|
|
104
|
+
...(preserveContext === true ? { preserveContext: true } : {}),
|
|
105
|
+
...(!checkpointSeen && owner === sessionId && typeof persistenceError === "string" && persistenceError.trim().length > 0 ? { persistenceError } : {}),
|
|
72
106
|
};
|
|
73
107
|
}
|
|
74
108
|
catch {
|
|
@@ -4,6 +4,9 @@ import { type TransitionBoundary } from "./temporal.ts";
|
|
|
4
4
|
/** Missing operational capability is not evidence that a checkpoint target is invalid. */
|
|
5
5
|
export declare class RevisionUnavailableError extends Error {
|
|
6
6
|
}
|
|
7
|
+
/** Expired history cannot be restored, but explicit activation may use validated current memory. */
|
|
8
|
+
export declare class HistoryBoundaryExpiredError extends RevisionUnavailableError {
|
|
9
|
+
}
|
|
7
10
|
export interface SnapshotConfig {
|
|
8
11
|
enabled: boolean;
|
|
9
12
|
}
|
|
@@ -8,6 +8,9 @@ const MAX_LEGACY_VALIDATION_ATTEMPT = 7;
|
|
|
8
8
|
/** Missing operational capability is not evidence that a checkpoint target is invalid. */
|
|
9
9
|
export class RevisionUnavailableError extends Error {
|
|
10
10
|
}
|
|
11
|
+
/** Expired history cannot be restored, but explicit activation may use validated current memory. */
|
|
12
|
+
export class HistoryBoundaryExpiredError extends RevisionUnavailableError {
|
|
13
|
+
}
|
|
11
14
|
export function validateSessionRuntime(value, cwd, sessionId) {
|
|
12
15
|
if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "config,meta"
|
|
13
16
|
|| !isObject(value.config) || !isObject(value.meta))
|
|
@@ -26,6 +26,7 @@ export interface StatusDiagnostics {
|
|
|
26
26
|
};
|
|
27
27
|
staleArtifacts: readonly StaleArtifactDiagnostic[];
|
|
28
28
|
durableStateError?: string;
|
|
29
|
+
publicationError?: string;
|
|
29
30
|
}
|
|
30
31
|
export declare function formatScopeRevisionVector(revisions: ScopeRevisions): string;
|
|
31
32
|
export declare function compactStatus(snapshot: Snapshot, revisions: ScopeRevisions, colorize: Colorize): string | undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { projectRecentTransitionsWithLimit } from "./history.js";
|
|
2
2
|
import { retainedMemoryScopes } from "./memory.js";
|
|
3
|
+
import { conciseDiagnostic } from "./protocol.js";
|
|
3
4
|
import { overlayStates } from "./state.js";
|
|
4
5
|
export const STATUS_KEY = "state-flow";
|
|
5
6
|
export function formatScopeRevisionVector(revisions) {
|
|
@@ -27,7 +28,7 @@ export function detailedStatus(snapshot, diagnostics) {
|
|
|
27
28
|
];
|
|
28
29
|
const temporal = available ? diagnostics.temporal : undefined;
|
|
29
30
|
const temporalLines = temporal === undefined
|
|
30
|
-
? [`Temporal materialization unavailable: ${diagnostics.durableStateError ?? "no selected branch runtime"}`,
|
|
31
|
+
? [`Temporal materialization unavailable: ${conciseDiagnostic(diagnostics.durableStateError ?? "no selected branch runtime")}`,
|
|
31
32
|
`Hot history: unavailable; configured maximum depth ${diagnostics.historyLimit}`,
|
|
32
33
|
"Retained patch tails: unavailable"]
|
|
33
34
|
: [`Temporal head: ${JSON.stringify(temporal.head.id)}; branch-local position ${temporal.head.position}`,
|
|
@@ -45,6 +46,7 @@ export function detailedStatus(snapshot, diagnostics) {
|
|
|
45
46
|
"Memory: owner state-flow; global retention enabled; global fallback active",
|
|
46
47
|
`Memory-bearing scopes: global ${memoryScopes?.global ?? "unknown"}; CWD ${memoryScopes?.cwd ?? "unknown"}; session ${memoryScopes?.session ?? "unknown"}`,
|
|
47
48
|
...temporalLines,
|
|
49
|
+
...(diagnostics.publicationError === undefined ? [] : [`Memory writes paused after Stop: ${conciseDiagnostic(diagnostics.publicationError)}`]),
|
|
48
50
|
`Artifacts: global ${artifacts("global")}; CWD ${artifacts("cwd")}; session ${artifacts("session")}; pending invalidations ${invalidated}`,
|
|
49
51
|
available ? `Recent transitions: global ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "global")).length}; CWD ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "cwd")).length}; session ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "session")).length}; active ${projectedRecent.length}` : "Recent transitions: unavailable",
|
|
50
52
|
...invalidationLines,
|
|
@@ -14,6 +14,17 @@ export declare function initializeFileStore(root: string): void;
|
|
|
14
14
|
export declare function acquirePublicationLock(path: string, unavailable: (cause: unknown) => Error): number;
|
|
15
15
|
/** Canonical writers and bounded backup capture share exclusion; no Git work runs under this lock. */
|
|
16
16
|
export declare function withStoragePublicationLock<T>(repositoryRoot: string, action: (root: string) => T): T;
|
|
17
|
+
export interface StorageTransaction {
|
|
18
|
+
readonly capture: typeof captureTemporalFileBase;
|
|
19
|
+
readonly publish: typeof publishTemporalStateToFiles;
|
|
20
|
+
}
|
|
21
|
+
export declare class PublicationBusyError extends Error {
|
|
22
|
+
constructor(path: string);
|
|
23
|
+
}
|
|
24
|
+
/** Await an exact file mutex; shared by canonical transactions and the independent Git backup owner. */
|
|
25
|
+
export declare function withFilePublicationLock<T>(lockPath: string, action: () => T | Promise<T>, signal?: AbortSignal, unavailable?: (cause: unknown) => Error, waitForLock?: boolean): Promise<T>;
|
|
26
|
+
/** Await store-wide exclusion, then capture/apply/publish through callback-scoped operations. */
|
|
27
|
+
export declare function withStorageTransaction<T>(repositoryRoot: string, action: (transaction: StorageTransaction) => T | Promise<T>, signal?: AbortSignal, waitForLock?: boolean): Promise<T>;
|
|
17
28
|
export declare function assertTemporalFileBase(expected: TemporalFileBase, current: TemporalFileBase): void;
|
|
18
29
|
/** Plan exact canonical updates; lifecycle-only writes exclude semantic files and provenance. */
|
|
19
30
|
export declare function planTemporalPublication(cwd: string, sessionId: string, view: TemporalState, scopes: readonly StateScope[], current: TemporalFileBase, root: string, runtime?: SessionRuntime, runtimeOnly?: boolean, sessionKey?: string, provenance?: Readonly<Record<StateScope, ArtifactProvenanceRegistry>>): {
|