windows-development-sandbox 1.11.7 → 1.12.0

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 (121) hide show
  1. package/README.md +12 -6
  2. package/config.example.json +4 -3
  3. package/dist/app/features/apps/apps-rows.d.ts +4 -1
  4. package/dist/app/features/apps/apps-rows.js +7 -2
  5. package/dist/app/features/apps/apps.def.d.ts +19 -0
  6. package/dist/app/features/apps/apps.def.js +49 -12
  7. package/dist/app/features/apps/apps.run.d.ts +2 -0
  8. package/dist/app/features/apps/apps.run.js +1 -1
  9. package/dist/app/features/audit/audit.def.d.ts +341 -0
  10. package/dist/app/features/audit/audit.def.js +454 -0
  11. package/dist/app/features/audit/audit.run.d.ts +107 -0
  12. package/dist/app/features/audit/audit.run.js +133 -0
  13. package/dist/app/features/audit/index.d.ts +2 -0
  14. package/dist/app/features/audit/index.js +3 -0
  15. package/dist/app/features/defs.d.ts +194 -1
  16. package/dist/app/features/defs.js +4 -0
  17. package/dist/app/features/settings/settings.run.d.ts +3 -0
  18. package/dist/app/features/settings/settings.run.js +16 -2
  19. package/dist/app/kernel/define.d.ts +12 -1
  20. package/dist/app/registry.js +2 -0
  21. package/dist/cli/args.d.ts +0 -3
  22. package/dist/cli/args.js +3 -4
  23. package/dist/cli/commands/index.d.ts +1 -1
  24. package/dist/cli/commands/index.js +1 -1
  25. package/dist/cli/commands/launch/prepare.js +1 -1
  26. package/dist/cli/commands/maintenance/index.d.ts +0 -1
  27. package/dist/cli/commands/maintenance/index.js +0 -1
  28. package/dist/cli/commands/registry.js +1 -2
  29. package/dist/cli/manifest.d.ts +0 -41
  30. package/dist/cli/manifest.js +0 -14
  31. package/dist/cli/menu/pages/apps-page.js +2 -15
  32. package/dist/cli/menu/pages/maintenance-page.d.ts +4 -0
  33. package/dist/cli/menu/pages/maintenance-page.js +51 -8
  34. package/dist/cli/menu/views.js +2 -0
  35. package/dist/core/config/defaults.js +1 -1
  36. package/dist/core/schema/apps.d.ts +9 -0
  37. package/dist/core/schema/apps.js +9 -0
  38. package/dist/core/schema/config.d.ts +1 -0
  39. package/dist/core/schema/config.js +3 -3
  40. package/dist/core/schema/sandbox.d.ts +1 -0
  41. package/dist/core/schema/sandbox.js +11 -3
  42. package/dist/guest/apps/app-installer.js +50 -4
  43. package/dist/guest/apps/index.d.ts +1 -1
  44. package/dist/guest/apps/index.js +1 -1
  45. package/dist/guest/apps/sources.js +1 -1
  46. package/dist/guest/audit/parse.d.ts +32 -2
  47. package/dist/guest/audit/parse.js +43 -5
  48. package/dist/guest/audit/process-audit.d.ts +3 -1
  49. package/dist/guest/audit/process-audit.js +9 -6
  50. package/dist/guest/boot/boot.js +12 -3
  51. package/dist/guest/boot/guide.d.ts +1 -1
  52. package/dist/guest/boot/guide.js +27 -3
  53. package/dist/guest/daemon/sessions.d.ts +7 -0
  54. package/dist/guest/daemon/sessions.js +27 -2
  55. package/dist/guest/entries/daemon.js +2 -1
  56. package/dist/guest/setup/addon-mcp.d.ts +2 -0
  57. package/dist/guest/setup/addon-mcp.js +49 -0
  58. package/dist/guest/setup/agent-addons.d.ts +9 -0
  59. package/dist/guest/setup/agent-addons.js +22 -0
  60. package/dist/guest/setup/headroom.d.ts +7 -0
  61. package/dist/guest/setup/headroom.js +60 -0
  62. package/dist/guest/setup/managed-policy.d.ts +2 -1
  63. package/dist/guest/setup/managed-policy.js +15 -6
  64. package/dist/guest/toolchains/plan.js +8 -3
  65. package/dist/host/audit/attribute.d.ts +46 -0
  66. package/dist/host/audit/attribute.js +76 -0
  67. package/dist/host/audit/collect.d.ts +29 -3
  68. package/dist/host/audit/collect.js +256 -61
  69. package/dist/host/audit/git-commits.d.ts +29 -0
  70. package/dist/host/audit/git-commits.js +157 -0
  71. package/dist/host/audit/index.d.ts +5 -0
  72. package/dist/host/audit/index.js +6 -0
  73. package/dist/host/audit/log.d.ts +44 -7
  74. package/dist/host/audit/log.js +148 -45
  75. package/dist/host/audit/redact.d.ts +2 -0
  76. package/dist/host/audit/redact.js +67 -0
  77. package/dist/runtime/agent.js +110 -15
  78. package/dist/runtime/daemon.js +569 -316
  79. package/dist/runtime/db.js +110 -15
  80. package/dist/runtime/main.js +1011 -672
  81. package/dist/runtime/netproxy.js +110 -15
  82. package/dist/runtime/view.js +110 -15
  83. package/dist/selftest/app.test.js +25 -1
  84. package/dist/selftest/audit.test.js +354 -47
  85. package/dist/selftest/hcs.test.js +35 -0
  86. package/dist/selftest/menu-screens.js +1 -1
  87. package/dist/selftest/menu.test.js +3 -3
  88. package/dist/selftest/support/engine-golden.js +9 -0
  89. package/dist/selftest/support/fixtures.d.ts +1 -0
  90. package/dist/selftest/support/web-preview.js +94 -0
  91. package/dist/selftest/toolchains.test.js +147 -2
  92. package/dist/shared/apps/agent-addons.d.ts +64 -0
  93. package/dist/shared/apps/agent-addons.js +95 -0
  94. package/dist/shared/apps/catalogue.d.ts +33 -2
  95. package/dist/shared/apps/catalogue.js +102 -11
  96. package/dist/shared/apps/index.d.ts +3 -2
  97. package/dist/shared/apps/index.js +3 -2
  98. package/dist/shared/apps/selection.d.ts +2 -0
  99. package/dist/shared/apps/selection.js +7 -0
  100. package/dist/sync/watcher.js +9 -1
  101. package/dist/web/index.html +2 -2
  102. package/dist/web/{main-RQFN4WBY.css → main-UAQPI5XD.css} +1 -1
  103. package/dist/web/{main-K2JMCAZI.js → main-UD4QCXZR.js} +66 -66
  104. package/native/csharp/HcsHost/AssemblyInfo.cs +5 -3
  105. package/native/csharp/HcsHost/Hcs.cs +9 -0
  106. package/native/csharp/HcsHost/Program.cs +26 -1
  107. package/native/csharp/HcsHost/Share.cs +173 -0
  108. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  109. package/package.json +1 -1
  110. package/sandbox/addons/ccusage/package-lock.json +111 -0
  111. package/sandbox/addons/ccusage/package.json +7 -0
  112. package/sandbox/addons/context7/package-lock.json +1313 -0
  113. package/sandbox/addons/context7/package.json +7 -0
  114. package/sandbox/addons/headroom/requirements.in +1 -0
  115. package/sandbox/addons/headroom/requirements.txt +2922 -0
  116. package/sandbox/addons/playwright-mcp/package-lock.json +56 -0
  117. package/sandbox/addons/playwright-mcp/package.json +7 -0
  118. package/schemas/config.schema.json +13 -4
  119. package/schemas/sandbox-config.schema.json +2 -2
  120. package/dist/cli/commands/maintenance/audit.d.ts +0 -3
  121. package/dist/cli/commands/maintenance/audit.js +0 -58
@@ -25560,14 +25560,19 @@ var NetworkSchema = external_exports.strictObject({
25560
25560
  });
25561
25561
  var AuditSchema = external_exports.strictObject({
25562
25562
  enabled: external_exports.boolean().describe(
25563
- "Keep an audit log on this PC of what happened in your sandboxes: programs started (with their command lines), files changed in the project, sessions, and what the repo guard caught. The sandbox cannot change it. Read it with sandbox audit."
25563
+ "Keep an audit log on this PC of what your agents did: programs started (with their command lines, secrets hidden), files changed in the projects, git commits, sessions, and what the repo guard caught, each linked to the agent session that did it where that can be known. The sandbox cannot change it. Read it with sandbox audit or on the Activity page."
25564
+ ),
25565
+ days: external_exports.number().int().min(1).max(365).describe(
25566
+ "Days the audit log is kept (1 to 30; a larger number keeps 30); older days are deleted when a sandbox starts and once a day."
25564
25567
  ),
25565
- days: external_exports.number().int().min(1).max(365).describe("Days the audit log is kept."),
25566
25568
  maxMb: external_exports.number().int().min(5).max(5e3).describe("Most space the audit log may take, in MB (older days go first). Days before today are compressed."),
25567
25569
  processes: external_exports.boolean().describe(
25568
25570
  "Record every program started in the sandbox with its command line (Windows process auditing; repeated runs are counted, not listed)."
25569
25571
  ),
25570
- files: external_exports.boolean().describe("Record the files changed in the project while the sandbox runs.")
25572
+ files: external_exports.boolean().describe("Record the files changed in the projects while the sandbox runs."),
25573
+ commits: external_exports.boolean().describe(
25574
+ "Record the git commits made in the projects while the sandbox runs (read from each project's .git folder on this PC)."
25575
+ )
25571
25576
  });
25572
25577
  var WorkspaceFilterSchema = external_exports.strictObject({
25573
25578
  enabled: external_exports.boolean().describe(
@@ -25770,7 +25775,100 @@ var ToolchainsSchema = external_exports.strictObject({
25770
25775
 
25771
25776
  // src/shared/apps/catalogue.ts
25772
25777
  var gh = (github, asset) => ({ github, asset });
25778
+ var RTK_VERSION = "1.0.1";
25779
+ var RTK_URL = "https://github.com/rtk-ai/rtk/releases/download/v" + RTK_VERSION + "/rtk-oss-x86_64-pc-windows-msvc.zip";
25780
+ var RTK_SHA256 = "0e53e2eca471e15fbd3636e64e6bc0b182cbc278ed0339c80450a2dd03f4cd6b";
25781
+ var HEADROOM_VERSION = "0.41.0";
25782
+ var CCUSAGE_VERSION = "20.0.36";
25783
+ var PLAYWRIGHT_MCP_VERSION = "0.0.83";
25784
+ var CONTEXT7_MCP_VERSION = "4.4.2";
25773
25785
  var APPS = [
25786
+ // ---- agent add-ons
25787
+ {
25788
+ id: "rtk",
25789
+ name: "rtk (Rust Token Killer)",
25790
+ category: "agents",
25791
+ about: "Compacts the output of shell commands (git, tests, builds, ls ...) before the agent reads it, so it uses fewer tokens. Claude Code's Bash commands go through it on their own.",
25792
+ size: "6 MB",
25793
+ source: { url: RTK_URL, sha256: RTK_SHA256 },
25794
+ exe: "rtk.exe",
25795
+ cli: true,
25796
+ plugsInto: "Claude Code through a hook in the sandbox's managed settings; other agents are told to use it",
25797
+ userOnly: true
25798
+ },
25799
+ {
25800
+ id: "headroom",
25801
+ name: "Headroom",
25802
+ category: "agents",
25803
+ about: "Compresses what Claude Code sends to the model (tool output, logs, files) so it uses fewer tokens: a local proxy on 127.0.0.1:8787 that sits in Claude Code's API path, offline apart from forwarding to api.anthropic.com.",
25804
+ size: "150 MB",
25805
+ source: { locked: "uv", package: "headroom-ai", version: HEADROOM_VERSION },
25806
+ exe: "headroom.exe",
25807
+ service: true,
25808
+ plugsInto: "Claude Code: the sandbox starts the proxy and points Claude Code at it (managed settings)",
25809
+ userOnly: true
25810
+ },
25811
+ {
25812
+ id: "ccusage",
25813
+ name: "ccusage",
25814
+ category: "agents",
25815
+ about: "Reports the agents' token use and cost by day, month or session from their logs in the sandbox (`ccusage daily`; add --offline to skip fetching prices).",
25816
+ size: "5 MB",
25817
+ source: { locked: "npm", package: "ccusage", version: CCUSAGE_VERSION },
25818
+ exe: "ccusage.cmd",
25819
+ cli: true,
25820
+ plugsInto: "Reads Claude Code's session logs in the sandbox; changes nothing",
25821
+ userOnly: true
25822
+ },
25823
+ {
25824
+ id: "playwright-mcp",
25825
+ name: "Playwright MCP",
25826
+ category: "agents",
25827
+ about: "Lets Claude Code drive a browser (Microsoft Edge, already in the sandbox) to open pages, click, fill forms and take screenshots: the MCP server of microsoft/playwright-mcp.",
25828
+ size: "20 MB",
25829
+ source: { locked: "npm", package: "@playwright/mcp", version: PLAYWRIGHT_MCP_VERSION },
25830
+ exe: "playwright-mcp.cmd",
25831
+ service: true,
25832
+ mcp: { name: "sbx-playwright", script: "node_modules/@playwright/mcp/cli.js", args: ["--browser", "msedge"] },
25833
+ plugsInto: "Claude Code as the MCP server sbx-playwright (the sandbox's Claude settings)",
25834
+ userOnly: true
25835
+ },
25836
+ {
25837
+ id: "context7",
25838
+ name: "Context7 MCP",
25839
+ category: "agents",
25840
+ about: "Up-to-date documentation and code examples for libraries, looked up by Claude Code while it works (upstash/context7). The library names and questions go to Upstash's context7.com.",
25841
+ size: "55 MB",
25842
+ source: { locked: "npm", package: "@upstash/context7-mcp", version: CONTEXT7_MCP_VERSION },
25843
+ exe: "context7-mcp.cmd",
25844
+ service: true,
25845
+ mcp: { name: "sbx-context7", script: "node_modules/@upstash/context7-mcp/dist/index.js" },
25846
+ plugsInto: "Claude Code as the MCP server sbx-context7 (the sandbox's Claude settings)",
25847
+ userOnly: true
25848
+ },
25849
+ {
25850
+ id: "claude-mem",
25851
+ name: "claude-mem",
25852
+ category: "agents",
25853
+ about: "Persistent memory for Claude Code (a plugin you install on this PC): its database is brought into the sandbox.",
25854
+ size: "",
25855
+ source: { resolve: "setting" },
25856
+ exe: "",
25857
+ setting: "claudeProfile.syncClaudeMem",
25858
+ plugsInto: "Claude Code, with your Claude settings copied from this PC",
25859
+ userOnly: true
25860
+ },
25861
+ {
25862
+ id: "rea",
25863
+ name: "REA (Reverse Engineer Anything)",
25864
+ category: "agents",
25865
+ about: "Agent-driven reverse engineering (MCP server and CLI, by morluto). Run `rea setup` once to add it to Claude Code and Codex. Native binaries need Ghidra; needs Node 22.19+.",
25866
+ size: "8 MB",
25867
+ source: { npm: "rea-agents" },
25868
+ exe: "rea.cmd",
25869
+ cli: true,
25870
+ plugsInto: "Claude Code and Codex as an MCP server, after `rea setup`"
25871
+ },
25774
25872
  // ---- editors
25775
25873
  {
25776
25874
  id: "vscode",
@@ -26232,16 +26330,6 @@ var APPS = [
26232
26330
  cli: true
26233
26331
  },
26234
26332
  // ---- reverse engineering
26235
- {
26236
- id: "rea",
26237
- name: "REA (Reverse Engineer Anything)",
26238
- category: "reversing",
26239
- about: "Agent-driven reverse engineering (MCP server and CLI, by morluto). Run `rea setup` once to add it to Claude Code and Codex. Native binaries need Ghidra; needs Node 22.19+.",
26240
- size: "8 MB",
26241
- source: { npm: "rea-agents" },
26242
- exe: "rea.cmd",
26243
- cli: true
26244
- },
26245
26333
  {
26246
26334
  id: "ghidra",
26247
26335
  name: "Ghidra",
@@ -26554,13 +26642,39 @@ function appsToInstall(apps, gitAccess) {
26554
26642
  const needs7zip = allowed.some((e) => e.needs?.includes("7zip") || e.unpack === "nsis") && !allowed.some((e) => e.id === "7zip");
26555
26643
  return { entries: [...needs7zip ? [appById("7zip")] : [], ...allowed], skipped };
26556
26644
  }
26645
+ function appsNeedUv(apps) {
26646
+ return Boolean(
26647
+ apps?.install.some((id) => {
26648
+ const source = appById(id)?.source;
26649
+ return source !== void 0 && "locked" in source && source.locked === "uv";
26650
+ })
26651
+ );
26652
+ }
26557
26653
  function appsNeedMesa(apps) {
26558
26654
  return Boolean(apps?.install.some((id) => appById(id)?.needs?.includes("mesa")));
26559
26655
  }
26560
26656
 
26657
+ // src/shared/apps/agent-addons.ts
26658
+ var RTK_GUIDE_LINE = "- rtk is on PATH and compacts command output: Claude Code's Bash commands go through it on their own; in other agents prefix shell commands with `rtk` (`rtk git status`, `rtk cargo test`, `rtk ls`).";
26659
+ var HEADROOM_PORT = 8787;
26660
+ function headroomUrl(port = HEADROOM_PORT) {
26661
+ return "http://127.0.0.1:" + port;
26662
+ }
26663
+ function headroomGuideLine(logFile2) {
26664
+ return "- Headroom compresses Claude Code's requests: Claude Code reaches the API through its proxy on " + headroomUrl() + " (log: `" + logFile2 + "`). If Claude Code cannot reach the API, the proxy stopped: it starts again with the next sandbox, or turn Headroom off in Apps.";
26665
+ }
26666
+ function addonGuideLine(mcpNames, ccusage) {
26667
+ return [
26668
+ ...mcpNames.length ? ["- MCP servers in Claude Code from Apps: " + mcpNames.join(", ") + "."] : [],
26669
+ ...ccusage ? ["- ccusage is on PATH: `ccusage daily` (or `monthly`, `session`) reports token use and cost."] : []
26670
+ ];
26671
+ }
26672
+
26561
26673
  // src/core/schema/apps.ts
26562
26674
  var CATALOGUE_IDS = APPS.filter((a) => !a.setting).map((a) => a.id);
26563
26675
  var AppIdSchema = external_exports.enum(CATALOGUE_IDS);
26676
+ var PROJECT_IDS = APPS.filter((a) => !a.setting && !a.userOnly).map((a) => a.id);
26677
+ var ProjectAppIdSchema = external_exports.enum(PROJECT_IDS);
26564
26678
  var CustomAppIdSchema = external_exports.string().regex(/^[a-z0-9][a-z0-9-]{0,40}$/, "lower-case letters, digits and - only");
26565
26679
  var compiles = (pattern) => {
26566
26680
  try {
@@ -26584,6 +26698,11 @@ var CustomAppSchema = external_exports.strictObject({
26584
26698
  }).refine((a) => a.github && a.asset ? !a.url : Boolean(a.url) && !a.github && !a.asset, {
26585
26699
  message: "give github + asset, or url (not both)"
26586
26700
  });
26701
+ var ProjectAppsSchema = external_exports.strictObject({
26702
+ install: external_exports.array(ProjectAppIdSchema).describe(
26703
+ "Apps from the catalogue this project adds to yours (agent add-ons such as rtk, Headroom and the MCP servers are set in your own settings only)."
26704
+ )
26705
+ });
26587
26706
  var AppsSchema = external_exports.strictObject({
26588
26707
  install: external_exports.array(AppIdSchema).describe(
26589
26708
  "Apps from the catalogue to install in every sandbox (the Apps screen in the menu lists them by category). Each is downloaded once and kept."
@@ -26748,8 +26867,8 @@ var LocalOverridesSchema = external_exports.strictObject({
26748
26867
  attach: AttachSchema.partial(),
26749
26868
  personalization: toggle("Copy your desktop look into the sandbox.").partial(),
26750
26869
  // Catalogue apps only (a vetted list): your own programs (apps.custom) download anything, so a repo cannot add
26751
- // them.
26752
- apps: AppsSchema.pick({ install: true }).partial(),
26870
+ // them, nor agent add-ons that are yours only (rtk hooks every agent command, Headroom sits in its API path).
26871
+ apps: ProjectAppsSchema.partial(),
26753
26872
  claudeProfile: ClaudeProfileSchema.pick({
26754
26873
  enabled: true,
26755
26874
  overwrite: true,
@@ -27599,9 +27718,29 @@ var NOISE = /* @__PURE__ */ new Set([
27599
27718
  "usoclient.exe",
27600
27719
  "musnotification.exe"
27601
27720
  ]);
27721
+ var ROOT_SLACK_MS = 5e3;
27722
+ var MAX_KNOWN = 5e4;
27723
+ var Attributor = class {
27724
+ owner = /* @__PURE__ */ new Map();
27725
+ /** The session of a new process (pid, started by ppid at time t), or undefined. */
27726
+ attribute(e, roots) {
27727
+ const root = roots.find(
27728
+ (r) => r.pid === e.ppid && e.t >= r.from - ROOT_SLACK_MS && (r.to === void 0 || e.t <= r.to + ROOT_SLACK_MS)
27729
+ );
27730
+ const session2 = root?.session ?? this.owner.get(e.ppid) ?? void 0;
27731
+ this.owner.delete(e.pid);
27732
+ this.owner.set(e.pid, session2 ?? null);
27733
+ if (this.owner.size > MAX_KNOWN) this.owner.delete(this.owner.keys().next().value);
27734
+ return session2;
27735
+ }
27736
+ };
27602
27737
  var decode3 = (s) => s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
27603
27738
  var field = (event, name) => decode3(new RegExp("<Data Name='" + name + "'>([^<]*)</Data>").exec(event)?.[1] ?? "");
27604
- function parseProcessEvents(xml) {
27739
+ var pidOf = (text) => {
27740
+ const n = /^0x[0-9a-f]{1,8}$/i.test(text) ? parseInt(text, 16) : /^\d{1,10}$/.test(text) ? Number(text) : 0;
27741
+ return Number.isSafeInteger(n) ? n : 0;
27742
+ };
27743
+ function parseProcessEvents(xml, attribution) {
27605
27744
  const lines = [];
27606
27745
  let last = 0;
27607
27746
  for (const event of xml.split("</Event>")) {
@@ -27609,15 +27748,19 @@ function parseProcessEvents(xml) {
27609
27748
  if (!id) continue;
27610
27749
  last = Math.max(last, id);
27611
27750
  const program = field(event, "NewProcessName");
27612
- if (!program || NOISE.has(path15.win32.basename(program).toLowerCase())) continue;
27613
27751
  const t = /SystemTime='([^']+)'/.exec(event)?.[1] ?? (/* @__PURE__ */ new Date()).toISOString();
27752
+ const pid = pidOf(field(event, "NewProcessId"));
27753
+ const ppid = pidOf(field(event, "ProcessId"));
27754
+ const session2 = attribution && pid && ppid ? attribution.attributor.attribute({ pid, ppid, t: Date.parse(t) || Date.now() }, attribution.roots) : void 0;
27755
+ if (!program || NOISE.has(path15.win32.basename(program).toLowerCase())) continue;
27614
27756
  lines.push(
27615
27757
  JSON.stringify({
27616
27758
  t,
27617
27759
  k: "proc",
27618
27760
  p: program.slice(0, 1024),
27619
27761
  c: field(event, "CommandLine").slice(0, 2e3),
27620
- pp: field(event, "ParentProcessName").slice(0, 1024)
27762
+ pp: field(event, "ParentProcessName").slice(0, 1024),
27763
+ ...session2 ? { s: session2 } : {}
27621
27764
  })
27622
27765
  );
27623
27766
  }
@@ -27645,9 +27788,10 @@ var newestRecord = () => {
27645
27788
  });
27646
27789
  return parseProcessEvents(r.stdout ?? "").last;
27647
27790
  };
27648
- function startProcessCollector() {
27791
+ function startProcessCollector(roots = () => []) {
27649
27792
  if (!auditOn()) return;
27650
27793
  let last = newestRecord();
27794
+ const attributor = new Attributor();
27651
27795
  const pass = () => {
27652
27796
  try {
27653
27797
  const r = spawnSync(
@@ -27661,7 +27805,7 @@ function startProcessCollector() {
27661
27805
  ],
27662
27806
  { encoding: "utf8", windowsHide: true, maxBuffer: 64 * 1024 * 1024 }
27663
27807
  );
27664
- const { lines, last: newest } = parseProcessEvents(r.stdout ?? "");
27808
+ const { lines, last: newest } = parseProcessEvents(r.stdout ?? "", { attributor, roots: roots() });
27665
27809
  if (newest) last = newest;
27666
27810
  if (lines.length)
27667
27811
  fs12.writeFileSync(
@@ -28281,18 +28425,76 @@ function readBootEnv() {
28281
28425
  }
28282
28426
 
28283
28427
  // src/guest/boot/guide.ts
28428
+ import fs19 from "node:fs";
28429
+ import path27 from "node:path";
28430
+
28431
+ // src/guest/apps/find-exe.ts
28284
28432
  import fs18 from "node:fs";
28285
28433
  import path25 from "node:path";
28434
+ var MAX_DEPTH = 7;
28435
+ var MAX_ENTRIES = 5e4;
28436
+ var X64_FOLDER = /(^|[\\/])(amd64|x64|x86_64|win64)([\\/]|$)/i;
28437
+ function findExe(root, exe) {
28438
+ const match = exeMatcher(exe);
28439
+ const best = (hits) => hits.find((h) => X64_FOLDER.test(path25.relative(root, h))) ?? hits[0] ?? null;
28440
+ let queue = [root];
28441
+ let seen = 0;
28442
+ for (let depth = 0; depth < MAX_DEPTH && queue.length; depth++) {
28443
+ const next = [];
28444
+ const hits = [];
28445
+ for (const dir of queue) {
28446
+ let entries2;
28447
+ try {
28448
+ entries2 = fs18.readdirSync(dir, { withFileTypes: true });
28449
+ } catch {
28450
+ continue;
28451
+ }
28452
+ const hit = entries2.find((e) => e.isFile() && match(e.name));
28453
+ if (hit) hits.push(path25.join(dir, hit.name));
28454
+ else
28455
+ next.push(...entries2.filter((e) => e.isDirectory() && !e.isSymbolicLink()).map((e) => path25.join(dir, e.name)));
28456
+ if ((seen += entries2.length) > MAX_ENTRIES) return best(hits);
28457
+ }
28458
+ if (hits.length) return best(hits);
28459
+ queue = next;
28460
+ }
28461
+ return null;
28462
+ }
28463
+
28464
+ // src/guest/toolchains/tool-env.ts
28465
+ import path26 from "node:path";
28466
+ function emptyEnv() {
28467
+ return { vars: {}, prepend: [], append: [], summary: [] };
28468
+ }
28469
+ var arch = process.arch === "arm64" ? "arm64" : "x64";
28470
+ function tool(...parts2) {
28471
+ return path26.join(toolsDir, ...parts2);
28472
+ }
28473
+ function versionOf(exe, args = ["--version"], options = {}) {
28474
+ return /\d+\.\d+\.\d+\S*/.exec(execOutput(exe, args, options))?.[0] ?? "installed";
28475
+ }
28476
+
28477
+ // src/guest/setup/agent-addons.ts
28478
+ function installedAddon(id) {
28479
+ const entry = appById(id);
28480
+ if (dryRun || !entry || !(session.apps?.install ?? []).includes(id)) return null;
28481
+ return findExe(tool("apps", id), entry.exe);
28482
+ }
28483
+ function installedRtk() {
28484
+ return installedAddon("rtk");
28485
+ }
28486
+
28487
+ // src/guest/boot/guide.ts
28286
28488
  var BEGIN = "<!-- agent-sandbox:begin (generated every boot, do not edit) -->";
28287
28489
  var END = "<!-- agent-sandbox:end -->";
28288
28490
  var mirrorLine = () => {
28289
- const drives = (session.substDrives ?? []).filter((d) => fs18.existsSync(d.letter + ":\\"));
28491
+ const drives = (session.substDrives ?? []).filter((d) => fs19.existsSync(d.letter + ":\\"));
28290
28492
  if (!drives.length) return [];
28291
28493
  return [
28292
28494
  "- " + drives.map((d) => d.letter + ":").join(", ") + " = mapped folders at their PC paths (subst onto `" + drives.map((d) => d.target).join("`, `") + "`; real-path tools may print that form)."
28293
28495
  ];
28294
28496
  };
28295
- function sandboxGuide(toolSummary, lan = "blocked", projects = []) {
28497
+ function sandboxGuide(toolSummary, lan = "blocked", projects = [], addons = []) {
28296
28498
  return [
28297
28499
  BEGIN,
28298
28500
  "## Sandbox manifest \u2014 you are in a DISPOSABLE Windows Sandbox",
@@ -28302,11 +28504,13 @@ function sandboxGuide(toolSummary, lan = "blocked", projects = []) {
28302
28504
  ...projects.map(
28303
28505
  (p) => "- RW, persists: `" + p.guest + "` = host folder " + p.host + " (another project in this sandbox; your own is `%AGENT_WORKSPACE%`)"
28304
28506
  ),
28305
- "- RW, persists: `" + toolsDir + "` (toolchains) \u2014 put portable exes in `" + path25.join(toolsDir, "bin") + "` (on PATH)",
28306
- "- RW, persists: `" + stateDir + "` (agent config/history; do not edit logins) \xB7 logs: `" + path25.join(dataDir, "logs") + "`",
28507
+ "- RW, persists: `" + toolsDir + "` (toolchains) \u2014 put portable exes in `" + path27.join(toolsDir, "bin") + "` (on PATH)",
28508
+ "- RW, persists: `" + stateDir + "` (agent config/history; do not edit logins) \xB7 logs: `" + path27.join(dataDir, "logs") + "`",
28307
28509
  "- RO: `" + launchDir + "` (this boot's scripts) \xB7 lost on close: everything else, incl. `C:\\nvm` (Node symlink)",
28308
28510
  ...mirrorLine(),
28309
28511
  "- Tools: " + toolSummary.join(" \xB7 "),
28512
+ // Agent add-ons that change how the agent works (rtk, Headroom, MCP servers, ccusage).
28513
+ ...addons,
28310
28514
  "- Persistent installs: `npm i -g` (per nvm version), `pip`, `cargo install`, `dotnet tool -g`, `uv tool`, `bun add -g`. Real installers must re-run each boot via a hook (ask the user).",
28311
28515
  "- Mapped folders (workspace, C:\\AgentData) allow NO symlinks or junctions: pnpm runs hoisted (set), npm workspaces / `file:` deps and deno node_modules dirs fail there (use a folder on C:\\ outside them), bun cannot save its lockfile there (`bun install --no-save`).",
28312
28516
  "- Network: " + (lan === "offline" ? "OFF" : lan === "blocked" ? "internet yes, host LAN blocked" : "internet yes, host LAN NOT blocked this time") + ". Other sessions may share this workspace; the user may be typing from the host.",
@@ -28314,37 +28518,54 @@ function sandboxGuide(toolSummary, lan = "blocked", projects = []) {
28314
28518
  ].join("\n");
28315
28519
  }
28316
28520
  function upsertGuide(file3, guide) {
28317
- fs18.mkdirSync(path25.dirname(file3), { recursive: true });
28318
- const current = fs18.existsSync(file3) ? fs18.readFileSync(file3, "utf8") : "";
28521
+ fs19.mkdirSync(path27.dirname(file3), { recursive: true });
28522
+ const current = fs19.existsSync(file3) ? fs19.readFileSync(file3, "utf8") : "";
28319
28523
  const start = current.indexOf(BEGIN);
28320
28524
  const end = current.indexOf(END);
28321
28525
  const without = start >= 0 && end > start ? current.slice(0, start) + current.slice(end + END.length) : current;
28322
- fs18.writeFileSync(file3, without.trimEnd() + (without.trim() ? "\n\n" : "") + guide + "\n", "utf8");
28526
+ fs19.writeFileSync(file3, without.trimEnd() + (without.trim() ? "\n\n" : "") + guide + "\n", "utf8");
28323
28527
  }
28324
28528
  function managedClaudeMd() {
28325
- return path25.join(
28326
- process.env.AGENT_MANAGED_DIR ?? path25.join(process.env.ProgramFiles ?? "C:\\Program Files", "ClaudeCode"),
28529
+ return path27.join(
28530
+ process.env.AGENT_MANAGED_DIR ?? path27.join(process.env.ProgramFiles ?? "C:\\Program Files", "ClaudeCode"),
28327
28531
  "CLAUDE.md"
28328
28532
  );
28329
28533
  }
28534
+ var headroomRuns = () => {
28535
+ try {
28536
+ const file3 = path27.join(path27.dirname(managedClaudeMd()), "managed-settings.json");
28537
+ const settings = JSON.parse(fs19.readFileSync(file3, "utf8"));
28538
+ return typeof settings.env?.ANTHROPIC_BASE_URL === "string";
28539
+ } catch {
28540
+ return false;
28541
+ }
28542
+ };
28543
+ function addonLines() {
28544
+ const mcp = APPS.flatMap((a) => a.mcp && installedAddon(a.id) ? [a.mcp.name] : []);
28545
+ return [
28546
+ ...installedRtk() ? [RTK_GUIDE_LINE] : [],
28547
+ ...installedAddon("headroom") && headroomRuns() ? [headroomGuideLine(path27.join(logsDir, "headroom.log"))] : [],
28548
+ ...addonGuideLine(mcp, Boolean(installedAddon("ccusage")))
28549
+ ];
28550
+ }
28330
28551
  function writeGuides(toolSummary, lan, projects = []) {
28331
- const guide = sandboxGuide(toolSummary, lan, projects);
28552
+ const guide = sandboxGuide(toolSummary, lan, projects, addonLines());
28332
28553
  const managed = managedClaudeMd();
28333
- fs18.mkdirSync(path25.dirname(managed), { recursive: true });
28334
- fs18.writeFileSync(managed, guide + "\n", "utf8");
28335
- fs18.writeFileSync(path25.join(dataDir, "SANDBOX.md"), guide + "\n", "utf8");
28336
- upsertGuide(path25.join(stateDir, "codex", "AGENTS.md"), guide);
28337
- upsertGuide(path25.join(stateDir, "gemini", ".gemini", "GEMINI.md"), guide);
28338
- removeGuide(path25.join(claudeDir, "CLAUDE.md"));
28554
+ fs19.mkdirSync(path27.dirname(managed), { recursive: true });
28555
+ fs19.writeFileSync(managed, guide + "\n", "utf8");
28556
+ fs19.writeFileSync(path27.join(dataDir, "SANDBOX.md"), guide + "\n", "utf8");
28557
+ upsertGuide(path27.join(stateDir, "codex", "AGENTS.md"), guide);
28558
+ upsertGuide(path27.join(stateDir, "gemini", ".gemini", "GEMINI.md"), guide);
28559
+ removeGuide(path27.join(claudeDir, "CLAUDE.md"));
28339
28560
  return managed;
28340
28561
  }
28341
28562
  var removeGuide = (file3) => {
28342
- if (!fs18.existsSync(file3)) return;
28343
- const current = fs18.readFileSync(file3, "utf8");
28563
+ if (!fs19.existsSync(file3)) return;
28564
+ const current = fs19.readFileSync(file3, "utf8");
28344
28565
  const start = current.indexOf(BEGIN);
28345
28566
  const end = current.indexOf(END);
28346
28567
  if (start >= 0 && end > start)
28347
- fs18.writeFileSync(file3, (current.slice(0, start) + current.slice(end + END.length)).trim() + "\n", "utf8");
28568
+ fs19.writeFileSync(file3, (current.slice(0, start) + current.slice(end + END.length)).trim() + "\n", "utf8");
28348
28569
  };
28349
28570
 
28350
28571
  // src/guest/runtime/project-context.ts
@@ -28381,11 +28602,11 @@ function saveProjectStatus(name, workspace2, phase, message, databases, summary)
28381
28602
  }
28382
28603
 
28383
28604
  // src/guest/setup/hooks.ts
28384
- import fs19 from "node:fs";
28385
- import path26 from "node:path";
28605
+ import fs20 from "node:fs";
28606
+ import path28 from "node:path";
28386
28607
  import { spawnSync as spawnSync3 } from "node:child_process";
28387
28608
  var commandFor = (file3) => {
28388
- const ext = path26.extname(file3).toLowerCase();
28609
+ const ext = path28.extname(file3).toLowerCase();
28389
28610
  if (ext === ".mjs" || ext === ".js") return [process.execPath, [file3]];
28390
28611
  if (!isWin) return null;
28391
28612
  if (ext === ".ps1")
@@ -28393,18 +28614,18 @@ var commandFor = (file3) => {
28393
28614
  if (ext === ".cmd" || ext === ".bat") return [SYSTEM.cmd(), ["/d", "/c", file3]];
28394
28615
  return null;
28395
28616
  };
28396
- var BOOT = { root: path26.join(launchDir, "hooks"), cwd: workspace };
28617
+ var BOOT = { root: path28.join(launchDir, "hooks"), cwd: workspace };
28397
28618
  function projectHooks(name, cwd) {
28398
28619
  return {
28399
- root: path26.join(launchDir, "projects", name, "hooks"),
28620
+ root: path28.join(launchDir, "projects", name, "hooks"),
28400
28621
  cwd
28401
28622
  };
28402
28623
  }
28403
28624
  var hooksOf = (stage, target) => {
28404
- const dir = path26.join(target.root, stage);
28625
+ const dir = path28.join(target.root, stage);
28405
28626
  if (!exists(dir)) return [];
28406
- return fs19.readdirSync(dir).sort().flatMap((name) => {
28407
- const command = commandFor(path26.join(dir, name));
28627
+ return fs20.readdirSync(dir).sort().flatMap((name) => {
28628
+ const command = commandFor(path28.join(dir, name));
28408
28629
  return command ? [{ name, command }] : [];
28409
28630
  });
28410
28631
  };
@@ -28433,26 +28654,109 @@ async function runHooksAsync(stage, env, target) {
28433
28654
  }
28434
28655
  }
28435
28656
 
28436
- // src/guest/toolchains/tool-env.ts
28437
- import path27 from "node:path";
28438
- function emptyEnv() {
28439
- return { vars: {}, prepend: [], append: [], summary: [] };
28440
- }
28441
- var arch = process.arch === "arm64" ? "arm64" : "x64";
28442
- function tool(...parts2) {
28443
- return path27.join(toolsDir, ...parts2);
28657
+ // src/guest/apps/app-installer.ts
28658
+ import fs24 from "node:fs";
28659
+ import path33 from "node:path";
28660
+
28661
+ // src/guest/toolchains/runtimes.ts
28662
+ import path29 from "node:path";
28663
+
28664
+ // src/guest/toolchains/install.ts
28665
+ import fs21 from "node:fs";
28666
+ async function downloadFile(url2, file3, sha256 = null) {
28667
+ const part = file3 + ".part";
28668
+ try {
28669
+ await downloader.file(url2, part, sha256);
28670
+ fs21.rmSync(file3, { force: true });
28671
+ fs21.renameSync(part, file3);
28672
+ } finally {
28673
+ fs21.rmSync(part, { force: true });
28674
+ }
28444
28675
  }
28445
- function versionOf(exe, args = ["--version"], options = {}) {
28446
- return /\d+\.\d+\.\d+\S*/.exec(execOutput(exe, args, options))?.[0] ?? "installed";
28676
+ async function installFromGithub(repo, pattern, o) {
28677
+ const asset = await githubAsset(repo, pattern, o.searchOlder);
28678
+ log("Downloading " + (typeof o.what === "string" ? o.what : o.what(asset)) + " ...");
28679
+ await downloader.archive(asset.url, o.dest, {
28680
+ sha256: asset.sha256,
28681
+ fileName: asset.name,
28682
+ ...o.locate ? { locate: o.locate } : {},
28683
+ ...o.up ? { up: o.up } : {}
28684
+ });
28447
28685
  }
28448
28686
 
28449
- // src/guest/apps/app-installer.ts
28450
- import fs23 from "node:fs";
28451
- import path32 from "node:path";
28687
+ // src/guest/toolchains/runtimes.ts
28688
+ async function setupSingleExe(env, name, repo, pattern, exeName, dir, options = {}) {
28689
+ if (dryRun) {
28690
+ log("(dry run) would provision " + name);
28691
+ return;
28692
+ }
28693
+ const bin = options.subdir ? path29.join(dir, options.subdir) : dir;
28694
+ const exe = path29.join(bin, exeName);
28695
+ if (!exists(exe) || options.refresh) {
28696
+ const size = options.size ? ", ~" + options.size : "";
28697
+ await installFromGithub(repo, pattern, {
28698
+ dest: dir,
28699
+ what: (a) => name + " (" + a.name + size + " once)",
28700
+ ...options.subdir ? {} : { locate: exeName }
28701
+ });
28702
+ }
28703
+ if (!exists(exe)) throw new Error(exeName + " missing after installing " + name);
28704
+ env.prepend.push(bin);
28705
+ env.summary.push(name + ": " + versionOf(exe, options.versionArgs) + (options.note ?? ""));
28706
+ }
28707
+ async function setupBun(env) {
28708
+ await setupSingleExe(
28709
+ env,
28710
+ "bun",
28711
+ "oven-sh/bun",
28712
+ arch === "arm64" ? /^bun-windows-aarch64\.zip$/ : /^bun-windows-x64\.zip$/,
28713
+ "bun.exe",
28714
+ tool("bun", "bin"),
28715
+ { size: "35 MB" }
28716
+ );
28717
+ Object.assign(env.vars, {
28718
+ BUN_INSTALL: tool("bun"),
28719
+ BUN_INSTALL_CACHE_DIR: path29.join(localDir, "bun-cache"),
28720
+ BUN_TMPDIR: path29.join(localDir, "bun-tmp")
28721
+ });
28722
+ }
28723
+ async function setupDeno(env) {
28724
+ await setupSingleExe(
28725
+ env,
28726
+ "deno",
28727
+ "denoland/deno",
28728
+ arch === "arm64" ? /^deno-aarch64-pc-windows-msvc[.]zip$/ : /^deno-x86_64-pc-windows-msvc[.]zip$/,
28729
+ "deno.exe",
28730
+ tool("deno", "bin"),
28731
+ { size: "40 MB" }
28732
+ );
28733
+ Object.assign(env.vars, { DENO_DIR: tool("deno-cache"), DENO_INSTALL_ROOT: tool("deno"), DENO_NO_UPDATE_CHECK: "1" });
28734
+ }
28735
+ function uvVars() {
28736
+ return {
28737
+ UV_CACHE_DIR: tool("uv-cache"),
28738
+ UV_PYTHON_INSTALL_DIR: tool("uv-python"),
28739
+ UV_TOOL_DIR: tool("uv-tools"),
28740
+ UV_TOOL_BIN_DIR: tool("uv-bin")
28741
+ };
28742
+ }
28743
+ async function setupUv(env) {
28744
+ await setupSingleExe(
28745
+ env,
28746
+ "uv",
28747
+ "astral-sh/uv",
28748
+ arch === "arm64" ? /^uv-aarch64-pc-windows-msvc\.zip$/ : /^uv-x86_64-pc-windows-msvc\.zip$/,
28749
+ "uv.exe",
28750
+ tool("uv"),
28751
+ { size: "20 MB" }
28752
+ );
28753
+ Object.assign(env.vars, uvVars());
28754
+ env.prepend.push(mkdirp(tool("uv-bin")));
28755
+ }
28452
28756
 
28453
28757
  // src/guest/apps/after-install.ts
28454
- import fs20 from "node:fs";
28455
- import path28 from "node:path";
28758
+ import fs22 from "node:fs";
28759
+ import path30 from "node:path";
28456
28760
  var SYSINTERNALS_TOOLS = [
28457
28761
  "Process Explorer",
28458
28762
  "Process Monitor",
@@ -28473,16 +28777,16 @@ var SYSINTERNALS_TOOLS = [
28473
28777
  "ZoomIt"
28474
28778
  ];
28475
28779
  async function afterInstall(entry, exe) {
28476
- const dir = path28.dirname(exe);
28780
+ const dir = path30.dirname(exe);
28477
28781
  if (entry.needs?.includes("mesa")) {
28478
28782
  const mesa = tool("mesa", arch === "arm64" ? "arm64" : "x64");
28479
28783
  for (const dll of ["opengl32.dll", "libgallium_wgl.dll", "dxil.dll"]) {
28480
- if (exists(path28.join(mesa, dll)) && !exists(path28.join(dir, dll)))
28481
- fs20.copyFileSync(path28.join(mesa, dll), path28.join(dir, dll));
28784
+ if (exists(path30.join(mesa, dll)) && !exists(path30.join(dir, dll)))
28785
+ fs22.copyFileSync(path30.join(mesa, dll), path30.join(dir, dll));
28482
28786
  }
28483
28787
  }
28484
- if (entry.id === "godot" && !exists(path28.join(dir, "._sc_"))) fs20.writeFileSync(path28.join(dir, "._sc_"), "");
28485
- if (entry.id === "sublime") mkdirp(path28.join(dir, "Data"));
28788
+ if (entry.id === "godot" && !exists(path30.join(dir, "._sc_"))) fs22.writeFileSync(path30.join(dir, "._sc_"), "");
28789
+ if (entry.id === "sublime") mkdirp(path30.join(dir, "Data"));
28486
28790
  if (entry.id === "sysinternals") {
28487
28791
  for (const name of SYSINTERNALS_TOOLS) {
28488
28792
  await execAsync(
@@ -28494,47 +28798,14 @@ async function afterInstall(entry, exe) {
28494
28798
  }
28495
28799
  }
28496
28800
 
28497
- // src/guest/apps/find-exe.ts
28498
- import fs21 from "node:fs";
28499
- import path29 from "node:path";
28500
- var MAX_DEPTH = 7;
28501
- var MAX_ENTRIES = 5e4;
28502
- var X64_FOLDER = /(^|[\\/])(amd64|x64|x86_64|win64)([\\/]|$)/i;
28503
- function findExe(root, exe) {
28504
- const match = exeMatcher(exe);
28505
- const best = (hits) => hits.find((h) => X64_FOLDER.test(path29.relative(root, h))) ?? hits[0] ?? null;
28506
- let queue = [root];
28507
- let seen = 0;
28508
- for (let depth = 0; depth < MAX_DEPTH && queue.length; depth++) {
28509
- const next = [];
28510
- const hits = [];
28511
- for (const dir of queue) {
28512
- let entries2;
28513
- try {
28514
- entries2 = fs21.readdirSync(dir, { withFileTypes: true });
28515
- } catch {
28516
- continue;
28517
- }
28518
- const hit = entries2.find((e) => e.isFile() && match(e.name));
28519
- if (hit) hits.push(path29.join(dir, hit.name));
28520
- else
28521
- next.push(...entries2.filter((e) => e.isDirectory() && !e.isSymbolicLink()).map((e) => path29.join(dir, e.name)));
28522
- if ((seen += entries2.length) > MAX_ENTRIES) return best(hits);
28523
- }
28524
- if (hits.length) return best(hits);
28525
- queue = next;
28526
- }
28527
- return null;
28528
- }
28529
-
28530
28801
  // src/guest/apps/shortcuts.ts
28531
28802
  import os5 from "node:os";
28532
- import path30 from "node:path";
28803
+ import path31 from "node:path";
28533
28804
  function shortcuts(apps) {
28534
28805
  if (!apps.length) return;
28535
- const desktop = path30.join(os5.homedir(), "Desktop");
28536
- const start = path30.join(
28537
- process.env.APPDATA ?? path30.join(os5.homedir(), "AppData", "Roaming"),
28806
+ const desktop = path31.join(os5.homedir(), "Desktop");
28807
+ const start = path31.join(
28808
+ process.env.APPDATA ?? path31.join(os5.homedir(), "AppData", "Roaming"),
28538
28809
  "Microsoft",
28539
28810
  "Windows",
28540
28811
  "Start Menu",
@@ -28544,9 +28815,9 @@ function shortcuts(apps) {
28544
28815
  const links = apps.flatMap((app) => {
28545
28816
  const name = app.name.replace(/[\\/:*?"<>|]/g, "");
28546
28817
  return [desktop, start].map((folder) => ({
28547
- link: path30.join(folder, name + ".lnk"),
28818
+ link: path31.join(folder, name + ".lnk"),
28548
28819
  target: app.exe,
28549
- workingDirectory: path30.dirname(app.exe)
28820
+ workingDirectory: path31.dirname(app.exe)
28550
28821
  }));
28551
28822
  });
28552
28823
  runScriptWithJson("new-shortcuts.ps1", "ListFile", links);
@@ -28671,19 +28942,19 @@ async function resolve(entry) {
28671
28942
  }
28672
28943
  if ("url" in s) {
28673
28944
  const name = decodeURIComponent(new URL(s.url).pathname.split("/").pop() || entry.id);
28674
- return { url: s.url, fileName: /\.\w{2,5}$/.test(name) ? name : name + ".zip" };
28945
+ return { url: s.url, fileName: /\.\w{2,5}$/.test(name) ? name : name + ".zip", sha256: s.sha256 };
28675
28946
  }
28676
28947
  if ("resolve" in s && RESOLVERS[s.resolve]) return RESOLVERS[s.resolve]();
28677
28948
  throw new Error("no download source for " + entry.name);
28678
28949
  }
28679
28950
 
28680
28951
  // src/guest/apps/unpack.ts
28681
- import fs22 from "node:fs";
28682
- import path31 from "node:path";
28952
+ import fs23 from "node:fs";
28953
+ import path32 from "node:path";
28683
28954
  var sevenZip = () => tool("apps", "7zip", "7z.exe");
28684
28955
  async function unpack(entry, file3, out, d) {
28685
28956
  if (entry.id === "7zip") {
28686
- const sevenZr = path31.join(path31.dirname(file3), "7zr.exe");
28957
+ const sevenZr = path32.join(path32.dirname(file3), "7zr.exe");
28687
28958
  const zr = await githubAsset("ip7z/7zip", /^7zr\.exe$/);
28688
28959
  await downloader.file(zr.url, sevenZr, zr.sha256);
28689
28960
  await execAsync(sevenZr, ["x", "-y", "-o" + out, file3], { stdio: "ignore" });
@@ -28692,17 +28963,17 @@ async function unpack(entry, file3, out, d) {
28692
28963
  switch (entry.unpack ?? "archive") {
28693
28964
  case "file": {
28694
28965
  mkdirp(out);
28695
- fs22.renameSync(file3, path31.join(out, /^\//.test(entry.exe) ? path31.basename(file3) : entry.exe));
28966
+ fs23.renameSync(file3, path32.join(out, /^\//.test(entry.exe) ? path32.basename(file3) : entry.exe));
28696
28967
  return;
28697
28968
  }
28698
28969
  case "nsis": {
28699
28970
  if (!exists(sevenZip()))
28700
28971
  throw new Error("7-Zip is needed to unpack " + entry.name + " and could not be installed");
28701
- const raw = path31.join(path31.dirname(out), "raw");
28972
+ const raw = path32.join(path32.dirname(out), "raw");
28702
28973
  await execAsync(sevenZip(), ["x", "-y", "-o" + raw, file3], { stdio: "ignore" });
28703
28974
  const inner = findExe(raw, "/^app-64\\.7z$/");
28704
28975
  if (inner) await execAsync(sevenZip(), ["x", "-y", "-o" + out, inner], { stdio: "ignore" });
28705
- else fs22.renameSync(raw, out);
28976
+ else fs23.renameSync(raw, out);
28706
28977
  return;
28707
28978
  }
28708
28979
  case "msi": {
@@ -28752,12 +29023,12 @@ var AppInstaller = class {
28752
29023
  async installOne(entry) {
28753
29024
  try {
28754
29025
  const dest = tool("apps", ...entry.id.split("/"));
28755
- if (!exists(path32.join(dest, MARKER)) || this.options.refresh) await this.download(entry, dest);
29026
+ if (!exists(path33.join(dest, MARKER)) || this.options.refresh) await this.download(entry, dest);
28756
29027
  const exe = findExe(dest, entry.exe);
28757
29028
  if (!exe) throw new Error(entry.exe + " not found in the download");
28758
29029
  await afterInstall(entry, exe);
28759
- if (entry.cli || entry.id === "sysinternals") this.env.prepend.push(path32.dirname(exe));
28760
- if (!entry.cli) this.gui.push({ name: entry.name, exe });
29030
+ if (!entry.service && (entry.cli || entry.id === "sysinternals")) this.env.prepend.push(path33.dirname(exe));
29031
+ if (!entry.cli && !entry.service) this.gui.push({ name: entry.name, exe });
28761
29032
  this.installed.push(entry.name);
28762
29033
  } catch (e) {
28763
29034
  warn(
@@ -28769,10 +29040,13 @@ var AppInstaller = class {
28769
29040
  /** Downloads and unpacks one entry into its folder (replaced in one step: never half there). */
28770
29041
  async download(entry, dest) {
28771
29042
  const work = stagingFolder(dest);
28772
- const out = path32.join(work, "out");
29043
+ const out = path33.join(work, "out");
28773
29044
  try {
28774
- if ("npm" in entry.source) {
28775
- const npmCli = path32.join(path32.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
29045
+ if ("locked" in entry.source) {
29046
+ log("Installing " + entry.name + " " + entry.source.version + " (pinned, every file checked) ...");
29047
+ await installLocked(entry.id, entry.source.locked, mkdirp(out));
29048
+ } else if ("npm" in entry.source) {
29049
+ const npmCli = path33.join(path33.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
28776
29050
  log("Installing " + entry.name + " (npm) ...");
28777
29051
  await execAsync(process.execPath, [
28778
29052
  npmCli,
@@ -28787,17 +29061,60 @@ var AppInstaller = class {
28787
29061
  } else {
28788
29062
  const d = await resolve(entry);
28789
29063
  log("Downloading " + entry.name + " (" + d.fileName + ", ~" + entry.size + " once) ...");
28790
- const file3 = path32.join(mkdirp(work), d.fileName.replace(/[\\/:*?"<>|]/g, "_"));
29064
+ const file3 = path33.join(mkdirp(work), d.fileName.replace(/[\\/:*?"<>|]/g, "_"));
28791
29065
  await downloader.file(d.url, file3, { sha256: d.sha256 ?? null });
28792
29066
  await unpack(entry, file3, out, d);
28793
29067
  }
28794
- fs23.writeFileSync(path32.join(out, MARKER), JSON.stringify({ id: entry.id, at: (/* @__PURE__ */ new Date()).toISOString() }));
29068
+ fs24.writeFileSync(path33.join(out, MARKER), JSON.stringify({ id: entry.id, at: (/* @__PURE__ */ new Date()).toISOString() }));
28795
29069
  moveIntoPlace(out, dest);
28796
29070
  } finally {
28797
- safeRemove(work, path32.dirname(work));
29071
+ safeRemove(work, path33.dirname(work));
28798
29072
  }
28799
29073
  }
28800
29074
  };
29075
+ var LOCKED_PYTHON = "3.13";
29076
+ async function installLocked(id, kind, out) {
29077
+ const lock = path33.join(launchDir, "sandbox", "addons", id);
29078
+ if (kind === "npm") {
29079
+ for (const f of ["package.json", "package-lock.json"]) fs24.copyFileSync(path33.join(lock, f), path33.join(out, f));
29080
+ const npmCli = path33.join(path33.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
29081
+ await execAsync(
29082
+ process.execPath,
29083
+ [npmCli, "ci", "--ignore-scripts", "--no-audit", "--no-fund", "--loglevel=error"],
29084
+ { cwd: out }
29085
+ );
29086
+ return;
29087
+ }
29088
+ const uv = findExe(tool("uv"), "uv.exe");
29089
+ if (!uv) throw new Error("uv is not installed (the uv step failed)");
29090
+ const env = { ...process.env, ...uvVars() };
29091
+ const venv = path33.join(out, "venv");
29092
+ await execAsync(
29093
+ uv,
29094
+ ["venv", "--relocatable", "--python", LOCKED_PYTHON, "--python-preference", "only-managed", venv],
29095
+ {
29096
+ env
29097
+ }
29098
+ );
29099
+ await execAsync(
29100
+ uv,
29101
+ [
29102
+ "pip",
29103
+ "install",
29104
+ "--python",
29105
+ path33.join(venv, "Scripts", "python.exe"),
29106
+ "--require-hashes",
29107
+ "--no-deps",
29108
+ "--only-binary",
29109
+ ":all:",
29110
+ "--link-mode",
29111
+ "copy",
29112
+ "-r",
29113
+ path33.join(lock, "requirements.txt")
29114
+ ],
29115
+ { env }
29116
+ );
29117
+ }
28801
29118
  async function setupApps(env, apps, o) {
28802
29119
  const { entries: entries2, skipped } = appsToInstall(apps, o.gitAccess);
28803
29120
  for (const s of skipped) env.summary.push("app skipped: " + s);
@@ -28809,8 +29126,8 @@ async function setupApps(env, apps, o) {
28809
29126
  }
28810
29127
 
28811
29128
  // src/guest/toolchains/detect.ts
28812
- import fs24 from "node:fs";
28813
- import path33 from "node:path";
29129
+ import fs25 from "node:fs";
29130
+ import path34 from "node:path";
28814
29131
 
28815
29132
  // src/core/detect/versions.ts
28816
29133
  function pickNodeVersion(releases, spec) {
@@ -28942,14 +29259,14 @@ function workspaceEntries() {
28942
29259
  if (known) return known;
28943
29260
  const list = (dir) => {
28944
29261
  try {
28945
- return fs24.readdirSync(dir, { withFileTypes: true });
29262
+ return fs25.readdirSync(dir, { withFileTypes: true });
28946
29263
  } catch {
28947
29264
  return [];
28948
29265
  }
28949
29266
  };
28950
29267
  const top = list(workspace2);
28951
29268
  const below = top.filter((d) => d.isDirectory() && !d.name.startsWith(".") && !SKIP_DIRS.has(d.name.toLowerCase())).flatMap(
28952
- (d) => list(path33.join(workspace2, d.name)).filter((f) => f.isFile()).map((f) => d.name + "/" + f.name)
29269
+ (d) => list(path34.join(workspace2, d.name)).filter((f) => f.isFile()).map((f) => d.name + "/" + f.name)
28953
29270
  );
28954
29271
  const found = [...top.filter((d) => d.isFile()).map((d) => d.name), ...below];
28955
29272
  entries.set(workspace2.toLowerCase(), found);
@@ -28960,9 +29277,9 @@ function wanted(setting, toolchain) {
28960
29277
  }
28961
29278
  function versionFromFiles(files, parse5 = (t) => specFromVersionFile(t)) {
28962
29279
  for (const name of files) {
28963
- const file3 = path33.join(projectWorkspace(), name);
29280
+ const file3 = path34.join(projectWorkspace(), name);
28964
29281
  if (!exists(file3)) continue;
28965
- const spec = parse5(fs24.readFileSync(file3, "utf8"), name);
29282
+ const spec = parse5(fs25.readFileSync(file3, "utf8"), name);
28966
29283
  if (spec) return { spec, source: name };
28967
29284
  }
28968
29285
  return null;
@@ -29035,22 +29352,22 @@ var DatabaseServer = class {
29035
29352
  };
29036
29353
 
29037
29354
  // src/guest/databases/dynamodb.ts
29038
- import fs29 from "node:fs";
29039
- import path36 from "node:path";
29355
+ import fs30 from "node:fs";
29356
+ import path37 from "node:path";
29040
29357
  import { spawn as spawn3 } from "node:child_process";
29041
29358
 
29042
29359
  // src/guest/toolchains/languages/java.ts
29043
- import fs27 from "node:fs";
29044
- import path34 from "node:path";
29360
+ import fs28 from "node:fs";
29361
+ import path35 from "node:path";
29045
29362
 
29046
29363
  // src/guest/toolchains/version-cache.ts
29047
- import fs26 from "node:fs";
29364
+ import fs27 from "node:fs";
29048
29365
 
29049
29366
  // src/guest/toolchains/state-file.ts
29050
- import fs25 from "node:fs";
29367
+ import fs26 from "node:fs";
29051
29368
  function readState(file3, schema, empty) {
29052
29369
  try {
29053
- return schema.safeParse(JSON.parse(fs25.readFileSync(file3, "utf8").replace(/^\uFEFF/, ""))).data ?? empty;
29370
+ return schema.safeParse(JSON.parse(fs26.readFileSync(file3, "utf8").replace(/^\uFEFF/, ""))).data ?? empty;
29054
29371
  } catch {
29055
29372
  return empty;
29056
29373
  }
@@ -29085,7 +29402,7 @@ var VersionCache = class {
29085
29402
  return cached2.value;
29086
29403
  try {
29087
29404
  const value = await resolve2();
29088
- fs26.mkdirSync(toolsDir, { recursive: true });
29405
+ fs27.mkdirSync(toolsDir, { recursive: true });
29089
29406
  writeJsonAtomic(this.file(), { ...this.remembered(), [key2]: { value, at: Date.now() } });
29090
29407
  return value;
29091
29408
  } catch (e) {
@@ -29138,7 +29455,7 @@ var setupJdk = async (env, config2) => {
29138
29455
  const { major, source } = await javaMajor(config2);
29139
29456
  const release = await resolveCached("java:" + major, async () => (await temurin(major)).release_name);
29140
29457
  const home = tool("java", release.replace(/[^A-Za-z0-9.-]/g, "_"));
29141
- if (!exists(path34.join(home, "bin", "java.exe"))) {
29458
+ if (!exists(path35.join(home, "bin", "java.exe"))) {
29142
29459
  const asset = await temurin(major);
29143
29460
  log("Downloading Java " + release + " (JDK, ~200 MB once) ...");
29144
29461
  await downloader.archive(asset.binary.package.link, home, {
@@ -29148,13 +29465,13 @@ var setupJdk = async (env, config2) => {
29148
29465
  });
29149
29466
  }
29150
29467
  env.vars.JAVA_HOME = home;
29151
- env.prepend.push(path34.join(home, "bin"));
29468
+ env.prepend.push(path35.join(home, "bin"));
29152
29469
  env.summary.push("java: " + release + " (Temurin, from " + source + ")");
29153
29470
  };
29154
29471
  async function ensureJdk() {
29155
29472
  const root = tool("java");
29156
- const installed = exists(root) ? fs27.readdirSync(root).filter((d) => exists(path34.join(root, d, "bin", "java.exe"))).sort((a, b) => b.localeCompare(a, void 0, { numeric: true })) : [];
29157
- if (installed.length) return path34.join(root, installed[0]);
29473
+ const installed = exists(root) ? fs28.readdirSync(root).filter((d) => exists(path35.join(root, d, "bin", "java.exe"))).sort((a, b) => b.localeCompare(a, void 0, { numeric: true })) : [];
29474
+ if (installed.length) return path35.join(root, installed[0]);
29158
29475
  const scratch = emptyEnv();
29159
29476
  await setupJdk(scratch, { enabled: true, version: "lts", maven: false, gradle: false });
29160
29477
  return scratch.vars.JAVA_HOME;
@@ -29167,7 +29484,7 @@ var setupMaven = async (env) => {
29167
29484
  return pickPackageVersion(versions, "3") ?? Promise.reject(new Error("No Maven 3 release found"));
29168
29485
  });
29169
29486
  const home = tool("maven", version2);
29170
- if (!exists(path34.join(home, "bin", "mvn.cmd"))) {
29487
+ if (!exists(path35.join(home, "bin", "mvn.cmd"))) {
29171
29488
  const url2 = MAVEN + version2 + "/apache-maven-" + version2 + "-bin.zip";
29172
29489
  log("Downloading Maven " + version2 + " (~10 MB once) ...");
29173
29490
  await downloader.archive(url2, home, {
@@ -29177,7 +29494,7 @@ var setupMaven = async (env) => {
29177
29494
  }
29178
29495
  const repo = tool("maven-repository");
29179
29496
  Object.assign(env.vars, { MAVEN_HOME: home, M2_HOME: home, MAVEN_OPTS: "-Dmaven.repo.local=" + repo });
29180
- env.prepend.push(path34.join(home, "bin"));
29497
+ env.prepend.push(path35.join(home, "bin"));
29181
29498
  env.summary.push("maven: " + version2);
29182
29499
  };
29183
29500
  var setupGradle = async (env) => {
@@ -29189,7 +29506,7 @@ var setupGradle = async (env) => {
29189
29506
  async () => current?.version ?? Promise.reject(new Error("Gradle version lookup failed"))
29190
29507
  );
29191
29508
  const home = tool("gradle", version2);
29192
- if (!exists(path34.join(home, "bin", "gradle.bat"))) {
29509
+ if (!exists(path35.join(home, "bin", "gradle.bat"))) {
29193
29510
  if (!current || current.version !== version2)
29194
29511
  throw new Error("Gradle " + version2 + " is not installed and cannot be downloaded right now");
29195
29512
  log("Downloading Gradle " + version2 + " (~130 MB once) ...");
@@ -29200,13 +29517,13 @@ var setupGradle = async (env) => {
29200
29517
  });
29201
29518
  }
29202
29519
  const userHome = tool("gradle-home");
29203
- fs27.mkdirSync(userHome, { recursive: true });
29520
+ fs28.mkdirSync(userHome, { recursive: true });
29204
29521
  Object.assign(env.vars, {
29205
29522
  GRADLE_HOME: home,
29206
29523
  GRADLE_USER_HOME: userHome,
29207
29524
  GRADLE_OPTS: "-Dorg.gradle.vfs.watch=false"
29208
29525
  });
29209
- env.prepend.push(path34.join(home, "bin"));
29526
+ env.prepend.push(path35.join(home, "bin"));
29210
29527
  env.summary.push("gradle: " + version2);
29211
29528
  };
29212
29529
  async function setupJava(env, config2) {
@@ -29230,29 +29547,29 @@ async function setupJava(env, config2) {
29230
29547
  }
29231
29548
 
29232
29549
  // src/guest/databases/server.ts
29233
- import fs28 from "node:fs";
29234
- import path35 from "node:path";
29550
+ import fs29 from "node:fs";
29551
+ import path36 from "node:path";
29235
29552
  import { spawn as spawn2 } from "node:child_process";
29236
29553
  function dataFor(name) {
29237
- return path35.join(dataDir, "databases", name);
29554
+ return path36.join(dataDir, "databases", name);
29238
29555
  }
29239
29556
  async function startServer(name, exe, args, port) {
29240
29557
  if (await portOpen(port)) throw new Error("port " + port + " is already in use (change databases." + name + ".port)");
29241
- const logFile2 = path35.join(logsDir, name + ".log");
29242
- const out = fs28.openSync(logFile2, "a");
29558
+ const logFile2 = path36.join(logsDir, name + ".log");
29559
+ const out = fs29.openSync(logFile2, "a");
29243
29560
  spawn2(exe, args, { detached: true, stdio: ["ignore", out, out], windowsHide: true }).unref();
29244
- fs28.closeSync(out);
29561
+ fs29.closeSync(out);
29245
29562
  if (!await waitForPort(port, 60))
29246
29563
  throw new Error(name + " did not start listening on port " + port + "; see " + logFile2);
29247
29564
  }
29248
29565
  async function clearStaleLocks(dir, files, port) {
29249
29566
  if (await portOpen(port)) return;
29250
- for (const f of files) fs28.rmSync(path35.join(dir, f), { force: true });
29567
+ for (const f of files) fs29.rmSync(path36.join(dir, f), { force: true });
29251
29568
  }
29252
29569
  async function createOnce(data, marker, create) {
29253
- if (exists(path35.join(data, marker))) return;
29570
+ if (exists(path36.join(data, marker))) return;
29254
29571
  const existed = exists(data);
29255
- mkdirp(path35.dirname(data));
29572
+ mkdirp(path36.dirname(data));
29256
29573
  try {
29257
29574
  await create();
29258
29575
  } catch (e) {
@@ -29276,9 +29593,9 @@ var DynamoServer = class extends DatabaseServer {
29276
29593
  * local endpoint up from AWS_ENDPOINT_URL_DYNAMODB, with dummy credentials (real ones never reach the sandbox).
29277
29594
  */
29278
29595
  async start(env, plan2) {
29279
- const java = path36.join(await ensureJdk(), "bin", "java.exe");
29596
+ const java = path37.join(await ensureJdk(), "bin", "java.exe");
29280
29597
  const home = tool("dynamodb-local");
29281
- if (!exists(path36.join(home, "DynamoDBLocal.jar"))) {
29598
+ if (!exists(path37.join(home, "DynamoDBLocal.jar"))) {
29282
29599
  log("Downloading DynamoDB Local (~60 MB once) ...");
29283
29600
  const sha256 = (await fetchText(DYNAMO_ZIP + ".sha256")).trim().split(/\s+/)[0];
29284
29601
  await downloader.archive(DYNAMO_ZIP, home, {
@@ -29292,9 +29609,9 @@ var DynamoServer = class extends DatabaseServer {
29292
29609
  "dynamodb",
29293
29610
  java,
29294
29611
  [
29295
- "-Djava.library.path=" + path36.join(home, "DynamoDBLocal_lib"),
29612
+ "-Djava.library.path=" + path37.join(home, "DynamoDBLocal_lib"),
29296
29613
  "-jar",
29297
- path36.join(home, "DynamoDBLocal.jar"),
29614
+ path37.join(home, "DynamoDBLocal.jar"),
29298
29615
  "-sharedDb",
29299
29616
  "-dbPath",
29300
29617
  data,
@@ -29318,10 +29635,10 @@ var DynamoServer = class extends DatabaseServer {
29318
29635
  );
29319
29636
  if (!plan2.adminPort) return;
29320
29637
  const prefix = tool("dynamodb-admin");
29321
- const script = path36.join(prefix, "node_modules", "dynamodb-admin", "dist", "dynamodb-admin.js");
29638
+ const script = path37.join(prefix, "node_modules", "dynamodb-admin", "dist", "dynamodb-admin.js");
29322
29639
  if (!exists(script)) {
29323
29640
  log("Installing dynamodb-admin, the DynamoDB web UI (~20 MB once) ...");
29324
- const npmCli = path36.join(path36.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
29641
+ const npmCli = path37.join(path37.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
29325
29642
  await execAsync(
29326
29643
  process.execPath,
29327
29644
  [npmCli, "install", "--global", "--prefix", prefix, "dynamodb-admin", "--no-fund", "--no-audit"],
@@ -29334,15 +29651,15 @@ var DynamoServer = class extends DatabaseServer {
29334
29651
  );
29335
29652
  return;
29336
29653
  }
29337
- const out = fs29.openSync(path36.join(logsDir, "dynamodb-admin.log"), "a");
29654
+ const out = fs30.openSync(path37.join(logsDir, "dynamodb-admin.log"), "a");
29338
29655
  spawn3(
29339
29656
  process.execPath,
29340
29657
  [script, "--port", String(plan2.adminPort), "--dynamo-endpoint", endpoint, "--skip-default-credentials"],
29341
29658
  { detached: true, stdio: ["ignore", out, out], windowsHide: true, env: { ...process.env, ...aws } }
29342
29659
  ).unref();
29343
- fs29.closeSync(out);
29660
+ fs30.closeSync(out);
29344
29661
  if (!await waitForPort(plan2.adminPort, 30))
29345
- throw new Error("dynamodb-admin did not start; see " + path36.join(logsDir, "dynamodb-admin.log"));
29662
+ throw new Error("dynamodb-admin did not start; see " + path37.join(logsDir, "dynamodb-admin.log"));
29346
29663
  env.summary.push(
29347
29664
  "dynamodb-admin: http://localhost:" + plan2.adminPort + " (browse and edit tables in the sandbox's browser)"
29348
29665
  );
@@ -29350,32 +29667,7 @@ var DynamoServer = class extends DatabaseServer {
29350
29667
  };
29351
29668
 
29352
29669
  // src/guest/databases/mongodb.ts
29353
- import path37 from "node:path";
29354
-
29355
- // src/guest/toolchains/install.ts
29356
- import fs30 from "node:fs";
29357
- async function downloadFile(url2, file3, sha256 = null) {
29358
- const part = file3 + ".part";
29359
- try {
29360
- await downloader.file(url2, part, sha256);
29361
- fs30.rmSync(file3, { force: true });
29362
- fs30.renameSync(part, file3);
29363
- } finally {
29364
- fs30.rmSync(part, { force: true });
29365
- }
29366
- }
29367
- async function installFromGithub(repo, pattern, o) {
29368
- const asset = await githubAsset(repo, pattern, o.searchOlder);
29369
- log("Downloading " + (typeof o.what === "string" ? o.what : o.what(asset)) + " ...");
29370
- await downloader.archive(asset.url, o.dest, {
29371
- sha256: asset.sha256,
29372
- fileName: asset.name,
29373
- ...o.locate ? { locate: o.locate } : {},
29374
- ...o.up ? { up: o.up } : {}
29375
- });
29376
- }
29377
-
29378
- // src/guest/databases/mongodb.ts
29670
+ import path38 from "node:path";
29379
29671
  var MongoServer = class extends DatabaseServer {
29380
29672
  id = "mongodb";
29381
29673
  async start(env, plan2) {
@@ -29390,13 +29682,13 @@ var MongoServer = class extends DatabaseServer {
29390
29682
  };
29391
29683
  const version2 = await resolveCached("mongodb:" + plan2.version, async () => (await find()).version);
29392
29684
  const home = tool("mongodb", version2);
29393
- if (!exists(path37.join(home, "bin", "mongod.exe"))) {
29685
+ if (!exists(path38.join(home, "bin", "mongod.exe"))) {
29394
29686
  const { archive } = await find();
29395
29687
  log("Downloading MongoDB " + version2 + " (~400 MB once) ...");
29396
29688
  await downloader.archive(archive.url, home, { sha256: archive.sha256, locate: "mongod.exe", up: 1 });
29397
29689
  }
29398
29690
  const shell = tool("mongosh");
29399
- if (!exists(path37.join(shell, "mongosh.exe"))) {
29691
+ if (!exists(path38.join(shell, "mongosh.exe"))) {
29400
29692
  await installFromGithub("mongodb-js/mongosh", /^mongosh-[\d.]+-win32-x64\.zip$/, {
29401
29693
  dest: shell,
29402
29694
  what: "mongosh, the MongoDB shell (~50 MB once)",
@@ -29407,17 +29699,17 @@ var MongoServer = class extends DatabaseServer {
29407
29699
  await clearStaleLocks(data, ["mongod.lock"], plan2.port);
29408
29700
  await startServer(
29409
29701
  "mongodb",
29410
- path37.join(home, "bin", "mongod.exe"),
29702
+ path38.join(home, "bin", "mongod.exe"),
29411
29703
  ["--dbpath", data, "--bind_ip", "127.0.0.1", "--port", String(plan2.port)],
29412
29704
  plan2.port
29413
29705
  );
29414
- env.prepend.push(path37.join(home, "bin"), shell);
29706
+ env.prepend.push(path38.join(home, "bin"), shell);
29415
29707
  env.summary.push("mongodb " + version2 + ": mongodb://127.0.0.1:" + plan2.port + " (mongosh on PATH)");
29416
29708
  }
29417
29709
  };
29418
29710
 
29419
29711
  // src/guest/databases/mysql.ts
29420
- import path38 from "node:path";
29712
+ import path39 from "node:path";
29421
29713
  var MARIA = "https://downloads.mariadb.org/rest-api/mariadb/";
29422
29714
  var MysqlServer = class extends DatabaseServer {
29423
29715
  id = "mysql";
@@ -29435,8 +29727,8 @@ var MysqlServer = class extends DatabaseServer {
29435
29727
  };
29436
29728
  const version2 = await resolveCached("mariadb-version:" + series, async () => (await latest()).version);
29437
29729
  const home = tool("mariadb", version2);
29438
- const bin = path38.join(home, "bin");
29439
- if (!exists(path38.join(bin, "mariadbd.exe"))) {
29730
+ const bin = path39.join(home, "bin");
29731
+ if (!exists(path39.join(bin, "mariadbd.exe"))) {
29440
29732
  const { zip } = await latest();
29441
29733
  log("Downloading MariaDB " + version2 + " (~90 MB once) ...");
29442
29734
  await downloader.archive(zip.file_download_url.replace(/^http:/, "https:"), home, {
@@ -29449,13 +29741,13 @@ var MysqlServer = class extends DatabaseServer {
29449
29741
  const data = dataFor("mariadb");
29450
29742
  await createOnce(data, "mysql", async () => {
29451
29743
  log("Creating the MariaDB database (first start) ...");
29452
- await execAsync(path38.join(bin, "mariadb-install-db.exe"), ["--datadir=" + data, "--port=" + plan2.port], {
29744
+ await execAsync(path39.join(bin, "mariadb-install-db.exe"), ["--datadir=" + data, "--port=" + plan2.port], {
29453
29745
  stdio: "ignore"
29454
29746
  });
29455
29747
  });
29456
29748
  await startServer(
29457
29749
  "mysql",
29458
- path38.join(bin, "mariadbd.exe"),
29750
+ path39.join(bin, "mariadbd.exe"),
29459
29751
  ["--datadir=" + data, "--port=" + plan2.port, "--bind-address=127.0.0.1", "--console"],
29460
29752
  plan2.port
29461
29753
  );
@@ -29469,7 +29761,7 @@ var MysqlServer = class extends DatabaseServer {
29469
29761
 
29470
29762
  // src/guest/databases/postgres.ts
29471
29763
  import fs31 from "node:fs";
29472
- import path39 from "node:path";
29764
+ import path40 from "node:path";
29473
29765
  function postgresData(version2) {
29474
29766
  return dataFor("postgres-" + version2.split(".")[0]);
29475
29767
  }
@@ -29484,8 +29776,8 @@ var PostgresServer = class extends DatabaseServer {
29484
29776
  return hit ? hit.major + "." + hit.latestMinor : Promise.reject(new Error("No supported PostgreSQL " + plan2.version));
29485
29777
  });
29486
29778
  const home = tool("postgres", exact);
29487
- const bin = path39.join(home, "bin");
29488
- if (!exists(path39.join(bin, "pg_ctl.exe"))) {
29779
+ const bin = path40.join(home, "bin");
29780
+ if (!exists(path40.join(bin, "pg_ctl.exe"))) {
29489
29781
  log("Downloading PostgreSQL " + exact + " (~330 MB once) ...");
29490
29782
  await downloader.archive(
29491
29783
  "https://get.enterprisedb.com/postgresql/postgresql-" + exact + "-1-windows-x64-binaries.zip",
@@ -29502,7 +29794,7 @@ var PostgresServer = class extends DatabaseServer {
29502
29794
  await createOnce(data, "PG_VERSION", async () => {
29503
29795
  log("Creating the PostgreSQL database (first start) ...");
29504
29796
  await execAsync(
29505
- path39.join(bin, "initdb.exe"),
29797
+ path40.join(bin, "initdb.exe"),
29506
29798
  ["-D", data, "-U", "postgres", "-A", "trust", "-E", "UTF8", "--locale=C"],
29507
29799
  { stdio: "ignore" }
29508
29800
  );
@@ -29511,7 +29803,7 @@ var PostgresServer = class extends DatabaseServer {
29511
29803
  throw new Error("port " + plan2.port + " is already in use (change databases.postgres.port)");
29512
29804
  await clearStaleLocks(data, ["postmaster.pid"], plan2.port);
29513
29805
  await execAsync(
29514
- path39.join(bin, "pg_ctl.exe"),
29806
+ path40.join(bin, "pg_ctl.exe"),
29515
29807
  [
29516
29808
  "start",
29517
29809
  "-D",
@@ -29520,7 +29812,7 @@ var PostgresServer = class extends DatabaseServer {
29520
29812
  "-t",
29521
29813
  "60",
29522
29814
  "-l",
29523
- path39.join(logsDir, "postgres.log"),
29815
+ path40.join(logsDir, "postgres.log"),
29524
29816
  "-o",
29525
29817
  "-p " + plan2.port + " -c listen_addresses=localhost"
29526
29818
  ],
@@ -29537,8 +29829,8 @@ var PostgresServer = class extends DatabaseServer {
29537
29829
  if (exists(tool("postgres"))) {
29538
29830
  for (const version2 of fs31.readdirSync(tool("postgres"))) {
29539
29831
  const data = postgresData(version2);
29540
- const pgCtl = path39.join(tool("postgres", version2), "bin", "pg_ctl.exe");
29541
- if (exists(path39.join(data, "postmaster.pid")) && exists(pgCtl))
29832
+ const pgCtl = path40.join(tool("postgres", version2), "bin", "pg_ctl.exe");
29833
+ if (exists(path40.join(data, "postmaster.pid")) && exists(pgCtl))
29542
29834
  await execAsync(pgCtl, ["stop", "-D", data, "-m", "fast", "-w"], { stdio: "ignore" });
29543
29835
  }
29544
29836
  }
@@ -29547,12 +29839,12 @@ var PostgresServer = class extends DatabaseServer {
29547
29839
  };
29548
29840
 
29549
29841
  // src/guest/databases/redis.ts
29550
- import path40 from "node:path";
29842
+ import path41 from "node:path";
29551
29843
  var RedisServer = class extends DatabaseServer {
29552
29844
  id = "redis";
29553
29845
  async start(env, plan2) {
29554
29846
  const home = tool("redis");
29555
- if (!exists(path40.join(home, "redis-server.exe"))) {
29847
+ if (!exists(path41.join(home, "redis-server.exe"))) {
29556
29848
  await installFromGithub("redis-windows/redis-windows", /^Redis-[\d.]+-Windows-x64-msys2\.zip$/, {
29557
29849
  dest: home,
29558
29850
  what: (a) => "Redis (" + a.name + ", ~10 MB once)",
@@ -29562,7 +29854,7 @@ var RedisServer = class extends DatabaseServer {
29562
29854
  const data = mkdirp(dataFor("redis"));
29563
29855
  await startServer(
29564
29856
  "redis",
29565
- path40.join(home, "redis-server.exe"),
29857
+ path41.join(home, "redis-server.exe"),
29566
29858
  ["--port", String(plan2.port), "--bind", "127.0.0.1", "--dir", data.replace(/\\/g, "/"), "--appendonly", "yes"],
29567
29859
  plan2.port
29568
29860
  );
@@ -29601,15 +29893,15 @@ async function setupDatabase(env, plan2) {
29601
29893
 
29602
29894
  // src/guest/toolchains/tools/git.ts
29603
29895
  import fs33 from "node:fs";
29604
- import path42 from "node:path";
29896
+ import path43 from "node:path";
29605
29897
 
29606
29898
  // src/guest/runtime/csc.ts
29607
29899
  import fs32 from "node:fs";
29608
29900
  import os6 from "node:os";
29609
- import path41 from "node:path";
29901
+ import path42 from "node:path";
29610
29902
  import crypto3 from "node:crypto";
29611
29903
  import { spawnSync as spawnSync5 } from "node:child_process";
29612
- var CSC = path41.join(process.env.WINDIR ?? "C:\\Windows", "Microsoft.NET", "Framework64", "v4.0.30319", "csc.exe");
29904
+ var CSC = path42.join(process.env.WINDIR ?? "C:\\Windows", "Microsoft.NET", "Framework64", "v4.0.30319", "csc.exe");
29613
29905
  var SOURCES = {
29614
29906
  ptyhost: "PtyHost.cs",
29615
29907
  gitshim: "GitShim.cs",
@@ -29621,17 +29913,17 @@ var SOURCES = {
29621
29913
  memgov: "MemGov.cs"
29622
29914
  };
29623
29915
  function sourceOf(name) {
29624
- return path41.join(launchDir, "sandbox", "csharp", SOURCES[name]);
29916
+ return path42.join(launchDir, "sandbox", "csharp", SOURCES[name]);
29625
29917
  }
29626
29918
  function sourceHash(name) {
29627
29919
  return crypto3.createHash("sha256").update(fs32.readFileSync(sourceOf(name))).digest("hex").slice(0, 12);
29628
29920
  }
29629
29921
  function ensureCompiled(name) {
29630
- const exe = path41.join(os6.tmpdir(), "agent-sandbox", name + "-" + sourceHash(name) + ".exe");
29922
+ const exe = path42.join(os6.tmpdir(), "agent-sandbox", name + "-" + sourceHash(name) + ".exe");
29631
29923
  if (exists(exe)) return exe;
29632
29924
  if (!exists(CSC))
29633
29925
  throw new Error("The .NET Framework C# compiler was not found at " + CSC + " (it ships with Windows 10/11).");
29634
- mkdirp(path41.dirname(exe));
29926
+ mkdirp(path42.dirname(exe));
29635
29927
  const result = spawnSync5(CSC, ["/nologo", "/optimize", "/target:exe", "/out:" + exe, sourceOf(name)], {
29636
29928
  encoding: "utf8",
29637
29929
  windowsHide: true
@@ -29670,18 +29962,18 @@ var installPortableGit = async (root) => {
29670
29962
  };
29671
29963
  var installGuard = (env, access, realGit) => {
29672
29964
  const dir = mkdirp(tool("gitguard"));
29673
- const guard = path42.join(launchDir, "runtime", "gitguard.js");
29674
- if (duringSession() && (exists(path42.join(dir, "git.exe")) || exists(path42.join(dir, "git.cmd")))) {
29965
+ const guard = path43.join(launchDir, "runtime", "gitguard.js");
29966
+ if (duringSession() && (exists(path43.join(dir, "git.exe")) || exists(path43.join(dir, "git.cmd")))) {
29675
29967
  } else
29676
29968
  try {
29677
- fs33.copyFileSync(ensureCompiled("gitshim"), path42.join(dir, "git.exe"));
29678
- fs33.rmSync(path42.join(dir, "git.cmd"), { force: true });
29969
+ fs33.copyFileSync(ensureCompiled("gitshim"), path43.join(dir, "git.exe"));
29970
+ fs33.rmSync(path43.join(dir, "git.cmd"), { force: true });
29679
29971
  } catch (e) {
29680
29972
  warn("git guard: falling back to git.cmd (" + errMsg(e) + ")");
29681
- fs33.writeFileSync(path42.join(dir, "git.cmd"), '@"' + process.execPath + '" "' + guard + '" %*\r\n');
29973
+ fs33.writeFileSync(path43.join(dir, "git.cmd"), '@"' + process.execPath + '" "' + guard + '" %*\r\n');
29682
29974
  }
29683
29975
  fs33.writeFileSync(
29684
- path42.join(dir, "bashenv.sh"),
29976
+ path43.join(dir, "bashenv.sh"),
29685
29977
  'git() { "' + posix(process.execPath) + '" "' + posix(guard) + '" "$@"; }\nexport -f git\n'
29686
29978
  );
29687
29979
  env.prepend.unshift(dir);
@@ -29690,13 +29982,13 @@ var installGuard = (env, access, realGit) => {
29690
29982
  AGENT_REAL_GIT: access === "readonly" ? realGit : "",
29691
29983
  AGENT_GUARD_NODE: process.execPath,
29692
29984
  AGENT_GUARD_SCRIPT: guard,
29693
- BASH_ENV: posix(path42.join(dir, "bashenv.sh"))
29985
+ BASH_ENV: posix(path43.join(dir, "bashenv.sh"))
29694
29986
  });
29695
29987
  if (access === "readonly") {
29696
- const hooks = mkdirp(path42.join(dir, "hooks"));
29988
+ const hooks = mkdirp(path43.join(dir, "hooks"));
29697
29989
  for (const hook of BLOCKED_HOOKS)
29698
29990
  fs33.writeFileSync(
29699
- path42.join(hooks, hook),
29991
+ path43.join(hooks, hook),
29700
29992
  '#!/bin/sh\necho "git is read-only in this sandbox: ' + hook + ' refused." >&2\nexit 1\n'
29701
29993
  );
29702
29994
  Object.assign(env.vars, {
@@ -29705,12 +29997,12 @@ var installGuard = (env, access, realGit) => {
29705
29997
  GIT_CONFIG_VALUE_0: posix(hooks)
29706
29998
  });
29707
29999
  } else {
29708
- env.vars.GIT_DIR = path42.join(dataDir, "git-disabled");
30000
+ env.vars.GIT_DIR = path43.join(dataDir, "git-disabled");
29709
30001
  }
29710
30002
  };
29711
30003
  var pointAtOwnCertificates = (root) => {
29712
- const config2 = path42.join(root, "etc", "gitconfig");
29713
- const bundle = path42.join(root, "mingw64", "etc", "ssl", "certs", "ca-bundle.crt");
30004
+ const config2 = path43.join(root, "etc", "gitconfig");
30005
+ const bundle = path43.join(root, "mingw64", "etc", "ssl", "certs", "ca-bundle.crt");
29714
30006
  if (!exists(config2) || !exists(bundle)) return;
29715
30007
  const text = fs33.readFileSync(config2, "utf8");
29716
30008
  const fixed = text.replace(/^(\s*sslCAInfo\s*=\s*).*$/gim, (_m, key2) => key2 + posix(bundle));
@@ -29718,9 +30010,9 @@ var pointAtOwnCertificates = (root) => {
29718
30010
  };
29719
30011
  async function setupGit(env, hostCopy, refresh, access) {
29720
30012
  const root = tool("git");
29721
- const bash = path42.join(root, "bin", "bash.exe");
29722
- const gitExe = path42.join(root, "cmd", "git.exe");
29723
- const hostGit = path42.join(hostToolsDir, "git");
30013
+ const bash = path43.join(root, "bin", "bash.exe");
30014
+ const gitExe = path43.join(root, "cmd", "git.exe");
30015
+ const hostGit = path43.join(hostToolsDir, "git");
29724
30016
  if (exists(root) && !exists(bash)) {
29725
30017
  log("Replacing the old MinGit (no bash) with full Git for Windows.");
29726
30018
  safeRemove(root, toolsDir);
@@ -29734,11 +30026,11 @@ async function setupGit(env, hostCopy, refresh, access) {
29734
30026
  }
29735
30027
  const work = stagingFolder(root);
29736
30028
  try {
29737
- if (hostCopy && exists(path42.join(hostGit, "bin", "bash.exe"))) {
30029
+ if (hostCopy && exists(path43.join(hostGit, "bin", "bash.exe"))) {
29738
30030
  log("Copying Git for Windows from this PC ...");
29739
30031
  robocopy(hostGit, work);
29740
30032
  } else await installPortableGit(work);
29741
- if (!exists(path42.join(work, "bin", "bash.exe"))) throw new Error("bash.exe missing after installing Git");
30033
+ if (!exists(path43.join(work, "bin", "bash.exe"))) throw new Error("bash.exe missing after installing Git");
29742
30034
  moveIntoPlace(work, root);
29743
30035
  } finally {
29744
30036
  safeRemove(work, toolsDir);
@@ -29747,10 +30039,10 @@ async function setupGit(env, hostCopy, refresh, access) {
29747
30039
  if (!exists(bash)) throw new Error("bash.exe missing after installing Git");
29748
30040
  pointAtOwnCertificates(root);
29749
30041
  env.vars.CLAUDE_CODE_GIT_BASH_PATH = bash;
29750
- env.append.push(path42.join(root, "usr", "bin"));
30042
+ env.append.push(path43.join(root, "usr", "bin"));
29751
30043
  const version2 = execOutput(gitExe, ["--version"]).replace(/^git version /, "");
29752
30044
  if (access === "full") {
29753
- env.prepend.push(path42.join(root, "cmd"));
30045
+ env.prepend.push(path43.join(root, "cmd"));
29754
30046
  env.summary.push("git: " + version2);
29755
30047
  return;
29756
30048
  }
@@ -29762,7 +30054,7 @@ async function setupGit(env, hostCopy, refresh, access) {
29762
30054
 
29763
30055
  // src/guest/toolchains/languages/go.ts
29764
30056
  import fs34 from "node:fs";
29765
- import path43 from "node:path";
30057
+ import path44 from "node:path";
29766
30058
 
29767
30059
  // src/guest/toolchains/package-ledger.ts
29768
30060
  var LedgerSchema = external_exports.record(external_exports.string(), external_exports.string());
@@ -29816,7 +30108,7 @@ async function setupGo(env, config2) {
29816
30108
  }
29817
30109
  const version2 = await resolveCached("go:" + config2.version, async () => (await findRelease(config2.version)).version);
29818
30110
  const root = tool("go", version2);
29819
- if (!exists(path43.join(root, "bin", "go.exe"))) {
30111
+ if (!exists(path44.join(root, "bin", "go.exe"))) {
29820
30112
  const goArch = arch === "arm64" ? "arm64" : "amd64";
29821
30113
  const file3 = (await findRelease(version2)).files.find(
29822
30114
  (f) => f.os === "windows" && f.arch === goArch && f.kind === "archive"
@@ -29833,16 +30125,16 @@ async function setupGo(env, config2) {
29833
30125
  const vars = {
29834
30126
  GOROOT: root,
29835
30127
  GOPATH,
29836
- GOBIN: path43.join(GOPATH, "bin"),
29837
- GOMODCACHE: path43.join(GOPATH, "pkg", "mod"),
30128
+ GOBIN: path44.join(GOPATH, "bin"),
30129
+ GOMODCACHE: path44.join(GOPATH, "pkg", "mod"),
29838
30130
  GOCACHE: tool("go-cache"),
29839
30131
  GOTOOLCHAIN: "auto",
29840
30132
  GOTELEMETRY: "off"
29841
30133
  };
29842
30134
  fs34.mkdirSync(vars.GOBIN, { recursive: true });
29843
30135
  Object.assign(env.vars, vars);
29844
- env.prepend.push(path43.join(root, "bin"), vars.GOBIN);
29845
- const go = path43.join(root, "bin", "go.exe");
30136
+ env.prepend.push(path44.join(root, "bin"), vars.GOBIN);
30137
+ const go = path44.join(root, "bin", "go.exe");
29846
30138
  env.summary.push("go: " + versionOf(go, ["version"]));
29847
30139
  await installOnce(
29848
30140
  env,
@@ -29900,77 +30192,6 @@ function decideCli(settings, ctx) {
29900
30192
  return Object.fromEntries(CLI_NAMES.map((name) => [name, decide2(name)]));
29901
30193
  }
29902
30194
 
29903
- // src/guest/toolchains/runtimes.ts
29904
- import path44 from "node:path";
29905
- async function setupSingleExe(env, name, repo, pattern, exeName, dir, options = {}) {
29906
- if (dryRun) {
29907
- log("(dry run) would provision " + name);
29908
- return;
29909
- }
29910
- const bin = options.subdir ? path44.join(dir, options.subdir) : dir;
29911
- const exe = path44.join(bin, exeName);
29912
- if (!exists(exe) || options.refresh) {
29913
- const size = options.size ? ", ~" + options.size : "";
29914
- await installFromGithub(repo, pattern, {
29915
- dest: dir,
29916
- what: (a) => name + " (" + a.name + size + " once)",
29917
- ...options.subdir ? {} : { locate: exeName }
29918
- });
29919
- }
29920
- if (!exists(exe)) throw new Error(exeName + " missing after installing " + name);
29921
- env.prepend.push(bin);
29922
- env.summary.push(name + ": " + versionOf(exe, options.versionArgs) + (options.note ?? ""));
29923
- }
29924
- async function setupBun(env) {
29925
- await setupSingleExe(
29926
- env,
29927
- "bun",
29928
- "oven-sh/bun",
29929
- arch === "arm64" ? /^bun-windows-aarch64\.zip$/ : /^bun-windows-x64\.zip$/,
29930
- "bun.exe",
29931
- tool("bun", "bin"),
29932
- { size: "35 MB" }
29933
- );
29934
- Object.assign(env.vars, {
29935
- BUN_INSTALL: tool("bun"),
29936
- BUN_INSTALL_CACHE_DIR: path44.join(localDir, "bun-cache"),
29937
- BUN_TMPDIR: path44.join(localDir, "bun-tmp")
29938
- });
29939
- }
29940
- async function setupDeno(env) {
29941
- await setupSingleExe(
29942
- env,
29943
- "deno",
29944
- "denoland/deno",
29945
- arch === "arm64" ? /^deno-aarch64-pc-windows-msvc[.]zip$/ : /^deno-x86_64-pc-windows-msvc[.]zip$/,
29946
- "deno.exe",
29947
- tool("deno", "bin"),
29948
- { size: "40 MB" }
29949
- );
29950
- Object.assign(env.vars, { DENO_DIR: tool("deno-cache"), DENO_INSTALL_ROOT: tool("deno"), DENO_NO_UPDATE_CHECK: "1" });
29951
- }
29952
- function uvVars() {
29953
- return {
29954
- UV_CACHE_DIR: tool("uv-cache"),
29955
- UV_PYTHON_INSTALL_DIR: tool("uv-python"),
29956
- UV_TOOL_DIR: tool("uv-tools"),
29957
- UV_TOOL_BIN_DIR: tool("uv-bin")
29958
- };
29959
- }
29960
- async function setupUv(env) {
29961
- await setupSingleExe(
29962
- env,
29963
- "uv",
29964
- "astral-sh/uv",
29965
- arch === "arm64" ? /^uv-aarch64-pc-windows-msvc\.zip$/ : /^uv-x86_64-pc-windows-msvc\.zip$/,
29966
- "uv.exe",
29967
- tool("uv"),
29968
- { size: "20 MB" }
29969
- );
29970
- Object.assign(env.vars, uvVars());
29971
- env.prepend.push(mkdirp(tool("uv-bin")));
29972
- }
29973
-
29974
30195
  // src/guest/toolchains/tools/cli.ts
29975
30196
  function ghConfigDir() {
29976
30197
  return path45.join(stateDir, "gh");
@@ -31610,7 +31831,12 @@ function plan(config2, versions, o) {
31610
31831
  },
31611
31832
  { name: "Deno", enabled: wanted(config2.deno.enabled, "deno"), install: (env) => setupDeno(env) },
31612
31833
  { name: "Bun", enabled: wanted(config2.bun.enabled, "bun"), install: (env) => setupBun(env) },
31613
- { name: "uv", enabled: wanted(config2.uv.enabled, "uv"), install: (env) => setupUv(env) },
31834
+ // Headroom (an agent add-on) installs with uv: the uv step runs for it too, before the apps.
31835
+ {
31836
+ name: "uv",
31837
+ enabled: wanted(config2.uv.enabled, "uv") || appsNeedUv(o.apps),
31838
+ install: (env) => setupUv(env)
31839
+ },
31614
31840
  { name: "PowerShell 7", enabled: wanted(config2.pwsh.enabled, "pwsh"), install: (env) => setupPwsh(env, o.refresh) },
31615
31841
  // The openssl tool comes from Git's install: after Git.
31616
31842
  {
@@ -31636,7 +31862,7 @@ function plan(config2, versions, o) {
31636
31862
  name: "Apps",
31637
31863
  enabled: apps,
31638
31864
  shared: true,
31639
- after: ["Mesa 3D", "Visual C++ runtime"],
31865
+ after: ["Mesa 3D", "Visual C++ runtime", "uv"],
31640
31866
  install: (env) => setupApps(env, o.apps, { gitAccess: o.gitAccess, refresh: o.refresh })
31641
31867
  },
31642
31868
  {
@@ -32330,6 +32556,7 @@ var IdleShutdown = class {
32330
32556
  };
32331
32557
 
32332
32558
  // src/guest/daemon/sessions.ts
32559
+ var ROOTS_KEPT_MS = 10 * 60 * 1e3;
32333
32560
  function terminalTab(id, title, cwd) {
32334
32561
  const view = [process.execPath, path68.join(path68.dirname(process.argv[1]), "view.js"), id];
32335
32562
  const wt = path68.join(toolsDir, "terminal", "wt.exe");
@@ -32374,7 +32601,7 @@ function writeBudget(cpus, memoryGb, jobs, env) {
32374
32601
  warn("budget.json could not be written: " + errMsg(e));
32375
32602
  }
32376
32603
  }
32377
- var launchPtyHost = async (req, remote, env, say, sessions2) => {
32604
+ var launchPtyHost = async (req, remote, env, say, sessions2, onSpawn) => {
32378
32605
  const harness = harnessFor(req);
32379
32606
  say("Preparing " + req.harness);
32380
32607
  await agents.provision(req.harness, harness, env, false);
@@ -32395,6 +32622,7 @@ var launchPtyHost = async (req, remote, env, say, sessions2) => {
32395
32622
  command: commandLine(launch.file, [...base, ...req.args]),
32396
32623
  restartCommand: commandLine(launch.file, [...base, ...harness.resumeArguments ?? []])
32397
32624
  });
32625
+ onSpawn(child);
32398
32626
  try {
32399
32627
  await waitForPtyHost(child, port, 15e3);
32400
32628
  } catch (e) {
@@ -32434,10 +32662,26 @@ var SessionManager = class {
32434
32662
  /** Sessions and projects being set up right now (a headless sandbox must not shut down under them). */
32435
32663
  starting = 0;
32436
32664
  idle = new IdleShutdown(() => this.live.size > 0 || this.starting > 0);
32665
+ /** Each session's ptyhost (process id and when it ran), for linking the programs it starts to it (audit). */
32666
+ ptyHosts = /* @__PURE__ */ new Map();
32437
32667
  /** How many sessions are running. */
32438
32668
  get count() {
32439
32669
  return this.live.size;
32440
32670
  }
32671
+ /** The sessions' ptyhosts, running or ended within ROOTS_KEPT_MS (audit events arrive after they happen). */
32672
+ roots() {
32673
+ const cutoff = Date.now() - ROOTS_KEPT_MS;
32674
+ for (const [id, r] of this.ptyHosts) if (r.to !== void 0 && r.to < cutoff) this.ptyHosts.delete(id);
32675
+ return [...this.ptyHosts.values()];
32676
+ }
32677
+ rootStarted(id, child, from) {
32678
+ if (child.pid) this.ptyHosts.set(id, { session: id, pid: child.pid, from });
32679
+ child.once("exit", () => this.rootEnded(id));
32680
+ }
32681
+ rootEnded(id) {
32682
+ const r = this.ptyHosts.get(id);
32683
+ if (r && r.to === void 0) this.ptyHosts.set(id, { ...r, to: Date.now() });
32684
+ }
32441
32685
  /** `session` requests: an agent in its own ptyhost, in the launch folder's project or a registered one. */
32442
32686
  async start(req) {
32443
32687
  let status = {
@@ -32462,7 +32706,15 @@ var SessionManager = class {
32462
32706
  try {
32463
32707
  const place = await placeFor(req, say);
32464
32708
  const remote = session.networking && session.attach.enabled;
32465
- const { child, port, token } = await launchPtyHost(req, remote, place.env, say, this.live.size + 1);
32709
+ const from = Date.now();
32710
+ const { child, port, token } = await launchPtyHost(
32711
+ req,
32712
+ remote,
32713
+ place.env,
32714
+ say,
32715
+ this.live.size + 1,
32716
+ (c) => this.rootStarted(req.id, c, from)
32717
+ );
32466
32718
  const resumed = req.harness === "claude" ? resumedConversation(req.args) : void 0;
32467
32719
  const running = {
32468
32720
  ...status,
@@ -32480,6 +32732,7 @@ var SessionManager = class {
32480
32732
  "Session " + req.id + " started (" + req.harness + " in " + req.cwd + ", project " + place.project + ", port " + port + ")"
32481
32733
  );
32482
32734
  } catch (e) {
32735
+ this.rootEnded(req.id);
32483
32736
  saveStatus({ ...status, phase: "failed", message: errMsg(e) });
32484
32737
  warn("Session " + req.id + " failed: " + errMsg(e));
32485
32738
  this.idle.check();
@@ -33070,7 +33323,7 @@ process.on("unhandledRejection", (e) => warn("daemon: unexpected error (continui
33070
33323
  adoptBootSecrets();
33071
33324
  log("Session daemon started (pid " + process.pid + ").");
33072
33325
  safely("port forwards", startRelays);
33073
- safely("audit", startProcessCollector);
33326
+ safely("audit", () => startProcessCollector(() => sessions.roots()));
33074
33327
  safely("memory governor", () => governor.start());
33075
33328
  log("Memory governor: " + governor.describe());
33076
33329
  new Daemon().start();