@earendil-works/pi-agent-core 0.85.1 → 0.86.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 (198) hide show
  1. package/README.md +16 -6
  2. package/dist/agent-loop.d.ts.map +1 -1
  3. package/dist/agent-loop.js +64 -22
  4. package/dist/agent-loop.js.map +1 -1
  5. package/dist/agent.d.ts +5 -3
  6. package/dist/agent.d.ts.map +1 -1
  7. package/dist/agent.js +15 -6
  8. package/dist/agent.js.map +1 -1
  9. package/dist/harness/config.d.ts +1 -1
  10. package/dist/harness/config.d.ts.map +1 -1
  11. package/dist/harness/config.js +10 -2
  12. package/dist/harness/config.js.map +1 -1
  13. package/dist/harness/env/nodejs.d.ts +2 -1
  14. package/dist/harness/env/nodejs.d.ts.map +1 -1
  15. package/dist/harness/env/nodejs.js +98 -27
  16. package/dist/harness/env/nodejs.js.map +1 -1
  17. package/dist/harness/execution/tools.d.ts +1 -1
  18. package/dist/harness/execution/tools.d.ts.map +1 -1
  19. package/dist/harness/execution/tools.js +0 -2
  20. package/dist/harness/execution/tools.js.map +1 -1
  21. package/dist/harness/messages.d.ts.map +1 -1
  22. package/dist/harness/messages.js +1 -0
  23. package/dist/harness/messages.js.map +1 -1
  24. package/dist/harness/pico3/bash.d.ts +10 -0
  25. package/dist/harness/pico3/bash.d.ts.map +1 -0
  26. package/dist/harness/pico3/bash.js +25 -0
  27. package/dist/harness/pico3/bash.js.map +1 -0
  28. package/dist/harness/pico3/bounded.d.ts +21 -0
  29. package/dist/harness/pico3/bounded.d.ts.map +1 -0
  30. package/dist/harness/pico3/bounded.js +98 -0
  31. package/dist/harness/pico3/bounded.js.map +1 -0
  32. package/dist/harness/pico3/chord.d.ts +41 -0
  33. package/dist/harness/pico3/chord.d.ts.map +1 -0
  34. package/dist/harness/pico3/chord.js +130 -0
  35. package/dist/harness/pico3/chord.js.map +1 -0
  36. package/dist/harness/pico3/context.d.ts +16 -0
  37. package/dist/harness/pico3/context.d.ts.map +1 -0
  38. package/dist/harness/pico3/context.js +88 -0
  39. package/dist/harness/pico3/context.js.map +1 -0
  40. package/dist/harness/pico3/harness.d.ts +197 -0
  41. package/dist/harness/pico3/harness.d.ts.map +1 -0
  42. package/dist/harness/pico3/harness.js +654 -0
  43. package/dist/harness/pico3/harness.js.map +1 -0
  44. package/dist/harness/pico3/hooks.d.ts +10 -0
  45. package/dist/harness/pico3/hooks.d.ts.map +1 -0
  46. package/dist/harness/pico3/hooks.js +33 -0
  47. package/dist/harness/pico3/hooks.js.map +1 -0
  48. package/dist/harness/pico3/index.d.ts +19 -0
  49. package/dist/harness/pico3/index.d.ts.map +1 -0
  50. package/dist/harness/pico3/index.js +15 -0
  51. package/dist/harness/pico3/index.js.map +1 -0
  52. package/dist/harness/pico3/jsonl.d.ts +58 -0
  53. package/dist/harness/pico3/jsonl.d.ts.map +1 -0
  54. package/dist/harness/pico3/jsonl.js +325 -0
  55. package/dist/harness/pico3/jsonl.js.map +1 -0
  56. package/dist/harness/pico3/kinds/collapse.d.ts +49 -0
  57. package/dist/harness/pico3/kinds/collapse.d.ts.map +1 -0
  58. package/dist/harness/pico3/kinds/collapse.js +191 -0
  59. package/dist/harness/pico3/kinds/collapse.js.map +1 -0
  60. package/dist/harness/pico3/kinds/entries.d.ts +78 -0
  61. package/dist/harness/pico3/kinds/entries.d.ts.map +1 -0
  62. package/dist/harness/pico3/kinds/entries.js +13 -0
  63. package/dist/harness/pico3/kinds/entries.js.map +1 -0
  64. package/dist/harness/pico3/kinds/frames.d.ts +9 -0
  65. package/dist/harness/pico3/kinds/frames.d.ts.map +1 -0
  66. package/dist/harness/pico3/kinds/frames.js +75 -0
  67. package/dist/harness/pico3/kinds/frames.js.map +1 -0
  68. package/dist/harness/pico3/kinds/generation.d.ts +94 -0
  69. package/dist/harness/pico3/kinds/generation.d.ts.map +1 -0
  70. package/dist/harness/pico3/kinds/generation.js +504 -0
  71. package/dist/harness/pico3/kinds/generation.js.map +1 -0
  72. package/dist/harness/pico3/kinds/job.d.ts +44 -0
  73. package/dist/harness/pico3/kinds/job.d.ts.map +1 -0
  74. package/dist/harness/pico3/kinds/job.js +143 -0
  75. package/dist/harness/pico3/kinds/job.js.map +1 -0
  76. package/dist/harness/pico3/kinds/plugin.d.ts +15 -0
  77. package/dist/harness/pico3/kinds/plugin.d.ts.map +1 -0
  78. package/dist/harness/pico3/kinds/plugin.js +35 -0
  79. package/dist/harness/pico3/kinds/plugin.js.map +1 -0
  80. package/dist/harness/pico3/kinds/post-tools.d.ts +22 -0
  81. package/dist/harness/pico3/kinds/post-tools.d.ts.map +1 -0
  82. package/dist/harness/pico3/kinds/post-tools.js +145 -0
  83. package/dist/harness/pico3/kinds/post-tools.js.map +1 -0
  84. package/dist/harness/pico3/kinds/task-api.d.ts +3 -0
  85. package/dist/harness/pico3/kinds/task-api.d.ts.map +1 -0
  86. package/dist/harness/pico3/kinds/task-api.js +45 -0
  87. package/dist/harness/pico3/kinds/task-api.js.map +1 -0
  88. package/dist/harness/pico3/kinds/tool.d.ts +43 -0
  89. package/dist/harness/pico3/kinds/tool.d.ts.map +1 -0
  90. package/dist/harness/pico3/kinds/tool.js +373 -0
  91. package/dist/harness/pico3/kinds/tool.js.map +1 -0
  92. package/dist/harness/pico3/membrane.d.ts +23 -0
  93. package/dist/harness/pico3/membrane.d.ts.map +1 -0
  94. package/dist/harness/pico3/membrane.js +141 -0
  95. package/dist/harness/pico3/membrane.js.map +1 -0
  96. package/dist/harness/pico3/memory.d.ts +53 -0
  97. package/dist/harness/pico3/memory.d.ts.map +1 -0
  98. package/dist/harness/pico3/memory.js +264 -0
  99. package/dist/harness/pico3/memory.js.map +1 -0
  100. package/dist/harness/pico3/scheduler.d.ts +46 -0
  101. package/dist/harness/pico3/scheduler.d.ts.map +1 -0
  102. package/dist/harness/pico3/scheduler.js +438 -0
  103. package/dist/harness/pico3/scheduler.js.map +1 -0
  104. package/dist/harness/pico3/session.d.ts +263 -0
  105. package/dist/harness/pico3/session.d.ts.map +1 -0
  106. package/dist/harness/pico3/session.js +1347 -0
  107. package/dist/harness/pico3/session.js.map +1 -0
  108. package/dist/harness/pico3/system.d.ts +126 -0
  109. package/dist/harness/pico3/system.d.ts.map +1 -0
  110. package/dist/harness/pico3/system.js +244 -0
  111. package/dist/harness/pico3/system.js.map +1 -0
  112. package/dist/harness/pico3/types.d.ts +973 -0
  113. package/dist/harness/pico3/types.d.ts.map +1 -0
  114. package/dist/harness/pico3/types.js +96 -0
  115. package/dist/harness/pico3/types.js.map +1 -0
  116. package/dist/harness/pico3/view.d.ts +34 -0
  117. package/dist/harness/pico3/view.d.ts.map +1 -0
  118. package/dist/harness/pico3/view.js +417 -0
  119. package/dist/harness/pico3/view.js.map +1 -0
  120. package/dist/harness/runtime/drive/boundary.d.ts.map +1 -1
  121. package/dist/harness/runtime/drive/boundary.js +6 -3
  122. package/dist/harness/runtime/drive/boundary.js.map +1 -1
  123. package/dist/harness/runtime/drive/response.d.ts.map +1 -1
  124. package/dist/harness/runtime/drive/response.js +4 -4
  125. package/dist/harness/runtime/drive/response.js.map +1 -1
  126. package/dist/harness/runtime/drive/retry.d.ts +2 -2
  127. package/dist/harness/runtime/drive/retry.d.ts.map +1 -1
  128. package/dist/harness/runtime/drive/retry.js +3 -6
  129. package/dist/harness/runtime/drive/retry.js.map +1 -1
  130. package/dist/harness/runtime/drive/structural.d.ts.map +1 -1
  131. package/dist/harness/runtime/drive/structural.js +5 -5
  132. package/dist/harness/runtime/drive/structural.js.map +1 -1
  133. package/dist/harness/runtime/drive/tool-placement.d.ts.map +1 -1
  134. package/dist/harness/runtime/drive/tool-placement.js +2 -27
  135. package/dist/harness/runtime/drive/tool-placement.js.map +1 -1
  136. package/dist/harness/runtime/drive/tools.d.ts.map +1 -1
  137. package/dist/harness/runtime/drive/tools.js.map +1 -1
  138. package/dist/harness/session/fork-policy.d.ts +20 -12
  139. package/dist/harness/session/fork-policy.d.ts.map +1 -1
  140. package/dist/harness/session/fork-policy.js +45 -12
  141. package/dist/harness/session/fork-policy.js.map +1 -1
  142. package/dist/harness/session/fork.d.ts +0 -2
  143. package/dist/harness/session/fork.d.ts.map +1 -1
  144. package/dist/harness/session/fork.js +20 -68
  145. package/dist/harness/session/fork.js.map +1 -1
  146. package/dist/harness/session/in-memory-storage-state.d.ts +5 -5
  147. package/dist/harness/session/in-memory-storage-state.d.ts.map +1 -1
  148. package/dist/harness/session/in-memory-storage-state.js +99 -39
  149. package/dist/harness/session/in-memory-storage-state.js.map +1 -1
  150. package/dist/harness/session/index.d.ts +1 -1
  151. package/dist/harness/session/index.d.ts.map +1 -1
  152. package/dist/harness/session/index.js +1 -1
  153. package/dist/harness/session/index.js.map +1 -1
  154. package/dist/harness/session/jsonl/fork.d.ts +38 -0
  155. package/dist/harness/session/jsonl/fork.d.ts.map +1 -0
  156. package/dist/harness/session/jsonl/fork.js +264 -0
  157. package/dist/harness/session/jsonl/fork.js.map +1 -0
  158. package/dist/harness/session/jsonl/io.d.ts +14 -0
  159. package/dist/harness/session/jsonl/io.d.ts.map +1 -0
  160. package/dist/harness/session/jsonl/io.js +86 -0
  161. package/dist/harness/session/jsonl/io.js.map +1 -0
  162. package/dist/harness/session/jsonl/legacy-v3.d.ts +33 -8
  163. package/dist/harness/session/jsonl/legacy-v3.d.ts.map +1 -1
  164. package/dist/harness/session/jsonl/legacy-v3.js +285 -206
  165. package/dist/harness/session/jsonl/legacy-v3.js.map +1 -1
  166. package/dist/harness/session/jsonl/repo.d.ts +2 -1
  167. package/dist/harness/session/jsonl/repo.d.ts.map +1 -1
  168. package/dist/harness/session/jsonl/repo.js +36 -20
  169. package/dist/harness/session/jsonl/repo.js.map +1 -1
  170. package/dist/harness/session/jsonl/storage.d.ts +4 -5
  171. package/dist/harness/session/jsonl/storage.d.ts.map +1 -1
  172. package/dist/harness/session/jsonl/storage.js +39 -113
  173. package/dist/harness/session/jsonl/storage.js.map +1 -1
  174. package/dist/harness/session/memory.d.ts +3 -5
  175. package/dist/harness/session/memory.d.ts.map +1 -1
  176. package/dist/harness/session/memory.js +9 -14
  177. package/dist/harness/session/memory.js.map +1 -1
  178. package/dist/harness/session/testing/conformance/session-repo.d.ts +2 -0
  179. package/dist/harness/session/testing/conformance/session-repo.d.ts.map +1 -1
  180. package/dist/harness/session/testing/conformance/session-repo.js +220 -1
  181. package/dist/harness/session/testing/conformance/session-repo.js.map +1 -1
  182. package/dist/harness/session/testing/index.d.ts +1 -1
  183. package/dist/harness/session/testing/index.d.ts.map +1 -1
  184. package/dist/harness/session/testing/index.js +1 -1
  185. package/dist/harness/session/testing/index.js.map +1 -1
  186. package/dist/harness/session/types.d.ts +1 -0
  187. package/dist/harness/session/types.d.ts.map +1 -1
  188. package/dist/harness/session/types.js.map +1 -1
  189. package/dist/harness/types.d.ts +14 -0
  190. package/dist/harness/types.d.ts.map +1 -1
  191. package/dist/harness/types.js.map +1 -1
  192. package/dist/proxy.d.ts +2 -2
  193. package/dist/proxy.d.ts.map +1 -1
  194. package/dist/proxy.js.map +1 -1
  195. package/dist/types.d.ts +30 -14
  196. package/dist/types.d.ts.map +1 -1
  197. package/dist/types.js.map +1 -1
  198. package/package.json +10 -6
@@ -0,0 +1,263 @@
1
+ import { type Context } from "@earendil-works/chord";
2
+ import { type Op, type Tracker } from "@earendil-works/chord/delta";
3
+ import { type AnyKind, type Checkpoint, type ContextView, type Conversation, type ConversationSpec, type CoreTx, type DocRef, type Entry, type EntryInput, type EntryKind, type EntryRef, type EntryScan, type Id, type Input, type InputOf, type Invoker, type JsonObject, type JsonValue, type Namespace, type NamespaceRegistration, type NewEntry, type OwnedConversationSpec, type RewindableState, type SendInput, type Seq, type SessionState, type SlotOf, type StickyState, type Storage, type Task, type TaskOf, type TaskRef, type TaskScan, type TaskSpec, type ToolSlot, type ViewEvent, type Write } from "./types.ts";
4
+ /** Remove matching elements from a tracked array IN PLACE (never `arr = arr.filter(...)` on a tracked doc). */
5
+ export declare function removeWhere<T>(arr: T[], pred: (item: T) => boolean): void;
6
+ export declare const isCoreKind: (name: string) => boolean;
7
+ /** Declared config defaults, derived from the registered kinds. Nothing is duplicated elsewhere. */
8
+ export declare class Defaults {
9
+ readonly rewindable: JsonObject;
10
+ readonly sticky: JsonObject;
11
+ readonly route: Map<string, "rewindable" | "sticky">;
12
+ private readonly owners;
13
+ constructor(kinds: Iterable<AnyKind>);
14
+ register(kind: AnyKind): void;
15
+ unregister(kind: AnyKind): void;
16
+ validate(key: string, value: unknown): value is JsonValue;
17
+ validateSeed(doc: "rewindable" | "sticky", seed: object): JsonObject;
18
+ /** Fill declared keys that are absent (never `??`: a stored null is a value). */
19
+ fill(doc: "rewindable" | "sticky", target: JsonObject): void;
20
+ freshRewindable(over?: Partial<RewindableState>, preservePlugins?: boolean): RewindableState;
21
+ freshSticky(over?: Partial<StickyState>): StickyState;
22
+ }
23
+ declare class Docs {
24
+ private readonly trackers;
25
+ private readonly storage;
26
+ private readonly defaults;
27
+ /** Bytes of ops written since the last base, per document. Drives rebase+truncate. */
28
+ readonly sinceBase: Map<string, number>;
29
+ constructor(storage: Storage, defaults: Defaults);
30
+ noteOps(ref: DocRef, ops: Op[]): void;
31
+ requestBase(ref: DocRef): void;
32
+ get(ref: DocRef, ctx: Context): Promise<Tracker<object>>;
33
+ evict(ref: DocRef): void;
34
+ peek(ref: DocRef): Tracker<object> | undefined;
35
+ loaded(ref: DocRef): JsonObject | undefined;
36
+ adopt(ref: DocRef, tracker: Tracker<object>): void;
37
+ }
38
+ export interface CommitChanges {
39
+ entries: Entry[];
40
+ tasks: Task[];
41
+ inputs: Input[];
42
+ conversations: Conversation[];
43
+ docs: {
44
+ ref: DocRef;
45
+ ops: Op[];
46
+ }[];
47
+ events: {
48
+ conversationId: Id;
49
+ event: ViewEvent;
50
+ }[];
51
+ }
52
+ /** Session-side conversation index: owner/parent graph for subtree checks and ancestry. */
53
+ export interface ConversationIndex {
54
+ get(id: Id): Conversation | undefined;
55
+ /** Conversation ids in the subtree rooted at `root` (inclusive), via ownership. */
56
+ subtree(root: Id): Set<Id>;
57
+ /** Conversation ids from the root down to `id` (exclusive of `id`) via ownership. */
58
+ ancestors(id: Id): Id[];
59
+ }
60
+ declare class TxImpl implements CoreTx {
61
+ private readonly writes;
62
+ private readonly touched;
63
+ private readonly membrane;
64
+ private readonly createdTasks;
65
+ private readonly createdEntries;
66
+ private readonly createdConversations;
67
+ private readonly inputsById;
68
+ private readonly inputsByRequest;
69
+ /**
70
+ * Transaction-local identity cache for heterogeneous namespace façades.
71
+ * Keys are current namespace token objects, validated before lookup; values are
72
+ * merged membrane wrappers. TxImpl owns the map and revoke() invalidates every value.
73
+ */
74
+ private readonly namespaceViews;
75
+ private readonly changedConfig;
76
+ private readonly wroteEntries;
77
+ private wroteTasks;
78
+ private poisoned;
79
+ private surfaceActive;
80
+ /** Set while a terminal closure runs: the invoker's task counts as gone for busy(). */
81
+ closing: boolean;
82
+ readonly changes: CommitChanges;
83
+ readonly invoker: Invoker;
84
+ private readonly storage;
85
+ private readonly docs;
86
+ private readonly defaults;
87
+ private readonly kinds;
88
+ private readonly namespaces;
89
+ private readonly liveTasks;
90
+ private readonly conversations;
91
+ private readonly now;
92
+ private readonly ctx;
93
+ constructor(invoker: Invoker, storage: Storage, docs: Docs, defaults: Defaults, kinds: ReadonlyMap<string, AnyKind>, namespaces: ReadonlyMap<string, NamespaceRegistration>, liveTasks: ReadonlyMap<Id, Task>, conversations: ConversationIndex, now: () => number, ctx: Context);
94
+ callbackSurface(): TxImpl;
95
+ closeSurface(): void;
96
+ private assertSurfaceActive;
97
+ private get core();
98
+ private assertCore;
99
+ private assertNotHost;
100
+ /** A task may touch its own conversation and the subtree it owns. The host and core may touch anything. */
101
+ private inScope;
102
+ private assertScope;
103
+ private assertEntryScope;
104
+ private poison;
105
+ private assertNoEntryWrites;
106
+ private assertNoTaskWrites;
107
+ get poison_(): Error | undefined;
108
+ conversation(id: Id): Promise<Conversation | undefined>;
109
+ entry(id: Id): Promise<Entry | undefined>;
110
+ entry<E extends Entry>(kind: EntryKind<E>, id: Id): Promise<E | undefined>;
111
+ entries(ids: readonly Id[]): Promise<Map<number, Entry>>;
112
+ newestEntry(conversationId: Id, opts?: {
113
+ kind?: string;
114
+ withHead?: boolean;
115
+ }): Promise<Entry | undefined>;
116
+ newestEntry<E extends Entry>(conversationId: Id, kind: EntryKind<E>): Promise<E | undefined>;
117
+ scanEntries(scan: EntryScan): Promise<Entry[]>;
118
+ context(conversationId: Id, at?: Id): Promise<ContextView>;
119
+ task(id: Id): Promise<Task | undefined>;
120
+ task<K extends AnyKind>(ref: TaskRef<K>): Promise<TaskOf<K> | undefined>;
121
+ tasks(scan: TaskScan): Promise<Task<import("@earendil-works/chord").JsonValue, Checkpoint>[]>;
122
+ input(id: Id): Promise<Input | undefined>;
123
+ private inputByRequest;
124
+ rewindableAsOf(conversationId: Id, at: Id): Promise<RewindableState | undefined>;
125
+ snapshot(ref: {
126
+ doc: "rewindable";
127
+ conversationId: Id;
128
+ }): RewindableState;
129
+ snapshot(ref: {
130
+ doc: "sticky";
131
+ conversationId: Id;
132
+ }): StickyState;
133
+ snapshot(ref: {
134
+ doc: "session";
135
+ }): SessionState;
136
+ private doc;
137
+ private view;
138
+ preload(refs: DocRef[]): Promise<void>;
139
+ rewindable(conversationId: Id): RewindableState;
140
+ sticky(conversationId: Id): StickyState;
141
+ session(): SessionState;
142
+ /** Internal, unchecked. */
143
+ private raw;
144
+ plugins<T extends JsonObject>(namespace: Namespace<T>): T;
145
+ emit<T extends JsonObject>(namespace: Namespace<T>, name: string, data: JsonValue): void;
146
+ emit(event: ViewEvent): void;
147
+ private invocationConversationId;
148
+ config(conversationId: Id): {
149
+ get: (key: string) => import("@earendil-works/chord").JsonValue | undefined;
150
+ set: (key: string, value: import("@earendil-works/chord").JsonValue) => void;
151
+ reset: (key: string) => void;
152
+ };
153
+ slot<K extends AnyKind>(ref: TaskRef<K>): SlotOf<K>;
154
+ toolSlot(task: {
155
+ conversationId: Id;
156
+ input: {
157
+ index: number;
158
+ };
159
+ }): ToolSlot;
160
+ appendEntry(conversationId: Id, entry: NewEntry): Id;
161
+ appendEntry<E extends Entry>(conversationId: Id, kind: EntryKind<E>, entry: EntryInput<E>): EntryRef<E>;
162
+ private appendEntryInternal;
163
+ write(conversationId: Id, entry: NewEntry): Promise<Id>;
164
+ write<E extends Entry>(conversationId: Id, kind: EntryKind<E>, entry: EntryInput<E>): Promise<Id>;
165
+ checkpoint<C extends Checkpoint>(value: C): void;
166
+ /** Kernel-internal: replace a task's mutable fields. Persists only what changed. */
167
+ setTask(task: Task): void;
168
+ setTaskInternalForControl(task: Task): void;
169
+ private setTaskInternal;
170
+ createTask(spec: TaskSpec): Id;
171
+ createTask<K extends AnyKind>(kind: K, input: InputOf<K>, opts?: {
172
+ conversationId?: Id;
173
+ background?: true;
174
+ after?: Id[];
175
+ }): TaskRef<K>;
176
+ private createTaskInternal;
177
+ private hasLiveKind;
178
+ createConversation(spec: ConversationSpec): Id;
179
+ createForkConversation(spec: ConversationSpec): Id;
180
+ createOwnedConversation(ownerTaskId: Id, sourceConversationId: Id, spec: OwnedConversationSpec): Promise<Id>;
181
+ private insertConversation;
182
+ private seedDoc;
183
+ markTask(id: Id): void;
184
+ /** Prospective: live turn tasks, minus this transaction's terminals and the closing task, plus this transaction's new turn tasks. */
185
+ busy(conversationId: Id): boolean;
186
+ private putInput;
187
+ send(conversationId: Id, input: SendInput): Promise<Id>;
188
+ private setInput;
189
+ /** Kernel: withdraw a queued input. */
190
+ withdrawInput(id: Id): Promise<"aborted" | "already_placed" | "not_found">;
191
+ resolveInputs(ids: readonly Id[], resolution: {
192
+ status: "done";
193
+ answer: Id;
194
+ } | {
195
+ status: "unanswered";
196
+ reason: NonNullable<Input["reason"]>;
197
+ detail?: string;
198
+ }): Promise<void>;
199
+ /**
200
+ * Boundary placement (pico §9.5). No storage scan: `headBoundary` is the newest head as the
201
+ * caller knows it, and it advances locally as same-batch self-heads are placed.
202
+ */
203
+ boundary(conversationId: Id, at: "postTools" | "final", headBoundary: Id | undefined): Promise<{
204
+ triggers: Id[];
205
+ terminated: boolean;
206
+ }>;
207
+ finish(): Write[];
208
+ /** Every wrapper handed out by this transaction throws from now on. */
209
+ revoke(): void;
210
+ evictTouched(): void;
211
+ }
212
+ export declare class NestedLineOperation extends Error {
213
+ constructor();
214
+ }
215
+ export interface CommitResult<T> {
216
+ value: T;
217
+ seq: Seq | undefined;
218
+ changes: CommitChanges;
219
+ }
220
+ export interface TransactionControl {
221
+ setTask(task: Task): void;
222
+ }
223
+ export declare class Session {
224
+ readonly storage: Storage;
225
+ readonly kinds: ReadonlyMap<string, AnyKind>;
226
+ readonly namespaces: ReadonlyMap<string, NamespaceRegistration>;
227
+ private tail;
228
+ private closed;
229
+ private fault;
230
+ private readonly now;
231
+ readonly docs: Docs;
232
+ readonly defaults: Defaults;
233
+ readonly liveTasks: Map<number, Task<import("@earendil-works/chord").JsonValue, Checkpoint>>;
234
+ /** Owner/parent graph, loaded at open and maintained on every commit. */
235
+ readonly conversationRecords: Map<number, Conversation>;
236
+ readonly lineListeners: Set<(result: CommitResult<unknown>) => void>;
237
+ readonly listeners: Set<(r: CommitResult<unknown>) => void>;
238
+ /** Errors from listeners and other post-commit work; never surface to the writer. */
239
+ onReport: (error: unknown) => void;
240
+ constructor(storage: Storage, kinds: ReadonlyMap<string, AnyKind>, namespaces: ReadonlyMap<string, NamespaceRegistration>, now?: () => number);
241
+ readonly index: ConversationIndex;
242
+ /** Owner tasks that are terminal but whose conversations still exist (ancestry after reopen). */
243
+ readonly ownerTaskCache: Map<number, Task<import("@earendil-works/chord").JsonValue, Checkpoint>>;
244
+ commit<T>(invoker: Invoker, fn: (tx: TxImpl, ctx: Context, control: TransactionControl) => T | Promise<T>, ctx: Context, opts?: {
245
+ docs?: DocRef[];
246
+ closing?: boolean;
247
+ }): Promise<CommitResult<T>>;
248
+ /** Read-only line operation without a transaction. */
249
+ read<T>(fn: (storage: Storage, ctx: Context) => Promise<T>, ctx: Context): Promise<T>;
250
+ /** Generic line operation (waiter registration and asynchronous lifecycle work). */
251
+ onLine<T>(fn: (ctx: Context) => T | Promise<T>, ctx: Context): Promise<T>;
252
+ static readonly STICKY_BASE_BUDGET: number;
253
+ /** After a task terminalizes: retire its slot; base + truncate when idle or over budget. Truncation runs on the line. */
254
+ retire(task: Task, ctx: Context): Promise<void>;
255
+ fork(parentId: Id, at: Id | "start", spec: Omit<ConversationSpec, "parent">, ctx: Context): Promise<Id>;
256
+ close(ctx: Context): Promise<void>;
257
+ loadedDocument(ref: DocRef): JsonObject | undefined;
258
+ private applyChanges;
259
+ private enter;
260
+ private assertUsable;
261
+ }
262
+ export type { TxImpl };
263
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/harness/pico3/session.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Context, isJsonValue } from \"@earendil-works/chord\";\nimport { createContextKey, withContextValue, withoutAbortSignal } from \"@earendil-works/chord/context\";\nimport { isBase, type Op, type Tracker, track } from \"@earendil-works/chord/delta\";\nimport { deriveContext } from \"./context.ts\";\nimport { Membrane } from \"./membrane.ts\";\nimport {\n\ttype AnyKind,\n\ttype Checkpoint,\n\tClosed,\n\tCollapseInProgress,\n\ttype ContextView,\n\ttype Conversation,\n\tConversationBusy,\n\ttype ConversationSpec,\n\ttype CoreTx,\n\ttype DocRef,\n\ttype Entry,\n\ttype EntryInput,\n\ttype EntryKind,\n\ttype EntryRef,\n\ttype EntryScan,\n\tFaulted,\n\tForbidden,\n\tGenerationInProgress,\n\ttype Id,\n\ttype Input,\n\ttype InputOf,\n\ttype Invoker,\n\ttype JsonObject,\n\ttype JsonValue,\n\ttype Namespace,\n\ttype NamespaceRegistration,\n\ttype NewEntry,\n\ttype OwnedConversationSpec,\n\ttype QueuedInput,\n\tReadAfterWrite,\n\ttype RewindableState,\n\ttype SendInput,\n\ttype Seq,\n\ttype SessionState,\n\ttype SlotOf,\n\ttype StickyState,\n\ttype Storage,\n\ttype Task,\n\ttype TaskOf,\n\ttype TaskPatch,\n\ttype TaskRef,\n\ttype TaskScan,\n\ttype TaskSpec,\n\ttype ToolSlot,\n\ttype ViewEvent,\n\ttype Write,\n} from \"./types.ts\";\n\n// ---------------------------------------------------------------------------\n// Document cache and defaults\n// ---------------------------------------------------------------------------\n\nconst docKey = (ref: DocRef) => (ref.doc === \"session\" ? \"session\" : `${ref.doc}:${ref.conversationId}`);\n\n/** Remove matching elements from a tracked array IN PLACE (never `arr = arr.filter(...)` on a tracked doc). */\nexport function removeWhere<T>(arr: T[], pred: (item: T) => boolean): void {\n\tfor (let i = arr.length - 1; i >= 0; i--) if (pred(arr[i]!)) arr.splice(i, 1);\n}\n\nconst CORE_KINDS = new Set([\"pi.generation\", \"pi.tool\", \"pi.post_tools\", \"pi.collapse\"]);\nconst CORE_CONFIG_VALIDATORS: Readonly<Record<string, (value: JsonValue) => boolean>> = {\n\tmodel: (value) => exactObject(value, [\"provider\", \"modelId\"]) && value.provider !== \"\" && value.modelId !== \"\",\n\tthinkingLevel: (value) =>\n\t\ttypeof value === \"string\" && [\"off\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"].includes(value),\n\tselectedTools: (value) => Array.isArray(value) && value.every((name) => typeof name === \"string\"),\n\tprofile: (value) => typeof value === \"string\",\n\tretry: (value) =>\n\t\texactObject(value, [\"enabled\", \"maxRetries\", \"baseDelayMs\"], [\"maxAgentDelayMs\"]) &&\n\t\ttypeof value.enabled === \"boolean\" &&\n\t\ttypeof value.maxRetries === \"number\" &&\n\t\tNumber.isSafeInteger(value.maxRetries) &&\n\t\tvalue.maxRetries >= 0 &&\n\t\tfiniteNonnegative(value.baseDelayMs) &&\n\t\t(value.maxAgentDelayMs === undefined || finiteNonnegative(value.maxAgentDelayMs)),\n\tthreshold: (value) => typeof value === \"number\" && Number.isFinite(value),\n\tkeepRecent: finiteNonnegative,\n\tsteeringMode: (value) => value === \"all\" || value === \"one-at-a-time\",\n\tfollowUpMode: (value) => value === \"all\" || value === \"one-at-a-time\",\n};\n\nfunction finiteNonnegative(value: JsonValue | undefined): boolean {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value >= 0;\n}\nfunction exactObject(\n\tvalue: JsonValue,\n\trequired: readonly string[],\n\toptional: readonly string[] = [],\n): value is JsonObject {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst keys = Object.keys(value);\n\treturn (\n\t\trequired.every((key) => Object.hasOwn(value, key)) &&\n\t\tkeys.every((key) => required.includes(key) || optional.includes(key))\n\t);\n}\nexport const isCoreKind = (name: string) => CORE_KINDS.has(name);\n\n/** Declared config defaults, derived from the registered kinds. Nothing is duplicated elsewhere. */\nexport class Defaults {\n\treadonly rewindable: JsonObject = {};\n\treadonly sticky: JsonObject = {};\n\treadonly route = new Map<string, \"rewindable\" | \"sticky\">();\n\tprivate readonly owners = new Map<string, AnyKind>();\n\tconstructor(kinds: Iterable<AnyKind>) {\n\t\tfor (const kind of kinds) this.register(kind);\n\t}\n\tregister(kind: AnyKind): void {\n\t\tconst declarations = ([\"rewindable\", \"sticky\"] as const).flatMap((doc) =>\n\t\t\tObject.entries(kind.config?.[doc] ?? {}).map(([key, value]) => ({ doc, key, value })),\n\t\t);\n\t\tconst local = new Set<string>();\n\t\tfor (const { key } of declarations) {\n\t\t\tif (local.has(key) || this.route.has(key))\n\t\t\t\tthrow new Error(`config key \"${key}\" declared by more than one kind`);\n\t\t\tlocal.add(key);\n\t\t}\n\t\tfor (const { doc, key, value } of declarations) {\n\t\t\tthis.route.set(key, doc);\n\t\t\tthis.owners.set(key, kind);\n\t\t\tif (value !== undefined) this[doc][key] = structuredClone(value);\n\t\t}\n\t}\n\tunregister(kind: AnyKind): void {\n\t\tfor (const [key, owner] of this.owners) {\n\t\t\tif (owner !== kind) continue;\n\t\t\tthis.owners.delete(key);\n\t\t\tconst doc = this.route.get(key);\n\t\t\tthis.route.delete(key);\n\t\t\tif (doc !== undefined) delete this[doc][key];\n\t\t}\n\t}\n\tvalidate(key: string, value: unknown): value is JsonValue {\n\t\tif (!isJsonValue(value)) return false;\n\t\tconst core = CORE_CONFIG_VALIDATORS[key];\n\t\tif (core !== undefined) return core(value);\n\t\treturn this.route.has(key);\n\t}\n\tvalidateSeed(doc: \"rewindable\" | \"sticky\", seed: object): JsonObject {\n\t\tconst out: JsonObject = {};\n\t\tfor (const [key, value] of Object.entries(seed)) {\n\t\t\tif (value === undefined) continue;\n\t\t\tif (this.route.get(key) !== doc || !this.validate(key, value))\n\t\t\t\tthrow new TypeError(`invalid ${doc} config value for \"${key}\"`);\n\t\t\tout[key] = structuredClone(value);\n\t\t}\n\t\treturn out;\n\t}\n\t/** Fill declared keys that are absent (never `??`: a stored null is a value). */\n\tfill(doc: \"rewindable\" | \"sticky\", target: JsonObject) {\n\t\tfor (const [key, value] of Object.entries(this[doc])) if (!(key in target)) target[key] = structuredClone(value);\n\t}\n\tfreshRewindable(over: Partial<RewindableState> = {}, preservePlugins = false): RewindableState {\n\t\tconst base: JsonObject = { plugins: {} };\n\t\tthis.fill(\"rewindable\", base);\n\t\tconst { plugins, ...raw } = over;\n\t\tconst rest = preservePlugins ? (raw as JsonObject) : this.validateSeed(\"rewindable\", raw);\n\t\treturn {\n\t\t\t...base,\n\t\t\t...rest,\n\t\t\t...(preservePlugins && plugins !== undefined ? { plugins: structuredClone(plugins) } : {}),\n\t\t} as unknown as RewindableState;\n\t}\n\tfreshSticky(over: Partial<StickyState> = {}): StickyState {\n\t\tconst base: JsonObject = { inbox: [], turn: { tools: [] }, tasks: {}, plugins: {} };\n\t\tthis.fill(\"sticky\", base);\n\t\tconst { inbox: _inbox, turn: _turn, tasks: _tasks, plugins: _plugins, ...raw } = over;\n\t\treturn { ...base, ...this.validateSeed(\"sticky\", raw) } as unknown as StickyState;\n\t}\n}\n\nclass Docs {\n\tprivate readonly trackers = new Map<string, Tracker<object>>();\n\tprivate readonly storage: Storage;\n\tprivate readonly defaults: Defaults;\n\t/** Bytes of ops written since the last base, per document. Drives rebase+truncate. */\n\treadonly sinceBase = new Map<string, number>();\n\tconstructor(storage: Storage, defaults: Defaults) {\n\t\tthis.storage = storage;\n\t\tthis.defaults = defaults;\n\t}\n\tnoteOps(ref: DocRef, ops: Op[]) {\n\t\tconst key = docKey(ref);\n\t\tthis.sinceBase.set(key, isBase(ops) ? 0 : (this.sinceBase.get(key) ?? 0) + JSON.stringify(ops).length);\n\t}\n\trequestBase(ref: DocRef) {\n\t\tthis.trackers.get(docKey(ref))?.rebase();\n\t}\n\tasync get(ref: DocRef, ctx: Context): Promise<Tracker<object>> {\n\t\tconst key = docKey(ref);\n\t\tconst cached = this.trackers.get(key);\n\t\tif (cached !== undefined) return cached;\n\t\tconst stored = await this.storage.doc(ref, ctx);\n\t\tif (stored === undefined) throw new Error(`document ${key} does not exist`);\n\t\tif (ref.doc !== \"session\") this.defaults.fill(ref.doc, stored); // declared defaults are visible without being persisted\n\t\tconst t = track(stored as object);\n\t\tt.flush(); // consume the synthetic first flush; never persisted\n\t\tthis.trackers.set(key, t);\n\t\treturn t;\n\t}\n\tevict(ref: DocRef) {\n\t\tthis.trackers.delete(docKey(ref));\n\t}\n\tpeek(ref: DocRef): Tracker<object> | undefined {\n\t\treturn this.trackers.get(docKey(ref));\n\t}\n\tloaded(ref: DocRef): JsonObject | undefined {\n\t\treturn this.trackers.get(docKey(ref))?.target as JsonObject | undefined;\n\t}\n\tadopt(ref: DocRef, tracker: Tracker<object>) {\n\t\tthis.trackers.set(docKey(ref), tracker);\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Transaction: one implementation, capability-checked per method.\n// ---------------------------------------------------------------------------\n\nexport interface CommitChanges {\n\tentries: Entry[];\n\ttasks: Task[];\n\tinputs: Input[];\n\tconversations: Conversation[];\n\tdocs: { ref: DocRef; ops: Op[] }[];\n\tevents: { conversationId: Id; event: ViewEvent }[];\n}\n\n/** Session-side conversation index: owner/parent graph for subtree checks and ancestry. */\nexport interface ConversationIndex {\n\tget(id: Id): Conversation | undefined;\n\t/** Conversation ids in the subtree rooted at `root` (inclusive), via ownership. */\n\tsubtree(root: Id): Set<Id>;\n\t/** Conversation ids from the root down to `id` (exclusive of `id`) via ownership. */\n\tancestors(id: Id): Id[];\n}\n\nconst HOST_TX_METHODS = [\n\t\"conversation\",\n\t\"entry\",\n\t\"entries\",\n\t\"newestEntry\",\n\t\"scanEntries\",\n\t\"context\",\n\t\"task\",\n\t\"tasks\",\n\t\"input\",\n\t\"rewindableAsOf\",\n\t\"snapshot\",\n\t\"plugins\",\n\t\"emit\",\n\t\"config\",\n\t\"write\",\n\t\"createTask\",\n\t\"createConversation\",\n] as const;\nconst TASK_TX_METHODS = [\"checkpoint\", \"slot\"] as const;\nconst CORE_TX_METHODS = [\n\t\"rewindable\",\n\t\"sticky\",\n\t\"session\",\n\t\"toolSlot\",\n\t\"appendEntry\",\n\t\"send\",\n\t\"resolveInputs\",\n\t\"boundary\",\n\t\"markTask\",\n\t\"setTask\",\n\t\"withdrawInput\",\n\t\"createOwnedConversation\",\n\t\"createForkConversation\",\n] as const;\nconst CALLBACK_TX_METHODS = new Set<string>([...HOST_TX_METHODS, ...TASK_TX_METHODS, ...CORE_TX_METHODS]);\n\nclass TxImpl implements CoreTx {\n\tprivate readonly writes: Write[] = [];\n\tprivate readonly touched = new Map<string, { ref: DocRef; tracker: Tracker<object> }>();\n\tprivate readonly membrane = new Membrane(\"tx\");\n\t// Overlays for direct reads: complete for the tables a closure reads after writing.\n\tprivate readonly createdTasks = new Map<Id, Task>();\n\tprivate readonly createdEntries = new Map<Id, Entry>();\n\tprivate readonly createdConversations = new Map<Id, Conversation>();\n\tprivate readonly inputsById = new Map<Id, Input>();\n\tprivate readonly inputsByRequest = new Map<string, Input>();\n\t/**\n\t * Transaction-local identity cache for heterogeneous namespace façades.\n\t * Keys are current namespace token objects, validated before lookup; values are\n\t * merged membrane wrappers. TxImpl owns the map and revoke() invalidates every value.\n\t */\n\tprivate readonly namespaceViews = new Map<object, object>();\n\tprivate readonly changedConfig = new Map<Id, Set<string>>();\n\t// Domains written: scans over them reject.\n\tprivate readonly wroteEntries = new Set<Id>();\n\tprivate wroteTasks = false;\n\tprivate poisoned: Error | undefined;\n\tprivate surfaceActive = true;\n\t/** Set while a terminal closure runs: the invoker's task counts as gone for busy(). */\n\tclosing = false;\n\treadonly changes: CommitChanges = { entries: [], tasks: [], inputs: [], conversations: [], docs: [], events: [] };\n\treadonly invoker: Invoker;\n\tprivate readonly storage: Storage;\n\tprivate readonly docs: Docs;\n\tprivate readonly defaults: Defaults;\n\tprivate readonly kinds: ReadonlyMap<string, AnyKind>;\n\tprivate readonly namespaces: ReadonlyMap<string, NamespaceRegistration>;\n\tprivate readonly liveTasks: ReadonlyMap<Id, Task>;\n\tprivate readonly conversations: ConversationIndex;\n\tprivate readonly now: () => number;\n\tprivate readonly ctx: Context;\n\n\tconstructor(\n\t\tinvoker: Invoker,\n\t\tstorage: Storage,\n\t\tdocs: Docs,\n\t\tdefaults: Defaults,\n\t\tkinds: ReadonlyMap<string, AnyKind>,\n\t\tnamespaces: ReadonlyMap<string, NamespaceRegistration>,\n\t\tliveTasks: ReadonlyMap<Id, Task>,\n\t\tconversations: ConversationIndex,\n\t\tnow: () => number,\n\t\tctx: Context,\n\t) {\n\t\tthis.invoker = invoker;\n\t\tthis.storage = storage;\n\t\tthis.docs = docs;\n\t\tthis.defaults = defaults;\n\t\tthis.kinds = kinds;\n\t\tthis.namespaces = namespaces;\n\t\tthis.liveTasks = liveTasks;\n\t\tthis.conversations = conversations;\n\t\tthis.now = now;\n\t\tthis.ctx = ctx;\n\t}\n\n\tcallbackSurface(): TxImpl {\n\t\tconst implementation = this as unknown as Record<string, unknown>;\n\t\tconst methods = new Map<string, (...args: unknown[]) => unknown>();\n\t\tconst target = Object.freeze(Object.create(null)) as object;\n\t\treturn new Proxy(target, {\n\t\t\tget: (_target, key) => {\n\t\t\t\tif (typeof key !== \"string\" || !CALLBACK_TX_METHODS.has(key)) return undefined;\n\t\t\t\tconst cached = methods.get(key);\n\t\t\t\tif (cached !== undefined) return cached;\n\t\t\t\tconst method = implementation[key];\n\t\t\t\tif (typeof method !== \"function\") throw new Error(`transaction method ${key} is missing`);\n\t\t\t\tconst bound = (...args: unknown[]) => {\n\t\t\t\t\tthis.assertSurfaceActive();\n\t\t\t\t\treturn Reflect.apply(method, this, args);\n\t\t\t\t};\n\t\t\t\tmethods.set(key, bound);\n\t\t\t\treturn bound;\n\t\t\t},\n\t\t}) as unknown as TxImpl;\n\t}\n\tcloseSurface(): void {\n\t\tthis.surfaceActive = false;\n\t}\n\tprivate assertSurfaceActive(): void {\n\t\tif (!this.surfaceActive) throw new TypeError(\"transaction used outside its callback\");\n\t}\n\n\t// --- capability & scope -------------------------------------------------\n\n\tprivate get core() {\n\t\treturn this.invoker.type === \"kernel\" || (this.invoker.type === \"task\" && this.invoker.core);\n\t}\n\tprivate assertCore(what: string) {\n\t\tif (!this.core) throw new Forbidden(`${what}: core turn machinery only`);\n\t}\n\tprivate assertNotHost(what: string) {\n\t\tif (this.invoker.type !== \"task\") throw new Forbidden(`${what} outside a task`);\n\t}\n\t/** A task may touch its own conversation and the subtree it owns. The host and core may touch anything. */\n\tprivate inScope(conversationId: Id): boolean {\n\t\tif (this.invoker.type !== \"task\" || this.invoker.core) return true;\n\t\tif (conversationId === this.invoker.conversationId || this.createdConversations.has(conversationId)) return true;\n\t\tconst task = this.createdTasks.get(this.invoker.id) ?? this.liveTasks.get(this.invoker.id);\n\t\tfor (const root of task?.owns ?? []) if (this.conversations.subtree(root).has(conversationId)) return true;\n\t\treturn false;\n\t}\n\tprivate assertScope(conversationId: Id, what: string) {\n\t\tif (!this.inScope(conversationId))\n\t\t\tthrow new Forbidden(`${what}: conversation ${conversationId} is outside this task's subtree`);\n\t}\n\tprivate assertEntryScope(entry: Entry, what: string) {\n\t\tif (this.inScope(entry.conversationId)) return;\n\t\tif (this.invoker.type !== \"task\" || this.invoker.core)\n\t\t\tthrow new Forbidden(`${what}: entry ${entry.id} is outside this task's subtree`);\n\t\tconst candidates = new Set<Id>([this.invoker.conversationId, ...this.createdConversations.keys()]);\n\t\tconst task = this.createdTasks.get(this.invoker.id) ?? this.liveTasks.get(this.invoker.id);\n\t\tfor (const root of task?.owns ?? []) for (const id of this.conversations.subtree(root)) candidates.add(id);\n\t\tfor (const candidate of candidates) {\n\t\t\tlet conversation = this.createdConversations.get(candidate) ?? this.conversations.get(candidate);\n\t\t\twhile (conversation?.parent !== undefined) {\n\t\t\t\tif (conversation.parent.conversationId === entry.conversationId && entry.id <= conversation.parent.at)\n\t\t\t\t\treturn;\n\t\t\t\tconversation = this.conversations.get(conversation.parent.conversationId);\n\t\t\t}\n\t\t}\n\t\tthrow new Forbidden(`${what}: entry ${entry.id} is not visible from this task's subtree`);\n\t}\n\tprivate poison(error: Error): never {\n\t\tthis.poisoned ??= error;\n\t\tthrow error;\n\t}\n\tprivate assertNoEntryWrites(conversationId: Id, read: string) {\n\t\tif (this.wroteEntries.has(conversationId)) this.poison(new ReadAfterWrite(read, \"entry append\"));\n\t}\n\tprivate assertNoTaskWrites(read: string) {\n\t\tif (this.wroteTasks) this.poison(new ReadAfterWrite(read, \"task write\"));\n\t}\n\tget poison_(): Error | undefined {\n\t\treturn this.poisoned;\n\t}\n\n\t// --- reads (direct reads see this transaction's writes) -------------------\n\n\tasync conversation(id: Id) {\n\t\tthis.assertScope(id, \"conversation\");\n\t\treturn this.createdConversations.get(id) ?? (await this.storage.conversation(id, this.ctx));\n\t}\n\tentry(id: Id): Promise<Entry | undefined>;\n\tentry<E extends Entry>(kind: EntryKind<E>, id: Id): Promise<E | undefined>;\n\tasync entry(a: Id | EntryKind, b?: Id): Promise<Entry | undefined> {\n\t\tconst id = typeof a === \"number\" ? a : b!;\n\t\tconst e = this.createdEntries.get(id) ?? (await this.storage.entries([id], this.ctx)).get(id);\n\t\tif (e !== undefined) this.assertEntryScope(e, \"entry\");\n\t\treturn typeof a === \"number\" || a.is(e) ? e : undefined;\n\t}\n\tasync entries(ids: readonly Id[]) {\n\t\tconst out = await this.storage.entries(\n\t\t\tids.filter((id) => !this.createdEntries.has(id)),\n\t\t\tthis.ctx,\n\t\t);\n\t\tfor (const id of ids) {\n\t\t\tconst c = this.createdEntries.get(id);\n\t\t\tif (c) out.set(id, c);\n\t\t}\n\t\tfor (const entry of out.values()) this.assertEntryScope(entry, \"entries\");\n\t\treturn out;\n\t}\n\tnewestEntry(conversationId: Id, opts?: { kind?: string; withHead?: boolean }): Promise<Entry | undefined>;\n\tnewestEntry<E extends Entry>(conversationId: Id, kind: EntryKind<E>): Promise<E | undefined>;\n\tasync newestEntry(\n\t\tconversationId: Id,\n\t\topts: { kind?: string; withHead?: boolean } | EntryKind = {},\n\t): Promise<Entry | undefined> {\n\t\tthis.assertScope(conversationId, \"newestEntry\");\n\t\tthis.assertNoEntryWrites(conversationId, \"newestEntry\");\n\t\tconst scan = \"is\" in opts ? { kind: opts.kind } : opts;\n\t\tconst [e] = await this.storage.scanEntries({ conversationId, ...scan, limit: 1 }, this.ctx);\n\t\treturn e;\n\t}\n\tscanEntries(scan: EntryScan) {\n\t\tthis.assertScope(scan.conversationId, \"scanEntries\");\n\t\tthis.assertNoEntryWrites(scan.conversationId, \"scanEntries\");\n\t\treturn this.storage.scanEntries(scan, this.ctx);\n\t}\n\tasync context(conversationId: Id, at?: Id): Promise<ContextView> {\n\t\tthis.assertScope(conversationId, \"context\");\n\t\tthis.assertNoEntryWrites(conversationId, \"context\");\n\t\treturn deriveContext(this.storage, conversationId, at, this.ctx);\n\t}\n\ttask(id: Id): Promise<Task | undefined>;\n\ttask<K extends AnyKind>(ref: TaskRef<K>): Promise<TaskOf<K> | undefined>;\n\tasync task(a: Id | TaskRef): Promise<Task | undefined> {\n\t\tconst id = typeof a === \"number\" ? a : a.id;\n\t\tconst task = this.createdTasks.get(id) ?? this.liveTasks.get(id) ?? (await this.storage.task(id, this.ctx));\n\t\tif (task !== undefined) this.assertScope(task.conversationId, \"task\");\n\t\treturn task;\n\t}\n\tasync tasks(scan: TaskScan) {\n\t\tthis.assertNoTaskWrites(\"tasks\");\n\t\tif (scan.conversationId !== undefined) this.assertScope(scan.conversationId, \"tasks\");\n\t\tconst rows = await this.storage.scanTasks(scan, this.ctx);\n\t\tconst seen = new Set(rows.map((t) => t.id));\n\t\tfor (const t of this.liveTasks.values())\n\t\t\tif (\n\t\t\t\t!seen.has(t.id) &&\n\t\t\t\t(scan.conversationId === undefined || t.conversationId === scan.conversationId) &&\n\t\t\t\t(scan.kind === undefined || t.kind === scan.kind)\n\t\t\t)\n\t\t\t\trows.push(t);\n\t\treturn rows.filter((t) => scan.status === undefined || scan.status.includes(t.status));\n\t}\n\tasync input(id: Id) {\n\t\tconst input = this.inputsById.get(id) ?? (await this.storage.input(id, this.ctx));\n\t\tif (input !== undefined) this.assertScope(input.conversationId, \"input\");\n\t\treturn input;\n\t}\n\tprivate async inputByRequest(conversationId: Id, requestId: string) {\n\t\tthis.assertScope(conversationId, \"inputByRequest\");\n\t\treturn (\n\t\t\tthis.inputsByRequest.get(`${conversationId}:${requestId}`) ??\n\t\t\t(await this.storage.inputByRequest(conversationId, requestId, this.ctx))\n\t\t);\n\t}\n\tasync rewindableAsOf(conversationId: Id, at: Id): Promise<RewindableState | undefined> {\n\t\tthis.assertScope(conversationId, \"rewindableAsOf\");\n\t\treturn this.storage.docAsOf(conversationId, at, this.ctx) as Promise<RewindableState | undefined>;\n\t}\n\n\tsnapshot(ref: { doc: \"rewindable\"; conversationId: Id }): RewindableState;\n\tsnapshot(ref: { doc: \"sticky\"; conversationId: Id }): StickyState;\n\tsnapshot(ref: { doc: \"session\" }): SessionState;\n\tsnapshot(ref: DocRef): object {\n\t\tif (ref.doc !== \"session\") this.assertScope(ref.conversationId, \"snapshot\");\n\t\tconst value = structuredClone(this.doc(ref).target) as JsonObject;\n\t\tif (ref.doc !== \"session\") {\n\t\t\tthis.defaults.fill(ref.doc, value);\n\t\t\tif (this.invoker.type === \"task\") {\n\t\t\t\tfor (const [key, fallback] of Object.entries(this.invoker.kind.config?.[ref.doc] ?? {})) {\n\t\t\t\t\tif (!(key in value) && fallback !== undefined) value[key] = structuredClone(fallback);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn value;\n\t}\n\n\t// --- documents ----------------------------------------------------------\n\n\tprivate doc(ref: DocRef): Tracker<object> {\n\t\tconst key = docKey(ref);\n\t\tconst hit = this.touched.get(key);\n\t\tif (hit !== undefined) return hit.tracker;\n\t\tconst cached = this.docs.peek(ref);\n\t\tif (cached !== undefined) {\n\t\t\tthis.touched.set(key, { ref, tracker: cached });\n\t\t\treturn cached;\n\t\t}\n\t\tthrow new Error(`document ${key} not loaded; pass it in commit({ docs })`);\n\t}\n\tprivate view<T extends object>(ref: DocRef): T {\n\t\treturn this.membrane.wrap(this.doc(ref).state as T);\n\t}\n\tasync preload(refs: DocRef[]) {\n\t\tfor (const ref of refs) {\n\t\t\tconst key = docKey(ref);\n\t\t\tif (this.touched.has(key)) continue;\n\t\t\tthis.touched.set(key, { ref, tracker: await this.docs.get(ref, this.ctx) });\n\t\t}\n\t}\n\n\trewindable(conversationId: Id): RewindableState {\n\t\tthis.assertCore(\"rewindable document\");\n\t\treturn this.view<RewindableState>({ doc: \"rewindable\", conversationId });\n\t}\n\tsticky(conversationId: Id): StickyState {\n\t\tthis.assertCore(\"sticky document\");\n\t\treturn this.view<StickyState>({ doc: \"sticky\", conversationId });\n\t}\n\tsession(): SessionState {\n\t\tthis.assertCore(\"session document\");\n\t\treturn this.view<SessionState>({ doc: \"session\" });\n\t}\n\t/** Internal, unchecked. */\n\tprivate raw<T extends object>(ref: DocRef): T {\n\t\treturn this.view<T>(ref);\n\t}\n\n\tplugins<T extends JsonObject>(namespace: Namespace<T>): T {\n\t\tconst registration = this.namespaces.get(namespace.id);\n\t\tif (registration?.token !== namespace) throw new Forbidden(`namespace \"${namespace.id}\" is stale`);\n\t\tconst cached = this.namespaceViews.get(namespace);\n\t\tif (cached !== undefined) return cached as T;\n\t\tconst conversationId = this.invocationConversationId(`plugins(${namespace.id})`);\n\t\tconst target = {};\n\t\tfor (const [key, doc] of registration.routes) {\n\t\t\tconst ref: DocRef = doc === \"session\" ? { doc } : { doc, conversationId };\n\t\t\tconst document = this.raw<{ plugins: { [key: string]: JsonObject } }>(ref);\n\t\t\tif (!Object.hasOwn(document.plugins, namespace.id)) document.plugins[namespace.id] = {};\n\t\t\tconst slice = document.plugins[namespace.id]!;\n\t\t\tconst defaultValue = registration.defaults[doc][key];\n\t\t\tif (!(key in slice) && defaultValue !== undefined) slice[key] = plain(defaultValue);\n\t\t\tObject.defineProperty(target, key, {\n\t\t\t\tenumerable: true,\n\t\t\t\tget: () => slice[key],\n\t\t\t\tset: (value: JsonValue) => {\n\t\t\t\t\tslice[key] = value;\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t\tObject.preventExtensions(target);\n\t\tconst view = this.membrane.wrap(target);\n\t\tthis.namespaceViews.set(namespace, view);\n\t\treturn view as T;\n\t}\n\n\temit<T extends JsonObject>(namespace: Namespace<T>, name: string, data: JsonValue): void;\n\temit(event: ViewEvent): void;\n\temit<T extends JsonObject>(namespaceOrEvent: Namespace<T> | ViewEvent, name?: string, data?: JsonValue): void {\n\t\tconst conversationId = this.invocationConversationId(\"emit\");\n\t\tif (\"id\" in namespaceOrEvent && \"unregister\" in namespaceOrEvent) {\n\t\t\tif (this.namespaces.get(namespaceOrEvent.id)?.token !== namespaceOrEvent)\n\t\t\t\tthrow new Forbidden(`namespace \"${namespaceOrEvent.id}\" is stale`);\n\t\t\tif (name === undefined || data === undefined || !/^[a-z][a-z0-9_.-]*$/i.test(name))\n\t\t\t\tthrow new Error(\"invalid plugin event\");\n\t\t\tthis.changes.events.push({\n\t\t\t\tconversationId,\n\t\t\t\tevent: { type: `plugin.${namespaceOrEvent.id}.${name}`, data: plain(data) },\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tthis.assertCore(\"core event\");\n\t\tthis.changes.events.push({ conversationId, event: plain(namespaceOrEvent) });\n\t}\n\n\tprivate invocationConversationId(what: string): Id {\n\t\tconst conversationId = this.invoker.conversationId;\n\t\tif (conversationId === undefined) throw new Forbidden(`${what}: no conversation is bound to this transaction`);\n\t\tthis.assertScope(conversationId, what);\n\t\treturn conversationId;\n\t}\n\n\tconfig(conversationId: Id) {\n\t\tthis.assertScope(conversationId, \"config\");\n\t\tconst definition = (key: string): { doc: \"rewindable\" | \"sticky\"; fallback: JsonValue | undefined } => {\n\t\t\tif (this.invoker.type === \"task\") {\n\t\t\t\tfor (const doc of [\"rewindable\", \"sticky\"] as const) {\n\t\t\t\t\tconst declared = this.invoker.kind.config?.[doc];\n\t\t\t\t\tif (declared !== undefined && Object.hasOwn(declared, key)) return { doc, fallback: declared[key] };\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst doc = this.defaults.route.get(key);\n\t\t\tif (doc === undefined) throw new Error(`unknown config key \"${key}\"`);\n\t\t\treturn { doc, fallback: this.defaults[doc][key] };\n\t\t};\n\t\tconst assertWritable = (key: string) => {\n\t\t\tif (this.invoker.type === \"task\" && !this.invoker.core)\n\t\t\t\tthrow new Forbidden(`config(${key}): ordinary tasks cannot write config`);\n\t\t};\n\t\treturn {\n\t\t\tget: (key: string) => {\n\t\t\t\tthis.assertSurfaceActive();\n\t\t\t\tconst { doc, fallback } = definition(key);\n\t\t\t\tconst document = this.doc({ doc, conversationId }).target as JsonObject;\n\t\t\t\treturn key in document ? structuredClone(document[key]) : structuredClone(fallback);\n\t\t\t},\n\t\t\tset: (key: string, value: JsonValue) => {\n\t\t\t\tthis.assertSurfaceActive();\n\t\t\t\tassertWritable(key);\n\t\t\t\tconst { doc } = definition(key);\n\t\t\t\tif (!this.defaults.validate(key, value)) throw new TypeError(`invalid config value for \"${key}\"`);\n\t\t\t\tthis.raw<JsonObject>({ doc, conversationId })[key] = structuredClone(value);\n\t\t\t\tconst keys = this.changedConfig.get(conversationId) ?? new Set<string>();\n\t\t\t\tkeys.add(key);\n\t\t\t\tthis.changedConfig.set(conversationId, keys);\n\t\t\t},\n\t\t\treset: (key: string) => {\n\t\t\t\tthis.assertSurfaceActive();\n\t\t\t\tassertWritable(key);\n\t\t\t\tconst { doc } = definition(key);\n\t\t\t\tdelete this.raw<JsonObject>({ doc, conversationId })[key];\n\t\t\t\tconst keys = this.changedConfig.get(conversationId) ?? new Set<string>();\n\t\t\t\tkeys.add(key);\n\t\t\t\tthis.changedConfig.set(conversationId, keys);\n\t\t\t},\n\t\t};\n\t}\n\n\tslot<K extends AnyKind>(ref: TaskRef<K>): SlotOf<K> {\n\t\tthis.assertNotHost(\"slot\");\n\t\tconst inv = this.invoker as Extract<Invoker, { type: \"task\" }>;\n\t\tconst task = this.liveTasks.get(ref.id) ?? this.createdTasks.get(ref.id);\n\t\tif (task === undefined) throw new Error(`task ${ref.id} is not live`);\n\t\tif (!inv.core && ref.id !== inv.id) throw new Forbidden(\"slot: another task's slot\");\n\t\tconst s = this.raw<StickyState>({ doc: \"sticky\", conversationId: task.conversationId });\n\t\tif (!(String(ref.id) in s.tasks)) s.tasks[ref.id] = (ref.kind.slot ?? (() => ({})))(task.input as never);\n\t\treturn s.tasks[ref.id] as never;\n\t}\n\ttoolSlot(task: { conversationId: Id; input: { index: number } }): ToolSlot {\n\t\tthis.assertCore(\"toolSlot\");\n\t\tconst slot = this.raw<StickyState>({ doc: \"sticky\", conversationId: task.conversationId }).turn.tools[\n\t\t\ttask.input.index\n\t\t];\n\t\tif (slot === undefined) throw new Error(`no tool slot at index ${task.input.index}`);\n\t\treturn slot;\n\t}\n\n\t// --- writes -------------------------------------------------------------\n\n\tappendEntry(conversationId: Id, entry: NewEntry): Id;\n\tappendEntry<E extends Entry>(conversationId: Id, kind: EntryKind<E>, entry: EntryInput<E>): EntryRef<E>;\n\tappendEntry(conversationId: Id, a: NewEntry | EntryKind, b?: object): Id | EntryRef {\n\t\tthis.assertCore(\"appendEntry\");\n\t\tif (\"is\" in a)\n\t\t\treturn {\n\t\t\t\tid: this.appendEntryInternal(conversationId, { ...(b as object), kind: a.kind } as NewEntry),\n\t\t\t\tkind: a,\n\t\t\t};\n\t\treturn this.appendEntryInternal(conversationId, a);\n\t}\n\tprivate appendEntryInternal(conversationId: Id, entry: NewEntry): Id {\n\t\tif (this.invoker.type === \"task\" && !this.liveTasks.has(this.invoker.id) && !this.closing)\n\t\t\tthrow new Forbidden(\"appendEntry from a task that is not live\");\n\t\tconst id = this.storage.mintId();\n\t\tconst head = entry.head === \"self\" ? id : entry.head;\n\t\tconst { head: _h, ...rest } = entry;\n\t\tconst record = plain<Entry>({\n\t\t\t...rest,\n\t\t\tid,\n\t\t\tconversationId,\n\t\t\t...(head === undefined ? {} : { head }),\n\t\t\t...(this.invoker.type === \"task\" ? { byTaskId: this.invoker.id } : {}),\n\t\t});\n\t\tvalidateEntry(record);\n\t\tthis.writes.push({ type: \"entry\", entry: record });\n\t\tthis.changes.entries.push(record);\n\t\tif (record.head !== undefined)\n\t\t\tthis.changes.events.push({ conversationId, event: { type: \"head.moved\", entry: record } });\n\t\tthis.changes.events.push({ conversationId, event: { type: \"entry.added\", entry: record } });\n\t\tthis.createdEntries.set(id, record);\n\t\tthis.wroteEntries.add(conversationId);\n\t\treturn id;\n\t}\n\n\twrite(conversationId: Id, entry: NewEntry): Promise<Id>;\n\twrite<E extends Entry>(conversationId: Id, kind: EntryKind<E>, entry: EntryInput<E>): Promise<Id>;\n\tasync write(conversationId: Id, a: NewEntry | EntryKind, b?: object): Promise<Id> {\n\t\tthis.assertScope(conversationId, \"write\");\n\t\tconst entry: NewEntry = \"is\" in a ? ({ ...(b as object), kind: a.kind } as NewEntry) : a;\n\t\tif (!this.core) {\n\t\t\tif (entry.head !== undefined) throw new Forbidden(\"write: head entries are core only\");\n\t\t\tif (entry.edits !== undefined) throw new Forbidden(\"write: edits are core only\");\n\t\t\tif (entry.kind.startsWith(\"pi.\") && entry.kind !== \"pi.notice\")\n\t\t\t\tthrow new Forbidden(`write: kind ${entry.kind} is reserved`);\n\t\t\tif (entry.kind === \"pi.notice\" && (entry.model?.length !== 1 || entry.model[0]?.role !== \"user\")) {\n\t\t\t\tthrow new Forbidden(\"write: pi.notice requires exactly one user model message\");\n\t\t\t}\n\t\t}\n\t\tif (this.busy(conversationId)) {\n\t\t\tconst id = this.storage.mintId();\n\t\t\tthis.raw<StickyState>({ doc: \"sticky\", conversationId }).inbox.push({\n\t\t\t\tid,\n\t\t\t\tmode: \"write\",\n\t\t\t\tentry: structuredClone(entry) as QueuedInput extends { entry: infer E } ? E : never,\n\t\t\t});\n\t\t\tthis.putInput({ id, conversationId, status: \"queued\" });\n\t\t\tthis.changes.events.push({ conversationId, event: { type: \"input.queued\", input: id, mode: \"write\" } });\n\t\t\treturn id;\n\t\t}\n\t\tconst id = this.storage.mintId();\n\t\tconst e = this.appendEntryInternal(conversationId, entry);\n\t\tthis.putInput({ id, conversationId, status: \"done\", entry: e });\n\t\treturn id;\n\t}\n\n\tcheckpoint<C extends Checkpoint>(value: C) {\n\t\tthis.assertNotHost(\"checkpoint\");\n\t\tconst inv = this.invoker as Extract<Invoker, { type: \"task\" }>;\n\t\tif (inv.mode === \"abort\") throw new Forbidden(\"checkpoint from an abort invocation\");\n\t\tconst current = this.createdTasks.get(inv.id) ?? this.liveTasks.get(inv.id);\n\t\tif (current === undefined) throw new Error(\"task not live\");\n\t\tthis.setTaskInternal({ ...current, checkpoint: plain(value) });\n\t}\n\t/** Kernel-internal: replace a task's mutable fields. Persists only what changed. */\n\tsetTask(task: Task) {\n\t\tthis.assertCore(\"setTask\");\n\t\tthis.setTaskInternal(task);\n\t}\n\tsetTaskInternalForControl(task: Task) {\n\t\tthis.setTaskInternal(task);\n\t}\n\tprivate setTaskInternal(task: Task) {\n\t\ttask = plain(task);\n\t\tconst prev = this.createdTasks.get(task.id) ?? this.liveTasks.get(task.id);\n\t\tconst patch: Record<string, unknown> = { id: task.id };\n\t\tfor (const key of [\"status\", \"checkpoint\", \"abort\", \"outcome\", \"owns\"] as const) {\n\t\t\tif (JSON.stringify(prev?.[key]) === JSON.stringify(task[key])) continue;\n\t\t\tpatch[key] = key === \"checkpoint\" && task.checkpoint === undefined ? null : task[key];\n\t\t}\n\t\tif (task.status === \"terminal\") {\n\t\t\tpatch.status = \"terminal\";\n\t\t\tpatch.checkpoint = null;\n\t\t\tpatch.owns = task.owns;\n\t\t\tpatch.outcome = task.outcome;\n\t\t\tif (task.abort === true) patch.abort = true;\n\t\t\ttask = { ...task };\n\t\t\tdelete (task as { checkpoint?: Checkpoint }).checkpoint;\n\t\t}\n\t\tif (Object.keys(patch).length === 1) return;\n\t\tif (task.status === \"terminal\")\n\t\t\tdelete this.raw<StickyState>({ doc: \"sticky\", conversationId: task.conversationId }).tasks[task.id];\n\t\tthis.writes.push({ type: \"task.patch\", patch: patch as TaskPatch });\n\t\tthis.changes.tasks.push(task);\n\t\tif (task.status === \"terminal\" && prev?.status !== \"terminal\") {\n\t\t\tconst kind = this.kinds.get(task.kind);\n\t\t\tif (task.kind === \"pi.collapse\") {\n\t\t\t\tif (task.outcome?.status === \"completed\") {\n\t\t\t\t\tconst result = task.outcome.result as { summary: Id };\n\t\t\t\t\tthis.changes.events.push({\n\t\t\t\t\t\tconversationId: task.conversationId,\n\t\t\t\t\t\tevent: { type: \"compaction.finished\", taskId: task.id, summary: result.summary },\n\t\t\t\t\t});\n\t\t\t\t} else if (task.outcome?.status === \"failed\") {\n\t\t\t\t\tconst failure = task.outcome.failure as {\n\t\t\t\t\t\treason: \"stale\" | \"declined\" | \"provider\" | \"retries_exhausted\" | \"no_model\";\n\t\t\t\t\t\tdetail: string;\n\t\t\t\t\t};\n\t\t\t\t\tthis.changes.events.push({\n\t\t\t\t\t\tconversationId: task.conversationId,\n\t\t\t\t\t\tevent: { type: \"compaction.failed\", taskId: task.id, reason: failure.reason, detail: failure.detail },\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} else if ((kind === undefined || kind.turn !== true) && task.outcome !== undefined) {\n\t\t\t\tthis.changes.events.push({\n\t\t\t\t\tconversationId: task.conversationId,\n\t\t\t\t\tevent: { type: \"task.ended\", taskId: task.id, kind: task.kind, outcome: task.outcome.status },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthis.createdTasks.set(task.id, task); // overlay: later direct reads see the patch\n\t\tthis.wroteTasks = true;\n\t}\n\n\tcreateTask(spec: TaskSpec): Id;\n\tcreateTask<K extends AnyKind>(\n\t\tkind: K,\n\t\tinput: InputOf<K>,\n\t\topts?: { conversationId?: Id; background?: true; after?: Id[] },\n\t): TaskRef<K>;\n\tcreateTask(\n\t\ta: TaskSpec | AnyKind,\n\t\tinput?: JsonValue,\n\t\topts: { conversationId?: Id; background?: true; after?: Id[] } = {},\n\t): Id | TaskRef {\n\t\tif (\"initial\" in a) {\n\t\t\tconst registered = this.kinds.get(a.name);\n\t\t\tif (registered !== a) throw new Forbidden(`createTask: kind \"${a.name}\" is not the registered token`);\n\t\t\treturn { id: this.createTaskInternal({ kind: a.name, input: input as JsonValue, ...opts }), kind: a };\n\t\t}\n\t\tthis.assertCore(\"createTask by name\");\n\t\treturn this.createTaskInternal(a);\n\t}\n\tprivate createTaskInternal(spec: TaskSpec): Id {\n\t\tconst kind = this.kinds.get(spec.kind);\n\t\tif (kind === undefined) throw new Error(`unknown task kind ${spec.kind}`);\n\t\tif (isCoreKind(spec.kind) && !this.core) throw new Forbidden(`create core task ${spec.kind}`);\n\t\tconst conversationId =\n\t\t\tspec.conversationId ?? (this.invoker.type === \"task\" ? this.invoker.conversationId : undefined);\n\t\tif (conversationId === undefined) throw new Error(\"createTask: conversationId required\");\n\t\tthis.assertScope(conversationId, \"createTask\");\n\t\tif (spec.kind === \"pi.generation\" && this.hasLiveKind(conversationId, spec.kind))\n\t\t\tthrow new GenerationInProgress(conversationId);\n\t\tif (spec.kind === \"pi.collapse\" && this.hasLiveKind(conversationId, spec.kind))\n\t\t\tthrow new CollapseInProgress(conversationId);\n\t\tconst id = this.storage.mintId();\n\t\tconst task: Task = {\n\t\t\tid,\n\t\t\tconversationId,\n\t\t\tkind: spec.kind,\n\t\t\tinput: plain(spec.input),\n\t\t\tstatus: \"pending\",\n\t\t\tafter: [...(spec.after ?? [])],\n\t\t\towns: [],\n\t\t\t...(spec.background ? { background: true } : {}),\n\t\t};\n\t\tthis.writes.push({ type: \"task\", task });\n\t\tthis.changes.tasks.push(task);\n\t\tif (spec.kind === \"pi.collapse\") {\n\t\t\tconst input = spec.input as { reason: \"threshold\" | \"manual\" | \"overflow\"; through: Id };\n\t\t\tthis.changes.events.push({\n\t\t\t\tconversationId,\n\t\t\t\tevent: { type: \"compaction.started\", taskId: id, reason: input.reason, through: input.through },\n\t\t\t});\n\t\t} else if (kind.turn !== true) {\n\t\t\tthis.changes.events.push({\n\t\t\t\tconversationId,\n\t\t\t\tevent: {\n\t\t\t\t\ttype: \"task.started\",\n\t\t\t\t\ttaskId: id,\n\t\t\t\t\tkind: task.kind,\n\t\t\t\t\t...(task.background ? { background: true } : {}),\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t\tthis.createdTasks.set(id, task);\n\t\tthis.wroteTasks = true;\n\t\treturn id;\n\t}\n\tprivate hasLiveKind(conversationId: Id, kind: string): boolean {\n\t\tfor (const task of this.liveTasks.values()) {\n\t\t\tif (task.conversationId !== conversationId || task.kind !== kind) continue;\n\t\t\tconst current = this.createdTasks.get(task.id) ?? task;\n\t\t\tif (current.status === \"terminal\") continue;\n\t\t\tif (this.closing && this.invoker.type === \"task\" && this.invoker.id === task.id) continue;\n\t\t\treturn true;\n\t\t}\n\t\tfor (const task of this.createdTasks.values()) {\n\t\t\tif (\n\t\t\t\ttask.conversationId === conversationId &&\n\t\t\t\ttask.kind === kind &&\n\t\t\t\ttask.status !== \"terminal\" &&\n\t\t\t\t!this.liveTasks.has(task.id)\n\t\t\t)\n\t\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tcreateConversation(spec: ConversationSpec): Id {\n\t\tconst owner = this.invoker.type === \"task\" ? this.invoker.id : undefined;\n\t\tif (spec.parent !== undefined) this.assertScope(spec.parent.conversationId, \"createConversation: parent\");\n\t\treturn this.insertConversation(spec, owner, false);\n\t}\n\n\tcreateForkConversation(spec: ConversationSpec): Id {\n\t\tthis.assertCore(\"createForkConversation\");\n\t\treturn this.insertConversation(spec, undefined, true);\n\t}\n\n\tasync createOwnedConversation(ownerTaskId: Id, sourceConversationId: Id, spec: OwnedConversationSpec): Promise<Id> {\n\t\tthis.assertCore(\"createOwnedConversation\");\n\t\tconst owner = this.liveTasks.get(ownerTaskId) ?? this.createdTasks.get(ownerTaskId);\n\t\tif (owner === undefined || owner.status === \"terminal\" || owner.conversationId !== sourceConversationId)\n\t\t\tthrow new Forbidden(`task ${ownerTaskId} cannot create an owned conversation`);\n\t\tconst tip = spec.inherit ? await this.newestEntry(sourceConversationId) : undefined;\n\t\tconst inherited = tip === undefined ? undefined : await this.rewindableAsOf(sourceConversationId, tip.id);\n\t\tconst rawOverrides = structuredClone(spec.rewindable ?? {});\n\t\tdelete rawOverrides.plugins;\n\t\tconst overrides = this.defaults.validateSeed(\"rewindable\", rawOverrides);\n\t\tconst rewindable =\n\t\t\tinherited === undefined\n\t\t\t\t? overrides\n\t\t\t\t: { ...inherited, ...overrides, plugins: structuredClone(inherited.plugins) };\n\t\treturn this.insertConversation(\n\t\t\t{\n\t\t\t\t...(tip === undefined ? {} : { parent: { conversationId: sourceConversationId, at: tip.id } }),\n\t\t\t\trewindable,\n\t\t\t\tsticky: spec.sticky,\n\t\t\t},\n\t\t\townerTaskId,\n\t\t\tinherited !== undefined,\n\t\t);\n\t}\n\n\tprivate insertConversation(spec: ConversationSpec, owner: Id | undefined, preservePlugins: boolean): Id {\n\t\tconst id = this.storage.mintId();\n\t\tconst parent =\n\t\t\tspec.parent === undefined || spec.parent.at === \"start\"\n\t\t\t\t? undefined\n\t\t\t\t: { conversationId: spec.parent.conversationId, at: spec.parent.at };\n\t\tconst conversation: Conversation = {\n\t\t\tid,\n\t\t\t...(parent ? { parent } : {}),\n\t\t\t...(owner === undefined ? {} : { owner }),\n\t\t\t...(spec.sections?.length ? { sections: spec.sections } : {}),\n\t\t};\n\t\tthis.writes.push({ type: \"conversation\", conversation });\n\t\tthis.changes.conversations.push(conversation);\n\t\tthis.createdConversations.set(id, conversation);\n\t\tthis.seedDoc(\n\t\t\t{ doc: \"rewindable\", conversationId: id },\n\t\t\tthis.defaults.freshRewindable(spec.rewindable, preservePlugins) as unknown as JsonObject,\n\t\t);\n\t\tthis.seedDoc(\n\t\t\t{ doc: \"sticky\", conversationId: id },\n\t\t\tthis.defaults.freshSticky(spec.sticky) as unknown as JsonObject,\n\t\t);\n\t\tif (owner !== undefined) {\n\t\t\tconst task = this.liveTasks.get(owner) ?? this.createdTasks.get(owner);\n\t\t\tif (task !== undefined) this.setTaskInternal({ ...task, owns: [...task.owns, id] });\n\t\t}\n\t\treturn id;\n\t}\n\tprivate seedDoc(ref: DocRef, value: object) {\n\t\tconst tracker = track(value);\n\t\ttracker.rebase();\n\t\tconst base = tracker.flush();\n\t\tthis.writes.push({ type: \"doc\", ref, ops: base });\n\t\tthis.changes.docs.push({ ref, ops: base });\n\t\tthis.docs.adopt(ref, tracker);\n\t\tthis.touched.set(docKey(ref), { ref, tracker });\n\t}\n\n\tmarkTask(id: Id) {\n\t\tthis.assertCore(\"markTask\");\n\t\tconst task = this.createdTasks.get(id) ?? this.liveTasks.get(id);\n\t\tif (task === undefined) throw new Error(`task ${id} not live`);\n\t\tif (task.abort !== true) this.setTaskInternal({ ...task, abort: true });\n\t}\n\n\t// --- admission ----------------------------------------------------------\n\n\t/** Prospective: live turn tasks, minus this transaction's terminals and the closing task, plus this transaction's new turn tasks. */\n\tbusy(conversationId: Id): boolean {\n\t\tconst isTurn = (t: Task) => this.kinds.get(t.kind)?.turn === true && t.background !== true;\n\t\tfor (const t of this.liveTasks.values()) {\n\t\t\tif (t.conversationId !== conversationId || !isTurn(t)) continue;\n\t\t\tconst overlay = this.createdTasks.get(t.id);\n\t\t\tif (overlay?.status === \"terminal\") continue;\n\t\t\tif (this.closing && this.invoker.type === \"task\" && t.id === this.invoker.id) continue;\n\t\t\treturn true;\n\t\t}\n\t\tfor (const t of this.createdTasks.values())\n\t\t\tif (t.conversationId === conversationId && isTurn(t) && t.status !== \"terminal\" && !this.liveTasks.has(t.id))\n\t\t\t\treturn true;\n\t\treturn false;\n\t}\n\n\tprivate putInput(input: Input) {\n\t\tinput = plain(input);\n\t\tthis.writes.push({ type: \"input\", input });\n\t\tthis.changes.inputs.push(input);\n\t\tthis.inputsById.set(input.id, input);\n\t\tif (input.requestId !== undefined) this.inputsByRequest.set(`${input.conversationId}:${input.requestId}`, input);\n\t}\n\n\tasync send(conversationId: Id, input: SendInput): Promise<Id> {\n\t\tthis.assertCore(\"send\");\n\t\tif (input.requestId !== undefined) {\n\t\t\tconst existing = await this.inputByRequest(conversationId, input.requestId); // before any write\n\t\t\tif (existing !== undefined) return existing.id;\n\t\t}\n\t\tif (this.busy(conversationId)) {\n\t\t\tconst mode = input.whenBusy ?? \"followUp\";\n\t\t\tif (mode === \"reject\") throw new ConversationBusy(conversationId);\n\t\t\tconst id = this.storage.mintId();\n\t\t\tthis.raw<StickyState>({ doc: \"sticky\", conversationId }).inbox.push({\n\t\t\t\tid,\n\t\t\t\tmode,\n\t\t\t\tinput: structuredClone(input.content),\n\t\t\t});\n\t\t\tthis.putInput({\n\t\t\t\tid,\n\t\t\t\tconversationId,\n\t\t\t\tstatus: \"queued\",\n\t\t\t\t...(input.requestId ? { requestId: input.requestId } : {}),\n\t\t\t});\n\t\t\tthis.changes.events.push({ conversationId, event: { type: \"input.queued\", input: id, mode } });\n\t\t\treturn id;\n\t\t}\n\t\t// Idle: read the head before the first append, place older queued items, then this one.\n\t\tconst head = await this.newestEntry(conversationId, { withHead: true });\n\t\tconst { triggers } = await this.boundary(conversationId, \"final\", head?.id);\n\t\tconst id = this.storage.mintId();\n\t\tconst eventStart = this.changes.events.length;\n\t\tconst entry = this.appendEntryInternal(conversationId, {\n\t\t\tkind: \"pi.user\",\n\t\t\tmodel: [{ role: \"user\", content: input.content, timestamp: this.now() }],\n\t\t});\n\t\tconst entryEvents = this.changes.events.splice(eventStart);\n\t\tthis.putInput({\n\t\t\tid,\n\t\t\tconversationId,\n\t\t\tstatus: \"placed\",\n\t\t\tentry,\n\t\t\t...(input.requestId ? { requestId: input.requestId } : {}),\n\t\t});\n\t\tthis.changes.events.push({ conversationId, event: { type: \"input.placed\", input: id, entry } }, ...entryEvents);\n\t\tconst inputs = [...triggers, id];\n\t\tthis.createTaskInternal({ kind: \"pi.generation\", conversationId, input: { inputs } });\n\t\tthis.changes.events.push({ conversationId, event: { type: \"turn.started\", inputs } });\n\t\treturn id;\n\t}\n\n\tprivate async setInput(id: Id, patch: Partial<Omit<Input, \"id\" | \"conversationId\">>) {\n\t\tconst current = await this.input(id);\n\t\tif (current === undefined) throw new Error(`input ${id} not found`);\n\t\tthis.putInput({ ...current, ...patch });\n\t}\n\t/** Kernel: withdraw a queued input. */\n\tasync withdrawInput(id: Id): Promise<\"aborted\" | \"already_placed\" | \"not_found\"> {\n\t\tthis.assertCore(\"withdrawInput\");\n\t\tconst i = await this.input(id);\n\t\tif (i === undefined) return \"not_found\";\n\t\tif (i.status !== \"queued\") return \"already_placed\";\n\t\tawait this.preload([{ doc: \"sticky\", conversationId: i.conversationId }]);\n\t\tremoveWhere(this.raw<StickyState>({ doc: \"sticky\", conversationId: i.conversationId }).inbox, (q) => q.id === id);\n\t\tawait this.setInput(id, { status: \"unanswered\", reason: \"aborted\" });\n\t\tthis.changes.events.push({ conversationId: i.conversationId, event: { type: \"input.aborted\", input: id } });\n\t\treturn \"aborted\";\n\t}\n\tasync resolveInputs(\n\t\tids: readonly Id[],\n\t\tresolution:\n\t\t\t| { status: \"done\"; answer: Id }\n\t\t\t| { status: \"unanswered\"; reason: NonNullable<Input[\"reason\"]>; detail?: string },\n\t) {\n\t\tthis.assertCore(\"resolveInputs\");\n\t\tfor (const id of ids) await this.setInput(id, resolution);\n\t}\n\n\t/**\n\t * Boundary placement (pico §9.5). No storage scan: `headBoundary` is the newest head as the\n\t * caller knows it, and it advances locally as same-batch self-heads are placed.\n\t */\n\tasync boundary(\n\t\tconversationId: Id,\n\t\tat: \"postTools\" | \"final\",\n\t\theadBoundary: Id | undefined,\n\t): Promise<{ triggers: Id[]; terminated: boolean }> {\n\t\tthis.assertCore(\"boundary\");\n\t\tconst s = this.raw<StickyState>({ doc: \"sticky\", conversationId });\n\t\tconst inbox = [...s.inbox].sort((a, b) => a.id - b.id);\n\t\tlet cut: Id | undefined;\n\t\tfor (const q of inbox) if (q.mode === \"write\" && q.entry.head === \"self\") cut = q.id;\n\t\tconst stale = cut === undefined ? [] : inbox.filter((q) => q.mode !== \"write\" && q.id < cut!).map((q) => q.id);\n\t\tfor (const id of stale) {\n\t\t\tawait this.setInput(id, { status: \"unanswered\", reason: \"stale\" });\n\t\t\tthis.changes.events.push({ conversationId, event: { type: \"input.aborted\", input: id } });\n\t\t}\n\t\tconst survivors = inbox.filter((q) => !stale.includes(q.id));\n\t\tconst pick = (mode: \"steer\" | \"followUp\", policy: \"all\" | \"one-at-a-time\") => {\n\t\t\tconst items = survivors.filter((q) => q.mode === mode);\n\t\t\treturn policy === \"all\" ? items : items.slice(0, 1);\n\t\t};\n\t\tconst selected = new Set<Id>(stale);\n\t\tfor (const q of survivors) if (q.mode === \"write\") selected.add(q.id);\n\t\tfor (const q of pick(\"steer\", s.steeringMode)) selected.add(q.id);\n\t\tif (at === \"final\") for (const q of pick(\"followUp\", s.followUpMode)) selected.add(q.id);\n\t\tlet head = headBoundary;\n\t\tconst triggers: Id[] = [];\n\t\tfor (const q of survivors) {\n\t\t\tif (!selected.has(q.id)) continue;\n\t\t\tif (q.mode === \"write\") {\n\t\t\t\tif (q.entry.head !== undefined && q.entry.head !== \"self\" && head !== undefined && q.entry.head < head) {\n\t\t\t\t\tawait this.setInput(q.id, { status: \"unanswered\", reason: \"stale\" });\n\t\t\t\t\tthis.changes.events.push({ conversationId, event: { type: \"input.aborted\", input: q.id } });\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tconst eventStart = this.changes.events.length;\n\t\t\t\tconst entry = this.appendEntryInternal(conversationId, q.entry as NewEntry);\n\t\t\t\tconst entryEvents = this.changes.events.splice(eventStart);\n\t\t\t\tif (q.entry.head !== undefined) head = q.entry.head === \"self\" ? entry : q.entry.head;\n\t\t\t\tawait this.setInput(q.id, { status: \"done\", entry });\n\t\t\t\tthis.changes.events.push(\n\t\t\t\t\t{ conversationId, event: { type: \"input.placed\", input: q.id, entry } },\n\t\t\t\t\t...entryEvents,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tconst eventStart = this.changes.events.length;\n\t\t\t\tconst entry = this.appendEntryInternal(conversationId, {\n\t\t\t\t\tkind: \"pi.user\",\n\t\t\t\t\tmodel: [{ role: \"user\", content: q.input, timestamp: this.now() }],\n\t\t\t\t});\n\t\t\t\tconst entryEvents = this.changes.events.splice(eventStart);\n\t\t\t\tawait this.setInput(q.id, { status: \"placed\", entry });\n\t\t\t\tthis.changes.events.push(\n\t\t\t\t\t{ conversationId, event: { type: \"input.placed\", input: q.id, entry } },\n\t\t\t\t\t...entryEvents,\n\t\t\t\t);\n\t\t\t\ttriggers.push(q.id);\n\t\t\t}\n\t\t}\n\t\tremoveWhere(s.inbox, (q) => selected.has(q.id));\n\t\treturn { triggers, terminated: cut !== undefined };\n\t}\n\n\t// --- finish -------------------------------------------------------------\n\n\tfinish(): Write[] {\n\t\tif (this.poisoned !== undefined) throw this.poisoned;\n\t\tfor (const [conversationId, keys] of this.changedConfig) {\n\t\t\tthis.changes.events.push({ conversationId, event: { type: \"config.changed\", keys: [...keys] } });\n\t\t}\n\t\tfor (const { ref, tracker } of this.touched.values()) {\n\t\t\tconst ops = tracker.flush();\n\t\t\tif (ops.length === 0) continue;\n\t\t\tthis.writes.push({ type: \"doc\", ref, ops });\n\t\t\tthis.changes.docs.push({ ref, ops });\n\t\t\tthis.docs.noteOps(ref, ops);\n\t\t}\n\t\treturn this.writes;\n\t}\n\t/** Every wrapper handed out by this transaction throws from now on. */\n\trevoke() {\n\t\tthis.membrane.revoke();\n\t}\n\tevictTouched() {\n\t\tfor (const { ref } of this.touched.values()) this.docs.evict(ref);\n\t}\n}\n\n/** A plain JSON copy: no document proxy, no live reference, strict JSON (throws on cycles/BigInt). */\nconst plain = <T>(v: T): T => JSON.parse(JSON.stringify(v)) as T;\n\nfunction validateEntry(e: Entry) {\n\tif (e.head !== undefined && e.head > e.id) throw new Error(`entry ${e.id}: head ${e.head} is in the future`);\n\tif (e.model !== undefined)\n\t\tfor (const m of e.model)\n\t\t\tif (typeof m !== \"object\" || m === null || typeof (m as { role?: unknown }).role !== \"string\")\n\t\t\t\tthrow new Error(`entry ${e.id}: malformed model message`);\n\tJSON.stringify(e); // strict JSON: throws on BigInt/cycles; undefined is dropped by construction\n}\n\n// ---------------------------------------------------------------------------\n// Session: the line.\n// ---------------------------------------------------------------------------\n\nconst LINE_KEY = createContextKey<true>(\"pico3.session.line\");\nconst owners = new WeakMap<Storage, Session>();\n\nexport class NestedLineOperation extends Error {\n\tconstructor() {\n\t\tsuper(\"nested line operation\");\n\t\tthis.name = \"NestedLineOperation\";\n\t}\n}\n\nexport interface CommitResult<T> {\n\tvalue: T;\n\tseq: Seq | undefined;\n\tchanges: CommitChanges;\n}\n\nexport interface TransactionControl {\n\tsetTask(task: Task): void;\n}\n\nexport class Session {\n\treadonly storage: Storage;\n\treadonly kinds: ReadonlyMap<string, AnyKind>;\n\treadonly namespaces: ReadonlyMap<string, NamespaceRegistration>;\n\tprivate tail: Promise<unknown> = Promise.resolve();\n\tprivate closed = false;\n\tprivate fault: Faulted | undefined;\n\tprivate readonly now: () => number;\n\treadonly docs: Docs;\n\treadonly defaults: Defaults;\n\treadonly liveTasks = new Map<Id, Task>();\n\t/** Owner/parent graph, loaded at open and maintained on every commit. */\n\treadonly conversationRecords = new Map<Id, Conversation>();\n\treadonly lineListeners = new Set<(result: CommitResult<unknown>) => void>();\n\treadonly listeners = new Set<(r: CommitResult<unknown>) => void>();\n\t/** Errors from listeners and other post-commit work; never surface to the writer. */\n\tonReport: (error: unknown) => void = () => {};\n\n\tconstructor(\n\t\tstorage: Storage,\n\t\tkinds: ReadonlyMap<string, AnyKind>,\n\t\tnamespaces: ReadonlyMap<string, NamespaceRegistration>,\n\t\tnow: () => number = Date.now,\n\t) {\n\t\tthis.storage = storage;\n\t\tthis.now = now;\n\t\tthis.kinds = kinds;\n\t\tthis.namespaces = namespaces;\n\t\tif (owners.has(storage)) throw new Error(\"this Storage already has an owning Session\");\n\t\tthis.defaults = new Defaults(kinds.values());\n\t\tthis.docs = new Docs(storage, this.defaults);\n\t\towners.set(storage, this);\n\t}\n\n\treadonly index: ConversationIndex = {\n\t\tget: (id) => this.conversationRecords.get(id),\n\t\tsubtree: (root) => {\n\t\t\tconst out = new Set<Id>([root]);\n\t\t\tlet grew = true;\n\t\t\twhile (grew) {\n\t\t\t\tgrew = false;\n\t\t\t\tfor (const c of this.conversationRecords.values()) {\n\t\t\t\t\tif (out.has(c.id) || c.owner === undefined) continue;\n\t\t\t\t\tconst ownerTask = this.liveTasks.get(c.owner) ?? this.ownerTaskCache.get(c.owner);\n\t\t\t\t\tif (ownerTask && out.has(ownerTask.conversationId)) {\n\t\t\t\t\t\tout.add(c.id);\n\t\t\t\t\t\tgrew = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out;\n\t\t},\n\t\tancestors: (id) => {\n\t\t\tconst chain: Id[] = [];\n\t\t\tlet c = this.conversationRecords.get(id);\n\t\t\twhile (c?.owner !== undefined) {\n\t\t\t\tconst t = this.liveTasks.get(c.owner) ?? this.ownerTaskCache.get(c.owner);\n\t\t\t\tif (t === undefined) break;\n\t\t\t\tchain.unshift(t.conversationId);\n\t\t\t\tc = this.conversationRecords.get(t.conversationId);\n\t\t\t}\n\t\t\treturn chain;\n\t\t},\n\t};\n\t/** Owner tasks that are terminal but whose conversations still exist (ancestry after reopen). */\n\treadonly ownerTaskCache = new Map<Id, Task>();\n\n\tasync commit<T>(\n\t\tinvoker: Invoker,\n\t\tfn: (tx: TxImpl, ctx: Context, control: TransactionControl) => T | Promise<T>,\n\t\tctx: Context,\n\t\topts: { docs?: DocRef[]; closing?: boolean } = {},\n\t): Promise<CommitResult<T>> {\n\t\tctx.abortSignal?.throwIfAborted();\n\t\tconst authority = Object.freeze({ ...invoker }) as Invoker;\n\t\tconst result = await this.enter(ctx, async (lineCtx) => {\n\t\t\tthis.assertUsable();\n\t\t\tlineCtx.abortSignal?.throwIfAborted();\n\t\t\tif (authority.type === \"task\") {\n\t\t\t\t// A captured runtime cannot write after its invocation returned, after terminalization, or after a mark (run mode).\n\t\t\t\tif (!authority.token.alive) throw new Forbidden(\"commit from a finished invocation\");\n\t\t\t\tconst live = this.liveTasks.get(authority.id);\n\t\t\t\tif (live === undefined) throw new Forbidden(\"commit from a task that is not live\");\n\t\t\t\tif (authority.mode === \"run\" && live.abort === true)\n\t\t\t\t\tthrow new Forbidden(\"commit from a marked run invocation\");\n\t\t\t}\n\t\t\tconst tx = new TxImpl(\n\t\t\t\tauthority,\n\t\t\t\tthis.storage,\n\t\t\t\tthis.docs,\n\t\t\t\tthis.defaults,\n\t\t\t\tthis.kinds,\n\t\t\t\tthis.namespaces,\n\t\t\t\tthis.liveTasks,\n\t\t\t\tthis.index,\n\t\t\t\tthis.now,\n\t\t\t\tlineCtx,\n\t\t\t);\n\t\t\ttx.closing = opts.closing === true;\n\t\t\tconst refs: DocRef[] = [{ doc: \"session\" }, ...(opts.docs ?? [])];\n\t\t\tif (authority.type === \"task\")\n\t\t\t\trefs.push(\n\t\t\t\t\t{ doc: \"rewindable\", conversationId: authority.conversationId },\n\t\t\t\t\t{ doc: \"sticky\", conversationId: authority.conversationId },\n\t\t\t\t);\n\t\t\tlet persisted = false;\n\t\t\ttry {\n\t\t\t\tawait tx.preload(refs);\n\t\t\t\tlet value: T;\n\t\t\t\ttry {\n\t\t\t\t\tvalue = await fn(tx.callbackSurface(), lineCtx, {\n\t\t\t\t\t\tsetTask: (task) => tx.setTaskInternalForControl(task),\n\t\t\t\t\t});\n\t\t\t\t} finally {\n\t\t\t\t\ttx.closeSurface();\n\t\t\t\t}\n\t\t\t\tawait tx.preload(\n\t\t\t\t\t[...new Set(tx.changes.tasks.map((task) => task.conversationId))].map((conversationId) => ({\n\t\t\t\t\t\tdoc: \"sticky\" as const,\n\t\t\t\t\t\tconversationId,\n\t\t\t\t\t})),\n\t\t\t\t);\n\t\t\t\tconst writes = tx.finish();\n\t\t\t\tlet seq: Seq | undefined;\n\t\t\t\tif (writes.length > 0 || tx.changes.events.length > 0) {\n\t\t\t\t\tpersisted = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tseq = await this.storage.commit(writes, withoutAbortSignal(lineCtx));\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tthis.fault = new Faulted(error);\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait this.storage.close(withoutAbortSignal(lineCtx));\n\t\t\t\t\t\t\towners.delete(this.storage);\n\t\t\t\t\t\t} catch {}\n\t\t\t\t\t\tthrow this.fault;\n\t\t\t\t\t}\n\t\t\t\t\tthis.applyChanges(tx.changes);\n\t\t\t\t}\n\t\t\t\tconst result = { value, seq, changes: tx.changes } as CommitResult<T>;\n\t\t\t\tif (seq !== undefined) {\n\t\t\t\t\tfor (const listener of this.lineListeners) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tlistener(result);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tthis.onReport(error);\n\t\t\t\t\t\t\t} catch {}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn result;\n\t\t\t} catch (error) {\n\t\t\t\tif (!persisted) tx.evictTouched();\n\t\t\t\tthrow error;\n\t\t\t} finally {\n\t\t\t\ttx.revoke();\n\t\t\t}\n\t\t});\n\t\tif (result.seq !== undefined)\n\t\t\tfor (const l of this.listeners) {\n\t\t\t\ttry {\n\t\t\t\t\tl(result);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.onReport(error);\n\t\t\t\t}\n\t\t\t}\n\t\treturn result;\n\t}\n\n\t/** Read-only line operation without a transaction. */\n\tread<T>(fn: (storage: Storage, ctx: Context) => Promise<T>, ctx: Context): Promise<T> {\n\t\treturn this.enter(ctx, (lineCtx) => {\n\t\t\tthis.assertUsable();\n\t\t\treturn fn(this.storage, lineCtx);\n\t\t});\n\t}\n\t/** Generic line operation (waiter registration and asynchronous lifecycle work). */\n\tonLine<T>(fn: (ctx: Context) => T | Promise<T>, ctx: Context): Promise<T> {\n\t\treturn this.enter(ctx, (lineCtx) => {\n\t\t\tthis.assertUsable();\n\t\t\treturn fn(lineCtx);\n\t\t});\n\t}\n\n\tstatic readonly STICKY_BASE_BUDGET = 256 * 1024;\n\n\t/** After a task terminalizes: retire its slot; base + truncate when idle or over budget. Truncation runs on the line. */\n\tasync retire(task: Task, ctx: Context) {\n\t\tconst ref: DocRef = { doc: \"sticky\", conversationId: task.conversationId };\n\t\tconst idle = ![...this.liveTasks.values()].some((t) => t.conversationId === task.conversationId);\n\t\tconst over = (this.docs.sinceBase.get(docKey(ref)) ?? 0) > Session.STICKY_BASE_BUDGET;\n\t\tawait this.commit(\n\t\t\t{ type: \"kernel\" },\n\t\t\t(tx) => {\n\t\t\t\tdelete tx.sticky(task.conversationId).tasks[task.id];\n\t\t\t\tif (idle || over) this.docs.requestBase(ref);\n\t\t\t},\n\t\t\tctx,\n\t\t\t{ docs: [ref] },\n\t\t);\n\t\tif (idle || over) await this.enter(ctx, (lineCtx) => this.storage.truncate(ref, withoutAbortSignal(lineCtx)));\n\t}\n\n\tasync fork(parentId: Id, at: Id | \"start\", spec: Omit<ConversationSpec, \"parent\">, ctx: Context): Promise<Id> {\n\t\tconst inherited =\n\t\t\tat === \"start\"\n\t\t\t\t? undefined\n\t\t\t\t: await this.read(async (storage, lineCtx) => {\n\t\t\t\t\t\tconst [entry] = await storage.scanEntries(\n\t\t\t\t\t\t\t{ conversationId: parentId, before: at + 1, limit: 1 },\n\t\t\t\t\t\t\tlineCtx,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (entry?.id !== at) throw new Error(`entry ${at} is not visible from conversation ${parentId}`);\n\t\t\t\t\t\treturn storage.docAsOf(parentId, at, lineCtx) as Promise<RewindableState | undefined>;\n\t\t\t\t\t}, ctx);\n\t\tconst rawOverrides = structuredClone(spec.rewindable ?? {});\n\t\tdelete rawOverrides.plugins;\n\t\tconst overrides = this.defaults.validateSeed(\"rewindable\", rawOverrides);\n\t\tconst rewindable =\n\t\t\tinherited === undefined\n\t\t\t\t? overrides\n\t\t\t\t: { ...inherited, ...overrides, plugins: structuredClone(inherited.plugins) };\n\t\tconst r = await this.commit(\n\t\t\t{ type: \"kernel\" },\n\t\t\t(tx) =>\n\t\t\t\ttx.createForkConversation({\n\t\t\t\t\tparent: { conversationId: parentId, at },\n\t\t\t\t\trewindable,\n\t\t\t\t\tsticky: spec.sticky,\n\t\t\t\t\tsections: spec.sections,\n\t\t\t\t}),\n\t\t\tctx,\n\t\t);\n\t\treturn r.value;\n\t}\n\n\tasync close(ctx: Context) {\n\t\tawait this.enter(ctx, async (lineCtx) => {\n\t\t\tif (this.closed) return;\n\t\t\tawait this.storage.close(withoutAbortSignal(lineCtx));\n\t\t\tthis.closed = true;\n\t\t\towners.delete(this.storage);\n\t\t});\n\t}\n\n\tloadedDocument(ref: DocRef): JsonObject | undefined {\n\t\treturn this.docs.loaded(ref);\n\t}\n\n\tprivate applyChanges(e: CommitChanges) {\n\t\tfor (const t of e.tasks) {\n\t\t\tif (t.status === \"terminal\") {\n\t\t\t\tthis.liveTasks.delete(t.id);\n\t\t\t\tif (t.owns.length) this.ownerTaskCache.set(t.id, t);\n\t\t\t} else this.liveTasks.set(t.id, t);\n\t\t}\n\t\tfor (const c of e.conversations) this.conversationRecords.set(c.id, c);\n\t}\n\n\tprivate enter<T>(ctx: Context, op: (ctx: Context) => T | Promise<T>): Promise<T> {\n\t\tif (ctx.value(LINE_KEY) === true) return Promise.reject(new NestedLineOperation());\n\t\tconst previous = this.tail;\n\t\tlet release!: () => void;\n\t\tthis.tail = new Promise<void>((resolve) => {\n\t\t\trelease = resolve;\n\t\t});\n\t\treturn (async () => {\n\t\t\tawait previous;\n\t\t\ttry {\n\t\t\t\treturn await op(withContextValue(LINE_KEY, true, ctx));\n\t\t\t} finally {\n\t\t\t\trelease();\n\t\t\t}\n\t\t})();\n\t}\n\n\tprivate assertUsable() {\n\t\tif (this.fault !== undefined) throw this.fault;\n\t\tif (this.closed) throw new Closed();\n\t}\n}\n\nexport type { TxImpl };\n"]}