codeam-cli 2.61.16 → 2.61.18
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 +14 -0
- package/dist/index.js +259 -45
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ 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.17] — 2026-07-18
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **cli:** Reuse bounded adapter-resolve retry in baton + codespace-gate paths; honest fail-then-succeed retry test
|
|
12
|
+
- **cli:** Make buildNpmInstallInvocation platform-injectable → deterministic cross-OS tests (fixes red Windows CI)
|
|
13
|
+
- **cli:** Never wipe a streamed transcript on a turn error (false 'couldn't finish')
|
|
14
|
+
|
|
15
|
+
## [2.61.16] — 2026-07-17
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **cli:** ACP dispatch is registry-static — never silently downgrade an ACP agent to PTY
|
|
20
|
+
|
|
7
21
|
## [2.61.15] — 2026-07-17
|
|
8
22
|
|
|
9
23
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -841,8 +841,8 @@ var HEADROOM_MODELS = [
|
|
|
841
841
|
}
|
|
842
842
|
];
|
|
843
843
|
function headroomSnapshotDownloadLine(model, opts = {}) {
|
|
844
|
-
const
|
|
845
|
-
const patterns = model.allowPatterns.map((p2) => `"${p2}"`).join(
|
|
844
|
+
const sep6 = opts.spaceAfterComma ? ", " : ",";
|
|
845
|
+
const patterns = model.allowPatterns.map((p2) => `"${p2}"`).join(sep6);
|
|
846
846
|
return `snapshot_download("${model.repo}", allow_patterns=[${patterns}])`;
|
|
847
847
|
}
|
|
848
848
|
function headroomModelPredownloadScript(opts = {}) {
|
|
@@ -6024,7 +6024,7 @@ function readAnonId() {
|
|
|
6024
6024
|
}
|
|
6025
6025
|
function superProperties() {
|
|
6026
6026
|
return {
|
|
6027
|
-
cliVersion: true ? "2.61.
|
|
6027
|
+
cliVersion: true ? "2.61.18" : "0.0.0-dev",
|
|
6028
6028
|
nodeVersion: process.version,
|
|
6029
6029
|
platform: process.platform,
|
|
6030
6030
|
arch: process.arch,
|
|
@@ -6205,7 +6205,7 @@ var os4 = __toESM(require("os"));
|
|
|
6205
6205
|
// package.json
|
|
6206
6206
|
var package_default = {
|
|
6207
6207
|
name: "codeam-cli",
|
|
6208
|
-
version: "2.61.
|
|
6208
|
+
version: "2.61.18",
|
|
6209
6209
|
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.",
|
|
6210
6210
|
type: "commonjs",
|
|
6211
6211
|
main: "dist/index.js",
|
|
@@ -7348,7 +7348,7 @@ var CommandRelayService = class _CommandRelayService {
|
|
|
7348
7348
|
// fresh + clear the "CLI update available" banner after a self-update
|
|
7349
7349
|
// (a codespace that reinstalls @latest reconnects via heartbeat, not
|
|
7350
7350
|
// pair/reconnect). Older backends ignore the extra field.
|
|
7351
|
-
..."2.61.
|
|
7351
|
+
..."2.61.18" ? { ideVersion: "2.61.18" } : {}
|
|
7352
7352
|
}).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
|
|
7353
7353
|
}
|
|
7354
7354
|
/**
|
|
@@ -12211,12 +12211,12 @@ function isAvailable() {
|
|
|
12211
12211
|
}
|
|
12212
12212
|
function augmentPath() {
|
|
12213
12213
|
const dirs = probeInstallDirs();
|
|
12214
|
-
const
|
|
12214
|
+
const sep6 = path16.delimiter;
|
|
12215
12215
|
const current = process.env.PATH ?? "";
|
|
12216
|
-
const existing = new Set(current.split(
|
|
12216
|
+
const existing = new Set(current.split(sep6).filter(Boolean));
|
|
12217
12217
|
const additions = dirs.filter((d3) => !existing.has(d3));
|
|
12218
12218
|
if (additions.length === 0) return;
|
|
12219
|
-
process.env.PATH = additions.join(
|
|
12219
|
+
process.env.PATH = additions.join(sep6) + sep6 + current;
|
|
12220
12220
|
}
|
|
12221
12221
|
function runInstaller() {
|
|
12222
12222
|
const isWindows = process.platform === "win32";
|
|
@@ -17949,7 +17949,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
17949
17949
|
if (process.env.NODE_ENV === "test") return;
|
|
17950
17950
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
17951
17951
|
if (process.env.CI) return;
|
|
17952
|
-
const current = true ? "2.61.
|
|
17952
|
+
const current = true ? "2.61.18" : null;
|
|
17953
17953
|
if (!current) return;
|
|
17954
17954
|
const cache = readCache();
|
|
17955
17955
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -17966,7 +17966,7 @@ function checkForUpdates() {
|
|
|
17966
17966
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
17967
17967
|
if (process.env.CI) return;
|
|
17968
17968
|
if (!process.stdout.isTTY) return;
|
|
17969
|
-
const current = true ? "2.61.
|
|
17969
|
+
const current = true ? "2.61.18" : null;
|
|
17970
17970
|
if (!current) return;
|
|
17971
17971
|
const cache = readCache();
|
|
17972
17972
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -17986,7 +17986,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
|
17986
17986
|
var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
|
|
17987
17987
|
var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
|
|
17988
17988
|
function currentCliVersion() {
|
|
17989
|
-
return true ? "2.61.
|
|
17989
|
+
return true ? "2.61.18" : null;
|
|
17990
17990
|
}
|
|
17991
17991
|
function runCmd(cmd, args2, timeoutMs) {
|
|
17992
17992
|
return new Promise((resolve8) => {
|
|
@@ -22655,13 +22655,15 @@ function buildNpmInstallInvocation(opts) {
|
|
|
22655
22655
|
const entryScript = opts?.entryScript ?? process.argv[1] ?? "";
|
|
22656
22656
|
const execPath = opts?.execPath ?? process.execPath;
|
|
22657
22657
|
const exists2 = opts?.existsSync ?? fs55.existsSync;
|
|
22658
|
-
const
|
|
22658
|
+
const platform3 = opts?.platform ?? process.platform;
|
|
22659
|
+
const p2 = platform3 === "win32" ? path59.win32 : path59.posix;
|
|
22660
|
+
const normalized = entryScript.split(/[\\/]/).join("/");
|
|
22659
22661
|
const marker = "/lib/node_modules/codeam-cli/";
|
|
22660
22662
|
const markerIdx = normalized.indexOf(marker);
|
|
22661
22663
|
const prefix = markerIdx > 0 ? entryScript.slice(0, markerIdx) : null;
|
|
22662
|
-
const siblingNpm =
|
|
22663
|
-
|
|
22664
|
-
|
|
22664
|
+
const siblingNpm = p2.join(
|
|
22665
|
+
p2.dirname(execPath),
|
|
22666
|
+
platform3 === "win32" ? "npm.cmd" : "npm"
|
|
22665
22667
|
);
|
|
22666
22668
|
const npmCommand = exists2(siblingNpm) ? siblingNpm : "npm";
|
|
22667
22669
|
const npmArgs = prefix ? ["install", "-g", "--prefix", prefix, "codeam-cli@latest"] : ["install", "-g", "codeam-cli@latest"];
|
|
@@ -24509,13 +24511,21 @@ async function resolveAcpAdapterWithRetry(agent, opts = {}) {
|
|
|
24509
24511
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_ADAPTER_RETRY_TIMEOUT_MS;
|
|
24510
24512
|
const pollMs = opts.pollMs ?? DEFAULT_ADAPTER_RETRY_POLL_MS;
|
|
24511
24513
|
const now = opts.now ?? Date.now;
|
|
24512
|
-
const sleep5 = opts.sleep ?? ((ms) => new Promise((
|
|
24513
|
-
|
|
24514
|
+
const sleep5 = opts.sleep ?? ((ms) => new Promise((resolve9) => setTimeout(resolve9, ms)));
|
|
24515
|
+
const resolve8 = opts.resolve ?? getAcpAdapter;
|
|
24516
|
+
const attempt = () => {
|
|
24517
|
+
try {
|
|
24518
|
+
return resolve8(agent);
|
|
24519
|
+
} catch {
|
|
24520
|
+
return null;
|
|
24521
|
+
}
|
|
24522
|
+
};
|
|
24523
|
+
let spec = attempt();
|
|
24514
24524
|
if (spec) return spec;
|
|
24515
24525
|
const deadline = now() + timeoutMs;
|
|
24516
24526
|
while (now() < deadline) {
|
|
24517
24527
|
await sleep5(pollMs);
|
|
24518
|
-
spec =
|
|
24528
|
+
spec = attempt();
|
|
24519
24529
|
if (spec) return spec;
|
|
24520
24530
|
}
|
|
24521
24531
|
return null;
|
|
@@ -29182,6 +29192,34 @@ var AcpClient = class {
|
|
|
29182
29192
|
connection = null;
|
|
29183
29193
|
stopping = false;
|
|
29184
29194
|
sessionId = null;
|
|
29195
|
+
/** The id of the NATIVE ACP `category:'model'` `SessionConfigSelect` option
|
|
29196
|
+
* captured from `newSession`/`loadSession` `configOptions`. It's the SINGLE
|
|
29197
|
+
* source of truth for model listing/switching: `session/set_config_option`
|
|
29198
|
+
* targets this id, and `undefined` here means the agent exposes no native
|
|
29199
|
+
* model selector → {@link getAvailableModels} is empty and {@link setModel}
|
|
29200
|
+
* throws (mobile then hides the picker). */
|
|
29201
|
+
modelConfigId = void 0;
|
|
29202
|
+
/** The models the native model config option advertises — mapped from its
|
|
29203
|
+
* `options` (id = option `value`, label = option `name`). Empty when the
|
|
29204
|
+
* agent has no model config option. NO hardcoded fallback. */
|
|
29205
|
+
availableModels = [];
|
|
29206
|
+
/** The native model select's current value — seeded from the config option's
|
|
29207
|
+
* `currentValue` and updated on a successful {@link setModel}. Exposed via
|
|
29208
|
+
* {@link getCurrentModelId} so `list_models` can report the in-use model to
|
|
29209
|
+
* mobile (the model line under the composer). `undefined` when no model
|
|
29210
|
+
* config option is advertised. */
|
|
29211
|
+
currentModelId = void 0;
|
|
29212
|
+
/** The operating/permission MODES the native ACP `SessionModeState` advertises
|
|
29213
|
+
* (a DIFFERENT axis than models) — mapped from `newSession`/`loadSession`
|
|
29214
|
+
* `modes.availableModes` (id = mode id, label = mode name, description passthrough).
|
|
29215
|
+
* Empty when the agent advertises no modes → {@link getAvailableModes} is empty
|
|
29216
|
+
* and {@link setMode} throws (mobile then hides the mode picker). */
|
|
29217
|
+
availableModes = [];
|
|
29218
|
+
/** The native `SessionModeState.currentModeId` — seeded from `modes.currentModeId`
|
|
29219
|
+
* and updated on a successful {@link setMode}. Exposed via {@link getCurrentModeId}
|
|
29220
|
+
* so `list_modes` can report the in-use mode to mobile. `undefined` when the agent
|
|
29221
|
+
* advertises no modes. */
|
|
29222
|
+
currentModeId = void 0;
|
|
29185
29223
|
/** Whether the agent advertised `loadSession` on `initialize`. Drives the
|
|
29186
29224
|
* post-turn session-closed recovery: an agent that supports resume (kimi,
|
|
29187
29225
|
* claude, codex, gemini) is re-established with `session/load` — preserving
|
|
@@ -29402,16 +29440,18 @@ var AcpClient = class {
|
|
|
29402
29440
|
}
|
|
29403
29441
|
this.startAbortReject = null;
|
|
29404
29442
|
this.sessionId = newSession.sessionId;
|
|
29405
|
-
|
|
29443
|
+
this.captureModelConfig(newSession.configOptions ?? []);
|
|
29444
|
+
this.captureModes(newSession.modes ?? null);
|
|
29445
|
+
const tier = newSession.currentServiceTier;
|
|
29406
29446
|
log.info(
|
|
29407
29447
|
"acpClient",
|
|
29408
|
-
`newSession \u2190 ok sessionId=${newSession.sessionId.slice(0, 8)} model=${
|
|
29448
|
+
`newSession \u2190 ok sessionId=${newSession.sessionId.slice(0, 8)} model=${this.currentModelId ?? "?"} models=${this.availableModels.length} tier=${tier ?? "?"}`
|
|
29409
29449
|
);
|
|
29410
29450
|
return {
|
|
29411
29451
|
sessionId: newSession.sessionId,
|
|
29412
29452
|
initialize,
|
|
29413
|
-
model:
|
|
29414
|
-
tier
|
|
29453
|
+
model: this.currentModelId,
|
|
29454
|
+
tier
|
|
29415
29455
|
};
|
|
29416
29456
|
} catch (err) {
|
|
29417
29457
|
this.cleanupAfterFailedStart(child);
|
|
@@ -29618,8 +29658,9 @@ var AcpClient = class {
|
|
|
29618
29658
|
}
|
|
29619
29659
|
log.info("acpClient", `loadSession \u2192 sessionId=${sessionId.slice(0, 8)}`);
|
|
29620
29660
|
this.opts.beginLoadReplay?.();
|
|
29661
|
+
let loaded;
|
|
29621
29662
|
try {
|
|
29622
|
-
await this.connection.loadSession({
|
|
29663
|
+
loaded = await this.connection.loadSession({
|
|
29623
29664
|
sessionId,
|
|
29624
29665
|
cwd: this.opts.cwd,
|
|
29625
29666
|
mcpServers: this.opts.mcpServers ?? []
|
|
@@ -29628,6 +29669,8 @@ var AcpClient = class {
|
|
|
29628
29669
|
this.opts.endLoadReplay?.();
|
|
29629
29670
|
}
|
|
29630
29671
|
this.sessionId = sessionId;
|
|
29672
|
+
if (loaded?.configOptions) this.captureModelConfig(loaded.configOptions);
|
|
29673
|
+
if (loaded?.modes !== void 0) this.captureModes(loaded.modes ?? null);
|
|
29631
29674
|
log.info("acpClient", `loadSession \u2190 ok sessionId=${sessionId.slice(0, 8)}`);
|
|
29632
29675
|
}
|
|
29633
29676
|
/**
|
|
@@ -29656,24 +29699,132 @@ var AcpClient = class {
|
|
|
29656
29699
|
}
|
|
29657
29700
|
}
|
|
29658
29701
|
/**
|
|
29659
|
-
* Switch the active session's model via the
|
|
29660
|
-
* `session/
|
|
29661
|
-
*
|
|
29662
|
-
*
|
|
29663
|
-
*
|
|
29702
|
+
* Switch the active session's model via the NATIVE ACP
|
|
29703
|
+
* `session/set_config_option` RPC, targeting the `category:'model'`
|
|
29704
|
+
* `SessionConfigSelect` option captured on newSession/loadSession. This is
|
|
29705
|
+
* the single source of truth — there is no PTY `/model` fallback and no
|
|
29706
|
+
* non-standard `session/set_model` extension.
|
|
29707
|
+
*
|
|
29708
|
+
* Throws a clear error when the agent exposes no model config option, so
|
|
29709
|
+
* `change_model` acks `failed` and mobile surfaces "model picker not
|
|
29710
|
+
* supported on this agent".
|
|
29664
29711
|
*/
|
|
29665
29712
|
async setModel(modelId) {
|
|
29666
29713
|
if (!this.connection || !this.sessionId) {
|
|
29667
29714
|
throw new Error("AcpClient.setModel called before start()");
|
|
29668
29715
|
}
|
|
29669
|
-
|
|
29670
|
-
|
|
29671
|
-
|
|
29716
|
+
if (!this.modelConfigId) {
|
|
29717
|
+
throw new Error(
|
|
29718
|
+
"model selection not available: this agent exposes no ACP model config option"
|
|
29719
|
+
);
|
|
29720
|
+
}
|
|
29721
|
+
log.info("acpClient", `setModel \u2192 ${modelId} (configId=${this.modelConfigId})`);
|
|
29722
|
+
await this.connection.setSessionConfigOption({
|
|
29672
29723
|
sessionId: this.sessionId,
|
|
29673
|
-
|
|
29724
|
+
configId: this.modelConfigId,
|
|
29725
|
+
value: modelId
|
|
29674
29726
|
});
|
|
29727
|
+
this.currentModelId = modelId;
|
|
29675
29728
|
log.info("acpClient", `setModel \u2190 ok modelId=${modelId}`);
|
|
29676
29729
|
}
|
|
29730
|
+
/**
|
|
29731
|
+
* The models the agent natively advertises via its `category:'model'` config
|
|
29732
|
+
* option (mapped: id = option value, label = option name, contextWindow
|
|
29733
|
+
* derived from the id). Empty array when the agent has no model config option
|
|
29734
|
+
* — mobile then hides the model selector. NO hardcoded fallback.
|
|
29735
|
+
*/
|
|
29736
|
+
getAvailableModels() {
|
|
29737
|
+
return this.availableModels;
|
|
29738
|
+
}
|
|
29739
|
+
/** The native model select's current value (or undefined if none advertised). */
|
|
29740
|
+
getCurrentModelId() {
|
|
29741
|
+
return this.currentModelId;
|
|
29742
|
+
}
|
|
29743
|
+
/**
|
|
29744
|
+
* Parse the native model config option out of a `newSession`/`loadSession`
|
|
29745
|
+
* `configOptions` array and populate {@link modelConfigId},
|
|
29746
|
+
* {@link availableModels}, and {@link currentModelId}. The list itself is
|
|
29747
|
+
* native (the option's `options`); `contextWindow` is a display attribute
|
|
29748
|
+
* derived per-id via {@link getContextWindow}. When no `category:'model'`
|
|
29749
|
+
* select option is present, everything resets to empty/undefined — no
|
|
29750
|
+
* hardcoded list, no guessed model.
|
|
29751
|
+
*/
|
|
29752
|
+
captureModelConfig(configOptions) {
|
|
29753
|
+
const modelOption = configOptions.find(
|
|
29754
|
+
(o) => o.category === "model" && o.type === "select"
|
|
29755
|
+
);
|
|
29756
|
+
if (!modelOption || modelOption.type !== "select") {
|
|
29757
|
+
this.modelConfigId = void 0;
|
|
29758
|
+
this.availableModels = [];
|
|
29759
|
+
this.currentModelId = void 0;
|
|
29760
|
+
return;
|
|
29761
|
+
}
|
|
29762
|
+
this.modelConfigId = modelOption.id;
|
|
29763
|
+
this.currentModelId = modelOption.currentValue;
|
|
29764
|
+
this.availableModels = flattenSelectOptions(modelOption.options).map((opt) => ({
|
|
29765
|
+
id: opt.value,
|
|
29766
|
+
label: opt.name,
|
|
29767
|
+
contextWindow: getContextWindow(opt.value)
|
|
29768
|
+
}));
|
|
29769
|
+
}
|
|
29770
|
+
/**
|
|
29771
|
+
* Switch the active session's operating/permission MODE via the NATIVE ACP
|
|
29772
|
+
* `session/set_mode` RPC (the standard {@link SessionModeState} axis — DISTINCT
|
|
29773
|
+
* from the model config option). This is the single source of truth — there is
|
|
29774
|
+
* no PTY fallback.
|
|
29775
|
+
*
|
|
29776
|
+
* Throws a clear error when the agent advertises no modes, so `set_mode` acks
|
|
29777
|
+
* `failed` and mobile surfaces "mode switching not supported on this agent".
|
|
29778
|
+
*/
|
|
29779
|
+
async setMode(modeId) {
|
|
29780
|
+
if (!this.connection || !this.sessionId) {
|
|
29781
|
+
throw new Error("AcpClient.setMode called before start()");
|
|
29782
|
+
}
|
|
29783
|
+
if (this.availableModes.length === 0) {
|
|
29784
|
+
throw new Error(
|
|
29785
|
+
"mode selection not available: this agent advertises no ACP session modes"
|
|
29786
|
+
);
|
|
29787
|
+
}
|
|
29788
|
+
log.info("acpClient", `setMode \u2192 ${modeId}`);
|
|
29789
|
+
await this.connection.setSessionMode({
|
|
29790
|
+
sessionId: this.sessionId,
|
|
29791
|
+
modeId
|
|
29792
|
+
});
|
|
29793
|
+
this.currentModeId = modeId;
|
|
29794
|
+
log.info("acpClient", `setMode \u2190 ok modeId=${modeId}`);
|
|
29795
|
+
}
|
|
29796
|
+
/**
|
|
29797
|
+
* The operating/permission modes the agent natively advertises via its ACP
|
|
29798
|
+
* `SessionModeState` (mapped: id = mode id, label = mode name, description
|
|
29799
|
+
* passthrough). Empty array when the agent advertises no modes — mobile then
|
|
29800
|
+
* hides the mode selector. NO hardcoded fallback.
|
|
29801
|
+
*/
|
|
29802
|
+
getAvailableModes() {
|
|
29803
|
+
return this.availableModes;
|
|
29804
|
+
}
|
|
29805
|
+
/** The native current mode id (or undefined if the agent advertises no modes). */
|
|
29806
|
+
getCurrentModeId() {
|
|
29807
|
+
return this.currentModeId;
|
|
29808
|
+
}
|
|
29809
|
+
/**
|
|
29810
|
+
* Parse the native ACP {@link SessionModeState} out of a `newSession`/`loadSession`
|
|
29811
|
+
* response `modes` field and populate {@link availableModes} + {@link currentModeId}.
|
|
29812
|
+
* When absent (`null`/undefined — the agent advertises no modes) everything resets
|
|
29813
|
+
* to empty/undefined — no hardcoded list, no guessed mode.
|
|
29814
|
+
*/
|
|
29815
|
+
captureModes(modes) {
|
|
29816
|
+
if (!modes) {
|
|
29817
|
+
this.availableModes = [];
|
|
29818
|
+
this.currentModeId = void 0;
|
|
29819
|
+
return;
|
|
29820
|
+
}
|
|
29821
|
+
this.currentModeId = modes.currentModeId;
|
|
29822
|
+
this.availableModes = (modes.availableModes ?? []).map((mode) => ({
|
|
29823
|
+
id: mode.id,
|
|
29824
|
+
label: mode.name,
|
|
29825
|
+
description: mode.description ?? void 0
|
|
29826
|
+
}));
|
|
29827
|
+
}
|
|
29677
29828
|
/**
|
|
29678
29829
|
* Cancel the in-flight prompt turn. Notification — no response.
|
|
29679
29830
|
* Safe to call when nothing is in flight (the adapter no-ops).
|
|
@@ -29826,6 +29977,17 @@ var AcpClient = class {
|
|
|
29826
29977
|
};
|
|
29827
29978
|
}
|
|
29828
29979
|
};
|
|
29980
|
+
function flattenSelectOptions(options) {
|
|
29981
|
+
const out2 = [];
|
|
29982
|
+
for (const entry of options) {
|
|
29983
|
+
if ("group" in entry) {
|
|
29984
|
+
out2.push(...entry.options);
|
|
29985
|
+
} else {
|
|
29986
|
+
out2.push(entry);
|
|
29987
|
+
}
|
|
29988
|
+
}
|
|
29989
|
+
return out2;
|
|
29990
|
+
}
|
|
29829
29991
|
function applyLineRange(content, line, limit) {
|
|
29830
29992
|
if (line === null && limit === null) return { content };
|
|
29831
29993
|
const lines = content.split("\n");
|
|
@@ -31671,7 +31833,7 @@ async function startTaskH(ctx) {
|
|
|
31671
31833
|
await relay.sendResult(cmd.id, "completed", { stopReason: reply.stopReason });
|
|
31672
31834
|
}
|
|
31673
31835
|
} catch (err) {
|
|
31674
|
-
const hadText = streaming.
|
|
31836
|
+
const hadText = streaming.hasVisibleProgress();
|
|
31675
31837
|
const detail = describeError(err);
|
|
31676
31838
|
log.warn("acpRunner", `prompt failed: ${detail}`);
|
|
31677
31839
|
await cancelStuckTurn(client3);
|
|
@@ -31782,8 +31944,38 @@ async function getConversationH(ctx) {
|
|
|
31782
31944
|
return;
|
|
31783
31945
|
}
|
|
31784
31946
|
async function listModelsH(ctx) {
|
|
31785
|
-
const { cmd, relay,
|
|
31786
|
-
await relay.sendResult(cmd.id, "completed", {
|
|
31947
|
+
const { cmd, relay, client: client3 } = ctx;
|
|
31948
|
+
await relay.sendResult(cmd.id, "completed", {
|
|
31949
|
+
models: client3.getAvailableModels(),
|
|
31950
|
+
currentModelId: client3.getCurrentModelId()
|
|
31951
|
+
});
|
|
31952
|
+
return;
|
|
31953
|
+
}
|
|
31954
|
+
async function listModesH(ctx) {
|
|
31955
|
+
const { cmd, relay, client: client3 } = ctx;
|
|
31956
|
+
await relay.sendResult(cmd.id, "completed", {
|
|
31957
|
+
modes: client3.getAvailableModes(),
|
|
31958
|
+
currentModeId: client3.getCurrentModeId()
|
|
31959
|
+
});
|
|
31960
|
+
return;
|
|
31961
|
+
}
|
|
31962
|
+
async function setModeH(ctx) {
|
|
31963
|
+
const { cmd, client: client3, relay, opts } = ctx;
|
|
31964
|
+
const payload = cmd.payload;
|
|
31965
|
+
const modeId = payload?.modeId?.trim();
|
|
31966
|
+
if (!modeId) {
|
|
31967
|
+
await relay.sendResult(cmd.id, "failed", { error: "modeId required" });
|
|
31968
|
+
return;
|
|
31969
|
+
}
|
|
31970
|
+
try {
|
|
31971
|
+
await client3.setMode(modeId);
|
|
31972
|
+
await relay.sendResult(cmd.id, "completed", { modeId });
|
|
31973
|
+
} catch (err) {
|
|
31974
|
+
log.warn("acpRunner", `set_mode failed: ${describeError(err)}`);
|
|
31975
|
+
await relay.sendResult(cmd.id, "failed", {
|
|
31976
|
+
error: `Mode switching not supported on ${opts.agent} via ACP: ${describeError(err)}`
|
|
31977
|
+
});
|
|
31978
|
+
}
|
|
31787
31979
|
return;
|
|
31788
31980
|
}
|
|
31789
31981
|
async function ackEmptyH(ctx) {
|
|
@@ -32039,6 +32231,8 @@ var ACP_COMMAND_HANDLERS = {
|
|
|
32039
32231
|
escape_key: stopTaskH,
|
|
32040
32232
|
get_conversation: getConversationH,
|
|
32041
32233
|
list_models: listModelsH,
|
|
32234
|
+
list_modes: listModesH,
|
|
32235
|
+
set_mode: setModeH,
|
|
32042
32236
|
set_keep_alive: ackEmptyH,
|
|
32043
32237
|
get_context: ackEmptyH,
|
|
32044
32238
|
select_option: selectOptionH,
|
|
@@ -32250,6 +32444,31 @@ var StreamingState = class {
|
|
|
32250
32444
|
getCurrentText() {
|
|
32251
32445
|
return this.text;
|
|
32252
32446
|
}
|
|
32447
|
+
/**
|
|
32448
|
+
* True when the in-progress turn has ALREADY streamed VISIBLE content —
|
|
32449
|
+
* assistant text OR any thinking / tool_use / tool_result activity (the
|
|
32450
|
+
* SessionDetail rich feed). Deliberately BROADER than {@link getCurrentText}:
|
|
32451
|
+
* a long agentic turn can stream lots of tool/thinking progress and yet NOT
|
|
32452
|
+
* have emitted a final assistant `text` reply at the instant `client.prompt`
|
|
32453
|
+
* throws — the idle watchdog tripping just as the agent finishes its last
|
|
32454
|
+
* tool, or a trailing adapter error AFTER the work completed. Keying the
|
|
32455
|
+
* failure-bubble decision off `text` alone then mis-reads that visible work
|
|
32456
|
+
* as "nothing streamed" → fires the generic TURN_FAILURE_MESSAGE, and
|
|
32457
|
+
* {@link closeWithBubble} REPLACES the whole turn with it (overwriting the
|
|
32458
|
+
* durable snapshot) — an error wiping a fully-streamed transcript AND a false
|
|
32459
|
+
* "couldn't finish" (2026-07-17 incident). The catch path keys "don't clobber
|
|
32460
|
+
* — a partial reply already serves as the terminal frame" off THIS instead,
|
|
32461
|
+
* routing to the non-destructive {@link closeAll}. streamingChunks still holds
|
|
32462
|
+
* the turn's content at catch time (none of the close/flush methods have run
|
|
32463
|
+
* yet on the throw path), so this reflects the real streamed progress.
|
|
32464
|
+
*/
|
|
32465
|
+
hasVisibleProgress() {
|
|
32466
|
+
if (this.text.trim().length > 0) return true;
|
|
32467
|
+
for (const { content } of this.streamingChunks.values()) {
|
|
32468
|
+
if (content.trim().length > 0) return true;
|
|
32469
|
+
}
|
|
32470
|
+
return false;
|
|
32471
|
+
}
|
|
32253
32472
|
async beginTurn(opts) {
|
|
32254
32473
|
this.text = "";
|
|
32255
32474
|
this.streamingChunks.clear();
|
|
@@ -32760,7 +32979,6 @@ async function runAcpSession(opts) {
|
|
|
32760
32979
|
done: true
|
|
32761
32980
|
});
|
|
32762
32981
|
const runtime = createInteractiveAgentStrategy(opts.agent, createOsStrategy());
|
|
32763
|
-
const models = await runtime.listModels();
|
|
32764
32982
|
const history = new AcpHistory(publisher, {
|
|
32765
32983
|
agent: opts.agent,
|
|
32766
32984
|
acpSessionId,
|
|
@@ -32836,7 +33054,6 @@ async function runAcpSession(opts) {
|
|
|
32836
33054
|
client3,
|
|
32837
33055
|
relay,
|
|
32838
33056
|
acpSessionId,
|
|
32839
|
-
models,
|
|
32840
33057
|
streaming,
|
|
32841
33058
|
opts,
|
|
32842
33059
|
history,
|
|
@@ -32892,12 +33109,11 @@ async function runAcpSession(opts) {
|
|
|
32892
33109
|
await new Promise(() => {
|
|
32893
33110
|
});
|
|
32894
33111
|
}
|
|
32895
|
-
async function handleCommand(cmd, client3, relay, acpSessionId,
|
|
33112
|
+
async function handleCommand(cmd, client3, relay, acpSessionId, streaming, opts, history, jsonlHistory, agentCaps, turnFiles, getBeads, publisher, recentStderr, budgetRecovery, budgetReachedFlag, onActiveSessionChanged) {
|
|
32896
33113
|
const session = {
|
|
32897
33114
|
client: client3,
|
|
32898
33115
|
relay,
|
|
32899
33116
|
acpSessionId,
|
|
32900
|
-
models,
|
|
32901
33117
|
streaming,
|
|
32902
33118
|
opts,
|
|
32903
33119
|
history,
|
|
@@ -34233,7 +34449,6 @@ var AcpDriver = class {
|
|
|
34233
34449
|
async ensureSession(relay, acpSessionId, agentCaps) {
|
|
34234
34450
|
if (this.session) return this.session;
|
|
34235
34451
|
const { opts, publisher, streaming, runtime, recentStderr } = this.deps;
|
|
34236
|
-
const models = await runtime.listModels();
|
|
34237
34452
|
const jsonlHistory = new HistoryService(runtime, opts.pluginId, opts.cwd, {
|
|
34238
34453
|
pluginAuthToken: opts.pluginAuthToken
|
|
34239
34454
|
});
|
|
@@ -34273,7 +34488,6 @@ var AcpDriver = class {
|
|
|
34273
34488
|
client: this.deps.client,
|
|
34274
34489
|
relay,
|
|
34275
34490
|
acpSessionId,
|
|
34276
|
-
models,
|
|
34277
34491
|
streaming,
|
|
34278
34492
|
opts,
|
|
34279
34493
|
history,
|
|
@@ -34782,7 +34996,7 @@ async function start(requestedAgent) {
|
|
|
34782
34996
|
const depsReady = process.env.CODESPACES === "true" ? provisionProjectDependencies(cwd).catch(() => void 0) : Promise.resolve();
|
|
34783
34997
|
if (process.env.CODESPACES === "true") {
|
|
34784
34998
|
const GATE_TIMEOUT_MS = 24e4;
|
|
34785
|
-
const acpAdapterForGate =
|
|
34999
|
+
const acpAdapterForGate = requiresAcp(session.agent) ? await resolveAcpAdapterWithRetry(session.agent) : null;
|
|
34786
35000
|
const agentBinaryReady = acpAdapterForGate ? Promise.all([
|
|
34787
35001
|
acpAdapterForGate.waitForBinary({ timeoutMs: GATE_TIMEOUT_MS }).catch(() => false),
|
|
34788
35002
|
waitForAdapterModuleGraph(acpAdapterForGate.command, acpAdapterForGate.args, {
|
|
@@ -34803,7 +35017,7 @@ async function start(requestedAgent) {
|
|
|
34803
35017
|
);
|
|
34804
35018
|
}
|
|
34805
35019
|
if (isLocalSession() && requiresAcp(session.agent)) {
|
|
34806
|
-
const adapter =
|
|
35020
|
+
const adapter = await resolveAcpAdapterWithRetry(session.agent);
|
|
34807
35021
|
const batonCapable = runtimeSupportsBaton(createRuntimeStrategy(session.agent));
|
|
34808
35022
|
if (batonCapable && adapter && session.pluginAuthToken) {
|
|
34809
35023
|
await runBatonSession({
|
|
@@ -37598,7 +37812,7 @@ function checkChokidar() {
|
|
|
37598
37812
|
}
|
|
37599
37813
|
async function doctor(args2 = []) {
|
|
37600
37814
|
const json = args2.includes("--json");
|
|
37601
|
-
const cliVersion = true ? "2.61.
|
|
37815
|
+
const cliVersion = true ? "2.61.18" : "0.0.0-dev";
|
|
37602
37816
|
const apiBase2 = resolveApiBaseUrl();
|
|
37603
37817
|
const diagnosticId = (0, import_node_crypto12.randomUUID)();
|
|
37604
37818
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -38109,7 +38323,7 @@ async function mcpRun(args2) {
|
|
|
38109
38323
|
// src/commands/version.ts
|
|
38110
38324
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
38111
38325
|
function version2() {
|
|
38112
|
-
const v = true ? "2.61.
|
|
38326
|
+
const v = true ? "2.61.18" : "unknown";
|
|
38113
38327
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
38114
38328
|
}
|
|
38115
38329
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.61.
|
|
3
|
+
"version": "2.61.18",
|
|
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",
|