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/ipc.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { openSourceFile } from './open-source-file.mjs';
|
|
2
2
|
import {submitReply} from './live-replies.mjs';
|
|
3
|
+
import { whyItCannotMove } from '../shared/move-thread.mjs';
|
|
3
4
|
import { createEngineSetup } from './engine-setup.mjs';
|
|
4
5
|
// IPC: a thin, typed-by-convention surface. The renderer asks; the main
|
|
5
6
|
// process derives from files and answers. Pushes go one way: "state changed,
|
|
@@ -20,13 +21,15 @@ import { createEngineSetup } from './engine-setup.mjs';
|
|
|
20
21
|
// process can hand over its own, and the same 78 channels then answer a
|
|
21
22
|
// browser over http without one line of this file being written twice.
|
|
22
23
|
import {terminalSenderAllowed} from './terminal-access.mjs';
|
|
23
|
-
import {TaskTerminals,terminalPlace} from './task-terminals.mjs';
|
|
24
|
+
import {TaskTerminals,terminalPlace,terminalEnv} from './task-terminals.mjs';
|
|
25
|
+
import {TerminalHosts,taskClosed} from './terminal-hosts.mjs';
|
|
24
26
|
import {SETTINGS_TERMINAL} from '../shared/settings-terminal.mjs';
|
|
25
27
|
import {AgentUpdates,installedAgent} from './agent-updates.mjs';
|
|
26
28
|
import {refreshAgentModels} from './refresh-agent-models.mjs';
|
|
27
29
|
import {readInstruction,writeInstruction,listVersions,readVersion} from './instruction-settings.mjs';
|
|
28
30
|
import path from 'node:path';
|
|
29
31
|
import os from 'node:os';
|
|
32
|
+
import { looksLikeClaudeCode } from '../shared/claude-code-shape.mjs';
|
|
30
33
|
import fsWatch from 'node:fs';
|
|
31
34
|
import { listFolders } from './folders.mjs';
|
|
32
35
|
import { feedbackUrl, sendFeedback } from './feedback.mjs';
|
|
@@ -40,16 +43,20 @@ import { readDoc, writeDoc } from './doc-file.mjs';
|
|
|
40
43
|
import { readChange } from './code-change.mjs';
|
|
41
44
|
import { findFile, readCodeFile, writeCodeFile } from './code-file.mjs';
|
|
42
45
|
import { readNote, writeNote } from './rail-note.mjs';
|
|
43
|
-
import {
|
|
46
|
+
import { teamForWindow } from './team/index.mjs';
|
|
47
|
+
import { addClaudeAccount, addCodexAccount, alphaFeatures, cornerTagTurnedOff, countsTurnedOff, lookAgainFor, outsideAgentsMode, permissionMode, pointAtEngineBin, readSettings, searchForEngineBin, setProjectSetting, setWorkspaceSetting } from './settings.mjs';
|
|
44
48
|
import { ICON_KINDS, clearProjectIcon, setProjectIcon, setProjectName } from './project-identity.mjs';
|
|
45
49
|
import { ClaudeUsage } from './claude-usage.mjs';
|
|
50
|
+
import { AccountUsage } from './account-usage.mjs';
|
|
51
|
+
import { effectiveProfiles } from './account-discovery.mjs';
|
|
46
52
|
import { usageEngine } from '../shared/usage.mjs';
|
|
47
53
|
import { DEFAULT_ENGINE, isEngine } from '../shared/engines.mjs';
|
|
48
54
|
import * as agents from './agents.mjs';
|
|
49
55
|
import { findAgentFolders, readAgentFiles, readFolderAgents } from './agent-files.mjs';
|
|
50
|
-
import {
|
|
56
|
+
import { readCodexThreadsOffThread } from './codex-threads.mjs';
|
|
51
57
|
import { appSessionFolders } from './agent-homes.mjs';
|
|
52
|
-
import { readImportableThreads } from './importable-threads.mjs';
|
|
58
|
+
import { readImportableThreads, readSearchableThreads } from './importable-threads.mjs';
|
|
59
|
+
import { readChatEnds } from './agent-sessions.mjs';
|
|
53
60
|
import { markImported } from '../shared/agent-import.mjs';
|
|
54
61
|
import { codexIdOf, importChoice, isCodexImportRow, isNotImportedRow } from '../shared/codex-import.mjs';
|
|
55
62
|
import { checkProjectFolder } from '../shared/project-folder-check.mjs';
|
|
@@ -61,6 +68,9 @@ import { machineryPath } from './store/home.mjs';
|
|
|
61
68
|
import { EVENT_NAMES } from '../shared/analytics-events.mjs';
|
|
62
69
|
import { Name } from '../shared/product-name.mjs';
|
|
63
70
|
import { handedOnByReply } from '../shared/team-rules.mjs';
|
|
71
|
+
import { closeRefusal, closedAnywayNote } from '../shared/not-shipped.mjs';
|
|
72
|
+
import { saveAttachments } from './save-attachments.mjs';
|
|
73
|
+
import { stageAttachment } from './stage-attachment.mjs';
|
|
64
74
|
|
|
65
75
|
// WHAT A WORKER IS TOLD WHEN HER DECISION COULD NOT BE RECORDED.
|
|
66
76
|
const SPOOL_REFUSED = `${Name} could not record the founder's decision, so nothing was allowed. Finish what your grants cover and file the rest as a review.`;
|
|
@@ -97,6 +107,9 @@ const NO_UPDATER = {
|
|
|
97
107
|
move: () => ({ moved: false, error: null }),
|
|
98
108
|
};
|
|
99
109
|
|
|
110
|
+
const TEAMMATE_OPS = new Set(['create', 'describe', 'start', 'update', 'remove', 'post', 'goAhead', 'remember', 'forget']);
|
|
111
|
+
const ROOM_OPS = new Set(['refresh', 'send', 'markRead', 'markUnread', 'react', 'edit', 'deleteMessage', 'putFile', 'file', 'older', 'saveProfile', 'goInvisible', 'comeBack', 'link', 'revokeLink', 'preview', 'listed', 'invitesSent', 'joinByLink', 'joinListed', 'acceptInvite', 'invite', 'create', 'rename', 'leave', 'remove', 'pin', 'mute']);
|
|
112
|
+
|
|
100
113
|
export function registerIpc({ store, supervisor, config, window, analytics = NO_ANALYTICS, docGrants = null, updater = NO_UPDATER, host, team = null }) {
|
|
101
114
|
// The three Electron things, or a plain Node stand-in for them. There is no
|
|
102
115
|
// default: a caller that forgets says so here rather than throwing eighty
|
|
@@ -167,8 +180,8 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
167
180
|
// THROW, out of an ipc handler and out of the ⌘Y `before-input-event`
|
|
168
181
|
// listener, which is worse than either outcome here. A Codex worker is parked
|
|
169
182
|
// on a promise in this process, so it is told the refusal directly and
|
|
170
|
-
// unblocks; a Claude Code worker has only the file, so it
|
|
171
|
-
//
|
|
183
|
+
// unblocks; a Claude Code worker has only the file, so it goes on waiting on
|
|
184
|
+
// its own card, which stays on screen and answerable. Nothing on this
|
|
172
185
|
// path can turn a failure into an allow, and the deny falls out of the SAME
|
|
173
186
|
// two calls the yes does rather than a second door beside them: `allow` and
|
|
174
187
|
// `note` stop meaning what she pressed the moment the write fails, and start
|
|
@@ -221,7 +234,10 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
221
234
|
try { return store.listProducts().some((p) => p.slug === slug && p.practice === true); } catch { return false; }
|
|
222
235
|
};
|
|
223
236
|
|
|
224
|
-
|
|
237
|
+
// From her phone the answer names the card it drew (main/phone-inbox.mjs),
|
|
238
|
+
// and a request rewritten since gets nothing.
|
|
239
|
+
ipcMain.handle('zero:approve', (_e, { id, allow, note, card }) => {
|
|
240
|
+
if (card !== undefined && !approvals.cardStillShown(config.storeRoot, id, card)) return { ok: false, error: 'the card changed after your phone showed it, so nothing was allowed' };
|
|
225
241
|
return { ok: answerApproval(id, allow, note) };
|
|
226
242
|
});
|
|
227
243
|
|
|
@@ -265,15 +281,42 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
265
281
|
bin: () => config.claudeBin,
|
|
266
282
|
// The account marked In use, asked each time so a switch is read at once.
|
|
267
283
|
account: () => supervisor.usageAccount(),
|
|
268
|
-
onChange:
|
|
284
|
+
onChange: () => {
|
|
285
|
+
const reading = usage.peek();
|
|
286
|
+
if (reading) supervisor.noteUsageReading(reading.profile, reading);
|
|
287
|
+
push();
|
|
288
|
+
},
|
|
269
289
|
});
|
|
270
290
|
|
|
271
|
-
ipcMain.handle('zero:refresh-usage', async event => {
|
|
291
|
+
ipcMain.handle('zero:refresh-usage', async (event, { engine = DEFAULT_ENGINE } = {}) => {
|
|
272
292
|
if (event.senderFrame?.parent || (event.sender && event.sender !== window.webContents)) throw Error('Usage refresh is only available in the main app window.');
|
|
293
|
+
// Codex is asked of this computer and not of OpenAI; main/codex-usage.mjs
|
|
294
|
+
// says why there is no explicit read.
|
|
295
|
+
if (engine !== DEFAULT_ENGINE) {
|
|
296
|
+
const reading = supervisor.refreshCodexUsage();
|
|
297
|
+
return reading ? { ok: true, reading: { engine, ...reading } } : { ok: false, error: 'Codex has reported no usage on this computer yet.' };
|
|
298
|
+
}
|
|
273
299
|
const result = await usage.refreshNow();
|
|
274
300
|
return result.reading ? { ...result, reading: { engine: DEFAULT_ENGINE, ...result.reading } } : result;
|
|
275
301
|
});
|
|
276
302
|
|
|
303
|
+
// Every Claude login's own reading, asked for only by the Settings page.
|
|
304
|
+
const accountUsage = new AccountUsage({
|
|
305
|
+
bin: () => config.claudeBin,
|
|
306
|
+
env: () => supervisor._workerEnv(DEFAULT_ENGINE),
|
|
307
|
+
onReading: (profile, reading) => supervisor.noteUsageReading(profile, reading),
|
|
308
|
+
});
|
|
309
|
+
ipcMain.handle('zero:account-usage', async (event, { refresh = false, engine = DEFAULT_ENGINE } = {}) => {
|
|
310
|
+
if (event.senderFrame?.parent || (event.sender && event.sender !== window.webContents)) throw Error('Usage is only available in the main app window.');
|
|
311
|
+
// Codex's are files on this computer, read on every ask.
|
|
312
|
+
if (engine === 'codex') return { readings: supervisor.codexAccountUsage() };
|
|
313
|
+
const profiles = effectiveProfiles(config.authProfiles, { home: config.home });
|
|
314
|
+
// The cloud's logins too; a look takes a sandbox session, so the page is
|
|
315
|
+
// not held for it and redraws off the snapshot when it lands.
|
|
316
|
+
void supervisor.lookAtCloudLogins({ force: !!refresh });
|
|
317
|
+
return { readings: await accountUsage.readAll(profiles.length ? profiles : ['default'], { refresh: !!refresh }) };
|
|
318
|
+
});
|
|
319
|
+
|
|
277
320
|
// Agents this run has already counted. In memory on purpose: a fresh launch
|
|
278
321
|
// seeing the same agent again is a fresh fact about the app being used.
|
|
279
322
|
const seenAgents = new Set();
|
|
@@ -334,12 +377,25 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
334
377
|
if(product===SETTINGS_TERMINAL.product&&id===SETTINGS_TERMINAL.id){
|
|
335
378
|
return pendingLogin ?? config.home ?? os.homedir();
|
|
336
379
|
}
|
|
380
|
+
return taskPlace(product,id);
|
|
381
|
+
}});
|
|
382
|
+
const taskPlace=(product,id)=>{
|
|
337
383
|
const agent=id.startsWith('agent:')?liveAgents().find(a=>'agent:'+a.pid===id && (a.product??'')===product):null;
|
|
338
384
|
return terminalPlace({store,supervisor,product,id,agent});
|
|
339
|
-
}
|
|
385
|
+
};
|
|
386
|
+
/*
|
|
387
|
+
* A TASK'S TERMINAL LIVES AS LONG AS THE TASK IS OPEN, in a process that
|
|
388
|
+
outlives the app (main/terminal-host.mjs). The sign-in terminal on
|
|
389
|
+
Settings belongs to no task and stays in the app. */
|
|
390
|
+
const taskTerminals=new TerminalHosts({local:terminals,logFile:path.join(supervisor.userDir,'terminal-host.log'),log:line=>console.warn(`zero: ${line}`)});
|
|
391
|
+
// A task's folder is not deleted while its terminal is open (supervisor.mjs, `_folderInUse`).
|
|
392
|
+
supervisor.terminalOpen=itemId=>taskTerminals.hasTerminal(itemId);
|
|
393
|
+
const lookAtTerminals=()=>taskTerminals.endClosed(taskClosed(store)).catch(e=>console.warn('zero: terminals:',e.message));
|
|
394
|
+
void taskTerminals.ready().then(lookAtTerminals);
|
|
395
|
+
setInterval(lookAtTerminals,60_000).unref?.();
|
|
340
396
|
const agentUpdates=new AgentUpdates({
|
|
341
|
-
installation:async engine=>{
|
|
342
|
-
refresh:async engine=>{
|
|
397
|
+
installation:async engine=>{await lookAgainFor(engine,config);return installedAgent(engine,config);},
|
|
398
|
+
refresh:async engine=>{await lookAgainFor(engine,config);await refreshAgentModels(engine,config,supervisor._codexOfferHomes());if(engine==='codex')for(const profile of supervisor._codexProfiles())supervisor._releaseIdleCodex(profile);},
|
|
343
399
|
});
|
|
344
400
|
ipcMain.handle('zero:agent-update',async(event,{engine,action}={})=>{
|
|
345
401
|
if(event.senderFrame?.parent||(event.sender&&event.sender!==window.webContents))throw Error('Updates are only available in the main app window.');
|
|
@@ -348,7 +404,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
348
404
|
if(action==='recheck')return agentUpdates.check(engine,true);
|
|
349
405
|
if(action==='start')return agentUpdates.start(engine);
|
|
350
406
|
if(action==='status')return agentUpdates.status(engine);
|
|
351
|
-
if(action==='refresh'){await refreshAgentModels(engine,config,supervisor.
|
|
407
|
+
if(action==='refresh'){await refreshAgentModels(engine,config,supervisor._codexOfferHomes());return true;}
|
|
352
408
|
throw Error('Unknown update action.');
|
|
353
409
|
});
|
|
354
410
|
let terminalQuitPending=false,terminalQuitReady=false;
|
|
@@ -357,13 +413,13 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
357
413
|
event.preventDefault();
|
|
358
414
|
if(!terminalQuitPending){terminalQuitPending=true;void Promise.all([terminals.shutdown(),agentUpdates.shutdown()]).finally(()=>{terminalQuitReady=true;app.quit();});}
|
|
359
415
|
});
|
|
360
|
-
ipcMain.handle('zero:terminal',(_event,payload={})=>{
|
|
416
|
+
ipcMain.handle('zero:terminal',async(_event,payload={})=>{
|
|
361
417
|
if(_event.senderFrame?.parent || (_event.sender && _event.sender!==window.webContents)) throw Error('Terminal is only available in the main app window.');
|
|
362
418
|
const {product,id,action,data,cols,rows,offset,wait}=payload;
|
|
363
419
|
if(typeof product!=='string'||typeof id!=='string'||!id||id.length>300)throw Error('Invalid task.');
|
|
364
420
|
const key=JSON.stringify({product,id});
|
|
365
421
|
if(product==='@agent-update')return agentUpdates.terminal(id,payload);
|
|
366
|
-
switch(action){
|
|
422
|
+
if(product===SETTINGS_TERMINAL.product&&id===SETTINGS_TERMINAL.id)switch(action){
|
|
367
423
|
case 'open':return terminals.open(key);
|
|
368
424
|
case 'read':return terminals.read(key,offset,wait);
|
|
369
425
|
case 'write':terminals.write(key,data);return true;
|
|
@@ -371,6 +427,14 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
371
427
|
case 'close':terminals.close(key);return true;
|
|
372
428
|
default:throw Error('Unknown terminal action.');
|
|
373
429
|
}
|
|
430
|
+
switch(action){
|
|
431
|
+
case 'open':return taskTerminals.open(key,{...taskPlace(product,id),env:terminalEnv()});
|
|
432
|
+
case 'read':return taskTerminals.read(key,offset,wait);
|
|
433
|
+
case 'write':await taskTerminals.write(key,data);return true;
|
|
434
|
+
case 'resize':await taskTerminals.resize(key,cols,rows);return true;
|
|
435
|
+
case 'close':await taskTerminals.close(key);return true;
|
|
436
|
+
default:throw Error('Unknown terminal action.');
|
|
437
|
+
}
|
|
374
438
|
});
|
|
375
439
|
|
|
376
440
|
// The Done and All tabs' next page of finished threads the snapshot's 8 MB
|
|
@@ -410,6 +474,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
410
474
|
// draw -- which is what a Codex-only workflow paid every five minutes until
|
|
411
475
|
// now. The Codex side never starts anything at all (main/codex-usage.mjs).
|
|
412
476
|
const reading = usageFor === DEFAULT_ENGINE ? usage.read() : supervisor.codexUsage();
|
|
477
|
+
const alpha = alphaFeatures(config);
|
|
413
478
|
return {
|
|
414
479
|
products: store.listProducts(),
|
|
415
480
|
// Each with the place she dragged it to, if she did (w-6e5b532a95).
|
|
@@ -417,7 +482,11 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
417
482
|
// THE TEAM: who is signed in, their team and its people, and the
|
|
418
483
|
// title-free activity of their private work. Null on a build with no
|
|
419
484
|
// team cloud, which is the single-person app.
|
|
420
|
-
team: team ? team.state() : null,
|
|
485
|
+
team: team ? teamForWindow(team.state(), alpha.multiplayer) : null,
|
|
486
|
+
// Which alpha features are on (Settings, Alpha features).
|
|
487
|
+
alpha,
|
|
488
|
+
// TEAMMATES: who they are, what each is on, and the one team chat.
|
|
489
|
+
teammates: alpha.teammates ? (() => { try { return store.teammatesView(items, now); } catch (err) { return { list: [], chat: [], runs: [], error: err.message }; } })() : null,
|
|
421
490
|
agents: liveAgents(),
|
|
422
491
|
approvals: approvals.listPending(config.storeRoot),
|
|
423
492
|
supervisor: running,
|
|
@@ -451,6 +520,9 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
451
520
|
// while fixes landed underneath it, and a boot-time check cannot see
|
|
452
521
|
// that. See main/staleness.mjs.
|
|
453
522
|
restartNeeded: restartNeeded(supervisor.appDir),
|
|
523
|
+
// WHERE THREADS RUN, for the cloud button beside the filters
|
|
524
|
+
// (renderer/src/cloud-menu.ts). `setUp` false draws no button at all.
|
|
525
|
+
cloud: supervisor.cloudStatus(),
|
|
454
526
|
// HER RATE LIMIT, WHICH IS WHAT ACTUALLY CAPS THE FLEET. Null until the
|
|
455
527
|
// first reading lands, and the corner draws nothing at all until then
|
|
456
528
|
// rather than a pill full of dashes.
|
|
@@ -590,8 +662,19 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
590
662
|
// answered at once (supervisor.sendNow).
|
|
591
663
|
ipcMain.handle('zero:send-now', (_e, { product, id }) => supervisor.sendNow(product, id));
|
|
592
664
|
|
|
593
|
-
ipcMain.handle('zero:answer', async (_e, { product, id, answer, status, priority, permissionMode, model, effort, now, inReplyTo }) => {
|
|
665
|
+
ipcMain.handle('zero:answer', async (_e, { product, id, answer, status, priority, permissionMode, engine, model, effort, now, inReplyTo, fromPhone, closeUnshipped }) => {
|
|
594
666
|
if (isAgentRow(id)) return { ok: false };
|
|
667
|
+
// WORK THAT NEVER REACHED MAIN IS NOT CLOSED UNASKED (shared/not-shipped.mjs).
|
|
668
|
+
// The window asks first and says so with `closeUnshipped`; the phone and
|
|
669
|
+
// anything else are turned down with the reason.
|
|
670
|
+
if (status === 'done' && product && id) {
|
|
671
|
+
let row = null;
|
|
672
|
+
try { row = store.readItem(product, id); } catch { row = null; }
|
|
673
|
+
const refused = closeRefusal(row, { closeUnshipped });
|
|
674
|
+
if (refused) return refused;
|
|
675
|
+
const said = closeUnshipped ? closedAnywayNote(row) : null;
|
|
676
|
+
if (said) store.noteItem(product, id, said);
|
|
677
|
+
}
|
|
595
678
|
// A level picked from ⌘K arrives here with no words, and gives way the
|
|
596
679
|
// same as one picked on the summary (zero:thread-edit). A reply that
|
|
597
680
|
// happens to carry its row's level does not.
|
|
@@ -618,7 +701,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
618
701
|
if (choice === 'yes') {
|
|
619
702
|
try {
|
|
620
703
|
const { codexDirs } = appSessionFolders({ config, supervisor });
|
|
621
|
-
thread =
|
|
704
|
+
thread = (await readCodexThreadsOffThread({ codexDirs })).threads.find((t) => t.id === codexIdOf(row)) ?? null;
|
|
622
705
|
} catch { thread = null; }
|
|
623
706
|
}
|
|
624
707
|
const out = store.answerCodexImport(product, id, choice, { thread });
|
|
@@ -637,7 +720,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
637
720
|
// handing them over in one call and writing the model afterwards would be a
|
|
638
721
|
// race the run usually wins, so she would pick a model and watch the old one
|
|
639
722
|
// take the task. `answerItem` writes both, and it writes the model first.
|
|
640
|
-
const out = await submitReply(supervisor,{product,id,answer,status,permissionMode,now:!!now},()=>store.answerItem(product, id, { answer, status, priority, permissionMode, model, effort, inReplyTo: thread }));
|
|
723
|
+
const out = await submitReply(supervisor,{product,id,answer,status,permissionMode,now:!!now},()=>store.answerItem(product, id, { answer, status, priority, permissionMode, engine, model, effort, inReplyTo: thread, fromPhone }));
|
|
641
724
|
// A REPLY ON A ROW A TEAMMATE GAVE YOU HANDS IT BACK TO THEM, so the
|
|
642
725
|
// conversation moves to their inbox instead of sitting in both
|
|
643
726
|
// (shared/team-rules.mjs handedOnByReply). Archiving hands nothing on.
|
|
@@ -673,6 +756,13 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
673
756
|
return { ok: true };
|
|
674
757
|
});
|
|
675
758
|
|
|
759
|
+
// THREADS KEPT ON THIS MAC whatever the cloud is set to: id to true, or null.
|
|
760
|
+
ipcMain.handle('zero:set-thread-on-mac', (_e, { onMac }) => {
|
|
761
|
+
supervisor.setThreadOnMac(onMac);
|
|
762
|
+
push();
|
|
763
|
+
return { ok: true };
|
|
764
|
+
});
|
|
765
|
+
|
|
676
766
|
// PINNED THREADS (w-20323283e6): id to true, or null to unpin.
|
|
677
767
|
ipcMain.handle('zero:set-thread-pins', (_e, { pins }) => {
|
|
678
768
|
supervisor.setThreadPins(pins);
|
|
@@ -712,14 +802,18 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
712
802
|
// THE TEAM'S DOORS. Each answers { ok, team } or { ok: false, error } with
|
|
713
803
|
// the error in words, so the window can say what went wrong where it
|
|
714
804
|
// happened instead of a row silently not appearing.
|
|
805
|
+
const teamNow = () => teamForWindow(team.state(), alphaFeatures(config).multiplayer);
|
|
715
806
|
const teamCall = (fn) => async (_e, arg) => {
|
|
716
807
|
if (!team) return { ok: false, error: 'This build has no team cloud set up.' };
|
|
717
808
|
try {
|
|
718
|
-
await fn(arg ?? {});
|
|
809
|
+
const got = await fn(arg ?? {});
|
|
719
810
|
push();
|
|
720
|
-
|
|
811
|
+
// A call that wrote a row (a message, into its conversation) says which,
|
|
812
|
+
// so the toast after it can open it with O (w-5513afd09a).
|
|
813
|
+
const wrote = typeof got?.id === 'string' && typeof got?.product === 'string';
|
|
814
|
+
return { ok: true, team: teamNow(), ...(wrote ? { made: { product: got.product, id: got.id } } : {}) };
|
|
721
815
|
} catch (err) {
|
|
722
|
-
return { ok: false, error: String(err?.message ?? err), team:
|
|
816
|
+
return { ok: false, error: String(err?.message ?? err), team: teamNow() };
|
|
723
817
|
}
|
|
724
818
|
};
|
|
725
819
|
// Signing in finishes in the browser, so the app comes back to the front
|
|
@@ -739,9 +833,9 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
739
833
|
try {
|
|
740
834
|
const out = await team.signUp(email, password, name);
|
|
741
835
|
push();
|
|
742
|
-
return { ok: true, team:
|
|
836
|
+
return { ok: true, team: teamNow(), confirm: !!out?.confirm };
|
|
743
837
|
} catch (err) {
|
|
744
|
-
return { ok: false, error: String(err?.message ?? err), team:
|
|
838
|
+
return { ok: false, error: String(err?.message ?? err), team: teamNow() };
|
|
745
839
|
}
|
|
746
840
|
});
|
|
747
841
|
ipcMain.handle('zero:team-create', teamCall(({ name }) => team.createTeam(name)));
|
|
@@ -759,7 +853,46 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
759
853
|
ipcMain.handle('zero:team-sync', teamCall(() => team.syncNow()));
|
|
760
854
|
// A MESSAGE TO A PERSON (approved 2026-10-01: people get messages, never
|
|
761
855
|
// tasks). main/team/index.mjs keeps it between the two of them.
|
|
762
|
-
ipcMain.handle('zero:team-message', teamCall(({ to, body, priority }) => team.message(to, body, priority)));
|
|
856
|
+
ipcMain.handle('zero:team-message', teamCall(({ to, body, priority, files }) => team.message(to, body, priority, files)));
|
|
857
|
+
// ROOMS (alpha): one door, and only these calls through it (main/team/rooms.mjs).
|
|
858
|
+
ipcMain.handle('zero:teammates', async (_e, { op, args = {} } = {}) => {
|
|
859
|
+
const mates = store.teammates;
|
|
860
|
+
if (!mates) return { ok: false, error: 'Teammates need a store.' };
|
|
861
|
+
if (!TEAMMATE_OPS.has(op)) return { ok: false, error: `no teammates call named ${op}` };
|
|
862
|
+
if (!alphaFeatures(config).teammates) return { ok: false, error: 'Teammates are an alpha feature. Turn them on in Settings, Alpha features.' };
|
|
863
|
+
try {
|
|
864
|
+
const a = args && typeof args === 'object' ? args : {};
|
|
865
|
+
let value;
|
|
866
|
+
if (op === 'create') {
|
|
867
|
+
value = mates.create({ ...a, engine: supervisor.engineOffered(a.engine) ?? null });
|
|
868
|
+
if (a.start !== false) value = mates.start(value.id);
|
|
869
|
+
} else if (op === 'describe') value = mates.describe({ ...a, engine: supervisor.engineOffered(a.engine) ?? null });
|
|
870
|
+
else if (op === 'start') value = mates.start(a.id);
|
|
871
|
+
else if (op === 'update') value = mates.update(a.id, { ...a.patch, ...(a.patch && 'engine' in a.patch ? { engine: supervisor.engineOffered(a.patch.engine) ?? null } : {}) });
|
|
872
|
+
else if (op === 'remove') value = mates.remove(a.id);
|
|
873
|
+
else if (op === 'post') value = mates.post({ text: a.text, to: a.to, fresh: a.fresh === true });
|
|
874
|
+
else if (op === 'goAhead') value = mates.goAhead(a.id, a.ref);
|
|
875
|
+
else if (op === 'remember') value = mates.remember(a.id, a.text, { from: 'you' });
|
|
876
|
+
else if (op === 'forget') value = mates.forget(a.id, a.memory);
|
|
877
|
+
supervisor.wake?.();
|
|
878
|
+
push();
|
|
879
|
+
return { ok: true, value: value ?? null };
|
|
880
|
+
} catch (err) {
|
|
881
|
+
return { ok: false, error: String(err?.message ?? err) };
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
ipcMain.handle('zero:rooms', async (_e, { op, args = [] } = {}) => {
|
|
885
|
+
const rooms = team?.rooms;
|
|
886
|
+
if (!rooms) return { ok: false, error: 'This build has no team cloud set up.' };
|
|
887
|
+
if (!ROOM_OPS.has(op)) return { ok: false, error: `no room call named ${op}` };
|
|
888
|
+
try {
|
|
889
|
+
const value = await rooms[op](...(Array.isArray(args) ? args : [args]));
|
|
890
|
+
push();
|
|
891
|
+
return { ok: true, value: value ?? null, rooms: rooms.state() };
|
|
892
|
+
} catch (err) {
|
|
893
|
+
return { ok: false, error: String(err?.message ?? err), rooms: rooms.state() };
|
|
894
|
+
}
|
|
895
|
+
});
|
|
763
896
|
// AN EDIT TO A THREAD, MADE IN PLACE from its summary: the summary's lines,
|
|
764
897
|
// who sees it, its priority and what it is linked to. Works with or without a
|
|
765
898
|
// team, since every thread has a summary.
|
|
@@ -775,6 +908,26 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
775
908
|
return { ok: false, error: String(err?.message ?? err) };
|
|
776
909
|
}
|
|
777
910
|
});
|
|
911
|
+
// MOVE A THREAD TO ANOTHER PROJECT (w-c9da359466). Only a thread nothing has
|
|
912
|
+
// ever run on: shared/move-thread.mjs says why, and the store asks again.
|
|
913
|
+
// The one fact only this side has is whether a worker is inside it right
|
|
914
|
+
// now, which no field on the row can stand in for.
|
|
915
|
+
ipcMain.handle('zero:move-item', (_e, { product, id, to }) => {
|
|
916
|
+
try {
|
|
917
|
+
const live = supervisor.status().running.some((r) => r.itemId === id);
|
|
918
|
+
if (live) {
|
|
919
|
+
const why = whyItCannotMove(store.readItem(product, id), { live: true });
|
|
920
|
+
return { ok: false, error: why };
|
|
921
|
+
}
|
|
922
|
+
const out = store.moveItem(product, id, to);
|
|
923
|
+
push();
|
|
924
|
+
supervisor.wake();
|
|
925
|
+
return { ok: true, ...out };
|
|
926
|
+
} catch (err) {
|
|
927
|
+
return { ok: false, error: String(err?.message ?? err) };
|
|
928
|
+
}
|
|
929
|
+
});
|
|
930
|
+
|
|
778
931
|
// A TASK A TEAMMATE GAVE YOU, ROUTED WITH ONE KEY. To an agent: it runs on
|
|
779
932
|
// your Mac (you become its runner). Keep it: it stays yours and the routing
|
|
780
933
|
// choices go away. Hand it back: it returns to whoever gave it to you.
|
|
@@ -810,7 +963,24 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
810
963
|
await team.syncNow();
|
|
811
964
|
}));
|
|
812
965
|
|
|
813
|
-
|
|
966
|
+
// 'granted', or 'settings' once macOS's Microphone page has been opened instead.
|
|
967
|
+
ipcMain.handle('zero:mic-access', async () => {
|
|
968
|
+
try { return host.micAccess ? await host.micAccess() : 'granted'; } catch { return 'settings'; }
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
// EDIT OR DELETE A MESSAGE YOU SENT TO A TEAMMATE, in a conversation only,
|
|
972
|
+
// for the same reason as a reaction above.
|
|
973
|
+
ipcMain.handle('zero:team-edit', teamCall(async ({ product, id, on, text, gone }) => {
|
|
974
|
+
if (!teamMe()) throw new Error('sign in first');
|
|
975
|
+
if (!store.readItem(product, id)) throw new Error('that conversation is gone');
|
|
976
|
+
const here = store.listProducts().find((p) => p.slug === product);
|
|
977
|
+
if (!here?.team?.direct) throw new Error('only a message to a teammate can be edited');
|
|
978
|
+
if (!gone && !String(text ?? '').trim()) throw new Error('a message needs words; delete it instead');
|
|
979
|
+
store.editMessage(product, id, { on, text, gone: gone === true });
|
|
980
|
+
await team.syncNow();
|
|
981
|
+
}));
|
|
982
|
+
|
|
983
|
+
ipcMain.handle('zero:compose', (_e, { product, title, body, kind, priority, runAt, start, labels, engine, model, effort, assignee, due, visibility, visibleTo, fromPhone }) => {
|
|
814
984
|
const out = store.composeItem(product, {
|
|
815
985
|
// HOW HARD IT THINKS rides through unjudged: the store keeps any word
|
|
816
986
|
// shaped like a level, and the spawn is the gate that knows each
|
|
@@ -824,6 +994,8 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
824
994
|
// AND WHO MAY SEE IT, when she chose people rather than the team
|
|
825
995
|
// (w-41ff964775). The store keeps the list only beside 'people'.
|
|
826
996
|
visibleTo,
|
|
997
|
+
// The phone command that asked for it (main/phone-inbox.mjs).
|
|
998
|
+
fromPhone,
|
|
827
999
|
people: assignee && teamMe() ? [teamMe(), assignee] : undefined,
|
|
828
1000
|
// WHICH CODING AGENT SHE CHOSE, AND ONLY IF SHE COULD HAVE. `engineOffered`
|
|
829
1001
|
// is the supervisor's own test and the door makes no judgement of its own:
|
|
@@ -1041,7 +1213,10 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1041
1213
|
// is the sentence, which the screens show as it stands.
|
|
1042
1214
|
ipcMain.handle('zero:choose-folder', async (_e, { startIn } = {}) => {
|
|
1043
1215
|
const r = await dialog.showOpenDialog(window, {
|
|
1044
|
-
title
|
|
1216
|
+
// The title is what the screen behind it asks. On Linux the chooser
|
|
1217
|
+
// draws one, and it was still asking "Where is its code?", the heading
|
|
1218
|
+
// that screen gave up in w-ec62ab6b38.
|
|
1219
|
+
title: 'Pick a project folder',
|
|
1045
1220
|
buttonLabel: 'Use this folder',
|
|
1046
1221
|
properties: ['openDirectory', 'createDirectory'],
|
|
1047
1222
|
...(startIn ? { defaultPath: startIn } : {}),
|
|
@@ -1165,9 +1340,9 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1165
1340
|
// EVERY FOLDER EITHER TOOL KEEPS SESSIONS IN (main/agent-homes.mjs), not only
|
|
1166
1341
|
// ~/.claude and ~/.codex, which is where the import used to look and nothing
|
|
1167
1342
|
// else (w-e46f2e9cea).
|
|
1168
|
-
const readAllThreads = () => readImportableThreads({ config, supervisor });
|
|
1169
|
-
ipcMain.handle('zero:agent-threads', () => {
|
|
1170
|
-
const threads = readAllThreads();
|
|
1343
|
+
const readAllThreads = () => readImportableThreads({ config, supervisor, readCodex: readCodexThreadsOffThread });
|
|
1344
|
+
ipcMain.handle('zero:agent-threads', async () => {
|
|
1345
|
+
const threads = await readAllThreads();
|
|
1171
1346
|
lastThreads = threads;
|
|
1172
1347
|
// Each one already in the inbox says so, and the card leaves it out
|
|
1173
1348
|
// (w-db6f5e331e). The walk's folder suggestions still read all of them.
|
|
@@ -1176,12 +1351,48 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1176
1351
|
return { threads: markImported(threads, items) };
|
|
1177
1352
|
});
|
|
1178
1353
|
|
|
1354
|
+
/**
|
|
1355
|
+
* EVERY CHAT THE NAME FIELD CAN SEARCH, read once and kept (w-6418318cd5).
|
|
1356
|
+
*
|
|
1357
|
+
* The card's own list is ten days. Typing a name has to reach past that, and
|
|
1358
|
+
* a name lives inside a transcript, so finding one means opening files —
|
|
1359
|
+
* 29,703 of them on the Mac this was written on. It is taken ONCE, the first
|
|
1360
|
+
* time somebody types, and held for the life of the window: every keystroke
|
|
1361
|
+
* after the first is a filter in the renderer, not a disk walk. `Look again`
|
|
1362
|
+
* on the card drops it, which is the one way back to the disk.
|
|
1363
|
+
*
|
|
1364
|
+
* `reached` is the moment the read gave up at, zero when it read the whole
|
|
1365
|
+
* year. The card prints it, because "no chat with that name" over a read
|
|
1366
|
+
* that stopped in August would be a lie nobody could see.
|
|
1367
|
+
*
|
|
1368
|
+
* What it searches is the card's latest ten-day list joined with that kept
|
|
1369
|
+
* read, so a chat newer than the first search is found too, and the join is
|
|
1370
|
+
* `lastThreads`, because importing resolves an id against it. A chat the
|
|
1371
|
+
* list holds keeps the list's copy: the search reads no title, answer or
|
|
1372
|
+
* ending, and its copy would bring the chat in without them.
|
|
1373
|
+
*/
|
|
1374
|
+
let searchable = null;
|
|
1375
|
+
ipcMain.handle('zero:search-agent-threads', async (_e, { again = false } = {}) => {
|
|
1376
|
+
if (again) searchable = null;
|
|
1377
|
+
if (!searchable) {
|
|
1378
|
+
let got = { threads: [], reached: 0 };
|
|
1379
|
+
try { got = await readSearchableThreads({ config, supervisor, readCodex: readCodexThreadsOffThread }); } catch { got = { threads: [], reached: 0 }; }
|
|
1380
|
+
searchable = got;
|
|
1381
|
+
}
|
|
1382
|
+
const byId = new Map(lastThreads.map((t) => [String(t?.id ?? ''), t]));
|
|
1383
|
+
for (const t of searchable.threads) if (!byId.has(String(t?.id ?? ''))) byId.set(String(t?.id ?? ''), t);
|
|
1384
|
+
lastThreads = [...byId.values()];
|
|
1385
|
+
let items = [];
|
|
1386
|
+
try { items = store.listItems(); } catch { items = []; }
|
|
1387
|
+
return { threads: markImported(lastThreads, items), reached: searchable.reached };
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1179
1390
|
// So this is `zero:import-agents` with the agent taken out of it. No ticks are
|
|
1180
1391
|
// saved, because there is nothing for the card to remember: an imported thread
|
|
1181
1392
|
// is a row, and a row is what stops it being imported twice
|
|
1182
|
-
// (`threadsNeedingRows`).
|
|
1183
|
-
//
|
|
1184
|
-
ipcMain.handle('zero:import-threads', (_e, { product, threads } = {}) => {
|
|
1393
|
+
// (`threadsNeedingRows`). The row's first reply resumes the conversation as a
|
|
1394
|
+
// copy (`Supervisor#importedThreadFor`, GitHub #44).
|
|
1395
|
+
ipcMain.handle('zero:import-threads', async (_e, { product, threads, land } = {}) => {
|
|
1185
1396
|
if (!product) return { ok: false, added: 0, already: 0, ids: [] };
|
|
1186
1397
|
const want = new Set(
|
|
1187
1398
|
(Array.isArray(threads) ? threads : [])
|
|
@@ -1191,15 +1402,17 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1191
1402
|
if (!want.size) { return { ok: true, added: 0, already: 0, ids: [] }; }
|
|
1192
1403
|
// The read is taken again only if nothing has been handed out yet, which is
|
|
1193
1404
|
// the case a test or a fresh window can reach and a person cannot.
|
|
1194
|
-
if (!lastThreads.length) lastThreads = readAllThreads();
|
|
1405
|
+
if (!lastThreads.length) lastThreads = await readAllThreads();
|
|
1195
1406
|
// Two kinds, two rows.
|
|
1196
|
-
const chosen = lastThreads.filter((t) => want.has(String(t?.id ?? '')));
|
|
1197
|
-
let out = { ids: [], added: 0, already: 0 };
|
|
1407
|
+
const chosen = readChatEnds(lastThreads.filter((t) => want.has(String(t?.id ?? ''))));
|
|
1408
|
+
let out = { ids: [], added: 0, already: 0, done: 0 };
|
|
1198
1409
|
try {
|
|
1199
1410
|
// Each store method takes its own kind off the list and leaves the other.
|
|
1200
|
-
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1411
|
+
// Setup's last screen sorts what it brings in: finished chats go to Done.
|
|
1412
|
+
const how = { land: land === 'sorted' ? 'sorted' : 'inbox' };
|
|
1413
|
+
const claude = store.importThreadRows(product, chosen, how);
|
|
1414
|
+
const codex = store.importCodexThreads(product, chosen, how);
|
|
1415
|
+
out = { ids: [...claude.ids, ...codex.ids], added: claude.added + codex.added, already: claude.already + codex.already, done: claude.done + codex.done };
|
|
1203
1416
|
} catch (err) {
|
|
1204
1417
|
push();
|
|
1205
1418
|
return { ok: false, error: String(err.message), added: 0, already: 0, ids: [] };
|
|
@@ -1278,6 +1491,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1278
1491
|
if (isAgentRow(id)) return { ok: false };
|
|
1279
1492
|
const out = store.answerItem(product, id, { status: 'open' });
|
|
1280
1493
|
supervisor.liftBrakeForHer?.();
|
|
1494
|
+
supervisor.forgetDeadStarts?.({ product, id });
|
|
1281
1495
|
supervisor.wake();
|
|
1282
1496
|
return out;
|
|
1283
1497
|
});
|
|
@@ -1358,9 +1572,9 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1358
1572
|
// it. Every handler answers `{ ok, error }` rather than throwing across the
|
|
1359
1573
|
// bridge, because the one thing this screen must never do is leave her
|
|
1360
1574
|
// believing a setting took when it did not.
|
|
1361
|
-
ipcMain.handle('zero:settings-read', () => {
|
|
1575
|
+
ipcMain.handle('zero:settings-read', (_e, how) => {
|
|
1362
1576
|
try {
|
|
1363
|
-
return { ok: true, ...readSettings({ config, supervisor, store }) };
|
|
1577
|
+
return { ok: true, ...readSettings({ config, supervisor, store }, { search: how?.search !== false }) };
|
|
1364
1578
|
} catch (err) {
|
|
1365
1579
|
return { ok: false, error: String(err.message) };
|
|
1366
1580
|
}
|
|
@@ -1368,12 +1582,12 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1368
1582
|
|
|
1369
1583
|
// CHECK AGAIN, from the last card of the walk. The card will not open the
|
|
1370
1584
|
// inbox on a Mac with no Claude Code on it, so the way through it is to
|
|
1371
|
-
// install Claude Code and press this. It
|
|
1372
|
-
//
|
|
1373
|
-
//
|
|
1374
|
-
ipcMain.handle('zero:claude-recheck', () => {
|
|
1585
|
+
// install Claude Code and press this. It waits for a fresh shell answer,
|
|
1586
|
+
// asked off the window's thread, because a button that says it looked again
|
|
1587
|
+
// and reads an old memory is the same broken promise as a key that does nothing.
|
|
1588
|
+
ipcMain.handle('zero:claude-recheck', async () => {
|
|
1375
1589
|
try {
|
|
1376
|
-
return { ok: true, workspace:
|
|
1590
|
+
return { ok: true, workspace: await lookAgainFor('claude', config) };
|
|
1377
1591
|
} catch (err) {
|
|
1378
1592
|
return { ok: false, error: String(err.message) };
|
|
1379
1593
|
}
|
|
@@ -1384,9 +1598,9 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1384
1598
|
// search finds lands on `config.codexBin`, which is the fact `engineChoices`
|
|
1385
1599
|
// reads, so a press that finds Codex is also what makes the Coding agent row
|
|
1386
1600
|
// appear -- without the restart a main-process fact usually costs.
|
|
1387
|
-
ipcMain.handle('zero:codex-recheck', () => {
|
|
1601
|
+
ipcMain.handle('zero:codex-recheck', async () => {
|
|
1388
1602
|
try {
|
|
1389
|
-
return { ok: true, workspace:
|
|
1603
|
+
return { ok: true, workspace: await lookAgainFor('codex', config) };
|
|
1390
1604
|
} catch (err) {
|
|
1391
1605
|
return { ok: false, error: String(err.message) };
|
|
1392
1606
|
}
|
|
@@ -1446,8 +1660,8 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1446
1660
|
const engineSetup = createEngineSetup({
|
|
1447
1661
|
find: (engine) => {
|
|
1448
1662
|
const finders = {
|
|
1449
|
-
claude: () => { const s =
|
|
1450
|
-
codex: () => { const s =
|
|
1663
|
+
claude: async () => { const s = await lookAgainFor('claude', config); return { found: s.claudeFound, path: s.claudeBin }; },
|
|
1664
|
+
codex: async () => { const s = await lookAgainFor('codex', config); return { found: s.found, path: s.bin }; },
|
|
1451
1665
|
};
|
|
1452
1666
|
return finders[engine]?.() ?? { found: false, path: null };
|
|
1453
1667
|
},
|
|
@@ -1515,7 +1729,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1515
1729
|
|
|
1516
1730
|
ipcMain.handle('zero:settings-set-project', (_e, payload) => {
|
|
1517
1731
|
try {
|
|
1518
|
-
setProjectSetting({ config, supervisor }, payload ?? {});
|
|
1732
|
+
setProjectSetting({ config, supervisor, store }, payload ?? {});
|
|
1519
1733
|
push();
|
|
1520
1734
|
return { ok: true, ...readSettings({ config, supervisor, store }) };
|
|
1521
1735
|
} catch (err) {
|
|
@@ -1523,9 +1737,90 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1523
1737
|
}
|
|
1524
1738
|
});
|
|
1525
1739
|
|
|
1740
|
+
// SIGNING IN TO AGENTBOX CLOUD ITSELF, on the app's own sign-in page
|
|
1741
|
+
// (main/cloud/account.mjs): Google, or an email and a password, traded for
|
|
1742
|
+
// this Mac's cloud key. One session per page, made when the page first asks.
|
|
1743
|
+
let cloudAccount = null;
|
|
1744
|
+
const cloudAccountSessionNow = async () => {
|
|
1745
|
+
const home = supervisor.config.home || os.homedir();
|
|
1746
|
+
const { cloudAccountConfig, cloudAccountSession } = await import('./cloud/account.mjs');
|
|
1747
|
+
const config = cloudAccountConfig(supervisor.appDir, { home });
|
|
1748
|
+
if (!config) return null;
|
|
1749
|
+
if (!cloudAccount || cloudAccount.config !== JSON.stringify(config)) {
|
|
1750
|
+
const { shell } = await import('electron');
|
|
1751
|
+
cloudAccount = { config: JSON.stringify(config), value: config, session: cloudAccountSession({ config, openExternal: (u) => shell.openExternal(u), log: (l) => console.log(`cloud account: ${l}`) }) };
|
|
1752
|
+
}
|
|
1753
|
+
return cloudAccount;
|
|
1754
|
+
};
|
|
1755
|
+
ipcMain.handle('zero:cloud-account-sign-in', async (_e, { method, email, password, name, acceptTerms } = {}) => {
|
|
1756
|
+
const acct = await cloudAccountSessionNow();
|
|
1757
|
+
if (!acct) return { ok: false, error: 'This copy of the app has no cloud to sign in to.' };
|
|
1758
|
+
const { cloudSignIn } = await import('./cloud/account.mjs');
|
|
1759
|
+
const out = await cloudSignIn({ config: acct.value, session: acct.session, method, email, password, name, acceptTerms: acceptTerms === true, home: supervisor.config.home || os.homedir() });
|
|
1760
|
+
if (method === 'google') { try { if (window?.isMinimized?.()) window.restore(); window?.show?.(); } catch { /* no window to raise */ } }
|
|
1761
|
+
push();
|
|
1762
|
+
return out;
|
|
1763
|
+
});
|
|
1764
|
+
ipcMain.handle('zero:cloud-account-cancel', async () => { (await cloudAccountSessionNow())?.session.cancel(); return { ok: true }; });
|
|
1765
|
+
ipcMain.handle('zero:cloud-account-reopen', async () => { await (await cloudAccountSessionNow())?.session.reopen(); return { ok: true }; });
|
|
1766
|
+
ipcMain.handle('zero:cloud-account-sign-out', async () => {
|
|
1767
|
+
const { signOutOfCloud } = await import('./cloud/account.mjs');
|
|
1768
|
+
const out = await signOutOfCloud({ home: supervisor.config.home || os.homedir() });
|
|
1769
|
+
push();
|
|
1770
|
+
return out;
|
|
1771
|
+
});
|
|
1772
|
+
// PAYING FOR A CLOUD PLAN: Stripe's own pages, in the browser, as other
|
|
1773
|
+
// desktop apps do. An app window drew them badly and never offers Apple Pay.
|
|
1774
|
+
const cloudCall = async (name, args = {}) => {
|
|
1775
|
+
try {
|
|
1776
|
+
const account = await import('./cloud/account.mjs');
|
|
1777
|
+
const { shell } = await import('electron');
|
|
1778
|
+
return await account[name]({ ...args, openExternal: (u) => shell.openExternal(u), home: supervisor.config.home || os.homedir() });
|
|
1779
|
+
} catch (err) {
|
|
1780
|
+
return { ok: false, error: 'unreachable', message: `The cloud did not answer: ${err?.message || err}` };
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
ipcMain.handle('zero:cloud-account-plan', async () => {
|
|
1784
|
+
const plan = await cloudCall('cloudPlan');
|
|
1785
|
+
supervisor.cloudPlanSeen?.(plan);
|
|
1786
|
+
return plan;
|
|
1787
|
+
});
|
|
1788
|
+
ipcMain.handle('zero:cloud-account-pay', (_e, { plan } = {}) => cloudCall('payForCloud', { plan }));
|
|
1789
|
+
ipcMain.handle('zero:cloud-account-billing', () => cloudCall('openCloudBilling'));
|
|
1790
|
+
|
|
1791
|
+
// THE CLOUD SETUP'S SIGN-IN for one account, made in the cloud
|
|
1792
|
+
// (main/cloud/logins.mjs). The Mac relays the link and the code only.
|
|
1793
|
+
ipcMain.handle('zero:cloud-signin', async (_e, { action, engine, key, code } = {}) => {
|
|
1794
|
+
try {
|
|
1795
|
+
const out = await supervisor.cloudSignIn(action, engine, key ?? null, code);
|
|
1796
|
+
push();
|
|
1797
|
+
return { ok: true, key: out?.key ?? null };
|
|
1798
|
+
} catch (err) {
|
|
1799
|
+
return { ok: false, error: String(err.message) };
|
|
1800
|
+
}
|
|
1801
|
+
});
|
|
1802
|
+
// CLAUDE'S CODE, FROM THE CLIPBOARD, WHEN SHE COMES BACK (2026-10-14: she
|
|
1803
|
+
// approved on Claude's page and waited, not seeing that the code it shows
|
|
1804
|
+
// has to come back). Answered only while a Claude sign-in is waiting for
|
|
1805
|
+
// its code, and only with text shaped like one: anything else she copied
|
|
1806
|
+
// stays where it is.
|
|
1807
|
+
ipcMain.handle('zero:cloud-clipboard-code', () => {
|
|
1808
|
+
try {
|
|
1809
|
+
const waiting = (supervisor.cloudStatus?.().signIns || []).some((s) => s.engine === 'claude' && s.state === 'needs-code');
|
|
1810
|
+
if (!waiting) return { code: null };
|
|
1811
|
+
const text = String(host?.readClipboardText?.() ?? '').trim();
|
|
1812
|
+
return { code: looksLikeClaudeCode(text) ? text : null };
|
|
1813
|
+
} catch {
|
|
1814
|
+
return { code: null };
|
|
1815
|
+
}
|
|
1816
|
+
});
|
|
1526
1817
|
ipcMain.handle('zero:settings-set-workspace', (_e, payload) => {
|
|
1527
1818
|
try {
|
|
1528
|
-
|
|
1819
|
+
// Before the save: once the switch is off, nothing leaves, not even this.
|
|
1820
|
+
if (countsTurnedOff(payload)) { analytics.track('counts_turned_off'); analytics.flush(); }
|
|
1821
|
+
setWorkspaceSetting({ config, supervisor, store }, payload ?? {});
|
|
1822
|
+
if (payload?.key === 'alphaRooms') team?.rooms?.tick().catch(() => {});
|
|
1823
|
+
if (cornerTagTurnedOff(payload)) analytics.track('tag_turned_off');
|
|
1529
1824
|
push();
|
|
1530
1825
|
return { ok: true, ...readSettings({ config, supervisor, store }) };
|
|
1531
1826
|
} catch (err) {
|
|
@@ -1649,29 +1944,9 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1649
1944
|
// in the product's attachments/ dir and come back as relative paths the
|
|
1650
1945
|
// caller embeds in the body as markdown. Relative paths keep them readable
|
|
1651
1946
|
// by workers through the MCP and renderable in the message view.
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
const outDir = path.join(dir, 'attachments');
|
|
1656
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
1657
|
-
const saved = [];
|
|
1658
|
-
for (const file of files ?? []) {
|
|
1659
|
-
const safe = String(file.name ?? 'file').replace(/[^\w.-]+/g, '-').slice(0, 80);
|
|
1660
|
-
// A RANDOM SUFFIX, THE SAME ONE THE STAGING PATH ALREADY USES. The name
|
|
1661
|
-
// was the millisecond plus the filename, so two files saved in the same
|
|
1662
|
-
// millisecond with the same name overwrote each other and the message
|
|
1663
|
-
// ended up with two links to one picture. Screenshots are often sent in
|
|
1664
|
-
// pairs, which is exactly the shape that collides. It has never been observed
|
|
1665
|
-
// happening; it is one line to make impossible.
|
|
1666
|
-
const rel = `attachments/${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}-${safe}`;
|
|
1667
|
-
const abs = path.join(dir, rel);
|
|
1668
|
-
if (file.dataBase64) fs.writeFileSync(abs, Buffer.from(file.dataBase64, 'base64'));
|
|
1669
|
-
else if (file.srcPath) fs.copyFileSync(file.srcPath, abs);
|
|
1670
|
-
else continue;
|
|
1671
|
-
saved.push({ rel, name: safe, image: /\.(png|jpe?g|gif|webp)$/i.test(safe) });
|
|
1672
|
-
}
|
|
1673
|
-
return saved;
|
|
1674
|
-
});
|
|
1947
|
+
// The saving itself is main/save-attachments.mjs, shared with a message to a
|
|
1948
|
+
// person, which saves its own once it knows which conversation it is in.
|
|
1949
|
+
ipcMain.handle('zero:save-attachments', async (_e, { product, files }) => saveAttachments(store.productDir(product), files));
|
|
1675
1950
|
|
|
1676
1951
|
// A PASTED SCREENSHOT GOES TO DISK THE MOMENT IT IS PASTED.
|
|
1677
1952
|
//
|
|
@@ -1712,32 +1987,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1712
1987
|
// costs nothing at send time because `zero:save-attachments` above already
|
|
1713
1988
|
// copies from `srcPath`. Bytes are copied on disk rather than carried through
|
|
1714
1989
|
// this bridge as base64, which a file picked off disk should never need.
|
|
1715
|
-
ipcMain.handle('zero:stage-attachment', async (_e,
|
|
1716
|
-
const fs = await import('node:fs');
|
|
1717
|
-
const dir = path.join(config.accountRoot, '.staging');
|
|
1718
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
1719
|
-
// Sweep on the way in rather than on a timer: an abandoned paste is only
|
|
1720
|
-
// ever discovered by the next one, and a week is longer than any draft she
|
|
1721
|
-
// has ever left open. Failure to sweep must never fail the paste.
|
|
1722
|
-
try {
|
|
1723
|
-
const week = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
1724
|
-
for (const f of fs.readdirSync(dir)) {
|
|
1725
|
-
const p = path.join(dir, f);
|
|
1726
|
-
try { if (fs.statSync(p).mtimeMs < week) fs.unlinkSync(p); } catch { /* leave it */ }
|
|
1727
|
-
}
|
|
1728
|
-
} catch { /* a sweep that cannot run is not a paste that failed */ }
|
|
1729
|
-
const safe = String(name ?? 'pasted.png').replace(/[^\w.-]+/g, '-').slice(0, 80);
|
|
1730
|
-
const abs = path.join(dir, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}-${safe}`);
|
|
1731
|
-
// A path is a drag, bytes are a paste. Already inside the folder we serve
|
|
1732
|
-
// from is left alone: copying it would only make a second file to sweep.
|
|
1733
|
-
if (typeof srcPath === 'string' && srcPath) {
|
|
1734
|
-
if (path.resolve(srcPath) === path.resolve(path.join(dir, path.basename(srcPath)))) return { path: srcPath };
|
|
1735
|
-
fs.copyFileSync(srcPath, abs);
|
|
1736
|
-
return { path: abs };
|
|
1737
|
-
}
|
|
1738
|
-
fs.writeFileSync(abs, Buffer.from(String(dataBase64 ?? ''), 'base64'));
|
|
1739
|
-
return { path: abs };
|
|
1740
|
-
});
|
|
1990
|
+
ipcMain.handle('zero:stage-attachment', async (_e, file) => stageAttachment(path.join(config.accountRoot, '.staging'), file));
|
|
1741
1991
|
|
|
1742
1992
|
// Unstick ONE stalled item: forget its answer's delivery mark and tick, so
|
|
1743
1993
|
// a fresh worker picks the answer up now.
|
|
@@ -1746,6 +1996,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1746
1996
|
const item = store.listItems().find((i) => i.product === product && i.id === id);
|
|
1747
1997
|
if (!item?.answer || item.answer === '(withdrawn)') return { ok: false, error: 'no live answer to redeliver' };
|
|
1748
1998
|
supervisor.liftBrakeForHer?.();
|
|
1999
|
+
supervisor.forgetDeadStarts?.(item);
|
|
1749
2000
|
supervisor.redeliverAnswer(item, item.answer);
|
|
1750
2001
|
supervisor.tick?.().catch(() => {});
|
|
1751
2002
|
return { ok: true };
|
|
@@ -1787,7 +2038,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1787
2038
|
const { shell } = await import('electron');
|
|
1788
2039
|
if (typeof src !== 'string' || !src) return { ok: false, error: 'no path' };
|
|
1789
2040
|
if (/^https?:/.test(src)) { if (mode !== 'resolve') shell.openExternal(src); return { ok: true, opened: src } }
|
|
1790
|
-
const found = resolveArtifact({
|
|
2041
|
+
const found = await resolveArtifact({
|
|
1791
2042
|
src,
|
|
1792
2043
|
product,
|
|
1793
2044
|
products: store.listProducts(),
|
|
@@ -1833,7 +2084,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1833
2084
|
// text rather than the path. What may be written is decided in
|
|
1834
2085
|
// main/doc-file.mjs: markdown only, inside this product's own folders only.
|
|
1835
2086
|
ipcMain.handle('zero:read-doc', async (_e, { product, src }) => {
|
|
1836
|
-
const found = resolveArtifact({
|
|
2087
|
+
const found = await resolveArtifact({
|
|
1837
2088
|
src,
|
|
1838
2089
|
product,
|
|
1839
2090
|
products: store.listProducts(),
|
|
@@ -1847,7 +2098,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1847
2098
|
// THE CHANGE A RUN MADE. Same finder as every other artifact, so a click on
|
|
1848
2099
|
// the chip and a click on a path in her message open the same file.
|
|
1849
2100
|
ipcMain.handle('zero:code-change', async (_e, { product, src }) => {
|
|
1850
|
-
const found = resolveArtifact({
|
|
2101
|
+
const found = await resolveArtifact({
|
|
1851
2102
|
src,
|
|
1852
2103
|
product,
|
|
1853
2104
|
products: store.listProducts(),
|
|
@@ -1868,8 +2119,8 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1868
2119
|
// same pair the pane already has on screen. The change is re-read here rather
|
|
1869
2120
|
// than trusted from the renderer, because it is what says which folders a
|
|
1870
2121
|
// write may land in and that decision may not come from the window.
|
|
1871
|
-
const openChange = (product, src) => {
|
|
1872
|
-
const found = resolveArtifact({
|
|
2122
|
+
const openChange = async (product, src) => {
|
|
2123
|
+
const found = await resolveArtifact({
|
|
1873
2124
|
src,
|
|
1874
2125
|
product,
|
|
1875
2126
|
products: store.listProducts(),
|
|
@@ -1885,7 +2136,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1885
2136
|
};
|
|
1886
2137
|
|
|
1887
2138
|
ipcMain.handle('zero:code-file', async (_e, { product, src, path: filePath }) => {
|
|
1888
|
-
const opened = openChange(product, src);
|
|
2139
|
+
const opened = await openChange(product, src);
|
|
1889
2140
|
if (!opened.ok) return opened;
|
|
1890
2141
|
const found = findFile({ change: opened.change, product: opened.prod, filePath });
|
|
1891
2142
|
if (!found.ok) return found;
|
|
@@ -1893,7 +2144,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1893
2144
|
});
|
|
1894
2145
|
|
|
1895
2146
|
ipcMain.handle('zero:save-code-file', async (_e, { product, src, path: filePath, text, mtime }) => {
|
|
1896
|
-
const opened = openChange(product, src);
|
|
2147
|
+
const opened = await openChange(product, src);
|
|
1897
2148
|
if (!opened.ok) return opened;
|
|
1898
2149
|
const found = findFile({ change: opened.change, product: opened.prod, filePath });
|
|
1899
2150
|
if (!found.ok) return found;
|
|
@@ -1902,7 +2153,7 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1902
2153
|
|
|
1903
2154
|
ipcMain.handle('zero:write-doc', async (_e, { product, src, text, mtime }) => {
|
|
1904
2155
|
const prod = store.listProducts().find((p) => p.slug === product) ?? null;
|
|
1905
|
-
const found = resolveArtifact({
|
|
2156
|
+
const found = await resolveArtifact({
|
|
1906
2157
|
src,
|
|
1907
2158
|
product,
|
|
1908
2159
|
products: store.listProducts(),
|
|
@@ -1954,9 +2205,9 @@ export function registerIpc({ store, supervisor, config, window, analytics = NO_
|
|
|
1954
2205
|
|
|
1955
2206
|
// The thread history behind the time under a task's title. Raw ledger lines,
|
|
1956
2207
|
// read fresh: the renderer owns what they mean, this owns getting them across.
|
|
1957
|
-
ipcMain.handle('zero:item-history', async (_e, { product, id }) => {
|
|
2208
|
+
ipcMain.handle('zero:item-history', async (_e, { product, id, before }) => {
|
|
1958
2209
|
try {
|
|
1959
|
-
return { ok: true, lines: store.readHistory(product, id) };
|
|
2210
|
+
return { ok: true, lines: store.readHistory(product, id, { before }) };
|
|
1960
2211
|
} catch (err) {
|
|
1961
2212
|
return { ok: false, error: err.message, lines: [] };
|
|
1962
2213
|
}
|