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.
- package/README.md +24 -9
- package/bin/agentbox.mjs +23 -1
- package/briefs/message-rules.md +36 -8
- package/briefs/worker.md +46 -23
- package/main/account-discovery.mjs +122 -30
- package/main/account-tooling.mjs +29 -1
- package/main/account-usage.mjs +46 -0
- package/main/acp.mjs +296 -0
- package/main/agent-homes.mjs +158 -0
- package/main/agent-sessions.mjs +311 -260
- package/main/agent-updates.mjs +9 -22
- package/main/agents.mjs +179 -466
- package/main/analytics.mjs +42 -2
- package/main/answer-modes.mjs +103 -37
- package/main/approval-prompt-core.mjs +197 -0
- package/main/approval-prompt-server.mjs +4 -138
- package/main/approvals.mjs +101 -297
- package/main/artifact-path.mjs +47 -18
- package/main/claude-bin.mjs +45 -311
- package/main/claude-input.mjs +57 -7
- package/main/claude-launch-env.mjs +54 -0
- package/main/claude-models.mjs +39 -7
- package/main/claude-plan.mjs +64 -16
- package/main/claude-session-open.mjs +28 -0
- package/main/claude-usage-process.mjs +40 -0
- package/main/claude-usage.mjs +94 -59
- package/main/cloud/account.mjs +179 -0
- package/main/cloud/aws.mjs +127 -0
- package/main/cloud/browser-return.mjs +58 -0
- package/main/cloud/clients.mjs +15 -0
- package/main/cloud/codex-cloud.mjs +53 -0
- package/main/cloud/config.mjs +107 -0
- package/main/cloud/docs.mjs +219 -0
- package/main/cloud/exec.mjs +27 -0
- package/main/cloud/host.mjs +165 -0
- package/main/cloud/kit.mjs +80 -0
- package/main/cloud/logins.mjs +543 -0
- package/main/cloud/managed.mjs +115 -0
- package/main/cloud/marker.mjs +104 -0
- package/main/cloud/org.mjs +53 -0
- package/main/cloud/reuse.mjs +208 -0
- package/main/cloud/run-cli.mjs +177 -0
- package/main/cloud/run.mjs +1934 -0
- package/main/cloud/usage.mjs +76 -0
- package/main/cloud-permission.mjs +51 -0
- package/main/codex-account.mjs +78 -0
- package/main/codex-app-server.mjs +276 -409
- package/main/codex-approvals.mjs +148 -433
- package/main/codex-bin.mjs +51 -249
- package/main/codex-models.mjs +20 -2
- package/main/codex-session.mjs +246 -506
- package/main/codex-threads-worker.mjs +12 -0
- package/main/codex-threads.mjs +112 -11
- package/main/codex-usage-file.mjs +5 -3
- package/main/codex-usage.mjs +56 -7
- package/main/codex-watch.mjs +83 -5
- package/main/codex.mjs +95 -363
- package/main/config.mjs +37 -5
- package/main/corner-tag.mjs +810 -0
- package/main/crash-report.mjs +26 -3
- package/main/dev-window.mjs +1 -1
- package/main/engine-bin.mjs +545 -0
- package/main/engine-setup.mjs +25 -24
- package/main/feedback.mjs +14 -31
- package/main/folder-pictures.mjs +2 -2
- package/main/freeze-recorder-thread.mjs +86 -0
- package/main/freeze-recorder.mjs +146 -0
- package/main/fresh-user.mjs +20 -9
- package/main/git-change-offthread.mjs +17 -3
- package/main/git-change-thread.mjs +5 -2
- package/main/harnesses/claude-stream.mjs +208 -0
- package/main/harnesses/claude.mjs +105 -0
- package/main/harnesses/codex.mjs +72 -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 +96 -0
- package/main/ipc.mjs +520 -135
- package/main/leftovers.mjs +117 -25
- package/main/liquid-glass.mjs +26 -0
- package/main/live-replies.mjs +10 -4
- package/main/local-app-frames.mjs +82 -0
- package/main/main-went-red.mjs +330 -0
- package/main/main.mjs +248 -37
- package/main/memory-gate-history.mjs +85 -20
- package/main/memory-gate-server.mjs +129 -11
- package/main/memory-gate.mjs +62 -8
- package/main/memory-log.mjs +124 -0
- package/main/menu.mjs +4 -2
- package/main/message-priority.mjs +2 -2
- package/main/mic.mjs +38 -0
- package/main/native/liquid-glass/build/liquid-glass.node +0 -0
- package/main/new-chats-watch.mjs +110 -0
- package/main/new-user-window.mjs +25 -0
- package/main/notify.mjs +64 -5
- package/main/opencode-server.mjs +271 -0
- package/main/opencode.mjs +287 -0
- package/main/phone-alerts.mjs +117 -0
- package/main/phone-inbox.mjs +625 -0
- package/main/phone-link.mjs +571 -0
- package/main/phone-uploads.mjs +476 -0
- package/main/picture-shape.mjs +114 -0
- package/main/quit-sequence.mjs +143 -0
- package/main/refresh-agent-models.mjs +15 -2
- package/main/relay-address.mjs +28 -0
- package/main/relay-client.mjs +314 -0
- package/main/repeats.mjs +10 -4
- package/main/row-label.mjs +18 -47
- package/main/run-change.mjs +13 -0
- package/main/run-ledger.mjs +135 -0
- package/main/runs-on.mjs +32 -35
- package/main/save-attachments.mjs +41 -0
- package/main/serve.mjs +155 -9
- package/main/settings.mjs +364 -50
- package/main/shell-path.mjs +234 -0
- package/main/ship-conflict-brief.mjs +119 -0
- package/main/ship-queue.mjs +134 -20
- package/main/sign-in-files.mjs +3 -7
- package/main/source-updater.mjs +45 -11
- package/main/stage-attachment.mjs +68 -0
- package/main/stall-watch.mjs +50 -0
- package/main/start-hold.mjs +73 -0
- package/main/store/home.mjs +87 -232
- package/main/store/paths.mjs +11 -0
- package/main/store/teammates-files.mjs +99 -0
- package/main/store/work-items.mjs +231 -13
- package/main/store.mjs +227 -53
- package/main/supervisor.mjs +3865 -699
- package/main/task-commands.mjs +13 -17
- package/main/task-folders-offthread.mjs +6 -1
- package/main/task-folders.mjs +897 -105
- package/main/task-terminals.mjs +147 -41
- package/main/team/index.mjs +114 -10
- package/main/team/memory-cloud.mjs +183 -1
- package/main/team/room-hours.mjs +48 -0
- package/main/team/room-setup.mjs +31 -0
- package/main/team/rooms-memory.mjs +454 -0
- package/main/team/rooms-shape.mjs +42 -0
- package/main/team/rooms-supabase.mjs +324 -0
- package/main/team/rooms.mjs +575 -0
- package/main/team/session.mjs +96 -26
- package/main/team/supabase-backend.mjs +180 -0
- package/main/team/sync.mjs +109 -1
- package/main/teammates.mjs +580 -0
- package/main/terminal-host.mjs +143 -0
- package/main/terminal-hosts.mjs +261 -0
- package/main/transcript-search.mjs +80 -0
- package/main/updater.mjs +71 -9
- package/main/webview-guard.mjs +45 -0
- package/main/worktree-include.mjs +365 -0
- package/mcp/core/documents.mjs +3 -1
- package/mcp/core/repeats.mjs +88 -0
- package/mcp/core/teammates.mjs +222 -0
- package/mcp/core/work.mjs +76 -4
- package/mcp/stdio.mjs +24 -11
- package/mcp/tools.mjs +116 -8
- package/package.json +30 -5
- package/renderer/dist/assets/abnfDiagram-YUKMZFIV-CoWB6YUK.js +1 -0
- package/renderer/dist/assets/arc-Ch0NrCul.js +1 -0
- package/renderer/dist/assets/architectureDiagram-47P4ROYG-CXz9M5tQ.js +36 -0
- package/renderer/dist/assets/blockDiagram-E7TT5TSB-DchGUuHH.js +140 -0
- package/renderer/dist/assets/c4Diagram-CRA5TL53-Cip-zNyr.js +38 -0
- package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
- package/renderer/dist/assets/chunk-2Q5K7J3B-BoXid_2r.js +1 -0
- package/renderer/dist/assets/chunk-5VM5RSS4-Cpez1rRz.js +15 -0
- package/renderer/dist/assets/chunk-7TKQ45FW-DKA84YkE.js +1 -0
- package/renderer/dist/assets/chunk-AW2ZBBNX-Dg-SumPG.js +88 -0
- package/renderer/dist/assets/chunk-FVRAUYC3-06TCvPm1.js +2 -0
- package/renderer/dist/assets/chunk-HTAEGDNF-CMEBy1Td.js +1 -0
- package/renderer/dist/assets/chunk-JWPE2WC7-BJ2ObmV-.js +1 -0
- package/renderer/dist/assets/chunk-KQW6MTUR-CGWOYXUE.js +1 -0
- package/renderer/dist/assets/chunk-XXDRQBXY-CiZp07XC.js +1 -0
- package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-DJNIX719.js +217 -0
- package/renderer/dist/assets/cornerTag-CPgvu-q3.js +1 -0
- package/renderer/dist/assets/cornerTag-oMfEjzCO.css +1 -0
- package/renderer/dist/assets/cose-bilkent-JH36ORCC-CbkyZOUH.js +1 -0
- package/renderer/dist/assets/cynefin-EF2NZ3EQ-78iOroZO.js +141 -0
- package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-GcCopMs7.js +62 -0
- package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
- package/renderer/dist/assets/dagre-W4DXFKR2-DHLlXLkz.js +4 -0
- package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/renderer/dist/assets/diagram-2UJZ2QOL-DuZSUHcH.js +200 -0
- package/renderer/dist/assets/diagram-OVF4WLC6-Ckxu3cqH.js +24 -0
- package/renderer/dist/assets/diagram-PFPMY2P6-CsTw9Ycn.js +30 -0
- package/renderer/dist/assets/diagram-UMYRVEAY-CTXd8qVg.js +24 -0
- package/renderer/dist/assets/diagram-YEKJPTXX-CJs1T_9m.js +41 -0
- package/renderer/dist/assets/diagram-ZIFT7M5P-CvjI23mf.js +3 -0
- package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-d_IeAzOJ.js +1 -0
- package/renderer/dist/assets/elk-IJKZMXRS-zo9KFP3N.js +27 -0
- package/renderer/dist/assets/erDiagram-O2IAPWRE-DqfYf5Dt.js +99 -0
- package/renderer/dist/assets/flowDiagram-OXPTDLAJ-DAWsMLmo.js +213 -0
- package/renderer/dist/assets/ganttDiagram-R7TSEDQI-4ml_bhwR.js +292 -0
- package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-B_WcqSId.js +106 -0
- package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
- package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-CI-jYqzc.js +2 -0
- package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
- package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-B56VWRlV.js +70 -0
- package/renderer/dist/assets/journeyDiagram-COXZFDF6-D1-p09Lr.js +139 -0
- package/renderer/dist/assets/kanban-definition-P3RFRI5V-Cv3w7JoN.js +89 -0
- package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
- package/renderer/dist/assets/linear-C35V2Spi.js +1 -0
- package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
- package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
- package/renderer/dist/assets/mermaid.core-BzGT5Vo9.js +314 -0
- package/renderer/dist/assets/mindmap-definition-LZFPQGKD-BZUfuymC.js +96 -0
- package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/renderer/dist/assets/pegDiagram-BGZESJAR-DFh9XNGe.js +1 -0
- package/renderer/dist/assets/pieDiagram-CAPJLFHJ-C65tPVsy.js +39 -0
- package/renderer/dist/assets/product-name-CSNW6d7s.js +40 -0
- package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-Cushj_nk.js +7 -0
- package/renderer/dist/assets/railroadDiagram-SM67HX2B-DZGebGx5.js +1 -0
- package/renderer/dist/assets/requirementDiagram-X7JNWC4A-DpAB0MNo.js +84 -0
- package/renderer/dist/assets/sankeyDiagram-UM26HJRW-D-29rHra.js +40 -0
- package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-FfOQAZol.js +169 -0
- package/renderer/dist/assets/sizeCapture-INFHLROL-C_n6fDBY.js +1 -0
- package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-BZjenzJG.js +291 -0
- package/renderer/dist/assets/swimlanes-N4OXWK64-DxaMU9vX.js +1 -0
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
- package/renderer/dist/assets/timeline-definition-YOQAKGHF-DTg5HJKz.js +120 -0
- package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-Bc3MZDsl.js +370 -0
- package/renderer/dist/assets/vennDiagram-BLWOH2XV-GIVGk9Id.js +34 -0
- package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-NmJnbQOg.js +78 -0
- package/renderer/dist/assets/xychartDiagram-TAQBALBS-p-kfGfnW.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 +30 -8
- package/scripts/approval-server.sh +24 -5
- package/scripts/before-publish.mjs +12 -10
- package/scripts/cloud-shim.pl +57 -0
- package/shared/account-names.d.mts +6 -0
- package/shared/account-names.mjs +15 -0
- package/shared/agent-hours.mjs +136 -0
- package/shared/agent-import.mjs +62 -34
- package/shared/agents.d.mts +1 -0
- package/shared/agents.mjs +126 -522
- package/shared/analytics-events.mjs +12 -0
- package/shared/approval-card.mjs +36 -0
- package/shared/bridge-map.mjs +45 -1
- package/shared/claude-code-shape.mjs +6 -0
- package/shared/claude-commands.generated.mjs +6 -4
- package/shared/claude-models.generated.mjs +6 -4
- package/shared/claude-usage.d.mts +1 -0
- package/shared/claude-usage.mjs +42 -1
- package/shared/cloud-hand-back.d.mts +7 -0
- package/shared/cloud-hand-back.mjs +20 -0
- package/shared/cloud-kit.d.mts +219 -0
- package/shared/cloud-kit.mjs +341 -0
- package/shared/cloud-login-name.mjs +4 -0
- package/shared/cloud-start.mjs +17 -0
- package/shared/cloud-versions.mjs +23 -0
- package/shared/codex-import.mjs +2 -2
- package/shared/codex-modes.d.mts +22 -9
- package/shared/codex-modes.mjs +67 -11
- package/shared/codex-usage.mjs +108 -2
- package/shared/corner-tag.d.mts +36 -0
- package/shared/corner-tag.mjs +396 -0
- package/shared/dead-run-trace.mjs +19 -2
- package/shared/engines.mjs +76 -300
- package/shared/feedback.d.mts +7 -0
- package/shared/feedback.mjs +25 -0
- package/shared/first-run-practice.mjs +17 -24
- package/shared/harness-definitions.mjs +111 -0
- package/shared/instruction-defaults.json +2 -2
- package/shared/key-caps.d.mts +14 -0
- package/shared/key-caps.mjs +68 -0
- package/shared/message-edits.d.mts +7 -0
- package/shared/message-edits.mjs +35 -0
- package/shared/move-thread.d.mts +24 -0
- package/shared/move-thread.mjs +112 -0
- package/shared/named-threads.mjs +72 -0
- package/shared/not-shipped.d.mts +22 -0
- package/shared/not-shipped.mjs +89 -0
- package/shared/notify-rules.d.mts +31 -0
- package/shared/notify-rules.mjs +55 -8
- package/shared/phone-files.mjs +28 -0
- package/shared/phone-settings.mjs +118 -0
- package/shared/phone-snapshot.d.mts +16 -0
- package/shared/phone-snapshot.mjs +348 -0
- package/shared/picture-formats.d.mts +1 -0
- package/shared/picture-formats.mjs +13 -0
- package/shared/provider-commands.mjs +10 -2
- package/shared/rank.d.mts +4 -1
- package/shared/rank.mjs +82 -19
- package/shared/relay-address.mjs +6 -0
- package/shared/relay-crypto.mjs +165 -0
- package/shared/repeats.d.mts +8 -0
- package/shared/repeats.mjs +33 -2
- package/shared/room-cards.mjs +167 -0
- package/shared/room-links.d.mts +4 -0
- package/shared/room-links.mjs +21 -0
- package/shared/runs-on.d.mts +3 -13
- package/shared/runs-on.mjs +31 -52
- package/shared/shipped-note.d.mts +4 -0
- package/shared/shipped-note.mjs +6 -0
- package/shared/shortcuts.mjs +24 -10
- package/shared/spawn-trouble.d.mts +4 -0
- package/shared/spawn-trouble.mjs +484 -30
- package/shared/team-rules.mjs +9 -2
- package/shared/teammates.d.mts +19 -0
- package/shared/teammates.mjs +756 -0
- package/shared/terminal-focus.d.mts +1 -0
- package/shared/terminal-focus.mjs +13 -0
- 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/thread-cards.mjs +4 -1
- package/shared/usage.d.mts +12 -0
- package/shared/usage.mjs +56 -3
- package/shared/websites.d.mts +15 -0
- package/shared/websites.mjs +61 -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 +150 -29
- package/shared/work-lines.mjs +93 -245
- 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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function terminalKeys(options?: { startOpen?: boolean; wasOpen?: boolean }): { takesKeys(revision?: number): boolean; hidden(): void };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Whether a terminal takes the keyboard when TaskTerminal builds it. Opening
|
|
2
|
+
// it, showing it again, Retry and New session do. Coming back to a thread that
|
|
3
|
+
// left its terminal open does not: xterm's input is a textarea, and the app's
|
|
4
|
+
// shortcuts stand aside for a textarea. `startOpen` is a page that is only a
|
|
5
|
+
// terminal, which always takes it.
|
|
6
|
+
// Pinned by tests/coming-back-to-an-open-terminal-leaves-the-keyboard-alone.test.mjs.
|
|
7
|
+
export function terminalKeys({ startOpen = false, wasOpen = false } = {}) {
|
|
8
|
+
let cameBack = wasOpen === true && !startOpen;
|
|
9
|
+
return {
|
|
10
|
+
takesKeys: (revision = 0) => !cameBack || revision > 0,
|
|
11
|
+
hidden: () => { cameBack = false; },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function inputQueue(send: (data: string) => Promise<unknown>, options?: { chunk?: number; onError?: (e: unknown) => void }): { push(data: string): void };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// KEYS TYPED WHILE ONE WRITE IS ON ITS WAY GO OUT TOGETHER, as the next write.
|
|
2
|
+
//
|
|
3
|
+
// The panel used to send every key as its own round trip to main, each one
|
|
4
|
+
// waiting for the last, so holding Delete built a line of trips that drained
|
|
5
|
+
// after she let go. Now the first key goes at once, and whatever she types
|
|
6
|
+
// while it travels is one write behind it, in order. A paste still goes in
|
|
7
|
+
// pieces main accepts (64 KB is its limit; `chunk` stays well under it).
|
|
8
|
+
//
|
|
9
|
+
// A write that fails (the shell exited) drops what was waiting behind it: those
|
|
10
|
+
// keys were for a shell that is gone, and the failure is reported once.
|
|
11
|
+
export function inputQueue(send, { chunk = 16384, onError } = {}) {
|
|
12
|
+
let queued = '', sending = false;
|
|
13
|
+
async function drain() {
|
|
14
|
+
if (sending) return;
|
|
15
|
+
sending = true;
|
|
16
|
+
try {
|
|
17
|
+
while (queued) {
|
|
18
|
+
const data = queued.slice(0, chunk);
|
|
19
|
+
queued = queued.slice(data.length);
|
|
20
|
+
await send(data);
|
|
21
|
+
}
|
|
22
|
+
} catch (e) {
|
|
23
|
+
queued = '';
|
|
24
|
+
onError?.(e);
|
|
25
|
+
} finally {
|
|
26
|
+
sending = false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { push(data) { queued += data; void drain(); } };
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function optionWordKey(event: { type: string; key: string; altKey: boolean; ctrlKey: boolean; metaKey: boolean; shiftKey: boolean }, isMac: boolean): string | undefined;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// OPTION+LEFT AND OPTION+RIGHT JUMP A WORD, as they do in Mac Terminal.
|
|
2
|
+
// xterm 6 sends them as ESC[1;3D and ESC[1;3C, which zsh and bash do not bind,
|
|
3
|
+
// so a shell typed `;3D` into the line instead. Mac Terminal sends ESC b and
|
|
4
|
+
// ESC f, the backward-word and forward-word every shell already knows.
|
|
5
|
+
//
|
|
6
|
+
// Returns what to send for this key, '' to swallow the keyup of one we
|
|
7
|
+
// handled, or undefined to leave the key to xterm. Off a Mac nothing changes:
|
|
8
|
+
// Linux terminals send the same ESC[1;3D xterm does.
|
|
9
|
+
const WORD = { ArrowLeft: '\x1bb', ArrowRight: '\x1bf' };
|
|
10
|
+
|
|
11
|
+
export function optionWordKey(event, isMac) {
|
|
12
|
+
if (!isMac || !event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return undefined;
|
|
13
|
+
const sequence = WORD[event.key];
|
|
14
|
+
if (!sequence) return undefined;
|
|
15
|
+
return event.type === 'keydown' ? sequence : '';
|
|
16
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export const SIZE_KEY: string;
|
|
2
|
+
export const TERMINAL_MIN: { height: number; width: number };
|
|
3
|
+
export function dragSize(dimension: 'height' | 'width', start: number, delta: number, room: number): number;
|
|
4
|
+
export function readSavedSize(raw: string | null | undefined): { height?: number; width?: number };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// HOW BIG THE TERMINAL MAY BE DRAGGED (renderer/src/components/TaskTerminal.tsx).
|
|
2
|
+
//
|
|
3
|
+
// Small enough still shows a few lines; large enough still leaves the page it
|
|
4
|
+
// sits in a strip to read (the thread's title and a line or two above it, or a
|
|
5
|
+
// column of the conversation beside it). The size is one for the whole app,
|
|
6
|
+
// remembered under SIZE_KEY, the way an editor remembers its panel.
|
|
7
|
+
export const SIZE_KEY = 'zero.terminal.size';
|
|
8
|
+
export const TERMINAL_MIN = { height: 120, width: 280 };
|
|
9
|
+
const LEAVE = { height: 120, width: 320 };
|
|
10
|
+
const LARGEST = 4000;
|
|
11
|
+
|
|
12
|
+
/** `start` px, the edge moved `delta` px toward growing, in a pane `room` px across. */
|
|
13
|
+
export function dragSize(dimension, start, delta, room) {
|
|
14
|
+
const min = TERMINAL_MIN[dimension];
|
|
15
|
+
const max = Math.max(min, room - LEAVE[dimension]);
|
|
16
|
+
return Math.round(Math.min(max, Math.max(min, start + delta)));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function readSavedSize(raw) {
|
|
20
|
+
let saved;
|
|
21
|
+
try { saved = JSON.parse(raw ?? ''); } catch { return {}; }
|
|
22
|
+
if (!saved || typeof saved !== 'object' || Array.isArray(saved)) return {};
|
|
23
|
+
const out = {};
|
|
24
|
+
for (const dimension of ['height', 'width']) {
|
|
25
|
+
const v = saved[dimension];
|
|
26
|
+
if (Number.isFinite(v) && v >= TERMINAL_MIN[dimension] && v <= LARGEST) out[dimension] = v;
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
package/shared/thread-cards.mjs
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
// private thread publishes no card at all (decided 2026-10-01
|
|
17
17
|
// from user research): not a wordless one, not a count.
|
|
18
18
|
|
|
19
|
+
import { shownPriority } from './rank.mjs';
|
|
20
|
+
|
|
19
21
|
export const THREAD_STATES = ['waiting', 'running', 'scheduled', 'done'];
|
|
20
22
|
|
|
21
23
|
/** Which of the four states a thread is in. */
|
|
@@ -245,7 +247,8 @@ export function cardsFor({ products, readItems, now = Date.now(), since = null }
|
|
|
245
247
|
// team. Never an empty list, which would have to mean both. Both clouds
|
|
246
248
|
// carry it and refuse to show the card to anybody else.
|
|
247
249
|
people: shownToPeople(item, product).length ? shownToPeople(item, product) : null,
|
|
248
|
-
|
|
250
|
+
// The level it runs at, so a teammate reads what the owner reads.
|
|
251
|
+
state, priority: shownPriority(item),
|
|
249
252
|
problem: s.problem || null, progress: s.progress || null, solution: s.solution || null,
|
|
250
253
|
blockedBy: linked(item.blockedBy), blocks: linked(item.blocks), updatedAt: item.updatedAt ?? now,
|
|
251
254
|
});
|
package/shared/usage.d.mts
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
// rule about which coding agent the corner is about, the zone primitives the
|
|
4
4
|
// Claude reader is built on, and the words the panel prints.
|
|
5
5
|
|
|
6
|
+
/** What a usage check that got nothing readable says, for any cause we have not named. */
|
|
7
|
+
export const USAGE_UNREADABLE: string;
|
|
8
|
+
|
|
6
9
|
export interface UsageLimit {
|
|
7
10
|
span: 'session' | 'week';
|
|
8
11
|
qualifier: string | null;
|
|
@@ -39,8 +42,17 @@ export declare function sessionLimit(limits: UsageLimit[] | null | undefined): U
|
|
|
39
42
|
export declare function headlineLimit(limits: UsageLimit[] | null | undefined): UsageLimit | null;
|
|
40
43
|
export declare const ANY_MOMENT: string;
|
|
41
44
|
export declare function spanLeft(ms: number): string;
|
|
45
|
+
export declare const toMinute: (at: number) => number;
|
|
46
|
+
export declare function resetDay(at: number, now?: number): string | null;
|
|
42
47
|
export declare function untilReset(ms: number): string;
|
|
43
48
|
|
|
49
|
+
/** The windows a reading can still answer for: one whose reset has gone past
|
|
50
|
+
* measures a window nobody is in any more. */
|
|
51
|
+
export declare function limitsStillRunning(
|
|
52
|
+
limits: UsageLimit[] | null | undefined,
|
|
53
|
+
now?: number,
|
|
54
|
+
): UsageLimit[];
|
|
55
|
+
|
|
44
56
|
export declare function limitRows(
|
|
45
57
|
limits: UsageLimit[] | null | undefined,
|
|
46
58
|
now?: number,
|
package/shared/usage.mjs
CHANGED
|
@@ -33,6 +33,13 @@ import { DEFAULT_ENGINE } from './engines.mjs';
|
|
|
33
33
|
|
|
34
34
|
/* ------------------ WHICH CODING AGENT THE CORNER IS ABOUT ---------------- */
|
|
35
35
|
|
|
36
|
+
// WHAT A USAGE CHECK THAT GOT NOTHING READABLE SAYS, for every reason we have
|
|
37
|
+
// not named. It used to be "Could not refresh usage. Try again.", which reads
|
|
38
|
+
// as the app having broken; the measured case (2026-10-11) was an account whose
|
|
39
|
+
// subscription had ended, and Claude Code simply printed no limits for it. So
|
|
40
|
+
// it points at the account, which is where an unnamed cause almost always is.
|
|
41
|
+
export const USAGE_UNREADABLE = "Claude Code didn't report usage for this account. Check that its plan is active at claude.ai.";
|
|
42
|
+
|
|
36
43
|
/**
|
|
37
44
|
* THE AGENT MOST OF HER LIVE WORK IS ON, or the workspace's own when nothing is
|
|
38
45
|
* running and when the fleet is level.
|
|
@@ -227,6 +234,51 @@ export function spanLeft(ms) {
|
|
|
227
234
|
return `${hours}h ${minutes % 60}m`;
|
|
228
235
|
}
|
|
229
236
|
|
|
237
|
+
/**
|
|
238
|
+
* HOW LONG A WINDOW GOES ON BEING DRAWN AFTER ITS RESET, so a countdown
|
|
239
|
+
* crossing zero does not flicker out of the panel a minute before the next
|
|
240
|
+
* reading lands. Inside it "any moment" is what it says: the reset is here.
|
|
241
|
+
*/
|
|
242
|
+
const JUST_RESET_MS = 2 * 60_000;
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* THE WINDOWS A READING CAN STILL ANSWER FOR (w-7b7182ef61).
|
|
246
|
+
*
|
|
247
|
+
* A window whose reset has gone past is over, and the one running in its place
|
|
248
|
+
* has never been read. shared/codex-usage.mjs already holds the rule for that:
|
|
249
|
+
* absence of data and a real zero are different facts and must never look the
|
|
250
|
+
* same. So it is DROPPED rather than relabelled -- no wording makes a
|
|
251
|
+
* percentage about a window nobody is in worth reading -- and the corner falls
|
|
252
|
+
* back to whatever the reading can still answer for (`headlineLimit`).
|
|
253
|
+
*
|
|
254
|
+
* A RESET WE COULD NOT PLACE ON A CLOCK KEEPS ITS ROW. `resetsAt` is null
|
|
255
|
+
* whenever Claude Code printed its stamp in a timezone we could not honestly
|
|
256
|
+
* convert, and a limit we cannot time is not a limit we may call finished.
|
|
257
|
+
*/
|
|
258
|
+
export function limitsStillRunning(limits, now = Date.now()) {
|
|
259
|
+
return (limits ?? []).filter((l) => !Number.isFinite(l?.resetsAt) || l.resetsAt + JUST_RESET_MS > now);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* A reset to the nearest minute. Claude Code gives one a moment before the
|
|
264
|
+
* minute (11:59:59.6) and the cloud's header the minute itself, and both name
|
|
265
|
+
* the same reset.
|
|
266
|
+
*/
|
|
267
|
+
export const toMinute = (at) => Math.round(at / 60_000) * 60_000;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* THE DAY A RESET FALLS ON, in this machine's calendar: null today, "Thu" this
|
|
271
|
+
* week, and "Tue Oct 27" six or more days out, where a weekday alone would
|
|
272
|
+
* read as today.
|
|
273
|
+
*/
|
|
274
|
+
export function resetDay(at, now = Date.now()) {
|
|
275
|
+
const d = new Date(toMinute(at));
|
|
276
|
+
if (d.toDateString() === new Date(now).toDateString()) return null;
|
|
277
|
+
const weekday = d.toLocaleDateString('en-US', { weekday: 'short' });
|
|
278
|
+
if (at - now < 6 * 86_400_000) return weekday;
|
|
279
|
+
return `${weekday} ${d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
230
282
|
/* -------------------------------- the panel ------------------------------- */
|
|
231
283
|
|
|
232
284
|
/**
|
|
@@ -274,7 +326,7 @@ export function spanLeft(ms) {
|
|
|
274
326
|
*/
|
|
275
327
|
export function limitRows(limits, now = Date.now()) {
|
|
276
328
|
const here = localZone();
|
|
277
|
-
return (limits
|
|
329
|
+
return limitsStillRunning(limits, now).map((l) => ({
|
|
278
330
|
// Stable across redraws, and it has to carry the qualifier: Claude Code's
|
|
279
331
|
// two week rows are otherwise the same string.
|
|
280
332
|
key: `${l.span}:${l.qualifier ?? ''}`,
|
|
@@ -309,8 +361,9 @@ function whenItStartsOver(l, now, here) {
|
|
|
309
361
|
* 2026-09-01.
|
|
310
362
|
*/
|
|
311
363
|
export function usageSentence(limits, now = Date.now(), engineWord = null) {
|
|
312
|
-
|
|
313
|
-
|
|
364
|
+
const live = limitsStillRunning(limits, now);
|
|
365
|
+
if (!live.length) return null;
|
|
366
|
+
const said = live.map((l) => {
|
|
314
367
|
const when = l.resetsAt ? `, ${untilReset(l.resetsAt - now)}` : l.resetsText ? `, resets ${l.resetsText}` : '';
|
|
315
368
|
return `${l.name}: ${l.percent}% used${when}.`;
|
|
316
369
|
}).join(' ');
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Types for shared/websites.mjs, the rules the main process and the screen
|
|
2
|
+
// share about a page off the open web: which addresses are one, what the
|
|
3
|
+
// pane's header calls it, and where its sign-ins go.
|
|
4
|
+
|
|
5
|
+
/** The session partition a website's cookies and sign-ins live in. */
|
|
6
|
+
export const WEBSITE_PARTITION: string;
|
|
7
|
+
|
|
8
|
+
/** Is this an ordinary page on the web, rather than a local app or a file? */
|
|
9
|
+
export function isWebsiteAddress(src: string | undefined | null): boolean;
|
|
10
|
+
|
|
11
|
+
/** What an address typed into the pane's bar means, with its scheme, or null. */
|
|
12
|
+
export function addressYouTyped(typed: string | undefined | null): string | null;
|
|
13
|
+
|
|
14
|
+
/** Can this page embed a website in a browser of its own (Electron only)? */
|
|
15
|
+
export function canFrameAWebsite(doc: { createElement(tag: string): unknown } | null | undefined): boolean;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// PURE. What counts as a website, and where its sign-ins live. Shared because
|
|
2
|
+
// the main process pins what an embedded page may do and the screen decides
|
|
3
|
+
// what to draw, and the two must agree.
|
|
4
|
+
//
|
|
5
|
+
// A local address is NOT a website here. A dev server is drawn in an ordinary
|
|
6
|
+
// frame with a liveness check in front of it (renderer/src/local-preview.ts)
|
|
7
|
+
// and its framing headers rewritten below the page (main/local-app-frames.mjs);
|
|
8
|
+
// none of that applies to a site on the web.
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The store a website's cookies and sign-ins go into. Not `persist:junk`:
|
|
12
|
+
* that one is safe for an agent to drive precisely because it carries
|
|
13
|
+
* nothing, and a person may sign into a page here.
|
|
14
|
+
*/
|
|
15
|
+
export const WEBSITE_PARTITION = 'persist:websites';
|
|
16
|
+
|
|
17
|
+
const LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);
|
|
18
|
+
|
|
19
|
+
/** Is this an ordinary page on the web, the kind the pane opens in a browser? */
|
|
20
|
+
export function isWebsiteAddress(src) {
|
|
21
|
+
if (typeof src !== 'string' || !src) return false;
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(src);
|
|
24
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;
|
|
25
|
+
return !LOOPBACK.has(url.hostname);
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* What an address typed into the pane's bar means: a page on the web or a
|
|
33
|
+
* local app, with the scheme a browser would add, or null. Never a search,
|
|
34
|
+
* which would send what you typed to somebody else.
|
|
35
|
+
*/
|
|
36
|
+
export function addressYouTyped(typed) {
|
|
37
|
+
const text = typeof typed === 'string' ? typed.trim() : '';
|
|
38
|
+
if (!text || /\s/.test(text)) return null;
|
|
39
|
+
const hasScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(text);
|
|
40
|
+
const bareHost = text.split(/[/?#]/)[0].replace(/:\d+$/, '').toLowerCase();
|
|
41
|
+
const full = hasScheme ? text : `${LOOPBACK.has(bareHost) ? 'http' : 'https'}://${text}`;
|
|
42
|
+
let url;
|
|
43
|
+
try { url = new URL(full); } catch { return null; }
|
|
44
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') return null;
|
|
45
|
+
if (url.username || url.password) return null;
|
|
46
|
+
if (!LOOPBACK.has(url.hostname) && !url.hostname.includes('.')) return null;
|
|
47
|
+
return url.href;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Can this page embed a website in a browser of its own? Only the desktop
|
|
52
|
+
* window can: `<webview>` is Electron's, and nothing registers the tag in an
|
|
53
|
+
* ordinary tab or on a phone. Asking the tag beats guessing which door this is.
|
|
54
|
+
*/
|
|
55
|
+
export function canFrameAWebsite(doc) {
|
|
56
|
+
try {
|
|
57
|
+
return !!doc && 'partition' in doc.createElement('webview');
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -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
|
+
}
|