@astrosheep/keiyaku 4.5.11 → 4.5.13

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 (272) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +135 -377
  2. package/build/integrations/pi/keiyaku.ts +11 -116
  3. package/build/src/akuma/akuma-errors.d.ts +1 -0
  4. package/build/src/akuma/akuma-errors.js +1 -0
  5. package/build/src/akuma/akuma-handle.d.ts +1 -0
  6. package/build/src/akuma/akuma-handle.js +6 -7
  7. package/build/src/akuma/akuma-product.d.ts +6 -8
  8. package/build/src/akuma/akuma-product.js +141 -57
  9. package/build/src/akuma/akuma.d.ts +5 -7
  10. package/build/src/akuma/akuma.js +1 -22
  11. package/build/src/akuma/archetype.d.ts +6 -5
  12. package/build/src/akuma/archetype.js +137 -38
  13. package/build/src/akuma/body.d.ts +5 -5
  14. package/build/src/akuma/body.js +223 -63
  15. package/build/src/akuma/call-input.d.ts +4 -0
  16. package/build/src/akuma/call-input.js +21 -0
  17. package/build/src/akuma/call-request.d.ts +5 -8
  18. package/build/src/akuma/call-request.js +28 -73
  19. package/build/src/akuma/fleet-execution.d.ts +26 -0
  20. package/build/src/akuma/fleet-execution.js +109 -0
  21. package/build/src/{library/fleet-result.d.ts → akuma/fleet-observation.d.ts} +13 -57
  22. package/build/src/{library/fleet-result.js → akuma/fleet-observation.js} +4 -25
  23. package/build/src/akuma/fleet-request.d.ts +122 -0
  24. package/build/src/akuma/fleet-request.js +168 -0
  25. package/build/src/akuma/heart/facts.d.ts +23 -0
  26. package/build/src/akuma/heart/facts.js +7 -0
  27. package/build/src/akuma/heart/index.d.ts +43 -4
  28. package/build/src/akuma/heart/index.js +107 -7
  29. package/build/src/akuma/heart/request-authority.d.ts +2 -0
  30. package/build/src/akuma/heart/request-authority.js +28 -3
  31. package/build/src/akuma/heart/request-rows.d.ts +2 -0
  32. package/build/src/akuma/heart/request-rows.js +24 -5
  33. package/build/src/akuma/heart/rows.d.ts +4 -1
  34. package/build/src/akuma/heart/rows.js +46 -19
  35. package/build/src/akuma/heart/schema.d.ts +1 -1
  36. package/build/src/akuma/heart/schema.js +28 -6
  37. package/build/src/akuma/heart/storage.d.ts +1 -1
  38. package/build/src/akuma/heart/tells.d.ts +29 -0
  39. package/build/src/akuma/heart/tells.js +136 -5
  40. package/build/src/akuma/heart/timeline.js +9 -3
  41. package/build/src/akuma/projection-read.js +36 -3
  42. package/build/src/akuma/projection.js +3 -4
  43. package/build/src/akuma/provider.d.ts +4 -0
  44. package/build/src/akuma/provider.js +56 -3
  45. package/build/src/akuma/providers/claude/index.js +3 -0
  46. package/build/src/akuma/providers/codex-app-server/index.js +14 -12
  47. package/build/src/akuma/providers/pi/index.js +7 -1
  48. package/build/src/akuma/publication.js +65 -49
  49. package/build/src/akuma/request-lifecycle.d.ts +5 -4
  50. package/build/src/akuma/request-lifecycle.js +29 -16
  51. package/build/src/akuma/request-rendezvous.d.ts +15 -12
  52. package/build/src/akuma/request-rendezvous.js +80 -51
  53. package/build/src/akuma/request-serve.d.ts +1 -2
  54. package/build/src/akuma/request-serve.js +94 -39
  55. package/build/src/akuma/request-wire.d.ts +57 -26
  56. package/build/src/akuma/request-wire.js +64 -25
  57. package/build/src/akuma/turn-drive.d.ts +5 -4
  58. package/build/src/akuma/turn-drive.js +27 -11
  59. package/build/src/akuma-body.d.ts +4 -6
  60. package/build/src/akuma-body.js +19 -17
  61. package/build/src/bounded-list.d.ts +8 -0
  62. package/build/src/bounded-list.js +14 -0
  63. package/build/src/cli/accepted.d.ts +1 -1
  64. package/build/src/cli/accepted.js +1 -0
  65. package/build/src/cli/commands/akuma-invoke.js +1 -25
  66. package/build/src/cli/commands/contract-help.d.ts +5 -2
  67. package/build/src/cli/commands/contract-help.js +62 -12
  68. package/build/src/cli/commands/contract-invoke.d.ts +5 -0
  69. package/build/src/cli/commands/contract-invoke.js +26 -1
  70. package/build/src/cli/commands/contract.js +50 -18
  71. package/build/src/cli/commands/task-invoke.js +10 -2
  72. package/build/src/cli/commands/task-query.js +3 -4
  73. package/build/src/cli/coordinates.d.ts +13 -0
  74. package/build/src/cli/coordinates.js +38 -24
  75. package/build/src/cli/draft.d.ts +1 -1
  76. package/build/src/cli/invoke.js +14 -1
  77. package/build/src/cli/render/akuma-activity.d.ts +0 -1
  78. package/build/src/cli/render/akuma-activity.js +5 -38
  79. package/build/src/cli/render/akuma.js +4 -1
  80. package/build/src/cli/render/audit.js +4 -1
  81. package/build/src/cli/render/catalog.js +13 -39
  82. package/build/src/cli/render/contract.js +4 -14
  83. package/build/src/cli/render/kanshi-akuma.js +3 -8
  84. package/build/src/cli/render/kanshi.js +8 -34
  85. package/build/src/cli/render/nuke.js +7 -3
  86. package/build/src/cli/render/receipt.d.ts +1 -0
  87. package/build/src/cli/render/receipt.js +8 -0
  88. package/build/src/cli/render/refusal.js +4 -3
  89. package/build/src/cli/render/task.js +4 -37
  90. package/build/src/cli/render/terminal.d.ts +2 -6
  91. package/build/src/cli/render/terminal.js +4 -9
  92. package/build/src/cli/result.d.ts +2 -1
  93. package/build/src/contract-guidance.js +13 -3
  94. package/build/src/contract-worktree.d.ts +1 -0
  95. package/build/src/contract-worktree.js +26 -2
  96. package/build/src/core/facts/codec.d.ts +2 -1
  97. package/build/src/core/facts/codec.js +3 -0
  98. package/build/src/core/facts/gate.d.ts +2 -1
  99. package/build/src/core/facts/gate.js +63 -0
  100. package/build/src/core/verbs/abandon.d.ts +2 -1
  101. package/build/src/core/verbs/abandon.js +16 -0
  102. package/build/src/core/verbs/amend.d.ts +2 -1
  103. package/build/src/core/verbs/amend.js +20 -0
  104. package/build/src/core/verbs/arc.d.ts +2 -1
  105. package/build/src/core/verbs/arc.js +16 -0
  106. package/build/src/core/verbs/attestation.d.ts +2 -1
  107. package/build/src/core/verbs/attestation.js +16 -0
  108. package/build/src/core/verbs/bind.d.ts +2 -1
  109. package/build/src/core/verbs/bind.js +16 -0
  110. package/build/src/core/verbs/deliver.d.ts +2 -1
  111. package/build/src/core/verbs/deliver.js +16 -0
  112. package/build/src/core/verbs/placement.d.ts +2 -1
  113. package/build/src/core/verbs/placement.js +58 -1
  114. package/build/src/dispatch/association.d.ts +13 -0
  115. package/build/src/dispatch/association.js +20 -0
  116. package/build/src/dispatch/index.d.ts +6 -0
  117. package/build/src/dispatch/index.js +27 -3
  118. package/build/src/git/hooks.d.ts +9 -1
  119. package/build/src/git/hooks.js +79 -1
  120. package/build/src/git/nuke.d.ts +2 -1
  121. package/build/src/git/nuke.js +6 -11
  122. package/build/src/git/private-state-seat.d.ts +26 -1
  123. package/build/src/git/private-state-seat.js +78 -13
  124. package/build/src/git/process.d.ts +2 -0
  125. package/build/src/git/reconcile.d.ts +1 -2
  126. package/build/src/git/reconcile.js +0 -6
  127. package/build/src/git/repository.d.ts +0 -2
  128. package/build/src/git/repository.js +27 -25
  129. package/build/src/git/result-codec.d.ts +29 -0
  130. package/build/src/git/result-codec.js +306 -0
  131. package/build/src/git/target-placement.js +2 -8
  132. package/build/src/git/workspace.d.ts +2 -0
  133. package/build/src/git/workspace.js +4 -0
  134. package/build/src/index.d.ts +1 -0
  135. package/build/src/kanshi/fleet.js +1 -1
  136. package/build/src/kanshi/read.js +55 -28
  137. package/build/src/kanshi/report.d.ts +4 -3
  138. package/build/src/kanshi/select.js +14 -1
  139. package/build/src/library/address.d.ts +1 -0
  140. package/build/src/library/address.js +4 -7
  141. package/build/src/library/akuma-creation.d.ts +5 -3
  142. package/build/src/library/akuma-creation.js +90 -33
  143. package/build/src/library/audit.js +2 -1
  144. package/build/src/library/catalog.d.ts +10 -2
  145. package/build/src/library/catalog.js +65 -25
  146. package/build/src/library/composition.d.ts +1 -0
  147. package/build/src/library/composition.js +1 -0
  148. package/build/src/library/configuration.d.ts +4 -21
  149. package/build/src/library/configuration.js +2 -158
  150. package/build/src/library/continuation.d.ts +20 -1
  151. package/build/src/library/continuation.js +36 -0
  152. package/build/src/library/contract-bind.d.ts +1 -0
  153. package/build/src/library/contract-bind.js +16 -5
  154. package/build/src/library/contract-forwarding.d.ts +2 -2
  155. package/build/src/library/contract-forwarding.js +18 -4
  156. package/build/src/library/contract-handle.js +4 -3
  157. package/build/src/library/contract-operations.d.ts +5 -9
  158. package/build/src/library/contract-operations.js +34 -47
  159. package/build/src/library/contract.d.ts +11 -2
  160. package/build/src/library/contract.js +21 -1
  161. package/build/src/library/delivery.d.ts +4 -1
  162. package/build/src/library/delivery.js +47 -0
  163. package/build/src/library/fleet.d.ts +4 -136
  164. package/build/src/library/fleet.js +31 -315
  165. package/build/src/library/keiyaku.d.ts +7 -3
  166. package/build/src/library/keiyaku.js +1 -0
  167. package/build/src/library/mutation.d.ts +431 -3
  168. package/build/src/library/mutation.js +192 -19
  169. package/build/src/library/nuke.d.ts +7 -1
  170. package/build/src/library/nuke.js +18 -4
  171. package/build/src/library/reconcile.d.ts +14 -0
  172. package/build/src/library/reconcile.js +22 -8
  173. package/build/src/library/refusal.d.ts +9 -1
  174. package/build/src/library/refusal.js +76 -0
  175. package/build/src/library/region.d.ts +0 -8
  176. package/build/src/library/region.js +1 -29
  177. package/build/src/library/result-codec.d.ts +2 -0
  178. package/build/src/library/result-codec.js +12 -0
  179. package/build/src/markdown/lex.js +9 -2
  180. package/build/src/markdown/types.d.ts +2 -1
  181. package/build/src/markdown/types.js +3 -1
  182. package/build/src/plugin/akuma-signals.d.ts +12 -0
  183. package/build/src/plugin/akuma-signals.js +14 -0
  184. package/build/src/plugin/public.d.ts +51 -0
  185. package/build/src/plugin/public.js +1 -0
  186. package/build/src/plugin/runtime.d.ts +14 -0
  187. package/build/src/plugin/runtime.js +357 -0
  188. package/build/src/protocol/amend.js +3 -3
  189. package/build/src/protocol/attempt.d.ts +2 -1
  190. package/build/src/protocol/attempt.js +1 -1
  191. package/build/src/protocol/audit.d.ts +1 -1
  192. package/build/src/protocol/audit.js +1 -1
  193. package/build/src/protocol/bind.d.ts +3 -1
  194. package/build/src/protocol/bind.js +26 -0
  195. package/build/src/protocol/completion.js +0 -1
  196. package/build/src/protocol/deliver.d.ts +1 -1
  197. package/build/src/protocol/deliver.js +136 -57
  198. package/build/src/protocol/intent.d.ts +0 -1
  199. package/build/src/protocol/intent.js +0 -1
  200. package/build/src/protocol/operations.d.ts +9 -2
  201. package/build/src/protocol/operations.js +16 -1
  202. package/build/src/protocol/outcome.d.ts +3 -0
  203. package/build/src/protocol/outcome.js +17 -1
  204. package/build/src/protocol/placement.js +10 -3
  205. package/build/src/protocol/read/status.d.ts +8 -0
  206. package/build/src/protocol/read/status.js +49 -0
  207. package/build/src/protocol/reintegrate.js +10 -3
  208. package/build/src/protocol/result-codec.d.ts +22 -0
  209. package/build/src/protocol/result-codec.js +442 -0
  210. package/build/src/protocol/review.d.ts +1 -1
  211. package/build/src/protocol/review.js +81 -46
  212. package/build/src/protocol/run.d.ts +22 -2
  213. package/build/src/protocol/run.js +119 -29
  214. package/build/src/runtime/proc/lifecycle.js +1 -1
  215. package/build/src/runtime/proc/line-rpc.d.ts +7 -1
  216. package/build/src/runtime/proc/line-rpc.js +49 -9
  217. package/build/src/runtime/proc/run.d.ts +0 -1
  218. package/build/src/runtime/proc/run.js +1 -11
  219. package/build/src/runtime/proc/termination.d.ts +1 -0
  220. package/build/src/runtime/proc/termination.js +63 -18
  221. package/build/src/runtime/proc/windows-launch.exe +0 -0
  222. package/build/src/settings.d.ts +10 -0
  223. package/build/src/settings.js +88 -2
  224. package/build/src/settlement/result-codec.d.ts +2 -0
  225. package/build/src/settlement/result-codec.js +56 -0
  226. package/build/src/settlement/settle.d.ts +5 -12
  227. package/build/src/settlement/settle.js +60 -55
  228. package/build/src/task/board.d.ts +3 -0
  229. package/build/src/task/board.js +22 -4
  230. package/build/src/task/catalog.d.ts +13 -2
  231. package/build/src/task/catalog.js +59 -5
  232. package/build/src/task/compose.d.ts +4 -2
  233. package/build/src/task/compose.js +56 -30
  234. package/build/src/task/created-observation.d.ts +39 -0
  235. package/build/src/task/created-observation.js +26 -0
  236. package/build/src/task/identity.d.ts +6 -0
  237. package/build/src/task/identity.js +48 -7
  238. package/build/src/task/index.d.ts +4 -3
  239. package/build/src/task/index.js +19 -16
  240. package/build/src/task/input.d.ts +3 -1
  241. package/build/src/task/input.js +7 -4
  242. package/build/src/task/lifecycle-operations.d.ts +6 -0
  243. package/build/src/task/lifecycle-operations.js +116 -0
  244. package/build/src/task/mutation-result.d.ts +762 -20
  245. package/build/src/task/mutation-result.js +34 -9
  246. package/build/src/task/mutation.d.ts +4 -9
  247. package/build/src/task/mutation.js +17 -52
  248. package/build/src/task/operation-types.d.ts +114 -0
  249. package/build/src/task/operation-types.js +1 -0
  250. package/build/src/task/operations.d.ts +6 -113
  251. package/build/src/task/operations.js +75 -117
  252. package/build/src/task/query.d.ts +8 -11
  253. package/build/src/task/query.js +7 -14
  254. package/build/src/task/store.d.ts +11 -1
  255. package/build/src/task/store.js +169 -55
  256. package/build/src/task/view.d.ts +4 -0
  257. package/build/src/task/view.js +7 -0
  258. package/build/src/verification/declaration.d.ts +1 -0
  259. package/build/src/verification/declaration.js +18 -1
  260. package/build/src/verification/execution.d.ts +0 -1
  261. package/build/src/verification/execution.js +5 -5
  262. package/build/src/world.d.ts +0 -2
  263. package/build/src/world.js +3 -7
  264. package/package.json +17 -5
  265. package/build/src/cli/square-edge.d.ts +0 -7
  266. package/build/src/cli/square-edge.js +0 -159
  267. package/build/src/git/ref-migration.d.ts +0 -22
  268. package/build/src/git/ref-migration.js +0 -55
  269. package/build/src/library/contract-forwarding-reconciliation-result.d.ts +0 -102
  270. package/build/src/library/contract-forwarding-reconciliation-result.js +0 -92
  271. package/build/src/library/contract-forwarding-result.d.ts +0 -4270
  272. package/build/src/library/contract-forwarding-result.js +0 -521
@@ -3,9 +3,10 @@ import { fileURLToPath } from "node:url";
3
3
  import { abortableDelay } from "./abort.js";
4
4
  import { BodySupervisor } from "./body-supervisor.js";
5
5
  import { driveTurn, turnRecipe } from "./turn-drive.js";
6
- import { HeldAkumaLeash, breakBody, endTurn, finishBodyIfIdle, heartExists, isHeartAbsent, projectTell, readHeart, readTell, readNonterminalRequests, } from "./heart/index.js";
6
+ import { HeldAkumaLeash, breakBody, decidePendingTellDisposition, drainPendingTells, endTurn, failOpenBoundTurns, finishBodyIfIdle, heartExists, isHeartAbsent, probeLeash, projectTell, provePendingTellDispositionCustody, readHeart, readOpenPendingTellDisposition, readTell, readTurn, readNonterminalRequests, recordUndeliveredPendingTells, resolvePendingTellDisposition, } from "./heart/index.js";
7
7
  import { worldRootForAkumaPaths } from "./identity.js";
8
- import { keiyakuSquarePath } from "../world.js";
8
+ import { pluginRuntime } from "../plugin/runtime.js";
9
+ import { World } from "../world.js";
9
10
  import { resolveProviderExecution } from "./providers/index.js";
10
11
  import { clearBodyRequestTransport, settleBodyRequests } from "./request-serve.js";
11
12
  import { spawnDetachedProcess, } from "../runtime/proc/run.js";
@@ -16,8 +17,14 @@ export { CONTROL_RESPONSE_MS } from "./body-supervisor.js";
16
17
  function missing(error) {
17
18
  return isHeartAbsent(error) || error.code === "ENOENT";
18
19
  }
20
+ async function failOpenBoundTurnsIfPresent(paths, bodySequence, diagnostic, at) {
21
+ if (!(await heartExists(paths)))
22
+ return;
23
+ await failOpenBoundTurns(paths, { bodySequence, diagnostic, completedAt: at });
24
+ }
19
25
  async function putDownIfControlled(paths, supervisor, bodySequence, now) {
20
26
  if (supervisor.reason === "control" && (await heartExists(paths))) {
27
+ await failOpenBoundTurnsIfPresent(paths, bodySequence, "Body put-down with open bound Turns", now());
21
28
  await breakBody(paths, { sequence: bodySequence, end: "put-down", at: now() });
22
29
  }
23
30
  }
@@ -61,8 +68,48 @@ async function launchCwd(launch) {
61
68
  throw new Error("Akuma wake has no born soul");
62
69
  return (await turnRecipe(launch.paths, soul)).cwd;
63
70
  }
71
+ function parseSchemaAnswerJson(raw) {
72
+ try {
73
+ JSON.parse(raw);
74
+ return { kind: "json", json: raw };
75
+ }
76
+ catch (error) {
77
+ return {
78
+ kind: "invalid",
79
+ diagnostic: error instanceof Error ? error.message : "Answer is not valid JSON",
80
+ };
81
+ }
82
+ }
64
83
  async function persistTurn(paths, turnSequence, result, completedAt) {
84
+ const turn = await readTurn(paths, turnSequence);
85
+ const schemaJson = turn?.schemaJson;
65
86
  if (result.kind === "answered" && result.session !== undefined) {
87
+ if (schemaJson !== undefined) {
88
+ const parsed = parseSchemaAnswerJson(result.answer);
89
+ if (parsed.kind === "invalid") {
90
+ const outcome = {
91
+ kind: "invalid-output",
92
+ diagnostic: parsed.diagnostic,
93
+ answer: result.answer,
94
+ session: result.session,
95
+ ...(result.historyId === undefined ? {} : { historyId: result.historyId }),
96
+ };
97
+ await endTurn(paths, { turnSequence, outcome, completedAt });
98
+ return { outcome: "failed", diagnostic: parsed.diagnostic };
99
+ }
100
+ await endTurn(paths, {
101
+ turnSequence,
102
+ outcome: {
103
+ kind: "answered",
104
+ session: result.session,
105
+ answer: result.answer,
106
+ answerJson: parsed.json,
107
+ ...(result.historyId === undefined ? {} : { historyId: result.historyId }),
108
+ },
109
+ completedAt,
110
+ });
111
+ return { outcome: "answered", answer: result.answer };
112
+ }
66
113
  await endTurn(paths, {
67
114
  turnSequence,
68
115
  outcome: {
@@ -79,52 +126,24 @@ async function persistTurn(paths, turnSequence, result, completedAt) {
79
126
  await endTurn(paths, { turnSequence, outcome: { kind: "failed", diagnostic }, completedAt });
80
127
  return { outcome: "failed", diagnostic };
81
128
  }
82
- function completionMessage(identity, completion) {
83
- const participant = completion?.participantName;
84
- const contract = completion?.contractId;
85
- const header = [identity, participant === undefined ? undefined : `(@${participant})`, contract]
86
- .filter((value) => value !== undefined)
87
- .join(" ");
88
- return `${header}\n✓ came back`;
89
- }
90
129
  function boundedDiagnostic(error) {
91
130
  const text = diagnostic(error);
92
131
  return text.length <= 500 ? text : `${text.slice(0, 500)}...`;
93
132
  }
94
- async function expressInitialOutcome(launch) {
133
+ async function recordPluginDiagnostic(paths, occurrence, error) {
95
134
  try {
96
- const { Square } = await import("@astrosheep/square");
97
- const identity = launch.seed?.id ?? (await readHeart(launch.paths)).soul?.id;
98
- if (identity === undefined)
99
- return;
100
- const square = await Square.at({ path: keiyakuSquarePath(worldRootForAkumaPaths(launch.paths)) });
101
- try {
102
- const joined = await square.implicitJoin(identity);
103
- if (joined.state === "done" || joined.participant === undefined)
104
- return;
105
- await joined.participant.express(completionMessage(identity, launch.completion));
106
- }
107
- finally {
108
- await square.close();
109
- }
135
+ await appendFile(paths.log, `plugin ${occurrence} failed: ${boundedDiagnostic(error)}\n`);
110
136
  }
111
- catch (error) {
112
- try {
113
- await appendFile(launch.paths.log, `square outcome express failed: ${boundedDiagnostic(error)}\n`);
114
- }
115
- catch {
116
- /* express loss never changes Heart truth */
117
- }
137
+ catch {
138
+ /* plugin diagnostic loss never changes Heart truth */
118
139
  }
119
140
  }
120
- function defaultRuntime() {
121
- return {
122
- now: () => new Date().toISOString(),
123
- spawnChild: spawnAkumaBody,
124
- commands: {},
125
- spawnBody: spawnAkumaBody,
126
- };
127
- }
141
+ const DEFAULT_RUNTIME = {
142
+ now: () => new Date().toISOString(),
143
+ spawnChild: spawnAkumaBody,
144
+ externalCommands: {},
145
+ spawnBody: spawnAkumaBody,
146
+ };
128
147
  export async function bodyProcessInput(launch, bodyModuleUrl = import.meta.url) {
129
148
  const encoded = Buffer.from(JSON.stringify(launch), "utf8").toString("base64url");
130
149
  const actorId = launch.seed?.id ?? (await readHeart(launch.paths)).soul?.id;
@@ -172,11 +191,40 @@ async function recoverBodyRequests(input) {
172
191
  }
173
192
  return false;
174
193
  }
194
+ function turnOutcomeEmitter(launch, soul) {
195
+ let plugins;
196
+ const reportDiagnostic = (message) => {
197
+ void recordPluginDiagnostic(launch.paths, "turn outcome", message);
198
+ };
199
+ return (turnSequence, outcome) => {
200
+ void (async () => {
201
+ try {
202
+ plugins ??= pluginRuntime({
203
+ world: await World.at(worldRootForAkumaPaths(launch.paths)),
204
+ reportDiagnostic,
205
+ });
206
+ (await plugins).emit({
207
+ kind: "akuma.turn-outcome",
208
+ akumaId: soul.id,
209
+ turnSequence,
210
+ outcome: outcome.outcome === "answered"
211
+ ? { kind: "answered", text: outcome.answer }
212
+ : { kind: "failed", reason: outcome.diagnostic },
213
+ ...(launch.completion?.contractId === undefined ? {} : { contractId: launch.completion.contractId }),
214
+ }, reportDiagnostic);
215
+ }
216
+ catch (error) {
217
+ await recordPluginDiagnostic(launch.paths, "turn outcome", error);
218
+ }
219
+ })();
220
+ };
221
+ }
175
222
  async function runBodyTurns(input) {
176
223
  const { launch, soul, adapter, bodySequence, supervisor, runtime } = input;
177
224
  let initial = launch.initialBody;
225
+ const emitTurnOutcome = turnOutcomeEmitter(launch, soul);
178
226
  for (;;) {
179
- const launchTells = supervisor.current().pending;
227
+ const launchTells = drainPendingTells(supervisor.current().pending);
180
228
  if (supervisor.signal.aborted) {
181
229
  await putDownIfControlled(launch.paths, supervisor, bodySequence, runtime.now);
182
230
  return;
@@ -201,28 +249,32 @@ async function runBodyTurns(input) {
201
249
  body: initial ?? "",
202
250
  ...(initial === undefined ? {} : { call: initial }),
203
251
  launchTells,
204
- ...(runtime.upstream === undefined ? {} : { upstream: runtime.upstream }),
205
- commands: runtime.commands,
252
+ ...(launchTells.find((tell) => tell.schemaJson !== undefined)?.schemaJson === undefined
253
+ ? {}
254
+ : { schemaJson: launchTells.find((tell) => tell.schemaJson !== undefined).schemaJson }),
255
+ world: runtime.world,
256
+ externalCommands: runtime.externalCommands,
206
257
  now: runtime.now,
207
258
  });
208
- if (result.kind === "hung")
259
+ if (result.kind === "hung") {
260
+ await failOpenBoundTurnsIfPresent(launch.paths, bodySequence, "Body hung with open bound Turns", runtime.now());
209
261
  return;
262
+ }
210
263
  if (result.kind === "stopped") {
211
264
  await putDownIfControlled(launch.paths, supervisor, bodySequence, runtime.now);
212
265
  return;
213
266
  }
214
- if (result.kind === "handoff") {
215
- await breakBody(launch.paths, { sequence: bodySequence, end: "put-down", at: runtime.now() });
216
- return;
217
- }
267
+ if (result.kind === "handoff")
268
+ return "handoff";
218
269
  if (result.kind === "resume-unsupported") {
270
+ await failOpenBoundTurnsIfPresent(launch.paths, bodySequence, "resume-unsupported with open bound Turns", runtime.now());
219
271
  await breakBody(launch.paths, { sequence: bodySequence, end: "broke-off", at: runtime.now() });
220
272
  return;
221
273
  }
222
274
  const outcome = await persistTurn(launch.paths, result.turnSequence, result, runtime.now());
223
- if (initial !== undefined)
224
- await expressInitialOutcome(launch);
275
+ emitTurnOutcome(result.turnSequence, outcome);
225
276
  if (outcome.outcome === "failed") {
277
+ await failOpenBoundTurnsIfPresent(launch.paths, bodySequence, outcome.diagnostic, runtime.now());
226
278
  await breakBody(launch.paths, { sequence: bodySequence, end: "broke-off", at: runtime.now() });
227
279
  return;
228
280
  }
@@ -230,24 +282,115 @@ async function runBodyTurns(input) {
230
282
  initial = undefined;
231
283
  }
232
284
  }
233
- export async function handoffPendingTells(paths, spawn = spawnAkumaBody) {
285
+ async function projectUndeliveredPendingTellEvidence(paths, error) {
234
286
  try {
235
- if ((await readHeart(paths)).pending.length === 0)
236
- return;
237
- (await spawn({ paths, refuseIfHeld: true })).release();
287
+ await appendFile(paths.log, `pending Tell disposition undelivered: ${boundedDiagnostic(error)}\n`);
238
288
  }
239
289
  catch {
240
- /* a later Heart interaction retries the unchanged pending Tell */
290
+ /* log evidence loss never changes Heart truth */
291
+ }
292
+ }
293
+ async function settleUndeliveredDisposition(paths, disposition, error) {
294
+ await projectUndeliveredPendingTellEvidence(paths, error);
295
+ if (!(await heartExists(paths)))
296
+ return;
297
+ const at = new Date().toISOString();
298
+ await recordUndeliveredPendingTells(paths, at, disposition.tellIds);
299
+ await resolvePendingTellDisposition(paths, disposition.bodySequence, at);
300
+ }
301
+ async function consumeProvenDisposition(paths, disposition) {
302
+ const proof = await provePendingTellDispositionCustody(paths, disposition);
303
+ if (proof.kind !== "proven")
304
+ return false;
305
+ await resolvePendingTellDisposition(paths, disposition.bodySequence, new Date().toISOString());
306
+ return true;
307
+ }
308
+ /**
309
+ * Wait for Heart custody proof of the frozen Tell-id snapshot, or for the
310
+ * spawned child to exit without that proof. Sequence growth, spawn resolution,
311
+ * and an unqualified held leash never consume the disposition.
312
+ */
313
+ async function awaitDispositionCustody(paths, disposition, child, schedule) {
314
+ for (;;) {
315
+ if ((await provePendingTellDispositionCustody(paths, disposition)).kind === "proven") {
316
+ child.release();
317
+ return { kind: "proven" };
318
+ }
319
+ const timerController = new AbortController();
320
+ const timer = schedule(WAKE_REREAD_MS, timerController.signal).then(() => ({ kind: "timer" }));
321
+ let winner;
322
+ try {
323
+ winner = await Promise.race([child.exited.then((exit) => ({ kind: "exited", exit })), timer]);
324
+ }
325
+ catch (error) {
326
+ timerController.abort();
327
+ await timer.catch(() => undefined);
328
+ throw error;
329
+ }
330
+ if (winner.kind === "timer")
331
+ continue;
332
+ timerController.abort();
333
+ await timer.catch(() => undefined);
334
+ if ((await provePendingTellDispositionCustody(paths, disposition)).kind === "proven") {
335
+ child.release();
336
+ return { kind: "proven" };
337
+ }
338
+ return winner.exit.code === LEASH_HELD_EXIT ? { kind: "held" } : failedChild(winner.exit);
339
+ }
340
+ }
341
+ async function resolveDecidedPendingTellDisposition(paths, disposition, spawn) {
342
+ try {
343
+ if (await consumeProvenDisposition(paths, disposition))
344
+ return;
345
+ const wake = await awaitDispositionCustody(paths, disposition, await spawn({ paths, refuseIfHeld: true }), abortableDelay);
346
+ if (wake.kind === "proven") {
347
+ await resolvePendingTellDisposition(paths, disposition.bodySequence, new Date().toISOString());
348
+ return;
349
+ }
350
+ // Predecessor still holds the leash: leave the Heart disposition open.
351
+ if (wake.kind === "held")
352
+ return;
353
+ await settleUndeliveredDisposition(paths, disposition, wake.kind === "failed" ? wake.diagnostic : "successor custody unproven");
354
+ }
355
+ catch (error) {
356
+ await settleUndeliveredDisposition(paths, disposition, error);
357
+ }
358
+ }
359
+ export async function handoffPendingTells(paths, spawn = spawnAkumaBody) {
360
+ const open = await readOpenPendingTellDisposition(paths);
361
+ if (open !== null) {
362
+ await resolveDecidedPendingTellDisposition(paths, open, spawn);
363
+ return;
241
364
  }
365
+ const before = await readHeart(paths);
366
+ if (before.pending.length === 0)
367
+ return;
368
+ if ((await probeLeash(paths)) === "held")
369
+ return;
370
+ const bodySequence = before.latestBody?.sequence;
371
+ if (bodySequence === undefined || before.latestBody?.end === undefined)
372
+ return;
373
+ const decided = await decidePendingTellDisposition(paths, {
374
+ bodySequence,
375
+ at: new Date().toISOString(),
376
+ handoff: before.latestBody.end === "put-down",
377
+ });
378
+ if (decided === null)
379
+ return;
380
+ await resolveDecidedPendingTellDisposition(paths, decided, spawn);
242
381
  }
243
- export async function driveAkumaBody(launch, adapter, runtime = defaultRuntime()) {
382
+ export async function driveAkumaBody(launch, adapter, runtimeInput = {}) {
383
+ const world = runtimeInput.world ?? (await World.at(worldRootForAkumaPaths(launch.paths)));
384
+ const runtime = { ...DEFAULT_RUNTIME, ...runtimeInput, world };
244
385
  const acquired = await takeLeash(launch.paths, launch.refuseIfHeld);
245
386
  if (acquired === "held")
246
387
  return "held";
247
388
  if (acquired === "absent")
248
389
  return;
249
390
  const leash = acquired;
250
- let bodyStarted = false;
391
+ let pendingTellDisposition = undefined;
392
+ let bodySequence;
393
+ let decidedDisposition = null;
251
394
  try {
252
395
  const soul = await bornSoul(launch, leash, runtime.now());
253
396
  if (soul === null)
@@ -261,12 +404,12 @@ export async function driveAkumaBody(launch, adapter, runtime = defaultRuntime()
261
404
  if (!(await prepareBodyStart(launch.paths, leash)))
262
405
  return;
263
406
  const body = await leash.recordBody(launch.paths, { leashTakenAt: runtime.now() });
264
- bodyStarted = true;
407
+ bodySequence = body.sequence;
265
408
  const supervisor = await BodySupervisor.open(launch.paths, body.sequence, leash);
266
409
  const execution = { launch, soul, adapter: selected, bodySequence: body.sequence, supervisor, runtime };
267
410
  try {
268
411
  if (await recoverBodyRequests(execution))
269
- await runBodyTurns(execution);
412
+ pendingTellDisposition = await runBodyTurns(execution);
270
413
  }
271
414
  finally {
272
415
  await supervisor.close();
@@ -287,16 +430,33 @@ export async function driveAkumaBody(launch, adapter, runtime = defaultRuntime()
287
430
  throw error;
288
431
  }
289
432
  finally {
433
+ if (bodySequence !== undefined && (await heartExists(launch.paths))) {
434
+ const body = (await readHeart(launch.paths)).latestBody;
435
+ const inert = body?.sequence === bodySequence && (body.end !== undefined || body.hung !== undefined);
436
+ if (inert) {
437
+ await failOpenBoundTurns(launch.paths, {
438
+ bodySequence,
439
+ diagnostic: body.hung?.diagnostic ?? `Body ${body.end ?? "ended"} with open bound Turns`,
440
+ completedAt: runtime.now(),
441
+ });
442
+ }
443
+ decidedDisposition = await decidePendingTellDisposition(launch.paths, {
444
+ bodySequence,
445
+ at: runtime.now(),
446
+ handoff: pendingTellDisposition === "handoff",
447
+ });
448
+ }
290
449
  leash.release();
291
- if (bodyStarted)
292
- await handoffPendingTells(launch.paths, runtime.spawnBody ?? spawnAkumaBody);
450
+ if (decidedDisposition !== null) {
451
+ await resolveDecidedPendingTellDisposition(launch.paths, decidedDisposition, runtime.spawnBody ?? spawnAkumaBody);
452
+ }
293
453
  }
294
454
  }
295
455
  export async function spawnAkumaBody(launch) {
296
456
  return await spawnDetachedProcess(await bodyProcessInput(launch));
297
457
  }
298
- export async function runAkumaBody(launch, upstream, commands) {
299
- return await driveAkumaBody(launch, undefined, { ...defaultRuntime(), upstream, commands });
458
+ export async function runAkumaBody(launch, world, externalCommands) {
459
+ return await driveAkumaBody(launch, undefined, { world, externalCommands });
300
460
  }
301
461
  const DIRECT_TELL_WAKE = {
302
462
  spawn: async (paths) => await spawnAkumaBody({ paths, refuseIfHeld: true }),
@@ -0,0 +1,4 @@
1
+ export declare function callReadonly(value: unknown, diagnostic?: string): Readonly<{
2
+ readonly?: true;
3
+ }>;
4
+ export declare function canonicalBirthCwd(input: string, diagnostic?: string): Promise<string>;
@@ -0,0 +1,21 @@
1
+ import { realpath, stat } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ export function callReadonly(value, diagnostic = "Akuma call readonly must be true") {
4
+ if (value === undefined)
5
+ return {};
6
+ if (value !== true)
7
+ throw new TypeError(diagnostic);
8
+ return { readonly: true };
9
+ }
10
+ export async function canonicalBirthCwd(input, diagnostic = `cwd is not an existing directory: ${input}`) {
11
+ const selected = resolve(input);
12
+ try {
13
+ const canonical = await realpath(selected);
14
+ if (!(await stat(canonical)).isDirectory())
15
+ throw new Error("not a directory");
16
+ return canonical;
17
+ }
18
+ catch {
19
+ throw new Error(diagnostic);
20
+ }
21
+ }
@@ -1,6 +1,6 @@
1
1
  import { type Soul } from "./heart/index.js";
2
2
  import { type AkuId, type AkumaPaths } from "./identity.js";
3
- import { type ErasedRequestCommand, type RequestCommand } from "./request-wire.js";
3
+ import { type ChildRequestCommand, type ErasedRequestCommand, type RequestProtocol } from "./request-wire.js";
4
4
  import type { OwnedProcess } from "../runtime/proc/run.js";
5
5
  import { z } from "zod";
6
6
  import { type WorldRoot } from "../world.js";
@@ -59,19 +59,16 @@ export type AkumaCallRequestChildLaunch = Readonly<{
59
59
  seed: Omit<Soul, "createdAt">;
60
60
  initialBody: string;
61
61
  }>;
62
- type CallExecutionContext = Readonly<{
63
- id: string;
64
- requester: string;
65
- signal: AbortSignal;
62
+ type AkumaCallRequestCapabilities = Readonly<{
66
63
  world: WorldRoot;
67
64
  paths: AkumaPaths;
68
65
  parent: Soul;
69
66
  spawn(launch: AkumaCallRequestChildLaunch): Promise<OwnedProcess | void>;
70
- admissionOpen(): boolean;
71
67
  }>;
72
68
  export declare function decodeAkumaCallRequest(value: unknown): AkumaCallRequest | null;
73
- export declare function akumaCallRequestCommand(): RequestCommand<AkumaCallRequest, AkuId, never, AkuId, CallExecutionContext>;
74
- export declare function akumaCallRequestCommands(): Readonly<Record<"akuma.call", ErasedRequestCommand>>;
69
+ export declare function akumaCallRequestProtocol(): RequestProtocol<AkumaCallRequest, AkuId, AkuId>;
70
+ export declare function akumaCallRequestCommand(capabilities: AkumaCallRequestCapabilities): ChildRequestCommand<AkumaCallRequest, AkuId, AkuId>;
71
+ export declare function akumaCallRequestCommands(capabilities: AkumaCallRequestCapabilities): Readonly<Record<"akuma.call", ErasedRequestCommand>>;
75
72
  export declare function requestForwardedAkumaCall(input: Omit<AkumaCallRequest, "action"> & Readonly<{
76
73
  directory: string;
77
74
  id: string;
@@ -6,7 +6,7 @@ import { publishAkuma } from "./publication.js";
6
6
  import { decodeProviderOptions, decodeReadonlyRestraint } from "./provider-recipe.js";
7
7
  import { decodeProviderExecution } from "./providers/index.js";
8
8
  import { requestBodyCommand } from "./request-rendezvous.js";
9
- import { eraseRequestCommand } from "./request-wire.js";
9
+ import { eraseRequestCommand, } from "./request-wire.js";
10
10
  import { z } from "zod";
11
11
  import { World } from "../world.js";
12
12
  function schemaDecode(decode, message) {
@@ -71,49 +71,6 @@ const akumaCallPayloadSchema = z
71
71
  recipe: akumaCallRecipeSchema,
72
72
  })
73
73
  .strict();
74
- function carriesLaunchWorld(value) {
75
- const upstream = object(value);
76
- return upstream !== null && typeof upstream.launchWorld === "function";
77
- }
78
- function decodeCallExecutionContext(value) {
79
- const context = object(value);
80
- const paths = object(context?.paths);
81
- const parent = object(context?.parent);
82
- const parentId = akuIdSchema.safeParse(parent?.id);
83
- if (context === null ||
84
- typeof context.id !== "string" ||
85
- context.id.trim() === "" ||
86
- typeof context.requester !== "string" ||
87
- typeof context.signal !== "object" ||
88
- context.signal === null ||
89
- typeof context.signal.throwIfAborted !== "function" ||
90
- paths === null ||
91
- !["directory", "heart", "leash", "log", "requests"].every((key) => typeof paths[key] === "string") ||
92
- parent === null ||
93
- !parentId.success ||
94
- typeof parent.cwd !== "string" ||
95
- !Array.isArray(parent.allowed) ||
96
- typeof context.spawn !== "function" ||
97
- typeof context.admissionOpen !== "function")
98
- throw new Error("invalid Akuma call execution context");
99
- if (!carriesLaunchWorld(context.upstream))
100
- throw new Error("Akuma call requires a minted launch World");
101
- return {
102
- id: context.id,
103
- requester: context.requester,
104
- signal: context.signal,
105
- world: context.upstream.launchWorld(),
106
- paths: context.paths,
107
- parent: context.parent,
108
- spawn: context.spawn,
109
- admissionOpen: context.admissionOpen,
110
- };
111
- }
112
- function object(value) {
113
- return value !== null && typeof value === "object" && !Array.isArray(value)
114
- ? value
115
- : null;
116
- }
117
74
  export function decodeAkumaCallRequest(value) {
118
75
  const parsed = akumaCallPayloadSchema.safeParse(value);
119
76
  return parsed.success ? { ...parsed.data, action: "akuma.call" } : null;
@@ -122,16 +79,16 @@ function callPayload(request) {
122
79
  const { action: _action, ...payload } = request;
123
80
  return payload;
124
81
  }
125
- async function executeAkumaCall(request, context) {
126
- const world = context.world;
82
+ async function executeAkumaCall(request, facts, capabilities) {
83
+ const { world, paths, parent, spawn } = capabilities;
127
84
  const requestWorld = await World.prove(request.world);
128
85
  if (requestWorld !== world) {
129
- await refuseRequest(context.paths, context.id, `request world ${requestWorld} does not match ${world}`);
86
+ await refuseRequest(paths, facts.id, `request world ${requestWorld} does not match ${world}`);
130
87
  throw new Error(`request world ${requestWorld} does not match ${world}`);
131
88
  }
132
89
  const recipe = {
133
90
  ...(request.recipe.description === undefined ? {} : { description: request.recipe.description }),
134
- allowed: clipAllowedActions(request.recipe.allowed, context.parent.allowed),
91
+ allowed: clipAllowedActions(request.recipe.allowed, parent.allowed),
135
92
  provider: request.recipe.provider,
136
93
  options: request.recipe.options,
137
94
  ...(request.recipe.readonly === undefined ? {} : { readonly: request.recipe.readonly }),
@@ -139,19 +96,19 @@ async function executeAkumaCall(request, context) {
139
96
  const published = await publishAkuma({
140
97
  worldPath: world,
141
98
  archetype: request.archetype,
142
- signal: context.signal,
99
+ signal: facts.signal,
143
100
  launch: async (allocated) => {
144
- if (!context.admissionOpen())
101
+ if (!facts.admissionOpen())
145
102
  throw new Error("body closed request admission");
146
- await reserveRequest(context.paths, context.id, allocated.id);
147
- return await context.spawn({
103
+ await reserveRequest(paths, facts.id, allocated.id);
104
+ return await spawn({
148
105
  paths: allocated.paths,
149
106
  seed: {
150
107
  id: allocated.id,
151
108
  archetype: allocated.archetype,
152
109
  ...recipe,
153
- cwd: request.cwd ?? context.parent.cwd,
154
- origin: { kind: "request", parent: context.parent.id, requestId: context.id },
110
+ cwd: request.cwd ?? parent.cwd,
111
+ origin: { kind: "request", parent: parent.id, requestId: facts.id },
155
112
  },
156
113
  initialBody: request.body,
157
114
  });
@@ -159,7 +116,7 @@ async function executeAkumaCall(request, context) {
159
116
  });
160
117
  return { result: published.id, child: published.id };
161
118
  }
162
- export function akumaCallRequestCommand() {
119
+ export function akumaCallRequestProtocol() {
163
120
  return {
164
121
  action: "akuma.call",
165
122
  encodeRequest: callPayload,
@@ -171,19 +128,6 @@ export function akumaCallRequestCommand() {
171
128
  throw new Error("Akuma call returned an invalid child");
172
129
  return child.data;
173
130
  },
174
- encodeService: (service) => service,
175
- decodeService: (_service) => {
176
- throw new Error("Akuma call has no service evidence");
177
- },
178
- projectService: (_service) => {
179
- throw new Error("Akuma call has no service evidence");
180
- },
181
- projectChild: (child) => {
182
- const id = akuIdSchema.safeParse(child);
183
- if (!id.success)
184
- throw new Error("Akuma call stored an invalid child reference");
185
- return id.data;
186
- },
187
131
  decodeReference: (reference) => {
188
132
  const child = akuIdSchema.safeParse(reference);
189
133
  if (!child.success)
@@ -191,19 +135,30 @@ export function akumaCallRequestCommand() {
191
135
  return child.data;
192
136
  },
193
137
  isPermitted: (allowed) => allowed.includes("akuma.call"),
194
- decodeExecutionContext: decodeCallExecutionContext,
195
- execute: executeAkumaCall,
196
138
  };
197
139
  }
198
- export function akumaCallRequestCommands() {
199
- return { "akuma.call": eraseRequestCommand(akumaCallRequestCommand()) };
140
+ export function akumaCallRequestCommand(capabilities) {
141
+ return {
142
+ completion: "child",
143
+ protocol: akumaCallRequestProtocol(),
144
+ projectChild: (child) => {
145
+ const id = akuIdSchema.safeParse(child);
146
+ if (!id.success)
147
+ throw new Error("Akuma call stored an invalid child reference");
148
+ return id.data;
149
+ },
150
+ execute: async (request, facts) => await executeAkumaCall(request, facts, capabilities),
151
+ };
152
+ }
153
+ export function akumaCallRequestCommands(capabilities) {
154
+ return { "akuma.call": eraseRequestCommand(akumaCallRequestCommand(capabilities)) };
200
155
  }
201
156
  export async function requestForwardedAkumaCall(input) {
202
157
  const { directory, id, signal, ...request } = input;
203
158
  const response = await requestBodyCommand({
204
159
  directory,
205
160
  id,
206
- command: akumaCallRequestCommand(),
161
+ command: akumaCallRequestProtocol(),
207
162
  value: { ...request, action: "akuma.call" },
208
163
  ...(signal === undefined ? {} : { signal }),
209
164
  });
@@ -0,0 +1,26 @@
1
+ import { type AkumaStatus } from "./akuma.js";
2
+ import type { WorldRoot } from "../world.js";
3
+ import { type AkumaKillResult, type AkumaTellResult, type AkumaWaitResult } from "./fleet-observation.js";
4
+ export type WaitExecutionInput = Readonly<{
5
+ path: WorldRoot;
6
+ ids: readonly AkumaStatus["id"][];
7
+ completion: "any" | "all";
8
+ timeoutMs?: number;
9
+ signal?: AbortSignal;
10
+ }>;
11
+ export declare function executeWaitAkuma(input: WaitExecutionInput): Promise<AkumaWaitResult>;
12
+ export type TellExecutionInput = Readonly<{
13
+ path: WorldRoot;
14
+ id: AkumaStatus["id"];
15
+ body: string;
16
+ tellId?: string;
17
+ recordedAt?: string;
18
+ signal?: AbortSignal;
19
+ }>;
20
+ export declare function executeTellAkuma(input: TellExecutionInput): Promise<AkumaTellResult>;
21
+ export type KillExecutionInput = Readonly<{
22
+ path: WorldRoot;
23
+ ids: readonly AkumaStatus["id"][];
24
+ signal?: AbortSignal;
25
+ }>;
26
+ export declare function executeKillAkuma(input: KillExecutionInput): Promise<AkumaKillResult>;