@astrosheep/keiyaku 4.6.8 → 4.6.9

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 (253) hide show
  1. package/README.md +40 -14
  2. package/build/plugins/claude/.claude-plugin/plugin.json +1 -1
  3. package/build/plugins/claude/skills/keiyaku/SKILL.md +5 -6
  4. package/build/plugins/claude/skills/keiyaku-akuma/SKILL.md +1 -1
  5. package/build/plugins/claude/skills/keiyaku-akuma/references/automation.md +8 -8
  6. package/build/plugins/claude/skills/keiyaku-babysit/SKILL.md +1 -1
  7. package/build/plugins/claude/skills/keiyaku-bind/SKILL.md +9 -9
  8. package/build/plugins/claude/skills/keiyaku-debt/SKILL.md +91 -0
  9. package/build/plugins/claude/skills/keiyaku-workflow/SKILL.md +2 -2
  10. package/build/plugins/codex/.codex-plugin/plugin.json +1 -1
  11. package/build/plugins/codex/skills/keiyaku/SKILL.md +5 -6
  12. package/build/plugins/codex/skills/keiyaku-akuma/SKILL.md +1 -1
  13. package/build/plugins/codex/skills/keiyaku-akuma/references/automation.md +8 -8
  14. package/build/plugins/codex/skills/keiyaku-babysit/SKILL.md +1 -1
  15. package/build/plugins/codex/skills/keiyaku-bind/SKILL.md +9 -9
  16. package/build/plugins/codex/skills/keiyaku-debt/SKILL.md +91 -0
  17. package/build/plugins/codex/skills/keiyaku-workflow/SKILL.md +2 -2
  18. package/build/plugins/opencode/index.js +1 -1
  19. package/build/plugins/opencode/skills/keiyaku/SKILL.md +5 -6
  20. package/build/plugins/opencode/skills/keiyaku-akuma/SKILL.md +1 -1
  21. package/build/plugins/opencode/skills/keiyaku-akuma/references/automation.md +8 -8
  22. package/build/plugins/opencode/skills/keiyaku-babysit/SKILL.md +1 -1
  23. package/build/plugins/opencode/skills/keiyaku-bind/SKILL.md +9 -9
  24. package/build/plugins/opencode/skills/keiyaku-debt/SKILL.md +91 -0
  25. package/build/plugins/opencode/skills/keiyaku-workflow/SKILL.md +2 -2
  26. package/build/skills/keiyaku/SKILL.md +5 -6
  27. package/build/skills/keiyaku-akuma/SKILL.md +1 -1
  28. package/build/skills/keiyaku-akuma/references/automation.md +8 -8
  29. package/build/skills/keiyaku-babysit/SKILL.md +1 -1
  30. package/build/skills/keiyaku-bind/SKILL.md +9 -9
  31. package/build/skills/keiyaku-debt/SKILL.md +91 -0
  32. package/build/skills/keiyaku-workflow/SKILL.md +2 -2
  33. package/build/src/akuma/akuma-instance.d.ts +2 -0
  34. package/build/src/akuma/akuma-instance.js +10 -0
  35. package/build/src/akuma/akuma-observe.d.ts +33 -8
  36. package/build/src/akuma/akuma-observe.js +88 -64
  37. package/build/src/akuma/akuma-owner.d.ts +11 -2
  38. package/build/src/akuma/akuma-owner.js +45 -35
  39. package/build/src/akuma/akuma-selection-execution.js +9 -1
  40. package/build/src/akuma/akuma-stream.d.ts +22 -0
  41. package/build/src/akuma/akuma-stream.js +116 -0
  42. package/build/src/akuma/akuma-wait.d.ts +1 -40
  43. package/build/src/akuma/akuma-wait.js +23 -84
  44. package/build/src/akuma/akuma.d.ts +17 -11
  45. package/build/src/akuma/akuma.js +2 -0
  46. package/build/src/akuma/allowed.d.ts +7 -12
  47. package/build/src/akuma/allowed.js +13 -4
  48. package/build/src/akuma/body-supervisor.d.ts +5 -5
  49. package/build/src/akuma/body-supervisor.js +22 -20
  50. package/build/src/akuma/body.js +28 -1
  51. package/build/src/akuma/call-request.d.ts +12 -22
  52. package/build/src/akuma/heart/activity-schema.d.ts +12 -0
  53. package/build/src/akuma/heart/activity-schema.js +6 -0
  54. package/build/src/akuma/heart/index.d.ts +59 -2
  55. package/build/src/akuma/heart/index.js +87 -8
  56. package/build/src/akuma/heart/rows.js +5 -3
  57. package/build/src/akuma/heart/soul.d.ts +9 -0
  58. package/build/src/akuma/heart/soul.js +29 -0
  59. package/build/src/akuma/heart/storage.d.ts +15 -0
  60. package/build/src/akuma/heart/storage.js +55 -0
  61. package/build/src/akuma/heart/timeline.d.ts +7 -0
  62. package/build/src/akuma/heart/timeline.js +10 -0
  63. package/build/src/akuma/index.d.ts +1 -0
  64. package/build/src/akuma/projection.d.ts +25 -0
  65. package/build/src/akuma/projection.js +24 -7
  66. package/build/src/akuma/provider-recipe.d.ts +11 -0
  67. package/build/src/akuma/provider-recipe.js +23 -0
  68. package/build/src/akuma/provider.d.ts +25 -0
  69. package/build/src/akuma/provider.js +99 -7
  70. package/build/src/akuma/providers/acp/events.js +25 -2
  71. package/build/src/akuma/providers/claude/events.js +76 -10
  72. package/build/src/akuma/providers/claude/index.js +2 -5
  73. package/build/src/akuma/providers/codex-app-server/events.js +17 -2
  74. package/build/src/akuma/providers/codex-app-server/index.js +15 -9
  75. package/build/src/akuma/providers/decode.d.ts +8 -0
  76. package/build/src/akuma/providers/decode.js +21 -0
  77. package/build/src/akuma/providers/grok-build/index.js +10 -26
  78. package/build/src/akuma/providers/opencode-sdk/event-stream.d.ts +3 -3
  79. package/build/src/akuma/providers/opencode-sdk/event-stream.js +2 -25
  80. package/build/src/akuma/providers/opencode-sdk/events.d.ts +7 -43
  81. package/build/src/akuma/providers/opencode-sdk/events.js +147 -284
  82. package/build/src/akuma/providers/opencode-sdk/index.js +267 -421
  83. package/build/src/akuma/providers/opencode-sdk/native-http.d.ts +9 -0
  84. package/build/src/akuma/providers/opencode-sdk/native-http.js +69 -0
  85. package/build/src/akuma/providers/opencode-sdk/session.d.ts +42 -36
  86. package/build/src/akuma/providers/opencode-sdk/session.js +75 -82
  87. package/build/src/akuma/providers/paseo/events.d.ts +8 -0
  88. package/build/src/akuma/providers/paseo/events.js +58 -11
  89. package/build/src/akuma/providers/paseo/index.js +11 -7
  90. package/build/src/akuma/providers/pi/events.d.ts +2 -0
  91. package/build/src/akuma/providers/pi/events.js +32 -24
  92. package/build/src/akuma/providers/pi/index.js +21 -35
  93. package/build/src/akuma/request-lifecycle.d.ts +1 -2
  94. package/build/src/akuma/request-lifecycle.js +2 -8
  95. package/build/src/akuma/request-rendezvous.js +38 -55
  96. package/build/src/akuma/request-serve.d.ts +1 -1
  97. package/build/src/akuma/request-serve.js +77 -113
  98. package/build/src/akuma/selection-observation.d.ts +57 -51
  99. package/build/src/akuma/selection-observation.js +3 -3
  100. package/build/src/akuma/turn-drive.d.ts +3 -0
  101. package/build/src/akuma/turn-drive.js +26 -5
  102. package/build/src/akuma-body.js +12 -22
  103. package/build/src/cli/akuma-caller.js +10 -0
  104. package/build/src/cli/commands/akuma-grammar.js +3 -4
  105. package/build/src/cli/commands/akuma.js +13 -51
  106. package/build/src/cli/commands/contract-grammar.d.ts +7 -6
  107. package/build/src/cli/commands/contract-grammar.js +27 -32
  108. package/build/src/cli/commands/contract-help.d.ts +16 -7
  109. package/build/src/cli/commands/contract-help.js +20 -11
  110. package/build/src/cli/commands/contract.js +11 -11
  111. package/build/src/cli/commands/debt-grammar.d.ts +19 -0
  112. package/build/src/cli/commands/debt-grammar.js +323 -0
  113. package/build/src/cli/commands/debt-query.d.ts +5 -0
  114. package/build/src/cli/commands/{task-query.js → debt-query.js} +9 -9
  115. package/build/src/cli/commands/{task.d.ts → debt.d.ts} +2 -2
  116. package/build/src/cli/commands/{task.js → debt.js} +83 -143
  117. package/build/src/cli/coordinates.d.ts +1 -1
  118. package/build/src/cli/coordinates.js +1 -1
  119. package/build/src/cli/parse.d.ts +4 -4
  120. package/build/src/cli/parse.js +17 -16
  121. package/build/src/cli/render/akuma-activity.d.ts +11 -83
  122. package/build/src/cli/render/akuma-activity.js +20 -382
  123. package/build/src/cli/render/catalog.d.ts +2 -2
  124. package/build/src/cli/render/catalog.js +5 -5
  125. package/build/src/cli/render/contract.js +2 -2
  126. package/build/src/cli/render/debt.d.ts +28 -0
  127. package/build/src/cli/render/{task.js → debt.js} +51 -112
  128. package/build/src/cli/render/kanshi.js +28 -28
  129. package/build/src/cli/render/marks.d.ts +5 -5
  130. package/build/src/cli/render/marks.js +4 -10
  131. package/build/src/cli/render/nuke.js +1 -1
  132. package/build/src/cli/runtime.js +3 -3
  133. package/build/src/cli/scan.d.ts +1 -1
  134. package/build/src/cli/usage.d.ts +1 -1
  135. package/build/src/cli/usage.js +4 -4
  136. package/build/src/contract-worktree.js +3 -3
  137. package/build/src/debt/board.d.ts +160 -0
  138. package/build/src/{task → debt}/board.js +89 -89
  139. package/build/src/debt/catalog.d.ts +11 -0
  140. package/build/src/debt/catalog.js +59 -0
  141. package/build/src/{task → debt}/context.d.ts +1 -1
  142. package/build/src/{task → debt}/context.js +1 -1
  143. package/build/src/{task → debt}/created-observation.d.ts +6 -10
  144. package/build/src/{task → debt}/created-observation.js +7 -7
  145. package/build/src/debt/document.d.ts +37 -0
  146. package/build/src/{task → debt}/document.js +37 -37
  147. package/build/src/debt/identity.d.ts +21 -0
  148. package/build/src/{task → debt}/identity.js +35 -35
  149. package/build/src/debt/index.d.ts +111 -0
  150. package/build/src/debt/index.js +203 -0
  151. package/build/src/{task → debt}/input.d.ts +8 -8
  152. package/build/src/{task → debt}/input.js +21 -21
  153. package/build/src/debt/lifecycle-operations.d.ts +6 -0
  154. package/build/src/{task → debt}/lifecycle-operations.js +27 -31
  155. package/build/src/debt/mutation-result.d.ts +981 -0
  156. package/build/src/debt/mutation-result.js +109 -0
  157. package/build/src/debt/mutation.d.ts +424 -0
  158. package/build/src/debt/mutation.js +223 -0
  159. package/build/src/debt/operation-types.d.ts +71 -0
  160. package/build/src/debt/operations.d.ts +28 -0
  161. package/build/src/{task → debt}/operations.js +59 -60
  162. package/build/src/debt/query.d.ts +76 -0
  163. package/build/src/{task → debt}/query.js +55 -55
  164. package/build/src/{task → debt}/store.d.ts +13 -13
  165. package/build/src/{task → debt}/store.js +58 -49
  166. package/build/src/debt/view.d.ts +4 -0
  167. package/build/src/debt/view.js +7 -0
  168. package/build/src/identity/coordinates.d.ts +1 -1
  169. package/build/src/index.d.ts +13 -13
  170. package/build/src/index.js +9 -9
  171. package/build/src/kanshi/index.d.ts +1 -1
  172. package/build/src/kanshi/read.js +33 -33
  173. package/build/src/kanshi/report.d.ts +7 -7
  174. package/build/src/kanshi/select.js +4 -4
  175. package/build/src/library/akumas.d.ts +2 -3
  176. package/build/src/library/bind.d.ts +3 -3
  177. package/build/src/library/bind.js +5 -5
  178. package/build/src/library/contract-creation.js +5 -5
  179. package/build/src/library/contract-execution.js +4 -4
  180. package/build/src/library/contract-types.d.ts +2 -2
  181. package/build/src/library/input.d.ts +2 -2
  182. package/build/src/library/input.js +5 -5
  183. package/build/src/library/invocation.js +2 -2
  184. package/build/src/library/nuke.js +3 -3
  185. package/build/src/library/outcome.d.ts +126 -126
  186. package/build/src/library/outcome.js +3 -3
  187. package/build/src/library/selection.d.ts +1 -4
  188. package/build/src/library/selection.js +11 -62
  189. package/build/src/plugin/public.d.ts +5 -0
  190. package/build/src/plugin/runtime.js +3 -1
  191. package/build/src/runtime/proc/windows-launch.exe +0 -0
  192. package/build/src/settlement/fence.d.ts +3 -3
  193. package/build/src/settlement/fence.js +4 -4
  194. package/build/src/settlement/holder.d.ts +16 -16
  195. package/build/src/settlement/holder.js +69 -63
  196. package/build/src/settlement/settle.d.ts +5 -5
  197. package/build/src/settlement/settle.js +53 -53
  198. package/package.json +12 -6
  199. package/plugins/paseo/plugin/index.server.ts +17 -0
  200. package/plugins/paseo/plugin/paseo-plugin.json +4 -0
  201. package/plugins/paseo/plugin/server/attach.ts +52 -0
  202. package/plugins/paseo/plugin/server/keiyaku.ts +44 -0
  203. package/plugins/paseo/plugin/server/message.ts +60 -0
  204. package/plugins/paseo/plugin/server/mirror.ts +193 -0
  205. package/plugins/paseo/plugin/server/model.ts +14 -0
  206. package/plugins/paseo/plugin/server/provider.ts +276 -0
  207. package/plugins/paseo/plugin/server/sdk-worker.ts +237 -0
  208. package/plugins/paseo/plugin/server/sdk.ts +289 -0
  209. package/plugins/paseo/plugin/server/stop.ts +26 -0
  210. package/plugins/paseo/plugin/server/transport.ts +35 -0
  211. package/plugins/paseo/plugin/shared/attach-rpc.ts +10 -0
  212. package/plugins/paseo/plugin/shared/refresh-rpc.ts +6 -0
  213. package/plugins/paseo/plugin/tsconfig.json +6 -0
  214. package/skills/keiyaku/SKILL.md +5 -6
  215. package/skills/keiyaku-akuma/SKILL.md +1 -1
  216. package/skills/keiyaku-akuma/references/automation.md +8 -8
  217. package/skills/keiyaku-babysit/SKILL.md +1 -1
  218. package/skills/keiyaku-bind/SKILL.md +9 -9
  219. package/skills/keiyaku-debt/SKILL.md +91 -0
  220. package/skills/keiyaku-workflow/SKILL.md +2 -2
  221. package/build/plugins/claude/skills/keiyaku-task/SKILL.md +0 -104
  222. package/build/plugins/codex/skills/keiyaku-task/SKILL.md +0 -104
  223. package/build/plugins/opencode/skills/keiyaku-task/SKILL.md +0 -104
  224. package/build/skills/keiyaku-task/SKILL.md +0 -104
  225. package/build/src/cli/commands/task-grammar.d.ts +0 -19
  226. package/build/src/cli/commands/task-grammar.js +0 -384
  227. package/build/src/cli/commands/task-query.d.ts +0 -5
  228. package/build/src/cli/render/task.d.ts +0 -39
  229. package/build/src/task/board.d.ts +0 -168
  230. package/build/src/task/catalog.d.ts +0 -11
  231. package/build/src/task/catalog.js +0 -59
  232. package/build/src/task/compose-language.d.ts +0 -57
  233. package/build/src/task/compose-language.js +0 -364
  234. package/build/src/task/compose-parser.d.ts +0 -32
  235. package/build/src/task/compose-parser.js +0 -180
  236. package/build/src/task/compose.d.ts +0 -16
  237. package/build/src/task/compose.js +0 -189
  238. package/build/src/task/document.d.ts +0 -39
  239. package/build/src/task/identity.d.ts +0 -21
  240. package/build/src/task/index.d.ts +0 -138
  241. package/build/src/task/index.js +0 -269
  242. package/build/src/task/lifecycle-operations.d.ts +0 -6
  243. package/build/src/task/mutation-result.d.ts +0 -1726
  244. package/build/src/task/mutation-result.js +0 -198
  245. package/build/src/task/mutation.d.ts +0 -590
  246. package/build/src/task/mutation.js +0 -262
  247. package/build/src/task/operation-types.d.ts +0 -71
  248. package/build/src/task/operations.d.ts +0 -28
  249. package/build/src/task/query.d.ts +0 -76
  250. package/build/src/task/view.d.ts +0 -4
  251. package/build/src/task/view.js +0 -7
  252. package/skills/keiyaku-task/SKILL.md +0 -104
  253. /package/build/src/{task → debt}/operation-types.js +0 -0
@@ -313,6 +313,29 @@ async function recoverBodyRequests(input) {
313
313
  }
314
314
  return false;
315
315
  }
316
+ function turnStartedEmitter(launch, soul, world) {
317
+ const reportDiagnostic = (message) => {
318
+ void recordPluginDiagnostic(launch.paths, "turn started", message);
319
+ };
320
+ return {
321
+ async emit(turnSequence) {
322
+ try {
323
+ const turn = await readTurn(launch.paths, turnSequence);
324
+ if (turn === null)
325
+ throw new Error(`Committed Turn ${turnSequence} is missing from Heart`);
326
+ await emitPluginSignal({ world, reportDiagnostic }, {
327
+ kind: "akuma.turn-started",
328
+ akumaId: soul.id,
329
+ turnSequence,
330
+ ...(turn.initiator === undefined ? {} : { initiator: turn.initiator }),
331
+ });
332
+ }
333
+ catch (error) {
334
+ await recordPluginDiagnostic(launch.paths, "turn started", error);
335
+ }
336
+ },
337
+ };
338
+ }
316
339
  function turnOutcomeEmitter(launch, soul, world) {
317
340
  const reportDiagnostic = (message) => {
318
341
  void recordPluginDiagnostic(launch.paths, "turn outcome", message);
@@ -383,7 +406,7 @@ async function admitInitialCallTell(runtime, input) {
383
406
  });
384
407
  }
385
408
  async function runBodyTurns(input) {
386
- const { launch, soul, adapter, bodySequence, supervisor, runtime, turnOutcome } = input;
409
+ const { launch, soul, adapter, bodySequence, supervisor, runtime, turnStarted, turnOutcome } = input;
387
410
  let initial = launch.initialBody;
388
411
  for (;;) {
389
412
  const launchTells = drainPendingTells(supervisor.current().pending, await readOpenBoundTurns(launch.paths, bodySequence));
@@ -418,6 +441,7 @@ async function runBodyTurns(input) {
418
441
  world: runtime.world,
419
442
  externalCommands: runtime.externalCommands,
420
443
  now: runtime.now,
444
+ turnStarted,
421
445
  });
422
446
  if (result.kind === "hung") {
423
447
  await failOpenBoundTurnsIfPresent(launch.paths, bodySequence, "Body hung with open bound Turns", runtime.now());
@@ -566,12 +590,14 @@ export async function driveAkumaBody(launch, adapter, runtimeInput = {}) {
566
590
  let pendingTellDisposition = undefined;
567
591
  let bodySequence;
568
592
  let emitBodyEnd;
593
+ let emitTurnStarted;
569
594
  let emitTurnOutcome;
570
595
  try {
571
596
  const prepared = await prepareBodyExecution(launch, leash, runtime, adapter);
572
597
  if (prepared === null)
573
598
  return;
574
599
  emitBodyEnd = bodyEndEmitter(launch, prepared.soul, world);
600
+ emitTurnStarted = turnStartedEmitter(launch, prepared.soul, world);
575
601
  emitTurnOutcome = turnOutcomeEmitter(launch, prepared.soul, world);
576
602
  const body = await leash.recordBody(launch.paths, { leashTakenAt: runtime.now() });
577
603
  bodySequence = body.sequence;
@@ -598,6 +624,7 @@ export async function driveAkumaBody(launch, adapter, runtimeInput = {}) {
598
624
  bodySequence: body.sequence,
599
625
  supervisor,
600
626
  runtime,
627
+ turnStarted: emitTurnStarted,
601
628
  turnOutcome: emitTurnOutcome,
602
629
  });
603
630
  }
@@ -8,23 +8,18 @@ import type { WorldRoot } from "../world.js";
8
8
  declare const akumaCallRecipeSchema: z.ZodObject<{
9
9
  description: z.ZodOptional<z.ZodString>;
10
10
  allowed: z.ZodPipe<z.ZodArray<z.ZodEnum<{
11
- "task.add": "task.add";
12
- "task.addDocument": "task.addDocument";
13
- "task.compose": "task.compose";
14
- "task.done": "task.done";
15
- "task.drop": "task.drop";
16
- "task.hold": "task.hold";
17
- "task.resume": "task.resume";
18
- "task.start": "task.start";
19
- "task.stop": "task.stop";
20
- "task.update": "task.update";
11
+ "debt.add": "debt.add";
12
+ "debt.addDocument": "debt.addDocument";
13
+ "debt.done": "debt.done";
14
+ "debt.drop": "debt.drop";
15
+ "debt.update": "debt.update";
21
16
  "akuma.call": "akuma.call";
22
17
  "akuma.kill": "akuma.kill";
23
18
  "akuma.tell": "akuma.tell";
24
19
  "contract.audit": "contract.audit";
25
20
  "contract.deliver": "contract.deliver";
26
21
  "contract.review": "contract.review";
27
- }>>, z.ZodTransform<import("./allowed.js").AllowedActions, ("task.add" | "task.addDocument" | "task.compose" | "task.done" | "task.drop" | "task.hold" | "task.resume" | "task.start" | "task.stop" | "task.update" | "akuma.call" | "akuma.kill" | "akuma.tell" | "contract.audit" | "contract.deliver" | "contract.review")[]>>;
22
+ }>>, z.ZodTransform<import("./allowed.js").AllowedActions, ("debt.add" | "debt.addDocument" | "debt.done" | "debt.drop" | "debt.update" | "akuma.call" | "akuma.kill" | "akuma.tell" | "contract.audit" | "contract.deliver" | "contract.review")[]>>;
28
23
  provider: z.ZodPipe<z.ZodObject<{
29
24
  name: z.ZodString;
30
25
  kind: z.ZodEnum<{
@@ -93,23 +88,18 @@ declare const akumaCallPayloadSchema: z.ZodObject<{
93
88
  recipe: z.ZodObject<{
94
89
  description: z.ZodOptional<z.ZodString>;
95
90
  allowed: z.ZodPipe<z.ZodArray<z.ZodEnum<{
96
- "task.add": "task.add";
97
- "task.addDocument": "task.addDocument";
98
- "task.compose": "task.compose";
99
- "task.done": "task.done";
100
- "task.drop": "task.drop";
101
- "task.hold": "task.hold";
102
- "task.resume": "task.resume";
103
- "task.start": "task.start";
104
- "task.stop": "task.stop";
105
- "task.update": "task.update";
91
+ "debt.add": "debt.add";
92
+ "debt.addDocument": "debt.addDocument";
93
+ "debt.done": "debt.done";
94
+ "debt.drop": "debt.drop";
95
+ "debt.update": "debt.update";
106
96
  "akuma.call": "akuma.call";
107
97
  "akuma.kill": "akuma.kill";
108
98
  "akuma.tell": "akuma.tell";
109
99
  "contract.audit": "contract.audit";
110
100
  "contract.deliver": "contract.deliver";
111
101
  "contract.review": "contract.review";
112
- }>>, z.ZodTransform<import("./allowed.js").AllowedActions, ("task.add" | "task.addDocument" | "task.compose" | "task.done" | "task.drop" | "task.hold" | "task.resume" | "task.start" | "task.stop" | "task.update" | "akuma.call" | "akuma.kill" | "akuma.tell" | "contract.audit" | "contract.deliver" | "contract.review")[]>>;
102
+ }>>, z.ZodTransform<import("./allowed.js").AllowedActions, ("debt.add" | "debt.addDocument" | "debt.done" | "debt.drop" | "debt.update" | "akuma.call" | "akuma.kill" | "akuma.tell" | "contract.audit" | "contract.deliver" | "contract.review")[]>>;
113
103
  provider: z.ZodPipe<z.ZodObject<{
114
104
  name: z.ZodString;
115
105
  kind: z.ZodEnum<{
@@ -24,6 +24,7 @@ export declare const fileChangeSchema: z.ZodObject<{
24
24
  added: z.ZodNumber;
25
25
  removed: z.ZodNumber;
26
26
  }, z.core.$strip>>;
27
+ patch: z.ZodOptional<z.ZodString>;
27
28
  }, z.core.$strip>;
28
29
  export declare const toolCallSchema: z.ZodUnion<readonly [z.ZodObject<{
29
30
  kind: z.ZodLiteral<"run">;
@@ -57,6 +58,7 @@ export declare const toolCallSchema: z.ZodUnion<readonly [z.ZodObject<{
57
58
  added: z.ZodNumber;
58
59
  removed: z.ZodNumber;
59
60
  }, z.core.$strip>>;
61
+ patch: z.ZodOptional<z.ZodString>;
60
62
  }, z.core.$strip>>>;
61
63
  }, z.core.$strip>, z.ZodObject<{
62
64
  kind: z.ZodLiteral<"other">;
@@ -72,6 +74,7 @@ export declare const toolResultSchema: z.ZodObject<{
72
74
  ok: "ok";
73
75
  }>;
74
76
  message: z.ZodOptional<z.ZodString>;
77
+ output: z.ZodOptional<z.ZodString>;
75
78
  exitCode: z.ZodOptional<z.ZodNumber>;
76
79
  }, z.core.$strip>;
77
80
  declare const toolEventSchema: z.ZodUnion<readonly [z.ZodObject<{
@@ -112,6 +115,7 @@ declare const toolEventSchema: z.ZodUnion<readonly [z.ZodObject<{
112
115
  added: z.ZodNumber;
113
116
  removed: z.ZodNumber;
114
117
  }, z.core.$strip>>;
118
+ patch: z.ZodOptional<z.ZodString>;
115
119
  }, z.core.$strip>>>;
116
120
  }, z.core.$strip>, z.ZodObject<{
117
121
  kind: z.ZodLiteral<"other">;
@@ -130,6 +134,7 @@ declare const toolEventSchema: z.ZodUnion<readonly [z.ZodObject<{
130
134
  ok: "ok";
131
135
  }>;
132
136
  message: z.ZodOptional<z.ZodString>;
137
+ output: z.ZodOptional<z.ZodString>;
133
138
  exitCode: z.ZodOptional<z.ZodNumber>;
134
139
  }, z.core.$strip>;
135
140
  type: z.ZodLiteral<"tool">;
@@ -167,6 +172,7 @@ declare const toolEventSchema: z.ZodUnion<readonly [z.ZodObject<{
167
172
  added: z.ZodNumber;
168
173
  removed: z.ZodNumber;
169
174
  }, z.core.$strip>>;
175
+ patch: z.ZodOptional<z.ZodString>;
170
176
  }, z.core.$strip>>>;
171
177
  }, z.core.$strip>, z.ZodObject<{
172
178
  kind: z.ZodLiteral<"other">;
@@ -195,6 +201,9 @@ export declare const agentEventSchema: z.ZodUnion<readonly [z.ZodObject<{
195
201
  }>>, z.ZodObject<{
196
202
  sessionId: z.ZodString;
197
203
  }, z.core.$strict>]>;
204
+ }, z.core.$strip>, z.ZodObject<{
205
+ type: z.ZodLiteral<"model">;
206
+ model: z.ZodOptional<z.ZodString>;
198
207
  }, z.core.$strip>, z.ZodObject<{
199
208
  type: z.ZodLiteral<"assistant">;
200
209
  text: z.ZodString;
@@ -241,6 +250,7 @@ export declare const agentEventSchema: z.ZodUnion<readonly [z.ZodObject<{
241
250
  added: z.ZodNumber;
242
251
  removed: z.ZodNumber;
243
252
  }, z.core.$strip>>;
253
+ patch: z.ZodOptional<z.ZodString>;
244
254
  }, z.core.$strip>>>;
245
255
  }, z.core.$strip>, z.ZodObject<{
246
256
  kind: z.ZodLiteral<"other">;
@@ -259,6 +269,7 @@ export declare const agentEventSchema: z.ZodUnion<readonly [z.ZodObject<{
259
269
  ok: "ok";
260
270
  }>;
261
271
  message: z.ZodOptional<z.ZodString>;
272
+ output: z.ZodOptional<z.ZodString>;
262
273
  exitCode: z.ZodOptional<z.ZodNumber>;
263
274
  }, z.core.$strip>;
264
275
  type: z.ZodLiteral<"tool">;
@@ -296,6 +307,7 @@ export declare const agentEventSchema: z.ZodUnion<readonly [z.ZodObject<{
296
307
  added: z.ZodNumber;
297
308
  removed: z.ZodNumber;
298
309
  }, z.core.$strip>>;
310
+ patch: z.ZodOptional<z.ZodString>;
299
311
  }, z.core.$strip>>>;
300
312
  }, z.core.$strip>, z.ZodObject<{
301
313
  kind: z.ZodLiteral<"other">;
@@ -11,6 +11,7 @@ export const fileChangeSchema = z.object({
11
11
  op: z.enum(["add", "update", "delete", "unspecified"]),
12
12
  path: text,
13
13
  diffstat: diffstatSchema.optional(),
14
+ patch: text.optional(),
14
15
  });
15
16
  export const toolCallSchema = z.union([
16
17
  z.object({ kind: z.literal("run"), command: text }),
@@ -33,6 +34,7 @@ export const toolCallSchema = z.union([
33
34
  export const toolResultSchema = z.object({
34
35
  status: z.enum(["ok", "error"]),
35
36
  message: text.optional(),
37
+ output: text.optional(),
36
38
  exitCode: z.number().int().safe().optional(),
37
39
  });
38
40
  const toolFields = { type: z.literal("tool"), id: text, name: text, call: toolCallSchema, truncated };
@@ -42,6 +44,10 @@ const toolEventSchema = z.union([
42
44
  ]);
43
45
  export const agentEventSchema = z.union([
44
46
  z.object({ type: z.literal("session"), coordinate: resumeCoordinateSchema }),
47
+ // One provider-neutral runtime-model observation in the shared agent-event
48
+ // vocabulary. Body admits it into the Soul's single current model; it is
49
+ // never appended to activity, so compaction cannot lose or resurrect it.
50
+ z.object({ type: z.literal("model"), model: text.optional() }),
45
51
  z.object({ type: z.literal("assistant"), text, truncated }),
46
52
  z.object({ type: z.literal("thought"), text, truncated }),
47
53
  toolEventSchema,
@@ -2,6 +2,7 @@ import type { AkumaPaths } from "../identity.js";
2
2
  import type { BodyEnd, BodyFact, ForkPoint, HeartSnapshot, KillFact, SealFact, SessionFact, Soul, TellDeliveryInput, TellFact, TellReceiptInput, TurnEndFact, TurnFact, TurnOutcome, TurnStartFact } from "./facts.js";
3
3
  import type { AgentEvent } from "./activity-schema.js";
4
4
  import { type StatusFactInput, type ActivityFactSlice } from "./timeline.js";
5
+ import { type HeartRead } from "./storage.js";
5
6
  export { HeartAbsentError, HeldAkumaLeash, classifyHeartSchema, initializeHeart, isHeartAbsent, probeLeash, } from "./storage.js";
6
7
  export { admitRequest, beginRequest, isRequestInputConflict, readNonterminalRequests, readRequest, refuseRequest, reserveRequest, serveRequest, serveUpstreamRequest, unproveRequest, voidRequest, } from "./request-authority.js";
7
8
  export { AkumaBusyError, life, lifeAt, projectTell } from "./facts.js";
@@ -19,10 +20,61 @@ export declare function appendActivity(paths: AkumaPaths, input: Readonly<{
19
20
  }>): Promise<number>;
20
21
  type ActivitySlice = ActivityFactSlice;
21
22
  export type { TimelineFact } from "./timeline.js";
23
+ export type CurrentModelUpdate = "recorded" | "unchanged" | "not-current-body";
24
+ /**
25
+ * Admit one runtime-model observation from the Body that is actually running.
26
+ * The model is the one mutable member of an otherwise frozen recipe, so only
27
+ * the exact latest Running Body may move it: evidence that arrives after that
28
+ * Body settled, or from a superseded Body, is refused rather than applied.
29
+ */
30
+ export declare function recordCurrentModel(paths: AkumaPaths, input: Readonly<{
31
+ bodySequence: number;
32
+ model: string | undefined;
33
+ }>): Promise<CurrentModelUpdate>;
22
34
  export declare function activitySlice(paths: AkumaPaths): Promise<ActivitySlice>;
23
35
  export declare function readStatusFacts(paths: AkumaPaths, input: StatusFactInput, signal?: AbortSignal): Promise<ActivitySlice["rows"]>;
24
- /** One live observation's successful raw input and decoded frontier, never a connection. */
36
+ /** One live observation's current retained timeline, or a no-change witness. */
37
+ export type TimelineRead = Readonly<{
38
+ changed: false;
39
+ }> | Readonly<{
40
+ changed: true;
41
+ born: boolean;
42
+ slice: ActivityFactSlice;
43
+ }>;
44
+ /**
45
+ * One operation-owned reader of the retained timeline. It keeps its read
46
+ * custody between observations, skips decoding when the same connection's
47
+ * change counter still names the observed snapshot, and renews that gate
48
+ * whenever a replaced Heart gives it a new connection.
49
+ */
50
+ export declare function createTimelineReader(paths: AkumaPaths): Readonly<{
51
+ read(signal?: AbortSignal): Promise<TimelineRead>;
52
+ clear(): void;
53
+ }>;
54
+ /** One observer owns its connection and successful decoded frontier. */
25
55
  export declare function createStatusFactReader(paths: AkumaPaths): {
56
+ life: (signal?: AbortSignal) => Promise<LifeSnapshot>;
57
+ tellOutcome: (id: string, signal?: AbortSignal) => Promise<{
58
+ tell: Readonly<{
59
+ kind: "tell";
60
+ sequence: number;
61
+ id: string;
62
+ body: string;
63
+ initiator?: string;
64
+ schemaJson?: string;
65
+ recordedAt: string;
66
+ state: "pending" | "told";
67
+ deliveries: readonly import("./facts.js").TellDelivery[];
68
+ binding?: import("./facts.js").TellBinding;
69
+ }> | null;
70
+ end: Readonly<{
71
+ kind: "turn-end";
72
+ sequence: number;
73
+ turnSequence: number;
74
+ outcome: TurnOutcome;
75
+ completedAt: string;
76
+ }> | undefined;
77
+ }>;
26
78
  clear(): void;
27
79
  read(input: StatusFactInput, signal?: AbortSignal): Promise<ActivitySlice["rows"]>;
28
80
  };
@@ -30,7 +82,7 @@ type LifeSnapshot = Pick<HeartSnapshot, "soul" | "latestBody" | "latestSession"
30
82
  hasPendingTell: boolean;
31
83
  }>;
32
84
  /** Lifecycle probes do not load narration, Tell bodies, or delivery histories. */
33
- export declare function readLifeSnapshot(paths: AkumaPaths, signal?: AbortSignal): Promise<LifeSnapshot>;
85
+ export declare function readLifeSnapshot(paths: AkumaPaths, signal?: AbortSignal, read?: HeartRead): Promise<LifeSnapshot>;
34
86
  export declare function recordTell(paths: AkumaPaths, tell: Omit<TellFact, "sequence" | "state" | "deliveries" | "binding">, options?: Readonly<{
35
87
  interrupt?: boolean;
36
88
  }>): Promise<Readonly<{
@@ -111,6 +163,11 @@ export declare function finishBodyIfIdle(paths: AkumaPaths, input: Readonly<{
111
163
  kind: "pending";
112
164
  tells: readonly string[];
113
165
  }>>;
166
+ export type BodyControlSnapshot = Pick<HeartSnapshot, "soul" | "pending" | "stop" | "pause">;
167
+ export declare function createBodyHeartReader(paths: AkumaPaths): {
168
+ clear: () => void;
169
+ read(): Promise<BodyControlSnapshot>;
170
+ };
114
171
  export declare function readHeart(paths: AkumaPaths): Promise<HeartSnapshot>;
115
172
  export declare function readLastAnsweredTurn(paths: AkumaPaths): Promise<TurnFact | null>;
116
173
  export declare function readForkPoint(paths: AkumaPaths, historyId: string): Promise<ForkPoint | null>;
@@ -1,9 +1,10 @@
1
1
  import { parsePublicHistoryId } from "../identity.js";
2
+ import { decodeRuntimeModel, withoutModel } from "../provider-recipe.js";
2
3
  import { answeredTurnFact, endBodyFact, finishBodyFact, deleteStopControl, insertActivityFact, insertTurnEndFact, insertTurnStartFact, insertPauseControl, insertKillFact, insertSessionFact, insertStopControl, killFactForBody, latestBodyFact, latestKillFact, latestSessionFact, latestTurnFactForBody, lastAnsweredTurnFact, pauseFact, sessionFactForCoordinate, stopFact, turnFact, } from "./rows.js";
3
4
  import { insertTellDeliveryFact, dispositionSnapshotProven, insertTellBindingFact, insertTellDispositionSnapshot, insertTellReceiptFact, insertUndeliveredTellReceipts, latestOpenTellDisposition, openBoundTurns, openTellDispositionIds, pendingTellFacts, hasPendingTell, resolveTellDispositionSnapshot, tellDispositionResolved, tellFact, tellIdsForFence, turnParticipants, } from "./tells.js";
4
- import { activityFactSlice, statusFacts, readStatusBatch, decodeTimelineBatch, lastActivityAt as readLastActivityAt, pruneActivityFacts, } from "./timeline.js";
5
- import { isHeartAbsent, readSealFromLeash, recordPendingTell, withHeartTransaction, withReadOnlyHeart, } from "./storage.js";
6
- import { soulFact } from "./soul.js";
5
+ import { activityFactSlice, statusFacts, readStatusBatch, decodeTimelineBatch, timelineDataVersion, lastActivityAt as readLastActivityAt, pruneActivityFacts, } from "./timeline.js";
6
+ import { createHeartReader, isHeartAbsent, readSealFromLeash, recordPendingTell, withHeartTransaction, withReadOnlyHeart, } from "./storage.js";
7
+ import { setSoulModel, soulFact } from "./soul.js";
7
8
  import { AkumaBusyError, HeartAuthorityCorruptionError } from "./facts.js";
8
9
  export { HeartAbsentError, HeldAkumaLeash, classifyHeartSchema, initializeHeart, isHeartAbsent, probeLeash, } from "./storage.js";
9
10
  export { admitRequest, beginRequest, isRequestInputConflict, readNonterminalRequests, readRequest, refuseRequest, reserveRequest, serveRequest, serveUpstreamRequest, unproveRequest, voidRequest, } from "./request-authority.js";
@@ -50,12 +51,57 @@ export async function appendActivity(paths, input) {
50
51
  return sequence;
51
52
  });
52
53
  }
54
+ /**
55
+ * Admit one runtime-model observation from the Body that is actually running.
56
+ * The model is the one mutable member of an otherwise frozen recipe, so only
57
+ * the exact latest Running Body may move it: evidence that arrives after that
58
+ * Body settled, or from a superseded Body, is refused rather than applied.
59
+ */
60
+ export async function recordCurrentModel(paths, input) {
61
+ const model = decodeRuntimeModel(input.model);
62
+ return await withHeartTransaction(paths, (heart) => {
63
+ const body = latestBodyFact(heart);
64
+ if (body === null || body.sequence !== input.bodySequence || body.end !== undefined || body.hung !== undefined) {
65
+ return "not-current-body";
66
+ }
67
+ return setSoulModel(heart, model) ? "recorded" : "unchanged";
68
+ });
69
+ }
53
70
  export async function activitySlice(paths) {
54
71
  return await withReadOnlyHeart(paths, (heart) => activityFactSlice(heart));
55
72
  }
56
73
  export async function readStatusFacts(paths, input, signal) {
57
74
  return await withReadOnlyHeart(paths, (heart) => statusFacts(heart, input), signal);
58
75
  }
76
+ /**
77
+ * One operation-owned reader of the retained timeline. It keeps its read
78
+ * custody between observations, skips decoding when the same connection's
79
+ * change counter still names the observed snapshot, and renews that gate
80
+ * whenever a replaced Heart gives it a new connection.
81
+ */
82
+ export function createTimelineReader(paths) {
83
+ const reader = createHeartReader(paths);
84
+ let generation = -1;
85
+ let version;
86
+ return {
87
+ clear: reader.clear,
88
+ async read(signal) {
89
+ // Publication follows COMMIT, so a failed read never leaves a memo behind.
90
+ return await reader.read((heart) => {
91
+ const current = reader.generation();
92
+ if (current !== generation) {
93
+ generation = current;
94
+ version = undefined;
95
+ }
96
+ const observed = timelineDataVersion(heart);
97
+ if (version !== undefined && observed === version)
98
+ return { changed: false };
99
+ version = observed;
100
+ return { changed: true, born: soulFact(heart) !== null, slice: activityFactSlice(heart) };
101
+ }, signal);
102
+ },
103
+ };
104
+ }
59
105
  // Each query returns a fixed set of scalar columns; compare JSON columns as text.
60
106
  function sameRawRows(left, right) {
61
107
  if (left.length !== right.length)
@@ -75,16 +121,24 @@ function sameStatusBatch(left, right) {
75
121
  sameRawRows(left.tells.deliveries, right.tells.deliveries) &&
76
122
  sameRawRows(left.tells.bindings, right.tells.bindings));
77
123
  }
78
- /** One live observation's successful raw input and decoded frontier, never a connection. */
124
+ /** One observer owns its connection and successful decoded frontier. */
79
125
  export function createStatusFactReader(paths) {
126
+ const reader = createHeartReader(paths);
80
127
  let memo;
81
128
  return {
129
+ life: (signal) => readLifeSnapshot(paths, signal, reader.read),
130
+ tellOutcome: (id, signal) => reader.read((heart) => {
131
+ const tell = tellFact(heart, id);
132
+ const end = tell?.binding === undefined ? undefined : turnFact(heart, tell.binding.turnSequence)?.end;
133
+ return { tell, end };
134
+ }, signal),
82
135
  clear() {
136
+ reader.clear();
83
137
  memo = undefined;
84
138
  },
85
139
  async read(input, signal) {
86
140
  try {
87
- const next = await withReadOnlyHeart(paths, (heart) => {
141
+ const next = await reader.read((heart) => {
88
142
  const batch = readStatusBatch(heart, input);
89
143
  if (memo !== undefined && sameStatusBatch(batch, memo.batch))
90
144
  return memo;
@@ -95,6 +149,7 @@ export function createStatusFactReader(paths) {
95
149
  return next.facts;
96
150
  }
97
151
  catch (error) {
152
+ reader.clear();
98
153
  memo = undefined;
99
154
  throw error;
100
155
  }
@@ -102,9 +157,9 @@ export function createStatusFactReader(paths) {
102
157
  };
103
158
  }
104
159
  /** Lifecycle probes do not load narration, Tell bodies, or delivery histories. */
105
- export async function readLifeSnapshot(paths, signal) {
160
+ export async function readLifeSnapshot(paths, signal, read = (body, signal) => withReadOnlyHeart(paths, body, signal)) {
106
161
  try {
107
- return await withReadOnlyHeart(paths, (heart) => ({
162
+ return await read((heart) => ({
108
163
  soul: soulFact(heart),
109
164
  latestBody: latestBodyFact(heart),
110
165
  latestSession: latestSessionFact(heart),
@@ -321,6 +376,27 @@ export async function finishBodyIfIdle(paths, input) {
321
376
  return { kind: "finished" };
322
377
  });
323
378
  }
379
+ export function createBodyHeartReader(paths) {
380
+ const reader = createHeartReader(paths);
381
+ return {
382
+ clear: reader.clear,
383
+ async read() {
384
+ try {
385
+ return await reader.read((heart) => ({
386
+ soul: soulFact(heart),
387
+ pending: pendingTellFacts(heart),
388
+ stop: stopFact(heart),
389
+ pause: pauseFact(heart),
390
+ }));
391
+ }
392
+ catch (error) {
393
+ if (isHeartAbsent(error))
394
+ return { soul: null, pending: [], stop: null, pause: null };
395
+ throw error;
396
+ }
397
+ },
398
+ };
399
+ }
324
400
  export async function readHeart(paths) {
325
401
  try {
326
402
  return await withReadOnlyHeart(paths, (heart) => ({
@@ -366,12 +442,15 @@ export async function readForkPoint(paths, historyId) {
366
442
  const recipe = sessionFactForCoordinate(heart, outcome.session);
367
443
  if (recipe === null)
368
444
  throw new Error(`Akuma fork point ${historyId} has no session recipe`);
445
+ // The source's current model is the admitted child selection. The session
446
+ // row's obsolete copy is dropped, and an absent current model stays absent.
447
+ const current = soulFact(heart)?.options.model;
369
448
  return {
370
449
  historyId: outcome.historyId,
371
450
  session: outcome.session,
372
451
  provider: recipe.provider,
373
452
  cwd: recipe.cwd,
374
- options: recipe.options,
453
+ options: { ...withoutModel(recipe.options), ...(current === undefined ? {} : { model: current }) },
375
454
  };
376
455
  });
377
456
  }
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { providerOptionsSchema } from "../provider-recipe.js";
2
+ import { providerOptionsSchema, withoutModel } from "../provider-recipe.js";
3
3
  import { resumeCoordinateSchema } from "../coordinate.js";
4
4
  import { decodeResumeCoordinate, encodeResumeCoordinate as encodeCoordinate } from "../coordinate.js";
5
5
  import { agentEventSchema } from "./activity-schema.js";
@@ -32,7 +32,7 @@ export function encodeSessionRow(session) {
32
32
  session.provider,
33
33
  encodeResumeCoordinate(session.coordinate),
34
34
  session.cwd,
35
- json(session.options),
35
+ json(withoutModel(session.options)),
36
36
  session.admittedAt,
37
37
  ];
38
38
  }
@@ -45,7 +45,9 @@ export function decodeSessionRow(row) {
45
45
  provider: row.provider,
46
46
  coordinate: resumeCoordinate(parsed(row.coordinate_json)),
47
47
  cwd: row.cwd,
48
- options: parsed(row.options_json),
48
+ // A session's own model copy is obsolete: the Soul owns the one current
49
+ // model, so an old row never re-admits the model it happened to start with.
50
+ options: withoutModel(parsed(row.options_json)),
49
51
  admittedAt: row.admitted_at,
50
52
  };
51
53
  }
@@ -7,3 +7,12 @@ export declare function encodeSoulRow(soul: Soul): readonly [string];
7
7
  export declare function decodeSoulRow(row: SoulRow): Soul;
8
8
  export declare function soulFact(database: DatabaseSync): Soul | null;
9
9
  export declare function insertSoulFact(database: DatabaseSync, soul: Soul): void;
10
+ /**
11
+ * Replace only the current-model member of the singleton Soul and report
12
+ * whether the stored value actually moved. Every other raw member is carried
13
+ * through byte-identically: the codec normalizes recipe options, origin and
14
+ * allowed, so decoding and re-encoding a whole Soul here would silently
15
+ * rewrite fields this update never touched. A Soul this build cannot read is
16
+ * refused rather than mutated.
17
+ */
18
+ export declare function setSoulModel(database: DatabaseSync, model: string | undefined): boolean;
@@ -78,3 +78,32 @@ export function soulFact(database) {
78
78
  export function insertSoulFact(database, soul) {
79
79
  database.prepare("INSERT INTO soul(singleton, soul_json) VALUES (1, ?)").run(...encodeSoulRow(soul));
80
80
  }
81
+ /**
82
+ * Replace only the current-model member of the singleton Soul and report
83
+ * whether the stored value actually moved. Every other raw member is carried
84
+ * through byte-identically: the codec normalizes recipe options, origin and
85
+ * allowed, so decoding and re-encoding a whole Soul here would silently
86
+ * rewrite fields this update never touched. A Soul this build cannot read is
87
+ * refused rather than mutated.
88
+ */
89
+ export function setSoulModel(database, model) {
90
+ const row = database.prepare("SELECT soul_json FROM soul WHERE singleton = 1").get();
91
+ if (row === undefined)
92
+ throw new Error("Akuma soul is absent");
93
+ const raw = JSON.parse(row.soul_json);
94
+ decodeSoul(raw);
95
+ const soul = record(raw);
96
+ const options = record(soul.options);
97
+ if (options === null)
98
+ throw new Error("Akuma soul options must be an object");
99
+ const current = options.model;
100
+ if (model === undefined ? current === undefined : current === model)
101
+ return false;
102
+ const next = { ...options };
103
+ if (model === undefined)
104
+ delete next.model;
105
+ else
106
+ next.model = model;
107
+ database.prepare("UPDATE soul SET soul_json = ? WHERE singleton = 1").run(JSON.stringify({ ...soul, options: next }));
108
+ return true;
109
+ }
@@ -9,6 +9,21 @@ export declare class HeartAbsentError extends Error {
9
9
  export declare function isHeartAbsent(error: unknown): error is HeartAbsentError;
10
10
  export declare function classifyHeartSchema(paths: AkumaPaths): Promise<"current" | "unsupported">;
11
11
  export declare function withReadOnlyHeart<T>(paths: AkumaPaths, body: (database: DatabaseSync) => T, signal?: AbortSignal): Promise<T>;
12
+ export type HeartRead = <T>(body: (database: DatabaseSync) => T, signal?: AbortSignal) => Promise<T>;
13
+ export type HeartReader = Readonly<{
14
+ read: HeartRead;
15
+ clear(): void;
16
+ /** Counts opened connections, so a caller's own reuse cannot survive a replaced Heart. */
17
+ generation(): number;
18
+ }>;
19
+ /**
20
+ * A bounded observer owns its connection between observations, never a
21
+ * transaction: each read settles the transaction it began, and a removed or
22
+ * replaced Heart invalidates the retained custody through file identity.
23
+ * Release never interrupts an executing read; it settles after the current
24
+ * observation, so a caller may release while a pull is still in flight.
25
+ */
26
+ export declare function createHeartReader(paths: AkumaPaths): HeartReader;
12
27
  export declare function withHeartTransaction<T>(paths: AkumaPaths, body: (database: DatabaseSync) => T, signal?: AbortSignal): Promise<T>;
13
28
  /** Insert one already-admitted pending Tell into an already-open Heart transaction. */
14
29
  export declare function recordPendingTell(heart: DatabaseSync, tell: Omit<TellFact, "sequence" | "state" | "deliveries" | "binding">): Readonly<{
@@ -112,6 +112,61 @@ export async function withReadOnlyHeart(paths, body, signal) {
112
112
  database.close();
113
113
  }
114
114
  }
115
+ /**
116
+ * A bounded observer owns its connection between observations, never a
117
+ * transaction: each read settles the transaction it began, and a removed or
118
+ * replaced Heart invalidates the retained custody through file identity.
119
+ * Release never interrupts an executing read; it settles after the current
120
+ * observation, so a caller may release while a pull is still in flight.
121
+ */
122
+ export function createHeartReader(paths) {
123
+ let database;
124
+ let identity;
125
+ let tail = Promise.resolve();
126
+ let opened = 0;
127
+ const release = () => {
128
+ const retained = database;
129
+ database = undefined;
130
+ identity = undefined;
131
+ try {
132
+ retained?.close();
133
+ }
134
+ catch {
135
+ // A connection already released by its own failed read stays released.
136
+ }
137
+ };
138
+ const clear = () => {
139
+ tail = tail.then(release, release);
140
+ };
141
+ const read = (body, signal) => {
142
+ const pending = tail.then(async () => {
143
+ try {
144
+ signal?.throwIfAborted();
145
+ const current = await lstat(paths.heart).catch((error) => {
146
+ if (error.code === "ENOENT")
147
+ throw new HeartAbsentError(paths.heart, { cause: error });
148
+ throw error;
149
+ });
150
+ if (identity?.dev !== current.dev || identity?.ino !== current.ino)
151
+ release();
152
+ if (database === undefined) {
153
+ database = await openHeart(paths.heart, "ro");
154
+ identity = current;
155
+ opened += 1;
156
+ }
157
+ await beginTransaction(database, "read", signal);
158
+ return finishTransaction(database, body);
159
+ }
160
+ catch (error) {
161
+ release();
162
+ throw error;
163
+ }
164
+ });
165
+ tail = pending.catch(() => undefined);
166
+ return pending;
167
+ };
168
+ return { read, clear, generation: () => opened };
169
+ }
115
170
  export async function withHeartTransaction(paths, body, signal) {
116
171
  signal?.throwIfAborted();
117
172
  const database = await openHeart(paths.heart, "rw");