@llblab/pi-kit 0.24.1 → 0.26.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/AGENTS.md +1 -1
- package/BACKLOG.md +5 -1
- package/CHANGELOG.md +12 -0
- package/README.md +11 -8
- package/node_modules/@llblab/pi-actors/AGENTS.md +2 -0
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +4 -1
- package/node_modules/@llblab/pi-actors/LICENSE +21 -0
- package/node_modules/@llblab/pi-actors/README.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +1 -1
- package/node_modules/@llblab/pi-actors/package.json +4 -3
- package/node_modules/@llblab/pi-claude-usage/AGENTS.md +23 -0
- package/node_modules/@llblab/pi-claude-usage/BACKLOG.md +4 -0
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +21 -0
- package/node_modules/@llblab/pi-claude-usage/LICENSE +22 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +155 -0
- package/node_modules/@llblab/pi-claude-usage/banner.jpg +0 -0
- package/node_modules/@llblab/pi-claude-usage/index.ts +8 -0
- package/node_modules/@llblab/pi-claude-usage/lib/extension.ts +30 -0
- package/node_modules/@llblab/pi-claude-usage/lib/fast.ts +24 -0
- package/node_modules/@llblab/pi-claude-usage/lib/query.ts +146 -0
- package/node_modules/@llblab/pi-claude-usage/lib/status-format.ts +297 -0
- package/node_modules/@llblab/pi-claude-usage/lib/status.ts +366 -0
- package/node_modules/@llblab/pi-claude-usage/lib/telegram.ts +44 -0
- package/node_modules/@llblab/pi-claude-usage/lib/usage-store.ts +221 -0
- package/node_modules/@llblab/pi-claude-usage/lib/usage.ts +128 -0
- package/node_modules/@llblab/pi-claude-usage/package.json +64 -0
- package/node_modules/@llblab/pi-clean-room/AGENTS.md +1 -0
- package/node_modules/@llblab/pi-clean-room/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-clean-room/LICENSE +21 -0
- package/node_modules/@llblab/pi-clean-room/README.md +1 -1
- package/node_modules/@llblab/pi-clean-room/package.json +3 -2
- package/node_modules/@llblab/pi-codex-usage/AGENTS.md +9 -6
- package/node_modules/@llblab/pi-codex-usage/BACKLOG.md +2 -1
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +17 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +75 -17
- package/node_modules/@llblab/pi-codex-usage/index.ts +8 -1602
- package/node_modules/@llblab/pi-codex-usage/lib/extension.ts +25 -0
- package/node_modules/@llblab/pi-codex-usage/lib/fast.ts +23 -0
- package/node_modules/@llblab/pi-codex-usage/lib/query.ts +368 -0
- package/node_modules/@llblab/pi-codex-usage/lib/status-format.ts +347 -0
- package/node_modules/@llblab/pi-codex-usage/lib/status.ts +435 -0
- package/node_modules/@llblab/pi-codex-usage/lib/telegram.ts +45 -0
- package/node_modules/@llblab/pi-codex-usage/lib/usage-store.ts +229 -0
- package/node_modules/@llblab/pi-codex-usage/lib/usage.ts +425 -0
- package/node_modules/@llblab/pi-codex-usage/package.json +11 -6
- package/node_modules/@llblab/pi-command-fast/AGENTS.md +7 -0
- package/node_modules/@llblab/pi-command-fast/BACKLOG.md +9 -0
- package/node_modules/@llblab/pi-command-fast/CHANGELOG.md +7 -0
- package/node_modules/@llblab/pi-command-fast/LICENSE +21 -0
- package/node_modules/@llblab/pi-command-fast/README.md +42 -0
- package/node_modules/@llblab/pi-command-fast/dist/command.d.ts +8 -0
- package/node_modules/@llblab/pi-command-fast/dist/command.js +52 -0
- package/node_modules/@llblab/pi-command-fast/dist/index.d.ts +3 -0
- package/node_modules/@llblab/pi-command-fast/dist/index.js +3 -0
- package/node_modules/@llblab/pi-command-fast/dist/models-json.d.ts +10 -0
- package/node_modules/@llblab/pi-command-fast/dist/models-json.js +81 -0
- package/node_modules/@llblab/pi-command-fast/package.json +49 -0
- package/node_modules/@llblab/pi-grow-loop/AGENTS.md +1 -0
- package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +4 -1
- package/node_modules/@llblab/pi-grow-loop/LICENSE +21 -0
- package/node_modules/@llblab/pi-grow-loop/README.md +1 -1
- package/node_modules/@llblab/pi-grow-loop/package.json +3 -2
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +43 -56
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +17 -3
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +25 -0
- package/node_modules/@llblab/pi-state-flow/LICENSE +21 -0
- package/node_modules/@llblab/pi-state-flow/README.md +18 -15
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/index.js +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +7 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +16 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +9 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +5 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +7 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +3 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +3 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +503 -235
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +16 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +11 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +6 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +4 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +1 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +4 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +13 -13
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +7 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +9 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +17 -12
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +13 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +62 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +23 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +52 -20
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +22 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +30 -10
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +5 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +19 -28
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +17 -15
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +52 -52
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +8 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +34 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +13 -19
- package/node_modules/@llblab/pi-state-flow/dist/package.json +12 -11
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -1
- package/node_modules/@llblab/pi-state-flow/docs/agent-contract-relocation.md +72 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +44 -36
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +14 -6
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +7 -5
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +6 -6
- package/node_modules/@llblab/pi-state-flow/docs/performance.md +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +13 -12
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +37 -33
- package/node_modules/@llblab/pi-state-flow/index.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +20 -10
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +15 -14
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +8 -6
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +484 -232
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +14 -5
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +16 -11
- package/node_modules/@llblab/pi-state-flow/lib/logging.ts +5 -1
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +16 -16
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +11 -11
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +19 -13
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +57 -3
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +57 -22
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +46 -13
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +23 -32
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +66 -65
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +39 -19
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +19 -27
- package/node_modules/@llblab/pi-state-flow/package.json +12 -11
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +2 -2
- package/node_modules/jsonc-parser/CHANGELOG.md +76 -0
- package/node_modules/jsonc-parser/LICENSE.md +21 -0
- package/node_modules/jsonc-parser/README.md +364 -0
- package/node_modules/jsonc-parser/SECURITY.md +41 -0
- package/node_modules/jsonc-parser/lib/esm/impl/edit.js +185 -0
- package/node_modules/jsonc-parser/lib/esm/impl/format.js +261 -0
- package/node_modules/jsonc-parser/lib/esm/impl/parser.js +659 -0
- package/node_modules/jsonc-parser/lib/esm/impl/scanner.js +443 -0
- package/node_modules/jsonc-parser/lib/esm/impl/string-intern.js +29 -0
- package/node_modules/jsonc-parser/lib/esm/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/esm/main.js +178 -0
- package/node_modules/jsonc-parser/lib/umd/impl/edit.js +201 -0
- package/node_modules/jsonc-parser/lib/umd/impl/format.js +275 -0
- package/node_modules/jsonc-parser/lib/umd/impl/parser.js +682 -0
- package/node_modules/jsonc-parser/lib/umd/impl/scanner.js +456 -0
- package/node_modules/jsonc-parser/lib/umd/impl/string-intern.js +42 -0
- package/node_modules/jsonc-parser/lib/umd/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/umd/main.js +194 -0
- package/node_modules/jsonc-parser/package.json +37 -0
- package/package.json +10 -6
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./artifact.ts";
|
|
9
9
|
import { applyPatch, isObject, type JsonObject } from "./json.ts";
|
|
10
10
|
|
|
11
|
-
/**
|
|
11
|
+
/** Runtime defaults for documented semantic planes; stored objects may omit them or retain other fields. */
|
|
12
12
|
export type MaterializedState = JsonObject & {
|
|
13
13
|
intents: JsonObject;
|
|
14
14
|
contract: JsonObject;
|
|
@@ -18,6 +18,17 @@ export type MaterializedState = JsonObject & {
|
|
|
18
18
|
lazy: JsonObject;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
/** Sparse semantic state: documented planes may be absent. Disk codecs select only known fields. */
|
|
22
|
+
export type SemanticState = JsonObject & Partial<{
|
|
23
|
+
intents: JsonObject;
|
|
24
|
+
contract: JsonObject;
|
|
25
|
+
working: JsonObject;
|
|
26
|
+
artifacts: ArtifactRegistry;
|
|
27
|
+
response: string;
|
|
28
|
+
lazy: JsonObject;
|
|
29
|
+
}>;
|
|
30
|
+
export type ScopedSemanticStates = Record<StateScope, SemanticState>;
|
|
31
|
+
|
|
21
32
|
/** Compatibility name for callers that still treat materialized state as a document. */
|
|
22
33
|
export type StateDocument = MaterializedState;
|
|
23
34
|
|
|
@@ -79,8 +90,12 @@ export function isMaterializedState(value: unknown): value is MaterializedState
|
|
|
79
90
|
&& isObject(value.working)
|
|
80
91
|
&& isObject(value.intents)
|
|
81
92
|
&& typeof value.response === "string"
|
|
82
|
-
&& isObject(value.lazy)
|
|
83
|
-
|
|
93
|
+
&& isObject(value.lazy);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Missing planes are valid storage, not missing authority. Present values retain their type checks. */
|
|
97
|
+
export function isSemanticState(value: unknown): value is SemanticState {
|
|
98
|
+
return isObject(value) && isMaterializedState({ ...emptyState(), ...value });
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
export const isStateDocument = isMaterializedState;
|
|
@@ -97,12 +112,13 @@ export function updateMaterializedArtifacts(
|
|
|
97
112
|
}
|
|
98
113
|
|
|
99
114
|
/** Overlay lower-to-higher scopes without mutating any scope document. */
|
|
100
|
-
export function overlayStates(...scopes: readonly
|
|
115
|
+
export function overlayStates(...scopes: readonly JsonObject[]): MaterializedState {
|
|
101
116
|
return scopes.reduce<MaterializedState>((effective, scope) => {
|
|
102
117
|
return applyPatch(effective, scope) as MaterializedState;
|
|
103
118
|
}, emptyState());
|
|
104
119
|
}
|
|
105
120
|
|
|
121
|
+
/** Default-bearing SDK compatibility view; model transport uses sparse SemanticState instead. */
|
|
106
122
|
export type ModelState = JsonObject & {
|
|
107
123
|
intents: JsonObject;
|
|
108
124
|
contract: JsonObject;
|
|
@@ -111,14 +127,31 @@ export type ModelState = JsonObject & {
|
|
|
111
127
|
response: string;
|
|
112
128
|
};
|
|
113
129
|
|
|
130
|
+
/** Select only owned top-level fields, preserving nested data and replay deletion markers. */
|
|
131
|
+
export function selectSemanticFields(value: JsonObject): JsonObject {
|
|
132
|
+
return structuredClone(Object.fromEntries(Object.keys(emptyState())
|
|
133
|
+
.filter((key) => Object.hasOwn(value, key))
|
|
134
|
+
.map((key) => [key, value[key]])));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Read only documented, present planes; empty responses carry no semantic value. */
|
|
138
|
+
export function projectSemanticState(state: JsonObject): SemanticState {
|
|
139
|
+
const projected = selectSemanticFields(state);
|
|
140
|
+
if (projected.response === "") delete projected.response;
|
|
141
|
+
return projected;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Preserve deletion meaning in visible history without exposing ignored fields or empty responses. */
|
|
145
|
+
export function projectSemanticPatch(patch: JsonObject): JsonObject {
|
|
146
|
+
const projected = selectSemanticFields(patch);
|
|
147
|
+
if (projected.response === "") projected.response = null;
|
|
148
|
+
return projected;
|
|
149
|
+
}
|
|
150
|
+
|
|
114
151
|
/** Model-visible projection: lazy bodies and runtime artifact bookkeeping stay out of ordinary context. */
|
|
115
|
-
export function projectStateForModel(state:
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
working: cloned.working,
|
|
121
|
-
artifacts: projectArtifactsForModel(cloned.artifacts, artifactHints),
|
|
122
|
-
response: cloned.response,
|
|
123
|
-
};
|
|
152
|
+
export function projectStateForModel(state: SemanticState, artifactHints: ArtifactModelHints = {}): SemanticState {
|
|
153
|
+
const { lazy: _hidden, ...visible } = state;
|
|
154
|
+
const projected = projectSemanticState(visible);
|
|
155
|
+
if (projected.artifacts) projected.artifacts = projectArtifactsForModel(projected.artifacts, artifactHints);
|
|
156
|
+
return projected;
|
|
124
157
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { ArtifactInvalidationReason } from "./artifact.ts";
|
|
2
|
-
import {
|
|
3
|
-
import { retainedMemoryScopes } from "./memory.ts";
|
|
2
|
+
import type { RecentTransitionWindow } from "./history.ts";
|
|
4
3
|
import { conciseDiagnostic } from "./protocol.ts";
|
|
5
4
|
import type { Snapshot } from "./snapshot.ts";
|
|
6
|
-
import { overlayStates, type ScopedStates, type StateScope } from "./state.ts";
|
|
5
|
+
import { overlayStates, projectSemanticState, type SemanticState, type ScopedStates, type StateScope } from "./state.ts";
|
|
7
6
|
import type { ScopeRevisions, TransitionBoundary } from "./temporal.ts";
|
|
8
7
|
|
|
9
8
|
export const STATUS_KEY = "state-flow";
|
|
@@ -23,6 +22,8 @@ export interface StatusDiagnostics {
|
|
|
23
22
|
cwdScopeKey: string;
|
|
24
23
|
sessionScopeKey: string;
|
|
25
24
|
scopeStates: ScopedStates;
|
|
25
|
+
/** Overlay raw scopes before defaults so absent scalar planes cannot mask lower scopes. */
|
|
26
|
+
effectiveState?: SemanticState;
|
|
26
27
|
recent: RecentTransitionWindow;
|
|
27
28
|
historyLimit: number;
|
|
28
29
|
temporal?: { head: TransitionBoundary; historyDepth: number; tailCounts: Record<StateScope, number>; revisions: ScopeRevisions };
|
|
@@ -32,12 +33,13 @@ export interface StatusDiagnostics {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export function formatScopeRevisionVector(revisions: ScopeRevisions): string {
|
|
35
|
-
return `
|
|
36
|
+
return `g${revisions.global}c${revisions.cwd}s${revisions.session}`;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
export function compactStatus(snapshot: Snapshot,
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
export function compactStatus(snapshot: Snapshot, _revisions: ScopeRevisions, colorize: Colorize): string | undefined {
|
|
40
|
+
const { mode } = snapshot.config;
|
|
41
|
+
if (mode === "off") return undefined;
|
|
42
|
+
return `${colorize("accent", "state-flow")} ${colorize("dim", mode)}`;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
function countArtifacts(states: ScopedStates, scope: StateScope): number {
|
|
@@ -45,20 +47,17 @@ function countArtifacts(states: ScopedStates, scope: StateScope): number {
|
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
export function detailedStatus(snapshot: Snapshot, diagnostics: StatusDiagnostics): string {
|
|
48
|
-
const projectedRecent = projectRecentTransitionsWithLimit(
|
|
49
|
-
diagnostics.historyLimit,
|
|
50
|
-
diagnostics.recent,
|
|
51
|
-
);
|
|
52
50
|
const available = diagnostics.temporal !== undefined && diagnostics.durableStateError === undefined;
|
|
53
|
-
const materialized = !available ? undefined : overlayStates(
|
|
51
|
+
const materialized = !available ? undefined : diagnostics.effectiveState ?? projectSemanticState(overlayStates(
|
|
54
52
|
diagnostics.scopeStates.global,
|
|
55
53
|
diagnostics.scopeStates.cwd,
|
|
56
54
|
diagnostics.scopeStates.session,
|
|
57
|
-
);
|
|
58
|
-
|
|
55
|
+
));
|
|
56
|
+
// Only top-level plane boundaries gain whitespace; nested user JSON stays unchanged.
|
|
57
|
+
const stateJson = materialized === undefined ? undefined : JSON.stringify(materialized, null, 2).replace(/,\n(?= ")/g, ",\n\n");
|
|
59
58
|
const invalidated = available ? String(diagnostics.staleArtifacts.length) : "unavailable";
|
|
60
59
|
const invalidationLines = diagnostics.staleArtifacts.length === 0
|
|
61
|
-
? [
|
|
60
|
+
? []
|
|
62
61
|
: [
|
|
63
62
|
"Pending artifact invalidations:",
|
|
64
63
|
...diagnostics.staleArtifacts.map(({ scope, path, reason }) => `- [${scope}] ${path} — ${reason}`),
|
|
@@ -66,30 +65,22 @@ export function detailedStatus(snapshot: Snapshot, diagnostics: StatusDiagnostic
|
|
|
66
65
|
const temporal = available ? diagnostics.temporal : undefined;
|
|
67
66
|
const temporalLines = temporal === undefined
|
|
68
67
|
? [`Temporal materialization unavailable: ${conciseDiagnostic(diagnostics.durableStateError ?? "no selected branch runtime")}`,
|
|
69
|
-
`Hot history: unavailable; configured maximum depth ${diagnostics.historyLimit}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
`
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const artifacts = (scope: StateScope) => available ? countArtifacts(diagnostics.scopeStates, scope) : "unknown";
|
|
76
|
-
const memoryScopes = available ? retainedMemoryScopes(diagnostics.scopeStates) : undefined;
|
|
68
|
+
`Hot history: unavailable; configured maximum depth ${diagnostics.historyLimit}`]
|
|
69
|
+
: [`Scope revisions: ${formatScopeRevisionVector(temporal.revisions)}`,
|
|
70
|
+
`Runtime metadata: step #${snapshot.meta.step}${snapshot.meta.bootstrap ? "; bootstrap" : ""}`,
|
|
71
|
+
`Hot history: offsets 0..${temporal.historyDepth}; maximum depth ${diagnostics.historyLimit}`];
|
|
72
|
+
const artifacts = (scope: StateScope) => countArtifacts(diagnostics.scopeStates, scope);
|
|
73
|
+
const hasArtifacts = available && (["global", "cwd", "session"] as const).some((scope) => artifacts(scope) > 0);
|
|
77
74
|
|
|
78
75
|
return [
|
|
79
|
-
`State Flow diagnostics — config.enabled=${snapshot.config.enabled}; branch mode=${snapshot.config.enabled ? "active" : "inactive"}`,
|
|
80
76
|
`Repository: ${diagnostics.repositoryRoot}`,
|
|
81
77
|
`Scope keys: CWD ${diagnostics.cwdScopeKey}; session ${diagnostics.sessionScopeKey}`,
|
|
82
|
-
"Session files: config.json owns behavior; runtime.json owns branch recovery; meta.json owns scope provenance",
|
|
83
|
-
`Runtime metadata: step #${snapshot.meta.step}; bootstrap ${snapshot.meta.bootstrap === true}`,
|
|
84
|
-
"Memory: owner state-flow; global retention enabled; global fallback active",
|
|
85
|
-
`Memory-bearing scopes: global ${memoryScopes?.global ?? "unknown"}; CWD ${memoryScopes?.cwd ?? "unknown"}; session ${memoryScopes?.session ?? "unknown"}`,
|
|
86
78
|
...temporalLines,
|
|
87
|
-
...(diagnostics.publicationError === undefined ? [] : [`Memory writes paused after
|
|
88
|
-
`Artifacts: global ${artifacts("global")}; CWD ${artifacts("cwd")}; session ${artifacts("session")}; pending invalidations ${invalidated}
|
|
89
|
-
available ? `Recent transitions: global ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "global")).length}; CWD ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "cwd")).length}; session ${diagnostics.recent.filter(({ transitions }) => transitions.some(({ scope }) => scope === "session")).length}; active ${projectedRecent.length}` : "Recent transitions: unavailable",
|
|
79
|
+
...(diagnostics.publicationError === undefined ? [] : [`Memory writes paused after mode change: ${conciseDiagnostic(diagnostics.publicationError)}`]),
|
|
80
|
+
...(hasArtifacts ? [`Artifacts: global ${artifacts("global")}; CWD ${artifacts("cwd")}; session ${artifacts("session")}; pending invalidations ${invalidated}`] : []),
|
|
90
81
|
...invalidationLines,
|
|
91
82
|
...(stateJson === undefined
|
|
92
83
|
? ["Effective memory: unavailable"]
|
|
93
|
-
: [
|
|
84
|
+
: ["Effective memory:", "", stateJson]),
|
|
94
85
|
].join("\n");
|
|
95
86
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// pi-telegram is absent or its registry is not ready, registration fails open and retries.
|
|
5
5
|
|
|
6
6
|
import { conciseDiagnostic, diagnosticText } from "./protocol.ts";
|
|
7
|
+
import type { StateFlowMode } from "./snapshot.ts";
|
|
7
8
|
import { formatScopeRevisionVector } from "./status.ts";
|
|
8
9
|
import type { ScopeRevisions } from "./temporal.ts";
|
|
9
10
|
|
|
@@ -18,7 +19,8 @@ export function stateFlowTelegramSectionSpecifiers(moduleUrl = import.meta.url):
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export interface StateFlowTelegramSnapshot {
|
|
21
|
-
|
|
22
|
+
/** The current session's selected mode. */
|
|
23
|
+
mode: StateFlowMode;
|
|
22
24
|
/** Legacy branch step retained for existing adapter ports; current runtime ports also supply owner revisions. */
|
|
23
25
|
step: number;
|
|
24
26
|
revisions?: ScopeRevisions;
|
|
@@ -29,11 +31,11 @@ export interface StateFlowTelegramSnapshot {
|
|
|
29
31
|
export type StateFlowTelegramScope = "global" | "cwd" | "session" | "effective";
|
|
30
32
|
|
|
31
33
|
export interface StateFlowTelegramState {
|
|
32
|
-
artifacts
|
|
33
|
-
contract
|
|
34
|
-
working
|
|
35
|
-
intents
|
|
36
|
-
response
|
|
34
|
+
artifacts?: Record<string, unknown>;
|
|
35
|
+
contract?: Record<string, unknown>;
|
|
36
|
+
working?: Record<string, unknown>;
|
|
37
|
+
intents?: Record<string, unknown>;
|
|
38
|
+
response?: string;
|
|
37
39
|
lazy?: unknown;
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -110,17 +112,17 @@ export interface StateFlowTelegramPort {
|
|
|
110
112
|
state(scope: StateFlowTelegramScope): StateFlowTelegramState;
|
|
111
113
|
/** Optional additive capability; absent legacy ports retain their branch-step presentation. */
|
|
112
114
|
revisions?(): ScopeRevisions;
|
|
115
|
+
/** Active may need a settled native boundary; inactive modes apply immediately. */
|
|
113
116
|
canStartNow(): boolean;
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
/** Select the current session's mode through the same lifecycle owners as the terminal commands. */
|
|
118
|
+
select(mode: StateFlowMode): StateFlowTelegramControlResult;
|
|
116
119
|
deferStart(): void;
|
|
117
120
|
cancelStart(): void;
|
|
118
121
|
}
|
|
119
122
|
|
|
120
|
-
export interface StateFlowTelegramInspectionPort extends Omit<StateFlowTelegramPort, "state" | "revisions" | "
|
|
123
|
+
export interface StateFlowTelegramInspectionPort extends Omit<StateFlowTelegramPort, "state" | "revisions" | "select"> {
|
|
121
124
|
inspect(scope: StateFlowTelegramScope): StateFlowTelegramInspection | Promise<StateFlowTelegramInspection>;
|
|
122
|
-
|
|
123
|
-
stop(): StateFlowTelegramControlResult | Promise<StateFlowTelegramControlResult>;
|
|
125
|
+
select(mode: StateFlowMode): StateFlowTelegramControlResult | Promise<StateFlowTelegramControlResult>;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
export interface StateFlowTelegramAdapter {
|
|
@@ -128,41 +130,56 @@ export interface StateFlowTelegramAdapter {
|
|
|
128
130
|
dispose(): void;
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
/** Main-menu section label
|
|
133
|
+
/** Main-menu section label shows only the current session mode. */
|
|
132
134
|
export function formatStateFlowSectionLabel(snapshot: StateFlowTelegramSnapshot): string {
|
|
133
|
-
|
|
134
|
-
return `🌀 State Flow: ${snapshot.revisions ? formatScopeRevisionVector(snapshot.revisions) : `#${snapshot.step}`}`;
|
|
135
|
+
return `🌀 State Flow: ${snapshot.mode}`;
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return snapshot.revisions ? formatScopeRevisionVector(snapshot.revisions) : `#${snapshot.step}`;
|
|
141
|
-
}
|
|
138
|
+
export const STATE_FLOW_MODES = ["off", "passive", "active"] as const satisfies readonly StateFlowMode[];
|
|
139
|
+
const STATE_FLOW_MODE_LABELS: Record<StateFlowMode, string> = { off: "Off", passive: "Passive", active: "Active" };
|
|
140
|
+
const STATE_FLOW_SELECTED_MARKERS: Record<StateFlowMode, string> = { off: "🟡", passive: "🟣", active: "🟢" };
|
|
142
141
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return `<b>🌀 State Flow: <code>${stateFlowLabelValue(snapshot)}</code></b>`;
|
|
142
|
+
function isStateFlowModeAction(value: string): value is StateFlowMode {
|
|
143
|
+
return (STATE_FLOW_MODES as readonly string[]).includes(value);
|
|
146
144
|
}
|
|
147
145
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
function modeReceiptNotice(mode: StateFlowMode, result: StateFlowTelegramControlResult): string | undefined {
|
|
147
|
+
if (result.ok && (result.message === `State Flow ${mode}` || result.message === `State Flow is already ${mode}`)) return undefined;
|
|
148
|
+
return result.message;
|
|
149
|
+
}
|
|
151
150
|
|
|
152
|
-
/**
|
|
151
|
+
/** One radio-style mode row followed directly by read-only scope actions. */
|
|
153
152
|
export function buildStateFlowSectionView(
|
|
154
153
|
snapshot: StateFlowTelegramSnapshot,
|
|
155
|
-
callbackData: (action: string) => string,
|
|
154
|
+
callbackData: (action: string, payload?: string) => string,
|
|
156
155
|
): StateFlowTelegramView {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
const modes: StateFlowTelegramButton[] = STATE_FLOW_MODES.map((mode) => ({
|
|
157
|
+
text: `${mode === snapshot.mode ? STATE_FLOW_SELECTED_MARKERS[mode] : "⚫️"} ${STATE_FLOW_MODE_LABELS[mode]}`,
|
|
158
|
+
callback_data: callbackData(mode),
|
|
159
|
+
}));
|
|
160
160
|
return {
|
|
161
|
-
text: [
|
|
161
|
+
text: [
|
|
162
|
+
`<b>🌀 State Flow:</b> <code>${snapshot.mode}</code>`,
|
|
163
|
+
"",
|
|
164
|
+
"<b>Mode</b> — choose a workflow (switching modes never erases stored memory):",
|
|
165
|
+
"",
|
|
166
|
+
"<code>-</code> <code>off</code> (default): regular chat without State Flow memory tools or context.",
|
|
167
|
+
"<code>-</code> <code>passive</code>: regular chat with memory tools; available combined memory enters the agent's context.",
|
|
168
|
+
"<code>-</code> <code>active</code>: same memory access; each completed answer closes a cycle, and the next request starts from saved state, not the full chat.",
|
|
169
|
+
"",
|
|
170
|
+
"<b>Inspect memory</b> — view stored state even in Off:",
|
|
171
|
+
"",
|
|
172
|
+
"<code>-</code> <code>global</code>: memory shared across projects and sessions.",
|
|
173
|
+
"<code>-</code> <code>cwd</code>: memory shared by sessions in this directory.",
|
|
174
|
+
"<code>-</code> <code>session</code>: private memory for this session, kept on resume.",
|
|
175
|
+
"<code>-</code> <code>effective</code>: merged Global, CWD and Session state; the agent can use it when memory is enabled and available.",
|
|
176
|
+
].join("\n"),
|
|
162
177
|
parseMode: "html",
|
|
163
178
|
replyMarkup: { inline_keyboard: [
|
|
164
|
-
|
|
165
|
-
[
|
|
179
|
+
modes,
|
|
180
|
+
...([["global", "cwd"], ["session", "effective"]] as const).map((row) => row.map((scope) => ({
|
|
181
|
+
text: STATE_FLOW_SCOPE_LABELS[scope], callback_data: callbackData("inspect", scope),
|
|
182
|
+
}))),
|
|
166
183
|
] },
|
|
167
184
|
};
|
|
168
185
|
}
|
|
@@ -174,18 +191,6 @@ const STATE_FLOW_SCOPE_LABELS: Record<StateFlowTelegramScope, string> = {
|
|
|
174
191
|
effective: "🧬 Effective",
|
|
175
192
|
};
|
|
176
193
|
|
|
177
|
-
export function buildStateFlowScopeChooser(callbackData: (action: string, payload?: string) => string): StateFlowTelegramView {
|
|
178
|
-
return {
|
|
179
|
-
text: "<b>👁 Show state:</b>",
|
|
180
|
-
parseMode: "html",
|
|
181
|
-
replyMarkup: { inline_keyboard: [
|
|
182
|
-
...(["global", "cwd", "session", "effective"] as const).map((scope) => [
|
|
183
|
-
{ text: STATE_FLOW_SCOPE_LABELS[scope], callback_data: callbackData("inspect", scope) },
|
|
184
|
-
]),
|
|
185
|
-
] },
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
194
|
// The complete message serializes each preformatted field one additional time;
|
|
190
195
|
// 3,000 leaves safe headroom for worst-case JSON escaping across all four fields.
|
|
191
196
|
const STATE_FLOW_TELEGRAM_FIELD_MAX_CHARS = 3_000;
|
|
@@ -230,10 +235,10 @@ export function renderStateFlowRichState(scope: StateFlowTelegramScope, revision
|
|
|
230
235
|
text: [`${STATE_FLOW_SCOPE_LABELS[scope]}: `, { type: "code", text: revision }],
|
|
231
236
|
size: 3,
|
|
232
237
|
},
|
|
233
|
-
...fields.map((field) => ({
|
|
238
|
+
...fields.filter((field) => state[field] !== undefined && state[field] !== "").map((field) => ({
|
|
234
239
|
type: "details" as const,
|
|
235
240
|
summary: { type: "code" as const, text: field },
|
|
236
|
-
blocks: [{ type: "pre" as const, language: "json", text: renderStateFlowTelegramField(state[field]
|
|
241
|
+
blocks: [{ type: "pre" as const, language: "json", text: renderStateFlowTelegramField(state[field]) }],
|
|
237
242
|
})),
|
|
238
243
|
],
|
|
239
244
|
skip_entity_detection: true,
|
|
@@ -251,19 +256,15 @@ function buildStateFlowTelegramSection(port: StateFlowTelegramPort | StateFlowTe
|
|
|
251
256
|
label: "🌀 State Flow",
|
|
252
257
|
getLabel: () => formatStateFlowSectionLabel(port.snapshot()),
|
|
253
258
|
render: (ctx: StateFlowTelegramSectionContext) =>
|
|
254
|
-
buildStateFlowSectionView(port.snapshot(), (action) => ctx.callbackData(action)),
|
|
259
|
+
buildStateFlowSectionView(port.snapshot(), (action, payload) => ctx.callbackData(action, payload)),
|
|
255
260
|
handleCallback: async (ctx: StateFlowTelegramCallbackContext) => {
|
|
256
|
-
//
|
|
257
|
-
|
|
261
|
+
// Keyboards sent by earlier versions re-render (start/stop/refresh) or withdraw deferral (cancel); they change no mode.
|
|
262
|
+
const legacy = ctx.action === "start" || ctx.action === "stop" || ctx.action === "cancel" || ctx.action === "refresh";
|
|
263
|
+
if (!isStateFlowModeAction(ctx.action) && !legacy && ctx.action !== "show-state" && ctx.action !== "inspect" && ctx.action !== "back") return "pass" as const;
|
|
258
264
|
const request = ++interaction;
|
|
259
265
|
let notice: string | undefined;
|
|
260
266
|
let acknowledged = false;
|
|
261
267
|
try {
|
|
262
|
-
if (ctx.action === "show-state") {
|
|
263
|
-
await ctx.answerCallback();
|
|
264
|
-
await ctx.edit(buildStateFlowScopeChooser((action, payload) => ctx.callbackData(action, payload)));
|
|
265
|
-
return "handled" as const;
|
|
266
|
-
}
|
|
267
268
|
if (ctx.action === "inspect") {
|
|
268
269
|
if (!isStateFlowTelegramScope(ctx.payload)) throw new Error("Unknown State Flow scope");
|
|
269
270
|
let observation: StateFlowTelegramInspection;
|
|
@@ -282,21 +283,21 @@ function buildStateFlowTelegramSection(port: StateFlowTelegramPort | StateFlowTe
|
|
|
282
283
|
if (!acknowledged) await ctx.answerCallback();
|
|
283
284
|
return "handled" as const;
|
|
284
285
|
}
|
|
285
|
-
const
|
|
286
|
-
if (
|
|
286
|
+
const mode = isStateFlowModeAction(ctx.action) && (ctx.action !== "active" || port.canStartNow()) ? ctx.action : undefined;
|
|
287
|
+
if (mode) {
|
|
287
288
|
if ("inspect" in port) {
|
|
288
289
|
acknowledged = true;
|
|
289
|
-
//
|
|
290
|
+
// Apply the control immediately and acknowledge in parallel; neither promise can reject unobserved.
|
|
290
291
|
const [, result] = await Promise.all([
|
|
291
|
-
ctx.answerCallback(
|
|
292
|
-
Promise.resolve().then(() => port
|
|
292
|
+
ctx.answerCallback(`Switching State Flow to ${mode}`),
|
|
293
|
+
Promise.resolve().then(() => port.select(mode)),
|
|
293
294
|
]);
|
|
294
295
|
if (result.signal?.aborted) return "handled" as const;
|
|
295
|
-
notice = result
|
|
296
|
-
} else notice = port
|
|
297
|
-
} else if (ctx.action === "
|
|
296
|
+
notice = modeReceiptNotice(mode, result);
|
|
297
|
+
} else notice = modeReceiptNotice(mode, port.select(mode));
|
|
298
|
+
} else if (ctx.action === "active") {
|
|
298
299
|
port.deferStart();
|
|
299
|
-
notice = "State Flow will
|
|
300
|
+
notice = "State Flow will become active after the current turn";
|
|
300
301
|
} else if (ctx.action === "cancel") {
|
|
301
302
|
port.cancelStart();
|
|
302
303
|
notice = "Pending start cancelled";
|
|
@@ -306,7 +307,7 @@ function buildStateFlowTelegramSection(port: StateFlowTelegramPort | StateFlowTe
|
|
|
306
307
|
}
|
|
307
308
|
if (request !== interaction || !isActive()) return "handled" as const;
|
|
308
309
|
const summary = notice === undefined ? undefined : conciseDiagnostic(notice, 200);
|
|
309
|
-
const view = buildStateFlowSectionView(port.snapshot(), (action) => ctx.callbackData(action));
|
|
310
|
+
const view = buildStateFlowSectionView(port.snapshot(), (action, payload) => ctx.callbackData(action, payload));
|
|
310
311
|
if (acknowledged && summary !== undefined) {
|
|
311
312
|
// Callback queries can expire during storage waits; retain errors in the existing menu instead.
|
|
312
313
|
view.text += `\n\n${summary.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT, validateRecentTransition, type RecentScopePatch } from "./history.ts";
|
|
2
2
|
import { applyPatch, containsNull, isJsonValue, isObject, sameJson, type JsonObject } from "./json.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { emptyState, isSemanticState, overlayStates, projectSemanticState, type MaterializedState, type SemanticState, type ScopedSemanticStates, type StateScope } from "./state.ts";
|
|
4
4
|
|
|
5
5
|
/** Owns hot temporal algebra; excludes filesystem, Git, identity allocation, and Pi lifecycle. */
|
|
6
6
|
export interface TransitionBoundary {
|
|
@@ -12,7 +12,7 @@ export interface TransitionBoundary {
|
|
|
12
12
|
|
|
13
13
|
export interface ScopeCheckpoint {
|
|
14
14
|
through: TransitionBoundary;
|
|
15
|
-
state:
|
|
15
|
+
state: SemanticState;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export interface TemporalPatch {
|
|
@@ -52,15 +52,20 @@ function validateBoundary(boundary: TransitionBoundary): void {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
function validateState(state:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
function validateState(state: JsonObject, location?: string): asserts state is SemanticState {
|
|
56
|
+
const json = isJsonValue(state);
|
|
57
|
+
const hasNull = json && isObject(state) && Object.keys(emptyState()).some((key) => containsNull(state[key]));
|
|
58
|
+
if (json && isSemanticState(state) && !hasNull) return;
|
|
59
|
+
const reason = !json ? "expected finite, acyclic JSON data"
|
|
60
|
+
: !isObject(state) ? "expected a semantic object"
|
|
61
|
+
: hasNull ? "null is not allowed"
|
|
62
|
+
: "invalid semantic fields or artifact metadata";
|
|
63
|
+
throw new Error(`Invalid temporal materialized semantic state${location ? ` in ${location}` : ""}: ${reason}`);
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
function apply(state:
|
|
62
|
-
const next = applyPatch(state, patch as JsonObject)
|
|
63
|
-
validateState(next);
|
|
66
|
+
function apply(state: SemanticState, patch: TemporalPatch["patch"], location?: string): SemanticState {
|
|
67
|
+
const next = applyPatch(state, patch as JsonObject);
|
|
68
|
+
validateState(next, location);
|
|
64
69
|
return next;
|
|
65
70
|
}
|
|
66
71
|
|
|
@@ -80,7 +85,7 @@ export function validateScopeStream(value: unknown, scope: StateScope, historyLi
|
|
|
80
85
|
}
|
|
81
86
|
const stream = value as unknown as ScopeStream;
|
|
82
87
|
validateBoundary(stream.checkpoint.through);
|
|
83
|
-
validateState(stream.checkpoint.state);
|
|
88
|
+
validateState(stream.checkpoint.state, `${scope} checkpoint`);
|
|
84
89
|
if (stream.patches.length > historyLimit) throw new Error(`Temporal scope tail exceeds configured history limit ${historyLimit}`);
|
|
85
90
|
if (stream.revision < stream.patches.length) throw new Error("Temporal scope revision predates its retained patch tail");
|
|
86
91
|
let previous = stream.checkpoint.through;
|
|
@@ -98,8 +103,7 @@ export function validateScopeStream(value: unknown, scope: StateScope, historyLi
|
|
|
98
103
|
throw new Error("Disconnected State Flow temporal ancestry");
|
|
99
104
|
}
|
|
100
105
|
validateRecentTransition({ id: record.transition.id, at: 0, transitions: [{ scope, patch: record.patch }] });
|
|
101
|
-
const next = apply(state, record.patch);
|
|
102
|
-
if (sameJson(next, state)) throw new Error("Temporal scope tail contains a semantic no-op");
|
|
106
|
+
const next = apply(state, record.patch, `${scope} tail`);
|
|
103
107
|
state = next;
|
|
104
108
|
previous = record.transition;
|
|
105
109
|
identities.add(previous.id);
|
|
@@ -193,7 +197,7 @@ export function adoptTemporalStreams(scopes: Record<StateScope, ScopeStream>, id
|
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
/** New or migrated state starts at a proven current boundary, with no invented past. */
|
|
196
|
-
export function createTemporalState(states:
|
|
200
|
+
export function createTemporalState(states: ScopedSemanticStates, id: string, historyLimit = DEFAULT_HISTORY_LIMIT): TemporalState {
|
|
197
201
|
const through: TransitionBoundary = { id, position: 0, parent: null };
|
|
198
202
|
const stream = (scope: StateScope): ScopeStream => ({
|
|
199
203
|
revision: 0,
|
|
@@ -205,7 +209,7 @@ export function createTemporalState(states: ScopedStates, id: string, historyLim
|
|
|
205
209
|
return view;
|
|
206
210
|
}
|
|
207
211
|
|
|
208
|
-
function scopeAt(stream: ScopeStream, boundary: TransitionBoundary):
|
|
212
|
+
function scopeAt(stream: ScopeStream, boundary: TransitionBoundary): SemanticState {
|
|
209
213
|
let state = structuredClone(stream.checkpoint.state);
|
|
210
214
|
for (const record of stream.patches) {
|
|
211
215
|
if (record.transition.position > boundary.position) break;
|
|
@@ -280,18 +284,34 @@ export function temporalScopeRevisions(view: TemporalState): ScopeRevisions {
|
|
|
280
284
|
return revisions;
|
|
281
285
|
}
|
|
282
286
|
|
|
283
|
-
|
|
284
|
-
export function readTemporalState(view: TemporalState, offset = 0, scope?: StateScope, historyLimit = DEFAULT_HISTORY_LIMIT): MaterializedState {
|
|
287
|
+
function readBoundary(view: TemporalState, offset: number, historyLimit: number): TransitionBoundary {
|
|
285
288
|
validateHistoryLimit(historyLimit);
|
|
286
289
|
if (!Number.isSafeInteger(offset) || offset < 0 || offset > historyLimit) {
|
|
287
290
|
throw new Error(`State Flow hot-history offset must be an integer from 0 to ${historyLimit}`);
|
|
288
291
|
}
|
|
289
|
-
if (scope !== undefined && !SCOPES.includes(scope)) throw new Error("Unknown temporal scope");
|
|
290
292
|
validateTemporalState(view, historyLimit);
|
|
291
293
|
const boundary = view.lineage[view.lineage.length - 1 - offset];
|
|
292
294
|
if (!boundary) throw new Error("Requested history predates the proven temporal origin");
|
|
293
|
-
|
|
294
|
-
|
|
295
|
+
return boundary;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Exact scope semantics for authored staging; defaults must never become implicit writes. */
|
|
299
|
+
export function readTemporalScopes(view: TemporalState, offset = 0, historyLimit = DEFAULT_HISTORY_LIMIT): ScopedSemanticStates {
|
|
300
|
+
const boundary = readBoundary(view, offset, historyLimit);
|
|
301
|
+
return { global: scopeAt(view.scopes.global, boundary), cwd: scopeAt(view.scopes.cwd, boundary), session: scopeAt(view.scopes.session, boundary) };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Sparse current/historical view: unknown planes and absent values never become effective data. */
|
|
305
|
+
export function readTemporalView(view: TemporalState, offset = 0, scope?: StateScope, historyLimit = DEFAULT_HISTORY_LIMIT): SemanticState {
|
|
306
|
+
if (scope !== undefined && !SCOPES.includes(scope)) throw new Error("Unknown temporal scope");
|
|
307
|
+
const boundary = readBoundary(view, offset, historyLimit);
|
|
308
|
+
const owners = scope === undefined ? SCOPES : [scope];
|
|
309
|
+
return owners.reduce<SemanticState>((state, owner) => applyPatch(state, projectSemanticState(scopeAt(view.scopes[owner], boundary))), {});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** Internal defaulted materialization for consumers that require object registries. */
|
|
313
|
+
export function readTemporalState(view: TemporalState, offset = 0, scope?: StateScope, historyLimit = DEFAULT_HISTORY_LIMIT): MaterializedState {
|
|
314
|
+
return overlayStates(readTemporalView(view, offset, scope, historyLimit));
|
|
295
315
|
}
|
|
296
316
|
|
|
297
317
|
/** Allocate the identity outside this algebra; only materially effective patches accept it. */
|