@llblab/pi-kit 0.24.1 → 0.26.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 (162) hide show
  1. package/AGENTS.md +1 -1
  2. package/BACKLOG.md +5 -1
  3. package/CHANGELOG.md +12 -0
  4. package/README.md +11 -8
  5. package/node_modules/@llblab/pi-actors/AGENTS.md +2 -0
  6. package/node_modules/@llblab/pi-actors/CHANGELOG.md +4 -1
  7. package/node_modules/@llblab/pi-actors/LICENSE +21 -0
  8. package/node_modules/@llblab/pi-actors/README.md +1 -1
  9. package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +1 -1
  10. package/node_modules/@llblab/pi-actors/package.json +4 -3
  11. package/node_modules/@llblab/pi-claude-usage/AGENTS.md +23 -0
  12. package/node_modules/@llblab/pi-claude-usage/BACKLOG.md +4 -0
  13. package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +21 -0
  14. package/node_modules/@llblab/pi-claude-usage/LICENSE +22 -0
  15. package/node_modules/@llblab/pi-claude-usage/README.md +155 -0
  16. package/node_modules/@llblab/pi-claude-usage/banner.jpg +0 -0
  17. package/node_modules/@llblab/pi-claude-usage/index.ts +8 -0
  18. package/node_modules/@llblab/pi-claude-usage/lib/extension.ts +30 -0
  19. package/node_modules/@llblab/pi-claude-usage/lib/fast.ts +24 -0
  20. package/node_modules/@llblab/pi-claude-usage/lib/query.ts +146 -0
  21. package/node_modules/@llblab/pi-claude-usage/lib/status-format.ts +297 -0
  22. package/node_modules/@llblab/pi-claude-usage/lib/status.ts +366 -0
  23. package/node_modules/@llblab/pi-claude-usage/lib/telegram.ts +44 -0
  24. package/node_modules/@llblab/pi-claude-usage/lib/usage-store.ts +221 -0
  25. package/node_modules/@llblab/pi-claude-usage/lib/usage.ts +128 -0
  26. package/node_modules/@llblab/pi-claude-usage/package.json +64 -0
  27. package/node_modules/@llblab/pi-clean-room/AGENTS.md +1 -0
  28. package/node_modules/@llblab/pi-clean-room/CHANGELOG.md +5 -0
  29. package/node_modules/@llblab/pi-clean-room/LICENSE +21 -0
  30. package/node_modules/@llblab/pi-clean-room/README.md +1 -1
  31. package/node_modules/@llblab/pi-clean-room/package.json +3 -2
  32. package/node_modules/@llblab/pi-codex-usage/AGENTS.md +9 -6
  33. package/node_modules/@llblab/pi-codex-usage/BACKLOG.md +2 -1
  34. package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +17 -0
  35. package/node_modules/@llblab/pi-codex-usage/README.md +75 -17
  36. package/node_modules/@llblab/pi-codex-usage/index.ts +8 -1602
  37. package/node_modules/@llblab/pi-codex-usage/lib/extension.ts +25 -0
  38. package/node_modules/@llblab/pi-codex-usage/lib/fast.ts +23 -0
  39. package/node_modules/@llblab/pi-codex-usage/lib/query.ts +368 -0
  40. package/node_modules/@llblab/pi-codex-usage/lib/status-format.ts +347 -0
  41. package/node_modules/@llblab/pi-codex-usage/lib/status.ts +435 -0
  42. package/node_modules/@llblab/pi-codex-usage/lib/telegram.ts +45 -0
  43. package/node_modules/@llblab/pi-codex-usage/lib/usage-store.ts +229 -0
  44. package/node_modules/@llblab/pi-codex-usage/lib/usage.ts +425 -0
  45. package/node_modules/@llblab/pi-codex-usage/package.json +11 -6
  46. package/node_modules/@llblab/pi-command-fast/AGENTS.md +7 -0
  47. package/node_modules/@llblab/pi-command-fast/BACKLOG.md +9 -0
  48. package/node_modules/@llblab/pi-command-fast/CHANGELOG.md +7 -0
  49. package/node_modules/@llblab/pi-command-fast/LICENSE +21 -0
  50. package/node_modules/@llblab/pi-command-fast/README.md +42 -0
  51. package/node_modules/@llblab/pi-command-fast/dist/command.d.ts +8 -0
  52. package/node_modules/@llblab/pi-command-fast/dist/command.js +52 -0
  53. package/node_modules/@llblab/pi-command-fast/dist/index.d.ts +3 -0
  54. package/node_modules/@llblab/pi-command-fast/dist/index.js +3 -0
  55. package/node_modules/@llblab/pi-command-fast/dist/models-json.d.ts +10 -0
  56. package/node_modules/@llblab/pi-command-fast/dist/models-json.js +81 -0
  57. package/node_modules/@llblab/pi-command-fast/package.json +49 -0
  58. package/node_modules/@llblab/pi-grow-loop/AGENTS.md +1 -0
  59. package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +4 -1
  60. package/node_modules/@llblab/pi-grow-loop/LICENSE +21 -0
  61. package/node_modules/@llblab/pi-grow-loop/README.md +1 -1
  62. package/node_modules/@llblab/pi-grow-loop/package.json +3 -2
  63. package/node_modules/@llblab/pi-state-flow/AGENTS.md +43 -56
  64. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +17 -3
  65. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +25 -0
  66. package/node_modules/@llblab/pi-state-flow/LICENSE +21 -0
  67. package/node_modules/@llblab/pi-state-flow/README.md +18 -15
  68. package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +2 -2
  69. package/node_modules/@llblab/pi-state-flow/dist/index.js +2 -2
  70. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +1 -1
  71. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +1 -1
  72. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +7 -3
  73. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +16 -7
  74. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +9 -9
  75. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +5 -4
  76. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +2 -2
  77. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +1 -1
  78. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +7 -4
  79. package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +3 -3
  80. package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +5 -5
  81. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +3 -5
  82. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +503 -235
  83. package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -2
  84. package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +16 -5
  85. package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +11 -4
  86. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +6 -7
  87. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +4 -1
  88. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +1 -0
  89. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +4 -5
  90. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +13 -13
  91. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +7 -6
  92. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +9 -9
  93. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +3 -2
  94. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +17 -12
  95. package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +13 -1
  96. package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +62 -2
  97. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +23 -8
  98. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +52 -20
  99. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +22 -3
  100. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +30 -10
  101. package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +5 -3
  102. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +19 -28
  103. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +17 -15
  104. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +52 -52
  105. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +8 -4
  106. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +34 -18
  107. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +5 -5
  108. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +13 -19
  109. package/node_modules/@llblab/pi-state-flow/dist/package.json +12 -11
  110. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +2 -2
  111. package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -1
  112. package/node_modules/@llblab/pi-state-flow/docs/agent-contract-relocation.md +72 -0
  113. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +44 -36
  114. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +14 -6
  115. package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +7 -5
  116. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +6 -6
  117. package/node_modules/@llblab/pi-state-flow/docs/performance.md +1 -1
  118. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +13 -12
  119. package/node_modules/@llblab/pi-state-flow/docs/usage.md +37 -33
  120. package/node_modules/@llblab/pi-state-flow/index.ts +3 -2
  121. package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +2 -2
  122. package/node_modules/@llblab/pi-state-flow/lib/config.ts +20 -10
  123. package/node_modules/@llblab/pi-state-flow/lib/context.ts +15 -14
  124. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +1 -1
  125. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +8 -6
  126. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +6 -6
  127. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +484 -232
  128. package/node_modules/@llblab/pi-state-flow/lib/git.ts +14 -5
  129. package/node_modules/@llblab/pi-state-flow/lib/history.ts +16 -11
  130. package/node_modules/@llblab/pi-state-flow/lib/logging.ts +5 -1
  131. package/node_modules/@llblab/pi-state-flow/lib/query.ts +16 -16
  132. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +11 -11
  133. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +19 -13
  134. package/node_modules/@llblab/pi-state-flow/lib/session.ts +57 -3
  135. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +57 -22
  136. package/node_modules/@llblab/pi-state-flow/lib/state.ts +46 -13
  137. package/node_modules/@llblab/pi-state-flow/lib/status.ts +23 -32
  138. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +66 -65
  139. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +39 -19
  140. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +19 -27
  141. package/node_modules/@llblab/pi-state-flow/package.json +12 -11
  142. package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +2 -2
  143. package/node_modules/jsonc-parser/CHANGELOG.md +76 -0
  144. package/node_modules/jsonc-parser/LICENSE.md +21 -0
  145. package/node_modules/jsonc-parser/README.md +364 -0
  146. package/node_modules/jsonc-parser/SECURITY.md +41 -0
  147. package/node_modules/jsonc-parser/lib/esm/impl/edit.js +185 -0
  148. package/node_modules/jsonc-parser/lib/esm/impl/format.js +261 -0
  149. package/node_modules/jsonc-parser/lib/esm/impl/parser.js +659 -0
  150. package/node_modules/jsonc-parser/lib/esm/impl/scanner.js +443 -0
  151. package/node_modules/jsonc-parser/lib/esm/impl/string-intern.js +29 -0
  152. package/node_modules/jsonc-parser/lib/esm/main.d.ts +351 -0
  153. package/node_modules/jsonc-parser/lib/esm/main.js +178 -0
  154. package/node_modules/jsonc-parser/lib/umd/impl/edit.js +201 -0
  155. package/node_modules/jsonc-parser/lib/umd/impl/format.js +275 -0
  156. package/node_modules/jsonc-parser/lib/umd/impl/parser.js +682 -0
  157. package/node_modules/jsonc-parser/lib/umd/impl/scanner.js +456 -0
  158. package/node_modules/jsonc-parser/lib/umd/impl/string-intern.js +42 -0
  159. package/node_modules/jsonc-parser/lib/umd/main.d.ts +351 -0
  160. package/node_modules/jsonc-parser/lib/umd/main.js +194 -0
  161. package/node_modules/jsonc-parser/package.json +37 -0
  162. package/package.json +10 -6
@@ -1,11 +1,11 @@
1
1
  /** Await a coherent capture; hosts without cancellation may refuse contention instead of hanging Abort. */
2
2
  export declare function backupCurrentStateFlowFiles(repositoryRoot: string, signal?: AbortSignal, waitForLock?: boolean): Promise<string | undefined>;
3
3
  /** Skip overlapping pushes; the next accepted turn can push the latest HEAD. */
4
- export declare function startStateFlowBackupPush(repositoryRoot: string, onFailure: (error: unknown) => void, onSuccess?: () => void): boolean;
4
+ export declare function startStateFlowBackupPush(repositoryRoot: string, onFailure: (error: unknown) => void, onSuccess?: () => void, signal?: AbortSignal): boolean;
5
5
  /** Resolve only after the push process has closed (including timeout termination). */
6
6
  export declare function awaitInFlightBackupPushes(repositoryRoot: string): Promise<void>;
7
7
  /** Push the current backup commit to its explicitly configured branch remote without blocking settlement. */
8
- export declare function pushCurrentStateFlowBackup(repositoryRoot: string): Promise<{
8
+ export declare function pushCurrentStateFlowBackup(repositoryRoot: string, signal?: AbortSignal): Promise<{
9
9
  commit: string;
10
10
  remote: string;
11
11
  ref: string;
@@ -204,18 +204,20 @@ export function backupCurrentStateFlowFiles(repositoryRoot, signal, waitForLock
204
204
  return withBackupLock(repositoryRoot, (root) => commitCurrentOwnedFiles(root, currentHead(root), signal, waitForLock), signal, waitForLock);
205
205
  }
206
206
  /** Skip overlapping pushes; the next accepted turn can push the latest HEAD. */
207
- export function startStateFlowBackupPush(repositoryRoot, onFailure, onSuccess) {
207
+ export function startStateFlowBackupPush(repositoryRoot, onFailure, onSuccess, signal) {
208
208
  const root = resolve(repositoryRoot);
209
- if (activePushes.has(root))
209
+ if (signal?.aborted || activePushes.has(root))
210
210
  return false;
211
- const push = pushCurrentStateFlowBackup(root).then((result) => {
212
- if (result) {
211
+ const push = pushCurrentStateFlowBackup(root, signal).then((result) => {
212
+ if (result && !signal?.aborted) {
213
213
  try {
214
214
  onSuccess?.();
215
215
  }
216
216
  catch { /* Reporting cannot change push acceptance. */ }
217
217
  }
218
218
  }, (error) => {
219
+ if (signal?.aborted)
220
+ return;
219
221
  try {
220
222
  onFailure(error);
221
223
  }
@@ -231,15 +233,17 @@ export async function awaitInFlightBackupPushes(repositoryRoot) {
231
233
  await push;
232
234
  }
233
235
  /** Push the current backup commit to its explicitly configured branch remote without blocking settlement. */
234
- export function pushCurrentStateFlowBackup(repositoryRoot) {
236
+ export function pushCurrentStateFlowBackup(repositoryRoot, signal) {
235
237
  return new Promise((resolvePush, rejectPush) => {
236
238
  let root;
237
239
  let commit;
238
240
  let destination;
239
241
  try {
242
+ signal?.throwIfAborted();
240
243
  root = assertRepositoryRoot(repositoryRoot);
241
244
  commit = currentHead(root);
242
245
  destination = configuredPushDestination(root);
246
+ signal?.throwIfAborted();
243
247
  }
244
248
  catch (error) {
245
249
  rejectPush(new Error(redactGitDiagnostic(error instanceof Error ? error.message : String(error))));
@@ -282,6 +286,7 @@ export function pushCurrentStateFlowBackup(repositoryRoot) {
282
286
  return;
283
287
  settled = true;
284
288
  clearTimeout(timeout);
289
+ signal?.removeEventListener("abort", cancelled);
285
290
  if (error)
286
291
  rejectPush(error);
287
292
  else
@@ -297,5 +302,11 @@ export function pushCurrentStateFlowBackup(repositoryRoot) {
297
302
  child.stderr?.destroy(); });
298
303
  child.once("close", (code, endedBy) => finish(failure ?? (code === 0 ? undefined
299
304
  : new Error(`Git backup push failed (${endedBy ?? code}): ${redactGitDiagnostic(stderr.trim()) || "no diagnostic output"}`))));
305
+ function cancelled() {
306
+ terminate(signal?.reason instanceof Error ? signal.reason : new Error("Git backup push cancelled"));
307
+ }
308
+ signal?.addEventListener("abort", cancelled, { once: true });
309
+ if (signal?.aborted)
310
+ cancelled();
300
311
  });
301
312
  }
@@ -1,10 +1,17 @@
1
- import type { ScopePatch, ScopedStates, StateScope } from "./state.ts";
1
+ import { type JsonObject, type JsonValue } from "./json.ts";
2
+ import type { ScopedSemanticStates, StateScope } from "./state.ts";
2
3
  export declare const DEFAULT_HISTORY_LIMIT = 7;
3
4
  export declare const MAX_HISTORY_LIMIT = 100;
4
5
  export interface RecentScopePatch {
5
6
  scope: StateScope;
6
- patch: ScopePatch & {
7
- response?: string;
7
+ patch: {
8
+ [key: string]: JsonValue | undefined;
9
+ intents?: JsonObject | null;
10
+ contract?: JsonObject | null;
11
+ working?: JsonObject | null;
12
+ artifacts?: JsonObject | null;
13
+ lazy?: JsonObject | null;
14
+ response?: string | null;
8
15
  };
9
16
  }
10
17
  /** Exact accepted replay cohort; temporal runtime owns its causal boundary. */
@@ -18,6 +25,6 @@ export interface RecentTransition extends AcceptedTransition {
18
25
  }
19
26
  export type RecentTransitionWindow = RecentTransition[];
20
27
  export declare function validateRecentTransition(value: unknown): asserts value is RecentTransition;
21
- export declare function createAcceptedTransition(currentStates: ScopedStates, nextStates: ScopedStates, id?: string): AcceptedTransition | undefined;
28
+ export declare function createAcceptedTransition(currentStates: ScopedSemanticStates, nextStates: ScopedSemanticStates, id?: string): AcceptedTransition | undefined;
22
29
  /** Preserve the configured per-scope budget, filtering in selected-lineage order. */
23
30
  export declare function projectRecentTransitionsWithLimit(limit: number, lineage: readonly RecentTransition[]): RecentTransitionWindow;
@@ -30,13 +30,10 @@ function validateScopedPatch(value) {
30
30
  validatePatch(value.patch);
31
31
  for (const [key, field] of Object.entries(value.patch)) {
32
32
  const valid = key === "response"
33
- ? value.scope === "session" && typeof field === "string"
34
- : key === "lazy"
35
- ? field !== null
36
- : PATCH_KEYS.has(key) && isObject(field);
37
- if (!PATCH_KEYS.has(key) || !valid) {
38
- throw new Error("Recent State Flow patches may contain hot object planes, ordinary-JSON lazy state, and a session response string");
39
- }
33
+ ? value.scope === "session" && (field === null || typeof field === "string")
34
+ : !PATCH_KEYS.has(key) || field === null || isObject(field);
35
+ if (!valid)
36
+ throw new Error("Recent State Flow patches require object planes or deletion, and a session response string or deletion");
40
37
  }
41
38
  }
42
39
  export function validateRecentTransition(value) {
@@ -60,6 +57,8 @@ export function createAcceptedTransition(currentStates, nextStates, id) {
60
57
  const transitions = [];
61
58
  for (const scope of SCOPES) {
62
59
  const patch = replayPatch(currentStates[scope], nextStates[scope]);
60
+ if ((currentStates[scope].response ?? "") === (nextStates[scope].response ?? ""))
61
+ delete patch.response;
63
62
  if (Object.keys(patch).length > 0)
64
63
  transitions.push({ scope, patch });
65
64
  }
@@ -1,4 +1,4 @@
1
- export type StateFlowDiagnosticCategory = "invalid-patch" | "publication-conflict" | "finalization";
1
+ export type StateFlowDiagnosticCategory = "invalid-patch" | "publication-conflict" | "finalization" | "barrier-block";
2
2
  /** Minimal structural block; only ordinary text keeps its exact content. */
3
3
  export interface StateFlowDiagnosticBlock {
4
4
  type: string;
@@ -15,6 +15,8 @@ export interface StateFlowDiagnosticRecord {
15
15
  input?: unknown;
16
16
  tool?: string;
17
17
  toolCallId?: string;
18
+ /** Names only, in native assistant-batch order; never sibling arguments. */
19
+ batchToolNames?: string[];
18
20
  }
19
21
  /** Preserve exact text blocks and block boundaries; reasoning bodies are never duplicated. */
20
22
  export declare function projectDiagnosticContent(content: unknown): StateFlowDiagnosticBlock[];
@@ -26,6 +28,7 @@ export interface DiagnosticExtras {
26
28
  input?: unknown;
27
29
  tool?: string;
28
30
  toolCallId?: string;
31
+ batchToolNames?: readonly string[];
29
32
  }
30
33
  /** Own diagnostic path safety, projection, persistence, and one-shot failure reporting. */
31
34
  export declare class StateFlowDiagnosticWriter {
@@ -77,6 +77,7 @@ export class StateFlowDiagnosticWriter {
77
77
  ...(extras.input === undefined ? {} : { input: extras.input }),
78
78
  ...(extras.tool === undefined ? {} : { tool: extras.tool }),
79
79
  ...(extras.toolCallId === undefined ? {} : { toolCallId: extras.toolCallId }),
80
+ ...(extras.batchToolNames === undefined ? {} : { batchToolNames: [...extras.batchToolNames] }),
80
81
  });
81
82
  return true;
82
83
  }
@@ -1,5 +1,6 @@
1
+ import { type RecentScopePatch } from "./history.ts";
1
2
  import { type JsonValue } from "./json.ts";
2
- import { type ModelState, type ScopePatch, type StateScope } from "./state.ts";
3
+ import { type SemanticState, type StateScope } from "./state.ts";
3
4
  import { type TemporalState, type TransitionBoundary } from "./temporal.ts";
4
5
  export type StateReadQuery = {
5
6
  kind: "state";
@@ -15,13 +16,11 @@ export type StateReadQuery = {
15
16
  export type StateReadResult = {
16
17
  path: string;
17
18
  boundary: TransitionBoundary;
18
- state: ModelState;
19
+ state: SemanticState;
19
20
  } | {
20
21
  path: string;
21
22
  boundary: TransitionBoundary;
22
- patch: ScopePatch & {
23
- response?: string;
24
- };
23
+ patch: RecentScopePatch["patch"];
25
24
  };
26
25
  export type StateReadProjection = "value" | "keys" | "patch";
27
26
  type StateReadMeta = {
@@ -1,7 +1,7 @@
1
1
  import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT } from "./history.js";
2
2
  import { isObject, sameJson } from "./json.js";
3
- import { projectStateForModel } from "./state.js";
4
- import { readTemporalState } from "./temporal.js";
3
+ import { emptyState, projectSemanticPatch, projectStateForModel } from "./state.js";
4
+ import { readTemporalView } from "./temporal.js";
5
5
  const MAX_REFERENCE_SOURCES = 3;
6
6
  const MAX_REFERENCE_SCAN_NODES = 10_000;
7
7
  const PATH_PATTERN = /^(effective|global|cwd|session)(?:\[(\d+)\])?(?:\.patches(?:\[(\d+)\])?)?$/;
@@ -32,12 +32,12 @@ export function readStatePath(view, path, historyLimit = DEFAULT_HISTORY_LIMIT)
32
32
  const boundary = view.lineage[view.lineage.length - 1 - query.offset];
33
33
  if (!boundary)
34
34
  throw new Error("Requested history predates the proven temporal origin");
35
- return { path, boundary: structuredClone(boundary), state: projectStateForModel(readTemporalState(view, query.offset, query.scope, historyLimit)) };
35
+ return { path, boundary: structuredClone(boundary), state: projectStateForModel(readTemporalView(view, query.offset, query.scope, historyLimit)) };
36
36
  }
37
37
  const record = view.scopes[query.scope].patches.at(-1 - query.offset);
38
38
  if (!record)
39
39
  throw new Error(`Requested ${query.scope} patch predates retained hot history`);
40
- return { path, boundary: structuredClone(record.transition), patch: structuredClone(record.patch) };
40
+ return { path, boundary: structuredClone(record.transition), patch: projectSemanticPatch(record.patch) };
41
41
  }
42
42
  function parseValuePath(path) {
43
43
  const explicitRoot = /^(?:effective|global|cwd|session)(?:\[\d+\])?(?=\.|$)/.exec(path)?.[0];
@@ -151,7 +151,7 @@ export function findStateReferenceSources(view, path, historyLimit = DEFAULT_HIS
151
151
  }
152
152
  };
153
153
  for (const scope of ["global", "cwd", "session"]) {
154
- const state = readTemporalState(view, 0, scope, historyLimit);
154
+ const state = readTemporalView(view, 0, scope, historyLimit);
155
155
  for (const plane of ["artifacts", "contract", "working", "intents", "lazy"]) {
156
156
  const value = state[plane];
157
157
  if (value !== undefined)
@@ -201,13 +201,13 @@ function patchAtPath(view, root, selectors, path, historyLimit) {
201
201
  throw new Error("Requested history predates the proven temporal origin");
202
202
  let patch = {};
203
203
  if (rootQuery.scope) {
204
- patch = structuredClone(view.scopes[rootQuery.scope].patches.find((record) => record.transition.id === boundary.id)?.patch ?? {});
204
+ patch = projectSemanticPatch((view.scopes[rootQuery.scope].patches.find((record) => record.transition.id === boundary.id)?.patch ?? {}));
205
205
  }
206
206
  else {
207
207
  const before = view.lineage.at(-2 - rootQuery.offset);
208
208
  if (before) {
209
- const current = readTemporalState(view, rootQuery.offset, undefined, historyLimit);
210
- const previous = readTemporalState(view, rootQuery.offset + 1, undefined, historyLimit);
209
+ const current = readTemporalView(view, rootQuery.offset, undefined, historyLimit);
210
+ const previous = readTemporalView(view, rootQuery.offset + 1, undefined, historyLimit);
211
211
  patch = diffObjects(previous, current);
212
212
  }
213
213
  }
@@ -270,11 +270,11 @@ export function readProjectedState(view, paths, projection = "value", historyLim
270
270
  if (query.kind !== "state")
271
271
  throw new Error("Value and keys projections require a state path");
272
272
  const readsLazy = selectors[0]?.kind === "key" && selectors[0].key === "lazy";
273
- const state = readsLazy
274
- ? readTemporalState(view, query.offset, query.scope, historyLimit)
275
- : projectStateForModel(readTemporalState(view, query.offset, query.scope, historyLimit));
276
- if (readsLazy && !Object.hasOwn(state, "lazy"))
277
- state.lazy = {};
273
+ const raw = readTemporalView(view, query.offset, query.scope, historyLimit);
274
+ const state = readsLazy ? raw : projectStateForModel(raw);
275
+ const field = selectors.length === 1 && selectors[0]?.kind === "key" ? selectors[0].key : undefined;
276
+ if (projection === "value" && field !== undefined && Object.hasOwn(emptyState(), field) && !Object.hasOwn(state, field))
277
+ return { value: null };
278
278
  return projectValue(selectValue(state, selectors, path), projection);
279
279
  }
280
280
  catch (error) {
@@ -1,19 +1,20 @@
1
- import { type RetainedBoundaryCheckpoint, type Snapshot } from "./snapshot.ts";
1
+ import { type InactiveMode, type RetainedBoundaryCheckpoint, type Snapshot } from "./snapshot.ts";
2
2
  export type RetainedCheckpointSelection = {
3
3
  kind: "boundary";
4
4
  checkpoint: RetainedBoundaryCheckpoint;
5
5
  skipped: string[];
6
6
  } | {
7
- kind: "disabled";
7
+ kind: "pre-runtime";
8
+ mode: InactiveMode;
8
9
  skipped: string[];
9
10
  } | {
10
11
  kind: "unavailable";
11
12
  snapshot: Snapshot;
12
13
  skipped: string[];
13
14
  };
14
- /** Select the newest supported retained-boundary checkpoint or disabled marker; unsupported pointers fail closed. */
15
- export declare function selectRetainedCheckpoint(candidates: readonly unknown[]): RetainedCheckpointSelection;
16
- /** Withdraw a caller's join without cancelling independently owned recovery or Stop persistence. */
15
+ /** Select the newest supported retained-boundary checkpoint or pre-runtime mode; unsupported pointers fail closed. */
16
+ export declare function selectRetainedCheckpoint(candidates: readonly unknown[], inactiveMode?: InactiveMode): RetainedCheckpointSelection;
17
+ /** Withdraw a caller's join without cancelling independently owned recovery or mode persistence. */
17
18
  export declare function waitForRecovery<T>(operation: Promise<T>, signal: AbortSignal): Promise<T>;
18
19
  /** A selected boundary that cannot be resolved stays unavailable; callers never fall through to older evidence. */
19
- export declare function selectedBoundaryFailure(cause: string): Snapshot;
20
+ export declare function selectedBoundaryFailure(cause: string, mode?: InactiveMode): Snapshot;
@@ -1,28 +1,28 @@
1
1
  import { parseRetainedPiCheckpoint, migrationFailure } from "./snapshot.js";
2
- /** Select the newest supported retained-boundary checkpoint or disabled marker; unsupported pointers fail closed. */
3
- export function selectRetainedCheckpoint(candidates) {
2
+ /** Select the newest supported retained-boundary checkpoint or pre-runtime mode; unsupported pointers fail closed. */
3
+ export function selectRetainedCheckpoint(candidates, inactiveMode = "passive") {
4
4
  const skipped = [];
5
5
  for (const candidate of candidates) {
6
6
  let retained;
7
7
  try {
8
8
  if (typeof candidate === "object" && candidate !== null && Object.hasOwn(candidate, "revision")) {
9
- return { kind: "unavailable", snapshot: migrationFailure({}, "Snapshot restoration failed: revision-pointer checkpoints are unsupported"), skipped };
9
+ return { kind: "unavailable", snapshot: migrationFailure({}, "Snapshot restoration failed: revision-pointer checkpoints are unsupported", inactiveMode), skipped };
10
10
  }
11
- retained = parseRetainedPiCheckpoint(candidate);
11
+ retained = parseRetainedPiCheckpoint(candidate, inactiveMode);
12
12
  }
13
13
  catch (error) {
14
14
  skipped.push(`Snapshot restoration failed: ${error instanceof Error ? error.message : String(error)}`);
15
15
  continue;
16
16
  }
17
- return "disabled" in retained ? { kind: "disabled", skipped } : { kind: "boundary", checkpoint: retained, skipped };
17
+ return "boundary" in retained ? { kind: "boundary", checkpoint: retained, skipped } : { kind: "pre-runtime", mode: retained.mode, skipped };
18
18
  }
19
19
  return {
20
20
  kind: "unavailable",
21
- snapshot: migrationFailure({}, skipped[0] ?? "Snapshot restoration failed: no supported checkpoint"),
21
+ snapshot: migrationFailure({}, skipped[0] ?? "Snapshot restoration failed: no supported checkpoint", inactiveMode),
22
22
  skipped,
23
23
  };
24
24
  }
25
- /** Withdraw a caller's join without cancelling independently owned recovery or Stop persistence. */
25
+ /** Withdraw a caller's join without cancelling independently owned recovery or mode persistence. */
26
26
  export function waitForRecovery(operation, signal) {
27
27
  return new Promise((resolve, reject) => {
28
28
  const aborted = () => reject(signal.reason);
@@ -42,6 +42,6 @@ export function waitForRecovery(operation, signal) {
42
42
  });
43
43
  }
44
44
  /** A selected boundary that cannot be resolved stays unavailable; callers never fall through to older evidence. */
45
- export function selectedBoundaryFailure(cause) {
46
- return migrationFailure({}, `Snapshot restoration failed: ${cause}`);
45
+ export function selectedBoundaryFailure(cause, mode = "passive") {
46
+ return migrationFailure({}, `Snapshot restoration failed: ${cause}`, mode);
47
47
  }
@@ -3,11 +3,11 @@ import { type ArtifactProvenance, type ArtifactProvenanceRegistry } from "./arti
3
3
  import { publishTemporalStateToFiles } from "./storage.ts";
4
4
  import { type AcceptedTransition, type RecentTransitionWindow } from "./history.ts";
5
5
  import { type RetainedBoundaryCheckpoint, type RetainedPiCheckpoint, type Snapshot } from "./snapshot.ts";
6
- import { type MaterializedState, type ScopedStates, type StateScope } from "./state.ts";
6
+ import { type MaterializedState, type SemanticState, type ScopedSemanticStates, type ScopedStates, type StateScope } from "./state.ts";
7
7
  import { type TemporalState } from "./temporal.ts";
8
8
  export type RuntimePublication = ReturnType<typeof publishTemporalStateToFiles>;
9
9
  export interface RuntimePatchTransaction {
10
- readonly states: ScopedStates;
10
+ readonly states: ScopedSemanticStates;
11
11
  readonly causalBasis: string;
12
12
  readonly provenance: Record<StateScope, ArtifactProvenanceRegistry>;
13
13
  publish(snapshot: Snapshot, accepted?: AcceptedTransition, provenance?: Partial<Record<StateScope, Record<string, ArtifactProvenance>>>): RuntimePublication;
@@ -45,6 +45,7 @@ export declare class TemporalRuntime {
45
45
  /** Canonical preparation is the default; Git backup is a later independent concern. */
46
46
  prepare(): void;
47
47
  read(offset?: number, scope?: StateScope): MaterializedState;
48
+ readView(offset?: number, scope?: StateScope): SemanticState;
48
49
  states(): ScopedStates;
49
50
  causalBasis(): string;
50
51
  /** Encode Pi lifecycle state against the current retained semantic boundary. */
@@ -6,9 +6,8 @@ import { pruneArtifactProvenance } from "./artifact.js";
6
6
  import { assertTemporalFileBase, captureTemporalFileBase, initializeFileStore, publishTemporalStateToFiles, withStorageTransaction } from "./storage.js";
7
7
  import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT } from "./history.js";
8
8
  import { hashJson, sameJson } from "./json.js";
9
- import { HistoryBoundaryExpiredError, RevisionUnavailableError, createSessionRuntime, parseSessionRuntime, retainedBoundaryCheckpoint } from "./snapshot.js";
10
- import { emptyState } from "./state.js";
11
- import { adoptTemporalStreams, advanceTemporalState, constrainTemporalState, createTemporalState, readTemporalState, selectScopeStreamAtBoundary, validateScopeLineage } from "./temporal.js";
9
+ import { HistoryBoundaryExpiredError, RevisionUnavailableError, createSessionRuntime, parseSessionRuntime, preRuntimeCheckpoint, retainedBoundaryCheckpoint } from "./snapshot.js";
10
+ import { adoptTemporalStreams, advanceTemporalState, constrainTemporalState, createTemporalState, readTemporalScopes, readTemporalState, readTemporalView, selectScopeStreamAtBoundary, validateScopeLineage } from "./temporal.js";
12
11
  const SCOPES = ["global", "cwd", "session"];
13
12
  const SHARED_SCOPES = ["global", "cwd"];
14
13
  function emptyProvenance() {
@@ -42,7 +41,7 @@ function removedTargetScopeConflict(scopes) {
42
41
  return new SharedScopeRemovalConflictError(scopes);
43
42
  }
44
43
  function freshEmptyScopeStream(scope, origin, historyLimit) {
45
- return createTemporalState({ global: emptyState(), cwd: emptyState(), session: emptyState() }, origin, historyLimit).scopes[scope];
44
+ return createTemporalState({ global: {}, cwd: {}, session: {} }, origin, historyLimit).scopes[scope];
46
45
  }
47
46
  /** Cached branch-selected temporal state and publication basis; excludes Pi event policy. */
48
47
  export class TemporalRuntime {
@@ -89,7 +88,7 @@ export class TemporalRuntime {
89
88
  return false;
90
89
  if (!shared.global)
91
90
  throw new Error("Incomplete passive State Flow shared storage: CWD state exists without global state");
92
- const fresh = createTemporalState({ global: emptyState(), cwd: emptyState(), session: emptyState() }, randomUUID(), this.historyLimit);
91
+ const fresh = createTemporalState({ global: {}, cwd: {}, session: {} }, randomUUID(), this.historyLimit);
93
92
  // Global memory is valid before this CWD has ever materialized its own scope.
94
93
  const view = adoptTemporalStreams({ global: shared.global, cwd: shared.cwd ?? fresh.scopes.cwd, session: fresh.scopes.session }, `passive:files:${randomUUID()}`, this.historyLimit);
95
94
  const provenance = {
@@ -119,6 +118,11 @@ export class TemporalRuntime {
119
118
  throw new Error("State Flow temporal runtime is unavailable");
120
119
  return readTemporalState(this.view, offset, scope, this.historyLimit);
121
120
  }
121
+ readView(offset = 0, scope) {
122
+ if (!this.view)
123
+ throw new Error("State Flow temporal runtime is unavailable");
124
+ return readTemporalView(this.view, offset, scope, this.historyLimit);
125
+ }
122
126
  states() {
123
127
  return { global: this.read(0, "global"), cwd: this.read(0, "cwd"), session: this.read(0, "session") };
124
128
  }
@@ -130,7 +134,7 @@ export class TemporalRuntime {
130
134
  /** Encode Pi lifecycle state against the current retained semantic boundary. */
131
135
  retainedCheckpoint(snapshot) {
132
136
  if (!this.view)
133
- return { disabled: true };
137
+ return preRuntimeCheckpoint(snapshot.config.mode);
134
138
  return retainedBoundaryCheckpoint(snapshot, this.causalBasis());
135
139
  }
136
140
  usesCanonicalFiles() {
@@ -179,7 +183,7 @@ export class TemporalRuntime {
179
183
  session: selectedSession,
180
184
  }, `restore:${checkpoint.boundary}:${randomUUID()}`, this.historyLimit);
181
185
  const snapshot = {
182
- config: { enabled: checkpoint.enabled },
186
+ config: { mode: checkpoint.mode },
183
187
  meta: {
184
188
  step: checkpoint.step,
185
189
  ...(checkpoint.bootstrap === true ? { bootstrap: true } : {}),
@@ -195,7 +199,7 @@ export class TemporalRuntime {
195
199
  for (const record of scopes.session.patches) {
196
200
  if (record.transition.position <= boundary.position)
197
201
  continue;
198
- for (const path of Object.keys(record.patch.artifacts ?? {}))
202
+ for (const path of Object.keys(record.patch.artifacts === null ? retained : record.patch.artifacts ?? {}))
199
203
  delete retained[path];
200
204
  }
201
205
  }
@@ -266,8 +270,9 @@ export class TemporalRuntime {
266
270
  const meta = temporalScopePaths(this.cwd, this.sessionId, scope, this.root, this.sessionKey).meta;
267
271
  return [scope, parseScopeProvenance(files.get(meta), meta)];
268
272
  }));
273
+ // Read-only recovery grants no active policy; callers select the inactive mode.
269
274
  const snapshot = {
270
- config: { enabled: false },
275
+ config: { mode: "passive" },
271
276
  meta: { step: document.meta.step, ...(document.meta.bootstrap === true ? { bootstrap: true } : {}) },
272
277
  };
273
278
  this.view = view;
@@ -355,7 +360,7 @@ export class TemporalRuntime {
355
360
  streams.session = undefined;
356
361
  if (copy)
357
362
  streams.session = copy.stream;
358
- const fresh = createTemporalState({ global: emptyState(), cwd: emptyState(), session: emptyState() }, randomUUID(), this.historyLimit);
363
+ const fresh = createTemporalState({ global: {}, cwd: {}, session: {} }, randomUUID(), this.historyLimit);
359
364
  const candidate = new TemporalRuntime(this.cwd, this.session, this.root, undefined, this.historyLimit);
360
365
  candidate.base = base;
361
366
  candidate.view = adoptTemporalStreams({ global: streams.global ?? fresh.scopes.global, cwd: streams.cwd ?? fresh.scopes.cwd, session: streams.session ?? fresh.scopes.session }, `files:${randomUUID()}`, this.historyLimit);
@@ -512,7 +517,7 @@ export class TemporalRuntime {
512
517
  if (!document || !stream)
513
518
  throw new RevisionUnavailableError("Current State Flow session memory is incomplete");
514
519
  validateScopeLineage(stream, "session", document.meta.lineage, MAX_HISTORY_LIMIT);
515
- throw new RevisionUnavailableError("Existing State Flow session memory is not selected; select an accepted boundary or use /state-flow-start");
520
+ throw new RevisionUnavailableError("Existing State Flow session memory is not selected; select an accepted boundary or use /state-flow-active");
516
521
  }
517
522
  const origin = `patch:${randomUUID()}`;
518
523
  const streams = Object.fromEntries(SCOPES.map((scope) => {
@@ -531,7 +536,7 @@ export class TemporalRuntime {
531
536
  /** Stage and accept synchronously inside an awaited lock; expose neither selection nor raw storage operations. */
532
537
  async withPatchTransaction(action, signal) {
533
538
  return this.withPublicationTransaction((candidate, publish) => action({
534
- states: candidate.states(),
539
+ states: readTemporalScopes(candidate.view, 0, this.historyLimit),
535
540
  causalBasis: candidate.causalBasis(),
536
541
  provenance: structuredClone(candidate.provenanceByScope),
537
542
  publish,
@@ -1,3 +1,4 @@
1
+ import { type InactiveMode, type StateFlowMode } from "./snapshot.ts";
1
2
  export declare const SNAPSHOT_ENTRY_TYPE = "state-flow-snapshot";
2
3
  interface BranchEntry {
3
4
  type?: unknown;
@@ -21,8 +22,12 @@ export interface PassiveStopBoundary {
21
22
  at: number;
22
23
  from?: number;
23
24
  preserveContext?: true;
24
- /** A same-owner failed Stop remains a write fence until a later accepted checkpoint. */
25
+ /** A same-owner failed mode change remains a write fence until a later accepted checkpoint. */
25
26
  persistenceError?: string;
27
+ /** The inactive mode selected by that failed change; legacy markers omit it. */
28
+ mode?: InactiveMode;
29
+ /** Native-only Off choice; canonical runtime mode is deliberately not updated. */
30
+ memoryDeferred?: true;
26
31
  }
27
32
  export interface SnapshotDiscovery {
28
33
  candidates: unknown[];
@@ -30,6 +35,13 @@ export interface SnapshotDiscovery {
30
35
  }
31
36
  /** Enumerate active-branch snapshots newest-first while containing hostile entries. */
32
37
  export declare function discoverSnapshotData(branch: readonly BranchEntry[]): SnapshotDiscovery;
38
+ /** Read native mode policy only; semantic checkpoint validity remains the recovery owner's concern. */
39
+ export declare function findBranchPolicy(branch: readonly BranchEntry[], sessionId: string | undefined, stopEntryType: string | undefined, inactiveMode: InactiveMode): {
40
+ mode: StateFlowMode;
41
+ persistenceError?: string;
42
+ } | undefined;
43
+ /** Native policy bookkeeping retains an unacquired fork across extension reloads. */
44
+ export declare function hasPendingFork(branch: readonly BranchEntry[], sessionId: string, entryType: string): boolean;
33
45
  export declare function snapshotDataNewestFirst(branch: readonly BranchEntry[]): unknown[];
34
46
  export declare function latestSnapshotData(branch: readonly BranchEntry[]): unknown;
35
47
  export declare function hasPriorConversation(branch: readonly BranchEntry[]): boolean;
@@ -1,4 +1,4 @@
1
- import { parseRetainedPiCheckpoint } from "./snapshot.js";
1
+ import { parseRetainedPiCheckpoint, readCheckpointMode } from "./snapshot.js";
2
2
  export const SNAPSHOT_ENTRY_TYPE = "state-flow-snapshot";
3
3
  /** Enumerate active-branch snapshots newest-first while containing hostile entries. */
4
4
  export function discoverSnapshotData(branch) {
@@ -16,6 +16,65 @@ export function discoverSnapshotData(branch) {
16
16
  }
17
17
  return { candidates, errors };
18
18
  }
19
+ /** Read native mode policy only; semantic checkpoint validity remains the recovery owner's concern. */
20
+ export function findBranchPolicy(branch, sessionId, stopEntryType, inactiveMode) {
21
+ let stopReset = false;
22
+ for (let index = branch.length - 1; index >= 0; index--) {
23
+ try {
24
+ const entry = branch[index];
25
+ if (entry?.type !== "custom")
26
+ continue;
27
+ if (entry.customType === SNAPSHOT_ENTRY_TYPE) {
28
+ const mode = readCheckpointMode(entry.data, inactiveMode);
29
+ if (mode !== undefined)
30
+ return { mode };
31
+ const data = entry.data;
32
+ if (data?.disabled === true && !Object.hasOwn(data, "mode") && !Object.hasOwn(data, "enabled"))
33
+ return { mode: inactiveMode };
34
+ }
35
+ else if (stopEntryType !== undefined && entry.customType === stopEntryType) {
36
+ const data = entry.data;
37
+ if (!data || stopReset || (sessionId === undefined ? typeof data.owner !== "string" || !data.owner.trim() : data.owner !== sessionId))
38
+ continue;
39
+ if (data.reset === true) {
40
+ stopReset = true;
41
+ continue;
42
+ }
43
+ if (Number.isSafeInteger(data.at) && data.at >= 0 && data.memoryDeferred === true && data.mode === "off") {
44
+ return { mode: "off", ...(typeof data.persistenceError === "string" && data.persistenceError.trim() ? { persistenceError: data.persistenceError } : {}) };
45
+ }
46
+ if (Number.isSafeInteger(data.at) && data.at >= 0 && typeof data.persistenceError === "string" && data.persistenceError.trim()) {
47
+ return { mode: data.mode === "off" || data.mode === "passive" ? data.mode : inactiveMode, persistenceError: data.persistenceError };
48
+ }
49
+ }
50
+ }
51
+ catch {
52
+ // Malformed native policy cannot prevent another explicit retained choice from being read.
53
+ }
54
+ }
55
+ return undefined;
56
+ }
57
+ /** Native policy bookkeeping retains an unacquired fork across extension reloads. */
58
+ export function hasPendingFork(branch, sessionId, entryType) {
59
+ for (let index = branch.length - 1; index >= 0; index--) {
60
+ try {
61
+ const entry = branch[index];
62
+ if (entry?.type !== "custom" || entry.customType !== entryType)
63
+ continue;
64
+ const data = entry.data;
65
+ if (data?.owner !== sessionId)
66
+ continue;
67
+ if (data.reset === true)
68
+ return false;
69
+ if (data.forkPending === true)
70
+ return true;
71
+ }
72
+ catch {
73
+ // Unrelated native entries grant no fork authority.
74
+ }
75
+ }
76
+ return false;
77
+ }
19
78
  export function snapshotDataNewestFirst(branch) {
20
79
  return discoverSnapshotData(branch).candidates;
21
80
  }
@@ -92,7 +151,7 @@ export function findPassiveStopBoundary(branch, sessionId, entryType) {
92
151
  }
93
152
  if (entry.customType !== entryType)
94
153
  continue;
95
- const { at, from, reset, owner, preserveContext, persistenceError } = entry.data ?? {};
154
+ const { at, from, reset, owner, preserveContext, persistenceError, mode } = entry.data ?? {};
96
155
  if (reset === true && owner === sessionId)
97
156
  return undefined;
98
157
  if (owner !== undefined && owner !== sessionId)
@@ -103,6 +162,7 @@ export function findPassiveStopBoundary(branch, sessionId, entryType) {
103
162
  ...(typeof from === "number" && Number.isSafeInteger(from) && from >= 0 ? { from } : {}),
104
163
  ...(preserveContext === true ? { preserveContext: true } : {}),
105
164
  ...(!checkpointSeen && owner === sessionId && typeof persistenceError === "string" && persistenceError.trim().length > 0 ? { persistenceError } : {}),
165
+ ...(mode === "passive" || mode === "off" ? { mode } : {}),
106
166
  };
107
167
  }
108
168
  catch {