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
@@ -0,0 +1,179 @@
1
+ // SIGNING IN TO AGENTBOX CLOUD FROM THIS MAC.
2
+ //
3
+ // A Google sign-in, through the database's own page, is traded once with the
4
+ // cloud server (cloud/gateway/) for a cloud key, kept beside cloud.json; the
5
+ // Mac's cloud.json then names the server, so its runs go there
6
+ // (main/cloud/clients.mjs). The database session itself is not kept. A Mac
7
+ // set up on its owner's own AWS account is never moved onto the server.
8
+ import fs from 'node:fs';
9
+ import os from 'node:os';
10
+ import path from 'node:path';
11
+ import { cloudConfigPath, readCloudConfig, readCloudKey, writeCloudKey, cloudKeyPath, cloudAccountConfig, forgetCloudKey } from './config.mjs';
12
+ import { withDeadline, plainAuthError } from '../team/session.mjs';
13
+ import { signInWithGoogle, cancelGoogleSignIn } from '../team/sign-in.mjs';
14
+ import { Name } from '../../shared/product-name.mjs';
15
+
16
+ const CALL_MS = 30_000;
17
+ const RETURN_URL = 'http://127.0.0.1:54783/auth/callback';
18
+
19
+ export { cloudAccountConfig };
20
+
21
+ async function ask(gateway, route, { method = 'GET', key, body } = {}) {
22
+ const init = { method, headers: { ...(key ? { authorization: `Bearer ${key}` } : {}), ...(body ? { 'content-type': 'application/json' } : {}) }, body: body ? JSON.stringify(body) : undefined };
23
+ const res = await fetch(`${gateway.replace(/\/+$/, '')}${route}`, { ...init, signal: AbortSignal.timeout(CALL_MS) });
24
+ return { status: res.status, body: await res.json().catch(() => ({})) };
25
+ }
26
+
27
+ /** The version of the terms a person accepts now. */
28
+ export async function currentCloudTerms(config) {
29
+ return (await ask(config.gateway, '/v1/terms')).body.terms ?? null;
30
+ }
31
+
32
+ /**
33
+ * The ways in that the app's sign-in page offers (team/SignInPage.tsx):
34
+ * Google, an email and a password, or a new account. Each gives the access
35
+ * token of a fresh sign-in, held in memory only: the cloud key is what this
36
+ * Mac keeps, so nothing asks the keychain.
37
+ */
38
+ export function cloudAccountSession({ config, openExternal, log = () => {}, createClient = null }) {
39
+ let lastUrl = null;
40
+ const client = async () => {
41
+ const make = createClient || (await import('@supabase/supabase-js')).createClient;
42
+ const mem = new Map();
43
+ return make(config.supabaseUrl, config.anonKey, {
44
+ auth: { flowType: 'pkce', persistSession: true, autoRefreshToken: false, storage: { getItem: (k) => mem.get(k) ?? null, setItem: (k, v) => mem.set(k, v), removeItem: (k) => mem.delete(k) } },
45
+ global: { fetch: withDeadline() },
46
+ });
47
+ };
48
+ const tokenOf = (data) => {
49
+ if (!data?.session?.access_token) throw new Error('Signing in came back without a session. Try again.');
50
+ return data.session.access_token;
51
+ };
52
+ return {
53
+ async google() {
54
+ const c = await client();
55
+ await signInWithGoogle({ client: c, openExternal, returnUrl: config.signInReturn || RETURN_URL, log, onUrl: (u) => { lastUrl = u; } });
56
+ return tokenOf((await c.auth.getSession()).data);
57
+ },
58
+ async email(email, password) {
59
+ const { data, error } = await (await client()).auth.signInWithPassword({ email: String(email ?? '').trim(), password: String(password ?? '') });
60
+ if (error) throw new Error(plainAuthError(error.message));
61
+ return tokenOf(data);
62
+ },
63
+ // With confirmation on, a new account has no session until its email's
64
+ // link is followed: null, and the page says to check the inbox.
65
+ async signUp({ name, email, password }) {
66
+ const { data, error } = await (await client()).auth.signUp({
67
+ email: String(email ?? '').trim(), password: String(password ?? ''),
68
+ options: { data: { full_name: String(name ?? '').trim() || undefined } },
69
+ });
70
+ if (error) throw new Error(plainAuthError(error.message));
71
+ return data?.session?.access_token ?? null;
72
+ },
73
+ cancel: () => cancelGoogleSignIn(),
74
+ reopen: () => (lastUrl ? openExternal(lastUrl) : null),
75
+ };
76
+ }
77
+
78
+ /**
79
+ * One press on the sign-in page, to a cloud key. It agrees to the current
80
+ * terms only when the page showed them (`acceptTerms`, and every new account);
81
+ * otherwise a person who never agreed gets `needsTerms`, and `agree` finishes
82
+ * that same sign-in once they do.
83
+ */
84
+ export async function cloudSignIn({ config, session, method, email, password, name, acceptTerms = false, env = process.env, home = os.homedir() }) {
85
+ let token;
86
+ let agreeing = acceptTerms;
87
+ try {
88
+ if (method === 'google') token = await session.google();
89
+ else if (method === 'email') token = await session.email(email, password);
90
+ else if (method === 'sign-up') {
91
+ agreeing = true;
92
+ token = await session.signUp({ name, email, password });
93
+ if (!token) return { ok: true, confirm: true };
94
+ } else if (method === 'agree') {
95
+ token = session.held;
96
+ agreeing = true;
97
+ if (!token) return { ok: false, error: 'Sign in again to finish.' };
98
+ } else return { ok: false, error: 'No such way to sign in.' };
99
+ } catch (err) {
100
+ return { ok: false, error: err?.message || String(err), ...(err?.cancelled ? { cancelled: true } : {}) };
101
+ }
102
+ session.held = token;
103
+ const acceptedTerms = agreeing ? await currentCloudTerms(config) : undefined;
104
+ const out = await signInToCloud({ config, acceptedTerms, googleSignIn: async () => token, env, home });
105
+ if (out.ok) { session.held = null; return out; }
106
+ if (out.error === 'terms' && !agreeing) return { ok: false, needsTerms: true, terms: out.terms ?? null };
107
+ return { ok: false, error: out.message || out.error };
108
+ }
109
+
110
+ // `googleSignIn` gives the access token of whichever sign-in the page made.
111
+ export async function signInToCloud({ config, acceptedTerms, name = os.hostname(), env = process.env, home = os.homedir(), googleSignIn }) {
112
+ const now = readCloudConfig(env, home);
113
+ if (now?.bucket) return { ok: false, error: 'own-aws', message: 'This Mac runs the cloud in its own AWS account.' };
114
+ let token;
115
+ try { token = await googleSignIn(); } catch (err) { return { ok: false, error: 'google', message: err?.message || String(err) }; }
116
+ const made = await ask(config.gateway, '/v1/devices', { method: 'POST', key: token, body: { acceptedTerms, name: String(name).slice(0, 80) } });
117
+ if (made.status !== 200 || !made.body.key) return { ok: false, error: made.body.error || 'refused', message: made.body.message || `the cloud said ${made.status}`, ...(made.body.terms ? { terms: made.body.terms } : {}) };
118
+ writeCloudKey(made.body.key, env, home);
119
+ const file = cloudConfigPath(env, home);
120
+ fs.mkdirSync(path.dirname(file), { recursive: true });
121
+ fs.writeFileSync(file, JSON.stringify({ gateway: config.gateway }, null, 2) + '\n', { mode: 0o600 });
122
+ const me = await ask(config.gateway, '/v1/me', { key: made.body.key });
123
+ return { ok: true, ...(me.status === 200 ? me.body : {}) };
124
+ }
125
+
126
+ // The server's cloud and this Mac's key for it, or null when signed out.
127
+ function signedIn(env, home) {
128
+ const gateway = readCloudConfig(env, home)?.gateway;
129
+ const key = gateway ? readCloudKey(env, home) : null;
130
+ return key ? { gateway, key } : null;
131
+ }
132
+
133
+ /** The plan, its agents and hours, and what is used, as the server counts them. */
134
+ export async function cloudPlan({ env = process.env, home = os.homedir() } = {}) {
135
+ const at = signedIn(env, home);
136
+ if (!at) return { signedIn: false };
137
+ const me = await ask(at.gateway, '/v1/me', { key: at.key });
138
+ if (me.status === 401) forgetCloudKey(at.key, env, home);
139
+ return me.status === 200 ? { signedIn: true, ...me.body } : { signedIn: me.status !== 401, error: me.body.error || 'refused', message: me.body.message };
140
+ }
141
+
142
+ /** End every other Mac's cloud key, for a Mac that was lost; this one stays signed in. */
143
+ export async function signOutOtherMacs({ env = process.env, home = os.homedir() } = {}) {
144
+ const at = signedIn(env, home);
145
+ if (!at) return { ok: false, error: 'signed-out', message: `Sign in to ${Name} Cloud first.` };
146
+ const out = await ask(at.gateway, '/v1/devices', { method: 'DELETE', key: at.key, body: { others: true } });
147
+ if (out.status === 401) forgetCloudKey(at.key, env, home);
148
+ return out.status === 200 ? { ok: true } : { ok: false, error: out.body.error || 'refused', message: out.body.message || `the cloud said ${out.status}` };
149
+ }
150
+
151
+ // Only Stripe's own two pages are opened: whatever else a server answers stays shut.
152
+ const STRIPE_PAGE = /^https:\/\/(checkout|billing)\.stripe\.com\//;
153
+
154
+ async function openStripe(route, body, openExternal, env, home) {
155
+ const at = signedIn(env, home);
156
+ if (!at) return { ok: false, error: 'signed-out', message: `Sign in to ${Name} Cloud first.` };
157
+ const out = await ask(at.gateway, route, { method: 'POST', key: at.key, body });
158
+ if (out.status !== 200) return { ok: false, error: out.body.error || 'refused', message: out.body.message || `the cloud said ${out.status}` };
159
+ if (typeof out.body.url !== 'string' || !STRIPE_PAGE.test(out.body.url)) return { ok: false, error: 'not-stripe', message: 'The cloud answered with a page that is not Stripe\'s.' };
160
+ await openExternal(out.body.url);
161
+ return { ok: true, ...(out.body.where ? { where: out.body.where } : {}) };
162
+ }
163
+
164
+ /** Stripe's checkout for a plan, in the browser; the billing page for someone already paying. */
165
+ export const payForCloud = ({ plan, openExternal, env = process.env, home = os.homedir() }) => openStripe('/v1/checkout', { plan }, openExternal, env, home);
166
+
167
+ /** Stripe's billing page: change plan, cancel, the card, invoices. */
168
+ export const openCloudBilling = ({ openExternal, env = process.env, home = os.homedir() }) => openStripe('/v1/billing', {}, openExternal, env, home);
169
+
170
+ /** Let this Mac's cloud key go, there and here. An own-AWS setup is left alone. */
171
+ export async function signOutOfCloud({ env = process.env, home = os.homedir() } = {}) {
172
+ const now = readCloudConfig(env, home);
173
+ if (!now?.gateway) return { ok: true };
174
+ const key = readCloudKey(env, home);
175
+ if (key) await ask(now.gateway, '/v1/devices', { method: 'DELETE', key }).catch(() => {});
176
+ fs.rmSync(cloudKeyPath(env, home), { force: true });
177
+ fs.rmSync(cloudConfigPath(env, home), { force: true });
178
+ return { ok: true };
179
+ }
@@ -0,0 +1,127 @@
1
+ // THE TWO THINGS A CLOUD RUN NEEDS FROM AWS, AND NOTHING ELSE.
2
+ //
3
+ // main/cloud/run.mjs is written against two small interfaces so a test can
4
+ // run it against the real sandbox server on this machine (see
5
+ // tests/a-cloud-run-brings-the-agents-work-home.test.mjs). These are the real
6
+ // ones: the AgentCore session (invoke, stop) and the S3 bucket (put, get, and
7
+ // the presigned links the sandbox uses, so it holds no AWS rights of its own).
8
+ //
9
+ // They sign as the `agentbox-cloud` profile that scripts/cloud-setup.mjs made:
10
+ // an IAM user that may only start and stop sessions on one runtime and read
11
+ // and write one bucket's `runs/`. Never the 12-hour admin sign-in.
12
+ import { BedrockAgentCoreClient, InvokeAgentRuntimeCommand, StopRuntimeSessionCommand } from '@aws-sdk/client-bedrock-agentcore';
13
+ import {
14
+ S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, HeadObjectCommand,
15
+ CreateMultipartUploadCommand, UploadPartCopyCommand, UploadPartCommand, CompleteMultipartUploadCommand,
16
+ } from '@aws-sdk/client-s3';
17
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
18
+ import fs from 'node:fs';
19
+ import { pipeline } from 'node:stream/promises';
20
+
21
+ // A link the sandbox uses must outlive the longest run (8 hours) plus the
22
+ // upload at its end, with room to spare.
23
+ const LINK_SECONDS = 12 * 60 * 60;
24
+
25
+ // A request that hangs is retried by the caller; one that never returns would
26
+ // freeze the run. The SDK only warns at its request timeout unless told to
27
+ // throw. Minutes, not seconds: sending a run's code up and a cold start both
28
+ // take longer than a minute, and a minute's limit killed every such run.
29
+ const TIMEOUT_MS = 5 * 60_000;
30
+ const COPY_PIECE_BYTES = 32 * 1024 * 1024;
31
+
32
+ // The byte ranges `extend` copies a `size`-byte base in: each at least `piece`
33
+ // long, so never under S3's 5 MiB, and together exactly the base.
34
+ export function copyRanges(size, piece = COPY_PIECE_BYTES) {
35
+ const pieces = Math.max(1, Math.floor(size / piece));
36
+ const each = Math.ceil(size / pieces);
37
+ return Array.from({ length: pieces }, (_, i) => `bytes=${i * each}-${Math.min(size, (i + 1) * each) - 1}`);
38
+ }
39
+
40
+ const http = (timeoutMs = TIMEOUT_MS) => ({ requestTimeout: timeoutMs, connectionTimeout: Math.min(10_000, timeoutMs), throwOnRequestTimeout: true });
41
+ // `endpoint` and `credentials` are for tests, which stand in for AWS.
42
+ const where = ({ endpoint, credentials }) => ({ ...(endpoint ? { endpoint } : {}), ...(credentials ? { credentials } : {}) });
43
+
44
+ export function awsTransport({ region, runtimeArn, profile, endpoint, credentials, timeoutMs }) {
45
+ const client = new BedrockAgentCoreClient({ region, profile, requestHandler: http(timeoutMs), ...where({ endpoint, credentials }) });
46
+ return {
47
+ async invoke(sessionId, payload) {
48
+ const out = await client.send(new InvokeAgentRuntimeCommand({
49
+ agentRuntimeArn: runtimeArn,
50
+ runtimeSessionId: sessionId,
51
+ payload: Buffer.from(JSON.stringify(payload)),
52
+ contentType: 'application/json',
53
+ accept: 'application/json',
54
+ }));
55
+ const text = await out.response.transformToString();
56
+ return JSON.parse(text);
57
+ },
58
+ async stop(sessionId) {
59
+ await client.send(new StopRuntimeSessionCommand({ agentRuntimeArn: runtimeArn, runtimeSessionId: sessionId }));
60
+ },
61
+ };
62
+ }
63
+
64
+ export function awsStorage({ region, bucket, profile, endpoint, credentials, timeoutMs }) {
65
+ // WHEN_REQUIRED: by default the SDK now signs a checksum of the body into
66
+ // every upload link, which a link made before the body exists cannot match,
67
+ // so every upload from the sandbox would be refused.
68
+ const s3 = new S3Client({ region, profile, requestHandler: http(timeoutMs), requestChecksumCalculation: 'WHEN_REQUIRED', responseChecksumValidation: 'WHEN_REQUIRED', ...where({ endpoint, credentials }), ...(endpoint ? { forcePathStyle: true } : {}) });
69
+ return {
70
+ async put(key, body) {
71
+ await s3.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: body }));
72
+ },
73
+ async get(key) {
74
+ try {
75
+ const o = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
76
+ return Buffer.from(await o.Body.transformToByteArray());
77
+ } catch (err) {
78
+ if (err?.name === 'NoSuchKey' || err?.$metadata?.httpStatusCode === 404) return null;
79
+ throw err;
80
+ }
81
+ },
82
+ // Straight to a file, a piece at a time; false when it is not there.
83
+ async getToFile(key, file) {
84
+ let o;
85
+ try { o = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key })); } catch (err) {
86
+ if (err?.name === 'NoSuchKey' || err?.$metadata?.httpStatusCode === 404) return false;
87
+ throw err;
88
+ }
89
+ await pipeline(o.Body, fs.createWriteStream(file));
90
+ return true;
91
+ },
92
+ async putFile(key, file) {
93
+ await s3.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: fs.createReadStream(file), ContentLength: fs.statSync(file).size }));
94
+ },
95
+ // `key` made in the bucket as `base` (which is `start` bytes long)
96
+ // followed by `file` from byte `start`, so only that tail leaves this Mac.
97
+ // The base is copied in pieces at once: as one piece, 400 MiB took 11 s.
98
+ // An upload that fails part-way is cleared by the bucket's lifecycle rule
99
+ // (scripts/cloud-setup.mjs), as this login may not abort one.
100
+ async extend(key, base, file, start) {
101
+ const { UploadId } = await s3.send(new CreateMultipartUploadCommand({ Bucket: bucket, Key: key }));
102
+ const copied = await Promise.all(copyRanges(start).map((range, i) => s3.send(new UploadPartCopyCommand({
103
+ Bucket: bucket, Key: key, UploadId, PartNumber: i + 1, CopySource: `${bucket}/${base}`, CopySourceRange: range,
104
+ })).then((r) => ({ PartNumber: i + 1, ETag: r.CopyPartResult?.ETag }))));
105
+ const last = copied.length + 1;
106
+ const length = fs.statSync(file).size - start;
107
+ const tail = await s3.send(new UploadPartCommand({ Bucket: bucket, Key: key, UploadId, PartNumber: last, Body: fs.createReadStream(file, { start }), ContentLength: length }));
108
+ await s3.send(new CompleteMultipartUploadCommand({
109
+ Bucket: bucket, Key: key, UploadId,
110
+ MultipartUpload: { Parts: [...copied, { PartNumber: last, ETag: tail.ETag }] },
111
+ }));
112
+ },
113
+ // Whether an object is there, without reading it.
114
+ async exists(key) {
115
+ try { await s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key })); return true; } catch (err) {
116
+ if (err?.name === 'NotFound' || err?.$metadata?.httpStatusCode === 404) return false;
117
+ throw err;
118
+ }
119
+ },
120
+ async del(key) {
121
+ await s3.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
122
+ },
123
+ getUrl: (key, seconds = LINK_SECONDS) => getSignedUrl(s3, new GetObjectCommand({ Bucket: bucket, Key: key }), { expiresIn: seconds }),
124
+ putUrl: (key, seconds = LINK_SECONDS) => getSignedUrl(s3, new PutObjectCommand({ Bucket: bucket, Key: key }), { expiresIn: seconds }),
125
+ headUrl: (key, seconds = LINK_SECONDS) => getSignedUrl(s3, new HeadObjectCommand({ Bucket: bucket, Key: key }), { expiresIn: seconds }),
126
+ };
127
+ }
@@ -0,0 +1,58 @@
1
+ // THE BROWSER'S WAY BACK FROM OPENAI, TAKEN ON THIS COMPUTER FOR A CODEX
2
+ // SIGN-IN THAT RUNS IN THE CLOUD.
3
+ //
4
+ // Codex's browser sign-in sends the browser back to the machine running
5
+ // `codex login`, at 127.0.0.1:1455. The browser is here and the command is in
6
+ // the cloud, so this listens on that address while she signs in and hands the
7
+ // query on: what OpenAI documents doing over SSH for a remote machine. The
8
+ // query is a one-time code only the cloud's Codex can spend (it alone holds
9
+ // the PKCE secret); no login passes through here.
10
+ import http from 'node:http';
11
+ import { NAME } from '../../shared/product-name.mjs';
12
+
13
+ // Where an OpenAI sign-in link sends the browser back, when that is this
14
+ // computer's own address; null otherwise.
15
+ export function wayBack(link) {
16
+ try {
17
+ const back = new URL(new URL(link).searchParams.get('redirect_uri') || '');
18
+ const port = Number(back.port);
19
+ if (back.protocol !== 'http:' || !['127.0.0.1', 'localhost'].includes(back.hostname) || !(port > 0)) return null;
20
+ return { port, path: back.pathname };
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ const page = (ok) => {
27
+ const line = ok ? 'Signed in to Codex. You can close this tab.' : `That didn’t finish. Go back to ${NAME} and try again.`;
28
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${ok ? 'Signed in to Codex' : 'Codex sign-in'}</title>`
29
+ + '<style>:root{color-scheme:light dark}body{margin:0;min-height:100vh;display:grid;place-items:center;background:Canvas;color:CanvasText;font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif}p{margin:0 24px;text-align:center}</style>'
30
+ + `</head><body><p>${line}</p></body></html>`;
31
+ };
32
+
33
+ // Listens at `port` until closed; anything but `path` is not found, including
34
+ // the /cancel a local `codex login` sends whatever holds its port.
35
+ // `onReturn(query)` says whether the cloud took it, and the browser is told.
36
+ export function listenForWayBack({ port, path, onReturn }) {
37
+ // Every answer closes its connection, so closing the server lets the port
38
+ // go at once and still lets a page being answered reach the browser.
39
+ const server = http.createServer(async (req, res) => {
40
+ const u = new URL(req.url || '/', 'http://127.0.0.1');
41
+ if (req.method !== 'GET' || u.pathname !== path) {
42
+ res.writeHead(404, { 'content-type': 'text/plain', connection: 'close' });
43
+ res.end('Not Found');
44
+ return;
45
+ }
46
+ let ok = false;
47
+ try { ok = !!(await onReturn(u.search.slice(1))); } catch { /* told as not finished */ }
48
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', connection: 'close' });
49
+ res.end(page(ok));
50
+ });
51
+ return new Promise((resolve, reject) => {
52
+ server.once('error', reject);
53
+ server.listen(port, '127.0.0.1', () => {
54
+ server.off('error', reject);
55
+ resolve({ close() { server.close(); server.closeIdleConnections?.(); } });
56
+ });
57
+ });
58
+ }
@@ -0,0 +1,15 @@
1
+ // The session and bucket clients a cloud run uses: the owner's own AWS
2
+ // account, or the managed cloud's server. Loaded only when a run needs them,
3
+ // so a Mac that never uses the cloud never loads AWS.
4
+ import { readCloudKey, forgetCloudKey } from './config.mjs';
5
+
6
+ export async function cloudClients(config, { env = process.env, home } = {}) {
7
+ if (typeof config?.gateway === 'string') {
8
+ const key = readCloudKey(env, home);
9
+ if (!key) throw new Error('this Mac has no cloud key yet; sign in for the cloud');
10
+ const { managedClients } = await import('./managed.mjs');
11
+ return managedClients({ url: config.gateway, key, onSignedOut: () => forgetCloudKey(key, env, home) });
12
+ }
13
+ const { awsTransport, awsStorage } = await import('./aws.mjs');
14
+ return { transport: awsTransport(config), storage: awsStorage(config) };
15
+ }
@@ -0,0 +1,53 @@
1
+ // A CODEX WORKER WHOSE APP-SERVER IS IN THE CLOUD (docs/cloud/PLAN.md).
2
+ //
3
+ // On the Mac a Codex worker ends when its turn ends, and the folder already
4
+ // holds its work. A cloud worker's turn ends in the sandbox, and its work
5
+ // reaches the folder only after the proxy (main/cloud/run-cli.mjs) has
6
+ // stopped the remote app-server, collected and imported. Everything that
7
+ // reads the folder on exit (the change card, the ship queue, the next run)
8
+ // must wait for that, so the worker's `exit` is held here until the proxy
9
+ // process itself has exited (Codex's review, 2026-10-13, finding 1):
10
+ //
11
+ // - the turn's own ending is kept, and the moment it happens the worker's
12
+ // input closes, its cards close (`onTurnEnd`), and the connection is
13
+ // closed, which stops the proxy and starts the homecoming;
14
+ // - a reply that arrives in that gap is refused as TURN_CLOSED, which is
15
+ // the contract main/live-replies.mjs already has for "too late for this
16
+ // turn": it is saved as an ordinary reply and carried by the next run;
17
+ // - when the proxy exits, the held ending goes out, unless the proxy said
18
+ // the work could not be brought home (75), or the account's cloud sign-in
19
+ // was missing, refused or ran out (77), each of which goes out as a
20
+ // transport fault: not a delivered reply, not an attempt spent.
21
+ import { TURN_CLOSED } from '../claude-input.mjs';
22
+
23
+ export const IMPORT_FAILED = 75;
24
+ export const LOGIN_TROUBLE = 77;
25
+
26
+ export function holdUntilHome({ worker, client, procExit, onTurnEnd = () => {} }) {
27
+ const realEmit = worker.emit.bind(worker);
28
+ let held = null;
29
+ worker.emit = (event, ...args) => {
30
+ if (event !== 'exit') return realEmit(event, ...args);
31
+ if (held) return true;
32
+ held = args; // [code, signal, how]
33
+ worker.steer = () => {
34
+ const err = new Error('The turn has ended; this reply will start the next one.');
35
+ err.code = TURN_CLOSED;
36
+ return Promise.reject(err);
37
+ };
38
+ try { onTurnEnd(); } catch { /* cards already gone */ }
39
+ try { client.close('the cloud run finished its turn'); } catch { /* already closed */ }
40
+ // A proxy that exits with a code finished: the code is the remote's, or
41
+ // 75 for work it could not bring home. One that was killed, or exited with
42
+ // no code at all, never said, so the work is not known to be home.
43
+ const notHome = (extra = {}) => realEmit('exit', IMPORT_FAILED, null, { ...(held[2] || {}), transportFault: true, ...extra });
44
+ Promise.resolve(procExit).then((out) => {
45
+ const code = out?.code;
46
+ if (code === LOGIN_TROUBLE) notHome({ cloudLoginTrouble: true });
47
+ else if (code === IMPORT_FAILED || !Number.isInteger(code) || out?.signal) notHome();
48
+ else realEmit('exit', ...held);
49
+ }, notHome);
50
+ return true;
51
+ };
52
+ return worker;
53
+ }
@@ -0,0 +1,107 @@
1
+ // WHERE A MAC'S CLOUD RUNS ARE SET UP, AND WHETHER THEY CAN RUN AT ALL.
2
+ //
3
+ // scripts/cloud-setup.mjs writes ~/.agentbox/cloud.json (the bucket, the
4
+ // runtime, the profile it signs as); it is never in git, because it names an
5
+ // AWS account. Each account's sign-in for the cloud is made and kept in the
6
+ // cloud, never on this Mac (main/cloud/logins.mjs).
7
+ import os from 'node:os';
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { Name } from '../../shared/product-name.mjs';
11
+
12
+ // `home` is the supervisor's own idea of the home folder (config.home), so a
13
+ // supervisor built for a test, with a home that is not the developer's, never
14
+ // reads the developer's real setup.
15
+ export function cloudConfigPath(env = process.env, home = os.homedir()) {
16
+ return env.AGENTBOX_CLOUD_CONFIG || path.join(home || os.homedir(), '.agentbox', 'cloud.json');
17
+ }
18
+
19
+ // Either the owner's own AWS account (bucket, runtime, region), or the
20
+ // managed cloud's server (`gateway`, its address), whose key sits beside it.
21
+ export function readCloudConfig(env = process.env, home = os.homedir()) {
22
+ try {
23
+ const c = JSON.parse(fs.readFileSync(cloudConfigPath(env, home), 'utf8'));
24
+ return (c?.bucket && c?.runtimeArn && c?.region) || typeof c?.gateway === 'string' ? c : null;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Where Agentbox Cloud is: the server's address and the database's public
32
+ * address and key. A checkout keeps it in cloud/, a Mac may keep its own
33
+ * copy, and a release build carries it baked into its package.json
34
+ * (scripts/release.mjs). All of them are out of git.
35
+ */
36
+ export function cloudAccountConfig(appDir, { home = os.homedir() } = {}) {
37
+ const whole = (c) => typeof c?.gateway === 'string' && typeof c.supabaseUrl === 'string' && typeof c.anonKey === 'string';
38
+ for (const file of [path.join(appDir, 'cloud', 'cloud.public.json'), path.join(home, '.agentbox', 'cloud.public.json')]) {
39
+ try {
40
+ const c = JSON.parse(fs.readFileSync(file, 'utf8'));
41
+ if (whole(c)) return c;
42
+ } catch { /* try the next */ }
43
+ }
44
+ try {
45
+ const baked = JSON.parse(fs.readFileSync(path.join(appDir, 'package.json'), 'utf8')).bakedCloud;
46
+ if (whole(baked)) return baked;
47
+ } catch { /* none */ }
48
+ return null;
49
+ }
50
+
51
+ export const cloudKeyPath =(env = process.env, home = os.homedir()) => path.join(path.dirname(cloudConfigPath(env, home)), 'cloud-key');
52
+
53
+ // WHAT THE CLOUD IMAGE SAID IT CAN DO at its last start, per cloud. A run can
54
+ // only learn it from a start, so the next run reads what the last one heard;
55
+ // an image rebuilt without something says so at its first start.
56
+ const capsPath = (env, home) => path.join(path.dirname(cloudConfigPath(env, home)), 'cloud-image.json');
57
+ const cloudId = (config) => String(config?.runtimeArn || config?.gateway || '');
58
+
59
+ export function imageCaps(config, env = process.env, home = os.homedir()) {
60
+ try {
61
+ const caps = JSON.parse(fs.readFileSync(capsPath(env, home), 'utf8'))[cloudId(config)]?.caps;
62
+ return Array.isArray(caps) ? caps.filter((c) => typeof c === 'string') : [];
63
+ } catch { return []; }
64
+ }
65
+
66
+ export function noteImageCaps(config, caps, env = process.env, home = os.homedir()) {
67
+ const file = capsPath(env, home);
68
+ let all = {};
69
+ try { all = JSON.parse(fs.readFileSync(file, 'utf8')) || {}; } catch { /* first */ }
70
+ const id = cloudId(config);
71
+ const now = [...new Set(caps)].sort();
72
+ if (JSON.stringify(all[id]?.caps) === JSON.stringify(now)) return;
73
+ all[id] = { caps: now, at: Date.now() };
74
+ fs.mkdirSync(path.dirname(file), { recursive: true });
75
+ const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
76
+ fs.writeFileSync(tmp, JSON.stringify(all, null, 2));
77
+ fs.renameSync(tmp, file);
78
+ }
79
+
80
+ export function readCloudKey(env = process.env, home = os.homedir()) {
81
+ try { return fs.readFileSync(cloudKeyPath(env, home), 'utf8').trim() || null; } catch { return null; }
82
+ }
83
+
84
+ /**
85
+ * The server said this key is not known (it went unused, or another Mac
86
+ * signed this one out): this Mac is signed out of the cloud, as if by its own
87
+ * hand, so nothing keeps sending threads there with it. A key a newer sign-in
88
+ * put in its place is left alone.
89
+ */
90
+ export function forgetCloudKey(key, env = process.env, home = os.homedir()) {
91
+ if (!key || readCloudKey(env, home) !== key) return false;
92
+ fs.rmSync(cloudKeyPath(env, home), { force: true });
93
+ if (typeof readCloudConfig(env, home)?.gateway === 'string') fs.rmSync(cloudConfigPath(env, home), { force: true });
94
+ return true;
95
+ }
96
+
97
+ export function writeCloudKey(key, env = process.env, home = os.homedir()) {
98
+ const file = cloudKeyPath(env, home);
99
+ fs.mkdirSync(path.dirname(file), { recursive: true });
100
+ const tmp = `${file}.${process.pid}.tmp`;
101
+ fs.writeFileSync(tmp, key, { mode: 0o600 });
102
+ fs.renameSync(tmp, file);
103
+ }
104
+
105
+ // The keychain service the first cloud build kept its own Claude tokens
106
+ // under. Nothing writes it now; main/cloud/logins.mjs deletes what is left.
107
+ export const OLD_TOKEN_SERVICE = `${Name} cloud: Claude sign-in`;