@astrosheep/keiyaku 4.0.1 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/README.md +137 -5
  2. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -35
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +98 -58
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +152 -53
  9. package/build/src/akuma/abort.d.ts +2 -0
  10. package/build/src/akuma/abort.js +50 -0
  11. package/build/src/akuma/akuma.d.ts +23 -14
  12. package/build/src/akuma/akuma.js +120 -104
  13. package/build/src/akuma/archetype.d.ts +7 -6
  14. package/build/src/akuma/archetype.js +68 -41
  15. package/build/src/akuma/body.d.ts +3 -5
  16. package/build/src/akuma/body.js +408 -213
  17. package/build/src/akuma/heart/facts.d.ts +25 -34
  18. package/build/src/akuma/heart/facts.js +18 -7
  19. package/build/src/akuma/heart/index.d.ts +39 -42
  20. package/build/src/akuma/heart/index.js +134 -93
  21. package/build/src/akuma/heart/rows.d.ts +9 -31
  22. package/build/src/akuma/heart/rows.js +26 -44
  23. package/build/src/akuma/heart/schema.d.ts +1 -1
  24. package/build/src/akuma/heart/schema.js +7 -15
  25. package/build/src/akuma/heart/soul.d.ts +8 -0
  26. package/build/src/akuma/heart/soul.js +136 -0
  27. package/build/src/akuma/heart/storage.d.ts +28 -14
  28. package/build/src/akuma/heart/storage.js +86 -26
  29. package/build/src/akuma/identity.d.ts +2 -2
  30. package/build/src/akuma/identity.js +9 -9
  31. package/build/src/akuma/index.d.ts +6 -1
  32. package/build/src/akuma/index.js +6 -0
  33. package/build/src/akuma/projection.d.ts +117 -38
  34. package/build/src/akuma/projection.js +208 -97
  35. package/build/src/akuma/provider-recipe.d.ts +24 -0
  36. package/build/src/akuma/provider-recipe.js +100 -0
  37. package/build/src/akuma/provider.d.ts +12 -4
  38. package/build/src/akuma/provider.js +117 -70
  39. package/build/src/akuma/providers/acp/core.d.ts +19 -0
  40. package/build/src/akuma/providers/acp/core.js +159 -0
  41. package/build/src/akuma/providers/acp/events.d.ts +20 -0
  42. package/build/src/akuma/providers/acp/events.js +73 -0
  43. package/build/src/akuma/providers/acp/index.d.ts +12 -0
  44. package/build/src/akuma/providers/acp/index.js +94 -0
  45. package/build/src/akuma/providers/claude/events.js +66 -13
  46. package/build/src/akuma/providers/claude/index.d.ts +2 -1
  47. package/build/src/akuma/providers/claude/index.js +35 -21
  48. package/build/src/akuma/providers/codex-app-server/events.js +4 -1
  49. package/build/src/akuma/providers/codex-app-server/index.d.ts +3 -3
  50. package/build/src/akuma/providers/codex-app-server/index.js +24 -31
  51. package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
  52. package/build/src/akuma/providers/grok-build/index.js +70 -0
  53. package/build/src/akuma/providers/index.d.ts +5 -3
  54. package/build/src/akuma/providers/index.js +24 -59
  55. package/build/src/akuma/providers/opencode-sdk/events.d.ts +40 -34
  56. package/build/src/akuma/providers/opencode-sdk/events.js +220 -101
  57. package/build/src/akuma/providers/opencode-sdk/index.d.ts +3 -2
  58. package/build/src/akuma/providers/opencode-sdk/index.js +233 -128
  59. package/build/src/akuma/providers/opencode-sdk/session.d.ts +11 -26
  60. package/build/src/akuma/providers/opencode-sdk/session.js +10 -9
  61. package/build/src/akuma/providers/pi/events.js +49 -11
  62. package/build/src/akuma/providers/pi/index.d.ts +1 -1
  63. package/build/src/akuma/providers/pi/index.js +50 -21
  64. package/build/src/akuma/publication.d.ts +3 -3
  65. package/build/src/akuma/publication.js +28 -40
  66. package/build/src/akuma/requests.d.ts +9 -7
  67. package/build/src/akuma/requests.js +92 -65
  68. package/build/src/alias/index.d.ts +2 -2
  69. package/build/src/alias/index.js +10 -10
  70. package/build/src/body/decode.js +7 -1
  71. package/build/src/body/region.d.ts +2 -0
  72. package/build/src/body/region.js +77 -7
  73. package/build/src/cli/accepted.d.ts +17 -13
  74. package/build/src/cli/accepted.js +72 -34
  75. package/build/src/cli/actor.js +3 -3
  76. package/build/src/cli/commands/akuma-invoke.d.ts +4 -3
  77. package/build/src/cli/commands/akuma-invoke.js +14 -9
  78. package/build/src/cli/commands/akuma.d.ts +13 -3
  79. package/build/src/cli/commands/akuma.js +67 -34
  80. package/build/src/cli/commands/contract.d.ts +18 -5
  81. package/build/src/cli/commands/contract.js +9 -3
  82. package/build/src/cli/commands/task-invoke.d.ts +17 -4
  83. package/build/src/cli/commands/task-invoke.js +43 -20
  84. package/build/src/cli/commands/task-query.d.ts +5 -0
  85. package/build/src/cli/commands/task-query.js +247 -0
  86. package/build/src/cli/commands/task.d.ts +3 -1
  87. package/build/src/cli/commands/task.js +51 -13
  88. package/build/src/cli/coordinates.d.ts +18 -0
  89. package/build/src/cli/coordinates.js +120 -0
  90. package/build/src/cli/draft.d.ts +8 -0
  91. package/build/src/cli/draft.js +96 -0
  92. package/build/src/cli/invoke.d.ts +4 -3
  93. package/build/src/cli/invoke.js +160 -179
  94. package/build/src/cli/main.js +34 -6
  95. package/build/src/cli/parse.d.ts +9 -2
  96. package/build/src/cli/parse.js +29 -7
  97. package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
  98. package/build/src/cli/render/akuma-tool-command.js +144 -0
  99. package/build/src/cli/render/akuma-tool.d.ts +2 -2
  100. package/build/src/cli/render/akuma-tool.js +30 -4
  101. package/build/src/cli/render/akuma.d.ts +1 -0
  102. package/build/src/cli/render/akuma.js +211 -116
  103. package/build/src/cli/render/audit.d.ts +3 -0
  104. package/build/src/cli/render/audit.js +104 -0
  105. package/build/src/cli/render/contract.d.ts +3 -3
  106. package/build/src/cli/render/contract.js +192 -79
  107. package/build/src/cli/render/kanshi.js +263 -168
  108. package/build/src/cli/render/receipt.d.ts +19 -0
  109. package/build/src/cli/render/receipt.js +106 -0
  110. package/build/src/cli/render/refusal.d.ts +16 -2
  111. package/build/src/cli/render/refusal.js +88 -20
  112. package/build/src/cli/render/region.d.ts +2 -0
  113. package/build/src/cli/render/region.js +17 -0
  114. package/build/src/cli/render/task.d.ts +2 -1
  115. package/build/src/cli/render/task.js +223 -60
  116. package/build/src/cli/render/terminal.d.ts +7 -0
  117. package/build/src/cli/render/terminal.js +46 -0
  118. package/build/src/cli/render/text.js +6 -3
  119. package/build/src/cli/result.d.ts +115 -20
  120. package/build/src/cli/usage.d.ts +1 -0
  121. package/build/src/cli/usage.js +3 -0
  122. package/build/src/contract-worktree.d.ts +6 -5
  123. package/build/src/contract-worktree.js +95 -57
  124. package/build/src/coordination/durable-file.d.ts +4 -3
  125. package/build/src/coordination/durable-file.js +29 -25
  126. package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
  127. package/build/src/coordination/sqlite-transaction-lock.js +22 -5
  128. package/build/src/core/facts/fold.js +1 -6
  129. package/build/src/core/facts/gate.d.ts +1 -0
  130. package/build/src/core/facts/gate.js +1 -0
  131. package/build/src/core/verbs/amend.d.ts +1 -1
  132. package/build/src/core/verbs/amend.js +0 -11
  133. package/build/src/core/verbs/deliver.d.ts +1 -1
  134. package/build/src/core/verbs/deliver.js +1 -7
  135. package/build/src/core/verbs/placement.d.ts +1 -1
  136. package/build/src/core/verbs/placement.js +4 -1
  137. package/build/src/dispatch/index.d.ts +2 -2
  138. package/build/src/dispatch/index.js +17 -17
  139. package/build/src/git/admission.d.ts +1 -1
  140. package/build/src/git/admission.js +11 -11
  141. package/build/src/git/hooks.js +13 -13
  142. package/build/src/git/integration.d.ts +14 -15
  143. package/build/src/git/integration.js +80 -44
  144. package/build/src/git/observe.d.ts +5 -3
  145. package/build/src/git/observe.js +18 -12
  146. package/build/src/git/read-observation.js +9 -9
  147. package/build/src/git/reconcile.d.ts +2 -0
  148. package/build/src/git/reconcile.js +138 -95
  149. package/build/src/git/repository.d.ts +27 -19
  150. package/build/src/git/repository.js +155 -83
  151. package/build/src/git/scratch.d.ts +7 -3
  152. package/build/src/git/scratch.js +44 -37
  153. package/build/src/git/target-placement.d.ts +40 -4
  154. package/build/src/git/target-placement.js +221 -101
  155. package/build/src/git/tender.d.ts +6 -5
  156. package/build/src/git/tender.js +24 -43
  157. package/build/src/git/terminal-seal.d.ts +1 -1
  158. package/build/src/git/terminal-seal.js +7 -5
  159. package/build/src/git/workspace.d.ts +36 -4
  160. package/build/src/git/workspace.js +53 -18
  161. package/build/src/identity/selector.js +1 -1
  162. package/build/src/index.d.ts +3 -3
  163. package/build/src/index.js +1 -1
  164. package/build/src/kanshi/index.d.ts +2 -2
  165. package/build/src/kanshi/index.js +1 -1
  166. package/build/src/kanshi/read.d.ts +2 -1
  167. package/build/src/kanshi/read.js +118 -27
  168. package/build/src/kanshi/report.d.ts +50 -0
  169. package/build/src/kanshi/select.d.ts +5 -0
  170. package/build/src/kanshi/select.js +32 -0
  171. package/build/src/library/address.d.ts +12 -3
  172. package/build/src/library/address.js +77 -46
  173. package/build/src/library/akuma-creation.d.ts +6 -1
  174. package/build/src/library/akuma-creation.js +86 -13
  175. package/build/src/library/audit.d.ts +18 -0
  176. package/build/src/library/audit.js +44 -0
  177. package/build/src/library/bind.js +4 -4
  178. package/build/src/library/catalog.d.ts +1 -1
  179. package/build/src/library/catalog.js +5 -8
  180. package/build/src/library/contract.d.ts +5 -6
  181. package/build/src/library/contract.js +11 -35
  182. package/build/src/library/delivery.d.ts +2 -1
  183. package/build/src/library/fleet.d.ts +2 -2
  184. package/build/src/library/fleet.js +35 -71
  185. package/build/src/library/keiyaku.d.ts +3 -2
  186. package/build/src/library/keiyaku.js +1 -0
  187. package/build/src/library/mutation.d.ts +7 -6
  188. package/build/src/library/mutation.js +23 -25
  189. package/build/src/library/reconcile.d.ts +29 -0
  190. package/build/src/library/reconcile.js +179 -0
  191. package/build/src/library/region.d.ts +12 -0
  192. package/build/src/library/region.js +15 -1
  193. package/build/src/library/repo.d.ts +4 -16
  194. package/build/src/library/repo.js +13 -38
  195. package/build/src/protocol/attempt.js +6 -6
  196. package/build/src/protocol/bind.js +4 -4
  197. package/build/src/protocol/intent.d.ts +15 -2
  198. package/build/src/protocol/intent.js +21 -6
  199. package/build/src/protocol/operations.d.ts +73 -22
  200. package/build/src/protocol/operations.js +292 -88
  201. package/build/src/protocol/outcome.d.ts +9 -3
  202. package/build/src/protocol/outcome.js +3 -1
  203. package/build/src/protocol/placement.d.ts +15 -4
  204. package/build/src/protocol/placement.js +58 -25
  205. package/build/src/protocol/read/status.d.ts +8 -1
  206. package/build/src/protocol/read/status.js +61 -14
  207. package/build/src/runtime/proc/line-rpc.d.ts +3 -8
  208. package/build/src/runtime/proc/line-rpc.js +16 -43
  209. package/build/src/runtime/proc/run.d.ts +15 -29
  210. package/build/src/runtime/proc/run.js +113 -105
  211. package/build/src/runtime/proc/stdio.d.ts +18 -0
  212. package/build/src/runtime/proc/stdio.js +58 -0
  213. package/build/src/settings.d.ts +2 -2
  214. package/build/src/settings.js +8 -8
  215. package/build/src/settlement/fence.d.ts +0 -4
  216. package/build/src/settlement/fence.js +0 -9
  217. package/build/src/settlement/holder.d.ts +11 -1
  218. package/build/src/settlement/holder.js +25 -0
  219. package/build/src/settlement/settle.d.ts +1 -1
  220. package/build/src/settlement/settle.js +52 -89
  221. package/build/src/task/board.d.ts +3 -3
  222. package/build/src/task/board.js +6 -9
  223. package/build/src/task/compose.d.ts +1 -1
  224. package/build/src/task/compose.js +18 -10
  225. package/build/src/task/context.d.ts +3 -3
  226. package/build/src/task/context.js +14 -14
  227. package/build/src/task/document.d.ts +2 -1
  228. package/build/src/task/document.js +15 -5
  229. package/build/src/task/index.d.ts +19 -7
  230. package/build/src/task/index.js +42 -23
  231. package/build/src/task/operations.d.ts +15 -9
  232. package/build/src/task/operations.js +78 -48
  233. package/build/src/task/query.d.ts +67 -0
  234. package/build/src/task/query.js +279 -0
  235. package/build/src/task/store.d.ts +2 -2
  236. package/build/src/task/store.js +40 -25
  237. package/build/src/verification/execution.d.ts +4 -2
  238. package/build/src/verification/execution.js +9 -5
  239. package/build/src/workspace-place.d.ts +41 -0
  240. package/build/src/workspace-place.js +386 -0
  241. package/build/src/world.d.ts +13 -2
  242. package/build/src/world.js +81 -48
  243. package/package.json +3 -2
  244. package/build/src/protocol/read/audit.d.ts +0 -31
  245. package/build/src/protocol/read/audit.js +0 -55
@@ -16,82 +16,110 @@ function object(value) {
16
16
  : null;
17
17
  }
18
18
  export { decodeResumeCoordinate, encodeResumeCoordinate };
19
- function optionText(options, field, blank) {
20
- const selected = options[field];
21
- if (selected === undefined)
19
+ function eventType(value) {
20
+ return typeof value === "string" && Object.hasOwn(AGENT_EVENT_TYPES, value);
21
+ }
22
+ const SEARCH_SCOPES = {
23
+ content: true,
24
+ files: true,
25
+ web: true,
26
+ };
27
+ function decodePositiveLine(value) {
28
+ if (value === undefined)
22
29
  return undefined;
23
- if (typeof selected !== "string" || (blank === "refuse" && selected.trim().length === 0)) {
24
- throw new TypeError(`provider option ${field} must be ${blank === "allow" ? "a string" : "a nonblank string"}`);
25
- }
26
- return selected;
30
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 ? value : null;
27
31
  }
28
- function optionEnum(options, field, allowed) {
29
- const selected = options[field];
30
- if (selected === undefined)
32
+ function decodeOptionalText(value) {
33
+ if (value === undefined)
31
34
  return undefined;
32
- if (typeof selected !== "string" || !allowed.includes(selected)) {
33
- throw new TypeError(`provider option ${field} must be ${allowed.join(", ")}`);
34
- }
35
- return selected;
36
- }
37
- export function decodeProviderOptions(value) {
38
- const options = object(value);
39
- if (options === null)
40
- throw new TypeError("provider options must be an object");
41
- const allowed = ["access", "effort", "model", "network", "systemPrompt"];
42
- const unknown = Object.keys(options).find((key) => !allowed.includes(key));
43
- if (unknown !== undefined)
44
- throw new TypeError(`provider options have unknown field ${unknown}`);
45
- const model = optionText(options, "model", "refuse");
46
- const effort = optionText(options, "effort", "refuse");
47
- const access = optionEnum(options, "access", ["read", "write", "auto"]);
48
- const network = optionEnum(options, "network", ["disabled", "enabled"]);
49
- const systemPrompt = optionText(options, "systemPrompt", "allow");
50
- return Object.freeze({
51
- ...(model === undefined ? {} : { model }),
52
- ...(effort === undefined ? {} : { effort }),
53
- ...(access === undefined ? {} : { access }),
54
- ...(network === undefined ? {} : { network }),
55
- ...(systemPrompt === undefined ? {} : { systemPrompt }),
56
- });
35
+ return typeof value === "string" ? value : null;
57
36
  }
58
- function eventType(value) {
59
- return typeof value === "string" && Object.hasOwn(AGENT_EVENT_TYPES, value);
37
+ function decodeSearchScope(value) {
38
+ if (value === undefined)
39
+ return undefined;
40
+ return typeof value === "string" && Object.hasOwn(SEARCH_SCOPES, value) ? value : null;
41
+ }
42
+ function decodeRunCall(call) {
43
+ return typeof call.command === "string" ? { kind: "run", command: call.command } : null;
44
+ }
45
+ function decodeReadCall(call) {
46
+ if (typeof call.path !== "string")
47
+ return null;
48
+ const offset = decodePositiveLine(call.offset);
49
+ const limit = decodePositiveLine(call.limit);
50
+ if (offset === null || limit === null)
51
+ return null;
52
+ return {
53
+ kind: "read",
54
+ path: call.path,
55
+ ...(offset === undefined ? {} : { offset }),
56
+ ...(limit === undefined ? {} : { limit }),
57
+ };
58
+ }
59
+ function decodeSearchCall(call) {
60
+ if (typeof call.query !== "string")
61
+ return null;
62
+ const scope = decodeSearchScope(call.scope);
63
+ const path = decodeOptionalText(call.path);
64
+ const glob = decodeOptionalText(call.glob);
65
+ if (scope === null || path === null || glob === null)
66
+ return null;
67
+ return {
68
+ kind: "search",
69
+ query: call.query,
70
+ ...(scope === undefined ? {} : { scope }),
71
+ ...(path === undefined ? {} : { path }),
72
+ ...(glob === undefined ? {} : { glob }),
73
+ };
74
+ }
75
+ function decodeNonnegativeCount(value) {
76
+ return Number.isSafeInteger(value) && value >= 0 ? value : null;
77
+ }
78
+ function decodeDiffstat(value) {
79
+ if (value === undefined)
80
+ return undefined;
81
+ const diffstat = object(value);
82
+ if (diffstat === null)
83
+ return null;
84
+ const added = decodeNonnegativeCount(diffstat.added);
85
+ const removed = decodeNonnegativeCount(diffstat.removed);
86
+ return added === null || removed === null ? null : { added, removed };
87
+ }
88
+ function decodeFileChangeMember(value) {
89
+ const change = object(value);
90
+ if (change === null || (change.op !== "add" && change.op !== "update" && change.op !== "delete")
91
+ || typeof change.path !== "string")
92
+ return null;
93
+ const diffstat = decodeDiffstat(change.diffstat);
94
+ if (diffstat === null)
95
+ return null;
96
+ return {
97
+ op: change.op,
98
+ path: change.path,
99
+ ...(diffstat === undefined ? {} : { diffstat }),
100
+ };
101
+ }
102
+ function decodeFileChangeCall(call) {
103
+ if (!Array.isArray(call.changes))
104
+ return null;
105
+ const changes = call.changes.map(decodeFileChangeMember);
106
+ return changes.every((change) => change !== null) ? { kind: "fileChange", changes } : null;
107
+ }
108
+ function decodeOtherCall(call) {
109
+ return typeof call.display === "string" ? { kind: "other", display: call.display } : null;
60
110
  }
61
111
  function decodeToolCall(value) {
62
112
  const call = object(value);
63
- if (call?.kind === "run" && typeof call.command === "string")
64
- return { kind: "run", command: call.command };
65
- if (call?.kind === "read" && typeof call.path === "string")
66
- return { kind: "read", path: call.path };
67
- if (call?.kind === "search" && typeof call.query === "string")
68
- return { kind: "search", query: call.query };
69
- if (call?.kind === "fileChange" && Array.isArray(call.changes)) {
70
- const changes = call.changes.map((value) => {
71
- const change = object(value);
72
- if (change === null || (change.op !== "add" && change.op !== "update" && change.op !== "delete")
73
- || typeof change.path !== "string")
74
- return null;
75
- const rawDiffstat = change.diffstat;
76
- const op = change.op;
77
- if (rawDiffstat === undefined)
78
- return { op, path: change.path };
79
- const diffstat = object(rawDiffstat);
80
- if (diffstat === null || !Number.isSafeInteger(diffstat.added) || !Number.isSafeInteger(diffstat.removed)
81
- || diffstat.added < 0 || diffstat.removed < 0)
82
- return null;
83
- return {
84
- op,
85
- path: change.path,
86
- diffstat: { added: diffstat.added, removed: diffstat.removed },
87
- };
88
- });
89
- if (changes.every((change) => change !== null))
90
- return { kind: "fileChange", changes };
113
+ if (call === null)
114
+ return null;
115
+ switch (call.kind) {
116
+ case "run": return decodeRunCall(call);
117
+ case "read": return decodeReadCall(call);
118
+ case "search": return decodeSearchCall(call);
119
+ case "fileChange": return decodeFileChangeCall(call);
120
+ case "other": return decodeOtherCall(call);
121
+ default: return null;
91
122
  }
92
- if (call?.kind === "other" && typeof call.display === "string")
93
- return { kind: "other", display: call.display };
94
- return null;
95
123
  }
96
124
  function decodeToolResult(value) {
97
125
  const result = object(value);
@@ -153,8 +181,27 @@ export function decodeAgentEvent(value) {
153
181
  function boundedToolCall(call) {
154
182
  switch (call.kind) {
155
183
  case "run": return { value: { kind: call.kind, command: boundedEventText(call.command) }, truncated: call.command.length > AGENT_EVENT_TEXT_LIMIT };
156
- case "read": return { value: { kind: call.kind, path: boundedEventText(call.path) }, truncated: call.path.length > AGENT_EVENT_TEXT_LIMIT };
157
- case "search": return { value: { kind: call.kind, query: boundedEventText(call.query) }, truncated: call.query.length > AGENT_EVENT_TEXT_LIMIT };
184
+ case "read": return {
185
+ value: {
186
+ kind: call.kind,
187
+ path: boundedEventText(call.path),
188
+ ...(call.offset === undefined ? {} : { offset: call.offset }),
189
+ ...(call.limit === undefined ? {} : { limit: call.limit }),
190
+ },
191
+ truncated: call.path.length > AGENT_EVENT_TEXT_LIMIT,
192
+ };
193
+ case "search": return {
194
+ value: {
195
+ kind: call.kind,
196
+ query: boundedEventText(call.query),
197
+ ...(call.scope === undefined ? {} : { scope: call.scope }),
198
+ ...(call.path === undefined ? {} : { path: boundedEventText(call.path) }),
199
+ ...(call.glob === undefined ? {} : { glob: boundedEventText(call.glob) }),
200
+ },
201
+ truncated: call.query.length > AGENT_EVENT_TEXT_LIMIT
202
+ || (call.path !== undefined && call.path.length > AGENT_EVENT_TEXT_LIMIT)
203
+ || (call.glob !== undefined && call.glob.length > AGENT_EVENT_TEXT_LIMIT),
204
+ };
158
205
  case "fileChange": return {
159
206
  value: {
160
207
  kind: call.kind,
@@ -0,0 +1,19 @@
1
+ import * as acp from "@agentclientprotocol/sdk";
2
+ import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
3
+ import { type ProviderAdapter, type Session } from "../../provider.js";
4
+ export type AcpStartInput = Parameters<ProviderAdapter["start"]>[0] | Parameters<NonNullable<ProviderAdapter["resume"]>>[0];
5
+ export type AcpDependencies = Readonly<{
6
+ spawnProcess?: typeof spawnStdioProcess;
7
+ }>;
8
+ export type AcpLiveSession = Readonly<{
9
+ session: Session;
10
+ agent: acp.ClientContext;
11
+ sessionId: string;
12
+ open(): boolean;
13
+ }>;
14
+ type AcpLaunch = Readonly<{
15
+ argv: readonly [string, ...string[]];
16
+ env?: Readonly<Record<string, string>>;
17
+ }>;
18
+ export declare function startAcpSession(launch: AcpLaunch, input: AcpStartInput, dependencies?: AcpDependencies): Promise<AcpLiveSession>;
19
+ export {};
@@ -0,0 +1,159 @@
1
+ import * as acp from "@agentclientprotocol/sdk";
2
+ import { Readable, Writable } from "node:stream";
3
+ import { spawnStdioProcess } from "../../../runtime/proc/stdio.js";
4
+ import { abortable } from "../../abort.js";
5
+ import { AgentEventChannel, } from "../../provider.js";
6
+ import { EMPTY_ACP_EVENT_STATE, flushAcpEvents, mapAcpUpdate } from "./events.js";
7
+ function diagnostic(error) {
8
+ if (!(error instanceof Error))
9
+ return String(error);
10
+ if (!(error instanceof acp.RequestError) || error.data === undefined)
11
+ return error.message;
12
+ let data;
13
+ try {
14
+ data = typeof error.data === "string" ? error.data : JSON.stringify(error.data);
15
+ }
16
+ catch {
17
+ data = String(error.data);
18
+ }
19
+ return data.length === 0 ? `${error.message} [${error.code}]` : `${error.message} [${error.code}]: ${data}`;
20
+ }
21
+ function createAcpClient(onUpdate) {
22
+ return acp.client({ name: "keiyaku" })
23
+ .onNotification(acp.methods.client.session.update, ({ params }) => onUpdate(params));
24
+ }
25
+ function promptResult(response) {
26
+ if (response.stopReason === "end_turn" || response.stopReason === "max_tokens"
27
+ || response.stopReason === "max_turn_requests") {
28
+ return { kind: "answered", answer: "" };
29
+ }
30
+ return { kind: "failed", diagnostic: `ACP prompt ended ${response.stopReason}` };
31
+ }
32
+ async function establishSession(agent, input) {
33
+ const initialized = await agent.request(acp.methods.agent.initialize, {
34
+ protocolVersion: acp.PROTOCOL_VERSION,
35
+ clientInfo: { name: "keiyaku", version: "4" },
36
+ });
37
+ if (input.session.kind === "resume" && initialized.agentCapabilities?.loadSession !== true) {
38
+ throw new Error("ACP agent does not advertise session/load");
39
+ }
40
+ if (input.session.kind === "fresh") {
41
+ return (await agent.request(acp.methods.agent.session.new, { cwd: input.cwd, mcpServers: [] })).sessionId;
42
+ }
43
+ const sessionId = input.session.coordinate.sessionId;
44
+ if (sessionId === undefined)
45
+ throw new Error("ACP resume coordinate has no session id");
46
+ await agent.request(acp.methods.agent.session.load, { cwd: input.cwd, mcpServers: [], sessionId });
47
+ return sessionId;
48
+ }
49
+ function createAcpTurn(connection) {
50
+ const events = new AgentEventChannel();
51
+ let state = EMPTY_ACP_EVENT_STATE;
52
+ let terminal = false;
53
+ let resolveCompletion;
54
+ const completion = new Promise((resolve) => { resolveCompletion = resolve; });
55
+ const update = (next) => {
56
+ if (terminal)
57
+ return;
58
+ const mapped = mapAcpUpdate(next, state);
59
+ state = mapped.state;
60
+ for (const event of mapped.events)
61
+ events.emit(event);
62
+ };
63
+ const finish = async (result, cleanup) => {
64
+ if (terminal)
65
+ return;
66
+ terminal = true;
67
+ let completionResult = result;
68
+ const failCleanup = (error) => {
69
+ completionResult = { kind: "failed", diagnostic: `ACP cleanup failed: ${diagnostic(error)}` };
70
+ };
71
+ try {
72
+ await cleanup();
73
+ }
74
+ catch (error) {
75
+ failCleanup(error);
76
+ }
77
+ try {
78
+ connection.close();
79
+ }
80
+ catch (error) {
81
+ failCleanup(error);
82
+ }
83
+ const flushed = flushAcpEvents(state);
84
+ for (const event of flushed.events)
85
+ events.emit(event);
86
+ events.end();
87
+ resolveCompletion(completionResult.kind === "answered"
88
+ ? { ...completionResult, answer: flushed.state.answer }
89
+ : completionResult);
90
+ };
91
+ return {
92
+ events,
93
+ completion,
94
+ update,
95
+ finish,
96
+ open: () => !terminal,
97
+ };
98
+ }
99
+ function beginAcpPrompt(agent, child, turn, sessionId, input) {
100
+ void agent.request(acp.methods.agent.session.prompt, {
101
+ sessionId,
102
+ prompt: [{ type: "text", text: input.body }, ...input.launchTells.map(({ text }) => ({ type: "text", text }))],
103
+ }).then((response) => turn.finish(promptResult(response), () => child.endInputAndDrain()), (error) => turn.finish({ kind: "failed", diagnostic: diagnostic(error) }, () => child.endInputAndDrain()));
104
+ void child.exited.then((exit) => turn.finish({
105
+ kind: "failed",
106
+ diagnostic: exit.stderr || `ACP process exited${exit.code === null ? "" : ` with code ${exit.code}`}`,
107
+ }, () => child.endInputAndDrain()));
108
+ return {
109
+ admission: { fence: sessionId },
110
+ events: turn.events,
111
+ completion: turn.completion,
112
+ abort: async () => {
113
+ let result = { kind: "failed", diagnostic: "ACP turn cancelled" };
114
+ try {
115
+ await agent.notify(acp.methods.agent.session.cancel, { sessionId });
116
+ }
117
+ catch (error) {
118
+ result = { kind: "failed", diagnostic: diagnostic(error) };
119
+ }
120
+ if (!turn.open())
121
+ await child.close(true);
122
+ else
123
+ await turn.finish(result, () => child.close(true));
124
+ },
125
+ };
126
+ }
127
+ export async function startAcpSession(launch, input, dependencies = {}) {
128
+ const signal = input.signal ?? new AbortController().signal;
129
+ signal.throwIfAborted();
130
+ const child = (dependencies.spawnProcess ?? spawnStdioProcess)({
131
+ argv: launch.argv,
132
+ cwd: input.cwd,
133
+ env: { ...globalThis.process.env, ...launch.env },
134
+ });
135
+ let sessionId;
136
+ let turn;
137
+ const connection = createAcpClient((notification) => {
138
+ if (notification.sessionId === sessionId)
139
+ turn.update(notification.update);
140
+ }).connect(acp.ndJsonStream(Writable.toWeb(child.input), Readable.toWeb(child.output)));
141
+ turn = createAcpTurn(connection);
142
+ try {
143
+ sessionId = await abortable(establishSession(connection.agent, input), signal);
144
+ turn.events.emit({ type: "session", coordinate: { sessionId } });
145
+ const session = beginAcpPrompt(connection.agent, child, turn, sessionId, input);
146
+ return { session, agent: connection.agent, sessionId, open: turn.open };
147
+ }
148
+ catch (error) {
149
+ connection.close(error);
150
+ try {
151
+ await child.close(true);
152
+ }
153
+ catch (cleanup) {
154
+ throw new Error(`${diagnostic(error)}; ACP cleanup failed: ${diagnostic(cleanup)}`, { cause: error });
155
+ }
156
+ turn.events.end();
157
+ throw error;
158
+ }
159
+ }
@@ -0,0 +1,20 @@
1
+ import type { SessionUpdate } from "@agentclientprotocol/sdk";
2
+ import { type AgentEvent } from "../../provider.js";
3
+ type OpenBlock = Readonly<{
4
+ type: "assistant" | "thought";
5
+ text: string;
6
+ }> | null;
7
+ export type AcpEventState = Readonly<{
8
+ answer: string;
9
+ assistantMessageId: string | null;
10
+ open: OpenBlock;
11
+ tools: ReadonlyMap<string, string>;
12
+ }>;
13
+ type AcpEventMapping = Readonly<{
14
+ events: readonly AgentEvent[];
15
+ state: AcpEventState;
16
+ }>;
17
+ export declare const EMPTY_ACP_EVENT_STATE: AcpEventState;
18
+ export declare function flushAcpEvents(state: AcpEventState, boundary?: readonly AgentEvent[]): AcpEventMapping;
19
+ export declare function mapAcpUpdate(update: SessionUpdate, previous: AcpEventState): AcpEventMapping;
20
+ export {};
@@ -0,0 +1,73 @@
1
+ import { noteEvent, unknownEvent } from "../../provider.js";
2
+ export const EMPTY_ACP_EVENT_STATE = Object.freeze({
3
+ answer: "",
4
+ assistantMessageId: null,
5
+ open: null,
6
+ tools: new Map(),
7
+ });
8
+ export function flushAcpEvents(state, boundary = []) {
9
+ const events = state.open === null ? boundary : [{ type: state.open.type, text: state.open.text }, ...boundary];
10
+ return {
11
+ events,
12
+ state: state.open === null ? state : { ...state, open: null },
13
+ };
14
+ }
15
+ function appendBlock(state, type, text, messageId) {
16
+ const newAssistantMessage = type === "assistant"
17
+ && messageId !== undefined
18
+ && state.assistantMessageId !== messageId;
19
+ const flushed = state.open === null || (state.open.type === type && !newAssistantMessage)
20
+ ? { events: [], state }
21
+ : flushAcpEvents(state);
22
+ return {
23
+ events: flushed.events,
24
+ state: {
25
+ ...flushed.state,
26
+ answer: type === "assistant"
27
+ ? `${newAssistantMessage ? "" : flushed.state.answer}${text}`
28
+ : flushed.state.answer,
29
+ assistantMessageId: type === "assistant" && messageId !== undefined
30
+ ? messageId
31
+ : flushed.state.assistantMessageId,
32
+ open: { type, text: `${flushed.state.open?.text ?? ""}${text}` },
33
+ },
34
+ };
35
+ }
36
+ export function mapAcpUpdate(update, previous) {
37
+ switch (update.sessionUpdate) {
38
+ case "agent_message_chunk":
39
+ case "agent_thought_chunk": {
40
+ const text = update.content.type === "text" ? update.content.text : undefined;
41
+ if (text === undefined)
42
+ return flushAcpEvents(previous, [unknownEvent(`${update.sessionUpdate}/${update.content.type}`)]);
43
+ return appendBlock(previous, update.sessionUpdate === "agent_message_chunk" ? "assistant" : "thought", text, update.sessionUpdate === "agent_message_chunk" && update.messageId !== null
44
+ ? update.messageId
45
+ : undefined);
46
+ }
47
+ case "user_message_chunk": return flushAcpEvents(previous);
48
+ case "tool_call":
49
+ case "tool_call_update": {
50
+ const name = update.title ?? (update.sessionUpdate === "tool_call" ? undefined : previous.tools.get(update.toolCallId)) ?? "ACP tool";
51
+ const observed = { name, call: { kind: "other", display: name } };
52
+ const tools = new Map(previous.tools);
53
+ if (update.status === "completed" || update.status === "failed") {
54
+ tools.delete(update.toolCallId);
55
+ return flushAcpEvents({ ...previous, tools }, [{ type: "tool", phase: "completed", id: update.toolCallId, ...observed, result: { status: update.status === "completed" ? "ok" : "error" } }]);
56
+ }
57
+ const started = tools.has(update.toolCallId);
58
+ tools.set(update.toolCallId, name);
59
+ return started
60
+ ? flushAcpEvents({ ...previous, tools })
61
+ : flushAcpEvents({ ...previous, tools }, [{ type: "tool", phase: "started", id: update.toolCallId, ...observed }]);
62
+ }
63
+ case "plan": return flushAcpEvents(previous, [noteEvent(`Plan updated: ${update.entries.map((entry) => entry.content).join("; ")}`)]);
64
+ case "available_commands_update": return flushAcpEvents(previous, [noteEvent("ACP commands updated")]);
65
+ case "current_mode_update": return flushAcpEvents(previous, [noteEvent("ACP mode updated")]);
66
+ case "config_option_update": return flushAcpEvents(previous, [noteEvent("ACP configuration updated")]);
67
+ case "session_info_update": return flushAcpEvents(previous, [noteEvent("ACP session metadata updated")]);
68
+ case "usage_update": return flushAcpEvents(previous);
69
+ case "plan_update":
70
+ case "plan_removed": return flushAcpEvents(previous, [unknownEvent(update.sessionUpdate)]);
71
+ default: return { events: [unknownEvent(String(update.sessionUpdate))], state: previous };
72
+ }
73
+ }
@@ -0,0 +1,12 @@
1
+ import type { ProviderAdapter } from "../../provider.js";
2
+ import type { ProviderExecution } from "../../provider-recipe.js";
3
+ import { type AcpDependencies } from "./core.js";
4
+ export type AcpExecutionConfig = Readonly<{
5
+ argvBefore: readonly string[];
6
+ argvAfter: readonly string[];
7
+ modelArg?: string;
8
+ effortArg?: string;
9
+ systemPromptArg?: string;
10
+ }>;
11
+ export declare function decodeAcpConfig(value: unknown): AcpExecutionConfig;
12
+ export declare function createAcpProvider(execution: ProviderExecution, dependencies?: AcpDependencies): ProviderAdapter;
@@ -0,0 +1,94 @@
1
+ import { startAcpSession } from "./core.js";
2
+ function argumentName(value, key) {
3
+ const selected = value[key];
4
+ if (selected === undefined)
5
+ return undefined;
6
+ if (typeof selected !== "string" || selected.trim().length === 0) {
7
+ throw new TypeError(`ACP provider config ${key} must be a nonblank string`);
8
+ }
9
+ return selected;
10
+ }
11
+ export function decodeAcpConfig(value) {
12
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
13
+ throw new TypeError("ACP provider config must be an object");
14
+ }
15
+ const config = value;
16
+ const unknown = Object.keys(config)
17
+ .find((key) => !["argvBefore", "argvAfter", "effortArg", "modelArg", "systemPromptArg"].includes(key));
18
+ if (unknown !== undefined)
19
+ throw new TypeError(`ACP provider config has unknown field ${unknown}`);
20
+ if (!Array.isArray(config.argvBefore)
21
+ || config.argvBefore.some((arg) => typeof arg !== "string" || arg.trim().length === 0)) {
22
+ throw new TypeError("ACP provider config argvBefore must be an array of nonblank strings");
23
+ }
24
+ if (!Array.isArray(config.argvAfter)
25
+ || config.argvAfter.some((arg) => typeof arg !== "string" || arg.trim().length === 0)) {
26
+ throw new TypeError("ACP provider config argvAfter must be an array of nonblank strings");
27
+ }
28
+ const modelArg = argumentName(config, "modelArg");
29
+ const effortArg = argumentName(config, "effortArg");
30
+ const systemPromptArg = argumentName(config, "systemPromptArg");
31
+ return Object.freeze({
32
+ argvBefore: Object.freeze([...config.argvBefore]),
33
+ argvAfter: Object.freeze([...config.argvAfter]),
34
+ ...(modelArg === undefined ? {} : { modelArg }),
35
+ ...(effortArg === undefined ? {} : { effortArg }),
36
+ ...(systemPromptArg === undefined ? {} : { systemPromptArg }),
37
+ });
38
+ }
39
+ function optionAdmission(options, config) {
40
+ if (options.network !== undefined) {
41
+ return { kind: "refused", diagnostic: "ACP provider does not support the network option" };
42
+ }
43
+ if (options.model !== undefined && config.modelArg === undefined) {
44
+ return { kind: "refused", diagnostic: "ACP provider has no model argument mapping" };
45
+ }
46
+ if (options.effort !== undefined && config.effortArg === undefined) {
47
+ return { kind: "refused", diagnostic: "ACP provider has no effort argument mapping" };
48
+ }
49
+ if (options.systemPrompt !== undefined && options.systemPrompt.length > 0 && config.systemPromptArg === undefined) {
50
+ return { kind: "refused", diagnostic: "ACP provider has no systemPrompt argument mapping" };
51
+ }
52
+ return {
53
+ kind: "admitted",
54
+ options,
55
+ ...(options.readonly === undefined ? {} : {
56
+ readonly: {
57
+ enforcement: "none",
58
+ diagnostic: "ACP cannot remove task-surface mutation capabilities",
59
+ },
60
+ }),
61
+ };
62
+ }
63
+ function argv(execution, config, options) {
64
+ if (execution.executable === undefined)
65
+ throw new Error("ACP provider execution requires executable");
66
+ const values = [execution.executable, ...config.argvBefore];
67
+ if (options.model !== undefined)
68
+ values.push(config.modelArg, options.model);
69
+ if (options.effort !== undefined)
70
+ values.push(config.effortArg, options.effort);
71
+ if (options.systemPrompt !== undefined && config.systemPromptArg !== undefined) {
72
+ values.push(config.systemPromptArg, options.systemPrompt);
73
+ }
74
+ values.push(...config.argvAfter);
75
+ return values;
76
+ }
77
+ export function createAcpProvider(execution, dependencies = {}) {
78
+ if (execution.executable === undefined)
79
+ throw new TypeError("ACP provider execution requires executable");
80
+ const config = decodeAcpConfig(execution.config);
81
+ const drive = async (input) => {
82
+ const launch = {
83
+ argv: argv(execution, config, input.options),
84
+ ...(execution.env === undefined ? {} : { env: execution.env }),
85
+ };
86
+ return (await startAcpSession(launch, input, dependencies)).session;
87
+ };
88
+ return {
89
+ confinement: () => ({ kind: "unconfined" }),
90
+ admitOptions: (options) => optionAdmission(options, config),
91
+ start: async (input) => await drive(input),
92
+ resume: async (input) => await drive(input),
93
+ };
94
+ }