reeval 0.2.0 → 0.4.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 (224) hide show
  1. package/THIRD_PARTY_NOTICES.md +9 -9
  2. package/assets/package.json +1 -1
  3. package/assets/pnpm-lock.yaml +84 -33
  4. package/dist/agent-C9dLMCM7.js +334 -0
  5. package/dist/agent-C9dLMCM7.js.map +1 -0
  6. package/dist/{agent-D-6z0Z3F.js → agent-M70OWyfG.js} +5 -5
  7. package/dist/{agent-D-6z0Z3F.js.map → agent-M70OWyfG.js.map} +1 -1
  8. package/dist/analysis-CxSCt1YY.js.map +1 -1
  9. package/dist/{assemble-DtouO0iQ.js → assemble-BmVT26kf.js} +25 -17
  10. package/dist/assemble-BmVT26kf.js.map +1 -0
  11. package/dist/assemble-DZiN1p_E.js +2 -0
  12. package/dist/{attempt-BdRQ9jKX.js → attempt-CGPV3BZY.js} +2 -2
  13. package/dist/{attempt-BdRQ9jKX.js.map → attempt-CGPV3BZY.js.map} +1 -1
  14. package/dist/bridge/index.mjs +1620 -1793
  15. package/dist/bridge/package.json +2 -2
  16. package/dist/bridge/pnpm-lock.yaml +75 -75
  17. package/dist/bridge/pnpm-workspace.yaml +1 -1
  18. package/dist/{cli-CGuUdOPK.js → cli-BdxFLEIc.js} +166 -55
  19. package/dist/cli-BdxFLEIc.js.map +1 -0
  20. package/dist/{cli-spec-DKAu09Gi.js → cli-spec-DUeyuSIQ.js} +11 -4
  21. package/dist/cli-spec-DUeyuSIQ.js.map +1 -0
  22. package/dist/cli.js +2 -2
  23. package/dist/{command-CCuYRGsI.js → command-BtcBitmY.js} +12 -12
  24. package/dist/{command-CCuYRGsI.js.map → command-BtcBitmY.js.map} +1 -1
  25. package/dist/{command-BekzrvZI.js → command-ByqQ-v-j.js} +13 -13
  26. package/dist/{command-BekzrvZI.js.map → command-ByqQ-v-j.js.map} +1 -1
  27. package/dist/{command-CLKuXn6Z.js → command-CIsO5xdR.js} +11 -9
  28. package/dist/command-CIsO5xdR.js.map +1 -0
  29. package/dist/{command-CEm8PZ1N.js → command-CQiFfn-C.js} +169 -280
  30. package/dist/command-CQiFfn-C.js.map +1 -0
  31. package/dist/command-DllipyoK.js +2 -0
  32. package/dist/{commands-CF8mOfiu.js → commands-CPrrZ2mI.js} +29 -13
  33. package/dist/commands-CPrrZ2mI.js.map +1 -0
  34. package/dist/{commands-A4udfyKZ.js → commands-D3Dxgh60.js} +1 -1
  35. package/dist/{config-DqJvSqYo.js → config-B93Csktg.js} +43 -15
  36. package/dist/{config-DqJvSqYo.js.map → config-B93Csktg.js.map} +1 -1
  37. package/dist/db-DIE0bsNO.js +2 -0
  38. package/dist/{db-DxDzMhtB.js → db-SpvY8l1g.js} +4 -4
  39. package/dist/{db-DxDzMhtB.js.map → db-SpvY8l1g.js.map} +1 -1
  40. package/dist/{dist-BZXYHN2X.js → dist-C2k9IvL3.js} +107 -55
  41. package/dist/dist-C2k9IvL3.js.map +1 -0
  42. package/dist/{docker-Buu3a60z.js → docker-Bv1ZC6V7.js} +1 -1
  43. package/dist/{docker-DQDZWXF9.js → docker-C2YwD_dh.js} +3 -3
  44. package/dist/{docker-DQDZWXF9.js.map → docker-C2YwD_dh.js.map} +1 -1
  45. package/dist/{docker-w0CSdQq0.js → docker-uNaAGE2C.js} +5 -5
  46. package/dist/{docker-w0CSdQq0.js.map → docker-uNaAGE2C.js.map} +1 -1
  47. package/dist/{doctor-Bs1_pG9_.js → doctor-COH-levb.js} +88 -20
  48. package/dist/doctor-COH-levb.js.map +1 -0
  49. package/dist/{egress-QJMzVtbw.js → egress-C7Obb71D.js} +35 -19
  50. package/dist/egress-C7Obb71D.js.map +1 -0
  51. package/dist/{environment-9WHSWLxH.js → environment-lDFQY2BF.js} +116 -4
  52. package/dist/environment-lDFQY2BF.js.map +1 -0
  53. package/dist/{files-D2-qn_Dl.js → files-BO9P6msM.js} +114 -14
  54. package/dist/files-BO9P6msM.js.map +1 -0
  55. package/dist/files-BRWmQJ-9.js +2 -0
  56. package/dist/files-Bwr8UW4b.js +2 -0
  57. package/dist/{files-bF2fPuT9.js → files-CMoTBEul.js} +40 -7
  58. package/dist/files-CMoTBEul.js.map +1 -0
  59. package/dist/{git-D6hMeieS.js → git-LJp55bkf.js} +3 -3
  60. package/dist/{git-D6hMeieS.js.map → git-LJp55bkf.js.map} +1 -1
  61. package/dist/{harness-ls5aX1UO.js → harness-BbMShUC-.js} +4 -4
  62. package/dist/{harness-ls5aX1UO.js.map → harness-BbMShUC-.js.map} +1 -1
  63. package/dist/harness.d.ts +2 -2
  64. package/dist/harness.js +1 -1
  65. package/dist/{header-DzFDoMCI.js → header-sPQ9Lq2S.js} +2 -2
  66. package/dist/{header-DzFDoMCI.js.map → header-sPQ9Lq2S.js.map} +1 -1
  67. package/dist/{images-2m2U-UQc.js → images-Pph_XJ68.js} +6 -6
  68. package/dist/{images-2m2U-UQc.js.map → images-Pph_XJ68.js.map} +1 -1
  69. package/dist/index.d.ts +11 -4
  70. package/dist/index.js +173 -65
  71. package/dist/index.js.map +1 -1
  72. package/dist/{init-CH9UY7Bt.js → init-BcYpzHXs.js} +10 -10
  73. package/dist/{init-CH9UY7Bt.js.map → init-BcYpzHXs.js.map} +1 -1
  74. package/dist/init-YS5qBRwV.js +2 -0
  75. package/dist/{invocations-rXktgSAn.js → invocations-DHC946b-.js} +4 -4
  76. package/dist/{invocations-rXktgSAn.js.map → invocations-DHC946b-.js.map} +1 -1
  77. package/dist/{jiti-BOLXpKyG.js → jiti-DWsGEgxD.js} +4 -4
  78. package/dist/{jiti-BOLXpKyG.js.map → jiti-DWsGEgxD.js.map} +1 -1
  79. package/dist/{judge-D8Uf4dA9.js → judge-D6wqgTDp.js} +17 -4
  80. package/dist/judge-D6wqgTDp.js.map +1 -0
  81. package/dist/judge.d.ts +12 -8
  82. package/dist/judge.js +13 -5
  83. package/dist/judge.js.map +1 -1
  84. package/dist/{live-Bs3LReMV.js → live-DZkCtmNP.js} +2 -2
  85. package/dist/{live-Bs3LReMV.js.map → live-DZkCtmNP.js.map} +1 -1
  86. package/dist/{matchers-DyLvm7EG.js → matchers-DbFPZQgA.js} +9 -4
  87. package/dist/{matchers-DyLvm7EG.js.map → matchers-DbFPZQgA.js.map} +1 -1
  88. package/dist/model-CJ6USX6C.js +2 -0
  89. package/dist/model-CNoc8t2Y.js +2 -0
  90. package/dist/model-DUZtNaYR.js +228 -0
  91. package/dist/model-DUZtNaYR.js.map +1 -0
  92. package/dist/{model-CDL98xue.js → model-ORtLm2UB.js} +41 -3
  93. package/dist/{model-CDL98xue.js.map → model-ORtLm2UB.js.map} +1 -1
  94. package/dist/{model-BdO79gWz.js → model-f0wprGPm.js} +3 -3
  95. package/dist/{model-BdO79gWz.js.map → model-f0wprGPm.js.map} +1 -1
  96. package/dist/{node-kG64XxdE.js → node-OXPxBD2N.js} +3 -3
  97. package/dist/{node-kG64XxdE.js.map → node-OXPxBD2N.js.map} +1 -1
  98. package/dist/{opencode-providers-DZuD_oYt.js → opencode-providers-DJVocFOU.js} +31 -3
  99. package/dist/opencode-providers-DJVocFOU.js.map +1 -0
  100. package/dist/{options-k25fjaER.js → options-Ftb2gE01.js} +85 -26
  101. package/dist/options-Ftb2gE01.js.map +1 -0
  102. package/dist/patch-BxqggH-T.js +118 -0
  103. package/dist/patch-BxqggH-T.js.map +1 -0
  104. package/dist/{plan-gwk6dJ8c.js → plan-BkhLKrsN.js} +23 -6
  105. package/dist/plan-BkhLKrsN.js.map +1 -0
  106. package/dist/{preflight-B6hfAXhh.js → preflight-CuvY0o3B.js} +3 -3
  107. package/dist/{preflight-B6hfAXhh.js.map → preflight-CuvY0o3B.js.map} +1 -1
  108. package/dist/{preflight-D2Hce5Jp.js → preflight-D3cJETui.js} +1 -1
  109. package/dist/{prepare-NuWsT11j.js → prepare-BI9j2-H-.js} +5 -5
  110. package/dist/{prepare-NuWsT11j.js.map → prepare-BI9j2-H-.js.map} +1 -1
  111. package/dist/{prepare-C3fefNnV.js → prepare-CVuTk5F9.js} +2 -2
  112. package/dist/{provenance-Dfb8YY5Y.js → provenance-6n49wska.js} +22 -4
  113. package/dist/provenance-6n49wska.js.map +1 -0
  114. package/dist/{providers-Bfx5Mnk9.js → providers-BFapaM2D.js} +3 -3
  115. package/dist/{providers-Bfx5Mnk9.js.map → providers-BFapaM2D.js.map} +1 -1
  116. package/dist/{providers-BMIGzPPG.js → providers-Cay8pI0y.js} +1 -1
  117. package/dist/query-Br77sQYj.js +2 -0
  118. package/dist/{query-CFATHZmV.js → query-CnstTDt8.js} +2 -2
  119. package/dist/{query-CFATHZmV.js.map → query-CnstTDt8.js.map} +1 -1
  120. package/dist/questions-Cm321-Mo.js +325 -0
  121. package/dist/questions-Cm321-Mo.js.map +1 -0
  122. package/dist/{record-DQ8wzyco.js → record-CbuQH4l1.js} +7 -3
  123. package/dist/record-CbuQH4l1.js.map +1 -0
  124. package/dist/{redact-C4fL_bZP.js → redact-DJ9JCShu.js} +99 -28
  125. package/dist/redact-DJ9JCShu.js.map +1 -0
  126. package/dist/{results-BtvBJAp-.js → results-D9GgBYw5.js} +4 -4
  127. package/dist/{results-BtvBJAp-.js.map → results-D9GgBYw5.js.map} +1 -1
  128. package/dist/{root-CFDjpWuc.js → root-Zeokqt3-.js} +2 -2
  129. package/dist/{root-CFDjpWuc.js.map → root-Zeokqt3-.js.map} +1 -1
  130. package/dist/{sandbox-BJPWuQE9.js → sandbox-CO__l-i1.js} +3 -3
  131. package/dist/{sandbox-BJPWuQE9.js.map → sandbox-CO__l-i1.js.map} +1 -1
  132. package/dist/sandbox.d.ts +2 -2
  133. package/dist/sandbox.js +1 -1
  134. package/dist/server-D5Zq3I5N.js +2 -0
  135. package/dist/{server-5DWmKKfV.js → server-DsmIrBzi.js} +12 -10
  136. package/dist/{server-5DWmKKfV.js.map → server-DsmIrBzi.js.map} +1 -1
  137. package/dist/source-BgqURlel.js +2 -0
  138. package/dist/{source-Bsfa3LZH.js → source-DJ1c8jwe.js} +2 -2
  139. package/dist/{source-Bsfa3LZH.js.map → source-DJ1c8jwe.js.map} +1 -1
  140. package/dist/{src-DXx_29pq.js → src-B1OT_Zhf.js} +46 -46
  141. package/dist/{src-DXx_29pq.js.map → src-B1OT_Zhf.js.map} +1 -1
  142. package/dist/{src-BQyMiWri.js → src-BAiYD4eC.js} +48 -7
  143. package/dist/{src-BQyMiWri.js.map → src-BAiYD4eC.js.map} +1 -1
  144. package/dist/{src-N7E_mfcZ.js → src-BC5FFzE8.js} +1 -1
  145. package/dist/src-Bi2Y3EkE.js +3 -0
  146. package/dist/{src-D8KYzxBX.js → src-BmBFlEIe.js} +12 -7
  147. package/dist/{src-D8KYzxBX.js.map → src-BmBFlEIe.js.map} +1 -1
  148. package/dist/src-C8WNyopo.js +3 -0
  149. package/dist/src-CYz-TreZ.js +3 -0
  150. package/dist/src-C_91Ln3Z.js +4 -0
  151. package/dist/{src-Cmp-7YrD.js → src-DWk0e7ix.js} +5 -5
  152. package/dist/{src-Cmp-7YrD.js.map → src-DWk0e7ix.js.map} +1 -1
  153. package/dist/{src-AEwxd1UP.js → src-DfEAlC9s.js} +4 -4
  154. package/dist/{src-AEwxd1UP.js.map → src-DfEAlC9s.js.map} +1 -1
  155. package/dist/{src-DwwiVLqv.js → src-V-iU5X9S.js} +24 -21
  156. package/dist/{src-DwwiVLqv.js.map → src-V-iU5X9S.js.map} +1 -1
  157. package/dist/{src-C-LHyTSV.js → src-gPJFzHcK.js} +1 -1
  158. package/dist/src-gVzsO4_f.js +3 -0
  159. package/dist/{state-DZ7eqb1F.js → state-DFbJrVi3.js} +15 -15
  160. package/dist/{state-DZ7eqb1F.js.map → state-DFbJrVi3.js.map} +1 -1
  161. package/dist/state-Joc2b4qk.js +2 -0
  162. package/dist/studio-app/assets/index-B1Pf3LAT.css +2 -0
  163. package/dist/studio-app/assets/index-BhorHbrj.js +58 -0
  164. package/dist/studio-app/index.html +2 -2
  165. package/dist/{summary-D7Ttky0z.js → summary-CEMYhzXm.js} +33 -5
  166. package/dist/summary-CEMYhzXm.js.map +1 -0
  167. package/dist/summary-WR5qLj_z.js +192 -0
  168. package/dist/summary-WR5qLj_z.js.map +1 -0
  169. package/dist/tools-DN-cJcfi.js +249 -0
  170. package/dist/tools-DN-cJcfi.js.map +1 -0
  171. package/dist/{types-CYJldf5N.d.ts → types-B_CqVrOS.d.ts} +210 -30
  172. package/dist/view/index.d.ts +63 -1
  173. package/dist/view/index.js +5 -5
  174. package/dist/{views-BrHOv6wg.js → views-DNbUdW9d.js} +4 -4
  175. package/dist/{views-BrHOv6wg.js.map → views-DNbUdW9d.js.map} +1 -1
  176. package/dist/views-DnYbVkKg.js +3 -0
  177. package/dist/{worker-entry-DnYkANPG.js → worker-entry-CJmG4hhE.js} +3 -3
  178. package/dist/{worker-entry-DnYkANPG.js.map → worker-entry-CJmG4hhE.js.map} +1 -1
  179. package/dist/{workspace-BVgNhC9h.js → workspace-CmSp2MXO.js} +1 -1
  180. package/dist/{workspace-uPapQj16.js → workspace-QtlJtRut.js} +5 -5
  181. package/dist/{workspace-uPapQj16.js.map → workspace-QtlJtRut.js.map} +1 -1
  182. package/docker/Dockerfile +2 -0
  183. package/docker/reeval-path.sh +21 -0
  184. package/package.json +13 -12
  185. package/dist/assemble-Dm4gD1TU.js +0 -2
  186. package/dist/assemble-DtouO0iQ.js.map +0 -1
  187. package/dist/cli-CGuUdOPK.js.map +0 -1
  188. package/dist/cli-spec-DKAu09Gi.js.map +0 -1
  189. package/dist/command-CEm8PZ1N.js.map +0 -1
  190. package/dist/command-CLKuXn6Z.js.map +0 -1
  191. package/dist/commands-CF8mOfiu.js.map +0 -1
  192. package/dist/db-Cjj2ZsfQ.js +0 -2
  193. package/dist/dist-BZXYHN2X.js.map +0 -1
  194. package/dist/doctor-Bs1_pG9_.js.map +0 -1
  195. package/dist/egress-QJMzVtbw.js.map +0 -1
  196. package/dist/environment-9WHSWLxH.js.map +0 -1
  197. package/dist/files--6CEUXm7.js +0 -2
  198. package/dist/files-Bd25OAQH.js +0 -2
  199. package/dist/files-D2-qn_Dl.js.map +0 -1
  200. package/dist/files-bF2fPuT9.js.map +0 -1
  201. package/dist/init-DJuxvYCW.js +0 -2
  202. package/dist/judge-D8Uf4dA9.js.map +0 -1
  203. package/dist/model-v-0_Ik7S.js +0 -2
  204. package/dist/opencode-providers-DZuD_oYt.js.map +0 -1
  205. package/dist/options-k25fjaER.js.map +0 -1
  206. package/dist/patch-BkKje4cp.js +0 -69
  207. package/dist/patch-BkKje4cp.js.map +0 -1
  208. package/dist/plan-gwk6dJ8c.js.map +0 -1
  209. package/dist/provenance-Dfb8YY5Y.js.map +0 -1
  210. package/dist/query-DR4m6CXf.js +0 -2
  211. package/dist/record-DQ8wzyco.js.map +0 -1
  212. package/dist/redact-C4fL_bZP.js.map +0 -1
  213. package/dist/server-buV3adNS.js +0 -2
  214. package/dist/source-BXzrmof5.js +0 -2
  215. package/dist/src-55j1zNHH.js +0 -3
  216. package/dist/src-BDwSuyIo.js +0 -3
  217. package/dist/src-DEgff9Z7.js +0 -4
  218. package/dist/src-W9g-y5X5.js +0 -3
  219. package/dist/src-daS0wKH2.js +0 -3
  220. package/dist/state-Cyw3QTsN.js +0 -2
  221. package/dist/studio-app/assets/index-Da1XDzx8.js +0 -58
  222. package/dist/studio-app/assets/index-xwzWdcE_.css +0 -2
  223. package/dist/summary-D7Ttky0z.js.map +0 -1
  224. package/dist/views-CwCWLWqo.js +0 -3
@@ -1,1911 +1,1738 @@
1
- // Modified by Invertase with patches/@ai-sdk__harness-claude-code@1.0.128.patch; see THIRD_PARTY_NOTICES.md.
2
- // ../harness/dist/bridge/index.js
3
- import { appendFile, mkdir, writeFile } from "fs/promises";
4
- import { existsSync, readFileSync } from "fs";
5
- import { randomUUID } from "crypto";
6
- import { env as procEnv, pid, stdout } from "process";
1
+ // Modified by Invertase with patches/@ai-sdk__harness-claude-code@1.0.138.patch; see THIRD_PARTY_NOTICES.md.
2
+ import { appendFile, mkdir, writeFile } from "node:fs/promises";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { randomUUID } from "node:crypto";
5
+ import { argv, env, pid, stdout } from "node:process";
7
6
  import { WebSocketServer } from "ws";
8
- var DEBUG_LEVEL_WEIGHT = {
9
- error: 0,
10
- warn: 1,
11
- info: 2,
12
- debug: 3,
13
- trace: 4
7
+ import * as claudeAgentSdk from "@anthropic-ai/claude-agent-sdk";
8
+ import * as mcpServerModule from "@modelcontextprotocol/sdk/server/mcp.js";
9
+ import { z } from "zod/v4";
10
+ //#region ../harness/dist/bridge/index.js
11
+ const DEBUG_LEVEL_WEIGHT = {
12
+ error: 0,
13
+ warn: 1,
14
+ info: 2,
15
+ debug: 3,
16
+ trace: 4
14
17
  };
18
+ /** Exact-or-dotted-prefix subsystem match (`'bridge'` matches `'bridge.turn'`). */
15
19
  function subsystemMatches(filters, subsystem) {
16
- if (!filters || filters.length === 0) return true;
17
- return filters.some(
18
- (filter) => subsystem === filter || subsystem.startsWith(`${filter}.`)
19
- );
20
+ if (!filters || filters.length === 0) return true;
21
+ return filters.some((filter) => subsystem === filter || subsystem.startsWith(`${filter}.`));
20
22
  }
21
23
  function formatBridgeError(err) {
22
- if (err instanceof Error) {
23
- return { name: err.name, message: err.message, stack: err.stack };
24
- }
25
- if (typeof err === "string") {
26
- return { message: err };
27
- }
28
- if (err !== null && typeof err === "object") {
29
- try {
30
- return { message: JSON.stringify(err) };
31
- } catch {
32
- }
33
- }
34
- return { message: String(err) };
24
+ if (err instanceof Error) return {
25
+ name: err.name,
26
+ message: err.message,
27
+ stack: err.stack
28
+ };
29
+ if (typeof err === "string") return { message: err };
30
+ if (err !== null && typeof err === "object") try {
31
+ return { message: JSON.stringify(err) };
32
+ } catch {}
33
+ return { message: String(err) };
35
34
  }
36
35
  function createBridgeUserMessageQueue(options) {
37
- const messages = [];
38
- const waiters = [];
39
- const entries = /* @__PURE__ */ new Map();
40
- let closed = false;
41
- let pendingCount = 0;
42
- const enqueue = (input) => {
43
- const existing = entries.get(input.messageId);
44
- if (existing != null) {
45
- if (existing.response != null) {
46
- options.respond(existing.response);
47
- }
48
- return;
49
- }
50
- let settled = false;
51
- const settle = (response) => {
52
- if (settled) return;
53
- settled = true;
54
- pendingCount--;
55
- const entry = entries.get(input.messageId);
56
- if (entry != null) entry.response = response;
57
- options.respond(response);
58
- };
59
- const message = {
60
- messageId: input.messageId,
61
- text: input.text,
62
- accept: () => {
63
- settle({
64
- type: "user-message-response",
65
- messageId: input.messageId,
66
- accepted: true
67
- });
68
- },
69
- reject: (error) => {
70
- settle({
71
- type: "user-message-response",
72
- messageId: input.messageId,
73
- accepted: false,
74
- error: { message: formatBridgeError(error).message }
75
- });
76
- }
77
- };
78
- entries.set(input.messageId, {
79
- reject: message.reject
80
- });
81
- pendingCount++;
82
- if (closed) {
83
- message.reject(
84
- new Error("The bridge turn is no longer accepting user messages.")
85
- );
86
- return;
87
- }
88
- const waiter = waiters.shift();
89
- if (waiter != null) {
90
- waiter({ done: false, value: message });
91
- } else {
92
- messages.push(message);
93
- }
94
- };
95
- const close = (error) => {
96
- if (closed) return;
97
- closed = true;
98
- const reason = error ?? new Error("The bridge turn ended before accepting the user message.");
99
- for (const entry of entries.values()) {
100
- if (entry.response == null) entry.reject(reason);
101
- }
102
- messages.length = 0;
103
- while (waiters.length > 0) {
104
- waiters.shift()({ done: true, value: void 0 });
105
- }
106
- };
107
- return {
108
- get pendingCount() {
109
- return pendingCount;
110
- },
111
- enqueue,
112
- close,
113
- [Symbol.asyncIterator]() {
114
- return {
115
- next: () => {
116
- const message = messages.shift();
117
- if (message != null) {
118
- return Promise.resolve({ done: false, value: message });
119
- }
120
- if (closed) {
121
- return Promise.resolve({
122
- done: true,
123
- value: void 0
124
- });
125
- }
126
- return new Promise(
127
- (resolve) => {
128
- waiters.push(resolve);
129
- }
130
- );
131
- }
132
- };
133
- }
134
- };
36
+ const messages = [];
37
+ const waiters = [];
38
+ const entries = /* @__PURE__ */ new Map();
39
+ let closed = false;
40
+ let pendingCount = 0;
41
+ const enqueue = (input) => {
42
+ const existing = entries.get(input.messageId);
43
+ if (existing != null) {
44
+ if (existing.response != null) options.respond(existing.response);
45
+ return;
46
+ }
47
+ let settled = false;
48
+ const settle = (response) => {
49
+ if (settled) return;
50
+ settled = true;
51
+ pendingCount--;
52
+ const entry = entries.get(input.messageId);
53
+ if (entry != null) entry.response = response;
54
+ options.respond(response);
55
+ };
56
+ const message = {
57
+ messageId: input.messageId,
58
+ text: input.text,
59
+ accept: () => {
60
+ settle({
61
+ type: "user-message-response",
62
+ messageId: input.messageId,
63
+ accepted: true
64
+ });
65
+ },
66
+ reject: (error) => {
67
+ settle({
68
+ type: "user-message-response",
69
+ messageId: input.messageId,
70
+ accepted: false,
71
+ error: { message: formatBridgeError(error).message }
72
+ });
73
+ }
74
+ };
75
+ entries.set(input.messageId, { reject: message.reject });
76
+ pendingCount++;
77
+ if (closed) {
78
+ message.reject(/* @__PURE__ */ new Error("The bridge turn is no longer accepting user messages."));
79
+ return;
80
+ }
81
+ const waiter = waiters.shift();
82
+ if (waiter != null) waiter({
83
+ done: false,
84
+ value: message
85
+ });
86
+ else messages.push(message);
87
+ };
88
+ const close = (error) => {
89
+ if (closed) return;
90
+ closed = true;
91
+ const reason = error ?? /* @__PURE__ */ new Error("The bridge turn ended before accepting the user message.");
92
+ for (const entry of entries.values()) if (entry.response == null) entry.reject(reason);
93
+ messages.length = 0;
94
+ while (waiters.length > 0) waiters.shift()({
95
+ done: true,
96
+ value: void 0
97
+ });
98
+ };
99
+ return {
100
+ get pendingCount() {
101
+ return pendingCount;
102
+ },
103
+ enqueue,
104
+ close,
105
+ [Symbol.asyncIterator]() {
106
+ return { next: () => {
107
+ const message = messages.shift();
108
+ if (message != null) return Promise.resolve({
109
+ done: false,
110
+ value: message
111
+ });
112
+ if (closed) return Promise.resolve({
113
+ done: true,
114
+ value: void 0
115
+ });
116
+ return new Promise((resolve) => {
117
+ waiters.push(resolve);
118
+ });
119
+ } };
120
+ }
121
+ };
135
122
  }
136
123
  function parseEnvList(value) {
137
- if (!value) return void 0;
138
- const items = value.split(",").map((item) => item.trim()).filter(Boolean);
139
- return items.length > 0 ? items : void 0;
124
+ if (!value) return void 0;
125
+ const items = value.split(",").map((item) => item.trim()).filter(Boolean);
126
+ return items.length > 0 ? items : void 0;
140
127
  }
141
- var ENV_TRUTHY = /* @__PURE__ */ new Set(["1", "true", "yes", "on"]);
142
- var WS_OPEN = 1;
128
+ const ENV_TRUTHY = /* @__PURE__ */ new Set([
129
+ "1",
130
+ "true",
131
+ "yes",
132
+ "on"
133
+ ]);
134
+ const WS_OPEN = 1;
143
135
  async function runBridge(options) {
144
- const { bridgeType, bridgeStateDir: bridgeStateDir2, onStart, onStop, onDestroy } = options;
145
- const teardownGraceMs = options.turnTeardownGraceMs ?? 1e4;
146
- const expectedToken = options.token ?? procEnv.BRIDGE_CHANNEL_TOKEN ?? "";
147
- const bridgeWsPort = options.port ?? parseInt(procEnv.BRIDGE_WS_PORT ?? "0", 10);
148
- const bridgeMetaPath = `${bridgeStateDir2}/bridge-meta.json`;
149
- const startConfigPath = `${bridgeStateDir2}/start-config.json`;
150
- const rerunStartConfigPath = `${bridgeStateDir2}/rerun-start-config.json`;
151
- const eventLogPath = `${bridgeStateDir2}/event-log.ndjson`;
152
- try {
153
- await mkdir(bridgeStateDir2, { recursive: true });
154
- } catch {
155
- }
156
- let currentBoundPort = 0;
157
- let currentTurnState = "init";
158
- let activeSocket;
159
- let isFirstTurn = true;
160
- let turnAbort;
161
- let currentUserMessages;
162
- let activeTurn;
163
- let debugConfig;
164
- let consoleCaptureInstalled = false;
165
- const envDebugEnabled = ENV_TRUTHY.has(
166
- (procEnv.HARNESS_DEBUG ?? "").toLowerCase()
167
- );
168
- let seqCounter = 0;
169
- let eventLog = [];
170
- let diskBuffer = "";
171
- let flushPromise = null;
172
- const flushEventsToDisk = async () => {
173
- while (diskBuffer.length > 0) {
174
- const buf = diskBuffer;
175
- diskBuffer = "";
176
- await appendFile(eventLogPath, buf).catch(() => {
177
- });
178
- }
179
- };
180
- const scheduleEventFlush = () => {
181
- if (flushPromise) return;
182
- flushPromise = new Promise((resolve) => {
183
- setImmediate(() => {
184
- void flushEventsToDisk().finally(resolve);
185
- });
186
- }).finally(() => {
187
- flushPromise = null;
188
- if (diskBuffer.length > 0) {
189
- scheduleEventFlush();
190
- }
191
- });
192
- };
193
- const flushPendingEventsToDisk = async () => {
194
- if (diskBuffer.length > 0 && !flushPromise) {
195
- scheduleEventFlush();
196
- }
197
- let inFlight = flushPromise;
198
- while (inFlight) {
199
- await inFlight;
200
- inFlight = flushPromise;
201
- }
202
- };
203
- const replayFromDisk = procEnv.BRIDGE_REPLAY_FROM_DISK === "1";
204
- if (replayFromDisk && existsSync(eventLogPath)) {
205
- try {
206
- const lines = readFileSync(eventLogPath, "utf8").split("\n").map((line) => line.trim()).filter(Boolean);
207
- eventLog = lines.map((line) => ({
208
- seq: JSON.parse(line).seq,
209
- line
210
- }));
211
- seqCounter = eventLog.at(-1)?.seq ?? 0;
212
- } catch {
213
- eventLog = [];
214
- seqCounter = 0;
215
- }
216
- }
217
- const pendingToolResults = /* @__PURE__ */ new Map();
218
- const bufferedToolResults = [];
219
- const pendingToolApprovals = /* @__PURE__ */ new Map();
220
- const writeBridgeMeta = async (state) => {
221
- try {
222
- await writeFile(
223
- bridgeMetaPath,
224
- JSON.stringify({
225
- type: bridgeType,
226
- port: currentBoundPort,
227
- state,
228
- pid
229
- })
230
- );
231
- } catch {
232
- }
233
- };
234
- const writeStartConfig = async (start) => {
235
- try {
236
- const serialized = JSON.stringify(start);
237
- await writeFile(startConfigPath, serialized);
238
- if (!existsSync(rerunStartConfigPath)) {
239
- await writeFile(rerunStartConfigPath, serialized);
240
- }
241
- } catch {
242
- }
243
- };
244
- const emit = (event) => {
245
- const seq = ++seqCounter;
246
- const line = JSON.stringify({ ...event, seq });
247
- eventLog.push({ seq, line });
248
- diskBuffer += `${line}
249
- `;
250
- scheduleEventFlush();
251
- if (activeSocket?.readyState === WS_OPEN) {
252
- try {
253
- activeSocket.send(line);
254
- } catch {
255
- }
256
- }
257
- };
258
- const replay = (ws, afterSeq) => {
259
- for (const entry of eventLog) {
260
- if (entry.seq > afterSeq && ws.readyState === WS_OPEN) {
261
- ws.send(entry.line);
262
- }
263
- }
264
- };
265
- const shouldEmitDebugEvent = (level, subsystem) => {
266
- if (!debugConfig?.enabled) return false;
267
- const threshold = debugConfig.level ?? "debug";
268
- if (DEBUG_LEVEL_WEIGHT[level] > DEBUG_LEVEL_WEIGHT[threshold]) return false;
269
- return subsystemMatches(debugConfig.subsystems, subsystem);
270
- };
271
- const rawStdoutWrite = process.stdout.write.bind(process.stdout);
272
- const rawStderrWrite = process.stderr.write.bind(process.stderr);
273
- const writeErrorToStderr = (input) => {
274
- try {
275
- const formatted = formatBridgeError(input.error);
276
- rawStderrWrite(
277
- `[harness:${bridgeType}:error] ${input.message}: ${formatted.message}
278
- `
279
- );
280
- if (formatted.stack) {
281
- rawStderrWrite(`${formatted.stack}
282
- `);
283
- }
284
- } catch {
285
- }
286
- };
287
- const emitWarning = (input) => {
288
- try {
289
- for (const line of input.message.split("\n")) {
290
- if (line.trim().length > 0) {
291
- rawStderrWrite(`[harness:${bridgeType}:warn] ${line}
292
- `);
293
- }
294
- }
295
- } catch {
296
- }
297
- };
298
- const emitError = (input) => {
299
- writeErrorToStderr({
300
- message: input.message ?? "bridge error",
301
- error: input.error
302
- });
303
- emit({ type: "error", error: serialiseError(input.error) });
304
- };
305
- const installConsoleCapture = () => {
306
- if (consoleCaptureInstalled) return;
307
- consoleCaptureInstalled = true;
308
- const buffers = {
309
- stdout: "",
310
- stderr: ""
311
- };
312
- const patch = (stream, raw) => (chunk, encoding, cb) => {
313
- if (debugConfig?.enabled) {
314
- try {
315
- const enc = typeof encoding === "string" ? encoding : "utf8";
316
- const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString(
317
- enc
318
- );
319
- const combined = buffers[stream] + text.replace(/\r\n/g, "\n");
320
- const parts = combined.split("\n");
321
- buffers[stream] = parts.pop() ?? "";
322
- for (const line of parts) {
323
- const trimmed = line.replace(/\s+$/, "");
324
- if (trimmed) {
325
- emit({
326
- type: "sandbox-log",
327
- source: bridgeType,
328
- stream,
329
- line: trimmed
330
- });
331
- }
332
- }
333
- } catch {
334
- }
335
- }
336
- return raw(
337
- chunk,
338
- encoding,
339
- cb
340
- );
341
- };
342
- process.stdout.write = patch(
343
- "stdout",
344
- rawStdoutWrite
345
- );
346
- process.stderr.write = patch(
347
- "stderr",
348
- rawStderrWrite
349
- );
350
- };
351
- const handleInbound = async (msg, ws) => {
352
- switch (msg.type) {
353
- case "start": {
354
- for (; ; ) {
355
- const pendingTurn = activeTurn;
356
- if (pendingTurn == null) break;
357
- turnAbort?.abort();
358
- currentUserMessages?.close(
359
- new Error("A new bridge turn replaced the active turn.")
360
- );
361
- let graceTimer;
362
- const settled = await Promise.race([
363
- pendingTurn.then(() => true),
364
- new Promise((resolve) => {
365
- graceTimer = setTimeout(() => resolve(false), teardownGraceMs);
366
- graceTimer.unref?.();
367
- })
368
- ]);
369
- clearTimeout(graceTimer);
370
- if (!settled) break;
371
- }
372
- let turnFinished;
373
- const thisTurn = new Promise((resolve) => turnFinished = resolve);
374
- activeTurn = thisTurn;
375
- activeSocket = ws;
376
- const firstTurn = isFirstTurn;
377
- isFirstTurn = false;
378
- eventLog = [];
379
- diskBuffer = "";
380
- void writeFile(eventLogPath, "").catch(() => {
381
- });
382
- turnAbort = new AbortController();
383
- currentTurnState = "running";
384
- void writeStartConfig(msg);
385
- void writeBridgeMeta("running");
386
- const startDebug = msg.debug;
387
- debugConfig = {
388
- enabled: startDebug?.enabled ?? envDebugEnabled,
389
- level: startDebug?.level ?? procEnv.HARNESS_DEBUG_LEVEL,
390
- subsystems: startDebug?.subsystems ?? parseEnvList(procEnv.HARNESS_DEBUG_SUBSYSTEMS)
391
- };
392
- if (debugConfig.enabled) {
393
- installConsoleCapture();
394
- }
395
- const userMessages = createBridgeUserMessageQueue({ respond: emit });
396
- const turn = {
397
- emit,
398
- requestToolResult: (requestInput) => {
399
- const request = typeof requestInput === "string" ? { toolCallId: requestInput } : requestInput;
400
- const bufferedIndex = bufferedToolResults.findIndex(
401
- (buffered) => buffered.toolCallId === request.toolCallId || request.matches?.(buffered.result) === true
402
- );
403
- if (bufferedIndex >= 0) {
404
- return Promise.resolve(
405
- bufferedToolResults.splice(bufferedIndex, 1)[0].result
406
- );
407
- }
408
- return new Promise((resolve) => {
409
- pendingToolResults.set(request.toolCallId, {
410
- resolve,
411
- matches: request.matches
412
- });
413
- });
414
- },
415
- requestToolApproval: (approvalId) => new Promise((resolve) => {
416
- pendingToolApprovals.set(approvalId, resolve);
417
- }),
418
- experimental_userMessages: userMessages,
419
- abortSignal: turnAbort.signal,
420
- firstTurn,
421
- bridgeLog: (input) => {
422
- const level = input.level ?? "debug";
423
- if (!shouldEmitDebugEvent(level, input.subsystem)) return;
424
- emit({
425
- type: "debug-event",
426
- level,
427
- subsystem: input.subsystem,
428
- message: input.message,
429
- ...input.attrs ? { attrs: input.attrs } : {},
430
- ...input.error !== void 0 ? { error: formatBridgeError(input.error) } : {}
431
- });
432
- },
433
- emitWarning,
434
- emitError
435
- };
436
- currentUserMessages = userMessages;
437
- try {
438
- await onStart(msg, turn);
439
- } catch (err) {
440
- emitError({ error: err, message: "bridge turn failed" });
441
- } finally {
442
- userMessages.close();
443
- if (currentUserMessages === userMessages) {
444
- currentUserMessages = void 0;
445
- }
446
- if (activeTurn === thisTurn) {
447
- activeTurn = void 0;
448
- currentTurnState = "waiting";
449
- void writeBridgeMeta("waiting");
450
- }
451
- turnFinished();
452
- }
453
- return;
454
- }
455
- case "tool-result": {
456
- const result = {
457
- output: msg.output,
458
- isError: msg.isError,
459
- toolResult: msg.toolResult
460
- };
461
- const exactPending = pendingToolResults.get(msg.toolCallId);
462
- const matchingPending = exactPending == null ? Array.from(pendingToolResults.entries()).find(
463
- ([, pending2]) => pending2.matches?.(result) === true
464
- ) : void 0;
465
- const pending = exactPending ?? matchingPending?.[1];
466
- const pendingId = exactPending != null ? msg.toolCallId : matchingPending?.[0];
467
- if (pending != null && pendingId != null) {
468
- pendingToolResults.delete(pendingId);
469
- pending.resolve(result);
470
- } else {
471
- bufferedToolResults.push({
472
- toolCallId: msg.toolCallId,
473
- result
474
- });
475
- }
476
- return;
477
- }
478
- case "tool-approval-response": {
479
- const resolver = pendingToolApprovals.get(msg.approvalId);
480
- if (resolver) {
481
- pendingToolApprovals.delete(msg.approvalId);
482
- resolver({ approved: msg.approved, reason: msg.reason });
483
- }
484
- return;
485
- }
486
- case "user-message": {
487
- const messageId = msg.messageId ?? randomUUID();
488
- if (currentUserMessages == null) {
489
- sendControl(ws, {
490
- type: "user-message-response",
491
- messageId,
492
- accepted: false,
493
- error: { message: "The bridge has no active turn to steer." }
494
- });
495
- return;
496
- }
497
- if (ws !== activeSocket) {
498
- sendControl(ws, {
499
- type: "user-message-response",
500
- messageId,
501
- accepted: false,
502
- error: {
503
- message: "The connection does not own the active bridge turn."
504
- }
505
- });
506
- return;
507
- }
508
- currentUserMessages.enqueue({
509
- messageId,
510
- text: msg.text
511
- });
512
- return;
513
- }
514
- case "abort":
515
- turnAbort?.abort();
516
- return;
517
- case "resume":
518
- activeSocket = ws;
519
- replay(ws, msg.lastSeenEventId);
520
- return;
521
- case "destroy":
522
- currentTurnState = "done";
523
- void writeBridgeMeta("done");
524
- await onDestroy?.();
525
- drainThenExit(ws, 1e3, "destroy");
526
- return;
527
- case "stop": {
528
- currentTurnState = "done";
529
- void writeBridgeMeta("done");
530
- const data = await onStop?.() ?? {};
531
- sendControl(ws, { type: "bridge-stop", data });
532
- drainThenExit(ws, 1e3, "stop");
533
- }
534
- }
535
- };
536
- void writeBridgeMeta("init");
537
- const wss = new WebSocketServer({ port: bridgeWsPort, host: "0.0.0.0" });
538
- const exit = () => {
539
- if (options.onExit) {
540
- options.onExit();
541
- return;
542
- }
543
- wss.close(() => process.exit(0));
544
- setTimeout(() => process.exit(0), 1e3).unref();
545
- };
546
- const drainThenExit = (ws, code, reason) => {
547
- const start = Date.now();
548
- const tick = () => {
549
- const drained = ws.bufferedAmount === 0 || ws.readyState !== WS_OPEN;
550
- if (drained || Date.now() - start >= 5e3) {
551
- void flushPendingEventsToDisk().finally(() => {
552
- try {
553
- ws.close(code, reason);
554
- } finally {
555
- exit();
556
- }
557
- });
558
- return;
559
- }
560
- setTimeout(tick, 10).unref();
561
- };
562
- tick();
563
- };
564
- wss.on("listening", () => {
565
- const addr = wss.address();
566
- currentBoundPort = typeof addr === "object" && addr ? addr.port : 0;
567
- currentTurnState = "waiting";
568
- void writeBridgeMeta("waiting");
569
- stdout.write(
570
- JSON.stringify({
571
- type: "bridge-ready",
572
- port: currentBoundPort
573
- }) + "\n"
574
- );
575
- options.onListening?.(currentBoundPort);
576
- });
577
- wss.on("connection", (ws, req) => {
578
- const url = new URL(req.url ?? "/", "http://localhost");
579
- if (url.searchParams.get("agent_bridge_token") !== expectedToken) {
580
- ws.close(1008, "unauthorized");
581
- return;
582
- }
583
- sendControl(ws, {
584
- type: "bridge-hello",
585
- state: currentTurnState,
586
- lastSeq: seqCounter,
587
- capabilities: { experimental_userMessageResponses: true }
588
- });
589
- ws.on("message", (raw) => {
590
- let parsed;
591
- try {
592
- const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
593
- parsed = JSON.parse(text);
594
- } catch (err) {
595
- sendControl(ws, {
596
- type: "error",
597
- error: `protocol parse error: ${err.message}`
598
- });
599
- return;
600
- }
601
- void handleInbound(parsed, ws);
602
- });
603
- ws.on("close", () => {
604
- if (activeSocket === ws) {
605
- activeSocket = void 0;
606
- }
607
- });
608
- ws.on("error", () => {
609
- });
610
- });
611
- process.on("uncaughtException", (err) => {
612
- emitError({ error: err, message: "uncaught exception" });
613
- });
614
- process.on("unhandledRejection", (err) => {
615
- emitError({ error: err, message: "unhandled rejection" });
616
- });
617
- await new Promise((resolve, reject) => {
618
- if (wss.address() != null) {
619
- resolve();
620
- return;
621
- }
622
- wss.once("listening", resolve);
623
- wss.once("error", reject);
624
- });
625
- return {
626
- port: currentBoundPort,
627
- close: () => new Promise((resolve) => {
628
- wss.close(() => resolve());
629
- })
630
- };
136
+ const { bridgeType, bridgeStateDir, onStart, onStop, onDestroy } = options;
137
+ const teardownGraceMs = options.turnTeardownGraceMs ?? 1e4;
138
+ const expectedToken = options.token ?? env.BRIDGE_CHANNEL_TOKEN ?? "";
139
+ const bridgeWsPort = options.port ?? parseInt(env.BRIDGE_WS_PORT ?? "0", 10);
140
+ const bridgeMetaPath = `${bridgeStateDir}/bridge-meta.json`;
141
+ const startConfigPath = `${bridgeStateDir}/start-config.json`;
142
+ const rerunStartConfigPath = `${bridgeStateDir}/rerun-start-config.json`;
143
+ const eventLogPath = `${bridgeStateDir}/event-log.ndjson`;
144
+ try {
145
+ await mkdir(bridgeStateDir, { recursive: true });
146
+ } catch {}
147
+ let currentBoundPort = 0;
148
+ let currentTurnState = "init";
149
+ let activeSocket;
150
+ let isFirstTurn = true;
151
+ let turnAbort;
152
+ let currentUserMessages;
153
+ /**
154
+ * Settles when the in-flight turn has fully wound down — `onStart`
155
+ * returned or threw AND its completion state was recorded. `undefined`
156
+ * between turns. A new `start` fences on this so turns never overlap.
157
+ */
158
+ let activeTurn;
159
+ let debugConfig;
160
+ let consoleCaptureInstalled = false;
161
+ const envDebugEnabled = ENV_TRUTHY.has((env.HARNESS_DEBUG ?? "").toLowerCase());
162
+ let seqCounter = 0;
163
+ let eventLog = [];
164
+ let diskBuffer = "";
165
+ let flushPromise = null;
166
+ const flushEventsToDisk = async () => {
167
+ while (diskBuffer.length > 0) {
168
+ const buf = diskBuffer;
169
+ diskBuffer = "";
170
+ await appendFile(eventLogPath, buf).catch(() => {});
171
+ }
172
+ };
173
+ const scheduleEventFlush = () => {
174
+ if (flushPromise) return;
175
+ flushPromise = new Promise((resolve) => {
176
+ setImmediate(() => {
177
+ flushEventsToDisk().finally(resolve);
178
+ });
179
+ }).finally(() => {
180
+ flushPromise = null;
181
+ if (diskBuffer.length > 0) scheduleEventFlush();
182
+ });
183
+ };
184
+ const flushPendingEventsToDisk = async () => {
185
+ if (diskBuffer.length > 0 && !flushPromise) scheduleEventFlush();
186
+ let inFlight = flushPromise;
187
+ while (inFlight) {
188
+ await inFlight;
189
+ inFlight = flushPromise;
190
+ }
191
+ };
192
+ if (env.BRIDGE_REPLAY_FROM_DISK === "1" && existsSync(eventLogPath)) try {
193
+ eventLog = readFileSync(eventLogPath, "utf8").split("\n").map((line) => line.trim()).filter(Boolean).map((line) => ({
194
+ seq: JSON.parse(line).seq,
195
+ line
196
+ }));
197
+ seqCounter = eventLog.at(-1)?.seq ?? 0;
198
+ } catch {
199
+ eventLog = [];
200
+ seqCounter = 0;
201
+ }
202
+ const pendingToolResults = /* @__PURE__ */ new Map();
203
+ const bufferedToolResults = [];
204
+ const pendingToolApprovals = /* @__PURE__ */ new Map();
205
+ const writeBridgeMeta = async (state) => {
206
+ try {
207
+ await writeFile(bridgeMetaPath, JSON.stringify({
208
+ type: bridgeType,
209
+ port: currentBoundPort,
210
+ state,
211
+ pid
212
+ }));
213
+ } catch {}
214
+ };
215
+ const writeStartConfig = async (start) => {
216
+ try {
217
+ const serialized = JSON.stringify(start);
218
+ await writeFile(startConfigPath, serialized);
219
+ if (!existsSync(rerunStartConfigPath)) await writeFile(rerunStartConfigPath, serialized);
220
+ } catch {}
221
+ };
222
+ const emit = (event) => {
223
+ const seq = ++seqCounter;
224
+ const line = JSON.stringify({
225
+ ...event,
226
+ seq
227
+ });
228
+ eventLog.push({
229
+ seq,
230
+ line
231
+ });
232
+ diskBuffer += `${line}\n`;
233
+ scheduleEventFlush();
234
+ if (activeSocket?.readyState === WS_OPEN) try {
235
+ activeSocket.send(line);
236
+ } catch {}
237
+ };
238
+ const replay = (ws, afterSeq) => {
239
+ for (const entry of eventLog) if (entry.seq > afterSeq && ws.readyState === WS_OPEN) ws.send(entry.line);
240
+ };
241
+ const shouldEmitDebugEvent = (level, subsystem) => {
242
+ if (!debugConfig?.enabled) return false;
243
+ const threshold = debugConfig.level ?? "debug";
244
+ if (DEBUG_LEVEL_WEIGHT[level] > DEBUG_LEVEL_WEIGHT[threshold]) return false;
245
+ return subsystemMatches(debugConfig.subsystems, subsystem);
246
+ };
247
+ const rawStdoutWrite = process.stdout.write.bind(process.stdout);
248
+ const rawStderrWrite = process.stderr.write.bind(process.stderr);
249
+ const writeErrorToStderr = (input) => {
250
+ try {
251
+ const formatted = formatBridgeError(input.error);
252
+ rawStderrWrite(`[harness:${bridgeType}:error] ${input.message}: ${formatted.message}\n`);
253
+ if (formatted.stack) rawStderrWrite(`${formatted.stack}\n`);
254
+ } catch {}
255
+ };
256
+ const emitWarning = (input) => {
257
+ try {
258
+ for (const line of input.message.split("\n")) if (line.trim().length > 0) rawStderrWrite(`[harness:${bridgeType}:warn] ${line}\n`);
259
+ } catch {}
260
+ };
261
+ const emitError = (input) => {
262
+ writeErrorToStderr({
263
+ message: input.message ?? "bridge error",
264
+ error: input.error
265
+ });
266
+ emit({
267
+ type: "error",
268
+ error: serialiseError(input.error)
269
+ });
270
+ };
271
+ const installConsoleCapture = () => {
272
+ if (consoleCaptureInstalled) return;
273
+ consoleCaptureInstalled = true;
274
+ const buffers = {
275
+ stdout: "",
276
+ stderr: ""
277
+ };
278
+ const patch = (stream, raw) => (chunk, encoding, cb) => {
279
+ if (debugConfig?.enabled) try {
280
+ const enc = typeof encoding === "string" ? encoding : "utf8";
281
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString(enc);
282
+ const parts = (buffers[stream] + text.replace(/\r\n/g, "\n")).split("\n");
283
+ buffers[stream] = parts.pop() ?? "";
284
+ for (const line of parts) {
285
+ const trimmed = line.replace(/\s+$/, "");
286
+ if (trimmed) emit({
287
+ type: "sandbox-log",
288
+ source: bridgeType,
289
+ stream,
290
+ line: trimmed
291
+ });
292
+ }
293
+ } catch {}
294
+ return raw(chunk, encoding, cb);
295
+ };
296
+ process.stdout.write = patch("stdout", rawStdoutWrite);
297
+ process.stderr.write = patch("stderr", rawStderrWrite);
298
+ };
299
+ const handleInbound = async (msg, ws) => {
300
+ switch (msg.type) {
301
+ case "start": {
302
+ for (;;) {
303
+ const pendingTurn = activeTurn;
304
+ if (pendingTurn == null) break;
305
+ turnAbort?.abort();
306
+ currentUserMessages?.close(/* @__PURE__ */ new Error("A new bridge turn replaced the active turn."));
307
+ let graceTimer;
308
+ const settled = await Promise.race([pendingTurn.then(() => true), new Promise((resolve) => {
309
+ graceTimer = setTimeout(() => resolve(false), teardownGraceMs);
310
+ graceTimer.unref?.();
311
+ })]);
312
+ clearTimeout(graceTimer);
313
+ if (!settled) break;
314
+ }
315
+ let turnFinished;
316
+ const thisTurn = new Promise((resolve) => turnFinished = resolve);
317
+ activeTurn = thisTurn;
318
+ activeSocket = ws;
319
+ const firstTurn = isFirstTurn;
320
+ isFirstTurn = false;
321
+ eventLog = [];
322
+ diskBuffer = "";
323
+ writeFile(eventLogPath, "").catch(() => {});
324
+ turnAbort = new AbortController();
325
+ currentTurnState = "running";
326
+ writeStartConfig(msg);
327
+ writeBridgeMeta("running");
328
+ const startDebug = msg.debug;
329
+ debugConfig = {
330
+ enabled: startDebug?.enabled ?? envDebugEnabled,
331
+ level: startDebug?.level ?? env.HARNESS_DEBUG_LEVEL,
332
+ subsystems: startDebug?.subsystems ?? parseEnvList(env.HARNESS_DEBUG_SUBSYSTEMS)
333
+ };
334
+ if (debugConfig.enabled) installConsoleCapture();
335
+ const userMessages = createBridgeUserMessageQueue({ respond: emit });
336
+ const turn = {
337
+ emit,
338
+ requestToolResult: (requestInput) => {
339
+ const request = typeof requestInput === "string" ? { toolCallId: requestInput } : requestInput;
340
+ const bufferedIndex = bufferedToolResults.findIndex((buffered) => buffered.toolCallId === request.toolCallId || request.matches?.(buffered.result) === true);
341
+ if (bufferedIndex >= 0) return Promise.resolve(bufferedToolResults.splice(bufferedIndex, 1)[0].result);
342
+ return new Promise((resolve) => {
343
+ pendingToolResults.set(request.toolCallId, {
344
+ resolve,
345
+ matches: request.matches
346
+ });
347
+ });
348
+ },
349
+ requestToolApproval: (approvalId) => new Promise((resolve) => {
350
+ pendingToolApprovals.set(approvalId, resolve);
351
+ }),
352
+ experimental_userMessages: userMessages,
353
+ abortSignal: turnAbort.signal,
354
+ firstTurn,
355
+ bridgeLog: (input) => {
356
+ const level = input.level ?? "debug";
357
+ if (!shouldEmitDebugEvent(level, input.subsystem)) return;
358
+ emit({
359
+ type: "debug-event",
360
+ level,
361
+ subsystem: input.subsystem,
362
+ message: input.message,
363
+ ...input.attrs ? { attrs: input.attrs } : {},
364
+ ...input.error !== void 0 ? { error: formatBridgeError(input.error) } : {}
365
+ });
366
+ },
367
+ emitWarning,
368
+ emitError
369
+ };
370
+ currentUserMessages = userMessages;
371
+ try {
372
+ await onStart(msg, turn);
373
+ } catch (err) {
374
+ emitError({
375
+ error: err,
376
+ message: "bridge turn failed"
377
+ });
378
+ } finally {
379
+ userMessages.close();
380
+ if (currentUserMessages === userMessages) currentUserMessages = void 0;
381
+ if (activeTurn === thisTurn) {
382
+ activeTurn = void 0;
383
+ currentTurnState = "waiting";
384
+ writeBridgeMeta("waiting");
385
+ }
386
+ turnFinished();
387
+ }
388
+ return;
389
+ }
390
+ case "tool-result": {
391
+ const result = {
392
+ output: msg.output,
393
+ isError: msg.isError,
394
+ toolResult: msg.toolResult
395
+ };
396
+ const exactPending = pendingToolResults.get(msg.toolCallId);
397
+ const matchingPending = exactPending == null ? Array.from(pendingToolResults.entries()).find(([, pending]) => pending.matches?.(result) === true) : void 0;
398
+ const pending = exactPending ?? matchingPending?.[1];
399
+ const pendingId = exactPending != null ? msg.toolCallId : matchingPending?.[0];
400
+ if (pending != null && pendingId != null) {
401
+ pendingToolResults.delete(pendingId);
402
+ pending.resolve(result);
403
+ } else bufferedToolResults.push({
404
+ toolCallId: msg.toolCallId,
405
+ result
406
+ });
407
+ return;
408
+ }
409
+ case "tool-approval-response": {
410
+ const resolver = pendingToolApprovals.get(msg.approvalId);
411
+ if (resolver) {
412
+ pendingToolApprovals.delete(msg.approvalId);
413
+ resolver({
414
+ approved: msg.approved,
415
+ reason: msg.reason
416
+ });
417
+ }
418
+ return;
419
+ }
420
+ case "user-message": {
421
+ const messageId = msg.messageId ?? randomUUID();
422
+ if (currentUserMessages == null) {
423
+ sendControl(ws, {
424
+ type: "user-message-response",
425
+ messageId,
426
+ accepted: false,
427
+ error: { message: "The bridge has no active turn to steer." }
428
+ });
429
+ return;
430
+ }
431
+ if (ws !== activeSocket) {
432
+ sendControl(ws, {
433
+ type: "user-message-response",
434
+ messageId,
435
+ accepted: false,
436
+ error: { message: "The connection does not own the active bridge turn." }
437
+ });
438
+ return;
439
+ }
440
+ currentUserMessages.enqueue({
441
+ messageId,
442
+ text: msg.text
443
+ });
444
+ return;
445
+ }
446
+ case "abort":
447
+ turnAbort?.abort();
448
+ return;
449
+ case "resume":
450
+ activeSocket = ws;
451
+ replay(ws, msg.lastSeenEventId);
452
+ return;
453
+ case "destroy":
454
+ currentTurnState = "done";
455
+ writeBridgeMeta("done");
456
+ await onDestroy?.();
457
+ drainThenExit(ws, 1e3, "destroy");
458
+ return;
459
+ case "stop":
460
+ currentTurnState = "done";
461
+ writeBridgeMeta("done");
462
+ sendControl(ws, {
463
+ type: "bridge-stop",
464
+ data: await onStop?.() ?? {}
465
+ });
466
+ drainThenExit(ws, 1e3, "stop");
467
+ }
468
+ };
469
+ writeBridgeMeta("init");
470
+ const wss = new WebSocketServer({
471
+ port: bridgeWsPort,
472
+ host: "0.0.0.0"
473
+ });
474
+ const exit = () => {
475
+ if (options.onExit) {
476
+ options.onExit();
477
+ return;
478
+ }
479
+ wss.close(() => process.exit(0));
480
+ setTimeout(() => process.exit(0), 1e3).unref();
481
+ };
482
+ const drainThenExit = (ws, code, reason) => {
483
+ const start = Date.now();
484
+ const tick = () => {
485
+ if (ws.bufferedAmount === 0 || ws.readyState !== WS_OPEN || Date.now() - start >= 5e3) {
486
+ flushPendingEventsToDisk().finally(() => {
487
+ try {
488
+ ws.close(code, reason);
489
+ } finally {
490
+ exit();
491
+ }
492
+ });
493
+ return;
494
+ }
495
+ setTimeout(tick, 10).unref();
496
+ };
497
+ tick();
498
+ };
499
+ wss.on("listening", () => {
500
+ const addr = wss.address();
501
+ currentBoundPort = typeof addr === "object" && addr ? addr.port : 0;
502
+ currentTurnState = "waiting";
503
+ writeBridgeMeta("waiting");
504
+ stdout.write(JSON.stringify({
505
+ type: "bridge-ready",
506
+ port: currentBoundPort
507
+ }) + "\n");
508
+ options.onListening?.(currentBoundPort);
509
+ });
510
+ wss.on("connection", (ws, req) => {
511
+ if (new URL(req.url ?? "/", "http://localhost").searchParams.get("agent_bridge_token") !== expectedToken) {
512
+ ws.close(1008, "unauthorized");
513
+ return;
514
+ }
515
+ sendControl(ws, {
516
+ type: "bridge-hello",
517
+ state: currentTurnState,
518
+ lastSeq: seqCounter,
519
+ capabilities: { experimental_userMessageResponses: true }
520
+ });
521
+ ws.on("message", (raw) => {
522
+ let parsed;
523
+ try {
524
+ const text = typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
525
+ parsed = JSON.parse(text);
526
+ } catch (err) {
527
+ sendControl(ws, {
528
+ type: "error",
529
+ error: `protocol parse error: ${err.message}`
530
+ });
531
+ return;
532
+ }
533
+ handleInbound(parsed, ws);
534
+ });
535
+ ws.on("close", () => {
536
+ if (activeSocket === ws) activeSocket = void 0;
537
+ });
538
+ ws.on("error", () => {});
539
+ });
540
+ process.on("uncaughtException", (err) => {
541
+ emitError({
542
+ error: err,
543
+ message: "uncaught exception"
544
+ });
545
+ });
546
+ process.on("unhandledRejection", (err) => {
547
+ emitError({
548
+ error: err,
549
+ message: "unhandled rejection"
550
+ });
551
+ });
552
+ await new Promise((resolve, reject) => {
553
+ if (wss.address() != null) {
554
+ resolve();
555
+ return;
556
+ }
557
+ wss.once("listening", resolve);
558
+ wss.once("error", reject);
559
+ });
560
+ return {
561
+ port: currentBoundPort,
562
+ close: () => new Promise((resolve) => {
563
+ wss.close(() => resolve());
564
+ })
565
+ };
631
566
  }
632
567
  function sendControl(socket, message) {
633
- if (socket?.readyState === WS_OPEN) {
634
- try {
635
- socket.send(JSON.stringify(message));
636
- } catch {
637
- }
638
- }
568
+ if (socket?.readyState === WS_OPEN) try {
569
+ socket.send(JSON.stringify(message));
570
+ } catch {}
639
571
  }
640
572
  function serialiseError(err) {
641
- if (err instanceof Error) {
642
- return { name: err.name, message: err.message, stack: err.stack };
643
- }
644
- return err;
573
+ if (err instanceof Error) return {
574
+ name: err.name,
575
+ message: err.message,
576
+ stack: err.stack
577
+ };
578
+ return err;
645
579
  }
646
-
647
- // src/bridge/compaction-latch.ts
580
+ //#endregion
581
+ //#region src/bridge/compaction-latch.ts
648
582
  function createCompactionLatch(emit) {
649
- let boundary;
650
- let summary;
651
- const tryEmit = () => {
652
- if (!boundary || summary === void 0) return;
653
- emit({
654
- type: "compaction",
655
- trigger: boundary.trigger,
656
- summary,
657
- ...boundary.tokensBefore !== void 0 ? { tokensBefore: boundary.tokensBefore } : {},
658
- ...boundary.tokensAfter !== void 0 ? { tokensAfter: boundary.tokensAfter } : {}
659
- });
660
- boundary = void 0;
661
- summary = void 0;
662
- };
663
- return {
664
- onBoundary(next) {
665
- boundary = next;
666
- tryEmit();
667
- },
668
- onSummary(next) {
669
- summary = next;
670
- tryEmit();
671
- }
672
- };
583
+ let boundary;
584
+ let summary;
585
+ const tryEmit = () => {
586
+ if (!boundary || summary === void 0) return;
587
+ emit({
588
+ type: "compaction",
589
+ trigger: boundary.trigger,
590
+ summary,
591
+ ...boundary.tokensBefore !== void 0 ? { tokensBefore: boundary.tokensBefore } : {},
592
+ ...boundary.tokensAfter !== void 0 ? { tokensAfter: boundary.tokensAfter } : {}
593
+ });
594
+ boundary = void 0;
595
+ summary = void 0;
596
+ };
597
+ return {
598
+ onBoundary(next) {
599
+ boundary = next;
600
+ tryEmit();
601
+ },
602
+ onSummary(next) {
603
+ summary = next;
604
+ tryEmit();
605
+ }
606
+ };
673
607
  }
674
-
675
- // src/bridge/index.ts
676
- import { randomUUID as randomUUID3 } from "crypto";
677
- import { argv, env as procEnv2, stdout as stdout2 } from "process";
678
- import * as claudeAgentSdk from "@anthropic-ai/claude-agent-sdk";
679
- import * as mcpServerModule from "@modelcontextprotocol/sdk/server/mcp.js";
680
-
681
- // src/bridge/claude-code-system-prompt.ts
608
+ //#endregion
609
+ //#region src/bridge/claude-code-system-prompt.ts
682
610
  function createClaudeCodeSystemPrompt(instructions) {
683
- return {
684
- type: "preset",
685
- preset: "claude_code",
686
- ...instructions ? { append: instructions } : {}
687
- };
611
+ return {
612
+ type: "preset",
613
+ preset: "claude_code",
614
+ ...instructions ? { append: instructions } : {}
615
+ };
688
616
  }
689
-
690
- // src/bridge/claude-skills-option.ts
617
+ //#endregion
618
+ //#region src/bridge/claude-skills-option.ts
691
619
  function toClaudeSkillsOption(skills) {
692
- return skills && skills.length > 0 ? "all" : void 0;
620
+ return skills && skills.length > 0 ? "all" : void 0;
693
621
  }
694
-
695
- // src/bridge/create-emit-stream-event.ts
696
- import { randomUUID as randomUUID2 } from "crypto";
622
+ //#endregion
623
+ //#region src/bridge/create-emit-stream-event.ts
697
624
  function createClaudeStreamEventState() {
698
- return {
699
- nativeToolCallNames: /* @__PURE__ */ new Map(),
700
- approvalRequestedToolUseIds: /* @__PURE__ */ new Set(),
701
- partialBlocks: /* @__PURE__ */ new Map(),
702
- stepUsage: void 0,
703
- pendingStepToolUseIds: /* @__PURE__ */ new Set(),
704
- pendingStepAssistantUsage: void 0,
705
- pendingStepDeltaUsage: void 0,
706
- pendingStepUsage: void 0,
707
- stepOpen: false,
708
- mcpToolUseIds: /* @__PURE__ */ new Set(),
709
- externalMcpToolUseIds: /* @__PURE__ */ new Set(),
710
- structuredOutputToolUseIds: /* @__PURE__ */ new Set(),
711
- observedTerminalError: void 0
712
- };
625
+ return {
626
+ nativeToolCallNames: /* @__PURE__ */ new Map(),
627
+ approvalRequestedToolUseIds: /* @__PURE__ */ new Set(),
628
+ partialBlocks: /* @__PURE__ */ new Map(),
629
+ stepUsage: void 0,
630
+ pendingStepToolUseIds: /* @__PURE__ */ new Set(),
631
+ pendingStepAssistantUsage: void 0,
632
+ pendingStepDeltaUsage: void 0,
633
+ pendingStepUsage: void 0,
634
+ pendingResponseUsage: void 0,
635
+ stepOpen: false,
636
+ mcpToolUseIds: /* @__PURE__ */ new Set(),
637
+ externalMcpToolUseIds: /* @__PURE__ */ new Set(),
638
+ structuredOutputToolUseIds: /* @__PURE__ */ new Set(),
639
+ observedTerminalError: void 0
640
+ };
713
641
  }
714
- var UNRECOVERABLE_API_RETRY_STATUSES = /* @__PURE__ */ new Set([401, 403, 404]);
715
- var HOST_TOOL_PREFIX = "mcp__harness-tools__";
642
+ const UNRECOVERABLE_API_RETRY_STATUSES = /* @__PURE__ */ new Set([
643
+ 401,
644
+ 403,
645
+ 404
646
+ ]);
647
+ const HOST_TOOL_PREFIX = "mcp__harness-tools__";
648
+ const RAW_TASK_MESSAGE_SUBTYPES = /* @__PURE__ */ new Set([
649
+ "background_tasks_changed",
650
+ "task_started",
651
+ "task_progress",
652
+ "task_updated",
653
+ "task_notification"
654
+ ]);
716
655
  function isExternalMcpTool(nativeName) {
717
- return nativeName.startsWith("mcp__") && !nativeName.startsWith(HOST_TOOL_PREFIX);
656
+ return nativeName.startsWith("mcp__") && !nativeName.startsWith(HOST_TOOL_PREFIX);
718
657
  }
719
- function createEmitStreamEvent({
720
- state,
721
- emit,
722
- emitWarning,
723
- emitTerminalError,
724
- onCompactionBoundary,
725
- toCommonName: toCommonName2
726
- }) {
727
- let streamStarted = false;
728
- return (msg) => {
729
- const type = msg.type;
730
- if (!streamStarted && type !== "command_lifecycle") {
731
- const initModel = type === "system" && msg.subtype === "init" && typeof msg.model === "string" ? msg.model : void 0;
732
- emit({
733
- type: "stream-start",
734
- ...initModel ? { modelId: initModel } : {}
735
- });
736
- streamStarted = true;
737
- }
738
- if (type === "system" && msg.subtype === "api_retry") {
739
- if (typeof msg.error_status === "number" && UNRECOVERABLE_API_RETRY_STATUSES.has(msg.error_status)) {
740
- emitTerminalError(
741
- `HTTP ${msg.error_status}: ${msg.error ?? "provider request failed"}`
742
- );
743
- return;
744
- }
745
- emitWarning({ message: formatApiRetryWarning(msg) });
746
- return;
747
- }
748
- if (typeof msg.error === "string" && msg.error.trim()) {
749
- state.observedTerminalError = msg.error.trim();
750
- }
751
- if (type === "auth_status" && typeof msg.error === "string" && msg.error.trim()) {
752
- emitTerminalError(msg.error);
753
- return;
754
- }
755
- if (type === "system" && msg.subtype === "task_updated" && msg.patch?.status === "failed" && typeof msg.patch.error === "string") {
756
- emitTerminalError(msg.patch.error);
757
- return;
758
- }
759
- if (type === "system" && msg.subtype === "compact_boundary") {
760
- const meta = msg.compact_metadata;
761
- if (meta) {
762
- onCompactionBoundary({
763
- trigger: meta.trigger,
764
- ...typeof meta.pre_tokens === "number" ? { tokensBefore: meta.pre_tokens } : {},
765
- ...typeof meta.post_tokens === "number" ? { tokensAfter: meta.post_tokens } : {}
766
- });
767
- }
768
- return;
769
- }
770
- if (msg.parent_tool_use_id != null) {
771
- return;
772
- }
773
- if (type === "stream_event") {
774
- handleStreamEvent({
775
- event: msg.event,
776
- state,
777
- send: emit,
778
- toCommonName: toCommonName2
779
- });
780
- return;
781
- }
782
- const messageContent = msg.message?.content;
783
- if (type === "assistant" && Array.isArray(messageContent)) {
784
- const usage = toUsageRecord(msg.message?.usage);
785
- const toolUseIds = [];
786
- let opensStep = false;
787
- for (const block of messageContent) {
788
- if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
789
- toolUseIds.push(block.id);
790
- if (block.name === "StructuredOutput") {
791
- state.structuredOutputToolUseIds.add(block.id);
792
- continue;
793
- }
794
- if (block.name.startsWith(HOST_TOOL_PREFIX)) {
795
- state.pendingStepToolUseIds.add(block.id);
796
- state.mcpToolUseIds.add(block.id);
797
- opensStep = true;
798
- continue;
799
- }
800
- state.nativeToolCallNames.set(block.id, block.name);
801
- if (block.name === "AskUserQuestion") {
802
- state.pendingStepToolUseIds.add(block.id);
803
- opensStep = true;
804
- continue;
805
- }
806
- const dynamic = isExternalMcpTool(block.name);
807
- if (dynamic) state.externalMcpToolUseIds.add(block.id);
808
- if (state.approvalRequestedToolUseIds.has(block.id)) {
809
- continue;
810
- }
811
- state.pendingStepToolUseIds.add(block.id);
812
- opensStep = true;
813
- emit({
814
- type: "tool-call",
815
- toolCallId: block.id,
816
- toolName: toCommonName2(block.name),
817
- nativeName: block.name,
818
- input: JSON.stringify(block.input ?? {}),
819
- providerExecuted: true,
820
- ...dynamic ? { dynamic: true } : {}
821
- });
822
- }
823
- }
824
- if (opensStep || toolUseIds.length === 0) {
825
- state.stepOpen = true;
826
- if (usage) {
827
- state.pendingStepAssistantUsage = usage;
828
- updatePendingStepUsage(state);
829
- }
830
- }
831
- return;
832
- }
833
- if (type === "user" && Array.isArray(messageContent)) {
834
- const toolResultBlocks = messageContent.filter(
835
- (block) => block.type === "tool_result"
836
- );
837
- const toolUseResult = toolResultBlocks.length === 1 ? msg.tool_use_result : void 0;
838
- for (const block of messageContent) {
839
- if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
840
- if (state.structuredOutputToolUseIds.delete(block.tool_use_id)) {
841
- continue;
842
- }
843
- if (state.mcpToolUseIds.has(block.tool_use_id)) {
844
- state.mcpToolUseIds.delete(block.tool_use_id);
845
- state.pendingStepToolUseIds.delete(block.tool_use_id);
846
- continue;
847
- }
848
- state.approvalRequestedToolUseIds.delete(block.tool_use_id);
849
- const nativeName = state.nativeToolCallNames.get(block.tool_use_id) ?? "unknown";
850
- state.nativeToolCallNames.delete(block.tool_use_id);
851
- const toolName = toCommonName2(nativeName);
852
- const dynamic = state.externalMcpToolUseIds.delete(block.tool_use_id);
853
- const isError = !!block.is_error;
854
- const result = toolUseResult !== void 0 ? toolUseResult : resolveToolResult({
855
- toolName,
856
- dynamic,
857
- isError,
858
- rawContent: block.content
859
- });
860
- emit({
861
- type: "tool-result",
862
- toolCallId: block.tool_use_id,
863
- toolName,
864
- result,
865
- isError,
866
- ...dynamic ? { dynamic: true } : {}
867
- });
868
- state.pendingStepToolUseIds.delete(block.tool_use_id);
869
- }
870
- }
871
- closeStepIfReady({ state, emit });
872
- }
873
- };
658
+ function createEmitStreamEvent({ state, emit, emitWarning, emitTerminalError, onCompactionBoundary, toCommonName }) {
659
+ let streamStarted = false;
660
+ return (msg) => {
661
+ const type = msg.type;
662
+ if (!streamStarted && type !== "command_lifecycle") {
663
+ const initModel = type === "system" && msg.subtype === "init" && typeof msg.model === "string" ? msg.model : void 0;
664
+ emit({
665
+ type: "stream-start",
666
+ ...initModel ? { modelId: initModel } : {}
667
+ });
668
+ streamStarted = true;
669
+ }
670
+ if (type === "system" && msg.subtype === "api_retry") {
671
+ if (typeof msg.error_status === "number" && UNRECOVERABLE_API_RETRY_STATUSES.has(msg.error_status)) {
672
+ emitTerminalError(`HTTP ${msg.error_status}: ${msg.error ?? "provider request failed"}`);
673
+ return;
674
+ }
675
+ emitWarning({ message: formatApiRetryWarning(msg) });
676
+ return;
677
+ }
678
+ if (typeof msg.error === "string" && msg.error.trim()) state.observedTerminalError = msg.error.trim();
679
+ if (type === "auth_status" && typeof msg.error === "string" && msg.error.trim()) {
680
+ emitTerminalError(msg.error);
681
+ return;
682
+ }
683
+ if (type === "system" && msg.subtype === "task_updated" && msg.patch?.status === "failed" && typeof msg.patch.error === "string") {
684
+ emitTerminalError(msg.patch.error);
685
+ return;
686
+ }
687
+ if (type === "system" && msg.subtype === "compact_boundary") {
688
+ const meta = msg.compact_metadata;
689
+ if (meta) onCompactionBoundary({
690
+ trigger: meta.trigger,
691
+ ...typeof meta.pre_tokens === "number" ? { tokensBefore: meta.pre_tokens } : {},
692
+ ...typeof meta.post_tokens === "number" ? { tokensAfter: meta.post_tokens } : {}
693
+ });
694
+ return;
695
+ }
696
+ if (type === "tool_progress" || type === "system" && msg.subtype != null && RAW_TASK_MESSAGE_SUBTYPES.has(msg.subtype)) {
697
+ emit({
698
+ type: "raw",
699
+ rawValue: msg
700
+ });
701
+ return;
702
+ }
703
+ if (msg.parent_tool_use_id != null) {
704
+ emit({
705
+ type: "raw",
706
+ rawValue: msg
707
+ });
708
+ return;
709
+ }
710
+ if (type === "stream_event") {
711
+ handleStreamEvent({
712
+ event: msg.event,
713
+ message: msg,
714
+ state,
715
+ send: emit,
716
+ toCommonName
717
+ });
718
+ return;
719
+ }
720
+ const messageContent = msg.message?.content;
721
+ if (type === "assistant" && Array.isArray(messageContent)) {
722
+ const usage = toUsageRecord(msg.message?.usage);
723
+ const toolUseIds = [];
724
+ let opensStep = false;
725
+ for (const block of messageContent) if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
726
+ toolUseIds.push(block.id);
727
+ if (block.name === "StructuredOutput") {
728
+ state.structuredOutputToolUseIds.add(block.id);
729
+ continue;
730
+ }
731
+ if (block.name.startsWith(HOST_TOOL_PREFIX)) {
732
+ state.pendingStepToolUseIds.add(block.id);
733
+ state.mcpToolUseIds.add(block.id);
734
+ opensStep = true;
735
+ continue;
736
+ }
737
+ state.nativeToolCallNames.set(block.id, block.name);
738
+ if (block.name === "AskUserQuestion") {
739
+ state.pendingStepToolUseIds.add(block.id);
740
+ opensStep = true;
741
+ continue;
742
+ }
743
+ const dynamic = isExternalMcpTool(block.name);
744
+ if (dynamic) state.externalMcpToolUseIds.add(block.id);
745
+ if (state.approvalRequestedToolUseIds.has(block.id)) continue;
746
+ state.pendingStepToolUseIds.add(block.id);
747
+ opensStep = true;
748
+ emit({
749
+ type: "tool-call",
750
+ toolCallId: block.id,
751
+ toolName: toCommonName(block.name),
752
+ nativeName: block.name,
753
+ input: JSON.stringify(block.input ?? {}),
754
+ providerExecuted: true,
755
+ ...dynamic ? { dynamic: true } : {}
756
+ });
757
+ }
758
+ if (opensStep || toolUseIds.length === 0) {
759
+ state.stepOpen = true;
760
+ if (usage) {
761
+ state.pendingStepAssistantUsage = usage;
762
+ updatePendingStepUsage(state);
763
+ }
764
+ }
765
+ return;
766
+ }
767
+ if (type === "user" && Array.isArray(messageContent)) {
768
+ const toolUseResult = messageContent.filter((block) => block.type === "tool_result").length === 1 ? msg.tool_use_result : void 0;
769
+ for (const block of messageContent) if (block.type === "tool_result" && typeof block.tool_use_id === "string") {
770
+ if (state.structuredOutputToolUseIds.delete(block.tool_use_id)) continue;
771
+ if (state.mcpToolUseIds.has(block.tool_use_id)) {
772
+ state.mcpToolUseIds.delete(block.tool_use_id);
773
+ state.pendingStepToolUseIds.delete(block.tool_use_id);
774
+ continue;
775
+ }
776
+ state.approvalRequestedToolUseIds.delete(block.tool_use_id);
777
+ const nativeName = state.nativeToolCallNames.get(block.tool_use_id) ?? "unknown";
778
+ state.nativeToolCallNames.delete(block.tool_use_id);
779
+ const toolName = toCommonName(nativeName);
780
+ const dynamic = state.externalMcpToolUseIds.delete(block.tool_use_id);
781
+ const isError = !!block.is_error;
782
+ const result = toolUseResult !== void 0 ? toolUseResult : resolveToolResult({
783
+ toolName,
784
+ dynamic,
785
+ isError,
786
+ rawContent: block.content
787
+ });
788
+ emit({
789
+ type: "tool-result",
790
+ toolCallId: block.tool_use_id,
791
+ toolName,
792
+ result,
793
+ isError,
794
+ ...dynamic ? { dynamic: true } : {}
795
+ });
796
+ state.pendingStepToolUseIds.delete(block.tool_use_id);
797
+ }
798
+ closeStepIfReady({
799
+ state,
800
+ emit
801
+ });
802
+ }
803
+ };
874
804
  }
875
- function finishApprovalStep({
876
- state,
877
- emit,
878
- approvalId
879
- }) {
880
- state.stepOpen = true;
881
- state.pendingStepToolUseIds.delete(approvalId);
882
- closeStepIfReady({ state, emit });
805
+ function finishApprovalStep({ state, emit, approvalId }) {
806
+ state.stepOpen = true;
807
+ state.pendingStepToolUseIds.delete(approvalId);
808
+ closeStepIfReady({
809
+ state,
810
+ emit
811
+ });
883
812
  }
884
- function emitFinishStep({
885
- state,
886
- emit,
887
- usage
888
- }) {
889
- emit({
890
- type: "finish-step",
891
- finishReason: { unified: "stop", raw: "stop" },
892
- usage: usage ?? defaultUsage()
893
- });
894
- state.stepUsage = usage ?? state.stepUsage;
895
- state.pendingStepAssistantUsage = void 0;
896
- state.pendingStepDeltaUsage = void 0;
897
- state.pendingStepUsage = void 0;
898
- state.pendingStepToolUseIds = /* @__PURE__ */ new Set();
899
- state.stepOpen = false;
813
+ function emitFinishStep({ state, emit, usage }) {
814
+ emit({
815
+ type: "finish-step",
816
+ finishReason: {
817
+ unified: "stop",
818
+ raw: "stop"
819
+ },
820
+ usage: usage ?? defaultUsage()
821
+ });
822
+ state.stepUsage = usage ?? state.stepUsage;
823
+ state.pendingStepAssistantUsage = void 0;
824
+ state.pendingStepDeltaUsage = void 0;
825
+ state.pendingStepUsage = void 0;
826
+ state.pendingStepToolUseIds = /* @__PURE__ */ new Set();
827
+ state.stepOpen = false;
900
828
  }
901
- function closeStepIfReady({
902
- state,
903
- emit
904
- }) {
905
- if (!state.stepOpen || state.pendingStepToolUseIds.size > 0 || state.partialBlocks.size > 0) {
906
- return;
907
- }
908
- emitFinishStep({ state, emit, usage: state.pendingStepUsage });
829
+ function closeStepIfReady({ state, emit }) {
830
+ if (!state.stepOpen || state.pendingStepToolUseIds.size > 0 || state.partialBlocks.size > 0) return;
831
+ emitFinishStep({
832
+ state,
833
+ emit,
834
+ usage: state.pendingStepUsage
835
+ });
909
836
  }
910
837
  function formatApiRetryWarning(msg) {
911
- const details = [];
912
- if (typeof msg.attempt === "number") {
913
- const maxRetries = typeof msg.max_retries === "number" ? `/${msg.max_retries}` : "";
914
- details.push(`attempt ${msg.attempt}${maxRetries}`);
915
- }
916
- if (typeof msg.error_status === "number") {
917
- details.push(`HTTP ${msg.error_status}`);
918
- }
919
- if (typeof msg.retry_delay_ms === "number") {
920
- details.push(`retrying in ${msg.retry_delay_ms}ms`);
921
- }
922
- if (msg.error) details.push(msg.error);
923
- return details.length > 0 ? `Claude Code API retry: ${details.join("; ")}` : "Claude Code API retry";
838
+ const details = [];
839
+ if (typeof msg.attempt === "number") {
840
+ const maxRetries = typeof msg.max_retries === "number" ? `/${msg.max_retries}` : "";
841
+ details.push(`attempt ${msg.attempt}${maxRetries}`);
842
+ }
843
+ if (typeof msg.error_status === "number") details.push(`HTTP ${msg.error_status}`);
844
+ if (typeof msg.retry_delay_ms === "number") details.push(`retrying in ${msg.retry_delay_ms}ms`);
845
+ if (msg.error) details.push(msg.error);
846
+ return details.length > 0 ? `Claude Code API retry: ${details.join("; ")}` : "Claude Code API retry";
924
847
  }
925
- function handleStreamEvent({
926
- event,
927
- state,
928
- send,
929
- toCommonName: toCommonName2
930
- }) {
931
- if (!event) return;
932
- if (event.type === "message_delta") {
933
- const usage = toUsageRecord(event.usage);
934
- if (usage) {
935
- state.pendingStepDeltaUsage = mergeNonNullUsage(
936
- state.pendingStepDeltaUsage,
937
- usage
938
- );
939
- updatePendingStepUsage(state);
940
- }
941
- return;
942
- }
943
- if (typeof event.index !== "number") return;
944
- const index = event.index;
945
- const partialBlocks = state.partialBlocks;
946
- if (event.type === "content_block_start") {
947
- const blockType = event.content_block?.type;
948
- if (blockType === "text") {
949
- const id = randomUUID2();
950
- partialBlocks.set(index, { id, kind: "text" });
951
- send({ type: "text-start", id });
952
- } else if (blockType === "thinking") {
953
- const id = randomUUID2();
954
- partialBlocks.set(index, { id, kind: "thinking" });
955
- send({ type: "reasoning-start", id });
956
- } else if (blockType === "tool_use" && typeof event.content_block?.id === "string" && typeof event.content_block.name === "string") {
957
- const id = event.content_block.id;
958
- const nativeName = event.content_block.name;
959
- if (nativeName === "StructuredOutput") {
960
- return;
961
- }
962
- if (nativeName === "AskUserQuestion") {
963
- return;
964
- }
965
- const hostToolName = nativeName.startsWith(HOST_TOOL_PREFIX) ? nativeName.slice(HOST_TOOL_PREFIX.length) : void 0;
966
- const dynamic = isExternalMcpTool(nativeName);
967
- partialBlocks.set(index, { id, kind: "tool-input" });
968
- send({
969
- type: "tool-input-start",
970
- id,
971
- toolName: hostToolName ?? toCommonName2(nativeName),
972
- providerExecuted: hostToolName === void 0,
973
- ...dynamic ? { dynamic: true } : {}
974
- });
975
- }
976
- return;
977
- }
978
- if (event.type === "content_block_delta") {
979
- const block = partialBlocks.get(index);
980
- if (!block) return;
981
- if (block.kind === "text" && event.delta?.type === "text_delta" && typeof event.delta.text === "string") {
982
- send({ type: "text-delta", id: block.id, delta: event.delta.text });
983
- } else if (block.kind === "thinking" && event.delta?.type === "thinking_delta" && typeof event.delta.thinking === "string") {
984
- send({
985
- type: "reasoning-delta",
986
- id: block.id,
987
- delta: event.delta.thinking
988
- });
989
- } else if (block.kind === "tool-input" && event.delta?.type === "input_json_delta" && typeof event.delta.partial_json === "string") {
990
- send({
991
- type: "tool-input-delta",
992
- id: block.id,
993
- delta: event.delta.partial_json
994
- });
995
- }
996
- return;
997
- }
998
- if (event.type === "content_block_stop") {
999
- const block = partialBlocks.get(index);
1000
- if (!block) return;
1001
- partialBlocks.delete(index);
1002
- if (block.kind === "text") {
1003
- send({ type: "text-end", id: block.id });
1004
- } else if (block.kind === "thinking") {
1005
- send({ type: "reasoning-end", id: block.id });
1006
- } else {
1007
- send({ type: "tool-input-end", id: block.id });
1008
- }
1009
- }
848
+ function handleStreamEvent({ event, message, state, send, toCommonName }) {
849
+ if (!event) return;
850
+ if (event.type === "message_start") {
851
+ state.pendingResponseUsage = toUsageRecord(event.message?.usage);
852
+ return;
853
+ }
854
+ if (event.type === "message_delta") {
855
+ const usage = toUsageRecord(event.usage);
856
+ if (usage) {
857
+ state.pendingResponseUsage = mergeNonNullUsage(state.pendingResponseUsage, usage);
858
+ state.pendingStepDeltaUsage = mergeNonNullUsage(state.pendingStepDeltaUsage, usage);
859
+ updatePendingStepUsage(state);
860
+ }
861
+ return;
862
+ }
863
+ if (event.type === "message_stop") {
864
+ send({
865
+ type: "raw",
866
+ rawValue: {
867
+ ...message,
868
+ usage: state.pendingResponseUsage ?? {}
869
+ }
870
+ });
871
+ state.pendingResponseUsage = void 0;
872
+ return;
873
+ }
874
+ if (typeof event.index !== "number") return;
875
+ const index = event.index;
876
+ const partialBlocks = state.partialBlocks;
877
+ if (event.type === "content_block_start") {
878
+ const blockType = event.content_block?.type;
879
+ if (blockType === "text") {
880
+ const id = randomUUID();
881
+ partialBlocks.set(index, {
882
+ id,
883
+ kind: "text"
884
+ });
885
+ send({
886
+ type: "text-start",
887
+ id
888
+ });
889
+ } else if (blockType === "thinking") {
890
+ const id = randomUUID();
891
+ partialBlocks.set(index, {
892
+ id,
893
+ kind: "thinking"
894
+ });
895
+ send({
896
+ type: "reasoning-start",
897
+ id
898
+ });
899
+ } else if (blockType === "tool_use" && typeof event.content_block?.id === "string" && typeof event.content_block.name === "string") {
900
+ const id = event.content_block.id;
901
+ const nativeName = event.content_block.name;
902
+ if (nativeName === "StructuredOutput") return;
903
+ if (nativeName === "AskUserQuestion") return;
904
+ const hostToolName = nativeName.startsWith(HOST_TOOL_PREFIX) ? nativeName.slice(20) : void 0;
905
+ const dynamic = isExternalMcpTool(nativeName);
906
+ partialBlocks.set(index, {
907
+ id,
908
+ kind: "tool-input"
909
+ });
910
+ send({
911
+ type: "tool-input-start",
912
+ id,
913
+ toolName: hostToolName ?? toCommonName(nativeName),
914
+ providerExecuted: hostToolName === void 0,
915
+ ...dynamic ? { dynamic: true } : {}
916
+ });
917
+ }
918
+ return;
919
+ }
920
+ if (event.type === "content_block_delta") {
921
+ const block = partialBlocks.get(index);
922
+ if (!block) return;
923
+ if (block.kind === "text" && event.delta?.type === "text_delta" && typeof event.delta.text === "string") send({
924
+ type: "text-delta",
925
+ id: block.id,
926
+ delta: event.delta.text
927
+ });
928
+ else if (block.kind === "thinking" && event.delta?.type === "thinking_delta" && typeof event.delta.thinking === "string") send({
929
+ type: "reasoning-delta",
930
+ id: block.id,
931
+ delta: event.delta.thinking
932
+ });
933
+ else if (block.kind === "tool-input" && event.delta?.type === "input_json_delta" && typeof event.delta.partial_json === "string") send({
934
+ type: "tool-input-delta",
935
+ id: block.id,
936
+ delta: event.delta.partial_json
937
+ });
938
+ return;
939
+ }
940
+ if (event.type === "content_block_stop") {
941
+ const block = partialBlocks.get(index);
942
+ if (!block) return;
943
+ partialBlocks.delete(index);
944
+ if (block.kind === "text") send({
945
+ type: "text-end",
946
+ id: block.id
947
+ });
948
+ else if (block.kind === "thinking") send({
949
+ type: "reasoning-end",
950
+ id: block.id
951
+ });
952
+ else send({
953
+ type: "tool-input-end",
954
+ id: block.id
955
+ });
956
+ }
1010
957
  }
1011
958
  function toUsageRecord(usage) {
1012
- return usage != null && typeof usage === "object" ? usage : void 0;
959
+ return usage != null && typeof usage === "object" ? usage : void 0;
1013
960
  }
1014
961
  function mergeNonNullUsage(current, update) {
1015
- const merged = { ...current };
1016
- for (const [key, value] of Object.entries(update)) {
1017
- if (value != null) {
1018
- merged[key] = value;
1019
- }
1020
- }
1021
- return merged;
962
+ const merged = { ...current };
963
+ for (const [key, value] of Object.entries(update)) if (value != null) merged[key] = value;
964
+ return merged;
1022
965
  }
1023
966
  function updatePendingStepUsage(state) {
1024
- const assistantUsage = state.pendingStepAssistantUsage;
1025
- const deltaUsage = state.pendingStepDeltaUsage;
1026
- state.pendingStepUsage = mapUsage(
1027
- assistantUsage || deltaUsage ? { ...assistantUsage, ...deltaUsage } : void 0
1028
- );
967
+ const assistantUsage = state.pendingStepAssistantUsage;
968
+ const deltaUsage = state.pendingStepDeltaUsage;
969
+ state.pendingStepUsage = mapUsage(assistantUsage || deltaUsage ? {
970
+ ...assistantUsage,
971
+ ...deltaUsage
972
+ } : void 0);
1029
973
  }
1030
974
  function isTextEntry(entry) {
1031
- return entry != null && typeof entry === "object" && "text" in entry;
975
+ return entry != null && typeof entry === "object" && "text" in entry;
1032
976
  }
1033
977
  function stringifyContent(content) {
1034
- if (typeof content === "string") return content;
1035
- if (Array.isArray(content)) {
1036
- return content.map(
1037
- (entry) => isTextEntry(entry) ? String(entry.text ?? "") : JSON.stringify(entry)
1038
- ).join("");
1039
- }
1040
- return JSON.stringify(content);
978
+ if (typeof content === "string") return content;
979
+ if (Array.isArray(content)) return content.map((entry) => isTextEntry(entry) ? String(entry.text ?? "") : JSON.stringify(entry)).join("");
980
+ return JSON.stringify(content);
1041
981
  }
1042
982
  function hasNonTextContent(content) {
1043
- return Array.isArray(content) && content.some((entry) => !isTextEntry(entry));
983
+ return Array.isArray(content) && content.some((entry) => !isTextEntry(entry));
1044
984
  }
1045
- function resolveToolResult({
1046
- toolName,
1047
- dynamic,
1048
- isError,
1049
- rawContent
1050
- }) {
1051
- if (toolName === "bash") {
1052
- return { exitCode: isError ? 1 : 0, stdout: stringifyContent(rawContent) };
1053
- }
1054
- if (hasNonTextContent(rawContent)) return rawContent;
1055
- const content = stringifyContent(rawContent);
1056
- return dynamic ? parseMcpToolResult(content) : content;
985
+ function resolveToolResult({ toolName, dynamic, isError, rawContent }) {
986
+ if (toolName === "bash") return {
987
+ exitCode: isError ? 1 : 0,
988
+ stdout: stringifyContent(rawContent)
989
+ };
990
+ if (hasNonTextContent(rawContent)) return rawContent;
991
+ const content = stringifyContent(rawContent);
992
+ return dynamic ? parseMcpToolResult(content) : content;
1057
993
  }
1058
994
  function parseMcpToolResult(content) {
1059
- try {
1060
- const parsed = JSON.parse(content);
1061
- return parsed !== null && typeof parsed === "object" ? parsed : content;
1062
- } catch {
1063
- return content;
1064
- }
995
+ try {
996
+ const parsed = JSON.parse(content);
997
+ return parsed !== null && typeof parsed === "object" ? parsed : content;
998
+ } catch {
999
+ return content;
1000
+ }
1065
1001
  }
1066
1002
  function mapUsage(usage) {
1067
- if (!usage || typeof usage !== "object") return void 0;
1068
- const u = usage;
1069
- return {
1070
- inputTokens: {
1071
- total: (u.input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0),
1072
- noCache: u.input_tokens ?? 0,
1073
- cacheRead: u.cache_read_input_tokens ?? 0,
1074
- cacheWrite: u.cache_creation_input_tokens ?? 0
1075
- },
1076
- outputTokens: {
1077
- total: u.output_tokens ?? 0,
1078
- text: u.output_tokens ?? 0
1079
- }
1080
- };
1003
+ if (!usage || typeof usage !== "object") return void 0;
1004
+ const u = usage;
1005
+ return {
1006
+ inputTokens: {
1007
+ total: (u.input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0),
1008
+ noCache: u.input_tokens ?? 0,
1009
+ cacheRead: u.cache_read_input_tokens ?? 0,
1010
+ cacheWrite: u.cache_creation_input_tokens ?? 0
1011
+ },
1012
+ outputTokens: {
1013
+ total: u.output_tokens ?? 0,
1014
+ text: u.output_tokens ?? 0
1015
+ }
1016
+ };
1081
1017
  }
1082
1018
  function defaultUsage() {
1083
- return {
1084
- inputTokens: { total: 0, noCache: 0, cacheRead: 0, cacheWrite: 0 },
1085
- outputTokens: { total: 0, text: 0 }
1086
- };
1019
+ return {
1020
+ inputTokens: {
1021
+ total: 0,
1022
+ noCache: 0,
1023
+ cacheRead: 0,
1024
+ cacheWrite: 0
1025
+ },
1026
+ outputTokens: {
1027
+ total: 0,
1028
+ text: 0
1029
+ }
1030
+ };
1087
1031
  }
1088
-
1089
- // src/bridge/json-schema-to-zod.ts
1090
- import { z } from "zod/v4";
1032
+ //#endregion
1033
+ //#region src/bridge/json-schema-to-zod.ts
1091
1034
  function jsonSchemaToZodObject(input) {
1092
- const schema = isJsonSchemaObject(input) ? input : {};
1093
- return toZodObject(schema);
1035
+ return toZodObject(isJsonSchemaObject(input) ? input : {});
1094
1036
  }
1095
1037
  function toZodObject(schema) {
1096
- const object = z.object(toZodShape(schema));
1097
- if (schema.additionalProperties === false) return object.strict();
1098
- return object.catchall(
1099
- isJsonSchemaObject(schema.additionalProperties) ? toZodType(schema.additionalProperties) : z.unknown()
1100
- );
1038
+ const object = z.object(toZodShape(schema));
1039
+ if (schema.additionalProperties === false) return object.strict();
1040
+ return object.catchall(isJsonSchemaObject(schema.additionalProperties) ? toZodType(schema.additionalProperties) : z.unknown());
1101
1041
  }
1102
1042
  function toZodShape(schema) {
1103
- if (!schema?.properties) return {};
1104
- const required = new Set(schema.required);
1105
- const shape = {};
1106
- for (const [key, propSchema] of Object.entries(schema.properties)) {
1107
- const propType = toZodType(propSchema);
1108
- let propertyType = required.has(key) ? propType : propType.optional();
1109
- if (propSchema.description) {
1110
- propertyType = propertyType.describe(propSchema.description);
1111
- }
1112
- shape[key] = propertyType;
1113
- }
1114
- return shape;
1043
+ if (!schema?.properties) return {};
1044
+ const required = new Set(schema.required);
1045
+ const shape = {};
1046
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
1047
+ const propType = toZodType(propSchema);
1048
+ let propertyType = required.has(key) ? propType : propType.optional();
1049
+ if (propSchema.description) propertyType = propertyType.describe(propSchema.description);
1050
+ shape[key] = propertyType;
1051
+ }
1052
+ return shape;
1115
1053
  }
1116
1054
  function toZodType(schema) {
1117
- if (!schema) return z.any();
1118
- let zType = zodForConst(schema) ?? zodForEnum(schema) ?? zodForUnion(schema) ?? zodForType(schema);
1119
- if (isNullable(schema)) zType = zType.nullable();
1120
- if (schema.description) zType = zType.describe(schema.description);
1121
- return zType;
1055
+ if (!schema) return z.any();
1056
+ let zType = zodForConst(schema) ?? zodForEnum(schema) ?? zodForUnion(schema) ?? zodForType(schema);
1057
+ if (isNullable(schema)) zType = zType.nullable();
1058
+ if (schema.description) zType = zType.describe(schema.description);
1059
+ return zType;
1122
1060
  }
1123
1061
  function zodForType(schema) {
1124
- const types = getNonNullTypes(schema);
1125
- if (types.length > 1) return z.any();
1126
- if (Array.isArray(schema.type) && types.length === 0) return z.null();
1127
- const type = types[0] ?? (schema.properties ? "object" : void 0);
1128
- switch (type) {
1129
- case "string":
1130
- return z.string();
1131
- case "number":
1132
- return z.number();
1133
- case "integer":
1134
- return z.number().int();
1135
- case "boolean":
1136
- return z.boolean();
1137
- case "array":
1138
- return z.array(
1139
- Array.isArray(schema.items) ? z.any() : toZodType(schema.items)
1140
- );
1141
- case "object":
1142
- return toZodObject(schema);
1143
- case "null":
1144
- return z.null();
1145
- default:
1146
- return z.any();
1147
- }
1062
+ const types = getNonNullTypes(schema);
1063
+ if (types.length > 1) return z.any();
1064
+ if (Array.isArray(schema.type) && types.length === 0) return z.null();
1065
+ switch (types[0] ?? (schema.properties ? "object" : void 0)) {
1066
+ case "string": return z.string();
1067
+ case "number": return z.number();
1068
+ case "integer": return z.number().int();
1069
+ case "boolean": return z.boolean();
1070
+ case "array": return z.array(Array.isArray(schema.items) ? z.any() : toZodType(schema.items));
1071
+ case "object": return toZodObject(schema);
1072
+ case "null": return z.null();
1073
+ default: return z.any();
1074
+ }
1148
1075
  }
1149
1076
  function zodForConst(schema) {
1150
- if (!("const" in schema)) return void 0;
1151
- return isJsonLiteral(schema.const) ? z.literal(schema.const) : z.any();
1077
+ if (!("const" in schema)) return void 0;
1078
+ return isJsonLiteral(schema.const) ? z.literal(schema.const) : z.any();
1152
1079
  }
1153
1080
  function zodForEnum(schema) {
1154
- if (!Array.isArray(schema.enum)) return void 0;
1155
- if (!schema.enum.every(isJsonLiteral)) return z.any();
1156
- return zodForLiterals(schema.enum);
1081
+ if (!Array.isArray(schema.enum)) return void 0;
1082
+ if (!schema.enum.every(isJsonLiteral)) return z.any();
1083
+ return zodForLiterals(schema.enum);
1157
1084
  }
1158
1085
  function zodForUnion(schema) {
1159
- const unionSchemas = schema.anyOf ?? schema.oneOf;
1160
- if (!unionSchemas || unionSchemas.length < 2) return void 0;
1161
- const options = unionSchemas.map((item) => toZodType(item));
1162
- return z.union(options);
1086
+ const unionSchemas = schema.anyOf ?? schema.oneOf;
1087
+ if (!unionSchemas || unionSchemas.length < 2) return void 0;
1088
+ const options = unionSchemas.map((item) => toZodType(item));
1089
+ return z.union(options);
1163
1090
  }
1164
1091
  function zodForLiterals(values) {
1165
- if (values.length === 0) return z.any();
1166
- if (values.length === 1) return z.literal(values[0]);
1167
- const literals = values.map((value) => z.literal(value));
1168
- return z.union(literals);
1092
+ if (values.length === 0) return z.any();
1093
+ if (values.length === 1) return z.literal(values[0]);
1094
+ const literals = values.map((value) => z.literal(value));
1095
+ return z.union(literals);
1169
1096
  }
1170
1097
  function getNonNullTypes(schema) {
1171
- return Array.isArray(schema.type) ? schema.type.filter((type) => type !== "null") : [schema.type].filter(Boolean);
1098
+ return Array.isArray(schema.type) ? schema.type.filter((type) => type !== "null") : [schema.type].filter(Boolean);
1172
1099
  }
1173
1100
  function isNullable(schema) {
1174
- return schema.nullable === true || Array.isArray(schema.type) && schema.type.includes("null");
1101
+ return schema.nullable === true || Array.isArray(schema.type) && schema.type.includes("null");
1175
1102
  }
1176
1103
  function isJsonSchemaObject(input) {
1177
- return input != null && typeof input === "object" && !Array.isArray(input);
1104
+ return input != null && typeof input === "object" && !Array.isArray(input);
1178
1105
  }
1179
1106
  function isJsonLiteral(value) {
1180
- return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
1107
+ return value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
1181
1108
  }
1182
-
1183
- // src/bridge/tool-filtering.ts
1184
- var PUBLIC_TO_NATIVE = {
1185
- read: "Read",
1186
- write: "Write",
1187
- edit: "Edit",
1188
- bash: "Bash",
1189
- glob: "Glob",
1190
- grep: "Grep",
1191
- webSearch: "WebSearch",
1192
- WebFetch: "WebFetch",
1193
- NotebookEdit: "NotebookEdit",
1194
- TodoWrite: "TodoWrite",
1195
- Agent: "Agent",
1196
- TaskCreate: "TaskCreate",
1197
- TaskGet: "TaskGet",
1198
- TaskUpdate: "TaskUpdate",
1199
- TaskList: "TaskList",
1200
- TaskStop: "TaskStop",
1201
- TaskOutput: "TaskOutput",
1202
- Monitor: "Monitor",
1203
- ListMcpResources: "ListMcpResources",
1204
- ReadMcpResource: "ReadMcpResource",
1205
- ExitPlanMode: "ExitPlanMode",
1206
- EnterWorktree: "EnterWorktree",
1207
- ExitWorktree: "ExitWorktree",
1208
- askUserQuestions: "AskUserQuestion",
1209
- Skill: "Skill",
1210
- ToolSearch: "ToolSearch"
1109
+ //#endregion
1110
+ //#region src/bridge/tool-filtering.ts
1111
+ const PUBLIC_TO_NATIVE = {
1112
+ read: "Read",
1113
+ write: "Write",
1114
+ edit: "Edit",
1115
+ bash: "Bash",
1116
+ glob: "Glob",
1117
+ grep: "Grep",
1118
+ webSearch: "WebSearch",
1119
+ WebFetch: "WebFetch",
1120
+ NotebookEdit: "NotebookEdit",
1121
+ TodoWrite: "TodoWrite",
1122
+ Agent: "Agent",
1123
+ TaskCreate: "TaskCreate",
1124
+ TaskGet: "TaskGet",
1125
+ TaskUpdate: "TaskUpdate",
1126
+ TaskList: "TaskList",
1127
+ TaskStop: "TaskStop",
1128
+ TaskOutput: "TaskOutput",
1129
+ Monitor: "Monitor",
1130
+ ListMcpResources: "ListMcpResources",
1131
+ ReadMcpResource: "ReadMcpResource",
1132
+ ExitPlanMode: "ExitPlanMode",
1133
+ EnterWorktree: "EnterWorktree",
1134
+ ExitWorktree: "ExitWorktree",
1135
+ askUserQuestions: "AskUserQuestion",
1136
+ Skill: "Skill",
1137
+ ToolSearch: "ToolSearch"
1211
1138
  };
1212
- var PUBLIC_TOOL_NAMES = Object.keys(PUBLIC_TO_NATIVE);
1139
+ const PUBLIC_TOOL_NAMES = Object.keys(PUBLIC_TO_NATIVE);
1213
1140
  function toNativeName(toolName) {
1214
- return PUBLIC_TO_NATIVE[toolName] ?? toolName;
1141
+ return PUBLIC_TO_NATIVE[toolName] ?? toolName;
1215
1142
  }
1216
1143
  function resolveNativeTools(toolFiltering) {
1217
- if (toolFiltering == null || toolFiltering.mode === "deny") return void 0;
1218
- return toolFiltering.toolNames.map((name) => toNativeName(name));
1144
+ if (toolFiltering == null || toolFiltering.mode === "deny") return void 0;
1145
+ return toolFiltering.toolNames.map((name) => toNativeName(name));
1219
1146
  }
1220
1147
  function resolveInactiveNativeTools(toolFiltering) {
1221
- if (toolFiltering == null) return [];
1222
- const inactiveToolNames = toolFiltering.mode === "allow" ? PUBLIC_TOOL_NAMES.filter(
1223
- (name) => !toolFiltering.toolNames.includes(name)
1224
- ) : toolFiltering.toolNames;
1225
- return inactiveToolNames.map((name) => toNativeName(name));
1148
+ if (toolFiltering == null) return [];
1149
+ return (toolFiltering.mode === "allow" ? PUBLIC_TOOL_NAMES.filter((name) => !toolFiltering.toolNames.includes(name)) : toolFiltering.toolNames).map((name) => toNativeName(name));
1226
1150
  }
1227
-
1228
- // src/bridge/question-tool.ts
1151
+ //#endregion
1152
+ //#region src/bridge/question-tool.ts
1229
1153
  function claudeCodeQuestionKey(nativeInput) {
1230
- return JSON.stringify(nativeInput.questions);
1154
+ return JSON.stringify(nativeInput.questions);
1231
1155
  }
1232
1156
  function toHarnessQuestionsInput(nativeInput) {
1233
- return {
1234
- allowPartialAnswers: true,
1235
- questions: nativeInput.questions.map((question, questionIndex) => ({
1236
- id: `question-${questionIndex + 1}`,
1237
- question: question.question,
1238
- header: question.header,
1239
- options: question.options.map((option, optionIndex) => ({
1240
- id: `option-${optionIndex + 1}`,
1241
- label: option.label,
1242
- description: option.description,
1243
- ...option.preview !== void 0 ? { preview: option.preview } : {}
1244
- })),
1245
- allowMultiple: question.multiSelect,
1246
- allowFreeForm: true
1247
- }))
1248
- };
1157
+ return {
1158
+ allowPartialAnswers: true,
1159
+ questions: nativeInput.questions.map((question, questionIndex) => ({
1160
+ id: `question-${questionIndex + 1}`,
1161
+ question: question.question,
1162
+ header: question.header,
1163
+ options: question.options.map((option, optionIndex) => ({
1164
+ id: `option-${optionIndex + 1}`,
1165
+ label: option.label,
1166
+ description: option.description,
1167
+ ...option.preview !== void 0 ? { preview: option.preview } : {}
1168
+ })),
1169
+ allowMultiple: question.multiSelect,
1170
+ allowFreeForm: true
1171
+ }))
1172
+ };
1249
1173
  }
1250
1174
  function toClaudeCodeQuestionResult(input) {
1251
- const output = input.output;
1252
- if (output.action === "declined" || output.action === "cancelled") {
1253
- return {
1254
- behavior: "deny",
1255
- message: `The user ${output.action} the questions.`
1256
- };
1257
- }
1258
- const canonicalInput = toHarnessQuestionsInput(input.nativeInput);
1259
- const answers = Object.fromEntries(
1260
- canonicalInput.questions.flatMap((question, questionIndex) => {
1261
- const answer = output.answers[question.id];
1262
- if (answer == null) return [];
1263
- const nativeQuestion = input.nativeInput.questions[questionIndex];
1264
- return [
1265
- [
1266
- nativeQuestion.question,
1267
- toClaudeCodeAnswer({
1268
- nativeQuestion,
1269
- optionIds: answer.optionIds,
1270
- freeform: answer.freeform
1271
- })
1272
- ]
1273
- ];
1274
- })
1275
- );
1276
- return {
1277
- behavior: "allow",
1278
- updatedInput: { ...input.nativeInput, answers }
1279
- };
1175
+ const output = input.output;
1176
+ if (output.action === "declined" || output.action === "cancelled") return {
1177
+ behavior: "deny",
1178
+ message: `The user ${output.action} the questions.`
1179
+ };
1180
+ const canonicalInput = toHarnessQuestionsInput(input.nativeInput);
1181
+ const answers = Object.fromEntries(canonicalInput.questions.flatMap((question, questionIndex) => {
1182
+ const answer = output.answers[question.id];
1183
+ if (answer == null) return [];
1184
+ const nativeQuestion = input.nativeInput.questions[questionIndex];
1185
+ return [[nativeQuestion.question, toClaudeCodeAnswer({
1186
+ nativeQuestion,
1187
+ optionIds: answer.optionIds,
1188
+ freeform: answer.freeform
1189
+ })]];
1190
+ }));
1191
+ return {
1192
+ behavior: "allow",
1193
+ updatedInput: {
1194
+ ...input.nativeInput,
1195
+ answers
1196
+ }
1197
+ };
1280
1198
  }
1281
1199
  function toClaudeCodeAnswer(input) {
1282
- if (input.freeform !== void 0) return input.freeform;
1283
- const labels = input.optionIds.flatMap((optionId) => {
1284
- const index = positionalIdIndex({ id: optionId, prefix: "option-" });
1285
- const option = index == null ? void 0 : input.nativeQuestion.options[index];
1286
- return option == null ? [] : [option.label];
1287
- });
1288
- return input.nativeQuestion.multiSelect ? labels.join(", ") : labels[0] ?? "";
1200
+ if (input.freeform !== void 0) return input.freeform;
1201
+ const labels = input.optionIds.flatMap((optionId) => {
1202
+ const index = positionalIdIndex({
1203
+ id: optionId,
1204
+ prefix: "option-"
1205
+ });
1206
+ const option = index == null ? void 0 : input.nativeQuestion.options[index];
1207
+ return option == null ? [] : [option.label];
1208
+ });
1209
+ return input.nativeQuestion.multiSelect ? labels.join(", ") : labels[0] ?? "";
1289
1210
  }
1290
1211
  function positionalIdIndex(input) {
1291
- if (!input.id.startsWith(input.prefix)) return void 0;
1292
- const oneBasedIndex = Number(input.id.slice(input.prefix.length));
1293
- return Number.isInteger(oneBasedIndex) && oneBasedIndex > 0 ? oneBasedIndex - 1 : void 0;
1212
+ if (!input.id.startsWith(input.prefix)) return void 0;
1213
+ const oneBasedIndex = Number(input.id.slice(input.prefix.length));
1214
+ return Number.isInteger(oneBasedIndex) && oneBasedIndex > 0 ? oneBasedIndex - 1 : void 0;
1294
1215
  }
1295
-
1296
- // src/bridge/index.ts
1297
- var NATIVE_TO_COMMON = {
1298
- Read: "read",
1299
- Write: "write",
1300
- Edit: "edit",
1301
- Bash: "bash",
1302
- Glob: "glob",
1303
- Grep: "grep",
1304
- WebSearch: "webSearch",
1305
- AskUserQuestion: "askUserQuestions"
1216
+ //#endregion
1217
+ //#region src/bridge/index.ts
1218
+ const NATIVE_TO_COMMON = {
1219
+ Read: "read",
1220
+ Write: "write",
1221
+ Edit: "edit",
1222
+ Bash: "bash",
1223
+ Glob: "glob",
1224
+ Grep: "grep",
1225
+ WebSearch: "webSearch",
1226
+ AskUserQuestion: "askUserQuestions"
1306
1227
  };
1307
- var NATIVE_TOOL_KINDS = {
1308
- Read: "readonly",
1309
- Glob: "readonly",
1310
- Grep: "readonly",
1311
- WebSearch: "readonly",
1312
- WebFetch: "readonly",
1313
- TaskGet: "readonly",
1314
- TaskList: "readonly",
1315
- TaskOutput: "readonly",
1316
- ListMcpResources: "readonly",
1317
- ReadMcpResource: "readonly",
1318
- Write: "edit",
1319
- Edit: "edit",
1320
- NotebookEdit: "edit",
1321
- TodoWrite: "edit",
1322
- TaskCreate: "edit",
1323
- TaskUpdate: "edit",
1324
- TaskStop: "edit",
1325
- EnterWorktree: "edit",
1326
- ExitWorktree: "edit",
1327
- ExitPlanMode: "edit",
1328
- Skill: "readonly",
1329
- AskUserQuestion: "readonly",
1330
- ToolSearch: "readonly",
1331
- Bash: "bash",
1332
- Monitor: "bash"
1228
+ const NATIVE_TOOL_KINDS = {
1229
+ Read: "readonly",
1230
+ Glob: "readonly",
1231
+ Grep: "readonly",
1232
+ WebSearch: "readonly",
1233
+ WebFetch: "readonly",
1234
+ TaskGet: "readonly",
1235
+ TaskList: "readonly",
1236
+ TaskOutput: "readonly",
1237
+ ListMcpResources: "readonly",
1238
+ ReadMcpResource: "readonly",
1239
+ Write: "edit",
1240
+ Edit: "edit",
1241
+ NotebookEdit: "edit",
1242
+ TodoWrite: "edit",
1243
+ TaskCreate: "edit",
1244
+ TaskUpdate: "edit",
1245
+ TaskStop: "edit",
1246
+ EnterWorktree: "edit",
1247
+ ExitWorktree: "edit",
1248
+ ExitPlanMode: "edit",
1249
+ Skill: "readonly",
1250
+ AskUserQuestion: "readonly",
1251
+ ToolSearch: "readonly",
1252
+ Bash: "bash",
1253
+ Monitor: "bash"
1333
1254
  };
1334
1255
  function toCommonName(nativeName) {
1335
- return NATIVE_TO_COMMON[nativeName] ?? nativeName;
1336
- }
1337
- var args = parseArgs(argv.slice(2));
1338
- var workdir = args.workdir;
1339
- var bridgeStateDir = args.bridgeStateDir;
1340
- if (!workdir) {
1341
- emitFatal("Missing --workdir argument.");
1256
+ return NATIVE_TO_COMMON[nativeName] ?? nativeName;
1342
1257
  }
1343
- if (!bridgeStateDir) {
1344
- emitFatal("Missing --bridge-state-dir argument.");
1345
- }
1346
- var claudeSdk = claudeAgentSdk;
1347
- var mcpModule = mcpServerModule;
1348
- var lastClaudeSessionId;
1258
+ const args = parseArgs(argv.slice(2));
1259
+ const workdir = args.workdir;
1260
+ const bridgeStateDir = args.bridgeStateDir;
1261
+ if (!workdir) emitFatal("Missing --workdir argument.");
1262
+ if (!bridgeStateDir) emitFatal("Missing --bridge-state-dir argument.");
1263
+ const claudeSdk = claudeAgentSdk;
1264
+ const mcpModule = mcpServerModule;
1265
+ /**
1266
+ * The Claude session id most recently reported by the SDK, captured from the
1267
+ * message stream. Every turn in this bridge process may fork a new id (the
1268
+ * SDK's `continue`/`resume` create a new session linked to the previous one),
1269
+ * so the latest observation is the one a later resume must name.
1270
+ */
1271
+ let lastClaudeSessionId;
1349
1272
  await runBridge({
1350
- bridgeType: "claude-code",
1351
- bridgeStateDir,
1352
- onStart: runTurn,
1353
- // Claude Code's conversation state lives in the runtime's own store, keyed
1354
- // by working directory. The resume payload names the exact conversation so a
1355
- // later resume does not have to fall back to "most recent in this workdir".
1356
- onStop: () => lastClaudeSessionId == null ? {} : { claudeSessionId: lastClaudeSessionId }
1273
+ bridgeType: "claude-code",
1274
+ bridgeStateDir,
1275
+ onStart: runTurn,
1276
+ onStop: () => lastClaudeSessionId == null ? {} : { claudeSessionId: lastClaudeSessionId }
1357
1277
  });
1358
1278
  function createPermissionOptions(input) {
1359
- const permissionMode = input.start.permissionMode ?? "allow-all";
1360
- const inactiveNativeTools = new Set(input.inactiveNativeTools);
1361
- const permissionSettings = createPermissionSettings({
1362
- permissionMode,
1363
- inactiveNativeTools
1364
- });
1365
- const baseOptions = {
1366
- permissionMode: permissionMode === "allow-all" ? "bypassPermissions" : permissionMode === "allow-edits" ? "acceptEdits" : "default",
1367
- allowDangerouslySkipPermissions: permissionMode === "allow-all",
1368
- ...permissionSettings ? { settings: permissionSettings } : {}
1369
- };
1370
- if (permissionMode === "allow-all") {
1371
- return {
1372
- ...baseOptions,
1373
- /*
1374
- * Claude Code exposes AskUserQuestion in headless SDK sessions only
1375
- * when a permission prompt tool is configured. The stdio prompt tool
1376
- * preserves that tool surface without supplying the canUseTool callback
1377
- * that bypassPermissions guarantees it will never invoke.
1378
- */
1379
- permissionPromptToolName: "stdio"
1380
- };
1381
- }
1382
- return {
1383
- ...baseOptions,
1384
- canUseTool: async (toolName, toolInput, options) => {
1385
- if (toolName.startsWith("mcp__harness-tools__")) {
1386
- return { behavior: "allow", updatedInput: toolInput };
1387
- }
1388
- if (!inactiveNativeTools.has(toolName) && !nativeToolRequiresApproval({
1389
- nativeName: toolName,
1390
- permissionMode
1391
- })) {
1392
- return { behavior: "allow", updatedInput: toolInput };
1393
- }
1394
- const approvalId = options.toolUseID;
1395
- input.approvalRequestedToolUseIds.add(approvalId);
1396
- input.nativeToolCallNames.set(approvalId, toolName);
1397
- input.emit({
1398
- type: "tool-call",
1399
- toolCallId: approvalId,
1400
- toolName: toCommonName(toolName),
1401
- nativeName: toolName,
1402
- input: JSON.stringify(toolInput ?? {}),
1403
- providerExecuted: true,
1404
- ...isExternalMcpTool(toolName) ? { dynamic: true } : {}
1405
- });
1406
- input.emit({
1407
- type: "tool-approval-request",
1408
- approvalId,
1409
- toolCallId: approvalId
1410
- });
1411
- input.finishApprovalStep(approvalId);
1412
- const decision = await input.turn.requestToolApproval(approvalId);
1413
- return decision.approved ? { behavior: "allow", updatedInput: toolInput, toolUseID: approvalId } : {
1414
- behavior: "deny",
1415
- message: decision.reason ?? "Denied",
1416
- toolUseID: approvalId
1417
- };
1418
- }
1419
- };
1279
+ const permissionMode = input.start.permissionMode ?? "allow-all";
1280
+ const inactiveNativeTools = new Set(input.inactiveNativeTools);
1281
+ const permissionSettings = createPermissionSettings({
1282
+ permissionMode,
1283
+ inactiveNativeTools
1284
+ });
1285
+ const baseOptions = {
1286
+ permissionMode: permissionMode === "allow-all" ? "bypassPermissions" : permissionMode === "allow-edits" ? "acceptEdits" : "default",
1287
+ allowDangerouslySkipPermissions: permissionMode === "allow-all",
1288
+ ...permissionSettings ? { settings: permissionSettings } : {}
1289
+ };
1290
+ if (permissionMode === "allow-all") return {
1291
+ ...baseOptions,
1292
+ permissionPromptToolName: "stdio"
1293
+ };
1294
+ return {
1295
+ ...baseOptions,
1296
+ canUseTool: async (toolName, toolInput, options) => {
1297
+ if (toolName.startsWith("mcp__harness-tools__")) return {
1298
+ behavior: "allow",
1299
+ updatedInput: toolInput
1300
+ };
1301
+ if (!inactiveNativeTools.has(toolName) && !nativeToolRequiresApproval({
1302
+ nativeName: toolName,
1303
+ permissionMode
1304
+ })) return {
1305
+ behavior: "allow",
1306
+ updatedInput: toolInput
1307
+ };
1308
+ const approvalId = options.toolUseID;
1309
+ input.approvalRequestedToolUseIds.add(approvalId);
1310
+ input.nativeToolCallNames.set(approvalId, toolName);
1311
+ input.emit({
1312
+ type: "tool-call",
1313
+ toolCallId: approvalId,
1314
+ toolName: toCommonName(toolName),
1315
+ nativeName: toolName,
1316
+ input: JSON.stringify(toolInput ?? {}),
1317
+ providerExecuted: true,
1318
+ ...isExternalMcpTool(toolName) ? { dynamic: true } : {}
1319
+ });
1320
+ input.emit({
1321
+ type: "tool-approval-request",
1322
+ approvalId,
1323
+ toolCallId: approvalId
1324
+ });
1325
+ input.finishApprovalStep(approvalId);
1326
+ const decision = await input.turn.requestToolApproval(approvalId);
1327
+ return decision.approved ? {
1328
+ behavior: "allow",
1329
+ updatedInput: toolInput,
1330
+ toolUseID: approvalId
1331
+ } : {
1332
+ behavior: "deny",
1333
+ message: decision.reason ?? "Denied",
1334
+ toolUseID: approvalId
1335
+ };
1336
+ }
1337
+ };
1420
1338
  }
1421
1339
  function createQuestionPreToolUseHook(input) {
1422
- return async (hookInput, toolUseID) => {
1423
- if (hookInput.hook_event_name !== "PreToolUse" || hookInput.tool_name !== "AskUserQuestion") {
1424
- return {};
1425
- }
1426
- const nativeInput = hookInput.tool_input;
1427
- const canonicalInput = toHarnessQuestionsInput(nativeInput);
1428
- const toolCallId = toolUseID ?? hookInput.tool_use_id;
1429
- input.nativeToolCallNames.set(toolCallId, hookInput.tool_name);
1430
- input.emit({
1431
- type: "tool-call",
1432
- toolCallId,
1433
- toolName: "askUserQuestions",
1434
- nativeName: hookInput.tool_name,
1435
- input: JSON.stringify(canonicalInput),
1436
- providerExecuted: false,
1437
- providerMetadata: {
1438
- "claude-code": {
1439
- nativeRequest: nativeInput
1440
- }
1441
- }
1442
- });
1443
- const questionKey = claudeCodeQuestionKey(nativeInput);
1444
- const result = await input.turn.requestToolResult({
1445
- toolCallId,
1446
- matches: (candidate) => {
1447
- const nativeRequest = candidate.toolResult?.providerOptions?.["claude-code"]?.nativeRequest;
1448
- return nativeRequest != null && claudeCodeQuestionKey(nativeRequest) === questionKey;
1449
- }
1450
- });
1451
- const nativeResult = toClaudeCodeQuestionResult({
1452
- nativeInput,
1453
- output: result.output
1454
- });
1455
- return {
1456
- hookSpecificOutput: {
1457
- hookEventName: "PreToolUse",
1458
- ...nativeResult.behavior === "allow" ? {
1459
- permissionDecision: "allow",
1460
- updatedInput: {
1461
- ...nativeResult.updatedInput
1462
- }
1463
- } : {
1464
- permissionDecision: "deny",
1465
- permissionDecisionReason: nativeResult.message
1466
- }
1467
- }
1468
- };
1469
- };
1340
+ return async (hookInput, toolUseID) => {
1341
+ if (hookInput.hook_event_name !== "PreToolUse" || hookInput.tool_name !== "AskUserQuestion") return {};
1342
+ const nativeInput = hookInput.tool_input;
1343
+ const canonicalInput = toHarnessQuestionsInput(nativeInput);
1344
+ const toolCallId = toolUseID ?? hookInput.tool_use_id;
1345
+ input.nativeToolCallNames.set(toolCallId, hookInput.tool_name);
1346
+ input.emit({
1347
+ type: "tool-call",
1348
+ toolCallId,
1349
+ toolName: "askUserQuestions",
1350
+ nativeName: hookInput.tool_name,
1351
+ input: JSON.stringify(canonicalInput),
1352
+ providerExecuted: false,
1353
+ providerMetadata: { "claude-code": { nativeRequest: nativeInput } }
1354
+ });
1355
+ const questionKey = claudeCodeQuestionKey(nativeInput);
1356
+ const nativeResult = toClaudeCodeQuestionResult({
1357
+ nativeInput,
1358
+ output: (await input.turn.requestToolResult({
1359
+ toolCallId,
1360
+ matches: (candidate) => {
1361
+ const nativeRequest = candidate.toolResult?.providerOptions?.["claude-code"]?.nativeRequest;
1362
+ return nativeRequest != null && claudeCodeQuestionKey(nativeRequest) === questionKey;
1363
+ }
1364
+ })).output
1365
+ });
1366
+ return { hookSpecificOutput: {
1367
+ hookEventName: "PreToolUse",
1368
+ ...nativeResult.behavior === "allow" ? {
1369
+ permissionDecision: "allow",
1370
+ updatedInput: { ...nativeResult.updatedInput }
1371
+ } : {
1372
+ permissionDecision: "deny",
1373
+ permissionDecisionReason: nativeResult.message
1374
+ }
1375
+ } };
1376
+ };
1470
1377
  }
1471
1378
  function createPermissionSettings(input) {
1472
- const askRules = /* @__PURE__ */ new Set();
1473
- for (const [nativeName, kind] of Object.entries(NATIVE_TOOL_KINDS)) {
1474
- if (input.inactiveNativeTools.has(nativeName) || (input.permissionMode === "allow-reads" ? kind === "edit" || kind === "bash" : input.permissionMode === "allow-edits" ? kind === "bash" : false)) {
1475
- askRules.add(`${nativeName}(*)`);
1476
- }
1477
- }
1478
- if (askRules.size === 0) return void 0;
1479
- return {
1480
- permissions: { ask: [...askRules] },
1481
- sandbox: { autoAllowBashIfSandboxed: false }
1482
- };
1379
+ const askRules = /* @__PURE__ */ new Set();
1380
+ for (const [nativeName, kind] of Object.entries(NATIVE_TOOL_KINDS)) if (input.inactiveNativeTools.has(nativeName) || (input.permissionMode === "allow-reads" ? kind === "edit" || kind === "bash" : input.permissionMode === "allow-edits" ? kind === "bash" : false)) askRules.add(`${nativeName}(*)`);
1381
+ if (askRules.size === 0) return void 0;
1382
+ return {
1383
+ permissions: { ask: [...askRules] },
1384
+ sandbox: { autoAllowBashIfSandboxed: false }
1385
+ };
1483
1386
  }
1484
1387
  function nativeToolRequiresApproval(input) {
1485
- if (input.permissionMode === "allow-all") return false;
1486
- const kind = NATIVE_TOOL_KINDS[input.nativeName] ?? "edit";
1487
- if (input.permissionMode === "allow-edits") return kind === "bash";
1488
- return kind === "edit" || kind === "bash";
1388
+ if (input.permissionMode === "allow-all") return false;
1389
+ const kind = NATIVE_TOOL_KINDS[input.nativeName] ?? "edit";
1390
+ if (input.permissionMode === "allow-edits") return kind === "bash";
1391
+ return kind === "edit" || kind === "bash";
1489
1392
  }
1490
1393
  async function runTurn(start, turn) {
1491
- const emit = (msg) => turn.emit(msg);
1492
- const abortCtl = new AbortController();
1493
- let gracefulAbort;
1494
- let hardAbortTimer;
1495
- const onHostAbort = () => {
1496
- if (gracefulAbort) {
1497
- gracefulAbort();
1498
- } else {
1499
- abortCtl.abort();
1500
- }
1501
- };
1502
- if (turn.abortSignal.aborted) {
1503
- abortCtl.abort();
1504
- } else {
1505
- turn.abortSignal.addEventListener("abort", onHostAbort, { once: true });
1506
- }
1507
- const streamEventState = createClaudeStreamEventState();
1508
- const mcpServers = { ...start.mcpServers };
1509
- if (start.tools && start.tools.length > 0) {
1510
- const server = new mcpModule.McpServer({
1511
- name: "harness-tools",
1512
- version: "1.0.0"
1513
- });
1514
- for (const tool of start.tools) {
1515
- server.registerTool(
1516
- tool.name,
1517
- {
1518
- description: tool.description ?? "",
1519
- inputSchema: jsonSchemaToZodObject(tool.inputSchema)
1520
- },
1521
- async (...handlerArgs) => {
1522
- const [input, extra] = handlerArgs;
1523
- const metadataToolCallId = extra._meta?.["claudecode/toolUseId"];
1524
- const toolCallId = typeof metadataToolCallId === "string" ? metadataToolCallId : randomUUID3();
1525
- emit({
1526
- type: "tool-call",
1527
- toolCallId,
1528
- toolName: tool.name,
1529
- input: JSON.stringify(input),
1530
- providerExecuted: false
1531
- });
1532
- const { output, isError } = await turn.requestToolResult(toolCallId);
1533
- emit({
1534
- type: "tool-result",
1535
- toolCallId,
1536
- toolName: tool.name,
1537
- result: output ?? null,
1538
- isError: !!isError
1539
- });
1540
- return {
1541
- content: [{ type: "text", text: JSON.stringify(output ?? null) }],
1542
- isError
1543
- };
1544
- }
1545
- );
1546
- }
1547
- mcpServers["harness-tools"] = {
1548
- type: "sdk",
1549
- name: "harness-tools",
1550
- instance: server
1551
- };
1552
- }
1553
- const compaction = createCompactionLatch((event) => emit(event));
1554
- const queryInput = createQueryInput({
1555
- initialUserMessage: start.prompt,
1556
- userMessages: turn.experimental_userMessages,
1557
- abortSignal: abortCtl.signal
1558
- });
1559
- const skillsOption = toClaudeSkillsOption(start.skills);
1560
- const nativeTools = resolveNativeTools(start.builtinToolFiltering);
1561
- const inactiveNativeTools = resolveInactiveNativeTools(
1562
- start.builtinToolFiltering
1563
- );
1564
- const permissionOptions = createPermissionOptions({
1565
- start,
1566
- inactiveNativeTools,
1567
- turn,
1568
- emit,
1569
- finishApprovalStep: (approvalId) => {
1570
- finishApprovalStep({ state: streamEventState, emit, approvalId });
1571
- },
1572
- nativeToolCallNames: streamEventState.nativeToolCallNames,
1573
- approvalRequestedToolUseIds: streamEventState.approvalRequestedToolUseIds
1574
- });
1575
- const questionPreToolUseHook = createQuestionPreToolUseHook({
1576
- turn,
1577
- emit,
1578
- nativeToolCallNames: streamEventState.nativeToolCallNames
1579
- });
1580
- const q = claudeSdk.query({
1581
- prompt: queryInput.input,
1582
- options: {
1583
- ...start.model ? { model: start.model } : {},
1584
- ...start.maxTurns !== void 0 ? { maxTurns: start.maxTurns } : {},
1585
- ...start.env !== void 0 ? { env: { ...procEnv2, ...start.env } } : {},
1586
- ...skillsOption ? { skills: skillsOption } : {},
1587
- ...nativeTools !== void 0 ? { tools: nativeTools } : {},
1588
- ...inactiveNativeTools.length > 0 ? { disallowedTools: inactiveNativeTools } : {},
1589
- systemPrompt: createClaudeCodeSystemPrompt(start.instructions),
1590
- thinking: start.thinking,
1591
- ...start.effort !== void 0 ? { effort: start.effort } : {},
1592
- ...start.responseFormat?.type === "json" && start.responseFormat.schema != null ? {
1593
- outputFormat: {
1594
- type: "json_schema",
1595
- schema: start.responseFormat.schema
1596
- }
1597
- } : {},
1598
- includePartialMessages: true,
1599
- // The `PostCompact` hook carries the compaction summary, which the
1600
- // `compact_boundary` system message does not. Latch it for the unified
1601
- // `compaction` event; return an empty output so compaction proceeds.
1602
- hooks: {
1603
- PreToolUse: [
1604
- {
1605
- matcher: "AskUserQuestion",
1606
- hooks: [questionPreToolUseHook]
1607
- }
1608
- ],
1609
- PostCompact: [
1610
- {
1611
- hooks: [
1612
- async (input) => {
1613
- if (typeof input?.compact_summary === "string") {
1614
- compaction.onSummary(input.compact_summary);
1615
- }
1616
- return {};
1617
- }
1618
- ]
1619
- }
1620
- ]
1621
- },
1622
- // Continuation rule, most specific first.
1623
- //
1624
- // `resumeSessionId` names the exact conversation and is what a
1625
- // cross-process resume should use: `continue` means "most recent thread
1626
- // in this workdir", which silently picks the wrong one once anything
1627
- // else has run there. The bridge also retains the id observed during its
1628
- // previous query, so every later query stays pinned to that conversation
1629
- // even when the host detached and reattached between turns. `resume` and
1630
- // `continue` are mutually exclusive in the SDK.
1631
- //
1632
- // Otherwise the host can force-continue by setting `start.continue`,
1633
- // and turns after the first fall back to the legacy cwd-based behavior
1634
- // when no exact id was observed.
1635
- ...start.resumeSessionId ?? lastClaudeSessionId ? { resume: start.resumeSessionId ?? lastClaudeSessionId } : start.continue === true || !turn.firstTurn ? { continue: true } : {},
1636
- ...permissionOptions,
1637
- mcpServers,
1638
- cwd: workdir,
1639
- abortSignal: abortCtl.signal
1640
- }
1641
- });
1642
- gracefulAbort = () => {
1643
- hardAbortTimer = setTimeout(() => abortCtl.abort(), 5e3);
1644
- hardAbortTimer.unref?.();
1645
- void Promise.resolve().then(() => q.interrupt()).catch(() => abortCtl.abort());
1646
- };
1647
- let turnUsage;
1648
- let totalCostUsd;
1649
- let emittedTerminalError = false;
1650
- let emittedTerminalFinish = false;
1651
- const emitTerminalError = (message) => {
1652
- const normalized = message?.trim();
1653
- if (!normalized || emittedTerminalError || emittedTerminalFinish) return;
1654
- streamEventState.observedTerminalError = normalized;
1655
- emittedTerminalError = true;
1656
- if (!turn.abortSignal.aborted) {
1657
- turn.emitError({
1658
- error: normalized,
1659
- message: "claude-code terminal error"
1660
- });
1661
- }
1662
- queryInput.close();
1663
- abortCtl.abort();
1664
- };
1665
- const emitStreamEvent = createEmitStreamEvent({
1666
- state: streamEventState,
1667
- emit,
1668
- emitWarning: turn.emitWarning,
1669
- emitTerminalError,
1670
- onCompactionBoundary: (boundary) => compaction.onBoundary(boundary),
1671
- toCommonName
1672
- });
1673
- try {
1674
- for await (const msg of q) {
1675
- if (abortCtl.signal.aborted) break;
1676
- const type = msg.type;
1677
- if (type === "command_lifecycle") {
1678
- queryInput.handleLifecycle(msg);
1679
- }
1680
- const sessionId = msg.session_id;
1681
- if (typeof sessionId === "string" && sessionId.length > 0) {
1682
- lastClaudeSessionId = sessionId;
1683
- }
1684
- emitStreamEvent(msg);
1685
- if (type === "result") {
1686
- if (msg.subtype === "success") {
1687
- if (msg.is_error) {
1688
- emitTerminalError(
1689
- msg.result?.trim() || streamEventState.observedTerminalError || (typeof msg.api_error_status === "number" ? `Claude Code reported an API error (HTTP ${msg.api_error_status})` : "Claude Code reported a failed result")
1690
- );
1691
- continue;
1692
- }
1693
- const emptyResult = !msg.result?.trim?.();
1694
- if (emptyResult && streamEventState.observedTerminalError) {
1695
- emitTerminalError(streamEventState.observedTerminalError);
1696
- continue;
1697
- }
1698
- const usage = msg.usage ?? msg.message?.usage;
1699
- const harnessUsage = mapUsage(usage);
1700
- if (harnessUsage) turnUsage = addUsage(turnUsage, harnessUsage);
1701
- if (typeof msg.total_cost_usd === "number") {
1702
- totalCostUsd = (totalCostUsd ?? 0) + msg.total_cost_usd;
1703
- }
1704
- if (start.responseFormat?.type === "json" && msg.structured_output !== void 0) {
1705
- const id = randomUUID3();
1706
- emit({ type: "text-start", id });
1707
- emit({
1708
- type: "text-delta",
1709
- id,
1710
- delta: JSON.stringify(msg.structured_output)
1711
- });
1712
- emit({ type: "text-end", id });
1713
- streamEventState.stepOpen = true;
1714
- }
1715
- if (streamEventState.stepOpen) {
1716
- emitFinishStep({
1717
- state: streamEventState,
1718
- emit,
1719
- usage: streamEventState.pendingStepUsage ?? harnessUsage
1720
- });
1721
- }
1722
- queryInput.observeResult();
1723
- if (!queryInput.hasActiveUserMessages()) {
1724
- queryInput.close();
1725
- break;
1726
- }
1727
- } else {
1728
- emitTerminalError(
1729
- (Array.isArray(msg.errors) ? msg.errors.join("\n") : void 0) || streamEventState.observedTerminalError || msg.result || "Unknown error"
1730
- );
1731
- }
1732
- continue;
1733
- }
1734
- if (queryInput.hasObservedResult && !queryInput.hasActiveUserMessages()) {
1735
- queryInput.close();
1736
- break;
1737
- }
1738
- }
1739
- } catch (err) {
1740
- if (!turn.abortSignal.aborted && !(abortCtl.signal.aborted && emittedTerminalError)) {
1741
- turn.emitError({ error: err, message: "claude-code turn failed" });
1742
- }
1743
- return;
1744
- } finally {
1745
- gracefulAbort = void 0;
1746
- if (hardAbortTimer != null) clearTimeout(hardAbortTimer);
1747
- turn.abortSignal.removeEventListener("abort", onHostAbort);
1748
- queryInput.close();
1749
- try {
1750
- await q.return?.(
1751
- void 0
1752
- );
1753
- } catch {
1754
- }
1755
- }
1756
- if (emittedTerminalError) return;
1757
- emittedTerminalFinish = true;
1758
- void emittedTerminalFinish;
1759
- emit({
1760
- type: "finish",
1761
- finishReason: { unified: "stop", raw: "stop" },
1762
- totalUsage: turnUsage ?? streamEventState.stepUsage ?? defaultUsage(),
1763
- ...totalCostUsd !== void 0 || lastClaudeSessionId !== void 0 ? {
1764
- harnessMetadata: {
1765
- "claude-code": {
1766
- ...totalCostUsd !== void 0 ? { costUsd: totalCostUsd } : {},
1767
- // The conversation this turn belongs to, resumable outside the
1768
- // SDK with `claude --resume <sessionId>` and captured by the
1769
- // adapter for exact cross-process resume.
1770
- ...lastClaudeSessionId !== void 0 ? { sessionId: lastClaudeSessionId } : {}
1771
- }
1772
- }
1773
- } : {}
1774
- });
1394
+ const emit = (msg) => turn.emit(msg);
1395
+ const abortCtl = new AbortController();
1396
+ let gracefulAbort;
1397
+ let hardAbortTimer;
1398
+ const onHostAbort = () => {
1399
+ if (gracefulAbort) gracefulAbort();
1400
+ else abortCtl.abort();
1401
+ };
1402
+ if (turn.abortSignal.aborted) abortCtl.abort();
1403
+ else turn.abortSignal.addEventListener("abort", onHostAbort, { once: true });
1404
+ const streamEventState = createClaudeStreamEventState();
1405
+ const mcpServers = { ...start.mcpServers };
1406
+ if (start.tools && start.tools.length > 0) {
1407
+ const server = new mcpModule.McpServer({
1408
+ name: "harness-tools",
1409
+ version: "1.0.0"
1410
+ });
1411
+ for (const tool of start.tools) server.registerTool(tool.name, {
1412
+ description: tool.description ?? "",
1413
+ inputSchema: jsonSchemaToZodObject(tool.inputSchema)
1414
+ }, async (...handlerArgs) => {
1415
+ const [input, extra] = handlerArgs;
1416
+ const metadataToolCallId = extra._meta?.["claudecode/toolUseId"];
1417
+ const toolCallId = typeof metadataToolCallId === "string" ? metadataToolCallId : randomUUID();
1418
+ emit({
1419
+ type: "tool-call",
1420
+ toolCallId,
1421
+ toolName: tool.name,
1422
+ input: JSON.stringify(input),
1423
+ providerExecuted: false
1424
+ });
1425
+ const { output, isError } = await turn.requestToolResult(toolCallId);
1426
+ emit({
1427
+ type: "tool-result",
1428
+ toolCallId,
1429
+ toolName: tool.name,
1430
+ result: output ?? null,
1431
+ isError: !!isError
1432
+ });
1433
+ return {
1434
+ content: [{
1435
+ type: "text",
1436
+ text: JSON.stringify(output ?? null)
1437
+ }],
1438
+ isError
1439
+ };
1440
+ });
1441
+ mcpServers["harness-tools"] = {
1442
+ type: "sdk",
1443
+ name: "harness-tools",
1444
+ instance: server
1445
+ };
1446
+ }
1447
+ const compaction = createCompactionLatch((event) => emit(event));
1448
+ const queryInput = createQueryInput({
1449
+ initialUserMessage: start.prompt,
1450
+ userMessages: turn.experimental_userMessages,
1451
+ abortSignal: abortCtl.signal
1452
+ });
1453
+ const skillsOption = toClaudeSkillsOption(start.skills);
1454
+ const nativeTools = resolveNativeTools(start.builtinToolFiltering);
1455
+ const inactiveNativeTools = resolveInactiveNativeTools(start.builtinToolFiltering);
1456
+ const permissionOptions = createPermissionOptions({
1457
+ start,
1458
+ inactiveNativeTools,
1459
+ turn,
1460
+ emit,
1461
+ finishApprovalStep: (approvalId) => {
1462
+ finishApprovalStep({
1463
+ state: streamEventState,
1464
+ emit,
1465
+ approvalId
1466
+ });
1467
+ },
1468
+ nativeToolCallNames: streamEventState.nativeToolCallNames,
1469
+ approvalRequestedToolUseIds: streamEventState.approvalRequestedToolUseIds
1470
+ });
1471
+ const questionPreToolUseHook = createQuestionPreToolUseHook({
1472
+ turn,
1473
+ emit,
1474
+ nativeToolCallNames: streamEventState.nativeToolCallNames
1475
+ });
1476
+ const q = claudeSdk.query({
1477
+ prompt: queryInput.input,
1478
+ options: {
1479
+ ...start.model ? { model: start.model } : {},
1480
+ ...start.maxTurns !== void 0 ? { maxTurns: start.maxTurns } : {},
1481
+ ...start.agentProgressSummaries !== void 0 ? { agentProgressSummaries: start.agentProgressSummaries } : {},
1482
+ ...start.forwardSubagentText !== void 0 ? { forwardSubagentText: start.forwardSubagentText } : {},
1483
+ ...start.env !== void 0 ? { env: {
1484
+ ...env,
1485
+ ...start.env
1486
+ } } : {},
1487
+ ...skillsOption ? { skills: skillsOption } : {},
1488
+ ...nativeTools !== void 0 ? { tools: nativeTools } : {},
1489
+ ...inactiveNativeTools.length > 0 ? { disallowedTools: inactiveNativeTools } : {},
1490
+ systemPrompt: createClaudeCodeSystemPrompt(start.instructions),
1491
+ thinking: start.thinking,
1492
+ ...start.effort !== void 0 ? { effort: start.effort } : {},
1493
+ ...start.responseFormat?.type === "json" && start.responseFormat.schema != null ? { outputFormat: {
1494
+ type: "json_schema",
1495
+ schema: start.responseFormat.schema
1496
+ } } : {},
1497
+ includePartialMessages: true,
1498
+ hooks: {
1499
+ PreToolUse: [{
1500
+ matcher: "AskUserQuestion",
1501
+ hooks: [questionPreToolUseHook]
1502
+ }],
1503
+ PostCompact: [{ hooks: [async (input) => {
1504
+ if (typeof input?.compact_summary === "string") compaction.onSummary(input.compact_summary);
1505
+ return {};
1506
+ }] }]
1507
+ },
1508
+ ...start.resumeSessionId ?? lastClaudeSessionId ? { resume: start.resumeSessionId ?? lastClaudeSessionId } : start.continue === true || !turn.firstTurn ? { continue: true } : {},
1509
+ ...permissionOptions,
1510
+ mcpServers,
1511
+ cwd: workdir,
1512
+ abortSignal: abortCtl.signal
1513
+ }
1514
+ });
1515
+ gracefulAbort = () => {
1516
+ hardAbortTimer = setTimeout(() => abortCtl.abort(), 5e3);
1517
+ hardAbortTimer.unref?.();
1518
+ Promise.resolve().then(() => q.interrupt()).catch(() => abortCtl.abort());
1519
+ };
1520
+ let turnUsage;
1521
+ let totalCostUsd;
1522
+ let emittedTerminalError = false;
1523
+ let emittedTerminalFinish = false;
1524
+ const emitTerminalError = (message) => {
1525
+ const normalized = message?.trim();
1526
+ if (!normalized || emittedTerminalError || emittedTerminalFinish) return;
1527
+ streamEventState.observedTerminalError = normalized;
1528
+ emittedTerminalError = true;
1529
+ if (!turn.abortSignal.aborted) turn.emitError({
1530
+ error: normalized,
1531
+ message: "claude-code terminal error"
1532
+ });
1533
+ queryInput.close();
1534
+ abortCtl.abort();
1535
+ };
1536
+ const emitStreamEvent = createEmitStreamEvent({
1537
+ state: streamEventState,
1538
+ emit,
1539
+ emitWarning: turn.emitWarning,
1540
+ emitTerminalError,
1541
+ onCompactionBoundary: (boundary) => compaction.onBoundary(boundary),
1542
+ toCommonName
1543
+ });
1544
+ try {
1545
+ for await (const msg of q) {
1546
+ if (abortCtl.signal.aborted) break;
1547
+ const type = msg.type;
1548
+ if (type === "command_lifecycle") queryInput.handleLifecycle(msg);
1549
+ const sessionId = msg.session_id;
1550
+ if (typeof sessionId === "string" && sessionId.length > 0) lastClaudeSessionId = sessionId;
1551
+ emitStreamEvent(msg);
1552
+ if (type === "result") {
1553
+ if (msg.subtype === "success") {
1554
+ if (msg.is_error) {
1555
+ emitTerminalError(msg.result?.trim() || streamEventState.observedTerminalError || (typeof msg.api_error_status === "number" ? `Claude Code reported an API error (HTTP ${msg.api_error_status})` : "Claude Code reported a failed result"));
1556
+ continue;
1557
+ }
1558
+ if (!msg.result?.trim?.() && streamEventState.observedTerminalError) {
1559
+ emitTerminalError(streamEventState.observedTerminalError);
1560
+ continue;
1561
+ }
1562
+ const harnessUsage = mapUsage(msg.usage ?? msg.message?.usage);
1563
+ if (harnessUsage) turnUsage = addUsage(turnUsage, harnessUsage);
1564
+ if (typeof msg.total_cost_usd === "number") totalCostUsd = (totalCostUsd ?? 0) + msg.total_cost_usd;
1565
+ if (start.responseFormat?.type === "json" && msg.structured_output !== void 0) {
1566
+ const id = randomUUID();
1567
+ emit({
1568
+ type: "text-start",
1569
+ id
1570
+ });
1571
+ emit({
1572
+ type: "text-delta",
1573
+ id,
1574
+ delta: JSON.stringify(msg.structured_output)
1575
+ });
1576
+ emit({
1577
+ type: "text-end",
1578
+ id
1579
+ });
1580
+ streamEventState.stepOpen = true;
1581
+ }
1582
+ if (streamEventState.stepOpen) emitFinishStep({
1583
+ state: streamEventState,
1584
+ emit,
1585
+ usage: streamEventState.pendingStepUsage ?? harnessUsage
1586
+ });
1587
+ queryInput.observeResult();
1588
+ if (!queryInput.hasActiveUserMessages()) {
1589
+ queryInput.close();
1590
+ break;
1591
+ }
1592
+ } else emitTerminalError((Array.isArray(msg.errors) ? msg.errors.join("\n") : void 0) || streamEventState.observedTerminalError || msg.result || "Unknown error");
1593
+ continue;
1594
+ }
1595
+ if (queryInput.hasObservedResult && !queryInput.hasActiveUserMessages()) {
1596
+ queryInput.close();
1597
+ break;
1598
+ }
1599
+ }
1600
+ } catch (err) {
1601
+ if (!turn.abortSignal.aborted && !(abortCtl.signal.aborted && emittedTerminalError)) turn.emitError({
1602
+ error: err,
1603
+ message: "claude-code turn failed"
1604
+ });
1605
+ return;
1606
+ } finally {
1607
+ gracefulAbort = void 0;
1608
+ if (hardAbortTimer != null) clearTimeout(hardAbortTimer);
1609
+ turn.abortSignal.removeEventListener("abort", onHostAbort);
1610
+ queryInput.close();
1611
+ try {
1612
+ await q.return?.(void 0);
1613
+ } catch {}
1614
+ }
1615
+ if (emittedTerminalError) return;
1616
+ emittedTerminalFinish = true;
1617
+ emit({
1618
+ type: "finish",
1619
+ finishReason: {
1620
+ unified: "stop",
1621
+ raw: "stop"
1622
+ },
1623
+ totalUsage: turnUsage ?? streamEventState.stepUsage ?? defaultUsage(),
1624
+ ...totalCostUsd !== void 0 || lastClaudeSessionId !== void 0 ? { harnessMetadata: { "claude-code": {
1625
+ ...totalCostUsd !== void 0 ? { costUsd: totalCostUsd } : {},
1626
+ ...lastClaudeSessionId !== void 0 ? { sessionId: lastClaudeSessionId } : {}
1627
+ } } } : {}
1628
+ });
1775
1629
  }
1776
- function createQueryInput({
1777
- initialUserMessage,
1778
- userMessages,
1779
- abortSignal
1780
- }) {
1781
- let closed = false;
1782
- let observedResult = false;
1783
- const submittedMessages = /* @__PURE__ */ new Map();
1784
- const close = (error) => {
1785
- if (closed) return;
1786
- closed = true;
1787
- userMessages.close(error);
1788
- };
1789
- if (abortSignal.aborted) {
1790
- close(abortSignal.reason);
1791
- } else {
1792
- abortSignal.addEventListener("abort", () => close(abortSignal.reason), {
1793
- once: true
1794
- });
1795
- }
1796
- const toUserMessage = (options) => ({
1797
- type: "user",
1798
- message: {
1799
- role: "user",
1800
- content: [{ type: "text", text: options.text }]
1801
- },
1802
- parent_tool_use_id: null,
1803
- uuid: options.messageId,
1804
- ...options.priority == null ? {} : { priority: options.priority }
1805
- });
1806
- const messageIterator = userMessages[Symbol.asyncIterator]();
1807
- return {
1808
- close,
1809
- handleLifecycle: (message) => {
1810
- const lifecycle = message;
1811
- if (lifecycle.command_uuid == null || lifecycle.state == null) return;
1812
- const submitted = submittedMessages.get(lifecycle.command_uuid);
1813
- if (submitted == null) return;
1814
- if (lifecycle.state === "queued" || lifecycle.state === "started") {
1815
- submitted.accept();
1816
- return;
1817
- }
1818
- if (lifecycle.state === "cancelled" || lifecycle.state === "discarded") {
1819
- submitted.reject(
1820
- new Error(`Claude Code ${lifecycle.state} the user message.`)
1821
- );
1822
- }
1823
- submittedMessages.delete(lifecycle.command_uuid);
1824
- },
1825
- hasActiveUserMessages: () => submittedMessages.size > 0 || userMessages.pendingCount > 0,
1826
- observeResult: () => {
1827
- observedResult = true;
1828
- },
1829
- get hasObservedResult() {
1830
- return observedResult;
1831
- },
1832
- input: {
1833
- [Symbol.asyncIterator]() {
1834
- let sentInitial = false;
1835
- return {
1836
- async next() {
1837
- if (closed || abortSignal.aborted) {
1838
- return {
1839
- value: void 0,
1840
- done: true
1841
- };
1842
- }
1843
- if (!sentInitial) {
1844
- sentInitial = true;
1845
- return {
1846
- value: toUserMessage({
1847
- text: initialUserMessage,
1848
- messageId: randomUUID3()
1849
- }),
1850
- done: false
1851
- };
1852
- }
1853
- const nextMessage = await messageIterator.next();
1854
- if (nextMessage.done) {
1855
- return {
1856
- value: void 0,
1857
- done: true
1858
- };
1859
- }
1860
- submittedMessages.set(
1861
- nextMessage.value.messageId,
1862
- nextMessage.value
1863
- );
1864
- return {
1865
- value: toUserMessage({
1866
- text: nextMessage.value.text,
1867
- messageId: nextMessage.value.messageId,
1868
- priority: "next"
1869
- }),
1870
- done: false
1871
- };
1872
- }
1873
- };
1874
- }
1875
- }
1876
- };
1630
+ function createQueryInput({ initialUserMessage, userMessages, abortSignal }) {
1631
+ let closed = false;
1632
+ let observedResult = false;
1633
+ const submittedMessages = /* @__PURE__ */ new Map();
1634
+ const close = (error) => {
1635
+ if (closed) return;
1636
+ closed = true;
1637
+ userMessages.close(error);
1638
+ };
1639
+ if (abortSignal.aborted) close(abortSignal.reason);
1640
+ else abortSignal.addEventListener("abort", () => close(abortSignal.reason), { once: true });
1641
+ const toUserMessage = (options) => ({
1642
+ type: "user",
1643
+ message: {
1644
+ role: "user",
1645
+ content: [{
1646
+ type: "text",
1647
+ text: options.text
1648
+ }]
1649
+ },
1650
+ parent_tool_use_id: null,
1651
+ uuid: options.messageId,
1652
+ ...options.priority == null ? {} : { priority: options.priority }
1653
+ });
1654
+ const messageIterator = userMessages[Symbol.asyncIterator]();
1655
+ return {
1656
+ close,
1657
+ handleLifecycle: (message) => {
1658
+ const lifecycle = message;
1659
+ if (lifecycle.command_uuid == null || lifecycle.state == null) return;
1660
+ const submitted = submittedMessages.get(lifecycle.command_uuid);
1661
+ if (submitted == null) return;
1662
+ if (lifecycle.state === "queued" || lifecycle.state === "started") {
1663
+ submitted.accept();
1664
+ return;
1665
+ }
1666
+ if (lifecycle.state === "cancelled" || lifecycle.state === "discarded") submitted.reject(/* @__PURE__ */ new Error(`Claude Code ${lifecycle.state} the user message.`));
1667
+ submittedMessages.delete(lifecycle.command_uuid);
1668
+ },
1669
+ hasActiveUserMessages: () => submittedMessages.size > 0 || userMessages.pendingCount > 0,
1670
+ observeResult: () => {
1671
+ observedResult = true;
1672
+ },
1673
+ get hasObservedResult() {
1674
+ return observedResult;
1675
+ },
1676
+ input: { [Symbol.asyncIterator]() {
1677
+ let sentInitial = false;
1678
+ return { async next() {
1679
+ if (closed || abortSignal.aborted) return {
1680
+ value: void 0,
1681
+ done: true
1682
+ };
1683
+ if (!sentInitial) {
1684
+ sentInitial = true;
1685
+ return {
1686
+ value: toUserMessage({
1687
+ text: initialUserMessage,
1688
+ messageId: randomUUID()
1689
+ }),
1690
+ done: false
1691
+ };
1692
+ }
1693
+ const nextMessage = await messageIterator.next();
1694
+ if (nextMessage.done) return {
1695
+ value: void 0,
1696
+ done: true
1697
+ };
1698
+ submittedMessages.set(nextMessage.value.messageId, nextMessage.value);
1699
+ return {
1700
+ value: toUserMessage({
1701
+ text: nextMessage.value.text,
1702
+ messageId: nextMessage.value.messageId,
1703
+ priority: "next"
1704
+ }),
1705
+ done: false
1706
+ };
1707
+ } };
1708
+ } }
1709
+ };
1877
1710
  }
1878
1711
  function addUsage(total, usage) {
1879
- if (total == null) return usage;
1880
- const result = { ...total };
1881
- for (const [key, value] of Object.entries(usage)) {
1882
- const previous = result[key];
1883
- if (typeof value === "number" && typeof previous === "number") {
1884
- result[key] = previous + value;
1885
- } else if (value != null && previous != null && typeof value === "object" && typeof previous === "object" && !Array.isArray(value) && !Array.isArray(previous)) {
1886
- result[key] = addUsage(
1887
- previous,
1888
- value
1889
- );
1890
- } else {
1891
- result[key] = value;
1892
- }
1893
- }
1894
- return result;
1712
+ if (total == null) return usage;
1713
+ const result = { ...total };
1714
+ for (const [key, value] of Object.entries(usage)) {
1715
+ const previous = result[key];
1716
+ if (typeof value === "number" && typeof previous === "number") result[key] = previous + value;
1717
+ else if (value != null && previous != null && typeof value === "object" && typeof previous === "object" && !Array.isArray(value) && !Array.isArray(previous)) result[key] = addUsage(previous, value);
1718
+ else result[key] = value;
1719
+ }
1720
+ return result;
1895
1721
  }
1896
- function parseArgs(args2) {
1897
- const out = {};
1898
- for (let i = 0; i < args2.length; i++) {
1899
- if (args2[i] === "--workdir" && i + 1 < args2.length) {
1900
- out.workdir = args2[++i];
1901
- } else if (args2[i] === "--bridge-state-dir" && i + 1 < args2.length) {
1902
- out.bridgeStateDir = args2[++i];
1903
- }
1904
- }
1905
- return out;
1722
+ function parseArgs(args) {
1723
+ const out = {};
1724
+ for (let i = 0; i < args.length; i++) if (args[i] === "--workdir" && i + 1 < args.length) out.workdir = args[++i];
1725
+ else if (args[i] === "--bridge-state-dir" && i + 1 < args.length) out.bridgeStateDir = args[++i];
1726
+ return out;
1906
1727
  }
1907
1728
  function emitFatal(message) {
1908
- stdout2.write(JSON.stringify({ type: "bridge-fatal", message }) + "\n");
1909
- process.exit(1);
1729
+ stdout.write(JSON.stringify({
1730
+ type: "bridge-fatal",
1731
+ message
1732
+ }) + "\n");
1733
+ process.exit(1);
1910
1734
  }
1735
+ //#endregion
1736
+ export {};
1737
+
1911
1738
  //# sourceMappingURL=index.mjs.map