agentbox-app 0.1.7 → 0.1.12

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 (255) hide show
  1. package/README.md +220 -58
  2. package/bin/agentbox.mjs +18 -3
  3. package/briefs/message-rules.md +24 -3
  4. package/briefs/worker.md +63 -49
  5. package/briefs/writing-rules.md +90 -131
  6. package/main/account-discovery.mjs +21 -20
  7. package/main/account-tooling.mjs +11 -11
  8. package/main/agent-activity.mjs +45 -5
  9. package/main/agent-schedule.mjs +14 -14
  10. package/main/agent-sessions.mjs +94 -10
  11. package/main/agent-updates.mjs +116 -0
  12. package/main/agents.mjs +52 -46
  13. package/main/analytics.mjs +27 -0
  14. package/main/approval-prompt-server.mjs +2 -2
  15. package/main/approvals.mjs +14 -5
  16. package/main/artifact-path.mjs +11 -0
  17. package/main/claude-bin.mjs +32 -5
  18. package/main/claude-input.mjs +45 -3
  19. package/main/claude-plan.mjs +25 -13
  20. package/main/code-change.mjs +3 -3
  21. package/main/codex-account.mjs +1 -1
  22. package/main/codex-bin.mjs +18 -0
  23. package/main/codex-launch-env.mjs +31 -0
  24. package/main/codex-memory-gate.mjs +260 -0
  25. package/main/codex-models.mjs +12 -7
  26. package/main/codex-threads.mjs +46 -23
  27. package/main/codex-usage-file.mjs +7 -7
  28. package/main/codex-usage.mjs +4 -4
  29. package/main/codex-watch.mjs +12 -12
  30. package/main/config.mjs +82 -17
  31. package/main/demo.mjs +17 -14
  32. package/main/dev-window.mjs +9 -9
  33. package/main/doc-scheme.mjs +2 -0
  34. package/main/engine-setup.mjs +204 -0
  35. package/main/feedback.mjs +55 -0
  36. package/main/folder-pictures.mjs +48 -0
  37. package/main/frame-scrollbars.mjs +90 -0
  38. package/main/fresh-user.mjs +7 -4
  39. package/main/git-change-offthread.mjs +70 -0
  40. package/main/git-change-thread.mjs +11 -0
  41. package/main/git-change.mjs +9 -3
  42. package/main/img-scheme.mjs +70 -1
  43. package/main/instruction-settings.mjs +134 -60
  44. package/main/ipc.mjs +335 -42
  45. package/main/leftovers.mjs +518 -0
  46. package/main/live-replies.mjs +43 -5
  47. package/main/machine.mjs +48 -1
  48. package/main/main.mjs +138 -15
  49. package/main/memory-gate-history.mjs +254 -0
  50. package/main/memory-gate-server.mjs +461 -0
  51. package/main/memory-gate.mjs +206 -0
  52. package/main/message-priority.mjs +87 -0
  53. package/main/notify.mjs +1 -1
  54. package/main/rail-note.mjs +2 -2
  55. package/main/refresh-agent-models.mjs +21 -0
  56. package/main/repeats.mjs +4 -4
  57. package/main/row-label.mjs +115 -51
  58. package/main/runs-on.mjs +93 -0
  59. package/main/serve.mjs +77 -5
  60. package/main/settings.mjs +245 -26
  61. package/main/ship-queue.mjs +279 -0
  62. package/main/sign-in-files.mjs +39 -0
  63. package/main/source-updater.mjs +298 -0
  64. package/main/store/home.mjs +84 -7
  65. package/main/store/project.mjs +15 -19
  66. package/main/store/work-items.mjs +150 -6
  67. package/main/store.mjs +245 -13
  68. package/main/supervisor.mjs +1634 -417
  69. package/main/task-commands.mjs +8 -10
  70. package/main/task-folders.mjs +94 -16
  71. package/main/task-remote-control.mjs +3 -3
  72. package/main/task-terminals.mjs +14 -2
  73. package/main/team/index.mjs +440 -0
  74. package/main/team/memory-cloud.mjs +247 -0
  75. package/main/team/projects.mjs +162 -0
  76. package/main/team/session.mjs +225 -0
  77. package/main/team/sign-in.mjs +106 -0
  78. package/main/team/supabase-backend.mjs +231 -0
  79. package/main/team/sync.mjs +163 -0
  80. package/main/updater.mjs +15 -5
  81. package/main/write-before-reload.mjs +50 -0
  82. package/mcp/core/page.mjs +10 -12
  83. package/mcp/core/work.mjs +123 -26
  84. package/mcp/tools.mjs +13 -6
  85. package/package.json +21 -9
  86. package/renderer/dist/assets/agentbox-icon-F1oN9nj_.svg +15 -0
  87. package/renderer/dist/assets/index-ChKsQkRw.js +1169 -0
  88. package/renderer/dist/assets/index-ClBXnq3B.css +32 -0
  89. package/renderer/dist/index.html +2 -2
  90. package/scripts/after-install.mjs +14 -0
  91. package/scripts/approval-server.sh +7 -2
  92. package/scripts/memory-gate-hook.sh +59 -0
  93. package/shared/agent-import.mjs +39 -16
  94. package/shared/agents.d.mts +26 -8
  95. package/shared/agents.mjs +100 -108
  96. package/shared/analytics-events.mjs +19 -2
  97. package/shared/answers.mjs +12 -0
  98. package/shared/bridge-map.d.mts +12 -0
  99. package/shared/bridge-map.mjs +38 -1
  100. package/shared/claude-commands.generated.mjs +2 -2
  101. package/shared/claude-models.generated.mjs +6 -6
  102. package/shared/claude-usage.mjs +6 -6
  103. package/shared/codex-import.mjs +7 -7
  104. package/shared/codex-modes.d.mts +50 -0
  105. package/shared/codex-modes.mjs +1 -1
  106. package/shared/codex-usage.mjs +7 -8
  107. package/shared/demo-world.mjs +6 -6
  108. package/shared/engines.mjs +27 -10
  109. package/shared/feedback.d.mts +14 -0
  110. package/shared/feedback.mjs +44 -0
  111. package/shared/first-run-examples.mjs +4 -4
  112. package/shared/first-run-practice.d.mts +16 -0
  113. package/shared/first-run-practice.mjs +176 -70
  114. package/shared/fresh-user-home.mjs +34 -4
  115. package/shared/instruction-defaults.json +2 -2
  116. package/shared/product-name.mjs +4 -2
  117. package/shared/project-folder-check.mjs +9 -3
  118. package/shared/rank.d.mts +1 -0
  119. package/shared/rank.mjs +22 -0
  120. package/shared/referenced-files.mjs +1 -1
  121. package/shared/runs-on.d.mts +20 -0
  122. package/shared/runs-on.mjs +79 -0
  123. package/shared/schemes.d.mts +11 -0
  124. package/shared/schemes.mjs +1 -1
  125. package/shared/shortcuts.d.mts +41 -0
  126. package/shared/shortcuts.mjs +236 -0
  127. package/shared/spawn-trouble.d.mts +2 -0
  128. package/shared/spawn-trouble.mjs +40 -19
  129. package/shared/team-rules.d.mts +17 -0
  130. package/shared/team-rules.mjs +230 -0
  131. package/shared/team-status.d.mts +11 -0
  132. package/shared/team-status.mjs +90 -0
  133. package/shared/terminal-links.d.mts +2 -0
  134. package/shared/terminal-links.mjs +55 -0
  135. package/shared/terminal-state.d.mts +1 -0
  136. package/shared/terminal-state.mjs +16 -0
  137. package/shared/thread-cards.d.mts +19 -0
  138. package/shared/thread-cards.mjs +254 -0
  139. package/shared/usage.d.mts +1 -1
  140. package/shared/usage.mjs +6 -7
  141. package/shared/work-items.mjs +258 -28
  142. package/shared/work-lines.mjs +393 -16
  143. package/worker-permissions.json +1 -1
  144. package/renderer/dist/assets/cel-dusk-CS5I7kJS.webp +0 -0
  145. package/renderer/dist/assets/cel-dusk-DUslqS6B.webp +0 -0
  146. package/renderer/dist/assets/cel-dusk-Jt_xVGxp.webp +0 -0
  147. package/renderer/dist/assets/cel-dusk-sm-BBkV-tWT.webp +0 -0
  148. package/renderer/dist/assets/frost-haze-CxPSOBEg.webp +0 -0
  149. package/renderer/dist/assets/frost-haze-sm-C_w21qgd.webp +0 -0
  150. package/renderer/dist/assets/ice-BC8luINc.webp +0 -0
  151. package/renderer/dist/assets/ice-CP9PrKHo.webp +0 -0
  152. package/renderer/dist/assets/ice-river-D6ziOhLY.webp +0 -0
  153. package/renderer/dist/assets/ice-river-UVM2-JY_.webp +0 -0
  154. package/renderer/dist/assets/ice-river-bErJwYWW.webp +0 -0
  155. package/renderer/dist/assets/ice-river-sm-Bg1U-SIj.webp +0 -0
  156. package/renderer/dist/assets/ice-sm-CbVcg3_D.webp +0 -0
  157. package/renderer/dist/assets/index-B7hhfHrT.js +0 -684
  158. package/renderer/dist/assets/index-CwiDv5bL.css +0 -32
  159. package/renderer/dist/assets/lake-C0WaW-6E.webp +0 -0
  160. package/renderer/dist/assets/lake-CSKjLC5K.webp +0 -0
  161. package/renderer/dist/assets/lake-ETGcOb25.webp +0 -0
  162. package/renderer/dist/assets/lake-harbour-BCQRbrHb.webp +0 -0
  163. package/renderer/dist/assets/lake-harbour-B_wfXTmE.webp +0 -0
  164. package/renderer/dist/assets/lake-harbour-Te1pEvBh.webp +0 -0
  165. package/renderer/dist/assets/lake-harbour-sm-Cme_ShEg.webp +0 -0
  166. package/renderer/dist/assets/lake-sm-CbG72Hbv.webp +0 -0
  167. package/renderer/dist/assets/lino-coast-CUZHcucI.webp +0 -0
  168. package/renderer/dist/assets/lino-coast-DP4J7cgd.webp +0 -0
  169. package/renderer/dist/assets/lino-coast-kOIAffr_.webp +0 -0
  170. package/renderer/dist/assets/lino-coast-sm-C1Mdbgit.webp +0 -0
  171. package/renderer/dist/assets/moon-pines-BskgnnXR.webp +0 -0
  172. package/renderer/dist/assets/moon-pines-CFSIIwt0.webp +0 -0
  173. package/renderer/dist/assets/moon-pines-PNhdOYww.webp +0 -0
  174. package/renderer/dist/assets/moon-pines-sm-C6K1NomK.webp +0 -0
  175. package/renderer/dist/assets/orbit-rings-2XV36Ch3.webp +0 -0
  176. package/renderer/dist/assets/orbit-rings-C4NrcYGQ.webp +0 -0
  177. package/renderer/dist/assets/orbit-rings-DGLk07dT.webp +0 -0
  178. package/renderer/dist/assets/orbit-rings-sm-Dky3YdvL.webp +0 -0
  179. package/renderer/dist/assets/peach-haze-2-C6f7xnWw.webp +0 -0
  180. package/renderer/dist/assets/peach-haze-2-sm-Dj_Ez_Sb.webp +0 -0
  181. package/renderer/dist/assets/pixel-harbour-DQjqMROg.webp +0 -0
  182. package/renderer/dist/assets/pixel-harbour-Dw71H_bc.webp +0 -0
  183. package/renderer/dist/assets/pixel-harbour-kOZfm-GM.webp +0 -0
  184. package/renderer/dist/assets/pixel-harbour-sm-Dturjuky.webp +0 -0
  185. package/renderer/dist/assets/pixel-orbit-C296JVMO.webp +0 -0
  186. package/renderer/dist/assets/pixel-orbit-C4oMRDSx.webp +0 -0
  187. package/renderer/dist/assets/pixel-orbit-CqVmQ4eh.webp +0 -0
  188. package/renderer/dist/assets/pixel-orbit-sm-CHagC_nO.webp +0 -0
  189. package/renderer/dist/assets/pixel-ridge-C6LYu6uE.webp +0 -0
  190. package/renderer/dist/assets/pixel-ridge-CIUCEESs.webp +0 -0
  191. package/renderer/dist/assets/pixel-ridge-sm-BnE0F_w-.webp +0 -0
  192. package/renderer/dist/assets/riso-hills-7heKVtry.webp +0 -0
  193. package/renderer/dist/assets/riso-hills-CIjAL3J6.webp +0 -0
  194. package/renderer/dist/assets/riso-hills-sm-DHsnbCfP.webp +0 -0
  195. package/renderer/dist/assets/slate-haze-BPWdkNsw.webp +0 -0
  196. package/renderer/dist/assets/slate-haze-sm-B0Cb5a2W.webp +0 -0
  197. package/renderer/dist/assets/valley-haze-BDHPqoJy.webp +0 -0
  198. package/renderer/dist/assets/valley-haze-sm-CTyjmPAG.webp +0 -0
  199. package/renderer/dist/assets/vector-dunes-DzzWLdKo.webp +0 -0
  200. package/renderer/dist/assets/vector-dunes-Yr9NBQUy.webp +0 -0
  201. package/renderer/dist/assets/vector-dunes-sm-D8MYY6ms.webp +0 -0
  202. package/renderer/dist/assets/watercolour-mist-BCGKN4u4.webp +0 -0
  203. package/renderer/dist/assets/watercolour-mist-BF-jk5MC.webp +0 -0
  204. package/renderer/dist/assets/watercolour-mist-sm-Ds1xzTw9.webp +0 -0
  205. package/renderer/dist/assets/woodblock-sea-B1um67Z9.webp +0 -0
  206. package/renderer/dist/assets/woodblock-sea-DKQdYs32.webp +0 -0
  207. package/renderer/dist/assets/woodblock-sea-sm-BJzNq8xh.webp +0 -0
  208. package/renderer/dist/emulator/audio-rules.mjs +0 -74
  209. package/renderer/dist/emulator/data/compression/README.md +0 -11
  210. package/renderer/dist/emulator/data/compression/extract7z.js +0 -1
  211. package/renderer/dist/emulator/data/compression/extractzip.js +0 -1
  212. package/renderer/dist/emulator/data/compression/libunrar.js +0 -519
  213. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  214. package/renderer/dist/emulator/data/cores/README.md +0 -22
  215. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  216. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  217. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  218. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  219. package/renderer/dist/emulator/data/cores/package.json +0 -65
  220. package/renderer/dist/emulator/data/cores/reports/mgba.json +0 -1
  221. package/renderer/dist/emulator/data/emulator.css +0 -1614
  222. package/renderer/dist/emulator/data/loader.js +0 -170
  223. package/renderer/dist/emulator/data/localization/README.md +0 -63
  224. package/renderer/dist/emulator/data/localization/af-FR.json +0 -339
  225. package/renderer/dist/emulator/data/localization/ar-AR.json +0 -301
  226. package/renderer/dist/emulator/data/localization/ben-BEN.json +0 -301
  227. package/renderer/dist/emulator/data/localization/de-GER.json +0 -301
  228. package/renderer/dist/emulator/data/localization/el-GR.json +0 -301
  229. package/renderer/dist/emulator/data/localization/en-US.json +0 -339
  230. package/renderer/dist/emulator/data/localization/es-ES.json +0 -310
  231. package/renderer/dist/emulator/data/localization/fa-AF.json +0 -310
  232. package/renderer/dist/emulator/data/localization/hi-HI.json +0 -301
  233. package/renderer/dist/emulator/data/localization/it-IT.json +0 -302
  234. package/renderer/dist/emulator/data/localization/ja-JA.json +0 -301
  235. package/renderer/dist/emulator/data/localization/jv-JV.json +0 -301
  236. package/renderer/dist/emulator/data/localization/ko-KO.json +0 -336
  237. package/renderer/dist/emulator/data/localization/pt-BR.json +0 -361
  238. package/renderer/dist/emulator/data/localization/retroarch.json +0 -617
  239. package/renderer/dist/emulator/data/localization/ro-RO.json +0 -310
  240. package/renderer/dist/emulator/data/localization/ru-RU.json +0 -301
  241. package/renderer/dist/emulator/data/localization/tr-TR.json +0 -310
  242. package/renderer/dist/emulator/data/localization/vi-VN.json +0 -302
  243. package/renderer/dist/emulator/data/localization/zh-CN.json +0 -351
  244. package/renderer/dist/emulator/data/src/GameManager.js +0 -464
  245. package/renderer/dist/emulator/data/src/compression.js +0 -142
  246. package/renderer/dist/emulator/data/src/emulator.js +0 -6158
  247. package/renderer/dist/emulator/data/src/gamepad.js +0 -167
  248. package/renderer/dist/emulator/data/src/nipplejs.js +0 -1
  249. package/renderer/dist/emulator/data/src/shaders.js +0 -236
  250. package/renderer/dist/emulator/data/src/socket.io.min.js +0 -7
  251. package/renderer/dist/emulator/data/src/storage.js +0 -125
  252. package/renderer/dist/emulator/data/version.json +0 -5
  253. package/renderer/dist/emulator/host.js +0 -299
  254. package/renderer/dist/emulator/index.html +0 -28
  255. package/renderer/dist/emulator/provenance.json +0 -6
package/main/main.mjs CHANGED
@@ -1,7 +1,13 @@
1
1
  // The app: the agent inbox. Electron shell around a keyboard-first renderer, a
2
2
  // file-derived store, and a supervisor of headless sessions.
3
3
 
4
- import { app, BrowserWindow, Menu, Notification, clipboard, crashReporter, dialog, ipcMain, nativeImage, net, powerMonitor, protocol, screen as electronScreen, shell, session } from 'electron';
4
+ import { app, BrowserWindow, Menu, Notification, clipboard, crashReporter, dialog, ipcMain, nativeImage, net, powerMonitor, protocol, screen as electronScreen, shell, session, safeStorage } from 'electron';
5
+ import * as workItemsDisk from './store/work-items.mjs';
6
+ import { createTeamService, teamStateFile } from './team/index.mjs';
7
+ import { loadCloudConfig, supabaseSession } from './team/session.mjs';
8
+ // MY WORKSPACE IS NOT A TEAM FEATURE ANY MORE (w-f8d123be62), even though it
9
+ // still lives in the team folder next to the sharing rules it has to obey.
10
+ import { ensurePersonalProject } from './team/projects.mjs';
5
11
  import path from 'node:path';
6
12
  import fs from 'node:fs';
7
13
  import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -15,19 +21,22 @@ import { storeRootEnv } from './store/home.mjs';
15
21
  import { Supervisor } from './supervisor.mjs';
16
22
  import { startCodexWatch } from './codex-watch.mjs';
17
23
  import { registerIpc } from './ipc.mjs';
18
- import { carryHerBriefsAcross, joinMessageRules } from './instruction-settings.mjs';
24
+ import { carryHerBriefsAcross, joinMessageRules, setAsideShippedMessageRules, unifyAgentInstructions } from './instruction-settings.mjs';
19
25
  import * as approvals from './approvals.mjs';
20
26
  import { recoveryToast } from '../shared/recovery.mjs';
21
27
  import { dataFolderName, isNewUserBuild } from '../shared/side-build.mjs';
22
28
  import { runningAsAFreshUser } from '../shared/fresh-user-home.mjs';
23
29
  import { openFreshUser } from './fresh-user.mjs';
24
30
  import { installCrashReports, reportFromRenderer, pending as pendingCrashes, setTransport } from './crash-report.mjs';
25
- import { createAnalytics } from './analytics.mjs';
31
+ import { createAnalytics, createDailyCount } from './analytics.mjs';
26
32
  import { createUpdater } from './updater.mjs';
33
+ import { createSourceUpdater } from './source-updater.mjs';
27
34
  import { installNotifier } from './notify.mjs';
28
35
  import { DOC_SCHEMES, DocGrants, docPath } from './doc-scheme.mjs';
29
- import { IMG_SCHEMES, imgPath, servable } from './img-scheme.mjs';
36
+ import { quietTheFramesScrollbars } from './frame-scrollbars.mjs';
37
+ import { IMG_SCHEMES, imgPath, mediaResponse, mediaType, servable } from './img-scheme.mjs';
30
38
  import { hotWindowVerdict, storeHasWork } from './dev-window.mjs';
39
+ import { writeHeldThenReload } from './write-before-reload.mjs';
31
40
  import { IS_SHE_TYPING_IN_THE_PAGE, theAppKeepsThisInput, theAppKeepsThisLetterInAPage } from '../shared/artifact-keys.mjs';
32
41
  import { NAME, Name, WAS } from '../shared/product-name.mjs';
33
42
 
@@ -99,6 +108,15 @@ const appDir = path.join(__dirname, '..');
99
108
  // live. This has to happen before ANYTHING reads a path, because everything
100
109
  // downstream keys off app.getName, the single-instance lock at the foot of
101
110
  // this file included.
111
+ // THE TEAM VERSION RUNS BESIDE THE ONE IN USE TODAY (2026-10-01). Run from
112
+ // source, this checkout is named exactly like the solo app, so it would read
113
+ // her data folder, take her single-instance lock and quit on the spot. A
114
+ // profile (`AGENTBOX_PROFILE=team npx electron .`) gives it a name of its own,
115
+ // and with the name a data folder and a lock of its own. Letters, digits and
116
+ // dashes only, and never a space: "agentbox team" reads as a side build, and
117
+ // side builds share the real data folder on purpose.
118
+ const PROFILE = String(process.env.AGENTBOX_PROFILE ?? '').trim().replace(/[^a-z0-9-]/gi, '');
119
+ if (PROFILE) app.setName(`${NAME}-${PROFILE}`);
102
120
  if (dataFolderName(app.getName()) !== app.getName()) app.setName(dataFolderName(app.getName()));
103
121
 
104
122
  // "ASTRAL NEW USER" IS A STRANGER EVERY TIME IT OPENS.
@@ -132,7 +150,8 @@ if (isNewUserBuild(app.getName()) && !runningAsAFreshUser()) {
132
150
  // existing user's folder is called the app, not the app, so a bare the app check would
133
151
  // have found nothing and handed them an empty Powerup. Whoever renames this app
134
152
  // a third time adds one entry here and nothing else.
135
- try {
153
+ // A profile never falls back to an older folder: an older folder is hers.
154
+ if (!PROFILE) try {
136
155
  const named = path.join(app.getPath('appData'), app.getName());
137
156
  if (!fs.existsSync(named)) {
138
157
  // THESE ARE HISTORY AND NOT COPY. They are the folder names this app has
@@ -184,6 +203,12 @@ try {
184
203
  // supervisor exists, so nothing has read the new name yet.
185
204
  const joined = joinMessageRules(appDir, userDir);
186
205
  if (joined) console.log(`zero: joined the message rules into one file, ${joined.total} characters`);
206
+ // AND A BOX THAT AN OLDER JOIN FILLED WITH ONLY OUR TEXT IS EMPTIED, ONCE
207
+ // (w-3ec9f07978). Kept as a restore point; our rules ride from the checkout.
208
+ if (setAsideShippedMessageRules(appDir, userDir)) console.log('zero: the message rules box held only the shipped rules; set aside as a restore point');
209
+ // Both kinds of user rules now live in founder.md. Only user content is
210
+ // appended; the app's message defaults continue to come from the bundle.
211
+ if (unifyAgentInstructions(appDir, userDir)) console.log('zero: combined the general and writing instructions');
187
212
  } catch (err) {
188
213
  console.warn(`zero: could not carry her instructions into ${userDir}: ${err.message}`);
189
214
  }
@@ -235,8 +260,18 @@ function reloadRenderer() {
235
260
  // identical build unless something records that it arrived at all.
236
261
  console.log(`zero: reload requested, renderer built ${new Date(builtAt()).toLocaleTimeString()}`);
237
262
  if (!window || window.isDestroyed()) return;
238
- reloadRequested = true;
239
- window.webContents.reloadIgnoringCache();
263
+ // The page may be holding something she did in the last three seconds (a
264
+ // close, an answer, a reply) for Z to take back. It is written first, or the
265
+ // reload throws it away and the thread comes back to her inbox
266
+ // (w-47218417a5). See write-before-reload.mjs.
267
+ void writeHeldThenReload({
268
+ webContents: window.webContents,
269
+ ipcMain,
270
+ reload: () => {
271
+ reloadRequested = true;
272
+ window.webContents.reloadIgnoringCache();
273
+ },
274
+ });
240
275
  }
241
276
 
242
277
  async function createWindow() {
@@ -290,6 +325,8 @@ async function createWindow() {
290
325
  // died are finally sent, because a report is written while the process is
291
326
  // dying and delivery cannot be synchronous.
292
327
  const analytics = createAnalytics({ config, version: app.getVersion(), dir: crashDir });
328
+ // "Used today", once a day, at launch and whenever the window comes forward.
329
+ const usedToday = createDailyCount((name) => analytics.track(name));
293
330
  // The seam is filled whenever there is somewhere to send to. Whether anything
294
331
  // actually goes is asked again inside, at every send, so her switch stops it
295
332
  // from the moment she moves it rather than at the next launch.
@@ -298,10 +335,23 @@ async function createWindow() {
298
335
  const drained = analytics.drainCrashes(pendingCrashes());
299
336
  if (drained) console.log(`zero: sent ${drained} crash report(s) from a previous launch`);
300
337
  analytics.track('app_opened');
338
+ usedToday(Date.now());
301
339
  } else {
302
340
  console.log(`zero: diagnostics is ${analytics.reason}, nothing is sent`);
303
341
  }
304
342
 
343
+ // EVERYBODY HAS SOMEWHERE TO SEND A THREAD (w-f8d123be62, approved
344
+ // 2026-10-05). My Workspace used to be made inside `signedIn()`, so the
345
+ // single-person app — a Mac with no team cloud, which never builds the team
346
+ // service at all — opened on no projects whatsoever: the chip read "No
347
+ // project", Send was off, and nothing said why. It is made HERE, before the
348
+ // store reads the account root, so the first list of projects already has it
349
+ // and no second pass is needed. Made once and never remade; a Mac that cannot
350
+ // write it still starts, and the card now says so out loud rather than
351
+ // leaving a dead button (renderer/src/compose-says.ts noProjectYet).
352
+ try {
353
+ ensurePersonalProject(config.accountRoot);
354
+ } catch (err) { console.warn(`zero: could not make My Workspace: ${err.message}`); }
305
355
  const store = await new Store(config).init();
306
356
  // Where the window's own pictures may come from (main/img-scheme.mjs). The
307
357
  // account root holds every product's docs, attachments, designs and the
@@ -310,7 +360,42 @@ async function createWindow() {
310
360
  // request so a project made after launch is not a folder of broken images.
311
361
  imageRoots = () => [config.accountRoot, ...store.listProducts().map((p) => p.repoPath).filter(Boolean)];
312
362
  const supervisor = new Supervisor(config, store, appDir, dataDir, userDir);
363
+ // THAT agents start and stop work (main/analytics.mjs checks the switch).
364
+ supervisor.count = (name, props) => analytics.track(name, props);
313
365
  app.on('before-quit', () => supervisor.killAll());
366
+ // THE TEAM. Off entirely on a build with no team cloud (the single-person
367
+ // app). Otherwise it restores whoever was signed in, and from then on every
368
+ // line this Mac writes says who wrote it and shared projects stay in sync.
369
+ // Its session file sits beside the store, encrypted with the Mac's own
370
+ // keychain-backed key, so each store root is its own signed-in person.
371
+ // THE KEYCHAIN IS ONLY ASKED WHEN THERE IS A TEAM (2026-10-06): asking it at
372
+ // every launch put a "Agentbox Safe Storage" password prompt in front of the
373
+ // single-person app, which has no sign-in to keep
374
+ // (tests/the-installed-app-is-the-single-person-app.test.mjs).
375
+ const cloudConfig = loadCloudConfig(appDir, { packaged: app.isPackaged });
376
+ const team = cloudConfig ? createTeamService({
377
+ session: supabaseSession({
378
+ cloudConfig,
379
+ sessionFile: path.join(config.storeRoot, '.team-session'),
380
+ ...(safeStorage.isEncryptionAvailable()
381
+ ? { encrypt: (text) => safeStorage.encryptString(text), decrypt: (buf) => safeStorage.decryptString(buf) }
382
+ : { encrypt: null, decrypt: null }),
383
+ packaged: app.isPackaged,
384
+ openExternal: (url) => shell.openExternal(url),
385
+ }),
386
+ store,
387
+ disk: workItemsDisk,
388
+ accountRoot: config.accountRoot,
389
+ stateFile: teamStateFile(config.storeRoot),
390
+ // The window's push is declared further down and wired once the window
391
+ // exists; until then a change has nobody to tell.
392
+ onChange: () => { try { pushUpdate(); } catch { /* no window yet */ } },
393
+ log: (line) => console.log(line),
394
+ }) : null;
395
+ if (team) {
396
+ team.start().then(() => supervisor.wake?.()).catch((err) => console.warn(`team: ${err.message}`));
397
+ app.on('before-quit', () => team.stop());
398
+ }
314
399
  // HER CODEX CONVERSATIONS ASK TO COME IN. Once a minute: a new one in a
315
400
  // folder a project points at gets a row asking yes or no, and the ones she
316
401
  // said yes to follow Codex's latest answer.
@@ -521,6 +606,11 @@ async function createWindow() {
521
606
  },
522
607
  });
523
608
 
609
+ // THE MENU IS A BAR INSIDE THE WINDOW on Linux and Windows. On macOS it is
610
+ // the system menu, so it stays. The menu itself stays everywhere: the Ctrl
611
+ // chords are bound to it, and taking the menu out would take them with it.
612
+ if (process.platform !== 'darwin') window.setMenuBarVisibility(false);
613
+
524
614
  // WHICH PICTURE SET THIS WINDOW IS ON, and a shout when it changes.
525
615
  // main/screen-detail.mjs has the rule and the reasoning; this is the wiring.
526
616
  // The initial answer rides bootInfo rather than being pushed, for the same
@@ -542,6 +632,7 @@ async function createWindow() {
542
632
  lastScreenDetail = next;
543
633
  window.webContents.send('zero:screen-detail', { detail: next });
544
634
  };
635
+ window.on('focus', () => usedToday(Date.now()));
545
636
  window.on('move', tellScreenDetail);
546
637
  window.on('moved', tellScreenDetail);
547
638
  electronScreen.on('display-metrics-changed', tellScreenDetail);
@@ -566,9 +657,17 @@ async function createWindow() {
566
657
  // `pushUpdate` is filled in on the next line, because the updater has to
567
658
  // exist before the IPC surface that reports it and the IPC surface is what
568
659
  // knows how to reach the window.
660
+ //
661
+ // AND A COPY RUN FROM SOURCE watches its git branch instead of a release
662
+ // feed (main/source-updater.mjs), because that is how teammates run the team
663
+ // build and their updates are pushes to main, not releases.
569
664
  let pushUpdate = () => {};
570
- const updater = createUpdater({
665
+ const updater = app.isPackaged ? createUpdater({
666
+ app,
667
+ onChanged: () => pushUpdate(),
668
+ }) : createSourceUpdater({
571
669
  app,
670
+ appDir,
572
671
  onChanged: () => pushUpdate(),
573
672
  });
574
673
 
@@ -577,6 +676,7 @@ async function createWindow() {
577
676
  const ipc = registerIpc({
578
677
  store, supervisor, config, window, analytics, docGrants, updater,
579
678
  host: { ipcMain, app, dialog },
679
+ team,
580
680
  });
581
681
  pushUpdate = ipc.push;
582
682
  // The Agents menu's Allow/Deny, bound to the real door now that there is one.
@@ -584,6 +684,9 @@ async function createWindow() {
584
684
  // Codex card.
585
685
  answerApproval = ipc.answerApproval;
586
686
  updater.start();
687
+ // A task the app just shipped moved main: look now rather than in half an
688
+ // hour, so the restart is offered while she still remembers the task.
689
+ supervisor.onShipped = () => { try { updater.check?.(); } catch {} };
587
690
  app.on('before-quit', () => updater.stop());
588
691
 
589
692
  // Cmd+Y / Cmd+N answer the oldest approval card from ANYWHERE, including
@@ -713,9 +816,7 @@ async function createWindow() {
713
816
  //
714
817
  // THIS LINE IS LOAD-BEARING: two tests slice main.mjs from the input handler
715
818
  // above to these words to read the chord wiring on its own, so the words at
716
- // the start of it are not free to change. It used to read "// The arcade.",
717
- // because what stood here was the emulator, its save vault and the shelf that
718
- // reached them. All of that is deleted; see decisions.md.
819
+ // the start of it are not free to change.
719
820
 
720
821
  store.watch();
721
822
  // ZERO_NO_SUPERVISOR: run the inbox without spawning any sessions (UI work,
@@ -802,6 +903,10 @@ async function createWindow() {
802
903
  webPreferences.autoplayPolicy = 'document-user-activation-required';
803
904
  });
804
905
 
906
+ // A page in the pane hides its scrollbar until you scroll, like the app's own
907
+ // (frame-scrollbars.mjs). Without it, a mouse on macOS gets a white track.
908
+ quietTheFramesScrollbars(window.webContents);
909
+
805
910
  // Escape must ALWAYS leave the junk browser, even when the guest page has
806
911
  // focus and would otherwise swallow the key. Intercept it below the page.
807
912
  app.on('web-contents-created', (_event, contents) => {
@@ -917,6 +1022,20 @@ if (!app.requestSingleInstanceLock()) {
917
1022
  }
918
1023
  });
919
1024
  app.whenReady().then(() => {
1025
+ // A from-source Linux launch keeps the launcher command installed. The
1026
+ // entry runs that command; this only refreshes where it points. A packaged
1027
+ // build ships its own entry and must not write one from here.
1028
+ // LOADED HERE AND NOT AT THE TOP: scripts/ is not in the packaged app or the
1029
+ // npx package, and a static import of it stopped main.mjs loading at all,
1030
+ // on every Mac (tests/the-mac-app-ships-every-file-it-starts-with).
1031
+ if (process.platform === 'linux' && !app.isPackaged) {
1032
+ import('../scripts/linux-launcher.mjs')
1033
+ .then(({ installLinuxLauncher }) => installLinuxLauncher({ appDir, env: process.env }))
1034
+ .catch(() => { /* a launcher is never a reason to stop a launch */ });
1035
+ }
1036
+ // THE ABOUT BOX SAYS AGENTBOX AND ITS OWN VERSION (w-db6f5e331e). Run from
1037
+ // source it read the bundle's, which is Electron's: "Electron 43.0.0".
1038
+ app.setAboutPanelOptions({ applicationName: NAME, applicationVersion: app.getVersion(), version: '' });
920
1039
  // Serve a granted file, and nothing else. A page under this scheme is one
921
1040
  // of ours, but it is written by an agent and read by her, so the check is
922
1041
  // on every request rather than only on the one the pane asked for.
@@ -930,9 +1049,9 @@ if (!app.requestSingleInstanceLock()) {
930
1049
  // Nothing is injected into a page any more; see doc-scheme.mjs for the
931
1050
  // pinch zoom that briefly lived here and why she turned it down.
932
1051
  //
933
- // NO-STORE, OR A REDRAWN PICTURE NEVER REACHES HER. Hers, w-3fc39983be,
934
- // 2026-09-28: "the font in the images is STILL not changing. Seems like
935
- // a bug". It was. An agent redrew six pictures under the same names;
1052
+ // NO-STORE, OR A REDRAWN PICTURE NEVER REACHES THE READER (w-3fc39983be).
1053
+ // The font in redrawn pictures looked unchanged, and that was a bug.
1054
+ // An agent redrew six pictures under the same names;
936
1055
  // the page's html came back new and every <img> in it came back from
937
1056
  // the window's memory, round one's pictures under round two's captions,
938
1057
  // until a restart. Measured in a hidden probe of this exact setup
@@ -952,7 +1071,11 @@ if (!app.requestSingleInstanceLock()) {
952
1071
  const file = imgPath(request.url);
953
1072
  if (!file || !servable(file, imageRoots())) return new Response('Not available.', { status: 403 });
954
1073
  try {
955
- const res = await net.fetch(pathToFileURL(file).toString());
1074
+ // A sound or a film is answered in the piece the player asked for, or
1075
+ // it plays but cannot be moved (mediaResponse, main/img-scheme.mjs).
1076
+ const res = mediaType(file)
1077
+ ? await mediaResponse(file, request.headers.get('range'))
1078
+ : await net.fetch(pathToFileURL(file).toString());
956
1079
  // The window is allowed to read its own pictures back out, which is
957
1080
  // what a copy needs. This widens nothing: the two rules above already
958
1081
  // decided this file may be seen at all, and only this window can ask on
@@ -0,0 +1,254 @@
1
+ // WHICH COMMANDS ARE HEAVY ON THIS MAC, LEARNED FROM WHAT THEY DID (w-3958c3753d).
2
+ //
3
+ // NOTHING HERE KNOWS WHAT ANY PROGRAM IS. The first proposal for holding heavy
4
+ // work back matched names (npm, pytest, xcodebuild) and the founder turned it
5
+ // down: another package manager, a script run directly, a training job, and a
6
+ // list of names is wrong. So a command is filed under what it is (its program
7
+ // and the first word after it that is not a flag) and judged by what it was
8
+ // measured using, here, the last few times it ran.
9
+ //
10
+ // THE ONE RULE THAT IS NOT NEGOTIABLE: missing evidence never makes a command
11
+ // light. Light means "seen light, more than once, and never heavy since". One
12
+ // heavy run is enough to make that exact command heavy. A program that has been
13
+ // light every time across many different arguments lends that to an argument
14
+ // it has not seen, and stops the moment one of its runs is not light.
15
+ //
16
+ // A RUN THAT ENDED INSIDE 2 SECONDS AND WAS NEVER SEEN BIG COUNTS AS LIGHT.
17
+ // Measured on 2026-10-04 across 30 recent agent sessions (2006 commands): the
18
+ // median command ran 1.3 s, so most finish between two samples and would never
19
+ // be measured at all. A command that short cannot hold memory long enough to
20
+ // starve anything, which is the whole of what this gate is for. A command sent
21
+ // to the background is the exception: its tool call returns at once while its
22
+ // process carries on, so its quickness proves nothing.
23
+ //
24
+ // The five lines of shell knowledge below are about the SHELL, not about any
25
+ // program: builtins start nothing, and time/nice/env/timeout/nohup only run the
26
+ // command after them.
27
+
28
+ const BUILTINS = new Set(['cd', 'export', 'echo', 'printf', 'true', 'false', 'test', '[', '[[', ']]', 'set', 'unset',
29
+ 'pwd', 'read', 'exit', 'return', 'shift', 'wait', ':', 'alias', 'type', 'local', 'declare', 'pushd', 'popd', 'umask']);
30
+ const WRAPPERS = new Set(['time', 'nice', 'env', 'timeout', 'nohup', 'caffeinate', 'exec', 'command']);
31
+ const ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
32
+ const REDIRECT = /^\d*(>>?|<<?<?|&>>?|>&|<&)/;
33
+
34
+ /** Light below this many MB at its sampled peak, heavy at or above HEAVY_MB. */
35
+ export const LIGHT_MB = 150;
36
+ export const HEAVY_MB = 400;
37
+ /** A run this short, never seen big, counts as light. */
38
+ export const QUICK_MS = 2_000;
39
+
40
+ /**
41
+ * Split a shell command into the programs it starts, as `program firstArg`
42
+ * keys, without running or fully parsing it. `opaque` is true when part of it
43
+ * is computed at run time ($(...) or backticks outside single quotes), which
44
+ * this cannot see into.
45
+ */
46
+ export function commandSegments(command) {
47
+ const text = String(command ?? '');
48
+ const segments = [];
49
+ let words = [];
50
+ let word = '';
51
+ let inWord = false;
52
+ let quote = null;
53
+ let opaque = false;
54
+ const heredocs = [];
55
+ const endWord = () => {
56
+ if (inWord) words.push(word);
57
+ word = ''; inWord = false;
58
+ };
59
+ const endSegment = () => { endWord(); if (words.length) segments.push(words); words = []; };
60
+
61
+ for (let i = 0; i < text.length; i++) {
62
+ const c = text[i];
63
+ if (quote === "'") {
64
+ if (c === "'") quote = null; else word += c;
65
+ continue;
66
+ }
67
+ if (c === '\\' && i + 1 < text.length) { word += text[++i]; inWord = true; continue; }
68
+ if (quote === '"') {
69
+ if (c === '"') quote = null;
70
+ else {
71
+ if (c === '`' || (c === '$' && text[i + 1] === '(')) opaque = true;
72
+ word += c;
73
+ }
74
+ continue;
75
+ }
76
+ if (c === "'" || c === '"') { quote = c; inWord = true; continue; }
77
+ if (c === '`' || (c === '$' && text[i + 1] === '(')) opaque = true;
78
+ if (c === '<' && text[i + 1] === '<' && text[i + 2] !== '<') {
79
+ // A here-document: its body is text handed to a program, not commands.
80
+ endWord();
81
+ let j = i + 2;
82
+ if (text[j] === '-') j++;
83
+ while (text[j] === ' ') j++;
84
+ let delim = '';
85
+ while (j < text.length && !/[\s;&|]/.test(text[j])) delim += text[j++];
86
+ heredocs.push(delim.replace(/^['"]|['"]$/g, ''));
87
+ i = j - 1;
88
+ continue;
89
+ }
90
+ if (c === '\n') {
91
+ endSegment();
92
+ while (heredocs.length) {
93
+ const delim = heredocs.shift();
94
+ let end = text.indexOf('\n', i + 1);
95
+ while (true) {
96
+ const line = text.slice(i + 1, end === -1 ? text.length : end);
97
+ i = end === -1 ? text.length : end;
98
+ if (line.trim() === delim || end === -1) break;
99
+ end = text.indexOf('\n', i + 1);
100
+ }
101
+ }
102
+ continue;
103
+ }
104
+ if (c === ';' || c === '|' || (c === '&' && !/[<>]$/.test(word) && text[i + 1] !== '>')) {
105
+ endSegment();
106
+ if ((c === '|' || c === '&') && text[i + 1] === c) i++;
107
+ continue;
108
+ }
109
+ if (c === ' ' || c === '\t') { endWord(); continue; }
110
+ word += c; inWord = true;
111
+ }
112
+ endSegment();
113
+
114
+ const keys = [];
115
+ for (const seg of segments) {
116
+ const key = keyOf(seg);
117
+ if (key) keys.push(key);
118
+ }
119
+ return { keys, opaque };
120
+ }
121
+
122
+ function keyOf(segmentWords) {
123
+ const words = [];
124
+ for (let i = 0; i < segmentWords.length; i++) {
125
+ const w = segmentWords[i];
126
+ if (REDIRECT.test(w)) {
127
+ // `> out.txt` carries its target as the next word; `>out.txt` does not.
128
+ if (/^\d*(>>?|<|&>>?)$/.test(w)) i++;
129
+ continue;
130
+ }
131
+ if (w === '(' || w === ')' || w === '{' || w === '}' || w === '!') continue;
132
+ words.push(w);
133
+ }
134
+ let i = 0;
135
+ while (i < words.length && ASSIGNMENT.test(words[i])) i++;
136
+ while (i < words.length && WRAPPERS.has(basename(words[i]))) {
137
+ i++;
138
+ while (i < words.length && (words[i].startsWith('-') || /^\d+(\.\d+)?[smhd]?$/.test(words[i]) || ASSIGNMENT.test(words[i]))) i++;
139
+ }
140
+ if (i >= words.length) return null;
141
+ const program = basename(words[i]);
142
+ if (!program || BUILTINS.has(program)) return null;
143
+ const arg = words.slice(i + 1).find((w) => !w.startsWith('-'));
144
+ return arg ? `${program} ${arg.slice(0, 80)}` : program;
145
+ }
146
+
147
+ function basename(p) {
148
+ const s = String(p);
149
+ const at = s.lastIndexOf('/');
150
+ return at === -1 ? s : s.slice(at + 1);
151
+ }
152
+
153
+ const programOf = (key) => key.split(' ')[0];
154
+
155
+ /**
156
+ * The learned record. Each key and each program keeps its last ten
157
+ * observations as letters: l light, m in between, h heavy, ? not measured.
158
+ * Both maps are kept in least-recently-seen-first order so the oldest falls off
159
+ * first when either passes `maxKeys`.
160
+ */
161
+ export class CommandHistory {
162
+ constructor({ lightMb = LIGHT_MB, heavyMb = HEAVY_MB, maxKeys = 2_000 } = {}) {
163
+ this.lightMb = lightMb;
164
+ this.heavyMb = heavyMb;
165
+ this.maxKeys = maxKeys;
166
+ this.keys = new Map();
167
+ this.programs = new Map();
168
+ }
169
+
170
+ /** What one finished run tells us. `peakMb` null means it was never sampled. */
171
+ record(command, { peakMb = null, durationMs = null, background = false } = {}) {
172
+ const { keys, opaque } = commandSegments(command);
173
+ if (!keys.length) return;
174
+ let obs;
175
+ if (typeof peakMb === 'number' && Number.isFinite(peakMb)) {
176
+ obs = peakMb >= this.heavyMb ? 'h' : peakMb < this.lightMb ? 'l' : 'm';
177
+ } else {
178
+ obs = !background && typeof durationMs === 'number' && durationMs < QUICK_MS ? 'l' : '?';
179
+ }
180
+ // A chain's memory belongs to the chain: `git status && npm test` measured
181
+ // heavy says nothing bad about `git status`. Only a light chain vouches for
182
+ // each of its parts.
183
+ if (keys.length > 1 || opaque) {
184
+ this._push(this.keys, chainKey(keys, opaque), obs);
185
+ if (obs !== 'l') return;
186
+ }
187
+ for (const key of keys) {
188
+ this._push(this.keys, key, obs);
189
+ this._push(this.programs, programOf(key), obs);
190
+ }
191
+ }
192
+
193
+ /** 'light' | 'unknown' | 'heavy' for a command about to run. */
194
+ classify(command) {
195
+ const { keys, opaque } = commandSegments(command);
196
+ if (!keys.length && !opaque) return 'light';
197
+ let verdict = 'light';
198
+ const worse = (v) => {
199
+ if (v === 'heavy' || verdict === 'heavy') verdict = 'heavy';
200
+ else if (v === 'unknown') verdict = 'unknown';
201
+ };
202
+ if (keys.length > 1 || opaque) {
203
+ const chain = this.keys.get(chainKey(keys, opaque));
204
+ if (chain?.slice(-5).includes('h')) return 'heavy';
205
+ if (opaque) worse('unknown');
206
+ }
207
+ for (const key of keys) worse(this._classifyKey(key));
208
+ return verdict;
209
+ }
210
+
211
+ _classifyKey(key) {
212
+ const own = this.keys.get(key);
213
+ const recent = own?.slice(-5) ?? [];
214
+ if (recent.includes('h')) return 'heavy';
215
+ if (own && own.length >= 2 && recent.every((o) => o === 'l')) return 'light';
216
+ if (recent.some((o) => o !== 'l')) return 'unknown';
217
+ const prog = this.programs.get(programOf(key));
218
+ if (prog && prog.length >= 5 && prog.slice(-10).every((o) => o === 'l')) return 'light';
219
+ return 'unknown';
220
+ }
221
+
222
+ _push(map, key, obs) {
223
+ const list = map.get(key) ?? [];
224
+ map.delete(key);
225
+ list.push(obs);
226
+ map.set(key, list.slice(-10));
227
+ while (map.size > this.maxKeys) map.delete(map.keys().next().value);
228
+ }
229
+
230
+ toJSON() {
231
+ return { v: 1, keys: [...this.keys], programs: [...this.programs] };
232
+ }
233
+
234
+ /** Anything unreadable is an empty history, which only means "unknown". */
235
+ static fromJSON(raw, opts) {
236
+ const h = new CommandHistory(opts);
237
+ if (!raw || raw.v !== 1) return h;
238
+ const load = (map, entries) => {
239
+ if (!Array.isArray(entries)) return;
240
+ for (const e of entries) {
241
+ if (Array.isArray(e) && typeof e[0] === 'string' && Array.isArray(e[1])) {
242
+ map.set(e[0], e[1].filter((o) => ['l', 'm', 'h', '?'].includes(o)).slice(-10));
243
+ }
244
+ }
245
+ };
246
+ load(h.keys, raw.keys);
247
+ load(h.programs, raw.programs);
248
+ return h;
249
+ }
250
+ }
251
+
252
+ function chainKey(keys, opaque) {
253
+ return `${opaque ? '$( ' : ''}${keys.join(' && ')}`;
254
+ }