@llblab/pi-kit 0.37.0 → 0.38.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 (152) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +3 -3
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +7 -3
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +679 -2
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +17 -0
  6. package/node_modules/@llblab/pi-state-flow/README.md +30 -14
  7. package/node_modules/@llblab/pi-state-flow/api/modules.ts +11 -0
  8. package/node_modules/@llblab/pi-state-flow/dist/api/modules.d.ts +3 -0
  9. package/node_modules/@llblab/pi-state-flow/dist/api/modules.js +3 -0
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +7 -4
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +77 -36
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +2 -0
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +26 -27
  14. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +3 -0
  15. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +13 -1
  16. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +27 -21
  17. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +144 -46
  18. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +14 -18
  19. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +6 -0
  20. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +60 -50
  21. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +1049 -310
  22. package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +4 -4
  23. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +15 -3
  24. package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +2 -0
  25. package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +2 -1
  26. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +3 -1
  27. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +7 -7
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +29 -0
  29. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +186 -0
  30. package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.d.ts +17 -0
  31. package/node_modules/@llblab/pi-state-flow/dist/lib/model-interface.js +158 -0
  32. package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.d.ts +21 -0
  33. package/node_modules/@llblab/pi-state-flow/dist/lib/module-registry.js +108 -0
  34. package/node_modules/@llblab/pi-state-flow/dist/lib/operation.js +3 -2
  35. package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.d.ts +4 -4
  36. package/node_modules/@llblab/pi-state-flow/dist/lib/ownership.js +5 -5
  37. package/node_modules/@llblab/pi-state-flow/dist/lib/planes.d.ts +29 -0
  38. package/node_modules/@llblab/pi-state-flow/dist/lib/planes.js +84 -0
  39. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +6 -3
  40. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +33 -16
  41. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +21 -4
  42. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +53 -13
  43. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +5 -2
  44. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +3 -0
  45. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +10 -1
  46. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +84 -5
  47. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +570 -20
  48. package/node_modules/@llblab/pi-state-flow/dist/lib/selection.d.ts +10 -0
  49. package/node_modules/@llblab/pi-state-flow/dist/lib/selection.js +32 -0
  50. package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.d.ts +79 -0
  51. package/node_modules/@llblab/pi-state-flow/dist/lib/semantic-module.js +187 -0
  52. package/node_modules/@llblab/pi-state-flow/dist/lib/session.d.ts +98 -1
  53. package/node_modules/@llblab/pi-state-flow/dist/lib/session.js +398 -3
  54. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +7 -2
  55. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +55 -3
  56. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +67 -14
  57. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +129 -9
  58. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +26 -14
  59. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +61 -19
  60. package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +14 -9
  61. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +59 -22
  62. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +111 -10
  63. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +728 -11
  64. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.d.ts +10 -7
  65. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +50 -14
  66. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +40 -4
  67. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +247 -43
  68. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +41 -8
  69. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +132 -39
  70. package/node_modules/@llblab/pi-state-flow/dist/package.json +6 -1
  71. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +28 -16
  72. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +12 -8
  73. package/node_modules/@llblab/pi-state-flow/docs/README.md +9 -0
  74. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +76 -43
  75. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +9 -1
  76. package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +6 -0
  77. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +23 -19
  78. package/node_modules/@llblab/pi-state-flow/docs/protocol-composition-decision.md +66 -0
  79. package/node_modules/@llblab/pi-state-flow/docs/public-module-contract.md +73 -0
  80. package/node_modules/@llblab/pi-state-flow/docs/scope-selection-contract.md +814 -0
  81. package/node_modules/@llblab/pi-state-flow/docs/semantic-compatibility-decision.md +212 -0
  82. package/node_modules/@llblab/pi-state-flow/docs/semantic-extensibility-decision.md +75 -0
  83. package/node_modules/@llblab/pi-state-flow/docs/semantic-handoff-contract.md +174 -0
  84. package/node_modules/@llblab/pi-state-flow/docs/semantic-memory-evaluation.md +166 -0
  85. package/node_modules/@llblab/pi-state-flow/docs/semantic-module-contract.md +84 -0
  86. package/node_modules/@llblab/pi-state-flow/docs/single-scope-interface-contract.md +201 -0
  87. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +22 -1
  88. package/node_modules/@llblab/pi-state-flow/docs/usage.md +32 -18
  89. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +68 -31
  90. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +21 -22
  91. package/node_modules/@llblab/pi-state-flow/lib/config.ts +14 -1
  92. package/node_modules/@llblab/pi-state-flow/lib/context.ts +142 -46
  93. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +14 -17
  94. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +53 -44
  95. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +897 -297
  96. package/node_modules/@llblab/pi-state-flow/lib/history.ts +16 -7
  97. package/node_modules/@llblab/pi-state-flow/lib/json.ts +2 -1
  98. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +10 -9
  99. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +189 -0
  100. package/node_modules/@llblab/pi-state-flow/lib/model-interface.ts +136 -0
  101. package/node_modules/@llblab/pi-state-flow/lib/module-registry.ts +119 -0
  102. package/node_modules/@llblab/pi-state-flow/lib/operation.ts +3 -2
  103. package/node_modules/@llblab/pi-state-flow/lib/ownership.ts +6 -6
  104. package/node_modules/@llblab/pi-state-flow/lib/planes.ts +98 -0
  105. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +37 -16
  106. package/node_modules/@llblab/pi-state-flow/lib/query.ts +59 -18
  107. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +5 -2
  108. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +10 -1
  109. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +574 -21
  110. package/node_modules/@llblab/pi-state-flow/lib/selection.ts +39 -0
  111. package/node_modules/@llblab/pi-state-flow/lib/semantic-module.ts +239 -0
  112. package/node_modules/@llblab/pi-state-flow/lib/session.ts +387 -3
  113. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +45 -4
  114. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +156 -16
  115. package/node_modules/@llblab/pi-state-flow/lib/state.ts +69 -28
  116. package/node_modules/@llblab/pi-state-flow/lib/status.ts +65 -31
  117. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +770 -18
  118. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +58 -20
  119. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +267 -50
  120. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +184 -52
  121. package/node_modules/@llblab/pi-state-flow/package.json +6 -1
  122. package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +28 -16
  123. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +12 -8
  124. package/node_modules/@llblab/pi-telegram/AGENTS.md +1 -1
  125. package/node_modules/@llblab/pi-telegram/BACKLOG.md +8 -10
  126. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +10 -0
  127. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +20 -18
  128. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +6 -6
  129. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +23 -65
  130. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +3 -3
  131. package/node_modules/@llblab/pi-telegram/dist/lib/prompts.d.ts +1 -1
  132. package/node_modules/@llblab/pi-telegram/dist/lib/prompts.js +1 -1
  133. package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +1 -0
  134. package/node_modules/@llblab/pi-telegram/dist/lib/runtime.js +1 -0
  135. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +4 -2
  136. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +24 -13
  137. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  138. package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/SKILL.md +5 -5
  139. package/node_modules/@llblab/pi-telegram/docs/architecture.md +5 -5
  140. package/node_modules/@llblab/pi-telegram/docs/compact-matrix-literal.md +1 -1
  141. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
  142. package/node_modules/@llblab/pi-telegram/docs/outbound.md +8 -8
  143. package/node_modules/@llblab/pi-telegram/docs/public-api.md +2 -2
  144. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +17 -15
  145. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +28 -68
  146. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +3 -3
  147. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +1 -1
  148. package/node_modules/@llblab/pi-telegram/lib/runtime.ts +2 -1
  149. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +25 -12
  150. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  151. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/SKILL.md +5 -5
  152. package/package.json +3 -3
@@ -1,5 +1,173 @@
1
- import { parseRetainedPiCheckpoint, readCheckpointMode } from "./snapshot.js";
1
+ import { parseRetainedPiCheckpoint, prepareRetainedPolicyMigration, readCheckpointMode, isStateFlowMode } from "./snapshot.js";
2
+ import { captureScopeSelection, DEFAULT_SCOPE_SELECTION } from "./selection.js";
3
+ import { isJsonValue, isObject } from "./json.js";
4
+ import { isAbsolute, resolve } from "node:path";
5
+ import { RenewableLifetime } from "./operation.js";
2
6
  export const SNAPSHOT_ENTRY_TYPE = "state-flow-snapshot";
7
+ function captureRequestPolicy(value) {
8
+ try {
9
+ if (!isObject(value) || Reflect.ownKeys(value).length !== 4
10
+ || !["cwd", "owner", "mode", "scopes"].every(key => Object.hasOwn(value, key)))
11
+ throw new Error();
12
+ const { cwd, owner, mode, scopes } = value;
13
+ if (typeof cwd !== "string" || !isAbsolute(cwd) || typeof owner !== "string" || !owner.trim() || !isStateFlowMode(mode))
14
+ throw new Error();
15
+ return Object.freeze({ cwd: resolve(cwd), owner, mode, scopes: captureScopeSelection(scopes) });
16
+ }
17
+ catch {
18
+ throw new Error("Invalid State Flow selected request policy");
19
+ }
20
+ }
21
+ /** Owns policy-bound cancellation only; no storage, attachment, source acquisition or slot registry. */
22
+ export class SelectedPolicyLifetime {
23
+ #lifetime = new RenewableLifetime();
24
+ #policy;
25
+ #ended = false;
26
+ get policy() { return this.#policy; }
27
+ /** Cancellation only, including Off; unlike capture(), this grants no enabled memory receipt. */
28
+ get signal() { return this.#lifetime.signal; }
29
+ /** Same captured policy preserves independent owners; a real choice revokes all old requests. */
30
+ select(value) {
31
+ if (this.#ended)
32
+ throw new Error("State Flow selected policy lifetime has ended");
33
+ const next = captureRequestPolicy(value), previous = this.#policy;
34
+ if (previous && previous.cwd === next.cwd && previous.owner === next.owner && previous.mode === next.mode
35
+ && previous.scopes.length === next.scopes.length && previous.scopes.every((scope, index) => scope === next.scopes[index]))
36
+ return;
37
+ this.#policy = next;
38
+ this.#lifetime.renew(new Error("State Flow selected native policy changed"));
39
+ }
40
+ /** Capture at admission, before any source acquisition, asynchronous wait or queued work. */
41
+ capture() {
42
+ const policy = this.#policy, signal = this.#lifetime.signal;
43
+ if (this.#ended || !policy || signal.aborted)
44
+ throw new Error("State Flow selected policy lifetime is unavailable");
45
+ if (policy.mode === "off")
46
+ throw new Error("Off cannot capture State Flow memory requests");
47
+ const revalidate = () => {
48
+ if (this.#ended || this.#policy !== policy || this.#lifetime.signal !== signal || signal.aborted)
49
+ throw new Error("State Flow selected policy request is no longer current");
50
+ };
51
+ return Object.freeze({ policy, signal, revalidate });
52
+ }
53
+ /** Same-policy branch movement/Abort may revoke requests without inventing a different selection. */
54
+ invalidate() {
55
+ if (!this.#ended)
56
+ this.#lifetime.renew(new Error("State Flow selected native request lifetime changed"));
57
+ }
58
+ /** Shutdown cannot be reopened by a late selection or abort listener. */
59
+ end() {
60
+ this.#ended = true;
61
+ this.#policy = undefined;
62
+ this.#lifetime.end(new Error("State Flow selected policy lifetime has ended"));
63
+ }
64
+ }
65
+ /** One SDK turn and its finalized assistant batch; IDs are evidence only within this owned turn. */
66
+ export class NativeToolRequestCorrelation {
67
+ #turn;
68
+ #ambiguous = false;
69
+ #context;
70
+ #operation;
71
+ #calls = new Map();
72
+ begin(request, signal, currentIdentity, controlSignal) {
73
+ this.#operation = signal;
74
+ // An overlapping turn has no public request ID to disambiguate its later messages.
75
+ if (this.#turn || this.#ambiguous) {
76
+ this.end();
77
+ this.#ambiguous = true;
78
+ return;
79
+ }
80
+ this.#turn = { request, signal: controlSignal ? AbortSignal.any([controlSignal, ...(signal ? [signal] : [])]) : signal, currentIdentity, finalized: false, controller: new AbortController() };
81
+ }
82
+ /** Stable lease from turn_start through provider preparation, before assistant finalization. */
83
+ preparing() {
84
+ const turn = this.#turn;
85
+ const revalidate = () => {
86
+ if (this.#ambiguous || !turn || this.#turn !== turn || turn.finalized || turn.signal?.aborted || !turn.currentIdentity())
87
+ throw new Error("State Flow context has no current native request");
88
+ turn.request?.revalidate();
89
+ turn.prepared?.();
90
+ };
91
+ revalidate();
92
+ return this.#context ??= Object.freeze({
93
+ signal: AbortSignal.any([turn.controller.signal, ...(turn.signal ? [turn.signal] : []), ...(turn.request ? [turn.request.signal] : [])]),
94
+ revalidate,
95
+ });
96
+ }
97
+ /** Provider-only revalidation of captured context data; callbacks must neither acquire nor recurse into this request. */
98
+ bindPreparedContext(check) {
99
+ this.preparing().revalidate();
100
+ const turn = this.#turn;
101
+ check();
102
+ if (this.#turn !== turn)
103
+ throw new Error("State Flow prepared context owner changed");
104
+ turn.prepared = check;
105
+ }
106
+ finalize(calls) {
107
+ if (!this.#turn || this.#ambiguous)
108
+ return;
109
+ if (this.#turn.finalized) {
110
+ this.end();
111
+ this.#ambiguous = true;
112
+ return;
113
+ }
114
+ this.#turn.finalized = true;
115
+ for (const { id, name } of calls) {
116
+ if (!id || !name || this.#calls.has(id)) {
117
+ this.end();
118
+ this.#ambiguous = true;
119
+ return;
120
+ }
121
+ this.#calls.set(id, { name, consumed: false });
122
+ }
123
+ }
124
+ get active() { return this.#turn !== undefined || this.#ambiguous; }
125
+ /** Identify the ambient model owner; deciding which Abort cause to forward belongs to the native port. */
126
+ ownsOperation(signal) { return signal !== undefined && signal === this.#operation; }
127
+ has(id) { return this.#calls.has(id); }
128
+ current() {
129
+ const turn = this.#turn;
130
+ if (this.#ambiguous || !turn?.request || !turn.finalized || turn.signal?.aborted || !turn.currentIdentity())
131
+ throw new Error("State Flow tool batch has no current native request");
132
+ turn.request.revalidate();
133
+ return turn.request;
134
+ }
135
+ check(id, name) {
136
+ const request = this.current(), call = this.#calls.get(id);
137
+ if (!call || call.name !== name)
138
+ throw new Error("State Flow tool batch has no current native request");
139
+ return request;
140
+ }
141
+ /** Only explicit SDK parent evidence, never an ID prefix or branch-history guess. */
142
+ nested(id, name, parent) {
143
+ const call = this.#calls.get(parent), previous = this.#calls.get(id);
144
+ if (!id || !call || previous && (previous.name !== name || previous.parent !== parent))
145
+ throw new Error("State Flow nested tool has no current native parent");
146
+ this.check(parent, call.name);
147
+ if (!previous)
148
+ this.#calls.set(id, { name, consumed: false, parent });
149
+ }
150
+ take(id, name) {
151
+ const request = this.check(id, name), call = this.#calls.get(id);
152
+ if (call.consumed)
153
+ throw new Error("State Flow native tool admission was already consumed");
154
+ call.consumed = true;
155
+ const turn = this.#turn;
156
+ return Object.freeze({ policy: request.policy,
157
+ signal: AbortSignal.any([request.signal, turn.controller.signal, ...(turn.signal ? [turn.signal] : [])]),
158
+ revalidate: () => { if (this.#turn !== turn)
159
+ throw new Error("State Flow native request turn has ended"); this.check(id, name); },
160
+ });
161
+ }
162
+ end() {
163
+ const previous = this.#turn;
164
+ this.#turn = undefined;
165
+ this.#ambiguous = false;
166
+ this.#context = undefined;
167
+ this.#calls.clear();
168
+ previous?.controller.abort();
169
+ }
170
+ }
3
171
  /** Enumerate active-branch snapshots newest-first while containing hostile entries. */
4
172
  export function discoverSnapshotData(branch) {
5
173
  const candidates = [];
@@ -54,6 +222,226 @@ export function findBranchPolicy(branch, sessionId, stopEntryType, inactiveMode)
54
222
  }
55
223
  return undefined;
56
224
  }
225
+ /** Checkpoint-only preflight: no Stop/fork precedence, installation or authority. Invalid owned data cannot fall back. */
226
+ export function prepareBranchCheckpointPolicy(branch, legacySelection, inactiveMode = "passive") {
227
+ for (let index = branch.length - 1; index >= 0; index--) {
228
+ let entry;
229
+ try {
230
+ entry = branch[index];
231
+ if (entry?.type !== "custom" || entry.customType !== SNAPSHOT_ENTRY_TYPE)
232
+ continue;
233
+ }
234
+ catch {
235
+ // Unclassifiable foreign entries grant no policy authority.
236
+ continue;
237
+ }
238
+ return prepareRetainedPolicyMigration(entry.data, legacySelection, inactiveMode);
239
+ }
240
+ return undefined;
241
+ }
242
+ function nativeMarkerVersion(value) {
243
+ let version;
244
+ try {
245
+ if (!Object.hasOwn(value, "version"))
246
+ return "legacy";
247
+ version = value.version;
248
+ }
249
+ catch {
250
+ throw new Error("Unreadable State Flow native policy marker version");
251
+ }
252
+ if (version !== 2)
253
+ throw new Error("Unsupported State Flow native policy marker version");
254
+ return 2;
255
+ }
256
+ function parseNativeMarker(value) {
257
+ if (!isObject(value))
258
+ throw new Error("Invalid State Flow native policy marker");
259
+ const version = nativeMarkerVersion(value);
260
+ if (version === "legacy" && Object.hasOwn(value, "scopes"))
261
+ throw new Error("Target policy fields in legacy State Flow native marker");
262
+ const keys = ["version", "owner", "mode", "scopes", "reset", "forkPending", "at", "from", "preserveContext", "persistenceError", "memoryDeferred"];
263
+ if (Object.keys(value).some(key => !keys.includes(key)) || !isJsonValue(value))
264
+ throw new Error("Invalid State Flow native policy marker");
265
+ const { owner, mode, reset, forkPending, at, from: rawFrom, preserveContext, persistenceError, memoryDeferred } = value;
266
+ // Legacy Stop treated an invalid optional anchor as absent, retaining the valid cutoff.
267
+ const from = version === "legacy" && (!Number.isSafeInteger(rawFrom) || rawFrom < 0) ? undefined : rawFrom;
268
+ if ((owner !== undefined && (typeof owner !== "string" || !owner.trim()))
269
+ || (mode !== undefined && !isStateFlowMode(mode))
270
+ || (version === 2 && (typeof owner !== "string" || !isStateFlowMode(mode) || !Object.hasOwn(value, "scopes")))
271
+ || [reset, forkPending, preserveContext, memoryDeferred].some(flag => flag !== undefined && flag !== true)
272
+ || [at, from].some(time => time !== undefined && (!Number.isSafeInteger(time) || time < 0))
273
+ || (persistenceError !== undefined && (typeof persistenceError !== "string" || !persistenceError.trim()))
274
+ || ((from !== undefined || preserveContext !== undefined || persistenceError !== undefined || memoryDeferred !== undefined) && at === undefined)
275
+ || (at !== undefined && mode === "active")
276
+ || (memoryDeferred === true && (mode !== "off" || owner === undefined))
277
+ || ((reset === true || forkPending === true) && owner === undefined)
278
+ || (reset === true && (forkPending !== undefined || at !== undefined || from !== undefined || preserveContext !== undefined || persistenceError !== undefined || memoryDeferred !== undefined))
279
+ || (version === "legacy" && at === undefined && reset !== true && forkPending !== true)
280
+ || (version === "legacy" && reset === true && mode !== undefined))
281
+ throw new Error("Invalid State Flow native policy marker");
282
+ return {
283
+ version, ...(owner === undefined ? {} : { owner: owner }), ...(mode === undefined ? {} : { mode: mode }),
284
+ ...(version === 2 ? { scopes: captureScopeSelection(value.scopes) } : {}),
285
+ ...(reset === true ? { reset: true } : {}), ...(forkPending === true ? { forkPending: true } : {}),
286
+ ...(at === undefined ? {} : { at: at }), ...(from === undefined ? {} : { from: from }),
287
+ ...(preserveContext === true ? { preserveContext: true } : {}), ...(memoryDeferred === true ? { memoryDeferred: true } : {}),
288
+ ...(persistenceError === undefined ? {} : { persistenceError: persistenceError }),
289
+ };
290
+ }
291
+ /** Closed v2 writer value. Legacy records are translated only in branch context, never given a fabricated owner. */
292
+ export function captureSelectedNativeMarker(value) {
293
+ const marker = parseNativeMarker(value);
294
+ if (marker.version !== 2)
295
+ throw new Error("Selected State Flow native marker requires version 2");
296
+ return marker;
297
+ }
298
+ /** Complete native branch admission only; never read canonical/parent files, attach policy or confer memory authority. */
299
+ export function prepareSelectedBranchPolicy(branch, options) {
300
+ const { owner, markerEntryType } = options;
301
+ const inactiveMode = options.inactiveMode ?? "passive";
302
+ const fallbackMode = options.defaults?.mode;
303
+ if (typeof owner !== "string" || !owner.trim() || typeof markerEntryType !== "string" || !markerEntryType.trim() || markerEntryType === SNAPSHOT_ENTRY_TYPE
304
+ || !isStateFlowMode(fallbackMode) || (inactiveMode !== "passive" && inactiveMode !== "off")
305
+ || (options.fork !== undefined && typeof options.fork !== "boolean"))
306
+ throw new Error("Invalid State Flow selected branch policy context");
307
+ let mode = fallbackMode, scopes = captureScopeSelection(options.defaults.scopes);
308
+ const classified = [];
309
+ for (let index = 0; index < branch.length; index++) {
310
+ try {
311
+ const entry = branch[index];
312
+ if (entry?.type !== "custom")
313
+ continue;
314
+ if (entry.customType === SNAPSHOT_ENTRY_TYPE)
315
+ classified.push({ type: "checkpoint", entry, index });
316
+ else if (entry.customType === markerEntryType)
317
+ classified.push({ type: "marker", entry, index });
318
+ }
319
+ catch { /* Unclassifiable foreign entries supply no policy. */ }
320
+ }
321
+ const data = new Map(), decoded = new Map();
322
+ const read = (record) => {
323
+ if (!data.has(record.index)) {
324
+ try {
325
+ data.set(record.index, record.entry.data);
326
+ }
327
+ catch {
328
+ throw new Error("Unreadable State Flow native policy data");
329
+ }
330
+ }
331
+ return data.get(record.index);
332
+ };
333
+ const markerOwner = (value) => {
334
+ try {
335
+ return isObject(value) ? value.owner : undefined;
336
+ }
337
+ catch {
338
+ throw new Error("Unreadable State Flow native policy owner");
339
+ }
340
+ };
341
+ const marker = (record) => {
342
+ if (!decoded.has(record.index))
343
+ decoded.set(record.index, parseNativeMarker(read(record)));
344
+ return decoded.get(record.index);
345
+ };
346
+ let forkPending = options.fork === true;
347
+ // A later owned reset/pending record supersedes older fork evidence, not a checkpoint or mode-only choice.
348
+ for (const record of [...classified].reverse()) {
349
+ if (record.type !== "marker")
350
+ continue;
351
+ const value = read(record);
352
+ if (markerOwner(value) !== owner || !isObject(value))
353
+ continue;
354
+ nativeMarkerVersion(value);
355
+ if (!Object.hasOwn(value, "reset") && !Object.hasOwn(value, "forkPending"))
356
+ continue;
357
+ const selected = marker(record);
358
+ if (selected.reset) {
359
+ forkPending = false;
360
+ break;
361
+ }
362
+ if (selected.forkPending) {
363
+ forkPending = true;
364
+ break;
365
+ }
366
+ }
367
+ const inherit = options.fork === true || forkPending;
368
+ let localSeen = false, policyError = false, requiresCapture = true;
369
+ let source = "default";
370
+ let checkpointMode;
371
+ let stop, persistenceError;
372
+ for (const record of classified) {
373
+ if (record.type === "checkpoint") {
374
+ try {
375
+ const selected = prepareRetainedPolicyMigration(read(record), scopes, inactiveMode);
376
+ mode = selected.checkpoint.mode;
377
+ scopes = selected.checkpoint.scopes;
378
+ checkpointMode = mode;
379
+ if (selected.sourceVersion === 2)
380
+ policyError = false;
381
+ requiresCapture = selected.requiresConversion || inherit;
382
+ source = "checkpoint";
383
+ if (Object.hasOwn(selected.checkpoint, "boundary"))
384
+ persistenceError = undefined;
385
+ }
386
+ catch {
387
+ policyError = true;
388
+ }
389
+ continue;
390
+ }
391
+ const raw = read(record), physicalOwner = markerOwner(raw);
392
+ const local = physicalOwner === owner;
393
+ const inherited = typeof physicalOwner === "string" && physicalOwner.trim() && inherit && !localSeen;
394
+ if (physicalOwner !== undefined && !local && !inherited)
395
+ continue;
396
+ try {
397
+ const selected = marker(record);
398
+ const policyOwner = local || inherited;
399
+ if (policyOwner) {
400
+ if (selected.version === 2) {
401
+ scopes = selected.scopes;
402
+ mode = selected.mode;
403
+ policyError = false;
404
+ source = "marker";
405
+ requiresCapture = !local;
406
+ }
407
+ else if (selected.reset) {
408
+ mode = checkpointMode ?? fallbackMode;
409
+ source = "marker";
410
+ requiresCapture = true;
411
+ }
412
+ else if (selected.memoryDeferred || selected.persistenceError) {
413
+ mode = selected.memoryDeferred ? "off" : selected.mode ?? inactiveMode;
414
+ source = "marker";
415
+ requiresCapture = true;
416
+ }
417
+ if (local)
418
+ localSeen = true;
419
+ }
420
+ if (local && selected.reset) {
421
+ stop = undefined;
422
+ persistenceError = undefined;
423
+ }
424
+ if ((local || physicalOwner === undefined) && selected.at !== undefined) {
425
+ stop = { at: selected.at, ...(selected.from === undefined ? {} : { from: selected.from }),
426
+ ...(selected.preserveContext ? { preserveContext: true } : {}), ...(selected.mode === "passive" || selected.mode === "off" ? { mode: selected.mode } : {}),
427
+ ...(selected.memoryDeferred ? { memoryDeferred: true } : {}) };
428
+ if (local && selected.persistenceError)
429
+ persistenceError = selected.persistenceError;
430
+ }
431
+ }
432
+ catch {
433
+ if (local || inherited || isObject(raw) && Object.hasOwn(raw, "version"))
434
+ policyError = true;
435
+ }
436
+ }
437
+ if (policyError)
438
+ throw new Error("State Flow selected branch policy is invalid or unsupported");
439
+ scopes = captureScopeSelection(scopes);
440
+ const capture = captureSelectedNativeMarker({ version: 2, owner, mode, scopes, ...(forkPending ? { forkPending: true } : {}) });
441
+ return { mode, scopes, source, forkPending, requiresCapture, capture,
442
+ ...(stop === undefined ? {} : { stop: { ...stop, ...(persistenceError === undefined ? {} : { persistenceError }) } }),
443
+ ...(persistenceError === undefined ? {} : { persistenceError }) };
444
+ }
57
445
  /** Native policy bookkeeping retains an unacquired fork across extension reloads. */
58
446
  export function hasPendingFork(branch, sessionId, entryType) {
59
447
  for (let index = branch.length - 1; index >= 0; index--) {
@@ -107,8 +495,15 @@ export function hasUncheckpointedConversation(branch) {
107
495
  return true;
108
496
  }
109
497
  if (entry?.type === "custom" && entry.customType === SNAPSHOT_ENTRY_TYPE) {
110
- parseRetainedPiCheckpoint(entry.data);
111
- return false;
498
+ if (isObject(entry.data) && Object.hasOwn(entry.data, "version")) {
499
+ const retained = prepareRetainedPolicyMigration(entry.data, DEFAULT_SCOPE_SELECTION).checkpoint;
500
+ if (Object.hasOwn(retained, "boundary"))
501
+ return false;
502
+ }
503
+ else {
504
+ parseRetainedPiCheckpoint(entry.data);
505
+ return false;
506
+ }
112
507
  }
113
508
  }
114
509
  catch {
@@ -1,5 +1,6 @@
1
1
  import { type ArtifactProvenance, type ArtifactRegistry } from "./artifact.ts";
2
- import type { StateScope } from "./state.ts";
2
+ import { type ScopeSelection } from "./selection.ts";
3
+ import type { AtomicScopePatches, StateScope } from "./state.ts";
3
4
  export declare const SKILL_ARTIFACT_COMPILER = "skill-artifact-v1";
4
5
  export declare function hasCompiledSkillArtifact(artifacts: ArtifactRegistry, provenance: ArtifactProvenance | undefined, source: string, expectedHash?: string): boolean;
5
6
  export type SkillSourceHasher = (source: string) => string;
@@ -29,7 +30,11 @@ export declare class SkillReadTracker {
29
30
  readonly successful: Map<string, SuccessfulSkillRead>;
30
31
  readonly hashSource: SkillSourceHasher;
31
32
  readonly resolveRegistered: RegisteredSkillResolver;
32
- constructor(hashSource?: SkillSourceHasher, resolveRegistered?: RegisteredSkillResolver);
33
+ constructor(hashSource?: SkillSourceHasher, resolveRegistered?: RegisteredSkillResolver, selection?: ScopeSelection);
34
+ /** Selection changes revoke correlated reads; a disabled Skill still retains ownership-only volatile evidence. */
35
+ select(selection: ScopeSelection): void;
36
+ /** Rehash only attempted selected compilations; this body acquisition must run outside canonical exclusion. */
37
+ validateCompilations(patches: AtomicScopePatches): void;
33
38
  clear(): void;
34
39
  recordStart(toolCallId: string, toolName: string, args: unknown): void;
35
40
  recordCall(toolCallId: string, toolName: string, input: unknown): void;
@@ -1,7 +1,8 @@
1
- import { readFileSync } from "node:fs";
1
+ import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { hashArtifactSource, isArtifactHash, } from "./artifact.js";
4
4
  import { isObject } from "./json.js";
5
+ import { captureScopeSelection, DEFAULT_SCOPE_SELECTION } from "./selection.js";
5
6
  export const SKILL_ARTIFACT_COMPILER = "skill-artifact-v1";
6
7
  function hasContent(value) {
7
8
  if (typeof value === "string")
@@ -30,7 +31,26 @@ export function hasCompiledSkillArtifact(artifacts, provenance, source, expected
30
31
  && (expectedHash === undefined || sourceHash === expectedHash);
31
32
  }
32
33
  export function hashSkillSource(source) {
33
- return hashArtifactSource(readFileSync(source));
34
+ const expected = lstatSync(source, { bigint: true });
35
+ if (!expected.isFile() || expected.isSymbolicLink())
36
+ throw new Error("Skill source must be a regular non-symlink file");
37
+ const descriptor = openSync(source, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
38
+ try {
39
+ const before = fstatSync(descriptor, { bigint: true });
40
+ if (!before.isFile() || before.dev !== expected.dev || before.ino !== expected.ino)
41
+ throw new Error("Skill source changed before identity capture");
42
+ const bytes = readFileSync(descriptor);
43
+ const after = fstatSync(descriptor, { bigint: true });
44
+ const current = lstatSync(source, { bigint: true });
45
+ if (current.isSymbolicLink() || !current.isFile() || current.dev !== before.dev || current.ino !== before.ino
46
+ || after.size !== before.size || after.mtimeNs !== before.mtimeNs || after.ctimeNs !== before.ctimeNs
47
+ || current.size !== after.size || current.mtimeNs !== after.mtimeNs || current.ctimeNs !== after.ctimeNs)
48
+ throw new Error("Skill source changed during identity capture");
49
+ return hashArtifactSource(bytes);
50
+ }
51
+ finally {
52
+ closeSync(descriptor);
53
+ }
34
54
  }
35
55
  function readPath(toolName, args) {
36
56
  if (toolName !== "read" || !isObject(args) || typeof args.path !== "string")
@@ -67,9 +87,36 @@ export class SkillReadTracker {
67
87
  #pending = new Map();
68
88
  hashSource;
69
89
  resolveRegistered;
70
- constructor(hashSource = hashSkillSource, resolveRegistered = () => undefined) {
90
+ #selection;
91
+ constructor(hashSource = hashSkillSource, resolveRegistered = () => undefined, selection = DEFAULT_SCOPE_SELECTION) {
71
92
  this.hashSource = hashSource;
72
93
  this.resolveRegistered = resolveRegistered;
94
+ this.#selection = captureScopeSelection(selection);
95
+ }
96
+ /** Selection changes revoke correlated reads; a disabled Skill still retains ownership-only volatile evidence. */
97
+ select(selection) {
98
+ const wanted = captureScopeSelection(selection);
99
+ if (wanted.join(",") !== this.#selection.join(","))
100
+ this.clear();
101
+ this.#selection = wanted;
102
+ }
103
+ /** Rehash only attempted selected compilations; this body acquisition must run outside canonical exclusion. */
104
+ validateCompilations(patches) {
105
+ for (const scope of Object.keys(patches))
106
+ if (!this.#selection.includes(scope))
107
+ throw new Error(`State Flow ${scope} Skill compilation owner is excluded`);
108
+ for (const read of this.successful.values()) {
109
+ if (!this.#selection.includes(read.scope) || !Object.hasOwn(patches, read.scope))
110
+ continue;
111
+ const artifacts = patches[read.scope]?.artifacts;
112
+ if (!isObject(artifacts) || !Object.hasOwn(artifacts, read.path) || !isObject(artifacts[read.path]))
113
+ continue;
114
+ const registered = this.resolveRegistered(read.path);
115
+ if (!registered || registered.path !== read.path || registered.scope !== read.scope)
116
+ throw new Error(`Registered Skill ownership changed after acquisition: ${read.path}`);
117
+ if (!isArtifactHash(read.hash) || read.error !== undefined || this.hashSource(read.path) !== read.hash)
118
+ throw new Error(`Skill source changed after acquisition: ${read.path}`);
119
+ }
73
120
  }
74
121
  clear() {
75
122
  this.successful.clear();
@@ -103,6 +150,11 @@ export class SkillReadTracker {
103
150
  const registered = this.resolveRegistered(source);
104
151
  if (!registered)
105
152
  return undefined;
153
+ if (!this.#selection.includes(registered.scope)) {
154
+ const read = { path: registered.path, scope: registered.scope };
155
+ this.successful.set(registered.path, read);
156
+ return read;
157
+ }
106
158
  let read;
107
159
  try {
108
160
  const hash = this.hashSource(registered.path);
@@ -1,6 +1,7 @@
1
1
  import { type ArtifactProvenanceRegistry } from "./artifact.ts";
2
2
  import { type JsonObject } from "./json.ts";
3
- import { type TransitionBoundary } from "./temporal.ts";
3
+ import { type ExcludedTransitionWitness, type TransitionBoundary } from "./temporal.ts";
4
+ import { type ScopeSelection } from "./selection.ts";
4
5
  /** Missing operational capability is not evidence that a checkpoint target is invalid. */
5
6
  export declare class RevisionUnavailableError extends Error {
6
7
  }
@@ -35,24 +36,47 @@ export interface StateFlowSnapshot {
35
36
  meta: SnapshotMeta;
36
37
  }
37
38
  export type Snapshot = StateFlowSnapshot;
39
+ /** Capture closed caller-owned run fields, omitting only undefined optional in-memory fields. */
40
+ export declare function captureLifecycleSnapshot(value: Snapshot): Snapshot;
38
41
  /** Failed Active selection retains its policy and fences inference until explicit recovery or an inactive choice. */
39
42
  export declare function isActiveRestorationBlocked(snapshot: Snapshot): boolean;
43
+ interface LifecycleMetadata extends SnapshotMeta {
44
+ identity: {
45
+ cwd: string;
46
+ sessionId: string;
47
+ };
48
+ lineage: TransitionBoundary[];
49
+ [key: string]: unknown;
50
+ }
51
+ interface SessionRuntimeMetadata extends LifecycleMetadata {
52
+ /** Runtime-owned artifact compilation evidence; never projected as semantic state. */
53
+ artifacts?: ArtifactProvenanceRegistry;
54
+ temporal?: {
55
+ checkpoint: TransitionBoundary;
56
+ patches: TransitionBoundary[];
57
+ };
58
+ }
40
59
  export interface SessionRuntime {
41
60
  config: SnapshotConfig;
42
- meta: SnapshotMeta & {
61
+ meta: SessionRuntimeMetadata & {
43
62
  version: 1;
44
- identity: {
45
- cwd: string;
46
- sessionId: string;
47
- };
48
- lineage: TransitionBoundary[];
49
- /** Runtime-owned artifact compilation evidence; never projected as semantic state. */
50
- artifacts?: ArtifactProvenanceRegistry;
51
- temporal?: {
52
- checkpoint: TransitionBoundary;
53
- patches: TransitionBoundary[];
54
- };
55
- [key: string]: unknown;
63
+ };
64
+ }
65
+ export type PrivateAcquisition = "unacquired" | "deferred" | "acquired";
66
+ export interface PrivateCausalBasis {
67
+ lineage: TransitionBoundary[];
68
+ causalWitnesses: ExcludedTransitionWitness[];
69
+ }
70
+ /** Retained private identity proof, never private values or acquisition permission. */
71
+ export declare function capturePrivateCausalBasis(value: unknown): PrivateCausalBasis;
72
+ export interface SelectedSessionRuntime {
73
+ config: SnapshotConfig & {
74
+ scopes: ScopeSelection;
75
+ };
76
+ meta: LifecycleMetadata & {
77
+ version: 2;
78
+ privateAcquisition: PrivateAcquisition;
79
+ privateCausalBasis?: PrivateCausalBasis;
56
80
  };
57
81
  }
58
82
  export declare function validateSessionRuntime(value: unknown, cwd: string, sessionId: string): asserts value is SessionRuntime;
@@ -63,6 +87,23 @@ export declare function serializeSessionRuntime(runtime: SessionRuntime, cwd: st
63
87
  };
64
88
  /** Legacy `enabled:false` decodes as non-active; native checkpoints, not this file, select branch policy. */
65
89
  export declare function parseSessionRuntime(config: string | undefined, runtimeSource: string | undefined, cwd: string, sessionId: string): SessionRuntime | undefined;
90
+ export interface SessionPolicyMigrationCandidate {
91
+ sourceVersion: 1 | 2;
92
+ targetVersion: 2;
93
+ requiresConversion: boolean;
94
+ /** Original strings, not file observations or publication authority. */
95
+ source: {
96
+ config: string;
97
+ runtime: string;
98
+ };
99
+ runtime: SelectedSessionRuntime;
100
+ target: {
101
+ config: string;
102
+ runtime: string;
103
+ };
104
+ }
105
+ /** Detached lifecycle preflight only; current native readers/writers still admit version 1 exclusively. */
106
+ export declare function prepareSessionPolicyMigration(config: string | undefined, runtimeSource: string | undefined, cwd: string, sessionId: string, legacySelection: ScopeSelection): SessionPolicyMigrationCandidate | undefined;
66
107
  export declare function emptySnapshot(mode?: StateFlowMode): Snapshot;
67
108
  export type RetainedBoundaryCheckpoint = {
68
109
  boundary: string;
@@ -84,5 +125,17 @@ export declare function retainedBoundaryCheckpoint(snapshot: Snapshot, boundary:
84
125
  export declare function preRuntimeCheckpoint(mode: StateFlowMode): PreRuntimeCheckpoint;
85
126
  /** Decode the retained-window checkpoint contract; legacy `enabled`/`{disabled:true}` markers map through `inactiveMode`. */
86
127
  export declare function parseRetainedPiCheckpoint(value: unknown, inactiveMode?: InactiveMode): RetainedPiCheckpoint;
128
+ export type SelectedRetainedPiCheckpoint = RetainedPiCheckpoint & {
129
+ version: 2;
130
+ scopes: ScopeSelection;
131
+ };
132
+ export interface RetainedPolicyMigrationCandidate {
133
+ sourceVersion: "legacy" | 2;
134
+ targetVersion: 2;
135
+ requiresConversion: boolean;
136
+ checkpoint: SelectedRetainedPiCheckpoint;
137
+ }
138
+ /** Native policy value preflight; no retained boundary, fork or acquisition authority is created. */
139
+ export declare function prepareRetainedPolicyMigration(value: unknown, legacySelection: ScopeSelection, inactiveMode?: InactiveMode): RetainedPolicyMigrationCandidate;
87
140
  export declare function migrationFailure(data: JsonObject, error: string, mode?: StateFlowMode): Snapshot;
88
141
  export {};