agentbox-app 0.1.13 → 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 (278) 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/agent-sessions.mjs +282 -268
  9. package/main/agents.mjs +179 -466
  10. package/main/analytics.mjs +12 -2
  11. package/main/answer-modes.mjs +103 -37
  12. package/main/approval-prompt-core.mjs +197 -0
  13. package/main/approval-prompt-server.mjs +4 -138
  14. package/main/approvals.mjs +101 -297
  15. package/main/artifact-path.mjs +47 -18
  16. package/main/claude-input.mjs +48 -4
  17. package/main/claude-plan.mjs +48 -0
  18. package/main/claude-session-open.mjs +28 -0
  19. package/main/claude-usage-process.mjs +40 -0
  20. package/main/claude-usage.mjs +18 -40
  21. package/main/cloud/account.mjs +179 -0
  22. package/main/cloud/aws.mjs +127 -0
  23. package/main/cloud/browser-return.mjs +58 -0
  24. package/main/cloud/clients.mjs +15 -0
  25. package/main/cloud/codex-cloud.mjs +53 -0
  26. package/main/cloud/config.mjs +107 -0
  27. package/main/cloud/docs.mjs +219 -0
  28. package/main/cloud/exec.mjs +27 -0
  29. package/main/cloud/host.mjs +165 -0
  30. package/main/cloud/kit.mjs +80 -0
  31. package/main/cloud/logins.mjs +543 -0
  32. package/main/cloud/managed.mjs +115 -0
  33. package/main/cloud/marker.mjs +104 -0
  34. package/main/cloud/org.mjs +53 -0
  35. package/main/cloud/reuse.mjs +208 -0
  36. package/main/cloud/run-cli.mjs +177 -0
  37. package/main/cloud/run.mjs +1934 -0
  38. package/main/cloud/usage.mjs +76 -0
  39. package/main/cloud-permission.mjs +51 -0
  40. package/main/codex-account.mjs +78 -0
  41. package/main/codex-app-server.mjs +276 -409
  42. package/main/codex-approvals.mjs +148 -433
  43. package/main/codex-models.mjs +20 -2
  44. package/main/codex-session.mjs +212 -507
  45. package/main/codex-threads-worker.mjs +12 -0
  46. package/main/codex-threads.mjs +71 -3
  47. package/main/codex-usage-file.mjs +5 -3
  48. package/main/codex-usage.mjs +56 -7
  49. package/main/codex-watch.mjs +17 -3
  50. package/main/codex.mjs +95 -363
  51. package/main/config.mjs +11 -1
  52. package/main/corner-tag.mjs +340 -19
  53. package/main/dev-window.mjs +1 -1
  54. package/main/engine-bin.mjs +232 -282
  55. package/main/engine-setup.mjs +21 -9
  56. package/main/feedback.mjs +3 -25
  57. package/main/folder-pictures.mjs +2 -2
  58. package/main/freeze-recorder-thread.mjs +86 -0
  59. package/main/freeze-recorder.mjs +146 -0
  60. package/main/fresh-user.mjs +20 -9
  61. package/main/git-change-offthread.mjs +17 -3
  62. package/main/git-change-thread.mjs +5 -2
  63. package/main/harnesses/claude.mjs +32 -11
  64. package/main/harnesses/codex.mjs +9 -2
  65. package/main/importable-threads.mjs +64 -6
  66. package/main/ipc.mjs +362 -111
  67. package/main/leftovers.mjs +117 -25
  68. package/main/liquid-glass.mjs +26 -0
  69. package/main/live-replies.mjs +10 -4
  70. package/main/main-went-red.mjs +330 -0
  71. package/main/main.mjs +199 -84
  72. package/main/memory-gate-history.mjs +36 -10
  73. package/main/memory-gate-server.mjs +62 -0
  74. package/main/memory-gate.mjs +14 -1
  75. package/main/menu.mjs +4 -2
  76. package/main/message-priority.mjs +2 -2
  77. package/main/mic.mjs +38 -0
  78. package/main/native/liquid-glass/build/liquid-glass.node +0 -0
  79. package/main/new-chats-watch.mjs +110 -0
  80. package/main/new-user-window.mjs +25 -0
  81. package/main/notify.mjs +24 -8
  82. package/main/phone-alerts.mjs +117 -0
  83. package/main/phone-inbox.mjs +625 -0
  84. package/main/phone-link.mjs +29 -9
  85. package/main/phone-uploads.mjs +476 -0
  86. package/main/picture-shape.mjs +114 -0
  87. package/main/quit-sequence.mjs +143 -0
  88. package/main/refresh-agent-models.mjs +15 -2
  89. package/main/relay-client.mjs +69 -1
  90. package/main/repeats.mjs +10 -4
  91. package/main/row-label.mjs +12 -41
  92. package/main/run-change.mjs +13 -0
  93. package/main/run-ledger.mjs +135 -0
  94. package/main/save-attachments.mjs +41 -0
  95. package/main/serve.mjs +45 -3
  96. package/main/settings.mjs +220 -44
  97. package/main/shell-path.mjs +185 -37
  98. package/main/ship-conflict-brief.mjs +119 -0
  99. package/main/ship-queue.mjs +115 -19
  100. package/main/source-updater.mjs +18 -10
  101. package/main/stage-attachment.mjs +68 -0
  102. package/main/stall-watch.mjs +50 -0
  103. package/main/store/home.mjs +87 -232
  104. package/main/store/paths.mjs +11 -0
  105. package/main/store/teammates-files.mjs +99 -0
  106. package/main/store/work-items.mjs +231 -13
  107. package/main/store.mjs +226 -52
  108. package/main/supervisor.mjs +3103 -341
  109. package/main/task-folders.mjs +430 -30
  110. package/main/task-terminals.mjs +110 -34
  111. package/main/team/index.mjs +114 -10
  112. package/main/team/memory-cloud.mjs +183 -1
  113. package/main/team/room-hours.mjs +48 -0
  114. package/main/team/room-setup.mjs +31 -0
  115. package/main/team/rooms-memory.mjs +454 -0
  116. package/main/team/rooms-shape.mjs +42 -0
  117. package/main/team/rooms-supabase.mjs +324 -0
  118. package/main/team/rooms.mjs +575 -0
  119. package/main/team/session.mjs +96 -26
  120. package/main/team/supabase-backend.mjs +180 -0
  121. package/main/team/sync.mjs +109 -1
  122. package/main/teammates.mjs +580 -0
  123. package/main/terminal-host.mjs +143 -0
  124. package/main/terminal-hosts.mjs +261 -0
  125. package/main/transcript-search.mjs +80 -0
  126. package/main/webview-guard.mjs +45 -0
  127. package/main/worktree-include.mjs +90 -219
  128. package/mcp/core/documents.mjs +3 -1
  129. package/mcp/core/repeats.mjs +88 -0
  130. package/mcp/core/teammates.mjs +222 -0
  131. package/mcp/core/work.mjs +76 -4
  132. package/mcp/stdio.mjs +24 -11
  133. package/mcp/tools.mjs +116 -8
  134. package/package.json +21 -5
  135. package/renderer/dist/assets/{abnfDiagram-YUKMZFIV-DPPuMWt-.js → abnfDiagram-YUKMZFIV-CoWB6YUK.js} +1 -1
  136. package/renderer/dist/assets/{arc-HmFfdseH.js → arc-Ch0NrCul.js} +1 -1
  137. package/renderer/dist/assets/{architectureDiagram-47P4ROYG-DxJ3zHrH.js → architectureDiagram-47P4ROYG-CXz9M5tQ.js} +1 -1
  138. package/renderer/dist/assets/{blockDiagram-E7TT5TSB-CbTXzOqQ.js → blockDiagram-E7TT5TSB-DchGUuHH.js} +1 -1
  139. package/renderer/dist/assets/{c4Diagram-CRA5TL53-X5ItFyLn.js → c4Diagram-CRA5TL53-Cip-zNyr.js} +1 -1
  140. package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
  141. package/renderer/dist/assets/{chunk-2Q5K7J3B-DC12Ho_L.js → chunk-2Q5K7J3B-BoXid_2r.js} +1 -1
  142. package/renderer/dist/assets/{chunk-5VM5RSS4-ClVtfPd0.js → chunk-5VM5RSS4-Cpez1rRz.js} +1 -1
  143. package/renderer/dist/assets/{chunk-7TKQ45FW-DgcOm0HT.js → chunk-7TKQ45FW-DKA84YkE.js} +1 -1
  144. package/renderer/dist/assets/{chunk-AW2ZBBNX-C01s4yoF.js → chunk-AW2ZBBNX-Dg-SumPG.js} +1 -1
  145. package/renderer/dist/assets/{chunk-FVRAUYC3-Bj0zxhR4.js → chunk-FVRAUYC3-06TCvPm1.js} +2 -2
  146. package/renderer/dist/assets/{chunk-HTAEGDNF-DWDeEhLU.js → chunk-HTAEGDNF-CMEBy1Td.js} +1 -1
  147. package/renderer/dist/assets/{chunk-JWPE2WC7-Dsqwv3eW.js → chunk-JWPE2WC7-BJ2ObmV-.js} +1 -1
  148. package/renderer/dist/assets/{chunk-KQW6MTUR-CE86ANL2.js → chunk-KQW6MTUR-CGWOYXUE.js} +1 -1
  149. package/renderer/dist/assets/{chunk-XXDRQBXY-CoNTASfs.js → chunk-XXDRQBXY-CiZp07XC.js} +1 -1
  150. package/renderer/dist/assets/{classDiagram-v2-K4WV4PDN-2wprLqtw.js → classDiagram-v2-K4WV4PDN-DJNIX719.js} +1 -1
  151. package/renderer/dist/assets/cornerTag-CPgvu-q3.js +1 -0
  152. package/renderer/dist/assets/cornerTag-oMfEjzCO.css +1 -0
  153. package/renderer/dist/assets/{cose-bilkent-JH36ORCC-enqMiYbn.js → cose-bilkent-JH36ORCC-CbkyZOUH.js} +1 -1
  154. package/renderer/dist/assets/{cynefin-EF2NZ3EQ-DHngLxvQ.js → cynefin-EF2NZ3EQ-78iOroZO.js} +1 -1
  155. package/renderer/dist/assets/{cynefinDiagram-3GCD6N5R-C0rZZIl3.js → cynefinDiagram-3GCD6N5R-GcCopMs7.js} +1 -1
  156. package/renderer/dist/assets/{dagre-W4DXFKR2-DQnaEWO7.js → dagre-W4DXFKR2-DHLlXLkz.js} +1 -1
  157. package/renderer/dist/assets/{diagram-2UJZ2QOL-CS8AlGPx.js → diagram-2UJZ2QOL-DuZSUHcH.js} +1 -1
  158. package/renderer/dist/assets/{diagram-OVF4WLC6-yiDMHIkY.js → diagram-OVF4WLC6-Ckxu3cqH.js} +1 -1
  159. package/renderer/dist/assets/{diagram-PFPMY2P6-XbQKzbBA.js → diagram-PFPMY2P6-CsTw9Ycn.js} +1 -1
  160. package/renderer/dist/assets/{diagram-UMYRVEAY-dhVvlfl_.js → diagram-UMYRVEAY-CTXd8qVg.js} +1 -1
  161. package/renderer/dist/assets/{diagram-YEKJPTXX-DZHe8SCf.js → diagram-YEKJPTXX-CJs1T_9m.js} +1 -1
  162. package/renderer/dist/assets/{diagram-ZIFT7M5P-B5h6aXVR.js → diagram-ZIFT7M5P-CvjI23mf.js} +1 -1
  163. package/renderer/dist/assets/{ebnfDiagram-VR2GEFS7-PR9ScDco.js → ebnfDiagram-VR2GEFS7-d_IeAzOJ.js} +1 -1
  164. package/renderer/dist/assets/{elk-IJKZMXRS-Cg-yQXPd.js → elk-IJKZMXRS-zo9KFP3N.js} +1 -1
  165. package/renderer/dist/assets/{erDiagram-O2IAPWRE-omOrJMxu.js → erDiagram-O2IAPWRE-DqfYf5Dt.js} +1 -1
  166. package/renderer/dist/assets/{flowDiagram-OXPTDLAJ-aOX2hzvT.js → flowDiagram-OXPTDLAJ-DAWsMLmo.js} +1 -1
  167. package/renderer/dist/assets/{ganttDiagram-R7TSEDQI-B1H3pWyI.js → ganttDiagram-R7TSEDQI-4ml_bhwR.js} +1 -1
  168. package/renderer/dist/assets/{gitGraphDiagram-XJZIOB7I-Cvw304HC.js → gitGraphDiagram-XJZIOB7I-B_WcqSId.js} +1 -1
  169. package/renderer/dist/assets/{infoDiagram-5W2HQ5XZ-_ZXcNViH.js → infoDiagram-5W2HQ5XZ-CI-jYqzc.js} +1 -1
  170. package/renderer/dist/assets/{ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js → ishikawaDiagram-K3B6WC7H-B56VWRlV.js} +1 -1
  171. package/renderer/dist/assets/{journeyDiagram-COXZFDF6-fr62QoAt.js → journeyDiagram-COXZFDF6-D1-p09Lr.js} +1 -1
  172. package/renderer/dist/assets/{kanban-definition-P3RFRI5V-J8zsabCe.js → kanban-definition-P3RFRI5V-Cv3w7JoN.js} +1 -1
  173. package/renderer/dist/assets/{linear-BmNdEtIi.js → linear-C35V2Spi.js} +1 -1
  174. package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
  175. package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
  176. package/renderer/dist/assets/{mermaid.core-CnYzvFkz.js → mermaid.core-BzGT5Vo9.js} +4 -4
  177. package/renderer/dist/assets/{mindmap-definition-LZFPQGKD-Bfcxc7PM.js → mindmap-definition-LZFPQGKD-BZUfuymC.js} +1 -1
  178. package/renderer/dist/assets/{pegDiagram-BGZESJAR-CwUJOq6X.js → pegDiagram-BGZESJAR-DFh9XNGe.js} +1 -1
  179. package/renderer/dist/assets/{pieDiagram-CAPJLFHJ-BrwMmqfC.js → pieDiagram-CAPJLFHJ-C65tPVsy.js} +1 -1
  180. package/renderer/dist/assets/{product-name-CTKLYNWY.js → product-name-CSNW6d7s.js} +1 -1
  181. package/renderer/dist/assets/{quadrantDiagram-GDMTTRAM-BN4Stoj-.js → quadrantDiagram-GDMTTRAM-Cushj_nk.js} +1 -1
  182. package/renderer/dist/assets/{railroadDiagram-SM67HX2B-CPZ5bbeR.js → railroadDiagram-SM67HX2B-DZGebGx5.js} +1 -1
  183. package/renderer/dist/assets/{requirementDiagram-X7JNWC4A-CgRnimVe.js → requirementDiagram-X7JNWC4A-DpAB0MNo.js} +1 -1
  184. package/renderer/dist/assets/{sankeyDiagram-UM26HJRW-Bt2rej__.js → sankeyDiagram-UM26HJRW-D-29rHra.js} +1 -1
  185. package/renderer/dist/assets/{sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js → sequenceDiagram-ZO4K6R2Y-FfOQAZol.js} +1 -1
  186. package/renderer/dist/assets/{sizeCapture-INFHLROL-usyNyXyh.js → sizeCapture-INFHLROL-C_n6fDBY.js} +1 -1
  187. package/renderer/dist/assets/{stateDiagram-v2-TBUQTH76-CRzUBb0u.js → stateDiagram-v2-TBUQTH76-BZjenzJG.js} +1 -1
  188. package/renderer/dist/assets/{swimlanes-N4OXWK64-C1GJvMS0.js → swimlanes-N4OXWK64-DxaMU9vX.js} +1 -1
  189. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
  190. package/renderer/dist/assets/{timeline-definition-YOQAKGHF-caeyPlcc.js → timeline-definition-YOQAKGHF-DTg5HJKz.js} +1 -1
  191. package/renderer/dist/assets/{usecaseDiagram-VIAY4XPW-BN_SV6PC.js → usecaseDiagram-VIAY4XPW-Bc3MZDsl.js} +1 -1
  192. package/renderer/dist/assets/{vennDiagram-BLWOH2XV-BLGuaUX-.js → vennDiagram-BLWOH2XV-GIVGk9Id.js} +1 -1
  193. package/renderer/dist/assets/{wardleyDiagram-YMQ3BMBF-Da0cJeNc.js → wardleyDiagram-YMQ3BMBF-NmJnbQOg.js} +1 -1
  194. package/renderer/dist/assets/{xychartDiagram-TAQBALBS-cGfUffMk.js → xychartDiagram-TAQBALBS-p-kfGfnW.js} +1 -1
  195. package/renderer/dist/corner-tag.html +3 -3
  196. package/renderer/dist/index.html +3 -3
  197. package/scripts/after-install.mjs +30 -8
  198. package/scripts/cloud-shim.pl +57 -0
  199. package/shared/account-names.d.mts +6 -0
  200. package/shared/account-names.mjs +15 -0
  201. package/shared/agent-hours.mjs +136 -0
  202. package/shared/agent-import.mjs +60 -34
  203. package/shared/agents.mjs +126 -522
  204. package/shared/analytics-events.mjs +12 -0
  205. package/shared/approval-card.mjs +36 -0
  206. package/shared/bridge-map.mjs +27 -1
  207. package/shared/claude-code-shape.mjs +6 -0
  208. package/shared/claude-usage.d.mts +1 -0
  209. package/shared/claude-usage.mjs +42 -1
  210. package/shared/cloud-hand-back.d.mts +7 -0
  211. package/shared/cloud-hand-back.mjs +20 -0
  212. package/shared/cloud-kit.d.mts +219 -0
  213. package/shared/cloud-kit.mjs +341 -0
  214. package/shared/cloud-login-name.mjs +4 -0
  215. package/shared/cloud-start.mjs +17 -0
  216. package/shared/cloud-versions.mjs +23 -0
  217. package/shared/codex-import.mjs +2 -2
  218. package/shared/codex-modes.d.mts +22 -9
  219. package/shared/codex-modes.mjs +67 -11
  220. package/shared/codex-usage.mjs +108 -2
  221. package/shared/corner-tag.d.mts +23 -1
  222. package/shared/corner-tag.mjs +284 -29
  223. package/shared/dead-run-trace.mjs +19 -2
  224. package/shared/engines.mjs +70 -309
  225. package/shared/feedback.d.mts +7 -0
  226. package/shared/feedback.mjs +25 -0
  227. package/shared/first-run-practice.mjs +17 -24
  228. package/shared/harness-definitions.mjs +8 -0
  229. package/shared/instruction-defaults.json +2 -2
  230. package/shared/key-caps.d.mts +14 -0
  231. package/shared/key-caps.mjs +68 -0
  232. package/shared/message-edits.d.mts +7 -0
  233. package/shared/message-edits.mjs +35 -0
  234. package/shared/move-thread.d.mts +24 -0
  235. package/shared/move-thread.mjs +112 -0
  236. package/shared/named-threads.mjs +72 -0
  237. package/shared/not-shipped.d.mts +22 -0
  238. package/shared/not-shipped.mjs +89 -0
  239. package/shared/notify-rules.d.mts +6 -0
  240. package/shared/notify-rules.mjs +36 -8
  241. package/shared/phone-files.mjs +28 -0
  242. package/shared/phone-settings.mjs +118 -0
  243. package/shared/phone-snapshot.d.mts +16 -0
  244. package/shared/phone-snapshot.mjs +348 -0
  245. package/shared/picture-formats.d.mts +1 -0
  246. package/shared/picture-formats.mjs +13 -0
  247. package/shared/rank.d.mts +2 -1
  248. package/shared/rank.mjs +32 -19
  249. package/shared/repeats.d.mts +8 -0
  250. package/shared/repeats.mjs +33 -2
  251. package/shared/room-cards.mjs +167 -0
  252. package/shared/room-links.d.mts +4 -0
  253. package/shared/room-links.mjs +21 -0
  254. package/shared/runs-on.d.mts +3 -3
  255. package/shared/runs-on.mjs +8 -5
  256. package/shared/shipped-note.d.mts +4 -0
  257. package/shared/shipped-note.mjs +6 -0
  258. package/shared/shortcuts.mjs +14 -6
  259. package/shared/spawn-trouble.d.mts +4 -0
  260. package/shared/spawn-trouble.mjs +443 -40
  261. package/shared/team-rules.mjs +9 -2
  262. package/shared/teammates.d.mts +19 -0
  263. package/shared/teammates.mjs +756 -0
  264. package/shared/terminal-focus.d.mts +1 -0
  265. package/shared/terminal-focus.mjs +13 -0
  266. package/shared/thread-cards.mjs +4 -1
  267. package/shared/usage.d.mts +9 -0
  268. package/shared/usage.mjs +49 -3
  269. package/shared/websites.d.mts +15 -0
  270. package/shared/websites.mjs +61 -0
  271. package/shared/work-items.mjs +127 -30
  272. package/shared/work-lines.mjs +92 -244
  273. package/renderer/dist/assets/channel-BlT2g9Sz.js +0 -1
  274. package/renderer/dist/assets/cornerTag-BLrxiDE5.css +0 -1
  275. package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +0 -1
  276. package/renderer/dist/assets/main-ChNdnUvt.js +0 -1132
  277. package/renderer/dist/assets/main-nj0QR5kg.css +0 -32
  278. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +0 -8
@@ -0,0 +1,104 @@
1
+ // "A CLOUD RUN'S WORK IS STILL ON ITS WAY BACK TO THIS FOLDER."
2
+ //
3
+ // A cloud run (docs/cloud/PLAN.md) commits in the sandbox, and the commits
4
+ // reach the task folder only when the run is over. Until then the folder's
5
+ // branch is stale, and two things in the app would act on it as if it were
6
+ // not: the ship queue (a worker adds `ship` BEFORE it exits, and "there is
7
+ // nothing to ship" counts as shipped) and the folder cleaner (a clean, merged
8
+ // folder is removed). This record is what both of them check.
9
+ //
10
+ // It lives in the folder's own git directory, not in the working tree, so no
11
+ // commit, park or `git clean` can carry it off or delete it, and it names the
12
+ // run, so a later run in the same folder can bring that run's work home first.
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+
16
+ const NAME = 'agentbox-cloud.json';
17
+
18
+ // Read off the folder's `.git` rather than asked of `git rev-parse`: this runs
19
+ // for every task folder on the app's main process and at every marker read in
20
+ // the relay all cloud runs share, and a busy Mac takes half a second to start git.
21
+ export function gitDirOf(folder) {
22
+ if (!folder) return null;
23
+ for (let dir = path.resolve(folder); ; dir = path.dirname(dir)) {
24
+ const dotGit = path.join(dir, '.git');
25
+ let st = null;
26
+ try { st = fs.statSync(dotGit); } catch { /* not here */ }
27
+ if (st?.isDirectory() && fs.existsSync(path.join(dotGit, 'HEAD'))) return dotGit;
28
+ if (st?.isFile()) {
29
+ const m = /^gitdir:\s*(.+?)\s*$/m.exec(fs.readFileSync(dotGit, 'utf8'));
30
+ const linked = m && path.resolve(dir, m[1]);
31
+ return linked && fs.existsSync(linked) ? linked : null;
32
+ }
33
+ if (path.dirname(dir) === dir) return null;
34
+ }
35
+ }
36
+
37
+ // The main repository's git directory, which a linked worktree's names in `commondir`.
38
+ export function commonGitDirOf(folder) {
39
+ const dir = gitDirOf(folder);
40
+ if (!dir) return null;
41
+ try { return path.resolve(dir, fs.readFileSync(path.join(dir, 'commondir'), 'utf8').trim()); } catch { return dir; }
42
+ }
43
+
44
+ // `folder` is a task folder, or `{ folder, file }` for a project with no
45
+ // repository: its threads share one documents folder, so each keeps its own
46
+ // marker at `file` (Supervisor#_plainMarker).
47
+ export function markerPath(folder) {
48
+ if (folder && typeof folder === 'object') return folder.file || null;
49
+ const dir = gitDirOf(folder);
50
+ return dir ? path.join(dir, NAME) : null;
51
+ }
52
+
53
+ export function readMarker(folder) {
54
+ const file = markerPath(folder);
55
+ if (!file) return null;
56
+ try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; }
57
+ }
58
+
59
+ export function writeMarker(folder, record) {
60
+ const file = markerPath(folder);
61
+ if (!file) throw new Error(`${folder} is not a git folder`);
62
+ if (typeof folder === 'object') fs.mkdirSync(path.dirname(file), { recursive: true });
63
+ const tmp = `${file}.${process.pid}.tmp`;
64
+ fs.writeFileSync(tmp, JSON.stringify({ ...record, at: Date.now() }, null, 2));
65
+ fs.renameSync(tmp, file);
66
+ }
67
+
68
+ export function clearMarker(folder) {
69
+ const file = markerPath(folder);
70
+ if (file) fs.rmSync(file, { force: true });
71
+ }
72
+
73
+ // The one question the ship queue and the folder cleaner ask. A folder that
74
+ // is not there, or not a git folder, has nothing pending.
75
+ export function cloudWorkPending(folder) {
76
+ if (folder && typeof folder === 'object') return !!folder.file && !!readMarker(folder);
77
+ if (!folder || !fs.existsSync(folder)) return false;
78
+ return !!readMarker(folder);
79
+ }
80
+
81
+ // WHAT A RUN BROUGHT HOME THAT ITS ROW MUST SAY (main/cloud/run.mjs `park`):
82
+ // written by the relay, which cannot reach the store, and said by the
83
+ // supervisor (`_sayCloudNews`). Beside the marker, and outliving it.
84
+ const NEWS = 'agentbox-cloud-news.json';
85
+ const newsPath = (folder) => { const dir = gitDirOf(folder); return dir ? path.join(dir, NEWS) : null; };
86
+
87
+ export function addCloudNews(folder, news) {
88
+ const file = newsPath(folder);
89
+ if (!file) return;
90
+ let all = [];
91
+ try { all = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { /* none yet */ }
92
+ const tmp = `${file}.${process.pid}.tmp`;
93
+ fs.writeFileSync(tmp, JSON.stringify([...(Array.isArray(all) ? all : []), { ...news, at: Date.now() }], null, 2));
94
+ fs.renameSync(tmp, file);
95
+ }
96
+
97
+ export function takeCloudNews(folder) {
98
+ const file = folder && fs.existsSync(folder) ? newsPath(folder) : null;
99
+ if (!file || !fs.existsSync(file)) return [];
100
+ let all = [];
101
+ try { all = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { /* unreadable: dropped */ }
102
+ fs.rmSync(file, { force: true });
103
+ return Array.isArray(all) ? all : [];
104
+ }
@@ -0,0 +1,53 @@
1
+ // WHICH CLAUDE ACCOUNT A CLOUD SIGN-IN IS, BY ITS ORGANIZATION.
2
+ //
3
+ // The profile lookup refuses these sign-ins (403, no user:profile scope), so
4
+ // the cloud cannot learn an account's email. A free token count answers with
5
+ // the `anthropic-organization-id` header instead, which is the same for two
6
+ // sign-ins of one account (measured 2026-10-16). It is asked by a short run in
7
+ // the cloud, with that account's own sign-in, so the sign-in never comes to
8
+ // this Mac; only the id does.
9
+ import fs from 'node:fs';
10
+ import os from 'node:os';
11
+ import path from 'node:path';
12
+ import { PassThrough } from 'node:stream';
13
+ import { EventEmitter } from 'node:events';
14
+ import { execFileSync } from 'node:child_process';
15
+ import { cloudRun } from './run.mjs';
16
+ import { loginObjectKey } from './logins.mjs';
17
+ import { Name, nameSlug } from '../../shared/product-name.mjs';
18
+
19
+ const MARK = 'agentbox-org:';
20
+ const SANDBOX_NODE = '/usr/local/bin/node';
21
+ const PROBE = `fetch('https://api.anthropic.com/v1/messages/count_tokens',{method:'POST',headers:{Authorization:'Bearer '+process.env.CLAUDE_CODE_OAUTH_TOKEN,'anthropic-beta':'oauth-2025-04-20','anthropic-version':'2023-06-01','content-type':'application/json'},body:JSON.stringify({model:'claude-haiku-4-5',messages:[{role:'user',content:'hi'}]})}).then(r=>console.log('${MARK}'+(r.headers.get('anthropic-organization-id')||''))).catch(()=>console.log('${MARK}'))`;
22
+
23
+ /** The id on the probe's own line, when it is one; null otherwise. */
24
+ export function orgFromOutput(text) {
25
+ const line = String(text ?? '').split('\n').find((l) => l.startsWith(MARK));
26
+ const id = line?.slice(MARK.length).trim();
27
+ return id && /^[0-9a-f-]{8,64}$/i.test(id) ? id : null;
28
+ }
29
+
30
+ /** The organization of one cloud Claude account, or null when the cloud would not say. */
31
+ export async function readCloudOrg(rec, { transport, storage }) {
32
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'agentbox-org-'));
33
+ try {
34
+ const git = (...a) => execFileSync('git', a, { cwd: dir, stdio: 'pipe' });
35
+ git('init', '-q', '-b', 'main', '.');
36
+ git('-c', `user.name=${Name}`, '-c', `user.email=${nameSlug}@localhost`, 'commit', '-q', '--allow-empty', '-m', 'org check');
37
+ git('checkout', '-q', '-b', 'agentbox/org-check');
38
+ const stdout = new PassThrough();
39
+ let out = '';
40
+ stdout.on('data', (d) => { out += d; });
41
+ const stdin = new PassThrough();
42
+ stdin.end();
43
+ await cloudRun({
44
+ argv: ['-e', PROBE], env: { PATH: process.env.PATH, ZERO_ITEM: 'org-check' }, cwd: dir,
45
+ transport, storage, login: { engine: 'claude', id: rec.id, key: loginObjectKey('claude', rec.id) },
46
+ remoteCommand: SANDBOX_NODE, stdin, stdout, stderr: new PassThrough(),
47
+ homeDir: path.join(dir, 'home'), watchParent: false, signals: new EventEmitter(),
48
+ });
49
+ return orgFromOutput(out);
50
+ } finally {
51
+ fs.rmSync(dir, { recursive: true, force: true });
52
+ }
53
+ }
@@ -0,0 +1,208 @@
1
+ // WHAT A CLOUD RUN SENDS ONCE AND THEN REUSES.
2
+ //
3
+ // Every reply in the cloud is a new machine, but the project's history and
4
+ // the account's skills and memories barely change between replies. Each is
5
+ // kept in the bucket under a name made from what it holds and sent again only
6
+ // when that changes. The history goes as one base shared by every thread of
7
+ // the repository, and each run sends only the commits past it (`pickBase`).
8
+ import fs from 'node:fs';
9
+ import os from 'node:os';
10
+ import path from 'node:path';
11
+ import crypto from 'node:crypto';
12
+ import { run } from './exec.mjs';
13
+ import { commonGitDirOf, markerPath } from './marker.mjs';
14
+
15
+ // The bucket deletes everything under `runs/` seven days after it was written
16
+ // (scripts/cloud-setup.mjs). Names carry the six-day period they were written
17
+ // in and are only reused inside it, so nothing reused is ever that old.
18
+ export const CACHE_PERIOD_MS = 6 * 24 * 60 * 60_000;
19
+
20
+ export const periodOf = (now = Date.now()) => Math.floor(now / CACHE_PERIOD_MS);
21
+ export const cacheKey = (name, now = Date.now()) => `runs/cache/${periodOf(now)}/${name}`;
22
+
23
+ // What a folder holds, as tar -h packs it: every file, following links, by its
24
+ // path, size, time and mode. Unchanged files give the same name; any change
25
+ // gives a new one.
26
+ export async function folderKey(dir) {
27
+ const h = crypto.createHash('sha256');
28
+ const seen = new Set();
29
+ async function walk(d, rel) {
30
+ let real;
31
+ try { real = await fs.promises.realpath(d); } catch { return; }
32
+ if (seen.has(real)) return;
33
+ seen.add(real);
34
+ let names;
35
+ try { names = (await fs.promises.readdir(d)).sort(); } catch { return; }
36
+ for (const name of names) {
37
+ const p = path.join(d, name);
38
+ const r = rel ? `${rel}/${name}` : name;
39
+ let st;
40
+ try { st = await fs.promises.stat(p); } catch { continue; }
41
+ if (st.isDirectory()) await walk(p, r);
42
+ else if (st.isFile()) h.update(`${r}\0${st.size}\0${st.mtimeMs}\0${st.mode}\n`);
43
+ }
44
+ }
45
+ await walk(dir, '');
46
+ return h.digest('hex');
47
+ }
48
+
49
+ // Puts `make()` at `key` unless the bucket already has it. A storage that
50
+ // cannot say what it has gets everything sent, as before.
51
+ export async function sendOnce(storage, key, make) {
52
+ if (storage.exists && await storage.exists(key).catch(() => false)) return { key, sent: false };
53
+ const body = await make();
54
+ await storage.put(key, body);
55
+ return { key, sent: true };
56
+ }
57
+
58
+ const has = (storage, key) => (storage.exists ? storage.exists(key).catch(() => false) : false);
59
+
60
+ // S3 copies a part only from 5 MiB, so a conversation smaller than that is
61
+ // sent whole every time.
62
+ export const MIN_JOIN_BYTES = 5 * 1024 * 1024;
63
+
64
+ // Where this Mac notes the conversation it last sent from a folder: beside
65
+ // the folder's cloud marker.
66
+ export function sentRecordPath(here) {
67
+ const marker = markerPath(here);
68
+ return marker ? `${marker.replace(/\.json$/, '')}-sent.json` : null;
69
+ }
70
+
71
+ // The hash of the first `upTo` bytes (null when the file is shorter) and of
72
+ // the whole file, in one read.
73
+ async function hashes(file, upTo) {
74
+ const whole = crypto.createHash('sha256');
75
+ let prefix = null;
76
+ let seen = 0;
77
+ for await (const chunk of fs.createReadStream(file)) {
78
+ if (prefix === null && upTo > 0 && seen + chunk.length >= upTo) {
79
+ whole.update(chunk.subarray(0, upTo - seen));
80
+ prefix = whole.copy().digest('hex');
81
+ whole.update(chunk.subarray(upTo - seen));
82
+ } else whole.update(chunk);
83
+ seen += chunk.length;
84
+ }
85
+ return { prefix, whole: whole.digest('hex'), size: seen };
86
+ }
87
+
88
+ /**
89
+ * Puts a resumed conversation in the bucket and returns its key. Claude Code
90
+ * only ever adds to one, so once a copy is there only what was added since is
91
+ * sent, and joined to it in the bucket (`storage.extend`). Anything else, a
92
+ * conversation that changed or a copy the bucket no longer has, goes whole.
93
+ * A copy from an earlier period is joined from, never read by the cloud: the
94
+ * join is a new object under this period's name. `record` is where the last
95
+ * send is noted.
96
+ */
97
+ export async function sendConversation(storage, file, { record = null, now = Date.now() } = {}) {
98
+ // Hashed and sent from one copy, so the key always names what was sent.
99
+ const copy = path.join(os.tmpdir(), `agentbox-conversation-${crypto.randomUUID()}.jsonl`);
100
+ try {
101
+ fs.copyFileSync(file, copy, fs.constants.COPYFILE_FICLONE);
102
+ let had = null;
103
+ try { had = record ? JSON.parse(fs.readFileSync(record, 'utf8')) : null; } catch { /* none yet */ }
104
+ const joinable = !!storage.extend && Number.isInteger(had?.size)
105
+ && had.size >= MIN_JOIN_BYTES && typeof had.key === 'string' && typeof had.sha === 'string';
106
+ const { prefix, whole, size } = await hashes(copy, joinable ? had.size : 0);
107
+ const key = cacheKey(`conversation-${whole}.jsonl`, now);
108
+ let sent = 0;
109
+ if (!(await has(storage, key))) {
110
+ if (joinable && prefix === had.sha && size > had.size && await has(storage, had.key)) {
111
+ try { await storage.extend(key, had.key, copy, had.size); sent = size - had.size; } catch { /* sent whole below */ }
112
+ }
113
+ if (!sent) {
114
+ if (storage.putFile) await storage.putFile(key, copy);
115
+ else await storage.put(key, fs.readFileSync(copy));
116
+ sent = size;
117
+ }
118
+ }
119
+ if (record) {
120
+ try {
121
+ const tmp = `${record}.${process.pid}.tmp`;
122
+ fs.writeFileSync(tmp, JSON.stringify({ period: periodOf(now), key, size, sha: whole }));
123
+ fs.renameSync(tmp, record);
124
+ } catch { /* the next send goes whole */ }
125
+ }
126
+ return { key, size, sent };
127
+ } finally {
128
+ fs.rmSync(copy, { force: true });
129
+ }
130
+ }
131
+
132
+ const git = (cwd, args) => run('git', args, { cwd });
133
+ const gitOk = (cwd, args) => git(cwd, args).then(() => true, () => false);
134
+
135
+ async function mergeBase(cwd) {
136
+ for (const r of ['refs/remotes/origin/main', 'refs/heads/main']) {
137
+ try { return (await git(cwd, ['merge-base', 'HEAD', r])).trim(); } catch { /* not there */ }
138
+ }
139
+ return (await git(cwd, ['rev-parse', 'HEAD'])).trim();
140
+ }
141
+
142
+ const baseRecordPath = (cwd) => {
143
+ const common = commonGitDirOf(cwd);
144
+ return common ? path.join(common, 'agentbox-cloud-base.json') : null;
145
+ };
146
+
147
+ /**
148
+ * The commit this run's history is sent against, and its bundle's name in the
149
+ * bucket. Kept beside the repository so every thread of it shares one base for
150
+ * the period; made afresh, from where the branch meets main, when the period
151
+ * has turned or the bucket no longer has it.
152
+ */
153
+ export async function pickBase(cwd, storage, { now = Date.now(), runId = crypto.randomUUID() } = {}) {
154
+ const record = baseRecordPath(cwd);
155
+ const period = periodOf(now);
156
+ let had = null;
157
+ try { had = JSON.parse(fs.readFileSync(record, 'utf8')); } catch { /* none yet */ }
158
+ if (had?.period === period && /^[0-9a-f]{40,64}$/.test(had.sha || '')
159
+ && await gitOk(cwd, ['cat-file', '-e', `${had.sha}^{commit}`])) {
160
+ const key = cacheKey(`base-${had.sha}.bundle`, now);
161
+ if (await storage.exists(key).catch(() => false)) return { sha: had.sha, key, sent: false };
162
+ }
163
+ const sha = await mergeBase(cwd);
164
+ const key = cacheKey(`base-${sha}.bundle`, now);
165
+ let sent = false;
166
+ if (!(await storage.exists(key).catch(() => false))) {
167
+ const ref = `refs/agentbox/cloud-base/${runId}`;
168
+ const file = path.join(os.tmpdir(), `agentbox-cloud-base-${runId}.bundle`);
169
+ try {
170
+ await git(cwd, ['update-ref', ref, sha]);
171
+ await git(cwd, ['bundle', 'create', '-q', file, ref]);
172
+ if (storage.putFile) await storage.putFile(key, file);
173
+ else await storage.put(key, fs.readFileSync(file));
174
+ sent = true;
175
+ } finally {
176
+ fs.rmSync(file, { force: true });
177
+ await gitOk(cwd, ['update-ref', '-d', ref]);
178
+ }
179
+ }
180
+ if (record) {
181
+ try {
182
+ const tmp = `${record}.${process.pid}.${runId}.tmp`;
183
+ fs.writeFileSync(tmp, JSON.stringify({ period, sha }));
184
+ fs.renameSync(tmp, record);
185
+ } catch { /* the next run makes its own */ }
186
+ }
187
+ return { sha, key, sent };
188
+ }
189
+
190
+ /**
191
+ * The commits of `refs` past `base`, as a bundle file, or null when the base
192
+ * already holds every one of them (git refuses a bundle with nothing in it).
193
+ */
194
+ export async function bundlePast(cwd, refs, base, file) {
195
+ const ahead = [];
196
+ for (const r of refs) if (!(await gitOk(cwd, ['merge-base', '--is-ancestor', r, base]))) ahead.push(r);
197
+ if (!ahead.length) return null;
198
+ await git(cwd, ['bundle', 'create', '-q', file, ...ahead, `^${base}`]);
199
+ return file;
200
+ }
201
+
202
+ // Each ref the run takes, with the commit it names, so the cloud can set the
203
+ // ones a thin bundle leaves out because the base already holds them.
204
+ export async function refsAt(cwd, refs) {
205
+ const out = {};
206
+ for (const r of refs) out[r] = (await git(cwd, ['rev-parse', `${r}^{commit}`])).trim();
207
+ return out;
208
+ }
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+ // THE PROGRAM THE SUPERVISOR STARTS IN PLACE OF `claude` FOR A CLOUD RUN.
3
+ //
4
+ // ELECTRON_RUN_AS_NODE=1 <electron> main/cloud/run-cli.mjs <claude's argv>
5
+ //
6
+ // Same argv, env and cwd as the local run would have had. Everything it does
7
+ // is main/cloud/run.mjs; this only connects it to the real AWS account named
8
+ // in ~/.agentbox/cloud.json and names this account's cloud sign-in, which
9
+ // lives in the cloud and is never read here (main/cloud/logins.mjs).
10
+ // `cloudRunFromArgs` is the same, for one of many runs sharing a process
11
+ // (main/cloud/host.mjs).
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import { cloudRun, IMPORT_FAILED, LOGIN_TROUBLE } from './run.mjs';
15
+ import { NEW_THREAD, NEW_THREAD_CODEX } from '../../shared/cloud-kit.mjs';
16
+ import { cloudClients } from './clients.mjs';
17
+ import { readCloudConfig, readCloudKey, imageCaps, noteImageCaps } from './config.mjs';
18
+ import { pickCloudLogin, markCloudLogin, loginObjectKey, cloudAccountsFrom, cloudAccountFrom, cloudLoginInUse, noCloudAccountSentence } from './logins.mjs';
19
+
20
+ // SIGNED AS THE CLOUD RUNS' OWN USER AND NOBODY ELSE. AWS's libraries read
21
+ // keys from the environment before they read a named profile, so an app
22
+ // started from a shell signed in as an admin would run every cloud call as
23
+ // that admin. Those keys go, and the `agentbox-cloud` profile is what signs.
24
+ export function dropAwsSignIns(env = process.env) {
25
+ for (const k of Object.keys(env)) {
26
+ if (/^AWS_(ACCESS_KEY_ID|SECRET_ACCESS_KEY|SESSION_TOKEN|SECURITY_TOKEN|PROFILE|DEFAULT_PROFILE|CREDENTIAL_EXPIRATION|WEB_IDENTITY_TOKEN_FILE|ROLE_ARN)$/.test(k)) delete env[k];
27
+ }
28
+ }
29
+
30
+ /**
31
+ * The other sign-ins a run carries besides its own, so the agent can call the
32
+ * other tool there the way it can on their Mac. A Claude run carries the Codex
33
+ * account a Codex run would use, when it is ready; a Codex run carries nothing,
34
+ * because Claude's token is long-lived and Codex's is not. Named only, like the
35
+ * run's own.
36
+ */
37
+ export function loginsCarriedBy(engine, { only = null, prefer = null, home } = {}) {
38
+ if (engine !== 'claude') return [];
39
+ const codex = pickCloudLogin('codex', { only, prefer, ...(home ? { home } : {}) });
40
+ return codex ? [{ engine: 'codex', id: codex.id, key: loginObjectKey('codex', codex.id) }] : [];
41
+ }
42
+
43
+ /**
44
+ * The cloud account a run of `engine` takes, from the run's own environment:
45
+ * the test copy's list and the account this Mac runs on. With none to take,
46
+ * `trouble` is why.
47
+ */
48
+ export function loginForCloudRun(engine, env, { home, now = Date.now() } = {}) {
49
+ const ask = { only: cloudAccountsFrom(env), prefer: cloudAccountFrom(env, engine), now, ...(home ? { home } : {}) };
50
+ const rec = pickCloudLogin(engine, ask);
51
+ return { rec, trouble: rec ? null : noCloudAccountSentence(engine, cloudLoginInUse(engine, ask), { now }) };
52
+ }
53
+
54
+ // One client pair per account and cloud key, shared by every run in a process.
55
+ const clients = new Map();
56
+ function clientsFor(config) {
57
+ const k = JSON.stringify([config, typeof config.gateway === 'string' ? readCloudKey() : null]);
58
+ if (!clients.has(k)) clients.set(k, cloudClients(config).catch((err) => { clients.delete(k); throw err; }));
59
+ return clients.get(k);
60
+ }
61
+
62
+ /** One cloud run from the relay's own arguments; resolves to its exit code. */
63
+ export async function cloudRunFromArgs({ args, env, cwd, stdin, stdout, stderr, signals, pid, watchParent }) {
64
+ const say = (m) => stderr.write(`agentbox cloud: ${m}\n`);
65
+ const config = readCloudConfig();
66
+ if (!config) { say('cloud runs are not set up on this Mac (run scripts/cloud-setup.mjs)'); return 1; }
67
+ // `--agentbox-import-only`: bring an earlier run in this folder home, and
68
+ // start nothing (Supervisor#_bringCloudHome).
69
+ // `--agentbox-codex app-server ...`: a Codex run, its own `codex app-server`
70
+ // in the sandbox, on the Codex login CODEX_HOME names.
71
+ // `--agentbox-kit <claude's argv>`: leave this thread's next turn ready
72
+ // for the cloud server to start with the computer shut, and start nothing
73
+ // (shared/cloud-kit.mjs, Supervisor#_writeKit).
74
+ const importOnly = args[0] === '--agentbox-import-only';
75
+ const kitOnly = args[0] === '--agentbox-kit';
76
+ // A kit for a Codex thread: `--agentbox-kit --agentbox-codex app-server`.
77
+ const codex = args[0] === '--agentbox-codex' || (kitOnly && args[1] === '--agentbox-codex');
78
+ const engine = codex ? 'codex' : 'claude';
79
+ // The cloud account it runs on (main/cloud/logins.mjs pickCloudLogin). The
80
+ // folder below only says where the conversation is kept.
81
+ const { rec, trouble } = importOnly ? { rec: null, trouble: null } : loginForCloudRun(engine, env);
82
+ if (!importOnly && !rec) { say(trouble); return LOGIN_TROUBLE; }
83
+ const dir = rec?.key;
84
+ let made;
85
+ try { made = await clientsFor(config); } catch (err) { say(err.message); return LOGIN_TROUBLE; }
86
+ const { transport, storage } = made;
87
+ let kit = null;
88
+ if (kitOnly) {
89
+ kit = kitFrom(env);
90
+ if (!kit) { say('no kit: the thread, its project, this computer or the reply\'s place was not named'); return 1; }
91
+ }
92
+ return cloudRun({
93
+ importOnly,
94
+ kit,
95
+ engine,
96
+ login: rec ? { engine, id: rec.id, key: loginObjectKey(engine, rec.id) } : null,
97
+ also: rec ? loginsCarriedBy(engine, { only: cloudAccountsFrom(env), prefer: cloudAccountFrom(env, 'codex') }) : [],
98
+ // A missing or refused login is marked so the menu asks for a new sign-in;
99
+ // an expired Codex one is left for the supervisor to renew.
100
+ onLoginTrouble: ({ state, id }) => {
101
+ if (state === 'expired') markCloudLogin(engine, dir, id, { expiresAt: Date.now() });
102
+ else markCloudLogin(engine, dir, id, { broken: state });
103
+ },
104
+ // An account at its usage limit sits out until it resets; the next run
105
+ // takes another cloud account, or this Mac.
106
+ onLimit: ({ until }) => { if (rec) markCloudLogin(engine, dir, rec.id, { limitedUntil: until }); },
107
+ imageCaps: imageCaps(config),
108
+ onCaps: (caps) => noteImageCaps(config, caps),
109
+ remoteCommand: codex ? 'codex' : 'claude',
110
+ argv: importOnly ? [] : args.slice((codex ? 1 : 0) + (kitOnly ? 1 : 0)),
111
+ env, cwd, transport, storage,
112
+ ...(stdin ? { stdin, stdout, stderr } : {}),
113
+ ...(signals ? { signals } : {}),
114
+ ...(pid ? { pid } : {}),
115
+ ...(watchParent !== undefined ? { watchParent } : {}),
116
+ });
117
+ }
118
+
119
+ // What the supervisor names for a kit: the thread, its project, this computer
120
+ // as the phone knows it, the reply's place (a file, read once) and where the
121
+ // copy is kept here.
122
+ export function kitFrom(env) {
123
+ const device = env.AGENTBOX_KIT_DEVICE;
124
+ const project = env.ZERO_PRODUCT;
125
+ const item = env.ZERO_ITEM;
126
+ if (!/^[0-9a-f]{12}$/.test(device || '') || !project || !item || !env.AGENTBOX_KIT_FRAME) return null;
127
+ let frame = null;
128
+ try { frame = JSON.parse(fs.readFileSync(env.AGENTBOX_KIT_FRAME, 'utf8')); } catch { return null; }
129
+ fs.rmSync(env.AGENTBOX_KIT_FRAME, { force: true });
130
+ if (typeof frame?.before !== 'string' || typeof frame?.after !== 'string') return null;
131
+ const codex = env.AGENTBOX_KIT_CODEX ? codexKitAsked(env.AGENTBOX_KIT_CODEX) : undefined;
132
+ if (codex === null) return null;
133
+ const newThread = env.AGENTBOX_KIT_NEW ? newThreadAsked(env.AGENTBOX_KIT_NEW) : undefined;
134
+ if (newThread === null) return null;
135
+ return {
136
+ device, project, item: newThread ? (codex ? NEW_THREAD_CODEX : NEW_THREAD) : item, frame: { before: frame.before, after: frame.after }, copy: env.AGENTBOX_KIT_COPY || null,
137
+ ...(codex ? { codex } : {}), ...(newThread ? { newThread } : {}),
138
+ };
139
+ }
140
+
141
+ // A project's kit for a new thread (Supervisor#_leaveNewThreadKit) names where
142
+ // the thread's folder will be, its first prompt with the marks, and the model
143
+ // and effort its command line runs, in a file read once.
144
+ function newThreadAsked(file) {
145
+ let n = null;
146
+ try { n = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; }
147
+ fs.rmSync(file, { force: true });
148
+ if (typeof n?.cwd !== 'string' || !path.isAbsolute(n.cwd) || typeof n.brief !== 'string') return null;
149
+ return {
150
+ cwd: n.cwd, brief: n.brief, model: typeof n.model === 'string' ? n.model : null, effort: typeof n.effort === 'string' ? n.effort : null,
151
+ // The engine the project gives a new thread nobody chose one for.
152
+ ...(n.defaultEngine === 'claude' || n.defaultEngine === 'codex' ? { defaultEngine: n.defaultEngine } : {}),
153
+ };
154
+ }
155
+
156
+ // A Codex thread's kit also names the thread to resume and its turn's
157
+ // settings (Supervisor#_writeKit), in a file read once; a new thread's names
158
+ // the thread to start instead (Supervisor#_leaveCodexNewThreadKit).
159
+ function codexKitAsked(file) {
160
+ let asked = null;
161
+ try { asked = JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; }
162
+ fs.rmSync(file, { force: true });
163
+ if (asked?.start && typeof asked.start === 'object' && asked.turn && typeof asked.turn === 'object' && asked.turn.threadId === undefined) return { start: asked.start, turn: asked.turn };
164
+ if (typeof asked?.resume?.threadId !== 'string' || typeof asked?.turn?.threadId !== 'string') return null;
165
+ return { resume: asked.resume, turn: asked.turn };
166
+ }
167
+
168
+ // Run as a program, not imported by the host: by the file's name, which holds
169
+ // inside the packaged app's archive as well as out of it.
170
+ const startedAsProgram = path.basename(process.argv[1] || '') === 'run-cli.mjs';
171
+ if (startedAsProgram) {
172
+ dropAwsSignIns();
173
+ cloudRunFromArgs({ args: process.argv.slice(2), env: process.env, cwd: process.cwd(), stdout: process.stdout, stderr: process.stderr }).then(
174
+ (code) => process.exit(code ?? 1),
175
+ (err) => { process.stderr.write(`agentbox cloud: stopped: ${err?.message || err}\n`); process.exit(IMPORT_FAILED); },
176
+ );
177
+ }