agentbox-app 0.1.12 → 0.1.16

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 (370) hide show
  1. package/README.md +24 -9
  2. package/bin/agentbox.mjs +23 -1
  3. package/briefs/message-rules.md +36 -8
  4. package/briefs/worker.md +46 -23
  5. package/main/account-discovery.mjs +122 -30
  6. package/main/account-tooling.mjs +29 -1
  7. package/main/account-usage.mjs +46 -0
  8. package/main/acp.mjs +296 -0
  9. package/main/agent-homes.mjs +158 -0
  10. package/main/agent-sessions.mjs +311 -260
  11. package/main/agent-updates.mjs +9 -22
  12. package/main/agents.mjs +179 -466
  13. package/main/analytics.mjs +42 -2
  14. package/main/answer-modes.mjs +103 -37
  15. package/main/approval-prompt-core.mjs +197 -0
  16. package/main/approval-prompt-server.mjs +4 -138
  17. package/main/approvals.mjs +101 -297
  18. package/main/artifact-path.mjs +47 -18
  19. package/main/claude-bin.mjs +45 -311
  20. package/main/claude-input.mjs +57 -7
  21. package/main/claude-launch-env.mjs +54 -0
  22. package/main/claude-models.mjs +39 -7
  23. package/main/claude-plan.mjs +64 -16
  24. package/main/claude-session-open.mjs +28 -0
  25. package/main/claude-usage-process.mjs +40 -0
  26. package/main/claude-usage.mjs +94 -59
  27. package/main/cloud/account.mjs +179 -0
  28. package/main/cloud/aws.mjs +127 -0
  29. package/main/cloud/browser-return.mjs +58 -0
  30. package/main/cloud/clients.mjs +15 -0
  31. package/main/cloud/codex-cloud.mjs +53 -0
  32. package/main/cloud/config.mjs +107 -0
  33. package/main/cloud/docs.mjs +219 -0
  34. package/main/cloud/exec.mjs +27 -0
  35. package/main/cloud/host.mjs +165 -0
  36. package/main/cloud/kit.mjs +80 -0
  37. package/main/cloud/logins.mjs +543 -0
  38. package/main/cloud/managed.mjs +115 -0
  39. package/main/cloud/marker.mjs +104 -0
  40. package/main/cloud/org.mjs +53 -0
  41. package/main/cloud/reuse.mjs +208 -0
  42. package/main/cloud/run-cli.mjs +177 -0
  43. package/main/cloud/run.mjs +1934 -0
  44. package/main/cloud/usage.mjs +76 -0
  45. package/main/cloud-permission.mjs +51 -0
  46. package/main/codex-account.mjs +78 -0
  47. package/main/codex-app-server.mjs +276 -409
  48. package/main/codex-approvals.mjs +148 -433
  49. package/main/codex-bin.mjs +51 -249
  50. package/main/codex-models.mjs +20 -2
  51. package/main/codex-session.mjs +246 -506
  52. package/main/codex-threads-worker.mjs +12 -0
  53. package/main/codex-threads.mjs +112 -11
  54. package/main/codex-usage-file.mjs +5 -3
  55. package/main/codex-usage.mjs +56 -7
  56. package/main/codex-watch.mjs +83 -5
  57. package/main/codex.mjs +95 -363
  58. package/main/config.mjs +37 -5
  59. package/main/corner-tag.mjs +810 -0
  60. package/main/crash-report.mjs +26 -3
  61. package/main/dev-window.mjs +1 -1
  62. package/main/engine-bin.mjs +545 -0
  63. package/main/engine-setup.mjs +25 -24
  64. package/main/feedback.mjs +14 -31
  65. package/main/folder-pictures.mjs +2 -2
  66. package/main/freeze-recorder-thread.mjs +86 -0
  67. package/main/freeze-recorder.mjs +146 -0
  68. package/main/fresh-user.mjs +20 -9
  69. package/main/git-change-offthread.mjs +17 -3
  70. package/main/git-change-thread.mjs +5 -2
  71. package/main/harnesses/claude-stream.mjs +208 -0
  72. package/main/harnesses/claude.mjs +105 -0
  73. package/main/harnesses/codex.mjs +72 -0
  74. package/main/harnesses/common.mjs +78 -0
  75. package/main/harnesses/instructions-file.mjs +116 -0
  76. package/main/harnesses/opencode.mjs +234 -0
  77. package/main/harnesses.mjs +65 -0
  78. package/main/importable-threads.mjs +96 -0
  79. package/main/ipc.mjs +520 -135
  80. package/main/leftovers.mjs +117 -25
  81. package/main/liquid-glass.mjs +26 -0
  82. package/main/live-replies.mjs +10 -4
  83. package/main/local-app-frames.mjs +82 -0
  84. package/main/main-went-red.mjs +330 -0
  85. package/main/main.mjs +248 -37
  86. package/main/memory-gate-history.mjs +85 -20
  87. package/main/memory-gate-server.mjs +129 -11
  88. package/main/memory-gate.mjs +62 -8
  89. package/main/memory-log.mjs +124 -0
  90. package/main/menu.mjs +4 -2
  91. package/main/message-priority.mjs +2 -2
  92. package/main/mic.mjs +38 -0
  93. package/main/native/liquid-glass/build/liquid-glass.node +0 -0
  94. package/main/new-chats-watch.mjs +110 -0
  95. package/main/new-user-window.mjs +25 -0
  96. package/main/notify.mjs +64 -5
  97. package/main/opencode-server.mjs +271 -0
  98. package/main/opencode.mjs +287 -0
  99. package/main/phone-alerts.mjs +117 -0
  100. package/main/phone-inbox.mjs +625 -0
  101. package/main/phone-link.mjs +571 -0
  102. package/main/phone-uploads.mjs +476 -0
  103. package/main/picture-shape.mjs +114 -0
  104. package/main/quit-sequence.mjs +143 -0
  105. package/main/refresh-agent-models.mjs +15 -2
  106. package/main/relay-address.mjs +28 -0
  107. package/main/relay-client.mjs +314 -0
  108. package/main/repeats.mjs +10 -4
  109. package/main/row-label.mjs +18 -47
  110. package/main/run-change.mjs +13 -0
  111. package/main/run-ledger.mjs +135 -0
  112. package/main/runs-on.mjs +32 -35
  113. package/main/save-attachments.mjs +41 -0
  114. package/main/serve.mjs +155 -9
  115. package/main/settings.mjs +364 -50
  116. package/main/shell-path.mjs +234 -0
  117. package/main/ship-conflict-brief.mjs +119 -0
  118. package/main/ship-queue.mjs +134 -20
  119. package/main/sign-in-files.mjs +3 -7
  120. package/main/source-updater.mjs +45 -11
  121. package/main/stage-attachment.mjs +68 -0
  122. package/main/stall-watch.mjs +50 -0
  123. package/main/start-hold.mjs +73 -0
  124. package/main/store/home.mjs +87 -232
  125. package/main/store/paths.mjs +11 -0
  126. package/main/store/teammates-files.mjs +99 -0
  127. package/main/store/work-items.mjs +231 -13
  128. package/main/store.mjs +227 -53
  129. package/main/supervisor.mjs +3865 -699
  130. package/main/task-commands.mjs +13 -17
  131. package/main/task-folders-offthread.mjs +6 -1
  132. package/main/task-folders.mjs +897 -105
  133. package/main/task-terminals.mjs +147 -41
  134. package/main/team/index.mjs +114 -10
  135. package/main/team/memory-cloud.mjs +183 -1
  136. package/main/team/room-hours.mjs +48 -0
  137. package/main/team/room-setup.mjs +31 -0
  138. package/main/team/rooms-memory.mjs +454 -0
  139. package/main/team/rooms-shape.mjs +42 -0
  140. package/main/team/rooms-supabase.mjs +324 -0
  141. package/main/team/rooms.mjs +575 -0
  142. package/main/team/session.mjs +96 -26
  143. package/main/team/supabase-backend.mjs +180 -0
  144. package/main/team/sync.mjs +109 -1
  145. package/main/teammates.mjs +580 -0
  146. package/main/terminal-host.mjs +143 -0
  147. package/main/terminal-hosts.mjs +261 -0
  148. package/main/transcript-search.mjs +80 -0
  149. package/main/updater.mjs +71 -9
  150. package/main/webview-guard.mjs +45 -0
  151. package/main/worktree-include.mjs +365 -0
  152. package/mcp/core/documents.mjs +3 -1
  153. package/mcp/core/repeats.mjs +88 -0
  154. package/mcp/core/teammates.mjs +222 -0
  155. package/mcp/core/work.mjs +76 -4
  156. package/mcp/stdio.mjs +24 -11
  157. package/mcp/tools.mjs +116 -8
  158. package/package.json +30 -5
  159. package/renderer/dist/assets/abnfDiagram-YUKMZFIV-CoWB6YUK.js +1 -0
  160. package/renderer/dist/assets/arc-Ch0NrCul.js +1 -0
  161. package/renderer/dist/assets/architectureDiagram-47P4ROYG-CXz9M5tQ.js +36 -0
  162. package/renderer/dist/assets/blockDiagram-E7TT5TSB-DchGUuHH.js +140 -0
  163. package/renderer/dist/assets/c4Diagram-CRA5TL53-Cip-zNyr.js +38 -0
  164. package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
  165. package/renderer/dist/assets/chunk-2Q5K7J3B-BoXid_2r.js +1 -0
  166. package/renderer/dist/assets/chunk-5VM5RSS4-Cpez1rRz.js +15 -0
  167. package/renderer/dist/assets/chunk-7TKQ45FW-DKA84YkE.js +1 -0
  168. package/renderer/dist/assets/chunk-AW2ZBBNX-Dg-SumPG.js +88 -0
  169. package/renderer/dist/assets/chunk-FVRAUYC3-06TCvPm1.js +2 -0
  170. package/renderer/dist/assets/chunk-HTAEGDNF-CMEBy1Td.js +1 -0
  171. package/renderer/dist/assets/chunk-JWPE2WC7-BJ2ObmV-.js +1 -0
  172. package/renderer/dist/assets/chunk-KQW6MTUR-CGWOYXUE.js +1 -0
  173. package/renderer/dist/assets/chunk-XXDRQBXY-CiZp07XC.js +1 -0
  174. package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-DJNIX719.js +217 -0
  175. package/renderer/dist/assets/cornerTag-CPgvu-q3.js +1 -0
  176. package/renderer/dist/assets/cornerTag-oMfEjzCO.css +1 -0
  177. package/renderer/dist/assets/cose-bilkent-JH36ORCC-CbkyZOUH.js +1 -0
  178. package/renderer/dist/assets/cynefin-EF2NZ3EQ-78iOroZO.js +141 -0
  179. package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-GcCopMs7.js +62 -0
  180. package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
  181. package/renderer/dist/assets/dagre-W4DXFKR2-DHLlXLkz.js +4 -0
  182. package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  183. package/renderer/dist/assets/diagram-2UJZ2QOL-DuZSUHcH.js +200 -0
  184. package/renderer/dist/assets/diagram-OVF4WLC6-Ckxu3cqH.js +24 -0
  185. package/renderer/dist/assets/diagram-PFPMY2P6-CsTw9Ycn.js +30 -0
  186. package/renderer/dist/assets/diagram-UMYRVEAY-CTXd8qVg.js +24 -0
  187. package/renderer/dist/assets/diagram-YEKJPTXX-CJs1T_9m.js +41 -0
  188. package/renderer/dist/assets/diagram-ZIFT7M5P-CvjI23mf.js +3 -0
  189. package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-d_IeAzOJ.js +1 -0
  190. package/renderer/dist/assets/elk-IJKZMXRS-zo9KFP3N.js +27 -0
  191. package/renderer/dist/assets/erDiagram-O2IAPWRE-DqfYf5Dt.js +99 -0
  192. package/renderer/dist/assets/flowDiagram-OXPTDLAJ-DAWsMLmo.js +213 -0
  193. package/renderer/dist/assets/ganttDiagram-R7TSEDQI-4ml_bhwR.js +292 -0
  194. package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-B_WcqSId.js +106 -0
  195. package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
  196. package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-CI-jYqzc.js +2 -0
  197. package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
  198. package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-B56VWRlV.js +70 -0
  199. package/renderer/dist/assets/journeyDiagram-COXZFDF6-D1-p09Lr.js +139 -0
  200. package/renderer/dist/assets/kanban-definition-P3RFRI5V-Cv3w7JoN.js +89 -0
  201. package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
  202. package/renderer/dist/assets/linear-C35V2Spi.js +1 -0
  203. package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
  204. package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
  205. package/renderer/dist/assets/mermaid.core-BzGT5Vo9.js +314 -0
  206. package/renderer/dist/assets/mindmap-definition-LZFPQGKD-BZUfuymC.js +96 -0
  207. package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  208. package/renderer/dist/assets/pegDiagram-BGZESJAR-DFh9XNGe.js +1 -0
  209. package/renderer/dist/assets/pieDiagram-CAPJLFHJ-C65tPVsy.js +39 -0
  210. package/renderer/dist/assets/product-name-CSNW6d7s.js +40 -0
  211. package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-Cushj_nk.js +7 -0
  212. package/renderer/dist/assets/railroadDiagram-SM67HX2B-DZGebGx5.js +1 -0
  213. package/renderer/dist/assets/requirementDiagram-X7JNWC4A-DpAB0MNo.js +84 -0
  214. package/renderer/dist/assets/sankeyDiagram-UM26HJRW-D-29rHra.js +40 -0
  215. package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-FfOQAZol.js +169 -0
  216. package/renderer/dist/assets/sizeCapture-INFHLROL-C_n6fDBY.js +1 -0
  217. package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-BZjenzJG.js +291 -0
  218. package/renderer/dist/assets/swimlanes-N4OXWK64-DxaMU9vX.js +1 -0
  219. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
  220. package/renderer/dist/assets/timeline-definition-YOQAKGHF-DTg5HJKz.js +120 -0
  221. package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-Bc3MZDsl.js +370 -0
  222. package/renderer/dist/assets/vennDiagram-BLWOH2XV-GIVGk9Id.js +34 -0
  223. package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-NmJnbQOg.js +78 -0
  224. package/renderer/dist/assets/xychartDiagram-TAQBALBS-p-kfGfnW.js +7 -0
  225. package/renderer/dist/corner-tag.html +13 -0
  226. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  227. package/renderer/dist/emulator/data/compression/README.md +11 -0
  228. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  229. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  230. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  231. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  232. package/renderer/dist/emulator/data/cores/README.md +22 -0
  233. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  234. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  235. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  236. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  237. package/renderer/dist/emulator/data/cores/package.json +65 -0
  238. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  239. package/renderer/dist/emulator/data/emulator.css +1614 -0
  240. package/renderer/dist/emulator/data/loader.js +170 -0
  241. package/renderer/dist/emulator/data/localization/README.md +63 -0
  242. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  243. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  244. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  245. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  246. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  247. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  248. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  249. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  250. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  251. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  252. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  253. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  254. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  255. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  256. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  257. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  258. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  259. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  260. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  261. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  262. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  263. package/renderer/dist/emulator/data/src/compression.js +142 -0
  264. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  265. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  266. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  267. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  268. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  269. package/renderer/dist/emulator/data/src/storage.js +125 -0
  270. package/renderer/dist/emulator/data/version.json +5 -0
  271. package/renderer/dist/emulator/host.js +297 -0
  272. package/renderer/dist/emulator/index.html +28 -0
  273. package/renderer/dist/emulator/provenance.json +6 -0
  274. package/renderer/dist/index.html +16 -3
  275. package/scripts/after-install.mjs +30 -8
  276. package/scripts/approval-server.sh +24 -5
  277. package/scripts/before-publish.mjs +12 -10
  278. package/scripts/cloud-shim.pl +57 -0
  279. package/shared/account-names.d.mts +6 -0
  280. package/shared/account-names.mjs +15 -0
  281. package/shared/agent-hours.mjs +136 -0
  282. package/shared/agent-import.mjs +62 -34
  283. package/shared/agents.d.mts +1 -0
  284. package/shared/agents.mjs +126 -522
  285. package/shared/analytics-events.mjs +12 -0
  286. package/shared/approval-card.mjs +36 -0
  287. package/shared/bridge-map.mjs +45 -1
  288. package/shared/claude-code-shape.mjs +6 -0
  289. package/shared/claude-commands.generated.mjs +6 -4
  290. package/shared/claude-models.generated.mjs +6 -4
  291. package/shared/claude-usage.d.mts +1 -0
  292. package/shared/claude-usage.mjs +42 -1
  293. package/shared/cloud-hand-back.d.mts +7 -0
  294. package/shared/cloud-hand-back.mjs +20 -0
  295. package/shared/cloud-kit.d.mts +219 -0
  296. package/shared/cloud-kit.mjs +341 -0
  297. package/shared/cloud-login-name.mjs +4 -0
  298. package/shared/cloud-start.mjs +17 -0
  299. package/shared/cloud-versions.mjs +23 -0
  300. package/shared/codex-import.mjs +2 -2
  301. package/shared/codex-modes.d.mts +22 -9
  302. package/shared/codex-modes.mjs +67 -11
  303. package/shared/codex-usage.mjs +108 -2
  304. package/shared/corner-tag.d.mts +36 -0
  305. package/shared/corner-tag.mjs +396 -0
  306. package/shared/dead-run-trace.mjs +19 -2
  307. package/shared/engines.mjs +76 -300
  308. package/shared/feedback.d.mts +7 -0
  309. package/shared/feedback.mjs +25 -0
  310. package/shared/first-run-practice.mjs +17 -24
  311. package/shared/harness-definitions.mjs +111 -0
  312. package/shared/instruction-defaults.json +2 -2
  313. package/shared/key-caps.d.mts +14 -0
  314. package/shared/key-caps.mjs +68 -0
  315. package/shared/message-edits.d.mts +7 -0
  316. package/shared/message-edits.mjs +35 -0
  317. package/shared/move-thread.d.mts +24 -0
  318. package/shared/move-thread.mjs +112 -0
  319. package/shared/named-threads.mjs +72 -0
  320. package/shared/not-shipped.d.mts +22 -0
  321. package/shared/not-shipped.mjs +89 -0
  322. package/shared/notify-rules.d.mts +31 -0
  323. package/shared/notify-rules.mjs +55 -8
  324. package/shared/phone-files.mjs +28 -0
  325. package/shared/phone-settings.mjs +118 -0
  326. package/shared/phone-snapshot.d.mts +16 -0
  327. package/shared/phone-snapshot.mjs +348 -0
  328. package/shared/picture-formats.d.mts +1 -0
  329. package/shared/picture-formats.mjs +13 -0
  330. package/shared/provider-commands.mjs +10 -2
  331. package/shared/rank.d.mts +4 -1
  332. package/shared/rank.mjs +82 -19
  333. package/shared/relay-address.mjs +6 -0
  334. package/shared/relay-crypto.mjs +165 -0
  335. package/shared/repeats.d.mts +8 -0
  336. package/shared/repeats.mjs +33 -2
  337. package/shared/room-cards.mjs +167 -0
  338. package/shared/room-links.d.mts +4 -0
  339. package/shared/room-links.mjs +21 -0
  340. package/shared/runs-on.d.mts +3 -13
  341. package/shared/runs-on.mjs +31 -52
  342. package/shared/shipped-note.d.mts +4 -0
  343. package/shared/shipped-note.mjs +6 -0
  344. package/shared/shortcuts.mjs +24 -10
  345. package/shared/spawn-trouble.d.mts +4 -0
  346. package/shared/spawn-trouble.mjs +484 -30
  347. package/shared/team-rules.mjs +9 -2
  348. package/shared/teammates.d.mts +19 -0
  349. package/shared/teammates.mjs +756 -0
  350. package/shared/terminal-focus.d.mts +1 -0
  351. package/shared/terminal-focus.mjs +13 -0
  352. package/shared/terminal-input.d.mts +1 -0
  353. package/shared/terminal-input.mjs +30 -0
  354. package/shared/terminal-keys.d.mts +1 -0
  355. package/shared/terminal-keys.mjs +16 -0
  356. package/shared/terminal-size.d.mts +4 -0
  357. package/shared/terminal-size.mjs +29 -0
  358. package/shared/thread-cards.mjs +4 -1
  359. package/shared/usage.d.mts +12 -0
  360. package/shared/usage.mjs +56 -3
  361. package/shared/websites.d.mts +15 -0
  362. package/shared/websites.mjs +61 -0
  363. package/shared/where-it-runs-from.d.mts +43 -0
  364. package/shared/where-it-runs-from.mjs +155 -0
  365. package/shared/work-items.mjs +150 -29
  366. package/shared/work-lines.mjs +93 -245
  367. package/shared/xterm-invalid-range.d.mts +2 -0
  368. package/shared/xterm-invalid-range.mjs +58 -0
  369. package/renderer/dist/assets/index-ChKsQkRw.js +0 -1169
  370. package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
@@ -66,28 +66,55 @@ export function artifactRoots(prod) {
66
66
  // now resolves by exact path or not at all.
67
67
  const SKIP = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'sessions', 'runs', 'pending-writes', 'checkpoints']);
68
68
 
69
- // Hunt the file down by NAME inside one product's trees, newest match wins.
70
- // Workers write links relative to whatever directory they were standing in, so
71
- // a fixed root list will always miss some of them, and every miss cost her a
72
- // 10-minute detour (2026-08-05). Bounded: six levels, own product only.
73
- function huntByName(base, roots, fs) {
74
- let best = null;
75
- const walk = (dir, depth) => {
69
+ // How long one walk of a tree answers for it. A thread asks for every name it
70
+ // mentions the moment it opens, so the walk has to outlive that burst; a
71
+ // picture that lands later is found by the next look, which comes after this.
72
+ export const HUNT_FRESH_MS = 5000;
73
+ const walks = new Map();
74
+
75
+ // Every file name in one tree, read without holding the main process.
76
+ async function walkNames(tree, fs) {
77
+ const names = new Map();
78
+ const walk = async (dir, depth) => {
76
79
  if (depth > 6) return;
77
80
  let entries;
78
- try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
81
+ try { entries = await fs.promises.readdir(dir, { withFileTypes: true }); } catch { return; }
82
+ const below = [];
79
83
  for (const e of entries) {
80
84
  if (e.name.startsWith('.')) continue;
81
85
  const full = path.join(dir, e.name);
82
- if (e.isDirectory()) { if (!SKIP.has(e.name)) walk(full, depth + 1); }
83
- else if (e.name === base) {
84
- let m = 0;
85
- try { m = fs.statSync(full).mtimeMs; } catch {}
86
- if (!best || m > best.m) best = { full, m };
87
- }
86
+ if (e.isDirectory()) { if (!SKIP.has(e.name)) below.push(walk(full, depth + 1)); }
87
+ else if (names.has(e.name)) names.get(e.name).push(full);
88
+ else names.set(e.name, [full]);
88
89
  }
90
+ await Promise.all(below);
89
91
  };
90
- for (const root of roots) walk(root, 0);
92
+ await walk(tree, 0);
93
+ return names;
94
+ }
95
+
96
+ function namesIn(tree, fs, now) {
97
+ const kept = walks.get(tree);
98
+ if (kept && kept.fs === fs && now - kept.at < HUNT_FRESH_MS) return kept.names;
99
+ const names = walkNames(tree, fs);
100
+ walks.set(tree, { at: now, fs, names });
101
+ if (walks.size > 32) walks.delete(walks.keys().next().value);
102
+ return names;
103
+ }
104
+
105
+ // Hunt the file down by NAME inside one product's trees, newest match wins.
106
+ // Workers write links relative to whatever directory they were standing in, so
107
+ // a fixed root list will always miss some of them, and every miss cost her a
108
+ // 10-minute detour (2026-08-05). Bounded: six levels, own product only.
109
+ async function huntByName(base, trees, fs, now) {
110
+ let best = null;
111
+ for (const tree of trees) {
112
+ for (const full of (await namesIn(tree, fs, now)).get(base) ?? []) {
113
+ let m;
114
+ try { m = (await fs.promises.stat(full)).mtimeMs; } catch { continue; }
115
+ if (!best || m > best.m) best = { full, m };
116
+ }
117
+ }
91
118
  return best?.full ?? null;
92
119
  }
93
120
 
@@ -104,7 +131,7 @@ export function notFoundMessage(raw, own, product) {
104
131
  //
105
132
  // `products` is the whole list only so the card's own product can be found in
106
133
  // it. Nothing in here reads another entry.
107
- export function resolveArtifact({ src, product, products = [], accountRoot, storeRoot, fs = fsDefault }) {
134
+ export async function resolveArtifact({ src, product, products = [], accountRoot, storeRoot, fs = fsDefault, now = Date.now() }) {
108
135
  if (typeof src !== 'string' || !src) return { ok: false, error: 'No file was named.' };
109
136
  const raw = decodeURIComponent(src.replace(/^file:\/\//, '')).replace(/^\.\//, '');
110
137
  const own = products.find((x) => x.slug === product) ?? null;
@@ -144,10 +171,12 @@ export function resolveArtifact({ src, product, products = [], accountRoot, stor
144
171
 
145
172
  // The hunt takes the two TREES, not the root list: designs/ and attachments/
146
173
  // sit inside the docs dir already, and walking them twice finds nothing new.
174
+ // A run's change record is never hunted for: `runs` is skipped, so the walk
175
+ // could not find it, and the open thread asks for it on every refresh.
147
176
  const base = path.basename(raw);
148
177
  const trees = own ? [own.dir, own.repoPath].filter(Boolean) : [];
149
- if (base && trees.length) {
150
- const found = huntByName(base, trees, fs);
178
+ if (base && trees.length && raw.split('/')[0] !== 'runs') {
179
+ const found = await huntByName(base, trees, fs, now);
151
180
  if (found) return { ok: true, path: found };
152
181
  }
153
182
 
@@ -1,338 +1,72 @@
1
- // Where Claude Code actually is, on a machine that is not hers.
1
+ // Where Claude Code actually is, on a machine that is not ours.
2
2
  //
3
- // Agentbox assumed one path, `~/.local/bin/claude`, which is where Claude Code's
4
- // own installer puts it. That is one of several normal answers: install through
5
- // npm or Homebrew and the binary is somewhere else entirely. For those people
6
- // the app found nothing, said "missing" on a grey dot in Settings, and gave no
7
- // sentence and no next step. It read as broken rather than as unfinished.
3
+ // THIS FILE IS NOW THE NAMES AND NOTHING ELSE. The search, the registry entry
4
+ // that declares Claude Code, the measured notes about where it installs and the
5
+ // `found` / `certain` rule all live in main/engine-bin.mjs, which does the same
6
+ // job for every coding agent the app can run. Until 2026-10-08 this was 338
7
+ // lines and main/codex-bin.mjs was 279, and they were two copies of one idea:
8
+ // adding a harness meant a third file and three hundred lines of test.
8
9
  //
9
- // THE RULE this file exists to keep: never tell someone Claude Code is
10
- // missing unless we are certain it is.
11
- //
12
- // So there are two different questions here and they must never be answered by
13
- // the same value. `found` is whether a runnable file is really there, and it
14
- // decides which path gets spawned. `certain` is whether we are entitled to SAY
15
- // it is missing, and everything below exists to make `certain` hard to earn.
16
- //
17
- // Four steps before an absence is ever claimed, cheapest first.
18
- //
19
- // KNOWN PATHS. A filesystem check is instant, spawns nothing, and is the right
20
- // answer for most machines. The list is every normal home for this binary: the
21
- // installer's own directory, Claude Code's local install (what
22
- // `claude migrate-installer` leaves behind), Homebrew on Apple Silicon and on
23
- // Intel, MacPorts, and the default bin of every package manager that is not npm.
24
- //
25
- // VERSION MANAGER DIRECTORIES. nvm, fnm, n and asdf each keep one bin directory
26
- // PER INSTALLED NODE, under a name nobody can guess, so they are read rather
27
- // than listed. A global npm install under any of them lands there and nowhere
28
- // else, and this is the case the shell step used to be the only cover for.
29
- //
30
- // THE SHELL. The catch-all for installs no list can enumerate: volta, bun, a
31
- // custom npm prefix. It must be a LOGIN, INTERACTIVE shell, because those
32
- // managers put themselves on PATH from `.zprofile` and `.zshrc` and nowhere
33
- // else. And it is needed at all because a double-clicked Electron app inherits
34
- // launchd's PATH, which is /usr/bin:/bin:/usr/sbin:/sbin and has never heard of
35
- // any of them. That is the whole reason an npm install is obvious in a terminal
36
- // and invisible to the app. It is asked more than once now, because ONE probe
37
- // that fails to start is not a fact about anybody's Mac.
10
+ // WHAT IS KEPT HERE is the vocabulary the rest of the app already imports --
11
+ // main/config.mjs, main/settings.mjs, scripts/read-claude-commands.mjs,
12
+ // scripts/read-claude-models.mjs -- plus `askShell` and `shellProbes`, which
13
+ // several tests reach for through this file because Claude Code is the binary the
14
+ // app will not open an inbox without.
38
15
  //
39
- // EVIDENCE THAT IT LIVES HERE ANYWAY. Last, and it decides nothing about which
40
- // binary to run: it only takes away our right to announce an absence. Claude
41
- // Code leaves a home folder, a settings file and a projects directory behind
42
- // the moment it is used once. If those are on this Mac then Claude Code is on
43
- // this Mac, whatever our search of PATH did or did not manage, and the honest
44
- // thing on the screen is silence rather than a sentence telling somebody to
45
- // install software they are already running. A dangling `claude` symlink counts
46
- // the same way, and it is a real state: the installer points ~/.local/bin/claude
47
- // at a versioned file and swaps that file on every update.
16
+ // THE RULE THIS FILE ARGUED FOR AND engine-bin.mjs KEEPS: never tell somebody
17
+ // Claude Code is missing unless we are certain it is. `found` is whether a
18
+ // runnable file is really there, and it decides which path gets spawned.
19
+ // `certain` is whether we are entitled to SAY it is missing.
48
20
  //
49
21
  // Nothing here overrides the config file. An explicit `claudeBin` in
50
- // zero.config.json wins untouched and is never searched around, because that
51
- // file promises explicit paths with no discovery, and a search that
52
- // second-guesses a written setting would break that promise.
22
+ // zero.config.json wins untouched and is never searched around.
53
23
 
54
- import fs from 'node:fs';
55
24
  import os from 'node:os';
56
- import path from 'node:path';
57
- import { execFileSync } from 'node:child_process';
58
-
59
- // In order. The first that exists wins, so the installer's path stays first and
60
- // a machine that has always worked keeps resolving exactly as it did.
61
- export function candidatePaths(home = os.homedir()) {
62
- return [
63
- path.join(home, '.local/bin/claude'), // the installer
64
- path.join(home, '.claude/local/claude'), // claude migrate-installer
65
- '/opt/homebrew/bin/claude', // Homebrew, Apple Silicon
66
- '/usr/local/bin/claude', // Homebrew on Intel, and npm -g
67
- path.join(home, '.bun/bin/claude'), // bun
68
- path.join(home, '.volta/bin/claude'), // volta
69
- path.join(home, '.asdf/shims/claude'), // asdf, through its shims
70
- path.join(home, '.npm-global/bin/claude'), // the two npm prefixes people
71
- path.join(home, '.npm-packages/bin/claude'), // actually write in a guide
72
- path.join(home, '.yarn/bin/claude'), // yarn global
73
- path.join(home, 'Library/pnpm/claude'), // pnpm's own bin on macOS
74
- path.join(home, '.local/share/pnpm/claude'), // pnpm's own bin on Linux
75
- path.join(home, '.deno/bin/claude'), // deno
76
- path.join(home, 'bin/claude'), // a hand-rolled ~/bin on PATH
77
- '/opt/local/bin/claude', // MacPorts
78
- '/usr/bin/claude', // packaged by something system-wide
79
- ];
80
- }
81
-
82
- // One bin directory per installed Node, under a name no list can hold. Read
83
- // rather than guessed, and a directory that is not there just contributes
84
- // nothing. This is where a global npm install under nvm, fnm, n or asdf ends
85
- // up, and until now only the shell could reach it.
86
- export function versionedPaths(home = os.homedir(), readdir = defaultReaddir) {
87
- const under = (dir, tail) => readdir(path.join(home, dir)).map((v) => path.join(home, dir, v, tail));
88
- return [
89
- ...under('.nvm/versions/node', 'bin/claude'),
90
- ...under('.local/share/fnm/node-versions', 'installation/bin/claude'),
91
- ...under('Library/Application Support/fnm/node-versions', 'installation/bin/claude'),
92
- ...under('n/versions/node', 'bin/claude'),
93
- ...under('.asdf/installs/nodejs', 'bin/claude'),
94
- ];
95
- }
96
-
97
- function defaultReaddir(dir) {
98
- try { return fs.readdirSync(dir); } catch { return []; }
99
- }
100
-
101
- // THE COPY THE CLAUDE APP KEEPS FOR ITSELF, one folder per version, newest
102
- // first. Somebody who has only ever used Claude Code inside the Claude app has
103
- // this and nothing else (w-9f6975906c, measured 2026-10-05: 2.1.209 there, and
104
- // it shared the CLI's sign-in). It is searched LAST, after the shell, because it
105
- // updates when the app does and a real install is fresher.
106
- export function appCopyPaths(home = os.homedir(), readdir = defaultReaddir) {
107
- const dir = path.join(home, 'Library/Application Support/Claude/claude-code');
108
- return readdir(dir)
109
- .filter((v) => /^\d+(\.\d+)*$/.test(v))
110
- .sort(newestFirst)
111
- .map((v) => path.join(dir, v, 'claude.app/Contents/MacOS/claude'));
112
- }
113
-
114
- function newestFirst(a, b) {
115
- const x = a.split('.').map(Number), y = b.split('.').map(Number);
116
- for (let i = 0; i < Math.max(x.length, y.length); i++) {
117
- if ((x[i] ?? 0) !== (y[i] ?? 0)) return (y[i] ?? 0) - (x[i] ?? 0);
118
- }
119
- return 0;
120
- }
121
-
122
- // WHAT CLAUDE CODE LEAVES BEHIND. Any one of these means it has run on this
123
- // Mac. None of them is a binary and none of them is ever spawned; they exist
124
- // only to stop the app announcing an absence it cannot support.
125
- export function evidencePaths(home = os.homedir()) {
126
- return [
127
- path.join(home, '.claude.json'), // its own config file
128
- path.join(home, '.claude/settings.json'),
129
- path.join(home, '.claude/projects'), // one folder per project it has opened
130
- path.join(home, '.claude/statsig'),
131
- path.join(home, '.claude/.credentials.json'), // it has been logged in
132
- path.join(home, '.claude/history.jsonl'),
133
- path.join(home, '.local/share/claude'), // the installer's version store
134
- ];
135
- }
136
-
137
- // A path that is a symlink to nothing. `existsSync` follows the link and says
138
- // false, which is indistinguishable from nothing being there at all, and the
139
- // two mean opposite things: an update that has swapped the versioned file out
140
- // from under ~/.local/bin/claude leaves exactly this for a moment.
141
- function defaultDangles(p) {
142
- try { return !!fs.lstatSync(p) && !fs.existsSync(p); } catch { return false; }
143
- }
144
-
145
- export function installEvidence({ home = os.homedir(), exists = fs.existsSync, dangles = defaultDangles } = {}) {
146
- for (const p of evidencePaths(home)) if (exists(p)) return p;
147
- for (const p of candidatePaths(home)) if (dangles(p)) return p;
148
- return null;
149
- }
25
+ import {
26
+ ENGINES, findEngine, resolveEngine, forgetEngine, installEvidence as evidenceFor,
27
+ versionedBinPaths, askShell, shellProbes,
28
+ } from './engine-bin.mjs';
150
29
 
151
- export const INSTALL_URL = 'https://code.claude.com/docs/en/setup';
30
+ export { askShell, shellProbes };
152
31
 
153
- // The shells worth asking, in order, and never the same one twice. Hers first
154
- // because it is the one that has her PATH in it; the other two because a
155
- // $SHELL that will not start is a fact about $SHELL and not about Claude Code.
156
- export function shellProbes(env = process.env) {
157
- const preferred = env.SHELL || '/bin/zsh';
158
- const out = [[preferred, '-lic', 4000], [preferred, '-lc', 2500]];
159
- for (const other of ['/bin/zsh', '/bin/bash']) {
160
- if (other !== preferred) out.push([other, '-lic', 2500]);
161
- }
162
- return out;
163
- }
32
+ const CLAUDE = ENGINES.claude;
164
33
 
165
- // Ask the user's own login shell, and go on asking until one of them answers.
166
- // Anything that is not an absolute path to a file that exists is treated as no
167
- // path: `command -v` also prints shell functions and aliases, and a function
168
- // name handed to spawn is a crash with a worse message than "not found".
169
- //
170
- // `answered` is the strict half. It is true only when every probe that ran came
171
- // back with a real exit status, and at least one did. One shell that timed out
172
- // alongside one that said no is not a machine we know anything about.
173
- //
174
- // `bin` is a parameter rather than the word `claude` baked into the command
175
- // because Codex is a second engine (her ask, 2026-08-25) and it is found in the
176
- // same way: same shells, same "two different silences" rule, same refusal to
177
- // read a shell function name as a path. It is DEFAULTED, so every existing
178
- // caller and every existing test asks about Claude Code as it always did, and
179
- // that matters more here than it would anywhere else: this is the binary the
180
- // app will not open an inbox without.
181
- /**
182
- * WHAT MAY BE HANDED TO A LOGIN SHELL, AND WHY THIS IS CHECKED AT ALL.
183
- *
184
- * The probe below interpolates `bin` into a string a LOGIN, INTERACTIVE shell
185
- * executes, with her profile loaded, as her. Both callers today pass a literal
186
- * ('claude' here, 'codex' in main/codex-bin.mjs), so nothing is exploitable
187
- * now. But the entire direction of the second-engine work is a binary somebody
188
- * can configure, and a `codexBin` read out of zero.config.json reaching this
189
- * argument is one plausible slice away; at that point `command -v x; curl | sh`
190
- * is a string in a file that runs itself.
191
- *
192
- * This function looks up program NAMES, so a name is all it takes. An allowlist
193
- * rather than quoting, because quoting is a thing a later edit can undo without
194
- * noticing and a shape check is not.
195
- */
196
- const PROGRAM_NAME = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
34
+ export const INSTALL_URL = CLAUDE.installUrl;
197
35
 
198
- export function askShell({ env = process.env, exists = fs.existsSync, run = defaultRun, bin = 'claude' } = {}) {
199
- if (typeof bin !== 'string' || !PROGRAM_NAME.test(bin)) {
200
- throw new Error(`askShell looks up a program name, and ${JSON.stringify(bin)} is not one`);
201
- }
202
- let said = false;
203
- let inconclusive = false;
204
- for (const [shell, flags, timeout] of shellProbes(env)) {
205
- let out = '';
206
- try {
207
- out = run(shell, [flags, `command -v ${bin}`], timeout);
208
- } catch (e) {
209
- // TWO DIFFERENT SILENCES, AND ONLY ONE OF THEM IS AN ANSWER.A shell that
210
- // ran and exited non-zero has told us claude is not on PATH, and that is
211
- // an answer. A shell that could not be started, or that ran past the
212
- // timeout, has told us nothing at all, and reporting nothing as "not
213
- // installed" is what put a line about missing software on the screen of
214
- // people who have it.
215
- if (exitedSayingNo(e)) said = true; else inconclusive = true;
216
- continue;
217
- }
218
- const line = String(out).split('\n').map((s) => s.trim()).filter(Boolean).pop();
219
- if (line && line.startsWith('/') && exists(line)) return { path: line, answered: true };
220
- said = true;
221
- }
222
- return { path: null, answered: said && !inconclusive };
36
+ /** Every normal home for this binary, in order; the first that exists wins. */
37
+ export function candidatePaths(home = os.homedir()) {
38
+ return CLAUDE.candidates(home, () => []);
223
39
  }
224
40
 
225
- // The shell ran to completion and returned a non-zero exit code, which for
226
- // `command -v` is how "not on PATH" is spelt. A timeout (SIGTERM, ETIMEDOUT)
227
- // and a shell that does not exist (ENOENT) both arrive here too, and neither
228
- // of them is a fact about Claude Code.
229
- function exitedSayingNo(e) {
230
- if (!e || typeof e !== 'object') return false;
231
- if (e.signal || e.code === 'ETIMEDOUT' || e.code === 'ENOENT') return false;
232
- return typeof e.status === 'number';
41
+ /** One bin directory per installed Node, read rather than guessed. */
42
+ export function versionedPaths(home = os.homedir(), readdir = undefined) {
43
+ return versionedBinPaths('claude', home, readdir);
233
44
  }
234
45
 
235
- // askShell used to answer with a path or null, and both a real answer and a
236
- // failed probe were null. Anything still handing back the old shape is read as
237
- // an answer, because that is what it always meant.
238
- function shellSaid(result) {
239
- if (typeof result === 'string') return { path: result, answered: true };
240
- if (!result || typeof result !== 'object') return { path: null, answered: true };
241
- return { path: result.path ?? null, answered: result.answered !== false };
46
+ /** The copy the Claude desktop app keeps for itself, newest version first. */
47
+ export function appCopyPaths(home = os.homedir(), readdir = undefined) {
48
+ return CLAUDE.appCopies(home, readdir);
242
49
  }
243
50
 
244
- function defaultRun(shell, args, timeout = 4000) {
245
- return execFileSync(shell, args, {
246
- encoding: 'utf8',
247
- // stdin ignored on purpose: an interactive shell handed a live terminal can
248
- // sit waiting for input, and this runs on the boot path.
249
- stdio: ['ignore', 'pipe', 'ignore'],
250
- timeout,
251
- });
51
+ /** What Claude Code leaves behind. None of it is ever spawned. */
52
+ export function evidencePaths(home = os.homedir()) {
53
+ return CLAUDE.evidence(home, {});
252
54
  }
253
55
 
254
- // The whole search, as a pure function of its inputs so a test can put Claude
255
- // Code anywhere without touching the machine it runs on.
256
- //
257
- // Returns { path, found, certain, from, searched, evidence }. `found` is the
258
- // only claim about the world: it means a file is really there. `certain` says
259
- // whether we are allowed to tell somebody it is NOT there, so a screen can stay
260
- // quiet rather than announce an absence it has not established. They come apart
261
- // in exactly one case that is not a failure, and it is the case worth keeping
262
- // honest: she has set `claudeBin` to a path that does not exist, so the path
263
- // stays HERS (anything that spawns it should fail naming the path she chose,
264
- // not one we picked) while the screen says it was not found and offers the
265
- // install link, the same as any other machine.
266
- export function findClaudeBin({
267
- configured = null,
268
- home = os.homedir(),
269
- exists = fs.existsSync,
270
- shellLookup = askShell,
271
- readdir = defaultReaddir,
272
- dangles = defaultDangles,
273
- } = {}) {
274
- if (configured) {
275
- return {
276
- path: configured, found: exists(configured), certain: true, from: 'settings',
277
- searched: [], evidence: null,
278
- };
279
- }
280
-
281
- const searched = [...candidatePaths(home), ...versionedPaths(home, readdir)];
282
- for (const candidate of searched) {
283
- if (exists(candidate)) return { path: candidate, found: true, certain: true, from: 'disk', searched, evidence: null };
284
- }
285
-
286
- const fromShell = shellSaid(shellLookup());
287
- if (fromShell.path) return { path: fromShell.path, found: true, certain: true, from: 'shell', searched, evidence: null };
288
-
289
- for (const candidate of appCopyPaths(home, readdir)) {
290
- if (exists(candidate)) return { path: candidate, found: true, certain: true, from: 'app', searched, evidence: null };
291
- }
292
-
293
- // NOT FOUND, AND WHETHER THAT IS KNOWN. Every cheap path missed and every
294
- // shell we could reach missed, so the last question is whether this Mac shows
295
- // any sign of Claude Code anyway. If it does, we looked in the wrong places
296
- // rather than at an empty machine, and nothing may be said out loud.
297
- const evidence = installEvidence({ home, exists, dangles });
298
- return {
299
- path: null,
300
- found: false,
301
- certain: fromShell.answered && !evidence,
302
- from: null,
303
- searched,
304
- evidence,
305
- };
56
+ export function installEvidence({ home = os.homedir(), exists, dangles } = {}) {
57
+ return evidenceFor('claude', { home, env: {}, exists, dangles });
306
58
  }
307
59
 
308
- /* ------------------------------- the cache -------------------------------- */
309
- // Only the SHELL step is remembered, and only briefly. Nothing else is: the
310
- // path checks are stat calls, and caching them is how the settings screen ends
311
- // up telling someone to install Claude Code and then still saying "not found"
312
- // after they have. The sentence on that screen makes a promise and a cached
313
- // answer breaks it.
314
- //
315
- // The shell is the one part worth holding, because it is up to three
316
- // subprocesses and a screen she toggles a switch on reads settings again on
317
- // every write. Thirty seconds is long enough that a burst of writes pays for it
318
- // once, and short enough that it is never the reason someone is still stuck.
319
-
320
- const SHELL_TTL = 30_000;
321
- let shellAnswer = null;
322
- let shellAskedAt = 0;
323
-
324
- function rememberedShellLookup(now, lookup) {
325
- if (shellAskedAt && now - shellAskedAt < SHELL_TTL) return shellAnswer;
326
- shellAnswer = lookup();
327
- shellAskedAt = now;
328
- return shellAnswer;
60
+ /** The whole search. See main/engine-bin.mjs for what each answer means. */
61
+ export function findClaudeBin(where = {}) {
62
+ return findEngine('claude', where);
329
63
  }
330
64
 
331
- export function resolveClaudeBin(configured = null, { now = Date.now(), shellLookup = askShell, ...where } = {}) {
332
- return findClaudeBin({ ...where, configured, shellLookup: () => rememberedShellLookup(now, shellLookup) });
65
+ /** The same search with the shell step held for thirty seconds. */
66
+ export function resolveClaudeBin(configured = null, where = {}) {
67
+ return resolveEngine('claude', configured, where);
333
68
  }
334
69
 
335
70
  export function forgetClaudeBin() {
336
- shellAnswer = null;
337
- shellAskedAt = 0;
71
+ forgetEngine('claude');
338
72
  }
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+ import { Name } from '../shared/product-name.mjs';
2
3
 
3
4
  // THE SESSION STOPPED LISTENING BEFORE IT TOOK THIS MESSAGE. Not a refusal:
4
5
  // the turn closed, or the process went, in the seconds between her pressing
@@ -16,12 +17,29 @@ function turnClosed(message) {
16
17
  // before the session is closed anyway. Measured wakes arrive in about a second.
17
18
  const WAKE_GRACE_MS = 2 * 60 * 1000;
18
19
 
19
- export function claudeStreamArgs(args) {
20
+ // Said to the agent, never shown to the person: the trace keeps no user turns.
21
+ // Paths are cut to their last part: a cloud run's relay uploads any file its
22
+ // input names outside the task folder (main/cloud/run.mjs `filesNamedIn`).
23
+ export function stillRunningNote(tasks) {
24
+ const plain = (text) => String(text).replace(/(^|[\s"'`(=:])\/(?:[^\s"'`<>()[\]{},;\\]*\/)+/g, '$1');
25
+ const names = tasks.map((t) => `"${plain(t.description || t.task_id).slice(0, 120)}"`).join(', ');
26
+ return `This is ${Name}, not the person. Your turn ended with ${tasks.length === 1 ? 'this' : 'these'} still running in the background: ${names}. `
27
+ + 'Your run stays open, and your answer is not delivered, until nothing you started is still running. '
28
+ + 'Stop any you no longer need. If you are waiting on one, say what for in one line and end your turn.';
29
+ }
30
+
31
+ // THE PROMPT LEAVES THE LINE WHEN REPLIES ARRIVE ON STDIN. Both the print flag
32
+ // and the input-format flags are read off the engine's `run` definition, so an
33
+ // engine of this family that spells either of them differently is a definition
34
+ // and not a change here. An engine with no `input` cannot take corrections on
35
+ // stdin, and gets its line back as it was.
36
+ export function streamInputArgs(run, args) {
37
+ if (!run?.input) return [...args];
20
38
  const copy = [...args];
21
- const index = copy.indexOf('-p');
39
+ const index = copy.indexOf(run.print);
22
40
  if (index < 0) throw Error('Missing print prompt');
23
41
  copy.splice(index + 1, 1);
24
- return [...copy, '--input-format', 'stream-json', '--replay-user-messages'];
42
+ return [...copy, ...run.input];
25
43
  }
26
44
 
27
45
  // Replay-user-messages acknowledges the exact UUID when Claude consumes it.
@@ -39,13 +57,35 @@ export function attachClaudeInput(child) {
39
57
  // end of the run, and closing there killed the suite it was waiting on
40
58
  // (tests/an-agent-waiting-on-its-own-tests-does-not-come-back-to-you).
41
59
  let waiting = [], wakeTimer = null;
60
+ // Jobs the agent has been told are still out, and the messages that told it.
61
+ // A forgotten job holds a finished answer until Claude Code's own limit
62
+ // stops it, thirty minutes and more
63
+ // (tests/a-command-an-agent-left-running-does-not-hold-its-answer-back).
64
+ const named = new Set(), ours = new Set();
42
65
  // Input only ever closes at a result with nothing out, so a closed input is
43
66
  // the one proof the run reached its end. A session that exits with input
44
67
  // still open was stopped part-way, whatever its last turn said
45
68
  // (tests/a-worker-whose-helpers-are-still-working-does-not-come-back-to-you).
46
69
  let ended = false;
70
+ // A TURN IS RUNNING between its `system/init` and its result. A message
71
+ // written while none is starts the next one, and a turn that starts while
72
+ // one waits takes it with it, so either way it is being answered, though
73
+ // Claude replays it only when that turn first speaks. Send now must not cut
74
+ // that turn: it was the answer (w-0202576e73).
75
+ let inTurn = false;
47
76
  const closeInput = () => { closed = true; ended = true; clearTimeout(wakeTimer); child.stdin.end(); };
48
77
  const mayClose = () => !held && atResult && !pending.size && !waiting.length;
78
+ // Not into a session she is driving, and not when her own message is about
79
+ // to start the next turn. Kept out of `pending`, so it never holds input open.
80
+ const nameWhatIsStillOut = () => {
81
+ if (closed || held || pending.size || !waiting.some((t) => t?.task_id && !named.has(t.task_id))) return;
82
+ for (const t of waiting) if (t?.task_id) named.add(t.task_id);
83
+ const uuid = randomUUID();
84
+ ours.add(uuid);
85
+ try {
86
+ child.stdin.write(JSON.stringify({ type: 'user', uuid, message: { role: 'user', content: stillRunningNote(waiting) }, parent_tool_use_id: null, session_id: '' }) + '\n');
87
+ } catch { /* the input is gone; `fail` hears it */ }
88
+ };
49
89
  const fail = reason => {
50
90
  closed = true;
51
91
  for (const request of pending.values()) {
@@ -75,12 +115,20 @@ export function attachClaudeInput(child) {
75
115
  }
76
116
  }
77
117
  if (event.type === 'system' && event.subtype === 'bridge_state') child.emit('bridge-state', event);
78
- if (event.type === 'assistant') atResult = false;
118
+ // A cloud run whose input the sandbox closed at the end of its turn
119
+ // while this Mac slept (main/cloud/run.mjs): the end of the run, as if
120
+ // closed here. A reply still waiting is kept for the next run.
121
+ if (event.type === 'system' && event.subtype === 'agentbox_input_closed_in_cloud' && !closed) closeInput();
122
+ if (event.type === 'system' && event.subtype === 'init') {
123
+ inTurn = true;
124
+ for (const request of pending.values()) request.taken = true;
125
+ }
126
+ if (event.type === 'assistant') { atResult = false; inTurn = true; }
79
127
  if (event.type === 'user' && !event.parent_tool_use_id && typeof event.uuid === 'string' && !seenUsers.has(event.uuid)) {
80
128
  seenUsers.add(event.uuid);
81
129
  const content = event.message?.content;
82
130
  const text = typeof content === 'string' ? content : Array.isArray(content) ? content.filter(b => b.type === 'text').map(b => b.text).join('\n') : '';
83
- if (held && !pending.has(event.uuid) && text && !event.isMeta) child.emit('remote-user', event);
131
+ if (held && !pending.has(event.uuid) && !ours.has(event.uuid) && text && !event.isMeta) child.emit('remote-user', event);
84
132
  if (text) { atResult = false; child.emit('input-turn', event); }
85
133
  }
86
134
  if (event.type === 'user' && pending.has(event.uuid)) {
@@ -103,7 +151,9 @@ export function attachClaudeInput(child) {
103
151
  }
104
152
  if (event.type === 'result') {
105
153
  atResult = true;
154
+ inTurn = false;
106
155
  if (mayClose()) closeInput();
156
+ else if (waiting.length) nameWhatIsStillOut();
107
157
  }
108
158
  }
109
159
  });
@@ -130,7 +180,7 @@ export function attachClaudeInput(child) {
130
180
  // ONLY WHILE A MESSAGE OF HERS IS WAITING. The cut turn ends in a result,
131
181
  // and with nothing pending that result closes the input and ends the run.
132
182
  child.interrupt = () => {
133
- if (closed || !pending.size) return Promise.resolve({ interrupted: false });
183
+ if (closed || ![...pending.values()].some(request => !request.taken)) return Promise.resolve({ interrupted: false });
134
184
  child.lastInterruptAt = Date.now();
135
185
  return child.control({ subtype: 'interrupt' }).then(() => ({ interrupted: true }));
136
186
  };
@@ -147,7 +197,7 @@ export function attachClaudeInput(child) {
147
197
  child.steer = text => new Promise((resolve, reject) => {
148
198
  if (closed || !child.stdin.writable) return reject(turnClosed('The turn has ended. Send the message again to continue it.'));
149
199
  const uuid = randomUUID();
150
- pending.set(uuid, { resolve, reject });
200
+ pending.set(uuid, { resolve, reject, taken: !inTurn });
151
201
  try {
152
202
  child.stdin.write(JSON.stringify({ type: 'user', uuid, message: { role: 'user', content: text }, parent_tool_use_id: null, session_id: '' }) + '\n');
153
203
  } catch (error) {