@akira-tl/forgerelay 0.8.7 → 0.8.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 (246) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +10 -18
  3. package/capabilities/host-integration/GUIDE.md +2 -2
  4. package/dist/activity/{audit-store.js → history/audit-store.js} +129 -21
  5. package/dist/activity/history/bash-output-store.js +250 -0
  6. package/dist/activity/{host-turn-store.js → history/host-turn-store.js} +4 -1
  7. package/dist/activity/{query-service.js → history/query-service.js} +13 -7
  8. package/dist/activity/{lifecycle.js → runtime/lifecycle.js} +1 -1
  9. package/dist/activity/{mcp-query-tools.js → runtime/mcp-query-tools.js} +3 -3
  10. package/dist/activity/storage/history.js +12 -0
  11. package/dist/activity/storage/paths.js +56 -0
  12. package/dist/activity/storage/segmented-log.js +149 -0
  13. package/dist/cli/setup-support.js +116 -0
  14. package/dist/cli.js +18 -126
  15. package/dist/lsp/code-intelligence.js +1 -1
  16. package/dist/lsp/test-support/server-fixture.js +9 -9
  17. package/dist/mcp/filesystem/filesystem-tools.js +245 -0
  18. package/dist/mcp/hooks/command-runner.js +79 -0
  19. package/dist/{hook-cli.js → mcp/hooks/hook-cli.js} +1 -1
  20. package/dist/{hooks.js → mcp/hooks/hooks.js} +3 -80
  21. package/dist/{oauth-provider.js → mcp/oauth/oauth-provider.js} +1 -1
  22. package/dist/{oauth-store.js → mcp/oauth/oauth-store.js} +5 -4
  23. package/dist/{operations → mcp/operations}/native-bulk-mutations.js +2 -2
  24. package/dist/mcp/panel/app.js +143 -0
  25. package/dist/{mcp-app-template.js → mcp/panel/mcp-app-template.js} +2 -38
  26. package/dist/mcp/process/runtime.js +257 -0
  27. package/dist/mcp/process/tools.js +346 -0
  28. package/dist/mcp/server/core/activity-support.js +70 -0
  29. package/dist/{capabilities.js → mcp/server/core/capabilities.js} +5 -3
  30. package/dist/{capability-registry.js → mcp/server/core/capability-registry.js} +2 -2
  31. package/dist/mcp/server/core/capability-support.js +90 -0
  32. package/dist/mcp/server/core/schemas.js +216 -0
  33. package/dist/mcp/server/core/tool-support.js +207 -0
  34. package/dist/mcp/server/core/version.js +9 -0
  35. package/dist/mcp/server/operations/runtime/filesystem-tools.js +400 -0
  36. package/dist/mcp/server/operations/runtime/operation-runtime.js +523 -0
  37. package/dist/mcp/server/transport/http-server.js +301 -0
  38. package/dist/mcp/server/transport/http-support.js +43 -0
  39. package/dist/mcp/server/workspace/runtime/workspace-open-presentation.js +222 -0
  40. package/dist/mcp/server/workspace/runtime/workspace-open-schema.js +187 -0
  41. package/dist/mcp/server/workspace/runtime/workspace-open.js +552 -0
  42. package/dist/mcp/server/workspace/runtime/workspace-tools.js +601 -0
  43. package/dist/{config.js → runtime/config/config.js} +3 -3
  44. package/dist/{user-config.js → runtime/config/user-config.js} +14 -54
  45. package/dist/{db → runtime/state/db}/client.js +1 -1
  46. package/dist/{db → runtime/state/db}/migrations.js +34 -3
  47. package/dist/runtime/state/lock/file-lock.js +130 -0
  48. package/dist/runtime/testing/server-fixture.js +190 -0
  49. package/dist/server.js +139 -4192
  50. package/dist/subagents/providers/adapters/claude.js +86 -52
  51. package/dist/subagents/providers/adapters/codex.js +103 -39
  52. package/dist/subagents/providers/adapters/opencode.js +114 -84
  53. package/dist/subagents/providers/adapters/pi.js +59 -8
  54. package/dist/subagents/providers/availability.js +15 -30
  55. package/dist/subagents/providers/runtime/external-command.js +56 -0
  56. package/dist/subagents/sessions/capability.js +1 -1
  57. package/dist/subagents/sessions/execution.js +1 -1
  58. package/dist/subagents/sessions/mcp/activity-audit-test-support.js +30 -0
  59. package/dist/subagents/sessions/store.js +1 -1
  60. package/dist/ui/.vite/manifest.json +342 -409
  61. package/dist/ui/activity-panel-app.html +2 -4
  62. package/dist/ui/assets/activity-panel-app-7Xe0alhm.css +1 -0
  63. package/dist/ui/assets/activity-panel-app-Sl1Jro8v.js +115 -0
  64. package/dist/ui/assets/angular-html-B_yn91wW.js +1 -0
  65. package/dist/ui/assets/{angular-ts-Bxh4-O1-.js → angular-ts-DSgtloKz.js} +1 -1
  66. package/dist/ui/assets/{apl-BmYHKQ6m.js → apl-NatWtF8X.js} +1 -1
  67. package/dist/ui/assets/{astro-C51vqDKq.js → astro-BYFEVBuN.js} +1 -1
  68. package/dist/ui/assets/{blade-CdhVsmwq.js → blade-DoO7yvMR.js} +1 -1
  69. package/dist/ui/assets/c-Rl2hMCcg.js +1 -0
  70. package/dist/ui/assets/{cobol-Cf6azoN_.js → cobol-ByC5mWiB.js} +1 -1
  71. package/dist/ui/assets/{coffee-vVdGzlmt.js → coffee-Dez77Qov.js} +1 -1
  72. package/dist/ui/assets/cpp-DtaAQT9h.js +1 -0
  73. package/dist/ui/assets/{crystal-5UZy6KIX.js → crystal-Bgmbf8ec.js} +1 -1
  74. package/dist/ui/assets/css-C1tjBtVN.js +1 -0
  75. package/dist/ui/assets/{edge-CESgo4ia.js → edge-DeH6u8Wg.js} +1 -1
  76. package/dist/ui/assets/{elixir-9FKfw4cJ.js → elixir-_H3KnmWT.js} +1 -1
  77. package/dist/ui/assets/{elm-Dv7A0aLa.js → elm-CsCe3U28.js} +1 -1
  78. package/dist/ui/assets/{erb-DHFmDvM0.js → erb-CbM-L2g8.js} +1 -1
  79. package/dist/ui/assets/{git-rebase-utbU6CzV.js → git-rebase-D0s5pDh9.js} +1 -1
  80. package/dist/ui/assets/{glimmer-js-t2kiCkyB.js → glimmer-js-BfOToq_A.js} +1 -1
  81. package/dist/ui/assets/{glimmer-ts-F0Jv5okV.js → glimmer-ts-CCUOkL2K.js} +1 -1
  82. package/dist/ui/assets/glsl-DH4ec9dE.js +1 -0
  83. package/dist/ui/assets/graphql-DilAMBoI.js +1 -0
  84. package/dist/ui/assets/{hack-8yjdbaSA.js → hack-BDkNDZlF.js} +1 -1
  85. package/dist/ui/assets/haml-D_QBp6K7.js +1 -0
  86. package/dist/ui/assets/{handlebars-BBFEg4Lo.js → handlebars-DePUrTpC.js} +1 -1
  87. package/dist/ui/assets/heavy-payload-DOwZpO6n.js +290 -0
  88. package/dist/ui/assets/html-By4dNnM4.js +1 -0
  89. package/dist/ui/assets/{html-derivative-DJAQcZyc.js → html-derivative-DDw9Ef4Q.js} +1 -1
  90. package/dist/ui/assets/{http-Dg5LSYRK.js → http-B0QxWc9M.js} +1 -1
  91. package/dist/ui/assets/{hurl-Y5uYzMZR.js → hurl-vMQ4lda1.js} +1 -1
  92. package/dist/ui/assets/java-DYRe2lWG.js +1 -0
  93. package/dist/ui/assets/javascript-CxyGqwcL.js +1 -0
  94. package/dist/ui/assets/{jinja-Bt30h8Ik.js → jinja-JUwV-fZt.js} +1 -1
  95. package/dist/ui/assets/{jison-D5uZ1Nyo.js → jison-7ZqCEvS8.js} +1 -1
  96. package/dist/ui/assets/json-dbltgiiA.js +1 -0
  97. package/dist/ui/assets/jsx-oDopT7YQ.js +1 -0
  98. package/dist/ui/assets/{julia-76hRaaGz.js → julia-CkKiNSav.js} +1 -1
  99. package/dist/ui/assets/{just-CDE7W0vW.js → just-DuqCAKJ3.js} +1 -1
  100. package/dist/ui/assets/{latex-CVO2Byab.js → latex-sudcRDSn.js} +1 -1
  101. package/dist/ui/assets/{liquid-CA-AS3EX.js → liquid-Di-08Js7.js} +1 -1
  102. package/dist/ui/assets/lua-YlqdMoV0.js +1 -0
  103. package/dist/ui/assets/{marko-_BWrPfny.js → marko-CJRNyJDb.js} +1 -1
  104. package/dist/ui/assets/{mdc-PyJv85eS.js → mdc-t12CzZHs.js} +1 -1
  105. package/dist/ui/assets/{nginx-DAWVw8yu.js → nginx-57-b4XwT.js} +1 -1
  106. package/dist/ui/assets/{nim-CKE1dzpU.js → nim-BzrswYQa.js} +1 -1
  107. package/dist/ui/assets/{perl-BBhWva19.js → perl-D9JR_iIh.js} +1 -1
  108. package/dist/ui/assets/{php-ChIeaAoF.js → php-Py2gFHil.js} +1 -1
  109. package/dist/ui/assets/{pug-CMgI5afi.js → pug-PzLLy0h1.js} +1 -1
  110. package/dist/ui/assets/{qml-CjxTkcFH.js → qml-By8fwgV4.js} +1 -1
  111. package/dist/ui/assets/r-CcezTbLq.js +1 -0
  112. package/dist/ui/assets/{razor-i4DbHr8p.js → razor-dG-vdhL_.js} +1 -1
  113. package/dist/ui/assets/regexp-BlsQ-N_8.js +1 -0
  114. package/dist/ui/assets/{rst-oy_97Kl4.js → rst-C2awDCat.js} +1 -1
  115. package/dist/ui/assets/{ruby-C5zYZIyl.js → ruby-zA2W_49d.js} +1 -1
  116. package/dist/ui/assets/{sas-BnMEh2S3.js → sas-D49bhyaN.js} +1 -1
  117. package/dist/ui/assets/scss-BQb-cp01.js +1 -0
  118. package/dist/ui/assets/shellscript-Dga4H_Ni.js +1 -0
  119. package/dist/ui/assets/{shellsession-BpE5o7_3.js → shellsession-n3-5CqcF.js} +1 -1
  120. package/dist/ui/assets/{soy-COesmhTY.js → soy-XYBOf_BA.js} +1 -1
  121. package/dist/ui/assets/sql-ET4u5Cpl.js +1 -0
  122. package/dist/ui/assets/{stata-CGQEuSZq.js → stata-CLHcJHO_.js} +1 -1
  123. package/dist/ui/assets/{surrealql-Bz4JbU-9.js → surrealql-BdjQ60T4.js} +1 -1
  124. package/dist/ui/assets/{svelte-1jM2ejXj.js → svelte-B2RPtKJe.js} +1 -1
  125. package/dist/ui/assets/{templ-C-VwELcp.js → templ-CrByPW_W.js} +1 -1
  126. package/dist/ui/assets/{tex-t1rUiQCm.js → tex-DZBkg363.js} +1 -1
  127. package/dist/ui/assets/{ts-tags-Bewoa6pS.js → ts-tags-DJR3Gphb.js} +1 -1
  128. package/dist/ui/assets/tsx-CajzROyy.js +1 -0
  129. package/dist/ui/assets/{twig-DVg-vELl.js → twig-BZjk5Zda.js} +1 -1
  130. package/dist/ui/assets/typescript-B-EWToLj.js +1 -0
  131. package/dist/ui/assets/{vue-wPQ7UlMy.js → vue-D_rHCCr8.js} +1 -1
  132. package/dist/ui/assets/{vue-html-BHtJ8geX.js → vue-html-CC4WlvVt.js} +1 -1
  133. package/dist/ui/assets/{vue-vine-CNSGAWwc.js → vue-vine-BgNMVgpk.js} +1 -1
  134. package/dist/ui/assets/xml-BI86-WbH.js +1 -0
  135. package/dist/ui/assets/{xsl-3_vJ27aE.js → xsl-DXeLZV4J.js} +1 -1
  136. package/dist/ui/assets/yaml-60_PFlWa.js +1 -0
  137. package/dist/workspaces/bootstrap.js +72 -0
  138. package/dist/workspaces/context.js +302 -0
  139. package/dist/workspaces/conversation-test-support.js +86 -0
  140. package/dist/{git-worktrees.js → workspaces/git/git-worktrees.js} +1 -1
  141. package/dist/workspaces/inventory.js +131 -0
  142. package/dist/workspaces/paths.js +55 -0
  143. package/dist/{remote-auth.js → workspaces/relay/auth/remote-auth.js} +2 -2
  144. package/dist/workspaces/relay/result-support.js +111 -0
  145. package/dist/workspaces/relay/tests/test-support.js +203 -0
  146. package/dist/{remote-mcp-connection-pool.js → workspaces/relay/transport/remote-mcp-connection-pool.js} +1 -1
  147. package/dist/workspaces/relay/types.js +1 -0
  148. package/dist/{remote-workspace-relay.js → workspaces/relay/workspace-relay.js} +23 -172
  149. package/dist/workspaces/resources/skills.js +285 -0
  150. package/dist/{review-checkpoints.js → workspaces/review/review-checkpoints.js} +2 -2
  151. package/dist/workspaces/sessions.js +457 -0
  152. package/dist/{workspace-store.js → workspaces/state/workspace-store.js} +2 -2
  153. package/dist/workspaces.js +69 -991
  154. package/docs/chatgpt-coding-workflow.md +3 -11
  155. package/docs/configuration.md +12 -25
  156. package/docs/gotchas.md +0 -30
  157. package/docs/roadmap.md +6 -10
  158. package/docs/security.md +1 -5
  159. package/docs/setup.md +0 -11
  160. package/package.json +6 -8
  161. package/scripts/ci/architecture.mjs +109 -0
  162. package/scripts/ci/verify.mjs +5 -0
  163. package/scripts/debug/accept/bootstrap.mjs +326 -0
  164. package/scripts/debug/accept/harness.mjs +76 -0
  165. package/scripts/debug/accept/support.mjs +432 -0
  166. package/scripts/debug/accept.mjs +43 -806
  167. package/scripts/debug/relay-accept/support.mjs +135 -0
  168. package/scripts/debug/relay-accept.mjs +26 -115
  169. package/scripts/debug/traffic/support.mjs +167 -0
  170. package/scripts/debug/traffic/traffic-audit.mjs +4 -161
  171. package/scripts/{lsp-interop-support.test.mjs → lsp/interop-support.test.mjs} +1 -1
  172. package/scripts/{lsp-interop.mjs → lsp/interop.mjs} +3 -3
  173. package/scripts/wiki/sync.mjs +21 -10
  174. package/dist/activity/bash-output-store.js +0 -127
  175. package/dist/pi-tools.js +0 -110
  176. package/dist/skills.js +0 -127
  177. package/dist/ui/assets/activity-panel-app-CUAN6zyW.js +0 -2
  178. package/dist/ui/assets/angular-html-BkgDQl-G.js +0 -1
  179. package/dist/ui/assets/c-IBzBpxAO.js +0 -1
  180. package/dist/ui/assets/chunk-EyZ2wyi3.js +0 -1
  181. package/dist/ui/assets/cpp-CVHeO-Jk.js +0 -1
  182. package/dist/ui/assets/css-DEfus9J5.js +0 -1
  183. package/dist/ui/assets/glsl-_ooCjaeB.js +0 -1
  184. package/dist/ui/assets/graphql-B9LqP-cY.js +0 -1
  185. package/dist/ui/assets/haml-BU5EoZBn.js +0 -1
  186. package/dist/ui/assets/heavy-payload-CgzrutLm.js +0 -4
  187. package/dist/ui/assets/html-D-C6YZk4.js +0 -1
  188. package/dist/ui/assets/java-ppHvycj5.js +0 -1
  189. package/dist/ui/assets/javascript-DDTjKLlQ.js +0 -1
  190. package/dist/ui/assets/json-Cw91r8h1.js +0 -1
  191. package/dist/ui/assets/jsx-04X2FT_1.js +0 -1
  192. package/dist/ui/assets/lua-CiyBp52Y.js +0 -1
  193. package/dist/ui/assets/r-DfFga4si.js +0 -1
  194. package/dist/ui/assets/regexp-iC9_nbSI.js +0 -1
  195. package/dist/ui/assets/review-payload-BrLbezbq.js +0 -1
  196. package/dist/ui/assets/scrollbar-CbhpdW05.js +0 -287
  197. package/dist/ui/assets/scss-BfZXsAWQ.js +0 -1
  198. package/dist/ui/assets/shellscript-BC2srNaB.js +0 -1
  199. package/dist/ui/assets/sql-Ccv_yeUm.js +0 -1
  200. package/dist/ui/assets/tsx-CTXzrBkN.js +0 -1
  201. package/dist/ui/assets/typescript--ElHoFkw.js +0 -1
  202. package/dist/ui/assets/workspace-app-Bhj96tsR.js +0 -1
  203. package/dist/ui/assets/workspace-app-CxwJuZyS.js +0 -111
  204. package/dist/ui/assets/workspace-app-D6UR0AFl.js +0 -5
  205. package/dist/ui/assets/workspace-app-ldjBmCJR.css +0 -1
  206. package/dist/ui/assets/workspace-lifecycle-app-Cqfhx9pV.js +0 -1
  207. package/dist/ui/assets/xml-JnmX6vyS.js +0 -1
  208. package/dist/ui/assets/yaml-OAcZkP-w.js +0 -1
  209. package/dist/ui/workspace-app.html +0 -18
  210. package/dist/ui/workspace-lifecycle-app.html +0 -18
  211. package/docs/agent-profile-schema.md +0 -114
  212. package/docs/artifact-exchange.md +0 -65
  213. package/docs/versioning.md +0 -205
  214. /package/capabilities/{managed-worktrees → workspace/managed-worktrees}/GUIDE.md +0 -0
  215. /package/capabilities/{workspace-tasks → workspace/workspace-tasks}/GUIDE.md +0 -0
  216. /package/dist/activity/{process-output-audit.js → runtime/process-output-audit.js} +0 -0
  217. /package/dist/{artifact-error.js → mcp/artifacts/artifact-error.js} +0 -0
  218. /package/dist/{artifact-tools.js → mcp/artifacts/artifact-tools.js} +0 -0
  219. /package/dist/{incoming-artifacts.js → mcp/artifacts/incoming-artifacts.js} +0 -0
  220. /package/dist/{advertised-files.js → mcp/filesystem/advertised-files.js} +0 -0
  221. /package/dist/{apply-patch.js → mcp/filesystem/apply-patch.js} +0 -0
  222. /package/dist/{file-mutations.js → mcp/filesystem/file-mutations.js} +0 -0
  223. /package/dist/{roots.js → mcp/filesystem/roots.js} +0 -0
  224. /package/dist/{oauth → mcp/oauth}/public-url.js +0 -0
  225. /package/dist/{oauth → mcp/oauth}/router.js +0 -0
  226. /package/dist/{operations → mcp/operations}/batch/executor.js +0 -0
  227. /package/dist/{operations → mcp/operations}/batch/scheduler.js +0 -0
  228. /package/dist/{operations → mcp/operations}/batch/types.js +0 -0
  229. /package/dist/{operations → mcp/operations}/bulk-mutation.js +0 -0
  230. /package/dist/{operations → mcp/operations}/bulk-read.js +0 -0
  231. /package/dist/{operations → mcp/operations}/core-operation-executor.js +0 -0
  232. /package/dist/{process-platform.js → mcp/process/process-platform.js} +0 -0
  233. /package/dist/{process-sessions.js → mcp/process/process-sessions.js} +0 -0
  234. /package/dist/{request-meta.js → mcp/request-meta.js} +0 -0
  235. /package/dist/{mcp-sessions.js → mcp/server/transport/mcp-sessions.js} +0 -0
  236. /package/dist/{server-shutdown.js → mcp/server/transport/server-shutdown.js} +0 -0
  237. /package/dist/{logger.js → runtime/logging/logger.js} +0 -0
  238. /package/dist/{db → runtime/state/db}/schema.js +0 -0
  239. /package/dist/{composite-activity.js → workspaces/composite/composite-activity.js} +0 -0
  240. /package/dist/{composite-workspaces.js → workspaces/composite/composite-workspaces.js} +0 -0
  241. /package/dist/{git.js → workspaces/git/git.js} +0 -0
  242. /package/dist/{workspace-presentation.js → workspaces/presentation/workspace-presentation.js} +0 -0
  243. /package/dist/{remote-transport.js → workspaces/relay/transport/remote-transport.js} +0 -0
  244. /package/dist/{workspace-task-reminders.js → workspaces/tasks/workspace-task-reminders.js} +0 -0
  245. /package/dist/{workspace-tasks.js → workspaces/tasks/workspace-tasks.js} +0 -0
  246. /package/scripts/{lsp-interop-support.mjs → lsp/interop-support.mjs} +0 -0
@@ -1,53 +1,94 @@
1
- import { spawnSync } from "node:child_process";
2
- import { directString, linkedAbortController, requireFinalResponse } from "../shared.js";
1
+ import { runExternalCommand, } from "../runtime/external-command.js";
2
+ import { removeForgeRelayNodeModulesBinFromPath } from "../path.js";
3
+ import { asRecord, directString, requireFinalResponse } from "../shared.js";
3
4
  export class ClaudeSubagentAdapter {
5
+ runner;
6
+ env;
4
7
  provider = "claude";
8
+ constructor(runner = runExternalCommand, env = process.env) {
9
+ this.runner = runner;
10
+ this.env = env;
11
+ }
5
12
  async run(input) {
6
- const { query } = await import("@anthropic-ai/claude-agent-sdk");
7
- const claudeExecutable = process.env.CLAUDE_COMMAND ?? resolveExecutable("claude");
8
- const linkedAbort = linkedAbortController(input.signal);
9
- try {
10
- const messages = query({
11
- prompt: input.prompt,
12
- options: {
13
- cwd: input.workspace,
14
- model: input.model,
15
- ...(input.thinking ? { thinking: { type: "adaptive" }, effort: input.thinking } : {}),
16
- resume: input.providerSessionId,
17
- permissionMode: "bypassPermissions",
18
- allowDangerouslySkipPermissions: true,
19
- env: claudeCommandEnvironment(process.env),
20
- ...(linkedAbort.controller ? { abortController: linkedAbort.controller } : {}),
21
- ...(claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {}),
22
- },
23
- });
24
- let providerSessionId = input.providerSessionId ?? null;
25
- let finalResponse = "";
26
- for await (const message of messages) {
27
- const record = message;
28
- if (typeof record.session_id === "string")
29
- providerSessionId = record.session_id;
30
- if (record.type === "result" && typeof record.result === "string") {
31
- const resultError = claudeResultError(record);
32
- if (resultError)
33
- throw new Error(resultError);
34
- finalResponse = record.result;
35
- }
36
- }
37
- finalResponse = requireFinalResponse("Claude", finalResponse);
38
- return {
39
- provider: this.provider,
40
- providerSessionId,
41
- finalResponse,
42
- };
13
+ const command = this.env.CLAUDE_COMMAND?.trim() || "claude";
14
+ const result = await this.runner({
15
+ command,
16
+ args: claudeCommandArgs(input),
17
+ cwd: input.workspace,
18
+ env: claudeCommandEnvironment(this.env),
19
+ signal: input.signal,
20
+ stdin: input.prompt,
21
+ label: "Claude",
22
+ });
23
+ const parsed = parseClaudeJsonOutput(result.stdout);
24
+ if (parsed.error)
25
+ throw new Error(parsed.error);
26
+ return {
27
+ provider: this.provider,
28
+ providerSessionId: parsed.providerSessionId ?? input.providerSessionId ?? null,
29
+ finalResponse: requireFinalResponse("Claude", parsed.finalResponse),
30
+ };
31
+ }
32
+ }
33
+ export function claudeCommandArgs(input) {
34
+ const args = [
35
+ "--print",
36
+ "--output-format",
37
+ "json",
38
+ "--dangerously-skip-permissions",
39
+ ];
40
+ if (input.model)
41
+ args.push("--model", input.model);
42
+ if (input.thinking)
43
+ args.push("--effort", input.thinking);
44
+ if (input.providerSessionId)
45
+ args.push("--resume", input.providerSessionId);
46
+ return args;
47
+ }
48
+ export function parseClaudeJsonOutput(output) {
49
+ const records = parseJsonRecords(output);
50
+ let providerSessionId;
51
+ let finalResponse = "";
52
+ let error;
53
+ for (const record of records) {
54
+ providerSessionId = directString(record.session_id) ?? providerSessionId;
55
+ const resultError = claudeResultError(record);
56
+ if (resultError)
57
+ error = resultError;
58
+ if (typeof record.result === "string")
59
+ finalResponse = record.result;
60
+ }
61
+ return { providerSessionId, finalResponse, error };
62
+ }
63
+ function parseJsonRecords(output) {
64
+ const trimmed = output.trim();
65
+ if (!trimmed)
66
+ return [];
67
+ try {
68
+ const parsed = JSON.parse(trimmed);
69
+ if (Array.isArray(parsed)) {
70
+ return parsed.map(asRecord).filter((record) => Boolean(record));
43
71
  }
44
- finally {
45
- linkedAbort.dispose();
72
+ const record = asRecord(parsed);
73
+ return record ? [record] : [];
74
+ }
75
+ catch {
76
+ const records = [];
77
+ for (const line of trimmed.split(/\r?\n/)) {
78
+ try {
79
+ const record = asRecord(JSON.parse(line));
80
+ if (record)
81
+ records.push(record);
82
+ }
83
+ catch {
84
+ // Ignore non-JSON diagnostic lines; the command runner preserves stderr separately.
85
+ }
46
86
  }
87
+ return records;
47
88
  }
48
89
  }
49
90
  function claudeResultError(record) {
50
- const subtype = typeof record.subtype === "string" ? record.subtype : undefined;
91
+ const subtype = directString(record.subtype);
51
92
  const isError = record.is_error === true || subtype?.startsWith("error");
52
93
  if (!isError)
53
94
  return undefined;
@@ -58,16 +99,6 @@ function claudeResultError(record) {
58
99
  "Claude returned an error result.";
59
100
  return `Claude returned an error result: ${message}`;
60
101
  }
61
- function resolveExecutable(command) {
62
- const result = spawnSync(process.platform === "win32" ? "where.exe" : "command", [
63
- ...(process.platform === "win32" ? [command] : ["-v", command]),
64
- ], {
65
- encoding: "utf8",
66
- shell: process.platform !== "win32",
67
- });
68
- const executable = result.stdout?.split(/\r?\n/).find((line) => line.trim());
69
- return executable?.trim() || undefined;
70
- }
71
102
  export function claudeCommandEnvironment(env) {
72
103
  const next = { ...env };
73
104
  for (const key of [
@@ -78,5 +109,8 @@ export function claudeCommandEnvironment(env) {
78
109
  ]) {
79
110
  delete next[key];
80
111
  }
112
+ if (!env.CLAUDE_COMMAND && next.PATH) {
113
+ next.PATH = removeForgeRelayNodeModulesBinFromPath(next.PATH);
114
+ }
81
115
  return next;
82
116
  }
@@ -1,4 +1,61 @@
1
- function sandboxModeFor(writeMode) {
1
+ import { runExternalCommand, } from "../runtime/external-command.js";
2
+ import { removeForgeRelayNodeModulesBinFromPath } from "../path.js";
3
+ import { asRecord, directString, requireFinalResponse } from "../shared.js";
4
+ export class CodexSubagentAdapter {
5
+ runner;
6
+ env;
7
+ provider = "codex";
8
+ constructor(runner = runExternalCommand, env = process.env) {
9
+ this.runner = runner;
10
+ this.env = env;
11
+ }
12
+ async run(input) {
13
+ const command = this.env.CODEX_COMMAND?.trim() || "codex";
14
+ const result = await this.runner({
15
+ command,
16
+ args: codexCommandArgs(input),
17
+ cwd: input.workspace,
18
+ env: codexCommandEnvironment(this.env),
19
+ signal: input.signal,
20
+ stdin: input.prompt,
21
+ label: "Codex",
22
+ });
23
+ const parsed = parseCodexJsonLines(result.stdout);
24
+ if (parsed.error)
25
+ throw new Error(`Codex returned an error: ${parsed.error}`);
26
+ if (input.providerSessionId &&
27
+ parsed.providerSessionId &&
28
+ parsed.providerSessionId !== input.providerSessionId) {
29
+ throw new Error(`Codex resume returned a different session id (${parsed.providerSessionId}) than requested (${input.providerSessionId}).`);
30
+ }
31
+ return {
32
+ provider: this.provider,
33
+ providerSessionId: parsed.providerSessionId ?? input.providerSessionId ?? null,
34
+ finalResponse: requireFinalResponse("Codex", parsed.finalResponse),
35
+ };
36
+ }
37
+ }
38
+ export function codexCommandArgs(input) {
39
+ const args = [
40
+ "exec",
41
+ "--json",
42
+ "--sandbox",
43
+ codexSandboxMode(input.writeMode),
44
+ "--cd",
45
+ input.workspace,
46
+ "--config",
47
+ 'approval_policy="never"',
48
+ ];
49
+ if (input.model)
50
+ args.push("--model", input.model);
51
+ if (input.thinking) {
52
+ args.push("--config", `model_reasoning_effort=${JSON.stringify(input.thinking)}`);
53
+ }
54
+ if (input.providerSessionId)
55
+ args.push("resume", input.providerSessionId);
56
+ return args;
57
+ }
58
+ function codexSandboxMode(writeMode) {
2
59
  switch (writeMode) {
3
60
  case "allowed":
4
61
  return "workspace-write";
@@ -9,46 +66,53 @@ function sandboxModeFor(writeMode) {
9
66
  return "read-only";
10
67
  }
11
68
  }
12
- function threadOptionsFor(input) {
69
+ export function codexCommandEnvironment(env) {
70
+ if (env.CODEX_COMMAND || !env.PATH)
71
+ return { ...env };
13
72
  return {
14
- workingDirectory: input.workspace,
15
- sandboxMode: sandboxModeFor(input.writeMode),
16
- approvalPolicy: "never",
17
- model: input.model,
18
- modelReasoningEffort: input.thinking,
73
+ ...env,
74
+ PATH: removeForgeRelayNodeModulesBinFromPath(env.PATH),
19
75
  };
20
76
  }
21
- export class CodexSdkSubagentRuntime {
22
- provider = "codex";
23
- codex;
24
- constructor(codex) {
25
- this.codex = codex;
26
- }
27
- async run(input) {
28
- const options = threadOptionsFor(input);
29
- const thread = input.providerSessionId
30
- ? this.codex.resumeThread(input.providerSessionId, options)
31
- : this.codex.startThread(options);
32
- const turn = await thread.run(input.prompt, { signal: input.signal });
33
- return {
34
- provider: this.provider,
35
- providerSessionId: thread.id,
36
- finalResponse: turn.finalResponse,
37
- };
38
- }
39
- }
40
- export async function createCodexSdkSubagentRuntime(options, codexFactory) {
41
- const factory = codexFactory ?? (await defaultCodexFactory());
42
- return new CodexSdkSubagentRuntime(factory(options));
43
- }
44
- export class CodexSubagentAdapter {
45
- provider = "codex";
46
- async run(input) {
47
- const runtime = await createCodexSdkSubagentRuntime();
48
- return runtime.run(input);
77
+ export function parseCodexJsonLines(output) {
78
+ let providerSessionId;
79
+ let finalResponse = "";
80
+ let error;
81
+ for (const line of output.split(/\r?\n/)) {
82
+ const trimmed = line.trim();
83
+ if (!trimmed)
84
+ continue;
85
+ let event;
86
+ try {
87
+ const parsed = JSON.parse(trimmed);
88
+ const record = asRecord(parsed);
89
+ if (!record)
90
+ continue;
91
+ event = record;
92
+ }
93
+ catch {
94
+ continue;
95
+ }
96
+ if (event.type === "thread.started") {
97
+ providerSessionId = directString(event.thread_id) ?? providerSessionId;
98
+ continue;
99
+ }
100
+ if (event.type === "item.completed") {
101
+ const item = asRecord(event.item);
102
+ if (item?.type === "agent_message") {
103
+ finalResponse = directString(item.text) ?? finalResponse;
104
+ }
105
+ continue;
106
+ }
107
+ if (event.type === "turn.failed") {
108
+ const failure = asRecord(event.error);
109
+ error = directString(failure?.message) ?? directString(event.message) ?? error;
110
+ continue;
111
+ }
112
+ if (event.type === "error") {
113
+ const nested = asRecord(event.error);
114
+ error = directString(event.message) ?? directString(nested?.message) ?? error;
115
+ }
49
116
  }
50
- }
51
- async function defaultCodexFactory() {
52
- const module = await import("@openai/codex-sdk");
53
- return (options) => new module.Codex(options);
117
+ return { providerSessionId, finalResponse, error };
54
118
  }
@@ -1,102 +1,132 @@
1
- import { asRecord, readArray, readNestedString, requireFinalResponse, unwrapProviderPayload, } from "../shared.js";
1
+ import { runExternalCommand, } from "../runtime/external-command.js";
2
+ import { removeForgeRelayNodeModulesBinFromPath } from "../path.js";
3
+ import { asRecord, directString, readArray, requireFinalResponse, unwrapProviderPayload, } from "../shared.js";
2
4
  export class OpencodeSubagentAdapter {
5
+ runner;
6
+ env;
3
7
  provider = "opencode";
8
+ constructor(runner = runExternalCommand, env = process.env) {
9
+ this.runner = runner;
10
+ this.env = env;
11
+ }
4
12
  async run(input) {
5
- const { createOpencode } = await import("@opencode-ai/sdk/v2");
6
- const { client, server } = await createOpencode();
7
- try {
8
- const sessionId = input.providerSessionId ?? await createOpencodeSession(client, input);
9
- let abortPromise;
10
- const abort = () => {
11
- abortPromise ??= abortOpencodeSession(client, sessionId, input.workspace);
12
- };
13
- if (input.signal?.aborted)
14
- abort();
15
- else
16
- input.signal?.addEventListener("abort", abort, { once: true });
17
- try {
18
- input.signal?.throwIfAborted();
19
- const promptResult = await promptOpencodeSession(client, sessionId, input);
20
- await waitForOpencodeSession(client, sessionId, input.signal);
21
- const messages = await readOpencodeMessages(client, sessionId, input.signal);
22
- const finalResponse = requireFinalResponse("OpenCode", extractOpenCodeFinalResponse(messages) || extractOpenCodeFinalResponse(promptResult));
23
- return {
24
- provider: this.provider,
25
- providerSessionId: sessionId,
26
- finalResponse,
27
- };
28
- }
29
- finally {
30
- input.signal?.removeEventListener("abort", abort);
31
- if (abortPromise)
32
- await abortPromise;
33
- }
34
- }
35
- finally {
36
- server.close();
13
+ const command = this.env.OPENCODE_COMMAND?.trim() || "opencode";
14
+ const commandEnv = opencodeCommandEnvironment(this.env);
15
+ const result = await this.runner({
16
+ command,
17
+ args: opencodeCommandArgs(input),
18
+ cwd: input.workspace,
19
+ env: commandEnv,
20
+ signal: input.signal,
21
+ stdin: input.prompt,
22
+ label: "OpenCode",
23
+ });
24
+ const streamed = parseOpenCodeJsonLines(result.stdout);
25
+ const providerSessionId = streamed.providerSessionId ?? input.providerSessionId ?? null;
26
+ let finalResponse = streamed.finalResponse;
27
+ if (!finalResponse && providerSessionId) {
28
+ const exported = await this.runner({
29
+ command,
30
+ args: ["export", providerSessionId],
31
+ cwd: input.workspace,
32
+ env: commandEnv,
33
+ signal: input.signal,
34
+ label: "OpenCode export",
35
+ });
36
+ finalResponse = extractOpenCodeExportFinalResponse(exported.stdout);
37
37
  }
38
+ return {
39
+ provider: this.provider,
40
+ providerSessionId,
41
+ finalResponse: requireFinalResponse("OpenCode", finalResponse),
42
+ };
38
43
  }
39
44
  }
40
- async function createOpencodeSession(client, input) {
41
- const sessionClient = client;
42
- const result = await sessionClient.session.create({
43
- directory: input.workspace,
44
- location: { directory: input.workspace },
45
- ...(input.model ? { model: parseOpencodeModel(input.model) } : {}),
46
- }, { throwOnError: true, signal: input.signal });
47
- const id = readNestedString(result, ["id"]) ??
48
- readNestedString(result, ["data", "id"]) ??
49
- readNestedString(result, ["session", "id"]) ??
50
- readNestedString(result, ["data", "session", "id"]);
51
- if (typeof id !== "string") {
52
- throw new Error("OpenCode did not return a session id.");
53
- }
54
- return id;
45
+ export function opencodeCommandArgs(input) {
46
+ const args = [
47
+ "run",
48
+ "--format",
49
+ "json",
50
+ "--dir",
51
+ input.workspace,
52
+ "--dangerously-skip-permissions",
53
+ ];
54
+ if (input.model)
55
+ args.push("--model", input.model);
56
+ if (input.thinking)
57
+ args.push("--variant", input.thinking);
58
+ if (input.providerSessionId)
59
+ args.push("--session", input.providerSessionId);
60
+ return args;
55
61
  }
56
- async function promptOpencodeSession(client, sessionId, input) {
57
- const session = client.session;
58
- const promptInput = {
59
- sessionID: sessionId,
60
- directory: input.workspace,
61
- prompt: { parts: [{ type: "text", text: input.prompt }] },
62
- parts: [{ type: "text", text: input.prompt }],
63
- ...(input.model ? { model: parseOpencodeModel(input.model) } : {}),
64
- ...(input.thinking ? { variant: input.thinking } : {}),
62
+ export function opencodeCommandEnvironment(env) {
63
+ if (env.OPENCODE_COMMAND || !env.PATH)
64
+ return { ...env };
65
+ return {
66
+ ...env,
67
+ PATH: removeForgeRelayNodeModulesBinFromPath(env.PATH),
65
68
  };
66
- return session.prompt(promptInput, { throwOnError: true, signal: input.signal });
67
- }
68
- async function waitForOpencodeSession(client, sessionId, signal) {
69
- const session = client.session;
70
- if (!session?.wait)
71
- return;
72
- await session.wait({ sessionID: sessionId }, { throwOnError: true, signal });
73
69
  }
74
- async function readOpencodeMessages(client, sessionId, signal) {
75
- const session = client.session;
76
- if (!session?.messages)
77
- return undefined;
78
- return session.messages({ sessionID: sessionId, order: "asc", limit: 100 }, { throwOnError: true, signal });
70
+ export function parseOpenCodeJsonLines(output) {
71
+ let providerSessionId;
72
+ const textByMessage = new Map();
73
+ const messageOrder = [];
74
+ let fallback = "";
75
+ let anonymousIndex = 0;
76
+ for (const line of output.split(/\r?\n/)) {
77
+ const trimmed = line.trim();
78
+ if (!trimmed)
79
+ continue;
80
+ let record;
81
+ try {
82
+ const parsed = asRecord(JSON.parse(trimmed));
83
+ if (!parsed)
84
+ continue;
85
+ record = parsed;
86
+ }
87
+ catch {
88
+ continue;
89
+ }
90
+ providerSessionId =
91
+ directString(record.sessionID) ??
92
+ directString(record.sessionId) ??
93
+ providerSessionId;
94
+ if (record.type === "text") {
95
+ const part = asRecord(record.part);
96
+ const text = typeof part?.text === "string" ? part.text : undefined;
97
+ if (!text)
98
+ continue;
99
+ const messageId = directString(part?.messageID) ??
100
+ directString(part?.messageId) ??
101
+ `anonymous-${anonymousIndex++}`;
102
+ if (!textByMessage.has(messageId)) {
103
+ textByMessage.set(messageId, []);
104
+ messageOrder.push(messageId);
105
+ }
106
+ textByMessage.get(messageId).push(text);
107
+ continue;
108
+ }
109
+ const extracted = extractOpenCodeFinalResponse(record);
110
+ if (extracted)
111
+ fallback = extracted;
112
+ }
113
+ const lastMessageId = messageOrder.at(-1);
114
+ const finalResponse = lastMessageId
115
+ ? (textByMessage.get(lastMessageId) ?? []).join("").trim()
116
+ : fallback;
117
+ return { providerSessionId, finalResponse };
79
118
  }
80
- async function abortOpencodeSession(client, sessionId, workspace) {
81
- const session = client.session;
82
- if (!session?.abort)
83
- return;
119
+ function extractOpenCodeExportFinalResponse(output) {
120
+ const trimmed = output.trim();
121
+ if (!trimmed)
122
+ return "";
84
123
  try {
85
- await session.abort({ sessionID: sessionId, directory: workspace }, { throwOnError: true });
124
+ return extractOpenCodeFinalResponse(JSON.parse(trimmed));
86
125
  }
87
126
  catch {
88
- // The prompt request may already have observed the AbortSignal and closed the local server.
127
+ return "";
89
128
  }
90
129
  }
91
- function parseOpencodeModel(model) {
92
- const separator = model.indexOf("/");
93
- if (separator === -1)
94
- return { providerID: "opencode", modelID: model };
95
- return {
96
- providerID: model.slice(0, separator),
97
- modelID: model.slice(separator + 1),
98
- };
99
- }
100
130
  export function extractOpenCodeFinalResponse(value) {
101
131
  const root = unwrapProviderPayload(value);
102
132
  const messages = Array.isArray(root) ? root : readArray(root, "messages");