@llblab/pi-kit 0.18.2 → 0.19.1

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 (113) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +3 -1
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +35 -37
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +5 -3
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +23 -1
  6. package/node_modules/@llblab/pi-state-flow/README.md +102 -48
  7. package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +6 -9
  8. package/node_modules/@llblab/pi-state-flow/dist/index.js +6 -9
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +2 -2
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +21 -8
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +40 -24
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +93 -63
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +4 -3
  14. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +9 -9
  15. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +1 -1
  16. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +6 -5
  17. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -7
  18. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +56 -58
  19. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +12 -24
  20. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +7 -18
  21. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +47 -78
  22. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +4 -6
  23. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +241 -437
  24. package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -72
  25. package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +120 -499
  26. package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +2 -1
  27. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +4 -3
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +3 -0
  29. package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +43 -22
  30. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +1 -5
  31. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +0 -2
  32. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +1 -14
  33. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +5 -37
  34. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +1 -2
  35. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +13 -32
  36. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +6 -6
  37. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +25 -21
  38. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +3 -3
  39. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +15 -12
  40. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +2 -4
  41. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +5 -1
  42. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +26 -88
  43. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +160 -255
  44. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +0 -3
  45. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +1 -47
  46. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +16 -33
  47. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +48 -137
  48. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +16 -11
  49. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +13 -14
  50. package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -9
  51. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +15 -43
  52. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +5 -9
  53. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +13 -45
  54. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +1 -1
  55. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +15 -7
  56. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +104 -24
  57. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +0 -2
  58. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +11 -14
  59. package/node_modules/@llblab/pi-state-flow/dist/package.json +9 -6
  60. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +11 -17
  61. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +8 -8
  62. package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -2
  63. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +64 -67
  64. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +24 -6
  65. package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +12 -15
  66. package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +22 -27
  67. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +25 -41
  68. package/node_modules/@llblab/pi-state-flow/docs/performance.md +38 -421
  69. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +33 -46
  70. package/node_modules/@llblab/pi-state-flow/docs/usage.md +37 -61
  71. package/node_modules/@llblab/pi-state-flow/index.ts +7 -71
  72. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +26 -11
  73. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +116 -88
  74. package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +12 -10
  75. package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -6
  76. package/node_modules/@llblab/pi-state-flow/lib/context.ts +52 -59
  77. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +11 -20
  78. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +44 -86
  79. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +237 -460
  80. package/node_modules/@llblab/pi-state-flow/lib/git.ts +110 -552
  81. package/node_modules/@llblab/pi-state-flow/lib/history.ts +4 -3
  82. package/node_modules/@llblab/pi-state-flow/lib/json.ts +39 -23
  83. package/node_modules/@llblab/pi-state-flow/lib/logging.ts +1 -7
  84. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +5 -44
  85. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +14 -25
  86. package/node_modules/@llblab/pi-state-flow/lib/query.ts +26 -22
  87. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +17 -11
  88. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +7 -5
  89. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +155 -254
  90. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +1 -49
  91. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +58 -142
  92. package/node_modules/@llblab/pi-state-flow/lib/state.ts +27 -19
  93. package/node_modules/@llblab/pi-state-flow/lib/status.ts +16 -54
  94. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +12 -40
  95. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +1 -1
  96. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +104 -22
  97. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +16 -26
  98. package/node_modules/@llblab/pi-state-flow/package.json +9 -6
  99. package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +11 -17
  100. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +8 -8
  101. package/package.json +2 -2
  102. package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.d.ts +0 -21
  103. package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.js +0 -125
  104. package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.d.ts +0 -36
  105. package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.js +0 -98
  106. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +0 -13
  107. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +0 -167
  108. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +0 -86
  109. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +0 -437
  110. package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +0 -133
  111. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +0 -147
  112. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +0 -171
  113. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +0 -458
@@ -3,6 +3,7 @@ import { lstatSync, readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
5
5
  import { getDurableRepositoryRoot } from "./durable.js";
6
+ import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT } from "./history.js";
6
7
  import { isObject } from "./json.js";
7
8
  /** Read the repository-global config once at extension load/reload. Missing config uses defaults; invalid config never falls back. */
8
9
  export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = getDurableRepositoryRoot(agentDir)) {
@@ -15,6 +16,7 @@ export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = g
15
16
  passiveTools: true,
16
17
  logging: false,
17
18
  showSuccessfulPatches: true,
19
+ historyLimit: DEFAULT_HISTORY_LIMIT,
18
20
  };
19
21
  let value;
20
22
  try {
@@ -25,7 +27,7 @@ export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = g
25
27
  catch (error) {
26
28
  throw new Error(`Cannot read State Flow configuration: ${path}`, { cause: error });
27
29
  }
28
- const allowed = new Set(["autoStart", "passiveBootstrap", "passiveTools", "logging", "showSuccessfulPatches", "remotePublication"]);
30
+ const allowed = new Set(["autoStart", "passiveBootstrap", "passiveTools", "logging", "showSuccessfulPatches", "historyLimit"]);
29
31
  if (!isObject(value) || Object.keys(value).some((key) => !allowed.has(key))) {
30
32
  throw new Error(`State Flow configuration contains unknown settings: ${path}`);
31
33
  }
@@ -39,9 +41,8 @@ export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = g
39
41
  throw new Error(`State Flow logging must be a boolean: ${path}`);
40
42
  if (Object.hasOwn(value, "showSuccessfulPatches") && typeof value.showSuccessfulPatches !== "boolean")
41
43
  throw new Error(`State Flow showSuccessfulPatches must be a boolean: ${path}`);
42
- if (Object.hasOwn(value, "remotePublication") && value.remotePublication !== "off" && value.remotePublication !== "turn-end" && value.remotePublication !== "transition") {
43
- throw new Error(`State Flow remotePublication must be off, turn-end, or transition: ${path}`);
44
- }
44
+ if (Object.hasOwn(value, "historyLimit") && (!Number.isSafeInteger(value.historyLimit) || value.historyLimit < 0 || value.historyLimit > MAX_HISTORY_LIMIT))
45
+ throw new Error(`State Flow historyLimit must be an integer from 0 to ${MAX_HISTORY_LIMIT}: ${path}`);
45
46
  return {
46
47
  directory,
47
48
  autoStart: value.autoStart === true,
@@ -49,6 +50,6 @@ export function loadStateFlowConfig(agentDir = getAgentDir(), repositoryRoot = g
49
50
  passiveTools: value.passiveTools !== false,
50
51
  logging: value.logging === true,
51
52
  showSuccessfulPatches: value.showSuccessfulPatches !== false,
52
- ...(value.remotePublication === undefined ? {} : { remotePublication: value.remotePublication }),
53
+ historyLimit: typeof value.historyLimit === "number" ? value.historyLimit : DEFAULT_HISTORY_LIMIT,
53
54
  };
54
55
  }
@@ -1,10 +1,11 @@
1
1
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
2
- import { type ArtifactInvalidationNotice } from "./artifact.ts";
2
+ import { type ArtifactInvalidationNotice, type ArtifactModelHints } from "./artifact.ts";
3
3
  import type { RecentTransitionWindow } from "./history.ts";
4
4
  import type { Snapshot } from "./snapshot.ts";
5
5
  import type { RehydrationPhase } from "./rehydration.ts";
6
- import { type MaterializedState } from "./state.ts";
7
- export declare const VALIDATION_MESSAGE_TYPE = "state-flow-validation";
6
+ import { type MaterializedState, type ModelState } from "./state.ts";
7
+ /** Refresh only our section; Pi owns system frames, tools and forced-prompt precedence. */
8
+ export declare function projectSystemProtocol(messages: AgentMessage[], protocol: string | undefined): AgentMessage[];
8
9
  type LazyValueKind = "array" | "boolean" | "null" | "number" | "object" | "string";
9
10
  /** Fixed-budget navigation only: never place lazy bodies or partial key catalogs in baseline context. */
10
11
  export declare function lazyNavigationHint(state: MaterializedState): {
@@ -19,12 +20,12 @@ export interface PassiveContinuation {
19
20
  handoff: AgentMessage;
20
21
  }
21
22
  export declare function syntheticUser(text: string): AgentMessage;
22
- export declare function withoutPrivateValidation(messages: AgentMessage[]): AgentMessage[];
23
- export declare function createPassiveContinuation(state: MaterializedState, startedAt?: number, activeRunStartedAt?: number): PassiveContinuation;
23
+ export declare function createPassiveContinuation(state: ModelState, startedAt?: number, activeRunStartedAt?: number): PassiveContinuation;
24
24
  /** Keep the interrupted run through later results; an idle stop retains only later conversation. */
25
25
  export declare function passiveContinuationMessages(messages: AgentMessage[], continuation: PassiveContinuation): AgentMessage[];
26
- export declare function runtimeContextMessage(snapshot: Snapshot, state: MaterializedState, recentTransitions?: RecentTransitionWindow, artifactInvalidations?: readonly ArtifactInvalidationNotice[], rehydrationPhase?: RehydrationPhase, resolutionPending?: boolean): AgentMessage;
27
- export declare function currentRunTrajectory(messages: AgentMessage[], specification: string, anchorTimestamp: number | undefined): {
26
+ export declare function runtimeContextMessage(snapshot: Snapshot, state: MaterializedState, recentTransitions?: RecentTransitionWindow, artifactInvalidations?: readonly ArtifactInvalidationNotice[], rehydrationPhase?: RehydrationPhase, artifactHints?: ArtifactModelHints): AgentMessage;
27
+ /** Captured identity survives text decoration; an uncertain boundary retains available context. */
28
+ export declare function currentRunTrajectory(messages: AgentMessage[], specification: string | undefined, anchorTimestamp: number | undefined): {
28
29
  messages: AgentMessage[];
29
30
  anchorTimestamp?: number;
30
31
  };
@@ -1,7 +1,34 @@
1
1
  import { projectArtifactForModel } from "./artifact.js";
2
- import { canonicalJson } from "./json.js";
2
+ import { isObject, presentationJson } from "./json.js";
3
3
  import { projectStateForModel } from "./state.js";
4
- export const VALIDATION_MESSAGE_TYPE = "state-flow-validation";
4
+ /** Refresh only our section; Pi owns system frames, tools and forced-prompt precedence. */
5
+ export function projectSystemProtocol(messages, protocol) {
6
+ let lastSystem = -1;
7
+ let current;
8
+ for (let index = 0; index < messages.length; index++) {
9
+ const message = messages[index];
10
+ if (message.role !== "system")
11
+ continue;
12
+ lastSystem = index;
13
+ if (message.sections && Object.hasOwn(message.sections, "state_flow"))
14
+ current = message.sections.state_flow ?? undefined;
15
+ }
16
+ const section = protocol === undefined ? undefined : `<state_flow>\n${protocol}\n</state_flow>`;
17
+ if (lastSystem < 0 || current === section)
18
+ return messages;
19
+ return messages.map((message, index) => {
20
+ if (message.role !== "system")
21
+ return message;
22
+ const ownsSection = message.sections !== undefined && Object.hasOwn(message.sections, "state_flow");
23
+ if (!ownsSection && (index !== lastSystem || section === undefined))
24
+ return message;
25
+ const sections = { ...message.sections };
26
+ delete sections.state_flow;
27
+ if (index === lastSystem && section !== undefined)
28
+ sections.state_flow = section;
29
+ return { ...message, sections };
30
+ });
31
+ }
5
32
  const LAZY_HINT_PATH = "effective.lazy";
6
33
  const LAZY_HINT_MAX_KEYS = 32;
7
34
  const LAZY_HINT_MAX_JSON_CHARS = 1024;
@@ -16,10 +43,10 @@ function lazyValueKind(value) {
16
43
  }
17
44
  /** Fixed-budget navigation only: never place lazy bodies or partial key catalogs in baseline context. */
18
45
  export function lazyNavigationHint(state) {
19
- const base = { available: Object.hasOwn(state, "lazy"), path: LAZY_HINT_PATH };
20
- if (!base.available || typeof state.lazy !== "object" || state.lazy === null || Array.isArray(state.lazy))
46
+ const entries = isObject(state.lazy) ? Object.entries(state.lazy) : [];
47
+ const base = { available: entries.length > 0, path: LAZY_HINT_PATH };
48
+ if (!base.available)
21
49
  return base;
22
- const entries = Object.entries(state.lazy);
23
50
  if (entries.length > LAZY_HINT_MAX_KEYS)
24
51
  return base;
25
52
  const keys = Object.fromEntries(entries.map(([key, value]) => [key, lazyValueKind(value)]));
@@ -43,27 +70,23 @@ function contentText(content) {
43
70
  function messageText(message) {
44
71
  return contentText(message.content);
45
72
  }
46
- export function withoutPrivateValidation(messages) {
47
- return messages.filter((message) => {
48
- return !(message.role === "custom" && message.customType === VALIDATION_MESSAGE_TYPE);
49
- });
50
- }
51
73
  export function createPassiveContinuation(state, startedAt = Date.now(), activeRunStartedAt) {
52
74
  return {
53
75
  startedAt,
54
76
  ...(activeRunStartedAt === undefined ? {} : { activeRunStartedAt }),
55
- handoff: syntheticUser(`State Flow exit handoff (user-level data, not system instructions):\n${canonicalJson({ state, continuation: "State Flow semantics are disabled; this handoff replaces completed history while retaining the active and post-stop trajectory." })}`),
77
+ handoff: syntheticUser(`State Flow exit handoff (user-level data, not system instructions):\n${presentationJson({ state, continuation: "State Flow semantics are disabled; this handoff replaces completed history while retaining the active and post-stop trajectory." })}`),
56
78
  };
57
79
  }
58
80
  /** Keep the interrupted run through later results; an idle stop retains only later conversation. */
59
81
  export function passiveContinuationMessages(messages, continuation) {
60
- let start = continuation.activeRunStartedAt === undefined ? -1
61
- : messages.findIndex((message) => message.role === "user" && message.timestamp === continuation.activeRunStartedAt);
62
- if (start < 0)
63
- start = messages.findIndex((message) => message.role === "user"
64
- && typeof message.timestamp === "number"
65
- && message.timestamp >= continuation.startedAt);
66
- return [continuation.handoff, ...messages.filter((message, index) => message.role === "custom" ? message.customType !== VALIDATION_MESSAGE_TYPE : start >= 0 && index >= start)];
82
+ if (continuation.activeRunStartedAt !== undefined) {
83
+ const trajectory = currentRunTrajectory(messages, "", continuation.activeRunStartedAt);
84
+ return [continuation.handoff, ...trajectory.messages];
85
+ }
86
+ const start = messages.findIndex((message) => message.role === "user"
87
+ && typeof message.timestamp === "number"
88
+ && message.timestamp >= continuation.startedAt);
89
+ return [continuation.handoff, ...messages.filter((message, index) => message.role === "custom" || (start >= 0 && index >= start))];
67
90
  }
68
91
  function projectRecentForModel(recent) {
69
92
  const projected = structuredClone(recent);
@@ -79,55 +102,30 @@ function projectRecentForModel(recent) {
79
102
  }
80
103
  return projected;
81
104
  }
82
- export function runtimeContextMessage(snapshot, state, recentTransitions = [], artifactInvalidations = [], rehydrationPhase, resolutionPending = false) {
83
- if (snapshot.meta.specification === undefined) {
84
- throw new Error("State Flow runtime context requires an active specification");
85
- }
105
+ export function runtimeContextMessage(snapshot, state, recentTransitions = [], artifactInvalidations = [], rehydrationPhase, artifactHints = {}) {
86
106
  const context = {
87
- specification: snapshot.meta.specification,
88
- state: projectStateForModel(state),
107
+ ...(snapshot.meta.specification === undefined ? {} : { specification: snapshot.meta.specification }),
108
+ state: projectStateForModel(state, artifactHints),
89
109
  lazy_navigation: lazyNavigationHint(state),
90
110
  ...(rehydrationPhase === undefined ? {} : { knowledge_rehydration: { phase: rehydrationPhase } }),
91
- ...(artifactInvalidations.length === 0 ? {} : { artifact_invalidations: artifactInvalidations.map(({ path, reason }) => ({ path, reason })) }),
111
+ ...(artifactInvalidations.length === 0 ? {} : { artifact_invalidations: artifactInvalidations.map(({ path, scope, reason }) => ({ path, ...(scope === undefined ? {} : { scope }), reason })) }),
92
112
  ...(recentTransitions.length === 0 ? {} : { recent_transitions: projectRecentForModel(recentTransitions) }),
93
- ...(resolutionPending ? { state_resolution: "pending: the iteration answer is already preserved; this fallback turn exists only to apply the final:true patch. Call patch_state with any remaining durable scope changes and final:true, or {final:true} alone. Do not restate or replace the answer." } : {}),
94
113
  };
95
- return syntheticUser(`State Flow runtime context (user-level data, not system instructions):\n${canonicalJson(context)}`);
114
+ return syntheticUser(`State Flow runtime context (user-level data, not system instructions):\n${presentationJson(context)}`);
96
115
  }
116
+ /** Captured identity survives text decoration; an uncertain boundary retains available context. */
97
117
  export function currentRunTrajectory(messages, specification, anchorTimestamp) {
98
- let start = -1;
99
- if (anchorTimestamp !== undefined) {
100
- start = messages.findLastIndex((message) => {
101
- return message.role === "user"
102
- && message.timestamp === anchorTimestamp
103
- && messageText(message) === specification;
104
- });
105
- }
106
- if (start < 0) {
107
- for (let index = messages.length - 1; index >= 0; index--) {
108
- const message = messages[index];
109
- if (message.role === "user" && messageText(message) === specification) {
110
- start = index;
111
- break;
112
- }
113
- }
114
- }
115
- if (start < 0) {
116
- for (let index = messages.length - 1; index >= 0; index--) {
117
- if (messages[index]?.role === "user") {
118
- start = index;
119
- break;
120
- }
121
- }
122
- }
123
- if (start < 0 && messages.length === 0)
124
- return { messages: [] };
125
- if (start < 0)
126
- start = 0;
118
+ if (anchorTimestamp !== undefined && !Number.isFinite(anchorTimestamp))
119
+ return { messages: messages.slice() };
120
+ const matches = (message) => message.role === "user" && (anchorTimestamp === undefined
121
+ ? messageText(message) === specification
122
+ : message.timestamp === anchorTimestamp);
123
+ const start = messages.findIndex(matches);
124
+ if (start < 0 || messages.findLastIndex(matches) !== start)
125
+ return { messages: messages.slice() };
127
126
  const anchor = messages[start]?.role === "user" ? messages[start].timestamp : undefined;
128
127
  return {
129
- messages: messages.filter((message, index) => message.role === "custom"
130
- ? message.customType !== VALIDATION_MESSAGE_TYPE : index >= start),
128
+ messages: messages.filter((message, index) => message.role === "custom" || index >= start),
131
129
  ...(typeof anchor === "number" ? { anchorTimestamp: anchor } : {}),
132
130
  };
133
131
  }
@@ -1,10 +1,9 @@
1
- import { execFileSync } from "node:child_process";
2
1
  import { closeSync, constants, existsSync, fstatSync, lstatSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
3
- import { join, relative, resolve } from "node:path";
2
+ import { join, resolve } from "node:path";
4
3
  import { loadScopeStream, resolveSessionAddress, sessionRuntimePaths } from "./durable.js";
5
- import { loadTemporalRevision } from "./git.js";
4
+ import { MAX_HISTORY_LIMIT } from "./history.js";
6
5
  import { parseSessionRuntime } from "./snapshot.js";
7
- import { validateTemporalState } from "./temporal.js";
6
+ import { validateScopeLineage } from "./temporal.js";
8
7
  /**
9
8
  * Preserve host-owned explicit session intent and consult State Flow only for
10
9
  * an ordinary default launch. The recommender is a read-only advisory port;
@@ -143,30 +142,19 @@ export function inspectStateFlowContinuationProvenance(header, repositoryRoot) {
143
142
  if (config === undefined && runtimeSource === undefined && meta === undefined) {
144
143
  return { stateFlow: { enabled: false, restorable: true }, reason: "no State Flow session runtime" };
145
144
  }
146
- const runtime = parseSessionRuntime(config, runtimeSource, header.cwd, header.id, meta);
145
+ const runtime = parseSessionRuntime(config, runtimeSource, header.cwd, header.id);
147
146
  if (!runtime)
148
147
  return { stateFlow: { enabled: false, restorable: true }, reason: "no State Flow session runtime" };
149
148
  if (!runtime.config.enabled)
150
149
  return { stateFlow: { enabled: false, restorable: true }, reason: "State Flow stopped on selected runtime" };
151
- if (runtime.meta.publication === "files") {
152
- const global = loadScopeStream(header.cwd, header.id, "global", repositoryRoot, sessionKey);
153
- const cwd = loadScopeStream(header.cwd, header.id, "cwd", repositoryRoot, sessionKey);
154
- const session = loadScopeStream(header.cwd, header.id, "session", repositoryRoot, sessionKey);
155
- if (!global || !cwd || !session)
156
- throw new Error("incomplete file-only temporal cohort");
157
- validateTemporalState({ lineage: runtime.meta.lineage, scopes: { global, cwd, session } });
158
- return { stateFlow: { enabled: true, restorable: true }, reason: "exact file-only runtime cohort is restorable" };
159
- }
160
- const runtimePath = relative(resolve(repositoryRoot), runtimeSource === undefined ? paths.meta : paths.runtime);
161
- if (runtimePath.startsWith(".."))
162
- throw new Error("runtime provenance escapes repository");
163
- const owner = execFileSync("git", ["-C", repositoryRoot, "log", "-1", "--format=%H", "--", runtimePath], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
164
- if (!owner)
165
- throw new Error("runtime owner commit is unavailable");
166
- const restored = loadTemporalRevision(header.cwd, header.id, repositoryRoot, owner, sessionKey);
167
- if (!restored.runtime || !restored.runtime.document.config.enabled)
168
- throw new Error("selected Git runtime is not enabled");
169
- return { stateFlow: { enabled: true, restorable: true }, reason: `exact Git runtime ${owner.slice(0, 12)} is restorable` };
150
+ const global = loadScopeStream(header.cwd, header.id, "global", repositoryRoot, sessionKey);
151
+ const cwd = loadScopeStream(header.cwd, header.id, "cwd", repositoryRoot, sessionKey);
152
+ const session = loadScopeStream(header.cwd, header.id, "session", repositoryRoot, sessionKey);
153
+ if (!global || !cwd || !session)
154
+ throw new Error("incomplete canonical temporal cohort");
155
+ // Shared streams are current, independently validated by their codecs, not frozen to this session's history.
156
+ validateScopeLineage(session, "session", runtime.meta.lineage, MAX_HISTORY_LIMIT);
157
+ return { stateFlow: { enabled: true, restorable: true }, reason: "canonical session lineage is valid beside current shared streams" };
170
158
  }
171
159
  catch (error) {
172
160
  return { stateFlow: { enabled: true, restorable: false }, reason: `State Flow runtime is ineligible: ${error instanceof Error ? error.message : String(error)}` };
@@ -1,12 +1,14 @@
1
1
  import { type ArtifactProvenanceRegistry } from "./artifact.ts";
2
2
  import { type ScopeStream, type TemporalState } from "./temporal.ts";
3
- import { type StateScope } from "./state.ts";
3
+ import type { StateScope } from "./state.ts";
4
4
  /** Canonical semantic sources plus runtime-owned temporal metadata. */
5
5
  export interface ScopeStreamSources {
6
6
  checkpoint: string;
7
7
  patches: string;
8
8
  temporal: ScopeTemporalMetadata;
9
9
  }
10
+ /** Read-only activation eligibility for one canonical CWD scope. */
11
+ export declare function hasCwdMaterialization(cwd: string, repositoryRoot: string): boolean;
10
12
  export interface ScopeTemporalMetadata {
11
13
  checkpoint: ScopeStream["checkpoint"]["through"];
12
14
  patches: ScopeStream["patches"][number]["transition"][];
@@ -42,15 +44,10 @@ export declare function sessionRuntimePaths(cwd: string, sessionId: string, repo
42
44
  };
43
45
  /** Unsupported state.json presence never becomes an anchored checkpoint. */
44
46
  export declare function loadScopeStream(cwd: string, sessionId: string, scope: StateScope, repositoryRoot: string, sessionKey?: string): ScopeStream | undefined;
45
- /** Include legacy names in the CAS basis solely to prevent format races during cutover. */
47
+ /** Include unsupported predecessor names in the CAS basis so they cannot race canonical publication. */
46
48
  export declare function captureTemporalFileBases(cwd: string, sessionId: string, repositoryRoot: string, sessionKey?: string): DurableFileBase[];
47
49
  /** Select exact serialized scope updates from one validated active temporal cohort. */
48
50
  export declare function temporalStateFileUpdates(cwd: string, sessionId: string, view: TemporalState, scopes: readonly StateScope[], repositoryRoot: string, sessionKey?: string): OwnedFileUpdate[];
49
- export interface DurablePaths {
50
- repositoryRoot: string;
51
- globalState: string;
52
- globalPatches: string;
53
- }
54
51
  /** Merge authoritative owned leaves while preserving forward-compatible metadata siblings. */
55
52
  export declare function serializeScopeMetadata(registry: Readonly<ArtifactProvenanceRegistry> | undefined, stream: ScopeStream, scope: StateScope, cwdIdentity?: string, existingSource?: string): string;
56
53
  /** Compatibility serializer retained for metadata-only callers. */
@@ -59,20 +56,16 @@ export declare function serializeScopeProvenance(registry: Readonly<ArtifactProv
59
56
  export declare function parseScopeProvenance(source: string | undefined, path: string): ArtifactProvenanceRegistry;
60
57
  export interface ScopePaths {
61
58
  directory: string;
62
- state: string;
63
- patches: string;
64
59
  }
65
60
  export interface DurableFileBase {
66
61
  path: string;
67
62
  identity: "missing" | `sha256:${string}`;
68
63
  content?: string;
69
- /** Opaque originals for byte-exact rollback, including non-UTF-8 legacy journals. */
64
+ /** Opaque originals for byte-exact rollback. */
70
65
  bytes?: Uint8Array;
71
66
  }
72
67
  /** Dedicated runtime storage, independent from Markdown source discovery. */
73
68
  export declare function getDurableRepositoryRoot(agentDir?: string): string;
74
- /** Legacy snapshot paths retained only for one-way migration and exact ownership checks. */
75
- export declare function durablePaths(repositoryRoot?: string): DurablePaths;
76
69
  /** Match Pi's native project-session directory convention exactly. */
77
70
  export declare function cwdScopeKey(cwd: string): string;
78
71
  /** One safe directory segment, normally the native Pi session filename stem. */
@@ -82,13 +75,9 @@ export declare function sessionStorageKey(sessionFile: string | undefined, sessi
82
75
  export declare function resolveSessionAddress(sessionFile: string | undefined, sessionId: string, timestamp?: string): SessionAddress;
83
76
  export declare function cwdScopePaths(cwd: string, repositoryRoot?: string): ScopePaths;
84
77
  export declare function sessionScopePaths(cwd: string, sessionId: string, repositoryRoot?: string, sessionKey?: string): ScopePaths;
85
- export declare function cwdStatePath(cwd: string, repositoryRoot?: string): string;
86
- export declare function cwdPatchesPath(cwd: string, repositoryRoot?: string): string;
87
- export declare function sessionStatePath(cwd: string, sessionId: string, repositoryRoot?: string, sessionKey?: string): string;
88
- export declare function sessionPatchesPath(cwd: string, sessionId: string, repositoryRoot?: string, sessionKey?: string): string;
89
- /** Exact semantic file shapes, including legacy snapshots only during the storage cutover. */
78
+ /** Exact canonical semantic and runtime file shapes. */
90
79
  export declare function isStateFlowOwnedPath(candidate: string, repositoryRoot?: string): boolean;
91
- /** Capture exact owned bytes for one compare-and-swap publication or migration cohort. */
80
+ /** Capture exact owned bytes for one compare-and-swap publication cohort. */
92
81
  export declare function captureOwnedFileBases(paths: readonly string[], repositoryRoot: string): DurableFileBase[];
93
82
  export interface OwnedFileUpdate {
94
83
  path: string;
@@ -3,18 +3,31 @@ import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readFi
3
3
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
4
4
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
5
5
  import { parseArtifactProvenanceRegistry, serializeArtifactProvenanceRegistry, } from "./artifact.js";
6
+ import { MAX_HISTORY_LIMIT } from "./history.js";
6
7
  import { canonicalJson, isJsonValue, isObject } from "./json.js";
7
8
  import { validateScopeStream, validateTemporalState } from "./temporal.js";
8
- import { migratePreIntentState } from "./state.js";
9
9
  const SESSION_KEY_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
10
10
  const STATE_FILE = "state.json";
11
11
  const CHECKPOINT_FILE = "checkpoint.json";
12
12
  const PATCHES_FILE = "patches.jsonl";
13
13
  const META_FILE = "meta.json";
14
14
  const RUNTIME_FILE = "runtime.json";
15
+ /** Read-only activation eligibility for one canonical CWD scope. */
16
+ export function hasCwdMaterialization(cwd, repositoryRoot) {
17
+ const root = resolve(repositoryRoot);
18
+ const directory = cwdScopePaths(cwd, root).directory;
19
+ const [checkpoint, patches, meta] = captureOwnedFileBases([
20
+ join(directory, CHECKPOINT_FILE), join(directory, PATCHES_FILE), join(directory, META_FILE),
21
+ ], root);
22
+ if (checkpoint.content !== undefined)
23
+ return parseScopeStream(checkpoint.content, patches.content, "cwd", cwd, meta.content) !== undefined;
24
+ if (patches.content !== undefined)
25
+ throw new Error(`State Flow tail has no provable checkpoint: ${directory}`);
26
+ return false;
27
+ }
15
28
  /** Semantic files contain no runtime envelope; temporal boundaries and CWD ownership live in meta.json. */
16
29
  export function serializeScopeStream(stream, scope, cwdIdentity) {
17
- validateScopeStream(stream, scope);
30
+ validateScopeStream(stream, scope, MAX_HISTORY_LIMIT);
18
31
  if (scope === "cwd" && cwdIdentity === undefined)
19
32
  throw new Error("State Flow CWD scope serialization requires its canonical identity");
20
33
  if (scope !== "cwd" && cwdIdentity !== undefined)
@@ -30,6 +43,8 @@ export function serializeScopeStream(stream, scope, cwdIdentity) {
30
43
  }
31
44
  /** Distinguish a wholly absent semantic cohort from partial or malformed surviving authority. */
32
45
  export function classifyScopeStream(checkpointSource, patchesSource, scope, expectedCwd, metaSource) {
46
+ if (scope !== "global" && scope !== "cwd" && scope !== "session")
47
+ throw new Error("Unknown temporal scope");
33
48
  if (checkpointSource === undefined && patchesSource === undefined)
34
49
  return { kind: "absent" };
35
50
  if (checkpointSource === undefined || patchesSource === undefined) {
@@ -65,44 +80,22 @@ export function classifyScopeStream(checkpointSource, patchesSource, scope, expe
65
80
  }
66
81
  }
67
82
  const temporal = meta?.temporal;
68
- if (isObject(temporal) && Object.hasOwn(temporal, "checkpoint") && Array.isArray(temporal.patches)) {
69
- checkpoint = migratePreIntentState(checkpoint) ?? checkpoint;
70
- const owner = meta?.owner;
71
- if (scope === "cwd" && expectedCwd !== undefined
72
- && (!isObject(owner) || Object.keys(owner).join(",") !== "cwd" || owner.cwd !== resolve(expectedCwd))) {
73
- throw new Error(owner === undefined ? "State Flow CWD scope identity is missing" : "State Flow CWD scope identity mismatch");
74
- }
75
- const boundaries = temporal.patches;
76
- if (boundaries.length !== patches.length)
77
- throw new Error(`State Flow ${scope} temporal metadata does not match its semantic tail`);
78
- const stream = {
79
- checkpoint: { through: temporal.checkpoint, state: checkpoint },
80
- patches: patches.map((patch, index) => ({ transition: boundaries[index], patch })),
81
- };
82
- validateScopeStream(stream, scope);
83
- return { kind: "present", stream };
84
- }
85
- // Complete predecessor envelopes are the only no-meta form that may be unwrapped.
86
- let legacyCheckpoint = checkpoint;
87
- if (isObject(checkpoint) && Object.hasOwn(checkpoint, "owner")) {
88
- const { owner, ...semantic } = checkpoint;
89
- if (scope !== "cwd" || !isObject(owner) || Object.keys(owner).join(",") !== "cwd" || typeof owner.cwd !== "string") {
90
- throw new Error("Invalid State Flow CWD scope identity");
91
- }
92
- if (expectedCwd !== undefined && owner.cwd !== resolve(expectedCwd))
93
- throw new Error("State Flow CWD scope identity mismatch");
94
- legacyCheckpoint = semantic;
95
- }
96
- else if (scope === "cwd" && expectedCwd !== undefined) {
97
- throw new Error("State Flow CWD scope identity is missing");
98
- }
99
- if (isObject(legacyCheckpoint) && isObject(legacyCheckpoint.state)) {
100
- const migrated = migratePreIntentState(legacyCheckpoint.state);
101
- if (migrated)
102
- legacyCheckpoint = { ...legacyCheckpoint, state: migrated };
103
- }
104
- const stream = { checkpoint: legacyCheckpoint, patches };
105
- validateScopeStream(stream, scope);
83
+ if (!isObject(temporal) || !Object.hasOwn(temporal, "checkpoint") || !Array.isArray(temporal.patches)) {
84
+ throw new Error(`Unsupported State Flow ${scope} storage format`);
85
+ }
86
+ const owner = meta?.owner;
87
+ if (scope === "cwd" && expectedCwd !== undefined
88
+ && (!isObject(owner) || Object.keys(owner).join(",") !== "cwd" || owner.cwd !== resolve(expectedCwd))) {
89
+ throw new Error(owner === undefined ? "State Flow CWD scope identity is missing" : "State Flow CWD scope identity mismatch");
90
+ }
91
+ const boundaries = temporal.patches;
92
+ if (boundaries.length !== patches.length)
93
+ throw new Error(`State Flow ${scope} temporal metadata does not match its semantic tail`);
94
+ const stream = {
95
+ checkpoint: { through: temporal.checkpoint, state: checkpoint },
96
+ patches: patches.map((patch, index) => ({ transition: boundaries[index], patch })),
97
+ };
98
+ validateScopeStream(stream, scope, MAX_HISTORY_LIMIT);
106
99
  return { kind: "present", stream };
107
100
  }
108
101
  /** Decode the entire bounded replay input before accepting any materialized state. */
@@ -127,22 +120,22 @@ export function sessionRuntimePaths(cwd, sessionId, repositoryRoot, sessionKey =
127
120
  export function loadScopeStream(cwd, sessionId, scope, repositoryRoot, sessionKey = sessionId) {
128
121
  const paths = temporalScopePaths(cwd, sessionId, scope, repositoryRoot, sessionKey);
129
122
  if (readRegularBytes(join(paths.directory, STATE_FILE), repositoryRoot) !== undefined) {
130
- throw new Error(`Legacy State Flow storage requires explicit migration: ${paths.directory}`);
123
+ throw new Error(`Unsupported State Flow storage format: ${paths.directory}`);
131
124
  }
132
125
  return parseScopeStream(readRegularFile(paths.checkpoint, repositoryRoot), readRegularFile(paths.patches, repositoryRoot), scope, scope === "cwd" ? cwd : undefined, readRegularFile(paths.meta, repositoryRoot));
133
126
  }
134
- /** Include legacy names in the CAS basis solely to prevent format races during cutover. */
127
+ /** Include unsupported predecessor names in the CAS basis so they cannot race canonical publication. */
135
128
  export function captureTemporalFileBases(cwd, sessionId, repositoryRoot, sessionKey = sessionId) {
136
129
  const paths = ["global", "cwd", "session"].flatMap((scope) => {
137
130
  const pair = temporalScopePaths(cwd, sessionId, scope, repositoryRoot, sessionKey);
138
131
  const runtime = scope === "session" ? sessionRuntimePaths(cwd, sessionId, repositoryRoot, sessionKey) : undefined;
139
- return [pair.checkpoint, pair.patches, join(pair.directory, STATE_FILE), ...(runtime === undefined ? [pair.meta] : [runtime.config, runtime.runtime, runtime.meta])];
132
+ return [pair.checkpoint, pair.patches, ...(runtime === undefined ? [pair.meta] : [runtime.config, runtime.runtime, runtime.meta])];
140
133
  });
141
134
  return captureOwnedFileBases(paths, repositoryRoot);
142
135
  }
143
136
  /** Select exact serialized scope updates from one validated active temporal cohort. */
144
137
  export function temporalStateFileUpdates(cwd, sessionId, view, scopes, repositoryRoot, sessionKey = sessionId) {
145
- validateTemporalState(view);
138
+ validateTemporalState(view, MAX_HISTORY_LIMIT);
146
139
  const seen = new Set();
147
140
  return scopes.flatMap((scope) => {
148
141
  if (seen.has(scope))
@@ -150,7 +143,7 @@ export function temporalStateFileUpdates(cwd, sessionId, view, scopes, repositor
150
143
  seen.add(scope);
151
144
  const paths = temporalScopePaths(cwd, sessionId, scope, repositoryRoot, sessionKey);
152
145
  if (readRegularBytes(join(paths.directory, STATE_FILE), repositoryRoot) !== undefined) {
153
- throw new Error(`Legacy State Flow storage requires explicit migration: ${paths.directory}`);
146
+ throw new Error(`Unsupported State Flow storage format: ${paths.directory}`);
154
147
  }
155
148
  const sources = serializeScopeStream(view.scopes[scope], scope, scope === "cwd" ? cwd : undefined);
156
149
  return [{ path: paths.checkpoint, content: sources.checkpoint }, { path: paths.patches, content: sources.patches }];
@@ -160,7 +153,7 @@ export function temporalStateFileUpdates(cwd, sessionId, view, scopes, repositor
160
153
  export function serializeScopeMetadata(registry, stream, scope, cwdIdentity, existingSource) {
161
154
  const existing = parseMetadataDocument(existingSource, `State Flow metadata`);
162
155
  if (scope === "session") {
163
- for (const key of ["identity", "lineage", "step", "specification", "validation", "bootstrap", "remotePublication", "revision", "temporalRevision", "publication"])
156
+ for (const key of ["identity", "lineage", "step", "specification", "validation", "bootstrap"])
164
157
  delete existing[key];
165
158
  }
166
159
  const sources = serializeScopeStream(stream, scope, cwdIdentity);
@@ -206,15 +199,6 @@ export function parseScopeProvenance(source, path) {
206
199
  export function getDurableRepositoryRoot(agentDir = getAgentDir()) {
207
200
  return resolve(agentDir, "state-flow");
208
201
  }
209
- /** Legacy snapshot paths retained only for one-way migration and exact ownership checks. */
210
- export function durablePaths(repositoryRoot = getDurableRepositoryRoot()) {
211
- const root = resolve(repositoryRoot);
212
- return {
213
- repositoryRoot: root,
214
- globalState: join(root, STATE_FILE),
215
- globalPatches: join(root, PATCHES_FILE),
216
- };
217
- }
218
202
  /** Match Pi's native project-session directory convention exactly. */
219
203
  export function cwdScopeKey(cwd) {
220
204
  const canonical = resolve(cwd);
@@ -244,32 +228,17 @@ export function resolveSessionAddress(sessionFile, sessionId, timestamp) {
244
228
  return Object.freeze({ id: sessionId, key: sessionStorageKey(sessionFile, sessionId, timestamp) });
245
229
  }
246
230
  export function cwdScopePaths(cwd, repositoryRoot = getDurableRepositoryRoot()) {
247
- const directory = join(resolve(repositoryRoot), cwdScopeKey(cwd));
248
- return { directory, state: join(directory, STATE_FILE), patches: join(directory, PATCHES_FILE) };
231
+ return { directory: join(resolve(repositoryRoot), cwdScopeKey(cwd)) };
249
232
  }
250
233
  export function sessionScopePaths(cwd, sessionId, repositoryRoot = getDurableRepositoryRoot(), sessionKey = sessionId) {
251
- const directory = join(cwdScopePaths(cwd, repositoryRoot).directory, sessionScopeKey(sessionKey));
252
- return { directory, state: join(directory, STATE_FILE), patches: join(directory, PATCHES_FILE) };
253
- }
254
- export function cwdStatePath(cwd, repositoryRoot = getDurableRepositoryRoot()) {
255
- return cwdScopePaths(cwd, repositoryRoot).state;
256
- }
257
- export function cwdPatchesPath(cwd, repositoryRoot = getDurableRepositoryRoot()) {
258
- return cwdScopePaths(cwd, repositoryRoot).patches;
259
- }
260
- export function sessionStatePath(cwd, sessionId, repositoryRoot = getDurableRepositoryRoot(), sessionKey = sessionId) {
261
- return sessionScopePaths(cwd, sessionId, repositoryRoot, sessionKey).state;
262
- }
263
- export function sessionPatchesPath(cwd, sessionId, repositoryRoot = getDurableRepositoryRoot(), sessionKey = sessionId) {
264
- return sessionScopePaths(cwd, sessionId, repositoryRoot, sessionKey).patches;
234
+ return { directory: join(cwdScopePaths(cwd, repositoryRoot).directory, sessionScopeKey(sessionKey)) };
265
235
  }
266
- /** Exact semantic file shapes, including legacy snapshots only during the storage cutover. */
236
+ /** Exact canonical semantic and runtime file shapes. */
267
237
  export function isStateFlowOwnedPath(candidate, repositoryRoot = getDurableRepositoryRoot()) {
268
238
  const root = resolve(repositoryRoot);
269
239
  const absolute = resolve(candidate);
270
- const global = durablePaths(root);
271
- if (absolute === global.globalState || absolute === global.globalPatches
272
- || absolute === join(root, CHECKPOINT_FILE) || absolute === join(root, META_FILE))
240
+ if (absolute === join(root, CHECKPOINT_FILE) || absolute === join(root, PATCHES_FILE)
241
+ || absolute === join(root, META_FILE))
273
242
  return true;
274
243
  const segments = relative(root, absolute).split(sep);
275
244
  const cwdKey = (value) => value.startsWith("--") && value.endsWith("--");
@@ -282,12 +251,12 @@ export function isStateFlowOwnedPath(candidate, repositoryRoot = getDurableRepos
282
251
  }
283
252
  };
284
253
  if (segments.length === 2) {
285
- return cwdKey(segments[0]) && (segments[1] === STATE_FILE || segments[1] === CHECKPOINT_FILE || segments[1] === PATCHES_FILE || segments[1] === META_FILE);
254
+ return cwdKey(segments[0]) && (segments[1] === CHECKPOINT_FILE || segments[1] === PATCHES_FILE || segments[1] === META_FILE);
286
255
  }
287
256
  if (segments.length === 3) {
288
257
  return cwdKey(segments[0])
289
258
  && sessionKey(segments[1])
290
- && (segments[2] === STATE_FILE || segments[2] === CHECKPOINT_FILE || segments[2] === PATCHES_FILE
259
+ && (segments[2] === CHECKPOINT_FILE || segments[2] === PATCHES_FILE
291
260
  || segments[2] === "config.json" || segments[2] === RUNTIME_FILE || segments[2] === META_FILE);
292
261
  }
293
262
  return false;
@@ -378,7 +347,7 @@ function assertCurrentBytes(path, root, expected) {
378
347
  throw new Error(`State Flow file conflict at ${path}; preserve concurrent bytes and reconcile before retrying`);
379
348
  }
380
349
  }
381
- /** Capture exact owned bytes for one compare-and-swap publication or migration cohort. */
350
+ /** Capture exact owned bytes for one compare-and-swap publication cohort. */
382
351
  export function captureOwnedFileBases(paths, repositoryRoot) {
383
352
  const root = resolve(repositoryRoot);
384
353
  return paths.map((path) => {
@@ -1,13 +1,12 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { formatPatchStateArguments, normalizePatchStateArguments } from "./protocol.ts";
2
+ import { formatPatchStateArguments } from "./protocol.ts";
3
+ import { type ModelState, type StateScope } from "./state.ts";
3
4
  import { type StateFlowTelegramLoader } from "./telegram.ts";
4
- import { type MaterializedState, type StateScope } from "./state.ts";
5
5
  export interface StateFlowExtensionOptions {
6
6
  agentDir?: string;
7
7
  repositoryRoot?: string;
8
- knowledgeRoot?: string;
9
8
  onRuntime?: (accessor: {
10
- read(offset?: number, scope?: StateScope): MaterializedState;
9
+ read(offset?: number, scope?: StateScope): ModelState;
11
10
  }) => void;
12
11
  telegram?: {
13
12
  load?: StateFlowTelegramLoader;
@@ -18,8 +17,7 @@ export interface StateFlowExtensionOptions {
18
17
  tools?: boolean;
19
18
  };
20
19
  }
21
- export { formatPatchStateArguments, normalizePatchStateArguments };
20
+ export { formatPatchStateArguments };
22
21
  export declare const PATCH_STATE_TOOL_NAME = "patch_state";
23
22
  export declare const READ_STATE_TOOL_NAME = "read_state";
24
- export declare const MAX_FALLBACK_ATTEMPTS: number;
25
23
  export default function stateFlowExtension(pi: ExtensionAPI, options?: StateFlowExtensionOptions): void;