codeam-cli 2.60.50 → 2.60.52
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 +135 -10
- 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.60.51] — 2026-07-12
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **cli:** Recover kimi ACP 'Session is closed' by re-establishing the session + retry (multi-turn)
|
|
12
|
+
|
|
13
|
+
### Tests
|
|
14
|
+
|
|
15
|
+
- **cli:** Reusable ACP-provision smoke harness (per-agent real handshake auth check; automates CLAUDE.md Step 8)
|
|
16
|
+
|
|
17
|
+
## [2.60.50] — 2026-07-11
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **cli:** Guard kimi ACP login-state slot + real `kimi acp` regression test
|
|
22
|
+
|
|
7
23
|
## [2.60.49] — 2026-07-11
|
|
8
24
|
|
|
9
25
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -478,13 +478,27 @@ function headroomKindFor(agentId) {
|
|
|
478
478
|
// ../../packages/shared/src/integrations/registry.ts
|
|
479
479
|
var INTEGRATION_REGISTRY = {
|
|
480
480
|
jira: {
|
|
481
|
+
// Registry id kept 'jira' for id-stability (existing LinkedIntegration rows
|
|
482
|
+
// stay valid — zero data migration); only the DISPLAY is "Atlassian". The
|
|
483
|
+
// one mcp-atlassian server serves BOTH Jira and Confluence, so the same
|
|
484
|
+
// entry now requests Confluence scopes too.
|
|
481
485
|
id: "jira",
|
|
482
|
-
name: "
|
|
486
|
+
name: "Atlassian",
|
|
483
487
|
icon: "jira",
|
|
484
488
|
enabled: true,
|
|
485
489
|
auth: {
|
|
486
490
|
kind: "oauth_redirect",
|
|
487
|
-
scopes
|
|
491
|
+
// Jira + Confluence 3LO granular scopes. The Confluence pair
|
|
492
|
+
// (read:confluence-content.all / write:confluence-content) is the set
|
|
493
|
+
// mcp-atlassian's own Authentication docs recommend for full read+write
|
|
494
|
+
// Confluence (matches its documented env scope string).
|
|
495
|
+
scopes: [
|
|
496
|
+
"read:jira-work",
|
|
497
|
+
"write:jira-work",
|
|
498
|
+
"read:confluence-content.all",
|
|
499
|
+
"write:confluence-content",
|
|
500
|
+
"offline_access"
|
|
501
|
+
]
|
|
488
502
|
},
|
|
489
503
|
delivery: {
|
|
490
504
|
mcp: {
|
|
@@ -5733,7 +5747,7 @@ function readAnonId() {
|
|
|
5733
5747
|
}
|
|
5734
5748
|
function superProperties() {
|
|
5735
5749
|
return {
|
|
5736
|
-
cliVersion: true ? "2.60.
|
|
5750
|
+
cliVersion: true ? "2.60.52" : "0.0.0-dev",
|
|
5737
5751
|
nodeVersion: process.version,
|
|
5738
5752
|
platform: process.platform,
|
|
5739
5753
|
arch: process.arch,
|
|
@@ -5914,7 +5928,7 @@ var os4 = __toESM(require("os"));
|
|
|
5914
5928
|
// package.json
|
|
5915
5929
|
var package_default = {
|
|
5916
5930
|
name: "codeam-cli",
|
|
5917
|
-
version: "2.60.
|
|
5931
|
+
version: "2.60.52",
|
|
5918
5932
|
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.",
|
|
5919
5933
|
type: "commonjs",
|
|
5920
5934
|
main: "dist/index.js",
|
|
@@ -7023,7 +7037,7 @@ var CommandRelayService = class {
|
|
|
7023
7037
|
// fresh + clear the "CLI update available" banner after a self-update
|
|
7024
7038
|
// (a codespace that reinstalls @latest reconnects via heartbeat, not
|
|
7025
7039
|
// pair/reconnect). Older backends ignore the extra field.
|
|
7026
|
-
..."2.60.
|
|
7040
|
+
..."2.60.52" ? { ideVersion: "2.60.52" } : {}
|
|
7027
7041
|
}).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
|
|
7028
7042
|
}
|
|
7029
7043
|
/**
|
|
@@ -17592,7 +17606,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
17592
17606
|
if (process.env.NODE_ENV === "test") return;
|
|
17593
17607
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
17594
17608
|
if (process.env.CI) return;
|
|
17595
|
-
const current = true ? "2.60.
|
|
17609
|
+
const current = true ? "2.60.52" : null;
|
|
17596
17610
|
if (!current) return;
|
|
17597
17611
|
const cache = readCache();
|
|
17598
17612
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -17609,7 +17623,7 @@ function checkForUpdates() {
|
|
|
17609
17623
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
17610
17624
|
if (process.env.CI) return;
|
|
17611
17625
|
if (!process.stdout.isTTY) return;
|
|
17612
|
-
const current = true ? "2.60.
|
|
17626
|
+
const current = true ? "2.60.52" : null;
|
|
17613
17627
|
if (!current) return;
|
|
17614
17628
|
const cache = readCache();
|
|
17615
17629
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -17629,7 +17643,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
|
17629
17643
|
var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
|
|
17630
17644
|
var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
|
|
17631
17645
|
function currentCliVersion() {
|
|
17632
|
-
return true ? "2.60.
|
|
17646
|
+
return true ? "2.60.52" : null;
|
|
17633
17647
|
}
|
|
17634
17648
|
function runCmd(cmd, args2, timeoutMs) {
|
|
17635
17649
|
return new Promise((resolve7) => {
|
|
@@ -26736,6 +26750,7 @@ function createIdleTimeout(idleMs, makeError) {
|
|
|
26736
26750
|
var TRANSIENT_ADAPTER_SPAWN_RE = /ETXTBSY|ENOENT/;
|
|
26737
26751
|
var MAX_START_ATTEMPTS = 5;
|
|
26738
26752
|
var TRANSPORT_DOWN_DURING_START_RE = /connection (is )?closed|stream (is )?closed|ECONNRESET|EPIPE|write after end|premature close/i;
|
|
26753
|
+
var SESSION_CLOSED_RE = /session is closed/i;
|
|
26739
26754
|
var AdapterStartCrashError = class extends Error {
|
|
26740
26755
|
adapterExitedDuringStart = true;
|
|
26741
26756
|
constructor(message) {
|
|
@@ -26763,6 +26778,12 @@ var AcpClient = class {
|
|
|
26763
26778
|
connection = null;
|
|
26764
26779
|
stopping = false;
|
|
26765
26780
|
sessionId = null;
|
|
26781
|
+
/** Whether the agent advertised `loadSession` on `initialize`. Drives the
|
|
26782
|
+
* post-turn session-closed recovery: an agent that supports resume (kimi,
|
|
26783
|
+
* claude, codex, gemini) is re-established with `session/load` — preserving
|
|
26784
|
+
* the conversation — whereas one without it falls back to a fresh
|
|
26785
|
+
* `session/new`. Set once during {@link startOnce}. */
|
|
26786
|
+
supportsLoadSession = false;
|
|
26766
26787
|
/** Idle watchdog for the in-flight prompt. The `Client` handlers
|
|
26767
26788
|
* (`sessionUpdate` / `requestPermission`) reach for this to keep
|
|
26768
26789
|
* the turn alive while the adapter is demonstrably working. Null
|
|
@@ -26946,6 +26967,7 @@ var AcpClient = class {
|
|
|
26946
26967
|
"acpClient",
|
|
26947
26968
|
`initialize \u2190 ok protocolVersion=${initialize.protocolVersion} agentCaps=${JSON.stringify(initialize.agentCapabilities ?? {}).slice(0, 200)}`
|
|
26948
26969
|
);
|
|
26970
|
+
this.supportsLoadSession = initialize.agentCapabilities?.loadSession === true;
|
|
26949
26971
|
log.info("acpClient", "newSession \u2192 sending");
|
|
26950
26972
|
let startupTimer;
|
|
26951
26973
|
const startupFailure = new Promise((_2, reject) => {
|
|
@@ -27033,6 +27055,29 @@ var AcpClient = class {
|
|
|
27033
27055
|
throw new Error("AcpClient.prompt called before start()");
|
|
27034
27056
|
}
|
|
27035
27057
|
const blocks = typeof input === "string" ? [{ type: "text", text: input }] : input;
|
|
27058
|
+
try {
|
|
27059
|
+
return await this.sendPromptOnce(blocks);
|
|
27060
|
+
} catch (err) {
|
|
27061
|
+
if (this.isSessionClosedError(err)) {
|
|
27062
|
+
log.warn(
|
|
27063
|
+
"acpClient",
|
|
27064
|
+
"session/prompt rejected 'Session is closed' \u2014 re-establishing session and retrying once"
|
|
27065
|
+
);
|
|
27066
|
+
await this.reestablishSession();
|
|
27067
|
+
return await this.sendPromptOnce(blocks);
|
|
27068
|
+
}
|
|
27069
|
+
throw err;
|
|
27070
|
+
}
|
|
27071
|
+
}
|
|
27072
|
+
/**
|
|
27073
|
+
* Send ONE `session/prompt` round-trip with the idle watchdog. Extracted from
|
|
27074
|
+
* {@link runPrompt} so the closed-session recovery can re-invoke it for the
|
|
27075
|
+
* single retry without duplicating the watchdog / tool-ledger setup.
|
|
27076
|
+
*/
|
|
27077
|
+
async sendPromptOnce(blocks) {
|
|
27078
|
+
if (!this.connection || !this.sessionId) {
|
|
27079
|
+
throw new Error("AcpClient.sendPromptOnce called before start()");
|
|
27080
|
+
}
|
|
27036
27081
|
const textLen = blocks.reduce(
|
|
27037
27082
|
(n, b) => b.type === "text" ? n + b.text.length : n,
|
|
27038
27083
|
0
|
|
@@ -27078,6 +27123,71 @@ var AcpClient = class {
|
|
|
27078
27123
|
this.promptIdle = null;
|
|
27079
27124
|
}
|
|
27080
27125
|
}
|
|
27126
|
+
/**
|
|
27127
|
+
* True when a `session/prompt` rejection is the "the agent closed its ACP
|
|
27128
|
+
* session between turns" fact — the kimi post-turn/idle close. Keyed on the
|
|
27129
|
+
* `session is closed` phrase (in `message` / `details` / serialised `data`),
|
|
27130
|
+
* NOT on the bare `-32603` code: that generic "Internal error" code is also
|
|
27131
|
+
* used for the adapter's own transient spawn races, so matching it alone would
|
|
27132
|
+
* misfire. See {@link SESSION_CLOSED_RE}.
|
|
27133
|
+
*/
|
|
27134
|
+
isSessionClosedError(err) {
|
|
27135
|
+
if (!err || typeof err !== "object") return false;
|
|
27136
|
+
const e = err;
|
|
27137
|
+
const parts = [];
|
|
27138
|
+
if (typeof e.message === "string") parts.push(e.message);
|
|
27139
|
+
if (typeof e.details === "string") parts.push(e.details);
|
|
27140
|
+
if (e.data !== void 0) {
|
|
27141
|
+
try {
|
|
27142
|
+
parts.push(JSON.stringify(e.data));
|
|
27143
|
+
} catch {
|
|
27144
|
+
}
|
|
27145
|
+
}
|
|
27146
|
+
return SESSION_CLOSED_RE.test(parts.join(" "));
|
|
27147
|
+
}
|
|
27148
|
+
/**
|
|
27149
|
+
* Re-establish a session the agent closed out from under us (kimi post-turn),
|
|
27150
|
+
* on the SAME still-running adapter process, so the pending prompt can be
|
|
27151
|
+
* retried. Prefers `session/load` (resume) when the agent advertised
|
|
27152
|
+
* `loadSession` — proven live to re-open kimi's closed session with the
|
|
27153
|
+
* conversation context intact. Calls `connection.loadSession` DIRECTLY — the
|
|
27154
|
+
* public {@link loadSession} skips a same-id load (a Claude relaunch-wedge
|
|
27155
|
+
* guard), which is exactly the load we MUST perform here (the closed id IS the
|
|
27156
|
+
* active id). Falls back to a fresh `session/new` (context lost) only when the
|
|
27157
|
+
* agent can't resume.
|
|
27158
|
+
*
|
|
27159
|
+
* ⚠️ kimi's `session/load` (0.23.6, live-verified) REPLAYS the ENTIRE prior
|
|
27160
|
+
* conversation as `session/update` notifications BEFORE it resolves — the
|
|
27161
|
+
* earlier assumption that it emitted no replay was wrong and caused a P0: the
|
|
27162
|
+
* replayed prior-turn text was published as live chunks and prepended to the
|
|
27163
|
+
* recovered turn's real reply. So the load is bracketed with
|
|
27164
|
+
* `beginLoadReplay()/endLoadReplay()` (wired by the runner to StreamingState)
|
|
27165
|
+
* so those replay updates are swallowed, exactly like the baton's load. The
|
|
27166
|
+
* retried `session/prompt` (after the load resolves) then streams the REAL new
|
|
27167
|
+
* reply cleanly. The `finally` guarantees the guard clears even if the load
|
|
27168
|
+
* throws, so a failed recovery can't wedge streaming off.
|
|
27169
|
+
*/
|
|
27170
|
+
async reestablishSession() {
|
|
27171
|
+
if (!this.connection) throw new Error("AcpClient.reestablishSession: no connection");
|
|
27172
|
+
const cwd = this.opts.cwd;
|
|
27173
|
+
const mcpServers = this.opts.mcpServers ?? [];
|
|
27174
|
+
const closedSid = this.sessionId;
|
|
27175
|
+
if (this.supportsLoadSession && closedSid) {
|
|
27176
|
+
log.info("acpClient", `reestablish \u2192 loadSession(resume) sid=${closedSid.slice(0, 8)}`);
|
|
27177
|
+
this.opts.beginLoadReplay?.();
|
|
27178
|
+
try {
|
|
27179
|
+
await this.connection.loadSession({ sessionId: closedSid, cwd, mcpServers });
|
|
27180
|
+
} finally {
|
|
27181
|
+
this.opts.endLoadReplay?.();
|
|
27182
|
+
}
|
|
27183
|
+
log.info("acpClient", "reestablish \u2190 loadSession ok");
|
|
27184
|
+
return;
|
|
27185
|
+
}
|
|
27186
|
+
log.info("acpClient", "reestablish \u2192 newSession (agent has no loadSession capability)");
|
|
27187
|
+
const ns = await this.connection.newSession({ cwd, mcpServers });
|
|
27188
|
+
this.sessionId = ns.sessionId;
|
|
27189
|
+
log.info("acpClient", `reestablish \u2190 newSession ok sid=${ns.sessionId.slice(0, 8)}`);
|
|
27190
|
+
}
|
|
27081
27191
|
/**
|
|
27082
27192
|
* Load a previously persisted session by id. Mobile's
|
|
27083
27193
|
* `resume_session` command flows through here. Requires the
|
|
@@ -29966,6 +30076,13 @@ async function runAcpSession(opts) {
|
|
|
29966
30076
|
streaming.append(delta);
|
|
29967
30077
|
}
|
|
29968
30078
|
},
|
|
30079
|
+
// kimi post-turn "Session is closed" recovery brackets its `session/load`
|
|
30080
|
+
// with these so the conversation history the load REPLAYS as session/update
|
|
30081
|
+
// notifications is swallowed by StreamingState instead of published as live
|
|
30082
|
+
// chunks (which prepended a prior turn's text to the recovered reply — P0).
|
|
30083
|
+
// Same guard the baton uses; the happy path never calls loadSession.
|
|
30084
|
+
beginLoadReplay: () => streaming.beginLoadReplay(),
|
|
30085
|
+
endLoadReplay: () => streaming.endLoadReplay(),
|
|
29969
30086
|
onRequestPermission: async (request) => {
|
|
29970
30087
|
if (opts.autoApprovePermissions) {
|
|
29971
30088
|
const allow = pickAllowOption(request.options);
|
|
@@ -31763,6 +31880,14 @@ async function runBatonSession(opts) {
|
|
|
31763
31880
|
onSessionUpdate: (notification) => {
|
|
31764
31881
|
for (const delta of mapSessionUpdate(notification)) streaming.append(delta);
|
|
31765
31882
|
},
|
|
31883
|
+
// Swallow the ACP session/load history replay during the kimi "Session is
|
|
31884
|
+
// closed" recovery (AcpClient.reestablishSession) — kimi 0.23.6 replays the
|
|
31885
|
+
// whole conversation as session/update, which would otherwise prepend a
|
|
31886
|
+
// prior turn's text to the recovered reply on the baton's mobile stream too
|
|
31887
|
+
// (same leak the plain ACP runner wires at runner.ts). Same `streaming`
|
|
31888
|
+
// instance the baton's own AcpDriver.start bracketing uses.
|
|
31889
|
+
beginLoadReplay: () => streaming.beginLoadReplay(),
|
|
31890
|
+
endLoadReplay: () => streaming.endLoadReplay(),
|
|
31766
31891
|
onRequestPermission: async (request) => {
|
|
31767
31892
|
const { event, optionIdByLabel } = mapPermissionRequest(request);
|
|
31768
31893
|
await publisher.publishAwaitingAnswer(event);
|
|
@@ -34846,7 +34971,7 @@ function checkChokidar() {
|
|
|
34846
34971
|
}
|
|
34847
34972
|
async function doctor(args2 = []) {
|
|
34848
34973
|
const json = args2.includes("--json");
|
|
34849
|
-
const cliVersion = true ? "2.60.
|
|
34974
|
+
const cliVersion = true ? "2.60.52" : "0.0.0-dev";
|
|
34850
34975
|
const apiBase2 = resolveApiBaseUrl();
|
|
34851
34976
|
const diagnosticId = (0, import_node_crypto12.randomUUID)();
|
|
34852
34977
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -35323,7 +35448,7 @@ async function mcpRun(args2) {
|
|
|
35323
35448
|
// src/commands/version.ts
|
|
35324
35449
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
35325
35450
|
function version2() {
|
|
35326
|
-
const v = true ? "2.60.
|
|
35451
|
+
const v = true ? "2.60.52" : "unknown";
|
|
35327
35452
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
35328
35453
|
}
|
|
35329
35454
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.60.
|
|
3
|
+
"version": "2.60.52",
|
|
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",
|