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
@@ -1,3 +1,4 @@
1
+ import { harnessFor } from './harnesses.mjs';
1
2
  // SETTING UP SOMEBODY'S PLAN FOR THEM (w-9f6975906c, picked 2026-10-05).
2
3
  //
3
4
  // A person with a Claude or ChatGPT plan and no Claude Code or Codex used to
@@ -25,28 +26,16 @@ import { spawn as nodeSpawn } from 'node:child_process';
25
26
  import { terminalEnv } from './task-terminals.mjs';
26
27
  import { codexLaunchEnv } from './codex-launch-env.mjs';
27
28
 
28
- const INSTALLERS = {
29
- claude: { shell: '/bin/bash', url: 'https://claude.ai/install.sh', pipe: 'bash' },
30
- codex: { shell: '/bin/sh', url: 'https://chatgpt.com/codex/install.sh', pipe: 'sh' },
31
- };
32
-
33
- export function installCommand(engine) {
34
- const i = INSTALLERS[engine];
35
- return { file: i.shell, args: ['-c', `curl -fsSL ${i.url} | ${i.pipe}`] };
36
- }
29
+ export function installCommand(engine) { return harnessFor(engine).install(); }
37
30
 
38
31
  /** The plan, never the API console: `--claudeai` is Claude Code's own name for it. */
39
32
  export function signInCommand(engine, bin) {
40
- return engine === 'claude'
41
- ? { file: bin, args: ['auth', 'login', '--claudeai'] }
42
- : { file: bin, args: ['login'] };
33
+ return harnessFor(engine).signIn(bin);
43
34
  }
44
35
 
45
36
  /** Exit 0 is signed in and 1 is not, for both (measured 2026-10-05). */
46
37
  export function statusCommand(engine, bin) {
47
- return engine === 'claude'
48
- ? { file: bin, args: ['auth', 'status', '--json'] }
49
- : { file: bin, args: ['login', 'status'] };
38
+ return harnessFor(engine).signInStatus(bin);
50
39
  }
51
40
 
52
41
  const LOG_LIMIT = 64 * 1024;
@@ -77,10 +66,10 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
77
66
  /**
78
67
  * One setup per tool, held for the life of the app.
79
68
  *
80
- * `find(engine)` is the finder, looking again from scratch (the caller passes
81
- * `recheckClaude` / `recheckCodex`, which also move the config so the rest of
82
- * the app runs on what was just installed). `spawn` and `wait` are seams for
83
- * the tests; nothing in the app passes them.
69
+ * `find(engine)` is the finder, looking again from scratch, and may answer
70
+ * with a promise (the caller passes `lookAgainFor`, which also moves the
71
+ * config so the rest of the app runs on what was just installed). `spawn` and
72
+ * `wait` are seams for the tests; nothing in the app passes them.
84
73
  */
85
74
  export function createEngineSetup({ find, spawn = realSpawn, wait = sleep, env = process.env, poll = POLL, onSignIn = () => {} } = {}) {
86
75
  const runEnv = terminalEnv(env);
@@ -120,7 +109,11 @@ export function createEngineSetup({ find, spawn = realSpawn, wait = sleep, env =
120
109
 
121
110
  async function go(j, run) {
122
111
  const live = () => j.run === run;
123
- let hit = find(j.engine);
112
+ let hit = await find(j.engine);
113
+ if (!live()) return;
114
+ // The card's first row says Found or Install off this, because it ticks
115
+ // that row either way.
116
+ j.already = !!hit?.found;
124
117
  if (!hit?.found) {
125
118
  j.phase = 'installing';
126
119
  const { file, args } = installCommand(j.engine);
@@ -130,7 +123,7 @@ export function createEngineSetup({ find, spawn = realSpawn, wait = sleep, env =
130
123
  j.running = null;
131
124
  if (!live()) return;
132
125
  if (code !== 0) return fail(j, `The installer stopped: ${lastLine(j.log) || `exit ${code}`}`);
133
- hit = find(j.engine);
126
+ hit = await find(j.engine);
134
127
  if (!hit?.found) return fail(j, 'The installer finished, but the app still cannot find it.');
135
128
  }
136
129
  j.bin = hit.path;
@@ -151,7 +144,15 @@ export function createEngineSetup({ find, spawn = realSpawn, wait = sleep, env =
151
144
  ended = false;
152
145
  watchLogin(login);
153
146
  }
154
- if (ended) return fail(j, `The sign-in was closed before it finished.${lastLine(j.log) ? ` ${lastLine(j.log)}` : ''}`);
147
+ // ASK ONCE MORE BEFORE CALLING IT CLOSED. A sign-in that finishes while
148
+ // the check above is running exits before that check answers, and the
149
+ // check, asked a moment too early, says no. Without this, somebody who
150
+ // had just approved it was told it was closed.
151
+ if (ended) {
152
+ if (await signedIn(j, hit.path)) { if (live()) ready(j); return; }
153
+ if (!live()) return;
154
+ return fail(j, `The sign-in was closed before it finished.${lastLine(j.log) ? ` ${lastLine(j.log)}` : ''}`);
155
+ }
155
156
  }
156
157
  }
157
158
 
@@ -183,14 +184,14 @@ export function createEngineSetup({ find, spawn = realSpawn, wait = sleep, env =
183
184
  },
184
185
  status(engine) {
185
186
  const j = job(engine);
186
- return { engine, phase: j.phase, error: j.error, log: j.log };
187
+ return { engine, phase: j.phase, error: j.error, log: j.log, bin: j.bin ?? null, already: !!j.already };
187
188
  },
188
189
  /**
189
190
  * WHETHER THIS TOOL CAN RUN AN AGENT RIGHT NOW: found, and signed in by its
190
191
  * own account. The walk asks before it asks anything, so a Mac that is
191
192
  * already set up never sees the plan question. */
192
193
  async readiness(engine) {
193
- const hit = find(engine);
194
+ const hit = await find(engine);
194
195
  if (!hit?.found) return { engine, found: false, signedIn: false };
195
196
  try { return { engine, found: true, signedIn: await signedIn({ engine, log: '' }, hit.path) }; }
196
197
  catch (error) { return { engine, found: true, signedIn: false, error: error.message }; }
package/main/feedback.mjs CHANGED
@@ -5,22 +5,28 @@
5
5
  // to as a secret (`FEEDBACK_TO`, cloud/supabase/functions/feedback), so the
6
6
  // address is in no file of this public repository and no install can show it.
7
7
  //
8
- // WHERE IT GOES IS BAKED IN, like the analytics key (main/analytics.mjs): the
9
- // release writes `bakedFeedbackUrl` into the packaged package.json, a copy run
10
- // from source carries none, and a `feedbackUrl` in zero.config.json outranks
11
- // both. A copy with nowhere to send says so in words rather than pretending.
8
+ // WHERE IT GOES SHIPS IN THE CODE (w-58e758c81e, 2026-10-09). It used to be
9
+ // baked only into the packaged Mac app, so the npm package and a git clone,
10
+ // the two installs the README offers, told everyone who pressed Send that
11
+ // feedback was not set up, and lost what they wrote. FEEDBACK_ADDRESS is the
12
+ // server function, not an inbox. A `feedbackUrl` in zero.config.json, the
13
+ // FEEDBACK_URL variable, or a `bakedFeedbackUrl` in a packaged package.json
14
+ // still outrank it.
12
15
  //
13
16
  // It is not behind the diagnostics switch. That switch is about what Agentbox
14
17
  // sends on its own; this is something a person typed and pressed Send on.
15
18
  import { createRequire } from 'node:module';
16
- import { checkFeedback } from '../shared/feedback.mjs';
17
- import { Name, readEnv } from '../shared/product-name.mjs';
19
+ import { readEnv } from '../shared/product-name.mjs';
20
+
21
+ export { sendFeedback } from '../shared/feedback.mjs';
22
+
23
+ export const FEEDBACK_ADDRESS = 'https://lywvthwkhkfzhqivvond.supabase.co/functions/v1/feedback';
18
24
 
19
25
  function bakedUrl() {
20
26
  try {
21
- return createRequire(import.meta.url)('../package.json')?.bakedFeedbackUrl ?? null;
27
+ return createRequire(import.meta.url)('../package.json')?.bakedFeedbackUrl ?? FEEDBACK_ADDRESS;
22
28
  } catch {
23
- return null;
29
+ return FEEDBACK_ADDRESS;
24
30
  }
25
31
  }
26
32
 
@@ -30,26 +36,3 @@ const clean = (v) => (typeof v === 'string' && v.trim() ? v.trim() : null);
30
36
  export function feedbackUrl(config, baked = BAKED, env = process.env) {
31
37
  return clean(config?.feedbackUrl) ?? clean(readEnv('FEEDBACK_URL', env)) ?? clean(baked);
32
38
  }
33
-
34
- // Answers { ok: true } or { ok: false, error } with the error as a sentence.
35
- // It never throws, so the card always has something to say.
36
- export async function sendFeedback({ url, payload, fetchImpl = globalThis.fetch, timeoutMs = 60_000 }) {
37
- if (!url) return { ok: false, error: `Feedback is not set up in this copy of ${Name}.` };
38
- // Checked again here, off the bytes that would actually travel, since the
39
- // renderer is not the last word on what leaves this machine.
40
- const sized = (payload?.files ?? []).map((f) => ({ size: Math.floor((String(f?.data ?? '').length * 3) / 4) }));
41
- const check = checkFeedback({ text: payload?.text, files: sized });
42
- if (!check.ok) return { ok: false, error: check.reason };
43
- try {
44
- const res = await fetchImpl(url, {
45
- method: 'POST',
46
- headers: { 'content-type': 'application/json' },
47
- body: JSON.stringify(payload),
48
- signal: AbortSignal.timeout(timeoutMs),
49
- });
50
- if (res?.ok) return { ok: true };
51
- return { ok: false, error: 'It did not send. Try again in a moment.' };
52
- } catch {
53
- return { ok: false, error: 'It did not send. Check your connection and try again.' };
54
- }
55
- }
@@ -17,10 +17,10 @@ import { servable } from './img-scheme.mjs';
17
17
 
18
18
  const PICTURE = /\.(png|jpe?g|gif|webp|avif)$/i;
19
19
 
20
- export function folderPictures({ src, product, products = [], accountRoot, storeRoot, limit = 60, fs = fsDefault }) {
20
+ export async function folderPictures({ src, product, products = [], accountRoot, storeRoot, limit = 60, fs = fsDefault }) {
21
21
  if (typeof src !== 'string' || !src.trim()) return { ok: false, error: 'No folder was named.' };
22
22
  const named = src.replace(/\/+$/, '');
23
- const found = resolveArtifact({ src: named, product, products, accountRoot, storeRoot, fs });
23
+ const found = await resolveArtifact({ src: named, product, products, accountRoot, storeRoot, fs });
24
24
  if (!found.ok) return { ok: false, error: found.error };
25
25
  const own = products.find((p) => p.slug === product) ?? null;
26
26
  const roots = [accountRoot, own?.dir, own?.repoPath].filter(Boolean);
@@ -0,0 +1,86 @@
1
+ // THE OTHER END OF main/freeze-recorder.mjs: the watcher, on its own thread.
2
+ //
3
+ // The profile is rotated every `rotateMs` so it never grows, and at the end of
4
+ // each freeze so the freeze is in the one it hands back. The one before is
5
+ // kept too, because a freeze can start before a rotation and end after it.
6
+ import { Session } from 'node:inspector';
7
+ import { parentPort, workerData } from 'node:worker_threads';
8
+ import { freezeFrom, writeFreeze } from './freeze-recorder.mjs';
9
+
10
+ const { beat, dir, appDir, thresholdMs, rotateMs, intervalUs } = workerData;
11
+ const stamp = new BigInt64Array(beat);
12
+ const nowUs = () => Number(process.hrtime.bigint() / 1000n);
13
+ // The main thread asleep rather than stuck (App Nap timers, say) has an idle
14
+ // profile. Below this share of busy samples it is not a freeze.
15
+ const BUSY_ENOUGH = 0.5;
16
+
17
+ const session = new Session();
18
+ const post = (method, params = {}) => new Promise((resolve, reject) => {
19
+ session.post(method, params, (err, out) => (err ? reject(err) : resolve(out)));
20
+ });
21
+
22
+ let running = null; // { anchorUs } of the profile being taken
23
+ let previous = null; // { profile, offsetUs } of the last one stopped
24
+ let frozeAt = null; // the last stamp before the freeze in progress, in µs
25
+ const seen = []; // freezes ended and not yet cut out of a profile
26
+ let busy = false;
27
+
28
+ async function start() {
29
+ const before = nowUs();
30
+ await post('Profiler.start');
31
+ running = { anchorUs: (before + nowUs()) / 2 };
32
+ }
33
+
34
+ async function rotate() {
35
+ const { profile } = await post('Profiler.stop');
36
+ const stopped = { profile, offsetUs: profile.startTime - running.anchorUs };
37
+ await start();
38
+ const profiles = [previous, stopped].filter(Boolean);
39
+ previous = stopped;
40
+ for (const f of seen.splice(0)) {
41
+ const read = freezeFrom(profiles, { fromUs: f.fromUs, toUs: f.toUs, appDir });
42
+ if (read.busy < BUSY_ENOUGH) continue;
43
+ const line = { at: new Date(Date.now() - (nowUs() - f.fromUs) / 1000).toISOString(), ms: Math.round((f.toUs - f.fromUs) / 1000), ...read };
44
+ writeFreeze(dir, line);
45
+ parentPort.postMessage({ freeze: { at: line.at, ms: line.ms, where: line.where } });
46
+ }
47
+ }
48
+
49
+ // A freeze that ends while a rotation is under way goes round again.
50
+ async function turn() {
51
+ if (busy || !running) return;
52
+ busy = true;
53
+ try {
54
+ do { await rotate(); } while (seen.length);
55
+ } catch (err) { quit(err); } finally { busy = false; }
56
+ }
57
+
58
+ function watch() {
59
+ const last = Number(Atomics.load(stamp, 0) / 1000n);
60
+ if (nowUs() - last > thresholdMs * 1000) { frozeAt ??= last; return; }
61
+ if (frozeAt === null) return;
62
+ seen.push({ fromUs: frozeAt, toUs: last });
63
+ frozeAt = null;
64
+ void turn();
65
+ }
66
+
67
+ let watching = null;
68
+ let rotating = null;
69
+ function quit(err) {
70
+ clearInterval(watching);
71
+ clearInterval(rotating);
72
+ try { session.disconnect(); } catch { /* already gone */ }
73
+ parentPort.postMessage({ gaveUp: String(err?.message ?? err) });
74
+ }
75
+
76
+ // The timers go first: an inspector reply does not keep this thread alive.
77
+ watching = setInterval(watch, 50);
78
+ rotating = setInterval(() => { if (frozeAt === null) void turn(); }, rotateMs);
79
+ try {
80
+ session.connectToMainThread();
81
+ await post('Profiler.enable');
82
+ await post('Profiler.setSamplingInterval', { interval: intervalUs });
83
+ await start();
84
+ } catch (err) {
85
+ quit(err);
86
+ }
@@ -0,0 +1,146 @@
1
+ // WHAT THE MAIN THREAD WAS DOING WHEN THE APP FROZE.
2
+ //
3
+ // The spinning wheel is this thread not answering, and it comes when nobody is
4
+ // profiling. So a thread of its own watches all the time: the main thread
5
+ // stamps a clock every 100 ms, the
6
+ // watcher keeps a low-rate CPU profile of it running through the inspector,
7
+ // and when the stamps stop for longer than `thresholdMs` it cuts that stretch
8
+ // out of the profile and writes one line saying which of our functions held
9
+ // it, to `freeze-<day>.jsonl` beside the memory log, kept a week.
10
+ //
11
+ // It stays on this computer. Nothing here is sent anywhere.
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { Worker } from 'node:worker_threads';
16
+
17
+ export const THRESHOLD_MS = 500;
18
+ export const KEEP_DAYS = 7;
19
+ const BEAT_MS = 100;
20
+ const FILE = /^freeze-(\d{4}-\d{2}-\d{2})\.jsonl$/;
21
+ const IDLE = new Set(['(idle)', '(program)']);
22
+ const GC = '(garbage collector)';
23
+ const FRAMES_KEPT = 12;
24
+
25
+ export const dayOf = (ms) => {
26
+ const d = new Date(ms);
27
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
28
+ };
29
+
30
+ function fileOf(url, appDirs) {
31
+ if (!url) return '';
32
+ let file = url;
33
+ if (url.startsWith('file://')) { try { file = fileURLToPath(url); } catch { /* keep the url */ } }
34
+ const appDir = appDirs.find((d) => file.startsWith(d + path.sep));
35
+ if (appDir) return file.slice(appDir.length + 1);
36
+ if (file.startsWith('node:') || !path.isAbsolute(file)) return file;
37
+ // Never a home path in the log: a file outside the app is named by its last parts.
38
+ return `…/${file.split(path.sep).slice(-2).join('/')}`;
39
+ }
40
+
41
+ // The profile names a file by its real path, which on macOS is /private/var for /var.
42
+ const realPath = (dir) => { try { return dir ? fs.realpathSync(dir) : ''; } catch { return ''; } };
43
+
44
+ const ours = (frame) => /\.(m?js|cjs|ts)$/.test(frame.file) && !/^(node:|…\/)/.test(frame.file) && !frame.file.includes('node_modules');
45
+ const named = (f) => (f.file ? `${f.fn} ${f.file}:${f.line}` : f.fn);
46
+
47
+ /**
48
+ * What held the thread between `fromUs` and `toUs`, read off one or more CPU
49
+ * profiles. Times are on the watcher's clock; `offsetUs` is each profile's
50
+ * clock minus that one. `appDir` is one path or several. Pure.
51
+ */
52
+ export function freezeFrom(profiles, { fromUs, toUs, appDir = '' }) {
53
+ const appDirs = [appDir].flat().filter(Boolean);
54
+ const groups = new Map();
55
+ let total = 0;
56
+ let idle = 0;
57
+ let gc = 0;
58
+ for (const { profile, offsetUs = 0 } of profiles) {
59
+ if (!profile?.nodes?.length || !profile.samples?.length) continue;
60
+ const byId = new Map(profile.nodes.map((n) => [n.id, n]));
61
+ const parent = new Map();
62
+ for (const n of profile.nodes) for (const c of n.children ?? []) parent.set(c, n.id);
63
+ const stacks = new Map();
64
+ const stackOf = (id) => {
65
+ if (stacks.has(id)) return stacks.get(id);
66
+ const frames = [];
67
+ for (let at = id; at != null; at = parent.get(at)) {
68
+ const cf = byId.get(at)?.callFrame;
69
+ if (!cf || cf.functionName === '(root)') continue;
70
+ frames.push({ fn: cf.functionName || '(anonymous)', file: fileOf(cf.url, appDirs), line: cf.url ? cf.lineNumber + 1 : 0 });
71
+ }
72
+ const leaf = frames[0]?.fn ?? '(program)';
73
+ const kind = frames.length <= 1 && IDLE.has(leaf) ? 'idle' : leaf === GC ? 'gc' : 'busy';
74
+ const first = frames.findIndex(ours);
75
+ const mine = first < 0 ? [] : frames.slice(first);
76
+ const s = { kind, frames, where: mine[0] ? named(mine[0]) : leaf, key: `${leaf}|${mine.slice(0, 10).map(named).join('<')}` };
77
+ stacks.set(id, s);
78
+ return s;
79
+ };
80
+ let t = profile.startTime - offsetUs;
81
+ for (let i = 0; i < profile.samples.length; i += 1) {
82
+ t += profile.timeDeltas?.[i] ?? 0;
83
+ if (t < fromUs || t > toUs) continue;
84
+ total += 1;
85
+ const s = stackOf(profile.samples[i]);
86
+ if (s.kind === 'idle') { idle += 1; continue; }
87
+ if (s.kind === 'gc') gc += 1;
88
+ const g = groups.get(s.key) ?? { samples: 0, s };
89
+ g.samples += 1;
90
+ groups.set(s.key, g);
91
+ }
92
+ }
93
+ const busy = total - idle;
94
+ const top = [...groups.values()].sort((a, b) => b.samples - a.samples).slice(0, 3);
95
+ return {
96
+ samples: total,
97
+ busy: total ? Math.round((busy / total) * 100) / 100 : 0,
98
+ gc: busy ? Math.round((gc / busy) * 100) / 100 : 0,
99
+ where: top[0]?.s.where ?? null,
100
+ stacks: top.map((g) => ({ share: Math.round((g.samples / busy) * 100) / 100, frames: g.s.frames.slice(0, FRAMES_KEPT).map(named) })),
101
+ };
102
+ }
103
+
104
+ /** One freeze's line, appended to today's file; older files than a week go. */
105
+ export function writeFreeze(dir, line, { keepDays = KEEP_DAYS } = {}) {
106
+ fs.mkdirSync(dir, { recursive: true });
107
+ const at = Date.parse(line.at) || Date.now();
108
+ fs.appendFileSync(path.join(dir, `freeze-${dayOf(at)}.jsonl`), `${JSON.stringify(line)}\n`);
109
+ const oldest = dayOf(new Date(at).setDate(new Date(at).getDate() - (keepDays - 1)));
110
+ for (const f of fs.readdirSync(dir)) {
111
+ const m = f.match(FILE);
112
+ if (m && m[1] < oldest) fs.rmSync(path.join(dir, f), { force: true });
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Starts the watcher for this thread. `onFreeze` hears each line as it is
118
+ * written. Never throws: a recorder that breaks the app is worse than a gap.
119
+ */
120
+ export function startFreezeRecorder({ dir, appDir = '', thresholdMs = THRESHOLD_MS, rotateMs = 60_000, intervalUs = 10_000, onFreeze = () => {} } = {}) {
121
+ const beat = new SharedArrayBuffer(8);
122
+ const stamp = new BigInt64Array(beat);
123
+ const tick = () => Atomics.store(stamp, 0, process.hrtime.bigint());
124
+ tick();
125
+ const timer = setInterval(tick, BEAT_MS);
126
+ timer.unref?.();
127
+ let worker = null;
128
+ try {
129
+ worker = new Worker(new URL('./freeze-recorder-thread.mjs', import.meta.url), {
130
+ workerData: { beat, dir, appDir: [appDir, realPath(appDir)], thresholdMs, rotateMs, intervalUs },
131
+ });
132
+ } catch (err) {
133
+ clearInterval(timer);
134
+ console.warn(`zero: freezes will not be recorded: ${err.message}`);
135
+ return { stop() {} };
136
+ }
137
+ worker.unref();
138
+ worker.on('message', (m) => {
139
+ if (m?.freeze) { try { onFreeze(m.freeze); } catch { /* the line is already on disk */ } }
140
+ if (m?.gaveUp) console.warn(`zero: freezes will not be recorded: ${m.gaveUp}`);
141
+ });
142
+ worker.on('error', (err) => { clearInterval(timer); console.warn(`zero: the freeze recorder stopped: ${err.message}`); });
143
+ return {
144
+ stop() { clearInterval(timer); worker?.terminate().catch(() => {}); },
145
+ };
146
+ }
@@ -44,6 +44,22 @@ export function relaunchCommand({ execPath = process.execPath, argv = process.ar
44
44
  return { bin: execPath, args: appPath ? [appPath] : [] };
45
45
  }
46
46
 
47
+ /**
48
+ * THE NOTES ARE THE HONEST PART. Each is something a fresh copy can be missing
49
+ * without looking broken, and a copy that quietly cannot run an agent is one
50
+ * that lies, so they are said rather than assumed.
51
+ *
52
+ * Pure, and reads nothing, because what it says about a missing Claude Code
53
+ * otherwise depends on whether the computer running the test has one.
54
+ */
55
+ export function freshUserNotes({ claudeBin, keychain, agents } = {}, { withTools = true, withAgents = true } = {}) {
56
+ const notes = [];
57
+ if (withTools && !claudeBin) notes.push('That copy has no Claude Code of its own, so its setup offers to set one up or to use ChatGPT.');
58
+ if (!keychain) notes.push('There is no ~/Library/Keychains here, so Claude Code will say it is not logged in.');
59
+ if (withAgents && !agents) notes.push('There is no ~/.claude on this computer, so it will have no agents to offer.');
60
+ return notes;
61
+ }
62
+
47
63
  /**
48
64
  * Open one. Returns what happened, in the words the app will show her: this
49
65
  * is reported to the window rather than thrown, because a row in ⌘K that
@@ -90,15 +106,10 @@ export function openFreshUser({
90
106
  return { ok: false, error: `Could not start a second ${NAME}: ${err?.message ?? err}`, home: prepared.home };
91
107
  }
92
108
 
93
- // THE NOTES ARE THE HONEST PART. Both of these are things a fresh copy can
94
- // be missing without looking broken, and a test that quietly cannot run an
95
- // agent is a test that lies. They are said rather than assumed.
96
- const notes = [];
97
- if (withTools && !prepared.claudeBin) notes.push('Claude Code is not at ~/.local/bin/claude, so agents will not run in it.');
98
- if (!prepared.keychain) notes.push('There is no ~/Library/Keychains here, so Claude Code will say it is not logged in.');
99
- if (withAgents && !prepared.agents) notes.push('There is no ~/.claude on this computer, so it will have no agents to offer.');
100
-
101
- return { ok: true, home: prepared.home, agents: prepared.agents, notes, pid: child?.pid ?? null };
109
+ return {
110
+ ok: true, home: prepared.home, agents: prepared.agents, pid: child?.pid ?? null,
111
+ notes: freshUserNotes(prepared, { withTools, withAgents }),
112
+ };
102
113
  }
103
114
 
104
115
  /**
@@ -9,9 +9,11 @@
9
9
  // runs whole inside a worker thread, and if the thread cannot start the job
10
10
  // runs here, the old way.
11
11
  import { Worker } from 'node:worker_threads';
12
- import * as change from './git-change.mjs';
12
+ import * as git from './git-change.mjs';
13
+ import { saveRunChange } from './run-change.mjs';
13
14
 
14
- const JOBS = new Set(['snapshotRepo']);
15
+ const change = { ...git, saveRunChange };
16
+ const JOBS = new Set(['snapshotRepo', 'saveRunChange']);
15
17
 
16
18
  let worker = null;
17
19
  let broken = false;
@@ -55,6 +57,11 @@ function thread() {
55
57
  return worker;
56
58
  }
57
59
 
60
+ /** Settles once no job is waiting on the thread. */
61
+ export async function __gitJobsIdle() {
62
+ while (waiting.size) await new Promise((resolve) => { setTimeout(resolve, 10); });
63
+ }
64
+
58
65
  /** Run one of git-change.mjs's exports without blocking this thread. */
59
66
  export function gitJob(name, ...args) {
60
67
  if (!JOBS.has(name)) return Promise.reject(new Error(`not a git job: ${name}`));
@@ -65,6 +72,13 @@ export function gitJob(name, ...args) {
65
72
  const id = ++seq;
66
73
  waiting.set(id, job);
67
74
  w.ref();
68
- w.postMessage({ id, name, args });
75
+ try {
76
+ w.postMessage({ id, name, args });
77
+ } catch (error) {
78
+ // Arguments a thread cannot be handed (a function, say) run here.
79
+ waiting.delete(id);
80
+ if (!waiting.size) w.unref();
81
+ runHere(job);
82
+ }
69
83
  });
70
84
  }
@@ -1,10 +1,13 @@
1
1
  // THE OTHER END OF main/git-change-offthread.mjs. One job at a time, in order.
2
2
  import { parentPort } from 'node:worker_threads';
3
- import * as change from './git-change.mjs';
3
+ import * as git from './git-change.mjs';
4
+ import { saveRunChange } from './run-change.mjs';
5
+
6
+ const jobs = { ...git, saveRunChange };
4
7
 
5
8
  parentPort.on('message', ({ id, name, args }) => {
6
9
  try {
7
- parentPort.postMessage({ id, ok: true, value: change[name](...args) });
10
+ parentPort.postMessage({ id, ok: true, value: jobs[name](...args) });
8
11
  } catch (error) {
9
12
  parentPort.postMessage({ id, ok: false, error: String(error?.message ?? error) });
10
13
  }