@astrosheep/keiyaku 4.1.0 → 4.1.2

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 (278) 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-errors.d.ts +6 -0
  6. package/build/src/akuma/akuma-errors.js +9 -0
  7. package/build/src/akuma/akuma-handle.d.ts +39 -0
  8. package/build/src/akuma/akuma-handle.js +263 -0
  9. package/build/src/akuma/akuma-observe.d.ts +19 -0
  10. package/build/src/akuma/akuma-observe.js +74 -0
  11. package/build/src/akuma/akuma-product-symbols.d.ts +1 -0
  12. package/build/src/akuma/akuma-product-symbols.js +1 -0
  13. package/build/src/akuma/akuma-product.d.ts +18 -0
  14. package/build/src/akuma/akuma-product.js +154 -0
  15. package/build/src/akuma/akuma.d.ts +37 -81
  16. package/build/src/akuma/akuma.js +39 -447
  17. package/build/src/akuma/allowed.d.ts +1 -0
  18. package/build/src/akuma/allowed.js +6 -0
  19. package/build/src/akuma/archetype.js +8 -5
  20. package/build/src/akuma/body-supervisor.d.ts +26 -0
  21. package/build/src/akuma/body-supervisor.js +89 -0
  22. package/build/src/akuma/body.d.ts +45 -4
  23. package/build/src/akuma/body.js +145 -35
  24. package/build/src/akuma/coordinate.js +17 -7
  25. package/build/src/akuma/heart/facts.js +1 -1
  26. package/build/src/akuma/heart/index.d.ts +1 -1
  27. package/build/src/akuma/heart/index.js +2 -4
  28. package/build/src/akuma/heart/request-authority.js +4 -4
  29. package/build/src/akuma/heart/request-rows.js +27 -16
  30. package/build/src/akuma/heart/rows.js +79 -41
  31. package/build/src/akuma/heart/soul.js +1 -1
  32. package/build/src/akuma/heart/storage.d.ts +1 -0
  33. package/build/src/akuma/heart/storage.js +76 -11
  34. package/build/src/akuma/heart/tells.js +35 -18
  35. package/build/src/akuma/heart/timeline.js +19 -11
  36. package/build/src/akuma/identity.d.ts +1 -0
  37. package/build/src/akuma/identity.js +1 -0
  38. package/build/src/akuma/index.d.ts +3 -3
  39. package/build/src/akuma/index.js +1 -1
  40. package/build/src/akuma/nuke.js +106 -14
  41. package/build/src/akuma/projection-read.d.ts +17 -0
  42. package/build/src/akuma/projection-read.js +135 -0
  43. package/build/src/akuma/projection.d.ts +1 -16
  44. package/build/src/akuma/projection.js +77 -154
  45. package/build/src/akuma/provider-recipe.js +11 -5
  46. package/build/src/akuma/provider.js +105 -62
  47. package/build/src/akuma/providers/acp/config.js +5 -6
  48. package/build/src/akuma/providers/acp/core.js +17 -10
  49. package/build/src/akuma/providers/acp/events.js +38 -29
  50. package/build/src/akuma/providers/acp/index.js +13 -9
  51. package/build/src/akuma/providers/claude/events.js +8 -12
  52. package/build/src/akuma/providers/claude/index.d.ts +1 -0
  53. package/build/src/akuma/providers/claude/index.js +55 -26
  54. package/build/src/akuma/providers/claude/input.js +6 -2
  55. package/build/src/akuma/providers/codex-app-server/events.js +61 -36
  56. package/build/src/akuma/providers/codex-app-server/index.js +25 -13
  57. package/build/src/akuma/providers/grok-build/index.js +22 -25
  58. package/build/src/akuma/providers/index.js +2 -18
  59. package/build/src/akuma/providers/opencode-sdk/events.js +32 -18
  60. package/build/src/akuma/providers/opencode-sdk/index.js +26 -17
  61. package/build/src/akuma/providers/opencode-sdk/session.js +21 -5
  62. package/build/src/akuma/providers/pi/events.js +26 -19
  63. package/build/src/akuma/providers/pi/index.js +58 -40
  64. package/build/src/akuma/publication.js +9 -9
  65. package/build/src/akuma/request-execution.d.ts +6 -0
  66. package/build/src/akuma/request-execution.js +86 -0
  67. package/build/src/akuma/request-serve.d.ts +12 -1
  68. package/build/src/akuma/request-serve.js +19 -122
  69. package/build/src/akuma/request-wire.d.ts +2 -2
  70. package/build/src/akuma/request-wire.js +75 -102
  71. package/build/src/akuma/requests.js +2 -2
  72. package/build/src/akuma/{body-turn.d.ts → turn-drive.d.ts} +2 -25
  73. package/build/src/akuma/{body-turn.js → turn-drive.js} +20 -103
  74. package/build/src/akuma-body.js +6 -5
  75. package/build/src/body/amend.d.ts +4 -1
  76. package/build/src/body/amend.js +14 -6
  77. package/build/src/body/decode.js +2 -4
  78. package/build/src/body/envelope.js +2 -4
  79. package/build/src/body/region.d.ts +1 -2
  80. package/build/src/body/region.js +2 -70
  81. package/build/src/body/render.js +17 -12
  82. package/build/src/body/shape.js +1 -7
  83. package/build/src/cli/accepted.d.ts +1 -1
  84. package/build/src/cli/accepted.js +8 -1
  85. package/build/src/cli/commands/akuma-invoke.js +18 -6
  86. package/build/src/cli/commands/akuma.d.ts +0 -6
  87. package/build/src/cli/commands/akuma.js +11 -24
  88. package/build/src/cli/commands/amend.d.ts +2 -2
  89. package/build/src/cli/commands/amend.js +2 -4
  90. package/build/src/cli/commands/bind.d.ts +4 -4
  91. package/build/src/cli/commands/bind.js +16 -6
  92. package/build/src/cli/commands/contract-invoke.d.ts +25 -0
  93. package/build/src/cli/commands/contract-invoke.js +193 -0
  94. package/build/src/cli/commands/contract.d.ts +109 -10
  95. package/build/src/cli/commands/contract.js +339 -16
  96. package/build/src/cli/commands/install.d.ts +1 -1
  97. package/build/src/cli/commands/install.js +23 -15
  98. package/build/src/cli/commands/task-invoke.js +187 -67
  99. package/build/src/cli/commands/task-query.js +48 -24
  100. package/build/src/cli/commands/task.js +150 -38
  101. package/build/src/cli/coordinates.js +2 -4
  102. package/build/src/cli/invoke.js +92 -208
  103. package/build/src/cli/main.js +4 -2
  104. package/build/src/cli/parse.d.ts +3 -90
  105. package/build/src/cli/parse.js +27 -233
  106. package/build/src/cli/render/akuma-activity.d.ts +38 -0
  107. package/build/src/cli/render/akuma-activity.js +362 -0
  108. package/build/src/cli/render/akuma-tool-command.js +6 -7
  109. package/build/src/cli/render/akuma-tool.js +10 -8
  110. package/build/src/cli/render/akuma.d.ts +1 -1
  111. package/build/src/cli/render/akuma.js +61 -353
  112. package/build/src/cli/render/audit.js +5 -15
  113. package/build/src/cli/render/board.d.ts +1 -0
  114. package/build/src/cli/render/board.js +17 -0
  115. package/build/src/cli/render/catalog.js +81 -8
  116. package/build/src/cli/render/contract-history.d.ts +2 -0
  117. package/build/src/cli/render/contract-history.js +83 -0
  118. package/build/src/cli/render/contract-observation.d.ts +10 -0
  119. package/build/src/cli/render/contract-observation.js +58 -0
  120. package/build/src/cli/render/contract.d.ts +1 -2
  121. package/build/src/cli/render/contract.js +41 -142
  122. package/build/src/cli/render/kanshi.js +239 -215
  123. package/build/src/cli/render/receipt.js +2 -9
  124. package/build/src/cli/render/refusal.js +10 -15
  125. package/build/src/cli/render/region.js +22 -6
  126. package/build/src/cli/render/settings.js +4 -4
  127. package/build/src/cli/render/task.js +34 -14
  128. package/build/src/cli/render/terminal.js +16 -5
  129. package/build/src/cli/result.d.ts +3 -3
  130. package/build/src/cli/runtime.d.ts +6 -1
  131. package/build/src/cli/runtime.js +84 -63
  132. package/build/src/cli/selectors.js +2 -4
  133. package/build/src/cli/usage.js +6 -5
  134. package/build/src/contract-worktree.d.ts +1 -23
  135. package/build/src/contract-worktree.js +79 -155
  136. package/build/src/coordination/durable-file.js +7 -3
  137. package/build/src/coordination/sqlite-transaction-lock.js +29 -12
  138. package/build/src/core/facts/codec.js +7 -3
  139. package/build/src/core/facts/types.d.ts +1 -1
  140. package/build/src/core/subject.js +16 -9
  141. package/build/src/core/verbs/abandon.d.ts +1 -1
  142. package/build/src/core/verbs/abandon.js +1 -1
  143. package/build/src/core/verbs/amend.d.ts +1 -1
  144. package/build/src/core/verbs/amend.js +7 -7
  145. package/build/src/core/verbs/attestation.js +4 -1
  146. package/build/src/core/verbs/bind.d.ts +1 -1
  147. package/build/src/core/verbs/bind.js +1 -1
  148. package/build/src/core/verbs/placement.d.ts +1 -1
  149. package/build/src/core/verbs/placement.js +10 -8
  150. package/build/src/dispatch/index.js +8 -6
  151. package/build/src/duration.js +5 -1
  152. package/build/src/git/admission.js +5 -3
  153. package/build/src/git/hooks.d.ts +8 -0
  154. package/build/src/git/hooks.js +63 -14
  155. package/build/src/git/identity.js +1 -4
  156. package/build/src/git/integration.d.ts +2 -3
  157. package/build/src/git/integration.js +32 -39
  158. package/build/src/git/nuke.d.ts +1 -1
  159. package/build/src/git/nuke.js +110 -57
  160. package/build/src/git/observe.d.ts +13 -1
  161. package/build/src/git/observe.js +51 -44
  162. package/build/src/git/process.js +4 -2
  163. package/build/src/git/read-observation.js +10 -8
  164. package/build/src/git/reconcile.d.ts +22 -2
  165. package/build/src/git/reconcile.js +38 -274
  166. package/build/src/git/repository.d.ts +2 -0
  167. package/build/src/git/repository.js +13 -22
  168. package/build/src/git/scratch.d.ts +8 -5
  169. package/build/src/git/scratch.js +29 -2
  170. package/build/src/git/target-placement.d.ts +9 -10
  171. package/build/src/git/target-placement.js +59 -62
  172. package/build/src/git/tender.js +13 -46
  173. package/build/src/git/terminal-reconcile.d.ts +21 -0
  174. package/build/src/git/terminal-reconcile.js +278 -0
  175. package/build/src/git/terminal-seal.js +15 -12
  176. package/build/src/git/tree.js +5 -2
  177. package/build/src/git/workspace.d.ts +24 -2
  178. package/build/src/git/workspace.js +120 -16
  179. package/build/src/identity/coordinates.js +3 -1
  180. package/build/src/identity/selector.js +6 -3
  181. package/build/src/kanshi/fleet.d.ts +6 -0
  182. package/build/src/kanshi/fleet.js +27 -0
  183. package/build/src/kanshi/index.d.ts +1 -1
  184. package/build/src/kanshi/read.js +60 -48
  185. package/build/src/kanshi/report.d.ts +11 -23
  186. package/build/src/kanshi/select.js +25 -24
  187. package/build/src/library/address.d.ts +2 -1
  188. package/build/src/library/address.js +7 -8
  189. package/build/src/library/akuma-creation.js +20 -12
  190. package/build/src/library/audit.d.ts +0 -1
  191. package/build/src/library/audit.js +1 -2
  192. package/build/src/library/bind.d.ts +53 -5
  193. package/build/src/library/bind.js +98 -22
  194. package/build/src/library/catalog.d.ts +1 -0
  195. package/build/src/library/catalog.js +6 -3
  196. package/build/src/library/configuration.js +3 -1
  197. package/build/src/library/continuation.d.ts +15 -4
  198. package/build/src/library/continuation.js +24 -16
  199. package/build/src/library/contract-bind.d.ts +26 -0
  200. package/build/src/library/contract-bind.js +106 -0
  201. package/build/src/library/contract-operations.d.ts +77 -0
  202. package/build/src/library/contract-operations.js +130 -0
  203. package/build/src/library/contract-types.d.ts +95 -0
  204. package/build/src/library/contract-types.js +1 -0
  205. package/build/src/library/contract.d.ts +19 -154
  206. package/build/src/library/contract.js +46 -261
  207. package/build/src/library/fleet.d.ts +1 -1
  208. package/build/src/library/fleet.js +19 -11
  209. package/build/src/library/input.d.ts +2 -0
  210. package/build/src/library/input.js +14 -0
  211. package/build/src/library/keiyaku.d.ts +8 -7
  212. package/build/src/library/keiyaku.js +3 -3
  213. package/build/src/library/nuke.js +14 -5
  214. package/build/src/library/reconcile.d.ts +9 -4
  215. package/build/src/library/reconcile.js +30 -24
  216. package/build/src/library/refusal.d.ts +13 -5
  217. package/build/src/library/refusal.js +7 -0
  218. package/build/src/library/region.d.ts +8 -7
  219. package/build/src/library/region.js +31 -11
  220. package/build/src/library/repo.js +2 -2
  221. package/build/src/markdown/lex.js +9 -2
  222. package/build/src/markdown/parse.js +11 -7
  223. package/build/src/markdown/query.js +4 -2
  224. package/build/src/protocol/amend.js +3 -1
  225. package/build/src/protocol/attempt.d.ts +7 -3
  226. package/build/src/protocol/attempt.js +9 -5
  227. package/build/src/protocol/audit.d.ts +1 -1
  228. package/build/src/protocol/audit.js +4 -14
  229. package/build/src/protocol/bind.d.ts +19 -6
  230. package/build/src/protocol/bind.js +28 -13
  231. package/build/src/protocol/completion.d.ts +0 -1
  232. package/build/src/protocol/completion.js +1 -3
  233. package/build/src/protocol/deliver.d.ts +1 -3
  234. package/build/src/protocol/deliver.js +15 -39
  235. package/build/src/protocol/intent.d.ts +1 -0
  236. package/build/src/protocol/intent.js +9 -7
  237. package/build/src/protocol/operations.d.ts +3 -7
  238. package/build/src/protocol/operations.js +5 -3
  239. package/build/src/protocol/placement.d.ts +0 -1
  240. package/build/src/protocol/placement.js +6 -6
  241. package/build/src/protocol/read/observation.d.ts +11 -0
  242. package/build/src/protocol/read/observation.js +37 -0
  243. package/build/src/protocol/read/status.d.ts +23 -12
  244. package/build/src/protocol/read/status.js +90 -72
  245. package/build/src/protocol/reconcile.d.ts +1 -0
  246. package/build/src/protocol/reconcile.js +2 -3
  247. package/build/src/protocol/reintegrate.js +1 -1
  248. package/build/src/protocol/review.d.ts +0 -1
  249. package/build/src/protocol/review.js +15 -22
  250. package/build/src/protocol/run.d.ts +1 -0
  251. package/build/src/protocol/run.js +4 -5
  252. package/build/src/runtime/proc/launch.d.ts +33 -0
  253. package/build/src/runtime/proc/launch.js +98 -0
  254. package/build/src/runtime/proc/line-rpc.js +9 -3
  255. package/build/src/runtime/proc/run.d.ts +12 -6
  256. package/build/src/runtime/proc/run.js +78 -21
  257. package/build/src/runtime/proc/stdio.js +7 -10
  258. package/build/src/settings.js +6 -2
  259. package/build/src/settlement/holder.js +13 -10
  260. package/build/src/settlement/settle.js +39 -6
  261. package/build/src/task/board.js +12 -6
  262. package/build/src/task/compose.js +85 -22
  263. package/build/src/task/context.js +21 -8
  264. package/build/src/task/document.js +46 -10
  265. package/build/src/task/identity.js +5 -2
  266. package/build/src/task/index.d.ts +0 -1
  267. package/build/src/task/index.js +119 -34
  268. package/build/src/task/input.js +29 -7
  269. package/build/src/task/mutation.js +61 -19
  270. package/build/src/task/operations.d.ts +6 -1
  271. package/build/src/task/operations.js +83 -29
  272. package/build/src/task/query.js +55 -26
  273. package/build/src/task/store.d.ts +3 -1
  274. package/build/src/task/store.js +81 -17
  275. package/build/src/verification/execution.js +14 -11
  276. package/build/src/workspace-place.js +19 -7
  277. package/build/src/world.js +16 -6
  278. package/package.json +8 -3
@@ -0,0 +1,89 @@
1
+ import { abortableDelay } from "./abort.js";
2
+ import { heartExists, readHeart } from "./heart/index.js";
3
+ const BODY_CONTROL_OBSERVATION_MS = 100;
4
+ export const CONTROL_RESPONSE_MS = 1_000;
5
+ export class BodySupervisor {
6
+ paths;
7
+ bodySequence;
8
+ leash;
9
+ signal;
10
+ controller = new AbortController();
11
+ finished = new AbortController();
12
+ observer;
13
+ waiter;
14
+ observation;
15
+ stopping;
16
+ constructor(paths, bodySequence, leash, observation) {
17
+ this.paths = paths;
18
+ this.bodySequence = bodySequence;
19
+ this.leash = leash;
20
+ this.signal = this.controller.signal;
21
+ this.observation = observation;
22
+ this.observer = this.observe();
23
+ }
24
+ static async open(paths, bodySequence, leash) {
25
+ return new BodySupervisor(paths, bodySequence, leash, await readHeart(paths));
26
+ }
27
+ get reason() {
28
+ return this.stopping;
29
+ }
30
+ current() {
31
+ return this.observation;
32
+ }
33
+ async recordHung(diagnostic, at) {
34
+ await this.leash.recordBodyHung(this.paths, { sequence: this.bodySequence, diagnostic, at });
35
+ }
36
+ async refresh() {
37
+ this.publish(await readHeart(this.paths));
38
+ return this.observation;
39
+ }
40
+ next(after) {
41
+ if (this.observation !== after)
42
+ return Promise.resolve(this.observation);
43
+ return new Promise((resolve) => {
44
+ this.waiter = resolve;
45
+ });
46
+ }
47
+ cancel(reason) {
48
+ if (this.stopping !== undefined)
49
+ return;
50
+ this.stopping = reason;
51
+ this.controller.abort(new Error(reason === "control" ? "Akuma Body interrupted by durable control" : "Akuma Heart disappeared"));
52
+ this.waiter?.(this.observation);
53
+ this.waiter = undefined;
54
+ }
55
+ async close() {
56
+ this.finished.abort();
57
+ await this.observer;
58
+ this.waiter = undefined;
59
+ }
60
+ publish(snapshot) {
61
+ this.observation = snapshot;
62
+ this.waiter?.(snapshot);
63
+ this.waiter = undefined;
64
+ }
65
+ async observe() {
66
+ for (;;) {
67
+ const snapshot = this.observation;
68
+ if (!(await heartExists(this.paths)))
69
+ return this.cancel("heart-gone");
70
+ if (snapshot.stop?.bodySequence === this.bodySequence || snapshot.pause?.bodySequence === this.bodySequence) {
71
+ return this.cancel("control");
72
+ }
73
+ try {
74
+ await abortableDelay(BODY_CONTROL_OBSERVATION_MS, this.finished.signal);
75
+ }
76
+ catch {
77
+ return;
78
+ }
79
+ try {
80
+ await this.refresh();
81
+ }
82
+ catch (error) {
83
+ if (await heartExists(this.paths))
84
+ throw error;
85
+ return this.cancel("heart-gone");
86
+ }
87
+ }
88
+ }
89
+ }
@@ -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
- export { CONTROL_RESPONSE_MS } from "./body-turn.js";
5
+ import { type OwnedProcess, type RunLogReference } from "../runtime/proc/run.js";
6
+ export declare const LEASH_HELD_EXIT = 75;
7
+ export { CONTROL_RESPONSE_MS } from "./body-supervisor.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,34 @@
1
1
  import { fileURLToPath } from "node:url";
2
2
  import { abortableDelay } from "./abort.js";
3
- import { BodySupervisor, driveTurn, turnRecipe } from "./body-turn.js";
4
- import { HeldAkumaLeash, breakBody, endTurn, finishBodyIfIdle, heartExists, isHeartAbsent, readHeart, } from "./heart/index.js";
3
+ import { BodySupervisor } from "./body-supervisor.js";
4
+ import { driveTurn, turnRecipe } from "./turn-drive.js";
5
+ import { HeldAkumaLeash, breakBody, endTurn, finishBodyIfIdle, heartExists, isHeartAbsent, readHeart, readNonterminalRequests, watchHeart, } from "./heart/index.js";
5
6
  import { resolveProviderExecution } from "./providers/index.js";
6
7
  import { clearBodyRequestTransport, settleBodyRequests, } from "./request-serve.js";
7
- import { spawnDetachedProcess, } from "../runtime/proc/run.js";
8
- const LEASH_RETRY_MS = 25;
9
- export { CONTROL_RESPONSE_MS } from "./body-turn.js";
8
+ import { handoffProcess, spawnDetachedProcess, } from "../runtime/proc/run.js";
9
+ const LEASH_RETRY_MS = 100;
10
+ export const LEASH_HELD_EXIT = 75;
11
+ export { CONTROL_RESPONSE_MS } from "./body-supervisor.js";
10
12
  function missing(error) {
11
13
  return isHeartAbsent(error) || error.code === "ENOENT";
12
14
  }
13
15
  async function putDownIfControlled(paths, supervisor, bodySequence, now) {
14
- if (supervisor.reason === "control" && await heartExists(paths)) {
16
+ if (supervisor.reason === "control" && (await heartExists(paths))) {
15
17
  await breakBody(paths, { sequence: bodySequence, end: "put-down", at: now() });
16
18
  }
17
19
  }
18
- async function takeLeash(paths) {
20
+ async function takeLeash(paths, refuseIfHeld = false) {
19
21
  for (;;) {
20
22
  try {
21
23
  const leash = await HeldAkumaLeash.try(paths);
22
24
  if (leash !== null)
23
25
  return leash;
26
+ if (refuseIfHeld)
27
+ return "held";
24
28
  }
25
29
  catch (error) {
26
30
  if (missing(error))
27
- return null;
31
+ return "absent";
28
32
  throw error;
29
33
  }
30
34
  await abortableDelay(LEASH_RETRY_MS);
@@ -37,7 +41,7 @@ async function bornSoul(launch, leash, now) {
37
41
  if (launch.seed === undefined)
38
42
  throw new Error("Akuma wake has no born soul");
39
43
  const candidate = { ...launch.seed, createdAt: now };
40
- if (await leash.birth(launch.paths, candidate, launch.birthSession) === "sealed")
44
+ if ((await leash.birth(launch.paths, candidate, launch.birthSession)) === "sealed")
41
45
  return null;
42
46
  const born = (await readHeart(launch.paths)).soul;
43
47
  if (born === null || born.id !== candidate.id) {
@@ -71,11 +75,25 @@ async function persistTurn(paths, turnSequence, result, completedAt) {
71
75
  return result.kind === "answered" && result.session === undefined ? "failed" : result.kind;
72
76
  }
73
77
  function defaultRuntime() {
78
+ return { now: () => new Date().toISOString(), spawnChild: handoffAkumaBody, spawnBody: spawnAkumaBody };
79
+ }
80
+ export async function bodyProcessInput(launch, bodyModuleUrl = import.meta.url) {
81
+ const encoded = Buffer.from(JSON.stringify(launch), "utf8").toString("base64url");
82
+ const actorId = launch.seed?.id ?? (await readHeart(launch.paths)).soul?.id;
83
+ if (actorId === undefined)
84
+ throw new Error("Akuma wake has no born soul");
85
+ const source = bodyModuleUrl.endsWith(".ts");
86
+ const entry = fileURLToPath(new URL(source ? "../akuma-body.ts" : "../akuma-body.js", bodyModuleUrl));
74
87
  return {
75
- now: () => new Date().toISOString(),
76
- spawnChild: spawnAkumaBody,
88
+ argv: source ? [process.execPath, "--import", "tsx", entry, encoded] : [process.execPath, entry, encoded],
89
+ cwd: await launchCwd(launch),
90
+ env: { ...process.env, KEIYAKU_ACTOR_ID: actorId },
91
+ log: launch.paths.log,
77
92
  };
78
93
  }
94
+ async function handoffAkumaBody(launch) {
95
+ await handoffProcess(await bodyProcessInput(launch));
96
+ }
79
97
  async function prepareBodyStart(paths, leash) {
80
98
  const before = await readHeart(paths);
81
99
  if (before.pause !== null)
@@ -132,7 +150,7 @@ async function runBodyTurns(input) {
132
150
  adapter,
133
151
  bodySequence,
134
152
  supervisor,
135
- runtimeSpawn: runtime.spawnChild ?? spawnAkumaBody,
153
+ runtimeSpawn: runtime.spawnChild ?? handoffAkumaBody,
136
154
  body: initial ?? "",
137
155
  ...(initial === undefined ? {} : { call: initial }),
138
156
  launchTells,
@@ -149,8 +167,8 @@ async function runBodyTurns(input) {
149
167
  await breakBody(launch.paths, { sequence: bodySequence, end: "put-down", at: runtime.now() });
150
168
  return;
151
169
  }
152
- if (result.kind === "resume-unsupported"
153
- || await persistTurn(launch.paths, result.turnSequence, result, runtime.now()) === "failed") {
170
+ if (result.kind === "resume-unsupported" ||
171
+ (await persistTurn(launch.paths, result.turnSequence, result, runtime.now())) === "failed") {
154
172
  await breakBody(launch.paths, { sequence: bodySequence, end: "broke-off", at: runtime.now() });
155
173
  return;
156
174
  }
@@ -158,18 +176,38 @@ async function runBodyTurns(input) {
158
176
  initial = undefined;
159
177
  }
160
178
  }
179
+ export async function handoffPendingTells(paths, spawn = spawnAkumaBody) {
180
+ try {
181
+ if ((await readHeart(paths)).pending.length === 0)
182
+ return;
183
+ (await spawn({ paths, refuseIfHeld: true })).release();
184
+ }
185
+ catch {
186
+ /* a later Heart interaction retries the unchanged pending Tell */
187
+ }
188
+ }
161
189
  export async function driveAkumaBody(launch, adapter, runtime = defaultRuntime()) {
162
- const leash = await takeLeash(launch.paths);
163
- if (leash === null)
190
+ const acquired = await takeLeash(launch.paths, launch.refuseIfHeld);
191
+ if (acquired === "held")
192
+ return "held";
193
+ if (acquired === "absent")
164
194
  return;
195
+ const leash = acquired;
196
+ let bodyStarted = false;
165
197
  try {
166
198
  const soul = await bornSoul(launch, leash, runtime.now());
167
199
  if (soul === null)
168
200
  return;
201
+ if (launch.seed === undefined && launch.initialBody === undefined) {
202
+ const [heart, requests] = await Promise.all([readHeart(launch.paths), readNonterminalRequests(launch.paths)]);
203
+ if (heart.pending.length === 0 && requests.length === 0)
204
+ return;
205
+ }
169
206
  const selected = adapter ?? (await resolveProviderExecution(soul.provider)).adapter;
170
- if (!await prepareBodyStart(launch.paths, leash))
207
+ if (!(await prepareBodyStart(launch.paths, leash)))
171
208
  return;
172
209
  const body = await leash.recordBody(launch.paths, { leashTakenAt: runtime.now() });
210
+ bodyStarted = true;
173
211
  const supervisor = await BodySupervisor.open(launch.paths, body.sequence, leash);
174
212
  const execution = { launch, soul, adapter: selected, bodySequence: body.sequence, supervisor, runtime };
175
213
  try {
@@ -181,7 +219,7 @@ export async function driveAkumaBody(launch, adapter, runtime = defaultRuntime()
181
219
  }
182
220
  }
183
221
  catch (error) {
184
- if (!await heartExists(launch.paths))
222
+ if (!(await heartExists(launch.paths)))
185
223
  return;
186
224
  try {
187
225
  await leash.sealIfUnborn(launch.paths, {
@@ -189,30 +227,102 @@ export async function driveAkumaBody(launch, adapter, runtime = defaultRuntime()
189
227
  at: runtime.now(),
190
228
  });
191
229
  }
192
- catch { /* the original Body failure remains authoritative */ }
230
+ catch {
231
+ /* the original Body failure remains authoritative */
232
+ }
193
233
  throw error;
194
234
  }
195
235
  finally {
196
236
  leash.release();
237
+ if (bodyStarted)
238
+ await handoffPendingTells(launch.paths, runtime.spawnBody ?? spawnAkumaBody);
197
239
  }
198
240
  }
199
241
  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();
242
+ return await spawnDetachedProcess(await bodyProcessInput(launch));
211
243
  }
212
244
  export async function runAkumaBody(launch, upstream) {
213
- await driveAkumaBody(launch, undefined, {
214
- now: () => new Date().toISOString(),
215
- spawnChild: spawnAkumaBody,
216
- upstream,
217
- });
245
+ return await driveAkumaBody(launch, undefined, { ...defaultRuntime(), upstream });
246
+ }
247
+ const DIRECT_TELL_WAKE = {
248
+ observeHeart: watchHeart,
249
+ spawn: async (paths) => await spawnAkumaBody({ paths, refuseIfHeld: true }),
250
+ };
251
+ function diagnostic(error) {
252
+ return error instanceof Error ? error.message : String(error);
253
+ }
254
+ function preAdmissionDiagnostic(exit) {
255
+ return exit.code === null ? `pre-admission signal ${exit.signal ?? "unknown"}` : `pre-admission exit ${exit.code}`;
256
+ }
257
+ async function settledWake(paths, tellId, after) {
258
+ const heart = await readHeart(paths);
259
+ if (tellId !== undefined && !heart.pending.some((tell) => tell.id === tellId))
260
+ return { kind: "told" };
261
+ if (heart.latestBody !== null && heart.latestBody.sequence > after) {
262
+ return { kind: "pursuing", bodySequence: heart.latestBody.sequence };
263
+ }
264
+ return null;
265
+ }
266
+ function failedChild(exit) {
267
+ return {
268
+ kind: "failed",
269
+ diagnostic: preAdmissionDiagnostic(exit),
270
+ child: { code: exit.code, signal: exit.signal, log: exit.log },
271
+ };
272
+ }
273
+ async function awaitWake(paths, tellId, after, child, changed) {
274
+ let nextChange = changed.next();
275
+ for (;;) {
276
+ const settled = await settledWake(paths, tellId, after);
277
+ if (settled !== null) {
278
+ child.release();
279
+ return settled;
280
+ }
281
+ const winner = await Promise.race([
282
+ nextChange.then(({ done }) => ({ kind: done ? "closed" : "changed" }), (error) => ({ kind: "observer-failed", error })),
283
+ child.exited.then((exit) => ({ kind: "exited", exit })),
284
+ ]);
285
+ if (winner.kind === "changed") {
286
+ nextChange = changed.next();
287
+ continue;
288
+ }
289
+ const final = await settledWake(paths, tellId, after);
290
+ if (final !== null) {
291
+ child.release();
292
+ return final;
293
+ }
294
+ if (winner.kind === "exited") {
295
+ return winner.exit.code === LEASH_HELD_EXIT ? { kind: "held" } : failedChild(winner.exit);
296
+ }
297
+ await child.terminate();
298
+ await child.exited;
299
+ return winner.kind === "observer-failed"
300
+ ? { kind: "failed", diagnostic: diagnostic(winner.error) }
301
+ : { kind: "failed", diagnostic: "Heart observer closed" };
302
+ }
303
+ }
304
+ async function wakePendingTells(paths, tellId, runtime) {
305
+ const watching = new AbortController();
306
+ let changed;
307
+ try {
308
+ const beforeHeart = await readHeart(paths);
309
+ if (tellId === undefined && beforeHeart.pending.length === 0)
310
+ return null;
311
+ const before = beforeHeart.latestBody?.sequence ?? 0;
312
+ changed = await runtime.observeHeart(paths, watching.signal);
313
+ return await awaitWake(paths, tellId, before, await runtime.spawn(paths), changed);
314
+ }
315
+ catch (error) {
316
+ return { kind: "failed", diagnostic: diagnostic(error) };
317
+ }
318
+ finally {
319
+ watching.abort();
320
+ await changed?.return(undefined);
321
+ }
322
+ }
323
+ export async function wakeRecordedTell(paths, tellId, runtime = DIRECT_TELL_WAKE) {
324
+ return {
325
+ admission: { tellId, fact: "recorded" },
326
+ wake: (await wakePendingTells(paths, tellId, runtime)),
327
+ };
218
328
  }
@@ -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
  }