@llblab/pi-kit 0.37.0 → 0.38.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 +10 -0
- package/README.md +3 -3
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +7 -3
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +679 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +17 -0
- package/node_modules/@llblab/pi-state-flow/README.md +30 -14
- package/node_modules/@llblab/pi-state-flow/api/modules.ts +11 -0
- package/node_modules/@llblab/pi-state-flow/dist/api/modules.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/api/modules.js +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +7 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +77 -36
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +26 -27
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +13 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +27 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +144 -46
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +14 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +60 -50
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +1049 -310
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +15 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +3 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +7 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +186 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.d.ts +17 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.js +158 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.d.ts +21 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.js +108 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/operation.js +3 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.d.ts +4 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.js +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/planes.d.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/planes.js +84 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +6 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +33 -16
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +21 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +53 -13
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +5 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +10 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +84 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +570 -20
- package/node_modules/@llblab/pi-state-flow/dist/lib/selection.d.ts +10 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/selection.js +32 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.d.ts +79 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.js +187 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +98 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +398 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +7 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +55 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +67 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +129 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +26 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +61 -19
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +14 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +59 -22
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +111 -10
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +728 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +10 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +50 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +40 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +247 -43
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +41 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +132 -39
- package/node_modules/@llblab/pi-state-flow/dist/package.json +6 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +28 -16
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +12 -8
- package/node_modules/@llblab/pi-state-flow/docs/README.md +9 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +76 -43
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +9 -1
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +6 -0
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +23 -19
- package/node_modules/@llblab/pi-state-flow/docs/protocol-composition-decision.md +66 -0
- package/node_modules/@llblab/pi-state-flow/docs/public-module-contract.md +73 -0
- package/node_modules/@llblab/pi-state-flow/docs/scope-selection-contract.md +814 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-compatibility-decision.md +212 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-extensibility-decision.md +75 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-handoff-contract.md +174 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-memory-evaluation.md +166 -0
- package/node_modules/@llblab/pi-state-flow/docs/semantic-module-contract.md +84 -0
- package/node_modules/@llblab/pi-state-flow/docs/single-scope-interface-contract.md +201 -0
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +22 -1
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +32 -18
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +68 -31
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +21 -22
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +14 -1
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +142 -46
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +14 -17
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +53 -44
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +897 -297
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +16 -7
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +10 -9
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +189 -0
- package/node_modules/@llblab/pi-state-flow/lib/model-interface.ts +136 -0
- package/node_modules/@llblab/pi-state-flow/lib/module-registry.ts +119 -0
- package/node_modules/@llblab/pi-state-flow/lib/operation.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/lib/ownership.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/lib/planes.ts +98 -0
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +37 -16
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +59 -18
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +5 -2
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +10 -1
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +574 -21
- package/node_modules/@llblab/pi-state-flow/lib/selection.ts +39 -0
- package/node_modules/@llblab/pi-state-flow/lib/semantic-module.ts +239 -0
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +387 -3
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +45 -4
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +156 -16
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +69 -28
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +65 -31
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +770 -18
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +58 -20
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +267 -50
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +184 -52
- package/node_modules/@llblab/pi-state-flow/package.json +6 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +28 -16
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +12 -8
- package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +8 -10
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +10 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +20 -18
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +6 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +23 -65
- package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/prompts.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/prompts.js +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/runtime.js +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +4 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +24 -13
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/SKILL.md +5 -5
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +5 -5
- package/node_modules/@llblab/pi-telegram/docs/compact-matrix-literal.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +8 -8
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +2 -2
- package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +17 -15
- package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +28 -68
- package/node_modules/@llblab/pi-telegram/lib/outbound.ts +3 -3
- package/node_modules/@llblab/pi-telegram/lib/prompts.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/runtime.ts +2 -1
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +25 -12
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/SKILL.md +5 -5
- package/package.json +3 -3
|
@@ -4,17 +4,16 @@ import type { RecentTransitionWindow } from "./history.ts";
|
|
|
4
4
|
import { type JsonValue } from "./json.ts";
|
|
5
5
|
import type { Snapshot } from "./snapshot.ts";
|
|
6
6
|
import { type OwnedPath } from "./ownership.ts";
|
|
7
|
+
import { type ScopeSelection } from "./selection.ts";
|
|
7
8
|
import type { RehydrationPhase } from "./rehydration.ts";
|
|
8
9
|
import { type AtomicScopePatches, type SemanticState, type StateScope } from "./state.ts";
|
|
10
|
+
import { type EvaluatedModuleContext, type ModuleFrame, type SemanticModule } from "./semantic-module.ts";
|
|
9
11
|
/** Refresh only our section; Pi owns system frames, tools and forced-prompt precedence. */
|
|
10
12
|
export declare function projectSystemProtocol(messages: AgentMessage[], protocol: string | undefined): AgentMessage[];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
path: string;
|
|
16
|
-
keys?: Record<string, LazyValueKind>;
|
|
17
|
-
};
|
|
13
|
+
/** Compatibility facade; the lazy plane owns the unchanged navigation policy. */
|
|
14
|
+
export declare function lazyNavigationHint(state: SemanticState): import("./planes.ts").LazyNavigation;
|
|
15
|
+
/** Materialize module contributions only; native context selection/replacement authority stays with the host. */
|
|
16
|
+
export declare function semanticModuleMessages<Content extends JsonValue, Input>(module: SemanticModule<Content, Input>, content: unknown, frame: ModuleFrame<Input>): AgentMessage[];
|
|
18
17
|
export type ModelStateUpdate = {
|
|
19
18
|
path: (string | number)[];
|
|
20
19
|
} & ({
|
|
@@ -25,13 +24,11 @@ export type ModelStateUpdate = {
|
|
|
25
24
|
/** Exact projected replacements, not authored merge patches; paths are unambiguous key/index segments. */
|
|
26
25
|
export declare function acceptedStateUpdates(before: SemanticState, after: SemanticState, patches: AtomicScopePatches): {
|
|
27
26
|
effective: ModelStateUpdate[];
|
|
28
|
-
lazy_navigation?:
|
|
29
|
-
available: boolean;
|
|
30
|
-
path: string;
|
|
31
|
-
keys?: Record<string, LazyValueKind>;
|
|
32
|
-
} | undefined;
|
|
27
|
+
lazy_navigation?: import("./planes.ts").LazyNavigation | undefined;
|
|
33
28
|
};
|
|
34
29
|
export interface ContextView {
|
|
30
|
+
/** Wanted presentation DATA only; never infer it from available state or use it as authority. */
|
|
31
|
+
modelScopes?: ScopeSelection;
|
|
35
32
|
state: SemanticState;
|
|
36
33
|
lazy_navigation?: ReturnType<typeof lazyNavigationHint>;
|
|
37
34
|
artifact_invalidations: readonly ArtifactInvalidationNotice[];
|
|
@@ -39,7 +36,9 @@ export interface ContextView {
|
|
|
39
36
|
phase: RehydrationPhase;
|
|
40
37
|
} | null;
|
|
41
38
|
}
|
|
42
|
-
export declare function contextView(state: SemanticState, hints: ArtifactModelHints, invalidations: readonly ArtifactInvalidationNotice[], phase?: RehydrationPhase, includeResponse?: boolean): ContextView;
|
|
39
|
+
export declare function contextView(state: SemanticState, hints: ArtifactModelHints, invalidations: readonly ArtifactInvalidationNotice[], phase?: RehydrationPhase, includeResponse?: boolean, modelScopes?: ScopeSelection): ContextView;
|
|
40
|
+
/** Same owned passive envelope as before, with presentation chosen from captured wanted DATA. */
|
|
41
|
+
export declare function passiveContextHead(view: ContextView): AgentMessage;
|
|
43
42
|
/** Volatile model projection only. Native messages own trajectory; this cache owns no persistence or lifecycle. */
|
|
44
43
|
export declare class ContextProjection {
|
|
45
44
|
private identity;
|
|
@@ -49,13 +48,9 @@ export declare class ContextProjection {
|
|
|
49
48
|
private notices;
|
|
50
49
|
reset(): void;
|
|
51
50
|
/** Called only after successful publication and ancillary acceptance, immediately before returning the native result. */
|
|
52
|
-
acceptPatch(before: SemanticState, after: SemanticState, patches: AtomicScopePatches, hints: ArtifactModelHints, cascades?: Partial<Record<StateScope, readonly OwnedPath[]>>, includeResponse?: boolean): {
|
|
51
|
+
acceptPatch(before: SemanticState, after: SemanticState, patches: AtomicScopePatches, hints: ArtifactModelHints, cascades?: Partial<Record<StateScope, readonly OwnedPath[]>>, includeResponse?: boolean, modelScopes?: ScopeSelection): {
|
|
53
52
|
effective: ModelStateUpdate[];
|
|
54
|
-
lazy_navigation?:
|
|
55
|
-
available: boolean;
|
|
56
|
-
path: string;
|
|
57
|
-
keys?: Record<string, LazyValueKind>;
|
|
58
|
-
} | undefined;
|
|
53
|
+
lazy_navigation?: import("./planes.ts").LazyNavigation | undefined;
|
|
59
54
|
projection: `${string}-${string}-${string}-${string}-${string}`;
|
|
60
55
|
cascaded?: string[] | undefined;
|
|
61
56
|
} | undefined;
|
|
@@ -70,7 +65,19 @@ export interface PassiveContinuation {
|
|
|
70
65
|
state: SemanticState;
|
|
71
66
|
}
|
|
72
67
|
export declare function syntheticUser(text: string): AgentMessage;
|
|
73
|
-
|
|
68
|
+
/** A unique native user anchor admits completed conversation, never the current run or protocol frames. */
|
|
69
|
+
export declare function moduleConversationSource(messages: readonly AgentMessage[], anchorTimestamp: number | undefined): {
|
|
70
|
+
conversation: readonly AgentMessage[];
|
|
71
|
+
prompt?: string;
|
|
72
|
+
};
|
|
73
|
+
export interface ScopedModuleContext {
|
|
74
|
+
id: string;
|
|
75
|
+
scope: StateScope;
|
|
76
|
+
contributions: readonly EvaluatedModuleContext[];
|
|
77
|
+
}
|
|
78
|
+
/** Provider projection only: preserve current/foreign identities and allow one explicit completed-history selector. */
|
|
79
|
+
export declare function projectModuleContext(projected: AgentMessage[], original: readonly AgentMessage[], conversation: readonly AgentMessage[], modules: readonly ScopedModuleContext[]): AgentMessage[];
|
|
80
|
+
export declare function createPassiveContinuation(state: SemanticState, startedAt?: number, activeRunStartedAt?: number, preserveContext?: boolean, modelScopes?: ScopeSelection): PassiveContinuation;
|
|
74
81
|
/** Keep the interrupted run through later results; an idle stop retains only later conversation. */
|
|
75
82
|
export declare function passiveContinuationMessages(messages: AgentMessage[], continuation: PassiveContinuation): AgentMessage[];
|
|
76
83
|
export declare function runtimeContextMessage(snapshot: Snapshot, state: SemanticState, recentTransitions?: RecentTransitionWindow, artifactInvalidations?: readonly ArtifactInvalidationNotice[], rehydrationPhase?: RehydrationPhase, artifactHints?: ArtifactModelHints): AgentMessage;
|
|
@@ -81,4 +88,3 @@ export declare function currentRunTrajectory(messages: AgentMessage[], specifica
|
|
|
81
88
|
messages: AgentMessage[];
|
|
82
89
|
anchorTimestamp?: number;
|
|
83
90
|
};
|
|
84
|
-
export {};
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { projectArtifactForModel } from "./artifact.js";
|
|
3
|
-
import { applyPatch, isObject, presentationJson, sameJson } from "./json.js";
|
|
3
|
+
import { applyPatch, isObject, presentationJson, pruneEmptyObjects, sameJson } from "./json.js";
|
|
4
4
|
import { formatOwnedPath } from "./ownership.js";
|
|
5
|
-
import {
|
|
5
|
+
import { projectModelReferences } from "./model-interface.js";
|
|
6
|
+
import { captureScopeSelection, DEFAULT_SCOPE_SELECTION } from "./selection.js";
|
|
7
|
+
import { projectSemanticPatch, projectSemanticState, projectStateForModel } from "./state.js";
|
|
8
|
+
import { lazyValueKind, projectLazyNavigation, projectPlaneContext } from "./planes.js";
|
|
9
|
+
import { evaluateModuleContext } from "./semantic-module.js";
|
|
6
10
|
/** Refresh only our section; Pi owns system frames, tools and forced-prompt precedence. */
|
|
7
11
|
export function projectSystemProtocol(messages, protocol) {
|
|
8
12
|
let lastSystem = -1;
|
|
@@ -31,31 +35,15 @@ export function projectSystemProtocol(messages, protocol) {
|
|
|
31
35
|
return { ...message, sections };
|
|
32
36
|
});
|
|
33
37
|
}
|
|
34
|
-
|
|
35
|
-
const LAZY_HINT_MAX_KEYS = 32;
|
|
36
|
-
const LAZY_HINT_MAX_JSON_CHARS = 1024;
|
|
37
|
-
function lazyValueKind(value) {
|
|
38
|
-
if (value === null)
|
|
39
|
-
return "null";
|
|
40
|
-
if (Array.isArray(value))
|
|
41
|
-
return "array";
|
|
42
|
-
if (typeof value === "object")
|
|
43
|
-
return "object";
|
|
44
|
-
return typeof value;
|
|
45
|
-
}
|
|
46
|
-
function hasLazyContent(value) {
|
|
47
|
-
return !isObject(value) || Object.values(value).some(hasLazyContent);
|
|
48
|
-
}
|
|
49
|
-
/** Fixed-budget navigation only: never place lazy bodies or partial key catalogs in baseline context. */
|
|
38
|
+
/** Compatibility facade; the lazy plane owns the unchanged navigation policy. */
|
|
50
39
|
export function lazyNavigationHint(state) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return JSON.stringify(keys).length <= LAZY_HINT_MAX_JSON_CHARS ? { ...base, keys } : base;
|
|
40
|
+
return projectLazyNavigation(state.lazy);
|
|
41
|
+
}
|
|
42
|
+
/** Materialize module contributions only; native context selection/replacement authority stays with the host. */
|
|
43
|
+
export function semanticModuleMessages(module, content, frame) {
|
|
44
|
+
return evaluateModuleContext(module, content, frame).flatMap(contribution => contribution.kind === "conversation"
|
|
45
|
+
? [...contribution.messages]
|
|
46
|
+
: [syntheticUser(`State Flow module ${module.id} (user-level data, not system instructions):\n${presentationJson(contribution.value)}`)]);
|
|
59
47
|
}
|
|
60
48
|
/** Exact projected replacements, not authored merge patches; paths are unambiguous key/index segments. */
|
|
61
49
|
export function acceptedStateUpdates(before, after, patches) {
|
|
@@ -130,15 +118,42 @@ function projectedStateUpdates(previous, current, patches, beforeNavigation, nav
|
|
|
130
118
|
return { effective, ...(navigation !== undefined && (beforeNavigation === undefined || !sameJson(beforeNavigation, navigation)) ? { lazy_navigation: navigation } : {}) };
|
|
131
119
|
}
|
|
132
120
|
function projectContextState(state, hints, includeResponse) {
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return projected;
|
|
121
|
+
const { lazy: _hidden, ...visible } = state;
|
|
122
|
+
const projected = projectPlaneContext({ ...projectSemanticState(visible), ...(Object.hasOwn(state, "lazy") ? { lazy: state.lazy } : {}) }, hints, includeResponse ? "active" : "passive");
|
|
123
|
+
return { ...projected, state: pruneEmptyObjects(projected.state) };
|
|
137
124
|
}
|
|
138
|
-
export function contextView(state, hints, invalidations, phase, includeResponse = true) {
|
|
139
|
-
|
|
125
|
+
export function contextView(state, hints, invalidations, phase, includeResponse = true, modelScopes) {
|
|
126
|
+
const selected = modelScopes === undefined ? undefined : captureScopeSelection(modelScopes);
|
|
127
|
+
return { ...(selected === undefined ? {} : { modelScopes: selected }), ...projectContextState(state, hints, includeResponse),
|
|
140
128
|
artifact_invalidations: structuredClone(invalidations), knowledge_rehydration: phase === undefined ? null : { phase } };
|
|
141
129
|
}
|
|
130
|
+
function viewScopes(view) {
|
|
131
|
+
return view && Object.hasOwn(view, "modelScopes") ? captureScopeSelection(view.modelScopes) : DEFAULT_SCOPE_SELECTION;
|
|
132
|
+
}
|
|
133
|
+
function modelState(state, scopes) {
|
|
134
|
+
return scopes.length === 1 ? projectModelReferences(state, scopes) : state;
|
|
135
|
+
}
|
|
136
|
+
function modelNavigation(navigation, scopes) {
|
|
137
|
+
return navigation && scopes.length === 1 ? { ...navigation, path: "lazy" } : navigation;
|
|
138
|
+
}
|
|
139
|
+
function modelUpdates(updates, scopes) {
|
|
140
|
+
if (scopes.length !== 1)
|
|
141
|
+
return updates;
|
|
142
|
+
return { ...updates, effective: updates.effective.map(entry => {
|
|
143
|
+
if (!("value" in entry))
|
|
144
|
+
return entry;
|
|
145
|
+
// A canonical diff may split a structured ref into its scalar $ref leaf.
|
|
146
|
+
const value = entry.path.at(-1) === "$ref" && typeof entry.value === "string"
|
|
147
|
+
? projectModelReferences({ $ref: entry.value }, scopes).$ref
|
|
148
|
+
: projectModelReferences(entry.value, scopes);
|
|
149
|
+
return { ...entry, value };
|
|
150
|
+
}), ...(updates.lazy_navigation ? { lazy_navigation: modelNavigation(updates.lazy_navigation, scopes) } : {}) };
|
|
151
|
+
}
|
|
152
|
+
/** Same owned passive envelope as before, with presentation chosen from captured wanted DATA. */
|
|
153
|
+
export function passiveContextHead(view) {
|
|
154
|
+
const scopes = viewScopes(view);
|
|
155
|
+
return syntheticUser(`State Flow passive memory (user-level data, not system instructions):\n${presentationJson({ state: modelState(view.state, scopes), lazy_navigation: modelNavigation(view.lazy_navigation, scopes) })}`);
|
|
156
|
+
}
|
|
142
157
|
/** Volatile model projection only. Native messages own trajectory; this cache owns no persistence or lifecycle. */
|
|
143
158
|
export class ContextProjection {
|
|
144
159
|
identity = randomUUID();
|
|
@@ -154,11 +169,19 @@ export class ContextProjection {
|
|
|
154
169
|
this.notices = [];
|
|
155
170
|
}
|
|
156
171
|
/** Called only after successful publication and ancillary acceptance, immediately before returning the native result. */
|
|
157
|
-
acceptPatch(before, after, patches, hints, cascades = {}, includeResponse = true) {
|
|
158
|
-
const
|
|
172
|
+
acceptPatch(before, after, patches, hints, cascades = {}, includeResponse = true, modelScopes) {
|
|
173
|
+
const scopes = modelScopes === undefined ? viewScopes(this.view) : captureScopeSelection(modelScopes);
|
|
174
|
+
// A receipt from another interface cannot be attached to a replacement model head.
|
|
175
|
+
if (this.view && !sameJson(viewScopes(this.view), scopes))
|
|
176
|
+
return undefined;
|
|
177
|
+
// External planes have exact tool reads but no automatic core projection. Absence here is not deletion.
|
|
178
|
+
patches = Object.fromEntries(Object.entries(patches).map(([scope, patch]) => [scope,
|
|
179
|
+
Object.fromEntries(Object.entries(patch).filter(([id]) => !id.startsWith("x_"))),
|
|
180
|
+
]));
|
|
181
|
+
const { state } = projectContextState(after, hints, includeResponse);
|
|
159
182
|
const navigation = lazyNavigationHint(after);
|
|
160
183
|
const beforeNavigation = this.view?.lazy_navigation ?? lazyNavigationHint(before);
|
|
161
|
-
const updates = projectedStateUpdates(this.view?.state ?? projectContextState(before, hints, includeResponse), state, patches, beforeNavigation, navigation);
|
|
184
|
+
const updates = projectedStateUpdates(this.view?.state ?? projectContextState(before, hints, includeResponse).state, state, patches, beforeNavigation, navigation);
|
|
162
185
|
// Suppress direct writes only when the accepted effective value matches.
|
|
163
186
|
// Overlap stays conservative except for explicit top-scope replacements:
|
|
164
187
|
// Session scalars/arrays mask every lower-scope value at that path.
|
|
@@ -266,11 +289,14 @@ export class ContextProjection {
|
|
|
266
289
|
this.view = { ...this.view, state, lazy_navigation: navigation };
|
|
267
290
|
// Owner paths remain informative even when effective state/navigation is masked
|
|
268
291
|
// or the model could predict every direct write. Never expose target values.
|
|
269
|
-
const cascaded =
|
|
292
|
+
const cascaded = scopes.flatMap(scope => (cascades[scope] ?? []).map(path => scopes.length === 1 ? [path.plane, ...path.keys].join(".") : formatOwnedPath(scope, path)));
|
|
270
293
|
return updates.effective.length || updates.lazy_navigation || cascaded.length
|
|
271
|
-
? { projection: this.identity, ...updates, ...(cascaded.length ? { cascaded } : {}) } : undefined;
|
|
294
|
+
? { projection: this.identity, ...modelUpdates(updates, scopes), ...(cascaded.length ? { cascaded } : {}) } : undefined;
|
|
272
295
|
}
|
|
273
296
|
project(messages, current, makeHead, initial) {
|
|
297
|
+
const scopes = viewScopes(current);
|
|
298
|
+
if (this.view && !sameJson(viewScopes(this.view), scopes))
|
|
299
|
+
this.reset();
|
|
274
300
|
const identities = messages.map((message) => JSON.stringify([message.role, message.timestamp,
|
|
275
301
|
"toolCallId" in message ? message.toolCallId : null]));
|
|
276
302
|
// Native compaction/selection normally resets explicitly; a removed/replaced prefix is also a safe cache boundary.
|
|
@@ -286,7 +312,7 @@ export class ContextProjection {
|
|
|
286
312
|
const previous = this.view;
|
|
287
313
|
const updates = projectedStateUpdates(previous.state, current.state, {}, previous.lazy_navigation, current.lazy_navigation);
|
|
288
314
|
const notice = {
|
|
289
|
-
...(updates.effective.length || updates.lazy_navigation ? { state_updates: { projection: this.identity, ...updates } } : {}),
|
|
315
|
+
...(updates.effective.length || updates.lazy_navigation ? { state_updates: { projection: this.identity, ...modelUpdates(updates, scopes) } } : {}),
|
|
290
316
|
...(!sameJson(previous.artifact_invalidations, current.artifact_invalidations) ? { artifact_invalidations: current.artifact_invalidations } : {}),
|
|
291
317
|
...(!sameJson(previous.knowledge_rehydration, current.knowledge_rehydration) ? { knowledge_rehydration: current.knowledge_rehydration } : {}),
|
|
292
318
|
};
|
|
@@ -324,7 +350,70 @@ function contentText(content) {
|
|
|
324
350
|
function messageText(message) {
|
|
325
351
|
return contentText(message.content);
|
|
326
352
|
}
|
|
327
|
-
|
|
353
|
+
/** A unique native user anchor admits completed conversation, never the current run or protocol frames. */
|
|
354
|
+
export function moduleConversationSource(messages, anchorTimestamp) {
|
|
355
|
+
if (anchorTimestamp === undefined || !Number.isFinite(anchorTimestamp))
|
|
356
|
+
return { conversation: [] };
|
|
357
|
+
const matches = messages.flatMap((message, index) => message.role === "user" && message.timestamp === anchorTimestamp ? [index] : []);
|
|
358
|
+
if (matches.length !== 1)
|
|
359
|
+
return { conversation: [] };
|
|
360
|
+
const start = matches[0];
|
|
361
|
+
return { conversation: messages.slice(0, start).filter(message => ["user", "assistant", "toolResult"].includes(message.role)), prompt: messageText(messages[start]) };
|
|
362
|
+
}
|
|
363
|
+
function assertModuleToolUnits(messages) {
|
|
364
|
+
const pending = new Map(), seen = new Set();
|
|
365
|
+
for (const message of messages) {
|
|
366
|
+
if (message.role === "toolResult") {
|
|
367
|
+
if (!pending.has(message.toolCallId) || pending.get(message.toolCallId) !== message.toolName)
|
|
368
|
+
throw new Error("Module conversation selection has an orphan tool result");
|
|
369
|
+
pending.delete(message.toolCallId);
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
if (pending.size)
|
|
373
|
+
throw new Error("Module conversation selection omits a tool result");
|
|
374
|
+
if (message.role !== "assistant")
|
|
375
|
+
continue;
|
|
376
|
+
for (const part of message.content)
|
|
377
|
+
if (part.type === "toolCall") {
|
|
378
|
+
if (!part.id || !part.name || seen.has(part.id))
|
|
379
|
+
throw new Error("Module conversation selection has duplicate or invalid tool calls");
|
|
380
|
+
seen.add(part.id);
|
|
381
|
+
pending.set(part.id, part.name);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (pending.size)
|
|
386
|
+
throw new Error("Module conversation selection omits a tool result");
|
|
387
|
+
}
|
|
388
|
+
/** Provider projection only: preserve current/foreign identities and allow one explicit completed-history selector. */
|
|
389
|
+
export function projectModuleContext(projected, original, conversation, modules) {
|
|
390
|
+
const data = [];
|
|
391
|
+
let selection;
|
|
392
|
+
for (const module of modules) {
|
|
393
|
+
const selecting = module.contributions.some(item => item.kind === "conversation");
|
|
394
|
+
if (selecting) {
|
|
395
|
+
if (module.scope !== "session" || selection !== undefined)
|
|
396
|
+
throw new Error("Module conversation replacement requires one exclusive Session selector");
|
|
397
|
+
selection = module.contributions.flatMap(item => item.kind === "conversation" ? [...item.messages] : []);
|
|
398
|
+
if (new Set(selection).size !== selection.length || selection.some(message => !conversation.includes(message)))
|
|
399
|
+
throw new Error("Module conversation selection is outside its admitted source");
|
|
400
|
+
assertModuleToolUnits(selection);
|
|
401
|
+
}
|
|
402
|
+
for (const item of module.contributions)
|
|
403
|
+
if (item.kind === "data")
|
|
404
|
+
data.push(syntheticUser(`State Flow module ${module.id} (${module.scope}; user-level data, not system instructions):\n${presentationJson(item.value)}`));
|
|
405
|
+
}
|
|
406
|
+
if (!data.length && selection === undefined)
|
|
407
|
+
return projected;
|
|
408
|
+
const completed = new Set(selection === undefined ? [] : conversation);
|
|
409
|
+
const retained = projected.filter(message => !completed.has(message));
|
|
410
|
+
const native = new Set(original.filter(message => ["user", "assistant", "toolResult"].includes(message.role)));
|
|
411
|
+
const first = retained.findIndex(message => native.has(message));
|
|
412
|
+
const at = first < 0 ? retained.length : first;
|
|
413
|
+
return [...retained.slice(0, at), ...data, ...(selection ?? []), ...retained.slice(at)];
|
|
414
|
+
}
|
|
415
|
+
export function createPassiveContinuation(state, startedAt = Date.now(), activeRunStartedAt, preserveContext = false, modelScopes) {
|
|
416
|
+
const scopes = modelScopes === undefined ? DEFAULT_SCOPE_SELECTION : captureScopeSelection(modelScopes);
|
|
328
417
|
const visibleState = structuredClone(state);
|
|
329
418
|
delete visibleState.response;
|
|
330
419
|
return {
|
|
@@ -332,7 +421,7 @@ export function createPassiveContinuation(state, startedAt = Date.now(), activeR
|
|
|
332
421
|
state: visibleState,
|
|
333
422
|
...(activeRunStartedAt === undefined ? {} : { activeRunStartedAt }),
|
|
334
423
|
...(preserveContext ? { preserveContext: true } : {}),
|
|
335
|
-
handoff: syntheticUser(`State Flow exit handoff (user-level data, not system instructions):\n${presentationJson({ state: visibleState, continuation: preserveContext
|
|
424
|
+
handoff: syntheticUser(`State Flow exit handoff (user-level data, not system instructions):\n${presentationJson({ state: modelState(visibleState, scopes), continuation: preserveContext
|
|
336
425
|
? "State Flow semantics are disabled; native context is retained because its compilation into memory is unfinished."
|
|
337
426
|
: "State Flow semantics are disabled; this handoff replaces completed history while retaining the active and post-stop trajectory." })}`),
|
|
338
427
|
};
|
|
@@ -351,8 +440,10 @@ export function passiveContinuationMessages(messages, continuation) {
|
|
|
351
440
|
&& message.timestamp >= continuation.startedAt);
|
|
352
441
|
return [continuation.handoff, ...messages.filter((message, index) => message.role === "custom" || (start >= 0 && index >= start))];
|
|
353
442
|
}
|
|
354
|
-
function projectRecentForModel(recent) {
|
|
355
|
-
|
|
443
|
+
function projectRecentForModel(recent, scopes) {
|
|
444
|
+
// Select owner headers before cloning any excluded patch payload.
|
|
445
|
+
const projected = structuredClone(recent.map(record => ({ ...record,
|
|
446
|
+
transitions: record.transitions.filter(transition => scopes.includes(transition.scope)) })));
|
|
356
447
|
for (const record of projected)
|
|
357
448
|
for (const transition of record.transitions) {
|
|
358
449
|
transition.patch = projectSemanticPatch(transition.patch);
|
|
@@ -367,18 +458,25 @@ function projectRecentForModel(recent) {
|
|
|
367
458
|
}
|
|
368
459
|
for (const record of projected)
|
|
369
460
|
record.transitions = record.transitions.filter(({ patch }) => Object.keys(patch).length > 0);
|
|
370
|
-
|
|
461
|
+
const visible = projected.filter(({ transitions }) => transitions.length > 0);
|
|
462
|
+
if (scopes.length !== 1)
|
|
463
|
+
return visible;
|
|
464
|
+
return visible.map(record => {
|
|
465
|
+
if (record.transitions.length !== 1)
|
|
466
|
+
throw new Error("Flat State Flow recent history requires one patch per captured owner");
|
|
467
|
+
return { id: record.id, at: record.at, patch: projectModelReferences(record.transitions[0].patch, scopes) };
|
|
468
|
+
});
|
|
371
469
|
}
|
|
372
470
|
export function runtimeContextMessage(snapshot, state, recentTransitions = [], artifactInvalidations = [], rehydrationPhase, artifactHints = {}) {
|
|
373
471
|
return runtimeContextHead(snapshot, contextView(state, artifactHints, artifactInvalidations, rehydrationPhase), recentTransitions);
|
|
374
472
|
}
|
|
375
473
|
/** Render a view already projected by this domain without cloning the full semantic overlay twice. */
|
|
376
474
|
export function runtimeContextHead(snapshot, view, recentTransitions = []) {
|
|
377
|
-
const recent = projectRecentForModel(recentTransitions);
|
|
475
|
+
const scopes = viewScopes(view), recent = projectRecentForModel(recentTransitions, scopes);
|
|
378
476
|
const context = {
|
|
379
477
|
...(snapshot.meta.specification === undefined ? {} : { specification: snapshot.meta.specification }),
|
|
380
|
-
state: view.state,
|
|
381
|
-
...(view.lazy_navigation === undefined ? {} : { lazy_navigation: view.lazy_navigation }),
|
|
478
|
+
state: modelState(view.state, scopes),
|
|
479
|
+
...(view.lazy_navigation === undefined ? {} : { lazy_navigation: modelNavigation(view.lazy_navigation, scopes) }),
|
|
382
480
|
...(view.knowledge_rehydration === null ? {} : { knowledge_rehydration: view.knowledge_rehydration }),
|
|
383
481
|
...(view.artifact_invalidations.length === 0 ? {} : { artifact_invalidations: view.artifact_invalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason })) }),
|
|
384
482
|
...(recent.length === 0 ? {} : { recent_transitions: recent }),
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { closeSync, constants, fstatSync, lstatSync, openSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { captureOwnedFileBases, resolveSessionAddress, sessionRuntimePaths } from "./durable.js";
|
|
4
4
|
import { MAX_HISTORY_LIMIT } from "./history.js";
|
|
5
5
|
import { diagnosticText } from "./protocol.js";
|
|
6
|
-
import {
|
|
6
|
+
import { DEFAULT_SCOPE_SELECTION } from "./selection.js";
|
|
7
|
+
import { prepareSessionPolicyMigration } from "./snapshot.js";
|
|
7
8
|
import { withStorageTransaction } from "./storage.js";
|
|
8
|
-
import { validateScopeLineage } from "./temporal.js";
|
|
9
9
|
/**
|
|
10
10
|
* Preserve host-owned explicit session intent and consult State Flow only for
|
|
11
11
|
* an ordinary default launch. The recommender is a read-only advisory port;
|
|
@@ -142,23 +142,19 @@ export async function inspectStateFlowContinuationProvenance(header, repositoryR
|
|
|
142
142
|
if (!lstatSync(root, { throwIfNoEntry: false }))
|
|
143
143
|
return absent;
|
|
144
144
|
const result = await withStorageTransaction(root, (tx) => {
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
|
|
145
|
+
const lifecycleFiles = captureOwnedFileBases([paths.config, paths.runtime], root);
|
|
146
|
+
const files = new Map(lifecycleFiles.map(file => [file.path, file.content]));
|
|
147
|
+
const policy = prepareSessionPolicyMigration(files.get(paths.config), files.get(paths.runtime), selected.cwd, selected.id, DEFAULT_SCOPE_SELECTION);
|
|
148
|
+
if (!policy) {
|
|
149
|
+
// Confirm absence/private presence without reading any shared or private semantic body.
|
|
150
|
+
if (tx.hasSelectedCurrentAuthority(selected.cwd, selected.id, DEFAULT_SCOPE_SELECTION, root, sessionKey))
|
|
151
|
+
throw new Error("State Flow lifecycle changed during inspection");
|
|
148
152
|
return absent;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (runtime.config.mode !== "active")
|
|
153
|
+
}
|
|
154
|
+
// Reuse current admission; bind the decoded selection to exact lifecycle bytes before any wider interpretation.
|
|
155
|
+
const current = tx.prepareSelectedCurrent(selected.cwd, selected.id, policy.runtime.config.scopes, MAX_HISTORY_LIMIT, root, sessionKey, { files: lifecycleFiles });
|
|
156
|
+
if (current.lifecycle.runtime.config.mode !== "active")
|
|
153
157
|
return { stateFlow: { enabled: false, restorable: true }, reason: "State Flow is not active on selected runtime" };
|
|
154
|
-
const streams = ["global", "cwd", "session"].map((scope) => {
|
|
155
|
-
const owned = temporalScopePaths(selected.cwd, selected.id, scope, root, sessionKey);
|
|
156
|
-
return parseScopeStream(files.get(owned.checkpoint), files.get(owned.patches), scope, scope === "cwd" ? selected.cwd : undefined, files.get(owned.meta));
|
|
157
|
-
});
|
|
158
|
-
if (streams.some((stream) => stream === undefined))
|
|
159
|
-
throw new Error("incomplete canonical temporal cohort");
|
|
160
|
-
// Shared streams remain current and independently valid; only the private stream binds to this lineage.
|
|
161
|
-
validateScopeLineage(streams[2], "session", runtime.meta.lineage, MAX_HISTORY_LIMIT);
|
|
162
158
|
return { stateFlow: { enabled: true, restorable: true }, reason: "canonical session lineage is valid beside current shared streams" };
|
|
163
159
|
}, signal);
|
|
164
160
|
signal?.throwIfAborted();
|
|
@@ -78,6 +78,12 @@ export declare function cwdScopePaths(cwd: string, repositoryRoot?: string): Sco
|
|
|
78
78
|
export declare function sessionScopePaths(cwd: string, sessionId: string, repositoryRoot?: string, sessionKey?: string): ScopePaths;
|
|
79
79
|
/** Exact canonical semantic and runtime file shapes. */
|
|
80
80
|
export declare function isStateFlowOwnedPath(candidate: string, repositoryRoot?: string): boolean;
|
|
81
|
+
export interface OwnedFilePresence {
|
|
82
|
+
path: string;
|
|
83
|
+
present: boolean;
|
|
84
|
+
}
|
|
85
|
+
/** Presence-only private-origin/unsupported-layout evidence; never open or decode semantic bodies. */
|
|
86
|
+
export declare function captureOwnedFilePresence(paths: readonly string[], repositoryRoot: string): OwnedFilePresence[];
|
|
81
87
|
/** Capture exact owned bytes for one compare-and-swap publication cohort. */
|
|
82
88
|
export declare function captureOwnedFileBases(paths: readonly string[], repositoryRoot: string): DurableFileBase[];
|
|
83
89
|
export interface OwnedFileUpdate {
|
|
@@ -4,8 +4,10 @@ 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
6
|
import { MAX_HISTORY_LIMIT } from "./history.js";
|
|
7
|
+
import { prepareSerializedScopeMigration } from "./migration.js";
|
|
7
8
|
import { canonicalJson, isJsonValue, isObject } from "./json.js";
|
|
8
9
|
import { validateScopeStream, validateTemporalState } from "./temporal.js";
|
|
10
|
+
import { assertRetainedModules, validateRetainedModules } from "./semantic-module.js";
|
|
9
11
|
import { selectSemanticFields } from "./state.js";
|
|
10
12
|
const SESSION_KEY_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
11
13
|
const STATE_FILE = "state.json";
|
|
@@ -34,8 +36,8 @@ export function serializeScopeStream(stream, scope, cwdIdentity) {
|
|
|
34
36
|
if (scope !== "cwd" && cwdIdentity !== undefined)
|
|
35
37
|
throw new Error("Only State Flow CWD scope serialization accepts a CWD identity");
|
|
36
38
|
return {
|
|
37
|
-
checkpoint: `${canonicalJson(selectSemanticFields(stream.checkpoint.state))}\n`,
|
|
38
|
-
patches: stream.patches.map((record) => `${canonicalJson(selectSemanticFields(record.patch))}\n`).join(""),
|
|
39
|
+
checkpoint: `${canonicalJson(selectSemanticFields(stream.checkpoint.state, stream.modules))}\n`,
|
|
40
|
+
patches: stream.patches.map((record) => `${canonicalJson(selectSemanticFields(record.patch, stream.modules))}\n`).join(""),
|
|
39
41
|
temporal: {
|
|
40
42
|
revision: stream.revision,
|
|
41
43
|
checkpoint: structuredClone(stream.checkpoint.through),
|
|
@@ -43,6 +45,9 @@ export function serializeScopeStream(stream, scope, cwdIdentity) {
|
|
|
43
45
|
},
|
|
44
46
|
};
|
|
45
47
|
}
|
|
48
|
+
function isSupportedScopeMetadataVersion(version) {
|
|
49
|
+
return version === 1 || version === 2 || version === 3 || version === 4;
|
|
50
|
+
}
|
|
46
51
|
/** Distinguish a wholly absent semantic cohort from partial or malformed surviving authority. */
|
|
47
52
|
export function classifyScopeStream(checkpointSource, patchesSource, scope, expectedCwd, metaSource) {
|
|
48
53
|
if (scope !== "global" && scope !== "cwd" && scope !== "session")
|
|
@@ -52,55 +57,39 @@ export function classifyScopeStream(checkpointSource, patchesSource, scope, expe
|
|
|
52
57
|
if (checkpointSource === undefined || patchesSource === undefined) {
|
|
53
58
|
throw new Error(`State Flow ${scope} scope has an incomplete checkpoint/tail pair`);
|
|
54
59
|
}
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
checkpoint = JSON.parse(checkpointSource);
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
throw new Error(`State Flow ${scope} checkpoint contains invalid JSON`);
|
|
61
|
-
}
|
|
62
|
-
let meta;
|
|
63
|
-
if (metaSource !== undefined) {
|
|
60
|
+
if (metaSource === undefined) {
|
|
64
61
|
try {
|
|
65
|
-
|
|
62
|
+
JSON.parse(checkpointSource);
|
|
66
63
|
}
|
|
67
64
|
catch {
|
|
68
|
-
throw new Error(`State Flow ${scope}
|
|
65
|
+
throw new Error(`State Flow ${scope} checkpoint contains invalid JSON`);
|
|
69
66
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
throw new Error(`State Flow ${scope} tail contains invalid JSON at line ${index + 1}`);
|
|
67
|
+
for (const [index, line] of patchesSource.split(/\r?\n/).entries()) {
|
|
68
|
+
if (line.trim().length === 0)
|
|
69
|
+
continue;
|
|
70
|
+
try {
|
|
71
|
+
JSON.parse(line);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
throw new Error(`State Flow ${scope} tail contains invalid JSON at line ${index + 1}`);
|
|
75
|
+
}
|
|
82
76
|
}
|
|
83
|
-
}
|
|
84
|
-
const temporal = meta?.temporal;
|
|
85
|
-
if (!isObject(temporal) || !Object.hasOwn(temporal, "checkpoint") || !Array.isArray(temporal.patches)) {
|
|
86
77
|
throw new Error(`Unsupported State Flow ${scope} storage format`);
|
|
87
78
|
}
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
79
|
+
const metadata = parseMetadataDocument(metaSource, `State Flow ${scope} metadata`);
|
|
80
|
+
if (!isSupportedScopeMetadataVersion(metadata.version))
|
|
81
|
+
throw new Error(`Unsupported State Flow ${scope} metadata version`);
|
|
82
|
+
// In Unicode mode this range matches lone UTF-16 surrogates, not valid pairs or JSON escapes.
|
|
83
|
+
if ([checkpointSource, patchesSource, metaSource].some((source) => /[\uD800-\uDFFF]/u.test(source))) {
|
|
84
|
+
throw new Error(`State Flow ${scope} source is not lossless UTF-8 text`);
|
|
92
85
|
}
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
86
|
+
const decoded = prepareSerializedScopeMigration({
|
|
87
|
+
checkpoint: Buffer.from(checkpointSource), patches: Buffer.from(patchesSource), meta: Buffer.from(metaSource),
|
|
88
|
+
}, scope, scope === "cwd" ? expectedCwd : undefined);
|
|
96
89
|
const stream = {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
checkpoint: { through: temporal.checkpoint, state: isObject(checkpoint) ? selectSemanticFields(checkpoint) : checkpoint },
|
|
101
|
-
patches: patches.map((patch, index) => ({
|
|
102
|
-
transition: boundaries[index], patch: isObject(patch) ? selectSemanticFields(patch) : patch,
|
|
103
|
-
})),
|
|
90
|
+
...decoded.stream,
|
|
91
|
+
checkpoint: { ...decoded.stream.checkpoint, state: selectSemanticFields(decoded.stream.checkpoint.state, decoded.stream.modules) },
|
|
92
|
+
patches: decoded.stream.patches.map((record) => ({ ...record, patch: selectSemanticFields(record.patch, decoded.stream.modules) })),
|
|
104
93
|
};
|
|
105
94
|
validateScopeStream(stream, scope, MAX_HISTORY_LIMIT);
|
|
106
95
|
return { kind: "present", stream };
|
|
@@ -159,23 +148,25 @@ export function temporalStateFileUpdates(cwd, sessionId, view, scopes, repositor
|
|
|
159
148
|
/** Merge authoritative owned leaves while preserving forward-compatible metadata siblings. */
|
|
160
149
|
export function serializeScopeMetadata(registry, stream, scope, cwdIdentity, existingSource) {
|
|
161
150
|
const existing = parseMetadataDocument(existingSource, `State Flow metadata`);
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
delete existing[key];
|
|
151
|
+
if (Object.keys(existing).length > 0 && !isSupportedScopeMetadataVersion(existing.version)) {
|
|
152
|
+
throw new Error("Unsupported State Flow metadata version");
|
|
165
153
|
}
|
|
154
|
+
if (existing.version === 4)
|
|
155
|
+
assertRetainedModules(existing.modules, stream.modules);
|
|
166
156
|
const sources = serializeScopeStream(stream, scope, cwdIdentity);
|
|
167
157
|
const value = {
|
|
168
158
|
...existing,
|
|
169
|
-
version:
|
|
159
|
+
version: stream.modules === undefined ? 3 : 4,
|
|
160
|
+
...(stream.modules === undefined ? {} : { modules: structuredClone(stream.modules) }),
|
|
170
161
|
...(registry === undefined ? {} : { artifacts: serializeArtifactProvenanceRegistry(registry) }),
|
|
171
|
-
temporal: sources.temporal,
|
|
162
|
+
temporal: { ...(isObject(existing.temporal) ? existing.temporal : {}), ...sources.temporal },
|
|
172
163
|
...(scope === "cwd" ? { owner: { cwd: resolve(cwdIdentity) } } : {}),
|
|
173
164
|
};
|
|
174
165
|
return `${canonicalJson(value)}\n`;
|
|
175
166
|
}
|
|
176
167
|
/** Compatibility serializer retained for metadata-only callers. */
|
|
177
168
|
export function serializeScopeProvenance(registry) {
|
|
178
|
-
return `${canonicalJson({ version:
|
|
169
|
+
return `${canonicalJson({ version: 3, artifacts: serializeArtifactProvenanceRegistry(registry) })}\n`;
|
|
179
170
|
}
|
|
180
171
|
function parseMetadataDocument(source, label) {
|
|
181
172
|
if (source === undefined)
|
|
@@ -196,8 +187,10 @@ export function parseScopeProvenance(source, path) {
|
|
|
196
187
|
const value = parseMetadataDocument(source, `State Flow provenance file: ${JSON.stringify(path)}`);
|
|
197
188
|
if (Object.keys(value).length === 0)
|
|
198
189
|
return {};
|
|
199
|
-
if (value.version
|
|
190
|
+
if (!isSupportedScopeMetadataVersion(value.version))
|
|
200
191
|
throw new Error(`Invalid State Flow provenance document: ${JSON.stringify(path)}`);
|
|
192
|
+
if (value.version === 4)
|
|
193
|
+
validateRetainedModules(value.modules);
|
|
201
194
|
if (!Object.hasOwn(value, "artifacts"))
|
|
202
195
|
return {};
|
|
203
196
|
return parseArtifactProvenanceRegistry(value.artifacts, `State Flow provenance at ${JSON.stringify(path)}`);
|
|
@@ -338,7 +331,8 @@ function readRegularBytes(path, repositoryRoot) {
|
|
|
338
331
|
}
|
|
339
332
|
}
|
|
340
333
|
function readRegularFile(path, repositoryRoot) {
|
|
341
|
-
|
|
334
|
+
const bytes = readRegularBytes(path, repositoryRoot);
|
|
335
|
+
return bytes === undefined ? undefined : new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
342
336
|
}
|
|
343
337
|
function byteIdentity(bytes) {
|
|
344
338
|
return bytes === undefined ? "missing" : `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
@@ -354,6 +348,22 @@ function assertCurrentBytes(path, root, expected) {
|
|
|
354
348
|
throw new Error(`State Flow file conflict at ${JSON.stringify(path)}; concurrent bytes preserved`);
|
|
355
349
|
}
|
|
356
350
|
}
|
|
351
|
+
/** Presence-only private-origin/unsupported-layout evidence; never open or decode semantic bodies. */
|
|
352
|
+
export function captureOwnedFilePresence(paths, repositoryRoot) {
|
|
353
|
+
const root = resolve(repositoryRoot);
|
|
354
|
+
return paths.map((requested) => {
|
|
355
|
+
const path = resolve(requested);
|
|
356
|
+
const retiredLayout = basename(path) === STATE_FILE && isStateFlowOwnedPath(join(dirname(path), CHECKPOINT_FILE), root);
|
|
357
|
+
if (!isStateFlowOwnedPath(path, root) && !retiredLayout)
|
|
358
|
+
throw new Error(`Cannot inspect a non-State Flow path: ${JSON.stringify(path)}`);
|
|
359
|
+
if (!assertDirectoryChain(root, dirname(path), false))
|
|
360
|
+
return { path, present: false };
|
|
361
|
+
const stat = lstatSync(path, { throwIfNoEntry: false });
|
|
362
|
+
if (stat && (!stat.isFile() || stat.isSymbolicLink()))
|
|
363
|
+
throw new Error(`Durable State Flow path is not a regular file: ${JSON.stringify(path)}`);
|
|
364
|
+
return { path, present: stat !== undefined };
|
|
365
|
+
});
|
|
366
|
+
}
|
|
357
367
|
/** Capture exact owned bytes for one compare-and-swap publication cohort. */
|
|
358
368
|
export function captureOwnedFileBases(paths, repositoryRoot) {
|
|
359
369
|
const root = resolve(repositoryRoot);
|