codeam-cli 2.65.13 → 2.65.15
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 +16 -0
- package/dist/index.js +257 -64
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ 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.65.14] — 2026-08-20
|
|
8
|
+
|
|
9
|
+
### CI
|
|
10
|
+
|
|
11
|
+
- **workflow:** All workflows report to Discord (catch-all) (#643)
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **cli:** Permission guards never scan plan text and never auto-reject silently
|
|
16
|
+
|
|
17
|
+
## [2.65.13] — 2026-08-20
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **cli:** Filter non-chat models out of the native ACP model catalog
|
|
22
|
+
|
|
7
23
|
## [2.65.12] — 2026-08-19
|
|
8
24
|
|
|
9
25
|
### CI
|
package/dist/index.js
CHANGED
|
@@ -8079,7 +8079,7 @@ function readAnonId() {
|
|
|
8079
8079
|
}
|
|
8080
8080
|
function superProperties() {
|
|
8081
8081
|
return {
|
|
8082
|
-
cliVersion: true ? "2.65.
|
|
8082
|
+
cliVersion: true ? "2.65.15" : "0.0.0-dev",
|
|
8083
8083
|
nodeVersion: process.version,
|
|
8084
8084
|
platform: process.platform,
|
|
8085
8085
|
arch: process.arch,
|
|
@@ -8321,7 +8321,7 @@ var http = __toESM(require("http"));
|
|
|
8321
8321
|
// package.json
|
|
8322
8322
|
var package_default = {
|
|
8323
8323
|
name: "codeam-cli",
|
|
8324
|
-
version: "2.65.
|
|
8324
|
+
version: "2.65.15",
|
|
8325
8325
|
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.",
|
|
8326
8326
|
type: "commonjs",
|
|
8327
8327
|
main: "dist/index.js",
|
|
@@ -9842,7 +9842,7 @@ var CommandRelayService = class _CommandRelayService {
|
|
|
9842
9842
|
// fresh + clear the "CLI update available" banner after a self-update
|
|
9843
9843
|
// (a codespace that reinstalls @latest reconnects via heartbeat, not
|
|
9844
9844
|
// pair/reconnect). Older backends ignore the extra field.
|
|
9845
|
-
..."2.65.
|
|
9845
|
+
..."2.65.15" ? { ideVersion: "2.65.15" } : {}
|
|
9846
9846
|
}).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
|
|
9847
9847
|
}
|
|
9848
9848
|
/**
|
|
@@ -18315,22 +18315,31 @@ async function waitForCommandOnPath(cmd, opts = {}) {
|
|
|
18315
18315
|
}
|
|
18316
18316
|
return check();
|
|
18317
18317
|
}
|
|
18318
|
-
function
|
|
18318
|
+
function agentInstallBinDirs(deps = {}) {
|
|
18319
18319
|
const env = deps.env ?? process.env;
|
|
18320
18320
|
const home = deps.homedir ?? import_os4.default.homedir();
|
|
18321
18321
|
const p2 = deps.pathApi ?? import_path4.default;
|
|
18322
|
-
|
|
18322
|
+
return [
|
|
18323
18323
|
// XDG-style per-user bin — npm's default global-prefix bin dir on most
|
|
18324
18324
|
// Linux setups (`npm config set prefix ~/.local` or an nvm-less
|
|
18325
|
-
// per-user npm), and where curl-based agent installers commonly land
|
|
18325
|
+
// per-user npm), and where curl-based agent installers commonly land
|
|
18326
|
+
// (cursor-agent, coderabbit, pip --user's aider).
|
|
18326
18327
|
p2.join(home, ".local", "bin"),
|
|
18327
18328
|
// Common explicit npm global-prefix conventions seen in the wild
|
|
18328
18329
|
// (`npm config set prefix ~/.npm-global`, and Debian/Fedora's
|
|
18329
18330
|
// `~/.local/share/npm` layout for `npm config set prefix
|
|
18330
18331
|
// ~/.local/share/npm`).
|
|
18331
18332
|
p2.join(home, ".npm-global", "bin"),
|
|
18332
|
-
p2.join(home, ".local", "share", "npm", "bin")
|
|
18333
|
+
p2.join(home, ".local", "share", "npm", "bin"),
|
|
18334
|
+
// Vendor curl-installer targets that are NOT on any default PATH.
|
|
18335
|
+
p2.join(env.KIMI_CODE_HOME || p2.join(home, ".kimi-code"), "bin"),
|
|
18336
|
+
p2.join(env.OPENCODE_HOME || p2.join(home, ".opencode"), "bin")
|
|
18333
18337
|
];
|
|
18338
|
+
}
|
|
18339
|
+
function augmentUserLocalBinPaths(deps = {}) {
|
|
18340
|
+
const env = deps.env ?? process.env;
|
|
18341
|
+
const p2 = deps.pathApi ?? import_path4.default;
|
|
18342
|
+
const candidates = agentInstallBinDirs(deps);
|
|
18334
18343
|
const parts = (env.PATH ?? "").split(p2.delimiter).filter((s) => s.length > 0);
|
|
18335
18344
|
const existing = new Set(parts);
|
|
18336
18345
|
const additions = candidates.filter((dir) => !existing.has(dir));
|
|
@@ -20784,6 +20793,38 @@ async function reportDeployProgress(auth, deployId, step, message, sessionId) {
|
|
|
20784
20793
|
} catch {
|
|
20785
20794
|
}
|
|
20786
20795
|
}
|
|
20796
|
+
async function postSessionErrorBubble(auth, message) {
|
|
20797
|
+
const chunks = [
|
|
20798
|
+
{ type: "new_turn", done: false },
|
|
20799
|
+
{ type: "text", content: message, done: true }
|
|
20800
|
+
];
|
|
20801
|
+
for (const chunk of chunks) {
|
|
20802
|
+
try {
|
|
20803
|
+
const controller = new AbortController();
|
|
20804
|
+
const timer = setTimeout(() => controller.abort(), PROGRESS_TIMEOUT_MS);
|
|
20805
|
+
timer.unref?.();
|
|
20806
|
+
try {
|
|
20807
|
+
await fetch(`${apiBase()}/api/commands/output`, {
|
|
20808
|
+
method: "POST",
|
|
20809
|
+
headers: {
|
|
20810
|
+
"Content-Type": "application/json",
|
|
20811
|
+
"X-Plugin-Auth-Token": auth.pluginAuthToken,
|
|
20812
|
+
...vercelBypassHeader()
|
|
20813
|
+
},
|
|
20814
|
+
body: JSON.stringify({
|
|
20815
|
+
sessionId: auth.sessionId,
|
|
20816
|
+
pluginId: auth.pluginId,
|
|
20817
|
+
...chunk
|
|
20818
|
+
}),
|
|
20819
|
+
signal: controller.signal
|
|
20820
|
+
});
|
|
20821
|
+
} finally {
|
|
20822
|
+
clearTimeout(timer);
|
|
20823
|
+
}
|
|
20824
|
+
} catch {
|
|
20825
|
+
}
|
|
20826
|
+
}
|
|
20827
|
+
}
|
|
20787
20828
|
|
|
20788
20829
|
// src/commands/host/workspace.ts
|
|
20789
20830
|
var fs39 = __toESM(require("fs"));
|
|
@@ -21966,7 +22007,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
21966
22007
|
if (process.env.NODE_ENV === "test") return;
|
|
21967
22008
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
21968
22009
|
if (process.env.CI) return;
|
|
21969
|
-
const current2 = true ? "2.65.
|
|
22010
|
+
const current2 = true ? "2.65.15" : null;
|
|
21970
22011
|
if (!current2) return;
|
|
21971
22012
|
const cache = readCache();
|
|
21972
22013
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -21983,7 +22024,7 @@ function checkForUpdates() {
|
|
|
21983
22024
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
21984
22025
|
if (process.env.CI) return;
|
|
21985
22026
|
if (!process.stdout.isTTY) return;
|
|
21986
|
-
const current2 = true ? "2.65.
|
|
22027
|
+
const current2 = true ? "2.65.15" : null;
|
|
21987
22028
|
if (!current2) return;
|
|
21988
22029
|
const cache = readCache();
|
|
21989
22030
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -22003,7 +22044,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
|
22003
22044
|
var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
|
|
22004
22045
|
var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
|
|
22005
22046
|
function currentCliVersion() {
|
|
22006
|
-
return true ? "2.65.
|
|
22047
|
+
return true ? "2.65.15" : null;
|
|
22007
22048
|
}
|
|
22008
22049
|
function runCmd(cmd, args2, timeoutMs) {
|
|
22009
22050
|
return new Promise((resolve10) => {
|
|
@@ -22096,6 +22137,9 @@ function resolveInputPricePerMillion(agentId) {
|
|
|
22096
22137
|
return getPricing(model).input;
|
|
22097
22138
|
}
|
|
22098
22139
|
var HEARTBEAT_INTERVAL_MS = 2e4;
|
|
22140
|
+
var RESUME_RETRY_BACKOFF_MS = [15e3, 3e4, 6e4, 12e4];
|
|
22141
|
+
var RESUME_REPROBE_INTERVAL_MS = 5 * 6e4;
|
|
22142
|
+
var RESUME_HEALTHY_AFTER_MS = 10 * 6e4;
|
|
22099
22143
|
function maybeStartHeadroomReporter(ctx) {
|
|
22100
22144
|
if (process.env["HEADROOM_ENABLED"] !== "1") return null;
|
|
22101
22145
|
try {
|
|
@@ -22431,6 +22475,7 @@ var HostAgentSupervisor = class {
|
|
|
22431
22475
|
this.selfUpdate = deps.selfUpdate ?? runSelfUpdate;
|
|
22432
22476
|
this.onUpdated = deps.onUpdated ?? defaultOnUpdated;
|
|
22433
22477
|
this.docker = deps.docker ?? defaultDockerRunner;
|
|
22478
|
+
this.postResumeFailure = deps.postResumeFailure ?? postSessionErrorBubble;
|
|
22434
22479
|
}
|
|
22435
22480
|
identity;
|
|
22436
22481
|
deps;
|
|
@@ -22472,6 +22517,20 @@ var HostAgentSupervisor = class {
|
|
|
22472
22517
|
docker;
|
|
22473
22518
|
/** Guards against firing the self-heal more than once. */
|
|
22474
22519
|
healing = false;
|
|
22520
|
+
/** Visible-error poster for the exhausted-resume path (injectable). */
|
|
22521
|
+
postResumeFailure;
|
|
22522
|
+
/** How many resume re-spawns have failed since the last healthy child. */
|
|
22523
|
+
resumeRetryAttempts = 0;
|
|
22524
|
+
/** Pending bounded-backoff resume retry (cleared on stop()). */
|
|
22525
|
+
resumeRetryTimer = null;
|
|
22526
|
+
/** Set once the bounded retries exhaust → heartbeat-ridden slow re-probe. */
|
|
22527
|
+
resumeExhausted = false;
|
|
22528
|
+
/** Guards the one-shot visible error bubble per failure episode. */
|
|
22529
|
+
resumeFailurePosted = false;
|
|
22530
|
+
/** Last slow re-probe attempt (epoch ms) — throttles the heartbeat rider. */
|
|
22531
|
+
lastResumeReprobeAt = 0;
|
|
22532
|
+
/** True after stop() — no resume retries may be scheduled past teardown. */
|
|
22533
|
+
stopped = false;
|
|
22475
22534
|
/**
|
|
22476
22535
|
* Resolve the self-update interval, honoring `CODEAM_HOST_SELF_UPDATE_MS`.
|
|
22477
22536
|
* A finite value > 0 overrides the default; 0 or negative DISABLES the
|
|
@@ -22515,6 +22574,11 @@ var HostAgentSupervisor = class {
|
|
|
22515
22574
|
}
|
|
22516
22575
|
/** Stop the control channel + heartbeats + kill every child. */
|
|
22517
22576
|
stop() {
|
|
22577
|
+
this.stopped = true;
|
|
22578
|
+
if (this.resumeRetryTimer) {
|
|
22579
|
+
clearTimeout(this.resumeRetryTimer);
|
|
22580
|
+
this.resumeRetryTimer = null;
|
|
22581
|
+
}
|
|
22518
22582
|
if (this.heartbeatTimer) {
|
|
22519
22583
|
clearInterval(this.heartbeatTimer);
|
|
22520
22584
|
this.heartbeatTimer = null;
|
|
@@ -22530,6 +22594,7 @@ var HostAgentSupervisor = class {
|
|
|
22530
22594
|
this.children.clear();
|
|
22531
22595
|
}
|
|
22532
22596
|
async beat() {
|
|
22597
|
+
this.resumeRecoveryTick();
|
|
22533
22598
|
try {
|
|
22534
22599
|
let metrics;
|
|
22535
22600
|
try {
|
|
@@ -23222,15 +23287,12 @@ var HostAgentSupervisor = class {
|
|
|
23222
23287
|
proc.once("exit", (code) => {
|
|
23223
23288
|
if (this.children.get(session.id)?.proc === proc) this.children.delete(session.id);
|
|
23224
23289
|
if (typeof code === "number" && code !== 0) {
|
|
23225
|
-
|
|
23226
|
-
"host-agent",
|
|
23227
|
-
`resumed session ${session.id.slice(0, 8)} exited (${code}): ${tail.trim().slice(-300)}`
|
|
23228
|
-
);
|
|
23290
|
+
this.onResumeChildExit(session, code, tail.trim().slice(-300));
|
|
23229
23291
|
}
|
|
23230
23292
|
});
|
|
23231
23293
|
log.info(
|
|
23232
23294
|
"host-agent",
|
|
23233
|
-
`resumed session ${session.id.slice(0, 8)} pluginId=${session.pluginId.slice(0, 12)} (ACP)`
|
|
23295
|
+
`resumed session ${session.id.slice(0, 8)} pluginId=${session.pluginId.slice(0, 12)} (ACP)` + (this.resumeRetryAttempts > 0 ? ` [retry ${this.resumeRetryAttempts}]` : "")
|
|
23234
23296
|
);
|
|
23235
23297
|
} catch (err) {
|
|
23236
23298
|
log.warn(
|
|
@@ -23239,6 +23301,92 @@ var HostAgentSupervisor = class {
|
|
|
23239
23301
|
);
|
|
23240
23302
|
}
|
|
23241
23303
|
}
|
|
23304
|
+
/**
|
|
23305
|
+
* A resumed session child died with a non-zero exit. Before 2026-08-20 this
|
|
23306
|
+
* was ONE warn log and permanent, silent surrender: the v2.65.13 self-update
|
|
23307
|
+
* restarted the fleet-1 unit, the resumed kimi child died `ENOENT — 'kimi'
|
|
23308
|
+
* was not found on PATH`, and the session sat dead for 3+ hours while the
|
|
23309
|
+
* HOST heartbeat stayed green (nothing retried, nothing surfaced anywhere).
|
|
23310
|
+
* Now: bounded backoff retries ({@link RESUME_RETRY_BACKOFF_MS}); when they
|
|
23311
|
+
* exhaust, post an HONEST error bubble into the session's chat (the relay/
|
|
23312
|
+
* backend are up — only the agent child is dead) and hand off to the
|
|
23313
|
+
* heartbeat-ridden slow re-probe ({@link resumeRecoveryTick}) so an
|
|
23314
|
+
* externally-fixed cause heals without a manual restart.
|
|
23315
|
+
*/
|
|
23316
|
+
onResumeChildExit(session, code, detail) {
|
|
23317
|
+
if (this.stopped) return;
|
|
23318
|
+
const reason = detail ? `exit ${code}: ${detail}` : `exit ${code}`;
|
|
23319
|
+
if (this.resumeRetryAttempts < RESUME_RETRY_BACKOFF_MS.length) {
|
|
23320
|
+
const delay = RESUME_RETRY_BACKOFF_MS[this.resumeRetryAttempts];
|
|
23321
|
+
this.resumeRetryAttempts += 1;
|
|
23322
|
+
log.warn(
|
|
23323
|
+
"host-agent",
|
|
23324
|
+
`resumed session ${session.id.slice(0, 8)} died (${reason}) \u2014 retry ${this.resumeRetryAttempts}/${RESUME_RETRY_BACKOFF_MS.length} in ${Math.round(delay / 1e3)}s`
|
|
23325
|
+
);
|
|
23326
|
+
this.resumeRetryTimer = setTimeout(() => {
|
|
23327
|
+
this.resumeRetryTimer = null;
|
|
23328
|
+
this.resumePersistedSession();
|
|
23329
|
+
}, delay);
|
|
23330
|
+
this.resumeRetryTimer.unref?.();
|
|
23331
|
+
return;
|
|
23332
|
+
}
|
|
23333
|
+
this.resumeExhausted = true;
|
|
23334
|
+
this.lastResumeReprobeAt = Date.now();
|
|
23335
|
+
log.error(
|
|
23336
|
+
"host-agent",
|
|
23337
|
+
`resumed session ${session.id.slice(0, 8)} FAILED permanently after ${RESUME_RETRY_BACKOFF_MS.length + 1} attempts (${reason}) \u2014 posting visible error, re-probing every ${Math.round(RESUME_REPROBE_INTERVAL_MS / 6e4)} min`
|
|
23338
|
+
);
|
|
23339
|
+
if (!this.resumeFailurePosted) {
|
|
23340
|
+
this.resumeFailurePosted = true;
|
|
23341
|
+
if (session.pluginId && session.pluginAuthToken) {
|
|
23342
|
+
void this.postResumeFailure(
|
|
23343
|
+
{
|
|
23344
|
+
sessionId: session.id,
|
|
23345
|
+
pluginId: session.pluginId,
|
|
23346
|
+
pluginAuthToken: session.pluginAuthToken
|
|
23347
|
+
},
|
|
23348
|
+
`The agent failed to restart after a CLI update/restart (${reason}). I tried ${RESUME_RETRY_BACKOFF_MS.length + 1} times without success. The host stays online and keeps retrying about every ${Math.round(RESUME_REPROBE_INTERVAL_MS / 6e4)} minutes \u2014 once the cause is fixed (for example the agent binary is reinstalled), the session reconnects automatically. You can also redeploy this server from My Servers.`
|
|
23349
|
+
).catch(() => void 0);
|
|
23350
|
+
} else {
|
|
23351
|
+
log.warn(
|
|
23352
|
+
"host-agent",
|
|
23353
|
+
"resume failure bubble skipped \u2014 persisted session has no pluginAuthToken"
|
|
23354
|
+
);
|
|
23355
|
+
}
|
|
23356
|
+
}
|
|
23357
|
+
}
|
|
23358
|
+
/**
|
|
23359
|
+
* Heartbeat rider for resume recovery (synchronous scheduling only — the
|
|
23360
|
+
* beat must stay punctual, and this adds NO new timers):
|
|
23361
|
+
* - a live session child ⇒ the failure episode (if any) is over: reset
|
|
23362
|
+
* the retry counter + flags so a FUTURE restart gets fresh retries and
|
|
23363
|
+
* a fresh (single) error bubble.
|
|
23364
|
+
* - retries exhausted + no child ⇒ re-probe the resume, throttled to
|
|
23365
|
+
* {@link RESUME_REPROBE_INTERVAL_MS}, so a fixed PATH / reinstalled
|
|
23366
|
+
* binary heals the session WITHOUT another manual restart. A re-probe
|
|
23367
|
+
* that fails again stays in this state (the bubble is not re-posted).
|
|
23368
|
+
*/
|
|
23369
|
+
resumeRecoveryTick() {
|
|
23370
|
+
if (this.children.size > 0) {
|
|
23371
|
+
const now2 = Date.now();
|
|
23372
|
+
const hasHealthyChild = [...this.children.values()].some(
|
|
23373
|
+
(c2) => now2 - c2.startedAt >= RESUME_HEALTHY_AFTER_MS
|
|
23374
|
+
);
|
|
23375
|
+
if (hasHealthyChild && (this.resumeRetryAttempts > 0 || this.resumeExhausted)) {
|
|
23376
|
+
log.info("host-agent", "resume recovered \u2014 session child healthy, retry state reset");
|
|
23377
|
+
this.resumeRetryAttempts = 0;
|
|
23378
|
+
this.resumeExhausted = false;
|
|
23379
|
+
this.resumeFailurePosted = false;
|
|
23380
|
+
}
|
|
23381
|
+
return;
|
|
23382
|
+
}
|
|
23383
|
+
if (!this.resumeExhausted || this.resumeRetryTimer) return;
|
|
23384
|
+
const now = Date.now();
|
|
23385
|
+
if (now - this.lastResumeReprobeAt < RESUME_REPROBE_INTERVAL_MS) return;
|
|
23386
|
+
this.lastResumeReprobeAt = now;
|
|
23387
|
+
log.info("host-agent", "resume re-probe (post-exhaustion heartbeat rider)");
|
|
23388
|
+
this.resumePersistedSession();
|
|
23389
|
+
}
|
|
23242
23390
|
/**
|
|
23243
23391
|
* Run the backend-supplied per-agent CLI install script (e.g.
|
|
23244
23392
|
* `claude.ai/install.sh`, `npm i -g @openai/codex`). Best-effort + bounded:
|
|
@@ -33942,6 +34090,10 @@ function createIdleTimeout(idleMs, makeError, activeIdleMs = idleMs) {
|
|
|
33942
34090
|
var path74 = __toESM(require("path"));
|
|
33943
34091
|
var os57 = __toESM(require("os"));
|
|
33944
34092
|
var INTERNAL_TOKENS = [".codeam", "house-claude"];
|
|
34093
|
+
var PROSE_ONLY_TOOL_KINDS = /* @__PURE__ */ new Set(["think", "switch_mode"]);
|
|
34094
|
+
function isProseOnlyToolKind(kind) {
|
|
34095
|
+
return kind != null && PROSE_ONLY_TOOL_KINDS.has(kind);
|
|
34096
|
+
}
|
|
33945
34097
|
var SELF_HOSTED_WORKSPACE_RE = /\.codeam[/\\]self-hosted/gi;
|
|
33946
34098
|
function textReferencesInternal(text) {
|
|
33947
34099
|
if (!text) return false;
|
|
@@ -33956,7 +34108,9 @@ function pathIsInternal(p2, homeDir2 = os57.homedir()) {
|
|
|
33956
34108
|
if (within(path74.join(home, ".codeam", "self-hosted"))) return false;
|
|
33957
34109
|
return within(path74.join(home, ".codeam")) || within(path74.join(home, ".beads")) || abs === path74.join(home, ".codeam-host.log") || abs.includes(`${path74.sep}house-claude${path74.sep}`) || abs.endsWith(`${path74.sep}house-claude`);
|
|
33958
34110
|
}
|
|
33959
|
-
function toolCallReferencesInternal(call) {
|
|
34111
|
+
function toolCallReferencesInternal(call, homeDir2) {
|
|
34112
|
+
if (call.locations?.some((l) => pathIsInternal(l.path, homeDir2))) return true;
|
|
34113
|
+
if (isProseOnlyToolKind(call.kind)) return false;
|
|
33960
34114
|
if (textReferencesInternal(call.title)) return true;
|
|
33961
34115
|
if (call.rawInput != null) {
|
|
33962
34116
|
try {
|
|
@@ -33967,9 +34121,9 @@ function toolCallReferencesInternal(call) {
|
|
|
33967
34121
|
}
|
|
33968
34122
|
return false;
|
|
33969
34123
|
}
|
|
33970
|
-
function internalPathPermissionOutcome(request) {
|
|
33971
|
-
if (!toolCallReferencesInternal(request.toolCall)) return null;
|
|
33972
|
-
const reject = request.options.find((o) => o.kind === "
|
|
34124
|
+
function internalPathPermissionOutcome(request, homeDir2) {
|
|
34125
|
+
if (!toolCallReferencesInternal(request.toolCall, homeDir2)) return null;
|
|
34126
|
+
const reject = request.options.find((o) => o.kind === "reject_once") ?? request.options.find((o) => o.kind === "reject_always");
|
|
33973
34127
|
if (reject) return { outcome: { outcome: "selected", optionId: reject.optionId } };
|
|
33974
34128
|
return { outcome: { outcome: "cancelled" } };
|
|
33975
34129
|
}
|
|
@@ -34029,6 +34183,7 @@ function toolPathIsSecret(p2) {
|
|
|
34029
34183
|
}
|
|
34030
34184
|
var GUARDRAIL_SECRET_READ_BLOCK_REASON = "Reading this secret file is blocked by this session's guardrails (Reading secrets = Deny). Adjust it in the session Guardrails settings if intended.";
|
|
34031
34185
|
function guardrailDecision(request, policy) {
|
|
34186
|
+
if (isProseOnlyToolKind(request.toolCall.kind)) return null;
|
|
34032
34187
|
const hay = haystack(request.toolCall);
|
|
34033
34188
|
if (!hay) return null;
|
|
34034
34189
|
const matched = matchedCategories(hay);
|
|
@@ -35054,6 +35209,7 @@ function knownAgentBinaryDirs() {
|
|
|
35054
35209
|
out2.push("/usr/bin");
|
|
35055
35210
|
out2.push(path75.join(home, ".local/bin"));
|
|
35056
35211
|
out2.push(path75.join(home, "bin"));
|
|
35212
|
+
out2.push(...agentInstallBinDirs());
|
|
35057
35213
|
if (process.platform === "win32") {
|
|
35058
35214
|
const { LOCALAPPDATA, APPDATA } = process.env;
|
|
35059
35215
|
if (LOCALAPPDATA) out2.push(path75.join(LOCALAPPDATA, "cursor-agent"));
|
|
@@ -36226,6 +36382,73 @@ function humanizeKind(kind) {
|
|
|
36226
36382
|
}
|
|
36227
36383
|
}
|
|
36228
36384
|
|
|
36385
|
+
// src/agents/acp/permission-gate.ts
|
|
36386
|
+
function pickAllowOption(options) {
|
|
36387
|
+
return options.find((o) => o.kind === "allow_always") ?? options.find((o) => o.kind === "allow_once") ?? null;
|
|
36388
|
+
}
|
|
36389
|
+
function describeToolCallForNotice(toolCall) {
|
|
36390
|
+
const title = toolCall.title?.trim();
|
|
36391
|
+
return title ? `"${title}"` : "a tool call";
|
|
36392
|
+
}
|
|
36393
|
+
function internalBlockNotice(toolCall) {
|
|
36394
|
+
return `\u26D4 CodeAgent auto-blocked ${describeToolCallForNotice(toolCall)} \u2014 it targets CodeAgent's own runtime files (~/.codeam), which are off-limits to the agent. This was an automatic block by CodeAgent, not a rejection by you.`;
|
|
36395
|
+
}
|
|
36396
|
+
function guardrailBlockNotice(decision, toolCall) {
|
|
36397
|
+
return `\u26D4 Guardrail auto-blocked ${describeToolCallForNotice(toolCall)} \u2014 ${decision.reason} This session's "${decision.category}" guardrail is set to Deny (adjust it in the session's Guardrails settings if intended). This was an automatic block, not a rejection by you.`;
|
|
36398
|
+
}
|
|
36399
|
+
function createOnRequestPermission(deps) {
|
|
36400
|
+
return async (request) => {
|
|
36401
|
+
let guardrailConfirm = false;
|
|
36402
|
+
if (!deps.isLocal()) {
|
|
36403
|
+
const denied = internalPathPermissionOutcome(request);
|
|
36404
|
+
if (denied) {
|
|
36405
|
+
log.warn(
|
|
36406
|
+
"acpRunner",
|
|
36407
|
+
"internal-path guard \u2014 denying tool call referencing a CodeAgent platform internal"
|
|
36408
|
+
);
|
|
36409
|
+
void deps.publisher.publishOutput({
|
|
36410
|
+
type: "text",
|
|
36411
|
+
content: internalBlockNotice(request.toolCall),
|
|
36412
|
+
done: true
|
|
36413
|
+
});
|
|
36414
|
+
return denied;
|
|
36415
|
+
}
|
|
36416
|
+
const g = guardrailDecision(request, deps.getPolicy());
|
|
36417
|
+
if (g?.kind === "deny") {
|
|
36418
|
+
log.warn("acpRunner", `guardrail [${g.category}] \u2014 denying tool call`);
|
|
36419
|
+
void deps.publisher.publishOutput({
|
|
36420
|
+
type: "text",
|
|
36421
|
+
content: guardrailBlockNotice(g, request.toolCall),
|
|
36422
|
+
done: true
|
|
36423
|
+
});
|
|
36424
|
+
return g.outcome;
|
|
36425
|
+
}
|
|
36426
|
+
if (g?.kind === "confirm") {
|
|
36427
|
+
guardrailConfirm = true;
|
|
36428
|
+
log.info("acpRunner", `guardrail [${g.category}] \u2014 requiring confirmation`);
|
|
36429
|
+
}
|
|
36430
|
+
}
|
|
36431
|
+
if (deps.autoApprovePermissions && !guardrailConfirm) {
|
|
36432
|
+
const allow = pickAllowOption(request.options);
|
|
36433
|
+
if (allow) {
|
|
36434
|
+
log.info(
|
|
36435
|
+
"acpRunner",
|
|
36436
|
+
`AUTO mode \u2014 auto-approving permission (${allow.kind}) optionId=${allow.optionId}`
|
|
36437
|
+
);
|
|
36438
|
+
return { outcome: { outcome: "selected", optionId: allow.optionId } };
|
|
36439
|
+
}
|
|
36440
|
+
log.warn("acpRunner", "AUTO mode \u2014 no allow option offered; falling back to interactive");
|
|
36441
|
+
}
|
|
36442
|
+
const { event, optionIdByLabel } = mapPermissionRequest(request);
|
|
36443
|
+
await deps.publisher.publishAwaitingAnswer(event);
|
|
36444
|
+
return deps.registerPermission({
|
|
36445
|
+
questionId: event.questionId,
|
|
36446
|
+
labels: event.options ?? [],
|
|
36447
|
+
optionIdByLabel
|
|
36448
|
+
});
|
|
36449
|
+
};
|
|
36450
|
+
}
|
|
36451
|
+
|
|
36229
36452
|
// src/agents/acp/selectPromptExtractor.ts
|
|
36230
36453
|
var MAX_OPTIONS = 6;
|
|
36231
36454
|
var MAX_OPTION_BODY_CHARS = 200;
|
|
@@ -39555,9 +39778,6 @@ var StreamingState = class {
|
|
|
39555
39778
|
}
|
|
39556
39779
|
};
|
|
39557
39780
|
var PERMISSION_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
39558
|
-
function pickAllowOption(options) {
|
|
39559
|
-
return options.find((o) => o.kind === "allow_always") ?? options.find((o) => o.kind === "allow_once") ?? null;
|
|
39560
|
-
}
|
|
39561
39781
|
var AcpHistory = class {
|
|
39562
39782
|
constructor(publisher, opts) {
|
|
39563
39783
|
this.publisher = publisher;
|
|
@@ -39798,46 +40018,19 @@ async function runAcpSession(opts) {
|
|
|
39798
40018
|
// Same guard the baton uses; the happy path never calls loadSession.
|
|
39799
40019
|
beginLoadReplay: () => streaming.beginLoadReplay(),
|
|
39800
40020
|
endLoadReplay: () => streaming.endLoadReplay(),
|
|
39801
|
-
|
|
39802
|
-
|
|
39803
|
-
|
|
39804
|
-
|
|
39805
|
-
|
|
39806
|
-
|
|
39807
|
-
|
|
39808
|
-
|
|
39809
|
-
|
|
39810
|
-
|
|
39811
|
-
|
|
39812
|
-
|
|
39813
|
-
|
|
39814
|
-
log.warn("acpRunner", `guardrail [${g.category}] \u2014 denying tool call`);
|
|
39815
|
-
return g.outcome;
|
|
39816
|
-
}
|
|
39817
|
-
if (g?.kind === "confirm") {
|
|
39818
|
-
guardrailConfirm = true;
|
|
39819
|
-
log.info("acpRunner", `guardrail [${g.category}] \u2014 requiring confirmation`);
|
|
39820
|
-
}
|
|
39821
|
-
}
|
|
39822
|
-
if (opts.autoApprovePermissions && !guardrailConfirm) {
|
|
39823
|
-
const allow = pickAllowOption(request.options);
|
|
39824
|
-
if (allow) {
|
|
39825
|
-
log.info(
|
|
39826
|
-
"acpRunner",
|
|
39827
|
-
`AUTO mode \u2014 auto-approving permission (${allow.kind}) optionId=${allow.optionId}`
|
|
39828
|
-
);
|
|
39829
|
-
return { outcome: { outcome: "selected", optionId: allow.optionId } };
|
|
39830
|
-
}
|
|
39831
|
-
log.warn("acpRunner", "AUTO mode \u2014 no allow option offered; falling back to interactive");
|
|
39832
|
-
}
|
|
39833
|
-
const { event, optionIdByLabel } = mapPermissionRequest(request);
|
|
39834
|
-
await publisher.publishAwaitingAnswer(event);
|
|
39835
|
-
return streaming.registerPermission({
|
|
39836
|
-
questionId: event.questionId,
|
|
39837
|
-
labels: event.options ?? [],
|
|
39838
|
-
optionIdByLabel
|
|
39839
|
-
});
|
|
39840
|
-
},
|
|
40021
|
+
// The full `session/request_permission` decision path (internal-path guard
|
|
40022
|
+
// → guardrails → AUTO auto-approve → interactive prompt) lives in
|
|
40023
|
+
// ./permission-gate.ts so it is unit-tested exactly as it runs here.
|
|
40024
|
+
// HARD RULE enforced there: every auto-REJECT publishes a visible chat
|
|
40025
|
+
// line — a guard must never silently answer for the user (the 2026-08-19
|
|
40026
|
+
// ExitPlanMode silent-rejection P0).
|
|
40027
|
+
onRequestPermission: createOnRequestPermission({
|
|
40028
|
+
autoApprovePermissions: opts.autoApprovePermissions === true,
|
|
40029
|
+
isLocal: isLocalSession,
|
|
40030
|
+
getPolicy: getGuardrailPolicy,
|
|
40031
|
+
publisher,
|
|
40032
|
+
registerPermission: (args2) => streaming.registerPermission(args2)
|
|
40033
|
+
}),
|
|
39841
40034
|
onStderr: (line) => {
|
|
39842
40035
|
recentStderr.push(line);
|
|
39843
40036
|
if (recentStderr.length > 40) recentStderr.shift();
|
|
@@ -45272,7 +45465,7 @@ function checkChokidar() {
|
|
|
45272
45465
|
}
|
|
45273
45466
|
async function doctor(args2 = []) {
|
|
45274
45467
|
const json = args2.includes("--json");
|
|
45275
|
-
const cliVersion = true ? "2.65.
|
|
45468
|
+
const cliVersion = true ? "2.65.15" : "0.0.0-dev";
|
|
45276
45469
|
const apiBase2 = resolveApiBaseUrl();
|
|
45277
45470
|
const diagnosticId = (0, import_node_crypto13.randomUUID)();
|
|
45278
45471
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -45663,7 +45856,7 @@ async function mcpRun(args2) {
|
|
|
45663
45856
|
// src/commands/version.ts
|
|
45664
45857
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
45665
45858
|
function version2() {
|
|
45666
|
-
const v = true ? "2.65.
|
|
45859
|
+
const v = true ? "2.65.15" : "unknown";
|
|
45667
45860
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
45668
45861
|
}
|
|
45669
45862
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.65.
|
|
3
|
+
"version": "2.65.15",
|
|
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",
|