@llblab/pi-kit 0.15.0 → 0.17.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 +12 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +12 -12
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +2 -13
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +31 -0
- package/node_modules/@llblab/pi-state-flow/README.md +11 -7
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +5 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +17 -16
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +24 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +4 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +2 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +15 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +4 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +7 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +124 -274
- package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +20 -23
- package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +8 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +29 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +18 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +44 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +2 -2
- package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +55 -21
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +7 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +65 -17
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +17 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +102 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +43 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +268 -10
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +34 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +17 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +38 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +5 -5
- package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +20 -24
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +11 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +14 -4
- package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +2 -0
- package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +55 -20
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +9 -6
- package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +10 -3
- package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +8 -3
- package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +79 -0
- package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +23 -121
- package/node_modules/@llblab/pi-state-flow/docs/README.md +1 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +27 -20
- package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +1 -1
- package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +3 -2
- package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +513 -0
- package/node_modules/@llblab/pi-state-flow/docs/usage.md +13 -10
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +18 -15
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +24 -0
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +4 -3
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +14 -5
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +5 -0
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +119 -262
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +23 -22
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +8 -4
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +31 -3
- package/node_modules/@llblab/pi-state-flow/lib/logging.ts +53 -1
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +51 -20
- package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +61 -17
- package/node_modules/@llblab/pi-state-flow/lib/publication.ts +96 -0
- package/node_modules/@llblab/pi-state-flow/lib/query.ts +261 -9
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +32 -4
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +45 -0
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +22 -25
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +22 -6
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +1 -1
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +46 -18
- package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +20 -6
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +8 -3
- package/node_modules/@llblab/pi-state-flow/package.json +1 -1
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +79 -0
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +23 -121
- package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -0
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +7 -0
- package/node_modules/@llblab/pi-telegram/README.md +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +134 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +297 -16
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +11 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +68 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +4 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +17 -13
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +55 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +23 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +20 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +18 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +152 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +5 -3
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +3 -3
- package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +4 -3
- package/node_modules/@llblab/pi-telegram/docs/sections.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +2 -1
- package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +3 -0
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +466 -21
- package/node_modules/@llblab/pi-telegram/lib/delivery.ts +15 -0
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +77 -9
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +25 -10
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +3 -0
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +84 -17
- package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +30 -0
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +199 -6
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +5 -2
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
|
@@ -2,100 +2,46 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { StringEnum, Type } from "@earendil-works/pi-ai";
|
|
3
3
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { Text } from "@earendil-works/pi-tui";
|
|
5
|
-
import { assistantToolCallCount, finalizedAssistantResponse, stateFlowProtocol } from "./protocol.js";
|
|
6
|
-
import { createPassiveContinuation, currentRunTrajectory, passiveContinuationMessages, runtimeContextMessage, VALIDATION_MESSAGE_TYPE, withoutPrivateValidation } from "./context.js";
|
|
5
|
+
import { assistantToolCallCount, finalizedAssistantResponse, formatPatchStateArguments, normalizePatchStateArguments, separatedFailure, separatedOutput, stateFlowProtocol } from "./protocol.js";
|
|
6
|
+
import { createPassiveContinuation, currentRunTrajectory, lazyNavigationHint, passiveContinuationMessages, runtimeContextMessage, syntheticUser, VALIDATION_MESSAGE_TYPE, withoutPrivateValidation } from "./context.js";
|
|
7
7
|
import { ArtifactReadTracker } from "./acquisition.js";
|
|
8
8
|
import { loadStateFlowConfig } from "./config.js";
|
|
9
9
|
import { createStateFlowTelegramAdapter } from "./telegram.js";
|
|
10
|
-
import { isAbsolute,
|
|
10
|
+
import { isAbsolute, resolve } from "node:path";
|
|
11
11
|
import { SkillReadTracker } from "./skills.js";
|
|
12
12
|
import { emptySnapshot, migrationFailure, persistableSnapshot, RevisionUnavailableError } from "./snapshot.js";
|
|
13
13
|
import { readNativeSessionHeader } from "./continuation.js";
|
|
14
14
|
import { MissingSessionRuntimeError, SharedScopeRemovalConflictError, TemporalRuntime } from "./runtime.js";
|
|
15
15
|
import { emptyState, overlayStates, projectStateForModel } from "./state.js";
|
|
16
16
|
import { commitScopedTransition, stageAtomicScopePatches, stageScopedTransition, validateFinalEligibility } from "./transition.js";
|
|
17
|
-
import { discoverSnapshotData, hasPriorConversation, isNewSession, SNAPSHOT_ENTRY_TYPE } from "./session.js";
|
|
17
|
+
import { discoverSnapshotData, findAssistantToolBatch, findPassiveStopBoundary, hasPriorConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.js";
|
|
18
18
|
import { compactStatus, detailedStatus, STATUS_KEY } from "./status.js";
|
|
19
|
-
import { prepareRun, resumeEpisode, startEpisode, stopEpisode } from "./episode.js";
|
|
19
|
+
import { completeRun, prepareRun, resumeEpisode, startEpisode, stopEpisode } from "./episode.js";
|
|
20
20
|
import { recoverSnapshot } from "./recovery.js";
|
|
21
|
-
import { resolveRemotePublicationPolicy, serializeRemotePublicationPolicyDocument } from "./publication.js";
|
|
22
|
-
import { coalescePublicationTarget, createPublicationQueue } from "./publication.js";
|
|
23
|
-
import { acquirePublicationWorkerLease, loadPublicationQueue, publicationQueuePath, removePublicationQueue, savePublicationQueue } from "./publication.js";
|
|
24
|
-
import { runPublicationWorker } from "./publication.js";
|
|
21
|
+
import { loadPublicationQueue, publicationQueuePath, PublicationWorkerController, resolveRemotePublicationPolicy, serializeRemotePublicationPolicyDocument } from "./publication.js";
|
|
25
22
|
import { getKnowledgeRoot, GlobalMarkdownDiscovery } from "./discovery.js";
|
|
26
|
-
import { isObject, sameJson } from "./json.js";
|
|
27
|
-
import { readStatePath } from "./query.js";
|
|
23
|
+
import { canonicalJson, isObject, sameJson } from "./json.js";
|
|
24
|
+
import { readProjectedState, readStatePath } from "./query.js";
|
|
28
25
|
import { cwdScopeKey, resolveSessionAddress, sessionScopeKey, } from "./durable.js";
|
|
29
26
|
import { projectRecentTransitionsWithLimit, RECENT_TRANSITION_LIMIT } from "./history.js";
|
|
30
|
-
import {
|
|
27
|
+
import { StateFlowDiagnosticWriter, stateFlowLogPath } from "./logging.js";
|
|
31
28
|
import { isGitCommitAncestor, pushGitCommit, pushGitTarget, resolveGitPushDestination } from "./git.js";
|
|
32
29
|
import { ORDINARY_ARTIFACT_COMPILER, planArtifactInvalidation, } from "./artifact.js";
|
|
33
30
|
import { hasCompactionSizedTranscript, planStateFlowCompaction, shouldRequestStateFlowCompaction, stateFlowCompactionResult } from "./compaction.js";
|
|
31
|
+
export { formatPatchStateArguments, normalizePatchStateArguments };
|
|
34
32
|
export const PATCH_STATE_TOOL_NAME = "patch_state";
|
|
35
33
|
export const READ_STATE_TOOL_NAME = "read_state";
|
|
36
34
|
export const MAX_FALLBACK_ATTEMPTS = 2;
|
|
37
35
|
const PASSIVE_STOP_ENTRY_TYPE = "state-flow-passive-stop";
|
|
38
36
|
const PUBLICATION_SHUTDOWN_WAIT_MS = 2_000;
|
|
39
|
-
const PATCH_DISPLAY_SECTION_KEYS = new Set([
|
|
40
|
-
"global",
|
|
41
|
-
"cwd",
|
|
42
|
-
"session",
|
|
43
|
-
"artifacts",
|
|
44
|
-
"contract",
|
|
45
|
-
"working",
|
|
46
|
-
"response",
|
|
47
|
-
"final",
|
|
48
|
-
]);
|
|
49
|
-
/** Keep successful patch JSON valid while separating adjacent scopes and memory sections visually. */
|
|
50
|
-
export function formatPatchStateArguments(args) {
|
|
51
|
-
const seenAtIndent = new Set();
|
|
52
|
-
return JSON.stringify(args, null, 2).split("\n").flatMap((line) => {
|
|
53
|
-
const indent = line.length - line.trimStart().length;
|
|
54
|
-
const match = /^(\s+)"([^"]+)":/.exec(line);
|
|
55
|
-
if (match === null || !PATCH_DISPLAY_SECTION_KEYS.has(match[2])) {
|
|
56
|
-
for (const seenIndent of seenAtIndent) {
|
|
57
|
-
if (seenIndent > indent)
|
|
58
|
-
seenAtIndent.delete(seenIndent);
|
|
59
|
-
}
|
|
60
|
-
return [line];
|
|
61
|
-
}
|
|
62
|
-
const separator = seenAtIndent.has(indent) ? [""] : [];
|
|
63
|
-
seenAtIndent.add(indent);
|
|
64
|
-
return [...separator, line];
|
|
65
|
-
}).join("\n");
|
|
66
|
-
}
|
|
67
|
-
/** Normalize a bounded compatibility superset without advertising aliases in the model-facing contract. */
|
|
68
|
-
export function normalizePatchStateArguments(args) {
|
|
69
|
-
if (!isObject(args) || !Object.hasOwn(args, "final"))
|
|
70
|
-
return args;
|
|
71
|
-
const value = args.final;
|
|
72
|
-
let final;
|
|
73
|
-
if (typeof value === "boolean")
|
|
74
|
-
final = value;
|
|
75
|
-
else if (value === 1)
|
|
76
|
-
final = true;
|
|
77
|
-
else if (value === 0)
|
|
78
|
-
final = false;
|
|
79
|
-
else if (typeof value === "string" && value.trim().toLowerCase() === "true")
|
|
80
|
-
final = true;
|
|
81
|
-
else if (typeof value === "string" && value.trim().toLowerCase() === "false")
|
|
82
|
-
final = false;
|
|
83
|
-
else
|
|
84
|
-
return args;
|
|
85
|
-
return { ...args, final };
|
|
86
|
-
}
|
|
87
|
-
/** Keep tool output visually separated from its heading with exactly one leading newline. */
|
|
88
|
-
function separatedOutput(text) {
|
|
89
|
-
return `\n${text.replace(/^\n+/, "")}`;
|
|
90
|
-
}
|
|
91
|
-
/** Keep a failed tool invocation visually separated from its rendered error without changing error semantics. */
|
|
92
|
-
function separatedFailure(error) {
|
|
93
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
-
return new Error(separatedOutput(message), error instanceof Error ? { cause: error } : undefined);
|
|
95
|
-
}
|
|
96
37
|
export default function stateFlowExtension(pi, options = {}) {
|
|
97
38
|
const agentDir = options.agentDir ?? getAgentDir();
|
|
98
|
-
const
|
|
39
|
+
const loadedConfig = loadStateFlowConfig(agentDir, options.repositoryRoot);
|
|
40
|
+
const config = {
|
|
41
|
+
...loadedConfig,
|
|
42
|
+
passiveBootstrap: options.passive?.bootstrap ?? loadedConfig.passiveBootstrap,
|
|
43
|
+
passiveTools: options.passive?.tools ?? loadedConfig.passiveTools,
|
|
44
|
+
};
|
|
99
45
|
let snapshot = emptySnapshot();
|
|
100
46
|
let scopeStates = { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
101
47
|
let branchHasSnapshot = false;
|
|
@@ -120,15 +66,19 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
120
66
|
let pendingPublication;
|
|
121
67
|
let rehydrationPhase;
|
|
122
68
|
let turnPublicationTarget;
|
|
123
|
-
const activePublicationWorkers = new Map();
|
|
124
|
-
let publicationStopped = false;
|
|
125
69
|
let publicationShutdown;
|
|
126
70
|
const repositoryRoot = resolve(options.repositoryRoot ?? config.directory);
|
|
71
|
+
const diagnosticWriter = new StateFlowDiagnosticWriter(config.logging, stateFlowLogPath(agentDir), repositoryRoot, (message) => activeContext?.ui.notify(message, "warning"));
|
|
72
|
+
const publicationWorker = new PublicationWorkerController({
|
|
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
|
+
});
|
|
127
78
|
const skillReads = new SkillReadTracker();
|
|
128
79
|
const artifactReads = new ArtifactReadTracker();
|
|
129
80
|
const globalMarkdown = new GlobalMarkdownDiscovery(options.knowledgeRoot ?? getKnowledgeRoot(agentDir));
|
|
130
81
|
let artifactInvalidations = [];
|
|
131
|
-
let loggingWarningReported = false;
|
|
132
82
|
let telegramStartPending = false;
|
|
133
83
|
function sessionAddress(ctx) {
|
|
134
84
|
return resolveSessionAddress(ctx.sessionManager.getSessionFile(), ctx.sessionManager.getSessionId(), ctx.sessionManager.getHeader()?.timestamp);
|
|
@@ -179,29 +129,6 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
179
129
|
skillReads.clear();
|
|
180
130
|
artifactReads.clear();
|
|
181
131
|
}
|
|
182
|
-
function passiveStopBoundary(ctx) {
|
|
183
|
-
for (const entry of [...ctx.sessionManager.getBranch()].reverse()) {
|
|
184
|
-
try {
|
|
185
|
-
if (entry?.type !== "custom" || entry.customType !== PASSIVE_STOP_ENTRY_TYPE)
|
|
186
|
-
continue;
|
|
187
|
-
const { at, from, reset, owner } = entry.data ?? {};
|
|
188
|
-
if (reset === true && owner === ctx.sessionManager.getSessionId())
|
|
189
|
-
return undefined;
|
|
190
|
-
if (typeof at === "number" && Number.isSafeInteger(at) && at >= 0)
|
|
191
|
-
return {
|
|
192
|
-
at,
|
|
193
|
-
...(typeof from === "number" && Number.isSafeInteger(from) && from >= 0 ? { from } : {}),
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
catch {
|
|
197
|
-
// A hostile unrelated branch entry cannot manufacture or suppress a valid marker.
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return undefined;
|
|
201
|
-
}
|
|
202
|
-
function retainsPhysicalSessionProjection(reason) {
|
|
203
|
-
return reason === undefined || reason === "startup" || reason === "reload" || reason === "resume";
|
|
204
|
-
}
|
|
205
132
|
function deferArtifactRefresh() {
|
|
206
133
|
artifactInvalidations = [];
|
|
207
134
|
artifactReads.setCandidates([]);
|
|
@@ -234,31 +161,19 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
234
161
|
function installScopeStates() {
|
|
235
162
|
scopeStates = runtime?.view ? runtime.states() : { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
236
163
|
}
|
|
164
|
+
function passiveToolsAvailable() {
|
|
165
|
+
return snapshot.config.enabled || config.passiveTools;
|
|
166
|
+
}
|
|
237
167
|
function syncStateFlowTools() {
|
|
238
168
|
const active = pi.getActiveTools();
|
|
239
169
|
const owned = [PATCH_STATE_TOOL_NAME, READ_STATE_TOOL_NAME];
|
|
240
|
-
|
|
170
|
+
const available = passiveToolsAvailable();
|
|
171
|
+
if (owned.every((name) => active.includes(name) === available))
|
|
241
172
|
return;
|
|
242
|
-
pi.setActiveTools(
|
|
173
|
+
pi.setActiveTools(available
|
|
243
174
|
? [...new Set([...active, ...owned])]
|
|
244
175
|
: active.filter((name) => !owned.includes(name)));
|
|
245
176
|
}
|
|
246
|
-
function assistantToolBatch(ctx, toolCallId) {
|
|
247
|
-
for (let cursor = ctx.sessionManager.getLeafEntry(); cursor; cursor = cursor.parentId ? ctx.sessionManager.getEntry(cursor.parentId) : undefined) {
|
|
248
|
-
const entry = cursor;
|
|
249
|
-
if (entry.type !== "message" || entry.message?.role !== "assistant" || !Array.isArray(entry.message.content))
|
|
250
|
-
continue;
|
|
251
|
-
const calls = entry.message.content.filter((block) => {
|
|
252
|
-
return typeof block === "object" && block !== null
|
|
253
|
-
&& block.type === "toolCall"
|
|
254
|
-
&& typeof block.id === "string"
|
|
255
|
-
&& typeof block.name === "string";
|
|
256
|
-
});
|
|
257
|
-
if (calls.some(({ id }) => id === toolCallId))
|
|
258
|
-
return calls.map(({ name }) => name);
|
|
259
|
-
}
|
|
260
|
-
return undefined;
|
|
261
|
-
}
|
|
262
177
|
function recordPublication(publication, ctx) {
|
|
263
178
|
const revision = publication.revision ?? publication.commit;
|
|
264
179
|
if (revision !== undefined)
|
|
@@ -285,8 +200,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
285
200
|
return;
|
|
286
201
|
turnPublicationTarget = target;
|
|
287
202
|
try {
|
|
288
|
-
enqueueTurnPublication(
|
|
289
|
-
|
|
203
|
+
enqueueTurnPublication();
|
|
204
|
+
publicationWorker.launch();
|
|
290
205
|
}
|
|
291
206
|
catch (error) {
|
|
292
207
|
ctx.ui.notify(`State Flow accepted the local commit; remote publication is deferred: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
@@ -329,104 +244,17 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
329
244
|
persist();
|
|
330
245
|
return true;
|
|
331
246
|
}
|
|
332
|
-
function enqueueTurnPublication(
|
|
247
|
+
function enqueueTurnPublication() {
|
|
333
248
|
const target = turnPublicationTarget;
|
|
334
249
|
turnPublicationTarget = undefined;
|
|
335
|
-
if (
|
|
336
|
-
|
|
337
|
-
const destination = resolveGitPushDestination(repositoryRoot);
|
|
338
|
-
if (!destination)
|
|
339
|
-
return;
|
|
340
|
-
const path = publicationQueuePath(destination);
|
|
341
|
-
const previous = loadPublicationQueue(path);
|
|
342
|
-
const next = previous
|
|
343
|
-
? coalescePublicationTarget(previous, destination, target, (ancestor, descendant) => isGitCommitAncestor(repositoryRoot, ancestor, descendant), {
|
|
344
|
-
onDivergedLineage: (dropped) => recordDiagnostic(`Retired publication queue target ${dropped.target} after a journal lineage rewrite; retargeting to ${target}`, "publication-conflict", ctx),
|
|
345
|
-
})
|
|
346
|
-
: createPublicationQueue(destination, target);
|
|
347
|
-
savePublicationQueue(path, next, previous);
|
|
348
|
-
}
|
|
349
|
-
function launchPublicationWorker() {
|
|
350
|
-
if (publicationStopped)
|
|
351
|
-
return;
|
|
352
|
-
let destination;
|
|
353
|
-
try {
|
|
354
|
-
destination = resolveGitPushDestination(repositoryRoot);
|
|
355
|
-
}
|
|
356
|
-
catch (error) {
|
|
357
|
-
if (error instanceof Error && /ENOENT/.test(error.message))
|
|
358
|
-
return;
|
|
359
|
-
throw error;
|
|
360
|
-
}
|
|
361
|
-
if (!destination)
|
|
362
|
-
return;
|
|
363
|
-
const path = publicationQueuePath(destination);
|
|
364
|
-
if (activePublicationWorkers.has(path))
|
|
365
|
-
return;
|
|
366
|
-
let queued;
|
|
367
|
-
let lease;
|
|
368
|
-
try {
|
|
369
|
-
queued = loadPublicationQueue(path);
|
|
370
|
-
if (!queued)
|
|
371
|
-
return;
|
|
372
|
-
lease = acquirePublicationWorkerLease(path);
|
|
373
|
-
}
|
|
374
|
-
catch {
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
if (!lease)
|
|
378
|
-
return;
|
|
379
|
-
const controller = new AbortController();
|
|
380
|
-
const done = runPublicationWorker(queued, ({ target }) => pushGitTarget(repositoryRoot, destination, target, controller.signal), () => loadPublicationQueue(path) ?? queued, (ancestor, descendant) => isGitCommitAncestor(repositoryRoot, ancestor, descendant)).then((result) => {
|
|
381
|
-
if (publicationStopped)
|
|
382
|
-
return; // Late outcomes belong to an unconfirmed queue, not the replacement generation.
|
|
383
|
-
const current = loadPublicationQueue(path);
|
|
384
|
-
if (!current)
|
|
385
|
-
return;
|
|
386
|
-
if (result.next === undefined) {
|
|
387
|
-
if (current.target === result.attempted.target)
|
|
388
|
-
removePublicationQueue(path, current);
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
if (current.target === result.attempted.target || result.next.target === current.target) {
|
|
392
|
-
savePublicationQueue(path, result.next, current);
|
|
393
|
-
}
|
|
394
|
-
}).catch(() => {
|
|
395
|
-
// Queue/CAS truth remains durable; status and a later activation expose retry.
|
|
396
|
-
}).finally(() => {
|
|
397
|
-
activePublicationWorkers.delete(path);
|
|
398
|
-
try {
|
|
399
|
-
lease.release();
|
|
400
|
-
if (!publicationStopped && loadPublicationQueue(path)?.status === "pending")
|
|
401
|
-
launchPublicationWorker();
|
|
402
|
-
}
|
|
403
|
-
catch {
|
|
404
|
-
// Failed lease cleanup or malformed persistence stays inert until retry or repair.
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
activePublicationWorkers.set(path, { controller, done });
|
|
250
|
+
if (target)
|
|
251
|
+
publicationWorker.enqueue(target);
|
|
408
252
|
}
|
|
409
253
|
function shutdownPublicationWorkers(ctx) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
controller.abort();
|
|
415
|
-
if (workers.length === 0)
|
|
416
|
-
return;
|
|
417
|
-
let timeout;
|
|
418
|
-
try {
|
|
419
|
-
const completed = await Promise.race([
|
|
420
|
-
Promise.all(workers.map(({ done }) => done)).then(() => true),
|
|
421
|
-
new Promise((resolve) => { timeout = setTimeout(() => resolve(false), PUBLICATION_SHUTDOWN_WAIT_MS); }),
|
|
422
|
-
]);
|
|
423
|
-
if (!completed)
|
|
424
|
-
ctx.ui.notify(`State Flow push cleanup is unconfirmed after ${PUBLICATION_SHUTDOWN_WAIT_MS}ms; worker leases remain held until child exit.`, "warning");
|
|
425
|
-
}
|
|
426
|
-
finally {
|
|
427
|
-
clearTimeout(timeout);
|
|
428
|
-
}
|
|
429
|
-
})();
|
|
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
|
+
});
|
|
430
258
|
}
|
|
431
259
|
function retryPendingPush(ctx) {
|
|
432
260
|
if (pendingPublication === undefined || !runtime?.view)
|
|
@@ -438,8 +266,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
438
266
|
if (mode === "turn-end") {
|
|
439
267
|
turnPublicationTarget = target;
|
|
440
268
|
try {
|
|
441
|
-
enqueueTurnPublication(
|
|
442
|
-
|
|
269
|
+
enqueueTurnPublication();
|
|
270
|
+
publicationWorker.launch();
|
|
443
271
|
}
|
|
444
272
|
catch (error) {
|
|
445
273
|
ctx.ui.notify(`State Flow retained local state; asynchronous publication recovery is deferred: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
@@ -646,7 +474,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
646
474
|
ctx.ui.notify(`State Flow restored disabled: ${snapshot.meta.validation.error}`, "error");
|
|
647
475
|
}
|
|
648
476
|
if (retainsPhysicalSessionProjection(sessionStartReason) && runtime?.view) {
|
|
649
|
-
const boundary =
|
|
477
|
+
const boundary = findPassiveStopBoundary(ctx.sessionManager.getBranch(), ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE);
|
|
650
478
|
if (boundary !== undefined) {
|
|
651
479
|
const continuation = createPassiveContinuation(projectStateForModel(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)), boundary.at, boundary.from);
|
|
652
480
|
if (!snapshot.config.enabled)
|
|
@@ -655,40 +483,22 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
655
483
|
bootstrapContinuation = continuation;
|
|
656
484
|
}
|
|
657
485
|
}
|
|
486
|
+
if (!snapshot.config.enabled && (config.passiveBootstrap || config.passiveTools) && !runtime?.view) {
|
|
487
|
+
try {
|
|
488
|
+
runtime?.loadPassive();
|
|
489
|
+
installScopeStates();
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
ctx.ui.notify(`State Flow passive memory is unavailable: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
493
|
+
}
|
|
494
|
+
}
|
|
658
495
|
if (snapshot.config.enabled)
|
|
659
496
|
deferArtifactRefresh();
|
|
660
497
|
syncStateFlowTools();
|
|
661
498
|
updateUi(ctx);
|
|
662
499
|
}
|
|
663
500
|
function recordDiagnostic(error, category, ctx, extras = {}) {
|
|
664
|
-
|
|
665
|
-
return;
|
|
666
|
-
try {
|
|
667
|
-
const path = stateFlowLogPath(agentDir);
|
|
668
|
-
const fromRepository = relative(repositoryRoot, path);
|
|
669
|
-
if (fromRepository === "" || (!isAbsolute(fromRepository) && fromRepository !== ".." && !fromRepository.startsWith(`..${sep}`))) {
|
|
670
|
-
throw new Error("diagnostic path overlaps the State Flow repository");
|
|
671
|
-
}
|
|
672
|
-
appendStateFlowDiagnostic(path, {
|
|
673
|
-
at: new Date().toISOString(),
|
|
674
|
-
sessionId: sessionAddress(ctx).id,
|
|
675
|
-
cwd: resolve(ctx.cwd),
|
|
676
|
-
category,
|
|
677
|
-
error,
|
|
678
|
-
...(extras.content === undefined ? {} : { content: projectDiagnosticContent(extras.content) }),
|
|
679
|
-
...(extras.input === undefined ? {} : { input: extras.input }),
|
|
680
|
-
...(extras.tool === undefined ? {} : { tool: extras.tool }),
|
|
681
|
-
...(extras.toolCallId === undefined ? {} : { toolCallId: extras.toolCallId }),
|
|
682
|
-
...(extras.resolutionAttempt === undefined ? {} : { resolutionAttempt: extras.resolutionAttempt }),
|
|
683
|
-
...(extras.terminalEligible === undefined ? {} : { terminalEligible: extras.terminalEligible }),
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
catch (failure) {
|
|
687
|
-
if (loggingWarningReported)
|
|
688
|
-
return;
|
|
689
|
-
loggingWarningReported = true;
|
|
690
|
-
ctx.ui.notify(`State Flow could not write diagnostics: ${failure instanceof Error ? failure.message : String(failure)}`, "warning");
|
|
691
|
-
}
|
|
501
|
+
diagnosticWriter.record(sessionAddress(ctx).id, ctx.cwd, error, category, extras);
|
|
692
502
|
}
|
|
693
503
|
function continueFallbackResolution(lastWarning) {
|
|
694
504
|
resolutionPending = true;
|
|
@@ -762,37 +572,44 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
762
572
|
pi.registerTool({
|
|
763
573
|
name: READ_STATE_TOOL_NAME,
|
|
764
574
|
label: "Read State",
|
|
765
|
-
description: "Read State Flow
|
|
766
|
-
promptSnippet: "Read
|
|
575
|
+
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.",
|
|
576
|
+
promptSnippet: "Read exact state values, structures, or historical semantic patches",
|
|
767
577
|
parameters: Type.Object({
|
|
768
|
-
path: Type.Optional(Type.String({ description: "
|
|
769
|
-
|
|
770
|
-
|
|
578
|
+
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" })),
|
|
579
|
+
paths: Type.Optional(Type.Array(Type.String(), { minItems: 1, description: "Ordered query paths evaluated as one all-or-error read" })),
|
|
580
|
+
projection: Type.Optional(StringEnum(["value", "keys", "patch"], { description: "Value snapshot by default, structural keys with minimal meta, or the selected historical semantic patch" })),
|
|
771
581
|
}, { additionalProperties: false }),
|
|
772
582
|
async execute(_toolCallId, params, signal) {
|
|
773
583
|
try {
|
|
774
|
-
if (!
|
|
775
|
-
throw new Error("State Flow
|
|
584
|
+
if (!passiveToolsAvailable())
|
|
585
|
+
throw new Error("State Flow tools are disabled by configuration");
|
|
776
586
|
if (signal?.aborted)
|
|
777
587
|
throw new Error("State Flow read was aborted");
|
|
778
588
|
if (!runtime?.view)
|
|
779
589
|
throw new Error("State Flow temporal runtime is unavailable");
|
|
780
|
-
if (params.path
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
590
|
+
if (params.path === undefined && params.paths === undefined)
|
|
591
|
+
throw new Error("read_state requires path or paths");
|
|
592
|
+
if (params.path !== undefined && params.paths !== undefined)
|
|
593
|
+
throw new Error("read_state accepts path or paths, not both");
|
|
594
|
+
{
|
|
595
|
+
const paths = params.paths ?? [params.path];
|
|
596
|
+
if (paths.length === 1 && /^(?:global|cwd|session)\.patches(?:\[\d+\])?$/.test(paths[0])) {
|
|
597
|
+
if (params.projection !== undefined && params.projection !== "value")
|
|
598
|
+
throw new Error("Scope patch paths support only the value projection");
|
|
599
|
+
const result = readStatePath(runtime.view, paths[0]);
|
|
600
|
+
if (!("patch" in result))
|
|
601
|
+
throw new Error("Expected a scope patch path");
|
|
602
|
+
return {
|
|
603
|
+
content: [{ type: "text", text: `\n${JSON.stringify({ patch: result.patch })}` }],
|
|
604
|
+
details: { path: paths[0], transitionId: result.boundary.id },
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
const result = readProjectedState(runtime.view, paths, params.projection);
|
|
784
608
|
return {
|
|
785
609
|
content: [{ type: "text", text: `\n${JSON.stringify(result)}` }],
|
|
786
|
-
details: { path: params.path,
|
|
610
|
+
details: { ...(params.path === undefined ? { paths } : { path: params.path }), projection: params.projection ?? "value" },
|
|
787
611
|
};
|
|
788
612
|
}
|
|
789
|
-
const { offset = 0, scope = "effective" } = params;
|
|
790
|
-
const state = runtime.read(offset, scope === "effective" ? undefined : scope);
|
|
791
|
-
const boundary = runtime.view.lineage.at(-1 - offset);
|
|
792
|
-
return {
|
|
793
|
-
content: [{ type: "text", text: `\n${JSON.stringify({ offset, scope, boundary, state: projectStateForModel(state) })}` }],
|
|
794
|
-
details: { offset, scope, transitionId: boundary.id },
|
|
795
|
-
};
|
|
796
613
|
}
|
|
797
614
|
catch (error) {
|
|
798
615
|
throw separatedFailure(error);
|
|
@@ -827,8 +644,8 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
827
644
|
},
|
|
828
645
|
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
829
646
|
try {
|
|
830
|
-
if (!
|
|
831
|
-
throw new Error("State Flow
|
|
647
|
+
if (!passiveToolsAvailable())
|
|
648
|
+
throw new Error("State Flow tools are disabled by configuration");
|
|
832
649
|
if (signal?.aborted)
|
|
833
650
|
throw new Error("State Flow patch was aborted before materialization");
|
|
834
651
|
if (!isObject(params))
|
|
@@ -858,17 +675,29 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
858
675
|
}
|
|
859
676
|
if (params.final !== true)
|
|
860
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 } };
|
|
861
680
|
validateFinalEligibility(scopeStates, skillReads.successful.values(), runtime.causalBasis(), artifactReads.successful.values());
|
|
862
681
|
terminalEligible = true;
|
|
863
682
|
return { content: [{ type: "text", text: "\nState iteration is terminal-eligible." }], details: { final: true } };
|
|
864
683
|
}
|
|
684
|
+
if (!runtime?.view) {
|
|
685
|
+
runtime ??= createRuntime(ctx);
|
|
686
|
+
runtime.prepare();
|
|
687
|
+
const publication = runtime.initialize(snapshot, true, undefined, true);
|
|
688
|
+
recordPolicyPublication(publication, ctx);
|
|
689
|
+
installScopeStates();
|
|
690
|
+
branchHasSnapshot = true;
|
|
691
|
+
}
|
|
692
|
+
runtime.migrateLegacyStorage();
|
|
693
|
+
installScopeStates();
|
|
865
694
|
const stage = stageAtomicScopePatches(scopeStates, patches, skillReads.successful.values(), runtime.causalBasis(), artifactReads.successful.values());
|
|
866
695
|
const semanticChange = ["global", "cwd", "session"].some((scope) => !sameJson(scopeStates[scope], stage.nextStates[scope]));
|
|
867
696
|
const provenanceChange = Object.values(stage.provenanceUpdates).some((updates) => Object.keys(updates).length > 0);
|
|
868
697
|
if (!semanticChange && !provenanceChange)
|
|
869
698
|
throw new Error('patch_state scope patches must materially update state or required provenance; omit them and use {"final":true} when unchanged');
|
|
870
699
|
commitStage(stage, ctx, false);
|
|
871
|
-
if (params.final === true) {
|
|
700
|
+
if (params.final === true && snapshot.config.enabled) {
|
|
872
701
|
terminalEligible = true;
|
|
873
702
|
}
|
|
874
703
|
updateUi(ctx);
|
|
@@ -1028,9 +857,15 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1028
857
|
bootstrap: snapshot.meta.bootstrap === true,
|
|
1029
858
|
startPending: telegramStartPending,
|
|
1030
859
|
}),
|
|
1031
|
-
state: (scope) =>
|
|
1032
|
-
|
|
1033
|
-
|
|
860
|
+
state: (scope) => {
|
|
861
|
+
const selected = scope === "effective"
|
|
862
|
+
? overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)
|
|
863
|
+
: scopeStates[scope];
|
|
864
|
+
return {
|
|
865
|
+
...projectStateForModel(selected),
|
|
866
|
+
...(Object.hasOwn(selected, "lazy") ? { lazy: structuredClone(selected.lazy) } : {}),
|
|
867
|
+
};
|
|
868
|
+
},
|
|
1034
869
|
canStartNow: () => activeContext === undefined || activeContext.isIdle(),
|
|
1035
870
|
start: () => {
|
|
1036
871
|
if (!activeContext)
|
|
@@ -1057,8 +892,13 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1057
892
|
});
|
|
1058
893
|
void telegram.ensure();
|
|
1059
894
|
pi.on("before_agent_start", (event, ctx) => {
|
|
1060
|
-
if (!snapshot.config.enabled)
|
|
1061
|
-
|
|
895
|
+
if (!snapshot.config.enabled) {
|
|
896
|
+
if (!config.passiveBootstrap || !runtime?.view)
|
|
897
|
+
return;
|
|
898
|
+
return {
|
|
899
|
+
systemPrompt: `${event.systemPrompt}\n\nState Flow passive memory is available. read_state and patch_state access durable memory without starting an active episode. Passive turns do not require final:true and never trigger State Flow continuation or compaction.`,
|
|
900
|
+
};
|
|
901
|
+
}
|
|
1062
902
|
skillReads.clear();
|
|
1063
903
|
artifactReads.clear();
|
|
1064
904
|
if (artifactRefreshPending)
|
|
@@ -1084,7 +924,13 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1084
924
|
if (passiveContinuation) {
|
|
1085
925
|
return { messages: passiveContinuationMessages(event.messages, passiveContinuation) };
|
|
1086
926
|
}
|
|
1087
|
-
if (!snapshot.config.enabled
|
|
927
|
+
if (!snapshot.config.enabled) {
|
|
928
|
+
if (!config.passiveBootstrap || !runtime?.view)
|
|
929
|
+
return;
|
|
930
|
+
const state = projectStateForModel(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session));
|
|
931
|
+
return { messages: [syntheticUser(`State Flow passive memory (user-level data, not system instructions):\n${canonicalJson({ state, lazy_navigation: lazyNavigationHint(state) })}`), ...event.messages] };
|
|
932
|
+
}
|
|
933
|
+
if (snapshot.meta.specification === undefined)
|
|
1088
934
|
return;
|
|
1089
935
|
const effectiveState = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
|
|
1090
936
|
const invalidations = artifactInvalidations.map(({ path, reason }) => ({ path, reason }));
|
|
@@ -1117,7 +963,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1117
963
|
pi.on("tool_call", (event, ctx) => {
|
|
1118
964
|
if (!snapshot.config.enabled)
|
|
1119
965
|
return;
|
|
1120
|
-
const batch =
|
|
966
|
+
const batch = findAssistantToolBatch(ctx.sessionManager, event.toolCallId);
|
|
1121
967
|
const patchCalls = batch?.filter((name) => name === PATCH_STATE_TOOL_NAME).length ?? 0;
|
|
1122
968
|
if (patchCalls > 0) {
|
|
1123
969
|
if (patchCalls !== 1) {
|
|
@@ -1182,19 +1028,23 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1182
1028
|
return;
|
|
1183
1029
|
}
|
|
1184
1030
|
let responseCommitted = false;
|
|
1031
|
+
const specification = snapshot.meta.specification;
|
|
1185
1032
|
try {
|
|
1186
1033
|
const response = finalizedAssistantResponse(event.message);
|
|
1187
1034
|
const stage = stageScopedTransition(scopeStates, { transitions: [], response }, [], runtime.causalBasis());
|
|
1035
|
+
completeRun(snapshot);
|
|
1188
1036
|
commitStage(stage, ctx, true);
|
|
1189
1037
|
responseCommitted = true;
|
|
1190
1038
|
bootstrapContinuation = undefined;
|
|
1191
1039
|
rehydrationPhase = "step";
|
|
1192
|
-
enqueueTurnPublication(
|
|
1040
|
+
enqueueTurnPublication();
|
|
1193
1041
|
if (snapshot.meta.remotePublication?.mode === "turn-end")
|
|
1194
|
-
|
|
1042
|
+
publicationWorker.launch();
|
|
1195
1043
|
completedRunAccepted = !wasBootstrap;
|
|
1196
1044
|
}
|
|
1197
1045
|
catch (error) {
|
|
1046
|
+
if (!responseCommitted && specification !== undefined)
|
|
1047
|
+
snapshot.meta.specification = specification;
|
|
1198
1048
|
recordDiagnostic(error instanceof Error ? error.message : String(error), "finalization", ctx);
|
|
1199
1049
|
ctx.ui.notify(responseCommitted
|
|
1200
1050
|
? `State Flow committed the final response; remote publication is deferred: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -1221,7 +1071,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1221
1071
|
startStateFlow(ctx);
|
|
1222
1072
|
}
|
|
1223
1073
|
if (snapshot.meta.remotePublication?.mode === "turn-end")
|
|
1224
|
-
|
|
1074
|
+
publicationWorker.launch();
|
|
1225
1075
|
if (!completedRunAccepted || compactionStopped || !snapshot.config.enabled || snapshot.meta.bootstrap || resolutionPending
|
|
1226
1076
|
|| compactionInFlight || !ctx.isIdle() || ctx.hasPendingMessages() || !snapshot.meta.durableBase
|
|
1227
1077
|
|| !shouldRequestStateFlowCompaction(ctx.getContextUsage()))
|
|
@@ -1246,7 +1096,7 @@ export default function stateFlowExtension(pi, options = {}) {
|
|
|
1246
1096
|
restoreActiveBranch(ctx, event.reason);
|
|
1247
1097
|
retryPendingPush(ctx);
|
|
1248
1098
|
if (snapshot.meta.remotePublication?.mode === "turn-end")
|
|
1249
|
-
|
|
1099
|
+
publicationWorker.launch();
|
|
1250
1100
|
updateUi(ctx);
|
|
1251
1101
|
void telegram.ensure();
|
|
1252
1102
|
});
|