@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
|
@@ -4,30 +4,27 @@ import { StringEnum, Type } from "@earendil-works/pi-ai";
|
|
|
4
4
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
6
6
|
import { Text } from "@earendil-works/pi-tui";
|
|
7
|
-
import { assistantToolCallCount, finalizedAssistantResponse, stateFlowProtocol } from "./protocol.ts";
|
|
8
|
-
import { createPassiveContinuation, currentRunTrajectory, passiveContinuationMessages, runtimeContextMessage, VALIDATION_MESSAGE_TYPE, withoutPrivateValidation, type PassiveContinuation } from "./context.ts";
|
|
7
|
+
import { assistantToolCallCount, finalizedAssistantResponse, formatPatchStateArguments, normalizePatchStateArguments, separatedFailure, separatedOutput, stateFlowProtocol } from "./protocol.ts";
|
|
8
|
+
import { createPassiveContinuation, currentRunTrajectory, lazyNavigationHint, passiveContinuationMessages, runtimeContextMessage, syntheticUser, VALIDATION_MESSAGE_TYPE, withoutPrivateValidation, type PassiveContinuation } from "./context.ts";
|
|
9
9
|
import { ArtifactReadTracker } from "./acquisition.ts";
|
|
10
10
|
import { loadStateFlowConfig } from "./config.ts";
|
|
11
11
|
import { createStateFlowTelegramAdapter, type StateFlowTelegramControlResult, type StateFlowTelegramLoader } from "./telegram.ts";
|
|
12
|
-
import { isAbsolute,
|
|
12
|
+
import { isAbsolute, resolve } from "node:path";
|
|
13
13
|
import { SkillReadTracker } from "./skills.ts";
|
|
14
14
|
import { emptySnapshot, migrationFailure, persistableSnapshot, RevisionUnavailableError, type Snapshot } from "./snapshot.ts";
|
|
15
15
|
import { readNativeSessionHeader } from "./continuation.ts";
|
|
16
16
|
import { MissingSessionRuntimeError, SharedScopeRemovalConflictError, TemporalRuntime, type RuntimePublication } from "./runtime.ts";
|
|
17
17
|
import { emptyState, overlayStates, projectStateForModel, type AtomicScopePatches, type MaterializedState, type ScopedStates, type StateScope } from "./state.ts";
|
|
18
18
|
import { commitScopedTransition, stageAtomicScopePatches, stageScopedTransition, validateFinalEligibility, type StagedScopedTransition } from "./transition.ts";
|
|
19
|
-
import { discoverSnapshotData, hasPriorConversation, isNewSession, SNAPSHOT_ENTRY_TYPE } from "./session.ts";
|
|
19
|
+
import { discoverSnapshotData, findAssistantToolBatch, findPassiveStopBoundary, hasPriorConversation, isNewSession, retainsPhysicalSessionProjection, SNAPSHOT_ENTRY_TYPE } from "./session.ts";
|
|
20
20
|
import { compactStatus, detailedStatus, STATUS_KEY, type PendingPublicationDiagnostic, type StatusDiagnostics } from "./status.ts";
|
|
21
|
-
import { prepareRun, resumeEpisode, startEpisode, stopEpisode } from "./episode.ts";
|
|
21
|
+
import { completeRun, prepareRun, resumeEpisode, startEpisode, stopEpisode } from "./episode.ts";
|
|
22
22
|
import { recoverSnapshot } from "./recovery.ts";
|
|
23
23
|
import type { RehydrationPhase } from "./rehydration.ts";
|
|
24
|
-
import { resolveRemotePublicationPolicy, serializeRemotePublicationPolicyDocument } from "./publication.ts";
|
|
25
|
-
import { coalescePublicationTarget, createPublicationQueue, type PublicationQueueState } from "./publication.ts";
|
|
26
|
-
import { acquirePublicationWorkerLease, loadPublicationQueue, publicationQueuePath, removePublicationQueue, savePublicationQueue } from "./publication.ts";
|
|
27
|
-
import { runPublicationWorker } from "./publication.ts";
|
|
24
|
+
import { loadPublicationQueue, publicationQueuePath, PublicationWorkerController, resolveRemotePublicationPolicy, serializeRemotePublicationPolicyDocument, type PublicationQueueState } from "./publication.ts";
|
|
28
25
|
import { getKnowledgeRoot, GlobalMarkdownDiscovery } from "./discovery.ts";
|
|
29
|
-
import { isObject, sameJson } from "./json.ts";
|
|
30
|
-
import { readStatePath } from "./query.ts";
|
|
26
|
+
import { canonicalJson, isObject, sameJson } from "./json.ts";
|
|
27
|
+
import { readProjectedState, readStatePath } from "./query.ts";
|
|
31
28
|
import {
|
|
32
29
|
cwdScopeKey,
|
|
33
30
|
resolveSessionAddress,
|
|
@@ -35,7 +32,7 @@ import {
|
|
|
35
32
|
type SessionAddress,
|
|
36
33
|
} from "./durable.ts";
|
|
37
34
|
import { projectRecentTransitionsWithLimit, RECENT_TRANSITION_LIMIT } from "./history.ts";
|
|
38
|
-
import {
|
|
35
|
+
import { StateFlowDiagnosticWriter, stateFlowLogPath, type DiagnosticExtras, type StateFlowDiagnosticCategory } from "./logging.ts";
|
|
39
36
|
import { isGitCommitAncestor, pushGitCommit, pushGitTarget, resolveGitPushDestination } from "./git.ts";
|
|
40
37
|
import {
|
|
41
38
|
ORDINARY_ARTIFACT_COMPILER,
|
|
@@ -50,70 +47,26 @@ export interface StateFlowExtensionOptions {
|
|
|
50
47
|
knowledgeRoot?: string;
|
|
51
48
|
onRuntime?: (accessor: { read(offset?: number, scope?: StateScope): MaterializedState }) => void;
|
|
52
49
|
telegram?: { load?: StateFlowTelegramLoader };
|
|
50
|
+
/** Test/SDK capability override; repository config remains the Pi default. */
|
|
51
|
+
passive?: { bootstrap?: boolean; tools?: boolean };
|
|
53
52
|
}
|
|
54
53
|
|
|
54
|
+
export { formatPatchStateArguments, normalizePatchStateArguments };
|
|
55
|
+
|
|
55
56
|
export const PATCH_STATE_TOOL_NAME = "patch_state";
|
|
56
57
|
export const READ_STATE_TOOL_NAME = "read_state";
|
|
57
58
|
export const MAX_FALLBACK_ATTEMPTS: number = 2;
|
|
58
59
|
const PASSIVE_STOP_ENTRY_TYPE = "state-flow-passive-stop";
|
|
59
60
|
const PUBLICATION_SHUTDOWN_WAIT_MS = 2_000;
|
|
60
|
-
const PATCH_DISPLAY_SECTION_KEYS = new Set([
|
|
61
|
-
"global",
|
|
62
|
-
"cwd",
|
|
63
|
-
"session",
|
|
64
|
-
"artifacts",
|
|
65
|
-
"contract",
|
|
66
|
-
"working",
|
|
67
|
-
"response",
|
|
68
|
-
"final",
|
|
69
|
-
]);
|
|
70
|
-
|
|
71
|
-
/** Keep successful patch JSON valid while separating adjacent scopes and memory sections visually. */
|
|
72
|
-
export function formatPatchStateArguments(args: unknown): string {
|
|
73
|
-
const seenAtIndent = new Set<number>();
|
|
74
|
-
return JSON.stringify(args, null, 2).split("\n").flatMap((line) => {
|
|
75
|
-
const indent = line.length - line.trimStart().length;
|
|
76
|
-
const match = /^(\s+)"([^"]+)":/.exec(line);
|
|
77
|
-
if (match === null || !PATCH_DISPLAY_SECTION_KEYS.has(match[2])) {
|
|
78
|
-
for (const seenIndent of seenAtIndent) {
|
|
79
|
-
if (seenIndent > indent) seenAtIndent.delete(seenIndent);
|
|
80
|
-
}
|
|
81
|
-
return [line];
|
|
82
|
-
}
|
|
83
|
-
const separator = seenAtIndent.has(indent) ? [""] : [];
|
|
84
|
-
seenAtIndent.add(indent);
|
|
85
|
-
return [...separator, line];
|
|
86
|
-
}).join("\n");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** Normalize a bounded compatibility superset without advertising aliases in the model-facing contract. */
|
|
90
|
-
export function normalizePatchStateArguments(args: unknown): any {
|
|
91
|
-
if (!isObject(args) || !Object.hasOwn(args, "final")) return args;
|
|
92
|
-
const value = args.final;
|
|
93
|
-
let final: boolean;
|
|
94
|
-
if (typeof value === "boolean") final = value;
|
|
95
|
-
else if (value === 1) final = true;
|
|
96
|
-
else if (value === 0) final = false;
|
|
97
|
-
else if (typeof value === "string" && value.trim().toLowerCase() === "true") final = true;
|
|
98
|
-
else if (typeof value === "string" && value.trim().toLowerCase() === "false") final = false;
|
|
99
|
-
else return args;
|
|
100
|
-
return { ...args, final };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Keep tool output visually separated from its heading with exactly one leading newline. */
|
|
104
|
-
function separatedOutput(text: string): string {
|
|
105
|
-
return `\n${text.replace(/^\n+/, "")}`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** Keep a failed tool invocation visually separated from its rendered error without changing error semantics. */
|
|
109
|
-
function separatedFailure(error: unknown): Error {
|
|
110
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
111
|
-
return new Error(separatedOutput(message), error instanceof Error ? { cause: error } : undefined);
|
|
112
|
-
}
|
|
113
61
|
|
|
114
62
|
export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowExtensionOptions = {}): void {
|
|
115
63
|
const agentDir = options.agentDir ?? getAgentDir();
|
|
116
|
-
const
|
|
64
|
+
const loadedConfig = loadStateFlowConfig(agentDir, options.repositoryRoot);
|
|
65
|
+
const config = {
|
|
66
|
+
...loadedConfig,
|
|
67
|
+
passiveBootstrap: options.passive?.bootstrap ?? loadedConfig.passiveBootstrap,
|
|
68
|
+
passiveTools: options.passive?.tools ?? loadedConfig.passiveTools,
|
|
69
|
+
};
|
|
117
70
|
let snapshot: Snapshot = emptySnapshot();
|
|
118
71
|
let scopeStates: ScopedStates = { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
119
72
|
let branchHasSnapshot = false;
|
|
@@ -138,15 +91,22 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
138
91
|
let pendingPublication: PendingPublicationDiagnostic | undefined;
|
|
139
92
|
let rehydrationPhase: RehydrationPhase | undefined;
|
|
140
93
|
let turnPublicationTarget: string | undefined;
|
|
141
|
-
const activePublicationWorkers = new Map<string, { controller: AbortController; done: Promise<void> }>();
|
|
142
|
-
let publicationStopped = false;
|
|
143
94
|
let publicationShutdown: Promise<void> | undefined;
|
|
144
95
|
const repositoryRoot = resolve(options.repositoryRoot ?? config.directory);
|
|
96
|
+
const diagnosticWriter = new StateFlowDiagnosticWriter(config.logging, stateFlowLogPath(agentDir), repositoryRoot, (message) => activeContext?.ui.notify(message, "warning"));
|
|
97
|
+
const publicationWorker = new PublicationWorkerController({
|
|
98
|
+
resolveDestination: () => resolveGitPushDestination(repositoryRoot),
|
|
99
|
+
isAncestor: (ancestor, descendant) => isGitCommitAncestor(repositoryRoot, ancestor, descendant),
|
|
100
|
+
push: (destination, target, signal) => pushGitTarget(repositoryRoot, destination, target, signal),
|
|
101
|
+
onDiverged: (dropped, target) => activeContext && recordDiagnostic(
|
|
102
|
+
`Retired publication queue target ${dropped.target} after a journal lineage rewrite; retargeting to ${target}`,
|
|
103
|
+
"publication-conflict", activeContext,
|
|
104
|
+
),
|
|
105
|
+
});
|
|
145
106
|
const skillReads = new SkillReadTracker();
|
|
146
107
|
const artifactReads = new ArtifactReadTracker();
|
|
147
108
|
const globalMarkdown = new GlobalMarkdownDiscovery(options.knowledgeRoot ?? getKnowledgeRoot(agentDir));
|
|
148
109
|
let artifactInvalidations: ArtifactInvalidationRequest[] = [];
|
|
149
|
-
let loggingWarningReported = false;
|
|
150
110
|
let telegramStartPending = false;
|
|
151
111
|
|
|
152
112
|
function sessionAddress(ctx: ExtensionContext): SessionAddress {
|
|
@@ -199,27 +159,6 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
199
159
|
artifactReads.clear();
|
|
200
160
|
}
|
|
201
161
|
|
|
202
|
-
function passiveStopBoundary(ctx: ExtensionContext): { at: number; from?: number } | undefined {
|
|
203
|
-
for (const entry of [...ctx.sessionManager.getBranch()].reverse()) {
|
|
204
|
-
try {
|
|
205
|
-
if (entry?.type !== "custom" || entry.customType !== PASSIVE_STOP_ENTRY_TYPE) continue;
|
|
206
|
-
const { at, from, reset, owner } = (entry.data as { at?: unknown; from?: unknown; reset?: unknown; owner?: unknown } | undefined) ?? {};
|
|
207
|
-
if (reset === true && owner === ctx.sessionManager.getSessionId()) return undefined;
|
|
208
|
-
if (typeof at === "number" && Number.isSafeInteger(at) && at >= 0) return {
|
|
209
|
-
at,
|
|
210
|
-
...(typeof from === "number" && Number.isSafeInteger(from) && from >= 0 ? { from } : {}),
|
|
211
|
-
};
|
|
212
|
-
} catch {
|
|
213
|
-
// A hostile unrelated branch entry cannot manufacture or suppress a valid marker.
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return undefined;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function retainsPhysicalSessionProjection(reason: unknown): boolean {
|
|
220
|
-
return reason === undefined || reason === "startup" || reason === "reload" || reason === "resume";
|
|
221
|
-
}
|
|
222
|
-
|
|
223
162
|
function deferArtifactRefresh(): void {
|
|
224
163
|
artifactInvalidations = [];
|
|
225
164
|
artifactReads.setCandidates([]);
|
|
@@ -268,30 +207,20 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
268
207
|
scopeStates = runtime?.view ? runtime.states() : { global: emptyState(), cwd: emptyState(), session: emptyState() };
|
|
269
208
|
}
|
|
270
209
|
|
|
210
|
+
function passiveToolsAvailable(): boolean {
|
|
211
|
+
return snapshot.config.enabled || config.passiveTools;
|
|
212
|
+
}
|
|
213
|
+
|
|
271
214
|
function syncStateFlowTools(): void {
|
|
272
215
|
const active = pi.getActiveTools();
|
|
273
216
|
const owned = [PATCH_STATE_TOOL_NAME, READ_STATE_TOOL_NAME];
|
|
274
|
-
|
|
275
|
-
|
|
217
|
+
const available = passiveToolsAvailable();
|
|
218
|
+
if (owned.every((name) => active.includes(name) === available)) return;
|
|
219
|
+
pi.setActiveTools(available
|
|
276
220
|
? [...new Set([...active, ...owned])]
|
|
277
221
|
: active.filter((name) => !owned.includes(name)));
|
|
278
222
|
}
|
|
279
223
|
|
|
280
|
-
function assistantToolBatch(ctx: ExtensionContext, toolCallId: string): string[] | undefined {
|
|
281
|
-
for (let cursor = ctx.sessionManager.getLeafEntry(); cursor; cursor = cursor.parentId ? ctx.sessionManager.getEntry(cursor.parentId) : undefined) {
|
|
282
|
-
const entry = cursor as { type?: unknown; message?: { role?: unknown; content?: unknown } };
|
|
283
|
-
if (entry.type !== "message" || entry.message?.role !== "assistant" || !Array.isArray(entry.message.content)) continue;
|
|
284
|
-
const calls = entry.message.content.filter((block): block is { type: "toolCall"; id: string; name: string } => {
|
|
285
|
-
return typeof block === "object" && block !== null
|
|
286
|
-
&& (block as { type?: unknown }).type === "toolCall"
|
|
287
|
-
&& typeof (block as { id?: unknown }).id === "string"
|
|
288
|
-
&& typeof (block as { name?: unknown }).name === "string";
|
|
289
|
-
});
|
|
290
|
-
if (calls.some(({ id }) => id === toolCallId)) return calls.map(({ name }) => name);
|
|
291
|
-
}
|
|
292
|
-
return undefined;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
224
|
function recordPublication(publication: RuntimePublication, ctx: ExtensionContext): void {
|
|
296
225
|
const revision = publication.revision ?? publication.commit;
|
|
297
226
|
if (revision !== undefined) snapshot.meta.durableBase = revision;
|
|
@@ -318,8 +247,8 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
318
247
|
if (mode !== "turn-end" || target === undefined || !/^[0-9a-f]{40,64}$/.test(target)) return;
|
|
319
248
|
turnPublicationTarget = target;
|
|
320
249
|
try {
|
|
321
|
-
enqueueTurnPublication(
|
|
322
|
-
|
|
250
|
+
enqueueTurnPublication();
|
|
251
|
+
publicationWorker.launch();
|
|
323
252
|
} catch (error) {
|
|
324
253
|
ctx.ui.notify(
|
|
325
254
|
`State Flow accepted the local commit; remote publication is deferred: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -360,95 +289,16 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
360
289
|
return true;
|
|
361
290
|
}
|
|
362
291
|
|
|
363
|
-
function enqueueTurnPublication(
|
|
292
|
+
function enqueueTurnPublication(): void {
|
|
364
293
|
const target = turnPublicationTarget;
|
|
365
294
|
turnPublicationTarget = undefined;
|
|
366
|
-
if (
|
|
367
|
-
const destination = resolveGitPushDestination(repositoryRoot);
|
|
368
|
-
if (!destination) return;
|
|
369
|
-
const path = publicationQueuePath(destination);
|
|
370
|
-
const previous = loadPublicationQueue(path);
|
|
371
|
-
const next = previous
|
|
372
|
-
? coalescePublicationTarget(previous, destination, target, (ancestor, descendant) => isGitCommitAncestor(repositoryRoot, ancestor, descendant), {
|
|
373
|
-
onDivergedLineage: (dropped) => recordDiagnostic(
|
|
374
|
-
`Retired publication queue target ${dropped.target} after a journal lineage rewrite; retargeting to ${target}`,
|
|
375
|
-
"publication-conflict", ctx,
|
|
376
|
-
),
|
|
377
|
-
})
|
|
378
|
-
: createPublicationQueue(destination, target);
|
|
379
|
-
savePublicationQueue(path, next, previous);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function launchPublicationWorker(): void {
|
|
383
|
-
if (publicationStopped) return;
|
|
384
|
-
let destination: ReturnType<typeof resolveGitPushDestination>;
|
|
385
|
-
try {
|
|
386
|
-
destination = resolveGitPushDestination(repositoryRoot);
|
|
387
|
-
} catch (error) {
|
|
388
|
-
if (error instanceof Error && /ENOENT/.test(error.message)) return;
|
|
389
|
-
throw error;
|
|
390
|
-
}
|
|
391
|
-
if (!destination) return;
|
|
392
|
-
const path = publicationQueuePath(destination);
|
|
393
|
-
if (activePublicationWorkers.has(path)) return;
|
|
394
|
-
let queued: PublicationQueueState | undefined;
|
|
395
|
-
let lease: ReturnType<typeof acquirePublicationWorkerLease>;
|
|
396
|
-
try {
|
|
397
|
-
queued = loadPublicationQueue(path);
|
|
398
|
-
if (!queued) return;
|
|
399
|
-
lease = acquirePublicationWorkerLease(path);
|
|
400
|
-
} catch {
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
if (!lease) return;
|
|
404
|
-
const controller = new AbortController();
|
|
405
|
-
const done = runPublicationWorker(
|
|
406
|
-
queued,
|
|
407
|
-
({ target }) => pushGitTarget(repositoryRoot, destination, target, controller.signal),
|
|
408
|
-
() => loadPublicationQueue(path) ?? queued,
|
|
409
|
-
(ancestor, descendant) => isGitCommitAncestor(repositoryRoot, ancestor, descendant),
|
|
410
|
-
).then((result) => {
|
|
411
|
-
if (publicationStopped) return; // Late outcomes belong to an unconfirmed queue, not the replacement generation.
|
|
412
|
-
const current = loadPublicationQueue(path);
|
|
413
|
-
if (!current) return;
|
|
414
|
-
if (result.next === undefined) {
|
|
415
|
-
if (current.target === result.attempted.target) removePublicationQueue(path, current);
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
if (current.target === result.attempted.target || result.next.target === current.target) {
|
|
419
|
-
savePublicationQueue(path, result.next, current);
|
|
420
|
-
}
|
|
421
|
-
}).catch(() => {
|
|
422
|
-
// Queue/CAS truth remains durable; status and a later activation expose retry.
|
|
423
|
-
}).finally(() => {
|
|
424
|
-
activePublicationWorkers.delete(path);
|
|
425
|
-
try {
|
|
426
|
-
lease.release();
|
|
427
|
-
if (!publicationStopped && loadPublicationQueue(path)?.status === "pending") launchPublicationWorker();
|
|
428
|
-
} catch {
|
|
429
|
-
// Failed lease cleanup or malformed persistence stays inert until retry or repair.
|
|
430
|
-
}
|
|
431
|
-
});
|
|
432
|
-
activePublicationWorkers.set(path, { controller, done });
|
|
295
|
+
if (target) publicationWorker.enqueue(target);
|
|
433
296
|
}
|
|
434
297
|
|
|
435
298
|
function shutdownPublicationWorkers(ctx: ExtensionContext): Promise<void> {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
for (const { controller } of workers) controller.abort();
|
|
440
|
-
if (workers.length === 0) return;
|
|
441
|
-
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
442
|
-
try {
|
|
443
|
-
const completed = await Promise.race([
|
|
444
|
-
Promise.all(workers.map(({ done }) => done)).then(() => true),
|
|
445
|
-
new Promise<false>((resolve) => { timeout = setTimeout(() => resolve(false), PUBLICATION_SHUTDOWN_WAIT_MS); }),
|
|
446
|
-
]);
|
|
447
|
-
if (!completed) ctx.ui.notify(`State Flow push cleanup is unconfirmed after ${PUBLICATION_SHUTDOWN_WAIT_MS}ms; worker leases remain held until child exit.`, "warning");
|
|
448
|
-
} finally {
|
|
449
|
-
clearTimeout(timeout);
|
|
450
|
-
}
|
|
451
|
-
})();
|
|
299
|
+
return publicationShutdown ??= publicationWorker.shutdown(PUBLICATION_SHUTDOWN_WAIT_MS).then((completed) => {
|
|
300
|
+
if (!completed) ctx.ui.notify(`State Flow push cleanup is unconfirmed after ${PUBLICATION_SHUTDOWN_WAIT_MS}ms; worker leases remain held until child exit.`, "warning");
|
|
301
|
+
});
|
|
452
302
|
}
|
|
453
303
|
|
|
454
304
|
function retryPendingPush(ctx: ExtensionContext): void {
|
|
@@ -460,8 +310,8 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
460
310
|
if (mode === "turn-end") {
|
|
461
311
|
turnPublicationTarget = target;
|
|
462
312
|
try {
|
|
463
|
-
enqueueTurnPublication(
|
|
464
|
-
|
|
313
|
+
enqueueTurnPublication();
|
|
314
|
+
publicationWorker.launch();
|
|
465
315
|
} catch (error) {
|
|
466
316
|
ctx.ui.notify(`State Flow retained local state; asynchronous publication recovery is deferred: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
467
317
|
}
|
|
@@ -669,7 +519,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
669
519
|
ctx.ui.notify(`State Flow restored disabled: ${snapshot.meta.validation.error}`, "error");
|
|
670
520
|
}
|
|
671
521
|
if (retainsPhysicalSessionProjection(sessionStartReason) && runtime?.view) {
|
|
672
|
-
const boundary =
|
|
522
|
+
const boundary = findPassiveStopBoundary(ctx.sessionManager.getBranch(), ctx.sessionManager.getSessionId(), PASSIVE_STOP_ENTRY_TYPE);
|
|
673
523
|
if (boundary !== undefined) {
|
|
674
524
|
const continuation = createPassiveContinuation(
|
|
675
525
|
projectStateForModel(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)),
|
|
@@ -680,46 +530,21 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
680
530
|
else if (snapshot.meta.bootstrap) bootstrapContinuation = continuation;
|
|
681
531
|
}
|
|
682
532
|
}
|
|
533
|
+
if (!snapshot.config.enabled && (config.passiveBootstrap || config.passiveTools) && !runtime?.view) {
|
|
534
|
+
try {
|
|
535
|
+
runtime?.loadPassive();
|
|
536
|
+
installScopeStates();
|
|
537
|
+
} catch (error) {
|
|
538
|
+
ctx.ui.notify(`State Flow passive memory is unavailable: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
539
|
+
}
|
|
540
|
+
}
|
|
683
541
|
if (snapshot.config.enabled) deferArtifactRefresh();
|
|
684
542
|
syncStateFlowTools();
|
|
685
543
|
updateUi(ctx);
|
|
686
544
|
}
|
|
687
545
|
|
|
688
|
-
interface DiagnosticExtras {
|
|
689
|
-
content?: unknown;
|
|
690
|
-
input?: unknown;
|
|
691
|
-
tool?: string;
|
|
692
|
-
toolCallId?: string;
|
|
693
|
-
resolutionAttempt?: number;
|
|
694
|
-
terminalEligible?: boolean;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
546
|
function recordDiagnostic(error: string, category: StateFlowDiagnosticCategory, ctx: ExtensionContext, extras: DiagnosticExtras = {}): void {
|
|
698
|
-
|
|
699
|
-
try {
|
|
700
|
-
const path = stateFlowLogPath(agentDir);
|
|
701
|
-
const fromRepository = relative(repositoryRoot, path);
|
|
702
|
-
if (fromRepository === "" || (!isAbsolute(fromRepository) && fromRepository !== ".." && !fromRepository.startsWith(`..${sep}`))) {
|
|
703
|
-
throw new Error("diagnostic path overlaps the State Flow repository");
|
|
704
|
-
}
|
|
705
|
-
appendStateFlowDiagnostic(path, {
|
|
706
|
-
at: new Date().toISOString(),
|
|
707
|
-
sessionId: sessionAddress(ctx).id,
|
|
708
|
-
cwd: resolve(ctx.cwd),
|
|
709
|
-
category,
|
|
710
|
-
error,
|
|
711
|
-
...(extras.content === undefined ? {} : { content: projectDiagnosticContent(extras.content) }),
|
|
712
|
-
...(extras.input === undefined ? {} : { input: extras.input }),
|
|
713
|
-
...(extras.tool === undefined ? {} : { tool: extras.tool }),
|
|
714
|
-
...(extras.toolCallId === undefined ? {} : { toolCallId: extras.toolCallId }),
|
|
715
|
-
...(extras.resolutionAttempt === undefined ? {} : { resolutionAttempt: extras.resolutionAttempt }),
|
|
716
|
-
...(extras.terminalEligible === undefined ? {} : { terminalEligible: extras.terminalEligible }),
|
|
717
|
-
});
|
|
718
|
-
} catch (failure) {
|
|
719
|
-
if (loggingWarningReported) return;
|
|
720
|
-
loggingWarningReported = true;
|
|
721
|
-
ctx.ui.notify(`State Flow could not write diagnostics: ${failure instanceof Error ? failure.message : String(failure)}`, "warning");
|
|
722
|
-
}
|
|
547
|
+
diagnosticWriter.record(sessionAddress(ctx).id, ctx.cwd, error, category, extras);
|
|
723
548
|
}
|
|
724
549
|
|
|
725
550
|
function continueFallbackResolution(lastWarning: boolean): void {
|
|
@@ -796,34 +621,38 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
796
621
|
pi.registerTool({
|
|
797
622
|
name: READ_STATE_TOOL_NAME,
|
|
798
623
|
label: "Read State",
|
|
799
|
-
description: "Read State Flow
|
|
800
|
-
promptSnippet: "Read
|
|
624
|
+
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.",
|
|
625
|
+
promptSnippet: "Read exact state values, structures, or historical semantic patches",
|
|
801
626
|
parameters: Type.Object({
|
|
802
|
-
path: Type.Optional(Type.String({ description: "
|
|
803
|
-
|
|
804
|
-
|
|
627
|
+
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" })),
|
|
628
|
+
paths: Type.Optional(Type.Array(Type.String(), { minItems: 1, description: "Ordered query paths evaluated as one all-or-error read" })),
|
|
629
|
+
projection: Type.Optional(StringEnum(["value", "keys", "patch"] as const, { description: "Value snapshot by default, structural keys with minimal meta, or the selected historical semantic patch" })),
|
|
805
630
|
}, { additionalProperties: false }),
|
|
806
631
|
async execute(_toolCallId, params, signal) {
|
|
807
632
|
try {
|
|
808
|
-
type ReadDetails = { path?: string;
|
|
809
|
-
if (!
|
|
633
|
+
type ReadDetails = { path?: string; paths?: string[]; projection?: string; transitionId?: string };
|
|
634
|
+
if (!passiveToolsAvailable()) throw new Error("State Flow tools are disabled by configuration");
|
|
810
635
|
if (signal?.aborted) throw new Error("State Flow read was aborted");
|
|
811
636
|
if (!runtime?.view) throw new Error("State Flow temporal runtime is unavailable");
|
|
812
|
-
if (params.path
|
|
813
|
-
|
|
814
|
-
|
|
637
|
+
if (params.path === undefined && params.paths === undefined) throw new Error("read_state requires path or paths");
|
|
638
|
+
if (params.path !== undefined && params.paths !== undefined) throw new Error("read_state accepts path or paths, not both");
|
|
639
|
+
{
|
|
640
|
+
const paths = params.paths ?? [params.path!];
|
|
641
|
+
if (paths.length === 1 && /^(?:global|cwd|session)\.patches(?:\[\d+\])?$/.test(paths[0]!)) {
|
|
642
|
+
if (params.projection !== undefined && params.projection !== "value") throw new Error("Scope patch paths support only the value projection");
|
|
643
|
+
const result = readStatePath(runtime.view, paths[0]!);
|
|
644
|
+
if (!("patch" in result)) throw new Error("Expected a scope patch path");
|
|
645
|
+
return {
|
|
646
|
+
content: [{ type: "text", text: `\n${JSON.stringify({ patch: result.patch })}` }],
|
|
647
|
+
details: { path: paths[0], transitionId: result.boundary.id } as ReadDetails,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
const result = readProjectedState(runtime.view, paths, params.projection);
|
|
815
651
|
return {
|
|
816
652
|
content: [{ type: "text", text: `\n${JSON.stringify(result)}` }],
|
|
817
|
-
details: { path: params.path,
|
|
653
|
+
details: { ...(params.path === undefined ? { paths } : { path: params.path }), projection: params.projection ?? "value" } as ReadDetails,
|
|
818
654
|
};
|
|
819
655
|
}
|
|
820
|
-
const { offset = 0, scope = "effective" } = params;
|
|
821
|
-
const state = runtime.read(offset, scope === "effective" ? undefined : scope);
|
|
822
|
-
const boundary = runtime.view.lineage.at(-1 - offset)!;
|
|
823
|
-
return {
|
|
824
|
-
content: [{ type: "text", text: `\n${JSON.stringify({ offset, scope, boundary, state: projectStateForModel(state) })}` }],
|
|
825
|
-
details: { offset, scope, transitionId: boundary.id } as ReadDetails,
|
|
826
|
-
};
|
|
827
656
|
} catch (error) {
|
|
828
657
|
throw separatedFailure(error);
|
|
829
658
|
}
|
|
@@ -856,7 +685,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
856
685
|
},
|
|
857
686
|
async execute(toolCallId, params, signal, _onUpdate, ctx) {
|
|
858
687
|
try {
|
|
859
|
-
if (!
|
|
688
|
+
if (!passiveToolsAvailable()) throw new Error("State Flow tools are disabled by configuration");
|
|
860
689
|
if (signal?.aborted) throw new Error("State Flow patch was aborted before materialization");
|
|
861
690
|
if (!isObject(params)) throw new Error("patch_state requires an object");
|
|
862
691
|
const allowed = new Set(["global", "cwd", "session", "final"]);
|
|
@@ -878,16 +707,27 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
878
707
|
return { content: [{ type: "text", text: "\nState unchanged; iteration remains non-terminal." }], details: { final: false } };
|
|
879
708
|
}
|
|
880
709
|
if (params.final !== true) throw new Error('patch_state requires at least one scope patch or {"final":true}');
|
|
710
|
+
if (!snapshot.config.enabled) return { content: [{ type: "text", text: "\nState unchanged; passive turns have no terminal barrier." }], details: { final: false } };
|
|
881
711
|
validateFinalEligibility(scopeStates, skillReads.successful.values(), runtime!.causalBasis(), artifactReads.successful.values());
|
|
882
712
|
terminalEligible = true;
|
|
883
713
|
return { content: [{ type: "text", text: "\nState iteration is terminal-eligible." }], details: { final: true } };
|
|
884
714
|
}
|
|
885
|
-
|
|
715
|
+
if (!runtime?.view) {
|
|
716
|
+
runtime ??= createRuntime(ctx);
|
|
717
|
+
runtime.prepare();
|
|
718
|
+
const publication = runtime.initialize(snapshot, true, undefined, true);
|
|
719
|
+
recordPolicyPublication(publication, ctx);
|
|
720
|
+
installScopeStates();
|
|
721
|
+
branchHasSnapshot = true;
|
|
722
|
+
}
|
|
723
|
+
runtime.migrateLegacyStorage();
|
|
724
|
+
installScopeStates();
|
|
725
|
+
const stage = stageAtomicScopePatches(scopeStates, patches, skillReads.successful.values(), runtime.causalBasis(), artifactReads.successful.values());
|
|
886
726
|
const semanticChange = (["global", "cwd", "session"] as const).some((scope) => !sameJson(scopeStates[scope], stage.nextStates[scope]));
|
|
887
727
|
const provenanceChange = Object.values(stage.provenanceUpdates).some((updates) => Object.keys(updates).length > 0);
|
|
888
728
|
if (!semanticChange && !provenanceChange) throw new Error('patch_state scope patches must materially update state or required provenance; omit them and use {"final":true} when unchanged');
|
|
889
729
|
commitStage(stage, ctx, false);
|
|
890
|
-
if (params.final === true) {
|
|
730
|
+
if (params.final === true && snapshot.config.enabled) {
|
|
891
731
|
terminalEligible = true;
|
|
892
732
|
}
|
|
893
733
|
updateUi(ctx);
|
|
@@ -1051,11 +891,15 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1051
891
|
bootstrap: snapshot.meta.bootstrap === true,
|
|
1052
892
|
startPending: telegramStartPending,
|
|
1053
893
|
}),
|
|
1054
|
-
state: (scope) =>
|
|
1055
|
-
scope === "effective"
|
|
894
|
+
state: (scope) => {
|
|
895
|
+
const selected = scope === "effective"
|
|
1056
896
|
? overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session)
|
|
1057
|
-
: scopeStates[scope]
|
|
1058
|
-
|
|
897
|
+
: scopeStates[scope];
|
|
898
|
+
return {
|
|
899
|
+
...projectStateForModel(selected),
|
|
900
|
+
...(Object.hasOwn(selected, "lazy") ? { lazy: structuredClone(selected.lazy) } : {}),
|
|
901
|
+
};
|
|
902
|
+
},
|
|
1059
903
|
canStartNow: () => activeContext === undefined || activeContext.isIdle(),
|
|
1060
904
|
start: () => {
|
|
1061
905
|
if (!activeContext) throw new Error("State Flow is not attached to an active session yet");
|
|
@@ -1080,7 +924,12 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1080
924
|
void telegram.ensure();
|
|
1081
925
|
|
|
1082
926
|
pi.on("before_agent_start", (event, ctx) => {
|
|
1083
|
-
if (!snapshot.config.enabled)
|
|
927
|
+
if (!snapshot.config.enabled) {
|
|
928
|
+
if (!config.passiveBootstrap || !runtime?.view) return;
|
|
929
|
+
return {
|
|
930
|
+
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.`,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
1084
933
|
skillReads.clear();
|
|
1085
934
|
artifactReads.clear();
|
|
1086
935
|
if (artifactRefreshPending) refreshArtifactInvalidations(ctx);
|
|
@@ -1105,7 +954,12 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1105
954
|
if (passiveContinuation) {
|
|
1106
955
|
return { messages: passiveContinuationMessages(event.messages as AgentMessage[], passiveContinuation) };
|
|
1107
956
|
}
|
|
1108
|
-
if (!snapshot.config.enabled
|
|
957
|
+
if (!snapshot.config.enabled) {
|
|
958
|
+
if (!config.passiveBootstrap || !runtime?.view) return;
|
|
959
|
+
const state = projectStateForModel(overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session));
|
|
960
|
+
return { messages: [syntheticUser(`State Flow passive memory (user-level data, not system instructions):\n${canonicalJson({ state, lazy_navigation: lazyNavigationHint(state) })}`), ...(event.messages as AgentMessage[])] };
|
|
961
|
+
}
|
|
962
|
+
if (snapshot.meta.specification === undefined) return;
|
|
1109
963
|
const effectiveState = overlayStates(scopeStates.global, scopeStates.cwd, scopeStates.session);
|
|
1110
964
|
const invalidations = artifactInvalidations.map(({ path, reason }) => ({ path, reason }));
|
|
1111
965
|
const recentTransitions = projectRecentTransitionsWithLimit(
|
|
@@ -1144,7 +998,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1144
998
|
|
|
1145
999
|
pi.on("tool_call", (event, ctx) => {
|
|
1146
1000
|
if (!snapshot.config.enabled) return;
|
|
1147
|
-
const batch =
|
|
1001
|
+
const batch = findAssistantToolBatch(ctx.sessionManager, event.toolCallId);
|
|
1148
1002
|
const patchCalls = batch?.filter((name) => name === PATCH_STATE_TOOL_NAME).length ?? 0;
|
|
1149
1003
|
if (patchCalls > 0) {
|
|
1150
1004
|
if (patchCalls !== 1) {
|
|
@@ -1206,17 +1060,20 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1206
1060
|
return;
|
|
1207
1061
|
}
|
|
1208
1062
|
let responseCommitted = false;
|
|
1063
|
+
const specification = snapshot.meta.specification;
|
|
1209
1064
|
try {
|
|
1210
1065
|
const response = finalizedAssistantResponse(event.message);
|
|
1211
1066
|
const stage = stageScopedTransition(scopeStates, { transitions: [], response }, [], runtime!.causalBasis());
|
|
1067
|
+
completeRun(snapshot);
|
|
1212
1068
|
commitStage(stage, ctx, true);
|
|
1213
1069
|
responseCommitted = true;
|
|
1214
1070
|
bootstrapContinuation = undefined;
|
|
1215
1071
|
rehydrationPhase = "step";
|
|
1216
|
-
enqueueTurnPublication(
|
|
1217
|
-
if (snapshot.meta.remotePublication?.mode === "turn-end")
|
|
1072
|
+
enqueueTurnPublication();
|
|
1073
|
+
if (snapshot.meta.remotePublication?.mode === "turn-end") publicationWorker.launch();
|
|
1218
1074
|
completedRunAccepted = !wasBootstrap;
|
|
1219
1075
|
} catch (error) {
|
|
1076
|
+
if (!responseCommitted && specification !== undefined) snapshot.meta.specification = specification;
|
|
1220
1077
|
recordDiagnostic(error instanceof Error ? error.message : String(error), "finalization", ctx);
|
|
1221
1078
|
ctx.ui.notify(responseCommitted
|
|
1222
1079
|
? `State Flow committed the final response; remote publication is deferred: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -1240,7 +1097,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1240
1097
|
telegramStartPending = false;
|
|
1241
1098
|
startStateFlow(ctx);
|
|
1242
1099
|
}
|
|
1243
|
-
if (snapshot.meta.remotePublication?.mode === "turn-end")
|
|
1100
|
+
if (snapshot.meta.remotePublication?.mode === "turn-end") publicationWorker.launch();
|
|
1244
1101
|
if (!completedRunAccepted || compactionStopped || !snapshot.config.enabled || snapshot.meta.bootstrap || resolutionPending
|
|
1245
1102
|
|| compactionInFlight || !ctx.isIdle() || ctx.hasPendingMessages() || !snapshot.meta.durableBase
|
|
1246
1103
|
|| !shouldRequestStateFlowCompaction(ctx.getContextUsage())) return;
|
|
@@ -1262,7 +1119,7 @@ export default function stateFlowExtension(pi: ExtensionAPI, options: StateFlowE
|
|
|
1262
1119
|
rehydrationPhase = event.reason === "resume" ? "resume-bootstrap" : "new-bootstrap";
|
|
1263
1120
|
restoreActiveBranch(ctx, event.reason);
|
|
1264
1121
|
retryPendingPush(ctx);
|
|
1265
|
-
if (snapshot.meta.remotePublication?.mode === "turn-end")
|
|
1122
|
+
if (snapshot.meta.remotePublication?.mode === "turn-end") publicationWorker.launch();
|
|
1266
1123
|
updateUi(ctx);
|
|
1267
1124
|
void telegram.ensure();
|
|
1268
1125
|
});
|