openmausbot 0.1.91 → 0.1.93

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.
Files changed (164) hide show
  1. package/dist/assets/DesktopViewer-CKNR3SEW.js +5 -0
  2. package/dist/assets/{abnfDiagram-O67JEVCF-yP-w8pRq.js → abnfDiagram-O67JEVCF-Ckrm1I1X.js} +1 -1
  3. package/dist/assets/{arc-DsivfZEh.js → arc-BBdBuCgB.js} +1 -1
  4. package/dist/assets/{architectureDiagram-NJMV4G6O-CILw7lzT.js → architectureDiagram-NJMV4G6O-CDdA3Hjs.js} +1 -1
  5. package/dist/assets/{blockDiagram-BEXU5L5S-DEQUV3HF.js → blockDiagram-BEXU5L5S-AUrsx1YR.js} +1 -1
  6. package/dist/assets/{c4Diagram-YGBWAQC7-lSXCZoKA.js → c4Diagram-YGBWAQC7-BEqQ0Unh.js} +1 -1
  7. package/dist/assets/channel-BU94VpxE.js +1 -0
  8. package/dist/assets/{chunk-2Q5K7J3B-QCD5bgWP.js → chunk-2Q5K7J3B-CEEuvd4C.js} +1 -1
  9. package/dist/assets/{chunk-3FUC2YCW-C5-iZJuL.js → chunk-3FUC2YCW-DyANCiHl.js} +2 -2
  10. package/dist/assets/{chunk-5VM5RSS4-cxsawMpY.js → chunk-5VM5RSS4-Dx_Slpw9.js} +1 -1
  11. package/dist/assets/{chunk-GTNCS2PH-DV8tV8H7.js → chunk-GTNCS2PH-Dl7h7iiD.js} +1 -1
  12. package/dist/assets/{chunk-GWA4HPMP-CJFZyfbW.js → chunk-GWA4HPMP-BLx07SPb.js} +1 -1
  13. package/dist/assets/{chunk-JWPE2WC7-BigbMXco.js → chunk-JWPE2WC7-BMxDj3n7.js} +1 -1
  14. package/dist/assets/{chunk-NETBCI7D-oVxTfpDy.js → chunk-NETBCI7D-CN66PT4H.js} +1 -1
  15. package/dist/assets/{chunk-WEXAMYUT-CH4BiroW.js → chunk-WEXAMYUT-6HwhsDKw.js} +1 -1
  16. package/dist/assets/{chunk-XXDRQBXY-KEDa6gax.js → chunk-XXDRQBXY-gWnLt7SN.js} +1 -1
  17. package/dist/assets/{classDiagram-v2-NBCMYWYE-BiPjfqVv.js → classDiagram-v2-NBCMYWYE-CMS5l2ea.js} +1 -1
  18. package/dist/assets/{cose-bilkent-JH36ORCC-Caxew3OY.js → cose-bilkent-JH36ORCC-CV7sQwIG.js} +1 -1
  19. package/dist/assets/{cynefin-OW5HDTMX-B_Pui369.js → cynefin-OW5HDTMX-CSyl-7vl.js} +1 -1
  20. package/dist/assets/{cynefinDiagram-VND7K2PF-X64ydIhP.js → cynefinDiagram-VND7K2PF-BCLKL6az.js} +1 -1
  21. package/dist/assets/{dagre-6A5THRUB-TqYCI1E1.js → dagre-6A5THRUB-DiaZs6S1.js} +1 -1
  22. package/dist/assets/{diagram-22UHCM2B-TeOt3rAQ.js → diagram-22UHCM2B-BeTtmt3I.js} +1 -1
  23. package/dist/assets/{diagram-3UASUU5V-D70BLhDA.js → diagram-3UASUU5V-CsdBG-QQ.js} +1 -1
  24. package/dist/assets/{diagram-ATOU4E4O-BbQDLwUC.js → diagram-ATOU4E4O-BczvdbR1.js} +1 -1
  25. package/dist/assets/{diagram-CDSNMT55-ChNJZ1u-.js → diagram-CDSNMT55-DHTTTMhi.js} +1 -1
  26. package/dist/assets/{diagram-MLGK6HIB-DMBl2APa.js → diagram-MLGK6HIB-RTjHyd8z.js} +1 -1
  27. package/dist/assets/{diagram-MPIPVDR6-CEqDchOp.js → diagram-MPIPVDR6-BDenHEH_.js} +1 -1
  28. package/dist/assets/{ebnfDiagram-ZINNZB2B-BVDKRp_-.js → ebnfDiagram-ZINNZB2B-BSiRSbtL.js} +1 -1
  29. package/dist/assets/{elk-276RUBZZ-CRkofO9B.js → elk-276RUBZZ-BCAElss-.js} +1 -1
  30. package/dist/assets/{erDiagram-OPXOYQCR-DruOMh5e.js → erDiagram-OPXOYQCR-C4_1ZPJe.js} +1 -1
  31. package/dist/assets/{flowDiagram-KWPJA3E3-8GMQeB7t.js → flowDiagram-KWPJA3E3-Bcb4Z75S.js} +1 -1
  32. package/dist/assets/{ganttDiagram-FUAMR5RP-ORE9jcsM.js → ganttDiagram-FUAMR5RP-BiPOzxzb.js} +1 -1
  33. package/dist/assets/{gitGraphDiagram-X574FWY7-BmsedsWh.js → gitGraphDiagram-X574FWY7-B1wtF3ZS.js} +1 -1
  34. package/dist/assets/index-Biji-bfL.css +1 -0
  35. package/dist/assets/{index-DKNEM8Q6.js → index-BkYwilkG.js} +1 -1
  36. package/dist/assets/index-DZlhoES0.js +597 -0
  37. package/dist/assets/{infoDiagram-VRGFBTTK-E_p7AooJ.js → infoDiagram-VRGFBTTK-A19No71U.js} +1 -1
  38. package/dist/assets/{ishikawaDiagram-OU5B5YK6-Y-B2mKda.js → ishikawaDiagram-OU5B5YK6-BQsqJ_cV.js} +1 -1
  39. package/dist/assets/{journeyDiagram-ZHPQQLJL-eA8RELcK.js → journeyDiagram-ZHPQQLJL-DAO0pVan.js} +1 -1
  40. package/dist/assets/{kanban-definition-PNTS6WVX-DqIL2e8e.js → kanban-definition-PNTS6WVX-NepmKEe2.js} +1 -1
  41. package/dist/assets/{linear-yC_29R6e.js → linear-bq1PTDTW.js} +1 -1
  42. package/dist/assets/{mermaid.core-VhEMkSND.js → mermaid.core-s4LTbQle.js} +5 -5
  43. package/dist/assets/{mindmap-definition-NLK3R4M7-oJ7kyQZy.js → mindmap-definition-NLK3R4M7-LtdbgHFH.js} +1 -1
  44. package/dist/assets/{pegDiagram-GJSIUBJH-BmfYcY0O.js → pegDiagram-GJSIUBJH-tdbaCxiK.js} +1 -1
  45. package/dist/assets/{pieDiagram-5QR66LMP-DKvgP7w9.js → pieDiagram-5QR66LMP-DvkbpW8l.js} +1 -1
  46. package/dist/assets/{quadrantDiagram-O4NWA36T-BEKL3W0L.js → quadrantDiagram-O4NWA36T-Do4KaH-E.js} +1 -1
  47. package/dist/assets/{railroadDiagram-XR7U4H2S-BEsiqR5s.js → railroadDiagram-XR7U4H2S-BQWULOAs.js} +1 -1
  48. package/dist/assets/{requirementDiagram-PLB6GJNP-DWBcWvAP.js → requirementDiagram-PLB6GJNP-w9kei1FU.js} +1 -1
  49. package/dist/assets/{sankeyDiagram-IPEJSGJF-rpZ2_O4s.js → sankeyDiagram-IPEJSGJF-D2gRy0e8.js} +1 -1
  50. package/dist/assets/{sequenceDiagram-PO4LG4MO-B99HQdJR.js → sequenceDiagram-PO4LG4MO-a2sO5_Eo.js} +1 -1
  51. package/dist/assets/{sizeCapture-INFHLROL-BjN0qT5T.js → sizeCapture-INFHLROL-CiPJmFEB.js} +1 -1
  52. package/dist/assets/{stateDiagram-v2-GCMORJYK-BbsVgGsC.js → stateDiagram-v2-GCMORJYK-DAkUUlL7.js} +1 -1
  53. package/dist/assets/{swimlanes-2SLR337P-DG3gKkeR.js → swimlanes-2SLR337P-DANf2R8Y.js} +1 -1
  54. package/dist/assets/swimlanesDiagram-TC7HE7FX-CdHxG8aM.js +8 -0
  55. package/dist/assets/{timeline-definition-EJHVYXUP-BTHOdtF_.js → timeline-definition-EJHVYXUP-BKpQmzrT.js} +1 -1
  56. package/dist/assets/{usecaseDiagram-POWQR4AR-acLSLFwc.js → usecaseDiagram-POWQR4AR-BtI7qLN-.js} +1 -1
  57. package/dist/assets/{vennDiagram-UO4OBE2U-C5JzNX9G.js → vennDiagram-UO4OBE2U-gXJXrAG_.js} +1 -1
  58. package/dist/assets/{wardleyDiagram-VNRHLVJA-DL1-PrR_.js → wardleyDiagram-VNRHLVJA-GU8Po5-n.js} +1 -1
  59. package/dist/assets/{xychartDiagram-PMCCYNJV-Bvr1477p.js → xychartDiagram-PMCCYNJV-BF4J30g6.js} +1 -1
  60. package/dist/index.html +2 -2
  61. package/dist/novnc-NOTICE.txt +118 -0
  62. package/dist-server/cloud-home-start.js +112 -14
  63. package/dist-server/companion/src/routes.js +24 -1
  64. package/dist-server/container-mcp.js +201 -16
  65. package/dist-server/drivers/agents-proxy.js +7 -4
  66. package/dist-server/enterprise/server/index.js +5 -1
  67. package/dist-server/index.js +10698 -4737
  68. package/dist-server/local-computer-proxy.js +133 -8
  69. package/dist-server/mcp-gate.js +16 -6
  70. package/dist-server/model-catalog/models-dev.snapshot.json +7299 -0
  71. package/dist-server/openmausbot.js +4656 -1258
  72. package/dist-server/pair-cli.js +4656 -1258
  73. package/dist-server/server/boat.js +5 -5
  74. package/dist-server/server/browser-engine.js +10 -1
  75. package/dist-server/server/browser-live.js +46 -2
  76. package/dist-server/server/browser-runtime.js +74 -8
  77. package/dist-server/server/browser-tool-shape.js +6 -4
  78. package/dist-server/server/chief-of-staff.js +1 -1
  79. package/dist-server/server/cloud-home-start.js +118 -26
  80. package/dist-server/server/cloud-home.js +39 -3
  81. package/dist-server/server/cloud-lending.js +74 -10
  82. package/dist-server/server/cloud-owner.js +196 -0
  83. package/dist-server/server/cloud-secrets-boot.js +6 -0
  84. package/dist-server/server/cloud-secrets.js +65 -0
  85. package/dist-server/server/computer-control.js +6 -0
  86. package/dist-server/server/config.js +187 -14
  87. package/dist-server/server/container-computer.js +46 -34
  88. package/dist-server/server/desktop-viewer-targets.js +26 -0
  89. package/dist-server/server/digest.js +12 -2
  90. package/dist-server/server/drivers/acp/core.js +339 -33
  91. package/dist-server/server/drivers/acp/cursor.js +13 -1
  92. package/dist-server/server/drivers/acp/opencode-go.js +614 -85
  93. package/dist-server/server/drivers/acp/quiet-status.js +257 -0
  94. package/dist-server/server/drivers/acp/qwen.js +18 -0
  95. package/dist-server/server/drivers/agents-catalog.js +6 -6
  96. package/dist-server/server/drivers/antigravity-acp.js +69 -11
  97. package/dist-server/server/drivers/antigravity-temp.js +360 -0
  98. package/dist-server/server/drivers/antigravity.js +21 -0
  99. package/dist-server/server/drivers/builtIn.js +2 -0
  100. package/dist-server/server/drivers/cerebras.js +108 -0
  101. package/dist-server/server/drivers/chat-mcp-tools.js +20 -3
  102. package/dist-server/server/drivers/chatgpt-plan-auth.js +481 -0
  103. package/dist-server/server/drivers/claude.js +329 -37
  104. package/dist-server/server/drivers/codex.js +228 -18
  105. package/dist-server/server/drivers/grok.js +2 -2
  106. package/dist-server/server/drivers/mistral.js +4 -3
  107. package/dist-server/server/drivers/openai-chat-protocol.js +4 -3
  108. package/dist-server/server/drivers/openai-chat.js +66 -4
  109. package/dist-server/server/drivers/openai-compat.js +73 -23
  110. package/dist-server/server/drivers/prompt-split.js +2 -0
  111. package/dist-server/server/engine-install.js +46 -12
  112. package/dist-server/server/env-path.js +21 -5
  113. package/dist-server/server/harness/registry.js +2 -2
  114. package/dist-server/server/index.js +1545 -238
  115. package/dist-server/server/lending-memory.js +479 -0
  116. package/dist-server/server/local-vm-idle.js +24 -6
  117. package/dist-server/server/local-vm-stop-reason.js +26 -0
  118. package/dist-server/server/managed-desktop.js +3 -0
  119. package/dist-server/server/mcp-bridge.js +5 -3
  120. package/dist-server/server/mcp-http.js +6 -3
  121. package/dist-server/server/mcp-oauth-discovery.js +133 -0
  122. package/dist-server/server/mcp-oauth-store.js +70 -0
  123. package/dist-server/server/mcp-oauth.js +550 -0
  124. package/dist-server/server/mcp-probe.js +14 -1
  125. package/dist-server/server/mcp-registry.js +43 -0
  126. package/dist-server/server/memory-upkeep.js +94 -84
  127. package/dist-server/server/model-catalog/catalog.js +374 -0
  128. package/dist-server/server/model-catalog/presets.js +176 -0
  129. package/dist-server/server/model-catalog/trim.js +219 -0
  130. package/dist-server/server/provider-key-check.js +5 -2
  131. package/dist-server/server/recent-work.js +1 -1
  132. package/dist-server/server/request-auth.js +23 -0
  133. package/dist-server/server/routes/antigravity-leftovers.js +45 -0
  134. package/dist-server/server/routes/bot-memory.js +31 -6
  135. package/dist-server/server/routes/desktop-viewer.js +238 -0
  136. package/dist-server/server/routine-requests.js +1 -1
  137. package/dist-server/server/routines.js +6 -3
  138. package/dist-server/server/sessions.js +56 -9
  139. package/dist-server/server/skills.js +25 -10
  140. package/dist-server/server/store.js +116 -122
  141. package/dist-server/server/thread-events.js +2 -0
  142. package/dist-server/server/thread-starters.js +33 -0
  143. package/dist-server/server/thread-title.js +46 -0
  144. package/dist-server/server/tts/fish.js +4 -5
  145. package/dist-server/server/tts/index.js +5 -3
  146. package/dist-server/server/vps-computer.js +80 -13
  147. package/dist-server/server/webhooks.js +18 -1
  148. package/dist-server/server/workspace-backup-policy.js +3 -0
  149. package/dist-server/server/workspace-backup.js +25 -0
  150. package/dist-server/server/workspace.js +66 -16
  151. package/dist-server/shared/approval-mode.js +1 -1
  152. package/dist-server/shared/redact.js +1 -0
  153. package/dist-server/shared/tool-name.js +22 -0
  154. package/dist-server/shared/workspace-backup-client.js +1 -0
  155. package/dist-server/src/lib/citations.js +177 -0
  156. package/dist-server/src/lib/composer-attachments.js +7 -1
  157. package/dist-server/vps-container-mcp.js +202 -16
  158. package/dist-server/workspace-backup.worker.js +87 -9
  159. package/enterprise/server/index.js +5 -1
  160. package/package.json +1 -1
  161. package/dist/assets/channel-DrfGMu7X.js +0 -1
  162. package/dist/assets/index-BlnCEcU6.js +0 -588
  163. package/dist/assets/index-DVxjOvID.css +0 -1
  164. package/dist/assets/swimlanesDiagram-TC7HE7FX-BCZhxl7e.js +0 -8
@@ -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 () => {
@@ -15,6 +15,7 @@ import { HermesAgentDriver } from "./acp/hermes.js";
15
15
  import { OpenAICompatDriver } from "./openai-compat.js";
16
16
  import { PiDriver } from "./pi.js";
17
17
  import { MistralDriver } from "./mistral.js";
18
+ import { CerebrasDriver } from "./cerebras.js";
18
19
  import { MinimaxDriver } from "./minimax.js";
19
20
  export const BUILT_IN_DRIVERS = [
20
21
  GrokDriver,
@@ -35,4 +36,5 @@ export const BUILT_IN_DRIVERS = [
35
36
  BoatAgentDriver,
36
37
  MinimaxDriver,
37
38
  MistralDriver,
39
+ CerebrasDriver,
38
40
  ];
@@ -0,0 +1,108 @@
1
+ import { z } from "zod";
2
+ import { createOpenAIChatRuntime } from "./openai-chat.js";
3
+ // Cerebras serves open models on wafer-scale chips: same OpenAI chat
4
+ // contract, several times the tokens per second of a GPU cloud.
5
+ const DEFAULT_URL = "https://api.cerebras.ai/v1";
6
+ // The account tier is unknown: use the shared free-tier limit, not paid 128K.
7
+ // https://inference-docs.cerebras.ai/models/overview
8
+ const DEFAULT_MODELS = {
9
+ default: "gpt-oss-120b",
10
+ options: [
11
+ { id: "gpt-oss-120b", label: "GPT OSS 120B", contextWindow: 65536 },
12
+ { id: "qwen-3.8-27b", label: "Qwen3.8 27B", contextWindow: 65536 },
13
+ ],
14
+ };
15
+ const configSchema = z.object({
16
+ url: z.string().trim().url().default(DEFAULT_URL),
17
+ model: z.string().trim().min(1).optional(),
18
+ tools: z.boolean().optional(),
19
+ });
20
+ function decodeConfig(raw) {
21
+ const config = configSchema.parse(raw ?? {});
22
+ config.url = config.url.replace(/\/+$/, "");
23
+ const url = new URL(config.url);
24
+ if (url.protocol !== "https:" && !(url.protocol === "http:" &&
25
+ ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
26
+ throw new Error("Cerebras requires HTTPS, except for a local test endpoint.");
27
+ }
28
+ return config;
29
+ }
30
+ const modelCard = z.object({ id: z.string().min(1) });
31
+ /** Cerebras answers "Only 'text' content type can be used" for these. */
32
+ const TEXT_ONLY_MODELS = /^(?:gpt-oss|llama3\.1-8b|llama-3\.3-70b)/i;
33
+ export const CerebrasDriver = {
34
+ driverKind: "cerebras",
35
+ metadata: { displayName: "Cerebras (API)", supportsMultipleInstances: true, access: "api" },
36
+ models: DEFAULT_MODELS,
37
+ install: {
38
+ docsUrl: "https://cloud.cerebras.ai/",
39
+ settings: "connections",
40
+ signInCommand: "Save a Cerebras API key in Settings → Connections, or set CEREBRAS_API_KEY on the server.",
41
+ },
42
+ decodeConfig,
43
+ defaultConfig: () => decodeConfig({}),
44
+ async create(input) {
45
+ const { config } = input;
46
+ const apiKey = (input.environment.CEREBRAS_API_KEY ?? process.env.CEREBRAS_API_KEY ?? "").trim();
47
+ const known = new Map(DEFAULT_MODELS.options.map((option) => [option.id, option]));
48
+ const withConfiguredModel = (options) => {
49
+ const preferred = config.model ?? DEFAULT_MODELS.default;
50
+ if (config.model && !options.some((option) => option.id === config.model)) {
51
+ options = [{ id: config.model, label: config.model }, ...options];
52
+ }
53
+ return { default: options.some((option) => option.id === preferred) ? preferred : options[0].id, options };
54
+ };
55
+ let catalog = withConfiguredModel(DEFAULT_MODELS.options);
56
+ const refreshModels = async () => {
57
+ if (!apiKey)
58
+ return;
59
+ try {
60
+ const response = await fetch(`${config.url}/models`, {
61
+ headers: { authorization: `Bearer ${apiKey}` },
62
+ redirect: "error",
63
+ signal: AbortSignal.timeout(8_000),
64
+ });
65
+ if (!response.ok)
66
+ return;
67
+ const json = await response.json();
68
+ const rows = Array.isArray(json) ? json : z.object({ data: z.array(z.unknown()) }).parse(json).data;
69
+ const options = [];
70
+ const seen = new Set();
71
+ for (const row of rows) {
72
+ const parsed = modelCard.safeParse(row);
73
+ if (!parsed.success || !parsed.data.id.trim() || seen.has(parsed.data.id))
74
+ continue;
75
+ seen.add(parsed.data.id);
76
+ options.push(known.get(parsed.data.id) ?? { id: parsed.data.id, label: parsed.data.id });
77
+ }
78
+ if (options.length)
79
+ catalog = withConfiguredModel(options);
80
+ }
81
+ catch {
82
+ // A failed refresh keeps the last usable catalog, including custom models.
83
+ }
84
+ };
85
+ if (apiKey)
86
+ void refreshModels();
87
+ return createOpenAIChatRuntime({
88
+ input, driverKind: "cerebras", apiKey, apiUrl: config.url,
89
+ tools: config.tools, models: () => catalog, refreshModels, reasoning: true,
90
+ reasoningReplayField: "reasoning",
91
+ computerUse: true, imageInput: (model) => !TEXT_ONLY_MODELS.test(model),
92
+ nudgeAnnouncedAction: true,
93
+ requestBody: (model, messages, stream) => ({
94
+ model, messages, stream,
95
+ ...(stream ? { stream_options: { include_usage: true } } : {}),
96
+ }),
97
+ httpErrorLabel: "Cerebras",
98
+ missingKeyError: "Save a Cerebras API key in Settings → Connections, or set CEREBRAS_API_KEY.",
99
+ unavailableReason: "No Cerebras API key — open Settings → Connections.",
100
+ timeoutMs: 180_000, billing: "metered", includeUsageInCompleted: true,
101
+ nativeLog: {
102
+ source: "cerebras.chat.completions",
103
+ outgoing: (_turn, messages, model) => ({ model, messageCount: messages.length }),
104
+ incoming: ({ text, reasoning, usage }) => ({ textLength: text.length, reasoningLength: reasoning.length, usage }),
105
+ },
106
+ });
107
+ },
108
+ };
@@ -266,6 +266,22 @@ function boundedText(value) {
266
266
  end -= 1;
267
267
  return `${bytes.subarray(0, end).toString()}\n[MCP result truncated at 50KB; request less output.]`;
268
268
  }
269
+ /** Optional fields a tool documents as "omit to use the default", where a
270
+ * blank string is an error instead. Smaller models fill optional fields with
271
+ * "" rather than leaving them out; dropping the blank restores the documented
272
+ * call. agent_browser_read: "Omit url to read the active tab." */
273
+ const BUILT_IN_BROWSER_BLANK_MEANS_OMITTED = {
274
+ agent_browser_read: ["url"],
275
+ };
276
+ function omitBlankDefaults(builtInBrowser, tool, args) {
277
+ if (!builtInBrowser || !object(args))
278
+ return;
279
+ for (const field of BUILT_IN_BROWSER_BLANK_MEANS_OMITTED[tool] ?? []) {
280
+ const value = args[field];
281
+ if (typeof value === "string" && !value.trim())
282
+ delete args[field];
283
+ }
284
+ }
269
285
  export async function mountChatTools(integrations, signal, computerUse = false) {
270
286
  const servers = [];
271
287
  if (computerUse && integrations?.computer)
@@ -316,12 +332,12 @@ export async function mountChatTools(integrations, signal, computerUse = false)
316
332
  // image delivery, including custom servers. Text stays bounded below.
317
333
  const client = "boxId" in descriptor ? new ChatBoatClient(descriptor) : new ChatMcpClient(descriptor, computerUse);
318
334
  clients.push(client);
319
- return { name, client, tools: await client.tools(signal) };
335
+ return { name, client, builtInBrowser: descriptor === integrations?.browser, tools: await client.tools(signal) };
320
336
  }));
321
337
  for (const mount of mounts) {
322
338
  if (mount.status === "rejected")
323
339
  throw mount.reason;
324
- const { name: server, client, tools } = mount.value;
340
+ const { name: server, client, builtInBrowser, tools } = mount.value;
325
341
  const originalNames = new Set();
326
342
  for (const tool of tools) {
327
343
  if (!object(tool) || typeof tool.name !== "string" || !tool.name.trim() || originalNames.has(tool.name))
@@ -352,7 +368,7 @@ export async function mountChatTools(integrations, signal, computerUse = false)
352
368
  const suffix = `_${index}`;
353
369
  name = base.slice(0, 64 - suffix.length) + suffix;
354
370
  }
355
- registered.set(name, { client, name: tool.name, schema });
371
+ registered.set(name, { client, builtInBrowser, name: tool.name, schema });
356
372
  definitions.push({ type: "function", function: { name, description, parameters } });
357
373
  if (Buffer.byteLength(JSON.stringify(definitions)) > CATALOG_BYTES)
358
374
  throw new Error("MCP tool catalog exceeds the 1MB limit");
@@ -371,6 +387,7 @@ export async function mountChatTools(integrations, signal, computerUse = false)
371
387
  const tool = registered.get(name);
372
388
  if (!tool)
373
389
  throw new Error("The requested tool was not advertised for this turn");
390
+ omitBlankDefaults(tool.builtInBrowser, tool.name, args);
374
391
  if (!object(args) || !tool.schema(args))
375
392
  throw new Error("Tool arguments do not match the advertised input schema; use its required fields and types");
376
393
  };