@llblab/pi-kit 0.24.0 → 0.25.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/CHANGELOG.md +10 -0
- package/README.md +6 -5
- package/node_modules/@llblab/pi-claude-usage/AGENTS.md +20 -0
- package/node_modules/@llblab/pi-claude-usage/BACKLOG.md +3 -0
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +13 -0
- package/node_modules/@llblab/pi-claude-usage/LICENSE +22 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +110 -0
- package/node_modules/@llblab/pi-claude-usage/banner.jpg +0 -0
- package/node_modules/@llblab/pi-claude-usage/index.ts +1159 -0
- package/node_modules/@llblab/pi-claude-usage/package.json +60 -0
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +42 -56
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +16 -3
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +19 -0
- package/node_modules/@llblab/pi-state-flow/README.md +15 -12
- 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/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 +275 -199
- 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 +4 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +17 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +49 -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 +3 -3
- 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 +36 -32
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +12 -4
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +5 -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 +32 -29
- package/node_modules/@llblab/pi-state-flow/index.ts +3 -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 +274 -197
- 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 +6 -3
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +55 -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 +3 -3
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +2 -2
- package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +55 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +21 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +144 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +9 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +19 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +13 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +29 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +16 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +6 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +7 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +13 -5
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +79 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +197 -0
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +33 -0
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +38 -6
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +15 -0
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +6 -1
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +10 -2
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +19 -5
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +7 -3
|
@@ -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. */
|
|
@@ -12,12 +12,13 @@ import { createAcceptedTransition, type AcceptedTransition } from "./history.ts"
|
|
|
12
12
|
import { applyPatch, containsNull, hashJson, isObject, validatePatch, type JsonObject } from "./json.ts";
|
|
13
13
|
import { hasCompiledSkillArtifact, SKILL_ARTIFACT_COMPILER, type SuccessfulSkillRead } from "./skills.ts";
|
|
14
14
|
import type { Snapshot } from "./snapshot.ts";
|
|
15
|
+
import { emptyState } from "./state.ts";
|
|
15
16
|
import type {
|
|
16
17
|
AtomicScopePatches,
|
|
17
|
-
|
|
18
|
+
SemanticState,
|
|
18
19
|
ScopePatch,
|
|
19
20
|
ScopedPatch,
|
|
20
|
-
|
|
21
|
+
ScopedSemanticStates,
|
|
21
22
|
SemanticTransition,
|
|
22
23
|
StateDocument,
|
|
23
24
|
StatePatch,
|
|
@@ -26,7 +27,7 @@ import type {
|
|
|
26
27
|
} from "./state.ts";
|
|
27
28
|
|
|
28
29
|
export interface StagedScopedTransition {
|
|
29
|
-
nextStates:
|
|
30
|
+
nextStates: ScopedSemanticStates;
|
|
30
31
|
stateHashes: Record<StateScope, string>;
|
|
31
32
|
/** Fresh runtime-owned provenance for artifacts compiled in this transition. */
|
|
32
33
|
provenanceUpdates: Record<StateScope, Record<string, ArtifactProvenance>>;
|
|
@@ -126,7 +127,7 @@ function compileReadSkills(
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
function validateMaterializedTransition(nextState: StateDocument, scope: StateScope): void {
|
|
129
|
-
if (containsNull(nextState)) {
|
|
130
|
+
if (Object.keys(emptyState()).some((key) => containsNull(nextState[key]))) {
|
|
130
131
|
throw new Error("Materialized state cannot contain null; use null only as an object-key deletion marker");
|
|
131
132
|
}
|
|
132
133
|
validateArtifactRegistry(nextState.artifacts, `${scope}.artifacts`);
|
|
@@ -156,20 +157,9 @@ function validateScopePatch(scope: unknown, patch: unknown): asserts patch is Sc
|
|
|
156
157
|
if (isObject(patch.artifacts)) validateModelArtifactPatch(patch.artifacts, `${scope}.artifacts`);
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
function completePatch(patch: ScopePatch, response: string): StatePatch {
|
|
160
|
-
return {
|
|
161
|
-
artifacts: patch.artifacts ?? {},
|
|
162
|
-
contract: patch.contract ?? {},
|
|
163
|
-
working: patch.working ?? {},
|
|
164
|
-
intents: patch.intents ?? {},
|
|
165
|
-
response,
|
|
166
|
-
lazy: structuredClone(patch.lazy ?? {}),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
160
|
/** Stage all scope updates against one immutable basis before any state is published. */
|
|
171
161
|
function stageScopedSemanticTransition(
|
|
172
|
-
currentStates:
|
|
162
|
+
currentStates: ScopedSemanticStates,
|
|
173
163
|
transition: SemanticTransition,
|
|
174
164
|
successfulSkillReads: Iterable<SuccessfulSkillRead>,
|
|
175
165
|
causalBasis: string,
|
|
@@ -197,19 +187,21 @@ function stageScopedSemanticTransition(
|
|
|
197
187
|
const provenanceUpdates: Record<StateScope, Record<string, ArtifactProvenance>> = { global: {}, cwd: {}, session: {} };
|
|
198
188
|
for (const scope of SCOPES) {
|
|
199
189
|
const authored = patches.get(scope) ?? {};
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
: currentStates[scope].response;
|
|
203
|
-
const patch = completePatch(authored, response);
|
|
204
|
-
const nextState = applyPatch(currentStates[scope], patch) as MaterializedState;
|
|
190
|
+
const patch = { ...authored, ...(scope === "session" && acceptedResponse !== undefined ? { response: acceptedResponse } : {}) };
|
|
191
|
+
const materialized = applyPatch({ ...emptyState(), ...currentStates[scope] }, patch) as StateDocument;
|
|
205
192
|
compileReadArtifacts(
|
|
206
|
-
|
|
193
|
+
materialized,
|
|
207
194
|
{ artifacts: authored.artifacts ?? {} },
|
|
208
195
|
artifactReads.filter((read) => (read.scope ?? "global") === scope),
|
|
209
196
|
provenanceUpdates[scope],
|
|
210
197
|
);
|
|
211
|
-
compileReadSkills(scope,
|
|
212
|
-
validateMaterializedTransition(
|
|
198
|
+
compileReadSkills(scope, materialized, { artifacts: authored.artifacts ?? {} }, skillReads.filter((read) => read.scope === scope), provenanceUpdates[scope]);
|
|
199
|
+
validateMaterializedTransition(materialized, scope);
|
|
200
|
+
const nextState: SemanticState = materialized;
|
|
201
|
+
for (const key of Object.keys(emptyState())) {
|
|
202
|
+
if (!Object.hasOwn(currentStates[scope], key) && !Object.hasOwn(patch, key)
|
|
203
|
+
&& !(key === "artifacts" && Object.keys(provenanceUpdates[scope]).length > 0)) delete nextState[key];
|
|
204
|
+
}
|
|
213
205
|
nextStates[scope] = nextState;
|
|
214
206
|
}
|
|
215
207
|
return {
|
|
@@ -227,7 +219,7 @@ function stageScopedSemanticTransition(
|
|
|
227
219
|
|
|
228
220
|
/** Stage one canonical atomic scope cohort without changing the finalized response. */
|
|
229
221
|
export function stageAtomicScopePatches(
|
|
230
|
-
currentStates:
|
|
222
|
+
currentStates: ScopedSemanticStates,
|
|
231
223
|
patches: AtomicScopePatches,
|
|
232
224
|
successfulSkillReads: Iterable<SuccessfulSkillRead>,
|
|
233
225
|
causalBasis: string,
|
|
@@ -251,7 +243,7 @@ export function stageAtomicScopePatches(
|
|
|
251
243
|
}
|
|
252
244
|
|
|
253
245
|
export function stageScopedTransition(
|
|
254
|
-
currentStates:
|
|
246
|
+
currentStates: ScopedSemanticStates,
|
|
255
247
|
transition: TerminalTransition,
|
|
256
248
|
successfulSkillReads: Iterable<SuccessfulSkillRead>,
|
|
257
249
|
causalBasis: string,
|
|
@@ -278,7 +270,7 @@ export interface CommitScopedTransitionOptions {
|
|
|
278
270
|
|
|
279
271
|
export function commitScopedTransition(
|
|
280
272
|
snapshot: Snapshot,
|
|
281
|
-
states:
|
|
273
|
+
states: ScopedSemanticStates,
|
|
282
274
|
stage: StagedScopedTransition,
|
|
283
275
|
publishDurable: (accepted: AcceptedTransition | undefined, nextSnapshot: Snapshot) => void,
|
|
284
276
|
causalBasis: string,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-state-flow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Incremental scoped state/context/memory compiler for Pi, inspired by SKILL.state",
|
|
6
6
|
"keywords": [
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"@earendil-works/pi-ai": "0.87.0",
|
|
77
77
|
"@earendil-works/pi-coding-agent": "0.87.0",
|
|
78
78
|
"@earendil-works/pi-tui": "0.87.0",
|
|
79
|
-
"@types/node": "
|
|
80
|
-
"typescript": "
|
|
79
|
+
"@types/node": "^26.4.0",
|
|
80
|
+
"typescript": "^7.0.2"
|
|
81
81
|
}
|
|
82
82
|
}
|
|
@@ -15,7 +15,7 @@ State Flow's on-demand operational reference. Resolve the usage question or iden
|
|
|
15
15
|
|
|
16
16
|
Passive tools access memory without starting an episode. Missing tools or storage are blockers, not permission to enable an episode or bypass storage; explanation alone remains possible.
|
|
17
17
|
|
|
18
|
-
Operator commands: `/state-flow-status` inspects; `/state-flow-
|
|
18
|
+
Operator commands: `/state-flow-status` inspects; `/state-flow-active` selects state-driven episodes; `/state-flow-passive` selects ordinary conversation with both memory tools and existing-state projection; `/state-flow-off` removes both tools and all State Flow context, including frozen handoffs, without deleting memory. Commands and Telegram change only the current session's `mode`. Global `mode` defaults to Off for new sessions and never overrides retained choices. Do not change mode without operator authorization.
|
|
19
19
|
|
|
20
20
|
## Map
|
|
21
21
|
|
|
@@ -56,7 +56,7 @@ Call `patch_state` alone per assistant response; await acceptance before depende
|
|
|
56
56
|
|
|
57
57
|
The runtime waits cancelably for publication ownership, then applies authored Global/CWD operations to current canonical values. Untouched fields survive; overlapping targets follow successful acceptance order. Correct repeats succeed as `State already current.` without another semantic revision. Do not repeat external actions during a memory wait, or rebuild an entire scope from an older snapshot. Session ownership/history fences remain private, not a universal merge.
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
When present, semantic planes `intents`, `contract`, `working`, `artifacts`, and `lazy` are objects; nested lazy values may contain ordinary JSON without stored nulls. Stored checkpoints and patches may omit any documented plane. Current and historical views assemble only known fields present in the selected scopes. Absent fields and empty responses are omitted from views. Checkpoint/tail readers ignore unknown top-level fields, and writers emit only known fields. Nested data within known planes remains intact. Explicit value reads of an absent documented top-level field return `null`. Authored `patch_state` keeps its documented field grammar. Objects merge, arrays/scalars replace, omitted fields persist. Nested `null` removes an owned object key; inherited content may reappear. Canonical `"[N]"` keys patch array elements; indexed deletion is forbidden.
|
|
60
60
|
|
|
61
61
|
Illustrative deletion, only for an actually completed intent and after satisfying pending acquisitions:
|
|
62
62
|
|
|
@@ -125,7 +125,7 @@ The detailed map is canonical in [`docs/architecture.md`](./docs/architecture.md
|
|
|
125
125
|
- Command templates remain compact and shell-free. Use string leaves or ordered `template` arrays; shell operators are not an execution contract. Examples use portable executable placeholders, never machine-local paths.
|
|
126
126
|
- `telegram_attach` is the canonical file path and `telegram_message` the direct Markdown text/buttons path. Both require current direct or registered-follower authority and must not replace the normal active-turn reply.
|
|
127
127
|
- Inbound handlers transform text/media before queueing; outbound handlers precede programmatic/provider fallbacks. Public contracts and ordering live in `docs/inbound.md`, `docs/outbound.md`, and `docs/public-api.md`.
|
|
128
|
-
- Pi integration uses public hooks and APIs. Telegram `/new` is scheduled against the exact durable update, dispatched only after that update is removed from the journal, and then routed through the single `/telegram-internal` Pi gateway via `pi.sendUserMessage(..., { expandPromptTemplates: true })`; only a runtime-armed typed action may execute, manual invocation reports that the command cannot be run manually, and the handler receives the real `ExtensionCommandContext` before calling `ctx.newSession()`. Before replacement, CAS-publish one exact expiring handoff in the profile target snapshot. `workspace-thread` successors re-key the matching Workspace binding; `classic-chat` successors preserve Profile/CWD/session/chat continuity without creating a binding or invoking topic APIs. Both atomically claim the intent before one terminal result, and the old `withSession` path never publishes the same success. Never replace the session while inbound authority is unsettled, store stale command contexts, accept an expired or mismatched handoff, inject terminal input, spawn a shadow Pi process, or mutate session files.
|
|
128
|
+
- Pi integration uses public hooks and APIs. Telegram `/new` is scheduled against the exact durable update, dispatched only after that update is removed from the journal, and then routed through the single `/telegram-internal` Pi gateway via `pi.sendUserMessage(..., { expandPromptTemplates: true })`; only a runtime-armed typed action may execute, manual invocation reports that the command cannot be run manually, and the handler receives the real `ExtensionCommandContext` before calling `ctx.newSession()`. Before replacement, CAS-publish one exact expiring handoff in the profile target snapshot. `workspace-thread` successors re-key the matching Workspace binding; `classic-chat` successors preserve Profile/CWD/session/chat continuity without creating a binding or invoking topic APIs. Both atomically claim the intent before one terminal result, and the old `withSession` path never publishes the same success. A registered follower cannot persist leader-owned state: its Workspace Thread intent is published and claimed by the leader over capability-gated exact-generation bus RPC after leader-side binding validation, and records its source runtime instance so only that follower lineage may re-key or claim it. Never replace the session while inbound authority is unsettled, store stale command contexts, accept an expired or mismatched handoff, inject terminal input, spawn a shadow Pi process, or mutate session files.
|
|
129
129
|
|
|
130
130
|
## 7. Engineering Conventions
|
|
131
131
|
|