@llblab/pi-kit 0.26.0 → 0.27.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/BACKLOG.md +2 -2
- package/CHANGELOG.md +5 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +12 -5
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +9 -0
- package/node_modules/@llblab/pi-state-flow/README.md +116 -34
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +24 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +80 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +17 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +40 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +149 -298
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +58 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/operation.d.ts +37 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/operation.js +59 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.d.ts +31 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.js +117 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +7 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +28 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +6 -1
- 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 +14 -6
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/README.md +19 -9
- package/node_modules/@llblab/pi-state-flow/docs/agent-contract-relocation.md +4 -4
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +644 -91
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +116 -37
- package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +118 -21
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +68 -8
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +88 -14
- package/node_modules/@llblab/pi-state-flow/docs/performance.md +83 -66
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +391 -62
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +317 -62
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +85 -1
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +47 -0
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +161 -295
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +63 -0
- package/node_modules/@llblab/pi-state-flow/lib/operation.ts +75 -0
- package/node_modules/@llblab/pi-state-flow/lib/ownership.ts +120 -0
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +7 -8
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +1 -2
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +5 -1
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +14 -6
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +2 -2
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -0
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +10 -0
- package/node_modules/@llblab/pi-telegram/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +4 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +16 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +7 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +12 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +137 -83
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +26 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +57 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +109 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/model.js +2 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +3 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/status.js +31 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +4 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +17 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +48 -12
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +6 -5
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +2 -0
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +4 -0
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +17 -10
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +8 -2
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +135 -97
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +25 -0
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +140 -4
- package/node_modules/@llblab/pi-telegram/lib/model.ts +2 -4
- package/node_modules/@llblab/pi-telegram/lib/status.ts +30 -1
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +4 -4
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +21 -3
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +46 -13
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
|
@@ -3,18 +3,11 @@ import { StringEnum, Type } from "@earendil-works/pi-ai";
|
|
|
3
3
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { Text } from "@earendil-works/pi-tui";
|
|
6
|
-
import { randomUUID } from "node:crypto";
|
|
7
6
|
import { existsSync } from "node:fs";
|
|
8
7
|
import { isAbsolute, join, resolve } from "node:path";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
inspectRegisteredArtifactPaths,
|
|
13
|
-
ORDINARY_ARTIFACT_COMPILER,
|
|
14
|
-
sameArtifactSourceFingerprint,
|
|
15
|
-
type ArtifactInvalidationRequest,
|
|
16
|
-
} from "./artifact.ts";
|
|
17
|
-
import { hasCompactionSizedTranscript, planStateFlowCompaction, shouldRequestStateFlowCompaction, stateFlowCompactionResult, type StateFlowCompactionPlan } from "./compaction.ts";
|
|
8
|
+
import { ArtifactAcquisitionState, missingArtifactRemovals } from "./acquisition.ts";
|
|
9
|
+
import { inspectRegisteredArtifactPaths, sameArtifactSourceFingerprint } from "./artifact.ts";
|
|
10
|
+
import { hasCompactionSizedTranscript, planStateFlowCompaction, shouldRequestStateFlowCompaction, StateFlowCompactionRequests } from "./compaction.ts";
|
|
18
11
|
import { inactiveModeFor, loadStateFlowConfig } from "./config.ts";
|
|
19
12
|
import { ContextProjection, contextView, createPassiveContinuation, currentRunTrajectory, passiveContinuationMessages, projectSystemProtocol, runtimeContextHead, syntheticUser, type PassiveContinuation } from "./context.ts";
|
|
20
13
|
import { readNativeSessionHeader } from "./continuation.ts";
|
|
@@ -25,11 +18,12 @@ import {
|
|
|
25
18
|
type SessionAddress,
|
|
26
19
|
} from "./durable.ts";
|
|
27
20
|
import { completeRun, deactivateEpisode, prepareRun, resumeEpisode, startEpisode } from "./episode.ts";
|
|
28
|
-
import { awaitInFlightBackupPushes, backupCurrentStateFlowFiles, startStateFlowBackupPush } from "./git.ts";
|
|
21
|
+
import { awaitInFlightBackupPushes, backupCurrentStateFlowFiles, SettledTurnBackup, startStateFlowBackupPush } from "./git.ts";
|
|
29
22
|
import { projectRecentTransitionsWithLimit } from "./history.ts";
|
|
30
|
-
import { isObject, presentationJson, sameJson
|
|
23
|
+
import { isObject, presentationJson, sameJson } from "./json.ts";
|
|
31
24
|
import { StateFlowDiagnosticWriter, stateFlowLogPath, type DiagnosticExtras, type StateFlowDiagnosticCategory } from "./logging.ts";
|
|
32
25
|
import { assistantToolCallCount, conciseDiagnostic, diagnosticText, finalizedAssistantResponse, formatPatchStateArguments, PASSIVE_MEMORY_PROTOCOL, separatedFailure, separatedOutput, stateFlowProtocol } from "./protocol.ts";
|
|
26
|
+
import { OwnedOperationSlot, RenewableLifetime, type OwnedOperation } from "./operation.ts";
|
|
33
27
|
import { readProjectedState, readStatePath } from "./query.ts";
|
|
34
28
|
import { selectedBoundaryFailure, selectRetainedCheckpoint, waitForRecovery } from "./recovery.ts";
|
|
35
29
|
import type { RehydrationPhase } from "./rehydration.ts";
|
|
@@ -37,7 +31,7 @@ import { TemporalRuntime, type RuntimePublication } from "./runtime.ts";
|
|
|
37
31
|
import { discoverSnapshotData, findAssistantToolBatch, findBranchPolicy, findPassiveStopBoundary, hasPendingFork, hasPriorConversation, hasUncheckpointedConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.ts";
|
|
38
32
|
import { hasCompiledSkillArtifact, hashSkillSource, registeredSkillResolver, SkillReadTracker, type SuccessfulSkillRead } from "./skills.ts";
|
|
39
33
|
import { HistoryBoundaryExpiredError, emptySnapshot, migrationFailure, preRuntimeCheckpoint, type InactiveMode, type RetainedBoundaryCheckpoint, type RetainedPiCheckpoint, type Snapshot, type StateFlowMode } from "./snapshot.ts";
|
|
40
|
-
import { emptyState, projectStateForModel, type AtomicScopePatches, type SemanticState, type ModelState, type
|
|
34
|
+
import { emptyState, projectStateForModel, type AtomicScopePatches, type SemanticState, type ModelState, type ScopedStates, type StateScope } from "./state.ts";
|
|
41
35
|
import { compactStatus, detailedStatus, STATUS_KEY, type StatusDiagnostics } from "./status.ts";
|
|
42
36
|
import { PublicationBusyError } from "./storage.ts";
|
|
43
37
|
import { createStateFlowTelegramAdapter, type StateFlowTelegramControlResult, type StateFlowTelegramInspection, type StateFlowTelegramLoader, type StateFlowTelegramScope } from "./telegram.ts";
|
|
@@ -59,14 +53,14 @@ export const PATCH_STATE_TOOL_NAME = "patch_state";
|
|
|
59
53
|
export const READ_STATE_TOOL_NAME = "read_state";
|
|
60
54
|
const PASSIVE_STOP_ENTRY_TYPE = "state-flow-passive-stop";
|
|
61
55
|
|
|
62
|
-
interface InferencePreparation {
|
|
56
|
+
interface InferencePreparation extends OwnedOperation {
|
|
63
57
|
readonly controller: AbortController;
|
|
64
58
|
readonly prompt?: string;
|
|
65
59
|
accepted?: boolean;
|
|
66
60
|
operation?: Promise<void>;
|
|
67
61
|
}
|
|
68
62
|
|
|
69
|
-
interface BranchRestoration {
|
|
63
|
+
interface BranchRestoration extends OwnedOperation {
|
|
70
64
|
readonly controller: AbortController;
|
|
71
65
|
/** Memory acceptance is independent of the requested session mode. */
|
|
72
66
|
awaitingAcceptance: boolean;
|
|
@@ -74,6 +68,15 @@ interface BranchRestoration {
|
|
|
74
68
|
operation?: Promise<void>;
|
|
75
69
|
}
|
|
76
70
|
|
|
71
|
+
interface ModeOperation extends OwnedOperation {
|
|
72
|
+
operation?: Promise<StateFlowTelegramControlResult>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface InactivePersistence extends ModeOperation {
|
|
76
|
+
/** The native mode marker is recorded; later inactive choices start a new persistence. */
|
|
77
|
+
published?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
77
80
|
type BranchSelection =
|
|
78
81
|
| { kind: "settled" }
|
|
79
82
|
| { kind: "current"; mode: InactiveMode }
|
|
@@ -93,50 +96,49 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
93
96
|
let selectedHistoryExpired = false;
|
|
94
97
|
let modePersistenceError: string | undefined;
|
|
95
98
|
/** One pending inactive-mode persistence; later inactive choices coalesce until it publishes. */
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
const inactivePersistence = new OwnedOperationSlot<InactivePersistence>();
|
|
100
|
+
const startActivation = new OwnedOperationSlot<ModeOperation>();
|
|
98
101
|
let forkInitialization = false;
|
|
99
|
-
|
|
102
|
+
const branchRestoration = new OwnedOperationSlot<BranchRestoration>();
|
|
100
103
|
let deferredBranch: { reason: unknown; owner: string; cwd: string } | undefined;
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
let memoryToolLifetime = new AbortController();
|
|
104
|
+
const responseReconciliation = new OwnedOperationSlot<OwnedOperation>();
|
|
105
|
+
const sharedInspectionLifetime = new RenewableLifetime();
|
|
106
|
+
const memoryToolLifetime = new RenewableLifetime();
|
|
105
107
|
let completedRunAccepted = false;
|
|
106
|
-
|
|
107
|
-
let compactionPlan: StateFlowCompactionPlan | undefined;
|
|
108
|
-
let compactionInFlight = false;
|
|
109
|
-
let compactionStopped = false;
|
|
110
|
-
const compactionPrefix = `state-flow-boundary:${randomUUID()}:`;
|
|
111
|
-
let compactionMarker: string | undefined;
|
|
108
|
+
const compactionRequests = new StateFlowCompactionRequests();
|
|
112
109
|
let passiveContinuation: PassiveContinuation | undefined;
|
|
113
110
|
let bootstrapContinuation: PassiveContinuation | undefined;
|
|
114
111
|
const contextProjection = new ContextProjection();
|
|
115
|
-
|
|
112
|
+
const inferencePreparation = new OwnedOperationSlot<InferencePreparation>();
|
|
116
113
|
let runAnchorTimestamp: number | undefined;
|
|
117
114
|
let runtime: TemporalRuntime | undefined;
|
|
118
115
|
let activeContext: ExtensionContext | undefined;
|
|
119
116
|
let rehydrationPhase: RehydrationPhase | undefined;
|
|
120
117
|
const repositoryRoot = resolve(options.repositoryRoot ?? config.directory);
|
|
121
118
|
const diagnosticWriter = new StateFlowDiagnosticWriter(config.logging, stateFlowLogPath(agentDir), repositoryRoot, (message) => notifyActiveContext(message));
|
|
122
|
-
|
|
123
|
-
let backupLifetime = new AbortController();
|
|
124
|
-
const backupOperations = new Set<Promise<string | undefined>>();
|
|
119
|
+
const backup = new SettledTurnBackup();
|
|
125
120
|
let shuttingDown = false;
|
|
126
|
-
let pushFailureNotified = false;
|
|
127
121
|
const skillReads = new SkillReadTracker(hashSkillSource, (path) => activeContext
|
|
128
122
|
? registeredSkillResolver(activeContext.cwd, pi.getCommands())(path)
|
|
129
123
|
: undefined);
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
let artifactHints: Record<string, string> = {};
|
|
133
|
-
const SOURCE_CHANGED_HINT = "Source changed since this artifact was compiled. Read and recompile it before relying on it.";
|
|
124
|
+
const acquisition = new ArtifactAcquisitionState();
|
|
125
|
+
const artifactReads = acquisition.reads;
|
|
134
126
|
let telegramStartPending = false;
|
|
135
127
|
|
|
136
128
|
function sessionAddress(ctx: ExtensionContext): SessionAddress {
|
|
137
129
|
return resolveSessionAddress(ctx.sessionManager.getSessionFile(), ctx.sessionManager.getSessionId(), ctx.sessionManager.getHeader()?.timestamp);
|
|
138
130
|
}
|
|
139
131
|
|
|
132
|
+
/** Native session identity captured before an await; any change supersedes the waiting operation. */
|
|
133
|
+
function sessionIdentity(ctx: ExtensionContext): () => boolean {
|
|
134
|
+
const cwd = ctx.cwd;
|
|
135
|
+
const owner = ctx.sessionManager.getSessionId();
|
|
136
|
+
const file = ctx.sessionManager.getSessionFile();
|
|
137
|
+
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
138
|
+
return () => ctx.cwd === cwd && ctx.sessionManager.getSessionId() === owner && ctx.sessionManager.getSessionFile() === file
|
|
139
|
+
&& ctx.sessionManager.getHeader()?.timestamp === timestamp;
|
|
140
|
+
}
|
|
141
|
+
|
|
140
142
|
function notifyProblem(ctx: ExtensionContext, message: string, level: "warning" | "error"): void {
|
|
141
143
|
ctx.ui.notify(conciseDiagnostic(message), level);
|
|
142
144
|
}
|
|
@@ -154,7 +156,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
function projectModelState(state: SemanticState): SemanticState {
|
|
157
|
-
return projectStateForModel(state,
|
|
159
|
+
return projectStateForModel(state, acquisition.hints);
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
function assertSelectedBranchAvailable(): void {
|
|
@@ -202,115 +204,32 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
202
204
|
ctx.ui.setStatus(STATUS_KEY, compactStatus(snapshot, scopeRevisions(), (color, text) => ctx.ui.theme.fg(color, text)));
|
|
203
205
|
}
|
|
204
206
|
|
|
205
|
-
function cancelInactivePersistence(): Promise<StateFlowTelegramControlResult> | undefined {
|
|
206
|
-
const pending = inactivePersistence;
|
|
207
|
-
pending?.controller.abort();
|
|
208
|
-
inactivePersistence = undefined;
|
|
209
|
-
return pending?.operation;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function cancelStartActivation(): Promise<StateFlowTelegramControlResult> | undefined {
|
|
213
|
-
const pending = startActivation;
|
|
214
|
-
pending?.controller.abort();
|
|
215
|
-
startActivation = undefined;
|
|
216
|
-
return pending?.operation;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function cancelBranchRestoration(): Promise<void> | undefined {
|
|
220
|
-
const pending = branchRestoration;
|
|
221
|
-
pending?.controller.abort();
|
|
222
|
-
branchRestoration = undefined;
|
|
223
|
-
return pending?.operation;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function cancelResponseReconciliation(): void {
|
|
227
|
-
responseReconciliation?.abort();
|
|
228
|
-
responseReconciliation = undefined;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
207
|
function cancelSharedInspections(): void {
|
|
232
|
-
sharedInspectionLifetime.
|
|
233
|
-
sharedInspectionLifetime = new AbortController();
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function cancelBackupWork(): void {
|
|
237
|
-
backupLifetime.abort();
|
|
238
|
-
backupLifetime = new AbortController();
|
|
239
|
-
backupPending = false;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function cancelInferencePreparation(): void {
|
|
243
|
-
inferencePreparation?.controller.abort();
|
|
244
|
-
inferencePreparation = undefined;
|
|
208
|
+
sharedInspectionLifetime.renew(new Error("State Flow inspection was cancelled; select the scope again"));
|
|
245
209
|
}
|
|
246
210
|
|
|
247
211
|
function clearRunTransient(): void {
|
|
248
|
-
|
|
249
|
-
|
|
212
|
+
inferencePreparation.cancel();
|
|
213
|
+
responseReconciliation.cancel();
|
|
250
214
|
cancelSharedInspections();
|
|
251
215
|
completedRunAccepted = false;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
compactionInFlight = false;
|
|
216
|
+
backup.clearTurn();
|
|
217
|
+
compactionRequests.clear();
|
|
255
218
|
skillReads.clear();
|
|
256
219
|
artifactReads.clear();
|
|
257
220
|
}
|
|
258
221
|
|
|
259
222
|
function deferInferencePreparation(prompt?: string): void {
|
|
260
|
-
|
|
261
|
-
inferencePreparation
|
|
262
|
-
|
|
263
|
-
artifactReads.setCandidates([]);
|
|
223
|
+
inferencePreparation.cancel();
|
|
224
|
+
inferencePreparation.claim({ controller: new AbortController(), prompt });
|
|
225
|
+
acquisition.clearInvalidations();
|
|
264
226
|
}
|
|
265
227
|
|
|
266
228
|
function refreshArtifactHints(): void {
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
artifactInvalidations = [];
|
|
270
|
-
artifactReads.setCandidates([]);
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
const paths = new Set<string>();
|
|
274
|
-
for (const scope of ["global", "cwd", "session"] as const) for (const path of Object.keys(scopeStates[scope].artifacts)) paths.add(path);
|
|
275
|
-
const observations = new Map(inspectRegisteredArtifactPaths(paths).map((observation) => [observation.path, observation]));
|
|
276
|
-
const nextHints: Record<string, string> = {};
|
|
277
|
-
const nextInvalidations = new Map<string, ArtifactInvalidationRequest>();
|
|
278
|
-
for (const scope of ["global", "cwd", "session"] as const) {
|
|
279
|
-
const provenance = runtime.artifactProvenance(scope);
|
|
280
|
-
for (const [path, metadata] of Object.entries(scopeStates[scope].artifacts)) {
|
|
281
|
-
delete nextHints[path];
|
|
282
|
-
nextInvalidations.delete(path);
|
|
283
|
-
if (metadata.kind === "skill") continue;
|
|
284
|
-
const observed = observations.get(path);
|
|
285
|
-
if (observed?.kind !== "present") continue;
|
|
286
|
-
const need = classifyArtifactCompilationNeed({ path, scope, sourceFingerprint: observed.fingerprint }, metadata, ORDINARY_ARTIFACT_COMPILER, false, provenance[path]);
|
|
287
|
-
if (need.kind !== "requires-compilation") continue;
|
|
288
|
-
if (need.reason === "source-changed") nextHints[path] = SOURCE_CHANGED_HINT;
|
|
289
|
-
nextInvalidations.set(path, { path, scope, reason: need.reason });
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
artifactHints = nextHints;
|
|
293
|
-
artifactInvalidations = [...nextInvalidations.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
294
|
-
artifactReads.setCandidates(artifactInvalidations);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function missingArtifactRemovals(states: ScopedSemanticStates): AtomicScopePatches {
|
|
298
|
-
const owners = new Map<string, StateScope[]>();
|
|
299
|
-
for (const scope of ["global", "cwd", "session"] as const) for (const path of Object.keys(states[scope].artifacts ?? {})) {
|
|
300
|
-
owners.set(path, [...owners.get(path) ?? [], scope]);
|
|
301
|
-
}
|
|
302
|
-
const removals: AtomicScopePatches = {};
|
|
303
|
-
for (const observation of inspectRegisteredArtifactPaths(owners.keys())) {
|
|
304
|
-
if (observation.kind !== "missing") continue;
|
|
305
|
-
for (const scope of owners.get(observation.path) ?? []) {
|
|
306
|
-
const artifacts = (removals[scope]?.artifacts ?? {}) as JsonObject;
|
|
307
|
-
removals[scope] = { ...(removals[scope] ?? {}), artifacts: { ...artifacts, [observation.path]: null } };
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
return removals;
|
|
229
|
+
if (runtime?.view) acquisition.refresh(scopeStates, (scope) => runtime!.artifactProvenance(scope));
|
|
230
|
+
else acquisition.reset();
|
|
311
231
|
}
|
|
312
232
|
|
|
313
|
-
|
|
314
233
|
function installScopeStates(): void {
|
|
315
234
|
scopeStates = runtime?.view ? runtime.states() : { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
316
235
|
effectiveState = runtime?.view ? runtime.readView() : {};
|
|
@@ -362,15 +281,11 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
362
281
|
: active.filter((name) => !owned.includes(name)));
|
|
363
282
|
}
|
|
364
283
|
|
|
365
|
-
|
|
284
|
+
/** Accepted canonical publication establishes runtime authority and makes the settled-turn Git backup due. */
|
|
285
|
+
function recordPublication(): void {
|
|
366
286
|
// A passive view is not runtime authority; only accepted canonical publication establishes it.
|
|
367
287
|
branchStartsWithoutRuntime = false;
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/** Record accepted lifecycle persistence; Git backup is scheduled only after an accepted turn settles. */
|
|
372
|
-
function recordPolicyPublication(publication: RuntimePublication | undefined, ctx: ExtensionContext): void {
|
|
373
|
-
if (publication) recordPublication(publication, ctx);
|
|
288
|
+
backup.markPublished();
|
|
374
289
|
}
|
|
375
290
|
|
|
376
291
|
function skillReadIsCurrent(read: SuccessfulSkillRead): boolean {
|
|
@@ -394,11 +309,9 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
394
309
|
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.`;
|
|
395
310
|
}
|
|
396
311
|
|
|
397
|
-
function clearAcceptedAcquisitions(acquiredArtifactPaths
|
|
398
|
-
|
|
399
|
-
artifactReads.setCandidates(artifactInvalidations);
|
|
312
|
+
function clearAcceptedAcquisitions(acquiredArtifactPaths?: ReadonlySet<string>): void {
|
|
313
|
+
acquisition.acceptAcquired(acquiredArtifactPaths);
|
|
400
314
|
dropCurrentSkillReads();
|
|
401
|
-
artifactReads.clear();
|
|
402
315
|
}
|
|
403
316
|
|
|
404
317
|
async function prepareInference(pending: InferencePreparation, selected: TemporalRuntime | undefined, ctx: ExtensionContext, operationSignal: AbortSignal): Promise<void> {
|
|
@@ -407,7 +320,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
407
320
|
if (!selected?.view) throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
408
321
|
await selected.withPatchTransaction((transaction) => {
|
|
409
322
|
signal.throwIfAborted();
|
|
410
|
-
if (runtime !== selected || inferencePreparation
|
|
323
|
+
if (runtime !== selected || !inferencePreparation.owns(pending) || !isActive() || shuttingDown) {
|
|
411
324
|
throw new Error("State Flow inference selection changed while awaiting publication");
|
|
412
325
|
}
|
|
413
326
|
assertPublicationAvailable();
|
|
@@ -430,13 +343,13 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
430
343
|
snapshot = nextSnapshot;
|
|
431
344
|
installScopeStates();
|
|
432
345
|
if (rotatesRun && rehydrationPhase !== "new-bootstrap" && rehydrationPhase !== "resume-bootstrap") rehydrationPhase = "step";
|
|
433
|
-
if (publication?.changed) recordPublication(
|
|
346
|
+
if (publication?.changed) recordPublication();
|
|
434
347
|
if (pending.prompt !== undefined || publication?.changed) appendCheckpoint();
|
|
435
348
|
if (Object.keys(removals).length > 0) clearAcceptedAcquisitions();
|
|
436
349
|
updateUi(ctx);
|
|
437
350
|
}, signal);
|
|
438
351
|
} catch (error) {
|
|
439
|
-
if (signal.aborted || runtime !== selected || inferencePreparation
|
|
352
|
+
if (signal.aborted || runtime !== selected || !inferencePreparation.owns(pending) || shuttingDown) return;
|
|
440
353
|
// Pi reports context-hook exceptions and continues. Abort through its public port rather than infer from stale preparation.
|
|
441
354
|
ctx.abort();
|
|
442
355
|
const cause = diagnosticText(error);
|
|
@@ -448,27 +361,22 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
448
361
|
}
|
|
449
362
|
|
|
450
363
|
|
|
451
|
-
function currentRehydrationPhase(): RehydrationPhase | undefined {
|
|
452
|
-
return rehydrationPhase;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
364
|
function statusDiagnostics(ctx: ExtensionContext): StatusDiagnostics {
|
|
456
365
|
const cwd = ctx.cwd;
|
|
457
|
-
const diagnosticStates = scopeStates;
|
|
458
366
|
const view = runtime?.view;
|
|
459
367
|
const diagnosticRecent = runtime?.recent() ?? [];
|
|
460
368
|
const durableStateError = snapshot.meta.validation?.attempt === 0 ? snapshot.meta.validation.error
|
|
461
369
|
: view ? undefined : "no temporal runtime is selected on this branch";
|
|
462
370
|
|
|
463
371
|
const staleArtifacts: StatusDiagnostics["staleArtifacts"] = durableStateError === undefined
|
|
464
|
-
?
|
|
372
|
+
? acquisition.invalidations.map(({ path, scope, reason }) => ({ scope: scope ?? "global", path, reason }))
|
|
465
373
|
: [];
|
|
466
374
|
const session = sessionAddress(ctx);
|
|
467
375
|
return {
|
|
468
376
|
repositoryRoot,
|
|
469
377
|
cwdScopeKey: cwdScopeKey(cwd),
|
|
470
378
|
sessionScopeKey: sessionScopeKey(session.key),
|
|
471
|
-
scopeStates
|
|
379
|
+
scopeStates,
|
|
472
380
|
effectiveState,
|
|
473
381
|
recent: diagnosticRecent,
|
|
474
382
|
historyLimit: config.historyLimit,
|
|
@@ -495,19 +403,17 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
495
403
|
/** Select the active native branch under one owned restoration lifetime; only current accepted work installs memory. */
|
|
496
404
|
function restoreActiveBranch(ctx: ExtensionContext, sessionStartReason?: unknown, notifyRecovery = true, startOwner?: AbortController, requestedMode?: InactiveMode): Promise<void> {
|
|
497
405
|
contextProjection.reset();
|
|
498
|
-
memoryToolLifetime.
|
|
499
|
-
|
|
500
|
-
cancelBranchRestoration();
|
|
406
|
+
memoryToolLifetime.renew();
|
|
407
|
+
branchRestoration.cancel();
|
|
501
408
|
// Start-owned attachment/fork recovery keeps its owner; only accepted Start cancels Stop.
|
|
502
409
|
if (!startOwner) {
|
|
503
|
-
|
|
504
|
-
|
|
410
|
+
startActivation.cancel();
|
|
411
|
+
inactivePersistence.cancel();
|
|
505
412
|
}
|
|
506
413
|
clearRunTransient();
|
|
507
414
|
passiveContinuation = undefined;
|
|
508
415
|
bootstrapContinuation = undefined;
|
|
509
|
-
|
|
510
|
-
artifactReads.setCandidates([]);
|
|
416
|
+
acquisition.clearInvalidations();
|
|
511
417
|
telegramStartPending = false;
|
|
512
418
|
activeContext = ctx;
|
|
513
419
|
if (!startOwner) {
|
|
@@ -533,7 +439,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
533
439
|
// Off needs only policy; unavailable native evidence grants no memory authority.
|
|
534
440
|
}
|
|
535
441
|
if (mode === "off") {
|
|
536
|
-
|
|
442
|
+
backup.cancel();
|
|
537
443
|
const owner = sessionAddress(ctx).key;
|
|
538
444
|
const reason = pendingFork || (deferredBranch?.owner === owner && deferredBranch.cwd === ctx.cwd && deferredBranch.reason === "fork"
|
|
539
445
|
&& retainsPhysicalSessionProjection(sessionStartReason)) ? "fork" : sessionStartReason;
|
|
@@ -546,7 +452,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
546
452
|
modePersistenceError = persistenceError;
|
|
547
453
|
forkInitialization = reason === "fork";
|
|
548
454
|
installScopeStates();
|
|
549
|
-
|
|
455
|
+
acquisition.reset();
|
|
550
456
|
if (adoptDefault) appendCheckpoint();
|
|
551
457
|
syncStateFlowTools();
|
|
552
458
|
updateUi(ctx);
|
|
@@ -621,13 +527,9 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
621
527
|
awaitingAcceptance: selection.kind !== "settled" && selection.kind !== "current",
|
|
622
528
|
...(requestedMode === undefined ? {} : { requestedMode }),
|
|
623
529
|
};
|
|
624
|
-
branchRestoration
|
|
625
|
-
|
|
626
|
-
pending
|
|
627
|
-
restorationOperations.add(operation);
|
|
628
|
-
const finished = () => { restorationOperations.delete(operation); };
|
|
629
|
-
void operation.then(finished, finished);
|
|
630
|
-
return operation;
|
|
530
|
+
branchRestoration.claim(pending);
|
|
531
|
+
// attachBranch releases its own ownership; settlement only drains it for shutdown.
|
|
532
|
+
return branchRestoration.track(pending, attachBranch(ctx, pending, selection, sessionStartReason, notifyRecovery, skipped), false);
|
|
631
533
|
}
|
|
632
534
|
|
|
633
535
|
async function attachBranch(
|
|
@@ -637,26 +539,23 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
637
539
|
const placeholder = runtime;
|
|
638
540
|
let selected = runtime;
|
|
639
541
|
const owner = ctx.sessionManager.getSessionId();
|
|
640
|
-
const
|
|
641
|
-
const file = ctx.sessionManager.getSessionFile();
|
|
642
|
-
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
542
|
+
const sameSession = sessionIdentity(ctx);
|
|
643
543
|
const signal = AbortSignal.any([pending.controller.signal, ...(ctx.signal ? [ctx.signal] : [])]);
|
|
644
|
-
const isCurrent = () => branchRestoration
|
|
645
|
-
&&
|
|
646
|
-
&& ctx.sessionManager.getHeader()?.timestamp === timestamp;
|
|
544
|
+
const isCurrent = () => branchRestoration.owns(pending) && !pending.controller.signal.aborted && !shuttingDown && runtime === selected
|
|
545
|
+
&& sameSession();
|
|
647
546
|
const assertCurrent = () => {
|
|
648
547
|
signal.throwIfAborted();
|
|
649
548
|
if (!isCurrent()) throw new Error("State Flow branch selection changed while awaiting publication");
|
|
650
549
|
};
|
|
651
550
|
let accepted = false;
|
|
652
551
|
// Install accepted memory before native writes; later ancillary failures cannot revert or replay it.
|
|
653
|
-
const accept = (candidate: TemporalRuntime, next: Snapshot,
|
|
552
|
+
const accept = (candidate: TemporalRuntime, next: Snapshot, nativeWrites: () => void): void => {
|
|
654
553
|
accepted = true;
|
|
655
554
|
pending.awaitingAcceptance = false;
|
|
656
555
|
runtime = selected = candidate;
|
|
657
556
|
snapshot = next;
|
|
658
557
|
installScopeStates();
|
|
659
|
-
|
|
558
|
+
recordPublication();
|
|
660
559
|
let failure: unknown;
|
|
661
560
|
try { nativeWrites(); } catch (error) { failure = error; }
|
|
662
561
|
settleSelection(ctx, reason, notifyRecovery, skipped, true);
|
|
@@ -683,16 +582,17 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
683
582
|
if (restored.config.mode === "active" && restored.meta.specification === undefined
|
|
684
583
|
&& retainsPhysicalSessionProjection(reason) && hasUncheckpointedConversation(ctx.sessionManager.getBranch())) restored.meta.bootstrap = true;
|
|
685
584
|
const next = pending.requestedMode ? deactivateEpisode(restored, pending.requestedMode) : restored;
|
|
686
|
-
|
|
585
|
+
publish(next);
|
|
586
|
+
accept(candidate, next, appendCheckpoint);
|
|
687
587
|
}, signal);
|
|
688
588
|
} else if (selection.kind === "fork") {
|
|
689
589
|
await candidate.withForkTransaction(selection.source, selection.checkpoint, (child, publish) => {
|
|
690
590
|
assertCurrent();
|
|
691
591
|
// Mode is selected independently of creating the child's private memory.
|
|
692
592
|
const next = pending.requestedMode ? deactivateEpisode(child, pending.requestedMode) : child;
|
|
693
|
-
|
|
593
|
+
publish(next);
|
|
694
594
|
forkInitialization = false;
|
|
695
|
-
accept(candidate, next,
|
|
595
|
+
accept(candidate, next, () => {
|
|
696
596
|
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { reset: true, owner });
|
|
697
597
|
appendCheckpoint();
|
|
698
598
|
});
|
|
@@ -706,7 +606,8 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
706
606
|
if (current || discovery.candidates.length > 0 || discovery.errors.length > 0) throw new Error("Existing session runtime requires retained-boundary restoration");
|
|
707
607
|
const activated = startEpisode(hasPriorConversation(branch));
|
|
708
608
|
const next = pending.requestedMode ? deactivateEpisode(activated, pending.requestedMode) : activated;
|
|
709
|
-
|
|
609
|
+
publish(next);
|
|
610
|
+
accept(candidate, next, appendCheckpoint);
|
|
710
611
|
}, signal, true);
|
|
711
612
|
}
|
|
712
613
|
} catch (error) {
|
|
@@ -740,7 +641,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
740
641
|
installScopeStates();
|
|
741
642
|
} finally {
|
|
742
643
|
pending.awaitingAcceptance = false;
|
|
743
|
-
|
|
644
|
+
branchRestoration.release(pending);
|
|
744
645
|
}
|
|
745
646
|
}
|
|
746
647
|
|
|
@@ -790,7 +691,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
790
691
|
pi.registerTool({
|
|
791
692
|
name: READ_STATE_TOOL_NAME,
|
|
792
693
|
label: "Read State",
|
|
793
|
-
description: "Read exact State Flow values or historical semantic patches with one path or an ordered path list. Unscoped semantic paths read the effective overlay; effective makes that overlay explicit, while global, cwd, and session select ownership. Array selectors support zero-based indices and half-open [start..end] ranges. Projection value returns semantic data; keys returns minimal structure; patch intersects the selected path at its boundary.",
|
|
694
|
+
description: "Read exact State Flow values or historical semantic patches with one path or an ordered path list. Unscoped semantic paths read the effective overlay; effective makes that overlay explicit, while global, cwd, and session select ownership. Array selectors support zero-based indices and half-open [start..end] ranges. Projection value returns semantic data; keys returns minimal structure; patch intersects the selected path at its boundary. Path object keys must be ASCII identifiers ([A-Za-z_$][A-Za-z0-9_$-]*); non-ASCII keys cannot be addressed.",
|
|
794
695
|
promptSnippet: "Read exact state values, structures, or historical semantic patches",
|
|
795
696
|
parameters: Type.Object({
|
|
796
697
|
path: Type.Optional(Type.String({ description: "One semantic path; unscoped paths use effective, explicit roots may use effective/global/cwd/session and historical [N], and arrays support half-open [start..end] ranges" })),
|
|
@@ -832,7 +733,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
832
733
|
pi.registerTool({
|
|
833
734
|
name: PATCH_STATE_TOOL_NAME,
|
|
834
735
|
label: "Patch State",
|
|
835
|
-
description: "The sole State Flow semantic mutation protocol. Supply one or more global, cwd, or session patches; all supplied scopes commit atomically. This call must be the only State Flow barrier in its assistant response.",
|
|
736
|
+
description: "The sole State Flow semantic mutation protocol. Supply one or more global, cwd, or session patches; all supplied scopes commit atomically. This call must be the only State Flow barrier in its assistant response. Deleting an intent also deletes same-scope working/lazy keys its {\"$ref\"} values own unless another intent refs them. Name keys in ASCII ([A-Za-z_$][A-Za-z0-9_$-]*) so read paths and refs resolve; values may use any language.",
|
|
836
737
|
promptSnippet: "Atomically patch one or more global/cwd/session scopes",
|
|
837
738
|
promptGuidelines: [
|
|
838
739
|
"Use patch_state only for material durable semantic changes; ordinary answers need no finalization call.",
|
|
@@ -901,12 +802,12 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
901
802
|
snapshot = nextSnapshot;
|
|
902
803
|
installScopeStates();
|
|
903
804
|
if (publication?.changed) {
|
|
904
|
-
recordPublication(
|
|
805
|
+
recordPublication();
|
|
905
806
|
appendCheckpoint();
|
|
906
807
|
}
|
|
907
808
|
clearAcceptedAcquisitions(new Set(acquiredArtifacts.map(({ path }) => path)));
|
|
908
809
|
updateUi(ctx);
|
|
909
|
-
const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches,
|
|
810
|
+
const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, acquisition.hints);
|
|
910
811
|
const acknowledgement = changed
|
|
911
812
|
? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
|
|
912
813
|
: "\nState already current.";
|
|
@@ -931,30 +832,20 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
931
832
|
|
|
932
833
|
function startStateFlow(ctx: ExtensionContext): Promise<StateFlowTelegramControlResult> {
|
|
933
834
|
if (shuttingDown) return Promise.resolve({ ok: false, message: "State Flow is shutting down" });
|
|
934
|
-
if (startActivation?.operation) return startActivation.operation;
|
|
835
|
+
if (startActivation.current?.operation) return startActivation.current.operation;
|
|
935
836
|
telegramStartPending = false;
|
|
936
|
-
if (activeContext && !branchRestoration && isActive()) {
|
|
837
|
+
if (activeContext && !branchRestoration.current && isActive()) {
|
|
937
838
|
return Promise.resolve({ ok: true, message: "State Flow is already active", signal: sharedInspectionLifetime.signal });
|
|
938
839
|
}
|
|
939
|
-
const pending
|
|
940
|
-
startActivation
|
|
941
|
-
const operation = runStart(ctx, pending.controller);
|
|
942
|
-
pending.operation = operation;
|
|
943
|
-
const finished = () => { if (startActivation === pending) startActivation = undefined; };
|
|
944
|
-
void operation.then(finished, finished);
|
|
945
|
-
return operation;
|
|
840
|
+
const pending = startActivation.claim({ controller: new AbortController() });
|
|
841
|
+
return startActivation.track(pending, runStart(ctx, pending.controller));
|
|
946
842
|
}
|
|
947
843
|
|
|
948
844
|
/** Await restoration-owned attachment and exact-source fork recovery before current-head activation. */
|
|
949
845
|
async function runStart(ctx: ExtensionContext, pending: AbortController): Promise<StateFlowTelegramControlResult> {
|
|
950
|
-
const
|
|
951
|
-
const cwd = ctx.cwd;
|
|
952
|
-
const file = ctx.sessionManager.getSessionFile();
|
|
953
|
-
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
846
|
+
const sameSession = sessionIdentity(ctx);
|
|
954
847
|
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
955
|
-
const isOwner = () => startActivation?.controller === pending && !signal.aborted && !shuttingDown
|
|
956
|
-
&& ctx.cwd === cwd && ctx.sessionManager.getSessionId() === owner && ctx.sessionManager.getSessionFile() === file
|
|
957
|
-
&& ctx.sessionManager.getHeader()?.timestamp === timestamp;
|
|
848
|
+
const isOwner = () => startActivation.current?.controller === pending && !signal.aborted && !shuttingDown && sameSession();
|
|
958
849
|
const superseded = (): StateFlowTelegramControlResult => {
|
|
959
850
|
pending.abort();
|
|
960
851
|
return { ok: false, message: "State Flow activation was superseded", signal: pending.signal };
|
|
@@ -962,12 +853,12 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
962
853
|
try {
|
|
963
854
|
if (deferredBranch?.reason === "fork") await waitForRecovery(restoreActiveBranch(ctx, "fork", false, pending), signal);
|
|
964
855
|
else if (!activeContext) await waitForRecovery(restoreActiveBranch(ctx, undefined, false, pending), signal);
|
|
965
|
-
else if (branchRestoration?.operation) await waitForRecovery(branchRestoration.operation, signal);
|
|
856
|
+
else if (branchRestoration.current?.operation) await waitForRecovery(branchRestoration.current.operation, signal);
|
|
966
857
|
if (!isOwner()) return superseded();
|
|
967
858
|
if (isActive()) return { ok: true, message: "State Flow is already active", signal: sharedInspectionLifetime.signal };
|
|
968
859
|
if (forkInitialization) {
|
|
969
860
|
// Withdraw this join on cancellation without revoking the independently owned mode persistence.
|
|
970
|
-
const stopping = inactivePersistence?.operation;
|
|
861
|
+
const stopping = inactivePersistence.current?.operation;
|
|
971
862
|
if (stopping) await waitForRecovery(stopping, signal);
|
|
972
863
|
if (!isOwner()) return superseded();
|
|
973
864
|
await waitForRecovery(restoreActiveBranch(ctx, "fork", false, pending), signal);
|
|
@@ -986,16 +877,13 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
986
877
|
async function activateCurrentState(ctx: ExtensionContext, pending: AbortController): Promise<StateFlowTelegramControlResult> {
|
|
987
878
|
let selected = runtime;
|
|
988
879
|
const owner = ctx.sessionManager.getSessionId();
|
|
989
|
-
const
|
|
990
|
-
const file = ctx.sessionManager.getSessionFile();
|
|
991
|
-
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
880
|
+
const sameSession = sessionIdentity(ctx);
|
|
992
881
|
const initiallyActive = isActive();
|
|
993
882
|
let accepted = false;
|
|
994
883
|
let receipt = AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]);
|
|
995
884
|
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
996
|
-
const isCurrent = () => startActivation?.controller === pending && !signal.aborted && runtime === selected && !shuttingDown
|
|
997
|
-
&&
|
|
998
|
-
&& ctx.sessionManager.getHeader()?.timestamp === timestamp && isActive() === (accepted || initiallyActive);
|
|
885
|
+
const isCurrent = () => startActivation.current?.controller === pending && !signal.aborted && runtime === selected && !shuttingDown
|
|
886
|
+
&& sameSession() && isActive() === (accepted || initiallyActive);
|
|
999
887
|
const superseded = (): StateFlowTelegramControlResult => {
|
|
1000
888
|
pending.abort();
|
|
1001
889
|
return { ok: false, message: "State Flow activation was superseded", signal: pending.signal };
|
|
@@ -1018,9 +906,9 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1018
906
|
const bootstrap = hasPriorConversation(ctx.sessionManager.getBranch()) || passiveContinuation !== undefined || boundary !== undefined;
|
|
1019
907
|
const activated = current ? resumeEpisode(current, bootstrap) : startEpisode(bootstrap);
|
|
1020
908
|
const recoveredCurrent = selectedHistoryExpired;
|
|
1021
|
-
|
|
909
|
+
publish(activated);
|
|
1022
910
|
accepted = true;
|
|
1023
|
-
|
|
911
|
+
inactivePersistence.cancel();
|
|
1024
912
|
runtime = selected = activation;
|
|
1025
913
|
snapshot = activated;
|
|
1026
914
|
activeContext = ctx;
|
|
@@ -1035,7 +923,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1035
923
|
bootstrapContinuation = snapshot.meta.bootstrap ? continuation : undefined;
|
|
1036
924
|
deferInferencePreparation();
|
|
1037
925
|
receipt = AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]);
|
|
1038
|
-
|
|
926
|
+
recordPublication();
|
|
1039
927
|
syncStateFlowTools();
|
|
1040
928
|
updateUi(ctx);
|
|
1041
929
|
if (forkInitialization) {
|
|
@@ -1100,16 +988,16 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1100
988
|
function deactivateStateFlow(ctx: ExtensionContext, mode: InactiveMode): Promise<StateFlowTelegramControlResult> {
|
|
1101
989
|
if (shuttingDown) return Promise.resolve({ ok: false, message: "State Flow is shutting down" });
|
|
1102
990
|
if (mode === "off") return selectOff(ctx);
|
|
1103
|
-
|
|
991
|
+
startActivation.cancel();
|
|
1104
992
|
telegramStartPending = false;
|
|
1105
|
-
const persisting = inactivePersistence;
|
|
993
|
+
const persisting = inactivePersistence.current;
|
|
1106
994
|
if (persisting?.operation && !persisting.published) {
|
|
1107
995
|
// Pending inactive choices coalesce: acceptance persists whichever inactive mode is current then.
|
|
1108
996
|
if (snapshot.config.mode !== mode) applyInactiveMode(ctx, mode);
|
|
1109
997
|
return persisting.operation;
|
|
1110
998
|
}
|
|
1111
999
|
if (!activeContext) void restoreActiveBranch(ctx, undefined, false);
|
|
1112
|
-
const restoring = branchRestoration;
|
|
1000
|
+
const restoring = branchRestoration.current;
|
|
1113
1001
|
// Read-only recovery also retains the latest policy; its native write fence is updated below.
|
|
1114
1002
|
if (restoring) restoring.requestedMode = mode;
|
|
1115
1003
|
if (restoring?.awaitingAcceptance) {
|
|
@@ -1122,16 +1010,11 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1122
1010
|
const recordedMode = retained.kind === "pre-runtime" ? retained.mode : retained.kind === "boundary" ? retained.checkpoint.mode : undefined;
|
|
1123
1011
|
if (modePersistenceError || recordedMode === mode) return Promise.resolve({ ok: true, message: `State Flow is already ${mode}` });
|
|
1124
1012
|
}
|
|
1125
|
-
|
|
1126
|
-
const pending
|
|
1127
|
-
inactivePersistence
|
|
1128
|
-
const operation = deferredBranch && mode === "passive" && !branchStartsWithoutRuntime
|
|
1013
|
+
inactivePersistence.cancel();
|
|
1014
|
+
const pending = inactivePersistence.claim({ controller: new AbortController() });
|
|
1015
|
+
return inactivePersistence.track(pending, deferredBranch && mode === "passive" && !branchStartsWithoutRuntime
|
|
1129
1016
|
? acquirePassiveMode(ctx, pending, deferredBranch.reason)
|
|
1130
|
-
: persistInactiveMode(ctx, pending, mode);
|
|
1131
|
-
pending.operation = operation;
|
|
1132
|
-
const finished = () => { if (inactivePersistence === pending) inactivePersistence = undefined; };
|
|
1133
|
-
void operation.then(finished, finished);
|
|
1134
|
-
return operation;
|
|
1017
|
+
: persistInactiveMode(ctx, pending, mode));
|
|
1135
1018
|
}
|
|
1136
1019
|
|
|
1137
1020
|
/** Off owns cancellation and native policy only; no canonical transaction or semantic handoff. */
|
|
@@ -1149,7 +1032,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1149
1032
|
if (!sameOwner && !sameDeferred) modePersistenceError = policy?.persistenceError;
|
|
1150
1033
|
const alreadyOff = current.config.mode === "off" && policy?.mode === "off";
|
|
1151
1034
|
const incoming = sameOwner || sameDeferred ? current.meta.bootstrap ? bootstrapContinuation : passiveContinuation : undefined;
|
|
1152
|
-
const unfinished = current.meta.specification !== undefined || (inferencePreparation?.prompt !== undefined && !inferencePreparation.accepted)
|
|
1035
|
+
const unfinished = current.meta.specification !== undefined || (inferencePreparation.current?.prompt !== undefined && !inferencePreparation.current.accepted)
|
|
1153
1036
|
|| (isActive() && hasUncheckpointedConversation(branch));
|
|
1154
1037
|
const at = incoming?.startedAt ?? Date.now();
|
|
1155
1038
|
const from = incoming ? incoming.activeRunStartedAt : (sameOwner || sameDeferred) && (!ctx.isIdle() || unfinished) ? runAnchorTimestamp : undefined;
|
|
@@ -1164,20 +1047,17 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1164
1047
|
} catch {
|
|
1165
1048
|
// An unusable cached bookmark cannot prevent native Off policy or authorize replacement memory.
|
|
1166
1049
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
memoryToolLifetime.
|
|
1172
|
-
memoryToolLifetime = new AbortController();
|
|
1050
|
+
startActivation.cancel();
|
|
1051
|
+
branchRestoration.cancel();
|
|
1052
|
+
inactivePersistence.cancel();
|
|
1053
|
+
backup.cancel();
|
|
1054
|
+
memoryToolLifetime.renew();
|
|
1173
1055
|
clearRunTransient();
|
|
1174
1056
|
contextProjection.reset();
|
|
1175
1057
|
telegramStartPending = false;
|
|
1176
1058
|
bootstrapContinuation = undefined;
|
|
1177
1059
|
passiveContinuation = undefined;
|
|
1178
|
-
|
|
1179
|
-
artifactHints = {};
|
|
1180
|
-
artifactReads.setCandidates([]);
|
|
1060
|
+
acquisition.reset();
|
|
1181
1061
|
runtime = undefined;
|
|
1182
1062
|
snapshot = emptySnapshot("off");
|
|
1183
1063
|
selectedHistoryExpired = false;
|
|
@@ -1207,21 +1087,21 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1207
1087
|
}
|
|
1208
1088
|
}
|
|
1209
1089
|
|
|
1210
|
-
async function acquirePassiveMode(ctx: ExtensionContext, pending:
|
|
1090
|
+
async function acquirePassiveMode(ctx: ExtensionContext, pending: InactivePersistence, reason: unknown): Promise<StateFlowTelegramControlResult> {
|
|
1211
1091
|
await restoreActiveBranch(ctx, reason, true, pending.controller, "passive");
|
|
1212
|
-
if (inactivePersistence
|
|
1092
|
+
if (!inactivePersistence.owns(pending) || pending.controller.signal.aborted || shuttingDown) {
|
|
1213
1093
|
return { ok: false, message: "State Flow mode change was superseded" };
|
|
1214
1094
|
}
|
|
1215
1095
|
return persistInactiveMode(ctx, pending, "passive");
|
|
1216
1096
|
}
|
|
1217
1097
|
|
|
1218
|
-
async function persistInactiveMode(ctx: ExtensionContext, pending:
|
|
1098
|
+
async function persistInactiveMode(ctx: ExtensionContext, pending: InactivePersistence, mode: InactiveMode): Promise<StateFlowTelegramControlResult> {
|
|
1219
1099
|
let selected = runtime;
|
|
1220
1100
|
const owner = ctx.sessionManager.getSessionId();
|
|
1221
1101
|
const current = snapshot;
|
|
1222
1102
|
const wasActive = current.config.mode === "active";
|
|
1223
1103
|
let unfinished = current.meta.specification !== undefined
|
|
1224
|
-
|| (inferencePreparation?.prompt !== undefined && !inferencePreparation.accepted);
|
|
1104
|
+
|| (inferencePreparation.current?.prompt !== undefined && !inferencePreparation.current.accepted);
|
|
1225
1105
|
applyInactiveMode(ctx, mode);
|
|
1226
1106
|
const stoppedAt = Date.now();
|
|
1227
1107
|
if (modePersistenceError) {
|
|
@@ -1237,7 +1117,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1237
1117
|
const incomingBoundary = current.meta.bootstrap ? bootstrapContinuation : undefined;
|
|
1238
1118
|
const anchor = runAnchorTimestamp;
|
|
1239
1119
|
const idle = ctx.isIdle();
|
|
1240
|
-
const isCurrent = () => inactivePersistence
|
|
1120
|
+
const isCurrent = () => inactivePersistence.owns(pending) && runtime === selected
|
|
1241
1121
|
&& !shuttingDown && ctx.sessionManager.getSessionId() === owner && !isActive();
|
|
1242
1122
|
const superseded = (): StateFlowTelegramControlResult => ({ ok: false, message: "State Flow mode change was superseded" });
|
|
1243
1123
|
const freezeHandoff = (): PassiveContinuation | undefined => {
|
|
@@ -1278,8 +1158,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1278
1158
|
// Projection changes before any wait; capture the old run's boundary before later native input can replace it.
|
|
1279
1159
|
freezeHandoff();
|
|
1280
1160
|
bootstrapContinuation = undefined;
|
|
1281
|
-
|
|
1282
|
-
artifactReads.setCandidates([]);
|
|
1161
|
+
acquisition.clearInvalidations();
|
|
1283
1162
|
assertPublicationAvailable();
|
|
1284
1163
|
const signal = ctx.signal ? AbortSignal.any([pending.controller.signal, ctx.signal]) : pending.controller.signal;
|
|
1285
1164
|
if (branchStartsWithoutRuntime || !selected?.view) {
|
|
@@ -1299,11 +1178,11 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1299
1178
|
if (!isCurrent()) throw new Error("State Flow mode selection changed while awaiting publication");
|
|
1300
1179
|
assertPublicationAvailable();
|
|
1301
1180
|
const next = structuredClone(snapshot);
|
|
1302
|
-
|
|
1181
|
+
publish(next);
|
|
1303
1182
|
accepted = true;
|
|
1304
1183
|
snapshot = next;
|
|
1305
1184
|
installScopeStates();
|
|
1306
|
-
|
|
1185
|
+
recordPublication();
|
|
1307
1186
|
return complete();
|
|
1308
1187
|
}, signal);
|
|
1309
1188
|
return isCurrent() ? result : superseded();
|
|
@@ -1317,8 +1196,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1317
1196
|
}
|
|
1318
1197
|
modePersistenceError = cause.trim() ? cause : "Canonical State Flow persistence failed";
|
|
1319
1198
|
bootstrapContinuation = undefined;
|
|
1320
|
-
|
|
1321
|
-
artifactReads.setCandidates([]);
|
|
1199
|
+
acquisition.clearInvalidations();
|
|
1322
1200
|
return complete();
|
|
1323
1201
|
}
|
|
1324
1202
|
}
|
|
@@ -1352,7 +1230,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1352
1230
|
},
|
|
1353
1231
|
cancelStart: () => {
|
|
1354
1232
|
telegramStartPending = false;
|
|
1355
|
-
|
|
1233
|
+
startActivation.cancel();
|
|
1356
1234
|
},
|
|
1357
1235
|
},
|
|
1358
1236
|
});
|
|
@@ -1369,7 +1247,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1369
1247
|
contextProjection.reset();
|
|
1370
1248
|
skillReads.clear();
|
|
1371
1249
|
artifactReads.clear();
|
|
1372
|
-
|
|
1250
|
+
responseReconciliation.cancel();
|
|
1373
1251
|
completedRunAccepted = false;
|
|
1374
1252
|
// This native hook has no operation signal. Capture only; the first active context owns acceptance.
|
|
1375
1253
|
deferInferencePreparation(event.prompt);
|
|
@@ -1389,12 +1267,12 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1389
1267
|
if (runtime?.view) refreshArtifactHints();
|
|
1390
1268
|
if (!isActive() && !passiveContinuation && !passiveMemoryAvailable()) return;
|
|
1391
1269
|
// Idle inspection must not freeze a pre-acceptance snapshot for the live inference.
|
|
1392
|
-
const projection = isActive() && inferencePreparation && !inferencePreparation.accepted
|
|
1270
|
+
const projection = isActive() && inferencePreparation.current && !inferencePreparation.current.accepted
|
|
1393
1271
|
? new ContextProjection() : contextProjection;
|
|
1394
1272
|
const effective = effectiveState;
|
|
1395
|
-
const invalidations =
|
|
1396
|
-
const phase = isActive() ?
|
|
1397
|
-
const view = contextView(effective,
|
|
1273
|
+
const invalidations = acquisition.invalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
|
|
1274
|
+
const phase = isActive() ? rehydrationPhase : undefined;
|
|
1275
|
+
const view = contextView(effective, acquisition.hints, invalidations, phase);
|
|
1398
1276
|
if (passiveContinuation) {
|
|
1399
1277
|
const retained = passiveContinuationMessages(messages, passiveContinuation);
|
|
1400
1278
|
if (!runtime?.view) return { messages: retained };
|
|
@@ -1414,20 +1292,20 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1414
1292
|
}
|
|
1415
1293
|
|
|
1416
1294
|
function prepareContext(messages: AgentMessage[], ctx: ExtensionContext): ReturnType<typeof projectContext> | Promise<ReturnType<typeof projectContext>> {
|
|
1417
|
-
const pending = inferencePreparation;
|
|
1295
|
+
const pending = inferencePreparation.current;
|
|
1418
1296
|
const selected = runtime;
|
|
1419
1297
|
const operationSignal = ctx.signal;
|
|
1420
1298
|
// Idle projections remain observational; only a live native operation may await preparation.
|
|
1421
1299
|
if (!isActive() || !pending || !operationSignal || shuttingDown) return projectContext(messages);
|
|
1422
1300
|
pending.operation ??= prepareInference(pending, selected, ctx, operationSignal).finally(() => {
|
|
1423
1301
|
// A late withdrawal must not clear newer work; accepted lifecycle is never replayed after an error.
|
|
1424
|
-
if (!pending.accepted && inferencePreparation
|
|
1302
|
+
if (!pending.accepted && inferencePreparation.owns(pending)) pending.operation = undefined;
|
|
1425
1303
|
});
|
|
1426
1304
|
return pending.operation.then(() => {
|
|
1427
1305
|
if (shuttingDown || operationSignal.aborted || runtime !== selected || ctx.signal !== operationSignal) return;
|
|
1428
|
-
if (isActive() && inferencePreparation
|
|
1306
|
+
if (isActive() && !inferencePreparation.owns(pending)) {
|
|
1429
1307
|
// Start may require same-run maintenance. A new captured user prompt instead owns a different context request.
|
|
1430
|
-
if (inferencePreparation?.prompt === undefined) return prepareContext(messages, ctx);
|
|
1308
|
+
if (inferencePreparation.current?.prompt === undefined) return prepareContext(messages, ctx);
|
|
1431
1309
|
return;
|
|
1432
1310
|
}
|
|
1433
1311
|
if (isActive() && !pending.accepted) return;
|
|
@@ -1487,32 +1365,32 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1487
1365
|
if (event.message.role === "user" && runAnchorTimestamp === undefined) runAnchorTimestamp = event.message.timestamp;
|
|
1488
1366
|
if (shuttingDown || !isActive()) return;
|
|
1489
1367
|
if (event.message.role !== "assistant") return;
|
|
1490
|
-
|
|
1368
|
+
responseReconciliation.cancel();
|
|
1491
1369
|
const message = event.message as unknown as { role: "assistant"; stopReason?: string; content?: unknown };
|
|
1492
1370
|
if (message.stopReason === "aborted" || assistantToolCallCount(message.content) > 0 || message.stopReason === "toolUse") return;
|
|
1493
1371
|
if (message.stopReason === "length" || message.stopReason === "error") {
|
|
1494
1372
|
recordDiagnostic(`Assistant response ended with ${message.stopReason}`, "finalization", ctx, { content: message.content });
|
|
1495
1373
|
return;
|
|
1496
1374
|
}
|
|
1497
|
-
responseReconciliation
|
|
1375
|
+
responseReconciliation.claim({ controller: new AbortController() });
|
|
1498
1376
|
});
|
|
1499
1377
|
|
|
1500
1378
|
pi.on("turn_end", async (event, ctx) => {
|
|
1501
1379
|
if (shuttingDown) return;
|
|
1502
|
-
const pending = responseReconciliation;
|
|
1380
|
+
const pending = responseReconciliation.current;
|
|
1503
1381
|
if (!isActive() || !pending) {
|
|
1504
1382
|
updateUi(ctx);
|
|
1505
1383
|
return;
|
|
1506
1384
|
}
|
|
1507
1385
|
const selected = runtime;
|
|
1508
|
-
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
1386
|
+
const signal = ctx.signal ? AbortSignal.any([pending.controller.signal, ctx.signal]) : pending.controller.signal;
|
|
1509
1387
|
let responseCommitted = false;
|
|
1510
1388
|
try {
|
|
1511
1389
|
const response = finalizedAssistantResponse(event.message);
|
|
1512
1390
|
if (!selected?.view) throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
1513
1391
|
await selected.withPatchTransaction((transaction) => {
|
|
1514
1392
|
signal.throwIfAborted();
|
|
1515
|
-
if (runtime !== selected || responseReconciliation
|
|
1393
|
+
if (runtime !== selected || !responseReconciliation.owns(pending) || !isActive()) {
|
|
1516
1394
|
throw new Error("State Flow response selection changed while awaiting publication");
|
|
1517
1395
|
}
|
|
1518
1396
|
assertPublicationAvailable();
|
|
@@ -1528,44 +1406,38 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1528
1406
|
installScopeStates();
|
|
1529
1407
|
responseCommitted = true;
|
|
1530
1408
|
contextProjection.reset();
|
|
1531
|
-
if (publication?.changed) recordPublication(
|
|
1409
|
+
if (publication?.changed) recordPublication();
|
|
1532
1410
|
appendCheckpoint();
|
|
1533
1411
|
clearAcceptedAcquisitions();
|
|
1534
1412
|
bootstrapContinuation = undefined;
|
|
1535
1413
|
rehydrationPhase = "step";
|
|
1536
1414
|
completedRunAccepted = !wasBootstrap;
|
|
1537
|
-
|
|
1415
|
+
backup.markTurnAccepted();
|
|
1538
1416
|
updateUi(ctx);
|
|
1539
1417
|
}, signal);
|
|
1540
1418
|
} catch (error) {
|
|
1541
1419
|
// Superseded completion owns neither the new lifecycle nor its diagnostics/UI.
|
|
1542
|
-
if (signal.aborted || runtime !== selected || responseReconciliation
|
|
1420
|
+
if (signal.aborted || runtime !== selected || !responseReconciliation.owns(pending)) return;
|
|
1543
1421
|
const cause = diagnosticText(error);
|
|
1544
1422
|
recordDiagnostic(cause, "finalization", ctx);
|
|
1545
1423
|
notifyProblem(ctx, responseCommitted
|
|
1546
1424
|
? `State Flow response saved; lifecycle update failed: ${cause}`
|
|
1547
1425
|
: `State Flow response reconciliation failed: ${cause}`, "error");
|
|
1548
1426
|
} finally {
|
|
1549
|
-
|
|
1427
|
+
responseReconciliation.release(pending);
|
|
1550
1428
|
}
|
|
1551
1429
|
});
|
|
1552
1430
|
|
|
1553
1431
|
pi.on("session_before_compact", (event) => {
|
|
1554
|
-
|
|
1555
|
-
const marker = compactionMarker;
|
|
1556
|
-
if (shuttingDown || compactionStopped || !isActive() || !compactionPlan || !marker || event.customInstructions !== marker) return { cancel: true };
|
|
1557
|
-
const result = stateFlowCompactionResult(compactionPlan, marker, event);
|
|
1432
|
+
const result = compactionRequests.resolve(event, !shuttingDown && isActive());
|
|
1558
1433
|
if (result === undefined || "cancel" in result) return result;
|
|
1559
1434
|
return { compaction: result };
|
|
1560
1435
|
});
|
|
1561
1436
|
|
|
1562
1437
|
pi.on("agent_before_settle", async (_event, ctx) => {
|
|
1563
|
-
if (shuttingDown || !memoryToolsAvailable() || !
|
|
1564
|
-
turnAcceptedForBackup = false;
|
|
1565
|
-
if (!backupPending) return;
|
|
1566
|
-
backupPending = false;
|
|
1438
|
+
if (shuttingDown || !memoryToolsAvailable() || !backup.takeSettledTurn()) return;
|
|
1567
1439
|
const operationSignal = ctx.signal;
|
|
1568
|
-
const lifetime =
|
|
1440
|
+
const lifetime = backup.lifetime;
|
|
1569
1441
|
const signal = operationSignal ? AbortSignal.any([lifetime, operationSignal]) : lifetime;
|
|
1570
1442
|
const selected = runtime;
|
|
1571
1443
|
try {
|
|
@@ -1573,21 +1445,17 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1573
1445
|
const pushSessionId = sessionAddress(ctx).id;
|
|
1574
1446
|
const pushCwd = ctx.cwd;
|
|
1575
1447
|
// Pi 0.87 settles after its agent signal ends; waiting there would strand native Abort.
|
|
1576
|
-
|
|
1577
|
-
backupOperations.add(operation);
|
|
1578
|
-
try { await operation; }
|
|
1579
|
-
finally { backupOperations.delete(operation); }
|
|
1448
|
+
await backup.track(backupCurrentStateFlowFiles(repositoryRoot, signal, operationSignal !== undefined));
|
|
1580
1449
|
if (signal.aborted || snapshot.config.mode === "off") return;
|
|
1581
1450
|
startStateFlowBackupPush(repositoryRoot, (error) => {
|
|
1582
1451
|
if (shuttingDown || lifetime.aborted || snapshot.config.mode === "off") return;
|
|
1583
1452
|
const message = diagnosticText(error);
|
|
1584
1453
|
const recorded = diagnosticWriter.recordBackupPushFailure(pushSessionId, pushCwd, message);
|
|
1585
|
-
if (
|
|
1586
|
-
pushFailureNotified = true;
|
|
1454
|
+
if (!backup.claimPushFailureNotice()) return;
|
|
1587
1455
|
notifyActiveContext(recorded
|
|
1588
1456
|
? `State Flow Git backup push failed; state is saved locally. Details: ${stateFlowLogPath(agentDir)}. A later accepted turn retries.`
|
|
1589
1457
|
: `State Flow Git backup push failed; local diagnostics unavailable: ${message}`);
|
|
1590
|
-
}, () =>
|
|
1458
|
+
}, () => backup.resetPushFailureNotice(), lifetime);
|
|
1591
1459
|
}
|
|
1592
1460
|
} catch (error) {
|
|
1593
1461
|
if (signal.aborted || runtime !== selected) return;
|
|
@@ -1605,21 +1473,19 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1605
1473
|
await startStateFlow(ctx);
|
|
1606
1474
|
return;
|
|
1607
1475
|
}
|
|
1608
|
-
if (!completedRunAccepted ||
|
|
1609
|
-
||
|
|
1476
|
+
if (!completedRunAccepted || compactionRequests.stopped || !isActive() || snapshot.meta.bootstrap
|
|
1477
|
+
|| compactionRequests.inFlight || !ctx.isIdle() || ctx.hasPendingMessages() || !runtime?.view
|
|
1610
1478
|
|| !shouldRequestStateFlowCompaction(ctx.getContextUsage())) return;
|
|
1611
1479
|
completedRunAccepted = false;
|
|
1612
1480
|
const entries = ctx.sessionManager.buildContextEntries();
|
|
1613
1481
|
if (!hasCompactionSizedTranscript(entries)) return;
|
|
1614
1482
|
const plan = planStateFlowCompaction(entries, runtime.causalBasis(), snapshot.meta.step, runAnchorTimestamp);
|
|
1615
1483
|
if (!plan) return;
|
|
1616
|
-
|
|
1617
|
-
const marker = compactionMarker = `${compactionPrefix}${randomUUID()}`;
|
|
1618
|
-
compactionInFlight = true;
|
|
1484
|
+
const marker = compactionRequests.begin(plan);
|
|
1619
1485
|
// Pi dispatches deferred companion prompts after all settled handlers return.
|
|
1620
1486
|
await new Promise<void>((resolve) => {
|
|
1621
1487
|
const finished = () => {
|
|
1622
|
-
|
|
1488
|
+
compactionRequests.finish(marker);
|
|
1623
1489
|
resolve();
|
|
1624
1490
|
};
|
|
1625
1491
|
ctx.compact({ customInstructions: marker, onComplete: finished, onError: finished });
|
|
@@ -1640,21 +1506,21 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1640
1506
|
});
|
|
1641
1507
|
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
1642
1508
|
shuttingDown = true;
|
|
1643
|
-
memoryToolLifetime.
|
|
1509
|
+
memoryToolLifetime.end();
|
|
1644
1510
|
contextProjection.reset();
|
|
1645
|
-
const restoring =
|
|
1646
|
-
const starting =
|
|
1647
|
-
const stopping =
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1511
|
+
const restoring = branchRestoration.cancel();
|
|
1512
|
+
const starting = startActivation.cancel();
|
|
1513
|
+
const stopping = inactivePersistence.cancel();
|
|
1514
|
+
backup.abort();
|
|
1515
|
+
inferencePreparation.cancel();
|
|
1516
|
+
responseReconciliation.cancel();
|
|
1651
1517
|
cancelSharedInspections();
|
|
1652
1518
|
telegramStartPending = false;
|
|
1653
|
-
|
|
1519
|
+
compactionRequests.stop();
|
|
1654
1520
|
completedRunAccepted = false;
|
|
1655
1521
|
activeContext = undefined;
|
|
1656
1522
|
telegram.dispose();
|
|
1657
|
-
await Promise.allSettled([...
|
|
1523
|
+
await Promise.allSettled([...backup.operations, ...branchRestoration.inflight, ...[restoring, stopping, starting].filter((operation) => operation !== undefined)]);
|
|
1658
1524
|
await awaitInFlightBackupPushes(repositoryRoot);
|
|
1659
1525
|
});
|
|
1660
1526
|
}
|