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
|
@@ -10,6 +10,12 @@ import { EventEmitter } from 'node:events';
|
|
|
10
10
|
import crypto from 'node:crypto';
|
|
11
11
|
import { asPulled } from '../../shared/team-rules.mjs';
|
|
12
12
|
import { cleanStatus } from '../../shared/team-status.mjs';
|
|
13
|
+
import { PHONE_COMMANDS } from '../../shared/phone-snapshot.mjs';
|
|
14
|
+
import { roomsMemory } from './rooms-memory.mjs';
|
|
15
|
+
|
|
16
|
+
// The database's own checks on a message's file (20261013000000_message_files.sql).
|
|
17
|
+
const FILE_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,159}$/;
|
|
18
|
+
const MAX_FILE_DATA = 14_000_000;
|
|
13
19
|
|
|
14
20
|
export function createMemoryCloud() {
|
|
15
21
|
return {
|
|
@@ -21,6 +27,7 @@ export function createMemoryCloud() {
|
|
|
21
27
|
projectPeople: [], // { projectId, personId }
|
|
22
28
|
lines: [], // { seq, projectId, uid, byPerson, body, createdAt }
|
|
23
29
|
cards: new Map(), // `${personId}:${threadId}` -> a thread card, as shared/thread-cards.mjs makes them
|
|
30
|
+
files: new Map(), // `${projectId}:${name}` -> { projectId, name, byPerson, data (base64) }
|
|
24
31
|
seq: 0,
|
|
25
32
|
teamsMade: 0, // the order teams were made in, which is their age
|
|
26
33
|
now: () => Date.now(), // the server's clock
|
|
@@ -36,6 +43,18 @@ export function signUpMemory(cloud, { id = crypto.randomUUID(), email, name, ava
|
|
|
36
43
|
return id;
|
|
37
44
|
}
|
|
38
45
|
|
|
46
|
+
// WHAT THE CLOUD SERVER DOES WITH THE SERVICE ROLE (cloud/gateway/phone.mjs):
|
|
47
|
+
// claims a command nobody has for the run it starts, as the database lets one
|
|
48
|
+
// claim win. Here so a test can be the server.
|
|
49
|
+
export function claimForTheCloud(cloud, id, runId) {
|
|
50
|
+
const c = (cloud.phoneCommands ?? []).find((x) => x.id === id);
|
|
51
|
+
if (!c || c.takenAt || c.claimedAt) return false;
|
|
52
|
+
c.claimedAt = new Date(cloud.now()).toISOString();
|
|
53
|
+
c.claimedBy = `cloud:${runId}`;
|
|
54
|
+
cloud.events.emit('phone', 1);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
39
58
|
export function memoryBackend(cloud, personId) {
|
|
40
59
|
const myTeams = () => cloud.members.filter((m) => m.personId === personId).map((m) => m.teamId)
|
|
41
60
|
.sort((a, b) => (cloud.teams.get(a)?.made ?? 0) - (cloud.teams.get(b)?.made ?? 0));
|
|
@@ -96,6 +115,7 @@ export function memoryBackend(cloud, personId) {
|
|
|
96
115
|
if (at < 0) refuse('join a team that did not invite you');
|
|
97
116
|
cloud.invites.splice(at, 1);
|
|
98
117
|
if (!cloud.members.some((m) => m.teamId === teamId && m.personId === personId)) cloud.members.push({ teamId, personId, role: 'member' });
|
|
118
|
+
cloud.events.emit('rooms', { kind: 'membership' });
|
|
99
119
|
return teamId;
|
|
100
120
|
},
|
|
101
121
|
|
|
@@ -145,6 +165,7 @@ export function memoryBackend(cloud, personId) {
|
|
|
145
165
|
async removeMember(teamId, who) {
|
|
146
166
|
if (who !== personId && !owns(teamId)) refuse('remove somebody from a team you do not own');
|
|
147
167
|
cloud.members = cloud.members.filter((m) => !(m.teamId === teamId && m.personId === who));
|
|
168
|
+
cloud.events.emit('rooms', { kind: 'membership' });
|
|
148
169
|
},
|
|
149
170
|
|
|
150
171
|
async listInvites(teamId) {
|
|
@@ -236,12 +257,173 @@ export function memoryBackend(cloud, personId) {
|
|
|
236
257
|
.map(({ teamId, ...c }) => structuredClone(c));
|
|
237
258
|
},
|
|
238
259
|
|
|
260
|
+
// THE FILES A CONVERSATION'S MESSAGES LINK TO (w-140ac855b1). Read and
|
|
261
|
+
// written by whoever may see the project, each name stored once and never
|
|
262
|
+
// replaced: 20261013000000_message_files.sql.
|
|
263
|
+
async hasFiles(projectId, names) {
|
|
264
|
+
if (!canSee(projectId)) return [];
|
|
265
|
+
return names.filter((n) => cloud.files.has(`${projectId}:${n}`));
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
async getFile(projectId, name) {
|
|
269
|
+
if (!canSee(projectId)) return null;
|
|
270
|
+
return cloud.files.get(`${projectId}:${name}`)?.data ?? null;
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
async putFile(projectId, name, data) {
|
|
274
|
+
if (!canSee(projectId)) refuse('send a file to a project you cannot see');
|
|
275
|
+
if (!FILE_NAME.test(name)) refuse('a file name that is not a plain name');
|
|
276
|
+
if (data.length > MAX_FILE_DATA) refuse('a file that big');
|
|
277
|
+
const key = `${projectId}:${name}`;
|
|
278
|
+
if (cloud.files.has(key)) return false;
|
|
279
|
+
cloud.files.set(key, { projectId, name, byPerson: personId, data });
|
|
280
|
+
cloud.events.emit('files', 1);
|
|
281
|
+
return true;
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
// HER OWN ROWS FOR HER PHONE (20261014000200_phone_inbox.sql): every one
|
|
285
|
+
// keyed by its owner and the computer it came from, and only ever that
|
|
286
|
+
// owner's. A command is written once and marked taken once.
|
|
287
|
+
// Each write may name who it is for; a write for somebody other than
|
|
288
|
+
// whoever this backend is signed in as is refused, as the database's
|
|
289
|
+
// `owner = auth.uid()` refuses it.
|
|
290
|
+
async putPhoneProject({ owner, device, project, name, cloud: carriesOn }) {
|
|
291
|
+
if (owner && owner !== personId) refuse('write to somebody else\'s phone');
|
|
292
|
+
const k = `${personId}:${device}:${project}`;
|
|
293
|
+
const was = cloud.phoneProjects?.get(k);
|
|
294
|
+
(cloud.phoneProjects ??= new Map()).set(k, { owner: personId, device, project, name, cloud: carriesOn === undefined ? was?.cloud ?? false : !!carriesOn, updatedAt: cloud.now() });
|
|
295
|
+
},
|
|
296
|
+
async dropPhoneProject(device, project, owner) {
|
|
297
|
+
if (owner && owner !== personId) return;
|
|
298
|
+
cloud.phoneProjects?.delete(`${personId}:${device}:${project}`);
|
|
299
|
+
for (const k of [...(cloud.phoneThreads?.keys() ?? [])]) if (k.startsWith(`${personId}:${device}:${project}:`)) cloud.phoneThreads.delete(k);
|
|
300
|
+
},
|
|
301
|
+
async putPhoneThreads(device, project, rows, owner) {
|
|
302
|
+
if (owner && owner !== personId) refuse('write to somebody else\'s phone');
|
|
303
|
+
cloud.phoneThreads ??= new Map();
|
|
304
|
+
if (!cloud.phoneProjects?.has(`${personId}:${device}:${project}`)) refuse('a thread of a project not on the phone');
|
|
305
|
+
for (const r of rows) {
|
|
306
|
+
if (new TextEncoder().encode(JSON.stringify(r.snapshot)).length > 1_000_000) refuse('a thread copy that big');
|
|
307
|
+
}
|
|
308
|
+
for (const r of rows) {
|
|
309
|
+
cloud.phoneThreads.set(`${personId}:${device}:${project}:${r.item}`, { owner: personId, device, project, item: r.item, snapshot: r.snapshot, updatedAt: cloud.now() });
|
|
310
|
+
}
|
|
311
|
+
cloud.events.emit('phone', 1);
|
|
312
|
+
},
|
|
313
|
+
// Her files for the phone (20261017000000_phone_files.sql): kept under her
|
|
314
|
+
// own id, never replaced, at most the bucket's size.
|
|
315
|
+
async putPhoneFile({ owner, device, name, data, type }) {
|
|
316
|
+
if (owner && owner !== personId) refuse('write to somebody else\'s phone');
|
|
317
|
+
if (!/^[0-9a-f]{64}\.[a-z0-9]{1,8}$/.test(name)) refuse('a file named that');
|
|
318
|
+
if (data.length > 26_214_400) refuse('a file that big');
|
|
319
|
+
const key = `${personId}/${device}/${name}`;
|
|
320
|
+
if (!(cloud.phoneFiles ??= new Map()).has(key)) cloud.phoneFiles.set(key, { data: Buffer.from(data), type });
|
|
321
|
+
},
|
|
322
|
+
async getPhoneFile({ owner, device, name }) {
|
|
323
|
+
if (owner && owner !== personId) refuse('read somebody else\'s phone');
|
|
324
|
+
const f = cloud.phoneFiles?.get(`${personId}/${device}/${name}`);
|
|
325
|
+
return f ? Buffer.from(f.data) : null;
|
|
326
|
+
},
|
|
327
|
+
async dropPhoneFiles({ owner, device, names }) {
|
|
328
|
+
if (owner && owner !== personId) return;
|
|
329
|
+
for (const n of names) cloud.phoneFiles?.delete(`${personId}/${device}/${n}`);
|
|
330
|
+
},
|
|
331
|
+
async dropPhoneThreads(device, project, items, owner) {
|
|
332
|
+
if (owner && owner !== personId) return;
|
|
333
|
+
for (const item of items) cloud.phoneThreads?.delete(`${personId}:${device}:${project}:${item}`);
|
|
334
|
+
},
|
|
335
|
+
async phoneThreads() {
|
|
336
|
+
return [...(cloud.phoneThreads?.values() ?? [])].filter((t) => t.owner === personId);
|
|
337
|
+
},
|
|
338
|
+
async phoneProjects() {
|
|
339
|
+
return [...(cloud.phoneProjects?.values() ?? [])].filter((p) => p.owner === personId);
|
|
340
|
+
},
|
|
341
|
+
// One row per computer (20261023000000_phone_settings.sql).
|
|
342
|
+
async putPhoneSettings({ owner, device, name, settings }) {
|
|
343
|
+
if (owner && owner !== personId) refuse('write to somebody else\'s phone');
|
|
344
|
+
if (!/^[0-9a-f]{12}$/.test(String(device))) refuse('settings for no computer');
|
|
345
|
+
if (new TextEncoder().encode(JSON.stringify(settings)).length > 200_000) refuse('settings that big');
|
|
346
|
+
(cloud.phoneSettings ??= new Map()).set(`${personId}:${device}`, { owner: personId, device, name, settings, updatedAt: cloud.now() });
|
|
347
|
+
},
|
|
348
|
+
async phoneSettings() {
|
|
349
|
+
return [...(cloud.phoneSettings?.values() ?? [])].filter((s) => s.owner === personId).map((s) => ({ ...s }));
|
|
350
|
+
},
|
|
351
|
+
// The phones her alerts go to (20261025000000_push_devices.sql), hers alone.
|
|
352
|
+
async putPushDevice({ token, platform, appVersion = null }) {
|
|
353
|
+
if (!/^ExponentPushToken\[[A-Za-z0-9_-]{10,200}\]$/.test(String(token))) refuse('a phone address that is not Expo\'s');
|
|
354
|
+
if (!['ios', 'android'].includes(platform)) refuse(`a ${platform} phone`);
|
|
355
|
+
(cloud.pushDevices ??= new Map()).set(`${personId}:${token}`, { owner: personId, token, platform, appVersion, updatedAt: cloud.now() });
|
|
356
|
+
},
|
|
357
|
+
async pushDevices(owner) {
|
|
358
|
+
if (owner && owner !== personId) return [];
|
|
359
|
+
return [...(cloud.pushDevices?.values() ?? [])].filter((d) => d.owner === personId).map((d) => ({ ...d }));
|
|
360
|
+
},
|
|
361
|
+
async dropPushDevice(token, owner) {
|
|
362
|
+
if (owner && owner !== personId) return;
|
|
363
|
+
cloud.pushDevices?.delete(`${personId}:${token}`);
|
|
364
|
+
},
|
|
365
|
+
// What her phone does: a command, as herself, for one computer, not yet taken.
|
|
366
|
+
async addPhoneCommand({ device, project = null, item = null, kind, payload = {} }) {
|
|
367
|
+
if (!/^[0-9a-f]{12}$/.test(String(device))) refuse('a command for no computer');
|
|
368
|
+
if (!PHONE_COMMANDS.includes(kind)) refuse(`a ${kind} command`);
|
|
369
|
+
if ((kind === 'setting') !== (project == null) || (kind === 'setting' && item != null)) refuse(`a ${kind} command for ${project == null ? 'no project' : 'a project'}`);
|
|
370
|
+
const row = { id: crypto.randomUUID(), owner: personId, device, project, item, kind, payload, createdAt: new Date(cloud.now()).toISOString(), claimedAt: null, claimedBy: null, takenAt: null, outcome: null };
|
|
371
|
+
(cloud.phoneCommands ??= []).push(row);
|
|
372
|
+
cloud.events.emit('phone', 1);
|
|
373
|
+
return { ...row };
|
|
374
|
+
},
|
|
375
|
+
async waitingPhoneCommands(device) {
|
|
376
|
+
return (cloud.phoneCommands ?? []).filter((c) => c.owner === personId && c.device === device && !c.takenAt).map((c) => ({ ...c }));
|
|
377
|
+
},
|
|
378
|
+
async phoneCommands() {
|
|
379
|
+
return (cloud.phoneCommands ?? []).filter((c) => c.owner === personId).map((c) => ({ ...c }));
|
|
380
|
+
},
|
|
381
|
+
async cloudPhoneCommands(device, since) {
|
|
382
|
+
return (cloud.phoneCommands ?? []).filter((c) => c.owner === personId && c.device === device && c.claimedBy && c.createdAt >= since).map((c) => ({ ...c }));
|
|
383
|
+
},
|
|
384
|
+
// THE COMPUTER CLAIMS ONE BEFORE IT DOES IT; the phone takes one back only
|
|
385
|
+
// while nobody has. Exactly one of the two wins, as in the database.
|
|
386
|
+
// 'claimed' is a claim made now, 'ours' one made before (this computer,
|
|
387
|
+
// starting again), 'gone' a command taken back or already done.
|
|
388
|
+
async claimPhoneCommand(id, owner) {
|
|
389
|
+
if (owner && owner !== personId) return 'gone';
|
|
390
|
+
const c = (cloud.phoneCommands ?? []).find((x) => x.id === id && x.owner === personId);
|
|
391
|
+
if (!c || c.takenAt) return 'gone';
|
|
392
|
+
if (c.claimedBy) return 'cloud';
|
|
393
|
+
if (c.claimedAt) return 'ours';
|
|
394
|
+
c.claimedAt = new Date(cloud.now()).toISOString();
|
|
395
|
+
return 'claimed';
|
|
396
|
+
},
|
|
397
|
+
async takeBackPhoneCommand(id, outcome) {
|
|
398
|
+
const c = (cloud.phoneCommands ?? []).find((x) => x.id === id && x.owner === personId);
|
|
399
|
+
if (!c || c.takenAt || c.claimedAt) return false;
|
|
400
|
+
c.takenAt = new Date(cloud.now()).toISOString();
|
|
401
|
+
c.outcome = outcome;
|
|
402
|
+
cloud.events.emit('phone', 1);
|
|
403
|
+
return true;
|
|
404
|
+
},
|
|
405
|
+
async markPhoneCommand(id, outcome, owner) {
|
|
406
|
+
if (owner && owner !== personId) return;
|
|
407
|
+
// Somebody else's command is not there for you, as row level security
|
|
408
|
+
// has it: the mark changes nothing rather than failing. Your own is
|
|
409
|
+
// marked once; a second mark finds nothing waiting and changes nothing,
|
|
410
|
+
// as the real backend's `taken_at is null` filter has it.
|
|
411
|
+
const c = (cloud.phoneCommands ?? []).find((x) => x.id === id && x.owner === personId);
|
|
412
|
+
if (!c || c.takenAt) return;
|
|
413
|
+
c.takenAt = new Date(cloud.now()).toISOString();
|
|
414
|
+
c.outcome = outcome;
|
|
415
|
+
cloud.events.emit('phone', 1);
|
|
416
|
+
},
|
|
417
|
+
|
|
239
418
|
subscribe(onChange) {
|
|
240
419
|
const fire = () => onChange();
|
|
241
420
|
cloud.events.on('lines', fire);
|
|
242
421
|
cloud.events.on('cards', fire);
|
|
243
422
|
cloud.events.on('people', fire);
|
|
244
|
-
|
|
423
|
+
cloud.events.on('phone', fire);
|
|
424
|
+
return () => { cloud.events.off('lines', fire); cloud.events.off('cards', fire); cloud.events.off('people', fire); cloud.events.off('phone', fire); };
|
|
245
425
|
},
|
|
426
|
+
|
|
427
|
+
...roomsMemory(cloud, personId, { confirmedEmail, refuse }),
|
|
246
428
|
};
|
|
247
429
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// MY AGENT HOURS FOR MY PRESENCE ROW (w-3d0fa23ffb). The run ledger is
|
|
2
|
+
// tens of thousands of lines on a busy Mac, and the presence row is checked
|
|
3
|
+
// every few seconds, so the summary is worked out again only when the ledger
|
|
4
|
+
// has changed or the day may have turned. The old run logs are read into the
|
|
5
|
+
// ledger once, the first time Rooms asks.
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { allRuns, backfillRuns, runLedgerPath } from '../run-ledger.mjs';
|
|
9
|
+
import { summarizeRuns } from '../../shared/agent-hours.mjs';
|
|
10
|
+
|
|
11
|
+
const RECHECK_MS = 10 * 60_000;
|
|
12
|
+
|
|
13
|
+
export function createRoomHours({ home, products = () => [], cloudLines = () => [], now = Date.now, log = () => {} }) {
|
|
14
|
+
const file = runLedgerPath(home);
|
|
15
|
+
const marker = path.join(home, 'runs.backfilled');
|
|
16
|
+
let cached = null;
|
|
17
|
+
let seen = null;
|
|
18
|
+
let at = 0;
|
|
19
|
+
let backfilling = null;
|
|
20
|
+
|
|
21
|
+
function backfillOnce() {
|
|
22
|
+
if (backfilling || fs.existsSync(marker)) return backfilling;
|
|
23
|
+
backfilling = backfillRuns({ home, products: products(), file, now: now() })
|
|
24
|
+
.then((n) => {
|
|
25
|
+
try { fs.writeFileSync(marker, `${new Date(now()).toISOString()} ${n}\n`); } catch { /* read again next start */ }
|
|
26
|
+
log(`rooms: read ${n} past runs into the run ledger`);
|
|
27
|
+
cached = null;
|
|
28
|
+
})
|
|
29
|
+
.catch((err) => log(`rooms: reading past runs: ${err.message}`));
|
|
30
|
+
return backfilling;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
backfillOnce,
|
|
35
|
+
summary() {
|
|
36
|
+
backfillOnce();
|
|
37
|
+
let stamp = '';
|
|
38
|
+
try { const s = fs.statSync(file); stamp = `${s.size}:${s.mtimeMs}`; } catch { stamp = 'none'; }
|
|
39
|
+
if (cached && stamp === seen && now() - at < RECHECK_MS) return cached;
|
|
40
|
+
let cloud = [];
|
|
41
|
+
try { cloud = cloudLines() ?? []; } catch { cloud = []; }
|
|
42
|
+
cached = summarizeRuns(allRuns({ file, cloudLines: cloud }), { now: now() });
|
|
43
|
+
seen = stamp;
|
|
44
|
+
at = now();
|
|
45
|
+
return cached;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// What the team service needs to run Rooms on this Mac, built once for the
|
|
2
|
+
// desktop (main/main.mjs) and the browser copy (main/serve.mjs) alike.
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { appHome } from '../store/home.mjs';
|
|
5
|
+
import { readEnv } from '../../shared/product-name.mjs';
|
|
6
|
+
import { readCloudUsage } from '../cloud/usage.mjs';
|
|
7
|
+
import { phoneDevice } from '../phone-inbox.mjs';
|
|
8
|
+
import { roomPrefsFile } from './rooms.mjs';
|
|
9
|
+
import { createRoomHours } from './room-hours.mjs';
|
|
10
|
+
|
|
11
|
+
export function roomsSetup({ config, store, supervisor, userDir, log = (line) => console.log(line) }) {
|
|
12
|
+
const hours = createRoomHours({
|
|
13
|
+
home: appHome(),
|
|
14
|
+
products: () => { try { return store.listProducts({ includeArchived: true }); } catch { return []; } },
|
|
15
|
+
// A copy with a home of its own reads its own cloud runs, not this Mac's.
|
|
16
|
+
cloudLines: () => (readEnv('HOME') ? readCloudUsage({ file: path.join(appHome(), 'cloud-usage.jsonl') }) : readCloudUsage()),
|
|
17
|
+
log,
|
|
18
|
+
});
|
|
19
|
+
return {
|
|
20
|
+
enabled: () => config.alphaRooms === true,
|
|
21
|
+
device: () => phoneDevice(userDir, config.storeRoot),
|
|
22
|
+
prefsFile: roomPrefsFile(config.storeRoot),
|
|
23
|
+
presenceFacts: () => {
|
|
24
|
+
const running = supervisor.runsGoing().map((s) => ({ itemId: s.itemId, product: s.product, startedAt: s.startedAt }));
|
|
25
|
+
const items = running.map((r) => { try { return store.readItem(r.product, r.itemId); } catch { return null; } }).filter(Boolean);
|
|
26
|
+
let products = [];
|
|
27
|
+
try { products = store.listProducts(); } catch { /* none to name */ }
|
|
28
|
+
return { running, items, products, summary: hours.summary() };
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|