@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
|
@@ -7,11 +7,11 @@ import { isAbsolute, join, resolve } from "node:path";
|
|
|
7
7
|
import { ArtifactReadTracker } from "./acquisition.js";
|
|
8
8
|
import { classifyArtifactCompilationNeed, inspectRegisteredArtifactPaths, ORDINARY_ARTIFACT_COMPILER, sameArtifactSourceFingerprint, } from "./artifact.js";
|
|
9
9
|
import { hasCompactionSizedTranscript, planStateFlowCompaction, shouldRequestStateFlowCompaction, stateFlowCompactionResult } from "./compaction.js";
|
|
10
|
-
import { loadStateFlowConfig } from "./config.js";
|
|
10
|
+
import { inactiveModeFor, loadStateFlowConfig } from "./config.js";
|
|
11
11
|
import { ContextProjection, contextView, createPassiveContinuation, currentRunTrajectory, passiveContinuationMessages, projectSystemProtocol, runtimeContextHead, syntheticUser } from "./context.js";
|
|
12
12
|
import { readNativeSessionHeader } from "./continuation.js";
|
|
13
13
|
import { cwdScopeKey, resolveSessionAddress, sessionScopeKey, } from "./durable.js";
|
|
14
|
-
import { completeRun, prepareRun, resumeEpisode, startEpisode
|
|
14
|
+
import { completeRun, deactivateEpisode, prepareRun, resumeEpisode, startEpisode } from "./episode.js";
|
|
15
15
|
import { awaitInFlightBackupPushes, backupCurrentStateFlowFiles, startStateFlowBackupPush } from "./git.js";
|
|
16
16
|
import { projectRecentTransitionsWithLimit } from "./history.js";
|
|
17
17
|
import { isObject, presentationJson, sameJson } from "./json.js";
|
|
@@ -22,8 +22,8 @@ import { selectedBoundaryFailure, selectRetainedCheckpoint, waitForRecovery } fr
|
|
|
22
22
|
import { TemporalRuntime } from "./runtime.js";
|
|
23
23
|
import { discoverSnapshotData, findAssistantToolBatch, findPassiveStopBoundary, hasPriorConversation, hasUncheckpointedConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.js";
|
|
24
24
|
import { hasCompiledSkillArtifact, hashSkillSource, registeredSkillResolver, SkillReadTracker } from "./skills.js";
|
|
25
|
-
import { HistoryBoundaryExpiredError, emptySnapshot, migrationFailure } from "./snapshot.js";
|
|
26
|
-
import { emptyState,
|
|
25
|
+
import { HistoryBoundaryExpiredError, emptySnapshot, migrationFailure, preRuntimeCheckpoint } from "./snapshot.js";
|
|
26
|
+
import { emptyState, projectStateForModel } from "./state.js";
|
|
27
27
|
import { compactStatus, detailedStatus, STATUS_KEY } from "./status.js";
|
|
28
28
|
import { PublicationBusyError } from "./storage.js";
|
|
29
29
|
import { createStateFlowTelegramAdapter } from "./telegram.js";
|
|
@@ -36,17 +36,16 @@ const PASSIVE_STOP_ENTRY_TYPE = "state-flow-passive-stop";
|
|
|
36
36
|
export default function stateFlowExtension(pi, options = {}) {
|
|
37
37
|
const agentDir = options.agentDir ?? getAgentDir();
|
|
38
38
|
const loadedConfig = loadStateFlowConfig(agentDir, options.repositoryRoot);
|
|
39
|
-
const config =
|
|
40
|
-
...loadedConfig,
|
|
41
|
-
|
|
42
|
-
passiveTools: options.passive?.tools ?? loadedConfig.passiveTools,
|
|
43
|
-
};
|
|
44
|
-
let snapshot = emptySnapshot();
|
|
39
|
+
const config = options.mode === undefined ? loadedConfig
|
|
40
|
+
: { ...loadedConfig, mode: options.mode, inactiveMode: inactiveModeFor(options.mode) };
|
|
41
|
+
let snapshot = emptySnapshot(config.inactiveMode);
|
|
45
42
|
let scopeStates = { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
43
|
+
let effectiveState = {};
|
|
46
44
|
let branchStartsWithoutRuntime = false;
|
|
47
45
|
let selectedHistoryExpired = false;
|
|
48
|
-
let
|
|
49
|
-
|
|
46
|
+
let modePersistenceError;
|
|
47
|
+
/** One pending inactive-mode persistence; later inactive choices coalesce until it publishes. */
|
|
48
|
+
let inactivePersistence;
|
|
50
49
|
let startActivation;
|
|
51
50
|
let forkInitialization = false;
|
|
52
51
|
let branchRestoration;
|
|
@@ -113,18 +112,26 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
113
112
|
assertSelectedBranchAvailable();
|
|
114
113
|
if (!runtime)
|
|
115
114
|
throw new Error("State Flow temporal runtime is unavailable");
|
|
116
|
-
|
|
115
|
+
const { lazy: _lazy, ...defaults } = emptyState();
|
|
116
|
+
return { ...defaults, ...projectModelState(runtime.readView(offset, scope)) };
|
|
117
117
|
} });
|
|
118
118
|
function assertPublicationAvailable() {
|
|
119
119
|
assertSelectedBranchAvailable();
|
|
120
|
-
if (
|
|
121
|
-
throw new Error(`Memory writes paused after
|
|
120
|
+
if (modePersistenceError)
|
|
121
|
+
throw new Error(`Memory writes paused after mode change: ${modePersistenceError}; use /state-flow-active`);
|
|
122
|
+
}
|
|
123
|
+
function isActive() {
|
|
124
|
+
return snapshot.config.mode === "active";
|
|
125
|
+
}
|
|
126
|
+
/** An unavailable or pending active selection keeps the configured inactive policy, never an invented one. */
|
|
127
|
+
function inactiveSelection(mode) {
|
|
128
|
+
return mode === "active" ? config.inactiveMode : mode;
|
|
122
129
|
}
|
|
123
130
|
function appendCheckpoint() {
|
|
124
|
-
const checkpoint = branchStartsWithoutRuntime ?
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
const checkpoint = branchStartsWithoutRuntime ? preRuntimeCheckpoint(snapshot.config.mode)
|
|
132
|
+
: runtime?.view ? runtime.retainedCheckpoint(snapshot) : undefined;
|
|
133
|
+
if (!checkpoint)
|
|
134
|
+
throw new Error("State Flow cannot checkpoint an unproven branch; restore a valid checkpoint first");
|
|
128
135
|
pi.appendEntry(SNAPSHOT_ENTRY_TYPE, checkpoint);
|
|
129
136
|
if ("boundary" in checkpoint)
|
|
130
137
|
branchStartsWithoutRuntime = false;
|
|
@@ -137,10 +144,10 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
137
144
|
function updateUi(ctx) {
|
|
138
145
|
ctx.ui.setStatus(STATUS_KEY, compactStatus(snapshot, scopeRevisions(), (color, text) => ctx.ui.theme.fg(color, text)));
|
|
139
146
|
}
|
|
140
|
-
function
|
|
141
|
-
const pending =
|
|
147
|
+
function cancelInactivePersistence() {
|
|
148
|
+
const pending = inactivePersistence;
|
|
142
149
|
pending?.controller.abort();
|
|
143
|
-
|
|
150
|
+
inactivePersistence = undefined;
|
|
144
151
|
return pending?.operation;
|
|
145
152
|
}
|
|
146
153
|
function cancelStartActivation() {
|
|
@@ -223,7 +230,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
223
230
|
function missingArtifactRemovals(states) {
|
|
224
231
|
const owners = new Map();
|
|
225
232
|
for (const scope of ["global", "cwd", "session"])
|
|
226
|
-
for (const path of Object.keys(states[scope].artifacts)) {
|
|
233
|
+
for (const path of Object.keys(states[scope].artifacts ?? {})) {
|
|
227
234
|
owners.set(path, [...owners.get(path) ?? [], scope]);
|
|
228
235
|
}
|
|
229
236
|
const removals = {};
|
|
@@ -239,9 +246,15 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
239
246
|
}
|
|
240
247
|
function installScopeStates() {
|
|
241
248
|
scopeStates = runtime?.view ? runtime.states() : { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
249
|
+
effectiveState = runtime?.view ? runtime.readView() : {};
|
|
242
250
|
}
|
|
243
|
-
|
|
244
|
-
|
|
251
|
+
/** Active and passive expose both memory tools; Off exposes neither. */
|
|
252
|
+
function memoryToolsAvailable() {
|
|
253
|
+
return snapshot.config.mode !== "off";
|
|
254
|
+
}
|
|
255
|
+
/** Passive memory context needs a loaded view; Off and Active never inject it. */
|
|
256
|
+
function passiveMemoryAvailable() {
|
|
257
|
+
return snapshot.config.mode === "passive" && runtime?.view !== undefined;
|
|
245
258
|
}
|
|
246
259
|
async function inspectTelegramState(scope) {
|
|
247
260
|
if (!activeContext || shuttingDown)
|
|
@@ -250,7 +263,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
250
263
|
assertSelectedBranchAvailable();
|
|
251
264
|
const selected = runtime ??= createRuntime(activeContext);
|
|
252
265
|
const signal = sharedInspectionLifetime.signal;
|
|
253
|
-
if (!selected.view || (scope !== "session" && !
|
|
266
|
+
if (!selected.view || (scope !== "session" && !modePersistenceError))
|
|
254
267
|
await selected.refreshShared(signal);
|
|
255
268
|
signal.throwIfAborted();
|
|
256
269
|
if (runtime !== selected)
|
|
@@ -260,15 +273,13 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
260
273
|
if (!selected.view)
|
|
261
274
|
throw new Error("State Flow temporal runtime is unavailable");
|
|
262
275
|
installScopeStates();
|
|
263
|
-
const state = scope === "effective"
|
|
264
|
-
|
|
265
|
-
: scopeStates[scope];
|
|
266
|
-
return { state: { ...projectModelState(state), lazy: structuredClone(state.lazy) }, revisions: scopeRevisions(), signal };
|
|
276
|
+
const state = scope === "effective" ? effectiveState : selected.readView(0, scope);
|
|
277
|
+
return { state: { ...projectModelState(state), ...(state.lazy === undefined ? {} : { lazy: structuredClone(state.lazy) }) }, revisions: scopeRevisions(), signal };
|
|
267
278
|
}
|
|
268
279
|
function syncStateFlowTools() {
|
|
269
280
|
const active = pi.getActiveTools();
|
|
270
281
|
const owned = [PATCH_STATE_TOOL_NAME, READ_STATE_TOOL_NAME];
|
|
271
|
-
const available =
|
|
282
|
+
const available = memoryToolsAvailable();
|
|
272
283
|
if (owned.every((name) => active.includes(name) === available))
|
|
273
284
|
return;
|
|
274
285
|
pi.setActiveTools(available
|
|
@@ -313,7 +324,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
313
324
|
throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
314
325
|
await selected.withPatchTransaction((transaction) => {
|
|
315
326
|
signal.throwIfAborted();
|
|
316
|
-
if (runtime !== selected || inferencePreparation !== pending || !
|
|
327
|
+
if (runtime !== selected || inferencePreparation !== pending || !isActive() || shuttingDown) {
|
|
317
328
|
throw new Error("State Flow inference selection changed while awaiting publication");
|
|
318
329
|
}
|
|
319
330
|
assertPublicationAvailable();
|
|
@@ -379,6 +390,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
379
390
|
cwdScopeKey: cwdScopeKey(cwd),
|
|
380
391
|
sessionScopeKey: sessionScopeKey(session.key),
|
|
381
392
|
scopeStates: diagnosticStates,
|
|
393
|
+
effectiveState,
|
|
382
394
|
recent: diagnosticRecent,
|
|
383
395
|
historyLimit: config.historyLimit,
|
|
384
396
|
...(view === undefined ? {} : { temporal: {
|
|
@@ -389,7 +401,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
389
401
|
} }),
|
|
390
402
|
staleArtifacts,
|
|
391
403
|
...(durableStateError === undefined ? {} : { durableStateError }),
|
|
392
|
-
...(
|
|
404
|
+
...(modePersistenceError === undefined ? {} : { publicationError: modePersistenceError }),
|
|
393
405
|
};
|
|
394
406
|
}
|
|
395
407
|
function forkSource(ctx) {
|
|
@@ -408,7 +420,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
408
420
|
// Start-owned attachment/fork recovery keeps its owner; only accepted Start cancels Stop.
|
|
409
421
|
if (!startOwner) {
|
|
410
422
|
cancelStartActivation();
|
|
411
|
-
|
|
423
|
+
cancelInactivePersistence();
|
|
412
424
|
}
|
|
413
425
|
clearRunTransient();
|
|
414
426
|
passiveContinuation = undefined;
|
|
@@ -427,53 +439,66 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
427
439
|
try {
|
|
428
440
|
const branch = ctx.sessionManager.getBranch();
|
|
429
441
|
const fence = retainsPhysicalSessionProjection(sessionStartReason)
|
|
430
|
-
? findPassiveStopBoundary(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE)
|
|
442
|
+
? findPassiveStopBoundary(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE)
|
|
431
443
|
: undefined;
|
|
432
|
-
|
|
433
|
-
if (fence) {
|
|
434
|
-
// The native
|
|
435
|
-
selection = { kind: "current" };
|
|
444
|
+
modePersistenceError = fence?.persistenceError ?? (startOwner ? modePersistenceError : undefined);
|
|
445
|
+
if (fence?.persistenceError) {
|
|
446
|
+
// The native marker owns policy only: read proven memory, never replay or publish a stale selection.
|
|
447
|
+
selection = { kind: "current", mode: fence.mode ?? config.inactiveMode };
|
|
436
448
|
}
|
|
437
449
|
else {
|
|
438
450
|
const discovery = discoverSnapshotData(branch);
|
|
439
|
-
const selected = selectRetainedCheckpoint(discovery.candidates);
|
|
451
|
+
const selected = selectRetainedCheckpoint(discovery.candidates, config.inactiveMode);
|
|
440
452
|
skipped = discovery.errors.length + selected.skipped.length;
|
|
441
|
-
branchStartsWithoutRuntime = selected.kind === "
|
|
453
|
+
branchStartsWithoutRuntime = selected.kind === "pre-runtime" || (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
442
454
|
if (discovery.candidates.length === 0 && discovery.errors.length > 0) {
|
|
443
|
-
snapshot = migrationFailure({}, `Snapshot restoration failed: ${discovery.errors[0]}
|
|
455
|
+
snapshot = migrationFailure({}, `Snapshot restoration failed: ${discovery.errors[0]}`, config.inactiveMode);
|
|
444
456
|
}
|
|
445
457
|
else if (selected.kind === "boundary" && forkInitialization) {
|
|
446
458
|
try {
|
|
447
459
|
// Native parent acquisition stays outside canonical exclusion.
|
|
448
460
|
const source = forkSource(ctx);
|
|
449
|
-
const
|
|
450
|
-
selection = { kind: "fork", source, checkpoint:
|
|
461
|
+
const sourceFence = findPassiveStopBoundary(branch, source.id, PASSIVE_STOP_ENTRY_TYPE);
|
|
462
|
+
selection = { kind: "fork", source, checkpoint: sourceFence?.persistenceError !== undefined
|
|
463
|
+
? { ...selected.checkpoint, mode: sourceFence.mode ?? config.inactiveMode } : selected.checkpoint };
|
|
451
464
|
}
|
|
452
465
|
catch (error) {
|
|
453
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
466
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), inactiveSelection(selected.checkpoint.mode));
|
|
454
467
|
}
|
|
455
468
|
}
|
|
456
469
|
else if (selected.kind === "boundary") {
|
|
457
470
|
selection = { kind: "restore", checkpoint: selected.checkpoint };
|
|
458
471
|
}
|
|
472
|
+
else if (selected.kind === "pre-runtime") {
|
|
473
|
+
snapshot = emptySnapshot(selected.mode);
|
|
474
|
+
}
|
|
459
475
|
else if (discovery.candidates.length > 0) {
|
|
460
|
-
snapshot = selected.
|
|
476
|
+
snapshot = selected.snapshot;
|
|
461
477
|
}
|
|
462
|
-
else if (
|
|
463
|
-
|
|
478
|
+
else if (isNewSession(sessionStartReason, branch)) {
|
|
479
|
+
// The repository mode is only a default for genuinely new sessions.
|
|
480
|
+
if (config.mode === "active")
|
|
481
|
+
selection = { kind: "auto-start" };
|
|
482
|
+
else {
|
|
483
|
+
snapshot = emptySnapshot(config.mode);
|
|
484
|
+
// Adopt the default once without initializing semantic storage.
|
|
485
|
+
appendCheckpoint();
|
|
486
|
+
}
|
|
464
487
|
}
|
|
465
488
|
else {
|
|
466
|
-
snapshot = emptySnapshot();
|
|
489
|
+
snapshot = emptySnapshot(config.inactiveMode);
|
|
467
490
|
}
|
|
468
491
|
}
|
|
469
492
|
}
|
|
470
493
|
catch (error) {
|
|
471
494
|
selection = { kind: "settled" };
|
|
472
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
495
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), config.inactiveMode);
|
|
473
496
|
}
|
|
474
497
|
// Pending selection grants neither private reads nor publication until its acceptance installs memory.
|
|
475
|
-
if (selection.kind !== "settled")
|
|
476
|
-
snapshot = migrationFailure({}, "State Flow branch restoration is pending"
|
|
498
|
+
if (selection.kind !== "settled") {
|
|
499
|
+
snapshot = migrationFailure({}, "State Flow branch restoration is pending", selection.kind === "current" ? selection.mode
|
|
500
|
+
: selection.kind === "auto-start" ? config.inactiveMode : inactiveSelection(selection.checkpoint.mode));
|
|
501
|
+
}
|
|
477
502
|
const pending = {
|
|
478
503
|
controller: new AbortController(),
|
|
479
504
|
awaitingAcceptance: selection.kind !== "settled" && selection.kind !== "current",
|
|
@@ -534,17 +559,18 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
534
559
|
assertCurrent();
|
|
535
560
|
if (current)
|
|
536
561
|
runtime = selected = candidate;
|
|
537
|
-
snapshot = current
|
|
562
|
+
snapshot = current ? deactivateEpisode(current, pending.requestedMode ?? selection.mode)
|
|
563
|
+
: migrationFailure({}, "Current State Flow session memory is unavailable", pending.requestedMode ?? selection.mode);
|
|
538
564
|
installScopeStates();
|
|
539
565
|
}
|
|
540
566
|
else if (selection.kind === "restore") {
|
|
541
567
|
await candidate.withRestoreTransaction(selection.checkpoint, (restored, publish) => {
|
|
542
568
|
assertCurrent();
|
|
543
569
|
// Canceled preparation or boundary continuation may have no specification. Retain uncompiled native context.
|
|
544
|
-
if (restored.config.
|
|
570
|
+
if (restored.config.mode === "active" && restored.meta.specification === undefined
|
|
545
571
|
&& retainsPhysicalSessionProjection(reason) && hasUncheckpointedConversation(ctx.sessionManager.getBranch()))
|
|
546
572
|
restored.meta.bootstrap = true;
|
|
547
|
-
const next = pending.
|
|
573
|
+
const next = pending.requestedMode ? deactivateEpisode(restored, pending.requestedMode) : restored;
|
|
548
574
|
accept(candidate, next, publish(next), appendCheckpoint);
|
|
549
575
|
}, signal);
|
|
550
576
|
}
|
|
@@ -552,7 +578,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
552
578
|
await candidate.withForkTransaction(selection.source, selection.checkpoint, (child, publish) => {
|
|
553
579
|
assertCurrent();
|
|
554
580
|
// Mode is selected independently of creating the child's private memory.
|
|
555
|
-
const next = pending.
|
|
581
|
+
const next = pending.requestedMode ? deactivateEpisode(child, pending.requestedMode) : child;
|
|
556
582
|
const publication = publish(next);
|
|
557
583
|
forkInitialization = false;
|
|
558
584
|
accept(candidate, next, publication, () => {
|
|
@@ -570,7 +596,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
570
596
|
if (current || discovery.candidates.length > 0 || discovery.errors.length > 0)
|
|
571
597
|
throw new Error("Existing session runtime requires retained-boundary restoration");
|
|
572
598
|
const activated = startEpisode(hasPriorConversation(branch));
|
|
573
|
-
const next = pending.
|
|
599
|
+
const next = pending.requestedMode ? deactivateEpisode(activated, pending.requestedMode) : activated;
|
|
574
600
|
accept(candidate, next, publish(next), appendCheckpoint);
|
|
575
601
|
}, signal, true);
|
|
576
602
|
}
|
|
@@ -585,7 +611,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
585
611
|
return;
|
|
586
612
|
if (error instanceof HistoryBoundaryExpiredError)
|
|
587
613
|
selectedHistoryExpired = true;
|
|
588
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
614
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), snapshot.config.mode === "active" ? config.inactiveMode : snapshot.config.mode);
|
|
589
615
|
installScopeStates();
|
|
590
616
|
}
|
|
591
617
|
finally {
|
|
@@ -595,21 +621,14 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
595
621
|
return;
|
|
596
622
|
}
|
|
597
623
|
settleSelection(ctx, reason, notifyRecovery, skipped, selected !== placeholder);
|
|
598
|
-
if (snapshot.config.
|
|
624
|
+
if (snapshot.config.mode !== "passive" || runtime?.view)
|
|
599
625
|
return;
|
|
600
|
-
const passive =
|
|
601
|
-
|
|
602
|
-
try {
|
|
603
|
-
await passive.refreshShared(signal);
|
|
604
|
-
}
|
|
605
|
-
catch (error) {
|
|
606
|
-
if (isCurrent() && !signal.aborted && notifyRecovery && !stopPersistenceError)
|
|
607
|
-
notifyProblem(ctx, `State Flow passive memory is unavailable: ${diagnosticText(error)}`, "warning");
|
|
626
|
+
const passive = await loadPassiveView(ctx, signal, isCurrent, notifyRecovery);
|
|
627
|
+
if (passive === undefined)
|
|
608
628
|
return;
|
|
609
|
-
}
|
|
610
629
|
assertCurrent();
|
|
611
630
|
// An intervening passive patch or inspection owns its newer cache, even on the same physical branch.
|
|
612
|
-
if (
|
|
631
|
+
if (runtime?.view)
|
|
613
632
|
return;
|
|
614
633
|
runtime = selected = passive;
|
|
615
634
|
installScopeStates();
|
|
@@ -619,7 +638,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
619
638
|
// Host failures before acceptance leave the selection unavailable, never invented empty memory.
|
|
620
639
|
if (accepted || !isCurrent())
|
|
621
640
|
return;
|
|
622
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
641
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), snapshot.config.mode === "active" ? config.inactiveMode : snapshot.config.mode);
|
|
623
642
|
installScopeStates();
|
|
624
643
|
}
|
|
625
644
|
finally {
|
|
@@ -628,28 +647,42 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
628
647
|
branchRestoration = undefined;
|
|
629
648
|
}
|
|
630
649
|
}
|
|
650
|
+
/** Load current shared memory for passive projection without initializing or publishing it. */
|
|
651
|
+
async function loadPassiveView(ctx, signal, isCurrent, notify) {
|
|
652
|
+
const passive = createRuntime(ctx);
|
|
653
|
+
try {
|
|
654
|
+
await passive.refreshShared(signal);
|
|
655
|
+
}
|
|
656
|
+
catch (error) {
|
|
657
|
+
if (isCurrent() && !signal.aborted && notify && !modePersistenceError)
|
|
658
|
+
notifyProblem(ctx, `State Flow passive memory is unavailable: ${diagnosticText(error)}`, "warning");
|
|
659
|
+
return undefined;
|
|
660
|
+
}
|
|
661
|
+
return passive;
|
|
662
|
+
}
|
|
631
663
|
function settleSelection(ctx, reason, notifyRecovery, skipped, selectedMemory) {
|
|
632
|
-
const failure = !
|
|
633
|
-
if (failure !== undefined && notifyRecovery && !
|
|
664
|
+
const failure = !isActive() && snapshot.meta.validation?.attempt === 0 ? snapshot.meta.validation.error : undefined;
|
|
665
|
+
if (failure !== undefined && notifyRecovery && !modePersistenceError) {
|
|
634
666
|
if (selectedHistoryExpired)
|
|
635
|
-
notifyProblem(ctx, "State Flow history is outside the retained temporal window; /state-flow-
|
|
667
|
+
notifyProblem(ctx, "State Flow history is outside the retained temporal window; /state-flow-active can use current session memory.", "warning");
|
|
636
668
|
else
|
|
637
669
|
notifyProblem(ctx, `State Flow restore failed: ${failure}`, "error");
|
|
638
670
|
}
|
|
639
671
|
if (selectedMemory && retainsPhysicalSessionProjection(reason) && runtime?.view) {
|
|
640
672
|
const boundary = findPassiveStopBoundary(ctx.sessionManager.getBranch(), ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE);
|
|
641
673
|
if (boundary !== undefined) {
|
|
642
|
-
const continuation = createPassiveContinuation(projectModelState(
|
|
643
|
-
|
|
674
|
+
const continuation = createPassiveContinuation(projectModelState(effectiveState), boundary.at, boundary.from, boundary.preserveContext);
|
|
675
|
+
// Off retains the handoff boundary for a later Passive or Active choice but never projects it.
|
|
676
|
+
if (!isActive())
|
|
644
677
|
passiveContinuation = continuation;
|
|
645
678
|
else if (snapshot.meta.bootstrap)
|
|
646
679
|
bootstrapContinuation = continuation;
|
|
647
680
|
}
|
|
648
681
|
}
|
|
649
|
-
if (notifyRecovery &&
|
|
682
|
+
if (notifyRecovery && isActive() && skipped > 0) {
|
|
650
683
|
notifyProblem(ctx, `State Flow skipped ${skipped} malformed snapshot(s); restored the last valid one.`, "warning");
|
|
651
684
|
}
|
|
652
|
-
if (
|
|
685
|
+
if (isActive())
|
|
653
686
|
deferInferencePreparation();
|
|
654
687
|
syncStateFlowTools();
|
|
655
688
|
updateUi(ctx);
|
|
@@ -669,8 +702,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
669
702
|
}, { additionalProperties: false }),
|
|
670
703
|
async execute(_toolCallId, params, signal) {
|
|
671
704
|
try {
|
|
672
|
-
if (!
|
|
673
|
-
throw new Error("State Flow tools are
|
|
705
|
+
if (!memoryToolsAvailable())
|
|
706
|
+
throw new Error("State Flow tools are off for this session");
|
|
674
707
|
if (signal?.aborted)
|
|
675
708
|
throw new Error("State Flow read was aborted");
|
|
676
709
|
if (!runtime?.view)
|
|
@@ -732,8 +765,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
732
765
|
},
|
|
733
766
|
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
734
767
|
try {
|
|
735
|
-
if (!
|
|
736
|
-
throw new Error("State Flow tools are
|
|
768
|
+
if (!memoryToolsAvailable())
|
|
769
|
+
throw new Error("State Flow tools are off for this session");
|
|
737
770
|
assertPublicationAvailable();
|
|
738
771
|
if (signal?.aborted)
|
|
739
772
|
throw new Error("State Flow patch was aborted before materialization");
|
|
@@ -762,12 +795,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
762
795
|
const selected = runtime ??= createRuntime(ctx);
|
|
763
796
|
const acquiredArtifacts = structuredClone([...artifactReads.successful.values()]);
|
|
764
797
|
const acquiredSkills = structuredClone([...skillReads.successful.values()]);
|
|
765
|
-
const previousEffective =
|
|
798
|
+
const previousEffective = effectiveState;
|
|
766
799
|
return await selected.withPatchTransaction((transaction) => {
|
|
767
800
|
if (runtime !== selected)
|
|
768
801
|
throw new Error("State Flow session selection changed while awaiting publication");
|
|
769
|
-
if (!
|
|
770
|
-
throw new Error("State Flow tools are
|
|
802
|
+
if (!memoryToolsAvailable())
|
|
803
|
+
throw new Error("State Flow tools are off for this session");
|
|
771
804
|
assertPublicationAvailable();
|
|
772
805
|
for (const acquired of acquiredArtifacts) {
|
|
773
806
|
const observation = inspectRegisteredArtifactPaths([acquired.path])[0];
|
|
@@ -792,7 +825,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
792
825
|
}
|
|
793
826
|
clearAcceptedAcquisitions(new Set(acquiredArtifacts.map(({ path }) => path)));
|
|
794
827
|
updateUi(ctx);
|
|
795
|
-
const updates = contextProjection.acceptPatch(previousEffective,
|
|
828
|
+
const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, artifactHints);
|
|
796
829
|
const acknowledgement = changed
|
|
797
830
|
? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
|
|
798
831
|
: "\nState already current.";
|
|
@@ -826,8 +859,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
826
859
|
if (startActivation?.operation)
|
|
827
860
|
return startActivation.operation;
|
|
828
861
|
telegramStartPending = false;
|
|
829
|
-
if (activeContext && !branchRestoration &&
|
|
830
|
-
return Promise.resolve({ ok: true, message: "State Flow is already
|
|
862
|
+
if (activeContext && !branchRestoration && isActive()) {
|
|
863
|
+
return Promise.resolve({ ok: true, message: "State Flow is already active", signal: sharedInspectionLifetime.signal });
|
|
831
864
|
}
|
|
832
865
|
const pending = { controller: new AbortController() };
|
|
833
866
|
startActivation = pending;
|
|
@@ -850,7 +883,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
850
883
|
&& ctx.sessionManager.getHeader()?.timestamp === timestamp;
|
|
851
884
|
const superseded = () => {
|
|
852
885
|
pending.abort();
|
|
853
|
-
return { ok: false, message: "State Flow
|
|
886
|
+
return { ok: false, message: "State Flow activation was superseded", signal: pending.signal };
|
|
854
887
|
};
|
|
855
888
|
try {
|
|
856
889
|
if (!activeContext)
|
|
@@ -859,11 +892,11 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
859
892
|
await waitForRecovery(branchRestoration.operation, signal);
|
|
860
893
|
if (!isOwner())
|
|
861
894
|
return superseded();
|
|
862
|
-
if (
|
|
863
|
-
return { ok: true, message: "State Flow is already
|
|
895
|
+
if (isActive())
|
|
896
|
+
return { ok: true, message: "State Flow is already active", signal: sharedInspectionLifetime.signal };
|
|
864
897
|
if (forkInitialization) {
|
|
865
|
-
// Withdraw this join on cancellation without revoking the independently owned
|
|
866
|
-
const stopping =
|
|
898
|
+
// Withdraw this join on cancellation without revoking the independently owned mode persistence.
|
|
899
|
+
const stopping = inactivePersistence?.operation;
|
|
867
900
|
if (stopping)
|
|
868
901
|
await waitForRecovery(stopping, signal);
|
|
869
902
|
if (!isOwner())
|
|
@@ -878,7 +911,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
878
911
|
catch (error) {
|
|
879
912
|
if (!isOwner())
|
|
880
913
|
return superseded();
|
|
881
|
-
const message = conciseDiagnostic(`State Flow
|
|
914
|
+
const message = conciseDiagnostic(`State Flow activation failed: ${diagnosticText(error)}`);
|
|
882
915
|
notifyProblem(ctx, message, "error");
|
|
883
916
|
return { ok: false, message, signal: AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]) };
|
|
884
917
|
}
|
|
@@ -889,15 +922,15 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
889
922
|
const cwd = ctx.cwd;
|
|
890
923
|
const file = ctx.sessionManager.getSessionFile();
|
|
891
924
|
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
892
|
-
const
|
|
925
|
+
const initiallyActive = isActive();
|
|
893
926
|
let accepted = false;
|
|
894
927
|
let receipt = AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]);
|
|
895
928
|
const isCurrent = () => startActivation?.controller === pending && runtime === selected && !shuttingDown
|
|
896
929
|
&& ctx.cwd === cwd && ctx.sessionManager.getSessionId() === owner && ctx.sessionManager.getSessionFile() === file
|
|
897
|
-
&& ctx.sessionManager.getHeader()?.timestamp === timestamp &&
|
|
930
|
+
&& ctx.sessionManager.getHeader()?.timestamp === timestamp && isActive() === (accepted || initiallyActive);
|
|
898
931
|
const superseded = () => {
|
|
899
932
|
pending.abort();
|
|
900
|
-
return { ok: false, message: "State Flow
|
|
933
|
+
return { ok: false, message: "State Flow activation was superseded", signal: pending.signal };
|
|
901
934
|
};
|
|
902
935
|
try {
|
|
903
936
|
const activation = createRuntime(ctx);
|
|
@@ -905,7 +938,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
905
938
|
const result = await activation.withStartTransaction((current, publish) => {
|
|
906
939
|
signal.throwIfAborted();
|
|
907
940
|
if (!isCurrent())
|
|
908
|
-
throw new Error("State Flow
|
|
941
|
+
throw new Error("State Flow activation selection changed while awaiting publication");
|
|
909
942
|
if (!current && !branchStartsWithoutRuntime)
|
|
910
943
|
throw new Error(snapshot.meta.validation?.error ?? "Current State Flow session memory is unavailable");
|
|
911
944
|
const continuation = passiveContinuation ?? bootstrapContinuation;
|
|
@@ -914,12 +947,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
914
947
|
const recoveredCurrent = selectedHistoryExpired;
|
|
915
948
|
const publication = publish(activated);
|
|
916
949
|
accepted = true;
|
|
917
|
-
|
|
950
|
+
cancelInactivePersistence();
|
|
918
951
|
runtime = selected = activation;
|
|
919
952
|
snapshot = activated;
|
|
920
953
|
activeContext = ctx;
|
|
921
954
|
selectedHistoryExpired = false;
|
|
922
|
-
|
|
955
|
+
modePersistenceError = undefined;
|
|
923
956
|
installScopeStates();
|
|
924
957
|
clearRunTransient();
|
|
925
958
|
contextProjection.reset();
|
|
@@ -932,127 +965,176 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
932
965
|
updateUi(ctx);
|
|
933
966
|
appendCheckpoint();
|
|
934
967
|
ctx.ui.notify(recoveredCurrent
|
|
935
|
-
? "State Flow
|
|
968
|
+
? "State Flow active from current session memory; unavailable historical state was not restored."
|
|
936
969
|
: snapshot.meta.bootstrap
|
|
937
|
-
? "State Flow
|
|
938
|
-
: "State Flow
|
|
939
|
-
return { ok: true, message: "State Flow
|
|
970
|
+
? "State Flow active. The next complete agent run will migrate active context into state."
|
|
971
|
+
: "State Flow active. The next prompt starts a stateful agent run.", "info");
|
|
972
|
+
return { ok: true, message: "State Flow active", signal: receipt };
|
|
940
973
|
}, signal, branchStartsWithoutRuntime);
|
|
941
974
|
return isCurrent() ? result : superseded();
|
|
942
975
|
}
|
|
943
976
|
catch (error) {
|
|
944
977
|
if (!isCurrent())
|
|
945
978
|
return superseded();
|
|
946
|
-
const message = conciseDiagnostic(`${accepted ? "State Flow
|
|
979
|
+
const message = conciseDiagnostic(`${accepted ? "State Flow active; lifecycle update failed" : "State Flow activation failed"}: ${diagnosticText(error)}`);
|
|
947
980
|
notifyProblem(ctx, message, accepted ? "warning" : "error");
|
|
948
981
|
return { ok: accepted, message, signal: receipt };
|
|
949
982
|
}
|
|
950
983
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
984
|
+
const MODE_COMMAND_DESCRIPTIONS = {
|
|
985
|
+
active: "Make State Flow active on the current session branch",
|
|
986
|
+
passive: "Use passive State Flow memory on the current session branch",
|
|
987
|
+
off: "Turn State Flow tools and context off on the current session branch",
|
|
988
|
+
};
|
|
989
|
+
/** Terminal commands and Telegram controls share these lifecycle owners. */
|
|
990
|
+
function selectMode(ctx, mode) {
|
|
991
|
+
return mode === "active" ? startStateFlow(ctx) : deactivateStateFlow(ctx, mode);
|
|
992
|
+
}
|
|
993
|
+
for (const mode of ["active", "passive", "off"]) {
|
|
994
|
+
pi.registerCommand(`state-flow-${mode}`, {
|
|
995
|
+
description: MODE_COMMAND_DESCRIPTIONS[mode],
|
|
996
|
+
handler: async (_args, ctx) => {
|
|
997
|
+
await selectMode(ctx, mode);
|
|
998
|
+
},
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
957
1001
|
pi.registerCommand("state-flow-status", {
|
|
958
1002
|
description: "Show State Flow runtime status",
|
|
959
1003
|
handler: async (_args, ctx) => {
|
|
960
1004
|
ctx.ui.notify(detailedStatus(snapshot, statusDiagnostics(ctx)), "info");
|
|
961
1005
|
},
|
|
962
1006
|
});
|
|
963
|
-
|
|
1007
|
+
/** Local policy, tools and UI change before any canonical wait. */
|
|
1008
|
+
function applyInactiveMode(ctx, mode) {
|
|
1009
|
+
snapshot = deactivateEpisode(snapshot, mode);
|
|
1010
|
+
clearRunTransient();
|
|
1011
|
+
syncStateFlowTools();
|
|
1012
|
+
updateUi(ctx);
|
|
1013
|
+
}
|
|
1014
|
+
function deactivateStateFlow(ctx, mode) {
|
|
964
1015
|
cancelStartActivation();
|
|
965
1016
|
if (shuttingDown)
|
|
966
1017
|
return Promise.resolve({ ok: false, message: "State Flow is shutting down" });
|
|
967
|
-
|
|
968
|
-
|
|
1018
|
+
telegramStartPending = false;
|
|
1019
|
+
const persisting = inactivePersistence;
|
|
1020
|
+
if (persisting?.operation && !persisting.published) {
|
|
1021
|
+
// Pending inactive choices coalesce: acceptance persists whichever inactive mode is current then.
|
|
1022
|
+
if (snapshot.config.mode !== mode)
|
|
1023
|
+
applyInactiveMode(ctx, mode);
|
|
1024
|
+
return persisting.operation;
|
|
1025
|
+
}
|
|
969
1026
|
if (!activeContext)
|
|
970
1027
|
void restoreActiveBranch(ctx, undefined, false);
|
|
971
1028
|
const restoring = branchRestoration;
|
|
1029
|
+
// Read-only recovery also retains the latest policy; its native write fence is updated below.
|
|
1030
|
+
if (restoring)
|
|
1031
|
+
restoring.requestedMode = mode;
|
|
972
1032
|
if (restoring?.awaitingAcceptance) {
|
|
973
|
-
//
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
clearRunTransient();
|
|
977
|
-
syncStateFlowTools();
|
|
978
|
-
updateUi(ctx);
|
|
979
|
-
return Promise.resolve({ ok: true, message: "State Flow disabled; memory restoration continues" });
|
|
1033
|
+
// A mode change never cancels memory restoration or initialization.
|
|
1034
|
+
applyInactiveMode(ctx, mode);
|
|
1035
|
+
return Promise.resolve({ ok: true, message: `State Flow ${mode}; memory restoration continues` });
|
|
980
1036
|
}
|
|
1037
|
+
if (snapshot.config.mode === mode) {
|
|
1038
|
+
const retained = selectRetainedCheckpoint(discoverSnapshotData(ctx.sessionManager.getBranch()).candidates, config.inactiveMode);
|
|
1039
|
+
const recordedMode = retained.kind === "pre-runtime" ? retained.mode : retained.kind === "boundary" ? retained.checkpoint.mode : undefined;
|
|
1040
|
+
if (modePersistenceError || recordedMode === mode)
|
|
1041
|
+
return Promise.resolve({ ok: true, message: `State Flow is already ${mode}` });
|
|
1042
|
+
}
|
|
1043
|
+
cancelInactivePersistence();
|
|
981
1044
|
const pending = { controller: new AbortController() };
|
|
982
|
-
|
|
983
|
-
const operation =
|
|
1045
|
+
inactivePersistence = pending;
|
|
1046
|
+
const operation = persistInactiveMode(ctx, pending, mode);
|
|
984
1047
|
pending.operation = operation;
|
|
985
|
-
const finished = () => { if (
|
|
986
|
-
|
|
1048
|
+
const finished = () => { if (inactivePersistence === pending)
|
|
1049
|
+
inactivePersistence = undefined; };
|
|
987
1050
|
void operation.then(finished, finished);
|
|
988
1051
|
return operation;
|
|
989
1052
|
}
|
|
990
|
-
async function
|
|
991
|
-
|
|
992
|
-
const selected = runtime;
|
|
1053
|
+
async function persistInactiveMode(ctx, pending, mode) {
|
|
1054
|
+
let selected = runtime;
|
|
993
1055
|
const owner = ctx.sessionManager.getSessionId();
|
|
994
1056
|
const current = snapshot;
|
|
1057
|
+
const wasActive = current.config.mode === "active";
|
|
995
1058
|
let unfinished = current.meta.specification !== undefined
|
|
996
1059
|
|| (inferencePreparation?.prompt !== undefined && !inferencePreparation.accepted);
|
|
997
|
-
|
|
998
|
-
clearRunTransient();
|
|
999
|
-
syncStateFlowTools();
|
|
1000
|
-
updateUi(ctx);
|
|
1001
|
-
if (stopPersistenceError)
|
|
1002
|
-
return { ok: true, message: "State Flow disabled; memory writes remain paused" };
|
|
1060
|
+
applyInactiveMode(ctx, mode);
|
|
1003
1061
|
const stoppedAt = Date.now();
|
|
1062
|
+
if (modePersistenceError) {
|
|
1063
|
+
// Writes stay paused; the native marker records only the newly selected inactive policy.
|
|
1064
|
+
pending.published = true;
|
|
1065
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, {
|
|
1066
|
+
at: passiveContinuation?.startedAt ?? stoppedAt,
|
|
1067
|
+
...(passiveContinuation?.activeRunStartedAt === undefined ? {} : { from: passiveContinuation.activeRunStartedAt }),
|
|
1068
|
+
preserveContext: true, owner, persistenceError: modePersistenceError, mode,
|
|
1069
|
+
});
|
|
1070
|
+
return { ok: true, message: `State Flow ${mode}; memory writes remain paused` };
|
|
1071
|
+
}
|
|
1004
1072
|
const incomingBoundary = current.meta.bootstrap ? bootstrapContinuation : undefined;
|
|
1005
1073
|
const anchor = runAnchorTimestamp;
|
|
1006
1074
|
const idle = ctx.isIdle();
|
|
1007
|
-
const isCurrent = () =>
|
|
1008
|
-
&& !shuttingDown && ctx.sessionManager.getSessionId() === owner && !
|
|
1009
|
-
const superseded = () => ({ ok: false, message: "State Flow
|
|
1075
|
+
const isCurrent = () => inactivePersistence === pending && runtime === selected
|
|
1076
|
+
&& !shuttingDown && ctx.sessionManager.getSessionId() === owner && !isActive();
|
|
1077
|
+
const superseded = () => ({ ok: false, message: "State Flow mode change was superseded" });
|
|
1010
1078
|
const freezeHandoff = () => {
|
|
1011
1079
|
contextProjection.reset();
|
|
1012
|
-
const handoff = (
|
|
1013
|
-
? createPassiveContinuation(projectModelState(
|
|
1080
|
+
const handoff = (wasActive || modePersistenceError) && selected?.view && current.meta.validation?.attempt !== 0
|
|
1081
|
+
? createPassiveContinuation(projectModelState(effectiveState), incomingBoundary?.startedAt ?? stoppedAt, incomingBoundary ? incomingBoundary.activeRunStartedAt : !idle || unfinished ? anchor : undefined, modePersistenceError !== undefined || (incomingBoundary ? incomingBoundary.preserveContext : current.meta.bootstrap === true || (unfinished && anchor === undefined)))
|
|
1014
1082
|
: undefined;
|
|
1015
|
-
|
|
1083
|
+
// Off keeps the frozen boundary for a later Passive/Active choice but never projects it.
|
|
1084
|
+
passiveContinuation = handoff ?? (!wasActive ? passiveContinuation : undefined);
|
|
1016
1085
|
return handoff;
|
|
1017
1086
|
};
|
|
1018
1087
|
const complete = () => {
|
|
1088
|
+
pending.published = true;
|
|
1019
1089
|
const exitHandoff = freezeHandoff();
|
|
1020
|
-
|
|
1090
|
+
const selectedMode = snapshot.config.mode;
|
|
1091
|
+
if (exitHandoff || modePersistenceError)
|
|
1021
1092
|
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, {
|
|
1022
1093
|
at: exitHandoff?.startedAt ?? stoppedAt,
|
|
1023
1094
|
...(exitHandoff?.activeRunStartedAt === undefined ? {} : { from: exitHandoff.activeRunStartedAt }),
|
|
1024
|
-
...(exitHandoff?.preserveContext ||
|
|
1025
|
-
...(
|
|
1095
|
+
...(exitHandoff?.preserveContext || modePersistenceError ? { preserveContext: true } : {}),
|
|
1096
|
+
...(modePersistenceError === undefined ? {} : { owner, persistenceError: modePersistenceError, mode: selectedMode }),
|
|
1026
1097
|
});
|
|
1027
|
-
if (!
|
|
1098
|
+
if (!modePersistenceError) {
|
|
1028
1099
|
appendCheckpoint();
|
|
1029
|
-
return { ok: true, message:
|
|
1100
|
+
return { ok: true, message: `State Flow ${selectedMode}` };
|
|
1030
1101
|
}
|
|
1031
|
-
const message = conciseDiagnostic(`State Flow
|
|
1102
|
+
const message = conciseDiagnostic(`State Flow ${selectedMode}; memory writes paused: ${modePersistenceError}`);
|
|
1032
1103
|
notifyProblem(ctx, message, "warning");
|
|
1033
1104
|
return { ok: true, message };
|
|
1034
1105
|
};
|
|
1035
1106
|
let accepted = false;
|
|
1036
1107
|
try {
|
|
1037
|
-
unfinished ||=
|
|
1108
|
+
unfinished ||= wasActive && hasUncheckpointedConversation(ctx.sessionManager.getBranch());
|
|
1038
1109
|
// Projection changes before any wait; capture the old run's boundary before later native input can replace it.
|
|
1039
1110
|
freezeHandoff();
|
|
1040
1111
|
bootstrapContinuation = undefined;
|
|
1041
1112
|
artifactInvalidations = [];
|
|
1042
1113
|
artifactReads.setCandidates([]);
|
|
1043
1114
|
assertPublicationAvailable();
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1115
|
+
const signal = ctx.signal ? AbortSignal.any([pending.controller.signal, ctx.signal]) : pending.controller.signal;
|
|
1116
|
+
if (branchStartsWithoutRuntime || !selected?.view) {
|
|
1117
|
+
// A pre-runtime choice is native-only; Passive then loads shared memory without initializing storage.
|
|
1118
|
+
const result = complete();
|
|
1119
|
+
if (snapshot.config.mode !== "passive" || runtime?.view)
|
|
1120
|
+
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
|
+
}
|
|
1047
1129
|
const result = await selected.withLifecycleTransaction((publish) => {
|
|
1048
1130
|
signal.throwIfAborted();
|
|
1049
1131
|
if (!isCurrent())
|
|
1050
|
-
throw new Error("State Flow
|
|
1132
|
+
throw new Error("State Flow mode selection changed while awaiting publication");
|
|
1051
1133
|
assertPublicationAvailable();
|
|
1052
|
-
const
|
|
1053
|
-
const publication = publish(
|
|
1134
|
+
const next = structuredClone(snapshot);
|
|
1135
|
+
const publication = publish(next);
|
|
1054
1136
|
accepted = true;
|
|
1055
|
-
snapshot =
|
|
1137
|
+
snapshot = next;
|
|
1056
1138
|
installScopeStates();
|
|
1057
1139
|
recordPolicyPublication(publication, ctx);
|
|
1058
1140
|
return complete();
|
|
@@ -1064,28 +1146,22 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1064
1146
|
return superseded();
|
|
1065
1147
|
const cause = diagnosticText(error);
|
|
1066
1148
|
if (accepted) {
|
|
1067
|
-
const message = conciseDiagnostic(`State Flow
|
|
1149
|
+
const message = conciseDiagnostic(`State Flow ${snapshot.config.mode}; lifecycle update failed: ${cause}`);
|
|
1068
1150
|
notifyProblem(ctx, message, "warning");
|
|
1069
1151
|
return { ok: true, message };
|
|
1070
1152
|
}
|
|
1071
|
-
|
|
1153
|
+
modePersistenceError = cause.trim() ? cause : "Canonical State Flow persistence failed";
|
|
1072
1154
|
bootstrapContinuation = undefined;
|
|
1073
1155
|
artifactInvalidations = [];
|
|
1074
1156
|
artifactReads.setCandidates([]);
|
|
1075
1157
|
return complete();
|
|
1076
1158
|
}
|
|
1077
1159
|
}
|
|
1078
|
-
pi.registerCommand("state-flow-stop", {
|
|
1079
|
-
description: "Stop State Flow on the current session branch",
|
|
1080
|
-
handler: async (_args, ctx) => {
|
|
1081
|
-
await stopStateFlow(ctx);
|
|
1082
|
-
},
|
|
1083
|
-
});
|
|
1084
1160
|
const telegram = createStateFlowTelegramAdapter({
|
|
1085
1161
|
...(options.telegram?.load === undefined ? {} : { load: options.telegram.load }),
|
|
1086
1162
|
port: {
|
|
1087
1163
|
snapshot: () => ({
|
|
1088
|
-
|
|
1164
|
+
mode: snapshot.config.mode,
|
|
1089
1165
|
step: snapshot.meta.step,
|
|
1090
1166
|
revisions: scopeRevisions(),
|
|
1091
1167
|
bootstrap: snapshot.meta.bootstrap === true,
|
|
@@ -1093,15 +1169,13 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1093
1169
|
}),
|
|
1094
1170
|
inspect: inspectTelegramState,
|
|
1095
1171
|
canStartNow: () => activeContext === undefined || activeContext.isIdle(),
|
|
1096
|
-
|
|
1172
|
+
select: async (mode) => {
|
|
1097
1173
|
if (!activeContext)
|
|
1098
1174
|
throw new Error("State Flow is not attached to an active session yet");
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
throw new Error("State Flow is not attached to an active session yet");
|
|
1104
|
-
const operation = stopStateFlow(activeContext);
|
|
1175
|
+
const operation = selectMode(activeContext, mode);
|
|
1176
|
+
if (mode === "active")
|
|
1177
|
+
return operation;
|
|
1178
|
+
// Inactive choices revoke older presentation at once; the receipt belongs to the new lifetime.
|
|
1105
1179
|
const signal = sharedInspectionLifetime.signal;
|
|
1106
1180
|
try {
|
|
1107
1181
|
return { ...await operation, signal };
|
|
@@ -1123,8 +1197,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1123
1197
|
pi.on("before_agent_start", (event) => {
|
|
1124
1198
|
// Native run identity is independent of semantic enablement and survives mode toggles.
|
|
1125
1199
|
runAnchorTimestamp = undefined;
|
|
1126
|
-
if (!
|
|
1127
|
-
if (!
|
|
1200
|
+
if (!isActive()) {
|
|
1201
|
+
if (!passiveMemoryAvailable())
|
|
1128
1202
|
return;
|
|
1129
1203
|
(event.systemPromptOptions.sections ??= {}).state_flow = PASSIVE_MEMORY_PROTOCOL;
|
|
1130
1204
|
return;
|
|
@@ -1139,21 +1213,25 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1139
1213
|
(event.systemPromptOptions.sections ??= {}).state_flow = stateFlowProtocol(snapshot.meta.bootstrap === true);
|
|
1140
1214
|
});
|
|
1141
1215
|
pi.on("context_with_system", (event) => {
|
|
1142
|
-
|
|
1143
|
-
|
|
1216
|
+
// Off removes the owned section, including one contributed before an in-flight mode change.
|
|
1217
|
+
const protocol = isActive() ? stateFlowProtocol(snapshot.meta.bootstrap === true)
|
|
1218
|
+
: passiveMemoryAvailable() ? PASSIVE_MEMORY_PROTOCOL : undefined;
|
|
1144
1219
|
return { messages: projectSystemProtocol(event.messages, protocol) };
|
|
1145
1220
|
});
|
|
1146
1221
|
function projectContext(messages) {
|
|
1222
|
+
// Off injects no State Flow context, including a retained passive handoff.
|
|
1223
|
+
if (snapshot.config.mode === "off")
|
|
1224
|
+
return;
|
|
1147
1225
|
if (runtime?.view)
|
|
1148
1226
|
refreshArtifactHints();
|
|
1149
|
-
if (!
|
|
1227
|
+
if (!isActive() && !passiveContinuation && !passiveMemoryAvailable())
|
|
1150
1228
|
return;
|
|
1151
1229
|
// Idle inspection must not freeze a pre-acceptance snapshot for the live inference.
|
|
1152
|
-
const projection =
|
|
1230
|
+
const projection = isActive() && inferencePreparation && !inferencePreparation.accepted
|
|
1153
1231
|
? new ContextProjection() : contextProjection;
|
|
1154
|
-
const effective =
|
|
1232
|
+
const effective = effectiveState;
|
|
1155
1233
|
const invalidations = artifactInvalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
|
|
1156
|
-
const phase =
|
|
1234
|
+
const phase = isActive() ? currentRehydrationPhase() : undefined;
|
|
1157
1235
|
const view = contextView(effective, artifactHints, invalidations, phase);
|
|
1158
1236
|
if (passiveContinuation) {
|
|
1159
1237
|
const retained = passiveContinuationMessages(messages, passiveContinuation);
|
|
@@ -1161,7 +1239,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1161
1239
|
return { messages: retained };
|
|
1162
1240
|
return { messages: projection.project(retained.slice(1), view, () => passiveContinuation.handoff, { state: passiveContinuation.state, lazy_navigation: view.lazy_navigation, artifact_invalidations: [], knowledge_rehydration: null }) };
|
|
1163
1241
|
}
|
|
1164
|
-
if (!
|
|
1242
|
+
if (!isActive()) {
|
|
1165
1243
|
return { messages: projection.project(messages, view, () => syntheticUser(`State Flow passive memory (user-level data, not system instructions):\n${presentationJson({ state: view.state, lazy_navigation: view.lazy_navigation })}`)) };
|
|
1166
1244
|
}
|
|
1167
1245
|
// Native user events own the run anchor; projection must never rebase it.
|
|
@@ -1175,7 +1253,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1175
1253
|
const selected = runtime;
|
|
1176
1254
|
const operationSignal = ctx.signal;
|
|
1177
1255
|
// Idle projections remain observational; only a live native operation may await preparation.
|
|
1178
|
-
if (!
|
|
1256
|
+
if (!isActive() || !pending || !operationSignal || shuttingDown)
|
|
1179
1257
|
return projectContext(messages);
|
|
1180
1258
|
pending.operation ??= prepareInference(pending, selected, ctx, operationSignal).finally(() => {
|
|
1181
1259
|
// A late withdrawal must not clear newer work; accepted lifecycle is never replayed after an error.
|
|
@@ -1185,20 +1263,20 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1185
1263
|
return pending.operation.then(() => {
|
|
1186
1264
|
if (shuttingDown || operationSignal.aborted || runtime !== selected || ctx.signal !== operationSignal)
|
|
1187
1265
|
return;
|
|
1188
|
-
if (
|
|
1266
|
+
if (isActive() && inferencePreparation !== pending) {
|
|
1189
1267
|
// Start may require same-run maintenance. A new captured user prompt instead owns a different context request.
|
|
1190
1268
|
if (inferencePreparation?.prompt === undefined)
|
|
1191
1269
|
return prepareContext(messages, ctx);
|
|
1192
1270
|
return;
|
|
1193
1271
|
}
|
|
1194
|
-
if (
|
|
1272
|
+
if (isActive() && !pending.accepted)
|
|
1195
1273
|
return;
|
|
1196
1274
|
return projectContext(messages);
|
|
1197
1275
|
});
|
|
1198
1276
|
}
|
|
1199
1277
|
pi.on("context", (event, ctx) => prepareContext(event.messages, ctx));
|
|
1200
1278
|
pi.on("tool_execution_start", (event) => {
|
|
1201
|
-
if (!
|
|
1279
|
+
if (!isActive())
|
|
1202
1280
|
return;
|
|
1203
1281
|
// Pi emits this before tool_call. Keep the argument object as a fallback;
|
|
1204
1282
|
// tool_call replaces it with the mutable, post-preflight input reference.
|
|
@@ -1206,36 +1284,34 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1206
1284
|
artifactReads.recordStart(event.toolCallId, event.toolName, event.args);
|
|
1207
1285
|
});
|
|
1208
1286
|
pi.on("tool_call", (event, ctx) => {
|
|
1209
|
-
if (!
|
|
1287
|
+
if (!isActive())
|
|
1210
1288
|
return;
|
|
1211
1289
|
const batch = findAssistantToolBatch(ctx.sessionManager, event.toolCallId);
|
|
1212
1290
|
const patchCalls = batch?.filter((name) => name === PATCH_STATE_TOOL_NAME).length ?? 0;
|
|
1213
1291
|
if (patchCalls > 0) {
|
|
1214
1292
|
if (patchCalls !== 1) {
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
};
|
|
1293
|
+
const reason = "A State Flow barrier response must contain exactly one patch_state call";
|
|
1294
|
+
recordDiagnostic(reason, "barrier-block", ctx, { tool: event.toolName, toolCallId: event.toolCallId, batchToolNames: batch });
|
|
1295
|
+
return { block: true, reason };
|
|
1219
1296
|
}
|
|
1220
1297
|
if (event.toolName !== PATCH_STATE_TOOL_NAME) {
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
};
|
|
1298
|
+
const reason = "Blocked by the patch_state barrier; reconsider this action after State Flow rematerializes context";
|
|
1299
|
+
recordDiagnostic(reason, "barrier-block", ctx, { tool: event.toolName, toolCallId: event.toolCallId, batchToolNames: batch });
|
|
1300
|
+
return { block: true, reason };
|
|
1225
1301
|
}
|
|
1226
1302
|
}
|
|
1227
1303
|
skillReads.recordCall(event.toolCallId, event.toolName, event.input);
|
|
1228
1304
|
artifactReads.recordCall(event.toolCallId, event.toolName, event.input);
|
|
1229
1305
|
});
|
|
1230
1306
|
pi.on("tool_execution_end", (event) => {
|
|
1231
|
-
if (!
|
|
1307
|
+
if (!isActive())
|
|
1232
1308
|
return;
|
|
1233
1309
|
skillReads.recordEnd(event.toolCallId, event.toolName, event.isError);
|
|
1234
1310
|
dropCurrentSkillReads();
|
|
1235
1311
|
artifactReads.recordEnd(event.toolCallId, event.toolName, event.isError);
|
|
1236
1312
|
});
|
|
1237
1313
|
pi.on("tool_result", (event) => {
|
|
1238
|
-
if (!
|
|
1314
|
+
if (!isActive())
|
|
1239
1315
|
return;
|
|
1240
1316
|
const read = skillReads.recordResult(event.toolName, event.input, event.isError);
|
|
1241
1317
|
if (!read)
|
|
@@ -1250,7 +1326,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1250
1326
|
// Observe actual user events even while disabled; Start/Stop cannot invent or erase them.
|
|
1251
1327
|
if (event.message.role === "user" && runAnchorTimestamp === undefined)
|
|
1252
1328
|
runAnchorTimestamp = event.message.timestamp;
|
|
1253
|
-
if (shuttingDown || !
|
|
1329
|
+
if (shuttingDown || !isActive())
|
|
1254
1330
|
return;
|
|
1255
1331
|
if (event.message.role !== "assistant")
|
|
1256
1332
|
return;
|
|
@@ -1268,7 +1344,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1268
1344
|
if (shuttingDown)
|
|
1269
1345
|
return;
|
|
1270
1346
|
const pending = responseReconciliation;
|
|
1271
|
-
if (!
|
|
1347
|
+
if (!isActive() || !pending) {
|
|
1272
1348
|
updateUi(ctx);
|
|
1273
1349
|
return;
|
|
1274
1350
|
}
|
|
@@ -1281,7 +1357,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1281
1357
|
throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
1282
1358
|
await selected.withPatchTransaction((transaction) => {
|
|
1283
1359
|
signal.throwIfAborted();
|
|
1284
|
-
if (runtime !== selected || responseReconciliation !== pending || !
|
|
1360
|
+
if (runtime !== selected || responseReconciliation !== pending || !isActive()) {
|
|
1285
1361
|
throw new Error("State Flow response selection changed while awaiting publication");
|
|
1286
1362
|
}
|
|
1287
1363
|
assertPublicationAvailable();
|
|
@@ -1383,12 +1459,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1383
1459
|
}
|
|
1384
1460
|
});
|
|
1385
1461
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
1386
|
-
if (telegramStartPending && !
|
|
1462
|
+
if (telegramStartPending && !isActive()) {
|
|
1387
1463
|
telegramStartPending = false;
|
|
1388
1464
|
await startStateFlow(ctx);
|
|
1389
1465
|
return;
|
|
1390
1466
|
}
|
|
1391
|
-
if (!completedRunAccepted || compactionStopped || !
|
|
1467
|
+
if (!completedRunAccepted || compactionStopped || !isActive() || snapshot.meta.bootstrap
|
|
1392
1468
|
|| compactionInFlight || !ctx.isIdle() || ctx.hasPendingMessages() || !runtime?.view
|
|
1393
1469
|
|| !shouldRequestStateFlowCompaction(ctx.getContextUsage()))
|
|
1394
1470
|
return;
|
|
@@ -1424,7 +1500,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1424
1500
|
contextProjection.reset();
|
|
1425
1501
|
const restoring = cancelBranchRestoration();
|
|
1426
1502
|
const starting = cancelStartActivation();
|
|
1427
|
-
const stopping =
|
|
1503
|
+
const stopping = cancelInactivePersistence();
|
|
1428
1504
|
backupLifetime.abort();
|
|
1429
1505
|
cancelInferencePreparation();
|
|
1430
1506
|
cancelResponseReconciliation();
|