agentbox-app 0.1.11 → 0.1.13

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 (239) hide show
  1. package/README.md +24 -16
  2. package/bin/agentbox.mjs +18 -9
  3. package/briefs/message-rules.md +15 -4
  4. package/main/account-tooling.mjs +2 -1
  5. package/main/acp.mjs +296 -0
  6. package/main/agent-homes.mjs +158 -0
  7. package/main/agent-sessions.mjs +57 -20
  8. package/main/agent-updates.mjs +9 -20
  9. package/main/analytics.mjs +30 -0
  10. package/main/approvals.mjs +14 -5
  11. package/main/claude-bin.mjs +45 -310
  12. package/main/claude-input.mjs +9 -3
  13. package/main/claude-launch-env.mjs +54 -0
  14. package/main/claude-models.mjs +39 -7
  15. package/main/claude-plan.mjs +14 -2
  16. package/main/claude-usage.mjs +82 -25
  17. package/main/codex-bin.mjs +51 -248
  18. package/main/codex-launch-env.mjs +31 -0
  19. package/main/codex-session.mjs +38 -3
  20. package/main/codex-threads.mjs +41 -8
  21. package/main/codex-watch.mjs +67 -3
  22. package/main/config.mjs +40 -13
  23. package/main/corner-tag.mjs +489 -0
  24. package/main/crash-report.mjs +26 -3
  25. package/main/demo.mjs +17 -14
  26. package/main/engine-bin.mjs +595 -0
  27. package/main/engine-setup.mjs +27 -27
  28. package/main/feedback.mjs +11 -6
  29. package/main/fresh-user.mjs +1 -1
  30. package/main/git-change.mjs +8 -2
  31. package/main/harnesses/claude-stream.mjs +208 -0
  32. package/main/harnesses/claude.mjs +84 -0
  33. package/main/harnesses/codex.mjs +65 -0
  34. package/main/harnesses/common.mjs +78 -0
  35. package/main/harnesses/instructions-file.mjs +116 -0
  36. package/main/harnesses/opencode.mjs +234 -0
  37. package/main/harnesses.mjs +65 -0
  38. package/main/importable-threads.mjs +38 -0
  39. package/main/instruction-settings.mjs +47 -11
  40. package/main/ipc.mjs +175 -22
  41. package/main/local-app-frames.mjs +82 -0
  42. package/main/machine.mjs +1 -1
  43. package/main/main.mjs +120 -5
  44. package/main/memory-gate-history.mjs +51 -12
  45. package/main/memory-gate-server.mjs +67 -11
  46. package/main/memory-gate.mjs +49 -8
  47. package/main/memory-log.mjs +124 -0
  48. package/main/notify.mjs +48 -5
  49. package/main/opencode-server.mjs +271 -0
  50. package/main/opencode.mjs +287 -0
  51. package/main/phone-link.mjs +551 -0
  52. package/main/refresh-agent-models.mjs +2 -1
  53. package/main/relay-address.mjs +28 -0
  54. package/main/relay-client.mjs +246 -0
  55. package/main/row-label.mjs +8 -7
  56. package/main/runs-on.mjs +90 -0
  57. package/main/serve.mjs +124 -10
  58. package/main/settings.mjs +171 -11
  59. package/main/shell-path.mjs +86 -0
  60. package/main/ship-queue.mjs +25 -7
  61. package/main/sign-in-files.mjs +3 -7
  62. package/main/source-updater.mjs +27 -1
  63. package/main/start-hold.mjs +73 -0
  64. package/main/store/home.mjs +37 -5
  65. package/main/store/project.mjs +15 -19
  66. package/main/store.mjs +10 -4
  67. package/main/supervisor.mjs +1030 -447
  68. package/main/task-commands.mjs +13 -17
  69. package/main/task-folders-offthread.mjs +6 -1
  70. package/main/task-folders.mjs +491 -82
  71. package/main/task-terminals.mjs +49 -8
  72. package/main/team/index.mjs +2 -2
  73. package/main/team/projects.mjs +1 -1
  74. package/main/updater.mjs +71 -9
  75. package/main/worktree-include.mjs +494 -0
  76. package/mcp/core/page.mjs +1 -1
  77. package/mcp/core/work.mjs +34 -3
  78. package/mcp/tools.mjs +2 -1
  79. package/package.json +10 -1
  80. package/renderer/dist/assets/abnfDiagram-YUKMZFIV-DPPuMWt-.js +1 -0
  81. package/renderer/dist/assets/arc-HmFfdseH.js +1 -0
  82. package/renderer/dist/assets/architectureDiagram-47P4ROYG-DxJ3zHrH.js +36 -0
  83. package/renderer/dist/assets/blockDiagram-E7TT5TSB-CbTXzOqQ.js +140 -0
  84. package/renderer/dist/assets/c4Diagram-CRA5TL53-X5ItFyLn.js +38 -0
  85. package/renderer/dist/assets/channel-BlT2g9Sz.js +1 -0
  86. package/renderer/dist/assets/chunk-2Q5K7J3B-DC12Ho_L.js +1 -0
  87. package/renderer/dist/assets/chunk-5VM5RSS4-ClVtfPd0.js +15 -0
  88. package/renderer/dist/assets/chunk-7TKQ45FW-DgcOm0HT.js +1 -0
  89. package/renderer/dist/assets/chunk-AW2ZBBNX-C01s4yoF.js +88 -0
  90. package/renderer/dist/assets/chunk-FVRAUYC3-Bj0zxhR4.js +2 -0
  91. package/renderer/dist/assets/chunk-HTAEGDNF-DWDeEhLU.js +1 -0
  92. package/renderer/dist/assets/chunk-JWPE2WC7-Dsqwv3eW.js +1 -0
  93. package/renderer/dist/assets/chunk-KQW6MTUR-CE86ANL2.js +1 -0
  94. package/renderer/dist/assets/chunk-XXDRQBXY-CoNTASfs.js +1 -0
  95. package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-2wprLqtw.js +217 -0
  96. package/renderer/dist/assets/cornerTag-BLrxiDE5.css +1 -0
  97. package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +1 -0
  98. package/renderer/dist/assets/cose-bilkent-JH36ORCC-enqMiYbn.js +1 -0
  99. package/renderer/dist/assets/cynefin-EF2NZ3EQ-DHngLxvQ.js +141 -0
  100. package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-C0rZZIl3.js +62 -0
  101. package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
  102. package/renderer/dist/assets/dagre-W4DXFKR2-DQnaEWO7.js +4 -0
  103. package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  104. package/renderer/dist/assets/diagram-2UJZ2QOL-CS8AlGPx.js +200 -0
  105. package/renderer/dist/assets/diagram-OVF4WLC6-yiDMHIkY.js +24 -0
  106. package/renderer/dist/assets/diagram-PFPMY2P6-XbQKzbBA.js +30 -0
  107. package/renderer/dist/assets/diagram-UMYRVEAY-dhVvlfl_.js +24 -0
  108. package/renderer/dist/assets/diagram-YEKJPTXX-DZHe8SCf.js +41 -0
  109. package/renderer/dist/assets/diagram-ZIFT7M5P-B5h6aXVR.js +3 -0
  110. package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-PR9ScDco.js +1 -0
  111. package/renderer/dist/assets/elk-IJKZMXRS-Cg-yQXPd.js +27 -0
  112. package/renderer/dist/assets/erDiagram-O2IAPWRE-omOrJMxu.js +99 -0
  113. package/renderer/dist/assets/flowDiagram-OXPTDLAJ-aOX2hzvT.js +213 -0
  114. package/renderer/dist/assets/ganttDiagram-R7TSEDQI-B1H3pWyI.js +292 -0
  115. package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-Cvw304HC.js +106 -0
  116. package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
  117. package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-_ZXcNViH.js +2 -0
  118. package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
  119. package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js +70 -0
  120. package/renderer/dist/assets/journeyDiagram-COXZFDF6-fr62QoAt.js +139 -0
  121. package/renderer/dist/assets/kanban-definition-P3RFRI5V-J8zsabCe.js +89 -0
  122. package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
  123. package/renderer/dist/assets/linear-BmNdEtIi.js +1 -0
  124. package/renderer/dist/assets/main-ChNdnUvt.js +1132 -0
  125. package/renderer/dist/assets/main-nj0QR5kg.css +32 -0
  126. package/renderer/dist/assets/mermaid.core-CnYzvFkz.js +314 -0
  127. package/renderer/dist/assets/mindmap-definition-LZFPQGKD-Bfcxc7PM.js +96 -0
  128. package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  129. package/renderer/dist/assets/pegDiagram-BGZESJAR-CwUJOq6X.js +1 -0
  130. package/renderer/dist/assets/pieDiagram-CAPJLFHJ-BrwMmqfC.js +39 -0
  131. package/renderer/dist/assets/product-name-CTKLYNWY.js +40 -0
  132. package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-BN4Stoj-.js +7 -0
  133. package/renderer/dist/assets/railroadDiagram-SM67HX2B-CPZ5bbeR.js +1 -0
  134. package/renderer/dist/assets/requirementDiagram-X7JNWC4A-CgRnimVe.js +84 -0
  135. package/renderer/dist/assets/sankeyDiagram-UM26HJRW-Bt2rej__.js +40 -0
  136. package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js +169 -0
  137. package/renderer/dist/assets/sizeCapture-INFHLROL-usyNyXyh.js +1 -0
  138. package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-CRzUBb0u.js +291 -0
  139. package/renderer/dist/assets/swimlanes-N4OXWK64-C1GJvMS0.js +1 -0
  140. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +8 -0
  141. package/renderer/dist/assets/timeline-definition-YOQAKGHF-caeyPlcc.js +120 -0
  142. package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-BN_SV6PC.js +370 -0
  143. package/renderer/dist/assets/vennDiagram-BLWOH2XV-BLGuaUX-.js +34 -0
  144. package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-Da0cJeNc.js +78 -0
  145. package/renderer/dist/assets/xychartDiagram-TAQBALBS-cGfUffMk.js +7 -0
  146. package/renderer/dist/corner-tag.html +13 -0
  147. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  148. package/renderer/dist/emulator/data/compression/README.md +11 -0
  149. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  150. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  151. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  152. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  153. package/renderer/dist/emulator/data/cores/README.md +22 -0
  154. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  155. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  156. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  157. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  158. package/renderer/dist/emulator/data/cores/package.json +65 -0
  159. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  160. package/renderer/dist/emulator/data/emulator.css +1614 -0
  161. package/renderer/dist/emulator/data/loader.js +170 -0
  162. package/renderer/dist/emulator/data/localization/README.md +63 -0
  163. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  164. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  165. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  166. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  167. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  168. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  169. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  170. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  171. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  172. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  173. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  174. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  175. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  176. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  177. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  178. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  179. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  180. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  181. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  182. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  183. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  184. package/renderer/dist/emulator/data/src/compression.js +142 -0
  185. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  186. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  187. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  188. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  189. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  190. package/renderer/dist/emulator/data/src/storage.js +125 -0
  191. package/renderer/dist/emulator/data/version.json +5 -0
  192. package/renderer/dist/emulator/host.js +297 -0
  193. package/renderer/dist/emulator/index.html +28 -0
  194. package/renderer/dist/emulator/provenance.json +6 -0
  195. package/renderer/dist/index.html +16 -3
  196. package/scripts/after-install.mjs +14 -0
  197. package/scripts/approval-server.sh +31 -7
  198. package/scripts/before-publish.mjs +12 -10
  199. package/shared/agent-import.mjs +3 -1
  200. package/shared/agents.d.mts +1 -0
  201. package/shared/bridge-map.mjs +19 -0
  202. package/shared/claude-commands.generated.mjs +8 -6
  203. package/shared/claude-models.generated.mjs +10 -8
  204. package/shared/codex-modes.mjs +1 -1
  205. package/shared/corner-tag.d.mts +14 -0
  206. package/shared/corner-tag.mjs +141 -0
  207. package/shared/demo-world.mjs +1 -1
  208. package/shared/engines.mjs +20 -5
  209. package/shared/fresh-user-home.mjs +12 -1
  210. package/shared/harness-definitions.mjs +103 -0
  211. package/shared/instruction-defaults.json +1 -1
  212. package/shared/notify-rules.d.mts +25 -0
  213. package/shared/notify-rules.mjs +19 -0
  214. package/shared/project-folder-check.mjs +3 -3
  215. package/shared/provider-commands.mjs +10 -2
  216. package/shared/rank.d.mts +2 -0
  217. package/shared/rank.mjs +50 -0
  218. package/shared/relay-address.mjs +6 -0
  219. package/shared/relay-crypto.mjs +165 -0
  220. package/shared/runs-on.d.mts +10 -0
  221. package/shared/runs-on.mjs +55 -0
  222. package/shared/shortcuts.mjs +11 -5
  223. package/shared/spawn-trouble.mjs +58 -7
  224. package/shared/terminal-input.d.mts +1 -0
  225. package/shared/terminal-input.mjs +30 -0
  226. package/shared/terminal-keys.d.mts +1 -0
  227. package/shared/terminal-keys.mjs +16 -0
  228. package/shared/terminal-size.d.mts +4 -0
  229. package/shared/terminal-size.mjs +29 -0
  230. package/shared/usage.d.mts +3 -0
  231. package/shared/usage.mjs +7 -0
  232. package/shared/where-it-runs-from.d.mts +43 -0
  233. package/shared/where-it-runs-from.mjs +155 -0
  234. package/shared/work-items.mjs +28 -2
  235. package/shared/work-lines.mjs +1 -1
  236. package/shared/xterm-invalid-range.d.mts +2 -0
  237. package/shared/xterm-invalid-range.mjs +58 -0
  238. package/renderer/dist/assets/index-B-2Az952.css +0 -32
  239. package/renderer/dist/assets/index-D6qncgai.js +0 -1168
@@ -27,6 +27,7 @@ import fs from 'node:fs';
27
27
  import os from 'node:os';
28
28
  import path from 'node:path';
29
29
  import { RECENT_DAYS, isScratchFolder, shortFolder, threadTitle } from './agent-sessions.mjs';
30
+ import { codexDataDirs } from './agent-homes.mjs';
30
31
  import { WAS_SLUGS, nameSlug } from '../shared/product-name.mjs';
31
32
 
32
33
  export const CODEX_RECENT_DAYS = RECENT_DAYS;
@@ -252,12 +253,40 @@ export const isLive = (mtime, now = Date.now()) => Number(mtime) > 0 && now - Nu
252
253
  /**
253
254
  * THE USER'S CONVERSATIONS, newest first, in the shape the import card and the
254
255
  * import row both read. One entry per thread a person typed into, in a real folder,
255
- * touched in the last `days` days. */
256
- export function readCodexThreads({ home = os.homedir(), codexDir = codexHome(home), days = CODEX_RECENT_DAYS, now = Date.now() } = {}) {
257
- const index = readCodexIndex(codexDir);
256
+ * touched in the last `days` days.
257
+ *
258
+ * EVERY CODEX FOLDER ON THE MACHINE, not only `~/.codex` (w-e46f2e9cea): a
259
+ * second login under `~/.codex-<name>` or a `CODEX_HOME` holds conversations
260
+ * the import never offered. `codexDirs` is the list main/agent-homes.mjs makes;
261
+ * `codexDir` still names exactly one folder for a caller that means one. The
262
+ * same conversation in two folders is one row.
263
+ *
264
+ * WHICH COPY, AND WHICH NAME, decided apart (Codex's review of this change,
265
+ * 2026-10-11). The copy kept is the transcript file written last, judged by
266
+ * the file alone: judging by `when`, which folds in the index's own date, let
267
+ * a stale copy whose folder's index was touched later beat the copy still
268
+ * being written. And the name Codex gave a conversation is read from every
269
+ * folder's index, newest entry winning, so the copy kept still carries a name
270
+ * that only another folder's index knew. */
271
+ export function readCodexThreads({ home = os.homedir(), codexDir = null, codexDirs = null, days = CODEX_RECENT_DAYS, now = Date.now() } = {}) {
272
+ const dirs = codexDirs ?? (codexDir ? [codexDir] : codexDataDirs({ home }));
258
273
  const cutoff = now - Math.max(0, Number(days) || 0) * 86400 * 1000;
259
- const out = [];
260
- const skipped = { notHers: 0, scratch: 0, old: 0, empty: 0, unreadable: 0 };
274
+ const index = new Map();
275
+ for (const dir of dirs) {
276
+ for (const [id, entry] of readCodexIndex(dir)) {
277
+ const had = index.get(id);
278
+ if (!had || (entry.name && (!had.name || entry.updatedAt > had.updatedAt))) index.set(id, entry);
279
+ }
280
+ }
281
+ const byId = new Map();
282
+ const skipped = { notHers: 0, scratch: 0, old: 0, empty: 0, unreadable: 0, copy: 0 };
283
+ for (const dir of dirs) readOneCodexHome(dir, { home, days, now, cutoff, index, byId, skipped });
284
+ const out = [...byId.values()].map(({ fileTime, ...t }) => t);
285
+ out.sort((a, b) => b.when - a.when);
286
+ return { threads: out, skipped };
287
+ }
288
+
289
+ function readOneCodexHome(codexDir, { home, days, now, cutoff, index, byId, skipped }) {
261
290
  for (const file of listRolloutFiles(codexDir, { days, since: cutoff })) {
262
291
  const r = readRollout(file);
263
292
  if (!r) { skipped.unreadable += 1; continue; }
@@ -268,7 +297,13 @@ export function readCodexThreads({ home = os.homedir(), codexDir = codexHome(hom
268
297
  if (when < cutoff) { skipped.old += 1; continue; }
269
298
  const title = named?.name || threadTitle([r.prompt]);
270
299
  if (!title) { skipped.empty += 1; continue; }
271
- out.push({
300
+ const had = byId.get(r.id);
301
+ if (had) {
302
+ skipped.copy += 1;
303
+ if (had.fileTime >= r.mtime) continue;
304
+ }
305
+ byId.set(r.id, {
306
+ fileTime: r.mtime,
272
307
  id: r.id,
273
308
  source: 'codex',
274
309
  folder: r.cwd,
@@ -286,6 +321,4 @@ export function readCodexThreads({ home = os.homedir(), codexDir = codexHome(hom
286
321
  live: isLive(r.mtime, now),
287
322
  });
288
323
  }
289
- out.sort((a, b) => b.when - a.when);
290
- return { threads: out, skipped };
291
324
  }
@@ -10,21 +10,85 @@
10
10
  // The founder, before launch: "after onboarding for both anthropic and openai
11
11
  // harnesses, it should only import on command, not continuously". Importing is
12
12
  // now the walk's last card and ⌘K import, for Claude Code and Codex alike.
13
+ //
14
+ // IT IS ALSO WHERE A CODEX AGENT IS NOTICED (w-1116fbb68a, 2026-10-08). It is
15
+ // the one thing in the app that already knows a person's Codex conversations,
16
+ // and it reads them on a timer rather than on the snapshot, which is what makes
17
+ // it the right place: see `noticeCodexAgents` below.
13
18
  import { readCodexThreads } from './codex-threads.mjs';
14
19
 
15
20
  export const CODEX_POLL_MS = 60_000;
16
21
 
17
- export function scanCodex({ store, readThreads = readCodexThreads, now = Date.now() } = {}) {
22
+ /**
23
+ * HOW LONG AFTER ITS LAST WRITE A CODEX THREAD IS STILL AN AGENT THAT WAS SEEN.
24
+ *
25
+ * A Claude Code session is a process, so `agent_seen` catches one that has sat
26
+ * open since earlier in the sitting (main/ipc.mjs, liveAgents). A Codex thread
27
+ * leaves no process behind: `isLive` is two minutes of file mtime, so counting
28
+ * only live threads would notice a Codex user ONLY when this watch's minute
29
+ * happened to land while they were typing. An hour is one sitting, which is
30
+ * the span the Claude read already covers without being asked to.
31
+ *
32
+ * AND IT IS NOT A DAY. The reader hands over ten days of conversations, and
33
+ * counting those would say every install with Codex on it had an agent up on
34
+ * every day the app was opened, which is the opposite of the under-count this
35
+ * was added to fix.
36
+ */
37
+ export const CODEX_SEEN_MS = 60 * 60_000;
38
+
39
+ /**
40
+ * "AN AGENT WAS SEEN", FOR CODEX TOO (w-1116fbb68a, 2026-10-08).
41
+ *
42
+ * `agent_seen` listed Claude Code processes and nothing else, and about a
43
+ * third of the people on this app are on Codex, so whether they had an agent
44
+ * at all was read off proxies: a run this app started, or a reply. The threads
45
+ * are already in hand once a minute for the mirror refresh, so this costs
46
+ * nothing beyond the comparison.
47
+ *
48
+ * NOTHING ABOUT A THREAD IS SENT. Not its folder, not its title, not its id,
49
+ * which is only ever compared inside this process. `engine` is the whole of
50
+ * the payload and it is one of two fixed words.
51
+ *
52
+ * ONCE PER THREAD PER RUN OF THE APP, for the reason the Claude side keeps a
53
+ * set: this reads the same conversations every minute, so without it one
54
+ * conversation would send an event a minute all day.
55
+ */
56
+ export function noticeCodexAgents(threads, { count = null, seen = null, now = Date.now() } = {}) {
57
+ if (typeof count !== 'function') return 0;
58
+ let sent = 0;
59
+ for (const t of threads ?? []) {
60
+ const id = String(t?.id ?? '');
61
+ if (!id) continue;
62
+ const at = Number(t.lastActive ?? 0);
63
+ if (!t.live && !(at > 0 && now - at < CODEX_SEEN_MS)) continue;
64
+ if (seen) {
65
+ if (seen.has(id)) continue;
66
+ seen.add(id);
67
+ }
68
+ // A count is never worth the watch: the mirror refresh below is what
69
+ // somebody actually sees, and it runs whatever the switch does.
70
+ try { count('agent_seen', { engine: 'codex' }); } catch { /* nothing */ }
71
+ sent += 1;
72
+ }
73
+ return sent;
74
+ }
75
+
76
+ export function scanCodex({ store, readThreads = readCodexThreads, now = Date.now(), count = null, seen = null } = {}) {
18
77
  let threads = [];
19
78
  try { ({ threads } = readThreads({ now })); } catch { return { refreshed: 0, retold: 0, threads: 0 }; }
79
+ noticeCodexAgents(threads, { count, seen, now });
20
80
  const refreshed = store.refreshCodexMirrors(threads, { now });
21
81
  const retold = store.refreshCodexAsks(threads, { now });
22
82
  return { refreshed, retold, threads: threads.length };
23
83
  }
24
84
 
25
- export function startCodexWatch({ store, readThreads = readCodexThreads, pollMs = CODEX_POLL_MS, firstDelayMs = 8_000 } = {}) {
85
+ export function startCodexWatch({ store, readThreads = readCodexThreads, pollMs = CODEX_POLL_MS, firstDelayMs = 8_000, count = null } = {}) {
86
+ // Conversations this run has already counted. In memory on purpose, exactly
87
+ // as the Claude side's set is: a fresh launch seeing the same conversation
88
+ // again is a fresh fact about the app being used.
89
+ const seen = new Set();
26
90
  const run = () => {
27
- try { scanCodex({ store, readThreads }); } catch (e) { console.warn('codex watch:', e?.message ?? e); }
91
+ try { scanCodex({ store, readThreads, count, seen }); } catch (e) { console.warn('codex watch:', e?.message ?? e); }
28
92
  };
29
93
  const first = setTimeout(run, firstDelayMs);
30
94
  const timer = setInterval(run, pollMs);
package/main/config.mjs CHANGED
@@ -10,8 +10,9 @@ import path from 'node:path';
10
10
  import os from 'node:os';
11
11
  import crypto from 'node:crypto';
12
12
  import { resolveClaudeBin, candidatePaths } from './claude-bin.mjs';
13
+ import { warmShellAnswers } from './engine-bin.mjs';
13
14
  import { resolveCodexBin } from './codex-bin.mjs';
14
- import { Name } from '../shared/product-name.mjs';
15
+ import { defaultStoreRoot } from './store/home.mjs';
15
16
  import { readPlans, slotsForPlans } from './claude-plan.mjs';
16
17
  import { machineSlots } from './machine.mjs';
17
18
  import { effectiveProfiles } from './account-discovery.mjs';
@@ -34,14 +35,6 @@ const CONFIG_NAME = 'zero.config.json';
34
35
  export const DEFAULT_SESSIONS_AT_ONCE = 3;
35
36
 
36
37
  const DEFAULTS = {
37
- // Where the inbox's own files live. WHICH FOLDER THIS SHOULD BE ON A
38
- // STRANGER'S MAC IS NOT DECIDED HERE: it is the open question on.
39
- //
40
- // IT IS THE APP'S OWN NAME SINCE 2026-09-22, where it used to be an older
41
- // product's. A real store is always named in the config, so this default is
42
- // only ever the answer for an install carrying none, and renaming it moved
43
- // no files: the folder it used to point at is untouched and still on disk.
44
- storeRoot: path.join(os.homedir(), Name),
45
38
  // ONE ACCOUNT FOLDER PER INSTALL, AND NOBODY ELSE'S.
46
39
  //
47
40
  // This was a literal id for most of the app's life, which meant every copy
@@ -71,6 +64,11 @@ const DEFAULTS = {
71
64
  // many times "Something felt slow" has been pressed, and only ever subtracts.
72
65
  agentsAuto: true,
73
66
  agentsNudge: 0,
67
+ // THE NUMBER PICKED ON THE MENU, FOR THE WHOLE COMPUTER (w-ec27ff1a51). Null
68
+ // until somebody picks one. It replaced `maxConcurrentSessions` as the thing
69
+ // the menu writes, because that one was per login: two logins turned a 7 into
70
+ // 14 agents on a 16 GB Mac, which then ran out of memory.
71
+ agentsAtOnce: null,
74
72
  // HOLD HEAVY WORK WHEN MEMORY IS SHORT (w-3958c3753d). Off until somebody
75
73
  // turns it on from the Agents page. With it on, every shell command a worker
76
74
  // runs asks main/memory-gate-server.mjs first, and heavy ones wait their turn
@@ -160,6 +158,19 @@ export function loadConfig(appDir, { home = os.homedir() } = {}) {
160
158
  else console.warn(`zero: could not read ${file}: ${err.message}`);
161
159
  }
162
160
  const config = { ...DEFAULTS, ...overrides, freshInstall: fresh };
161
+ // Where the inbox's own files live, when the file names no store. A path in
162
+ // the file always wins, including on Linux. The default is computed here, not
163
+ // at import: on Linux it is the XDG data directory, and on macOS a folder of
164
+ // the app's name. An import-time path cannot see the `home` a test passes,
165
+ // and it cannot see a blanked XDG_DATA_HOME.
166
+ //
167
+ // A stand-in home does not inherit this machine's XDG_DATA_HOME. That
168
+ // variable is an absolute path in the real home, and a throwaway asked with
169
+ // `{ home }` would otherwise open the real store.
170
+ if (typeof overrides.storeRoot !== 'string' || !overrides.storeRoot.trim()) {
171
+ const real = path.resolve(home) === path.resolve(os.homedir());
172
+ config.storeRoot = defaultStoreRoot({ home, env: real ? process.env : {} });
173
+ }
163
174
  // An older alias for `storeRoot` was read here until 2026-09-22. A config
164
175
  // still carrying only the old key falls through to the default now, which is
165
176
  // why the key was renamed in zero.config.json in the same step and not after
@@ -200,6 +211,14 @@ export function loadConfig(appDir, { home = os.homedir() } = {}) {
200
211
  config.claudeBinConfigured = typeof overrides.claudeBin === 'string' && overrides.claudeBin
201
212
  ? overrides.claudeBin
202
213
  : null;
214
+ // ONE LOGIN SHELL FOR BOTH ENGINES, ASKED BEFORE EITHER SEARCH RUNS. This is
215
+ // on the boot path -- main/main.mjs calls loadConfig inside createWindow and
216
+ // before the window exists -- and a login shell costs about 0.7 s to start
217
+ // whether it is asked about one program or six. Measured on this Mac
218
+ // 2026-10-11, the two searches together took 1.4 s cold and the shell was all
219
+ // of it. It seeds only what it positively found, so a shell that will not
220
+ // start leaves both searches exactly as they were (main/engine-bin.mjs).
221
+ warmShellAnswers();
203
222
  const claude = resolveClaudeBin(config.claudeBinConfigured);
204
223
  config.claudeBin = claude.path ?? candidatePaths()[0];
205
224
  config.claudeFound = claude.found;
@@ -276,10 +295,18 @@ export function loadConfig(appDir, { home = os.homedir() } = {}) {
276
295
  // same thing said out loud and kept up to date. An explicit `agentsAuto`
277
296
  // outranks the guess, because turning Automatic back on after setting a number
278
297
  // has to be possible.
279
- if (typeof overrides.agentsAuto !== 'boolean') {
280
- config.agentsAuto = typeof overrides.maxConcurrentSessions !== 'number';
281
- }
282
- if (typeof overrides.maxConcurrentSessions !== 'number') {
298
+ //
299
+ // ONLY A NUMBER FOR THE WHOLE COMPUTER IS A CHOICE (w-ec27ff1a51). A
300
+ // `maxConcurrentSessions` in the file was written per login, by the stepper
301
+ // that was here before 5 Oct, and reading it as "you set this" is what ran
302
+ // 14 agents on a Mac Automatic would have run 6 on, with nobody having picked
303
+ // 14. So that install goes back to Automatic, even with `agentsAuto: false`
304
+ // written beside it, and a number picked on today's menu (`agentsAtOnce`)
305
+ // is kept as picked unless Automatic was chosen after it.
306
+ config.agentsAuto = typeof overrides.agentsAtOnce !== 'number' || overrides.agentsAuto === true;
307
+ // And the plan is read whenever the number is Automatic's, old per-login
308
+ // number or not, so a plan that allows one at a time still says so.
309
+ if (typeof overrides.agentsAtOnce !== 'number') {
283
310
  const plans = readPlans(effectiveProfiles(config.authProfiles, { home }), { home });
284
311
  const slots = slotsForPlans(plans, config.machineSlots);
285
312
  config.maxConcurrentSessions = slots;