@oisincoveney/pipeline 3.24.2 → 4.0.0

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 (382) hide show
  1. package/README.md +14 -2
  2. package/defaults/pipeline.yaml +23 -5
  3. package/defaults/profiles.yaml +104 -21
  4. package/dist/argo-graph.js +90 -88
  5. package/dist/argo-submit.d.ts +110 -138
  6. package/dist/argo-submit.js +363 -378
  7. package/dist/argo-workflow.d.ts +156 -177
  8. package/dist/argo-workflow.js +96 -138
  9. package/dist/backlog.js +5 -4
  10. package/dist/bench/eval-report.js +21 -12
  11. package/dist/claude-settings-config.js +23 -31
  12. package/dist/claude-user-config.js +6 -16
  13. package/dist/cli/bootstrap-commands.js +3 -3
  14. package/dist/cli/doctor.d.ts +1 -1
  15. package/dist/cli/doctor.js +176 -171
  16. package/dist/cli/factory-commands.js +13 -13
  17. package/dist/cli/format.js +92 -86
  18. package/dist/cli/loop-commands.js +21 -21
  19. package/dist/cli/mcp-gateway-commands.js +12 -12
  20. package/dist/cli/plan-commands.js +85 -116
  21. package/dist/cli/program.d.ts +3 -3
  22. package/dist/cli/program.js +41 -43
  23. package/dist/cli/run-command.d.ts +1 -1
  24. package/dist/cli/run-command.js +25 -25
  25. package/dist/cli/run-commands.js +31 -29
  26. package/dist/cli/run-resolver.d.ts +1 -1
  27. package/dist/cli/run-resolver.js +30 -32
  28. package/dist/cli/run-service.d.ts +2 -3
  29. package/dist/cli/run-service.js +229 -239
  30. package/dist/cli/submit-options.js +85 -83
  31. package/dist/cluster-doctor.js +147 -175
  32. package/dist/codex-config.js +24 -30
  33. package/dist/commands/bench-command.js +9 -7
  34. package/dist/commands/coordinator-command.js +35 -0
  35. package/dist/commands/pipeline-command.js +13 -7
  36. package/dist/commands/runner-command-command.js +39 -24
  37. package/dist/commands/runner-node-command.js +31 -0
  38. package/dist/commands/ticket/complete.js +29 -33
  39. package/dist/commands/ticket/create.js +84 -82
  40. package/dist/commands/ticket/graph-check.js +11 -11
  41. package/dist/commands/ticket/next.js +36 -36
  42. package/dist/commands/ticket/sequence.js +11 -12
  43. package/dist/commands/ticket/shared.d.ts +5 -2
  44. package/dist/commands/ticket/shared.js +63 -66
  45. package/dist/commands/ticket/start.js +58 -67
  46. package/dist/commands/ticket-command.js +2 -2
  47. package/dist/config/defaults.d.ts +139 -140
  48. package/dist/config/defaults.js +81 -80
  49. package/dist/config/lint.js +52 -64
  50. package/dist/config/load.d.ts +4 -5
  51. package/dist/config/load.js +64 -74
  52. package/dist/config/schema/catalog.d.ts +2 -1
  53. package/dist/config/schema/catalog.js +3 -2
  54. package/dist/config/schema/mcp.js +46 -52
  55. package/dist/config/schemas.d.ts +382 -528
  56. package/dist/config/schemas.js +400 -398
  57. package/dist/config/validate.d.ts +1 -2
  58. package/dist/config/validate.js +236 -201
  59. package/dist/config.d.ts +2 -2
  60. package/dist/context/repo-map.js +105 -119
  61. package/dist/coordinator/composition.js +169 -0
  62. package/dist/coordinator/config.js +71 -0
  63. package/dist/coordinator/run-owner.js +64 -0
  64. package/dist/coordinator/wave-advance.js +148 -0
  65. package/dist/coordinator/wave-scheduler.js +50 -0
  66. package/dist/credentials/broker.d.ts +6 -8
  67. package/dist/credentials/broker.js +17 -16
  68. package/dist/credentials/codex-config.js +15 -19
  69. package/dist/credentials/file-targets.js +9 -15
  70. package/dist/credentials/local-codex-auth-sync.js +55 -58
  71. package/dist/credentials/opencode-config.js +28 -32
  72. package/dist/credentials/runner.js +13 -13
  73. package/dist/effect-sync-errors.js +28 -0
  74. package/dist/factory/create-experiment.js +285 -121
  75. package/dist/factory/exec.js +10 -12
  76. package/dist/factory/git-credentials.js +4 -4
  77. package/dist/factory/stamp-answers.js +10 -11
  78. package/dist/factory/template-update.js +65 -44
  79. package/dist/factory-lane.d.ts +26 -40
  80. package/dist/factory-lane.js +26 -25
  81. package/dist/gates.js +3 -3
  82. package/dist/git-remote-url.js +17 -19
  83. package/dist/hooks.d.ts +26 -26
  84. package/dist/hooks.js +20 -22
  85. package/dist/index.d.ts +4 -3
  86. package/dist/index.js +27 -36
  87. package/dist/install-commands/claude-code.js +100 -92
  88. package/dist/install-commands/host-selection.js +3 -6
  89. package/dist/install-commands/opencode.js +264 -259
  90. package/dist/install-commands/planner.js +163 -181
  91. package/dist/install-commands/result-format.js +1 -3
  92. package/dist/install-commands/shared.js +25 -42
  93. package/dist/install-commands/writer.js +22 -25
  94. package/dist/install-commands.js +8 -8
  95. package/dist/jscpd-output.js +29 -0
  96. package/dist/json-config-merge.js +14 -20
  97. package/dist/json-line-values.js +17 -6
  98. package/dist/loop/argo-poll.d.ts +0 -1
  99. package/dist/loop/argo-poll.js +15 -95
  100. package/dist/loop/backlog-records.js +1 -3
  101. package/dist/loop/controller-deps.js +150 -161
  102. package/dist/loop/controller.js +164 -186
  103. package/dist/loop/gh-checks.js +62 -61
  104. package/dist/loop/gh-runner.js +22 -25
  105. package/dist/loop/loop-command.js +66 -58
  106. package/dist/loop/loop-controller-entrypoint.js +29 -30
  107. package/dist/loop/merge.js +36 -40
  108. package/dist/loop/run-terminal-poll.js +69 -0
  109. package/dist/mcp/gateway-config.js +25 -29
  110. package/dist/mcp/gateway-doctor.js +77 -119
  111. package/dist/mcp/gateway-reconcile.js +17 -22
  112. package/dist/mcp/gateway-runtime.js +1 -3
  113. package/dist/mcp/host-config.js +37 -40
  114. package/dist/mcp/host-renderers.js +10 -18
  115. package/dist/mcp/repo-local-backends.js +51 -54
  116. package/dist/mcp/toolhive-vmcp.js +39 -39
  117. package/dist/model-resolver.js +29 -33
  118. package/dist/moka-global-config.d.ts +78 -55
  119. package/dist/moka-global-config.js +93 -92
  120. package/dist/moka-submit.d.ts +2179 -866
  121. package/dist/moka-submit.js +97 -99
  122. package/dist/opencode-project-config.js +34 -40
  123. package/dist/package-assets.js +1 -3
  124. package/dist/path-refs.js +2 -2
  125. package/dist/pipeline-init.js +25 -25
  126. package/dist/pipeline-runtime.d.ts +15 -14
  127. package/dist/pipeline-runtime.js +51 -50
  128. package/dist/planned-node.js +6 -3
  129. package/dist/planning/compile.d.ts +5 -5
  130. package/dist/planning/compile.js +140 -139
  131. package/dist/planning/dependency-refs.js +5 -5
  132. package/dist/planning/generate.d.ts +1296 -1296
  133. package/dist/planning/generate.js +307 -314
  134. package/dist/planning/graph.d.ts +0 -1
  135. package/dist/planning/graph.js +131 -149
  136. package/dist/remote/argo/model.d.ts +104 -122
  137. package/dist/remote/argo/model.js +205 -197
  138. package/dist/remote/argo/policy.js +39 -47
  139. package/dist/remote/argo/storage.js +81 -85
  140. package/dist/remote/jobs/execution-identity.js +70 -0
  141. package/dist/remote/jobs/job-outcome.js +108 -0
  142. package/dist/remote/jobs/manifest.js +96 -0
  143. package/dist/remote/jobs/model.d.ts +158 -0
  144. package/dist/remote/jobs/model.js +162 -0
  145. package/dist/remote/jobs/node-bootstrap.js +213 -0
  146. package/dist/remote/jobs/policy.js +123 -0
  147. package/dist/remote/jobs/reconcile.js +100 -0
  148. package/dist/remote/jobs/run-spec-client.js +64 -0
  149. package/dist/remote/jobs/run-spec-path.js +31 -0
  150. package/dist/remote/jobs/run-spec-server.d.ts +34 -0
  151. package/dist/remote/jobs/run-spec-server.js +116 -0
  152. package/dist/remote/jobs/submit.d.ts +23 -0
  153. package/dist/remote/jobs/submit.js +116 -0
  154. package/dist/remote/jobs/watch.js +59 -0
  155. package/dist/remote/submit/argo-submission.d.ts +3 -1
  156. package/dist/remote/submit/argo-submission.js +53 -58
  157. package/dist/remote/submit/compilation.d.ts +2 -0
  158. package/dist/remote/submit/compilation.js +46 -49
  159. package/dist/remote/submit/event-boundary.js +62 -76
  160. package/dist/remote/submit/hook-events.d.ts +6 -0
  161. package/dist/remote/submit/io.d.ts +7 -1
  162. package/dist/remote/submit/io.js +40 -43
  163. package/dist/remote/submit/jobs-submission.d.ts +59 -0
  164. package/dist/remote/submit/jobs-submission.js +97 -0
  165. package/dist/remote/submit/jobs-submit-result.d.ts +25 -0
  166. package/dist/remote/submit/jobs-submit-result.js +34 -0
  167. package/dist/remote/submit/options.d.ts +49 -0
  168. package/dist/remote/submit/options.js +27 -0
  169. package/dist/remote/submit/service.d.ts +40 -1
  170. package/dist/remote/submit/service.js +67 -49
  171. package/dist/run-control/command-context.js +6 -8
  172. package/dist/run-control/commands.js +10 -10
  173. package/dist/run-control/contracts.d.ts +52 -101
  174. package/dist/run-control/contracts.js +83 -69
  175. package/dist/run-control/detach.js +66 -72
  176. package/dist/run-control/execution-store.js +0 -0
  177. package/dist/run-control/file-errors.js +1 -3
  178. package/dist/run-control/logical-segment.js +3 -3
  179. package/dist/run-control/next-node.js +67 -78
  180. package/dist/run-control/postgres/postgres-run-control-store.js +196 -225
  181. package/dist/run-control/resume-command.js +34 -41
  182. package/dist/run-control/run-artifacts-command.js +84 -105
  183. package/dist/run-control/run-command-domain.js +7 -11
  184. package/dist/run-control/run-control-store.js +9 -9
  185. package/dist/run-control/run-query-command.js +73 -100
  186. package/dist/run-control/run-record.js +5 -3
  187. package/dist/run-control/run-state-lock.js +5 -5
  188. package/dist/run-control/runtime-event-projection.js +38 -50
  189. package/dist/run-control/runtime-reporter.js +86 -102
  190. package/dist/run-control/stop-command.js +42 -53
  191. package/dist/run-control/store-fs-effects.js +3 -1
  192. package/dist/run-control/store-manifest.js +38 -13
  193. package/dist/run-control/store-paths.js +3 -3
  194. package/dist/run-control/store.js +1 -1
  195. package/dist/run-control/submit-result.js +21 -21
  196. package/dist/run-control/supervisor.js +78 -68
  197. package/dist/run-control/workspace.js +2 -2
  198. package/dist/run-state/git-refs.js +258 -307
  199. package/dist/runner/opencode-excludes.js +4 -4
  200. package/dist/runner/subprocess-result.js +28 -37
  201. package/dist/runner/subprocess.js +41 -41
  202. package/dist/runner/timeouts.js +14 -13
  203. package/dist/runner-command/dynamic-command.js +46 -39
  204. package/dist/runner-command/environment-commands.js +91 -0
  205. package/dist/runner-command/finalize.js +174 -153
  206. package/dist/runner-command/lifecycle-context.js +115 -130
  207. package/dist/runner-command/lifecycle.js +78 -72
  208. package/dist/runner-command/pre-schedule.js +203 -223
  209. package/dist/runner-command/run.js +450 -448
  210. package/dist/runner-command/schedule-source-options.js +8 -10
  211. package/dist/runner-command/select-ready-wave.js +39 -30
  212. package/dist/runner-command/task-descriptor.js +11 -17
  213. package/dist/runner-command-contract.d.ts +147 -157
  214. package/dist/runner-command-contract.js +403 -411
  215. package/dist/runner-event-schema.d.ts +364 -505
  216. package/dist/runner-event-schema.js +187 -175
  217. package/dist/runner-event-sink.js +37 -43
  218. package/dist/runner-output.js +6 -6
  219. package/dist/runner.d.ts +57 -37
  220. package/dist/runner.js +176 -127
  221. package/dist/runtime/actor-ids.d.ts +1 -0
  222. package/dist/runtime/actor-ids.js +2 -2
  223. package/dist/runtime/agent-node/agent-node.js +99 -110
  224. package/dist/runtime/agent-node/handoff-finalization.js +23 -24
  225. package/dist/runtime/agent-node/model-selection.js +19 -22
  226. package/dist/runtime/agent-node/output-finalization.js +174 -204
  227. package/dist/runtime/agent-node/prompt-rendering.js +208 -251
  228. package/dist/runtime/agent-node/session-execution.js +43 -39
  229. package/dist/runtime/builtins/builtins.js +218 -284
  230. package/dist/runtime/changed-files/changed-files.js +29 -34
  231. package/dist/runtime/command-executor/command-executor.js +31 -23
  232. package/dist/runtime/config-error.js +1 -3
  233. package/dist/runtime/context/context.js +35 -37
  234. package/dist/runtime/contracts/contracts.d.ts +3 -0
  235. package/dist/runtime/contracts/index.d.ts +1 -1
  236. package/dist/runtime/detached-race.js +11 -14
  237. package/dist/runtime/drain-merge/drain-merge.js +139 -164
  238. package/dist/runtime/durable-store/acquisition.js +5 -3
  239. package/dist/runtime/durable-store/durable-store.d.ts +3 -1
  240. package/dist/runtime/durable-store/postgres/client.js +29 -0
  241. package/dist/runtime/durable-store/postgres/migrate-substrate.js +34 -29
  242. package/dist/runtime/durable-store/postgres/postgres-store.js +18 -20
  243. package/dist/runtime/events/events.js +273 -331
  244. package/dist/runtime/events/index.js +2 -2
  245. package/dist/runtime/execution-envelope.js +44 -0
  246. package/dist/runtime/gates/adjudication/llm-judge.js +21 -24
  247. package/dist/runtime/gates/adjudication/structured-claim.js +15 -16
  248. package/dist/runtime/gates/adjudicator/adjudicator.js +27 -27
  249. package/dist/runtime/gates/adjudicator/index.js +2 -2
  250. package/dist/runtime/gates/contract.js +5 -9
  251. package/dist/runtime/gates/gates.js +9 -8
  252. package/dist/runtime/gates/kinds/acceptance/acceptance.js +68 -71
  253. package/dist/runtime/gates/kinds/acceptance/index.js +2 -2
  254. package/dist/runtime/gates/kinds/artifact/artifact.js +4 -4
  255. package/dist/runtime/gates/kinds/artifact/index.js +2 -2
  256. package/dist/runtime/gates/kinds/builtin/builtin.js +4 -4
  257. package/dist/runtime/gates/kinds/builtin/index.js +2 -2
  258. package/dist/runtime/gates/kinds/changed-files/changed-files.js +31 -35
  259. package/dist/runtime/gates/kinds/changed-files/index.js +2 -2
  260. package/dist/runtime/gates/kinds/command/command.js +3 -3
  261. package/dist/runtime/gates/kinds/command/index.js +2 -2
  262. package/dist/runtime/gates/kinds/json-schema/index.js +2 -2
  263. package/dist/runtime/gates/kinds/json-schema/json-schema.js +7 -6
  264. package/dist/runtime/gates/kinds/verdict/index.js +2 -2
  265. package/dist/runtime/gates/kinds/verdict/verdict.js +3 -3
  266. package/dist/runtime/gates/orchestrator.js +72 -86
  267. package/dist/runtime/gates/registry.js +1 -3
  268. package/dist/runtime/handoff.d.ts +2 -1
  269. package/dist/runtime/handoff.js +43 -46
  270. package/dist/runtime/hooks/command-hook.js +57 -71
  271. package/dist/runtime/hooks/context.js +21 -25
  272. package/dist/runtime/hooks/events.js +84 -100
  273. package/dist/runtime/hooks/execution.js +6 -12
  274. package/dist/runtime/hooks/hooks.js +34 -23
  275. package/dist/runtime/hooks/invocation.js +23 -29
  276. package/dist/runtime/hooks/module-hook.js +36 -35
  277. package/dist/runtime/hooks/policy.js +32 -29
  278. package/dist/runtime/hooks/results.js +56 -66
  279. package/dist/runtime/journal-acquisition.d.ts +3 -2
  280. package/dist/runtime/journal-acquisition.js +14 -14
  281. package/dist/runtime/json-validation/json-validation.js +51 -72
  282. package/dist/runtime/launch-plan-args.js +39 -0
  283. package/dist/runtime/local-scheduler.js +46 -48
  284. package/dist/runtime/node-execution.js +376 -445
  285. package/dist/runtime/node-protocol/node-protocol.js +36 -79
  286. package/dist/runtime/node-state-store.d.ts +4 -1
  287. package/dist/runtime/node-state-store.js +13 -57
  288. package/dist/runtime/node-state-tracker.js +46 -54
  289. package/dist/runtime/open-pull-request/open-pull-request.js +201 -187
  290. package/dist/runtime/opencode-adapter.js +23 -24
  291. package/dist/runtime/opencode-agent-name.js +6 -6
  292. package/dist/runtime/opencode-runtime.js +115 -93
  293. package/dist/runtime/opencode-server.js +39 -53
  294. package/dist/runtime/opencode-session-executor.js +312 -344
  295. package/dist/runtime/parallel-node/parallel-node.js +123 -134
  296. package/dist/runtime/parallel-worktrees/parallel-worktrees.js +90 -73
  297. package/dist/runtime/protected-paths/protected-paths.js +70 -71
  298. package/dist/runtime/remediation/remediation.js +202 -231
  299. package/dist/runtime/retry.js +8 -10
  300. package/dist/runtime/run-journal.d.ts +2 -0
  301. package/dist/runtime/run-journal.js +7 -7
  302. package/dist/runtime/runtime-results.js +54 -69
  303. package/dist/runtime/scheduled-dependencies.d.ts +2 -1
  304. package/dist/runtime/scheduled-dependencies.js +61 -75
  305. package/dist/runtime/scheduler.js +179 -202
  306. package/dist/runtime/services/agent-node-runtime-service.js +3 -3
  307. package/dist/runtime/services/backlog-service.d.ts +1 -2
  308. package/dist/runtime/services/backlog-service.js +8 -14
  309. package/dist/runtime/services/command-executor-service.js +1 -1
  310. package/dist/runtime/services/config-io-service.js +24 -21
  311. package/dist/runtime/services/drain-merge-git-service.js +1 -1
  312. package/dist/runtime/services/file-system-service.js +9 -7
  313. package/dist/runtime/services/git-porcelain-service.js +6 -6
  314. package/dist/runtime/services/kubernetes-argo-service.d.ts +10 -3
  315. package/dist/runtime/services/kubernetes-argo-service.js +90 -84
  316. package/dist/runtime/services/kubernetes-jobs-read-service.js +115 -0
  317. package/dist/runtime/services/kubernetes-jobs-service.d.ts +37 -0
  318. package/dist/runtime/services/kubernetes-jobs-service.js +45 -0
  319. package/dist/runtime/services/kubernetes-jobs-watch-service.js +61 -0
  320. package/dist/runtime/services/kubernetes-lease-service.js +21 -0
  321. package/dist/runtime/services/mcp-gateway-service.js +143 -94
  322. package/dist/runtime/services/open-pull-request-git-service.js +1 -3
  323. package/dist/runtime/services/opencode-runtime-server-service.js +10 -12
  324. package/dist/runtime/services/opencode-sdk-service.js +7 -7
  325. package/dist/runtime/services/repo-io-service.js +10 -12
  326. package/dist/runtime/services/run-journal-file-service.js +2 -2
  327. package/dist/runtime/services/runner-command-io-service.js +48 -52
  328. package/dist/runtime/services/runner-event-sink-http-service.js +32 -39
  329. package/dist/runtime/step/step-node.js +15 -15
  330. package/dist/runtime/workflow-execution.js +56 -57
  331. package/dist/runtime/workflow-lifecycle.js +82 -101
  332. package/dist/runtime/yeet-event-record.js +104 -0
  333. package/dist/runtime/yeet-executor.js +184 -0
  334. package/dist/safe-json.js +16 -16
  335. package/dist/schedule/backlog-context.js +122 -116
  336. package/dist/schedule/baseline.js +249 -255
  337. package/dist/schedule/passes/coverage.js +63 -68
  338. package/dist/schedule/passes/drain-merge.js +34 -40
  339. package/dist/schedule/passes/ids.js +15 -17
  340. package/dist/schedule/passes/models.js +40 -37
  341. package/dist/schedule/passes/open-pull-request.js +16 -20
  342. package/dist/schedule/passes/references.js +18 -22
  343. package/dist/schedule/prompts.js +135 -141
  344. package/dist/schedule/scheduling-roles.js +8 -14
  345. package/dist/schema-boundary.d.ts +4 -0
  346. package/dist/schema-boundary.js +145 -0
  347. package/dist/serialized-write-queue.js +2 -2
  348. package/dist/standard-output-schemas.js +61 -59
  349. package/dist/strings.js +12 -12
  350. package/dist/task-ref.js +5 -5
  351. package/dist/tickets/apply-ticket-plan.js +52 -61
  352. package/dist/tickets/backlog-task-store.d.ts +15 -23
  353. package/dist/tickets/backlog-task-store.js +82 -94
  354. package/dist/tickets/completion/complete-ticket.js +53 -59
  355. package/dist/tickets/ticket-graph-dto.d.ts +24 -48
  356. package/dist/tickets/ticket-graph-dto.js +40 -40
  357. package/dist/tickets/ticket-graph.d.ts +7 -2
  358. package/dist/tickets/ticket-graph.js +36 -50
  359. package/dist/tickets/ticket-plan-command-args.js +6 -10
  360. package/dist/tickets/ticket-plan-render.js +10 -16
  361. package/dist/tickets/ticket-plan.d.ts +32 -41
  362. package/dist/tickets/ticket-plan.js +97 -77
  363. package/dist/tickets/ticket-selection.d.ts +3 -1
  364. package/dist/tickets/ticket-selection.js +37 -52
  365. package/dist/tickets/ticket-task-index.js +13 -17
  366. package/dist/tickets/validation-error-format.js +3 -10
  367. package/dist/token-estimator.js +12 -7
  368. package/dist/unknown-fields.js +21 -0
  369. package/dist/workflow-submit-contract.d.ts +9 -10
  370. package/dist/workflow-submit-contract.js +10 -9
  371. package/docs/adr-moka-postgres-schema-isolation.md +48 -0
  372. package/docs/adr-opencode-first-goal-loop-runtime.md +11 -11
  373. package/docs/config-architecture.md +13 -13
  374. package/docs/mcp-host-isolation.md +1 -0
  375. package/docs/moka-orchestrator-design.md +3 -3
  376. package/docs/operator-guide.md +1 -1
  377. package/docs/pipeline-console-runner-contract.md +10 -6
  378. package/docs/runtime-actor-model.md +18 -18
  379. package/docs/runtime-test-subsystems.md +200 -0
  380. package/docs/slash-command-adapter-contract.md +3 -3
  381. package/package.json +49 -85
  382. package/dist/config/schema/reference-validation.js +0 -83
@@ -1,138 +1,60 @@
1
+ import { isNumberValue, isStringValue } from "../schema-boundary.js";
1
2
  import { isRecord } from "../safe-json.js";
2
3
  import { raceDetached } from "./detached-race.js";
4
+ import { promptText } from "./launch-plan-args.js";
3
5
  import { opencodeAgentName } from "./opencode-agent-name.js";
4
6
  import { OpencodeSdkService, OpencodeSdkServiceLive } from "./services/opencode-sdk-service.js";
5
- import { Duration, Effect } from "effect";
7
+ import { Duration, Effect, Option } from "effect";
6
8
  //#region src/runtime/opencode-session-executor.ts
7
- function createOpencodeSessionRegistry() {
8
- return { sessions: /* @__PURE__ */ new Map() };
9
- }
10
- /**
11
- * SDK-backed replacement for the subprocess `runLaunchPlan`. Conforms to the
12
- * RuntimeContext.executor seam so agent-node never learns the transport.
13
- */
14
- function createOpencodeExecutor(deps) {
15
- return async function execute(plan, options = {}) {
16
- return await Effect.runPromise(Effect.provide(executeOpencodeEffect(deps, plan, options), OpencodeSdkServiceLive), options.signal ? { signal: options.signal } : void 0);
17
- };
18
- }
19
- function executeOpencodeEffect(deps, plan, options) {
20
- return Effect.gen(function* () {
21
- yield* validateOpencodePlan(plan);
22
- return yield* executeOpencodeSession(deps, plan, options);
23
- });
24
- }
25
- function executeOpencodeSession(deps, plan, options) {
26
- const activity = { last: Date.now() };
27
- return Effect.gen(function* () {
28
- return successResult(plan, yield* driveSession(deps, plan, options, activity));
29
- }).pipe(boundByIdle(deps, plan, options, activity), boundByAgentTimeout(plan), Effect.catch((error) => Effect.succeed(failureResult(plan, error))));
30
- }
31
- function boundByAgentTimeout(plan) {
32
- return (effect) => {
33
- const timeoutMs = plan.timeoutMs;
34
- if (!timeoutMs || timeoutMs <= 0) return effect;
35
- return raceDetached(effect, timeoutFailure(timeoutMs, `agent session timed out after ${timeoutMs}ms`));
36
- };
37
- }
38
- function boundByIdle(deps, plan, options, activity) {
39
- return (effect) => {
40
- const idleMs = plan.idleTimeoutMs;
41
- if (!idleMs || idleMs <= 0 || !options.onOutput) return effect;
42
- if (plan.timeoutMs && idleMs >= plan.timeoutMs) return effect;
43
- return raceDetached(effect, idleWatchdog(deps, plan, options, activity, idleMs));
44
- };
45
- }
46
- function timeoutFailure(milliseconds, message) {
47
- return Effect.sleep(Duration.millis(milliseconds)).pipe(Effect.andThen(Effect.fail(new Error(message))));
48
- }
49
- function idleWatchdog(deps, plan, options, activity, idleMs) {
50
- const loop = Effect.suspend(() => {
51
- const remaining = idleMs - (Date.now() - activity.last);
52
- if (remaining <= 0) return onIdleTimeout(deps, plan, options, idleMs);
53
- return Effect.sleep(Duration.millis(remaining)).pipe(Effect.andThen(loop));
54
- });
55
- return loop;
56
- }
57
- function onIdleTimeout(deps, plan, options, idleMs) {
58
- return Effect.sync(() => {
59
- options.onOutput?.({
60
- chunk: `opencode session idle for ${idleMs}ms (no progress); aborting\n`,
61
- nodeId: plan.nodeId,
62
- stream: "stderr"
63
- });
64
- }).pipe(Effect.andThen(abortBestEffort(deps, plan)), Effect.andThen(Effect.fail(/* @__PURE__ */ new Error(`agent session idle for ${idleMs}ms (no progress)`))));
65
- }
66
- function abortBestEffort(deps, plan) {
9
+ const createOpencodeSessionRegistry = () => ({ sessions: /* @__PURE__ */ new Map() });
10
+ const timeoutFailure = (milliseconds, message) => Effect.sleep(Duration.millis(milliseconds)).pipe(Effect.andThen(Effect.fail(new Error(message))));
11
+ const boundByAgentTimeout = (plan) => (effect) => {
12
+ const { timeoutMs } = plan;
13
+ if (timeoutMs === void 0 || timeoutMs <= 0) return effect;
14
+ return raceDetached(effect, timeoutFailure(timeoutMs, `agent session timed out after ${timeoutMs}ms`));
15
+ };
16
+ const validateOpencodePlan = (plan) => {
17
+ if (plan.type === "opencode") return Effect.void;
18
+ return Effect.fail(/* @__PURE__ */ new Error(`opencode executor cannot drive runner type '${plan.type}'`));
19
+ };
20
+ const sessionDirectory = (deps, plan) => plan.cwd;
21
+ const abortBestEffort = (deps, plan) => {
67
22
  const sessionId = deps.registry.sessions.get(plan.nodeId);
68
- if (!sessionId) return Effect.void;
69
- return Effect.gen(function* () {
23
+ if (sessionId === void 0) return Effect.void;
24
+ return Effect.gen(function* effectBody() {
70
25
  yield* (yield* OpencodeSdkService).abortSession(deps.client, {
71
26
  directory: sessionDirectory(deps, plan),
72
27
  sessionID: sessionId
73
28
  });
74
29
  }).pipe(Effect.timeout(Duration.millis(5e3)), Effect.asVoid, Effect.catch(() => Effect.void));
75
- }
76
- function validateOpencodePlan(plan) {
77
- if (plan.type === "opencode") return Effect.void;
78
- return Effect.fail(/* @__PURE__ */ new Error(`opencode executor cannot drive runner type '${plan.type}'`));
79
- }
80
- function sessionDirectory(deps, plan) {
81
- return plan.cwd ?? deps.directory;
82
- }
83
- function driveSession(deps, plan, options, activity) {
84
- return Effect.gen(function* () {
85
- const sessionId = yield* resolveSessionId(deps, plan, options);
86
- recordSession(deps, plan.nodeId, sessionId);
87
- const stream = yield* streamEventsToOutput(deps, sessionId, plan, options, activity);
88
- return yield* promptSessionResult(deps, plan, sessionId, options).pipe(Effect.ensuring(stopStream(stream)));
30
+ };
31
+ const onIdleTimeout = (deps, plan, options, idleMs) => Effect.sync(() => {
32
+ options.onOutput?.({
33
+ chunk: `opencode session idle for ${idleMs}ms (no progress); aborting\n`,
34
+ nodeId: plan.nodeId,
35
+ stream: "stderr"
89
36
  });
90
- }
91
- function promptSessionResult(deps, plan, sessionId, options) {
92
- return retryTransientTransport(() => Effect.gen(function* () {
93
- const data = yield* unwrapEffect(yield* (yield* OpencodeSdkService).promptSession(deps.client, promptRequest(deps, plan, sessionId)));
94
- return {
95
- ...data.info ? { assistant: data.info } : {},
96
- parts: data.parts ?? [],
97
- sessionId
98
- };
99
- }), {
100
- label: "session.prompt",
101
- options,
102
- plan
37
+ }).pipe(Effect.andThen(abortBestEffort(deps, plan)), Effect.andThen(Effect.fail(/* @__PURE__ */ new Error(`agent session idle for ${idleMs}ms (no progress)`))));
38
+ const idleWatchdog = (deps, plan, options, activity, idleMs) => {
39
+ const loop = Effect.suspend(() => {
40
+ const remaining = idleMs - (Date.now() - activity.last);
41
+ if (remaining <= 0) return onIdleTimeout(deps, plan, options, idleMs);
42
+ return Effect.sleep(Duration.millis(remaining)).pipe(Effect.andThen(loop));
103
43
  });
104
- }
105
- function promptRequest(deps, plan, sessionId) {
106
- return {
107
- directory: sessionDirectory(deps, plan),
108
- sessionID: sessionId,
109
- ...promptBody(plan)
110
- };
111
- }
112
- function stopStream(stream) {
113
- return Effect.tryPromise(() => stream.stop()).pipe(Effect.asVoid, Effect.catch(() => Effect.void));
114
- }
115
- function recordSession(deps, nodeId, sessionId) {
44
+ return loop;
45
+ };
46
+ const boundByIdle = (deps, plan, options, activity) => (effect) => {
47
+ const idleMs = plan.idleTimeoutMs;
48
+ if (idleMs === void 0 || idleMs <= 0 || options.onOutput === void 0) return effect;
49
+ if (plan.timeoutMs !== void 0 && idleMs >= plan.timeoutMs) return effect;
50
+ return raceDetached(effect, idleWatchdog(deps, plan, options, activity, idleMs));
51
+ };
52
+ const stopStream = (stream) => Effect.tryPromise(async () => {
53
+ await stream.stop();
54
+ }).pipe(Effect.asVoid, Effect.catch(() => Effect.void));
55
+ const recordSession = (deps, nodeId, sessionId) => {
116
56
  deps.onSession?.(nodeId, sessionId);
117
- }
118
- function resolveSessionId(deps, plan, options) {
119
- const existing = deps.registry.sessions.get(plan.nodeId);
120
- if (existing) return Effect.succeed(existing);
121
- return Effect.gen(function* () {
122
- const session = yield* retryTransientTransport(() => Effect.gen(function* () {
123
- return yield* unwrapEffect(yield* (yield* OpencodeSdkService).createSession(deps.client, {
124
- directory: plan.cwd ?? deps.directory,
125
- title: `moka:${plan.nodeId}`
126
- }));
127
- }), {
128
- label: "session.create",
129
- options,
130
- plan
131
- });
132
- deps.registry.sessions.set(plan.nodeId, session.id);
133
- return session.id;
134
- });
135
- }
57
+ };
136
58
  /**
137
59
  * Bounded retry for transient OpenCode transport failures at the SDK boundary.
138
60
  * Distinct from the node-level retry in retry.ts (which reprompts on gate
@@ -143,182 +65,133 @@ function resolveSessionId(deps, plan, options) {
143
65
  */
144
66
  const MAX_TRANSIENT_RETRIES = 2;
145
67
  const TRANSIENT_RETRY_BASE_MS = 250;
146
- function retryTransientTransport(make, ctx, attempt = 0) {
147
- return make().pipe(Effect.catch((error) => attempt < MAX_TRANSIENT_RETRIES && isTransientTransportError(error) ? scheduleTransientRetry(make, ctx, attempt, error) : Effect.fail(error)));
148
- }
149
- function scheduleTransientRetry(make, ctx, attempt, error) {
150
- const nextAttempt = attempt + 1;
151
- const delay = Duration.millis(TRANSIENT_RETRY_BASE_MS * 2 ** attempt);
152
- return emitTransientRetry(ctx, error, nextAttempt, delay).pipe(Effect.andThen(Effect.sleep(delay)), Effect.andThen(retryTransientTransport(make, ctx, nextAttempt)));
153
- }
154
- function emitTransientRetry(ctx, error, attempt, delay) {
155
- return Effect.sync(() => {
156
- ctx.options.onOutput?.({
157
- chunk: `opencode ${ctx.label} transient failure: ${errorMessage(error)}; retry ${attempt}/${MAX_TRANSIENT_RETRIES} in ${Duration.toMillis(delay)}ms\n`,
158
- nodeId: ctx.plan.nodeId,
159
- stream: "stderr"
160
- });
161
- });
162
- }
163
- const TRANSIENT_TRANSPORT_RE = /fetch failed|econnreset|etimedout|enotfound|eai_again|socket hang ?up|network|connection (?:reset|closed|refused)|aborterror|operation was aborted|timed? ?out/i;
164
- /**
165
- * Retry only failures that prove the turn was NOT accepted: transport errors
166
- * (no completed round trip) and HTTP 429/5xx rejections. Deterministic agent
167
- * outcomes (output-length, aborted message, schema/contract problems) and gate
168
- * failures are out of scope and never reach this classifier as retryable.
169
- */
170
- function isTransientTransportError(error) {
171
- if (TRANSIENT_TRANSPORT_RE.test(errorMessage(error))) return true;
172
- const status = httpStatusFromError(error);
173
- return status !== void 0 && (status === 429 || status >= 500);
174
- }
175
- function numericField(container, key) {
68
+ const TRANSIENT_TRANSPORT_RE = /fetch failed|econnreset|etimedout|enotfound|eai_again|socket hang ?up|network|connection (?:reset|closed|refused)|aborterror|operation was aborted|timed? ?out/iu;
69
+ const numericField = (container, key) => {
176
70
  const value = isRecord(container) ? container[key] : void 0;
177
- return typeof value === "number" ? value : void 0;
178
- }
179
- function httpStatusFromError(error) {
71
+ return isNumberValue(value) ? Option.some(value) : Option.none();
72
+ };
73
+ const httpStatusFromError = (error) => {
180
74
  const response = isRecord(error) ? error.response : void 0;
181
- return numericField(error, "status") ?? numericField(error, "statusCode") ?? numericField(response, "status");
182
- }
183
- function unwrapEffect(response) {
184
- return Effect.try({
185
- catch: (error) => error,
186
- try: () => unwrap(response)
75
+ return Option.orElse(numericField(error, "status"), () => Option.orElse(numericField(error, "statusCode"), () => numericField(response, "status")));
76
+ };
77
+ const parseModel = (model) => {
78
+ if (model === void 0 || model.length === 0) return Option.none();
79
+ const slash = model.indexOf("/");
80
+ if (slash === -1) return Option.none();
81
+ return Option.some({
82
+ modelID: model.slice(slash + 1),
83
+ providerID: model.slice(0, slash)
187
84
  });
188
- }
189
- function promptBody(plan) {
85
+ };
86
+ const promptBody = (plan) => {
190
87
  const prompt = promptText(plan);
191
88
  const model = parseModel(plan.model);
192
- const agent = plan.profileId ? opencodeAgentName(plan.profileId) : void 0;
89
+ const agent = plan.profileId === void 0 || plan.profileId.length === 0 ? Option.none() : Option.some(opencodeAgentName(plan.profileId));
193
90
  return {
194
91
  parts: [{
195
92
  text: prompt,
196
93
  type: "text"
197
94
  }],
198
- ...agent ? { agent } : {},
199
- ...model ? { model } : {},
200
- ...plan.variant ? { variant: plan.variant } : {}
201
- };
202
- }
203
- const FLAGS_TAKING_VALUE = /* @__PURE__ */ new Set([
204
- "--agent",
205
- "--dir",
206
- "--file",
207
- "--format",
208
- "--model",
209
- "--variant"
210
- ]);
211
- /**
212
- * The launch plan carries the prompt inside the CLI argv (`run <prompt>` or
213
- * `run <prompt> --file <ctx>`). Recover it as the trailing positional arg
214
- * (skipping flags and their values) so the adapter boundary is identical
215
- * regardless of transport.
216
- */
217
- function promptText(plan) {
218
- return plan.args.filter((arg, index) => index > 0 && !arg.startsWith("-") && !FLAGS_TAKING_VALUE.has(plan.args[index - 1] ?? "")).at(-1) ?? "";
219
- }
220
- function parseModel(model) {
221
- if (!model) return;
222
- const slash = model.indexOf("/");
223
- if (slash === -1) return;
224
- return {
225
- modelID: model.slice(slash + 1),
226
- providerID: model.slice(0, slash)
95
+ ...Option.match(agent, {
96
+ onNone: () => ({}),
97
+ onSome: (value) => ({ agent: value })
98
+ }),
99
+ ...Option.match(model, {
100
+ onNone: () => ({}),
101
+ onSome: (value) => ({ model: value })
102
+ }),
103
+ ...plan.variant === void 0 || plan.variant.length === 0 ? {} : { variant: plan.variant }
227
104
  };
228
- }
229
- function streamEventsToOutput(deps, sessionId, plan, options, activity) {
230
- if (!options.onOutput) return Effect.succeed({ stop: () => Promise.resolve() });
231
- return Effect.gen(function* () {
232
- const iterator = (yield* (yield* OpencodeSdkService).subscribeEvents(deps.client)).stream;
233
- activity.last = Date.now();
234
- const pump = Effect.runPromise(pumpEvents(iterator, sessionId, plan, options, activity));
235
- return { stop: () => stopIterator(iterator, pump) };
236
- });
237
- }
238
- function pumpEvents(iterator, sessionId, plan, options, activity) {
239
- return Effect.gen(function* () {
240
- let done = false;
241
- while (!done) {
242
- const next = yield* readNextEvent(iterator);
243
- done = next.done === true;
244
- if (!done) {
245
- activity.last = Date.now();
246
- forwardEvent(next.value, sessionId, plan, options);
247
- }
248
- }
249
- }).pipe(Effect.catch((error) => reportStreamDrop(error, plan, options)));
250
- }
251
- function readNextEvent(iterator) {
252
- return Effect.tryPromise({
253
- catch: (error) => error,
254
- try: () => iterator.next()
255
- });
256
- }
257
- function reportStreamDrop(error, plan, options) {
258
- return Effect.sync(() => {
259
- options.onOutput?.({
260
- chunk: `opencode event stream dropped: ${errorMessage(error)}\n`,
261
- nodeId: plan.nodeId,
262
- stream: "stderr"
263
- });
264
- });
265
- }
266
- function stopIterator(iterator, pump) {
267
- return Effect.runPromise(stopIteratorEffect(iterator, pump));
268
- }
269
- function stopIteratorEffect(iterator, pump) {
270
- return Effect.gen(function* () {
271
- yield* requestIteratorReturn(iterator);
272
- yield* Effect.tryPromise(() => pump).pipe(Effect.catch(() => Effect.void));
273
- });
274
- }
275
- function requestIteratorReturn(iterator) {
276
- const returnIterator = iterator.return;
277
- if (!returnIterator) return Effect.void;
105
+ };
106
+ const promptRequest = (deps, plan, sessionId) => ({
107
+ directory: sessionDirectory(deps, plan),
108
+ sessionID: sessionId,
109
+ ...promptBody(plan)
110
+ });
111
+ const readNextEvent = (iterator) => Effect.tryPromise({
112
+ catch: (error) => error,
113
+ try: async () => await iterator.next()
114
+ });
115
+ const requestIteratorReturn = (iterator) => {
116
+ if (iterator.return === void 0) return Effect.void;
278
117
  return Effect.tryPromise({
279
118
  catch: (error) => error,
280
- try: () => returnIterator.call(iterator, void 0)
119
+ try: async () => {
120
+ await iterator.return?.();
121
+ }
281
122
  }).pipe(Effect.asVoid, Effect.catch(() => Effect.void));
282
- }
283
- function forwardEvent(event, sessionId, plan, options) {
284
- const forwarded = eventChunk(event, sessionId);
285
- if (forwarded) options.onOutput?.({
286
- chunk: forwarded.chunk,
287
- nodeId: plan.nodeId,
288
- stream: forwarded.stream
289
- });
290
- }
291
- function eventChunk(event, sessionId) {
292
- if (event.type === "message.part.updated") return partChunk(event.properties.part, sessionId);
293
- if (event.type === "session.error" && belongsToSession(event, sessionId)) return {
294
- chunk: `opencode session error: ${describeMessageError(event.properties.error)}\n`,
295
- stream: "stderr"
296
- };
297
- }
298
- function belongsToSession(event, sessionId) {
299
- return event.properties.sessionID === void 0 || event.properties.sessionID === sessionId;
300
- }
301
- function partChunk(part, sessionId) {
302
- if (part.sessionID !== sessionId) return;
303
- if (part.type === "text") return {
123
+ };
124
+ const stopIteratorEffect = (iterator, pump) => Effect.gen(function* effectBody() {
125
+ yield* requestIteratorReturn(iterator);
126
+ yield* Effect.tryPromise(async () => {
127
+ await pump;
128
+ }).pipe(Effect.catch(() => Effect.void));
129
+ });
130
+ const stopIterator = async (iterator, pump) => {
131
+ await Effect.runPromise(stopIteratorEffect(iterator, pump));
132
+ };
133
+ const belongsToSession = (event, sessionId) => event.properties.sessionID === void 0 || event.properties.sessionID === sessionId;
134
+ const partChunk = (part, sessionId) => {
135
+ if (part.sessionID !== sessionId) return Option.none();
136
+ if (part.type === "text") return Option.some({
304
137
  chunk: `${JSON.stringify({ part: {
305
138
  text: part.text,
306
139
  type: "text"
307
140
  } })}\n`,
308
141
  stream: "stdout"
309
- };
310
- if (part.type === "tool") return {
142
+ });
143
+ if (part.type === "tool") return Option.some({
311
144
  chunk: `opencode tool ${part.tool} ${part.state.status}\n`,
312
145
  stream: "stderr"
313
- };
314
- }
146
+ });
147
+ return Option.none();
148
+ };
149
+ /**
150
+ * Map opencode message errors to the runner's infra-vs-agent exit convention.
151
+ * Output-length / aborted are agent-task outcomes (exit 1, gate territory);
152
+ * provider-auth, API, and unknown are infra (exit 70, retry-eligible).
153
+ */
154
+ const infraErrorExitCode = (error) => {
155
+ switch (error.name) {
156
+ case "MessageOutputLengthError":
157
+ case "MessageAbortedError": return 1;
158
+ case "ContentFilterError":
159
+ case "ContextOverflowError":
160
+ case "StructuredOutputError": return 1;
161
+ case "APIError":
162
+ case "ProviderAuthError":
163
+ case "UnknownError": return 70;
164
+ }
165
+ };
166
+ const describeMessageError = (error) => {
167
+ if (error === void 0) return "unknown opencode error";
168
+ const data = isRecord(error.data) ? error.data : void 0;
169
+ const detail = data !== void 0 && isStringValue(data.message) ? `: ${data.message}` : "";
170
+ return `${error.name}${detail}`;
171
+ };
172
+ const eventChunk = (event, sessionId) => {
173
+ if (event.type === "message.part.updated") return partChunk(event.properties.part, sessionId);
174
+ if (event.type === "session.error" && belongsToSession(event, sessionId)) return Option.some({
175
+ chunk: `opencode session error: ${describeMessageError(event.properties.error)}\n`,
176
+ stream: "stderr"
177
+ });
178
+ return Option.none();
179
+ };
180
+ const forwardEvent = (event, sessionId, plan, options) => {
181
+ const forwarded = eventChunk(event, sessionId);
182
+ if (Option.isSome(forwarded)) options.onOutput?.({
183
+ chunk: forwarded.value.chunk,
184
+ nodeId: plan.nodeId,
185
+ stream: forwarded.value.stream
186
+ });
187
+ };
315
188
  /**
316
189
  * Reconstruct the JSONL stdout the existing normalizeOutput/outputCandidates
317
190
  * parser already understands, so the structured-output and repair passes work
318
191
  * unchanged on top of SDK responses.
319
192
  */
320
- function successResult(plan, drive) {
321
- const stdout = drive.parts.filter((part) => part.type === "text").map((part) => JSON.stringify({ part: {
193
+ const successResult = (plan, drive) => {
194
+ const stdout = drive.parts.filter((part) => part.type === "text" && isStringValue(part.text)).map((part) => JSON.stringify({ part: {
322
195
  text: part.text,
323
196
  type: "text"
324
197
  } })).join("\n");
@@ -336,38 +209,40 @@ function successResult(plan, drive) {
336
209
  sessionId: drive.sessionId,
337
210
  stdout
338
211
  };
339
- }
340
- function failureResult(plan, error) {
341
- return {
342
- argv: plan.args,
343
- exitCode: 70,
344
- stderr: `opencode session failed: ${errorMessage(error)}`,
345
- stdout: ""
346
- };
347
- }
348
- /**
349
- * Map opencode message errors to the runner's infra-vs-agent exit convention.
350
- * Output-length / aborted are agent-task outcomes (exit 1, gate territory);
351
- * provider-auth, API, and unknown are infra (exit 70, retry-eligible).
352
- */
353
- function infraErrorExitCode(error) {
354
- switch (error.name) {
355
- case "MessageOutputLengthError":
356
- case "MessageAbortedError": return 1;
357
- default: return 70;
358
- }
359
- }
360
- function describeMessageError(error) {
361
- if (!error) return "unknown opencode error";
362
- const data = error.data;
363
- const detail = data && typeof data.message === "string" ? `: ${data.message}` : "";
364
- return `${error.name}${detail}`;
365
- }
366
- function unwrap(result) {
367
- if (result.error) throw new Error(resultErrorMessage(result));
368
- if (result.data === void 0) throw new Error(`opencode response contained no data${httpContext(result)}`);
369
- return result.data;
370
- }
212
+ };
213
+ const nonEmptyString = (error) => isStringValue(error) && error.length > 0 ? Option.some(error) : Option.none();
214
+ const nonEmptyJson = (error) => {
215
+ if (error === void 0) return Option.none();
216
+ const json = JSON.stringify(error);
217
+ return json.length > 0 && json !== "{}" ? Option.some(json) : Option.none();
218
+ };
219
+ const requestTarget = (request) => `${request?.method ?? "?"} ${request?.url ?? "?"}`;
220
+ const hasRequestTarget = (request) => request?.method !== void 0 && request.method.length > 0 || request?.url !== void 0 && request.url.length > 0;
221
+ const isHttpStatus = (status) => status !== void 0 && status !== 0 && !Number.isNaN(status);
222
+ const statusSuffix = (status) => isHttpStatus(status) ? ` HTTP ${status}` : "";
223
+ const httpStatusLine = (result) => {
224
+ const status = result.response?.status;
225
+ const { request } = result;
226
+ if (!(hasRequestTarget(request) || isHttpStatus(status))) return Option.none();
227
+ return Option.some(`${requestTarget(request)}${statusSuffix(status)}`);
228
+ };
229
+ const httpContext = (result) => {
230
+ const status = httpStatusLine(result);
231
+ return Option.match(status, {
232
+ onNone: () => "",
233
+ onSome: (value) => ` (${value})`
234
+ });
235
+ };
236
+ const stringField = (value, field) => {
237
+ const fieldValue = isRecord(value) ? value[field] : void 0;
238
+ if (isStringValue(fieldValue)) return fieldValue.length > 0 ? Option.some(fieldValue) : Option.none();
239
+ return Option.none();
240
+ };
241
+ const bodyMessage = (error) => {
242
+ if (!isRecord(error)) return Option.none();
243
+ return Option.orElse(stringField(error.data, "message"), () => Option.orElse(stringField(error, "message"), () => stringField(error, "name")));
244
+ };
245
+ const errorDetail = (error) => Option.orElse(bodyMessage(error), () => Option.orElse(nonEmptyString(error), () => nonEmptyJson(error)));
371
246
  /**
372
247
  * Build the richest available message for a failed opencode result tuple. The
373
248
  * runner reads the result-tuple path (not throwOnError), so the SDK leaves
@@ -377,47 +252,140 @@ function unwrap(result) {
377
252
  * surfaces as "POST …/prompt → 504 Gateway Timeout" instead of "{}". Mirrors the
378
253
  * SDK's own error-interceptor describe().
379
254
  */
380
- function resultErrorMessage(result) {
255
+ const resultErrorMessage = (result) => {
381
256
  const detail = errorDetail(result.error);
382
- if (detail) return `${detail}${httpContext(result)}`;
383
- return httpStatusLine(result) ?? "opencode error with empty response body";
384
- }
385
- function errorDetail(error) {
386
- return bodyMessage(error) ?? nonEmptyString(error) ?? nonEmptyJson(error);
387
- }
388
- function bodyMessage(error) {
389
- if (!isRecord(error)) return;
390
- return stringField(error.data, "message") ?? stringField(error, "message") ?? stringField(error, "name");
391
- }
392
- function nonEmptyString(error) {
393
- return typeof error === "string" && error.length > 0 ? error : void 0;
394
- }
395
- function nonEmptyJson(error) {
396
- if (error === void 0) return;
397
- const json = JSON.stringify(error);
398
- return json && json !== "{}" ? json : void 0;
399
- }
400
- function httpContext(result) {
401
- const status = httpStatusLine(result);
402
- return status ? ` (${status})` : "";
403
- }
404
- function httpStatusLine(result) {
405
- const status = result.response?.status;
406
- const { request } = result;
407
- if (!(request?.method || request?.url || status)) return;
408
- return `${requestTarget(request)}${statusSuffix(status)}`;
409
- }
410
- function requestTarget(request) {
411
- return `${request?.method ?? "?"} ${request?.url ?? "?"}`;
412
- }
413
- function statusSuffix(status) {
414
- return status ? ` → HTTP ${status}` : "";
415
- }
416
- function stringField(value, field) {
417
- if (isRecord(value) && typeof value[field] === "string" && value[field]) return value[field];
418
- }
419
- function errorMessage(error) {
420
- return error instanceof Error ? error.message : String(error);
421
- }
257
+ if (Option.isSome(detail)) return `${detail.value}${httpContext(result)}`;
258
+ return Option.getOrElse(httpStatusLine(result), () => "opencode error with empty response body");
259
+ };
260
+ const unwrap = (result) => {
261
+ if (result.error !== void 0) throw new Error(resultErrorMessage(result));
262
+ if (result.data === void 0) throw new Error(`opencode response contained no data${httpContext(result)}`);
263
+ return result.data;
264
+ };
265
+ const unwrapEffect = (response) => Effect.try({
266
+ catch: (error) => error,
267
+ try: () => unwrap(response)
268
+ });
269
+ const errorMessage = (error) => error instanceof Error ? error.message : String(error);
270
+ const emitTransientRetry = (ctx, error, attempt, delay) => Effect.sync(() => {
271
+ ctx.options.onOutput?.({
272
+ chunk: `opencode ${ctx.label} transient failure: ${errorMessage(error)}; retry ${attempt}/${MAX_TRANSIENT_RETRIES} in ${Duration.toMillis(delay)}ms\n`,
273
+ nodeId: ctx.plan.nodeId,
274
+ stream: "stderr"
275
+ });
276
+ });
277
+ /**
278
+ * Retry only failures that prove the turn was NOT accepted: transport errors
279
+ * (no completed round trip) and HTTP 429/5xx rejections. Deterministic agent
280
+ * outcomes (output-length, aborted message, schema/contract problems) and gate
281
+ * failures are out of scope and never reach this classifier as retryable.
282
+ */
283
+ const isTransientTransportError = (error) => {
284
+ if (TRANSIENT_TRANSPORT_RE.test(errorMessage(error))) return true;
285
+ const status = httpStatusFromError(error);
286
+ return Option.match(status, {
287
+ onNone: () => false,
288
+ onSome: (value) => value === 429 || value >= 500
289
+ });
290
+ };
291
+ const retryTransientTransport = function retryTransientTransport(make, ctx, attempt = 0) {
292
+ return make().pipe(Effect.catch((error) => {
293
+ if (!(attempt < MAX_TRANSIENT_RETRIES && isTransientTransportError(error))) return Effect.fail(error);
294
+ const nextAttempt = attempt + 1;
295
+ const delay = Duration.millis(TRANSIENT_RETRY_BASE_MS * 2 ** attempt);
296
+ return emitTransientRetry(ctx, error, nextAttempt, delay).pipe(Effect.andThen(Effect.sleep(delay)), Effect.andThen(retryTransientTransport(make, ctx, nextAttempt)));
297
+ }));
298
+ };
299
+ const promptSessionResult = (deps, plan, sessionId, options) => retryTransientTransport(() => Effect.gen(function* effectBody() {
300
+ const response = yield* (yield* OpencodeSdkService).promptSession(deps.client, promptRequest(deps, plan, sessionId));
301
+ const data = yield* unwrapEffect(response);
302
+ return {
303
+ assistant: data.info,
304
+ parts: data.parts,
305
+ sessionId
306
+ };
307
+ }), {
308
+ label: "session.prompt",
309
+ options,
310
+ plan
311
+ });
312
+ const resolveSessionId = (deps, plan, options) => {
313
+ const existing = deps.registry.sessions.get(plan.nodeId);
314
+ if (existing !== void 0 && existing.length > 0) return Effect.succeed(existing);
315
+ return Effect.gen(function* effectBody() {
316
+ const session = yield* retryTransientTransport(() => Effect.gen(function* session() {
317
+ const created = yield* (yield* OpencodeSdkService).createSession(deps.client, {
318
+ directory: plan.cwd,
319
+ title: `moka:${plan.nodeId}`
320
+ });
321
+ return yield* unwrapEffect(created);
322
+ }), {
323
+ label: "session.create",
324
+ options,
325
+ plan
326
+ });
327
+ deps.registry.sessions.set(plan.nodeId, session.id);
328
+ return session.id;
329
+ });
330
+ };
331
+ const reportStreamDrop = (error, plan, options) => Effect.sync(() => {
332
+ options.onOutput?.({
333
+ chunk: `opencode event stream dropped: ${errorMessage(error)}\n`,
334
+ nodeId: plan.nodeId,
335
+ stream: "stderr"
336
+ });
337
+ });
338
+ const pumpEvents = (iterator, sessionId, plan, options, activity) => Effect.gen(function* effectBody() {
339
+ let done = false;
340
+ while (!done) {
341
+ const next = yield* readNextEvent(iterator);
342
+ done = next.done === true;
343
+ if (!done) {
344
+ activity.last = Date.now();
345
+ forwardEvent(next.value, sessionId, plan, options);
346
+ }
347
+ }
348
+ }).pipe(Effect.catch((error) => reportStreamDrop(error, plan, options)));
349
+ const streamEventsToOutput = (deps, sessionId, plan, options, activity) => {
350
+ if (options.onOutput === void 0) return Effect.succeed({ stop: async () => {} });
351
+ return Effect.gen(function* effectBody() {
352
+ const iterator = (yield* (yield* OpencodeSdkService).subscribeEvents(deps.client)).stream;
353
+ activity.last = Date.now();
354
+ const pump = Effect.runPromise(pumpEvents(iterator, sessionId, plan, options, activity));
355
+ return { stop: async () => {
356
+ await stopIterator(iterator, pump);
357
+ } };
358
+ });
359
+ };
360
+ const driveSession = (deps, plan, options, activity) => Effect.gen(function* effectBody() {
361
+ const sessionId = yield* resolveSessionId(deps, plan, options);
362
+ recordSession(deps, plan.nodeId, sessionId);
363
+ const stream = yield* streamEventsToOutput(deps, sessionId, plan, options, activity);
364
+ return yield* promptSessionResult(deps, plan, sessionId, options).pipe(Effect.ensuring(stopStream(stream)));
365
+ });
366
+ const failureResult = (plan, error) => ({
367
+ argv: plan.args,
368
+ exitCode: 70,
369
+ stderr: `opencode session failed: ${errorMessage(error)}`,
370
+ stdout: ""
371
+ });
372
+ const executeOpencodeSession = (deps, plan, options) => {
373
+ const activity = { last: Date.now() };
374
+ return Effect.gen(function* effectBody() {
375
+ const drive = yield* driveSession(deps, plan, options, activity);
376
+ return successResult(plan, drive);
377
+ }).pipe(boundByIdle(deps, plan, options, activity), boundByAgentTimeout(plan), Effect.catch((error) => Effect.succeed(failureResult(plan, error))));
378
+ };
379
+ const executeOpencodeEffect = (deps, plan, options) => Effect.gen(function* effectBody() {
380
+ yield* validateOpencodePlan(plan);
381
+ return yield* executeOpencodeSession(deps, plan, options);
382
+ });
383
+ /**
384
+ * SDK-backed replacement for the subprocess `runLaunchPlan`. Conforms to the
385
+ * RuntimeContext.executor seam so agent-node never learns the transport.
386
+ */
387
+ const createOpencodeExecutor = (deps) => async function execute(plan, options = {}) {
388
+ return await Effect.runPromise(Effect.provide(executeOpencodeEffect(deps, plan, options), OpencodeSdkServiceLive), options.signal ? { signal: options.signal } : void 0);
389
+ };
422
390
  //#endregion
423
391
  export { createOpencodeExecutor, createOpencodeSessionRegistry };