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,135 @@
|
|
|
1
|
+
// THE RUN LEDGER: one line per finished run, for agent hours (w-3d0fa23ffb).
|
|
2
|
+
//
|
|
3
|
+
// `<appHome>/runs.jsonl`, written when a run ends (supervisor `_recordRun`).
|
|
4
|
+
// Cloud runs keep their own ledger (main/cloud/usage.mjs) and are read from
|
|
5
|
+
// there. Old runs are read once from their trace logs (`backfillRuns`). Every
|
|
6
|
+
// line has a key, and every reader dedupes on it, so a run written twice is
|
|
7
|
+
// counted once.
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { appHome, machineryDir } from './store/home.mjs';
|
|
11
|
+
import { dedupeRuns, runFromTrace } from '../shared/agent-hours.mjs';
|
|
12
|
+
|
|
13
|
+
const BACKFILL_DAYS = 400;
|
|
14
|
+
const TAIL_BYTES = 400;
|
|
15
|
+
// A log with no exit line that was written to this recently is a run still
|
|
16
|
+
// going; it writes its own line when it ends.
|
|
17
|
+
const STILL_GOING_MS = 30 * 60_000;
|
|
18
|
+
|
|
19
|
+
export function runLedgerPath(home = appHome()) {
|
|
20
|
+
return path.join(home, 'runs.jsonl');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function appendRun(run, { file = runLedgerPath() } = {}) {
|
|
24
|
+
try {
|
|
25
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
26
|
+
fs.appendFileSync(file, JSON.stringify(run) + '\n');
|
|
27
|
+
return true;
|
|
28
|
+
} catch {
|
|
29
|
+
// A run that cannot be written never fails the run.
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function readRuns({ file = runLedgerPath() } = {}) {
|
|
35
|
+
let text;
|
|
36
|
+
try { text = fs.readFileSync(file, 'utf8'); } catch { return []; }
|
|
37
|
+
const out = [];
|
|
38
|
+
for (const line of text.split('\n')) {
|
|
39
|
+
if (!line.trim()) continue;
|
|
40
|
+
try { out.push(JSON.parse(line)); } catch { /* one broken line loses only itself */ }
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function runsFromCloudUsage(lines) {
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const l of lines ?? []) {
|
|
48
|
+
const at = Date.parse(l?.at);
|
|
49
|
+
const seconds = Number(l?.wallSeconds);
|
|
50
|
+
if (!Number.isFinite(at) || !(seconds > 0)) continue;
|
|
51
|
+
out.push({
|
|
52
|
+
key: `cloud:${l.runId ?? at}`,
|
|
53
|
+
at,
|
|
54
|
+
start: at - seconds * 1000,
|
|
55
|
+
item: l.item ?? null,
|
|
56
|
+
product: null,
|
|
57
|
+
engine: l.engine ?? null,
|
|
58
|
+
seconds,
|
|
59
|
+
failed: Number.isInteger(l.exitCode) ? l.exitCode !== 0 : null,
|
|
60
|
+
where: 'cloud',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function allRuns({ file = runLedgerPath(), cloudLines = [] } = {}) {
|
|
67
|
+
return dedupeRuns([...readRuns({ file }), ...runsFromCloudUsage(cloudLines)]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function tailOf(file) {
|
|
71
|
+
let fd;
|
|
72
|
+
try {
|
|
73
|
+
fd = fs.openSync(file, 'r');
|
|
74
|
+
const size = fs.fstatSync(fd).size;
|
|
75
|
+
const len = Math.min(size, TAIL_BYTES);
|
|
76
|
+
const buf = Buffer.alloc(len);
|
|
77
|
+
fs.readSync(fd, buf, 0, len, size - len);
|
|
78
|
+
return buf.toString('utf8');
|
|
79
|
+
} catch {
|
|
80
|
+
return '';
|
|
81
|
+
} finally {
|
|
82
|
+
if (fd !== undefined) try { fs.closeSync(fd); } catch {}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Reads every run log of this store's projects into the ledger, once per run.
|
|
87
|
+
// Resolves to how many runs it added. It hands the event loop back every
|
|
88
|
+
// YIELD_EVERY logs: on the founder's Mac it reads 25,780 logs in 3.4 s, which
|
|
89
|
+
// in one piece would freeze the window.
|
|
90
|
+
const YIELD_EVERY = 200;
|
|
91
|
+
const breathe = () => new Promise((r) => setImmediate(r));
|
|
92
|
+
|
|
93
|
+
export async function backfillRuns({ home = appHome(), products = [], file = runLedgerPath(home), now = Date.now() } = {}) {
|
|
94
|
+
const have = new Set(readRuns({ file }).map((r) => r?.key));
|
|
95
|
+
const since = now - BACKFILL_DAYS * 86_400_000;
|
|
96
|
+
let added = 0;
|
|
97
|
+
let read = 0;
|
|
98
|
+
let pending = [];
|
|
99
|
+
const flush = () => {
|
|
100
|
+
if (!pending.length) return;
|
|
101
|
+
try {
|
|
102
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
103
|
+
fs.appendFileSync(file, pending.map((r) => JSON.stringify(r)).join('\n') + '\n');
|
|
104
|
+
added += pending.length;
|
|
105
|
+
} catch { /* the next backfill tries again */ }
|
|
106
|
+
pending = [];
|
|
107
|
+
};
|
|
108
|
+
for (const p of products) {
|
|
109
|
+
if (!p?.dir) continue;
|
|
110
|
+
const sessions = path.join(machineryDir(p.dir, home), 'sessions');
|
|
111
|
+
let items;
|
|
112
|
+
try { items = fs.readdirSync(sessions, { withFileTypes: true }); } catch { continue; }
|
|
113
|
+
for (const it of items) {
|
|
114
|
+
if (!it.isDirectory()) continue;
|
|
115
|
+
let logs;
|
|
116
|
+
try { logs = fs.readdirSync(path.join(sessions, it.name)); } catch { continue; }
|
|
117
|
+
for (const name of logs) {
|
|
118
|
+
const start = Number(/^(\d{12,14})\.log$/.exec(name)?.[1]);
|
|
119
|
+
if (!Number.isFinite(start) || start < since || start > now) continue;
|
|
120
|
+
if (++read % YIELD_EVERY === 0) { flush(); await breathe(); }
|
|
121
|
+
const full = path.join(sessions, it.name, name);
|
|
122
|
+
let mtimeMs;
|
|
123
|
+
try { mtimeMs = fs.statSync(full).mtimeMs; } catch { continue; }
|
|
124
|
+
const tail = tailOf(full);
|
|
125
|
+
if (!/# exited \(/.test(tail) && now - mtimeMs < STILL_GOING_MS) continue;
|
|
126
|
+
const run = runFromTrace({ name, text: tail, mtimeMs, item: it.name, product: p.slug ?? null });
|
|
127
|
+
if (!run || have.has(run.key)) continue;
|
|
128
|
+
pending.push({ ...run, backfilled: true });
|
|
129
|
+
have.add(run.key);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
flush();
|
|
134
|
+
return added;
|
|
135
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// SAVING THE FILES A MESSAGE CARRIES, into a project's attachments folder.
|
|
2
|
+
//
|
|
3
|
+
// One copy, used by the reply box (main/ipc.mjs zero:save-attachments) and by
|
|
4
|
+
// a message to a person (main/team/index.mjs), whose conversation may not
|
|
5
|
+
// exist until the message makes it, so it cannot save them beforehand.
|
|
6
|
+
// Relative paths keep them readable by workers through the MCP and renderable
|
|
7
|
+
// in the message view, and they are the names the team sync sends on
|
|
8
|
+
// (main/team/sync.mjs).
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* `files` are `{ name, dataBase64 }` or `{ name, srcPath }`. Answers what was
|
|
14
|
+
* saved: `{ rel, name, image }`, in order, skipping one with neither.
|
|
15
|
+
*/
|
|
16
|
+
export function saveAttachments(dir, files) {
|
|
17
|
+
const outDir = path.join(dir, 'attachments');
|
|
18
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
19
|
+
const saved = [];
|
|
20
|
+
for (const file of files ?? []) {
|
|
21
|
+
const safe = String(file?.name ?? 'file').replace(/[^\w.-]+/g, '-').slice(0, 80);
|
|
22
|
+
// A RANDOM SUFFIX, THE SAME ONE THE STAGING PATH ALREADY USES. The name
|
|
23
|
+
// was the millisecond plus the filename, so two files saved in the same
|
|
24
|
+
// millisecond with the same name overwrote each other and the message
|
|
25
|
+
// ended up with two links to one picture. Screenshots are often sent in
|
|
26
|
+
// pairs, which is exactly the shape that collides. It has never been observed
|
|
27
|
+
// happening; it is one line to make impossible.
|
|
28
|
+
const rel = `attachments/${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}-${safe}`;
|
|
29
|
+
const abs = path.join(dir, rel);
|
|
30
|
+
if (file?.dataBase64) fs.writeFileSync(abs, Buffer.from(file.dataBase64, 'base64'));
|
|
31
|
+
else if (file?.srcPath) fs.copyFileSync(file.srcPath, abs);
|
|
32
|
+
else continue;
|
|
33
|
+
saved.push({ rel, name: safe, image: /\.(png|jpe?g|gif|webp)$/i.test(safe) });
|
|
34
|
+
}
|
|
35
|
+
return saved;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The links a message carries for what was saved, one per line. */
|
|
39
|
+
export function attachmentMarkdown(saved) {
|
|
40
|
+
return saved.map((s) => (s.image ? `` : `[${s.name}](${s.rel})`)).join('\n');
|
|
41
|
+
}
|
package/main/serve.mjs
CHANGED
|
@@ -30,7 +30,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
30
30
|
|
|
31
31
|
import { spawn } from 'node:child_process';
|
|
32
32
|
import { Readable } from 'node:stream';
|
|
33
|
-
import { mediaResponse, mediaType } from './img-scheme.mjs';
|
|
33
|
+
import { mediaResponse, mediaType, servable } from './img-scheme.mjs';
|
|
34
|
+
import { readPictureShape } from './picture-shape.mjs';
|
|
34
35
|
import { loadConfig } from './config.mjs';
|
|
35
36
|
import { Store } from './store.mjs';
|
|
36
37
|
import { Supervisor } from './supervisor.mjs';
|
|
@@ -40,7 +41,9 @@ import { reportFromRenderer } from './crash-report.mjs';
|
|
|
40
41
|
import * as workItemsDisk from './store/work-items.mjs';
|
|
41
42
|
import { createTeamService, teamStateFile } from './team/index.mjs';
|
|
42
43
|
import { loadCloudConfig, supabaseSession, headlessSessionFile } from './team/session.mjs';
|
|
44
|
+
import { roomsSetup } from './team/room-setup.mjs';
|
|
43
45
|
import { appHome, carryMisplacedMachinery, storeRootEnv } from './store/home.mjs';
|
|
46
|
+
import { keepAnswerModesIn } from './answer-modes.mjs';
|
|
44
47
|
|
|
45
48
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
46
49
|
const repoRoot = path.dirname(here);
|
|
@@ -143,6 +146,8 @@ export function answerWhatOnlyTheWindowAnswered(ipcMain) {
|
|
|
143
146
|
ipcMain.handle('zero:corner-tag', () => null);
|
|
144
147
|
ipcMain.handle('zero:notify', () => null);
|
|
145
148
|
ipcMain.handle('zero:boot-info', () => ({ reloaded: false, builtAt: null, recovered: null }));
|
|
149
|
+
// A room invite link only ever opens the desktop app.
|
|
150
|
+
ipcMain.handle('zero:take-join', () => null);
|
|
146
151
|
ipcMain.handle('zero:crash', (_e, payload) => { reportFromRenderer(payload ?? {}); return null; });
|
|
147
152
|
// The phone door belongs to the desktop app (main/phone-link.mjs); a tab is told there is none.
|
|
148
153
|
ipcMain.handle('zero:phone-link', () => null);
|
|
@@ -175,6 +180,7 @@ export async function bootHeadless({ dataDir = repoRoot, appDir = repoRoot, user
|
|
|
175
180
|
const inheritedHome = appHome();
|
|
176
181
|
Object.assign(process.env, storeRootEnv(config.storeRoot));
|
|
177
182
|
carryMisplacedMachinery(inheritedHome, config.storeRoot);
|
|
183
|
+
keepAnswerModesIn({ userDir, storeRoot: config.storeRoot });
|
|
178
184
|
const store = await new Store(config).init();
|
|
179
185
|
const supervisor = new Supervisor(config, store, appDir, dataDir, userDir);
|
|
180
186
|
const window = broadcastingWindow();
|
|
@@ -196,6 +202,7 @@ export async function bootHeadless({ dataDir = repoRoot, appDir = repoRoot, user
|
|
|
196
202
|
accountRoot: config.accountRoot,
|
|
197
203
|
stateFile: teamStateFile(config.storeRoot),
|
|
198
204
|
onChange: () => ipc?.push?.(),
|
|
205
|
+
rooms: roomsSetup({ config, store, supervisor, userDir }),
|
|
199
206
|
}) : null;
|
|
200
207
|
// AND IT STOPS ITS OWN WORKERS ON THE WAY OUT, as main/main.mjs does for the
|
|
201
208
|
// desktop. Control-C reaches the whole process group, so without this each
|
|
@@ -212,6 +219,23 @@ export async function bootHeadless({ dataDir = repoRoot, appDir = repoRoot, user
|
|
|
212
219
|
/** A token in the url, because the port alone is not a door key. */
|
|
213
220
|
export const newToken = () => crypto.randomBytes(24).toString('base64url');
|
|
214
221
|
|
|
222
|
+
/**
|
|
223
|
+
* THE PICTURES A TAB MAY DRAW: the desktop window's own rule (`servable`,
|
|
224
|
+
* main/img-scheme.mjs) over its own roots (`imageRoots`, main/main.mjs), so a
|
|
225
|
+
* door opened for a screenshot can never hand out `zero.config.json`.
|
|
226
|
+
*
|
|
227
|
+
* Pictures only. A whole document is served under a grant the pane opens one
|
|
228
|
+
* at a time (main/doc-scheme.mjs) and this copy opens none.
|
|
229
|
+
*/
|
|
230
|
+
export function picturesTheAppOwns({ config, store }) {
|
|
231
|
+
return {
|
|
232
|
+
allows: (kind, file) => kind === 'img' && servable(file, [
|
|
233
|
+
config?.accountRoot,
|
|
234
|
+
...(store?.listProducts?.() ?? []).map((p) => p.repoPath),
|
|
235
|
+
].filter(Boolean)),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
215
239
|
/**
|
|
216
240
|
* THE KEY THAT ONLY READS FILES. A design page runs its own scripts and can
|
|
217
241
|
* read its own address, so the address a phone loads it from must not carry
|
|
@@ -254,14 +278,24 @@ const MIME = {
|
|
|
254
278
|
*/
|
|
255
279
|
async function answerFile(req, res, url, { token, files }) {
|
|
256
280
|
const [, , key, kind, ...rest] = url.pathname.split('/');
|
|
257
|
-
if (kind !== 'doc' && kind !== 'img') { res.writeHead(404).end('No such kind of file.'); return; }
|
|
281
|
+
if (kind !== 'doc' && kind !== 'img' && kind !== 'shape') { res.writeHead(404).end('No such kind of file.'); return; }
|
|
258
282
|
if (!sameKey(key, fileKey(typeof token === 'function' ? token() : token))) { res.writeHead(403).end('Wrong key.'); return; }
|
|
259
283
|
let file;
|
|
260
284
|
try { file = path.normalize('/' + rest.map(decodeURIComponent).join('/')); } catch { res.writeHead(400).end('Bad path.'); return; }
|
|
261
|
-
|
|
285
|
+
// A shape is asked of a picture, under the picture's own rule.
|
|
286
|
+
if (!path.isAbsolute(file) || !files.allows(kind === 'shape' ? 'img' : kind, file)) { res.writeHead(403).end('Not available.'); return; }
|
|
262
287
|
let stat;
|
|
263
288
|
try { stat = await fs.promises.stat(file); } catch { res.writeHead(404).end('Not there.'); return; }
|
|
264
289
|
if (!stat.isFile()) { res.writeHead(404).end('Not there.'); return; }
|
|
290
|
+
// ITS WIDTH AND HEIGHT, so a picture the phone has never seen holds its
|
|
291
|
+
// place before its last byte comes (main/picture-shape.mjs).
|
|
292
|
+
if (kind === 'shape') {
|
|
293
|
+
const shape = await readPictureShape(file).catch(() => null);
|
|
294
|
+
if (!shape) { res.writeHead(404).end('Not a picture this can measure.'); return; }
|
|
295
|
+
res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' });
|
|
296
|
+
res.end(JSON.stringify(shape));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
265
299
|
const media = mediaType(file);
|
|
266
300
|
if (media) {
|
|
267
301
|
const out = await mediaResponse(file, req.headers.range);
|
|
@@ -316,6 +350,14 @@ export function createHandler({ channels, token, listeners = new Set(), dist = p
|
|
|
316
350
|
// NOR DOES THE PAGE ITSELF ASK: the tab takes the token out of its address
|
|
317
351
|
// bar on load, so a reload asks for `/` bare, and refusing that drew
|
|
318
352
|
// "Wrong or missing token" on every reload (tests/a-browser-tab-survives-a-reload).
|
|
353
|
+
//
|
|
354
|
+
// AND THE HOST HEADER IS NOT CHECKED, asked and answered 2026-10-24.
|
|
355
|
+
// Refusing a Host that is not localhost is the usual guard against a page
|
|
356
|
+
// whose own name resolves to this machine, and it cannot go here:
|
|
357
|
+
// main/phone-link.mjs serves this same handler to a phone through a relay
|
|
358
|
+
// or over Tailscale, where the Host is neither. The token is the key,
|
|
359
|
+
// whatever name the request arrives under
|
|
360
|
+
// (tests/the-doing-half-runs-with-no-electron).
|
|
319
361
|
const guarded = url.pathname === '/events' || url.pathname.startsWith('/api/') || url.pathname === '/file-key';
|
|
320
362
|
const given = req.headers['x-agentbox-token'] || url.searchParams.get('token');
|
|
321
363
|
if (guarded && !sameKey(given, typeof token === 'function' ? token() : token)) {
|