@llblab/pi-kit 0.20.0 → 0.21.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 +13 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +6 -6
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +9 -0
- package/node_modules/@llblab/pi-state-flow/README.md +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/index.js +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +1 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +7 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +41 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +94 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +2 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +11 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +4 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +8 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +6 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +19 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +5 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +27 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +10 -8
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +4 -3
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +5 -5
- package/node_modules/@llblab/pi-state-flow/index.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +8 -3
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +41 -9
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +83 -1
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +2 -4
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +11 -0
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +10 -4
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +23 -6
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +29 -3
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +2 -2
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +3 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +6 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +5 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +9 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +8 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +63 -11
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +4 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +14 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +5 -0
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +7 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +13 -2
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +19 -0
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +9 -0
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +75 -16
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +18 -0
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +7 -0
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
|
@@ -2,7 +2,7 @@ import type { ArtifactInvalidationReason } from "./artifact.ts";
|
|
|
2
2
|
import { type RecentTransitionWindow } from "./history.ts";
|
|
3
3
|
import type { Snapshot } from "./snapshot.ts";
|
|
4
4
|
import { type ScopedStates, type StateScope } from "./state.ts";
|
|
5
|
-
import type { TransitionBoundary } from "./temporal.ts";
|
|
5
|
+
import type { ScopeRevisions, TransitionBoundary } from "./temporal.ts";
|
|
6
6
|
export declare const STATUS_KEY = "state-flow";
|
|
7
7
|
export type Colorize = (color: "accent" | "dim", text: string) => string;
|
|
8
8
|
export type StaleArtifactReason = ArtifactInvalidationReason | "source-removed";
|
|
@@ -22,9 +22,11 @@ export interface StatusDiagnostics {
|
|
|
22
22
|
head: TransitionBoundary;
|
|
23
23
|
historyDepth: number;
|
|
24
24
|
tailCounts: Record<StateScope, number>;
|
|
25
|
+
revisions: ScopeRevisions;
|
|
25
26
|
};
|
|
26
27
|
staleArtifacts: readonly StaleArtifactDiagnostic[];
|
|
27
28
|
durableStateError?: string;
|
|
28
29
|
}
|
|
29
|
-
export declare function
|
|
30
|
+
export declare function formatScopeRevisionVector(revisions: ScopeRevisions): string;
|
|
31
|
+
export declare function compactStatus(snapshot: Snapshot, revisions: ScopeRevisions, colorize: Colorize): string | undefined;
|
|
30
32
|
export declare function detailedStatus(snapshot: Snapshot, diagnostics: StatusDiagnostics): string;
|
|
@@ -2,8 +2,13 @@ import { projectRecentTransitionsWithLimit } from "./history.js";
|
|
|
2
2
|
import { retainedMemoryScopes } from "./memory.js";
|
|
3
3
|
import { overlayStates } from "./state.js";
|
|
4
4
|
export const STATUS_KEY = "state-flow";
|
|
5
|
-
export function
|
|
6
|
-
return
|
|
5
|
+
export function formatScopeRevisionVector(revisions) {
|
|
6
|
+
return `G${revisions.global}/C${revisions.cwd}/S${revisions.session}`;
|
|
7
|
+
}
|
|
8
|
+
export function compactStatus(snapshot, revisions, colorize) {
|
|
9
|
+
if (!snapshot.config.enabled)
|
|
10
|
+
return undefined;
|
|
11
|
+
return `${colorize("accent", "state-flow")} ${colorize("dim", formatScopeRevisionVector(revisions))}`;
|
|
7
12
|
}
|
|
8
13
|
function countArtifacts(states, scope) {
|
|
9
14
|
return Object.keys(states[scope].artifacts).length;
|
|
@@ -26,6 +31,7 @@ export function detailedStatus(snapshot, diagnostics) {
|
|
|
26
31
|
`Hot history: unavailable; configured maximum depth ${diagnostics.historyLimit}`,
|
|
27
32
|
"Retained patch tails: unavailable"]
|
|
28
33
|
: [`Temporal head: ${JSON.stringify(temporal.head.id)}; branch-local position ${temporal.head.position}`,
|
|
34
|
+
`Scope revisions: global #${temporal.revisions.global}; CWD #${temporal.revisions.cwd}; session #${temporal.revisions.session}; effective ${formatScopeRevisionVector(temporal.revisions)}`,
|
|
29
35
|
`Hot history: offsets 0..${temporal.historyDepth}; maximum depth ${diagnostics.historyLimit}`,
|
|
30
36
|
`Retained patch tails: global ${temporal.tailCounts.global}; CWD ${temporal.tailCounts.cwd}; session ${temporal.tailCounts.session}`];
|
|
31
37
|
const artifacts = (scope) => available ? countArtifacts(diagnostics.scopeStates, scope) : "unknown";
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import type { ScopeRevisions } from "./temporal.ts";
|
|
1
2
|
export declare const STATE_FLOW_TELEGRAM_ID = "@llblab/pi-state-flow";
|
|
2
3
|
/** Resolve the package export or the compiled sibling-extension layout used in local development. */
|
|
3
4
|
export declare function stateFlowTelegramSectionSpecifiers(moduleUrl?: string): string[];
|
|
4
5
|
export interface StateFlowTelegramSnapshot {
|
|
5
6
|
enabled: boolean;
|
|
7
|
+
/** Legacy branch step retained for existing adapter ports; current runtime ports also supply owner revisions. */
|
|
6
8
|
step: number;
|
|
9
|
+
revisions?: ScopeRevisions;
|
|
7
10
|
bootstrap: boolean;
|
|
8
11
|
startPending: boolean;
|
|
9
12
|
}
|
|
@@ -79,6 +82,8 @@ export interface StateFlowTelegramControlResult {
|
|
|
79
82
|
export interface StateFlowTelegramPort {
|
|
80
83
|
snapshot(): StateFlowTelegramSnapshot;
|
|
81
84
|
state(scope: StateFlowTelegramScope): StateFlowTelegramState;
|
|
85
|
+
/** Optional additive capability; absent legacy ports retain their branch-step presentation. */
|
|
86
|
+
revisions?(): ScopeRevisions;
|
|
82
87
|
canStartNow(): boolean;
|
|
83
88
|
start(): StateFlowTelegramControlResult;
|
|
84
89
|
stop(): StateFlowTelegramControlResult;
|
|
@@ -94,7 +99,7 @@ export declare function formatStateFlowSectionLabel(snapshot: StateFlowTelegramS
|
|
|
94
99
|
/** The submenu header repeats the button's state line; the single action matches the current state. */
|
|
95
100
|
export declare function buildStateFlowSectionView(snapshot: StateFlowTelegramSnapshot, callbackData: (action: string) => string): StateFlowTelegramView;
|
|
96
101
|
export declare function buildStateFlowScopeChooser(callbackData: (action: string, payload?: string) => string): StateFlowTelegramView;
|
|
97
|
-
export declare function renderStateFlowRichState(scope: StateFlowTelegramScope,
|
|
102
|
+
export declare function renderStateFlowRichState(scope: StateFlowTelegramScope, revisions: ScopeRevisions, state: StateFlowTelegramState): StateFlowTelegramRichMessage;
|
|
98
103
|
/** Default loader; injectable so tests and embedded hosts can control transport presence. */
|
|
99
104
|
export declare function loadStateFlowTelegramModules(): Promise<StateFlowTelegramModules>;
|
|
100
105
|
export declare function createStateFlowTelegramAdapter(options: {
|
|
@@ -10,6 +10,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
10
10
|
}
|
|
11
11
|
return path;
|
|
12
12
|
};
|
|
13
|
+
import { formatScopeRevisionVector } from "./status.js";
|
|
13
14
|
export const STATE_FLOW_TELEGRAM_ID = "@llblab/pi-state-flow";
|
|
14
15
|
/** Resolve the package export or the compiled sibling-extension layout used in local development. */
|
|
15
16
|
export function stateFlowTelegramSectionSpecifiers(moduleUrl = import.meta.url) {
|
|
@@ -20,11 +21,15 @@ export function stateFlowTelegramSectionSpecifiers(moduleUrl = import.meta.url)
|
|
|
20
21
|
}
|
|
21
22
|
/** Main-menu section label doubles as the live status value: the spiral identity is constant, the value is not. */
|
|
22
23
|
export function formatStateFlowSectionLabel(snapshot) {
|
|
23
|
-
|
|
24
|
+
if (!snapshot.enabled)
|
|
25
|
+
return "🌀 State Flow: off";
|
|
26
|
+
return `🌀 State Flow: ${snapshot.revisions ? formatScopeRevisionVector(snapshot.revisions) : `#${snapshot.step}`}`;
|
|
24
27
|
}
|
|
25
28
|
/** Shared live value: plain in the button label, monospaced in the submenu state line. */
|
|
26
29
|
function stateFlowLabelValue(snapshot) {
|
|
27
|
-
|
|
30
|
+
if (!snapshot.enabled)
|
|
31
|
+
return "off";
|
|
32
|
+
return snapshot.revisions ? formatScopeRevisionVector(snapshot.revisions) : `#${snapshot.step}`;
|
|
28
33
|
}
|
|
29
34
|
/** Submenu state line: the same identity as the button label, with the live value in monospace. */
|
|
30
35
|
function formatStateFlowSectionHeader(snapshot) {
|
|
@@ -93,13 +98,18 @@ function renderStateFlowTelegramField(value) {
|
|
|
93
98
|
}
|
|
94
99
|
return rendered;
|
|
95
100
|
}
|
|
96
|
-
export function renderStateFlowRichState(scope,
|
|
97
|
-
const fields =
|
|
101
|
+
export function renderStateFlowRichState(scope, revisions, state) {
|
|
102
|
+
const fields = scope === "global" || scope === "cwd"
|
|
103
|
+
? ["intents", "contract", "working", "artifacts", "lazy"]
|
|
104
|
+
: ["intents", "contract", "working", "artifacts", "response", "lazy"];
|
|
105
|
+
const revision = scope === "effective"
|
|
106
|
+
? formatScopeRevisionVector(revisions)
|
|
107
|
+
: `#${revisions[scope]}`;
|
|
98
108
|
return {
|
|
99
109
|
blocks: [
|
|
100
110
|
{
|
|
101
111
|
type: "heading",
|
|
102
|
-
text: [`${STATE_FLOW_SCOPE_LABELS[scope]}: `, { type: "code", text:
|
|
112
|
+
text: [`${STATE_FLOW_SCOPE_LABELS[scope]}: `, { type: "code", text: revision }],
|
|
103
113
|
size: 3,
|
|
104
114
|
},
|
|
105
115
|
...fields.map((field) => ({
|
|
@@ -134,7 +144,10 @@ function buildStateFlowTelegramSection(port) {
|
|
|
134
144
|
if (ctx.action === "inspect") {
|
|
135
145
|
if (!isStateFlowTelegramScope(ctx.payload))
|
|
136
146
|
throw new Error("Unknown State Flow scope");
|
|
137
|
-
|
|
147
|
+
const state = port.state(ctx.payload);
|
|
148
|
+
const live = port.snapshot();
|
|
149
|
+
const revisions = port.revisions?.() ?? live.revisions ?? { global: live.step, cwd: live.step, session: live.step };
|
|
150
|
+
await ctx.openRich(renderStateFlowRichState(ctx.payload, revisions, state));
|
|
138
151
|
await ctx.answerCallback();
|
|
139
152
|
return "handled";
|
|
140
153
|
}
|
|
@@ -16,6 +16,8 @@ export interface TemporalPatch {
|
|
|
16
16
|
patch: RecentScopePatch["patch"];
|
|
17
17
|
}
|
|
18
18
|
export interface ScopeStream {
|
|
19
|
+
/** Monotonic semantic revision owned by this scope; independent of branch-local boundary positions. */
|
|
20
|
+
revision: number;
|
|
19
21
|
checkpoint: ScopeCheckpoint;
|
|
20
22
|
patches: TemporalPatch[];
|
|
21
23
|
}
|
|
@@ -24,6 +26,7 @@ export interface TemporalState {
|
|
|
24
26
|
lineage: TransitionBoundary[];
|
|
25
27
|
scopes: Record<StateScope, ScopeStream>;
|
|
26
28
|
}
|
|
29
|
+
export type ScopeRevisions = Record<StateScope, number>;
|
|
27
30
|
/** Replay validation is shared by disk codecs and active-lineage materialization. */
|
|
28
31
|
export declare function validateScopeStream(value: unknown, scope: StateScope, historyLimit?: number): asserts value is ScopeStream;
|
|
29
32
|
export declare function validateTemporalLineage(value: unknown, historyLimit?: number): asserts value is TransitionBoundary[];
|
|
@@ -41,6 +44,8 @@ export declare function constrainTemporalState(view: TemporalState, historyLimit
|
|
|
41
44
|
export declare function selectScopeStreamAtBoundary(stream: ScopeStream, scope: StateScope, boundary: TransitionBoundary, historyLimit?: number): ScopeStream;
|
|
42
45
|
/** Select one still-retained causal boundary without consulting an external history store. */
|
|
43
46
|
export declare function selectTemporalStateBoundary(view: TemporalState, boundaryId: string, historyLimit?: number): TemporalState;
|
|
47
|
+
/** Current independent scope revisions; Effective uses this vector rather than inventing a scalar owner. */
|
|
48
|
+
export declare function temporalScopeRevisions(view: TemporalState): ScopeRevisions;
|
|
44
49
|
/** Lazy scope/effective read at one shared transition boundary, never by local patch count. */
|
|
45
50
|
export declare function readTemporalState(view: TemporalState, offset?: number, scope?: StateScope, historyLimit?: number): MaterializedState;
|
|
46
51
|
/** Allocate the identity outside this algebra; only materially effective patches accept it. */
|
|
@@ -34,7 +34,8 @@ export function validateScopeStream(value, scope, historyLimit = DEFAULT_HISTORY
|
|
|
34
34
|
validateHistoryLimit(historyLimit);
|
|
35
35
|
if (!SCOPES.includes(scope))
|
|
36
36
|
throw new Error("Unknown temporal scope");
|
|
37
|
-
if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "checkpoint,patches"
|
|
37
|
+
if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "checkpoint,patches,revision"
|
|
38
|
+
|| !Number.isSafeInteger(value.revision) || value.revision < 0
|
|
38
39
|
|| !isObject(value.checkpoint) || Object.keys(value.checkpoint).sort().join(",") !== "state,through"
|
|
39
40
|
|| !Array.isArray(value.patches)) {
|
|
40
41
|
throw new Error("Invalid temporal checkpoint/tail envelope");
|
|
@@ -44,6 +45,8 @@ export function validateScopeStream(value, scope, historyLimit = DEFAULT_HISTORY
|
|
|
44
45
|
validateState(stream.checkpoint.state);
|
|
45
46
|
if (stream.patches.length > historyLimit)
|
|
46
47
|
throw new Error(`Temporal scope tail exceeds configured history limit ${historyLimit}`);
|
|
48
|
+
if (stream.revision < stream.patches.length)
|
|
49
|
+
throw new Error("Temporal scope revision predates its retained patch tail");
|
|
47
50
|
let previous = stream.checkpoint.through;
|
|
48
51
|
let state = stream.checkpoint.state;
|
|
49
52
|
const identities = new Set([previous.id]);
|
|
@@ -160,6 +163,7 @@ export function adoptTemporalStreams(scopes, id, historyLimit = DEFAULT_HISTORY_
|
|
|
160
163
|
export function createTemporalState(states, id, historyLimit = DEFAULT_HISTORY_LIMIT) {
|
|
161
164
|
const through = { id, position: 0, parent: null };
|
|
162
165
|
const stream = (scope) => ({
|
|
166
|
+
revision: 0,
|
|
163
167
|
checkpoint: { through: structuredClone(through), state: structuredClone(states[scope]) },
|
|
164
168
|
patches: [],
|
|
165
169
|
});
|
|
@@ -201,7 +205,9 @@ export function selectScopeStreamAtBoundary(stream, scope, boundary, historyLimi
|
|
|
201
205
|
throw new Error("Selected State Flow history boundary predates the retained scope checkpoint");
|
|
202
206
|
}
|
|
203
207
|
const selected = structuredClone(stream);
|
|
204
|
-
|
|
208
|
+
const retained = selected.patches.filter(({ transition }) => transition.position <= boundary.position);
|
|
209
|
+
selected.revision -= selected.patches.length - retained.length;
|
|
210
|
+
selected.patches = retained;
|
|
205
211
|
validateScopeStream(selected, scope, historyLimit);
|
|
206
212
|
return selected;
|
|
207
213
|
}
|
|
@@ -218,11 +224,26 @@ export function selectTemporalStateBoundary(view, boundaryId, historyLimit = DEF
|
|
|
218
224
|
const selected = structuredClone(view);
|
|
219
225
|
selected.lineage = selected.lineage.slice(0, index + 1);
|
|
220
226
|
for (const scope of SCOPES) {
|
|
221
|
-
|
|
227
|
+
const stream = selected.scopes[scope];
|
|
228
|
+
const retained = stream.patches.filter(({ transition }) => transition.position <= target.position);
|
|
229
|
+
stream.revision -= stream.patches.length - retained.length;
|
|
230
|
+
stream.patches = retained;
|
|
222
231
|
}
|
|
223
232
|
validateTemporalState(selected, historyLimit);
|
|
224
233
|
return selected;
|
|
225
234
|
}
|
|
235
|
+
/** Current independent scope revisions; Effective uses this vector rather than inventing a scalar owner. */
|
|
236
|
+
export function temporalScopeRevisions(view) {
|
|
237
|
+
const revisions = {
|
|
238
|
+
global: view.scopes.global.revision,
|
|
239
|
+
cwd: view.scopes.cwd.revision,
|
|
240
|
+
session: view.scopes.session.revision,
|
|
241
|
+
};
|
|
242
|
+
if (Object.values(revisions).some((revision) => !Number.isSafeInteger(revision) || revision < 0)) {
|
|
243
|
+
throw new Error("Invalid State Flow scope revision vector");
|
|
244
|
+
}
|
|
245
|
+
return revisions;
|
|
246
|
+
}
|
|
226
247
|
/** Lazy scope/effective read at one shared transition boundary, never by local patch count. */
|
|
227
248
|
export function readTemporalState(view, offset = 0, scope, historyLimit = DEFAULT_HISTORY_LIMIT) {
|
|
228
249
|
validateHistoryLimit(historyLimit);
|
|
@@ -266,6 +287,9 @@ export function advanceTemporalState(view, transitions, id, historyLimit = DEFAU
|
|
|
266
287
|
const next = structuredClone(view);
|
|
267
288
|
for (const { scope, patch } of changes) {
|
|
268
289
|
const stream = next.scopes[scope];
|
|
290
|
+
if (stream.revision >= Number.MAX_SAFE_INTEGER)
|
|
291
|
+
throw new Error(`State Flow ${scope} scope revision is exhausted`);
|
|
292
|
+
stream.revision += 1;
|
|
269
293
|
if (historyLimit === 0) {
|
|
270
294
|
stream.checkpoint = { through: structuredClone(boundary), state: apply(scopeAt(stream, head), patch) };
|
|
271
295
|
stream.patches = [];
|
|
@@ -184,8 +184,8 @@ export function stageAtomicScopePatches(currentStates, patches, successfulSkillR
|
|
|
184
184
|
return stageScopedSemanticTransition(currentStates, { transitions }, successfulSkillReads, causalBasis, successfulArtifactReads);
|
|
185
185
|
}
|
|
186
186
|
export function stageScopedTransition(currentStates, transition, successfulSkillReads, causalBasis, successfulArtifactReads = []) {
|
|
187
|
-
if (typeof transition.response !== "string"
|
|
188
|
-
throw new Error("Accepted State Flow response body must be
|
|
187
|
+
if (typeof transition.response !== "string") {
|
|
188
|
+
throw new Error("Accepted State Flow response body must be a string");
|
|
189
189
|
}
|
|
190
190
|
return stageScopedSemanticTransition(currentStates, transition, successfulSkillReads, causalBasis, successfulArtifactReads, transition.response);
|
|
191
191
|
}
|
|
@@ -40,7 +40,7 @@ Every materialized scope has exactly this shape:
|
|
|
40
40
|
- `contract` retains durable requirements, decisions, interfaces and rejected approaches.
|
|
41
41
|
- `working` retains verified current facts, unresolved work and exact continuation.
|
|
42
42
|
- `artifacts` maps exact source paths to compiled routing metadata.
|
|
43
|
-
- `response` is the latest
|
|
43
|
+
- `response` is owned only by the session scope and stores the exact latest accepted assistant answer, including the empty string. Global and CWD retain the required key as an empty structural placeholder so canonical scopes keep one shape; the effective overlay receives `response` only from Session.
|
|
44
44
|
- `lazy` is a required object root for ordinary JSON detail, omitted from baseline model state and read explicitly.
|
|
45
45
|
|
|
46
46
|
Effective state recursively overlays:
|
|
@@ -67,9 +67,9 @@ The checkpoint is an older anchored materialization. The tail contains at most t
|
|
|
67
67
|
|
|
68
68
|
Persisted streams and lineage are validated against the format maximum before applying a newly configured lower limit. Restore/reload/fork accepts only boundaries inside the configured window, then folds excess scope tails during canonical origin acceptance. That representation-only folding preserves selected private state and current shared values/provenance; a fork never rewrites parent-private files. Zero keeps only current checkpoints, and a later increase does not reconstruct discarded records or lineage.
|
|
69
69
|
|
|
70
|
-
`effective[n]`, `global[n]`, `cwd[n]`, and `session[n]` resolve the same nth previous causal boundary
|
|
70
|
+
`effective[n]`, `global[n]`, `cwd[n]`, and `session[n]` resolve the same nth previous causal boundary; the history index remains a composed-lineage offset, not a scope revision. Separately, each materially changed owner advances its persisted semantic revision once. Global and CWD counters remain shared across their canonical writers, Session remains private, and Effective is identified by the current `G#/C#/S#` revision vector. The retired top-level `state` segment is rejected; pre-origin history is unavailable rather than empty.
|
|
71
71
|
|
|
72
|
-
A changed accepted response is runtime-owned semantic state and advances history. Ordinary completion requires no `patch_state` call when durable semantic state is already correct.
|
|
72
|
+
A changed accepted response is runtime-owned, session-only semantic state and advances history. An accepted empty answer becomes `""` and finalizes normally rather than producing a recovery error. Ordinary completion requires no `patch_state` call when durable semantic state is already correct.
|
|
73
73
|
|
|
74
74
|
## Pi lifecycle
|
|
75
75
|
|
|
@@ -79,7 +79,7 @@ Tool preflight follows Pi's public `getLeafEntry()` / `getEntry(parentId)` links
|
|
|
79
79
|
|
|
80
80
|
`read_state` reads one cached effective or scoped projection at current index zero or a retained causal index through the configured `historyLimit`. It never publishes or advances history.
|
|
81
81
|
|
|
82
|
-
Before answering, the model uses `patch_state` only when future-relevant durable state must change.
|
|
82
|
+
Before answering, the model uses `patch_state` only when future-relevant durable state must change. The exact accepted ordinary answer is reconciled directly into runtime-owned `response` at `turn_end`, including `""` when the accepted answer is empty; no terminal eligibility latch, finalization patch, repair inference, or fallback budget exists. If required ordinary-artifact compilation prevents reconciliation, State Flow reports the failure without generating another inference. Optional Skill acquisition never blocks unrelated reconciliation. State Flow does not parse `state_flow` or generic HTML comments; historical comments are ordinary text and other extensions retain their own comment handling.
|
|
83
83
|
|
|
84
84
|
## Lifecycle planes
|
|
85
85
|
|
|
@@ -93,7 +93,7 @@ Stopping State Flow ends active episode semantics, restores the configured passi
|
|
|
93
93
|
|
|
94
94
|
A proven pre-runtime branch has no accepted runtime to persist: Stop appends State Flow's existing `{disabled:true}` checkpoint in Pi without creating canonical files. Its cached passive view remains readable under the configured policy, but is not runtime authority. Accepted canonical publication, including a later Start or passive patch, ends this pre-runtime condition; failed selected-boundary recovery never qualifies for it.
|
|
95
95
|
|
|
96
|
-
Lifecycle-only persistence for accepted runtimes reconciles valid live global/CWD drift before publishing the current session's config/runtime pair. Changed shared streams establish a fresh proven origin, not a semantic transition;
|
|
96
|
+
Lifecycle-only persistence for accepted runtimes reconciles valid live global/CWD drift before publishing the current session's config/runtime pair. Changed shared streams establish a fresh proven origin, not a semantic transition; the runtime adopts their already-advanced owner revisions without incrementing them, while semantic files and all provenance files remain unchanged. Cached reads may constrain wider tails left by another writer without rewriting them. Same-session file races and explicitly requested stale provenance writes still fail closed under CAS. The host refreshes its scope cache after adoption: Stop freezes the accepted view, and new-run registered-artifact maintenance runs against that view before inference.
|
|
97
97
|
|
|
98
98
|
The existing native passive-stop marker stores the stop timestamp and an optional `from` timestamp identifying the active run's first user message. Native user events are observed independently of State Flow enablement, so starting mid-tool and repeated Start/Stop retain the actual first-user timestamp. Native user-run preparation and session-start/tree events reset capture; semantic mode changes do not. No new marker field, stored format or projection-derived lifecycle authority is introduced. Transcript bodies remain in Pi's trace rather than being copied into another state store. A recorded active anchor uses the same conservative selector as active inference: if native compaction removed it, or matching is ambiguous/nonfinite, retain the available native summary and tool trajectory without guessing a post-stop boundary or rereading discarded raw entries. Idle and legacy markers without an active anchor still retain only post-stop conversation plus foreign custom context. The initial system prompt is composed at `before_agent_start`; Stop does not rewrite an already-issued request, while the next provider request receives the current owned protocol section as described below.
|
|
99
99
|
|
|
@@ -128,7 +128,7 @@ meta.json
|
|
|
128
128
|
|
|
129
129
|
CWD and session keys mirror Pi's native encoding. The Pi UUID remains authoritative; readable directory keys never replace identity validation.
|
|
130
130
|
|
|
131
|
-
Root `config.json` is the read-only operator configuration shared by every session in the repository; it never participates in semantic overlay or State Flow-owned staging. Include operator configuration in operator-managed copies/versioning. `checkpoint.json` is only the canonical materialized semantic state, and each nonblank `patches.jsonl` line is only one semantic patch. Every scope's `meta.json` symmetrically owns checkpoint/tail boundaries and artifact provenance, with CWD ownership added where applicable. Session `config.json` owns behavior; session `runtime.json` asymmetrically owns lineage,
|
|
131
|
+
Root `config.json` is the read-only operator configuration shared by every session in the repository; it never participates in semantic overlay or State Flow-owned staging. Include operator configuration in operator-managed copies/versioning. `checkpoint.json` is only the canonical materialized semantic state, and each nonblank `patches.jsonl` line is only one semantic patch. Every scope's `meta.json` symmetrically owns its independent semantic revision, checkpoint/tail boundaries and artifact provenance, with CWD ownership added where applicable. Session `config.json` owns behavior; session `runtime.json` asymmetrically owns lineage, the internal branch step, session identity, and the full specification only while a run is unfinished. Predecessor combined session metadata is unsupported; session `meta.json`, `config.json`, and `runtime.json` must already satisfy their canonical ownership contracts. Metadata writers replace only their owned leaves and preserve JSON-safe unknown siblings. A pre-revision 0.17 scope initializes its counter from the still-retained semantic tail and persists that baseline on its next owned write; folded ancestry is not guessed. Revision-aware writes emit scope metadata version 2 while continuing to read version 1. The version fence makes an older writer refuse a scope after its first revision-aware write instead of silently dropping the counter; all cooperating instances should still upgrade together. Pi checkpoints retain only a semantic boundary plus lifecycle fields, or a proven ordinary-disabled marker. Revision-pointer checkpoints are unsupported and fail closed without Git restoration.
|
|
132
132
|
|
|
133
133
|
In-memory patching detaches one basis at its public boundary, then privately path-copies changed object/array containers while sharing untouched nodes only inside that owned draft. Incoming replacement values remain detached; staging no longer makes redundant cohort/per-scope pre-clones. Mutable staged responses and artifact registries stay isolated from accepted scopes, and commit/public temporal reads retain their detachment boundaries. This is not cross-version mutable sharing or a new disk generation format; see [copy-work evidence](performance.md#memory-only-owned-draft-cow).
|
|
134
134
|
|
|
@@ -144,7 +144,9 @@ After response reconciliation and Pi's retry/queue processing, `agent_before_set
|
|
|
144
144
|
|
|
145
145
|
Only exact backed-up owned paths are synchronized in the caller's index, preserving unrelated staged additions, modifications, deletions, index-only content, and worktree edits. HEAD-owned paths remain candidates when their deletion is already staged. Unchanged trees and unowned-only initial backups are skipped; failed index synchronization rolls back only the backup ref, never canonical files. Failure cannot suppress the answer or trigger another inference. Notification-only `agent_settled` does not perform backup writes.
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
After a successful backup attempt, State Flow resolves only the attached branch's explicitly configured remote and destination ref, snapshots the exact current commit, and starts one non-interactive, non-force push outside all backup and canonical locks. Settlement does not await network completion. Failure is diagnostic-only; no queue is persisted, and the next accepted settled turn attempts the latest current backup again. A repository without an explicitly configured branch remote remains local-only.
|
|
148
|
+
|
|
149
|
+
Durable push queues, publication workers, leases, retry generations, queue filesystem state, and publication-policy metadata remain absent. Git revision restore, immutable-revision fork APIs, and the legacy semantic Git backend have been removed from `TemporalRuntime`; all initialization, passive loading, model patches, runtime-only persistence, retained-boundary restoration, and retained-boundary forks use canonical files only.
|
|
148
150
|
|
|
149
151
|
Predecessor checkpoint envelopes, combined session metadata, pre-intents checkpoints, `state.json`, hashed layouts, and semantic Pi checkpoints are unsupported and remain untouched.
|
|
150
152
|
|
|
@@ -232,7 +234,7 @@ Agent configuration is read once per extension load; session runtime configurati
|
|
|
232
234
|
|
|
233
235
|
## Observability
|
|
234
236
|
|
|
235
|
-
Status is a projection of the selected runtime and semantic view, not a second store.
|
|
237
|
+
Status is a projection of the selected runtime and semantic view, not a second store. Compact terminal and Telegram main-menu status render `G#/C#/S#` only while active; passive Telegram renders `State Flow: off`. Requested Global, CWD and Session Rich snapshots show their independent `#revision`, while Effective shows the vector. Global/CWD Rich views omit the empty structural response placeholder; Session and Effective expose the Session-owned response. Missing evidence stays unavailable instead of appearing empty. The optional Telegram leaf adapter calls the same Start/Stop owners, and its inspectors remain available in either mode. Inspection may refresh live Global/CWD streams in memory so foreign accepted revisions become visible, but never publishes or increments a revision. If model-facing passive access is disabled and no runtime is selected, inspection may lazily load existing canonical shared state under the same read-only rule. Registration is fail-open and disposal belongs to session shutdown. Local diagnostics stay outside semantic state and cannot change accepted state. Operator-facing fields and privacy boundaries are in [usage](usage.md#status-and-controls).
|
|
236
238
|
|
|
237
239
|
## Validation boundaries
|
|
238
240
|
|
|
@@ -42,11 +42,11 @@ global | CWD | session
|
|
|
42
42
|
├── contract
|
|
43
43
|
├── working
|
|
44
44
|
├── artifacts
|
|
45
|
-
├── response (session-owned
|
|
45
|
+
├── response (session-owned; empty structural slot in Global/CWD)
|
|
46
46
|
└── lazy
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
`intents`, `contract`, `working`, `artifacts
|
|
49
|
+
`intents`, `contract`, `working`, and `artifacts` remain hot in every scope. Session-owned `response` is also hot; Global and CWD keep only its required empty structural slot, and Effective inherits the Session value. `intents` may keep compact active direction while referring to large supporting detail in `lazy`. `lazy` differs only in projection policy:
|
|
50
50
|
|
|
51
51
|
- It is canonical semantic JSON, validated and versioned with its owning scope.
|
|
52
52
|
- It is excluded from the ordinary baseline effective-state body.
|
|
@@ -17,13 +17,14 @@ This is a maintained property-to-test map for the current canonical-file contrac
|
|
|
17
17
|
11. **Barrier shifts current to offset 1:** `tests/integration.test.ts` — “real Pi patch_state barriers rematerialize every scope before the next inference” observes the predecessor immediately after a barrier.
|
|
18
18
|
12. **Next inference sees new current state:** The same real-Pi test inspects actual model-input projections after session, CWD, and global barriers. “real Pi reads prior scoped state lazily after a barrier and rejects path offset eight without a transition” adds model-tool access to the predecessor.
|
|
19
19
|
13. **No automatic old full-state duplication:** `tests/context.test.ts` — “projects only the latest seven compact accepted transitions” rejects full-state records in transition context. The real-Pi barrier test requires exactly one current runtime projection per inference. Explicitly requested history remains ordinary tool-result trajectory, not eager snapshot injection.
|
|
20
|
-
14. **Accepted response changes are transitions:** `tests/extension.test.ts`
|
|
21
|
-
15. **No-op mutation signals are rejected:**
|
|
20
|
+
14. **Accepted response changes are transitions:** `tests/extension.test.ts` verifies that an ordinary accepted answer becomes runtime-owned `response` without a finalization patch or fallback inference, and “an empty accepted answer finalizes the run and stores an empty response” proves `""` is accepted after an earlier barrier. `tests/transition.test.ts` proves the empty value is an ordinary Session-owned semantic change when it replaces prior text.
|
|
21
|
+
15. **No-op mutation signals are rejected:** “accepts only canonical materially changing atomic scope patches” rejects empty scope patches and obsolete finalization-shaped calls, while a changed accepted response remains a runtime-owned transition.
|
|
22
22
|
16. **Configured hot-history bounds:** `tests/temporal.test.ts` verifies hot-range and unavailable pre-origin boundaries; the real-Pi history-reader test rejects offset eight at the default limit seven without a transition. `tests/config.test.ts` exercises materialized and scope patch-history paths at limits 0, 1, 7, and 12, including single-path/one-item batch reads above seven and distinct configured versus actually retained boundaries.
|
|
23
23
|
17. **Selected history fails closed:** `tests/recovery.test.ts` proves every failure resolving a selected retained boundary refuses without falling through to older boundaries or disabled markers. `tests/extension.test.ts` covers all passive bootstrap/tool combinations; the native “real Pi expired selection cannot reset private state through passive Start, Stop, patch, or reload” witness preserves exact canonical bytes and Pi checkpoints while allowing shared reads. Fork identity/CWD repair witnesses retry the original source with passive access both enabled and disabled.
|
|
24
24
|
18. **Tree/resume select the correct lineage:** `tests/integration.test.ts` — “real Pi preserves branch-local state through compaction and rejects an expired sibling after fresh-origin navigation” and “real Pi old tree branch stop and resume preserve selected semantics without rewinding shared files”.
|
|
25
25
|
19. **Stop changes config, not semantic history:** `tests/runtime.test.ts` lifecycle-only witnesses use a separate process to advance global/CWD semantics or provenance, including wider foreign retention, then prove exact semantic/sidecar preservation, unchanged steps, idempotent Stop, and same-session/stale-evidence refusal. Native Stop/new-request witnesses verify the accepted shared view reaches handoff/inference without a lifecycle semantic write, while a shared write racing after inference still fails closed. Mid-tool Stop tests preserve ordinary/bootstrap trajectories through tree, reload, resume, and restart. The native “real Pi retains a native split-turn continuation through late tools” Stop/no-Stop controls actually remove the original user with native threshold compaction, then require summary, paired reads and foreign context in model input. The Stop case also checks frozen semantics/step, unchanged trace prefix, tree/reload/cold resume/bootstrap restart, no resurrection of discarded input, and persistent foreign context after the next active run. Pure passive-selector tests cover missing, colliding and nonfinite recorded active anchors without changing idle/legacy cutoffs. `tests/storage.test.ts` fences lifecycle-only writes to config/runtime files; `tests/extension.test.ts` covers idle/legacy cutoffs and new/fork boundaries.
|
|
26
|
-
20. **Passive observability
|
|
26
|
+
20. **Passive observability omits active status without hiding owner revisions:** `tests/status.test.ts` proves an accepted passive patch advances semantic history while compact terminal status stays absent. `tests/telegram.test.ts` drives the real extension port after Stop, requires the passive main-menu identity to render `State Flow: off`, verifies an owner Rich-state heading uses its independent `#revision` and Effective uses `G#/C#/S#`, and separately proves Telegram can lazily observe existing shared canonical state when passive model tools are disabled without initializing or mutating storage.
|
|
27
|
+
21. **Independent revisions survive folding and foreign writers:** `tests/temporal.test.ts` proves each materially changed scope advances once for sparse and multi-scope cohorts, response-only transitions advance only Session, no-ops advance none, selected retained history restores the matching revision, and folding never resets it. `tests/durable.test.ts` covers revision serialization plus the pre-revision 0.17 retained-tail baseline. `tests/runtime.test.ts` uses an independent file-backed writer to advance Global, then refreshes the first runtime without file mutation and proves `G1/C0/S1` becomes `G1/C0/S2` after one private Session patch.
|
|
27
28
|
|
|
28
29
|
## Additional preservation boundaries
|
|
29
30
|
|
|
@@ -64,14 +64,14 @@ Logs remain local unless you move them; rotation/deletion is operator-owned. Tre
|
|
|
64
64
|
|
|
65
65
|
`/state-flow-status` separates runtime configuration/metadata from semantic state. It reports:
|
|
66
66
|
|
|
67
|
-
- Selected CWD/session keys, step, temporal head, recovery failures, and available hot history.
|
|
67
|
+
- Selected CWD/session keys, internal step, independent scope revisions, temporal head, recovery failures, and available hot history.
|
|
68
68
|
- Per-scope retained patch tails and artifact counts, plus one JSON representation of effective global → CWD → session memory. Individual scope JSON is available through `read_state`, not duplicated in status.
|
|
69
69
|
- Already-known runtime hints or pending artifact invalidations; status does not discover or validate sources.
|
|
70
70
|
- Memory-bearing scopes. Promotion-shaped values receive no special interpretation.
|
|
71
71
|
|
|
72
72
|
Tail counts are not history depth: inherited records may predate the active origin. Failed inspection reports unavailable evidence, not invented empty state. Status is observational: it does not read source files, calculate fingerprints, create invalidations, or mutate semantic state.
|
|
73
73
|
|
|
74
|
-
The terminal indicator is `state-flow
|
|
74
|
+
The terminal indicator is `state-flow G15/C8/S31` only in active mode. Global, CWD and Session own independent semantic revisions; one atomic transition advances each materially changed scope once, including session-only response reconciliation. Effective has no scalar counter and uses the `G#/C#/S#` revision vector. When `pi-telegram` is available, its main-menu section shows that vector only while active and `State Flow: off` while passive. Requested owner-scope Rich snapshots show `#revision`; Effective shows the vector. Telegram inspection may lazily load or refresh live shared state from other instances even when passive model tools are disabled, but it does not initialize, publish, or advance storage. Start requested during a run waits for settlement; Stop currently applies immediately. The adapter is optional and the Pi commands remain available without it. Open implementation work is tracked in [BACKLOG.md](../BACKLOG.md).
|
|
75
75
|
|
|
76
76
|
## Storage and recovery
|
|
77
77
|
|
|
@@ -92,19 +92,19 @@ Exactly one surviving pair member is corruption and fails closed. Present malfor
|
|
|
92
92
|
|
|
93
93
|
After a selected-boundary failure, configured passive access may still expose current global/CWD memory, but it never grants access to the unavailable session layer or permission to publish an empty replacement. Session reads, every `patch_state`, and Stop refuse without changing canonical files or appending substitute checkpoints. Status retains the restoration error even when shared reads work. Start retries the original selection; repair its missing or invalid evidence, select a still-retained boundary, or use a genuinely new Pi session instead of forcing a reset.
|
|
94
94
|
|
|
95
|
-
Missing artifact provenance inside an otherwise complete scope `meta.json` means compilation evidence is unavailable while semantic state remains usable; removing the whole metadata file also removes temporal authority and fails closed. An unavailable registered source path does not prove that durable artifact routing was deleted, and external files are never created. State Flow has no durable push queue or publication-worker lease. See the complete [filesystem recovery contract](filesystem-recovery.md).
|
|
95
|
+
Missing artifact provenance inside an otherwise complete scope `meta.json` means compilation evidence is unavailable while semantic state remains usable; removing the whole metadata file also removes temporal authority and fails closed. An unavailable registered source path does not prove that durable artifact routing was deleted, and external files are never created. State Flow has no durable push queue or publication-worker lease; failed replication is attempted again only after a later accepted turn. See the complete [filesystem recovery contract](filesystem-recovery.md).
|
|
96
96
|
|
|
97
97
|
### Canonical files and optional Git backup
|
|
98
98
|
|
|
99
99
|
Canonical scope/runtime files own current materialization and retained hot history regardless of Git availability. Pi checkpoints identify a retained semantic boundary, not a Git commit or arbitrary historical snapshot. Restart and branch restoration fail closed when the selected boundary has expired rather than substituting newer files as the selected past.
|
|
100
100
|
|
|
101
|
-
After an accepted turn has reconciled its response, Pi 0.87's final actionable `agent_before_settle` boundary may create one best-effort backup commit when Git is available.
|
|
101
|
+
After an accepted turn has reconciled its response, Pi 0.87's final actionable `agent_before_settle` boundary may create one best-effort backup commit when Git is available. If the attached branch has an explicitly configured remote/ref, State Flow starts a non-interactive asynchronous push of the exact current commit without force. Settlement does not wait for the network. Commit or push failure is diagnostic-only, and the next accepted turn retries the latest backup without a durable queue. Git availability never changes semantic authority, step, or retained lineage.
|
|
102
102
|
|
|
103
103
|
### Moving a store and the 0.17 format boundary
|
|
104
104
|
|
|
105
105
|
An SDK `repositoryRoot` override or a different `PI_CODING_AGENT_DIR` selects a location; it does not relocate existing state or retained history. Copy the complete canonical store while all writers are quiescent, or use a genuinely new Pi session for an independent store. Copying only current checkpoints without their tails and metadata cannot preserve retained boundaries.
|
|
106
106
|
|
|
107
|
-
State Flow 0.17 accepts only its canonical checkpoint/tail, temporal metadata, and separate session config/runtime contract. Predecessor checkpoint envelopes, combined session metadata, pre-intents checkpoints, `state.json`, hashed layouts, and semantic Pi checkpoint envelopes fail as unsupported without rewriting existing bytes. State Flow does not provide an in-place converter; external conversion or a fresh store is operator-owned.
|
|
107
|
+
State Flow 0.17 accepts only its canonical checkpoint/tail, temporal metadata, and separate session config/runtime contract. A canonical 0.17 scope written before independent revisions remains readable: its initial counter uses only the retained semantic tail and is persisted in metadata version 2 on the next owned write, without inventing folded ancestry. Version 1 remains readable; older writers refuse version 2 through the existing provenance-version fence, so cooperating instances should upgrade together. Predecessor checkpoint envelopes, combined session metadata, pre-intents checkpoints, `state.json`, hashed layouts, and semantic Pi checkpoint envelopes fail as unsupported without rewriting existing bytes. State Flow does not provide an in-place converter; external conversion or a fresh store is operator-owned.
|
|
108
108
|
|
|
109
109
|
### Conflicts and interrupted publication
|
|
110
110
|
|
|
@@ -71,7 +71,7 @@ export {
|
|
|
71
71
|
temporalStateFileUpdates, type ScopeStreamSources, type TemporalScopePaths
|
|
72
72
|
} from "./lib/durable.ts";
|
|
73
73
|
export { default, PATCH_STATE_TOOL_NAME, READ_STATE_TOOL_NAME, type StateFlowExtensionOptions } from "./lib/extension.ts";
|
|
74
|
-
export { backupCurrentStateFlowFiles } from "./lib/git.ts";
|
|
74
|
+
export { backupCurrentStateFlowFiles, pushCurrentStateFlowBackup } from "./lib/git.ts";
|
|
75
75
|
export {
|
|
76
76
|
createAcceptedTransition,
|
|
77
77
|
DEFAULT_HISTORY_LIMIT,
|
|
@@ -139,5 +139,5 @@ export {
|
|
|
139
139
|
type StateFlowTelegramSnapshot,
|
|
140
140
|
type StateFlowTelegramView
|
|
141
141
|
} from "./lib/telegram.ts";
|
|
142
|
-
export { advanceTemporalState, readTemporalState, type TemporalState } from "./lib/temporal.ts";
|
|
142
|
+
export { advanceTemporalState, readTemporalState, temporalScopeRevisions, type ScopeRevisions, type TemporalState } from "./lib/temporal.ts";
|
|
143
143
|
export { parseStateReadPath, readStatePath, type StateReadQuery, type StateReadResult } from "./lib/query.ts";
|
|
@@ -50,6 +50,7 @@ export function hasCwdMaterialization(cwd: string, repositoryRoot: string): bool
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export interface ScopeTemporalMetadata {
|
|
53
|
+
revision: number;
|
|
53
54
|
checkpoint: ScopeStream["checkpoint"]["through"];
|
|
54
55
|
patches: ScopeStream["patches"][number]["transition"][];
|
|
55
56
|
}
|
|
@@ -68,6 +69,7 @@ export function serializeScopeStream(stream: ScopeStream, scope: StateScope, cwd
|
|
|
68
69
|
checkpoint: `${canonicalJson(stream.checkpoint.state)}\n`,
|
|
69
70
|
patches: stream.patches.map((record) => `${canonicalJson(record.patch)}\n`).join(""),
|
|
70
71
|
temporal: {
|
|
72
|
+
revision: stream.revision,
|
|
71
73
|
checkpoint: structuredClone(stream.checkpoint.through),
|
|
72
74
|
patches: stream.patches.map((record) => structuredClone(record.transition)),
|
|
73
75
|
},
|
|
@@ -124,6 +126,9 @@ export function classifyScopeStream(
|
|
|
124
126
|
const boundaries = temporal.patches;
|
|
125
127
|
if (boundaries.length !== patches.length) throw new Error(`State Flow ${scope} temporal metadata does not match its semantic tail`);
|
|
126
128
|
const stream = {
|
|
129
|
+
// 0.17.4 and earlier did not persist scope revisions. Credit only their still-retained
|
|
130
|
+
// semantic tail; discarded ancestry cannot be reconstructed without inventing history.
|
|
131
|
+
revision: temporal.revision === undefined ? patches.length : temporal.revision,
|
|
127
132
|
checkpoint: { through: temporal.checkpoint, state: checkpoint },
|
|
128
133
|
patches: patches.map((patch, index) => ({ transition: boundaries[index], patch })),
|
|
129
134
|
};
|
|
@@ -219,7 +224,7 @@ export function serializeScopeMetadata(
|
|
|
219
224
|
const sources = serializeScopeStream(stream, scope, cwdIdentity);
|
|
220
225
|
const value = {
|
|
221
226
|
...existing,
|
|
222
|
-
version:
|
|
227
|
+
version: 2,
|
|
223
228
|
...(registry === undefined ? {} : { artifacts: serializeArtifactProvenanceRegistry(registry) }),
|
|
224
229
|
temporal: sources.temporal,
|
|
225
230
|
...(scope === "cwd" ? { owner: { cwd: resolve(cwdIdentity!) } } : {}),
|
|
@@ -229,7 +234,7 @@ export function serializeScopeMetadata(
|
|
|
229
234
|
|
|
230
235
|
/** Compatibility serializer retained for metadata-only callers. */
|
|
231
236
|
export function serializeScopeProvenance(registry: Readonly<ArtifactProvenanceRegistry>): string {
|
|
232
|
-
return `${canonicalJson({ version:
|
|
237
|
+
return `${canonicalJson({ version: 2, artifacts: serializeArtifactProvenanceRegistry(registry) })}\n`;
|
|
233
238
|
}
|
|
234
239
|
|
|
235
240
|
function parseMetadataDocument(source: string | undefined, label: string): Record<string, unknown> {
|
|
@@ -245,7 +250,7 @@ function parseMetadataDocument(source: string | undefined, label: string): Recor
|
|
|
245
250
|
export function parseScopeProvenance(source: string | undefined, path: string): ArtifactProvenanceRegistry {
|
|
246
251
|
const value = parseMetadataDocument(source, `State Flow provenance file: ${path}`);
|
|
247
252
|
if (Object.keys(value).length === 0) return {};
|
|
248
|
-
if (value.version !== 1) throw new Error(`Invalid State Flow provenance document: ${path}`);
|
|
253
|
+
if (value.version !== 1 && value.version !== 2) throw new Error(`Invalid State Flow provenance document: ${path}`);
|
|
249
254
|
if (!Object.hasOwn(value, "artifacts")) return {};
|
|
250
255
|
return parseArtifactProvenanceRegistry(value.artifacts, `State Flow provenance at ${path}`);
|
|
251
256
|
}
|