@llblab/pi-kit 0.25.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 +5 -1
- package/CHANGELOG.md +11 -0
- package/README.md +10 -8
- package/node_modules/@llblab/pi-actors/AGENTS.md +2 -0
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +4 -1
- package/node_modules/@llblab/pi-actors/LICENSE +21 -0
- package/node_modules/@llblab/pi-actors/README.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +1 -1
- package/node_modules/@llblab/pi-actors/package.json +4 -3
- package/node_modules/@llblab/pi-claude-usage/AGENTS.md +6 -3
- package/node_modules/@llblab/pi-claude-usage/BACKLOG.md +2 -1
- package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +8 -0
- package/node_modules/@llblab/pi-claude-usage/README.md +48 -3
- package/node_modules/@llblab/pi-claude-usage/index.ts +8 -1159
- package/node_modules/@llblab/pi-claude-usage/lib/extension.ts +30 -0
- package/node_modules/@llblab/pi-claude-usage/lib/fast.ts +24 -0
- package/node_modules/@llblab/pi-claude-usage/lib/query.ts +146 -0
- package/node_modules/@llblab/pi-claude-usage/lib/status-format.ts +297 -0
- package/node_modules/@llblab/pi-claude-usage/lib/status.ts +366 -0
- package/node_modules/@llblab/pi-claude-usage/lib/telegram.ts +44 -0
- package/node_modules/@llblab/pi-claude-usage/lib/usage-store.ts +221 -0
- package/node_modules/@llblab/pi-claude-usage/lib/usage.ts +128 -0
- package/node_modules/@llblab/pi-claude-usage/package.json +9 -5
- package/node_modules/@llblab/pi-clean-room/AGENTS.md +1 -0
- package/node_modules/@llblab/pi-clean-room/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-clean-room/LICENSE +21 -0
- package/node_modules/@llblab/pi-clean-room/README.md +1 -1
- package/node_modules/@llblab/pi-clean-room/package.json +3 -2
- package/node_modules/@llblab/pi-codex-usage/AGENTS.md +9 -6
- package/node_modules/@llblab/pi-codex-usage/BACKLOG.md +2 -1
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +17 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +75 -17
- package/node_modules/@llblab/pi-codex-usage/index.ts +8 -1602
- package/node_modules/@llblab/pi-codex-usage/lib/extension.ts +25 -0
- package/node_modules/@llblab/pi-codex-usage/lib/fast.ts +23 -0
- package/node_modules/@llblab/pi-codex-usage/lib/query.ts +368 -0
- package/node_modules/@llblab/pi-codex-usage/lib/status-format.ts +347 -0
- package/node_modules/@llblab/pi-codex-usage/lib/status.ts +435 -0
- package/node_modules/@llblab/pi-codex-usage/lib/telegram.ts +45 -0
- package/node_modules/@llblab/pi-codex-usage/lib/usage-store.ts +229 -0
- package/node_modules/@llblab/pi-codex-usage/lib/usage.ts +425 -0
- package/node_modules/@llblab/pi-codex-usage/package.json +11 -6
- package/node_modules/@llblab/pi-command-fast/AGENTS.md +7 -0
- package/node_modules/@llblab/pi-command-fast/BACKLOG.md +9 -0
- package/node_modules/@llblab/pi-command-fast/CHANGELOG.md +7 -0
- package/node_modules/@llblab/pi-command-fast/LICENSE +21 -0
- package/node_modules/@llblab/pi-command-fast/README.md +42 -0
- package/node_modules/@llblab/pi-command-fast/dist/command.d.ts +8 -0
- package/node_modules/@llblab/pi-command-fast/dist/command.js +52 -0
- package/node_modules/@llblab/pi-command-fast/dist/index.d.ts +3 -0
- package/node_modules/@llblab/pi-command-fast/dist/index.js +3 -0
- package/node_modules/@llblab/pi-command-fast/dist/models-json.d.ts +10 -0
- package/node_modules/@llblab/pi-command-fast/dist/models-json.js +81 -0
- package/node_modules/@llblab/pi-command-fast/package.json +49 -0
- package/node_modules/@llblab/pi-grow-loop/AGENTS.md +1 -0
- package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +4 -1
- package/node_modules/@llblab/pi-grow-loop/LICENSE +21 -0
- package/node_modules/@llblab/pi-grow-loop/README.md +1 -1
- package/node_modules/@llblab/pi-grow-loop/package.json +3 -2
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +7 -6
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +13 -5
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +16 -1
- package/node_modules/@llblab/pi-state-flow/LICENSE +21 -0
- package/node_modules/@llblab/pi-state-flow/README.md +117 -35
- 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 +18 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +41 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +346 -303
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +31 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +74 -5
- 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/session.d.ts +10 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +60 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +8 -5
- 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 +10 -9
- 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 +646 -89
- 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 +70 -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 -61
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +85 -1
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +49 -2
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +342 -301
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +77 -5
- 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/session.ts +52 -1
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +8 -7
- 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 +10 -9
- 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/node_modules/jsonc-parser/CHANGELOG.md +76 -0
- package/node_modules/jsonc-parser/LICENSE.md +21 -0
- package/node_modules/jsonc-parser/README.md +364 -0
- package/node_modules/jsonc-parser/SECURITY.md +41 -0
- package/node_modules/jsonc-parser/lib/esm/impl/edit.js +185 -0
- package/node_modules/jsonc-parser/lib/esm/impl/format.js +261 -0
- package/node_modules/jsonc-parser/lib/esm/impl/parser.js +659 -0
- package/node_modules/jsonc-parser/lib/esm/impl/scanner.js +443 -0
- package/node_modules/jsonc-parser/lib/esm/impl/string-intern.js +29 -0
- package/node_modules/jsonc-parser/lib/esm/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/esm/main.js +178 -0
- package/node_modules/jsonc-parser/lib/umd/impl/edit.js +201 -0
- package/node_modules/jsonc-parser/lib/umd/impl/format.js +275 -0
- package/node_modules/jsonc-parser/lib/umd/impl/parser.js +682 -0
- package/node_modules/jsonc-parser/lib/umd/impl/scanner.js +456 -0
- package/node_modules/jsonc-parser/lib/umd/impl/string-intern.js +42 -0
- package/node_modules/jsonc-parser/lib/umd/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/umd/main.js +194 -0
- package/node_modules/jsonc-parser/package.json +37 -0
- package/package.json +8 -8
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { StringEnum, Type } from "@earendil-works/pi-ai";
|
|
2
2
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { Text } from "@earendil-works/pi-tui";
|
|
4
|
-
import { randomUUID } from "node:crypto";
|
|
5
4
|
import { existsSync } from "node:fs";
|
|
6
5
|
import { isAbsolute, join, resolve } from "node:path";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { hasCompactionSizedTranscript, planStateFlowCompaction, shouldRequestStateFlowCompaction,
|
|
6
|
+
import { ArtifactAcquisitionState, missingArtifactRemovals } from "./acquisition.js";
|
|
7
|
+
import { inspectRegisteredArtifactPaths, sameArtifactSourceFingerprint } from "./artifact.js";
|
|
8
|
+
import { hasCompactionSizedTranscript, planStateFlowCompaction, shouldRequestStateFlowCompaction, StateFlowCompactionRequests } from "./compaction.js";
|
|
10
9
|
import { inactiveModeFor, loadStateFlowConfig } from "./config.js";
|
|
11
10
|
import { ContextProjection, contextView, createPassiveContinuation, currentRunTrajectory, passiveContinuationMessages, projectSystemProtocol, runtimeContextHead, syntheticUser } from "./context.js";
|
|
12
11
|
import { readNativeSessionHeader } from "./continuation.js";
|
|
13
12
|
import { cwdScopeKey, resolveSessionAddress, sessionScopeKey, } from "./durable.js";
|
|
14
13
|
import { completeRun, deactivateEpisode, prepareRun, resumeEpisode, startEpisode } from "./episode.js";
|
|
15
|
-
import { awaitInFlightBackupPushes, backupCurrentStateFlowFiles, startStateFlowBackupPush } from "./git.js";
|
|
14
|
+
import { awaitInFlightBackupPushes, backupCurrentStateFlowFiles, SettledTurnBackup, startStateFlowBackupPush } from "./git.js";
|
|
16
15
|
import { projectRecentTransitionsWithLimit } from "./history.js";
|
|
17
16
|
import { isObject, presentationJson, sameJson } from "./json.js";
|
|
18
17
|
import { StateFlowDiagnosticWriter, stateFlowLogPath } from "./logging.js";
|
|
19
18
|
import { assistantToolCallCount, conciseDiagnostic, diagnosticText, finalizedAssistantResponse, formatPatchStateArguments, PASSIVE_MEMORY_PROTOCOL, separatedFailure, separatedOutput, stateFlowProtocol } from "./protocol.js";
|
|
19
|
+
import { OwnedOperationSlot, RenewableLifetime } from "./operation.js";
|
|
20
20
|
import { readProjectedState, readStatePath } from "./query.js";
|
|
21
21
|
import { selectedBoundaryFailure, selectRetainedCheckpoint, waitForRecovery } from "./recovery.js";
|
|
22
22
|
import { TemporalRuntime } from "./runtime.js";
|
|
23
|
-
import { discoverSnapshotData, findAssistantToolBatch, findPassiveStopBoundary, hasPriorConversation, hasUncheckpointedConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.js";
|
|
23
|
+
import { discoverSnapshotData, findAssistantToolBatch, findBranchPolicy, findPassiveStopBoundary, hasPendingFork, hasPriorConversation, hasUncheckpointedConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.js";
|
|
24
24
|
import { hasCompiledSkillArtifact, hashSkillSource, registeredSkillResolver, SkillReadTracker } from "./skills.js";
|
|
25
25
|
import { HistoryBoundaryExpiredError, emptySnapshot, migrationFailure, preRuntimeCheckpoint } from "./snapshot.js";
|
|
26
26
|
import { emptyState, projectStateForModel } from "./state.js";
|
|
@@ -45,45 +45,46 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
45
45
|
let selectedHistoryExpired = false;
|
|
46
46
|
let modePersistenceError;
|
|
47
47
|
/** One pending inactive-mode persistence; later inactive choices coalesce until it publishes. */
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
const inactivePersistence = new OwnedOperationSlot();
|
|
49
|
+
const startActivation = new OwnedOperationSlot();
|
|
50
50
|
let forkInitialization = false;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
const branchRestoration = new OwnedOperationSlot();
|
|
52
|
+
let deferredBranch;
|
|
53
|
+
const responseReconciliation = new OwnedOperationSlot();
|
|
54
|
+
const sharedInspectionLifetime = new RenewableLifetime();
|
|
55
|
+
const memoryToolLifetime = new RenewableLifetime();
|
|
55
56
|
let completedRunAccepted = false;
|
|
56
|
-
|
|
57
|
-
let compactionPlan;
|
|
58
|
-
let compactionInFlight = false;
|
|
59
|
-
let compactionStopped = false;
|
|
60
|
-
const compactionMarker = `state-flow-boundary:${randomUUID()}`;
|
|
57
|
+
const compactionRequests = new StateFlowCompactionRequests();
|
|
61
58
|
let passiveContinuation;
|
|
62
59
|
let bootstrapContinuation;
|
|
63
60
|
const contextProjection = new ContextProjection();
|
|
64
|
-
|
|
61
|
+
const inferencePreparation = new OwnedOperationSlot();
|
|
65
62
|
let runAnchorTimestamp;
|
|
66
63
|
let runtime;
|
|
67
64
|
let activeContext;
|
|
68
65
|
let rehydrationPhase;
|
|
69
66
|
const repositoryRoot = resolve(options.repositoryRoot ?? config.directory);
|
|
70
67
|
const diagnosticWriter = new StateFlowDiagnosticWriter(config.logging, stateFlowLogPath(agentDir), repositoryRoot, (message) => notifyActiveContext(message));
|
|
71
|
-
|
|
72
|
-
const backupLifetime = new AbortController();
|
|
73
|
-
const backupOperations = new Set();
|
|
68
|
+
const backup = new SettledTurnBackup();
|
|
74
69
|
let shuttingDown = false;
|
|
75
|
-
let pushFailureNotified = false;
|
|
76
70
|
const skillReads = new SkillReadTracker(hashSkillSource, (path) => activeContext
|
|
77
71
|
? registeredSkillResolver(activeContext.cwd, pi.getCommands())(path)
|
|
78
72
|
: undefined);
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
let artifactHints = {};
|
|
82
|
-
const SOURCE_CHANGED_HINT = "Source changed since this artifact was compiled. Read and recompile it before relying on it.";
|
|
73
|
+
const acquisition = new ArtifactAcquisitionState();
|
|
74
|
+
const artifactReads = acquisition.reads;
|
|
83
75
|
let telegramStartPending = false;
|
|
84
76
|
function sessionAddress(ctx) {
|
|
85
77
|
return resolveSessionAddress(ctx.sessionManager.getSessionFile(), ctx.sessionManager.getSessionId(), ctx.sessionManager.getHeader()?.timestamp);
|
|
86
78
|
}
|
|
79
|
+
/** Native session identity captured before an await; any change supersedes the waiting operation. */
|
|
80
|
+
function sessionIdentity(ctx) {
|
|
81
|
+
const cwd = ctx.cwd;
|
|
82
|
+
const owner = ctx.sessionManager.getSessionId();
|
|
83
|
+
const file = ctx.sessionManager.getSessionFile();
|
|
84
|
+
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
85
|
+
return () => ctx.cwd === cwd && ctx.sessionManager.getSessionId() === owner && ctx.sessionManager.getSessionFile() === file
|
|
86
|
+
&& ctx.sessionManager.getHeader()?.timestamp === timestamp;
|
|
87
|
+
}
|
|
87
88
|
function notifyProblem(ctx, message, level) {
|
|
88
89
|
ctx.ui.notify(conciseDiagnostic(message), level);
|
|
89
90
|
}
|
|
@@ -100,7 +101,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
100
101
|
return new TemporalRuntime(ctx.cwd, sessionAddress(ctx), repositoryRoot, undefined, config.historyLimit);
|
|
101
102
|
}
|
|
102
103
|
function projectModelState(state) {
|
|
103
|
-
return projectStateForModel(state,
|
|
104
|
+
return projectStateForModel(state, acquisition.hints);
|
|
104
105
|
}
|
|
105
106
|
function assertSelectedBranchAvailable() {
|
|
106
107
|
if (snapshot.meta.validation?.attempt === 0) {
|
|
@@ -144,105 +145,29 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
144
145
|
function updateUi(ctx) {
|
|
145
146
|
ctx.ui.setStatus(STATUS_KEY, compactStatus(snapshot, scopeRevisions(), (color, text) => ctx.ui.theme.fg(color, text)));
|
|
146
147
|
}
|
|
147
|
-
function cancelInactivePersistence() {
|
|
148
|
-
const pending = inactivePersistence;
|
|
149
|
-
pending?.controller.abort();
|
|
150
|
-
inactivePersistence = undefined;
|
|
151
|
-
return pending?.operation;
|
|
152
|
-
}
|
|
153
|
-
function cancelStartActivation() {
|
|
154
|
-
const pending = startActivation;
|
|
155
|
-
pending?.controller.abort();
|
|
156
|
-
startActivation = undefined;
|
|
157
|
-
return pending?.operation;
|
|
158
|
-
}
|
|
159
|
-
function cancelBranchRestoration() {
|
|
160
|
-
const pending = branchRestoration;
|
|
161
|
-
pending?.controller.abort();
|
|
162
|
-
branchRestoration = undefined;
|
|
163
|
-
return pending?.operation;
|
|
164
|
-
}
|
|
165
|
-
function cancelResponseReconciliation() {
|
|
166
|
-
responseReconciliation?.abort();
|
|
167
|
-
responseReconciliation = undefined;
|
|
168
|
-
}
|
|
169
148
|
function cancelSharedInspections() {
|
|
170
|
-
sharedInspectionLifetime.
|
|
171
|
-
sharedInspectionLifetime = new AbortController();
|
|
172
|
-
}
|
|
173
|
-
function cancelInferencePreparation() {
|
|
174
|
-
inferencePreparation?.controller.abort();
|
|
175
|
-
inferencePreparation = undefined;
|
|
149
|
+
sharedInspectionLifetime.renew(new Error("State Flow inspection was cancelled; select the scope again"));
|
|
176
150
|
}
|
|
177
151
|
function clearRunTransient() {
|
|
178
|
-
|
|
179
|
-
|
|
152
|
+
inferencePreparation.cancel();
|
|
153
|
+
responseReconciliation.cancel();
|
|
180
154
|
cancelSharedInspections();
|
|
181
155
|
completedRunAccepted = false;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
compactionInFlight = false;
|
|
156
|
+
backup.clearTurn();
|
|
157
|
+
compactionRequests.clear();
|
|
185
158
|
skillReads.clear();
|
|
186
159
|
artifactReads.clear();
|
|
187
160
|
}
|
|
188
161
|
function deferInferencePreparation(prompt) {
|
|
189
|
-
|
|
190
|
-
inferencePreparation
|
|
191
|
-
|
|
192
|
-
artifactReads.setCandidates([]);
|
|
162
|
+
inferencePreparation.cancel();
|
|
163
|
+
inferencePreparation.claim({ controller: new AbortController(), prompt });
|
|
164
|
+
acquisition.clearInvalidations();
|
|
193
165
|
}
|
|
194
166
|
function refreshArtifactHints() {
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
const paths = new Set();
|
|
202
|
-
for (const scope of ["global", "cwd", "session"])
|
|
203
|
-
for (const path of Object.keys(scopeStates[scope].artifacts))
|
|
204
|
-
paths.add(path);
|
|
205
|
-
const observations = new Map(inspectRegisteredArtifactPaths(paths).map((observation) => [observation.path, observation]));
|
|
206
|
-
const nextHints = {};
|
|
207
|
-
const nextInvalidations = new Map();
|
|
208
|
-
for (const scope of ["global", "cwd", "session"]) {
|
|
209
|
-
const provenance = runtime.artifactProvenance(scope);
|
|
210
|
-
for (const [path, metadata] of Object.entries(scopeStates[scope].artifacts)) {
|
|
211
|
-
delete nextHints[path];
|
|
212
|
-
nextInvalidations.delete(path);
|
|
213
|
-
if (metadata.kind === "skill")
|
|
214
|
-
continue;
|
|
215
|
-
const observed = observations.get(path);
|
|
216
|
-
if (observed?.kind !== "present")
|
|
217
|
-
continue;
|
|
218
|
-
const need = classifyArtifactCompilationNeed({ path, scope, sourceFingerprint: observed.fingerprint }, metadata, ORDINARY_ARTIFACT_COMPILER, false, provenance[path]);
|
|
219
|
-
if (need.kind !== "requires-compilation")
|
|
220
|
-
continue;
|
|
221
|
-
if (need.reason === "source-changed")
|
|
222
|
-
nextHints[path] = SOURCE_CHANGED_HINT;
|
|
223
|
-
nextInvalidations.set(path, { path, scope, reason: need.reason });
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
artifactHints = nextHints;
|
|
227
|
-
artifactInvalidations = [...nextInvalidations.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
228
|
-
artifactReads.setCandidates(artifactInvalidations);
|
|
229
|
-
}
|
|
230
|
-
function missingArtifactRemovals(states) {
|
|
231
|
-
const owners = new Map();
|
|
232
|
-
for (const scope of ["global", "cwd", "session"])
|
|
233
|
-
for (const path of Object.keys(states[scope].artifacts ?? {})) {
|
|
234
|
-
owners.set(path, [...owners.get(path) ?? [], scope]);
|
|
235
|
-
}
|
|
236
|
-
const removals = {};
|
|
237
|
-
for (const observation of inspectRegisteredArtifactPaths(owners.keys())) {
|
|
238
|
-
if (observation.kind !== "missing")
|
|
239
|
-
continue;
|
|
240
|
-
for (const scope of owners.get(observation.path) ?? []) {
|
|
241
|
-
const artifacts = (removals[scope]?.artifacts ?? {});
|
|
242
|
-
removals[scope] = { ...(removals[scope] ?? {}), artifacts: { ...artifacts, [observation.path]: null } };
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return removals;
|
|
167
|
+
if (runtime?.view)
|
|
168
|
+
acquisition.refresh(scopeStates, (scope) => runtime.artifactProvenance(scope));
|
|
169
|
+
else
|
|
170
|
+
acquisition.reset();
|
|
246
171
|
}
|
|
247
172
|
function installScopeStates() {
|
|
248
173
|
scopeStates = runtime?.view ? runtime.states() : { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
@@ -259,22 +184,35 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
259
184
|
async function inspectTelegramState(scope) {
|
|
260
185
|
if (!activeContext || shuttingDown)
|
|
261
186
|
throw new Error("State Flow is not attached to an active session yet");
|
|
262
|
-
|
|
187
|
+
const ctx = activeContext;
|
|
188
|
+
const owner = sessionAddress(ctx).key;
|
|
189
|
+
const cwd = ctx.cwd;
|
|
190
|
+
const off = snapshot.config.mode === "off";
|
|
191
|
+
const privateScope = scope === "session" || scope === "effective";
|
|
192
|
+
if (!off && privateScope)
|
|
263
193
|
assertSelectedBranchAvailable();
|
|
264
|
-
|
|
194
|
+
// Off operator reads are disposable: they confer no selected-branch/cache authority.
|
|
195
|
+
const selected = off ? createRuntime(ctx) : runtime ??= createRuntime(ctx);
|
|
265
196
|
const signal = sharedInspectionLifetime.signal;
|
|
266
|
-
|
|
197
|
+
let privateAvailable = true;
|
|
198
|
+
if (off && privateScope)
|
|
199
|
+
privateAvailable = await selected.refreshCurrentMemory(signal) !== undefined;
|
|
200
|
+
else if (!selected.view || (scope !== "session" && !modePersistenceError))
|
|
267
201
|
await selected.refreshShared(signal);
|
|
268
202
|
signal.throwIfAborted();
|
|
269
|
-
if (runtime !== selected)
|
|
203
|
+
if (activeContext !== ctx || ctx.cwd !== cwd || sessionAddress(ctx).key !== owner || (!off && runtime !== selected)) {
|
|
270
204
|
throw new Error("State Flow inspection selection changed; select the scope again");
|
|
271
|
-
|
|
205
|
+
}
|
|
206
|
+
if (!privateAvailable)
|
|
207
|
+
throw new Error("Current State Flow session memory is unavailable");
|
|
208
|
+
if (!off && privateScope)
|
|
272
209
|
assertSelectedBranchAvailable();
|
|
273
210
|
if (!selected.view)
|
|
274
211
|
throw new Error("State Flow temporal runtime is unavailable");
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
212
|
+
if (!off)
|
|
213
|
+
installScopeStates();
|
|
214
|
+
const state = selected.readView(0, scope === "effective" ? undefined : scope);
|
|
215
|
+
return { state: { ...projectModelState(state), ...(state.lazy === undefined ? {} : { lazy: structuredClone(state.lazy) }) }, revisions: temporalScopeRevisions(selected.view), signal };
|
|
278
216
|
}
|
|
279
217
|
function syncStateFlowTools() {
|
|
280
218
|
const active = pi.getActiveTools();
|
|
@@ -286,15 +224,11 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
286
224
|
? [...new Set([...active, ...owned])]
|
|
287
225
|
: active.filter((name) => !owned.includes(name)));
|
|
288
226
|
}
|
|
289
|
-
|
|
227
|
+
/** Accepted canonical publication establishes runtime authority and makes the settled-turn Git backup due. */
|
|
228
|
+
function recordPublication() {
|
|
290
229
|
// A passive view is not runtime authority; only accepted canonical publication establishes it.
|
|
291
230
|
branchStartsWithoutRuntime = false;
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
/** Record accepted lifecycle persistence; Git backup is scheduled only after an accepted turn settles. */
|
|
295
|
-
function recordPolicyPublication(publication, ctx) {
|
|
296
|
-
if (publication)
|
|
297
|
-
recordPublication(publication, ctx);
|
|
231
|
+
backup.markPublished();
|
|
298
232
|
}
|
|
299
233
|
function skillReadIsCurrent(read) {
|
|
300
234
|
return read.hash !== undefined && runtime?.view !== undefined && hasCompiledSkillArtifact(scopeStates[read.scope].artifacts, runtime.artifactProvenance(read.scope)[read.path], read.path, read.hash);
|
|
@@ -311,11 +245,9 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
311
245
|
const target = `${read.scope}.artifacts[${JSON.stringify(read.path)}]`;
|
|
312
246
|
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.`;
|
|
313
247
|
}
|
|
314
|
-
function clearAcceptedAcquisitions(acquiredArtifactPaths
|
|
315
|
-
|
|
316
|
-
artifactReads.setCandidates(artifactInvalidations);
|
|
248
|
+
function clearAcceptedAcquisitions(acquiredArtifactPaths) {
|
|
249
|
+
acquisition.acceptAcquired(acquiredArtifactPaths);
|
|
317
250
|
dropCurrentSkillReads();
|
|
318
|
-
artifactReads.clear();
|
|
319
251
|
}
|
|
320
252
|
async function prepareInference(pending, selected, ctx, operationSignal) {
|
|
321
253
|
const signal = AbortSignal.any([pending.controller.signal, operationSignal]);
|
|
@@ -324,7 +256,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
324
256
|
throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
325
257
|
await selected.withPatchTransaction((transaction) => {
|
|
326
258
|
signal.throwIfAborted();
|
|
327
|
-
if (runtime !== selected || inferencePreparation
|
|
259
|
+
if (runtime !== selected || !inferencePreparation.owns(pending) || !isActive() || shuttingDown) {
|
|
328
260
|
throw new Error("State Flow inference selection changed while awaiting publication");
|
|
329
261
|
}
|
|
330
262
|
assertPublicationAvailable();
|
|
@@ -351,7 +283,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
351
283
|
if (rotatesRun && rehydrationPhase !== "new-bootstrap" && rehydrationPhase !== "resume-bootstrap")
|
|
352
284
|
rehydrationPhase = "step";
|
|
353
285
|
if (publication?.changed)
|
|
354
|
-
recordPublication(
|
|
286
|
+
recordPublication();
|
|
355
287
|
if (pending.prompt !== undefined || publication?.changed)
|
|
356
288
|
appendCheckpoint();
|
|
357
289
|
if (Object.keys(removals).length > 0)
|
|
@@ -360,7 +292,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
360
292
|
}, signal);
|
|
361
293
|
}
|
|
362
294
|
catch (error) {
|
|
363
|
-
if (signal.aborted || runtime !== selected || inferencePreparation
|
|
295
|
+
if (signal.aborted || runtime !== selected || !inferencePreparation.owns(pending) || shuttingDown)
|
|
364
296
|
return;
|
|
365
297
|
// Pi reports context-hook exceptions and continues. Abort through its public port rather than infer from stale preparation.
|
|
366
298
|
ctx.abort();
|
|
@@ -371,25 +303,21 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
371
303
|
: `State Flow inference preparation failed: ${cause}`, "error");
|
|
372
304
|
}
|
|
373
305
|
}
|
|
374
|
-
function currentRehydrationPhase() {
|
|
375
|
-
return rehydrationPhase;
|
|
376
|
-
}
|
|
377
306
|
function statusDiagnostics(ctx) {
|
|
378
307
|
const cwd = ctx.cwd;
|
|
379
|
-
const diagnosticStates = scopeStates;
|
|
380
308
|
const view = runtime?.view;
|
|
381
309
|
const diagnosticRecent = runtime?.recent() ?? [];
|
|
382
310
|
const durableStateError = snapshot.meta.validation?.attempt === 0 ? snapshot.meta.validation.error
|
|
383
311
|
: view ? undefined : "no temporal runtime is selected on this branch";
|
|
384
312
|
const staleArtifacts = durableStateError === undefined
|
|
385
|
-
?
|
|
313
|
+
? acquisition.invalidations.map(({ path, scope, reason }) => ({ scope: scope ?? "global", path, reason }))
|
|
386
314
|
: [];
|
|
387
315
|
const session = sessionAddress(ctx);
|
|
388
316
|
return {
|
|
389
317
|
repositoryRoot,
|
|
390
318
|
cwdScopeKey: cwdScopeKey(cwd),
|
|
391
319
|
sessionScopeKey: sessionScopeKey(session.key),
|
|
392
|
-
scopeStates
|
|
320
|
+
scopeStates,
|
|
393
321
|
effectiveState,
|
|
394
322
|
recent: diagnosticRecent,
|
|
395
323
|
historyLimit: config.historyLimit,
|
|
@@ -414,21 +342,69 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
414
342
|
return resolveSessionAddress(parent.file, parent.id, parent.timestamp);
|
|
415
343
|
}
|
|
416
344
|
/** Select the active native branch under one owned restoration lifetime; only current accepted work installs memory. */
|
|
417
|
-
function restoreActiveBranch(ctx, sessionStartReason, notifyRecovery = true, startOwner) {
|
|
345
|
+
function restoreActiveBranch(ctx, sessionStartReason, notifyRecovery = true, startOwner, requestedMode) {
|
|
418
346
|
contextProjection.reset();
|
|
419
|
-
|
|
347
|
+
memoryToolLifetime.renew();
|
|
348
|
+
branchRestoration.cancel();
|
|
420
349
|
// Start-owned attachment/fork recovery keeps its owner; only accepted Start cancels Stop.
|
|
421
350
|
if (!startOwner) {
|
|
422
|
-
|
|
423
|
-
|
|
351
|
+
startActivation.cancel();
|
|
352
|
+
inactivePersistence.cancel();
|
|
424
353
|
}
|
|
425
354
|
clearRunTransient();
|
|
426
355
|
passiveContinuation = undefined;
|
|
427
356
|
bootstrapContinuation = undefined;
|
|
428
|
-
|
|
429
|
-
artifactReads.setCandidates([]);
|
|
357
|
+
acquisition.clearInvalidations();
|
|
430
358
|
telegramStartPending = false;
|
|
431
359
|
activeContext = ctx;
|
|
360
|
+
if (!startOwner) {
|
|
361
|
+
let mode = config.inactiveMode;
|
|
362
|
+
let adoptDefault = false;
|
|
363
|
+
let withoutRuntime = false;
|
|
364
|
+
let persistenceError;
|
|
365
|
+
let pendingFork = false;
|
|
366
|
+
try {
|
|
367
|
+
const branch = ctx.sessionManager.getBranch();
|
|
368
|
+
const discovery = discoverSnapshotData(branch);
|
|
369
|
+
withoutRuntime = discovery.candidates.length === 0 && discovery.errors.length === 0;
|
|
370
|
+
adoptDefault = withoutRuntime && isNewSession(sessionStartReason, branch);
|
|
371
|
+
pendingFork = hasPendingFork(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE);
|
|
372
|
+
const inherited = sessionStartReason === "fork" || pendingFork;
|
|
373
|
+
const policy = findBranchPolicy(branch, inherited ? undefined : ctx.sessionManager.getSessionId(), inherited || retainsPhysicalSessionProjection(sessionStartReason) ? PASSIVE_STOP_ENTRY_TYPE : undefined, config.inactiveMode);
|
|
374
|
+
mode = policy?.mode ?? (adoptDefault ? config.mode : config.inactiveMode);
|
|
375
|
+
persistenceError = inherited
|
|
376
|
+
? findBranchPolicy(branch, ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE, config.inactiveMode)?.persistenceError
|
|
377
|
+
: policy?.persistenceError;
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
// Off needs only policy; unavailable native evidence grants no memory authority.
|
|
381
|
+
}
|
|
382
|
+
if (mode === "off") {
|
|
383
|
+
backup.cancel();
|
|
384
|
+
const owner = sessionAddress(ctx).key;
|
|
385
|
+
const reason = pendingFork || (deferredBranch?.owner === owner && deferredBranch.cwd === ctx.cwd && deferredBranch.reason === "fork"
|
|
386
|
+
&& retainsPhysicalSessionProjection(sessionStartReason)) ? "fork" : sessionStartReason;
|
|
387
|
+
if (reason === "fork" && !pendingFork)
|
|
388
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { owner: ctx.sessionManager.getSessionId(), forkPending: true });
|
|
389
|
+
deferredBranch = { reason, owner, cwd: ctx.cwd };
|
|
390
|
+
runtime = undefined;
|
|
391
|
+
snapshot = emptySnapshot("off");
|
|
392
|
+
branchStartsWithoutRuntime = withoutRuntime;
|
|
393
|
+
selectedHistoryExpired = false;
|
|
394
|
+
modePersistenceError = persistenceError;
|
|
395
|
+
forkInitialization = reason === "fork";
|
|
396
|
+
installScopeStates();
|
|
397
|
+
acquisition.reset();
|
|
398
|
+
if (adoptDefault)
|
|
399
|
+
appendCheckpoint();
|
|
400
|
+
syncStateFlowTools();
|
|
401
|
+
updateUi(ctx);
|
|
402
|
+
return Promise.resolve();
|
|
403
|
+
}
|
|
404
|
+
if (pendingFork)
|
|
405
|
+
sessionStartReason = "fork";
|
|
406
|
+
}
|
|
407
|
+
deferredBranch = undefined;
|
|
432
408
|
runtime = createRuntime(ctx);
|
|
433
409
|
installScopeStates();
|
|
434
410
|
branchStartsWithoutRuntime = false;
|
|
@@ -499,29 +475,25 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
499
475
|
snapshot = migrationFailure({}, "State Flow branch restoration is pending", selection.kind === "current" ? selection.mode
|
|
500
476
|
: selection.kind === "auto-start" ? config.inactiveMode : inactiveSelection(selection.checkpoint.mode));
|
|
501
477
|
}
|
|
478
|
+
if (requestedMode)
|
|
479
|
+
snapshot = deactivateEpisode(snapshot, requestedMode);
|
|
502
480
|
const pending = {
|
|
503
481
|
controller: new AbortController(),
|
|
504
482
|
awaitingAcceptance: selection.kind !== "settled" && selection.kind !== "current",
|
|
483
|
+
...(requestedMode === undefined ? {} : { requestedMode }),
|
|
505
484
|
};
|
|
506
|
-
branchRestoration
|
|
507
|
-
|
|
508
|
-
pending
|
|
509
|
-
restorationOperations.add(operation);
|
|
510
|
-
const finished = () => { restorationOperations.delete(operation); };
|
|
511
|
-
void operation.then(finished, finished);
|
|
512
|
-
return operation;
|
|
485
|
+
branchRestoration.claim(pending);
|
|
486
|
+
// attachBranch releases its own ownership; settlement only drains it for shutdown.
|
|
487
|
+
return branchRestoration.track(pending, attachBranch(ctx, pending, selection, sessionStartReason, notifyRecovery, skipped), false);
|
|
513
488
|
}
|
|
514
489
|
async function attachBranch(ctx, pending, selection, reason, notifyRecovery, skipped) {
|
|
515
490
|
const placeholder = runtime;
|
|
516
491
|
let selected = runtime;
|
|
517
492
|
const owner = ctx.sessionManager.getSessionId();
|
|
518
|
-
const
|
|
519
|
-
const file = ctx.sessionManager.getSessionFile();
|
|
520
|
-
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
493
|
+
const sameSession = sessionIdentity(ctx);
|
|
521
494
|
const signal = AbortSignal.any([pending.controller.signal, ...(ctx.signal ? [ctx.signal] : [])]);
|
|
522
|
-
const isCurrent = () => branchRestoration
|
|
523
|
-
&&
|
|
524
|
-
&& ctx.sessionManager.getHeader()?.timestamp === timestamp;
|
|
495
|
+
const isCurrent = () => branchRestoration.owns(pending) && !pending.controller.signal.aborted && !shuttingDown && runtime === selected
|
|
496
|
+
&& sameSession();
|
|
525
497
|
const assertCurrent = () => {
|
|
526
498
|
signal.throwIfAborted();
|
|
527
499
|
if (!isCurrent())
|
|
@@ -529,13 +501,13 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
529
501
|
};
|
|
530
502
|
let accepted = false;
|
|
531
503
|
// Install accepted memory before native writes; later ancillary failures cannot revert or replay it.
|
|
532
|
-
const accept = (candidate, next,
|
|
504
|
+
const accept = (candidate, next, nativeWrites) => {
|
|
533
505
|
accepted = true;
|
|
534
506
|
pending.awaitingAcceptance = false;
|
|
535
507
|
runtime = selected = candidate;
|
|
536
508
|
snapshot = next;
|
|
537
509
|
installScopeStates();
|
|
538
|
-
|
|
510
|
+
recordPublication();
|
|
539
511
|
let failure;
|
|
540
512
|
try {
|
|
541
513
|
nativeWrites();
|
|
@@ -571,7 +543,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
571
543
|
&& retainsPhysicalSessionProjection(reason) && hasUncheckpointedConversation(ctx.sessionManager.getBranch()))
|
|
572
544
|
restored.meta.bootstrap = true;
|
|
573
545
|
const next = pending.requestedMode ? deactivateEpisode(restored, pending.requestedMode) : restored;
|
|
574
|
-
|
|
546
|
+
publish(next);
|
|
547
|
+
accept(candidate, next, appendCheckpoint);
|
|
575
548
|
}, signal);
|
|
576
549
|
}
|
|
577
550
|
else if (selection.kind === "fork") {
|
|
@@ -579,9 +552,9 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
579
552
|
assertCurrent();
|
|
580
553
|
// Mode is selected independently of creating the child's private memory.
|
|
581
554
|
const next = pending.requestedMode ? deactivateEpisode(child, pending.requestedMode) : child;
|
|
582
|
-
|
|
555
|
+
publish(next);
|
|
583
556
|
forkInitialization = false;
|
|
584
|
-
accept(candidate, next,
|
|
557
|
+
accept(candidate, next, () => {
|
|
585
558
|
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { reset: true, owner });
|
|
586
559
|
appendCheckpoint();
|
|
587
560
|
});
|
|
@@ -597,7 +570,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
597
570
|
throw new Error("Existing session runtime requires retained-boundary restoration");
|
|
598
571
|
const activated = startEpisode(hasPriorConversation(branch));
|
|
599
572
|
const next = pending.requestedMode ? deactivateEpisode(activated, pending.requestedMode) : activated;
|
|
600
|
-
|
|
573
|
+
publish(next);
|
|
574
|
+
accept(candidate, next, appendCheckpoint);
|
|
601
575
|
}, signal, true);
|
|
602
576
|
}
|
|
603
577
|
}
|
|
@@ -643,8 +617,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
643
617
|
}
|
|
644
618
|
finally {
|
|
645
619
|
pending.awaitingAcceptance = false;
|
|
646
|
-
|
|
647
|
-
branchRestoration = undefined;
|
|
620
|
+
branchRestoration.release(pending);
|
|
648
621
|
}
|
|
649
622
|
}
|
|
650
623
|
/** Load current shared memory for passive projection without initializing or publishing it. */
|
|
@@ -660,6 +633,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
660
633
|
}
|
|
661
634
|
return passive;
|
|
662
635
|
}
|
|
636
|
+
function selectedContinuation(ctx, reason) {
|
|
637
|
+
if (!retainsPhysicalSessionProjection(reason) || !runtime?.view)
|
|
638
|
+
return undefined;
|
|
639
|
+
const boundary = findPassiveStopBoundary(ctx.sessionManager.getBranch(), ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE);
|
|
640
|
+
return boundary === undefined ? undefined : createPassiveContinuation(projectModelState(effectiveState), boundary.at, boundary.from, boundary.preserveContext);
|
|
641
|
+
}
|
|
663
642
|
function settleSelection(ctx, reason, notifyRecovery, skipped, selectedMemory) {
|
|
664
643
|
const failure = !isActive() && snapshot.meta.validation?.attempt === 0 ? snapshot.meta.validation.error : undefined;
|
|
665
644
|
if (failure !== undefined && notifyRecovery && !modePersistenceError) {
|
|
@@ -668,16 +647,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
668
647
|
else
|
|
669
648
|
notifyProblem(ctx, `State Flow restore failed: ${failure}`, "error");
|
|
670
649
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
if (
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
passiveContinuation = continuation;
|
|
678
|
-
else if (snapshot.meta.bootstrap)
|
|
679
|
-
bootstrapContinuation = continuation;
|
|
680
|
-
}
|
|
650
|
+
const continuation = selectedMemory ? selectedContinuation(ctx, reason) : undefined;
|
|
651
|
+
if (continuation !== undefined) {
|
|
652
|
+
if (!isActive())
|
|
653
|
+
passiveContinuation = continuation;
|
|
654
|
+
else if (snapshot.meta.bootstrap)
|
|
655
|
+
bootstrapContinuation = continuation;
|
|
681
656
|
}
|
|
682
657
|
if (notifyRecovery && isActive() && skipped > 0) {
|
|
683
658
|
notifyProblem(ctx, `State Flow skipped ${skipped} malformed snapshot(s); restored the last valid one.`, "warning");
|
|
@@ -693,7 +668,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
693
668
|
pi.registerTool({
|
|
694
669
|
name: READ_STATE_TOOL_NAME,
|
|
695
670
|
label: "Read State",
|
|
696
|
-
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.",
|
|
671
|
+
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.",
|
|
697
672
|
promptSnippet: "Read exact state values, structures, or historical semantic patches",
|
|
698
673
|
parameters: Type.Object({
|
|
699
674
|
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" })),
|
|
@@ -742,7 +717,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
742
717
|
pi.registerTool({
|
|
743
718
|
name: PATCH_STATE_TOOL_NAME,
|
|
744
719
|
label: "Patch State",
|
|
745
|
-
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.",
|
|
720
|
+
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.",
|
|
746
721
|
promptSnippet: "Atomically patch one or more global/cwd/session scopes",
|
|
747
722
|
promptGuidelines: [
|
|
748
723
|
"Use patch_state only for material durable semantic changes; ordinary answers need no finalization call.",
|
|
@@ -764,6 +739,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
764
739
|
return new Text(separatedOutput(theme.fg("dim", formatPatchStateArguments(context.args))), 0, 0);
|
|
765
740
|
},
|
|
766
741
|
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
742
|
+
signal = signal ? AbortSignal.any([signal, memoryToolLifetime.signal]) : memoryToolLifetime.signal;
|
|
767
743
|
try {
|
|
768
744
|
if (!memoryToolsAvailable())
|
|
769
745
|
throw new Error("State Flow tools are off for this session");
|
|
@@ -820,12 +796,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
820
796
|
snapshot = nextSnapshot;
|
|
821
797
|
installScopeStates();
|
|
822
798
|
if (publication?.changed) {
|
|
823
|
-
recordPublication(
|
|
799
|
+
recordPublication();
|
|
824
800
|
appendCheckpoint();
|
|
825
801
|
}
|
|
826
802
|
clearAcceptedAcquisitions(new Set(acquiredArtifacts.map(({ path }) => path)));
|
|
827
803
|
updateUi(ctx);
|
|
828
|
-
const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches,
|
|
804
|
+
const updates = contextProjection.acceptPatch(previousEffective, effectiveState, patches, acquisition.hints);
|
|
829
805
|
const acknowledgement = changed
|
|
830
806
|
? `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.`
|
|
831
807
|
: "\nState already current.";
|
|
@@ -844,11 +820,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
844
820
|
attempted = undefined;
|
|
845
821
|
}
|
|
846
822
|
const cause = diagnosticText(error);
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
823
|
+
if (!signal.aborted && memoryToolsAvailable())
|
|
824
|
+
recordDiagnostic(cause, /concurrently|advanced/.test(cause) ? "publication-conflict" : "invalid-patch", ctx, {
|
|
825
|
+
input: attempted,
|
|
826
|
+
tool: PATCH_STATE_TOOL_NAME,
|
|
827
|
+
toolCallId,
|
|
828
|
+
});
|
|
852
829
|
throw separatedFailure(error);
|
|
853
830
|
}
|
|
854
831
|
},
|
|
@@ -856,47 +833,38 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
856
833
|
function startStateFlow(ctx) {
|
|
857
834
|
if (shuttingDown)
|
|
858
835
|
return Promise.resolve({ ok: false, message: "State Flow is shutting down" });
|
|
859
|
-
if (startActivation?.operation)
|
|
860
|
-
return startActivation.operation;
|
|
836
|
+
if (startActivation.current?.operation)
|
|
837
|
+
return startActivation.current.operation;
|
|
861
838
|
telegramStartPending = false;
|
|
862
|
-
if (activeContext && !branchRestoration && isActive()) {
|
|
839
|
+
if (activeContext && !branchRestoration.current && isActive()) {
|
|
863
840
|
return Promise.resolve({ ok: true, message: "State Flow is already active", signal: sharedInspectionLifetime.signal });
|
|
864
841
|
}
|
|
865
|
-
const pending = { controller: new AbortController() };
|
|
866
|
-
startActivation
|
|
867
|
-
const operation = runStart(ctx, pending.controller);
|
|
868
|
-
pending.operation = operation;
|
|
869
|
-
const finished = () => { if (startActivation === pending)
|
|
870
|
-
startActivation = undefined; };
|
|
871
|
-
void operation.then(finished, finished);
|
|
872
|
-
return operation;
|
|
842
|
+
const pending = startActivation.claim({ controller: new AbortController() });
|
|
843
|
+
return startActivation.track(pending, runStart(ctx, pending.controller));
|
|
873
844
|
}
|
|
874
845
|
/** Await restoration-owned attachment and exact-source fork recovery before current-head activation. */
|
|
875
846
|
async function runStart(ctx, pending) {
|
|
876
|
-
const
|
|
877
|
-
const cwd = ctx.cwd;
|
|
878
|
-
const file = ctx.sessionManager.getSessionFile();
|
|
879
|
-
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
847
|
+
const sameSession = sessionIdentity(ctx);
|
|
880
848
|
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
881
|
-
const isOwner = () => startActivation?.controller === pending && !signal.aborted && !shuttingDown
|
|
882
|
-
&& ctx.cwd === cwd && ctx.sessionManager.getSessionId() === owner && ctx.sessionManager.getSessionFile() === file
|
|
883
|
-
&& ctx.sessionManager.getHeader()?.timestamp === timestamp;
|
|
849
|
+
const isOwner = () => startActivation.current?.controller === pending && !signal.aborted && !shuttingDown && sameSession();
|
|
884
850
|
const superseded = () => {
|
|
885
851
|
pending.abort();
|
|
886
852
|
return { ok: false, message: "State Flow activation was superseded", signal: pending.signal };
|
|
887
853
|
};
|
|
888
854
|
try {
|
|
889
|
-
if (
|
|
855
|
+
if (deferredBranch?.reason === "fork")
|
|
856
|
+
await waitForRecovery(restoreActiveBranch(ctx, "fork", false, pending), signal);
|
|
857
|
+
else if (!activeContext)
|
|
890
858
|
await waitForRecovery(restoreActiveBranch(ctx, undefined, false, pending), signal);
|
|
891
|
-
else if (branchRestoration?.operation)
|
|
892
|
-
await waitForRecovery(branchRestoration.operation, signal);
|
|
859
|
+
else if (branchRestoration.current?.operation)
|
|
860
|
+
await waitForRecovery(branchRestoration.current.operation, signal);
|
|
893
861
|
if (!isOwner())
|
|
894
862
|
return superseded();
|
|
895
863
|
if (isActive())
|
|
896
864
|
return { ok: true, message: "State Flow is already active", signal: sharedInspectionLifetime.signal };
|
|
897
865
|
if (forkInitialization) {
|
|
898
866
|
// Withdraw this join on cancellation without revoking the independently owned mode persistence.
|
|
899
|
-
const stopping = inactivePersistence?.operation;
|
|
867
|
+
const stopping = inactivePersistence.current?.operation;
|
|
900
868
|
if (stopping)
|
|
901
869
|
await waitForRecovery(stopping, signal);
|
|
902
870
|
if (!isOwner())
|
|
@@ -919,50 +887,61 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
919
887
|
async function activateCurrentState(ctx, pending) {
|
|
920
888
|
let selected = runtime;
|
|
921
889
|
const owner = ctx.sessionManager.getSessionId();
|
|
922
|
-
const
|
|
923
|
-
const file = ctx.sessionManager.getSessionFile();
|
|
924
|
-
const timestamp = ctx.sessionManager.getHeader()?.timestamp;
|
|
890
|
+
const sameSession = sessionIdentity(ctx);
|
|
925
891
|
const initiallyActive = isActive();
|
|
926
892
|
let accepted = false;
|
|
927
893
|
let receipt = AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]);
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
&&
|
|
894
|
+
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
895
|
+
const isCurrent = () => startActivation.current?.controller === pending && !signal.aborted && runtime === selected && !shuttingDown
|
|
896
|
+
&& sameSession() && isActive() === (accepted || initiallyActive);
|
|
931
897
|
const superseded = () => {
|
|
932
898
|
pending.abort();
|
|
933
899
|
return { ok: false, message: "State Flow activation was superseded", signal: pending.signal };
|
|
934
900
|
};
|
|
935
901
|
try {
|
|
936
902
|
const activation = createRuntime(ctx);
|
|
937
|
-
const
|
|
903
|
+
const deferred = deferredBranch;
|
|
904
|
+
if (deferred) {
|
|
905
|
+
const discovery = discoverSnapshotData(ctx.sessionManager.getBranch());
|
|
906
|
+
const retained = selectRetainedCheckpoint(discovery.candidates, config.inactiveMode);
|
|
907
|
+
branchStartsWithoutRuntime = retained.kind === "pre-runtime" || (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
908
|
+
}
|
|
909
|
+
// Off activation owns one cancellable current-head publication, not an independent read-only restore.
|
|
938
910
|
const result = await activation.withStartTransaction((current, publish) => {
|
|
939
911
|
signal.throwIfAborted();
|
|
940
912
|
if (!isCurrent())
|
|
941
913
|
throw new Error("State Flow activation selection changed while awaiting publication");
|
|
942
914
|
if (!current && !branchStartsWithoutRuntime)
|
|
943
915
|
throw new Error(snapshot.meta.validation?.error ?? "Current State Flow session memory is unavailable");
|
|
944
|
-
const
|
|
945
|
-
|
|
916
|
+
const boundary = deferred && retainsPhysicalSessionProjection(deferred.reason)
|
|
917
|
+
? findPassiveStopBoundary(ctx.sessionManager.getBranch(), owner, PASSIVE_STOP_ENTRY_TYPE) : undefined;
|
|
918
|
+
const bootstrap = hasPriorConversation(ctx.sessionManager.getBranch()) || passiveContinuation !== undefined || boundary !== undefined;
|
|
946
919
|
const activated = current ? resumeEpisode(current, bootstrap) : startEpisode(bootstrap);
|
|
947
920
|
const recoveredCurrent = selectedHistoryExpired;
|
|
948
|
-
|
|
921
|
+
publish(activated);
|
|
949
922
|
accepted = true;
|
|
950
|
-
|
|
923
|
+
inactivePersistence.cancel();
|
|
951
924
|
runtime = selected = activation;
|
|
952
925
|
snapshot = activated;
|
|
953
926
|
activeContext = ctx;
|
|
954
927
|
selectedHistoryExpired = false;
|
|
955
928
|
modePersistenceError = undefined;
|
|
956
929
|
installScopeStates();
|
|
930
|
+
const continuation = passiveContinuation ?? bootstrapContinuation ?? (deferred ? selectedContinuation(ctx, deferred.reason) : undefined);
|
|
931
|
+
deferredBranch = undefined;
|
|
957
932
|
clearRunTransient();
|
|
958
933
|
contextProjection.reset();
|
|
959
934
|
passiveContinuation = undefined;
|
|
960
935
|
bootstrapContinuation = snapshot.meta.bootstrap ? continuation : undefined;
|
|
961
936
|
deferInferencePreparation();
|
|
962
937
|
receipt = AbortSignal.any([pending.signal, sharedInspectionLifetime.signal]);
|
|
963
|
-
|
|
938
|
+
recordPublication();
|
|
964
939
|
syncStateFlowTools();
|
|
965
940
|
updateUi(ctx);
|
|
941
|
+
if (forkInitialization) {
|
|
942
|
+
forkInitialization = false;
|
|
943
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { reset: true, owner });
|
|
944
|
+
}
|
|
966
945
|
appendCheckpoint();
|
|
967
946
|
ctx.ui.notify(recoveredCurrent
|
|
968
947
|
? "State Flow active from current session memory; unavailable historical state was not restored."
|
|
@@ -1012,11 +991,13 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1012
991
|
updateUi(ctx);
|
|
1013
992
|
}
|
|
1014
993
|
function deactivateStateFlow(ctx, mode) {
|
|
1015
|
-
cancelStartActivation();
|
|
1016
994
|
if (shuttingDown)
|
|
1017
995
|
return Promise.resolve({ ok: false, message: "State Flow is shutting down" });
|
|
996
|
+
if (mode === "off")
|
|
997
|
+
return selectOff(ctx);
|
|
998
|
+
startActivation.cancel();
|
|
1018
999
|
telegramStartPending = false;
|
|
1019
|
-
const persisting = inactivePersistence;
|
|
1000
|
+
const persisting = inactivePersistence.current;
|
|
1020
1001
|
if (persisting?.operation && !persisting.published) {
|
|
1021
1002
|
// Pending inactive choices coalesce: acceptance persists whichever inactive mode is current then.
|
|
1022
1003
|
if (snapshot.config.mode !== mode)
|
|
@@ -1025,12 +1006,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1025
1006
|
}
|
|
1026
1007
|
if (!activeContext)
|
|
1027
1008
|
void restoreActiveBranch(ctx, undefined, false);
|
|
1028
|
-
const restoring = branchRestoration;
|
|
1009
|
+
const restoring = branchRestoration.current;
|
|
1029
1010
|
// Read-only recovery also retains the latest policy; its native write fence is updated below.
|
|
1030
1011
|
if (restoring)
|
|
1031
1012
|
restoring.requestedMode = mode;
|
|
1032
1013
|
if (restoring?.awaitingAcceptance) {
|
|
1033
|
-
//
|
|
1014
|
+
// Passive keeps independently owned restoration; Off withdraws it through selectOff.
|
|
1034
1015
|
applyInactiveMode(ctx, mode);
|
|
1035
1016
|
return Promise.resolve({ ok: true, message: `State Flow ${mode}; memory restoration continues` });
|
|
1036
1017
|
}
|
|
@@ -1040,15 +1021,92 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1040
1021
|
if (modePersistenceError || recordedMode === mode)
|
|
1041
1022
|
return Promise.resolve({ ok: true, message: `State Flow is already ${mode}` });
|
|
1042
1023
|
}
|
|
1043
|
-
|
|
1044
|
-
const pending = { controller: new AbortController() };
|
|
1045
|
-
inactivePersistence
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1024
|
+
inactivePersistence.cancel();
|
|
1025
|
+
const pending = inactivePersistence.claim({ controller: new AbortController() });
|
|
1026
|
+
return inactivePersistence.track(pending, deferredBranch && mode === "passive" && !branchStartsWithoutRuntime
|
|
1027
|
+
? acquirePassiveMode(ctx, pending, deferredBranch.reason)
|
|
1028
|
+
: persistInactiveMode(ctx, pending, mode));
|
|
1029
|
+
}
|
|
1030
|
+
/** Off owns cancellation and native policy only; no canonical transaction or semantic handoff. */
|
|
1031
|
+
function selectOff(ctx) {
|
|
1032
|
+
const address = sessionAddress(ctx);
|
|
1033
|
+
const owner = ctx.sessionManager.getSessionId();
|
|
1034
|
+
const branch = ctx.sessionManager.getBranch();
|
|
1035
|
+
const selected = runtime;
|
|
1036
|
+
const sameOwner = selected?.sessionKey === address.key && selected.cwd === ctx.cwd;
|
|
1037
|
+
const forkRecorded = hasPendingFork(branch, owner, PASSIVE_STOP_ENTRY_TYPE);
|
|
1038
|
+
const pendingFork = forkRecorded || (sameOwner && forkInitialization) || (deferredBranch?.owner === address.key && deferredBranch.cwd === ctx.cwd && deferredBranch.reason === "fork");
|
|
1039
|
+
const policy = findBranchPolicy(branch, owner, PASSIVE_STOP_ENTRY_TYPE, config.inactiveMode);
|
|
1040
|
+
const sameDeferred = deferredBranch?.owner === address.key && deferredBranch.cwd === ctx.cwd;
|
|
1041
|
+
const current = sameOwner || sameDeferred ? snapshot : emptySnapshot("off");
|
|
1042
|
+
if (!sameOwner && !sameDeferred)
|
|
1043
|
+
modePersistenceError = policy?.persistenceError;
|
|
1044
|
+
const alreadyOff = current.config.mode === "off" && policy?.mode === "off";
|
|
1045
|
+
const incoming = sameOwner || sameDeferred ? current.meta.bootstrap ? bootstrapContinuation : passiveContinuation : undefined;
|
|
1046
|
+
const unfinished = current.meta.specification !== undefined || (inferencePreparation.current?.prompt !== undefined && !inferencePreparation.current.accepted)
|
|
1047
|
+
|| (isActive() && hasUncheckpointedConversation(branch));
|
|
1048
|
+
const at = incoming?.startedAt ?? Date.now();
|
|
1049
|
+
const from = incoming ? incoming.activeRunStartedAt : (sameOwner || sameDeferred) && (!ctx.isIdle() || unfinished) ? runAnchorTimestamp : undefined;
|
|
1050
|
+
const preserveContext = modePersistenceError !== undefined || (incoming ? incoming.preserveContext : current.meta.bootstrap === true || (unfinished && from === undefined));
|
|
1051
|
+
const discovery = discoverSnapshotData(branch);
|
|
1052
|
+
const preRuntime = (sameOwner && branchStartsWithoutRuntime) || (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
1053
|
+
let checkpoint;
|
|
1054
|
+
try {
|
|
1055
|
+
checkpoint = !modePersistenceError && current.meta.validation?.attempt !== 0
|
|
1056
|
+
? preRuntime ? preRuntimeCheckpoint("off") : sameOwner && selected?.view ? selected.retainedCheckpoint(deactivateEpisode(current, "off")) : undefined
|
|
1057
|
+
: undefined;
|
|
1058
|
+
}
|
|
1059
|
+
catch {
|
|
1060
|
+
// An unusable cached bookmark cannot prevent native Off policy or authorize replacement memory.
|
|
1061
|
+
}
|
|
1062
|
+
startActivation.cancel();
|
|
1063
|
+
branchRestoration.cancel();
|
|
1064
|
+
inactivePersistence.cancel();
|
|
1065
|
+
backup.cancel();
|
|
1066
|
+
memoryToolLifetime.renew();
|
|
1067
|
+
clearRunTransient();
|
|
1068
|
+
contextProjection.reset();
|
|
1069
|
+
telegramStartPending = false;
|
|
1070
|
+
bootstrapContinuation = undefined;
|
|
1071
|
+
passiveContinuation = undefined;
|
|
1072
|
+
acquisition.reset();
|
|
1073
|
+
runtime = undefined;
|
|
1074
|
+
snapshot = emptySnapshot("off");
|
|
1075
|
+
selectedHistoryExpired = false;
|
|
1076
|
+
branchStartsWithoutRuntime = preRuntime;
|
|
1077
|
+
forkInitialization = pendingFork;
|
|
1078
|
+
deferredBranch = { owner: address.key, cwd: ctx.cwd, reason: pendingFork ? "fork" : undefined };
|
|
1079
|
+
activeContext = ctx;
|
|
1080
|
+
installScopeStates();
|
|
1081
|
+
syncStateFlowTools();
|
|
1082
|
+
updateUi(ctx);
|
|
1083
|
+
try {
|
|
1084
|
+
if (!alreadyOff || (pendingFork && !forkRecorded)) {
|
|
1085
|
+
if (!checkpoint || !preRuntime || pendingFork || preserveContext || from !== undefined || modePersistenceError)
|
|
1086
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, {
|
|
1087
|
+
owner, at, mode: "off", memoryDeferred: true,
|
|
1088
|
+
...(from === undefined ? {} : { from }),
|
|
1089
|
+
...(preserveContext ? { preserveContext: true } : {}),
|
|
1090
|
+
...(pendingFork ? { forkPending: true } : {}),
|
|
1091
|
+
...(modePersistenceError === undefined ? {} : { persistenceError: modePersistenceError }),
|
|
1092
|
+
});
|
|
1093
|
+
if (checkpoint)
|
|
1094
|
+
pi.appendEntry(SNAPSHOT_ENTRY_TYPE, checkpoint);
|
|
1095
|
+
}
|
|
1096
|
+
return Promise.resolve({ ok: true, message: alreadyOff ? "State Flow is already off" : "State Flow off" });
|
|
1097
|
+
}
|
|
1098
|
+
catch (error) {
|
|
1099
|
+
const message = conciseDiagnostic(`State Flow off; native mode recording failed: ${diagnosticText(error)}`);
|
|
1100
|
+
notifyProblem(ctx, message, "error");
|
|
1101
|
+
return Promise.resolve({ ok: false, message });
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
async function acquirePassiveMode(ctx, pending, reason) {
|
|
1105
|
+
await restoreActiveBranch(ctx, reason, true, pending.controller, "passive");
|
|
1106
|
+
if (!inactivePersistence.owns(pending) || pending.controller.signal.aborted || shuttingDown) {
|
|
1107
|
+
return { ok: false, message: "State Flow mode change was superseded" };
|
|
1108
|
+
}
|
|
1109
|
+
return persistInactiveMode(ctx, pending, "passive");
|
|
1052
1110
|
}
|
|
1053
1111
|
async function persistInactiveMode(ctx, pending, mode) {
|
|
1054
1112
|
let selected = runtime;
|
|
@@ -1056,7 +1114,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1056
1114
|
const current = snapshot;
|
|
1057
1115
|
const wasActive = current.config.mode === "active";
|
|
1058
1116
|
let unfinished = current.meta.specification !== undefined
|
|
1059
|
-
|| (inferencePreparation?.prompt !== undefined && !inferencePreparation.accepted);
|
|
1117
|
+
|| (inferencePreparation.current?.prompt !== undefined && !inferencePreparation.current.accepted);
|
|
1060
1118
|
applyInactiveMode(ctx, mode);
|
|
1061
1119
|
const stoppedAt = Date.now();
|
|
1062
1120
|
if (modePersistenceError) {
|
|
@@ -1072,7 +1130,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1072
1130
|
const incomingBoundary = current.meta.bootstrap ? bootstrapContinuation : undefined;
|
|
1073
1131
|
const anchor = runAnchorTimestamp;
|
|
1074
1132
|
const idle = ctx.isIdle();
|
|
1075
|
-
const isCurrent = () => inactivePersistence
|
|
1133
|
+
const isCurrent = () => inactivePersistence.owns(pending) && runtime === selected
|
|
1076
1134
|
&& !shuttingDown && ctx.sessionManager.getSessionId() === owner && !isActive();
|
|
1077
1135
|
const superseded = () => ({ ok: false, message: "State Flow mode change was superseded" });
|
|
1078
1136
|
const freezeHandoff = () => {
|
|
@@ -1080,7 +1138,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1080
1138
|
const handoff = (wasActive || modePersistenceError) && selected?.view && current.meta.validation?.attempt !== 0
|
|
1081
1139
|
? createPassiveContinuation(projectModelState(effectiveState), incomingBoundary?.startedAt ?? stoppedAt, incomingBoundary ? incomingBoundary.activeRunStartedAt : !idle || unfinished ? anchor : undefined, modePersistenceError !== undefined || (incomingBoundary ? incomingBoundary.preserveContext : current.meta.bootstrap === true || (unfinished && anchor === undefined)))
|
|
1082
1140
|
: undefined;
|
|
1083
|
-
//
|
|
1141
|
+
// Repeated Passive choices keep the already selected continuation boundary.
|
|
1084
1142
|
passiveContinuation = handoff ?? (!wasActive ? passiveContinuation : undefined);
|
|
1085
1143
|
return handoff;
|
|
1086
1144
|
};
|
|
@@ -1109,8 +1167,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1109
1167
|
// Projection changes before any wait; capture the old run's boundary before later native input can replace it.
|
|
1110
1168
|
freezeHandoff();
|
|
1111
1169
|
bootstrapContinuation = undefined;
|
|
1112
|
-
|
|
1113
|
-
artifactReads.setCandidates([]);
|
|
1170
|
+
acquisition.clearInvalidations();
|
|
1114
1171
|
assertPublicationAvailable();
|
|
1115
1172
|
const signal = ctx.signal ? AbortSignal.any([pending.controller.signal, ctx.signal]) : pending.controller.signal;
|
|
1116
1173
|
if (branchStartsWithoutRuntime || !selected?.view) {
|
|
@@ -1132,11 +1189,11 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1132
1189
|
throw new Error("State Flow mode selection changed while awaiting publication");
|
|
1133
1190
|
assertPublicationAvailable();
|
|
1134
1191
|
const next = structuredClone(snapshot);
|
|
1135
|
-
|
|
1192
|
+
publish(next);
|
|
1136
1193
|
accepted = true;
|
|
1137
1194
|
snapshot = next;
|
|
1138
1195
|
installScopeStates();
|
|
1139
|
-
|
|
1196
|
+
recordPublication();
|
|
1140
1197
|
return complete();
|
|
1141
1198
|
}, signal);
|
|
1142
1199
|
return isCurrent() ? result : superseded();
|
|
@@ -1152,8 +1209,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1152
1209
|
}
|
|
1153
1210
|
modePersistenceError = cause.trim() ? cause : "Canonical State Flow persistence failed";
|
|
1154
1211
|
bootstrapContinuation = undefined;
|
|
1155
|
-
|
|
1156
|
-
artifactReads.setCandidates([]);
|
|
1212
|
+
acquisition.clearInvalidations();
|
|
1157
1213
|
return complete();
|
|
1158
1214
|
}
|
|
1159
1215
|
}
|
|
@@ -1189,7 +1245,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1189
1245
|
},
|
|
1190
1246
|
cancelStart: () => {
|
|
1191
1247
|
telegramStartPending = false;
|
|
1192
|
-
|
|
1248
|
+
startActivation.cancel();
|
|
1193
1249
|
},
|
|
1194
1250
|
},
|
|
1195
1251
|
});
|
|
@@ -1206,7 +1262,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1206
1262
|
contextProjection.reset();
|
|
1207
1263
|
skillReads.clear();
|
|
1208
1264
|
artifactReads.clear();
|
|
1209
|
-
|
|
1265
|
+
responseReconciliation.cancel();
|
|
1210
1266
|
completedRunAccepted = false;
|
|
1211
1267
|
// This native hook has no operation signal. Capture only; the first active context owns acceptance.
|
|
1212
1268
|
deferInferencePreparation(event.prompt);
|
|
@@ -1227,12 +1283,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1227
1283
|
if (!isActive() && !passiveContinuation && !passiveMemoryAvailable())
|
|
1228
1284
|
return;
|
|
1229
1285
|
// Idle inspection must not freeze a pre-acceptance snapshot for the live inference.
|
|
1230
|
-
const projection = isActive() && inferencePreparation && !inferencePreparation.accepted
|
|
1286
|
+
const projection = isActive() && inferencePreparation.current && !inferencePreparation.current.accepted
|
|
1231
1287
|
? new ContextProjection() : contextProjection;
|
|
1232
1288
|
const effective = effectiveState;
|
|
1233
|
-
const invalidations =
|
|
1234
|
-
const phase = isActive() ?
|
|
1235
|
-
const view = contextView(effective,
|
|
1289
|
+
const invalidations = acquisition.invalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
|
|
1290
|
+
const phase = isActive() ? rehydrationPhase : undefined;
|
|
1291
|
+
const view = contextView(effective, acquisition.hints, invalidations, phase);
|
|
1236
1292
|
if (passiveContinuation) {
|
|
1237
1293
|
const retained = passiveContinuationMessages(messages, passiveContinuation);
|
|
1238
1294
|
if (!runtime?.view)
|
|
@@ -1249,7 +1305,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1249
1305
|
return { messages: projection.project(source, view, () => runtimeContextHead(snapshot, view, projectRecentTransitionsWithLimit(config.historyLimit, runtime?.recent() ?? []))) };
|
|
1250
1306
|
}
|
|
1251
1307
|
function prepareContext(messages, ctx) {
|
|
1252
|
-
const pending = inferencePreparation;
|
|
1308
|
+
const pending = inferencePreparation.current;
|
|
1253
1309
|
const selected = runtime;
|
|
1254
1310
|
const operationSignal = ctx.signal;
|
|
1255
1311
|
// Idle projections remain observational; only a live native operation may await preparation.
|
|
@@ -1257,15 +1313,15 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1257
1313
|
return projectContext(messages);
|
|
1258
1314
|
pending.operation ??= prepareInference(pending, selected, ctx, operationSignal).finally(() => {
|
|
1259
1315
|
// A late withdrawal must not clear newer work; accepted lifecycle is never replayed after an error.
|
|
1260
|
-
if (!pending.accepted && inferencePreparation
|
|
1316
|
+
if (!pending.accepted && inferencePreparation.owns(pending))
|
|
1261
1317
|
pending.operation = undefined;
|
|
1262
1318
|
});
|
|
1263
1319
|
return pending.operation.then(() => {
|
|
1264
1320
|
if (shuttingDown || operationSignal.aborted || runtime !== selected || ctx.signal !== operationSignal)
|
|
1265
1321
|
return;
|
|
1266
|
-
if (isActive() && inferencePreparation
|
|
1322
|
+
if (isActive() && !inferencePreparation.owns(pending)) {
|
|
1267
1323
|
// Start may require same-run maintenance. A new captured user prompt instead owns a different context request.
|
|
1268
|
-
if (inferencePreparation?.prompt === undefined)
|
|
1324
|
+
if (inferencePreparation.current?.prompt === undefined)
|
|
1269
1325
|
return prepareContext(messages, ctx);
|
|
1270
1326
|
return;
|
|
1271
1327
|
}
|
|
@@ -1330,7 +1386,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1330
1386
|
return;
|
|
1331
1387
|
if (event.message.role !== "assistant")
|
|
1332
1388
|
return;
|
|
1333
|
-
|
|
1389
|
+
responseReconciliation.cancel();
|
|
1334
1390
|
const message = event.message;
|
|
1335
1391
|
if (message.stopReason === "aborted" || assistantToolCallCount(message.content) > 0 || message.stopReason === "toolUse")
|
|
1336
1392
|
return;
|
|
@@ -1338,18 +1394,18 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1338
1394
|
recordDiagnostic(`Assistant response ended with ${message.stopReason}`, "finalization", ctx, { content: message.content });
|
|
1339
1395
|
return;
|
|
1340
1396
|
}
|
|
1341
|
-
responseReconciliation
|
|
1397
|
+
responseReconciliation.claim({ controller: new AbortController() });
|
|
1342
1398
|
});
|
|
1343
1399
|
pi.on("turn_end", async (event, ctx) => {
|
|
1344
1400
|
if (shuttingDown)
|
|
1345
1401
|
return;
|
|
1346
|
-
const pending = responseReconciliation;
|
|
1402
|
+
const pending = responseReconciliation.current;
|
|
1347
1403
|
if (!isActive() || !pending) {
|
|
1348
1404
|
updateUi(ctx);
|
|
1349
1405
|
return;
|
|
1350
1406
|
}
|
|
1351
1407
|
const selected = runtime;
|
|
1352
|
-
const signal = ctx.signal ? AbortSignal.any([pending.signal, ctx.signal]) : pending.signal;
|
|
1408
|
+
const signal = ctx.signal ? AbortSignal.any([pending.controller.signal, ctx.signal]) : pending.controller.signal;
|
|
1353
1409
|
let responseCommitted = false;
|
|
1354
1410
|
try {
|
|
1355
1411
|
const response = finalizedAssistantResponse(event.message);
|
|
@@ -1357,7 +1413,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1357
1413
|
throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
1358
1414
|
await selected.withPatchTransaction((transaction) => {
|
|
1359
1415
|
signal.throwIfAborted();
|
|
1360
|
-
if (runtime !== selected || responseReconciliation
|
|
1416
|
+
if (runtime !== selected || !responseReconciliation.owns(pending) || !isActive()) {
|
|
1361
1417
|
throw new Error("State Flow response selection changed while awaiting publication");
|
|
1362
1418
|
}
|
|
1363
1419
|
assertPublicationAvailable();
|
|
@@ -1374,19 +1430,19 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1374
1430
|
responseCommitted = true;
|
|
1375
1431
|
contextProjection.reset();
|
|
1376
1432
|
if (publication?.changed)
|
|
1377
|
-
recordPublication(
|
|
1433
|
+
recordPublication();
|
|
1378
1434
|
appendCheckpoint();
|
|
1379
1435
|
clearAcceptedAcquisitions();
|
|
1380
1436
|
bootstrapContinuation = undefined;
|
|
1381
1437
|
rehydrationPhase = "step";
|
|
1382
1438
|
completedRunAccepted = !wasBootstrap;
|
|
1383
|
-
|
|
1439
|
+
backup.markTurnAccepted();
|
|
1384
1440
|
updateUi(ctx);
|
|
1385
1441
|
}, signal);
|
|
1386
1442
|
}
|
|
1387
1443
|
catch (error) {
|
|
1388
1444
|
// Superseded completion owns neither the new lifecycle nor its diagnostics/UI.
|
|
1389
|
-
if (signal.aborted || runtime !== selected || responseReconciliation
|
|
1445
|
+
if (signal.aborted || runtime !== selected || !responseReconciliation.owns(pending))
|
|
1390
1446
|
return;
|
|
1391
1447
|
const cause = diagnosticText(error);
|
|
1392
1448
|
recordDiagnostic(cause, "finalization", ctx);
|
|
@@ -1395,57 +1451,41 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1395
1451
|
: `State Flow response reconciliation failed: ${cause}`, "error");
|
|
1396
1452
|
}
|
|
1397
1453
|
finally {
|
|
1398
|
-
|
|
1399
|
-
responseReconciliation = undefined;
|
|
1454
|
+
responseReconciliation.release(pending);
|
|
1400
1455
|
}
|
|
1401
1456
|
});
|
|
1402
1457
|
pi.on("session_before_compact", (event) => {
|
|
1403
|
-
|
|
1404
|
-
return;
|
|
1405
|
-
if (compactionStopped && event.reason === "manual" && event.customInstructions === compactionMarker)
|
|
1406
|
-
return { cancel: true };
|
|
1407
|
-
const result = stateFlowCompactionResult(compactionPlan, compactionMarker, event);
|
|
1458
|
+
const result = compactionRequests.resolve(event, !shuttingDown && isActive());
|
|
1408
1459
|
if (result === undefined || "cancel" in result)
|
|
1409
1460
|
return result;
|
|
1410
1461
|
return { compaction: result };
|
|
1411
1462
|
});
|
|
1412
1463
|
pi.on("agent_before_settle", async (_event, ctx) => {
|
|
1413
|
-
if (shuttingDown || !
|
|
1464
|
+
if (shuttingDown || !memoryToolsAvailable() || !backup.takeSettledTurn())
|
|
1414
1465
|
return;
|
|
1415
|
-
turnAcceptedForBackup = false;
|
|
1416
|
-
if (!backupPending)
|
|
1417
|
-
return;
|
|
1418
|
-
backupPending = false;
|
|
1419
1466
|
const operationSignal = ctx.signal;
|
|
1420
|
-
const
|
|
1467
|
+
const lifetime = backup.lifetime;
|
|
1468
|
+
const signal = operationSignal ? AbortSignal.any([lifetime, operationSignal]) : lifetime;
|
|
1421
1469
|
const selected = runtime;
|
|
1422
1470
|
try {
|
|
1423
1471
|
if (existsSync(join(repositoryRoot, ".git"))) {
|
|
1424
1472
|
const pushSessionId = sessionAddress(ctx).id;
|
|
1425
1473
|
const pushCwd = ctx.cwd;
|
|
1426
1474
|
// Pi 0.87 settles after its agent signal ends; waiting there would strand native Abort.
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
try {
|
|
1430
|
-
await operation;
|
|
1431
|
-
}
|
|
1432
|
-
finally {
|
|
1433
|
-
backupOperations.delete(operation);
|
|
1434
|
-
}
|
|
1435
|
-
if (signal.aborted)
|
|
1475
|
+
await backup.track(backupCurrentStateFlowFiles(repositoryRoot, signal, operationSignal !== undefined));
|
|
1476
|
+
if (signal.aborted || snapshot.config.mode === "off")
|
|
1436
1477
|
return;
|
|
1437
1478
|
startStateFlowBackupPush(repositoryRoot, (error) => {
|
|
1438
|
-
if (shuttingDown)
|
|
1479
|
+
if (shuttingDown || lifetime.aborted || snapshot.config.mode === "off")
|
|
1439
1480
|
return;
|
|
1440
1481
|
const message = diagnosticText(error);
|
|
1441
1482
|
const recorded = diagnosticWriter.recordBackupPushFailure(pushSessionId, pushCwd, message);
|
|
1442
|
-
if (
|
|
1483
|
+
if (!backup.claimPushFailureNotice())
|
|
1443
1484
|
return;
|
|
1444
|
-
pushFailureNotified = true;
|
|
1445
1485
|
notifyActiveContext(recorded
|
|
1446
1486
|
? `State Flow Git backup push failed; state is saved locally. Details: ${stateFlowLogPath(agentDir)}. A later accepted turn retries.`
|
|
1447
1487
|
: `State Flow Git backup push failed; local diagnostics unavailable: ${message}`);
|
|
1448
|
-
}, () =>
|
|
1488
|
+
}, () => backup.resetPushFailureNotice(), lifetime);
|
|
1449
1489
|
}
|
|
1450
1490
|
}
|
|
1451
1491
|
catch (error) {
|
|
@@ -1464,8 +1504,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1464
1504
|
await startStateFlow(ctx);
|
|
1465
1505
|
return;
|
|
1466
1506
|
}
|
|
1467
|
-
if (!completedRunAccepted ||
|
|
1468
|
-
||
|
|
1507
|
+
if (!completedRunAccepted || compactionRequests.stopped || !isActive() || snapshot.meta.bootstrap
|
|
1508
|
+
|| compactionRequests.inFlight || !ctx.isIdle() || ctx.hasPendingMessages() || !runtime?.view
|
|
1469
1509
|
|| !shouldRequestStateFlowCompaction(ctx.getContextUsage()))
|
|
1470
1510
|
return;
|
|
1471
1511
|
completedRunAccepted = false;
|
|
@@ -1475,12 +1515,14 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1475
1515
|
const plan = planStateFlowCompaction(entries, runtime.causalBasis(), snapshot.meta.step, runAnchorTimestamp);
|
|
1476
1516
|
if (!plan)
|
|
1477
1517
|
return;
|
|
1478
|
-
|
|
1479
|
-
compactionInFlight = true;
|
|
1518
|
+
const marker = compactionRequests.begin(plan);
|
|
1480
1519
|
// Pi dispatches deferred companion prompts after all settled handlers return.
|
|
1481
1520
|
await new Promise((resolve) => {
|
|
1482
|
-
const finished = () => {
|
|
1483
|
-
|
|
1521
|
+
const finished = () => {
|
|
1522
|
+
compactionRequests.finish(marker);
|
|
1523
|
+
resolve();
|
|
1524
|
+
};
|
|
1525
|
+
ctx.compact({ customInstructions: marker, onComplete: finished, onError: finished });
|
|
1484
1526
|
});
|
|
1485
1527
|
});
|
|
1486
1528
|
pi.on("session_compact", () => { contextProjection.reset(); });
|
|
@@ -1497,20 +1539,21 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1497
1539
|
});
|
|
1498
1540
|
pi.on("session_shutdown", async (_event, _ctx) => {
|
|
1499
1541
|
shuttingDown = true;
|
|
1542
|
+
memoryToolLifetime.end();
|
|
1500
1543
|
contextProjection.reset();
|
|
1501
|
-
const restoring =
|
|
1502
|
-
const starting =
|
|
1503
|
-
const stopping =
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1544
|
+
const restoring = branchRestoration.cancel();
|
|
1545
|
+
const starting = startActivation.cancel();
|
|
1546
|
+
const stopping = inactivePersistence.cancel();
|
|
1547
|
+
backup.abort();
|
|
1548
|
+
inferencePreparation.cancel();
|
|
1549
|
+
responseReconciliation.cancel();
|
|
1507
1550
|
cancelSharedInspections();
|
|
1508
1551
|
telegramStartPending = false;
|
|
1509
|
-
|
|
1552
|
+
compactionRequests.stop();
|
|
1510
1553
|
completedRunAccepted = false;
|
|
1511
1554
|
activeContext = undefined;
|
|
1512
1555
|
telegram.dispose();
|
|
1513
|
-
await Promise.allSettled([...
|
|
1556
|
+
await Promise.allSettled([...backup.operations, ...branchRestoration.inflight, ...[restoring, stopping, starting].filter((operation) => operation !== undefined)]);
|
|
1514
1557
|
await awaitInFlightBackupPushes(repositoryRoot);
|
|
1515
1558
|
});
|
|
1516
1559
|
}
|