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
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// ROOMS ON SUPABASE (w-3d0fa23ffb): the same calls as rooms-memory.mjs, against
|
|
2
|
+
// cloud/supabase/migrations/20261021000000_rooms.sql. Row level security and
|
|
3
|
+
// the functions there are what keep people apart; nothing here is trusted to.
|
|
4
|
+
import { Name } from '../../shared/product-name.mjs';
|
|
5
|
+
import { PROFILE_COLS, PRESENCE_COLS, OLD_PRESENCE_COLS, PROFILE_FIELDS, PRESENCE_FIELDS, OLD_PRESENCE_FIELDS, MSG_COLS, REACTION_COLS, pick, roomOut, messageOut, reactionOut, fileExists } from './rooms-shape.mjs';
|
|
6
|
+
|
|
7
|
+
// How long the chat waits to hear that reactions are listening before it says
|
|
8
|
+
// it is ready anyway.
|
|
9
|
+
const REACTIONS_WAIT_MS = 10_000;
|
|
10
|
+
// The columns a cloud from before 20261022000000_room_messages_do_more.sql has.
|
|
11
|
+
const OLD_MSG_COLS = 'id,room_id,by_person,body,in_reply_to,mentions,created_at';
|
|
12
|
+
const OLD_RECHECK_MS = 60_000;
|
|
13
|
+
// Postgres and PostgREST's words for a column or a table that is not there.
|
|
14
|
+
const notThere = (err) => ['42703', '42P01', '42883', 'PGRST202', 'PGRST204', 'PGRST205'].includes(String(err?.code ?? ''))
|
|
15
|
+
|| /does not exist|could not find/i.test(String(err?.message ?? ''));
|
|
16
|
+
|
|
17
|
+
export function roomsSupabase(client, myId, must) {
|
|
18
|
+
const rpc = async (fn, args, what) => must(await client.rpc(fn, args), what);
|
|
19
|
+
// AN APP CAN BE UPDATED BEFORE ITS CLOUD IS, since the cloud's update is run
|
|
20
|
+
// by hand. Until it is, messages are read on the columns it has and nothing
|
|
21
|
+
// hangs off them, rather than the whole chat failing to read.
|
|
22
|
+
// The update can land while the app is open, so an old cloud is asked again
|
|
23
|
+
// after a while. Anything only an updated cloud can do settles it for good,
|
|
24
|
+
// since a cloud is never taken back, so a late answer from before is ignored.
|
|
25
|
+
let oldSince = 0;
|
|
26
|
+
let updated = false;
|
|
27
|
+
const isOld = () => !updated && oldSince > 0 && Date.now() - oldSince < OLD_RECHECK_MS;
|
|
28
|
+
const settle = () => { updated = true; oldSince = 0; };
|
|
29
|
+
// The same for presence on a cloud from before 20261022000100: no activity.
|
|
30
|
+
let noActivitySince = 0;
|
|
31
|
+
const noActivity = () => noActivitySince > 0 && Date.now() - noActivitySince < OLD_RECHECK_MS;
|
|
32
|
+
const reading = async (make, what) => {
|
|
33
|
+
const old = isOld();
|
|
34
|
+
let res = await make(old ? OLD_MSG_COLS : MSG_COLS, old);
|
|
35
|
+
if (res.error && !old && notThere(res.error)) {
|
|
36
|
+
if (!updated) oldSince = Date.now();
|
|
37
|
+
res = await make(OLD_MSG_COLS, true);
|
|
38
|
+
} else if (!old && !res.error) settle();
|
|
39
|
+
return must(res, what);
|
|
40
|
+
};
|
|
41
|
+
// What only an updated cloud can do says so, in place of a database error.
|
|
42
|
+
const BEHIND = 'Your team cloud needs its latest update for this. Ask whoever set it up to apply it.';
|
|
43
|
+
const newer = async (res, what) => {
|
|
44
|
+
if (res.error && notThere(res.error)) throw new Error(BEHIND);
|
|
45
|
+
const data = must(res, what);
|
|
46
|
+
settle();
|
|
47
|
+
return data;
|
|
48
|
+
};
|
|
49
|
+
const newRpc = async (fn, args, what) => newer(await client.rpc(fn, args), what);
|
|
50
|
+
|
|
51
|
+
// A row of my own: changed where it is, made when it is not there. Never an
|
|
52
|
+
// upsert, whose update half rewrites the key columns, which nobody may
|
|
53
|
+
// update on the hosted cloud ("permission denied", 2026-10-21).
|
|
54
|
+
async function saveOwn(table, keys, fields, cols, what) {
|
|
55
|
+
const change = async () => {
|
|
56
|
+
let q = Object.keys(fields).length ? client.from(table).update(fields) : client.from(table).select(cols);
|
|
57
|
+
for (const [k, v] of Object.entries(keys)) q = q.eq(k, v);
|
|
58
|
+
return must(await (Object.keys(fields).length ? q.select(cols) : q), what);
|
|
59
|
+
};
|
|
60
|
+
const had = await change();
|
|
61
|
+
if (had.length) return had[0];
|
|
62
|
+
const made = await client.from(table).insert({ ...keys, ...fields }).select(cols);
|
|
63
|
+
// Another Mac of mine made it a moment ago.
|
|
64
|
+
if (made.error?.code === '23505') return (await change())[0] ?? null;
|
|
65
|
+
return must(made, what)[0] ?? null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
async myRooms() {
|
|
70
|
+
await myId();
|
|
71
|
+
return ((await rpc('my_rooms', {}, 'reading your rooms')) ?? []).map(roomOut);
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
async roomPeople(roomId) {
|
|
75
|
+
await myId();
|
|
76
|
+
const rows = (await rpc('room_people', { r: roomId }, 'reading who is in the room')) ?? [];
|
|
77
|
+
return rows.map((r) => (typeof r === 'string' ? r : r.room_people ?? r.person_id)).filter(Boolean);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// Null on a cloud without room_names, which the wall and chat read as "use the cards".
|
|
81
|
+
async roomNames(roomId) {
|
|
82
|
+
await myId();
|
|
83
|
+
const res = await client.rpc('room_names', { r: roomId });
|
|
84
|
+
if (res.error && notThere(res.error)) return null;
|
|
85
|
+
return (must(res, 'reading the names in the room') ?? []).map((r) => ({ person_id: r.person_id, name: r.name ?? '', avatar_url: r.avatar_url ?? null, has_card: !!r.has_card }));
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
async roomProfiles(ids) {
|
|
89
|
+
await myId();
|
|
90
|
+
if (!ids?.length) return [];
|
|
91
|
+
return must(await client.from('room_profiles').select(PROFILE_COLS).in('person_id', ids), 'reading people in your rooms');
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
async roomPresence(ids) {
|
|
95
|
+
await myId();
|
|
96
|
+
if (!ids?.length) return [];
|
|
97
|
+
const read = () => client.from('room_presence').select(noActivity() ? OLD_PRESENCE_COLS : PRESENCE_COLS).in('person_id', ids);
|
|
98
|
+
let res = await read();
|
|
99
|
+
if (res.error && !noActivity() && notThere(res.error)) { noActivitySince = Date.now(); res = await read(); }
|
|
100
|
+
return must(res, 'reading who is working');
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
async myRoomProfile() {
|
|
104
|
+
const me = await myId();
|
|
105
|
+
const rows = must(await client.from('room_profiles').select(PROFILE_COLS).eq('person_id', me).limit(1), 'reading your room profile');
|
|
106
|
+
return rows[0] ?? null;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
async saveRoomProfile(fields) {
|
|
110
|
+
const me = await myId();
|
|
111
|
+
return saveOwn('room_profiles', { person_id: me }, pick(fields, PROFILE_FIELDS), PROFILE_COLS, 'saving your room profile');
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
async putPresence(device, fields) {
|
|
115
|
+
const me = await myId();
|
|
116
|
+
const put = () => saveOwn('room_presence', { person_id: me, device }, pick(fields, noActivity() ? OLD_PRESENCE_FIELDS : PRESENCE_FIELDS), 'person_id', 'telling your rooms what you are doing');
|
|
117
|
+
try { await put(); } catch (err) {
|
|
118
|
+
if (noActivity() || !('activity' in (fields ?? {})) || !notThere(err)) throw err;
|
|
119
|
+
noActivitySince = Date.now();
|
|
120
|
+
await put();
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
async roomMessages(roomId, { before = null, limit = 200 } = {}) {
|
|
125
|
+
await myId();
|
|
126
|
+
return (await reading((cols) => {
|
|
127
|
+
let q = client.from('room_messages').select(cols).eq('room_id', roomId).order('created_at', { ascending: false }).limit(limit);
|
|
128
|
+
if (before) q = q.lt('created_at', new Date(before).toISOString());
|
|
129
|
+
return q;
|
|
130
|
+
}, 'reading the chat')).map(messageOut).reverse();
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
// My newest mentions in every room I am in, however busy the room is.
|
|
134
|
+
async roomMentions({ limit = 50 } = {}) {
|
|
135
|
+
const me = await myId();
|
|
136
|
+
return (await reading((cols, old) => {
|
|
137
|
+
let q = client.from('room_messages').select(cols).contains('mentions', [me]);
|
|
138
|
+
if (!old) q = q.is('deleted_at', null);
|
|
139
|
+
return q.order('created_at', { ascending: false }).limit(limit);
|
|
140
|
+
}, 'reading your mentions')).map(messageOut);
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
async sendRoomMessage(roomId, { body, inReplyTo = null, mentions = [] }) {
|
|
144
|
+
const me = await myId();
|
|
145
|
+
const row = await reading((cols) => client.from('room_messages').insert({ room_id: roomId, by_person: me, body, in_reply_to: inReplyTo, mentions }).select(cols).single(), 'sending your message');
|
|
146
|
+
return messageOut(row);
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
async editRoomMessage(messageId, { body, mentions = [] }) {
|
|
150
|
+
await myId();
|
|
151
|
+
const row = await newRpc('edit_room_message', { m: messageId, p_body: body, p_mentions: mentions }, 'editing your message');
|
|
152
|
+
return messageOut(Array.isArray(row) ? row[0] : row);
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
async deleteRoomMessage(messageId) {
|
|
156
|
+
await myId();
|
|
157
|
+
await newRpc('delete_room_message', { m: messageId }, 'deleting your message');
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Off ones too, since the service folds them. The newest 1000, oldest first.
|
|
161
|
+
async roomReactions(roomId) {
|
|
162
|
+
await myId();
|
|
163
|
+
if (isOld()) return [];
|
|
164
|
+
const res = await client.from('room_reactions').select(REACTION_COLS).eq('room_id', roomId).order('updated_at', { ascending: false }).limit(1000);
|
|
165
|
+
if (res.error && notThere(res.error)) return [];
|
|
166
|
+
return (await newer(res, 'reading reactions')).map(reactionOut).reverse();
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
async reactRoomMessage(messageId, emoji, on = true) {
|
|
170
|
+
await myId();
|
|
171
|
+
await newRpc('react_room_message', { m: messageId, p_emoji: emoji, p_on: !!on }, 'reacting');
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
async putRoomFile(roomId, name, data) {
|
|
175
|
+
const me = await myId();
|
|
176
|
+
const { error } = await client.from('room_files').insert({ room_id: roomId, name, by_person: me, data });
|
|
177
|
+
if (error?.code === '23505') { settle(); throw fileExists(name); }
|
|
178
|
+
if (error && notThere(error)) throw new Error(BEHIND);
|
|
179
|
+
if (error) throw new Error(`sending the file: ${error.message}`);
|
|
180
|
+
settle();
|
|
181
|
+
return name;
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
async roomFile(roomId, name) {
|
|
185
|
+
await myId();
|
|
186
|
+
const rows = await newer(await client.from('room_files').select('data').eq('room_id', roomId).eq('name', name).limit(1), 'taking a file from the room');
|
|
187
|
+
return rows[0]?.data ?? null;
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
async roomReads() {
|
|
191
|
+
const me = await myId();
|
|
192
|
+
const rows = must(await client.from('room_reads').select('room_id,read_at').eq('person_id', me), 'reading what you have read');
|
|
193
|
+
return rows.map((r) => ({ roomId: r.room_id, readAt: Date.parse(r.read_at) }));
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
async markRoomRead(roomId, at) {
|
|
197
|
+
await myId();
|
|
198
|
+
await rpc('mark_room_read', { r: roomId, at: new Date(at).toISOString() }, 'marking the room read');
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
async markRoomUnread(roomId, at) {
|
|
202
|
+
await myId();
|
|
203
|
+
await newRpc('mark_room_unread', { r: roomId, at: new Date(at).toISOString() }, 'marking the room unread');
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
async roomLink(roomId) {
|
|
207
|
+
await myId();
|
|
208
|
+
return rpc('room_link', { r: roomId }, 'making an invite link');
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
async revokeRoomLink(roomId) {
|
|
212
|
+
await myId();
|
|
213
|
+
return rpc('revoke_room_link', { r: roomId }, 'turning the old link off');
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
async joinRoomByLink(token) {
|
|
217
|
+
await myId();
|
|
218
|
+
return rpc('join_room_by_link', { tok: token }, 'joining the room');
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
async joinListedRoom(roomId) {
|
|
222
|
+
await myId();
|
|
223
|
+
return rpc('join_listed_room', { r: roomId }, 'joining the room');
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
async listedRooms() {
|
|
227
|
+
await myId();
|
|
228
|
+
const rows = (await rpc('listed_rooms', {}, 'finding rooms')) ?? [];
|
|
229
|
+
return rows.map((r) => ({ id: r.id, name: r.name, members: r.members, hereNow: r.here_now, joined: !!r.joined, faces: r.faces ?? [], createdAt: Date.parse(r.created_at) || 0 }));
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
async roomPreview(token) {
|
|
233
|
+
const rows = (await rpc('room_preview', { tok: token }, 'reading the invite')) ?? [];
|
|
234
|
+
const r = rows[0];
|
|
235
|
+
return r ? { name: r.name, members: r.members, agentsRunning: r.agents_running, faces: r.faces ?? [], invitedBy: r.invited_by_name ? { name: r.invited_by_name, avatar: r.invited_by_avatar ?? null } : null } : null;
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
async createRoom({ name, kind = 'room', listed = false, people = [], inviteOnly = false }) {
|
|
239
|
+
await myId();
|
|
240
|
+
return rpc('create_room', { p_name: name, p_kind: kind, p_listed: listed, p_people: people, p_invite_only: inviteOnly }, 'starting the room');
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
async renameRoom(roomId, name) {
|
|
244
|
+
await myId();
|
|
245
|
+
const rows = must(await client.from('rooms').update({ name: String(name).trim() }).eq('id', roomId).select('id'), 'renaming the room');
|
|
246
|
+
if (!rows.length) throw new Error('only the room’s owner can rename it');
|
|
247
|
+
},
|
|
248
|
+
|
|
249
|
+
async leaveRoom(roomId) {
|
|
250
|
+
const me = await myId();
|
|
251
|
+
must(await client.from('room_members').delete().eq('room_id', roomId).eq('person_id', me), 'leaving the room');
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
async removeRoomMember(roomId, personId) {
|
|
255
|
+
await myId();
|
|
256
|
+
await rpc('remove_room_member', { r: roomId, p: personId }, 'removing them from the room');
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
async inviteToRoom(roomId, email) {
|
|
260
|
+
const me = await myId();
|
|
261
|
+
const clean = String(email).trim().toLowerCase();
|
|
262
|
+
const { error } = await client.from('room_email_invites').insert({ room_id: roomId, email: clean, invited_by: me });
|
|
263
|
+
if (error && error.code !== '23505') throw new Error(`inviting ${clean}: ${error.message}`);
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
async roomInvitesSent(roomId) {
|
|
267
|
+
const me = await myId();
|
|
268
|
+
const rows = must(await client.from('room_email_invites').select('email, created_at, accepted_at')
|
|
269
|
+
.eq('room_id', roomId).eq('invited_by', me).order('created_at', { ascending: false }).limit(50), 'reading who you invited');
|
|
270
|
+
return rows.map((r) => ({ email: r.email, at: Date.parse(r.created_at) || 0, joined: !!r.accepted_at }));
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
async pendingRoomInvites() {
|
|
274
|
+
await myId();
|
|
275
|
+
const rows = (await rpc('pending_room_invites', {}, 'reading your room invites')) ?? [];
|
|
276
|
+
return rows.map((r) => ({ roomId: r.room_id, roomName: r.room_name, invitedBy: r.invited_by ?? null, invitedByName: r.invited_by_name || null }));
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
async acceptRoomInvite(roomId) {
|
|
280
|
+
await myId();
|
|
281
|
+
return rpc('accept_room_invite', { r: roomId }, 'joining the room');
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
async agentboxRanks(ids) {
|
|
285
|
+
await myId();
|
|
286
|
+
const rows = (await rpc('agentbox_ranks', { ps: ids ?? [] }, `reading places on ${Name}`)) ?? [];
|
|
287
|
+
return Object.fromEntries(rows.map((r) => [r.person_id, r.rank]));
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
// Realtime sends inserts and updates through row level security, never a
|
|
291
|
+
// delete, so leaving is heard as a room_events or access_events insert.
|
|
292
|
+
// Subscribing takes a few seconds and a dropped socket subscribes again;
|
|
293
|
+
// each time it is told, so whatever was sent meanwhile is read.
|
|
294
|
+
subscribeRooms(onEvent) {
|
|
295
|
+
// SUBSCRIBED IS SAID ONCE BOTH CHANNELS LISTEN, or the reactions one has
|
|
296
|
+
// given up, since what is said on it is read again then. Said first by
|
|
297
|
+
// the chat's channel alone, a reaction made in the moment between was
|
|
298
|
+
// heard by nobody (measured on a local cloud). A channel that drops and
|
|
299
|
+
// subscribes again says it again.
|
|
300
|
+
let chatUp = false;
|
|
301
|
+
let reactionsSettled = false;
|
|
302
|
+
const ready = () => { if (chatUp && reactionsSettled) onEvent({ kind: 'subscribed' }); };
|
|
303
|
+
const channel = client.channel(`rooms-${Math.random().toString(36).slice(2)}`)
|
|
304
|
+
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'room_messages' }, (p) => onEvent({ kind: 'message', message: messageOut(p.new) }))
|
|
305
|
+
// An edit, and a delete, which empties the message rather than removing it.
|
|
306
|
+
.on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'room_messages' }, (p) => onEvent({ kind: 'message', message: messageOut(p.new) }))
|
|
307
|
+
.on('postgres_changes', { event: '*', schema: 'public', table: 'room_presence' }, () => onEvent({ kind: 'presence' }))
|
|
308
|
+
.on('postgres_changes', { event: '*', schema: 'public', table: 'room_profiles' }, () => onEvent({ kind: 'profile' }))
|
|
309
|
+
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'room_events' }, () => onEvent({ kind: 'membership' }))
|
|
310
|
+
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'access_events' }, () => onEvent({ kind: 'membership' }))
|
|
311
|
+
.subscribe((status) => { if (status === 'SUBSCRIBED') { chatUp = true; ready(); } });
|
|
312
|
+
// REACTIONS ON A CHANNEL OF THEIR OWN: one change the server cannot
|
|
313
|
+
// follow fails its whole channel, and a cloud not yet updated has no
|
|
314
|
+
// room_reactions table. Turning a reaction off is an update too.
|
|
315
|
+
const reacting = client.channel(`room-reactions-${Math.random().toString(36).slice(2)}`)
|
|
316
|
+
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'room_reactions' }, (p) => onEvent({ kind: 'reaction', reaction: reactionOut(p.new) }))
|
|
317
|
+
.on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'room_reactions' }, (p) => onEvent({ kind: 'reaction', reaction: reactionOut(p.new) }))
|
|
318
|
+
.subscribe((status) => { if (['SUBSCRIBED', 'CHANNEL_ERROR', 'TIMED_OUT'].includes(status)) { reactionsSettled = true; ready(); } });
|
|
319
|
+
const giveUp = setTimeout(() => { reactionsSettled = true; ready(); }, REACTIONS_WAIT_MS);
|
|
320
|
+
giveUp.unref?.();
|
|
321
|
+
return () => { clearTimeout(giveUp); client.removeChannel(channel); client.removeChannel(reacting); };
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|