@astrosheep/keiyaku 4.5.6 → 4.5.8

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 +10 -0
  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/opencode.js +3 -2
  5. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +10 -8
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +22 -1
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-babysit/SKILL.md +53 -0
  9. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +28 -49
  10. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +21 -15
  11. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +148 -89
  12. package/build/src/akuma/abort.d.ts +2 -1
  13. package/build/src/akuma/abort.js +6 -23
  14. package/build/src/akuma/akuma-handle.js +4 -2
  15. package/build/src/akuma/akuma-observe.d.ts +2 -0
  16. package/build/src/akuma/akuma-observe.js +1 -0
  17. package/build/src/akuma/akuma-product.js +9 -7
  18. package/build/src/akuma/akuma.d.ts +372 -9
  19. package/build/src/akuma/akuma.js +31 -1
  20. package/build/src/akuma/allowed.d.ts +1 -1
  21. package/build/src/akuma/allowed.js +1 -0
  22. package/build/src/akuma/body.d.ts +7 -5
  23. package/build/src/akuma/body.js +25 -36
  24. package/build/src/akuma/call-request.d.ts +80 -0
  25. package/build/src/akuma/call-request.js +211 -0
  26. package/build/src/akuma/heart/facts.d.ts +6 -78
  27. package/build/src/akuma/heart/index.d.ts +3 -3
  28. package/build/src/akuma/heart/index.js +2 -2
  29. package/build/src/akuma/heart/request-authority.d.ts +6 -4
  30. package/build/src/akuma/heart/request-authority.js +31 -35
  31. package/build/src/akuma/heart/request-rows.js +12 -23
  32. package/build/src/akuma/heart/schema.d.ts +1 -1
  33. package/build/src/akuma/heart/schema.js +5 -13
  34. package/build/src/akuma/heart/storage.d.ts +0 -1
  35. package/build/src/akuma/heart/storage.js +0 -54
  36. package/build/src/akuma/index.d.ts +1 -1
  37. package/build/src/akuma/index.js +1 -1
  38. package/build/src/akuma/projection-read.d.ts +1 -0
  39. package/build/src/akuma/projection-read.js +8 -1
  40. package/build/src/akuma/projection.d.ts +856 -137
  41. package/build/src/akuma/projection.js +190 -0
  42. package/build/src/akuma/provider.d.ts +30 -3
  43. package/build/src/akuma/provider.js +104 -0
  44. package/build/src/akuma/providers/acp/core.d.ts +2 -2
  45. package/build/src/akuma/providers/acp/core.js +13 -6
  46. package/build/src/akuma/providers/acp/index.js +5 -4
  47. package/build/src/akuma/providers/claude/index.d.ts +3 -3
  48. package/build/src/akuma/providers/claude/index.js +16 -5
  49. package/build/src/akuma/providers/codex-app-server/events.js +1 -1
  50. package/build/src/akuma/providers/codex-app-server/index.js +18 -6
  51. package/build/src/akuma/providers/grok-build/index.js +5 -4
  52. package/build/src/akuma/providers/opencode-sdk/index.js +66 -25
  53. package/build/src/akuma/providers/opencode-sdk/session.d.ts +3 -2
  54. package/build/src/akuma/providers/opencode-sdk/session.js +28 -11
  55. package/build/src/akuma/providers/pi/index.js +42 -14
  56. package/build/src/akuma/publication.js +75 -13
  57. package/build/src/akuma/request-lifecycle.d.ts +3 -9
  58. package/build/src/akuma/request-lifecycle.js +12 -25
  59. package/build/src/akuma/request-rendezvous.d.ts +26 -0
  60. package/build/src/akuma/request-rendezvous.js +100 -0
  61. package/build/src/akuma/request-serve.d.ts +5 -65
  62. package/build/src/akuma/request-serve.js +92 -204
  63. package/build/src/akuma/request-wire.d.ts +109 -115
  64. package/build/src/akuma/request-wire.js +63 -318
  65. package/build/src/akuma/requests.d.ts +19 -47
  66. package/build/src/akuma/requests.js +51 -107
  67. package/build/src/akuma/turn-drive.d.ts +5 -3
  68. package/build/src/akuma/turn-drive.js +32 -10
  69. package/build/src/akuma-body.d.ts +7 -2
  70. package/build/src/akuma-body.js +68 -46
  71. package/build/src/cli/accepted.d.ts +1 -1
  72. package/build/src/cli/accepted.js +2 -2
  73. package/build/src/cli/commands/akuma-invoke.d.ts +2 -0
  74. package/build/src/cli/commands/akuma-invoke.js +17 -9
  75. package/build/src/cli/commands/akuma.js +7 -7
  76. package/build/src/cli/commands/contract-invoke.d.ts +3 -2
  77. package/build/src/cli/commands/contract-invoke.js +26 -19
  78. package/build/src/cli/commands/contract.d.ts +13 -13
  79. package/build/src/cli/commands/contract.js +32 -17
  80. package/build/src/cli/commands/install.d.ts +1 -1
  81. package/build/src/cli/commands/install.js +1 -1
  82. package/build/src/cli/commands/task-invoke.d.ts +7 -2
  83. package/build/src/cli/commands/task-invoke.js +67 -158
  84. package/build/src/cli/commands/task.d.ts +1 -1
  85. package/build/src/cli/commands/task.js +60 -22
  86. package/build/src/cli/coordinates.d.ts +2 -0
  87. package/build/src/cli/coordinates.js +18 -2
  88. package/build/src/cli/invoke.js +39 -25
  89. package/build/src/cli/main.js +2 -1
  90. package/build/src/cli/parse.d.ts +2 -2
  91. package/build/src/cli/parse.js +37 -22
  92. package/build/src/cli/render/akuma-activity.d.ts +4 -1
  93. package/build/src/cli/render/akuma-activity.js +45 -30
  94. package/build/src/cli/render/akuma-tool.d.ts +5 -1
  95. package/build/src/cli/render/akuma.js +5 -9
  96. package/build/src/cli/render/audit.js +1 -1
  97. package/build/src/cli/render/catalog.js +80 -21
  98. package/build/src/cli/render/contract-observation.d.ts +2 -0
  99. package/build/src/cli/render/contract-observation.js +11 -0
  100. package/build/src/cli/render/contract.js +25 -65
  101. package/build/src/cli/render/kanshi-akuma.d.ts +8 -0
  102. package/build/src/cli/render/kanshi-akuma.js +111 -0
  103. package/build/src/cli/render/kanshi.js +108 -213
  104. package/build/src/cli/render/receipt.d.ts +1 -2
  105. package/build/src/cli/render/receipt.js +76 -26
  106. package/build/src/cli/render/refusal.js +2 -10
  107. package/build/src/cli/render/region.js +5 -2
  108. package/build/src/cli/render/settings.d.ts +1 -1
  109. package/build/src/cli/render/settings.js +27 -8
  110. package/build/src/cli/render/task.js +28 -10
  111. package/build/src/cli/render/terminal.d.ts +28 -0
  112. package/build/src/cli/render/terminal.js +65 -0
  113. package/build/src/cli/result.d.ts +3 -3
  114. package/build/src/cli/runtime.js +3 -1
  115. package/build/src/cli/selectors.d.ts +2 -1
  116. package/build/src/cli/selectors.js +4 -3
  117. package/build/src/cli/square-edge.js +120 -36
  118. package/build/src/cli/usage.js +2 -1
  119. package/build/src/contract-guidance.d.ts +5 -0
  120. package/build/src/contract-guidance.js +121 -0
  121. package/build/src/contract-worktree.d.ts +0 -1
  122. package/build/src/contract-worktree.js +32 -83
  123. package/build/src/coordination/sqlite-transaction-lock.d.ts +1 -0
  124. package/build/src/coordination/sqlite-transaction-lock.js +5 -0
  125. package/build/src/core/facts/codec.d.ts +2 -0
  126. package/build/src/core/facts/codec.js +4 -0
  127. package/build/src/core/verbs/placement.d.ts +6 -1
  128. package/build/src/core/verbs/placement.js +5 -3
  129. package/build/src/dispatch/index.d.ts +0 -2
  130. package/build/src/dispatch/index.js +54 -55
  131. package/build/src/git/admission.js +2 -2
  132. package/build/src/git/hooks.d.ts +9 -15
  133. package/build/src/git/hooks.js +23 -317
  134. package/build/src/git/nuke.d.ts +2 -1
  135. package/build/src/git/nuke.js +36 -18
  136. package/build/src/git/private-state-seat.d.ts +12 -0
  137. package/build/src/git/private-state-seat.js +39 -0
  138. package/build/src/git/process.d.ts +6 -0
  139. package/build/src/git/process.js +19 -7
  140. package/build/src/git/read-observation.js +29 -20
  141. package/build/src/git/reconcile.d.ts +11 -0
  142. package/build/src/git/reconcile.js +64 -11
  143. package/build/src/git/repository.d.ts +8 -2
  144. package/build/src/git/repository.js +42 -21
  145. package/build/src/git/target-placement.d.ts +1 -0
  146. package/build/src/git/target-placement.js +5 -6
  147. package/build/src/git/tender.d.ts +1 -0
  148. package/build/src/git/terminal-reconcile.d.ts +1 -1
  149. package/build/src/git/terminal-reconcile.js +22 -20
  150. package/build/src/git/workspace.d.ts +14 -0
  151. package/build/src/git/workspace.js +44 -0
  152. package/build/src/kanshi/index.d.ts +1 -1
  153. package/build/src/kanshi/read.js +16 -16
  154. package/build/src/kanshi/report.d.ts +2 -2
  155. package/build/src/library/address.d.ts +9 -3
  156. package/build/src/library/address.js +3 -2
  157. package/build/src/library/akuma-creation.d.ts +4 -3
  158. package/build/src/library/akuma-creation.js +19 -13
  159. package/build/src/library/audit.d.ts +1 -1
  160. package/build/src/library/audit.js +5 -3
  161. package/build/src/library/catalog.d.ts +3 -1
  162. package/build/src/library/catalog.js +20 -6
  163. package/build/src/library/composition.d.ts +371 -0
  164. package/build/src/library/composition.js +28 -0
  165. package/build/src/library/configuration.js +8 -2
  166. package/build/src/library/continuation.js +66 -30
  167. package/build/src/library/contract-bind.js +10 -2
  168. package/build/src/library/contract-forwarding-reconciliation-result.d.ts +96 -0
  169. package/build/src/library/contract-forwarding-reconciliation-result.js +83 -0
  170. package/build/src/library/contract-forwarding-result.d.ts +6723 -0
  171. package/build/src/library/contract-forwarding-result.js +521 -0
  172. package/build/src/library/contract-forwarding.d.ts +72 -0
  173. package/build/src/library/contract-forwarding.js +125 -0
  174. package/build/src/library/contract-handle.d.ts +36 -0
  175. package/build/src/library/contract-handle.js +313 -0
  176. package/build/src/library/contract-operations.d.ts +1044 -67
  177. package/build/src/library/contract-operations.js +190 -118
  178. package/build/src/library/contract.d.ts +11 -32
  179. package/build/src/library/contract.js +16 -289
  180. package/build/src/library/fleet-result.d.ts +1454 -0
  181. package/build/src/library/fleet-result.js +96 -0
  182. package/build/src/library/fleet.d.ts +112 -54
  183. package/build/src/library/fleet.js +211 -71
  184. package/build/src/library/input.d.ts +2 -0
  185. package/build/src/library/input.js +4 -0
  186. package/build/src/library/keiyaku.d.ts +371 -20
  187. package/build/src/library/keiyaku.js +8 -21
  188. package/build/src/library/mutation.d.ts +3 -3
  189. package/build/src/library/mutation.js +5 -9
  190. package/build/src/library/nuke.d.ts +1 -1
  191. package/build/src/library/nuke.js +4 -3
  192. package/build/src/library/reconcile.d.ts +4 -0
  193. package/build/src/library/reconcile.js +2 -0
  194. package/build/src/library/refusal.d.ts +0 -9
  195. package/build/src/library/refusal.js +0 -7
  196. package/build/src/protocol/amend.js +56 -50
  197. package/build/src/protocol/attempt.d.ts +2 -0
  198. package/build/src/protocol/attempt.js +17 -5
  199. package/build/src/protocol/completion.js +7 -0
  200. package/build/src/protocol/deliver.js +6 -2
  201. package/build/src/protocol/operations.d.ts +4 -1
  202. package/build/src/protocol/operations.js +5 -2
  203. package/build/src/protocol/placement.d.ts +1 -0
  204. package/build/src/protocol/placement.js +52 -38
  205. package/build/src/protocol/read/observation.d.ts +1 -4
  206. package/build/src/protocol/read/observation.js +2 -15
  207. package/build/src/protocol/reintegrate.js +62 -58
  208. package/build/src/protocol/review.js +5 -0
  209. package/build/src/protocol/run.js +17 -13
  210. package/build/src/runtime/proc/run.d.ts +8 -0
  211. package/build/src/runtime/proc/run.js +27 -0
  212. package/build/src/runtime/proc/termination.js +26 -22
  213. package/build/src/runtime/proc/windows-launch.exe +0 -0
  214. package/build/src/settlement/holder.d.ts +2 -1
  215. package/build/src/settlement/holder.js +15 -4
  216. package/build/src/settlement/settle.js +99 -33
  217. package/build/src/task/board.d.ts +68 -27
  218. package/build/src/task/board.js +31 -1
  219. package/build/src/task/catalog.d.ts +4 -0
  220. package/build/src/task/catalog.js +23 -0
  221. package/build/src/task/compose-language.d.ts +4 -0
  222. package/build/src/task/compose-language.js +40 -7
  223. package/build/src/task/compose-parser.d.ts +2 -1
  224. package/build/src/task/compose-parser.js +5 -3
  225. package/build/src/task/compose.d.ts +1 -0
  226. package/build/src/task/compose.js +4 -1
  227. package/build/src/task/context.d.ts +7 -1
  228. package/build/src/task/context.js +25 -6
  229. package/build/src/task/index.d.ts +18 -5
  230. package/build/src/task/index.js +97 -16
  231. package/build/src/task/mutation-result.d.ts +923 -0
  232. package/build/src/task/mutation-result.js +140 -0
  233. package/build/src/task/mutation.d.ts +552 -28
  234. package/build/src/task/mutation.js +304 -97
  235. package/build/src/task/operations.d.ts +1 -4
  236. package/build/src/task/operations.js +0 -6
  237. package/build/src/task/public.d.ts +2 -0
  238. package/build/src/task/public.js +1 -0
  239. package/build/src/workspace-place.d.ts +7 -2
  240. package/build/src/workspace-place.js +34 -24
  241. package/build/src/world.d.ts +1 -0
  242. package/build/src/world.js +11 -0
  243. package/package.json +8 -6
  244. package/build/src/akuma/request-execution.d.ts +0 -8
  245. package/build/src/akuma/request-execution.js +0 -98
@@ -1,6 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { abortable } from "../../abort.js";
3
- import { AKUMA_REQUESTS_ENV, AgentEventChannel, } from "../../provider.js";
2
+ import { AKUMA_REQUESTS_ENV, AgentEventChannel, createProviderAttempt, } from "../../provider.js";
4
3
  import { createEventState, mapEvent } from "./events.js";
5
4
  import { coordinate, loadOpencode, OPENCODE_SDK_PROVIDER, parseModel } from "./session.js";
6
5
  function object(value) {
@@ -201,27 +200,51 @@ async function forceDisposeOpencode(abortController, close, finish) {
201
200
  await close();
202
201
  await finish({ kind: "failed", diagnostic: "OpenCode session force-disposed" });
203
202
  }
204
- async function loadDriveRuntime(execution, input, signal, loader) {
205
- return await abortable(loadOpencode({
203
+ async function loadDriveRuntime(execution, input, signal, loader, onRuntime) {
204
+ const runtime = await loadOpencode({
206
205
  ...execution,
207
206
  env: {
208
207
  ...execution.env,
209
208
  ...(input.requests === undefined ? {} : { [AKUMA_REQUESTS_ENV]: input.requests.dir }),
210
209
  },
211
- }, input.cwd, signal, loader), signal, async (late) => await late.close());
210
+ }, input.cwd, signal, loader, onRuntime);
211
+ if (signal.aborted) {
212
+ await runtime.close();
213
+ signal.throwIfAborted();
214
+ }
215
+ return runtime;
216
+ }
217
+ function ownRuntime(custody, runtime) {
218
+ let closing;
219
+ let settleRuntimeClosed;
220
+ let rejectRuntimeClosed;
221
+ const runtimeClosed = new Promise((resolve, reject) => {
222
+ settleRuntimeClosed = resolve;
223
+ rejectRuntimeClosed = reject;
224
+ });
225
+ const close = () => {
226
+ if (closing === undefined) {
227
+ closing = runtime.close();
228
+ void closing.then(settleRuntimeClosed, rejectRuntimeClosed);
229
+ }
230
+ return closing;
231
+ };
232
+ custody?.own({ closed: runtimeClosed, abort: close, forceDispose: close });
233
+ return close;
212
234
  }
213
235
  async function openDriveSession(runtime, input, resumeSessionId, signal) {
214
236
  const session = runtime.client.session;
215
- const response = await abortable(input.session.kind === "fresh"
237
+ const response = await (input.session.kind === "fresh"
216
238
  ? session.create({ query: { directory: input.cwd }, throwOnError: true })
217
- : session.get({ path: { id: resumeSessionId }, query: { directory: input.cwd }, throwOnError: true }), signal);
239
+ : session.get({ path: { id: resumeSessionId }, query: { directory: input.cwd }, throwOnError: true }));
240
+ signal.throwIfAborted();
218
241
  const info = object(object(response)?.data) ?? object(response);
219
242
  const sessionId = text(info?.id) ?? resumeSessionId;
220
243
  if (sessionId === undefined)
221
244
  throw new Error("OpenCode did not return a session id");
222
245
  return { session, sessionId };
223
246
  }
224
- async function drive(execution, input, loader) {
247
+ async function drive(execution, input, loader, custody) {
225
248
  admit(input.options);
226
249
  const signal = input.signal ?? new AbortController().signal;
227
250
  const resumeSessionId = input.session.kind === "resume" ? opencodeSessionId(input.session.coordinate) : undefined;
@@ -229,13 +252,11 @@ async function drive(execution, input, loader) {
229
252
  const abortSetup = () => abortController.abort(signal.reason);
230
253
  signal.addEventListener("abort", abortSetup, { once: true });
231
254
  signal.throwIfAborted();
232
- const runtime = await loadDriveRuntime(execution, input, abortController.signal, loader);
233
- let closing;
255
+ let closeOnce;
256
+ const runtime = await loadDriveRuntime(execution, input, abortController.signal, loader, (ready) => {
257
+ closeOnce = ownRuntime(custody, ready);
258
+ });
234
259
  let iterator;
235
- const closeOnce = () => {
236
- closing ??= runtime.close();
237
- return closing;
238
- };
239
260
  try {
240
261
  const { session, sessionId } = await openDriveSession(runtime, input, resumeSessionId, abortController.signal);
241
262
  const events = new AgentEventChannel();
@@ -243,9 +264,11 @@ async function drive(execution, input, loader) {
243
264
  const messageID = `msg_${randomUUID().replaceAll("-", "")}`;
244
265
  const submissionState = { started: false };
245
266
  events.emit({ type: "session", coordinate: coordinate(sessionId) });
246
- const streamResult = await abortable(runtime.client.event.subscribe({ query: { directory: input.cwd } }), abortController.signal, async (late) => {
247
- await late.stream[Symbol.asyncIterator]().return?.(undefined);
248
- });
267
+ const streamResult = await runtime.client.event.subscribe({ query: { directory: input.cwd } });
268
+ if (abortController.signal.aborted) {
269
+ await streamResult.stream[Symbol.asyncIterator]().return?.(undefined);
270
+ abortController.signal.throwIfAborted();
271
+ }
249
272
  iterator = streamResult.stream[Symbol.asyncIterator]();
250
273
  const { completion, finish } = terminalSettlement(events, closeOnce);
251
274
  const observation = observeTurn({
@@ -259,12 +282,13 @@ async function drive(execution, input, loader) {
259
282
  submissionState,
260
283
  });
261
284
  submissionState.started = true;
262
- await abortable(session.promptAsync({
285
+ await session.promptAsync({
263
286
  path: { id: sessionId },
264
287
  query: { directory: input.cwd },
265
288
  body: promptBody(input, messageID),
266
289
  throwOnError: true,
267
- }), abortController.signal);
290
+ });
291
+ abortController.signal.throwIfAborted();
268
292
  void observation.then(finish);
269
293
  signal.removeEventListener("abort", abortSetup);
270
294
  return {
@@ -312,11 +336,28 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
312
336
  }),
313
337
  };
314
338
  },
315
- start: (input) => drive(execution, input, loader),
316
- resume: (input) => drive(execution, input, loader),
317
- fork: async (input) => {
339
+ start: (input) => createProviderAttempt(input.signal, async (custody) => await drive(execution, { ...input, signal: custody.signal }, loader, custody)),
340
+ resume: (input) => createProviderAttempt(input.signal, async (custody) => await drive(execution, { ...input, signal: custody.signal }, loader, custody)),
341
+ fork: (input) => createProviderAttempt(new AbortController().signal, async (custody) => {
318
342
  const sessionId = opencodeSessionId(input.session);
319
- const runtime = await loadOpencode(execution, input.cwd, new AbortController().signal, loader);
343
+ let close;
344
+ const runtime = await loadOpencode(execution, input.cwd, custody.signal, loader, (ready) => {
345
+ let closing;
346
+ let settleRuntimeClosed;
347
+ let rejectRuntimeClosed;
348
+ const runtimeClosed = new Promise((resolve, reject) => {
349
+ settleRuntimeClosed = resolve;
350
+ rejectRuntimeClosed = reject;
351
+ });
352
+ close = () => {
353
+ if (closing === undefined) {
354
+ closing = ready.close();
355
+ void closing.then(settleRuntimeClosed, rejectRuntimeClosed);
356
+ }
357
+ return closing;
358
+ };
359
+ custody.own({ closed: runtimeClosed, abort: close, forceDispose: close });
360
+ });
320
361
  try {
321
362
  const result = await runtime.client.session.fork({
322
363
  path: { id: sessionId },
@@ -330,8 +371,8 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
330
371
  return { session: coordinate(id) };
331
372
  }
332
373
  finally {
333
- await runtime.close();
374
+ await close();
334
375
  }
335
- },
376
+ }),
336
377
  };
337
378
  }
@@ -3,12 +3,13 @@ import type { ResumeCoordinate } from "../../heart/index.js";
3
3
  import type { ProviderExecution } from "../../provider-recipe.js";
4
4
  export type OpencodeSdkSession = Pick<OpencodeClient["session"], "create" | "get" | "fork" | "abort" | "promptAsync" | "messages">;
5
5
  export type OpencodeSdkEvent = Pick<OpencodeClient["event"], "subscribe">;
6
- export type OpencodeSdkLoader = (cwd: string, execution: ProviderExecution) => Promise<Readonly<{
6
+ export type OpencodeSdkLoader = (cwd: string, execution: ProviderExecution, signal: AbortSignal) => Promise<Readonly<{
7
7
  client: {
8
8
  session: OpencodeSdkSession;
9
9
  event: OpencodeSdkEvent;
10
10
  };
11
11
  close?: () => Promise<void> | void;
12
+ ready?: Promise<void>;
12
13
  }>>;
13
14
  export declare const OPENCODE_SDK_PROVIDER: "opencode-sdk";
14
15
  export declare function parseModel(model: string): Readonly<{
@@ -22,5 +23,5 @@ export type OpencodeRuntime = Readonly<{
22
23
  };
23
24
  close: () => Promise<void>;
24
25
  }>;
25
- export declare function loadOpencode(execution: ProviderExecution, cwd: string, signal: AbortSignal, loader?: OpencodeSdkLoader): Promise<OpencodeRuntime>;
26
+ export declare function loadOpencode(execution: ProviderExecution, cwd: string, signal: AbortSignal, loader?: OpencodeSdkLoader, onRuntime?: (runtime: OpencodeRuntime) => void): Promise<OpencodeRuntime>;
26
27
  export declare function coordinate(sessionId: string): ResumeCoordinate;
@@ -37,15 +37,28 @@ async function availablePort() {
37
37
  });
38
38
  });
39
39
  }
40
- export async function loadOpencode(execution, cwd, signal, loader) {
40
+ export async function loadOpencode(execution, cwd, signal, loader, onRuntime) {
41
41
  if (loader) {
42
- const loaded = await loader(cwd, execution);
43
- return {
42
+ const loaded = await abortable(loader(cwd, execution, signal), signal);
43
+ let closing;
44
+ const runtime = {
44
45
  client: loaded.client,
45
46
  close: async () => {
46
- await loaded.close?.();
47
+ closing ??= Promise.resolve(loaded.close?.()).then(() => undefined);
48
+ await closing;
47
49
  },
48
50
  };
51
+ onRuntime?.(runtime);
52
+ try {
53
+ if (loaded.ready !== undefined)
54
+ await abortable(loaded.ready, signal);
55
+ signal.throwIfAborted();
56
+ return runtime;
57
+ }
58
+ catch (error) {
59
+ await runtime.close();
60
+ throw error;
61
+ }
49
62
  }
50
63
  const port = await availablePort();
51
64
  const owned = await spawnDetachedProcess({
@@ -60,19 +73,23 @@ export async function loadOpencode(execution, cwd, signal, loader) {
60
73
  });
61
74
  const { createOpencodeClient } = await import("@opencode-ai/sdk");
62
75
  const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}`, directory: cwd });
76
+ let closing;
77
+ const runtime = {
78
+ client,
79
+ close: async () => {
80
+ closing ??= owned.terminate();
81
+ await closing;
82
+ },
83
+ };
84
+ onRuntime?.(runtime);
63
85
  try {
64
86
  await waitReady(client, cwd, signal);
65
87
  }
66
88
  catch (error) {
67
- await owned.terminate();
89
+ await runtime.close();
68
90
  throw error;
69
91
  }
70
- return {
71
- client,
72
- close: async () => {
73
- await owned.terminate();
74
- },
75
- };
92
+ return runtime;
76
93
  }
77
94
  export function coordinate(sessionId) {
78
95
  return { sessionId };
@@ -1,5 +1,5 @@
1
1
  import { abortable } from "../../abort.js";
2
- import { AKUMA_REQUESTS_ENV, AgentEventChannel, } from "../../provider.js";
2
+ import { AKUMA_REQUESTS_ENV, AgentEventChannel, createProviderAttempt, } from "../../provider.js";
3
3
  import { piTerminalFailure, translatePiEvent } from "./events.js";
4
4
  const PI_THINKING_LEVELS = new Set(["minimal", "low", "medium", "high", "xhigh", "max"]);
5
5
  const MODEL_PATTERN = /^[^/\s]+\/[^\s]+$/u;
@@ -75,7 +75,12 @@ async function createPiSession(sdk, execution, input, signal) {
75
75
  }
76
76
  return await sdk.createAgentSession(options);
77
77
  });
78
- return await abortable(setup, signal, (created) => created.session.dispose());
78
+ const created = await setup;
79
+ if (signal.aborted) {
80
+ await created.session.dispose();
81
+ signal.throwIfAborted();
82
+ }
83
+ return created;
79
84
  }
80
85
  function forceDisposePi(dispose, settle, setAborting) {
81
86
  setAborting();
@@ -109,9 +114,27 @@ async function runPiPrompt(native, input, events, state, settle) {
109
114
  : { kind: "failed", diagnostic: diagnostic(error) });
110
115
  }
111
116
  }
112
- async function drivePi(sdk, execution, input, signal) {
117
+ async function drivePi(sdk, execution, input, signal, custody) {
113
118
  const created = await createPiSession(sdk, execution, input, signal);
114
119
  const native = created.session;
120
+ let retired = false;
121
+ let settleRetired;
122
+ const closed = new Promise((resolve) => {
123
+ settleRetired = resolve;
124
+ });
125
+ custody?.own({
126
+ closed,
127
+ abort: async () => {
128
+ native.dispose();
129
+ retired = true;
130
+ settleRetired();
131
+ },
132
+ forceDispose: async () => {
133
+ native.dispose();
134
+ retired = true;
135
+ settleRetired();
136
+ },
137
+ });
115
138
  if (signal.aborted) {
116
139
  native.dispose();
117
140
  signal.throwIfAborted();
@@ -138,6 +161,10 @@ async function drivePi(sdk, execution, input, signal) {
138
161
  finally {
139
162
  try {
140
163
  native.dispose();
164
+ if (!retired) {
165
+ retired = true;
166
+ settleRetired();
167
+ }
141
168
  }
142
169
  finally {
143
170
  events.end();
@@ -216,21 +243,22 @@ export function createPiProvider(execution = { name: "pi", kind: "pi" }, load =
216
243
  if (execution.env !== undefined && Object.keys(execution.env).length > 0) {
217
244
  throw new TypeError("env injection not supported for provider pi");
218
245
  }
219
- const drive = async (input) => {
220
- const signal = input.signal ?? new AbortController().signal;
221
- signal.throwIfAborted();
222
- return drivePi(await abortable(load(), signal), execution, input, signal);
246
+ const drive = async (input, custody) => {
247
+ custody.signal.throwIfAborted();
248
+ const sdk = await abortable(load(), custody.signal);
249
+ custody.signal.throwIfAborted();
250
+ return await drivePi(sdk, execution, { ...input, signal: custody.signal }, custody.signal, custody);
223
251
  };
224
252
  return {
225
253
  admitOptions: admitPiOptions,
226
- start: drive,
227
- resume: async (input) => {
254
+ start: (input) => createProviderAttempt(input.signal, async (custody) => await drive(input, custody)),
255
+ resume: (input) => createProviderAttempt(input.signal, async (custody) => {
228
256
  piSessionFile(input.session.coordinate);
229
- return drive(input);
230
- },
231
- fork: async (input) => {
257
+ return await drive(input, custody);
258
+ }),
259
+ fork: (input) => createProviderAttempt(new AbortController().signal, async () => {
232
260
  piSessionFile(input.session);
233
- return forkPi(await load(), input);
234
- },
261
+ return await forkPi(await load(), input);
262
+ }),
235
263
  };
236
264
  }
@@ -127,6 +127,60 @@ async function takeLeashUntil(paths, deadline) {
127
127
  await abortableDelay(Math.min(POLL_MS, Math.max(0, deadline - performance.now())));
128
128
  }
129
129
  }
130
+ async function takeLeash(paths) {
131
+ for (;;) {
132
+ const leash = await HeldAkumaLeash.try(paths);
133
+ if (leash !== null)
134
+ return leash;
135
+ await abortableDelay(POLL_MS);
136
+ }
137
+ }
138
+ async function allocatedSoul(allocated) {
139
+ const soul = await readSoul(allocated.paths);
140
+ if (soul === null)
141
+ throw new Error("Akuma birth settled without a soul");
142
+ if (soul.id !== allocated.id)
143
+ throw new Error("Akuma birth returned a different identity");
144
+ return soul;
145
+ }
146
+ async function awaitConfirmation(result) {
147
+ try {
148
+ await result;
149
+ }
150
+ catch {
151
+ await new Promise(() => { });
152
+ }
153
+ }
154
+ async function settleCancelledLaunch(allocated, owned, cancellation) {
155
+ if ((await readSoul(allocated.paths)) !== null) {
156
+ await allocatedSoul(allocated);
157
+ return "born";
158
+ }
159
+ await awaitConfirmation(owned.terminate());
160
+ await awaitConfirmation(owned.exited);
161
+ if ((await readSoul(allocated.paths)) !== null) {
162
+ await allocatedSoul(allocated);
163
+ return "born";
164
+ }
165
+ const leash = await takeLeash(allocated.paths);
166
+ let outcome;
167
+ try {
168
+ outcome = await leash.sealIfUnborn(allocated.paths, {
169
+ evidence: diagnostic(cancellation),
170
+ at: new Date().toISOString(),
171
+ });
172
+ }
173
+ finally {
174
+ leash.release();
175
+ }
176
+ if (outcome === "born") {
177
+ await allocatedSoul(allocated);
178
+ return "born";
179
+ }
180
+ if ((await readSeal(allocated.paths)) === null)
181
+ throw new Error("Akuma birth settled without a seal");
182
+ return "sealed";
183
+ }
130
184
  async function awaitAsleepBirth(paths) {
131
185
  const leash = await takeLeashUntil(paths, performance.now() + BIRTH_TIMEOUT_MS);
132
186
  if (leash === null)
@@ -174,25 +228,33 @@ export async function birthAkuma(input) {
174
228
  }
175
229
  export async function launchAkuma(input) {
176
230
  const { allocated } = input;
177
- let owned;
231
+ let owned = undefined;
178
232
  try {
179
233
  input.signal?.throwIfAborted();
180
234
  owned = await input.launch(allocated);
181
- try {
182
- input.signal?.throwIfAborted();
183
- const soul = await awaitBirth(allocated.paths, owned ?? undefined, input.signal);
184
- if (soul.id !== allocated.id)
185
- throw new Error("Akuma birth returned a different identity");
186
- if (input.awaitAsleep === true)
187
- await awaitAsleepBirth(allocated.paths);
188
- return allocated;
189
- }
190
- finally {
191
- owned?.release();
192
- }
235
+ input.signal?.throwIfAborted();
236
+ const soul = await awaitBirth(allocated.paths, owned ?? undefined, input.signal);
237
+ if (soul.id !== allocated.id)
238
+ throw new Error("Akuma birth returned a different identity");
239
+ if (input.awaitAsleep === true)
240
+ await awaitAsleepBirth(allocated.paths);
241
+ owned?.release();
242
+ return allocated;
193
243
  }
194
244
  catch (error) {
245
+ if (owned !== undefined && input.signal?.aborted && error === input.signal.reason) {
246
+ const outcome = await settleCancelledLaunch(allocated, owned, error);
247
+ if (outcome === "born") {
248
+ if (input.awaitAsleep === true)
249
+ await awaitAsleepBirth(allocated.paths);
250
+ owned.release();
251
+ return allocated;
252
+ }
253
+ owned.release();
254
+ throw error;
255
+ }
195
256
  await sealLocalFailure(allocated, error);
257
+ owned?.release();
196
258
  throw error;
197
259
  }
198
260
  }
@@ -1,25 +1,19 @@
1
1
  import { type Soul } from "./heart/index.js";
2
2
  import { type AkumaPaths } from "./identity.js";
3
- import { type StructuralRequestClaim } from "./request-wire.js";
4
- import type { OwnedProcess } from "../runtime/proc/run.js";
5
- export type RequestChildLaunch = Readonly<{
6
- paths: AkumaPaths;
7
- seed: Omit<Soul, "createdAt">;
8
- initialBody: string;
9
- }>;
3
+ import { type RequestEnvelope } from "./request-wire.js";
10
4
  export type PumpInput = Readonly<{
11
5
  paths: AkumaPaths;
12
6
  parent: Soul;
13
7
  bodySequence: number;
14
8
  now(): string;
15
- spawn(launch: RequestChildLaunch): Promise<OwnedProcess | void>;
9
+ spawn(launch: unknown): Promise<unknown>;
16
10
  upstream?: unknown;
17
11
  signal: AbortSignal;
18
12
  }>;
19
13
  type ServeClaim = (input: PumpInput & Readonly<{
20
14
  directory: string;
21
15
  transportId: string;
22
- claim: StructuralRequestClaim;
16
+ claim: RequestEnvelope;
23
17
  signal: AbortSignal;
24
18
  admissionOpen(): boolean;
25
19
  }>) => Promise<void>;
@@ -2,9 +2,9 @@ import { access, mkdir, readFile, readdir, rm } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { abortableDelay } from "./abort.js";
4
4
  import { HeldAkumaLeash, readNonterminalRequests, readSoul, serveRequest, voidRequest, } from "./heart/index.js";
5
- import { pathsForAkuId } from "./identity.js";
5
+ import { pathsForAkuId, worldRootForAkumaPaths } from "./identity.js";
6
6
  import { BIRTH_TIMEOUT_MS } from "./publication.js";
7
- import { atomicJson, decodeClaim, receiptPath } from "./request-wire.js";
7
+ import { decodeEnvelope } from "./request-wire.js";
8
8
  const POLL_MS = 100;
9
9
  export class BodyRequestPump {
10
10
  input;
@@ -45,26 +45,21 @@ export class BodyRequestPump {
45
45
  if (this.handled.has(transportId))
46
46
  continue;
47
47
  const path = join(this.directory, name);
48
- const claim = decodeClaim(await readFile(path, "utf8"), transportId);
48
+ const claim = decodeEnvelope(await readFile(path, "utf8"), transportId);
49
49
  if (claim === null) {
50
50
  await rm(path, { force: true });
51
51
  this.handled.add(transportId);
52
52
  continue;
53
53
  }
54
54
  if (!this.admissionClosed) {
55
- try {
56
- await this.serveClaim({
57
- directory: this.directory,
58
- transportId,
59
- claim,
60
- ...this.input,
61
- signal: this.executionSignal.signal,
62
- admissionOpen: () => !this.admissionClosed,
63
- });
64
- }
65
- catch (error) {
66
- await projectTransportFailure(this.directory, transportId, claim, error);
67
- }
55
+ await this.serveClaim({
56
+ directory: this.directory,
57
+ transportId,
58
+ claim,
59
+ ...this.input,
60
+ signal: this.executionSignal.signal,
61
+ admissionOpen: () => !this.admissionClosed,
62
+ });
68
63
  }
69
64
  this.handled.add(transportId);
70
65
  }
@@ -107,14 +102,6 @@ async function requestFiles(directory) {
107
102
  throw error;
108
103
  }
109
104
  }
110
- async function projectTransportFailure(directory, transportId, claim, error) {
111
- await atomicJson(receiptPath(directory, transportId), {
112
- id: claim.id,
113
- action: claim.action,
114
- state: "refused",
115
- diagnostic: error instanceof Error ? error.message : String(error),
116
- });
117
- }
118
105
  function matchingRequestOrigin(soul, parent, id) {
119
106
  return soul.origin.kind === "request" && soul.origin.parent === parent && soul.origin.requestId === id;
120
107
  }
@@ -124,7 +111,7 @@ async function settleReservedSoul(paths, parent, request, soul) {
124
111
  : voidRequest(paths, request.id, "reserved child origin does not match the request"));
125
112
  }
126
113
  async function settleReserved(paths, parent, request, now, signal) {
127
- const childPaths = pathsForAkuId(request.world, request.child);
114
+ const childPaths = pathsForAkuId(worldRootForAkumaPaths(paths), request.child);
128
115
  const deadline = performance.now() + BIRTH_TIMEOUT_MS;
129
116
  for (;;) {
130
117
  signal?.throwIfAborted();
@@ -0,0 +1,26 @@
1
+ import { type RequestCommand } from "./request-wire.js";
2
+ export declare class AkumaBodyRequestError extends Error {
3
+ readonly action: string;
4
+ readonly outcome: "refused" | "voided";
5
+ readonly diagnostic: string;
6
+ readonly kind = "akuma-body-request";
7
+ constructor(action: string, outcome: "refused" | "voided", diagnostic: string);
8
+ }
9
+ /** Generic rendezvous only: operation owners supply their own request and result codecs. */
10
+ export declare function requestBodyCommand<Input, Output, Service, Reference = Service>(input: Readonly<{
11
+ directory: string;
12
+ id?: string;
13
+ command: RequestCommand<Input, Output, Service, Reference>;
14
+ value: Input;
15
+ signal?: AbortSignal;
16
+ }>): Promise<Readonly<{
17
+ kind: "returned";
18
+ result: Output;
19
+ requestId: string;
20
+ action: string;
21
+ }> | Readonly<{
22
+ kind: "reference";
23
+ reference: Reference;
24
+ requestId: string;
25
+ action: string;
26
+ }>>;
@@ -0,0 +1,100 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { access, readFile } from "node:fs/promises";
3
+ import { abortableDelay } from "./abort.js";
4
+ import { atomicJson, decodeReceiptEnvelope, receiptPath, requestPath } from "./request-wire.js";
5
+ const POLL_MS = 100;
6
+ export class AkumaBodyRequestError extends Error {
7
+ action;
8
+ outcome;
9
+ diagnostic;
10
+ kind = "akuma-body-request";
11
+ constructor(action, outcome, diagnostic) {
12
+ super(`${action} ${outcome === "voided" ? "failed" : "refused"}: ${diagnostic}`);
13
+ this.action = action;
14
+ this.outcome = outcome;
15
+ this.diagnostic = diagnostic;
16
+ this.name = "AkumaBodyRequestError";
17
+ }
18
+ }
19
+ function throwVoidedRequestFailure(action, evidence, failure, decodeFailure) {
20
+ const ownerFailure = decodeVoidedOwnerFailure(failure, decodeFailure);
21
+ if (ownerFailure !== null)
22
+ throw ownerFailure;
23
+ throw new AkumaBodyRequestError(action, "voided", evidence);
24
+ }
25
+ function decodeVoidedOwnerFailure(failure, decodeFailure) {
26
+ if (failure === undefined || decodeFailure === undefined)
27
+ return null;
28
+ try {
29
+ return decodeFailure(failure);
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ function withRequestMetadata(response, id, action) {
36
+ Object.defineProperties(response, {
37
+ requestId: { value: id, enumerable: false },
38
+ action: { value: action, enumerable: false },
39
+ });
40
+ return response;
41
+ }
42
+ /** Generic rendezvous only: operation owners supply their own request and result codecs. */
43
+ export async function requestBodyCommand(input) {
44
+ const id = input.id ?? randomUUID();
45
+ input.signal?.throwIfAborted();
46
+ const transportId = randomUUID();
47
+ await atomicJson(requestPath(input.directory, transportId), {
48
+ id,
49
+ action: input.command.action,
50
+ payload: input.command.encodeRequest(input.value),
51
+ });
52
+ const path = receiptPath(input.directory, transportId);
53
+ for (;;) {
54
+ try {
55
+ const receipt = decodeReceiptEnvelope(await readFile(path, "utf8"), id, input.command.action);
56
+ if (receipt === null)
57
+ throw new Error(`Akuma body request ${id} has an invalid receipt`);
58
+ if (receipt.state === "refused")
59
+ throw new AkumaBodyRequestError(receipt.action, "refused", receipt.diagnostic);
60
+ if (receipt.state === "voided") {
61
+ throwVoidedRequestFailure(receipt.action, receipt.evidence, receipt.failure, input.command.decodeFailure);
62
+ }
63
+ if ("reference" in receipt) {
64
+ let reference;
65
+ try {
66
+ reference = input.command.decodeReference(receipt.reference);
67
+ }
68
+ catch (error) {
69
+ const diagnostic = error instanceof Error ? error.message : String(error);
70
+ throw new Error(`transport integrity: request ${id} action ${input.command.action} returned an invalid durable reference: ${diagnostic}`);
71
+ }
72
+ return withRequestMetadata({
73
+ kind: "reference",
74
+ reference,
75
+ }, id, input.command.action);
76
+ }
77
+ if (receipt.outcome.kind === "failed") {
78
+ const failure = receipt.outcome.failure;
79
+ throw new Error(failure.kind === "failed" ? failure.diagnostic : `Akuma ${failure.id} was not born`);
80
+ }
81
+ let result;
82
+ try {
83
+ result = input.command.decodeResult(receipt.outcome.result);
84
+ }
85
+ catch (error) {
86
+ const diagnostic = error instanceof Error ? error.message : String(error);
87
+ throw new Error(`transport integrity: request ${id} action ${input.command.action} returned an invalid live result: ${diagnostic}`);
88
+ }
89
+ return withRequestMetadata({ kind: "returned", result }, id, input.command.action);
90
+ }
91
+ catch (error) {
92
+ if (error.code !== "ENOENT")
93
+ throw error;
94
+ }
95
+ if (!(await access(input.directory).then(() => true, () => false))) {
96
+ throw new AkumaBodyRequestError(input.command.action, "voided", "parent request channel closed before a receipt");
97
+ }
98
+ await abortableDelay(POLL_MS, input.signal);
99
+ }
100
+ }