@llblab/pi-kit 0.18.2 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +3 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +35 -37
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +5 -3
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +19 -1
- package/node_modules/@llblab/pi-state-flow/README.md +102 -48
- package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +6 -9
- package/node_modules/@llblab/pi-state-flow/dist/index.js +6 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +21 -8
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +40 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +93 -63
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +9 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +6 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +56 -58
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +12 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +7 -18
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +47 -78
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +4 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +241 -437
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -72
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +120 -499
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +3 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +43 -22
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +1 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +0 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +1 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +5 -37
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +1 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +13 -32
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +25 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +3 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +15 -12
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +2 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +5 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +26 -88
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +159 -255
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +0 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +1 -47
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +16 -33
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +48 -137
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +16 -11
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +13 -14
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +15 -43
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +5 -9
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +13 -45
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +15 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +104 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +0 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +11 -14
- package/node_modules/@llblab/pi-state-flow/dist/package.json +9 -6
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +11 -17
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +8 -8
- package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -2
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +64 -67
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +24 -6
- package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +12 -15
- package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +22 -27
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +25 -41
- package/node_modules/@llblab/pi-state-flow/docs/performance.md +38 -421
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +33 -46
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +37 -61
- package/node_modules/@llblab/pi-state-flow/index.ts +7 -71
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +26 -11
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +116 -88
- package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +12 -10
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -6
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +52 -59
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +11 -20
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +44 -86
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +237 -460
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +110 -552
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +39 -23
- package/node_modules/@llblab/pi-state-flow/lib/logging.ts +1 -7
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +5 -44
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +14 -25
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +26 -22
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +17 -11
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +7 -5
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +154 -254
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +1 -49
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +58 -142
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +27 -19
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +16 -54
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +12 -40
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +104 -22
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +16 -26
- package/node_modules/@llblab/pi-state-flow/package.json +9 -6
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +11 -17
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +8 -8
- package/package.json +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.d.ts +0 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.js +0 -125
- package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.d.ts +0 -36
- package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.js +0 -98
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +0 -13
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +0 -167
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +0 -86
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +0 -437
- package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +0 -133
- package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +0 -147
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +0 -171
- package/node_modules/@llblab/pi-state-flow/lib/publication.ts +0 -458
|
@@ -1,39 +1,36 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
1
|
import { StringEnum, Type } from "@earendil-works/pi-ai";
|
|
3
2
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
3
|
import { Text } from "@earendil-works/pi-tui";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
7
7
|
import { ArtifactReadTracker } from "./acquisition.js";
|
|
8
|
+
import { classifyArtifactCompilationNeed, inspectRegisteredArtifactPaths, ORDINARY_ARTIFACT_COMPILER, sameArtifactSourceFingerprint, } from "./artifact.js";
|
|
9
|
+
import { hasCompactionSizedTranscript, planStateFlowCompaction, shouldRequestStateFlowCompaction, stateFlowCompactionResult } from "./compaction.js";
|
|
8
10
|
import { loadStateFlowConfig } from "./config.js";
|
|
9
|
-
import {
|
|
10
|
-
import { isAbsolute, resolve } from "node:path";
|
|
11
|
-
import { SkillReadTracker } from "./skills.js";
|
|
12
|
-
import { emptySnapshot, migrationFailure, persistableSnapshot, RevisionUnavailableError } from "./snapshot.js";
|
|
11
|
+
import { createPassiveContinuation, currentRunTrajectory, lazyNavigationHint, passiveContinuationMessages, projectSystemProtocol, runtimeContextMessage, syntheticUser } from "./context.js";
|
|
13
12
|
import { readNativeSessionHeader } from "./continuation.js";
|
|
14
|
-
import { MissingSessionRuntimeError, SharedScopeRemovalConflictError, TemporalRuntime } from "./runtime.js";
|
|
15
|
-
import { emptyState, overlayStates, projectStateForModel } from "./state.js";
|
|
16
|
-
import { commitScopedTransition, stageAtomicScopePatches, stageScopedTransition, validateFinalEligibility } from "./transition.js";
|
|
17
|
-
import { discoverSnapshotData, findAssistantToolBatch, findPassiveStopBoundary, hasPriorConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.js";
|
|
18
|
-
import { compactStatus, detailedStatus, STATUS_KEY } from "./status.js";
|
|
19
|
-
import { completeRun, prepareRun, resumeEpisode, startEpisode, stopEpisode } from "./episode.js";
|
|
20
|
-
import { recoverSnapshot } from "./recovery.js";
|
|
21
|
-
import { loadPublicationQueue, publicationQueuePath, PublicationWorkerController, resolveRemotePublicationPolicy, serializeRemotePublicationPolicyDocument } from "./publication.js";
|
|
22
|
-
import { getKnowledgeRoot, GlobalMarkdownDiscovery } from "./discovery.js";
|
|
23
|
-
import { canonicalJson, isObject, sameJson } from "./json.js";
|
|
24
|
-
import { readProjectedState, readStatePath } from "./query.js";
|
|
25
13
|
import { cwdScopeKey, resolveSessionAddress, sessionScopeKey, } from "./durable.js";
|
|
26
|
-
import {
|
|
14
|
+
import { completeRun, prepareRun, resumeEpisode, startEpisode, stopEpisode } from "./episode.js";
|
|
15
|
+
import { backupCurrentStateFlowFiles } from "./git.js";
|
|
16
|
+
import { projectRecentTransitionsWithLimit } from "./history.js";
|
|
17
|
+
import { isObject, presentationJson, sameJson } from "./json.js";
|
|
27
18
|
import { StateFlowDiagnosticWriter, stateFlowLogPath } from "./logging.js";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
|
|
19
|
+
import { assistantToolCallCount, finalizedAssistantResponse, formatPatchStateArguments, PASSIVE_MEMORY_PROTOCOL, separatedFailure, separatedOutput, stateFlowProtocol } from "./protocol.js";
|
|
20
|
+
import { readProjectedState, readStatePath } from "./query.js";
|
|
21
|
+
import { recoverSnapshot } from "./recovery.js";
|
|
22
|
+
import { SharedScopeRemovalConflictError, TemporalRuntime } from "./runtime.js";
|
|
23
|
+
import { discoverSnapshotData, findAssistantToolBatch, findPassiveStopBoundary, hasPriorConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.js";
|
|
24
|
+
import { SkillReadTracker } from "./skills.js";
|
|
25
|
+
import { emptySnapshot, migrationFailure } from "./snapshot.js";
|
|
26
|
+
import { emptyState, overlayStates, projectStateForModel } from "./state.js";
|
|
27
|
+
import { compactStatus, detailedStatus, STATUS_KEY } from "./status.js";
|
|
28
|
+
import { createStateFlowTelegramAdapter } from "./telegram.js";
|
|
29
|
+
import { commitScopedTransition, stageAtomicScopePatches, stageScopedTransition } from "./transition.js";
|
|
30
|
+
export { formatPatchStateArguments };
|
|
32
31
|
export const PATCH_STATE_TOOL_NAME = "patch_state";
|
|
33
32
|
export const READ_STATE_TOOL_NAME = "read_state";
|
|
34
|
-
export const MAX_FALLBACK_ATTEMPTS = 2;
|
|
35
33
|
const PASSIVE_STOP_ENTRY_TYPE = "state-flow-passive-stop";
|
|
36
|
-
const PUBLICATION_SHUTDOWN_WAIT_MS = 2_000;
|
|
37
34
|
export default function stateFlowExtension(pi, options = {}) {
|
|
38
35
|
const agentDir = options.agentDir ?? getAgentDir();
|
|
39
36
|
const loadedConfig = loadStateFlowConfig(agentDir, options.repositoryRoot);
|
|
@@ -47,12 +44,9 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
47
44
|
let branchHasSnapshot = false;
|
|
48
45
|
let branchStartsWithoutRuntime = false;
|
|
49
46
|
let forkInitialization = false;
|
|
50
|
-
let terminalEligible = false;
|
|
51
|
-
let resolutionPending = false;
|
|
52
|
-
let fallbackAttempts = 0;
|
|
53
|
-
let fallbackFailureReported = false;
|
|
54
47
|
let responseAwaitingReconciliation = false;
|
|
55
48
|
let completedRunAccepted = false;
|
|
49
|
+
let turnAcceptedForBackup = false;
|
|
56
50
|
let compactionPlan;
|
|
57
51
|
let compactionInFlight = false;
|
|
58
52
|
let compactionStopped = false;
|
|
@@ -63,69 +57,62 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
63
57
|
let runAnchorTimestamp;
|
|
64
58
|
let runtime;
|
|
65
59
|
let activeContext;
|
|
66
|
-
let pendingPublication;
|
|
67
60
|
let rehydrationPhase;
|
|
68
|
-
let turnPublicationTarget;
|
|
69
|
-
let publicationShutdown;
|
|
70
61
|
const repositoryRoot = resolve(options.repositoryRoot ?? config.directory);
|
|
71
62
|
const diagnosticWriter = new StateFlowDiagnosticWriter(config.logging, stateFlowLogPath(agentDir), repositoryRoot, (message) => activeContext?.ui.notify(message, "warning"));
|
|
72
|
-
|
|
73
|
-
resolveDestination: () => resolveGitPushDestination(repositoryRoot),
|
|
74
|
-
isAncestor: (ancestor, descendant) => isGitCommitAncestor(repositoryRoot, ancestor, descendant),
|
|
75
|
-
push: (destination, target, signal) => pushGitTarget(repositoryRoot, destination, target, signal),
|
|
76
|
-
onDiverged: (dropped, target) => activeContext && recordDiagnostic(`Retired publication queue target ${dropped.target} after a journal lineage rewrite; retargeting to ${target}`, "publication-conflict", activeContext),
|
|
77
|
-
});
|
|
63
|
+
let backupPending = false;
|
|
78
64
|
const skillReads = new SkillReadTracker();
|
|
79
65
|
const artifactReads = new ArtifactReadTracker();
|
|
80
|
-
const globalMarkdown = new GlobalMarkdownDiscovery(options.knowledgeRoot ?? getKnowledgeRoot(agentDir));
|
|
81
66
|
let artifactInvalidations = [];
|
|
67
|
+
let artifactHints = {};
|
|
68
|
+
const SOURCE_CHANGED_HINT = "Source changed since this artifact was compiled. Read and recompile it before relying on it.";
|
|
82
69
|
let telegramStartPending = false;
|
|
83
70
|
function sessionAddress(ctx) {
|
|
84
71
|
return resolveSessionAddress(ctx.sessionManager.getSessionFile(), ctx.sessionManager.getSessionId(), ctx.sessionManager.getHeader()?.timestamp);
|
|
85
72
|
}
|
|
86
73
|
function createRuntime(ctx) {
|
|
87
|
-
return new TemporalRuntime(ctx.cwd, sessionAddress(ctx), repositoryRoot);
|
|
74
|
+
return new TemporalRuntime(ctx.cwd, sessionAddress(ctx), repositoryRoot, undefined, config.historyLimit);
|
|
75
|
+
}
|
|
76
|
+
function projectModelState(state) {
|
|
77
|
+
return projectStateForModel(state, artifactHints);
|
|
78
|
+
}
|
|
79
|
+
function assertSelectedBranchAvailable() {
|
|
80
|
+
if (snapshot.meta.validation?.attempt === 0) {
|
|
81
|
+
throw new Error(`State Flow selected branch is unavailable: ${snapshot.meta.validation.error}. Restore its canonical files and retry /state-flow-start, or select a retained boundary.`);
|
|
82
|
+
}
|
|
88
83
|
}
|
|
89
84
|
options.onRuntime?.({ read: (offset, scope) => {
|
|
85
|
+
if (scope === "session")
|
|
86
|
+
assertSelectedBranchAvailable();
|
|
90
87
|
if (!runtime)
|
|
91
88
|
throw new Error("State Flow temporal runtime is unavailable");
|
|
92
|
-
return
|
|
89
|
+
return projectModelState(runtime.read(offset, scope));
|
|
93
90
|
} });
|
|
94
91
|
function persist() {
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
assertSelectedBranchAvailable();
|
|
93
|
+
const previousView = runtime?.view;
|
|
94
|
+
const publication = !branchStartsWithoutRuntime && runtime?.view ? runtime.publish(snapshot, false, undefined) : undefined;
|
|
95
|
+
if (runtime?.view !== previousView)
|
|
96
|
+
installScopeStates();
|
|
99
97
|
if (publication && activeContext)
|
|
100
98
|
recordPublication(publication, activeContext);
|
|
101
|
-
const checkpoint =
|
|
99
|
+
const checkpoint = branchStartsWithoutRuntime ? { disabled: true } : runtime?.retainedCheckpoint(snapshot) ?? { disabled: true };
|
|
102
100
|
if ("disabled" in checkpoint && !branchStartsWithoutRuntime) {
|
|
103
101
|
throw new Error("State Flow cannot checkpoint an unproven branch as ordinary disabled; restore a valid checkpoint first");
|
|
104
102
|
}
|
|
105
103
|
pi.appendEntry(SNAPSHOT_ENTRY_TYPE, checkpoint);
|
|
106
|
-
if ("
|
|
104
|
+
if ("boundary" in checkpoint)
|
|
107
105
|
branchStartsWithoutRuntime = false;
|
|
108
106
|
}
|
|
109
107
|
function updateUi(ctx) {
|
|
110
108
|
ctx.ui.setStatus(STATUS_KEY, compactStatus(snapshot, (color, text) => ctx.ui.theme.fg(color, text)));
|
|
111
109
|
}
|
|
112
|
-
function setPendingPublication(value) {
|
|
113
|
-
pendingPublication = value === undefined ? undefined : structuredClone(value);
|
|
114
|
-
if (value === undefined)
|
|
115
|
-
delete snapshot.meta.pendingPublication;
|
|
116
|
-
else
|
|
117
|
-
snapshot.meta.pendingPublication = structuredClone(value);
|
|
118
|
-
}
|
|
119
110
|
function clearRunTransient() {
|
|
120
|
-
terminalEligible = false;
|
|
121
|
-
resolutionPending = false;
|
|
122
|
-
fallbackAttempts = 0;
|
|
123
|
-
fallbackFailureReported = false;
|
|
124
111
|
responseAwaitingReconciliation = false;
|
|
125
112
|
completedRunAccepted = false;
|
|
113
|
+
turnAcceptedForBackup = false;
|
|
126
114
|
compactionPlan = undefined;
|
|
127
115
|
compactionInFlight = false;
|
|
128
|
-
runAnchorTimestamp = undefined;
|
|
129
116
|
skillReads.clear();
|
|
130
117
|
artifactReads.clear();
|
|
131
118
|
}
|
|
@@ -134,29 +121,64 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
134
121
|
artifactReads.setCandidates([]);
|
|
135
122
|
artifactRefreshPending = true;
|
|
136
123
|
}
|
|
137
|
-
function
|
|
138
|
-
|
|
139
|
-
|
|
124
|
+
function refreshArtifactHints() {
|
|
125
|
+
if (!runtime?.view) {
|
|
126
|
+
artifactHints = {};
|
|
140
127
|
artifactInvalidations = [];
|
|
141
128
|
artifactReads.setCandidates([]);
|
|
142
129
|
return;
|
|
143
130
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
131
|
+
const paths = new Set();
|
|
132
|
+
for (const scope of ["global", "cwd", "session"])
|
|
133
|
+
for (const path of Object.keys(scopeStates[scope].artifacts))
|
|
134
|
+
paths.add(path);
|
|
135
|
+
const observations = new Map(inspectRegisteredArtifactPaths(paths).map((observation) => [observation.path, observation]));
|
|
136
|
+
const nextHints = {};
|
|
137
|
+
const nextInvalidations = new Map();
|
|
138
|
+
for (const scope of ["global", "cwd", "session"]) {
|
|
139
|
+
const provenance = runtime.artifactProvenance(scope);
|
|
140
|
+
for (const [path, metadata] of Object.entries(scopeStates[scope].artifacts)) {
|
|
141
|
+
delete nextHints[path];
|
|
142
|
+
nextInvalidations.delete(path);
|
|
143
|
+
if (metadata.kind === "skill")
|
|
144
|
+
continue;
|
|
145
|
+
const observed = observations.get(path);
|
|
146
|
+
if (observed?.kind !== "present")
|
|
147
|
+
continue;
|
|
148
|
+
const need = classifyArtifactCompilationNeed({ path, scope, sourceFingerprint: observed.fingerprint }, metadata, ORDINARY_ARTIFACT_COMPILER, false, provenance[path]);
|
|
149
|
+
if (need.kind !== "requires-compilation")
|
|
150
|
+
continue;
|
|
151
|
+
if (need.reason === "source-changed")
|
|
152
|
+
nextHints[path] = SOURCE_CHANGED_HINT;
|
|
153
|
+
nextInvalidations.set(path, { path, scope, reason: need.reason });
|
|
152
154
|
}
|
|
153
|
-
artifactReads.setCandidates(artifactInvalidations);
|
|
154
155
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
artifactHints = nextHints;
|
|
157
|
+
artifactInvalidations = [...nextInvalidations.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
158
|
+
artifactReads.setCandidates(artifactInvalidations);
|
|
159
|
+
}
|
|
160
|
+
function removeMissingRegisteredArtifacts(ctx) {
|
|
161
|
+
if (!snapshot.config.enabled || !runtime?.view)
|
|
162
|
+
return;
|
|
163
|
+
const owners = new Map();
|
|
164
|
+
for (const scope of ["global", "cwd", "session"])
|
|
165
|
+
for (const path of Object.keys(scopeStates[scope].artifacts)) {
|
|
166
|
+
owners.set(path, [...owners.get(path) ?? [], scope]);
|
|
167
|
+
}
|
|
168
|
+
const removals = {};
|
|
169
|
+
for (const observation of inspectRegisteredArtifactPaths(owners.keys())) {
|
|
170
|
+
if (observation.kind !== "missing")
|
|
171
|
+
continue;
|
|
172
|
+
for (const scope of owners.get(observation.path) ?? []) {
|
|
173
|
+
const artifacts = (removals[scope]?.artifacts ?? {});
|
|
174
|
+
removals[scope] = { ...(removals[scope] ?? {}), artifacts: { ...artifacts, [observation.path]: null } };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (Object.keys(removals).length > 0) {
|
|
178
|
+
const stage = stageAtomicScopePatches(scopeStates, removals, [], runtime.causalBasis());
|
|
179
|
+
commitStage(stage, ctx, false);
|
|
159
180
|
}
|
|
181
|
+
refreshArtifactHints();
|
|
160
182
|
}
|
|
161
183
|
function installScopeStates() {
|
|
162
184
|
scopeStates = runtime?.view ? runtime.states() : { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
@@ -174,38 +196,15 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
174
196
|
? [...new Set([...active, ...owned])]
|
|
175
197
|
: active.filter((name) => !owned.includes(name)));
|
|
176
198
|
}
|
|
177
|
-
function recordPublication(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (publication.push?.status === "pending") {
|
|
182
|
-
setPendingPublication({
|
|
183
|
-
commit: publication.push.commit,
|
|
184
|
-
error: publication.push.error ?? "unknown push failure",
|
|
185
|
-
});
|
|
186
|
-
ctx.ui.notify(`State Flow accepted durable commit ${publication.push.commit.slice(0, 12)}, but push is pending: ${publication.push.error}`, "warning");
|
|
187
|
-
}
|
|
188
|
-
else if (revision !== undefined) {
|
|
189
|
-
setPendingPublication(undefined);
|
|
190
|
-
}
|
|
199
|
+
function recordPublication(_publication, _ctx) {
|
|
200
|
+
// A passive view is not runtime authority; only accepted canonical publication establishes it.
|
|
201
|
+
branchStartsWithoutRuntime = false;
|
|
202
|
+
backupPending = true;
|
|
191
203
|
}
|
|
192
|
-
/**
|
|
204
|
+
/** Record accepted lifecycle persistence; Git backup is scheduled only after an accepted turn settles. */
|
|
193
205
|
function recordPolicyPublication(publication, ctx) {
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
recordPublication(publication, ctx);
|
|
197
|
-
const mode = snapshot.meta.remotePublication?.mode ?? "transition";
|
|
198
|
-
const target = publication.commit ?? publication.revision;
|
|
199
|
-
if (mode !== "turn-end" || target === undefined || !/^[0-9a-f]{40,64}$/.test(target))
|
|
200
|
-
return;
|
|
201
|
-
turnPublicationTarget = target;
|
|
202
|
-
try {
|
|
203
|
-
enqueueTurnPublication();
|
|
204
|
-
publicationWorker.launch();
|
|
205
|
-
}
|
|
206
|
-
catch (error) {
|
|
207
|
-
ctx.ui.notify(`State Flow accepted the local commit; remote publication is deferred: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
208
|
-
}
|
|
206
|
+
if (publication)
|
|
207
|
+
recordPublication(publication, ctx);
|
|
209
208
|
}
|
|
210
209
|
function commitStage(stage, ctx, finalizeRun) {
|
|
211
210
|
const acquiredArtifactPaths = new Set(artifactReads.successful.keys());
|
|
@@ -214,13 +213,9 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
214
213
|
committed = commitScopedTransition(snapshot, scopeStates, stage, (accepted, nextSnapshot) => {
|
|
215
214
|
if (!runtime?.view)
|
|
216
215
|
throw new Error("Temporal State Flow runtime is unavailable; reload before publishing");
|
|
217
|
-
const mode = nextSnapshot.meta.remotePublication?.mode ?? "transition";
|
|
218
216
|
const publication = runtime.publish(nextSnapshot, accepted !== undefined, accepted, {
|
|
219
|
-
pushRemote: mode === "transition",
|
|
220
217
|
provenance: stage.provenanceUpdates,
|
|
221
218
|
});
|
|
222
|
-
if (publication?.commit && mode === "turn-end")
|
|
223
|
-
turnPublicationTarget = publication.commit;
|
|
224
219
|
if (publication)
|
|
225
220
|
recordPublication(publication, ctx);
|
|
226
221
|
}, runtime.causalBasis(), { finalizeRun });
|
|
@@ -233,61 +228,13 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
233
228
|
if (!committed)
|
|
234
229
|
return false;
|
|
235
230
|
installScopeStates();
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
artifactReads.setCandidates(artifactInvalidations);
|
|
241
|
-
skillReads.clear();
|
|
242
|
-
artifactReads.clear();
|
|
243
|
-
}
|
|
231
|
+
artifactInvalidations = artifactInvalidations.filter(({ path }) => !acquiredArtifactPaths.has(path));
|
|
232
|
+
artifactReads.setCandidates(artifactInvalidations);
|
|
233
|
+
skillReads.clear();
|
|
234
|
+
artifactReads.clear();
|
|
244
235
|
persist();
|
|
245
236
|
return true;
|
|
246
237
|
}
|
|
247
|
-
function enqueueTurnPublication() {
|
|
248
|
-
const target = turnPublicationTarget;
|
|
249
|
-
turnPublicationTarget = undefined;
|
|
250
|
-
if (target)
|
|
251
|
-
publicationWorker.enqueue(target);
|
|
252
|
-
}
|
|
253
|
-
function shutdownPublicationWorkers(ctx) {
|
|
254
|
-
return publicationShutdown ??= publicationWorker.shutdown(PUBLICATION_SHUTDOWN_WAIT_MS).then((completed) => {
|
|
255
|
-
if (!completed)
|
|
256
|
-
ctx.ui.notify(`State Flow push cleanup is unconfirmed after ${PUBLICATION_SHUTDOWN_WAIT_MS}ms; worker leases remain held until child exit.`, "warning");
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
function retryPendingPush(ctx) {
|
|
260
|
-
if (pendingPublication === undefined || !runtime?.view)
|
|
261
|
-
return;
|
|
262
|
-
const mode = snapshot.meta.remotePublication?.mode ?? "transition";
|
|
263
|
-
if (mode !== "transition") {
|
|
264
|
-
const target = pendingPublication.commit;
|
|
265
|
-
setPendingPublication(undefined);
|
|
266
|
-
if (mode === "turn-end") {
|
|
267
|
-
turnPublicationTarget = target;
|
|
268
|
-
try {
|
|
269
|
-
enqueueTurnPublication();
|
|
270
|
-
publicationWorker.launch();
|
|
271
|
-
}
|
|
272
|
-
catch (error) {
|
|
273
|
-
ctx.ui.notify(`State Flow retained local state; asynchronous publication recovery is deferred: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
const result = pushGitCommit(repositoryRoot, pendingPublication.commit);
|
|
279
|
-
if (result.status !== "pending") {
|
|
280
|
-
setPendingPublication(undefined);
|
|
281
|
-
persist();
|
|
282
|
-
ctx.ui.notify(result.status === "local"
|
|
283
|
-
? `State Flow retained local-only durable commit ${result.commit.slice(0, 12)}; no remote configured.`
|
|
284
|
-
: `State Flow pushed pending durable commit ${result.commit.slice(0, 12)}.`, "info");
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
setPendingPublication({ commit: result.commit, error: result.error ?? "unknown push failure" });
|
|
288
|
-
persist();
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
238
|
function currentRehydrationPhase() {
|
|
292
239
|
return rehydrationPhase;
|
|
293
240
|
}
|
|
@@ -296,86 +243,36 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
296
243
|
const diagnosticStates = scopeStates;
|
|
297
244
|
const view = runtime?.view;
|
|
298
245
|
const diagnosticRecent = runtime?.recent() ?? [];
|
|
299
|
-
const durableStateError =
|
|
300
|
-
:
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
artifactFreshnessError = "durable global artifact registry is unavailable";
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
try {
|
|
308
|
-
const discovery = globalMarkdown.refresh(Object.keys(diagnosticStates.global.artifacts));
|
|
309
|
-
artifactFreshnessError = discovery.unavailable;
|
|
310
|
-
const plan = planArtifactInvalidation(discovery.sources, diagnosticStates.global.artifacts, ORDINARY_ARTIFACT_COMPILER, { removed: discovery.removed }, runtime?.artifactProvenance("global") ?? {});
|
|
311
|
-
staleArtifacts = [
|
|
312
|
-
...plan.requiresCompilation.map(({ path, reason }) => ({ scope: "global", path, reason })),
|
|
313
|
-
...plan.removed.map((path) => ({ scope: "global", path, reason: "source-removed" })),
|
|
314
|
-
];
|
|
315
|
-
}
|
|
316
|
-
catch (error) {
|
|
317
|
-
artifactFreshnessError = error instanceof Error ? error.message : String(error);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
246
|
+
const durableStateError = snapshot.meta.validation?.attempt === 0 ? snapshot.meta.validation.error
|
|
247
|
+
: view ? undefined : "no temporal runtime is selected on this branch";
|
|
248
|
+
const staleArtifacts = durableStateError === undefined
|
|
249
|
+
? artifactInvalidations.map(({ path, scope, reason }) => ({ scope: scope ?? "global", path, reason }))
|
|
250
|
+
: [];
|
|
320
251
|
const session = sessionAddress(ctx);
|
|
321
|
-
let publicationQueue;
|
|
322
|
-
let publicationQueueError;
|
|
323
|
-
try {
|
|
324
|
-
const destination = resolveGitPushDestination(repositoryRoot);
|
|
325
|
-
publicationQueue = destination ? loadPublicationQueue(publicationQueuePath(destination)) : undefined;
|
|
326
|
-
}
|
|
327
|
-
catch (error) {
|
|
328
|
-
publicationQueue = undefined;
|
|
329
|
-
publicationQueueError = error instanceof Error ? error.message : String(error);
|
|
330
|
-
}
|
|
331
252
|
return {
|
|
332
253
|
repositoryRoot,
|
|
333
254
|
cwdScopeKey: cwdScopeKey(cwd),
|
|
334
255
|
sessionScopeKey: sessionScopeKey(session.key),
|
|
335
256
|
scopeStates: diagnosticStates,
|
|
336
257
|
recent: diagnosticRecent,
|
|
258
|
+
historyLimit: config.historyLimit,
|
|
337
259
|
...(view === undefined ? {} : { temporal: {
|
|
338
260
|
head: structuredClone(view.lineage.at(-1)),
|
|
339
261
|
historyDepth: view.lineage.length - 1,
|
|
340
262
|
tailCounts: { global: view.scopes.global.patches.length, cwd: view.scopes.cwd.patches.length, session: view.scopes.session.patches.length },
|
|
341
263
|
} }),
|
|
342
264
|
staleArtifacts,
|
|
343
|
-
...(artifactFreshnessError === undefined ? {} : { artifactFreshnessError }),
|
|
344
265
|
...(durableStateError === undefined ? {} : { durableStateError }),
|
|
345
|
-
...(pendingPublication === undefined ? {} : { pendingPublication }),
|
|
346
|
-
...(publicationQueue === undefined ? {} : { publicationQueue }),
|
|
347
|
-
...(publicationQueueError === undefined ? {} : { publicationQueueError }),
|
|
348
266
|
};
|
|
349
267
|
}
|
|
350
|
-
function
|
|
351
|
-
if (!forkInitialization) {
|
|
352
|
-
try {
|
|
353
|
-
return runtime.prepareRestore(revision, legacy);
|
|
354
|
-
}
|
|
355
|
-
catch (error) {
|
|
356
|
-
if (error instanceof MissingSessionRuntimeError && typeof ctx.sessionManager.getHeader()?.parentSession === "string") {
|
|
357
|
-
throw new RevisionUnavailableError("State Flow checkpoint has no child-owned runtime; select a child checkpoint or resume the parent");
|
|
358
|
-
}
|
|
359
|
-
throw error;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
268
|
+
function forkSource(ctx) {
|
|
362
269
|
const file = ctx.sessionManager.getHeader()?.parentSession;
|
|
363
270
|
if (typeof file !== "string" || !isAbsolute(file))
|
|
364
271
|
throw new Error("State Flow fork requires a persisted native parent session");
|
|
365
272
|
const parent = readNativeSessionHeader(file);
|
|
366
273
|
if (parent.cwd !== resolve(ctx.cwd))
|
|
367
274
|
throw new Error("State Flow fork parent CWD identity mismatch");
|
|
368
|
-
|
|
369
|
-
const prepared = runtime.prepareFork(source, revision);
|
|
370
|
-
return { snapshot: prepared.snapshot, restore: () => {
|
|
371
|
-
const accepted = prepared.fork();
|
|
372
|
-
snapshot = accepted.snapshot;
|
|
373
|
-
recordPolicyPublication(accepted.publication, ctx);
|
|
374
|
-
// Copied Stop markers belong to the parent, including after a child reload/resume.
|
|
375
|
-
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { reset: true, owner: ctx.sessionManager.getSessionId() });
|
|
376
|
-
forkInitialization = false;
|
|
377
|
-
return snapshot;
|
|
378
|
-
} };
|
|
275
|
+
return resolveSessionAddress(parent.file, parent.id, parent.timestamp);
|
|
379
276
|
}
|
|
380
277
|
function restoreActiveBranch(ctx, sessionStartReason) {
|
|
381
278
|
clearRunTransient();
|
|
@@ -387,9 +284,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
387
284
|
telegramStartPending = false;
|
|
388
285
|
activeContext = ctx;
|
|
389
286
|
const session = sessionAddress(ctx);
|
|
390
|
-
runtime = new TemporalRuntime(ctx.cwd, session, repositoryRoot);
|
|
287
|
+
runtime = new TemporalRuntime(ctx.cwd, session, repositoryRoot, undefined, config.historyLimit);
|
|
391
288
|
installScopeStates();
|
|
392
|
-
pendingPublication = undefined;
|
|
393
289
|
branchHasSnapshot = false;
|
|
394
290
|
branchStartsWithoutRuntime = false;
|
|
395
291
|
forkInitialization = sessionStartReason === "fork";
|
|
@@ -397,18 +293,27 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
397
293
|
const branch = ctx.sessionManager.getBranch();
|
|
398
294
|
const discovery = discoverSnapshotData(branch);
|
|
399
295
|
let restoreSelected;
|
|
400
|
-
const recovery = recoverSnapshot(discovery.candidates, (
|
|
401
|
-
|
|
402
|
-
const prepared =
|
|
403
|
-
restoreSelected =
|
|
296
|
+
const recovery = recoverSnapshot(discovery.candidates, (checkpoint) => {
|
|
297
|
+
if (forkInitialization) {
|
|
298
|
+
const prepared = runtime.prepareBoundaryFork(forkSource(ctx), checkpoint);
|
|
299
|
+
restoreSelected = () => {
|
|
300
|
+
const accepted = prepared.fork();
|
|
301
|
+
snapshot = accepted.snapshot;
|
|
302
|
+
recordPolicyPublication(accepted.publication, ctx);
|
|
303
|
+
pi.appendEntry(PASSIVE_STOP_ENTRY_TYPE, { reset: true, owner: ctx.sessionManager.getSessionId() });
|
|
304
|
+
forkInitialization = false;
|
|
305
|
+
return snapshot;
|
|
306
|
+
};
|
|
404
307
|
return prepared.snapshot;
|
|
405
308
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
309
|
+
const prepared = runtime.prepareBoundaryRestore(checkpoint);
|
|
310
|
+
restoreSelected = () => {
|
|
311
|
+
const restored = prepared.restore();
|
|
312
|
+
const publication = runtime.acceptRestoredOrigin(restored);
|
|
313
|
+
recordPolicyPublication(publication, ctx);
|
|
314
|
+
return restored;
|
|
315
|
+
};
|
|
316
|
+
return prepared.snapshot;
|
|
412
317
|
});
|
|
413
318
|
branchStartsWithoutRuntime = recovery.disabledMarker === true
|
|
414
319
|
|| (discovery.candidates.length === 0 && discovery.errors.length === 0);
|
|
@@ -419,11 +324,9 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
419
324
|
}
|
|
420
325
|
else if (discovery.candidates.length > 0) {
|
|
421
326
|
snapshot = recovery.snapshot;
|
|
422
|
-
if (branchHasSnapshot &&
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (snapshot.meta.durableBase !== selectedRevision)
|
|
426
|
-
persist();
|
|
327
|
+
if (branchHasSnapshot && restoreSelected) {
|
|
328
|
+
snapshot = restoreSelected();
|
|
329
|
+
persist();
|
|
427
330
|
}
|
|
428
331
|
else if (branchHasSnapshot && snapshot.config.enabled) {
|
|
429
332
|
const publication = runtime.initialize(snapshot, true);
|
|
@@ -433,7 +336,6 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
433
336
|
}
|
|
434
337
|
else if (config.autoStart && isNewSession(sessionStartReason, branch)) {
|
|
435
338
|
snapshot = startEpisode(hasPriorConversation(branch));
|
|
436
|
-
snapshot.meta.remotePublication = serializeRemotePublicationPolicyDocument(resolveRemotePublicationPolicy(config.remotePublication, { legacyRuntime: false }));
|
|
437
339
|
runtime.prepare();
|
|
438
340
|
const initialization = runtime.initialize(snapshot, true);
|
|
439
341
|
recordPolicyPublication(initialization, ctx);
|
|
@@ -461,22 +363,16 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
461
363
|
meta: { ...snapshot.meta, validation: failure.meta.validation },
|
|
462
364
|
};
|
|
463
365
|
}
|
|
464
|
-
if (branchHasSnapshot && snapshot.meta.remotePublication === undefined) {
|
|
465
|
-
snapshot.meta.remotePublication = serializeRemotePublicationPolicyDocument(resolveRemotePublicationPolicy(undefined, { legacyRuntime: true }));
|
|
466
|
-
}
|
|
467
|
-
if (snapshot.config.enabled && snapshot.meta.remotePublication === undefined) {
|
|
468
|
-
snapshot.meta.remotePublication = serializeRemotePublicationPolicyDocument(resolveRemotePublicationPolicy(undefined, { legacyRuntime: true }));
|
|
469
|
-
}
|
|
470
|
-
pendingPublication = snapshot.meta.pendingPublication === undefined
|
|
471
|
-
? undefined
|
|
472
|
-
: structuredClone(snapshot.meta.pendingPublication);
|
|
473
366
|
if (!snapshot.config.enabled && snapshot.meta.validation?.attempt === 0) {
|
|
367
|
+
// A failed acceptance may have installed a prepared view; only shared passive reads may survive.
|
|
368
|
+
runtime = createRuntime(ctx);
|
|
369
|
+
installScopeStates();
|
|
474
370
|
ctx.ui.notify(`State Flow restored disabled: ${snapshot.meta.validation.error}`, "error");
|
|
475
371
|
}
|
|
476
372
|
if (retainsPhysicalSessionProjection(sessionStartReason) && runtime?.view) {
|
|
477
373
|
const boundary = findPassiveStopBoundary(ctx.sessionManager.getBranch(), ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE);
|
|
478
374
|
if (boundary !== undefined) {
|
|
479
|
-
const continuation = createPassiveContinuation(
|
|
375
|
+
const continuation = createPassiveContinuation(projectModelState(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)), boundary.at, boundary.from);
|
|
480
376
|
if (!snapshot.config.enabled)
|
|
481
377
|
passiveContinuation = continuation;
|
|
482
378
|
else if (snapshot.meta.bootstrap)
|
|
@@ -500,75 +396,6 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
500
396
|
function recordDiagnostic(error, category, ctx, extras = {}) {
|
|
501
397
|
diagnosticWriter.record(sessionAddress(ctx).id, ctx.cwd, error, category, extras);
|
|
502
398
|
}
|
|
503
|
-
function continueFallbackResolution(lastWarning) {
|
|
504
|
-
resolutionPending = true;
|
|
505
|
-
pi.sendMessage({
|
|
506
|
-
customType: VALIDATION_MESSAGE_TYPE,
|
|
507
|
-
content: lastWarning
|
|
508
|
-
? "This is the last State Flow fallback turn. The iteration's answer is preserved and will not change. Apply the final:true patch now: call patch_state with any remaining durable scope changes and final:true, or with {\"final\":true} when nothing remains. Do not restate the answer."
|
|
509
|
-
: "The iteration's answer is preserved as the final response; later turns cannot replace it. Apply the final:true patch: call patch_state with any durable scope changes from this iteration (including required artifact or Skill compilation) and final:true, or with {\"final\":true} when nothing remains to persist. Do not restate the answer.",
|
|
510
|
-
display: false,
|
|
511
|
-
}, { deliverAs: "steer", triggerTurn: true });
|
|
512
|
-
}
|
|
513
|
-
/** Preserve the primary answer as this iteration's response and steer bounded fallback turns whose only purpose is the final:true patch. */
|
|
514
|
-
function beginFallbackResolution(ctx, message, reason) {
|
|
515
|
-
responseAwaitingReconciliation = true;
|
|
516
|
-
fallbackAttempts = 0;
|
|
517
|
-
fallbackFailureReported = false;
|
|
518
|
-
recordDiagnostic(`Preserved the terminal draft as the iteration response; fallback resolution started: ${reason}`, "terminal-pending", ctx, {
|
|
519
|
-
content: message.content,
|
|
520
|
-
resolutionAttempt: 0,
|
|
521
|
-
terminalEligible,
|
|
522
|
-
});
|
|
523
|
-
continueFallbackResolution(MAX_FALLBACK_ATTEMPTS === 1);
|
|
524
|
-
}
|
|
525
|
-
/** Fallback turns never become the response; they exist only to supply the final:true patch. */
|
|
526
|
-
function resolveFallbackTurn(ctx, message) {
|
|
527
|
-
let resolved = terminalEligible;
|
|
528
|
-
if (resolved) {
|
|
529
|
-
try {
|
|
530
|
-
validateFinalEligibility(scopeStates, skillReads.successful.values(), runtime.causalBasis(), artifactReads.successful.values());
|
|
531
|
-
}
|
|
532
|
-
catch (error) {
|
|
533
|
-
resolved = false;
|
|
534
|
-
recordDiagnostic(error instanceof Error ? error.message : String(error), "terminal-pending", ctx, {
|
|
535
|
-
content: message.content,
|
|
536
|
-
resolutionAttempt: fallbackAttempts,
|
|
537
|
-
terminalEligible,
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
if (resolved) {
|
|
542
|
-
resolutionPending = false;
|
|
543
|
-
recordDiagnostic(`Fallback resolution obtained final:true after ${fallbackAttempts} fallback turn${fallbackAttempts === 1 ? "" : "s"}; the preserved answer stands`, "finalization", ctx, {
|
|
544
|
-
content: message.content,
|
|
545
|
-
resolutionAttempt: fallbackAttempts,
|
|
546
|
-
terminalEligible,
|
|
547
|
-
});
|
|
548
|
-
return { message: { ...message, role: "assistant", content: [] } };
|
|
549
|
-
}
|
|
550
|
-
fallbackAttempts = Math.min(MAX_FALLBACK_ATTEMPTS, fallbackAttempts + 1);
|
|
551
|
-
if (fallbackAttempts < MAX_FALLBACK_ATTEMPTS) {
|
|
552
|
-
recordDiagnostic(`Fallback turn ended without final:true (${fallbackAttempts}/${MAX_FALLBACK_ATTEMPTS})`, "terminal-pending", ctx, {
|
|
553
|
-
content: message.content,
|
|
554
|
-
resolutionAttempt: fallbackAttempts,
|
|
555
|
-
terminalEligible,
|
|
556
|
-
});
|
|
557
|
-
continueFallbackResolution(fallbackAttempts + 1 >= MAX_FALLBACK_ATTEMPTS);
|
|
558
|
-
return { message: { ...message, role: "assistant", content: [] } };
|
|
559
|
-
}
|
|
560
|
-
resolutionPending = false;
|
|
561
|
-
recordDiagnostic(`Fallback resolution exhausted without final:true (${fallbackAttempts}/${MAX_FALLBACK_ATTEMPTS}); the preserved response and current state remain`, "finalization", ctx, {
|
|
562
|
-
content: message.content,
|
|
563
|
-
resolutionAttempt: fallbackAttempts,
|
|
564
|
-
terminalEligible,
|
|
565
|
-
});
|
|
566
|
-
if (!fallbackFailureReported) {
|
|
567
|
-
fallbackFailureReported = true;
|
|
568
|
-
ctx.ui.notify(`State Flow kept the preserved answer; no final:true patch arrived after ${MAX_FALLBACK_ATTEMPTS} fallback turns, so the iteration closed with its current state.`, "warning");
|
|
569
|
-
}
|
|
570
|
-
return { message: { ...message, role: "assistant", content: [] } };
|
|
571
|
-
}
|
|
572
399
|
pi.registerTool({
|
|
573
400
|
name: READ_STATE_TOOL_NAME,
|
|
574
401
|
label: "Read State",
|
|
@@ -593,10 +420,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
593
420
|
throw new Error("read_state accepts path or paths, not both");
|
|
594
421
|
{
|
|
595
422
|
const paths = params.paths ?? [params.path];
|
|
423
|
+
if (paths.some((path) => /^session(?:\.|\[|$)/.test(path)))
|
|
424
|
+
assertSelectedBranchAvailable();
|
|
596
425
|
if (paths.length === 1 && /^(?:global|cwd|session)\.patches(?:\[\d+\])?$/.test(paths[0])) {
|
|
597
426
|
if (params.projection !== undefined && params.projection !== "value")
|
|
598
427
|
throw new Error("Scope patch paths support only the value projection");
|
|
599
|
-
const result = readStatePath(runtime.view, paths[0]);
|
|
428
|
+
const result = readStatePath(runtime.view, paths[0], config.historyLimit);
|
|
600
429
|
if (!("patch" in result))
|
|
601
430
|
throw new Error("Expected a scope patch path");
|
|
602
431
|
return {
|
|
@@ -604,7 +433,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
604
433
|
details: { path: paths[0], transitionId: result.boundary.id },
|
|
605
434
|
};
|
|
606
435
|
}
|
|
607
|
-
const result = readProjectedState(runtime.view, paths, params.projection);
|
|
436
|
+
const result = readProjectedState(runtime.view, paths, params.projection, config.historyLimit);
|
|
608
437
|
return {
|
|
609
438
|
content: [{ type: "text", text: `\n${JSON.stringify(result)}` }],
|
|
610
439
|
details: { ...(params.path === undefined ? { paths } : { path: params.path }), projection: params.projection ?? "value" },
|
|
@@ -619,10 +448,10 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
619
448
|
pi.registerTool({
|
|
620
449
|
name: PATCH_STATE_TOOL_NAME,
|
|
621
450
|
label: "Patch State",
|
|
622
|
-
description: "The sole State Flow semantic mutation protocol. Supply
|
|
623
|
-
promptSnippet: "Atomically patch global/cwd/session
|
|
451
|
+
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.",
|
|
452
|
+
promptSnippet: "Atomically patch one or more global/cwd/session scopes",
|
|
624
453
|
promptGuidelines: [
|
|
625
|
-
"Use patch_state for durable semantic changes
|
|
454
|
+
"Use patch_state only for material durable semantic changes; ordinary answers need no finalization call.",
|
|
626
455
|
"Use patch_state as reconciliation, not append-only notes: place new knowledge at the narrowest valid scope and remove superseded or completed state from touched branches.",
|
|
627
456
|
"Call patch_state alone in an assistant response; after its acknowledgement, further reasoning, tools, and later patch_state calls remain allowed.",
|
|
628
457
|
],
|
|
@@ -631,9 +460,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
631
460
|
global: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional global semantic patch" })),
|
|
632
461
|
cwd: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional project semantic patch" })),
|
|
633
462
|
session: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Optional session semantic patch" })),
|
|
634
|
-
final: Type.Optional(Type.Boolean({ description: "Set exactly true to permit this iteration to finish at a later turn_end" })),
|
|
635
463
|
}, { additionalProperties: false }),
|
|
636
|
-
prepareArguments: normalizePatchStateArguments,
|
|
637
464
|
renderResult(result, { isPartial }, theme, context) {
|
|
638
465
|
const text = result.content.find((block) => block.type === "text")?.text ?? "";
|
|
639
466
|
if (context.isError)
|
|
@@ -646,17 +473,16 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
646
473
|
try {
|
|
647
474
|
if (!passiveToolsAvailable())
|
|
648
475
|
throw new Error("State Flow tools are disabled by configuration");
|
|
476
|
+
assertSelectedBranchAvailable();
|
|
649
477
|
if (signal?.aborted)
|
|
650
478
|
throw new Error("State Flow patch was aborted before materialization");
|
|
651
479
|
if (!isObject(params))
|
|
652
480
|
throw new Error("patch_state requires an object");
|
|
653
|
-
const allowed = new Set(["global", "cwd", "session"
|
|
481
|
+
const allowed = new Set(["global", "cwd", "session"]);
|
|
654
482
|
for (const key of Object.keys(params)) {
|
|
655
483
|
if (!allowed.has(key))
|
|
656
484
|
throw new Error(`patch_state does not accept field ${key}`);
|
|
657
485
|
}
|
|
658
|
-
if (Object.hasOwn(params, "final") && typeof params.final !== "boolean")
|
|
659
|
-
throw new Error("patch_state final must be a Boolean when supplied");
|
|
660
486
|
const patches = {};
|
|
661
487
|
for (const scope of ["global", "cwd", "session"]) {
|
|
662
488
|
if (!Object.hasOwn(params, scope))
|
|
@@ -669,18 +495,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
669
495
|
patches[scope] = patch;
|
|
670
496
|
}
|
|
671
497
|
const scopes = Object.keys(patches);
|
|
672
|
-
if (scopes.length === 0)
|
|
673
|
-
|
|
674
|
-
return { content: [{ type: "text", text: "\nState unchanged; iteration remains non-terminal." }], details: { final: false } };
|
|
675
|
-
}
|
|
676
|
-
if (params.final !== true)
|
|
677
|
-
throw new Error('patch_state requires at least one scope patch or {"final":true}');
|
|
678
|
-
if (!snapshot.config.enabled)
|
|
679
|
-
return { content: [{ type: "text", text: "\nState unchanged; passive turns have no terminal barrier." }], details: { final: false } };
|
|
680
|
-
validateFinalEligibility(scopeStates, skillReads.successful.values(), runtime.causalBasis(), artifactReads.successful.values());
|
|
681
|
-
terminalEligible = true;
|
|
682
|
-
return { content: [{ type: "text", text: "\nState iteration is terminal-eligible." }], details: { final: true } };
|
|
683
|
-
}
|
|
498
|
+
if (scopes.length === 0)
|
|
499
|
+
throw new Error("patch_state requires at least one materially changed scope patch");
|
|
684
500
|
if (!runtime?.view) {
|
|
685
501
|
runtime ??= createRuntime(ctx);
|
|
686
502
|
runtime.prepare();
|
|
@@ -689,20 +505,22 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
689
505
|
installScopeStates();
|
|
690
506
|
branchHasSnapshot = true;
|
|
691
507
|
}
|
|
692
|
-
runtime.migrateLegacyStorage();
|
|
693
508
|
installScopeStates();
|
|
509
|
+
for (const acquired of artifactReads.successful.values()) {
|
|
510
|
+
const observation = inspectRegisteredArtifactPaths([acquired.path])[0];
|
|
511
|
+
if (acquired.sourceFingerprint === undefined || observation?.kind !== "present"
|
|
512
|
+
|| !sameArtifactSourceFingerprint(acquired.sourceFingerprint, observation.fingerprint)) {
|
|
513
|
+
throw new Error(`Artifact source changed after acquisition: ${acquired.path}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
694
516
|
const stage = stageAtomicScopePatches(scopeStates, patches, skillReads.successful.values(), runtime.causalBasis(), artifactReads.successful.values());
|
|
695
517
|
const semanticChange = ["global", "cwd", "session"].some((scope) => !sameJson(scopeStates[scope], stage.nextStates[scope]));
|
|
696
518
|
const provenanceChange = Object.values(stage.provenanceUpdates).some((updates) => Object.keys(updates).length > 0);
|
|
697
519
|
if (!semanticChange && !provenanceChange)
|
|
698
|
-
throw new Error(
|
|
520
|
+
throw new Error("patch_state scope patches must materially update state or required provenance");
|
|
699
521
|
commitStage(stage, ctx, false);
|
|
700
|
-
if (params.final === true && snapshot.config.enabled) {
|
|
701
|
-
terminalEligible = true;
|
|
702
|
-
}
|
|
703
522
|
updateUi(ctx);
|
|
704
|
-
|
|
705
|
-
return { content: [{ type: "text", text: `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}${publication}.` }], details: { scopes, final: params.final === true, step: snapshot.meta.step } };
|
|
523
|
+
return { content: [{ type: "text", text: `\nState materialized atomically at ${scopes.join("+")} scope${scopes.length === 1 ? "" : "s"}.` }], details: { scopes, step: snapshot.meta.step } };
|
|
706
524
|
}
|
|
707
525
|
catch (error) {
|
|
708
526
|
let attempted;
|
|
@@ -716,8 +534,6 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
716
534
|
input: attempted,
|
|
717
535
|
tool: PATCH_STATE_TOOL_NAME,
|
|
718
536
|
toolCallId,
|
|
719
|
-
resolutionAttempt: fallbackAttempts,
|
|
720
|
-
terminalEligible,
|
|
721
537
|
});
|
|
722
538
|
throw separatedFailure(error);
|
|
723
539
|
}
|
|
@@ -733,35 +549,30 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
733
549
|
const previousArtifactRefreshPending = artifactRefreshPending;
|
|
734
550
|
const previousArtifactInvalidations = structuredClone(artifactInvalidations);
|
|
735
551
|
try {
|
|
736
|
-
if (!runtime?.view
|
|
737
|
-
|
|
552
|
+
if (!branchStartsWithoutRuntime && (!runtime?.view || snapshot.meta.validation?.attempt === 0)) {
|
|
553
|
+
const retryFork = forkInitialization;
|
|
554
|
+
restoreActiveBranch(ctx, retryFork ? "fork" : undefined);
|
|
738
555
|
}
|
|
556
|
+
assertSelectedBranchAvailable();
|
|
557
|
+
if (!runtime?.view && !branchStartsWithoutRuntime)
|
|
558
|
+
throw new Error("Selected State Flow boundary is unavailable; restore its canonical files before starting State Flow");
|
|
739
559
|
const branch = ctx.sessionManager.getBranch();
|
|
740
560
|
activeContext = ctx;
|
|
741
561
|
runtime ??= createRuntime(ctx);
|
|
742
562
|
if (branchStartsWithoutRuntime)
|
|
743
563
|
runtime.prepare();
|
|
744
|
-
runtime.migrateLegacyStorage();
|
|
745
564
|
const bootstrap = (!branchHasSnapshot || !snapshot.config.enabled)
|
|
746
565
|
&& (hasPriorConversation(branch) || previousPassiveContinuation !== undefined);
|
|
747
|
-
if (!runtime.view && snapshot.meta.durableBase) {
|
|
748
|
-
snapshot = prepareBranchRestore(ctx, snapshot.meta.durableBase, snapshot).restore();
|
|
749
|
-
setPendingPublication(snapshot.meta.pendingPublication);
|
|
750
|
-
branchHasSnapshot = true;
|
|
751
|
-
}
|
|
752
566
|
const existingBranch = branchHasSnapshot;
|
|
753
567
|
snapshot = existingBranch
|
|
754
568
|
? resumeEpisode(snapshot, bootstrap)
|
|
755
569
|
: startEpisode(bootstrap);
|
|
756
|
-
if (snapshot.meta.remotePublication === undefined) {
|
|
757
|
-
snapshot.meta.remotePublication = serializeRemotePublicationPolicyDocument(resolveRemotePublicationPolicy(existingBranch ? undefined : config.remotePublication, { legacyRuntime: existingBranch }));
|
|
758
|
-
}
|
|
759
570
|
branchHasSnapshot = true;
|
|
760
571
|
// Activation reasserts the selected session stream as a complete cohort. A resumed
|
|
761
572
|
// branch may have no reusable temporal revision after an extension upgrade; a
|
|
762
573
|
// runtime-only write would then reject its selected session as an omitted change.
|
|
763
574
|
const publication = runtime.view
|
|
764
|
-
? runtime.
|
|
575
|
+
? runtime.publish(snapshot, true)
|
|
765
576
|
: runtime.initialize(snapshot, true, undefined, branchStartsWithoutRuntime);
|
|
766
577
|
recordPolicyPublication(publication, ctx);
|
|
767
578
|
installScopeStates();
|
|
@@ -807,25 +618,18 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
807
618
|
function stopStateFlow(ctx) {
|
|
808
619
|
if (!activeContext)
|
|
809
620
|
restoreActiveBranch(ctx);
|
|
621
|
+
assertSelectedBranchAvailable();
|
|
810
622
|
telegramStartPending = false;
|
|
811
|
-
|
|
812
|
-
let current = snapshot;
|
|
813
|
-
if (!selected?.view && snapshot.meta.durableBase) {
|
|
814
|
-
selected = createRuntime(ctx);
|
|
815
|
-
current = selected.restore(snapshot.meta.durableBase, snapshot);
|
|
816
|
-
}
|
|
623
|
+
const current = snapshot;
|
|
817
624
|
const stoppedAt = Date.now();
|
|
818
|
-
const
|
|
819
|
-
const
|
|
820
|
-
|
|
625
|
+
const stopped = stopEpisode(current);
|
|
626
|
+
const publication = !branchStartsWithoutRuntime && runtime?.view ? runtime.publish(stopped) : undefined;
|
|
627
|
+
installScopeStates();
|
|
628
|
+
// Freeze the accepted shared adoption, not the pre-publication cache.
|
|
629
|
+
const exitHandoff = current.config.enabled && runtime?.view
|
|
630
|
+
? createPassiveContinuation(projectModelState(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)), stoppedAt, ctx.isIdle() ? undefined : runAnchorTimestamp)
|
|
821
631
|
: undefined;
|
|
822
632
|
const retainedHandoff = exitHandoff ?? (!current.config.enabled ? passiveContinuation : undefined);
|
|
823
|
-
const stopped = stopEpisode(current);
|
|
824
|
-
const publication = selected?.view ? selected.publish(stopped) : undefined;
|
|
825
|
-
if (selected !== runtime) {
|
|
826
|
-
runtime = selected;
|
|
827
|
-
installScopeStates();
|
|
828
|
-
}
|
|
829
633
|
snapshot = stopped;
|
|
830
634
|
recordPolicyPublication(publication, ctx);
|
|
831
635
|
branchHasSnapshot = true;
|
|
@@ -861,13 +665,12 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
861
665
|
startPending: telegramStartPending,
|
|
862
666
|
}),
|
|
863
667
|
state: (scope) => {
|
|
668
|
+
if (scope === "session")
|
|
669
|
+
assertSelectedBranchAvailable();
|
|
864
670
|
const selected = scope === "effective"
|
|
865
671
|
? overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)
|
|
866
672
|
: scopeStates[scope];
|
|
867
|
-
return {
|
|
868
|
-
...projectStateForModel(selected),
|
|
869
|
-
...(Object.hasOwn(selected, "lazy") ? { lazy: structuredClone(selected.lazy) } : {}),
|
|
870
|
-
};
|
|
673
|
+
return { ...projectModelState(selected), lazy: structuredClone(selected.lazy) };
|
|
871
674
|
},
|
|
872
675
|
canStartNow: () => activeContext === undefined || activeContext.isIdle(),
|
|
873
676
|
start: () => {
|
|
@@ -895,62 +698,65 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
895
698
|
});
|
|
896
699
|
void telegram.ensure();
|
|
897
700
|
pi.on("before_agent_start", (event, ctx) => {
|
|
701
|
+
// Native run identity is independent of semantic enablement and survives mode toggles.
|
|
702
|
+
runAnchorTimestamp = undefined;
|
|
898
703
|
if (!snapshot.config.enabled) {
|
|
899
704
|
if (!config.passiveBootstrap || !runtime?.view)
|
|
900
705
|
return;
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
};
|
|
706
|
+
(event.systemPromptOptions.sections ??= {}).state_flow = PASSIVE_MEMORY_PROTOCOL;
|
|
707
|
+
return;
|
|
904
708
|
}
|
|
905
709
|
skillReads.clear();
|
|
906
710
|
artifactReads.clear();
|
|
907
|
-
if (artifactRefreshPending)
|
|
908
|
-
refreshArtifactInvalidations(ctx);
|
|
909
|
-
terminalEligible = false;
|
|
910
|
-
resolutionPending = false;
|
|
911
|
-
fallbackAttempts = 0;
|
|
912
|
-
fallbackFailureReported = false;
|
|
913
711
|
responseAwaitingReconciliation = false;
|
|
914
712
|
completedRunAccepted = false;
|
|
915
713
|
const rotatesRun = snapshot.meta.specification !== undefined;
|
|
916
714
|
if (rotatesRun && rehydrationPhase !== "new-bootstrap" && rehydrationPhase !== "resume-bootstrap")
|
|
917
715
|
rehydrationPhase = "step";
|
|
918
|
-
if (prepareRun(snapshot, event.prompt))
|
|
919
|
-
runAnchorTimestamp = undefined;
|
|
716
|
+
if (prepareRun(snapshot, event.prompt))
|
|
920
717
|
persist();
|
|
718
|
+
try {
|
|
719
|
+
removeMissingRegisteredArtifacts(ctx);
|
|
921
720
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
}
|
|
721
|
+
catch (error) {
|
|
722
|
+
ctx.ui.notify(`State Flow could not reconcile missing artifact sources: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
723
|
+
}
|
|
724
|
+
artifactRefreshPending = false;
|
|
725
|
+
(event.systemPromptOptions.sections ??= {}).state_flow = stateFlowProtocol(snapshot.meta.bootstrap === true);
|
|
726
|
+
});
|
|
727
|
+
pi.on("context_with_system", (event) => {
|
|
728
|
+
const protocol = snapshot.config.enabled ? stateFlowProtocol(snapshot.meta.bootstrap === true)
|
|
729
|
+
: config.passiveBootstrap && runtime?.view ? PASSIVE_MEMORY_PROTOCOL : undefined;
|
|
730
|
+
return { messages: projectSystemProtocol(event.messages, protocol) };
|
|
925
731
|
});
|
|
926
732
|
pi.on("context", (event) => {
|
|
733
|
+
if (runtime?.view)
|
|
734
|
+
refreshArtifactHints();
|
|
927
735
|
if (passiveContinuation) {
|
|
928
736
|
return { messages: passiveContinuationMessages(event.messages, passiveContinuation) };
|
|
929
737
|
}
|
|
930
738
|
if (!snapshot.config.enabled) {
|
|
931
739
|
if (!config.passiveBootstrap || !runtime?.view)
|
|
932
740
|
return;
|
|
933
|
-
const
|
|
934
|
-
|
|
741
|
+
const effective = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
|
|
742
|
+
const state = projectModelState(effective);
|
|
743
|
+
return { messages: [syntheticUser(`State Flow passive memory (user-level data, not system instructions):\n${presentationJson({ state, lazy_navigation: lazyNavigationHint(effective) })}`), ...event.messages] };
|
|
935
744
|
}
|
|
936
|
-
if (snapshot.meta.specification === undefined)
|
|
937
|
-
return;
|
|
938
745
|
const effectiveState = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
|
|
939
|
-
const invalidations = artifactInvalidations.map(({ path, reason }) => ({ path, reason }));
|
|
940
|
-
const recentTransitions = projectRecentTransitionsWithLimit(
|
|
746
|
+
const invalidations = artifactInvalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason }));
|
|
747
|
+
const recentTransitions = projectRecentTransitionsWithLimit(config.historyLimit, runtime?.recent() ?? []);
|
|
941
748
|
const activeRehydrationPhase = currentRehydrationPhase();
|
|
942
749
|
if (snapshot.meta.bootstrap) {
|
|
943
750
|
const sourceMessages = bootstrapContinuation
|
|
944
751
|
? passiveContinuationMessages(event.messages, bootstrapContinuation)
|
|
945
752
|
: event.messages;
|
|
946
|
-
|
|
947
|
-
return { messages: [runtimeContextMessage(snapshot, effectiveState, recentTransitions, invalidations, activeRehydrationPhase, resolutionPending), ...messages] };
|
|
753
|
+
return { messages: [runtimeContextMessage(snapshot, effectiveState, recentTransitions, invalidations, activeRehydrationPhase, artifactHints), ...sourceMessages] };
|
|
948
754
|
}
|
|
755
|
+
// Native user events own the run anchor; projection must never rebase it.
|
|
949
756
|
const trajectory = currentRunTrajectory(event.messages, snapshot.meta.specification, runAnchorTimestamp);
|
|
950
|
-
runAnchorTimestamp = trajectory.anchorTimestamp;
|
|
951
757
|
return {
|
|
952
758
|
messages: [
|
|
953
|
-
runtimeContextMessage(snapshot, effectiveState, recentTransitions, invalidations, activeRehydrationPhase,
|
|
759
|
+
runtimeContextMessage(snapshot, effectiveState, recentTransitions, invalidations, activeRehydrationPhase, artifactHints),
|
|
954
760
|
...trajectory.messages,
|
|
955
761
|
],
|
|
956
762
|
};
|
|
@@ -992,11 +798,11 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
992
798
|
artifactReads.recordEnd(event.toolCallId, event.toolName, event.isError);
|
|
993
799
|
});
|
|
994
800
|
pi.on("message_end", (event, ctx) => {
|
|
995
|
-
|
|
996
|
-
return;
|
|
997
|
-
// Capture the first native user boundary even during bootstrap; steering keeps that anchor.
|
|
801
|
+
// Observe actual user events even while disabled; Start/Stop cannot invent or erase them.
|
|
998
802
|
if (event.message.role === "user" && runAnchorTimestamp === undefined)
|
|
999
803
|
runAnchorTimestamp = event.message.timestamp;
|
|
804
|
+
if (!snapshot.config.enabled)
|
|
805
|
+
return;
|
|
1000
806
|
if (event.message.role !== "assistant")
|
|
1001
807
|
return;
|
|
1002
808
|
const message = event.message;
|
|
@@ -1006,20 +812,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1006
812
|
}
|
|
1007
813
|
if (message.stopReason === "length" || message.stopReason === "error") {
|
|
1008
814
|
responseAwaitingReconciliation = false;
|
|
1009
|
-
recordDiagnostic(`Assistant response ended with ${message.stopReason}`, "finalization", ctx, { content: message.content
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
if (resolutionPending)
|
|
1013
|
-
return resolveFallbackTurn(ctx, message);
|
|
1014
|
-
if (!terminalEligible) {
|
|
1015
|
-
beginFallbackResolution(ctx, message, "the terminal draft ended without State Flow eligibility");
|
|
1016
|
-
return;
|
|
1017
|
-
}
|
|
1018
|
-
try {
|
|
1019
|
-
validateFinalEligibility(scopeStates, skillReads.successful.values(), runtime.causalBasis(), artifactReads.successful.values());
|
|
1020
|
-
}
|
|
1021
|
-
catch (error) {
|
|
1022
|
-
beginFallbackResolution(ctx, message, error instanceof Error ? error.message : String(error));
|
|
815
|
+
recordDiagnostic(`Assistant response ended with ${message.stopReason}`, "finalization", ctx, { content: message.content });
|
|
1023
816
|
return;
|
|
1024
817
|
}
|
|
1025
818
|
responseAwaitingReconciliation = true;
|
|
@@ -1040,17 +833,15 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1040
833
|
responseCommitted = true;
|
|
1041
834
|
bootstrapContinuation = undefined;
|
|
1042
835
|
rehydrationPhase = "step";
|
|
1043
|
-
enqueueTurnPublication();
|
|
1044
|
-
if (snapshot.meta.remotePublication?.mode === "turn-end")
|
|
1045
|
-
publicationWorker.launch();
|
|
1046
836
|
completedRunAccepted = !wasBootstrap;
|
|
837
|
+
turnAcceptedForBackup = true;
|
|
1047
838
|
}
|
|
1048
839
|
catch (error) {
|
|
1049
840
|
if (!responseCommitted && specification !== undefined)
|
|
1050
841
|
snapshot.meta.specification = specification;
|
|
1051
842
|
recordDiagnostic(error instanceof Error ? error.message : String(error), "finalization", ctx);
|
|
1052
843
|
ctx.ui.notify(responseCommitted
|
|
1053
|
-
? `State Flow
|
|
844
|
+
? `State Flow accepted the final response; a post-acceptance lifecycle update failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1054
845
|
: `State Flow could not reconcile the final response: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
1055
846
|
}
|
|
1056
847
|
finally {
|
|
@@ -1068,42 +859,56 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1068
859
|
return result;
|
|
1069
860
|
return { compaction: result };
|
|
1070
861
|
});
|
|
1071
|
-
pi.on("
|
|
862
|
+
pi.on("agent_before_settle", (_event, ctx) => {
|
|
863
|
+
if (!turnAcceptedForBackup)
|
|
864
|
+
return;
|
|
865
|
+
turnAcceptedForBackup = false;
|
|
866
|
+
if (!backupPending)
|
|
867
|
+
return;
|
|
868
|
+
backupPending = false;
|
|
869
|
+
try {
|
|
870
|
+
if (existsSync(join(repositoryRoot, ".git")))
|
|
871
|
+
backupCurrentStateFlowFiles(repositoryRoot);
|
|
872
|
+
}
|
|
873
|
+
catch (error) {
|
|
874
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
875
|
+
recordDiagnostic(message, "publication-conflict", ctx);
|
|
876
|
+
ctx.ui.notify(`State Flow accepted canonical state; Git backup failed: ${message}`, "warning");
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
pi.on("agent_settled", async (_event, ctx) => {
|
|
1072
880
|
if (telegramStartPending && !snapshot.config.enabled) {
|
|
1073
881
|
telegramStartPending = false;
|
|
1074
882
|
startStateFlow(ctx);
|
|
1075
883
|
}
|
|
1076
|
-
if (snapshot.
|
|
1077
|
-
|
|
1078
|
-
if (!completedRunAccepted || compactionStopped || !snapshot.config.enabled || snapshot.meta.bootstrap || resolutionPending
|
|
1079
|
-
|| compactionInFlight || !ctx.isIdle() || ctx.hasPendingMessages() || !snapshot.meta.durableBase
|
|
884
|
+
if (!completedRunAccepted || compactionStopped || !snapshot.config.enabled || snapshot.meta.bootstrap
|
|
885
|
+
|| compactionInFlight || !ctx.isIdle() || ctx.hasPendingMessages() || !runtime?.view
|
|
1080
886
|
|| !shouldRequestStateFlowCompaction(ctx.getContextUsage()))
|
|
1081
887
|
return;
|
|
1082
888
|
completedRunAccepted = false;
|
|
1083
889
|
const entries = ctx.sessionManager.buildContextEntries();
|
|
1084
890
|
if (!hasCompactionSizedTranscript(entries))
|
|
1085
891
|
return;
|
|
1086
|
-
const plan = planStateFlowCompaction(entries,
|
|
892
|
+
const plan = planStateFlowCompaction(entries, runtime.causalBasis(), snapshot.meta.step, runAnchorTimestamp);
|
|
1087
893
|
if (!plan)
|
|
1088
894
|
return;
|
|
1089
895
|
compactionPlan = plan;
|
|
1090
896
|
compactionInFlight = true;
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
897
|
+
// Pi dispatches deferred companion prompts after all settled handlers return.
|
|
898
|
+
await new Promise((resolve) => {
|
|
899
|
+
const finished = () => { compactionPlan = undefined; compactionInFlight = false; resolve(); };
|
|
900
|
+
ctx.compact({ customInstructions: compactionMarker, onComplete: finished, onError: finished });
|
|
1095
901
|
});
|
|
1096
902
|
});
|
|
1097
903
|
pi.on("session_start", (event, ctx) => {
|
|
904
|
+
runAnchorTimestamp = undefined;
|
|
1098
905
|
rehydrationPhase = event.reason === "resume" ? "resume-bootstrap" : "new-bootstrap";
|
|
1099
906
|
restoreActiveBranch(ctx, event.reason);
|
|
1100
|
-
retryPendingPush(ctx);
|
|
1101
|
-
if (snapshot.meta.remotePublication?.mode === "turn-end")
|
|
1102
|
-
publicationWorker.launch();
|
|
1103
907
|
updateUi(ctx);
|
|
1104
908
|
void telegram.ensure();
|
|
1105
909
|
});
|
|
1106
910
|
pi.on("session_tree", (_event, ctx) => {
|
|
911
|
+
runAnchorTimestamp = undefined;
|
|
1107
912
|
restoreActiveBranch(ctx);
|
|
1108
913
|
});
|
|
1109
914
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
@@ -1111,6 +916,5 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1111
916
|
compactionStopped = true;
|
|
1112
917
|
completedRunAccepted = false;
|
|
1113
918
|
telegram.dispose();
|
|
1114
|
-
return shutdownPublicationWorkers(ctx);
|
|
1115
919
|
});
|
|
1116
920
|
}
|