@astrosheep/keiyaku 4.6.8 → 4.6.9

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 (253) hide show
  1. package/README.md +40 -14
  2. package/build/plugins/claude/.claude-plugin/plugin.json +1 -1
  3. package/build/plugins/claude/skills/keiyaku/SKILL.md +5 -6
  4. package/build/plugins/claude/skills/keiyaku-akuma/SKILL.md +1 -1
  5. package/build/plugins/claude/skills/keiyaku-akuma/references/automation.md +8 -8
  6. package/build/plugins/claude/skills/keiyaku-babysit/SKILL.md +1 -1
  7. package/build/plugins/claude/skills/keiyaku-bind/SKILL.md +9 -9
  8. package/build/plugins/claude/skills/keiyaku-debt/SKILL.md +91 -0
  9. package/build/plugins/claude/skills/keiyaku-workflow/SKILL.md +2 -2
  10. package/build/plugins/codex/.codex-plugin/plugin.json +1 -1
  11. package/build/plugins/codex/skills/keiyaku/SKILL.md +5 -6
  12. package/build/plugins/codex/skills/keiyaku-akuma/SKILL.md +1 -1
  13. package/build/plugins/codex/skills/keiyaku-akuma/references/automation.md +8 -8
  14. package/build/plugins/codex/skills/keiyaku-babysit/SKILL.md +1 -1
  15. package/build/plugins/codex/skills/keiyaku-bind/SKILL.md +9 -9
  16. package/build/plugins/codex/skills/keiyaku-debt/SKILL.md +91 -0
  17. package/build/plugins/codex/skills/keiyaku-workflow/SKILL.md +2 -2
  18. package/build/plugins/opencode/index.js +1 -1
  19. package/build/plugins/opencode/skills/keiyaku/SKILL.md +5 -6
  20. package/build/plugins/opencode/skills/keiyaku-akuma/SKILL.md +1 -1
  21. package/build/plugins/opencode/skills/keiyaku-akuma/references/automation.md +8 -8
  22. package/build/plugins/opencode/skills/keiyaku-babysit/SKILL.md +1 -1
  23. package/build/plugins/opencode/skills/keiyaku-bind/SKILL.md +9 -9
  24. package/build/plugins/opencode/skills/keiyaku-debt/SKILL.md +91 -0
  25. package/build/plugins/opencode/skills/keiyaku-workflow/SKILL.md +2 -2
  26. package/build/skills/keiyaku/SKILL.md +5 -6
  27. package/build/skills/keiyaku-akuma/SKILL.md +1 -1
  28. package/build/skills/keiyaku-akuma/references/automation.md +8 -8
  29. package/build/skills/keiyaku-babysit/SKILL.md +1 -1
  30. package/build/skills/keiyaku-bind/SKILL.md +9 -9
  31. package/build/skills/keiyaku-debt/SKILL.md +91 -0
  32. package/build/skills/keiyaku-workflow/SKILL.md +2 -2
  33. package/build/src/akuma/akuma-instance.d.ts +2 -0
  34. package/build/src/akuma/akuma-instance.js +10 -0
  35. package/build/src/akuma/akuma-observe.d.ts +33 -8
  36. package/build/src/akuma/akuma-observe.js +88 -64
  37. package/build/src/akuma/akuma-owner.d.ts +11 -2
  38. package/build/src/akuma/akuma-owner.js +45 -35
  39. package/build/src/akuma/akuma-selection-execution.js +9 -1
  40. package/build/src/akuma/akuma-stream.d.ts +22 -0
  41. package/build/src/akuma/akuma-stream.js +116 -0
  42. package/build/src/akuma/akuma-wait.d.ts +1 -40
  43. package/build/src/akuma/akuma-wait.js +23 -84
  44. package/build/src/akuma/akuma.d.ts +17 -11
  45. package/build/src/akuma/akuma.js +2 -0
  46. package/build/src/akuma/allowed.d.ts +7 -12
  47. package/build/src/akuma/allowed.js +13 -4
  48. package/build/src/akuma/body-supervisor.d.ts +5 -5
  49. package/build/src/akuma/body-supervisor.js +22 -20
  50. package/build/src/akuma/body.js +28 -1
  51. package/build/src/akuma/call-request.d.ts +12 -22
  52. package/build/src/akuma/heart/activity-schema.d.ts +12 -0
  53. package/build/src/akuma/heart/activity-schema.js +6 -0
  54. package/build/src/akuma/heart/index.d.ts +59 -2
  55. package/build/src/akuma/heart/index.js +87 -8
  56. package/build/src/akuma/heart/rows.js +5 -3
  57. package/build/src/akuma/heart/soul.d.ts +9 -0
  58. package/build/src/akuma/heart/soul.js +29 -0
  59. package/build/src/akuma/heart/storage.d.ts +15 -0
  60. package/build/src/akuma/heart/storage.js +55 -0
  61. package/build/src/akuma/heart/timeline.d.ts +7 -0
  62. package/build/src/akuma/heart/timeline.js +10 -0
  63. package/build/src/akuma/index.d.ts +1 -0
  64. package/build/src/akuma/projection.d.ts +25 -0
  65. package/build/src/akuma/projection.js +24 -7
  66. package/build/src/akuma/provider-recipe.d.ts +11 -0
  67. package/build/src/akuma/provider-recipe.js +23 -0
  68. package/build/src/akuma/provider.d.ts +25 -0
  69. package/build/src/akuma/provider.js +99 -7
  70. package/build/src/akuma/providers/acp/events.js +25 -2
  71. package/build/src/akuma/providers/claude/events.js +76 -10
  72. package/build/src/akuma/providers/claude/index.js +2 -5
  73. package/build/src/akuma/providers/codex-app-server/events.js +17 -2
  74. package/build/src/akuma/providers/codex-app-server/index.js +15 -9
  75. package/build/src/akuma/providers/decode.d.ts +8 -0
  76. package/build/src/akuma/providers/decode.js +21 -0
  77. package/build/src/akuma/providers/grok-build/index.js +10 -26
  78. package/build/src/akuma/providers/opencode-sdk/event-stream.d.ts +3 -3
  79. package/build/src/akuma/providers/opencode-sdk/event-stream.js +2 -25
  80. package/build/src/akuma/providers/opencode-sdk/events.d.ts +7 -43
  81. package/build/src/akuma/providers/opencode-sdk/events.js +147 -284
  82. package/build/src/akuma/providers/opencode-sdk/index.js +267 -421
  83. package/build/src/akuma/providers/opencode-sdk/native-http.d.ts +9 -0
  84. package/build/src/akuma/providers/opencode-sdk/native-http.js +69 -0
  85. package/build/src/akuma/providers/opencode-sdk/session.d.ts +42 -36
  86. package/build/src/akuma/providers/opencode-sdk/session.js +75 -82
  87. package/build/src/akuma/providers/paseo/events.d.ts +8 -0
  88. package/build/src/akuma/providers/paseo/events.js +58 -11
  89. package/build/src/akuma/providers/paseo/index.js +11 -7
  90. package/build/src/akuma/providers/pi/events.d.ts +2 -0
  91. package/build/src/akuma/providers/pi/events.js +32 -24
  92. package/build/src/akuma/providers/pi/index.js +21 -35
  93. package/build/src/akuma/request-lifecycle.d.ts +1 -2
  94. package/build/src/akuma/request-lifecycle.js +2 -8
  95. package/build/src/akuma/request-rendezvous.js +38 -55
  96. package/build/src/akuma/request-serve.d.ts +1 -1
  97. package/build/src/akuma/request-serve.js +77 -113
  98. package/build/src/akuma/selection-observation.d.ts +57 -51
  99. package/build/src/akuma/selection-observation.js +3 -3
  100. package/build/src/akuma/turn-drive.d.ts +3 -0
  101. package/build/src/akuma/turn-drive.js +26 -5
  102. package/build/src/akuma-body.js +12 -22
  103. package/build/src/cli/akuma-caller.js +10 -0
  104. package/build/src/cli/commands/akuma-grammar.js +3 -4
  105. package/build/src/cli/commands/akuma.js +13 -51
  106. package/build/src/cli/commands/contract-grammar.d.ts +7 -6
  107. package/build/src/cli/commands/contract-grammar.js +27 -32
  108. package/build/src/cli/commands/contract-help.d.ts +16 -7
  109. package/build/src/cli/commands/contract-help.js +20 -11
  110. package/build/src/cli/commands/contract.js +11 -11
  111. package/build/src/cli/commands/debt-grammar.d.ts +19 -0
  112. package/build/src/cli/commands/debt-grammar.js +323 -0
  113. package/build/src/cli/commands/debt-query.d.ts +5 -0
  114. package/build/src/cli/commands/{task-query.js → debt-query.js} +9 -9
  115. package/build/src/cli/commands/{task.d.ts → debt.d.ts} +2 -2
  116. package/build/src/cli/commands/{task.js → debt.js} +83 -143
  117. package/build/src/cli/coordinates.d.ts +1 -1
  118. package/build/src/cli/coordinates.js +1 -1
  119. package/build/src/cli/parse.d.ts +4 -4
  120. package/build/src/cli/parse.js +17 -16
  121. package/build/src/cli/render/akuma-activity.d.ts +11 -83
  122. package/build/src/cli/render/akuma-activity.js +20 -382
  123. package/build/src/cli/render/catalog.d.ts +2 -2
  124. package/build/src/cli/render/catalog.js +5 -5
  125. package/build/src/cli/render/contract.js +2 -2
  126. package/build/src/cli/render/debt.d.ts +28 -0
  127. package/build/src/cli/render/{task.js → debt.js} +51 -112
  128. package/build/src/cli/render/kanshi.js +28 -28
  129. package/build/src/cli/render/marks.d.ts +5 -5
  130. package/build/src/cli/render/marks.js +4 -10
  131. package/build/src/cli/render/nuke.js +1 -1
  132. package/build/src/cli/runtime.js +3 -3
  133. package/build/src/cli/scan.d.ts +1 -1
  134. package/build/src/cli/usage.d.ts +1 -1
  135. package/build/src/cli/usage.js +4 -4
  136. package/build/src/contract-worktree.js +3 -3
  137. package/build/src/debt/board.d.ts +160 -0
  138. package/build/src/{task → debt}/board.js +89 -89
  139. package/build/src/debt/catalog.d.ts +11 -0
  140. package/build/src/debt/catalog.js +59 -0
  141. package/build/src/{task → debt}/context.d.ts +1 -1
  142. package/build/src/{task → debt}/context.js +1 -1
  143. package/build/src/{task → debt}/created-observation.d.ts +6 -10
  144. package/build/src/{task → debt}/created-observation.js +7 -7
  145. package/build/src/debt/document.d.ts +37 -0
  146. package/build/src/{task → debt}/document.js +37 -37
  147. package/build/src/debt/identity.d.ts +21 -0
  148. package/build/src/{task → debt}/identity.js +35 -35
  149. package/build/src/debt/index.d.ts +111 -0
  150. package/build/src/debt/index.js +203 -0
  151. package/build/src/{task → debt}/input.d.ts +8 -8
  152. package/build/src/{task → debt}/input.js +21 -21
  153. package/build/src/debt/lifecycle-operations.d.ts +6 -0
  154. package/build/src/{task → debt}/lifecycle-operations.js +27 -31
  155. package/build/src/debt/mutation-result.d.ts +981 -0
  156. package/build/src/debt/mutation-result.js +109 -0
  157. package/build/src/debt/mutation.d.ts +424 -0
  158. package/build/src/debt/mutation.js +223 -0
  159. package/build/src/debt/operation-types.d.ts +71 -0
  160. package/build/src/debt/operations.d.ts +28 -0
  161. package/build/src/{task → debt}/operations.js +59 -60
  162. package/build/src/debt/query.d.ts +76 -0
  163. package/build/src/{task → debt}/query.js +55 -55
  164. package/build/src/{task → debt}/store.d.ts +13 -13
  165. package/build/src/{task → debt}/store.js +58 -49
  166. package/build/src/debt/view.d.ts +4 -0
  167. package/build/src/debt/view.js +7 -0
  168. package/build/src/identity/coordinates.d.ts +1 -1
  169. package/build/src/index.d.ts +13 -13
  170. package/build/src/index.js +9 -9
  171. package/build/src/kanshi/index.d.ts +1 -1
  172. package/build/src/kanshi/read.js +33 -33
  173. package/build/src/kanshi/report.d.ts +7 -7
  174. package/build/src/kanshi/select.js +4 -4
  175. package/build/src/library/akumas.d.ts +2 -3
  176. package/build/src/library/bind.d.ts +3 -3
  177. package/build/src/library/bind.js +5 -5
  178. package/build/src/library/contract-creation.js +5 -5
  179. package/build/src/library/contract-execution.js +4 -4
  180. package/build/src/library/contract-types.d.ts +2 -2
  181. package/build/src/library/input.d.ts +2 -2
  182. package/build/src/library/input.js +5 -5
  183. package/build/src/library/invocation.js +2 -2
  184. package/build/src/library/nuke.js +3 -3
  185. package/build/src/library/outcome.d.ts +126 -126
  186. package/build/src/library/outcome.js +3 -3
  187. package/build/src/library/selection.d.ts +1 -4
  188. package/build/src/library/selection.js +11 -62
  189. package/build/src/plugin/public.d.ts +5 -0
  190. package/build/src/plugin/runtime.js +3 -1
  191. package/build/src/runtime/proc/windows-launch.exe +0 -0
  192. package/build/src/settlement/fence.d.ts +3 -3
  193. package/build/src/settlement/fence.js +4 -4
  194. package/build/src/settlement/holder.d.ts +16 -16
  195. package/build/src/settlement/holder.js +69 -63
  196. package/build/src/settlement/settle.d.ts +5 -5
  197. package/build/src/settlement/settle.js +53 -53
  198. package/package.json +12 -6
  199. package/plugins/paseo/plugin/index.server.ts +17 -0
  200. package/plugins/paseo/plugin/paseo-plugin.json +4 -0
  201. package/plugins/paseo/plugin/server/attach.ts +52 -0
  202. package/plugins/paseo/plugin/server/keiyaku.ts +44 -0
  203. package/plugins/paseo/plugin/server/message.ts +60 -0
  204. package/plugins/paseo/plugin/server/mirror.ts +193 -0
  205. package/plugins/paseo/plugin/server/model.ts +14 -0
  206. package/plugins/paseo/plugin/server/provider.ts +276 -0
  207. package/plugins/paseo/plugin/server/sdk-worker.ts +237 -0
  208. package/plugins/paseo/plugin/server/sdk.ts +289 -0
  209. package/plugins/paseo/plugin/server/stop.ts +26 -0
  210. package/plugins/paseo/plugin/server/transport.ts +35 -0
  211. package/plugins/paseo/plugin/shared/attach-rpc.ts +10 -0
  212. package/plugins/paseo/plugin/shared/refresh-rpc.ts +6 -0
  213. package/plugins/paseo/plugin/tsconfig.json +6 -0
  214. package/skills/keiyaku/SKILL.md +5 -6
  215. package/skills/keiyaku-akuma/SKILL.md +1 -1
  216. package/skills/keiyaku-akuma/references/automation.md +8 -8
  217. package/skills/keiyaku-babysit/SKILL.md +1 -1
  218. package/skills/keiyaku-bind/SKILL.md +9 -9
  219. package/skills/keiyaku-debt/SKILL.md +91 -0
  220. package/skills/keiyaku-workflow/SKILL.md +2 -2
  221. package/build/plugins/claude/skills/keiyaku-task/SKILL.md +0 -104
  222. package/build/plugins/codex/skills/keiyaku-task/SKILL.md +0 -104
  223. package/build/plugins/opencode/skills/keiyaku-task/SKILL.md +0 -104
  224. package/build/skills/keiyaku-task/SKILL.md +0 -104
  225. package/build/src/cli/commands/task-grammar.d.ts +0 -19
  226. package/build/src/cli/commands/task-grammar.js +0 -384
  227. package/build/src/cli/commands/task-query.d.ts +0 -5
  228. package/build/src/cli/render/task.d.ts +0 -39
  229. package/build/src/task/board.d.ts +0 -168
  230. package/build/src/task/catalog.d.ts +0 -11
  231. package/build/src/task/catalog.js +0 -59
  232. package/build/src/task/compose-language.d.ts +0 -57
  233. package/build/src/task/compose-language.js +0 -364
  234. package/build/src/task/compose-parser.d.ts +0 -32
  235. package/build/src/task/compose-parser.js +0 -180
  236. package/build/src/task/compose.d.ts +0 -16
  237. package/build/src/task/compose.js +0 -189
  238. package/build/src/task/document.d.ts +0 -39
  239. package/build/src/task/identity.d.ts +0 -21
  240. package/build/src/task/index.d.ts +0 -138
  241. package/build/src/task/index.js +0 -269
  242. package/build/src/task/lifecycle-operations.d.ts +0 -6
  243. package/build/src/task/mutation-result.d.ts +0 -1726
  244. package/build/src/task/mutation-result.js +0 -198
  245. package/build/src/task/mutation.d.ts +0 -590
  246. package/build/src/task/mutation.js +0 -262
  247. package/build/src/task/operation-types.d.ts +0 -71
  248. package/build/src/task/operations.d.ts +0 -28
  249. package/build/src/task/query.d.ts +0 -76
  250. package/build/src/task/view.d.ts +0 -4
  251. package/build/src/task/view.js +0 -7
  252. package/skills/keiyaku-task/SKILL.md +0 -104
  253. /package/build/src/{task → debt}/operation-types.js +0 -0
@@ -0,0 +1,237 @@
1
+ /**
2
+ * The dedicated Keiyaku worker the Paseo bridge spawns.
3
+ *
4
+ * Paseo compiles a directory-source plugin into one evaluated bundle, so the
5
+ * plugin directory has no worker module to point at once it is loaded, and a
6
+ * static or literal dynamic import of the package root is rejected by Paseo's
7
+ * own module boundary (the repository self-link resolves outside the plugin
8
+ * directory). The worker therefore travels as source text and is started with
9
+ * `node --input-type=module -e`. It locates the installed public package
10
+ * portably: the `keiyaku` executable on PATH, then the package root that owns
11
+ * it, then that package's own declared public entry. An explicit entry
12
+ * argument overrides discovery for tests and embedding.
13
+ *
14
+ * The worker is deliberately the thinnest possible host: it imports the public
15
+ * package root, serves one JSON request per line, and answers with the public
16
+ * operations' own values. It never formats CLI arguments, never renders text,
17
+ * and never decides a lifecycle outcome. Identity is stripped by the spawning
18
+ * transport's environment; this module never touches `process.env`.
19
+ *
20
+ * `tests/paseo-sdk-transport.test.ts` type-checks this text against the public
21
+ * TypeScript surface and runs it as a real process, so keep it valid JavaScript
22
+ * with JSDoc types and free of backticks and template substitutions.
23
+ */
24
+ export const WORKER_SOURCE = String.raw`import { readFile, realpath } from "node:fs/promises";
25
+ import path from "node:path";
26
+ import { pathToFileURL } from "node:url";
27
+ import { createInterface } from "node:readline";
28
+
29
+ const EXPLICIT_ENTRY = process.argv[1];
30
+
31
+ /** @param {string} candidate @returns {Promise<string | undefined>} */
32
+ async function realFile(candidate) {
33
+ try { return await realpath(candidate); } catch { return undefined; }
34
+ }
35
+
36
+ /** @param {string} name @returns {Promise<string | undefined>} */
37
+ async function findOnPath(name) {
38
+ const extensions = process.platform === "win32" ? [".cmd", ".exe", ".bat", ""] : [""];
39
+ for (const directory of (process.env.PATH || "").split(path.delimiter)) {
40
+ if (directory === "") continue;
41
+ for (const extension of extensions) {
42
+ const found = await realFile(path.join(directory, name + extension));
43
+ if (found !== undefined) return found;
44
+ }
45
+ }
46
+ return undefined;
47
+ }
48
+
49
+ /** @param {string} bin @returns {Promise<{ root: string, manifest: any } | undefined>} */
50
+ async function packageRoot(bin) {
51
+ let directory = path.dirname(bin);
52
+ for (;;) {
53
+ try {
54
+ const manifest = JSON.parse(await readFile(path.join(directory, "package.json"), "utf8"));
55
+ if (manifest.name === "@astrosheep/keiyaku") return { root: directory, manifest };
56
+ } catch {}
57
+ const parent = path.dirname(directory);
58
+ if (parent === directory) return undefined;
59
+ directory = parent;
60
+ }
61
+ }
62
+
63
+ /** @param {string} root @param {any} manifest @returns {string | undefined} */
64
+ function declaredEntry(root, manifest) {
65
+ const main = manifest.exports === undefined ? undefined : manifest.exports["."];
66
+ const relative = typeof main === "string" ? main : main === undefined ? undefined : main.import || main.default;
67
+ const chosen = relative === undefined ? manifest.main : relative;
68
+ return typeof chosen === "string" ? pathToFileURL(path.join(root, chosen)).href : undefined;
69
+ }
70
+
71
+ /** @returns {Promise<string>} */
72
+ async function resolveEntry() {
73
+ if (EXPLICIT_ENTRY !== undefined && EXPLICIT_ENTRY !== "") {
74
+ return EXPLICIT_ENTRY.startsWith("file:") ? EXPLICIT_ENTRY : pathToFileURL(path.resolve(EXPLICIT_ENTRY)).href;
75
+ }
76
+ const bin = await findOnPath("keiyaku");
77
+ if (bin === undefined) throw new Error("the keiyaku executable is not on PATH; the Paseo bridge cannot locate its public package");
78
+ const found = await packageRoot(bin);
79
+ if (found === undefined) throw new Error("no @astrosheep/keiyaku package owns " + bin);
80
+ const entry = declaredEntry(found.root, found.manifest);
81
+ if (entry === undefined) throw new Error("the installed @astrosheep/keiyaku package declares no public entry");
82
+ return entry;
83
+ }
84
+
85
+ /** @returns {Promise<typeof import("@astrosheep/keiyaku")>} */
86
+ async function loadSdk() {
87
+ const entry = await resolveEntry();
88
+ /** @type {typeof import("@astrosheep/keiyaku")} */
89
+ const sdk = await import(entry);
90
+ if (typeof sdk.Akuma?.select !== "function") {
91
+ throw new Error("the public @astrosheep/keiyaku entry does not expose Akuma.select");
92
+ }
93
+ return sdk;
94
+ }
95
+
96
+ const boot = loadSdk();
97
+ // A boot that fails before the first request still belongs to those requests;
98
+ // keep the rejection handled until one of them observes it.
99
+ boot.catch(() => {});
100
+
101
+ /** @type {Map<number, AbortController>} */
102
+ const controllers = new Map();
103
+
104
+ /**
105
+ * One live observation of an Akuma's current window. The iterator is undefined
106
+ * only while the opening request is still resolving the public package.
107
+ * @typedef {{ controller: AbortController, iterator: AsyncIterator<unknown> | undefined }} Subscription
108
+ */
109
+ /** @type {Map<number, Subscription>} */
110
+ const subscriptions = new Map();
111
+
112
+ /**
113
+ * The installed public package may predate the streaming surface, so probe the
114
+ * capability at runtime instead of assuming this bridge's own vintage.
115
+ * @param {unknown} value @returns {value is { stream(options: { signal: AbortSignal }): AsyncIterable<unknown> }}
116
+ */
117
+ function exposesStream(value) {
118
+ return typeof value === "object" && value !== null &&
119
+ typeof (/** @type {{ stream?: unknown }} */ (value)).stream === "function";
120
+ }
121
+
122
+ /**
123
+ * The model configuration a public status reports, when it reports one. An
124
+ * installed package that carries no model in status stays unknown here; the
125
+ * bridge never invents a configuration from its own defaults.
126
+ * @param {unknown} status @returns {{ provider?: string, model?: string }}
127
+ */
128
+ function modelConfiguration(status) {
129
+ if (typeof status !== "object" || status === null) return {};
130
+ const fields = /** @type {Record<string, unknown>} */ (status);
131
+ return {
132
+ ...(typeof fields.provider === "string" ? { provider: fields.provider } : {}),
133
+ ...(typeof fields.model === "string" ? { model: fields.model } : {}),
134
+ };
135
+ }
136
+
137
+ /** @param {any} message */
138
+ function respond(message) {
139
+ process.stdout.write(JSON.stringify(message) + "\n");
140
+ }
141
+
142
+ /** @param {unknown} error @returns {string} */
143
+ function messageOf(error) {
144
+ return error instanceof Error ? error.message : String(error);
145
+ }
146
+
147
+ /**
148
+ * One request through the public operations only. Every value is the
149
+ * producer's own admission, evidence, page, or status projection.
150
+ * @param {any} request @param {typeof import("@astrosheep/keiyaku")} sdk @param {AbortController} controller @returns {Promise<any>}
151
+ */
152
+ async function operate(request, sdk, controller) {
153
+ const akuma = sdk.Akuma.select(request.world, request.aku);
154
+ if (request.op === "tell") return await akuma.tell(request.text, { signal: controller.signal });
155
+ if (request.op === "stop") return { evidence: await akuma.kill({ signal: controller.signal }) };
156
+ if (request.op === "status") return modelConfiguration(await akuma.status());
157
+ throw new Error("unsupported Keiyaku SDK operation: " + String(request.op));
158
+ }
159
+
160
+ /** @param {string} line */
161
+ async function dispatch(line) {
162
+ /** @type {any} */
163
+ let request;
164
+ try { request = JSON.parse(line); } catch { return; }
165
+ if (request === null || typeof request !== "object" || typeof request.id !== "number") return;
166
+ if (request.op === "cancel") {
167
+ const cancelled = controllers.get(request.target);
168
+ if (cancelled !== undefined) cancelled.abort();
169
+ return;
170
+ }
171
+ // A stream subscription outlives its opening request: the pull requests that
172
+ // follow carry the subscription's own identity, so ordinary requests keep
173
+ // their own lifetime and cancellation never reaches an unrelated observation.
174
+ if (request.op === "stream") {
175
+ const controller = new AbortController();
176
+ subscriptions.set(request.id, { controller, iterator: undefined });
177
+ try {
178
+ const sdk = await boot;
179
+ const akuma = sdk.Akuma.select(request.world, request.aku);
180
+ if (!exposesStream(akuma)) {
181
+ throw new Error("the installed @astrosheep/keiyaku package does not expose Akuma.stream; " +
182
+ "the Paseo bridge cannot observe this Akuma continuously");
183
+ }
184
+ if (controller.signal.aborted) throw new Error("Keiyaku SDK stream was cancelled before it opened");
185
+ const observed = akuma.stream({ signal: controller.signal })[Symbol.asyncIterator]();
186
+ subscriptions.set(request.id, { controller, iterator: observed });
187
+ respond({ id: request.id, ok: true, value: { subscription: request.id } });
188
+ } catch (error) {
189
+ subscriptions.delete(request.id);
190
+ controller.abort();
191
+ respond({ id: request.id, ok: false, error: { message: messageOf(error) } });
192
+ }
193
+ return;
194
+ }
195
+ if (request.op === "stream-pull") {
196
+ const subscription = subscriptions.get(request.target);
197
+ if (subscription === undefined || subscription.iterator === undefined) {
198
+ respond({ id: request.id, ok: false, error: { message: "Keiyaku SDK stream subscription is not open" } });
199
+ return;
200
+ }
201
+ try {
202
+ const frame = await subscription.iterator.next();
203
+ // A released subscription discards its late frame instead of publishing it
204
+ // into whatever observation the caller opened next.
205
+ if (subscriptions.get(request.target) !== subscription) throw new Error("Keiyaku SDK stream subscription was released");
206
+ respond({ id: request.id, ok: true, value: frame.done === true ? { done: true } : { done: false, history: frame.value } });
207
+ } catch (error) {
208
+ respond({ id: request.id, ok: false, error: { message: messageOf(error) } });
209
+ }
210
+ return;
211
+ }
212
+ if (request.op === "stream-cancel") {
213
+ const subscription = subscriptions.get(request.target);
214
+ if (subscription !== undefined) {
215
+ subscriptions.delete(request.target);
216
+ subscription.controller.abort();
217
+ }
218
+ respond({ id: request.id, ok: true, value: null });
219
+ return;
220
+ }
221
+ const controller = new AbortController();
222
+ controllers.set(request.id, controller);
223
+ try {
224
+ const sdk = await boot;
225
+ if (controller.signal.aborted) throw new Error("Keiyaku SDK request was cancelled before it started");
226
+ respond({ id: request.id, ok: true, value: await operate(request, sdk, controller) });
227
+ } catch (error) {
228
+ respond({ id: request.id, ok: false, error: { message: messageOf(error) } });
229
+ } finally {
230
+ controllers.delete(request.id);
231
+ }
232
+ }
233
+
234
+ const lines = createInterface({ input: process.stdin, crlfDelay: Infinity });
235
+ lines.on("line", (line) => { void dispatch(line); });
236
+ lines.on("close", () => { process.exit(0); });
237
+ `;
@@ -0,0 +1,289 @@
1
+ import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
2
+ import { z } from "zod";
3
+ import { historySchema, identityKeys, type History } from "./keiyaku.js";
4
+ import { tellReceiptSchema } from "./message.js";
5
+ import { WORKER_SOURCE } from "./sdk-worker.js";
6
+ import { stopAttempt } from "./stop.js";
7
+ import type { AkumaTransport } from "./transport.js";
8
+
9
+ const DEFAULT_TIMEOUT_MS = 30_000;
10
+ const STDERR_LIMIT = 8 * 1024;
11
+
12
+ /**
13
+ * How one request behaves when it is abandoned or when its worker retires. A
14
+ * bounded ordinary request carries its own id and its own outcome; an unbounded
15
+ * stream frame carries the subscription it belongs to, so releasing it cancels
16
+ * that observation alone and says what became of the frame.
17
+ */
18
+ type RequestMode = Readonly<{ timeout: boolean; subscription: boolean; tail: string }>;
19
+ const ORDINARY_REQUEST: RequestMode = {
20
+ timeout: true, subscription: false, tail: "its outcome is uncertain",
21
+ };
22
+ /** Opening a subscription is one bounded handshake naming the observation to serve. */
23
+ const OPEN_STREAM: RequestMode = { timeout: true, subscription: true, tail: "the observation was released" };
24
+ /** An idle frame has no bound: waiting for the next change is the stream's whole job. */
25
+ const STREAM_FRAME: RequestMode = { timeout: false, subscription: true, tail: "the observation was released" };
26
+
27
+ const workerResponse = z.union([
28
+ z.object({ id: z.number().int().positive(), ok: z.literal(true), value: z.unknown() }),
29
+ z.object({ id: z.number().int().positive(), ok: z.literal(false), error: z.object({ message: z.string() }) }),
30
+ ]);
31
+ const statusObservation = z.object({ provider: z.string().optional(), model: z.string().optional() });
32
+ const stopEvidence = z.object({ evidence: z.string() });
33
+
34
+ export type SdkTransportOptions = Readonly<{
35
+ /** An explicit public-entry file or URL; discovery through PATH is the production path. */
36
+ entry?: string;
37
+ /** One request's bound. A mutation that outlives it is uncertain, never retried. */
38
+ timeoutMs?: number;
39
+ }>;
40
+
41
+ /**
42
+ * The identity-free environment the worker inherits. The worker runs the
43
+ * public operations in its own process, so inherited harness, Square, and
44
+ * Keiyaku caller identities are removed rather than inherited or replaced. The
45
+ * shared process environment is never mutated.
46
+ */
47
+ export function cleanEnvironment(source: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
48
+ const env: NodeJS.ProcessEnv = { ...source };
49
+ for (const key of identityKeys) delete env[key];
50
+ return env;
51
+ }
52
+
53
+ function diagnostic(error: unknown): string {
54
+ return error instanceof Error ? error.message : String(error);
55
+ }
56
+
57
+ type Pending = {
58
+ /** The worker that owns this request; another worker's exit never settles it. */
59
+ worker: ChildProcessWithoutNullStreams;
60
+ resolve(value: unknown): void;
61
+ reject(error: Error): void;
62
+ /** Drop every listener and timer this request added, once it has settled. */
63
+ release(): void;
64
+ timer: NodeJS.Timeout | undefined;
65
+ /** How this request's disappearance is spoken about when it never settles itself. */
66
+ tail: string;
67
+ };
68
+
69
+ /**
70
+ * The dedicated public-SDK transport. One worker process serves every request
71
+ * over a typed line protocol; requests carry their own cancellation and never
72
+ * terminate one another. A worker that exits or fails to start settles only the
73
+ * requests it owned, and the next request starts a fresh one.
74
+ */
75
+ export function createSdkTransport(options: SdkTransportOptions = {}): AkumaTransport {
76
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
77
+ let child: ChildProcessWithoutNullStreams | undefined;
78
+ let nextId = 1;
79
+ let buffered = "";
80
+ let stderr = "";
81
+ let closed = false;
82
+ const pending = new Map<number, Pending>();
83
+
84
+ function settle(id: number, entry: Pending, outcome: Readonly<{ value: unknown }> | Readonly<{ error: Error }>): void {
85
+ if (pending.get(id) !== entry) return;
86
+ pending.delete(id);
87
+ entry.release();
88
+ if ("error" in outcome) entry.reject(outcome.error);
89
+ else entry.resolve(outcome.value);
90
+ }
91
+
92
+ function retire(worker: ChildProcessWithoutNullStreams, diagnostic: (entry: Pending) => string): void {
93
+ if (child === worker) child = undefined;
94
+ for (const [id, entry] of [...pending]) if (entry.worker === worker) settle(id, entry, { error: new Error(diagnostic(entry)) });
95
+ }
96
+
97
+ function ensure(): ChildProcessWithoutNullStreams {
98
+ if (child !== undefined) return child;
99
+ const args = ["--input-type=module", "-e", WORKER_SOURCE];
100
+ if (options.entry !== undefined) args.push(options.entry);
101
+ const spawned = spawn(process.execPath, args, { env: cleanEnvironment(), stdio: "pipe" });
102
+ child = spawned;
103
+ buffered = "";
104
+ stderr = "";
105
+ spawned.stdout.setEncoding("utf8");
106
+ spawned.stderr.setEncoding("utf8");
107
+ spawned.stdout.on("data", (chunk: string) => { buffered += chunk; read(); });
108
+ spawned.stderr.on("data", (chunk: string) => {
109
+ stderr = (stderr + chunk).slice(-STDERR_LIMIT);
110
+ process.stderr.write(chunk);
111
+ });
112
+ // EPIPE can race a worker that is already retiring; the exit event owns its diagnostic.
113
+ spawned.stdin.on("error", () => {});
114
+ spawned.on("error", (error) => retire(spawned,
115
+ (entry) => `Keiyaku SDK worker failed to start: ${diagnostic(error)}; ${entry.tail}`));
116
+ spawned.on("exit", (code, signal) => retire(spawned, (entry) =>
117
+ `Keiyaku SDK worker exited (${signal === null ? String(code) : signal})` +
118
+ `${stderr.trim() === "" ? "" : `: ${stderr.trim()}`}; ${entry.tail}`));
119
+ return spawned;
120
+ }
121
+
122
+ function read(): void {
123
+ for (;;) {
124
+ const newline = buffered.indexOf("\n");
125
+ if (newline < 0) return;
126
+ const line = buffered.slice(0, newline);
127
+ buffered = buffered.slice(newline + 1);
128
+ if (line.trim() === "") continue;
129
+ const decoded = workerResponse.safeParse((() => { try { return JSON.parse(line); } catch { return undefined; } })());
130
+ if (!decoded.success) {
131
+ process.stderr.write("Keiyaku SDK worker wrote an unrecognized response line\n");
132
+ continue;
133
+ }
134
+ const entry = pending.get(decoded.data.id);
135
+ if (entry === undefined) continue;
136
+ settle(decoded.data.id, entry, decoded.data.ok ? { value: decoded.data.value } : { error: new Error(decoded.data.error.message) });
137
+ }
138
+ }
139
+
140
+ function request(op: string, payload: Record<string, unknown>, signal: AbortSignal, mode: RequestMode = ORDINARY_REQUEST): Promise<unknown> {
141
+ signal.throwIfAborted();
142
+ if (closed) return Promise.reject(new Error("Keiyaku SDK transport is closed"));
143
+ const worker = ensure();
144
+ const id = nextId++;
145
+ // A stream request names the subscription it serves; every other request is
146
+ // its own subject.
147
+ const target = mode.subscription && typeof payload.target === "number" ? payload.target : id;
148
+ return new Promise<unknown>((resolve, reject) => {
149
+ const cancel = () => {
150
+ if (!worker.stdin.writable) return;
151
+ worker.stdin.write(JSON.stringify(mode.subscription
152
+ ? { id, op: "stream-cancel", target }
153
+ : { id, op: "cancel", target: id }) + "\n");
154
+ };
155
+ const entry: Pending = {
156
+ worker, resolve, reject, tail: mode.tail,
157
+ release: () => { clearTimeout(entry.timer); signal.removeEventListener("abort", abandon); },
158
+ timer: undefined,
159
+ };
160
+ // A cancelled request tells the worker to stop observing it. The worker
161
+ // keeps serving every other request, and an observation of its own is
162
+ // released without reaching any unrelated one.
163
+ const abandon = () => {
164
+ cancel();
165
+ settle(id, entry, { error: signal.reason instanceof Error ? signal.reason : new Error("Keiyaku SDK request was cancelled") });
166
+ };
167
+ if (mode.timeout) entry.timer = setTimeout(() => {
168
+ cancel();
169
+ settle(id, entry, { error: new Error(`Keiyaku SDK request timed out after ${timeoutMs}ms; any admitted work's outcome is uncertain`) });
170
+ }, timeoutMs);
171
+ pending.set(id, entry);
172
+ signal.addEventListener("abort", abandon, { once: true });
173
+ worker.stdin.write(JSON.stringify({ id, op, ...payload }) + "\n", (error) => {
174
+ if (error) settle(id, entry, { error: new Error(`Keiyaku SDK worker is not writable: ${diagnostic(error)}`) });
175
+ });
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Release one subscription without owning its response: the observation's
181
+ * frames are already worthless to the caller that gave it up, and the worker
182
+ * answers the cancel itself.
183
+ */
184
+ function releaseSubscription(target: number): void {
185
+ if (closed) return;
186
+ const worker = child;
187
+ if (worker === undefined || !worker.stdin.writable) return;
188
+ worker.stdin.write(JSON.stringify({ id: nextId++, op: "stream-cancel", target }) + "\n", () => {});
189
+ }
190
+
191
+ const tell: AkumaTransport["tell"] = async (world, akuId, signal, text) => {
192
+ const value = await request("tell", { world, aku: akuId, text }, signal);
193
+ const decoded = tellReceiptSchema.parse(value);
194
+ if (decoded.akuma !== akuId || decoded.tell.admission.tellId !== decoded.tell.row.tellId || decoded.tell.row.text !== text)
195
+ throw new Error("Keiyaku Tell outcome is uncertain: the admission receipt did not name the submitted Tell");
196
+ return decoded.tell;
197
+ };
198
+
199
+ const stop: AkumaTransport["stop"] = async (world, akuId, signal) => {
200
+ const value = await request("stop", { world, aku: akuId }, signal);
201
+ return stopAttempt(stopEvidence.parse(value).evidence, akuId);
202
+ };
203
+
204
+ const status: AkumaTransport["status"] = async (world, akuId, signal) => {
205
+ const decoded = statusObservation.parse(await request("status", { world, aku: akuId }, signal));
206
+ return {
207
+ ...(decoded.provider === undefined ? {} : { provider: decoded.provider }),
208
+ ...(decoded.model === undefined ? {} : { model: decoded.model }),
209
+ };
210
+ };
211
+
212
+ /**
213
+ * One pull-backed observation. The subscription is established by the first
214
+ * pull, each pull has at most one outstanding frame, and abandoning the
215
+ * caller's signal or the iterator releases exactly this subscription.
216
+ */
217
+ const stream: AkumaTransport["stream"] = (world, akuId, signal) => ({
218
+ [Symbol.asyncIterator](): AsyncIterator<History> {
219
+ let subscription: number | undefined;
220
+ let released = false;
221
+ let finished = false;
222
+ let pulling = false;
223
+ const release = (): void => {
224
+ const target = subscription;
225
+ subscription = undefined;
226
+ if (target !== undefined) releaseSubscription(target);
227
+ };
228
+ // Abort releases the observation even while the caller is not pulling.
229
+ const abandon = () => { released = true; release(); };
230
+ signal.addEventListener("abort", abandon, { once: true });
231
+ const unwind = (): void => { signal.removeEventListener("abort", abandon); release(); };
232
+ return {
233
+ async next(): Promise<IteratorResult<History>> {
234
+ // One frame at a time is the transport's own contract, not a habit of
235
+ // its caller: a second pull here would put two frames in flight.
236
+ if (pulling)
237
+ throw new Error("Keiyaku SDK stream subscription already has a frame in flight; pull one frame at a time");
238
+ pulling = true;
239
+ try {
240
+ if (finished || released || signal.aborted) { unwind(); return { done: true, value: undefined }; }
241
+ if (subscription === undefined) {
242
+ const opened = z.object({ subscription: z.number().int().positive() })
243
+ .parse(await request("stream", { world, aku: akuId }, signal, OPEN_STREAM));
244
+ subscription = opened.subscription;
245
+ }
246
+ const frame = z.union([z.object({ done: z.literal(true) }),
247
+ z.object({ done: z.literal(false), history: z.unknown() })])
248
+ .parse(await request("stream-pull", { target: subscription }, signal, STREAM_FRAME));
249
+ if (frame.done) { unwind(); return { done: true, value: undefined }; }
250
+ return { done: false, value: historySchema.parse(frame.history) };
251
+ } catch (error) {
252
+ // A failed or released observation is over: it never silently reopens
253
+ // a subscription the caller did not ask for again.
254
+ finished = true;
255
+ unwind();
256
+ throw error;
257
+ } finally {
258
+ pulling = false;
259
+ }
260
+ },
261
+ async return(): Promise<IteratorResult<History>> {
262
+ finished = true;
263
+ unwind();
264
+ return { done: true, value: undefined };
265
+ },
266
+ async throw(error?: unknown): Promise<IteratorResult<History>> { finished = true; unwind(); throw error; },
267
+ };
268
+ },
269
+ });
270
+
271
+ async function close(): Promise<void> {
272
+ if (closed) return;
273
+ closed = true;
274
+ const worker = child;
275
+ child = undefined;
276
+ for (const [id, entry] of [...pending])
277
+ settle(id, entry, { error: new Error(`Keiyaku SDK transport closed while the request was in flight; ${entry.tail}`) });
278
+ if (worker === undefined) return;
279
+ if (worker.exitCode !== null || worker.signalCode !== null) return;
280
+ await new Promise<void>((resolve) => {
281
+ const force = setTimeout(() => worker.kill("SIGKILL"), 2_000);
282
+ worker.once("close", () => { clearTimeout(force); resolve(); });
283
+ try { worker.stdin.end(); } catch { /* the worker may already be gone */ }
284
+ worker.kill("SIGTERM");
285
+ });
286
+ }
287
+
288
+ return Object.freeze({ stream, tell, stop, status, close });
289
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * One Stop attempt's honest verdict. Only `stopped` proves the Akuma's current
3
+ * execution settled; `not-stopped` carries the public evidence's own refusal or
4
+ * unsettled outcome, and `uncertain` admits that no decided answer exists — a
5
+ * timeout or lost receipt never proves either outcome.
6
+ */
7
+ export type StopAttempt =
8
+ | Readonly<{ kind: "stopped" }>
9
+ | Readonly<{ kind: "not-stopped"; message: string }>
10
+ | Readonly<{ kind: "uncertain"; message: string }>;
11
+
12
+ const settledEvidence = new Set(["killed", "already-killed", "already-stopped"]);
13
+
14
+ /**
15
+ * One public kill evidence's honest verdict. Only settled evidence proves the
16
+ * Akuma's current execution ended; every other evidence names a refusal or an
17
+ * unsettled outcome in Keiyaku's own words.
18
+ */
19
+ export function stopAttempt(evidence: string, akuId: string): StopAttempt {
20
+ return settledEvidence.has(evidence)
21
+ ? { kind: "stopped" }
22
+ : { kind: "not-stopped", message: `Keiyaku reported ${evidence} for ${akuId}` };
23
+ }
24
+
25
+ /** Stop one Akuma and return its verdict; transport failure rejects. */
26
+ export type Stop = (world: string, akuId: string, signal: AbortSignal) => Promise<StopAttempt>;
@@ -0,0 +1,35 @@
1
+ import type { History } from "./keiyaku.js";
2
+ import type { Tell } from "./message.js";
3
+ import type { Stop } from "./stop.js";
4
+
5
+ /** The public Akuma configuration one read-only observation reports, when it is known. */
6
+ export type StatusObservation = Readonly<{ provider?: string; model?: string }>;
7
+ /** Read one Akuma's public status projection for display; failure rejects. */
8
+ export type StatusReader = (world: string, akuId: string, signal: AbortSignal) => Promise<StatusObservation>;
9
+ /**
10
+ * Observe one Akuma's current retained window continuously, including while it is
11
+ * idle and across later Turns. Each frame is a complete bounded window, never a
12
+ * delta; the subscription releases its reader when the caller stops pulling or
13
+ * aborts. Failure rejects the pull that saw it and ends that subscription.
14
+ */
15
+ export type Stream = (world: string, akuId: string, signal: AbortSignal) => AsyncIterable<History>;
16
+
17
+ /**
18
+ * The one typed boundary between the Paseo bridge and the Keiyaku public
19
+ * surface. It replaces the earlier CLI-shaped executor: no command strings, no
20
+ * stdout JSON, no exit-code interpretation. Every operation carries its World
21
+ * and Akuma coordinates, observes the caller's cancellation, and rejects with
22
+ * its own diagnostic when it cannot answer; mutation outcomes are only ever the
23
+ * public operations' own receipts.
24
+ *
25
+ * `close` owns the transport's whole lifecycle: it refuses new requests and
26
+ * settles every request still in flight as uncertain, but a single request's
27
+ * cancellation must never terminate unrelated work.
28
+ */
29
+ export type AkumaTransport = Readonly<{
30
+ stream: Stream;
31
+ tell: Tell;
32
+ stop: Stop;
33
+ status: StatusReader;
34
+ close(): Promise<void>;
35
+ }>;
@@ -0,0 +1,10 @@
1
+ import { defineRpc } from "@getpaseo/plugin";
2
+ import { z } from "zod";
3
+ export const attachRpc = defineRpc({ name: "attach",
4
+ input: z.object({ world: z.string(), akuId: z.string(), caller: z.union([
5
+ z.object({ kind: z.literal("akuma"), world: z.string(), akuId: z.string() }),
6
+ z.object({ kind: z.literal("external"), integration: z.string(), id: z.string() }),
7
+ z.object({ kind: z.literal("unknown") }),
8
+ ]).optional() }),
9
+ output: z.object({ attached: z.boolean() }),
10
+ });
@@ -0,0 +1,6 @@
1
+ import { defineRpc } from "@getpaseo/plugin";
2
+ import { z } from "zod";
3
+ export const refreshRpc = defineRpc({ name: "refresh",
4
+ input: z.object({ world: z.string(), akuId: z.string() }),
5
+ output: z.object({ woken: z.number().int().nonnegative() }),
6
+ });
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": { "noEmit": true, "rootDir": "." },
4
+ "include": ["**/*.ts"],
5
+ "exclude": []
6
+ }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: keiyaku
3
3
  description: >-
4
- Keiyaku is a CLI that records plans, writes handoff terms for deliveries,
4
+ Keiyaku is a CLI that records unresolved work, writes handoff terms for deliveries,
5
5
  and calls delegates. Use this skill when you need to figure out which
6
6
  Keiyaku skill or command to reach for.
7
7
  ---
@@ -10,8 +10,8 @@ description: >-
10
10
 
11
11
  ## Model
12
12
 
13
- Keiyaku is three independent pillars, not a bundle: **Task** remembers plans
14
- and dependencies, **Contract** makes acceptance standing and auditable,
13
+ Keiyaku is three independent pillars, not a bundle: **Debt** records unresolved
14
+ work and dependencies, **Contract** makes acceptance standing and auditable,
15
15
  **Akuma** invokes capability. Use any one alone; combine them only when the
16
16
  work calls for it. Behind every Contract in flight there is one holder of its
17
17
  fulfillment loop — whoever turns intent into commissions and returns into
@@ -23,9 +23,8 @@ equally. See `keiyaku-workflow` for the loop, `keiyaku-bind` for authoring,
23
23
  ## Quick Start
24
24
 
25
25
  ```bash
26
- keiyaku -C <repo> task add "title"
27
- keiyaku -C <repo> task start <task-id>
28
- keiyaku -C <repo> bind [--task <task-id>] -
26
+ keiyaku -C <repo> debt add "title"
27
+ keiyaku -C <repo> bind [--debt <debt-id>] -
29
28
  keiyaku -C <repo> status
30
29
  keiyaku -C <repo> deliver [<contract>|@<contract>]
31
30
  keiyaku -C <repo> review [<contract>|@<contract>] --satisfied
@@ -113,7 +113,7 @@ complete grammar.
113
113
  ## JavaScript automation
114
114
 
115
115
  Use [Automation With The Akuma API](references/automation.md) for a
116
- task-specific JavaScript coordinator, structured answers, or multi-worker
116
+ debt-specific JavaScript coordinator, structured answers, or multi-worker
117
117
  orchestration. The CLI is enough for one commission and its return. A
118
118
  prompt-free birth followed by one input-bound ask uses the same identity:
119
119