@astrosheep/keiyaku 4.6.7 → 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 (336) hide show
  1. package/README.md +46 -16
  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/plugins/pi/keiyaku.ts +0 -5
  27. package/build/skills/keiyaku/SKILL.md +5 -6
  28. package/build/skills/keiyaku-akuma/SKILL.md +1 -1
  29. package/build/skills/keiyaku-akuma/references/automation.md +8 -8
  30. package/build/skills/keiyaku-babysit/SKILL.md +1 -1
  31. package/build/skills/keiyaku-bind/SKILL.md +9 -9
  32. package/build/skills/keiyaku-debt/SKILL.md +91 -0
  33. package/build/skills/keiyaku-workflow/SKILL.md +2 -2
  34. package/build/src/akuma/akuma-call.d.ts +2 -0
  35. package/build/src/akuma/akuma-call.js +8 -2
  36. package/build/src/akuma/akuma-instance.d.ts +14 -1
  37. package/build/src/akuma/akuma-instance.js +37 -7
  38. package/build/src/akuma/akuma-observe.d.ts +33 -8
  39. package/build/src/akuma/akuma-observe.js +89 -63
  40. package/build/src/akuma/akuma-owner.d.ts +14 -2
  41. package/build/src/akuma/akuma-owner.js +49 -35
  42. package/build/src/akuma/akuma-selection-execution.js +9 -1
  43. package/build/src/akuma/akuma-stream.d.ts +22 -0
  44. package/build/src/akuma/akuma-stream.js +116 -0
  45. package/build/src/akuma/akuma-wait.d.ts +1 -40
  46. package/build/src/akuma/akuma-wait.js +23 -84
  47. package/build/src/akuma/akuma.d.ts +52 -16
  48. package/build/src/akuma/akuma.js +10 -10
  49. package/build/src/akuma/allowed.d.ts +7 -12
  50. package/build/src/akuma/allowed.js +13 -4
  51. package/build/src/akuma/archetype.d.ts +7 -15
  52. package/build/src/akuma/archetype.js +36 -35
  53. package/build/src/akuma/body-supervisor.d.ts +5 -5
  54. package/build/src/akuma/body-supervisor.js +22 -20
  55. package/build/src/akuma/body.d.ts +5 -3
  56. package/build/src/akuma/body.js +57 -8
  57. package/build/src/akuma/call-request.d.ts +30 -32
  58. package/build/src/akuma/call-request.js +10 -3
  59. package/build/src/akuma/caller.d.ts +39 -0
  60. package/build/src/akuma/caller.js +36 -0
  61. package/build/src/akuma/dispatch-association.d.ts +0 -1
  62. package/build/src/akuma/dispatch-association.js +0 -3
  63. package/build/src/akuma/heart/activity-schema.d.ts +12 -0
  64. package/build/src/akuma/heart/activity-schema.js +6 -0
  65. package/build/src/akuma/heart/facts.d.ts +16 -12
  66. package/build/src/akuma/heart/facts.js +8 -0
  67. package/build/src/akuma/heart/index.d.ts +61 -9
  68. package/build/src/akuma/heart/index.js +89 -24
  69. package/build/src/akuma/heart/rows.d.ts +3 -3
  70. package/build/src/akuma/heart/rows.js +5 -3
  71. package/build/src/akuma/heart/soul.d.ts +10 -1
  72. package/build/src/akuma/heart/soul.js +34 -24
  73. package/build/src/akuma/heart/storage.d.ts +20 -0
  74. package/build/src/akuma/heart/storage.js +62 -3
  75. package/build/src/akuma/heart/timeline.d.ts +7 -0
  76. package/build/src/akuma/heart/timeline.js +10 -0
  77. package/build/src/akuma/identity.d.ts +0 -1
  78. package/build/src/akuma/identity.js +2 -5
  79. package/build/src/akuma/index.d.ts +6 -2
  80. package/build/src/akuma/index.js +1 -0
  81. package/build/src/akuma/nuke.js +6 -28
  82. package/build/src/akuma/projection.d.ts +25 -11
  83. package/build/src/akuma/projection.js +24 -7
  84. package/build/src/akuma/provider-recipe.d.ts +18 -6
  85. package/build/src/akuma/provider-recipe.js +24 -1
  86. package/build/src/akuma/provider.d.ts +31 -10
  87. package/build/src/akuma/provider.js +105 -14
  88. package/build/src/akuma/providers/acp/events.js +29 -19
  89. package/build/src/akuma/providers/acp/index.js +2 -3
  90. package/build/src/akuma/providers/claude/events.js +110 -69
  91. package/build/src/akuma/providers/claude/index.d.ts +2 -2
  92. package/build/src/akuma/providers/claude/index.js +4 -8
  93. package/build/src/akuma/providers/codex-app-server/events.js +17 -2
  94. package/build/src/akuma/providers/codex-app-server/index.js +17 -11
  95. package/build/src/akuma/providers/decode.d.ts +33 -0
  96. package/build/src/akuma/providers/decode.js +65 -0
  97. package/build/src/akuma/providers/execution-environment.d.ts +2 -1
  98. package/build/src/akuma/providers/execution-environment.js +3 -0
  99. package/build/src/akuma/providers/grok-build/index.js +16 -48
  100. package/build/src/akuma/providers/index.js +2 -0
  101. package/build/src/akuma/providers/lifecycle.d.ts +9 -0
  102. package/build/src/akuma/providers/lifecycle.js +10 -0
  103. package/build/src/akuma/providers/opencode-sdk/event-stream.d.ts +3 -3
  104. package/build/src/akuma/providers/opencode-sdk/event-stream.js +2 -25
  105. package/build/src/akuma/providers/opencode-sdk/events.d.ts +7 -43
  106. package/build/src/akuma/providers/opencode-sdk/events.js +146 -296
  107. package/build/src/akuma/providers/opencode-sdk/index.js +270 -431
  108. package/build/src/akuma/providers/opencode-sdk/native-http.d.ts +9 -0
  109. package/build/src/akuma/providers/opencode-sdk/native-http.js +69 -0
  110. package/build/src/akuma/providers/opencode-sdk/session.d.ts +42 -36
  111. package/build/src/akuma/providers/opencode-sdk/session.js +75 -84
  112. package/build/src/akuma/providers/paseo/events.d.ts +33 -0
  113. package/build/src/akuma/providers/paseo/events.js +204 -0
  114. package/build/src/akuma/providers/paseo/index.d.ts +9 -0
  115. package/build/src/akuma/providers/paseo/index.js +357 -0
  116. package/build/src/akuma/providers/pi/events.d.ts +2 -0
  117. package/build/src/akuma/providers/pi/events.js +36 -38
  118. package/build/src/akuma/providers/pi/index.js +21 -35
  119. package/build/src/akuma/publication.d.ts +2 -0
  120. package/build/src/akuma/publication.js +5 -0
  121. package/build/src/akuma/request-lifecycle.d.ts +4 -2
  122. package/build/src/akuma/request-lifecycle.js +27 -12
  123. package/build/src/akuma/request-rendezvous.js +38 -55
  124. package/build/src/akuma/request-serve.d.ts +1 -1
  125. package/build/src/akuma/request-serve.js +77 -113
  126. package/build/src/akuma/requests.d.ts +3 -1
  127. package/build/src/akuma/requests.js +32 -0
  128. package/build/src/akuma/selection-observation.d.ts +129 -53
  129. package/build/src/akuma/selection-observation.js +3 -9
  130. package/build/src/akuma/turn-drive.d.ts +3 -0
  131. package/build/src/akuma/turn-drive.js +28 -10
  132. package/build/src/akuma-body.js +12 -22
  133. package/build/src/cli/akuma-caller.d.ts +3 -0
  134. package/build/src/cli/akuma-caller.js +31 -0
  135. package/build/src/cli/commands/akuma-grammar.js +10 -16
  136. package/build/src/cli/commands/akuma.js +25 -55
  137. package/build/src/cli/commands/contract-grammar.d.ts +9 -7
  138. package/build/src/cli/commands/contract-grammar.js +40 -47
  139. package/build/src/cli/commands/contract-help.d.ts +17 -7
  140. package/build/src/cli/commands/contract-help.js +50 -11
  141. package/build/src/cli/commands/contract.js +34 -63
  142. package/build/src/cli/commands/debt-grammar.d.ts +19 -0
  143. package/build/src/cli/commands/debt-grammar.js +323 -0
  144. package/build/src/cli/commands/debt-query.d.ts +5 -0
  145. package/build/src/cli/commands/{task-query.js → debt-query.js} +9 -9
  146. package/build/src/cli/commands/{task.d.ts → debt.d.ts} +2 -2
  147. package/build/src/cli/commands/{task.js → debt.js} +87 -164
  148. package/build/src/cli/coordinates.d.ts +1 -1
  149. package/build/src/cli/coordinates.js +1 -1
  150. package/build/src/cli/parse.d.ts +5 -5
  151. package/build/src/cli/parse.js +17 -16
  152. package/build/src/cli/render/akuma-activity.d.ts +11 -83
  153. package/build/src/cli/render/akuma-activity.js +20 -382
  154. package/build/src/cli/render/catalog.d.ts +4 -7
  155. package/build/src/cli/render/catalog.js +5 -5
  156. package/build/src/cli/render/contract-history.js +3 -6
  157. package/build/src/cli/render/contract.js +17 -52
  158. package/build/src/cli/render/debt.d.ts +28 -0
  159. package/build/src/cli/render/{task.js → debt.js} +55 -133
  160. package/build/src/cli/render/kanshi.js +28 -28
  161. package/build/src/cli/render/marks.d.ts +5 -5
  162. package/build/src/cli/render/marks.js +4 -10
  163. package/build/src/cli/render/nuke.js +1 -1
  164. package/build/src/cli/render/receipt.js +3 -10
  165. package/build/src/cli/runtime.d.ts +1 -1
  166. package/build/src/cli/runtime.js +14 -20
  167. package/build/src/cli/scan.d.ts +1 -1
  168. package/build/src/cli/selectors.d.ts +3 -6
  169. package/build/src/cli/selectors.js +22 -53
  170. package/build/src/cli/usage.d.ts +1 -1
  171. package/build/src/cli/usage.js +4 -4
  172. package/build/src/contract-worktree.js +3 -3
  173. package/build/src/debt/board.d.ts +160 -0
  174. package/build/src/{task → debt}/board.js +89 -89
  175. package/build/src/debt/catalog.d.ts +11 -0
  176. package/build/src/debt/catalog.js +59 -0
  177. package/build/src/{task → debt}/context.d.ts +1 -1
  178. package/build/src/{task → debt}/context.js +1 -1
  179. package/build/src/{task → debt}/created-observation.d.ts +6 -10
  180. package/build/src/{task → debt}/created-observation.js +7 -7
  181. package/build/src/debt/document.d.ts +37 -0
  182. package/build/src/{task → debt}/document.js +37 -37
  183. package/build/src/debt/identity.d.ts +21 -0
  184. package/build/src/{task → debt}/identity.js +35 -35
  185. package/build/src/debt/index.d.ts +111 -0
  186. package/build/src/debt/index.js +203 -0
  187. package/build/src/{task → debt}/input.d.ts +8 -8
  188. package/build/src/{task → debt}/input.js +21 -21
  189. package/build/src/debt/lifecycle-operations.d.ts +6 -0
  190. package/build/src/{task → debt}/lifecycle-operations.js +27 -31
  191. package/build/src/debt/mutation-result.d.ts +981 -0
  192. package/build/src/debt/mutation-result.js +109 -0
  193. package/build/src/debt/mutation.d.ts +424 -0
  194. package/build/src/debt/mutation.js +223 -0
  195. package/build/src/debt/operation-types.d.ts +71 -0
  196. package/build/src/debt/operations.d.ts +28 -0
  197. package/build/src/{task → debt}/operations.js +59 -60
  198. package/build/src/debt/query.d.ts +76 -0
  199. package/build/src/{task → debt}/query.js +55 -55
  200. package/build/src/{task → debt}/store.d.ts +13 -13
  201. package/build/src/{task → debt}/store.js +58 -49
  202. package/build/src/debt/view.d.ts +4 -0
  203. package/build/src/debt/view.js +7 -0
  204. package/build/src/dispatch/index.d.ts +3 -3
  205. package/build/src/dispatch/index.js +1 -2
  206. package/build/src/git/observe.d.ts +6 -2
  207. package/build/src/git/observe.js +6 -2
  208. package/build/src/identity/coordinates.d.ts +1 -1
  209. package/build/src/index.d.ts +17 -17
  210. package/build/src/index.js +11 -11
  211. package/build/src/kanshi/index.d.ts +2 -2
  212. package/build/src/kanshi/read.js +54 -39
  213. package/build/src/kanshi/report.d.ts +7 -7
  214. package/build/src/kanshi/select.js +4 -4
  215. package/build/src/library/address.js +2 -4
  216. package/build/src/library/akuma-creation.d.ts +5 -1
  217. package/build/src/library/akuma-creation.js +27 -47
  218. package/build/src/library/akumas.d.ts +7 -7
  219. package/build/src/library/akumas.js +13 -9
  220. package/build/src/library/bind.d.ts +17 -17
  221. package/build/src/library/bind.js +16 -12
  222. package/build/src/library/contract-address.d.ts +9 -0
  223. package/build/src/library/contract-address.js +71 -0
  224. package/build/src/library/contract-composition.d.ts +4 -7
  225. package/build/src/library/contract-composition.js +6 -18
  226. package/build/src/library/contract-creation.d.ts +3 -5
  227. package/build/src/library/contract-creation.js +24 -22
  228. package/build/src/library/contract-execution.js +21 -26
  229. package/build/src/library/contract-outcomes.d.ts +2 -14
  230. package/build/src/library/contract-outcomes.js +2 -13
  231. package/build/src/library/contract-types.d.ts +38 -4
  232. package/build/src/library/delivery.d.ts +0 -2
  233. package/build/src/library/delivery.js +2 -5
  234. package/build/src/library/input.d.ts +2 -2
  235. package/build/src/library/input.js +5 -5
  236. package/build/src/library/invocation.js +2 -2
  237. package/build/src/library/keiyaku.d.ts +0 -5
  238. package/build/src/library/keiyaku.js +6 -12
  239. package/build/src/library/nuke.js +3 -3
  240. package/build/src/library/outcome.d.ts +626 -625
  241. package/build/src/library/outcome.js +13 -3
  242. package/build/src/library/reconcile.js +8 -16
  243. package/build/src/library/refusal.d.ts +2 -2
  244. package/build/src/library/repo.d.ts +3 -2
  245. package/build/src/library/repo.js +10 -5
  246. package/build/src/library/selection.d.ts +5 -8
  247. package/build/src/library/selection.js +22 -73
  248. package/build/src/plugin/akuma-signals.d.ts +2 -0
  249. package/build/src/plugin/akuma-signals.js +4 -0
  250. package/build/src/plugin/public.d.ts +32 -2
  251. package/build/src/plugin/runtime.js +5 -3
  252. package/build/src/protocol/amend.js +3 -2
  253. package/build/src/protocol/attempt.js +3 -8
  254. package/build/src/protocol/bind.d.ts +2 -2
  255. package/build/src/protocol/bind.js +1 -1
  256. package/build/src/protocol/deliver.js +3 -3
  257. package/build/src/protocol/operations.d.ts +1 -3
  258. package/build/src/protocol/operations.js +0 -8
  259. package/build/src/protocol/outcome.d.ts +1 -2
  260. package/build/src/protocol/outcome.js +1 -13
  261. package/build/src/protocol/placement.js +3 -10
  262. package/build/src/protocol/read/status.js +31 -70
  263. package/build/src/protocol/reintegrate.js +3 -10
  264. package/build/src/protocol/review.js +3 -3
  265. package/build/src/protocol/run.d.ts +5 -1
  266. package/build/src/protocol/run.js +3 -3
  267. package/build/src/runtime/proc/windows-launch.exe +0 -0
  268. package/build/src/settlement/fence.d.ts +3 -3
  269. package/build/src/settlement/fence.js +4 -4
  270. package/build/src/settlement/holder.d.ts +16 -16
  271. package/build/src/settlement/holder.js +69 -63
  272. package/build/src/settlement/settle.d.ts +5 -5
  273. package/build/src/settlement/settle.js +53 -53
  274. package/package.json +19 -9
  275. package/plugins/paseo/plugin/index.server.ts +17 -0
  276. package/plugins/paseo/plugin/paseo-plugin.json +4 -0
  277. package/plugins/paseo/plugin/server/attach.ts +52 -0
  278. package/plugins/paseo/plugin/server/keiyaku.ts +44 -0
  279. package/plugins/paseo/plugin/server/message.ts +60 -0
  280. package/plugins/paseo/plugin/server/mirror.ts +193 -0
  281. package/plugins/paseo/plugin/server/model.ts +14 -0
  282. package/plugins/paseo/plugin/server/provider.ts +276 -0
  283. package/plugins/paseo/plugin/server/sdk-worker.ts +237 -0
  284. package/plugins/paseo/plugin/server/sdk.ts +289 -0
  285. package/plugins/paseo/plugin/server/stop.ts +26 -0
  286. package/plugins/paseo/plugin/server/transport.ts +35 -0
  287. package/plugins/paseo/plugin/shared/attach-rpc.ts +10 -0
  288. package/plugins/paseo/plugin/shared/refresh-rpc.ts +6 -0
  289. package/plugins/paseo/plugin/tsconfig.json +6 -0
  290. package/plugins/pi/keiyaku.ts +57 -0
  291. package/skills/keiyaku/SKILL.md +43 -0
  292. package/skills/keiyaku-akuma/SKILL.md +130 -0
  293. package/skills/keiyaku-akuma/references/automation.md +269 -0
  294. package/skills/keiyaku-babysit/SKILL.md +65 -0
  295. package/skills/keiyaku-bind/SKILL.md +216 -0
  296. package/skills/keiyaku-bind/agents/openai.yaml +4 -0
  297. package/skills/keiyaku-debt/SKILL.md +91 -0
  298. package/skills/keiyaku-workflow/SKILL.md +129 -0
  299. package/build/plugins/claude/skills/keiyaku-task/SKILL.md +0 -104
  300. package/build/plugins/codex/skills/keiyaku-task/SKILL.md +0 -104
  301. package/build/plugins/opencode/skills/keiyaku-task/SKILL.md +0 -104
  302. package/build/skills/keiyaku-task/SKILL.md +0 -104
  303. package/build/src/cli/commands/amend.d.ts +0 -12
  304. package/build/src/cli/commands/amend.js +0 -10
  305. package/build/src/cli/commands/bind.d.ts +0 -11
  306. package/build/src/cli/commands/bind.js +0 -23
  307. package/build/src/cli/commands/task-grammar.d.ts +0 -19
  308. package/build/src/cli/commands/task-grammar.js +0 -384
  309. package/build/src/cli/commands/task-query.d.ts +0 -5
  310. package/build/src/cli/render/task.d.ts +0 -41
  311. package/build/src/dispatch/association.d.ts +0 -1
  312. package/build/src/dispatch/association.js +0 -1
  313. package/build/src/task/board.d.ts +0 -168
  314. package/build/src/task/catalog.d.ts +0 -11
  315. package/build/src/task/catalog.js +0 -59
  316. package/build/src/task/compose-language.d.ts +0 -57
  317. package/build/src/task/compose-language.js +0 -364
  318. package/build/src/task/compose-parser.d.ts +0 -32
  319. package/build/src/task/compose-parser.js +0 -180
  320. package/build/src/task/compose.d.ts +0 -16
  321. package/build/src/task/compose.js +0 -189
  322. package/build/src/task/document.d.ts +0 -39
  323. package/build/src/task/identity.d.ts +0 -21
  324. package/build/src/task/index.d.ts +0 -138
  325. package/build/src/task/index.js +0 -269
  326. package/build/src/task/lifecycle-operations.d.ts +0 -6
  327. package/build/src/task/mutation-result.d.ts +0 -1726
  328. package/build/src/task/mutation-result.js +0 -198
  329. package/build/src/task/mutation.d.ts +0 -590
  330. package/build/src/task/mutation.js +0 -262
  331. package/build/src/task/operation-types.d.ts +0 -71
  332. package/build/src/task/operations.d.ts +0 -28
  333. package/build/src/task/query.d.ts +0 -76
  334. package/build/src/task/view.d.ts +0 -4
  335. package/build/src/task/view.js +0 -7
  336. /package/build/src/{task → debt}/operation-types.js +0 -0
@@ -1,451 +1,301 @@
1
- import { AgentEventChannel, createProviderAttempt, } from "../../provider.js";
1
+ import { AgentEventChannel, createProviderAttempt, launchPrompt, tellRegistry, } from "../../provider.js";
2
+ import { nonblank, object } from "../decode.js";
3
+ import { providerLifecycle } from "../lifecycle.js";
2
4
  import { ownEventStream } from "./event-stream.js";
3
- import { createEventState, mapEvent } from "./events.js";
5
+ import { createEventState, diagnostic, mapEvent } from "./events.js";
4
6
  import { coordinate, loadOpencode, messageId, OPENCODE_SDK_PROVIDER, parseModel, } from "./session.js";
5
- function object(value) {
6
- return value !== null && typeof value === "object" && !Array.isArray(value)
7
- ? value
8
- : 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
+ function sessionId(value) {
8
+ if (!("sessionId" in value) || value.sessionId === undefined)
20
9
  throw new Error("OpenCode resume requires sessionId");
21
- }
22
- return coordinateValue.sessionId;
10
+ return value.sessionId;
23
11
  }
24
12
  function admit(options) {
25
13
  if (options.network !== undefined)
26
14
  throw new Error("OpenCode does not support explicit network");
27
- if (options.systemPromptMode === "replace") {
28
- throw new Error("OpenCode V1 does not support replacing the native system prompt");
29
- }
15
+ if (options.systemPromptMode === "replace")
16
+ throw new Error("OpenCode v2 does not support replacing the native system prompt per Turn");
30
17
  if (options.model !== undefined)
31
18
  parseModel(options.model);
19
+ if (options.effort !== undefined && options.model === undefined)
20
+ throw new Error("OpenCode effort requires an explicit model");
32
21
  }
33
- function eventValue(value) {
34
- if (typeof value !== "string")
35
- return value;
36
- try {
37
- return JSON.parse(value);
38
- }
39
- catch {
40
- return { type: value };
41
- }
42
- }
43
- function promptBody(input, messageID) {
44
- const model = input.options.model === undefined ? undefined : parseModel(input.options.model);
45
- const promptText = [input.body, ...input.launchTells.map((tell) => tell.text)]
46
- .filter((part) => part.length > 0)
47
- .join("\n\n");
48
- return {
49
- messageID,
50
- ...(model === undefined ? {} : { model }),
51
- ...(input.options.effort === undefined ? {} : { variant: input.options.effort }),
52
- ...(input.options.systemPrompt === undefined ? {} : { system: input.options.systemPrompt }),
53
- parts: [{ type: "text", text: promptText }],
54
- };
55
- }
56
- function messageRows(value, sessionId) {
57
- const response = object(value);
58
- const rows = Array.isArray(response?.data) ? response.data : Array.isArray(value) ? value : [];
59
- return rows.flatMap((row) => {
60
- const entry = object(row);
61
- const info = object(entry?.info);
62
- if (info?.sessionID !== sessionId)
63
- return [];
64
- return [{ info, parts: Array.isArray(entry?.parts) ? entry.parts : [] }];
22
+ function ownRuntime(custody, runtime) {
23
+ let closing;
24
+ let resolve;
25
+ let reject;
26
+ const closed = new Promise((yes, no) => {
27
+ resolve = yes;
28
+ reject = no;
65
29
  });
66
- }
67
- function newest(rows) {
68
- return rows.reduce((latest, row) => {
69
- if (latest === undefined)
70
- return row;
71
- const at = Number(object(row.info.time)?.created ?? 0);
72
- const latestAt = Number(object(latest.info.time)?.created ?? 0);
73
- return at > latestAt || (at === latestAt && String(row.info.id) > String(latest.info.id)) ? row : latest;
74
- }, undefined);
75
- }
76
- async function readTurnResult(input) {
77
- try {
78
- const response = await input.session.messages({
79
- path: { id: input.sessionId },
80
- query: { directory: input.cwd },
81
- throwOnError: true,
82
- });
83
- const rows = messageRows(response, input.sessionId);
84
- const user = rows.find((row) => row.info.role === "user" && row.info.id === input.messageID);
85
- if (user === undefined)
86
- return { kind: "failed", diagnostic: "OpenCode completed without the admitted user message" };
87
- const userId = text(user.info.id);
88
- const assistant = newest(rows.filter((row) => row.info.role === "assistant" && row.info.parentID === userId && row.info.summary !== true));
89
- const historyId = text(assistant?.info.id);
90
- if (input.state.failure !== undefined)
91
- return { kind: "failed", diagnostic: input.state.failure };
92
- if (assistant === undefined)
93
- return { kind: "failed", diagnostic: "OpenCode completed without a native assistant answer" };
94
- if (assistant.info.error !== undefined)
95
- return { kind: "failed", diagnostic: diagnostic(assistant.info.error) };
96
- const answer = assistant.parts
97
- .map((part) => object(part))
98
- .filter((part) => part?.type === "text")
99
- .map((part) => part?.text)
100
- .filter((part) => typeof part === "string")
101
- .join("\n\n");
102
- return historyId === undefined ? { kind: "answered", answer } : { kind: "answered", answer, historyId };
103
- }
104
- catch (error) {
105
- return { kind: "failed", diagnostic: diagnostic(error) };
106
- }
107
- }
108
- function nativeSessionId(value) {
109
- const event = object(value);
110
- const properties = object(event?.properties);
111
- const direct = text(properties?.sessionID);
112
- if (direct !== undefined)
113
- return direct;
114
- const info = object(properties?.info);
115
- const part = object(properties?.part);
116
- return text(info?.sessionID) ?? text(part?.sessionID);
117
- }
118
- function scopedProperties(value, sessionId) {
119
- const properties = object(object(value)?.properties);
120
- return nativeSessionId(value) === sessionId ? properties : undefined;
121
- }
122
- function nativeProgress(type, properties, busy) {
123
- if (type === "session.idle")
124
- return { busy, terminal: busy };
125
- if (type !== "session.status")
126
- return { busy, terminal: false };
127
- const status = object(properties.status)?.type;
128
- const nextBusy = busy || status === "busy";
129
- return { busy: nextBusy, terminal: status === "idle" && nextBusy };
130
- }
131
- function startsTurn(value, sessionId, messageID) {
132
- const event = object(value);
133
- const info = object(object(event?.properties)?.info);
134
- return (event?.type === "message.updated" && info?.sessionID === sessionId && info.role === "user" && info.id === messageID);
135
- }
136
- function recognizeAdmission(value, sessionId, admission) {
137
- const event = object(value);
138
- const properties = object(event?.properties);
139
- const info = object(properties?.info);
140
- if (event?.type === "message.updated" &&
141
- info?.sessionID === sessionId &&
142
- info.role === "user" &&
143
- typeof info.id === "string") {
144
- admission.admit(info.id);
145
- }
146
- }
147
- function observeEvent(input) {
148
- const observedSessionId = nativeSessionId(input.value);
149
- if (observedSessionId !== undefined && observedSessionId !== input.sessionId) {
150
- return { active: input.active, busy: input.busy, terminal: false };
151
- }
152
- const event = object(input.value);
153
- const properties = scopedProperties(input.value, input.sessionId);
154
- if (properties === undefined) {
155
- if (input.active)
156
- mapEvent(input.value, input.events, input.state);
157
- return { active: input.active, busy: input.busy, terminal: false };
158
- }
159
- const starts = !input.active && startsTurn(input.value, input.sessionId, input.messageID);
160
- const active = input.active || starts;
161
- const busy = starts ? false : input.busy;
162
- recognizeAdmission(input.value, input.sessionId, input.admission);
163
- const submittedError = event?.type === "session.error" && input.submissionState.started;
164
- if (!active && !submittedError)
165
- return { active, busy, terminal: false };
166
- mapEvent(input.value, input.events, input.state);
167
- if (event?.type === "session.error") {
168
- input.admission.end();
169
- return {
170
- active,
171
- busy,
172
- terminal: true,
173
- result: { kind: "failed", diagnostic: input.state.failure ?? diagnostic(properties.error) },
174
- };
175
- }
176
- const progress = nativeProgress(event?.type, properties, busy);
177
- return { active, busy: progress.busy, terminal: progress.terminal };
178
- }
179
- async function observeTurn(input) {
180
- let active = false;
181
- let busy = false;
182
- try {
183
- for (;;) {
184
- const next = await input.stream.next();
185
- if (next.done)
186
- return { kind: "failed", diagnostic: "OpenCode event stream ended before Turn completion" };
187
- const observed = observeEvent({
188
- value: eventValue(next.value),
189
- sessionId: input.sessionId,
190
- messageID: input.messageID,
191
- active,
192
- busy,
193
- admission: input.admission,
194
- events: input.events,
195
- state: input.state,
196
- submissionState: input.submissionState,
197
- });
198
- active = observed.active;
199
- busy = observed.busy;
200
- if (observed.result !== undefined)
201
- return observed.result;
202
- if (!observed.terminal)
203
- continue;
204
- input.admission.end();
205
- return await readTurnResult({ ...input, messageID: input.admission.latest() });
30
+ const close = () => {
31
+ if (closing === undefined) {
32
+ closing = runtime.close();
33
+ void closing.then(resolve, reject);
206
34
  }
207
- }
208
- catch (error) {
209
- return { kind: "failed", diagnostic: `OpenCode event stream failed: ${diagnostic(error)}` };
210
- }
211
- finally {
212
- input.admission.end();
213
- }
35
+ return closing;
36
+ };
37
+ custody.own({ closed, abort: close, forceDispose: close });
38
+ return close;
214
39
  }
215
40
  function liveAdmission(initial) {
216
41
  let latest = initial;
217
42
  let ended = false;
218
- const pending = new Map();
43
+ const submitted = new Set([initial]);
44
+ const tells = tellRegistry();
219
45
  const wait = (id) => {
220
- if (ended)
221
- return Promise.resolve({ kind: "turn-ended" });
222
- return new Promise((resolve, reject) => {
223
- pending.set(id, { resolve, reject });
224
- });
46
+ submitted.add(id);
47
+ return tells.register(id, id);
225
48
  };
226
49
  return {
227
50
  initial: wait(initial),
228
51
  latest: () => latest,
229
52
  open: () => !ended,
53
+ owns: (id) => submitted.has(id),
230
54
  wait,
231
55
  admit(id) {
232
- const waiter = pending.get(id);
233
- if (waiter === undefined || ended)
234
- return;
235
- pending.delete(id);
236
- latest = id;
237
- waiter.resolve({ kind: "accepted", fence: id });
238
- },
239
- fail(id, error) {
240
- pending.get(id)?.reject(error);
241
- pending.delete(id);
56
+ if (tells.accept(id))
57
+ latest = id;
242
58
  },
243
- end() {
244
- if (ended)
245
- return;
59
+ fail: tells.fail,
60
+ end: () => {
246
61
  ended = true;
247
- for (const waiter of pending.values())
248
- waiter.resolve({ kind: "turn-ended" });
249
- pending.clear();
62
+ tells.end();
250
63
  },
251
64
  };
252
65
  }
253
- function terminalSettlement(events, close) {
254
- let finish;
255
- const completion = new Promise((resolve) => {
256
- let settlement;
257
- finish = (result) => {
258
- settlement ??= (async () => {
259
- // Whole closure is proved before narration ends. The driver therefore
260
- // stays inside its Heart/control observation race while retirement is
261
- // outstanding, so a rejected or unprovable closure still reaches attempt
262
- // custody's bounded hung handling instead of parking on a pending
263
- // completion. Ending narration first would move that await outside the
264
- // driver's race and make the Body unresponsive to stop or Heart loss.
265
- await close();
266
- events.end();
267
- resolve(result);
268
- })();
269
- return settlement;
270
- };
271
- });
272
- return { completion, finish };
273
- }
274
- /**
275
- * One memoized whole-close: stream retirement and existing server retirement
276
- * start together on every terminal path, so a live socket can never outwait the
277
- * server's death.
278
- */
279
- function wholeClosure(retireStream, closeServer) {
280
- let closing;
281
- return () => {
282
- closing ??= Promise.all([retireStream(), closeServer()]).then(() => undefined);
283
- return closing;
284
- };
285
- }
286
- async function forceDisposeOpencode(abortController, close, finish) {
287
- abortController.abort();
288
- await close();
289
- await finish({ kind: "failed", diagnostic: "OpenCode session force-disposed" });
290
- }
291
- async function loadDriveRuntime(execution, input, signal, loader, onRuntime) {
292
- const runtime = await loadOpencode({
293
- execution,
294
- cwd: input.cwd,
295
- signal,
296
- ...(loader === undefined ? {} : { loader }),
297
- ...(onRuntime === undefined ? {} : { onRuntime }),
298
- ...(input.requests === undefined ? {} : { requests: input.requests.dir }),
299
- });
300
- if (signal.aborted) {
301
- await runtime.close();
302
- signal.throwIfAborted();
66
+ async function answer(runtime, id, assistantID, terminalID) {
67
+ const response = object(await runtime.client.session.message({ id, messageID: assistantID }));
68
+ const message = object(response?.data);
69
+ if (message?.id !== assistantID || message.type !== "assistant" || object(message.time)?.completed === undefined) {
70
+ return { kind: "failed", diagnostic: "OpenCode completed without its exact completed assistant message" };
303
71
  }
304
- return runtime;
72
+ if (message.error !== undefined)
73
+ return { kind: "failed", diagnostic: diagnostic(message.error) };
74
+ if (!Array.isArray(message.content))
75
+ return { kind: "failed", diagnostic: "OpenCode assistant content missing" };
76
+ const text = message.content
77
+ .flatMap((item) => {
78
+ const part = object(item);
79
+ return part?.type === "text" && typeof part.text === "string" ? [part.text] : [];
80
+ })
81
+ .join("\n\n");
82
+ // v2's terminal event projects a real idle message with this exact ID. Forking
83
+ // BEFORE that message includes the answer, without copying later native work.
84
+ const boundary = terminalID.replace(/^evt_/u, "msg_");
85
+ const projected = object(object(await runtime.client.session.message({ id, messageID: boundary }))?.data);
86
+ if (projected?.id !== boundary || projected.type !== "idle" || projected.outcome !== "succeeded") {
87
+ return { kind: "failed", diagnostic: "OpenCode terminal fork boundary was not projected" };
88
+ }
89
+ return { kind: "answered", answer: text, historyId: boundary };
305
90
  }
306
- function ownRuntime(custody, runtime) {
307
- let closing;
308
- let settleRuntimeClosed;
309
- let rejectRuntimeClosed;
310
- const runtimeClosed = new Promise((resolve, reject) => {
311
- settleRuntimeClosed = resolve;
312
- rejectRuntimeClosed = reject;
313
- });
314
- const close = () => {
315
- if (closing === undefined) {
316
- closing = runtime.close();
317
- void closing.then(settleRuntimeClosed, rejectRuntimeClosed);
91
+ async function observe(input) {
92
+ const state = createEventState(input.id);
93
+ let delivered;
94
+ let step;
95
+ try {
96
+ for (;;) {
97
+ const next = await input.stream.next();
98
+ if (next.done)
99
+ return { kind: "failed", diagnostic: "OpenCode event stream ended before Turn completion" };
100
+ const event = object(next.value);
101
+ const data = object(event?.data);
102
+ if (data?.sessionID !== input.id)
103
+ continue;
104
+ const type = event?.type;
105
+ if (type === "session.inbox.enqueued" && typeof data.inboxID === "string" && object(data.item)?.type === "user") {
106
+ input.admission.admit(data.inboxID);
107
+ }
108
+ if (type === "session.inbox.delivered" && typeof data.inboxID === "string") {
109
+ if (!input.admission.owns(data.inboxID)) {
110
+ return {
111
+ kind: "failed",
112
+ diagnostic: "OpenCode consumed another writer's input; exact answer attribution unavailable",
113
+ };
114
+ }
115
+ input.admission.admit(data.inboxID);
116
+ delivered = data.inboxID;
117
+ }
118
+ if (type === "session.step.started" && delivered !== undefined && typeof data.assistantMessageID === "string") {
119
+ step = { id: data.assistantMessageID, input: delivered, ended: false };
120
+ }
121
+ if (data.assistantMessageID !== undefined && data.assistantMessageID !== step?.id)
122
+ continue;
123
+ if (delivered !== undefined)
124
+ mapEvent(event, input.events, state);
125
+ if (type === "session.step.ended" && step !== undefined)
126
+ step.ended = true;
127
+ if (type === "session.step.failed" || type === "session.execution.failed") {
128
+ return { kind: "failed", diagnostic: diagnostic(data.error) };
129
+ }
130
+ if (type === "session.execution.interrupted")
131
+ return { kind: "failed", diagnostic: "OpenCode native execution interrupted" };
132
+ if (type !== "session.execution.succeeded" || delivered === undefined)
133
+ continue;
134
+ if (step === undefined || !step.ended || step.input !== input.admission.latest()) {
135
+ return { kind: "failed", diagnostic: "OpenCode completed without an assistant for the admitted input" };
136
+ }
137
+ const terminalID = nonblank(event?.id);
138
+ if (terminalID === undefined || !terminalID.startsWith("evt_"))
139
+ return { kind: "failed", diagnostic: "OpenCode terminal identity missing" };
140
+ return await answer(input.runtime, input.id, step.id, terminalID);
318
141
  }
319
- return closing;
320
- };
321
- custody?.own({ closed: runtimeClosed, abort: close, forceDispose: close });
322
- return close;
142
+ }
143
+ catch (error) {
144
+ return { kind: "failed", diagnostic: `OpenCode observation failed: ${diagnostic(error)}` };
145
+ }
146
+ finally {
147
+ input.admission.end();
148
+ }
323
149
  }
324
- async function openDriveSession(runtime, input, resumeSessionId, signal) {
150
+ async function openSession(runtime, input) {
151
+ const model = input.options.model === undefined
152
+ ? undefined
153
+ : {
154
+ ...parseModel(input.options.model),
155
+ ...(input.options.effort === undefined ? {} : { variant: input.options.effort }),
156
+ };
325
157
  const session = runtime.client.session;
326
- const response = await (input.session.kind === "fresh"
327
- ? session.create({ query: { directory: input.cwd }, throwOnError: true })
328
- : session.get({ path: { id: resumeSessionId }, query: { directory: input.cwd }, throwOnError: true }));
329
- signal.throwIfAborted();
330
- const info = object(object(response)?.data) ?? object(response);
331
- const sessionId = text(info?.id) ?? resumeSessionId;
332
- if (sessionId === undefined)
158
+ const response = input.session.kind === "fresh"
159
+ ? await session.create({
160
+ body: { location: { directory: input.cwd }, ...(model === undefined ? {} : { model }) },
161
+ })
162
+ : await session.get({ id: sessionId(input.session.coordinate) });
163
+ const info = object(object(response)?.data);
164
+ const id = nonblank(info?.id);
165
+ if (id === undefined)
333
166
  throw new Error("OpenCode did not return a session id");
334
- return { session, sessionId };
335
- }
336
- function createLiveSession(input) {
337
- let submissions = input.admission.initial.then(() => undefined);
338
- const submit = (tell) => {
339
- const submission = async () => {
340
- if (!input.admission.open())
341
- return { kind: "turn-ended" };
342
- const receipt = input.admission.wait(tell.id);
343
- void input.session
344
- .promptAsync({
345
- path: { id: input.sessionId },
346
- query: { directory: input.cwd },
347
- body: input.promptBody(tell.id, tell.text),
348
- throwOnError: true,
349
- })
350
- .catch((error) => input.admission.fail(tell.id, error));
351
- return await receipt;
352
- };
353
- const next = submissions.then(submission);
354
- submissions = next.then(() => undefined, () => undefined);
355
- return next;
356
- };
357
- return {
358
- admission: { fence: input.sessionId },
359
- events: input.events,
360
- completion: input.completion,
361
- tell: (tell) => submit({ ...tell, id: input.nextMessageId() }),
362
- abort: async () => {
363
- input.admission.end();
364
- input.abortController.abort();
365
- void input.session
366
- .abort({ path: { id: input.sessionId }, query: { directory: input.cwd }, throwOnError: true })
367
- .catch(() => undefined);
368
- await input.finish({ kind: "failed", diagnostic: "OpenCode session interrupted" });
369
- },
370
- forceDispose: () => {
371
- input.admission.end();
372
- return forceDisposeOpencode(input.abortController, input.close, input.finish);
373
- },
374
- };
167
+ const active = object(object(await session.active())?.data);
168
+ if (active === undefined)
169
+ throw new Error("OpenCode active execution response missing");
170
+ if (active[id] !== undefined)
171
+ throw new Error("OpenCode native session is already busy");
172
+ let selected = object(info?.model);
173
+ if (input.session.kind === "resume" && model !== undefined) {
174
+ await session.switchModel({ id, model });
175
+ selected = object(object(object(await session.get({ id }))?.data)?.model);
176
+ }
177
+ if (model !== undefined &&
178
+ (selected?.providerID !== model.providerID ||
179
+ selected.id !== model.id ||
180
+ (selected.variant ?? "default") !== (input.options.effort ?? "default"))) {
181
+ throw new Error("OpenCode did not select the requested model");
182
+ }
183
+ if (input.options.systemPrompt !== undefined) {
184
+ // Native instruction entries join the system transcript and survive resume
185
+ // and fork. Updating the same entry announces an upstream instruction change.
186
+ await session.instructions({ id, text: input.options.systemPrompt });
187
+ }
188
+ return id;
375
189
  }
376
190
  async function drive(execution, input, loader, custody) {
377
191
  admit(input.options);
378
- const signal = input.signal ?? new AbortController().signal;
379
- const resumeSessionId = input.session.kind === "resume" ? opencodeSessionId(input.session.coordinate) : undefined;
380
- const abortController = new AbortController();
381
- const abortSetup = () => abortController.abort(signal.reason);
382
- signal.addEventListener("abort", abortSetup, { once: true });
383
- signal.throwIfAborted();
384
- let closeOnce;
385
- const runtime = await loadDriveRuntime(execution, input, abortController.signal, loader, (ready) => {
386
- closeOnce = ownRuntime(custody, ready);
192
+ if (input.schemaJson !== undefined)
193
+ throw new Error("OpenCode v2 does not expose native structured answer schemas");
194
+ if (input.session.kind === "resume")
195
+ sessionId(input.session.coordinate);
196
+ let closeRuntime;
197
+ const runtime = await loadOpencode({
198
+ execution,
199
+ cwd: input.cwd,
200
+ signal: custody.signal,
201
+ ...(loader === undefined ? {} : { loader }),
202
+ onRuntime: (ready) => {
203
+ closeRuntime = ownRuntime(custody, ready);
204
+ },
205
+ ...(input.requests === undefined ? {} : { requests: input.requests.dir }),
387
206
  });
388
207
  const stream = ownEventStream({
389
208
  directory: input.cwd,
390
- signal: abortController.signal,
391
- ...(custody === undefined ? {} : { custody }),
392
- subscribe: (request) => runtime.client.event.subscribe(request),
209
+ signal: custody.signal,
210
+ custody,
211
+ subscribe: runtime.client.event.subscribe,
393
212
  });
394
- const wholeClose = wholeClosure(() => stream.retire(), () => closeOnce());
213
+ let closing;
214
+ const close = () => (closing ??= Promise.all([stream.retire(), closeRuntime()]).then(() => undefined));
395
215
  try {
396
- const { session, sessionId } = await openDriveSession(runtime, input, resumeSessionId, abortController.signal);
397
- const events = new AgentEventChannel();
398
- const state = createEventState(sessionId);
399
- const messageSequence = { value: 0 };
400
- const nextMessageId = () => messageId(messageSequence.value++);
401
- const messageID = nextMessageId();
402
- const admission = liveAdmission(messageID);
403
- const submissionState = { started: false };
404
- events.emit({ type: "session", coordinate: coordinate(sessionId) });
216
+ const id = await openSession(runtime, input);
405
217
  await stream.ready;
406
- if (abortController.signal.aborted)
407
- abortController.signal.throwIfAborted();
408
- const { completion, finish } = terminalSettlement(events, wholeClose);
409
- const observation = observeTurn({
410
- stream,
411
- session,
412
- sessionId,
413
- cwd: input.cwd,
414
- messageID,
415
- admission,
416
- events,
417
- state,
418
- submissionState,
419
- });
420
- submissionState.started = true;
421
- await session.promptAsync({
422
- path: { id: sessionId },
423
- query: { directory: input.cwd },
424
- body: promptBody(input, messageID),
425
- throwOnError: true,
218
+ custody.signal.throwIfAborted();
219
+ const events = new AgentEventChannel();
220
+ events.emit({ type: "session", coordinate: coordinate(id) });
221
+ let sequence = 0;
222
+ const initial = messageId(sequence++);
223
+ const admission = liveAdmission(initial);
224
+ let control;
225
+ let finishing = false;
226
+ let finish;
227
+ const completion = new Promise((resolve) => {
228
+ let settlement;
229
+ finish = (result) => {
230
+ finishing = true;
231
+ return (settlement ??= (async () => {
232
+ await close();
233
+ events.end();
234
+ resolve(result);
235
+ })());
236
+ };
426
237
  });
427
- abortController.signal.throwIfAborted();
428
- // The observer never awaits its own completion: finishing retires the whole
429
- // stream, but the observer that invoked finish stays outside that wait. Its
430
- // rejection is owned by attempt custody, not by an unobserved promise.
431
- void observation.then(finish).catch(() => undefined);
432
- return createLiveSession({
433
- session,
434
- sessionId,
435
- cwd: input.cwd,
436
- promptBody: (messageID, text) => promptBody({ ...input, body: text, launchTells: [] }, messageID),
437
- admission,
438
- nextMessageId,
238
+ const observed = observe({ runtime, stream, id, admission, events });
239
+ // Start observation before submission: admission broadcasts can precede HTTP completion.
240
+ void observed
241
+ .then(async (result) => {
242
+ // Explicit interrupt owns its response too: a terminal broadcast can be
243
+ // published before that response, and must not close its connection early.
244
+ if (control !== undefined)
245
+ await control;
246
+ await finish(result);
247
+ })
248
+ .catch(() => undefined);
249
+ const prompt = async (nativeID, text) => {
250
+ const response = object(object(await runtime.client.session.prompt({ id, body: { id: nativeID, text } }))?.data);
251
+ if (response?.id !== nativeID || response.sessionID !== id || response.type !== "user") {
252
+ throw new Error("OpenCode returned no exact native inbox admission");
253
+ }
254
+ };
255
+ await prompt(initial, launchPrompt(input.body, input.launchTells));
256
+ custody.signal.throwIfAborted();
257
+ let submissions = admission.initial.then(() => undefined);
258
+ return {
259
+ admission: { fence: initial },
439
260
  events,
440
261
  completion,
441
- abortController,
442
- close: wholeClose,
443
- finish,
444
- });
262
+ tell(tell) {
263
+ const submit = async () => {
264
+ if (!admission.open())
265
+ return { kind: "turn-ended" };
266
+ const nativeID = messageId(sequence++);
267
+ const receipt = admission.wait(nativeID);
268
+ void prompt(nativeID, tell.text).catch((error) => admission.fail(nativeID, error));
269
+ return await receipt;
270
+ };
271
+ const next = submissions.then(submit);
272
+ submissions = next.then(() => undefined, () => undefined);
273
+ return next;
274
+ },
275
+ abort: async () => {
276
+ admission.end();
277
+ if (finishing) {
278
+ await close();
279
+ return;
280
+ }
281
+ // The v2 interrupt response follows execution settlement. Do not hide its
282
+ // refusal; server and connection retirement must still run on failure.
283
+ control ??= runtime.client.session.interrupt({ id });
284
+ try {
285
+ await control;
286
+ }
287
+ finally {
288
+ await finish({ kind: "failed", diagnostic: "OpenCode session interrupted" });
289
+ }
290
+ },
291
+ forceDispose: async () => {
292
+ admission.end();
293
+ await finish({ kind: "failed", diagnostic: "OpenCode session force-disposed" });
294
+ },
295
+ };
445
296
  }
446
297
  catch (error) {
447
- signal.removeEventListener("abort", abortSetup);
448
- await wholeClose();
298
+ await close();
449
299
  throw error;
450
300
  }
451
301
  }
@@ -456,19 +306,15 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
456
306
  admitOptions(options) {
457
307
  try {
458
308
  admit(options);
309
+ return { kind: "admitted", options: Object.freeze({ ...options }) };
459
310
  }
460
311
  catch (error) {
461
312
  return { kind: "refused", diagnostic: diagnostic(error) };
462
313
  }
463
- return {
464
- kind: "admitted",
465
- options: Object.freeze({ ...options }),
466
- };
467
314
  },
468
- start: (input) => createProviderAttempt(input.signal, async (custody) => await drive(execution, { ...input, signal: custody.signal }, loader, custody)),
469
- resume: (input) => createProviderAttempt(input.signal, async (custody) => await drive(execution, { ...input, signal: custody.signal }, loader, custody)),
315
+ ...providerLifecycle((input, custody) => drive(execution, input, loader, custody)),
470
316
  fork: (input) => createProviderAttempt(new AbortController().signal, async (custody) => {
471
- const sessionId = opencodeSessionId(input.session);
317
+ const id = sessionId(input.session);
472
318
  let close;
473
319
  const runtime = await loadOpencode({
474
320
  execution,
@@ -476,34 +322,27 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
476
322
  signal: custody.signal,
477
323
  ...(loader === undefined ? {} : { loader }),
478
324
  onRuntime: (ready) => {
479
- let closing;
480
- let settleRuntimeClosed;
481
- let rejectRuntimeClosed;
482
- const runtimeClosed = new Promise((resolve, reject) => {
483
- settleRuntimeClosed = resolve;
484
- rejectRuntimeClosed = reject;
485
- });
486
- close = () => {
487
- if (closing === undefined) {
488
- closing = ready.close();
489
- void closing.then(settleRuntimeClosed, rejectRuntimeClosed);
490
- }
491
- return closing;
492
- };
493
- custody.own({ closed: runtimeClosed, abort: close, forceDispose: close });
325
+ close = ownRuntime(custody, ready);
494
326
  },
495
327
  });
496
328
  try {
497
- const result = await runtime.client.session.fork({
498
- path: { id: sessionId },
499
- query: { directory: input.cwd },
500
- body: { messageID: input.at },
501
- });
502
- const info = object(object(result)?.data) ?? object(result);
503
- const id = text(info?.id);
504
- if (id === undefined || id === sessionId)
505
- throw new Error("OpenCode fork returned an invalid session id");
506
- return { session: coordinate(id) };
329
+ custody.signal.throwIfAborted();
330
+ const point = object(object(await runtime.client.session.message({ id, messageID: input.at }))?.data);
331
+ if (point?.id !== input.at || point.type !== "idle" || point.outcome !== "succeeded") {
332
+ throw new Error("OpenCode fork requires the retained successful native terminal boundary");
333
+ }
334
+ const response = object(object(await runtime.client.session.fork({ id, before: input.at }))?.data);
335
+ const child = nonblank(response?.id);
336
+ const fork = object(response?.fork);
337
+ const boundary = object(fork?.boundary);
338
+ if (child === undefined ||
339
+ child === id ||
340
+ fork?.sessionID !== id ||
341
+ boundary?.type !== "before" ||
342
+ boundary.messageID !== input.at) {
343
+ throw new Error("OpenCode fork did not preserve the exact native boundary");
344
+ }
345
+ return { session: coordinate(child) };
507
346
  }
508
347
  finally {
509
348
  await close();