@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.
Files changed (120) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +2 -2
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +12 -12
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +2 -13
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +31 -0
  6. package/node_modules/@llblab/pi-state-flow/README.md +11 -7
  7. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +5 -2
  8. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +17 -16
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -0
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +24 -0
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +4 -3
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +2 -1
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +15 -3
  14. package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +2 -0
  15. package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +4 -0
  16. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +7 -4
  17. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +124 -274
  18. package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +20 -23
  19. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +8 -4
  20. package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +29 -3
  21. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +18 -0
  22. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +44 -1
  23. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +2 -2
  24. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +55 -21
  25. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +7 -0
  26. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +65 -17
  27. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +17 -0
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +102 -0
  29. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +43 -1
  30. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +268 -10
  31. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +2 -0
  32. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +34 -5
  33. package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +17 -0
  34. package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +38 -0
  35. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +5 -5
  36. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +20 -24
  37. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +11 -3
  38. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +14 -4
  39. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +1 -1
  40. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +2 -0
  41. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +55 -20
  42. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +9 -6
  43. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +10 -3
  44. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +8 -3
  45. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  46. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +79 -0
  47. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +23 -121
  48. package/node_modules/@llblab/pi-state-flow/docs/README.md +1 -0
  49. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +27 -20
  50. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +1 -1
  51. package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +3 -2
  52. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +513 -0
  53. package/node_modules/@llblab/pi-state-flow/docs/usage.md +13 -10
  54. package/node_modules/@llblab/pi-state-flow/lib/config.ts +18 -15
  55. package/node_modules/@llblab/pi-state-flow/lib/context.ts +24 -0
  56. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +4 -3
  57. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +14 -5
  58. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +5 -0
  59. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +119 -262
  60. package/node_modules/@llblab/pi-state-flow/lib/git.ts +23 -22
  61. package/node_modules/@llblab/pi-state-flow/lib/history.ts +8 -4
  62. package/node_modules/@llblab/pi-state-flow/lib/json.ts +31 -3
  63. package/node_modules/@llblab/pi-state-flow/lib/logging.ts +53 -1
  64. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +51 -20
  65. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +61 -17
  66. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +96 -0
  67. package/node_modules/@llblab/pi-state-flow/lib/query.ts +261 -9
  68. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +32 -4
  69. package/node_modules/@llblab/pi-state-flow/lib/session.ts +45 -0
  70. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +22 -25
  71. package/node_modules/@llblab/pi-state-flow/lib/state.ts +22 -6
  72. package/node_modules/@llblab/pi-state-flow/lib/status.ts +1 -1
  73. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +46 -18
  74. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +20 -6
  75. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +8 -3
  76. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  77. package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +79 -0
  78. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +23 -121
  79. package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
  80. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -0
  81. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +7 -0
  82. package/node_modules/@llblab/pi-telegram/README.md +1 -0
  83. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +2 -1
  84. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +2 -1
  85. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +134 -2
  86. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +297 -16
  87. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +2 -0
  88. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +11 -0
  89. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +68 -8
  90. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +4 -3
  91. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +17 -13
  92. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -1
  93. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +1 -0
  94. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +1 -0
  95. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +55 -5
  96. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +23 -0
  97. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +20 -0
  98. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +18 -0
  99. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +152 -6
  100. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +1 -0
  101. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +5 -3
  102. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  103. package/node_modules/@llblab/pi-telegram/docs/architecture.md +3 -3
  104. package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
  105. package/node_modules/@llblab/pi-telegram/docs/public-api.md +4 -3
  106. package/node_modules/@llblab/pi-telegram/docs/sections.md +2 -2
  107. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +2 -1
  108. package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
  109. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +3 -0
  110. package/node_modules/@llblab/pi-telegram/lib/commands.ts +466 -21
  111. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +15 -0
  112. package/node_modules/@llblab/pi-telegram/lib/extension.ts +77 -9
  113. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +25 -10
  114. package/node_modules/@llblab/pi-telegram/lib/pi.ts +3 -0
  115. package/node_modules/@llblab/pi-telegram/lib/routing.ts +84 -17
  116. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +30 -0
  117. package/node_modules/@llblab/pi-telegram/lib/threads.ts +199 -6
  118. package/node_modules/@llblab/pi-telegram/lib/updates.ts +5 -2
  119. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  120. 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, relative, resolve, sep } from "node:path";
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 { appendStateFlowDiagnostic, projectDiagnosticContent, stateFlowLogPath, type StateFlowDiagnosticCategory } from "./logging.ts";
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 config = loadStateFlowConfig(agentDir);
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
- if (owned.every((name) => active.includes(name) === snapshot.config.enabled)) return;
275
- pi.setActiveTools(snapshot.config.enabled
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(ctx);
322
- launchPublicationWorker();
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(ctx: ExtensionContext): void {
292
+ function enqueueTurnPublication(): void {
364
293
  const target = turnPublicationTarget;
365
294
  turnPublicationTarget = undefined;
366
- if (!target) return;
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
- publicationStopped = true;
437
- return publicationShutdown ??= (async () => {
438
- const workers = [...activePublicationWorkers.values()];
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(ctx);
464
- launchPublicationWorker();
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 = passiveStopBoundary(ctx);
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
- if (!config.logging) return;
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 through a unified path such as state, state[1], state.cwd[2], or state.global.patches[0]. The legacy offset/scope form remains accepted. Read-only and lazy; unavailable hot history is an error.",
800
- promptSnippet: "Read cached state or accepted scope patches with a unified path",
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: "Unified query path; current aliases use index 0" })),
803
- offset: Type.Optional(Type.Integer({ minimum: 0, maximum: 7, description: "Legacy accepted-transition offset; defaults to 0" })),
804
- scope: Type.Optional(StringEnum(["effective", "global", "cwd", "session"] as const, { description: "Legacy projection at the same boundary; defaults to effective" })),
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; offset?: number; scope?: string; transitionId: string };
809
- if (!snapshot.config.enabled) throw new Error("State Flow is disabled on this session branch");
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 !== undefined) {
813
- if (params.offset !== undefined || params.scope !== undefined) throw new Error("read_state path cannot be combined with legacy offset or scope");
814
- const result = readStatePath(runtime.view, params.path);
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, transitionId: result.boundary.id } as ReadDetails,
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 (!snapshot.config.enabled) throw new Error("State Flow is disabled on this session branch");
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
- const stage = stageAtomicScopePatches(scopeStates, patches, skillReads.successful.values(), runtime!.causalBasis(), artifactReads.successful.values());
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) => projectStateForModel(
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) return;
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 || snapshot.meta.specification === undefined) return;
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 = assistantToolBatch(ctx, event.toolCallId);
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(ctx);
1217
- if (snapshot.meta.remotePublication?.mode === "turn-end") launchPublicationWorker();
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") launchPublicationWorker();
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") launchPublicationWorker();
1122
+ if (snapshot.meta.remotePublication?.mode === "turn-end") publicationWorker.launch();
1266
1123
  updateUi(ctx);
1267
1124
  void telegram.ensure();
1268
1125
  });