codeam-cli 2.50.0 → 2.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/index.js +339 -23
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ All notable changes to `codeam-cli` are documented here.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [2.50.0] — 2026-06-28
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **cli:** Persisted beads-config.json (default-on disable flag)
|
|
12
|
+
- **cli:** Honor persisted beads disable flag in provisionBeadsForStart
|
|
13
|
+
- **cli:** ConfigureBeads enable/disable/status service
|
|
14
|
+
- **cli:** Beads_configure command handler + event emitter
|
|
15
|
+
- **cli:** Emit input_suggestion chip on ACP agent path after closing question
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **cli:** Status honors persisted disable flag; drop beads handler cast
|
|
20
|
+
- **cli:** Make beads_configure status probe read-only (no provisioning)
|
|
21
|
+
|
|
22
|
+
### Tests
|
|
23
|
+
|
|
24
|
+
- **cli:** Gated beads_configure integration smoke
|
|
25
|
+
- **cli:** Import vitest globals in beads test files (fix typecheck)
|
|
26
|
+
|
|
7
27
|
## [2.49.5] — 2026-06-28
|
|
8
28
|
|
|
9
29
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -5397,7 +5397,7 @@ function readAnonId() {
|
|
|
5397
5397
|
}
|
|
5398
5398
|
function superProperties() {
|
|
5399
5399
|
return {
|
|
5400
|
-
cliVersion: true ? "2.
|
|
5400
|
+
cliVersion: true ? "2.51.0" : "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.
|
|
5581
|
+
version: "2.51.0",
|
|
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",
|
|
@@ -14578,6 +14578,14 @@ function mapStatsToSavings(stats, prev, inputPricePerMillion = DEFAULT_INPUT_PRI
|
|
|
14578
14578
|
};
|
|
14579
14579
|
return { next, delta };
|
|
14580
14580
|
}
|
|
14581
|
+
function defaultGetBudgetEnv() {
|
|
14582
|
+
const raw = process.env["HEADROOM_BUDGET"];
|
|
14583
|
+
if (!raw) return null;
|
|
14584
|
+
const budgetUsd = Number(raw);
|
|
14585
|
+
if (!isFinite(budgetUsd) || budgetUsd <= 0) return null;
|
|
14586
|
+
const budgetPeriod = process.env["HEADROOM_BUDGET_PERIOD"] ?? "daily";
|
|
14587
|
+
return { budgetUsd, budgetPeriod };
|
|
14588
|
+
}
|
|
14581
14589
|
var HeadroomStatsReporter = class {
|
|
14582
14590
|
constructor(deps) {
|
|
14583
14591
|
this.deps = deps;
|
|
@@ -14591,14 +14599,22 @@ var HeadroomStatsReporter = class {
|
|
|
14591
14599
|
}
|
|
14592
14600
|
async tick() {
|
|
14593
14601
|
try {
|
|
14602
|
+
const stats = await this.deps.fetchStats();
|
|
14594
14603
|
const { next, delta } = mapStatsToSavings(
|
|
14595
|
-
|
|
14604
|
+
stats,
|
|
14596
14605
|
this.prev,
|
|
14597
14606
|
this.deps.inputPricePerMillionUsd ?? DEFAULT_INPUT_PRICE_PER_MILLION
|
|
14598
14607
|
);
|
|
14599
14608
|
this.prev = next;
|
|
14600
14609
|
if (delta.compressionTokens > 0 || delta.compressionSavingsUsd > 0 || delta.rawTokensEst > 0 || delta.cacheReadTokens > 0 || delta.cacheSavingsUsd > 0) {
|
|
14601
|
-
|
|
14610
|
+
const getBudgetEnv = this.deps.getBudgetEnv ?? defaultGetBudgetEnv;
|
|
14611
|
+
const budgetEnv = getBudgetEnv();
|
|
14612
|
+
const budget = budgetEnv ? {
|
|
14613
|
+
periodSpendUsd: stats.cost?.cost_with_headroom_usd ?? 0,
|
|
14614
|
+
budgetUsd: budgetEnv.budgetUsd,
|
|
14615
|
+
budgetPeriod: budgetEnv.budgetPeriod
|
|
14616
|
+
} : void 0;
|
|
14617
|
+
await this.deps.postSavings(delta, budget);
|
|
14602
14618
|
}
|
|
14603
14619
|
} catch {
|
|
14604
14620
|
}
|
|
@@ -14611,6 +14627,14 @@ var HeadroomStatsReporter = class {
|
|
|
14611
14627
|
}
|
|
14612
14628
|
};
|
|
14613
14629
|
|
|
14630
|
+
// src/services/headroom/budget-args.ts
|
|
14631
|
+
function buildBudgetProxyArgs(env) {
|
|
14632
|
+
const budget = env["HEADROOM_BUDGET"];
|
|
14633
|
+
if (!budget) return [];
|
|
14634
|
+
const period = env["HEADROOM_BUDGET_PERIOD"] ?? "daily";
|
|
14635
|
+
return ["--budget", budget, "--budget-period", period];
|
|
14636
|
+
}
|
|
14637
|
+
|
|
14614
14638
|
// src/lib/updateNotifier.ts
|
|
14615
14639
|
var fs30 = __toESM(require("fs"));
|
|
14616
14640
|
var os27 = __toESM(require("os"));
|
|
@@ -14769,7 +14793,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
14769
14793
|
if (process.env.NODE_ENV === "test") return;
|
|
14770
14794
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
14771
14795
|
if (process.env.CI) return;
|
|
14772
|
-
const current = true ? "2.
|
|
14796
|
+
const current = true ? "2.51.0" : null;
|
|
14773
14797
|
if (!current) return;
|
|
14774
14798
|
const cache = readCache();
|
|
14775
14799
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -14786,7 +14810,7 @@ function checkForUpdates() {
|
|
|
14786
14810
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
14787
14811
|
if (process.env.CI) return;
|
|
14788
14812
|
if (!process.stdout.isTTY) return;
|
|
14789
|
-
const current = true ? "2.
|
|
14813
|
+
const current = true ? "2.51.0" : null;
|
|
14790
14814
|
if (!current) return;
|
|
14791
14815
|
const cache = readCache();
|
|
14792
14816
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -14823,7 +14847,7 @@ function maybeStartHeadroomReporter(ctx) {
|
|
|
14823
14847
|
const res = await fetch("http://localhost:8787/stats");
|
|
14824
14848
|
return res.json();
|
|
14825
14849
|
},
|
|
14826
|
-
postSavings: async (delta) => {
|
|
14850
|
+
postSavings: async (delta, budget) => {
|
|
14827
14851
|
await fetch(ingestUrl, {
|
|
14828
14852
|
method: "POST",
|
|
14829
14853
|
headers: {
|
|
@@ -14834,7 +14858,12 @@ function maybeStartHeadroomReporter(ctx) {
|
|
|
14834
14858
|
sessionId: ctx.sessionId,
|
|
14835
14859
|
pluginId: ctx.pluginId,
|
|
14836
14860
|
agentId: process.env["HEADROOM_AGENT"] ?? "claude",
|
|
14837
|
-
savings: delta
|
|
14861
|
+
savings: delta,
|
|
14862
|
+
...budget ? {
|
|
14863
|
+
periodSpendUsd: budget.periodSpendUsd,
|
|
14864
|
+
budgetUsd: budget.budgetUsd,
|
|
14865
|
+
budgetPeriod: budget.budgetPeriod
|
|
14866
|
+
} : {}
|
|
14838
14867
|
})
|
|
14839
14868
|
});
|
|
14840
14869
|
}
|
|
@@ -14867,7 +14896,7 @@ function maybeResumeLocalHeadroomReporter(ctx) {
|
|
|
14867
14896
|
// Body MUST match HeadroomSavingsDto + PluginAuthGuard (sessionId +
|
|
14868
14897
|
// pluginId in the body) — same shape as the codespace reporter above and
|
|
14869
14898
|
// the on-demand `startReporter` in handlers.ts.
|
|
14870
|
-
postSavings: async (delta) => {
|
|
14899
|
+
postSavings: async (delta, budget) => {
|
|
14871
14900
|
await fetch(ingestUrl, {
|
|
14872
14901
|
method: "POST",
|
|
14873
14902
|
headers: {
|
|
@@ -14878,7 +14907,12 @@ function maybeResumeLocalHeadroomReporter(ctx) {
|
|
|
14878
14907
|
sessionId: ctx.sessionId,
|
|
14879
14908
|
pluginId: ctx.pluginId,
|
|
14880
14909
|
agentId: agent,
|
|
14881
|
-
savings: delta
|
|
14910
|
+
savings: delta,
|
|
14911
|
+
...budget ? {
|
|
14912
|
+
periodSpendUsd: budget.periodSpendUsd,
|
|
14913
|
+
budgetUsd: budget.budgetUsd,
|
|
14914
|
+
budgetPeriod: budget.budgetPeriod
|
|
14915
|
+
} : {}
|
|
14882
14916
|
})
|
|
14883
14917
|
});
|
|
14884
14918
|
}
|
|
@@ -15179,11 +15213,16 @@ function readHeadroomChildEnv() {
|
|
|
15179
15213
|
if (typeof parsed !== "object" || parsed === null) return {};
|
|
15180
15214
|
const o = parsed;
|
|
15181
15215
|
if (o.enabled === true && typeof o.agent === "string" && o.agent.length > 0 && typeof o.ingestUrl === "string" && o.ingestUrl.length > 0) {
|
|
15182
|
-
|
|
15216
|
+
const env = {
|
|
15183
15217
|
HEADROOM_ENABLED: "1",
|
|
15184
15218
|
HEADROOM_AGENT: o.agent,
|
|
15185
15219
|
HEADROOM_SAVINGS_INGEST_URL: o.ingestUrl
|
|
15186
15220
|
};
|
|
15221
|
+
if (o.budgetEnabled === true && typeof o.budgetUsd === "number") {
|
|
15222
|
+
env["HEADROOM_BUDGET"] = String(o.budgetUsd);
|
|
15223
|
+
env["HEADROOM_BUDGET_PERIOD"] = typeof o.budgetPeriod === "string" ? o.budgetPeriod : "daily";
|
|
15224
|
+
}
|
|
15225
|
+
return env;
|
|
15187
15226
|
}
|
|
15188
15227
|
return {};
|
|
15189
15228
|
} catch {
|
|
@@ -15439,11 +15478,16 @@ async function setupHeadroomForSelfHosted(agent, runner = defaultHeadroomRunner,
|
|
|
15439
15478
|
}
|
|
15440
15479
|
onProgress("proxy");
|
|
15441
15480
|
try {
|
|
15442
|
-
const
|
|
15443
|
-
|
|
15444
|
-
|
|
15445
|
-
|
|
15446
|
-
|
|
15481
|
+
const proxyEnv = { ...process.env, HEADROOM_KOMPRESS_BACKEND: "onnx_cpu" };
|
|
15482
|
+
const proxy = (0, import_node_child_process14.spawn)(
|
|
15483
|
+
"headroom",
|
|
15484
|
+
["proxy", "--port", "8787", ...buildBudgetProxyArgs(proxyEnv)],
|
|
15485
|
+
{
|
|
15486
|
+
stdio: "ignore",
|
|
15487
|
+
detached: true,
|
|
15488
|
+
env: proxyEnv
|
|
15489
|
+
}
|
|
15490
|
+
);
|
|
15447
15491
|
proxy.once("error", (e) => {
|
|
15448
15492
|
log.warn(
|
|
15449
15493
|
"host-agent",
|
|
@@ -15467,7 +15511,7 @@ var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process14.s
|
|
|
15467
15511
|
detached: false
|
|
15468
15512
|
});
|
|
15469
15513
|
function currentCliVersion() {
|
|
15470
|
-
return true ? "2.
|
|
15514
|
+
return true ? "2.51.0" : null;
|
|
15471
15515
|
}
|
|
15472
15516
|
function runCmd(cmd, args2, timeoutMs) {
|
|
15473
15517
|
return new Promise((resolve7) => {
|
|
@@ -18527,7 +18571,7 @@ var headroomConfigureH = async (ctx, cmd, parsed) => {
|
|
|
18527
18571
|
const res = await fetch("http://localhost:8787/stats");
|
|
18528
18572
|
return res.json();
|
|
18529
18573
|
},
|
|
18530
|
-
postSavings: async (delta) => {
|
|
18574
|
+
postSavings: async (delta, budget) => {
|
|
18531
18575
|
if (!opts.ingestUrl) return;
|
|
18532
18576
|
await fetch(opts.ingestUrl, {
|
|
18533
18577
|
method: "POST",
|
|
@@ -18546,7 +18590,12 @@ var headroomConfigureH = async (ctx, cmd, parsed) => {
|
|
|
18546
18590
|
sessionId: ctx.sessionId,
|
|
18547
18591
|
pluginId: ctx.pluginId,
|
|
18548
18592
|
agentId: opts.agent,
|
|
18549
|
-
savings: delta
|
|
18593
|
+
savings: delta,
|
|
18594
|
+
...budget ? {
|
|
18595
|
+
periodSpendUsd: budget.periodSpendUsd,
|
|
18596
|
+
budgetUsd: budget.budgetUsd,
|
|
18597
|
+
budgetPeriod: budget.budgetPeriod
|
|
18598
|
+
} : {}
|
|
18550
18599
|
})
|
|
18551
18600
|
});
|
|
18552
18601
|
}
|
|
@@ -18587,6 +18636,89 @@ var headroomConfigureH = async (ctx, cmd, parsed) => {
|
|
|
18587
18636
|
});
|
|
18588
18637
|
await ctx.relay.sendResult(cmd.id, "completed", result);
|
|
18589
18638
|
};
|
|
18639
|
+
var headroomBudgetH = async (ctx, cmd) => {
|
|
18640
|
+
const payload = cmd.payload;
|
|
18641
|
+
let rawAgentId = ctx.agentId || (typeof payload.agentId === "string" ? payload.agentId : "");
|
|
18642
|
+
if (rawAgentId === "claude_code") rawAgentId = "claude";
|
|
18643
|
+
let payloadAgentId = typeof payload.agentId === "string" ? payload.agentId : "";
|
|
18644
|
+
if (payloadAgentId === "claude_code") payloadAgentId = "claude";
|
|
18645
|
+
if (!rawAgentId || !isHeadroomSupportedAgent(rawAgentId)) {
|
|
18646
|
+
await ctx.relay.sendResult(cmd.id, "completed", { applied: false });
|
|
18647
|
+
return;
|
|
18648
|
+
}
|
|
18649
|
+
if (!payloadAgentId || payloadAgentId !== rawAgentId) {
|
|
18650
|
+
await ctx.relay.sendResult(cmd.id, "completed", { applied: false });
|
|
18651
|
+
return;
|
|
18652
|
+
}
|
|
18653
|
+
let headroomActive = false;
|
|
18654
|
+
try {
|
|
18655
|
+
const raw = JSON.parse(fs41.readFileSync(headroomConfigPath(), "utf8"));
|
|
18656
|
+
headroomActive = raw.enabled === true;
|
|
18657
|
+
} catch {
|
|
18658
|
+
}
|
|
18659
|
+
if (!headroomActive) {
|
|
18660
|
+
await ctx.relay.sendResult(cmd.id, "completed", { applied: false });
|
|
18661
|
+
return;
|
|
18662
|
+
}
|
|
18663
|
+
let existingConfig = { enabled: true };
|
|
18664
|
+
try {
|
|
18665
|
+
existingConfig = JSON.parse(fs41.readFileSync(headroomConfigPath(), "utf8"));
|
|
18666
|
+
} catch {
|
|
18667
|
+
}
|
|
18668
|
+
if (payload.budgetEnabled && payload.budgetUsd != null) {
|
|
18669
|
+
persistHeadroomConfig({
|
|
18670
|
+
...existingConfig,
|
|
18671
|
+
enabled: existingConfig.enabled ?? true,
|
|
18672
|
+
budgetEnabled: true,
|
|
18673
|
+
budgetUsd: payload.budgetUsd,
|
|
18674
|
+
budgetPeriod: payload.budgetPeriod ?? "daily"
|
|
18675
|
+
});
|
|
18676
|
+
process.env["HEADROOM_BUDGET"] = String(payload.budgetUsd);
|
|
18677
|
+
process.env["HEADROOM_BUDGET_PERIOD"] = payload.budgetPeriod ?? "daily";
|
|
18678
|
+
} else {
|
|
18679
|
+
persistHeadroomConfig({
|
|
18680
|
+
...existingConfig,
|
|
18681
|
+
enabled: existingConfig.enabled ?? true,
|
|
18682
|
+
budgetEnabled: false,
|
|
18683
|
+
budgetUsd: void 0,
|
|
18684
|
+
budgetPeriod: void 0
|
|
18685
|
+
});
|
|
18686
|
+
delete process.env["HEADROOM_BUDGET"];
|
|
18687
|
+
delete process.env["HEADROOM_BUDGET_PERIOD"];
|
|
18688
|
+
}
|
|
18689
|
+
try {
|
|
18690
|
+
const killer = (0, import_child_process20.spawn)("pkill", ["-TERM", "-f", "headroom.*proxy"], {
|
|
18691
|
+
detached: true,
|
|
18692
|
+
stdio: "ignore"
|
|
18693
|
+
});
|
|
18694
|
+
killer.once("error", () => {
|
|
18695
|
+
});
|
|
18696
|
+
killer.unref();
|
|
18697
|
+
} catch {
|
|
18698
|
+
}
|
|
18699
|
+
try {
|
|
18700
|
+
const proxyEnv = { ...process.env, HEADROOM_KOMPRESS_BACKEND: "onnx_cpu" };
|
|
18701
|
+
const proxy = (0, import_child_process20.spawn)(
|
|
18702
|
+
"headroom",
|
|
18703
|
+
["proxy", "--port", "8787", ...buildBudgetProxyArgs(proxyEnv)],
|
|
18704
|
+
{
|
|
18705
|
+
stdio: "ignore",
|
|
18706
|
+
detached: true,
|
|
18707
|
+
env: proxyEnv
|
|
18708
|
+
}
|
|
18709
|
+
);
|
|
18710
|
+
proxy.once("error", (e) => {
|
|
18711
|
+
log.warn("headroom-budget", `proxy relaunch error (best-effort): ${e.message}`);
|
|
18712
|
+
});
|
|
18713
|
+
proxy.unref();
|
|
18714
|
+
} catch (e) {
|
|
18715
|
+
log.warn(
|
|
18716
|
+
"headroom-budget",
|
|
18717
|
+
`proxy relaunch failed (best-effort): ${e instanceof Error ? e.message : String(e)}`
|
|
18718
|
+
);
|
|
18719
|
+
}
|
|
18720
|
+
await ctx.relay.sendResult(cmd.id, "completed", { applied: true });
|
|
18721
|
+
};
|
|
18590
18722
|
var _beadsEmitChain = Promise.resolve();
|
|
18591
18723
|
var beadsConfigureH = async (ctx, cmd, parsed) => {
|
|
18592
18724
|
const action = parsed.action;
|
|
@@ -19632,6 +19764,7 @@ var handlers = {
|
|
|
19632
19764
|
env_read: envReadH,
|
|
19633
19765
|
env_write: envWriteH,
|
|
19634
19766
|
headroom_configure: headroomConfigureH,
|
|
19767
|
+
headroom_budget: headroomBudgetH,
|
|
19635
19768
|
beads_configure: beadsConfigureH
|
|
19636
19769
|
};
|
|
19637
19770
|
async function dispatchCommand(ctx, cmd) {
|
|
@@ -24504,6 +24637,79 @@ function createOneMRecovery(deps) {
|
|
|
24504
24637
|
};
|
|
24505
24638
|
}
|
|
24506
24639
|
|
|
24640
|
+
// src/agents/acp/budgetRecovery.ts
|
|
24641
|
+
var BUDGET_EXCEEDED_RE = /budget exceeded for (\w+) period/i;
|
|
24642
|
+
function looksLikeBudgetExceeded(text) {
|
|
24643
|
+
return BUDGET_EXCEEDED_RE.test(text);
|
|
24644
|
+
}
|
|
24645
|
+
function extractBudgetPeriod(text) {
|
|
24646
|
+
const m = BUDGET_EXCEEDED_RE.exec(text);
|
|
24647
|
+
return m ? m[1] ?? "current" : "current";
|
|
24648
|
+
}
|
|
24649
|
+
var BUDGET_PAUSE_OPTION = "Pause budget this session";
|
|
24650
|
+
var BUDGET_RAISE_OPTION = "Raise budget";
|
|
24651
|
+
function createBudgetRecovery(deps) {
|
|
24652
|
+
let pending = null;
|
|
24653
|
+
const buildQuestion = (period) => `\u{1F4B8} **Headroom budget reached for the ${period} period.**
|
|
24654
|
+
|
|
24655
|
+
You have two options:
|
|
24656
|
+
\u2022 **Pause budget this session** \u2014 removes the spending cap for this session only (the proxy restarts without a budget limit).
|
|
24657
|
+
\u2022 **Raise budget** \u2014 open your agent budget settings to increase the cap.`;
|
|
24658
|
+
return {
|
|
24659
|
+
offer: async (commandId, blocks, detail) => {
|
|
24660
|
+
const period = extractBudgetPeriod(detail);
|
|
24661
|
+
pending = { blocks, period };
|
|
24662
|
+
const question = buildQuestion(period);
|
|
24663
|
+
const options = [BUDGET_PAUSE_OPTION, BUDGET_RAISE_OPTION];
|
|
24664
|
+
await deps.publishText(question);
|
|
24665
|
+
await deps.publishSelectPrompt(question, options);
|
|
24666
|
+
await deps.publishAwaitingAnswer(question, options);
|
|
24667
|
+
deps.appendAgentReply(question);
|
|
24668
|
+
deps.flushHistory();
|
|
24669
|
+
deps.log?.(`budget-exceeded recovery offered period=${period} id=${commandId.slice(0, 8)}`);
|
|
24670
|
+
await deps.sendResult(commandId, "failed", {
|
|
24671
|
+
error: `Headroom budget exceeded (${period}) \u2014 recovery offered`
|
|
24672
|
+
});
|
|
24673
|
+
},
|
|
24674
|
+
tryRecover: async (commandId, optionIndex) => {
|
|
24675
|
+
if (!pending) return false;
|
|
24676
|
+
const { blocks, period } = pending;
|
|
24677
|
+
pending = null;
|
|
24678
|
+
if (optionIndex === 0) {
|
|
24679
|
+
deps.log?.("budget recovery \u2192 pause: relaunching proxy without budget");
|
|
24680
|
+
try {
|
|
24681
|
+
await deps.relaunchProxyWithoutBudget();
|
|
24682
|
+
} catch (err) {
|
|
24683
|
+
deps.log?.(`budget recovery \u2192 relaunch failed (best-effort): ${String(err)}`);
|
|
24684
|
+
}
|
|
24685
|
+
await deps.sendResult(commandId, "completed", {
|
|
24686
|
+
action: "pause",
|
|
24687
|
+
period,
|
|
24688
|
+
note: "Headroom proxy relaunched without budget limit for this session. Send your message again."
|
|
24689
|
+
});
|
|
24690
|
+
const note = "\u2705 **Budget paused for this session.** The proxy is running without a spending cap \u2014 send your message again.";
|
|
24691
|
+
await deps.publishText(note);
|
|
24692
|
+
deps.appendAgentReply(note);
|
|
24693
|
+
deps.flushHistory();
|
|
24694
|
+
deps.log?.(`budget recovery \u2192 pause complete id=${commandId.slice(0, 8)}`);
|
|
24695
|
+
} else {
|
|
24696
|
+
deps.log?.(`budget recovery \u2192 raise: emitting deep-link for agent=${deps.agentId}`);
|
|
24697
|
+
await deps.publishRawChunk({
|
|
24698
|
+
type: "open_agent_budget_settings",
|
|
24699
|
+
agentId: deps.agentId,
|
|
24700
|
+
done: true
|
|
24701
|
+
});
|
|
24702
|
+
await deps.sendResult(commandId, "completed", {
|
|
24703
|
+
action: "raise",
|
|
24704
|
+
agentId: deps.agentId
|
|
24705
|
+
});
|
|
24706
|
+
deps.log?.(`budget recovery \u2192 raise emitted id=${commandId.slice(0, 8)}`);
|
|
24707
|
+
}
|
|
24708
|
+
return true;
|
|
24709
|
+
}
|
|
24710
|
+
};
|
|
24711
|
+
}
|
|
24712
|
+
|
|
24507
24713
|
// src/agents/acp/reconcileDelta.ts
|
|
24508
24714
|
function reconcileCumulative(existing, incoming) {
|
|
24509
24715
|
if (incoming.length === 0) return existing;
|
|
@@ -25938,10 +26144,26 @@ async function surfaceStartupFailure(opts) {
|
|
|
25938
26144
|
);
|
|
25939
26145
|
errRelay.start();
|
|
25940
26146
|
}
|
|
26147
|
+
function budgetBubbleMessage(agent, period) {
|
|
26148
|
+
return `\u{1F4B8} **Headroom budget reached for the ${period} period.**
|
|
26149
|
+
|
|
26150
|
+
The local Headroom proxy rejected the ${agent} request because the configured spending cap was hit. Choose an option below to continue.`;
|
|
26151
|
+
}
|
|
26152
|
+
function buildRelaunchProxyEnv(baseEnv) {
|
|
26153
|
+
const env = { ...baseEnv, HEADROOM_KOMPRESS_BACKEND: "onnx_cpu" };
|
|
26154
|
+
delete env["HEADROOM_BUDGET"];
|
|
26155
|
+
delete env["HEADROOM_BUDGET_PERIOD"];
|
|
26156
|
+
return env;
|
|
26157
|
+
}
|
|
25941
26158
|
function failureBubble(opts) {
|
|
25942
26159
|
if (looksLikeAuthFailure(opts.detail) || looksLikeAuthFailure(opts.recentStderr)) {
|
|
25943
26160
|
return AUTH_FAILURE_MESSAGE;
|
|
25944
26161
|
}
|
|
26162
|
+
const budgetHaystack = `${opts.detail}
|
|
26163
|
+
${opts.recentStderr}`;
|
|
26164
|
+
if (looksLikeBudgetExceeded(budgetHaystack)) {
|
|
26165
|
+
return budgetBubbleMessage(opts.agent, extractBudgetPeriod(budgetHaystack));
|
|
26166
|
+
}
|
|
25945
26167
|
if (looksLikeProviderOutage(opts.detail) || looksLikeProviderOutage(opts.recentStderr)) {
|
|
25946
26168
|
return providerOutageMessage(opts.agent);
|
|
25947
26169
|
}
|
|
@@ -25974,6 +26196,25 @@ async function reportCredentialInvalid(opts, fetchImpl = fetch) {
|
|
|
25974
26196
|
} catch {
|
|
25975
26197
|
}
|
|
25976
26198
|
}
|
|
26199
|
+
async function postBudgetReached(opts, fetchImpl = fetch) {
|
|
26200
|
+
const url = `${resolveApiBaseUrl()}/api/sessions/${encodeURIComponent(opts.sessionId)}/headroom-budget-reached`;
|
|
26201
|
+
try {
|
|
26202
|
+
await fetchImpl(url, {
|
|
26203
|
+
method: "POST",
|
|
26204
|
+
headers: {
|
|
26205
|
+
"Content-Type": "application/json",
|
|
26206
|
+
"X-Plugin-Auth-Token": opts.pluginAuthToken
|
|
26207
|
+
},
|
|
26208
|
+
body: JSON.stringify({
|
|
26209
|
+
sessionId: opts.sessionId,
|
|
26210
|
+
pluginId: opts.pluginId,
|
|
26211
|
+
agent: opts.agent,
|
|
26212
|
+
period: opts.period
|
|
26213
|
+
})
|
|
26214
|
+
});
|
|
26215
|
+
} catch {
|
|
26216
|
+
}
|
|
26217
|
+
}
|
|
25977
26218
|
async function handleGetConversation(args2) {
|
|
25978
26219
|
const { relay, commandId, jsonlHistory, acpSessionId } = args2;
|
|
25979
26220
|
try {
|
|
@@ -26139,6 +26380,58 @@ async function runAcpSession(opts) {
|
|
|
26139
26380
|
describeError,
|
|
26140
26381
|
log: (msg) => log.info("acpRunner", msg)
|
|
26141
26382
|
});
|
|
26383
|
+
let _budgetReachedPosted = false;
|
|
26384
|
+
const relaunchProxyWithoutBudget = async () => {
|
|
26385
|
+
const { spawn: spawn32 } = await import("child_process");
|
|
26386
|
+
try {
|
|
26387
|
+
const killer = spawn32("pkill", ["-TERM", "-f", "headroom.*proxy"], {
|
|
26388
|
+
detached: true,
|
|
26389
|
+
stdio: "ignore"
|
|
26390
|
+
});
|
|
26391
|
+
killer.once("error", () => {
|
|
26392
|
+
});
|
|
26393
|
+
killer.unref();
|
|
26394
|
+
} catch {
|
|
26395
|
+
}
|
|
26396
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
26397
|
+
const proxyEnv = buildRelaunchProxyEnv(process.env);
|
|
26398
|
+
try {
|
|
26399
|
+
const proxy = spawn32(
|
|
26400
|
+
"headroom",
|
|
26401
|
+
["proxy", "--port", "8787"],
|
|
26402
|
+
{ stdio: "ignore", detached: true, env: proxyEnv }
|
|
26403
|
+
);
|
|
26404
|
+
proxy.once("error", (e) => {
|
|
26405
|
+
log.warn("acpRunner", `budget recovery proxy relaunch error (best-effort): ${e.message}`);
|
|
26406
|
+
});
|
|
26407
|
+
proxy.unref();
|
|
26408
|
+
} catch (e) {
|
|
26409
|
+
log.warn(
|
|
26410
|
+
"acpRunner",
|
|
26411
|
+
`budget recovery proxy relaunch failed (best-effort): ${e instanceof Error ? e.message : String(e)}`
|
|
26412
|
+
);
|
|
26413
|
+
}
|
|
26414
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
26415
|
+
};
|
|
26416
|
+
const budgetRecovery = createBudgetRecovery({
|
|
26417
|
+
publishText: (text) => publisher.publishOutput({ type: "text", content: text, done: true }),
|
|
26418
|
+
publishSelectPrompt: (question, options) => publisher.publishOutput({
|
|
26419
|
+
type: "select_prompt",
|
|
26420
|
+
content: question,
|
|
26421
|
+
options,
|
|
26422
|
+
optionDescriptions: options.map(() => ""),
|
|
26423
|
+
currentIndex: 0,
|
|
26424
|
+
done: true
|
|
26425
|
+
}),
|
|
26426
|
+
publishAwaitingAnswer: (prompt, options) => publisher.publishAwaitingAnswer({ questionId: (0, import_node_crypto7.randomUUID)(), prompt, options }),
|
|
26427
|
+
publishRawChunk: (chunk) => publisher.publishOutput(chunk),
|
|
26428
|
+
sendResult: (commandId, status2, result) => relay.sendResult(commandId, status2, result),
|
|
26429
|
+
appendAgentReply: (text) => history.appendAgentReply(text),
|
|
26430
|
+
flushHistory: () => void history.flush(),
|
|
26431
|
+
relaunchProxyWithoutBudget,
|
|
26432
|
+
agentId: opts.agent,
|
|
26433
|
+
log: (msg) => log.info("acpRunner", msg)
|
|
26434
|
+
});
|
|
26142
26435
|
showInfo(`Starting ${opts.agent} via ACP adapter (${opts.adapter.requiresAgentBinary})\u2026`);
|
|
26143
26436
|
let handshake;
|
|
26144
26437
|
try {
|
|
@@ -26248,7 +26541,11 @@ async function runAcpSession(opts) {
|
|
|
26248
26541
|
getBeads,
|
|
26249
26542
|
publisher,
|
|
26250
26543
|
recentStderr,
|
|
26251
|
-
oneMRecovery
|
|
26544
|
+
oneMRecovery,
|
|
26545
|
+
budgetRecovery,
|
|
26546
|
+
{ get: () => _budgetReachedPosted, set: (v) => {
|
|
26547
|
+
_budgetReachedPosted = v;
|
|
26548
|
+
} }
|
|
26252
26549
|
);
|
|
26253
26550
|
},
|
|
26254
26551
|
{ id: opts.agent, name: opts.agent, displayName: opts.agent }
|
|
@@ -26272,7 +26569,7 @@ async function runAcpSession(opts) {
|
|
|
26272
26569
|
await new Promise(() => {
|
|
26273
26570
|
});
|
|
26274
26571
|
}
|
|
26275
|
-
async function handleCommand(cmd, client2, relay, acpSessionId, models, streaming, opts, history, jsonlHistory, agentCaps, turnFiles, getBeads, publisher, recentStderr, oneMRecovery) {
|
|
26572
|
+
async function handleCommand(cmd, client2, relay, acpSessionId, models, streaming, opts, history, jsonlHistory, agentCaps, turnFiles, getBeads, publisher, recentStderr, oneMRecovery, budgetRecovery, budgetReachedFlag) {
|
|
26276
26573
|
switch (cmd.type) {
|
|
26277
26574
|
case "beads_action": {
|
|
26278
26575
|
const beads = getBeads();
|
|
@@ -26356,6 +26653,24 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
|
|
|
26356
26653
|
const detail = describeError(err);
|
|
26357
26654
|
log.warn("acpRunner", `prompt failed: ${detail}`);
|
|
26358
26655
|
await cancelStuckTurn(client2);
|
|
26656
|
+
if (looksLikeBudgetExceeded(`${detail}
|
|
26657
|
+
${recentStderr.join("\n")}`)) {
|
|
26658
|
+
await streaming.closeAll();
|
|
26659
|
+
if (!budgetReachedFlag.get()) {
|
|
26660
|
+
budgetReachedFlag.set(true);
|
|
26661
|
+
void postBudgetReached({
|
|
26662
|
+
sessionId: opts.sessionId,
|
|
26663
|
+
pluginId: opts.pluginId,
|
|
26664
|
+
pluginAuthToken: opts.pluginAuthToken,
|
|
26665
|
+
agent: opts.agent,
|
|
26666
|
+
period: extractBudgetPeriod(`${detail}
|
|
26667
|
+
${recentStderr.join("\n")}`)
|
|
26668
|
+
});
|
|
26669
|
+
}
|
|
26670
|
+
await budgetRecovery.offer(cmd.id, blocks, `${detail}
|
|
26671
|
+
${recentStderr.join("\n")}`);
|
|
26672
|
+
return;
|
|
26673
|
+
}
|
|
26359
26674
|
if (shouldOfferOneMRecovery({ detail, recentStderr: recentStderr.join("\n"), finalText: "" })) {
|
|
26360
26675
|
await streaming.closeAll();
|
|
26361
26676
|
await oneMRecovery.offer(cmd.id, blocks);
|
|
@@ -26462,6 +26777,7 @@ async function handleCommand(cmd, client2, relay, acpSessionId, models, streamin
|
|
|
26462
26777
|
const index = typeof payload?.index === "number" ? payload.index : 0;
|
|
26463
26778
|
const offset = typeof payload?.from === "number" ? payload.from : 0;
|
|
26464
26779
|
const absoluteIndex = index + offset;
|
|
26780
|
+
if (await budgetRecovery.tryRecover(cmd.id, absoluteIndex)) return;
|
|
26465
26781
|
const result = streaming.resolveSelection(absoluteIndex);
|
|
26466
26782
|
switch (result.kind) {
|
|
26467
26783
|
case "resolved":
|
|
@@ -30602,7 +30918,7 @@ function checkChokidar() {
|
|
|
30602
30918
|
}
|
|
30603
30919
|
async function doctor(args2 = []) {
|
|
30604
30920
|
const json = args2.includes("--json");
|
|
30605
|
-
const cliVersion = true ? "2.
|
|
30921
|
+
const cliVersion = true ? "2.51.0" : "0.0.0-dev";
|
|
30606
30922
|
const apiBase2 = resolveApiBaseUrl();
|
|
30607
30923
|
const diagnosticId = (0, import_node_crypto8.randomUUID)();
|
|
30608
30924
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -30801,7 +31117,7 @@ async function completion(args2) {
|
|
|
30801
31117
|
// src/commands/version.ts
|
|
30802
31118
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
30803
31119
|
function version2() {
|
|
30804
|
-
const v = true ? "2.
|
|
31120
|
+
const v = true ? "2.51.0" : "unknown";
|
|
30805
31121
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
30806
31122
|
}
|
|
30807
31123
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.51.0",
|
|
4
4
|
"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 — async. The terminal companion for CodeAgent Mobile.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "dist/index.js",
|