codeam-cli 2.50.0 → 2.51.1

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 (3) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/index.js +625 -290
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -522,8 +522,8 @@ var _default = makeConfig();
522
522
  var { getConfig, ensurePluginId, addSession, removeSession, setActiveSession, getActiveSession, getActiveSessionForAgent, setDisable1mContext, clearAll, saveCliConfig, loadCliConfig } = _default;
523
523
 
524
524
  // src/commands/pair-auto.ts
525
- var fs42 = __toESM(require("fs"));
526
- var os35 = __toESM(require("os"));
525
+ var fs43 = __toESM(require("fs"));
526
+ var os36 = __toESM(require("os"));
527
527
  var path48 = __toESM(require("path"));
528
528
  var import_crypto4 = require("crypto");
529
529
 
@@ -5397,7 +5397,7 @@ function readAnonId() {
5397
5397
  }
5398
5398
  function superProperties() {
5399
5399
  return {
5400
- cliVersion: true ? "2.50.0" : "0.0.0-dev",
5400
+ cliVersion: true ? "2.51.1" : "0.0.0-dev",
5401
5401
  nodeVersion: process.version,
5402
5402
  platform: process.platform,
5403
5403
  arch: process.arch,
@@ -5578,7 +5578,7 @@ var os4 = __toESM(require("os"));
5578
5578
  // package.json
5579
5579
  var package_default = {
5580
5580
  name: "codeam-cli",
5581
- version: "2.50.0",
5581
+ version: "2.51.1",
5582
5582
  description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
5583
5583
  type: "commonjs",
5584
5584
  main: "dist/index.js",
@@ -7728,8 +7728,8 @@ function closeAllTerminals() {
7728
7728
  }
7729
7729
 
7730
7730
  // src/commands/start/handlers.ts
7731
- var fs41 = __toESM(require("fs"));
7732
- var os34 = __toESM(require("os"));
7731
+ var fs42 = __toESM(require("fs"));
7732
+ var os35 = __toESM(require("os"));
7733
7733
  var path47 = __toESM(require("path"));
7734
7734
  var import_crypto3 = require("crypto");
7735
7735
  var import_child_process20 = require("child_process");
@@ -11164,10 +11164,10 @@ function buildForPlatform(platform3) {
11164
11164
  var import_node_crypto4 = require("crypto");
11165
11165
 
11166
11166
  // src/agents/claude/resolver.ts
11167
- function buildClaudeLaunch(extraArgs = [], os41 = createOsStrategy()) {
11168
- const found = os41.findInPath("claude") ?? os41.findInPath("claude-code");
11167
+ function buildClaudeLaunch(extraArgs = [], os42 = createOsStrategy()) {
11168
+ const found = os42.findInPath("claude") ?? os42.findInPath("claude-code");
11169
11169
  if (!found) return null;
11170
- return os41.buildLaunch(found, extraArgs);
11170
+ return os42.buildLaunch(found, extraArgs);
11171
11171
  }
11172
11172
 
11173
11173
  // src/agents/claude/installer.ts
@@ -11760,8 +11760,8 @@ var ClaudeRuntimeStrategy = class {
11760
11760
  meta = getAgent("claude");
11761
11761
  mode = "interactive";
11762
11762
  os;
11763
- constructor(os41) {
11764
- this.os = os41;
11763
+ constructor(os42) {
11764
+ this.os = os42;
11765
11765
  }
11766
11766
  /**
11767
11767
  * Claude Code's react-ink TUI enables bracketed-paste mode at
@@ -12438,8 +12438,8 @@ function codexCredentialLocator() {
12438
12438
  function codexLoginLauncher() {
12439
12439
  return {
12440
12440
  async ensureInstalled() {
12441
- const os41 = createOsStrategy();
12442
- return os41.findInPath("codex") !== null;
12441
+ const os42 = createOsStrategy();
12442
+ return os42.findInPath("codex") !== null;
12443
12443
  },
12444
12444
  launch() {
12445
12445
  return (0, import_node_child_process3.spawn)("codex", ["login"], { stdio: "inherit" });
@@ -12462,8 +12462,8 @@ var CodexRuntimeStrategy = class {
12462
12462
  meta = getAgent("codex");
12463
12463
  mode = "interactive";
12464
12464
  os;
12465
- constructor(os41) {
12466
- this.os = os41;
12465
+ constructor(os42) {
12466
+ this.os = os42;
12467
12467
  }
12468
12468
  async prepareLaunch() {
12469
12469
  let binary = this.os.findInPath("codex");
@@ -12562,12 +12562,12 @@ var CodexRuntimeStrategy = class {
12562
12562
  });
12563
12563
  }
12564
12564
  };
12565
- function resolveNpm(os41) {
12566
- return os41.id === "win32" ? "npm.cmd" : "npm";
12565
+ function resolveNpm(os42) {
12566
+ return os42.id === "win32" ? "npm.cmd" : "npm";
12567
12567
  }
12568
- async function installCodexViaNpm(os41) {
12568
+ async function installCodexViaNpm(os42) {
12569
12569
  return new Promise((resolve7, reject) => {
12570
- const proc = (0, import_node_child_process4.spawn)(resolveNpm(os41), ["install", "-g", "@openai/codex"], {
12570
+ const proc = (0, import_node_child_process4.spawn)(resolveNpm(os42), ["install", "-g", "@openai/codex"], {
12571
12571
  stdio: "inherit"
12572
12572
  });
12573
12573
  proc.on("close", (code) => {
@@ -12584,16 +12584,16 @@ async function installCodexViaNpm(os41) {
12584
12584
  });
12585
12585
  });
12586
12586
  }
12587
- function augmentNpmGlobalBin(os41) {
12587
+ function augmentNpmGlobalBin(os42) {
12588
12588
  try {
12589
- const result = (0, import_node_child_process4.spawnSync)(resolveNpm(os41), ["prefix", "-g"], {
12589
+ const result = (0, import_node_child_process4.spawnSync)(resolveNpm(os42), ["prefix", "-g"], {
12590
12590
  stdio: ["ignore", "pipe", "ignore"]
12591
12591
  });
12592
12592
  if (result.status !== 0) return;
12593
12593
  const prefix = result.stdout.toString().trim();
12594
12594
  if (!prefix) return;
12595
- const binDir = os41.id === "win32" ? prefix : path22.join(prefix, "bin");
12596
- os41.augmentPath([binDir]);
12595
+ const binDir = os42.id === "win32" ? prefix : path22.join(prefix, "bin");
12596
+ os42.augmentPath([binDir]);
12597
12597
  } catch {
12598
12598
  }
12599
12599
  }
@@ -12677,9 +12677,9 @@ var import_node_child_process7 = require("child_process");
12677
12677
  // src/agents/coderabbit/installer.ts
12678
12678
  var import_node_child_process5 = require("child_process");
12679
12679
  var INSTALL_URL = "https://cli.coderabbit.ai/install.sh";
12680
- async function ensureCoderabbitInstalled(os41) {
12681
- if (os41.findInPath("coderabbit")) return true;
12682
- if (os41.id === "win32") {
12680
+ async function ensureCoderabbitInstalled(os42) {
12681
+ if (os42.findInPath("coderabbit")) return true;
12682
+ if (os42.id === "win32") {
12683
12683
  console.error(
12684
12684
  "\n \u2717 CodeRabbit on Windows requires WSL.\n Install the CLI inside your WSL distribution\n (curl -fsSL https://cli.coderabbit.ai/install.sh | sh)\n then re-run `codeam link coderabbit` from WSL.\n"
12685
12685
  );
@@ -12694,8 +12694,8 @@ async function ensureCoderabbitInstalled(os41) {
12694
12694
  proc.on("error", () => resolve7(false));
12695
12695
  });
12696
12696
  if (!ok) return false;
12697
- os41.augmentPath([`${os41.homeDir()}/.local/bin`, "/opt/homebrew/bin"]);
12698
- return os41.findInPath("coderabbit") !== null;
12697
+ os42.augmentPath([`${os42.homeDir()}/.local/bin`, "/opt/homebrew/bin"]);
12698
+ return os42.findInPath("coderabbit") !== null;
12699
12699
  }
12700
12700
 
12701
12701
  // src/agents/coderabbit/link.ts
@@ -12730,10 +12730,10 @@ function coderabbitCredentialLocator() {
12730
12730
  validate: validateNonEmptyCredential
12731
12731
  };
12732
12732
  }
12733
- function coderabbitLoginLauncher(os41) {
12733
+ function coderabbitLoginLauncher(os42) {
12734
12734
  return {
12735
12735
  async ensureInstalled() {
12736
- return ensureCoderabbitInstalled(os41);
12736
+ return ensureCoderabbitInstalled(os42);
12737
12737
  },
12738
12738
  launch() {
12739
12739
  return (0, import_node_child_process6.spawn)("coderabbit", ["login"], { stdio: "inherit" });
@@ -12779,8 +12779,8 @@ var CoderabbitRuntimeStrategy = class {
12779
12779
  meta = getAgent("coderabbit");
12780
12780
  mode = "batch";
12781
12781
  os;
12782
- constructor(os41) {
12783
- this.os = os41;
12782
+ constructor(os42) {
12783
+ this.os = os42;
12784
12784
  }
12785
12785
  getDefaultArgs() {
12786
12786
  return ["review"];
@@ -12896,10 +12896,10 @@ function cursorCredentialLocator() {
12896
12896
  validate: validateNonEmptyCredential
12897
12897
  };
12898
12898
  }
12899
- function cursorLoginLauncher(os41) {
12899
+ function cursorLoginLauncher(os42) {
12900
12900
  return {
12901
12901
  async ensureInstalled() {
12902
- if (os41.findInPath("cursor-agent")) return true;
12902
+ if (os42.findInPath("cursor-agent")) return true;
12903
12903
  console.error(
12904
12904
  "\n \u2717 cursor-agent binary not on PATH.\n Install Cursor (https://cursor.com/) and ensure the CLI\n plugin is enabled, then re-run `codeam link cursor`.\n"
12905
12905
  );
@@ -12961,8 +12961,8 @@ var CursorRuntimeStrategy = class {
12961
12961
  meta = getAgent("cursor");
12962
12962
  mode = "interactive";
12963
12963
  os;
12964
- constructor(os41) {
12965
- this.os = os41;
12964
+ constructor(os42) {
12965
+ this.os = os42;
12966
12966
  }
12967
12967
  async prepareLaunch() {
12968
12968
  const binary = this.os.findInPath("cursor-agent");
@@ -13101,10 +13101,10 @@ function aiderCredentialLocator() {
13101
13101
  validate: validateNonEmptyCredential
13102
13102
  };
13103
13103
  }
13104
- function aiderLoginLauncher(os41) {
13104
+ function aiderLoginLauncher(os42) {
13105
13105
  return {
13106
13106
  async ensureInstalled() {
13107
- if (os41.findInPath("aider")) return true;
13107
+ if (os42.findInPath("aider")) return true;
13108
13108
  console.error(
13109
13109
  "\n \u2717 aider binary not on PATH.\n Install Aider:\n pip install aider-chat\n then re-run `codeam link aider`.\n"
13110
13110
  );
@@ -13114,7 +13114,7 @@ function aiderLoginLauncher(os41) {
13114
13114
  console.error(
13115
13115
  "\n Aider has no interactive login flow.\n Set ANTHROPIC_API_KEY or OPENAI_API_KEY in your shell,\n or re-run `codeam link aider --api-key=<your-key>`.\n"
13116
13116
  );
13117
- return (0, import_node_child_process9.spawn)(os41.id === "win32" ? "cmd.exe" : "sh", os41.id === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"], {
13117
+ return (0, import_node_child_process9.spawn)(os42.id === "win32" ? "cmd.exe" : "sh", os42.id === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"], {
13118
13118
  stdio: "ignore"
13119
13119
  });
13120
13120
  }
@@ -13186,8 +13186,8 @@ var AiderRuntimeStrategy = class {
13186
13186
  meta = getAgent("aider");
13187
13187
  mode = "interactive";
13188
13188
  os;
13189
- constructor(os41) {
13190
- this.os = os41;
13189
+ constructor(os42) {
13190
+ this.os = os42;
13191
13191
  }
13192
13192
  async prepareLaunch() {
13193
13193
  const binary = this.os.findInPath("aider");
@@ -13316,8 +13316,8 @@ function geminiCredentialLocator() {
13316
13316
  function geminiLoginLauncher() {
13317
13317
  return {
13318
13318
  async ensureInstalled() {
13319
- const os41 = createOsStrategy();
13320
- return os41.findInPath("gemini") !== null;
13319
+ const os42 = createOsStrategy();
13320
+ return os42.findInPath("gemini") !== null;
13321
13321
  },
13322
13322
  launch() {
13323
13323
  return (0, import_node_child_process10.spawn)("gemini", ["auth", "login"], { stdio: "inherit" });
@@ -13350,8 +13350,8 @@ var GeminiRuntimeStrategy = class {
13350
13350
  meta = getAgent("gemini");
13351
13351
  mode = "interactive";
13352
13352
  os;
13353
- constructor(os41) {
13354
- this.os = os41;
13353
+ constructor(os42) {
13354
+ this.os = os42;
13355
13355
  }
13356
13356
  async prepareLaunch() {
13357
13357
  const binary = this.os.findInPath("gemini");
@@ -13450,18 +13450,18 @@ var GeminiRuntimeStrategy = class {
13450
13450
 
13451
13451
  // src/agents/registry.ts
13452
13452
  var runtimeBuilders = {
13453
- claude: (os41) => new ClaudeRuntimeStrategy(os41),
13454
- codex: (os41) => new CodexRuntimeStrategy(os41),
13455
- coderabbit: (os41) => new CoderabbitRuntimeStrategy(os41),
13456
- cursor: (os41) => new CursorRuntimeStrategy(os41),
13457
- aider: (os41) => new AiderRuntimeStrategy(os41),
13458
- gemini: (os41) => new GeminiRuntimeStrategy(os41)
13453
+ claude: (os42) => new ClaudeRuntimeStrategy(os42),
13454
+ codex: (os42) => new CodexRuntimeStrategy(os42),
13455
+ coderabbit: (os42) => new CoderabbitRuntimeStrategy(os42),
13456
+ cursor: (os42) => new CursorRuntimeStrategy(os42),
13457
+ aider: (os42) => new AiderRuntimeStrategy(os42),
13458
+ gemini: (os42) => new GeminiRuntimeStrategy(os42)
13459
13459
  };
13460
13460
  var deployBuilders = {
13461
13461
  claude: () => new ClaudeDeployStrategy(),
13462
13462
  codex: () => new CodexDeployStrategy()
13463
13463
  };
13464
- function createAgentStrategy(agent, os41 = createOsStrategy()) {
13464
+ function createAgentStrategy(agent, os42 = createOsStrategy()) {
13465
13465
  if (!AGENT_REGISTRY[agent]?.enabled) {
13466
13466
  throw new Error(
13467
13467
  `Agent "${agent}" is not supported in this codeam-cli version. Upgrade with 'npm i -g codeam-cli@latest'.`
@@ -13471,10 +13471,10 @@ function createAgentStrategy(agent, os41 = createOsStrategy()) {
13471
13471
  if (!build) {
13472
13472
  throw new Error(`No runtime strategy registered for agent "${agent}"`);
13473
13473
  }
13474
- return build(os41);
13474
+ return build(os42);
13475
13475
  }
13476
- function createInteractiveAgentStrategy(agent, os41 = createOsStrategy()) {
13477
- const s = createAgentStrategy(agent, os41);
13476
+ function createInteractiveAgentStrategy(agent, os42 = createOsStrategy()) {
13477
+ const s = createAgentStrategy(agent, os42);
13478
13478
  if (s.mode !== "interactive") {
13479
13479
  throw new Error(
13480
13480
  `Agent "${agent}" is a batch agent; use createAgentStrategy + .runOneShot for one-shot reviews.`
@@ -13877,19 +13877,39 @@ async function linkDryRunPreflight(ctx) {
13877
13877
  }
13878
13878
 
13879
13879
  // src/commands/host-agent.ts
13880
- var import_node_child_process14 = require("child_process");
13881
- var os28 = __toESM(require("os"));
13882
- var fs31 = __toESM(require("fs"));
13880
+ var import_node_child_process15 = require("child_process");
13881
+ var os29 = __toESM(require("os"));
13882
+ var fs32 = __toESM(require("fs"));
13883
13883
  var path36 = __toESM(require("path"));
13884
13884
 
13885
+ // src/util/restrict-to-owner.ts
13886
+ var import_node_fs5 = __toESM(require("fs"));
13887
+ var import_node_os3 = __toESM(require("os"));
13888
+ var import_node_child_process11 = require("child_process");
13889
+ function restrictToOwner(filePath) {
13890
+ try {
13891
+ if (process.platform === "win32") {
13892
+ const username = import_node_os3.default.userInfo().username;
13893
+ (0, import_node_child_process11.execFileSync)(
13894
+ "icacls",
13895
+ [filePath, "/inheritance:r", "/grant:r", `${username}:F`],
13896
+ { stdio: "ignore" }
13897
+ );
13898
+ } else {
13899
+ import_node_fs5.default.chmodSync(filePath, 384);
13900
+ }
13901
+ } catch {
13902
+ }
13903
+ }
13904
+
13885
13905
  // src/commands/host/host-client.ts
13886
- var fs26 = __toESM(require("fs"));
13887
- var os23 = __toESM(require("os"));
13906
+ var fs27 = __toESM(require("fs"));
13907
+ var os24 = __toESM(require("os"));
13888
13908
  var path31 = __toESM(require("path"));
13889
13909
  function sampleCpuTimes() {
13890
13910
  let idle = 0;
13891
13911
  let total = 0;
13892
- for (const cpu of os23.cpus()) {
13912
+ for (const cpu of os24.cpus()) {
13893
13913
  const t2 = cpu.times;
13894
13914
  idle += t2.idle;
13895
13915
  total += t2.user + t2.nice + t2.sys + t2.idle + t2.irq;
@@ -13908,8 +13928,8 @@ var MetricsCollector = class {
13908
13928
  const prev = this.prevCpu;
13909
13929
  this.prevCpu = current;
13910
13930
  if (!prev) {
13911
- const cores = os23.cpus().length || 1;
13912
- const proxy = os23.loadavg()[0] / cores * 100;
13931
+ const cores = os24.cpus().length || 1;
13932
+ const proxy = os24.loadavg()[0] / cores * 100;
13913
13933
  return Math.min(100, Math.max(0, Math.round(proxy)));
13914
13934
  }
13915
13935
  const idleDelta = current.idle - prev.idle;
@@ -13922,8 +13942,8 @@ var MetricsCollector = class {
13922
13942
  collect() {
13923
13943
  return {
13924
13944
  cpuPct: this.cpuPct(),
13925
- ramUsedMb: Math.round((os23.totalmem() - os23.freemem()) / 1048576),
13926
- ramTotalMb: Math.round(os23.totalmem() / 1048576),
13945
+ ramUsedMb: Math.round((os24.totalmem() - os24.freemem()) / 1048576),
13946
+ ramTotalMb: Math.round(os24.totalmem() / 1048576),
13927
13947
  latencyMs: this.lastLatencyMs
13928
13948
  };
13929
13949
  }
@@ -13932,19 +13952,19 @@ function apiBase() {
13932
13952
  return process.env.CODEAM_API_URL ?? resolveApiBaseUrl();
13933
13953
  }
13934
13954
  function hostIdentityPath() {
13935
- return path31.join(os23.homedir(), ".codeam", "host-agent.json");
13955
+ return path31.join(os24.homedir(), ".codeam", "host-agent.json");
13936
13956
  }
13937
13957
  function collectOsInfo() {
13938
13958
  return {
13939
- distro: os23.platform(),
13940
- arch: os23.arch(),
13941
- kernel: os23.release(),
13959
+ distro: os24.platform(),
13960
+ arch: os24.arch(),
13961
+ kernel: os24.release(),
13942
13962
  nodeVersion: process.versions.node
13943
13963
  };
13944
13964
  }
13945
13965
  function loadHostIdentity() {
13946
13966
  try {
13947
- const raw = fs26.readFileSync(hostIdentityPath(), "utf8");
13967
+ const raw = fs27.readFileSync(hostIdentityPath(), "utf8");
13948
13968
  const parsed = JSON.parse(raw);
13949
13969
  if (typeof parsed === "object" && parsed !== null && typeof parsed.hostId === "string" && typeof parsed.hostToken === "string" && typeof parsed.controlPluginId === "string") {
13950
13970
  const p2 = parsed;
@@ -13957,12 +13977,12 @@ function loadHostIdentity() {
13957
13977
  }
13958
13978
  function saveHostIdentity(identity) {
13959
13979
  const file = hostIdentityPath();
13960
- fs26.mkdirSync(path31.dirname(file), { recursive: true, mode: 448 });
13961
- fs26.writeFileSync(file, JSON.stringify(identity, null, 2), {
13980
+ fs27.mkdirSync(path31.dirname(file), { recursive: true, mode: 448 });
13981
+ fs27.writeFileSync(file, JSON.stringify(identity, null, 2), {
13962
13982
  encoding: "utf8",
13963
13983
  mode: 384
13964
13984
  });
13965
- fs26.chmodSync(file, 384);
13985
+ fs27.chmodSync(file, 384);
13966
13986
  }
13967
13987
  var HostHttpError = class extends Error {
13968
13988
  constructor(message, status2) {
@@ -13985,7 +14005,7 @@ function isHostAuthRejection(err) {
13985
14005
  }
13986
14006
  function deleteHostIdentity() {
13987
14007
  try {
13988
- fs26.rmSync(hostIdentityPath(), { force: true });
14008
+ fs27.rmSync(hostIdentityPath(), { force: true });
13989
14009
  } catch {
13990
14010
  }
13991
14011
  }
@@ -14107,17 +14127,17 @@ async function reportDeployProgress(auth, deployId, step, message, sessionId) {
14107
14127
  }
14108
14128
 
14109
14129
  // src/commands/host/workspace.ts
14110
- var fs27 = __toESM(require("fs"));
14111
- var os24 = __toESM(require("os"));
14130
+ var fs28 = __toESM(require("fs"));
14131
+ var os25 = __toESM(require("os"));
14112
14132
  var path32 = __toESM(require("path"));
14113
- var import_node_child_process11 = require("child_process");
14133
+ var import_node_child_process12 = require("child_process");
14114
14134
  var import_node_util4 = require("util");
14115
- var execFileP4 = (0, import_node_util4.promisify)(import_node_child_process11.execFile);
14135
+ var execFileP4 = (0, import_node_util4.promisify)(import_node_child_process12.execFile);
14116
14136
  function isAbsolutePathTarget(target) {
14117
14137
  return path32.isAbsolute(target);
14118
14138
  }
14119
14139
  function selfHostedWorkspaceRoot() {
14120
- return path32.join(os24.homedir(), ".codeam", "self-hosted");
14140
+ return path32.join(os25.homedir(), ".codeam", "self-hosted");
14121
14141
  }
14122
14142
  function nonInteractiveGitEnv() {
14123
14143
  return {
@@ -14171,12 +14191,9 @@ async function configureGitCredentials(dest, repoRef, cloneToken) {
14171
14191
  const gh = githubOwnerRepo(repoRef.trim());
14172
14192
  if (!gh || !cloneToken) return;
14173
14193
  const credFile = path32.join(dest, ".git", "codeam-credentials");
14174
- fs27.writeFileSync(credFile, `https://x-access-token:${cloneToken}@github.com
14194
+ fs28.writeFileSync(credFile, `https://x-access-token:${cloneToken}@github.com
14175
14195
  `, { mode: 384 });
14176
- try {
14177
- fs27.chmodSync(credFile, 384);
14178
- } catch {
14179
- }
14196
+ restrictToOwner(credFile);
14180
14197
  const env = nonInteractiveGitEnv();
14181
14198
  const git2 = (args2) => execFileP4("git", ["-C", dest, ...args2], { timeout: 3e4, env });
14182
14199
  const credFilePosix = credFile.split(path32.sep).join("/");
@@ -14215,17 +14232,17 @@ function maskToken(text, cloneToken) {
14215
14232
  }
14216
14233
  async function prepareWorkspace(repoOrPath, deployId, cloneToken) {
14217
14234
  if (isAbsolutePathTarget(repoOrPath)) {
14218
- if (!fs27.existsSync(repoOrPath)) {
14235
+ if (!fs28.existsSync(repoOrPath)) {
14219
14236
  throw new Error(`deploy target path does not exist: ${repoOrPath}`);
14220
14237
  }
14221
14238
  return repoOrPath;
14222
14239
  }
14223
14240
  const dest = path32.join(selfHostedWorkspaceRoot(), deployId);
14224
- if (fs27.existsSync(path32.join(dest, ".git"))) {
14241
+ if (fs28.existsSync(path32.join(dest, ".git"))) {
14225
14242
  if (cloneToken) await configureGitCredentials(dest, repoOrPath, cloneToken);
14226
14243
  return dest;
14227
14244
  }
14228
- fs27.mkdirSync(selfHostedWorkspaceRoot(), { recursive: true, mode: 448 });
14245
+ fs28.mkdirSync(selfHostedWorkspaceRoot(), { recursive: true, mode: 448 });
14229
14246
  const cloneUrl = repoCloneUrl(repoOrPath, cloneToken);
14230
14247
  try {
14231
14248
  await execFileP4("git", ["clone", "--depth", "1", cloneUrl, dest], {
@@ -14242,8 +14259,8 @@ async function prepareWorkspace(repoOrPath, deployId, cloneToken) {
14242
14259
  }
14243
14260
 
14244
14261
  // src/commands/host/agent-provisioning.ts
14245
- var fs28 = __toESM(require("fs"));
14246
- var os25 = __toESM(require("os"));
14262
+ var fs29 = __toESM(require("fs"));
14263
+ var os26 = __toESM(require("os"));
14247
14264
  var path33 = __toESM(require("path"));
14248
14265
  var PUBLIC_TO_INTERNAL_AGENT = {
14249
14266
  claude_code: "claude",
@@ -14259,16 +14276,16 @@ function toInternalAgentId(publicAgentId) {
14259
14276
  return PUBLIC_TO_INTERNAL_AGENT[publicAgentId] ?? null;
14260
14277
  }
14261
14278
  function ensureDir(dir) {
14262
- fs28.mkdirSync(dir, { recursive: true, mode: 448 });
14279
+ fs29.mkdirSync(dir, { recursive: true, mode: 448 });
14263
14280
  }
14264
14281
  function writeFile0600(filePath, contents) {
14265
14282
  ensureDir(path33.dirname(filePath));
14266
- fs28.writeFileSync(filePath, contents, { encoding: "utf8", mode: 384 });
14267
- fs28.chmodSync(filePath, 384);
14283
+ fs29.writeFileSync(filePath, contents, { encoding: "utf8", mode: 384 });
14284
+ restrictToOwner(filePath);
14268
14285
  }
14269
14286
  function rmIfExists(filePath) {
14270
14287
  try {
14271
- fs28.rmSync(filePath, { force: true });
14288
+ fs29.rmSync(filePath, { force: true });
14272
14289
  } catch {
14273
14290
  }
14274
14291
  }
@@ -14287,7 +14304,7 @@ var claudeProvisioner = {
14287
14304
  rmIfExists(credentialsJson);
14288
14305
  }
14289
14306
  const claudeJson = path33.join(home, ".claude.json");
14290
- if (!fs28.existsSync(claudeJson)) {
14307
+ if (!fs29.existsSync(claudeJson)) {
14291
14308
  writeFile0600(
14292
14309
  claudeJson,
14293
14310
  JSON.stringify({ hasCompletedOnboarding: true, customApiKeyResponses: { approved: [] } })
@@ -14360,7 +14377,7 @@ var UnsupportedAgentError = class extends Error {
14360
14377
  this.agentId = agentId;
14361
14378
  }
14362
14379
  };
14363
- function provisionAgentCredentials(publicAgentId, auth, homeDir2 = os25.homedir()) {
14380
+ function provisionAgentCredentials(publicAgentId, auth, homeDir2 = os26.homedir()) {
14364
14381
  const internal = toInternalAgentId(publicAgentId);
14365
14382
  if (!internal) throw new UnsupportedAgentError(publicAgentId);
14366
14383
  const provisioner = PROVISIONERS[internal];
@@ -14369,12 +14386,12 @@ function provisionAgentCredentials(publicAgentId, auth, homeDir2 = os25.homedir(
14369
14386
  }
14370
14387
 
14371
14388
  // src/commands/host/git-tooling.ts
14372
- var import_node_child_process12 = require("child_process");
14373
- var fs29 = __toESM(require("fs"));
14374
- var os26 = __toESM(require("os"));
14389
+ var import_node_child_process13 = require("child_process");
14390
+ var fs30 = __toESM(require("fs"));
14391
+ var os27 = __toESM(require("os"));
14375
14392
  var path34 = __toESM(require("path"));
14376
14393
  function codeamBinDir() {
14377
- return process.env.CODEAM_BIN_DIR ?? path34.join(os26.homedir(), ".codeam", "bin");
14394
+ return process.env.CODEAM_BIN_DIR ?? path34.join(os27.homedir(), ".codeam", "bin");
14378
14395
  }
14379
14396
  var FALLBACK_GH_VERSION = "2.62.0";
14380
14397
  var RELEASE_API = "https://api.github.com/repos/cli/cli/releases/latest";
@@ -14406,7 +14423,7 @@ async function download(url, dest) {
14406
14423
  const res = await fetch(url, { headers: { "User-Agent": "codeam-cli" } });
14407
14424
  if (!res.ok || !res.body) return false;
14408
14425
  const buf = Buffer.from(await res.arrayBuffer());
14409
- fs29.writeFileSync(dest, buf);
14426
+ fs30.writeFileSync(dest, buf);
14410
14427
  return true;
14411
14428
  } catch {
14412
14429
  return false;
@@ -14429,7 +14446,7 @@ async function ensureGhCli(runner, token, deps = {}) {
14429
14446
  const version3 = await resolveVersionFn(token);
14430
14447
  const asset = `gh_${version3}_${osToken}_${arch2}`;
14431
14448
  const url = `https://github.com/cli/cli/releases/download/v${version3}/${asset}.${ext}`;
14432
- const tmpRoot = fs29.mkdtempSync(path34.join(os26.tmpdir(), "codeam-gh-"));
14449
+ const tmpRoot = fs30.mkdtempSync(path34.join(os27.tmpdir(), "codeam-gh-"));
14433
14450
  const archive = path34.join(tmpRoot, `${asset}.${ext}`);
14434
14451
  if (!await downloadFn(url, archive)) {
14435
14452
  log.warn("host-agent", "gh download failed \u2014 skipping (git pull/push still work via the credential helper)");
@@ -14441,15 +14458,15 @@ async function ensureGhCli(runner, token, deps = {}) {
14441
14458
  return null;
14442
14459
  }
14443
14460
  const extractedBin = path34.join(tmpRoot, asset, "bin", binaryName);
14444
- if (!fs29.existsSync(extractedBin)) {
14461
+ if (!fs30.existsSync(extractedBin)) {
14445
14462
  log.warn("host-agent", "gh binary not found in the extracted archive \u2014 skipping");
14446
14463
  return null;
14447
14464
  }
14448
14465
  const binDir = codeamBinDir();
14449
- fs29.mkdirSync(binDir, { recursive: true });
14466
+ fs30.mkdirSync(binDir, { recursive: true });
14450
14467
  const target = path34.join(binDir, binaryName);
14451
- fs29.copyFileSync(extractedBin, target);
14452
- fs29.chmodSync(target, 493);
14468
+ fs30.copyFileSync(extractedBin, target);
14469
+ fs30.chmodSync(target, 493);
14453
14470
  log.info("host-agent", `gh installed to ${target} (v${version3})`);
14454
14471
  return target;
14455
14472
  } catch (e) {
@@ -14483,7 +14500,7 @@ var defaultGitToolingRunner = {
14483
14500
  which(cmd) {
14484
14501
  try {
14485
14502
  const probe = process.platform === "win32" ? "where" : "which";
14486
- (0, import_node_child_process12.execFileSync)(probe, [cmd], { stdio: "ignore" });
14503
+ (0, import_node_child_process13.execFileSync)(probe, [cmd], { stdio: "ignore" });
14487
14504
  return true;
14488
14505
  } catch {
14489
14506
  return false;
@@ -14491,7 +14508,7 @@ var defaultGitToolingRunner = {
14491
14508
  },
14492
14509
  run(cmd, args2, opts = {}) {
14493
14510
  return new Promise((resolve7) => {
14494
- const child = (0, import_node_child_process12.spawn)(cmd, args2, {
14511
+ const child = (0, import_node_child_process13.spawn)(cmd, args2, {
14495
14512
  stdio: [opts.input !== void 0 ? "pipe" : "ignore", "ignore", "pipe"]
14496
14513
  });
14497
14514
  let stderr = "";
@@ -14578,6 +14595,14 @@ function mapStatsToSavings(stats, prev, inputPricePerMillion = DEFAULT_INPUT_PRI
14578
14595
  };
14579
14596
  return { next, delta };
14580
14597
  }
14598
+ function defaultGetBudgetEnv() {
14599
+ const raw = process.env["HEADROOM_BUDGET"];
14600
+ if (!raw) return null;
14601
+ const budgetUsd = Number(raw);
14602
+ if (!isFinite(budgetUsd) || budgetUsd <= 0) return null;
14603
+ const budgetPeriod = process.env["HEADROOM_BUDGET_PERIOD"] ?? "daily";
14604
+ return { budgetUsd, budgetPeriod };
14605
+ }
14581
14606
  var HeadroomStatsReporter = class {
14582
14607
  constructor(deps) {
14583
14608
  this.deps = deps;
@@ -14591,14 +14616,22 @@ var HeadroomStatsReporter = class {
14591
14616
  }
14592
14617
  async tick() {
14593
14618
  try {
14619
+ const stats = await this.deps.fetchStats();
14594
14620
  const { next, delta } = mapStatsToSavings(
14595
- await this.deps.fetchStats(),
14621
+ stats,
14596
14622
  this.prev,
14597
14623
  this.deps.inputPricePerMillionUsd ?? DEFAULT_INPUT_PRICE_PER_MILLION
14598
14624
  );
14599
14625
  this.prev = next;
14600
14626
  if (delta.compressionTokens > 0 || delta.compressionSavingsUsd > 0 || delta.rawTokensEst > 0 || delta.cacheReadTokens > 0 || delta.cacheSavingsUsd > 0) {
14601
- await this.deps.postSavings(delta);
14627
+ const getBudgetEnv = this.deps.getBudgetEnv ?? defaultGetBudgetEnv;
14628
+ const budgetEnv = getBudgetEnv();
14629
+ const budget = budgetEnv ? {
14630
+ periodSpendUsd: stats.cost?.cost_with_headroom_usd ?? 0,
14631
+ budgetUsd: budgetEnv.budgetUsd,
14632
+ budgetPeriod: budgetEnv.budgetPeriod
14633
+ } : void 0;
14634
+ await this.deps.postSavings(delta, budget);
14602
14635
  }
14603
14636
  } catch {
14604
14637
  }
@@ -14611,24 +14644,32 @@ var HeadroomStatsReporter = class {
14611
14644
  }
14612
14645
  };
14613
14646
 
14647
+ // src/services/headroom/budget-args.ts
14648
+ function buildBudgetProxyArgs(env) {
14649
+ const budget = env["HEADROOM_BUDGET"];
14650
+ if (!budget) return [];
14651
+ const period = env["HEADROOM_BUDGET_PERIOD"] ?? "daily";
14652
+ return ["--budget", budget, "--budget-period", period];
14653
+ }
14654
+
14614
14655
  // src/lib/updateNotifier.ts
14615
- var fs30 = __toESM(require("fs"));
14616
- var os27 = __toESM(require("os"));
14656
+ var fs31 = __toESM(require("fs"));
14657
+ var os28 = __toESM(require("os"));
14617
14658
  var path35 = __toESM(require("path"));
14618
14659
  var https6 = __toESM(require("https"));
14619
- var import_node_child_process13 = require("child_process");
14660
+ var import_node_child_process14 = require("child_process");
14620
14661
  var import_picocolors3 = __toESM(require("picocolors"));
14621
14662
  var PKG_NAME = "codeam-cli";
14622
14663
  var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
14623
14664
  var TTL_MS = 24 * 60 * 60 * 1e3;
14624
14665
  var REQUEST_TIMEOUT_MS = 1500;
14625
14666
  function cachePath() {
14626
- const dir = path35.join(os27.homedir(), ".codeam");
14667
+ const dir = path35.join(os28.homedir(), ".codeam");
14627
14668
  return path35.join(dir, "update-check.json");
14628
14669
  }
14629
14670
  function readCache() {
14630
14671
  try {
14631
- const raw = fs30.readFileSync(cachePath(), "utf8");
14672
+ const raw = fs31.readFileSync(cachePath(), "utf8");
14632
14673
  const parsed = JSON.parse(raw);
14633
14674
  if (typeof parsed.fetchedAt !== "number" || typeof parsed.latest !== "string") return null;
14634
14675
  return parsed;
@@ -14639,10 +14680,10 @@ function readCache() {
14639
14680
  function writeCache(cache) {
14640
14681
  try {
14641
14682
  const file = cachePath();
14642
- fs30.mkdirSync(path35.dirname(file), { recursive: true });
14683
+ fs31.mkdirSync(path35.dirname(file), { recursive: true });
14643
14684
  const tmp = `${file}.${process.pid}.tmp`;
14644
- fs30.writeFileSync(tmp, JSON.stringify(cache));
14645
- fs30.renameSync(tmp, file);
14685
+ fs31.writeFileSync(tmp, JSON.stringify(cache));
14686
+ fs31.renameSync(tmp, file);
14646
14687
  } catch {
14647
14688
  }
14648
14689
  }
@@ -14710,14 +14751,14 @@ function notifyIfStale(currentVersion, latest) {
14710
14751
  }
14711
14752
  function isLinkedInstall() {
14712
14753
  try {
14713
- const root = (0, import_node_child_process13.execSync)("npm root -g", {
14754
+ const root = (0, import_node_child_process14.execSync)("npm root -g", {
14714
14755
  encoding: "utf8",
14715
14756
  stdio: ["ignore", "pipe", "ignore"],
14716
14757
  timeout: 2e3
14717
14758
  }).trim();
14718
14759
  if (!root) return false;
14719
14760
  const pkgPath = path35.join(root, PKG_NAME);
14720
- return fs30.lstatSync(pkgPath).isSymbolicLink();
14761
+ return fs31.lstatSync(pkgPath).isSymbolicLink();
14721
14762
  } catch {
14722
14763
  return false;
14723
14764
  }
@@ -14738,7 +14779,7 @@ function maybeAutoUpdate(currentVersion, latest) {
14738
14779
 
14739
14780
  `
14740
14781
  );
14741
- const install = (0, import_node_child_process13.spawnSync)("npm", ["install", "-g", `${PKG_NAME}@latest`], {
14782
+ const install = (0, import_node_child_process14.spawnSync)("npm", ["install", "-g", `${PKG_NAME}@latest`], {
14742
14783
  stdio: "inherit",
14743
14784
  env: process.env
14744
14785
  });
@@ -14753,13 +14794,13 @@ function maybeAutoUpdate(currentVersion, latest) {
14753
14794
  return;
14754
14795
  }
14755
14796
  try {
14756
- fs30.unlinkSync(cachePath());
14797
+ fs31.unlinkSync(cachePath());
14757
14798
  } catch {
14758
14799
  }
14759
14800
  process.stderr.write(` ${import_picocolors3.default.green("\u2713")} Updated. Resuming session...
14760
14801
 
14761
14802
  `);
14762
- const child = (0, import_node_child_process13.spawnSync)("codeam", process.argv.slice(2), {
14803
+ const child = (0, import_node_child_process14.spawnSync)("codeam", process.argv.slice(2), {
14763
14804
  stdio: "inherit",
14764
14805
  env: process.env
14765
14806
  });
@@ -14769,7 +14810,7 @@ async function autoUpgradeBeforeCriticalCommand() {
14769
14810
  if (process.env.NODE_ENV === "test") return;
14770
14811
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
14771
14812
  if (process.env.CI) return;
14772
- const current = true ? "2.50.0" : null;
14813
+ const current = true ? "2.51.1" : null;
14773
14814
  if (!current) return;
14774
14815
  const cache = readCache();
14775
14816
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -14786,7 +14827,7 @@ function checkForUpdates() {
14786
14827
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
14787
14828
  if (process.env.CI) return;
14788
14829
  if (!process.stdout.isTTY) return;
14789
- const current = true ? "2.50.0" : null;
14830
+ const current = true ? "2.51.1" : null;
14790
14831
  if (!current) return;
14791
14832
  const cache = readCache();
14792
14833
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -14823,7 +14864,7 @@ function maybeStartHeadroomReporter(ctx) {
14823
14864
  const res = await fetch("http://localhost:8787/stats");
14824
14865
  return res.json();
14825
14866
  },
14826
- postSavings: async (delta) => {
14867
+ postSavings: async (delta, budget) => {
14827
14868
  await fetch(ingestUrl, {
14828
14869
  method: "POST",
14829
14870
  headers: {
@@ -14834,7 +14875,12 @@ function maybeStartHeadroomReporter(ctx) {
14834
14875
  sessionId: ctx.sessionId,
14835
14876
  pluginId: ctx.pluginId,
14836
14877
  agentId: process.env["HEADROOM_AGENT"] ?? "claude",
14837
- savings: delta
14878
+ savings: delta,
14879
+ ...budget ? {
14880
+ periodSpendUsd: budget.periodSpendUsd,
14881
+ budgetUsd: budget.budgetUsd,
14882
+ budgetPeriod: budget.budgetPeriod
14883
+ } : {}
14838
14884
  })
14839
14885
  });
14840
14886
  }
@@ -14853,8 +14899,8 @@ function maybeResumeLocalHeadroomReporter(ctx) {
14853
14899
  if (process.env["HEADROOM_ENABLED"] === "1") return null;
14854
14900
  try {
14855
14901
  const file = headroomConfigPath();
14856
- if (!fs31.existsSync(file)) return null;
14857
- const cfg = JSON.parse(fs31.readFileSync(file, "utf8"));
14902
+ if (!fs32.existsSync(file)) return null;
14903
+ const cfg = JSON.parse(fs32.readFileSync(file, "utf8"));
14858
14904
  if (!cfg?.enabled) return null;
14859
14905
  const agent = cfg.agent ?? "claude";
14860
14906
  const ingestUrl = `${resolveApiBaseUrl()}/api/sessions/${ctx.sessionId}/headroom-savings`;
@@ -14867,7 +14913,7 @@ function maybeResumeLocalHeadroomReporter(ctx) {
14867
14913
  // Body MUST match HeadroomSavingsDto + PluginAuthGuard (sessionId +
14868
14914
  // pluginId in the body) — same shape as the codespace reporter above and
14869
14915
  // the on-demand `startReporter` in handlers.ts.
14870
- postSavings: async (delta) => {
14916
+ postSavings: async (delta, budget) => {
14871
14917
  await fetch(ingestUrl, {
14872
14918
  method: "POST",
14873
14919
  headers: {
@@ -14878,7 +14924,12 @@ function maybeResumeLocalHeadroomReporter(ctx) {
14878
14924
  sessionId: ctx.sessionId,
14879
14925
  pluginId: ctx.pluginId,
14880
14926
  agentId: agent,
14881
- savings: delta
14927
+ savings: delta,
14928
+ ...budget ? {
14929
+ periodSpendUsd: budget.periodSpendUsd,
14930
+ budgetUsd: budget.budgetUsd,
14931
+ budgetPeriod: budget.budgetPeriod
14932
+ } : {}
14882
14933
  })
14883
14934
  });
14884
14935
  }
@@ -14940,7 +14991,7 @@ var HEADROOM_MIN_FREE_DISK_BYTES = 2 * 1024 * 1024 * 1024;
14940
14991
  var defaultHeadroomRunner = {
14941
14992
  which(cmd) {
14942
14993
  try {
14943
- (0, import_node_child_process14.execFileSync)("which", [cmd], { stdio: "ignore" });
14994
+ (0, import_node_child_process15.execFileSync)("which", [cmd], { stdio: "ignore" });
14944
14995
  return true;
14945
14996
  } catch {
14946
14997
  return false;
@@ -14949,7 +15000,7 @@ var defaultHeadroomRunner = {
14949
15000
  run(cmd, args2, opts = {}) {
14950
15001
  return new Promise((resolve7) => {
14951
15002
  const spawnEnv = opts.env ?? process.env;
14952
- const child = (0, import_node_child_process14.spawn)(cmd, args2, { stdio: ["ignore", "pipe", "pipe"], env: spawnEnv });
15003
+ const child = (0, import_node_child_process15.spawn)(cmd, args2, { stdio: ["ignore", "pipe", "pipe"], env: spawnEnv });
14953
15004
  let stderrBuf = "";
14954
15005
  let stdoutBuf = "";
14955
15006
  let settled = false;
@@ -15113,15 +15164,16 @@ function isHeadroomSupportedAgent(agentId) {
15113
15164
  return n.startsWith("claude") || n.startsWith("codex") || n.startsWith("copilot");
15114
15165
  }
15115
15166
  function headroomConfigPath() {
15116
- return path36.join(os28.homedir(), ".codeam", "headroom-config.json");
15167
+ return path36.join(os29.homedir(), ".codeam", "headroom-config.json");
15117
15168
  }
15118
15169
  function persistHeadroomConfig(config) {
15119
15170
  try {
15120
15171
  const file = headroomConfigPath();
15121
- fs31.mkdirSync(path36.dirname(file), { recursive: true, mode: 448 });
15172
+ fs32.mkdirSync(path36.dirname(file), { recursive: true, mode: 448 });
15122
15173
  const tmp = `${file}.tmp-${process.pid}`;
15123
- fs31.writeFileSync(tmp, JSON.stringify(config, null, 2), { encoding: "utf8", mode: 384 });
15124
- fs31.renameSync(tmp, file);
15174
+ fs32.writeFileSync(tmp, JSON.stringify(config, null, 2), { encoding: "utf8", mode: 384 });
15175
+ fs32.renameSync(tmp, file);
15176
+ restrictToOwner(file);
15125
15177
  } catch (err) {
15126
15178
  log.warn(
15127
15179
  "host-agent",
@@ -15130,7 +15182,7 @@ function persistHeadroomConfig(config) {
15130
15182
  }
15131
15183
  }
15132
15184
  function agentSettingsPath(kind) {
15133
- const home = os28.homedir();
15185
+ const home = os29.homedir();
15134
15186
  if (kind === "claude") return path36.join(home, ".claude", "settings.json");
15135
15187
  if (kind === "codex") return path36.join(home, ".codex", "auth.json");
15136
15188
  if (kind === "copilot") return path36.join(home, ".config", "github-copilot", "hosts.json");
@@ -15140,11 +15192,11 @@ function backupAgentHeadroomConfig(kind) {
15140
15192
  const src = agentSettingsPath(kind);
15141
15193
  if (!src) return;
15142
15194
  try {
15143
- if (!fs31.existsSync(src)) return;
15144
- const dest = path36.join(os28.homedir(), ".codeam", `headroom-backup-${kind}.json`);
15145
- fs31.mkdirSync(path36.dirname(dest), { recursive: true, mode: 448 });
15146
- fs31.copyFileSync(src, dest);
15147
- fs31.chmodSync(dest, 384);
15195
+ if (!fs32.existsSync(src)) return;
15196
+ const dest = path36.join(os29.homedir(), ".codeam", `headroom-backup-${kind}.json`);
15197
+ fs32.mkdirSync(path36.dirname(dest), { recursive: true, mode: 448 });
15198
+ fs32.copyFileSync(src, dest);
15199
+ fs32.chmodSync(dest, 384);
15148
15200
  log.info("host-agent", `headroom config backup: ${src} \u2192 ${dest}`);
15149
15201
  } catch (err) {
15150
15202
  log.warn(
@@ -15156,12 +15208,12 @@ function backupAgentHeadroomConfig(kind) {
15156
15208
  function restoreAgentHeadroomConfig(kind) {
15157
15209
  const dest = agentSettingsPath(kind);
15158
15210
  if (!dest) return false;
15159
- const src = path36.join(os28.homedir(), ".codeam", `headroom-backup-${kind}.json`);
15160
- if (!fs31.existsSync(src)) return false;
15211
+ const src = path36.join(os29.homedir(), ".codeam", `headroom-backup-${kind}.json`);
15212
+ if (!fs32.existsSync(src)) return false;
15161
15213
  try {
15162
- fs31.mkdirSync(path36.dirname(dest), { recursive: true, mode: 448 });
15163
- fs31.copyFileSync(src, dest);
15164
- fs31.chmodSync(dest, 384);
15214
+ fs32.mkdirSync(path36.dirname(dest), { recursive: true, mode: 448 });
15215
+ fs32.copyFileSync(src, dest);
15216
+ fs32.chmodSync(dest, 384);
15165
15217
  log.info("host-agent", `headroom config restored: ${src} \u2192 ${dest}`);
15166
15218
  return true;
15167
15219
  } catch (err) {
@@ -15174,16 +15226,21 @@ function restoreAgentHeadroomConfig(kind) {
15174
15226
  }
15175
15227
  function readHeadroomChildEnv() {
15176
15228
  try {
15177
- const raw = fs31.readFileSync(headroomConfigPath(), "utf8");
15229
+ const raw = fs32.readFileSync(headroomConfigPath(), "utf8");
15178
15230
  const parsed = JSON.parse(raw);
15179
15231
  if (typeof parsed !== "object" || parsed === null) return {};
15180
15232
  const o = parsed;
15181
15233
  if (o.enabled === true && typeof o.agent === "string" && o.agent.length > 0 && typeof o.ingestUrl === "string" && o.ingestUrl.length > 0) {
15182
- return {
15234
+ const env = {
15183
15235
  HEADROOM_ENABLED: "1",
15184
15236
  HEADROOM_AGENT: o.agent,
15185
15237
  HEADROOM_SAVINGS_INGEST_URL: o.ingestUrl
15186
15238
  };
15239
+ if (o.budgetEnabled === true && typeof o.budgetUsd === "number") {
15240
+ env["HEADROOM_BUDGET"] = String(o.budgetUsd);
15241
+ env["HEADROOM_BUDGET_PERIOD"] = typeof o.budgetPeriod === "string" ? o.budgetPeriod : "daily";
15242
+ }
15243
+ return env;
15187
15244
  }
15188
15245
  return {};
15189
15246
  } catch {
@@ -15210,21 +15267,21 @@ function bundledClaudeBinDir() {
15210
15267
  const atAnthropic = path36.join(nm, "@anthropic-ai");
15211
15268
  let entries;
15212
15269
  try {
15213
- entries = fs31.readdirSync(atAnthropic);
15270
+ entries = fs32.readdirSync(atAnthropic);
15214
15271
  } catch {
15215
15272
  continue;
15216
15273
  }
15217
15274
  for (const entry of entries) {
15218
15275
  if (!entry.startsWith("claude-agent-sdk-")) continue;
15219
15276
  const bin = path36.join(atAnthropic, entry, "claude");
15220
- if (fs31.existsSync(bin)) return path36.dirname(bin);
15277
+ if (fs32.existsSync(bin)) return path36.dirname(bin);
15221
15278
  }
15222
15279
  }
15223
15280
  return null;
15224
15281
  }
15225
15282
  async function getFreeDiskBytes(dir) {
15226
15283
  try {
15227
- const s = await fs31.promises.statfs(dir);
15284
+ const s = await fs32.promises.statfs(dir);
15228
15285
  return s.bsize * s.bavail;
15229
15286
  } catch {
15230
15287
  return null;
@@ -15439,11 +15496,16 @@ async function setupHeadroomForSelfHosted(agent, runner = defaultHeadroomRunner,
15439
15496
  }
15440
15497
  onProgress("proxy");
15441
15498
  try {
15442
- const proxy = (0, import_node_child_process14.spawn)("headroom", ["proxy", "--port", "8787"], {
15443
- stdio: "ignore",
15444
- detached: true,
15445
- env: { ...process.env, HEADROOM_KOMPRESS_BACKEND: "onnx_cpu" }
15446
- });
15499
+ const proxyEnv = { ...process.env, HEADROOM_KOMPRESS_BACKEND: "onnx_cpu" };
15500
+ const proxy = (0, import_node_child_process15.spawn)(
15501
+ "headroom",
15502
+ ["proxy", "--port", "8787", ...buildBudgetProxyArgs(proxyEnv)],
15503
+ {
15504
+ stdio: "ignore",
15505
+ detached: true,
15506
+ env: proxyEnv
15507
+ }
15508
+ );
15447
15509
  proxy.once("error", (e) => {
15448
15510
  log.warn(
15449
15511
  "host-agent",
@@ -15460,18 +15522,18 @@ async function setupHeadroomForSelfHosted(agent, runner = defaultHeadroomRunner,
15460
15522
  onProgress("ready");
15461
15523
  return true;
15462
15524
  }
15463
- var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process14.spawn)(process.execPath, [process.argv[1], "pair-auto", ...args2], {
15525
+ var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process15.spawn)(process.execPath, [process.argv[1], "pair-auto", ...args2], {
15464
15526
  cwd,
15465
15527
  env: { ...process.env, ...env },
15466
15528
  stdio: ["ignore", "pipe", "pipe"],
15467
15529
  detached: false
15468
15530
  });
15469
15531
  function currentCliVersion() {
15470
- return true ? "2.50.0" : null;
15532
+ return true ? "2.51.1" : null;
15471
15533
  }
15472
15534
  function runCmd(cmd, args2, timeoutMs) {
15473
15535
  return new Promise((resolve7) => {
15474
- (0, import_node_child_process14.execFile)(cmd, args2, { timeout: timeoutMs }, (err, stdout, stderr) => {
15536
+ (0, import_node_child_process15.execFile)(cmd, args2, { timeout: timeoutMs }, (err, stdout, stderr) => {
15475
15537
  const code = err && typeof err.code === "number" ? err.code : err ? null : 0;
15476
15538
  resolve7({ code, stdout: stdout ?? "", stderr: stderr ?? "" });
15477
15539
  });
@@ -15542,20 +15604,20 @@ var defaultOnUpdated = (version3) => {
15542
15604
  };
15543
15605
  var defaultDisableService = () => {
15544
15606
  try {
15545
- (0, import_node_child_process14.execFileSync)("systemctl", ["disable", "--now", "codeam-host-agent"], { stdio: "ignore" });
15607
+ (0, import_node_child_process15.execFileSync)("systemctl", ["disable", "--now", "codeam-host-agent"], { stdio: "ignore" });
15546
15608
  } catch {
15547
15609
  }
15548
15610
  };
15549
15611
  var defaultTeardownHeadroom = () => {
15550
15612
  try {
15551
- const kind = JSON.parse(fs31.readFileSync(headroomConfigPath(), "utf8")).agent;
15613
+ const kind = JSON.parse(fs32.readFileSync(headroomConfigPath(), "utf8")).agent;
15552
15614
  if (kind) {
15553
- (0, import_node_child_process14.execFileSync)("headroom", ["unwrap", kind], { stdio: "ignore", timeout: 15e3 });
15615
+ (0, import_node_child_process15.execFileSync)("headroom", ["unwrap", kind], { stdio: "ignore", timeout: 15e3 });
15554
15616
  }
15555
15617
  } catch {
15556
15618
  }
15557
15619
  try {
15558
- (0, import_node_child_process14.execFileSync)("pkill", ["-TERM", "-f", "headroom.*proxy"], { stdio: "ignore" });
15620
+ (0, import_node_child_process15.execFileSync)("pkill", ["-TERM", "-f", "headroom.*proxy"], { stdio: "ignore" });
15559
15621
  } catch {
15560
15622
  }
15561
15623
  persistHeadroomConfig({ enabled: false });
@@ -15862,9 +15924,9 @@ var HostAgentSupervisor = class {
15862
15924
  API_TIMEOUT_MS: "3000000",
15863
15925
  CODEAM_AUTO_TOKEN: payload.autoPairToken
15864
15926
  };
15865
- const houseConfigDir = path36.join(os28.homedir(), ".codeam", "house-claude");
15927
+ const houseConfigDir = path36.join(os29.homedir(), ".codeam", "house-claude");
15866
15928
  try {
15867
- fs31.mkdirSync(houseConfigDir, { recursive: true, mode: 448 });
15929
+ fs32.mkdirSync(houseConfigDir, { recursive: true, mode: 448 });
15868
15930
  } catch {
15869
15931
  }
15870
15932
  childEnv.CLAUDE_CONFIG_DIR = houseConfigDir;
@@ -15882,7 +15944,7 @@ var HostAgentSupervisor = class {
15882
15944
  report("installing", "installing agent CLI");
15883
15945
  await this.runAgentInstall(payload.agentInstallScript);
15884
15946
  }
15885
- const home = process.env.HOME || os28.homedir();
15947
+ const home = process.env.HOME || os29.homedir();
15886
15948
  childEnv.PATH = `${home}/.local/bin:${process.env.PATH ?? ""}`;
15887
15949
  if (payload.cloneToken) {
15888
15950
  try {
@@ -15905,7 +15967,7 @@ var HostAgentSupervisor = class {
15905
15967
  }
15906
15968
  if (payload.headroomEnabled && payload.headroomAgent && payload.headroomSavingsIngestUrl && isHeadroomSupportedAgent(payload.headroomAgent)) {
15907
15969
  report("headroom", "setting up Headroom proxy");
15908
- const freeBytes = await this.getFreeDisk(os28.homedir());
15970
+ const freeBytes = await this.getFreeDisk(os29.homedir());
15909
15971
  const alreadyInstalled = this.isHeadroomInstalled();
15910
15972
  if (!alreadyInstalled && freeBytes !== null && freeBytes < HEADROOM_MIN_FREE_DISK_BYTES) {
15911
15973
  const freeGb = (freeBytes / 1e9).toFixed(1);
@@ -16027,8 +16089,8 @@ var HostAgentSupervisor = class {
16027
16089
  */
16028
16090
  runAgentInstall(script) {
16029
16091
  return new Promise((resolve7) => {
16030
- const home = process.env.HOME || os28.homedir();
16031
- const child = (0, import_node_child_process14.spawn)("sh", ["-c", script], {
16092
+ const home = process.env.HOME || os29.homedir();
16093
+ const child = (0, import_node_child_process15.spawn)("sh", ["-c", script], {
16032
16094
  env: { ...process.env, HOME: home },
16033
16095
  stdio: ["ignore", "pipe", "pipe"]
16034
16096
  });
@@ -16831,8 +16893,8 @@ function activePreviewSessionIds() {
16831
16893
 
16832
16894
  // src/beads/bd-adapter.ts
16833
16895
  var import_child_process15 = require("child_process");
16834
- var fs36 = __toESM(require("fs"));
16835
- var os30 = __toESM(require("os"));
16896
+ var fs37 = __toESM(require("fs"));
16897
+ var os31 = __toESM(require("os"));
16836
16898
  var path41 = __toESM(require("path"));
16837
16899
  var BD_PACKAGE = "@beads/bd";
16838
16900
  function resolveBundledBdBinary() {
@@ -16849,7 +16911,7 @@ function _defaultResolveBundled() {
16849
16911
  const binaryName = process.platform === "win32" ? "bd.exe" : "bd";
16850
16912
  const binaryPath = path41.join(binDir, binaryName);
16851
16913
  try {
16852
- fs36.accessSync(binaryPath, fs36.constants.F_OK);
16914
+ fs37.accessSync(binaryPath, fs37.constants.F_OK);
16853
16915
  return binaryPath;
16854
16916
  } catch {
16855
16917
  return null;
@@ -16865,7 +16927,7 @@ function _defaultResolveOnPath() {
16865
16927
  for (const candidate of candidates) {
16866
16928
  const full = path41.join(dir, candidate);
16867
16929
  try {
16868
- fs36.accessSync(full, fs36.constants.F_OK);
16930
+ fs37.accessSync(full, fs37.constants.F_OK);
16869
16931
  return full;
16870
16932
  } catch {
16871
16933
  }
@@ -16950,7 +17012,7 @@ var BdAdapter = class {
16950
17012
  const env = { ...process.env };
16951
17013
  if (!env.HOME) {
16952
17014
  try {
16953
- const home = os30.homedir();
17015
+ const home = os31.homedir();
16954
17016
  if (home) env.HOME = home;
16955
17017
  } catch {
16956
17018
  }
@@ -17039,8 +17101,8 @@ function coerceIssue(row, projectKey) {
17039
17101
 
17040
17102
  // src/beads/provisioner.ts
17041
17103
  var import_child_process19 = require("child_process");
17042
- var fs39 = __toESM(require("fs"));
17043
- var os32 = __toESM(require("os"));
17104
+ var fs40 = __toESM(require("fs"));
17105
+ var os33 = __toESM(require("os"));
17044
17106
  var path44 = __toESM(require("path"));
17045
17107
 
17046
17108
  // src/beads/install-bd.ts
@@ -17106,8 +17168,8 @@ async function installBd(platform3 = process.platform) {
17106
17168
 
17107
17169
  // src/beads/install-dolt.ts
17108
17170
  var import_child_process17 = require("child_process");
17109
- var fs37 = __toESM(require("fs"));
17110
- var os31 = __toESM(require("os"));
17171
+ var fs38 = __toESM(require("fs"));
17172
+ var os32 = __toESM(require("os"));
17111
17173
  var path42 = __toESM(require("path"));
17112
17174
  var DOLT_INSTALL_SH_URL = "https://github.com/dolthub/dolt/releases/latest/download/install.sh";
17113
17175
  var DOLT_MSI_URL = "https://github.com/dolthub/dolt/releases/latest/download/dolt-windows-amd64.msi";
@@ -17148,11 +17210,11 @@ function resolveDoltInstallStrategy(platform3) {
17148
17210
  }
17149
17211
  var DOLT_RELEASE_BASE = "https://github.com/dolthub/dolt/releases/latest/download";
17150
17212
  function doltPlatformTuple(platform3, arch2) {
17151
- const os41 = platform3 === "win32" ? "windows" : platform3 === "darwin" ? "darwin" : "linux";
17213
+ const os42 = platform3 === "win32" ? "windows" : platform3 === "darwin" ? "darwin" : "linux";
17152
17214
  const a = arch2 === "x64" ? "amd64" : arch2 === "arm64" ? "arm64" : null;
17153
17215
  if (!a) return null;
17154
- if (os41 === "windows" && a !== "amd64") return null;
17155
- return `${os41}-${a}`;
17216
+ if (os42 === "windows" && a !== "amd64") return null;
17217
+ return `${os42}-${a}`;
17156
17218
  }
17157
17219
  function resolveDoltTarballStrategy(targetDir, platform3, arch2) {
17158
17220
  const tuple = doltPlatformTuple(platform3, arch2);
@@ -17197,14 +17259,14 @@ async function installDoltToDir(targetDir, platform3 = process.platform, arch2 =
17197
17259
  return result;
17198
17260
  }
17199
17261
  var _doltPathSeam = {
17200
- homedir: () => os31.homedir(),
17262
+ homedir: () => os32.homedir(),
17201
17263
  getPath: () => process.env.PATH ?? "",
17202
17264
  setPath: (p2) => {
17203
17265
  process.env.PATH = p2;
17204
17266
  },
17205
17267
  exists: (p2) => {
17206
17268
  try {
17207
- fs37.accessSync(p2, fs37.constants.F_OK);
17269
+ fs38.accessSync(p2, fs38.constants.F_OK);
17208
17270
  return true;
17209
17271
  } catch {
17210
17272
  return false;
@@ -17367,7 +17429,7 @@ async function ensureSharedServer(adapter, options = {}) {
17367
17429
  // src/beads/project-key.ts
17368
17430
  var import_child_process18 = require("child_process");
17369
17431
  var crypto2 = __toESM(require("crypto"));
17370
- var fs38 = __toESM(require("fs"));
17432
+ var fs39 = __toESM(require("fs"));
17371
17433
  var path43 = __toESM(require("path"));
17372
17434
  function normalizeOrigin(raw) {
17373
17435
  const trimmed = raw.trim();
@@ -17400,7 +17462,7 @@ function findRepoRoot(cwd) {
17400
17462
  if (seen.has(dir)) return null;
17401
17463
  seen.add(dir);
17402
17464
  try {
17403
- const stat3 = fs38.statSync(path43.join(dir, ".git"), { throwIfNoEntry: false });
17465
+ const stat3 = fs39.statSync(path43.join(dir, ".git"), { throwIfNoEntry: false });
17404
17466
  if (stat3 && (stat3.isDirectory() || stat3.isFile())) return dir;
17405
17467
  } catch {
17406
17468
  }
@@ -17415,7 +17477,7 @@ var _execSeam2 = {
17415
17477
  const out2 = (0, import_child_process18.execFileSync)(file, args2, opts);
17416
17478
  return typeof out2 === "string" ? out2 : out2.toString("utf8");
17417
17479
  },
17418
- realpath: (p2) => fs38.realpathSync(p2)
17480
+ realpath: (p2) => fs39.realpathSync(p2)
17419
17481
  };
17420
17482
  function readOrigin(cwd) {
17421
17483
  try {
@@ -17486,17 +17548,17 @@ var _provisionSeam = {
17486
17548
  };
17487
17549
  var _linkSeam = {
17488
17550
  platform: () => process.platform,
17489
- homedir: () => os32.homedir(),
17551
+ homedir: () => os33.homedir(),
17490
17552
  isWritableDir: (dir) => {
17491
17553
  try {
17492
- fs39.accessSync(dir, fs39.constants.W_OK);
17554
+ fs40.accessSync(dir, fs40.constants.W_OK);
17493
17555
  return true;
17494
17556
  } catch {
17495
17557
  return false;
17496
17558
  }
17497
17559
  },
17498
17560
  ensureDir: (dir) => {
17499
- fs39.mkdirSync(dir, { recursive: true });
17561
+ fs40.mkdirSync(dir, { recursive: true });
17500
17562
  },
17501
17563
  /**
17502
17564
  * A directory to symlink `bd` into so the AGENT's shell + Claude Code's
@@ -17536,7 +17598,7 @@ var _linkSeam = {
17536
17598
  const entry = process.argv[1];
17537
17599
  if (entry) {
17538
17600
  try {
17539
- candidates.push(path44.dirname(fs39.realpathSync(entry)));
17601
+ candidates.push(path44.dirname(fs40.realpathSync(entry)));
17540
17602
  } catch {
17541
17603
  candidates.push(path44.dirname(entry));
17542
17604
  }
@@ -17550,13 +17612,13 @@ var _linkSeam = {
17550
17612
  /** Current symlink target at `linkPath`, or null when absent / not a link. */
17551
17613
  readlink: (linkPath) => {
17552
17614
  try {
17553
- return fs39.readlinkSync(linkPath);
17615
+ return fs40.readlinkSync(linkPath);
17554
17616
  } catch {
17555
17617
  return null;
17556
17618
  }
17557
17619
  },
17558
- unlink: (linkPath) => fs39.unlinkSync(linkPath),
17559
- symlink: (target, linkPath) => fs39.symlinkSync(target, linkPath)
17620
+ unlink: (linkPath) => fs40.unlinkSync(linkPath),
17621
+ symlink: (target, linkPath) => fs40.symlinkSync(target, linkPath)
17560
17622
  };
17561
17623
  function linkBdOntoPath(binaryPath) {
17562
17624
  if (_linkSeam.platform() === "win32") return;
@@ -18032,15 +18094,15 @@ async function handleBeadsActionCommand(action, started) {
18032
18094
  }
18033
18095
 
18034
18096
  // src/beads/config-store.ts
18035
- var import_node_fs5 = __toESM(require("fs"));
18036
- var import_node_os3 = __toESM(require("os"));
18097
+ var import_node_fs6 = __toESM(require("fs"));
18098
+ var import_node_os4 = __toESM(require("os"));
18037
18099
  var import_node_path4 = __toESM(require("path"));
18038
18100
  function beadsConfigPath() {
18039
- return import_node_path4.default.join(import_node_os3.default.homedir(), ".codeam", "beads-config.json");
18101
+ return import_node_path4.default.join(import_node_os4.default.homedir(), ".codeam", "beads-config.json");
18040
18102
  }
18041
18103
  function readBeadsEnabled() {
18042
18104
  try {
18043
- const raw = import_node_fs5.default.readFileSync(beadsConfigPath(), "utf8");
18105
+ const raw = import_node_fs6.default.readFileSync(beadsConfigPath(), "utf8");
18044
18106
  const cfg = JSON.parse(raw);
18045
18107
  return cfg.enabled !== false;
18046
18108
  } catch {
@@ -18049,10 +18111,11 @@ function readBeadsEnabled() {
18049
18111
  }
18050
18112
  function persistBeadsConfig(cfg) {
18051
18113
  const file = beadsConfigPath();
18052
- import_node_fs5.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
18114
+ import_node_fs6.default.mkdirSync(import_node_path4.default.dirname(file), { recursive: true });
18053
18115
  const tmp = `${file}.tmp`;
18054
- import_node_fs5.default.writeFileSync(tmp, JSON.stringify(cfg), { mode: 384 });
18055
- import_node_fs5.default.renameSync(tmp, file);
18116
+ import_node_fs6.default.writeFileSync(tmp, JSON.stringify(cfg), { mode: 384 });
18117
+ import_node_fs6.default.renameSync(tmp, file);
18118
+ restrictToOwner(file);
18056
18119
  }
18057
18120
 
18058
18121
  // src/beads/wiring.ts
@@ -18191,7 +18254,7 @@ var pendingAttachmentFiles = /* @__PURE__ */ new Set();
18191
18254
  function cleanupAttachmentTempFiles() {
18192
18255
  for (const p2 of pendingAttachmentFiles) {
18193
18256
  try {
18194
- fs41.unlinkSync(p2);
18257
+ fs42.unlinkSync(p2);
18195
18258
  } catch {
18196
18259
  }
18197
18260
  }
@@ -18200,8 +18263,8 @@ function cleanupAttachmentTempFiles() {
18200
18263
  function saveFilesTemp(files) {
18201
18264
  return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
18202
18265
  const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
18203
- const tmpPath = path47.join(os34.tmpdir(), `codeam-${(0, import_crypto3.randomUUID)()}-${safeName}`);
18204
- fs41.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
18266
+ const tmpPath = path47.join(os35.tmpdir(), `codeam-${(0, import_crypto3.randomUUID)()}-${safeName}`);
18267
+ fs42.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
18205
18268
  pendingAttachmentFiles.add(tmpPath);
18206
18269
  return tmpPath;
18207
18270
  });
@@ -18221,7 +18284,7 @@ var startTask = (ctx, _cmd, parsed) => {
18221
18284
  setTimeout(() => {
18222
18285
  for (const p2 of paths) {
18223
18286
  try {
18224
- fs41.unlinkSync(p2);
18287
+ fs42.unlinkSync(p2);
18225
18288
  } catch {
18226
18289
  }
18227
18290
  pendingAttachmentFiles.delete(p2);
@@ -18424,7 +18487,7 @@ var listFiles = async (ctx, cmd, parsed) => {
18424
18487
  var envReadH = async (ctx, cmd) => {
18425
18488
  const envPath = path47.join(process.cwd(), ".env");
18426
18489
  try {
18427
- const raw = await fs41.promises.readFile(envPath, "utf8");
18490
+ const raw = await fs42.promises.readFile(envPath, "utf8");
18428
18491
  await ctx.relay.sendResult(cmd.id, "completed", {
18429
18492
  exists: true,
18430
18493
  vars: parseDotenv(raw)
@@ -18458,11 +18521,11 @@ var envWriteH = async (ctx, cmd, parsed) => {
18458
18521
  const envPath = path47.join(process.cwd(), ".env");
18459
18522
  const tmpPath = path47.join(process.cwd(), ".env.codeam.tmp");
18460
18523
  try {
18461
- await fs41.promises.writeFile(tmpPath, serializeDotenv(vars), "utf8");
18462
- await fs41.promises.rename(tmpPath, envPath);
18524
+ await fs42.promises.writeFile(tmpPath, serializeDotenv(vars), "utf8");
18525
+ await fs42.promises.rename(tmpPath, envPath);
18463
18526
  await ctx.relay.sendResult(cmd.id, "completed", { ok: true, count: vars.length });
18464
18527
  } catch (err) {
18465
- await fs41.promises.rm(tmpPath, { force: true }).catch(() => void 0);
18528
+ await fs42.promises.rm(tmpPath, { force: true }).catch(() => void 0);
18466
18529
  await ctx.relay.sendResult(cmd.id, "failed", { error: err.message });
18467
18530
  }
18468
18531
  };
@@ -18480,7 +18543,7 @@ var headroomConfigureH = async (ctx, cmd, parsed) => {
18480
18543
  let configuredAgent = rawAgentId;
18481
18544
  if (!configuredAgent) {
18482
18545
  try {
18483
- const raw = JSON.parse(fs41.readFileSync(headroomConfigPath(), "utf8"));
18546
+ const raw = JSON.parse(fs42.readFileSync(headroomConfigPath(), "utf8"));
18484
18547
  configuredAgent = raw.agent ?? "";
18485
18548
  } catch {
18486
18549
  }
@@ -18514,7 +18577,7 @@ var headroomConfigureH = async (ctx, cmd, parsed) => {
18514
18577
  persist: persistHeadroomConfig,
18515
18578
  readEnabled: () => {
18516
18579
  try {
18517
- const raw = JSON.parse(fs41.readFileSync(headroomConfigPath(), "utf8"));
18580
+ const raw = JSON.parse(fs42.readFileSync(headroomConfigPath(), "utf8"));
18518
18581
  return raw.enabled === true;
18519
18582
  } catch {
18520
18583
  return false;
@@ -18527,7 +18590,7 @@ var headroomConfigureH = async (ctx, cmd, parsed) => {
18527
18590
  const res = await fetch("http://localhost:8787/stats");
18528
18591
  return res.json();
18529
18592
  },
18530
- postSavings: async (delta) => {
18593
+ postSavings: async (delta, budget) => {
18531
18594
  if (!opts.ingestUrl) return;
18532
18595
  await fetch(opts.ingestUrl, {
18533
18596
  method: "POST",
@@ -18546,7 +18609,12 @@ var headroomConfigureH = async (ctx, cmd, parsed) => {
18546
18609
  sessionId: ctx.sessionId,
18547
18610
  pluginId: ctx.pluginId,
18548
18611
  agentId: opts.agent,
18549
- savings: delta
18612
+ savings: delta,
18613
+ ...budget ? {
18614
+ periodSpendUsd: budget.periodSpendUsd,
18615
+ budgetUsd: budget.budgetUsd,
18616
+ budgetPeriod: budget.budgetPeriod
18617
+ } : {}
18550
18618
  })
18551
18619
  });
18552
18620
  }
@@ -18587,6 +18655,89 @@ var headroomConfigureH = async (ctx, cmd, parsed) => {
18587
18655
  });
18588
18656
  await ctx.relay.sendResult(cmd.id, "completed", result);
18589
18657
  };
18658
+ var headroomBudgetH = async (ctx, cmd) => {
18659
+ const payload = cmd.payload;
18660
+ let rawAgentId = ctx.agentId || (typeof payload.agentId === "string" ? payload.agentId : "");
18661
+ if (rawAgentId === "claude_code") rawAgentId = "claude";
18662
+ let payloadAgentId = typeof payload.agentId === "string" ? payload.agentId : "";
18663
+ if (payloadAgentId === "claude_code") payloadAgentId = "claude";
18664
+ if (!rawAgentId || !isHeadroomSupportedAgent(rawAgentId)) {
18665
+ await ctx.relay.sendResult(cmd.id, "completed", { applied: false });
18666
+ return;
18667
+ }
18668
+ if (!payloadAgentId || payloadAgentId !== rawAgentId) {
18669
+ await ctx.relay.sendResult(cmd.id, "completed", { applied: false });
18670
+ return;
18671
+ }
18672
+ let headroomActive = false;
18673
+ try {
18674
+ const raw = JSON.parse(fs42.readFileSync(headroomConfigPath(), "utf8"));
18675
+ headroomActive = raw.enabled === true;
18676
+ } catch {
18677
+ }
18678
+ if (!headroomActive) {
18679
+ await ctx.relay.sendResult(cmd.id, "completed", { applied: false });
18680
+ return;
18681
+ }
18682
+ let existingConfig = { enabled: true };
18683
+ try {
18684
+ existingConfig = JSON.parse(fs42.readFileSync(headroomConfigPath(), "utf8"));
18685
+ } catch {
18686
+ }
18687
+ if (payload.budgetEnabled && payload.budgetUsd != null) {
18688
+ persistHeadroomConfig({
18689
+ ...existingConfig,
18690
+ enabled: existingConfig.enabled ?? true,
18691
+ budgetEnabled: true,
18692
+ budgetUsd: payload.budgetUsd,
18693
+ budgetPeriod: payload.budgetPeriod ?? "daily"
18694
+ });
18695
+ process.env["HEADROOM_BUDGET"] = String(payload.budgetUsd);
18696
+ process.env["HEADROOM_BUDGET_PERIOD"] = payload.budgetPeriod ?? "daily";
18697
+ } else {
18698
+ persistHeadroomConfig({
18699
+ ...existingConfig,
18700
+ enabled: existingConfig.enabled ?? true,
18701
+ budgetEnabled: false,
18702
+ budgetUsd: void 0,
18703
+ budgetPeriod: void 0
18704
+ });
18705
+ delete process.env["HEADROOM_BUDGET"];
18706
+ delete process.env["HEADROOM_BUDGET_PERIOD"];
18707
+ }
18708
+ try {
18709
+ const killer = (0, import_child_process20.spawn)("pkill", ["-TERM", "-f", "headroom.*proxy"], {
18710
+ detached: true,
18711
+ stdio: "ignore"
18712
+ });
18713
+ killer.once("error", () => {
18714
+ });
18715
+ killer.unref();
18716
+ } catch {
18717
+ }
18718
+ try {
18719
+ const proxyEnv = { ...process.env, HEADROOM_KOMPRESS_BACKEND: "onnx_cpu" };
18720
+ const proxy = (0, import_child_process20.spawn)(
18721
+ "headroom",
18722
+ ["proxy", "--port", "8787", ...buildBudgetProxyArgs(proxyEnv)],
18723
+ {
18724
+ stdio: "ignore",
18725
+ detached: true,
18726
+ env: proxyEnv
18727
+ }
18728
+ );
18729
+ proxy.once("error", (e) => {
18730
+ log.warn("headroom-budget", `proxy relaunch error (best-effort): ${e.message}`);
18731
+ });
18732
+ proxy.unref();
18733
+ } catch (e) {
18734
+ log.warn(
18735
+ "headroom-budget",
18736
+ `proxy relaunch failed (best-effort): ${e instanceof Error ? e.message : String(e)}`
18737
+ );
18738
+ }
18739
+ await ctx.relay.sendResult(cmd.id, "completed", { applied: true });
18740
+ };
18590
18741
  var _beadsEmitChain = Promise.resolve();
18591
18742
  var beadsConfigureH = async (ctx, cmd, parsed) => {
18592
18743
  const action = parsed.action;
@@ -19083,7 +19234,7 @@ function normalizeDetectionForSpawn(detection, cwd) {
19083
19234
  const binName = args2[0];
19084
19235
  if (binName.startsWith("-")) return detection;
19085
19236
  const binPath = path47.join(cwd, "node_modules", ".bin", binName);
19086
- if (!fs41.existsSync(binPath)) return detection;
19237
+ if (!fs42.existsSync(binPath)) return detection;
19087
19238
  return {
19088
19239
  ...detection,
19089
19240
  command: binPath,
@@ -19632,6 +19783,7 @@ var handlers = {
19632
19783
  env_read: envReadH,
19633
19784
  env_write: envWriteH,
19634
19785
  headroom_configure: headroomConfigureH,
19786
+ headroom_budget: headroomBudgetH,
19635
19787
  beads_configure: beadsConfigureH
19636
19788
  };
19637
19789
  async function dispatchCommand(ctx, cmd) {
@@ -19902,10 +20054,10 @@ function readTokenFromArgs(args2) {
19902
20054
  if (fileFlag) {
19903
20055
  const path61 = fileFlag.slice("--token-file=".length);
19904
20056
  try {
19905
- const content = fs42.readFileSync(path61, "utf8").trim();
20057
+ const content = fs43.readFileSync(path61, "utf8").trim();
19906
20058
  if (content.length === 0) fail(`--token-file ${path61} is empty`);
19907
20059
  try {
19908
- fs42.unlinkSync(path61);
20060
+ fs43.unlinkSync(path61);
19909
20061
  } catch {
19910
20062
  }
19911
20063
  return content;
@@ -19931,7 +20083,7 @@ async function claimOnce(token, pluginId, pluginSecretHash) {
19931
20083
  pluginId,
19932
20084
  ideName: "codeam-cli (codespace)",
19933
20085
  ideVersion: process.env.npm_package_version ?? "unknown",
19934
- hostname: os35.hostname(),
20086
+ hostname: os36.hostname(),
19935
20087
  codespaceName: process.env.CODESPACE_NAME ?? "",
19936
20088
  // Current git branch of the codespace's working directory, so the
19937
20089
  // backend can populate `PairedSession.branch` for the codespace pair.
@@ -19992,7 +20144,7 @@ async function claim(token, pluginId, pluginSecretHash) {
19992
20144
  }
19993
20145
  }
19994
20146
  function pairAutoLockPath() {
19995
- return path48.join(os35.homedir(), ".codeam", "pair-auto.lock");
20147
+ return path48.join(os36.homedir(), ".codeam", "pair-auto.lock");
19996
20148
  }
19997
20149
  function isLivePairAuto(pid) {
19998
20150
  if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid) return false;
@@ -20002,7 +20154,7 @@ function isLivePairAuto(pid) {
20002
20154
  if (e.code !== "EPERM") return false;
20003
20155
  }
20004
20156
  try {
20005
- return fs42.readFileSync(`/proc/${pid}/cmdline`, "utf8").includes("codeam");
20157
+ return fs43.readFileSync(`/proc/${pid}/cmdline`, "utf8").includes("codeam");
20006
20158
  } catch {
20007
20159
  return true;
20008
20160
  }
@@ -20012,24 +20164,24 @@ function isLiveCodeam(pid) {
20012
20164
  }
20013
20165
  function daemonLockPath(sessionId) {
20014
20166
  const safe = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
20015
- return path48.join(os35.homedir(), ".codeam", `daemon-${safe}.lock`);
20167
+ return path48.join(os36.homedir(), ".codeam", `daemon-${safe}.lock`);
20016
20168
  }
20017
20169
  function acquireDaemonLock(sessionId) {
20018
20170
  const lockPath = daemonLockPath(sessionId);
20019
20171
  try {
20020
- fs42.mkdirSync(path48.dirname(lockPath), { recursive: true });
20172
+ fs43.mkdirSync(path48.dirname(lockPath), { recursive: true });
20021
20173
  try {
20022
- fs42.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
20174
+ fs43.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
20023
20175
  } catch (e) {
20024
20176
  if (e.code !== "EEXIST") throw e;
20025
- const holder = Number(fs42.readFileSync(lockPath, "utf8").trim());
20177
+ const holder = Number(fs43.readFileSync(lockPath, "utf8").trim());
20026
20178
  if (holder && holder !== process.pid && isLiveCodeam(holder)) return false;
20027
- fs42.writeFileSync(lockPath, String(process.pid));
20179
+ fs43.writeFileSync(lockPath, String(process.pid));
20028
20180
  }
20029
20181
  const release3 = () => {
20030
20182
  try {
20031
- if (fs42.existsSync(lockPath) && Number(fs42.readFileSync(lockPath, "utf8").trim()) === process.pid) {
20032
- fs42.unlinkSync(lockPath);
20183
+ if (fs43.existsSync(lockPath) && Number(fs43.readFileSync(lockPath, "utf8").trim()) === process.pid) {
20184
+ fs43.unlinkSync(lockPath);
20033
20185
  }
20034
20186
  } catch {
20035
20187
  }
@@ -20051,19 +20203,19 @@ function acquireDaemonLock(sessionId) {
20051
20203
  function acquireSingletonLock() {
20052
20204
  const lockPath = pairAutoLockPath();
20053
20205
  try {
20054
- fs42.mkdirSync(path48.dirname(lockPath), { recursive: true });
20206
+ fs43.mkdirSync(path48.dirname(lockPath), { recursive: true });
20055
20207
  try {
20056
- fs42.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
20208
+ fs43.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
20057
20209
  } catch (e) {
20058
20210
  if (e.code !== "EEXIST") throw e;
20059
- const holder = Number(fs42.readFileSync(lockPath, "utf8").trim());
20211
+ const holder = Number(fs43.readFileSync(lockPath, "utf8").trim());
20060
20212
  if (isLivePairAuto(holder)) return false;
20061
- fs42.writeFileSync(lockPath, String(process.pid));
20213
+ fs43.writeFileSync(lockPath, String(process.pid));
20062
20214
  }
20063
20215
  process.once("exit", () => {
20064
20216
  try {
20065
- if (fs42.existsSync(lockPath) && Number(fs42.readFileSync(lockPath, "utf8").trim()) === process.pid) {
20066
- fs42.unlinkSync(lockPath);
20217
+ if (fs43.existsSync(lockPath) && Number(fs43.readFileSync(lockPath, "utf8").trim()) === process.pid) {
20218
+ fs43.unlinkSync(lockPath);
20067
20219
  }
20068
20220
  } catch {
20069
20221
  }
@@ -20145,7 +20297,7 @@ async function pairAuto(args2) {
20145
20297
  }
20146
20298
 
20147
20299
  // src/services/headroom/wrap-launch.ts
20148
- var import_node_child_process15 = require("child_process");
20300
+ var import_node_child_process16 = require("child_process");
20149
20301
  function wrapWithHeadroom(launch, opts) {
20150
20302
  if (!opts.enabled || !opts.headroomPresent) return launch;
20151
20303
  return {
@@ -20158,7 +20310,7 @@ var _present;
20158
20310
  function headroomPresent() {
20159
20311
  if (_present !== void 0) return Promise.resolve(_present);
20160
20312
  return new Promise((resolve7) => {
20161
- (0, import_node_child_process15.execFile)("headroom", ["--version"], (err) => {
20313
+ (0, import_node_child_process16.execFile)("headroom", ["--version"], (err) => {
20162
20314
  _present = !err;
20163
20315
  resolve7(_present);
20164
20316
  });
@@ -20590,9 +20742,9 @@ function requiresAcp(agent) {
20590
20742
  var import_node_crypto7 = require("crypto");
20591
20743
 
20592
20744
  // src/services/history.service.ts
20593
- var fs43 = __toESM(require("fs"));
20745
+ var fs44 = __toESM(require("fs"));
20594
20746
  var path50 = __toESM(require("path"));
20595
- var os36 = __toESM(require("os"));
20747
+ var os37 = __toESM(require("os"));
20596
20748
  var https7 = __toESM(require("https"));
20597
20749
  var http6 = __toESM(require("http"));
20598
20750
  var import_zod2 = require("zod");
@@ -20619,7 +20771,7 @@ function parseJsonl(filePath) {
20619
20771
  const messages = [];
20620
20772
  let raw;
20621
20773
  try {
20622
- raw = fs43.readFileSync(filePath, "utf8");
20774
+ raw = fs44.readFileSync(filePath, "utf8");
20623
20775
  } catch (err) {
20624
20776
  if (err.code !== "ENOENT") {
20625
20777
  log.warn("history:parseJsonl", `read failed for ${filePath}`, err);
@@ -20760,7 +20912,7 @@ var HistoryService = class _HistoryService {
20760
20912
  return this._quotaPercent === null || Date.now() - this._quotaFetchedAt > ttlMs;
20761
20913
  }
20762
20914
  get projectDir() {
20763
- return this.runtime.resolveHistoryDir(this.cwd) ?? path50.join(os36.homedir(), ".claude", "projects", encodeCwd(this.cwd));
20915
+ return this.runtime.resolveHistoryDir(this.cwd) ?? path50.join(os37.homedir(), ".claude", "projects", encodeCwd(this.cwd));
20764
20916
  }
20765
20917
  /** Set the current Claude conversation ID (extracted from /cost command or session start) */
20766
20918
  setCurrentConversationId(id) {
@@ -20809,9 +20961,9 @@ var HistoryService = class _HistoryService {
20809
20961
  const dir = this.projectDir;
20810
20962
  const cutoff = this.bootTimeMs - _HistoryService.BIRTHTIME_GRACE_MS;
20811
20963
  try {
20812
- const files = fs43.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
20964
+ const files = fs44.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
20813
20965
  try {
20814
- const stat3 = fs43.statSync(path50.join(dir, e.name));
20966
+ const stat3 = fs44.statSync(path50.join(dir, e.name));
20815
20967
  return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
20816
20968
  } catch {
20817
20969
  return { name: e.name, mtime: 0, birthtime: 0 };
@@ -20852,13 +21004,13 @@ var HistoryService = class _HistoryService {
20852
21004
  const cutoff = this.bootTimeMs - _HistoryService.BIRTHTIME_GRACE_MS;
20853
21005
  let entries;
20854
21006
  try {
20855
- entries = fs43.readdirSync(dir, { withFileTypes: true });
21007
+ entries = fs44.readdirSync(dir, { withFileTypes: true });
20856
21008
  } catch {
20857
21009
  return null;
20858
21010
  }
20859
21011
  const files = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
20860
21012
  try {
20861
- const stat3 = fs43.statSync(path50.join(dir, e.name));
21013
+ const stat3 = fs44.statSync(path50.join(dir, e.name));
20862
21014
  return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
20863
21015
  } catch {
20864
21016
  return { name: e.name, mtime: 0, birthtime: 0 };
@@ -20872,7 +21024,7 @@ var HistoryService = class _HistoryService {
20872
21024
  extractUsageFromFile(filePath) {
20873
21025
  let raw;
20874
21026
  try {
20875
- raw = fs43.readFileSync(filePath, "utf8");
21027
+ raw = fs44.readFileSync(filePath, "utf8");
20876
21028
  } catch {
20877
21029
  return null;
20878
21030
  }
@@ -20917,9 +21069,9 @@ var HistoryService = class _HistoryService {
20917
21069
  let totalCost = 0;
20918
21070
  let files;
20919
21071
  try {
20920
- files = fs43.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).filter((f) => {
21072
+ files = fs44.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).filter((f) => {
20921
21073
  try {
20922
- return fs43.statSync(path50.join(projectDir, f)).mtimeMs >= monthStartMs;
21074
+ return fs44.statSync(path50.join(projectDir, f)).mtimeMs >= monthStartMs;
20923
21075
  } catch {
20924
21076
  return false;
20925
21077
  }
@@ -20930,7 +21082,7 @@ var HistoryService = class _HistoryService {
20930
21082
  for (const file of files) {
20931
21083
  let raw;
20932
21084
  try {
20933
- raw = fs43.readFileSync(path50.join(projectDir, file), "utf8");
21085
+ raw = fs44.readFileSync(path50.join(projectDir, file), "utf8");
20934
21086
  } catch {
20935
21087
  continue;
20936
21088
  }
@@ -21099,7 +21251,7 @@ var HistoryService = class _HistoryService {
21099
21251
  if (!filePath) return false;
21100
21252
  let mtimeMs;
21101
21253
  try {
21102
- mtimeMs = fs43.statSync(filePath).mtimeMs;
21254
+ mtimeMs = fs44.statSync(filePath).mtimeMs;
21103
21255
  } catch {
21104
21256
  return false;
21105
21257
  }
@@ -21167,10 +21319,10 @@ var HistoryService = class _HistoryService {
21167
21319
  };
21168
21320
 
21169
21321
  // src/agents/acp/client.ts
21170
- var import_node_child_process16 = require("child_process");
21171
- var fs44 = __toESM(require("fs/promises"));
21322
+ var import_node_child_process17 = require("child_process");
21323
+ var fs45 = __toESM(require("fs/promises"));
21172
21324
  var fsSync = __toESM(require("fs"));
21173
- var os37 = __toESM(require("os"));
21325
+ var os38 = __toESM(require("os"));
21174
21326
  var path51 = __toESM(require("path"));
21175
21327
  var import_node_stream = require("stream");
21176
21328
 
@@ -23718,7 +23870,7 @@ var AcpClient = class {
23718
23870
  "acpClient",
23719
23871
  `spawn cmd=${adapter.command} args=[${adapter.args.join(",")}] cwd=${cwd}`
23720
23872
  );
23721
- const child = (0, import_node_child_process16.spawn)(adapter.command, adapter.args, {
23873
+ const child = (0, import_node_child_process17.spawn)(adapter.command, adapter.args, {
23722
23874
  cwd,
23723
23875
  // extraEnv (e.g. CLAUDE_CODE_DISABLE_1M_CONTEXT=1 on an on-demand
23724
23876
  // re-spawn) layers over process.env; PATH stays last so the augmented
@@ -24004,7 +24156,7 @@ var AcpClient = class {
24004
24156
  },
24005
24157
  readTextFile: async (params) => {
24006
24158
  try {
24007
- const content = await fs44.readFile(params.path, "utf8");
24159
+ const content = await fs45.readFile(params.path, "utf8");
24008
24160
  return applyLineRange(content, params.line ?? null, params.limit ?? null);
24009
24161
  } catch (err) {
24010
24162
  const code = err.code;
@@ -24024,7 +24176,7 @@ var AcpClient = class {
24024
24176
  },
24025
24177
  writeTextFile: async (params) => {
24026
24178
  try {
24027
- await fs44.writeFile(params.path, params.content, "utf8");
24179
+ await fs45.writeFile(params.path, params.content, "utf8");
24028
24180
  return {};
24029
24181
  } catch (err) {
24030
24182
  const code = err.code;
@@ -24073,7 +24225,7 @@ function applyLineRange(content, line, limit) {
24073
24225
  return { content: lines.slice(start2, end).join("\n") };
24074
24226
  }
24075
24227
  function knownAgentBinaryDirs() {
24076
- const home = os37.homedir();
24228
+ const home = os38.homedir();
24077
24229
  const out2 = [];
24078
24230
  out2.push("/tmp/codeam-node20/bin");
24079
24231
  for (const root of [
@@ -24504,6 +24656,79 @@ function createOneMRecovery(deps) {
24504
24656
  };
24505
24657
  }
24506
24658
 
24659
+ // src/agents/acp/budgetRecovery.ts
24660
+ var BUDGET_EXCEEDED_RE = /budget exceeded for (\w+) period/i;
24661
+ function looksLikeBudgetExceeded(text) {
24662
+ return BUDGET_EXCEEDED_RE.test(text);
24663
+ }
24664
+ function extractBudgetPeriod(text) {
24665
+ const m = BUDGET_EXCEEDED_RE.exec(text);
24666
+ return m ? m[1] ?? "current" : "current";
24667
+ }
24668
+ var BUDGET_PAUSE_OPTION = "Pause budget this session";
24669
+ var BUDGET_RAISE_OPTION = "Raise budget";
24670
+ function createBudgetRecovery(deps) {
24671
+ let pending = null;
24672
+ const buildQuestion = (period) => `\u{1F4B8} **Headroom budget reached for the ${period} period.**
24673
+
24674
+ You have two options:
24675
+ \u2022 **Pause budget this session** \u2014 removes the spending cap for this session only (the proxy restarts without a budget limit).
24676
+ \u2022 **Raise budget** \u2014 open your agent budget settings to increase the cap.`;
24677
+ return {
24678
+ offer: async (commandId, blocks, detail) => {
24679
+ const period = extractBudgetPeriod(detail);
24680
+ pending = { blocks, period };
24681
+ const question = buildQuestion(period);
24682
+ const options = [BUDGET_PAUSE_OPTION, BUDGET_RAISE_OPTION];
24683
+ await deps.publishText(question);
24684
+ await deps.publishSelectPrompt(question, options);
24685
+ await deps.publishAwaitingAnswer(question, options);
24686
+ deps.appendAgentReply(question);
24687
+ deps.flushHistory();
24688
+ deps.log?.(`budget-exceeded recovery offered period=${period} id=${commandId.slice(0, 8)}`);
24689
+ await deps.sendResult(commandId, "failed", {
24690
+ error: `Headroom budget exceeded (${period}) \u2014 recovery offered`
24691
+ });
24692
+ },
24693
+ tryRecover: async (commandId, optionIndex) => {
24694
+ if (!pending) return false;
24695
+ const { blocks, period } = pending;
24696
+ pending = null;
24697
+ if (optionIndex === 0) {
24698
+ deps.log?.("budget recovery \u2192 pause: relaunching proxy without budget");
24699
+ try {
24700
+ await deps.relaunchProxyWithoutBudget();
24701
+ } catch (err) {
24702
+ deps.log?.(`budget recovery \u2192 relaunch failed (best-effort): ${String(err)}`);
24703
+ }
24704
+ await deps.sendResult(commandId, "completed", {
24705
+ action: "pause",
24706
+ period,
24707
+ note: "Headroom proxy relaunched without budget limit for this session. Send your message again."
24708
+ });
24709
+ const note = "\u2705 **Budget paused for this session.** The proxy is running without a spending cap \u2014 send your message again.";
24710
+ await deps.publishText(note);
24711
+ deps.appendAgentReply(note);
24712
+ deps.flushHistory();
24713
+ deps.log?.(`budget recovery \u2192 pause complete id=${commandId.slice(0, 8)}`);
24714
+ } else {
24715
+ deps.log?.(`budget recovery \u2192 raise: emitting deep-link for agent=${deps.agentId}`);
24716
+ await deps.publishRawChunk({
24717
+ type: "open_agent_budget_settings",
24718
+ agentId: deps.agentId,
24719
+ done: true
24720
+ });
24721
+ await deps.sendResult(commandId, "completed", {
24722
+ action: "raise",
24723
+ agentId: deps.agentId
24724
+ });
24725
+ deps.log?.(`budget recovery \u2192 raise emitted id=${commandId.slice(0, 8)}`);
24726
+ }
24727
+ return true;
24728
+ }
24729
+ };
24730
+ }
24731
+
24507
24732
  // src/agents/acp/reconcileDelta.ts
24508
24733
  function reconcileCumulative(existing, incoming) {
24509
24734
  if (incoming.length === 0) return existing;
@@ -24524,15 +24749,15 @@ function commonPrefixLength(a, b) {
24524
24749
 
24525
24750
  // src/agents/acp/onboarding.ts
24526
24751
  var import_child_process22 = require("child_process");
24527
- var fs45 = __toESM(require("fs"));
24528
- var os38 = __toESM(require("os"));
24752
+ var fs46 = __toESM(require("fs"));
24753
+ var os39 = __toESM(require("os"));
24529
24754
  var path52 = __toESM(require("path"));
24530
24755
  var _onboardingSeam = {
24531
- markerPath: (sessionId) => path52.join(os38.homedir(), ".codeam", "welcomed", `${sessionId}.done`),
24532
- exists: (p2) => fs45.existsSync(p2),
24756
+ markerPath: (sessionId) => path52.join(os39.homedir(), ".codeam", "welcomed", `${sessionId}.done`),
24757
+ exists: (p2) => fs46.existsSync(p2),
24533
24758
  write: (p2) => {
24534
- fs45.mkdirSync(path52.dirname(p2), { recursive: true });
24535
- fs45.writeFileSync(p2, "");
24759
+ fs46.mkdirSync(path52.dirname(p2), { recursive: true });
24760
+ fs46.writeFileSync(p2, "");
24536
24761
  },
24537
24762
  disabled: () => {
24538
24763
  const v = process.env.CODEAM_ONBOARDING_DISABLED;
@@ -24862,7 +25087,7 @@ var import_crypto5 = require("crypto");
24862
25087
 
24863
25088
  // src/services/turn-files/git-changeset.ts
24864
25089
  var import_child_process23 = require("child_process");
24865
- var fs46 = __toESM(require("fs/promises"));
25090
+ var fs47 = __toESM(require("fs/promises"));
24866
25091
  var path53 = __toESM(require("path"));
24867
25092
  async function collectRepoChangeset(opts) {
24868
25093
  const status2 = await runGit3(opts.repoRoot, ["status", "--porcelain=v1", "-z"]);
@@ -24912,7 +25137,7 @@ function readUntrackedLineCount(absPath) {
24912
25137
  }
24913
25138
  async function defaultReadUntrackedLineCount(absPath) {
24914
25139
  try {
24915
- const content = await fs46.readFile(absPath, "utf8");
25140
+ const content = await fs47.readFile(absPath, "utf8");
24916
25141
  let count = 0;
24917
25142
  let pos = -1;
24918
25143
  while ((pos = content.indexOf("\n", pos + 1)) !== -1) {
@@ -25004,7 +25229,7 @@ function defaultRunGit(cwd, args2) {
25004
25229
  });
25005
25230
  }
25006
25231
  async function discoverRepos(workingDir, maxDepth = 4) {
25007
- const fs50 = await import("fs/promises");
25232
+ const fs51 = await import("fs/promises");
25008
25233
  const out2 = [];
25009
25234
  await walk(workingDir, 0);
25010
25235
  return out2;
@@ -25012,7 +25237,7 @@ async function discoverRepos(workingDir, maxDepth = 4) {
25012
25237
  if (depth > maxDepth) return;
25013
25238
  let entries = [];
25014
25239
  try {
25015
- const dirents = await fs50.readdir(dir, { withFileTypes: true });
25240
+ const dirents = await fs51.readdir(dir, { withFileTypes: true });
25016
25241
  entries = dirents.filter((d3) => !d3.name.startsWith(".") || d3.name === ".git").map((d3) => ({ name: d3.name, isDirectory: d3.isDirectory() }));
25017
25242
  } catch {
25018
25243
  return;
@@ -25038,7 +25263,7 @@ async function discoverRepos(workingDir, maxDepth = 4) {
25038
25263
  }
25039
25264
 
25040
25265
  // src/services/turn-files/files-outbox.ts
25041
- var fs47 = __toESM(require("fs/promises"));
25266
+ var fs48 = __toESM(require("fs/promises"));
25042
25267
  var path54 = __toESM(require("path"));
25043
25268
  var import_os7 = require("os");
25044
25269
  var HOME_OUTBOX_DIR = ".codeam/outbox";
@@ -25080,8 +25305,8 @@ var FilesOutbox = class {
25080
25305
  /** Persist the entry to disk and trigger a flush. Returns once the
25081
25306
  * line is durable on disk (not once the POST succeeds). */
25082
25307
  async enqueue(entry) {
25083
- await fs47.mkdir(path54.dirname(this.filePath), { recursive: true });
25084
- await fs47.appendFile(this.filePath, JSON.stringify(entry) + "\n", "utf8");
25308
+ await fs48.mkdir(path54.dirname(this.filePath), { recursive: true });
25309
+ await fs48.appendFile(this.filePath, JSON.stringify(entry) + "\n", "utf8");
25085
25310
  this.backoffIndex = 0;
25086
25311
  if (this.autoSchedule) this.scheduleFlush(0);
25087
25312
  }
@@ -25170,7 +25395,7 @@ var FilesOutbox = class {
25170
25395
  async readAll() {
25171
25396
  let raw = "";
25172
25397
  try {
25173
- raw = await fs47.readFile(this.filePath, "utf8");
25398
+ raw = await fs48.readFile(this.filePath, "utf8");
25174
25399
  } catch {
25175
25400
  return [];
25176
25401
  }
@@ -25194,12 +25419,12 @@ var FilesOutbox = class {
25194
25419
  async rewrite(entries) {
25195
25420
  const tmpPath = `${this.filePath}.${process.pid}.tmp`;
25196
25421
  if (entries.length === 0) {
25197
- await fs47.unlink(this.filePath).catch(() => void 0);
25422
+ await fs48.unlink(this.filePath).catch(() => void 0);
25198
25423
  return;
25199
25424
  }
25200
25425
  const payload = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
25201
- await fs47.writeFile(tmpPath, payload, "utf8");
25202
- await fs47.rename(tmpPath, this.filePath);
25426
+ await fs48.writeFile(tmpPath, payload, "utf8");
25427
+ await fs48.rename(tmpPath, this.filePath);
25203
25428
  }
25204
25429
  };
25205
25430
  function applyJitter(ms) {
@@ -25938,10 +26163,26 @@ async function surfaceStartupFailure(opts) {
25938
26163
  );
25939
26164
  errRelay.start();
25940
26165
  }
26166
+ function budgetBubbleMessage(agent, period) {
26167
+ return `\u{1F4B8} **Headroom budget reached for the ${period} period.**
26168
+
26169
+ The local Headroom proxy rejected the ${agent} request because the configured spending cap was hit. Choose an option below to continue.`;
26170
+ }
26171
+ function buildRelaunchProxyEnv(baseEnv) {
26172
+ const env = { ...baseEnv, HEADROOM_KOMPRESS_BACKEND: "onnx_cpu" };
26173
+ delete env["HEADROOM_BUDGET"];
26174
+ delete env["HEADROOM_BUDGET_PERIOD"];
26175
+ return env;
26176
+ }
25941
26177
  function failureBubble(opts) {
25942
26178
  if (looksLikeAuthFailure(opts.detail) || looksLikeAuthFailure(opts.recentStderr)) {
25943
26179
  return AUTH_FAILURE_MESSAGE;
25944
26180
  }
26181
+ const budgetHaystack = `${opts.detail}
26182
+ ${opts.recentStderr}`;
26183
+ if (looksLikeBudgetExceeded(budgetHaystack)) {
26184
+ return budgetBubbleMessage(opts.agent, extractBudgetPeriod(budgetHaystack));
26185
+ }
25945
26186
  if (looksLikeProviderOutage(opts.detail) || looksLikeProviderOutage(opts.recentStderr)) {
25946
26187
  return providerOutageMessage(opts.agent);
25947
26188
  }
@@ -25974,6 +26215,25 @@ async function reportCredentialInvalid(opts, fetchImpl = fetch) {
25974
26215
  } catch {
25975
26216
  }
25976
26217
  }
26218
+ async function postBudgetReached(opts, fetchImpl = fetch) {
26219
+ const url = `${resolveApiBaseUrl()}/api/sessions/${encodeURIComponent(opts.sessionId)}/headroom-budget-reached`;
26220
+ try {
26221
+ await fetchImpl(url, {
26222
+ method: "POST",
26223
+ headers: {
26224
+ "Content-Type": "application/json",
26225
+ "X-Plugin-Auth-Token": opts.pluginAuthToken
26226
+ },
26227
+ body: JSON.stringify({
26228
+ sessionId: opts.sessionId,
26229
+ pluginId: opts.pluginId,
26230
+ agent: opts.agent,
26231
+ period: opts.period
26232
+ })
26233
+ });
26234
+ } catch {
26235
+ }
26236
+ }
25977
26237
  async function handleGetConversation(args2) {
25978
26238
  const { relay, commandId, jsonlHistory, acpSessionId } = args2;
25979
26239
  try {
@@ -26139,6 +26399,58 @@ async function runAcpSession(opts) {
26139
26399
  describeError,
26140
26400
  log: (msg) => log.info("acpRunner", msg)
26141
26401
  });
26402
+ let _budgetReachedPosted = false;
26403
+ const relaunchProxyWithoutBudget = async () => {
26404
+ const { spawn: spawn32 } = await import("child_process");
26405
+ try {
26406
+ const killer = spawn32("pkill", ["-TERM", "-f", "headroom.*proxy"], {
26407
+ detached: true,
26408
+ stdio: "ignore"
26409
+ });
26410
+ killer.once("error", () => {
26411
+ });
26412
+ killer.unref();
26413
+ } catch {
26414
+ }
26415
+ await new Promise((r) => setTimeout(r, 500));
26416
+ const proxyEnv = buildRelaunchProxyEnv(process.env);
26417
+ try {
26418
+ const proxy = spawn32(
26419
+ "headroom",
26420
+ ["proxy", "--port", "8787"],
26421
+ { stdio: "ignore", detached: true, env: proxyEnv }
26422
+ );
26423
+ proxy.once("error", (e) => {
26424
+ log.warn("acpRunner", `budget recovery proxy relaunch error (best-effort): ${e.message}`);
26425
+ });
26426
+ proxy.unref();
26427
+ } catch (e) {
26428
+ log.warn(
26429
+ "acpRunner",
26430
+ `budget recovery proxy relaunch failed (best-effort): ${e instanceof Error ? e.message : String(e)}`
26431
+ );
26432
+ }
26433
+ await new Promise((r) => setTimeout(r, 3e3));
26434
+ };
26435
+ const budgetRecovery = createBudgetRecovery({
26436
+ publishText: (text) => publisher.publishOutput({ type: "text", content: text, done: true }),
26437
+ publishSelectPrompt: (question, options) => publisher.publishOutput({
26438
+ type: "select_prompt",
26439
+ content: question,
26440
+ options,
26441
+ optionDescriptions: options.map(() => ""),
26442
+ currentIndex: 0,
26443
+ done: true
26444
+ }),
26445
+ publishAwaitingAnswer: (prompt, options) => publisher.publishAwaitingAnswer({ questionId: (0, import_node_crypto7.randomUUID)(), prompt, options }),
26446
+ publishRawChunk: (chunk) => publisher.publishOutput(chunk),
26447
+ sendResult: (commandId, status2, result) => relay.sendResult(commandId, status2, result),
26448
+ appendAgentReply: (text) => history.appendAgentReply(text),
26449
+ flushHistory: () => void history.flush(),
26450
+ relaunchProxyWithoutBudget,
26451
+ agentId: opts.agent,
26452
+ log: (msg) => log.info("acpRunner", msg)
26453
+ });
26142
26454
  showInfo(`Starting ${opts.agent} via ACP adapter (${opts.adapter.requiresAgentBinary})\u2026`);
26143
26455
  let handshake;
26144
26456
  try {
@@ -26248,7 +26560,11 @@ async function runAcpSession(opts) {
26248
26560
  getBeads,
26249
26561
  publisher,
26250
26562
  recentStderr,
26251
- oneMRecovery
26563
+ oneMRecovery,
26564
+ budgetRecovery,
26565
+ { get: () => _budgetReachedPosted, set: (v) => {
26566
+ _budgetReachedPosted = v;
26567
+ } }
26252
26568
  );
26253
26569
  },
26254
26570
  { id: opts.agent, name: opts.agent, displayName: opts.agent }
@@ -26272,7 +26588,7 @@ async function runAcpSession(opts) {
26272
26588
  await new Promise(() => {
26273
26589
  });
26274
26590
  }
26275
- async function handleCommand(cmd, client2, relay, acpSessionId, models, streaming, opts, history, jsonlHistory, agentCaps, turnFiles, getBeads, publisher, recentStderr, oneMRecovery) {
26591
+ async function handleCommand(cmd, client2, relay, acpSessionId, models, streaming, opts, history, jsonlHistory, agentCaps, turnFiles, getBeads, publisher, recentStderr, oneMRecovery, budgetRecovery, budgetReachedFlag) {
26276
26592
  switch (cmd.type) {
26277
26593
  case "beads_action": {
26278
26594
  const beads = getBeads();
@@ -26356,6 +26672,24 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
26356
26672
  const detail = describeError(err);
26357
26673
  log.warn("acpRunner", `prompt failed: ${detail}`);
26358
26674
  await cancelStuckTurn(client2);
26675
+ if (looksLikeBudgetExceeded(`${detail}
26676
+ ${recentStderr.join("\n")}`)) {
26677
+ await streaming.closeAll();
26678
+ if (!budgetReachedFlag.get()) {
26679
+ budgetReachedFlag.set(true);
26680
+ void postBudgetReached({
26681
+ sessionId: opts.sessionId,
26682
+ pluginId: opts.pluginId,
26683
+ pluginAuthToken: opts.pluginAuthToken,
26684
+ agent: opts.agent,
26685
+ period: extractBudgetPeriod(`${detail}
26686
+ ${recentStderr.join("\n")}`)
26687
+ });
26688
+ }
26689
+ await budgetRecovery.offer(cmd.id, blocks, `${detail}
26690
+ ${recentStderr.join("\n")}`);
26691
+ return;
26692
+ }
26359
26693
  if (shouldOfferOneMRecovery({ detail, recentStderr: recentStderr.join("\n"), finalText: "" })) {
26360
26694
  await streaming.closeAll();
26361
26695
  await oneMRecovery.offer(cmd.id, blocks);
@@ -26462,6 +26796,7 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
26462
26796
  const index = typeof payload?.index === "number" ? payload.index : 0;
26463
26797
  const offset = typeof payload?.from === "number" ? payload.from : 0;
26464
26798
  const absoluteIndex = index + offset;
26799
+ if (await budgetRecovery.tryRecover(cmd.id, absoluteIndex)) return;
26465
26800
  const result = streaming.resolveSelection(absoluteIndex);
26466
26801
  switch (result.kind) {
26467
26802
  case "resolved":
@@ -27718,15 +28053,15 @@ function fetchQuotaUsage(runtime, historySvc) {
27718
28053
  }
27719
28054
 
27720
28055
  // src/agents/claude/onboarding.ts
27721
- var fs48 = __toESM(require("fs"));
27722
- var os39 = __toESM(require("os"));
28056
+ var fs49 = __toESM(require("fs"));
28057
+ var os40 = __toESM(require("os"));
27723
28058
  var path55 = __toESM(require("path"));
27724
28059
  function ensureClaudeOnboarded() {
27725
28060
  try {
27726
- const file = path55.join(os39.homedir(), ".claude.json");
28061
+ const file = path55.join(os40.homedir(), ".claude.json");
27727
28062
  let config = {};
27728
28063
  try {
27729
- config = JSON.parse(fs48.readFileSync(file, "utf8"));
28064
+ config = JSON.parse(fs49.readFileSync(file, "utf8"));
27730
28065
  } catch {
27731
28066
  }
27732
28067
  if (config.hasCompletedOnboarding === true && typeof config.theme === "string") {
@@ -27737,8 +28072,8 @@ function ensureClaudeOnboarded() {
27737
28072
  if (typeof config.lastOnboardingVersion !== "string") {
27738
28073
  config.lastOnboardingVersion = "2.1.177";
27739
28074
  }
27740
- fs48.mkdirSync(path55.dirname(file), { recursive: true });
27741
- fs48.writeFileSync(file, JSON.stringify(config, null, 2));
28075
+ fs49.mkdirSync(path55.dirname(file), { recursive: true });
28076
+ fs49.writeFileSync(file, JSON.stringify(config, null, 2));
27742
28077
  log.info("claude", "pre-completed Claude onboarding (skip first-run theme picker)");
27743
28078
  } catch (err) {
27744
28079
  log.warn("claude", `ensureClaudeOnboarded failed (non-fatal): ${err.message}`);
@@ -30433,7 +30768,7 @@ async function invite() {
30433
30768
  var import_node_dns = require("dns");
30434
30769
  var import_node_util5 = require("util");
30435
30770
  var import_node_crypto8 = require("crypto");
30436
- var fs49 = __toESM(require("fs"));
30771
+ var fs50 = __toESM(require("fs"));
30437
30772
  var path60 = __toESM(require("path"));
30438
30773
  var import_picocolors14 = __toESM(require("picocolors"));
30439
30774
  var dnsResolveP = (0, import_node_util5.promisify)(import_node_dns.resolve);
@@ -30492,11 +30827,11 @@ async function checkHealth(apiBase2) {
30492
30827
  function checkConfigDir() {
30493
30828
  const dir = path60.join(require("os").homedir(), ".codeam");
30494
30829
  try {
30495
- fs49.mkdirSync(dir, { recursive: true, mode: 448 });
30830
+ fs50.mkdirSync(dir, { recursive: true, mode: 448 });
30496
30831
  const probe = path60.join(dir, ".doctor-probe");
30497
- fs49.writeFileSync(probe, "ok", { mode: 384 });
30498
- const read2 = fs49.readFileSync(probe, "utf8");
30499
- fs49.unlinkSync(probe);
30832
+ fs50.writeFileSync(probe, "ok", { mode: 384 });
30833
+ const read2 = fs50.readFileSync(probe, "utf8");
30834
+ fs50.unlinkSync(probe);
30500
30835
  if (read2 !== "ok") throw new Error("write/read round-trip mismatch");
30501
30836
  return {
30502
30837
  id: "config-dir",
@@ -30536,9 +30871,9 @@ function checkSessions() {
30536
30871
  }
30537
30872
  }
30538
30873
  function checkAgentBinaries() {
30539
- const os41 = createOsStrategy();
30874
+ const os42 = createOsStrategy();
30540
30875
  return getEnabledAgents().map((meta) => {
30541
- const found = os41.findInPath(meta.binaryName);
30876
+ const found = os42.findInPath(meta.binaryName);
30542
30877
  return {
30543
30878
  id: `agent-${meta.id}`,
30544
30879
  label: `Agent binary: ${meta.displayName} (${meta.binaryName})`,
@@ -30602,7 +30937,7 @@ function checkChokidar() {
30602
30937
  }
30603
30938
  async function doctor(args2 = []) {
30604
30939
  const json = args2.includes("--json");
30605
- const cliVersion = true ? "2.50.0" : "0.0.0-dev";
30940
+ const cliVersion = true ? "2.51.1" : "0.0.0-dev";
30606
30941
  const apiBase2 = resolveApiBaseUrl();
30607
30942
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
30608
30943
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -30801,7 +31136,7 @@ async function completion(args2) {
30801
31136
  // src/commands/version.ts
30802
31137
  var import_picocolors15 = __toESM(require("picocolors"));
30803
31138
  function version2() {
30804
- const v = true ? "2.50.0" : "unknown";
31139
+ const v = true ? "2.51.1" : "unknown";
30805
31140
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
30806
31141
  }
30807
31142
 
@@ -30950,10 +31285,10 @@ var EXIT_CODE_NAMES = {
30950
31285
  };
30951
31286
 
30952
31287
  // src/index.ts
30953
- var os40 = __toESM(require("os"));
31288
+ var os41 = __toESM(require("os"));
30954
31289
  if (!process.env.HOME) {
30955
31290
  try {
30956
- const home = os40.homedir();
31291
+ const home = os41.homedir();
30957
31292
  if (home) process.env.HOME = home;
30958
31293
  } catch {
30959
31294
  }