@llblab/pi-kit 0.18.2 → 0.19.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 +11 -0
- package/README.md +3 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +35 -37
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +5 -3
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +23 -1
- package/node_modules/@llblab/pi-state-flow/README.md +102 -48
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +6 -9
- package/node_modules/@llblab/pi-state-flow/dist/index.js +6 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +21 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +40 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +93 -63
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +9 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +6 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +56 -58
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +12 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +7 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +47 -78
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +4 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +241 -437
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -72
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +120 -499
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +43 -22
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +1 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +0 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +1 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +5 -37
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +1 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +13 -32
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +25 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +3 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +15 -12
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +2 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +5 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +26 -88
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +160 -255
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +0 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +1 -47
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +16 -33
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +48 -137
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +16 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +13 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +15 -43
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +5 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +13 -45
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +15 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +104 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +0 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +11 -14
- package/node_modules/@llblab/pi-state-flow/dist/package.json +9 -6
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +11 -17
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +8 -8
- package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +64 -67
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +24 -6
- package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +12 -15
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +22 -27
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +25 -41
- package/node_modules/@llblab/pi-state-flow/docs/performance.md +38 -421
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +33 -46
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +37 -61
- package/node_modules/@llblab/pi-state-flow/index.ts +7 -71
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +26 -11
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +116 -88
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +12 -10
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +52 -59
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +11 -20
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +44 -86
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +237 -460
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +110 -552
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +39 -23
- package/node_modules/@llblab/pi-state-flow/lib/logging.ts +1 -7
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +5 -44
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +14 -25
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +26 -22
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +17 -11
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +7 -5
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +155 -254
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +1 -49
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +58 -142
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +27 -19
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +16 -54
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +12 -40
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +104 -22
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +16 -26
- package/node_modules/@llblab/pi-state-flow/package.json +9 -6
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +11 -17
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +8 -8
- package/package.json +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.d.ts +0 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.js +0 -125
- package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.d.ts +0 -36
- package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.js +0 -98
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +0 -13
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +0 -167
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +0 -86
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +0 -437
- package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +0 -133
- package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +0 -147
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +0 -171
- package/node_modules/@llblab/pi-state-flow/lib/publication.ts +0 -458
|
@@ -3,6 +3,7 @@ import { lstatSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { getDurableRepositoryRoot } from "./durable.js";
|
|
6
|
+
import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT } from "./history.js";
|
|
6
7
|
import { isObject } from "./json.js";
|
|
7
8
|
/** Read the repository-global config once at extension load/reload. Missing config uses defaults; invalid config never falls back. */
|
|
8
9
|
export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = getDurableRepositoryRoot(agentDir)) {
|
|
@@ -15,6 +16,7 @@ export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = g
|
|
|
15
16
|
passiveTools: true,
|
|
16
17
|
logging: false,
|
|
17
18
|
showSuccessfulPatches: true,
|
|
19
|
+
historyLimit: DEFAULT_HISTORY_LIMIT,
|
|
18
20
|
};
|
|
19
21
|
let value;
|
|
20
22
|
try {
|
|
@@ -25,7 +27,7 @@ export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = g
|
|
|
25
27
|
catch (error) {
|
|
26
28
|
throw new Error(`Cannot read State Flow configuration: ${path}`, { cause: error });
|
|
27
29
|
}
|
|
28
|
-
const allowed = new Set(["autoStart", "passiveBootstrap", "passiveTools", "logging", "showSuccessfulPatches", "
|
|
30
|
+
const allowed = new Set(["autoStart", "passiveBootstrap", "passiveTools", "logging", "showSuccessfulPatches", "historyLimit"]);
|
|
29
31
|
if (!isObject(value) || Object.keys(value).some((key) => !allowed.has(key))) {
|
|
30
32
|
throw new Error(`State Flow configuration contains unknown settings: ${path}`);
|
|
31
33
|
}
|
|
@@ -39,9 +41,8 @@ export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = g
|
|
|
39
41
|
throw new Error(`State Flow logging must be a boolean: ${path}`);
|
|
40
42
|
if (Object.hasOwn(value, "showSuccessfulPatches") && typeof value.showSuccessfulPatches !== "boolean")
|
|
41
43
|
throw new Error(`State Flow showSuccessfulPatches must be a boolean: ${path}`);
|
|
42
|
-
if (Object.hasOwn(value, "
|
|
43
|
-
throw new Error(`State Flow
|
|
44
|
-
}
|
|
44
|
+
if (Object.hasOwn(value, "historyLimit") && (!Number.isSafeInteger(value.historyLimit) || value.historyLimit < 0 || value.historyLimit > MAX_HISTORY_LIMIT))
|
|
45
|
+
throw new Error(`State Flow historyLimit must be an integer from 0 to ${MAX_HISTORY_LIMIT}: ${path}`);
|
|
45
46
|
return {
|
|
46
47
|
directory,
|
|
47
48
|
autoStart: value.autoStart === true,
|
|
@@ -49,6 +50,6 @@ export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = g
|
|
|
49
50
|
passiveTools: value.passiveTools !== false,
|
|
50
51
|
logging: value.logging === true,
|
|
51
52
|
showSuccessfulPatches: value.showSuccessfulPatches !== false,
|
|
52
|
-
|
|
53
|
+
historyLimit: typeof value.historyLimit === "number" ? value.historyLimit : DEFAULT_HISTORY_LIMIT,
|
|
53
54
|
};
|
|
54
55
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import { type ArtifactInvalidationNotice } from "./artifact.ts";
|
|
2
|
+
import { type ArtifactInvalidationNotice, type ArtifactModelHints } from "./artifact.ts";
|
|
3
3
|
import type { RecentTransitionWindow } from "./history.ts";
|
|
4
4
|
import type { Snapshot } from "./snapshot.ts";
|
|
5
5
|
import type { RehydrationPhase } from "./rehydration.ts";
|
|
6
|
-
import { type MaterializedState } from "./state.ts";
|
|
7
|
-
|
|
6
|
+
import { type MaterializedState, type ModelState } from "./state.ts";
|
|
7
|
+
/** Refresh only our section; Pi owns system frames, tools and forced-prompt precedence. */
|
|
8
|
+
export declare function projectSystemProtocol(messages: AgentMessage[], protocol: string | undefined): AgentMessage[];
|
|
8
9
|
type LazyValueKind = "array" | "boolean" | "null" | "number" | "object" | "string";
|
|
9
10
|
/** Fixed-budget navigation only: never place lazy bodies or partial key catalogs in baseline context. */
|
|
10
11
|
export declare function lazyNavigationHint(state: MaterializedState): {
|
|
@@ -19,12 +20,12 @@ export interface PassiveContinuation {
|
|
|
19
20
|
handoff: AgentMessage;
|
|
20
21
|
}
|
|
21
22
|
export declare function syntheticUser(text: string): AgentMessage;
|
|
22
|
-
export declare function
|
|
23
|
-
export declare function createPassiveContinuation(state: MaterializedState, startedAt?: number, activeRunStartedAt?: number): PassiveContinuation;
|
|
23
|
+
export declare function createPassiveContinuation(state: ModelState, startedAt?: number, activeRunStartedAt?: number): PassiveContinuation;
|
|
24
24
|
/** Keep the interrupted run through later results; an idle stop retains only later conversation. */
|
|
25
25
|
export declare function passiveContinuationMessages(messages: AgentMessage[], continuation: PassiveContinuation): AgentMessage[];
|
|
26
|
-
export declare function runtimeContextMessage(snapshot: Snapshot, state: MaterializedState, recentTransitions?: RecentTransitionWindow, artifactInvalidations?: readonly ArtifactInvalidationNotice[], rehydrationPhase?: RehydrationPhase,
|
|
27
|
-
|
|
26
|
+
export declare function runtimeContextMessage(snapshot: Snapshot, state: MaterializedState, recentTransitions?: RecentTransitionWindow, artifactInvalidations?: readonly ArtifactInvalidationNotice[], rehydrationPhase?: RehydrationPhase, artifactHints?: ArtifactModelHints): AgentMessage;
|
|
27
|
+
/** Captured identity survives text decoration; an uncertain boundary retains available context. */
|
|
28
|
+
export declare function currentRunTrajectory(messages: AgentMessage[], specification: string | undefined, anchorTimestamp: number | undefined): {
|
|
28
29
|
messages: AgentMessage[];
|
|
29
30
|
anchorTimestamp?: number;
|
|
30
31
|
};
|
|
@@ -1,7 +1,34 @@
|
|
|
1
1
|
import { projectArtifactForModel } from "./artifact.js";
|
|
2
|
-
import {
|
|
2
|
+
import { isObject, presentationJson } from "./json.js";
|
|
3
3
|
import { projectStateForModel } from "./state.js";
|
|
4
|
-
|
|
4
|
+
/** Refresh only our section; Pi owns system frames, tools and forced-prompt precedence. */
|
|
5
|
+
export function projectSystemProtocol(messages, protocol) {
|
|
6
|
+
let lastSystem = -1;
|
|
7
|
+
let current;
|
|
8
|
+
for (let index = 0; index < messages.length; index++) {
|
|
9
|
+
const message = messages[index];
|
|
10
|
+
if (message.role !== "system")
|
|
11
|
+
continue;
|
|
12
|
+
lastSystem = index;
|
|
13
|
+
if (message.sections && Object.hasOwn(message.sections, "state_flow"))
|
|
14
|
+
current = message.sections.state_flow ?? undefined;
|
|
15
|
+
}
|
|
16
|
+
const section = protocol === undefined ? undefined : `<state_flow>\n${protocol}\n</state_flow>`;
|
|
17
|
+
if (lastSystem < 0 || current === section)
|
|
18
|
+
return messages;
|
|
19
|
+
return messages.map((message, index) => {
|
|
20
|
+
if (message.role !== "system")
|
|
21
|
+
return message;
|
|
22
|
+
const ownsSection = message.sections !== undefined && Object.hasOwn(message.sections, "state_flow");
|
|
23
|
+
if (!ownsSection && (index !== lastSystem || section === undefined))
|
|
24
|
+
return message;
|
|
25
|
+
const sections = { ...message.sections };
|
|
26
|
+
delete sections.state_flow;
|
|
27
|
+
if (index === lastSystem && section !== undefined)
|
|
28
|
+
sections.state_flow = section;
|
|
29
|
+
return { ...message, sections };
|
|
30
|
+
});
|
|
31
|
+
}
|
|
5
32
|
const LAZY_HINT_PATH = "effective.lazy";
|
|
6
33
|
const LAZY_HINT_MAX_KEYS = 32;
|
|
7
34
|
const LAZY_HINT_MAX_JSON_CHARS = 1024;
|
|
@@ -16,10 +43,10 @@ function lazyValueKind(value) {
|
|
|
16
43
|
}
|
|
17
44
|
/** Fixed-budget navigation only: never place lazy bodies or partial key catalogs in baseline context. */
|
|
18
45
|
export function lazyNavigationHint(state) {
|
|
19
|
-
const
|
|
20
|
-
|
|
46
|
+
const entries = isObject(state.lazy) ? Object.entries(state.lazy) : [];
|
|
47
|
+
const base = { available: entries.length > 0, path: LAZY_HINT_PATH };
|
|
48
|
+
if (!base.available)
|
|
21
49
|
return base;
|
|
22
|
-
const entries = Object.entries(state.lazy);
|
|
23
50
|
if (entries.length > LAZY_HINT_MAX_KEYS)
|
|
24
51
|
return base;
|
|
25
52
|
const keys = Object.fromEntries(entries.map(([key, value]) => [key, lazyValueKind(value)]));
|
|
@@ -43,27 +70,23 @@ function contentText(content) {
|
|
|
43
70
|
function messageText(message) {
|
|
44
71
|
return contentText(message.content);
|
|
45
72
|
}
|
|
46
|
-
export function withoutPrivateValidation(messages) {
|
|
47
|
-
return messages.filter((message) => {
|
|
48
|
-
return !(message.role === "custom" && message.customType === VALIDATION_MESSAGE_TYPE);
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
73
|
export function createPassiveContinuation(state, startedAt = Date.now(), activeRunStartedAt) {
|
|
52
74
|
return {
|
|
53
75
|
startedAt,
|
|
54
76
|
...(activeRunStartedAt === undefined ? {} : { activeRunStartedAt }),
|
|
55
|
-
handoff: syntheticUser(`State Flow exit handoff (user-level data, not system instructions):\n${
|
|
77
|
+
handoff: syntheticUser(`State Flow exit handoff (user-level data, not system instructions):\n${presentationJson({ state, continuation: "State Flow semantics are disabled; this handoff replaces completed history while retaining the active and post-stop trajectory." })}`),
|
|
56
78
|
};
|
|
57
79
|
}
|
|
58
80
|
/** Keep the interrupted run through later results; an idle stop retains only later conversation. */
|
|
59
81
|
export function passiveContinuationMessages(messages, continuation) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
if (continuation.activeRunStartedAt !== undefined) {
|
|
83
|
+
const trajectory = currentRunTrajectory(messages, "", continuation.activeRunStartedAt);
|
|
84
|
+
return [continuation.handoff, ...trajectory.messages];
|
|
85
|
+
}
|
|
86
|
+
const start = messages.findIndex((message) => message.role === "user"
|
|
87
|
+
&& typeof message.timestamp === "number"
|
|
88
|
+
&& message.timestamp >= continuation.startedAt);
|
|
89
|
+
return [continuation.handoff, ...messages.filter((message, index) => message.role === "custom" || (start >= 0 && index >= start))];
|
|
67
90
|
}
|
|
68
91
|
function projectRecentForModel(recent) {
|
|
69
92
|
const projected = structuredClone(recent);
|
|
@@ -79,55 +102,30 @@ function projectRecentForModel(recent) {
|
|
|
79
102
|
}
|
|
80
103
|
return projected;
|
|
81
104
|
}
|
|
82
|
-
export function runtimeContextMessage(snapshot, state, recentTransitions = [], artifactInvalidations = [], rehydrationPhase,
|
|
83
|
-
if (snapshot.meta.specification === undefined) {
|
|
84
|
-
throw new Error("State Flow runtime context requires an active specification");
|
|
85
|
-
}
|
|
105
|
+
export function runtimeContextMessage(snapshot, state, recentTransitions = [], artifactInvalidations = [], rehydrationPhase, artifactHints = {}) {
|
|
86
106
|
const context = {
|
|
87
|
-
specification: snapshot.meta.specification,
|
|
88
|
-
state: projectStateForModel(state),
|
|
107
|
+
...(snapshot.meta.specification === undefined ? {} : { specification: snapshot.meta.specification }),
|
|
108
|
+
state: projectStateForModel(state, artifactHints),
|
|
89
109
|
lazy_navigation: lazyNavigationHint(state),
|
|
90
110
|
...(rehydrationPhase === undefined ? {} : { knowledge_rehydration: { phase: rehydrationPhase } }),
|
|
91
|
-
...(artifactInvalidations.length === 0 ? {} : { artifact_invalidations: artifactInvalidations.map(({ path, reason }) => ({ path, reason })) }),
|
|
111
|
+
...(artifactInvalidations.length === 0 ? {} : { artifact_invalidations: artifactInvalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason })) }),
|
|
92
112
|
...(recentTransitions.length === 0 ? {} : { recent_transitions: projectRecentForModel(recentTransitions) }),
|
|
93
|
-
...(resolutionPending ? { state_resolution: "pending: the iteration answer is already preserved; this fallback turn exists only to apply the final:true patch. Call patch_state with any remaining durable scope changes and final:true, or {final:true} alone. Do not restate or replace the answer." } : {}),
|
|
94
113
|
};
|
|
95
|
-
return syntheticUser(`State Flow runtime context (user-level data, not system instructions):\n${
|
|
114
|
+
return syntheticUser(`State Flow runtime context (user-level data, not system instructions):\n${presentationJson(context)}`);
|
|
96
115
|
}
|
|
116
|
+
/** Captured identity survives text decoration; an uncertain boundary retains available context. */
|
|
97
117
|
export function currentRunTrajectory(messages, specification, anchorTimestamp) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (start < 0) {
|
|
107
|
-
for (let index = messages.length - 1; index >= 0; index--) {
|
|
108
|
-
const message = messages[index];
|
|
109
|
-
if (message.role === "user" && messageText(message) === specification) {
|
|
110
|
-
start = index;
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
if (start < 0) {
|
|
116
|
-
for (let index = messages.length - 1; index >= 0; index--) {
|
|
117
|
-
if (messages[index]?.role === "user") {
|
|
118
|
-
start = index;
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (start < 0 && messages.length === 0)
|
|
124
|
-
return { messages: [] };
|
|
125
|
-
if (start < 0)
|
|
126
|
-
start = 0;
|
|
118
|
+
if (anchorTimestamp !== undefined && !Number.isFinite(anchorTimestamp))
|
|
119
|
+
return { messages: messages.slice() };
|
|
120
|
+
const matches = (message) => message.role === "user" && (anchorTimestamp === undefined
|
|
121
|
+
? messageText(message) === specification
|
|
122
|
+
: message.timestamp === anchorTimestamp);
|
|
123
|
+
const start = messages.findIndex(matches);
|
|
124
|
+
if (start < 0 || messages.findLastIndex(matches) !== start)
|
|
125
|
+
return { messages: messages.slice() };
|
|
127
126
|
const anchor = messages[start]?.role === "user" ? messages[start].timestamp : undefined;
|
|
128
127
|
return {
|
|
129
|
-
messages: messages.filter((message, index) => message.role === "custom"
|
|
130
|
-
? message.customType !== VALIDATION_MESSAGE_TYPE : index >= start),
|
|
128
|
+
messages: messages.filter((message, index) => message.role === "custom" || index >= start),
|
|
131
129
|
...(typeof anchor === "number" ? { anchorTimestamp: anchor } : {}),
|
|
132
130
|
};
|
|
133
131
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
1
|
import { closeSync, constants, existsSync, fstatSync, lstatSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
3
|
-
import { join,
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
4
3
|
import { loadScopeStream, resolveSessionAddress, sessionRuntimePaths } from "./durable.js";
|
|
5
|
-
import {
|
|
4
|
+
import { MAX_HISTORY_LIMIT } from "./history.js";
|
|
6
5
|
import { parseSessionRuntime } from "./snapshot.js";
|
|
7
|
-
import {
|
|
6
|
+
import { validateScopeLineage } from "./temporal.js";
|
|
8
7
|
/**
|
|
9
8
|
* Preserve host-owned explicit session intent and consult State Flow only for
|
|
10
9
|
* an ordinary default launch. The recommender is a read-only advisory port;
|
|
@@ -143,30 +142,19 @@ export function inspectStateFlowContinuationProvenance(header, repositoryRoot) {
|
|
|
143
142
|
if (config === undefined && runtimeSource === undefined && meta === undefined) {
|
|
144
143
|
return { stateFlow: { enabled: false, restorable: true }, reason: "no State Flow session runtime" };
|
|
145
144
|
}
|
|
146
|
-
const runtime = parseSessionRuntime(config, runtimeSource, header.cwd, header.id
|
|
145
|
+
const runtime = parseSessionRuntime(config, runtimeSource, header.cwd, header.id);
|
|
147
146
|
if (!runtime)
|
|
148
147
|
return { stateFlow: { enabled: false, restorable: true }, reason: "no State Flow session runtime" };
|
|
149
148
|
if (!runtime.config.enabled)
|
|
150
149
|
return { stateFlow: { enabled: false, restorable: true }, reason: "State Flow stopped on selected runtime" };
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
const runtimePath = relative(resolve(repositoryRoot), runtimeSource === undefined ? paths.meta : paths.runtime);
|
|
161
|
-
if (runtimePath.startsWith(".."))
|
|
162
|
-
throw new Error("runtime provenance escapes repository");
|
|
163
|
-
const owner = execFileSync("git", ["-C", repositoryRoot, "log", "-1", "--format=%H", "--", runtimePath], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
164
|
-
if (!owner)
|
|
165
|
-
throw new Error("runtime owner commit is unavailable");
|
|
166
|
-
const restored = loadTemporalRevision(header.cwd, header.id, repositoryRoot, owner, sessionKey);
|
|
167
|
-
if (!restored.runtime || !restored.runtime.document.config.enabled)
|
|
168
|
-
throw new Error("selected Git runtime is not enabled");
|
|
169
|
-
return { stateFlow: { enabled: true, restorable: true }, reason: `exact Git runtime ${owner.slice(0, 12)} is restorable` };
|
|
150
|
+
const global = loadScopeStream(header.cwd, header.id, "global", repositoryRoot, sessionKey);
|
|
151
|
+
const cwd = loadScopeStream(header.cwd, header.id, "cwd", repositoryRoot, sessionKey);
|
|
152
|
+
const session = loadScopeStream(header.cwd, header.id, "session", repositoryRoot, sessionKey);
|
|
153
|
+
if (!global || !cwd || !session)
|
|
154
|
+
throw new Error("incomplete canonical temporal cohort");
|
|
155
|
+
// Shared streams are current, independently validated by their codecs, not frozen to this session's history.
|
|
156
|
+
validateScopeLineage(session, "session", runtime.meta.lineage, MAX_HISTORY_LIMIT);
|
|
157
|
+
return { stateFlow: { enabled: true, restorable: true }, reason: "canonical session lineage is valid beside current shared streams" };
|
|
170
158
|
}
|
|
171
159
|
catch (error) {
|
|
172
160
|
return { stateFlow: { enabled: true, restorable: false }, reason: `State Flow runtime is ineligible: ${error instanceof Error ? error.message : String(error)}` };
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { type ArtifactProvenanceRegistry } from "./artifact.ts";
|
|
2
2
|
import { type ScopeStream, type TemporalState } from "./temporal.ts";
|
|
3
|
-
import {
|
|
3
|
+
import type { StateScope } from "./state.ts";
|
|
4
4
|
/** Canonical semantic sources plus runtime-owned temporal metadata. */
|
|
5
5
|
export interface ScopeStreamSources {
|
|
6
6
|
checkpoint: string;
|
|
7
7
|
patches: string;
|
|
8
8
|
temporal: ScopeTemporalMetadata;
|
|
9
9
|
}
|
|
10
|
+
/** Read-only activation eligibility for one canonical CWD scope. */
|
|
11
|
+
export declare function hasCwdMaterialization(cwd: string, repositoryRoot: string): boolean;
|
|
10
12
|
export interface ScopeTemporalMetadata {
|
|
11
13
|
checkpoint: ScopeStream["checkpoint"]["through"];
|
|
12
14
|
patches: ScopeStream["patches"][number]["transition"][];
|
|
@@ -42,15 +44,10 @@ export declare function sessionRuntimePaths(cwd: string, sessionId: string, repo
|
|
|
42
44
|
};
|
|
43
45
|
/** Unsupported state.json presence never becomes an anchored checkpoint. */
|
|
44
46
|
export declare function loadScopeStream(cwd: string, sessionId: string, scope: StateScope, repositoryRoot: string, sessionKey?: string): ScopeStream | undefined;
|
|
45
|
-
/** Include
|
|
47
|
+
/** Include unsupported predecessor names in the CAS basis so they cannot race canonical publication. */
|
|
46
48
|
export declare function captureTemporalFileBases(cwd: string, sessionId: string, repositoryRoot: string, sessionKey?: string): DurableFileBase[];
|
|
47
49
|
/** Select exact serialized scope updates from one validated active temporal cohort. */
|
|
48
50
|
export declare function temporalStateFileUpdates(cwd: string, sessionId: string, view: TemporalState, scopes: readonly StateScope[], repositoryRoot: string, sessionKey?: string): OwnedFileUpdate[];
|
|
49
|
-
export interface DurablePaths {
|
|
50
|
-
repositoryRoot: string;
|
|
51
|
-
globalState: string;
|
|
52
|
-
globalPatches: string;
|
|
53
|
-
}
|
|
54
51
|
/** Merge authoritative owned leaves while preserving forward-compatible metadata siblings. */
|
|
55
52
|
export declare function serializeScopeMetadata(registry: Readonly<ArtifactProvenanceRegistry> | undefined, stream: ScopeStream, scope: StateScope, cwdIdentity?: string, existingSource?: string): string;
|
|
56
53
|
/** Compatibility serializer retained for metadata-only callers. */
|
|
@@ -59,20 +56,16 @@ export declare function serializeScopeProvenance(registry: Readonly<ArtifactProv
|
|
|
59
56
|
export declare function parseScopeProvenance(source: string | undefined, path: string): ArtifactProvenanceRegistry;
|
|
60
57
|
export interface ScopePaths {
|
|
61
58
|
directory: string;
|
|
62
|
-
state: string;
|
|
63
|
-
patches: string;
|
|
64
59
|
}
|
|
65
60
|
export interface DurableFileBase {
|
|
66
61
|
path: string;
|
|
67
62
|
identity: "missing" | `sha256:${string}`;
|
|
68
63
|
content?: string;
|
|
69
|
-
/** Opaque originals for byte-exact rollback
|
|
64
|
+
/** Opaque originals for byte-exact rollback. */
|
|
70
65
|
bytes?: Uint8Array;
|
|
71
66
|
}
|
|
72
67
|
/** Dedicated runtime storage, independent from Markdown source discovery. */
|
|
73
68
|
export declare function getDurableRepositoryRoot(agentDir?: string): string;
|
|
74
|
-
/** Legacy snapshot paths retained only for one-way migration and exact ownership checks. */
|
|
75
|
-
export declare function durablePaths(repositoryRoot?: string): DurablePaths;
|
|
76
69
|
/** Match Pi's native project-session directory convention exactly. */
|
|
77
70
|
export declare function cwdScopeKey(cwd: string): string;
|
|
78
71
|
/** One safe directory segment, normally the native Pi session filename stem. */
|
|
@@ -82,13 +75,9 @@ export declare function sessionStorageKey(sessionFile: string | undefined, sessi
|
|
|
82
75
|
export declare function resolveSessionAddress(sessionFile: string | undefined, sessionId: string, timestamp?: string): SessionAddress;
|
|
83
76
|
export declare function cwdScopePaths(cwd: string, repositoryRoot?: string): ScopePaths;
|
|
84
77
|
export declare function sessionScopePaths(cwd: string, sessionId: string, repositoryRoot?: string, sessionKey?: string): ScopePaths;
|
|
85
|
-
|
|
86
|
-
export declare function cwdPatchesPath(cwd: string, repositoryRoot?: string): string;
|
|
87
|
-
export declare function sessionStatePath(cwd: string, sessionId: string, repositoryRoot?: string, sessionKey?: string): string;
|
|
88
|
-
export declare function sessionPatchesPath(cwd: string, sessionId: string, repositoryRoot?: string, sessionKey?: string): string;
|
|
89
|
-
/** Exact semantic file shapes, including legacy snapshots only during the storage cutover. */
|
|
78
|
+
/** Exact canonical semantic and runtime file shapes. */
|
|
90
79
|
export declare function isStateFlowOwnedPath(candidate: string, repositoryRoot?: string): boolean;
|
|
91
|
-
/** Capture exact owned bytes for one compare-and-swap publication
|
|
80
|
+
/** Capture exact owned bytes for one compare-and-swap publication cohort. */
|
|
92
81
|
export declare function captureOwnedFileBases(paths: readonly string[], repositoryRoot: string): DurableFileBase[];
|
|
93
82
|
export interface OwnedFileUpdate {
|
|
94
83
|
path: string;
|
|
@@ -3,18 +3,31 @@ import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readFi
|
|
|
3
3
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { parseArtifactProvenanceRegistry, serializeArtifactProvenanceRegistry, } from "./artifact.js";
|
|
6
|
+
import { MAX_HISTORY_LIMIT } from "./history.js";
|
|
6
7
|
import { canonicalJson, isJsonValue, isObject } from "./json.js";
|
|
7
8
|
import { validateScopeStream, validateTemporalState } from "./temporal.js";
|
|
8
|
-
import { migratePreIntentState } from "./state.js";
|
|
9
9
|
const SESSION_KEY_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
10
10
|
const STATE_FILE = "state.json";
|
|
11
11
|
const CHECKPOINT_FILE = "checkpoint.json";
|
|
12
12
|
const PATCHES_FILE = "patches.jsonl";
|
|
13
13
|
const META_FILE = "meta.json";
|
|
14
14
|
const RUNTIME_FILE = "runtime.json";
|
|
15
|
+
/** Read-only activation eligibility for one canonical CWD scope. */
|
|
16
|
+
export function hasCwdMaterialization(cwd, repositoryRoot) {
|
|
17
|
+
const root = resolve(repositoryRoot);
|
|
18
|
+
const directory = cwdScopePaths(cwd, root).directory;
|
|
19
|
+
const [checkpoint, patches, meta] = captureOwnedFileBases([
|
|
20
|
+
join(directory, CHECKPOINT_FILE), join(directory, PATCHES_FILE), join(directory, META_FILE),
|
|
21
|
+
], root);
|
|
22
|
+
if (checkpoint.content !== undefined)
|
|
23
|
+
return parseScopeStream(checkpoint.content, patches.content, "cwd", cwd, meta.content) !== undefined;
|
|
24
|
+
if (patches.content !== undefined)
|
|
25
|
+
throw new Error(`State Flow tail has no provable checkpoint: ${directory}`);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
15
28
|
/** Semantic files contain no runtime envelope; temporal boundaries and CWD ownership live in meta.json. */
|
|
16
29
|
export function serializeScopeStream(stream, scope, cwdIdentity) {
|
|
17
|
-
validateScopeStream(stream, scope);
|
|
30
|
+
validateScopeStream(stream, scope, MAX_HISTORY_LIMIT);
|
|
18
31
|
if (scope === "cwd" && cwdIdentity === undefined)
|
|
19
32
|
throw new Error("State Flow CWD scope serialization requires its canonical identity");
|
|
20
33
|
if (scope !== "cwd" && cwdIdentity !== undefined)
|
|
@@ -30,6 +43,8 @@ export function serializeScopeStream(stream, scope, cwdIdentity) {
|
|
|
30
43
|
}
|
|
31
44
|
/** Distinguish a wholly absent semantic cohort from partial or malformed surviving authority. */
|
|
32
45
|
export function classifyScopeStream(checkpointSource, patchesSource, scope, expectedCwd, metaSource) {
|
|
46
|
+
if (scope !== "global" && scope !== "cwd" && scope !== "session")
|
|
47
|
+
throw new Error("Unknown temporal scope");
|
|
33
48
|
if (checkpointSource === undefined && patchesSource === undefined)
|
|
34
49
|
return { kind: "absent" };
|
|
35
50
|
if (checkpointSource === undefined || patchesSource === undefined) {
|
|
@@ -65,44 +80,22 @@ export function classifyScopeStream(checkpointSource, patchesSource, scope, expe
|
|
|
65
80
|
}
|
|
66
81
|
}
|
|
67
82
|
const temporal = meta?.temporal;
|
|
68
|
-
if (isObject(temporal)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
// Complete predecessor envelopes are the only no-meta form that may be unwrapped.
|
|
86
|
-
let legacyCheckpoint = checkpoint;
|
|
87
|
-
if (isObject(checkpoint) && Object.hasOwn(checkpoint, "owner")) {
|
|
88
|
-
const { owner, ...semantic } = checkpoint;
|
|
89
|
-
if (scope !== "cwd" || !isObject(owner) || Object.keys(owner).join(",") !== "cwd" || typeof owner.cwd !== "string") {
|
|
90
|
-
throw new Error("Invalid State Flow CWD scope identity");
|
|
91
|
-
}
|
|
92
|
-
if (expectedCwd !== undefined && owner.cwd !== resolve(expectedCwd))
|
|
93
|
-
throw new Error("State Flow CWD scope identity mismatch");
|
|
94
|
-
legacyCheckpoint = semantic;
|
|
95
|
-
}
|
|
96
|
-
else if (scope === "cwd" && expectedCwd !== undefined) {
|
|
97
|
-
throw new Error("State Flow CWD scope identity is missing");
|
|
98
|
-
}
|
|
99
|
-
if (isObject(legacyCheckpoint) && isObject(legacyCheckpoint.state)) {
|
|
100
|
-
const migrated = migratePreIntentState(legacyCheckpoint.state);
|
|
101
|
-
if (migrated)
|
|
102
|
-
legacyCheckpoint = { ...legacyCheckpoint, state: migrated };
|
|
103
|
-
}
|
|
104
|
-
const stream = { checkpoint: legacyCheckpoint, patches };
|
|
105
|
-
validateScopeStream(stream, scope);
|
|
83
|
+
if (!isObject(temporal) || !Object.hasOwn(temporal, "checkpoint") || !Array.isArray(temporal.patches)) {
|
|
84
|
+
throw new Error(`Unsupported State Flow ${scope} storage format`);
|
|
85
|
+
}
|
|
86
|
+
const owner = meta?.owner;
|
|
87
|
+
if (scope === "cwd" && expectedCwd !== undefined
|
|
88
|
+
&& (!isObject(owner) || Object.keys(owner).join(",") !== "cwd" || owner.cwd !== resolve(expectedCwd))) {
|
|
89
|
+
throw new Error(owner === undefined ? "State Flow CWD scope identity is missing" : "State Flow CWD scope identity mismatch");
|
|
90
|
+
}
|
|
91
|
+
const boundaries = temporal.patches;
|
|
92
|
+
if (boundaries.length !== patches.length)
|
|
93
|
+
throw new Error(`State Flow ${scope} temporal metadata does not match its semantic tail`);
|
|
94
|
+
const stream = {
|
|
95
|
+
checkpoint: { through: temporal.checkpoint, state: checkpoint },
|
|
96
|
+
patches: patches.map((patch, index) => ({ transition: boundaries[index], patch })),
|
|
97
|
+
};
|
|
98
|
+
validateScopeStream(stream, scope, MAX_HISTORY_LIMIT);
|
|
106
99
|
return { kind: "present", stream };
|
|
107
100
|
}
|
|
108
101
|
/** Decode the entire bounded replay input before accepting any materialized state. */
|
|
@@ -127,22 +120,22 @@ export function sessionRuntimePaths(cwd, sessionId, repositoryRoot, sessionKey =
|
|
|
127
120
|
export function loadScopeStream(cwd, sessionId, scope, repositoryRoot, sessionKey = sessionId) {
|
|
128
121
|
const paths = temporalScopePaths(cwd, sessionId, scope, repositoryRoot, sessionKey);
|
|
129
122
|
if (readRegularBytes(join(paths.directory, STATE_FILE), repositoryRoot) !== undefined) {
|
|
130
|
-
throw new Error(`
|
|
123
|
+
throw new Error(`Unsupported State Flow storage format: ${paths.directory}`);
|
|
131
124
|
}
|
|
132
125
|
return parseScopeStream(readRegularFile(paths.checkpoint, repositoryRoot), readRegularFile(paths.patches, repositoryRoot), scope, scope === "cwd" ? cwd : undefined, readRegularFile(paths.meta, repositoryRoot));
|
|
133
126
|
}
|
|
134
|
-
/** Include
|
|
127
|
+
/** Include unsupported predecessor names in the CAS basis so they cannot race canonical publication. */
|
|
135
128
|
export function captureTemporalFileBases(cwd, sessionId, repositoryRoot, sessionKey = sessionId) {
|
|
136
129
|
const paths = ["global", "cwd", "session"].flatMap((scope) => {
|
|
137
130
|
const pair = temporalScopePaths(cwd, sessionId, scope, repositoryRoot, sessionKey);
|
|
138
131
|
const runtime = scope === "session" ? sessionRuntimePaths(cwd, sessionId, repositoryRoot, sessionKey) : undefined;
|
|
139
|
-
return [pair.checkpoint, pair.patches,
|
|
132
|
+
return [pair.checkpoint, pair.patches, ...(runtime === undefined ? [pair.meta] : [runtime.config, runtime.runtime, runtime.meta])];
|
|
140
133
|
});
|
|
141
134
|
return captureOwnedFileBases(paths, repositoryRoot);
|
|
142
135
|
}
|
|
143
136
|
/** Select exact serialized scope updates from one validated active temporal cohort. */
|
|
144
137
|
export function temporalStateFileUpdates(cwd, sessionId, view, scopes, repositoryRoot, sessionKey = sessionId) {
|
|
145
|
-
validateTemporalState(view);
|
|
138
|
+
validateTemporalState(view, MAX_HISTORY_LIMIT);
|
|
146
139
|
const seen = new Set();
|
|
147
140
|
return scopes.flatMap((scope) => {
|
|
148
141
|
if (seen.has(scope))
|
|
@@ -150,7 +143,7 @@ export function temporalStateFileUpdates(cwd, sessionId, view, scopes, repositor
|
|
|
150
143
|
seen.add(scope);
|
|
151
144
|
const paths = temporalScopePaths(cwd, sessionId, scope, repositoryRoot, sessionKey);
|
|
152
145
|
if (readRegularBytes(join(paths.directory, STATE_FILE), repositoryRoot) !== undefined) {
|
|
153
|
-
throw new Error(`
|
|
146
|
+
throw new Error(`Unsupported State Flow storage format: ${paths.directory}`);
|
|
154
147
|
}
|
|
155
148
|
const sources = serializeScopeStream(view.scopes[scope], scope, scope === "cwd" ? cwd : undefined);
|
|
156
149
|
return [{ path: paths.checkpoint, content: sources.checkpoint }, { path: paths.patches, content: sources.patches }];
|
|
@@ -160,7 +153,7 @@ export function temporalStateFileUpdates(cwd, sessionId, view, scopes, repositor
|
|
|
160
153
|
export function serializeScopeMetadata(registry, stream, scope, cwdIdentity, existingSource) {
|
|
161
154
|
const existing = parseMetadataDocument(existingSource, `State Flow metadata`);
|
|
162
155
|
if (scope === "session") {
|
|
163
|
-
for (const key of ["identity", "lineage", "step", "specification", "validation", "bootstrap"
|
|
156
|
+
for (const key of ["identity", "lineage", "step", "specification", "validation", "bootstrap"])
|
|
164
157
|
delete existing[key];
|
|
165
158
|
}
|
|
166
159
|
const sources = serializeScopeStream(stream, scope, cwdIdentity);
|
|
@@ -206,15 +199,6 @@ export function parseScopeProvenance(source, path) {
|
|
|
206
199
|
export function getDurableRepositoryRoot(agentDir = getAgentDir()) {
|
|
207
200
|
return resolve(agentDir, "state-flow");
|
|
208
201
|
}
|
|
209
|
-
/** Legacy snapshot paths retained only for one-way migration and exact ownership checks. */
|
|
210
|
-
export function durablePaths(repositoryRoot = getDurableRepositoryRoot()) {
|
|
211
|
-
const root = resolve(repositoryRoot);
|
|
212
|
-
return {
|
|
213
|
-
repositoryRoot: root,
|
|
214
|
-
globalState: join(root, STATE_FILE),
|
|
215
|
-
globalPatches: join(root, PATCHES_FILE),
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
202
|
/** Match Pi's native project-session directory convention exactly. */
|
|
219
203
|
export function cwdScopeKey(cwd) {
|
|
220
204
|
const canonical = resolve(cwd);
|
|
@@ -244,32 +228,17 @@ export function resolveSessionAddress(sessionFile, sessionId, timestamp) {
|
|
|
244
228
|
return Object.freeze({ id: sessionId, key: sessionStorageKey(sessionFile, sessionId, timestamp) });
|
|
245
229
|
}
|
|
246
230
|
export function cwdScopePaths(cwd, repositoryRoot = getDurableRepositoryRoot()) {
|
|
247
|
-
|
|
248
|
-
return { directory, state: join(directory, STATE_FILE), patches: join(directory, PATCHES_FILE) };
|
|
231
|
+
return { directory: join(resolve(repositoryRoot), cwdScopeKey(cwd)) };
|
|
249
232
|
}
|
|
250
233
|
export function sessionScopePaths(cwd, sessionId, repositoryRoot = getDurableRepositoryRoot(), sessionKey = sessionId) {
|
|
251
|
-
|
|
252
|
-
return { directory, state: join(directory, STATE_FILE), patches: join(directory, PATCHES_FILE) };
|
|
253
|
-
}
|
|
254
|
-
export function cwdStatePath(cwd, repositoryRoot = getDurableRepositoryRoot()) {
|
|
255
|
-
return cwdScopePaths(cwd, repositoryRoot).state;
|
|
256
|
-
}
|
|
257
|
-
export function cwdPatchesPath(cwd, repositoryRoot = getDurableRepositoryRoot()) {
|
|
258
|
-
return cwdScopePaths(cwd, repositoryRoot).patches;
|
|
259
|
-
}
|
|
260
|
-
export function sessionStatePath(cwd, sessionId, repositoryRoot = getDurableRepositoryRoot(), sessionKey = sessionId) {
|
|
261
|
-
return sessionScopePaths(cwd, sessionId, repositoryRoot, sessionKey).state;
|
|
262
|
-
}
|
|
263
|
-
export function sessionPatchesPath(cwd, sessionId, repositoryRoot = getDurableRepositoryRoot(), sessionKey = sessionId) {
|
|
264
|
-
return sessionScopePaths(cwd, sessionId, repositoryRoot, sessionKey).patches;
|
|
234
|
+
return { directory: join(cwdScopePaths(cwd, repositoryRoot).directory, sessionScopeKey(sessionKey)) };
|
|
265
235
|
}
|
|
266
|
-
/** Exact semantic file shapes
|
|
236
|
+
/** Exact canonical semantic and runtime file shapes. */
|
|
267
237
|
export function isStateFlowOwnedPath(candidate, repositoryRoot = getDurableRepositoryRoot()) {
|
|
268
238
|
const root = resolve(repositoryRoot);
|
|
269
239
|
const absolute = resolve(candidate);
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|| absolute === join(root, CHECKPOINT_FILE) || absolute === join(root, META_FILE))
|
|
240
|
+
if (absolute === join(root, CHECKPOINT_FILE) || absolute === join(root, PATCHES_FILE)
|
|
241
|
+
|| absolute === join(root, META_FILE))
|
|
273
242
|
return true;
|
|
274
243
|
const segments = relative(root, absolute).split(sep);
|
|
275
244
|
const cwdKey = (value) => value.startsWith("--") && value.endsWith("--");
|
|
@@ -282,12 +251,12 @@ export function isStateFlowOwnedPath(candidate, repositoryRoot = getDurableRepos
|
|
|
282
251
|
}
|
|
283
252
|
};
|
|
284
253
|
if (segments.length === 2) {
|
|
285
|
-
return cwdKey(segments[0]) && (segments[1] ===
|
|
254
|
+
return cwdKey(segments[0]) && (segments[1] === CHECKPOINT_FILE || segments[1] === PATCHES_FILE || segments[1] === META_FILE);
|
|
286
255
|
}
|
|
287
256
|
if (segments.length === 3) {
|
|
288
257
|
return cwdKey(segments[0])
|
|
289
258
|
&& sessionKey(segments[1])
|
|
290
|
-
&& (segments[2] ===
|
|
259
|
+
&& (segments[2] === CHECKPOINT_FILE || segments[2] === PATCHES_FILE
|
|
291
260
|
|| segments[2] === "config.json" || segments[2] === RUNTIME_FILE || segments[2] === META_FILE);
|
|
292
261
|
}
|
|
293
262
|
return false;
|
|
@@ -378,7 +347,7 @@ function assertCurrentBytes(path, root, expected) {
|
|
|
378
347
|
throw new Error(`State Flow file conflict at ${path}; preserve concurrent bytes and reconcile before retrying`);
|
|
379
348
|
}
|
|
380
349
|
}
|
|
381
|
-
/** Capture exact owned bytes for one compare-and-swap publication
|
|
350
|
+
/** Capture exact owned bytes for one compare-and-swap publication cohort. */
|
|
382
351
|
export function captureOwnedFileBases(paths, repositoryRoot) {
|
|
383
352
|
const root = resolve(repositoryRoot);
|
|
384
353
|
return paths.map((path) => {
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { formatPatchStateArguments
|
|
2
|
+
import { formatPatchStateArguments } from "./protocol.ts";
|
|
3
|
+
import { type ModelState, type StateScope } from "./state.ts";
|
|
3
4
|
import { type StateFlowTelegramLoader } from "./telegram.ts";
|
|
4
|
-
import { type MaterializedState, type StateScope } from "./state.ts";
|
|
5
5
|
export interface StateFlowExtensionOptions {
|
|
6
6
|
agentDir?: string;
|
|
7
7
|
repositoryRoot?: string;
|
|
8
|
-
knowledgeRoot?: string;
|
|
9
8
|
onRuntime?: (accessor: {
|
|
10
|
-
read(offset?: number, scope?: StateScope):
|
|
9
|
+
read(offset?: number, scope?: StateScope): ModelState;
|
|
11
10
|
}) => void;
|
|
12
11
|
telegram?: {
|
|
13
12
|
load?: StateFlowTelegramLoader;
|
|
@@ -18,8 +17,7 @@ export interface StateFlowExtensionOptions {
|
|
|
18
17
|
tools?: boolean;
|
|
19
18
|
};
|
|
20
19
|
}
|
|
21
|
-
export { formatPatchStateArguments
|
|
20
|
+
export { formatPatchStateArguments };
|
|
22
21
|
export declare const PATCH_STATE_TOOL_NAME = "patch_state";
|
|
23
22
|
export declare const READ_STATE_TOOL_NAME = "read_state";
|
|
24
|
-
export declare const MAX_FALLBACK_ATTEMPTS: number;
|
|
25
23
|
export default function stateFlowExtension(pi: ExtensionAPI, options?: StateFlowExtensionOptions): void;
|