@astrosheep/keiyaku 4.0.6 → 4.1.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 (251) hide show
  1. package/README.md +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -0
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +3 -0
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -6
  5. package/build/src/akuma/akuma.d.ts +17 -14
  6. package/build/src/akuma/akuma.js +122 -114
  7. package/build/src/akuma/allowed.d.ts +1 -0
  8. package/build/src/akuma/allowed.js +6 -0
  9. package/build/src/akuma/archetype.js +12 -2
  10. package/build/src/akuma/body-turn.js +27 -15
  11. package/build/src/akuma/body.d.ts +44 -3
  12. package/build/src/akuma/body.js +142 -33
  13. package/build/src/akuma/coordinate.js +17 -7
  14. package/build/src/akuma/heart/facts.js +1 -1
  15. package/build/src/akuma/heart/index.d.ts +1 -1
  16. package/build/src/akuma/heart/index.js +2 -4
  17. package/build/src/akuma/heart/request-authority.js +4 -4
  18. package/build/src/akuma/heart/request-rows.js +27 -16
  19. package/build/src/akuma/heart/rows.js +79 -41
  20. package/build/src/akuma/heart/soul.js +1 -1
  21. package/build/src/akuma/heart/storage.d.ts +1 -0
  22. package/build/src/akuma/heart/storage.js +76 -11
  23. package/build/src/akuma/heart/tells.js +35 -18
  24. package/build/src/akuma/heart/timeline.js +19 -11
  25. package/build/src/akuma/identity.d.ts +1 -0
  26. package/build/src/akuma/identity.js +1 -0
  27. package/build/src/akuma/index.d.ts +3 -3
  28. package/build/src/akuma/index.js +1 -1
  29. package/build/src/akuma/nuke.js +106 -14
  30. package/build/src/akuma/projection.d.ts +11 -9
  31. package/build/src/akuma/projection.js +110 -53
  32. package/build/src/akuma/provider-recipe.d.ts +2 -0
  33. package/build/src/akuma/provider-recipe.js +21 -5
  34. package/build/src/akuma/provider.js +105 -62
  35. package/build/src/akuma/providers/acp/config.d.ts +2 -0
  36. package/build/src/akuma/providers/acp/config.js +13 -6
  37. package/build/src/akuma/providers/acp/core.d.ts +5 -2
  38. package/build/src/akuma/providers/acp/core.js +45 -25
  39. package/build/src/akuma/providers/acp/events.js +38 -29
  40. package/build/src/akuma/providers/acp/index.js +18 -8
  41. package/build/src/akuma/providers/claude/events.js +8 -12
  42. package/build/src/akuma/providers/claude/index.d.ts +2 -1
  43. package/build/src/akuma/providers/claude/index.js +59 -26
  44. package/build/src/akuma/providers/claude/input.js +6 -2
  45. package/build/src/akuma/providers/codex-app-server/events.js +61 -36
  46. package/build/src/akuma/providers/codex-app-server/index.js +29 -14
  47. package/build/src/akuma/providers/grok-build/index.js +28 -17
  48. package/build/src/akuma/providers/index.js +3 -19
  49. package/build/src/akuma/providers/opencode-sdk/events.js +32 -18
  50. package/build/src/akuma/providers/opencode-sdk/index.js +29 -17
  51. package/build/src/akuma/providers/opencode-sdk/session.js +21 -5
  52. package/build/src/akuma/providers/pi/events.js +26 -19
  53. package/build/src/akuma/providers/pi/index.js +58 -38
  54. package/build/src/akuma/publication.js +9 -9
  55. package/build/src/akuma/request-serve.js +37 -40
  56. package/build/src/akuma/request-wire.d.ts +2 -2
  57. package/build/src/akuma/request-wire.js +75 -102
  58. package/build/src/akuma/requests.js +2 -2
  59. package/build/src/akuma-body.js +6 -5
  60. package/build/src/body/amend.d.ts +4 -1
  61. package/build/src/body/amend.js +14 -15
  62. package/build/src/body/decode.js +2 -4
  63. package/build/src/body/envelope.js +2 -4
  64. package/build/src/body/region.d.ts +1 -2
  65. package/build/src/body/region.js +4 -72
  66. package/build/src/body/render.js +17 -12
  67. package/build/src/body/shape.js +1 -7
  68. package/build/src/cli/accepted.d.ts +1 -1
  69. package/build/src/cli/accepted.js +10 -1
  70. package/build/src/cli/commands/akuma-invoke.js +18 -6
  71. package/build/src/cli/commands/akuma.js +10 -13
  72. package/build/src/cli/commands/amend.d.ts +2 -2
  73. package/build/src/cli/commands/amend.js +2 -4
  74. package/build/src/cli/commands/bind.d.ts +4 -4
  75. package/build/src/cli/commands/bind.js +16 -6
  76. package/build/src/cli/commands/contract.d.ts +99 -10
  77. package/build/src/cli/commands/contract.js +128 -24
  78. package/build/src/cli/commands/install.d.ts +1 -1
  79. package/build/src/cli/commands/install.js +23 -15
  80. package/build/src/cli/commands/task-invoke.d.ts +4 -1
  81. package/build/src/cli/commands/task-invoke.js +191 -68
  82. package/build/src/cli/commands/task-query.js +48 -24
  83. package/build/src/cli/commands/task.js +150 -30
  84. package/build/src/cli/coordinates.js +2 -4
  85. package/build/src/cli/invoke.js +88 -44
  86. package/build/src/cli/main.js +4 -2
  87. package/build/src/cli/parse.d.ts +2 -89
  88. package/build/src/cli/parse.js +87 -79
  89. package/build/src/cli/render/akuma-tool-command.js +6 -7
  90. package/build/src/cli/render/akuma-tool.js +10 -8
  91. package/build/src/cli/render/akuma.js +149 -53
  92. package/build/src/cli/render/audit.js +5 -15
  93. package/build/src/cli/render/board.d.ts +1 -0
  94. package/build/src/cli/render/board.js +17 -0
  95. package/build/src/cli/render/catalog.js +84 -7
  96. package/build/src/cli/render/contract-observation.d.ts +10 -0
  97. package/build/src/cli/render/contract-observation.js +58 -0
  98. package/build/src/cli/render/contract.js +57 -54
  99. package/build/src/cli/render/kanshi.js +281 -209
  100. package/build/src/cli/render/receipt.d.ts +1 -0
  101. package/build/src/cli/render/receipt.js +20 -23
  102. package/build/src/cli/render/refusal.js +16 -15
  103. package/build/src/cli/render/region.js +22 -6
  104. package/build/src/cli/render/settings.js +4 -4
  105. package/build/src/cli/render/task.js +93 -21
  106. package/build/src/cli/render/terminal.js +16 -5
  107. package/build/src/cli/result.d.ts +11 -4
  108. package/build/src/cli/runtime.d.ts +6 -1
  109. package/build/src/cli/runtime.js +84 -63
  110. package/build/src/cli/selectors.js +2 -4
  111. package/build/src/cli/usage.js +6 -5
  112. package/build/src/contract-worktree.d.ts +1 -25
  113. package/build/src/contract-worktree.js +87 -174
  114. package/build/src/coordination/durable-file.js +7 -3
  115. package/build/src/coordination/sqlite-transaction-lock.js +29 -12
  116. package/build/src/core/facts/codec.js +7 -3
  117. package/build/src/core/facts/types.d.ts +1 -1
  118. package/build/src/core/subject.js +16 -9
  119. package/build/src/core/verbs/abandon.d.ts +1 -1
  120. package/build/src/core/verbs/abandon.js +1 -1
  121. package/build/src/core/verbs/amend.d.ts +1 -1
  122. package/build/src/core/verbs/amend.js +7 -7
  123. package/build/src/core/verbs/attestation.js +4 -1
  124. package/build/src/core/verbs/bind.d.ts +1 -1
  125. package/build/src/core/verbs/bind.js +1 -1
  126. package/build/src/core/verbs/placement.d.ts +1 -1
  127. package/build/src/core/verbs/placement.js +10 -8
  128. package/build/src/dispatch/index.js +8 -6
  129. package/build/src/duration.js +5 -1
  130. package/build/src/git/admission.js +5 -3
  131. package/build/src/git/hooks.d.ts +8 -0
  132. package/build/src/git/hooks.js +63 -14
  133. package/build/src/git/identity.js +1 -4
  134. package/build/src/git/integration.d.ts +2 -3
  135. package/build/src/git/integration.js +32 -39
  136. package/build/src/git/nuke.d.ts +1 -1
  137. package/build/src/git/nuke.js +110 -62
  138. package/build/src/git/observe.d.ts +13 -1
  139. package/build/src/git/observe.js +51 -44
  140. package/build/src/git/process.js +4 -2
  141. package/build/src/git/read-observation.js +10 -8
  142. package/build/src/git/reconcile.d.ts +14 -3
  143. package/build/src/git/reconcile.js +123 -102
  144. package/build/src/git/repository.d.ts +2 -0
  145. package/build/src/git/repository.js +13 -22
  146. package/build/src/git/target-placement.d.ts +9 -10
  147. package/build/src/git/target-placement.js +59 -62
  148. package/build/src/git/tender.d.ts +13 -2
  149. package/build/src/git/tender.js +28 -22
  150. package/build/src/git/terminal-seal.js +15 -12
  151. package/build/src/git/tree.js +5 -2
  152. package/build/src/git/workspace.d.ts +24 -2
  153. package/build/src/git/workspace.js +120 -16
  154. package/build/src/identity/coordinates.js +3 -1
  155. package/build/src/identity/selector.js +6 -3
  156. package/build/src/kanshi/fleet.d.ts +6 -0
  157. package/build/src/kanshi/fleet.js +27 -0
  158. package/build/src/kanshi/index.d.ts +1 -1
  159. package/build/src/kanshi/read.js +77 -45
  160. package/build/src/kanshi/report.d.ts +13 -24
  161. package/build/src/kanshi/select.js +25 -24
  162. package/build/src/library/address.d.ts +2 -1
  163. package/build/src/library/address.js +7 -8
  164. package/build/src/library/akuma-creation.js +20 -12
  165. package/build/src/library/audit.d.ts +0 -1
  166. package/build/src/library/audit.js +1 -2
  167. package/build/src/library/bind.d.ts +53 -5
  168. package/build/src/library/bind.js +98 -22
  169. package/build/src/library/catalog.d.ts +1 -0
  170. package/build/src/library/catalog.js +6 -3
  171. package/build/src/library/configuration.js +3 -1
  172. package/build/src/library/continuation.d.ts +37 -0
  173. package/build/src/library/continuation.js +86 -0
  174. package/build/src/library/contract.d.ts +39 -45
  175. package/build/src/library/contract.js +141 -118
  176. package/build/src/library/delivery.d.ts +18 -13
  177. package/build/src/library/fleet.d.ts +1 -1
  178. package/build/src/library/fleet.js +19 -11
  179. package/build/src/library/input.d.ts +2 -0
  180. package/build/src/library/input.js +14 -0
  181. package/build/src/library/keiyaku.d.ts +8 -7
  182. package/build/src/library/keiyaku.js +3 -3
  183. package/build/src/library/mutation.js +11 -4
  184. package/build/src/library/nuke.js +14 -5
  185. package/build/src/library/reconcile.d.ts +9 -4
  186. package/build/src/library/reconcile.js +30 -24
  187. package/build/src/library/refusal.d.ts +13 -5
  188. package/build/src/library/refusal.js +7 -0
  189. package/build/src/library/region.d.ts +8 -7
  190. package/build/src/library/region.js +31 -11
  191. package/build/src/library/repo.js +2 -2
  192. package/build/src/markdown/lex.js +9 -2
  193. package/build/src/markdown/parse.js +11 -7
  194. package/build/src/markdown/query.js +4 -2
  195. package/build/src/protocol/amend.js +3 -1
  196. package/build/src/protocol/attempt.d.ts +7 -3
  197. package/build/src/protocol/attempt.js +9 -5
  198. package/build/src/protocol/audit.d.ts +1 -1
  199. package/build/src/protocol/audit.js +4 -14
  200. package/build/src/protocol/bind.d.ts +19 -6
  201. package/build/src/protocol/bind.js +28 -13
  202. package/build/src/protocol/completion.d.ts +0 -1
  203. package/build/src/protocol/completion.js +1 -3
  204. package/build/src/protocol/deliver.d.ts +12 -4
  205. package/build/src/protocol/deliver.js +60 -51
  206. package/build/src/protocol/intent.d.ts +1 -0
  207. package/build/src/protocol/intent.js +9 -7
  208. package/build/src/protocol/operations.d.ts +10 -9
  209. package/build/src/protocol/operations.js +5 -3
  210. package/build/src/protocol/placement.d.ts +0 -1
  211. package/build/src/protocol/placement.js +6 -6
  212. package/build/src/protocol/read/observation.d.ts +11 -0
  213. package/build/src/protocol/read/observation.js +37 -0
  214. package/build/src/protocol/read/status.d.ts +23 -12
  215. package/build/src/protocol/read/status.js +90 -72
  216. package/build/src/protocol/reconcile.d.ts +1 -0
  217. package/build/src/protocol/reconcile.js +2 -3
  218. package/build/src/protocol/reintegrate.js +1 -1
  219. package/build/src/protocol/review.d.ts +0 -1
  220. package/build/src/protocol/review.js +15 -22
  221. package/build/src/protocol/run.d.ts +1 -0
  222. package/build/src/protocol/run.js +4 -5
  223. package/build/src/runtime/proc/launch.d.ts +33 -0
  224. package/build/src/runtime/proc/launch.js +98 -0
  225. package/build/src/runtime/proc/line-rpc.js +9 -3
  226. package/build/src/runtime/proc/run.d.ts +12 -6
  227. package/build/src/runtime/proc/run.js +78 -21
  228. package/build/src/runtime/proc/stdio.js +7 -10
  229. package/build/src/settings.js +6 -2
  230. package/build/src/settlement/fence.d.ts +0 -2
  231. package/build/src/settlement/fence.js +0 -24
  232. package/build/src/settlement/holder.js +13 -10
  233. package/build/src/settlement/settle.js +39 -6
  234. package/build/src/task/board.d.ts +21 -2
  235. package/build/src/task/board.js +35 -16
  236. package/build/src/task/compose.js +85 -22
  237. package/build/src/task/context.js +21 -8
  238. package/build/src/task/document.js +46 -10
  239. package/build/src/task/identity.js +19 -6
  240. package/build/src/task/index.js +118 -27
  241. package/build/src/task/input.js +29 -7
  242. package/build/src/task/mutation.js +61 -19
  243. package/build/src/task/operations.d.ts +8 -2
  244. package/build/src/task/operations.js +97 -32
  245. package/build/src/task/query.js +57 -28
  246. package/build/src/task/store.d.ts +5 -1
  247. package/build/src/task/store.js +88 -95
  248. package/build/src/verification/execution.js +14 -11
  249. package/build/src/workspace-place.js +19 -7
  250. package/build/src/world.js +16 -6
  251. package/package.json +8 -3
@@ -1,7 +1,7 @@
1
1
  import { abortableDelay } from "./abort.js";
2
2
  import { appendActivity, beginTurn, breakBody, heartExists, readHeart, recordSession, recordTellDeliveries, recordTellReceipt, } from "./heart/index.js";
3
- import { encodeAgentEvent, } from "./provider.js";
4
- import { BodyRequestPump, } from "./request-serve.js";
3
+ import { encodeAgentEvent } from "./provider.js";
4
+ import { BodyRequestPump } from "./request-serve.js";
5
5
  const BODY_CONTROL_OBSERVATION_MS = 100;
6
6
  export const CONTROL_RESPONSE_MS = 1_000;
7
7
  export class BodySupervisor {
@@ -26,8 +26,12 @@ export class BodySupervisor {
26
26
  static async open(paths, bodySequence, leash) {
27
27
  return new BodySupervisor(paths, bodySequence, leash, await readHeart(paths));
28
28
  }
29
- get reason() { return this.stopping; }
30
- current() { return this.observation; }
29
+ get reason() {
30
+ return this.stopping;
31
+ }
32
+ current() {
33
+ return this.observation;
34
+ }
31
35
  async recordHung(diagnostic, at) {
32
36
  await this.leash.recordBodyHung(this.paths, { sequence: this.bodySequence, diagnostic, at });
33
37
  }
@@ -38,15 +42,15 @@ export class BodySupervisor {
38
42
  next(after) {
39
43
  if (this.observation !== after)
40
44
  return Promise.resolve(this.observation);
41
- return new Promise((resolve) => { this.waiter = resolve; });
45
+ return new Promise((resolve) => {
46
+ this.waiter = resolve;
47
+ });
42
48
  }
43
49
  cancel(reason) {
44
50
  if (this.stopping !== undefined)
45
51
  return;
46
52
  this.stopping = reason;
47
- this.controller.abort(new Error(reason === "control"
48
- ? "Akuma Body interrupted by durable control"
49
- : "Akuma Heart disappeared"));
53
+ this.controller.abort(new Error(reason === "control" ? "Akuma Body interrupted by durable control" : "Akuma Heart disappeared"));
50
54
  this.waiter?.(this.observation);
51
55
  this.waiter = undefined;
52
56
  }
@@ -63,7 +67,7 @@ export class BodySupervisor {
63
67
  async observe() {
64
68
  for (;;) {
65
69
  const snapshot = this.observation;
66
- if (!await heartExists(this.paths))
70
+ if (!(await heartExists(this.paths)))
67
71
  return this.cancel("heart-gone");
68
72
  if (snapshot.stop?.bodySequence === this.bodySequence || snapshot.pause?.bodySequence === this.bodySequence) {
69
73
  return this.cancel("control");
@@ -119,7 +123,9 @@ async function failedTurnSetup(input, turnSequence, requests, cancelDrive, error
119
123
  try {
120
124
  await requests.close();
121
125
  }
122
- catch { /* Preserve the setup or pump failure. */ }
126
+ catch {
127
+ /* Preserve the setup or pump failure. */
128
+ }
123
129
  input.supervisor.signal.removeEventListener("abort", cancelDrive);
124
130
  if (input.supervisor.signal.aborted) {
125
131
  if (input.supervisor.reason === "heart-gone")
@@ -255,7 +261,9 @@ async function retireProviderCustody(input, turnSequence, drive) {
255
261
  at,
256
262
  });
257
263
  }
258
- catch { /* Undisposed provider custody still owns the leash. */ }
264
+ catch {
265
+ /* Undisposed provider custody still owns the leash. */
266
+ }
259
267
  }
260
268
  await breakBody(input.paths, { sequence: input.bodySequence, end: "broke-off", at: input.now() });
261
269
  return { kind: "hung" };
@@ -291,14 +299,16 @@ async function submitPendingLiveTells(writers, pending, attempted, tellLive) {
291
299
  const submission = await tellLive({ id: tell.id, text: tell.body });
292
300
  if (!mayWrite() || submission.kind === "turn-ended")
293
301
  return "turn-ended";
294
- await recordTellDeliveries(input.paths, [{
302
+ await recordTellDeliveries(input.paths, [
303
+ {
295
304
  tellId: tell.id,
296
305
  route: "live",
297
306
  turnSequence,
298
307
  fence: submission.fence,
299
308
  receipt: drive.receipts === undefined ? "unavailable" : "required",
300
309
  deliveredAt: input.now(),
301
- }]);
310
+ },
311
+ ]);
302
312
  return "accepted";
303
313
  });
304
314
  if (outcome === "turn-ended")
@@ -400,7 +410,9 @@ async function consumeTurnDrive(input, active) {
400
410
  try {
401
411
  await requests.close();
402
412
  }
403
- catch { /* Preserve the first pump failure. */ }
413
+ catch {
414
+ /* Preserve the first pump failure. */
415
+ }
404
416
  active.releaseDriveSignal();
405
417
  if (retirement.kind === "hung")
406
418
  return retirement;
@@ -416,7 +428,7 @@ export async function driveTurn(input) {
416
428
  active = started;
417
429
  }
418
430
  catch (error) {
419
- if (!await heartExists(input.paths)) {
431
+ if (!(await heartExists(input.paths))) {
420
432
  input.supervisor.cancel("heart-gone");
421
433
  return { kind: "stopped" };
422
434
  }
@@ -2,18 +2,59 @@ import { type SessionFact, type Soul } from "./heart/index.js";
2
2
  import type { AkumaPaths } from "./identity.js";
3
3
  import type { ProviderAdapter } from "./provider.js";
4
4
  import { type UpstreamExecutionPort, type RequestChildLaunch } from "./request-serve.js";
5
+ import { type OwnedProcess, type RunLogReference } from "../runtime/proc/run.js";
6
+ export declare const LEASH_HELD_EXIT = 75;
5
7
  export { CONTROL_RESPONSE_MS } from "./body-turn.js";
6
8
  export type BodyLaunch = Readonly<{
7
9
  paths: AkumaPaths;
8
10
  seed?: Omit<Soul, "createdAt">;
9
11
  birthSession?: Omit<SessionFact, "sequence">;
10
12
  initialBody?: string;
13
+ refuseIfHeld?: boolean;
14
+ }>;
15
+ export type TellWake = Readonly<{
16
+ kind: "told";
17
+ }> | Readonly<{
18
+ kind: "pursuing";
19
+ bodySequence: number;
20
+ }> | Readonly<{
21
+ kind: "held";
22
+ }> | Readonly<{
23
+ kind: "failed";
24
+ diagnostic: string;
25
+ child?: Readonly<{
26
+ code: number | null;
27
+ signal: string | null;
28
+ log: RunLogReference;
29
+ }>;
30
+ }>;
31
+ export type TellResult = Readonly<{
32
+ admission: Readonly<{
33
+ tellId: string;
34
+ fact: "recorded";
35
+ }>;
36
+ wake: TellWake;
37
+ }>;
38
+ export type TellWakeRuntime = Readonly<{
39
+ observeHeart(paths: AkumaPaths, signal: AbortSignal): Promise<AsyncGenerator<void>>;
40
+ spawn(paths: AkumaPaths): Promise<OwnedProcess>;
11
41
  }>;
12
42
  type BodyRuntime = Readonly<{
13
43
  now(): string;
14
44
  spawnChild?(launch: RequestChildLaunch): Promise<void>;
45
+ spawnBody?(launch: BodyLaunch): Promise<OwnedProcess>;
15
46
  upstream?: UpstreamExecutionPort;
16
47
  }>;
17
- export declare function driveAkumaBody(launch: BodyLaunch, adapter?: ProviderAdapter, runtime?: BodyRuntime): Promise<void>;
18
- export declare function spawnAkumaBody(launch: BodyLaunch): Promise<void>;
19
- export declare function runAkumaBody(launch: BodyLaunch, upstream: UpstreamExecutionPort): Promise<void>;
48
+ export declare function bodyProcessInput(launch: BodyLaunch, bodyModuleUrl?: string): Promise<{
49
+ argv: string[];
50
+ cwd: string;
51
+ env: {
52
+ KEIYAKU_ACTOR_ID: import("./identity.js").AkuId;
53
+ };
54
+ log: string;
55
+ }>;
56
+ export declare function handoffPendingTells(paths: AkumaPaths, spawn?: (launch: BodyLaunch) => Promise<OwnedProcess>): Promise<void>;
57
+ export declare function driveAkumaBody(launch: BodyLaunch, adapter?: ProviderAdapter, runtime?: BodyRuntime): Promise<"held" | void>;
58
+ export declare function spawnAkumaBody(launch: BodyLaunch): Promise<OwnedProcess>;
59
+ export declare function runAkumaBody(launch: BodyLaunch, upstream: UpstreamExecutionPort): Promise<"held" | void>;
60
+ export declare function wakeRecordedTell(paths: AkumaPaths, tellId: string, runtime?: TellWakeRuntime): Promise<TellResult>;
@@ -1,30 +1,33 @@
1
1
  import { fileURLToPath } from "node:url";
2
2
  import { abortableDelay } from "./abort.js";
3
3
  import { BodySupervisor, driveTurn, turnRecipe } from "./body-turn.js";
4
- import { HeldAkumaLeash, breakBody, endTurn, finishBodyIfIdle, heartExists, isHeartAbsent, readHeart, } from "./heart/index.js";
4
+ import { HeldAkumaLeash, breakBody, endTurn, finishBodyIfIdle, heartExists, isHeartAbsent, readHeart, readNonterminalRequests, watchHeart, } from "./heart/index.js";
5
5
  import { resolveProviderExecution } from "./providers/index.js";
6
6
  import { clearBodyRequestTransport, settleBodyRequests, } from "./request-serve.js";
7
- import { spawnDetachedProcess, } from "../runtime/proc/run.js";
8
- const LEASH_RETRY_MS = 25;
7
+ import { handoffProcess, spawnDetachedProcess, } from "../runtime/proc/run.js";
8
+ const LEASH_RETRY_MS = 100;
9
+ export const LEASH_HELD_EXIT = 75;
9
10
  export { CONTROL_RESPONSE_MS } from "./body-turn.js";
10
11
  function missing(error) {
11
12
  return isHeartAbsent(error) || error.code === "ENOENT";
12
13
  }
13
14
  async function putDownIfControlled(paths, supervisor, bodySequence, now) {
14
- if (supervisor.reason === "control" && await heartExists(paths)) {
15
+ if (supervisor.reason === "control" && (await heartExists(paths))) {
15
16
  await breakBody(paths, { sequence: bodySequence, end: "put-down", at: now() });
16
17
  }
17
18
  }
18
- async function takeLeash(paths) {
19
+ async function takeLeash(paths, refuseIfHeld = false) {
19
20
  for (;;) {
20
21
  try {
21
22
  const leash = await HeldAkumaLeash.try(paths);
22
23
  if (leash !== null)
23
24
  return leash;
25
+ if (refuseIfHeld)
26
+ return "held";
24
27
  }
25
28
  catch (error) {
26
29
  if (missing(error))
27
- return null;
30
+ return "absent";
28
31
  throw error;
29
32
  }
30
33
  await abortableDelay(LEASH_RETRY_MS);
@@ -37,7 +40,7 @@ async function bornSoul(launch, leash, now) {
37
40
  if (launch.seed === undefined)
38
41
  throw new Error("Akuma wake has no born soul");
39
42
  const candidate = { ...launch.seed, createdAt: now };
40
- if (await leash.birth(launch.paths, candidate, launch.birthSession) === "sealed")
43
+ if ((await leash.birth(launch.paths, candidate, launch.birthSession)) === "sealed")
41
44
  return null;
42
45
  const born = (await readHeart(launch.paths)).soul;
43
46
  if (born === null || born.id !== candidate.id) {
@@ -71,11 +74,25 @@ async function persistTurn(paths, turnSequence, result, completedAt) {
71
74
  return result.kind === "answered" && result.session === undefined ? "failed" : result.kind;
72
75
  }
73
76
  function defaultRuntime() {
77
+ return { now: () => new Date().toISOString(), spawnChild: handoffAkumaBody, spawnBody: spawnAkumaBody };
78
+ }
79
+ export async function bodyProcessInput(launch, bodyModuleUrl = import.meta.url) {
80
+ const encoded = Buffer.from(JSON.stringify(launch), "utf8").toString("base64url");
81
+ const actorId = launch.seed?.id ?? (await readHeart(launch.paths)).soul?.id;
82
+ if (actorId === undefined)
83
+ throw new Error("Akuma wake has no born soul");
84
+ const source = bodyModuleUrl.endsWith(".ts");
85
+ const entry = fileURLToPath(new URL(source ? "../akuma-body.ts" : "../akuma-body.js", bodyModuleUrl));
74
86
  return {
75
- now: () => new Date().toISOString(),
76
- spawnChild: spawnAkumaBody,
87
+ argv: source ? [process.execPath, "--import", "tsx", entry, encoded] : [process.execPath, entry, encoded],
88
+ cwd: await launchCwd(launch),
89
+ env: { ...process.env, KEIYAKU_ACTOR_ID: actorId },
90
+ log: launch.paths.log,
77
91
  };
78
92
  }
93
+ async function handoffAkumaBody(launch) {
94
+ await handoffProcess(await bodyProcessInput(launch));
95
+ }
79
96
  async function prepareBodyStart(paths, leash) {
80
97
  const before = await readHeart(paths);
81
98
  if (before.pause !== null)
@@ -132,7 +149,7 @@ async function runBodyTurns(input) {
132
149
  adapter,
133
150
  bodySequence,
134
151
  supervisor,
135
- runtimeSpawn: runtime.spawnChild ?? spawnAkumaBody,
152
+ runtimeSpawn: runtime.spawnChild ?? handoffAkumaBody,
136
153
  body: initial ?? "",
137
154
  ...(initial === undefined ? {} : { call: initial }),
138
155
  launchTells,
@@ -149,8 +166,8 @@ async function runBodyTurns(input) {
149
166
  await breakBody(launch.paths, { sequence: bodySequence, end: "put-down", at: runtime.now() });
150
167
  return;
151
168
  }
152
- if (result.kind === "resume-unsupported"
153
- || await persistTurn(launch.paths, result.turnSequence, result, runtime.now()) === "failed") {
169
+ if (result.kind === "resume-unsupported" ||
170
+ (await persistTurn(launch.paths, result.turnSequence, result, runtime.now())) === "failed") {
154
171
  await breakBody(launch.paths, { sequence: bodySequence, end: "broke-off", at: runtime.now() });
155
172
  return;
156
173
  }
@@ -158,18 +175,38 @@ async function runBodyTurns(input) {
158
175
  initial = undefined;
159
176
  }
160
177
  }
178
+ export async function handoffPendingTells(paths, spawn = spawnAkumaBody) {
179
+ try {
180
+ if ((await readHeart(paths)).pending.length === 0)
181
+ return;
182
+ (await spawn({ paths, refuseIfHeld: true })).release();
183
+ }
184
+ catch {
185
+ /* a later Heart interaction retries the unchanged pending Tell */
186
+ }
187
+ }
161
188
  export async function driveAkumaBody(launch, adapter, runtime = defaultRuntime()) {
162
- const leash = await takeLeash(launch.paths);
163
- if (leash === null)
189
+ const acquired = await takeLeash(launch.paths, launch.refuseIfHeld);
190
+ if (acquired === "held")
191
+ return "held";
192
+ if (acquired === "absent")
164
193
  return;
194
+ const leash = acquired;
195
+ let bodyStarted = false;
165
196
  try {
166
197
  const soul = await bornSoul(launch, leash, runtime.now());
167
198
  if (soul === null)
168
199
  return;
200
+ if (launch.seed === undefined && launch.initialBody === undefined) {
201
+ const [heart, requests] = await Promise.all([readHeart(launch.paths), readNonterminalRequests(launch.paths)]);
202
+ if (heart.pending.length === 0 && requests.length === 0)
203
+ return;
204
+ }
169
205
  const selected = adapter ?? (await resolveProviderExecution(soul.provider)).adapter;
170
- if (!await prepareBodyStart(launch.paths, leash))
206
+ if (!(await prepareBodyStart(launch.paths, leash)))
171
207
  return;
172
208
  const body = await leash.recordBody(launch.paths, { leashTakenAt: runtime.now() });
209
+ bodyStarted = true;
173
210
  const supervisor = await BodySupervisor.open(launch.paths, body.sequence, leash);
174
211
  const execution = { launch, soul, adapter: selected, bodySequence: body.sequence, supervisor, runtime };
175
212
  try {
@@ -181,7 +218,7 @@ export async function driveAkumaBody(launch, adapter, runtime = defaultRuntime()
181
218
  }
182
219
  }
183
220
  catch (error) {
184
- if (!await heartExists(launch.paths))
221
+ if (!(await heartExists(launch.paths)))
185
222
  return;
186
223
  try {
187
224
  await leash.sealIfUnborn(launch.paths, {
@@ -189,30 +226,102 @@ export async function driveAkumaBody(launch, adapter, runtime = defaultRuntime()
189
226
  at: runtime.now(),
190
227
  });
191
228
  }
192
- catch { /* the original Body failure remains authoritative */ }
229
+ catch {
230
+ /* the original Body failure remains authoritative */
231
+ }
193
232
  throw error;
194
233
  }
195
234
  finally {
196
235
  leash.release();
236
+ if (bodyStarted)
237
+ await handoffPendingTells(launch.paths, runtime.spawnBody ?? spawnAkumaBody);
197
238
  }
198
239
  }
199
240
  export async function spawnAkumaBody(launch) {
200
- const encoded = Buffer.from(JSON.stringify(launch), "utf8").toString("base64url");
201
- const actorId = launch.seed?.id ?? (await readHeart(launch.paths)).soul?.id;
202
- if (actorId === undefined)
203
- throw new Error("Akuma wake has no born soul");
204
- const owned = await spawnDetachedProcess({
205
- argv: [process.execPath, ...process.execArgv, fileURLToPath(new URL("../akuma-body.js", import.meta.url)), encoded],
206
- cwd: await launchCwd(launch),
207
- env: { ...process.env, KEIYAKU_ACTOR_ID: actorId },
208
- log: launch.paths.log,
209
- });
210
- owned.release();
241
+ return await spawnDetachedProcess(await bodyProcessInput(launch));
211
242
  }
212
243
  export async function runAkumaBody(launch, upstream) {
213
- await driveAkumaBody(launch, undefined, {
214
- now: () => new Date().toISOString(),
215
- spawnChild: spawnAkumaBody,
216
- upstream,
217
- });
244
+ return await driveAkumaBody(launch, undefined, { ...defaultRuntime(), upstream });
245
+ }
246
+ const DIRECT_TELL_WAKE = {
247
+ observeHeart: watchHeart,
248
+ spawn: async (paths) => await spawnAkumaBody({ paths, refuseIfHeld: true }),
249
+ };
250
+ function diagnostic(error) {
251
+ return error instanceof Error ? error.message : String(error);
252
+ }
253
+ function preAdmissionDiagnostic(exit) {
254
+ return exit.code === null ? `pre-admission signal ${exit.signal ?? "unknown"}` : `pre-admission exit ${exit.code}`;
255
+ }
256
+ async function settledWake(paths, tellId, after) {
257
+ const heart = await readHeart(paths);
258
+ if (tellId !== undefined && !heart.pending.some((tell) => tell.id === tellId))
259
+ return { kind: "told" };
260
+ if (heart.latestBody !== null && heart.latestBody.sequence > after) {
261
+ return { kind: "pursuing", bodySequence: heart.latestBody.sequence };
262
+ }
263
+ return null;
264
+ }
265
+ function failedChild(exit) {
266
+ return {
267
+ kind: "failed",
268
+ diagnostic: preAdmissionDiagnostic(exit),
269
+ child: { code: exit.code, signal: exit.signal, log: exit.log },
270
+ };
271
+ }
272
+ async function awaitWake(paths, tellId, after, child, changed) {
273
+ let nextChange = changed.next();
274
+ for (;;) {
275
+ const settled = await settledWake(paths, tellId, after);
276
+ if (settled !== null) {
277
+ child.release();
278
+ return settled;
279
+ }
280
+ const winner = await Promise.race([
281
+ nextChange.then(({ done }) => ({ kind: done ? "closed" : "changed" }), (error) => ({ kind: "observer-failed", error })),
282
+ child.exited.then((exit) => ({ kind: "exited", exit })),
283
+ ]);
284
+ if (winner.kind === "changed") {
285
+ nextChange = changed.next();
286
+ continue;
287
+ }
288
+ const final = await settledWake(paths, tellId, after);
289
+ if (final !== null) {
290
+ child.release();
291
+ return final;
292
+ }
293
+ if (winner.kind === "exited") {
294
+ return winner.exit.code === LEASH_HELD_EXIT ? { kind: "held" } : failedChild(winner.exit);
295
+ }
296
+ await child.terminate();
297
+ await child.exited;
298
+ return winner.kind === "observer-failed"
299
+ ? { kind: "failed", diagnostic: diagnostic(winner.error) }
300
+ : { kind: "failed", diagnostic: "Heart observer closed" };
301
+ }
302
+ }
303
+ async function wakePendingTells(paths, tellId, runtime) {
304
+ const watching = new AbortController();
305
+ let changed;
306
+ try {
307
+ const beforeHeart = await readHeart(paths);
308
+ if (tellId === undefined && beforeHeart.pending.length === 0)
309
+ return null;
310
+ const before = beforeHeart.latestBody?.sequence ?? 0;
311
+ changed = await runtime.observeHeart(paths, watching.signal);
312
+ return await awaitWake(paths, tellId, before, await runtime.spawn(paths), changed);
313
+ }
314
+ catch (error) {
315
+ return { kind: "failed", diagnostic: diagnostic(error) };
316
+ }
317
+ finally {
318
+ watching.abort();
319
+ await changed?.return(undefined);
320
+ }
321
+ }
322
+ export async function wakeRecordedTell(paths, tellId, runtime = DIRECT_TELL_WAKE) {
323
+ return {
324
+ admission: { tellId, fact: "recorded" },
325
+ wake: (await wakePendingTells(paths, tellId, runtime)),
326
+ };
218
327
  }
@@ -1,22 +1,32 @@
1
1
  function record(value) {
2
2
  return value !== null && typeof value === "object" && !Array.isArray(value)
3
- ? value : null;
3
+ ? value
4
+ : null;
4
5
  }
5
6
  export function decodeResumeCoordinate(value) {
6
7
  const coordinate = record(value);
7
8
  if (coordinate === null)
8
9
  return null;
9
10
  const keys = Object.keys(coordinate);
10
- if (typeof coordinate.sessionFile === "string" && coordinate.sessionFile.trim().length > 0
11
- && (coordinate.sessionId === undefined || (typeof coordinate.sessionId === "string" && coordinate.sessionId.trim().length > 0))
12
- && keys.every((key) => key === "sessionFile" || key === "sessionId")) {
13
- return { sessionFile: coordinate.sessionFile, ...(coordinate.sessionId === undefined ? {} : { sessionId: coordinate.sessionId }) };
11
+ if (typeof coordinate.sessionFile === "string" &&
12
+ coordinate.sessionFile.trim().length > 0 &&
13
+ (coordinate.sessionId === undefined ||
14
+ (typeof coordinate.sessionId === "string" && coordinate.sessionId.trim().length > 0)) &&
15
+ keys.every((key) => key === "sessionFile" || key === "sessionId")) {
16
+ return {
17
+ sessionFile: coordinate.sessionFile,
18
+ ...(coordinate.sessionId === undefined ? {} : { sessionId: coordinate.sessionId }),
19
+ };
14
20
  }
15
21
  return typeof coordinate.sessionId === "string" && coordinate.sessionId.trim().length > 0 && keys.length === 1
16
- ? { sessionId: coordinate.sessionId } : null;
22
+ ? { sessionId: coordinate.sessionId }
23
+ : null;
17
24
  }
18
25
  export function encodeResumeCoordinate(coordinate) {
19
26
  return "sessionFile" in coordinate
20
- ? { sessionFile: coordinate.sessionFile, ...(coordinate.sessionId === undefined ? {} : { sessionId: coordinate.sessionId }) }
27
+ ? {
28
+ sessionFile: coordinate.sessionFile,
29
+ ...(coordinate.sessionId === undefined ? {} : { sessionId: coordinate.sessionId }),
30
+ }
21
31
  : { sessionId: coordinate.sessionId };
22
32
  }
@@ -18,5 +18,5 @@ export function lifeAt(current, body, kill, createdAt) {
18
18
  return kill?.at ?? null;
19
19
  if (current === "asleep")
20
20
  return body?.endedAt ?? createdAt;
21
- return current === "stranded" ? body?.endedAt ?? null : null;
21
+ return current === "stranded" ? (body?.endedAt ?? null) : null;
22
22
  }
@@ -3,7 +3,7 @@ import type { BodyEnd, BodyFact, ForkPoint, HeartSnapshot, KillFact, SealFact, S
3
3
  export type { CallFact } from "./facts.js";
4
4
  import type { ActivityFact } from "./rows.js";
5
5
  import { type ActivityFactSlice } from "./timeline.js";
6
- export { HeartAbsentError, HeldAkumaLeash, initializeHeart, isHeartAbsent, probeLeash } from "./storage.js";
6
+ export { HeartAbsentError, HeldAkumaLeash, initializeHeart, isHeartAbsent, probeLeash, watchHeart } from "./storage.js";
7
7
  export { admitRequest, readNonterminalRequests, readRequest, refuseRequest, reserveRequest, serveRequest, serveUpstreamRequest, voidRequest, } from "./request-authority.js";
8
8
  export { life, lifeAt } from "./facts.js";
9
9
  export type { AkumaLife, AkumaOrigin, BodyEnd, BodyFact, HeartSnapshot, ForkPoint, KillEvidence, KillFact, LeashProbe, PauseFact, RequestFact, RequestInput, RequestRecipe, UpstreamRequestService, SealFact, SessionFact, Soul, StopFact, TellFact, TellDelivery, TellDeliveryInput, TellReceiptInput, TurnEndFact, TurnFact, TurnOutcome, TurnStartFact, } from "./facts.js";
@@ -3,7 +3,7 @@ import { insertTellDeliveryFact, insertTellFact, insertTellReceiptFact, pendingT
3
3
  import { activityFactSlice, lastActivityAt as readLastActivityAt, pruneActivityFacts, } from "./timeline.js";
4
4
  import { isHeartAbsent, readSealFromLeash, readTransaction, transaction, withHeart } from "./storage.js";
5
5
  import { soulFact } from "./soul.js";
6
- export { HeartAbsentError, HeldAkumaLeash, initializeHeart, isHeartAbsent, probeLeash } from "./storage.js";
6
+ export { HeartAbsentError, HeldAkumaLeash, initializeHeart, isHeartAbsent, probeLeash, watchHeart } from "./storage.js";
7
7
  export { admitRequest, readNonterminalRequests, readRequest, refuseRequest, reserveRequest, serveRequest, serveUpstreamRequest, voidRequest, } from "./request-authority.js";
8
8
  export { life, lifeAt } from "./facts.js";
9
9
  const ACTIVITY_LIMIT = 5_000;
@@ -80,9 +80,7 @@ export async function recordTellDeliveries(paths, inputs) {
80
80
  }
81
81
  export async function recordTellReceipt(paths, input) {
82
82
  await withHeart(paths, (heart) => transaction(heart, () => {
83
- const tellIds = input.evidence === "exact"
84
- ? [input.tellId]
85
- : tellIdsForFence(heart, input.turnSequence, input.fence);
83
+ const tellIds = input.evidence === "exact" ? [input.tellId] : tellIdsForFence(heart, input.turnSequence, input.fence);
86
84
  if (tellIds.length === 0)
87
85
  throw new Error("tell receipt has no delivery mapping");
88
86
  insertTellReceiptFact(heart, input);
@@ -29,10 +29,10 @@ export async function admitRequest(paths, input) {
29
29
  const fact = requestFact(heart, input.id);
30
30
  if (fact === null)
31
31
  throw new Error(`Akuma request ${input.id} was not admitted`);
32
- if (fact.requester !== soul.id
33
- || fact.id !== normalized.id
34
- || fact.action !== normalized.action
35
- || requestPayloadJson(fact) !== requestPayloadJson(normalized)) {
32
+ if (fact.requester !== soul.id ||
33
+ fact.id !== normalized.id ||
34
+ fact.action !== normalized.action ||
35
+ requestPayloadJson(fact) !== requestPayloadJson(normalized)) {
36
36
  throw new Error(`Akuma request ${input.id} reused different input`);
37
37
  }
38
38
  return fact;
@@ -12,9 +12,7 @@ export function requestPayloadJson(input) {
12
12
  return json(payload);
13
13
  }
14
14
  function decodeRequestRow(row) {
15
- const knownAction = [
16
- "akuma.call", "akuma.wait", "akuma.tell", "akuma.kill", "contract.deliver", "contract.review",
17
- ].includes(row.action) || isTaskMutationAction(row.action);
15
+ const knownAction = ["akuma.call", "akuma.wait", "akuma.tell", "akuma.kill", "contract.deliver", "contract.review"].includes(row.action) || isTaskMutationAction(row.action);
18
16
  if (!knownAction)
19
17
  throw new Error(`Akuma authority contains an unknown request action: ${row.action}`);
20
18
  const payload = parsed(row.payload_json);
@@ -53,36 +51,49 @@ const REQUEST_COLUMNS = `sequence, id, requester, action, payload_json, admitted
53
51
  state, child, service_json, diagnostic, evidence`;
54
52
  export function insertRequestFact(database, input) {
55
53
  const { id, action, requester, admittedAt, refusal } = input;
56
- database.prepare(`INSERT OR IGNORE INTO requests(
54
+ database
55
+ .prepare(`INSERT OR IGNORE INTO requests(
57
56
  id, requester, action, payload_json, admitted_at, state, diagnostic
58
- ) VALUES (?, ?, ?, ?, ?, ?, ?)`).run(id, requester, action, requestPayloadJson(input), admittedAt, refusal === undefined ? "admitted" : "refused", refusal ?? null);
57
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)`)
58
+ .run(id, requester, action, requestPayloadJson(input), admittedAt, refusal === undefined ? "admitted" : "refused", refusal ?? null);
59
59
  }
60
60
  export function requestFact(database, id) {
61
61
  const row = database.prepare(`SELECT ${REQUEST_COLUMNS} FROM requests WHERE id = ?`).get(id);
62
62
  return row === undefined ? null : decodeRequestRow(row);
63
63
  }
64
64
  export function nonterminalRequestFacts(database) {
65
- const rows = database.prepare(`SELECT ${REQUEST_COLUMNS} FROM requests
66
- WHERE state IN ('admitted', 'reserved') ORDER BY sequence`).all();
65
+ const rows = database
66
+ .prepare(`SELECT ${REQUEST_COLUMNS} FROM requests
67
+ WHERE state IN ('admitted', 'reserved') ORDER BY sequence`)
68
+ .all();
67
69
  return rows.map(decodeRequestRow);
68
70
  }
69
71
  export function updateRequestReserved(database, id, child) {
70
- database.prepare("UPDATE requests SET state = 'reserved', child = ? WHERE id = ? AND state = 'admitted'")
72
+ database
73
+ .prepare("UPDATE requests SET state = 'reserved', child = ? WHERE id = ? AND state = 'admitted'")
71
74
  .run(child, id);
72
75
  }
73
76
  export function updateRequestServed(database, id, child) {
74
- database.prepare(`UPDATE requests SET state = 'served', child = ?
75
- WHERE id = ? AND state = 'reserved'`).run(child, id);
77
+ database
78
+ .prepare(`UPDATE requests SET state = 'served', child = ?
79
+ WHERE id = ? AND state = 'reserved'`)
80
+ .run(child, id);
76
81
  }
77
82
  export function updateUpstreamRequestServed(database, id, service) {
78
- database.prepare(`UPDATE requests SET state = 'served', service_json = ?
79
- WHERE id = ? AND state = 'admitted' AND action != 'akuma.call'`).run(json(service), id);
83
+ database
84
+ .prepare(`UPDATE requests SET state = 'served', service_json = ?
85
+ WHERE id = ? AND state = 'admitted' AND action != 'akuma.call'`)
86
+ .run(json(service), id);
80
87
  }
81
88
  export function updateRequestRefused(database, id, diagnostic) {
82
- database.prepare(`UPDATE requests SET state = 'refused', diagnostic = ?
83
- WHERE id = ? AND state = 'admitted'`).run(diagnostic, id);
89
+ database
90
+ .prepare(`UPDATE requests SET state = 'refused', diagnostic = ?
91
+ WHERE id = ? AND state = 'admitted'`)
92
+ .run(diagnostic, id);
84
93
  }
85
94
  export function updateRequestVoided(database, id, evidence) {
86
- database.prepare(`UPDATE requests SET state = 'voided', child = NULL, service_json = NULL, evidence = ?
87
- WHERE id = ? AND state IN ('admitted', 'reserved')`).run(evidence, id);
95
+ database
96
+ .prepare(`UPDATE requests SET state = 'voided', child = NULL, service_json = NULL, evidence = ?
97
+ WHERE id = ? AND state IN ('admitted', 'reserved')`)
98
+ .run(evidence, id);
88
99
  }