agentbox-app 0.1.12 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/main/acp.mjs +296 -0
  2. package/main/agent-homes.mjs +158 -0
  3. package/main/agent-sessions.mjs +57 -20
  4. package/main/agent-updates.mjs +9 -22
  5. package/main/analytics.mjs +30 -0
  6. package/main/claude-bin.mjs +45 -311
  7. package/main/claude-input.mjs +9 -3
  8. package/main/claude-launch-env.mjs +54 -0
  9. package/main/claude-models.mjs +39 -7
  10. package/main/claude-plan.mjs +16 -16
  11. package/main/claude-usage.mjs +82 -25
  12. package/main/codex-bin.mjs +51 -249
  13. package/main/codex-session.mjs +38 -3
  14. package/main/codex-threads.mjs +41 -8
  15. package/main/codex-watch.mjs +67 -3
  16. package/main/config.mjs +26 -4
  17. package/main/corner-tag.mjs +489 -0
  18. package/main/crash-report.mjs +26 -3
  19. package/main/engine-bin.mjs +595 -0
  20. package/main/engine-setup.mjs +4 -15
  21. package/main/feedback.mjs +11 -6
  22. package/main/harnesses/claude-stream.mjs +208 -0
  23. package/main/harnesses/claude.mjs +84 -0
  24. package/main/harnesses/codex.mjs +65 -0
  25. package/main/harnesses/common.mjs +78 -0
  26. package/main/harnesses/instructions-file.mjs +116 -0
  27. package/main/harnesses/opencode.mjs +234 -0
  28. package/main/harnesses.mjs +65 -0
  29. package/main/importable-threads.mjs +38 -0
  30. package/main/ipc.mjs +165 -31
  31. package/main/local-app-frames.mjs +82 -0
  32. package/main/main.mjs +100 -4
  33. package/main/memory-gate-history.mjs +51 -12
  34. package/main/memory-gate-server.mjs +67 -11
  35. package/main/memory-gate.mjs +49 -8
  36. package/main/memory-log.mjs +124 -0
  37. package/main/notify.mjs +47 -4
  38. package/main/opencode-server.mjs +271 -0
  39. package/main/opencode.mjs +287 -0
  40. package/main/phone-link.mjs +551 -0
  41. package/main/relay-address.mjs +28 -0
  42. package/main/relay-client.mjs +246 -0
  43. package/main/row-label.mjs +6 -6
  44. package/main/runs-on.mjs +32 -35
  45. package/main/serve.mjs +113 -9
  46. package/main/settings.mjs +148 -10
  47. package/main/shell-path.mjs +86 -0
  48. package/main/ship-queue.mjs +24 -6
  49. package/main/sign-in-files.mjs +3 -7
  50. package/main/source-updater.mjs +27 -1
  51. package/main/start-hold.mjs +73 -0
  52. package/main/store.mjs +1 -1
  53. package/main/supervisor.mjs +832 -428
  54. package/main/task-commands.mjs +13 -17
  55. package/main/task-folders-offthread.mjs +6 -1
  56. package/main/task-folders.mjs +490 -98
  57. package/main/task-terminals.mjs +37 -7
  58. package/main/updater.mjs +71 -9
  59. package/main/worktree-include.mjs +494 -0
  60. package/package.json +10 -1
  61. package/renderer/dist/assets/abnfDiagram-YUKMZFIV-DPPuMWt-.js +1 -0
  62. package/renderer/dist/assets/arc-HmFfdseH.js +1 -0
  63. package/renderer/dist/assets/architectureDiagram-47P4ROYG-DxJ3zHrH.js +36 -0
  64. package/renderer/dist/assets/blockDiagram-E7TT5TSB-CbTXzOqQ.js +140 -0
  65. package/renderer/dist/assets/c4Diagram-CRA5TL53-X5ItFyLn.js +38 -0
  66. package/renderer/dist/assets/channel-BlT2g9Sz.js +1 -0
  67. package/renderer/dist/assets/chunk-2Q5K7J3B-DC12Ho_L.js +1 -0
  68. package/renderer/dist/assets/chunk-5VM5RSS4-ClVtfPd0.js +15 -0
  69. package/renderer/dist/assets/chunk-7TKQ45FW-DgcOm0HT.js +1 -0
  70. package/renderer/dist/assets/chunk-AW2ZBBNX-C01s4yoF.js +88 -0
  71. package/renderer/dist/assets/chunk-FVRAUYC3-Bj0zxhR4.js +2 -0
  72. package/renderer/dist/assets/chunk-HTAEGDNF-DWDeEhLU.js +1 -0
  73. package/renderer/dist/assets/chunk-JWPE2WC7-Dsqwv3eW.js +1 -0
  74. package/renderer/dist/assets/chunk-KQW6MTUR-CE86ANL2.js +1 -0
  75. package/renderer/dist/assets/chunk-XXDRQBXY-CoNTASfs.js +1 -0
  76. package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-2wprLqtw.js +217 -0
  77. package/renderer/dist/assets/cornerTag-BLrxiDE5.css +1 -0
  78. package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +1 -0
  79. package/renderer/dist/assets/cose-bilkent-JH36ORCC-enqMiYbn.js +1 -0
  80. package/renderer/dist/assets/cynefin-EF2NZ3EQ-DHngLxvQ.js +141 -0
  81. package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-C0rZZIl3.js +62 -0
  82. package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
  83. package/renderer/dist/assets/dagre-W4DXFKR2-DQnaEWO7.js +4 -0
  84. package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  85. package/renderer/dist/assets/diagram-2UJZ2QOL-CS8AlGPx.js +200 -0
  86. package/renderer/dist/assets/diagram-OVF4WLC6-yiDMHIkY.js +24 -0
  87. package/renderer/dist/assets/diagram-PFPMY2P6-XbQKzbBA.js +30 -0
  88. package/renderer/dist/assets/diagram-UMYRVEAY-dhVvlfl_.js +24 -0
  89. package/renderer/dist/assets/diagram-YEKJPTXX-DZHe8SCf.js +41 -0
  90. package/renderer/dist/assets/diagram-ZIFT7M5P-B5h6aXVR.js +3 -0
  91. package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-PR9ScDco.js +1 -0
  92. package/renderer/dist/assets/elk-IJKZMXRS-Cg-yQXPd.js +27 -0
  93. package/renderer/dist/assets/erDiagram-O2IAPWRE-omOrJMxu.js +99 -0
  94. package/renderer/dist/assets/flowDiagram-OXPTDLAJ-aOX2hzvT.js +213 -0
  95. package/renderer/dist/assets/ganttDiagram-R7TSEDQI-B1H3pWyI.js +292 -0
  96. package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-Cvw304HC.js +106 -0
  97. package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
  98. package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-_ZXcNViH.js +2 -0
  99. package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
  100. package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js +70 -0
  101. package/renderer/dist/assets/journeyDiagram-COXZFDF6-fr62QoAt.js +139 -0
  102. package/renderer/dist/assets/kanban-definition-P3RFRI5V-J8zsabCe.js +89 -0
  103. package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
  104. package/renderer/dist/assets/linear-BmNdEtIi.js +1 -0
  105. package/renderer/dist/assets/main-ChNdnUvt.js +1132 -0
  106. package/renderer/dist/assets/main-nj0QR5kg.css +32 -0
  107. package/renderer/dist/assets/mermaid.core-CnYzvFkz.js +314 -0
  108. package/renderer/dist/assets/mindmap-definition-LZFPQGKD-Bfcxc7PM.js +96 -0
  109. package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  110. package/renderer/dist/assets/pegDiagram-BGZESJAR-CwUJOq6X.js +1 -0
  111. package/renderer/dist/assets/pieDiagram-CAPJLFHJ-BrwMmqfC.js +39 -0
  112. package/renderer/dist/assets/product-name-CTKLYNWY.js +40 -0
  113. package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-BN4Stoj-.js +7 -0
  114. package/renderer/dist/assets/railroadDiagram-SM67HX2B-CPZ5bbeR.js +1 -0
  115. package/renderer/dist/assets/requirementDiagram-X7JNWC4A-CgRnimVe.js +84 -0
  116. package/renderer/dist/assets/sankeyDiagram-UM26HJRW-Bt2rej__.js +40 -0
  117. package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js +169 -0
  118. package/renderer/dist/assets/sizeCapture-INFHLROL-usyNyXyh.js +1 -0
  119. package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-CRzUBb0u.js +291 -0
  120. package/renderer/dist/assets/swimlanes-N4OXWK64-C1GJvMS0.js +1 -0
  121. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +8 -0
  122. package/renderer/dist/assets/timeline-definition-YOQAKGHF-caeyPlcc.js +120 -0
  123. package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-BN_SV6PC.js +370 -0
  124. package/renderer/dist/assets/vennDiagram-BLWOH2XV-BLGuaUX-.js +34 -0
  125. package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-Da0cJeNc.js +78 -0
  126. package/renderer/dist/assets/xychartDiagram-TAQBALBS-cGfUffMk.js +7 -0
  127. package/renderer/dist/corner-tag.html +13 -0
  128. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  129. package/renderer/dist/emulator/data/compression/README.md +11 -0
  130. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  131. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  132. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  133. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  134. package/renderer/dist/emulator/data/cores/README.md +22 -0
  135. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  136. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  137. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  138. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  139. package/renderer/dist/emulator/data/cores/package.json +65 -0
  140. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  141. package/renderer/dist/emulator/data/emulator.css +1614 -0
  142. package/renderer/dist/emulator/data/loader.js +170 -0
  143. package/renderer/dist/emulator/data/localization/README.md +63 -0
  144. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  145. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  146. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  147. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  148. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  149. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  150. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  151. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  152. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  153. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  154. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  155. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  156. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  157. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  158. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  159. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  160. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  161. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  162. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  163. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  164. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  165. package/renderer/dist/emulator/data/src/compression.js +142 -0
  166. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  167. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  168. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  169. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  170. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  171. package/renderer/dist/emulator/data/src/storage.js +125 -0
  172. package/renderer/dist/emulator/data/version.json +5 -0
  173. package/renderer/dist/emulator/host.js +297 -0
  174. package/renderer/dist/emulator/index.html +28 -0
  175. package/renderer/dist/emulator/provenance.json +6 -0
  176. package/renderer/dist/index.html +16 -3
  177. package/scripts/approval-server.sh +24 -5
  178. package/scripts/before-publish.mjs +12 -10
  179. package/shared/agent-import.mjs +2 -0
  180. package/shared/agents.d.mts +1 -0
  181. package/shared/bridge-map.mjs +18 -0
  182. package/shared/claude-commands.generated.mjs +6 -4
  183. package/shared/claude-models.generated.mjs +6 -4
  184. package/shared/corner-tag.d.mts +14 -0
  185. package/shared/corner-tag.mjs +141 -0
  186. package/shared/engines.mjs +20 -5
  187. package/shared/harness-definitions.mjs +103 -0
  188. package/shared/notify-rules.d.mts +25 -0
  189. package/shared/notify-rules.mjs +19 -0
  190. package/shared/provider-commands.mjs +10 -2
  191. package/shared/rank.d.mts +2 -0
  192. package/shared/rank.mjs +50 -0
  193. package/shared/relay-address.mjs +6 -0
  194. package/shared/relay-crypto.mjs +165 -0
  195. package/shared/runs-on.d.mts +0 -10
  196. package/shared/runs-on.mjs +25 -49
  197. package/shared/shortcuts.mjs +10 -4
  198. package/shared/spawn-trouble.mjs +57 -6
  199. package/shared/terminal-input.d.mts +1 -0
  200. package/shared/terminal-input.mjs +30 -0
  201. package/shared/terminal-keys.d.mts +1 -0
  202. package/shared/terminal-keys.mjs +16 -0
  203. package/shared/terminal-size.d.mts +4 -0
  204. package/shared/terminal-size.mjs +29 -0
  205. package/shared/usage.d.mts +3 -0
  206. package/shared/usage.mjs +7 -0
  207. package/shared/where-it-runs-from.d.mts +43 -0
  208. package/shared/where-it-runs-from.mjs +155 -0
  209. package/shared/work-items.mjs +25 -1
  210. package/shared/work-lines.mjs +1 -1
  211. package/shared/xterm-invalid-range.d.mts +2 -0
  212. package/shared/xterm-invalid-range.mjs +58 -0
  213. package/renderer/dist/assets/index-ChKsQkRw.js +0 -1169
  214. package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
@@ -0,0 +1,43 @@
1
+ // Types for shared/where-it-runs-from.mjs, which is plain ESM so that main
2
+ // (Node) and the screen (Vite) share one copy of the rule about whether this
3
+ // copy of the app can update itself from where it is, and one copy of the
4
+ // words that say so. The screen asks for the words and `sayWhere`; the places
5
+ // and the move are main's.
6
+
7
+ /** The place a copy of the app is in, as far as updating itself is concerned. */
8
+ export type AppPlace =
9
+ | 'disk-image' | 'translocated' | 'applications' | 'elsewhere' | 'source' | 'not-mac';
10
+
11
+ export const PLACE: {
12
+ diskImage: 'disk-image';
13
+ translocated: 'translocated';
14
+ applications: 'applications';
15
+ elsewhere: 'elsewhere';
16
+ source: 'source';
17
+ notMac: 'not-mac';
18
+ };
19
+
20
+ export const MOVE_SAY: {
21
+ cannotUpdateHere: string;
22
+ button: string;
23
+ title: string;
24
+ where: Record<string, string>;
25
+ shortWhere: Record<string, string>;
26
+ fix: string;
27
+ retry: string;
28
+ };
29
+
30
+ /** The sentence for a place, or null where there is nothing to say. */
31
+ export function sayWhere(place: string | null | undefined, how?: { short?: boolean }): string | null;
32
+
33
+ export function placeOfApp(facts?: {
34
+ platform?: string;
35
+ packaged?: boolean;
36
+ exePath?: string | null;
37
+ inApplications?: boolean;
38
+ }): { place: AppPlace; canUpdate: boolean; offerMove: boolean };
39
+
40
+ export function createPlace(host: { app: unknown; log?: unknown }): {
41
+ state(): { place: AppPlace; canUpdate: boolean; offerMove: boolean };
42
+ move(): { moved: boolean; error: string | null };
43
+ };
@@ -0,0 +1,155 @@
1
+ // AN APP IN THE WRONG PLACE CAN NEVER UPDATE ITSELF, AND NEVER SAID SO.
2
+ //
3
+ // macOS has two places it will happily run Agentbox from and never let it
4
+ // replace itself:
5
+ //
6
+ // THE DISK IMAGE. You double-click the .dmg, you double-click the app
7
+ // inside it, and it works. The volume is read-only, so Squirrel.Mac refuses
8
+ // the swap with "Cannot update while running on a read-only volume".
9
+ //
10
+ // A TRANSLOCATED COPY. You drag the app to Downloads and open it. Because
11
+ // it still carries the quarantine flag and is not in Applications, macOS
12
+ // runs it from a read-only copy it makes under
13
+ // `/private/var/folders/…/AppTranslocation/<uuid>/d/`. Same refusal, and
14
+ // this is why Squirrel's own sentence talks about the Downloads directory.
15
+ //
16
+ // Measured in PostHog on 2026-10-12: 23 crash reports over three weeks, two
17
+ // installs, both on 0.1.11, one report every six hours (`EVERY_MS` in
18
+ // main/updater.mjs) for as long as the app was open. The reports stopped being
19
+ // filed as crashes in 292d419. The two Macs are still on 0.1.11.
20
+ //
21
+ // SO THE APP READS WHERE IT IS AND OFFERS THE ONE PRESS THAT FIXES IT. Electron
22
+ // has had `moveToApplicationsFolder` since 4: it moves the bundle, replaces an
23
+ // older copy if you say so, and relaunches from the new place. After that the
24
+ // ordinary six-hourly check works and the person never thinks about it again.
25
+ //
26
+ // EVERYTHING HERE IS A FACT AND NOT A GUESS, which is why the decision is a
27
+ // pure function with the four facts passed in: a wrong "you are in the wrong
28
+ // place" is a nag on a healthy install, and two of those are worse than the
29
+ // silence this replaces.
30
+
31
+ import { Name } from './product-name.mjs';
32
+
33
+ // THE WORDS, IN ONE PLACE, and main and the renderer both read them from here.
34
+ // `cannotUpdateHere` is the sentence `shortError` in main/updater.mjs puts in
35
+ // place of Squirrel's own paragraph, which is 240 characters with a URL in the
36
+ // middle and was arriving on the Settings row cut off at 160.
37
+ //
38
+ // Every sentence says where the app is and what one press does about it, and
39
+ // none of them says "read-only volume": that is the mechanism, and nobody
40
+ // holding the Mac asked about the mechanism.
41
+ export const MOVE_SAY = {
42
+ cannotUpdateHere: `${Name} cannot update itself from where it is.`,
43
+ button: 'Move to Applications',
44
+ /** The sidebar card's heading. */
45
+ title: 'Updates are stuck',
46
+ /** What the place is, as a person would say it. */
47
+ where: {
48
+ 'disk-image': `${Name} is running from the disk image it was downloaded in.`,
49
+ translocated: `${Name} is running from a read-only copy macOS made of it.`,
50
+ },
51
+ /** The same fact in half the words, for the sidebar card, which is one
52
+ * column wide and had the row's sentence running to six lines in it. */
53
+ shortWhere: {
54
+ 'disk-image': `${Name} can’t update itself from the disk image.`,
55
+ translocated: `${Name} can’t update itself from the copy macOS made.`,
56
+ },
57
+ /** And the press, which is the same either way. */
58
+ fix: 'Moving it to your Applications folder takes a few seconds and gets updates working again.',
59
+ retry: 'Pressing it again tries again.',
60
+ };
61
+
62
+ /** The sentence for a place, or null where there is nothing to say. `short` is
63
+ * the sidebar card's half of it: the button beside it already says the fix. */
64
+ export function sayWhere(place, { short = false } = {}) {
65
+ const words = short ? MOVE_SAY.shortWhere : MOVE_SAY.where;
66
+ return words[place] ?? null;
67
+ }
68
+
69
+ /** Where a copy of the app can be, as far as updating itself is concerned. */
70
+ export const PLACE = {
71
+ /** Run out of the mounted .dmg. Read-only; cannot update. */
72
+ diskImage: 'disk-image',
73
+ /** macOS is running a read-only copy of its own. Cannot update. */
74
+ translocated: 'translocated',
75
+ /** Where it belongs. */
76
+ applications: 'applications',
77
+ /** Somewhere writable that is not Applications: updates itself fine. */
78
+ elsewhere: 'elsewhere',
79
+ /** `npm start` from a checkout: main/source-updater.mjs, nothing to move. */
80
+ source: 'source',
81
+ /** No Applications folder and no `moveToApplicationsFolder`. */
82
+ notMac: 'not-mac',
83
+ };
84
+
85
+ // The folder macOS puts a translocated copy in. The path is always under
86
+ // `/private/var/folders`, but the one segment that names the mechanism is this
87
+ // one, and it is the only part Apple has kept stable. Written with "/" and
88
+ // not `path.sep`: these are only ever read on a Mac (`placeOfApp` returns
89
+ // before them anywhere else), and the screen imports this file, so it may not
90
+ // import anything from Node.
91
+ const TRANSLOCATED = '/AppTranslocation/';
92
+ // Every mounted volume lives here, including the disk image the download is.
93
+ const VOLUMES = '/Volumes/';
94
+
95
+ /**
96
+ * PURE. The whole decision, off four facts, so every case can be tested
97
+ * without a packaged app, a disk image, or a Mac.
98
+ *
99
+ * `exePath` is `app.getPath('exe')`, the binary inside the bundle;
100
+ * `inApplications` is Electron's own `app.isInApplicationsFolder()`, which
101
+ * knows about `~/Applications` as well as `/Applications` and is therefore
102
+ * never re-derived from the path here.
103
+ */
104
+ export function placeOfApp({ platform, packaged, exePath, inApplications } = {}) {
105
+ const no = (place) => ({ place, canUpdate: true, offerMove: false });
106
+ if (platform !== 'darwin') return no(PLACE.notMac);
107
+ if (!packaged) return no(PLACE.source);
108
+ if (inApplications) return no(PLACE.applications);
109
+ if (typeof exePath !== 'string' || !exePath) return no(PLACE.elsewhere);
110
+ const where = exePath.includes(TRANSLOCATED) ? PLACE.translocated
111
+ : exePath.startsWith(VOLUMES) ? PLACE.diskImage
112
+ : null;
113
+ // THE PLACES THAT ARE NOT APPLICATIONS AND UPDATE THEMSELVES ANYWAY are left
114
+ // alone on purpose. A copy sitting in Downloads that macOS did not
115
+ // translocate is on a writable disk and Squirrel swaps it happily; offering
116
+ // to move it would be the app asking for something it does not need.
117
+ if (!where) return no(PLACE.elsewhere);
118
+ return { place: where, canUpdate: false, offerMove: true };
119
+ }
120
+
121
+ /**
122
+ * The live one, over Electron's `app`. Read ONCE, at construction: the bundle
123
+ * cannot move under a running process except by the move below, which
124
+ * relaunches.
125
+ */
126
+ export function createPlace({ app, log = console }) {
127
+ const where = placeOfApp({
128
+ platform: app?.platform ?? process.platform,
129
+ packaged: !!app?.isPackaged,
130
+ exePath: (() => { try { return app?.getPath?.('exe') ?? null; } catch { return null; } })(),
131
+ inApplications: (() => { try { return !!app?.isInApplicationsFolder?.(); } catch { return false; } })(),
132
+ });
133
+
134
+ return {
135
+ state: () => ({ ...where }),
136
+
137
+ /**
138
+ * THE PRESS. Electron moves the bundle and relaunches, so nothing after a
139
+ * `true` here ever runs. It answers false when the person cancels its own
140
+ * "a copy is already there" box, which is an answer and not a failure.
141
+ */
142
+ move() {
143
+ if (!where.offerMove) return { moved: false, error: null };
144
+ try {
145
+ const moved = !!app.moveToApplicationsFolder();
146
+ return { moved, error: null };
147
+ } catch (e) {
148
+ // One line, because Settings shows this string to a person.
149
+ const error = String(e?.message ?? e ?? 'The move did not work.').split('\n')[0].trim();
150
+ log.warn?.('[place] could not move the app into Applications:', error);
151
+ return { moved: false, error };
152
+ }
153
+ },
154
+ };
155
+ }
@@ -366,6 +366,14 @@ function str(value, max) {
366
366
  return trimmed.slice(0, lo) + mark(trimmed.length - lo);
367
367
  }
368
368
 
369
+ // A text field whose empty string MEANS something: nothing chosen. Only a
370
+ // string may clear it, so a `null` that reached here without going through
371
+ // `answerItem` is still dropped rather than guessed at.
372
+ function clearable(value, max) {
373
+ if (typeof value !== 'string') return undefined;
374
+ return value.trim() ? str(value, max) : '';
375
+ }
376
+
369
377
  function coerceField(field, value) {
370
378
  switch (field) {
371
379
  case 'title': return str(value, MAX_TITLE);
@@ -388,7 +396,23 @@ function coerceField(field, value) {
388
396
  // shared/effort-levels.mjs, and the spawn gates on them, so a value this
389
397
  // module does not recognise is a run at Claude Code's own choice rather
390
398
  // than a row that never runs.
391
- case 'kind': case 'product': case 'parent': case 'engine': case 'model': case 'effort': return str(value, MAX_SHORT);
399
+ case 'kind': case 'product': case 'parent': case 'engine': return str(value, MAX_SHORT);
400
+ // THESE TWO ARE CLEARED BY WRITING '' AND SO '' IS A REAL VALUE, exactly
401
+ // as runAt's 0 and start's 'now' are, and for the same reason: a field that
402
+ // coerces to undefined is dropped by the fold, so without a falsy value
403
+ // that survives, a model could be chosen and never unchosen.
404
+ //
405
+ // They used to sit on the line above, which cost us the most common crash
406
+ // of launch week. The reply box's drawer hands a pick back as null for
407
+ // "nothing chosen" and sends `{ model, effort }` whenever it was touched at
408
+ // all; `answerItem` turns null into '' because `modelForEngine` reads the
409
+ // empty string as nothing chosen. `str()` then dropped it, `pickFields`
410
+ // answered null, and `buildLine` threw — before the answer was written,
411
+ // because the model is written first on purpose. So opening the drawer,
412
+ // picking a model and pressing send lost the message, with nothing on
413
+ // screen to say so. Five of the 14 crashes since the Oct 6 launch, on two
414
+ // installs (PostHog, 2026-10-08).
415
+ case 'model': case 'effort': return clearable(value, MAX_SHORT);
392
416
  case 'status': return WORK_ITEM_STATUSES.includes(value) ? value : undefined;
393
417
  case 'priority': return Number.isFinite(value) ? Math.trunc(value) : undefined;
394
418
  // When this item may next be acted on, in epoch ms. 0 means unscheduled,
@@ -637,7 +637,7 @@ export function groupWork(events, { min = RUN_MIN } = {}) {
637
637
  for (const e of events ?? []) {
638
638
  // SOMETHING SHE DID IS NOT THE AGENT'S WORK and never folds into it: three
639
639
  // snoozes are three lines, not one "You put it off" (w-49b4e45403).
640
- if (e && e.kind === 'work' && !e.yours) { run.push(e); continue; }
640
+ if (e && e.kind === 'work' && !e.yours && !e.standalone) { run.push(e); continue; }
641
641
  flush();
642
642
  out.push(e);
643
643
  }
@@ -0,0 +1,2 @@
1
+ export const INVALID_RANGE: string;
2
+ export function guardInvalidRange(terminal: unknown, options?: { warn?: (message: string) => void }): boolean;
@@ -0,0 +1,58 @@
1
+ // XTERM'S OWN ASSERTION, CAUGHT WHERE WE CAN REACH IT.
2
+ //
3
+ // @xterm/xterm 6.0.0's AccessibilityManager listens on the document for
4
+ // 'selectionchange', maps the selection onto terminal rows and columns, and
5
+ // does `throw new Error('invalid range')` when the result covers no cells,
6
+ // under its own comment "This should not happen unless we have some bugs". It
7
+ // happens. Three ordinary selections reach it, measured against the published
8
+ // bundle; the test beside this file
9
+ // (tests/a-selection-that-covers-no-terminal-cells-is-not-a-crash.test.mjs)
10
+ // lists them and says how.
11
+ //
12
+ // Only a screen reader user gets that handler at all, because the pane asks
13
+ // `app.accessibilitySupportEnabled` before turning `screenReaderMode` on. For
14
+ // them the throw escapes as an unhandled renderer error, and the terminal keeps
15
+ // whatever was selected before it, so a copy afterwards takes stale text.
16
+ //
17
+ // WHY A WRAPPER AND NOT A PATCHED BUNDLE. What we build against is minified
18
+ // `lib/xterm.mjs`, and rewriting a file inside node_modules at build time is
19
+ // machinery that has to hold through `npm ci` on two CI runners and every
20
+ // release. xterm dispatches the handler as `() => this._handleSelectionChange()`,
21
+ // off the instance, so one own property on the manager shadows it and the
22
+ // wrapper dies with the terminal.
23
+ //
24
+ // IT SWALLOWS ONE MESSAGE AND NOTHING ELSE. The same handler warns about two
25
+ // genuine race conditions and returns; anything that is not this exact
26
+ // assertion is somebody's bug and goes on up.
27
+ //
28
+ // This is a workaround, and the upstream fix is the assertion becoming a
29
+ // clearSelection and a return. When an xterm release carries that, the test
30
+ // above turns red on the bundle it reads, and this file goes.
31
+ export const INVALID_RANGE = 'invalid range';
32
+
33
+ /**
34
+ * Wrap the accessibility manager's selection handler on `terminal`, which must
35
+ * already be open. Answers whether there was one to wrap: false when
36
+ * screenReaderMode is off, which is every install not running a screen reader,
37
+ * and false when it is already wrapped.
38
+ */
39
+ export function guardInvalidRange(terminal, { warn = console.warn } = {}) {
40
+ const manager = terminal?._core?._accessibilityManager?.value;
41
+ const handler = manager?._handleSelectionChange;
42
+ if (typeof handler !== 'function' || handler.guardsInvalidRange) return false;
43
+ const guarded = function (...args) {
44
+ try {
45
+ return handler.apply(this, args);
46
+ } catch (e) {
47
+ // Message, not instanceof: an Error from another realm is still this bug.
48
+ if (e?.message !== INVALID_RANGE) throw e;
49
+ warn(`xterm mapped the selection onto no cells and threw "${INVALID_RANGE}"; cleared the terminal's selection instead`);
50
+ // It threw before mirroring the selection, so the terminal is still
51
+ // holding the previous one. Nothing is selected; say that.
52
+ terminal.clearSelection();
53
+ }
54
+ };
55
+ guarded.guardsInvalidRange = true;
56
+ manager._handleSelectionChange = guarded;
57
+ return true;
58
+ }