openmausbot 0.1.90 → 0.1.92
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/dist/assets/DesktopViewer-Do6lBa4C.js +5 -0
- package/dist/assets/{abnfDiagram-O67JEVCF-DUyXd0kL.js → abnfDiagram-O67JEVCF-Cd3X7Hxc.js} +1 -1
- package/dist/assets/{arc-q73qM-At.js → arc-DIYc7Ymx.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-D33O3Wur.js → architectureDiagram-NJMV4G6O-DBmRGVyP.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-DLsJeoXC.js → blockDiagram-BEXU5L5S-CPFePSEE.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-DnaljZnu.js → c4Diagram-YGBWAQC7-DD_DO1qy.js} +1 -1
- package/dist/assets/channel-Dtk5Mtic.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-Da0h2i79.js → chunk-2Q5K7J3B-BNEeNSjX.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-CfzHi54f.js → chunk-3FUC2YCW-BbinLaqH.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-LjE4d9Jf.js → chunk-5VM5RSS4-BBgYrgDR.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-DY-UGmvc.js → chunk-GTNCS2PH-ezXj-kA-.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-BqeGhyhP.js → chunk-GWA4HPMP-9FxZ3diV.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BYa9EBAg.js → chunk-JWPE2WC7-CO-40JpI.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-vTrDtWyb.js → chunk-NETBCI7D-BEhjslYE.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-ic5tXadM.js → chunk-WEXAMYUT-CNeq4n7_.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-DvRCY-c6.js → chunk-XXDRQBXY-OsCY7KSi.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-DVbmgz_L.js → classDiagram-v2-NBCMYWYE-CloRUEtk.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-vdUs2n57.js → cose-bilkent-JH36ORCC-D1BGZhp4.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-BvLGlV3U.js → cynefin-OW5HDTMX-CUf4T2CN.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-C4GcJQDt.js → cynefinDiagram-VND7K2PF-DPEZrFGA.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-BMSaHpt_.js → dagre-6A5THRUB-bDe6GIv5.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-D5b9UBhC.js → diagram-22UHCM2B-CdcUQz55.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-CIKLlB7Y.js → diagram-3UASUU5V-BZpqGg7c.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-BzXb-7k7.js → diagram-ATOU4E4O-DvGM6ugp.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-HsTvJ7uT.js → diagram-CDSNMT55-DSTmMm0s.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-Db9ySNbQ.js → diagram-MLGK6HIB-15cucnMn.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-D6BKbpqY.js → diagram-MPIPVDR6-tMbuaE6n.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-Bq067L0t.js → ebnfDiagram-ZINNZB2B-BYxYzjHI.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-DhaD7fNJ.js → elk-276RUBZZ-v6bj852W.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-fwIhfz36.js → erDiagram-OPXOYQCR-5lBBlnd2.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-DygIyu0V.js → flowDiagram-KWPJA3E3-CZyl1zIj.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-DPYfSO96.js → ganttDiagram-FUAMR5RP-BiAI-ltZ.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-DhkHeCle.js → gitGraphDiagram-X574FWY7-BAZISkj3.js} +1 -1
- package/dist/assets/index-Cdmjrl28.css +1 -0
- package/dist/assets/{index-G1PHKGyh.js → index-DGBaEXrM.js} +1 -1
- package/dist/assets/index-DR7YPMkW.js +597 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-DstBLxME.js → infoDiagram-VRGFBTTK-B6ooOy-Z.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-CrIsGgch.js → ishikawaDiagram-OU5B5YK6-BZ9ksi-B.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-CYypGDbB.js → journeyDiagram-ZHPQQLJL-DWqlzIEe.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-DGjPt8E_.js → kanban-definition-PNTS6WVX-JX6piDwL.js} +1 -1
- package/dist/assets/{linear-Dwc3Hrru.js → linear-CmOWzX0P.js} +1 -1
- package/dist/assets/{mermaid.core-Dc4OePBL.js → mermaid.core-CF1zwoQ-.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-DV-vepj-.js → mindmap-definition-NLK3R4M7-CGh2K4fW.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-lx5Kj-n5.js → pegDiagram-GJSIUBJH-CDwYN0s7.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-Bq7Vmm15.js → pieDiagram-5QR66LMP-1rfjJocB.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-DeqeqOBj.js → quadrantDiagram-O4NWA36T-tKPvPiok.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-CXwRQzSC.js → railroadDiagram-XR7U4H2S-BHLm5vXR.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-8tAeoB88.js → requirementDiagram-PLB6GJNP-CB45STp7.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-BG5zjGWU.js → sankeyDiagram-IPEJSGJF-55QA_huC.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-DKF3iLcw.js → sequenceDiagram-PO4LG4MO-BckFmQHn.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-B-y1qUgT.js → sizeCapture-INFHLROL-BFCOJF11.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-MkzPgxAc.js → stateDiagram-v2-GCMORJYK-CZvmiVVN.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DnbaOEwH.js → swimlanes-2SLR337P-BpDUCEKy.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-M0GYW9Y3.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-Bb4xG_Bt.js → timeline-definition-EJHVYXUP-BpxHq6IK.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-JgkR9pFf.js → usecaseDiagram-POWQR4AR-Cj66vm02.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-CIPIrAYe.js → vennDiagram-UO4OBE2U-C9nRkWE_.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-BjCLuUvJ.js → wardleyDiagram-VNRHLVJA-D-me_nWj.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-DKgiaYxT.js → xychartDiagram-PMCCYNJV-Dc_fL4mk.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/novnc-NOTICE.txt +118 -0
- package/dist-server/cloud-home-start.js +128 -15
- package/dist-server/container-mcp.js +277 -45
- package/dist-server/drivers/agents-proxy.js +16 -4
- package/dist-server/enterprise/server/index.js +5 -1
- package/dist-server/index.js +46703 -39955
- package/dist-server/local-computer-proxy.js +128 -7
- package/dist-server/mcp-gate.js +11 -5
- package/dist-server/model-catalog/models-dev.snapshot.json +7299 -0
- package/dist-server/openmausbot.js +4099 -1217
- package/dist-server/pair-cli.js +4099 -1217
- package/dist-server/server/boat.js +67 -22
- package/dist-server/server/bot-overview.js +7 -4
- package/dist-server/server/browser-engine.js +10 -1
- package/dist-server/server/browser-live.js +46 -2
- package/dist-server/server/browser-runtime.js +74 -8
- package/dist-server/server/browser-tool-shape.js +6 -4
- package/dist-server/server/chief-of-staff.js +1 -1
- package/dist-server/server/cloud-home-start.js +136 -26
- package/dist-server/server/cloud-home.js +64 -5
- package/dist-server/server/cloud-lending.js +167 -0
- package/dist-server/server/cloud-move-http.js +268 -0
- package/dist-server/server/cloud-move.js +418 -0
- package/dist-server/server/cloud-owner.js +196 -0
- package/dist-server/server/cloud-secrets-boot.js +6 -0
- package/dist-server/server/cloud-secrets.js +65 -0
- package/dist-server/server/config.js +215 -17
- package/dist-server/server/container-computer.js +3 -2
- package/dist-server/server/decider/index.js +238 -0
- package/dist-server/server/decider/jev.js +183 -0
- package/dist-server/server/decider/log.js +62 -0
- package/dist-server/server/decider/relay.js +52 -0
- package/dist-server/server/decider/room-routing.js +88 -0
- package/dist-server/server/decider/types.js +5 -0
- package/dist-server/server/desktop-viewer-targets.js +26 -0
- package/dist-server/server/drivers/acp/core.js +167 -21
- package/dist-server/server/drivers/acp/opencode-go.js +614 -85
- package/dist-server/server/drivers/agents-catalog.js +22 -6
- package/dist-server/server/drivers/antigravity-acp.js +69 -11
- package/dist-server/server/drivers/antigravity-temp.js +360 -0
- package/dist-server/server/drivers/antigravity.js +21 -0
- package/dist-server/server/drivers/boatagent.js +20 -12
- package/dist-server/server/drivers/chatgpt-plan-auth.js +481 -0
- package/dist-server/server/drivers/claude.js +313 -34
- package/dist-server/server/drivers/codex.js +228 -18
- package/dist-server/server/drivers/grok.js +2 -2
- package/dist-server/server/drivers/mistral.js +4 -3
- package/dist-server/server/drivers/openai-chat-protocol.js +4 -3
- package/dist-server/server/drivers/openai-chat.js +41 -3
- package/dist-server/server/drivers/openai-compat.js +73 -23
- package/dist-server/server/engine-install.js +46 -12
- package/dist-server/server/env-path.js +15 -3
- package/dist-server/server/group-goal-run.js +6 -4
- package/dist-server/server/harness/registry.js +2 -2
- package/dist-server/server/harness-capabilities.js +2 -0
- package/dist-server/server/included-services.js +70 -0
- package/dist-server/server/index.js +1753 -263
- package/dist-server/server/lending-memory.js +479 -0
- package/dist-server/server/local-vm-idle.js +24 -6
- package/dist-server/server/managed-desktop.js +3 -0
- package/dist-server/server/mcp-bridge.js +5 -3
- package/dist-server/server/mcp-http.js +6 -3
- package/dist-server/server/mcp-oauth-discovery.js +133 -0
- package/dist-server/server/mcp-oauth-store.js +70 -0
- package/dist-server/server/mcp-oauth.js +550 -0
- package/dist-server/server/mcp-probe.js +14 -1
- package/dist-server/server/mcp-registry.js +43 -0
- package/dist-server/server/memory-upkeep.js +94 -84
- package/dist-server/server/model-catalog/catalog.js +374 -0
- package/dist-server/server/model-catalog/presets.js +176 -0
- package/dist-server/server/model-catalog/trim.js +219 -0
- package/dist-server/server/model-context-window.js +4 -4
- package/dist-server/server/package-export.js +17 -10
- package/dist-server/server/package-import.js +6 -3
- package/dist-server/server/package-parts.js +3 -1
- package/dist-server/server/provider-key-check.js +4 -2
- package/dist-server/server/recent-work.js +1 -1
- package/dist-server/server/request-auth.js +25 -0
- package/dist-server/server/routes/antigravity-leftovers.js +45 -0
- package/dist-server/server/routes/bot-memory.js +31 -6
- package/dist-server/server/routes/decider.js +19 -0
- package/dist-server/server/routes/desktop-viewer.js +194 -0
- package/dist-server/server/routine-requests.js +1 -1
- package/dist-server/server/routines.js +1 -1
- package/dist-server/server/sessions.js +56 -9
- package/dist-server/server/shared-computers.js +116 -30
- package/dist-server/server/skills.js +25 -10
- package/dist-server/server/store.js +79 -124
- package/dist-server/server/surface.js +13 -0
- package/dist-server/server/system-prompt.js +11 -0
- package/dist-server/server/team-backup.js +6 -2
- package/dist-server/server/team-manifest.js +11 -3
- package/dist-server/server/thread-events.js +2 -0
- package/dist-server/server/thread-starters.js +33 -0
- package/dist-server/server/thread-title.js +46 -0
- package/dist-server/server/tts/elevenlabs.js +11 -6
- package/dist-server/server/tts/fish.js +4 -5
- package/dist-server/server/tts/index.js +20 -11
- package/dist-server/server/vps-computer.js +79 -12
- package/dist-server/server/webhooks.js +18 -1
- package/dist-server/server/workspace-backup-policy.js +25 -3
- package/dist-server/server/workspace-backup.js +28 -8
- package/dist-server/server/workspace.js +65 -15
- package/dist-server/shared/cloud-home.js +4 -0
- package/dist-server/shared/package-format.js +30 -4
- package/dist-server/shared/team-backup.js +20 -3
- package/dist-server/shared/workspace-backup-client.js +1 -0
- package/dist-server/src/lib/citations.js +177 -0
- package/dist-server/src/lib/composer-attachments.js +7 -1
- package/dist-server/vps-container-mcp.js +278 -45
- package/dist-server/workspace-backup.worker.js +184 -38
- package/enterprise/server/index.js +5 -1
- package/package.json +1 -1
- package/dist/assets/channel-BCuyHjFV.js +0 -1
- package/dist/assets/index-CRvo-DS2.css +0 -1
- package/dist/assets/index-CWYGlA63.js +0 -588
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BZQ07DGE.js +0 -8
|
@@ -24,6 +24,8 @@ export function catalogProfileFromEnv(env) {
|
|
|
24
24
|
skillAuthoring: env.OMB_SKILL_AUTHORING_ENABLED === "1",
|
|
25
25
|
sharedComputers: env.OMB_SHARED_COMPUTERS_ENABLED === "1",
|
|
26
26
|
voiceNotes: env.OMB_VOICE_NOTES === "1",
|
|
27
|
+
cloudHome: env.OMB_CLOUD_HOME === "1",
|
|
28
|
+
memoryEnabled: env.OMB_MEMORY_ENABLED !== "0",
|
|
27
29
|
botId: env.OMB_BOT_ID ?? "",
|
|
28
30
|
};
|
|
29
31
|
}
|
|
@@ -209,14 +211,14 @@ const toolDefinitions = (externalRuntime) => [
|
|
|
209
211
|
},
|
|
210
212
|
{
|
|
211
213
|
name: "coordinate_bots",
|
|
212
|
-
description: "Ask existing OpenMausBot teammates for advice or assign concrete work. From normal chat
|
|
214
|
+
description: "Ask existing OpenMausBot teammates for advice or assign concrete work. From normal chat each distinct assignment starts a fresh thread for that teammate; from a room it defaults to this room. Give a self-contained brief. Use group_id from list_room_targets for a specific room. Give 1-4 bot_ids — teammate ids as list_bots or your roster prints them; a unique teammate name also resolves: they receive only your brief and use their own model, tools and permissions. Busy bots queue. They can consult their specialists; all results return here and resume you automatically. Include exact file paths, constraints and what must be verified. After sending all assignments, END your turn; do not poll or wait. On return, resolve tradeoffs, verify the requested outcome and request concrete corrections if necessary before giving one final answer. Do not send acknowledgements as new work.",
|
|
213
215
|
inputSchema: { type: "object", additionalProperties: false, properties: {
|
|
214
|
-
group_id: { type: "string", description: "Optional destination room. Omit for this room, or
|
|
216
|
+
group_id: { type: "string", description: "Optional destination room. Omit for this room, or a fresh thread for each teammate when chatting directly." },
|
|
215
217
|
bot_ids: { type: "array", items: { type: "string", description: "A teammate's id exactly as list_bots or your roster prints it ([id: …]). A teammate's unique display name also resolves; a name shared by two reachable teammates is refused." }, minItems: 1, maxItems: 4, uniqueItems: true },
|
|
216
218
|
message: { type: "string", minLength: 1, maxLength: 4000, description: "Self-contained question or task for these teammates. Send separate requests when responsibilities differ." },
|
|
217
219
|
request_key: { type: "string", description: "A short unique assignment key. Reuse for an identical retry." },
|
|
218
220
|
rework: { type: "boolean", description: "True only for concrete additional work from someone who already completed a request." },
|
|
219
|
-
label: { type: "string", description: "Optional short name (one line, at most 60 characters) for this job.
|
|
221
|
+
label: { type: "string", description: "Optional short name (one line, at most 60 characters) for this job. Names the new direct assignment thread." },
|
|
220
222
|
}, required: ["bot_ids", "message", "request_key"] },
|
|
221
223
|
},
|
|
222
224
|
{
|
|
@@ -759,12 +761,26 @@ const ROOM_ONLY_TOOLS = new Set(["list_room_targets", "coordinate_bots"]);
|
|
|
759
761
|
const ROOM_REPLACED_TOOLS = new Set(["ask_bot", "delegate_bot", "check_delegation", "wait_delegation", "start_thread", "send_to_thread", "wait_thread"]);
|
|
760
762
|
const EXTERNAL_TOOL_NAMES = new Set(["list_bots", "ask_bot", "delegate_bot", "check_delegation", "wait_delegation"]);
|
|
761
763
|
const WATCHER_TOOL_NAMES = new Set(["create_options_card"]);
|
|
764
|
+
// A Cloud home never offers this computer or a Local VM, so its bots are not
|
|
765
|
+
// shown them as choices, nor a VM shell they could never have.
|
|
766
|
+
const LOCAL_VM_TOOL_NAMES = new Set(["vm_exec"]);
|
|
767
|
+
const CLOUD_HOME_SURFACE = {
|
|
768
|
+
type: "string", enum: ["auto", "cloud", "browser"],
|
|
769
|
+
description: "auto = suitable configured computer, cloud = remote Boat/VPS, browser = built-in browser. Omit to list. This server runs in the cloud: the user's own computer and a Local VM are not places here.",
|
|
770
|
+
};
|
|
762
771
|
/** The tools one turn is shown, exactly as tools/list serializes them. */
|
|
763
772
|
export function availableTools(profile) {
|
|
773
|
+
const tools = catalogTools(profile);
|
|
774
|
+
return profile.cloudHome
|
|
775
|
+
? tools.filter(tool => !LOCAL_VM_TOOL_NAMES.has(tool.name)).map(tool => tool.name === "select_computer"
|
|
776
|
+
? { ...tool, inputSchema: { ...tool.inputSchema, properties: { surface: CLOUD_HOME_SURFACE } } }
|
|
777
|
+
: tool)
|
|
778
|
+
: tools;
|
|
779
|
+
}
|
|
780
|
+
function catalogTools(profile) {
|
|
764
781
|
const TOOLS = toolDefinitions(profile.externalRuntime);
|
|
765
|
-
const BOT_SCOPED_TOOLS = profile.botId === WATCHER_OPTIONS_CARD_BOT_ID
|
|
766
|
-
|
|
767
|
-
: TOOLS.filter((tool) => !WATCHER_TOOL_NAMES.has(tool.name));
|
|
782
|
+
const BOT_SCOPED_TOOLS = TOOLS.filter((tool) => (profile.botId === WATCHER_OPTIONS_CARD_BOT_ID || !WATCHER_TOOL_NAMES.has(tool.name)) &&
|
|
783
|
+
(profile.memoryEnabled !== false || (tool.name !== "memory_update" && tool.name !== "memory_log")));
|
|
768
784
|
const AUTHORING_TOOLS = profile.skillAuthoring
|
|
769
785
|
? BOT_SCOPED_TOOLS
|
|
770
786
|
: BOT_SCOPED_TOOLS.filter((tool) => !SKILL_TOOL_NAMES.has(tool.name));
|
|
@@ -6,6 +6,7 @@ import { delimiter, join, resolve } from "node:path";
|
|
|
6
6
|
import { DATA_DIR, stripWorkspaceCredentialEnv } from "../config.js";
|
|
7
7
|
import { writeFileAtomic } from "../atomic.js";
|
|
8
8
|
import { killCliTree, spawnCli } from "../procs.js";
|
|
9
|
+
import { VERIFICATION_TEMP_KEY, antigravityTempDir, setAntigravityTempEnvironment, sweepAntigravityTemp, } from "./antigravity-temp.js";
|
|
9
10
|
// Printed on stderr by Google's server, not stdout.
|
|
10
11
|
export const ANTIGRAVITY_AUTH_PREFIX = "Open the following link to authenticate the ACP server: ";
|
|
11
12
|
const AUTH_TIMEOUT_MS = 5 * 60_000;
|
|
@@ -16,6 +17,10 @@ const MAX_PROTOCOL_LINE_BYTES = 16 * 1024 * 1024;
|
|
|
16
17
|
// Past the 16 KiB ceiling parseAntigravityAuthorizationUrl accepts, so a
|
|
17
18
|
// partial stderr line is never dropped while it could still become a link.
|
|
18
19
|
const MAX_DIAGNOSTIC_LINE_CHARS = 64 * 1024;
|
|
20
|
+
// How long a closed runtime gets to exit on its own after its input ends.
|
|
21
|
+
// A forced stop on Windows skips the runtime's own cleanup of the files it
|
|
22
|
+
// unpacked; exiting by itself removes them. Same grace as the ACP pool.
|
|
23
|
+
const GRACEFUL_EXIT_MS = 5_000;
|
|
19
24
|
const REMOVED_ENVIRONMENT_KEYS = new Set([
|
|
20
25
|
"GEMINI_API_KEY",
|
|
21
26
|
"GOOGLE_API_KEY",
|
|
@@ -28,6 +33,8 @@ const REMOVED_ENVIRONMENT_KEYS = new Set([
|
|
|
28
33
|
"CLOUDSDK_CORE_PROJECT",
|
|
29
34
|
"AGY_ACP_CCPA_PROJECT",
|
|
30
35
|
"AGY_ACP_ENABLE_OAUTH",
|
|
36
|
+
// OMB sets the new-session model itself (antigravity.ts sessionModelEnv).
|
|
37
|
+
"AGY_ACP_DEFAULT_MODEL",
|
|
31
38
|
"GEMINI_HOME",
|
|
32
39
|
"AGY_ACP_FORCE_FILE_STORAGE",
|
|
33
40
|
"ANTIGRAVITY_HARNESS_PATH",
|
|
@@ -121,7 +128,22 @@ export async function prepareAntigravityProfile(input) {
|
|
|
121
128
|
PYTHONUNBUFFERED: "1",
|
|
122
129
|
ELECTRON_RUN_AS_NODE: "1",
|
|
123
130
|
});
|
|
124
|
-
|
|
131
|
+
// On Windows the runtime unpacks 0.34-1.26 GB into TEMP on every launch.
|
|
132
|
+
// Keep that in a folder OMB owns and can clean. It is the same folder for
|
|
133
|
+
// every launch of this instance, so the pooled process's spawn contract
|
|
134
|
+
// (which hashes this environment) does not change from turn to turn.
|
|
135
|
+
let tempDirectory;
|
|
136
|
+
if ((input.platform ?? process.platform) === "win32") {
|
|
137
|
+
tempDirectory = resolve(input.tempDirectory ?? antigravityTempDir(input.baseDir ?? DATA_DIR, input.instanceId));
|
|
138
|
+
await mkdir(tempDirectory, { recursive: true });
|
|
139
|
+
setAntigravityTempEnvironment(environment, tempDirectory, "win32");
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
directory,
|
|
143
|
+
tokenPath: join(acpDirectory, "acp_token.json"),
|
|
144
|
+
environment,
|
|
145
|
+
...(tempDirectory ? { tempDirectory } : {}),
|
|
146
|
+
};
|
|
125
147
|
}
|
|
126
148
|
export async function antigravityProfileAuthenticated(profile) {
|
|
127
149
|
try {
|
|
@@ -177,6 +199,10 @@ export class AntigravityAcpClient {
|
|
|
177
199
|
});
|
|
178
200
|
}
|
|
179
201
|
consume(chunk) {
|
|
202
|
+
// Output after close has no reader; do not buffer it while the process
|
|
203
|
+
// is given time to exit.
|
|
204
|
+
if (this.closed)
|
|
205
|
+
return;
|
|
180
206
|
if (!this.initializationComplete)
|
|
181
207
|
this.startupOutputBytes += Buffer.byteLength(chunk);
|
|
182
208
|
this.buffer += chunk;
|
|
@@ -287,15 +313,40 @@ export class AntigravityAcpClient {
|
|
|
287
313
|
this.nativeStartupHint = undefined;
|
|
288
314
|
return initialized;
|
|
289
315
|
}
|
|
316
|
+
/** End the runtime's input and let it exit by itself, so it removes what
|
|
317
|
+
* it unpacked; stop it by force only if it is still running after
|
|
318
|
+
* GRACEFUL_EXIT_MS. killCliTree also runs after a clean exit. On macOS and
|
|
319
|
+
* Linux that reaps any helper still in the process group. On Windows it
|
|
320
|
+
* does nothing once the runtime has exited (procs.ts stopCliTree), so a
|
|
321
|
+
* helper that outlives a clean exit is left running, as in the ACP pool. */
|
|
290
322
|
close() {
|
|
291
323
|
if (this.closed)
|
|
292
324
|
return;
|
|
293
325
|
this.closed = true;
|
|
294
326
|
this.failAll(new Error("Antigravity ACP was closed."));
|
|
295
|
-
this.stopping =
|
|
327
|
+
this.stopping = this.stopGracefully();
|
|
296
328
|
}
|
|
297
|
-
|
|
298
|
-
|
|
329
|
+
async stopGracefully() {
|
|
330
|
+
try {
|
|
331
|
+
this.child.stdin?.end();
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
/* already closed; the kill below still applies */
|
|
335
|
+
}
|
|
336
|
+
let timer;
|
|
337
|
+
await Promise.race([
|
|
338
|
+
this.exited,
|
|
339
|
+
new Promise((resolve) => {
|
|
340
|
+
timer = setTimeout(resolve, GRACEFUL_EXIT_MS);
|
|
341
|
+
timer.unref?.();
|
|
342
|
+
}),
|
|
343
|
+
]);
|
|
344
|
+
clearTimeout(timer);
|
|
345
|
+
return killCliTree(this.child);
|
|
346
|
+
}
|
|
347
|
+
/** Allow the graceful exit, the shared 5s TERM grace, and the 1s
|
|
348
|
+
* force-stop verification to finish. */
|
|
349
|
+
async closeAndWait(timeoutMs = 12_000) {
|
|
299
350
|
this.close();
|
|
300
351
|
let timer;
|
|
301
352
|
const timedOut = new Promise((resolve) => {
|
|
@@ -478,20 +529,19 @@ export function isValidAntigravityInitializeResult(initialized, expectedVersion)
|
|
|
478
529
|
export async function validateAntigravityRuntime(runtime, expectedVersion) {
|
|
479
530
|
const profileDirectory = await mkdtemp(join(tmpdir(), "openmaus-antigravity-verify-"));
|
|
480
531
|
let client;
|
|
532
|
+
let tempDirectory;
|
|
481
533
|
let failed = false;
|
|
482
534
|
let failure;
|
|
483
535
|
try {
|
|
536
|
+
// On Windows every verification unpacks into one shared folder under
|
|
537
|
+
// DATA_DIR/tmp/agy, swept below once the runtime is gone.
|
|
484
538
|
const profile = await prepareAntigravityProfile({
|
|
485
539
|
instanceId: `verify-${randomUUID()}`,
|
|
486
540
|
runtime,
|
|
487
541
|
profileDirectory,
|
|
488
|
-
|
|
489
|
-
// into TEMP. Forced shutdown skips its own cleanup. Keep verification's
|
|
490
|
-
// extraction inside the profile we already remove after confirmed close.
|
|
491
|
-
baseEnv: process.platform === "win32"
|
|
492
|
-
? { ...process.env, TEMP: profileDirectory, TMP: profileDirectory }
|
|
493
|
-
: undefined,
|
|
542
|
+
tempDirectory: antigravityTempDir(DATA_DIR, VERIFICATION_TEMP_KEY),
|
|
494
543
|
});
|
|
544
|
+
tempDirectory = profile.tempDirectory;
|
|
495
545
|
client = new AntigravityAcpClient(runtime, profile, profileDirectory);
|
|
496
546
|
const initialized = await client.initialize();
|
|
497
547
|
if (!isValidAntigravityInitializeResult(initialized, expectedVersion)) {
|
|
@@ -521,8 +571,16 @@ export async function validateAntigravityRuntime(runtime, expectedVersion) {
|
|
|
521
571
|
if (stopped) {
|
|
522
572
|
await rm(profileDirectory, { recursive: true, force: true, maxRetries: 4, retryDelay: 250 })
|
|
523
573
|
.catch((error) => {
|
|
524
|
-
console.warn(`antigravity: could not remove verification
|
|
574
|
+
console.warn(`antigravity: could not remove verification files ${profileDirectory}: ${error instanceof Error ? error.message : String(error)}`);
|
|
525
575
|
});
|
|
576
|
+
// The temp folder is shared, so only unpack folders whose process is
|
|
577
|
+
// gone go: this run's, and any an earlier run left when its runtime
|
|
578
|
+
// would not stop.
|
|
579
|
+
if (tempDirectory) {
|
|
580
|
+
await sweepAntigravityTemp(VERIFICATION_TEMP_KEY).catch((error) => {
|
|
581
|
+
console.warn(`antigravity: could not clear verification files: ${error instanceof Error ? error.message : String(error)}`);
|
|
582
|
+
});
|
|
583
|
+
}
|
|
526
584
|
}
|
|
527
585
|
if (failed)
|
|
528
586
|
throw failure;
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// Where Google's Windows Antigravity runtime unpacks itself, and how the
|
|
2
|
+
// space it leaves behind is reclaimed.
|
|
3
|
+
//
|
|
4
|
+
// Adapted from pingdotgg/t3code@c18e5ea6ed, MIT License, Copyright (c) 2026
|
|
5
|
+
// T3 Tools Inc. (full text in third_party/t3-code/LICENSE); modified:
|
|
6
|
+
// - apps/server/src/provider/antigravityAuthSupport.ts:187-212, 238-253
|
|
7
|
+
// (a short temp folder per instance, beside rather than inside the
|
|
8
|
+
// profile, and TEMP/TMP pointed at it for the runtime);
|
|
9
|
+
// - apps/server/src/provider/acp/AntigravitySessionFiles.ts:45-61 and
|
|
10
|
+
// apps/server/src/provider/Drivers/AntigravityDriver.ts:121-132
|
|
11
|
+
// (reclaim what earlier runs left behind when the driver starts).
|
|
12
|
+
// What OMB does differently: the folder is stable per instance, never per
|
|
13
|
+
// process, because the ACP pool's contractKey hashes the launch environment
|
|
14
|
+
// and a new TEMP per process would respawn on every turn. t3code's per-process
|
|
15
|
+
// run-* folders (AntigravityDriver.ts:202-227) are deliberately not copied.
|
|
16
|
+
// Because the folder is shared, the sweep removes only unpack folders whose
|
|
17
|
+
// process is gone instead of the whole folder. The user-started cleanup of the
|
|
18
|
+
// system temp folder is OMB's own; t3code never touches system temp.
|
|
19
|
+
//
|
|
20
|
+
// Background: agy_acp_server.exe is a PyInstaller one-file build. Every launch
|
|
21
|
+
// unpacks thousands of files (0.34-1.26 GB) into a new %TEMP%\_MEI... folder,
|
|
22
|
+
// and a forced stop (taskkill /T /F) skips PyInstaller's own cleanup, so the
|
|
23
|
+
// folder stays behind. PyInstaller names it `_MEI` + the unpacking process ID
|
|
24
|
+
// + a counter from _wtempnam: the ID is `%08x` since 6.22.1 and `%d` before
|
|
25
|
+
// (bootloader/src/pyi_utils_win32.c, pyi_create_temporary_application_directory).
|
|
26
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
27
|
+
import { lstat, readdir, realpath, rename, rm } from "node:fs/promises";
|
|
28
|
+
import { tmpdir } from "node:os";
|
|
29
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
30
|
+
import { DATA_DIR } from "../config.js";
|
|
31
|
+
const UNPACK_PREFIX = "_MEI";
|
|
32
|
+
/** An unpack folder OMB has claimed by renaming it and is deleting. If the
|
|
33
|
+
* delete stops part way, the next sweep finishes it. */
|
|
34
|
+
const CLAIM_PREFIX = ".omb-removing-";
|
|
35
|
+
/** Files at the root of every Antigravity unpack folder. Other programs use
|
|
36
|
+
* PyInstaller too; without both of these a folder is not Antigravity's. */
|
|
37
|
+
const ANTIGRAVITY_MARKERS = ["agy_acp_licenses.txt", "python310.dll"];
|
|
38
|
+
/** A folder this recent may still be unpacking, even if its ID looks free. */
|
|
39
|
+
export const SYSTEM_LEFTOVER_MIN_AGE_MS = 10 * 60_000;
|
|
40
|
+
const INSTANCE_FOLDER = /^[0-9a-f]{12}$/u;
|
|
41
|
+
/** The folder every instance's temp folder lives in. `tmp/` is already left
|
|
42
|
+
* out of workspace backups (workspace-backup.ts EXCLUDED). */
|
|
43
|
+
export function antigravityTempRoot(dataDir = DATA_DIR) {
|
|
44
|
+
return join(dataDir, "tmp", "agy");
|
|
45
|
+
}
|
|
46
|
+
/** Used in place of an instance ID for the one temp folder every runtime
|
|
47
|
+
* verification shares. It has a space, which no instance ID has, so no
|
|
48
|
+
* instance shares the folder. Being one fixed folder, what a verification
|
|
49
|
+
* leaves behind (a runtime that would not stop) is swept like an instance's
|
|
50
|
+
* leftovers: after the next verification and when the driver starts. */
|
|
51
|
+
export const VERIFICATION_TEMP_KEY = "runtime verification";
|
|
52
|
+
/** One stable, short temp folder per instance. Short, because the runtime
|
|
53
|
+
* unpacks files up to 120 characters deep and Windows paths stop at 260.
|
|
54
|
+
* Hashed, so instance IDs that differ only in case stay apart on
|
|
55
|
+
* case-insensitive disks. */
|
|
56
|
+
export function antigravityTempDir(dataDir, instanceId) {
|
|
57
|
+
const key = createHash("sha256").update(instanceId).digest("hex").slice(0, 12);
|
|
58
|
+
return join(antigravityTempRoot(dataDir), key);
|
|
59
|
+
}
|
|
60
|
+
/** Point the runtime's temp folder at `directory`. Windows only: that is
|
|
61
|
+
* where the runtime unpacks itself. Windows environment names ignore case,
|
|
62
|
+
* so an inherited `Temp` or `tmp` would otherwise sit beside ours and either
|
|
63
|
+
* could win. */
|
|
64
|
+
export function setAntigravityTempEnvironment(environment, directory, platform = process.platform) {
|
|
65
|
+
if (platform !== "win32")
|
|
66
|
+
return;
|
|
67
|
+
for (const key of Object.keys(environment)) {
|
|
68
|
+
const upper = key.toUpperCase();
|
|
69
|
+
if (upper === "TEMP" || upper === "TMP")
|
|
70
|
+
delete environment[key];
|
|
71
|
+
}
|
|
72
|
+
environment.TEMP = directory;
|
|
73
|
+
environment.TMP = directory;
|
|
74
|
+
}
|
|
75
|
+
/** Process IDs that could own an unpack folder with this name, or null when
|
|
76
|
+
* the name is not a PyInstaller unpack folder at all.
|
|
77
|
+
*
|
|
78
|
+
* Since 6.22.1 the ID is 8 hex digits. Before that it is plain decimal and
|
|
79
|
+
* runs straight into _wtempnam's decimal counter, so the split is ambiguous:
|
|
80
|
+
* every split that leaves a 1-3 digit counter is a candidate. A folder counts
|
|
81
|
+
* as abandoned only when every candidate is gone, so the guess can keep a
|
|
82
|
+
* leftover a while longer but can never delete a live one. */
|
|
83
|
+
export function unpackFolderOwnerPids(name) {
|
|
84
|
+
if (!name.startsWith(UNPACK_PREFIX))
|
|
85
|
+
return null;
|
|
86
|
+
const rest = name.slice(UNPACK_PREFIX.length);
|
|
87
|
+
const pids = new Set();
|
|
88
|
+
if (/^[0-9a-f]{8}\d+$/iu.test(rest))
|
|
89
|
+
pids.add(Number.parseInt(rest.slice(0, 8), 16));
|
|
90
|
+
if (/^[1-9]\d+$/u.test(rest)) {
|
|
91
|
+
for (let counter = 1; counter <= 3 && counter < rest.length; counter++) {
|
|
92
|
+
pids.add(Number.parseInt(rest.slice(0, rest.length - counter), 10));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const valid = [...pids].filter((pid) => Number.isSafeInteger(pid) && pid > 0);
|
|
96
|
+
return valid.length ? valid : null;
|
|
97
|
+
}
|
|
98
|
+
/** Whether a process with this ID exists. Only "no such process" (ESRCH)
|
|
99
|
+
* proves it is gone: EPERM means it exists but belongs to someone else. */
|
|
100
|
+
export function processIsAlive(pid, kill = (target, signal) => process.kill(target, signal)) {
|
|
101
|
+
try {
|
|
102
|
+
kill(pid, 0);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return error?.code !== "ESRCH";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function ownedByDeadProcess(name, isAlive) {
|
|
110
|
+
const pids = unpackFolderOwnerPids(name);
|
|
111
|
+
return pids !== null && !pids.some((pid) => isAlive(pid));
|
|
112
|
+
}
|
|
113
|
+
/** A real directory (not a link or junction to one), nothing there at all,
|
|
114
|
+
* or anything else. */
|
|
115
|
+
async function entryKind(path) {
|
|
116
|
+
try {
|
|
117
|
+
const info = await lstat(path);
|
|
118
|
+
return info.isDirectory() && !info.isSymbolicLink() ? "directory" : "other";
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
return error?.code === "ENOENT" ? "gone" : "other";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function realDirectory(path) {
|
|
125
|
+
return (await entryKind(path)) === "directory";
|
|
126
|
+
}
|
|
127
|
+
async function listDirectory(path) {
|
|
128
|
+
try {
|
|
129
|
+
return await readdir(path, { withFileTypes: true });
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** The instance folders under OMB's temp root, each resolved through the
|
|
136
|
+
* disk. Returns nothing when any step of dataDir/tmp/agy/<id> is a link or
|
|
137
|
+
* junction, so a sweep can never be steered outside OMB's own folder. */
|
|
138
|
+
async function ownedTempDirectories(dataDir, only) {
|
|
139
|
+
const root = resolve(antigravityTempRoot(dataDir));
|
|
140
|
+
let realRoot;
|
|
141
|
+
try {
|
|
142
|
+
const realData = await realpath(resolve(dataDir));
|
|
143
|
+
realRoot = await realpath(root);
|
|
144
|
+
if (realRoot !== join(realData, "tmp", "agy"))
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
const names = only === undefined
|
|
151
|
+
? (await listDirectory(root)).filter((entry) => entry.isDirectory()).map((entry) => entry.name)
|
|
152
|
+
: [only];
|
|
153
|
+
const owned = [];
|
|
154
|
+
for (const name of names) {
|
|
155
|
+
if (!INSTANCE_FOLDER.test(name))
|
|
156
|
+
continue;
|
|
157
|
+
const directory = join(root, name);
|
|
158
|
+
try {
|
|
159
|
+
if (await realpath(directory) !== join(realRoot, name))
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
owned.push(directory);
|
|
166
|
+
}
|
|
167
|
+
return owned;
|
|
168
|
+
}
|
|
169
|
+
/** Unpack folders in one OMB-owned temp folder whose process is gone, and
|
|
170
|
+
* folders a previous sweep claimed but did not finish deleting. */
|
|
171
|
+
async function abandonedIn(directory, isAlive) {
|
|
172
|
+
const found = [];
|
|
173
|
+
for (const entry of await listDirectory(directory)) {
|
|
174
|
+
// Dirent follows lstat: a link or junction is never a directory here.
|
|
175
|
+
if (!entry.isDirectory())
|
|
176
|
+
continue;
|
|
177
|
+
if (entry.name.startsWith(CLAIM_PREFIX) || ownedByDeadProcess(entry.name, isAlive)) {
|
|
178
|
+
found.push(join(directory, entry.name));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return found;
|
|
182
|
+
}
|
|
183
|
+
async function isAntigravityUnpack(directory) {
|
|
184
|
+
for (const marker of ANTIGRAVITY_MARKERS) {
|
|
185
|
+
try {
|
|
186
|
+
if (!(await lstat(join(directory, marker))).isFile())
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
/** Antigravity unpack folders in the system temp folder: both marker files,
|
|
196
|
+
* no live owner, and untouched for ten minutes. Other apps (Zed, t3code)
|
|
197
|
+
* start the same runtime, so this runs only when the person asks. */
|
|
198
|
+
async function systemLeftovers(systemTemp, isAlive, now) {
|
|
199
|
+
const found = [];
|
|
200
|
+
for (const entry of await listDirectory(systemTemp)) {
|
|
201
|
+
if (!entry.isDirectory())
|
|
202
|
+
continue;
|
|
203
|
+
const directory = join(systemTemp, entry.name);
|
|
204
|
+
const claimed = entry.name.startsWith(CLAIM_PREFIX + UNPACK_PREFIX);
|
|
205
|
+
if (!claimed && !ownedByDeadProcess(entry.name, isAlive))
|
|
206
|
+
continue;
|
|
207
|
+
if (!claimed && !(await isAntigravityUnpack(directory)))
|
|
208
|
+
continue;
|
|
209
|
+
try {
|
|
210
|
+
if (now - (await lstat(directory)).mtimeMs < SYSTEM_LEFTOVER_MIN_AGE_MS)
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
found.push(directory);
|
|
217
|
+
}
|
|
218
|
+
return found;
|
|
219
|
+
}
|
|
220
|
+
/** Bytes under a folder, without following links. Stops at the deadline. */
|
|
221
|
+
async function folderBytes(directory, deadline = Infinity) {
|
|
222
|
+
let bytes = 0;
|
|
223
|
+
const pending = [directory];
|
|
224
|
+
while (pending.length) {
|
|
225
|
+
if (Date.now() > deadline)
|
|
226
|
+
return { bytes, complete: false };
|
|
227
|
+
const current = pending.pop();
|
|
228
|
+
for (const entry of await listDirectory(current)) {
|
|
229
|
+
const path = join(current, entry.name);
|
|
230
|
+
if (entry.isDirectory())
|
|
231
|
+
pending.push(path);
|
|
232
|
+
else if (entry.isFile()) {
|
|
233
|
+
try {
|
|
234
|
+
bytes += (await lstat(path)).size;
|
|
235
|
+
}
|
|
236
|
+
catch { /* already gone */ }
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return { bytes, complete: true };
|
|
241
|
+
}
|
|
242
|
+
/** Claim a folder by renaming it, then delete it. On Windows the rename
|
|
243
|
+
* fails while any process still has a file inside open (every live runtime
|
|
244
|
+
* has its DLLs loaded), which is a second check after the process ID.
|
|
245
|
+
* Returns the bytes freed; "gone" when someone else (the automatic sweep and
|
|
246
|
+
* "Free up space" can pick the same folder) removed it first; or null when
|
|
247
|
+
* the folder was left alone. */
|
|
248
|
+
async function claimAndRemove(directory, countBytes) {
|
|
249
|
+
const name = basename(directory);
|
|
250
|
+
// Checked again at the last moment: the listing may be stale.
|
|
251
|
+
const kind = await entryKind(directory);
|
|
252
|
+
if (kind !== "directory")
|
|
253
|
+
return kind === "gone" ? "gone" : null;
|
|
254
|
+
let claimed = directory;
|
|
255
|
+
if (!name.startsWith(CLAIM_PREFIX)) {
|
|
256
|
+
claimed = join(dirname(directory), `${CLAIM_PREFIX}${name}-${randomUUID().slice(0, 8)}`);
|
|
257
|
+
try {
|
|
258
|
+
await rename(directory, claimed);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
// Lost the race to another remover: the folder is gone, not in use.
|
|
262
|
+
if (error?.code === "ENOENT" && (await entryKind(directory)) === "gone")
|
|
263
|
+
return "gone";
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const { bytes } = countBytes ? await folderBytes(claimed) : { bytes: 0 };
|
|
268
|
+
try {
|
|
269
|
+
await rm(claimed, { recursive: true, force: true, maxRetries: 3, retryDelay: 200 });
|
|
270
|
+
return bytes;
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// The claimed name stays, and the next sweep finishes the job.
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const sweeps = new Map();
|
|
278
|
+
/** Delete this instance's abandoned unpack folders. Runs when the driver
|
|
279
|
+
* starts and before each launch (and, for VERIFICATION_TEMP_KEY, after each
|
|
280
|
+
* runtime verification). Touches nothing outside dataDir/tmp/agy/<instance>.
|
|
281
|
+
* Returns how many folders it removed. */
|
|
282
|
+
export function sweepAntigravityTemp(instanceId, options = {}) {
|
|
283
|
+
const dataDir = options.dataDir ?? DATA_DIR;
|
|
284
|
+
const key = antigravityTempDir(dataDir, instanceId);
|
|
285
|
+
const running = sweeps.get(key);
|
|
286
|
+
if (running)
|
|
287
|
+
return running;
|
|
288
|
+
const sweep = (async () => {
|
|
289
|
+
const isAlive = options.isAlive ?? processIsAlive;
|
|
290
|
+
let removed = 0;
|
|
291
|
+
for (const directory of await ownedTempDirectories(dataDir, basename(key))) {
|
|
292
|
+
for (const folder of await abandonedIn(directory, isAlive)) {
|
|
293
|
+
if (typeof await claimAndRemove(folder, false) === "number")
|
|
294
|
+
removed++;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return removed;
|
|
298
|
+
})().finally(() => sweeps.delete(key));
|
|
299
|
+
sweeps.set(key, sweep);
|
|
300
|
+
return sweep;
|
|
301
|
+
}
|
|
302
|
+
/** Fire-and-forget form for launch paths: never delays or fails a turn. */
|
|
303
|
+
export function scheduleAntigravityTempSweep(instanceId, options = {}) {
|
|
304
|
+
void sweepAntigravityTemp(instanceId, options).catch((error) => {
|
|
305
|
+
console.warn(`antigravity: could not clear leftover runtime files: ${error instanceof Error ? error.message : String(error)}`);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
async function leftoverCandidates(options) {
|
|
309
|
+
const isAlive = options.isAlive ?? processIsAlive;
|
|
310
|
+
const candidates = [];
|
|
311
|
+
for (const directory of await ownedTempDirectories(options.dataDir ?? DATA_DIR)) {
|
|
312
|
+
for (const path of await abandonedIn(directory, isAlive))
|
|
313
|
+
candidates.push({ path, where: "app" });
|
|
314
|
+
}
|
|
315
|
+
if ((options.platform ?? process.platform) === "win32") {
|
|
316
|
+
// TEMP itself may legitimately be a junction; what is inside it may not.
|
|
317
|
+
const systemTemp = await realpath(resolve(options.systemTemp ?? tmpdir())).catch(() => null);
|
|
318
|
+
if (systemTemp && await realDirectory(systemTemp)) {
|
|
319
|
+
for (const path of await systemLeftovers(systemTemp, isAlive, options.now ?? Date.now())) {
|
|
320
|
+
candidates.push({ path, where: "system" });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return candidates;
|
|
325
|
+
}
|
|
326
|
+
/** What "Free up space" would delete, and roughly how much it is. Deletes
|
|
327
|
+
* nothing. */
|
|
328
|
+
export async function findAntigravityLeftovers(options = {}) {
|
|
329
|
+
const deadline = Date.now() + (options.budgetMs ?? 15_000);
|
|
330
|
+
const folders = [];
|
|
331
|
+
let complete = true;
|
|
332
|
+
for (const candidate of await leftoverCandidates(options)) {
|
|
333
|
+
const counted = await folderBytes(candidate.path, deadline);
|
|
334
|
+
complete &&= counted.complete;
|
|
335
|
+
folders.push({ ...candidate, bytes: counted.bytes });
|
|
336
|
+
}
|
|
337
|
+
return { folders, bytes: folders.reduce((total, folder) => total + folder.bytes, 0), complete };
|
|
338
|
+
}
|
|
339
|
+
/** "Free up space": looks again rather than trusting an earlier scan, so a
|
|
340
|
+
* folder that came back to life since is left alone. */
|
|
341
|
+
export async function removeAntigravityLeftovers(options = {}) {
|
|
342
|
+
const deadline = Date.now() + (options.budgetMs ?? 120_000);
|
|
343
|
+
const candidates = await leftoverCandidates(options);
|
|
344
|
+
let removed = 0;
|
|
345
|
+
let alreadyGone = 0;
|
|
346
|
+
let freedBytes = 0;
|
|
347
|
+
for (const candidate of candidates) {
|
|
348
|
+
if (Date.now() > deadline)
|
|
349
|
+
break;
|
|
350
|
+
const bytes = await claimAndRemove(candidate.path, true);
|
|
351
|
+
if (bytes === "gone")
|
|
352
|
+
alreadyGone++;
|
|
353
|
+
if (typeof bytes !== "number")
|
|
354
|
+
continue;
|
|
355
|
+
removed++;
|
|
356
|
+
freedBytes += bytes;
|
|
357
|
+
}
|
|
358
|
+
// A folder another remover got to first is not "still in use".
|
|
359
|
+
return { removed, freedBytes, remaining: candidates.length - removed - alreadyGone };
|
|
360
|
+
}
|
|
@@ -2,6 +2,7 @@ import { createAcpDriver } from "./acp/core.js";
|
|
|
2
2
|
import { AntigravityAuthController, antigravityProfileAuthenticated, catalogFromAntigravityConfigOptions, prepareAntigravityProfile, probeAntigravityModels, validateAntigravityRuntime, } from "./antigravity-acp.js";
|
|
3
3
|
import { antigravityManagedInstallAvailable, installAntigravityRuntime, resolveAntigravityRuntime, } from "./antigravity-runtime.js";
|
|
4
4
|
import { resolveAntigravityReleaseAsset } from "./antigravity-release.js";
|
|
5
|
+
import { VERIFICATION_TEMP_KEY, scheduleAntigravityTempSweep } from "./antigravity-temp.js";
|
|
5
6
|
import { augmentedPath } from "../env-path.js";
|
|
6
7
|
export const STATIC_ANTIGRAVITY_MODELS = {
|
|
7
8
|
default: "gemini-3.8-flash-high",
|
|
@@ -39,6 +40,14 @@ const support = {
|
|
|
39
40
|
images: true,
|
|
40
41
|
spawnArgs: () => [],
|
|
41
42
|
selectModel: { configId: "model" },
|
|
43
|
+
// Google's server reads its new-session default from this variable
|
|
44
|
+
// (model_selection.get_default_model_id in agy_acp_server 1.1.1). Starting
|
|
45
|
+
// on OMB's model skips a switch that re-fetches the account's model list
|
|
46
|
+
// from Google: 1.3-1.8 s on every new or cold-resumed conversation.
|
|
47
|
+
sessionModelEnv: "AGY_ACP_DEFAULT_MODEL",
|
|
48
|
+
// configureSession sets the permission mode on every turn and nothing in
|
|
49
|
+
// the launch depends on it, so an approval change keeps the process.
|
|
50
|
+
sessionScopedApproval: true,
|
|
42
51
|
resumeMethod: "resume",
|
|
43
52
|
clientFileSystem: true,
|
|
44
53
|
redactStderr: true,
|
|
@@ -47,6 +56,11 @@ const support = {
|
|
|
47
56
|
resolveCommand: async (env, config, instanceId) => {
|
|
48
57
|
const runtime = await resolveAntigravityRuntime(config.cli, env);
|
|
49
58
|
const profile = await prepareAntigravityProfile({ instanceId, runtime, baseEnv: env });
|
|
59
|
+
// A process stopped by force since the last launch (an idle close that
|
|
60
|
+
// outlived its grace, a crash) left its unpacked files behind. Reclaim
|
|
61
|
+
// them in the background; live folders are kept.
|
|
62
|
+
if (profile.tempDirectory)
|
|
63
|
+
scheduleAntigravityTempSweep(instanceId);
|
|
50
64
|
return {
|
|
51
65
|
command: runtime.executablePath,
|
|
52
66
|
args: process.platform === "linux" ? ["--uid="] : [],
|
|
@@ -112,6 +126,13 @@ export const AntigravityDriver = {
|
|
|
112
126
|
...AcpAntigravityDriver,
|
|
113
127
|
async create(input) {
|
|
114
128
|
const base = await AcpAntigravityDriver.create(input);
|
|
129
|
+
// Reclaim what a previous run of this instance left behind when it was
|
|
130
|
+
// stopped by force (Windows keeps 0.34-1.26 GB per forced stop). Only
|
|
131
|
+
// this instance's folder under DATA_DIR/tmp/agy, only folders whose
|
|
132
|
+
// process is gone; the system temp folder is cleaned only on request.
|
|
133
|
+
scheduleAntigravityTempSweep(input.instanceId);
|
|
134
|
+
// The same for a runtime verification whose runtime would not stop.
|
|
135
|
+
scheduleAntigravityTempSweep(VERIFICATION_TEMP_KEY);
|
|
115
136
|
const auth = new AntigravityAuthController();
|
|
116
137
|
let installFailure;
|
|
117
138
|
const runtimeAndProfile = async () => {
|