@astrosheep/keiyaku 4.0.1 → 4.0.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 (245) hide show
  1. package/README.md +137 -5
  2. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -35
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +98 -58
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +152 -53
  9. package/build/src/akuma/abort.d.ts +2 -0
  10. package/build/src/akuma/abort.js +50 -0
  11. package/build/src/akuma/akuma.d.ts +23 -14
  12. package/build/src/akuma/akuma.js +120 -104
  13. package/build/src/akuma/archetype.d.ts +7 -6
  14. package/build/src/akuma/archetype.js +68 -41
  15. package/build/src/akuma/body.d.ts +3 -5
  16. package/build/src/akuma/body.js +408 -213
  17. package/build/src/akuma/heart/facts.d.ts +25 -34
  18. package/build/src/akuma/heart/facts.js +18 -7
  19. package/build/src/akuma/heart/index.d.ts +39 -42
  20. package/build/src/akuma/heart/index.js +134 -93
  21. package/build/src/akuma/heart/rows.d.ts +9 -31
  22. package/build/src/akuma/heart/rows.js +26 -44
  23. package/build/src/akuma/heart/schema.d.ts +1 -1
  24. package/build/src/akuma/heart/schema.js +7 -15
  25. package/build/src/akuma/heart/soul.d.ts +8 -0
  26. package/build/src/akuma/heart/soul.js +136 -0
  27. package/build/src/akuma/heart/storage.d.ts +28 -14
  28. package/build/src/akuma/heart/storage.js +86 -26
  29. package/build/src/akuma/identity.d.ts +2 -2
  30. package/build/src/akuma/identity.js +9 -9
  31. package/build/src/akuma/index.d.ts +6 -1
  32. package/build/src/akuma/index.js +6 -0
  33. package/build/src/akuma/projection.d.ts +117 -38
  34. package/build/src/akuma/projection.js +208 -97
  35. package/build/src/akuma/provider-recipe.d.ts +24 -0
  36. package/build/src/akuma/provider-recipe.js +100 -0
  37. package/build/src/akuma/provider.d.ts +12 -4
  38. package/build/src/akuma/provider.js +117 -70
  39. package/build/src/akuma/providers/acp/core.d.ts +19 -0
  40. package/build/src/akuma/providers/acp/core.js +159 -0
  41. package/build/src/akuma/providers/acp/events.d.ts +20 -0
  42. package/build/src/akuma/providers/acp/events.js +73 -0
  43. package/build/src/akuma/providers/acp/index.d.ts +12 -0
  44. package/build/src/akuma/providers/acp/index.js +94 -0
  45. package/build/src/akuma/providers/claude/events.js +66 -13
  46. package/build/src/akuma/providers/claude/index.d.ts +2 -1
  47. package/build/src/akuma/providers/claude/index.js +35 -21
  48. package/build/src/akuma/providers/codex-app-server/events.js +4 -1
  49. package/build/src/akuma/providers/codex-app-server/index.d.ts +3 -3
  50. package/build/src/akuma/providers/codex-app-server/index.js +24 -31
  51. package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
  52. package/build/src/akuma/providers/grok-build/index.js +70 -0
  53. package/build/src/akuma/providers/index.d.ts +5 -3
  54. package/build/src/akuma/providers/index.js +24 -59
  55. package/build/src/akuma/providers/opencode-sdk/events.d.ts +40 -34
  56. package/build/src/akuma/providers/opencode-sdk/events.js +220 -101
  57. package/build/src/akuma/providers/opencode-sdk/index.d.ts +3 -2
  58. package/build/src/akuma/providers/opencode-sdk/index.js +233 -128
  59. package/build/src/akuma/providers/opencode-sdk/session.d.ts +11 -26
  60. package/build/src/akuma/providers/opencode-sdk/session.js +10 -9
  61. package/build/src/akuma/providers/pi/events.js +49 -11
  62. package/build/src/akuma/providers/pi/index.d.ts +1 -1
  63. package/build/src/akuma/providers/pi/index.js +50 -21
  64. package/build/src/akuma/publication.d.ts +3 -3
  65. package/build/src/akuma/publication.js +28 -40
  66. package/build/src/akuma/requests.d.ts +9 -7
  67. package/build/src/akuma/requests.js +92 -65
  68. package/build/src/alias/index.d.ts +2 -2
  69. package/build/src/alias/index.js +10 -10
  70. package/build/src/body/decode.js +7 -1
  71. package/build/src/body/region.d.ts +2 -0
  72. package/build/src/body/region.js +77 -7
  73. package/build/src/cli/accepted.d.ts +17 -13
  74. package/build/src/cli/accepted.js +72 -34
  75. package/build/src/cli/actor.js +3 -3
  76. package/build/src/cli/commands/akuma-invoke.d.ts +4 -3
  77. package/build/src/cli/commands/akuma-invoke.js +14 -9
  78. package/build/src/cli/commands/akuma.d.ts +13 -3
  79. package/build/src/cli/commands/akuma.js +67 -34
  80. package/build/src/cli/commands/contract.d.ts +18 -5
  81. package/build/src/cli/commands/contract.js +9 -3
  82. package/build/src/cli/commands/task-invoke.d.ts +17 -4
  83. package/build/src/cli/commands/task-invoke.js +43 -20
  84. package/build/src/cli/commands/task-query.d.ts +5 -0
  85. package/build/src/cli/commands/task-query.js +247 -0
  86. package/build/src/cli/commands/task.d.ts +3 -1
  87. package/build/src/cli/commands/task.js +51 -13
  88. package/build/src/cli/coordinates.d.ts +18 -0
  89. package/build/src/cli/coordinates.js +120 -0
  90. package/build/src/cli/draft.d.ts +8 -0
  91. package/build/src/cli/draft.js +96 -0
  92. package/build/src/cli/invoke.d.ts +4 -3
  93. package/build/src/cli/invoke.js +160 -179
  94. package/build/src/cli/main.js +34 -6
  95. package/build/src/cli/parse.d.ts +9 -2
  96. package/build/src/cli/parse.js +29 -7
  97. package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
  98. package/build/src/cli/render/akuma-tool-command.js +144 -0
  99. package/build/src/cli/render/akuma-tool.d.ts +2 -2
  100. package/build/src/cli/render/akuma-tool.js +30 -4
  101. package/build/src/cli/render/akuma.d.ts +1 -0
  102. package/build/src/cli/render/akuma.js +211 -116
  103. package/build/src/cli/render/audit.d.ts +3 -0
  104. package/build/src/cli/render/audit.js +104 -0
  105. package/build/src/cli/render/contract.d.ts +3 -3
  106. package/build/src/cli/render/contract.js +192 -79
  107. package/build/src/cli/render/kanshi.js +263 -168
  108. package/build/src/cli/render/receipt.d.ts +19 -0
  109. package/build/src/cli/render/receipt.js +106 -0
  110. package/build/src/cli/render/refusal.d.ts +16 -2
  111. package/build/src/cli/render/refusal.js +88 -20
  112. package/build/src/cli/render/region.d.ts +2 -0
  113. package/build/src/cli/render/region.js +17 -0
  114. package/build/src/cli/render/task.d.ts +2 -1
  115. package/build/src/cli/render/task.js +223 -60
  116. package/build/src/cli/render/terminal.d.ts +7 -0
  117. package/build/src/cli/render/terminal.js +46 -0
  118. package/build/src/cli/render/text.js +6 -3
  119. package/build/src/cli/result.d.ts +115 -20
  120. package/build/src/cli/usage.d.ts +1 -0
  121. package/build/src/cli/usage.js +3 -0
  122. package/build/src/contract-worktree.d.ts +6 -5
  123. package/build/src/contract-worktree.js +95 -57
  124. package/build/src/coordination/durable-file.d.ts +4 -3
  125. package/build/src/coordination/durable-file.js +29 -25
  126. package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
  127. package/build/src/coordination/sqlite-transaction-lock.js +22 -5
  128. package/build/src/core/facts/fold.js +1 -6
  129. package/build/src/core/facts/gate.d.ts +1 -0
  130. package/build/src/core/facts/gate.js +1 -0
  131. package/build/src/core/verbs/amend.d.ts +1 -1
  132. package/build/src/core/verbs/amend.js +0 -11
  133. package/build/src/core/verbs/deliver.d.ts +1 -1
  134. package/build/src/core/verbs/deliver.js +1 -7
  135. package/build/src/core/verbs/placement.d.ts +1 -1
  136. package/build/src/core/verbs/placement.js +4 -1
  137. package/build/src/dispatch/index.d.ts +2 -2
  138. package/build/src/dispatch/index.js +17 -17
  139. package/build/src/git/admission.d.ts +1 -1
  140. package/build/src/git/admission.js +11 -11
  141. package/build/src/git/hooks.js +13 -13
  142. package/build/src/git/integration.d.ts +14 -15
  143. package/build/src/git/integration.js +80 -44
  144. package/build/src/git/observe.d.ts +5 -3
  145. package/build/src/git/observe.js +18 -12
  146. package/build/src/git/read-observation.js +9 -9
  147. package/build/src/git/reconcile.d.ts +2 -0
  148. package/build/src/git/reconcile.js +138 -95
  149. package/build/src/git/repository.d.ts +27 -19
  150. package/build/src/git/repository.js +155 -83
  151. package/build/src/git/scratch.d.ts +7 -3
  152. package/build/src/git/scratch.js +44 -37
  153. package/build/src/git/target-placement.d.ts +40 -4
  154. package/build/src/git/target-placement.js +221 -101
  155. package/build/src/git/tender.d.ts +6 -5
  156. package/build/src/git/tender.js +24 -43
  157. package/build/src/git/terminal-seal.d.ts +1 -1
  158. package/build/src/git/terminal-seal.js +7 -5
  159. package/build/src/git/workspace.d.ts +36 -4
  160. package/build/src/git/workspace.js +53 -18
  161. package/build/src/identity/selector.js +1 -1
  162. package/build/src/index.d.ts +3 -3
  163. package/build/src/index.js +1 -1
  164. package/build/src/kanshi/index.d.ts +2 -2
  165. package/build/src/kanshi/index.js +1 -1
  166. package/build/src/kanshi/read.d.ts +2 -1
  167. package/build/src/kanshi/read.js +118 -27
  168. package/build/src/kanshi/report.d.ts +50 -0
  169. package/build/src/kanshi/select.d.ts +5 -0
  170. package/build/src/kanshi/select.js +32 -0
  171. package/build/src/library/address.d.ts +12 -3
  172. package/build/src/library/address.js +77 -46
  173. package/build/src/library/akuma-creation.d.ts +6 -1
  174. package/build/src/library/akuma-creation.js +86 -13
  175. package/build/src/library/audit.d.ts +18 -0
  176. package/build/src/library/audit.js +44 -0
  177. package/build/src/library/bind.js +4 -4
  178. package/build/src/library/catalog.d.ts +1 -1
  179. package/build/src/library/catalog.js +5 -8
  180. package/build/src/library/contract.d.ts +5 -6
  181. package/build/src/library/contract.js +11 -35
  182. package/build/src/library/delivery.d.ts +2 -1
  183. package/build/src/library/fleet.d.ts +2 -2
  184. package/build/src/library/fleet.js +35 -71
  185. package/build/src/library/keiyaku.d.ts +3 -2
  186. package/build/src/library/keiyaku.js +1 -0
  187. package/build/src/library/mutation.d.ts +7 -6
  188. package/build/src/library/mutation.js +23 -25
  189. package/build/src/library/reconcile.d.ts +29 -0
  190. package/build/src/library/reconcile.js +179 -0
  191. package/build/src/library/region.d.ts +12 -0
  192. package/build/src/library/region.js +15 -1
  193. package/build/src/library/repo.d.ts +4 -16
  194. package/build/src/library/repo.js +13 -38
  195. package/build/src/protocol/attempt.js +6 -6
  196. package/build/src/protocol/bind.js +4 -4
  197. package/build/src/protocol/intent.d.ts +15 -2
  198. package/build/src/protocol/intent.js +21 -6
  199. package/build/src/protocol/operations.d.ts +73 -22
  200. package/build/src/protocol/operations.js +292 -88
  201. package/build/src/protocol/outcome.d.ts +9 -3
  202. package/build/src/protocol/outcome.js +3 -1
  203. package/build/src/protocol/placement.d.ts +15 -4
  204. package/build/src/protocol/placement.js +58 -25
  205. package/build/src/protocol/read/status.d.ts +8 -1
  206. package/build/src/protocol/read/status.js +61 -14
  207. package/build/src/runtime/proc/line-rpc.d.ts +3 -8
  208. package/build/src/runtime/proc/line-rpc.js +16 -43
  209. package/build/src/runtime/proc/run.d.ts +15 -29
  210. package/build/src/runtime/proc/run.js +113 -105
  211. package/build/src/runtime/proc/stdio.d.ts +18 -0
  212. package/build/src/runtime/proc/stdio.js +58 -0
  213. package/build/src/settings.d.ts +2 -2
  214. package/build/src/settings.js +8 -8
  215. package/build/src/settlement/fence.d.ts +0 -4
  216. package/build/src/settlement/fence.js +0 -9
  217. package/build/src/settlement/holder.d.ts +11 -1
  218. package/build/src/settlement/holder.js +25 -0
  219. package/build/src/settlement/settle.d.ts +1 -1
  220. package/build/src/settlement/settle.js +52 -89
  221. package/build/src/task/board.d.ts +3 -3
  222. package/build/src/task/board.js +6 -9
  223. package/build/src/task/compose.d.ts +1 -1
  224. package/build/src/task/compose.js +18 -10
  225. package/build/src/task/context.d.ts +3 -3
  226. package/build/src/task/context.js +14 -14
  227. package/build/src/task/document.d.ts +2 -1
  228. package/build/src/task/document.js +15 -5
  229. package/build/src/task/index.d.ts +19 -7
  230. package/build/src/task/index.js +42 -23
  231. package/build/src/task/operations.d.ts +15 -9
  232. package/build/src/task/operations.js +78 -48
  233. package/build/src/task/query.d.ts +67 -0
  234. package/build/src/task/query.js +279 -0
  235. package/build/src/task/store.d.ts +2 -2
  236. package/build/src/task/store.js +40 -25
  237. package/build/src/verification/execution.d.ts +4 -2
  238. package/build/src/verification/execution.js +9 -5
  239. package/build/src/workspace-place.d.ts +41 -0
  240. package/build/src/workspace-place.js +386 -0
  241. package/build/src/world.d.ts +13 -2
  242. package/build/src/world.js +81 -48
  243. package/package.json +3 -2
  244. package/build/src/protocol/read/audit.d.ts +0 -31
  245. package/build/src/protocol/read/audit.js +0 -55
@@ -1,33 +1,151 @@
1
1
  import { fileURLToPath } from "node:url";
2
- import { HeldAkumaLeash, appendActivity, beginTurn, breakBody, endTurn, finishBodyIfIdle, heartExists, pauseRequested, readHeart, recordBody, recordSession, recordTellDeliveries, recordTellReceipt, stopRequested, } from "./heart/index.js";
3
- import { encodeAgentEvent } from "./provider.js";
4
- import { providerNamed } from "./providers/index.js";
2
+ import { abortableDelay } from "./abort.js";
3
+ import { HeldAkumaLeash, appendActivity, beginTurn, breakBody, endTurn, finishBodyIfIdle, heartExists, isHeartAbsent, readHeart, recordSession, recordTellDeliveries, recordTellReceipt, } from "./heart/index.js";
4
+ import { encodeAgentEvent, } from "./provider.js";
5
+ import { resolveProviderExecution } from "./providers/index.js";
5
6
  import { BodyRequestPump, clearBodyRequestTransport, settleBodyRequests, } from "./requests.js";
6
- import { currentProcessCollar, probeProcessTree, putDownProcessTree, spawnDetachedProcess, } from "../runtime/proc/run.js";
7
+ import { spawnDetachedProcess, } from "../runtime/proc/run.js";
7
8
  const LEASH_RETRY_MS = 25;
8
- function wait(milliseconds) {
9
- return new Promise((resolve) => setTimeout(resolve, milliseconds));
9
+ const BODY_CONTROL_OBSERVATION_MS = 100;
10
+ export const CONTROL_RESPONSE_MS = 1_000;
11
+ class BodySupervisor {
12
+ paths;
13
+ bodySequence;
14
+ leash;
15
+ signal;
16
+ controller = new AbortController();
17
+ finished = new AbortController();
18
+ observer;
19
+ waiter;
20
+ observation;
21
+ stopping;
22
+ constructor(paths, bodySequence, leash, observation) {
23
+ this.paths = paths;
24
+ this.bodySequence = bodySequence;
25
+ this.leash = leash;
26
+ this.signal = this.controller.signal;
27
+ this.observation = observation;
28
+ this.observer = this.observe();
29
+ }
30
+ static async open(paths, bodySequence, leash) {
31
+ return new BodySupervisor(paths, bodySequence, leash, await readHeart(paths));
32
+ }
33
+ get reason() { return this.stopping; }
34
+ current() { return this.observation; }
35
+ async recordHung(diagnostic, at) {
36
+ await this.leash.recordBodyHung(this.paths, { sequence: this.bodySequence, diagnostic, at });
37
+ }
38
+ async refresh() {
39
+ this.publish(await readHeart(this.paths));
40
+ return this.observation;
41
+ }
42
+ next(after) {
43
+ if (this.observation !== after)
44
+ return Promise.resolve(this.observation);
45
+ return new Promise((resolve) => { this.waiter = resolve; });
46
+ }
47
+ cancel(reason) {
48
+ if (this.stopping !== undefined)
49
+ return;
50
+ this.stopping = reason;
51
+ this.controller.abort(new Error(reason === "control"
52
+ ? "Akuma Body interrupted by durable control"
53
+ : "Akuma Heart disappeared"));
54
+ this.waiter?.(this.observation);
55
+ this.waiter = undefined;
56
+ }
57
+ async close() {
58
+ this.finished.abort();
59
+ await this.observer;
60
+ this.waiter = undefined;
61
+ }
62
+ publish(snapshot) {
63
+ this.observation = snapshot;
64
+ this.waiter?.(snapshot);
65
+ this.waiter = undefined;
66
+ }
67
+ async observe() {
68
+ for (;;) {
69
+ const snapshot = this.observation;
70
+ if (!await heartExists(this.paths)) {
71
+ this.cancel("heart-gone");
72
+ return;
73
+ }
74
+ if (bodyControlRequested(snapshot, this.bodySequence)) {
75
+ this.cancel("control");
76
+ return;
77
+ }
78
+ try {
79
+ await abortableDelay(BODY_CONTROL_OBSERVATION_MS, this.finished.signal);
80
+ }
81
+ catch {
82
+ return;
83
+ }
84
+ try {
85
+ await this.refresh();
86
+ }
87
+ catch (error) {
88
+ if (await heartExists(this.paths))
89
+ throw error;
90
+ this.cancel("heart-gone");
91
+ return;
92
+ }
93
+ }
94
+ }
10
95
  }
11
96
  function serializeEffects() {
12
97
  let tail = Promise.resolve();
13
- return async (effect) => {
14
- const before = tail;
15
- let release;
16
- tail = new Promise((resolve) => { release = resolve; });
17
- await before;
18
- try {
19
- return await effect();
20
- }
21
- finally {
22
- release();
23
- }
98
+ let failed = false;
99
+ let failure;
100
+ const serialize = async (effect) => {
101
+ const result = tail.then(effect);
102
+ tail = result.then(() => undefined, (error) => {
103
+ if (!failed)
104
+ failure = error;
105
+ failed = true;
106
+ });
107
+ return await result;
108
+ };
109
+ serialize.drain = async () => {
110
+ await tail;
111
+ if (failed)
112
+ throw failure;
24
113
  };
114
+ return serialize;
25
115
  }
26
116
  function missing(error) {
27
- return error.code === "ENOENT";
117
+ return isHeartAbsent(error) || error.code === "ENOENT";
118
+ }
119
+ async function writeProviderEvent(input, active, event) {
120
+ if (input.supervisor.signal.aborted)
121
+ return;
122
+ const at = input.now();
123
+ if (event.type === "session") {
124
+ await recordSession(input.paths, {
125
+ provider: input.soul.provider.name,
126
+ coordinate: event.coordinate,
127
+ cwd: active.cwd,
128
+ options: active.options,
129
+ admittedAt: at,
130
+ });
131
+ }
132
+ await appendActivity(input.paths, {
133
+ turnSequence: active.turnSequence,
134
+ event: encodeAgentEvent(event),
135
+ at,
136
+ });
137
+ }
138
+ function bodyControlRequested(snapshot, bodySequence) {
139
+ return snapshot.stop?.bodySequence === bodySequence
140
+ || snapshot.pause?.bodySequence === bodySequence;
28
141
  }
29
- function turnRecipe(paths, soul) {
30
- const latest = readHeart(paths).latestSession;
142
+ async function putDownIfControlled(paths, supervisor, bodySequence, now) {
143
+ if (supervisor.reason === "control" && await heartExists(paths)) {
144
+ await breakBody(paths, { sequence: bodySequence, end: "put-down", at: now() });
145
+ }
146
+ }
147
+ async function turnRecipe(paths, soul) {
148
+ const latest = (await readHeart(paths)).latestSession;
31
149
  const admitted = latest?.provider === soul.provider.name ? latest : undefined;
32
150
  const session = admitted?.coordinate;
33
151
  return {
@@ -39,7 +157,7 @@ function turnRecipe(paths, soul) {
39
157
  async function takeLeash(paths) {
40
158
  for (;;) {
41
159
  try {
42
- const leash = HeldAkumaLeash.try(paths);
160
+ const leash = await HeldAkumaLeash.try(paths);
43
161
  if (leash !== null)
44
162
  return leash;
45
163
  }
@@ -48,52 +166,48 @@ async function takeLeash(paths) {
48
166
  return null;
49
167
  throw error;
50
168
  }
51
- await wait(LEASH_RETRY_MS);
169
+ await abortableDelay(LEASH_RETRY_MS);
52
170
  }
53
171
  }
54
- async function settlePredecessor(paths) {
55
- const predecessor = readHeart(paths).latestBody;
56
- if (predecessor === null)
57
- return true;
58
- const probe = probeProcessTree(predecessor.collar);
59
- if (probe.kind === "gone")
60
- return true;
61
- if (probe.kind === "unverifiable")
62
- return false;
63
- const evidence = await putDownProcessTree(predecessor.collar);
64
- return evidence !== "unavailable" && evidence !== "alive-after-sigkill";
65
- }
66
172
  async function startTurnDrive(input) {
67
- const { cwd, options, session } = turnRecipe(input.paths, input.soul);
173
+ const { cwd, options, session } = await turnRecipe(input.paths, input.soul);
68
174
  if (session !== undefined && input.adapter.resume === undefined)
69
175
  return { kind: "resume-unsupported" };
70
- const turn = beginTurn(input.paths, {
176
+ const turn = await beginTurn(input.paths, {
71
177
  bodySequence: input.bodySequence,
72
178
  startedAt: input.now(),
73
179
  ...(input.call === undefined ? {} : { call: input.call }),
74
180
  });
75
181
  const requests = input.soul.confinement.kind === "declared"
76
- ? new BodyRequestPump({
182
+ ? await BodyRequestPump.open({
77
183
  paths: input.paths,
78
184
  parent: input.soul,
79
185
  bodySequence: input.bodySequence,
80
186
  now: input.now,
81
187
  spawn: input.runtimeSpawn,
188
+ signal: input.supervisor.signal,
82
189
  })
83
190
  : null;
191
+ const driveInput = {
192
+ body: input.body,
193
+ launchTells: input.launchTells.map((tell) => ({ id: tell.id, text: tell.body })),
194
+ cwd,
195
+ options,
196
+ signal: input.supervisor.signal,
197
+ ...(requests === null ? {} : { requests: { dir: requests.directory } }),
198
+ };
199
+ const setup = session === undefined
200
+ ? input.adapter.start({ ...driveInput, session: { kind: "fresh" } })
201
+ : input.adapter.resume({ ...driveInput, session: { kind: "resume", coordinate: session } });
84
202
  try {
85
- const driveInput = {
86
- body: input.body,
87
- launchTells: input.launchTells.map((tell) => ({ id: tell.id, text: tell.body })),
88
- cwd,
89
- options,
90
- ...(requests === null ? {} : { requests: { dir: requests.directory } }),
91
- };
92
- const selected = session === undefined
93
- ? await input.adapter.start({ ...driveInput, session: { kind: "fresh" } })
94
- : await input.adapter.resume({ ...driveInput, session: { kind: "resume", coordinate: session } });
203
+ const selected = await setup;
204
+ if (input.supervisor.signal.aborted) {
205
+ await retireProviderCustody(input, turn.sequence, selected);
206
+ await requests?.close();
207
+ return { kind: "stopped" };
208
+ }
95
209
  if (input.launchTells.length > 0) {
96
- recordTellDeliveries(input.paths, input.launchTells.map((tell) => ({
210
+ await recordTellDeliveries(input.paths, input.launchTells.map((tell) => ({
97
211
  tellId: tell.id,
98
212
  route: "launch",
99
213
  turnSequence: turn.sequence,
@@ -107,40 +221,79 @@ async function startTurnDrive(input) {
107
221
  ...(session === undefined ? {} : { resume: session }) };
108
222
  }
109
223
  catch (error) {
110
- if (requests !== null)
111
- await requests.close();
112
- const diagnostic = error instanceof Error ? error.message : String(error);
113
- endTurn(input.paths, {
224
+ await requests?.close();
225
+ if (input.supervisor.signal.aborted) {
226
+ if (input.supervisor.reason === "heart-gone")
227
+ throw error;
228
+ return { kind: "stopped" };
229
+ }
230
+ return {
231
+ kind: "failed",
114
232
  turnSequence: turn.sequence,
115
- outcome: { kind: "failed", diagnostic },
116
- completedAt: input.now(),
117
- });
118
- return { kind: "start-failed", turnSequence: turn.sequence, diagnostic };
233
+ diagnostic: error instanceof Error ? error.message : String(error),
234
+ };
119
235
  }
120
236
  }
121
- function pumpReceipts(input, turnSequence, drive, writeWitness) {
237
+ async function retireProviderCustody(input, turnSequence, drive) {
238
+ const outcome = await Promise.race([
239
+ drive.abort().then(() => ({ kind: "retired" }), (error) => ({ kind: "held", error })),
240
+ abortableDelay(CONTROL_RESPONSE_MS).then(() => ({
241
+ kind: "held",
242
+ error: new Error(`provider custody remained live after ${CONTROL_RESPONSE_MS}ms`),
243
+ })),
244
+ ]);
245
+ if (outcome.kind === "retired")
246
+ return;
247
+ if (await heartExists(input.paths)) {
248
+ const diagnostic = outcome.error instanceof Error ? outcome.error.message : String(outcome.error);
249
+ const at = input.now();
250
+ try {
251
+ await input.supervisor.recordHung(diagnostic, at);
252
+ await appendActivity(input.paths, {
253
+ turnSequence,
254
+ event: encodeAgentEvent({ type: "note", text: `Provider custody was not retired: ${diagnostic}` }),
255
+ at,
256
+ });
257
+ }
258
+ catch { /* undisposed provider custody still owns the leash */ }
259
+ }
260
+ for (;;)
261
+ await abortableDelay(60_000);
262
+ }
263
+ function pumpReceipts(writers) {
264
+ const { input, turnSequence, drive, writeWitness, mayWrite } = writers;
122
265
  return (async () => {
123
266
  if (drive.receipts === undefined)
124
267
  return;
125
268
  for await (const receipt of drive.receipts) {
126
- await writeWitness(() => recordTellReceipt(input.paths, receipt.evidence === "exact"
127
- ? { ...receipt, receivedAt: input.now() }
128
- : { ...receipt, turnSequence, receivedAt: input.now() }));
269
+ if (!mayWrite())
270
+ return;
271
+ await writeWitness(async () => {
272
+ if (!mayWrite())
273
+ return;
274
+ await recordTellReceipt(input.paths, receipt.evidence === "exact"
275
+ ? { ...receipt, receivedAt: input.now() }
276
+ : { ...receipt, turnSequence, receivedAt: input.now() });
277
+ });
129
278
  }
130
279
  })();
131
280
  }
132
- async function submitPendingLiveTells(input, turnSequence, drive, attempted, writeWitness) {
133
- if (drive.tell === undefined)
134
- return "live";
135
- for (const tell of readHeart(input.paths).pending) {
281
+ function observeReceiptFailure(receiptPump) {
282
+ return receiptPump.then(() => new Promise(() => { }), (error) => Promise.reject(error));
283
+ }
284
+ async function submitPendingLiveTells(writers, pending, attempted, tellLive) {
285
+ const { input, turnSequence, drive, writeWitness, mayWrite } = writers;
286
+ for (const tell of pending) {
136
287
  if (attempted.has(tell.id))
137
288
  continue;
138
289
  attempted.add(tell.id);
139
290
  const outcome = await writeWitness(async () => {
140
- const submission = await drive.tell({ id: tell.id, text: tell.body });
291
+ const submission = await tellLive({ id: tell.id, text: tell.body });
292
+ if (!mayWrite())
293
+ return "turn-ended";
141
294
  if (submission.kind === "turn-ended")
142
295
  return "turn-ended";
143
- recordTellDeliveries(input.paths, [{
296
+ await recordTellDeliveries(input.paths, [{
144
297
  tellId: tell.id,
145
298
  route: "live",
146
299
  turnSequence,
@@ -155,81 +308,93 @@ async function submitPendingLiveTells(input, turnSequence, drive, attempted, wri
155
308
  }
156
309
  return "live";
157
310
  }
311
+ async function stopActiveDrive(input, active) {
312
+ await retireProviderCustody(input, active.turnSequence, active.drive);
313
+ await active.requests?.close();
314
+ }
315
+ function hasUnattemptedTell(liveTells, tellPump, pending, attempted) {
316
+ return liveTells && tellPump === null && pending.some((tell) => !attempted.has(tell.id));
317
+ }
318
+ function persistProviderEvent(_input, _active, event) {
319
+ return event.type === "session" ? event.coordinate : undefined;
320
+ }
321
+ async function settleCompletion(result, session, requests) {
322
+ await requests?.close();
323
+ return result.kind === "answered" ? { ...result, ...(session === undefined ? {} : { session }) } : result;
324
+ }
158
325
  async function consumeTurnDrive(input, active) {
159
- const { turnSequence, drive, requests, cwd, options, resume } = active;
326
+ const { turnSequence, drive, requests, resume } = active;
327
+ let heart = input.supervisor.current();
160
328
  let turnSession = resume;
161
329
  const attempted = new Set(input.launchTells.map((tell) => tell.id));
162
330
  const writeWitness = serializeEffects();
163
- const receiptPump = pumpReceipts(input, turnSequence, drive, writeWitness);
164
- const receiptFailure = receiptPump.then(() => new Promise(() => { }), (error) => Promise.reject(error));
331
+ let writesOpen = true;
332
+ const mayWrite = () => writesOpen && !input.supervisor.signal.aborted;
333
+ const writers = { input, turnSequence, drive, writeWitness, mayWrite };
334
+ const receiptPump = pumpReceipts(writers);
335
+ const receiptFailure = observeReceiptFailure(receiptPump);
165
336
  const iterator = drive.events[Symbol.asyncIterator]();
166
337
  let pending = iterator.next();
167
338
  let liveTells = true;
339
+ let tellPump = null;
340
+ let tellObservation = null;
168
341
  try {
169
342
  for (;;) {
170
- if (stopRequested(input.paths, input.bodySequence) || pauseRequested(input.paths)) {
171
- const draining = requests?.close();
172
- try {
173
- await drive.abort();
174
- }
175
- finally {
176
- await draining;
177
- await receiptPump;
178
- }
343
+ if (input.supervisor.signal.aborted) {
344
+ writesOpen = false;
345
+ await stopActiveDrive(input, active);
179
346
  return { kind: "stopped" };
180
347
  }
181
- if (liveTells) {
182
- liveTells = await submitPendingLiveTells(input, turnSequence, drive, attempted, writeWitness) === "live";
348
+ if (hasUnattemptedTell(liveTells, tellPump, heart.pending, attempted)) {
349
+ if (drive.tell === undefined) {
350
+ writesOpen = false;
351
+ await stopActiveDrive(input, active);
352
+ return { kind: "handoff" };
353
+ }
354
+ tellPump = submitPendingLiveTells(writers, heart.pending, attempted, drive.tell);
355
+ tellObservation = tellPump.then((result) => ({ kind: "tell", result }));
183
356
  }
184
357
  const next = await Promise.race([
185
- pending,
186
- wait(LEASH_RETRY_MS).then(() => null),
358
+ pending.then((event) => ({ kind: "event", event })),
359
+ input.supervisor.next(heart).then((observation) => ({ kind: "heart", observation })),
360
+ ...(tellObservation === null ? [] : [tellObservation]),
187
361
  receiptFailure,
188
362
  ...(requests === null ? [] : [requests.failure]),
189
363
  ]);
190
- if (next === null)
364
+ if (next.kind === "event" && !next.event.done) {
365
+ await writeProviderEvent(input, active, next.event.value);
366
+ }
367
+ if (next.kind === "heart") {
368
+ heart = next.observation;
191
369
  continue;
192
- if (next.done)
193
- break;
194
- const event = next.value;
195
- const at = input.now();
196
- if (event.type === "session") {
197
- turnSession = event.coordinate;
198
- recordSession(input.paths, {
199
- provider: input.soul.provider.name,
200
- coordinate: event.coordinate,
201
- cwd,
202
- options,
203
- admittedAt: at,
204
- });
205
370
  }
206
- appendActivity(input.paths, {
207
- turnSequence,
208
- event: encodeAgentEvent(event),
209
- at,
210
- });
371
+ if (next.kind === "tell") {
372
+ tellPump = null;
373
+ tellObservation = null;
374
+ liveTells = next.result === "live";
375
+ continue;
376
+ }
377
+ if (next.event.done)
378
+ break;
379
+ turnSession = persistProviderEvent(input, active, next.event.value) ?? turnSession;
211
380
  pending = iterator.next();
212
381
  }
213
382
  const result = await drive.completion;
214
- await receiptPump;
215
- if (requests !== null)
216
- await requests.close();
217
- return result.kind === "answered"
218
- ? { ...result, ...(turnSession === undefined ? {} : { session: turnSession }) }
219
- : result;
383
+ if (tellPump !== null)
384
+ await tellPump;
385
+ await writeWitness.drain();
386
+ writesOpen = false;
387
+ return await settleCompletion(result, turnSession, requests);
220
388
  }
221
389
  catch (error) {
222
- const draining = requests?.close();
223
- try {
224
- await drive.abort();
225
- }
226
- catch { /* the pump failure owns the turn result */ }
227
- try {
228
- await receiptPump;
390
+ writesOpen = false;
391
+ if (input.supervisor.signal.aborted) {
392
+ await stopActiveDrive(input, active);
393
+ return { kind: "stopped" };
229
394
  }
230
- catch { /* preserve the first pump failure */ }
395
+ await retireProviderCustody(input, turnSequence, drive);
231
396
  try {
232
- await draining;
397
+ await requests?.close();
233
398
  }
234
399
  catch { /* preserve the first pump failure */ }
235
400
  return { kind: "failed", diagnostic: error instanceof Error ? error.message : String(error) };
@@ -244,64 +409,62 @@ async function driveTurn(input) {
244
409
  active = started;
245
410
  }
246
411
  catch (error) {
247
- if (!heartExists(input.paths))
248
- return { kind: "heart-gone" };
412
+ if (!await heartExists(input.paths)) {
413
+ input.supervisor.cancel("heart-gone");
414
+ return { kind: "stopped" };
415
+ }
249
416
  throw error;
250
417
  }
251
418
  try {
252
- return { ...await consumeTurnDrive(input, active), turnSequence: active.turnSequence };
419
+ const result = await consumeTurnDrive(input, active);
420
+ return result.kind === "stopped" || result.kind === "handoff"
421
+ ? result
422
+ : { ...result, turnSequence: active.turnSequence };
253
423
  }
254
424
  catch (error) {
255
- const draining = active.requests?.close();
256
- if (heartExists(input.paths)) {
257
- await draining;
425
+ if (await heartExists(input.paths)) {
426
+ await active.requests?.close();
258
427
  return { kind: "failed", diagnostic: error instanceof Error ? error.message : String(error),
259
428
  turnSequence: active.turnSequence };
260
429
  }
261
- try {
262
- await active.drive.abort();
263
- }
264
- catch { /* the heart loss still owns shutdown */ }
265
- try {
266
- await draining;
267
- }
268
- catch { /* the vanished heart owns the request loss */ }
269
- return { kind: "heart-gone" };
430
+ input.supervisor.cancel("heart-gone");
431
+ await stopActiveDrive(input, active);
432
+ return { kind: "stopped" };
270
433
  }
271
434
  }
272
- function bornSoul(launch, leash, now) {
273
- const before = readHeart(launch.paths);
274
- if (before.soul !== null) {
275
- leash.clearPause(launch.paths);
435
+ async function bornSoul(launch, leash, now) {
436
+ const before = await readHeart(launch.paths);
437
+ if (before.soul !== null)
276
438
  return before.soul;
277
- }
278
439
  if (launch.seed === undefined)
279
440
  throw new Error("Akuma wake has no born soul");
280
441
  const candidate = { ...launch.seed, createdAt: now };
281
- if (leash.birth(launch.paths, candidate, launch.birthSession) === "sealed")
442
+ if (await leash.birth(launch.paths, candidate, launch.birthSession) === "sealed")
282
443
  return null;
283
- const born = readHeart(launch.paths).soul;
444
+ const born = (await readHeart(launch.paths)).soul;
284
445
  if (born === null || born.id !== candidate.id) {
285
446
  throw new Error("Akuma birth identity does not match its directory");
286
447
  }
287
448
  return born;
288
449
  }
289
- function launchCwd(launch) {
450
+ async function launchCwd(launch) {
290
451
  if (launch.seed !== undefined)
291
452
  return launch.seed.cwd;
292
- const snapshot = readHeart(launch.paths);
293
- const soul = snapshot.soul;
453
+ const soul = (await readHeart(launch.paths)).soul;
294
454
  if (soul === null)
295
455
  throw new Error("Akuma wake has no born soul");
296
- return snapshot.latestSession?.provider === soul.provider.name
297
- ? snapshot.latestSession.cwd
298
- : soul.cwd;
456
+ return (await turnRecipe(launch.paths, soul)).cwd;
299
457
  }
300
- function persistTurn(paths, turnSequence, result, completedAt) {
301
- endTurn(paths, {
458
+ async function persistTurn(paths, turnSequence, result, completedAt) {
459
+ await endTurn(paths, {
302
460
  turnSequence,
303
461
  outcome: result.kind === "answered" && result.session !== undefined
304
- ? { kind: "answered", historyId: result.historyId, session: result.session, answer: result.answer }
462
+ ? {
463
+ kind: "answered",
464
+ session: result.session,
465
+ answer: result.answer,
466
+ ...(result.historyId === undefined ? {} : { historyId: result.historyId }),
467
+ }
305
468
  : result.kind === "answered"
306
469
  ? { kind: "failed", diagnostic: "Provider answered without a resumable session" }
307
470
  : { kind: "failed", diagnostic: result.diagnostic },
@@ -311,87 +474,114 @@ function persistTurn(paths, turnSequence, result, completedAt) {
311
474
  }
312
475
  function defaultRuntime() {
313
476
  return {
314
- collar: currentProcessCollar(),
315
477
  now: () => new Date().toISOString(),
316
- putDownOwnTree: async (collar) => { await putDownProcessTree(collar); },
317
- spawnChild: async (launch) => await spawnAkumaBody(launch),
478
+ spawnChild: spawnAkumaBody,
318
479
  };
319
480
  }
320
- function childSpawner(runtime) {
321
- return runtime.spawnChild ?? spawnAkumaBody;
481
+ async function prepareBodyStart(paths, leash) {
482
+ const before = await readHeart(paths);
483
+ if (before.pause !== null)
484
+ return false;
485
+ if (before.stop === null)
486
+ return true;
487
+ if (before.latestBody?.sequence === before.stop.bodySequence && before.latestBody.end === "put-down") {
488
+ await leash.settleStop(paths);
489
+ }
490
+ else {
491
+ await leash.clearStop(paths);
492
+ }
493
+ return true;
322
494
  }
323
- async function beginBody(paths, soul, leash, runtime) {
324
- if (!await settlePredecessor(paths))
325
- return null;
326
- leash.settleStop(paths);
327
- const body = recordBody(paths, { collar: runtime.collar, leashTakenAt: runtime.now() });
328
- const requests = await settleBodyRequests(paths, soul, runtime.now);
329
- clearBodyRequestTransport(paths);
330
- if (requests === "settled")
331
- return body;
332
- breakBody(paths, { sequence: body.sequence, end: "broke-off", at: runtime.now() });
333
- return null;
495
+ async function recoverBodyRequests(input) {
496
+ const { launch, soul, bodySequence, supervisor, runtime } = input;
497
+ try {
498
+ const result = await settleBodyRequests(launch.paths, soul, runtime.now, supervisor.signal);
499
+ await clearBodyRequestTransport(launch.paths);
500
+ if (result === "settled")
501
+ return true;
502
+ await breakBody(launch.paths, { sequence: bodySequence, end: "broke-off", at: runtime.now() });
503
+ }
504
+ catch (error) {
505
+ if (!supervisor.signal.aborted)
506
+ throw error;
507
+ await clearBodyRequestTransport(launch.paths);
508
+ await putDownIfControlled(launch.paths, supervisor, bodySequence, runtime.now);
509
+ }
510
+ return false;
511
+ }
512
+ async function runBodyTurns(input) {
513
+ const { launch, soul, adapter, bodySequence, supervisor, runtime } = input;
514
+ let initial = launch.initialBody;
515
+ for (;;) {
516
+ const launchTells = supervisor.current().pending;
517
+ if (supervisor.signal.aborted) {
518
+ await putDownIfControlled(launch.paths, supervisor, bodySequence, runtime.now);
519
+ return;
520
+ }
521
+ if (initial === undefined && launchTells.length === 0) {
522
+ const finished = await finishBodyIfIdle(launch.paths, { sequence: bodySequence, at: runtime.now() });
523
+ if (finished.kind === "finished")
524
+ return;
525
+ if (finished.kind === "controlled") {
526
+ supervisor.cancel("control");
527
+ return;
528
+ }
529
+ continue;
530
+ }
531
+ const result = await driveTurn({
532
+ paths: launch.paths,
533
+ soul,
534
+ adapter,
535
+ bodySequence,
536
+ supervisor,
537
+ runtimeSpawn: runtime.spawnChild ?? spawnAkumaBody,
538
+ body: initial ?? "",
539
+ ...(initial === undefined ? {} : { call: initial }),
540
+ launchTells,
541
+ now: runtime.now,
542
+ });
543
+ if (result.kind === "stopped") {
544
+ await putDownIfControlled(launch.paths, supervisor, bodySequence, runtime.now);
545
+ return;
546
+ }
547
+ if (result.kind === "handoff") {
548
+ await breakBody(launch.paths, { sequence: bodySequence, end: "put-down", at: runtime.now() });
549
+ return;
550
+ }
551
+ if (result.kind === "resume-unsupported"
552
+ || await persistTurn(launch.paths, result.turnSequence, result, runtime.now()) === "failed") {
553
+ await breakBody(launch.paths, { sequence: bodySequence, end: "broke-off", at: runtime.now() });
554
+ return;
555
+ }
556
+ await supervisor.refresh();
557
+ initial = undefined;
558
+ }
334
559
  }
335
560
  export async function driveAkumaBody(launch, adapter, runtime = defaultRuntime()) {
336
561
  const leash = await takeLeash(launch.paths);
337
562
  if (leash === null)
338
563
  return;
339
564
  try {
340
- const soul = bornSoul(launch, leash, runtime.now());
565
+ const soul = await bornSoul(launch, leash, runtime.now());
341
566
  if (soul === null)
342
567
  return;
343
- const selected = adapter ?? providerNamed(soul.provider);
344
- const body = await beginBody(launch.paths, soul, leash, runtime);
345
- if (body === null)
568
+ const selected = adapter ?? resolveProviderExecution(soul.provider).adapter;
569
+ if (!await prepareBodyStart(launch.paths, leash))
346
570
  return;
347
- let initial = launch.initialBody;
348
- for (;;) {
349
- const snapshot = readHeart(launch.paths);
350
- const launchTells = snapshot.pending;
351
- if (initial === undefined && launchTells.length === 0) {
352
- const finished = finishBodyIfIdle(launch.paths, { sequence: body.sequence, at: runtime.now() });
353
- if (finished.kind === "finished")
354
- return;
355
- continue;
356
- }
357
- const result = await driveTurn({
358
- paths: launch.paths,
359
- soul,
360
- adapter: selected,
361
- bodySequence: body.sequence,
362
- runtimeSpawn: childSpawner(runtime),
363
- body: initial ?? "",
364
- ...(initial === undefined ? {} : { call: initial }),
365
- launchTells,
366
- now: runtime.now,
367
- });
368
- if (result.kind === "heart-gone") {
369
- await runtime.putDownOwnTree(runtime.collar);
370
- return;
371
- }
372
- if (result.kind === "stopped") {
373
- breakBody(launch.paths, { sequence: body.sequence, end: "put-down", at: runtime.now() });
374
- return;
375
- }
376
- if (result.kind === "resume-unsupported") {
377
- breakBody(launch.paths, { sequence: body.sequence, end: "broke-off", at: runtime.now() });
378
- return;
379
- }
380
- if (result.kind === "start-failed") {
381
- breakBody(launch.paths, { sequence: body.sequence, end: "broke-off", at: runtime.now() });
382
- return;
383
- }
384
- if (persistTurn(launch.paths, result.turnSequence, result, runtime.now()) === "failed") {
385
- breakBody(launch.paths, { sequence: body.sequence, end: "broke-off", at: runtime.now() });
386
- return;
387
- }
388
- initial = undefined;
571
+ const body = await leash.recordBody(launch.paths, { leashTakenAt: runtime.now() });
572
+ const supervisor = await BodySupervisor.open(launch.paths, body.sequence, leash);
573
+ const execution = { launch, soul, adapter: selected, bodySequence: body.sequence, supervisor, runtime };
574
+ try {
575
+ if (await recoverBodyRequests(execution))
576
+ await runBodyTurns(execution);
577
+ }
578
+ finally {
579
+ await supervisor.close();
389
580
  }
390
581
  }
391
582
  catch (error) {
392
- if (heartExists(launch.paths))
583
+ if (await heartExists(launch.paths))
393
584
  throw error;
394
- await runtime.putDownOwnTree(runtime.collar);
395
585
  }
396
586
  finally {
397
587
  leash.release();
@@ -399,11 +589,16 @@ export async function driveAkumaBody(launch, adapter, runtime = defaultRuntime()
399
589
  }
400
590
  export async function spawnAkumaBody(launch) {
401
591
  const encoded = Buffer.from(JSON.stringify(launch), "utf8").toString("base64url");
402
- return await spawnDetachedProcess({
592
+ const actorId = launch.seed?.id ?? (await readHeart(launch.paths)).soul?.id;
593
+ if (actorId === undefined)
594
+ throw new Error("Akuma wake has no born soul");
595
+ const owned = await spawnDetachedProcess({
403
596
  argv: [process.execPath, ...process.execArgv, fileURLToPath(import.meta.url), encoded],
404
- cwd: launchCwd(launch),
597
+ cwd: await launchCwd(launch),
598
+ env: { ...process.env, KEIYAKU_ACTOR_ID: actorId },
405
599
  log: launch.paths.log,
406
600
  });
601
+ owned.release();
407
602
  }
408
603
  async function main() {
409
604
  const encoded = process.argv[2];