@llblab/pi-kit 0.24.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/BACKLOG.md +5 -1
- package/CHANGELOG.md +12 -0
- package/README.md +11 -8
- package/node_modules/@llblab/pi-actors/AGENTS.md +2 -0
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +4 -1
- package/node_modules/@llblab/pi-actors/LICENSE +21 -0
- package/node_modules/@llblab/pi-actors/README.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +1 -1
- package/node_modules/@llblab/pi-actors/package.json +4 -3
- package/node_modules/@llblab/pi-claude-usage/AGENTS.md +23 -0
- package/node_modules/@llblab/pi-claude-usage/BACKLOG.md +4 -0
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +21 -0
- package/node_modules/@llblab/pi-claude-usage/LICENSE +22 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +155 -0
- package/node_modules/@llblab/pi-claude-usage/banner.jpg +0 -0
- package/node_modules/@llblab/pi-claude-usage/index.ts +8 -0
- package/node_modules/@llblab/pi-claude-usage/lib/extension.ts +30 -0
- package/node_modules/@llblab/pi-claude-usage/lib/fast.ts +24 -0
- package/node_modules/@llblab/pi-claude-usage/lib/query.ts +146 -0
- package/node_modules/@llblab/pi-claude-usage/lib/status-format.ts +297 -0
- package/node_modules/@llblab/pi-claude-usage/lib/status.ts +366 -0
- package/node_modules/@llblab/pi-claude-usage/lib/telegram.ts +44 -0
- package/node_modules/@llblab/pi-claude-usage/lib/usage-store.ts +221 -0
- package/node_modules/@llblab/pi-claude-usage/lib/usage.ts +128 -0
- package/node_modules/@llblab/pi-claude-usage/package.json +64 -0
- package/node_modules/@llblab/pi-clean-room/AGENTS.md +1 -0
- package/node_modules/@llblab/pi-clean-room/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-clean-room/LICENSE +21 -0
- package/node_modules/@llblab/pi-clean-room/README.md +1 -1
- package/node_modules/@llblab/pi-clean-room/package.json +3 -2
- package/node_modules/@llblab/pi-codex-usage/AGENTS.md +9 -6
- package/node_modules/@llblab/pi-codex-usage/BACKLOG.md +2 -1
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +17 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +75 -17
- package/node_modules/@llblab/pi-codex-usage/index.ts +8 -1602
- package/node_modules/@llblab/pi-codex-usage/lib/extension.ts +25 -0
- package/node_modules/@llblab/pi-codex-usage/lib/fast.ts +23 -0
- package/node_modules/@llblab/pi-codex-usage/lib/query.ts +368 -0
- package/node_modules/@llblab/pi-codex-usage/lib/status-format.ts +347 -0
- package/node_modules/@llblab/pi-codex-usage/lib/status.ts +435 -0
- package/node_modules/@llblab/pi-codex-usage/lib/telegram.ts +45 -0
- package/node_modules/@llblab/pi-codex-usage/lib/usage-store.ts +229 -0
- package/node_modules/@llblab/pi-codex-usage/lib/usage.ts +425 -0
- package/node_modules/@llblab/pi-codex-usage/package.json +11 -6
- package/node_modules/@llblab/pi-command-fast/AGENTS.md +7 -0
- package/node_modules/@llblab/pi-command-fast/BACKLOG.md +9 -0
- package/node_modules/@llblab/pi-command-fast/CHANGELOG.md +7 -0
- package/node_modules/@llblab/pi-command-fast/LICENSE +21 -0
- package/node_modules/@llblab/pi-command-fast/README.md +42 -0
- package/node_modules/@llblab/pi-command-fast/dist/command.d.ts +8 -0
- package/node_modules/@llblab/pi-command-fast/dist/command.js +52 -0
- package/node_modules/@llblab/pi-command-fast/dist/index.d.ts +3 -0
- package/node_modules/@llblab/pi-command-fast/dist/index.js +3 -0
- package/node_modules/@llblab/pi-command-fast/dist/models-json.d.ts +10 -0
- package/node_modules/@llblab/pi-command-fast/dist/models-json.js +81 -0
- package/node_modules/@llblab/pi-command-fast/package.json +49 -0
- package/node_modules/@llblab/pi-grow-loop/AGENTS.md +1 -0
- package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +4 -1
- package/node_modules/@llblab/pi-grow-loop/LICENSE +21 -0
- package/node_modules/@llblab/pi-grow-loop/README.md +1 -1
- package/node_modules/@llblab/pi-grow-loop/package.json +3 -2
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +43 -56
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +17 -3
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +25 -0
- package/node_modules/@llblab/pi-state-flow/LICENSE +21 -0
- package/node_modules/@llblab/pi-state-flow/README.md +18 -15
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/index.js +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +7 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +16 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +9 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +5 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +7 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +3 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +3 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +503 -235
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +16 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +11 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +6 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +4 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +1 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +4 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +13 -13
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +7 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +9 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +17 -12
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +13 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +62 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +23 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +52 -20
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +22 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +30 -10
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +5 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +19 -28
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +17 -15
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +52 -52
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +8 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +34 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +13 -19
- package/node_modules/@llblab/pi-state-flow/dist/package.json +12 -11
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -1
- package/node_modules/@llblab/pi-state-flow/docs/agent-contract-relocation.md +72 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +44 -36
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +14 -6
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +7 -5
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +6 -6
- package/node_modules/@llblab/pi-state-flow/docs/performance.md +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +13 -12
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +37 -33
- package/node_modules/@llblab/pi-state-flow/index.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +20 -10
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +15 -14
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +8 -6
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +484 -232
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +14 -5
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +16 -11
- package/node_modules/@llblab/pi-state-flow/lib/logging.ts +5 -1
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +16 -16
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +11 -11
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +19 -13
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +57 -3
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +57 -22
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +46 -13
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +23 -32
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +66 -65
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +39 -19
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +19 -27
- package/node_modules/@llblab/pi-state-flow/package.json +12 -11
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +2 -2
- package/node_modules/jsonc-parser/CHANGELOG.md +76 -0
- package/node_modules/jsonc-parser/LICENSE.md +21 -0
- package/node_modules/jsonc-parser/README.md +364 -0
- package/node_modules/jsonc-parser/SECURITY.md +41 -0
- package/node_modules/jsonc-parser/lib/esm/impl/edit.js +185 -0
- package/node_modules/jsonc-parser/lib/esm/impl/format.js +261 -0
- package/node_modules/jsonc-parser/lib/esm/impl/parser.js +659 -0
- package/node_modules/jsonc-parser/lib/esm/impl/scanner.js +443 -0
- package/node_modules/jsonc-parser/lib/esm/impl/string-intern.js +29 -0
- package/node_modules/jsonc-parser/lib/esm/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/esm/main.js +178 -0
- package/node_modules/jsonc-parser/lib/umd/impl/edit.js +201 -0
- package/node_modules/jsonc-parser/lib/umd/impl/format.js +275 -0
- package/node_modules/jsonc-parser/lib/umd/impl/parser.js +682 -0
- package/node_modules/jsonc-parser/lib/umd/impl/scanner.js +456 -0
- package/node_modules/jsonc-parser/lib/umd/impl/string-intern.js +42 -0
- package/node_modules/jsonc-parser/lib/umd/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/umd/main.js +194 -0
- package/node_modules/jsonc-parser/package.json +37 -0
- package/package.json +10 -6
|
@@ -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";
|
|
@@ -20,10 +20,10 @@ import { assistantToolCallCount, conciseDiagnostic, diagnosticText, finalizedAss
|
|
|
20
20
|
import { readProjectedState, readStatePath } from "./query.js";
|
|
21
21
|
import { selectedBoundaryFailure, selectRetainedCheckpoint, waitForRecovery } from "./recovery.js";
|
|
22
22
|
import { TemporalRuntime } from "./runtime.js";
|
|
23
|
-
import { discoverSnapshotData, findAssistantToolBatch, findPassiveStopBoundary, hasPriorConversation, hasUncheckpointedConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.js";
|
|
23
|
+
import { discoverSnapshotData, findAssistantToolBatch, findBranchPolicy, findPassiveStopBoundary, hasPendingFork, 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,29 +36,31 @@ 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;
|
|
52
|
+
let deferredBranch;
|
|
53
53
|
const restorationOperations = new Set();
|
|
54
54
|
let responseReconciliation;
|
|
55
55
|
let sharedInspectionLifetime = new AbortController();
|
|
56
|
+
let memoryToolLifetime = new AbortController();
|
|
56
57
|
let completedRunAccepted = false;
|
|
57
58
|
let turnAcceptedForBackup = false;
|
|
58
59
|
let compactionPlan;
|
|
59
60
|
let compactionInFlight = false;
|
|
60
61
|
let compactionStopped = false;
|
|
61
|
-
const
|
|
62
|
+
const compactionPrefix = `state-flow-boundary:${randomUUID()}:`;
|
|
63
|
+
let compactionMarker;
|
|
62
64
|
let passiveContinuation;
|
|
63
65
|
let bootstrapContinuation;
|
|
64
66
|
const contextProjection = new ContextProjection();
|
|
@@ -70,7 +72,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
70
72
|
const repositoryRoot = resolve(options.repositoryRoot ?? config.directory);
|
|
71
73
|
const diagnosticWriter = new StateFlowDiagnosticWriter(config.logging, stateFlowLogPath(agentDir), repositoryRoot, (message) => notifyActiveContext(message));
|
|
72
74
|
let backupPending = false;
|
|
73
|
-
|
|
75
|
+
let backupLifetime = new AbortController();
|
|
74
76
|
const backupOperations = new Set();
|
|
75
77
|
let shuttingDown = false;
|
|
76
78
|
let pushFailureNotified = false;
|
|
@@ -113,18 +115,26 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
113
115
|
assertSelectedBranchAvailable();
|
|
114
116
|
if (!runtime)
|
|
115
117
|
throw new Error("State Flow temporal runtime is unavailable");
|
|
116
|
-
|
|
118
|
+
const { lazy: _lazy, ...defaults } = emptyState();
|
|
119
|
+
return { ...defaults, ...projectModelState(runtime.readView(offset, scope)) };
|
|
117
120
|
} });
|
|
118
121
|
function assertPublicationAvailable() {
|
|
119
122
|
assertSelectedBranchAvailable();
|
|
120
|
-
if (
|
|
121
|
-
throw new Error(`Memory writes paused after
|
|
123
|
+
if (modePersistenceError)
|
|
124
|
+
throw new Error(`Memory writes paused after mode change: ${modePersistenceError}; use /state-flow-active`);
|
|
125
|
+
}
|
|
126
|
+
function isActive() {
|
|
127
|
+
return snapshot.config.mode === "active";
|
|
128
|
+
}
|
|
129
|
+
/** An unavailable or pending active selection keeps the configured inactive policy, never an invented one. */
|
|
130
|
+
function inactiveSelection(mode) {
|
|
131
|
+
return mode === "active" ? config.inactiveMode : mode;
|
|
122
132
|
}
|
|
123
133
|
function appendCheckpoint() {
|
|
124
|
-
const checkpoint = branchStartsWithoutRuntime ?
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
const checkpoint = branchStartsWithoutRuntime ? preRuntimeCheckpoint(snapshot.config.mode)
|
|
135
|
+
: runtime?.view ? runtime.retainedCheckpoint(snapshot) : undefined;
|
|
136
|
+
if (!checkpoint)
|
|
137
|
+
throw new Error("State Flow cannot checkpoint an unproven branch; restore a valid checkpoint first");
|
|
128
138
|
pi.appendEntry(SNAPSHOT_ENTRY_TYPE, checkpoint);
|
|
129
139
|
if ("boundary" in checkpoint)
|
|
130
140
|
branchStartsWithoutRuntime = false;
|
|
@@ -137,10 +147,10 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
137
147
|
function updateUi(ctx) {
|
|
138
148
|
ctx.ui.setStatus(STATUS_KEY, compactStatus(snapshot, scopeRevisions(), (color, text) => ctx.ui.theme.fg(color, text)));
|
|
139
149
|
}
|
|
140
|
-
function
|
|
141
|
-
const pending =
|
|
150
|
+
function cancelInactivePersistence() {
|
|
151
|
+
const pending = inactivePersistence;
|
|
142
152
|
pending?.controller.abort();
|
|
143
|
-
|
|
153
|
+
inactivePersistence = undefined;
|
|
144
154
|
return pending?.operation;
|
|
145
155
|
}
|
|
146
156
|
function cancelStartActivation() {
|
|
@@ -163,6 +173,11 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
163
173
|
sharedInspectionLifetime.abort(new Error("State Flow inspection was cancelled; select the scope again"));
|
|
164
174
|
sharedInspectionLifetime = new AbortController();
|
|
165
175
|
}
|
|
176
|
+
function cancelBackupWork() {
|
|
177
|
+
backupLifetime.abort();
|
|
178
|
+
backupLifetime = new AbortController();
|
|
179
|
+
backupPending = false;
|
|
180
|
+
}
|
|
166
181
|
function cancelInferencePreparation() {
|
|
167
182
|
inferencePreparation?.controller.abort();
|
|
168
183
|
inferencePreparation = undefined;
|
|
@@ -223,7 +238,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
223
238
|
function missingArtifactRemovals(states) {
|
|
224
239
|
const owners = new Map();
|
|
225
240
|
for (const scope of ["global", "cwd", "session"])
|
|
226
|
-
for (const path of Object.keys(states[scope].artifacts)) {
|
|
241
|
+
for (const path of Object.keys(states[scope].artifacts ?? {})) {
|
|
227
242
|
owners.set(path, [...owners.get(path) ?? [], scope]);
|
|
228
243
|
}
|
|
229
244
|
const removals = {};
|
|
@@ -239,36 +254,53 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
239
254
|
}
|
|
240
255
|
function installScopeStates() {
|
|
241
256
|
scopeStates = runtime?.view ? runtime.states() : { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
257
|
+
effectiveState = runtime?.view ? runtime.readView() : {};
|
|
258
|
+
}
|
|
259
|
+
/** Active and passive expose both memory tools; Off exposes neither. */
|
|
260
|
+
function memoryToolsAvailable() {
|
|
261
|
+
return snapshot.config.mode !== "off";
|
|
242
262
|
}
|
|
243
|
-
|
|
244
|
-
|
|
263
|
+
/** Passive memory context needs a loaded view; Off and Active never inject it. */
|
|
264
|
+
function passiveMemoryAvailable() {
|
|
265
|
+
return snapshot.config.mode === "passive" && runtime?.view !== undefined;
|
|
245
266
|
}
|
|
246
267
|
async function inspectTelegramState(scope) {
|
|
247
268
|
if (!activeContext || shuttingDown)
|
|
248
269
|
throw new Error("State Flow is not attached to an active session yet");
|
|
249
|
-
|
|
270
|
+
const ctx = activeContext;
|
|
271
|
+
const owner = sessionAddress(ctx).key;
|
|
272
|
+
const cwd = ctx.cwd;
|
|
273
|
+
const off = snapshot.config.mode === "off";
|
|
274
|
+
const privateScope = scope === "session" || scope === "effective";
|
|
275
|
+
if (!off && privateScope)
|
|
250
276
|
assertSelectedBranchAvailable();
|
|
251
|
-
|
|
277
|
+
// Off operator reads are disposable: they confer no selected-branch/cache authority.
|
|
278
|
+
const selected = off ? createRuntime(ctx) : runtime ??= createRuntime(ctx);
|
|
252
279
|
const signal = sharedInspectionLifetime.signal;
|
|
253
|
-
|
|
280
|
+
let privateAvailable = true;
|
|
281
|
+
if (off && privateScope)
|
|
282
|
+
privateAvailable = await selected.refreshCurrentMemory(signal) !== undefined;
|
|
283
|
+
else if (!selected.view || (scope !== "session" && !modePersistenceError))
|
|
254
284
|
await selected.refreshShared(signal);
|
|
255
285
|
signal.throwIfAborted();
|
|
256
|
-
if (runtime !== selected)
|
|
286
|
+
if (activeContext !== ctx || ctx.cwd !== cwd || sessionAddress(ctx).key !== owner || (!off && runtime !== selected)) {
|
|
257
287
|
throw new Error("State Flow inspection selection changed; select the scope again");
|
|
258
|
-
|
|
288
|
+
}
|
|
289
|
+
if (!privateAvailable)
|
|
290
|
+
throw new Error("Current State Flow session memory is unavailable");
|
|
291
|
+
if (!off && privateScope)
|
|
259
292
|
assertSelectedBranchAvailable();
|
|
260
293
|
if (!selected.view)
|
|
261
294
|
throw new Error("State Flow temporal runtime is unavailable");
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return { state: { ...projectModelState(state), lazy: structuredClone(state.lazy) }, revisions: scopeRevisions(), signal };
|
|
295
|
+
if (!off)
|
|
296
|
+
installScopeStates();
|
|
297
|
+
const state = selected.readView(0, scope === "effective" ? undefined : scope);
|
|
298
|
+
return { state: { ...projectModelState(state), ...(state.lazy === undefined ? {} : { lazy: structuredClone(state.lazy) }) }, revisions: temporalScopeRevisions(selected.view), signal };
|
|
267
299
|
}
|
|
268
300
|
function syncStateFlowTools() {
|
|
269
301
|
const active = pi.getActiveTools();
|
|
270
302
|
const owned = [PATCH_STATE_TOOL_NAME, READ_STATE_TOOL_NAME];
|
|
271
|
-
const available =
|
|
303
|
+
const available = memoryToolsAvailable();
|
|
272
304
|
if (owned.every((name) => active.includes(name) === available))
|
|
273
305
|
return;
|
|
274
306
|
pi.setActiveTools(available
|
|
@@ -313,7 +345,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
313
345
|
throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
314
346
|
await selected.withPatchTransaction((transaction) => {
|
|
315
347
|
signal.throwIfAborted();
|
|
316
|
-
if (runtime !== selected || inferencePreparation !== pending || !
|
|
348
|
+
if (runtime !== selected || inferencePreparation !== pending || !isActive() || shuttingDown) {
|
|
317
349
|
throw new Error("State Flow inference selection changed while awaiting publication");
|
|
318
350
|
}
|
|
319
351
|
assertPublicationAvailable();
|
|
@@ -379,6 +411,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
379
411
|
cwdScopeKey: cwdScopeKey(cwd),
|
|
380
412
|
sessionScopeKey: sessionScopeKey(session.key),
|
|
381
413
|
scopeStates: diagnosticStates,
|
|
414
|
+
effectiveState,
|
|
382
415
|
recent: diagnosticRecent,
|
|
383
416
|
historyLimit: config.historyLimit,
|
|
384
417
|
...(view === undefined ? {} : { temporal: {
|
|
@@ -389,7 +422,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
389
422
|
} }),
|
|
390
423
|
staleArtifacts,
|
|
391
424
|
...(durableStateError === undefined ? {} : { durableStateError }),
|
|
392
|
-
...(
|
|
425
|
+
...(modePersistenceError === undefined ? {} : { publicationError: modePersistenceError }),
|
|
393
426
|
};
|
|
394
427
|
}
|
|
395
428
|
function forkSource(ctx) {
|
|
@@ -402,13 +435,15 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
402
435
|
return resolveSessionAddress(parent.file, parent.id, parent.timestamp);
|
|
403
436
|
}
|
|
404
437
|
/** Select the active native branch under one owned restoration lifetime; only current accepted work installs memory. */
|
|
405
|
-
function restoreActiveBranch(ctx, sessionStartReason, notifyRecovery = true, startOwner) {
|
|
438
|
+
function restoreActiveBranch(ctx, sessionStartReason, notifyRecovery = true, startOwner, requestedMode) {
|
|
406
439
|
contextProjection.reset();
|
|
440
|
+
memoryToolLifetime.abort();
|
|
441
|
+
memoryToolLifetime = new AbortController();
|
|
407
442
|
cancelBranchRestoration();
|
|
408
443
|
// Start-owned attachment/fork recovery keeps its owner; only accepted Start cancels Stop.
|
|
409
444
|
if (!startOwner) {
|
|
410
445
|
cancelStartActivation();
|
|
411
|
-
|
|
446
|
+
cancelInactivePersistence();
|
|
412
447
|
}
|
|
413
448
|
clearRunTransient();
|
|
414
449
|
passiveContinuation = undefined;
|
|
@@ -417,6 +452,54 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
417
452
|
artifactReads.setCandidates([]);
|
|
418
453
|
telegramStartPending = false;
|
|
419
454
|
activeContext = ctx;
|
|
455
|
+
if (!startOwner) {
|
|
456
|
+
let mode = config.inactiveMode;
|
|
457
|
+
let adoptDefault = false;
|
|
458
|
+
let withoutRuntime = false;
|
|
459
|
+
let persistenceError;
|
|
460
|
+
let pendingFork = false;
|
|
461
|
+
try {
|
|
462
|
+
const branch = ctx.sessionManager.getBranch();
|
|
463
|
+
const discovery = discoverSnapshotData(branch);
|
|
464
|
+
withoutRuntime = discovery.candidates.length === 0 && discovery.errors.length === 0;
|
|
465
|
+
adoptDefault = withoutRuntime && isNewSession(sessionStartReason, branch);
|
|
466
|
+
pendingFork = hasPendingFork(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE);
|
|
467
|
+
const inherited = sessionStartReason === "fork" || pendingFork;
|
|
468
|
+
const policy = findBranchPolicy(branch, inherited ? undefined : ctx.sessionManager.getSessionId(), inherited || retainsPhysicalSessionProjection(sessionStartReason) ? PASSIVE_STOP_ENTRY_TYPE : undefined, config.inactiveMode);
|
|
469
|
+
mode = policy?.mode ?? (adoptDefault ? config.mode : config.inactiveMode);
|
|
470
|
+
persistenceError = inherited
|
|
471
|
+
? findBranchPolicy(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE, config.inactiveMode)?.persistenceError
|
|
472
|
+
: policy?.persistenceError;
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
// Off needs only policy; unavailable native evidence grants no memory authority.
|
|
476
|
+
}
|
|
477
|
+
if (mode === "off") {
|
|
478
|
+
cancelBackupWork();
|
|
479
|
+
const owner = sessionAddress(ctx).key;
|
|
480
|
+
const reason = pendingFork || (deferredBranch?.owner === owner && deferredBranch.cwd === ctx.cwd && deferredBranch.reason === "fork"
|
|
481
|
+
&& retainsPhysicalSessionProjection(sessionStartReason)) ? "fork" : sessionStartReason;
|
|
482
|
+
if (reason === "fork" && !pendingFork)
|
|
483
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { owner: ctx.sessionManager.getSessionId(), forkPending: true });
|
|
484
|
+
deferredBranch = { reason, owner, cwd: ctx.cwd };
|
|
485
|
+
runtime = undefined;
|
|
486
|
+
snapshot = emptySnapshot("off");
|
|
487
|
+
branchStartsWithoutRuntime = withoutRuntime;
|
|
488
|
+
selectedHistoryExpired = false;
|
|
489
|
+
modePersistenceError = persistenceError;
|
|
490
|
+
forkInitialization = reason === "fork";
|
|
491
|
+
installScopeStates();
|
|
492
|
+
artifactHints = {};
|
|
493
|
+
if (adoptDefault)
|
|
494
|
+
appendCheckpoint();
|
|
495
|
+
syncStateFlowTools();
|
|
496
|
+
updateUi(ctx);
|
|
497
|
+
return Promise.resolve();
|
|
498
|
+
}
|
|
499
|
+
if (pendingFork)
|
|
500
|
+
sessionStartReason = "fork";
|
|
501
|
+
}
|
|
502
|
+
deferredBranch = undefined;
|
|
420
503
|
runtime = createRuntime(ctx);
|
|
421
504
|
installScopeStates();
|
|
422
505
|
branchStartsWithoutRuntime = false;
|
|
@@ -427,56 +510,72 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
427
510
|
try {
|
|
428
511
|
const branch = ctx.sessionManager.getBranch();
|
|
429
512
|
const fence = retainsPhysicalSessionProjection(sessionStartReason)
|
|
430
|
-
? findPassiveStopBoundary(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE)
|
|
513
|
+
? findPassiveStopBoundary(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE)
|
|
431
514
|
: undefined;
|
|
432
|
-
|
|
433
|
-
if (fence) {
|
|
434
|
-
// The native
|
|
435
|
-
selection = { kind: "current" };
|
|
515
|
+
modePersistenceError = fence?.persistenceError ?? (startOwner ? modePersistenceError : undefined);
|
|
516
|
+
if (fence?.persistenceError) {
|
|
517
|
+
// The native marker owns policy only: read proven memory, never replay or publish a stale selection.
|
|
518
|
+
selection = { kind: "current", mode: fence.mode ?? config.inactiveMode };
|
|
436
519
|
}
|
|
437
520
|
else {
|
|
438
521
|
const discovery = discoverSnapshotData(branch);
|
|
439
|
-
const selected = selectRetainedCheckpoint(discovery.candidates);
|
|
522
|
+
const selected = selectRetainedCheckpoint(discovery.candidates, config.inactiveMode);
|
|
440
523
|
skipped = discovery.errors.length + selected.skipped.length;
|
|
441
|
-
branchStartsWithoutRuntime = selected.kind === "
|
|
524
|
+
branchStartsWithoutRuntime = selected.kind === "pre-runtime" || (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
442
525
|
if (discovery.candidates.length === 0 && discovery.errors.length > 0) {
|
|
443
|
-
snapshot = migrationFailure({}, `Snapshot restoration failed: ${discovery.errors[0]}
|
|
526
|
+
snapshot = migrationFailure({}, `Snapshot restoration failed: ${discovery.errors[0]}`, config.inactiveMode);
|
|
444
527
|
}
|
|
445
528
|
else if (selected.kind === "boundary" && forkInitialization) {
|
|
446
529
|
try {
|
|
447
530
|
// Native parent acquisition stays outside canonical exclusion.
|
|
448
531
|
const source = forkSource(ctx);
|
|
449
|
-
const
|
|
450
|
-
selection = { kind: "fork", source, checkpoint:
|
|
532
|
+
const sourceFence = findPassiveStopBoundary(branch, source.id, PASSIVE_STOP_ENTRY_TYPE);
|
|
533
|
+
selection = { kind: "fork", source, checkpoint: sourceFence?.persistenceError !== undefined
|
|
534
|
+
? { ...selected.checkpoint, mode: sourceFence.mode ?? config.inactiveMode } : selected.checkpoint };
|
|
451
535
|
}
|
|
452
536
|
catch (error) {
|
|
453
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
537
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), inactiveSelection(selected.checkpoint.mode));
|
|
454
538
|
}
|
|
455
539
|
}
|
|
456
540
|
else if (selected.kind === "boundary") {
|
|
457
541
|
selection = { kind: "restore", checkpoint: selected.checkpoint };
|
|
458
542
|
}
|
|
543
|
+
else if (selected.kind === "pre-runtime") {
|
|
544
|
+
snapshot = emptySnapshot(selected.mode);
|
|
545
|
+
}
|
|
459
546
|
else if (discovery.candidates.length > 0) {
|
|
460
|
-
snapshot = selected.
|
|
547
|
+
snapshot = selected.snapshot;
|
|
461
548
|
}
|
|
462
|
-
else if (
|
|
463
|
-
|
|
549
|
+
else if (isNewSession(sessionStartReason, branch)) {
|
|
550
|
+
// The repository mode is only a default for genuinely new sessions.
|
|
551
|
+
if (config.mode === "active")
|
|
552
|
+
selection = { kind: "auto-start" };
|
|
553
|
+
else {
|
|
554
|
+
snapshot = emptySnapshot(config.mode);
|
|
555
|
+
// Adopt the default once without initializing semantic storage.
|
|
556
|
+
appendCheckpoint();
|
|
557
|
+
}
|
|
464
558
|
}
|
|
465
559
|
else {
|
|
466
|
-
snapshot = emptySnapshot();
|
|
560
|
+
snapshot = emptySnapshot(config.inactiveMode);
|
|
467
561
|
}
|
|
468
562
|
}
|
|
469
563
|
}
|
|
470
564
|
catch (error) {
|
|
471
565
|
selection = { kind: "settled" };
|
|
472
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
566
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), config.inactiveMode);
|
|
473
567
|
}
|
|
474
568
|
// 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"
|
|
569
|
+
if (selection.kind !== "settled") {
|
|
570
|
+
snapshot = migrationFailure({}, "State Flow branch restoration is pending", selection.kind === "current" ? selection.mode
|
|
571
|
+
: selection.kind === "auto-start" ? config.inactiveMode : inactiveSelection(selection.checkpoint.mode));
|
|
572
|
+
}
|
|
573
|
+
if (requestedMode)
|
|
574
|
+
snapshot = deactivateEpisode(snapshot, requestedMode);
|
|
477
575
|
const pending = {
|
|
478
576
|
controller: new AbortController(),
|
|
479
577
|
awaitingAcceptance: selection.kind !== "settled" && selection.kind !== "current",
|
|
578
|
+
...(requestedMode === undefined ? {} : { requestedMode }),
|
|
480
579
|
};
|
|
481
580
|
branchRestoration = pending;
|
|
482
581
|
const operation = attachBranch(ctx, pending, selection, sessionStartReason, notifyRecovery, skipped);
|
|
@@ -534,17 +633,18 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
534
633
|
assertCurrent();
|
|
535
634
|
if (current)
|
|
536
635
|
runtime = selected = candidate;
|
|
537
|
-
snapshot = current
|
|
636
|
+
snapshot = current ? deactivateEpisode(current, pending.requestedMode ?? selection.mode)
|
|
637
|
+
: migrationFailure({}, "Current State Flow session memory is unavailable", pending.requestedMode ?? selection.mode);
|
|
538
638
|
installScopeStates();
|
|
539
639
|
}
|
|
540
640
|
else if (selection.kind === "restore") {
|
|
541
641
|
await candidate.withRestoreTransaction(selection.checkpoint, (restored, publish) => {
|
|
542
642
|
assertCurrent();
|
|
543
643
|
// Canceled preparation or boundary continuation may have no specification. Retain uncompiled native context.
|
|
544
|
-
if (restored.config.
|
|
644
|
+
if (restored.config.mode === "active" && restored.meta.specification === undefined
|
|
545
645
|
&& retainsPhysicalSessionProjection(reason) && hasUncheckpointedConversation(ctx.sessionManager.getBranch()))
|
|
546
646
|
restored.meta.bootstrap = true;
|
|
547
|
-
const next = pending.
|
|
647
|
+
const next = pending.requestedMode ? deactivateEpisode(restored, pending.requestedMode) : restored;
|
|
548
648
|
accept(candidate, next, publish(next), appendCheckpoint);
|
|
549
649
|
}, signal);
|
|
550
650
|
}
|
|
@@ -552,7 +652,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
552
652
|
await candidate.withForkTransaction(selection.source, selection.checkpoint, (child, publish) => {
|
|
553
653
|
assertCurrent();
|
|
554
654
|
// Mode is selected independently of creating the child's private memory.
|
|
555
|
-
const next = pending.
|
|
655
|
+
const next = pending.requestedMode ? deactivateEpisode(child, pending.requestedMode) : child;
|
|
556
656
|
const publication = publish(next);
|
|
557
657
|
forkInitialization = false;
|
|
558
658
|
accept(candidate, next, publication, () => {
|
|
@@ -570,7 +670,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
570
670
|
if (current || discovery.candidates.length > 0 || discovery.errors.length > 0)
|
|
571
671
|
throw new Error("Existing session runtime requires retained-boundary restoration");
|
|
572
672
|
const activated = startEpisode(hasPriorConversation(branch));
|
|
573
|
-
const next = pending.
|
|
673
|
+
const next = pending.requestedMode ? deactivateEpisode(activated, pending.requestedMode) : activated;
|
|
574
674
|
accept(candidate, next, publish(next), appendCheckpoint);
|
|
575
675
|
}, signal, true);
|
|
576
676
|
}
|
|
@@ -585,7 +685,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
585
685
|
return;
|
|
586
686
|
if (error instanceof HistoryBoundaryExpiredError)
|
|
587
687
|
selectedHistoryExpired = true;
|
|
588
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
688
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), snapshot.config.mode === "active" ? config.inactiveMode : snapshot.config.mode);
|
|
589
689
|
installScopeStates();
|
|
590
690
|
}
|
|
591
691
|
finally {
|
|
@@ -595,21 +695,14 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
595
695
|
return;
|
|
596
696
|
}
|
|
597
697
|
settleSelection(ctx, reason, notifyRecovery, skipped, selected !== placeholder);
|
|
598
|
-
if (snapshot.config.
|
|
698
|
+
if (snapshot.config.mode !== "passive" || runtime?.view)
|
|
599
699
|
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");
|
|
700
|
+
const passive = await loadPassiveView(ctx, signal, isCurrent, notifyRecovery);
|
|
701
|
+
if (passive === undefined)
|
|
608
702
|
return;
|
|
609
|
-
}
|
|
610
703
|
assertCurrent();
|
|
611
704
|
// An intervening passive patch or inspection owns its newer cache, even on the same physical branch.
|
|
612
|
-
if (
|
|
705
|
+
if (runtime?.view)
|
|
613
706
|
return;
|
|
614
707
|
runtime = selected = passive;
|
|
615
708
|
installScopeStates();
|
|
@@ -619,7 +712,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
619
712
|
// Host failures before acceptance leave the selection unavailable, never invented empty memory.
|
|
620
713
|
if (accepted || !isCurrent())
|
|
621
714
|
return;
|
|
622
|
-
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
715
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error), snapshot.config.mode === "active" ? config.inactiveMode : snapshot.config.mode);
|
|
623
716
|
installScopeStates();
|
|
624
717
|
}
|
|
625
718
|
finally {
|
|
@@ -628,28 +721,44 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
628
721
|
branchRestoration = undefined;
|
|
629
722
|
}
|
|
630
723
|
}
|
|
724
|
+
/** Load current shared memory for passive projection without initializing or publishing it. */
|
|
725
|
+
async function loadPassiveView(ctx, signal, isCurrent, notify) {
|
|
726
|
+
const passive = createRuntime(ctx);
|
|
727
|
+
try {
|
|
728
|
+
await passive.refreshShared(signal);
|
|
729
|
+
}
|
|
730
|
+
catch (error) {
|
|
731
|
+
if (isCurrent() && !signal.aborted && notify && !modePersistenceError)
|
|
732
|
+
notifyProblem(ctx, `State Flow passive memory is unavailable: ${diagnosticText(error)}`, "warning");
|
|
733
|
+
return undefined;
|
|
734
|
+
}
|
|
735
|
+
return passive;
|
|
736
|
+
}
|
|
737
|
+
function selectedContinuation(ctx, reason) {
|
|
738
|
+
if (!retainsPhysicalSessionProjection(reason) || !runtime?.view)
|
|
739
|
+
return undefined;
|
|
740
|
+
const boundary = findPassiveStopBoundary(ctx.sessionManager.getBranch(), ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE);
|
|
741
|
+
return boundary === undefined ? undefined : createPassiveContinuation(projectModelState(effectiveState), boundary.at, boundary.from, boundary.preserveContext);
|
|
742
|
+
}
|
|
631
743
|
function settleSelection(ctx, reason, notifyRecovery, skipped, selectedMemory) {
|
|
632
|
-
const failure = !
|
|
633
|
-
if (failure !== undefined && notifyRecovery && !
|
|
744
|
+
const failure = !isActive() && snapshot.meta.validation?.attempt === 0 ? snapshot.meta.validation.error : undefined;
|
|
745
|
+
if (failure !== undefined && notifyRecovery && !modePersistenceError) {
|
|
634
746
|
if (selectedHistoryExpired)
|
|
635
|
-
notifyProblem(ctx, "State Flow history is outside the retained temporal window; /state-flow-
|
|
747
|
+
notifyProblem(ctx, "State Flow history is outside the retained temporal window; /state-flow-active can use current session memory.", "warning");
|
|
636
748
|
else
|
|
637
749
|
notifyProblem(ctx, `State Flow restore failed: ${failure}`, "error");
|
|
638
750
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
else if (snapshot.meta.bootstrap)
|
|
646
|
-
bootstrapContinuation = continuation;
|
|
647
|
-
}
|
|
751
|
+
const continuation = selectedMemory ? selectedContinuation(ctx, reason) : undefined;
|
|
752
|
+
if (continuation !== undefined) {
|
|
753
|
+
if (!isActive())
|
|
754
|
+
passiveContinuation = continuation;
|
|
755
|
+
else if (snapshot.meta.bootstrap)
|
|
756
|
+
bootstrapContinuation = continuation;
|
|
648
757
|
}
|
|
649
|
-
if (notifyRecovery &&
|
|
758
|
+
if (notifyRecovery && isActive() && skipped > 0) {
|
|
650
759
|
notifyProblem(ctx, `State Flow skipped ${skipped} malformed snapshot(s); restored the last valid one.`, "warning");
|
|
651
760
|
}
|
|
652
|
-
if (
|
|
761
|
+
if (isActive())
|
|
653
762
|
deferInferencePreparation();
|
|
654
763
|
syncStateFlowTools();
|
|
655
764
|
updateUi(ctx);
|
|
@@ -669,8 +778,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
669
778
|
}, { additionalProperties: false }),
|
|
670
779
|
async execute(_toolCallId, params, signal) {
|
|
671
780
|
try {
|
|
672
|
-
if (!
|
|
673
|
-
throw new Error("State Flow tools are
|
|
781
|
+
if (!memoryToolsAvailable())
|
|
782
|
+
throw new Error("State Flow tools are off for this session");
|
|
674
783
|
if (signal?.aborted)
|
|
675
784
|
throw new Error("State Flow read was aborted");
|
|
676
785
|
if (!runtime?.view)
|
|
@@ -731,9 +840,10 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
731
840
|
return new Text(separatedOutput(theme.fg("dim", formatPatchStateArguments(context.args))), 0, 0);
|
|
732
841
|
},
|
|
733
842
|
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
843
|
+
signal = signal ? AbortSignal.any([signal, memoryToolLifetime.signal]) : memoryToolLifetime.signal;
|
|
734
844
|
try {
|
|
735
|
-
if (!
|
|
736
|
-
throw new Error("State Flow tools are
|
|
845
|
+
if (!memoryToolsAvailable())
|
|
846
|
+
throw new Error("State Flow tools are off for this session");
|
|
737
847
|
assertPublicationAvailable();
|
|
738
848
|
if (signal?.aborted)
|
|
739
849
|
throw new Error("State Flow patch was aborted before materialization");
|
|
@@ -762,12 +872,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
762
872
|
const selected = runtime ??= createRuntime(ctx);
|
|
763
873
|
const acquiredArtifacts = structuredClone([...artifactReads.successful.values()]);
|
|
764
874
|
const acquiredSkills = structuredClone([...skillReads.successful.values()]);
|
|
765
|
-
const previousEffective =
|
|
875
|
+
const previousEffective = effectiveState;
|
|
766
876
|
return await selected.withPatchTransaction((transaction) => {
|
|
767
877
|
if (runtime !== selected)
|
|
768
878
|
throw new Error("State Flow session selection changed while awaiting publication");
|
|
769
|
-
if (!
|
|
770
|
-
throw new Error("State Flow tools are
|
|
879
|
+
if (!memoryToolsAvailable())
|
|
880
|
+
throw new Error("State Flow tools are off for this session");
|
|
771
881
|
assertPublicationAvailable();
|
|
772
882
|
for (const acquired of acquiredArtifacts) {
|
|
773
883
|
const observation = inspectRegisteredArtifactPaths([acquired.path])[0];
|
|
@@ -792,7 +902,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
792
902
|
}
|
|
793
903
|
clearAcceptedAcquisitions(new Set(acquiredArtifacts.map(({ path }) => path)));
|
|
794
904
|
updateUi(ctx);
|
|
795
|
-
const updates = contextProjection.acceptPatch(previousEffective,
|
|
905
|
+
const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, artifactHints);
|
|
796
906
|
const acknowledgement = changed
|
|
797
907
|
? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
|
|
798
908
|
: "\nState already current.";
|
|
@@ -811,11 +921,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
811
921
|
attempted = undefined;
|
|
812
922
|
}
|
|
813
923
|
const cause = diagnosticText(error);
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
924
|
+
if (!signal.aborted && memoryToolsAvailable())
|
|
925
|
+
recordDiagnostic(cause, /concurrently|advanced/.test(cause) ? "publication-conflict" : "invalid-patch", ctx, {
|
|
926
|
+
input: attempted,
|
|
927
|
+
tool: PATCH_STATE_TOOL_NAME,
|
|
928
|
+
toolCallId,
|
|
929
|
+
});
|
|
819
930
|
throw separatedFailure(error);
|
|
820
931
|
}
|
|
821
932
|
},
|
|
@@ -826,8 +937,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
826
937
|
if (startActivation?.operation)
|
|
827
938
|
return startActivation.operation;
|
|
828
939
|
telegramStartPending = false;
|
|
829
|
-
if (activeContext && !branchRestoration &&
|
|
830
|
-
return Promise.resolve({ ok: true, message: "State Flow is already
|
|
940
|
+
if (activeContext && !branchRestoration && isActive()) {
|
|
941
|
+
return Promise.resolve({ ok: true, message: "State Flow is already active", signal: sharedInspectionLifetime.signal });
|
|
831
942
|
}
|
|
832
943
|
const pending = { controller: new AbortController() };
|
|
833
944
|
startActivation = pending;
|
|
@@ -850,20 +961,22 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
850
961
|
&& ctx.sessionManager.getHeader()?.timestamp === timestamp;
|
|
851
962
|
const superseded = () => {
|
|
852
963
|
pending.abort();
|
|
853
|
-
return { ok: false, message: "State Flow
|
|
964
|
+
return { ok: false, message: "State Flow activation was superseded", signal: pending.signal };
|
|
854
965
|
};
|
|
855
966
|
try {
|
|
856
|
-
if (
|
|
967
|
+
if (deferredBranch?.reason === "fork")
|
|
968
|
+
await waitForRecovery(restoreActiveBranch(ctx, "fork", false, pending), signal);
|
|
969
|
+
else if (!activeContext)
|
|
857
970
|
await waitForRecovery(restoreActiveBranch(ctx, undefined, false, pending), signal);
|
|
858
971
|
else if (branchRestoration?.operation)
|
|
859
972
|
await waitForRecovery(branchRestoration.operation, signal);
|
|
860
973
|
if (!isOwner())
|
|
861
974
|
return superseded();
|
|
862
|
-
if (
|
|
863
|
-
return { ok: true, message: "State Flow is already
|
|
975
|
+
if (isActive())
|
|
976
|
+
return { ok: true, message: "State Flow is already active", signal: sharedInspectionLifetime.signal };
|
|
864
977
|
if (forkInitialization) {
|
|
865
|
-
// Withdraw this join on cancellation without revoking the independently owned
|
|
866
|
-
const stopping =
|
|
978
|
+
// Withdraw this join on cancellation without revoking the independently owned mode persistence.
|
|
979
|
+
const stopping = inactivePersistence?.operation;
|
|
867
980
|
if (stopping)
|
|
868
981
|
await waitForRecovery(stopping, signal);
|
|
869
982
|
if (!isOwner())
|
|
@@ -878,7 +991,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
878
991
|
catch (error) {
|
|
879
992
|
if (!isOwner())
|
|
880
993
|
return superseded();
|
|
881
|
-
const message = conciseDiagnostic(`State Flow
|
|
994
|
+
const message = conciseDiagnostic(`State Flow activation failed: ${diagnosticText(error)}`);
|
|
882
995
|
notifyProblem(ctx, message, "error");
|
|
883
996
|
return { ok: false, message, signal: AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]) };
|
|
884
997
|
}
|
|
@@ -889,38 +1002,48 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
889
1002
|
const cwd = ctx.cwd;
|
|
890
1003
|
const file = ctx.sessionManager.getSessionFile();
|
|
891
1004
|
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
892
|
-
const
|
|
1005
|
+
const initiallyActive = isActive();
|
|
893
1006
|
let accepted = false;
|
|
894
1007
|
let receipt = AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]);
|
|
895
|
-
const
|
|
1008
|
+
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
1009
|
+
const isCurrent = () => startActivation?.controller === pending && !signal.aborted && runtime === selected && !shuttingDown
|
|
896
1010
|
&& ctx.cwd === cwd && ctx.sessionManager.getSessionId() === owner && ctx.sessionManager.getSessionFile() === file
|
|
897
|
-
&& ctx.sessionManager.getHeader()?.timestamp === timestamp &&
|
|
1011
|
+
&& ctx.sessionManager.getHeader()?.timestamp === timestamp && isActive() === (accepted || initiallyActive);
|
|
898
1012
|
const superseded = () => {
|
|
899
1013
|
pending.abort();
|
|
900
|
-
return { ok: false, message: "State Flow
|
|
1014
|
+
return { ok: false, message: "State Flow activation was superseded", signal: pending.signal };
|
|
901
1015
|
};
|
|
902
1016
|
try {
|
|
903
1017
|
const activation = createRuntime(ctx);
|
|
904
|
-
const
|
|
1018
|
+
const deferred = deferredBranch;
|
|
1019
|
+
if (deferred) {
|
|
1020
|
+
const discovery = discoverSnapshotData(ctx.sessionManager.getBranch());
|
|
1021
|
+
const retained = selectRetainedCheckpoint(discovery.candidates, config.inactiveMode);
|
|
1022
|
+
branchStartsWithoutRuntime = retained.kind === "pre-runtime" || (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
1023
|
+
}
|
|
1024
|
+
// Off activation owns one cancellable current-head publication, not an independent read-only restore.
|
|
905
1025
|
const result = await activation.withStartTransaction((current, publish) => {
|
|
906
1026
|
signal.throwIfAborted();
|
|
907
1027
|
if (!isCurrent())
|
|
908
|
-
throw new Error("State Flow
|
|
1028
|
+
throw new Error("State Flow activation selection changed while awaiting publication");
|
|
909
1029
|
if (!current && !branchStartsWithoutRuntime)
|
|
910
1030
|
throw new Error(snapshot.meta.validation?.error ?? "Current State Flow session memory is unavailable");
|
|
911
|
-
const
|
|
912
|
-
|
|
1031
|
+
const boundary = deferred && retainsPhysicalSessionProjection(deferred.reason)
|
|
1032
|
+
? findPassiveStopBoundary(ctx.sessionManager.getBranch(), owner, PASSIVE_STOP_ENTRY_TYPE) : undefined;
|
|
1033
|
+
const bootstrap = hasPriorConversation(ctx.sessionManager.getBranch()) || passiveContinuation !== undefined || boundary !== undefined;
|
|
913
1034
|
const activated = current ? resumeEpisode(current, bootstrap) : startEpisode(bootstrap);
|
|
914
1035
|
const recoveredCurrent = selectedHistoryExpired;
|
|
915
1036
|
const publication = publish(activated);
|
|
916
1037
|
accepted = true;
|
|
917
|
-
|
|
1038
|
+
cancelInactivePersistence();
|
|
918
1039
|
runtime = selected = activation;
|
|
919
1040
|
snapshot = activated;
|
|
920
1041
|
activeContext = ctx;
|
|
921
1042
|
selectedHistoryExpired = false;
|
|
922
|
-
|
|
1043
|
+
modePersistenceError = undefined;
|
|
923
1044
|
installScopeStates();
|
|
1045
|
+
const continuation = passiveContinuation ?? bootstrapContinuation ?? (deferred ? selectedContinuation(ctx, deferred.reason) : undefined);
|
|
1046
|
+
deferredBranch = undefined;
|
|
924
1047
|
clearRunTransient();
|
|
925
1048
|
contextProjection.reset();
|
|
926
1049
|
passiveContinuation = undefined;
|
|
@@ -930,129 +1053,270 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
930
1053
|
recordPolicyPublication(publication, ctx);
|
|
931
1054
|
syncStateFlowTools();
|
|
932
1055
|
updateUi(ctx);
|
|
1056
|
+
if (forkInitialization) {
|
|
1057
|
+
forkInitialization = false;
|
|
1058
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { reset: true, owner });
|
|
1059
|
+
}
|
|
933
1060
|
appendCheckpoint();
|
|
934
1061
|
ctx.ui.notify(recoveredCurrent
|
|
935
|
-
? "State Flow
|
|
1062
|
+
? "State Flow active from current session memory; unavailable historical state was not restored."
|
|
936
1063
|
: snapshot.meta.bootstrap
|
|
937
|
-
? "State Flow
|
|
938
|
-
: "State Flow
|
|
939
|
-
return { ok: true, message: "State Flow
|
|
1064
|
+
? "State Flow active. The next complete agent run will migrate active context into state."
|
|
1065
|
+
: "State Flow active. The next prompt starts a stateful agent run.", "info");
|
|
1066
|
+
return { ok: true, message: "State Flow active", signal: receipt };
|
|
940
1067
|
}, signal, branchStartsWithoutRuntime);
|
|
941
1068
|
return isCurrent() ? result : superseded();
|
|
942
1069
|
}
|
|
943
1070
|
catch (error) {
|
|
944
1071
|
if (!isCurrent())
|
|
945
1072
|
return superseded();
|
|
946
|
-
const message = conciseDiagnostic(`${accepted ? "State Flow
|
|
1073
|
+
const message = conciseDiagnostic(`${accepted ? "State Flow active; lifecycle update failed" : "State Flow activation failed"}: ${diagnosticText(error)}`);
|
|
947
1074
|
notifyProblem(ctx, message, accepted ? "warning" : "error");
|
|
948
1075
|
return { ok: accepted, message, signal: receipt };
|
|
949
1076
|
}
|
|
950
1077
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1078
|
+
const MODE_COMMAND_DESCRIPTIONS = {
|
|
1079
|
+
active: "Make State Flow active on the current session branch",
|
|
1080
|
+
passive: "Use passive State Flow memory on the current session branch",
|
|
1081
|
+
off: "Turn State Flow tools and context off on the current session branch",
|
|
1082
|
+
};
|
|
1083
|
+
/** Terminal commands and Telegram controls share these lifecycle owners. */
|
|
1084
|
+
function selectMode(ctx, mode) {
|
|
1085
|
+
return mode === "active" ? startStateFlow(ctx) : deactivateStateFlow(ctx, mode);
|
|
1086
|
+
}
|
|
1087
|
+
for (const mode of ["active", "passive", "off"]) {
|
|
1088
|
+
pi.registerCommand(`state-flow-${mode}`, {
|
|
1089
|
+
description: MODE_COMMAND_DESCRIPTIONS[mode],
|
|
1090
|
+
handler: async (_args, ctx) => {
|
|
1091
|
+
await selectMode(ctx, mode);
|
|
1092
|
+
},
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
957
1095
|
pi.registerCommand("state-flow-status", {
|
|
958
1096
|
description: "Show State Flow runtime status",
|
|
959
1097
|
handler: async (_args, ctx) => {
|
|
960
1098
|
ctx.ui.notify(detailedStatus(snapshot, statusDiagnostics(ctx)), "info");
|
|
961
1099
|
},
|
|
962
1100
|
});
|
|
963
|
-
|
|
964
|
-
|
|
1101
|
+
/** Local policy, tools and UI change before any canonical wait. */
|
|
1102
|
+
function applyInactiveMode(ctx, mode) {
|
|
1103
|
+
snapshot = deactivateEpisode(snapshot, mode);
|
|
1104
|
+
clearRunTransient();
|
|
1105
|
+
syncStateFlowTools();
|
|
1106
|
+
updateUi(ctx);
|
|
1107
|
+
}
|
|
1108
|
+
function deactivateStateFlow(ctx, mode) {
|
|
965
1109
|
if (shuttingDown)
|
|
966
1110
|
return Promise.resolve({ ok: false, message: "State Flow is shutting down" });
|
|
967
|
-
if (
|
|
968
|
-
return
|
|
1111
|
+
if (mode === "off")
|
|
1112
|
+
return selectOff(ctx);
|
|
1113
|
+
cancelStartActivation();
|
|
1114
|
+
telegramStartPending = false;
|
|
1115
|
+
const persisting = inactivePersistence;
|
|
1116
|
+
if (persisting?.operation && !persisting.published) {
|
|
1117
|
+
// Pending inactive choices coalesce: acceptance persists whichever inactive mode is current then.
|
|
1118
|
+
if (snapshot.config.mode !== mode)
|
|
1119
|
+
applyInactiveMode(ctx, mode);
|
|
1120
|
+
return persisting.operation;
|
|
1121
|
+
}
|
|
969
1122
|
if (!activeContext)
|
|
970
1123
|
void restoreActiveBranch(ctx, undefined, false);
|
|
971
1124
|
const restoring = branchRestoration;
|
|
1125
|
+
// Read-only recovery also retains the latest policy; its native write fence is updated below.
|
|
1126
|
+
if (restoring)
|
|
1127
|
+
restoring.requestedMode = mode;
|
|
972
1128
|
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" });
|
|
1129
|
+
// Passive keeps independently owned restoration; Off withdraws it through selectOff.
|
|
1130
|
+
applyInactiveMode(ctx, mode);
|
|
1131
|
+
return Promise.resolve({ ok: true, message: `State Flow ${mode}; memory restoration continues` });
|
|
980
1132
|
}
|
|
1133
|
+
if (snapshot.config.mode === mode) {
|
|
1134
|
+
const retained = selectRetainedCheckpoint(discoverSnapshotData(ctx.sessionManager.getBranch()).candidates, config.inactiveMode);
|
|
1135
|
+
const recordedMode = retained.kind === "pre-runtime" ? retained.mode : retained.kind === "boundary" ? retained.checkpoint.mode : undefined;
|
|
1136
|
+
if (modePersistenceError || recordedMode === mode)
|
|
1137
|
+
return Promise.resolve({ ok: true, message: `State Flow is already ${mode}` });
|
|
1138
|
+
}
|
|
1139
|
+
cancelInactivePersistence();
|
|
981
1140
|
const pending = { controller: new AbortController() };
|
|
982
|
-
|
|
983
|
-
const operation =
|
|
1141
|
+
inactivePersistence = pending;
|
|
1142
|
+
const operation = deferredBranch && mode === "passive" && !branchStartsWithoutRuntime
|
|
1143
|
+
? acquirePassiveMode(ctx, pending, deferredBranch.reason)
|
|
1144
|
+
: persistInactiveMode(ctx, pending, mode);
|
|
984
1145
|
pending.operation = operation;
|
|
985
|
-
const finished = () => { if (
|
|
986
|
-
|
|
1146
|
+
const finished = () => { if (inactivePersistence === pending)
|
|
1147
|
+
inactivePersistence = undefined; };
|
|
987
1148
|
void operation.then(finished, finished);
|
|
988
1149
|
return operation;
|
|
989
1150
|
}
|
|
990
|
-
|
|
991
|
-
|
|
1151
|
+
/** Off owns cancellation and native policy only; no canonical transaction or semantic handoff. */
|
|
1152
|
+
function selectOff(ctx) {
|
|
1153
|
+
const address = sessionAddress(ctx);
|
|
1154
|
+
const owner = ctx.sessionManager.getSessionId();
|
|
1155
|
+
const branch = ctx.sessionManager.getBranch();
|
|
992
1156
|
const selected = runtime;
|
|
1157
|
+
const sameOwner = selected?.sessionKey === address.key && selected.cwd === ctx.cwd;
|
|
1158
|
+
const forkRecorded = hasPendingFork(branch, owner, PASSIVE_STOP_ENTRY_TYPE);
|
|
1159
|
+
const pendingFork = forkRecorded || (sameOwner && forkInitialization) || (deferredBranch?.owner === address.key && deferredBranch.cwd === ctx.cwd && deferredBranch.reason === "fork");
|
|
1160
|
+
const policy = findBranchPolicy(branch, owner, PASSIVE_STOP_ENTRY_TYPE, config.inactiveMode);
|
|
1161
|
+
const sameDeferred = deferredBranch?.owner === address.key && deferredBranch.cwd === ctx.cwd;
|
|
1162
|
+
const current = sameOwner || sameDeferred ? snapshot : emptySnapshot("off");
|
|
1163
|
+
if (!sameOwner && !sameDeferred)
|
|
1164
|
+
modePersistenceError = policy?.persistenceError;
|
|
1165
|
+
const alreadyOff = current.config.mode === "off" && policy?.mode === "off";
|
|
1166
|
+
const incoming = sameOwner || sameDeferred ? current.meta.bootstrap ? bootstrapContinuation : passiveContinuation : undefined;
|
|
1167
|
+
const unfinished = current.meta.specification !== undefined || (inferencePreparation?.prompt !== undefined && !inferencePreparation.accepted)
|
|
1168
|
+
|| (isActive() && hasUncheckpointedConversation(branch));
|
|
1169
|
+
const at = incoming?.startedAt ?? Date.now();
|
|
1170
|
+
const from = incoming ? incoming.activeRunStartedAt : (sameOwner || sameDeferred) && (!ctx.isIdle() || unfinished) ? runAnchorTimestamp : undefined;
|
|
1171
|
+
const preserveContext = modePersistenceError !== undefined || (incoming ? incoming.preserveContext : current.meta.bootstrap === true || (unfinished && from === undefined));
|
|
1172
|
+
const discovery = discoverSnapshotData(branch);
|
|
1173
|
+
const preRuntime = (sameOwner && branchStartsWithoutRuntime) || (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
1174
|
+
let checkpoint;
|
|
1175
|
+
try {
|
|
1176
|
+
checkpoint = !modePersistenceError && current.meta.validation?.attempt !== 0
|
|
1177
|
+
? preRuntime ? preRuntimeCheckpoint("off") : sameOwner && selected?.view ? selected.retainedCheckpoint(deactivateEpisode(current, "off")) : undefined
|
|
1178
|
+
: undefined;
|
|
1179
|
+
}
|
|
1180
|
+
catch {
|
|
1181
|
+
// An unusable cached bookmark cannot prevent native Off policy or authorize replacement memory.
|
|
1182
|
+
}
|
|
1183
|
+
cancelStartActivation();
|
|
1184
|
+
cancelBranchRestoration();
|
|
1185
|
+
cancelInactivePersistence();
|
|
1186
|
+
cancelBackupWork();
|
|
1187
|
+
memoryToolLifetime.abort();
|
|
1188
|
+
memoryToolLifetime = new AbortController();
|
|
1189
|
+
clearRunTransient();
|
|
1190
|
+
contextProjection.reset();
|
|
1191
|
+
telegramStartPending = false;
|
|
1192
|
+
bootstrapContinuation = undefined;
|
|
1193
|
+
passiveContinuation = undefined;
|
|
1194
|
+
artifactInvalidations = [];
|
|
1195
|
+
artifactHints = {};
|
|
1196
|
+
artifactReads.setCandidates([]);
|
|
1197
|
+
runtime = undefined;
|
|
1198
|
+
snapshot = emptySnapshot("off");
|
|
1199
|
+
selectedHistoryExpired = false;
|
|
1200
|
+
branchStartsWithoutRuntime = preRuntime;
|
|
1201
|
+
forkInitialization = pendingFork;
|
|
1202
|
+
deferredBranch = { owner: address.key, cwd: ctx.cwd, reason: pendingFork ? "fork" : undefined };
|
|
1203
|
+
activeContext = ctx;
|
|
1204
|
+
installScopeStates();
|
|
1205
|
+
syncStateFlowTools();
|
|
1206
|
+
updateUi(ctx);
|
|
1207
|
+
try {
|
|
1208
|
+
if (!alreadyOff || (pendingFork && !forkRecorded)) {
|
|
1209
|
+
if (!checkpoint || !preRuntime || pendingFork || preserveContext || from !== undefined || modePersistenceError)
|
|
1210
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, {
|
|
1211
|
+
owner, at, mode: "off", memoryDeferred: true,
|
|
1212
|
+
...(from === undefined ? {} : { from }),
|
|
1213
|
+
...(preserveContext ? { preserveContext: true } : {}),
|
|
1214
|
+
...(pendingFork ? { forkPending: true } : {}),
|
|
1215
|
+
...(modePersistenceError === undefined ? {} : { persistenceError: modePersistenceError }),
|
|
1216
|
+
});
|
|
1217
|
+
if (checkpoint)
|
|
1218
|
+
pi.appendEntry(SNAPSHOT_ENTRY_TYPE, checkpoint);
|
|
1219
|
+
}
|
|
1220
|
+
return Promise.resolve({ ok: true, message: alreadyOff ? "State Flow is already off" : "State Flow off" });
|
|
1221
|
+
}
|
|
1222
|
+
catch (error) {
|
|
1223
|
+
const message = conciseDiagnostic(`State Flow off; native mode recording failed: ${diagnosticText(error)}`);
|
|
1224
|
+
notifyProblem(ctx, message, "error");
|
|
1225
|
+
return Promise.resolve({ ok: false, message });
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
async function acquirePassiveMode(ctx, pending, reason) {
|
|
1229
|
+
await restoreActiveBranch(ctx, reason, true, pending.controller, "passive");
|
|
1230
|
+
if (inactivePersistence !== pending || pending.controller.signal.aborted || shuttingDown) {
|
|
1231
|
+
return { ok: false, message: "State Flow mode change was superseded" };
|
|
1232
|
+
}
|
|
1233
|
+
return persistInactiveMode(ctx, pending, "passive");
|
|
1234
|
+
}
|
|
1235
|
+
async function persistInactiveMode(ctx, pending, mode) {
|
|
1236
|
+
let selected = runtime;
|
|
993
1237
|
const owner = ctx.sessionManager.getSessionId();
|
|
994
1238
|
const current = snapshot;
|
|
1239
|
+
const wasActive = current.config.mode === "active";
|
|
995
1240
|
let unfinished = current.meta.specification !== undefined
|
|
996
1241
|
|| (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" };
|
|
1242
|
+
applyInactiveMode(ctx, mode);
|
|
1003
1243
|
const stoppedAt = Date.now();
|
|
1244
|
+
if (modePersistenceError) {
|
|
1245
|
+
// Writes stay paused; the native marker records only the newly selected inactive policy.
|
|
1246
|
+
pending.published = true;
|
|
1247
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, {
|
|
1248
|
+
at: passiveContinuation?.startedAt ?? stoppedAt,
|
|
1249
|
+
...(passiveContinuation?.activeRunStartedAt === undefined ? {} : { from: passiveContinuation.activeRunStartedAt }),
|
|
1250
|
+
preserveContext: true, owner, persistenceError: modePersistenceError, mode,
|
|
1251
|
+
});
|
|
1252
|
+
return { ok: true, message: `State Flow ${mode}; memory writes remain paused` };
|
|
1253
|
+
}
|
|
1004
1254
|
const incomingBoundary = current.meta.bootstrap ? bootstrapContinuation : undefined;
|
|
1005
1255
|
const anchor = runAnchorTimestamp;
|
|
1006
1256
|
const idle = ctx.isIdle();
|
|
1007
|
-
const isCurrent = () =>
|
|
1008
|
-
&& !shuttingDown && ctx.sessionManager.getSessionId() === owner && !
|
|
1009
|
-
const superseded = () => ({ ok: false, message: "State Flow
|
|
1257
|
+
const isCurrent = () => inactivePersistence === pending && runtime === selected
|
|
1258
|
+
&& !shuttingDown && ctx.sessionManager.getSessionId() === owner && !isActive();
|
|
1259
|
+
const superseded = () => ({ ok: false, message: "State Flow mode change was superseded" });
|
|
1010
1260
|
const freezeHandoff = () => {
|
|
1011
1261
|
contextProjection.reset();
|
|
1012
|
-
const handoff = (
|
|
1013
|
-
? createPassiveContinuation(projectModelState(
|
|
1262
|
+
const handoff = (wasActive || modePersistenceError) && selected?.view && current.meta.validation?.attempt !== 0
|
|
1263
|
+
? 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
1264
|
: undefined;
|
|
1015
|
-
|
|
1265
|
+
// Repeated Passive choices keep the already selected continuation boundary.
|
|
1266
|
+
passiveContinuation = handoff ?? (!wasActive ? passiveContinuation : undefined);
|
|
1016
1267
|
return handoff;
|
|
1017
1268
|
};
|
|
1018
1269
|
const complete = () => {
|
|
1270
|
+
pending.published = true;
|
|
1019
1271
|
const exitHandoff = freezeHandoff();
|
|
1020
|
-
|
|
1272
|
+
const selectedMode = snapshot.config.mode;
|
|
1273
|
+
if (exitHandoff || modePersistenceError)
|
|
1021
1274
|
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, {
|
|
1022
1275
|
at: exitHandoff?.startedAt ?? stoppedAt,
|
|
1023
1276
|
...(exitHandoff?.activeRunStartedAt === undefined ? {} : { from: exitHandoff.activeRunStartedAt }),
|
|
1024
|
-
...(exitHandoff?.preserveContext ||
|
|
1025
|
-
...(
|
|
1277
|
+
...(exitHandoff?.preserveContext || modePersistenceError ? { preserveContext: true } : {}),
|
|
1278
|
+
...(modePersistenceError === undefined ? {} : { owner, persistenceError: modePersistenceError, mode: selectedMode }),
|
|
1026
1279
|
});
|
|
1027
|
-
if (!
|
|
1280
|
+
if (!modePersistenceError) {
|
|
1028
1281
|
appendCheckpoint();
|
|
1029
|
-
return { ok: true, message:
|
|
1282
|
+
return { ok: true, message: `State Flow ${selectedMode}` };
|
|
1030
1283
|
}
|
|
1031
|
-
const message = conciseDiagnostic(`State Flow
|
|
1284
|
+
const message = conciseDiagnostic(`State Flow ${selectedMode}; memory writes paused: ${modePersistenceError}`);
|
|
1032
1285
|
notifyProblem(ctx, message, "warning");
|
|
1033
1286
|
return { ok: true, message };
|
|
1034
1287
|
};
|
|
1035
1288
|
let accepted = false;
|
|
1036
1289
|
try {
|
|
1037
|
-
unfinished ||=
|
|
1290
|
+
unfinished ||= wasActive && hasUncheckpointedConversation(ctx.sessionManager.getBranch());
|
|
1038
1291
|
// Projection changes before any wait; capture the old run's boundary before later native input can replace it.
|
|
1039
1292
|
freezeHandoff();
|
|
1040
1293
|
bootstrapContinuation = undefined;
|
|
1041
1294
|
artifactInvalidations = [];
|
|
1042
1295
|
artifactReads.setCandidates([]);
|
|
1043
1296
|
assertPublicationAvailable();
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1297
|
+
const signal = ctx.signal ? AbortSignal.any([pending.controller.signal, ctx.signal]) : pending.controller.signal;
|
|
1298
|
+
if (branchStartsWithoutRuntime || !selected?.view) {
|
|
1299
|
+
// A pre-runtime choice is native-only; Passive then loads shared memory without initializing storage.
|
|
1300
|
+
const result = complete();
|
|
1301
|
+
if (snapshot.config.mode !== "passive" || runtime?.view)
|
|
1302
|
+
return result;
|
|
1303
|
+
const passive = await loadPassiveView(ctx, signal, isCurrent, true);
|
|
1304
|
+
if (passive && isCurrent() && !signal.aborted && !runtime?.view) {
|
|
1305
|
+
runtime = selected = passive;
|
|
1306
|
+
installScopeStates();
|
|
1307
|
+
updateUi(ctx);
|
|
1308
|
+
}
|
|
1309
|
+
return isCurrent() ? result : superseded();
|
|
1310
|
+
}
|
|
1047
1311
|
const result = await selected.withLifecycleTransaction((publish) => {
|
|
1048
1312
|
signal.throwIfAborted();
|
|
1049
1313
|
if (!isCurrent())
|
|
1050
|
-
throw new Error("State Flow
|
|
1314
|
+
throw new Error("State Flow mode selection changed while awaiting publication");
|
|
1051
1315
|
assertPublicationAvailable();
|
|
1052
|
-
const
|
|
1053
|
-
const publication = publish(
|
|
1316
|
+
const next = structuredClone(snapshot);
|
|
1317
|
+
const publication = publish(next);
|
|
1054
1318
|
accepted = true;
|
|
1055
|
-
snapshot =
|
|
1319
|
+
snapshot = next;
|
|
1056
1320
|
installScopeStates();
|
|
1057
1321
|
recordPolicyPublication(publication, ctx);
|
|
1058
1322
|
return complete();
|
|
@@ -1064,28 +1328,22 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1064
1328
|
return superseded();
|
|
1065
1329
|
const cause = diagnosticText(error);
|
|
1066
1330
|
if (accepted) {
|
|
1067
|
-
const message = conciseDiagnostic(`State Flow
|
|
1331
|
+
const message = conciseDiagnostic(`State Flow ${snapshot.config.mode}; lifecycle update failed: ${cause}`);
|
|
1068
1332
|
notifyProblem(ctx, message, "warning");
|
|
1069
1333
|
return { ok: true, message };
|
|
1070
1334
|
}
|
|
1071
|
-
|
|
1335
|
+
modePersistenceError = cause.trim() ? cause : "Canonical State Flow persistence failed";
|
|
1072
1336
|
bootstrapContinuation = undefined;
|
|
1073
1337
|
artifactInvalidations = [];
|
|
1074
1338
|
artifactReads.setCandidates([]);
|
|
1075
1339
|
return complete();
|
|
1076
1340
|
}
|
|
1077
1341
|
}
|
|
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
1342
|
const telegram = createStateFlowTelegramAdapter({
|
|
1085
1343
|
...(options.telegram?.load === undefined ? {} : { load: options.telegram.load }),
|
|
1086
1344
|
port: {
|
|
1087
1345
|
snapshot: () => ({
|
|
1088
|
-
|
|
1346
|
+
mode: snapshot.config.mode,
|
|
1089
1347
|
step: snapshot.meta.step,
|
|
1090
1348
|
revisions: scopeRevisions(),
|
|
1091
1349
|
bootstrap: snapshot.meta.bootstrap === true,
|
|
@@ -1093,15 +1351,13 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1093
1351
|
}),
|
|
1094
1352
|
inspect: inspectTelegramState,
|
|
1095
1353
|
canStartNow: () => activeContext === undefined || activeContext.isIdle(),
|
|
1096
|
-
|
|
1354
|
+
select: async (mode) => {
|
|
1097
1355
|
if (!activeContext)
|
|
1098
1356
|
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);
|
|
1357
|
+
const operation = selectMode(activeContext, mode);
|
|
1358
|
+
if (mode === "active")
|
|
1359
|
+
return operation;
|
|
1360
|
+
// Inactive choices revoke older presentation at once; the receipt belongs to the new lifetime.
|
|
1105
1361
|
const signal = sharedInspectionLifetime.signal;
|
|
1106
1362
|
try {
|
|
1107
1363
|
return { ...await operation, signal };
|
|
@@ -1123,8 +1379,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1123
1379
|
pi.on("before_agent_start", (event) => {
|
|
1124
1380
|
// Native run identity is independent of semantic enablement and survives mode toggles.
|
|
1125
1381
|
runAnchorTimestamp = undefined;
|
|
1126
|
-
if (!
|
|
1127
|
-
if (!
|
|
1382
|
+
if (!isActive()) {
|
|
1383
|
+
if (!passiveMemoryAvailable())
|
|
1128
1384
|
return;
|
|
1129
1385
|
(event.systemPromptOptions.sections ??= {}).state_flow = PASSIVE_MEMORY_PROTOCOL;
|
|
1130
1386
|
return;
|
|
@@ -1139,21 +1395,25 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1139
1395
|
(event.systemPromptOptions.sections ??= {}).state_flow = stateFlowProtocol(snapshot.meta.bootstrap === true);
|
|
1140
1396
|
});
|
|
1141
1397
|
pi.on("context_with_system", (event) => {
|
|
1142
|
-
|
|
1143
|
-
|
|
1398
|
+
// Off removes the owned section, including one contributed before an in-flight mode change.
|
|
1399
|
+
const protocol = isActive() ? stateFlowProtocol(snapshot.meta.bootstrap === true)
|
|
1400
|
+
: passiveMemoryAvailable() ? PASSIVE_MEMORY_PROTOCOL : undefined;
|
|
1144
1401
|
return { messages: projectSystemProtocol(event.messages, protocol) };
|
|
1145
1402
|
});
|
|
1146
1403
|
function projectContext(messages) {
|
|
1404
|
+
// Off injects no State Flow context, including a retained passive handoff.
|
|
1405
|
+
if (snapshot.config.mode === "off")
|
|
1406
|
+
return;
|
|
1147
1407
|
if (runtime?.view)
|
|
1148
1408
|
refreshArtifactHints();
|
|
1149
|
-
if (!
|
|
1409
|
+
if (!isActive() && !passiveContinuation && !passiveMemoryAvailable())
|
|
1150
1410
|
return;
|
|
1151
1411
|
// Idle inspection must not freeze a pre-acceptance snapshot for the live inference.
|
|
1152
|
-
const projection =
|
|
1412
|
+
const projection = isActive() && inferencePreparation && !inferencePreparation.accepted
|
|
1153
1413
|
? new ContextProjection() : contextProjection;
|
|
1154
|
-
const effective =
|
|
1414
|
+
const effective = effectiveState;
|
|
1155
1415
|
const invalidations = artifactInvalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
|
|
1156
|
-
const phase =
|
|
1416
|
+
const phase = isActive() ? currentRehydrationPhase() : undefined;
|
|
1157
1417
|
const view = contextView(effective, artifactHints, invalidations, phase);
|
|
1158
1418
|
if (passiveContinuation) {
|
|
1159
1419
|
const retained = passiveContinuationMessages(messages, passiveContinuation);
|
|
@@ -1161,7 +1421,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1161
1421
|
return { messages: retained };
|
|
1162
1422
|
return { messages: projection.project(retained.slice(1), view, () => passiveContinuation.handoff, { state: passiveContinuation.state, lazy_navigation: view.lazy_navigation, artifact_invalidations: [], knowledge_rehydration: null }) };
|
|
1163
1423
|
}
|
|
1164
|
-
if (!
|
|
1424
|
+
if (!isActive()) {
|
|
1165
1425
|
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
1426
|
}
|
|
1167
1427
|
// Native user events own the run anchor; projection must never rebase it.
|
|
@@ -1175,7 +1435,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1175
1435
|
const selected = runtime;
|
|
1176
1436
|
const operationSignal = ctx.signal;
|
|
1177
1437
|
// Idle projections remain observational; only a live native operation may await preparation.
|
|
1178
|
-
if (!
|
|
1438
|
+
if (!isActive() || !pending || !operationSignal || shuttingDown)
|
|
1179
1439
|
return projectContext(messages);
|
|
1180
1440
|
pending.operation ??= prepareInference(pending, selected, ctx, operationSignal).finally(() => {
|
|
1181
1441
|
// A late withdrawal must not clear newer work; accepted lifecycle is never replayed after an error.
|
|
@@ -1185,20 +1445,20 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1185
1445
|
return pending.operation.then(() => {
|
|
1186
1446
|
if (shuttingDown || operationSignal.aborted || runtime !== selected || ctx.signal !== operationSignal)
|
|
1187
1447
|
return;
|
|
1188
|
-
if (
|
|
1448
|
+
if (isActive() && inferencePreparation !== pending) {
|
|
1189
1449
|
// Start may require same-run maintenance. A new captured user prompt instead owns a different context request.
|
|
1190
1450
|
if (inferencePreparation?.prompt === undefined)
|
|
1191
1451
|
return prepareContext(messages, ctx);
|
|
1192
1452
|
return;
|
|
1193
1453
|
}
|
|
1194
|
-
if (
|
|
1454
|
+
if (isActive() && !pending.accepted)
|
|
1195
1455
|
return;
|
|
1196
1456
|
return projectContext(messages);
|
|
1197
1457
|
});
|
|
1198
1458
|
}
|
|
1199
1459
|
pi.on("context", (event, ctx) => prepareContext(event.messages, ctx));
|
|
1200
1460
|
pi.on("tool_execution_start", (event) => {
|
|
1201
|
-
if (!
|
|
1461
|
+
if (!isActive())
|
|
1202
1462
|
return;
|
|
1203
1463
|
// Pi emits this before tool_call. Keep the argument object as a fallback;
|
|
1204
1464
|
// tool_call replaces it with the mutable, post-preflight input reference.
|
|
@@ -1206,36 +1466,34 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1206
1466
|
artifactReads.recordStart(event.toolCallId, event.toolName, event.args);
|
|
1207
1467
|
});
|
|
1208
1468
|
pi.on("tool_call", (event, ctx) => {
|
|
1209
|
-
if (!
|
|
1469
|
+
if (!isActive())
|
|
1210
1470
|
return;
|
|
1211
1471
|
const batch = findAssistantToolBatch(ctx.sessionManager, event.toolCallId);
|
|
1212
1472
|
const patchCalls = batch?.filter((name) => name === PATCH_STATE_TOOL_NAME).length ?? 0;
|
|
1213
1473
|
if (patchCalls > 0) {
|
|
1214
1474
|
if (patchCalls !== 1) {
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
};
|
|
1475
|
+
const reason = "A State Flow barrier response must contain exactly one patch_state call";
|
|
1476
|
+
recordDiagnostic(reason, "barrier-block", ctx, { tool: event.toolName, toolCallId: event.toolCallId, batchToolNames: batch });
|
|
1477
|
+
return { block: true, reason };
|
|
1219
1478
|
}
|
|
1220
1479
|
if (event.toolName !== PATCH_STATE_TOOL_NAME) {
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
};
|
|
1480
|
+
const reason = "Blocked by the patch_state barrier; reconsider this action after State Flow rematerializes context";
|
|
1481
|
+
recordDiagnostic(reason, "barrier-block", ctx, { tool: event.toolName, toolCallId: event.toolCallId, batchToolNames: batch });
|
|
1482
|
+
return { block: true, reason };
|
|
1225
1483
|
}
|
|
1226
1484
|
}
|
|
1227
1485
|
skillReads.recordCall(event.toolCallId, event.toolName, event.input);
|
|
1228
1486
|
artifactReads.recordCall(event.toolCallId, event.toolName, event.input);
|
|
1229
1487
|
});
|
|
1230
1488
|
pi.on("tool_execution_end", (event) => {
|
|
1231
|
-
if (!
|
|
1489
|
+
if (!isActive())
|
|
1232
1490
|
return;
|
|
1233
1491
|
skillReads.recordEnd(event.toolCallId, event.toolName, event.isError);
|
|
1234
1492
|
dropCurrentSkillReads();
|
|
1235
1493
|
artifactReads.recordEnd(event.toolCallId, event.toolName, event.isError);
|
|
1236
1494
|
});
|
|
1237
1495
|
pi.on("tool_result", (event) => {
|
|
1238
|
-
if (!
|
|
1496
|
+
if (!isActive())
|
|
1239
1497
|
return;
|
|
1240
1498
|
const read = skillReads.recordResult(event.toolName, event.input, event.isError);
|
|
1241
1499
|
if (!read)
|
|
@@ -1250,7 +1508,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1250
1508
|
// Observe actual user events even while disabled; Start/Stop cannot invent or erase them.
|
|
1251
1509
|
if (event.message.role === "user" && runAnchorTimestamp === undefined)
|
|
1252
1510
|
runAnchorTimestamp = event.message.timestamp;
|
|
1253
|
-
if (shuttingDown || !
|
|
1511
|
+
if (shuttingDown || !isActive())
|
|
1254
1512
|
return;
|
|
1255
1513
|
if (event.message.role !== "assistant")
|
|
1256
1514
|
return;
|
|
@@ -1268,7 +1526,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1268
1526
|
if (shuttingDown)
|
|
1269
1527
|
return;
|
|
1270
1528
|
const pending = responseReconciliation;
|
|
1271
|
-
if (!
|
|
1529
|
+
if (!isActive() || !pending) {
|
|
1272
1530
|
updateUi(ctx);
|
|
1273
1531
|
return;
|
|
1274
1532
|
}
|
|
@@ -1281,7 +1539,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1281
1539
|
throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
1282
1540
|
await selected.withPatchTransaction((transaction) => {
|
|
1283
1541
|
signal.throwIfAborted();
|
|
1284
|
-
if (runtime !== selected || responseReconciliation !== pending || !
|
|
1542
|
+
if (runtime !== selected || responseReconciliation !== pending || !isActive()) {
|
|
1285
1543
|
throw new Error("State Flow response selection changed while awaiting publication");
|
|
1286
1544
|
}
|
|
1287
1545
|
assertPublicationAvailable();
|
|
@@ -1324,24 +1582,26 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1324
1582
|
}
|
|
1325
1583
|
});
|
|
1326
1584
|
pi.on("session_before_compact", (event) => {
|
|
1327
|
-
if (!
|
|
1585
|
+
if (event.reason !== "manual" || !event.customInstructions?.startsWith("state-flow-boundary:"))
|
|
1328
1586
|
return;
|
|
1329
|
-
|
|
1587
|
+
const marker = compactionMarker;
|
|
1588
|
+
if (shuttingDown || compactionStopped || !isActive() || !compactionPlan || !marker || event.customInstructions !== marker)
|
|
1330
1589
|
return { cancel: true };
|
|
1331
|
-
const result = stateFlowCompactionResult(compactionPlan,
|
|
1590
|
+
const result = stateFlowCompactionResult(compactionPlan, marker, event);
|
|
1332
1591
|
if (result === undefined || "cancel" in result)
|
|
1333
1592
|
return result;
|
|
1334
1593
|
return { compaction: result };
|
|
1335
1594
|
});
|
|
1336
1595
|
pi.on("agent_before_settle", async (_event, ctx) => {
|
|
1337
|
-
if (shuttingDown || !turnAcceptedForBackup)
|
|
1596
|
+
if (shuttingDown || !memoryToolsAvailable() || !turnAcceptedForBackup)
|
|
1338
1597
|
return;
|
|
1339
1598
|
turnAcceptedForBackup = false;
|
|
1340
1599
|
if (!backupPending)
|
|
1341
1600
|
return;
|
|
1342
1601
|
backupPending = false;
|
|
1343
1602
|
const operationSignal = ctx.signal;
|
|
1344
|
-
const
|
|
1603
|
+
const lifetime = backupLifetime.signal;
|
|
1604
|
+
const signal = operationSignal ? AbortSignal.any([lifetime, operationSignal]) : lifetime;
|
|
1345
1605
|
const selected = runtime;
|
|
1346
1606
|
try {
|
|
1347
1607
|
if (existsSync(join(repositoryRoot, ".git"))) {
|
|
@@ -1356,10 +1616,10 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1356
1616
|
finally {
|
|
1357
1617
|
backupOperations.delete(operation);
|
|
1358
1618
|
}
|
|
1359
|
-
if (signal.aborted)
|
|
1619
|
+
if (signal.aborted || snapshot.config.mode === "off")
|
|
1360
1620
|
return;
|
|
1361
1621
|
startStateFlowBackupPush(repositoryRoot, (error) => {
|
|
1362
|
-
if (shuttingDown)
|
|
1622
|
+
if (shuttingDown || lifetime.aborted || snapshot.config.mode === "off")
|
|
1363
1623
|
return;
|
|
1364
1624
|
const message = diagnosticText(error);
|
|
1365
1625
|
const recorded = diagnosticWriter.recordBackupPushFailure(pushSessionId, pushCwd, message);
|
|
@@ -1369,7 +1629,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1369
1629
|
notifyActiveContext(recorded
|
|
1370
1630
|
? `State Flow Git backup push failed; state is saved locally. Details: ${stateFlowLogPath(agentDir)}. A later accepted turn retries.`
|
|
1371
1631
|
: `State Flow Git backup push failed; local diagnostics unavailable: ${message}`);
|
|
1372
|
-
}, () => { pushFailureNotified = false; });
|
|
1632
|
+
}, () => { pushFailureNotified = false; }, lifetime);
|
|
1373
1633
|
}
|
|
1374
1634
|
}
|
|
1375
1635
|
catch (error) {
|
|
@@ -1383,12 +1643,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1383
1643
|
}
|
|
1384
1644
|
});
|
|
1385
1645
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
1386
|
-
if (telegramStartPending && !
|
|
1646
|
+
if (telegramStartPending && !isActive()) {
|
|
1387
1647
|
telegramStartPending = false;
|
|
1388
1648
|
await startStateFlow(ctx);
|
|
1389
1649
|
return;
|
|
1390
1650
|
}
|
|
1391
|
-
if (!completedRunAccepted || compactionStopped || !
|
|
1651
|
+
if (!completedRunAccepted || compactionStopped || !isActive() || snapshot.meta.bootstrap
|
|
1392
1652
|
|| compactionInFlight || !ctx.isIdle() || ctx.hasPendingMessages() || !runtime?.view
|
|
1393
1653
|
|| !shouldRequestStateFlowCompaction(ctx.getContextUsage()))
|
|
1394
1654
|
return;
|
|
@@ -1400,11 +1660,18 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1400
1660
|
if (!plan)
|
|
1401
1661
|
return;
|
|
1402
1662
|
compactionPlan = plan;
|
|
1663
|
+
const marker = compactionMarker = `${compactionPrefix}${randomUUID()}`;
|
|
1403
1664
|
compactionInFlight = true;
|
|
1404
1665
|
// Pi dispatches deferred companion prompts after all settled handlers return.
|
|
1405
1666
|
await new Promise((resolve) => {
|
|
1406
|
-
const finished = () => {
|
|
1407
|
-
|
|
1667
|
+
const finished = () => {
|
|
1668
|
+
if (compactionMarker === marker) {
|
|
1669
|
+
compactionPlan = undefined;
|
|
1670
|
+
compactionInFlight = false;
|
|
1671
|
+
}
|
|
1672
|
+
resolve();
|
|
1673
|
+
};
|
|
1674
|
+
ctx.compact({ customInstructions: marker, onComplete: finished, onError: finished });
|
|
1408
1675
|
});
|
|
1409
1676
|
});
|
|
1410
1677
|
pi.on("session_compact", () => { contextProjection.reset(); });
|
|
@@ -1421,10 +1688,11 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1421
1688
|
});
|
|
1422
1689
|
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
1423
1690
|
shuttingDown = true;
|
|
1691
|
+
memoryToolLifetime.abort();
|
|
1424
1692
|
contextProjection.reset();
|
|
1425
1693
|
const restoring = cancelBranchRestoration();
|
|
1426
1694
|
const starting = cancelStartActivation();
|
|
1427
|
-
const stopping =
|
|
1695
|
+
const stopping = cancelInactivePersistence();
|
|
1428
1696
|
backupLifetime.abort();
|
|
1429
1697
|
cancelInferencePreparation();
|
|
1430
1698
|
cancelResponseReconciliation();
|