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.
- package/main/acp.mjs +296 -0
- package/main/agent-homes.mjs +158 -0
- package/main/agent-sessions.mjs +57 -20
- package/main/agent-updates.mjs +9 -22
- package/main/analytics.mjs +30 -0
- package/main/claude-bin.mjs +45 -311
- package/main/claude-input.mjs +9 -3
- package/main/claude-launch-env.mjs +54 -0
- package/main/claude-models.mjs +39 -7
- package/main/claude-plan.mjs +16 -16
- package/main/claude-usage.mjs +82 -25
- package/main/codex-bin.mjs +51 -249
- package/main/codex-session.mjs +38 -3
- package/main/codex-threads.mjs +41 -8
- package/main/codex-watch.mjs +67 -3
- package/main/config.mjs +26 -4
- package/main/corner-tag.mjs +489 -0
- package/main/crash-report.mjs +26 -3
- package/main/engine-bin.mjs +595 -0
- package/main/engine-setup.mjs +4 -15
- package/main/feedback.mjs +11 -6
- package/main/harnesses/claude-stream.mjs +208 -0
- package/main/harnesses/claude.mjs +84 -0
- package/main/harnesses/codex.mjs +65 -0
- package/main/harnesses/common.mjs +78 -0
- package/main/harnesses/instructions-file.mjs +116 -0
- package/main/harnesses/opencode.mjs +234 -0
- package/main/harnesses.mjs +65 -0
- package/main/importable-threads.mjs +38 -0
- package/main/ipc.mjs +165 -31
- package/main/local-app-frames.mjs +82 -0
- package/main/main.mjs +100 -4
- package/main/memory-gate-history.mjs +51 -12
- package/main/memory-gate-server.mjs +67 -11
- package/main/memory-gate.mjs +49 -8
- package/main/memory-log.mjs +124 -0
- package/main/notify.mjs +47 -4
- package/main/opencode-server.mjs +271 -0
- package/main/opencode.mjs +287 -0
- package/main/phone-link.mjs +551 -0
- package/main/relay-address.mjs +28 -0
- package/main/relay-client.mjs +246 -0
- package/main/row-label.mjs +6 -6
- package/main/runs-on.mjs +32 -35
- package/main/serve.mjs +113 -9
- package/main/settings.mjs +148 -10
- package/main/shell-path.mjs +86 -0
- package/main/ship-queue.mjs +24 -6
- package/main/sign-in-files.mjs +3 -7
- package/main/source-updater.mjs +27 -1
- package/main/start-hold.mjs +73 -0
- package/main/store.mjs +1 -1
- package/main/supervisor.mjs +832 -428
- package/main/task-commands.mjs +13 -17
- package/main/task-folders-offthread.mjs +6 -1
- package/main/task-folders.mjs +490 -98
- package/main/task-terminals.mjs +37 -7
- package/main/updater.mjs +71 -9
- package/main/worktree-include.mjs +494 -0
- package/package.json +10 -1
- package/renderer/dist/assets/abnfDiagram-YUKMZFIV-DPPuMWt-.js +1 -0
- package/renderer/dist/assets/arc-HmFfdseH.js +1 -0
- package/renderer/dist/assets/architectureDiagram-47P4ROYG-DxJ3zHrH.js +36 -0
- package/renderer/dist/assets/blockDiagram-E7TT5TSB-CbTXzOqQ.js +140 -0
- package/renderer/dist/assets/c4Diagram-CRA5TL53-X5ItFyLn.js +38 -0
- package/renderer/dist/assets/channel-BlT2g9Sz.js +1 -0
- package/renderer/dist/assets/chunk-2Q5K7J3B-DC12Ho_L.js +1 -0
- package/renderer/dist/assets/chunk-5VM5RSS4-ClVtfPd0.js +15 -0
- package/renderer/dist/assets/chunk-7TKQ45FW-DgcOm0HT.js +1 -0
- package/renderer/dist/assets/chunk-AW2ZBBNX-C01s4yoF.js +88 -0
- package/renderer/dist/assets/chunk-FVRAUYC3-Bj0zxhR4.js +2 -0
- package/renderer/dist/assets/chunk-HTAEGDNF-DWDeEhLU.js +1 -0
- package/renderer/dist/assets/chunk-JWPE2WC7-Dsqwv3eW.js +1 -0
- package/renderer/dist/assets/chunk-KQW6MTUR-CE86ANL2.js +1 -0
- package/renderer/dist/assets/chunk-XXDRQBXY-CoNTASfs.js +1 -0
- package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-2wprLqtw.js +217 -0
- package/renderer/dist/assets/cornerTag-BLrxiDE5.css +1 -0
- package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +1 -0
- package/renderer/dist/assets/cose-bilkent-JH36ORCC-enqMiYbn.js +1 -0
- package/renderer/dist/assets/cynefin-EF2NZ3EQ-DHngLxvQ.js +141 -0
- package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-C0rZZIl3.js +62 -0
- package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
- package/renderer/dist/assets/dagre-W4DXFKR2-DQnaEWO7.js +4 -0
- package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/renderer/dist/assets/diagram-2UJZ2QOL-CS8AlGPx.js +200 -0
- package/renderer/dist/assets/diagram-OVF4WLC6-yiDMHIkY.js +24 -0
- package/renderer/dist/assets/diagram-PFPMY2P6-XbQKzbBA.js +30 -0
- package/renderer/dist/assets/diagram-UMYRVEAY-dhVvlfl_.js +24 -0
- package/renderer/dist/assets/diagram-YEKJPTXX-DZHe8SCf.js +41 -0
- package/renderer/dist/assets/diagram-ZIFT7M5P-B5h6aXVR.js +3 -0
- package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-PR9ScDco.js +1 -0
- package/renderer/dist/assets/elk-IJKZMXRS-Cg-yQXPd.js +27 -0
- package/renderer/dist/assets/erDiagram-O2IAPWRE-omOrJMxu.js +99 -0
- package/renderer/dist/assets/flowDiagram-OXPTDLAJ-aOX2hzvT.js +213 -0
- package/renderer/dist/assets/ganttDiagram-R7TSEDQI-B1H3pWyI.js +292 -0
- package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-Cvw304HC.js +106 -0
- package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
- package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-_ZXcNViH.js +2 -0
- package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
- package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js +70 -0
- package/renderer/dist/assets/journeyDiagram-COXZFDF6-fr62QoAt.js +139 -0
- package/renderer/dist/assets/kanban-definition-P3RFRI5V-J8zsabCe.js +89 -0
- package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
- package/renderer/dist/assets/linear-BmNdEtIi.js +1 -0
- package/renderer/dist/assets/main-ChNdnUvt.js +1132 -0
- package/renderer/dist/assets/main-nj0QR5kg.css +32 -0
- package/renderer/dist/assets/mermaid.core-CnYzvFkz.js +314 -0
- package/renderer/dist/assets/mindmap-definition-LZFPQGKD-Bfcxc7PM.js +96 -0
- package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/renderer/dist/assets/pegDiagram-BGZESJAR-CwUJOq6X.js +1 -0
- package/renderer/dist/assets/pieDiagram-CAPJLFHJ-BrwMmqfC.js +39 -0
- package/renderer/dist/assets/product-name-CTKLYNWY.js +40 -0
- package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-BN4Stoj-.js +7 -0
- package/renderer/dist/assets/railroadDiagram-SM67HX2B-CPZ5bbeR.js +1 -0
- package/renderer/dist/assets/requirementDiagram-X7JNWC4A-CgRnimVe.js +84 -0
- package/renderer/dist/assets/sankeyDiagram-UM26HJRW-Bt2rej__.js +40 -0
- package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js +169 -0
- package/renderer/dist/assets/sizeCapture-INFHLROL-usyNyXyh.js +1 -0
- package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-CRzUBb0u.js +291 -0
- package/renderer/dist/assets/swimlanes-N4OXWK64-C1GJvMS0.js +1 -0
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +8 -0
- package/renderer/dist/assets/timeline-definition-YOQAKGHF-caeyPlcc.js +120 -0
- package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-BN_SV6PC.js +370 -0
- package/renderer/dist/assets/vennDiagram-BLWOH2XV-BLGuaUX-.js +34 -0
- package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-Da0cJeNc.js +78 -0
- package/renderer/dist/assets/xychartDiagram-TAQBALBS-cGfUffMk.js +7 -0
- package/renderer/dist/corner-tag.html +13 -0
- package/renderer/dist/emulator/audio-rules.mjs +74 -0
- package/renderer/dist/emulator/data/compression/README.md +11 -0
- package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
- package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
- package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
- package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
- package/renderer/dist/emulator/data/cores/README.md +22 -0
- package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/package.json +65 -0
- package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
- package/renderer/dist/emulator/data/emulator.css +1614 -0
- package/renderer/dist/emulator/data/loader.js +170 -0
- package/renderer/dist/emulator/data/localization/README.md +63 -0
- package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
- package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
- package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
- package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
- package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
- package/renderer/dist/emulator/data/localization/en-US.json +339 -0
- package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
- package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
- package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
- package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
- package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
- package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
- package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
- package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
- package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
- package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
- package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
- package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
- package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
- package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
- package/renderer/dist/emulator/data/src/GameManager.js +464 -0
- package/renderer/dist/emulator/data/src/compression.js +142 -0
- package/renderer/dist/emulator/data/src/emulator.js +6158 -0
- package/renderer/dist/emulator/data/src/gamepad.js +167 -0
- package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
- package/renderer/dist/emulator/data/src/shaders.js +236 -0
- package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
- package/renderer/dist/emulator/data/src/storage.js +125 -0
- package/renderer/dist/emulator/data/version.json +5 -0
- package/renderer/dist/emulator/host.js +297 -0
- package/renderer/dist/emulator/index.html +28 -0
- package/renderer/dist/emulator/provenance.json +6 -0
- package/renderer/dist/index.html +16 -3
- package/scripts/approval-server.sh +24 -5
- package/scripts/before-publish.mjs +12 -10
- package/shared/agent-import.mjs +2 -0
- package/shared/agents.d.mts +1 -0
- package/shared/bridge-map.mjs +18 -0
- package/shared/claude-commands.generated.mjs +6 -4
- package/shared/claude-models.generated.mjs +6 -4
- package/shared/corner-tag.d.mts +14 -0
- package/shared/corner-tag.mjs +141 -0
- package/shared/engines.mjs +20 -5
- package/shared/harness-definitions.mjs +103 -0
- package/shared/notify-rules.d.mts +25 -0
- package/shared/notify-rules.mjs +19 -0
- package/shared/provider-commands.mjs +10 -2
- package/shared/rank.d.mts +2 -0
- package/shared/rank.mjs +50 -0
- package/shared/relay-address.mjs +6 -0
- package/shared/relay-crypto.mjs +165 -0
- package/shared/runs-on.d.mts +0 -10
- package/shared/runs-on.mjs +25 -49
- package/shared/shortcuts.mjs +10 -4
- package/shared/spawn-trouble.mjs +57 -6
- package/shared/terminal-input.d.mts +1 -0
- package/shared/terminal-input.mjs +30 -0
- package/shared/terminal-keys.d.mts +1 -0
- package/shared/terminal-keys.mjs +16 -0
- package/shared/terminal-size.d.mts +4 -0
- package/shared/terminal-size.mjs +29 -0
- package/shared/usage.d.mts +3 -0
- package/shared/usage.mjs +7 -0
- package/shared/where-it-runs-from.d.mts +43 -0
- package/shared/where-it-runs-from.mjs +155 -0
- package/shared/work-items.mjs +25 -1
- package/shared/work-lines.mjs +1 -1
- package/shared/xterm-invalid-range.d.mts +2 -0
- package/shared/xterm-invalid-range.mjs +58 -0
- package/renderer/dist/assets/index-ChKsQkRw.js +0 -1169
- package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
package/main/task-terminals.mjs
CHANGED
|
@@ -9,6 +9,11 @@ const LIMIT=262144;
|
|
|
9
9
|
export function terminalEnv(source=process.env){
|
|
10
10
|
const env={...source,TERM:'xterm-256color',COLORTERM:'truecolor'};
|
|
11
11
|
for(const key of Object.keys(env)) if(/^(ANTHROPIC_|OPENAI_API_KEY$|CLAUDE_CODE_|CLAUDECODE$|CLAUDE_PID$|CLAUDE_EFFORT$|ELECTRON_|NODE_OPTIONS$)/.test(key)) delete env[key];
|
|
12
|
+
// NPM'S STAND-IN EDITOR IS NOBODY'S CHOICE. `npm start` gives everything it
|
|
13
|
+
// runs EDITOR=vi when neither EDITOR nor VISUAL is set, and zsh reads "vi"
|
|
14
|
+
// there as a request for vi keys: Option+Left deleted and Option+Delete
|
|
15
|
+
// moved. A login shell sets the person's own EDITOR from their startup files.
|
|
16
|
+
if(env.EDITOR==='vi'&&!env.VISUAL) delete env.EDITOR;
|
|
12
17
|
return env;
|
|
13
18
|
}
|
|
14
19
|
/*
|
|
@@ -68,8 +73,9 @@ export function disposeTerminalProcess(pty){
|
|
|
68
73
|
* it is the same call the toolbar makes to decide whether to draw Stop command,
|
|
69
74
|
* deliberately, so a shell she can see is busy can never be one this reaps.
|
|
70
75
|
*
|
|
71
|
-
* WHICH SHELLS NOBODY IS WATCHING: the panel asks for output
|
|
72
|
-
* it is on screen
|
|
76
|
+
* WHICH SHELLS NOBODY IS WATCHING: the panel asks for output at least once a
|
|
77
|
+
* second while it is on screen (each ask waits up to READ_WAIT_MS for some)
|
|
78
|
+
* and stops the instant it is hidden or she leaves the task
|
|
73
79
|
* (TaskTerminal.tsx). So `lastReadAt` is not a guess about attention, it is the
|
|
74
80
|
* last time the app was actually asked what this shell had to say.
|
|
75
81
|
*
|
|
@@ -77,6 +83,10 @@ export function disposeTerminalProcess(pty){
|
|
|
77
83
|
* worth nothing, and it goes. Everything else stays until she or the app quits.
|
|
78
84
|
*/
|
|
79
85
|
const IDLE_MS=600000;
|
|
86
|
+
// The longest a read waits for output before answering with none, so the panel
|
|
87
|
+
// still hears about a command that finished quietly and the sweep still sees
|
|
88
|
+
// it watching.
|
|
89
|
+
const READ_WAIT_MS=1000;
|
|
80
90
|
const SWEEP_MS=60000;
|
|
81
91
|
const MAX_TERMINALS=20;
|
|
82
92
|
export class TaskTerminals {
|
|
@@ -147,7 +157,7 @@ export class TaskTerminals {
|
|
|
147
157
|
const command=place?.command;
|
|
148
158
|
const pty=this.spawn(command?.file??this.shell,command?.args??['-l'],{cwd,env:{...this.env,...command?.env},cols:80,rows:24,name:'xterm-256color'});
|
|
149
159
|
let resolveExit;const exitPromise=new Promise(resolve=>{resolveExit=resolve;});
|
|
150
|
-
const session={token:randomUUID(),pty,cwd,buffer:'',offset:0,exitCode:null,exited:false,exitPromise,lastReadAt:this.now()};this.sessions.set(key,session);
|
|
160
|
+
const session={token:randomUUID(),pty,cwd,buffer:'',offset:0,exitCode:null,exited:false,exitPromise,lastReadAt:this.now(),waiters:new Set()};this.sessions.set(key,session);
|
|
151
161
|
/*
|
|
152
162
|
* AND IT SAYS WHAT HAPPENED TO THE LAST ONE. Without this line, reopening a
|
|
153
163
|
terminal the app tidied away looks exactly like the app losing her
|
|
@@ -156,17 +166,37 @@ export class TaskTerminals {
|
|
|
156
166
|
const why=this.reaped.get(key);
|
|
157
167
|
if(why!==undefined){this.reaped.delete(key);session.buffer=`\x1b[2m[${why} This is a new one.]\x1b[0m\r\n`;}
|
|
158
168
|
if(place?.notice)session.buffer+=`\x1b[2m[${place.notice}]\x1b[0m\r\n`;
|
|
159
|
-
pty.onData(data=>{session.buffer+=data;const trim=Math.max(0,session.buffer.length-LIMIT);session.buffer=session.buffer.slice(trim);session.offset+=trim;});
|
|
160
|
-
pty.onExit(({exitCode})=>{session.exited=true;session.exitCode=exitCode;resolveExit();});
|
|
169
|
+
pty.onData(data=>{session.buffer+=data;const trim=Math.max(0,session.buffer.length-LIMIT);session.buffer=session.buffer.slice(trim);session.offset+=trim;this.wake(session);});
|
|
170
|
+
pty.onExit(({exitCode})=>{session.exited=true;session.exitCode=exitCode;resolveExit();this.wake(session);});
|
|
161
171
|
return this.read(key,0);
|
|
162
172
|
}
|
|
163
173
|
get(key){const s=this.sessions.get(key);if(!s)throw Error('Terminal session ended. Open a new terminal to continue.');return s;}
|
|
164
174
|
// EVERY READ IS THE PANEL SAYING IT IS STILL THERE. This one line is the whole
|
|
165
175
|
// attention signal the sweep runs on; see the note on the class.
|
|
166
|
-
read(key,offset=0
|
|
176
|
+
read(key,offset=0,wait=0){
|
|
177
|
+
const s=this.get(key);if(!Number.isSafeInteger(offset)||offset<0)throw Error('Invalid terminal cursor.');s.lastReadAt=this.now();
|
|
178
|
+
const end=s.offset+s.buffer.length;
|
|
179
|
+
/*
|
|
180
|
+
* A READ MAY WAIT FOR THE SHELL'S NEXT OUTPUT, and comes back the moment
|
|
181
|
+
there is some. The panel used to ask every 150ms, so the echo of a key
|
|
182
|
+
sat in this buffer until the next ask: deleted characters stayed on her
|
|
183
|
+
screen after she deleted them (w-16e47d0836). `live` tells the panel this
|
|
184
|
+
main can wait, so it asks again at once instead of in 150ms; a main
|
|
185
|
+
older than its renderer (⌘R ahead of a restart) answers without it. */
|
|
186
|
+
if(wait>0&&!s.exited&&offset>=end)return new Promise((resolve,reject)=>{
|
|
187
|
+
const done=()=>{clearTimeout(timer);s.waiters.delete(done);try{if(this.sessions.get(key)!==s)throw Error('Terminal session ended. Open a new terminal to continue.');resolve({...this.read(key,offset),live:true});}catch(e){reject(e);}};
|
|
188
|
+
const timer=setTimeout(done,Math.min(wait,READ_WAIT_MS));
|
|
189
|
+
s.waiters.add(done);
|
|
190
|
+
});
|
|
191
|
+
const state={token:s.token,cwd:s.cwd,data:s.buffer.slice(Math.max(0,offset-s.offset)),offset:end,truncated:offset<s.offset,exited:s.exited,exitCode:s.exitCode,process:s.exited?'Exited':s.pty.process||'Shell'};
|
|
192
|
+
return wait>0?{...state,live:true}:state;
|
|
193
|
+
}
|
|
194
|
+
// Answers every waiting read. Deferred a turn so the pieces of one echo the
|
|
195
|
+
// pty hands over back to back go out as one answer.
|
|
196
|
+
wake(s){if(!s.waiters.size||s.waking)return;s.waking=true;setImmediate(()=>{s.waking=false;for(const done of [...s.waiters])done();});}
|
|
167
197
|
write(key,data){const s=this.get(key);if(s.exited)throw Error('This shell has exited. Start a new session.');if(typeof data!=='string'||data.length>65536)throw Error('Terminal input is too large. Paste at most 64 KB at a time.');s.pty.write(data);}
|
|
168
198
|
resize(key,cols,rows){if(!Number.isInteger(cols)||!Number.isInteger(rows)||cols<2||cols>500||rows<2||rows>300)throw Error('Invalid terminal size.');const s=this.get(key);if(!s.exited)s.pty.resize(cols,rows);}
|
|
169
|
-
close(key){const s=this.sessions.get(key);if(s){if(!s.exited)this.disposeProcess(s.pty);this.sessions.delete(key);}}
|
|
199
|
+
close(key){const s=this.sessions.get(key);if(s){if(!s.exited)this.disposeProcess(s.pty);this.sessions.delete(key);for(const done of [...s.waiters])done();}}
|
|
170
200
|
async shutdown(){
|
|
171
201
|
const exits=[...this.sessions.values()].map(s=>s.exitPromise);
|
|
172
202
|
this.dispose();
|
package/main/updater.mjs
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
import { createRequire } from 'node:module';
|
|
50
50
|
import { Name } from '../shared/product-name.mjs';
|
|
51
|
+
import { MOVE_SAY } from '../shared/where-it-runs-from.mjs';
|
|
51
52
|
|
|
52
53
|
const require = createRequire(import.meta.url);
|
|
53
54
|
|
|
@@ -68,6 +69,14 @@ export function describe(state) {
|
|
|
68
69
|
const { phase, currentVersion, newVersion, percent, error, checkedAt, readyAt, source, changes, behind } = state;
|
|
69
70
|
const installing = phase === 'installing';
|
|
70
71
|
return {
|
|
72
|
+
// WHETHER THIS COPY CAN UPDATE ITSELF FROM WHERE IT IS, and the one press
|
|
73
|
+
// that fixes it when it cannot (main/where-it-runs-from.mjs). It rides the
|
|
74
|
+
// updater's own state because the three surfaces that would have to ask
|
|
75
|
+
// separately — the Settings row, the sidebar card and ⌘K — all already
|
|
76
|
+
// read this one object, and a second channel for it is a second answer.
|
|
77
|
+
canMove: !!state.canMove,
|
|
78
|
+
place: state.canMove ? (state.place ?? null) : null,
|
|
79
|
+
moveError: state.moveError ?? null,
|
|
71
80
|
phase,
|
|
72
81
|
currentVersion,
|
|
73
82
|
newVersion: newVersion ?? null,
|
|
@@ -96,7 +105,17 @@ export function describe(state) {
|
|
|
96
105
|
};
|
|
97
106
|
}
|
|
98
107
|
|
|
99
|
-
|
|
108
|
+
/**
|
|
109
|
+
* `autoUpdater` is a SEAM FOR THE TESTS and nothing else. The real one is
|
|
110
|
+
* required below; passing one in is how the failure paths get tested without a
|
|
111
|
+
* packaged build, a signed release and a network, which is the only reason the
|
|
112
|
+
* download bug (see the swallow in `check`) went seven reports unnoticed.
|
|
113
|
+
*/
|
|
114
|
+
export function createUpdater({ app, log = console, onChanged = () => {}, autoUpdater: given = null, place = null }) {
|
|
115
|
+
// READ ONCE, at construction (main/where-it-runs-from.mjs says why the
|
|
116
|
+
// bundle cannot move under a running process). A build with no place passed
|
|
117
|
+
// in — the tests, and `main/serve.mjs` — simply never offers the move.
|
|
118
|
+
const where = place?.state?.() ?? { place: null, canUpdate: true, offerMove: false };
|
|
100
119
|
let state = {
|
|
101
120
|
phase: 'idle',
|
|
102
121
|
currentVersion: app.getVersion(),
|
|
@@ -105,6 +124,9 @@ export function createUpdater({ app, log = console, onChanged = () => {} }) {
|
|
|
105
124
|
error: null,
|
|
106
125
|
checkedAt: null,
|
|
107
126
|
readyAt: null,
|
|
127
|
+
canMove: !!where.offerMove,
|
|
128
|
+
place: where.place ?? null,
|
|
129
|
+
moveError: null,
|
|
108
130
|
};
|
|
109
131
|
let timer = null;
|
|
110
132
|
let stopped = false;
|
|
@@ -117,12 +139,14 @@ export function createUpdater({ app, log = console, onChanged = () => {} }) {
|
|
|
117
139
|
// Loaded lazily and inside a try, so a build that somehow shipped without the
|
|
118
140
|
// dependency degrades to "no updates" instead of failing to boot at all. The
|
|
119
141
|
// app not starting is a strictly worse outcome than the app not updating.
|
|
120
|
-
let autoUpdater =
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
142
|
+
let autoUpdater = given;
|
|
143
|
+
if (!autoUpdater) {
|
|
144
|
+
try {
|
|
145
|
+
({ autoUpdater } = require('electron-updater'));
|
|
146
|
+
} catch (e) {
|
|
147
|
+
set({ phase: 'unsupported', error: 'This build has no updater in it.' });
|
|
148
|
+
log.warn?.('[updater] electron-updater is not available:', e?.message ?? e);
|
|
149
|
+
}
|
|
126
150
|
}
|
|
127
151
|
|
|
128
152
|
if (autoUpdater) {
|
|
@@ -184,7 +208,23 @@ export function createUpdater({ app, log = console, onChanged = () => {} }) {
|
|
|
184
208
|
if (state.phase === 'ready') return describe(state);
|
|
185
209
|
if (state.phase === 'checking' || (state.phase === 'downloading' && !manual)) return describe(state);
|
|
186
210
|
try {
|
|
187
|
-
await autoUpdater.checkForUpdates();
|
|
211
|
+
const found = await autoUpdater.checkForUpdates();
|
|
212
|
+
// THE DOWNLOAD IS A SECOND PROMISE AND NOBODY WAS HOLDING IT. With
|
|
213
|
+
// `autoDownload` on, electron-updater starts the download inside the
|
|
214
|
+
// check and hands it back here as `downloadPromise`; its own
|
|
215
|
+
// `downloadUpdate` dispatches the `error` event and then RE-THROWS. The
|
|
216
|
+
// await above resolves on the RESULT, not on the download, so a failed
|
|
217
|
+
// download had nowhere to land but `unhandledRejection` — and
|
|
218
|
+
// `main/crash-report.mjs` files one of those as a crash.
|
|
219
|
+
//
|
|
220
|
+
// That is 7 of the 14 crash reports of launch week (PostHog, 2026-10-08)
|
|
221
|
+
// and not one of them was a crash: five are Squirrel's read-only volume
|
|
222
|
+
// error from an app run out of the disk image, two are a dropped
|
|
223
|
+
// connection, and in every one of them the app carried on running. The
|
|
224
|
+
// failure is ALREADY on screen by the time we get here, because the
|
|
225
|
+
// `error` handler above set it, so there is nothing left to do with this
|
|
226
|
+
// rejection but stop it being mistaken for a crash.
|
|
227
|
+
found?.downloadPromise?.catch?.(() => {});
|
|
188
228
|
} catch (e) {
|
|
189
229
|
set({ phase: 'error', error: shortError(e), checkedAt: Date.now() });
|
|
190
230
|
}
|
|
@@ -201,6 +241,18 @@ export function createUpdater({ app, log = console, onChanged = () => {} }) {
|
|
|
201
241
|
return true;
|
|
202
242
|
}
|
|
203
243
|
|
|
244
|
+
// THE OTHER BUTTON, for a copy that cannot update itself from where it is.
|
|
245
|
+
// Electron moves the bundle and relaunches, so a `true` here is the last
|
|
246
|
+
// thing this process does. A failure goes into the state like every other
|
|
247
|
+
// failure, so the row and the sidebar card both say it and both offer
|
|
248
|
+
// another press.
|
|
249
|
+
function move() {
|
|
250
|
+
if (!state.canMove) return { moved: false, error: null };
|
|
251
|
+
const got = place?.move?.() ?? { moved: false, error: null };
|
|
252
|
+
if (!got.moved) set({ moveError: got.error ?? null });
|
|
253
|
+
return got;
|
|
254
|
+
}
|
|
255
|
+
|
|
204
256
|
function start() {
|
|
205
257
|
if (stopped || timer) return;
|
|
206
258
|
const tick = () => { void check(); };
|
|
@@ -215,7 +267,7 @@ export function createUpdater({ app, log = console, onChanged = () => {} }) {
|
|
|
215
267
|
timer = null;
|
|
216
268
|
}
|
|
217
269
|
|
|
218
|
-
return { start, stop, check, install, state: () => describe(state) };
|
|
270
|
+
return { start, stop, check, install, move, state: () => describe(state) };
|
|
219
271
|
}
|
|
220
272
|
|
|
221
273
|
// electron-updater's errors arrive as Error objects whose `message` is
|
|
@@ -234,6 +286,16 @@ export function shortError(e) {
|
|
|
234
286
|
// releases/download/v0.1.0/latest-mac.yml): HttpErr…" to her. The sentence
|
|
235
287
|
// for exactly that failure was already on the line below it.
|
|
236
288
|
const named = `${e?.code ?? ''} ${raw}`;
|
|
289
|
+
// SQUIRREL'S OWN PARAGRAPH, WHICH IS 240 CHARACTERS AND HAS A URL IN IT.
|
|
290
|
+
// "Cannot update while running on a read-only volume. The application is on
|
|
291
|
+
// a read-only volume. Please move the application and try again. If you'll
|
|
292
|
+
// need to move the application out of the Downloads directory. See
|
|
293
|
+
// https://github.com/Squirrel/Squirrel.Mac …" — the whole of what 23 of the
|
|
294
|
+
// 26 main-rejection reports carried (PostHog, 2026-10-12), and the Settings
|
|
295
|
+
// row showed the first 160 characters of it. The app knows where it is
|
|
296
|
+
// running from (shared/where-it-runs-from.mjs), so this is one sentence and
|
|
297
|
+
// the row beside it is the press that fixes it.
|
|
298
|
+
if (/read-only volume/i.test(raw)) return MOVE_SAY.cannotUpdateHere;
|
|
237
299
|
if (/ERR_UPDATER_ZIP_FILE_NOT_FOUND/.test(named)) return 'That release has no app in it to install.';
|
|
238
300
|
if (/ERR_UPDATER_CHANNEL_FILE_NOT_FOUND/.test(named)) return 'That release is missing its update file.';
|
|
239
301
|
if (/ERR_UPDATER_NO_PUBLISHED_VERSIONS|LATEST_VERSION_NOT_FOUND/.test(named)) return 'No published version to update to yet.';
|