@llblab/pi-kit 0.13.0 → 0.14.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 (95) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +1 -1
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +7 -7
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +8 -9
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +26 -0
  6. package/node_modules/@llblab/pi-state-flow/README.md +1 -3
  7. package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +21 -0
  8. package/node_modules/@llblab/pi-state-flow/dist/index.js +20 -0
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +39 -0
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +78 -0
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +110 -0
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +334 -0
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +49 -0
  14. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +67 -0
  15. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +11 -0
  16. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +53 -0
  17. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +23 -0
  18. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +109 -0
  19. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.d.ts +111 -0
  20. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +189 -0
  21. package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.d.ts +21 -0
  22. package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.js +125 -0
  23. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +102 -0
  24. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +507 -0
  25. package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +8 -0
  26. package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +27 -0
  27. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +22 -0
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +1263 -0
  29. package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +72 -0
  30. package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +565 -0
  31. package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +22 -0
  32. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +79 -0
  33. package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +12 -0
  34. package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +109 -0
  35. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +25 -0
  36. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +24 -0
  37. package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.d.ts +36 -0
  38. package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.js +98 -0
  39. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +15 -0
  40. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +42 -0
  41. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +13 -0
  42. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +133 -0
  43. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +7 -0
  44. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +59 -0
  45. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +69 -0
  46. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +335 -0
  47. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +27 -0
  48. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +35 -0
  49. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +8 -0
  50. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +27 -0
  51. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +36 -0
  52. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +38 -0
  53. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +142 -0
  54. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +529 -0
  55. package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +21 -0
  56. package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +44 -0
  57. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +25 -0
  58. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +131 -0
  59. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +88 -0
  60. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +255 -0
  61. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +55 -0
  62. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +31 -0
  63. package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +38 -0
  64. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +79 -0
  65. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +46 -0
  66. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +217 -0
  67. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +98 -0
  68. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +231 -0
  69. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +39 -0
  70. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +203 -0
  71. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +25 -0
  72. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +204 -0
  73. package/node_modules/@llblab/pi-state-flow/dist/package.json +79 -0
  74. package/node_modules/@llblab/pi-state-flow/dist/pi-state-flow/index.js +1 -0
  75. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +138 -0
  76. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +11 -5
  77. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +5 -1
  78. package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +3 -3
  79. package/node_modules/@llblab/pi-state-flow/docs/usage.md +6 -4
  80. package/node_modules/@llblab/pi-state-flow/index.ts +1 -0
  81. package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +17 -1
  82. package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -1
  83. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +88 -96
  84. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +64 -12
  85. package/node_modules/@llblab/pi-state-flow/lib/git.ts +32 -188
  86. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +84 -48
  87. package/node_modules/@llblab/pi-state-flow/lib/query.ts +40 -0
  88. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +7 -30
  89. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +48 -50
  90. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +41 -97
  91. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +17 -12
  92. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +4 -4
  93. package/node_modules/@llblab/pi-state-flow/package.json +23 -6
  94. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +3 -1
  95. package/package.json +4 -4
@@ -0,0 +1,21 @@
1
+ export declare const SNAPSHOT_ENTRY_TYPE = "state-flow-snapshot";
2
+ interface BranchEntry {
3
+ type?: unknown;
4
+ customType?: unknown;
5
+ data?: unknown;
6
+ message?: {
7
+ role?: unknown;
8
+ };
9
+ }
10
+ export interface SnapshotDiscovery {
11
+ candidates: unknown[];
12
+ errors: string[];
13
+ }
14
+ /** Enumerate active-branch snapshots newest-first while containing hostile entries. */
15
+ export declare function discoverSnapshotData(branch: readonly BranchEntry[]): SnapshotDiscovery;
16
+ export declare function snapshotDataNewestFirst(branch: readonly BranchEntry[]): unknown[];
17
+ export declare function latestSnapshotData(branch: readonly BranchEntry[]): unknown;
18
+ export declare function hasPriorConversation(branch: readonly BranchEntry[]): boolean;
19
+ /** Auto-start eligibility is session identity/lifecycle, not the presence of CWD materialization. */
20
+ export declare function isNewSession(reason: unknown, branch: readonly BranchEntry[]): boolean;
21
+ export {};
@@ -0,0 +1,44 @@
1
+ export const SNAPSHOT_ENTRY_TYPE = "state-flow-snapshot";
2
+ /** Enumerate active-branch snapshots newest-first while containing hostile entries. */
3
+ export function discoverSnapshotData(branch) {
4
+ const candidates = [];
5
+ const errors = [];
6
+ for (let index = branch.length - 1; index >= 0; index--) {
7
+ try {
8
+ const entry = branch[index];
9
+ if (entry?.type === "custom" && entry.customType === SNAPSHOT_ENTRY_TYPE)
10
+ candidates.push(entry.data);
11
+ }
12
+ catch (error) {
13
+ errors.push(error instanceof Error ? error.message : String(error));
14
+ }
15
+ }
16
+ return { candidates, errors };
17
+ }
18
+ export function snapshotDataNewestFirst(branch) {
19
+ return discoverSnapshotData(branch).candidates;
20
+ }
21
+ export function latestSnapshotData(branch) {
22
+ return snapshotDataNewestFirst(branch)[0];
23
+ }
24
+ export function hasPriorConversation(branch) {
25
+ for (const entry of branch) {
26
+ try {
27
+ if (entry.type !== "message")
28
+ continue;
29
+ const role = entry.message?.role;
30
+ if (role === "user" || role === "assistant" || role === "toolResult")
31
+ return true;
32
+ }
33
+ catch {
34
+ // A hostile unrelated entry must not prevent explicit episode startup.
35
+ }
36
+ }
37
+ return false;
38
+ }
39
+ /** Auto-start eligibility is session identity/lifecycle, not the presence of CWD materialization. */
40
+ export function isNewSession(reason, branch) {
41
+ if (reason === "new")
42
+ return true;
43
+ return reason === "startup" && !hasPriorConversation(branch);
44
+ }
@@ -0,0 +1,25 @@
1
+ import { type ArtifactProvenance, type ArtifactRegistry } from "./artifact.ts";
2
+ import type { MaterializedState } from "./state.ts";
3
+ export declare const SKILL_ARTIFACT_COMPILER = "skill-artifact-v1";
4
+ export declare function hasCompiledSkillArtifact(artifacts: ArtifactRegistry, provenance: ArtifactProvenance | undefined, source: string, expectedHash?: string): boolean;
5
+ export type SkillSourceHasher = (source: string) => string;
6
+ export declare function hashSkillSource(source: string): string;
7
+ /** Move the retired contract store into source-addressed Skill artifacts. */
8
+ export declare function migrateLegacySkillCompilations(state: MaterializedState, hasher?: SkillSourceHasher): MaterializedState;
9
+ export declare function skillPathFromRead(toolName: unknown, args: unknown): string | undefined;
10
+ export interface SuccessfulSkillRead {
11
+ path: string;
12
+ hash?: string;
13
+ error?: string;
14
+ }
15
+ /** Correlates Pi's mutable tool lifecycle and captures trusted source identity. */
16
+ export declare class SkillReadTracker {
17
+ #private;
18
+ readonly successful: Map<string, SuccessfulSkillRead>;
19
+ readonly hashSource: SkillSourceHasher;
20
+ constructor(hashSource?: SkillSourceHasher);
21
+ clear(): void;
22
+ recordStart(toolCallId: string, toolName: string, args: unknown): void;
23
+ recordCall(toolCallId: string, toolName: string, input: unknown): void;
24
+ recordEnd(toolCallId: string, toolName: string, isError: boolean): void;
25
+ }
@@ -0,0 +1,131 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { hashArtifactSource, isArtifactHash, } from "./artifact.js";
3
+ import { canonicalJson, isObject } from "./json.js";
4
+ export const SKILL_ARTIFACT_COMPILER = "skill-artifact-v1";
5
+ function hasContent(value) {
6
+ if (typeof value === "string")
7
+ return value.trim().length > 0;
8
+ if (Array.isArray(value))
9
+ return value.length > 0;
10
+ if (isObject(value))
11
+ return Object.keys(value).length > 0;
12
+ return value !== undefined && value !== null;
13
+ }
14
+ export function hasCompiledSkillArtifact(artifacts, provenance, source, expectedHash) {
15
+ const metadata = artifacts[source];
16
+ if (!isObject(metadata)
17
+ || metadata.kind !== "skill"
18
+ || !isObject(metadata.compilation)
19
+ || !hasContent(metadata.compilation)
20
+ || provenance?.malformed === true)
21
+ return false;
22
+ const compilerRevision = provenance !== undefined && Object.hasOwn(provenance, "compilerRevision")
23
+ ? provenance.compilerRevision
24
+ : metadata.compiler;
25
+ const sourceHash = provenance !== undefined && Object.hasOwn(provenance, "sourceHash")
26
+ ? provenance.sourceHash
27
+ : metadata.hash;
28
+ return compilerRevision === SKILL_ARTIFACT_COMPILER
29
+ && (expectedHash === undefined || sourceHash === expectedHash);
30
+ }
31
+ export function hashSkillSource(source) {
32
+ return hashArtifactSource(readFileSync(source));
33
+ }
34
+ function legacyCompilation(value) {
35
+ if (!hasContent(value))
36
+ return undefined;
37
+ return isObject(value) ? structuredClone(value) : { value: structuredClone(value) };
38
+ }
39
+ /** Move the retired contract store into source-addressed Skill artifacts. */
40
+ export function migrateLegacySkillCompilations(state, hasher = hashSkillSource) {
41
+ if (!isObject(state.contract.compiled_skills))
42
+ return structuredClone(state);
43
+ const legacy = state.contract.compiled_skills;
44
+ const next = structuredClone(state);
45
+ delete next.contract.compiled_skills;
46
+ for (const [source, value] of Object.entries(legacy)) {
47
+ const compilation = legacyCompilation(value);
48
+ if (!compilation)
49
+ continue;
50
+ let hash;
51
+ let verified = true;
52
+ try {
53
+ hash = hasher(source);
54
+ if (!isArtifactHash(hash))
55
+ throw new Error("invalid source hash");
56
+ }
57
+ catch {
58
+ // Preserve useful legacy behavior while ensuring a later observable source
59
+ // identity normally invalidates this explicitly unverified fallback.
60
+ hash = hashArtifactSource(canonicalJson({ source, compilation }));
61
+ verified = false;
62
+ }
63
+ const metadata = {
64
+ description: `Compiled operational guidance for the Skill at ${source}`,
65
+ hash,
66
+ compiler: SKILL_ARTIFACT_COMPILER,
67
+ kind: "skill",
68
+ compilation,
69
+ ...(verified ? {} : { source_hash_verified: false }),
70
+ };
71
+ Object.defineProperty(next.artifacts, source, {
72
+ value: metadata,
73
+ enumerable: true,
74
+ configurable: true,
75
+ writable: true,
76
+ });
77
+ }
78
+ return next;
79
+ }
80
+ export function skillPathFromRead(toolName, args) {
81
+ if (toolName !== "read" || !isObject(args) || typeof args.path !== "string")
82
+ return undefined;
83
+ return /(^|[\\/])SKILL\.md$/.test(args.path) ? args.path : undefined;
84
+ }
85
+ /** Correlates Pi's mutable tool lifecycle and captures trusted source identity. */
86
+ export class SkillReadTracker {
87
+ successful = new Map();
88
+ #pending = new Map();
89
+ hashSource;
90
+ constructor(hashSource = hashSkillSource) {
91
+ this.hashSource = hashSource;
92
+ }
93
+ clear() {
94
+ this.successful.clear();
95
+ this.#pending.clear();
96
+ }
97
+ recordStart(toolCallId, toolName, args) {
98
+ this.#record(toolCallId, toolName, args);
99
+ }
100
+ recordCall(toolCallId, toolName, input) {
101
+ this.#record(toolCallId, toolName, input);
102
+ }
103
+ recordEnd(toolCallId, toolName, isError) {
104
+ const pending = this.#pending.get(toolCallId);
105
+ this.#pending.delete(toolCallId);
106
+ if (isError || !pending || toolName !== pending.toolName)
107
+ return;
108
+ const source = skillPathFromRead(pending.toolName, pending.args);
109
+ if (!source)
110
+ return;
111
+ try {
112
+ const hash = this.hashSource(source);
113
+ if (!isArtifactHash(hash))
114
+ throw new Error("hasher returned a non-canonical SHA-256 identity");
115
+ this.successful.set(source, { path: source, hash });
116
+ }
117
+ catch (error) {
118
+ this.successful.set(source, {
119
+ path: source,
120
+ error: error instanceof Error ? error.message : String(error),
121
+ });
122
+ }
123
+ }
124
+ #record(toolCallId, toolName, args) {
125
+ if (toolName !== "read") {
126
+ this.#pending.delete(toolCallId);
127
+ return;
128
+ }
129
+ this.#pending.set(toolCallId, { toolName, args });
130
+ }
131
+ }
@@ -0,0 +1,88 @@
1
+ import { type ArtifactProvenanceRegistry } from "./artifact.ts";
2
+ import { type RemotePublicationPolicyDocument } from "./publication.ts";
3
+ import { type JsonObject } from "./json.ts";
4
+ import { type ScopeStream, type TransitionBoundary } from "./temporal.ts";
5
+ /** Missing operational capability is not evidence that a checkpoint target is invalid. */
6
+ export declare class RevisionUnavailableError extends Error {
7
+ }
8
+ export interface SnapshotConfig {
9
+ enabled: boolean;
10
+ }
11
+ export interface PendingPublicationState {
12
+ commit: string;
13
+ error: string;
14
+ }
15
+ interface LegacyValidationFeedback {
16
+ attempt: number;
17
+ error: string;
18
+ instruction: string;
19
+ }
20
+ export interface SnapshotMeta {
21
+ durableBase?: string;
22
+ pendingPublication?: PendingPublicationState;
23
+ step: number;
24
+ specification?: string;
25
+ /** Read-only compatibility/recovery diagnostic; 0.7 never schedules terminal-envelope retries. */
26
+ validation?: LegacyValidationFeedback;
27
+ bootstrap?: boolean;
28
+ remotePublication?: RemotePublicationPolicyDocument;
29
+ }
30
+ /** In-memory runtime config/provenance; durable config/meta and scope files own restoration. */
31
+ export interface StateFlowSnapshot {
32
+ config: SnapshotConfig;
33
+ meta: SnapshotMeta;
34
+ }
35
+ export type Snapshot = StateFlowSnapshot;
36
+ export interface SessionRuntime {
37
+ config: SnapshotConfig;
38
+ meta: Omit<SnapshotMeta, "durableBase" | "pendingPublication"> & {
39
+ version: 1;
40
+ identity: {
41
+ cwd: string;
42
+ sessionId: string;
43
+ };
44
+ lineage: TransitionBoundary[];
45
+ /** Runtime-owned artifact freshness evidence; never projected as semantic state. */
46
+ artifacts?: ArtifactProvenanceRegistry;
47
+ /** Resolved against the commit that last wrote this runtime record, not arbitrary HEAD. */
48
+ revision: "self";
49
+ temporalRevision?: "self" | string;
50
+ /** Durable intent survives a crash before the push result can be observed. */
51
+ publication: "unconfirmed" | "files";
52
+ temporal?: {
53
+ checkpoint: TransitionBoundary;
54
+ patches: TransitionBoundary[];
55
+ };
56
+ [key: string]: unknown;
57
+ };
58
+ }
59
+ export declare function validateSessionRuntime(value: unknown, cwd: string, sessionId: string): asserts value is SessionRuntime;
60
+ export declare function createSessionRuntime(snapshot: Snapshot, cwd: string, sessionId: string, lineage: readonly TransitionBoundary[], publication?: SessionRuntime["meta"]["publication"], artifacts?: ArtifactProvenanceRegistry): SessionRuntime;
61
+ export declare function serializeSessionRuntime(runtime: SessionRuntime, cwd: string, sessionId: string, stream?: ScopeStream, existingSource?: string): {
62
+ config: string;
63
+ meta: string;
64
+ };
65
+ export declare function parseSessionRuntime(config: string | undefined, meta: string | undefined, cwd: string, sessionId: string): SessionRuntime | undefined;
66
+ export declare function resolveSessionRuntime(runtime: SessionRuntime, revision: string): {
67
+ snapshot: Snapshot;
68
+ lineage: TransitionBoundary[];
69
+ publicationTarget: string;
70
+ artifacts: ArtifactProvenanceRegistry;
71
+ };
72
+ export declare function resolveFileSessionRuntime(runtime: SessionRuntime, revision: string): Snapshot;
73
+ export declare function emptySnapshot(enabled?: boolean): Snapshot;
74
+ export type PiCheckpoint = {
75
+ revision: string;
76
+ } | {
77
+ disabled: true;
78
+ };
79
+ export type FileRevision = `file:${string}`;
80
+ export declare function isFileRevision(value: unknown): value is FileRevision;
81
+ export declare function isDurableRevision(value: unknown): value is string;
82
+ export declare function isExactRevision(value: unknown): value is string;
83
+ export declare function persistableSnapshot(snapshot: Snapshot): PiCheckpoint;
84
+ /** Pi checkpoints admit only strict revision pointers or the disabled marker. */
85
+ export declare function parsePiCheckpoint(value: unknown): PiCheckpoint;
86
+ export declare function migrationFailure(data: JsonObject, error: string): Snapshot;
87
+ export declare function migrateSnapshot(value: unknown): Snapshot;
88
+ export {};
@@ -0,0 +1,255 @@
1
+ import { resolve } from "node:path";
2
+ import { parseArtifactProvenanceRegistry } from "./artifact.js";
3
+ import { parseRemotePublicationPolicyDocument, serializeRemotePublicationPolicyDocument } from "./publication.js";
4
+ import { canonicalJson, isJsonValue, isObject } from "./json.js";
5
+ import { validateTemporalLineage } from "./temporal.js";
6
+ const MAX_RESTORED_STEP = Number.MAX_SAFE_INTEGER - 1;
7
+ const MAX_LEGACY_VALIDATION_ATTEMPT = 7;
8
+ /** Missing operational capability is not evidence that a checkpoint target is invalid. */
9
+ export class RevisionUnavailableError extends Error {
10
+ }
11
+ export function validateSessionRuntime(value, cwd, sessionId) {
12
+ if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "config,meta"
13
+ || !isObject(value.config) || !isObject(value.meta))
14
+ throw new Error("Invalid State Flow session runtime envelope");
15
+ const { version, identity, lineage, revision, temporalRevision, publication, artifacts, temporal: _temporalScope, ...fields } = value.meta;
16
+ if (artifacts !== undefined)
17
+ parseArtifactProvenanceRegistry(artifacts, "State Flow session artifact provenance");
18
+ if (temporalRevision !== undefined && temporalRevision !== "self"
19
+ && !isExactRevision(temporalRevision))
20
+ throw new Error("Invalid temporal revision reference");
21
+ if (version !== 1 || revision !== "self" || (publication !== "unconfirmed" && publication !== "files"))
22
+ throw new Error("Unsupported State Flow runtime provenance format");
23
+ if (publication === "files" && temporalRevision !== undefined && temporalRevision !== "self")
24
+ throw new Error("File runtime cannot select a historical temporal revision");
25
+ if (!isObject(identity) || Object.keys(identity).sort().join(",") !== "cwd,sessionId"
26
+ || identity.cwd !== resolve(cwd) || identity.sessionId !== sessionId || sessionId.trim().length === 0 || sessionId !== sessionId.trim()) {
27
+ throw new Error("State Flow runtime scope identity mismatch");
28
+ }
29
+ validateTemporalLineage(lineage);
30
+ const known = new Set(["step", "specification", "validation", "bootstrap", "remotePublication"]);
31
+ if (Object.keys(fields).some((key) => ["state", "contract", "working", "response"].includes(key))) {
32
+ throw new Error("Semantic state does not belong in State Flow runtime metadata");
33
+ }
34
+ const runtimeFields = Object.fromEntries(Object.entries(fields).filter(([key]) => known.has(key)));
35
+ const { transitionWindow: _retiredWindow, ...supportedConfig } = value.config;
36
+ const normalized = migrateSnapshot({ config: supportedConfig, meta: runtimeFields });
37
+ if (runtimeFields.bootstrap === false)
38
+ normalized.meta.bootstrap = false;
39
+ if (runtimeFields.step === Number.MAX_SAFE_INTEGER)
40
+ normalized.meta.step = Number.MAX_SAFE_INTEGER;
41
+ if (canonicalJson({ config: normalized.config, meta: normalized.meta }) !== canonicalJson({ config: supportedConfig, meta: runtimeFields })) {
42
+ throw new Error("Invalid State Flow runtime configuration or counters");
43
+ }
44
+ }
45
+ export function createSessionRuntime(snapshot, cwd, sessionId, lineage, publication = "unconfirmed", artifacts = {}) {
46
+ const { durableBase: _base, pendingPublication: _publication, ...fields } = snapshot.meta;
47
+ const runtime = {
48
+ config: structuredClone(snapshot.config),
49
+ meta: {
50
+ ...Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined)),
51
+ version: 1,
52
+ identity: { cwd: resolve(cwd), sessionId },
53
+ lineage: structuredClone([...lineage]),
54
+ ...(Object.keys(artifacts).length === 0 ? {} : { artifacts: structuredClone(artifacts) }),
55
+ revision: "self",
56
+ publication,
57
+ },
58
+ };
59
+ validateSessionRuntime(runtime, cwd, sessionId);
60
+ return runtime;
61
+ }
62
+ export function serializeSessionRuntime(runtime, cwd, sessionId, stream, existingSource) {
63
+ validateSessionRuntime(runtime, cwd, sessionId);
64
+ let existing = {};
65
+ if (existingSource !== undefined) {
66
+ try {
67
+ existing = JSON.parse(existingSource);
68
+ }
69
+ catch {
70
+ throw new Error("State Flow session runtime contains invalid JSON");
71
+ }
72
+ if (!isObject(existing) || !isJsonValue(existing))
73
+ throw new Error("Invalid State Flow session runtime metadata");
74
+ }
75
+ const temporal = stream === undefined ? runtime.meta.temporal : {
76
+ checkpoint: structuredClone(stream.checkpoint.through),
77
+ patches: stream.patches.map((record) => structuredClone(record.transition)),
78
+ };
79
+ return { config: `${canonicalJson(runtime.config)}\n`, meta: `${canonicalJson({ ...existing, ...runtime.meta, ...(temporal ? { temporal } : {}) })}\n` };
80
+ }
81
+ export function parseSessionRuntime(config, meta, cwd, sessionId) {
82
+ if (config === undefined && meta === undefined)
83
+ return undefined;
84
+ if (config === undefined && meta !== undefined) {
85
+ let document;
86
+ try {
87
+ document = JSON.parse(meta);
88
+ }
89
+ catch {
90
+ throw new Error("State Flow session runtime contains invalid JSON");
91
+ }
92
+ if (isObject(document) && !Object.hasOwn(document, "identity") && !Object.hasOwn(document, "lineage")
93
+ && Object.keys(document).every((key) => ["version", "artifacts", "temporal", "owner"].includes(key)))
94
+ return undefined;
95
+ }
96
+ if (config === undefined || meta === undefined)
97
+ throw new Error("Incomplete State Flow config/meta pair");
98
+ let runtime;
99
+ try {
100
+ runtime = { config: JSON.parse(config), meta: JSON.parse(meta) };
101
+ }
102
+ catch {
103
+ throw new Error("State Flow session runtime contains invalid JSON");
104
+ }
105
+ validateSessionRuntime(runtime, cwd, sessionId);
106
+ const { temporal: _temporal, ...runtimeMeta } = runtime.meta;
107
+ return { config: { enabled: runtime.config.enabled }, meta: runtimeMeta };
108
+ }
109
+ export function resolveSessionRuntime(runtime, revision) {
110
+ validateSessionRuntime(runtime, runtime.meta.identity.cwd, runtime.meta.identity.sessionId);
111
+ if (!isExactRevision(revision) || runtime.meta.publication !== "unconfirmed")
112
+ throw new Error("Runtime self reference requires its exact Git revision and Git publication provenance");
113
+ const { version: _version, identity: _identity, lineage, revision: _self, temporalRevision: _temporal, publication: _intent, artifacts, ...fields } = runtime.meta;
114
+ return {
115
+ snapshot: { config: structuredClone(runtime.config), meta: { ...structuredClone(fields), durableBase: revision } },
116
+ lineage: structuredClone(lineage),
117
+ publicationTarget: revision,
118
+ artifacts: structuredClone(artifacts ?? {}),
119
+ };
120
+ }
121
+ export function resolveFileSessionRuntime(runtime, revision) {
122
+ validateSessionRuntime(runtime, runtime.meta.identity.cwd, runtime.meta.identity.sessionId);
123
+ if (!isFileRevision(revision) || runtime.meta.publication !== "files")
124
+ throw new Error("File runtime requires its exact file revision and file publication provenance");
125
+ const { version: _version, identity: _identity, lineage: _lineage, revision: _self, temporalRevision: _temporal, publication: _intent, ...fields } = runtime.meta;
126
+ return { config: structuredClone(runtime.config), meta: { ...structuredClone(fields), durableBase: revision } };
127
+ }
128
+ function isLegacyTwoPartState(value) {
129
+ return isObject(value)
130
+ && isObject(value.contract)
131
+ && isObject(value.working)
132
+ && Object.keys(value).every((key) => key === "contract" || key === "working");
133
+ }
134
+ function isLegacyThreePartState(value) {
135
+ return isObject(value)
136
+ && isObject(value.contract)
137
+ && isObject(value.working)
138
+ && typeof value.response === "string"
139
+ && Object.keys(value).every((key) => key === "contract" || key === "working" || key === "response");
140
+ }
141
+ function restoredStep(value) {
142
+ return typeof value === "number"
143
+ && Number.isSafeInteger(value)
144
+ && value >= 0
145
+ && value <= MAX_RESTORED_STEP
146
+ ? value
147
+ : 0;
148
+ }
149
+ function restoredValidation(value) {
150
+ if (!isObject(value)
151
+ || !Number.isSafeInteger(value.attempt)
152
+ || value.attempt < 0
153
+ || value.attempt > MAX_LEGACY_VALIDATION_ATTEMPT
154
+ || typeof value.error !== "string"
155
+ || typeof value.instruction !== "string")
156
+ return undefined;
157
+ return {
158
+ attempt: value.attempt,
159
+ error: value.error,
160
+ instruction: value.instruction,
161
+ };
162
+ }
163
+ function restoredPendingPublication(value) {
164
+ if (!isObject(value)
165
+ || typeof value.commit !== "string"
166
+ || !/^[0-9a-f]{40,64}$/.test(value.commit)
167
+ || typeof value.error !== "string"
168
+ || value.error.trim().length === 0)
169
+ return undefined;
170
+ return { commit: value.commit, error: value.error };
171
+ }
172
+ function restoredMeta(value, legacy = {}) {
173
+ const meta = isObject(value) ? value : legacy;
174
+ const pendingPublication = restoredPendingPublication(meta.pendingPublication);
175
+ const validation = restoredValidation(meta.validation);
176
+ let remotePublication;
177
+ try {
178
+ if (meta.remotePublication !== undefined)
179
+ remotePublication = serializeRemotePublicationPolicyDocument(parseRemotePublicationPolicyDocument(meta.remotePublication, { legacyRuntime: false }));
180
+ }
181
+ catch {
182
+ remotePublication = undefined;
183
+ }
184
+ return {
185
+ ...(isDurableRevision(meta.durableBase)
186
+ ? { durableBase: meta.durableBase }
187
+ : {}),
188
+ ...(pendingPublication === undefined ? {} : { pendingPublication }),
189
+ step: restoredStep(meta.step),
190
+ ...(typeof meta.specification === "string" ? { specification: meta.specification } : {}),
191
+ ...(validation === undefined ? {} : { validation }),
192
+ ...(meta.bootstrap === true ? { bootstrap: true } : {}),
193
+ ...(remotePublication === undefined ? {} : { remotePublication }),
194
+ };
195
+ }
196
+ function envelope(enabled, meta) {
197
+ return { config: { enabled }, meta };
198
+ }
199
+ export function emptySnapshot(enabled = false) {
200
+ return envelope(enabled, { step: 0 });
201
+ }
202
+ export function isFileRevision(value) {
203
+ return typeof value === "string" && /^file:[0-9a-f]{64}$/.test(value);
204
+ }
205
+ export function isDurableRevision(value) {
206
+ return isExactRevision(value) || isFileRevision(value);
207
+ }
208
+ export function isExactRevision(value) {
209
+ return typeof value === "string" && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(value);
210
+ }
211
+ export function persistableSnapshot(snapshot) {
212
+ if (snapshot.meta.durableBase !== undefined) {
213
+ if (!isDurableRevision(snapshot.meta.durableBase))
214
+ throw new Error("Checkpoint requires an exact Git revision or file reference");
215
+ return { revision: snapshot.meta.durableBase };
216
+ }
217
+ if (snapshot.config.enabled)
218
+ throw new Error("Enabled checkpoint requires a durable runtime revision");
219
+ return { disabled: true };
220
+ }
221
+ /** Pi checkpoints admit only strict revision pointers or the disabled marker. */
222
+ export function parsePiCheckpoint(value) {
223
+ if (!isObject(value))
224
+ throw new Error("Invalid State Flow checkpoint");
225
+ if (Object.hasOwn(value, "revision") || Object.hasOwn(value, "disabled")) {
226
+ if (Object.keys(value).length === 1) {
227
+ if (isDurableRevision(value.revision))
228
+ return { revision: value.revision };
229
+ if (value.disabled === true)
230
+ return { disabled: true };
231
+ }
232
+ throw new Error("Invalid State Flow checkpoint pointer or disabled marker");
233
+ }
234
+ throw new Error("Unrecognized State Flow checkpoint");
235
+ }
236
+ export function migrationFailure(data, error) {
237
+ const meta = restoredMeta(data.meta, data);
238
+ meta.validation = {
239
+ attempt: 0,
240
+ error,
241
+ instruction: "Start a fresh State Flow episode; null is reserved for patch deletion.",
242
+ };
243
+ return envelope(false, meta);
244
+ }
245
+ export function migrateSnapshot(value) {
246
+ if (!isObject(value))
247
+ return emptySnapshot();
248
+ const isEnvelope = Object.hasOwn(value, "config") || Object.hasOwn(value, "meta");
249
+ const config = isEnvelope && isObject(value.config) ? value.config : value;
250
+ const meta = restoredMeta(isEnvelope ? value.meta : undefined, value);
251
+ const enabled = config.enabled === true;
252
+ if (Object.hasOwn(value, "state") || Object.hasOwn(value, "stateBasis") || Object.hasOwn(value, "previousStatePatch"))
253
+ return envelope(false, meta);
254
+ return envelope(enabled, meta);
255
+ }
@@ -0,0 +1,55 @@
1
+ import { type ArtifactCompilationUpdate, type ArtifactRegistry } from "./artifact.ts";
2
+ import { type JsonObject } from "./json.ts";
3
+ /** The canonical semantic state shape shared by global, CWD, and session scopes. */
4
+ export interface MaterializedState extends JsonObject {
5
+ artifacts: ArtifactRegistry;
6
+ contract: JsonObject;
7
+ working: JsonObject;
8
+ response: string;
9
+ }
10
+ /** Compatibility name for callers that still treat materialized state as a document. */
11
+ export type StateDocument = MaterializedState;
12
+ /** Model patch shape; artifact entries may be compiler outputs before trusted metadata is attached. */
13
+ export interface StatePatch extends JsonObject {
14
+ artifacts: JsonObject;
15
+ contract: JsonObject;
16
+ working: JsonObject;
17
+ response: string;
18
+ }
19
+ export type StateScope = "global" | "cwd" | "session";
20
+ /** A model-authored patch for one scope. Response is captured by the runtime in session state. */
21
+ export interface ScopePatch {
22
+ artifacts?: JsonObject;
23
+ contract?: JsonObject;
24
+ working?: JsonObject;
25
+ }
26
+ export interface ScopedPatch {
27
+ scope: StateScope;
28
+ patch: ScopePatch;
29
+ }
30
+ /** Canonical model-authored scope cohort before runtime final-eligibility handling. */
31
+ export interface AtomicScopePatches {
32
+ global?: ScopePatch;
33
+ cwd?: ScopePatch;
34
+ session?: ScopePatch;
35
+ }
36
+ export interface SemanticTransition {
37
+ transitions: ScopedPatch[];
38
+ }
39
+ export interface TerminalTransition extends SemanticTransition {
40
+ response: string;
41
+ }
42
+ export interface ScopedStates {
43
+ global: MaterializedState;
44
+ cwd: MaterializedState;
45
+ session: MaterializedState;
46
+ }
47
+ export declare function emptyState(): MaterializedState;
48
+ export declare function isMaterializedState(value: unknown): value is MaterializedState;
49
+ export declare const isStateDocument: typeof isMaterializedState;
50
+ /** Atomically replace compiled and removed artifacts inside one materialized scope. */
51
+ export declare function updateMaterializedArtifacts(state: MaterializedState, updates: readonly ArtifactCompilationUpdate[], removed?: readonly string[]): MaterializedState;
52
+ /** Overlay lower-to-higher scopes without mutating any scope document. */
53
+ export declare function overlayStates(...scopes: readonly MaterializedState[]): MaterializedState;
54
+ /** Model-visible projection: runtime artifact bookkeeping never reaches ordinary context. */
55
+ export declare function projectStateForModel(state: MaterializedState): MaterializedState;
@@ -0,0 +1,31 @@
1
+ import { isArtifactRegistry, projectArtifactsForModel, updateArtifactRegistry, } from "./artifact.js";
2
+ import { applyPatch, isObject } from "./json.js";
3
+ export function emptyState() {
4
+ return { artifacts: {}, contract: {}, working: {}, response: "" };
5
+ }
6
+ export function isMaterializedState(value) {
7
+ return isObject(value)
8
+ && isArtifactRegistry(value.artifacts)
9
+ && isObject(value.contract)
10
+ && isObject(value.working)
11
+ && typeof value.response === "string"
12
+ && Object.keys(value).every((key) => key === "artifacts" || key === "contract" || key === "working" || key === "response");
13
+ }
14
+ export const isStateDocument = isMaterializedState;
15
+ /** Atomically replace compiled and removed artifacts inside one materialized scope. */
16
+ export function updateMaterializedArtifacts(state, updates, removed = []) {
17
+ if (!isMaterializedState(state))
18
+ throw new Error("Cannot update artifacts in an invalid materialized state");
19
+ const artifacts = updateArtifactRegistry(state.artifacts, updates, removed);
20
+ return { ...structuredClone(state), artifacts };
21
+ }
22
+ /** Overlay lower-to-higher scopes without mutating any scope document. */
23
+ export function overlayStates(...scopes) {
24
+ return scopes.reduce((effective, scope) => {
25
+ return applyPatch(effective, scope);
26
+ }, emptyState());
27
+ }
28
+ /** Model-visible projection: runtime artifact bookkeeping never reaches ordinary context. */
29
+ export function projectStateForModel(state) {
30
+ return { ...structuredClone(state), artifacts: projectArtifactsForModel(state.artifacts) };
31
+ }