codeam-cli 2.75.42 → 2.75.44
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 +12 -0
- package/dist/index.js +154 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ 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.75.43] — 2026-09-27
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **cli:** Stop the EPIPE log loop and retire daemons a restarted host-agent left behind (#851)
|
|
12
|
+
|
|
13
|
+
## [2.75.42] — 2026-09-27
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **cli:** A CodeAgent Box reports its label, not its container id (#849)
|
|
18
|
+
|
|
7
19
|
## [2.75.41] — 2026-09-27
|
|
8
20
|
|
|
9
21
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -3437,6 +3437,18 @@ function resolveLogDir() {
|
|
|
3437
3437
|
var LOG_DIR = resolveLogDir();
|
|
3438
3438
|
var debugFilePath = path.join(LOG_DIR, `debug-${process.pid}.log`);
|
|
3439
3439
|
var fileInitialized = false;
|
|
3440
|
+
var consoleBroken = false;
|
|
3441
|
+
process.stderr.on("error", () => {
|
|
3442
|
+
consoleBroken = true;
|
|
3443
|
+
});
|
|
3444
|
+
function writeConsole(line) {
|
|
3445
|
+
if (consoleBroken) return;
|
|
3446
|
+
try {
|
|
3447
|
+
process.stderr.write(line);
|
|
3448
|
+
} catch {
|
|
3449
|
+
consoleBroken = true;
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3440
3452
|
var LOG_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
3441
3453
|
var LOG_DIR_BUDGET_BYTES = 200 * 1024 * 1024;
|
|
3442
3454
|
function pidAlive(pid) {
|
|
@@ -3556,7 +3568,7 @@ function emit(level, tag, msg, err) {
|
|
|
3556
3568
|
appendToFile(jsonMode ? json : `${(/* @__PURE__ */ new Date()).toISOString()} ${text}`);
|
|
3557
3569
|
}
|
|
3558
3570
|
if (LEVELS[level] <= currentLevel()) {
|
|
3559
|
-
|
|
3571
|
+
writeConsole(jsonMode ? json : text);
|
|
3560
3572
|
}
|
|
3561
3573
|
}
|
|
3562
3574
|
var log = {
|
|
@@ -10565,7 +10577,7 @@ function readAnonId() {
|
|
|
10565
10577
|
}
|
|
10566
10578
|
function superProperties() {
|
|
10567
10579
|
return {
|
|
10568
|
-
cliVersion: true ? "2.75.
|
|
10580
|
+
cliVersion: true ? "2.75.44" : "0.0.0-dev",
|
|
10569
10581
|
nodeVersion: process.version,
|
|
10570
10582
|
platform: process.platform,
|
|
10571
10583
|
arch: process.arch,
|
|
@@ -10810,7 +10822,7 @@ var http = __toESM(require("http"));
|
|
|
10810
10822
|
// package.json
|
|
10811
10823
|
var package_default = {
|
|
10812
10824
|
name: "codeam-cli",
|
|
10813
|
-
version: "2.75.
|
|
10825
|
+
version: "2.75.44",
|
|
10814
10826
|
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.",
|
|
10815
10827
|
type: "commonjs",
|
|
10816
10828
|
main: "dist/index.js",
|
|
@@ -12144,7 +12156,7 @@ var CommandRelayService = class _CommandRelayService {
|
|
|
12144
12156
|
// fresh + clear the "CLI update available" banner after a self-update
|
|
12145
12157
|
// (a codespace that reinstalls @latest reconnects via heartbeat, not
|
|
12146
12158
|
// pair/reconnect). Older backends ignore the extra field.
|
|
12147
|
-
..."2.75.
|
|
12159
|
+
..."2.75.44" ? { ideVersion: "2.75.44" } : {}
|
|
12148
12160
|
};
|
|
12149
12161
|
try {
|
|
12150
12162
|
const res = await _postJson(`${API_BASE2}/api/plugin/heartbeat`, body);
|
|
@@ -27706,13 +27718,13 @@ function resolveGlobalNodeModulesDir(opts) {
|
|
|
27706
27718
|
var STALE_STAGING_AGE_MS = CLI_UPDATE_INSTALL_TIMEOUT_MS;
|
|
27707
27719
|
function sweepStaleCliStagingDirs(nodeModulesDir, now = Date.now(), deps) {
|
|
27708
27720
|
if (!nodeModulesDir) return 0;
|
|
27709
|
-
const
|
|
27721
|
+
const readdirSync16 = deps?.readdirSync ?? fs51.readdirSync;
|
|
27710
27722
|
const statSync16 = deps?.statSync ?? fs51.statSync;
|
|
27711
27723
|
const rmSync10 = deps?.rmSync ?? fs51.rmSync;
|
|
27712
27724
|
let removed = 0;
|
|
27713
27725
|
let entries;
|
|
27714
27726
|
try {
|
|
27715
|
-
entries =
|
|
27727
|
+
entries = readdirSync16(nodeModulesDir);
|
|
27716
27728
|
} catch {
|
|
27717
27729
|
return 0;
|
|
27718
27730
|
}
|
|
@@ -28532,6 +28544,9 @@ function installRelayCrashGuards() {
|
|
|
28532
28544
|
process.on("uncaughtException", (err) => {
|
|
28533
28545
|
log.error("process", `uncaughtException \u2014 relay kept alive \u2014 ${describeReason(err)}`);
|
|
28534
28546
|
});
|
|
28547
|
+
process.stdout.on("error", (err) => {
|
|
28548
|
+
if (err.code !== "EPIPE") log.warn("process", `stdout error \u2014 ${describeReason(err)}`);
|
|
28549
|
+
});
|
|
28535
28550
|
}
|
|
28536
28551
|
function describeReason(reason) {
|
|
28537
28552
|
if (reason instanceof Error) {
|
|
@@ -28923,6 +28938,59 @@ function releaseOwnLock(lockPath) {
|
|
|
28923
28938
|
} catch {
|
|
28924
28939
|
}
|
|
28925
28940
|
}
|
|
28941
|
+
function procPpid(pid) {
|
|
28942
|
+
const stat3 = readProcFile(`${pid}/stat`);
|
|
28943
|
+
if (!stat3) return void 0;
|
|
28944
|
+
const ppid = Number(stat3.slice(stat3.lastIndexOf(")") + 2).trim().split(/\s+/)[1]);
|
|
28945
|
+
return Number.isInteger(ppid) ? ppid : void 0;
|
|
28946
|
+
}
|
|
28947
|
+
function orphanedSupervisorDaemon(sessionId) {
|
|
28948
|
+
const rec = readLockRecord(daemonLockPath(sessionId));
|
|
28949
|
+
if (!rec || !isLiveLockHolder(rec)) return void 0;
|
|
28950
|
+
if (process.pid === 1 || procPpid(rec.pid) !== 1) return void 0;
|
|
28951
|
+
const env = readProcFile(`${rec.pid}/environ`)?.split("\0") ?? [];
|
|
28952
|
+
const spawnedBySupervisor = env.some(
|
|
28953
|
+
(v) => v === `CODEAM_RESUME_SESSION_ID=${sessionId}` || v.startsWith("CODEAM_AUTO_TOKEN=")
|
|
28954
|
+
);
|
|
28955
|
+
return spawnedBySupervisor ? rec.pid : void 0;
|
|
28956
|
+
}
|
|
28957
|
+
function snapshotProcessTree(root) {
|
|
28958
|
+
const children = /* @__PURE__ */ new Map();
|
|
28959
|
+
let entries = [];
|
|
28960
|
+
try {
|
|
28961
|
+
entries = fs52.readdirSync(procRoot).filter((e) => /^\d+$/.test(e));
|
|
28962
|
+
} catch {
|
|
28963
|
+
}
|
|
28964
|
+
for (const e of entries) {
|
|
28965
|
+
const pid = Number(e);
|
|
28966
|
+
const ppid = procPpid(pid);
|
|
28967
|
+
if (ppid === void 0) continue;
|
|
28968
|
+
const list = children.get(ppid) ?? [];
|
|
28969
|
+
list.push(pid);
|
|
28970
|
+
children.set(ppid, list);
|
|
28971
|
+
}
|
|
28972
|
+
const tree = [];
|
|
28973
|
+
const queue = [root];
|
|
28974
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
28975
|
+
while (queue.length > 0) {
|
|
28976
|
+
const pid = queue.shift();
|
|
28977
|
+
if (seen2.has(pid)) continue;
|
|
28978
|
+
seen2.add(pid);
|
|
28979
|
+
const start2 = procStartTicks(pid);
|
|
28980
|
+
tree.push(start2 ? { pid, start: start2 } : { pid });
|
|
28981
|
+
queue.push(...children.get(pid) ?? []);
|
|
28982
|
+
}
|
|
28983
|
+
return tree;
|
|
28984
|
+
}
|
|
28985
|
+
function signalProcessTree(refs, signal) {
|
|
28986
|
+
for (const ref of refs) {
|
|
28987
|
+
if (ref.start !== void 0 && procStartTicks(ref.pid) !== ref.start) continue;
|
|
28988
|
+
try {
|
|
28989
|
+
process.kill(ref.pid, signal);
|
|
28990
|
+
} catch {
|
|
28991
|
+
}
|
|
28992
|
+
}
|
|
28993
|
+
}
|
|
28926
28994
|
function daemonLockPath(sessionId) {
|
|
28927
28995
|
const safe = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
28928
28996
|
return path60.join(os40.homedir(), ".codeam", `daemon-${safe}.lock`);
|
|
@@ -48895,7 +48963,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
48895
48963
|
if (process.env.NODE_ENV === "test") return;
|
|
48896
48964
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
48897
48965
|
if (process.env.CI) return;
|
|
48898
|
-
const current2 = true ? "2.75.
|
|
48966
|
+
const current2 = true ? "2.75.44" : null;
|
|
48899
48967
|
if (!current2) return;
|
|
48900
48968
|
const cache = readCache();
|
|
48901
48969
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -48912,7 +48980,7 @@ function checkForUpdates() {
|
|
|
48912
48980
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
48913
48981
|
if (process.env.CI) return;
|
|
48914
48982
|
if (!process.stdout.isTTY) return;
|
|
48915
|
-
const current2 = true ? "2.75.
|
|
48983
|
+
const current2 = true ? "2.75.44" : null;
|
|
48916
48984
|
if (!current2) return;
|
|
48917
48985
|
const cache = readCache();
|
|
48918
48986
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -48935,7 +49003,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
|
|
|
48935
49003
|
var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
|
|
48936
49004
|
var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
|
|
48937
49005
|
function currentCliVersion() {
|
|
48938
|
-
return true ? "2.75.
|
|
49006
|
+
return true ? "2.75.44" : null;
|
|
48939
49007
|
}
|
|
48940
49008
|
async function installedVersion(deps) {
|
|
48941
49009
|
const ls = await deps.run(
|
|
@@ -49160,6 +49228,11 @@ function buildFleetBoxRunArgs(p2) {
|
|
|
49160
49228
|
...process.env.CODEAM_FLEET_BOX_IMAGE ? [] : ["--pull=always"],
|
|
49161
49229
|
"--name",
|
|
49162
49230
|
p2.containerName,
|
|
49231
|
+
// The box's host-agent is the container's pid 1 and its self-update restart
|
|
49232
|
+
// is an exit(0): without a restart policy that stopped the whole box until
|
|
49233
|
+
// the next wake. `docker stop` (the sleep sweep) still keeps it stopped.
|
|
49234
|
+
"--restart",
|
|
49235
|
+
"unless-stopped",
|
|
49163
49236
|
"--cap-drop",
|
|
49164
49237
|
"ALL",
|
|
49165
49238
|
"--security-opt",
|
|
@@ -49275,8 +49348,33 @@ var defaultOnIdentityRejected = () => {
|
|
|
49275
49348
|
);
|
|
49276
49349
|
process.exit(1);
|
|
49277
49350
|
};
|
|
49351
|
+
var ORPHAN_TERM_GRACE_MS = 3e3;
|
|
49352
|
+
var ORPHAN_KILL_SETTLE_MS = 500;
|
|
49353
|
+
function needsSelfRelaunch(env = process.env, pid = process.pid) {
|
|
49354
|
+
return !env.INVOCATION_ID && pid !== 1;
|
|
49355
|
+
}
|
|
49356
|
+
function relaunchArgv(execPath, argv) {
|
|
49357
|
+
return ["-c", 'sleep 2; exec "$0" "$@"', execPath, ...argv.slice(1)];
|
|
49358
|
+
}
|
|
49278
49359
|
var defaultOnUpdated = (version3) => {
|
|
49279
|
-
|
|
49360
|
+
const relaunch = needsSelfRelaunch();
|
|
49361
|
+
log.info(
|
|
49362
|
+
"host-agent",
|
|
49363
|
+
`self-update: installed ${version3}, restarting${relaunch ? " (self-relaunch, no supervisor)" : ""}`
|
|
49364
|
+
);
|
|
49365
|
+
if (relaunch) {
|
|
49366
|
+
try {
|
|
49367
|
+
(0, import_node_child_process30.spawn)("/bin/sh", relaunchArgv(process.execPath, process.argv), {
|
|
49368
|
+
cwd: process.cwd(),
|
|
49369
|
+
env: process.env,
|
|
49370
|
+
detached: true,
|
|
49371
|
+
stdio: "ignore"
|
|
49372
|
+
}).unref();
|
|
49373
|
+
} catch (err) {
|
|
49374
|
+
log.error("host-agent", "self-update: relaunch failed \u2014 staying on the old binary", err);
|
|
49375
|
+
return;
|
|
49376
|
+
}
|
|
49377
|
+
}
|
|
49280
49378
|
process.exit(0);
|
|
49281
49379
|
};
|
|
49282
49380
|
var HostAgentSupervisor = class {
|
|
@@ -49287,6 +49385,9 @@ var HostAgentSupervisor = class {
|
|
|
49287
49385
|
this.resumeSpawner = deps.resumeSpawner ?? defaultResumeSpawner;
|
|
49288
49386
|
this.sessionStore = deps.sessionStore ?? fileSessionChildStore();
|
|
49289
49387
|
this.listSavedSessions = deps.listSavedSessions ?? (() => loadCliConfig().sessions);
|
|
49388
|
+
this.orphanedDaemonFor = deps.orphanedDaemonFor ?? orphanedSupervisorDaemon;
|
|
49389
|
+
this.snapshotTree = deps.snapshotProcessTree ?? snapshotProcessTree;
|
|
49390
|
+
this.signalTree = deps.signalProcessTree ?? signalProcessTree;
|
|
49290
49391
|
this.resolveAgentAuth = deps.resolveAgentAuth ?? unsealAgentAuth;
|
|
49291
49392
|
this.metrics = deps.metricsCollector ?? new MetricsCollector();
|
|
49292
49393
|
this.onIdentityRejected = deps.onIdentityRejected ?? defaultOnIdentityRejected;
|
|
@@ -49341,6 +49442,11 @@ var HostAgentSupervisor = class {
|
|
|
49341
49442
|
postResumeFailure;
|
|
49342
49443
|
sessionStore;
|
|
49343
49444
|
listSavedSessions;
|
|
49445
|
+
orphanedDaemonFor;
|
|
49446
|
+
snapshotTree;
|
|
49447
|
+
signalTree;
|
|
49448
|
+
/** Resumes waiting for a left-behind daemon to die (see replaceOrphanedDaemon). */
|
|
49449
|
+
pendingOrphanReplacements = 0;
|
|
49344
49450
|
/**
|
|
49345
49451
|
* Per-session resume bookkeeping (codeagent-v07a: N sessions resume on
|
|
49346
49452
|
* boot, each with its OWN bounded retries / exhaustion / re-probe throttle —
|
|
@@ -49881,6 +49987,7 @@ var HostAgentSupervisor = class {
|
|
|
49881
49987
|
return;
|
|
49882
49988
|
}
|
|
49883
49989
|
log.info("host-agent", `fleet_start_box id=${payload.boxId} name=${containerName}`);
|
|
49990
|
+
await this.docker.run(["update", "--restart", "unless-stopped", containerName]);
|
|
49884
49991
|
const res = await this.docker.run(["start", containerName]);
|
|
49885
49992
|
if (res.code !== 0 && !isMissingContainerError(res.stderr)) {
|
|
49886
49993
|
log.warn(
|
|
@@ -50211,7 +50318,7 @@ var HostAgentSupervisor = class {
|
|
|
50211
50318
|
const records = this.sessionStore.load();
|
|
50212
50319
|
if (records === null) {
|
|
50213
50320
|
const target = this.fallbackResumeTarget();
|
|
50214
|
-
if (target) this.
|
|
50321
|
+
if (target) this.resumeOrReplace(target);
|
|
50215
50322
|
return;
|
|
50216
50323
|
}
|
|
50217
50324
|
if (records.length === 0) return;
|
|
@@ -50238,14 +50345,16 @@ var HostAgentSupervisor = class {
|
|
|
50238
50345
|
);
|
|
50239
50346
|
continue;
|
|
50240
50347
|
}
|
|
50241
|
-
this.
|
|
50348
|
+
this.resumeOrReplace({ deployId: rec.deployId, cwd: rec.cwd, session, agent: rec.agent });
|
|
50242
50349
|
resumed += 1;
|
|
50243
50350
|
}
|
|
50244
50351
|
log.info(
|
|
50245
50352
|
"host-agent",
|
|
50246
50353
|
`resume: ${resumed} session(s) resumed, ${dropped.length} ended (bound ${max}), ${resume.length - resumed} skipped`
|
|
50247
50354
|
);
|
|
50248
|
-
if (this.children.size === 0
|
|
50355
|
+
if (this.children.size === 0 && this.pendingOrphanReplacements === 0) {
|
|
50356
|
+
this.sessionStore.save([]);
|
|
50357
|
+
}
|
|
50249
50358
|
} catch (err) {
|
|
50250
50359
|
log.warn(
|
|
50251
50360
|
"host-agent",
|
|
@@ -50352,6 +50461,36 @@ var HostAgentSupervisor = class {
|
|
|
50352
50461
|
return [];
|
|
50353
50462
|
}
|
|
50354
50463
|
}
|
|
50464
|
+
/**
|
|
50465
|
+
* Resume `target` — first retiring a daemon the PREVIOUS host-agent left
|
|
50466
|
+
* holding the session, if there is one. Without this the resume child
|
|
50467
|
+
* defers to that orphan (it holds the daemon lock and is alive), so the
|
|
50468
|
+
* session keeps running the old code with dead stdio pipes — on 2.75.41 and
|
|
50469
|
+
* older that wedged it offline for good (see `orphanedSupervisorDaemon`).
|
|
50470
|
+
* Replacing it matches what systemd already does on a self-hosted box,
|
|
50471
|
+
* where a unit restart kills the whole control group.
|
|
50472
|
+
*/
|
|
50473
|
+
resumeOrReplace(target) {
|
|
50474
|
+
const orphan = this.orphanedDaemonFor(target.session.id);
|
|
50475
|
+
if (orphan === void 0) {
|
|
50476
|
+
this.resumeOne(target);
|
|
50477
|
+
return;
|
|
50478
|
+
}
|
|
50479
|
+
log.warn(
|
|
50480
|
+
"host-agent",
|
|
50481
|
+
`resume: session ${target.session.id.slice(0, 8)} is held by pid ${orphan}, left behind by the previous host-agent \u2014 replacing it`
|
|
50482
|
+
);
|
|
50483
|
+
this.pendingOrphanReplacements += 1;
|
|
50484
|
+
const tree = this.snapshotTree(orphan);
|
|
50485
|
+
this.signalTree(tree.slice(0, 1), "SIGTERM");
|
|
50486
|
+
setTimeout(() => {
|
|
50487
|
+
this.signalTree(tree, "SIGKILL");
|
|
50488
|
+
setTimeout(() => {
|
|
50489
|
+
this.pendingOrphanReplacements -= 1;
|
|
50490
|
+
if (!this.stopped) this.resumeOne(target);
|
|
50491
|
+
}, ORPHAN_KILL_SETTLE_MS);
|
|
50492
|
+
}, ORPHAN_TERM_GRACE_MS);
|
|
50493
|
+
}
|
|
50355
50494
|
/** Spawn ONE resume child for `target` (boot, retry, or re-probe). */
|
|
50356
50495
|
resumeOne(target) {
|
|
50357
50496
|
const { session, deployId } = target;
|
|
@@ -50911,7 +51050,7 @@ function checkChokidar() {
|
|
|
50911
51050
|
}
|
|
50912
51051
|
async function doctor(args2 = []) {
|
|
50913
51052
|
const json = args2.includes("--json");
|
|
50914
|
-
const cliVersion = true ? "2.75.
|
|
51053
|
+
const cliVersion = true ? "2.75.44" : "0.0.0-dev";
|
|
50915
51054
|
const apiBase2 = resolveApiBaseUrl();
|
|
50916
51055
|
const diagnosticId = (0, import_node_crypto13.randomUUID)();
|
|
50917
51056
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -51435,7 +51574,7 @@ async function mcpWarm(argv = [], opts = {}) {
|
|
|
51435
51574
|
// src/commands/version.ts
|
|
51436
51575
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
51437
51576
|
function version2() {
|
|
51438
|
-
const v = true ? "2.75.
|
|
51577
|
+
const v = true ? "2.75.44" : "unknown";
|
|
51439
51578
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
51440
51579
|
}
|
|
51441
51580
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.75.
|
|
3
|
+
"version": "2.75.44",
|
|
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",
|