agentbox-app 0.1.7 → 0.1.11

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 (244) hide show
  1. package/README.md +209 -55
  2. package/bin/agentbox.mjs +6 -0
  3. package/briefs/message-rules.md +13 -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 +9 -10
  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 +114 -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/artifact-path.mjs +11 -0
  16. package/main/claude-bin.mjs +31 -5
  17. package/main/claude-input.mjs +45 -3
  18. package/main/claude-plan.mjs +11 -11
  19. package/main/code-change.mjs +3 -3
  20. package/main/codex-account.mjs +1 -1
  21. package/main/codex-bin.mjs +17 -0
  22. package/main/codex-memory-gate.mjs +260 -0
  23. package/main/codex-models.mjs +12 -7
  24. package/main/codex-threads.mjs +46 -23
  25. package/main/codex-usage-file.mjs +7 -7
  26. package/main/codex-usage.mjs +4 -4
  27. package/main/codex-watch.mjs +12 -12
  28. package/main/config.mjs +68 -8
  29. package/main/dev-window.mjs +9 -9
  30. package/main/doc-scheme.mjs +2 -0
  31. package/main/engine-setup.mjs +193 -0
  32. package/main/feedback.mjs +55 -0
  33. package/main/folder-pictures.mjs +48 -0
  34. package/main/frame-scrollbars.mjs +90 -0
  35. package/main/fresh-user.mjs +6 -3
  36. package/main/git-change-offthread.mjs +70 -0
  37. package/main/git-change-thread.mjs +11 -0
  38. package/main/git-change.mjs +1 -1
  39. package/main/img-scheme.mjs +70 -1
  40. package/main/instruction-settings.mjs +92 -54
  41. package/main/ipc.mjs +316 -42
  42. package/main/leftovers.mjs +518 -0
  43. package/main/live-replies.mjs +43 -5
  44. package/main/machine.mjs +47 -0
  45. package/main/main.mjs +119 -15
  46. package/main/memory-gate-history.mjs +254 -0
  47. package/main/memory-gate-server.mjs +461 -0
  48. package/main/memory-gate.mjs +206 -0
  49. package/main/message-priority.mjs +87 -0
  50. package/main/rail-note.mjs +2 -2
  51. package/main/refresh-agent-models.mjs +20 -0
  52. package/main/repeats.mjs +4 -4
  53. package/main/row-label.mjs +114 -51
  54. package/main/serve.mjs +67 -5
  55. package/main/settings.mjs +223 -26
  56. package/main/ship-queue.mjs +279 -0
  57. package/main/sign-in-files.mjs +39 -0
  58. package/main/source-updater.mjs +298 -0
  59. package/main/store/home.mjs +47 -2
  60. package/main/store/work-items.mjs +150 -6
  61. package/main/store.mjs +235 -9
  62. package/main/supervisor.mjs +1444 -406
  63. package/main/task-commands.mjs +8 -10
  64. package/main/task-folders.mjs +74 -13
  65. package/main/task-remote-control.mjs +3 -3
  66. package/main/task-terminals.mjs +2 -1
  67. package/main/team/index.mjs +440 -0
  68. package/main/team/memory-cloud.mjs +247 -0
  69. package/main/team/projects.mjs +162 -0
  70. package/main/team/session.mjs +225 -0
  71. package/main/team/sign-in.mjs +106 -0
  72. package/main/team/supabase-backend.mjs +231 -0
  73. package/main/team/sync.mjs +163 -0
  74. package/main/updater.mjs +15 -5
  75. package/main/write-before-reload.mjs +50 -0
  76. package/mcp/core/page.mjs +9 -11
  77. package/mcp/core/work.mjs +91 -25
  78. package/mcp/tools.mjs +12 -6
  79. package/package.json +21 -9
  80. package/renderer/dist/assets/agentbox-icon-F1oN9nj_.svg +15 -0
  81. package/renderer/dist/assets/index-B-2Az952.css +32 -0
  82. package/renderer/dist/assets/index-D6qncgai.js +1168 -0
  83. package/renderer/dist/index.html +2 -2
  84. package/scripts/memory-gate-hook.sh +59 -0
  85. package/shared/agent-import.mjs +38 -15
  86. package/shared/agents.d.mts +26 -8
  87. package/shared/agents.mjs +100 -108
  88. package/shared/analytics-events.mjs +19 -2
  89. package/shared/answers.mjs +12 -0
  90. package/shared/bridge-map.d.mts +12 -0
  91. package/shared/bridge-map.mjs +37 -1
  92. package/shared/claude-commands.generated.mjs +2 -2
  93. package/shared/claude-models.generated.mjs +4 -4
  94. package/shared/claude-usage.mjs +6 -6
  95. package/shared/codex-import.mjs +7 -7
  96. package/shared/codex-modes.d.mts +50 -0
  97. package/shared/codex-usage.mjs +7 -8
  98. package/shared/demo-world.mjs +5 -5
  99. package/shared/engines.mjs +27 -10
  100. package/shared/feedback.d.mts +14 -0
  101. package/shared/feedback.mjs +44 -0
  102. package/shared/first-run-examples.mjs +4 -4
  103. package/shared/first-run-practice.d.mts +16 -0
  104. package/shared/first-run-practice.mjs +176 -70
  105. package/shared/fresh-user-home.mjs +22 -3
  106. package/shared/instruction-defaults.json +2 -2
  107. package/shared/product-name.mjs +4 -2
  108. package/shared/project-folder-check.mjs +6 -0
  109. package/shared/rank.d.mts +1 -0
  110. package/shared/rank.mjs +22 -0
  111. package/shared/referenced-files.mjs +1 -1
  112. package/shared/schemes.d.mts +11 -0
  113. package/shared/schemes.mjs +1 -1
  114. package/shared/shortcuts.d.mts +41 -0
  115. package/shared/shortcuts.mjs +236 -0
  116. package/shared/spawn-trouble.d.mts +2 -0
  117. package/shared/spawn-trouble.mjs +39 -18
  118. package/shared/team-rules.d.mts +17 -0
  119. package/shared/team-rules.mjs +230 -0
  120. package/shared/team-status.d.mts +11 -0
  121. package/shared/team-status.mjs +90 -0
  122. package/shared/terminal-links.d.mts +2 -0
  123. package/shared/terminal-links.mjs +55 -0
  124. package/shared/terminal-state.d.mts +1 -0
  125. package/shared/terminal-state.mjs +16 -0
  126. package/shared/thread-cards.d.mts +19 -0
  127. package/shared/thread-cards.mjs +254 -0
  128. package/shared/usage.d.mts +1 -1
  129. package/shared/usage.mjs +6 -7
  130. package/shared/work-items.mjs +255 -27
  131. package/shared/work-lines.mjs +393 -16
  132. package/worker-permissions.json +1 -1
  133. package/renderer/dist/assets/cel-dusk-CS5I7kJS.webp +0 -0
  134. package/renderer/dist/assets/cel-dusk-DUslqS6B.webp +0 -0
  135. package/renderer/dist/assets/cel-dusk-Jt_xVGxp.webp +0 -0
  136. package/renderer/dist/assets/cel-dusk-sm-BBkV-tWT.webp +0 -0
  137. package/renderer/dist/assets/frost-haze-CxPSOBEg.webp +0 -0
  138. package/renderer/dist/assets/frost-haze-sm-C_w21qgd.webp +0 -0
  139. package/renderer/dist/assets/ice-BC8luINc.webp +0 -0
  140. package/renderer/dist/assets/ice-CP9PrKHo.webp +0 -0
  141. package/renderer/dist/assets/ice-river-D6ziOhLY.webp +0 -0
  142. package/renderer/dist/assets/ice-river-UVM2-JY_.webp +0 -0
  143. package/renderer/dist/assets/ice-river-bErJwYWW.webp +0 -0
  144. package/renderer/dist/assets/ice-river-sm-Bg1U-SIj.webp +0 -0
  145. package/renderer/dist/assets/ice-sm-CbVcg3_D.webp +0 -0
  146. package/renderer/dist/assets/index-B7hhfHrT.js +0 -684
  147. package/renderer/dist/assets/index-CwiDv5bL.css +0 -32
  148. package/renderer/dist/assets/lake-C0WaW-6E.webp +0 -0
  149. package/renderer/dist/assets/lake-CSKjLC5K.webp +0 -0
  150. package/renderer/dist/assets/lake-ETGcOb25.webp +0 -0
  151. package/renderer/dist/assets/lake-harbour-BCQRbrHb.webp +0 -0
  152. package/renderer/dist/assets/lake-harbour-B_wfXTmE.webp +0 -0
  153. package/renderer/dist/assets/lake-harbour-Te1pEvBh.webp +0 -0
  154. package/renderer/dist/assets/lake-harbour-sm-Cme_ShEg.webp +0 -0
  155. package/renderer/dist/assets/lake-sm-CbG72Hbv.webp +0 -0
  156. package/renderer/dist/assets/lino-coast-CUZHcucI.webp +0 -0
  157. package/renderer/dist/assets/lino-coast-DP4J7cgd.webp +0 -0
  158. package/renderer/dist/assets/lino-coast-kOIAffr_.webp +0 -0
  159. package/renderer/dist/assets/lino-coast-sm-C1Mdbgit.webp +0 -0
  160. package/renderer/dist/assets/moon-pines-BskgnnXR.webp +0 -0
  161. package/renderer/dist/assets/moon-pines-CFSIIwt0.webp +0 -0
  162. package/renderer/dist/assets/moon-pines-PNhdOYww.webp +0 -0
  163. package/renderer/dist/assets/moon-pines-sm-C6K1NomK.webp +0 -0
  164. package/renderer/dist/assets/orbit-rings-2XV36Ch3.webp +0 -0
  165. package/renderer/dist/assets/orbit-rings-C4NrcYGQ.webp +0 -0
  166. package/renderer/dist/assets/orbit-rings-DGLk07dT.webp +0 -0
  167. package/renderer/dist/assets/orbit-rings-sm-Dky3YdvL.webp +0 -0
  168. package/renderer/dist/assets/peach-haze-2-C6f7xnWw.webp +0 -0
  169. package/renderer/dist/assets/peach-haze-2-sm-Dj_Ez_Sb.webp +0 -0
  170. package/renderer/dist/assets/pixel-harbour-DQjqMROg.webp +0 -0
  171. package/renderer/dist/assets/pixel-harbour-Dw71H_bc.webp +0 -0
  172. package/renderer/dist/assets/pixel-harbour-kOZfm-GM.webp +0 -0
  173. package/renderer/dist/assets/pixel-harbour-sm-Dturjuky.webp +0 -0
  174. package/renderer/dist/assets/pixel-orbit-C296JVMO.webp +0 -0
  175. package/renderer/dist/assets/pixel-orbit-C4oMRDSx.webp +0 -0
  176. package/renderer/dist/assets/pixel-orbit-CqVmQ4eh.webp +0 -0
  177. package/renderer/dist/assets/pixel-orbit-sm-CHagC_nO.webp +0 -0
  178. package/renderer/dist/assets/pixel-ridge-C6LYu6uE.webp +0 -0
  179. package/renderer/dist/assets/pixel-ridge-CIUCEESs.webp +0 -0
  180. package/renderer/dist/assets/pixel-ridge-sm-BnE0F_w-.webp +0 -0
  181. package/renderer/dist/assets/riso-hills-7heKVtry.webp +0 -0
  182. package/renderer/dist/assets/riso-hills-CIjAL3J6.webp +0 -0
  183. package/renderer/dist/assets/riso-hills-sm-DHsnbCfP.webp +0 -0
  184. package/renderer/dist/assets/slate-haze-BPWdkNsw.webp +0 -0
  185. package/renderer/dist/assets/slate-haze-sm-B0Cb5a2W.webp +0 -0
  186. package/renderer/dist/assets/valley-haze-BDHPqoJy.webp +0 -0
  187. package/renderer/dist/assets/valley-haze-sm-CTyjmPAG.webp +0 -0
  188. package/renderer/dist/assets/vector-dunes-DzzWLdKo.webp +0 -0
  189. package/renderer/dist/assets/vector-dunes-Yr9NBQUy.webp +0 -0
  190. package/renderer/dist/assets/vector-dunes-sm-D8MYY6ms.webp +0 -0
  191. package/renderer/dist/assets/watercolour-mist-BCGKN4u4.webp +0 -0
  192. package/renderer/dist/assets/watercolour-mist-BF-jk5MC.webp +0 -0
  193. package/renderer/dist/assets/watercolour-mist-sm-Ds1xzTw9.webp +0 -0
  194. package/renderer/dist/assets/woodblock-sea-B1um67Z9.webp +0 -0
  195. package/renderer/dist/assets/woodblock-sea-DKQdYs32.webp +0 -0
  196. package/renderer/dist/assets/woodblock-sea-sm-BJzNq8xh.webp +0 -0
  197. package/renderer/dist/emulator/audio-rules.mjs +0 -74
  198. package/renderer/dist/emulator/data/compression/README.md +0 -11
  199. package/renderer/dist/emulator/data/compression/extract7z.js +0 -1
  200. package/renderer/dist/emulator/data/compression/extractzip.js +0 -1
  201. package/renderer/dist/emulator/data/compression/libunrar.js +0 -519
  202. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  203. package/renderer/dist/emulator/data/cores/README.md +0 -22
  204. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  205. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  206. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  207. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  208. package/renderer/dist/emulator/data/cores/package.json +0 -65
  209. package/renderer/dist/emulator/data/cores/reports/mgba.json +0 -1
  210. package/renderer/dist/emulator/data/emulator.css +0 -1614
  211. package/renderer/dist/emulator/data/loader.js +0 -170
  212. package/renderer/dist/emulator/data/localization/README.md +0 -63
  213. package/renderer/dist/emulator/data/localization/af-FR.json +0 -339
  214. package/renderer/dist/emulator/data/localization/ar-AR.json +0 -301
  215. package/renderer/dist/emulator/data/localization/ben-BEN.json +0 -301
  216. package/renderer/dist/emulator/data/localization/de-GER.json +0 -301
  217. package/renderer/dist/emulator/data/localization/el-GR.json +0 -301
  218. package/renderer/dist/emulator/data/localization/en-US.json +0 -339
  219. package/renderer/dist/emulator/data/localization/es-ES.json +0 -310
  220. package/renderer/dist/emulator/data/localization/fa-AF.json +0 -310
  221. package/renderer/dist/emulator/data/localization/hi-HI.json +0 -301
  222. package/renderer/dist/emulator/data/localization/it-IT.json +0 -302
  223. package/renderer/dist/emulator/data/localization/ja-JA.json +0 -301
  224. package/renderer/dist/emulator/data/localization/jv-JV.json +0 -301
  225. package/renderer/dist/emulator/data/localization/ko-KO.json +0 -336
  226. package/renderer/dist/emulator/data/localization/pt-BR.json +0 -361
  227. package/renderer/dist/emulator/data/localization/retroarch.json +0 -617
  228. package/renderer/dist/emulator/data/localization/ro-RO.json +0 -310
  229. package/renderer/dist/emulator/data/localization/ru-RU.json +0 -301
  230. package/renderer/dist/emulator/data/localization/tr-TR.json +0 -310
  231. package/renderer/dist/emulator/data/localization/vi-VN.json +0 -302
  232. package/renderer/dist/emulator/data/localization/zh-CN.json +0 -351
  233. package/renderer/dist/emulator/data/src/GameManager.js +0 -464
  234. package/renderer/dist/emulator/data/src/compression.js +0 -142
  235. package/renderer/dist/emulator/data/src/emulator.js +0 -6158
  236. package/renderer/dist/emulator/data/src/gamepad.js +0 -167
  237. package/renderer/dist/emulator/data/src/nipplejs.js +0 -1
  238. package/renderer/dist/emulator/data/src/shaders.js +0 -236
  239. package/renderer/dist/emulator/data/src/socket.io.min.js +0 -7
  240. package/renderer/dist/emulator/data/src/storage.js +0 -125
  241. package/renderer/dist/emulator/data/version.json +0 -5
  242. package/renderer/dist/emulator/host.js +0 -299
  243. package/renderer/dist/emulator/index.html +0 -28
  244. package/renderer/dist/emulator/provenance.json +0 -6
package/shared/agents.mjs CHANGED
@@ -49,24 +49,22 @@ export function sameInstant(a, b) {
49
49
  // already records each worker's sessionId when it spawns it. Either alone is
50
50
  // enough; both means the fold cannot drift as one of them changes.
51
51
  //
52
- // This matters because without it Agentbox shouts its own work back at her. In
53
- // the raw picture she asked to see, rows two and three were agentbox-bf and
54
- // agentbox-a8 — Agentbox's own workers, already in her inbox as work items, filing
55
- // a second row about themselves.
52
+ // This matters because without it Agentbox shouts its own work back at the
53
+ // user. In the raw list, Agentbox's own workers, already in the inbox as work
54
+ // items, showed up again as a second row about themselves.
56
55
  export function startedByZero(agent, { pids = new Set(), sessionIds = new Set() } = {}) {
57
56
  if (agent.sessionId && sessionIds.has(agent.sessionId)) return true;
58
57
  return !!agent.ppid && pids.has(agent.ppid);
59
58
  }
60
59
 
61
60
  /* --------------------------- is it asking anything ----------------------- */
62
- // A ROW THAT ASKS NOTHING DOES NOT GET FILED. Her standing rule, and the one
63
- // place this feature could most easily break it: of the 13 outside agents on
64
- // her machine, 12 were idle and 1 was waiting. Filing all 13 puts twelve rows
65
- // in front of her that say "nothing is asked", which is the picture she
66
- // rejected.
67
- //
68
- // So the inbox takes exactly the agents that are waiting on a human. The other
69
- // twelve are still listed, still findable, one keystroke away — they just do
61
+ // A ROW THAT ASKS NOTHING DOES NOT GET FILED. A standing rule, and the one
62
+ // place this feature could most easily break it: most outside agents on a
63
+ // machine are idle at any moment, and filing them all puts a wall of rows in
64
+ // the inbox that say "nothing is asked".
65
+ //
66
+ // So the inbox takes exactly the agents that are waiting on a human. The
67
+ // others are still listed, still findable, one keystroke away — they just do
70
68
  // not interrupt.
71
69
  export function asksSomething(agent) {
72
70
  return agent.status === 'waiting';
@@ -79,14 +77,14 @@ export function asksSomething(agent) {
79
77
  // "input needed" the agent finished its turn and is waiting for a human
80
78
  // to say something next. A message IS that human turn.
81
79
  // Proven on this machine 2026-08-16: a message written
82
- // to a live session's socket arrived, was read as her
83
- // turn, and the agent went back to work.
80
+ // to a live session's socket arrived, was read as the
81
+ // human turn, and the agent went back to work.
84
82
  //
85
83
  // "permission prompt" the agent is frozen on a box asking to approve a tool
86
84
  // call. A message queues BEHIND that box. Sending one
87
85
  // would look like it worked and change nothing, which is
88
86
  // the failure this codebase cares about most: the system
89
- // swallowing something she said.
87
+ // swallowing something the user said.
90
88
  //
91
89
  // So a reply box appears on the first and never on the second, and the second
92
90
  // says out loud that it has to be answered where it is running.
@@ -105,7 +103,7 @@ export function replyReaches(agent) {
105
103
  // there was nothing there to answer. That sentence was wrong.
106
104
  //
107
105
  // MEASURED 2026-08-17, not reasoned: a throwaway session was left sitting at
108
- // its prompt with nothing asked — the exact state of the card she sent — and
106
+ // its prompt with nothing asked, the exact state of the card in question, and
109
107
  // the same line `reply` writes was written into its socket. It arrived, the
110
108
  // session read it and went back to work and answered it. An idle session is
111
109
  // listening; idle is what a session at its prompt IS.
@@ -142,12 +140,12 @@ export function replyIsSwallowed(agent) {
142
140
  // because a timer here ran out.
143
141
  //
144
142
  // The comparison is `>`, against the stamp and not the status alone, because
145
- // the session is still idle for the moment or two between her pressing send and
146
- // it picking the message up. Reading the status alone would send the row
147
- // straight back to the inbox, which is the bug she reported wearing a hat.
143
+ // the session is still idle for the moment or two between the user pressing
144
+ // send and it picking the message up. Reading the status alone would send the
145
+ // row straight back to the inbox, which is the original bug wearing a hat.
148
146
  //
149
147
  // AND THE ONE SESSION THIS CANNOT PROMISE ANYTHING ABOUT: one that publishes no
150
- // status at all (1 of her 16, an older CLI). Agentbox cannot see that one work
148
+ // status at all (an older CLI). Agentbox cannot see that one work
151
149
  // and cannot see it stop, so it is not moved anywhere and the reply is still
152
150
  // delivered. A promise that could never be kept is worse than no promise.
153
151
  export function tookHerReply(agent) {
@@ -193,7 +191,7 @@ export function agentKey(agent) {
193
191
  return `pid:${agent?.pid ?? 0}:${agent?.startedAt ?? 0}`;
194
192
  }
195
193
 
196
- // The moment she said she would deal with it. A past moment is not a deferral:
194
+ // The moment the user said they would deal with it. A past moment is not a deferral:
197
195
  // once it passes the row is back, which is the same predicate rule work items
198
196
  // follow. So a missed one comes back late rather than being lost, and nothing
199
197
  // has to fire at 8am.
@@ -214,7 +212,7 @@ export function putOff(agent, now = Date.now()) {
214
212
  // session exists. WHAT THIS COSTS, stated rather than hidden: a session she
215
213
  // closes while it is working will not return to her inbox when it later stops
216
214
  // and asks something. That is the same sentence read the other way, and it is
217
- // the one she wrote.
215
+ // the one that was chosen.
218
216
  //
219
217
  // "UNLESS ASKED OTHERWISE" IS THE UNDO, and it is why this stays a number in
220
218
  // the file instead of becoming a boolean: `close(key, 0)` clears it, which is
@@ -229,8 +227,8 @@ export function putAway(agent) {
229
227
  }
230
228
 
231
229
  /* -------------------------------- the words ------------------------------ */
232
- // Plain sentences, no jargon, no metaphor (hers, 2026-08-16), and the first
233
- // line of the body is the one thing she has to do about it, in bold, one
230
+ // Plain sentences, no jargon, no metaphor, and the first line of the body is
231
+ // the one thing the user has to do about it, in bold, one
234
232
  // sentence, under fifteen words (instructions.md). The row clips at
235
233
  // SUMMARY_BUDGET = 112 characters on the last full stop inside it, so that
236
234
  // sentence has to carry the whole point on its own.
@@ -260,7 +258,7 @@ export function whereItRuns(agent) {
260
258
  // goes because a card is not a diff viewer.
261
259
  //
262
260
  // Split out from `oneSentence` when `howItEnded` needed the same cleaning from
263
- // the other end of the message. One copy, so a quote of hers and a quote of its
261
+ // the other end of the message. One copy, so a quote of the user's and a quote of its
264
262
  // answer can never be tidied by two different rules.
265
263
  function readableWords(raw) {
266
264
  return String(raw ?? '')
@@ -331,26 +329,25 @@ function saidWhen(agent, now) {
331
329
  return `**It answered ${when}:**`;
332
330
  }
333
331
 
334
- // THE LAST THING SHE TYPED INTO IT. Read straight off the session's own
335
- // `last-prompt` line, so it is her turn and never a tool result wearing a user
332
+ // THE LAST THING THE USER TYPED INTO IT. Read straight off the session's own
333
+ // `last-prompt` line, so it is the user's turn and never a tool result wearing a user
336
334
  // message's clothes.
337
335
  export function lastAsk(agent, budget = 180) {
338
336
  return oneSentence(agent.lastAsked, budget);
339
337
  }
340
338
 
341
339
  /* ------------------------------- what it IS ------------------------------ */
342
- // (hers), about a card whose loudest word was `session-71`. A session name is a
340
+ // A card whose loudest word is `session-71` says nothing. A session name is a
343
341
  // handle, not a memory.
344
342
  //
345
343
  // Every Claude Code session writes a one-line title of ITSELF into its own
346
- // transcript and keeps it current. Measured across her machine 2026-08-17: 14
347
- // of her 14 outside sessions had one, and session-71's was "Agentbox product not
348
- // displaying in the app" — the sentence she said she could not remember, three
349
- // days old, sitting in a file Agentbox was already reading and throwing away.
344
+ // transcript and keeps it current. Every outside session measured had one, and
345
+ // it is usually the very sentence a person would use to recall the work,
346
+ // sitting in a file Agentbox was already reading and throwing away.
350
347
  //
351
348
  // So the title of the row is what the work IS, with the session's name in front
352
- // of it because she has two of them running in the same folder and the name is
353
- // how she tells them apart. When a session has no title of its own the row says
349
+ // of it because two sessions can run in the same folder and the name is how
350
+ // they are told apart. When a session has no title of its own the row says
354
351
  // what it used to say, which is better than a blank.
355
352
  export function whatItIs(agent) {
356
353
  return String(agent?.about ?? '').replace(/\s+/g, ' ').trim();
@@ -361,57 +358,56 @@ export function agentTitle(agent, now = Date.now()) {
361
358
  if (about) return `${agent.name}: ${about}`;
362
359
  if (replyReaches(agent)) return `${agent.name} is waiting for you`;
363
360
  if (asksSomething(agent)) return `${agent.name} needs you where it is running`;
364
- // NOT "QUIET" ONCE SHE HAS SPOKEN TO IT. Without this the row she just
361
+ // NOT "QUIET" ONCE THE USER HAS SPOKEN TO IT. Without this the row just
365
362
  // replied to sat in In progress under a heading saying it had been quiet for
366
- // three days, which is the same sentence it wore before she said anything.
363
+ // three days, which is the same sentence it wore before anything was said.
367
364
  if (tookHerReply(agent)) return `${agent.name} is working on what you told it`;
368
365
  return `${agent.name} has been quiet for ${howLong(now - (agent.lastActiveAt ?? agent.startedAt ?? now))}`;
369
366
  }
370
367
 
371
368
  /* ------------------------------ RECALLING IT ----------------------------- */
372
- // Hers, 2026-08-17, about a card that already carried the title and the last
373
- // thing she typed.
369
+ // A card that carries only the title and the last thing typed is not enough to
370
+ // recall a session.
374
371
  //
375
372
  // WHAT WAS MISSING WAS THE BEGINNING, and it is the one thing on a transcript
376
- // that never changes. Measured over all 16 of her live sessions on 2026-08-17:
377
- // her FIRST message is the ask in 16 of 16, and no card had ever shown it.
378
- // `last-prompt` is the LAST thing she typed, and five days into a session that
379
- // is "okay no worries. just make sure that auth persists across sessions" —
380
- // perfectly true, and no help whatsoever in remembering what the thing is for.
373
+ // that never changes. In every live session measured, the FIRST message was
374
+ // the ask, and no card had ever shown it. `last-prompt` is the LAST thing the
375
+ // user typed, and days into a session that is usually a short follow-up that
376
+ // is perfectly true and no help whatsoever in remembering what the thing is for.
381
377
  //
382
- // Her side of a conversation is also small enough to just print: 155 turns
383
- // across 16 sessions, median 6 per session, median 131 characters each. The
384
- // megabytes in a transcript are the agent's half, not hers. So the recall is
385
- // her own words, read off the file, and no model sees any of it.
378
+ // The human side of a conversation is also small enough to just print: a
379
+ // handful of turns per session, a sentence or two each. The megabytes in a
380
+ // transcript are the agent's half. So the recall is the user's own words, read
381
+ // off the file, and no model sees any of it.
386
382
  //
387
383
  // ONE SHAPE, NOT THREE, AND IT IS NOT CONFIGURABLE.
388
384
  //
389
- // So the card does the same thing on every session: the message she started it
390
- // with, up to three later messages of hers, where it stopped, and where it is
391
- // working. `ask` and `trail` are gone, and there is deliberately no setting for
392
- // this — deciding per session which of them a card deserves is exactly the
393
- // judgement she said she did not want made, whether by a model or by a knob.
385
+ // So the card does the same thing on every session: the message the user
386
+ // started it with, up to three later messages of theirs, where it stopped, and
387
+ // where it is working. `ask` and `trail` are gone, and there is deliberately no
388
+ // setting for this: deciding per session which of them a card deserves is a
389
+ // judgement that should not be made, whether by a model or by a knob.
394
390
 
395
- // The messages she typed, oldest first, with the newest ones kept whole and the
391
+ // The messages the user typed, oldest first, with the newest ones kept whole and the
396
392
  // opening always present.
397
393
  //
398
394
  // AN EMPTY LIST IS NOT A FALLBACK TO `lastAsked`, and the difference is the
399
395
  // whole point of this change rather than a nicety. A session whose transcript
400
- // could not be read has exactly one thing she typed on record, and it is the
401
- // LAST one. So the caller is told which of the two it has. WHAT SHE SAID FROM
402
- // AGENTBOX IS ALSO WHAT SHE SAID.
396
+ // could not be read has exactly one thing the user typed on record, and it is
397
+ // the LAST one. So the caller is told which of the two it has. WHAT THE USER
398
+ // SAID FROM AGENTBOX IS ALSO WHAT THE USER SAID.
403
399
  //
404
- // So the card went on saying only what she started the session with.
400
+ // So the card went on saying only what the session was started with.
405
401
  //
406
402
  // `spoke` is the other half, written by main/agent-schedule.mjs at the moment
407
403
  // of delivery. It is a RECORD, not a guess: Agentbox does not try to work out
408
- // which wrapped peer message in a transcript was hers, because another of her
409
- // sessions pinging this one is a real peer and drawing that as her words would
410
- // put a sentence she never typed on the one card built to be trusted.
404
+ // which wrapped peer message in a transcript was the user's, because another of
405
+ // their sessions pinging this one is a real peer and drawing that as the user's
406
+ // words would put a sentence they never typed on the one card built to be trusted.
411
407
  //
412
408
  // Merged by time, so the thread reads forwards however it arrived, and deduped
413
- // on the text because a session that DOES record her injected turn as its own
414
- // (a future Claude Code, or one she typed into directly) must not show it
409
+ // on the text because a session that DOES record the user's injected turn as
410
+ // its own (a future Claude Code, or one typed into directly) must not show it
415
411
  // twice.
416
412
  export function herWords(agent) {
417
413
  const asked = (agent?.asked ?? []).filter((t) => t && t.text);
@@ -434,11 +430,11 @@ export function herWords(agent) {
434
430
  return first ? [first, ...rest] : [];
435
431
  }
436
432
 
437
- // WHICH OF HER MESSAGES ACTUALLY RECALL ANYTHING, and the cheapest honest
438
- // answer is how long they are. Half of what she types while an agent works is
439
- // driving it, not describing it: measured across her 155 real turns, "restart
440
- // it", "move forward", "lgtm!", "copy it to clipboard", "oh that works got it"
441
- // and "open again" are all under sixty characters, and her median turn is 131.
433
+ // WHICH OF THE USER'S MESSAGES ACTUALLY RECALL ANYTHING, and the cheapest
434
+ // honest answer is how long they are. Much of what a person types while an
435
+ // agent works is driving it, not describing it: short nudges to restart,
436
+ // carry on or approve are almost always under sixty characters, while a turn
437
+ // that describes the work typically runs well past a hundred.
442
438
  //
443
439
  // So the long ones are preferred, and the short ones are still there when
444
440
  // there is nothing longer, because a card that drops to silence is worse.
@@ -459,7 +455,7 @@ function ago(at, now) {
459
455
  return `${howLong(gap)} ago`;
460
456
  }
461
457
 
462
- // The rest of the context, in her sentences: what she asked for, where the
458
+ // The rest of the context, in the user's sentences: what they asked for, where the
463
459
  // thread got to, what it last said back, and what it has had its hands on.
464
460
  function context(agent, now = Date.now()) {
465
461
  const words = herWords(agent);
@@ -472,13 +468,13 @@ function context(agent, now = Date.now()) {
472
468
  const when = ago(opening.at, now);
473
469
  lines.push(`**You started it${when ? ` ${when}` : ''} with:**\n\n“${oneSentence(opening.text, 420)}”`);
474
470
  } else {
475
- // No history, so the only thing on record is the LAST thing she typed, and
471
+ // No history, so the only thing on record is the LAST thing the user typed, and
476
472
  // it gets the label that is true of it.
477
473
  const last = lastAsk(agent);
478
474
  if (last) lines.push(`**The last thing you told it:**\n\n“${last}”`);
479
475
  }
480
476
 
481
- // Everything else she typed, oldest first, so the thread reads forwards.
477
+ // Everything else the user typed, oldest first, so the thread reads forwards.
482
478
  // Three of them; more than that is the transcript rather than a card.
483
479
  if (rest.length) {
484
480
  const recent = worthRecalling(rest).slice(-3);
@@ -586,10 +582,10 @@ export function reachesInbox(agent, now = Date.now(), mode = 'all') {
586
582
  // argument is not taken because a reply cancels a deferral (`replyClearsSchedule`
587
583
  // does the same for work items) — she spoke to it, so it is not put off any more.
588
584
  //
589
- // THIS IS NOT `reachesProgress`, WHICH SHE KILLED, and the name is different on
590
- // purpose. It stays dead: a working session she has not spoken to is in her
591
- // inbox, exactly as she left it. What moves a row here is HER, and only her —
592
- // the reply she wrote.
585
+ // THIS IS NOT `reachesProgress`, WHICH WAS REMOVED, and the name is different
586
+ // on purpose. It stays dead: a working session the user has not spoken to is in
587
+ // the inbox, exactly as they left it. What moves a row here is THE USER, and
588
+ // only the user: the reply they wrote.
593
589
  export function progressAfterReply(agent, now = Date.now(), mode = 'all') {
594
590
  if (agent.startedByZero) return false;
595
591
  if (mode === 'off') return false;
@@ -603,7 +599,7 @@ export function listed(agent) {
603
599
  }
604
600
 
605
601
  /* --------------------- ACTIVE AGENTS, IN THE SIDEBAR --------------------- */
606
- // Then her pick of the design at 18:17: one line per session in its own words,
602
+ // The design: one line per session in its own words,
607
603
  // the clock on the right, and no dot, mark, colour, wash, edge or line of any
608
604
  // kind.
609
605
  //
@@ -611,8 +607,8 @@ export function listed(agent) {
611
607
  // where the rule lives, because it decides what the rule can be: nothing
612
608
  // FINISHED is ever on this list and never has been. `main/agents.mjs` drops any
613
609
  // record whose process is gone, so a session that finishes exits and disappears
614
- // by itself. Measured on her machine that evening, ten sessions alive and six
615
- // of them her own terminals sitting at a prompt for between two and seven days.
610
+ // by itself. Measured: most live sessions were terminals sitting at a prompt
611
+ // for between two and seven days.
616
612
  // So length comes from sessions that are alive and forgotten, and a day is
617
613
  // where the app's own clock (`format.ts`) stops saying hours and starts saying
618
614
  // `1d`. The panel therefore holds exactly what the clock beside each row can
@@ -649,21 +645,19 @@ export function byRecency(a, b) {
649
645
  }
650
646
 
651
647
  /* --------------------------- THE CONVERSATION ---------------------------- */
652
- // Hers, 2026-08-17. Her "go ahead" on the same row, an hour later.
653
- //
654
- // THE CARD IS THE RECALL AND THIS IS THE REST OF IT. The card is drawn for her
655
- // whether she asked or not, so it is four sentences; this is only ever drawn
656
- // because she pressed something, so it can be the conversation.
648
+ // THE CARD IS THE RECALL AND THIS IS THE REST OF IT. The card is drawn for the
649
+ // user whether they asked or not, so it is four sentences; this is only ever
650
+ // drawn because they pressed something, so it can be the conversation.
657
651
  //
658
- // STILL NO MODEL, AND NOW NO COST AT ALL UNTIL SHE PRESSES IT. Measured on her
659
- // machine 2026-08-17: the readable conversation is 0.6% of a transcript's
660
- // bytes, and the worst single file on the disk — 160MB — carries 3,800 readable
661
- // turns, 2.6MB of text, 729ms to pull out. That last number is why the read is
662
- // streamed and never happens on a draw.
652
+ // STILL NO MODEL, AND NOW NO COST AT ALL UNTIL IT IS PRESSED. Measured: the
653
+ // readable conversation is 0.6% of a transcript's bytes, and the worst single
654
+ // file measured (160MB) carries 3,800 readable turns, 2.6MB of text, 729ms to
655
+ // pull out. That last number is why the read is streamed and never happens on
656
+ // a draw.
663
657
  //
664
658
  // WHY IT IS A WINDOW AND NOT THE WHOLE FILE. 3,800 turns is not a conversation
665
- // she can get up to speed on, it is a scroll bar. So it is the opening, which
666
- // is the ask in 16 of her 16 sessions, and the end, which is where it got to,
659
+ // anyone can get up to speed on, it is a scroll bar. So it is the opening,
660
+ // which is the ask in every session measured, and the end, which is where it got to,
667
661
  // and one plain line saying how many are in between rather than pretending
668
662
  // there are none.
669
663
  export const TRAIL_OPENING = 3;
@@ -681,12 +675,11 @@ export function conversationWindow(turns, { opening = TRAIL_OPENING, keep = TRAI
681
675
  // because "some messages" is the kind of sentence that makes her wonder what
682
676
  // else the app is rounding off.
683
677
  //
684
- // She is right and it is not rare. Measured over her own Agentbox store the same
685
- // day, rebuilding every row's thread the way the pane draws it: 267 rows have a
686
- // conversation, 123 of them hide part of it, and 97 hide messages SHE wrote.
687
- // 550 of her own messages were behind this line, and the line was a plain div
688
- // with nothing to press. "Not shown" was true and it was also the end of the
689
- // road, so a thread that had eaten her follow-up simply said so and stopped.
678
+ // And it is not rare. Rebuilding every row's thread the way the pane draws it,
679
+ // nearly half the rows with a conversation hide part of it, many of them
680
+ // hiding messages the user wrote, and the line was a plain div with nothing to
681
+ // press. "Not shown" was true and it was also the end of the road, so a thread
682
+ // that had eaten a follow-up simply said so and stopped.
690
683
  //
691
684
  // So the words say what pressing does. The window itself is untouched: it still
692
685
  // opens on the end of a long conversation, because an 18,100px page is the
@@ -700,17 +693,17 @@ export function conversationGap(omitted) {
700
693
  // So the item is the conversation, and each thing the agent ran sits on its own
701
694
  // quiet line between the messages, in the order it happened. This half of the
702
695
  // file decides what one of those lines SAYS. What it looks like is the frosted
703
- // treatment she picked in round two and lives in the stylesheet.
696
+ // treatment chosen in round two and lives in the stylesheet.
704
697
  //
705
- // WHY IT IS BOUNDED THREE WAYS, all measured over the 40 largest transcripts on
706
- // her machine, 2026-08-19:
698
+ // WHY IT IS BOUNDED THREE WAYS, all measured over the 40 largest transcripts
699
+ // available:
707
700
  //
708
701
  // * one session held 6,070 tool calls and 8MB of output. So output is capped
709
702
  // per line (WORK_CHARS) rather than sent whole.
710
703
  // * the longest unbroken run of tool calls between two messages was 111, and
711
704
  // the median session's longest run was 25. So a run is capped (WORK_RUN)
712
705
  // and says how many it is not showing, because 111 quiet lines standing
713
- // between her question and its answer is her original complaint wearing a
706
+ // between a question and its answer is the original problem wearing a
714
707
  // different hat.
715
708
  // * a windowed thread of 40 messages could still carry thousands of work
716
709
  // lines, so the whole thread is capped too (WORK_TOTAL), oldest first.
@@ -723,12 +716,12 @@ export const WORK_RUN = 20;
723
716
  export const WORK_TOTAL = 200;
724
717
 
725
718
  // WHAT A TOOL CALL SAYS lives in its own file now (`shared/work-lines.mjs`),
726
- // because her 2026-08-20 report on the imported threads turned one rule into
719
+ // because a report on the imported threads turned one rule into
727
720
  // three: the verb, the subject and the fold. It is re-exported here so that
728
721
  // nothing that already reads this file has to learn a second import.
729
722
  export {
730
- workVerb, workSubject, fullSubject, shortPath, plainCommand,
731
- groupWork, runSummary, runFailures, RUN_MIN,
723
+ workVerb, workSubject, fullSubject, shortPath, plainCommand, commandWork,
724
+ groupWork, runSummary, runFailures, RUN_MIN, isBookkeeping,
732
725
  changedFile, fileInChange, CHANGES_A_FILE,
733
726
  } from './work-lines.mjs';
734
727
 
@@ -755,8 +748,8 @@ export function outputCut(shownLines, allLines) {
755
748
  //
756
749
  // `whole` IS WHAT THE GAP LINE PRESSES. Every message stands, and nothing else
757
750
  // changes: the work is still capped by `capRuns` below, because 530 tool calls
758
- // is not a conversation and she asked for her follow-ups, not for the output
759
- // under them. Opening it is her decision and it lasts until she leaves the row,
751
+ // is not a conversation and what is wanted is the follow-ups, not the output
752
+ // under them. Opening it is the user's decision and it lasts until they leave the row,
760
753
  // so no thread is ever born at this size.
761
754
  export function threadWindow(events, { opening = TRAIL_OPENING, keep = TRAIL_TURNS, whole = false } = {}) {
762
755
  const all = (events ?? []).filter((e) => e && (e.kind === 'work' ? true : !!e.text));
@@ -884,15 +877,14 @@ export function saidCount(events) {
884
877
  // A provider writing a tool call sometimes emits its closing tags as ordinary
885
878
  // text, and the field it was writing then holds the answer with the machinery
886
879
  // of the call still stuck to the end. On w-b108b1d596 the result that reached
887
- // her pane ended "7,369 tests both times.</result>\n</invoke>" and she read it.
888
- // Measured across her store, 2026-09-23: 49 field writes on 27 rows carry it,
889
- // and 50 messages on screen.
880
+ // the pane ended "7,369 tests both times.</result>\n</invoke>" and was shown as
881
+ // is. Measured: dozens of field writes across dozens of rows carried it.
890
882
  //
891
883
  // IT ALSO BREAKS THE FOLD THAT SAYS ONE THING ONCE. That run noticed the leak
892
884
  // and wrote its result again, clean. The fold keeps the FULLER of two copies of
893
885
  // one answer, and the junk made the broken copy the fuller one, so the leak was
894
886
  // what stayed on the screen and the good rewrite was the copy thrown away. That
895
- // is the row she photographed, and it is why the cut happens before anything
887
+ // is what happened on that row, and it is why the cut happens before anything
896
888
  // compares two messages.
897
889
  //
898
890
  // The cut runs from the first tag to the end, because nothing after one is
@@ -25,6 +25,15 @@ export const EVENTS = {
25
25
  task_opened: 'A task was opened',
26
26
  reply_sent: 'A reply was sent',
27
27
  task_finished: 'A task finished',
28
+ // REAL USE, NOT ONLY THE WALKTHROUGH (2026-10-06). Every "task finished" on
29
+ // the day downloads opened was somebody closing practice rows in setup; there
30
+ // was no count of an agent doing real work, of where setup was left, or of an
31
+ // install being used on a day, which is what retention is read from.
32
+ day_active: `${Name} was used today`,
33
+ first_run_step: 'A setup step was reached',
34
+ task_written: 'A task was written',
35
+ run_started: 'An agent started work',
36
+ run_finished: 'An agent stopped work',
28
37
  };
29
38
 
30
39
  export const EVENT_NAMES = Object.keys(EVENTS);
@@ -39,11 +48,15 @@ export const ALLOWED_PROPS = new Set([
39
48
  'count', // how many of a thing, never which ones
40
49
  'kind', // -> KINDS below, an enum and nothing else
41
50
  'fromAgent', // whether an agent or the founder did it
51
+ 'failed', // whether an agent's run ended in failure
52
+ 'practice', // whether it happened in setup's practice project
53
+ 'engine', // -> ENGINES below, which coding agent ran
42
54
  ]);
43
55
 
44
- // `kind` is the one property that is not a number, so it is a closed list. A
45
- // value outside it is dropped, not passed through.
56
+ // `kind` and `engine` are the properties that are not numbers, so each is a
57
+ // closed list. A value outside it is dropped, not passed through.
46
58
  export const KINDS = new Set(['task', 'question', 'review', 'directive', 'agent', 'digest']);
59
+ export const ENGINES = new Set(['claude', 'codex']);
47
60
 
48
61
  // Refuse an event nobody approved; drop a property nobody approved. Returns the
49
62
  // payload that may be sent, or null when the event itself is not one of hers.
@@ -56,6 +69,10 @@ export function sanitize(name, props = {}) {
56
69
  if (typeof value === 'string' && KINDS.has(value)) out.kind = value;
57
70
  continue;
58
71
  }
72
+ if (key === 'engine') {
73
+ if (typeof value === 'string' && ENGINES.has(value)) out.engine = value;
74
+ continue;
75
+ }
59
76
  if (typeof value === 'number' && Number.isFinite(value)) out[key] = value;
60
77
  else if (typeof value === 'boolean') out[key] = value;
61
78
  }
@@ -78,3 +78,15 @@ export function answerSettled(item) {
78
78
  export function answerTs(item) {
79
79
  return item?.wrote?.answer?.ts ?? 0;
80
80
  }
81
+
82
+ // SHE STOPPED THIS ROW AND HAS NOT SPOKEN SINCE. Stop writes `blocked` in her
83
+ // hand (zero:stop-session), and it promises the row stays in her inbox until
84
+ // she replies, resumes it or sends it back. A reply she wrote BEFORE the stop
85
+ // is not one of those, though a kill hands it back to the queue: that is how a
86
+ // stopped run came straight back ten seconds later (2026-10-01).
87
+ // tests/a-stopped-agent-stays-stopped-until-she-resumes-it.test.mjs
88
+ export function stoppedByHer(item) {
89
+ const status = item?.wrote?.status;
90
+ return item?.status === 'blocked' && status?.source === 'founder'
91
+ && answerTs(item) <= (status.ts ?? 0);
92
+ }
@@ -0,0 +1,12 @@
1
+ // Types for shared/bridge-map.mjs, for the window side.
2
+ //
3
+ // EVERY ONE OF THESE IS A PHONE BOOK AND IS TYPED AS ONE. Naming the hundred
4
+ // and thirty keys over again here would be a second copy of the list the file
5
+ // exists to be the only copy of, and the first one to fall behind it. What the
6
+ // screen needs from a type is that a channel is a string and a wrap is a list
7
+ // of argument names; which names exist is checked against preload.cjs at test
8
+ // time instead (tests/both-front-doors-offer-the-same-bridge.test.mjs).
9
+ export declare const REQUEST_CHANNELS: Readonly<Record<string, string>>;
10
+ export declare const PUSH_CHANNELS: Readonly<Record<string, string>>;
11
+ export declare const WRAPPED_ARGS: Readonly<Record<string, string[]>>;
12
+ export declare const DESKTOP_ONLY: readonly string[];
@@ -13,7 +13,10 @@
13
13
  /** Asked, and the screen waits for the answer. */
14
14
  export const REQUEST_CHANNELS = {
15
15
  terminal: 'zero:terminal',
16
+ agentUpdate: 'zero:agent-update',
16
17
  snapshot: 'zero:snapshot',
18
+ // The next page of old finished threads, at the foot of Done and All.
19
+ olderItems: 'zero:older-items',
17
20
  dashboard: 'zero:dashboard',
18
21
  commandCatalog: 'zero:command-catalog',
19
22
  remoteControl: 'zero:remote-control',
@@ -21,8 +24,32 @@ export const REQUEST_CHANNELS = {
21
24
  compact: 'zero:compact',
22
25
  compactionStatus: 'zero:compaction-status',
23
26
  answer: 'zero:answer',
27
+ sendNow: 'zero:send-now',
24
28
  compose: 'zero:compose',
25
29
  schedule: 'zero:schedule',
30
+ // Feedback to the Agentbox team, from the sidebar's Feedback card.
31
+ sendFeedback: 'zero:send-feedback',
32
+ // The team version: signing in, the team, sharing, routing a given task.
33
+ teamSignIn: 'zero:team-sign-in',
34
+ teamSignInCancel: 'zero:team-sign-in-cancel',
35
+ teamSignInReopen: 'zero:team-sign-in-reopen',
36
+ teamSignOut: 'zero:team-sign-out',
37
+ teamSignInEmail: 'zero:team-sign-in-email',
38
+ teamSignUp: 'zero:team-sign-up',
39
+ teamCreate: 'zero:team-create',
40
+ teamInvite: 'zero:team-invite',
41
+ teamRename: 'zero:team-rename',
42
+ teamRemoveMember: 'zero:team-remove-member',
43
+ teamLeave: 'zero:team-leave',
44
+ teamCancelInvite: 'zero:team-cancel-invite',
45
+ teamAcceptInvite: 'zero:team-accept-invite',
46
+ teamStatus: 'zero:team-status',
47
+ teamShare: 'zero:team-share',
48
+ teamSync: 'zero:team-sync',
49
+ teamRoute: 'zero:team-route',
50
+ teamReact: 'zero:team-react',
51
+ teamMessage: 'zero:team-message',
52
+ threadEdit: 'zero:thread-edit',
26
53
  repeats: 'zero:repeats',
27
54
  composeRepeat: 'zero:compose-repeat',
28
55
  setRepeat: 'zero:set-repeat',
@@ -50,6 +77,7 @@ export const REQUEST_CHANNELS = {
50
77
  updateInstall: 'zero:update-install',
51
78
  notify: 'zero:notify',
52
79
  stopSession: 'zero:stop-session',
80
+ runNow: 'zero:run-now',
53
81
  reopen: 'zero:reopen',
54
82
  instructionRead: 'zero:instruction-read',
55
83
  instructionWrite: 'zero:instruction-write',
@@ -62,11 +90,14 @@ export const REQUEST_CHANNELS = {
62
90
  settingsRead: 'zero:settings-read',
63
91
  claudeRecheck: 'zero:claude-recheck',
64
92
  codexRecheck: 'zero:codex-recheck',
93
+ engineSetup: 'zero:engine-setup',
65
94
  codexAddAccount: 'zero:codex-add-account',
66
95
  claudeAddAccount: 'zero:claude-add-account',
67
96
  setProjectSetting: 'zero:settings-set-project',
68
97
  setWorkspaceSetting: 'zero:settings-set-workspace',
69
98
  projectRename: 'zero:project-rename',
99
+ projectArchive: 'zero:project-archive',
100
+ projectSeenBy: 'zero:project-seen-by',
70
101
  projectIcon: 'zero:project-icon',
71
102
  projectIconClear: 'zero:project-icon-clear',
72
103
  projectInstructionsRead: 'zero:project-instructions-read',
@@ -78,6 +109,7 @@ export const REQUEST_CHANNELS = {
78
109
  sessionTrace: 'zero:session-trace',
79
110
  itemHistory: 'zero:item-history',
80
111
  openArtifact: 'zero:open-artifact',
112
+ folderPictures: 'zero:folder-pictures',
81
113
  readDoc: 'zero:read-doc',
82
114
  codeChange: 'zero:code-change',
83
115
  codeFile: 'zero:code-file',
@@ -125,4 +157,8 @@ export const WRAPPED_ARGS = {
125
157
  // Electron can tell you the real path of a file somebody dropped onto the
126
158
  // window. A browser cannot, by design, and no server can answer it either, so
127
159
  // this one is absent there rather than wrong. The screen already reads null.
128
- export const DESKTOP_ONLY = ['pathForFile'];
160
+ //
161
+ // `onWriteHeld` is the desktop's ⌘R asking the page to write what the undo
162
+ // window holds before it reloads (main/write-before-reload.mjs). A browser
163
+ // reload asks nobody; the page writes on `pagehide` there instead.
164
+ export const DESKTOP_ONLY = ['pathForFile', 'onWriteHeld'];
@@ -10,8 +10,8 @@
10
10
  // build time, rather than typing them into a file, is the only way the list
11
11
  // cannot rot quietly.
12
12
 
13
- export const READ_FROM = "2.1.283";
14
- export const READ_AT = "2026-09-29";
13
+ export const READ_FROM = "2.1.289";
14
+ export const READ_AT = "2026-10-07";
15
15
  export const CLAUDE_COMMANDS = [
16
16
  {
17
17
  "name": "model",
@@ -11,14 +11,14 @@
11
11
  // number typed into a file of ours would look authoritative while being a
12
12
  // guess. Running an old model by mistake is the failure being avoided.
13
13
 
14
- export const READ_FROM = "2.1.283";
15
- export const READ_AT = "2026-09-29";
14
+ export const READ_FROM = "2.1.289";
15
+ export const READ_AT = "2026-10-07";
16
16
 
17
17
  export const CLAUDE_MODELS = [
18
18
  {"alias":"opus","id":"claude-opus-5-5","label":"Opus 5.5","defaultLevel":"medium"},
19
19
  {"alias":"claude-opus-5","id":"claude-opus-5","label":"Opus 5","defaultLevel":"high"},
20
- {"alias":"sonnet","id":"claude-sonnet-5","label":"Sonnet 5","defaultLevel":"high"},
21
- {"alias":"claude-sonnet-4-6","id":"claude-sonnet-4-6","label":"Sonnet 4.6","defaultLevel":null},
20
+ {"alias":"sonnet","id":"claude-sonnet-5-5","label":"Sonnet 5.5","defaultLevel":"medium"},
21
+ {"alias":"claude-sonnet-5","id":"claude-sonnet-5","label":"Sonnet 5","defaultLevel":"high"},
22
22
  {"alias":"haiku","id":"claude-haiku-4-5","label":"Haiku 4.5","defaultLevel":null},
23
23
  {"alias":"claude-3-5-haiku","id":"claude-3-5-haiku","label":"Haiku 3.5","defaultLevel":null},
24
24
  {"alias":"fable","id":"claude-fable-5-1","label":"Fable 5.1","defaultLevel":"high"},