@llblab/pi-kit 0.24.0 → 0.25.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 (112) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +10 -0
  3. package/README.md +6 -5
  4. package/node_modules/@llblab/pi-claude-usage/AGENTS.md +20 -0
  5. package/node_modules/@llblab/pi-claude-usage/BACKLOG.md +3 -0
  6. package/node_modules/@llblab/pi-claude-usage/CHANGELOG.md +13 -0
  7. package/node_modules/@llblab/pi-claude-usage/LICENSE +22 -0
  8. package/node_modules/@llblab/pi-claude-usage/README.md +110 -0
  9. package/node_modules/@llblab/pi-claude-usage/banner.jpg +0 -0
  10. package/node_modules/@llblab/pi-claude-usage/index.ts +1159 -0
  11. package/node_modules/@llblab/pi-claude-usage/package.json +60 -0
  12. package/node_modules/@llblab/pi-state-flow/AGENTS.md +42 -56
  13. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +16 -3
  14. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +19 -0
  15. package/node_modules/@llblab/pi-state-flow/README.md +15 -12
  16. package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +2 -2
  17. package/node_modules/@llblab/pi-state-flow/dist/index.js +2 -2
  18. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +7 -3
  19. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +16 -7
  20. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +9 -9
  21. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +5 -4
  22. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +2 -2
  23. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +1 -1
  24. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +7 -4
  25. package/node_modules/@llblab/pi-state-flow/dist/lib/episode.d.ts +3 -3
  26. package/node_modules/@llblab/pi-state-flow/dist/lib/episode.js +5 -5
  27. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +3 -5
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +275 -199
  29. package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +11 -4
  30. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +6 -7
  31. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +4 -1
  32. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +1 -0
  33. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +4 -5
  34. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +13 -13
  35. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +7 -6
  36. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +9 -9
  37. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +3 -2
  38. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +17 -12
  39. package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +4 -1
  40. package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +2 -1
  41. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +17 -8
  42. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +49 -20
  43. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +22 -3
  44. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +30 -10
  45. package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +5 -3
  46. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +19 -28
  47. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +17 -15
  48. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +52 -52
  49. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +8 -4
  50. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +34 -18
  51. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +5 -5
  52. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +13 -19
  53. package/node_modules/@llblab/pi-state-flow/dist/package.json +3 -3
  54. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +2 -2
  55. package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -1
  56. package/node_modules/@llblab/pi-state-flow/docs/agent-contract-relocation.md +72 -0
  57. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +36 -32
  58. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +12 -4
  59. package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +5 -5
  60. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +6 -6
  61. package/node_modules/@llblab/pi-state-flow/docs/performance.md +1 -1
  62. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +13 -12
  63. package/node_modules/@llblab/pi-state-flow/docs/usage.md +32 -29
  64. package/node_modules/@llblab/pi-state-flow/index.ts +3 -2
  65. package/node_modules/@llblab/pi-state-flow/lib/config.ts +20 -10
  66. package/node_modules/@llblab/pi-state-flow/lib/context.ts +15 -14
  67. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +1 -1
  68. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +8 -6
  69. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +6 -6
  70. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +274 -197
  71. package/node_modules/@llblab/pi-state-flow/lib/history.ts +16 -11
  72. package/node_modules/@llblab/pi-state-flow/lib/logging.ts +5 -1
  73. package/node_modules/@llblab/pi-state-flow/lib/query.ts +16 -16
  74. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +11 -11
  75. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +19 -13
  76. package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -3
  77. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +55 -22
  78. package/node_modules/@llblab/pi-state-flow/lib/state.ts +46 -13
  79. package/node_modules/@llblab/pi-state-flow/lib/status.ts +23 -32
  80. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +66 -65
  81. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +39 -19
  82. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +19 -27
  83. package/node_modules/@llblab/pi-state-flow/package.json +3 -3
  84. package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +2 -2
  85. package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
  86. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +5 -0
  87. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +2 -0
  88. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +55 -2
  89. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +21 -0
  90. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +144 -1
  91. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +9 -0
  92. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +19 -0
  93. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +13 -0
  94. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +29 -6
  95. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +16 -0
  96. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +5 -1
  97. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +6 -2
  98. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +7 -0
  99. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +13 -5
  100. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  101. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +2 -2
  102. package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
  103. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +79 -1
  104. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +197 -0
  105. package/node_modules/@llblab/pi-telegram/lib/bus.ts +33 -0
  106. package/node_modules/@llblab/pi-telegram/lib/commands.ts +38 -6
  107. package/node_modules/@llblab/pi-telegram/lib/extension.ts +15 -0
  108. package/node_modules/@llblab/pi-telegram/lib/locks.ts +6 -1
  109. package/node_modules/@llblab/pi-telegram/lib/polling.ts +10 -2
  110. package/node_modules/@llblab/pi-telegram/lib/threads.ts +19 -5
  111. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  112. package/package.json +7 -3
@@ -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 } 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,10 @@ 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;
26
29
  }
27
30
  export interface SnapshotDiscovery {
28
31
  candidates: unknown[];
@@ -92,7 +92,7 @@ export function findPassiveStopBoundary(branch, sessionId, entryType) {
92
92
  }
93
93
  if (entry.customType !== entryType)
94
94
  continue;
95
- const { at, from, reset, owner, preserveContext, persistenceError } = entry.data ?? {};
95
+ const { at, from, reset, owner, preserveContext, persistenceError, mode } = entry.data ?? {};
96
96
  if (reset === true && owner === sessionId)
97
97
  return undefined;
98
98
  if (owner !== undefined && owner !== sessionId)
@@ -103,6 +103,7 @@ export function findPassiveStopBoundary(branch, sessionId, entryType) {
103
103
  ...(typeof from === "number" && Number.isSafeInteger(from) && from >= 0 ? { from } : {}),
104
104
  ...(preserveContext === true ? { preserveContext: true } : {}),
105
105
  ...(!checkpointSeen && owner === sessionId && typeof persistenceError === "string" && persistenceError.trim().length > 0 ? { persistenceError } : {}),
106
+ ...(mode === "passive" || mode === "off" ? { mode } : {}),
106
107
  };
107
108
  }
108
109
  catch {
@@ -7,8 +7,12 @@ export declare class RevisionUnavailableError extends Error {
7
7
  /** Expired history cannot be restored, but explicit activation may use validated current memory. */
8
8
  export declare class HistoryBoundaryExpiredError extends RevisionUnavailableError {
9
9
  }
10
+ export type StateFlowMode = "active" | "passive" | "off";
11
+ export type InactiveMode = Exclude<StateFlowMode, "active">;
12
+ export declare function isStateFlowMode(value: unknown): value is StateFlowMode;
13
+ /** The session's selected mode is the only serialized behavior switch. */
10
14
  export interface SnapshotConfig {
11
- enabled: boolean;
15
+ mode: StateFlowMode;
12
16
  }
13
17
  interface LegacyValidationFeedback {
14
18
  attempt: number;
@@ -52,23 +56,28 @@ export declare function serializeSessionRuntime(runtime: SessionRuntime, cwd: st
52
56
  config: string;
53
57
  runtime: string;
54
58
  };
59
+ /** Legacy `enabled:false` decodes as non-active; native checkpoints, not this file, select branch policy. */
55
60
  export declare function parseSessionRuntime(config: string | undefined, runtimeSource: string | undefined, cwd: string, sessionId: string): SessionRuntime | undefined;
56
- export declare function emptySnapshot(enabled?: boolean): Snapshot;
61
+ export declare function emptySnapshot(mode?: StateFlowMode): Snapshot;
57
62
  export type RetainedBoundaryCheckpoint = {
58
63
  boundary: string;
59
- enabled: boolean;
64
+ mode: StateFlowMode;
60
65
  step: number;
61
66
  bootstrap?: true;
62
67
  specification?: string;
63
68
  };
64
- export type RetainedPiCheckpoint = RetainedBoundaryCheckpoint | {
65
- disabled: true;
69
+ /** A proven pre-runtime branch retains only its explicit inactive choice, never semantic storage. */
70
+ export type PreRuntimeCheckpoint = {
71
+ mode: InactiveMode;
66
72
  };
73
+ export type RetainedPiCheckpoint = RetainedBoundaryCheckpoint | PreRuntimeCheckpoint;
67
74
  export type FileRevision = `file:${string}`;
68
75
  export declare function isFileRevision(value: unknown): value is FileRevision;
69
76
  /** Encode branch lifecycle against one retained temporal identity without semantic or backup data. */
70
77
  export declare function retainedBoundaryCheckpoint(snapshot: Snapshot, boundary: string): RetainedBoundaryCheckpoint;
71
- /** Decode the 0.17 retained-window checkpoint contract. */
72
- export declare function parseRetainedPiCheckpoint(value: unknown): RetainedPiCheckpoint;
73
- export declare function migrationFailure(data: JsonObject, error: string): Snapshot;
78
+ /** Encode an explicit inactive choice on a branch that has no accepted runtime. */
79
+ export declare function preRuntimeCheckpoint(mode: StateFlowMode): PreRuntimeCheckpoint;
80
+ /** Decode the retained-window checkpoint contract; legacy `enabled`/`{disabled:true}` markers map through `inactiveMode`. */
81
+ export declare function parseRetainedPiCheckpoint(value: unknown, inactiveMode?: InactiveMode): RetainedPiCheckpoint;
82
+ export declare function migrationFailure(data: JsonObject, error: string, mode?: InactiveMode): Snapshot;
74
83
  export {};
@@ -11,6 +11,18 @@ export class RevisionUnavailableError extends Error {
11
11
  /** Expired history cannot be restored, but explicit activation may use validated current memory. */
12
12
  export class HistoryBoundaryExpiredError extends RevisionUnavailableError {
13
13
  }
14
+ export function isStateFlowMode(value) {
15
+ return value === "active" || value === "passive" || value === "off";
16
+ }
17
+ /**
18
+ * Read-only compatibility for session config and native checkpoints written before `mode`:
19
+ * `enabled:true` stays active; `enabled:false` stays the caller's inactive policy.
20
+ */
21
+ function decodeLegacyMode(value, inactiveMode) {
22
+ if (Object.hasOwn(value, "mode"))
23
+ return Object.hasOwn(value, "enabled") || !isStateFlowMode(value.mode) ? undefined : value.mode;
24
+ return typeof value.enabled === "boolean" ? value.enabled ? "active" : inactiveMode : undefined;
25
+ }
14
26
  export function validateSessionRuntime(value, cwd, sessionId) {
15
27
  if (!isJsonValue(value) || !isObject(value) || Object.keys(value).sort().join(",") !== "config,meta"
16
28
  || !isObject(value.config) || !isObject(value.meta))
@@ -31,7 +43,7 @@ export function validateSessionRuntime(value, cwd, sessionId) {
31
43
  }
32
44
  const runtimeFields = Object.fromEntries(Object.entries(fields).filter(([key]) => known.has(key)));
33
45
  const normalized = {
34
- config: { enabled: value.config.enabled === true },
46
+ config: { mode: isStateFlowMode(value.config.mode) ? value.config.mode : "off" },
35
47
  meta: restoredMeta(runtimeFields),
36
48
  };
37
49
  if (runtimeFields.bootstrap === false)
@@ -61,6 +73,7 @@ export function serializeSessionRuntime(runtime, cwd, sessionId) {
61
73
  const { temporal: _retiredTemporal, artifacts: _legacyArtifacts, ...runtimeMeta } = runtime.meta;
62
74
  return { config: `${canonicalJson(runtime.config)}\n`, runtime: `${canonicalJson(runtimeMeta)}\n` };
63
75
  }
76
+ /** Legacy `enabled:false` decodes as non-active; native checkpoints, not this file, select branch policy. */
64
77
  export function parseSessionRuntime(config, runtimeSource, cwd, sessionId) {
65
78
  if (config === undefined && runtimeSource === undefined)
66
79
  return undefined;
@@ -68,14 +81,20 @@ export function parseSessionRuntime(config, runtimeSource, cwd, sessionId) {
68
81
  throw new Error("Incomplete State Flow config/runtime pair");
69
82
  let runtime;
70
83
  try {
71
- runtime = { config: JSON.parse(config), meta: JSON.parse(runtimeSource) };
84
+ const settings = JSON.parse(config);
85
+ const mode = isObject(settings) && Object.keys(settings).length === 1 ? decodeLegacyMode(settings, "passive") : undefined;
86
+ if (mode === undefined)
87
+ throw new Error("Invalid State Flow runtime configuration or counters");
88
+ runtime = { config: { mode }, meta: JSON.parse(runtimeSource) };
72
89
  }
73
- catch {
74
- throw new Error("State Flow session runtime contains invalid JSON");
90
+ catch (error) {
91
+ if (error instanceof SyntaxError)
92
+ throw new Error("State Flow session runtime contains invalid JSON");
93
+ throw error;
75
94
  }
76
95
  validateSessionRuntime(runtime, cwd, sessionId);
77
96
  const { temporal: _legacyTemporal, ...runtimeMeta } = runtime.meta;
78
- return { config: { enabled: runtime.config.enabled }, meta: runtimeMeta };
97
+ return { config: { mode: runtime.config.mode }, meta: runtimeMeta };
79
98
  }
80
99
  function restoredStep(value) {
81
100
  return typeof value === "number"
@@ -109,11 +128,11 @@ function restoredMeta(value, legacy = {}) {
109
128
  ...(meta.bootstrap === true ? { bootstrap: true } : {}),
110
129
  };
111
130
  }
112
- function envelope(enabled, meta) {
113
- return { config: { enabled }, meta };
131
+ function envelope(mode, meta) {
132
+ return { config: { mode }, meta };
114
133
  }
115
- export function emptySnapshot(enabled = false) {
116
- return envelope(enabled, { step: 0 });
134
+ export function emptySnapshot(mode = "passive") {
135
+ return envelope(mode, { step: 0 });
117
136
  }
118
137
  export function isFileRevision(value) {
119
138
  return typeof value === "string" && /^file:[0-9a-f]{64}$/.test(value);
@@ -124,23 +143,33 @@ export function retainedBoundaryCheckpoint(snapshot, boundary) {
124
143
  throw new Error("Checkpoint requires a retained temporal boundary identity");
125
144
  const checkpoint = {
126
145
  boundary,
127
- enabled: snapshot.config.enabled,
146
+ mode: snapshot.config.mode,
128
147
  step: snapshot.meta.step,
129
148
  ...(snapshot.meta.bootstrap === true ? { bootstrap: true } : {}),
130
149
  ...(snapshot.meta.specification === undefined ? {} : { specification: snapshot.meta.specification }),
131
150
  };
132
151
  return parseRetainedPiCheckpoint(checkpoint);
133
152
  }
134
- /** Decode the 0.17 retained-window checkpoint contract. */
135
- export function parseRetainedPiCheckpoint(value) {
153
+ /** Encode an explicit inactive choice on a branch that has no accepted runtime. */
154
+ export function preRuntimeCheckpoint(mode) {
155
+ if (mode === "active")
156
+ throw new Error("An active State Flow branch requires a retained semantic boundary");
157
+ return { mode };
158
+ }
159
+ /** Decode the retained-window checkpoint contract; legacy `enabled`/`{disabled:true}` markers map through `inactiveMode`. */
160
+ export function parseRetainedPiCheckpoint(value, inactiveMode = "passive") {
136
161
  if (!isObject(value))
137
162
  throw new Error("Invalid State Flow retained-boundary checkpoint");
138
- if (Object.keys(value).length === 1 && value.disabled === true)
139
- return { disabled: true };
140
- const allowed = new Set(["boundary", "enabled", "step", "bootstrap", "specification"]);
141
- if (Object.keys(value).some((key) => !allowed.has(key))
163
+ const keys = Object.keys(value);
164
+ if (keys.length === 1 && value.disabled === true)
165
+ return { mode: inactiveMode };
166
+ if (keys.length === 1 && (value.mode === "passive" || value.mode === "off"))
167
+ return { mode: value.mode };
168
+ const allowed = new Set(["boundary", "mode", "enabled", "step", "bootstrap", "specification"]);
169
+ const mode = decodeLegacyMode(value, inactiveMode);
170
+ if (keys.some((key) => !allowed.has(key))
142
171
  || typeof value.boundary !== "string" || value.boundary.trim().length === 0
143
- || typeof value.enabled !== "boolean"
172
+ || mode === undefined
144
173
  || !Number.isSafeInteger(value.step) || value.step < 0 || value.step > MAX_RESTORED_STEP
145
174
  || (value.bootstrap !== undefined && value.bootstrap !== true)
146
175
  || (value.specification !== undefined && typeof value.specification !== "string")) {
@@ -148,18 +177,18 @@ export function parseRetainedPiCheckpoint(value) {
148
177
  }
149
178
  return {
150
179
  boundary: value.boundary,
151
- enabled: value.enabled,
180
+ mode,
152
181
  step: value.step,
153
182
  ...(value.bootstrap === true ? { bootstrap: true } : {}),
154
183
  ...(typeof value.specification === "string" ? { specification: value.specification } : {}),
155
184
  };
156
185
  }
157
- export function migrationFailure(data, error) {
186
+ export function migrationFailure(data, error, mode = "passive") {
158
187
  const meta = restoredMeta(data.meta, data);
159
188
  meta.validation = {
160
189
  attempt: 0,
161
190
  error,
162
191
  instruction: "Start a fresh State Flow episode; null is reserved for patch deletion.",
163
192
  };
164
- return envelope(false, meta);
193
+ return envelope(mode, meta);
165
194
  }