@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
|
@@ -2,7 +2,8 @@ import { resolve } from "node:path";
|
|
|
2
2
|
import { parseArtifactProvenanceRegistry } from "./artifact.js";
|
|
3
3
|
import { MAX_HISTORY_LIMIT } from "./history.js";
|
|
4
4
|
import { canonicalJson, isJsonValue, isObject } from "./json.js";
|
|
5
|
-
import { validateTemporalLineage } from "./temporal.js";
|
|
5
|
+
import { captureTemporalWitnesses, validateTemporalLineage } from "./temporal.js";
|
|
6
|
+
import { captureScopeSelection } from "./selection.js";
|
|
6
7
|
const MAX_RESTORED_STEP = Number.MAX_SAFE_INTEGER - 1;
|
|
7
8
|
const MAX_LEGACY_VALIDATION_ATTEMPT = 7;
|
|
8
9
|
/** Missing operational capability is not evidence that a checkpoint target is invalid. */
|
|
@@ -23,26 +24,70 @@ export function readCheckpointMode(value, inactiveMode = "passive") {
|
|
|
23
24
|
return Object.hasOwn(value, "enabled") || !isStateFlowMode(value.mode) ? undefined : value.mode;
|
|
24
25
|
return typeof value.enabled === "boolean" ? value.enabled ? "active" : inactiveMode : undefined;
|
|
25
26
|
}
|
|
27
|
+
/** Capture closed caller-owned run fields, omitting only undefined optional in-memory fields. */
|
|
28
|
+
export function captureLifecycleSnapshot(value) {
|
|
29
|
+
if (!isObject(value) || Object.keys(value).sort().join(",") !== "config,meta" || !isObject(value.config)
|
|
30
|
+
|| Object.keys(value.config).join(",") !== "mode" || !isObject(value.meta)
|
|
31
|
+
|| Object.keys(value.meta).some((key) => !["step", "specification", "bootstrap", "validation"].includes(key))) {
|
|
32
|
+
throw new Error("Selected State Flow lifecycle update contains non-owned fields");
|
|
33
|
+
}
|
|
34
|
+
const meta = Object.fromEntries(Object.entries(value.meta).filter(([, field]) => field !== undefined));
|
|
35
|
+
const supplied = { config: value.config, meta };
|
|
36
|
+
if (!isJsonValue(supplied))
|
|
37
|
+
throw new Error("Invalid State Flow lifecycle snapshot");
|
|
38
|
+
const normalized = { config: { mode: isStateFlowMode(value.config.mode) ? value.config.mode : "off" }, meta: restoredMeta(meta) };
|
|
39
|
+
if (meta.bootstrap === false)
|
|
40
|
+
normalized.meta.bootstrap = false;
|
|
41
|
+
if (meta.step === Number.MAX_SAFE_INTEGER)
|
|
42
|
+
normalized.meta.step = Number.MAX_SAFE_INTEGER;
|
|
43
|
+
if (canonicalJson(normalized) !== canonicalJson(supplied))
|
|
44
|
+
throw new Error("Invalid State Flow lifecycle snapshot");
|
|
45
|
+
return structuredClone(normalized);
|
|
46
|
+
}
|
|
26
47
|
/** Failed Active selection retains its policy and fences inference until explicit recovery or an inactive choice. */
|
|
27
48
|
export function isActiveRestorationBlocked(snapshot) {
|
|
28
49
|
return snapshot.config.mode === "active" && snapshot.meta.validation?.attempt === 0;
|
|
29
50
|
}
|
|
51
|
+
/** Retained private identity proof, never private values or acquisition permission. */
|
|
52
|
+
export function capturePrivateCausalBasis(value) {
|
|
53
|
+
if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "causalWitnesses,lineage")
|
|
54
|
+
throw new Error("Invalid State Flow private causal basis");
|
|
55
|
+
const lineage = value.lineage;
|
|
56
|
+
validateTemporalLineage(lineage, MAX_HISTORY_LIMIT);
|
|
57
|
+
const causalWitnesses = captureTemporalWitnesses(lineage, value.causalWitnesses, MAX_HISTORY_LIMIT);
|
|
58
|
+
const witnessed = new Set(causalWitnesses.map(witness => witness.transition.id));
|
|
59
|
+
if (lineage.slice(1).some(boundary => !witnessed.has(boundary.id)))
|
|
60
|
+
throw new Error("Insufficient State Flow private causal basis witnesses");
|
|
61
|
+
return structuredClone({ lineage, causalWitnesses });
|
|
62
|
+
}
|
|
30
63
|
export function validateSessionRuntime(value, cwd, sessionId) {
|
|
64
|
+
validateRuntimeVersion(value, cwd, sessionId, 1);
|
|
65
|
+
}
|
|
66
|
+
/** Shared syntax checks do not widen native admission or prove private-stream acquisition. */
|
|
67
|
+
function validateRuntimeVersion(value, cwd, sessionId, expectedVersion) {
|
|
31
68
|
if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "config,meta"
|
|
32
69
|
|| !isObject(value.config) || !isObject(value.meta))
|
|
33
70
|
throw new Error("Invalid State Flow session runtime envelope");
|
|
34
71
|
const { version, identity, lineage, artifacts, temporal: _temporalScope, ...fields } = value.meta;
|
|
72
|
+
if (!Object.hasOwn(value.meta, "version") || version !== expectedVersion)
|
|
73
|
+
throw new Error("Unsupported State Flow runtime provenance format");
|
|
35
74
|
if (artifacts !== undefined)
|
|
36
75
|
parseArtifactProvenanceRegistry(artifacts, "State Flow session artifact provenance");
|
|
37
|
-
if (
|
|
38
|
-
throw new Error("
|
|
76
|
+
if (expectedVersion === 2 && (!Object.hasOwn(value.meta, "privateAcquisition") || !["unacquired", "deferred", "acquired"].includes(value.meta.privateAcquisition))) {
|
|
77
|
+
throw new Error("Invalid State Flow private acquisition provenance");
|
|
78
|
+
}
|
|
79
|
+
if (Object.hasOwn(value.meta, "privateCausalBasis")) {
|
|
80
|
+
if (expectedVersion !== 2 || value.meta.privateAcquisition !== "acquired")
|
|
81
|
+
throw new Error("Invalid State Flow private causal basis provenance");
|
|
82
|
+
capturePrivateCausalBasis(value.meta.privateCausalBasis);
|
|
83
|
+
}
|
|
39
84
|
if (!isObject(identity) || Object.keys(identity).sort().join(",") !== "cwd,sessionId"
|
|
40
85
|
|| identity.cwd !== resolve(cwd) || identity.sessionId !== sessionId || sessionId.trim().length === 0 || sessionId !== sessionId.trim()) {
|
|
41
86
|
throw new Error("State Flow runtime scope identity mismatch");
|
|
42
87
|
}
|
|
43
88
|
validateTemporalLineage(lineage, MAX_HISTORY_LIMIT);
|
|
44
89
|
const known = new Set(["step", "specification", "validation", "bootstrap"]);
|
|
45
|
-
if (Object.keys(fields).some((key) => ["state", "contract", "working", "response"].includes(key))) {
|
|
90
|
+
if (Object.keys(fields).some((key) => ["state", "strategy", "tactics", "contract", "working", "response"].includes(key))) {
|
|
46
91
|
throw new Error("Semantic state does not belong in State Flow runtime metadata");
|
|
47
92
|
}
|
|
48
93
|
const runtimeFields = Object.fromEntries(Object.entries(fields).filter(([key]) => known.has(key)));
|
|
@@ -54,7 +99,12 @@ export function validateSessionRuntime(value, cwd, sessionId) {
|
|
|
54
99
|
normalized.meta.bootstrap = false;
|
|
55
100
|
if (runtimeFields.step === Number.MAX_SAFE_INTEGER)
|
|
56
101
|
normalized.meta.step = Number.MAX_SAFE_INTEGER;
|
|
57
|
-
if (
|
|
102
|
+
if (expectedVersion === 2 && !Object.hasOwn(value.config, "scopes"))
|
|
103
|
+
throw new Error("State Flow lifecycle v2 requires explicit scopes");
|
|
104
|
+
const scopes = expectedVersion === 2 ? captureScopeSelection(value.config.scopes) : undefined;
|
|
105
|
+
const normalizedConfig = scopes === undefined ? normalized.config : { ...normalized.config, scopes };
|
|
106
|
+
const suppliedConfig = scopes === undefined ? value.config : { ...value.config, scopes };
|
|
107
|
+
if (canonicalJson({ config: normalizedConfig, meta: normalized.meta }) !== canonicalJson({ config: suppliedConfig, meta: runtimeFields })) {
|
|
58
108
|
throw new Error("Invalid State Flow runtime configuration or counters");
|
|
59
109
|
}
|
|
60
110
|
}
|
|
@@ -86,10 +136,7 @@ export function parseSessionRuntime(config, runtimeSource, cwd, sessionId) {
|
|
|
86
136
|
let runtime;
|
|
87
137
|
try {
|
|
88
138
|
const settings = JSON.parse(config);
|
|
89
|
-
|
|
90
|
-
if (mode === undefined)
|
|
91
|
-
throw new Error("Invalid State Flow runtime configuration or counters");
|
|
92
|
-
runtime = { config: { mode }, meta: JSON.parse(runtimeSource) };
|
|
139
|
+
runtime = { config: legacySessionConfig(settings), meta: JSON.parse(runtimeSource) };
|
|
93
140
|
}
|
|
94
141
|
catch (error) {
|
|
95
142
|
if (error instanceof SyntaxError)
|
|
@@ -100,6 +147,48 @@ export function parseSessionRuntime(config, runtimeSource, cwd, sessionId) {
|
|
|
100
147
|
const { temporal: _legacyTemporal, ...runtimeMeta } = runtime.meta;
|
|
101
148
|
return { config: { mode: runtime.config.mode }, meta: runtimeMeta };
|
|
102
149
|
}
|
|
150
|
+
function legacySessionConfig(settings) {
|
|
151
|
+
const mode = isObject(settings) && Object.keys(settings).length === 1 ? readCheckpointMode(settings, "passive") : undefined;
|
|
152
|
+
if (mode === undefined)
|
|
153
|
+
throw new Error("Invalid State Flow runtime configuration or counters");
|
|
154
|
+
return { mode };
|
|
155
|
+
}
|
|
156
|
+
/** Detached lifecycle preflight only; current native readers/writers still admit version 1 exclusively. */
|
|
157
|
+
export function prepareSessionPolicyMigration(config, runtimeSource, cwd, sessionId, legacySelection) {
|
|
158
|
+
if (config === undefined && runtimeSource === undefined)
|
|
159
|
+
return undefined;
|
|
160
|
+
if (config === undefined || runtimeSource === undefined)
|
|
161
|
+
throw new Error("Incomplete State Flow config/runtime pair");
|
|
162
|
+
const metadata = parsePolicyDocument(runtimeSource);
|
|
163
|
+
if (!isObject(metadata) || !Object.hasOwn(metadata, "version") || (metadata.version !== 1 && metadata.version !== 2))
|
|
164
|
+
throw new Error("Unsupported State Flow runtime provenance format");
|
|
165
|
+
const sourceVersion = metadata.version;
|
|
166
|
+
const settings = parsePolicyDocument(config);
|
|
167
|
+
if (sourceVersion === 1 && (Object.hasOwn(metadata, "privateAcquisition") || Object.hasOwn(metadata, "causalWitnesses") || Object.hasOwn(metadata, "privateCausalBasis") || (isObject(settings) && Object.hasOwn(settings, "scopes")))) {
|
|
168
|
+
throw new Error("Target State Flow policy fields in legacy lifecycle format");
|
|
169
|
+
}
|
|
170
|
+
const sourceValue = { config: sourceVersion === 1 ? legacySessionConfig(settings) : settings, meta: metadata };
|
|
171
|
+
validateRuntimeVersion(sourceValue, cwd, sessionId, sourceVersion);
|
|
172
|
+
const scopes = captureScopeSelection(sourceVersion === 1 ? legacySelection : settings.scopes);
|
|
173
|
+
const runtime = {
|
|
174
|
+
config: { ...sourceValue.config, scopes },
|
|
175
|
+
meta: { ...metadata, version: 2, ...(sourceVersion === 1 ? { privateAcquisition: "acquired" } : {}) },
|
|
176
|
+
};
|
|
177
|
+
validateRuntimeVersion(runtime, cwd, sessionId, 2);
|
|
178
|
+
return {
|
|
179
|
+
sourceVersion, targetVersion: 2, requiresConversion: sourceVersion === 1,
|
|
180
|
+
source: { config, runtime: runtimeSource }, runtime,
|
|
181
|
+
target: { config: `${canonicalJson(runtime.config)}\n`, runtime: `${canonicalJson(runtime.meta)}\n` },
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function parsePolicyDocument(source) {
|
|
185
|
+
try {
|
|
186
|
+
return JSON.parse(source);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
throw new Error("State Flow session policy contains invalid JSON");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
103
192
|
function restoredStep(value) {
|
|
104
193
|
return typeof value === "number"
|
|
105
194
|
&& Number.isSafeInteger(value)
|
|
@@ -187,6 +276,37 @@ export function parseRetainedPiCheckpoint(value, inactiveMode = "passive") {
|
|
|
187
276
|
...(typeof value.specification === "string" ? { specification: value.specification } : {}),
|
|
188
277
|
};
|
|
189
278
|
}
|
|
279
|
+
/** Native policy value preflight; no retained boundary, fork or acquisition authority is created. */
|
|
280
|
+
export function prepareRetainedPolicyMigration(value, legacySelection, inactiveMode = "passive") {
|
|
281
|
+
if (!isObject(value))
|
|
282
|
+
throw new Error("Invalid State Flow policy checkpoint");
|
|
283
|
+
const legacy = !Object.hasOwn(value, "version");
|
|
284
|
+
let declaredVersion;
|
|
285
|
+
try {
|
|
286
|
+
if (!legacy)
|
|
287
|
+
declaredVersion = value.version;
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
throw new Error("Invalid State Flow policy checkpoint");
|
|
291
|
+
}
|
|
292
|
+
if (!legacy && declaredVersion !== 2)
|
|
293
|
+
throw new Error("Unsupported State Flow native policy version");
|
|
294
|
+
const sourceVersion = legacy ? "legacy" : 2;
|
|
295
|
+
if (sourceVersion === "legacy" && Object.hasOwn(value, "scopes"))
|
|
296
|
+
throw new Error("Target State Flow policy fields in legacy native checkpoint");
|
|
297
|
+
if (!isJsonValue(value))
|
|
298
|
+
throw new Error("Invalid State Flow policy checkpoint");
|
|
299
|
+
const { version: _version, scopes, ...lifecycle } = value;
|
|
300
|
+
if (sourceVersion === 2 && (!Object.hasOwn(value, "scopes") || !isStateFlowMode(lifecycle.mode)
|
|
301
|
+
|| Object.hasOwn(lifecycle, "enabled") || Object.hasOwn(lifecycle, "disabled"))) {
|
|
302
|
+
throw new Error("Invalid State Flow lifecycle v2 policy");
|
|
303
|
+
}
|
|
304
|
+
const checkpoint = parseRetainedPiCheckpoint(lifecycle, inactiveMode);
|
|
305
|
+
return {
|
|
306
|
+
sourceVersion, targetVersion: 2, requiresConversion: sourceVersion === "legacy",
|
|
307
|
+
checkpoint: { ...checkpoint, version: 2, scopes: captureScopeSelection(sourceVersion === "legacy" ? legacySelection : scopes) },
|
|
308
|
+
};
|
|
309
|
+
}
|
|
190
310
|
export function migrationFailure(data, error, mode = "passive") {
|
|
191
311
|
const meta = restoredMeta(data.meta, data);
|
|
192
312
|
meta.validation = {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { type ArtifactCompilationUpdate, type ArtifactModelHints, type ArtifactRegistry } from "./artifact.ts";
|
|
2
2
|
import { type JsonObject } from "./json.ts";
|
|
3
|
+
import { type RetainedModules } from "./semantic-module.ts";
|
|
3
4
|
/** Runtime defaults for documented semantic planes; stored objects may omit them or retain other fields. */
|
|
4
5
|
export type MaterializedState = JsonObject & {
|
|
5
6
|
intents: JsonObject;
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
strategy: JsonObject;
|
|
8
|
+
tactics: JsonObject;
|
|
8
9
|
artifacts: ArtifactRegistry;
|
|
9
10
|
response: string;
|
|
10
11
|
lazy: JsonObject;
|
|
@@ -12,20 +13,24 @@ export type MaterializedState = JsonObject & {
|
|
|
12
13
|
/** Sparse semantic state: documented planes may be absent. Disk codecs select only known fields. */
|
|
13
14
|
export type SemanticState = JsonObject & Partial<{
|
|
14
15
|
intents: JsonObject;
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
strategy: JsonObject;
|
|
17
|
+
tactics: JsonObject;
|
|
17
18
|
artifacts: ArtifactRegistry;
|
|
18
19
|
response: string;
|
|
19
20
|
lazy: JsonObject;
|
|
20
21
|
}>;
|
|
21
22
|
export type ScopedSemanticStates = Record<StateScope, SemanticState>;
|
|
23
|
+
/** Present owners only; a selection-aware caller must validate the exact acquired key set. */
|
|
24
|
+
export type ScopeStateValues = Partial<Record<StateScope, SemanticState>>;
|
|
25
|
+
/** Value admission only: owners must already be a captured policy, never inferred from missing data. */
|
|
26
|
+
export declare function assertScopeStateValues(states: ScopeStateValues, owners: readonly StateScope[]): void;
|
|
22
27
|
/** Compatibility name for callers that still treat materialized state as a document. */
|
|
23
28
|
export type StateDocument = MaterializedState;
|
|
24
29
|
/** Model patch shape; artifact entries may be compiler outputs before trusted metadata is attached. */
|
|
25
30
|
export interface StatePatch extends JsonObject {
|
|
26
31
|
artifacts: JsonObject;
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
strategy: JsonObject;
|
|
33
|
+
tactics: JsonObject;
|
|
29
34
|
intents: JsonObject;
|
|
30
35
|
response: string;
|
|
31
36
|
lazy: JsonObject;
|
|
@@ -33,9 +38,10 @@ export interface StatePatch extends JsonObject {
|
|
|
33
38
|
export type StateScope = "global" | "cwd" | "session";
|
|
34
39
|
/** A model-authored patch for one scope. Response is captured by the runtime in session state. */
|
|
35
40
|
export interface ScopePatch {
|
|
41
|
+
[module: `x_${string}`]: JsonObject | undefined;
|
|
36
42
|
artifacts?: JsonObject;
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
strategy?: JsonObject;
|
|
44
|
+
tactics?: JsonObject;
|
|
39
45
|
intents?: JsonObject;
|
|
40
46
|
lazy?: JsonObject;
|
|
41
47
|
}
|
|
@@ -60,6 +66,8 @@ export interface ScopedStates {
|
|
|
60
66
|
cwd: MaterializedState;
|
|
61
67
|
session: MaterializedState;
|
|
62
68
|
}
|
|
69
|
+
/** Temporary root-plane reservation; nested domain data is not an alternate semantic namespace. */
|
|
70
|
+
export declare function assertNoRetiredSemanticPlanes(value: JsonObject): void;
|
|
63
71
|
export declare function emptyState(): MaterializedState;
|
|
64
72
|
export declare function isMaterializedState(value: unknown): value is MaterializedState;
|
|
65
73
|
/** Missing planes are valid storage, not missing authority. Present values retain their type checks. */
|
|
@@ -72,18 +80,22 @@ export declare function overlayStates(...scopes: readonly JsonObject[]): Materia
|
|
|
72
80
|
/** Default-bearing SDK compatibility view; model transport uses sparse SemanticState instead. */
|
|
73
81
|
export type ModelState = JsonObject & {
|
|
74
82
|
intents: JsonObject;
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
strategy: JsonObject;
|
|
84
|
+
tactics: JsonObject;
|
|
77
85
|
artifacts: ArtifactRegistry;
|
|
78
86
|
response: string;
|
|
79
87
|
};
|
|
80
88
|
/** Select only owned top-level fields, preserving nested data and replay deletion markers. */
|
|
81
|
-
export declare function selectSemanticFields(value: JsonObject): JsonObject;
|
|
89
|
+
export declare function selectSemanticFields(value: JsonObject, modules?: RetainedModules): JsonObject;
|
|
90
|
+
/** v4 admits only declared object planes; null is a replay deletion, never stored content. */
|
|
91
|
+
export declare function validateExternalPlanes(value: JsonObject, modules: RetainedModules, patch?: boolean): void;
|
|
92
|
+
/** Normalize only core planes: opaque external bodies are not empty-branch policy subjects. */
|
|
93
|
+
export declare function pruneSemanticState(value: JsonObject): JsonObject;
|
|
82
94
|
/** Read only documented, present planes; empty responses carry no semantic value. */
|
|
83
|
-
export declare function projectSemanticState(state: JsonObject): SemanticState;
|
|
95
|
+
export declare function projectSemanticState(state: JsonObject, modules?: RetainedModules): SemanticState;
|
|
84
96
|
/** Preserve deletion meaning in visible history without exposing ignored fields or empty responses. */
|
|
85
|
-
export declare function projectSemanticPatch(patch: JsonObject): JsonObject;
|
|
97
|
+
export declare function projectSemanticPatch(patch: JsonObject, modules?: RetainedModules): JsonObject;
|
|
86
98
|
/** Explicit hot reads filter hidden bodies/bookkeeping without normalizing legacy semantic data. */
|
|
87
|
-
export declare function projectStateForRead(state: SemanticState, artifactHints?: ArtifactModelHints): SemanticState;
|
|
99
|
+
export declare function projectStateForRead(state: SemanticState, artifactHints?: ArtifactModelHints, modules?: RetainedModules): SemanticState;
|
|
88
100
|
/** Ordinary model context omits empty object fields; exact reads and replay remain observational. */
|
|
89
101
|
export declare function projectStateForModel(state: SemanticState, artifactHints?: ArtifactModelHints): SemanticState;
|
|
@@ -1,16 +1,32 @@
|
|
|
1
|
-
import { isArtifactRegistry,
|
|
2
|
-
import { applyPatch, isObject, pruneEmptyObjects } from "./json.js";
|
|
1
|
+
import { isArtifactRegistry, updateArtifactRegistry, } from "./artifact.js";
|
|
2
|
+
import { applyPatch, isJsonValue, isObject, pruneEmptyObjects } from "./json.js";
|
|
3
|
+
import { projectPlaneContext, SEMANTIC_PLANES } from "./planes.js";
|
|
4
|
+
import { validateModuleObject, validateRetainedModules } from "./semantic-module.js";
|
|
5
|
+
/** Value admission only: owners must already be a captured policy, never inferred from missing data. */
|
|
6
|
+
export function assertScopeStateValues(states, owners) {
|
|
7
|
+
if (!isObject(states) || Object.keys(states).length !== owners.length
|
|
8
|
+
|| Object.keys(states).some((scope) => !owners.includes(scope))
|
|
9
|
+
|| owners.some((scope) => !Object.hasOwn(states, scope)))
|
|
10
|
+
throw new Error("Selected State Flow values must match their owners exactly");
|
|
11
|
+
for (const scope of owners)
|
|
12
|
+
if (!isJsonValue(states[scope]) || !isSemanticState(states[scope]))
|
|
13
|
+
throw new Error(`Invalid selected State Flow ${scope} value`);
|
|
14
|
+
}
|
|
15
|
+
/** Temporary root-plane reservation; nested domain data is not an alternate semantic namespace. */
|
|
16
|
+
export function assertNoRetiredSemanticPlanes(value) {
|
|
17
|
+
for (const plane of ["contract", "working"]) {
|
|
18
|
+
if (Object.hasOwn(value, plane))
|
|
19
|
+
throw new Error(`Retired State Flow semantic plane: ${plane}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
3
22
|
export function emptyState() {
|
|
4
|
-
return { intents: {},
|
|
23
|
+
return { intents: {}, strategy: {}, tactics: {}, artifacts: {}, response: "", lazy: {} };
|
|
5
24
|
}
|
|
6
25
|
export function isMaterializedState(value) {
|
|
7
26
|
return isObject(value)
|
|
27
|
+
&& !Object.hasOwn(value, "contract") && !Object.hasOwn(value, "working")
|
|
8
28
|
&& isArtifactRegistry(value.artifacts)
|
|
9
|
-
&&
|
|
10
|
-
&& isObject(value.working)
|
|
11
|
-
&& isObject(value.intents)
|
|
12
|
-
&& typeof value.response === "string"
|
|
13
|
-
&& isObject(value.lazy);
|
|
29
|
+
&& SEMANTIC_PLANES.every(({ module }) => module.validate(value[module.id]));
|
|
14
30
|
}
|
|
15
31
|
/** Missing planes are valid storage, not missing authority. Present values retain their type checks. */
|
|
16
32
|
export function isSemanticState(value) {
|
|
@@ -27,36 +43,62 @@ export function updateMaterializedArtifacts(state, updates, removed = []) {
|
|
|
27
43
|
/** Overlay lower-to-higher scopes without mutating any scope document. */
|
|
28
44
|
export function overlayStates(...scopes) {
|
|
29
45
|
return scopes.reduce((effective, scope) => {
|
|
46
|
+
assertNoRetiredSemanticPlanes(scope);
|
|
30
47
|
return applyPatch(effective, scope);
|
|
31
48
|
}, emptyState());
|
|
32
49
|
}
|
|
33
50
|
/** Select only owned top-level fields, preserving nested data and replay deletion markers. */
|
|
34
|
-
export function selectSemanticFields(value) {
|
|
35
|
-
|
|
51
|
+
export function selectSemanticFields(value, modules) {
|
|
52
|
+
assertNoRetiredSemanticPlanes(value);
|
|
53
|
+
if (modules !== undefined)
|
|
54
|
+
validateExternalPlanes(value, modules, true);
|
|
55
|
+
return structuredClone(Object.fromEntries([...Object.keys(emptyState()), ...Object.keys(modules ?? {})]
|
|
36
56
|
.filter((key) => Object.hasOwn(value, key))
|
|
37
57
|
.map((key) => [key, value[key]])));
|
|
38
58
|
}
|
|
59
|
+
/** v4 admits only declared object planes; null is a replay deletion, never stored content. */
|
|
60
|
+
export function validateExternalPlanes(value, modules, patch = false) {
|
|
61
|
+
validateRetainedModules(modules);
|
|
62
|
+
const core = emptyState();
|
|
63
|
+
for (const [key, field] of Object.entries(value)) {
|
|
64
|
+
if (Object.hasOwn(core, key))
|
|
65
|
+
continue;
|
|
66
|
+
if (!Object.hasOwn(modules, key) || !(patch && field === null || isObject(field))) {
|
|
67
|
+
throw new Error(`Undeclared or invalid State Flow external plane: ${key}`);
|
|
68
|
+
}
|
|
69
|
+
if (field !== null)
|
|
70
|
+
validateModuleObject(field);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/** Normalize only core planes: opaque external bodies are not empty-branch policy subjects. */
|
|
74
|
+
export function pruneSemanticState(value) {
|
|
75
|
+
return Object.fromEntries(Object.entries(value).flatMap(([key, field]) => {
|
|
76
|
+
if (key.startsWith("x_"))
|
|
77
|
+
return [[key, field]];
|
|
78
|
+
const pruned = pruneEmptyObjects({ [key]: field });
|
|
79
|
+
return Object.entries(pruned);
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
39
82
|
/** Read only documented, present planes; empty responses carry no semantic value. */
|
|
40
|
-
export function projectSemanticState(state) {
|
|
41
|
-
const projected = selectSemanticFields(state);
|
|
83
|
+
export function projectSemanticState(state, modules) {
|
|
84
|
+
const projected = selectSemanticFields(state, modules);
|
|
42
85
|
if (projected.response === "")
|
|
43
86
|
delete projected.response;
|
|
44
87
|
return projected;
|
|
45
88
|
}
|
|
46
89
|
/** Preserve deletion meaning in visible history without exposing ignored fields or empty responses. */
|
|
47
|
-
export function projectSemanticPatch(patch) {
|
|
48
|
-
const projected = selectSemanticFields(patch);
|
|
90
|
+
export function projectSemanticPatch(patch, modules) {
|
|
91
|
+
const projected = selectSemanticFields(patch, modules);
|
|
49
92
|
if (projected.response === "")
|
|
50
93
|
projected.response = null;
|
|
51
94
|
return projected;
|
|
52
95
|
}
|
|
53
96
|
/** Explicit hot reads filter hidden bodies/bookkeeping without normalizing legacy semantic data. */
|
|
54
|
-
export function projectStateForRead(state, artifactHints = {}) {
|
|
97
|
+
export function projectStateForRead(state, artifactHints = {}, modules) {
|
|
55
98
|
const { lazy: _hidden, ...visible } = state;
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return projected;
|
|
99
|
+
const selected = projectSemanticState(visible, modules);
|
|
100
|
+
const core = projectPlaneContext(selected, artifactHints).state;
|
|
101
|
+
return { ...core, ...Object.fromEntries(Object.keys(modules ?? {}).filter(id => Object.hasOwn(selected, id)).map(id => [id, structuredClone(selected[id])])) };
|
|
60
102
|
}
|
|
61
103
|
/** Ordinary model context omits empty object fields; exact reads and replay remain observational. */
|
|
62
104
|
export function projectStateForModel(state, artifactHints = {}) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ArtifactInvalidationReason } from "./artifact.ts";
|
|
2
2
|
import type { RecentTransitionWindow } from "./history.ts";
|
|
3
3
|
import { type Snapshot } from "./snapshot.ts";
|
|
4
|
-
import { type SemanticState, type
|
|
4
|
+
import { type SemanticState, type ScopeStateValues, type StateScope } from "./state.ts";
|
|
5
|
+
import { type ScopeSelection } from "./selection.ts";
|
|
5
6
|
import type { ScopeRevisions, TransitionBoundary } from "./temporal.ts";
|
|
6
7
|
export declare const STATUS_KEY = "state-flow";
|
|
7
8
|
export type Colorize = (color: "accent" | "dim", text: string) => string;
|
|
@@ -15,23 +16,27 @@ export interface StatusDiagnostics {
|
|
|
15
16
|
repositoryRoot: string;
|
|
16
17
|
cwdScopeKey: string;
|
|
17
18
|
sessionScopeKey: string;
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
/** Omission retains all-three compatibility; a subset must be explicit, never inferred from missing data. */
|
|
20
|
+
selection?: ScopeSelection;
|
|
21
|
+
/** Captured policy may request Session while only shared owners are observable. Never a materialization grant. */
|
|
22
|
+
requestedSelection?: ScopeSelection;
|
|
23
|
+
scopeStates: ScopeStateValues;
|
|
24
|
+
/** Legacy overlay override. Explicit selected observations derive only from scopeStates. */
|
|
20
25
|
effectiveState?: SemanticState;
|
|
21
26
|
recent: RecentTransitionWindow;
|
|
22
27
|
historyLimit: number;
|
|
23
28
|
temporal?: {
|
|
24
29
|
head: TransitionBoundary;
|
|
25
30
|
historyDepth: number;
|
|
26
|
-
tailCounts: Record<StateScope, number
|
|
27
|
-
revisions: ScopeRevisions
|
|
31
|
+
tailCounts: Partial<Record<StateScope, number>>;
|
|
32
|
+
revisions: Partial<ScopeRevisions>;
|
|
28
33
|
};
|
|
29
34
|
staleArtifacts: readonly StaleArtifactDiagnostic[];
|
|
30
35
|
durableStateError?: string;
|
|
31
36
|
publicationError?: string;
|
|
32
37
|
}
|
|
33
|
-
export declare function formatScopeRevisionVector(revisions: ScopeRevisions): string;
|
|
34
|
-
export declare function compactStatus(snapshot: Snapshot, _revisions: ScopeRevisions
|
|
35
|
-
/** Operator-only footprint: serialized plane sizes and intent-owned share of top-level
|
|
36
|
-
export declare function scopeMemoryLines(states:
|
|
38
|
+
export declare function formatScopeRevisionVector(revisions: Partial<ScopeRevisions>, selection?: ScopeSelection): string;
|
|
39
|
+
export declare function compactStatus(snapshot: Snapshot, _revisions: Partial<ScopeRevisions>, colorize: Colorize): string | undefined;
|
|
40
|
+
/** Operator-only footprint: serialized plane sizes and intent-owned share of top-level tactics/lazy entries. */
|
|
41
|
+
export declare function scopeMemoryLines(states: ScopeStateValues, selection?: ScopeSelection): string[];
|
|
37
42
|
export declare function detailedStatus(snapshot: Snapshot, diagnostics: StatusDiagnostics): string;
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { ownedTopLevelKeys } from "./ownership.js";
|
|
2
2
|
import { conciseDiagnostic } from "./protocol.js";
|
|
3
3
|
import { isActiveRestorationBlocked } from "./snapshot.js";
|
|
4
|
-
import { overlayStates, projectSemanticState } from "./state.js";
|
|
4
|
+
import { assertScopeStateValues, overlayStates, projectSemanticState } from "./state.js";
|
|
5
|
+
import { applyPatch, isObject } from "./json.js";
|
|
6
|
+
import { captureScopeSelection, DEFAULT_SCOPE_SELECTION } from "./selection.js";
|
|
5
7
|
export const STATUS_KEY = "state-flow";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
function assertScopeNumbers(values, selection, label) {
|
|
9
|
+
if (!isObject(values) || Object.keys(values).length !== selection.length
|
|
10
|
+
|| Object.keys(values).some(scope => !selection.includes(scope))
|
|
11
|
+
|| selection.some(scope => !Object.hasOwn(values, scope)))
|
|
12
|
+
throw new Error(`Invalid State Flow selected ${label}`);
|
|
13
|
+
for (const scope of selection)
|
|
14
|
+
if (!Number.isSafeInteger(values[scope]) || values[scope] < 0)
|
|
15
|
+
throw new Error(`Invalid State Flow selected ${label}`);
|
|
16
|
+
}
|
|
17
|
+
export function formatScopeRevisionVector(revisions, selection = DEFAULT_SCOPE_SELECTION) {
|
|
18
|
+
const selected = captureScopeSelection(selection);
|
|
19
|
+
assertScopeNumbers(revisions, selected, "revision vector");
|
|
20
|
+
const prefix = { global: "g", cwd: "c", session: "s" };
|
|
21
|
+
return selected.map(scope => `${prefix[scope]}${revisions[scope]}`).join("");
|
|
8
22
|
}
|
|
9
23
|
export function compactStatus(snapshot, _revisions, colorize) {
|
|
10
24
|
const { mode } = snapshot.config;
|
|
@@ -13,26 +27,28 @@ export function compactStatus(snapshot, _revisions, colorize) {
|
|
|
13
27
|
return `${colorize("accent", "state-flow")} ${colorize("dim", isActiveRestorationBlocked(snapshot) ? "active (blocked)" : mode)}`;
|
|
14
28
|
}
|
|
15
29
|
function countArtifacts(states, scope) {
|
|
16
|
-
return Object.keys(states[scope].artifacts).length;
|
|
30
|
+
return Object.keys(states[scope].artifacts ?? {}).length;
|
|
17
31
|
}
|
|
18
|
-
const STATUS_PLANES = ["intents", "
|
|
32
|
+
const STATUS_PLANES = ["intents", "strategy", "tactics", "artifacts", "response", "lazy"];
|
|
19
33
|
const encoder = new TextEncoder();
|
|
20
34
|
function isPresentPlane(value) {
|
|
21
35
|
if (value === undefined || value === "")
|
|
22
36
|
return false;
|
|
23
37
|
return typeof value !== "object" || value === null || Object.keys(value).length > 0;
|
|
24
38
|
}
|
|
25
|
-
/** Operator-only footprint: serialized plane sizes and intent-owned share of top-level
|
|
26
|
-
export function scopeMemoryLines(states) {
|
|
39
|
+
/** Operator-only footprint: serialized plane sizes and intent-owned share of top-level tactics/lazy entries. */
|
|
40
|
+
export function scopeMemoryLines(states, selection = DEFAULT_SCOPE_SELECTION) {
|
|
41
|
+
const selected = captureScopeSelection(selection);
|
|
42
|
+
assertScopeStateValues(states, selected);
|
|
27
43
|
const lines = [];
|
|
28
|
-
for (const scope of
|
|
44
|
+
for (const scope of selected) {
|
|
29
45
|
const state = states[scope];
|
|
30
46
|
const sizes = STATUS_PLANES.filter((plane) => isPresentPlane(state[plane]))
|
|
31
47
|
.map((plane) => `${plane} ${encoder.encode(JSON.stringify(state[plane])).length} B`);
|
|
32
48
|
if (sizes.length === 0)
|
|
33
49
|
continue;
|
|
34
50
|
const owned = ownedTopLevelKeys(scope, state);
|
|
35
|
-
const shares = ["
|
|
51
|
+
const shares = ["tactics", "lazy"].flatMap((plane) => {
|
|
36
52
|
const value = state[plane];
|
|
37
53
|
const keys = value !== null && typeof value === "object" && !Array.isArray(value) ? Object.keys(value) : [];
|
|
38
54
|
return keys.length === 0 ? [] : [`${plane} ${keys.filter((key) => owned[plane].has(key)).length}/${keys.length}`];
|
|
@@ -42,36 +58,57 @@ export function scopeMemoryLines(states) {
|
|
|
42
58
|
return lines.length ? ["Scope memory:", ...lines] : [];
|
|
43
59
|
}
|
|
44
60
|
export function detailedStatus(snapshot, diagnostics) {
|
|
61
|
+
const selection = diagnostics.selection;
|
|
62
|
+
const explicit = selection !== undefined;
|
|
63
|
+
const selected = captureScopeSelection(explicit ? selection : DEFAULT_SCOPE_SELECTION);
|
|
64
|
+
const requested = diagnostics.requestedSelection === undefined ? selected : captureScopeSelection(diagnostics.requestedSelection);
|
|
45
65
|
const available = diagnostics.temporal !== undefined && diagnostics.durableStateError === undefined;
|
|
46
|
-
|
|
66
|
+
if (available) {
|
|
67
|
+
if (selected.some(scope => !requested.includes(scope)))
|
|
68
|
+
throw new Error("State Flow materialization exceeds requested scopes");
|
|
69
|
+
assertScopeStateValues(diagnostics.scopeStates, selected);
|
|
70
|
+
assertScopeNumbers(diagnostics.temporal.revisions, selected, "revision vector");
|
|
71
|
+
assertScopeNumbers(diagnostics.temporal.tailCounts, selected, "tail counts");
|
|
72
|
+
}
|
|
73
|
+
// A selected observation derives its overlay from exact owners, never a stale all-owner override.
|
|
74
|
+
const materialized = !available ? undefined : explicit
|
|
75
|
+
? selected.reduce((state, scope) => applyPatch(state, projectSemanticState(diagnostics.scopeStates[scope])), {})
|
|
76
|
+
: diagnostics.effectiveState ?? projectSemanticState(overlayStates(...selected.map(scope => diagnostics.scopeStates[scope])));
|
|
47
77
|
// Only top-level plane boundaries gain whitespace; nested user JSON stays unchanged.
|
|
48
78
|
const stateJson = materialized === undefined ? undefined : JSON.stringify(materialized, null, 2).replace(/,\n(?= ")/g, ",\n\n");
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
? []
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
];
|
|
79
|
+
const stale = explicit ? !available ? [] : diagnostics.staleArtifacts.flatMap(notice => {
|
|
80
|
+
const scope = notice.scope;
|
|
81
|
+
return selected.includes(scope) ? [{ scope, path: notice.path, reason: notice.reason }] : [];
|
|
82
|
+
}) : diagnostics.staleArtifacts;
|
|
83
|
+
const invalidated = available ? String(stale.length) : "unavailable";
|
|
84
|
+
const invalidationLines = stale.length === 0 ? []
|
|
85
|
+
: ["Pending artifact invalidations:", ...stale.map(({ scope, path, reason }) => `- [${scope}] ${path} — ${reason}`)];
|
|
56
86
|
const temporal = available ? diagnostics.temporal : undefined;
|
|
57
87
|
const temporalLines = temporal === undefined
|
|
58
88
|
? [`Temporal materialization unavailable: ${conciseDiagnostic(diagnostics.durableStateError ?? "no selected branch runtime")}`,
|
|
59
89
|
`Hot history: unavailable; configured maximum depth ${diagnostics.historyLimit}`]
|
|
60
|
-
: [`Scope revisions: ${formatScopeRevisionVector(temporal.revisions)}`,
|
|
90
|
+
: [`Scope revisions: ${formatScopeRevisionVector(temporal.revisions, selected)}`,
|
|
61
91
|
`Runtime metadata: step #${snapshot.meta.step}${snapshot.meta.bootstrap ? "; bootstrap" : ""}`,
|
|
62
92
|
`Hot history: offsets 0..${temporal.historyDepth}; maximum depth ${diagnostics.historyLimit}`];
|
|
63
93
|
const artifacts = (scope) => countArtifacts(diagnostics.scopeStates, scope);
|
|
64
|
-
const hasArtifacts = available &&
|
|
94
|
+
const hasArtifacts = available && selected.some((scope) => artifacts(scope) > 0);
|
|
95
|
+
const scopeKeys = [
|
|
96
|
+
...(selected.includes("cwd") ? [`CWD ${diagnostics.cwdScopeKey}`] : []),
|
|
97
|
+
...(selected.includes("session") ? [`session ${diagnostics.sessionScopeKey}`] : []),
|
|
98
|
+
];
|
|
99
|
+
const artifactLabels = { global: "global", cwd: "CWD", session: "session" };
|
|
65
100
|
return [
|
|
66
101
|
`Mode: ${snapshot.config.mode}`,
|
|
102
|
+
...(explicit ? [`Selected scopes: ${requested.join(", ")}`] : []),
|
|
103
|
+
...(available && selected.length !== requested.length ? [`Materialized scopes: ${selected.join(", ")}`] : []),
|
|
67
104
|
...(isActiveRestorationBlocked(snapshot) ? ["Inference blocked: Active memory restoration failed. Use /state-flow-active for current memory or select Passive/Off for native context."] : []),
|
|
68
105
|
`Repository: ${diagnostics.repositoryRoot}`,
|
|
69
|
-
`Scope keys:
|
|
106
|
+
...(scopeKeys.length ? [`Scope keys: ${scopeKeys.join("; ")}`] : []),
|
|
70
107
|
...temporalLines,
|
|
71
108
|
...(diagnostics.publicationError === undefined ? [] : [`Memory writes paused after mode change: ${conciseDiagnostic(diagnostics.publicationError)}`]),
|
|
72
|
-
...(hasArtifacts ? [`Artifacts:
|
|
109
|
+
...(hasArtifacts ? [`Artifacts: ${selected.map(scope => `${artifactLabels[scope]} ${artifacts(scope)}`).join("; ")}; pending invalidations ${invalidated}`] : []),
|
|
73
110
|
...invalidationLines,
|
|
74
|
-
...(available ? scopeMemoryLines(diagnostics.scopeStates) : []),
|
|
111
|
+
...(available ? scopeMemoryLines(diagnostics.scopeStates, selected) : []),
|
|
75
112
|
...(stateJson === undefined
|
|
76
113
|
? ["Effective memory: unavailable"]
|
|
77
114
|
: ["Effective memory:", "", stateJson]),
|