farming-code 2.2.30 → 2.2.32

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 (377) hide show
  1. package/THIRD_PARTY_NOTICES.md +7 -0
  2. package/backend/acp/{client-services.js → client-services.cjs} +132 -134
  3. package/backend/acp/packaged-claude-acp.cjs +37 -0
  4. package/backend/acp/packaged-codex-acp.cjs +37 -0
  5. package/backend/acp/patch-decisions.cjs +182 -0
  6. package/backend/acp/permission-security.cjs +131 -0
  7. package/backend/{acp-checkpoint-store.js → acp-checkpoint-store.cjs} +90 -59
  8. package/backend/{acp-runtime.js → acp-runtime.cjs} +1220 -1112
  9. package/backend/acp-session-state.cjs +622 -0
  10. package/backend/acp-transcript.cjs +525 -0
  11. package/backend/agent-env.cjs +257 -0
  12. package/backend/agent-extension-discovery.cjs +452 -0
  13. package/backend/agent-extension-inventory.cjs +116 -0
  14. package/backend/agent-json-stream.cjs +283 -0
  15. package/backend/agent-lifecycle-journal.cjs +185 -0
  16. package/backend/{agent-manager.js → agent-manager.cjs} +3116 -4275
  17. package/backend/agent-order-transaction.cjs +85 -0
  18. package/backend/agent-order.cjs +159 -0
  19. package/backend/agent-provider-session.cjs +98 -0
  20. package/backend/agent-runtime-binding.cjs +179 -0
  21. package/backend/agent-session-history.cjs +1022 -0
  22. package/backend/agent-session-inventory.cjs +291 -0
  23. package/backend/async-cache.cjs +104 -0
  24. package/backend/atomic-json-store.cjs +67 -0
  25. package/backend/auth.cjs +261 -0
  26. package/backend/authoritative-inventory-cache.cjs +383 -0
  27. package/backend/business-health.cjs +68 -0
  28. package/backend/chat-runtime.cjs +50 -0
  29. package/backend/claude-settings.cjs +132 -0
  30. package/backend/cli-agents.cjs +435 -0
  31. package/backend/codex-context-window.cjs +196 -0
  32. package/backend/codex-models.cjs +228 -0
  33. package/backend/codex-session-archive.cjs +82 -0
  34. package/backend/codex-session-history.cjs +590 -0
  35. package/backend/{codex-terminal-profile.js → codex-terminal-profile.cjs} +149 -155
  36. package/backend/codex-transcript-sanitizer.cjs +130 -0
  37. package/backend/codex-transcript.cjs +2107 -0
  38. package/backend/command-runner-child.cjs +105 -0
  39. package/backend/config-manager.cjs +1263 -0
  40. package/backend/control-api.cjs +439 -0
  41. package/backend/data/LICENSE.cua +21 -0
  42. package/backend/executable-discovery.cjs +204 -0
  43. package/backend/farming-agent-bootstrap.cjs +81 -0
  44. package/backend/farming-agent-bootstrap.zh_cn.md +6 -2
  45. package/backend/{farming-app-cli.js → farming-app-cli.cjs} +441 -493
  46. package/backend/farming-cli.cjs +414 -0
  47. package/backend/farming-net-pass.cjs +319 -0
  48. package/backend/farming-net-registry.cjs +123 -0
  49. package/backend/farming-net-server.cjs +309 -0
  50. package/backend/farming-session-store.cjs +651 -0
  51. package/backend/git-worktree-info.cjs +181 -0
  52. package/backend/haiku-token.cjs +828 -0
  53. package/backend/index-html.cjs +96 -0
  54. package/backend/input-parts.cjs +68 -0
  55. package/backend/input-routing.cjs +40 -0
  56. package/backend/{local-session-engine.js → local-session-engine.cjs} +276 -282
  57. package/backend/main-agent-skills.cjs +309 -0
  58. package/backend/main-page-session.cjs +132 -0
  59. package/backend/native-pty-controller-generation.cjs +139 -0
  60. package/backend/{native-pty-host-client.js → native-pty-host-client.cjs} +338 -346
  61. package/backend/native-pty-host-identity.cjs +125 -0
  62. package/backend/native-pty-host-path.cjs +68 -0
  63. package/backend/{native-pty-host.js → native-pty-host.cjs} +480 -569
  64. package/backend/native-session-engine.cjs +296 -0
  65. package/backend/network.cjs +57 -0
  66. package/backend/npm-update-helper.cjs +399 -0
  67. package/backend/packaged-node-pty.cjs +120 -0
  68. package/backend/preview-session-manager.cjs +89 -0
  69. package/backend/provider-adapters.cjs +492 -0
  70. package/backend/provider-home-configuration.cjs +211 -0
  71. package/backend/provider-session-id.cjs +79 -0
  72. package/backend/provider-session-service.cjs +333 -0
  73. package/backend/qr-share-tickets.cjs +119 -0
  74. package/backend/resource-broadcast-protocol.cjs +50 -0
  75. package/backend/review-diff-router.cjs +202 -0
  76. package/backend/review-diff-service.cjs +1267 -0
  77. package/backend/review-session-router.cjs +93 -0
  78. package/backend/review-session-service.cjs +470 -0
  79. package/backend/review-session-store.cjs +161 -0
  80. package/backend/review-state-router.cjs +132 -0
  81. package/backend/review-state-store.cjs +299 -0
  82. package/backend/run-history-store.cjs +97 -0
  83. package/backend/runtime-dependency-manager.cjs +893 -0
  84. package/backend/runtime-executable-invocation.cjs +37 -0
  85. package/backend/runtime-observation.cjs +96 -0
  86. package/backend/server-process-identity.cjs +70 -0
  87. package/backend/server.cjs +3356 -0
  88. package/backend/session-engine-bridge.cjs +144 -0
  89. package/backend/session-engine-router.cjs +105 -0
  90. package/backend/session-engine.cjs +64 -0
  91. package/backend/session-stream-protocol.cjs +181 -0
  92. package/backend/shell-busy-integration.cjs +442 -0
  93. package/backend/slash-command-discovery.cjs +423 -0
  94. package/backend/storage-layout.cjs +263 -0
  95. package/backend/system-monitor.cjs +96 -0
  96. package/backend/terminal-attach-checkpoint.cjs +73 -0
  97. package/backend/terminal-exit-quiescence.cjs +57 -0
  98. package/backend/{terminal-reducer-flow-control.js → terminal-reducer-flow-control.cjs} +45 -22
  99. package/backend/terminal-runtime-cleanup.cjs +99 -0
  100. package/backend/{terminal-screen-state.js → terminal-screen-state.cjs} +100 -108
  101. package/backend/{terminal-screen-worker-pool.js → terminal-screen-worker-pool.cjs} +89 -79
  102. package/backend/{terminal-screen-worker-thread.js → terminal-screen-worker-thread.cjs} +64 -65
  103. package/backend/terminal-screen-worker.cjs +352 -0
  104. package/backend/terminal-state-serialization.cjs +147 -0
  105. package/backend/{terminal-status.js → terminal-status.cjs} +137 -187
  106. package/backend/theme-manager.cjs +185 -0
  107. package/backend/{update-service.js → update-service.cjs} +258 -248
  108. package/backend/usage-forecast.cjs +95 -0
  109. package/backend/{usage-history-client.js → usage-history-client.cjs} +144 -94
  110. package/backend/usage-history-smoke.cjs +85 -0
  111. package/backend/usage-history-worker.cjs +60 -0
  112. package/backend/{usage-monitor.js → usage-monitor.cjs} +363 -390
  113. package/backend/workspace-directory.cjs +239 -0
  114. package/backend/workspace-discovery.cjs +271 -0
  115. package/backend/{workspace-file-router.js → workspace-file-router.cjs} +213 -282
  116. package/backend/{workspace-file-service.js → workspace-file-service.cjs} +1139 -1318
  117. package/backend/workspace-root-registry.cjs +192 -0
  118. package/bin/farming +1 -1
  119. package/dist/acp/codex-acp-1.1.4.mjs +18 -1
  120. package/dist/assets/App-BCkdpWB6.css +1 -0
  121. package/dist/assets/App-CRjKpaGp.js +697 -0
  122. package/dist/assets/CodeSelect-Ci8sKZPU.js +1 -0
  123. package/dist/assets/CodeSelect-T2eNzrOc.css +1 -0
  124. package/dist/assets/{FileEditorMarkdownPreview-Cs9cLv8a.js → FileEditorMarkdownPreview-V06QHlAg.js} +55 -55
  125. package/dist/assets/FileEditorPane-BX-Wjg9U.js +2 -0
  126. package/dist/assets/IconGlyphs-YhV7tqd4.js +1 -0
  127. package/dist/assets/ProjectFilesSection-BDT2_r0o.js +12 -0
  128. package/dist/assets/ReviewPage-BqAs5sG1.js +3 -0
  129. package/dist/assets/code-dark-B7eM_9G9.css +1 -0
  130. package/dist/assets/{cssMode-Bdg5RpcJ.js → cssMode-ADdAlxTO.js} +1 -1
  131. package/dist/assets/{editor.api2-D74ufkbr.js → editor.api2-BFLAr0YF.js} +162 -162
  132. package/dist/assets/file-icons-BpeqWkIB.js +1 -0
  133. package/dist/assets/{freemarker2-DfMDJfnE.js → freemarker2-C90q5jrp.js} +1 -1
  134. package/dist/assets/{handlebars-BEdPtzMT.js → handlebars-BwUQcEDq.js} +1 -1
  135. package/dist/assets/{html-B1fPb1QD.js → html-1s3uPKfh.js} +1 -1
  136. package/dist/assets/{htmlMode-CY2ZjHxH.js → htmlMode-xtKD99de.js} +1 -1
  137. package/dist/assets/{index-D-zetSol.js → index-C8MchCem.js} +3 -3
  138. package/dist/assets/{javascript-BV2OsWA0.js → javascript-Ph7ijyny.js} +1 -1
  139. package/dist/assets/{jsonMode-zd07aeuX.js → jsonMode-D-jGAnFH.js} +1 -1
  140. package/dist/assets/linkComputer-CbGyI_qx.js +1 -0
  141. package/dist/assets/{liquid-BParI24X.js → liquid-0IamE_cI.js} +1 -1
  142. package/dist/assets/{lspLanguageFeatures-BzredkoV.js → lspLanguageFeatures-BxnQunWF.js} +1 -1
  143. package/dist/assets/main-CLKXa6hf.css +1 -0
  144. package/dist/assets/{mdx-vGzUJN98.js → mdx-DdNmfivn.js} +1 -1
  145. package/dist/assets/{monaco.contribution-BmaeBQEc.js → monaco.contribution-B43jsUs1.js} +2 -2
  146. package/dist/assets/{python-CUwdLgsI.js → python-IAt4X5TS.js} +1 -1
  147. package/dist/assets/{razor-BScebUxU.js → razor-DRkFYW0S.js} +1 -1
  148. package/dist/assets/review-C8-oPKXk.css +1 -0
  149. package/dist/assets/{tsMode-DzUAibXK.js → tsMode-_d_4U2gf.js} +1 -1
  150. package/dist/assets/{typescript-D9EOeM7N.js → typescript-C7zcMq9l.js} +1 -1
  151. package/dist/assets/{workers-DAiomYZM.js → workers-D0nOyJzf.js} +1 -1
  152. package/dist/assets/workspace-editor-model-DND_Y-Bj.js +1 -0
  153. package/dist/assets/workspace-editor-monaco-BPWhgcIB.js +4 -0
  154. package/dist/assets/workspace-editor-monaco-N641qPjX.js +1 -0
  155. package/dist/assets/workspace-view-state-k1bTlJ_Z.js +13 -0
  156. package/dist/assets/{xml-wWl1dhkQ.js → xml-BR-HvxT8.js} +1 -1
  157. package/dist/assets/{yaml-Dc2WDaOZ.js → yaml-YtFQwh4q.js} +1 -1
  158. package/dist/index.html +1 -1
  159. package/extensions/browser/backend/agent-browser-runtime.cjs +1040 -0
  160. package/extensions/browser/backend/agent-capability.cjs +85 -0
  161. package/extensions/browser/backend/browser-mcp-server.cjs +655 -0
  162. package/extensions/browser/backend/browser-resource-manager.cjs +1486 -0
  163. package/extensions/browser/backend/browser-resource-store.cjs +228 -0
  164. package/extensions/browser/backend/browser-router.cjs +203 -0
  165. package/extensions/browser/backend/executable-discovery.cjs +248 -0
  166. package/extensions/browser/backend/farming-browser-client.cjs +116 -0
  167. package/extensions/browser/backend/index.cjs +32 -0
  168. package/extensions/browser/backend/managed-chromium-installer.cjs +833 -0
  169. package/extensions/browser/bin/farming-browser +16 -7
  170. package/extensions/computer/backend/agent-capability.cjs +72 -0
  171. package/extensions/computer/backend/computer-constants.cjs +59 -0
  172. package/extensions/computer/backend/computer-mcp-server.cjs +182 -0
  173. package/extensions/computer/backend/computer-resource-manager.cjs +1219 -0
  174. package/extensions/computer/backend/computer-resource-store.cjs +190 -0
  175. package/extensions/computer/backend/computer-router.cjs +257 -0
  176. package/extensions/computer/backend/cua-tools.json +3067 -0
  177. package/extensions/computer/backend/farming-computer-client.cjs +84 -0
  178. package/extensions/computer/backend/index.cjs +35 -0
  179. package/extensions/computer/backend/isolated-browser-provider.cjs +157 -0
  180. package/extensions/computer/bin/farming-computer +131 -0
  181. package/frontend/farming-net/app.js +255 -262
  182. package/frontend/ghostty-loader.js +24 -20
  183. package/frontend/reading-anchor.js +210 -193
  184. package/frontend/runtime-paths.js +24 -26
  185. package/frontend/session-bridge.js +90 -111
  186. package/frontend/session-modal-bridge.js +184 -302
  187. package/frontend/skin-bridge.js +20 -23
  188. package/frontend/skins/crt/app.js +7686 -7901
  189. package/frontend/skins/crt/effects.js +7 -6
  190. package/frontend/skins/crt/index.html +6 -6
  191. package/frontend/skins/crt/styles/markdown.css +4 -4
  192. package/frontend/terminal-bridge.js +174 -181
  193. package/frontend/terminal-replay.js +320 -345
  194. package/frontend/theme-loader.js +55 -71
  195. package/package.json +64 -37
  196. package/shared/backend-connection-status.js +38 -69
  197. package/shared/browser-protocol.js +235 -110
  198. package/shared/websocket-liveness.js +40 -0
  199. package/backend/acp/packaged-claude-acp.js +0 -13
  200. package/backend/acp/packaged-codex-acp.js +0 -13
  201. package/backend/acp/patch-decisions.js +0 -153
  202. package/backend/acp/permission-security.js +0 -108
  203. package/backend/acp-session-state.js +0 -634
  204. package/backend/acp-transcript.js +0 -512
  205. package/backend/agent-env.js +0 -250
  206. package/backend/agent-json-stream.js +0 -268
  207. package/backend/agent-lifecycle-journal.js +0 -133
  208. package/backend/agent-order.js +0 -166
  209. package/backend/agent-provider-session.js +0 -76
  210. package/backend/agent-runtime-binding.js +0 -174
  211. package/backend/agent-session-history.js +0 -986
  212. package/backend/async-cache.js +0 -94
  213. package/backend/atomic-json-store.js +0 -37
  214. package/backend/auth.js +0 -251
  215. package/backend/chat-runtime.js +0 -28
  216. package/backend/claude-settings.js +0 -121
  217. package/backend/cli-agents.js +0 -486
  218. package/backend/codex-context-window.js +0 -189
  219. package/backend/codex-models.js +0 -226
  220. package/backend/codex-session-archive.js +0 -65
  221. package/backend/codex-session-history.js +0 -647
  222. package/backend/codex-transcript-sanitizer.js +0 -115
  223. package/backend/codex-transcript.js +0 -2362
  224. package/backend/command-runner-child.js +0 -67
  225. package/backend/config-manager.js +0 -1117
  226. package/backend/control-api.js +0 -455
  227. package/backend/executable-discovery.js +0 -216
  228. package/backend/farming-agent-bootstrap.js +0 -49
  229. package/backend/farming-cli.js +0 -403
  230. package/backend/farming-net-pass.js +0 -285
  231. package/backend/farming-net-registry.js +0 -112
  232. package/backend/farming-net-server.js +0 -298
  233. package/backend/farming-session-store.js +0 -677
  234. package/backend/git-worktree-info.js +0 -181
  235. package/backend/haiku-token.js +0 -772
  236. package/backend/index-html.js +0 -80
  237. package/backend/input-parts.js +0 -54
  238. package/backend/input-routing.js +0 -24
  239. package/backend/json-cli-runtime.js +0 -275
  240. package/backend/main-agent-skills.js +0 -288
  241. package/backend/main-page-session.js +0 -122
  242. package/backend/native-pty-controller-generation.js +0 -106
  243. package/backend/native-pty-host-identity.js +0 -86
  244. package/backend/native-pty-host-path.js +0 -42
  245. package/backend/native-session-engine.js +0 -268
  246. package/backend/network.js +0 -23
  247. package/backend/npm-update-helper.js +0 -374
  248. package/backend/packaged-node-pty.js +0 -99
  249. package/backend/preview-session-manager.js +0 -68
  250. package/backend/provider-adapters.js +0 -399
  251. package/backend/provider-session-id.js +0 -47
  252. package/backend/provider-session-service.js +0 -345
  253. package/backend/qr-share-tickets.js +0 -100
  254. package/backend/review-diff-router.js +0 -180
  255. package/backend/review-diff-service.js +0 -1270
  256. package/backend/review-session-router.js +0 -74
  257. package/backend/review-session-service.js +0 -446
  258. package/backend/review-session-store.js +0 -151
  259. package/backend/review-state-router.js +0 -116
  260. package/backend/review-state-store.js +0 -286
  261. package/backend/run-history-store.js +0 -67
  262. package/backend/runtime-dependency-manager.js +0 -759
  263. package/backend/runtime-executable-invocation.js +0 -20
  264. package/backend/runtime-observation.js +0 -81
  265. package/backend/server-process-identity.js +0 -47
  266. package/backend/server.js +0 -3225
  267. package/backend/session-engine-bridge.js +0 -129
  268. package/backend/session-engine-router.js +0 -66
  269. package/backend/session-engine.js +0 -39
  270. package/backend/session-stream-protocol.js +0 -185
  271. package/backend/shell-busy-integration.js +0 -472
  272. package/backend/slash-command-discovery.js +0 -433
  273. package/backend/storage-layout.js +0 -205
  274. package/backend/system-monitor.js +0 -77
  275. package/backend/terminal-attach-checkpoint.js +0 -74
  276. package/backend/terminal-exit-quiescence.js +0 -39
  277. package/backend/terminal-runtime-cleanup.js +0 -80
  278. package/backend/terminal-screen-worker.js +0 -337
  279. package/backend/terminal-state-serialization.js +0 -127
  280. package/backend/theme-manager.js +0 -163
  281. package/backend/usage-forecast.js +0 -86
  282. package/backend/usage-history-scanner.ts +0 -2718
  283. package/backend/usage-history-smoke.js +0 -64
  284. package/backend/usage-history-worker.js +0 -34
  285. package/backend/workspace-directory.js +0 -232
  286. package/backend/workspace-discovery.js +0 -311
  287. package/backend/workspace-root-registry.js +0 -166
  288. package/dist/assets/App-BKNUILuL.css +0 -1
  289. package/dist/assets/App-Z1N7T0eM.js +0 -695
  290. package/dist/assets/FileEditorPane-CNtuGjQ3.js +0 -2
  291. package/dist/assets/IconGlyphs-BClgag6E.js +0 -1
  292. package/dist/assets/ProjectFilesSection-K3GHuQ4U.js +0 -12
  293. package/dist/assets/ReviewPage-BRuxFaH5.js +0 -3
  294. package/dist/assets/code-dark-kLsQLvpf.css +0 -1
  295. package/dist/assets/file-icons-BOlcJ0ZF.js +0 -1
  296. package/dist/assets/main-DUTz-Rd2.css +0 -1
  297. package/dist/assets/review-BcnwQtJv.css +0 -1
  298. package/dist/assets/workspace-editor-model-CSGCl21x.js +0 -1
  299. package/dist/assets/workspace-editor-monaco-CJNwbpCy.js +0 -4
  300. package/dist/assets/workspace-editor-monaco-CO9w8YV0.js +0 -1
  301. package/dist/assets/workspace-view-state-BjKPYp0E.js +0 -7
  302. package/extensions/browser/backend/agent-browser-runtime.js +0 -1041
  303. package/extensions/browser/backend/agent-capability.js +0 -55
  304. package/extensions/browser/backend/browser-mcp-server.js +0 -621
  305. package/extensions/browser/backend/browser-resource-manager.js +0 -1331
  306. package/extensions/browser/backend/browser-resource-store.js +0 -190
  307. package/extensions/browser/backend/browser-router.js +0 -141
  308. package/extensions/browser/backend/executable-discovery.js +0 -238
  309. package/extensions/browser/backend/farming-browser-client.js +0 -83
  310. package/extensions/browser/backend/index.js +0 -7
  311. package/extensions/browser/backend/managed-chromium-installer.js +0 -593
  312. package/extensions/browser/frontend/BrowserSidebarPortals.tsx +0 -360
  313. package/extensions/browser/frontend/BrowserViewer.tsx +0 -500
  314. package/extensions/browser/frontend/browser-resource-state.ts +0 -60
  315. package/extensions/browser/frontend/browser.css +0 -567
  316. package/extensions/browser/frontend/types.ts +0 -38
  317. package/extensions/browser/frontend/useBrowserResources.ts +0 -166
  318. package/frontend/vendor/ghostty-web/index.d.ts +0 -1842
  319. package/shared/backend-connection-status.d.ts +0 -37
  320. package/shared/browser-protocol.d.ts +0 -5
  321. /package/dist/assets/{api-w2x5QfxB.js → api-ByCV9MEr.js} +0 -0
  322. /package/dist/assets/{core-vPkV8P_m.js → core-Czwdx12k.js} +0 -0
  323. /package/dist/assets/{fsharp-DNZNZyEE.js → fsharp-BjPpkoJe.js} +0 -0
  324. /package/dist/assets/{go-_kPJjNc_.js → go-BkIDmSUP.js} +0 -0
  325. /package/dist/assets/{graphql-4HCNVt5g.js → graphql-CCJTpqi1.js} +0 -0
  326. /package/dist/assets/{hcl-C87ymY0o.js → hcl-s4z8lUFg.js} +0 -0
  327. /package/dist/assets/{ini-BYrrGNTc.js → ini-CyUW1I7Z.js} +0 -0
  328. /package/dist/assets/{java-B211rJbk.js → java-CFCcGRiM.js} +0 -0
  329. /package/dist/assets/{julia-DVPsRtBm.js → julia-DQyH_EYb.js} +0 -0
  330. /package/dist/assets/{kotlin-BYyNerjp.js → kotlin-B8hA5O51.js} +0 -0
  331. /package/dist/assets/{less-EC9UblGE.js → less-DaVdaas0.js} +0 -0
  332. /package/dist/assets/{lexon-B1yuUdqW.js → lexon-DLBhPd3y.js} +0 -0
  333. /package/dist/assets/{lua-Bo6GFzxT.js → lua-gB1c_cif.js} +0 -0
  334. /package/dist/assets/{m3-C-Y9cBVI.js → m3-C2T3z3gB.js} +0 -0
  335. /package/dist/assets/{markdown-BHu-wLbe.js → markdown-CKq-Euj1.js} +0 -0
  336. /package/dist/assets/{mips-Dkds26rX.js → mips-Bcs5qIDX.js} +0 -0
  337. /package/dist/assets/{msdax-PU4g-8_g.js → msdax-CBP48FHF.js} +0 -0
  338. /package/dist/assets/{mysql-fPpuGpS7.js → mysql-c7tiPcFj.js} +0 -0
  339. /package/dist/assets/{objective-c-Wy1e5JUf.js → objective-c-D4BZkeEV.js} +0 -0
  340. /package/dist/assets/{pascal-BIXwvrqU.js → pascal-CJDZg0MN.js} +0 -0
  341. /package/dist/assets/{pascaligo-DwR0AtL_.js → pascaligo-Bygi8Ynl.js} +0 -0
  342. /package/dist/assets/{perl-CGHAl8my.js → perl-aS8bkf7E.js} +0 -0
  343. /package/dist/assets/{pgsql-B91M73E2.js → pgsql-CzNAYFYl.js} +0 -0
  344. /package/dist/assets/{php-C70ttrCx.js → php-DOtoEFTT.js} +0 -0
  345. /package/dist/assets/{pla-BNrXaRh0.js → pla-DugRnao1.js} +0 -0
  346. /package/dist/assets/{postiats-Dv2IAZR9.js → postiats-DsXwusKh.js} +0 -0
  347. /package/dist/assets/{powerquery-CkY5Wq7J.js → powerquery-DL3o72oD.js} +0 -0
  348. /package/dist/assets/{powershell-uGIYLU3C.js → powershell-C1qHekys.js} +0 -0
  349. /package/dist/assets/{protobuf-CavfmJu-.js → protobuf-DHgsc-cH.js} +0 -0
  350. /package/dist/assets/{pug-Drqnyitx.js → pug-FZXlE_Iu.js} +0 -0
  351. /package/dist/assets/{qrcode-7uGsTEmi.js → qrcode-DJDTdjL7.js} +0 -0
  352. /package/dist/assets/{qsharp-C7yF-Dti.js → qsharp-bLyhI3DT.js} +0 -0
  353. /package/dist/assets/{r-HXLGP0_-.js → r-DjFGXbAa.js} +0 -0
  354. /package/dist/assets/{redis-C4y6mf2p.js → redis-DSoZ_V6u.js} +0 -0
  355. /package/dist/assets/{redshift-DdQrFPrv.js → redshift-CzkdCsrg.js} +0 -0
  356. /package/dist/assets/{restructuredtext-CBrf2PNj.js → restructuredtext-B_XPrGCQ.js} +0 -0
  357. /package/dist/assets/{ruby-CVtMoX2B.js → ruby-Cry2RneJ.js} +0 -0
  358. /package/dist/assets/{rust-Ckq4sbMP.js → rust-CWzdbFq1.js} +0 -0
  359. /package/dist/assets/{sb-er66OpE3.js → sb-DmofU59h.js} +0 -0
  360. /package/dist/assets/{scala-BdWZz-hH.js → scala-BUC2l-vN.js} +0 -0
  361. /package/dist/assets/{scheme-kTV_s2kP.js → scheme-CNIB_72f.js} +0 -0
  362. /package/dist/assets/{scss-Bymy2VJQ.js → scss-DcL4-NbX.js} +0 -0
  363. /package/dist/assets/{shell-bwEP1LQ1.js → shell-CgAGvSSA.js} +0 -0
  364. /package/dist/assets/{solidity-DCddeBgL.js → solidity-DSkt6O_P.js} +0 -0
  365. /package/dist/assets/{sophia-BMN3AQig.js → sophia--5ytcjDN.js} +0 -0
  366. /package/dist/assets/{sparql-CSXOLjzi.js → sparql-C1Srr7cD.js} +0 -0
  367. /package/dist/assets/{sql-COg8ewmK.js → sql-2k_O5fj7.js} +0 -0
  368. /package/dist/assets/{st-CIaZtBS_.js → st-DCCCPqfz.js} +0 -0
  369. /package/dist/assets/{swift-DXiOhUHu.js → swift-Dd8mAfOt.js} +0 -0
  370. /package/dist/assets/{systemverilog-BTDE6Dr2.js → systemverilog-B0-aZYXu.js} +0 -0
  371. /package/dist/assets/{tcl-B0zy9NvV.js → tcl-CZAZwPVX.js} +0 -0
  372. /package/dist/assets/{twig-D594l4Z1.js → twig-D-Ag0tdS.js} +0 -0
  373. /package/dist/assets/{typespec-XKVjaPqo.js → typespec-B7kTFp0Z.js} +0 -0
  374. /package/dist/assets/{useWorkspaceMenuKeyboard-Bpy_s69Z.js → useWorkspaceMenuKeyboard-uYZpDxhU.js} +0 -0
  375. /package/dist/assets/{vb-B2wYoKRV.js → vb-20w1JPum.js} +0 -0
  376. /package/dist/assets/{wgsl-D0Yf2LbK.js → wgsl-CLJfqFt0.js} +0 -0
  377. /package/dist/assets/{workspace-working-copy-C3FSR3N6.js → workspace-working-copy-Cfz07cOd.js} +0 -0
@@ -0,0 +1,3356 @@
1
+ // Generated by scripts/build-backend-runtime.ts. Do not edit.
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+ var server_exports = {};
21
+ __export(server_exports, {
22
+ agentManager: () => agentManager,
23
+ app: () => app,
24
+ appendIndexHtmlAssetToken: () => import_index_html.appendIndexHtmlAssetToken,
25
+ browserResourceManager: () => browserResourceManager,
26
+ computerResourceManager: () => computerResourceManager,
27
+ handleMessage: () => handleMessage,
28
+ resolveCliBinDir: () => resolveCliBinDir,
29
+ resolveInputTargetAgentId: () => import_input_routing.resolveInputTargetAgentId,
30
+ rewriteIndexHtmlForBasePath: () => import_index_html.rewriteIndexHtmlForBasePath,
31
+ runTerminalRuntimeStartupCleanup: () => runTerminalRuntimeStartupCleanup,
32
+ server: () => server,
33
+ startServer: () => startServer,
34
+ workspaceFileService: () => workspaceFileService,
35
+ wss: () => wss
36
+ });
37
+ module.exports = __toCommonJS(server_exports);
38
+ var import_agent_manager = require("./agent-manager.cjs");
39
+ var import_agent_runtime_binding = require("./agent-runtime-binding.cjs");
40
+ var import_config_manager = require("./config-manager.cjs");
41
+ var import_theme_manager = require("./theme-manager.cjs");
42
+ var import_auth = require("./auth.cjs");
43
+ var import_network = require("./network.cjs");
44
+ var import_executable_discovery = require("./executable-discovery.cjs");
45
+ var import_claude_settings = require("./claude-settings.cjs");
46
+ var import_codex_models = require("./codex-models.cjs");
47
+ var import_provider_adapters = require("./provider-adapters.cjs");
48
+ var import_codex_session_history = require("./codex-session-history.cjs");
49
+ var import_agent_session_history = require("./agent-session-history.cjs");
50
+ var import_main_page_session = require("./main-page-session.cjs");
51
+ var import_workspace_discovery = require("./workspace-discovery.cjs");
52
+ var import_git_worktree_info = require("./git-worktree-info.cjs");
53
+ var import_workspace_directory = require("./workspace-directory.cjs");
54
+ var import_control_api = require("./control-api.cjs");
55
+ var import_workspace_file_service = require("./workspace-file-service.cjs");
56
+ var import_workspace_file_router = require("./workspace-file-router.cjs");
57
+ var import_workspace_root_registry = require("./workspace-root-registry.cjs");
58
+ var import_backend = require("../extensions/browser/backend/index.cjs");
59
+ var import_backend2 = require("../extensions/computer/backend/index.cjs");
60
+ var import_usage_monitor = require("./usage-monitor.cjs");
61
+ var import_codex_context_window = require("./codex-context-window.cjs");
62
+ var import_async_cache = require("./async-cache.cjs");
63
+ var import_main_agent_skills = require("./main-agent-skills.cjs");
64
+ var import_agent_extension_inventory = require("./agent-extension-inventory.cjs");
65
+ var import_agent_session_inventory = require("./agent-session-inventory.cjs");
66
+ var import_slash_command_discovery = require("./slash-command-discovery.cjs");
67
+ var import_storage_layout = require("./storage-layout.cjs");
68
+ var import_update_service = require("./update-service.cjs");
69
+ var import_input_parts = require("./input-parts.cjs");
70
+ var import_terminal_runtime_cleanup = require("./terminal-runtime-cleanup.cjs");
71
+ var import_resource_broadcast_protocol = require("./resource-broadcast-protocol.cjs");
72
+ var import_qr_share_tickets = require("./qr-share-tickets.cjs");
73
+ var import_review_state_store = require("./review-state-store.cjs");
74
+ var import_review_state_router = require("./review-state-router.cjs");
75
+ var import_review_diff_service = require("./review-diff-service.cjs");
76
+ var import_review_diff_router = require("./review-diff-router.cjs");
77
+ var import_review_session_store = require("./review-session-store.cjs");
78
+ var import_review_session_service = require("./review-session-service.cjs");
79
+ var import_review_session_router = require("./review-session-router.cjs");
80
+ var import_index_html = require("./index-html.cjs");
81
+ var import_acp_transcript = require("./acp-transcript.cjs");
82
+ var import_session_stream_protocol = require("./session-stream-protocol.cjs");
83
+ var import_business_health = require("./business-health.cjs");
84
+ var import_input_routing = require("./input-routing.cjs");
85
+ function isRecord(value) {
86
+ return value !== null && typeof value === "object" && !Array.isArray(value);
87
+ }
88
+ function optionalString(value) {
89
+ return typeof value === "string" ? value : void 0;
90
+ }
91
+ function requiredString(value) {
92
+ return typeof value === "string" ? value : "";
93
+ }
94
+ function optionalNumber(value) {
95
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
96
+ }
97
+ function isAcpConfigValue(value) {
98
+ return value === null || ["string", "number", "boolean"].includes(typeof value) || Array.isArray(value) && value.every((item) => typeof item === "string");
99
+ }
100
+ function isForkMode(value) {
101
+ return value === "conversation" || value === "same-worktree" || value === "new-worktree";
102
+ }
103
+ function caughtError(error) {
104
+ if (error instanceof Error) return error;
105
+ const normalized = new Error(String(error));
106
+ if (error && typeof error === "object") Object.assign(normalized, error);
107
+ return normalized;
108
+ }
109
+ const express = require("express");
110
+ const compression = require("compression");
111
+ const WebSocket = require("ws");
112
+ const http = require("http");
113
+ const path = require("path");
114
+ const fs = require("fs");
115
+ const os = require("os");
116
+ const crypto = require("crypto");
117
+ const { execFile } = require("child_process");
118
+ const { promisify } = require("util");
119
+ const { URLSearchParams, pathToFileURL } = require("url");
120
+ const {
121
+ MIN_PROTOCOL_VERSION,
122
+ PROTOCOL_VERSION,
123
+ protocolCompatible,
124
+ sanitizeAgentUpdatePatch,
125
+ validateClientMessage
126
+ } = require("../shared/browser-protocol");
127
+ const {
128
+ initializeWebSocketLiveness,
129
+ startWebSocketLivenessMonitor
130
+ } = require("../shared/websocket-liveness.js");
131
+ const BASE_PATH = (0, import_index_html.normalizeBasePath)(process.env.FARMING_BASE_PATH || "/");
132
+ const PORT = process.env.PORT || 3e3;
133
+ const tokenAuth = new import_auth.TokenAuth({ basePath: BASE_PATH || "/" });
134
+ const authEnabled = tokenAuth.isEnabled();
135
+ const WS_PATH = (0, import_index_html.routePath)(BASE_PATH, "/ws");
136
+ const SERVER_EPOCH = crypto.randomUUID();
137
+ const DEFAULT_TRANSCRIPT_MAX_TURNS = 240;
138
+ const MAX_TRANSCRIPT_TURNS = 1e3;
139
+ const INTERACTIVE_REFRESH_CACHE_MAX_AGE_MS = 3e3;
140
+ const execFileAsync = promisify(execFile);
141
+ const app = express();
142
+ app.use(compression());
143
+ const server = http.createServer(app);
144
+ const wss = new WebSocket.Server({ server });
145
+ const websocketLivenessTimer = startWebSocketLivenessMonitor(wss, { openState: WebSocket.OPEN });
146
+ server.on("close", () => clearInterval(websocketLivenessTimer));
147
+ const configManager = new import_config_manager.ConfigManager();
148
+ configManager.init();
149
+ const agentSessionInventory = new import_agent_session_inventory.AgentSessionInventory({
150
+ cacheFile: (0, import_storage_layout.agentSessionInventoryCacheFile)(configManager.farmingDir)
151
+ });
152
+ const agentExtensionInventory = new import_agent_extension_inventory.AgentExtensionInventory({
153
+ cacheFile: (0, import_storage_layout.agentExtensionInventoryCacheFile)(configManager.farmingDir)
154
+ });
155
+ function resolveCliBinDir() {
156
+ if (process.env.FARMING_CLI_BIN_DIR) {
157
+ return process.env.FARMING_CLI_BIN_DIR;
158
+ }
159
+ if (process.pkg || process.env.FARMING_PACKAGED_RUNTIME === "1") {
160
+ return path.dirname(process.execPath);
161
+ }
162
+ return path.join(__dirname, "..", "bin");
163
+ }
164
+ const computerResourceManager = new import_backend2.ComputerResourceManager({
165
+ configDir: configManager.farmingDir,
166
+ getSettings: () => configManager.getSettings(),
167
+ isEnabled: () => configManager.getSettings().computerExtensionEnabled === true
168
+ });
169
+ const isolatedBrowserProvider = new import_backend2.IsolatedBrowserProvider({
170
+ configDir: configManager.farmingDir,
171
+ computerResourceManager
172
+ });
173
+ const browserResourceManager = new import_backend.BrowserResourceManager({
174
+ configDir: configManager.farmingDir,
175
+ isEnabled: () => configManager.getSettings().browserExtensionEnabled === true,
176
+ getBrowserSettings: () => configManager.getSettings(),
177
+ saveBrowserSelection: (selection) => configManager.updateSettings({
178
+ browserSource: selection.source,
179
+ browserExecutablePath: selection.executablePath
180
+ }),
181
+ isolatedBrowserProvider
182
+ });
183
+ const agentManager = new import_agent_manager.AgentManager(
184
+ configManager,
185
+ {
186
+ controlUrl: `http://127.0.0.1:${PORT}${BASE_PATH}`,
187
+ tokenFile: tokenAuth.getTokenFile(),
188
+ authDisabled: !authEnabled,
189
+ cliBinDir: resolveCliBinDir(),
190
+ browserMcpEnabled: () => configManager.getSettings().browserExtensionEnabled === true,
191
+ browserPermissionDecision: (agentId, tool, input) => browserResourceManager.permissionDecision(agentId, tool, input),
192
+ computerMcpEnabled: () => configManager.getSettings().computerExtensionEnabled === true
193
+ }
194
+ );
195
+ async function requireAgentRecoveryForHttp(res) {
196
+ try {
197
+ await agentManager.whenRecovered();
198
+ return true;
199
+ } catch (caught) {
200
+ const error = caughtError(caught);
201
+ res.status(503).json({
202
+ error: error?.message || "Agent lifecycle recovery is unavailable",
203
+ retryable: true
204
+ });
205
+ return false;
206
+ }
207
+ }
208
+ const themeManager = new import_theme_manager.ThemeManager({ configDir: configManager.farmingDir });
209
+ const workspaceFileService = new import_workspace_file_service.WorkspaceFileService();
210
+ const workspaceRootRegistry = new import_workspace_root_registry.WorkspaceRootRegistry(
211
+ agentManager
212
+ );
213
+ let agentResourceReconcileRequested = false;
214
+ let agentResourceReconcileRunning = false;
215
+ const reconcileAgentResourceLifecycle = () => {
216
+ agentResourceReconcileRequested = true;
217
+ if (agentResourceReconcileRunning) return;
218
+ agentResourceReconcileRunning = true;
219
+ void (async () => {
220
+ try {
221
+ await agentManager.whenRecovered();
222
+ while (agentResourceReconcileRequested) {
223
+ agentResourceReconcileRequested = false;
224
+ const agents = agentManager.getState().agents;
225
+ await browserResourceManager.reconcileAgentLifecycle(Array.isArray(agents) ? agents : []);
226
+ await computerResourceManager.reconcileAgentLifecycle(Array.isArray(agents) ? agents : []);
227
+ }
228
+ } catch (error) {
229
+ console.warn("Failed to reconcile Agent-owned resources:", caughtError(error).message || error);
230
+ } finally {
231
+ agentResourceReconcileRunning = false;
232
+ if (agentResourceReconcileRequested) reconcileAgentResourceLifecycle();
233
+ }
234
+ })();
235
+ };
236
+ const computerRuntimeRecoveryPromise = computerResourceManager.init().catch((error) => {
237
+ console.warn("Failed to recover Computer runtimes:", caughtError(error).message || error);
238
+ return null;
239
+ });
240
+ const browserRuntimeRecoveryPromise = computerRuntimeRecoveryPromise.then(() => isolatedBrowserProvider.recover()).then(() => browserResourceManager.init()).then(() => {
241
+ agentManager.on("update", reconcileAgentResourceLifecycle);
242
+ reconcileAgentResourceLifecycle();
243
+ }).catch((error) => {
244
+ console.warn("Failed to recover Browser runtimes:", caughtError(error).message || error);
245
+ return null;
246
+ });
247
+ const updateService = new import_update_service.FarmingUpdateService({
248
+ rootDir: path.join(__dirname, ".."),
249
+ configDir: configManager.farmingDir,
250
+ platform: process.platform,
251
+ arch: process.arch,
252
+ packagedRuntime: Boolean(process.pkg || process.env.FARMING_PACKAGED_RUNTIME === "1")
253
+ });
254
+ const usageMonitor = new import_usage_monitor.UsageMonitor({
255
+ agentManager,
256
+ configDir: configManager.farmingDir,
257
+ getProviderHomes: configuredProviderHomes
258
+ });
259
+ const codexContextWindowReader = new import_codex_context_window.CodexContextWindowReader();
260
+ const usageSummaryCache = new import_async_cache.AsyncCache(() => usageMonitor.getUsageSummary(), {
261
+ ttlMs: 15e3,
262
+ staleMs: 2 * 6e4
263
+ });
264
+ const codexModelOptionsCache = new import_async_cache.AsyncCache((homePath) => (0, import_codex_models.listCodexModelOptions)({
265
+ env: (0, import_provider_adapters.applyProviderHomeEnvironment)({ ...process.env }, "codex", homePath)
266
+ }), {
267
+ ttlMs: 5 * 6e4,
268
+ staleMs: 5 * 6e4
269
+ });
270
+ function configuredProviderMetadata() {
271
+ const result = {};
272
+ const settings = configManager.getSettings();
273
+ const records = configManager.listAgentSessionRecords();
274
+ const homeBindings = configManager.agentHomeBindings(records);
275
+ const providers = Object.keys(settings.agentHomes || {});
276
+ for (const provider of providers) {
277
+ result[provider] = configManager.getKnownAgentHomes(provider, homeBindings).map((home) => ({
278
+ id: String(home.id || "default"),
279
+ path: String(home.path || "")
280
+ })).filter((home) => home.id && home.path);
281
+ }
282
+ const providerSessionBindings = records.flatMap((record) => {
283
+ const provider = String(record.provider || "").trim().toLowerCase();
284
+ const providerHomeId = String(record.providerHomeId || "default").trim() || "default";
285
+ const providerHomePath = configManager.expandWorkspacePath(String(record.providerHomePath || ""));
286
+ const providerSessionId = String(record.providerSessionId || "").trim();
287
+ return provider && providerHomePath && providerSessionId && record.providerSessionTemporary !== true ? [{ provider, providerHomeId, providerHomePath, providerSessionId }] : [];
288
+ });
289
+ return { providerHomes: result, providerSessionBindings };
290
+ }
291
+ function configuredProviderHomes() {
292
+ return configuredProviderMetadata().providerHomes;
293
+ }
294
+ function requestedProviderHome(provider, rawHomeId) {
295
+ const homeId = typeof rawHomeId === "string" && rawHomeId.trim() ? rawHomeId.trim() : "default";
296
+ if (!/^[A-Za-z0-9._-]+$/.test(homeId)) {
297
+ return { error: "Invalid Agent Home id", home: null, status: 400 };
298
+ }
299
+ const home = configManager.getKnownAgentHome(provider, homeId);
300
+ return home ? { error: "", home, status: 200 } : { error: `Unknown ${provider} Agent Home: ${homeId}`, home: null, status: 404 };
301
+ }
302
+ function currentAgentSessions() {
303
+ return agentSessionInventory.list(
304
+ () => configuredProviderMetadata()
305
+ );
306
+ }
307
+ function withSearchTimeout(promise, timeoutMs) {
308
+ let timer;
309
+ const timeout = new Promise((_, reject) => {
310
+ timer = setTimeout(() => {
311
+ const error = new Error("Agent search timed out");
312
+ error.code = "ETIMEDOUT";
313
+ reject(error);
314
+ }, timeoutMs);
315
+ });
316
+ return Promise.race([promise, timeout]).finally(() => {
317
+ if (timer) clearTimeout(timer);
318
+ });
319
+ }
320
+ const qrShareTickets = new import_qr_share_tickets.QrShareTicketStore({ ttlMs: import_qr_share_tickets.SHARE_TICKET_TTL_MS });
321
+ const reviewStateStore = new import_review_state_store.ReviewStateStore(configManager.farmingDir, {
322
+ seedReviews: {
323
+ "review-fixture-553987": {
324
+ patchsets: {
325
+ "Patchset 20": { reviewedPaths: ["clis/dataflow.py", "clis/fetch_instance_log.py"], revision: 0 },
326
+ "Patchset 19": { reviewedPaths: ["clis/fetch_instance_log.py"], revision: 0 }
327
+ }
328
+ }
329
+ }
330
+ });
331
+ const reviewDiffService = new import_review_diff_service.ReviewDiffService(
332
+ agentManager,
333
+ workspaceFileService
334
+ );
335
+ const reviewSessionStore = new import_review_session_store.ReviewSessionStore(configManager.farmingDir);
336
+ const reviewSessionService = new import_review_session_service.ReviewSessionService(workspaceFileService, reviewSessionStore, reviewStateStore, {
337
+ resolveAgentRoot: (agentId) => agentManager.getAgentWorkspaceRoot(agentId),
338
+ resolveAcpReviewChanges: (agentId, itemIds) => {
339
+ if (typeof agentId !== "string" || !Array.isArray(itemIds) || !itemIds.every((item) => typeof item === "string")) {
340
+ throw new Error("ACP review request is invalid");
341
+ }
342
+ return agentManager.getAcpReviewChanges(agentId, itemIds);
343
+ }
344
+ });
345
+ const workspaceDiscoveryCache = new import_async_cache.AsyncCache((key) => {
346
+ const request = JSON.parse(key);
347
+ return (0, import_workspace_discovery.discoverAgentWorkspaces)({
348
+ limit: request.limit,
349
+ agent: request.agent
350
+ });
351
+ }, {
352
+ ttlMs: 3e4,
353
+ staleMs: 2 * 6e4
354
+ });
355
+ const frontendDir = path.join(__dirname, "../frontend");
356
+ const crtFrontendDir = path.join(frontendDir, "skins", "crt");
357
+ const distDir = path.join(__dirname, "../dist");
358
+ const staticAppDir = fs.existsSync(distDir) ? distDir : frontendDir;
359
+ const immutableAssetStaticOptions = {
360
+ immutable: true,
361
+ index: false,
362
+ maxAge: "1y"
363
+ };
364
+ const xtermBrowserEntryPath = path.join(__dirname, "..", "node_modules", "@xterm", "xterm", "lib", "xterm.js");
365
+ const xtermFitEntryPath = path.join(__dirname, "..", "node_modules", "@xterm", "addon-fit", "lib", "addon-fit.js");
366
+ const xtermWebglEntryPath = path.join(__dirname, "..", "node_modules", "@xterm", "addon-webgl", "lib", "addon-webgl.js");
367
+ const xtermCssPath = path.join(__dirname, "..", "node_modules", "@xterm", "xterm", "css", "xterm.css");
368
+ const materialIconDir = path.join(__dirname, "..", "node_modules", "material-icon-theme", "icons");
369
+ function getAvailableAgentsForRequest() {
370
+ if (process.env.FARMING_E2E_FAKE_EXECUTABLES === "1") {
371
+ return withLaunchCapabilities([
372
+ {
373
+ name: "codex",
374
+ command: "codex",
375
+ description: "Codex CLI - OpenAI coding assistant",
376
+ category: "coding",
377
+ supported: true,
378
+ interactive: true
379
+ },
380
+ {
381
+ name: "claude",
382
+ command: "claude",
383
+ description: "Claude CLI - Anthropic assistant",
384
+ category: "coding",
385
+ supported: true,
386
+ interactive: true
387
+ },
388
+ {
389
+ name: "opencode",
390
+ command: "opencode",
391
+ description: "OpenCode - AI coding assistant",
392
+ category: "coding",
393
+ supported: true,
394
+ interactive: true
395
+ },
396
+ {
397
+ name: "qoder",
398
+ command: "qodercli",
399
+ description: "Qoder - AI coding assistant",
400
+ category: "coding",
401
+ supported: true,
402
+ interactive: true
403
+ },
404
+ {
405
+ name: "qwen",
406
+ command: "qwen",
407
+ description: "Qwen Code - AI coding assistant",
408
+ category: "coding",
409
+ supported: true,
410
+ interactive: true
411
+ },
412
+ {
413
+ name: "bash",
414
+ command: "bash",
415
+ description: "Shell session",
416
+ category: "shell",
417
+ supported: true,
418
+ interactive: true
419
+ },
420
+ {
421
+ name: "zsh",
422
+ command: "zsh",
423
+ description: "Z shell",
424
+ category: "shell",
425
+ supported: true,
426
+ interactive: true
427
+ }
428
+ ]);
429
+ }
430
+ const shellEnv = agentManager.resolveAgentShellEnv("", { maxAgeMs: INTERACTIVE_REFRESH_CACHE_MAX_AGE_MS });
431
+ const pathEnv = typeof shellEnv?.PATH === "string" && shellEnv.PATH.trim() ? shellEnv.PATH : process.env.PATH || "";
432
+ return withLaunchCapabilities((0, import_executable_discovery.listAvailableAgents)(pathEnv));
433
+ }
434
+ function withLaunchCapabilities(agents) {
435
+ return agents.map((agent) => ({
436
+ ...agent,
437
+ capabilities: {
438
+ supportsChat: (0, import_provider_adapters.providerCapabilities)(agent.name).supportsChat === true
439
+ }
440
+ }));
441
+ }
442
+ function normalizeWorkspaceCompletionInput(value) {
443
+ const raw = typeof value === "string" ? value.trim() : "";
444
+ if (!raw) {
445
+ return { raw: "", parent: os.homedir(), prefix: "", displayParent: "~", explicitDirectory: false };
446
+ }
447
+ const home = os.homedir();
448
+ const expanded = raw === "~" ? home : raw.startsWith("~/") ? path.join(home, raw.slice(2)) : raw;
449
+ const explicitDirectory = raw.endsWith(path.sep) || raw === "~";
450
+ const parent = explicitDirectory ? expanded : path.dirname(expanded);
451
+ const prefix = explicitDirectory ? "" : path.basename(expanded);
452
+ const trimTrailingSeparator = (input) => {
453
+ let next = input;
454
+ while (next.length > 1 && next.endsWith(path.sep)) next = next.slice(0, -1);
455
+ return next;
456
+ };
457
+ const displayParent = explicitDirectory ? trimTrailingSeparator(raw) : trimTrailingSeparator(raw.slice(0, raw.length - prefix.length));
458
+ return {
459
+ raw,
460
+ parent: parent || path.sep,
461
+ prefix,
462
+ displayParent: displayParent || (path.isAbsolute(raw) ? path.sep : ""),
463
+ explicitDirectory
464
+ };
465
+ }
466
+ async function listWorkspacePathCompletions(partialPath, limit = 12) {
467
+ const query = normalizeWorkspaceCompletionInput(partialPath);
468
+ const entries = await fs.promises.readdir(query.parent, { withFileTypes: true });
469
+ const normalizedPrefix = query.prefix.toLowerCase();
470
+ const maxResults = Math.max(1, Math.min(Number(limit) || 12, 100));
471
+ return entries.filter((entry) => entry.isDirectory()).filter((entry) => normalizedPrefix.startsWith(".") || !entry.name.startsWith(".")).filter((entry) => !normalizedPrefix || entry.name.toLowerCase().startsWith(normalizedPrefix)).sort((a, b) => a.name.localeCompare(b.name)).slice(0, maxResults).map((entry) => {
472
+ const fullPath = path.join(query.parent, entry.name);
473
+ const displayPath = query.raw.startsWith("~") ? path.join(query.displayParent || "~", entry.name) : fullPath;
474
+ return {
475
+ name: entry.name,
476
+ path: `${displayPath}${path.sep}`
477
+ };
478
+ });
479
+ }
480
+ const publicProductAssetsDir = path.join(staticAppDir, "farming-2");
481
+ app.use((0, import_index_html.routePath)(BASE_PATH, "/farming-2"), express.static(publicProductAssetsDir, { index: false }));
482
+ const sendPublicProductAsset = (filename) => (_req, res) => {
483
+ res.sendFile(path.join(publicProductAssetsDir, filename));
484
+ };
485
+ app.get(["/favicon.ico", (0, import_index_html.routePath)(BASE_PATH, "/favicon.ico")], sendPublicProductAsset("favicon-v2.ico"));
486
+ app.get([
487
+ "/apple-touch-icon.png",
488
+ "/apple-touch-icon-precomposed.png",
489
+ "/apple-touch-icon-180x180.png",
490
+ (0, import_index_html.routePath)(BASE_PATH, "/apple-touch-icon.png"),
491
+ (0, import_index_html.routePath)(BASE_PATH, "/apple-touch-icon-precomposed.png"),
492
+ (0, import_index_html.routePath)(BASE_PATH, "/apple-touch-icon-180x180.png")
493
+ ], sendPublicProductAsset("app-icon-v2-180.png"));
494
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/j/:code"), (req, res) => {
495
+ if (req.method === "HEAD") {
496
+ res.status(204).end();
497
+ return;
498
+ }
499
+ const ticket = qrShareTickets.consume(req.params.code);
500
+ if (!ticket || authEnabled && !tokenAuth.verify(ticket.token)) {
501
+ res.status(410).send("Farming share link expired.");
502
+ return;
503
+ }
504
+ if (authEnabled) {
505
+ res.setHeader(
506
+ "Set-Cookie",
507
+ `farming_token=${(0, import_auth.encodeCookieToken)(ticket.token)}; Path=/; HttpOnly; SameSite=Lax`
508
+ );
509
+ }
510
+ res.redirect(302, entryPathWithQuery(ticket.targetQuery));
511
+ });
512
+ app.use(tokenAuth.middleware());
513
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/auth/status"), (req, res) => {
514
+ res.json({ authRequired: authEnabled });
515
+ });
516
+ function absoluteClientUrl(req, urlPath) {
517
+ const forwardedProto = String(req.headers["x-forwarded-proto"] || "").split(",")[0].trim();
518
+ const protocol = forwardedProto || req.protocol || "http";
519
+ const forwardedHost = String(req.headers["x-forwarded-host"] || "").split(",")[0].trim();
520
+ const host = forwardedHost || req.headers.host || `127.0.0.1:${PORT}`;
521
+ return `${protocol}://${host}${urlPath}`;
522
+ }
523
+ function shareTargetPositiveInteger(value) {
524
+ const number = Number(value);
525
+ return Number.isInteger(number) && number > 0 ? String(number) : "";
526
+ }
527
+ function shareTargetString(value, maxLength) {
528
+ const string = String(value || "").trim();
529
+ if (!string || string.length > maxLength || string.includes("\0")) return "";
530
+ return string;
531
+ }
532
+ function shareTargetQueryFromBody(body) {
533
+ const target = body && typeof body === "object" ? body.target : null;
534
+ if (!target || typeof target !== "object") return "";
535
+ const targetRecord = target;
536
+ const kind = targetRecord.kind === "file" ? "file" : targetRecord.kind === "folder" ? "folder" : targetRecord.kind === "agent" ? "agent" : "";
537
+ const agentId = shareTargetString(targetRecord.agentId, 160);
538
+ const absolutePath = shareTargetString(targetRecord.absolutePath, 2048);
539
+ const projectLabel = shareTargetString(targetRecord.projectLabel, 160);
540
+ const readingAnchor = shareTargetString(targetRecord.readingAnchor, 1800);
541
+ if (!kind || kind === "agent" && !agentId || kind !== "agent" && !absolutePath && !agentId && !projectLabel) return "";
542
+ const params = new URLSearchParams();
543
+ params.set("ftarget", kind);
544
+ if (agentId) params.set("agent", agentId);
545
+ if (kind === "agent" && readingAnchor && /^[A-Za-z0-9_-]+$/.test(readingAnchor)) params.set("fra", readingAnchor);
546
+ if (absolutePath) params.set("path", absolutePath);
547
+ if (projectLabel) params.set("project", projectLabel);
548
+ if (kind === "folder") {
549
+ const folderPath = shareTargetString(targetRecord.folderPath, 2048);
550
+ if (!absolutePath && !folderPath) return "";
551
+ if (folderPath) params.set("folder", folderPath);
552
+ } else if (kind === "file") {
553
+ const filePath = shareTargetString(targetRecord.filePath, 2048);
554
+ if (!absolutePath && !filePath) return "";
555
+ if (filePath) params.set("file", filePath);
556
+ if (targetRecord.view === "diff") params.set("view", "diff");
557
+ const line = shareTargetPositiveInteger(targetRecord.lineNumber);
558
+ const column = shareTargetPositiveInteger(targetRecord.column);
559
+ const endColumn = shareTargetPositiveInteger(targetRecord.endColumn);
560
+ if (line) params.set("line", line);
561
+ if (column) params.set("column", column);
562
+ if (endColumn) params.set("endColumn", endColumn);
563
+ }
564
+ if (absolutePath) {
565
+ const absoluteParams = new URLSearchParams(params);
566
+ absoluteParams.delete("agent");
567
+ absoluteParams.delete(kind === "folder" ? "folder" : "file");
568
+ if (absoluteParams.toString().length <= 1800) return absoluteParams.toString();
569
+ params.delete("path");
570
+ }
571
+ return params.toString();
572
+ }
573
+ function entryPathWithQuery(query = "", options = {}) {
574
+ const entryPath = BASE_PATH || "/";
575
+ const params = new URLSearchParams(query || "");
576
+ if (options.includeToken && authEnabled) {
577
+ params.set("token", tokenAuth.getToken());
578
+ }
579
+ const queryString = params.toString();
580
+ return queryString ? `${entryPath}?${queryString}` : entryPath;
581
+ }
582
+ function entryPathWithToken(targetQuery = "") {
583
+ return entryPathWithQuery(targetQuery, { includeToken: true });
584
+ }
585
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/share/qr-ticket"), express.json({ limit: "8kb" }), (req, res) => {
586
+ try {
587
+ const targetQuery = shareTargetQueryFromBody(req.body);
588
+ const ticket = qrShareTickets.create(authEnabled ? tokenAuth.getToken() : "", { targetQuery });
589
+ const shortPath = (0, import_index_html.routePath)(BASE_PATH, `/j/${ticket.code}`);
590
+ const longPath = entryPathWithToken(ticket.targetQuery);
591
+ res.json({
592
+ code: ticket.code,
593
+ expiresAt: ticket.expiresAt,
594
+ ttlMs: import_qr_share_tickets.SHARE_TICKET_TTL_MS,
595
+ shortPath,
596
+ shortUrl: absoluteClientUrl(req, shortPath),
597
+ longUrl: absoluteClientUrl(req, longPath),
598
+ tokenLabel: authEnabled ? tokenAuth.getToken() : ""
599
+ });
600
+ } catch (caught) {
601
+ const error = caughtError(caught);
602
+ res.status(500).json({ error: error.message || "Failed to create share ticket" });
603
+ }
604
+ });
605
+ app.delete((0, import_index_html.routePath)(BASE_PATH, "/api/share/qr-ticket/:code"), (req, res) => {
606
+ res.json({ revoked: qrShareTickets.revoke(req.params.code) });
607
+ });
608
+ app.use((0, import_index_html.routePath)(BASE_PATH, "/vendor"), express.static(path.join(frontendDir, "vendor")));
609
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/vendor/xterm/xterm.js"), (_req, res) => {
610
+ res.sendFile(xtermBrowserEntryPath);
611
+ });
612
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/vendor/xterm/addon-fit.js"), (_req, res) => {
613
+ res.sendFile(xtermFitEntryPath);
614
+ });
615
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/vendor/xterm/addon-webgl.js"), (_req, res) => {
616
+ res.sendFile(xtermWebglEntryPath);
617
+ });
618
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/vendor/xterm/xterm.css"), (_req, res) => {
619
+ res.sendFile(xtermCssPath);
620
+ });
621
+ app.use((0, import_index_html.routePath)(BASE_PATH, "/vendor/material-icons"), express.static(materialIconDir));
622
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/vendor/material-icons/:iconId.svg"), (req, res) => {
623
+ const fallbackIcon = String(req.params.iconId || "").startsWith("folder-") ? "folder.svg" : "file.svg";
624
+ res.sendFile(path.join(materialIconDir, fallbackIcon));
625
+ });
626
+ app.use(
627
+ (0, import_index_html.routePath)(BASE_PATH, "/assets"),
628
+ express.static(path.join(staticAppDir, "assets"), immutableAssetStaticOptions)
629
+ );
630
+ if (BASE_PATH) {
631
+ app.use("/assets", express.static(path.join(staticAppDir, "assets"), immutableAssetStaticOptions));
632
+ app.use("/farming-2", express.static(path.join(staticAppDir, "farming-2"), { index: false }));
633
+ }
634
+ const crtEntryPath = (0, import_index_html.routePath)(BASE_PATH, "/crt");
635
+ app.get(crtEntryPath, (req, res) => {
636
+ if (req.path.endsWith("/")) {
637
+ res.setHeader("Cache-Control", "no-cache");
638
+ res.sendFile(path.join(crtFrontendDir, "index.html"));
639
+ return;
640
+ }
641
+ const requestUrl = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
642
+ res.redirect(308, `${crtEntryPath}/${requestUrl.search}`);
643
+ });
644
+ app.use(`${crtEntryPath}/shared`, express.static(frontendDir, { index: false }));
645
+ app.get(`${crtEntryPath}/crt-markdown-renderer.js`, (_req, res) => {
646
+ res.sendFile(path.join(distDir, "crt-markdown-renderer.js"));
647
+ });
648
+ app.get(`${crtEntryPath}/crt-markdown-renderer.css`, (_req, res) => {
649
+ res.sendFile(path.join(distDir, "crt-markdown-renderer.css"));
650
+ });
651
+ app.use(`${crtEntryPath}/crt-markdown-assets`, express.static(path.join(distDir, "crt-markdown-assets"), { index: false }));
652
+ app.get(`${crtEntryPath}/crt-mermaid-renderer.js`, (_req, res) => {
653
+ res.sendFile(path.join(distDir, "crt-mermaid-renderer.js"));
654
+ });
655
+ app.use(`${crtEntryPath}/`, express.static(crtFrontendDir, { index: false }));
656
+ app.use(BASE_PATH || "/", express.static(staticAppDir, { index: false }));
657
+ app.use((0, import_index_html.routePath)(BASE_PATH, "/api/files"), (0, import_workspace_file_router.createWorkspaceFileRouter)(
658
+ agentManager,
659
+ workspaceFileService,
660
+ {
661
+ rootRegistry: workspaceRootRegistry
662
+ }
663
+ ));
664
+ app.use((0, import_index_html.routePath)(BASE_PATH, "/api/browsers"), (0, import_backend.createBrowserRouter)(
665
+ browserResourceManager,
666
+ workspaceRootRegistry,
667
+ agentManager
668
+ ));
669
+ app.use((0, import_index_html.routePath)(BASE_PATH, "/api/computers"), (0, import_backend2.createComputerRouter)(
670
+ computerResourceManager,
671
+ workspaceRootRegistry,
672
+ agentManager
673
+ ));
674
+ app.use((0, import_index_html.routePath)(BASE_PATH, "/api/review-sessions"), (0, import_review_session_router.createReviewSessionRouter)(reviewSessionService));
675
+ app.use((0, import_index_html.routePath)(BASE_PATH, "/api/reviews"), (0, import_review_diff_router.createReviewDiffRouter)(reviewDiffService, reviewSessionService));
676
+ app.use(
677
+ (0, import_index_html.routePath)(BASE_PATH, "/api/reviews"),
678
+ (0, import_review_state_router.createReviewStateRouter)(reviewStateStore)
679
+ );
680
+ if (process.env.NODE_ENV === "test" && process.env.FARMING_E2E_FAKE_EXECUTABLES === "1") {
681
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/control/e2e/close-websockets"), express.json(), (_req, res) => {
682
+ const clients = [...wss.clients].filter((client) => client.readyState === WebSocket.OPEN);
683
+ res.json({ closing: clients.length, code: 1013 });
684
+ setImmediate(() => {
685
+ clients.forEach((client) => client.close(1013, "terminal stream backpressure test"));
686
+ });
687
+ });
688
+ }
689
+ app.use((0, import_index_html.routePath)(BASE_PATH, "/api/control"), (0, import_control_api.createControlRouter)(
690
+ agentManager,
691
+ {
692
+ notifyUpdate: broadcastState,
693
+ allowConcurrentTestControl: process.env.NODE_ENV === "test" && process.env.FARMING_E2E_FAKE_EXECUTABLES === "1"
694
+ }
695
+ ));
696
+ app.get([
697
+ BASE_PATH || "/",
698
+ `${BASE_PATH || ""}/`,
699
+ (0, import_index_html.routePath)(BASE_PATH, "/code"),
700
+ (0, import_index_html.routePath)(BASE_PATH, "/code/"),
701
+ (0, import_index_html.routePath)(BASE_PATH, "/error-preview"),
702
+ (0, import_index_html.routePath)(BASE_PATH, "/review")
703
+ ].filter(Boolean), (req, res) => {
704
+ const indexPath = path.join(staticAppDir, "index.html");
705
+ fs.readFile(indexPath, "utf8", (error, html) => {
706
+ if (error) {
707
+ res.status(500).send("Farming frontend is not built");
708
+ return;
709
+ }
710
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
711
+ res.setHeader("Cache-Control", "no-cache");
712
+ const rewrittenHtml = (0, import_index_html.applyIndexHtmlAppearance)(
713
+ (0, import_index_html.rewriteIndexHtmlForBasePath)(html, BASE_PATH),
714
+ configManager.getSettings().appearance
715
+ );
716
+ const requestUrl = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
717
+ const requestToken = requestUrl.searchParams.has("token") ? tokenAuth.extractToken(req) : "";
718
+ const htmlWithAssetToken = authEnabled && requestToken && tokenAuth.verify(requestToken) ? (0, import_index_html.appendIndexHtmlAssetToken)(rewrittenHtml, requestToken) : rewrittenHtml;
719
+ res.send(htmlWithAssetToken);
720
+ });
721
+ });
722
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/executables"), (req, res) => {
723
+ const availableAgents = getAvailableAgentsForRequest();
724
+ res.setHeader("Cache-Control", "no-store");
725
+ res.json({
726
+ agents: availableAgents,
727
+ total: availableAgents.length
728
+ });
729
+ });
730
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/workspaces/complete"), async (req, res) => {
731
+ try {
732
+ const partialPath = typeof req.query.path === "string" ? req.query.path : "";
733
+ const requestedLimit = Number(req.query.limit);
734
+ const suggestions = await listWorkspacePathCompletions(partialPath, requestedLimit);
735
+ res.json({ suggestions });
736
+ } catch (caught) {
737
+ const error = caughtError(caught);
738
+ res.status(200).json({
739
+ suggestions: [],
740
+ error: error.message || "Failed to read directory"
741
+ });
742
+ }
743
+ });
744
+ app.use((0, import_index_html.routePath)(BASE_PATH, "/api/workspaces"), (0, import_workspace_directory.createWorkspaceDirectoryRouter)());
745
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/skills"), (_req, res) => {
746
+ res.json({ skills: (0, import_main_agent_skills.getMainAgentSkillsCatalog)() });
747
+ });
748
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agent-extensions"), async (_req, res) => {
749
+ try {
750
+ const availableAgents = getAvailableAgentsForRequest().filter((agent) => agent.category === "coding");
751
+ const availableByProvider = new Map(availableAgents.map((agent) => [
752
+ String(agent.name || agent.command || "").trim().toLowerCase(),
753
+ agent
754
+ ]));
755
+ const configuredProviders = Object.keys(configManager.getSettings().agentHomes || {});
756
+ const retainedHomes = [];
757
+ const agents = await Promise.all(configuredProviders.map(async (provider) => {
758
+ const agent = availableByProvider.get(provider);
759
+ const configuredHomes = configManager.getAgentHomes(provider);
760
+ const homes = configuredHomes.length > 0 ? configuredHomes : [{
761
+ id: "default",
762
+ path: "",
763
+ order: Number.MAX_SAFE_INTEGER,
764
+ newAgentDefaults: { model: "inherit", reasoning: "inherit", fast: "inherit" }
765
+ }];
766
+ return {
767
+ id: provider,
768
+ name: agent?.name || provider,
769
+ description: agent?.description || "",
770
+ available: Boolean(agent),
771
+ discoverySupported: true,
772
+ homes: await Promise.all(homes.map(async (home) => {
773
+ if (home.path) retainedHomes.push({ provider, path: home.path });
774
+ const inventory = await agentExtensionInventory.get(provider, home.path);
775
+ return {
776
+ id: home.id,
777
+ path: home.path,
778
+ order: home.order,
779
+ newAgentDefaults: home.newAgentDefaults,
780
+ configuration: {
781
+ rootId: (0, import_workspace_root_registry.rootIdForPath)(home.path),
782
+ ...inventory.configuration
783
+ },
784
+ extensions: inventory.extensions.map((extension) => ({
785
+ ...extension,
786
+ rootId: (0, import_workspace_root_registry.rootIdForPath)(home.path)
787
+ }))
788
+ };
789
+ }))
790
+ };
791
+ }));
792
+ await agentExtensionInventory.retain(retainedHomes);
793
+ res.setHeader("Cache-Control", "no-store");
794
+ res.json({ agents });
795
+ } catch (caught) {
796
+ const error = caughtError(caught);
797
+ console.error("Failed to read Agent extension inventory:", error);
798
+ res.setHeader("Cache-Control", "no-store");
799
+ res.status(500).json({ error: error.message || "Failed to read Agent extensions" });
800
+ }
801
+ });
802
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/slash-commands"), (req, res) => {
803
+ const provider = typeof req.query.provider === "string" ? req.query.provider : "";
804
+ const workspace = typeof req.query.workspace === "string" ? req.query.workspace : "";
805
+ const requested = requestedProviderHome(provider, req.query.homeId);
806
+ if (!requested.home) {
807
+ res.status(requested.status).json({ error: requested.error });
808
+ return;
809
+ }
810
+ res.json({
811
+ commands: (0, import_slash_command_discovery.discoverSlashCommands)({
812
+ provider,
813
+ providerHomePath: requested.home.path,
814
+ workspace
815
+ })
816
+ });
817
+ });
818
+ const IMAGE_ATTACHMENT_EXTENSIONS = {
819
+ "image/png": "png",
820
+ "image/jpeg": "jpg",
821
+ "image/gif": "gif",
822
+ "image/webp": "webp"
823
+ };
824
+ const AUDIO_ATTACHMENT_EXTENSIONS = {
825
+ "audio/aac": "aac",
826
+ "audio/flac": "flac",
827
+ "audio/mp4": "m4a",
828
+ "audio/mpeg": "mp3",
829
+ "audio/ogg": "ogg",
830
+ "audio/wav": "wav",
831
+ "audio/wave": "wav",
832
+ "audio/webm": "webm",
833
+ "audio/x-wav": "wav"
834
+ };
835
+ const IMAGE_ATTACHMENT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
836
+ const IMAGE_ATTACHMENT_GC_INTERVAL_MS = 60 * 60 * 1e3;
837
+ const IMAGE_ATTACHMENT_FILENAME_RE = /^pasted-image-\d+-[a-f0-9]{8}\.(?:png|jpg|gif|webp)$/;
838
+ const AUDIO_ATTACHMENT_FILENAME_RE = /^pasted-audio-\d+-[a-f0-9]{8}\.(?:aac|flac|m4a|mp3|ogg|wav|webm)$/;
839
+ let lastImageAttachmentGcAt = 0;
840
+ function imageAttachmentExtension(contentType) {
841
+ const normalized = String(contentType || "").split(";")[0].trim().toLowerCase();
842
+ return IMAGE_ATTACHMENT_EXTENSIONS[normalized] || "";
843
+ }
844
+ function audioAttachmentExtension(contentType) {
845
+ const normalized = String(contentType || "").split(";")[0].trim().toLowerCase();
846
+ return AUDIO_ATTACHMENT_EXTENSIONS[normalized] || "";
847
+ }
848
+ function imageAttachmentsDir() {
849
+ return path.join(configManager.farmingDir, "attachments");
850
+ }
851
+ async function cleanupExpiredImageAttachments(options = {}) {
852
+ const now = Date.now();
853
+ if (!options.force && now - lastImageAttachmentGcAt < IMAGE_ATTACHMENT_GC_INTERVAL_MS) return;
854
+ lastImageAttachmentGcAt = now;
855
+ const attachmentsDir = imageAttachmentsDir();
856
+ let entries = [];
857
+ try {
858
+ entries = await fs.promises.readdir(attachmentsDir, { withFileTypes: true });
859
+ } catch (caught) {
860
+ const error = caughtError(caught);
861
+ if (error && error.code !== "ENOENT") {
862
+ console.warn("Failed to scan image attachments:", error.message || error);
863
+ }
864
+ return;
865
+ }
866
+ const cutoff = now - IMAGE_ATTACHMENT_RETENTION_MS;
867
+ await Promise.all(entries.map(async (entry) => {
868
+ if (!entry.isFile() || !IMAGE_ATTACHMENT_FILENAME_RE.test(entry.name) && !AUDIO_ATTACHMENT_FILENAME_RE.test(entry.name)) return;
869
+ const filePath = path.join(attachmentsDir, entry.name);
870
+ try {
871
+ const stat = await fs.promises.stat(filePath);
872
+ if (stat.mtimeMs < cutoff) {
873
+ await fs.promises.unlink(filePath);
874
+ }
875
+ } catch (caught) {
876
+ const error = caughtError(caught);
877
+ if (!error || error.code !== "ENOENT") {
878
+ console.warn("Failed to remove expired image attachment:", error && (error.message || error));
879
+ }
880
+ }
881
+ }));
882
+ }
883
+ void cleanupExpiredImageAttachments({ force: true });
884
+ app.post(
885
+ (0, import_index_html.routePath)(BASE_PATH, "/api/attachments/image"),
886
+ express.raw({ type: "image/*", limit: "12mb" }),
887
+ (req, res) => {
888
+ const contentType = String(req.headers["content-type"] || "").split(";")[0].trim().toLowerCase();
889
+ const extension = imageAttachmentExtension(contentType);
890
+ if (!extension) {
891
+ res.status(415).json({ error: "unsupported image type" });
892
+ return;
893
+ }
894
+ if (!Buffer.isBuffer(req.body) || req.body.length === 0) {
895
+ res.status(400).json({ error: "empty image attachment" });
896
+ return;
897
+ }
898
+ const attachmentsDir = imageAttachmentsDir();
899
+ fs.mkdirSync(attachmentsDir, { recursive: true });
900
+ const filename = `pasted-image-${Date.now()}-${crypto.randomBytes(4).toString("hex")}.${extension}`;
901
+ const filePath = path.join(attachmentsDir, filename);
902
+ fs.writeFileSync(filePath, req.body);
903
+ void cleanupExpiredImageAttachments();
904
+ res.status(201).json({
905
+ path: filePath,
906
+ name: filename,
907
+ type: contentType,
908
+ size: req.body.length
909
+ });
910
+ }
911
+ );
912
+ app.post(
913
+ (0, import_index_html.routePath)(BASE_PATH, "/api/attachments/audio"),
914
+ express.raw({ type: "audio/*", limit: "25mb" }),
915
+ (req, res) => {
916
+ const contentType = String(req.headers["content-type"] || "").split(";")[0].trim().toLowerCase();
917
+ const extension = audioAttachmentExtension(contentType);
918
+ if (!extension) {
919
+ res.status(415).json({ error: "unsupported audio type" });
920
+ return;
921
+ }
922
+ if (!Buffer.isBuffer(req.body) || req.body.length === 0) {
923
+ res.status(400).json({ error: "empty audio attachment" });
924
+ return;
925
+ }
926
+ const attachmentsDir = imageAttachmentsDir();
927
+ fs.mkdirSync(attachmentsDir, { recursive: true });
928
+ const filename = `pasted-audio-${Date.now()}-${crypto.randomBytes(4).toString("hex")}.${extension}`;
929
+ const filePath = path.join(attachmentsDir, filename);
930
+ fs.writeFileSync(filePath, req.body);
931
+ void cleanupExpiredImageAttachments();
932
+ res.status(201).json({ path: filePath, name: filename, type: contentType, size: req.body.length });
933
+ }
934
+ );
935
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/codex/models"), async (req, res) => {
936
+ const requested = requestedProviderHome("codex", req.query.homeId);
937
+ if (!requested.home) {
938
+ res.status(requested.status).json({ error: requested.error });
939
+ return;
940
+ }
941
+ try {
942
+ const catalog = await codexModelOptionsCache.get(requested.home.path);
943
+ res.json(catalog);
944
+ } catch (caught) {
945
+ const error = caughtError(caught);
946
+ const timedOut = error && error.code === "CODEX_MODELS_TIMEOUT";
947
+ res.status(timedOut ? 504 : 502).json({
948
+ error: error && error.message ? error.message : "Failed to load Codex model catalog",
949
+ code: error && error.code ? error.code : "CODEX_MODELS_FAILED"
950
+ });
951
+ }
952
+ });
953
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/claude/settings"), (req, res) => {
954
+ const requested = requestedProviderHome("claude", req.query.homeId);
955
+ if (!requested.home) {
956
+ res.status(requested.status).json({ error: requested.error });
957
+ return;
958
+ }
959
+ res.json({
960
+ settings: (0, import_claude_settings.readClaudeSettingsSummary)({
961
+ settingsFile: path.join(requested.home.path, "settings.json")
962
+ })
963
+ });
964
+ });
965
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/usage"), async (req, res) => {
966
+ try {
967
+ const fresh = req.query.fresh === "1";
968
+ const live = req.query.live === "1";
969
+ if (fresh) usageMonitor.invalidateDailyCache();
970
+ const usage = await usageSummaryCache.get(
971
+ "summary",
972
+ fresh ? { force: true } : live ? { maxAgeMs: 15e3 } : {}
973
+ );
974
+ res.json({ usage });
975
+ } catch (caught) {
976
+ const error = caughtError(caught);
977
+ res.status(500).json({ error: error.message || "Failed to read usage information" });
978
+ }
979
+ });
980
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/usage/day"), async (req, res) => {
981
+ try {
982
+ const date = String(req.query.date || "").trim();
983
+ const detail = await usageMonitor.getUsageDay(date, {
984
+ fresh: req.query.fresh === "1",
985
+ live: req.query.live === "1"
986
+ });
987
+ res.json({ detail });
988
+ } catch (caught) {
989
+ const error = caughtError(caught);
990
+ const invalidDate = error instanceof RangeError;
991
+ res.status(invalidDate ? 400 : 500).json({
992
+ error: invalidDate ? error.message : error.message || "Failed to read usage day information"
993
+ });
994
+ }
995
+ });
996
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/codex/context-windows"), express.json(), async (req, res) => {
997
+ try {
998
+ const requestedIds = Array.isArray(req.body?.agentIds) ? req.body.agentIds.map((value) => String(value || "").trim()).filter(Boolean).slice(0, 20) : [];
999
+ const requestedIdSet = new Set(requestedIds);
1000
+ const agents = agentManager.getState().agents.filter((agent) => requestedIdSet.has(agent.id));
1001
+ const contextWindows = await codexContextWindowReader.readForAgents(agents);
1002
+ res.json({ contextWindows });
1003
+ } catch (caught) {
1004
+ const error = caughtError(caught);
1005
+ res.status(500).json({ error: error.message || "Failed to read Codex context windows" });
1006
+ }
1007
+ });
1008
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/update"), async (req, res) => {
1009
+ try {
1010
+ const update = await updateService.check({ force: req.query.force === "1" });
1011
+ res.json({ update });
1012
+ } catch (caught) {
1013
+ const error = caughtError(caught);
1014
+ res.status(502).json({ error: error.message || "Failed to check for updates" });
1015
+ }
1016
+ });
1017
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/update/install"), express.json(), async (req, res) => {
1018
+ try {
1019
+ const state = await updateService.startInstall({
1020
+ assetName: req.body && typeof req.body.assetName === "string" ? req.body.assetName : ""
1021
+ });
1022
+ res.status(202).json({ update: { state } });
1023
+ } catch (caught) {
1024
+ const error = caughtError(caught);
1025
+ res.status(500).json({ error: error.message || "Failed to start update" });
1026
+ }
1027
+ });
1028
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/update/restart"), express.json(), async (req, res) => {
1029
+ try {
1030
+ const state = await updateService.applyPreparedUpdate();
1031
+ res.status(202).json({ update: { state } });
1032
+ } catch (caught) {
1033
+ const error = caughtError(caught);
1034
+ res.status(500).json({ error: error.message || "Failed to restart for update" });
1035
+ }
1036
+ });
1037
+ function warmCodexExecutableVersionCache() {
1038
+ const startedAt = Date.now();
1039
+ try {
1040
+ const result = (0, import_executable_discovery.resolveCompatibleCodexExecutable)("");
1041
+ if (result.path) {
1042
+ console.log(`Codex executable ready: ${result.version || "unknown version"} (${Date.now() - startedAt}ms)`);
1043
+ }
1044
+ } catch (caught) {
1045
+ const error = caughtError(caught);
1046
+ console.warn("Failed to warm Codex executable version cache:", error.message || error);
1047
+ }
1048
+ }
1049
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/codex/sessions"), async (req, res) => {
1050
+ const requested = requestedProviderHome("codex", req.query.homeId);
1051
+ if (!requested.home) {
1052
+ res.status(requested.status).json({ error: requested.error });
1053
+ return;
1054
+ }
1055
+ const requestedLimit = Number(req.query.limit);
1056
+ const limit = Number.isFinite(requestedLimit) ? Math.max(0, Math.min(1e3, requestedLimit)) : 40;
1057
+ const requestedScanLimit = Number(req.query.scanLimit);
1058
+ const scanLimit = Number.isFinite(requestedScanLimit) ? Math.max(limit, Math.min(5e3, requestedScanLimit)) : void 0;
1059
+ const sessions = await (0, import_codex_session_history.listCodexSessions)({ codexHome: requested.home.path, limit, scanLimit });
1060
+ res.json({ sessions });
1061
+ });
1062
+ function providerSessionDisplayStates() {
1063
+ const states = /* @__PURE__ */ new Map();
1064
+ for (const record of configManager.listAgentSessionRecords()) {
1065
+ if (typeof record.providerSessionKey === "string" && record.providerSessionKey) {
1066
+ states.set(record.providerSessionKey, record);
1067
+ }
1068
+ }
1069
+ return states;
1070
+ }
1071
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agent-sessions"), async (req, res) => {
1072
+ try {
1073
+ res.setHeader("Cache-Control", "no-store");
1074
+ const requestedLimit = Number(req.query.limit);
1075
+ const limit = Number.isFinite(requestedLimit) ? Math.max(0, Math.min(1e3, requestedLimit)) : 60;
1076
+ const cursor = typeof req.query.cursor === "string" ? req.query.cursor : "";
1077
+ if (req.query.force === "1") agentSessionInventory.invalidate();
1078
+ const sessions = await currentAgentSessions();
1079
+ const page = (0, import_agent_session_history.paginateAgentSessions)(sessions, { limit: Math.max(1, limit), cursor });
1080
+ if (page.invalidCursor) {
1081
+ res.status(400).json({ error: "Invalid Agent session cursor" });
1082
+ return;
1083
+ }
1084
+ const displayStateByKey = providerSessionDisplayStates();
1085
+ res.json({
1086
+ sessions: page.sessions.map((session) => {
1087
+ const key = (0, import_main_page_session.mainPageAgentSessionKey)(session.provider, session.id, session.providerHomeId);
1088
+ const displayState = displayStateByKey.get(key);
1089
+ return typeof displayState?.displayPinned === "boolean" ? { ...session, pinned: displayState.displayPinned } : session;
1090
+ }),
1091
+ nextCursor: page.nextCursor,
1092
+ hasMore: page.hasMore,
1093
+ total: sessions.length
1094
+ });
1095
+ } catch (caught) {
1096
+ const error = caughtError(caught);
1097
+ console.error("Failed to read agent sessions:", error);
1098
+ res.status(500).json({ error: error.message || "Failed to read agent sessions" });
1099
+ }
1100
+ });
1101
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agent-sessions/search"), async (req, res) => {
1102
+ try {
1103
+ res.setHeader("Cache-Control", "no-store");
1104
+ const query = typeof req.query.q === "string" ? req.query.q : "";
1105
+ const requestedLimit = Number(req.query.limit);
1106
+ const limit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(1e3, requestedLimit)) : 100;
1107
+ const settings = configManager.getSettings();
1108
+ if (req.query.force === "1") agentSessionInventory.invalidate();
1109
+ const sessions = await withSearchTimeout(
1110
+ currentAgentSessions(),
1111
+ Number(settings.searchTimeoutMs) || 1e4
1112
+ );
1113
+ const result = (0, import_agent_session_history.searchAgentSessions)(sessions, query, {
1114
+ limit,
1115
+ projectNames: settings.projectNames
1116
+ });
1117
+ const displayStateByKey = providerSessionDisplayStates();
1118
+ res.json({
1119
+ ...result,
1120
+ sessions: result.sessions.map((session) => {
1121
+ const key = (0, import_main_page_session.mainPageAgentSessionKey)(session.provider, session.id, session.providerHomeId);
1122
+ const displayState = displayStateByKey.get(key);
1123
+ return typeof displayState?.displayPinned === "boolean" ? { ...session, pinned: displayState.displayPinned } : session;
1124
+ })
1125
+ });
1126
+ } catch (caught) {
1127
+ const error = caughtError(caught);
1128
+ if (error?.code === "ETIMEDOUT") {
1129
+ res.status(504).json({ error: error.message });
1130
+ return;
1131
+ }
1132
+ console.error("Failed to search agent sessions:", error);
1133
+ res.status(500).json({ error: error.message || "Failed to search Agent sessions" });
1134
+ }
1135
+ });
1136
+ app.patch((0, import_index_html.routePath)(BASE_PATH, "/api/agent-sessions/:provider/:sessionId"), express.json(), (req, res) => {
1137
+ const provider = (0, import_agent_session_history.normalizeProvider)(req.params.provider);
1138
+ const sessionId = String(req.params.sessionId || "").trim();
1139
+ const providerHomeId = String(req.body?.providerHomeId || "default").trim() || "default";
1140
+ if (!provider || !(0, import_agent_session_history.isSafeSessionId)(sessionId) || !/^[A-Za-z0-9._-]+$/.test(providerHomeId)) {
1141
+ res.status(400).json({ error: "Invalid Agent session" });
1142
+ return;
1143
+ }
1144
+ if (typeof req.body?.pinned !== "boolean") {
1145
+ res.status(400).json({ error: "Pinned state is required" });
1146
+ return;
1147
+ }
1148
+ const sessionKey = (0, import_main_page_session.mainPageAgentSessionKey)(provider, sessionId, providerHomeId);
1149
+ configManager.setProviderSessionDisplayState(sessionKey, { pinned: req.body.pinned });
1150
+ res.json({ sessionKey, pinned: req.body.pinned });
1151
+ });
1152
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/main-page-agent-sessions"), express.json(), (req, res) => {
1153
+ const operation = typeof req.body?.operation === "string" ? req.body.operation : "";
1154
+ const requestedKeys = Array.isArray(req.body?.sessionKeys) ? req.body.sessionKeys : [];
1155
+ const sessionKeys = [...new Set(requestedKeys.map((key) => String(key || "").trim()).filter(Boolean))];
1156
+ if (!["add", "remove"].includes(operation) || sessionKeys.length === 0 || sessionKeys.length > 50 || sessionKeys.some((key) => !(0, import_main_page_session.mainPageAgentSessionFromKey)(key))) {
1157
+ res.status(400).json({ error: "A valid main-page Agent session mutation is required" });
1158
+ return;
1159
+ }
1160
+ if (operation === "add") {
1161
+ [...sessionKeys].reverse().forEach((sessionKey) => {
1162
+ const session = (0, import_main_page_session.mainPageAgentSessionFromKey)(sessionKey);
1163
+ if (!session) return;
1164
+ configManager.rememberMainPageSessionKey(sessionKey, {
1165
+ provider: session.provider,
1166
+ providerSessionId: session.sessionId,
1167
+ providerSessionKey: sessionKey,
1168
+ providerHomeId: session.providerHomeId || "default"
1169
+ });
1170
+ });
1171
+ } else {
1172
+ configManager.removeMainPageSessionKeys(sessionKeys);
1173
+ }
1174
+ const mainPageSessionKeys = configManager.getMainPageSessionKeys();
1175
+ agentSessionInventory.invalidate();
1176
+ res.json({ success: true, mainPageSessionKeys });
1177
+ scheduleBroadcastState();
1178
+ });
1179
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/themes"), (req, res) => {
1180
+ const currentTheme = configManager.getSettings().theme || "terminal";
1181
+ res.json({
1182
+ themes: themeManager.getAllThemes(),
1183
+ current: currentTheme
1184
+ });
1185
+ });
1186
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/settings"), (req, res) => {
1187
+ res.json({
1188
+ settings: configManager.getSettings()
1189
+ });
1190
+ });
1191
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/workspaces/discovered"), (req, res) => {
1192
+ const requestedLimit = Number(req.query.limit);
1193
+ const limit = Number.isFinite(requestedLimit) ? Math.max(0, Math.min(20, requestedLimit)) : 12;
1194
+ const agent = typeof req.query.agent === "string" ? req.query.agent : "";
1195
+ const cacheToken = JSON.stringify({ limit, agent });
1196
+ workspaceDiscoveryCache.get(cacheToken).then((workspaces) => {
1197
+ res.json({ workspaces });
1198
+ }).catch((error) => {
1199
+ const message = caughtError(error).message;
1200
+ console.error("Failed to discover workspaces:", error);
1201
+ res.status(500).json({ error: message || "Failed to discover workspaces" });
1202
+ });
1203
+ });
1204
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/session-text"), async (req, res) => {
1205
+ const text = await agentManager.getAgentSessionText(req.params.agentId);
1206
+ if (text === null) {
1207
+ res.status(404).json({ error: "Agent not found" });
1208
+ return;
1209
+ }
1210
+ res.type("text/plain");
1211
+ res.send(text);
1212
+ });
1213
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/session-view"), async (req, res) => {
1214
+ const sessionView = await agentManager.getAgentSessionView(req.params.agentId);
1215
+ if (!sessionView) {
1216
+ res.status(404).json({ error: "Agent not found" });
1217
+ return;
1218
+ }
1219
+ res.json({ session: sessionView });
1220
+ });
1221
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-session"), async (req, res) => {
1222
+ try {
1223
+ res.json({
1224
+ session: agentManager.getAcpSession(req.params.agentId, {
1225
+ includeUpdates: req.query.includeUpdates === "1",
1226
+ includeEntries: req.query.includeEntries !== "0"
1227
+ })
1228
+ });
1229
+ } catch (caught) {
1230
+ const error = caughtError(caught);
1231
+ const message = error && error.message ? error.message : "Failed to read ACP session";
1232
+ res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
1233
+ }
1234
+ });
1235
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-transcript"), async (req, res) => {
1236
+ try {
1237
+ const requestedMaxTurns = Number.parseInt(String(req.query.maxTurns || ""), 10);
1238
+ const maxTurns = Number.isFinite(requestedMaxTurns) ? Math.min(MAX_TRANSCRIPT_TURNS, Math.max(20, requestedMaxTurns)) : DEFAULT_TRANSCRIPT_MAX_TURNS;
1239
+ const requestedRevision = Number.parseInt(String(req.query.sinceRevision || ""), 10);
1240
+ const externalMedia = req.query.media === "external-v1";
1241
+ res.json({ transcript: agentManager.getAcpTranscript(req.params.agentId, {
1242
+ maxTurns,
1243
+ ...externalMedia ? {
1244
+ mediaPathPrefix: (0, import_index_html.routePath)(
1245
+ BASE_PATH,
1246
+ `/api/agents/${encodeURIComponent(req.params.agentId)}/acp-media`
1247
+ )
1248
+ } : {},
1249
+ ...Number.isFinite(requestedRevision) && requestedRevision >= 0 ? { sinceRevision: requestedRevision } : {}
1250
+ }) });
1251
+ } catch (caught) {
1252
+ const error = caughtError(caught);
1253
+ const message = error && error.message ? error.message : "Failed to read ACP transcript";
1254
+ res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
1255
+ }
1256
+ });
1257
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-media/:entryId/:mediaId"), (req, res) => {
1258
+ try {
1259
+ const media = agentManager.getAcpTranscriptMedia(
1260
+ req.params.agentId,
1261
+ req.params.entryId,
1262
+ req.params.mediaId
1263
+ );
1264
+ const decoded = (0, import_acp_transcript.decodeAcpTranscriptMedia)(media);
1265
+ if (!decoded) {
1266
+ res.status(415).json({ error: "unsupported ACP transcript media" });
1267
+ return;
1268
+ }
1269
+ res.setHeader("Cache-Control", "private, max-age=31536000, immutable");
1270
+ res.setHeader("X-Content-Type-Options", "nosniff");
1271
+ res.type(decoded.mimeType).send(decoded.content);
1272
+ } catch (caught) {
1273
+ const error = caughtError(caught);
1274
+ const message = error && error.message ? error.message : "Failed to read ACP transcript media";
1275
+ const status = message === "Agent not found" || message === "ACP transcript entry not found" || message === "ACP transcript media not found" ? 404 : 409;
1276
+ res.status(status).json({ error: message });
1277
+ }
1278
+ });
1279
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-tool-details/:toolCallId"), async (req, res) => {
1280
+ try {
1281
+ res.json(agentManager.getAcpToolDetail(req.params.agentId, req.params.toolCallId));
1282
+ } catch (caught) {
1283
+ const error = caughtError(caught);
1284
+ const message = error && error.message ? error.message : "Failed to read ACP tool details";
1285
+ const status = message === "Agent not found" || message === "ACP tool call not found" ? 404 : 409;
1286
+ res.status(status).json({ error: message });
1287
+ }
1288
+ });
1289
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-terminals/:terminalId/kill"), (req, res) => {
1290
+ try {
1291
+ res.json(agentManager.killAcpTerminal(req.params.agentId, req.params.terminalId));
1292
+ } catch (caught) {
1293
+ const error = caughtError(caught);
1294
+ const message = error && error.message ? error.message : "Failed to stop ACP terminal";
1295
+ const status = message === "Agent not found" || message === "Unknown ACP terminal" ? 404 : 409;
1296
+ res.status(status).json({ error: message });
1297
+ }
1298
+ });
1299
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-terminals/:terminalId/input"), express.json({ limit: "96kb" }), (req, res) => {
1300
+ try {
1301
+ res.json(agentManager.inputAcpTerminal(
1302
+ req.params.agentId,
1303
+ req.params.terminalId,
1304
+ requiredString(req.body?.input)
1305
+ ));
1306
+ } catch (caught) {
1307
+ const error = caughtError(caught);
1308
+ const message = error && error.message ? error.message : "Failed to write ACP terminal input";
1309
+ const status = message === "Agent not found" || message === "Unknown ACP terminal" ? 404 : 409;
1310
+ res.status(status).json({ error: message });
1311
+ }
1312
+ });
1313
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-terminals/:terminalId/resize"), express.json(), (req, res) => {
1314
+ try {
1315
+ res.json(agentManager.resizeAcpTerminal(
1316
+ req.params.agentId,
1317
+ req.params.terminalId,
1318
+ Number(req.body?.cols),
1319
+ Number(req.body?.rows)
1320
+ ));
1321
+ } catch (caught) {
1322
+ const error = caughtError(caught);
1323
+ const message = error && error.message ? error.message : "Failed to resize ACP terminal";
1324
+ const status = message === "Agent not found" || message === "Unknown ACP terminal" ? 404 : 409;
1325
+ res.status(status).json({ error: message });
1326
+ }
1327
+ });
1328
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-subagents/:sessionId/cancel"), async (req, res) => {
1329
+ try {
1330
+ res.json(await agentManager.cancelAcpSubagent(req.params.agentId, req.params.sessionId));
1331
+ } catch (caught) {
1332
+ const error = caughtError(caught);
1333
+ const message = error && error.message ? error.message : "Failed to stop ACP subagent";
1334
+ const status = message === "Agent not found" || message === "ACP subagent session not found" ? 404 : 409;
1335
+ res.status(status).json({ error: message });
1336
+ }
1337
+ });
1338
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-patches/:toolCallId/decision"), express.json({ limit: "16kb" }), async (req, res) => {
1339
+ try {
1340
+ const requestedDecision = req.body?.decision;
1341
+ const decision = requestedDecision === "accept" ? "keep" : requestedDecision === "reject" ? "revert" : requestedDecision;
1342
+ if (decision !== "keep" && decision !== "revert") {
1343
+ throw new Error("ACP patch decision is invalid");
1344
+ }
1345
+ res.json(await agentManager.decideAcpPatch(
1346
+ req.params.agentId,
1347
+ req.params.toolCallId,
1348
+ requiredString(req.body?.path),
1349
+ decision
1350
+ ));
1351
+ } catch (caught) {
1352
+ const error = caughtError(caught);
1353
+ const message = error && error.message ? error.message : "Failed to decide ACP patch";
1354
+ const status = Number(error?.statusCode) || (message === "Agent not found" || message === "ACP tool call not found" ? 404 : 409);
1355
+ res.status(status).json({ error: message });
1356
+ }
1357
+ });
1358
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-sessions"), async (req, res) => {
1359
+ try {
1360
+ const result = await agentManager.listAcpSessions(req.params.agentId, {
1361
+ cwd: typeof req.query.cwd === "string" ? req.query.cwd : "",
1362
+ cursor: typeof req.query.cursor === "string" ? req.query.cursor : ""
1363
+ });
1364
+ res.json(result);
1365
+ } catch (caught) {
1366
+ const error = caughtError(caught);
1367
+ const message = error && error.message ? error.message : "Failed to list ACP sessions";
1368
+ res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
1369
+ }
1370
+ });
1371
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-permission"), express.json(), (req, res) => {
1372
+ try {
1373
+ const result = agentManager.respondToAcpPermission(
1374
+ req.params.agentId,
1375
+ requiredString(req.body?.requestId),
1376
+ requiredString(req.body?.optionId),
1377
+ req.body?.cancelled === true
1378
+ );
1379
+ res.json(result);
1380
+ } catch (caught) {
1381
+ const error = caughtError(caught);
1382
+ const message = error && error.message ? error.message : "Failed to respond to ACP permission";
1383
+ res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
1384
+ }
1385
+ });
1386
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-elicitation"), express.json(), (req, res) => {
1387
+ try {
1388
+ const result = agentManager.respondToAcpElicitation(
1389
+ req.params.agentId,
1390
+ requiredString(req.body?.requestId),
1391
+ requiredString(req.body?.action),
1392
+ req.body?.content
1393
+ );
1394
+ res.json(result);
1395
+ } catch (caught) {
1396
+ const error = caughtError(caught);
1397
+ const message = error && error.message ? error.message : "Failed to respond to ACP input request";
1398
+ res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
1399
+ }
1400
+ });
1401
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-session/authenticate"), express.json(), async (req, res) => {
1402
+ try {
1403
+ res.json(await agentManager.authenticateAcpAgent(req.params.agentId, requiredString(req.body?.methodId)));
1404
+ } catch (caught) {
1405
+ const error = caughtError(caught);
1406
+ const message = error && error.message ? error.message : "Failed to authenticate ACP Agent";
1407
+ res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
1408
+ }
1409
+ });
1410
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-session/logout"), async (req, res) => {
1411
+ try {
1412
+ res.json(await agentManager.logoutAcpAgent(req.params.agentId));
1413
+ } catch (caught) {
1414
+ const error = caughtError(caught);
1415
+ const message = error && error.message ? error.message : "Failed to log out ACP Agent";
1416
+ res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
1417
+ }
1418
+ });
1419
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-session/fork"), express.json(), async (req, res) => {
1420
+ try {
1421
+ res.json(await agentManager.forkAcpSession(req.params.agentId, req.body || {}));
1422
+ } catch (caught) {
1423
+ const error = caughtError(caught);
1424
+ const message = error && error.message ? error.message : "Failed to fork ACP session";
1425
+ res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
1426
+ }
1427
+ });
1428
+ app.delete((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-sessions/:sessionId"), async (req, res) => {
1429
+ try {
1430
+ res.json(await agentManager.deleteAcpSession(req.params.agentId, req.params.sessionId));
1431
+ } catch (caught) {
1432
+ const error = caughtError(caught);
1433
+ const message = error && error.message ? error.message : "Failed to delete ACP session";
1434
+ res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
1435
+ }
1436
+ });
1437
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-session/close"), async (req, res) => {
1438
+ try {
1439
+ res.json(await agentManager.closeAcpSession(req.params.agentId));
1440
+ } catch (caught) {
1441
+ const error = caughtError(caught);
1442
+ const message = error && error.message ? error.message : "Failed to close ACP session";
1443
+ res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
1444
+ }
1445
+ });
1446
+ app.patch((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-session"), express.json(), async (req, res) => {
1447
+ try {
1448
+ if (typeof req.body?.modeId === "string") {
1449
+ res.json(await agentManager.setAcpSessionMode(req.params.agentId, req.body.modeId));
1450
+ return;
1451
+ }
1452
+ if (Array.isArray(req.body?.configOptions)) {
1453
+ res.json(await agentManager.setAcpSessionConfigOptions(req.params.agentId, req.body.configOptions));
1454
+ return;
1455
+ }
1456
+ if (typeof req.body?.configId === "string" && Object.prototype.hasOwnProperty.call(req.body, "value") && isAcpConfigValue(req.body.value)) {
1457
+ res.json(await agentManager.setAcpSessionConfigOption(
1458
+ req.params.agentId,
1459
+ req.body.configId,
1460
+ req.body.value
1461
+ ));
1462
+ return;
1463
+ }
1464
+ res.status(400).json({ error: "ACP modeId, configOptions, or configId/value is required" });
1465
+ } catch (caught) {
1466
+ const error = caughtError(caught);
1467
+ const message = error && error.message ? error.message : "Failed to update ACP session";
1468
+ res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
1469
+ }
1470
+ });
1471
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/codex-terminal-profile"), express.json(), async (req, res) => {
1472
+ const requestAbort = new globalThis.AbortController();
1473
+ const abortRequest = () => requestAbort.abort(new Error("Terminal profile request was canceled"));
1474
+ const abortClosedResponse = () => {
1475
+ if (!res.writableEnded) abortRequest();
1476
+ };
1477
+ req.once("aborted", abortRequest);
1478
+ res.once("close", abortClosedResponse);
1479
+ try {
1480
+ const profile = await agentManager.setCodexTerminalProfile(req.params.agentId, {
1481
+ model: optionalString(req.body?.model),
1482
+ effort: optionalString(req.body?.effort),
1483
+ serviceTier: optionalString(req.body?.serviceTier)
1484
+ }, {
1485
+ signal: requestAbort.signal,
1486
+ timeoutMs: 44e3
1487
+ });
1488
+ res.json({ profile });
1489
+ } catch (caught) {
1490
+ const error = caughtError(caught);
1491
+ const message = error && error.message ? error.message : "Failed to update Codex Terminal profile";
1492
+ const status = message === "Agent not found" ? 404 : /^A valid Codex /.test(message) ? 400 : 409;
1493
+ if (!res.headersSent && !res.destroyed) res.status(status).json({ error: message });
1494
+ } finally {
1495
+ req.off("aborted", abortRequest);
1496
+ res.off("close", abortClosedResponse);
1497
+ }
1498
+ });
1499
+ app.patch((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId"), express.json(), async (req, res) => {
1500
+ if (!await requireAgentRecoveryForHttp(res)) return;
1501
+ const body = req.body || {};
1502
+ const updates = {};
1503
+ const providedPatchFields = [
1504
+ "customTitle",
1505
+ "task",
1506
+ "pinned",
1507
+ "unread",
1508
+ "archived",
1509
+ "readAttentionSeq",
1510
+ "readOutputEpoch",
1511
+ "readOutputSeq",
1512
+ "launchPermissionMode",
1513
+ "agentRuntimeMode"
1514
+ ].filter((field) => Object.prototype.hasOwnProperty.call(body, field));
1515
+ const lifecyclePatchFields = providedPatchFields.filter((field) => field === "launchPermissionMode" || field === "agentRuntimeMode" || field === "archived" && body.archived === true);
1516
+ if (lifecyclePatchFields.length > 0 && providedPatchFields.length > 1) {
1517
+ res.status(400).json({
1518
+ error: "Archive, permission restart, and runtime switch must be requested separately from other Agent updates"
1519
+ });
1520
+ return;
1521
+ }
1522
+ const ordinaryPatchGroups = [
1523
+ providedPatchFields.includes("customTitle") ? "customTitle" : "",
1524
+ providedPatchFields.includes("task") ? "task" : "",
1525
+ providedPatchFields.some((field) => [
1526
+ "pinned",
1527
+ "unread",
1528
+ "archived",
1529
+ "readAttentionSeq",
1530
+ "readOutputEpoch",
1531
+ "readOutputSeq"
1532
+ ].includes(field)) ? "flags" : ""
1533
+ ].filter(Boolean);
1534
+ if (ordinaryPatchGroups.length > 1) {
1535
+ res.status(400).json({
1536
+ error: "Agent title, task, and flags must be updated in separate requests"
1537
+ });
1538
+ return;
1539
+ }
1540
+ await agentManager.whenAgentLifecycleIdle(req.params.agentId);
1541
+ if (typeof body.customTitle === "string") {
1542
+ const result = agentManager.renameAgent(req.params.agentId, body.customTitle);
1543
+ if (result.error) {
1544
+ const status = result.error === "Agent not found" ? 404 : result.error.startsWith("Failed to ") ? 500 : 409;
1545
+ res.status(status).json({ error: result.error });
1546
+ return;
1547
+ }
1548
+ updates.customTitle = result.customTitle;
1549
+ }
1550
+ if (typeof body.task === "string") {
1551
+ const result = agentManager.setAgentTask(req.params.agentId, body.task);
1552
+ if (result.error) {
1553
+ const status = result.error === "Agent not found" ? 404 : result.error.startsWith("Failed to ") ? 500 : 409;
1554
+ res.status(status).json({ error: result.error });
1555
+ return;
1556
+ }
1557
+ updates.task = result.task;
1558
+ }
1559
+ const flagPatch = {};
1560
+ ["pinned", "unread", "archived"].forEach((flagName) => {
1561
+ if (typeof body[flagName] === "boolean") {
1562
+ flagPatch[flagName] = body[flagName];
1563
+ }
1564
+ });
1565
+ if (typeof body.readAttentionSeq === "number" && Number.isFinite(body.readAttentionSeq)) {
1566
+ flagPatch.readAttentionSeq = body.readAttentionSeq;
1567
+ }
1568
+ if (typeof body.readOutputEpoch === "string" && body.readOutputEpoch && typeof body.readOutputSeq === "number" && Number.isFinite(body.readOutputSeq)) {
1569
+ flagPatch.readOutputEpoch = body.readOutputEpoch;
1570
+ flagPatch.readOutputSeq = body.readOutputSeq;
1571
+ }
1572
+ if (flagPatch.archived === true) {
1573
+ const result = await agentManager.archiveAgent(req.params.agentId);
1574
+ if (result.error) {
1575
+ const status = result.stopped === true ? 409 : result.error === "Agent not found" ? 404 : 400;
1576
+ res.status(status).json(result);
1577
+ return;
1578
+ }
1579
+ Object.assign(updates, result);
1580
+ delete updates.agentId;
1581
+ delete flagPatch.archived;
1582
+ }
1583
+ let flagUpdateRequiresState = false;
1584
+ if (Object.keys(flagPatch).length > 0) {
1585
+ const result = agentManager.updateAgentFlags(req.params.agentId, flagPatch);
1586
+ if (result.error) {
1587
+ const status = result.error === "Agent not found" ? 404 : result.error.startsWith("Failed to ") ? 500 : 409;
1588
+ res.status(status).json({ error: result.error });
1589
+ return;
1590
+ }
1591
+ Object.assign(updates, result);
1592
+ delete updates.agentId;
1593
+ flagUpdateRequiresState = "requiresState" in result && result.requiresState === true;
1594
+ }
1595
+ if (typeof body.launchPermissionMode === "string") {
1596
+ const result = await agentManager.syncCodexTerminalPermissionMode(req.params.agentId, body.launchPermissionMode);
1597
+ if (result.error) {
1598
+ const status = result.error === "Agent not found" ? 404 : 400;
1599
+ res.status(status).json({ error: result.error });
1600
+ return;
1601
+ }
1602
+ updates.launchPermissionMode = result.launchPermissionMode;
1603
+ if (result.restarted === true) updates.restarted = true;
1604
+ if (result.restartedAgentId) updates.restartedAgentId = result.restartedAgentId;
1605
+ }
1606
+ if (typeof body.agentRuntimeMode === "string") {
1607
+ if (!(0, import_agent_runtime_binding.isAgentRuntimeModeRequest)(body.agentRuntimeMode)) {
1608
+ res.status(400).json({ error: "Unsupported Agent runtime mode" });
1609
+ return;
1610
+ }
1611
+ const result = await agentManager.restartAgentRuntimeMode(req.params.agentId, body.agentRuntimeMode);
1612
+ if (result.error) {
1613
+ const status = result.error === "Agent not found" ? 404 : 400;
1614
+ res.status(status).json({ error: result.error });
1615
+ return;
1616
+ }
1617
+ updates.agentRuntimeMode = result.agentRuntimeMode;
1618
+ if (result.restarted === true) updates.restarted = true;
1619
+ if (result.restartedAgentId) updates.restartedAgentId = result.restartedAgentId;
1620
+ if (result.switchFailed === true) updates.switchFailed = true;
1621
+ if (result.warning) updates.warning = result.warning;
1622
+ }
1623
+ if (Object.keys(updates).length === 0) {
1624
+ res.status(400).json({ error: "customTitle, task, pinned, unread, archived, readAttentionSeq, readOutputEpoch/readOutputSeq, launchPermissionMode, or agentRuntimeMode is required" });
1625
+ return;
1626
+ }
1627
+ if (flagUpdateRequiresState || typeof body.task === "string" || typeof body.customTitle === "string" || typeof body.launchPermissionMode === "string" || typeof body.agentRuntimeMode === "string") {
1628
+ scheduleBroadcastState();
1629
+ }
1630
+ res.json({ agentId: req.params.agentId, ...updates });
1631
+ });
1632
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/reorder"), express.json(), async (req, res) => {
1633
+ if (!await requireAgentRecoveryForHttp(res)) return;
1634
+ await agentManager.whenAgentLifecycleIdle(req.params.agentId);
1635
+ const result = agentManager.reorderAgent(req.params.agentId, {
1636
+ beforeAgentId: optionalString(req.body?.beforeAgentId),
1637
+ afterAgentId: optionalString(req.body?.afterAgentId)
1638
+ });
1639
+ if (result.error) {
1640
+ const status = result.error === "Agent not found" ? 404 : result.error.startsWith("Failed to ") ? 500 : 409;
1641
+ res.status(status).json({ error: result.error });
1642
+ return;
1643
+ }
1644
+ scheduleBroadcastState();
1645
+ res.json(result);
1646
+ });
1647
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/fork"), express.json(), async (req, res) => {
1648
+ if (!await requireAgentRecoveryForHttp(res)) return;
1649
+ const mode = req.body && typeof req.body.mode === "string" ? req.body.mode : "same-worktree";
1650
+ if (!isForkMode(mode)) {
1651
+ res.status(400).json({ error: "Unsupported Fork mode" });
1652
+ return;
1653
+ }
1654
+ const targetRuntime = req.body && typeof req.body.targetRuntime === "string" ? req.body.targetRuntime : "";
1655
+ if (targetRuntime && targetRuntime !== "chat") {
1656
+ res.status(400).json({ error: "Unsupported Fork target runtime" });
1657
+ return;
1658
+ }
1659
+ const requestId = typeof req.body?.requestId === "string" ? req.body.requestId.trim() : "";
1660
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
1661
+ res.status(400).json({ error: "Fork requires a valid requestId" });
1662
+ return;
1663
+ }
1664
+ const result = await agentManager.forkAgent(req.params.agentId, mode, {
1665
+ requestId,
1666
+ ...targetRuntime === "chat" ? { targetRuntime: "chat" } : {},
1667
+ ...Number.isSafeInteger(req.body?.expectedRevision) ? { expectedRevision: optionalNumber(req.body.expectedRevision) } : {}
1668
+ });
1669
+ if (result.error) {
1670
+ const status = result.error === "Agent not found" ? 404 : 400;
1671
+ res.status(status).json(result);
1672
+ return;
1673
+ }
1674
+ try {
1675
+ const membership = configManager.mountProjectWorkspace(result.workspace);
1676
+ result.projectWorkspaces = membership.projectWorkspaces;
1677
+ result.pinnedProjectWorkspaces = membership.pinnedProjectWorkspaces;
1678
+ } catch (caught) {
1679
+ const error = caughtError(caught);
1680
+ broadcastState();
1681
+ const mountError = error.message || "Failed to create Project";
1682
+ res.status(500).json({
1683
+ ...result,
1684
+ error: `${mountError}. Retry the same Fork request to reconcile Project membership.`,
1685
+ retryable: true
1686
+ });
1687
+ return;
1688
+ }
1689
+ broadcastState();
1690
+ res.status(201).json(result);
1691
+ });
1692
+ function resolveProjectActionRoot(rootId) {
1693
+ const root = workspaceRootRegistry.resolve(rootId);
1694
+ if (!root || root.kind === "global") {
1695
+ throw new import_workspace_file_service.WorkspaceFileError("project workspace is required", 400);
1696
+ }
1697
+ return root;
1698
+ }
1699
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/projects/reveal"), express.json(), async (req, res) => {
1700
+ try {
1701
+ const root = resolveProjectActionRoot(typeof req.body?.rootId === "string" ? req.body.rootId : "");
1702
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer" : "xdg-open";
1703
+ await execFileAsync(command, [root.canonicalPath], {
1704
+ timeout: 15e3,
1705
+ maxBuffer: 1024 * 1024
1706
+ });
1707
+ res.json({ revealed: true, workspace: root.canonicalPath });
1708
+ } catch (caught) {
1709
+ const error = caughtError(caught);
1710
+ const status = error instanceof import_workspace_file_service.WorkspaceFileError ? error.statusCode || 500 : 500;
1711
+ res.status(status).json({ error: error.message || "Failed to reveal project workspace" });
1712
+ }
1713
+ });
1714
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/projects/mount"), express.json(), async (req, res) => {
1715
+ try {
1716
+ const workspace = await canonicalProjectWorkspace(typeof req.body?.workspace === "string" ? req.body.workspace : "");
1717
+ const membership = configManager.mountProjectWorkspace(workspace);
1718
+ broadcastState();
1719
+ res.json(membership);
1720
+ } catch (caught) {
1721
+ const error = caughtError(caught);
1722
+ res.status(400).json({ error: error.message || "Failed to create Project" });
1723
+ }
1724
+ });
1725
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/projects/remove"), express.json(), (req, res) => {
1726
+ try {
1727
+ const membership = configManager.removeProjectWorkspace(req.body?.workspace);
1728
+ broadcastState();
1729
+ res.json(membership);
1730
+ } catch (caught) {
1731
+ const error = caughtError(caught);
1732
+ res.status(400).json({ error: error.message || "Failed to remove Project" });
1733
+ }
1734
+ });
1735
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/projects/pin"), express.json(), (req, res) => {
1736
+ try {
1737
+ const membership = configManager.setProjectWorkspacePinned(
1738
+ req.body?.workspace,
1739
+ req.body?.pinned === true
1740
+ );
1741
+ broadcastState();
1742
+ res.json(membership);
1743
+ } catch (caught) {
1744
+ const error = caughtError(caught);
1745
+ res.status(400).json({ error: error.message || "Failed to update Project pin" });
1746
+ }
1747
+ });
1748
+ app.patch((0, import_index_html.routePath)(BASE_PATH, "/api/projects/name"), express.json(), (req, res) => {
1749
+ try {
1750
+ const result = configManager.setProjectName(req.body?.workspace, req.body?.name);
1751
+ scheduleBroadcastState();
1752
+ res.json(result);
1753
+ } catch (caught) {
1754
+ const error = caughtError(caught);
1755
+ res.status(400).json({ error: error.message || "Failed to rename Project" });
1756
+ }
1757
+ });
1758
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/projects/create-worktree"), express.json(), async (req, res) => {
1759
+ const requestId = typeof req.body?.requestId === "string" ? req.body.requestId.trim() : "";
1760
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
1761
+ res.status(400).json({ error: "Project worktree creation requires a valid requestId" });
1762
+ return;
1763
+ }
1764
+ try {
1765
+ const root = resolveProjectActionRoot(typeof req.body?.rootId === "string" ? req.body.rootId : "");
1766
+ const created = await agentManager.createPermanentWorktree(root.canonicalPath, { requestId });
1767
+ if (!isRecord(created)) throw new Error("Project worktree creation returned an invalid result");
1768
+ broadcastState();
1769
+ res.status(201).json({
1770
+ ...created
1771
+ });
1772
+ } catch (caught) {
1773
+ const error = caughtError(caught);
1774
+ const status = error instanceof import_workspace_file_service.WorkspaceFileError ? error.statusCode || 400 : 400;
1775
+ const operation = configManager.getProjectOperation?.(requestId);
1776
+ res.status(status).json({
1777
+ error: error.message || "Failed to create permanent worktree",
1778
+ requestId,
1779
+ ...operation?.state === "pending" ? { retryable: true } : {},
1780
+ ...["unknown", "blocked"].includes(operation?.state || "") ? { uncertain: true } : {}
1781
+ });
1782
+ }
1783
+ });
1784
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/projects/delete-worktree"), express.json(), async (req, res) => {
1785
+ if (!await requireAgentRecoveryForHttp(res)) return;
1786
+ const body = req.body || {};
1787
+ const workspace = typeof body.workspace === "string" ? body.workspace : "";
1788
+ const requestId = typeof body.requestId === "string" ? body.requestId.trim() : "";
1789
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
1790
+ res.status(400).json({ error: "Project worktree deletion requires a valid requestId" });
1791
+ return;
1792
+ }
1793
+ const result = await agentManager.deleteForkWorktreeProject(workspace, {
1794
+ force: body.force === true,
1795
+ requestId
1796
+ });
1797
+ if (result.error) {
1798
+ broadcastState();
1799
+ const status = result.requiresForce ? 409 : result.error === "Workspace not found" || result.error === "Workspace is required" ? 404 : 400;
1800
+ res.status(status).json(result);
1801
+ return;
1802
+ }
1803
+ broadcastState();
1804
+ res.json(result);
1805
+ });
1806
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/codex/sessions/:sessionId/resume"), express.json(), async (req, res) => {
1807
+ await startResumedAgentSession(req, res, "codex", req.params.sessionId);
1808
+ });
1809
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agent-sessions/:provider/:sessionId/resume"), express.json(), async (req, res) => {
1810
+ await startResumedAgentSession(req, res, req.params.provider, req.params.sessionId);
1811
+ });
1812
+ const pendingResumeStarts = /* @__PURE__ */ new Map();
1813
+ const pendingProjectWorkspaceResolutions = /* @__PURE__ */ new Map();
1814
+ function resumedAgentStartKey(provider, sessionId, options = {}) {
1815
+ return [
1816
+ provider,
1817
+ options.providerHomeId || "default",
1818
+ sessionId,
1819
+ options.fork === true ? "fork" : "resume",
1820
+ options.asMain === true ? "main" : "agent"
1821
+ ].join(":");
1822
+ }
1823
+ function findResumedAgent(provider, sessionId, providerHomeId = "") {
1824
+ return (0, import_main_page_session.findActiveAgentClaimingSession)(agentManager.getState().agents, provider, { id: sessionId, providerHomeId });
1825
+ }
1826
+ function isMainAgentSessionWorkspace(session) {
1827
+ const values = [session?.cwd, session?.workspace];
1828
+ return values.some((value) => {
1829
+ const normalized = String(value || "").trim().replace(/[\\/]+$/, "");
1830
+ return normalized === "~/.farming" || /(^|[/\\])\.farming$/.test(normalized);
1831
+ });
1832
+ }
1833
+ function rememberMainPageAgentSession(provider, sessionId, providerHomeId = "") {
1834
+ const sessionKey = (0, import_main_page_session.mainPageAgentSessionKey)(provider, sessionId, providerHomeId);
1835
+ if (typeof configManager.rememberMainPageSessionKey === "function") {
1836
+ configManager.rememberMainPageSessionKey(sessionKey, {
1837
+ provider,
1838
+ providerSessionId: sessionId,
1839
+ providerSessionKey: sessionKey,
1840
+ providerHomeId: providerHomeId || "default",
1841
+ source: "resume"
1842
+ });
1843
+ return;
1844
+ }
1845
+ const currentKeys = typeof configManager.getMainPageSessionKeys === "function" ? configManager.getMainPageSessionKeys() : Array.isArray(configManager.getSettings().mainPageSessionKeys) ? configManager.getSettings().mainPageSessionKeys : [];
1846
+ configManager.updateSettings({
1847
+ mainPageSessionKeys: [
1848
+ sessionKey,
1849
+ ...currentKeys.filter((key) => key !== sessionKey)
1850
+ ]
1851
+ });
1852
+ }
1853
+ function forgetMainPageAgentSession(provider, sessionId, providerHomeId = "") {
1854
+ const sessionKey = (0, import_main_page_session.mainPageAgentSessionKey)(provider, sessionId, providerHomeId);
1855
+ if (typeof configManager.removeMainPageSessionKey === "function") {
1856
+ configManager.removeMainPageSessionKey(sessionKey);
1857
+ return;
1858
+ }
1859
+ const currentKeys = typeof configManager.getMainPageSessionKeys === "function" ? configManager.getMainPageSessionKeys() : Array.isArray(configManager.getSettings().mainPageSessionKeys) ? configManager.getSettings().mainPageSessionKeys : [];
1860
+ if (!currentKeys.includes(sessionKey)) return;
1861
+ configManager.updateSettings({
1862
+ mainPageSessionKeys: currentKeys.filter((key) => key !== sessionKey)
1863
+ });
1864
+ }
1865
+ function savedFarmingAgentSession(provider, sessionId, providerHomeId = "") {
1866
+ if (typeof configManager.getAgentSessionRecordForProviderSessionKey !== "function") return null;
1867
+ const sessionKey = (0, import_main_page_session.mainPageAgentSessionKey)(provider, sessionId, providerHomeId);
1868
+ return sessionKey ? configManager.getAgentSessionRecordForProviderSessionKey(sessionKey) : null;
1869
+ }
1870
+ async function resumeAgentSessionById(provider, rawSessionId, options = {}) {
1871
+ const normalizedProvider = (0, import_agent_session_history.normalizeProvider)(provider);
1872
+ const sessionId = String(rawSessionId || "").trim();
1873
+ const providerHomeId = typeof options.providerHomeId === "string" && options.providerHomeId.trim() ? options.providerHomeId.trim() : "default";
1874
+ if (!normalizedProvider || !(0, import_agent_session_history.isSafeSessionId)(sessionId)) {
1875
+ return { error: "invalid session id", status: 400 };
1876
+ }
1877
+ const shouldFork = options.fork === true;
1878
+ const requestedAsMain = options.asMain === true && !shouldFork;
1879
+ const shouldRememberMainPageSession = options.rememberMainPageSession !== false && !shouldFork && !requestedAsMain;
1880
+ const pendingResumeId = resumedAgentStartKey(normalizedProvider, sessionId, {
1881
+ fork: shouldFork,
1882
+ asMain: requestedAsMain,
1883
+ providerHomeId
1884
+ });
1885
+ if (!shouldFork) {
1886
+ const existingAgent = findResumedAgent(normalizedProvider, sessionId, providerHomeId);
1887
+ if (existingAgent) {
1888
+ if (shouldRememberMainPageSession) rememberMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
1889
+ const projectWorkspace = requestedAsMain ? "" : await canonicalProjectWorkspace(
1890
+ existingAgent.gitWorktree?.workspace || existingAgent.projectWorkspace || existingAgent.cwd || null
1891
+ );
1892
+ return { agentId: existingAgent.id, projectWorkspace, reused: true };
1893
+ }
1894
+ }
1895
+ const pendingStart = pendingResumeStarts.get(pendingResumeId);
1896
+ if (pendingStart) {
1897
+ const result2 = await pendingStart;
1898
+ if (result2.error) {
1899
+ return result2;
1900
+ }
1901
+ if (shouldRememberMainPageSession) rememberMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
1902
+ return {
1903
+ agentId: result2.agentId,
1904
+ projectWorkspace: result2.projectWorkspace || "",
1905
+ reused: true,
1906
+ pending: true,
1907
+ ...result2.claimed ? { claimed: true } : {}
1908
+ };
1909
+ }
1910
+ const startPromise = (async () => {
1911
+ let session = await (0, import_agent_session_history.findAgentSession)(normalizedProvider, sessionId, {
1912
+ limit: 1e3,
1913
+ providerLimit: 1e3,
1914
+ scanLimit: 5e3,
1915
+ providerHomeId,
1916
+ providerHomes: configuredProviderHomes()
1917
+ });
1918
+ if (options.allowUnarchiveArchived === true && normalizedProvider === "codex" && !requestedAsMain) {
1919
+ const providerHomes = configuredProviderHomes();
1920
+ const configuredHomePath = (providerHomes.codex || []).find((home) => home.id === providerHomeId)?.path || "";
1921
+ const unarchiveResult = await agentManager.ensureCodexSessionAvailable(sessionId, {
1922
+ providerHomeId,
1923
+ providerHomePath: session?.providerHomePath || configuredHomePath,
1924
+ providerHomes,
1925
+ cwd: session?.cwd || session?.workspace || ""
1926
+ });
1927
+ if (unarchiveResult?.error) return unarchiveResult;
1928
+ session = await (0, import_agent_session_history.findAgentSession)(normalizedProvider, sessionId, {
1929
+ limit: 1e3,
1930
+ providerLimit: 1e3,
1931
+ scanLimit: 5e3,
1932
+ providerHomeId,
1933
+ providerHomes
1934
+ }) || (session ? { ...session, archived: false } : session);
1935
+ }
1936
+ if (session && session.archived && !shouldFork) {
1937
+ forgetMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
1938
+ return {
1939
+ error: `${session.providerName || normalizedProvider} session is archived. Unarchive it before resuming.`,
1940
+ status: 409,
1941
+ archived: true
1942
+ };
1943
+ }
1944
+ if (!shouldFork && !requestedAsMain) {
1945
+ const claimingAgent = (0, import_main_page_session.findActiveAgentClaimingSession)(agentManager.getState().agents, normalizedProvider, {
1946
+ id: sessionId,
1947
+ providerHomeId,
1948
+ ...session || {}
1949
+ });
1950
+ if (claimingAgent) {
1951
+ if (shouldRememberMainPageSession) rememberMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
1952
+ const projectWorkspace2 = await canonicalProjectWorkspace(
1953
+ claimingAgent.gitWorktree?.workspace || claimingAgent.projectWorkspace || claimingAgent.cwd || null
1954
+ );
1955
+ return { agentId: claimingAgent.id, projectWorkspace: projectWorkspace2, reused: true, claimed: true };
1956
+ }
1957
+ }
1958
+ const resumeAsMain = requestedAsMain && isMainAgentSessionWorkspace(session);
1959
+ if (requestedAsMain && !resumeAsMain) {
1960
+ return { error: "session is not a Main Agent session", status: 400 };
1961
+ }
1962
+ const savedSession = shouldFork ? null : savedFarmingAgentSession(normalizedProvider, sessionId, session ? session.providerHomeId || providerHomeId : providerHomeId);
1963
+ const hasRequestedCustomTitle = Object.prototype.hasOwnProperty.call(options, "customTitle");
1964
+ const savedAttentionSeq = Number(savedSession?.attentionSeq) || 0;
1965
+ const savedReadAttentionSeq = Number(savedSession?.readAttentionSeq) || 0;
1966
+ const workingDirectory = session?.cwd || session?.workspace || null;
1967
+ const projectWorkspace = resumeAsMain ? "" : await canonicalProjectWorkspace(
1968
+ String(savedSession?.projectWorkspace || (session ? session.workspace || session.cwd || "" : workingDirectory) || "")
1969
+ );
1970
+ const command = (0, import_agent_session_history.buildAgentSessionResumeCommand)(normalizedProvider, sessionId, {
1971
+ fork: shouldFork,
1972
+ cwd: workingDirectory,
1973
+ modelProvider: normalizedProvider === "codex" ? (0, import_agent_session_history.resolveCodexResumeModelProvider)(session?.providerHomePath || "") : ""
1974
+ });
1975
+ if (!command) {
1976
+ return { error: "invalid session id", status: 400 };
1977
+ }
1978
+ return new Promise((resolve) => {
1979
+ const resolvedProviderHomeId = session ? session.providerHomeId || providerHomeId : providerHomeId;
1980
+ const resumeSource = (0, import_main_page_session.resumedAgentSource)(normalizedProvider, sessionId, resolvedProviderHomeId);
1981
+ const startResult = agentManager.startAgent(command, workingDirectory, (agentId, error) => {
1982
+ if (error) {
1983
+ resolve({ error, status: 400 });
1984
+ return;
1985
+ }
1986
+ if (!agentId) {
1987
+ resolve({ error: "failed to resume agent session", status: 500 });
1988
+ return;
1989
+ }
1990
+ resolve({ agentId, projectWorkspace });
1991
+ }, {
1992
+ wantsMain: resumeAsMain,
1993
+ task: savedSession?.task || (session ? session.title : ""),
1994
+ workflowTemplate: savedSession?.workflowTemplate || "",
1995
+ customTitle: hasRequestedCustomTitle ? typeof options.customTitle === "string" ? options.customTitle : "" : savedSession?.customTitle || "",
1996
+ customTitleExplicit: hasRequestedCustomTitle,
1997
+ requiredCliVersion: normalizedProvider === "codex" && session ? session.cliVersion : "",
1998
+ projectWorkspace,
1999
+ source: shouldFork ? resumeSource.replace("-history:", "-history-fork:") : resumeSource,
2000
+ agentRuntimeMode: typeof options.agentRuntimeMode === "string" && ["chat", "acp"].includes(options.agentRuntimeMode) ? "chat" : "terminal",
2001
+ acpHistoryMode: options.acpHistoryMode === "resume" ? "resume" : "load",
2002
+ providerHomeId: resolvedProviderHomeId,
2003
+ providerHomePath: session ? session.providerHomePath || "" : "",
2004
+ providerSessionTitle: session?.title || savedSession?.providerSessionTitle || "",
2005
+ persistentSessionId: savedSession?.id || "",
2006
+ pinned: savedSession?.pinned === true,
2007
+ projectOrder: savedSession?.projectOrder,
2008
+ pinnedOrder: savedSession?.pinnedOrder,
2009
+ attentionSeq: savedAttentionSeq,
2010
+ readAttentionSeq: savedReadAttentionSeq,
2011
+ attentionUpdatedAt: savedSession?.attentionUpdatedAt,
2012
+ readAttentionAt: savedSession?.readAttentionAt,
2013
+ attentionReason: savedSession?.attentionReason,
2014
+ attentionOutputEpoch: savedSession?.attentionOutputEpoch,
2015
+ attentionOutputSeq: savedSession?.attentionOutputSeq,
2016
+ readOutputEpoch: savedSession?.readOutputEpoch,
2017
+ readOutputSeq: savedSession?.readOutputSeq,
2018
+ autoReadInitialAttention: options.autoReadInitialAttention === true && savedAttentionSeq <= savedReadAttentionSeq,
2019
+ preserveProviderSessionProfile: normalizedProvider === "codex" || normalizedProvider === "claude"
2020
+ });
2021
+ Promise.resolve(startResult).catch((error) => {
2022
+ resolve({ error: error.message || "failed to resume agent session", status: 500 });
2023
+ });
2024
+ });
2025
+ })();
2026
+ pendingResumeStarts.set(pendingResumeId, startPromise);
2027
+ const result = await startPromise;
2028
+ if (pendingResumeStarts.get(pendingResumeId) === startPromise) {
2029
+ pendingResumeStarts.delete(pendingResumeId);
2030
+ }
2031
+ if (result.error) {
2032
+ return result;
2033
+ }
2034
+ if (shouldRememberMainPageSession) rememberMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
2035
+ if (result.reused) {
2036
+ return {
2037
+ agentId: result.agentId,
2038
+ projectWorkspace: result.projectWorkspace || "",
2039
+ reused: true,
2040
+ ...result.claimed ? { claimed: true } : {}
2041
+ };
2042
+ }
2043
+ return {
2044
+ agentId: result.agentId,
2045
+ projectWorkspace: result.projectWorkspace || ""
2046
+ };
2047
+ }
2048
+ async function canonicalProjectWorkspace(workspace) {
2049
+ const candidate = configManager.expandWorkspacePath(String(workspace || "").trim());
2050
+ if (!candidate) return "";
2051
+ const pending = pendingProjectWorkspaceResolutions.get(candidate);
2052
+ if (pending) return pending;
2053
+ const resolution = (async () => {
2054
+ try {
2055
+ const worktree = await (0, import_git_worktree_info.inspectGitWorktree)(candidate);
2056
+ if (worktree?.workspace) return worktree.workspace;
2057
+ } catch (caught) {
2058
+ const error = caughtError(caught);
2059
+ console.warn("Failed to resolve project worktree:", candidate, error?.message || error);
2060
+ }
2061
+ try {
2062
+ return fs.realpathSync(path.resolve(candidate));
2063
+ } catch {
2064
+ return candidate;
2065
+ }
2066
+ })();
2067
+ pendingProjectWorkspaceResolutions.set(candidate, resolution);
2068
+ try {
2069
+ return await resolution;
2070
+ } finally {
2071
+ if (pendingProjectWorkspaceResolutions.get(candidate) === resolution) {
2072
+ pendingProjectWorkspaceResolutions.delete(candidate);
2073
+ }
2074
+ }
2075
+ }
2076
+ async function startResumedAgentSession(req, res, provider, rawSessionId) {
2077
+ const shouldFork = req.body && req.body.fork === true;
2078
+ const requestedAsMain = req.body && req.body.asMain === true && !shouldFork;
2079
+ const allowUnarchiveArchived = req.body && req.body.unarchiveArchived === true && !shouldFork && !requestedAsMain;
2080
+ const providerHomeId = req.body && typeof req.body.providerHomeId === "string" ? req.body.providerHomeId : "";
2081
+ const sessionKey = (0, import_main_page_session.mainPageAgentSessionKey)(provider, rawSessionId, providerHomeId);
2082
+ const mainPageSessionWasRemembered = !shouldFork && !requestedAsMain && configManager.getMainPageSessionKeys().includes(sessionKey);
2083
+ if (req.body && Object.prototype.hasOwnProperty.call(req.body, "customTitle") && typeof req.body.customTitle !== "string") {
2084
+ res.status(400).json({ error: "customTitle must be a string" });
2085
+ return;
2086
+ }
2087
+ const result = await resumeAgentSessionById(provider, rawSessionId, {
2088
+ fork: shouldFork,
2089
+ asMain: requestedAsMain,
2090
+ allowUnarchiveArchived,
2091
+ providerHomeId,
2092
+ ...req.body && Object.prototype.hasOwnProperty.call(req.body, "customTitle") ? { customTitle: req.body.customTitle } : {},
2093
+ agentRuntimeMode: typeof req.body?.agentRuntimeMode === "string" && ["chat", "acp"].includes(req.body.agentRuntimeMode) ? "chat" : "terminal",
2094
+ acpHistoryMode: req.body && req.body.acpHistoryMode === "resume" ? "resume" : "load"
2095
+ });
2096
+ if (result.error) {
2097
+ res.status(result.status || 400).json({ error: result.error });
2098
+ return;
2099
+ }
2100
+ if (!result.agentId) {
2101
+ res.status(500).json({ error: "Agent session resume returned no Agent identity" });
2102
+ return;
2103
+ }
2104
+ let projectMembership = {
2105
+ projectWorkspaces: configManager.getSettings().projectWorkspaces || [],
2106
+ pinnedProjectWorkspaces: configManager.getSettings().pinnedProjectWorkspaces || []
2107
+ };
2108
+ try {
2109
+ if (!requestedAsMain && result.projectWorkspace) {
2110
+ projectMembership = configManager.mountProjectWorkspace(result.projectWorkspace);
2111
+ }
2112
+ } catch (caught) {
2113
+ const error = caughtError(caught);
2114
+ let rollbackError = "";
2115
+ if (!result.reused) {
2116
+ try {
2117
+ const rollback = await agentManager.archiveAgent(result.agentId, {
2118
+ reason: "project-mount-failed",
2119
+ recordHistory: false,
2120
+ requireEngineExit: true,
2121
+ scheduleProviderArchive: false
2122
+ });
2123
+ if (rollback?.error) rollbackError = rollback.error;
2124
+ } catch (cleanupError) {
2125
+ rollbackError = caughtError(cleanupError).message || String(cleanupError);
2126
+ }
2127
+ }
2128
+ if (!mainPageSessionWasRemembered && !rollbackError) {
2129
+ forgetMainPageAgentSession(provider, rawSessionId, providerHomeId || "default");
2130
+ }
2131
+ broadcastState();
2132
+ const mountError = error.message || "Failed to create Project";
2133
+ res.status(500).json({
2134
+ error: rollbackError ? `${mountError}. Rollback failed: ${rollbackError}` : mountError,
2135
+ ...rollbackError ? { rollbackError } : {}
2136
+ });
2137
+ return;
2138
+ }
2139
+ broadcastState();
2140
+ if (result.reused) {
2141
+ res.status(200).json({
2142
+ agentId: result.agentId,
2143
+ ...result.projectWorkspace ? { projectWorkspace: result.projectWorkspace } : {},
2144
+ projectWorkspaces: projectMembership.projectWorkspaces,
2145
+ pinnedProjectWorkspaces: projectMembership.pinnedProjectWorkspaces,
2146
+ reused: true,
2147
+ ...result.claimed ? { claimed: true } : {},
2148
+ ...result.pending ? { pending: true } : {}
2149
+ });
2150
+ return;
2151
+ }
2152
+ res.status(201).json({
2153
+ agentId: result.agentId,
2154
+ ...result.projectWorkspace ? { projectWorkspace: result.projectWorkspace } : {},
2155
+ projectWorkspaces: projectMembership.projectWorkspaces,
2156
+ pinnedProjectWorkspaces: projectMembership.pinnedProjectWorkspaces
2157
+ });
2158
+ }
2159
+ async function autoResumeMainPageAgentSessions() {
2160
+ if (typeof agentManager.whenRecovered === "function") {
2161
+ await agentManager.whenRecovered();
2162
+ }
2163
+ const sessions = (0, import_main_page_session.mainPageAgentSessionsToAutoResume)(configManager.getSettings());
2164
+ if (sessions.length === 0) return;
2165
+ let knownSessions;
2166
+ try {
2167
+ knownSessions = await currentAgentSessions();
2168
+ } catch (caught) {
2169
+ const error = caughtError(caught);
2170
+ console.warn("Failed to load Agent session catalog for auto-resume:", error && (error.message || error));
2171
+ return;
2172
+ }
2173
+ const knownSessionByKey = new Map(knownSessions.map((session) => [
2174
+ (0, import_main_page_session.mainPageAgentSessionKey)(session.provider, session.id, session.providerHomeId || "default"),
2175
+ session
2176
+ ]));
2177
+ let resumedCount = 0;
2178
+ for (const session of sessions) {
2179
+ try {
2180
+ const sessionDetails = knownSessionByKey.get((0, import_main_page_session.mainPageAgentSessionKey)(
2181
+ session.provider,
2182
+ session.sessionId,
2183
+ session.providerHomeId || "default"
2184
+ ));
2185
+ if (!sessionDetails) {
2186
+ console.warn("Dropping stale main-page session from auto-resume:", session.provider, session.sessionId);
2187
+ forgetMainPageAgentSession(session.provider, session.sessionId, session.providerHomeId || "default");
2188
+ continue;
2189
+ }
2190
+ const claimingAgent = (0, import_main_page_session.findActiveAgentClaimingSession)(agentManager.getState().agents, session.provider, {
2191
+ id: session.sessionId,
2192
+ ...sessionDetails || {}
2193
+ });
2194
+ if (claimingAgent) {
2195
+ continue;
2196
+ }
2197
+ const result = await resumeAgentSessionById(session.provider, session.sessionId, {
2198
+ rememberMainPageSession: false,
2199
+ providerHomeId: session.providerHomeId || "default",
2200
+ autoReadInitialAttention: true
2201
+ });
2202
+ if (result.error) {
2203
+ const message = String(result.error || "").toLowerCase();
2204
+ if (session.provider === "qoder" && message.includes("invalid session identifier")) {
2205
+ console.warn("Dropping stale qoder session from auto-resume:", session.provider, session.sessionId, result.error);
2206
+ forgetMainPageAgentSession(session.provider, session.sessionId, session.providerHomeId || "default");
2207
+ continue;
2208
+ }
2209
+ console.warn("Failed to auto-resume main page agent session:", session.provider, session.sessionId, result.error);
2210
+ } else {
2211
+ resumedCount += 1;
2212
+ }
2213
+ } catch (caught) {
2214
+ const error = caughtError(caught);
2215
+ const message = error instanceof Error ? error.message : String(error);
2216
+ console.warn("Failed to auto-resume main page agent session:", session.provider, session.sessionId, message);
2217
+ }
2218
+ }
2219
+ if (resumedCount > 0) {
2220
+ broadcastState();
2221
+ }
2222
+ }
2223
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/settings"), express.json(), async (req, res) => {
2224
+ const settingsPatch = { ...req.body || {} };
2225
+ delete settingsPatch.mainPageSessionKeys;
2226
+ delete settingsPatch.projectWorkspaces;
2227
+ delete settingsPatch.pinnedProjectWorkspaces;
2228
+ const browserConfigurationKeys = [
2229
+ "browserSource",
2230
+ "browserExecutablePath",
2231
+ "browserExternalCdpUrl"
2232
+ ];
2233
+ const computerConfigurationKeys = [
2234
+ "computerImage",
2235
+ "computerCompatibilityMode"
2236
+ ];
2237
+ const currentSettings = configManager.getSettings();
2238
+ const requestsIsolatedBrowser = (Object.prototype.hasOwnProperty.call(settingsPatch, "browserExtensionEnabled") ? settingsPatch.browserExtensionEnabled === true : currentSettings.browserExtensionEnabled === true) && (optionalString(settingsPatch.browserSource) ?? currentSettings.browserSource) === "isolated";
2239
+ if (requestsIsolatedBrowser) {
2240
+ settingsPatch.computerExtensionEnabled = true;
2241
+ }
2242
+ const changesAgentHomes = Object.prototype.hasOwnProperty.call(settingsPatch, "agentHomes");
2243
+ const changesBrowserExtension = Object.prototype.hasOwnProperty.call(settingsPatch, "browserExtensionEnabled");
2244
+ const changesBrowserConfiguration = browserConfigurationKeys.some(
2245
+ (key) => Object.prototype.hasOwnProperty.call(settingsPatch, key)
2246
+ );
2247
+ let changesComputerExtension = Object.prototype.hasOwnProperty.call(settingsPatch, "computerExtensionEnabled");
2248
+ const changesComputerConfiguration = computerConfigurationKeys.some(
2249
+ (key) => Object.prototype.hasOwnProperty.call(settingsPatch, key)
2250
+ );
2251
+ const browserExtensionEnabled = settingsPatch.browserExtensionEnabled === true;
2252
+ const desiredBrowserEnabled = changesBrowserExtension ? browserExtensionEnabled : currentSettings.browserExtensionEnabled === true;
2253
+ let computerExtensionEnabled = settingsPatch.computerExtensionEnabled === true;
2254
+ let desiredComputerEnabled = changesComputerExtension ? computerExtensionEnabled : currentSettings.computerExtensionEnabled === true;
2255
+ if (changesBrowserExtension && browserExtensionEnabled || changesBrowserConfiguration) {
2256
+ const desiredSelection = browserResourceManager.browserSelection({
2257
+ browserSource: optionalString(settingsPatch.browserSource) ?? currentSettings.browserSource,
2258
+ browserExecutablePath: optionalString(settingsPatch.browserExecutablePath) ?? currentSettings.browserExecutablePath,
2259
+ browserExternalCdpUrl: optionalString(settingsPatch.browserExternalCdpUrl) ?? currentSettings.browserExternalCdpUrl
2260
+ });
2261
+ const probe = await browserResourceManager.probeCapability(desiredSelection);
2262
+ if ((changesBrowserConfiguration || desiredBrowserEnabled) && (!probe.runtimeCapability || probe.runtimeCapability.error)) {
2263
+ res.status(400).json({
2264
+ error: probe.runtimeCapability?.error || "Choose a local Chromium browser or prepare the isolated Browser runtime",
2265
+ code: "BROWSER_EXECUTABLE_NOT_FOUND"
2266
+ });
2267
+ return;
2268
+ }
2269
+ if (desiredBrowserEnabled && probe.runtimeCapability?.kind === "isolated-computer") {
2270
+ settingsPatch.computerExtensionEnabled = true;
2271
+ changesComputerExtension = true;
2272
+ computerExtensionEnabled = true;
2273
+ desiredComputerEnabled = true;
2274
+ }
2275
+ }
2276
+ if (currentSettings.browserExtensionEnabled === true && (changesBrowserExtension && !browserExtensionEnabled || changesBrowserConfiguration)) {
2277
+ try {
2278
+ await browserResourceManager.stopAll();
2279
+ } catch (caught) {
2280
+ const error = caughtError(caught);
2281
+ res.status(Number(error?.status) || 500).json({
2282
+ error: error?.message || "Browser extension could not be disabled",
2283
+ code: error?.code || "BROWSER_DISABLE_FAILED"
2284
+ });
2285
+ return;
2286
+ }
2287
+ }
2288
+ if (changesComputerExtension && computerExtensionEnabled || changesComputerConfiguration) {
2289
+ try {
2290
+ const probe = await computerResourceManager.probeSettings({
2291
+ ...currentSettings,
2292
+ ...settingsPatch
2293
+ });
2294
+ if (desiredComputerEnabled && !probe.imageReady) {
2295
+ res.status(400).json({
2296
+ error: probe.error || "Prepare the pinned Computer runtime before enabling this plugin",
2297
+ code: probe.dockerAvailable ? "COMPUTER_IMAGE_NOT_READY" : "COMPUTER_DOCKER_NOT_AVAILABLE"
2298
+ });
2299
+ return;
2300
+ }
2301
+ } catch (caught) {
2302
+ const error = caughtError(caught);
2303
+ res.status(Number(error?.status) || 400).json({
2304
+ error: error?.message || "Computer configuration is invalid",
2305
+ code: error?.code || "COMPUTER_CONFIGURATION_INVALID"
2306
+ });
2307
+ return;
2308
+ }
2309
+ }
2310
+ if (currentSettings.computerExtensionEnabled === true && (changesComputerExtension && !computerExtensionEnabled || changesComputerConfiguration)) {
2311
+ try {
2312
+ if (changesComputerConfiguration) {
2313
+ await computerResourceManager.resetAllContainers();
2314
+ } else {
2315
+ await computerResourceManager.stopAll();
2316
+ }
2317
+ } catch (caught) {
2318
+ const error = caughtError(caught);
2319
+ res.status(Number(error?.status) || 500).json({
2320
+ error: error?.message || "Computer extension could not be disabled",
2321
+ code: error?.code || "COMPUTER_DISABLE_FAILED"
2322
+ });
2323
+ return;
2324
+ }
2325
+ }
2326
+ try {
2327
+ configManager.updateSettings(settingsPatch);
2328
+ } catch (caught) {
2329
+ const error = caughtError(caught);
2330
+ if (error?.code && String(error.code).startsWith("AGENT_HOME_")) {
2331
+ res.status(Number(error.status) || 409).json({
2332
+ error: error.message || "Agent Home configuration conflicts with persisted Agent metadata",
2333
+ code: error.code
2334
+ });
2335
+ return;
2336
+ }
2337
+ throw caught;
2338
+ }
2339
+ if (changesBrowserExtension || changesBrowserConfiguration) {
2340
+ await browserResourceManager.refreshCapability();
2341
+ }
2342
+ if (changesComputerExtension || changesComputerConfiguration) {
2343
+ computerResourceManager.capabilityCache = null;
2344
+ await computerResourceManager.capability(true);
2345
+ }
2346
+ if (changesAgentHomes) {
2347
+ agentSessionInventory.invalidate();
2348
+ agentExtensionInventory.invalidate();
2349
+ }
2350
+ res.json({
2351
+ success: true,
2352
+ settings: configManager.getSettings()
2353
+ });
2354
+ scheduleBroadcastState();
2355
+ });
2356
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/themes/:themeId/set"), express.json(), (req, res) => {
2357
+ const theme = themeManager.getTheme(req.params.themeId);
2358
+ if (!theme) {
2359
+ res.status(404).json({ error: "Theme not found" });
2360
+ return;
2361
+ }
2362
+ configManager.updateSettings({ theme: req.params.themeId });
2363
+ res.json({ success: true, theme: req.params.themeId });
2364
+ });
2365
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/themes/:themeId/settings"), (req, res) => {
2366
+ const theme = themeManager.getTheme(req.params.themeId);
2367
+ if (!theme) {
2368
+ res.status(404).json({ error: "Theme not found" });
2369
+ return;
2370
+ }
2371
+ const settings = themeManager.getThemeSettings(req.params.themeId);
2372
+ res.json({ settings });
2373
+ });
2374
+ app.post((0, import_index_html.routePath)(BASE_PATH, "/api/themes/:themeId/settings"), express.json(), (req, res) => {
2375
+ const theme = themeManager.getTheme(req.params.themeId);
2376
+ if (!theme) {
2377
+ res.status(404).json({ error: "Theme not found" });
2378
+ return;
2379
+ }
2380
+ const success = themeManager.updateThemeSettings(req.params.themeId, req.body);
2381
+ if (success) {
2382
+ res.json({ success: true, settings: themeManager.getThemeSettings(req.params.themeId) });
2383
+ } else {
2384
+ res.status(500).json({ error: "Failed to update theme settings" });
2385
+ }
2386
+ });
2387
+ app.get((0, import_index_html.routePath)(BASE_PATH, "/api/themes/:themeId"), (req, res) => {
2388
+ const theme = themeManager.getTheme(req.params.themeId);
2389
+ if (!theme) {
2390
+ res.status(404).json({ error: "Theme not found" });
2391
+ return;
2392
+ }
2393
+ const css = themeManager.getThemeCSS(req.params.themeId);
2394
+ res.json({
2395
+ theme,
2396
+ css
2397
+ });
2398
+ });
2399
+ wss.on("connection", (ws, req) => {
2400
+ initializeWebSocketLiveness(ws);
2401
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
2402
+ const computerViewerPrefix = (0, import_index_html.routePath)(BASE_PATH, "/api/computers/");
2403
+ if (url.pathname.startsWith(computerViewerPrefix) && url.pathname.endsWith("/viewer-websocket")) {
2404
+ if (authEnabled && !tokenAuth.verifyWebSocket(req)) {
2405
+ ws.close(4001, "Authentication required");
2406
+ return;
2407
+ }
2408
+ const encodedId = url.pathname.slice(
2409
+ computerViewerPrefix.length,
2410
+ -"/viewer-websocket".length
2411
+ );
2412
+ try {
2413
+ computerResourceManager.attachViewer(decodeURIComponent(encodedId), ws);
2414
+ } catch (caught) {
2415
+ const error = caughtError(caught);
2416
+ ws.close(4004, error?.message || "Computer Resource not found");
2417
+ }
2418
+ return;
2419
+ }
2420
+ const viewerPrefix = (0, import_index_html.routePath)(BASE_PATH, "/api/browsers/");
2421
+ if (url.pathname.startsWith(viewerPrefix) && url.pathname.endsWith("/viewer")) {
2422
+ if (authEnabled && !tokenAuth.verifyWebSocket(req)) {
2423
+ ws.close(4001, "Authentication required");
2424
+ return;
2425
+ }
2426
+ const encodedId = url.pathname.slice(viewerPrefix.length, -"/viewer".length);
2427
+ try {
2428
+ browserResourceManager.attachViewer(decodeURIComponent(encodedId), ws);
2429
+ } catch (caught) {
2430
+ const error = caughtError(caught);
2431
+ ws.close(4004, error?.message || "Browser resource not found");
2432
+ }
2433
+ return;
2434
+ }
2435
+ if (url.pathname !== WS_PATH) {
2436
+ ws.close(1008, "Invalid path");
2437
+ return;
2438
+ }
2439
+ if (authEnabled && !tokenAuth.verifyWebSocket(req)) {
2440
+ ws.close(4001, "Authentication required");
2441
+ return;
2442
+ }
2443
+ console.log("Client connected");
2444
+ ws.connectionId = crypto.randomUUID();
2445
+ ws.on("message", (message) => {
2446
+ try {
2447
+ const data = JSON.parse(message);
2448
+ const validation = validateClientMessage(data);
2449
+ if (validation.ok === false) {
2450
+ ws.send(JSON.stringify({
2451
+ type: "protocol-error",
2452
+ protocolVersion: PROTOCOL_VERSION,
2453
+ requestId: typeof data?.requestId === "string" ? data.requestId : "",
2454
+ message: validation.error
2455
+ }));
2456
+ return;
2457
+ }
2458
+ handleMessage(ws, validation.value);
2459
+ } catch (e) {
2460
+ console.error("Failed to parse message:", e);
2461
+ ws.send(JSON.stringify({
2462
+ type: "protocol-error",
2463
+ protocolVersion: PROTOCOL_VERSION,
2464
+ requestId: "",
2465
+ message: "message must be valid JSON"
2466
+ }));
2467
+ }
2468
+ });
2469
+ ws.on("close", () => {
2470
+ clearWorkspaceFileWatch(ws);
2471
+ console.log("Client disconnected");
2472
+ });
2473
+ ws.send(JSON.stringify({
2474
+ type: "protocol-hello",
2475
+ protocolVersion: PROTOCOL_VERSION,
2476
+ minProtocolVersion: MIN_PROTOCOL_VERSION
2477
+ }));
2478
+ sendState(ws);
2479
+ });
2480
+ const MAIN_AGENT_RESTART_COMMANDS = /* @__PURE__ */ new Set(["codex", "claude", "opencode", "qoder", "qwen", "bash", "zsh"]);
2481
+ function normalizeMainAgentRestartCommand(command) {
2482
+ const normalized = String(command || "").trim();
2483
+ return MAIN_AGENT_RESTART_COMMANDS.has(normalized) ? normalized : "";
2484
+ }
2485
+ function restartMainAgent(ws, command) {
2486
+ const normalizedCommand = normalizeMainAgentRestartCommand(command);
2487
+ if (!normalizedCommand) {
2488
+ ws.send(JSON.stringify({ type: "error", message: "Unsupported Main Agent restart command" }));
2489
+ return;
2490
+ }
2491
+ void (async () => {
2492
+ try {
2493
+ const state = agentManager.getState();
2494
+ const currentMain = state.agents.find((agent) => agent.id === state.mainAgentId || agent.isMain === true);
2495
+ if (currentMain) {
2496
+ const killed = await agentManager.killAgent(currentMain.id);
2497
+ if (killed?.error) {
2498
+ ws.send(JSON.stringify({ type: "error", message: killed.error }));
2499
+ return;
2500
+ }
2501
+ }
2502
+ await agentManager.startAgent(normalizedCommand, null, (agentId, error) => {
2503
+ if (error) {
2504
+ ws.send(JSON.stringify({ type: "error", message: error }));
2505
+ } else if (agentId) {
2506
+ ws.agentId = agentId;
2507
+ broadcastState();
2508
+ ws.send(JSON.stringify({ type: "agent-started", agentId }));
2509
+ }
2510
+ }, {
2511
+ wantsMain: true
2512
+ });
2513
+ } catch (caught) {
2514
+ const error = caughtError(caught);
2515
+ const message = error instanceof Error ? error.message : "Failed to restart Main Agent";
2516
+ ws.send(JSON.stringify({ type: "error", message }));
2517
+ broadcastState();
2518
+ }
2519
+ })();
2520
+ }
2521
+ async function killAgentFromMessage(ws, agentId, options = {}) {
2522
+ try {
2523
+ const requested = await agentManager.requestKillAgent(agentId, {
2524
+ acknowledgeUnprovenAcpExit: options.acknowledgeUnprovenAcpExit === true
2525
+ });
2526
+ const result = requested.result;
2527
+ if (result && "error" in result && result.error) {
2528
+ ws.send(JSON.stringify({ type: "error", message: result.error }));
2529
+ } else if (result?.accepted) {
2530
+ ws.send(JSON.stringify({ type: "agent-delete-accepted", ...result }));
2531
+ void requested.completion.then((completed) => {
2532
+ if (completed?.error && ws.readyState === WebSocket.OPEN) {
2533
+ ws.send(JSON.stringify({ type: "error", message: completed.error }));
2534
+ }
2535
+ }).catch((error) => {
2536
+ if (ws.readyState === WebSocket.OPEN) {
2537
+ ws.send(JSON.stringify({
2538
+ type: "error",
2539
+ message: error instanceof Error ? error.message : "Failed to stop Agent"
2540
+ }));
2541
+ }
2542
+ }).finally(() => broadcastState());
2543
+ }
2544
+ } catch (caught) {
2545
+ const error = caughtError(caught);
2546
+ ws.send(JSON.stringify({
2547
+ type: "error",
2548
+ message: error instanceof Error ? error.message : "Failed to stop Agent"
2549
+ }));
2550
+ } finally {
2551
+ broadcastState();
2552
+ }
2553
+ }
2554
+ async function sendInputMessage(ws, data) {
2555
+ const targetAgentId = (0, import_input_routing.resolveInputTargetAgentId)(ws, data);
2556
+ if (!targetAgentId) return;
2557
+ const inputParts = (0, import_input_parts.inputPartsFromMessage)(data);
2558
+ if (inputParts.length === 0) return;
2559
+ await agentManager.sendInput(targetAgentId, inputParts);
2560
+ }
2561
+ async function sendComposerInputMessage(ws, data) {
2562
+ const targetAgentId = (0, import_input_routing.resolveInputTargetAgentId)(ws, data);
2563
+ const requestId = typeof data.requestId === "string" ? data.requestId.trim() : "";
2564
+ const delivery = data.delivery === "steer" || data.delivery === "prompt" ? data.delivery : "auto";
2565
+ const responseAgentId = targetAgentId || (typeof data.agentId === "string" ? data.agentId : "");
2566
+ const respond = (accepted, message2 = "", uncertain = false) => {
2567
+ if (!requestId || !responseAgentId) return;
2568
+ ws.send(JSON.stringify({
2569
+ type: "composer-input-result",
2570
+ requestId,
2571
+ agentId: responseAgentId,
2572
+ accepted,
2573
+ ...message2 ? { message: message2 } : {},
2574
+ ...uncertain ? { uncertain: true } : {}
2575
+ }));
2576
+ };
2577
+ const message = typeof data.message === "string" ? data.message : "";
2578
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
2579
+ if (responseAgentId) {
2580
+ ws.send(JSON.stringify({
2581
+ type: "composer-input-result",
2582
+ requestId: requestId || "invalid-request",
2583
+ agentId: responseAgentId,
2584
+ accepted: false,
2585
+ message: "Structured Composer input requires a valid requestId"
2586
+ }));
2587
+ }
2588
+ return;
2589
+ }
2590
+ const content = [];
2591
+ if (message.trim()) content.push({ type: "text", text: message });
2592
+ const attachmentsRoot = path.resolve(imageAttachmentsDir());
2593
+ const attachments = Array.isArray(data.attachments) ? data.attachments.slice(0, 8) : [];
2594
+ for (const attachment of attachments) {
2595
+ if (!["image", "audio"].includes(attachment?.kind) || typeof attachment.path !== "string") continue;
2596
+ const filePath = path.resolve(attachment.path);
2597
+ if (!filePath.startsWith(`${attachmentsRoot}${path.sep}`)) continue;
2598
+ const mimeType = typeof attachment.type === "string" && attachment.kind === "image" && /^image\/(?:png|jpe?g|gif|webp)$/i.test(attachment.type) ? attachment.type.toLowerCase() : typeof attachment.type === "string" && attachment.kind === "audio" && Object.hasOwn(AUDIO_ATTACHMENT_EXTENSIONS, attachment.type.toLowerCase()) ? attachment.type.toLowerCase() : "";
2599
+ if (!mimeType) continue;
2600
+ try {
2601
+ const data2 = await fs.promises.readFile(filePath);
2602
+ if (data2.length === 0 || data2.length > 12 * 1024 * 1024) continue;
2603
+ content.push({
2604
+ type: attachment.kind,
2605
+ data: data2.toString("base64"),
2606
+ mimeType,
2607
+ path: filePath,
2608
+ uri: pathToFileURL(filePath).href
2609
+ });
2610
+ } catch {
2611
+ }
2612
+ }
2613
+ if (!targetAgentId || content.length === 0) {
2614
+ respond(false, "Composer message is empty");
2615
+ return;
2616
+ }
2617
+ const targetAgent = agentManager.getState().agents.find((agent) => agent.id === targetAgentId);
2618
+ const structuredRuntime = targetAgent && (0, import_agent_runtime_binding.runtimeKind)(targetAgent) !== "terminal";
2619
+ if (!structuredRuntime) {
2620
+ const error = "Terminal Composer input requires the active terminal owner";
2621
+ if (requestId) respond(false, error);
2622
+ else ws.send(JSON.stringify({ type: "error", message: error }));
2623
+ return;
2624
+ }
2625
+ try {
2626
+ await agentManager.sendComposerMessage(targetAgentId, content, { requestId, delivery });
2627
+ respond(true);
2628
+ } catch (caught) {
2629
+ const error = caughtError(caught);
2630
+ const message2 = error && error.message ? error.message : "Failed to send Composer message";
2631
+ if (requestId) respond(false, message2, error?.uncertain === true);
2632
+ else ws.send(JSON.stringify({ type: "error", message: message2 }));
2633
+ }
2634
+ }
2635
+ async function sendBusinessHealthResult(ws, requestId) {
2636
+ const health = await (0, import_business_health.probeAgentManagerBusinessHealth)(agentManager);
2637
+ if (ws.readyState !== WebSocket.OPEN) return;
2638
+ recoverResourceSnapshotIfReady(ws);
2639
+ ws.send(JSON.stringify({
2640
+ type: "business-health-result",
2641
+ requestId,
2642
+ serverEpoch: SERVER_EPOCH,
2643
+ protocolVersion: PROTOCOL_VERSION,
2644
+ ...health
2645
+ }));
2646
+ }
2647
+ function handleMessage(ws, data) {
2648
+ switch (data.type) {
2649
+ case "protocol-hello":
2650
+ if (!protocolCompatible(data.protocolVersion)) {
2651
+ ws.close(4002, `Unsupported Farming protocol version ${data.protocolVersion}`);
2652
+ return;
2653
+ }
2654
+ ws.protocolVersion = data.protocolVersion;
2655
+ sendResourceSnapshots(ws);
2656
+ break;
2657
+ case "business-health-probe":
2658
+ if (!ws.protocolVersion) {
2659
+ ws.send(JSON.stringify({
2660
+ type: "protocol-error",
2661
+ protocolVersion: PROTOCOL_VERSION,
2662
+ requestId: data.requestId,
2663
+ message: "Business health requires a negotiated Farming protocol"
2664
+ }));
2665
+ return;
2666
+ }
2667
+ void sendBusinessHealthResult(ws, data.requestId);
2668
+ break;
2669
+ case "start-agent": {
2670
+ const workspace = typeof data.workspace === "string" ? data.workspace : null;
2671
+ void (async () => {
2672
+ const projectWorkspace = data.asMain === true ? "" : await canonicalProjectWorkspace(
2673
+ typeof data.projectWorkspace === "string" && data.projectWorkspace.trim() ? data.projectWorkspace : workspace
2674
+ );
2675
+ agentManager.startAgent(data.command, workspace, (agentId, error) => {
2676
+ if (error) {
2677
+ ws.send(JSON.stringify({ type: "error", message: error }));
2678
+ } else if (agentId) {
2679
+ void (async () => {
2680
+ try {
2681
+ if (projectWorkspace) configManager.mountProjectWorkspace(projectWorkspace);
2682
+ } catch (mountError) {
2683
+ let rollbackError = "";
2684
+ try {
2685
+ const rollback = await agentManager.archiveAgent(agentId, {
2686
+ reason: "project-mount-failed",
2687
+ recordHistory: false,
2688
+ requireEngineExit: true,
2689
+ scheduleProviderArchive: false
2690
+ });
2691
+ if (rollback?.error) rollbackError = rollback.error;
2692
+ } catch (cleanupError) {
2693
+ rollbackError = caughtError(cleanupError).message || String(cleanupError);
2694
+ }
2695
+ broadcastState();
2696
+ const errorMessage = caughtError(mountError).message || "Failed to create Project";
2697
+ ws.send(JSON.stringify({
2698
+ type: "error",
2699
+ message: rollbackError ? `${errorMessage}. Rollback failed: ${rollbackError}` : errorMessage
2700
+ }));
2701
+ return;
2702
+ }
2703
+ ws.agentId = agentId;
2704
+ broadcastState();
2705
+ ws.send(JSON.stringify({ type: "agent-started", agentId }));
2706
+ })().catch((callbackError) => {
2707
+ console.warn("Failed to finish started Agent transition:", agentId, caughtError(callbackError).message || callbackError);
2708
+ });
2709
+ }
2710
+ }, {
2711
+ wantsMain: data.asMain === true,
2712
+ projectWorkspace,
2713
+ task: typeof data.task === "string" ? data.task : "",
2714
+ workflowTemplate: typeof data.workflowTemplate === "string" ? data.workflowTemplate : "",
2715
+ customTitle: typeof data.customTitle === "string" ? data.customTitle : "",
2716
+ createRequestId: typeof data.requestId === "string" ? data.requestId : "",
2717
+ codexApprovalMode: typeof data.codexApprovalMode === "string" ? data.codexApprovalMode : void 0,
2718
+ agentRuntimeMode: typeof data.agentRuntimeMode === "string" && ["acp", "chat"].includes(data.agentRuntimeMode) ? data.agentRuntimeMode : "terminal",
2719
+ acpHistoryMode: data.acpHistoryMode === "resume" ? "resume" : "load",
2720
+ providerHomeId: typeof data.providerHomeId === "string" ? data.providerHomeId : "",
2721
+ ...Array.isArray(data.additionalDirectories) ? { additionalDirectories: data.additionalDirectories } : {},
2722
+ ...Array.isArray(data.mcpServers) ? { mcpServers: data.mcpServers } : {},
2723
+ ...data.dangerouslySkipPermissions === true ? { dangerouslySkipPermissions: true } : {}
2724
+ });
2725
+ })().catch((error) => {
2726
+ ws.send(JSON.stringify({
2727
+ type: "error",
2728
+ message: caughtError(error).message || "Failed to resolve Project workspace"
2729
+ }));
2730
+ });
2731
+ break;
2732
+ }
2733
+ case "input":
2734
+ {
2735
+ void sendInputMessage(ws, data);
2736
+ }
2737
+ break;
2738
+ case "composer-input":
2739
+ {
2740
+ void sendComposerInputMessage(ws, data);
2741
+ }
2742
+ break;
2743
+ case "acp-permission-response":
2744
+ try {
2745
+ agentManager.respondToAcpPermission(
2746
+ data.agentId,
2747
+ data.requestId,
2748
+ data.optionId,
2749
+ data.cancelled === true
2750
+ );
2751
+ } catch (caught) {
2752
+ const error = caughtError(caught);
2753
+ ws.send(JSON.stringify({
2754
+ type: "error",
2755
+ message: error && error.message ? error.message : "Failed to respond to ACP permission"
2756
+ }));
2757
+ }
2758
+ break;
2759
+ case "interrupt-agent":
2760
+ if (data.agentId) {
2761
+ void agentManager.interruptAgent(data.agentId);
2762
+ }
2763
+ break;
2764
+ case "focus-agent":
2765
+ ws.focusedAgentId = data.agentId;
2766
+ if (data.streamScope === "focused" || data.streamScope === "all") {
2767
+ ws.streamScope = data.streamScope;
2768
+ }
2769
+ if (data.previewScope === "none" || data.previewScope === "focused" || data.previewScope === "all") {
2770
+ ws.previewScope = data.previewScope;
2771
+ }
2772
+ if (data.refreshState === true) {
2773
+ sendState(ws);
2774
+ }
2775
+ break;
2776
+ case "resize-agent":
2777
+ if (data.agentId && Number.isFinite(data.cols) && Number.isFinite(data.rows)) {
2778
+ agentManager.requestAgentSessionResize(data.agentId, data.cols, data.rows);
2779
+ }
2780
+ break;
2781
+ case "clear-terminal":
2782
+ if (data.agentId) void agentManager.clearAgentSessionBuffer(data.agentId);
2783
+ break;
2784
+ case "watch-workspace-files":
2785
+ watchWorkspaceFiles(ws, data);
2786
+ break;
2787
+ case "unwatch-workspace-files":
2788
+ clearWorkspaceFileWatch(ws, data.agentId);
2789
+ break;
2790
+ case "kill-agent":
2791
+ void killAgentFromMessage(ws, data.agentId, {
2792
+ acknowledgeUnprovenAcpExit: data.acknowledgeUnprovenAcpExit === true
2793
+ });
2794
+ break;
2795
+ case "restart-main-agent":
2796
+ restartMainAgent(ws, data.command);
2797
+ break;
2798
+ default:
2799
+ console.log("Unknown message type");
2800
+ }
2801
+ }
2802
+ function sendWorkspaceFileWatchError(ws, error) {
2803
+ if (ws.readyState !== WebSocket.OPEN) return;
2804
+ const message = error instanceof import_workspace_file_service.WorkspaceFileError ? caughtError(error).message : "failed to watch workspace files";
2805
+ ws.send(JSON.stringify({ type: "error", message }));
2806
+ }
2807
+ function isCurrentWorkspaceFileWatch(ws, agentId, record) {
2808
+ return Boolean(
2809
+ !record.cancelled && ws.workspaceFileUnsubscribes?.get(agentId) === record
2810
+ );
2811
+ }
2812
+ function closeWorkspaceFileWatchRecord(record) {
2813
+ record.cancelled = true;
2814
+ if (!record.unsubscribe) return;
2815
+ Promise.resolve(record.unsubscribe()).catch((error) => {
2816
+ console.error("Failed to clear workspace file watch:", error);
2817
+ });
2818
+ }
2819
+ function clearWorkspaceFileWatch(ws, agentId = null) {
2820
+ const watches = ws.workspaceFileUnsubscribes;
2821
+ if (!watches) return;
2822
+ const entries = agentId ? [[agentId, watches.get(agentId)]] : Array.from(watches.entries());
2823
+ entries.forEach(([watchedAgentId, record]) => {
2824
+ if (!record) return;
2825
+ if (watches.get(watchedAgentId) === record) watches.delete(watchedAgentId);
2826
+ closeWorkspaceFileWatchRecord(record);
2827
+ });
2828
+ if (watches.size === 0) {
2829
+ ws.workspaceFileUnsubscribes = null;
2830
+ }
2831
+ }
2832
+ async function watchWorkspaceFiles(ws, data) {
2833
+ try {
2834
+ if (!data.agentId) {
2835
+ throw new import_workspace_file_service.WorkspaceFileError("agentId is required", 400);
2836
+ }
2837
+ if (!ws.workspaceFileUnsubscribes) {
2838
+ ws.workspaceFileUnsubscribes = /* @__PURE__ */ new Map();
2839
+ }
2840
+ const watches = ws.workspaceFileUnsubscribes;
2841
+ const existing = watches.get(data.agentId);
2842
+ if (existing) {
2843
+ const watching = await existing.ready;
2844
+ if (watching && isCurrentWorkspaceFileWatch(ws, data.agentId, existing) && ws.readyState === WebSocket.OPEN) {
2845
+ ws.send(JSON.stringify({
2846
+ type: "workspace-file-watch",
2847
+ agentId: data.agentId,
2848
+ watching: true
2849
+ }));
2850
+ }
2851
+ return;
2852
+ }
2853
+ const root = (0, import_workspace_file_router.resolveWorkspaceRoot)(agentManager, data.agentId);
2854
+ const record = {
2855
+ cancelled: false,
2856
+ unsubscribe: null,
2857
+ ready: null
2858
+ };
2859
+ record.ready = (async () => {
2860
+ const unsubscribe = await workspaceFileService.subscribe(root, (event) => {
2861
+ if (!isCurrentWorkspaceFileWatch(ws, data.agentId, record) || ws.readyState !== WebSocket.OPEN) return;
2862
+ ws.send(JSON.stringify({
2863
+ type: "workspace-file-event",
2864
+ event: {
2865
+ agentId: data.agentId,
2866
+ ...event
2867
+ }
2868
+ }));
2869
+ });
2870
+ record.unsubscribe = unsubscribe;
2871
+ if (!isCurrentWorkspaceFileWatch(ws, data.agentId, record) || ws.readyState !== WebSocket.OPEN) {
2872
+ closeWorkspaceFileWatchRecord(record);
2873
+ return false;
2874
+ }
2875
+ return true;
2876
+ })();
2877
+ watches.set(data.agentId, record);
2878
+ try {
2879
+ const watching = await record.ready;
2880
+ if (watching && isCurrentWorkspaceFileWatch(ws, data.agentId, record) && ws.readyState === WebSocket.OPEN) {
2881
+ ws.send(JSON.stringify({
2882
+ type: "workspace-file-watch",
2883
+ agentId: data.agentId,
2884
+ watching: true
2885
+ }));
2886
+ }
2887
+ } catch (caught) {
2888
+ const error = caughtError(caught);
2889
+ if (watches.get(data.agentId) === record) watches.delete(data.agentId);
2890
+ closeWorkspaceFileWatchRecord(record);
2891
+ if (watches.size === 0 && ws.workspaceFileUnsubscribes === watches) {
2892
+ ws.workspaceFileUnsubscribes = null;
2893
+ }
2894
+ throw error;
2895
+ }
2896
+ } catch (caught) {
2897
+ const error = caughtError(caught);
2898
+ sendWorkspaceFileWatchError(ws, error);
2899
+ }
2900
+ }
2901
+ function buildStatePayload() {
2902
+ const state = agentManager.getState();
2903
+ const settings = configManager.getSettings();
2904
+ return {
2905
+ ...state,
2906
+ agents: state.agents.map((agent) => ({
2907
+ ...agent,
2908
+ workspaceRootId: (0, import_workspace_root_registry.rootIdForPath)(String(agent.projectWorkspace || agent.gitWorktree?.workspace || agent.cwd || ""))
2909
+ })),
2910
+ workspaceRoots: workspaceRootRegistry.list(),
2911
+ mainPageSessionKeys: typeof configManager.getMainPageSessionKeys === "function" ? configManager.getMainPageSessionKeys() : Array.isArray(settings.mainPageSessionKeys) ? settings.mainPageSessionKeys : [],
2912
+ projectWorkspaces: settings.projectWorkspaces || [],
2913
+ pinnedProjectWorkspaces: settings.pinnedProjectWorkspaces || []
2914
+ };
2915
+ }
2916
+ function sendState(ws) {
2917
+ const state = buildStatePayload();
2918
+ ws.send(JSON.stringify({ type: "state", state }));
2919
+ agentManager.getPreviewPayloads().forEach((preview) => {
2920
+ sendPreview(ws, preview);
2921
+ });
2922
+ }
2923
+ function sendResourceSnapshots(ws) {
2924
+ if (ws.readyState !== WebSocket.OPEN || ws.protocolVersion !== PROTOCOL_VERSION) return;
2925
+ ws.send(JSON.stringify({
2926
+ type: "browser-resource-snapshot",
2927
+ snapshot: browserResourceManager.stateSnapshot()
2928
+ }));
2929
+ ws.send(JSON.stringify({
2930
+ type: "computer-resource-snapshot",
2931
+ snapshot: computerResourceManager.stateSnapshot()
2932
+ }));
2933
+ ws.resourceSnapshotPending = false;
2934
+ }
2935
+ function previewForClient(preview, client) {
2936
+ if (!preview || !client || client.focusedAgentId !== preview.agentId || !preview.previewSnapshot) {
2937
+ return preview;
2938
+ }
2939
+ return {
2940
+ ...preview,
2941
+ previewSnapshot: null
2942
+ };
2943
+ }
2944
+ function sendPreview(ws, preview) {
2945
+ ws.send(JSON.stringify({
2946
+ type: "session-preview",
2947
+ preview: previewForClient(preview, ws)
2948
+ }));
2949
+ }
2950
+ const STATE_BROADCAST_INTERVAL_MS = 120;
2951
+ const PREVIEW_BROADCAST_INTERVAL_MS = 500;
2952
+ const SESSION_STREAM_BROADCAST_INTERVAL_MS = 33;
2953
+ const AGENT_ACTIVITY_BROADCAST_DELAY_MS = SESSION_STREAM_BROADCAST_INTERVAL_MS;
2954
+ const MAX_SESSION_STREAM_CLIENT_BUFFERED_AMOUNT = 4 * 1024 * 1024;
2955
+ const RESOURCE_BROADCAST_INTERVAL_MS = 100;
2956
+ const MAX_RESOURCE_CLIENT_BUFFERED_AMOUNT = 512 * 1024;
2957
+ function isAgentScopedServerEvent(value) {
2958
+ return isRecord(value) && typeof value.agentId === "string" && value.agentId.length > 0;
2959
+ }
2960
+ let stateBroadcastTimer = null;
2961
+ let lastStateBroadcastAt = 0;
2962
+ const pendingPreviewBroadcasts = /* @__PURE__ */ new Map();
2963
+ const pendingAgentActivityBroadcasts = /* @__PURE__ */ new Map();
2964
+ const pendingAgentUpdates = /* @__PURE__ */ new Map();
2965
+ const pendingAcpSessionRevisions = /* @__PURE__ */ new Map();
2966
+ const pendingSessionStreams = /* @__PURE__ */ new Map();
2967
+ const pendingResourceBroadcasts = /* @__PURE__ */ new Map();
2968
+ let sessionStreamBroadcastTimer = null;
2969
+ let lastSessionStreamBroadcastAt = 0;
2970
+ let resourceBroadcastTimer = null;
2971
+ function broadcastResourceEvent(event) {
2972
+ const message = JSON.stringify(event.message);
2973
+ wss.clients.forEach((client) => {
2974
+ if (client.readyState !== WebSocket.OPEN || client.protocolVersion !== PROTOCOL_VERSION) return;
2975
+ const delivery = (0, import_resource_broadcast_protocol.resourceClientDelivery)(
2976
+ client.bufferedAmount,
2977
+ client.resourceSnapshotPending === true,
2978
+ MAX_RESOURCE_CLIENT_BUFFERED_AMOUNT
2979
+ );
2980
+ if (delivery === "defer") {
2981
+ client.resourceSnapshotPending = true;
2982
+ return;
2983
+ }
2984
+ if (delivery === "snapshot") {
2985
+ sendResourceSnapshots(client);
2986
+ return;
2987
+ }
2988
+ client.send(message);
2989
+ });
2990
+ }
2991
+ function recoverResourceSnapshotIfReady(client) {
2992
+ if (client.readyState !== WebSocket.OPEN || client.protocolVersion !== PROTOCOL_VERSION) return;
2993
+ const delivery = (0, import_resource_broadcast_protocol.resourceClientDelivery)(
2994
+ client.bufferedAmount,
2995
+ client.resourceSnapshotPending === true,
2996
+ MAX_RESOURCE_CLIENT_BUFFERED_AMOUNT
2997
+ );
2998
+ if (delivery === "snapshot") sendResourceSnapshots(client);
2999
+ }
3000
+ function flushResourceBroadcasts() {
3001
+ resourceBroadcastTimer = null;
3002
+ (0, import_resource_broadcast_protocol.drainResourceBroadcasts)(pendingResourceBroadcasts).forEach(broadcastResourceEvent);
3003
+ }
3004
+ function scheduleResourceBroadcast(event) {
3005
+ (0, import_resource_broadcast_protocol.coalesceResourceBroadcast)(pendingResourceBroadcasts, event);
3006
+ if (resourceBroadcastTimer) return;
3007
+ resourceBroadcastTimer = setTimeout(flushResourceBroadcasts, RESOURCE_BROADCAST_INTERVAL_MS);
3008
+ }
3009
+ function scheduleResourceUpdate(domain, resource) {
3010
+ if (!isRecord(resource)) return;
3011
+ const id = typeof resource.id === "string" ? resource.id : "";
3012
+ const collectionRevision = Number(resource.collectionRevision);
3013
+ const revision = Number(resource.revision);
3014
+ if (!id || !Number.isInteger(collectionRevision) || collectionRevision < 0 || !Number.isInteger(revision) || revision < 0) return;
3015
+ scheduleResourceBroadcast({
3016
+ domain,
3017
+ id,
3018
+ collectionRevision,
3019
+ kind: "updated",
3020
+ message: {
3021
+ type: `${domain}-resource-updated`,
3022
+ resource
3023
+ }
3024
+ });
3025
+ }
3026
+ function scheduleResourceDeletion(domain, deletion) {
3027
+ if (!isRecord(deletion)) return;
3028
+ const id = typeof deletion.id === "string" ? deletion.id : "";
3029
+ const collectionRevision = Number(deletion.collectionRevision);
3030
+ if (!id || !Number.isInteger(collectionRevision) || collectionRevision < 0) return;
3031
+ scheduleResourceBroadcast({
3032
+ domain,
3033
+ id,
3034
+ collectionRevision,
3035
+ kind: "deleted",
3036
+ message: {
3037
+ type: `${domain}-resource-deleted`,
3038
+ deletion
3039
+ }
3040
+ });
3041
+ }
3042
+ browserResourceManager.on("resource", (resource) => scheduleResourceUpdate("browser", resource));
3043
+ browserResourceManager.on("deleted", (deletion) => scheduleResourceDeletion("browser", deletion));
3044
+ computerResourceManager.on("resource", (resource) => scheduleResourceUpdate("computer", resource));
3045
+ computerResourceManager.on("deleted", (deletion) => scheduleResourceDeletion("computer", deletion));
3046
+ function broadcastState() {
3047
+ lastStateBroadcastAt = Date.now();
3048
+ stateBroadcastTimer = null;
3049
+ const state = buildStatePayload();
3050
+ const message = JSON.stringify({ type: "state", state });
3051
+ wss.clients.forEach((client) => {
3052
+ if (client.readyState === WebSocket.OPEN) {
3053
+ client.send(message);
3054
+ }
3055
+ });
3056
+ }
3057
+ function scheduleBroadcastState() {
3058
+ const now = Date.now();
3059
+ const elapsed = now - lastStateBroadcastAt;
3060
+ if (elapsed >= STATE_BROADCAST_INTERVAL_MS) {
3061
+ broadcastState();
3062
+ return;
3063
+ }
3064
+ if (stateBroadcastTimer) {
3065
+ return;
3066
+ }
3067
+ stateBroadcastTimer = setTimeout(() => {
3068
+ broadcastState();
3069
+ }, STATE_BROADCAST_INTERVAL_MS - elapsed);
3070
+ }
3071
+ function broadcastSessionPreview(preview) {
3072
+ wss.clients.forEach((client) => {
3073
+ const previewAllowed = client.previewScope !== "none" && (client.previewScope !== "focused" || client.focusedAgentId === preview.agentId);
3074
+ if (client.readyState === WebSocket.OPEN && previewAllowed) {
3075
+ sendPreview(client, preview);
3076
+ }
3077
+ });
3078
+ }
3079
+ function schedulePreviewBroadcast(preview) {
3080
+ const agentId = preview && preview.agentId;
3081
+ if (!agentId) {
3082
+ broadcastSessionPreview(preview);
3083
+ return;
3084
+ }
3085
+ const now = Date.now();
3086
+ const entry = pendingPreviewBroadcasts.get(agentId) || {
3087
+ lastAt: 0,
3088
+ timer: null,
3089
+ preview: null
3090
+ };
3091
+ entry.preview = preview;
3092
+ const elapsed = now - entry.lastAt;
3093
+ if (elapsed >= PREVIEW_BROADCAST_INTERVAL_MS) {
3094
+ if (entry.timer) {
3095
+ clearTimeout(entry.timer);
3096
+ entry.timer = null;
3097
+ }
3098
+ entry.lastAt = now;
3099
+ const latest = entry.preview;
3100
+ entry.preview = null;
3101
+ pendingPreviewBroadcasts.set(agentId, entry);
3102
+ broadcastSessionPreview(latest);
3103
+ return;
3104
+ }
3105
+ if (!entry.timer) {
3106
+ entry.timer = setTimeout(() => {
3107
+ entry.timer = null;
3108
+ entry.lastAt = Date.now();
3109
+ const latest = entry.preview;
3110
+ entry.preview = null;
3111
+ pendingPreviewBroadcasts.set(agentId, entry);
3112
+ if (latest) {
3113
+ broadcastSessionPreview(latest);
3114
+ }
3115
+ }, PREVIEW_BROADCAST_INTERVAL_MS - elapsed);
3116
+ }
3117
+ pendingPreviewBroadcasts.set(agentId, entry);
3118
+ }
3119
+ agentManager.onUpdate(() => {
3120
+ scheduleBroadcastState();
3121
+ });
3122
+ agentManager.on("provider-session-updated", () => {
3123
+ agentSessionInventory.invalidate();
3124
+ scheduleBroadcastState();
3125
+ });
3126
+ function broadcastAgentActivity(activity) {
3127
+ const message = JSON.stringify({
3128
+ type: "agent-activity",
3129
+ activity
3130
+ });
3131
+ wss.clients.forEach((client) => {
3132
+ if (client.readyState === WebSocket.OPEN) {
3133
+ client.send(message);
3134
+ }
3135
+ });
3136
+ }
3137
+ function scheduleAgentActivityBroadcast(activity) {
3138
+ if (!isAgentScopedServerEvent(activity)) return;
3139
+ const agentId = activity.agentId;
3140
+ const existing = pendingAgentActivityBroadcasts.get(agentId);
3141
+ if (existing) {
3142
+ existing.activity = activity;
3143
+ return;
3144
+ }
3145
+ const entry = {
3146
+ activity,
3147
+ timer: setTimeout(() => {
3148
+ pendingAgentActivityBroadcasts.delete(agentId);
3149
+ broadcastAgentActivity(entry.activity);
3150
+ }, AGENT_ACTIVITY_BROADCAST_DELAY_MS)
3151
+ };
3152
+ entry.timer.unref?.();
3153
+ pendingAgentActivityBroadcasts.set(agentId, entry);
3154
+ }
3155
+ agentManager.onAgentActivity(scheduleAgentActivityBroadcast);
3156
+ function scheduleAgentUpdate(update) {
3157
+ if (!isAgentScopedServerEvent(update)) return;
3158
+ const patch = sanitizeAgentUpdatePatch(update?.patch);
3159
+ if (!patch) return;
3160
+ const existing = pendingAgentUpdates.get(update.agentId);
3161
+ if (existing) {
3162
+ Object.assign(existing.patch, patch);
3163
+ return;
3164
+ }
3165
+ const entry = {
3166
+ patch,
3167
+ timer: setTimeout(() => {
3168
+ pendingAgentUpdates.delete(update.agentId);
3169
+ const message = JSON.stringify({
3170
+ type: "agent-update",
3171
+ update: { agentId: update.agentId, patch: entry.patch }
3172
+ });
3173
+ wss.clients.forEach((client) => {
3174
+ if (client.readyState === WebSocket.OPEN) client.send(message);
3175
+ });
3176
+ }, AGENT_ACTIVITY_BROADCAST_DELAY_MS)
3177
+ };
3178
+ entry.timer.unref?.();
3179
+ pendingAgentUpdates.set(update.agentId, entry);
3180
+ }
3181
+ agentManager.on("agent-update", scheduleAgentUpdate);
3182
+ function scheduleAcpSessionRevision(session) {
3183
+ if (!isAgentScopedServerEvent(session) || !Number.isFinite(Number(session.revision)) || typeof session.updatedAt !== "string") return;
3184
+ const previous = pendingAcpSessionRevisions.get(session.agentId);
3185
+ if (previous) {
3186
+ if (Number(session.revision) >= Number(previous.session.revision)) {
3187
+ previous.session = session;
3188
+ }
3189
+ return;
3190
+ }
3191
+ const entry = {
3192
+ session,
3193
+ timer: setTimeout(() => {
3194
+ pendingAcpSessionRevisions.delete(session.agentId);
3195
+ const message = JSON.stringify({
3196
+ type: "acp-session-revision",
3197
+ session: entry.session
3198
+ });
3199
+ wss.clients.forEach((client) => {
3200
+ if (client.readyState === WebSocket.OPEN) client.send(message);
3201
+ });
3202
+ }, AGENT_ACTIVITY_BROADCAST_DELAY_MS)
3203
+ };
3204
+ entry.timer.unref?.();
3205
+ pendingAcpSessionRevisions.set(session.agentId, entry);
3206
+ }
3207
+ agentManager.on("acp-session-revision", scheduleAcpSessionRevision);
3208
+ function broadcastAgentRead(read) {
3209
+ if (!isAgentScopedServerEvent(read)) return;
3210
+ const message = JSON.stringify({ type: "agent-read", read });
3211
+ wss.clients.forEach((client) => {
3212
+ if (client.readyState === WebSocket.OPEN) {
3213
+ client.send(message);
3214
+ }
3215
+ });
3216
+ }
3217
+ agentManager.on("agent-read", broadcastAgentRead);
3218
+ function sessionStreamKey(stream) {
3219
+ return `${stream.agentId}\0${stream.sessionSource || ""}`;
3220
+ }
3221
+ function broadcastSessionStream(stream) {
3222
+ const message = JSON.stringify({
3223
+ type: "session-output",
3224
+ stream
3225
+ });
3226
+ (0, import_session_stream_protocol.deliverSessionStreamToClients)(Array.from(wss.clients), stream, {
3227
+ openState: WebSocket.OPEN,
3228
+ maxBufferedAmount: MAX_SESSION_STREAM_CLIENT_BUFFERED_AMOUNT,
3229
+ message
3230
+ });
3231
+ }
3232
+ function flushSessionStreams() {
3233
+ sessionStreamBroadcastTimer = null;
3234
+ const streams = Array.from(pendingSessionStreams.values());
3235
+ pendingSessionStreams.clear();
3236
+ if (streams.length > 0) lastSessionStreamBroadcastAt = Date.now();
3237
+ streams.forEach(broadcastSessionStream);
3238
+ }
3239
+ function scheduleSessionStreamBroadcast(stream) {
3240
+ if (!isAgentScopedServerEvent(stream)) return;
3241
+ const now = Date.now();
3242
+ if ((0, import_session_stream_protocol.shouldBroadcastSessionStreamImmediately)({
3243
+ pendingCount: pendingSessionStreams.size,
3244
+ lastBroadcastAt: lastSessionStreamBroadcastAt,
3245
+ now,
3246
+ intervalMs: SESSION_STREAM_BROADCAST_INTERVAL_MS
3247
+ })) {
3248
+ lastSessionStreamBroadcastAt = now;
3249
+ broadcastSessionStream((0, import_session_stream_protocol.coalesceSessionStream)(null, stream));
3250
+ return;
3251
+ }
3252
+ const key = sessionStreamKey(stream);
3253
+ const existing = pendingSessionStreams.get(key);
3254
+ pendingSessionStreams.set(key, (0, import_session_stream_protocol.coalesceSessionStream)(existing, stream));
3255
+ if (!sessionStreamBroadcastTimer) {
3256
+ sessionStreamBroadcastTimer = setTimeout(flushSessionStreams, SESSION_STREAM_BROADCAST_INTERVAL_MS);
3257
+ if (typeof sessionStreamBroadcastTimer.unref === "function") sessionStreamBroadcastTimer.unref();
3258
+ }
3259
+ }
3260
+ agentManager.onSessionStream((stream) => {
3261
+ scheduleSessionStreamBroadcast(stream);
3262
+ });
3263
+ agentManager.onSessionPreview((preview) => {
3264
+ if (isAgentScopedServerEvent(preview)) schedulePreviewBroadcast(preview);
3265
+ });
3266
+ agentManager.onSystemStats((systemStats) => {
3267
+ if (!isRecord(systemStats)) return;
3268
+ const usageSnapshot = agentManager.getAgentUsageSnapshots();
3269
+ const message = JSON.stringify({
3270
+ type: "system-stats",
3271
+ stats: {
3272
+ ...systemStats,
3273
+ ip: (0, import_network.getPrimaryLocalIP)(),
3274
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
3275
+ },
3276
+ uptime: agentManager.getUptime(),
3277
+ usageRate: {
3278
+ windowMs: usageSnapshot.windowMs,
3279
+ estimatedTokensPerMinute: usageSnapshot.estimatedTokensPerMinute,
3280
+ source: usageSnapshot.source
3281
+ }
3282
+ });
3283
+ wss.clients.forEach((client) => {
3284
+ if (client.readyState === WebSocket.OPEN) {
3285
+ client.send(message);
3286
+ }
3287
+ });
3288
+ });
3289
+ let serverStarted = false;
3290
+ let terminalRuntimeCleanupPromise = null;
3291
+ function runTerminalRuntimeStartupCleanup() {
3292
+ if (!terminalRuntimeCleanupPromise) {
3293
+ terminalRuntimeCleanupPromise = (0, import_terminal_runtime_cleanup.cleanupTerminalRuntime)({ configDir: configManager.farmingDir }).catch((error) => {
3294
+ console.warn("Failed to clean terminal runtime leftovers:", caughtError(error).message || error);
3295
+ return null;
3296
+ });
3297
+ }
3298
+ return terminalRuntimeCleanupPromise;
3299
+ }
3300
+ function startServer() {
3301
+ if (serverStarted) return server;
3302
+ serverStarted = true;
3303
+ void Promise.all([
3304
+ runTerminalRuntimeStartupCleanup(),
3305
+ browserRuntimeRecoveryPromise,
3306
+ computerRuntimeRecoveryPromise
3307
+ ]).finally(() => {
3308
+ server.listen(PORT, () => {
3309
+ const token = tokenAuth.getToken();
3310
+ const localIPs = (0, import_network.getLocalIPs)();
3311
+ const entryPath = BASE_PATH || "/";
3312
+ const entrySuffix = authEnabled ? `${entryPath}?token=${token}` : entryPath;
3313
+ console.log("");
3314
+ console.log(" Farming server running on:");
3315
+ console.log("");
3316
+ console.log(` Local: http://localhost:${PORT}${entrySuffix}`);
3317
+ localIPs.forEach((ip) => {
3318
+ console.log(` Network: http://${ip}:${PORT}${entrySuffix}`);
3319
+ });
3320
+ console.log("");
3321
+ if (authEnabled) {
3322
+ console.log(` Token: ${token}`);
3323
+ const tokenInfo = tokenAuth.getTokenInfo();
3324
+ if (tokenInfo) {
3325
+ console.log(` Token style: ${tokenInfo.style} (${tokenInfo.source}, ~${tokenInfo.entropyBits} bits)`);
3326
+ }
3327
+ } else {
3328
+ console.log(" Token auth: disabled");
3329
+ }
3330
+ console.log("");
3331
+ setTimeout(warmCodexExecutableVersionCache, 100);
3332
+ void autoResumeMainPageAgentSessions();
3333
+ });
3334
+ });
3335
+ return server;
3336
+ }
3337
+ if (require.main === module) {
3338
+ startServer();
3339
+ }
3340
+ // Annotate the CommonJS export names for ESM import in node:
3341
+ 0 && (module.exports = {
3342
+ agentManager,
3343
+ app,
3344
+ appendIndexHtmlAssetToken,
3345
+ browserResourceManager,
3346
+ computerResourceManager,
3347
+ handleMessage,
3348
+ resolveCliBinDir,
3349
+ resolveInputTargetAgentId,
3350
+ rewriteIndexHtmlForBasePath,
3351
+ runTerminalRuntimeStartupCleanup,
3352
+ server,
3353
+ startServer,
3354
+ workspaceFileService,
3355
+ wss
3356
+ });