agentbox-app 0.1.12 → 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/acp.mjs +296 -0
- package/main/agent-homes.mjs +158 -0
- package/main/agent-sessions.mjs +311 -260
- package/main/agent-updates.mjs +9 -22
- package/main/agents.mjs +179 -466
- package/main/analytics.mjs +42 -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-bin.mjs +45 -311
- package/main/claude-input.mjs +57 -7
- package/main/claude-launch-env.mjs +54 -0
- package/main/claude-models.mjs +39 -7
- package/main/claude-plan.mjs +64 -16
- package/main/claude-session-open.mjs +28 -0
- package/main/claude-usage-process.mjs +40 -0
- package/main/claude-usage.mjs +94 -59
- 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-bin.mjs +51 -249
- package/main/codex-models.mjs +20 -2
- package/main/codex-session.mjs +246 -506
- package/main/codex-threads-worker.mjs +12 -0
- package/main/codex-threads.mjs +112 -11
- package/main/codex-usage-file.mjs +5 -3
- package/main/codex-usage.mjs +56 -7
- package/main/codex-watch.mjs +83 -5
- package/main/codex.mjs +95 -363
- package/main/config.mjs +37 -5
- package/main/corner-tag.mjs +810 -0
- package/main/crash-report.mjs +26 -3
- package/main/dev-window.mjs +1 -1
- package/main/engine-bin.mjs +545 -0
- package/main/engine-setup.mjs +25 -24
- package/main/feedback.mjs +14 -31
- 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-stream.mjs +208 -0
- package/main/harnesses/claude.mjs +105 -0
- package/main/harnesses/codex.mjs +72 -0
- package/main/harnesses/common.mjs +78 -0
- package/main/harnesses/instructions-file.mjs +116 -0
- package/main/harnesses/opencode.mjs +234 -0
- package/main/harnesses.mjs +65 -0
- package/main/importable-threads.mjs +96 -0
- package/main/ipc.mjs +520 -135
- package/main/leftovers.mjs +117 -25
- package/main/liquid-glass.mjs +26 -0
- package/main/live-replies.mjs +10 -4
- package/main/local-app-frames.mjs +82 -0
- package/main/main-went-red.mjs +330 -0
- package/main/main.mjs +248 -37
- package/main/memory-gate-history.mjs +85 -20
- package/main/memory-gate-server.mjs +129 -11
- package/main/memory-gate.mjs +62 -8
- package/main/memory-log.mjs +124 -0
- 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 +64 -5
- package/main/opencode-server.mjs +271 -0
- package/main/opencode.mjs +287 -0
- package/main/phone-alerts.mjs +117 -0
- package/main/phone-inbox.mjs +625 -0
- package/main/phone-link.mjs +571 -0
- 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-address.mjs +28 -0
- package/main/relay-client.mjs +314 -0
- package/main/repeats.mjs +10 -4
- package/main/row-label.mjs +18 -47
- package/main/run-change.mjs +13 -0
- package/main/run-ledger.mjs +135 -0
- package/main/runs-on.mjs +32 -35
- package/main/save-attachments.mjs +41 -0
- package/main/serve.mjs +155 -9
- package/main/settings.mjs +364 -50
- package/main/shell-path.mjs +234 -0
- package/main/ship-conflict-brief.mjs +119 -0
- package/main/ship-queue.mjs +134 -20
- package/main/sign-in-files.mjs +3 -7
- package/main/source-updater.mjs +45 -11
- package/main/stage-attachment.mjs +68 -0
- package/main/stall-watch.mjs +50 -0
- package/main/start-hold.mjs +73 -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 +227 -53
- package/main/supervisor.mjs +3865 -699
- package/main/task-commands.mjs +13 -17
- package/main/task-folders-offthread.mjs +6 -1
- package/main/task-folders.mjs +897 -105
- package/main/task-terminals.mjs +147 -41
- 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/updater.mjs +71 -9
- package/main/webview-guard.mjs +45 -0
- package/main/worktree-include.mjs +365 -0
- 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 +30 -5
- package/renderer/dist/assets/abnfDiagram-YUKMZFIV-CoWB6YUK.js +1 -0
- package/renderer/dist/assets/arc-Ch0NrCul.js +1 -0
- package/renderer/dist/assets/architectureDiagram-47P4ROYG-CXz9M5tQ.js +36 -0
- package/renderer/dist/assets/blockDiagram-E7TT5TSB-DchGUuHH.js +140 -0
- package/renderer/dist/assets/c4Diagram-CRA5TL53-Cip-zNyr.js +38 -0
- package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
- package/renderer/dist/assets/chunk-2Q5K7J3B-BoXid_2r.js +1 -0
- package/renderer/dist/assets/chunk-5VM5RSS4-Cpez1rRz.js +15 -0
- package/renderer/dist/assets/chunk-7TKQ45FW-DKA84YkE.js +1 -0
- package/renderer/dist/assets/chunk-AW2ZBBNX-Dg-SumPG.js +88 -0
- package/renderer/dist/assets/chunk-FVRAUYC3-06TCvPm1.js +2 -0
- package/renderer/dist/assets/chunk-HTAEGDNF-CMEBy1Td.js +1 -0
- package/renderer/dist/assets/chunk-JWPE2WC7-BJ2ObmV-.js +1 -0
- package/renderer/dist/assets/chunk-KQW6MTUR-CGWOYXUE.js +1 -0
- package/renderer/dist/assets/chunk-XXDRQBXY-CiZp07XC.js +1 -0
- package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-DJNIX719.js +217 -0
- 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-CbkyZOUH.js +1 -0
- package/renderer/dist/assets/cynefin-EF2NZ3EQ-78iOroZO.js +141 -0
- package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-GcCopMs7.js +62 -0
- package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
- package/renderer/dist/assets/dagre-W4DXFKR2-DHLlXLkz.js +4 -0
- package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/renderer/dist/assets/diagram-2UJZ2QOL-DuZSUHcH.js +200 -0
- package/renderer/dist/assets/diagram-OVF4WLC6-Ckxu3cqH.js +24 -0
- package/renderer/dist/assets/diagram-PFPMY2P6-CsTw9Ycn.js +30 -0
- package/renderer/dist/assets/diagram-UMYRVEAY-CTXd8qVg.js +24 -0
- package/renderer/dist/assets/diagram-YEKJPTXX-CJs1T_9m.js +41 -0
- package/renderer/dist/assets/diagram-ZIFT7M5P-CvjI23mf.js +3 -0
- package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-d_IeAzOJ.js +1 -0
- package/renderer/dist/assets/elk-IJKZMXRS-zo9KFP3N.js +27 -0
- package/renderer/dist/assets/erDiagram-O2IAPWRE-DqfYf5Dt.js +99 -0
- package/renderer/dist/assets/flowDiagram-OXPTDLAJ-DAWsMLmo.js +213 -0
- package/renderer/dist/assets/ganttDiagram-R7TSEDQI-4ml_bhwR.js +292 -0
- package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-B_WcqSId.js +106 -0
- package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
- package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-CI-jYqzc.js +2 -0
- package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
- package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-B56VWRlV.js +70 -0
- package/renderer/dist/assets/journeyDiagram-COXZFDF6-D1-p09Lr.js +139 -0
- package/renderer/dist/assets/kanban-definition-P3RFRI5V-Cv3w7JoN.js +89 -0
- package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
- package/renderer/dist/assets/linear-C35V2Spi.js +1 -0
- package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
- package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
- package/renderer/dist/assets/mermaid.core-BzGT5Vo9.js +314 -0
- package/renderer/dist/assets/mindmap-definition-LZFPQGKD-BZUfuymC.js +96 -0
- package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/renderer/dist/assets/pegDiagram-BGZESJAR-DFh9XNGe.js +1 -0
- package/renderer/dist/assets/pieDiagram-CAPJLFHJ-C65tPVsy.js +39 -0
- package/renderer/dist/assets/product-name-CSNW6d7s.js +40 -0
- package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-Cushj_nk.js +7 -0
- package/renderer/dist/assets/railroadDiagram-SM67HX2B-DZGebGx5.js +1 -0
- package/renderer/dist/assets/requirementDiagram-X7JNWC4A-DpAB0MNo.js +84 -0
- package/renderer/dist/assets/sankeyDiagram-UM26HJRW-D-29rHra.js +40 -0
- package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-FfOQAZol.js +169 -0
- package/renderer/dist/assets/sizeCapture-INFHLROL-C_n6fDBY.js +1 -0
- package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-BZjenzJG.js +291 -0
- package/renderer/dist/assets/swimlanes-N4OXWK64-DxaMU9vX.js +1 -0
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
- package/renderer/dist/assets/timeline-definition-YOQAKGHF-DTg5HJKz.js +120 -0
- package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-Bc3MZDsl.js +370 -0
- package/renderer/dist/assets/vennDiagram-BLWOH2XV-GIVGk9Id.js +34 -0
- package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-NmJnbQOg.js +78 -0
- package/renderer/dist/assets/xychartDiagram-TAQBALBS-p-kfGfnW.js +7 -0
- package/renderer/dist/corner-tag.html +13 -0
- package/renderer/dist/emulator/audio-rules.mjs +74 -0
- package/renderer/dist/emulator/data/compression/README.md +11 -0
- package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
- package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
- package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
- package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
- package/renderer/dist/emulator/data/cores/README.md +22 -0
- package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/package.json +65 -0
- package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
- package/renderer/dist/emulator/data/emulator.css +1614 -0
- package/renderer/dist/emulator/data/loader.js +170 -0
- package/renderer/dist/emulator/data/localization/README.md +63 -0
- package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
- package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
- package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
- package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
- package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
- package/renderer/dist/emulator/data/localization/en-US.json +339 -0
- package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
- package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
- package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
- package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
- package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
- package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
- package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
- package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
- package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
- package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
- package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
- package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
- package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
- package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
- package/renderer/dist/emulator/data/src/GameManager.js +464 -0
- package/renderer/dist/emulator/data/src/compression.js +142 -0
- package/renderer/dist/emulator/data/src/emulator.js +6158 -0
- package/renderer/dist/emulator/data/src/gamepad.js +167 -0
- package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
- package/renderer/dist/emulator/data/src/shaders.js +236 -0
- package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
- package/renderer/dist/emulator/data/src/storage.js +125 -0
- package/renderer/dist/emulator/data/version.json +5 -0
- package/renderer/dist/emulator/host.js +297 -0
- package/renderer/dist/emulator/index.html +28 -0
- package/renderer/dist/emulator/provenance.json +6 -0
- package/renderer/dist/index.html +16 -3
- package/scripts/after-install.mjs +30 -8
- package/scripts/approval-server.sh +24 -5
- package/scripts/before-publish.mjs +12 -10
- 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 +62 -34
- package/shared/agents.d.mts +1 -0
- 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 +45 -1
- package/shared/claude-code-shape.mjs +6 -0
- package/shared/claude-commands.generated.mjs +6 -4
- package/shared/claude-models.generated.mjs +6 -4
- 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 +36 -0
- package/shared/corner-tag.mjs +396 -0
- package/shared/dead-run-trace.mjs +19 -2
- package/shared/engines.mjs +76 -300
- 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 +111 -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 +31 -0
- package/shared/notify-rules.mjs +55 -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/provider-commands.mjs +10 -2
- package/shared/rank.d.mts +4 -1
- package/shared/rank.mjs +82 -19
- package/shared/relay-address.mjs +6 -0
- package/shared/relay-crypto.mjs +165 -0
- 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 -13
- package/shared/runs-on.mjs +31 -52
- package/shared/shipped-note.d.mts +4 -0
- package/shared/shipped-note.mjs +6 -0
- package/shared/shortcuts.mjs +24 -10
- package/shared/spawn-trouble.d.mts +4 -0
- package/shared/spawn-trouble.mjs +484 -30
- 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/terminal-input.d.mts +1 -0
- package/shared/terminal-input.mjs +30 -0
- package/shared/terminal-keys.d.mts +1 -0
- package/shared/terminal-keys.mjs +16 -0
- package/shared/terminal-size.d.mts +4 -0
- package/shared/terminal-size.mjs +29 -0
- package/shared/thread-cards.mjs +4 -1
- package/shared/usage.d.mts +12 -0
- package/shared/usage.mjs +56 -3
- package/shared/websites.d.mts +15 -0
- package/shared/websites.mjs +61 -0
- package/shared/where-it-runs-from.d.mts +43 -0
- package/shared/where-it-runs-from.mjs +155 -0
- package/shared/work-items.mjs +150 -29
- package/shared/work-lines.mjs +93 -245
- package/shared/xterm-invalid-range.d.mts +2 -0
- package/shared/xterm-invalid-range.mjs +58 -0
- package/renderer/dist/assets/index-ChKsQkRw.js +0 -1169
- package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
// THE PROJECT'S DOCUMENTS FOLDER, FOR A CLOUD RUN.
|
|
2
|
+
//
|
|
3
|
+
// A worker on this Mac reads the plans and notes in the project's documents
|
|
4
|
+
// folder and writes its designs and reports there, where the app draws them.
|
|
5
|
+
// A cloud run gets a copy at the same path: everything small, and this
|
|
6
|
+
// thread's own designs folder, newest first, as much as fits. What it writes
|
|
7
|
+
// there comes back while it works and once more at the end
|
|
8
|
+
// (main/cloud/run.mjs), and is laid in here. Never a deletion, never over a
|
|
9
|
+
// file someone changed on this Mac while the run was away, and never over one
|
|
10
|
+
// the run did not take with it: those are kept, and the run's copy goes beside.
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import crypto from 'node:crypto';
|
|
15
|
+
import { run } from './exec.mjs';
|
|
16
|
+
|
|
17
|
+
// The folders that hold pictures by the gigabyte. Only this thread's own
|
|
18
|
+
// designs go up from them; the rest of the folder is small.
|
|
19
|
+
const HEAVY = new Set(['designs', 'attachments']);
|
|
20
|
+
export const DOCS_LIMITS = {
|
|
21
|
+
file: 256 * 1024,
|
|
22
|
+
ownFile: 25 * 1024 * 1024,
|
|
23
|
+
own: 50 * 1024 * 1024,
|
|
24
|
+
total: 100 * 1024 * 1024,
|
|
25
|
+
files: 5000,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const inside = (p, root) => p === root || p.startsWith(root + path.sep);
|
|
29
|
+
|
|
30
|
+
/** The files, by path inside `dir`, that go up with a cloud run for `item`. */
|
|
31
|
+
export function docsToSend(dir, { item = null, limits = DOCS_LIMITS } = {}) {
|
|
32
|
+
const own = item && /^[A-Za-z0-9_-]+$/.test(item) ? `designs/${item}` : null;
|
|
33
|
+
const picked = [];
|
|
34
|
+
let total = 0;
|
|
35
|
+
let ownTotal = 0;
|
|
36
|
+
const walk = (rel) => {
|
|
37
|
+
let entries = [];
|
|
38
|
+
try { entries = fs.readdirSync(path.join(dir, rel), { withFileTypes: true }); } catch { return; }
|
|
39
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
40
|
+
for (const e of entries) {
|
|
41
|
+
if (picked.length >= limits.files) return;
|
|
42
|
+
if (e.name.startsWith('.')) continue;
|
|
43
|
+
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
44
|
+
if (/[\n\r]/.test(r)) continue;
|
|
45
|
+
if (e.isDirectory()) {
|
|
46
|
+
if (!rel && HEAVY.has(e.name)) { if (own && e.name === 'designs') walkOwn(); continue; }
|
|
47
|
+
walk(r);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (!e.isFile()) continue;
|
|
51
|
+
let size;
|
|
52
|
+
try { size = fs.statSync(path.join(dir, r)).size; } catch { continue; }
|
|
53
|
+
if (size > limits.file || total + size > limits.total) continue;
|
|
54
|
+
picked.push(r);
|
|
55
|
+
total += size;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
// Newest first: a thread whose designs do not all fit takes its latest
|
|
59
|
+
// work, and the rest stays on this Mac.
|
|
60
|
+
const walkOwn = () => {
|
|
61
|
+
const found = [];
|
|
62
|
+
const stack = [own];
|
|
63
|
+
while (stack.length) {
|
|
64
|
+
const rel = stack.shift();
|
|
65
|
+
let entries = [];
|
|
66
|
+
try { entries = fs.readdirSync(path.join(dir, rel), { withFileTypes: true }); } catch { continue; }
|
|
67
|
+
for (const e of entries) {
|
|
68
|
+
if (e.name.startsWith('.')) continue;
|
|
69
|
+
const r = `${rel}/${e.name}`;
|
|
70
|
+
if (/[\n\r]/.test(r)) continue;
|
|
71
|
+
if (e.isDirectory()) { stack.push(r); continue; }
|
|
72
|
+
if (!e.isFile()) continue;
|
|
73
|
+
let st;
|
|
74
|
+
try { st = fs.statSync(path.join(dir, r)); } catch { continue; }
|
|
75
|
+
if (st.size <= limits.ownFile) found.push({ r, size: st.size, at: st.mtimeMs });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
found.sort((a, b) => b.at - a.at || a.r.localeCompare(b.r));
|
|
79
|
+
for (const f of found) {
|
|
80
|
+
if (picked.length >= limits.files) return;
|
|
81
|
+
if (ownTotal + f.size > limits.own) continue;
|
|
82
|
+
picked.push(f.r);
|
|
83
|
+
ownTotal += f.size;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
walk('');
|
|
87
|
+
return picked;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Those files as one gzipped tar, paths inside `dir`. */
|
|
91
|
+
export function tarDocs(dir, files) {
|
|
92
|
+
return run('tar', ['-czf', '-', '-C', dir, '--null', '-T', '-'], { input: files.join('\0') + '\0', encoding: null, env: { ...process.env, COPYFILE_DISABLE: '1' } });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const stampOf = (st) => `${st.size}:${Math.round(st.mtimeMs)}`;
|
|
96
|
+
const hashOf = (body) => crypto.createHash('sha256').update(body).digest('hex');
|
|
97
|
+
|
|
98
|
+
// `notes.md` -> `notes (cloud).md`, then `notes (cloud 2).md`, and so on.
|
|
99
|
+
function besideName(file) {
|
|
100
|
+
const ext = path.extname(file);
|
|
101
|
+
const base = file.slice(0, file.length - ext.length);
|
|
102
|
+
for (let i = 1; ; i += 1) {
|
|
103
|
+
const p = `${base} (cloud${i > 1 ? ` ${i}` : ''})${ext}`;
|
|
104
|
+
if (!fs.existsSync(p)) return p;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Lay an archive of documents the run wrote into `dir`.
|
|
110
|
+
*
|
|
111
|
+
* `sentAt` is when the run left with the folder; `laid` is what earlier calls
|
|
112
|
+
* for the same run wrote, path -> { stamp, hash }, and is updated in place. A
|
|
113
|
+
* version this run already laid is never laid again: the run sends a file
|
|
114
|
+
* until the Mac confirms it, so the same one can arrive twice. Otherwise a file
|
|
115
|
+
* is written over only if it is missing here, unchanged here since the run
|
|
116
|
+
* left, or is still exactly what this run itself laid. Anything else is a
|
|
117
|
+
* change made on this Mac, and the run's copy goes beside it.
|
|
118
|
+
*
|
|
119
|
+
* `sent` is every path the run took with it. A file here that is not one of
|
|
120
|
+
* them was never the run's to change, however old it is, so its copy goes
|
|
121
|
+
* beside it too. A run recorded before the list was kept passes none.
|
|
122
|
+
*/
|
|
123
|
+
export async function layDocs({ tgz, dir, sentAt = 0, laid = {}, sent = null }) {
|
|
124
|
+
const took = sent ? new Set(sent) : null;
|
|
125
|
+
const out = { written: [], beside: [], moved: [] };
|
|
126
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'agentbox-docs-'));
|
|
127
|
+
try {
|
|
128
|
+
// From a file, not a pipe: tar may stop reading at the archive's end
|
|
129
|
+
// marker, and a pipe it stopped reading fails the write (EPIPE) on a busy Mac.
|
|
130
|
+
const file = path.join(tmp, '.agentbox-docs.tgz');
|
|
131
|
+
fs.writeFileSync(file, tgz);
|
|
132
|
+
const into = path.join(tmp, 'docs');
|
|
133
|
+
fs.mkdirSync(into);
|
|
134
|
+
await run('tar', ['-xzf', file, '-C', into]);
|
|
135
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
136
|
+
const root = fs.realpathSync(dir);
|
|
137
|
+
const files = [];
|
|
138
|
+
const walk = (rel) => {
|
|
139
|
+
for (const e of fs.readdirSync(path.join(into, rel), { withFileTypes: true })) {
|
|
140
|
+
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
141
|
+
if (e.isDirectory()) walk(r);
|
|
142
|
+
else if (e.isFile()) files.push(r);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
walk('');
|
|
146
|
+
// The nearest folder that exists, after links, must be this one or in
|
|
147
|
+
// it: a link inside the folder never carries a file out of it.
|
|
148
|
+
const safeParent = (file) => {
|
|
149
|
+
let parent = path.dirname(file);
|
|
150
|
+
while (!fs.existsSync(parent)) parent = path.dirname(parent);
|
|
151
|
+
if (!inside(fs.realpathSync(parent), root)) return false;
|
|
152
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
153
|
+
return inside(fs.realpathSync(path.dirname(file)), root);
|
|
154
|
+
};
|
|
155
|
+
for (const rel of files) {
|
|
156
|
+
const asIs = path.join(root, rel);
|
|
157
|
+
let target = asIs;
|
|
158
|
+
const body = fs.readFileSync(path.join(into, rel));
|
|
159
|
+
const hash = hashOf(body);
|
|
160
|
+
if (laid[rel]?.hash === hash) continue;
|
|
161
|
+
// A file whose folder here is a link out of it is not dropped: it lands
|
|
162
|
+
// in a folder of its own, and the run's work is all still home.
|
|
163
|
+
if (!safeParent(target)) {
|
|
164
|
+
target = path.join(root, 'from-the-cloud', rel);
|
|
165
|
+
if (!safeParent(target)) throw new Error(`nowhere safe to put ${rel}`);
|
|
166
|
+
out.moved.push(path.relative(root, target));
|
|
167
|
+
}
|
|
168
|
+
let here = null;
|
|
169
|
+
try { here = fs.lstatSync(target); } catch { /* new */ }
|
|
170
|
+
// A link or a folder where the file goes is left as it is, and the
|
|
171
|
+
// run's copy goes beside it.
|
|
172
|
+
if (here && !here.isFile()) {
|
|
173
|
+
target = besideName(target);
|
|
174
|
+
out.beside.push(path.relative(root, target));
|
|
175
|
+
here = null;
|
|
176
|
+
}
|
|
177
|
+
if (here) {
|
|
178
|
+
// The same bytes already here: nothing to write. It becomes the run's
|
|
179
|
+
// only if the run took it or wrote it before, never a file left here.
|
|
180
|
+
if (here.size === body.length && fs.readFileSync(target).equals(body)) {
|
|
181
|
+
const owned = !took || took.has(rel) || !!laid[rel]?.stamp;
|
|
182
|
+
laid[rel] = { stamp: owned ? stampOf(here) : null, hash };
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const mine = laid[rel]?.stamp === stampOf(here);
|
|
186
|
+
// Whole milliseconds on both sides: `sentAt` is a Date.now() and counts
|
|
187
|
+
// them, an mtime does not, so a file saved 0.7 ms before the run left
|
|
188
|
+
// read as newer than it and the run's own edit went beside a file
|
|
189
|
+
// nobody had touched.
|
|
190
|
+
const untouched = mine || (Math.floor(here.mtimeMs) <= sentAt && (!took || took.has(rel)));
|
|
191
|
+
if (!untouched) {
|
|
192
|
+
target = besideName(target);
|
|
193
|
+
out.beside.push(path.relative(root, target));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// A fresh name, made only if nothing is there: a link left at a name
|
|
197
|
+
// this could guess would otherwise be written through.
|
|
198
|
+
const part = path.join(path.dirname(target), `.agentbox-${crypto.randomUUID()}.part`);
|
|
199
|
+
fs.writeFileSync(part, body, { flag: 'wx' });
|
|
200
|
+
fs.renameSync(part, target);
|
|
201
|
+
if (target === asIs) out.written.push(rel);
|
|
202
|
+
laid[rel] = { stamp: target === asIs ? stampOf(fs.statSync(target)) : laid[rel]?.stamp ?? null, hash };
|
|
203
|
+
}
|
|
204
|
+
} finally {
|
|
205
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** The documents folder a run may take, or null: it must exist and must not hold, or sit in, the task folder. */
|
|
211
|
+
export function docsFolderFor(docs, cwd) {
|
|
212
|
+
if (!docs || !path.isAbsolute(docs)) return null;
|
|
213
|
+
let real;
|
|
214
|
+
let realCwd;
|
|
215
|
+
try { real = fs.realpathSync(docs); realCwd = fs.realpathSync(cwd); } catch { return null; }
|
|
216
|
+
if (!fs.statSync(real).isDirectory()) return null;
|
|
217
|
+
if (inside(real, realCwd) || inside(realCwd, real)) return null;
|
|
218
|
+
return docs;
|
|
219
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// A command run without holding up this process, which every cloud run's relay
|
|
2
|
+
// shares (./host.mjs): one run's git or tar must not stop the others.
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
// Resolves to stdout (a string, or a Buffer when `encoding` is null); rejects,
|
|
6
|
+
// as execFileSync throws, with "Command failed: <command>" and its stderr.
|
|
7
|
+
export function run(cmd, args, { cwd, env = process.env, input = null, encoding = 'utf8' } = {}) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
let child;
|
|
10
|
+
try { child = spawn(cmd, args, { cwd, env, stdio: ['pipe', 'pipe', 'pipe'] }); } catch (err) { reject(err); return; }
|
|
11
|
+
const out = [];
|
|
12
|
+
const err = [];
|
|
13
|
+
child.stdout.on('data', (d) => out.push(d));
|
|
14
|
+
child.stderr.on('data', (d) => err.push(d));
|
|
15
|
+
child.on('error', reject);
|
|
16
|
+
child.on('close', (code, signal) => {
|
|
17
|
+
const stdout = Buffer.concat(out);
|
|
18
|
+
if (code === 0) { resolve(encoding ? stdout.toString(encoding) : stdout); return; }
|
|
19
|
+
const stderr = Buffer.concat(err).toString('utf8');
|
|
20
|
+
const e = new Error(`Command failed: ${cmd} ${args.join(' ')}\n${stderr}`);
|
|
21
|
+
Object.assign(e, { status: code, signal, stderr });
|
|
22
|
+
reject(e);
|
|
23
|
+
});
|
|
24
|
+
child.stdin.on('error', () => {});
|
|
25
|
+
child.stdin.end(input ?? undefined);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// ONE PROCESS ON THIS MAC FOR EVERY CLOUD RUN'S RELAY (main/cloud/run.mjs).
|
|
2
|
+
// The supervisor holds, for each run, a stand-in of a few megabytes
|
|
3
|
+
// (scripts/cloud-shim.pl) that carries the run's input, output, signals and
|
|
4
|
+
// exit code here and back, so to the supervisor a cloud run is still one
|
|
5
|
+
// process.
|
|
6
|
+
//
|
|
7
|
+
// Lines over a socket only this user can reach. The stand-in sends a hello
|
|
8
|
+
// ({ argv, env, cwd, pid }), then `i <b64>` input, `I` end of input,
|
|
9
|
+
// `k <signal>`; this answers `o <b64>` output, `e <b64>` errors, `x <code>`.
|
|
10
|
+
// A stand-in that goes without saying (killed outright) is a run whose app
|
|
11
|
+
// has gone: it is left going in the cloud, as on a quit, and brought home
|
|
12
|
+
// later. With no run for a minute this process ends.
|
|
13
|
+
//
|
|
14
|
+
// ELECTRON_RUN_AS_NODE=1 <electron> main/cloud/host.mjs <socket>
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import os from 'node:os';
|
|
17
|
+
import net from 'node:net';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import crypto from 'node:crypto';
|
|
20
|
+
import { spawnSync } from 'node:child_process';
|
|
21
|
+
import { EventEmitter } from 'node:events';
|
|
22
|
+
import { PassThrough } from 'node:stream';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
import { nameSlug } from '../../shared/product-name.mjs';
|
|
25
|
+
|
|
26
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
// The files a host is made of. A host started by an older app serves only the
|
|
28
|
+
// stand-ins of that app: a newer one starts its own beside it.
|
|
29
|
+
const MADE_OF = ['host.mjs', 'run.mjs', 'run-cli.mjs', 'docs.mjs', 'exec.mjs', 'marker.mjs', 'aws.mjs', '../approval-prompt-core.mjs', '../../mcp/stdio.mjs', '../../mcp/tools.mjs'];
|
|
30
|
+
const TRANSPORT_FAULT = 75;
|
|
31
|
+
// The exit of a host that found one of this app's already serving its socket.
|
|
32
|
+
export const HOST_ALREADY_THERE = 3;
|
|
33
|
+
let hosts = 0;
|
|
34
|
+
|
|
35
|
+
// Whether this perl has every module the stand-in uses: some Linux perls ship
|
|
36
|
+
// without JSON::PP.
|
|
37
|
+
export function perlRunsTheShim(perl) {
|
|
38
|
+
try {
|
|
39
|
+
return spawnSync(perl, ['-MIO::Socket::UNIX', '-MIO::Select', '-MMIME::Base64', '-MJSON::PP', '-MCwd', '-MPOSIX', '-e1'], { stdio: 'ignore', timeout: 10_000 }).status === 0;
|
|
40
|
+
} catch { return false; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function hostStamp() {
|
|
44
|
+
const h = crypto.createHash('sha1');
|
|
45
|
+
for (const f of MADE_OF) { try { h.update(fs.readFileSync(path.join(HERE, f))); } catch { h.update(f); } }
|
|
46
|
+
return h.digest('hex').slice(0, 12);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Short enough for a socket's path (104 bytes on macOS), and this user's own.
|
|
50
|
+
export function hostSocketPath({ stamp = hostStamp(), dir = os.tmpdir(), uid = process.getuid?.() ?? 0 } = {}) {
|
|
51
|
+
return path.join(dir, `${nameSlug}-cloud-${uid}-${stamp}.sock`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Serves runs on `sockPath`; `runOne(opts)` is one run, resolving to its exit
|
|
56
|
+
* code. `started` says 'serving', or 'taken' when a host already answers there.
|
|
57
|
+
*/
|
|
58
|
+
export function startCloudHost({ sockPath, runOne, idleMs = 60_000, onIdle = () => {}, log = () => {} }) {
|
|
59
|
+
let live = 0;
|
|
60
|
+
let idleTimer = null;
|
|
61
|
+
const idle = () => {
|
|
62
|
+
clearTimeout(idleTimer);
|
|
63
|
+
if (live === 0) { idleTimer = setTimeout(() => { if (live === 0) onIdle(); }, idleMs); idleTimer.unref?.(); }
|
|
64
|
+
};
|
|
65
|
+
const server = net.createServer((sock) => {
|
|
66
|
+
let buf = '';
|
|
67
|
+
let started = false;
|
|
68
|
+
let done = false;
|
|
69
|
+
const stdin = new PassThrough();
|
|
70
|
+
const stdout = new PassThrough();
|
|
71
|
+
const stderr = new PassThrough();
|
|
72
|
+
const signals = new EventEmitter();
|
|
73
|
+
const send = (line) => { if (!sock.destroyed) return sock.write(line + '\n'); return true; };
|
|
74
|
+
// What the run prints goes out as it comes, never faster than the
|
|
75
|
+
// stand-in takes it.
|
|
76
|
+
for (const [stream, tag] of [[stdout, 'o'], [stderr, 'e']]) {
|
|
77
|
+
stream.on('data', (b) => { if (send(`${tag} ${b.toString('base64')}`) === false && !sock.destroyed) { stream.pause(); sock.once('drain', () => stream.resume()); } });
|
|
78
|
+
}
|
|
79
|
+
const finish = (code) => {
|
|
80
|
+
if (done) return;
|
|
81
|
+
done = true;
|
|
82
|
+
let open = 2;
|
|
83
|
+
const both = () => { open -= 1; if (open === 0) { send(`x ${Number.isInteger(code) ? code : TRANSPORT_FAULT}`); sock.end(); live -= 1; idle(); } };
|
|
84
|
+
stdout.once('end', both); stderr.once('end', both);
|
|
85
|
+
stdout.end(); stderr.end();
|
|
86
|
+
stdout.resume(); stderr.resume();
|
|
87
|
+
};
|
|
88
|
+
sock.setEncoding('utf8');
|
|
89
|
+
sock.on('data', (d) => {
|
|
90
|
+
buf += d;
|
|
91
|
+
let n;
|
|
92
|
+
while ((n = buf.indexOf('\n')) >= 0) {
|
|
93
|
+
const line = buf.slice(0, n);
|
|
94
|
+
buf = buf.slice(n + 1);
|
|
95
|
+
if (!started) {
|
|
96
|
+
let hello = null;
|
|
97
|
+
try { hello = JSON.parse(line); } catch { /* not a hello */ }
|
|
98
|
+
if (!Array.isArray(hello?.argv) || typeof hello.cwd !== 'string') { send(`e ${Buffer.from('agentbox cloud: the stand-in sent no run\n').toString('base64')}`); send(`x ${TRANSPORT_FAULT}`); sock.end(); return; }
|
|
99
|
+
started = true;
|
|
100
|
+
live += 1;
|
|
101
|
+
clearTimeout(idleTimer);
|
|
102
|
+
Promise.resolve()
|
|
103
|
+
.then(() => runOne({ args: hello.argv, env: hello.env || {}, cwd: hello.cwd, stdin, stdout, stderr, signals, pid: Number(hello.pid) || undefined, watchParent: false }))
|
|
104
|
+
.then((code) => finish(code), (err) => { stderr.write(`agentbox cloud: stopped: ${err?.message || err}\n`); finish(TRANSPORT_FAULT); });
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (line.startsWith('i ')) stdin.write(Buffer.from(line.slice(2), 'base64'));
|
|
108
|
+
else if (line === 'I') stdin.end();
|
|
109
|
+
else if (line.startsWith('k ') && /^SIG(TERM|INT|HUP)$/.test(line.slice(2))) signals.emit(line.slice(2));
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// Gone without a word: its app is gone, so the run carries on in the
|
|
113
|
+
// cloud and is brought home when the app next opens.
|
|
114
|
+
// Its output then goes nowhere, so a run held waiting for room is let go.
|
|
115
|
+
sock.on('close', () => { stdout.resume(); stderr.resume(); if (started && !done) signals.emit('SIGHUP'); });
|
|
116
|
+
sock.on('error', () => {});
|
|
117
|
+
});
|
|
118
|
+
server.on('error', (err) => log(`cloud host: ${err.message}`));
|
|
119
|
+
// The socket file this host made. Ending, it takes away that one only: a
|
|
120
|
+
// newer host may have put its own at the same path. Node deletes the path a
|
|
121
|
+
// server listened on as it closes, so it listens on a name of its own and
|
|
122
|
+
// moves it into place.
|
|
123
|
+
let mine = null;
|
|
124
|
+
const own = `${sockPath}.${process.pid}${(hosts += 1)}`;
|
|
125
|
+
const ours = () => { try { return mine !== null && fs.statSync(sockPath).ino === mine; } catch { return false; } };
|
|
126
|
+
const started = new Promise((resolve) => {
|
|
127
|
+
const probe = net.connect(sockPath);
|
|
128
|
+
probe.once('connect', () => { probe.destroy(); resolve('taken'); });
|
|
129
|
+
probe.once('error', () => {
|
|
130
|
+
try { fs.unlinkSync(own); } catch { /* not there */ }
|
|
131
|
+
server.listen(own, () => {
|
|
132
|
+
try { fs.chmodSync(own, 0o600); fs.renameSync(own, sockPath); mine = fs.statSync(sockPath).ino; } catch (err) { log(`cloud host: ${err.message}`); }
|
|
133
|
+
idle();
|
|
134
|
+
resolve('serving');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
server,
|
|
140
|
+
started,
|
|
141
|
+
live: () => live,
|
|
142
|
+
close: () => started.then(() => new Promise((resolve) => {
|
|
143
|
+
clearTimeout(idleTimer);
|
|
144
|
+
if (ours()) { try { fs.unlinkSync(sockPath); } catch { /* gone */ } }
|
|
145
|
+
if (server.listening) server.close(() => resolve()); else resolve();
|
|
146
|
+
})),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Started as a program by the supervisor (Supervisor#_cloudHost).
|
|
151
|
+
if (path.basename(process.argv[1] || '') === 'host.mjs') {
|
|
152
|
+
const sockPath = process.argv[2];
|
|
153
|
+
const { cloudRunFromArgs, dropAwsSignIns } = await import('./run-cli.mjs');
|
|
154
|
+
dropAwsSignIns();
|
|
155
|
+
const host = startCloudHost({
|
|
156
|
+
sockPath,
|
|
157
|
+
runOne: cloudRunFromArgs,
|
|
158
|
+
onIdle: () => host.close().then(() => process.exit(0)),
|
|
159
|
+
log: (m) => process.stderr.write(`${m}\n`),
|
|
160
|
+
});
|
|
161
|
+
if (await host.started === 'taken') process.exit(HOST_ALREADY_THERE);
|
|
162
|
+
// A run's stray error is that run's, not every run's.
|
|
163
|
+
process.on('uncaughtException', (err) => process.stderr.write(`cloud host: ${err?.stack || err}\n`));
|
|
164
|
+
process.on('unhandledRejection', (err) => process.stderr.write(`cloud host: ${err?.stack || err}\n`));
|
|
165
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// THE COMPUTER'S SIDE OF A KIT (shared/cloud-kit.mjs): where its copy is kept
|
|
2
|
+
// here, and the marker that makes a run the cloud server started from it this
|
|
3
|
+
// thread's own, so the import path joins it or brings it home
|
|
4
|
+
// (main/cloud/run.mjs `joining`, Supervisor#adoptCloudRun).
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { PassThrough } from 'node:stream';
|
|
8
|
+
import { cloudSessionId, APPROVALS_SERVER } from '../../shared/cloud-kit.mjs';
|
|
9
|
+
import { serveApprovals } from '../approval-prompt-core.mjs';
|
|
10
|
+
|
|
11
|
+
const safe = (s) => String(s).replace(/[^A-Za-z0-9_-]/g, '_');
|
|
12
|
+
|
|
13
|
+
/** Where the last kit written for a thread is kept on this computer. */
|
|
14
|
+
export const kitCopyPath = (userDir, product, item) => path.join(userDir, 'cloud-kits', `${safe(product)}--${safe(item)}.json`);
|
|
15
|
+
/** Where the reply's place in the prompt waits for the kit writer, read once. */
|
|
16
|
+
export const kitFramePath = (userDir, product, item) => path.join(userDir, 'cloud-kits', `${safe(product)}--${safe(item)}.frame.json`);
|
|
17
|
+
|
|
18
|
+
export function readKitCopy(file) {
|
|
19
|
+
try {
|
|
20
|
+
const kit = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
21
|
+
return kit && typeof kit === 'object' && kit.marker && typeof kit.cwd === 'string' ? kit : null;
|
|
22
|
+
} catch { return null; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// None of its events have been read here (`cursor`). Left `detached`, as one
|
|
26
|
+
// the app left going: the import path asks the sandbox which it is now, and
|
|
27
|
+
// numbers this computer's input on from what the sandbox says it took
|
|
28
|
+
// (`inSeq`), however many replies the server sent. Its input is closed when
|
|
29
|
+
// joined (`unsent`), so the turn it is on ends and its work comes home; her
|
|
30
|
+
// next reply is then an ordinary run here.
|
|
31
|
+
export function markerForCloudRun(kit, runId) {
|
|
32
|
+
return {
|
|
33
|
+
...kit.marker,
|
|
34
|
+
runId,
|
|
35
|
+
sessionId: cloudSessionId(kit.item, runId),
|
|
36
|
+
offline: true,
|
|
37
|
+
state: 'detached', pid: null, cursor: 0, conns: {},
|
|
38
|
+
unsent: [{ action: 'stdin', end: true }],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// PERMISSION REQUESTS WITH THE COMPUTER AWAY (cloud/sandbox/server.mjs
|
|
43
|
+
// `offline.hold`). A run that asks permission names the app's approvals
|
|
44
|
+
// server on its command line; one in a mode that never asks does not need it
|
|
45
|
+
// held.
|
|
46
|
+
export function asksPermission(argv) {
|
|
47
|
+
const args = (Array.isArray(argv) ? argv : []).map(String);
|
|
48
|
+
if (args.includes('--dangerously-skip-permissions')) return false;
|
|
49
|
+
const at = args.indexOf('--permission-prompt-tool');
|
|
50
|
+
if (at < 0 || !String(args[at + 1] ?? '').includes(`__${APPROVALS_SERVER}__`)) return false;
|
|
51
|
+
const mode = args.indexOf('--permission-mode');
|
|
52
|
+
return !(mode >= 0 && args[mode + 1] === 'bypassPermissions');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The approvals server's own answers to a run opening it, read off the server
|
|
56
|
+
// itself, so a run the computer never spoke to opens it in the cloud.
|
|
57
|
+
export async function approvalsHandshakes({ timeoutMs = 5_000 } = {}) {
|
|
58
|
+
const input = new PassThrough();
|
|
59
|
+
const output = new PassThrough();
|
|
60
|
+
const server = serveApprovals({ env: {}, input, output, warn: () => {} });
|
|
61
|
+
const answers = new Map();
|
|
62
|
+
let buf = '';
|
|
63
|
+
const done = new Promise((resolve, reject) => {
|
|
64
|
+
const timer = setTimeout(() => reject(new Error('the approvals server did not answer its handshake')), timeoutMs);
|
|
65
|
+
output.on('data', (d) => {
|
|
66
|
+
buf += d;
|
|
67
|
+
let i;
|
|
68
|
+
while ((i = buf.indexOf('\n')) >= 0) {
|
|
69
|
+
const msg = JSON.parse(buf.slice(0, i));
|
|
70
|
+
buf = buf.slice(i + 1);
|
|
71
|
+
answers.set(msg.id, msg.result);
|
|
72
|
+
if (answers.size === 2) { clearTimeout(timer); resolve(); }
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
input.write(`${JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05' } })}\n`);
|
|
77
|
+
input.write(`${JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list' })}\n`);
|
|
78
|
+
try { await done; } finally { server.close(); }
|
|
79
|
+
return { initialize: answers.get(1), 'tools/list': answers.get(2) };
|
|
80
|
+
}
|