agentbox-app 0.1.12 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/README.md +24 -9
  2. package/bin/agentbox.mjs +23 -1
  3. package/briefs/message-rules.md +36 -8
  4. package/briefs/worker.md +46 -23
  5. package/main/account-discovery.mjs +122 -30
  6. package/main/account-tooling.mjs +29 -1
  7. package/main/account-usage.mjs +46 -0
  8. package/main/acp.mjs +296 -0
  9. package/main/agent-homes.mjs +158 -0
  10. package/main/agent-sessions.mjs +311 -260
  11. package/main/agent-updates.mjs +9 -22
  12. package/main/agents.mjs +179 -466
  13. package/main/analytics.mjs +42 -2
  14. package/main/answer-modes.mjs +103 -37
  15. package/main/approval-prompt-core.mjs +197 -0
  16. package/main/approval-prompt-server.mjs +4 -138
  17. package/main/approvals.mjs +101 -297
  18. package/main/artifact-path.mjs +47 -18
  19. package/main/claude-bin.mjs +45 -311
  20. package/main/claude-input.mjs +57 -7
  21. package/main/claude-launch-env.mjs +54 -0
  22. package/main/claude-models.mjs +39 -7
  23. package/main/claude-plan.mjs +64 -16
  24. package/main/claude-session-open.mjs +28 -0
  25. package/main/claude-usage-process.mjs +40 -0
  26. package/main/claude-usage.mjs +94 -59
  27. package/main/cloud/account.mjs +179 -0
  28. package/main/cloud/aws.mjs +127 -0
  29. package/main/cloud/browser-return.mjs +58 -0
  30. package/main/cloud/clients.mjs +15 -0
  31. package/main/cloud/codex-cloud.mjs +53 -0
  32. package/main/cloud/config.mjs +107 -0
  33. package/main/cloud/docs.mjs +219 -0
  34. package/main/cloud/exec.mjs +27 -0
  35. package/main/cloud/host.mjs +165 -0
  36. package/main/cloud/kit.mjs +80 -0
  37. package/main/cloud/logins.mjs +543 -0
  38. package/main/cloud/managed.mjs +115 -0
  39. package/main/cloud/marker.mjs +104 -0
  40. package/main/cloud/org.mjs +53 -0
  41. package/main/cloud/reuse.mjs +208 -0
  42. package/main/cloud/run-cli.mjs +177 -0
  43. package/main/cloud/run.mjs +1934 -0
  44. package/main/cloud/usage.mjs +76 -0
  45. package/main/cloud-permission.mjs +51 -0
  46. package/main/codex-account.mjs +78 -0
  47. package/main/codex-app-server.mjs +276 -409
  48. package/main/codex-approvals.mjs +148 -433
  49. package/main/codex-bin.mjs +51 -249
  50. package/main/codex-models.mjs +20 -2
  51. package/main/codex-session.mjs +246 -506
  52. package/main/codex-threads-worker.mjs +12 -0
  53. package/main/codex-threads.mjs +112 -11
  54. package/main/codex-usage-file.mjs +5 -3
  55. package/main/codex-usage.mjs +56 -7
  56. package/main/codex-watch.mjs +83 -5
  57. package/main/codex.mjs +95 -363
  58. package/main/config.mjs +37 -5
  59. package/main/corner-tag.mjs +810 -0
  60. package/main/crash-report.mjs +26 -3
  61. package/main/dev-window.mjs +1 -1
  62. package/main/engine-bin.mjs +545 -0
  63. package/main/engine-setup.mjs +25 -24
  64. package/main/feedback.mjs +14 -31
  65. package/main/folder-pictures.mjs +2 -2
  66. package/main/freeze-recorder-thread.mjs +86 -0
  67. package/main/freeze-recorder.mjs +146 -0
  68. package/main/fresh-user.mjs +20 -9
  69. package/main/git-change-offthread.mjs +17 -3
  70. package/main/git-change-thread.mjs +5 -2
  71. package/main/harnesses/claude-stream.mjs +208 -0
  72. package/main/harnesses/claude.mjs +105 -0
  73. package/main/harnesses/codex.mjs +72 -0
  74. package/main/harnesses/common.mjs +78 -0
  75. package/main/harnesses/instructions-file.mjs +116 -0
  76. package/main/harnesses/opencode.mjs +234 -0
  77. package/main/harnesses.mjs +65 -0
  78. package/main/importable-threads.mjs +96 -0
  79. package/main/ipc.mjs +520 -135
  80. package/main/leftovers.mjs +117 -25
  81. package/main/liquid-glass.mjs +26 -0
  82. package/main/live-replies.mjs +10 -4
  83. package/main/local-app-frames.mjs +82 -0
  84. package/main/main-went-red.mjs +330 -0
  85. package/main/main.mjs +248 -37
  86. package/main/memory-gate-history.mjs +85 -20
  87. package/main/memory-gate-server.mjs +129 -11
  88. package/main/memory-gate.mjs +62 -8
  89. package/main/memory-log.mjs +124 -0
  90. package/main/menu.mjs +4 -2
  91. package/main/message-priority.mjs +2 -2
  92. package/main/mic.mjs +38 -0
  93. package/main/native/liquid-glass/build/liquid-glass.node +0 -0
  94. package/main/new-chats-watch.mjs +110 -0
  95. package/main/new-user-window.mjs +25 -0
  96. package/main/notify.mjs +64 -5
  97. package/main/opencode-server.mjs +271 -0
  98. package/main/opencode.mjs +287 -0
  99. package/main/phone-alerts.mjs +117 -0
  100. package/main/phone-inbox.mjs +625 -0
  101. package/main/phone-link.mjs +571 -0
  102. package/main/phone-uploads.mjs +476 -0
  103. package/main/picture-shape.mjs +114 -0
  104. package/main/quit-sequence.mjs +143 -0
  105. package/main/refresh-agent-models.mjs +15 -2
  106. package/main/relay-address.mjs +28 -0
  107. package/main/relay-client.mjs +314 -0
  108. package/main/repeats.mjs +10 -4
  109. package/main/row-label.mjs +18 -47
  110. package/main/run-change.mjs +13 -0
  111. package/main/run-ledger.mjs +135 -0
  112. package/main/runs-on.mjs +32 -35
  113. package/main/save-attachments.mjs +41 -0
  114. package/main/serve.mjs +155 -9
  115. package/main/settings.mjs +364 -50
  116. package/main/shell-path.mjs +234 -0
  117. package/main/ship-conflict-brief.mjs +119 -0
  118. package/main/ship-queue.mjs +134 -20
  119. package/main/sign-in-files.mjs +3 -7
  120. package/main/source-updater.mjs +45 -11
  121. package/main/stage-attachment.mjs +68 -0
  122. package/main/stall-watch.mjs +50 -0
  123. package/main/start-hold.mjs +73 -0
  124. package/main/store/home.mjs +87 -232
  125. package/main/store/paths.mjs +11 -0
  126. package/main/store/teammates-files.mjs +99 -0
  127. package/main/store/work-items.mjs +231 -13
  128. package/main/store.mjs +227 -53
  129. package/main/supervisor.mjs +3865 -699
  130. package/main/task-commands.mjs +13 -17
  131. package/main/task-folders-offthread.mjs +6 -1
  132. package/main/task-folders.mjs +897 -105
  133. package/main/task-terminals.mjs +147 -41
  134. package/main/team/index.mjs +114 -10
  135. package/main/team/memory-cloud.mjs +183 -1
  136. package/main/team/room-hours.mjs +48 -0
  137. package/main/team/room-setup.mjs +31 -0
  138. package/main/team/rooms-memory.mjs +454 -0
  139. package/main/team/rooms-shape.mjs +42 -0
  140. package/main/team/rooms-supabase.mjs +324 -0
  141. package/main/team/rooms.mjs +575 -0
  142. package/main/team/session.mjs +96 -26
  143. package/main/team/supabase-backend.mjs +180 -0
  144. package/main/team/sync.mjs +109 -1
  145. package/main/teammates.mjs +580 -0
  146. package/main/terminal-host.mjs +143 -0
  147. package/main/terminal-hosts.mjs +261 -0
  148. package/main/transcript-search.mjs +80 -0
  149. package/main/updater.mjs +71 -9
  150. package/main/webview-guard.mjs +45 -0
  151. package/main/worktree-include.mjs +365 -0
  152. package/mcp/core/documents.mjs +3 -1
  153. package/mcp/core/repeats.mjs +88 -0
  154. package/mcp/core/teammates.mjs +222 -0
  155. package/mcp/core/work.mjs +76 -4
  156. package/mcp/stdio.mjs +24 -11
  157. package/mcp/tools.mjs +116 -8
  158. package/package.json +30 -5
  159. package/renderer/dist/assets/abnfDiagram-YUKMZFIV-CoWB6YUK.js +1 -0
  160. package/renderer/dist/assets/arc-Ch0NrCul.js +1 -0
  161. package/renderer/dist/assets/architectureDiagram-47P4ROYG-CXz9M5tQ.js +36 -0
  162. package/renderer/dist/assets/blockDiagram-E7TT5TSB-DchGUuHH.js +140 -0
  163. package/renderer/dist/assets/c4Diagram-CRA5TL53-Cip-zNyr.js +38 -0
  164. package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
  165. package/renderer/dist/assets/chunk-2Q5K7J3B-BoXid_2r.js +1 -0
  166. package/renderer/dist/assets/chunk-5VM5RSS4-Cpez1rRz.js +15 -0
  167. package/renderer/dist/assets/chunk-7TKQ45FW-DKA84YkE.js +1 -0
  168. package/renderer/dist/assets/chunk-AW2ZBBNX-Dg-SumPG.js +88 -0
  169. package/renderer/dist/assets/chunk-FVRAUYC3-06TCvPm1.js +2 -0
  170. package/renderer/dist/assets/chunk-HTAEGDNF-CMEBy1Td.js +1 -0
  171. package/renderer/dist/assets/chunk-JWPE2WC7-BJ2ObmV-.js +1 -0
  172. package/renderer/dist/assets/chunk-KQW6MTUR-CGWOYXUE.js +1 -0
  173. package/renderer/dist/assets/chunk-XXDRQBXY-CiZp07XC.js +1 -0
  174. package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-DJNIX719.js +217 -0
  175. package/renderer/dist/assets/cornerTag-CPgvu-q3.js +1 -0
  176. package/renderer/dist/assets/cornerTag-oMfEjzCO.css +1 -0
  177. package/renderer/dist/assets/cose-bilkent-JH36ORCC-CbkyZOUH.js +1 -0
  178. package/renderer/dist/assets/cynefin-EF2NZ3EQ-78iOroZO.js +141 -0
  179. package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-GcCopMs7.js +62 -0
  180. package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
  181. package/renderer/dist/assets/dagre-W4DXFKR2-DHLlXLkz.js +4 -0
  182. package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  183. package/renderer/dist/assets/diagram-2UJZ2QOL-DuZSUHcH.js +200 -0
  184. package/renderer/dist/assets/diagram-OVF4WLC6-Ckxu3cqH.js +24 -0
  185. package/renderer/dist/assets/diagram-PFPMY2P6-CsTw9Ycn.js +30 -0
  186. package/renderer/dist/assets/diagram-UMYRVEAY-CTXd8qVg.js +24 -0
  187. package/renderer/dist/assets/diagram-YEKJPTXX-CJs1T_9m.js +41 -0
  188. package/renderer/dist/assets/diagram-ZIFT7M5P-CvjI23mf.js +3 -0
  189. package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-d_IeAzOJ.js +1 -0
  190. package/renderer/dist/assets/elk-IJKZMXRS-zo9KFP3N.js +27 -0
  191. package/renderer/dist/assets/erDiagram-O2IAPWRE-DqfYf5Dt.js +99 -0
  192. package/renderer/dist/assets/flowDiagram-OXPTDLAJ-DAWsMLmo.js +213 -0
  193. package/renderer/dist/assets/ganttDiagram-R7TSEDQI-4ml_bhwR.js +292 -0
  194. package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-B_WcqSId.js +106 -0
  195. package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
  196. package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-CI-jYqzc.js +2 -0
  197. package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
  198. package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-B56VWRlV.js +70 -0
  199. package/renderer/dist/assets/journeyDiagram-COXZFDF6-D1-p09Lr.js +139 -0
  200. package/renderer/dist/assets/kanban-definition-P3RFRI5V-Cv3w7JoN.js +89 -0
  201. package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
  202. package/renderer/dist/assets/linear-C35V2Spi.js +1 -0
  203. package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
  204. package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
  205. package/renderer/dist/assets/mermaid.core-BzGT5Vo9.js +314 -0
  206. package/renderer/dist/assets/mindmap-definition-LZFPQGKD-BZUfuymC.js +96 -0
  207. package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  208. package/renderer/dist/assets/pegDiagram-BGZESJAR-DFh9XNGe.js +1 -0
  209. package/renderer/dist/assets/pieDiagram-CAPJLFHJ-C65tPVsy.js +39 -0
  210. package/renderer/dist/assets/product-name-CSNW6d7s.js +40 -0
  211. package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-Cushj_nk.js +7 -0
  212. package/renderer/dist/assets/railroadDiagram-SM67HX2B-DZGebGx5.js +1 -0
  213. package/renderer/dist/assets/requirementDiagram-X7JNWC4A-DpAB0MNo.js +84 -0
  214. package/renderer/dist/assets/sankeyDiagram-UM26HJRW-D-29rHra.js +40 -0
  215. package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-FfOQAZol.js +169 -0
  216. package/renderer/dist/assets/sizeCapture-INFHLROL-C_n6fDBY.js +1 -0
  217. package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-BZjenzJG.js +291 -0
  218. package/renderer/dist/assets/swimlanes-N4OXWK64-DxaMU9vX.js +1 -0
  219. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
  220. package/renderer/dist/assets/timeline-definition-YOQAKGHF-DTg5HJKz.js +120 -0
  221. package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-Bc3MZDsl.js +370 -0
  222. package/renderer/dist/assets/vennDiagram-BLWOH2XV-GIVGk9Id.js +34 -0
  223. package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-NmJnbQOg.js +78 -0
  224. package/renderer/dist/assets/xychartDiagram-TAQBALBS-p-kfGfnW.js +7 -0
  225. package/renderer/dist/corner-tag.html +13 -0
  226. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  227. package/renderer/dist/emulator/data/compression/README.md +11 -0
  228. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  229. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  230. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  231. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  232. package/renderer/dist/emulator/data/cores/README.md +22 -0
  233. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  234. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  235. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  236. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  237. package/renderer/dist/emulator/data/cores/package.json +65 -0
  238. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  239. package/renderer/dist/emulator/data/emulator.css +1614 -0
  240. package/renderer/dist/emulator/data/loader.js +170 -0
  241. package/renderer/dist/emulator/data/localization/README.md +63 -0
  242. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  243. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  244. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  245. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  246. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  247. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  248. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  249. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  250. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  251. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  252. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  253. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  254. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  255. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  256. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  257. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  258. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  259. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  260. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  261. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  262. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  263. package/renderer/dist/emulator/data/src/compression.js +142 -0
  264. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  265. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  266. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  267. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  268. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  269. package/renderer/dist/emulator/data/src/storage.js +125 -0
  270. package/renderer/dist/emulator/data/version.json +5 -0
  271. package/renderer/dist/emulator/host.js +297 -0
  272. package/renderer/dist/emulator/index.html +28 -0
  273. package/renderer/dist/emulator/provenance.json +6 -0
  274. package/renderer/dist/index.html +16 -3
  275. package/scripts/after-install.mjs +30 -8
  276. package/scripts/approval-server.sh +24 -5
  277. package/scripts/before-publish.mjs +12 -10
  278. package/scripts/cloud-shim.pl +57 -0
  279. package/shared/account-names.d.mts +6 -0
  280. package/shared/account-names.mjs +15 -0
  281. package/shared/agent-hours.mjs +136 -0
  282. package/shared/agent-import.mjs +62 -34
  283. package/shared/agents.d.mts +1 -0
  284. package/shared/agents.mjs +126 -522
  285. package/shared/analytics-events.mjs +12 -0
  286. package/shared/approval-card.mjs +36 -0
  287. package/shared/bridge-map.mjs +45 -1
  288. package/shared/claude-code-shape.mjs +6 -0
  289. package/shared/claude-commands.generated.mjs +6 -4
  290. package/shared/claude-models.generated.mjs +6 -4
  291. package/shared/claude-usage.d.mts +1 -0
  292. package/shared/claude-usage.mjs +42 -1
  293. package/shared/cloud-hand-back.d.mts +7 -0
  294. package/shared/cloud-hand-back.mjs +20 -0
  295. package/shared/cloud-kit.d.mts +219 -0
  296. package/shared/cloud-kit.mjs +341 -0
  297. package/shared/cloud-login-name.mjs +4 -0
  298. package/shared/cloud-start.mjs +17 -0
  299. package/shared/cloud-versions.mjs +23 -0
  300. package/shared/codex-import.mjs +2 -2
  301. package/shared/codex-modes.d.mts +22 -9
  302. package/shared/codex-modes.mjs +67 -11
  303. package/shared/codex-usage.mjs +108 -2
  304. package/shared/corner-tag.d.mts +36 -0
  305. package/shared/corner-tag.mjs +396 -0
  306. package/shared/dead-run-trace.mjs +19 -2
  307. package/shared/engines.mjs +76 -300
  308. package/shared/feedback.d.mts +7 -0
  309. package/shared/feedback.mjs +25 -0
  310. package/shared/first-run-practice.mjs +17 -24
  311. package/shared/harness-definitions.mjs +111 -0
  312. package/shared/instruction-defaults.json +2 -2
  313. package/shared/key-caps.d.mts +14 -0
  314. package/shared/key-caps.mjs +68 -0
  315. package/shared/message-edits.d.mts +7 -0
  316. package/shared/message-edits.mjs +35 -0
  317. package/shared/move-thread.d.mts +24 -0
  318. package/shared/move-thread.mjs +112 -0
  319. package/shared/named-threads.mjs +72 -0
  320. package/shared/not-shipped.d.mts +22 -0
  321. package/shared/not-shipped.mjs +89 -0
  322. package/shared/notify-rules.d.mts +31 -0
  323. package/shared/notify-rules.mjs +55 -8
  324. package/shared/phone-files.mjs +28 -0
  325. package/shared/phone-settings.mjs +118 -0
  326. package/shared/phone-snapshot.d.mts +16 -0
  327. package/shared/phone-snapshot.mjs +348 -0
  328. package/shared/picture-formats.d.mts +1 -0
  329. package/shared/picture-formats.mjs +13 -0
  330. package/shared/provider-commands.mjs +10 -2
  331. package/shared/rank.d.mts +4 -1
  332. package/shared/rank.mjs +82 -19
  333. package/shared/relay-address.mjs +6 -0
  334. package/shared/relay-crypto.mjs +165 -0
  335. package/shared/repeats.d.mts +8 -0
  336. package/shared/repeats.mjs +33 -2
  337. package/shared/room-cards.mjs +167 -0
  338. package/shared/room-links.d.mts +4 -0
  339. package/shared/room-links.mjs +21 -0
  340. package/shared/runs-on.d.mts +3 -13
  341. package/shared/runs-on.mjs +31 -52
  342. package/shared/shipped-note.d.mts +4 -0
  343. package/shared/shipped-note.mjs +6 -0
  344. package/shared/shortcuts.mjs +24 -10
  345. package/shared/spawn-trouble.d.mts +4 -0
  346. package/shared/spawn-trouble.mjs +484 -30
  347. package/shared/team-rules.mjs +9 -2
  348. package/shared/teammates.d.mts +19 -0
  349. package/shared/teammates.mjs +756 -0
  350. package/shared/terminal-focus.d.mts +1 -0
  351. package/shared/terminal-focus.mjs +13 -0
  352. package/shared/terminal-input.d.mts +1 -0
  353. package/shared/terminal-input.mjs +30 -0
  354. package/shared/terminal-keys.d.mts +1 -0
  355. package/shared/terminal-keys.mjs +16 -0
  356. package/shared/terminal-size.d.mts +4 -0
  357. package/shared/terminal-size.mjs +29 -0
  358. package/shared/thread-cards.mjs +4 -1
  359. package/shared/usage.d.mts +12 -0
  360. package/shared/usage.mjs +56 -3
  361. package/shared/websites.d.mts +15 -0
  362. package/shared/websites.mjs +61 -0
  363. package/shared/where-it-runs-from.d.mts +43 -0
  364. package/shared/where-it-runs-from.mjs +155 -0
  365. package/shared/work-items.mjs +150 -29
  366. package/shared/work-lines.mjs +93 -245
  367. package/shared/xterm-invalid-range.d.mts +2 -0
  368. package/shared/xterm-invalid-range.mjs +58 -0
  369. package/renderer/dist/assets/index-ChKsQkRw.js +0 -1169
  370. package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
@@ -1,216 +1,83 @@
1
- // WHICH CODING AGENT A TASK IS HANDED TO.
1
+ import { HARNESS_DEFINITIONS } from './harness-definitions.mjs';
2
+ // Which coding agent a task is handed to.
2
3
  //
3
- // AND THE PREMISE SHE GAVE FOR IT DID NOT HOLD. Measured head to head on her
4
- // Mac the same day, identical task, four fresh repos each: Codex 16.8 / 45.3 /
5
- // 96.9 / 18.6 seconds, Claude Code 19.0 / 19.8 / 19.8 / 21.2. Both correct
6
- // every run. Codex is NOT faster; it is slower on median and it wanders. That
7
- // is written here rather than in a report because it is the reason this file
8
- // must never make Codex the default for anybody, including her. Claude Code is
9
- // the default and stays the default until she says otherwise. What this file
10
- // buys is a SECOND ENGINE on a second subscription, which is the thing that
11
- // actually went wrong for her, and a choice she makes per task.
4
+ // Claude Code is the default. Codex measured slower on median and wandered on
5
+ // the same tasks, so do not make it the default for anybody.
12
6
  //
13
- // THIS FILE WAS DELETED AND IS COMING BACK IN STAGES, WHICH IS WHY IT NOW HAS
14
- // A GATE. Her own message on that commit says thirteen; the number here is the
15
- // one the tree can be asked for.
7
+ // Old rows may still carry `engine: "codex"`, because the work-item fold keeps
8
+ // every field it was given. So `engineFor` answers Claude Code unless the
9
+ // caller hands it `ENGINE_CHOICE_ENABLED`, and a caller that passes nothing
10
+ // gets the safe answer.
16
11
  //
17
- // `engine` DID NOT GO. It is still a live name on the work-item contract
18
- // (`WORK_ITEM_FIELDS`, shared/work-items.mjs:95, accepted by the patch
19
- // validator at :290, stored as a free string and validated against nothing,
20
- // because "nothing here reads them"). The fold has no migration and drops
21
- // nothing, so a row marked between 08-25 and 08-27 still carries
22
- // `engine: "codex"` UNLESS a later line overwrote that one field — which is
23
- // possible: the fold lets an equal-or-stronger write replace a field held by
24
- // an earlier one (`beats`, the authority comparison near shared/work-items.mjs
25
- // :494). Nothing has ever been written to clear it on purpose, so the honest
26
- // statement is "probably still there", not "always will be".
27
- //
28
- // HOW MANY SUCH ROWS A STORE ACTUALLY HAS IS AN OPEN QUESTION, AND IT IS NOT
29
- // ZERO UNTIL SOMEBODY LOOKS. It could not be measured from the machine this was
30
- // written on: there is no ~/Zero there at all. On a real store it is one command,
31
- // `grep -rho '"engine":"[a-z]*"' ~/Zero | sort | uniq -c`, and the answer
32
- // belongs in this paragraph when someone runs it. The gate below costs nothing
33
- // if the count turns out to be zero, and is the whole story if it is not.
34
- //
35
- // So the 08-25 resolver, restored unchanged, would have been a silent reversal
36
- // of the decision to withhold Codex: those rows would start choosing Codex again the moment this
37
- // module landed, with no picker, nothing in Settings, and no approval path
38
- // built. The resolver is therefore CLOSED unless the caller hands it
39
- // `ENGINE_CHOICE_ENABLED` below. A caller that passes nothing gets Claude
40
- // Code, because the safe answer is the one that has to be the accident.
41
- //
42
- // This module holds the names and the one rule. Everything Codex-shaped —
43
- // arguments, its event stream, where it writes its transcript — belongs in
44
- // main/codex.mjs when that comes back, because the renderer must never import
45
- // spawn code.
12
+ // This module holds the names and the one rule. Codex arguments and its event
13
+ // stream live in main/codex.mjs, because the renderer imports this file and
14
+ // must never import spawn code.
46
15
 
47
- /** The engines, in the order the picker draws them. */
48
- export const ENGINES = [
49
- { id: 'claude', label: 'Claude Code', word: 'Claude Code' },
50
- { id: 'codex', label: 'Codex', word: 'Codex' },
51
- ];
16
+ /**
17
+ * The engines, in the order the picker draws them. Admitted harnesses only: a
18
+ * registered harness (OpenCode, say) still works through `harnessFor` by name
19
+ * but is not offered anywhere until it is admitted. The bar for admission is in
20
+ * docs/harnesses.md.
21
+ */
22
+ export const ENGINES = HARNESS_DEFINITIONS
23
+ .filter(({ admitted }) => admitted !== false)
24
+ .map(({ id, label, word }) => ({ id, label, word }));
52
25
 
53
26
  export const ENGINE_IDS = ENGINES.map((e) => e.id);
54
27
 
55
28
  /**
56
- * What a task runs on when nobody has chosen.
57
- *
58
- * Not a setting and not a coin flip. See the measurement above: Claude Code is
59
- * faster on median and never wandered, and it is the only one of the two that
60
- * is WIRED to stop and ask her for permission mid-run.
61
- *
62
- * That second clause used to say Codex CANNOT ask, and it is now wrong twice
63
- * over. It was true of `codex exec`, which is what the removed 08-25 build
64
- * drove and what the sentence was written about. `codex app-server` raises real
65
- * approval requests, measured 2026-09-04, main/codex-app-server.mjs answers
66
- * them, main/codex-approvals.mjs draws them as the card she already knows, and
67
- * the spool they land in is the one the other engine uses. This paragraph went
68
- * on listing all four of those as missing for a whole slice after two of them
69
- * had been built.
70
- *
71
- * THE PICKER IS BUILT NOW TOO, 2026-09-04: a word in the composer's sentence
72
- * ("With Codex."), a row in Settings, and the engine named in the byline --
73
- * every one of them drawn only where `Supervisor#engineChoices` says this Mac
74
- * really offers a choice, which means the gate is open and the binary is there.
75
- * That paragraph used to say the missing picker was why the default had not
76
- * moved, and it was the last of four such claims to stop being true.
77
- *
78
- * SO THE DEFAULT IS HELD BY THE MEASUREMENT AT THE TOP OF THIS FILE AND BY
79
- * NOTHING ELSE. Codex is slower on median and it wanders; Claude Code stays the
80
- * default until she says otherwise, and now that there is a picker that is a
81
- * decision rather than a consequence.
29
+ * What a task runs on when nobody has chosen. It is Claude Code for the speed
30
+ * reason at the top of this file, not because Codex lacks approvals or a
31
+ * picker: both exist now.
82
32
  */
83
33
  export const DEFAULT_ENGINE = 'claude';
84
34
 
85
35
  /**
86
- * THE CAPABILITY GATE: an explicit code opt-in, saying that whoever wrote this
87
- * call site knew there was a second engine and meant to allow it.
88
- *
89
- * BE PRECISE ABOUT WHAT IT PROVES, because a gate believed to be stronger than
90
- * it is, is worse than no gate. It is a hard barrier against SERIALIZED DATA:
91
- * nothing that arrives as a ledger line, a config file or an IPC message can
92
- * open it, ever. It is NOT proof that the approval path she needs was actually
93
- * built, and it is not a defence against our own code — any module in this repo
94
- * can import the token. It moves the decision from data to source, and that is
95
- * all it does. It is the second half, the picker and her consent, that makes
96
- * the choice real.
97
- *
98
- * It is a Symbol, and that shape is the whole design:
99
- *
100
- * - A SYMBOL CANNOT ARRIVE FROM OUTSIDE THE PROCESS. Every input that could
101
- * carry a stale `codex` is serialized: the ledger line off disk and
102
- * `zero.config.json` are JSON, which has no way to spell a Symbol and drops
103
- * it in silence, and an Electron IPC payload goes through the structured
104
- * clone algorithm, which does not drop a Symbol but THROWS DataCloneError on
105
- * one. Neither can express the gate. The louder of the two failures is the
106
- * better one and we get it where it matters least; the point is that the
107
- * untrusted data and the permission live in different type systems.
108
- * - IT IS `Symbol`, NEVER `Symbol.for`. The global registry would let any code
109
- * anywhere mint an equal one out of a string. This one exists only where
110
- * somebody imported this module and named it.
111
- * - IT IS NOT A BOOLEAN. `enabled: true` is the kind of thing a caller spreads
112
- * in by accident from some other options object. This is not.
36
+ * The capability gate: passing this to `engineFor` says the call site meant to
37
+ * allow a second engine. It blocks serialized data only. Any module here can
38
+ * import it.
39
+ *
40
+ * It is a Symbol because nothing from outside the process can carry one: JSON
41
+ * drops it, and Electron's structured clone throws DataCloneError on it. It
42
+ * uses `Symbol`, not `Symbol.for`, so no string can mint an equal one, and it
43
+ * is not a boolean, so a spread options object cannot open it by accident.
113
44
  */
114
45
  export const ENGINE_CHOICE_ENABLED = Symbol('the second engine is wired up in this build');
115
46
 
116
- /**
117
- * WHEN SHE OPENED THE GATE, read off `zero.config.json`, or null. This is the
118
- * one value in the app that can cause the Symbol above to be handed over
119
- * (main/supervisor.mjs `_engineFor`, the only holder of it).
120
- *
121
- * THE OPT-IN IS A MOMENT AND NOT A FLAG, and that is the whole design of it.
122
- *
123
- * `engine` survived the 2026-08-27 removal on the work-item contract and the
124
- * fold drops nothing, so her store may still hold rows marked `codex` between
125
- * 08-25 and 08-27. How many is unknown; on her Mac it is one command,
126
- * `grep -rho '"engine":"[a-z]*"' ~/Zero | sort | uniq -c`, and the answer
127
- * belongs here when somebody runs it. A boolean opt-in would mean "every row
128
- * that ever said codex is live from now on", and the first thing it would do is
129
- * route an unknown number of those onto a second engine she had marked before
130
- * there was any way to choose. That is the exact hazard the gate was built for,
131
- * so an opt-in that walks into it is not one worth having. The picker built on
132
- * 2026-09-04 does not soften this: an August row is still not a choice she is
133
- * making now, and the byline draws it as the engine that will really run it.
134
- *
135
- * THOSE TWO MEANINGS ARE SEPARABLE, AND THE LEDGER ALREADY HOLDS WHAT
136
- * SEPARATES THEM. The fold records `wrote[field] = { ts, source }` per field
137
- * and stopped deleting it on the way out precisely because readers needed it
138
- * (shared/work-items.mjs:324; shared/answers.mjs reads `wrote.answer.ts` the
139
- * same way). So "Codex may be chosen FROM NOW ON" is a checkable statement
140
- * rather than a hope, and `engineChoiceOnRowIsStale` below is the check.
141
- *
142
- * WHY NOT A BOOLEAN PLUS A REMEMBERED "first seen at". Because that would live
143
- * in the app's own state rather than in her file: a fresh install, a state file
144
- * she deleted, or a second Mac would each invent a different moment, and the
145
- * one thing this value must be is the same on every machine reading the same
146
- * store. A date the user typed is a fact about their decision; a date we noticed is a
147
- * fact about a process.
148
- *
149
- * THE WORKSPACE DEFAULT (`config.engine`) IS TIME-SCOPED TOO, SINCE 2026-09-05,
150
- * AND THIS PARAGRAPH USED TO SAY IT COULD NOT BE. Its words were: "JSON has no
151
- * per-key write times, and `engine` sits in the same file as this, so opening
152
- * that file to write the moment is the same act as reading the line above it."
153
- * The first clause is true of JSON and irrelevant: the APP has write times, one
154
- * module writes that key (`setWorkspaceSetting`, main/settings.mjs), and
155
- * `saveConfig` takes a patch of several keys, so `engineAt` rides in the same
156
- * write. Without it, closing the gate and reopening it with a NEWER moment
157
- * re-armed a workspace default set weeks earlier -- every row that names
158
- * nothing went straight back to Codex without her saying so again, which is the
159
- * exact claim this value is supposed to make impossible, and the rollback
160
- * lifecycle is the thing the gate is FOR.
161
- *
162
- * AND THE SECOND CLAUSE, READ CORRECTLY, IS WHY AN ABSENT STAMP MEANS HONOURED.
163
- * `engineChoice` is hand-written into this file; a hand-written `engine` on the
164
- * line above it was put there by the same hand in the same act, so the two ARE
165
- * contemporaneous and there is nothing to be stale against. Only a default the
166
- * app wrote can carry a stamp, and only a stamped default can go stale — which
167
- * is also what keeps every config that exists today behaving exactly as it did.
168
- * `engineDefaultIsStale` below is the check.
169
- *
170
- * IT IS PARSED STRICTLY, WHICH IS NOT THE SAME AS PARSED. `Date.parse` reads
171
- * "1", "2026", "Sep 4 2026" and "2026/09/04" as real dates, so a bare parse
172
- * would let `"engineChoice": "1"` open the second engine. The shape is checked
173
- * first and everything else is off, because the safe answer is the one that has
174
- * to be the accident.
175
- */
47
+ // The shape of a date somebody wrote down. `Date.parse` alone would accept "1"
48
+ // or "2026", and `"engineChoice": "1"` must not open the second engine.
176
49
  const A_MOMENT = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
177
50
 
178
- /**
179
- * One strict parse, used by both moments in this file, so the two cannot come
180
- * to disagree about what counts as a date somebody wrote down. */
181
51
  function momentIn(written) {
182
52
  if (typeof written !== 'string' || !A_MOMENT.test(written)) return null;
183
53
  const at = Date.parse(written);
184
54
  return Number.isFinite(at) ? at : null;
185
55
  }
186
56
 
57
+ /**
58
+ * When the user opened the gate (`engineChoice` in zero.config.json), or null.
59
+ * It is a moment, not a flag, so an engine written on a row or as the default
60
+ * before it is not a choice made now and is ignored. It lives in the config
61
+ * rather than app state so every machine reading the same store agrees.
62
+ * main/supervisor.mjs `_engineFor` is the only place that turns it into the token.
63
+ */
187
64
  export function engineChoiceSince(config = {}) {
188
65
  return momentIn(config?.engineChoice);
189
66
  }
190
67
 
191
68
  /**
192
- * WHEN THE WORKSPACE DEFAULT WAS SET, read off `zero.config.json`, or null.
193
- *
194
- * Null means no stamp, and no stamp means a hand-written default — see the long
195
- * note above `engineChoiceSince`. It is the same strict parse for the same
196
- * reason: a claim that cannot be read is not a claim, so it reads as no stamp,
197
- * which is the hand-written case.
69
+ * When the app last wrote the workspace default (`engineAt`, written beside
70
+ * `engine` by `setWorkspaceSetting`), or null. Null means the default was typed
71
+ * into the file by hand, in the same act as `engineChoice`, so it is never stale.
198
72
  */
199
73
  export function engineDefaultSince(config = {}) {
200
74
  return momentIn(config?.engineAt);
201
75
  }
202
76
 
203
77
  /**
204
- * Whether the workspace default is one she set BEFORE the gate's current
205
- * moment: a setting from the build before the rollback, rather than a decision
206
- * she is making now.
207
- *
208
- * False when nobody has opted in, because there is no moment to be stale
209
- * against. False when the default is Claude Code, which asks for nothing the
210
- * gate withholds — the same rule `engineChoiceOnRowIsStale` follows below, and
211
- * for the same reason: discarding it could only ever move work TO the second
212
- * engine, which is the one direction the gate exists to prevent. False when
213
- * there is no stamp, which is a default typed beside the moment itself.
78
+ * Whether the workspace default was set before the gate's current moment.
79
+ * Never true for Claude Code: discarding that default could only move work onto
80
+ * the second engine.
214
81
  */
215
82
  export function engineDefaultIsStale(config = {}) {
216
83
  const since = engineChoiceSince(config);
@@ -221,33 +88,13 @@ export function engineDefaultIsStale(config = {}) {
221
88
  }
222
89
 
223
90
  /**
224
- * Whether the engine named on this row is one picked BEFORE she opened the
225
- * gate: an August row, rather than a decision she is making now.
226
- *
227
- * False when nobody has opted in, because there is no moment to be stale
228
- * against and the gate itself is what refuses then. False when the row names no
229
- * engine, or names one nothing recognises, because neither is asking for
230
- * anything and a warning on an ordinary row is noise on every row. True when
231
- * the row names an engine and there is no `wrote.engine.ts` at or after the
232
- * moment — which also covers an imported session and an old line with no
233
- * `wrote` map at all, where there is simply no evidence she chose anything.
234
- *
235
- * AND FALSE FOR A ROW THAT NAMES CLAUDE CODE, WHICH THIS ASKED WITH `isEngine`
236
- * AND GOT BACKWARDS. A row saying `engine: "claude"`, written before the moment,
237
- * was discarded as stale — and `engineFor` then fell through to `config.engine`,
238
- * which may be `codex`. So her explicit "run this on Claude Code" was read as no
239
- * choice at all and answered with the second engine, which is the precise
240
- * opposite of what staleness is for. `enginePicked` is the fix and it is the
241
- * whole of it: naming the DEFAULT asks for nothing the gate withholds, so there
242
- * is nothing to be stale about, and discarding it could only ever move work onto
243
- * the engine the gate was built to hold back.
91
+ * Whether the engine named on this row was written before the gate's moment.
92
+ * A row with no `wrote.engine.ts` counts as stale, since there is no evidence
93
+ * of a choice. A row naming Claude Code is never stale: discarding it would let
94
+ * `engineFor` fall through to a Codex workspace default.
244
95
  *
245
- * `wrote.engine.source` is deliberately NOT consulted. would be the stronger
246
- * rule, but CLAUDE.md is explicit that a ledger line names its own author as a
247
- * plain string and nothing checks it, so a source check would guard against an
248
- * accident and never against a hostile worker, while silently refusing whatever
249
- * a future picker happens to write. Who may write `engine` at all belongs to
250
- * the slice that draws one.
96
+ * `wrote.engine.source` is not consulted, because a ledger line names its own
97
+ * author as an unchecked string.
251
98
  */
252
99
  export function engineChoiceOnRowIsStale(item, config = {}) {
253
100
  const since = engineChoiceSince(config);
@@ -266,34 +113,14 @@ export function engineLabel(id) {
266
113
  }
267
114
 
268
115
  /**
269
- * Which engines this Mac can actually run, given what the finders found.
270
- *
271
- * `found` is a map of id to boolean. An engine whose binary is not on the
272
- * machine is not offered, because a picker that offers something that cannot
273
- * run is a picker that hands her a session that dies on arrival. Claude Code
274
- * is in the list even when it was not found, UNLESS Codex was: a Mac with only
275
- * Codex is offered only Codex (`homeEngine` below). With neither, the app
276
- * refuses to open an inbox and says so in its own words.
277
- *
278
- * UNGATED, AND THE INVARIANT IS THEREFORE SCOPED TO ROUTING. Say it plainly,
279
- * because the file must not claim more than the code delivers: this function,
280
- * `engineChoiceExists` and `enginePicked` will all happily describe a two-engine
281
- * Mac without anyone holding the token. A future renderer could draw a picker
282
- * out of them, and the gate would not stop it — she would be offered a choice
283
- * that `engineFor` then refuses, which is its own kind of lie.
284
- *
285
- * That is a deliberate trade and not an oversight. These three answer "what is
286
- * on this Mac" and "what did she pick"; they spawn nothing, and requiring a
287
- * capability token to answer a question about the filesystem buys nothing real.
288
- *
289
- * AND THE SLICE THAT DREW A PICKER ANSWERED FOR IT, 2026-09-04. There is
290
- * exactly one caller, `Supervisor#engineChoices`, and it asks
291
- * `engineChoiceSince` FIRST -- the same expression `_engineFor` derives the gate
292
- * token from -- so the offer and the routing are refused by the same fact and
293
- * cannot come apart. A Mac with Codex installed and no moment written answers
294
- * one engine here and Claude Code there. The renderer calls none of these: it
295
- * is handed the list on the snapshot. tests/a-codex-row-from-august-still-runs-on-claude.test.mjs
296
- * holds the caller list exactly, so the next one has to argue for itself here.
116
+ * Which engines this Mac can run, given `found` (id to boolean from the
117
+ * finders). Claude Code is listed even when not found, unless Codex was.
118
+ *
119
+ * This is ungated, like `engineChoiceExists` and `enginePicked`. Its one caller
120
+ * is `Supervisor#engineChoices`, which asks `engineChoiceSince` first, so the
121
+ * offer and the routing cannot disagree. The renderer gets the list on the
122
+ * snapshot. tests/a-codex-row-from-august-still-runs-on-claude.test.mjs pins the
123
+ * caller list.
297
124
  */
298
125
  export function availableEngines(found = {}) {
299
126
  const home = homeEngine(found);
@@ -301,13 +128,9 @@ export function availableEngines(found = {}) {
301
128
  }
302
129
 
303
130
  /**
304
- * WHAT RUNS WHEN NOBODY HAS CHOSEN, ON THIS MAC. Claude Code, unless this Mac
305
- * has said for certain that Claude Code is not here and Codex is. The app needs
306
- * one of the two, not both, so a Mac with only Codex runs everything on Codex.
307
- *
308
- * `found.claude` has to be an explicit `false`. A caller that never asked
309
- * (every call written before this, and every test config) leaves it undefined,
310
- * and that reads as Claude Code being here, which is what it always meant.
131
+ * What runs on this Mac when nobody has chosen: Codex only when `found.claude`
132
+ * is explicitly false and Codex was found. Undefined means a caller that never
133
+ * asked, and reads as Claude Code being here.
311
134
  */
312
135
  export function homeEngine(found = {}) {
313
136
  return found.claude === false && found.codex ? 'codex' : DEFAULT_ENGINE;
@@ -319,37 +142,13 @@ export function engineChoiceExists(found = {}) {
319
142
  }
320
143
 
321
144
  /**
322
- * THE RULE. Which engine one work item runs on, and it is deliberately narrow.
323
- *
324
- * The gate answers first: without `enabled: ENGINE_CHOICE_ENABLED` this is
325
- * Claude Code, whatever the row says and whatever the workspace says. That is
326
- * what holds the August rows described at the top of this file, and it means
327
- * the honest answer for any caller that has not been taught about the second
328
- * engine — including every caller written before it comes back — is the engine
329
- * she is running.
330
- *
331
- * Then her choice on the task, over the workspace default, which is hers to
332
- * set in Settings, over Claude Code. An engine named on a row whose binary is
333
- * not on this Mac falls back rather than dying: she may have chosen Codex on
334
- * her laptop and be reading the row on a machine without it, and a session
335
- * that never starts tells her nothing about why. That fallback is unchanged
336
- * and the gate is IN ADDITION to it, not instead of it — the binary is a fact
337
- * about this Mac, the gate is a fact about this build, and either one missing
338
- * is enough to answer Claude Code.
339
- *
340
- * AND A CHOICE FROM BEFORE SHE OPENED THE GATE IS NOT A CHOICE SHE IS MAKING.
341
- * When the config names the moment she opted in, a row whose `engine` predates
342
- * it reads as no choice at all and the workspace default answers instead. That
343
- * is `engineChoiceOnRowIsStale` above, and it is the whole of what makes the
344
- * opt-in mean "Codex may be chosen from now on" rather than "every row that
345
- * ever said codex is live". A caller that passes no such moment gets the old
346
- * rule unchanged, which is why every assertion written before this one still
347
- * holds -- but the single caller in the app that can open the gate derives the
348
- * token FROM that same value, so in her app the two cannot come apart.
145
+ * Which engine one work item runs on. A Mac with only Codex runs Codex.
146
+ * Otherwise, without `enabled: ENGINE_CHOICE_ENABLED` it is Claude Code. With
147
+ * it, the row's engine wins over the workspace default, each ignored if written
148
+ * before the gate's moment. An engine whose binary is not on this Mac falls back
149
+ * to Claude Code, since the row may have been chosen on another machine.
349
150
  */
350
151
  export function engineFor(item, { config = {}, found = {}, enabled } = {}) {
351
- // ONE ENGINE ON THE MAC IS NOT A CHOICE, so the gate has nothing to guard.
352
- // Without Claude Code, a row marked `claude` would only die on arrival.
353
152
  if (homeEngine(found) !== DEFAULT_ENGINE) return homeEngine(found);
354
153
  if (enabled !== ENGINE_CHOICE_ENABLED) return DEFAULT_ENGINE;
355
154
  const wanted = isEngine(item?.engine) && !engineChoiceOnRowIsStale(item, config) ? item.engine : null;
@@ -360,43 +159,20 @@ export function engineFor(item, { config = {}, found = {}, enabled } = {}) {
360
159
  }
361
160
 
362
161
  /**
363
- * Whether the engine on this row is one she chose, as opposed to the one it
364
- * would run on anyway. What makes the composer's footer stay open, the same
365
- * rule the level and the model follow (renderer/src/compose-footer.ts).
162
+ * Whether the row names an engine other than the default. The composer's footer
163
+ * stays open on it, like the level and the model (renderer/src/compose-footer.ts).
366
164
  */
367
165
  export function enginePicked(id) {
368
166
  return isEngine(id) && id !== DEFAULT_ENGINE;
369
167
  }
370
168
 
371
169
  /**
372
- * THE MODEL ON THIS ROW, OR NULL BECAUSE IT BELONGS TO THE OTHER HARNESS.
373
- *
374
- * THE MIRROR OF `codexModelRefusal`, WHICH ONLY EVER GUARDED ONE DIRECTION.
375
- * That one stops a Claude alias reaching a Codex run. Nothing stopped the
376
- * reverse, and the reverse happens BY ITSELF: `engineFor` above falls back to
377
- * Claude Code when Codex is not on this Mac -- which is correct and stays --
378
- * and `spawnPlan` then applied `item.model` without asking which engine wrote
379
- * it. Measured 2026-09-05 by reading the resolved plan: a row saying `engine:
380
- * "codex", model: "gpt-5.6-sol"` -- the slug her own config.toml names --
381
- * produced `claude -p <brief> --model gpt-5.6-sol`, which Claude Code has never
382
- * heard of. It dies on arrival and dies again on every retry.
383
- *
384
- * THE TWO FIELDS ARE ONE CHOICE AND THIS IS WHY. The composer's model list is
385
- * scoped to the engine (`engineModelChoices`, renderer/src/models.ts) because
386
- * the two vocabularies do not overlap at all: `opus` is an alias Claude Code
387
- * resolves, `gpt-5.6-sol` is an exact slug Codex takes. So a model word means
388
- * nothing without the engine beside it, and a row whose engine has changed
389
- * under it has no model, rather than one that reads across.
390
- *
391
- * NULL IS NOT A STAND-IN AND THE CALLER MUST NOT TREAT IT AS ONE. This says
392
- * "there is no model on this row for this run"; whether that means running on
393
- * the workspace's own model or refusing the run outright is the caller's
394
- * decision, and main/supervisor.mjs makes it -- it refuses, and says so on the
395
- * row, for the reason `codexModelRefusal` gives on the other side.
396
- *
397
- * A ROW THAT NAMES NO ENGINE IS UNTOUCHED, which is every row she has ever
398
- * written: there is no other harness in the sentence, so the model rides
399
- * exactly as it did.
170
+ * The row's model for a run on `engine`, or null when the row names a different
171
+ * engine. Claude and Codex model names do not overlap, so a Codex slug must not
172
+ * reach `claude --model` when `engineFor` falls back to Claude Code. This is the
173
+ * mirror of `codexModelRefusal`. Null means "no model for this run", and
174
+ * main/supervisor.mjs decides what to do with it. A row naming no engine keeps
175
+ * its model.
400
176
  */
401
177
  export function modelForEngine(item, engine) {
402
178
  const model = typeof item?.model === 'string' && item.model ? item.model : null;
@@ -12,3 +12,10 @@ export function feedbackPayload(p?: {
12
12
  files?: { name: string; type: string; data: string }[];
13
13
  app?: { version?: string; os?: string };
14
14
  }): { text: string; files: { name: string; type: string; data: string }[]; app: { version: string; os: string } };
15
+
16
+ export function sendFeedback(p: {
17
+ url: string | null | undefined;
18
+ payload: { text: string; files: { name: string; type: string; data: string }[]; app?: { version: string; os: string } };
19
+ fetchImpl?: (url: string, init: RequestInit) => Promise<{ ok: boolean }>;
20
+ timeoutMs?: number;
21
+ }): Promise<{ ok: boolean; error?: string }>;
@@ -5,6 +5,7 @@
5
5
  //
6
6
  // Words, files, or both, and never nothing. The size limit keeps one request
7
7
  // well inside what the mail provider takes as attachments.
8
+ import { Name } from './product-name.mjs';
8
9
 
9
10
  export const FEEDBACK_LIMITS = Object.freeze({
10
11
  text: 20_000,
@@ -42,3 +43,27 @@ export function feedbackPayload({ text = '', files = [], app = {} } = {}) {
42
43
  app: { version: String(app.version ?? ''), os: String(app.os ?? '') },
43
44
  };
44
45
  }
46
+
47
+ // Answers { ok: true } or { ok: false, error } with the error as a sentence.
48
+ // It never throws, so the card always has something to say. The Mac sends
49
+ // through it (main/feedback.mjs) and so does the phone website (cloud-bridge.ts).
50
+ export async function sendFeedback({ url, payload, fetchImpl = globalThis.fetch, timeoutMs = 60_000 }) {
51
+ if (!url) return { ok: false, error: `Feedback is not set up in this copy of ${Name}.` };
52
+ // Checked again here, off the bytes that would actually travel, since the
53
+ // card is not the last word on what leaves the device.
54
+ const sized = (payload?.files ?? []).map((f) => ({ size: Math.floor((String(f?.data ?? '').length * 3) / 4) }));
55
+ const check = checkFeedback({ text: payload?.text, files: sized });
56
+ if (!check.ok) return { ok: false, error: check.reason };
57
+ try {
58
+ const res = await fetchImpl(url, {
59
+ method: 'POST',
60
+ headers: { 'content-type': 'application/json' },
61
+ body: JSON.stringify(payload),
62
+ signal: AbortSignal.timeout(timeoutMs),
63
+ });
64
+ if (res?.ok) return { ok: true };
65
+ return { ok: false, error: 'It did not send. Try again in a moment.' };
66
+ } catch {
67
+ return { ok: false, error: 'It did not send. Check your connection and try again.' };
68
+ }
69
+ }
@@ -267,47 +267,40 @@ export const PRACTICE_ROWS = [
267
267
  * off the screen by `walkRows` until the tour. Each `state` is the column it
268
268
  * lands in, read off the row by the app's own rules and not by this file:
269
269
  *
270
- * * `needs`: an agent finished, or stopped on a question. Needs you. Only on
271
- * the board, never in the tour, because the tour opens on "Your inbox is
272
- * now empty" and these would make that untrue.
273
270
  * * `working`: sent and not finished. In progress.
274
271
  * * `scheduled`: set for tomorrow morning. Scheduled.
275
272
  *
276
273
  * None is finished, so "Everything you finished is kept here" stays true of
277
- * the Closed tab. */
274
+ * the Closed tab.
275
+ *
276
+ * AND NOT ONE OF THEM MAY EVER NEED YOU (2026-10-24). Two were `needs`, held
277
+ * back from the tour and let through from the board on, so pressing B put two
278
+ * rows into a Needs you the tour had just called empty. The same two pieces of
279
+ * work are still here in states that ask nothing of anybody, which keeps both
280
+ * rules: something in every column, and the inbox stays at zero. */
278
281
  export const PRACTICE_BACKDROP = [
279
282
  {
280
- state: 'needs',
283
+ state: 'working',
281
284
  kind: 'task',
282
- title: 'Booked the train for the offsite.',
285
+ title: 'Book the train for the offsite.',
283
286
  body: 'Book train tickets for the team to the offsite on Thursday morning.',
284
287
  trace: [
285
288
  '[Read] Offsite plan.docx',
286
289
  '[Read] Team contacts.xlsx',
287
290
  'Twelve people, all leaving from the city on Thursday morning.',
288
- '[Write] Train booking.pdf',
291
+ 'Comparing the 8:10 and the 9:10, which is the last one that still gets everybody there in time.',
289
292
  ],
290
- result: 'Twelve seats on the 8:10 on Thursday, all in one carriage. The tickets are in the offsite folder.',
291
293
  agoMs: 9 * 60_000,
292
294
  },
293
295
  {
294
- state: 'needs',
295
- kind: 'question',
296
- title: 'Should the welcome pack go out Monday or Wednesday?',
297
- body: 'Send the welcome pack to the two new starters, and check the day with me first.',
298
- trace: [
299
- '[Read] Welcome pack.docx',
300
- '[Read] Team calendar.ics',
301
- 'One starter begins on Monday and the other on Wednesday.',
302
- ],
303
- result: [
304
- 'One new starter begins Monday and the other Wednesday. Sending both on Monday means nobody waits.',
305
- '',
306
- '## Options',
307
- '1. Monday, for both (recommended)',
308
- '2. On each person\'s first day',
309
- ].join('\n'),
296
+ state: 'scheduled',
297
+ kind: 'task',
298
+ title: 'Send the welcome pack to the two new starters.',
299
+ body: 'Send the welcome pack to the two new starters on Monday, before either of them begins.',
300
+ trace: [],
310
301
  agoMs: 26 * 60_000,
302
+ // Monday morning, counted from when the practice project is made.
303
+ inMs: 66 * 3_600_000,
311
304
  },
312
305
  {
313
306
  state: 'working',