@llblab/pi-kit 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.md +1 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +21 -21
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +3 -122
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +11 -0
- package/node_modules/@llblab/pi-state-flow/README.md +41 -35
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +36 -16
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +4 -4
- package/node_modules/@llblab/pi-state-flow/index.ts +23 -1
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +191 -29
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -1
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +42 -7
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +38 -8
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +1 -13
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +290 -134
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +32 -16
- package/node_modules/@llblab/pi-state-flow/lib/logging.ts +41 -0
- package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +12 -6
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +16 -0
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +167 -31
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +15 -6
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +40 -34
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +4 -9
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +25 -5
- package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +17 -147
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +49 -27
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/package.json +2 -2
- package/node_modules/@llblab/pi-state-flow/lib/validation.ts +0 -27
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
compileArtifact,
|
|
2
3
|
ORDINARY_ARTIFACT_COMPILER,
|
|
3
4
|
validateArtifactMetadata,
|
|
4
5
|
validateArtifactRegistry,
|
|
5
|
-
type
|
|
6
|
+
type ArtifactCompilerOutput,
|
|
7
|
+
type ArtifactProvenance,
|
|
6
8
|
} from "./artifact.ts";
|
|
7
9
|
import type { SuccessfulArtifactRead } from "./acquisition.ts";
|
|
8
10
|
import { createAcceptedTransition, type AcceptedTransition } from "./history.ts";
|
|
@@ -24,6 +26,8 @@ import type {
|
|
|
24
26
|
export interface StagedScopedTransition {
|
|
25
27
|
nextStates: ScopedStates;
|
|
26
28
|
stateHashes: Record<StateScope, string>;
|
|
29
|
+
/** Fresh runtime-owned provenance for artifacts compiled in this transition. */
|
|
30
|
+
provenanceUpdates: Record<StateScope, Record<string, ArtifactProvenance>>;
|
|
27
31
|
causalBasis: string;
|
|
28
32
|
committed: boolean;
|
|
29
33
|
}
|
|
@@ -35,27 +39,26 @@ function compileReadArtifacts(
|
|
|
35
39
|
nextState: StateDocument,
|
|
36
40
|
patch: Pick<StatePatch, "artifacts">,
|
|
37
41
|
successfulArtifactReads: Iterable<SuccessfulArtifactRead>,
|
|
42
|
+
provenance: Record<string, ArtifactProvenance>,
|
|
38
43
|
): void {
|
|
39
44
|
for (const read of successfulArtifactReads) {
|
|
40
45
|
const output = patch.artifacts[read.path];
|
|
41
46
|
if (!isObject(output)) {
|
|
42
47
|
throw new Error(`Every successfully read invalidated artifact must have a global compiler output at artifacts[exact candidate path]; missing: ${read.path}`);
|
|
43
48
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
const metadata = {
|
|
48
|
-
...structuredClone(output),
|
|
49
|
-
hash: read.hash,
|
|
49
|
+
const compiled = compileArtifact({
|
|
50
|
+
source: { path: read.path, hash: read.hash },
|
|
50
51
|
compiler: ORDINARY_ARTIFACT_COMPILER,
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
output: output as ArtifactCompilerOutput,
|
|
53
|
+
});
|
|
54
|
+
validateArtifactMetadata(compiled.semantic, read.path);
|
|
53
55
|
Object.defineProperty(nextState.artifacts, read.path, {
|
|
54
|
-
value:
|
|
56
|
+
value: compiled.semantic,
|
|
55
57
|
enumerable: true,
|
|
56
58
|
configurable: true,
|
|
57
59
|
writable: true,
|
|
58
60
|
});
|
|
61
|
+
provenance[read.path] = compiled.provenance;
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
|
|
@@ -63,6 +66,7 @@ function compileReadSkills(
|
|
|
63
66
|
nextState: StateDocument,
|
|
64
67
|
patch: Pick<StatePatch, "artifacts">,
|
|
65
68
|
successfulSkillReads: Iterable<SuccessfulSkillRead>,
|
|
69
|
+
provenance: Record<string, ArtifactProvenance>,
|
|
66
70
|
): void {
|
|
67
71
|
for (const read of successfulSkillReads) {
|
|
68
72
|
if (read.hash === undefined) {
|
|
@@ -84,20 +88,20 @@ function compileReadSkills(
|
|
|
84
88
|
if (!isObject(output.compilation) || Object.keys(output.compilation).length === 0) {
|
|
85
89
|
throw new Error(`Skill artifact compiler output at ${read.path} must have a non-empty compilation object`);
|
|
86
90
|
}
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
hash: read.hash,
|
|
91
|
+
const compiled = compileArtifact({
|
|
92
|
+
source: { path: read.path, hash: read.hash },
|
|
90
93
|
compiler: SKILL_ARTIFACT_COMPILER,
|
|
91
|
-
kind: "skill",
|
|
92
|
-
}
|
|
93
|
-
validateArtifactMetadata(
|
|
94
|
+
output: { ...structuredClone(output), kind: "skill" } as ArtifactCompilerOutput,
|
|
95
|
+
});
|
|
96
|
+
validateArtifactMetadata(compiled.semantic, read.path);
|
|
94
97
|
Object.defineProperty(nextState.artifacts, read.path, {
|
|
95
|
-
value:
|
|
98
|
+
value: compiled.semantic,
|
|
96
99
|
enumerable: true,
|
|
97
100
|
configurable: true,
|
|
98
101
|
writable: true,
|
|
99
102
|
});
|
|
100
|
-
|
|
103
|
+
provenance[read.path] = compiled.provenance;
|
|
104
|
+
if (!hasCompiledSkillArtifact(nextState.artifacts, provenance[read.path], read.path, read.hash)) {
|
|
101
105
|
throw new Error(`Skill artifact compilation at ${read.path} is not locally materialized for its executed source identity`);
|
|
102
106
|
}
|
|
103
107
|
}
|
|
@@ -146,7 +150,7 @@ function stageScopedSemanticTransition(
|
|
|
146
150
|
successfulSkillReads: Iterable<SuccessfulSkillRead>,
|
|
147
151
|
causalBasis: string,
|
|
148
152
|
successfulArtifactReads: Iterable<SuccessfulArtifactRead>,
|
|
149
|
-
|
|
153
|
+
acceptedResponse?: string,
|
|
150
154
|
): StagedScopedTransition {
|
|
151
155
|
if (!Array.isArray(transition.transitions)) throw new Error("State Flow transitions must be an array");
|
|
152
156
|
const patches = new Map<StateScope, ScopePatch>();
|
|
@@ -164,20 +168,22 @@ function stageScopedSemanticTransition(
|
|
|
164
168
|
|
|
165
169
|
const cwdPatch = patches.get("cwd") ?? {};
|
|
166
170
|
const nextStates = structuredClone(currentStates);
|
|
171
|
+
const provenanceUpdates: Record<StateScope, Record<string, ArtifactProvenance>> = { global: {}, cwd: {}, session: {} };
|
|
167
172
|
for (const scope of SCOPES) {
|
|
168
173
|
const authored = patches.get(scope) ?? {};
|
|
169
|
-
const response = scope === "session" &&
|
|
170
|
-
?
|
|
174
|
+
const response = scope === "session" && acceptedResponse !== undefined
|
|
175
|
+
? acceptedResponse
|
|
171
176
|
: currentStates[scope].response;
|
|
172
177
|
const patch = completePatch(authored, response);
|
|
173
178
|
const nextState = applyPatch(structuredClone(currentStates[scope]), patch) as MaterializedState;
|
|
174
|
-
compileReadArtifacts(nextState, { artifacts: scope === "global" ? authored.artifacts ?? {} : {} }, scope === "global" ? successfulArtifactReads : []);
|
|
175
|
-
compileReadSkills(nextState, { artifacts: scope === "cwd" ? cwdPatch.artifacts ?? {} : {} }, scope === "cwd" ? successfulSkillReads : []);
|
|
179
|
+
compileReadArtifacts(nextState, { artifacts: scope === "global" ? authored.artifacts ?? {} : {} }, scope === "global" ? successfulArtifactReads : [], provenanceUpdates.global);
|
|
180
|
+
compileReadSkills(nextState, { artifacts: scope === "cwd" ? cwdPatch.artifacts ?? {} : {} }, scope === "cwd" ? successfulSkillReads : [], provenanceUpdates.cwd);
|
|
176
181
|
validateMaterializedTransition(nextState);
|
|
177
182
|
nextStates[scope] = nextState;
|
|
178
183
|
}
|
|
179
184
|
return {
|
|
180
185
|
nextStates,
|
|
186
|
+
provenanceUpdates,
|
|
181
187
|
stateHashes: {
|
|
182
188
|
global: hashJson(currentStates.global),
|
|
183
189
|
cwd: hashJson(currentStates.cwd),
|
|
@@ -188,6 +194,22 @@ function stageScopedSemanticTransition(
|
|
|
188
194
|
};
|
|
189
195
|
}
|
|
190
196
|
|
|
197
|
+
/** Validate that explicit unchanged resolution has no pending acquisition/compilation obligation. */
|
|
198
|
+
export function validateUnchangedResolution(
|
|
199
|
+
currentStates: ScopedStates,
|
|
200
|
+
successfulSkillReads: Iterable<SuccessfulSkillRead>,
|
|
201
|
+
causalBasis: string,
|
|
202
|
+
successfulArtifactReads: Iterable<SuccessfulArtifactRead> = [],
|
|
203
|
+
): void {
|
|
204
|
+
stageScopedSemanticTransition(
|
|
205
|
+
currentStates,
|
|
206
|
+
{ transitions: [] },
|
|
207
|
+
successfulSkillReads,
|
|
208
|
+
causalBasis,
|
|
209
|
+
successfulArtifactReads,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
191
213
|
/** Stage one intermediate state barrier without changing the finalized response. */
|
|
192
214
|
export function stageScopedPatch(
|
|
193
215
|
currentStates: ScopedStates,
|
|
@@ -213,7 +235,7 @@ export function stageScopedTransition(
|
|
|
213
235
|
successfulArtifactReads: Iterable<SuccessfulArtifactRead> = [],
|
|
214
236
|
): StagedScopedTransition {
|
|
215
237
|
if (typeof transition.response !== "string" || transition.response.trim().length === 0) {
|
|
216
|
-
throw new Error("
|
|
238
|
+
throw new Error("Accepted State Flow response body must be non-empty");
|
|
217
239
|
}
|
|
218
240
|
return stageScopedSemanticTransition(
|
|
219
241
|
currentStates,
|
|
@@ -227,7 +249,7 @@ export function stageScopedTransition(
|
|
|
227
249
|
|
|
228
250
|
/** Commit one accepted transition; durable publication receives all changed scopes as one cohort. */
|
|
229
251
|
export interface CommitScopedTransitionOptions {
|
|
230
|
-
/**
|
|
252
|
+
/** Runtime response reconciliation finalizes bootstrap lifecycle state. */
|
|
231
253
|
finalizeRun?: boolean;
|
|
232
254
|
}
|
|
233
255
|
|
|
@@ -240,10 +262,10 @@ export function commitScopedTransition(
|
|
|
240
262
|
options: CommitScopedTransitionOptions = {},
|
|
241
263
|
): boolean {
|
|
242
264
|
if (stage.committed) return false;
|
|
243
|
-
if (causalBasis !== stage.causalBasis) throw new Error("State Flow causal basis changed
|
|
265
|
+
if (causalBasis !== stage.causalBasis) throw new Error("State Flow causal basis changed before response reconciliation; rematerialize state before retrying");
|
|
244
266
|
for (const scope of SCOPES) {
|
|
245
267
|
if (hashJson(states[scope]) !== stage.stateHashes[scope]) {
|
|
246
|
-
throw new Error(`State Flow ${scope} scope changed
|
|
268
|
+
throw new Error(`State Flow ${scope} scope changed before response reconciliation; rematerialize state before retrying`);
|
|
247
269
|
}
|
|
248
270
|
}
|
|
249
271
|
// The finalized response may differ from message_end after chained handlers.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@llblab/pi-clean-room": "0.1.1",
|
|
45
45
|
"@llblab/pi-codex-usage": "0.9.4",
|
|
46
46
|
"@llblab/pi-grow-loop": "0.7.5",
|
|
47
|
-
"@llblab/pi-state-flow": "0.
|
|
47
|
+
"@llblab/pi-state-flow": "0.7.0",
|
|
48
48
|
"@llblab/pi-telegram": "0.45.1",
|
|
49
49
|
"@llblab/skills": "1.15.0"
|
|
50
50
|
},
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { terminalRegenerationInstruction } from "./terminal.ts";
|
|
2
|
-
|
|
3
|
-
export const MAX_VALIDATION_RETRIES = 3;
|
|
4
|
-
|
|
5
|
-
export interface ValidationFeedback {
|
|
6
|
-
attempt: number;
|
|
7
|
-
error: string;
|
|
8
|
-
instruction: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type ValidationDecision =
|
|
12
|
-
| { kind: "retry"; feedback: ValidationFeedback }
|
|
13
|
-
| { kind: "exhausted"; error: string };
|
|
14
|
-
|
|
15
|
-
/** Decide retry progression without mutating persistent runtime state. */
|
|
16
|
-
export function nextValidation(previous: ValidationFeedback | undefined, error: string): ValidationDecision {
|
|
17
|
-
const attempt = (previous?.attempt ?? 0) + 1;
|
|
18
|
-
if (attempt > MAX_VALIDATION_RETRIES) return { kind: "exhausted", error };
|
|
19
|
-
return {
|
|
20
|
-
kind: "retry",
|
|
21
|
-
feedback: {
|
|
22
|
-
attempt,
|
|
23
|
-
error,
|
|
24
|
-
instruction: terminalRegenerationInstruction(error),
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
}
|