@llblab/pi-kit 0.22.1 → 0.23.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/CHANGELOG.md +13 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-grow-loop/AGENTS.md +10 -6
- package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +7 -0
- package/node_modules/@llblab/pi-grow-loop/README.md +2 -0
- package/node_modules/@llblab/pi-grow-loop/dist/index.d.ts +33 -0
- package/node_modules/@llblab/pi-grow-loop/dist/index.js +286 -0
- package/node_modules/@llblab/pi-grow-loop/dist/pi-grow-loop/index.d.ts +1 -0
- package/node_modules/@llblab/pi-grow-loop/dist/pi-grow-loop/index.js +1 -0
- package/node_modules/@llblab/pi-grow-loop/dist/skills/grow-loop/SKILL.md +117 -0
- package/node_modules/@llblab/pi-grow-loop/dist/skills/while-true/SKILL.md +233 -0
- package/node_modules/@llblab/pi-grow-loop/index.ts +67 -12
- package/node_modules/@llblab/pi-grow-loop/package.json +9 -8
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +23 -17
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +5 -3
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +11 -0
- package/node_modules/@llblab/pi-state-flow/README.md +18 -6
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/index.js +1 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +3 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +7 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.d.ts +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +54 -40
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +20 -20
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +755 -325
- 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 +14 -17
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +4 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +65 -23
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +16 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +32 -15
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +28 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +276 -26
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +5 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +36 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +3 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +11 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +150 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +14 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +52 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +8 -8
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +3 -1
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +53 -7
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +84 -44
- package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +12 -2
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +6 -4
- package/node_modules/@llblab/pi-state-flow/docs/performance.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +28 -8
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +41 -14
- package/node_modules/@llblab/pi-state-flow/index.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +5 -5
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +8 -3
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +57 -40
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +20 -20
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +719 -316
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +16 -18
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +60 -24
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +34 -21
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +290 -25
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +37 -2
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +4 -1
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +141 -22
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +60 -19
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +8 -8
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +3 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +3 -2
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +10 -0
- package/node_modules/@llblab/pi-telegram/README.md +3 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +22 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/skills.d.ts +8 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/skills.js +36 -4
- package/node_modules/@llblab/pi-telegram/dist/package.json +3 -8
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +27 -3
- package/node_modules/@llblab/pi-telegram/lib/skills.ts +49 -5
- package/node_modules/@llblab/pi-telegram/package.json +3 -8
- package/node_modules/@llblab/pi-telegram/scripts/build-dist.mjs +103 -32
- package/package.json +6 -6
|
@@ -16,15 +16,16 @@ import { awaitInFlightBackupPushes, backupCurrentStateFlowFiles, startStateFlowB
|
|
|
16
16
|
import { projectRecentTransitionsWithLimit } from "./history.js";
|
|
17
17
|
import { isObject, presentationJson, sameJson } from "./json.js";
|
|
18
18
|
import { StateFlowDiagnosticWriter, stateFlowLogPath } from "./logging.js";
|
|
19
|
-
import { assistantToolCallCount, finalizedAssistantResponse, formatPatchStateArguments, PASSIVE_MEMORY_PROTOCOL, separatedFailure, separatedOutput, stateFlowProtocol } from "./protocol.js";
|
|
19
|
+
import { assistantToolCallCount, conciseDiagnostic, diagnosticText, finalizedAssistantResponse, formatPatchStateArguments, PASSIVE_MEMORY_PROTOCOL, separatedFailure, separatedOutput, stateFlowProtocol } from "./protocol.js";
|
|
20
20
|
import { readProjectedState, readStatePath } from "./query.js";
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { discoverSnapshotData, findAssistantToolBatch, findPassiveStopBoundary, hasPriorConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.js";
|
|
21
|
+
import { selectedBoundaryFailure, selectRetainedCheckpoint, waitForRecovery } from "./recovery.js";
|
|
22
|
+
import { TemporalRuntime } from "./runtime.js";
|
|
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 { emptySnapshot, migrationFailure } from "./snapshot.js";
|
|
25
|
+
import { HistoryBoundaryExpiredError, emptySnapshot, migrationFailure } from "./snapshot.js";
|
|
26
26
|
import { emptyState, overlayStates, projectStateForModel } from "./state.js";
|
|
27
27
|
import { compactStatus, detailedStatus, STATUS_KEY } from "./status.js";
|
|
28
|
+
import { PublicationBusyError } from "./storage.js";
|
|
28
29
|
import { createStateFlowTelegramAdapter } from "./telegram.js";
|
|
29
30
|
import { temporalScopeRevisions } from "./temporal.js";
|
|
30
31
|
import { commitScopedTransition, stageAtomicScopePatches, stageScopedTransition } from "./transition.js";
|
|
@@ -42,10 +43,16 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
42
43
|
};
|
|
43
44
|
let snapshot = emptySnapshot();
|
|
44
45
|
let scopeStates = { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
45
|
-
let branchHasSnapshot = false;
|
|
46
46
|
let branchStartsWithoutRuntime = false;
|
|
47
|
+
let selectedHistoryExpired = false;
|
|
48
|
+
let stopPersistenceError;
|
|
49
|
+
let stopPersistence;
|
|
50
|
+
let startActivation;
|
|
47
51
|
let forkInitialization = false;
|
|
48
|
-
let
|
|
52
|
+
let branchRestoration;
|
|
53
|
+
const restorationOperations = new Set();
|
|
54
|
+
let responseReconciliation;
|
|
55
|
+
let sharedInspectionLifetime = new AbortController();
|
|
49
56
|
let completedRunAccepted = false;
|
|
50
57
|
let turnAcceptedForBackup = false;
|
|
51
58
|
let compactionPlan;
|
|
@@ -54,7 +61,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
54
61
|
const compactionMarker = `state-flow-boundary:${randomUUID()}`;
|
|
55
62
|
let passiveContinuation;
|
|
56
63
|
let bootstrapContinuation;
|
|
57
|
-
let
|
|
64
|
+
let inferencePreparation;
|
|
58
65
|
let runAnchorTimestamp;
|
|
59
66
|
let runtime;
|
|
60
67
|
let activeContext;
|
|
@@ -62,6 +69,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
62
69
|
const repositoryRoot = resolve(options.repositoryRoot ?? config.directory);
|
|
63
70
|
const diagnosticWriter = new StateFlowDiagnosticWriter(config.logging, stateFlowLogPath(agentDir), repositoryRoot, (message) => notifyActiveContext(message));
|
|
64
71
|
let backupPending = false;
|
|
72
|
+
const backupLifetime = new AbortController();
|
|
73
|
+
const backupOperations = new Set();
|
|
65
74
|
let shuttingDown = false;
|
|
66
75
|
let pushFailureNotified = false;
|
|
67
76
|
const skillReads = new SkillReadTracker(hashSkillSource, (path) => activeContext
|
|
@@ -75,9 +84,13 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
75
84
|
function sessionAddress(ctx) {
|
|
76
85
|
return resolveSessionAddress(ctx.sessionManager.getSessionFile(), ctx.sessionManager.getSessionId(), ctx.sessionManager.getHeader()?.timestamp);
|
|
77
86
|
}
|
|
87
|
+
function notifyProblem(ctx, message, level) {
|
|
88
|
+
ctx.ui.notify(conciseDiagnostic(message), level);
|
|
89
|
+
}
|
|
78
90
|
function notifyActiveContext(message) {
|
|
79
91
|
try {
|
|
80
|
-
activeContext
|
|
92
|
+
if (activeContext)
|
|
93
|
+
notifyProblem(activeContext, message, "warning");
|
|
81
94
|
}
|
|
82
95
|
catch {
|
|
83
96
|
// An asynchronous push attempt can outlive the Pi context that launched it.
|
|
@@ -91,7 +104,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
91
104
|
}
|
|
92
105
|
function assertSelectedBranchAvailable() {
|
|
93
106
|
if (snapshot.meta.validation?.attempt === 0) {
|
|
94
|
-
throw new Error(`State Flow selected branch is unavailable: ${snapshot.meta.validation.error}
|
|
107
|
+
throw new Error(`State Flow selected branch is unavailable: ${snapshot.meta.validation.error}`);
|
|
95
108
|
}
|
|
96
109
|
}
|
|
97
110
|
options.onRuntime?.({ read: (offset, scope) => {
|
|
@@ -101,14 +114,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
101
114
|
throw new Error("State Flow temporal runtime is unavailable");
|
|
102
115
|
return projectModelState(runtime.read(offset, scope));
|
|
103
116
|
} });
|
|
104
|
-
function
|
|
117
|
+
function assertPublicationAvailable() {
|
|
105
118
|
assertSelectedBranchAvailable();
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (publication && activeContext)
|
|
111
|
-
recordPublication(publication, activeContext);
|
|
119
|
+
if (stopPersistenceError)
|
|
120
|
+
throw new Error(`Memory writes paused after Stop: ${stopPersistenceError}; use /state-flow-start`);
|
|
121
|
+
}
|
|
122
|
+
function appendCheckpoint() {
|
|
112
123
|
const checkpoint = branchStartsWithoutRuntime ? { disabled: true } : runtime?.retainedCheckpoint(snapshot) ?? { disabled: true };
|
|
113
124
|
if ("disabled" in checkpoint && !branchStartsWithoutRuntime) {
|
|
114
125
|
throw new Error("State Flow cannot checkpoint an unproven branch as ordinary disabled; restore a valid checkpoint first");
|
|
@@ -125,8 +136,40 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
125
136
|
function updateUi(ctx) {
|
|
126
137
|
ctx.ui.setStatus(STATUS_KEY, compactStatus(snapshot, scopeRevisions(), (color, text) => ctx.ui.theme.fg(color, text)));
|
|
127
138
|
}
|
|
139
|
+
function cancelStopPersistence() {
|
|
140
|
+
const pending = stopPersistence;
|
|
141
|
+
pending?.controller.abort();
|
|
142
|
+
stopPersistence = undefined;
|
|
143
|
+
return pending?.operation;
|
|
144
|
+
}
|
|
145
|
+
function cancelStartActivation() {
|
|
146
|
+
const pending = startActivation;
|
|
147
|
+
pending?.controller.abort();
|
|
148
|
+
startActivation = undefined;
|
|
149
|
+
return pending?.operation;
|
|
150
|
+
}
|
|
151
|
+
function cancelBranchRestoration() {
|
|
152
|
+
const pending = branchRestoration;
|
|
153
|
+
pending?.controller.abort();
|
|
154
|
+
branchRestoration = undefined;
|
|
155
|
+
return pending?.operation;
|
|
156
|
+
}
|
|
157
|
+
function cancelResponseReconciliation() {
|
|
158
|
+
responseReconciliation?.abort();
|
|
159
|
+
responseReconciliation = undefined;
|
|
160
|
+
}
|
|
161
|
+
function cancelSharedInspections() {
|
|
162
|
+
sharedInspectionLifetime.abort(new Error("State Flow inspection was cancelled; select the scope again"));
|
|
163
|
+
sharedInspectionLifetime = new AbortController();
|
|
164
|
+
}
|
|
165
|
+
function cancelInferencePreparation() {
|
|
166
|
+
inferencePreparation?.controller.abort();
|
|
167
|
+
inferencePreparation = undefined;
|
|
168
|
+
}
|
|
128
169
|
function clearRunTransient() {
|
|
129
|
-
|
|
170
|
+
cancelInferencePreparation();
|
|
171
|
+
cancelResponseReconciliation();
|
|
172
|
+
cancelSharedInspections();
|
|
130
173
|
completedRunAccepted = false;
|
|
131
174
|
turnAcceptedForBackup = false;
|
|
132
175
|
compactionPlan = undefined;
|
|
@@ -134,10 +177,11 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
134
177
|
skillReads.clear();
|
|
135
178
|
artifactReads.clear();
|
|
136
179
|
}
|
|
137
|
-
function
|
|
180
|
+
function deferInferencePreparation(prompt) {
|
|
181
|
+
cancelInferencePreparation();
|
|
182
|
+
inferencePreparation = { controller: new AbortController(), prompt };
|
|
138
183
|
artifactInvalidations = [];
|
|
139
184
|
artifactReads.setCandidates([]);
|
|
140
|
-
artifactRefreshPending = true;
|
|
141
185
|
}
|
|
142
186
|
function refreshArtifactHints() {
|
|
143
187
|
if (!runtime?.view) {
|
|
@@ -175,12 +219,10 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
175
219
|
artifactInvalidations = [...nextInvalidations.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
176
220
|
artifactReads.setCandidates(artifactInvalidations);
|
|
177
221
|
}
|
|
178
|
-
function
|
|
179
|
-
if (!snapshot.config.enabled || !runtime?.view)
|
|
180
|
-
return;
|
|
222
|
+
function missingArtifactRemovals(states) {
|
|
181
223
|
const owners = new Map();
|
|
182
224
|
for (const scope of ["global", "cwd", "session"])
|
|
183
|
-
for (const path of Object.keys(
|
|
225
|
+
for (const path of Object.keys(states[scope].artifacts)) {
|
|
184
226
|
owners.set(path, [...owners.get(path) ?? [], scope]);
|
|
185
227
|
}
|
|
186
228
|
const removals = {};
|
|
@@ -192,12 +234,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
192
234
|
removals[scope] = { ...(removals[scope] ?? {}), artifacts: { ...artifacts, [observation.path]: null } };
|
|
193
235
|
}
|
|
194
236
|
}
|
|
195
|
-
|
|
196
|
-
const stage = stageAtomicScopePatches(scopeStates, removals, [], runtime.causalBasis());
|
|
197
|
-
commitStage(stage, ctx, false);
|
|
198
|
-
updateUi(ctx);
|
|
199
|
-
}
|
|
200
|
-
refreshArtifactHints();
|
|
237
|
+
return removals;
|
|
201
238
|
}
|
|
202
239
|
function installScopeStates() {
|
|
203
240
|
scopeStates = runtime?.view ? runtime.states() : { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
@@ -205,19 +242,27 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
205
242
|
function passiveToolsAvailable() {
|
|
206
243
|
return snapshot.config.enabled || config.passiveTools;
|
|
207
244
|
}
|
|
208
|
-
function
|
|
245
|
+
async function inspectTelegramState(scope) {
|
|
246
|
+
if (!activeContext || shuttingDown)
|
|
247
|
+
throw new Error("State Flow is not attached to an active session yet");
|
|
209
248
|
if (scope === "session" || scope === "effective")
|
|
210
249
|
assertSelectedBranchAvailable();
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
250
|
+
const selected = runtime ??= createRuntime(activeContext);
|
|
251
|
+
const signal = sharedInspectionLifetime.signal;
|
|
252
|
+
if (!selected.view || (scope !== "session" && !stopPersistenceError))
|
|
253
|
+
await selected.refreshShared(signal);
|
|
254
|
+
signal.throwIfAborted();
|
|
255
|
+
if (runtime !== selected)
|
|
256
|
+
throw new Error("State Flow inspection selection changed; select the scope again");
|
|
257
|
+
if (scope === "session" || scope === "effective")
|
|
258
|
+
assertSelectedBranchAvailable();
|
|
259
|
+
if (!selected.view)
|
|
260
|
+
throw new Error("State Flow temporal runtime is unavailable");
|
|
220
261
|
installScopeStates();
|
|
262
|
+
const state = scope === "effective"
|
|
263
|
+
? overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)
|
|
264
|
+
: scopeStates[scope];
|
|
265
|
+
return { state: { ...projectModelState(state), lazy: structuredClone(state.lazy) }, revisions: scopeRevisions(), signal };
|
|
221
266
|
}
|
|
222
267
|
function syncStateFlowTools() {
|
|
223
268
|
const active = pi.getActiveTools();
|
|
@@ -254,34 +299,65 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
254
299
|
const target = `${read.scope}.artifacts[${JSON.stringify(read.path)}]`;
|
|
255
300
|
return `State Flow acquisition: this registered Skill belongs at ${target}. If durable compiled guidance is useful, include a non-empty description, kind:"skill", and compilation object there. Unrelated semantic patches do not need to include it.`;
|
|
256
301
|
}
|
|
257
|
-
function
|
|
258
|
-
|
|
259
|
-
|
|
302
|
+
function clearAcceptedAcquisitions(acquiredArtifactPaths = new Set(artifactReads.successful.keys())) {
|
|
303
|
+
artifactInvalidations = artifactInvalidations.filter(({ path }) => !acquiredArtifactPaths.has(path));
|
|
304
|
+
artifactReads.setCandidates(artifactInvalidations);
|
|
305
|
+
dropCurrentSkillReads();
|
|
306
|
+
artifactReads.clear();
|
|
307
|
+
}
|
|
308
|
+
async function prepareInference(pending, selected, ctx, operationSignal) {
|
|
309
|
+
const signal = AbortSignal.any([pending.controller.signal, operationSignal]);
|
|
260
310
|
try {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
311
|
+
if (!selected?.view)
|
|
312
|
+
throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
313
|
+
await selected.withPatchTransaction((transaction) => {
|
|
314
|
+
signal.throwIfAborted();
|
|
315
|
+
if (runtime !== selected || inferencePreparation !== pending || !snapshot.config.enabled || shuttingDown) {
|
|
316
|
+
throw new Error("State Flow inference selection changed while awaiting publication");
|
|
317
|
+
}
|
|
318
|
+
assertPublicationAvailable();
|
|
319
|
+
const nextSnapshot = structuredClone(snapshot);
|
|
320
|
+
const rotatesRun = pending.prompt !== undefined && nextSnapshot.meta.specification !== undefined;
|
|
321
|
+
if (pending.prompt !== undefined)
|
|
322
|
+
prepareRun(nextSnapshot, pending.prompt);
|
|
323
|
+
// Exact-path metadata validation belongs to the locked current head, including newly adopted registrations.
|
|
324
|
+
const removals = missingArtifactRemovals(transaction.states);
|
|
325
|
+
let publication;
|
|
326
|
+
if (Object.keys(removals).length > 0) {
|
|
327
|
+
const stage = stageAtomicScopePatches(transaction.states, removals, [], transaction.causalBasis);
|
|
328
|
+
commitScopedTransition(nextSnapshot, transaction.states, stage, (transition, next) => {
|
|
329
|
+
publication = transaction.publish(next, transition);
|
|
330
|
+
pending.accepted = true;
|
|
331
|
+
}, transaction.causalBasis, { finalizeRun: false });
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
publication = transaction.publish(nextSnapshot);
|
|
335
|
+
pending.accepted = true;
|
|
336
|
+
}
|
|
337
|
+
snapshot = nextSnapshot;
|
|
338
|
+
installScopeStates();
|
|
339
|
+
if (rotatesRun && rehydrationPhase !== "new-bootstrap" && rehydrationPhase !== "resume-bootstrap")
|
|
340
|
+
rehydrationPhase = "step";
|
|
341
|
+
if (publication?.changed)
|
|
268
342
|
recordPublication(publication, ctx);
|
|
269
|
-
|
|
343
|
+
if (pending.prompt !== undefined || publication?.changed)
|
|
344
|
+
appendCheckpoint();
|
|
345
|
+
if (Object.keys(removals).length > 0)
|
|
346
|
+
clearAcceptedAcquisitions();
|
|
347
|
+
updateUi(ctx);
|
|
348
|
+
}, signal);
|
|
270
349
|
}
|
|
271
350
|
catch (error) {
|
|
272
|
-
if (
|
|
273
|
-
|
|
274
|
-
|
|
351
|
+
if (signal.aborted || runtime !== selected || inferencePreparation !== pending || shuttingDown)
|
|
352
|
+
return;
|
|
353
|
+
// Pi reports context-hook exceptions and continues. Abort through its public port rather than infer from stale preparation.
|
|
354
|
+
ctx.abort();
|
|
355
|
+
const cause = diagnosticText(error);
|
|
356
|
+
recordDiagnostic(cause, "publication-conflict", ctx);
|
|
357
|
+
notifyProblem(ctx, pending.accepted
|
|
358
|
+
? `State Flow preparation saved; lifecycle update failed: ${cause}`
|
|
359
|
+
: `State Flow inference preparation failed: ${cause}`, "error");
|
|
275
360
|
}
|
|
276
|
-
if (!committed)
|
|
277
|
-
return false;
|
|
278
|
-
installScopeStates();
|
|
279
|
-
artifactInvalidations = artifactInvalidations.filter(({ path }) => !acquiredArtifactPaths.has(path));
|
|
280
|
-
artifactReads.setCandidates(artifactInvalidations);
|
|
281
|
-
dropCurrentSkillReads();
|
|
282
|
-
artifactReads.clear();
|
|
283
|
-
persist();
|
|
284
|
-
return true;
|
|
285
361
|
}
|
|
286
362
|
function currentRehydrationPhase() {
|
|
287
363
|
return rehydrationPhase;
|
|
@@ -312,6 +388,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
312
388
|
} }),
|
|
313
389
|
staleArtifacts,
|
|
314
390
|
...(durableStateError === undefined ? {} : { durableStateError }),
|
|
391
|
+
...(stopPersistenceError === undefined ? {} : { publicationError: stopPersistenceError }),
|
|
315
392
|
};
|
|
316
393
|
}
|
|
317
394
|
function forkSource(ctx) {
|
|
@@ -323,122 +400,255 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
323
400
|
throw new Error("State Flow fork parent CWD identity mismatch");
|
|
324
401
|
return resolveSessionAddress(parent.file, parent.id, parent.timestamp);
|
|
325
402
|
}
|
|
326
|
-
|
|
403
|
+
/** Select the active native branch under one owned restoration lifetime; only current accepted work installs memory. */
|
|
404
|
+
function restoreActiveBranch(ctx, sessionStartReason, notifyRecovery = true, startOwner) {
|
|
405
|
+
cancelBranchRestoration();
|
|
406
|
+
// Start-owned attachment/fork recovery keeps its owner; only accepted Start cancels Stop.
|
|
407
|
+
if (!startOwner) {
|
|
408
|
+
cancelStartActivation();
|
|
409
|
+
cancelStopPersistence();
|
|
410
|
+
}
|
|
327
411
|
clearRunTransient();
|
|
328
412
|
passiveContinuation = undefined;
|
|
329
413
|
bootstrapContinuation = undefined;
|
|
330
414
|
artifactInvalidations = [];
|
|
331
415
|
artifactReads.setCandidates([]);
|
|
332
|
-
artifactRefreshPending = false;
|
|
333
416
|
telegramStartPending = false;
|
|
334
417
|
activeContext = ctx;
|
|
335
|
-
|
|
336
|
-
runtime = new TemporalRuntime(ctx.cwd, session, repositoryRoot, undefined, config.historyLimit);
|
|
418
|
+
runtime = createRuntime(ctx);
|
|
337
419
|
installScopeStates();
|
|
338
|
-
branchHasSnapshot = false;
|
|
339
420
|
branchStartsWithoutRuntime = false;
|
|
421
|
+
selectedHistoryExpired = false;
|
|
340
422
|
forkInitialization = sessionStartReason === "fork";
|
|
423
|
+
let selection = { kind: "settled" };
|
|
424
|
+
let skipped = 0;
|
|
341
425
|
try {
|
|
342
426
|
const branch = ctx.sessionManager.getBranch();
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
snapshot = accepted.snapshot;
|
|
351
|
-
recordPolicyPublication(accepted.publication, ctx);
|
|
352
|
-
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { reset: true, owner: ctx.sessionManager.getSessionId() });
|
|
353
|
-
forkInitialization = false;
|
|
354
|
-
return snapshot;
|
|
355
|
-
};
|
|
356
|
-
return prepared.snapshot;
|
|
357
|
-
}
|
|
358
|
-
const prepared = runtime.prepareBoundaryRestore(checkpoint);
|
|
359
|
-
restoreSelected = () => {
|
|
360
|
-
const restored = prepared.restore();
|
|
361
|
-
const publication = runtime.acceptRestoredOrigin(restored);
|
|
362
|
-
recordPolicyPublication(publication, ctx);
|
|
363
|
-
return restored;
|
|
364
|
-
};
|
|
365
|
-
return prepared.snapshot;
|
|
366
|
-
});
|
|
367
|
-
branchStartsWithoutRuntime = recovery.disabledMarker === true
|
|
368
|
-
|| (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
369
|
-
const skipped = discovery.errors.length + recovery.skipped.length;
|
|
370
|
-
branchHasSnapshot = recovery.skipped.length < discovery.candidates.length;
|
|
371
|
-
if (discovery.candidates.length === 0 && discovery.errors.length > 0) {
|
|
372
|
-
snapshot = migrationFailure({}, `Snapshot restoration failed: ${discovery.errors[0]}`);
|
|
427
|
+
const fence = retainsPhysicalSessionProjection(sessionStartReason)
|
|
428
|
+
? findPassiveStopBoundary(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE)?.persistenceError
|
|
429
|
+
: undefined;
|
|
430
|
+
stopPersistenceError = fence ?? (startOwner ? stopPersistenceError : undefined);
|
|
431
|
+
if (fence) {
|
|
432
|
+
// The native Stop owns policy only: read proven memory, never replay or publish a stale selection.
|
|
433
|
+
selection = { kind: "current" };
|
|
373
434
|
}
|
|
374
|
-
else
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
435
|
+
else {
|
|
436
|
+
const discovery = discoverSnapshotData(branch);
|
|
437
|
+
const selected = selectRetainedCheckpoint(discovery.candidates);
|
|
438
|
+
skipped = discovery.errors.length + selected.skipped.length;
|
|
439
|
+
branchStartsWithoutRuntime = selected.kind === "disabled" || (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
440
|
+
if (discovery.candidates.length === 0 && discovery.errors.length > 0) {
|
|
441
|
+
snapshot = migrationFailure({}, `Snapshot restoration failed: ${discovery.errors[0]}`);
|
|
379
442
|
}
|
|
380
|
-
else if (
|
|
381
|
-
|
|
382
|
-
|
|
443
|
+
else if (selected.kind === "boundary" && forkInitialization) {
|
|
444
|
+
try {
|
|
445
|
+
// Native parent acquisition stays outside canonical exclusion.
|
|
446
|
+
const source = forkSource(ctx);
|
|
447
|
+
const sourceStopped = findPassiveStopBoundary(branch, source.id, PASSIVE_STOP_ENTRY_TYPE)?.persistenceError !== undefined;
|
|
448
|
+
selection = { kind: "fork", source, checkpoint: sourceStopped ? { ...selected.checkpoint, enabled: false } : selected.checkpoint };
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
452
|
+
}
|
|
383
453
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
454
|
+
else if (selected.kind === "boundary") {
|
|
455
|
+
selection = { kind: "restore", checkpoint: selected.checkpoint };
|
|
456
|
+
}
|
|
457
|
+
else if (discovery.candidates.length > 0) {
|
|
458
|
+
snapshot = selected.kind === "disabled" ? emptySnapshot() : selected.snapshot;
|
|
459
|
+
}
|
|
460
|
+
else if (config.autoStart && isNewSession(sessionStartReason, branch)) {
|
|
461
|
+
selection = { kind: "auto-start" };
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
392
464
|
snapshot = emptySnapshot();
|
|
393
|
-
installScopeStates();
|
|
394
|
-
if (snapshot.config.enabled) {
|
|
395
|
-
branchHasSnapshot = true;
|
|
396
|
-
persist();
|
|
397
465
|
}
|
|
398
466
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
selection = { kind: "settled" };
|
|
470
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
471
|
+
}
|
|
472
|
+
// Pending selection grants neither private reads nor publication until its acceptance installs memory.
|
|
473
|
+
if (selection.kind !== "settled")
|
|
474
|
+
snapshot = migrationFailure({}, "State Flow branch restoration is pending");
|
|
475
|
+
const pending = {
|
|
476
|
+
controller: new AbortController(),
|
|
477
|
+
awaitingAcceptance: selection.kind !== "settled" && selection.kind !== "current",
|
|
478
|
+
};
|
|
479
|
+
branchRestoration = pending;
|
|
480
|
+
const operation = attachBranch(ctx, pending, selection, sessionStartReason, notifyRecovery, skipped);
|
|
481
|
+
pending.operation = operation;
|
|
482
|
+
restorationOperations.add(operation);
|
|
483
|
+
const finished = () => { restorationOperations.delete(operation); };
|
|
484
|
+
void operation.then(finished, finished);
|
|
485
|
+
return operation;
|
|
486
|
+
}
|
|
487
|
+
async function attachBranch(ctx, pending, selection, reason, notifyRecovery, skipped) {
|
|
488
|
+
const placeholder = runtime;
|
|
489
|
+
let selected = runtime;
|
|
490
|
+
const owner = ctx.sessionManager.getSessionId();
|
|
491
|
+
const cwd = ctx.cwd;
|
|
492
|
+
const file = ctx.sessionManager.getSessionFile();
|
|
493
|
+
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
494
|
+
const signal = AbortSignal.any([pending.controller.signal, ...(ctx.signal ? [ctx.signal] : [])]);
|
|
495
|
+
const isCurrent = () => branchRestoration === pending && !pending.controller.signal.aborted && !shuttingDown && runtime === selected
|
|
496
|
+
&& ctx.cwd === cwd && ctx.sessionManager.getSessionId() === owner && ctx.sessionManager.getSessionFile() === file
|
|
497
|
+
&& ctx.sessionManager.getHeader()?.timestamp === timestamp;
|
|
498
|
+
const assertCurrent = () => {
|
|
499
|
+
signal.throwIfAborted();
|
|
500
|
+
if (!isCurrent())
|
|
501
|
+
throw new Error("State Flow branch selection changed while awaiting publication");
|
|
502
|
+
};
|
|
503
|
+
let accepted = false;
|
|
504
|
+
// Install accepted memory before native writes; later ancillary failures cannot revert or replay it.
|
|
505
|
+
const accept = (candidate, next, publication, nativeWrites) => {
|
|
506
|
+
accepted = true;
|
|
507
|
+
pending.awaitingAcceptance = false;
|
|
508
|
+
runtime = selected = candidate;
|
|
509
|
+
snapshot = next;
|
|
510
|
+
installScopeStates();
|
|
511
|
+
recordPolicyPublication(publication, ctx);
|
|
512
|
+
let failure;
|
|
513
|
+
try {
|
|
514
|
+
nativeWrites();
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
failure = error;
|
|
518
|
+
}
|
|
519
|
+
settleSelection(ctx, reason, notifyRecovery, skipped, true);
|
|
520
|
+
if (failure !== undefined)
|
|
521
|
+
throw failure;
|
|
522
|
+
};
|
|
523
|
+
try {
|
|
524
|
+
if (selection.kind !== "settled") {
|
|
525
|
+
// Local policy is already passive while the selection waits for exclusion.
|
|
526
|
+
syncStateFlowTools();
|
|
527
|
+
updateUi(ctx);
|
|
528
|
+
const candidate = createRuntime(ctx);
|
|
529
|
+
try {
|
|
530
|
+
if (selection.kind === "current") {
|
|
531
|
+
const current = await candidate.refreshCurrentMemory(signal);
|
|
532
|
+
assertCurrent();
|
|
533
|
+
if (current)
|
|
534
|
+
runtime = selected = candidate;
|
|
535
|
+
snapshot = current ?? migrationFailure({}, "Current State Flow session memory is unavailable");
|
|
536
|
+
installScopeStates();
|
|
537
|
+
}
|
|
538
|
+
else if (selection.kind === "restore") {
|
|
539
|
+
await candidate.withRestoreTransaction(selection.checkpoint, (restored, publish) => {
|
|
540
|
+
assertCurrent();
|
|
541
|
+
// Canceled preparation or boundary continuation may have no specification. Retain uncompiled native context.
|
|
542
|
+
if (restored.config.enabled && restored.meta.specification === undefined
|
|
543
|
+
&& retainsPhysicalSessionProjection(reason) && hasUncheckpointedConversation(ctx.sessionManager.getBranch()))
|
|
544
|
+
restored.meta.bootstrap = true;
|
|
545
|
+
const next = pending.passiveRequested ? stopEpisode(restored) : restored;
|
|
546
|
+
accept(candidate, next, publish(next), appendCheckpoint);
|
|
547
|
+
}, signal);
|
|
548
|
+
}
|
|
549
|
+
else if (selection.kind === "fork") {
|
|
550
|
+
await candidate.withForkTransaction(selection.source, selection.checkpoint, (child, publish) => {
|
|
551
|
+
assertCurrent();
|
|
552
|
+
// Mode is selected independently of creating the child's private memory.
|
|
553
|
+
const next = pending.passiveRequested ? stopEpisode(child) : child;
|
|
554
|
+
const publication = publish(next);
|
|
555
|
+
forkInitialization = false;
|
|
556
|
+
accept(candidate, next, publication, () => {
|
|
557
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { reset: true, owner });
|
|
558
|
+
appendCheckpoint();
|
|
559
|
+
});
|
|
560
|
+
}, signal);
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
await candidate.withStartTransaction((current, publish) => {
|
|
564
|
+
assertCurrent();
|
|
565
|
+
// Truly new branch authority is rechecked after waiting; existing memory needs its own selection.
|
|
566
|
+
const branch = ctx.sessionManager.getBranch();
|
|
567
|
+
const discovery = discoverSnapshotData(branch);
|
|
568
|
+
if (current || discovery.candidates.length > 0 || discovery.errors.length > 0)
|
|
569
|
+
throw new Error("Existing session runtime requires retained-boundary restoration");
|
|
570
|
+
const activated = startEpisode(hasPriorConversation(branch));
|
|
571
|
+
const next = pending.passiveRequested ? stopEpisode(activated) : activated;
|
|
572
|
+
accept(candidate, next, publish(next), appendCheckpoint);
|
|
573
|
+
}, signal, true);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
if (accepted) {
|
|
578
|
+
if (isCurrent())
|
|
579
|
+
notifyProblem(ctx, `State Flow memory restored; lifecycle update failed: ${diagnosticText(error)}`, "warning");
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if (!isCurrent())
|
|
583
|
+
return;
|
|
584
|
+
if (error instanceof HistoryBoundaryExpiredError)
|
|
585
|
+
selectedHistoryExpired = true;
|
|
586
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
587
|
+
installScopeStates();
|
|
588
|
+
}
|
|
589
|
+
finally {
|
|
590
|
+
pending.awaitingAcceptance = false;
|
|
591
|
+
}
|
|
592
|
+
if (accepted)
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
settleSelection(ctx, reason, notifyRecovery, skipped, selected !== placeholder);
|
|
596
|
+
if (snapshot.config.enabled || !(config.passiveBootstrap || config.passiveTools) || runtime?.view)
|
|
597
|
+
return;
|
|
598
|
+
const passive = createRuntime(ctx);
|
|
599
|
+
const priorView = selected?.view;
|
|
600
|
+
try {
|
|
601
|
+
await passive.refreshShared(signal);
|
|
402
602
|
}
|
|
403
|
-
|
|
404
|
-
|
|
603
|
+
catch (error) {
|
|
604
|
+
if (isCurrent() && !signal.aborted && notifyRecovery && !stopPersistenceError)
|
|
605
|
+
notifyProblem(ctx, `State Flow passive memory is unavailable: ${diagnosticText(error)}`, "warning");
|
|
606
|
+
return;
|
|
405
607
|
}
|
|
608
|
+
assertCurrent();
|
|
609
|
+
// An intervening passive patch or inspection owns its newer cache, even on the same physical branch.
|
|
610
|
+
if (selected?.view !== priorView)
|
|
611
|
+
return;
|
|
612
|
+
runtime = selected = passive;
|
|
613
|
+
installScopeStates();
|
|
614
|
+
updateUi(ctx);
|
|
406
615
|
}
|
|
407
616
|
catch (error) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
meta: { ...snapshot.meta, validation: failure.meta.validation },
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
if (!snapshot.config.enabled && snapshot.meta.validation?.attempt === 0) {
|
|
416
|
-
// A failed acceptance may have installed a prepared view; only shared passive reads may survive.
|
|
417
|
-
runtime = createRuntime(ctx);
|
|
617
|
+
// Host failures before acceptance leave the selection unavailable, never invented empty memory.
|
|
618
|
+
if (accepted || !isCurrent())
|
|
619
|
+
return;
|
|
620
|
+
snapshot = selectedBoundaryFailure(diagnosticText(error));
|
|
418
621
|
installScopeStates();
|
|
419
|
-
ctx.ui.notify(`State Flow restored disabled: ${snapshot.meta.validation.error}`, "error");
|
|
420
622
|
}
|
|
421
|
-
|
|
623
|
+
finally {
|
|
624
|
+
pending.awaitingAcceptance = false;
|
|
625
|
+
if (branchRestoration === pending)
|
|
626
|
+
branchRestoration = undefined;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function settleSelection(ctx, reason, notifyRecovery, skipped, selectedMemory) {
|
|
630
|
+
const failure = !snapshot.config.enabled && snapshot.meta.validation?.attempt === 0 ? snapshot.meta.validation.error : undefined;
|
|
631
|
+
if (failure !== undefined && notifyRecovery && !stopPersistenceError) {
|
|
632
|
+
if (selectedHistoryExpired)
|
|
633
|
+
notifyProblem(ctx, "State Flow history is outside the retained temporal window; /state-flow-start can use current session memory.", "warning");
|
|
634
|
+
else
|
|
635
|
+
notifyProblem(ctx, `State Flow restore failed: ${failure}`, "error");
|
|
636
|
+
}
|
|
637
|
+
if (selectedMemory && retainsPhysicalSessionProjection(reason) && runtime?.view) {
|
|
422
638
|
const boundary = findPassiveStopBoundary(ctx.sessionManager.getBranch(), ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE);
|
|
423
639
|
if (boundary !== undefined) {
|
|
424
|
-
const continuation = createPassiveContinuation(projectModelState(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)), boundary.at, boundary.from);
|
|
640
|
+
const continuation = createPassiveContinuation(projectModelState(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)), boundary.at, boundary.from, boundary.preserveContext);
|
|
425
641
|
if (!snapshot.config.enabled)
|
|
426
642
|
passiveContinuation = continuation;
|
|
427
643
|
else if (snapshot.meta.bootstrap)
|
|
428
644
|
bootstrapContinuation = continuation;
|
|
429
645
|
}
|
|
430
646
|
}
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
runtime?.loadPassive();
|
|
434
|
-
installScopeStates();
|
|
435
|
-
}
|
|
436
|
-
catch (error) {
|
|
437
|
-
ctx.ui.notify(`State Flow passive memory is unavailable: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
438
|
-
}
|
|
647
|
+
if (notifyRecovery && snapshot.config.enabled && skipped > 0) {
|
|
648
|
+
notifyProblem(ctx, `State Flow skipped ${skipped} malformed snapshot(s); restored the last valid one.`, "warning");
|
|
439
649
|
}
|
|
440
650
|
if (snapshot.config.enabled)
|
|
441
|
-
|
|
651
|
+
deferInferencePreparation();
|
|
442
652
|
syncStateFlowTools();
|
|
443
653
|
updateUi(ctx);
|
|
444
654
|
}
|
|
@@ -522,11 +732,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
522
732
|
try {
|
|
523
733
|
if (!passiveToolsAvailable())
|
|
524
734
|
throw new Error("State Flow tools are disabled by configuration");
|
|
525
|
-
|
|
735
|
+
assertPublicationAvailable();
|
|
526
736
|
if (signal?.aborted)
|
|
527
737
|
throw new Error("State Flow patch was aborted before materialization");
|
|
528
738
|
if (!isObject(params))
|
|
529
739
|
throw new Error("patch_state requires an object");
|
|
740
|
+
params = structuredClone(params);
|
|
530
741
|
const allowed = new Set(["global", "cwd", "session"]);
|
|
531
742
|
for (const key of Object.keys(params)) {
|
|
532
743
|
if (!allowed.has(key))
|
|
@@ -545,31 +756,43 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
545
756
|
}
|
|
546
757
|
const scopes = Object.keys(patches);
|
|
547
758
|
if (scopes.length === 0)
|
|
548
|
-
throw new Error("patch_state requires at least one
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
759
|
+
throw new Error("patch_state requires at least one scope patch");
|
|
760
|
+
const selected = runtime ??= createRuntime(ctx);
|
|
761
|
+
const acquiredArtifacts = structuredClone([...artifactReads.successful.values()]);
|
|
762
|
+
const acquiredSkills = structuredClone([...skillReads.successful.values()]);
|
|
763
|
+
return await selected.withPatchTransaction((transaction) => {
|
|
764
|
+
if (runtime !== selected)
|
|
765
|
+
throw new Error("State Flow session selection changed while awaiting publication");
|
|
766
|
+
if (!passiveToolsAvailable())
|
|
767
|
+
throw new Error("State Flow tools are disabled by configuration");
|
|
768
|
+
assertPublicationAvailable();
|
|
769
|
+
for (const acquired of acquiredArtifacts) {
|
|
770
|
+
const observation = inspectRegisteredArtifactPaths([acquired.path])[0];
|
|
771
|
+
if (acquired.sourceFingerprint === undefined || observation?.kind !== "present"
|
|
772
|
+
|| !sameArtifactSourceFingerprint(acquired.sourceFingerprint, observation.fingerprint)) {
|
|
773
|
+
throw new Error(`Artifact source changed after acquisition: ${acquired.path}`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
const stage = stageAtomicScopePatches(transaction.states, patches, acquiredSkills, transaction.causalBasis, acquiredArtifacts);
|
|
777
|
+
const changed = ["global", "cwd", "session"].some((scope) => !sameJson(transaction.states[scope], stage.nextStates[scope])
|
|
778
|
+
|| Object.entries(stage.provenanceUpdates[scope]).some(([path, evidence]) => !Object.hasOwn(transaction.provenance[scope], path) || !sameJson(transaction.provenance[scope][path], evidence)));
|
|
779
|
+
const nextSnapshot = structuredClone(snapshot);
|
|
780
|
+
let publication;
|
|
781
|
+
commitScopedTransition(nextSnapshot, transaction.states, stage, (accepted, next) => {
|
|
782
|
+
publication = transaction.publish(next, accepted, stage.provenanceUpdates);
|
|
783
|
+
}, transaction.causalBasis, { finalizeRun: false });
|
|
784
|
+
snapshot = nextSnapshot;
|
|
554
785
|
installScopeStates();
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
for (const acquired of artifactReads.successful.values()) {
|
|
559
|
-
const observation = inspectRegisteredArtifactPaths([acquired.path])[0];
|
|
560
|
-
if (acquired.sourceFingerprint === undefined || observation?.kind !== "present"
|
|
561
|
-
|| !sameArtifactSourceFingerprint(acquired.sourceFingerprint, observation.fingerprint)) {
|
|
562
|
-
throw new Error(`Artifact source changed after acquisition: ${acquired.path}`);
|
|
786
|
+
if (publication?.changed) {
|
|
787
|
+
recordPublication(publication, ctx);
|
|
788
|
+
appendCheckpoint();
|
|
563
789
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
commitStage(stage, ctx, false);
|
|
571
|
-
updateUi(ctx);
|
|
572
|
-
return { content: [{ type: "text", text: `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.` }], details: { scopes, step: snapshot.meta.step } };
|
|
790
|
+
clearAcceptedAcquisitions(new Set(acquiredArtifacts.map(({ path }) => path)));
|
|
791
|
+
updateUi(ctx);
|
|
792
|
+
return { content: [{ type: "text", text: changed
|
|
793
|
+
? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
|
|
794
|
+
: "\nState already current." }], details: { scopes, step: snapshot.meta.step, changed } };
|
|
795
|
+
}, signal);
|
|
573
796
|
}
|
|
574
797
|
catch (error) {
|
|
575
798
|
let attempted;
|
|
@@ -579,7 +802,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
579
802
|
catch {
|
|
580
803
|
attempted = undefined;
|
|
581
804
|
}
|
|
582
|
-
|
|
805
|
+
const cause = diagnosticText(error);
|
|
806
|
+
recordDiagnostic(cause, /concurrently|advanced/.test(cause) ? "publication-conflict" : "invalid-patch", ctx, {
|
|
583
807
|
input: attempted,
|
|
584
808
|
tool: PATCH_STATE_TOOL_NAME,
|
|
585
809
|
toolCallId,
|
|
@@ -589,73 +813,136 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
589
813
|
},
|
|
590
814
|
});
|
|
591
815
|
function startStateFlow(ctx) {
|
|
592
|
-
if (
|
|
593
|
-
|
|
816
|
+
if (shuttingDown)
|
|
817
|
+
return Promise.resolve({ ok: false, message: "State Flow is shutting down" });
|
|
818
|
+
if (startActivation?.operation)
|
|
819
|
+
return startActivation.operation;
|
|
594
820
|
telegramStartPending = false;
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
const
|
|
599
|
-
|
|
821
|
+
if (activeContext && !branchRestoration && snapshot.config.enabled) {
|
|
822
|
+
return Promise.resolve({ ok: true, message: "State Flow is already enabled", signal: sharedInspectionLifetime.signal });
|
|
823
|
+
}
|
|
824
|
+
const pending = { controller: new AbortController() };
|
|
825
|
+
startActivation = pending;
|
|
826
|
+
const operation = runStart(ctx, pending.controller);
|
|
827
|
+
pending.operation = operation;
|
|
828
|
+
const finished = () => { if (startActivation === pending)
|
|
829
|
+
startActivation = undefined; };
|
|
830
|
+
void operation.then(finished, finished);
|
|
831
|
+
return operation;
|
|
832
|
+
}
|
|
833
|
+
/** Await restoration-owned attachment and exact-source fork recovery before current-head activation. */
|
|
834
|
+
async function runStart(ctx, pending) {
|
|
835
|
+
const owner = ctx.sessionManager.getSessionId();
|
|
836
|
+
const cwd = ctx.cwd;
|
|
837
|
+
const file = ctx.sessionManager.getSessionFile();
|
|
838
|
+
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
839
|
+
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
840
|
+
const isOwner = () => startActivation?.controller === pending && !signal.aborted && !shuttingDown
|
|
841
|
+
&& ctx.cwd === cwd && ctx.sessionManager.getSessionId() === owner && ctx.sessionManager.getSessionFile() === file
|
|
842
|
+
&& ctx.sessionManager.getHeader()?.timestamp === timestamp;
|
|
843
|
+
const superseded = () => {
|
|
844
|
+
pending.abort();
|
|
845
|
+
return { ok: false, message: "State Flow Start was superseded", signal: pending.signal };
|
|
846
|
+
};
|
|
600
847
|
try {
|
|
601
|
-
if (!
|
|
602
|
-
|
|
603
|
-
|
|
848
|
+
if (!activeContext)
|
|
849
|
+
await waitForRecovery(restoreActiveBranch(ctx, undefined, false, pending), signal);
|
|
850
|
+
else if (branchRestoration?.operation)
|
|
851
|
+
await waitForRecovery(branchRestoration.operation, signal);
|
|
852
|
+
if (!isOwner())
|
|
853
|
+
return superseded();
|
|
854
|
+
if (snapshot.config.enabled)
|
|
855
|
+
return { ok: true, message: "State Flow is already enabled", signal: sharedInspectionLifetime.signal };
|
|
856
|
+
if (forkInitialization) {
|
|
857
|
+
// Withdraw this join on cancellation without revoking the independently owned Stop.
|
|
858
|
+
const stopping = stopPersistence?.operation;
|
|
859
|
+
if (stopping)
|
|
860
|
+
await waitForRecovery(stopping, signal);
|
|
861
|
+
if (!isOwner())
|
|
862
|
+
return superseded();
|
|
863
|
+
await waitForRecovery(restoreActiveBranch(ctx, "fork", false, pending), signal);
|
|
864
|
+
if (!isOwner())
|
|
865
|
+
return superseded();
|
|
866
|
+
assertSelectedBranchAvailable();
|
|
604
867
|
}
|
|
605
|
-
|
|
606
|
-
if (!runtime?.view && !branchStartsWithoutRuntime)
|
|
607
|
-
throw new Error("Selected State Flow boundary is unavailable; restore its canonical files before starting State Flow");
|
|
608
|
-
const branch = ctx.sessionManager.getBranch();
|
|
609
|
-
activeContext = ctx;
|
|
610
|
-
runtime ??= createRuntime(ctx);
|
|
611
|
-
if (branchStartsWithoutRuntime)
|
|
612
|
-
runtime.prepare();
|
|
613
|
-
const bootstrap = (!branchHasSnapshot || !snapshot.config.enabled)
|
|
614
|
-
&& (hasPriorConversation(branch) || previousPassiveContinuation !== undefined);
|
|
615
|
-
const existingBranch = branchHasSnapshot;
|
|
616
|
-
snapshot = existingBranch
|
|
617
|
-
? resumeEpisode(snapshot, bootstrap)
|
|
618
|
-
: startEpisode(bootstrap);
|
|
619
|
-
branchHasSnapshot = true;
|
|
620
|
-
// Activation reasserts the selected session stream as a complete cohort. A resumed
|
|
621
|
-
// branch may have no reusable temporal revision after an extension upgrade; a
|
|
622
|
-
// runtime-only write would then reject its selected session as an omitted change.
|
|
623
|
-
const publication = runtime.view
|
|
624
|
-
? runtime.publish(snapshot, true)
|
|
625
|
-
: runtime.initialize(snapshot, true, undefined, branchStartsWithoutRuntime);
|
|
626
|
-
recordPolicyPublication(publication, ctx);
|
|
627
|
-
installScopeStates();
|
|
628
|
-
clearRunTransient();
|
|
629
|
-
passiveContinuation = undefined;
|
|
630
|
-
bootstrapContinuation = snapshot.meta.bootstrap
|
|
631
|
-
? previousPassiveContinuation ?? previousBootstrapContinuation
|
|
632
|
-
: undefined;
|
|
633
|
-
deferArtifactRefresh();
|
|
634
|
-
syncStateFlowTools();
|
|
635
|
-
persist();
|
|
636
|
-
updateUi(ctx);
|
|
637
|
-
ctx.ui.notify(snapshot.meta.bootstrap
|
|
638
|
-
? "State Flow enabled. The next complete agent run will migrate active context into state."
|
|
639
|
-
: "State Flow enabled. The next prompt starts a stateful agent run.", "info");
|
|
640
|
-
return { ok: true, message: "State Flow enabled" };
|
|
868
|
+
return activateCurrentState(ctx, pending);
|
|
641
869
|
}
|
|
642
870
|
catch (error) {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
871
|
+
if (!isOwner())
|
|
872
|
+
return superseded();
|
|
873
|
+
const message = conciseDiagnostic(`State Flow Start failed: ${diagnosticText(error)}`);
|
|
874
|
+
notifyProblem(ctx, message, "error");
|
|
875
|
+
return { ok: false, message, signal: AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]) };
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
async function activateCurrentState(ctx, pending) {
|
|
879
|
+
let selected = runtime;
|
|
880
|
+
const owner = ctx.sessionManager.getSessionId();
|
|
881
|
+
const cwd = ctx.cwd;
|
|
882
|
+
const file = ctx.sessionManager.getSessionFile();
|
|
883
|
+
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
884
|
+
const initiallyEnabled = snapshot.config.enabled;
|
|
885
|
+
let accepted = false;
|
|
886
|
+
let receipt = AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]);
|
|
887
|
+
const isCurrent = () => startActivation?.controller === pending && runtime === selected && !shuttingDown
|
|
888
|
+
&& ctx.cwd === cwd && ctx.sessionManager.getSessionId() === owner && ctx.sessionManager.getSessionFile() === file
|
|
889
|
+
&& ctx.sessionManager.getHeader()?.timestamp === timestamp && snapshot.config.enabled === (accepted || initiallyEnabled);
|
|
890
|
+
const superseded = () => {
|
|
891
|
+
pending.abort();
|
|
892
|
+
return { ok: false, message: "State Flow Start was superseded", signal: pending.signal };
|
|
893
|
+
};
|
|
894
|
+
try {
|
|
895
|
+
const activation = createRuntime(ctx);
|
|
896
|
+
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
897
|
+
const result = await activation.withStartTransaction((current, publish) => {
|
|
898
|
+
signal.throwIfAborted();
|
|
899
|
+
if (!isCurrent())
|
|
900
|
+
throw new Error("State Flow Start selection changed while awaiting publication");
|
|
901
|
+
if (!current && !branchStartsWithoutRuntime)
|
|
902
|
+
throw new Error(snapshot.meta.validation?.error ?? "Current State Flow session memory is unavailable");
|
|
903
|
+
const continuation = passiveContinuation ?? bootstrapContinuation;
|
|
904
|
+
const bootstrap = hasPriorConversation(ctx.sessionManager.getBranch()) || passiveContinuation !== undefined;
|
|
905
|
+
const activated = current ? resumeEpisode(current, bootstrap) : startEpisode(bootstrap);
|
|
906
|
+
const recoveredCurrent = selectedHistoryExpired;
|
|
907
|
+
const publication = publish(activated);
|
|
908
|
+
accepted = true;
|
|
909
|
+
cancelStopPersistence();
|
|
910
|
+
runtime = selected = activation;
|
|
911
|
+
snapshot = activated;
|
|
912
|
+
activeContext = ctx;
|
|
913
|
+
selectedHistoryExpired = false;
|
|
914
|
+
stopPersistenceError = undefined;
|
|
915
|
+
installScopeStates();
|
|
916
|
+
clearRunTransient();
|
|
917
|
+
passiveContinuation = undefined;
|
|
918
|
+
bootstrapContinuation = snapshot.meta.bootstrap ? continuation : undefined;
|
|
919
|
+
deferInferencePreparation();
|
|
920
|
+
receipt = AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]);
|
|
921
|
+
recordPolicyPublication(publication, ctx);
|
|
922
|
+
syncStateFlowTools();
|
|
923
|
+
updateUi(ctx);
|
|
924
|
+
appendCheckpoint();
|
|
925
|
+
ctx.ui.notify(recoveredCurrent
|
|
926
|
+
? "State Flow enabled from current session memory; unavailable historical state was not restored."
|
|
927
|
+
: snapshot.meta.bootstrap
|
|
928
|
+
? "State Flow enabled. The next complete agent run will migrate active context into state."
|
|
929
|
+
: "State Flow enabled. The next prompt starts a stateful agent run.", "info");
|
|
930
|
+
return { ok: true, message: "State Flow enabled", signal: receipt };
|
|
931
|
+
}, signal, branchStartsWithoutRuntime);
|
|
932
|
+
return isCurrent() ? result : superseded();
|
|
933
|
+
}
|
|
934
|
+
catch (error) {
|
|
935
|
+
if (!isCurrent())
|
|
936
|
+
return superseded();
|
|
937
|
+
const message = conciseDiagnostic(`${accepted ? "State Flow enabled; lifecycle update failed" : "State Flow Start failed"}: ${diagnosticText(error)}`);
|
|
938
|
+
notifyProblem(ctx, message, accepted ? "warning" : "error");
|
|
939
|
+
return { ok: accepted, message, signal: receipt };
|
|
653
940
|
}
|
|
654
941
|
}
|
|
655
942
|
pi.registerCommand("state-flow-start", {
|
|
656
943
|
description: "Start State Flow mode",
|
|
657
944
|
handler: async (_args, ctx) => {
|
|
658
|
-
startStateFlow(ctx);
|
|
945
|
+
await startStateFlow(ctx);
|
|
659
946
|
},
|
|
660
947
|
});
|
|
661
948
|
pi.registerCommand("state-flow-status", {
|
|
@@ -665,43 +952,123 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
665
952
|
},
|
|
666
953
|
});
|
|
667
954
|
function stopStateFlow(ctx) {
|
|
955
|
+
cancelStartActivation();
|
|
956
|
+
if (shuttingDown)
|
|
957
|
+
return Promise.resolve({ ok: false, message: "State Flow is shutting down" });
|
|
958
|
+
if (stopPersistence?.operation)
|
|
959
|
+
return stopPersistence.operation;
|
|
668
960
|
if (!activeContext)
|
|
669
|
-
restoreActiveBranch(ctx);
|
|
670
|
-
|
|
961
|
+
void restoreActiveBranch(ctx, undefined, false);
|
|
962
|
+
const restoring = branchRestoration;
|
|
963
|
+
if (restoring?.awaitingAcceptance) {
|
|
964
|
+
// Stop changes policy without cancelling memory restoration or initialization.
|
|
965
|
+
restoring.passiveRequested = true;
|
|
966
|
+
snapshot = stopEpisode(snapshot);
|
|
967
|
+
clearRunTransient();
|
|
968
|
+
syncStateFlowTools();
|
|
969
|
+
updateUi(ctx);
|
|
970
|
+
return Promise.resolve({ ok: true, message: "State Flow disabled; memory restoration continues" });
|
|
971
|
+
}
|
|
972
|
+
const pending = { controller: new AbortController() };
|
|
973
|
+
stopPersistence = pending;
|
|
974
|
+
const operation = persistStoppedState(ctx, pending.controller);
|
|
975
|
+
pending.operation = operation;
|
|
976
|
+
const finished = () => { if (stopPersistence === pending)
|
|
977
|
+
stopPersistence = undefined; };
|
|
978
|
+
void operation.then(finished, finished);
|
|
979
|
+
return operation;
|
|
980
|
+
}
|
|
981
|
+
async function persistStoppedState(ctx, pending) {
|
|
671
982
|
telegramStartPending = false;
|
|
983
|
+
const selected = runtime;
|
|
984
|
+
const owner = ctx.sessionManager.getSessionId();
|
|
672
985
|
const current = snapshot;
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
installScopeStates();
|
|
677
|
-
// Freeze the accepted shared adoption, not the pre-publication cache.
|
|
678
|
-
const exitHandoff = current.config.enabled && runtime?.view
|
|
679
|
-
? createPassiveContinuation(projectModelState(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)), stoppedAt, ctx.isIdle() ? undefined : runAnchorTimestamp)
|
|
680
|
-
: undefined;
|
|
681
|
-
const retainedHandoff = exitHandoff ?? (!current.config.enabled ? passiveContinuation : undefined);
|
|
682
|
-
snapshot = stopped;
|
|
683
|
-
recordPolicyPublication(publication, ctx);
|
|
684
|
-
branchHasSnapshot = true;
|
|
986
|
+
let unfinished = current.meta.specification !== undefined
|
|
987
|
+
|| (inferencePreparation?.prompt !== undefined && !inferencePreparation.accepted);
|
|
988
|
+
snapshot = stopEpisode(current);
|
|
685
989
|
clearRunTransient();
|
|
686
|
-
passiveContinuation = retainedHandoff;
|
|
687
|
-
bootstrapContinuation = undefined;
|
|
688
|
-
artifactInvalidations = [];
|
|
689
|
-
artifactReads.setCandidates([]);
|
|
690
|
-
artifactRefreshPending = false;
|
|
691
|
-
if (exitHandoff)
|
|
692
|
-
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, {
|
|
693
|
-
at: stoppedAt,
|
|
694
|
-
...(exitHandoff.activeRunStartedAt === undefined ? {} : { from: exitHandoff.activeRunStartedAt }),
|
|
695
|
-
});
|
|
696
990
|
syncStateFlowTools();
|
|
697
|
-
persist();
|
|
698
991
|
updateUi(ctx);
|
|
699
|
-
|
|
992
|
+
if (stopPersistenceError)
|
|
993
|
+
return { ok: true, message: "State Flow disabled; memory writes remain paused" };
|
|
994
|
+
const stoppedAt = Date.now();
|
|
995
|
+
const incomingBoundary = current.meta.bootstrap ? bootstrapContinuation : undefined;
|
|
996
|
+
const anchor = runAnchorTimestamp;
|
|
997
|
+
const idle = ctx.isIdle();
|
|
998
|
+
const isCurrent = () => stopPersistence?.controller === pending && runtime === selected
|
|
999
|
+
&& !shuttingDown && ctx.sessionManager.getSessionId() === owner && !snapshot.config.enabled;
|
|
1000
|
+
const superseded = () => ({ ok: false, message: "State Flow Stop was superseded" });
|
|
1001
|
+
const freezeHandoff = () => {
|
|
1002
|
+
const handoff = (current.config.enabled || stopPersistenceError) && selected?.view && current.meta.validation?.attempt !== 0
|
|
1003
|
+
? createPassiveContinuation(projectModelState(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)), incomingBoundary?.startedAt ?? stoppedAt, incomingBoundary ? incomingBoundary.activeRunStartedAt : !idle || unfinished ? anchor : undefined, stopPersistenceError !== undefined || (incomingBoundary ? incomingBoundary.preserveContext : current.meta.bootstrap === true || (unfinished && anchor === undefined)))
|
|
1004
|
+
: undefined;
|
|
1005
|
+
passiveContinuation = handoff ?? (!current.config.enabled ? passiveContinuation : undefined);
|
|
1006
|
+
return handoff;
|
|
1007
|
+
};
|
|
1008
|
+
const complete = () => {
|
|
1009
|
+
const exitHandoff = freezeHandoff();
|
|
1010
|
+
if (exitHandoff || stopPersistenceError)
|
|
1011
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, {
|
|
1012
|
+
at: exitHandoff?.startedAt ?? stoppedAt,
|
|
1013
|
+
...(exitHandoff?.activeRunStartedAt === undefined ? {} : { from: exitHandoff.activeRunStartedAt }),
|
|
1014
|
+
...(exitHandoff?.preserveContext || stopPersistenceError ? { preserveContext: true } : {}),
|
|
1015
|
+
...(stopPersistenceError === undefined ? {} : { owner, persistenceError: stopPersistenceError }),
|
|
1016
|
+
});
|
|
1017
|
+
if (!stopPersistenceError) {
|
|
1018
|
+
appendCheckpoint();
|
|
1019
|
+
return { ok: true, message: "State Flow disabled" };
|
|
1020
|
+
}
|
|
1021
|
+
const message = conciseDiagnostic(`State Flow disabled; memory writes paused: ${stopPersistenceError}`);
|
|
1022
|
+
notifyProblem(ctx, message, "warning");
|
|
1023
|
+
return { ok: true, message };
|
|
1024
|
+
};
|
|
1025
|
+
let accepted = false;
|
|
1026
|
+
try {
|
|
1027
|
+
unfinished ||= current.config.enabled && hasUncheckpointedConversation(ctx.sessionManager.getBranch());
|
|
1028
|
+
// Projection changes before any wait; capture the old run's boundary before later native input can replace it.
|
|
1029
|
+
freezeHandoff();
|
|
1030
|
+
bootstrapContinuation = undefined;
|
|
1031
|
+
artifactInvalidations = [];
|
|
1032
|
+
artifactReads.setCandidates([]);
|
|
1033
|
+
assertPublicationAvailable();
|
|
1034
|
+
if (branchStartsWithoutRuntime || !selected?.view)
|
|
1035
|
+
return complete();
|
|
1036
|
+
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
1037
|
+
const result = await selected.withLifecycleTransaction((publish) => {
|
|
1038
|
+
signal.throwIfAborted();
|
|
1039
|
+
if (!isCurrent())
|
|
1040
|
+
throw new Error("State Flow Stop selection changed while awaiting publication");
|
|
1041
|
+
assertPublicationAvailable();
|
|
1042
|
+
const stopped = stopEpisode(snapshot);
|
|
1043
|
+
const publication = publish(stopped);
|
|
1044
|
+
accepted = true;
|
|
1045
|
+
snapshot = stopped;
|
|
1046
|
+
installScopeStates();
|
|
1047
|
+
recordPolicyPublication(publication, ctx);
|
|
1048
|
+
return complete();
|
|
1049
|
+
}, signal);
|
|
1050
|
+
return isCurrent() ? result : superseded();
|
|
1051
|
+
}
|
|
1052
|
+
catch (error) {
|
|
1053
|
+
if (!isCurrent())
|
|
1054
|
+
return superseded();
|
|
1055
|
+
const cause = diagnosticText(error);
|
|
1056
|
+
if (accepted) {
|
|
1057
|
+
const message = conciseDiagnostic(`State Flow disabled; lifecycle update failed: ${cause}`);
|
|
1058
|
+
notifyProblem(ctx, message, "warning");
|
|
1059
|
+
return { ok: true, message };
|
|
1060
|
+
}
|
|
1061
|
+
stopPersistenceError = cause.trim() ? cause : "Canonical State Flow persistence failed";
|
|
1062
|
+
bootstrapContinuation = undefined;
|
|
1063
|
+
artifactInvalidations = [];
|
|
1064
|
+
artifactReads.setCandidates([]);
|
|
1065
|
+
return complete();
|
|
1066
|
+
}
|
|
700
1067
|
}
|
|
701
1068
|
pi.registerCommand("state-flow-stop", {
|
|
702
1069
|
description: "Stop State Flow on the current session branch",
|
|
703
1070
|
handler: async (_args, ctx) => {
|
|
704
|
-
stopStateFlow(ctx);
|
|
1071
|
+
await stopStateFlow(ctx);
|
|
705
1072
|
},
|
|
706
1073
|
});
|
|
707
1074
|
const telegram = createStateFlowTelegramAdapter({
|
|
@@ -714,28 +1081,23 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
714
1081
|
bootstrap: snapshot.meta.bootstrap === true,
|
|
715
1082
|
startPending: telegramStartPending,
|
|
716
1083
|
}),
|
|
717
|
-
|
|
718
|
-
refreshTelegramStateView(scope);
|
|
719
|
-
const selected = scope === "effective"
|
|
720
|
-
? overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)
|
|
721
|
-
: scopeStates[scope];
|
|
722
|
-
return { ...projectModelState(selected), lazy: structuredClone(selected.lazy) };
|
|
723
|
-
},
|
|
724
|
-
revisions: () => scopeRevisions(),
|
|
1084
|
+
inspect: inspectTelegramState,
|
|
725
1085
|
canStartNow: () => activeContext === undefined || activeContext.isIdle(),
|
|
726
1086
|
start: () => {
|
|
727
1087
|
if (!activeContext)
|
|
728
1088
|
throw new Error("State Flow is not attached to an active session yet");
|
|
729
1089
|
return startStateFlow(activeContext);
|
|
730
1090
|
},
|
|
731
|
-
stop: () => {
|
|
1091
|
+
stop: async () => {
|
|
732
1092
|
if (!activeContext)
|
|
733
1093
|
throw new Error("State Flow is not attached to an active session yet");
|
|
1094
|
+
const operation = stopStateFlow(activeContext);
|
|
1095
|
+
const signal = sharedInspectionLifetime.signal;
|
|
734
1096
|
try {
|
|
735
|
-
return
|
|
1097
|
+
return { ...await operation, signal };
|
|
736
1098
|
}
|
|
737
1099
|
catch (error) {
|
|
738
|
-
return { ok: false, message:
|
|
1100
|
+
return { ok: false, message: diagnosticText(error), signal };
|
|
739
1101
|
}
|
|
740
1102
|
},
|
|
741
1103
|
deferStart: () => {
|
|
@@ -743,11 +1105,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
743
1105
|
},
|
|
744
1106
|
cancelStart: () => {
|
|
745
1107
|
telegramStartPending = false;
|
|
1108
|
+
cancelStartActivation();
|
|
746
1109
|
},
|
|
747
1110
|
},
|
|
748
1111
|
});
|
|
749
1112
|
void telegram.ensure();
|
|
750
|
-
pi.on("before_agent_start", (event
|
|
1113
|
+
pi.on("before_agent_start", (event) => {
|
|
751
1114
|
// Native run identity is independent of semantic enablement and survives mode toggles.
|
|
752
1115
|
runAnchorTimestamp = undefined;
|
|
753
1116
|
if (!snapshot.config.enabled) {
|
|
@@ -758,20 +1121,10 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
758
1121
|
}
|
|
759
1122
|
skillReads.clear();
|
|
760
1123
|
artifactReads.clear();
|
|
761
|
-
|
|
1124
|
+
cancelResponseReconciliation();
|
|
762
1125
|
completedRunAccepted = false;
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
rehydrationPhase = "step";
|
|
766
|
-
if (prepareRun(snapshot, event.prompt))
|
|
767
|
-
persist();
|
|
768
|
-
try {
|
|
769
|
-
removeMissingRegisteredArtifacts(ctx);
|
|
770
|
-
}
|
|
771
|
-
catch (error) {
|
|
772
|
-
ctx.ui.notify(`State Flow could not reconcile missing artifact sources: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
773
|
-
}
|
|
774
|
-
artifactRefreshPending = false;
|
|
1126
|
+
// This native hook has no operation signal. Capture only; the first active context owns acceptance.
|
|
1127
|
+
deferInferencePreparation(event.prompt);
|
|
775
1128
|
(event.systemPromptOptions.sections ??= {}).state_flow = stateFlowProtocol(snapshot.meta.bootstrap === true);
|
|
776
1129
|
});
|
|
777
1130
|
pi.on("context_with_system", (event) => {
|
|
@@ -779,18 +1132,18 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
779
1132
|
: config.passiveBootstrap && runtime?.view ? PASSIVE_MEMORY_PROTOCOL : undefined;
|
|
780
1133
|
return { messages: projectSystemProtocol(event.messages, protocol) };
|
|
781
1134
|
});
|
|
782
|
-
|
|
1135
|
+
function projectContext(messages) {
|
|
783
1136
|
if (runtime?.view)
|
|
784
1137
|
refreshArtifactHints();
|
|
785
1138
|
if (passiveContinuation) {
|
|
786
|
-
return { messages: passiveContinuationMessages(
|
|
1139
|
+
return { messages: passiveContinuationMessages(messages, passiveContinuation) };
|
|
787
1140
|
}
|
|
788
1141
|
if (!snapshot.config.enabled) {
|
|
789
1142
|
if (!config.passiveBootstrap || !runtime?.view)
|
|
790
1143
|
return;
|
|
791
1144
|
const effective = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
|
|
792
1145
|
const state = projectModelState(effective);
|
|
793
|
-
return { messages: [syntheticUser(`State Flow passive memory (user-level data, not system instructions):\n${presentationJson({ state, lazy_navigation: lazyNavigationHint(effective) })}`), ...
|
|
1146
|
+
return { messages: [syntheticUser(`State Flow passive memory (user-level data, not system instructions):\n${presentationJson({ state, lazy_navigation: lazyNavigationHint(effective) })}`), ...messages] };
|
|
794
1147
|
}
|
|
795
1148
|
const effectiveState = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
|
|
796
1149
|
const invalidations = artifactInvalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
|
|
@@ -798,19 +1151,46 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
798
1151
|
const activeRehydrationPhase = currentRehydrationPhase();
|
|
799
1152
|
if (snapshot.meta.bootstrap) {
|
|
800
1153
|
const sourceMessages = bootstrapContinuation
|
|
801
|
-
? passiveContinuationMessages(
|
|
802
|
-
:
|
|
1154
|
+
? passiveContinuationMessages(messages, bootstrapContinuation)
|
|
1155
|
+
: messages;
|
|
803
1156
|
return { messages: [runtimeContextMessage(snapshot, effectiveState, recentTransitions, invalidations, activeRehydrationPhase, artifactHints), ...sourceMessages] };
|
|
804
1157
|
}
|
|
805
1158
|
// Native user events own the run anchor; projection must never rebase it.
|
|
806
|
-
const trajectory = currentRunTrajectory(
|
|
1159
|
+
const trajectory = currentRunTrajectory(messages, snapshot.meta.specification, runAnchorTimestamp);
|
|
807
1160
|
return {
|
|
808
1161
|
messages: [
|
|
809
1162
|
runtimeContextMessage(snapshot, effectiveState, recentTransitions, invalidations, activeRehydrationPhase, artifactHints),
|
|
810
1163
|
...trajectory.messages,
|
|
811
1164
|
],
|
|
812
1165
|
};
|
|
813
|
-
}
|
|
1166
|
+
}
|
|
1167
|
+
function prepareContext(messages, ctx) {
|
|
1168
|
+
const pending = inferencePreparation;
|
|
1169
|
+
const selected = runtime;
|
|
1170
|
+
const operationSignal = ctx.signal;
|
|
1171
|
+
// Idle projections remain observational; only a live native operation may await preparation.
|
|
1172
|
+
if (!snapshot.config.enabled || !pending || !operationSignal || shuttingDown)
|
|
1173
|
+
return projectContext(messages);
|
|
1174
|
+
pending.operation ??= prepareInference(pending, selected, ctx, operationSignal).finally(() => {
|
|
1175
|
+
// A late withdrawal must not clear newer work; accepted lifecycle is never replayed after an error.
|
|
1176
|
+
if (!pending.accepted && inferencePreparation === pending)
|
|
1177
|
+
pending.operation = undefined;
|
|
1178
|
+
});
|
|
1179
|
+
return pending.operation.then(() => {
|
|
1180
|
+
if (shuttingDown || operationSignal.aborted || runtime !== selected || ctx.signal !== operationSignal)
|
|
1181
|
+
return;
|
|
1182
|
+
if (snapshot.config.enabled && inferencePreparation !== pending) {
|
|
1183
|
+
// Start may require same-run maintenance. A new captured user prompt instead owns a different context request.
|
|
1184
|
+
if (inferencePreparation?.prompt === undefined)
|
|
1185
|
+
return prepareContext(messages, ctx);
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
if (snapshot.config.enabled && !pending.accepted)
|
|
1189
|
+
return;
|
|
1190
|
+
return projectContext(messages);
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
pi.on("context", (event, ctx) => prepareContext(event.messages, ctx));
|
|
814
1194
|
pi.on("tool_execution_start", (event) => {
|
|
815
1195
|
if (!snapshot.config.enabled)
|
|
816
1196
|
return;
|
|
@@ -864,53 +1244,77 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
864
1244
|
// Observe actual user events even while disabled; Start/Stop cannot invent or erase them.
|
|
865
1245
|
if (event.message.role === "user" && runAnchorTimestamp === undefined)
|
|
866
1246
|
runAnchorTimestamp = event.message.timestamp;
|
|
867
|
-
if (!snapshot.config.enabled)
|
|
1247
|
+
if (shuttingDown || !snapshot.config.enabled)
|
|
868
1248
|
return;
|
|
869
1249
|
if (event.message.role !== "assistant")
|
|
870
1250
|
return;
|
|
1251
|
+
cancelResponseReconciliation();
|
|
871
1252
|
const message = event.message;
|
|
872
|
-
if (message.stopReason === "aborted" || assistantToolCallCount(message.content) > 0 || message.stopReason === "toolUse")
|
|
873
|
-
responseAwaitingReconciliation = false;
|
|
1253
|
+
if (message.stopReason === "aborted" || assistantToolCallCount(message.content) > 0 || message.stopReason === "toolUse")
|
|
874
1254
|
return;
|
|
875
|
-
}
|
|
876
1255
|
if (message.stopReason === "length" || message.stopReason === "error") {
|
|
877
|
-
responseAwaitingReconciliation = false;
|
|
878
1256
|
recordDiagnostic(`Assistant response ended with ${message.stopReason}`, "finalization", ctx, { content: message.content });
|
|
879
1257
|
return;
|
|
880
1258
|
}
|
|
881
|
-
|
|
1259
|
+
responseReconciliation = new AbortController();
|
|
882
1260
|
});
|
|
883
|
-
pi.on("turn_end", (event, ctx) => {
|
|
884
|
-
|
|
885
|
-
|
|
1261
|
+
pi.on("turn_end", async (event, ctx) => {
|
|
1262
|
+
if (shuttingDown)
|
|
1263
|
+
return;
|
|
1264
|
+
const pending = responseReconciliation;
|
|
1265
|
+
if (!snapshot.config.enabled || !pending) {
|
|
886
1266
|
updateUi(ctx);
|
|
887
1267
|
return;
|
|
888
1268
|
}
|
|
1269
|
+
const selected = runtime;
|
|
1270
|
+
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
889
1271
|
let responseCommitted = false;
|
|
890
|
-
const specification = snapshot.meta.specification;
|
|
891
1272
|
try {
|
|
892
1273
|
const response = finalizedAssistantResponse(event.message);
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1274
|
+
if (!selected?.view)
|
|
1275
|
+
throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
1276
|
+
await selected.withPatchTransaction((transaction) => {
|
|
1277
|
+
signal.throwIfAborted();
|
|
1278
|
+
if (runtime !== selected || responseReconciliation !== pending || !snapshot.config.enabled) {
|
|
1279
|
+
throw new Error("State Flow response selection changed while awaiting publication");
|
|
1280
|
+
}
|
|
1281
|
+
assertPublicationAvailable();
|
|
1282
|
+
const wasBootstrap = snapshot.meta.bootstrap === true;
|
|
1283
|
+
const nextSnapshot = structuredClone(snapshot);
|
|
1284
|
+
const stage = stageScopedTransition(transaction.states, { transitions: [], response }, [], transaction.causalBasis);
|
|
1285
|
+
completeRun(nextSnapshot);
|
|
1286
|
+
let publication;
|
|
1287
|
+
commitScopedTransition(nextSnapshot, transaction.states, stage, (accepted, next) => {
|
|
1288
|
+
publication = transaction.publish(next, accepted);
|
|
1289
|
+
}, transaction.causalBasis, { finalizeRun: true });
|
|
1290
|
+
snapshot = nextSnapshot;
|
|
1291
|
+
installScopeStates();
|
|
1292
|
+
responseCommitted = true;
|
|
1293
|
+
if (publication?.changed)
|
|
1294
|
+
recordPublication(publication, ctx);
|
|
1295
|
+
appendCheckpoint();
|
|
1296
|
+
clearAcceptedAcquisitions();
|
|
1297
|
+
bootstrapContinuation = undefined;
|
|
1298
|
+
rehydrationPhase = "step";
|
|
1299
|
+
completedRunAccepted = !wasBootstrap;
|
|
1300
|
+
turnAcceptedForBackup = true;
|
|
1301
|
+
updateUi(ctx);
|
|
1302
|
+
}, signal);
|
|
901
1303
|
}
|
|
902
1304
|
catch (error) {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1305
|
+
// Superseded completion owns neither the new lifecycle nor its diagnostics/UI.
|
|
1306
|
+
if (signal.aborted || runtime !== selected || responseReconciliation !== pending)
|
|
1307
|
+
return;
|
|
1308
|
+
const cause = diagnosticText(error);
|
|
1309
|
+
recordDiagnostic(cause, "finalization", ctx);
|
|
1310
|
+
notifyProblem(ctx, responseCommitted
|
|
1311
|
+
? `State Flow response saved; lifecycle update failed: ${cause}`
|
|
1312
|
+
: `State Flow response reconciliation failed: ${cause}`, "error");
|
|
909
1313
|
}
|
|
910
1314
|
finally {
|
|
911
|
-
|
|
1315
|
+
if (responseReconciliation === pending)
|
|
1316
|
+
responseReconciliation = undefined;
|
|
912
1317
|
}
|
|
913
|
-
updateUi(ctx);
|
|
914
1318
|
});
|
|
915
1319
|
pi.on("session_before_compact", (event) => {
|
|
916
1320
|
if (!compactionPlan)
|
|
@@ -922,22 +1326,35 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
922
1326
|
return result;
|
|
923
1327
|
return { compaction: result };
|
|
924
1328
|
});
|
|
925
|
-
pi.on("agent_before_settle", (_event, ctx) => {
|
|
926
|
-
if (!turnAcceptedForBackup)
|
|
1329
|
+
pi.on("agent_before_settle", async (_event, ctx) => {
|
|
1330
|
+
if (shuttingDown || !turnAcceptedForBackup)
|
|
927
1331
|
return;
|
|
928
1332
|
turnAcceptedForBackup = false;
|
|
929
1333
|
if (!backupPending)
|
|
930
1334
|
return;
|
|
931
1335
|
backupPending = false;
|
|
1336
|
+
const operationSignal = ctx.signal;
|
|
1337
|
+
const signal = operationSignal ? AbortSignal.any([backupLifetime.signal, operationSignal]) : backupLifetime.signal;
|
|
1338
|
+
const selected = runtime;
|
|
932
1339
|
try {
|
|
933
1340
|
if (existsSync(join(repositoryRoot, ".git"))) {
|
|
934
|
-
backupCurrentStateFlowFiles(repositoryRoot);
|
|
935
1341
|
const pushSessionId = sessionAddress(ctx).id;
|
|
936
1342
|
const pushCwd = ctx.cwd;
|
|
1343
|
+
// Pi 0.87 settles after its agent signal ends; waiting there would strand native Abort.
|
|
1344
|
+
const operation = backupCurrentStateFlowFiles(repositoryRoot, signal, operationSignal !== undefined);
|
|
1345
|
+
backupOperations.add(operation);
|
|
1346
|
+
try {
|
|
1347
|
+
await operation;
|
|
1348
|
+
}
|
|
1349
|
+
finally {
|
|
1350
|
+
backupOperations.delete(operation);
|
|
1351
|
+
}
|
|
1352
|
+
if (signal.aborted)
|
|
1353
|
+
return;
|
|
937
1354
|
startStateFlowBackupPush(repositoryRoot, (error) => {
|
|
938
1355
|
if (shuttingDown)
|
|
939
1356
|
return;
|
|
940
|
-
const message =
|
|
1357
|
+
const message = diagnosticText(error);
|
|
941
1358
|
const recorded = diagnosticWriter.recordBackupPushFailure(pushSessionId, pushCwd, message);
|
|
942
1359
|
if (pushFailureNotified)
|
|
943
1360
|
return;
|
|
@@ -949,15 +1366,20 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
949
1366
|
}
|
|
950
1367
|
}
|
|
951
1368
|
catch (error) {
|
|
952
|
-
|
|
1369
|
+
if (signal.aborted || runtime !== selected)
|
|
1370
|
+
return;
|
|
1371
|
+
const message = diagnosticText(error);
|
|
953
1372
|
recordDiagnostic(message, "publication-conflict", ctx);
|
|
954
|
-
ctx
|
|
1373
|
+
notifyProblem(ctx, error instanceof PublicationBusyError
|
|
1374
|
+
? "State Flow state saved; Git backup deferred: Pi supplied no cancellable settlement wait. A later accepted turn retries."
|
|
1375
|
+
: `State Flow state saved; Git backup failed: ${message}`, "warning");
|
|
955
1376
|
}
|
|
956
1377
|
});
|
|
957
1378
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
958
1379
|
if (telegramStartPending && !snapshot.config.enabled) {
|
|
959
1380
|
telegramStartPending = false;
|
|
960
|
-
startStateFlow(ctx);
|
|
1381
|
+
await startStateFlow(ctx);
|
|
1382
|
+
return;
|
|
961
1383
|
}
|
|
962
1384
|
if (!completedRunAccepted || compactionStopped || !snapshot.config.enabled || snapshot.meta.bootstrap
|
|
963
1385
|
|| compactionInFlight || !ctx.isIdle() || ctx.hasPendingMessages() || !runtime?.view
|
|
@@ -978,24 +1400,32 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
978
1400
|
ctx.compact({ customInstructions: compactionMarker, onComplete: finished, onError: finished });
|
|
979
1401
|
});
|
|
980
1402
|
});
|
|
981
|
-
pi.on("session_start", (event, ctx) => {
|
|
1403
|
+
pi.on("session_start", async (event, ctx) => {
|
|
982
1404
|
runAnchorTimestamp = undefined;
|
|
983
1405
|
rehydrationPhase = event.reason === "resume" ? "resume-bootstrap" : "new-bootstrap";
|
|
984
|
-
restoreActiveBranch(ctx, event.reason);
|
|
985
|
-
|
|
986
|
-
|
|
1406
|
+
await restoreActiveBranch(ctx, event.reason);
|
|
1407
|
+
if (!shuttingDown)
|
|
1408
|
+
void telegram.ensure();
|
|
987
1409
|
});
|
|
988
|
-
pi.on("session_tree", (_event, ctx) => {
|
|
1410
|
+
pi.on("session_tree", async (_event, ctx) => {
|
|
989
1411
|
runAnchorTimestamp = undefined;
|
|
990
|
-
restoreActiveBranch(ctx);
|
|
1412
|
+
await restoreActiveBranch(ctx);
|
|
991
1413
|
});
|
|
992
1414
|
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
993
1415
|
shuttingDown = true;
|
|
1416
|
+
const restoring = cancelBranchRestoration();
|
|
1417
|
+
const starting = cancelStartActivation();
|
|
1418
|
+
const stopping = cancelStopPersistence();
|
|
1419
|
+
backupLifetime.abort();
|
|
1420
|
+
cancelInferencePreparation();
|
|
1421
|
+
cancelResponseReconciliation();
|
|
1422
|
+
cancelSharedInspections();
|
|
994
1423
|
telegramStartPending = false;
|
|
995
1424
|
compactionStopped = true;
|
|
996
1425
|
completedRunAccepted = false;
|
|
997
1426
|
activeContext = undefined;
|
|
998
1427
|
telegram.dispose();
|
|
1428
|
+
await Promise.allSettled([...backupOperations, ...restorationOperations, ...[restoring, stopping, starting].filter((operation) => operation !== undefined)]);
|
|
999
1429
|
await awaitInFlightBackupPushes(repositoryRoot);
|
|
1000
1430
|
});
|
|
1001
1431
|
}
|