@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.
Files changed (32) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -1
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +21 -21
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +3 -122
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +11 -0
  6. package/node_modules/@llblab/pi-state-flow/README.md +41 -35
  7. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +36 -16
  8. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +4 -4
  9. package/node_modules/@llblab/pi-state-flow/index.ts +23 -1
  10. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +3 -0
  11. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +191 -29
  12. package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -1
  13. package/node_modules/@llblab/pi-state-flow/lib/context.ts +42 -7
  14. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +38 -8
  15. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +1 -13
  16. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +290 -134
  17. package/node_modules/@llblab/pi-state-flow/lib/git.ts +32 -16
  18. package/node_modules/@llblab/pi-state-flow/lib/logging.ts +41 -0
  19. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +12 -6
  20. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +16 -0
  21. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +3 -0
  22. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +167 -31
  23. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +15 -6
  24. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +40 -34
  25. package/node_modules/@llblab/pi-state-flow/lib/state.ts +6 -0
  26. package/node_modules/@llblab/pi-state-flow/lib/status.ts +4 -9
  27. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +25 -5
  28. package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +17 -147
  29. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +49 -27
  30. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  31. package/package.json +2 -2
  32. 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 ArtifactMetadata,
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
- if (Object.hasOwn(output, "hash") || Object.hasOwn(output, "compiler")) {
45
- throw new Error(`Artifact compiler output at ${read.path} cannot set runtime-owned hash or compiler fields`);
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
- } as ArtifactMetadata;
52
- validateArtifactMetadata(metadata, read.path);
52
+ output: output as ArtifactCompilerOutput,
53
+ });
54
+ validateArtifactMetadata(compiled.semantic, read.path);
53
55
  Object.defineProperty(nextState.artifacts, read.path, {
54
- value: metadata,
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 metadata = {
88
- ...structuredClone(output),
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
- } as ArtifactMetadata;
93
- validateArtifactMetadata(metadata, read.path);
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: metadata,
98
+ value: compiled.semantic,
96
99
  enumerable: true,
97
100
  configurable: true,
98
101
  writable: true,
99
102
  });
100
- if (!hasCompiledSkillArtifact(nextState.artifacts, read.path, read.hash)) {
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
- terminalResponse?: string,
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" && terminalResponse !== undefined
170
- ? terminalResponse
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("Terminal State Flow response body must be non-empty");
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
- /** Terminal reconciliation finalizes bootstrap and validation lifecycle state. */
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 after response validation; regenerate the terminal response");
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 after response validation; regenerate the terminal response`);
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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-state-flow",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "description": "Incremental scoped state/context compiler for Pi, inspired by SKILL.state",
6
6
  "keywords": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-kit",
3
- "version": "0.7.1",
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.6.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
- }