codeam-cli 2.65.11 → 2.65.13
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 +51 -0
- package/dist/index.js +276 -28
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,57 @@ 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.12] — 2026-08-19
|
|
8
|
+
|
|
9
|
+
### CI
|
|
10
|
+
|
|
11
|
+
- Make the real session-baton gate actually run, and catch failed publishes (#637)
|
|
12
|
+
- CI results → Discord #ci_notifications, cross-os PR label, release success notice (#639)
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **cli:** Baton follows the native TUI through /clear (+ /rename) (#640)
|
|
17
|
+
|
|
18
|
+
### Tests
|
|
19
|
+
|
|
20
|
+
- **cli:** Fix cross-OS CI failures in augment-PATH and adapter module-graph tests (#638)
|
|
21
|
+
|
|
22
|
+
## [2.65.11] — 2026-08-19
|
|
23
|
+
|
|
24
|
+
### Documentation
|
|
25
|
+
|
|
26
|
+
- **cli:** Record the 2026-08-18 baton hand-off + goodbye-heartbeat invariants
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **cli:** Take-control never wedges on "Switching…" again
|
|
31
|
+
- **cli:** Say goodbye before exiting so mobile stops showing ONLINE
|
|
32
|
+
- **cli:** LOCAL_DRIVE mirrors the transcript only — no raw TUI bytes to mobile
|
|
33
|
+
|
|
34
|
+
### Tests
|
|
35
|
+
|
|
36
|
+
- **cli:** Real baton local integration test (take-control before first turn, handback, goodbye heartbeat)
|
|
37
|
+
|
|
38
|
+
## [2.65.10] — 2026-08-19
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **cli:** Codespace sessions report the user's repo, not the shared wrapper hostname
|
|
43
|
+
- **cli:** Dedupe the native ACP model list and validate model/tier at the wire boundary
|
|
44
|
+
- **cli:** Welcome card greets first-time users correctly and never shows a raw internal path
|
|
45
|
+
|
|
46
|
+
## [2.65.9] — 2026-08-19
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **cli:** Report an ineligible-tier credential to the backend on startup failure
|
|
51
|
+
|
|
52
|
+
## [2.65.8] — 2026-08-18
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **cli:** Baton re-affirms its state on the relay heartbeat so the 1h backend snapshot never expires under a live session
|
|
57
|
+
|
|
7
58
|
## [2.65.7] — 2026-08-15
|
|
8
59
|
|
|
9
60
|
### Fixed
|
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.13" : "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.13",
|
|
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.13" ? { ideVersion: "2.65.13" } : {}
|
|
9846
9846
|
}).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
|
|
9847
9847
|
}
|
|
9848
9848
|
/**
|
|
@@ -15283,6 +15283,7 @@ function parseHistoryFile(filePath) {
|
|
|
15283
15283
|
if (!msg) continue;
|
|
15284
15284
|
const text = extractText(msg["content"]).trim();
|
|
15285
15285
|
if (!text) continue;
|
|
15286
|
+
if (type === "user" && isLocalCommandEcho(text)) continue;
|
|
15286
15287
|
const ts = r["timestamp"];
|
|
15287
15288
|
const timestamp = typeof ts === "string" ? ts : typeof ts === "number" ? new Date(ts).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
15288
15289
|
const uuid = typeof r["uuid"] === "string" ? r["uuid"] : `${Date.now()}-${Math.random()}`;
|
|
@@ -15347,6 +15348,175 @@ function listResumableSessions(cwd) {
|
|
|
15347
15348
|
out2.sort((a, b) => b.timestamp - a.timestamp);
|
|
15348
15349
|
return out2;
|
|
15349
15350
|
}
|
|
15351
|
+
function isLocalCommandEcho(text) {
|
|
15352
|
+
return /^\s*<(command-name|local-command-[a-z]+)>/.test(text);
|
|
15353
|
+
}
|
|
15354
|
+
var CLEAR_COMMAND_ECHO = "<command-name>/clear</command-name>";
|
|
15355
|
+
var SWITCH_PROBE_BYTES = 256 * 1024;
|
|
15356
|
+
function isClearedConversationFile(filePath) {
|
|
15357
|
+
let fd;
|
|
15358
|
+
try {
|
|
15359
|
+
fd = fs17.openSync(filePath, "r");
|
|
15360
|
+
} catch {
|
|
15361
|
+
return false;
|
|
15362
|
+
}
|
|
15363
|
+
let raw;
|
|
15364
|
+
try {
|
|
15365
|
+
const buf = Buffer.alloc(SWITCH_PROBE_BYTES);
|
|
15366
|
+
const n = fs17.readSync(fd, buf, 0, SWITCH_PROBE_BYTES, 0);
|
|
15367
|
+
raw = buf.toString("utf8", 0, n);
|
|
15368
|
+
} catch {
|
|
15369
|
+
return false;
|
|
15370
|
+
} finally {
|
|
15371
|
+
fs17.closeSync(fd);
|
|
15372
|
+
}
|
|
15373
|
+
if (!raw.includes(CLEAR_COMMAND_ECHO) && !raw.includes("SessionStart:clear")) return false;
|
|
15374
|
+
for (const line of raw.split("\n")) {
|
|
15375
|
+
if (!line.trim()) continue;
|
|
15376
|
+
let r;
|
|
15377
|
+
try {
|
|
15378
|
+
r = JSON.parse(line);
|
|
15379
|
+
} catch {
|
|
15380
|
+
continue;
|
|
15381
|
+
}
|
|
15382
|
+
if (r["type"] === "user" && !r["isMeta"]) {
|
|
15383
|
+
const msg = r["message"];
|
|
15384
|
+
if (extractText(msg?.["content"]).trimStart().startsWith(CLEAR_COMMAND_ECHO)) return true;
|
|
15385
|
+
}
|
|
15386
|
+
if (r["type"] === "attachment") {
|
|
15387
|
+
const att = r["attachment"];
|
|
15388
|
+
if (typeof att?.["hookName"] === "string" && att["hookName"].startsWith("SessionStart:clear")) {
|
|
15389
|
+
return true;
|
|
15390
|
+
}
|
|
15391
|
+
}
|
|
15392
|
+
}
|
|
15393
|
+
return false;
|
|
15394
|
+
}
|
|
15395
|
+
function appendedTailHasResumeMarker(filePath, fromOffset) {
|
|
15396
|
+
let fd;
|
|
15397
|
+
try {
|
|
15398
|
+
fd = fs17.openSync(filePath, "r");
|
|
15399
|
+
} catch {
|
|
15400
|
+
return false;
|
|
15401
|
+
}
|
|
15402
|
+
try {
|
|
15403
|
+
const size = fs17.fstatSync(fd).size;
|
|
15404
|
+
const len = Math.min(Math.max(size - fromOffset, 0), SWITCH_PROBE_BYTES);
|
|
15405
|
+
if (len <= 0) return false;
|
|
15406
|
+
const buf = Buffer.alloc(len);
|
|
15407
|
+
const n = fs17.readSync(fd, buf, 0, len, fromOffset);
|
|
15408
|
+
const raw = buf.toString("utf8", 0, n);
|
|
15409
|
+
if (!raw.includes('"last-prompt"')) return false;
|
|
15410
|
+
for (const line of raw.split("\n")) {
|
|
15411
|
+
if (!line.trim()) continue;
|
|
15412
|
+
try {
|
|
15413
|
+
if (JSON.parse(line)["type"] === "last-prompt") return true;
|
|
15414
|
+
} catch {
|
|
15415
|
+
}
|
|
15416
|
+
}
|
|
15417
|
+
return false;
|
|
15418
|
+
} catch {
|
|
15419
|
+
return false;
|
|
15420
|
+
} finally {
|
|
15421
|
+
fs17.closeSync(fd);
|
|
15422
|
+
}
|
|
15423
|
+
}
|
|
15424
|
+
function watchConversationSwitch(cwd, opts, onSwitch, projectsRoot) {
|
|
15425
|
+
const root = projectsRoot ?? path21.join(os15.homedir(), ".claude", "projects");
|
|
15426
|
+
let currentId = opts.currentId;
|
|
15427
|
+
const baseline = /* @__PURE__ */ new Map();
|
|
15428
|
+
const unclassified = /* @__PURE__ */ new Map();
|
|
15429
|
+
let dirWatcher = null;
|
|
15430
|
+
let rootWatcher = null;
|
|
15431
|
+
let closed = false;
|
|
15432
|
+
const fileSize = (file) => {
|
|
15433
|
+
try {
|
|
15434
|
+
return fs17.statSync(file).size;
|
|
15435
|
+
} catch {
|
|
15436
|
+
return null;
|
|
15437
|
+
}
|
|
15438
|
+
};
|
|
15439
|
+
const switchTo = (dir, id, kind) => {
|
|
15440
|
+
const leaving = fileSize(path21.join(dir, `${currentId}.jsonl`));
|
|
15441
|
+
if (leaving !== null) baseline.set(currentId, leaving);
|
|
15442
|
+
baseline.delete(id);
|
|
15443
|
+
unclassified.delete(id);
|
|
15444
|
+
currentId = id;
|
|
15445
|
+
onSwitch(id, { kind });
|
|
15446
|
+
};
|
|
15447
|
+
const scan = (dir) => {
|
|
15448
|
+
if (closed) return;
|
|
15449
|
+
let entries;
|
|
15450
|
+
try {
|
|
15451
|
+
entries = fs17.readdirSync(dir);
|
|
15452
|
+
} catch {
|
|
15453
|
+
return;
|
|
15454
|
+
}
|
|
15455
|
+
for (const name of entries) {
|
|
15456
|
+
if (!name.endsWith(".jsonl")) continue;
|
|
15457
|
+
const id = name.slice(0, -".jsonl".length);
|
|
15458
|
+
if (id === currentId) continue;
|
|
15459
|
+
const file = path21.join(dir, name);
|
|
15460
|
+
const size = fileSize(file);
|
|
15461
|
+
if (size === null) continue;
|
|
15462
|
+
const known = baseline.get(id);
|
|
15463
|
+
if (known !== void 0) {
|
|
15464
|
+
if (size > known && appendedTailHasResumeMarker(file, known)) {
|
|
15465
|
+
switchTo(dir, id, "resumed");
|
|
15466
|
+
return;
|
|
15467
|
+
}
|
|
15468
|
+
continue;
|
|
15469
|
+
}
|
|
15470
|
+
if (unclassified.get(id) === size) continue;
|
|
15471
|
+
unclassified.set(id, size);
|
|
15472
|
+
if (isClearedConversationFile(file)) {
|
|
15473
|
+
switchTo(dir, id, "new");
|
|
15474
|
+
return;
|
|
15475
|
+
}
|
|
15476
|
+
}
|
|
15477
|
+
};
|
|
15478
|
+
const attachDir = (initial) => {
|
|
15479
|
+
const dir = resolveHistoryDir(cwd, root);
|
|
15480
|
+
if (!dir) return false;
|
|
15481
|
+
rootWatcher?.close();
|
|
15482
|
+
rootWatcher = null;
|
|
15483
|
+
if (initial) {
|
|
15484
|
+
try {
|
|
15485
|
+
for (const name of fs17.readdirSync(dir)) {
|
|
15486
|
+
if (!name.endsWith(".jsonl")) continue;
|
|
15487
|
+
const id = name.slice(0, -".jsonl".length);
|
|
15488
|
+
if (id === currentId) continue;
|
|
15489
|
+
const size = fileSize(path21.join(dir, name));
|
|
15490
|
+
if (size !== null) baseline.set(id, size);
|
|
15491
|
+
}
|
|
15492
|
+
} catch {
|
|
15493
|
+
}
|
|
15494
|
+
}
|
|
15495
|
+
try {
|
|
15496
|
+
dirWatcher = fs17.watch(dir, { persistent: false }, () => scan(dir));
|
|
15497
|
+
} catch {
|
|
15498
|
+
return false;
|
|
15499
|
+
}
|
|
15500
|
+
scan(dir);
|
|
15501
|
+
return true;
|
|
15502
|
+
};
|
|
15503
|
+
if (!attachDir(true)) {
|
|
15504
|
+
try {
|
|
15505
|
+
fs17.mkdirSync(root, { recursive: true });
|
|
15506
|
+
rootWatcher = fs17.watch(root, { persistent: false }, () => {
|
|
15507
|
+
if (!closed && !dirWatcher) attachDir(false);
|
|
15508
|
+
});
|
|
15509
|
+
} catch {
|
|
15510
|
+
}
|
|
15511
|
+
}
|
|
15512
|
+
return () => {
|
|
15513
|
+
closed = true;
|
|
15514
|
+
dirWatcher?.close();
|
|
15515
|
+
rootWatcher?.close();
|
|
15516
|
+
dirWatcher = null;
|
|
15517
|
+
rootWatcher = null;
|
|
15518
|
+
};
|
|
15519
|
+
}
|
|
15350
15520
|
|
|
15351
15521
|
// src/agents/claude/runtime.ts
|
|
15352
15522
|
var ClaudeRuntimeStrategy = class {
|
|
@@ -15436,6 +15606,14 @@ var ClaudeRuntimeStrategy = class {
|
|
|
15436
15606
|
parseHistoryFile(filePath) {
|
|
15437
15607
|
return parseHistoryFile(filePath);
|
|
15438
15608
|
}
|
|
15609
|
+
/**
|
|
15610
|
+
* Baton: follow the native TUI through `/clear` (new conversation id, new
|
|
15611
|
+
* JSONL) and `/resume` (an existing one) — see
|
|
15612
|
+
* {@link history.watchConversationSwitch}.
|
|
15613
|
+
*/
|
|
15614
|
+
watchConversationSwitch(cwd, opts, onSwitch) {
|
|
15615
|
+
return watchConversationSwitch(cwd, opts, onSwitch);
|
|
15616
|
+
}
|
|
15439
15617
|
getCurrentUsage(historyDir) {
|
|
15440
15618
|
return getCurrentUsage(historyDir);
|
|
15441
15619
|
}
|
|
@@ -18137,25 +18315,27 @@ async function waitForCommandOnPath(cmd, opts = {}) {
|
|
|
18137
18315
|
}
|
|
18138
18316
|
return check();
|
|
18139
18317
|
}
|
|
18140
|
-
function augmentUserLocalBinPaths() {
|
|
18141
|
-
const
|
|
18318
|
+
function augmentUserLocalBinPaths(deps = {}) {
|
|
18319
|
+
const env = deps.env ?? process.env;
|
|
18320
|
+
const home = deps.homedir ?? import_os4.default.homedir();
|
|
18321
|
+
const p2 = deps.pathApi ?? import_path4.default;
|
|
18142
18322
|
const candidates = [
|
|
18143
18323
|
// XDG-style per-user bin — npm's default global-prefix bin dir on most
|
|
18144
18324
|
// Linux setups (`npm config set prefix ~/.local` or an nvm-less
|
|
18145
18325
|
// per-user npm), and where curl-based agent installers commonly land.
|
|
18146
|
-
|
|
18326
|
+
p2.join(home, ".local", "bin"),
|
|
18147
18327
|
// Common explicit npm global-prefix conventions seen in the wild
|
|
18148
18328
|
// (`npm config set prefix ~/.npm-global`, and Debian/Fedora's
|
|
18149
18329
|
// `~/.local/share/npm` layout for `npm config set prefix
|
|
18150
18330
|
// ~/.local/share/npm`).
|
|
18151
|
-
|
|
18152
|
-
|
|
18331
|
+
p2.join(home, ".npm-global", "bin"),
|
|
18332
|
+
p2.join(home, ".local", "share", "npm", "bin")
|
|
18153
18333
|
];
|
|
18154
|
-
const parts = (
|
|
18334
|
+
const parts = (env.PATH ?? "").split(p2.delimiter).filter((s) => s.length > 0);
|
|
18155
18335
|
const existing = new Set(parts);
|
|
18156
18336
|
const additions = candidates.filter((dir) => !existing.has(dir));
|
|
18157
18337
|
if (additions.length === 0) return;
|
|
18158
|
-
|
|
18338
|
+
env.PATH = [...additions, ...parts].join(p2.delimiter);
|
|
18159
18339
|
}
|
|
18160
18340
|
function resolveCursorAgentBinary(deps = {}) {
|
|
18161
18341
|
const existsSync29 = deps.existsSync ?? import_fs.default.existsSync;
|
|
@@ -21786,7 +21966,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
21786
21966
|
if (process.env.NODE_ENV === "test") return;
|
|
21787
21967
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
21788
21968
|
if (process.env.CI) return;
|
|
21789
|
-
const current2 = true ? "2.65.
|
|
21969
|
+
const current2 = true ? "2.65.13" : null;
|
|
21790
21970
|
if (!current2) return;
|
|
21791
21971
|
const cache = readCache();
|
|
21792
21972
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -21803,7 +21983,7 @@ function checkForUpdates() {
|
|
|
21803
21983
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
21804
21984
|
if (process.env.CI) return;
|
|
21805
21985
|
if (!process.stdout.isTTY) return;
|
|
21806
|
-
const current2 = true ? "2.65.
|
|
21986
|
+
const current2 = true ? "2.65.13" : null;
|
|
21807
21987
|
if (!current2) return;
|
|
21808
21988
|
const cache = readCache();
|
|
21809
21989
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -21823,7 +22003,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
|
21823
22003
|
var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
|
|
21824
22004
|
var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
|
|
21825
22005
|
function currentCliVersion() {
|
|
21826
|
-
return true ? "2.65.
|
|
22006
|
+
return true ? "2.65.13" : null;
|
|
21827
22007
|
}
|
|
21828
22008
|
function runCmd(cmd, args2, timeoutMs) {
|
|
21829
22009
|
return new Promise((resolve10) => {
|
|
@@ -34580,14 +34760,17 @@ var AcpClient = class {
|
|
|
34580
34760
|
this.modelConfigId = modelOption.id;
|
|
34581
34761
|
this.currentModelId = nonEmptyString(modelOption.currentValue);
|
|
34582
34762
|
this.availableModels = dedupeModelOptions(
|
|
34583
|
-
|
|
34584
|
-
|
|
34585
|
-
|
|
34586
|
-
|
|
34587
|
-
|
|
34588
|
-
|
|
34589
|
-
|
|
34590
|
-
|
|
34763
|
+
filterChatModelOptions(
|
|
34764
|
+
flattenSelectOptions(modelOption.options).map((opt) => ({
|
|
34765
|
+
id: opt.value,
|
|
34766
|
+
label: opt.name,
|
|
34767
|
+
// Only when it's a real catalog match — native ids are often opaque
|
|
34768
|
+
// aliases ("default"/"opus") or proxied (MiniMax house agent), for which a
|
|
34769
|
+
// default 200K is a fake; undefined → the UI omits the context sub-label.
|
|
34770
|
+
contextWindow: tryGetContextWindow(opt.value)
|
|
34771
|
+
})),
|
|
34772
|
+
this.currentModelId
|
|
34773
|
+
)
|
|
34591
34774
|
);
|
|
34592
34775
|
}
|
|
34593
34776
|
/**
|
|
@@ -34815,6 +34998,21 @@ function flattenSelectOptions(options) {
|
|
|
34815
34998
|
function nonEmptyString(value) {
|
|
34816
34999
|
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
34817
35000
|
}
|
|
35001
|
+
var NON_CHAT_MODEL_PATTERNS = [
|
|
35002
|
+
/\bembed(?:ding)?s?\b/i,
|
|
35003
|
+
// text-embedding-3-large, *-embed-*, embeddings
|
|
35004
|
+
/\brerank(?:er|ing)?\b/i,
|
|
35005
|
+
/\bmoderation\b/i,
|
|
35006
|
+
/\btts\b/i,
|
|
35007
|
+
/\bwhisper\b/i,
|
|
35008
|
+
/\btranscribe\b/i,
|
|
35009
|
+
/\bdall-?e\b/i
|
|
35010
|
+
];
|
|
35011
|
+
function filterChatModelOptions(models, currentModelId) {
|
|
35012
|
+
return models.filter(
|
|
35013
|
+
(m) => m.id === currentModelId || !NON_CHAT_MODEL_PATTERNS.some((re2) => re2.test(`${m.id} ${m.label}`))
|
|
35014
|
+
);
|
|
35015
|
+
}
|
|
34818
35016
|
function dedupeModelOptions(models) {
|
|
34819
35017
|
const seen = /* @__PURE__ */ new Set();
|
|
34820
35018
|
const unique = [];
|
|
@@ -41268,6 +41466,21 @@ var BatonController = class _BatonController {
|
|
|
41268
41466
|
this._conversationId = conversationId;
|
|
41269
41467
|
this.setState("LOCAL_DRIVE");
|
|
41270
41468
|
}
|
|
41469
|
+
/**
|
|
41470
|
+
* The native TUI SWITCHED conversation while driving (Claude `/clear` →
|
|
41471
|
+
* a new id, `/resume` → an existing one): the process and the pairing are
|
|
41472
|
+
* unchanged, but the conversation the user is in has another id. Re-point
|
|
41473
|
+
* the baton at it so the read-only mirror follows that transcript and a
|
|
41474
|
+
* later Take Control resumes THAT conversation instead of the abandoned one. Guarded to LOCAL_DRIVE — only
|
|
41475
|
+
* the terminal can switch its own conversation, and during a hand-off /
|
|
41476
|
+
* MOBILE_DRIVE the native watcher is torn down anyway (`stop()`). Same id →
|
|
41477
|
+
* no-op. Re-publishes LOCAL_DRIVE so the mirror re-arms + mobile learns the id.
|
|
41478
|
+
*/
|
|
41479
|
+
switchConversation(conversationId) {
|
|
41480
|
+
if (this._state !== "LOCAL_DRIVE" || this._conversationId === conversationId) return;
|
|
41481
|
+
this._conversationId = conversationId;
|
|
41482
|
+
this.setState("LOCAL_DRIVE");
|
|
41483
|
+
}
|
|
41271
41484
|
async takeControl() {
|
|
41272
41485
|
await this.switchDriver(
|
|
41273
41486
|
"LOCAL_DRIVE",
|
|
@@ -41419,18 +41632,25 @@ var NativeTuiDriver = class _NativeTuiDriver {
|
|
|
41419
41632
|
static QUICK_DISCOVER_MS = 8e3;
|
|
41420
41633
|
outputSvc;
|
|
41421
41634
|
historySvc;
|
|
41635
|
+
/** Unsubscribe for the runtime's conversation-switch watcher (armed per
|
|
41636
|
+
* `start()`, torn down in `stop()`), or null when not armed. */
|
|
41637
|
+
unwatchSwitch = null;
|
|
41422
41638
|
setKeepAlive;
|
|
41423
41639
|
keepAliveCtx;
|
|
41424
41640
|
async start(resumeId) {
|
|
41425
41641
|
if (resumeId !== void 0) {
|
|
41426
41642
|
await this.deps.runtime.syncTranscriptForNativeResume?.(this.deps.opts.cwd, resumeId);
|
|
41427
41643
|
await this.agent.restart(resumeId, false);
|
|
41644
|
+
this.armSwitchWatch(resumeId);
|
|
41428
41645
|
return resumeId;
|
|
41429
41646
|
}
|
|
41430
41647
|
const spawnedAt = this.now();
|
|
41431
41648
|
await this.agent.spawn();
|
|
41432
41649
|
const preMinted = this.agent.spawnedSessionId;
|
|
41433
|
-
if (preMinted)
|
|
41650
|
+
if (preMinted) {
|
|
41651
|
+
this.armSwitchWatch(preMinted);
|
|
41652
|
+
return preMinted;
|
|
41653
|
+
}
|
|
41434
41654
|
const discover = this.deps.runtime.discoverSessionId;
|
|
41435
41655
|
if (!discover) {
|
|
41436
41656
|
throw new Error("NativeTuiDriver: agent did not expose a session id after spawn");
|
|
@@ -41446,7 +41666,26 @@ var NativeTuiDriver = class _NativeTuiDriver {
|
|
|
41446
41666
|
});
|
|
41447
41667
|
return null;
|
|
41448
41668
|
}
|
|
41669
|
+
/**
|
|
41670
|
+
* Follow the native TUI if it switches conversation mid-session (Claude
|
|
41671
|
+
* `/clear` / `/resume`): the runtime's event-driven watcher reports the id
|
|
41672
|
+
* now being driven, we point the PTY-side history at it and tell the
|
|
41673
|
+
* controller to rebind. Armed once per `start()` (fresh or resume) on the
|
|
41674
|
+
* id the TUI is driving; inert for agents without the hook.
|
|
41675
|
+
*/
|
|
41676
|
+
armSwitchWatch(currentId) {
|
|
41677
|
+
this.unwatchSwitch?.();
|
|
41678
|
+
this.unwatchSwitch = null;
|
|
41679
|
+
const watch3 = this.deps.runtime.watchConversationSwitch;
|
|
41680
|
+
if (!watch3) return;
|
|
41681
|
+
this.unwatchSwitch = watch3(this.deps.opts.cwd, { currentId }, (id) => {
|
|
41682
|
+
this.historySvc.setCurrentConversationId(id);
|
|
41683
|
+
this.deps.onConversationSwitch?.(id);
|
|
41684
|
+
});
|
|
41685
|
+
}
|
|
41449
41686
|
async stop() {
|
|
41687
|
+
this.unwatchSwitch?.();
|
|
41688
|
+
this.unwatchSwitch = null;
|
|
41450
41689
|
this.agent.kill();
|
|
41451
41690
|
parkTerminalForReadonly();
|
|
41452
41691
|
}
|
|
@@ -41693,8 +41932,8 @@ var TranscriptMirror = class {
|
|
|
41693
41932
|
if (!file) return false;
|
|
41694
41933
|
this.attached = true;
|
|
41695
41934
|
this.emit(file);
|
|
41696
|
-
const
|
|
41697
|
-
this.unwatch =
|
|
41935
|
+
const watch3 = this.deps.watch ?? defaultWatch;
|
|
41936
|
+
this.unwatch = watch3(file, () => this.emit(file));
|
|
41698
41937
|
return true;
|
|
41699
41938
|
}
|
|
41700
41939
|
clearPoll() {
|
|
@@ -41909,10 +42148,17 @@ async function runBatonSession(opts) {
|
|
|
41909
42148
|
// calls this once the user's first terminal turn creates the transcript.
|
|
41910
42149
|
// Safe forward-ref — `controller` is initialised long before `begin()` runs,
|
|
41911
42150
|
// and onLateBind only fires from inside `begin()`'s spawn.
|
|
41912
|
-
onLateBind: (id) => controller.rebindConversation(id)
|
|
42151
|
+
onLateBind: (id) => controller.rebindConversation(id),
|
|
42152
|
+
// The native TUI switched conversation mid-drive (Claude `/clear` mints a
|
|
42153
|
+
// new id + JSONL; `/resume` re-opens an existing one): rebind so the mirror
|
|
42154
|
+
// follows that transcript and a later Take Control resumes THAT
|
|
42155
|
+
// conversation. Owner report 2026-08-18: after `/clear` (+`/rename`) the
|
|
42156
|
+
// mobile went silent — the mirror kept tailing the abandoned file.
|
|
42157
|
+
onConversationSwitch: (id) => controller.switchConversation(id)
|
|
41913
42158
|
});
|
|
41914
42159
|
let mirror = null;
|
|
41915
42160
|
let firstLocalDrive = true;
|
|
42161
|
+
let lastPublished = null;
|
|
41916
42162
|
const startMirror = (conversationId, fresh) => {
|
|
41917
42163
|
mirror?.stop();
|
|
41918
42164
|
mirror = new TranscriptMirror({
|
|
@@ -41945,11 +42191,13 @@ async function runBatonSession(opts) {
|
|
|
41945
42191
|
publishState: (state, driver, conversationId) => {
|
|
41946
42192
|
publishBatonState(state, driver, conversationId);
|
|
41947
42193
|
if (state === "LOCAL_DRIVE" && conversationId) {
|
|
41948
|
-
|
|
42194
|
+
const switched = lastPublished === "LOCAL_DRIVE";
|
|
42195
|
+
startMirror(conversationId, firstLocalDrive || switched);
|
|
41949
42196
|
firstLocalDrive = false;
|
|
41950
42197
|
} else if (state !== "LOCAL_DRIVE") {
|
|
41951
42198
|
mirror?.stop();
|
|
41952
42199
|
}
|
|
42200
|
+
lastPublished = state;
|
|
41953
42201
|
}
|
|
41954
42202
|
});
|
|
41955
42203
|
const dispatchActive = (cmd) => controller.activeSessionDriver.dispatch(cmd);
|
|
@@ -45024,7 +45272,7 @@ function checkChokidar() {
|
|
|
45024
45272
|
}
|
|
45025
45273
|
async function doctor(args2 = []) {
|
|
45026
45274
|
const json = args2.includes("--json");
|
|
45027
|
-
const cliVersion = true ? "2.65.
|
|
45275
|
+
const cliVersion = true ? "2.65.13" : "0.0.0-dev";
|
|
45028
45276
|
const apiBase2 = resolveApiBaseUrl();
|
|
45029
45277
|
const diagnosticId = (0, import_node_crypto13.randomUUID)();
|
|
45030
45278
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -45415,7 +45663,7 @@ async function mcpRun(args2) {
|
|
|
45415
45663
|
// src/commands/version.ts
|
|
45416
45664
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
45417
45665
|
function version2() {
|
|
45418
|
-
const v = true ? "2.65.
|
|
45666
|
+
const v = true ? "2.65.13" : "unknown";
|
|
45419
45667
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
45420
45668
|
}
|
|
45421
45669
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.65.
|
|
3
|
+
"version": "2.65.13",
|
|
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",
|