@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
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
type ArtifactInvalidationRequest,
|
|
16
16
|
} from "./artifact.ts";
|
|
17
17
|
import { hasCompactionSizedTranscript, planStateFlowCompaction, shouldRequestStateFlowCompaction, stateFlowCompactionResult, type StateFlowCompactionPlan } from "./compaction.ts";
|
|
18
|
-
import { loadStateFlowConfig } from "./config.ts";
|
|
18
|
+
import { inactiveModeFor, loadStateFlowConfig } from "./config.ts";
|
|
19
19
|
import { ContextProjection, contextView, createPassiveContinuation, currentRunTrajectory, passiveContinuationMessages, projectSystemProtocol, runtimeContextHead, syntheticUser, type PassiveContinuation } from "./context.ts";
|
|
20
20
|
import { readNativeSessionHeader } from "./continuation.ts";
|
|
21
21
|
import {
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
sessionScopeKey,
|
|
25
25
|
type SessionAddress,
|
|
26
26
|
} from "./durable.ts";
|
|
27
|
-
import { completeRun, prepareRun, resumeEpisode, startEpisode
|
|
27
|
+
import { completeRun, deactivateEpisode, prepareRun, resumeEpisode, startEpisode } from "./episode.ts";
|
|
28
28
|
import { awaitInFlightBackupPushes, backupCurrentStateFlowFiles, startStateFlowBackupPush } from "./git.ts";
|
|
29
29
|
import { projectRecentTransitionsWithLimit } from "./history.ts";
|
|
30
30
|
import { isObject, presentationJson, sameJson, type JsonObject } from "./json.ts";
|
|
@@ -36,8 +36,8 @@ import type { RehydrationPhase } from "./rehydration.ts";
|
|
|
36
36
|
import { TemporalRuntime, type RuntimePublication } from "./runtime.ts";
|
|
37
37
|
import { discoverSnapshotData, findAssistantToolBatch, findPassiveStopBoundary, hasPriorConversation, hasUncheckpointedConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.ts";
|
|
38
38
|
import { hasCompiledSkillArtifact, hashSkillSource, registeredSkillResolver, SkillReadTracker, type SuccessfulSkillRead } from "./skills.ts";
|
|
39
|
-
import { HistoryBoundaryExpiredError, emptySnapshot, migrationFailure, type RetainedBoundaryCheckpoint, type Snapshot } from "./snapshot.ts";
|
|
40
|
-
import { emptyState,
|
|
39
|
+
import { HistoryBoundaryExpiredError, emptySnapshot, migrationFailure, preRuntimeCheckpoint, type InactiveMode, type RetainedBoundaryCheckpoint, type Snapshot, type StateFlowMode } from "./snapshot.ts";
|
|
40
|
+
import { emptyState, projectStateForModel, type AtomicScopePatches, type SemanticState, type ModelState, type ScopedSemanticStates, type ScopedStates, type StateScope } from "./state.ts";
|
|
41
41
|
import { compactStatus, detailedStatus, STATUS_KEY, type StatusDiagnostics } from "./status.ts";
|
|
42
42
|
import { PublicationBusyError } from "./storage.ts";
|
|
43
43
|
import { createStateFlowTelegramAdapter, type StateFlowTelegramControlResult, type StateFlowTelegramInspection, type StateFlowTelegramLoader, type StateFlowTelegramScope } from "./telegram.ts";
|
|
@@ -49,8 +49,8 @@ export interface StateFlowExtensionOptions {
|
|
|
49
49
|
repositoryRoot?: string;
|
|
50
50
|
onRuntime?: (accessor: { read(offset?: number, scope?: StateScope): ModelState }) => void;
|
|
51
51
|
telegram?: { load?: StateFlowTelegramLoader };
|
|
52
|
-
/** Test/SDK
|
|
53
|
-
|
|
52
|
+
/** Test/SDK override of the default mode for new sessions; repository config remains the Pi default. */
|
|
53
|
+
mode?: StateFlowMode;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export { formatPatchStateArguments };
|
|
@@ -68,15 +68,15 @@ interface InferencePreparation {
|
|
|
68
68
|
|
|
69
69
|
interface BranchRestoration {
|
|
70
70
|
readonly controller: AbortController;
|
|
71
|
-
/** Memory acceptance is independent of the requested
|
|
71
|
+
/** Memory acceptance is independent of the requested session mode. */
|
|
72
72
|
awaitingAcceptance: boolean;
|
|
73
|
-
|
|
73
|
+
requestedMode?: InactiveMode;
|
|
74
74
|
operation?: Promise<void>;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
type BranchSelection =
|
|
78
78
|
| { kind: "settled" }
|
|
79
|
-
| { kind: "current" }
|
|
79
|
+
| { kind: "current"; mode: InactiveMode }
|
|
80
80
|
| { kind: "restore"; checkpoint: RetainedBoundaryCheckpoint }
|
|
81
81
|
| { kind: "fork"; checkpoint: RetainedBoundaryCheckpoint; source: SessionAddress }
|
|
82
82
|
| { kind: "auto-start" };
|
|
@@ -84,17 +84,16 @@ type BranchSelection =
|
|
|
84
84
|
export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowExtensionOptions = {}): void {
|
|
85
85
|
const agentDir = options.agentDir ?? getAgentDir();
|
|
86
86
|
const loadedConfig = loadStateFlowConfig(agentDir, options.repositoryRoot);
|
|
87
|
-
const config =
|
|
88
|
-
...loadedConfig,
|
|
89
|
-
|
|
90
|
-
passiveTools: options.passive?.tools ?? loadedConfig.passiveTools,
|
|
91
|
-
};
|
|
92
|
-
let snapshot: Snapshot = emptySnapshot();
|
|
87
|
+
const config = options.mode === undefined ? loadedConfig
|
|
88
|
+
: { ...loadedConfig, mode: options.mode, inactiveMode: inactiveModeFor(options.mode) };
|
|
89
|
+
let snapshot: Snapshot = emptySnapshot(config.inactiveMode);
|
|
93
90
|
let scopeStates: ScopedStates = { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
91
|
+
let effectiveState: SemanticState = {};
|
|
94
92
|
let branchStartsWithoutRuntime = false;
|
|
95
93
|
let selectedHistoryExpired = false;
|
|
96
|
-
let
|
|
97
|
-
|
|
94
|
+
let modePersistenceError: string | undefined;
|
|
95
|
+
/** One pending inactive-mode persistence; later inactive choices coalesce until it publishes. */
|
|
96
|
+
let inactivePersistence: { controller: AbortController; published?: boolean; operation?: Promise<StateFlowTelegramControlResult> } | undefined;
|
|
98
97
|
let startActivation: { controller: AbortController; operation?: Promise<StateFlowTelegramControlResult> } | undefined;
|
|
99
98
|
let forkInitialization = false;
|
|
100
99
|
let branchRestoration: BranchRestoration | undefined;
|
|
@@ -151,7 +150,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
151
150
|
return new TemporalRuntime(ctx.cwd, sessionAddress(ctx), repositoryRoot, undefined, config.historyLimit);
|
|
152
151
|
}
|
|
153
152
|
|
|
154
|
-
function projectModelState(state:
|
|
153
|
+
function projectModelState(state: SemanticState): SemanticState {
|
|
155
154
|
return projectStateForModel(state, artifactHints);
|
|
156
155
|
}
|
|
157
156
|
|
|
@@ -164,19 +163,28 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
164
163
|
options.onRuntime?.({ read: (offset, scope) => {
|
|
165
164
|
if (scope === "session") assertSelectedBranchAvailable();
|
|
166
165
|
if (!runtime) throw new Error("State Flow temporal runtime is unavailable");
|
|
167
|
-
|
|
166
|
+
const { lazy: _lazy, ...defaults } = emptyState();
|
|
167
|
+
return { ...defaults, ...projectModelState(runtime.readView(offset, scope)) };
|
|
168
168
|
} });
|
|
169
169
|
|
|
170
170
|
function assertPublicationAvailable(): void {
|
|
171
171
|
assertSelectedBranchAvailable();
|
|
172
|
-
if (
|
|
172
|
+
if (modePersistenceError) throw new Error(`Memory writes paused after mode change: ${modePersistenceError}; use /state-flow-active`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isActive(): boolean {
|
|
176
|
+
return snapshot.config.mode === "active";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** An unavailable or pending active selection keeps the configured inactive policy, never an invented one. */
|
|
180
|
+
function inactiveSelection(mode: StateFlowMode): InactiveMode {
|
|
181
|
+
return mode === "active" ? config.inactiveMode : mode;
|
|
173
182
|
}
|
|
174
183
|
|
|
175
184
|
function appendCheckpoint(): void {
|
|
176
|
-
const checkpoint = branchStartsWithoutRuntime ?
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
185
|
+
const checkpoint = branchStartsWithoutRuntime ? preRuntimeCheckpoint(snapshot.config.mode)
|
|
186
|
+
: runtime?.view ? runtime.retainedCheckpoint(snapshot) : undefined;
|
|
187
|
+
if (!checkpoint) throw new Error("State Flow cannot checkpoint an unproven branch; restore a valid checkpoint first");
|
|
180
188
|
pi.appendEntry(SNAPSHOT_ENTRY_TYPE, checkpoint);
|
|
181
189
|
if ("boundary" in checkpoint) branchStartsWithoutRuntime = false;
|
|
182
190
|
}
|
|
@@ -191,10 +199,10 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
191
199
|
ctx.ui.setStatus(STATUS_KEY, compactStatus(snapshot, scopeRevisions(), (color, text) => ctx.ui.theme.fg(color, text)));
|
|
192
200
|
}
|
|
193
201
|
|
|
194
|
-
function
|
|
195
|
-
const pending =
|
|
202
|
+
function cancelInactivePersistence(): Promise<StateFlowTelegramControlResult> | undefined {
|
|
203
|
+
const pending = inactivePersistence;
|
|
196
204
|
pending?.controller.abort();
|
|
197
|
-
|
|
205
|
+
inactivePersistence = undefined;
|
|
198
206
|
return pending?.operation;
|
|
199
207
|
}
|
|
200
208
|
|
|
@@ -277,9 +285,9 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
277
285
|
artifactReads.setCandidates(artifactInvalidations);
|
|
278
286
|
}
|
|
279
287
|
|
|
280
|
-
function missingArtifactRemovals(states:
|
|
288
|
+
function missingArtifactRemovals(states: ScopedSemanticStates): AtomicScopePatches {
|
|
281
289
|
const owners = new Map<string, StateScope[]>();
|
|
282
|
-
for (const scope of ["global", "cwd", "session"] as const) for (const path of Object.keys(states[scope].artifacts)) {
|
|
290
|
+
for (const scope of ["global", "cwd", "session"] as const) for (const path of Object.keys(states[scope].artifacts ?? {})) {
|
|
283
291
|
owners.set(path, [...owners.get(path) ?? [], scope]);
|
|
284
292
|
}
|
|
285
293
|
const removals: AtomicScopePatches = {};
|
|
@@ -296,10 +304,17 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
296
304
|
|
|
297
305
|
function installScopeStates(): void {
|
|
298
306
|
scopeStates = runtime?.view ? runtime.states() : { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
307
|
+
effectiveState = runtime?.view ? runtime.readView() : {};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Active and passive expose both memory tools; Off exposes neither. */
|
|
311
|
+
function memoryToolsAvailable(): boolean {
|
|
312
|
+
return snapshot.config.mode !== "off";
|
|
299
313
|
}
|
|
300
314
|
|
|
301
|
-
|
|
302
|
-
|
|
315
|
+
/** Passive memory context needs a loaded view; Off and Active never inject it. */
|
|
316
|
+
function passiveMemoryAvailable(): boolean {
|
|
317
|
+
return snapshot.config.mode === "passive" && runtime?.view !== undefined;
|
|
303
318
|
}
|
|
304
319
|
|
|
305
320
|
async function inspectTelegramState(scope: StateFlowTelegramScope): Promise<StateFlowTelegramInspection> {
|
|
@@ -307,22 +322,20 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
307
322
|
if (scope === "session" || scope === "effective") assertSelectedBranchAvailable();
|
|
308
323
|
const selected = runtime ??= createRuntime(activeContext);
|
|
309
324
|
const signal = sharedInspectionLifetime.signal;
|
|
310
|
-
if (!selected.view || (scope !== "session" && !
|
|
325
|
+
if (!selected.view || (scope !== "session" && !modePersistenceError)) await selected.refreshShared(signal);
|
|
311
326
|
signal.throwIfAborted();
|
|
312
327
|
if (runtime !== selected) throw new Error("State Flow inspection selection changed; select the scope again");
|
|
313
328
|
if (scope === "session" || scope === "effective") assertSelectedBranchAvailable();
|
|
314
329
|
if (!selected.view) throw new Error("State Flow temporal runtime is unavailable");
|
|
315
330
|
installScopeStates();
|
|
316
|
-
const state = scope === "effective"
|
|
317
|
-
|
|
318
|
-
: scopeStates[scope];
|
|
319
|
-
return { state: { ...projectModelState(state), lazy: structuredClone(state.lazy) }, revisions: scopeRevisions(), signal };
|
|
331
|
+
const state = scope === "effective" ? effectiveState : selected.readView(0, scope);
|
|
332
|
+
return { state: { ...projectModelState(state), ...(state.lazy === undefined ? {} : { lazy: structuredClone(state.lazy) }) }, revisions: scopeRevisions(), signal };
|
|
320
333
|
}
|
|
321
334
|
|
|
322
335
|
function syncStateFlowTools(): void {
|
|
323
336
|
const active = pi.getActiveTools();
|
|
324
337
|
const owned = [PATCH_STATE_TOOL_NAME, READ_STATE_TOOL_NAME];
|
|
325
|
-
const available =
|
|
338
|
+
const available = memoryToolsAvailable();
|
|
326
339
|
if (owned.every((name) => active.includes(name) === available)) return;
|
|
327
340
|
pi.setActiveTools(available
|
|
328
341
|
? [...new Set([...active, ...owned])]
|
|
@@ -374,7 +387,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
374
387
|
if (!selected?.view) throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
375
388
|
await selected.withPatchTransaction((transaction) => {
|
|
376
389
|
signal.throwIfAborted();
|
|
377
|
-
if (runtime !== selected || inferencePreparation !== pending || !
|
|
390
|
+
if (runtime !== selected || inferencePreparation !== pending || !isActive() || shuttingDown) {
|
|
378
391
|
throw new Error("State Flow inference selection changed while awaiting publication");
|
|
379
392
|
}
|
|
380
393
|
assertPublicationAvailable();
|
|
@@ -436,6 +449,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
436
449
|
cwdScopeKey: cwdScopeKey(cwd),
|
|
437
450
|
sessionScopeKey: sessionScopeKey(session.key),
|
|
438
451
|
scopeStates: diagnosticStates,
|
|
452
|
+
effectiveState,
|
|
439
453
|
recent: diagnosticRecent,
|
|
440
454
|
historyLimit: config.historyLimit,
|
|
441
455
|
...(view === undefined ? {} : { temporal: {
|
|
@@ -446,7 +460,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
446
460
|
} }),
|
|
447
461
|
staleArtifacts,
|
|
448
462
|
...(durableStateError === undefined ? {} : { durableStateError }),
|
|
449
|
-
...(
|
|
463
|
+
...(modePersistenceError === undefined ? {} : { publicationError: modePersistenceError }),
|
|
450
464
|
};
|
|
451
465
|
}
|
|
452
466
|
|
|
@@ -465,7 +479,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
465
479
|
// Start-owned attachment/fork recovery keeps its owner; only accepted Start cancels Stop.
|
|
466
480
|
if (!startOwner) {
|
|
467
481
|
cancelStartActivation();
|
|
468
|
-
|
|
482
|
+
cancelInactivePersistence();
|
|
469
483
|
}
|
|
470
484
|
clearRunTransient();
|
|
471
485
|
passiveContinuation = undefined;
|
|
@@ -484,44 +498,56 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
484
498
|
try {
|
|
485
499
|
const branch = ctx.sessionManager.getBranch();
|
|
486
500
|
const fence = retainsPhysicalSessionProjection(sessionStartReason)
|
|
487
|
-
? findPassiveStopBoundary(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE)
|
|
501
|
+
? findPassiveStopBoundary(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE)
|
|
488
502
|
: undefined;
|
|
489
|
-
|
|
490
|
-
if (fence) {
|
|
491
|
-
// The native
|
|
492
|
-
selection = { kind: "current" };
|
|
503
|
+
modePersistenceError = fence?.persistenceError ?? (startOwner ? modePersistenceError : undefined);
|
|
504
|
+
if (fence?.persistenceError) {
|
|
505
|
+
// The native marker owns policy only: read proven memory, never replay or publish a stale selection.
|
|
506
|
+
selection = { kind: "current", mode: fence.mode ?? config.inactiveMode };
|
|
493
507
|
} else {
|
|
494
508
|
const discovery = discoverSnapshotData(branch);
|
|
495
|
-
const selected = selectRetainedCheckpoint(discovery.candidates);
|
|
509
|
+
const selected = selectRetainedCheckpoint(discovery.candidates, config.inactiveMode);
|
|
496
510
|
skipped = discovery.errors.length + selected.skipped.length;
|
|
497
|
-
branchStartsWithoutRuntime = selected.kind === "
|
|
511
|
+
branchStartsWithoutRuntime = selected.kind === "pre-runtime" || (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
498
512
|
if (discovery.candidates.length === 0 && discovery.errors.length > 0) {
|
|
499
|
-
snapshot = migrationFailure({}, `Snapshot restoration failed: ${discovery.errors[0]}
|
|
513
|
+
snapshot = migrationFailure({}, `Snapshot restoration failed: ${discovery.errors[0]}`, config.inactiveMode);
|
|
500
514
|
} else if (selected.kind === "boundary" && forkInitialization) {
|
|
501
515
|
try {
|
|
502
516
|
// Native parent acquisition stays outside canonical exclusion.
|
|
503
517
|
const source = forkSource(ctx);
|
|
504
|
-
const
|
|
505
|
-
selection = { kind: "fork", source, checkpoint:
|
|
518
|
+
const sourceFence = findPassiveStopBoundary(branch, source.id, PASSIVE_STOP_ENTRY_TYPE);
|
|
519
|
+
selection = { kind: "fork", source, checkpoint: sourceFence?.persistenceError !== undefined
|
|
520
|
+
? { ...selected.checkpoint, mode: sourceFence.mode ?? config.inactiveMode } : selected.checkpoint };
|
|
506
521
|
} catch (error) {
|
|
507
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
522
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), inactiveSelection(selected.checkpoint.mode));
|
|
508
523
|
}
|
|
509
524
|
} else if (selected.kind === "boundary") {
|
|
510
525
|
selection = { kind: "restore", checkpoint: selected.checkpoint };
|
|
526
|
+
} else if (selected.kind === "pre-runtime") {
|
|
527
|
+
snapshot = emptySnapshot(selected.mode);
|
|
511
528
|
} else if (discovery.candidates.length > 0) {
|
|
512
|
-
snapshot = selected.
|
|
513
|
-
} else if (
|
|
514
|
-
|
|
529
|
+
snapshot = selected.snapshot;
|
|
530
|
+
} else if (isNewSession(sessionStartReason, branch)) {
|
|
531
|
+
// The repository mode is only a default for genuinely new sessions.
|
|
532
|
+
if (config.mode === "active") selection = { kind: "auto-start" };
|
|
533
|
+
else {
|
|
534
|
+
snapshot = emptySnapshot(config.mode);
|
|
535
|
+
// Adopt the default once without initializing semantic storage.
|
|
536
|
+
appendCheckpoint();
|
|
537
|
+
}
|
|
515
538
|
} else {
|
|
516
|
-
snapshot = emptySnapshot();
|
|
539
|
+
snapshot = emptySnapshot(config.inactiveMode);
|
|
517
540
|
}
|
|
518
541
|
}
|
|
519
542
|
} catch (error) {
|
|
520
543
|
selection = { kind: "settled" };
|
|
521
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
544
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), config.inactiveMode);
|
|
522
545
|
}
|
|
523
546
|
// Pending selection grants neither private reads nor publication until its acceptance installs memory.
|
|
524
|
-
if (selection.kind !== "settled")
|
|
547
|
+
if (selection.kind !== "settled") {
|
|
548
|
+
snapshot = migrationFailure({}, "State Flow branch restoration is pending", selection.kind === "current" ? selection.mode
|
|
549
|
+
: selection.kind === "auto-start" ? config.inactiveMode : inactiveSelection(selection.checkpoint.mode));
|
|
550
|
+
}
|
|
525
551
|
const pending: BranchRestoration = {
|
|
526
552
|
controller: new AbortController(),
|
|
527
553
|
awaitingAcceptance: selection.kind !== "settled" && selection.kind !== "current",
|
|
@@ -578,22 +604,23 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
578
604
|
const current = await candidate.refreshCurrentMemory(signal);
|
|
579
605
|
assertCurrent();
|
|
580
606
|
if (current) runtime = selected = candidate;
|
|
581
|
-
snapshot = current
|
|
607
|
+
snapshot = current ? deactivateEpisode(current, pending.requestedMode ?? selection.mode)
|
|
608
|
+
: migrationFailure({}, "Current State Flow session memory is unavailable", pending.requestedMode ?? selection.mode);
|
|
582
609
|
installScopeStates();
|
|
583
610
|
} else if (selection.kind === "restore") {
|
|
584
611
|
await candidate.withRestoreTransaction(selection.checkpoint, (restored, publish) => {
|
|
585
612
|
assertCurrent();
|
|
586
613
|
// Canceled preparation or boundary continuation may have no specification. Retain uncompiled native context.
|
|
587
|
-
if (restored.config.
|
|
614
|
+
if (restored.config.mode === "active" && restored.meta.specification === undefined
|
|
588
615
|
&& retainsPhysicalSessionProjection(reason) && hasUncheckpointedConversation(ctx.sessionManager.getBranch())) restored.meta.bootstrap = true;
|
|
589
|
-
const next = pending.
|
|
616
|
+
const next = pending.requestedMode ? deactivateEpisode(restored, pending.requestedMode) : restored;
|
|
590
617
|
accept(candidate, next, publish(next), appendCheckpoint);
|
|
591
618
|
}, signal);
|
|
592
619
|
} else if (selection.kind === "fork") {
|
|
593
620
|
await candidate.withForkTransaction(selection.source, selection.checkpoint, (child, publish) => {
|
|
594
621
|
assertCurrent();
|
|
595
622
|
// Mode is selected independently of creating the child's private memory.
|
|
596
|
-
const next = pending.
|
|
623
|
+
const next = pending.requestedMode ? deactivateEpisode(child, pending.requestedMode) : child;
|
|
597
624
|
const publication = publish(next);
|
|
598
625
|
forkInitialization = false;
|
|
599
626
|
accept(candidate, next, publication, () => {
|
|
@@ -609,7 +636,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
609
636
|
const discovery = discoverSnapshotData(branch);
|
|
610
637
|
if (current || discovery.candidates.length > 0 || discovery.errors.length > 0) throw new Error("Existing session runtime requires retained-boundary restoration");
|
|
611
638
|
const activated = startEpisode(hasPriorConversation(branch));
|
|
612
|
-
const next = pending.
|
|
639
|
+
const next = pending.requestedMode ? deactivateEpisode(activated, pending.requestedMode) : activated;
|
|
613
640
|
accept(candidate, next, publish(next), appendCheckpoint);
|
|
614
641
|
}, signal, true);
|
|
615
642
|
}
|
|
@@ -620,7 +647,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
620
647
|
}
|
|
621
648
|
if (!isCurrent()) return;
|
|
622
649
|
if (error instanceof HistoryBoundaryExpiredError) selectedHistoryExpired = true;
|
|
623
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
650
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), snapshot.config.mode === "active" ? config.inactiveMode : snapshot.config.mode);
|
|
624
651
|
installScopeStates();
|
|
625
652
|
} finally {
|
|
626
653
|
pending.awaitingAcceptance = false;
|
|
@@ -628,25 +655,19 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
628
655
|
if (accepted) return;
|
|
629
656
|
}
|
|
630
657
|
settleSelection(ctx, reason, notifyRecovery, skipped, selected !== placeholder);
|
|
631
|
-
if (snapshot.config.
|
|
632
|
-
const passive =
|
|
633
|
-
|
|
634
|
-
try {
|
|
635
|
-
await passive.refreshShared(signal);
|
|
636
|
-
} catch (error) {
|
|
637
|
-
if (isCurrent() && !signal.aborted && notifyRecovery && !stopPersistenceError) notifyProblem(ctx, `State Flow passive memory is unavailable: ${diagnosticText(error)}`, "warning");
|
|
638
|
-
return;
|
|
639
|
-
}
|
|
658
|
+
if (snapshot.config.mode !== "passive" || runtime?.view) return;
|
|
659
|
+
const passive = await loadPassiveView(ctx, signal, isCurrent, notifyRecovery);
|
|
660
|
+
if (passive === undefined) return;
|
|
640
661
|
assertCurrent();
|
|
641
662
|
// An intervening passive patch or inspection owns its newer cache, even on the same physical branch.
|
|
642
|
-
if (
|
|
663
|
+
if (runtime?.view) return;
|
|
643
664
|
runtime = selected = passive;
|
|
644
665
|
installScopeStates();
|
|
645
666
|
updateUi(ctx);
|
|
646
667
|
} catch (error) {
|
|
647
668
|
// Host failures before acceptance leave the selection unavailable, never invented empty memory.
|
|
648
669
|
if (accepted || !isCurrent()) return;
|
|
649
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
670
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), snapshot.config.mode === "active" ? config.inactiveMode : snapshot.config.mode);
|
|
650
671
|
installScopeStates();
|
|
651
672
|
} finally {
|
|
652
673
|
pending.awaitingAcceptance = false;
|
|
@@ -654,29 +675,42 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
654
675
|
}
|
|
655
676
|
}
|
|
656
677
|
|
|
678
|
+
/** Load current shared memory for passive projection without initializing or publishing it. */
|
|
679
|
+
async function loadPassiveView(ctx: ExtensionContext, signal: AbortSignal, isCurrent: () => boolean, notify: boolean): Promise<TemporalRuntime | undefined> {
|
|
680
|
+
const passive = createRuntime(ctx);
|
|
681
|
+
try {
|
|
682
|
+
await passive.refreshShared(signal);
|
|
683
|
+
} catch (error) {
|
|
684
|
+
if (isCurrent() && !signal.aborted && notify && !modePersistenceError) notifyProblem(ctx, `State Flow passive memory is unavailable: ${diagnosticText(error)}`, "warning");
|
|
685
|
+
return undefined;
|
|
686
|
+
}
|
|
687
|
+
return passive;
|
|
688
|
+
}
|
|
689
|
+
|
|
657
690
|
function settleSelection(ctx: ExtensionContext, reason: unknown, notifyRecovery: boolean, skipped: number, selectedMemory: boolean): void {
|
|
658
|
-
const failure = !
|
|
659
|
-
if (failure !== undefined && notifyRecovery && !
|
|
660
|
-
if (selectedHistoryExpired) notifyProblem(ctx, "State Flow history is outside the retained temporal window; /state-flow-
|
|
691
|
+
const failure = !isActive() && snapshot.meta.validation?.attempt === 0 ? snapshot.meta.validation.error : undefined;
|
|
692
|
+
if (failure !== undefined && notifyRecovery && !modePersistenceError) {
|
|
693
|
+
if (selectedHistoryExpired) notifyProblem(ctx, "State Flow history is outside the retained temporal window; /state-flow-active can use current session memory.", "warning");
|
|
661
694
|
else notifyProblem(ctx, `State Flow restore failed: ${failure}`, "error");
|
|
662
695
|
}
|
|
663
696
|
if (selectedMemory && retainsPhysicalSessionProjection(reason) && runtime?.view) {
|
|
664
697
|
const boundary = findPassiveStopBoundary(ctx.sessionManager.getBranch(), ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE);
|
|
665
698
|
if (boundary !== undefined) {
|
|
666
699
|
const continuation = createPassiveContinuation(
|
|
667
|
-
projectModelState(
|
|
700
|
+
projectModelState(effectiveState),
|
|
668
701
|
boundary.at,
|
|
669
702
|
boundary.from,
|
|
670
703
|
boundary.preserveContext,
|
|
671
704
|
);
|
|
672
|
-
|
|
705
|
+
// Off retains the handoff boundary for a later Passive or Active choice but never projects it.
|
|
706
|
+
if (!isActive()) passiveContinuation = continuation;
|
|
673
707
|
else if (snapshot.meta.bootstrap) bootstrapContinuation = continuation;
|
|
674
708
|
}
|
|
675
709
|
}
|
|
676
|
-
if (notifyRecovery &&
|
|
710
|
+
if (notifyRecovery && isActive() && skipped > 0) {
|
|
677
711
|
notifyProblem(ctx, `State Flow skipped ${skipped} malformed snapshot(s); restored the last valid one.`, "warning");
|
|
678
712
|
}
|
|
679
|
-
if (
|
|
713
|
+
if (isActive()) deferInferencePreparation();
|
|
680
714
|
syncStateFlowTools();
|
|
681
715
|
updateUi(ctx);
|
|
682
716
|
}
|
|
@@ -698,7 +732,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
698
732
|
async execute(_toolCallId, params, signal) {
|
|
699
733
|
try {
|
|
700
734
|
type ReadDetails = { path?: string; paths?: string[]; projection?: string; transitionId?: string };
|
|
701
|
-
if (!
|
|
735
|
+
if (!memoryToolsAvailable()) throw new Error("State Flow tools are off for this session");
|
|
702
736
|
if (signal?.aborted) throw new Error("State Flow read was aborted");
|
|
703
737
|
if (!runtime?.view) throw new Error("State Flow temporal runtime is unavailable");
|
|
704
738
|
if (params.path === undefined && params.paths === undefined) throw new Error("read_state requires path or paths");
|
|
@@ -751,7 +785,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
751
785
|
},
|
|
752
786
|
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
753
787
|
try {
|
|
754
|
-
if (!
|
|
788
|
+
if (!memoryToolsAvailable()) throw new Error("State Flow tools are off for this session");
|
|
755
789
|
assertPublicationAvailable();
|
|
756
790
|
if (signal?.aborted) throw new Error("State Flow patch was aborted before materialization");
|
|
757
791
|
if (!isObject(params)) throw new Error("patch_state requires an object");
|
|
@@ -773,10 +807,10 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
773
807
|
const selected = runtime ??= createRuntime(ctx);
|
|
774
808
|
const acquiredArtifacts = structuredClone([...artifactReads.successful.values()]);
|
|
775
809
|
const acquiredSkills = structuredClone([...skillReads.successful.values()]);
|
|
776
|
-
const previousEffective =
|
|
810
|
+
const previousEffective = effectiveState;
|
|
777
811
|
return await selected.withPatchTransaction((transaction) => {
|
|
778
812
|
if (runtime !== selected) throw new Error("State Flow session selection changed while awaiting publication");
|
|
779
|
-
if (!
|
|
813
|
+
if (!memoryToolsAvailable()) throw new Error("State Flow tools are off for this session");
|
|
780
814
|
assertPublicationAvailable();
|
|
781
815
|
for (const acquired of acquiredArtifacts) {
|
|
782
816
|
const observation = inspectRegisteredArtifactPaths([acquired.path])[0];
|
|
@@ -803,7 +837,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
803
837
|
}
|
|
804
838
|
clearAcceptedAcquisitions(new Set(acquiredArtifacts.map(({ path }) => path)));
|
|
805
839
|
updateUi(ctx);
|
|
806
|
-
const updates = contextProjection.acceptPatch(previousEffective,
|
|
840
|
+
const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, artifactHints);
|
|
807
841
|
const acknowledgement = changed
|
|
808
842
|
? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
|
|
809
843
|
: "\nState already current.";
|
|
@@ -830,8 +864,8 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
830
864
|
if (shuttingDown) return Promise.resolve({ ok: false, message: "State Flow is shutting down" });
|
|
831
865
|
if (startActivation?.operation) return startActivation.operation;
|
|
832
866
|
telegramStartPending = false;
|
|
833
|
-
if (activeContext && !branchRestoration &&
|
|
834
|
-
return Promise.resolve({ ok: true, message: "State Flow is already
|
|
867
|
+
if (activeContext && !branchRestoration && isActive()) {
|
|
868
|
+
return Promise.resolve({ ok: true, message: "State Flow is already active", signal: sharedInspectionLifetime.signal });
|
|
835
869
|
}
|
|
836
870
|
const pending: NonNullable<typeof startActivation> = { controller: new AbortController() };
|
|
837
871
|
startActivation = pending;
|
|
@@ -854,16 +888,16 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
854
888
|
&& ctx.sessionManager.getHeader()?.timestamp === timestamp;
|
|
855
889
|
const superseded = (): StateFlowTelegramControlResult => {
|
|
856
890
|
pending.abort();
|
|
857
|
-
return { ok: false, message: "State Flow
|
|
891
|
+
return { ok: false, message: "State Flow activation was superseded", signal: pending.signal };
|
|
858
892
|
};
|
|
859
893
|
try {
|
|
860
894
|
if (!activeContext) await waitForRecovery(restoreActiveBranch(ctx, undefined, false, pending), signal);
|
|
861
895
|
else if (branchRestoration?.operation) await waitForRecovery(branchRestoration.operation, signal);
|
|
862
896
|
if (!isOwner()) return superseded();
|
|
863
|
-
if (
|
|
897
|
+
if (isActive()) return { ok: true, message: "State Flow is already active", signal: sharedInspectionLifetime.signal };
|
|
864
898
|
if (forkInitialization) {
|
|
865
|
-
// Withdraw this join on cancellation without revoking the independently owned
|
|
866
|
-
const stopping =
|
|
899
|
+
// Withdraw this join on cancellation without revoking the independently owned mode persistence.
|
|
900
|
+
const stopping = inactivePersistence?.operation;
|
|
867
901
|
if (stopping) await waitForRecovery(stopping, signal);
|
|
868
902
|
if (!isOwner()) return superseded();
|
|
869
903
|
await waitForRecovery(restoreActiveBranch(ctx, "fork", false, pending), signal);
|
|
@@ -873,7 +907,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
873
907
|
return activateCurrentState(ctx, pending);
|
|
874
908
|
} catch (error) {
|
|
875
909
|
if (!isOwner()) return superseded();
|
|
876
|
-
const message = conciseDiagnostic(`State Flow
|
|
910
|
+
const message = conciseDiagnostic(`State Flow activation failed: ${diagnosticText(error)}`);
|
|
877
911
|
notifyProblem(ctx, message, "error");
|
|
878
912
|
return { ok: false, message, signal: AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]) };
|
|
879
913
|
}
|
|
@@ -885,22 +919,22 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
885
919
|
const cwd = ctx.cwd;
|
|
886
920
|
const file = ctx.sessionManager.getSessionFile();
|
|
887
921
|
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
888
|
-
const
|
|
922
|
+
const initiallyActive = isActive();
|
|
889
923
|
let accepted = false;
|
|
890
924
|
let receipt = AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]);
|
|
891
925
|
const isCurrent = () => startActivation?.controller === pending && runtime === selected && !shuttingDown
|
|
892
926
|
&& ctx.cwd === cwd && ctx.sessionManager.getSessionId() === owner && ctx.sessionManager.getSessionFile() === file
|
|
893
|
-
&& ctx.sessionManager.getHeader()?.timestamp === timestamp &&
|
|
927
|
+
&& ctx.sessionManager.getHeader()?.timestamp === timestamp && isActive() === (accepted || initiallyActive);
|
|
894
928
|
const superseded = (): StateFlowTelegramControlResult => {
|
|
895
929
|
pending.abort();
|
|
896
|
-
return { ok: false, message: "State Flow
|
|
930
|
+
return { ok: false, message: "State Flow activation was superseded", signal: pending.signal };
|
|
897
931
|
};
|
|
898
932
|
try {
|
|
899
933
|
const activation = createRuntime(ctx);
|
|
900
934
|
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
901
935
|
const result = await activation.withStartTransaction((current, publish) => {
|
|
902
936
|
signal.throwIfAborted();
|
|
903
|
-
if (!isCurrent()) throw new Error("State Flow
|
|
937
|
+
if (!isCurrent()) throw new Error("State Flow activation selection changed while awaiting publication");
|
|
904
938
|
if (!current && !branchStartsWithoutRuntime) throw new Error(snapshot.meta.validation?.error ?? "Current State Flow session memory is unavailable");
|
|
905
939
|
const continuation = passiveContinuation ?? bootstrapContinuation;
|
|
906
940
|
const bootstrap = hasPriorConversation(ctx.sessionManager.getBranch()) || passiveContinuation !== undefined;
|
|
@@ -908,12 +942,12 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
908
942
|
const recoveredCurrent = selectedHistoryExpired;
|
|
909
943
|
const publication = publish(activated);
|
|
910
944
|
accepted = true;
|
|
911
|
-
|
|
945
|
+
cancelInactivePersistence();
|
|
912
946
|
runtime = selected = activation;
|
|
913
947
|
snapshot = activated;
|
|
914
948
|
activeContext = ctx;
|
|
915
949
|
selectedHistoryExpired = false;
|
|
916
|
-
|
|
950
|
+
modePersistenceError = undefined;
|
|
917
951
|
installScopeStates();
|
|
918
952
|
clearRunTransient();
|
|
919
953
|
contextProjection.reset();
|
|
@@ -927,29 +961,42 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
927
961
|
appendCheckpoint();
|
|
928
962
|
ctx.ui.notify(
|
|
929
963
|
recoveredCurrent
|
|
930
|
-
? "State Flow
|
|
964
|
+
? "State Flow active from current session memory; unavailable historical state was not restored."
|
|
931
965
|
: snapshot.meta.bootstrap
|
|
932
|
-
? "State Flow
|
|
933
|
-
: "State Flow
|
|
966
|
+
? "State Flow active. The next complete agent run will migrate active context into state."
|
|
967
|
+
: "State Flow active. The next prompt starts a stateful agent run.",
|
|
934
968
|
"info",
|
|
935
969
|
);
|
|
936
|
-
return { ok: true, message: "State Flow
|
|
970
|
+
return { ok: true, message: "State Flow active", signal: receipt };
|
|
937
971
|
}, signal, branchStartsWithoutRuntime);
|
|
938
972
|
return isCurrent() ? result : superseded();
|
|
939
973
|
} catch (error) {
|
|
940
974
|
if (!isCurrent()) return superseded();
|
|
941
|
-
const message = conciseDiagnostic(`${accepted ? "State Flow
|
|
975
|
+
const message = conciseDiagnostic(`${accepted ? "State Flow active; lifecycle update failed" : "State Flow activation failed"}: ${diagnosticText(error)}`);
|
|
942
976
|
notifyProblem(ctx, message, accepted ? "warning" : "error");
|
|
943
977
|
return { ok: accepted, message, signal: receipt };
|
|
944
978
|
}
|
|
945
979
|
}
|
|
946
980
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
981
|
+
const MODE_COMMAND_DESCRIPTIONS: Record<StateFlowMode, string> = {
|
|
982
|
+
active: "Make State Flow active on the current session branch",
|
|
983
|
+
passive: "Use passive State Flow memory on the current session branch",
|
|
984
|
+
off: "Turn State Flow tools and context off on the current session branch",
|
|
985
|
+
};
|
|
986
|
+
|
|
987
|
+
/** Terminal commands and Telegram controls share these lifecycle owners. */
|
|
988
|
+
function selectMode(ctx: ExtensionContext, mode: StateFlowMode): Promise<StateFlowTelegramControlResult> {
|
|
989
|
+
return mode === "active" ? startStateFlow(ctx) : deactivateStateFlow(ctx, mode);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
for (const mode of ["active", "passive", "off"] as const) {
|
|
993
|
+
pi.registerCommand(`state-flow-${mode}`, {
|
|
994
|
+
description: MODE_COMMAND_DESCRIPTIONS[mode],
|
|
995
|
+
handler: async (_args, ctx) => {
|
|
996
|
+
await selectMode(ctx, mode);
|
|
997
|
+
},
|
|
998
|
+
});
|
|
999
|
+
}
|
|
953
1000
|
|
|
954
1001
|
pi.registerCommand("state-flow-status", {
|
|
955
1002
|
description: "Show State Flow runtime status",
|
|
@@ -958,97 +1005,135 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
958
1005
|
},
|
|
959
1006
|
});
|
|
960
1007
|
|
|
961
|
-
|
|
1008
|
+
/** Local policy, tools and UI change before any canonical wait. */
|
|
1009
|
+
function applyInactiveMode(ctx: ExtensionContext, mode: InactiveMode): void {
|
|
1010
|
+
snapshot = deactivateEpisode(snapshot, mode);
|
|
1011
|
+
clearRunTransient();
|
|
1012
|
+
syncStateFlowTools();
|
|
1013
|
+
updateUi(ctx);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function deactivateStateFlow(ctx: ExtensionContext, mode: InactiveMode): Promise<StateFlowTelegramControlResult> {
|
|
962
1017
|
cancelStartActivation();
|
|
963
1018
|
if (shuttingDown) return Promise.resolve({ ok: false, message: "State Flow is shutting down" });
|
|
964
|
-
|
|
1019
|
+
telegramStartPending = false;
|
|
1020
|
+
const persisting = inactivePersistence;
|
|
1021
|
+
if (persisting?.operation && !persisting.published) {
|
|
1022
|
+
// Pending inactive choices coalesce: acceptance persists whichever inactive mode is current then.
|
|
1023
|
+
if (snapshot.config.mode !== mode) applyInactiveMode(ctx, mode);
|
|
1024
|
+
return persisting.operation;
|
|
1025
|
+
}
|
|
965
1026
|
if (!activeContext) void restoreActiveBranch(ctx, undefined, false);
|
|
966
1027
|
const restoring = branchRestoration;
|
|
1028
|
+
// Read-only recovery also retains the latest policy; its native write fence is updated below.
|
|
1029
|
+
if (restoring) restoring.requestedMode = mode;
|
|
967
1030
|
if (restoring?.awaitingAcceptance) {
|
|
968
|
-
//
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1031
|
+
// A mode change never cancels memory restoration or initialization.
|
|
1032
|
+
applyInactiveMode(ctx, mode);
|
|
1033
|
+
return Promise.resolve({ ok: true, message: `State Flow ${mode}; memory restoration continues` });
|
|
1034
|
+
}
|
|
1035
|
+
if (snapshot.config.mode === mode) {
|
|
1036
|
+
const retained = selectRetainedCheckpoint(discoverSnapshotData(ctx.sessionManager.getBranch()).candidates, config.inactiveMode);
|
|
1037
|
+
const recordedMode = retained.kind === "pre-runtime" ? retained.mode : retained.kind === "boundary" ? retained.checkpoint.mode : undefined;
|
|
1038
|
+
if (modePersistenceError || recordedMode === mode) return Promise.resolve({ ok: true, message: `State Flow is already ${mode}` });
|
|
975
1039
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1040
|
+
cancelInactivePersistence();
|
|
1041
|
+
const pending: NonNullable<typeof inactivePersistence> = { controller: new AbortController() };
|
|
1042
|
+
inactivePersistence = pending;
|
|
1043
|
+
const operation = persistInactiveMode(ctx, pending, mode);
|
|
979
1044
|
pending.operation = operation;
|
|
980
|
-
const finished = () => { if (
|
|
1045
|
+
const finished = () => { if (inactivePersistence === pending) inactivePersistence = undefined; };
|
|
981
1046
|
void operation.then(finished, finished);
|
|
982
1047
|
return operation;
|
|
983
1048
|
}
|
|
984
1049
|
|
|
985
|
-
async function
|
|
986
|
-
|
|
987
|
-
const selected = runtime;
|
|
1050
|
+
async function persistInactiveMode(ctx: ExtensionContext, pending: NonNullable<typeof inactivePersistence>, mode: InactiveMode): Promise<StateFlowTelegramControlResult> {
|
|
1051
|
+
let selected = runtime;
|
|
988
1052
|
const owner = ctx.sessionManager.getSessionId();
|
|
989
1053
|
const current = snapshot;
|
|
1054
|
+
const wasActive = current.config.mode === "active";
|
|
990
1055
|
let unfinished = current.meta.specification !== undefined
|
|
991
1056
|
|| (inferencePreparation?.prompt !== undefined && !inferencePreparation.accepted);
|
|
992
|
-
|
|
993
|
-
clearRunTransient();
|
|
994
|
-
syncStateFlowTools();
|
|
995
|
-
updateUi(ctx);
|
|
996
|
-
if (stopPersistenceError) return { ok: true, message: "State Flow disabled; memory writes remain paused" };
|
|
1057
|
+
applyInactiveMode(ctx, mode);
|
|
997
1058
|
const stoppedAt = Date.now();
|
|
1059
|
+
if (modePersistenceError) {
|
|
1060
|
+
// Writes stay paused; the native marker records only the newly selected inactive policy.
|
|
1061
|
+
pending.published = true;
|
|
1062
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, {
|
|
1063
|
+
at: passiveContinuation?.startedAt ?? stoppedAt,
|
|
1064
|
+
...(passiveContinuation?.activeRunStartedAt === undefined ? {} : { from: passiveContinuation.activeRunStartedAt }),
|
|
1065
|
+
preserveContext: true, owner, persistenceError: modePersistenceError, mode,
|
|
1066
|
+
});
|
|
1067
|
+
return { ok: true, message: `State Flow ${mode}; memory writes remain paused` };
|
|
1068
|
+
}
|
|
998
1069
|
const incomingBoundary = current.meta.bootstrap ? bootstrapContinuation : undefined;
|
|
999
1070
|
const anchor = runAnchorTimestamp;
|
|
1000
1071
|
const idle = ctx.isIdle();
|
|
1001
|
-
const isCurrent = () =>
|
|
1002
|
-
&& !shuttingDown && ctx.sessionManager.getSessionId() === owner && !
|
|
1003
|
-
const superseded = (): StateFlowTelegramControlResult => ({ ok: false, message: "State Flow
|
|
1072
|
+
const isCurrent = () => inactivePersistence === pending && runtime === selected
|
|
1073
|
+
&& !shuttingDown && ctx.sessionManager.getSessionId() === owner && !isActive();
|
|
1074
|
+
const superseded = (): StateFlowTelegramControlResult => ({ ok: false, message: "State Flow mode change was superseded" });
|
|
1004
1075
|
const freezeHandoff = (): PassiveContinuation | undefined => {
|
|
1005
1076
|
contextProjection.reset();
|
|
1006
|
-
const handoff = (
|
|
1077
|
+
const handoff = (wasActive || modePersistenceError) && selected?.view && current.meta.validation?.attempt !== 0
|
|
1007
1078
|
? createPassiveContinuation(
|
|
1008
|
-
projectModelState(
|
|
1079
|
+
projectModelState(effectiveState),
|
|
1009
1080
|
incomingBoundary?.startedAt ?? stoppedAt,
|
|
1010
1081
|
incomingBoundary ? incomingBoundary.activeRunStartedAt : !idle || unfinished ? anchor : undefined,
|
|
1011
|
-
|
|
1082
|
+
modePersistenceError !== undefined || (incomingBoundary ? incomingBoundary.preserveContext : current.meta.bootstrap === true || (unfinished && anchor === undefined)),
|
|
1012
1083
|
)
|
|
1013
1084
|
: undefined;
|
|
1014
|
-
|
|
1085
|
+
// Off keeps the frozen boundary for a later Passive/Active choice but never projects it.
|
|
1086
|
+
passiveContinuation = handoff ?? (!wasActive ? passiveContinuation : undefined);
|
|
1015
1087
|
return handoff;
|
|
1016
1088
|
};
|
|
1017
1089
|
const complete = (): StateFlowTelegramControlResult => {
|
|
1090
|
+
pending.published = true;
|
|
1018
1091
|
const exitHandoff = freezeHandoff();
|
|
1019
|
-
|
|
1092
|
+
const selectedMode = snapshot.config.mode;
|
|
1093
|
+
if (exitHandoff || modePersistenceError) pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, {
|
|
1020
1094
|
at: exitHandoff?.startedAt ?? stoppedAt,
|
|
1021
1095
|
...(exitHandoff?.activeRunStartedAt === undefined ? {} : { from: exitHandoff.activeRunStartedAt }),
|
|
1022
|
-
...(exitHandoff?.preserveContext ||
|
|
1023
|
-
...(
|
|
1096
|
+
...(exitHandoff?.preserveContext || modePersistenceError ? { preserveContext: true } : {}),
|
|
1097
|
+
...(modePersistenceError === undefined ? {} : { owner, persistenceError: modePersistenceError, mode: selectedMode }),
|
|
1024
1098
|
});
|
|
1025
|
-
if (!
|
|
1099
|
+
if (!modePersistenceError) {
|
|
1026
1100
|
appendCheckpoint();
|
|
1027
|
-
return { ok: true, message:
|
|
1101
|
+
return { ok: true, message: `State Flow ${selectedMode}` };
|
|
1028
1102
|
}
|
|
1029
|
-
const message = conciseDiagnostic(`State Flow
|
|
1103
|
+
const message = conciseDiagnostic(`State Flow ${selectedMode}; memory writes paused: ${modePersistenceError}`);
|
|
1030
1104
|
notifyProblem(ctx, message, "warning");
|
|
1031
1105
|
return { ok: true, message };
|
|
1032
1106
|
};
|
|
1033
1107
|
let accepted = false;
|
|
1034
1108
|
try {
|
|
1035
|
-
unfinished ||=
|
|
1109
|
+
unfinished ||= wasActive && hasUncheckpointedConversation(ctx.sessionManager.getBranch());
|
|
1036
1110
|
// Projection changes before any wait; capture the old run's boundary before later native input can replace it.
|
|
1037
1111
|
freezeHandoff();
|
|
1038
1112
|
bootstrapContinuation = undefined;
|
|
1039
1113
|
artifactInvalidations = [];
|
|
1040
1114
|
artifactReads.setCandidates([]);
|
|
1041
1115
|
assertPublicationAvailable();
|
|
1042
|
-
|
|
1043
|
-
|
|
1116
|
+
const signal = ctx.signal ? AbortSignal.any([pending.controller.signal, ctx.signal]) : pending.controller.signal;
|
|
1117
|
+
if (branchStartsWithoutRuntime || !selected?.view) {
|
|
1118
|
+
// A pre-runtime choice is native-only; Passive then loads shared memory without initializing storage.
|
|
1119
|
+
const result = complete();
|
|
1120
|
+
if (snapshot.config.mode !== "passive" || runtime?.view) return result;
|
|
1121
|
+
const passive = await loadPassiveView(ctx, signal, isCurrent, true);
|
|
1122
|
+
if (passive && isCurrent() && !signal.aborted && !runtime?.view) {
|
|
1123
|
+
runtime = selected = passive;
|
|
1124
|
+
installScopeStates();
|
|
1125
|
+
updateUi(ctx);
|
|
1126
|
+
}
|
|
1127
|
+
return isCurrent() ? result : superseded();
|
|
1128
|
+
}
|
|
1044
1129
|
const result = await selected.withLifecycleTransaction((publish) => {
|
|
1045
1130
|
signal.throwIfAborted();
|
|
1046
|
-
if (!isCurrent()) throw new Error("State Flow
|
|
1131
|
+
if (!isCurrent()) throw new Error("State Flow mode selection changed while awaiting publication");
|
|
1047
1132
|
assertPublicationAvailable();
|
|
1048
|
-
const
|
|
1049
|
-
const publication = publish(
|
|
1133
|
+
const next = structuredClone(snapshot);
|
|
1134
|
+
const publication = publish(next);
|
|
1050
1135
|
accepted = true;
|
|
1051
|
-
snapshot =
|
|
1136
|
+
snapshot = next;
|
|
1052
1137
|
installScopeStates();
|
|
1053
1138
|
recordPolicyPublication(publication, ctx);
|
|
1054
1139
|
return complete();
|
|
@@ -1058,11 +1143,11 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1058
1143
|
if (!isCurrent()) return superseded();
|
|
1059
1144
|
const cause = diagnosticText(error);
|
|
1060
1145
|
if (accepted) {
|
|
1061
|
-
const message = conciseDiagnostic(`State Flow
|
|
1146
|
+
const message = conciseDiagnostic(`State Flow ${snapshot.config.mode}; lifecycle update failed: ${cause}`);
|
|
1062
1147
|
notifyProblem(ctx, message, "warning");
|
|
1063
1148
|
return { ok: true, message };
|
|
1064
1149
|
}
|
|
1065
|
-
|
|
1150
|
+
modePersistenceError = cause.trim() ? cause : "Canonical State Flow persistence failed";
|
|
1066
1151
|
bootstrapContinuation = undefined;
|
|
1067
1152
|
artifactInvalidations = [];
|
|
1068
1153
|
artifactReads.setCandidates([]);
|
|
@@ -1070,18 +1155,11 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1070
1155
|
}
|
|
1071
1156
|
}
|
|
1072
1157
|
|
|
1073
|
-
pi.registerCommand("state-flow-stop", {
|
|
1074
|
-
description: "Stop State Flow on the current session branch",
|
|
1075
|
-
handler: async (_args, ctx) => {
|
|
1076
|
-
await stopStateFlow(ctx);
|
|
1077
|
-
},
|
|
1078
|
-
});
|
|
1079
|
-
|
|
1080
1158
|
const telegram = createStateFlowTelegramAdapter({
|
|
1081
1159
|
...(options.telegram?.load === undefined ? {} : { load: options.telegram.load }),
|
|
1082
1160
|
port: {
|
|
1083
1161
|
snapshot: () => ({
|
|
1084
|
-
|
|
1162
|
+
mode: snapshot.config.mode,
|
|
1085
1163
|
step: snapshot.meta.step,
|
|
1086
1164
|
revisions: scopeRevisions(),
|
|
1087
1165
|
bootstrap: snapshot.meta.bootstrap === true,
|
|
@@ -1089,13 +1167,11 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1089
1167
|
}),
|
|
1090
1168
|
inspect: inspectTelegramState,
|
|
1091
1169
|
canStartNow: () => activeContext === undefined || activeContext.isIdle(),
|
|
1092
|
-
|
|
1093
|
-
if (!activeContext) throw new Error("State Flow is not attached to an active session yet");
|
|
1094
|
-
return startStateFlow(activeContext);
|
|
1095
|
-
},
|
|
1096
|
-
stop: async () => {
|
|
1170
|
+
select: async (mode) => {
|
|
1097
1171
|
if (!activeContext) throw new Error("State Flow is not attached to an active session yet");
|
|
1098
|
-
const operation =
|
|
1172
|
+
const operation = selectMode(activeContext, mode);
|
|
1173
|
+
if (mode === "active") return operation;
|
|
1174
|
+
// Inactive choices revoke older presentation at once; the receipt belongs to the new lifetime.
|
|
1099
1175
|
const signal = sharedInspectionLifetime.signal;
|
|
1100
1176
|
try {
|
|
1101
1177
|
return { ...await operation, signal };
|
|
@@ -1117,8 +1193,8 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1117
1193
|
pi.on("before_agent_start", (event) => {
|
|
1118
1194
|
// Native run identity is independent of semantic enablement and survives mode toggles.
|
|
1119
1195
|
runAnchorTimestamp = undefined;
|
|
1120
|
-
if (!
|
|
1121
|
-
if (!
|
|
1196
|
+
if (!isActive()) {
|
|
1197
|
+
if (!passiveMemoryAvailable()) return;
|
|
1122
1198
|
(event.systemPromptOptions.sections ??= {}).state_flow = PASSIVE_MEMORY_PROTOCOL;
|
|
1123
1199
|
return;
|
|
1124
1200
|
}
|
|
@@ -1133,20 +1209,23 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1133
1209
|
});
|
|
1134
1210
|
|
|
1135
1211
|
pi.on("context_with_system", (event) => {
|
|
1136
|
-
|
|
1137
|
-
|
|
1212
|
+
// Off removes the owned section, including one contributed before an in-flight mode change.
|
|
1213
|
+
const protocol = isActive() ? stateFlowProtocol(snapshot.meta.bootstrap === true)
|
|
1214
|
+
: passiveMemoryAvailable() ? PASSIVE_MEMORY_PROTOCOL : undefined;
|
|
1138
1215
|
return { messages: projectSystemProtocol(event.messages, protocol) };
|
|
1139
1216
|
});
|
|
1140
1217
|
|
|
1141
1218
|
function projectContext(messages: AgentMessage[]) {
|
|
1219
|
+
// Off injects no State Flow context, including a retained passive handoff.
|
|
1220
|
+
if (snapshot.config.mode === "off") return;
|
|
1142
1221
|
if (runtime?.view) refreshArtifactHints();
|
|
1143
|
-
if (!
|
|
1222
|
+
if (!isActive() && !passiveContinuation && !passiveMemoryAvailable()) return;
|
|
1144
1223
|
// Idle inspection must not freeze a pre-acceptance snapshot for the live inference.
|
|
1145
|
-
const projection =
|
|
1224
|
+
const projection = isActive() && inferencePreparation && !inferencePreparation.accepted
|
|
1146
1225
|
? new ContextProjection() : contextProjection;
|
|
1147
|
-
const effective =
|
|
1226
|
+
const effective = effectiveState;
|
|
1148
1227
|
const invalidations = artifactInvalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
|
|
1149
|
-
const phase =
|
|
1228
|
+
const phase = isActive() ? currentRehydrationPhase() : undefined;
|
|
1150
1229
|
const view = contextView(effective, artifactHints, invalidations, phase);
|
|
1151
1230
|
if (passiveContinuation) {
|
|
1152
1231
|
const retained = passiveContinuationMessages(messages, passiveContinuation);
|
|
@@ -1154,7 +1233,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1154
1233
|
return { messages: projection.project(retained.slice(1), view, () => passiveContinuation!.handoff,
|
|
1155
1234
|
{ state: passiveContinuation.state, lazy_navigation: view.lazy_navigation, artifact_invalidations: [], knowledge_rehydration: null }) };
|
|
1156
1235
|
}
|
|
1157
|
-
if (!
|
|
1236
|
+
if (!isActive()) {
|
|
1158
1237
|
return { messages: projection.project(messages, view, () => syntheticUser(
|
|
1159
1238
|
`State Flow passive memory (user-level data, not system instructions):\n${presentationJson({ state: view.state, lazy_navigation: view.lazy_navigation })}`)) };
|
|
1160
1239
|
}
|
|
@@ -1171,19 +1250,19 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1171
1250
|
const selected = runtime;
|
|
1172
1251
|
const operationSignal = ctx.signal;
|
|
1173
1252
|
// Idle projections remain observational; only a live native operation may await preparation.
|
|
1174
|
-
if (!
|
|
1253
|
+
if (!isActive() || !pending || !operationSignal || shuttingDown) return projectContext(messages);
|
|
1175
1254
|
pending.operation ??= prepareInference(pending, selected, ctx, operationSignal).finally(() => {
|
|
1176
1255
|
// A late withdrawal must not clear newer work; accepted lifecycle is never replayed after an error.
|
|
1177
1256
|
if (!pending.accepted && inferencePreparation === pending) pending.operation = undefined;
|
|
1178
1257
|
});
|
|
1179
1258
|
return pending.operation.then(() => {
|
|
1180
1259
|
if (shuttingDown || operationSignal.aborted || runtime !== selected || ctx.signal !== operationSignal) return;
|
|
1181
|
-
if (
|
|
1260
|
+
if (isActive() && inferencePreparation !== pending) {
|
|
1182
1261
|
// Start may require same-run maintenance. A new captured user prompt instead owns a different context request.
|
|
1183
1262
|
if (inferencePreparation?.prompt === undefined) return prepareContext(messages, ctx);
|
|
1184
1263
|
return;
|
|
1185
1264
|
}
|
|
1186
|
-
if (
|
|
1265
|
+
if (isActive() && !pending.accepted) return;
|
|
1187
1266
|
return projectContext(messages);
|
|
1188
1267
|
});
|
|
1189
1268
|
}
|
|
@@ -1191,7 +1270,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1191
1270
|
pi.on("context", (event, ctx) => prepareContext(event.messages as AgentMessage[], ctx));
|
|
1192
1271
|
|
|
1193
1272
|
pi.on("tool_execution_start", (event) => {
|
|
1194
|
-
if (!
|
|
1273
|
+
if (!isActive()) return;
|
|
1195
1274
|
// Pi emits this before tool_call. Keep the argument object as a fallback;
|
|
1196
1275
|
// tool_call replaces it with the mutable, post-preflight input reference.
|
|
1197
1276
|
skillReads.recordStart(event.toolCallId, event.toolName, event.args);
|
|
@@ -1199,21 +1278,19 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1199
1278
|
});
|
|
1200
1279
|
|
|
1201
1280
|
pi.on("tool_call", (event, ctx) => {
|
|
1202
|
-
if (!
|
|
1281
|
+
if (!isActive()) return;
|
|
1203
1282
|
const batch = findAssistantToolBatch(ctx.sessionManager, event.toolCallId);
|
|
1204
1283
|
const patchCalls = batch?.filter((name) => name === PATCH_STATE_TOOL_NAME).length ?? 0;
|
|
1205
1284
|
if (patchCalls > 0) {
|
|
1206
1285
|
if (patchCalls !== 1) {
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
};
|
|
1286
|
+
const reason = "A State Flow barrier response must contain exactly one patch_state call";
|
|
1287
|
+
recordDiagnostic(reason, "barrier-block", ctx, { tool: event.toolName, toolCallId: event.toolCallId, batchToolNames: batch });
|
|
1288
|
+
return { block: true, reason };
|
|
1211
1289
|
}
|
|
1212
1290
|
if (event.toolName !== PATCH_STATE_TOOL_NAME) {
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
};
|
|
1291
|
+
const reason = "Blocked by the patch_state barrier; reconsider this action after State Flow rematerializes context";
|
|
1292
|
+
recordDiagnostic(reason, "barrier-block", ctx, { tool: event.toolName, toolCallId: event.toolCallId, batchToolNames: batch });
|
|
1293
|
+
return { block: true, reason };
|
|
1217
1294
|
}
|
|
1218
1295
|
}
|
|
1219
1296
|
skillReads.recordCall(event.toolCallId, event.toolName, event.input);
|
|
@@ -1221,14 +1298,14 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1221
1298
|
});
|
|
1222
1299
|
|
|
1223
1300
|
pi.on("tool_execution_end", (event) => {
|
|
1224
|
-
if (!
|
|
1301
|
+
if (!isActive()) return;
|
|
1225
1302
|
skillReads.recordEnd(event.toolCallId, event.toolName, event.isError);
|
|
1226
1303
|
dropCurrentSkillReads();
|
|
1227
1304
|
artifactReads.recordEnd(event.toolCallId, event.toolName, event.isError);
|
|
1228
1305
|
});
|
|
1229
1306
|
|
|
1230
1307
|
pi.on("tool_result", (event) => {
|
|
1231
|
-
if (!
|
|
1308
|
+
if (!isActive()) return;
|
|
1232
1309
|
const read = skillReads.recordResult(event.toolName, event.input, event.isError);
|
|
1233
1310
|
if (!read) return;
|
|
1234
1311
|
dropCurrentSkillReads();
|
|
@@ -1240,7 +1317,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1240
1317
|
pi.on("message_end", (event, ctx): any => {
|
|
1241
1318
|
// Observe actual user events even while disabled; Start/Stop cannot invent or erase them.
|
|
1242
1319
|
if (event.message.role === "user" && runAnchorTimestamp === undefined) runAnchorTimestamp = event.message.timestamp;
|
|
1243
|
-
if (shuttingDown || !
|
|
1320
|
+
if (shuttingDown || !isActive()) return;
|
|
1244
1321
|
if (event.message.role !== "assistant") return;
|
|
1245
1322
|
cancelResponseReconciliation();
|
|
1246
1323
|
const message = event.message as unknown as { role: "assistant"; stopReason?: string; content?: unknown };
|
|
@@ -1255,7 +1332,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1255
1332
|
pi.on("turn_end", async (event, ctx) => {
|
|
1256
1333
|
if (shuttingDown) return;
|
|
1257
1334
|
const pending = responseReconciliation;
|
|
1258
|
-
if (!
|
|
1335
|
+
if (!isActive() || !pending) {
|
|
1259
1336
|
updateUi(ctx);
|
|
1260
1337
|
return;
|
|
1261
1338
|
}
|
|
@@ -1267,7 +1344,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1267
1344
|
if (!selected?.view) throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
1268
1345
|
await selected.withPatchTransaction((transaction) => {
|
|
1269
1346
|
signal.throwIfAborted();
|
|
1270
|
-
if (runtime !== selected || responseReconciliation !== pending || !
|
|
1347
|
+
if (runtime !== selected || responseReconciliation !== pending || !isActive()) {
|
|
1271
1348
|
throw new Error("State Flow response selection changed while awaiting publication");
|
|
1272
1349
|
}
|
|
1273
1350
|
assertPublicationAvailable();
|
|
@@ -1353,12 +1430,12 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1353
1430
|
});
|
|
1354
1431
|
|
|
1355
1432
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
1356
|
-
if (telegramStartPending && !
|
|
1433
|
+
if (telegramStartPending && !isActive()) {
|
|
1357
1434
|
telegramStartPending = false;
|
|
1358
1435
|
await startStateFlow(ctx);
|
|
1359
1436
|
return;
|
|
1360
1437
|
}
|
|
1361
|
-
if (!completedRunAccepted || compactionStopped || !
|
|
1438
|
+
if (!completedRunAccepted || compactionStopped || !isActive() || snapshot.meta.bootstrap
|
|
1362
1439
|
|| compactionInFlight || !ctx.isIdle() || ctx.hasPendingMessages() || !runtime?.view
|
|
1363
1440
|
|| !shouldRequestStateFlowCompaction(ctx.getContextUsage())) return;
|
|
1364
1441
|
completedRunAccepted = false;
|
|
@@ -1392,7 +1469,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1392
1469
|
contextProjection.reset();
|
|
1393
1470
|
const restoring = cancelBranchRestoration();
|
|
1394
1471
|
const starting = cancelStartActivation();
|
|
1395
|
-
const stopping =
|
|
1472
|
+
const stopping = cancelInactivePersistence();
|
|
1396
1473
|
backupLifetime.abort();
|
|
1397
1474
|
cancelInferencePreparation();
|
|
1398
1475
|
cancelResponseReconciliation();
|