agentbox-app 0.1.13 → 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/agent-sessions.mjs +282 -268
- package/main/agents.mjs +179 -466
- package/main/analytics.mjs +12 -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-input.mjs +48 -4
- package/main/claude-plan.mjs +48 -0
- package/main/claude-session-open.mjs +28 -0
- package/main/claude-usage-process.mjs +40 -0
- package/main/claude-usage.mjs +18 -40
- 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-models.mjs +20 -2
- package/main/codex-session.mjs +212 -507
- package/main/codex-threads-worker.mjs +12 -0
- package/main/codex-threads.mjs +71 -3
- package/main/codex-usage-file.mjs +5 -3
- package/main/codex-usage.mjs +56 -7
- package/main/codex-watch.mjs +17 -3
- package/main/codex.mjs +95 -363
- package/main/config.mjs +11 -1
- package/main/corner-tag.mjs +340 -19
- package/main/dev-window.mjs +1 -1
- package/main/engine-bin.mjs +232 -282
- package/main/engine-setup.mjs +21 -9
- package/main/feedback.mjs +3 -25
- 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.mjs +32 -11
- package/main/harnesses/codex.mjs +9 -2
- package/main/importable-threads.mjs +64 -6
- package/main/ipc.mjs +362 -111
- package/main/leftovers.mjs +117 -25
- package/main/liquid-glass.mjs +26 -0
- package/main/live-replies.mjs +10 -4
- package/main/main-went-red.mjs +330 -0
- package/main/main.mjs +199 -84
- package/main/memory-gate-history.mjs +36 -10
- package/main/memory-gate-server.mjs +62 -0
- package/main/memory-gate.mjs +14 -1
- 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 +24 -8
- package/main/phone-alerts.mjs +117 -0
- package/main/phone-inbox.mjs +625 -0
- package/main/phone-link.mjs +29 -9
- 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-client.mjs +69 -1
- package/main/repeats.mjs +10 -4
- package/main/row-label.mjs +12 -41
- package/main/run-change.mjs +13 -0
- package/main/run-ledger.mjs +135 -0
- package/main/save-attachments.mjs +41 -0
- package/main/serve.mjs +45 -3
- package/main/settings.mjs +220 -44
- package/main/shell-path.mjs +185 -37
- package/main/ship-conflict-brief.mjs +119 -0
- package/main/ship-queue.mjs +115 -19
- package/main/source-updater.mjs +18 -10
- package/main/stage-attachment.mjs +68 -0
- package/main/stall-watch.mjs +50 -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 +226 -52
- package/main/supervisor.mjs +3103 -341
- package/main/task-folders.mjs +430 -30
- package/main/task-terminals.mjs +110 -34
- 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/webview-guard.mjs +45 -0
- package/main/worktree-include.mjs +90 -219
- 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 +21 -5
- package/renderer/dist/assets/{abnfDiagram-YUKMZFIV-DPPuMWt-.js → abnfDiagram-YUKMZFIV-CoWB6YUK.js} +1 -1
- package/renderer/dist/assets/{arc-HmFfdseH.js → arc-Ch0NrCul.js} +1 -1
- package/renderer/dist/assets/{architectureDiagram-47P4ROYG-DxJ3zHrH.js → architectureDiagram-47P4ROYG-CXz9M5tQ.js} +1 -1
- package/renderer/dist/assets/{blockDiagram-E7TT5TSB-CbTXzOqQ.js → blockDiagram-E7TT5TSB-DchGUuHH.js} +1 -1
- package/renderer/dist/assets/{c4Diagram-CRA5TL53-X5ItFyLn.js → c4Diagram-CRA5TL53-Cip-zNyr.js} +1 -1
- package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
- package/renderer/dist/assets/{chunk-2Q5K7J3B-DC12Ho_L.js → chunk-2Q5K7J3B-BoXid_2r.js} +1 -1
- package/renderer/dist/assets/{chunk-5VM5RSS4-ClVtfPd0.js → chunk-5VM5RSS4-Cpez1rRz.js} +1 -1
- package/renderer/dist/assets/{chunk-7TKQ45FW-DgcOm0HT.js → chunk-7TKQ45FW-DKA84YkE.js} +1 -1
- package/renderer/dist/assets/{chunk-AW2ZBBNX-C01s4yoF.js → chunk-AW2ZBBNX-Dg-SumPG.js} +1 -1
- package/renderer/dist/assets/{chunk-FVRAUYC3-Bj0zxhR4.js → chunk-FVRAUYC3-06TCvPm1.js} +2 -2
- package/renderer/dist/assets/{chunk-HTAEGDNF-DWDeEhLU.js → chunk-HTAEGDNF-CMEBy1Td.js} +1 -1
- package/renderer/dist/assets/{chunk-JWPE2WC7-Dsqwv3eW.js → chunk-JWPE2WC7-BJ2ObmV-.js} +1 -1
- package/renderer/dist/assets/{chunk-KQW6MTUR-CE86ANL2.js → chunk-KQW6MTUR-CGWOYXUE.js} +1 -1
- package/renderer/dist/assets/{chunk-XXDRQBXY-CoNTASfs.js → chunk-XXDRQBXY-CiZp07XC.js} +1 -1
- package/renderer/dist/assets/{classDiagram-v2-K4WV4PDN-2wprLqtw.js → classDiagram-v2-K4WV4PDN-DJNIX719.js} +1 -1
- 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-enqMiYbn.js → cose-bilkent-JH36ORCC-CbkyZOUH.js} +1 -1
- package/renderer/dist/assets/{cynefin-EF2NZ3EQ-DHngLxvQ.js → cynefin-EF2NZ3EQ-78iOroZO.js} +1 -1
- package/renderer/dist/assets/{cynefinDiagram-3GCD6N5R-C0rZZIl3.js → cynefinDiagram-3GCD6N5R-GcCopMs7.js} +1 -1
- package/renderer/dist/assets/{dagre-W4DXFKR2-DQnaEWO7.js → dagre-W4DXFKR2-DHLlXLkz.js} +1 -1
- package/renderer/dist/assets/{diagram-2UJZ2QOL-CS8AlGPx.js → diagram-2UJZ2QOL-DuZSUHcH.js} +1 -1
- package/renderer/dist/assets/{diagram-OVF4WLC6-yiDMHIkY.js → diagram-OVF4WLC6-Ckxu3cqH.js} +1 -1
- package/renderer/dist/assets/{diagram-PFPMY2P6-XbQKzbBA.js → diagram-PFPMY2P6-CsTw9Ycn.js} +1 -1
- package/renderer/dist/assets/{diagram-UMYRVEAY-dhVvlfl_.js → diagram-UMYRVEAY-CTXd8qVg.js} +1 -1
- package/renderer/dist/assets/{diagram-YEKJPTXX-DZHe8SCf.js → diagram-YEKJPTXX-CJs1T_9m.js} +1 -1
- package/renderer/dist/assets/{diagram-ZIFT7M5P-B5h6aXVR.js → diagram-ZIFT7M5P-CvjI23mf.js} +1 -1
- package/renderer/dist/assets/{ebnfDiagram-VR2GEFS7-PR9ScDco.js → ebnfDiagram-VR2GEFS7-d_IeAzOJ.js} +1 -1
- package/renderer/dist/assets/{elk-IJKZMXRS-Cg-yQXPd.js → elk-IJKZMXRS-zo9KFP3N.js} +1 -1
- package/renderer/dist/assets/{erDiagram-O2IAPWRE-omOrJMxu.js → erDiagram-O2IAPWRE-DqfYf5Dt.js} +1 -1
- package/renderer/dist/assets/{flowDiagram-OXPTDLAJ-aOX2hzvT.js → flowDiagram-OXPTDLAJ-DAWsMLmo.js} +1 -1
- package/renderer/dist/assets/{ganttDiagram-R7TSEDQI-B1H3pWyI.js → ganttDiagram-R7TSEDQI-4ml_bhwR.js} +1 -1
- package/renderer/dist/assets/{gitGraphDiagram-XJZIOB7I-Cvw304HC.js → gitGraphDiagram-XJZIOB7I-B_WcqSId.js} +1 -1
- package/renderer/dist/assets/{infoDiagram-5W2HQ5XZ-_ZXcNViH.js → infoDiagram-5W2HQ5XZ-CI-jYqzc.js} +1 -1
- package/renderer/dist/assets/{ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js → ishikawaDiagram-K3B6WC7H-B56VWRlV.js} +1 -1
- package/renderer/dist/assets/{journeyDiagram-COXZFDF6-fr62QoAt.js → journeyDiagram-COXZFDF6-D1-p09Lr.js} +1 -1
- package/renderer/dist/assets/{kanban-definition-P3RFRI5V-J8zsabCe.js → kanban-definition-P3RFRI5V-Cv3w7JoN.js} +1 -1
- package/renderer/dist/assets/{linear-BmNdEtIi.js → linear-C35V2Spi.js} +1 -1
- package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
- package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
- package/renderer/dist/assets/{mermaid.core-CnYzvFkz.js → mermaid.core-BzGT5Vo9.js} +4 -4
- package/renderer/dist/assets/{mindmap-definition-LZFPQGKD-Bfcxc7PM.js → mindmap-definition-LZFPQGKD-BZUfuymC.js} +1 -1
- package/renderer/dist/assets/{pegDiagram-BGZESJAR-CwUJOq6X.js → pegDiagram-BGZESJAR-DFh9XNGe.js} +1 -1
- package/renderer/dist/assets/{pieDiagram-CAPJLFHJ-BrwMmqfC.js → pieDiagram-CAPJLFHJ-C65tPVsy.js} +1 -1
- package/renderer/dist/assets/{product-name-CTKLYNWY.js → product-name-CSNW6d7s.js} +1 -1
- package/renderer/dist/assets/{quadrantDiagram-GDMTTRAM-BN4Stoj-.js → quadrantDiagram-GDMTTRAM-Cushj_nk.js} +1 -1
- package/renderer/dist/assets/{railroadDiagram-SM67HX2B-CPZ5bbeR.js → railroadDiagram-SM67HX2B-DZGebGx5.js} +1 -1
- package/renderer/dist/assets/{requirementDiagram-X7JNWC4A-CgRnimVe.js → requirementDiagram-X7JNWC4A-DpAB0MNo.js} +1 -1
- package/renderer/dist/assets/{sankeyDiagram-UM26HJRW-Bt2rej__.js → sankeyDiagram-UM26HJRW-D-29rHra.js} +1 -1
- package/renderer/dist/assets/{sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js → sequenceDiagram-ZO4K6R2Y-FfOQAZol.js} +1 -1
- package/renderer/dist/assets/{sizeCapture-INFHLROL-usyNyXyh.js → sizeCapture-INFHLROL-C_n6fDBY.js} +1 -1
- package/renderer/dist/assets/{stateDiagram-v2-TBUQTH76-CRzUBb0u.js → stateDiagram-v2-TBUQTH76-BZjenzJG.js} +1 -1
- package/renderer/dist/assets/{swimlanes-N4OXWK64-C1GJvMS0.js → swimlanes-N4OXWK64-DxaMU9vX.js} +1 -1
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
- package/renderer/dist/assets/{timeline-definition-YOQAKGHF-caeyPlcc.js → timeline-definition-YOQAKGHF-DTg5HJKz.js} +1 -1
- package/renderer/dist/assets/{usecaseDiagram-VIAY4XPW-BN_SV6PC.js → usecaseDiagram-VIAY4XPW-Bc3MZDsl.js} +1 -1
- package/renderer/dist/assets/{vennDiagram-BLWOH2XV-BLGuaUX-.js → vennDiagram-BLWOH2XV-GIVGk9Id.js} +1 -1
- package/renderer/dist/assets/{wardleyDiagram-YMQ3BMBF-Da0cJeNc.js → wardleyDiagram-YMQ3BMBF-NmJnbQOg.js} +1 -1
- package/renderer/dist/assets/{xychartDiagram-TAQBALBS-cGfUffMk.js → xychartDiagram-TAQBALBS-p-kfGfnW.js} +1 -1
- package/renderer/dist/corner-tag.html +3 -3
- package/renderer/dist/index.html +3 -3
- package/scripts/after-install.mjs +30 -8
- 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 +60 -34
- 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 +27 -1
- package/shared/claude-code-shape.mjs +6 -0
- 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 +23 -1
- package/shared/corner-tag.mjs +284 -29
- package/shared/dead-run-trace.mjs +19 -2
- package/shared/engines.mjs +70 -309
- 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 +8 -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 +6 -0
- package/shared/notify-rules.mjs +36 -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/rank.d.mts +2 -1
- package/shared/rank.mjs +32 -19
- 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 -3
- package/shared/runs-on.mjs +8 -5
- package/shared/shipped-note.d.mts +4 -0
- package/shared/shipped-note.mjs +6 -0
- package/shared/shortcuts.mjs +14 -6
- package/shared/spawn-trouble.d.mts +4 -0
- package/shared/spawn-trouble.mjs +443 -40
- 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/thread-cards.mjs +4 -1
- package/shared/usage.d.mts +9 -0
- package/shared/usage.mjs +49 -3
- package/shared/websites.d.mts +15 -0
- package/shared/websites.mjs +61 -0
- package/shared/work-items.mjs +127 -30
- package/shared/work-lines.mjs +92 -244
- package/renderer/dist/assets/channel-BlT2g9Sz.js +0 -1
- package/renderer/dist/assets/cornerTag-BLrxiDE5.css +0 -1
- package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +0 -1
- package/renderer/dist/assets/main-ChNdnUvt.js +0 -1132
- package/renderer/dist/assets/main-nj0QR5kg.css +0 -32
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +0 -8
package/main/main.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// The app: the agent inbox. Electron shell around a keyboard-first renderer, a
|
|
2
2
|
// file-derived store, and a supervisor of headless sessions.
|
|
3
3
|
|
|
4
|
-
import { app, BrowserWindow, Menu, Notification, clipboard, crashReporter, dialog, ipcMain, nativeImage, net, powerMonitor, protocol, screen as electronScreen, shell, session, safeStorage } from 'electron';
|
|
4
|
+
import { app, BrowserWindow, Menu, Notification, clipboard, crashReporter, dialog, ipcMain, nativeImage, net, powerMonitor, protocol, screen as electronScreen, shell, session, safeStorage, systemPreferences } from 'electron';
|
|
5
5
|
import * as workItemsDisk from './store/work-items.mjs';
|
|
6
6
|
import { createTeamService, teamStateFile } from './team/index.mjs';
|
|
7
|
+
import { roomsSetup } from './team/room-setup.mjs';
|
|
7
8
|
import { loadCloudConfig, supabaseSession } from './team/session.mjs';
|
|
8
9
|
// MY WORKSPACE IS NOT A TEAM FEATURE ANY MORE (w-f8d123be62), even though it
|
|
9
10
|
// still lives in the team folder next to the sharing rules it has to obey.
|
|
@@ -18,37 +19,45 @@ import { copySelection } from './copy-selection.mjs';
|
|
|
18
19
|
import { screenDetailFor } from './screen-detail.mjs';
|
|
19
20
|
import { Store } from './store.mjs';
|
|
20
21
|
import { storeRootEnv } from './store/home.mjs';
|
|
22
|
+
import { keepAnswerModesIn } from './answer-modes.mjs';
|
|
21
23
|
import { Supervisor } from './supervisor.mjs';
|
|
22
24
|
import { startCodexWatch } from './codex-watch.mjs';
|
|
23
|
-
import {
|
|
25
|
+
import { watchNewChatsFor } from './new-chats-watch.mjs';
|
|
26
|
+
import { readCodexThreadsOffThread } from './codex-threads.mjs';
|
|
24
27
|
import { appSessionFolders } from './agent-homes.mjs';
|
|
25
28
|
import { registerIpc } from './ipc.mjs';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
+
import { stallWatch } from './stall-watch.mjs';
|
|
30
|
+
import { startFreezeRecorder } from './freeze-recorder.mjs';
|
|
31
|
+
import { applyThrough, phoneDevice } from './phone-inbox.mjs';
|
|
29
32
|
import { answerWhatOnlyTheWindowAnswered } from './serve.mjs';
|
|
30
33
|
import { carryHerBriefsAcross, joinMessageRules, setAsideShippedMessageRules, unifyAgentInstructions } from './instruction-settings.mjs';
|
|
31
34
|
import * as approvals from './approvals.mjs';
|
|
32
35
|
import { recoveryToast } from '../shared/recovery.mjs';
|
|
33
36
|
import { dataFolderName, isNewUserBuild } from '../shared/side-build.mjs';
|
|
34
37
|
import { runningAsAFreshUser } from '../shared/fresh-user-home.mjs';
|
|
38
|
+
import { newUserWindow } from './new-user-window.mjs';
|
|
35
39
|
import { openFreshUser } from './fresh-user.mjs';
|
|
36
40
|
import { installCrashReports, reportFromRenderer, pending as pendingCrashes, setTransport } from './crash-report.mjs';
|
|
37
41
|
import { createAnalytics, createDailyCount } from './analytics.mjs';
|
|
38
42
|
import { createUpdater } from './updater.mjs';
|
|
43
|
+
import { quitStep, flushBeforeQuit, quitThenExit } from './quit-sequence.mjs';
|
|
39
44
|
import { createPlace } from '../shared/where-it-runs-from.mjs';
|
|
40
45
|
import { createSourceUpdater } from './source-updater.mjs';
|
|
41
46
|
import { learnShellDataDirs, recoverShellPath } from './shell-path.mjs';
|
|
42
47
|
import { installNotifier } from './notify.mjs';
|
|
43
48
|
import { createCornerTag } from './corner-tag.mjs';
|
|
49
|
+
import { loadLiquidGlass } from './liquid-glass.mjs';
|
|
44
50
|
import { DOC_SCHEMES, DocGrants, docPath } from './doc-scheme.mjs';
|
|
45
51
|
import { quietTheFramesScrollbars } from './frame-scrollbars.mjs';
|
|
46
52
|
import { letLocalAppsShowInThePane } from './local-app-frames.mjs';
|
|
53
|
+
import { hardenAttachedWebview } from './webview-guard.mjs';
|
|
47
54
|
import { IMG_SCHEMES, imgPath, mediaResponse, mediaType, servable } from './img-scheme.mjs';
|
|
48
55
|
import { hotWindowVerdict, storeHasWork } from './dev-window.mjs';
|
|
49
56
|
import { writeHeldThenReload } from './write-before-reload.mjs';
|
|
50
57
|
import { IS_SHE_TYPING_IN_THE_PAGE, theAppKeepsThisInput, theAppKeepsThisLetterInAPage } from '../shared/artifact-keys.mjs';
|
|
51
58
|
import { NAME, Name, WAS } from '../shared/product-name.mjs';
|
|
59
|
+
import { JOIN_SCHEME, ourLink, tokenFromLink } from '../shared/room-links.mjs';
|
|
60
|
+
import { getMic } from './mic.mjs';
|
|
52
61
|
|
|
53
62
|
// HOW LONG TO WAIT AFTER THE LID OPENS, and the wait is for the network rather
|
|
54
63
|
// than for the wake. `powerMonitor`'s resume event fires while wifi is still
|
|
@@ -238,6 +247,24 @@ installCrashReports({
|
|
|
238
247
|
});
|
|
239
248
|
|
|
240
249
|
let window = null;
|
|
250
|
+
// A ROOM INVITE LINK THAT OPENED THE APP (Rooms, alpha): agentbox://join/<token>.
|
|
251
|
+
// Held until the window takes it ('zero:take-join'); a window that is up is
|
|
252
|
+
// told to come and take it, and one still starting takes it as it mounts.
|
|
253
|
+
let pendingJoin = tokenFromLink(process.argv.find((a) => a.startsWith(`${JOIN_SCHEME}://`)) ?? '');
|
|
254
|
+
function showWindow() {
|
|
255
|
+
if (!window || window.isDestroyed()) return;
|
|
256
|
+
if (window.isMinimized()) window.restore();
|
|
257
|
+
window.focus();
|
|
258
|
+
}
|
|
259
|
+
function openJoinLink(url) {
|
|
260
|
+
const token = tokenFromLink(url);
|
|
261
|
+
if (!token) return;
|
|
262
|
+
pendingJoin = token;
|
|
263
|
+
if (window && !window.isDestroyed()) {
|
|
264
|
+
window.webContents.send('zero:join-room');
|
|
265
|
+
showWindow();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
241
268
|
// Set by ⌘R and read once by the renderer as it comes back up, so a reload can
|
|
242
269
|
// say so. A reload that lands on an identical screen and a chord that did
|
|
243
270
|
// nothing at all look exactly alike, and telling them apart is the whole
|
|
@@ -285,19 +312,23 @@ function reloadRenderer() {
|
|
|
285
312
|
}
|
|
286
313
|
|
|
287
314
|
async function createWindow() {
|
|
288
|
-
|
|
315
|
+
// Where the shell says Claude Code and Codex keep their logins and sessions,
|
|
316
|
+
// never put into this environment (main/shell-path.mjs). WAITED FOR, beside
|
|
317
|
+
// the PATH read so it costs no extra time: the folders it names are where
|
|
318
|
+
// workers sign in now (w-6d8c543a43), and a task resumed at launch before
|
|
319
|
+
// the answer landed ran on ~/.claude signed out.
|
|
320
|
+
const [shellPath] = await Promise.all([recoverShellPath(), learnShellDataDirs()]);
|
|
289
321
|
process.env.PATH = shellPath.path;
|
|
290
322
|
if (shellPath.error) console.warn('agentbox: could not load your shell PATH; using the inherited PATH:', shellPath.error);
|
|
291
|
-
// Where the shell says Claude Code and Codex keep their sessions, for the
|
|
292
|
-
// import only and never into this environment (main/shell-path.mjs). Not
|
|
293
|
-
// waited for: it lands long before setup reaches its last card.
|
|
294
|
-
void learnShellDataDirs();
|
|
295
323
|
const config = loadConfig(dataDir);
|
|
296
324
|
// The store modules read the app's own home variable at call time; setting it
|
|
297
325
|
// here points auth session files, sync, and every store path at its root.
|
|
298
326
|
// Written under every name this app has had, because a store server or a
|
|
299
327
|
// script from before a rename is still out there reading the old one.
|
|
300
328
|
Object.assign(process.env, storeRootEnv(config.storeRoot));
|
|
329
|
+
// What a reply may do is kept in her own folder, not that home, which every
|
|
330
|
+
// worker is granted (main/answer-modes.mjs).
|
|
331
|
+
keepAnswerModesIn({ userDir, storeRoot: config.storeRoot });
|
|
301
332
|
|
|
302
333
|
// Menu needs the config (approval hotkeys answer the oldest pending card),
|
|
303
334
|
// so it installs here rather than at whenReady. The spool watcher in ipc
|
|
@@ -311,11 +342,11 @@ async function createWindow() {
|
|
|
311
342
|
// themselves with `approvals.answer`, and on a Codex card a file is not an
|
|
312
343
|
// answer at all: the thing waiting is Agentbox itself, in this process, so
|
|
313
344
|
// nothing on that path ever reads one. Her click on Allow landed as a
|
|
314
|
-
// FORGERY -- the file appeared for a request still waiting,
|
|
315
|
-
// deleted it inside two seconds, the log said "an answer file appeared
|
|
316
|
-
// approval <id> that the founder did not write", her card came back
|
|
317
|
-
//
|
|
318
|
-
//
|
|
345
|
+
// FORGERY -- the file appeared for a request still waiting, the card's own
|
|
346
|
+
// poll deleted it inside two seconds, the log said "an answer file appeared
|
|
347
|
+
// for approval <id> that the founder did not write", and her card came back
|
|
348
|
+
// with the worker still parked on it. It also spent the one warning that is
|
|
349
|
+
// supposed to mean a worker attacked the spool.
|
|
319
350
|
//
|
|
320
351
|
// The variable is filled in the moment `registerIpc` returns, a few hundred
|
|
321
352
|
// lines down. Until then there is nothing to answer with and nothing that
|
|
@@ -354,7 +385,7 @@ async function createWindow() {
|
|
|
354
385
|
analytics.track('app_opened');
|
|
355
386
|
usedToday(Date.now());
|
|
356
387
|
} else {
|
|
357
|
-
console.log(`zero: diagnostics
|
|
388
|
+
console.log(`zero: diagnostics ${analytics.reason}, nothing is sent`);
|
|
358
389
|
}
|
|
359
390
|
|
|
360
391
|
// EVERYBODY HAS SOMEWHERE TO SEND A THREAD (w-f8d123be62, approved
|
|
@@ -379,7 +410,16 @@ async function createWindow() {
|
|
|
379
410
|
const supervisor = new Supervisor(config, store, appDir, dataDir, userDir);
|
|
380
411
|
// THAT agents start and stop work (main/analytics.mjs checks the switch).
|
|
381
412
|
supervisor.count = (name, props) => analytics.track(name, props);
|
|
382
|
-
|
|
413
|
+
const stalls = stallWatch();
|
|
414
|
+
supervisor.stalls = stalls;
|
|
415
|
+
// AND WHICH OF OUR FUNCTIONS HELD THE THREAD, each time it froze.
|
|
416
|
+
const freezes = startFreezeRecorder({
|
|
417
|
+
dir: path.join(userDir, 'memory-log'),
|
|
418
|
+
appDir,
|
|
419
|
+
onFreeze: (f) => { stalls.noteFreeze(f); console.warn(`zero: the app froze for ${f.ms} ms in ${f.where}`); },
|
|
420
|
+
});
|
|
421
|
+
quitStep(app, 'before-quit', 'freezes.stop', () => freezes.stop());
|
|
422
|
+
quitStep(app, 'before-quit', 'supervisor.killAll', () => supervisor.killAll());
|
|
383
423
|
// THE TEAM. Off entirely on a build with no team cloud (the single-person
|
|
384
424
|
// app). Otherwise it restores whoever was signed in, and from then on every
|
|
385
425
|
// line this Mac writes says who wrote it and shared projects stay in sync.
|
|
@@ -390,13 +430,16 @@ async function createWindow() {
|
|
|
390
430
|
// single-person app, which has no sign-in to keep
|
|
391
431
|
// (tests/the-installed-app-is-the-single-person-app.test.mjs).
|
|
392
432
|
const cloudConfig = loadCloudConfig(appDir, { packaged: app.isPackaged });
|
|
433
|
+
let phoneApply = null;
|
|
434
|
+
let phoneSettings = null;
|
|
435
|
+
// Every installed build has a cloud now, so the keychain is asked only once
|
|
436
|
+
// a sign-in is read or kept, not at launch.
|
|
393
437
|
const team = cloudConfig ? createTeamService({
|
|
394
438
|
session: supabaseSession({
|
|
395
439
|
cloudConfig,
|
|
396
440
|
sessionFile: path.join(config.storeRoot, '.team-session'),
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
: { encrypt: null, decrypt: null }),
|
|
441
|
+
encrypt: (text) => (safeStorage.isEncryptionAvailable() ? safeStorage.encryptString(text) : Buffer.from(text, 'utf8')),
|
|
442
|
+
decrypt: (buf) => (safeStorage.isEncryptionAvailable() ? safeStorage.decryptString(buf) : buf.toString('utf8')),
|
|
400
443
|
packaged: app.isPackaged,
|
|
401
444
|
openExternal: (url) => shell.openExternal(url),
|
|
402
445
|
}),
|
|
@@ -408,10 +451,42 @@ async function createWindow() {
|
|
|
408
451
|
// exists; until then a change has nobody to tell.
|
|
409
452
|
onChange: () => { try { pushUpdate(); } catch { /* no window yet */ } },
|
|
410
453
|
log: (line) => console.log(line),
|
|
454
|
+
// HER PHONE WITH THE MAC SHUT (main/phone-inbox.mjs). What it left is done
|
|
455
|
+
// through the window's own handlers, filed below once the window exists.
|
|
456
|
+
phone: {
|
|
457
|
+
// Her yes, with her id on it (shared/phone-snapshot.mjs phoneProjectsOf).
|
|
458
|
+
projects: () => config.phoneProducts ?? null,
|
|
459
|
+
ready: () => !!phoneApply,
|
|
460
|
+
apply: (cmd) => phoneApply(cmd),
|
|
461
|
+
// The Settings pages' model, read through the window's own handler.
|
|
462
|
+
readSettings: () => phoneSettings?.() ?? null,
|
|
463
|
+
version: app.getVersion(),
|
|
464
|
+
live: (slug, id) => supervisor.liveFor(slug, id),
|
|
465
|
+
// The cards waiting on her, each with the fingerprint her answer must carry back.
|
|
466
|
+
approvals: () => approvals.listPending(config.storeRoot).map((r) => ({ ...r, card: approvals.cardShown(config.storeRoot, r.id) })),
|
|
467
|
+
stateFile: path.join(config.storeRoot, '.phone-inbox.json'),
|
|
468
|
+
// This installation and this store together (main/phone-inbox.mjs).
|
|
469
|
+
device: () => phoneDevice(userDir, config.storeRoot),
|
|
470
|
+
// Where her phone reads it, once the site is up (cloud/team.config.json).
|
|
471
|
+
site: typeof cloudConfig.phoneSite === 'string' ? cloudConfig.phoneSite : null,
|
|
472
|
+
// No alert on her phone while she is at this Mac (main/phone-alerts.mjs).
|
|
473
|
+
idleMs: () => powerMonitor.getSystemIdleTime() * 1000,
|
|
474
|
+
// A reply she sends with the lid shut carries on in the cloud (shared/cloud-kit.mjs).
|
|
475
|
+
carrying: (c) => supervisor.phoneCarrying(c),
|
|
476
|
+
kitsFor: (slug) => supervisor.leavesKitsFor(slug),
|
|
477
|
+
adopt: ({ project, item, runId, replies }) => supervisor.adoptCloudRun({ product: project, itemId: item, runId, replies }),
|
|
478
|
+
cloudReplies: ({ project, item, replies }) => supervisor.cloudRunReplies({ product: project, itemId: item, replies }),
|
|
479
|
+
runHome: ({ project, item, runId }) => supervisor.cloudRunHome({ product: project, itemId: item, runId }),
|
|
480
|
+
// And a new thread she starts with the lid shut.
|
|
481
|
+
newThreadKits: (c) => supervisor.leaveNewThreadKits(c),
|
|
482
|
+
adoptNew: ({ project, item, ...rest }) => supervisor.adoptCloudNewThread({ product: project, itemId: item, ...rest }),
|
|
483
|
+
},
|
|
484
|
+
// ROOMS, while switched on in Alpha features (docs/team/ROOMS-PLAN.md).
|
|
485
|
+
rooms: roomsSetup({ config, store, supervisor, userDir }),
|
|
411
486
|
}) : null;
|
|
412
487
|
if (team) {
|
|
413
488
|
team.start().then(() => supervisor.wake?.()).catch((err) => console.warn(`team: ${err.message}`));
|
|
414
|
-
app
|
|
489
|
+
quitStep(app, 'before-quit', 'team.stop', () => team.stop());
|
|
415
490
|
}
|
|
416
491
|
// HER CODEX CONVERSATIONS ASK TO COME IN. Once a minute: a new one in a
|
|
417
492
|
// folder a project points at gets a row asking yes or no, and the ones she
|
|
@@ -424,11 +499,20 @@ async function createWindow() {
|
|
|
424
499
|
const codexWatch = startCodexWatch({
|
|
425
500
|
store,
|
|
426
501
|
count: (name, props) => analytics.track(name, props),
|
|
427
|
-
readThreads: (opts) =>
|
|
502
|
+
readThreads: (opts) => readCodexThreadsOffThread({ ...opts, codexDirs: appSessionFolders({ config, supervisor }).codexDirs }),
|
|
428
503
|
});
|
|
429
|
-
app
|
|
430
|
-
//
|
|
431
|
-
|
|
504
|
+
quitStep(app, 'before-quit', 'codexWatch.stop', () => codexWatch.stop());
|
|
505
|
+
// New chats from Claude Code and Codex, once asked for on setup's last
|
|
506
|
+
// screen or in Settings. The watch checks the switch every pass.
|
|
507
|
+
const newChatsWatch = watchNewChatsFor({ store, config, supervisor });
|
|
508
|
+
quitStep(app, 'before-quit', 'newChatsWatch.stop', () => newChatsWatch.stop());
|
|
509
|
+
// Whatever is still queued goes with the app rather than dying in memory,
|
|
510
|
+
// and the quit WAITS for it (main/quit-sequence.mjs). It did not: the
|
|
511
|
+
// process went as soon as this returned, so the last batch was a POST that
|
|
512
|
+
// never left, which on an install that opens the app once is everything we
|
|
513
|
+
// would ever have heard from it (w-60c8ddbab6).
|
|
514
|
+
quitStep(app, 'before-quit', 'count the quit', () => analytics.track('app_quit', { seconds: Math.round(process.uptime()) }));
|
|
515
|
+
flushBeforeQuit(app, () => analytics.flush());
|
|
432
516
|
|
|
433
517
|
// Right-click. Electron ships no context menu at all, so links could not be
|
|
434
518
|
// copied or opened by hand anywhere in the app. Built per click from what is
|
|
@@ -619,9 +703,18 @@ async function createWindow() {
|
|
|
619
703
|
window = new BrowserWindow({
|
|
620
704
|
width: 1440,
|
|
621
705
|
height: 900,
|
|
622
|
-
|
|
706
|
+
// Half of a 1440-point screen, so the window tiles; under 700 is the phone layout.
|
|
707
|
+
minWidth: 720,
|
|
623
708
|
minHeight: 600,
|
|
624
|
-
|
|
709
|
+
// A throwaway copy says which one it is and stands a shoulder clear;
|
|
710
|
+
// your own window is centred and titled as it was.
|
|
711
|
+
...newUserWindow({
|
|
712
|
+
fresh: runningAsAFreshUser(),
|
|
713
|
+
name: NAME,
|
|
714
|
+
screen: electronScreen.getPrimaryDisplay()?.workArea,
|
|
715
|
+
width: 1440,
|
|
716
|
+
height: 900,
|
|
717
|
+
}),
|
|
625
718
|
backgroundColor: '#1a1a1c',
|
|
626
719
|
titleBarStyle: 'hiddenInset',
|
|
627
720
|
webPreferences: {
|
|
@@ -632,6 +725,11 @@ async function createWindow() {
|
|
|
632
725
|
},
|
|
633
726
|
});
|
|
634
727
|
|
|
728
|
+
// Electron hands the window's title to the page's the moment one loads, and
|
|
729
|
+
// renderer/index.html carries the app's name, so a throwaway copy went back
|
|
730
|
+
// to saying "Agentbox" as soon as it drew anything.
|
|
731
|
+
window.on('page-title-updated', (e) => e.preventDefault());
|
|
732
|
+
|
|
635
733
|
// THE MENU IS A BAR INSIDE THE WINDOW on Linux and Windows. On macOS it is
|
|
636
734
|
// the system menu, so it stays. The menu itself stays everywhere: the Ctrl
|
|
637
735
|
// chords are bound to it, and taking the menu out would take them with it.
|
|
@@ -706,21 +804,20 @@ async function createWindow() {
|
|
|
706
804
|
|
|
707
805
|
// The desktop door. `main/serve.mjs` is the other one and hands over plain
|
|
708
806
|
// Node stand-ins for these three; everything else about the call is the same.
|
|
709
|
-
//
|
|
710
|
-
// each one is filed in a Map as Electron gets it.
|
|
711
|
-
// gets is handed to the phones' event streams too.
|
|
807
|
+
// What the phone leaves in the cloud is done through these same handlers
|
|
808
|
+
// (main/phone-inbox.mjs), so each one is filed in a Map as Electron gets it.
|
|
712
809
|
const phoneChannels = new Map();
|
|
713
|
-
const phoneListeners = new Set();
|
|
714
810
|
const filingIpcMain = {
|
|
715
|
-
handle(channel, fn) { ipcMain.handle(channel, fn); phoneChannels.set(channel, fn); },
|
|
811
|
+
handle(channel, fn) { ipcMain.handle(channel, stalls.time(channel, fn)); phoneChannels.set(channel, fn); },
|
|
716
812
|
};
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
for
|
|
722
|
-
|
|
723
|
-
|
|
813
|
+
// `readClipboardText` is read only by the cloud setup, and only for a
|
|
814
|
+
// Claude sign-in code (main/ipc.mjs `zero:cloud-clipboard-code`).
|
|
815
|
+
const filingHost = {
|
|
816
|
+
ipcMain: filingIpcMain, app, dialog, readClipboardText: () => clipboard.readText(),
|
|
817
|
+
// A voice note asks for the microphone on the press of its button.
|
|
818
|
+
micAccess: () => (process.platform === 'darwin'
|
|
819
|
+
? getMic({ systemPreferences, openExternal: (url) => shell.openExternal(url) })
|
|
820
|
+
: 'granted'),
|
|
724
821
|
};
|
|
725
822
|
const ipc = registerIpc({
|
|
726
823
|
store, supervisor, config, window, analytics, docGrants, updater,
|
|
@@ -732,38 +829,17 @@ async function createWindow() {
|
|
|
732
829
|
// See the `installMenu` call above for what writing the file instead did to a
|
|
733
830
|
// Codex card.
|
|
734
831
|
answerApproval = ipc.answerApproval;
|
|
735
|
-
// What the window answers itself (badge, boot info)
|
|
736
|
-
//
|
|
832
|
+
// What the window answers itself (badge, boot info) gets the plain stand-ins
|
|
833
|
+
// a browser tab gets.
|
|
737
834
|
answerWhatOnlyTheWindowAnswered({ handle: (channel, fn) => { if (!phoneChannels.has(channel)) phoneChannels.set(channel, fn); } });
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
listeners: phoneListeners,
|
|
742
|
-
dist: path.join(appDir, 'renderer', 'dist'),
|
|
743
|
-
// Pictures and design pages, under the same two rules the window's own
|
|
744
|
-
// schemes use below: a page the pane was granted, and a picture inside a
|
|
745
|
-
// root the app owns (main/serve.mjs answerFile).
|
|
746
|
-
files: { allows: (kind, f) => (kind === 'doc' ? docGrants.allows(f) : servable(f, imageRoots())) },
|
|
747
|
-
// "Anywhere": the relay a phone reaches this Mac through from any network
|
|
748
|
-
// (main/relay-client.mjs). None until it is deployed, and then no choice.
|
|
749
|
-
relayUrl: relayAddress({ appDir }),
|
|
750
|
-
onChange: () => { if (!window.isDestroyed()) sendToWindow('zero:phone-changed'); },
|
|
751
|
-
// The phone stays quiet while she is at the desktop app, by the same rule
|
|
752
|
-
// the desktop's own notifications use (shared/notify-rules.mjs).
|
|
753
|
-
atDesk: () => {
|
|
754
|
-
try { return atTheApp({ focused: !window.isDestroyed() && window.isFocused(), idleMs: powerMonitor.getSystemIdleTime() * 1000 }); } catch { return false; }
|
|
755
|
-
},
|
|
756
|
-
});
|
|
757
|
-
window.on('focus', () => phone.seen());
|
|
758
|
-
// Settings' own channel, on the window only: a phone does not get to turn
|
|
759
|
-
// the phone door off or hand out its key.
|
|
760
|
-
ipcMain.handle('zero:phone-link', (_e, payload) => phone.act(payload ?? {}));
|
|
761
|
-
app.on('before-quit', () => phone.close());
|
|
835
|
+
phoneApply = applyThrough(phoneChannels);
|
|
836
|
+
// Every minute, so no engine search: that is a login shell on this thread.
|
|
837
|
+
phoneSettings = () => phoneChannels.get('zero:settings-read')?.({ sender: null }, { search: false }) ?? null;
|
|
762
838
|
updater.start();
|
|
763
839
|
// A task the app just shipped moved main: look now rather than in half an
|
|
764
840
|
// hour, so the restart is offered while she still remembers the task.
|
|
765
841
|
supervisor.onShipped = () => { try { updater.check?.(); } catch {} };
|
|
766
|
-
app
|
|
842
|
+
quitStep(app, 'before-quit', 'updater.stop', () => updater.stop());
|
|
767
843
|
|
|
768
844
|
// Cmd+Y / Cmd+N answer the oldest approval card from ANYWHERE, including
|
|
769
845
|
// while a child web contents owns the keyboard (menu accelerators turned out
|
|
@@ -931,7 +1007,6 @@ async function createWindow() {
|
|
|
931
1007
|
// Unread badge + notification on new inbox arrivals are driven by the
|
|
932
1008
|
// renderer (it owns the definition of "inbox"), through the badge API.
|
|
933
1009
|
ipcMain.handle('zero:badge', (_e, count) => {
|
|
934
|
-
phone.count(count);
|
|
935
1010
|
if (process.platform === 'darwin') app.dock.setBadge(count > 0 ? String(count) : '');
|
|
936
1011
|
});
|
|
937
1012
|
// WHETHER to speak, and what to say, is not the renderer's call: only this
|
|
@@ -940,13 +1015,23 @@ async function createWindow() {
|
|
|
940
1015
|
// a locked screen all read the same from in there). The renderer says what
|
|
941
1016
|
// arrived; main/notify.mjs decides. tell me when I am in another app, never
|
|
942
1017
|
// while I am in Agentbox.
|
|
943
|
-
|
|
1018
|
+
// A banner and the tray never disagree: whatever a banner told you about
|
|
1019
|
+
// while you were away is in the tray until you are back (w-93b3a1a796).
|
|
1020
|
+
let cornerTag = null;
|
|
1021
|
+
const notifier = installNotifier({
|
|
1022
|
+
app, window, Notification, powerMonitor, ipcMain, nativeImage,
|
|
1023
|
+
onArrivals: (list, needsYou) => team?.phoneArrivals(list, needsYou),
|
|
1024
|
+
onChange: () => cornerTag?.refresh(),
|
|
1025
|
+
});
|
|
944
1026
|
// THE CORNER TAG (w-dafae58a23): what is ready for her, floating over
|
|
945
1027
|
// whatever app she is in. Its page is built beside the app's own, and comes
|
|
946
1028
|
// from the dev server only when the app's own page does.
|
|
947
|
-
createCornerTag({
|
|
1029
|
+
cornerTag = createCornerTag({
|
|
948
1030
|
BrowserWindow, screen: electronScreen, ipcMain, Menu, powerMonitor, config, saveConfig, mainWindow: window,
|
|
949
1031
|
preload: path.join(appDir, 'main', 'corner-tag-preload.cjs'),
|
|
1032
|
+
glass: loadLiquidGlass(),
|
|
1033
|
+
told: () => notifier.told(),
|
|
1034
|
+
count: (name, props) => analytics.track(name, props),
|
|
950
1035
|
// `part` is the tag itself or the list beside it, one page drawn twice.
|
|
951
1036
|
load: (tagWindow, part) => {
|
|
952
1037
|
const devTag = process.env.ZERO_DEV_URL;
|
|
@@ -971,6 +1056,7 @@ async function createWindow() {
|
|
|
971
1056
|
// itself (it has no disk and should not have one), and 'render-process-gone'
|
|
972
1057
|
// only fires when the process DIES, which a thrown React error does not.
|
|
973
1058
|
ipcMain.handle('zero:crash', (_e, payload) => { reportFromRenderer(payload ?? {}); });
|
|
1059
|
+
ipcMain.handle('zero:take-join', () => { const token = pendingJoin; pendingJoin = null; return token; });
|
|
974
1060
|
// External links from markdown open in the real browser; local artifacts
|
|
975
1061
|
// (a demo page, a screenshot a worker saved) open in their default app, so
|
|
976
1062
|
// "see it working" is one click from the message.
|
|
@@ -980,18 +1066,14 @@ async function createWindow() {
|
|
|
980
1066
|
return { action: 'deny' };
|
|
981
1067
|
});
|
|
982
1068
|
|
|
983
|
-
//
|
|
1069
|
+
// WHAT AN EMBEDDED PAGE MAY DO IS DECIDED HERE, NOT ON THE TAG THAT ASKED.
|
|
984
1070
|
//
|
|
985
|
-
//
|
|
986
|
-
//
|
|
987
|
-
//
|
|
988
|
-
//
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
// works as before. Every webview this window attaches gets it, the junk
|
|
992
|
-
// browser included, which is what "never" asks for.
|
|
993
|
-
window.webContents.on('will-attach-webview', (_e, webPreferences) => {
|
|
994
|
-
webPreferences.autoplayPolicy = 'document-user-activation-required';
|
|
1071
|
+
// The pane draws a website now, which is the first code in this window that
|
|
1072
|
+
// nobody here wrote, so every webview is pinned below the tag: no preload,
|
|
1073
|
+
// sandboxed, context-isolated, node off, web security on, and no sound
|
|
1074
|
+
// before a click. main/webview-guard.mjs is the rule and the reasons.
|
|
1075
|
+
window.webContents.on('will-attach-webview', (_e, webPreferences, params) => {
|
|
1076
|
+
hardenAttachedWebview(webPreferences, params);
|
|
995
1077
|
});
|
|
996
1078
|
|
|
997
1079
|
// A page in the pane hides its scrollbar until you scroll, like the app's own
|
|
@@ -1007,10 +1089,34 @@ async function createWindow() {
|
|
|
1007
1089
|
// focus and would otherwise swallow the key. Intercept it below the page.
|
|
1008
1090
|
app.on('web-contents-created', (_event, contents) => {
|
|
1009
1091
|
if (contents.getType() !== 'webview') return;
|
|
1092
|
+
|
|
1093
|
+
// A LINK IN A WEBSITE THAT WANTS A NEW WINDOW GOES TO YOUR OWN BROWSER.
|
|
1094
|
+
// The pane is one page at a time; a second Electron window would be a
|
|
1095
|
+
// browser we have not designed, and `allowpopups` is off anyway
|
|
1096
|
+
// (webview-guard.mjs), so without this such a link does nothing at all.
|
|
1097
|
+
contents.setWindowOpenHandler(({ url }) => {
|
|
1098
|
+
if (/^https?:/.test(url)) shell.openExternal(url);
|
|
1099
|
+
return { action: 'deny' };
|
|
1100
|
+
});
|
|
1101
|
+
|
|
1010
1102
|
contents.on('before-input-event', (e, input) => {
|
|
1103
|
+
// ESCAPE HAS TO REACH THE APP FROM INSIDE A PAGE, and a webview is not
|
|
1104
|
+
// a frame of this window, so the handler above never hears it. It comes
|
|
1105
|
+
// up the same channel a frame's does, because the renderer answers that
|
|
1106
|
+
// one and decides there what escape means (whatTheFileSentUp). It used
|
|
1107
|
+
// to be sent on `zero:escape-browser`, the junk browser's own channel,
|
|
1108
|
+
// which no renderer code has listened to since that pane went: the key
|
|
1109
|
+
// landed nowhere and the pane took two presses to close.
|
|
1011
1110
|
if (input.type === 'keyDown' && input.key === 'Escape' && window && !window.isDestroyed()) {
|
|
1012
|
-
|
|
1013
|
-
|
|
1111
|
+
// The keyboard is left where it is, exactly as it is for a frame: the
|
|
1112
|
+
// renderer blurs the page before the pane goes. Taking focus back
|
|
1113
|
+
// here first would move `document.activeElement` off the webview, and
|
|
1114
|
+
// the renderer's own check of the same fact would then refuse the key
|
|
1115
|
+
// that this just sent.
|
|
1116
|
+
window.webContents.send('zero:key-in-the-file', {
|
|
1117
|
+
key: input.key, meta: !!input.meta, ctrl: !!input.control, alt: !!input.alt, shift: !!input.shift,
|
|
1118
|
+
fromGuest: true,
|
|
1119
|
+
});
|
|
1014
1120
|
return;
|
|
1015
1121
|
}
|
|
1016
1122
|
// The guest page owns the keyboard while she is in the junk browser, so
|
|
@@ -1093,7 +1199,9 @@ async function createWindow() {
|
|
|
1093
1199
|
} catch (err) {
|
|
1094
1200
|
console.error('screenshot failed:', err.message);
|
|
1095
1201
|
}
|
|
1096
|
-
|
|
1202
|
+
// The picture is taken, so the run is over whether the quit works or
|
|
1203
|
+
// not. On Linux it did not, and the shell's `timeout` ended these runs.
|
|
1204
|
+
quitThenExit(app, { what: 'the screenshot run' });
|
|
1097
1205
|
}, Number(process.env.ZERO_SCREENSHOT_DELAY_MS ?? 2500));
|
|
1098
1206
|
}
|
|
1099
1207
|
|
|
@@ -1111,12 +1219,16 @@ async function createWindow() {
|
|
|
1111
1219
|
if (!app.requestSingleInstanceLock()) {
|
|
1112
1220
|
app.quit();
|
|
1113
1221
|
} else {
|
|
1114
|
-
app.on('second-instance', () => {
|
|
1222
|
+
app.on('second-instance', (_e, argv = []) => {
|
|
1115
1223
|
if (window) {
|
|
1116
1224
|
if (window.isMinimized()) window.restore();
|
|
1117
1225
|
window.focus();
|
|
1118
1226
|
}
|
|
1227
|
+
const link = argv.find((a) => String(a).startsWith(`${JOIN_SCHEME}://`));
|
|
1228
|
+
if (link) openJoinLink(link);
|
|
1119
1229
|
});
|
|
1230
|
+
// Stripe's last page links back on the app's scheme, which only needs the window.
|
|
1231
|
+
app.on('open-url', (e, url) => { e.preventDefault(); if (ourLink(url)) showWindow(); openJoinLink(url); });
|
|
1120
1232
|
app.whenReady().then(() => {
|
|
1121
1233
|
// A from-source Linux launch keeps the launcher command installed. The
|
|
1122
1234
|
// entry runs that command; this only refreshes where it points. A packaged
|
|
@@ -1132,6 +1244,9 @@ if (!app.requestSingleInstanceLock()) {
|
|
|
1132
1244
|
// THE ABOUT BOX SAYS AGENTBOX AND ITS OWN VERSION (w-db6f5e331e). Run from
|
|
1133
1245
|
// source it read the bundle's, which is Electron's: "Electron 43.0.0".
|
|
1134
1246
|
app.setAboutPanelOptions({ applicationName: NAME, applicationVersion: app.getVersion(), version: '' });
|
|
1247
|
+
// Only the installed app takes the invite scheme: run from source it would
|
|
1248
|
+
// point every link on the Mac at this checkout's Electron.
|
|
1249
|
+
if (app.isPackaged) app.setAsDefaultProtocolClient(JOIN_SCHEME);
|
|
1135
1250
|
// Serve a granted file, and nothing else. A page under this scheme is one
|
|
1136
1251
|
// of ours, but it is written by an agent and read by her, so the check is
|
|
1137
1252
|
// on every request rather than only on the one the pane asked for.
|
|
@@ -177,9 +177,20 @@ const programOf = (key) => key.split(' ')[0];
|
|
|
177
177
|
*/
|
|
178
178
|
const measured = (list) => (list ?? []).filter((o) => o !== '?');
|
|
179
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Light, however it was learned: `l` measured on its own, `c` only as part of
|
|
182
|
+
* a chain. Read the same everywhere, so `c` changes no verdict; it is there to
|
|
183
|
+
* be counted. Pinned by
|
|
184
|
+
* tests/the-record-says-whether-it-learned-light-from-a-chain.test.mjs.
|
|
185
|
+
*/
|
|
186
|
+
const isLight = (o) => o === 'l' || o === 'c';
|
|
187
|
+
|
|
180
188
|
/**
|
|
181
189
|
* The learned record. Each key and each program keeps its last ten
|
|
182
|
-
* observations as letters: l light,
|
|
190
|
+
* observations as letters: l light, c light but learned only inside a chain,
|
|
191
|
+
* m in between, h heavy, ? not measured. A build that predates `c` drops it on
|
|
192
|
+
* load, which costs those runs as evidence and holds a little more than it
|
|
193
|
+
* needs to; it cannot call anything light that is not.
|
|
183
194
|
* Both maps are kept in least-recently-seen-first order so the oldest falls off
|
|
184
195
|
* first when either passes `maxKeys`.
|
|
185
196
|
*/
|
|
@@ -221,6 +232,15 @@ export class CommandHistory {
|
|
|
221
232
|
if (keys.length > 1 || opaque) {
|
|
222
233
|
this._push(this.keys, chainKey(keys, opaque), obs);
|
|
223
234
|
if (obs !== 'l') return true;
|
|
235
|
+
// And only as a chain: the early return above means these records only
|
|
236
|
+
// ever hear about the chains that came out light, so they drift light.
|
|
237
|
+
// `c` marks that weaker evidence so it can be counted. It is read as `l`
|
|
238
|
+
// everywhere, so nothing is classified differently yet.
|
|
239
|
+
for (const key of keys) {
|
|
240
|
+
this._push(this.keys, key, 'c');
|
|
241
|
+
this._push(this.programs, programOf(key), 'c');
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
224
244
|
}
|
|
225
245
|
for (const key of keys) {
|
|
226
246
|
this._push(this.keys, key, obs);
|
|
@@ -238,11 +258,17 @@ export class CommandHistory {
|
|
|
238
258
|
if (v === 'heavy' || verdict === 'heavy') verdict = 'heavy';
|
|
239
259
|
else if (v === 'unknown') verdict = 'unknown';
|
|
240
260
|
};
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
261
|
+
// A command's own record outranks the records of its parts: it is the only
|
|
262
|
+
// reading that covered everything the command starts, and the parts drift
|
|
263
|
+
// light (see `record`). The key is coarser than the command, so an old
|
|
264
|
+
// heavy run also holds back cheap variants that share it; that is the
|
|
265
|
+
// price. A middling run only keeps the command out of the light lane, where
|
|
266
|
+
// `lightLane` of them would start together, and `_holdsSlot` hands the slot
|
|
267
|
+
// back once a run measures small.
|
|
268
|
+
const own = measured(this.keys.get(keys.length > 1 || opaque ? chainKey(keys, opaque) : keys[0]));
|
|
269
|
+
if (own.includes('h')) return 'heavy';
|
|
270
|
+
if (own.includes('m')) worse('unknown');
|
|
271
|
+
if (opaque) worse('unknown');
|
|
246
272
|
for (const key of keys) worse(this._classifyKey(key));
|
|
247
273
|
return verdict;
|
|
248
274
|
}
|
|
@@ -251,10 +277,10 @@ export class CommandHistory {
|
|
|
251
277
|
const own = this.keys.get(key);
|
|
252
278
|
const recent = measured(own?.slice(-5));
|
|
253
279
|
if (recent.includes('h')) return 'heavy';
|
|
254
|
-
if (recent.length >= 2 && recent.every(
|
|
255
|
-
if (recent.some((o) => o
|
|
280
|
+
if (recent.length >= 2 && recent.every(isLight)) return 'light';
|
|
281
|
+
if (recent.some((o) => !isLight(o))) return 'unknown';
|
|
256
282
|
const prog = measured(this.programs.get(programOf(key))?.slice(-10));
|
|
257
|
-
if (prog.length >= 5 && prog.every(
|
|
283
|
+
if (prog.length >= 5 && prog.every(isLight)) return 'light';
|
|
258
284
|
return 'unknown';
|
|
259
285
|
}
|
|
260
286
|
|
|
@@ -278,7 +304,7 @@ export class CommandHistory {
|
|
|
278
304
|
if (!Array.isArray(entries)) return;
|
|
279
305
|
for (const e of entries) {
|
|
280
306
|
if (Array.isArray(e) && typeof e[0] === 'string' && Array.isArray(e[1])) {
|
|
281
|
-
map.set(e[0], e[1].filter((o) => ['l', 'm', 'h', '?'].includes(o)).slice(-10));
|
|
307
|
+
map.set(e[0], e[1].filter((o) => ['l', 'c', 'm', 'h', '?'].includes(o)).slice(-10));
|
|
282
308
|
}
|
|
283
309
|
}
|
|
284
310
|
};
|