@astrosheep/keiyaku 4.5.17 → 4.5.18

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 (190) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +5 -2
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +4 -2
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-babysit/SKILL.md +3 -1
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +3 -1
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +3 -2
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +13 -1
  8. package/build/src/akuma/akuma-handle.d.ts +18 -7
  9. package/build/src/akuma/akuma-handle.js +89 -112
  10. package/build/src/akuma/akuma-instance.d.ts +19 -4
  11. package/build/src/akuma/akuma-instance.js +102 -38
  12. package/build/src/akuma/akuma-observe.js +40 -22
  13. package/build/src/akuma/akuma-product.d.ts +12 -8
  14. package/build/src/akuma/akuma-product.js +66 -54
  15. package/build/src/akuma/akuma.d.ts +9 -21
  16. package/build/src/akuma/akuma.js +11 -67
  17. package/build/src/akuma/body.js +127 -96
  18. package/build/src/akuma/call-request.d.ts +2 -2
  19. package/build/src/akuma/call-request.js +2 -2
  20. package/build/src/akuma/control.d.ts +8 -0
  21. package/build/src/akuma/control.js +23 -0
  22. package/build/src/akuma/dispatch-association.d.ts +13 -0
  23. package/build/src/akuma/dispatch-association.js +20 -0
  24. package/build/src/akuma/fleet-execution.js +10 -15
  25. package/build/src/akuma/fleet-observation.js +1 -1
  26. package/build/src/akuma/fleet-request.js +9 -2
  27. package/build/src/akuma/heart/facts.d.ts +3 -0
  28. package/build/src/akuma/heart/facts.js +6 -0
  29. package/build/src/akuma/heart/index.d.ts +1 -0
  30. package/build/src/akuma/heart/index.js +5 -8
  31. package/build/src/akuma/heart/rows.d.ts +1 -0
  32. package/build/src/akuma/heart/rows.js +7 -0
  33. package/build/src/akuma/heart/schema.d.ts +1 -1
  34. package/build/src/akuma/heart/schema.js +5 -3
  35. package/build/src/akuma/heart/tells.d.ts +2 -2
  36. package/build/src/akuma/heart/tells.js +16 -11
  37. package/build/src/akuma/index.d.ts +5 -3
  38. package/build/src/akuma/index.js +1 -1
  39. package/build/src/akuma/nuke.js +8 -45
  40. package/build/src/akuma/providers/claude/events.d.ts +1 -1
  41. package/build/src/akuma/providers/claude/events.js +2 -1
  42. package/build/src/akuma/providers/codex-app-server/events.d.ts +2 -2
  43. package/build/src/akuma/providers/codex-app-server/events.js +6 -2
  44. package/build/src/akuma/providers/opencode-sdk/events.d.ts +1 -1
  45. package/build/src/akuma/providers/opencode-sdk/events.js +5 -1
  46. package/build/src/akuma/providers/pi/events.d.ts +4 -0
  47. package/build/src/akuma/providers/pi/events.js +8 -3
  48. package/build/src/akuma/publication.js +2 -11
  49. package/build/src/akuma/request-rendezvous.d.ts +1 -1
  50. package/build/src/akuma/request-rendezvous.js +9 -8
  51. package/build/src/akuma/schema.d.ts +4 -6
  52. package/build/src/akuma/schema.js +35 -31
  53. package/build/src/akuma-body.js +3 -2
  54. package/build/src/cli/accepted.d.ts +1 -2
  55. package/build/src/cli/accepted.js +18 -16
  56. package/build/src/cli/commands/akuma-invoke.d.ts +0 -2
  57. package/build/src/cli/commands/akuma-invoke.js +8 -25
  58. package/build/src/cli/commands/akuma.d.ts +2 -1
  59. package/build/src/cli/commands/akuma.js +6 -6
  60. package/build/src/cli/commands/contract-help.d.ts +2 -1
  61. package/build/src/cli/commands/contract-help.js +27 -13
  62. package/build/src/cli/commands/contract-invoke.js +15 -0
  63. package/build/src/cli/commands/contract.d.ts +1 -0
  64. package/build/src/cli/commands/contract.js +4 -3
  65. package/build/src/cli/commands/install.js +8 -7
  66. package/build/src/cli/commands/task.d.ts +2 -0
  67. package/build/src/cli/commands/task.js +9 -5
  68. package/build/src/cli/invoke.d.ts +0 -1
  69. package/build/src/cli/invoke.js +19 -5
  70. package/build/src/cli/parse.d.ts +3 -0
  71. package/build/src/cli/parse.js +26 -13
  72. package/build/src/cli/render/akuma-activity.js +4 -4
  73. package/build/src/cli/render/akuma.js +7 -9
  74. package/build/src/cli/render/audit.js +28 -24
  75. package/build/src/cli/render/board.js +7 -3
  76. package/build/src/cli/render/catalog.js +16 -23
  77. package/build/src/cli/render/contract-history.js +20 -21
  78. package/build/src/cli/render/contract-observation.d.ts +2 -0
  79. package/build/src/cli/render/contract-observation.js +30 -6
  80. package/build/src/cli/render/contract.js +39 -33
  81. package/build/src/cli/render/kanshi-akuma.js +5 -6
  82. package/build/src/cli/render/kanshi.js +56 -99
  83. package/build/src/cli/render/nuke.js +3 -3
  84. package/build/src/cli/render/receipt.d.ts +15 -5
  85. package/build/src/cli/render/receipt.js +54 -20
  86. package/build/src/cli/render/refusal.d.ts +1 -1
  87. package/build/src/cli/render/refusal.js +55 -45
  88. package/build/src/cli/render/region.js +8 -8
  89. package/build/src/cli/render/settings.js +13 -11
  90. package/build/src/cli/render/status-set.js +13 -8
  91. package/build/src/cli/render/task.d.ts +1 -0
  92. package/build/src/cli/render/task.js +15 -14
  93. package/build/src/cli/render/terminal.d.ts +3 -1
  94. package/build/src/cli/render/terminal.js +33 -21
  95. package/build/src/cli/render/value.d.ts +3 -0
  96. package/build/src/cli/render/value.js +24 -0
  97. package/build/src/cli/result.d.ts +11 -18
  98. package/build/src/cli/runtime.d.ts +0 -1
  99. package/build/src/cli/runtime.js +46 -31
  100. package/build/src/cli/usage.d.ts +15 -2
  101. package/build/src/cli/usage.js +59 -6
  102. package/build/src/contract-guidance.d.ts +1 -0
  103. package/build/src/contract-guidance.js +26 -2
  104. package/build/src/contract-worktree.js +2 -1
  105. package/build/src/core/facts/gate.d.ts +8 -1
  106. package/build/src/core/facts/gate.js +48 -6
  107. package/build/src/core/subject.d.ts +4 -3
  108. package/build/src/core/subject.js +6 -2
  109. package/build/src/dispatch/association.d.ts +1 -13
  110. package/build/src/dispatch/association.js +1 -20
  111. package/build/src/git/hooks.d.ts +1 -1
  112. package/build/src/git/hooks.js +10 -3
  113. package/build/src/git/result-codec.js +4 -1
  114. package/build/src/git/scratch.js +1 -1
  115. package/build/src/git/target-placement.d.ts +2 -2
  116. package/build/src/git/target-placement.js +21 -20
  117. package/build/src/git/terminal-reconcile.js +16 -9
  118. package/build/src/git/workspace.d.ts +18 -3
  119. package/build/src/git/workspace.js +21 -4
  120. package/build/src/kanshi/read.js +3 -3
  121. package/build/src/library/address.js +2 -2
  122. package/build/src/library/akuma-creation.d.ts +6 -3
  123. package/build/src/library/akuma-creation.js +43 -23
  124. package/build/src/library/audit.js +11 -6
  125. package/build/src/library/catalog.js +2 -3
  126. package/build/src/library/composition.d.ts +1 -0
  127. package/build/src/library/continuation.d.ts +23 -31
  128. package/build/src/library/continuation.js +97 -99
  129. package/build/src/library/contract-bind.d.ts +2 -0
  130. package/build/src/library/contract-bind.js +31 -18
  131. package/build/src/library/contract-execution.d.ts +39 -0
  132. package/build/src/library/contract-execution.js +145 -0
  133. package/build/src/library/contract-forwarding.d.ts +9 -29
  134. package/build/src/library/contract-forwarding.js +14 -78
  135. package/build/src/library/contract-handle.js +38 -5
  136. package/build/src/library/contract-operations.d.ts +29 -0
  137. package/build/src/library/contract-operations.js +35 -2
  138. package/build/src/library/contract-types.d.ts +4 -0
  139. package/build/src/library/delivery.d.ts +2 -3
  140. package/build/src/library/delivery.js +3 -5
  141. package/build/src/library/execution-result.d.ts +243 -0
  142. package/build/src/library/execution-result.js +101 -0
  143. package/build/src/library/fleet.d.ts +3 -0
  144. package/build/src/library/fleet.js +6 -5
  145. package/build/src/library/keiyaku.d.ts +5 -4
  146. package/build/src/library/keiyaku.js +1 -0
  147. package/build/src/library/mutation.d.ts +121 -89
  148. package/build/src/library/mutation.js +109 -85
  149. package/build/src/library/reconcile.d.ts +7 -0
  150. package/build/src/library/reconcile.js +13 -0
  151. package/build/src/library/refusal.d.ts +6 -0
  152. package/build/src/library/refusal.js +40 -0
  153. package/build/src/plugin/runtime.d.ts +2 -2
  154. package/build/src/plugin/runtime.js +30 -22
  155. package/build/src/protocol/attempt.d.ts +3 -1
  156. package/build/src/protocol/attempt.js +13 -5
  157. package/build/src/protocol/audit.d.ts +8 -1
  158. package/build/src/protocol/audit.js +17 -8
  159. package/build/src/protocol/completion.d.ts +20 -16
  160. package/build/src/protocol/completion.js +162 -136
  161. package/build/src/protocol/deliver.d.ts +17 -15
  162. package/build/src/protocol/deliver.js +107 -52
  163. package/build/src/protocol/intent.d.ts +5 -2
  164. package/build/src/protocol/intent.js +9 -2
  165. package/build/src/protocol/operations.d.ts +4 -4
  166. package/build/src/protocol/operations.js +3 -1
  167. package/build/src/protocol/outcome.d.ts +9 -0
  168. package/build/src/protocol/placement.d.ts +3 -1
  169. package/build/src/protocol/placement.js +13 -0
  170. package/build/src/protocol/progress.d.ts +82 -0
  171. package/build/src/protocol/progress.js +142 -0
  172. package/build/src/protocol/read/status.d.ts +27 -3
  173. package/build/src/protocol/read/status.js +40 -9
  174. package/build/src/protocol/reintegrate.d.ts +2 -0
  175. package/build/src/protocol/reintegrate.js +1 -0
  176. package/build/src/protocol/result-codec.d.ts +2 -1
  177. package/build/src/protocol/result-codec.js +79 -49
  178. package/build/src/protocol/review.d.ts +11 -7
  179. package/build/src/protocol/review.js +17 -27
  180. package/build/src/protocol/run.d.ts +2 -0
  181. package/build/src/protocol/run.js +1 -0
  182. package/build/src/runtime/proc/windows-launch.exe +0 -0
  183. package/build/src/settings.js +15 -4
  184. package/build/src/verification/execution.d.ts +2 -2
  185. package/build/src/verification/execution.js +2 -2
  186. package/build/src/workspace-place.d.ts +4 -0
  187. package/build/src/workspace-place.js +15 -7
  188. package/build/src/world.d.ts +2 -2
  189. package/build/src/world.js +20 -51
  190. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "keiyaku",
3
- "version": "0.1.2+codex.20260826070830",
3
+ "version": "0.1.2+codex.20260905023250",
4
4
  "description": "Use the Keiyaku contract, task, and Akuma CLI.",
5
5
  "author": {
6
6
  "name": "Keiyaku"
@@ -1,6 +1,9 @@
1
1
  ---
2
2
  name: keiyaku
3
- description: Use the Keiyaku v4 CLI for contract delivery, task coordination, and Akuma work.
3
+ description: >-
4
+ Keiyaku is a CLI that records plans, writes handoff terms for deliveries,
5
+ and calls delegates. Use this skill when you need to figure out which
6
+ Keiyaku skill or command to reach for.
4
7
  ---
5
8
 
6
9
  # Keiyaku
@@ -33,7 +36,7 @@ keiyaku -C <repo> review [<contract>|@<contract>] --satisfied
33
36
  ```
34
37
 
35
38
  ```bash
36
- keiyaku -C <cwd> call <akuma-name> [--contract <kei/...>] [--alias @name] [--readonly] [--allowed <product.action>]... [--schema <file>] [--wait <duration> | -d | --detach] [--json] (<prompt> | -)
39
+ keiyaku -C <cwd> call <akuma-name> [--contract <kei/...>] [--alias @name] [--allowed <product.action>]... [--schema <file>] [--wait <duration> | -d | --detach] [--json] (<prompt> | -)
37
40
  keiyaku -C <repo> wait <akuma-selector>... [--any | --all]
38
41
  keiyaku -C <repo> tell <aku/...|@alias> (<prompt> | -)
39
42
  ```
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: keiyaku-akuma
3
- description: Use when delegating work to, supervising, steering, inspecting, forking, or stopping a Keiyaku v4 Akuma.
3
+ description: >-
4
+ Calling an Akuma: delegating scouting, mechanical chores, or fanned-out
5
+ parallel work instead of doing it inline.
4
6
  ---
5
7
 
6
8
  # Keiyaku Akuma
@@ -13,7 +15,7 @@ is accepted; the identity underneath never changes.
13
15
  ## Start One
14
16
 
15
17
  ```bash
16
- keiyaku -C <cwd> call <akuma-name> [--alias @name] [--readonly] [--allowed <product.action>]... [--schema <file>] [--wait <duration> | -d | --detach] [--json] (<prompt> | -)
18
+ keiyaku -C <cwd> call <akuma-name> [--alias @name] [--allowed <product.action>]... [--schema <file>] [--wait <duration> | -d | --detach] [--json] (<prompt> | -)
17
19
  ```
18
20
 
19
21
  Give the worker's initial prompt as one argument (quote it when it contains
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: keiyaku-babysit
3
- description: Babysit a long-running multi-lane goal — a room of lanes that advance on their own, call for you on their schedule, and hold each other up while you are busy being productive.
3
+ description: >-
4
+ Entering babysit mode: supervising parallel work across multiple Akuma and
5
+ Contracts as they advance, instead of doing the work yourself.
4
6
  ---
5
7
 
6
8
  # Keiyaku Babysit
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: keiyaku-bind
3
- description: Author and bind one Keiyaku delivery Contract. Use when deciding whether a bounded delivery is ready for Contract terms, writing those terms for an implementer, binding an existing Task with `bind --task`, choosing bind inputs, or interpreting the bind receipt and worktree handoff.
3
+ description: >-
4
+ Binding a Keiyaku: writing down what a bounded piece of work must deliver and
5
+ how it will be judged, so it can be handed off.
4
6
  ---
5
7
 
6
8
  # Keiyaku Bind
@@ -1,8 +1,9 @@
1
1
  ---
2
2
  name: keiyaku-task
3
3
  description: >-
4
- Use when recording, decomposing, or tracking a complex task, typically
5
- involving three or more steps.
4
+ Organizing, tracking progress and status, or binding work of three or more
5
+ steps as a Keiyaku Task: what to do, in what order, what depends on what,
6
+ what is done and what is blocked.
6
7
  ---
7
8
 
8
9
  # Keiyaku Task
@@ -1,6 +1,9 @@
1
1
  ---
2
2
  name: keiyaku-workflow
3
- description: Hold a Contract's fulfillment loop — the loop-holder's five decisions across the whole active loop, from a waiting or bound Contract to claimed or abandoned. Hold or delegate; shape the work into Contracts, Arcs, and Tasks; commission Deliverer and Reviewer seats; adjudicate deliver, review, and audit returns, amend or abandon; schedule landing. For the flagship or the single delegate holding a whole loop.
3
+ description: >-
4
+ The Keiyaku workflow: how work moves through Keiyaku from planning through
5
+ binding, commissioning, delivery, review, and landing, and what you decide
6
+ at each stage.
4
7
  ---
5
8
 
6
9
  # Holding a fulfillment loop
@@ -111,6 +114,15 @@ immutable commit without moving the branch or the real index — a
111
114
  prior git commit is optional shaping, never a prerequisite. Deliver
112
115
  never satisfies a review gate.
113
116
 
117
+ Conflict materialization projects an uncommitted merge; it does not resolve
118
+ its contents or record a delivery. After editing the final worktree bytes,
119
+ `deliver --include-dirty` can capture them even while the real index still
120
+ reports `UU`. No `git add` or commit is required, and capture preserves the
121
+ real index. The candidate includes the complete non-ignored worktree, not
122
+ just the Contract's Region or the conflict paths. Candidate capture and target
123
+ placement are separate: this authorization does not permit overwriting
124
+ blocking changes in the target checkout.
125
+
114
126
  Audit observes: prospective candidate, integration, Verification,
115
127
  diff, target. It records no candidate and satisfies no gate.
116
128
 
@@ -1,10 +1,15 @@
1
- import { type TellResult } from "./body.js";
2
- import { type KillEvidence } from "./heart/index.js";
3
- import { type AkuId } from "./identity.js";
1
+ import { type TellResult, type TellWakeRuntime } from "./body.js";
2
+ import { HeldAkumaLeash, type KillEvidence } from "./heart/index.js";
3
+ import { type AkuId, type AkumaPaths } from "./identity.js";
4
4
  import { type ActivityHistory, type ExactHistory } from "./projection.js";
5
5
  import type { AkumaCallExecution, AkumaStatus, ForkReceipt, InterruptReceipt } from "./akuma.js";
6
6
  import type { WorldRoot } from "../world.js";
7
7
  declare const CALL_EXECUTION: unique symbol;
8
+ export declare function settleAkumaKill(paths: AkumaPaths, signal?: AbortSignal, retainLeash?: boolean): Promise<Readonly<{
9
+ evidence: KillEvidence;
10
+ leash?: HeldAkumaLeash;
11
+ }>>;
12
+ export declare function killAkumaWithRecovery(paths: AkumaPaths, recover?: (paths: AkumaPaths) => Promise<void>, signal?: AbortSignal): Promise<KillEvidence>;
8
13
  export declare class AkumaHandle {
9
14
  readonly id: AkuId;
10
15
  private readonly worldPath;
@@ -23,13 +28,19 @@ export declare class AkumaHandle {
23
28
  wait(predicate?: (status: AkumaStatus) => boolean, options?: Readonly<{
24
29
  timeoutMs?: number;
25
30
  }>): Promise<AkumaStatus>;
26
- tell(body: string): Promise<TellResult>;
27
- interrupt(body: string): Promise<InterruptReceipt>;
28
- interruptSchema(body: string, schemaJson: string): Promise<TellResult>;
31
+ tell(body: string, tellId?: string, recordedAt?: string, runtime?: TellWakeRuntime, schemaJson?: string): Promise<TellResult>;
32
+ interrupt(body: string, options?: Readonly<{
33
+ tellId?: string;
34
+ schemaJson?: string;
35
+ signal?: AbortSignal;
36
+ runtime?: TellWakeRuntime;
37
+ }>): Promise<InterruptReceipt>;
29
38
  fork(input: Readonly<{
30
39
  at: string;
31
40
  }>): Promise<ForkReceipt>;
32
- kill(): Promise<KillEvidence>;
41
+ kill(options?: Readonly<{
42
+ signal?: AbortSignal;
43
+ }>): Promise<KillEvidence>;
33
44
  lastAnswer(): Promise<LastAnswer>;
34
45
  }
35
46
  /** Package-internal provenance retained only by the handle returned from call. */
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { CONTROL_RESPONSE_MS, wakeRecordedTell } from "./body.js";
2
+ import { handoffPendingTells, wakeRecordedTell } from "./body.js";
3
3
  import { HeldAkumaLeash, activitySlice, readForkPoint, readHeart, readKill, readLastAnsweredTurn, readSoul, recordTell, requestPause, requestStop, } from "./heart/index.js";
4
+ import { acquireLeash } from "./control.js";
4
5
  import { parsePublicHistoryId, pathsForAkuId } from "./identity.js";
5
6
  import { projectTurns, selectHistory, selectExactHistory, } from "./projection.js";
6
7
  import { resolveProviderExecution } from "./providers/index.js";
@@ -18,56 +19,67 @@ function defaultWaitComplete(status) {
18
19
  return (status.life !== "running" &&
19
20
  !status.timeline.entries.some((entry) => entry.kind === "row" && entry.row.kind === "tell" && entry.row.state === "pending"));
20
21
  }
21
- async function takeLeashUntil(paths, deadline) {
22
- for (;;) {
23
- const leash = await HeldAkumaLeash.try(paths);
24
- if (leash !== null)
25
- return leash;
26
- if (performance.now() >= deadline)
27
- return null;
28
- await wait(Math.min(POLL_MS, Math.max(0, deadline - performance.now())));
29
- }
30
- }
31
- async function recordTellBody(paths, akuma, body, id = randomUUID(), recordedAt = new Date().toISOString()) {
32
- const admitted = await recordTell(paths, { kind: "tell", id, body, recordedAt });
33
- if (admitted.kind === "not-born")
34
- throw new AkumaNotBornError(akuma);
35
- return { kind: "recorded", tellId: admitted.tell.id };
22
+ async function takeLeashUntilSignal(paths, bodySequence, signal, unbounded = false) {
23
+ const leash = await acquireLeash(paths, {
24
+ bodySequence,
25
+ ...(signal === undefined && unbounded ? { deadline: Number.POSITIVE_INFINITY } : {}),
26
+ ...(signal === undefined ? {} : { signal }),
27
+ });
28
+ if (leash !== null)
29
+ return leash;
30
+ const latestBody = (await readHeart(paths)).latestBody;
31
+ if (latestBody?.sequence === bodySequence && latestBody.hung !== undefined)
32
+ return { kind: "unavailable", evidence: "hung" };
33
+ if (latestBody?.sequence === bodySequence && latestBody.end !== undefined)
34
+ return { kind: "unavailable", evidence: "untidy" };
35
+ return { kind: "unavailable", evidence: "unavailable" };
36
36
  }
37
- async function killAkumaWithRecovery(paths, recover = async () => { }) {
37
+ export async function settleAkumaKill(paths, signal, retainLeash = false) {
38
+ const request = await requestStop(paths, new Date().toISOString());
39
+ if (request.kind !== "requested") {
40
+ if (!retainLeash)
41
+ return { evidence: request.kind };
42
+ const leash = await acquireLeash(paths, signal === undefined ? {} : { signal });
43
+ return leash === null ? { evidence: "unavailable" } : { evidence: request.kind, leash };
44
+ }
45
+ const target = request.body;
46
+ const waited = await takeLeashUntilSignal(paths, target.sequence, signal);
47
+ if ("kind" in waited) {
48
+ if ((await readKill(paths, target.sequence)) !== null)
49
+ return { evidence: "killed" };
50
+ return { evidence: waited.evidence };
51
+ }
52
+ const leash = waited;
53
+ let retain = false;
38
54
  try {
39
- const request = await requestStop(paths, new Date().toISOString());
40
- if (request.kind !== "requested")
41
- return request.kind;
42
- const target = request.body;
43
- const leash = await takeLeashUntil(paths, performance.now() + CONTROL_RESPONSE_MS);
44
- if ((await readKill(paths, target.sequence)) !== null) {
45
- leash?.release();
46
- return "killed";
55
+ if ((await readKill(paths, target.sequence)) !== null)
56
+ return { evidence: "killed" };
57
+ const settledBody = (await readHeart(paths)).latestBody;
58
+ if (settledBody?.sequence !== target.sequence) {
59
+ return { evidence: (await readKill(paths, target.sequence)) === null ? "unavailable" : "killed" };
47
60
  }
48
- if (leash === null) {
49
- if ((await readKill(paths, target.sequence)) !== null)
50
- return "killed";
51
- const body = (await readHeart(paths)).latestBody;
52
- return body?.sequence === target.sequence && body.hung !== undefined ? "hung" : "unavailable";
61
+ if (settledBody.end !== "put-down") {
62
+ await leash.clearStop(paths);
63
+ return { evidence: "untidy" };
53
64
  }
54
- try {
55
- const settledBody = (await readHeart(paths)).latestBody;
56
- if (settledBody?.sequence !== target.sequence)
57
- return (await readKill(paths, target.sequence)) === null ? "unavailable" : "killed";
58
- if (settledBody.end !== "put-down") {
59
- await leash.clearStop(paths);
60
- return "untidy";
61
- }
62
- const settled = await leash.settleStop(paths, target.sequence);
63
- return settled === null ? "unavailable" : "killed";
64
- }
65
- finally {
65
+ const settled = await leash.settleStop(paths, target.sequence);
66
+ if (settled === null)
67
+ return { evidence: "unavailable" };
68
+ retain = retainLeash;
69
+ return retainLeash ? { evidence: "killed", leash } : { evidence: "killed" };
70
+ }
71
+ finally {
72
+ if (!retain)
66
73
  leash.release();
67
- }
74
+ }
75
+ }
76
+ export async function killAkumaWithRecovery(paths, recover, signal) {
77
+ try {
78
+ return (await settleAkumaKill(paths, signal)).evidence;
68
79
  }
69
80
  finally {
70
- void recover(paths).catch(() => undefined);
81
+ if (recover !== undefined)
82
+ void recover(paths).catch(() => undefined);
71
83
  }
72
84
  }
73
85
  export class AkumaHandle {
@@ -135,11 +147,19 @@ export class AkumaHandle {
135
147
  await wait(deadline === undefined ? POLL_MS : Math.min(POLL_MS, Math.max(0, deadline - performance.now())));
136
148
  }
137
149
  }
138
- async tell(body) {
139
- const recorded = await recordTellBody(this.paths, this.id, body);
140
- return await wakeRecordedTell(this.paths, recorded.tellId);
150
+ async tell(body, tellId = randomUUID(), recordedAt = new Date().toISOString(), runtime, schemaJson) {
151
+ const admitted = await recordTell(this.paths, {
152
+ kind: "tell",
153
+ id: tellId,
154
+ body,
155
+ recordedAt,
156
+ ...(schemaJson === undefined ? {} : { schemaJson }),
157
+ });
158
+ if (admitted.kind === "not-born")
159
+ throw new AkumaNotBornError(this.id);
160
+ return await wakeRecordedTell(this.paths, admitted.tell.id, runtime);
141
161
  }
142
- async interrupt(body) {
162
+ async interrupt(body, options = {}) {
143
163
  const request = await requestPause(this.paths, new Date().toISOString());
144
164
  if (request.kind === "not-born") {
145
165
  throw new AkumaNotBornError(this.id);
@@ -147,90 +167,47 @@ export class AkumaHandle {
147
167
  let putDown = "was-idle";
148
168
  let leash = await HeldAkumaLeash.try(this.paths);
149
169
  if (leash === null) {
150
- leash = await takeLeashUntil(this.paths, performance.now() + CONTROL_RESPONSE_MS);
170
+ const waited = await takeLeashUntilSignal(this.paths, request.body.sequence, options.signal, true);
171
+ if ("kind" in waited)
172
+ return waited;
173
+ leash = waited;
151
174
  putDown = "self-aborted";
152
175
  }
153
- if (leash === null) {
154
- const body = (await readHeart(this.paths)).latestBody;
155
- return {
156
- kind: "unavailable",
157
- evidence: body?.sequence === request.body.sequence && body.hung !== undefined ? "hung" : "unavailable",
158
- };
159
- }
160
- const settledBody = (await readHeart(this.paths)).latestBody;
161
- if (settledBody?.sequence === request.body.sequence && settledBody.hung !== undefined) {
162
- try {
163
- await leash.clearPause(this.paths);
164
- }
165
- finally {
166
- leash.release();
167
- }
168
- return { kind: "unavailable", evidence: "hung" };
169
- }
170
- if (settledBody?.sequence !== request.body.sequence || settledBody.end === undefined) {
171
- try {
172
- await leash.clearPause(this.paths);
173
- }
174
- finally {
175
- leash.release();
176
- }
177
- return { kind: "unavailable", evidence: "untidy" };
178
- }
179
- if (request.body.end !== undefined || settledBody.end !== "put-down")
180
- putDown = "was-idle";
181
176
  let recorded;
182
177
  try {
183
- const id = randomUUID();
184
- const admitted = await leash.recordInterruptTell(this.paths, {
185
- kind: "tell",
186
- id,
187
- body,
188
- recordedAt: new Date().toISOString(),
189
- });
190
- if (admitted.kind === "not-born")
191
- throw new AkumaNotBornError(this.id);
192
- recorded = { kind: "recorded", tellId: admitted.tell.id };
193
- }
194
- finally {
195
- leash.release();
196
- }
197
- return { kind: "interrupted", putDown, tell: await wakeRecordedTell(this.paths, recorded.tellId) };
198
- }
199
- async interruptSchema(body, schemaJson) {
200
- const request = await requestPause(this.paths, new Date().toISOString());
201
- if (request.kind === "not-born")
202
- throw new AkumaNotBornError(this.id);
203
- let tellId;
204
- let leash = await HeldAkumaLeash.try(this.paths);
205
- if (leash === null)
206
- leash = await takeLeashUntil(this.paths, performance.now() + CONTROL_RESPONSE_MS);
207
- if (leash === null)
208
- throw new Error("schema interrupt could not acquire Body leash");
209
- try {
178
+ options.signal?.throwIfAborted();
210
179
  const settledBody = (await readHeart(this.paths)).latestBody;
211
180
  if (settledBody?.sequence === request.body.sequence && settledBody.hung !== undefined) {
212
181
  await leash.clearPause(this.paths);
213
- throw new Error("schema interrupt found a hung Body");
182
+ return { kind: "unavailable", evidence: "hung" };
214
183
  }
215
184
  if (settledBody?.sequence !== request.body.sequence || settledBody.end === undefined) {
216
185
  await leash.clearPause(this.paths);
217
- throw new Error("schema interrupt could not prove Body settlement");
186
+ return { kind: "unavailable", evidence: "untidy" };
218
187
  }
188
+ if (request.body.end !== undefined || settledBody.end !== "put-down")
189
+ putDown = "was-idle";
190
+ options.signal?.throwIfAborted();
191
+ const id = randomUUID();
219
192
  const admitted = await leash.recordInterruptTell(this.paths, {
220
193
  kind: "tell",
221
- id: randomUUID(),
194
+ id: options.tellId ?? id,
222
195
  body,
223
196
  recordedAt: new Date().toISOString(),
224
- schemaJson,
197
+ ...(options.schemaJson === undefined ? {} : { schemaJson: options.schemaJson }),
225
198
  });
226
199
  if (admitted.kind === "not-born")
227
200
  throw new AkumaNotBornError(this.id);
228
- tellId = admitted.tell.id;
201
+ recorded = { kind: "recorded", tellId: admitted.tell.id };
229
202
  }
230
203
  finally {
231
204
  leash.release();
232
205
  }
233
- return await wakeRecordedTell(this.paths, tellId);
206
+ return {
207
+ kind: "interrupted",
208
+ putDown,
209
+ tell: await wakeRecordedTell(this.paths, recorded.tellId, options.runtime),
210
+ };
234
211
  }
235
212
  async fork(input) {
236
213
  const source = await readSoul(this.paths);
@@ -296,8 +273,8 @@ export class AkumaHandle {
296
273
  };
297
274
  }
298
275
  }
299
- async kill() {
300
- return await killAkumaWithRecovery(this.paths);
276
+ async kill(options = {}) {
277
+ return await killAkumaWithRecovery(this.paths, handoffPendingTells, options.signal);
301
278
  }
302
279
  async lastAnswer() {
303
280
  const turn = await readLastAnsweredTurn(this.paths);
@@ -1,9 +1,22 @@
1
1
  import type { AllowedAction } from "./allowed.js";
2
+ import type { AkumaStatus } from "./akuma.js";
3
+ import type { InterruptReceipt, KillEvidence } from "./akuma.js";
2
4
  import { type AkuId } from "./identity.js";
3
5
  import { type ActivityHistory } from "./projection.js";
4
6
  import type { Settings } from "../settings.js";
5
7
  import type { WorldRoot } from "../world.js";
6
- import type { Schema } from "./schema.js";
8
+ import { type Schema } from "./schema.js";
9
+ export type AkumaIdleOptions = Readonly<{
10
+ timeoutMs?: number;
11
+ }>;
12
+ export type AkumaHistoryOptions = Readonly<{
13
+ before?: number;
14
+ since?: number;
15
+ limit?: number;
16
+ }>;
17
+ export type AkumaSignalOptions = Readonly<{
18
+ signal?: AbortSignal;
19
+ }>;
7
20
  export type AkumaBirthInput = Readonly<{
8
21
  root: WorldRoot;
9
22
  cwd?: string;
@@ -25,7 +38,9 @@ export declare class Akuma {
25
38
  static select(root: WorldRoot, selector: string): Akuma;
26
39
  tell(text: string): Promise<string>;
27
40
  tell<T>(text: string, options: AkumaTellOptions<T>): Promise<T>;
28
- idle(): Promise<void>;
29
- history(): Promise<ActivityHistory>;
30
- kill(): Promise<void>;
41
+ status(): Promise<AkumaStatus>;
42
+ interrupt(text: string, options?: AkumaSignalOptions): Promise<InterruptReceipt>;
43
+ idle(options?: AkumaIdleOptions): Promise<void>;
44
+ history(options?: AkumaHistoryOptions): Promise<ActivityHistory>;
45
+ kill(options?: AkumaSignalOptions): Promise<KillEvidence>;
31
46
  }
@@ -1,44 +1,53 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { callReadonly, canonicalBirthCwd } from "./call-input.js";
3
- import { spawnAkumaBody, wakeRecordedTell } from "./body.js";
3
+ import { spawnAkumaBody } from "./body.js";
4
4
  import { decodeAllowedActions, unionAllowedActions } from "./allowed.js";
5
- import { AkumaDecodeError, AkumaNotBornError, AkumaProviderError } from "./akuma-errors.js";
5
+ import { AkumaDecodeError, AkumaProviderError } from "./akuma-errors.js";
6
6
  import { AkumaHandle } from "./akuma-handle.js";
7
- import { POLL_MS, defaultWaitComplete, killAkumaWithRecovery } from "./akuma.js";
7
+ import { POLL_MS, defaultWaitComplete } from "./akuma.js";
8
8
  import { bornStatus } from "./akuma-observe.js";
9
9
  import { loadArchetype } from "./archetype.js";
10
- import { activitySlice, readTell, readTurn, recordTell } from "./heart/index.js";
10
+ import { activitySlice, readTell, readTurn } from "./heart/index.js";
11
11
  import { parseAkuId, pathsForAkuId } from "./identity.js";
12
12
  import { birthAkuma, launchAkuma } from "./publication.js";
13
13
  import { projectTurns, selectHistory } from "./projection.js";
14
14
  import { settings as readSettings } from "../settings.js";
15
+ import { schemaJsonText } from "./schema.js";
16
+ import { abortable } from "./abort.js";
15
17
  const HISTORY_LIMIT = 12;
18
+ function signalOption(value) {
19
+ if (value === undefined)
20
+ return undefined;
21
+ if (!(value instanceof AbortSignal))
22
+ throw new TypeError("signal must be an AbortSignal");
23
+ return value;
24
+ }
16
25
  function wait(milliseconds) {
17
26
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
18
27
  }
19
- async function recordPlainTell(paths, id, body, tellId) {
20
- const admitted = await recordTell(paths, { kind: "tell", id: tellId, body, recordedAt: new Date().toISOString() });
21
- if (admitted.kind === "not-born")
22
- throw new AkumaNotBornError(id);
23
- return admitted.tell.id;
28
+ function recordedTell(result) {
29
+ if (result.wake.kind === "failed")
30
+ throw new AkumaProviderError(result.wake.diagnostic);
31
+ return { tellId: result.admission.tellId };
32
+ }
33
+ async function recordPlainTell(id, root, body, tellId) {
34
+ const admitted = await new AkumaHandle(id, root).tell(body, tellId);
35
+ return recordedTell(admitted);
24
36
  }
25
- async function recordSchemaTell(paths, id, body, tellId, options, root) {
37
+ async function recordSchemaTell(input) {
38
+ const { id, body, tellId, options, root } = input;
26
39
  if (options.interrupt === true) {
27
- const interrupted = await new AkumaHandle(id, root).interruptSchema(body, options.schema.jsonText);
28
- return interrupted.admission.tellId;
40
+ const interrupted = await new AkumaHandle(id, root).interrupt(body, {
41
+ tellId,
42
+ schemaJson: schemaJsonText(options.schema),
43
+ });
44
+ if (interrupted.kind === "unavailable") {
45
+ throw new AkumaProviderError(`schema interrupt unavailable: ${interrupted.evidence}`);
46
+ }
47
+ return recordedTell(interrupted.tell);
29
48
  }
30
- const recordedAt = new Date().toISOString();
31
- const tell = {
32
- kind: "tell",
33
- id: tellId,
34
- body,
35
- recordedAt,
36
- schemaJson: options.schema.jsonText,
37
- };
38
- const admitted = await recordTell(paths, tell);
39
- if (admitted.kind === "not-born")
40
- throw new AkumaNotBornError(id);
41
- return admitted.tell.id;
49
+ const admitted = await new AkumaHandle(id, root).tell(body, tellId, undefined, undefined, schemaJsonText(options.schema));
50
+ return recordedTell(admitted);
42
51
  }
43
52
  function outcomeError(outcome) {
44
53
  if (outcome.kind === "invalid-output")
@@ -54,9 +63,6 @@ async function boundOutcome(paths, tell) {
54
63
  return turn?.end?.outcome ?? null;
55
64
  }
56
65
  async function awaitTellOutcome(paths, tellId) {
57
- const wake = await wakeRecordedTell(paths, tellId);
58
- if (wake.wake.kind === "failed")
59
- throw new AkumaProviderError(wake.wake.diagnostic);
60
66
  for (;;) {
61
67
  const tell = await readTell(paths, tellId);
62
68
  if (tell === null)
@@ -126,9 +132,15 @@ export class Akuma {
126
132
  throw new TypeError("Akuma tell text must be a string");
127
133
  const tellId = randomUUID();
128
134
  const recorded = options === undefined
129
- ? await recordPlainTell(this.paths, this.id, text, tellId)
130
- : await recordSchemaTell(this.paths, this.id, text, tellId, options, this.root);
131
- const outcome = await awaitTellOutcome(this.paths, recorded);
135
+ ? await recordPlainTell(this.id, this.root, text, tellId)
136
+ : await recordSchemaTell({
137
+ id: this.id,
138
+ body: text,
139
+ tellId,
140
+ options,
141
+ root: this.root,
142
+ });
143
+ const outcome = await awaitTellOutcome(this.paths, recorded.tellId);
132
144
  if (outcome.kind !== "answered")
133
145
  outcomeError(outcome);
134
146
  if (options === undefined)
@@ -142,27 +154,79 @@ export class Akuma {
142
154
  throw new AkumaDecodeError(error instanceof Error ? error.message : "Answer is not valid JSON", outcome.answer);
143
155
  }
144
156
  try {
145
- return options.schema.parse(parsed);
157
+ return options.schema.decode(parsed);
146
158
  }
147
159
  catch (error) {
148
160
  throw new AkumaDecodeError(error instanceof Error ? error.message : "Answer failed schema decode", outcome.answer);
149
161
  }
150
162
  }
151
- async idle() {
163
+ async status() {
164
+ return (await bornStatus(this.paths, this.id, { aperture: "monitoring" })).status;
165
+ }
166
+ async interrupt(text, options = {}) {
167
+ if (typeof text !== "string")
168
+ throw new TypeError("Akuma interrupt text must be a string");
169
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
170
+ throw new TypeError("Akuma interrupt options must be an object");
171
+ }
172
+ const signal = signalOption(options.signal);
173
+ signal?.throwIfAborted();
174
+ const operation = new AkumaHandle(this.id, this.root).interrupt(text, signal === undefined ? {} : { signal });
175
+ return await abortable(operation, signal ?? new AbortController().signal);
176
+ }
177
+ async idle(options = {}) {
178
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
179
+ throw new TypeError("Akuma idle options must be an object");
180
+ }
181
+ const unknown = Object.keys(options).find((key) => key !== "timeoutMs");
182
+ if (unknown !== undefined)
183
+ throw new TypeError(`Akuma idle options has unknown field: ${unknown}`);
184
+ if (options.timeoutMs !== undefined && (!Number.isFinite(options.timeoutMs) || options.timeoutMs < 0)) {
185
+ throw new TypeError("Akuma idle timeoutMs must be a nonnegative finite millisecond duration");
186
+ }
187
+ const deadline = options.timeoutMs === undefined ? undefined : performance.now() + options.timeoutMs;
152
188
  for (;;) {
153
189
  const observed = await bornStatus(this.paths, this.id, { aperture: "monitoring" });
154
- if (defaultWaitComplete(observed.status))
190
+ if (defaultWaitComplete(observed.status) || (deadline !== undefined && performance.now() >= deadline))
155
191
  return;
156
- await wait(POLL_MS);
192
+ await wait(deadline === undefined ? POLL_MS : Math.min(POLL_MS, Math.max(0, deadline - performance.now())));
157
193
  }
158
194
  }
159
- async history() {
195
+ async history(options = {}) {
196
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
197
+ throw new TypeError("Akuma history options must be an object");
198
+ }
199
+ const unknown = Object.keys(options).find((key) => !["before", "since", "limit"].includes(key));
200
+ if (unknown !== undefined)
201
+ throw new TypeError(`Akuma history options has unknown field: ${unknown}`);
202
+ if (options.before !== undefined && options.since !== undefined) {
203
+ throw new TypeError("Akuma history before and since are mutually exclusive");
204
+ }
205
+ for (const [name, value] of [
206
+ ["before", options.before],
207
+ ["since", options.since],
208
+ ]) {
209
+ if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0)) {
210
+ throw new TypeError(`Akuma history ${name} must be a positive safe integer`);
211
+ }
212
+ }
213
+ const limit = options.limit ?? HISTORY_LIMIT;
214
+ if (!Number.isSafeInteger(limit) || limit <= 0 || limit > 5_000) {
215
+ throw new TypeError("Akuma history limit must be a positive safe integer no greater than 5000");
216
+ }
160
217
  const slice = await activitySlice(this.paths);
161
218
  return selectHistory(projectTurns(slice.rows, { lowestRetained: slice.lowestRetained, highest: slice.highest }), {
162
- limit: HISTORY_LIMIT,
219
+ ...(options.before === undefined ? {} : { before: options.before }),
220
+ ...(options.since === undefined ? {} : { since: options.since }),
221
+ limit,
163
222
  });
164
223
  }
165
- async kill() {
166
- await killAkumaWithRecovery(this.paths);
224
+ async kill(options = {}) {
225
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
226
+ throw new TypeError("Akuma kill options must be an object");
227
+ }
228
+ const signal = signalOption(options.signal);
229
+ signal?.throwIfAborted();
230
+ return await abortable(new AkumaHandle(this.id, this.root).kill(signal === undefined ? {} : { signal }), signal ?? new AbortController().signal);
167
231
  }
168
232
  }