@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
@@ -1,31 +1,33 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { abortable } from "../../abort.js";
1
3
  import { AgentEventChannel } from "../../provider.js";
2
4
  import { createEventState, mapEvent } from "./events.js";
3
5
  import { coordinate, loadOpencode, OPENCODE_SDK_PROVIDER, parseModel } from "./session.js";
4
- function diagnostic(error) { return error instanceof Error ? error.message : String(error); }
5
- function opencodeSessionId(coordinate) {
6
- if (!("sessionId" in coordinate) || coordinate.sessionId === undefined) {
6
+ function object(value) {
7
+ return value !== null && typeof value === "object" && !Array.isArray(value)
8
+ ? value : undefined;
9
+ }
10
+ function text(value) {
11
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
12
+ }
13
+ function diagnostic(error) {
14
+ const value = object(error);
15
+ const nested = object(value?.data);
16
+ return text(nested?.message) ?? text(value?.message) ?? String(error);
17
+ }
18
+ function opencodeSessionId(coordinateValue) {
19
+ if (!("sessionId" in coordinateValue) || coordinateValue.sessionId === undefined) {
7
20
  throw new Error("OpenCode resume requires sessionId");
8
21
  }
9
- return coordinate.sessionId;
22
+ return coordinateValue.sessionId;
10
23
  }
11
24
  function admit(options) {
12
- if (options.access !== undefined)
13
- throw new Error("OpenCode does not support explicit access");
14
25
  if (options.network !== undefined)
15
26
  throw new Error("OpenCode does not support explicit network");
16
- if (options.systemPrompt !== undefined && options.systemPrompt.length > 0) {
17
- throw new Error("OpenCode V2 does not expose an enforceable system prompt option");
18
- }
19
- if (options.effort !== undefined && options.model === undefined) {
20
- throw new Error("OpenCode effort requires an explicit model");
21
- }
22
27
  if (options.model !== undefined)
23
- parseModel(options.model, options.effort);
28
+ parseModel(options.model);
24
29
  }
25
30
  function eventValue(value) {
26
- const envelope = objectData(value);
27
- if (envelope !== undefined && typeof envelope.data === "string")
28
- return eventValue(envelope.data);
29
31
  if (typeof value !== "string")
30
32
  return value;
31
33
  try {
@@ -35,135 +37,228 @@ function eventValue(value) {
35
37
  return { type: value };
36
38
  }
37
39
  }
40
+ function promptBody(input, messageID) {
41
+ const model = input.options.model === undefined ? undefined : parseModel(input.options.model);
42
+ const promptText = [input.body, ...input.launchTells.map((tell) => tell.text)]
43
+ .filter((part) => part.length > 0)
44
+ .join("\n\n");
45
+ return {
46
+ messageID,
47
+ ...(model === undefined ? {} : { model }),
48
+ ...(input.options.effort === undefined ? {} : { variant: input.options.effort }),
49
+ ...(input.options.systemPrompt === undefined ? {} : { system: input.options.systemPrompt }),
50
+ parts: [{ type: "text", text: promptText }],
51
+ };
52
+ }
53
+ function messageRows(value, sessionId) {
54
+ const response = object(value);
55
+ const rows = Array.isArray(response?.data) ? response.data : Array.isArray(value) ? value : [];
56
+ return rows.flatMap((row) => {
57
+ const entry = object(row);
58
+ const info = object(entry?.info);
59
+ if (info?.sessionID !== sessionId)
60
+ return [];
61
+ return [{ info, parts: Array.isArray(entry?.parts) ? entry.parts : [] }];
62
+ });
63
+ }
64
+ function newest(rows) {
65
+ return rows.reduce((latest, row) => {
66
+ if (latest === undefined)
67
+ return row;
68
+ const at = Number(object(row.info.time)?.created ?? 0);
69
+ const latestAt = Number(object(latest.info.time)?.created ?? 0);
70
+ return at >= latestAt ? row : latest;
71
+ }, undefined);
72
+ }
73
+ async function readTurnResult(input) {
74
+ try {
75
+ const response = await input.session.messages({
76
+ path: { id: input.sessionId },
77
+ query: { directory: input.cwd },
78
+ throwOnError: true,
79
+ });
80
+ const rows = messageRows(response, input.sessionId);
81
+ const user = rows.find((row) => row.info.role === "user" && row.info.id === input.messageID);
82
+ if (user === undefined)
83
+ return undefined;
84
+ const userId = text(user?.info.id);
85
+ const assistant = newest(rows.filter((row) => row.info.role === "assistant" && row.info.parentID === userId));
86
+ const historyId = text(assistant?.info.id);
87
+ if (input.state.failure !== undefined)
88
+ return { kind: "failed", diagnostic: input.state.failure };
89
+ if (assistant === undefined)
90
+ return { kind: "failed", diagnostic: "OpenCode completed without a native assistant answer" };
91
+ if (assistant.info.error !== undefined)
92
+ return { kind: "failed", diagnostic: diagnostic(assistant.info.error) };
93
+ const answer = assistant.parts
94
+ .map((part) => object(part))
95
+ .filter((part) => part?.type === "text")
96
+ .map((part) => part?.text)
97
+ .filter((part) => typeof part === "string")
98
+ .join("\n\n");
99
+ return historyId === undefined ? { kind: "answered", answer } : { kind: "answered", answer, historyId };
100
+ }
101
+ catch (error) {
102
+ return { kind: "failed", diagnostic: diagnostic(error) };
103
+ }
104
+ }
105
+ function nativeSessionId(value) {
106
+ const event = object(value);
107
+ const properties = object(event?.properties);
108
+ const direct = text(properties?.sessionID);
109
+ if (direct !== undefined)
110
+ return direct;
111
+ const info = object(properties?.info);
112
+ const part = object(properties?.part);
113
+ return text(info?.sessionID) ?? text(part?.sessionID);
114
+ }
115
+ function scopedProperties(value, sessionId) {
116
+ const properties = object(object(value)?.properties);
117
+ return nativeSessionId(value) === sessionId ? properties : undefined;
118
+ }
119
+ function stopIterator(iterator) {
120
+ const stopped = iterator?.return?.(undefined);
121
+ void stopped?.catch(() => undefined);
122
+ }
123
+ function nativeProgress(type, properties, busy) {
124
+ if (type === "session.idle")
125
+ return { busy, terminal: busy };
126
+ if (type !== "session.status")
127
+ return { busy, terminal: false };
128
+ const status = object(properties.status)?.type;
129
+ const nextBusy = busy || status === "busy";
130
+ return { busy: nextBusy, terminal: status === "idle" && nextBusy };
131
+ }
132
+ function startsTurn(value, sessionId, messageID) {
133
+ const event = object(value);
134
+ const info = object(object(event?.properties)?.info);
135
+ return event?.type === "message.updated"
136
+ && info?.sessionID === sessionId
137
+ && info.role === "user"
138
+ && info.id === messageID;
139
+ }
140
+ async function observeTurn(input) {
141
+ let active = false;
142
+ let busy = false;
143
+ try {
144
+ for (;;) {
145
+ const next = await input.iterator.next();
146
+ if (next.done)
147
+ return { kind: "failed", diagnostic: "OpenCode event stream ended before Turn completion" };
148
+ const value = eventValue(next.value);
149
+ const observedSessionId = nativeSessionId(value);
150
+ if (observedSessionId !== undefined && observedSessionId !== input.sessionId)
151
+ continue;
152
+ const event = object(value);
153
+ const properties = scopedProperties(value, input.sessionId);
154
+ if (properties === undefined) {
155
+ if (active)
156
+ mapEvent(value, input.events, input.state);
157
+ continue;
158
+ }
159
+ if (startsTurn(value, input.sessionId, input.messageID)) {
160
+ active = true;
161
+ busy = false;
162
+ }
163
+ const submittedError = event?.type === "session.error" && input.submissionState.started;
164
+ if (!active && !submittedError)
165
+ continue;
166
+ mapEvent(value, input.events, input.state);
167
+ if (event?.type === "session.error") {
168
+ return { kind: "failed", diagnostic: input.state.failure ?? diagnostic(properties.error) };
169
+ }
170
+ const progress = nativeProgress(event?.type, properties, busy);
171
+ busy = progress.busy;
172
+ if (!progress.terminal)
173
+ continue;
174
+ const result = await readTurnResult(input);
175
+ if (result !== undefined)
176
+ return result;
177
+ }
178
+ }
179
+ catch (error) {
180
+ return { kind: "failed", diagnostic: `OpenCode event stream failed: ${diagnostic(error)}` };
181
+ }
182
+ }
183
+ function terminalSettlement(events, close) {
184
+ let finish;
185
+ const completion = new Promise((resolve) => {
186
+ let settlement;
187
+ finish = (result) => {
188
+ settlement ??= (async () => {
189
+ events.end();
190
+ await close();
191
+ resolve(result);
192
+ })();
193
+ return settlement;
194
+ };
195
+ });
196
+ return { completion, finish };
197
+ }
38
198
  async function drive(execution, input, loader) {
39
199
  admit(input.options);
200
+ const signal = input.signal ?? new AbortController().signal;
40
201
  const resumeSessionId = input.session.kind === "resume"
41
202
  ? opencodeSessionId(input.session.coordinate)
42
203
  : undefined;
43
- const events = new AgentEventChannel();
44
- const state = createEventState();
45
204
  const abortController = new AbortController();
46
- const runtime = await loadOpencode(execution, input.cwd, abortController.signal, loader);
47
- let closed = false;
48
- const closeOnce = async () => {
49
- if (closed)
50
- return;
51
- closed = true;
52
- await runtime.close();
205
+ const abortSetup = () => abortController.abort(signal.reason);
206
+ signal.addEventListener("abort", abortSetup, { once: true });
207
+ signal.throwIfAborted();
208
+ const runtime = await abortable(loadOpencode(execution, input.cwd, abortController.signal, loader), abortController.signal, async (late) => await late.close());
209
+ let closing;
210
+ let iterator;
211
+ const closeOnce = () => {
212
+ closing ??= runtime.close();
213
+ return closing;
53
214
  };
54
215
  try {
55
- const session = runtime.client.v2.session;
56
- const model = input.options.model === undefined ? undefined : parseModel(input.options.model, input.options.effort);
57
- const response = input.session.kind === "fresh"
58
- ? await session.create({ location: { directory: input.cwd }, ...(model === undefined ? {} : { model }) }, { throwOnError: true })
59
- : await session.get({ sessionID: resumeSessionId }, { throwOnError: true });
60
- const responseData = objectData(objectData(response)?.data);
61
- const info = objectData(responseData?.data) ?? responseData;
62
- const sessionId = textData(info?.id) ?? resumeSessionId;
216
+ const session = runtime.client.session;
217
+ const sessionResponse = await abortable(input.session.kind === "fresh"
218
+ ? session.create({ query: { directory: input.cwd }, throwOnError: true })
219
+ : session.get({ path: { id: resumeSessionId }, query: { directory: input.cwd }, throwOnError: true }), abortController.signal);
220
+ const info = object(object(sessionResponse)?.data) ?? object(sessionResponse);
221
+ const sessionId = text(info?.id) ?? resumeSessionId;
63
222
  if (sessionId === undefined)
64
223
  throw new Error("OpenCode did not return a session id");
65
- if (input.session.kind === "resume" && model !== undefined) {
66
- await session.switchModel({ sessionID: sessionId, model }, { throwOnError: true });
67
- }
224
+ const events = new AgentEventChannel();
225
+ const state = createEventState(sessionId);
226
+ const messageID = `msg_${randomUUID().replaceAll("-", "")}`;
227
+ const submissionState = { started: false };
68
228
  events.emit({ type: "session", coordinate: coordinate(sessionId) });
69
- const streamResult = await session.events({ sessionID: sessionId });
70
- const iterator = streamResult.stream[Symbol.asyncIterator]();
71
- const promptText = [input.body, ...input.launchTells.map((tell) => tell.text)].filter((part) => part.length > 0).join("\n\n");
72
- const promptResponse = await session.prompt({ sessionID: sessionId, prompt: { text: promptText } }, { throwOnError: true });
73
- const fence = admittedFence(sessionId, promptResponse);
74
- let settled = false;
75
- let settle;
76
- const completion = new Promise((resolve) => { settle = resolve; });
77
- const finish = async (result) => {
78
- if (settled)
79
- return;
80
- settled = true;
81
- events.end();
82
- try {
83
- await closeOnce();
84
- settle(result);
85
- }
86
- catch (error) {
87
- settle({ kind: "failed", diagnostic: `OpenCode cleanup failed: ${diagnostic(error)}` });
88
- }
89
- };
90
- void (async () => {
91
- try {
92
- await consumeSession(session, sessionId, iterator, state, events);
93
- const messages = await session.messages({ sessionID: sessionId, order: "desc", limit: 32 }, { throwOnError: true });
94
- const final = messages.data.data.find((message) => message.type === "assistant");
95
- const answer = final?.type === "assistant" ? final.content.filter((part) => part.type === "text").map((part) => part.text).join("\n\n") : state.answer.join("\n\n");
96
- const historyId = final?.type === "assistant" ? textData(final.id) : undefined;
97
- if (state.failure !== undefined || final?.type === "assistant" && final.error !== undefined)
98
- await finish({ kind: "failed", diagnostic: state.failure ?? diagnostic(final?.type === "assistant" ? final.error : undefined) });
99
- else if (historyId === undefined) {
100
- await finish({ kind: "failed", diagnostic: "OpenCode completed without a native assistant answer/history point" });
101
- }
102
- else
103
- await finish({ kind: "answered", answer, historyId });
104
- }
105
- catch (error) {
106
- await finish({ kind: "failed", diagnostic: diagnostic(error) });
107
- }
108
- })();
229
+ const streamResult = await abortable(runtime.client.event.subscribe({ query: { directory: input.cwd } }), abortController.signal, async (late) => { await late.stream[Symbol.asyncIterator]().return?.(undefined); });
230
+ iterator = streamResult.stream[Symbol.asyncIterator]();
231
+ const { completion, finish } = terminalSettlement(events, closeOnce);
232
+ const observation = observeTurn({
233
+ iterator, session, sessionId, cwd: input.cwd, messageID, events, state, submissionState,
234
+ });
235
+ submissionState.started = true;
236
+ await abortable(session.promptAsync({
237
+ path: { id: sessionId },
238
+ query: { directory: input.cwd },
239
+ body: promptBody(input, messageID),
240
+ throwOnError: true,
241
+ }), abortController.signal);
242
+ void observation.then(finish);
243
+ signal.removeEventListener("abort", abortSetup);
109
244
  return {
110
- admission: { fence },
245
+ admission: { fence: sessionId },
111
246
  events,
112
247
  completion,
113
248
  abort: async () => {
114
249
  abortController.abort();
115
- void session.interrupt({ sessionID: sessionId }, { throwOnError: true }).catch(() => undefined);
250
+ void session.abort({ path: { id: sessionId }, query: { directory: input.cwd }, throwOnError: true }).catch(() => undefined);
116
251
  await finish({ kind: "failed", diagnostic: "OpenCode session interrupted" });
117
252
  },
118
253
  };
119
254
  }
120
255
  catch (error) {
121
- try {
122
- await closeOnce();
123
- }
124
- catch (cleanupError) {
125
- throw new Error(`OpenCode setup failed: ${diagnostic(error)}; cleanup failed: ${diagnostic(cleanupError)}`);
126
- }
256
+ signal.removeEventListener("abort", abortSetup);
257
+ stopIterator(iterator);
258
+ await closeOnce();
127
259
  throw error;
128
260
  }
129
261
  }
130
- function objectData(value) { return value !== null && typeof value === "object" ? value : undefined; }
131
- function textData(value) { return typeof value === "string" && value.trim().length > 0 ? value : undefined; }
132
- function admittedFence(sessionId, response) {
133
- const outer = objectData(response);
134
- const envelope = objectData(outer?.data);
135
- const admission = objectData(envelope?.data) ?? envelope;
136
- const id = textData(admission?.id);
137
- const sequence = typeof admission?.admittedSeq === "number" && Number.isSafeInteger(admission.admittedSeq)
138
- ? admission.admittedSeq : undefined;
139
- if (id === undefined && sequence === undefined)
140
- throw new Error("OpenCode did not return prompt admission evidence");
141
- return `${sessionId}:${sequence ?? id}`;
142
- }
143
- async function consumeSession(session, sessionId, iterator, state, events) {
144
- const stream = (async () => {
145
- for (;;) {
146
- const next = await iterator.next();
147
- if (next.done)
148
- return;
149
- mapEvent(eventValue(next.value), events, state);
150
- }
151
- })();
152
- stream.catch(() => undefined);
153
- await session.wait({ sessionID: sessionId }, { throwOnError: true });
154
- await iterator.return?.();
155
- await stream;
156
- let after;
157
- for (;;) {
158
- const history = await session.history({ sessionID: sessionId, limit: 200, ...(after === undefined ? {} : { after }) }, { throwOnError: true });
159
- for (const event of history.data.data)
160
- mapEvent(event, events, state);
161
- const latest = history.data.data.at(-1)?.durable?.seq;
162
- if (!history.data.hasMore || latest === undefined || latest === after)
163
- return;
164
- after = latest;
165
- }
166
- }
167
262
  export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, kind: "opencode-sdk" }) {
168
263
  const execution = "kind" in input ? input : { name: OPENCODE_SDK_PROVIDER, kind: "opencode-sdk" };
169
264
  const loader = "loader" in input ? input.loader : undefined;
@@ -176,7 +271,16 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
176
271
  catch (error) {
177
272
  return { kind: "refused", diagnostic: diagnostic(error) };
178
273
  }
179
- return { kind: "admitted", options: Object.freeze({ ...options }) };
274
+ return {
275
+ kind: "admitted",
276
+ options: Object.freeze({ ...options }),
277
+ ...(options.readonly === undefined ? {} : {
278
+ readonly: {
279
+ enforcement: "none",
280
+ diagnostic: "OpenCode V1 cannot remove task-surface mutation capabilities",
281
+ },
282
+ }),
283
+ };
180
284
  },
181
285
  start: (input) => drive(execution, input, loader),
182
286
  resume: (input) => drive(execution, input, loader),
@@ -184,12 +288,13 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
184
288
  const sessionId = opencodeSessionId(input.session);
185
289
  const runtime = await loadOpencode(execution, input.cwd, new AbortController().signal, loader);
186
290
  try {
187
- const fork = runtime.client.session?.fork;
188
- if (fork === undefined)
189
- throw new Error("OpenCode fork is unavailable");
190
- const result = objectData(await fork({ sessionID: sessionId, messageID: input.at }));
191
- const data = objectData(result?.data);
192
- const id = textData(data?.id);
291
+ const result = await runtime.client.session.fork({
292
+ path: { id: sessionId },
293
+ query: { directory: input.cwd },
294
+ body: { messageID: input.at },
295
+ });
296
+ const info = object(object(result)?.data) ?? object(result);
297
+ const id = text(info?.id);
193
298
  if (id === undefined || id === sessionId)
194
299
  throw new Error("OpenCode fork returned an invalid session id");
195
300
  return { session: coordinate(id) };
@@ -1,39 +1,24 @@
1
- import { type OpencodeClient } from "@opencode-ai/sdk/v2";
2
- import { type ProviderExecution, type ResumeCoordinate } from "../../heart/index.js";
3
- export type OpencodeSdkSession = Pick<OpencodeClient["v2"]["session"], "create" | "get" | "prompt" | "events" | "history" | "messages" | "interrupt" | "switchModel" | "wait">;
1
+ import { type OpencodeClient } from "@opencode-ai/sdk";
2
+ import type { ResumeCoordinate } from "../../heart/index.js";
3
+ import type { ProviderExecution } from "../../provider-recipe.js";
4
+ export type OpencodeSdkSession = Pick<OpencodeClient["session"], "create" | "get" | "fork" | "abort" | "promptAsync" | "messages">;
5
+ export type OpencodeSdkEvent = Pick<OpencodeClient["event"], "subscribe">;
4
6
  export type OpencodeSdkLoader = (cwd: string) => Promise<Readonly<{
5
7
  client: {
6
- v2: {
7
- session: OpencodeSdkSession;
8
- model?: Pick<OpencodeClient["v2"]["model"], "list">;
9
- };
10
- session?: {
11
- fork(params: {
12
- sessionID: string;
13
- messageID: string;
14
- }): Promise<unknown>;
15
- };
8
+ session: OpencodeSdkSession;
9
+ event: OpencodeSdkEvent;
16
10
  };
17
11
  close?: () => Promise<void> | void;
18
12
  }>>;
19
13
  export declare const OPENCODE_SDK_PROVIDER: "opencode-sdk";
20
- export declare function parseModel(model: string, effort?: string): Readonly<{
14
+ export declare function parseModel(model: string): Readonly<{
21
15
  providerID: string;
22
- id: string;
23
- variant?: string;
16
+ modelID: string;
24
17
  }>;
25
18
  export type OpencodeRuntime = Readonly<{
26
19
  client: {
27
- v2: {
28
- session: OpencodeSdkSession;
29
- model?: Pick<OpencodeClient["v2"]["model"], "list">;
30
- };
31
- session?: {
32
- fork(params: {
33
- sessionID: string;
34
- messageID: string;
35
- }): Promise<unknown>;
36
- };
20
+ session: OpencodeSdkSession;
21
+ event: OpencodeSdkEvent;
37
22
  };
38
23
  close: () => Promise<void>;
39
24
  }>;
@@ -1,19 +1,20 @@
1
1
  import net from "node:net";
2
- import { spawnDetachedProcess, putDownProcessTree } from "../../../runtime/proc/run.js";
3
- import { createOpencodeClient } from "@opencode-ai/sdk/v2";
2
+ import { abortable } from "../../abort.js";
3
+ import { spawnDetachedProcess } from "../../../runtime/proc/run.js";
4
+ import { createOpencodeClient } from "@opencode-ai/sdk";
4
5
  export const OPENCODE_SDK_PROVIDER = "opencode-sdk";
5
- export function parseModel(model, effort) {
6
+ export function parseModel(model) {
6
7
  const slash = model.indexOf("/");
7
8
  if (slash <= 0 || slash === model.length - 1)
8
9
  throw new Error("OpenCode model must be <provider>/<model>");
9
- return { providerID: model.slice(0, slash), id: model.slice(slash + 1), ...(effort === undefined ? {} : { variant: effort }) };
10
+ return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
10
11
  }
11
12
  async function waitReady(client, cwd, signal) {
12
13
  for (let attempt = 0; attempt < 100; attempt += 1) {
13
14
  if (signal.aborted)
14
15
  throw new Error("OpenCode startup aborted");
15
16
  try {
16
- await client.v2.model.list({ location: { directory: cwd } }, { throwOnError: true });
17
+ await abortable(client.session.list({ query: { directory: cwd }, throwOnError: true }), signal);
17
18
  return;
18
19
  }
19
20
  catch {
@@ -43,10 +44,10 @@ export async function loadOpencode(execution, cwd, signal, loader) {
43
44
  return { client: loaded.client, close: async () => { await loaded.close?.(); } };
44
45
  }
45
46
  const port = await availablePort();
46
- const collar = await spawnDetachedProcess({
47
+ const owned = await spawnDetachedProcess({
47
48
  argv: [execution.executable ?? "opencode", "serve", "--hostname", "127.0.0.1", "--port", String(port)],
48
49
  cwd,
49
- env: { ...process.env, ...(execution.env ?? {}) },
50
+ env: { ...process.env, ...execution.env },
50
51
  log: `${cwd}/.opencode.log`,
51
52
  });
52
53
  const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}`, directory: cwd });
@@ -54,9 +55,9 @@ export async function loadOpencode(execution, cwd, signal, loader) {
54
55
  await waitReady(client, cwd, signal);
55
56
  }
56
57
  catch (error) {
57
- await putDownProcessTree(collar);
58
+ await owned.terminate();
58
59
  throw error;
59
60
  }
60
- return { client, close: async () => { await putDownProcessTree(collar); } };
61
+ return { client, close: async () => { await owned.terminate(); } };
61
62
  }
62
63
  export function coordinate(sessionId) { return { sessionId }; }
@@ -42,19 +42,57 @@ function hasTextContent(content) {
42
42
  return value?.type === "text" && typeof value.text === "string";
43
43
  });
44
44
  }
45
+ function positiveLine(value) {
46
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 ? value : undefined;
47
+ }
48
+ function optionalText(value) {
49
+ return typeof value === "string" && value.trim().length > 0 ? value : undefined;
50
+ }
51
+ function runCall(name, value) {
52
+ return name === "bash" && typeof value.command === "string"
53
+ ? { kind: "run", command: value.command }
54
+ : undefined;
55
+ }
56
+ function readCall(name, value) {
57
+ if (name !== "read" || typeof value.path !== "string")
58
+ return undefined;
59
+ const offset = positiveLine(value.offset);
60
+ const limit = positiveLine(value.limit);
61
+ return {
62
+ kind: "read",
63
+ path: value.path,
64
+ ...(offset === undefined ? {} : { offset }),
65
+ ...(limit === undefined ? {} : { limit }),
66
+ };
67
+ }
68
+ function searchCall(name, value) {
69
+ if (name !== "grep" && name !== "find")
70
+ return undefined;
71
+ const query = value.pattern ?? value.query;
72
+ if (typeof query !== "string")
73
+ return undefined;
74
+ const path = optionalText(value.path);
75
+ const glob = optionalText(value.glob);
76
+ return {
77
+ kind: "search",
78
+ query,
79
+ scope: name === "find" ? "files" : "content",
80
+ ...(path === undefined ? {} : { path }),
81
+ ...(name === "grep" && glob !== undefined ? { glob } : {}),
82
+ };
83
+ }
84
+ function fileChangeCall(name, value) {
85
+ if ((name !== "edit" && name !== "write") || typeof value.path !== "string")
86
+ return undefined;
87
+ return { kind: "fileChange", changes: [{ op: name === "write" ? "add" : "update", path: value.path }] };
88
+ }
45
89
  function toolCall(name, args) {
46
90
  const value = record(args) ?? {};
47
- if (name === "bash" && typeof value.command === "string")
48
- return { kind: "run", command: value.command };
49
- if (name === "read" && typeof value.path === "string")
50
- return { kind: "read", path: value.path };
51
- if ((name === "grep" || name === "find") && typeof (value.pattern ?? value.query) === "string") {
52
- return { kind: "search", query: (value.pattern ?? value.query) };
53
- }
54
- if ((name === "edit" || name === "write") && typeof value.path === "string") {
55
- return { kind: "fileChange", changes: [{ op: name === "write" ? "add" : "update", path: value.path }] };
56
- }
57
- return { kind: "other", display: name };
91
+ return runCall(name, value)
92
+ ?? readCall(name, value)
93
+ ?? searchCall(name, value)
94
+ ?? fileChangeCall(name, value)
95
+ ?? { kind: "other", display: name };
58
96
  }
59
97
  function translateNote(event) {
60
98
  if (event.type === "auto_retry_start")
@@ -1,5 +1,5 @@
1
1
  import { createAgentSession, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager, type CreateAgentSessionOptions } from "@earendil-works/pi-coding-agent";
2
- import type { ProviderExecution } from "../../heart/index.js";
2
+ import type { ProviderExecution } from "../../provider-recipe.js";
3
3
  import { type ProviderAdapter } from "../../provider.js";
4
4
  export type PiSdk = Readonly<{
5
5
  createAgentSession(options?: CreateAgentSessionOptions): ReturnType<typeof createAgentSession>;