agentbox-app 0.1.11 → 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/README.md +24 -16
- package/bin/agentbox.mjs +18 -9
- package/briefs/message-rules.md +15 -4
- package/main/account-tooling.mjs +2 -1
- 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 -20
- package/main/analytics.mjs +30 -0
- package/main/approvals.mjs +14 -5
- package/main/claude-bin.mjs +45 -310
- 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 +14 -2
- package/main/claude-usage.mjs +82 -25
- package/main/codex-bin.mjs +51 -248
- package/main/codex-launch-env.mjs +31 -0
- 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 +40 -13
- package/main/corner-tag.mjs +489 -0
- package/main/crash-report.mjs +26 -3
- package/main/demo.mjs +17 -14
- package/main/engine-bin.mjs +595 -0
- package/main/engine-setup.mjs +27 -27
- package/main/feedback.mjs +11 -6
- package/main/fresh-user.mjs +1 -1
- package/main/git-change.mjs +8 -2
- 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/instruction-settings.mjs +47 -11
- package/main/ipc.mjs +175 -22
- package/main/local-app-frames.mjs +82 -0
- package/main/machine.mjs +1 -1
- package/main/main.mjs +120 -5
- 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 +48 -5
- package/main/opencode-server.mjs +271 -0
- package/main/opencode.mjs +287 -0
- package/main/phone-link.mjs +551 -0
- package/main/refresh-agent-models.mjs +2 -1
- package/main/relay-address.mjs +28 -0
- package/main/relay-client.mjs +246 -0
- package/main/row-label.mjs +8 -7
- package/main/runs-on.mjs +90 -0
- package/main/serve.mjs +124 -10
- package/main/settings.mjs +171 -11
- package/main/shell-path.mjs +86 -0
- package/main/ship-queue.mjs +25 -7
- 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/home.mjs +37 -5
- package/main/store/project.mjs +15 -19
- package/main/store.mjs +10 -4
- package/main/supervisor.mjs +1030 -447
- package/main/task-commands.mjs +13 -17
- package/main/task-folders-offthread.mjs +6 -1
- package/main/task-folders.mjs +491 -82
- package/main/task-terminals.mjs +49 -8
- package/main/team/index.mjs +2 -2
- package/main/team/projects.mjs +1 -1
- package/main/updater.mjs +71 -9
- package/main/worktree-include.mjs +494 -0
- package/mcp/core/page.mjs +1 -1
- package/mcp/core/work.mjs +34 -3
- package/mcp/tools.mjs +2 -1
- 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/after-install.mjs +14 -0
- package/scripts/approval-server.sh +31 -7
- package/scripts/before-publish.mjs +12 -10
- package/shared/agent-import.mjs +3 -1
- package/shared/agents.d.mts +1 -0
- package/shared/bridge-map.mjs +19 -0
- package/shared/claude-commands.generated.mjs +8 -6
- package/shared/claude-models.generated.mjs +10 -8
- package/shared/codex-modes.mjs +1 -1
- package/shared/corner-tag.d.mts +14 -0
- package/shared/corner-tag.mjs +141 -0
- package/shared/demo-world.mjs +1 -1
- package/shared/engines.mjs +20 -5
- package/shared/fresh-user-home.mjs +12 -1
- package/shared/harness-definitions.mjs +103 -0
- package/shared/instruction-defaults.json +1 -1
- package/shared/notify-rules.d.mts +25 -0
- package/shared/notify-rules.mjs +19 -0
- package/shared/project-folder-check.mjs +3 -3
- 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 +10 -0
- package/shared/runs-on.mjs +55 -0
- package/shared/shortcuts.mjs +11 -5
- package/shared/spawn-trouble.mjs +58 -7
- 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 +28 -2
- 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-B-2Az952.css +0 -32
- package/renderer/dist/assets/index-D6qncgai.js +0 -1168
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
|
/*
|
|
@@ -32,6 +37,17 @@ export function terminalPlace({store,supervisor,product,id,agent}){
|
|
|
32
37
|
return {cwd,notice};
|
|
33
38
|
}
|
|
34
39
|
export function terminalFolder(args){return terminalPlace(args).cwd;}
|
|
40
|
+
// The shell a person actually has. `$SHELL` when that file exists, then
|
|
41
|
+
// zsh, bash, and sh. A machine with no zsh used to spawn `/bin/zsh` and
|
|
42
|
+
// the terminal never came up.
|
|
43
|
+
export function loginShell(env=process.env,exists=fs.existsSync){
|
|
44
|
+
const preferred=env.SHELL;
|
|
45
|
+
if(preferred&&exists(preferred))return preferred;
|
|
46
|
+
for(const candidate of ['/bin/zsh','/bin/bash','/bin/sh']){
|
|
47
|
+
if(exists(candidate))return candidate;
|
|
48
|
+
}
|
|
49
|
+
return preferred||'/bin/sh';
|
|
50
|
+
}
|
|
35
51
|
// Enumerate only descendants of our own shell, including background process
|
|
36
52
|
// groups. Kill children before shell; closing a pane never calls this.
|
|
37
53
|
export function disposeTerminalProcess(pty){
|
|
@@ -57,8 +73,9 @@ export function disposeTerminalProcess(pty){
|
|
|
57
73
|
* it is the same call the toolbar makes to decide whether to draw Stop command,
|
|
58
74
|
* deliberately, so a shell she can see is busy can never be one this reaps.
|
|
59
75
|
*
|
|
60
|
-
* WHICH SHELLS NOBODY IS WATCHING: the panel asks for output
|
|
61
|
-
* 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
|
|
62
79
|
* (TaskTerminal.tsx). So `lastReadAt` is not a guess about attention, it is the
|
|
63
80
|
* last time the app was actually asked what this shell had to say.
|
|
64
81
|
*
|
|
@@ -66,10 +83,14 @@ export function disposeTerminalProcess(pty){
|
|
|
66
83
|
* worth nothing, and it goes. Everything else stays until she or the app quits.
|
|
67
84
|
*/
|
|
68
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;
|
|
69
90
|
const SWEEP_MS=60000;
|
|
70
91
|
const MAX_TERMINALS=20;
|
|
71
92
|
export class TaskTerminals {
|
|
72
|
-
constructor({spawn,resolve,disposeProcess=disposeTerminalProcess,env=terminalEnv(),shell=
|
|
93
|
+
constructor({spawn,resolve,disposeProcess=disposeTerminalProcess,env=terminalEnv(),shell=loginShell(),idleMs=IDLE_MS,sweepMs=SWEEP_MS,now=()=>Date.now()}={}){
|
|
73
94
|
this.spawn=spawn??((...args)=>require('node-pty').spawn(...args));this.resolve=resolve;this.disposeProcess=disposeProcess;this.env=env;this.shell=shell;this.sessions=new Map();
|
|
74
95
|
this.idleMs=idleMs;this.now=now;
|
|
75
96
|
// Keys whose shell this class ended on its own, and WHY. The next open on
|
|
@@ -136,7 +157,7 @@ export class TaskTerminals {
|
|
|
136
157
|
const command=place?.command;
|
|
137
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'});
|
|
138
159
|
let resolveExit;const exitPromise=new Promise(resolve=>{resolveExit=resolve;});
|
|
139
|
-
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);
|
|
140
161
|
/*
|
|
141
162
|
* AND IT SAYS WHAT HAPPENED TO THE LAST ONE. Without this line, reopening a
|
|
142
163
|
terminal the app tidied away looks exactly like the app losing her
|
|
@@ -145,17 +166,37 @@ export class TaskTerminals {
|
|
|
145
166
|
const why=this.reaped.get(key);
|
|
146
167
|
if(why!==undefined){this.reaped.delete(key);session.buffer=`\x1b[2m[${why} This is a new one.]\x1b[0m\r\n`;}
|
|
147
168
|
if(place?.notice)session.buffer+=`\x1b[2m[${place.notice}]\x1b[0m\r\n`;
|
|
148
|
-
pty.onData(data=>{session.buffer+=data;const trim=Math.max(0,session.buffer.length-LIMIT);session.buffer=session.buffer.slice(trim);session.offset+=trim;});
|
|
149
|
-
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);});
|
|
150
171
|
return this.read(key,0);
|
|
151
172
|
}
|
|
152
173
|
get(key){const s=this.sessions.get(key);if(!s)throw Error('Terminal session ended. Open a new terminal to continue.');return s;}
|
|
153
174
|
// EVERY READ IS THE PANEL SAYING IT IS STILL THERE. This one line is the whole
|
|
154
175
|
// attention signal the sweep runs on; see the note on the class.
|
|
155
|
-
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();});}
|
|
156
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);}
|
|
157
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);}
|
|
158
|
-
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();}}
|
|
159
200
|
async shutdown(){
|
|
160
201
|
const exits=[...this.sessions.values()].map(s=>s.exitPromise);
|
|
161
202
|
this.dispose();
|
package/main/team/index.mjs
CHANGED
|
@@ -133,7 +133,7 @@ export function createTeamService({
|
|
|
133
133
|
: [[], [], state.me];
|
|
134
134
|
set({ lastSyncAt: Date.now(), error: null, cards, people, me: me ?? state.me });
|
|
135
135
|
if (report.joined.length || report.pulled) log(`team: joined ${report.joined.length}, pulled ${report.pulled}, pushed ${report.pushed}`);
|
|
136
|
-
if (report.notJoined) log(`team: left ${report.notJoined} shared project(s) unjoined, this
|
|
136
|
+
if (report.notJoined) log(`team: left ${report.notJoined} shared project(s) unjoined, this computer already holds the most it takes on by itself`);
|
|
137
137
|
return report;
|
|
138
138
|
} catch (err) {
|
|
139
139
|
set({ error: String(err?.message ?? err) });
|
|
@@ -320,7 +320,7 @@ export function createTeamService({
|
|
|
320
320
|
// already in a team does not move: that is a choice nobody offers here.
|
|
321
321
|
async acceptInvite(teamId) {
|
|
322
322
|
if (!backend || !state.me) throw new Error('sign in first');
|
|
323
|
-
if (state.team) throw new Error(`this
|
|
323
|
+
if (state.team) throw new Error(`this computer is already in ${state.team.name}`);
|
|
324
324
|
if (!state.invites.some((i) => i.teamId === teamId)) throw new Error('that invite is no longer waiting');
|
|
325
325
|
await backend.acceptInvite(teamId);
|
|
326
326
|
kept().setTeam(state.me.id, teamId);
|
package/main/team/projects.mjs
CHANGED
|
@@ -100,7 +100,7 @@ export function markShared(dir, { teamId, visibility = 'team', people = [], shar
|
|
|
100
100
|
if (!project) throw new Error(`no project.json in ${dir}`);
|
|
101
101
|
// Sharing it would put every line of it in the cloud, which is the one thing
|
|
102
102
|
// this project exists to make hard. A thread in it is shared one at a time.
|
|
103
|
-
if (project[PERSONAL_FLAG] === true) throw new Error(`${PERSONAL_NAME} stays on this
|
|
103
|
+
if (project[PERSONAL_FLAG] === true) throw new Error(`${PERSONAL_NAME} stays on this computer. Share a thread in it instead.`);
|
|
104
104
|
const projectId = project.team?.projectId || crypto.randomUUID();
|
|
105
105
|
project.team = {
|
|
106
106
|
projectId, teamId, visibility: visibility === 'people' ? 'people' : 'team', people: [...new Set(people)],
|
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.';
|