codeam-cli 2.61.7 → 2.61.9
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 +21 -0
- package/dist/index.js +118 -37
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@ 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.61.8] — 2026-07-16
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **cli:** Agent-agnostic RECENT list + auto-resume latest conversation (ACP session/list)
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **cli:** Wrap public loadSession() with the load-replay guard (keystone)
|
|
16
|
+
|
|
17
|
+
### Tests
|
|
18
|
+
|
|
19
|
+
- **cli:** Harness invariant — no unguarded connection.loadSession
|
|
20
|
+
|
|
21
|
+
## [2.61.7] — 2026-07-16
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **cli:** Auto-resume the user's session on host-agent restart (no churn)
|
|
26
|
+
- **cli:** Engines-compat test uses CommonJS require (was import.meta → tsc fail)
|
|
27
|
+
|
|
7
28
|
## [2.61.5] — 2026-07-16
|
|
8
29
|
|
|
9
30
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -5975,7 +5975,7 @@ function readAnonId() {
|
|
|
5975
5975
|
}
|
|
5976
5976
|
function superProperties() {
|
|
5977
5977
|
return {
|
|
5978
|
-
cliVersion: true ? "2.61.
|
|
5978
|
+
cliVersion: true ? "2.61.9" : "0.0.0-dev",
|
|
5979
5979
|
nodeVersion: process.version,
|
|
5980
5980
|
platform: process.platform,
|
|
5981
5981
|
arch: process.arch,
|
|
@@ -6156,7 +6156,7 @@ var os4 = __toESM(require("os"));
|
|
|
6156
6156
|
// package.json
|
|
6157
6157
|
var package_default = {
|
|
6158
6158
|
name: "codeam-cli",
|
|
6159
|
-
version: "2.61.
|
|
6159
|
+
version: "2.61.9",
|
|
6160
6160
|
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.",
|
|
6161
6161
|
type: "commonjs",
|
|
6162
6162
|
main: "dist/index.js",
|
|
@@ -7299,7 +7299,7 @@ var CommandRelayService = class _CommandRelayService {
|
|
|
7299
7299
|
// fresh + clear the "CLI update available" banner after a self-update
|
|
7300
7300
|
// (a codespace that reinstalls @latest reconnects via heartbeat, not
|
|
7301
7301
|
// pair/reconnect). Older backends ignore the extra field.
|
|
7302
|
-
..."2.61.
|
|
7302
|
+
..."2.61.9" ? { ideVersion: "2.61.9" } : {}
|
|
7303
7303
|
}).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
|
|
7304
7304
|
}
|
|
7305
7305
|
/**
|
|
@@ -17900,7 +17900,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
17900
17900
|
if (process.env.NODE_ENV === "test") return;
|
|
17901
17901
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
17902
17902
|
if (process.env.CI) return;
|
|
17903
|
-
const current = true ? "2.61.
|
|
17903
|
+
const current = true ? "2.61.9" : null;
|
|
17904
17904
|
if (!current) return;
|
|
17905
17905
|
const cache = readCache();
|
|
17906
17906
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -17917,7 +17917,7 @@ function checkForUpdates() {
|
|
|
17917
17917
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
17918
17918
|
if (process.env.CI) return;
|
|
17919
17919
|
if (!process.stdout.isTTY) return;
|
|
17920
|
-
const current = true ? "2.61.
|
|
17920
|
+
const current = true ? "2.61.9" : null;
|
|
17921
17921
|
if (!current) return;
|
|
17922
17922
|
const cache = readCache();
|
|
17923
17923
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -17937,7 +17937,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
|
17937
17937
|
var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
|
|
17938
17938
|
var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
|
|
17939
17939
|
function currentCliVersion() {
|
|
17940
|
-
return true ? "2.61.
|
|
17940
|
+
return true ? "2.61.9" : null;
|
|
17941
17941
|
}
|
|
17942
17942
|
function runCmd(cmd, args2, timeoutMs) {
|
|
17943
17943
|
return new Promise((resolve8) => {
|
|
@@ -18256,7 +18256,10 @@ var defaultSpawner = (env, cwd, args2 = []) => (0, import_node_child_process23.s
|
|
|
18256
18256
|
});
|
|
18257
18257
|
var defaultResumeSpawner = (env, cwd) => (0, import_node_child_process23.spawn)(process.execPath, [process.argv[1]], {
|
|
18258
18258
|
cwd,
|
|
18259
|
-
|
|
18259
|
+
// CODEAM_AUTO_APPROVE=1 → ACP path (baton off). CODEAM_RESUME_LATEST=1 →
|
|
18260
|
+
// continue the user's most-recent conversation instead of opening an empty
|
|
18261
|
+
// one (runAcpSession loads it via the ACP session/list RPC).
|
|
18262
|
+
env: { ...process.env, ...env, CODEAM_AUTO_APPROVE: "1", CODEAM_RESUME_LATEST: "1" },
|
|
18260
18263
|
stdio: ["ignore", "pipe", "pipe"],
|
|
18261
18264
|
detached: false
|
|
18262
18265
|
});
|
|
@@ -22595,16 +22598,23 @@ function buildNpmInstallInvocation(opts) {
|
|
|
22595
22598
|
path59.dirname(execPath),
|
|
22596
22599
|
process.platform === "win32" ? "npm.cmd" : "npm"
|
|
22597
22600
|
);
|
|
22598
|
-
const
|
|
22599
|
-
const
|
|
22600
|
-
|
|
22601
|
+
const npmCommand = exists2(siblingNpm) ? siblingNpm : "npm";
|
|
22602
|
+
const npmArgs = prefix ? ["install", "-g", "--prefix", prefix, "codeam-cli@latest"] : ["install", "-g", "codeam-cli@latest"];
|
|
22603
|
+
if (opts?.sudo) {
|
|
22604
|
+
return { command: "sudo", args: ["-n", npmCommand, ...npmArgs] };
|
|
22605
|
+
}
|
|
22606
|
+
return { command: npmCommand, args: npmArgs };
|
|
22607
|
+
}
|
|
22608
|
+
function isPermissionError(stderr) {
|
|
22609
|
+
return /EACCES|permission denied|errno[\s"']*-13|operation not permitted/i.test(stderr);
|
|
22601
22610
|
}
|
|
22602
22611
|
async function runNpmInstallLatest() {
|
|
22603
22612
|
let lastError = "";
|
|
22604
|
-
|
|
22605
|
-
const useShell = process.platform === "win32";
|
|
22606
|
-
const command2 = useShell && invocation.command.includes(" ") ? `"${invocation.command}"` : invocation.command;
|
|
22613
|
+
let useSudo = false;
|
|
22607
22614
|
for (let attempt = 1; attempt <= CLI_UPDATE_MAX_ATTEMPTS; attempt++) {
|
|
22615
|
+
const invocation = buildNpmInstallInvocation({ sudo: useSudo });
|
|
22616
|
+
const useShell = process.platform === "win32";
|
|
22617
|
+
const command2 = useShell && invocation.command.includes(" ") ? `"${invocation.command}"` : invocation.command;
|
|
22608
22618
|
const result = await new Promise((resolve8) => {
|
|
22609
22619
|
(0, import_child_process24.execFile)(
|
|
22610
22620
|
command2,
|
|
@@ -22621,6 +22631,10 @@ async function runNpmInstallLatest() {
|
|
|
22621
22631
|
});
|
|
22622
22632
|
if (result.ok) return { ok: true };
|
|
22623
22633
|
lastError = result.error ?? "unknown";
|
|
22634
|
+
if (!useSudo && process.platform !== "win32" && isPermissionError(lastError)) {
|
|
22635
|
+
useSudo = true;
|
|
22636
|
+
continue;
|
|
22637
|
+
}
|
|
22624
22638
|
if (attempt < CLI_UPDATE_MAX_ATTEMPTS) {
|
|
22625
22639
|
await new Promise((r) => setTimeout(r, 1e3 * attempt));
|
|
22626
22640
|
}
|
|
@@ -29068,6 +29082,7 @@ var AcpClient = class {
|
|
|
29068
29082
|
* the conversation — whereas one without it falls back to a fresh
|
|
29069
29083
|
* `session/new`. Set once during {@link startOnce}. */
|
|
29070
29084
|
supportsLoadSession = false;
|
|
29085
|
+
supportsListSessions = false;
|
|
29071
29086
|
/** Idle watchdog for the in-flight prompt. The `Client` handlers
|
|
29072
29087
|
* (`sessionUpdate` / `requestPermission`) reach for this to keep
|
|
29073
29088
|
* the turn alive while the adapter is demonstrably working. Null
|
|
@@ -29252,6 +29267,7 @@ var AcpClient = class {
|
|
|
29252
29267
|
`initialize \u2190 ok protocolVersion=${initialize.protocolVersion} agentCaps=${JSON.stringify(initialize.agentCapabilities ?? {}).slice(0, 200)}`
|
|
29253
29268
|
);
|
|
29254
29269
|
this.supportsLoadSession = initialize.agentCapabilities?.loadSession === true;
|
|
29270
|
+
this.supportsListSessions = !!initialize.agentCapabilities?.sessionCapabilities?.list;
|
|
29255
29271
|
log.info("acpClient", "newSession \u2192 sending");
|
|
29256
29272
|
let startupTimer;
|
|
29257
29273
|
const startupFailure = new Promise((_2, reject) => {
|
|
@@ -29495,14 +29511,44 @@ var AcpClient = class {
|
|
|
29495
29511
|
return;
|
|
29496
29512
|
}
|
|
29497
29513
|
log.info("acpClient", `loadSession \u2192 sessionId=${sessionId.slice(0, 8)}`);
|
|
29498
|
-
|
|
29499
|
-
|
|
29500
|
-
|
|
29501
|
-
|
|
29502
|
-
|
|
29514
|
+
this.opts.beginLoadReplay?.();
|
|
29515
|
+
try {
|
|
29516
|
+
await this.connection.loadSession({
|
|
29517
|
+
sessionId,
|
|
29518
|
+
cwd: this.opts.cwd,
|
|
29519
|
+
mcpServers: this.opts.mcpServers ?? []
|
|
29520
|
+
});
|
|
29521
|
+
} finally {
|
|
29522
|
+
this.opts.endLoadReplay?.();
|
|
29523
|
+
}
|
|
29503
29524
|
this.sessionId = sessionId;
|
|
29504
29525
|
log.info("acpClient", `loadSession \u2190 ok sessionId=${sessionId.slice(0, 8)}`);
|
|
29505
29526
|
}
|
|
29527
|
+
/**
|
|
29528
|
+
* Enumerate the workspace's conversations via the ACP `session/list` RPC.
|
|
29529
|
+
* Agent-agnostic — works for ANY adapter that advertises
|
|
29530
|
+
* `sessionCapabilities.list` (claude/codex/gemini alike), so the mobile RECENT
|
|
29531
|
+
* list no longer depends on per-agent JSONL scanning. Maps SessionInfo (id +
|
|
29532
|
+
* agent-authored title + updatedAt) to the backend's RECENT-list shape.
|
|
29533
|
+
*
|
|
29534
|
+
* Returns null when the agent doesn't advertise `list` (caller falls back or
|
|
29535
|
+
* skips). Best-effort — never throws out to the caller.
|
|
29536
|
+
*/
|
|
29537
|
+
async listSessions() {
|
|
29538
|
+
if (!this.connection || !this.supportsListSessions) return null;
|
|
29539
|
+
try {
|
|
29540
|
+
const res = await this.connection.listSessions({ cwd: this.opts.cwd });
|
|
29541
|
+
return (res.sessions ?? []).map((s) => ({
|
|
29542
|
+
id: s.sessionId,
|
|
29543
|
+
summary: s.title ?? "",
|
|
29544
|
+
// updatedAt is an ISO string; fall back to now on absent/unparseable.
|
|
29545
|
+
timestamp: s.updatedAt ? Date.parse(s.updatedAt) || Date.now() : Date.now()
|
|
29546
|
+
}));
|
|
29547
|
+
} catch (err) {
|
|
29548
|
+
log.trace("acpClient", "listSessions failed (best-effort)", err);
|
|
29549
|
+
return null;
|
|
29550
|
+
}
|
|
29551
|
+
}
|
|
29506
29552
|
/**
|
|
29507
29553
|
* Switch the active session's model via the non-standard
|
|
29508
29554
|
* `session/set_model` RPC. The standard ACP SDK doesn't expose
|
|
@@ -32330,17 +32376,17 @@ var AcpHistory = class {
|
|
|
32330
32376
|
async flush() {
|
|
32331
32377
|
if (this.summary === null || this.messages.length === 0) return;
|
|
32332
32378
|
const timestamp = Date.now();
|
|
32379
|
+
const current = { id: this.opts.acpSessionId, summary: this.summary, timestamp };
|
|
32380
|
+
let sessions3 = [current];
|
|
32381
|
+
const listed = this.opts.listSessions ? await this.opts.listSessions() : null;
|
|
32382
|
+
if (listed && listed.length > 0) {
|
|
32383
|
+
sessions3 = listed.map(
|
|
32384
|
+
(s) => s.id === this.opts.acpSessionId && !s.summary ? { ...s, summary: this.summary } : s
|
|
32385
|
+
);
|
|
32386
|
+
if (!sessions3.some((s) => s.id === this.opts.acpSessionId)) sessions3.unshift(current);
|
|
32387
|
+
}
|
|
32333
32388
|
await Promise.all([
|
|
32334
|
-
this.publisher.pushSessionList({
|
|
32335
|
-
agentId: this.opts.agent,
|
|
32336
|
-
sessions: [
|
|
32337
|
-
{
|
|
32338
|
-
id: this.opts.acpSessionId,
|
|
32339
|
-
summary: this.summary,
|
|
32340
|
-
timestamp
|
|
32341
|
-
}
|
|
32342
|
-
]
|
|
32343
|
-
}),
|
|
32389
|
+
this.publisher.pushSessionList({ agentId: this.opts.agent, sessions: sessions3 }),
|
|
32344
32390
|
this.publisher.pushConversation({
|
|
32345
32391
|
agentId: this.opts.agent,
|
|
32346
32392
|
sessionId: this.opts.acpSessionId,
|
|
@@ -32386,6 +32432,10 @@ function computeAdapterExtraEnv(params) {
|
|
|
32386
32432
|
);
|
|
32387
32433
|
return env;
|
|
32388
32434
|
}
|
|
32435
|
+
function pickLatestResumableConversation(sessions3, currentId) {
|
|
32436
|
+
const prior = (sessions3 ?? []).filter((s) => s.id !== currentId).sort((a, b) => b.timestamp - a.timestamp)[0];
|
|
32437
|
+
return prior?.id ?? null;
|
|
32438
|
+
}
|
|
32389
32439
|
async function runAcpSession(opts) {
|
|
32390
32440
|
const publisher = new AcpPublisher({
|
|
32391
32441
|
sessionId: opts.sessionId,
|
|
@@ -32534,16 +32584,24 @@ async function runAcpSession(opts) {
|
|
|
32534
32584
|
});
|
|
32535
32585
|
return;
|
|
32536
32586
|
}
|
|
32537
|
-
|
|
32538
|
-
|
|
32539
|
-
initialize,
|
|
32540
|
-
model: handshakeModel,
|
|
32541
|
-
tier: handshakeTier
|
|
32542
|
-
} = handshake;
|
|
32587
|
+
let acpSessionId = handshake.sessionId;
|
|
32588
|
+
const { initialize, model: handshakeModel, tier: handshakeTier } = handshake;
|
|
32543
32589
|
log.trace(
|
|
32544
32590
|
"acpRunner",
|
|
32545
32591
|
`adapter handshake ok protocolVersion=${initialize.protocolVersion} sessionId=${acpSessionId.slice(0, 8)}`
|
|
32546
32592
|
);
|
|
32593
|
+
if (process.env.CODEAM_RESUME_LATEST === "1") {
|
|
32594
|
+
try {
|
|
32595
|
+
const priorId = pickLatestResumableConversation(await client3.listSessions(), acpSessionId);
|
|
32596
|
+
if (priorId) {
|
|
32597
|
+
await client3.loadSession(priorId);
|
|
32598
|
+
acpSessionId = priorId;
|
|
32599
|
+
log.info("acpRunner", `auto-resumed latest conversation ${priorId.slice(0, 8)}`);
|
|
32600
|
+
}
|
|
32601
|
+
} catch (err) {
|
|
32602
|
+
log.trace("acpRunner", "auto-resume-latest failed (best-effort)", err);
|
|
32603
|
+
}
|
|
32604
|
+
}
|
|
32547
32605
|
showSuccess(`${opts.agent} online (ACP) \u2014 awaiting prompts from mobile.`);
|
|
32548
32606
|
showRelayNotice();
|
|
32549
32607
|
void publisher.publishOutput({
|
|
@@ -32565,10 +32623,33 @@ async function runAcpSession(opts) {
|
|
|
32565
32623
|
});
|
|
32566
32624
|
const runtime = createInteractiveAgentStrategy(opts.agent, createOsStrategy());
|
|
32567
32625
|
const models = await runtime.listModels();
|
|
32568
|
-
const history = new AcpHistory(publisher, {
|
|
32626
|
+
const history = new AcpHistory(publisher, {
|
|
32627
|
+
agent: opts.agent,
|
|
32628
|
+
acpSessionId,
|
|
32629
|
+
// Agent-agnostic RECENT list: enumerate via the ACP session/list RPC (any
|
|
32630
|
+
// adapter advertising sessionCapabilities.list). Returns null on unsupported
|
|
32631
|
+
// agents → flush() falls back to the current session only.
|
|
32632
|
+
listSessions: () => client3.listSessions()
|
|
32633
|
+
});
|
|
32569
32634
|
const jsonlHistory = new HistoryService(runtime, opts.pluginId, opts.cwd, {
|
|
32570
32635
|
pluginAuthToken: opts.pluginAuthToken
|
|
32571
32636
|
});
|
|
32637
|
+
void (async () => {
|
|
32638
|
+
try {
|
|
32639
|
+
const listed = await client3.listSessions();
|
|
32640
|
+
if (listed && listed.length > 0) {
|
|
32641
|
+
await publisher.pushSessionList({ agentId: opts.agent, sessions: listed });
|
|
32642
|
+
log.info("acpRunner", `session-start: pushed ${listed.length} conversation(s) to RECENT`);
|
|
32643
|
+
}
|
|
32644
|
+
const uploaded = await jsonlHistory.uploadConversationIfChanged(acpSessionId);
|
|
32645
|
+
log.info(
|
|
32646
|
+
"acpRunner",
|
|
32647
|
+
`session-start: transcript upload ${acpSessionId.slice(0, 8)} \u2192 ${uploaded}`
|
|
32648
|
+
);
|
|
32649
|
+
} catch (err) {
|
|
32650
|
+
log.warn("acpRunner", `session-start push failed (best-effort): ${describeError(err)}`);
|
|
32651
|
+
}
|
|
32652
|
+
})();
|
|
32572
32653
|
const onboardingWelcomeDone = maybeSendOnboardingWelcome({
|
|
32573
32654
|
streaming,
|
|
32574
32655
|
history,
|
|
@@ -37340,7 +37421,7 @@ function checkChokidar() {
|
|
|
37340
37421
|
}
|
|
37341
37422
|
async function doctor(args2 = []) {
|
|
37342
37423
|
const json = args2.includes("--json");
|
|
37343
|
-
const cliVersion = true ? "2.61.
|
|
37424
|
+
const cliVersion = true ? "2.61.9" : "0.0.0-dev";
|
|
37344
37425
|
const apiBase2 = resolveApiBaseUrl();
|
|
37345
37426
|
const diagnosticId = (0, import_node_crypto12.randomUUID)();
|
|
37346
37427
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -37851,7 +37932,7 @@ async function mcpRun(args2) {
|
|
|
37851
37932
|
// src/commands/version.ts
|
|
37852
37933
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
37853
37934
|
function version2() {
|
|
37854
|
-
const v = true ? "2.61.
|
|
37935
|
+
const v = true ? "2.61.9" : "unknown";
|
|
37855
37936
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
37856
37937
|
}
|
|
37857
37938
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.61.
|
|
3
|
+
"version": "2.61.9",
|
|
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",
|