codeam-cli 2.65.10 → 2.65.12
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 +36 -0
- package/dist/index.js +432 -46
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,42 @@ 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.11] — 2026-08-19
|
|
8
|
+
|
|
9
|
+
### Documentation
|
|
10
|
+
|
|
11
|
+
- **cli:** Record the 2026-08-18 baton hand-off + goodbye-heartbeat invariants
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **cli:** Take-control never wedges on "Switching…" again
|
|
16
|
+
- **cli:** Say goodbye before exiting so mobile stops showing ONLINE
|
|
17
|
+
- **cli:** LOCAL_DRIVE mirrors the transcript only — no raw TUI bytes to mobile
|
|
18
|
+
|
|
19
|
+
### Tests
|
|
20
|
+
|
|
21
|
+
- **cli:** Real baton local integration test (take-control before first turn, handback, goodbye heartbeat)
|
|
22
|
+
|
|
23
|
+
## [2.65.10] — 2026-08-19
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- **cli:** Codespace sessions report the user's repo, not the shared wrapper hostname
|
|
28
|
+
- **cli:** Dedupe the native ACP model list and validate model/tier at the wire boundary
|
|
29
|
+
- **cli:** Welcome card greets first-time users correctly and never shows a raw internal path
|
|
30
|
+
|
|
31
|
+
## [2.65.9] — 2026-08-19
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **cli:** Report an ineligible-tier credential to the backend on startup failure
|
|
36
|
+
|
|
37
|
+
## [2.65.8] — 2026-08-18
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **cli:** Baton re-affirms its state on the relay heartbeat so the 1h backend snapshot never expires under a live session
|
|
42
|
+
|
|
7
43
|
## [2.65.7] — 2026-08-15
|
|
8
44
|
|
|
9
45
|
### 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.12" : "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.12",
|
|
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",
|
|
@@ -9542,12 +9542,30 @@ var CommandRelayService = class _CommandRelayService {
|
|
|
9542
9542
|
this.connectSSE();
|
|
9543
9543
|
}
|
|
9544
9544
|
}
|
|
9545
|
+
/**
|
|
9546
|
+
* Stop the relay. Kept SYNCHRONOUS for the many callers that just tear down
|
|
9547
|
+
* — the goodbye heartbeat is fired but NOT awaited here.
|
|
9548
|
+
*
|
|
9549
|
+
* ⚠️ On a shutdown path that calls `process.exit()` right after, use
|
|
9550
|
+
* {@link stopAndFlush} (or {@link stopRelayWithGoodbye}) instead: the process
|
|
9551
|
+
* dies before the fire-and-forget POST hits the wire, so the backend never
|
|
9552
|
+
* learns the CLI went away and mobile keeps showing the session ONLINE until
|
|
9553
|
+
* the 30 s Redis heartbeat key expires — silently, since nothing publishes on
|
|
9554
|
+
* expiry (the 2026-08-18 "closing Claude Code leaves mobile online" report).
|
|
9555
|
+
*/
|
|
9545
9556
|
stop() {
|
|
9557
|
+
void this.stopAndFlush();
|
|
9558
|
+
}
|
|
9559
|
+
/**
|
|
9560
|
+
* Same teardown as {@link stop}, but resolves only once the `online:false`
|
|
9561
|
+
* heartbeat POST has settled — so a caller can `await` it before exiting.
|
|
9562
|
+
* Never rejects (`sendHeartbeat` swallows its own transport errors).
|
|
9563
|
+
*/
|
|
9564
|
+
async stopAndFlush() {
|
|
9546
9565
|
if (!this._running) return;
|
|
9547
9566
|
this._running = false;
|
|
9548
9567
|
this.cleanup();
|
|
9549
|
-
this.sendHeartbeat(false)
|
|
9550
|
-
});
|
|
9568
|
+
await this.sendHeartbeat(false);
|
|
9551
9569
|
}
|
|
9552
9570
|
async sendResult(commandId, status2, result) {
|
|
9553
9571
|
if (this.pairingInvalid) return;
|
|
@@ -9824,7 +9842,7 @@ var CommandRelayService = class _CommandRelayService {
|
|
|
9824
9842
|
// fresh + clear the "CLI update available" banner after a self-update
|
|
9825
9843
|
// (a codespace that reinstalls @latest reconnects via heartbeat, not
|
|
9826
9844
|
// pair/reconnect). Older backends ignore the extra field.
|
|
9827
|
-
..."2.65.
|
|
9845
|
+
..."2.65.12" ? { ideVersion: "2.65.12" } : {}
|
|
9828
9846
|
}).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
|
|
9829
9847
|
}
|
|
9830
9848
|
/**
|
|
@@ -9905,6 +9923,21 @@ var CommandRelayService = class _CommandRelayService {
|
|
|
9905
9923
|
}
|
|
9906
9924
|
}
|
|
9907
9925
|
};
|
|
9926
|
+
var RELAY_GOODBYE_TIMEOUT_MS = 1500;
|
|
9927
|
+
async function stopRelayWithGoodbye(relay, timeoutMs = RELAY_GOODBYE_TIMEOUT_MS) {
|
|
9928
|
+
let timer;
|
|
9929
|
+
try {
|
|
9930
|
+
await Promise.race([
|
|
9931
|
+
relay.stopAndFlush(),
|
|
9932
|
+
new Promise((resolve10) => {
|
|
9933
|
+
timer = setTimeout(resolve10, timeoutMs);
|
|
9934
|
+
})
|
|
9935
|
+
]);
|
|
9936
|
+
} catch {
|
|
9937
|
+
} finally {
|
|
9938
|
+
if (timer) clearTimeout(timer);
|
|
9939
|
+
}
|
|
9940
|
+
}
|
|
9908
9941
|
|
|
9909
9942
|
// src/services/file-watcher.service.ts
|
|
9910
9943
|
var import_child_process4 = require("child_process");
|
|
@@ -15250,6 +15283,7 @@ function parseHistoryFile(filePath) {
|
|
|
15250
15283
|
if (!msg) continue;
|
|
15251
15284
|
const text = extractText(msg["content"]).trim();
|
|
15252
15285
|
if (!text) continue;
|
|
15286
|
+
if (type === "user" && isLocalCommandEcho(text)) continue;
|
|
15253
15287
|
const ts = r["timestamp"];
|
|
15254
15288
|
const timestamp = typeof ts === "string" ? ts : typeof ts === "number" ? new Date(ts).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
15255
15289
|
const uuid = typeof r["uuid"] === "string" ? r["uuid"] : `${Date.now()}-${Math.random()}`;
|
|
@@ -15314,6 +15348,175 @@ function listResumableSessions(cwd) {
|
|
|
15314
15348
|
out2.sort((a, b) => b.timestamp - a.timestamp);
|
|
15315
15349
|
return out2;
|
|
15316
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
|
+
}
|
|
15317
15520
|
|
|
15318
15521
|
// src/agents/claude/runtime.ts
|
|
15319
15522
|
var ClaudeRuntimeStrategy = class {
|
|
@@ -15403,6 +15606,14 @@ var ClaudeRuntimeStrategy = class {
|
|
|
15403
15606
|
parseHistoryFile(filePath) {
|
|
15404
15607
|
return parseHistoryFile(filePath);
|
|
15405
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
|
+
}
|
|
15406
15617
|
getCurrentUsage(historyDir) {
|
|
15407
15618
|
return getCurrentUsage(historyDir);
|
|
15408
15619
|
}
|
|
@@ -18104,25 +18315,27 @@ async function waitForCommandOnPath(cmd, opts = {}) {
|
|
|
18104
18315
|
}
|
|
18105
18316
|
return check();
|
|
18106
18317
|
}
|
|
18107
|
-
function augmentUserLocalBinPaths() {
|
|
18108
|
-
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;
|
|
18109
18322
|
const candidates = [
|
|
18110
18323
|
// XDG-style per-user bin — npm's default global-prefix bin dir on most
|
|
18111
18324
|
// Linux setups (`npm config set prefix ~/.local` or an nvm-less
|
|
18112
18325
|
// per-user npm), and where curl-based agent installers commonly land.
|
|
18113
|
-
|
|
18326
|
+
p2.join(home, ".local", "bin"),
|
|
18114
18327
|
// Common explicit npm global-prefix conventions seen in the wild
|
|
18115
18328
|
// (`npm config set prefix ~/.npm-global`, and Debian/Fedora's
|
|
18116
18329
|
// `~/.local/share/npm` layout for `npm config set prefix
|
|
18117
18330
|
// ~/.local/share/npm`).
|
|
18118
|
-
|
|
18119
|
-
|
|
18331
|
+
p2.join(home, ".npm-global", "bin"),
|
|
18332
|
+
p2.join(home, ".local", "share", "npm", "bin")
|
|
18120
18333
|
];
|
|
18121
|
-
const parts = (
|
|
18334
|
+
const parts = (env.PATH ?? "").split(p2.delimiter).filter((s) => s.length > 0);
|
|
18122
18335
|
const existing = new Set(parts);
|
|
18123
18336
|
const additions = candidates.filter((dir) => !existing.has(dir));
|
|
18124
18337
|
if (additions.length === 0) return;
|
|
18125
|
-
|
|
18338
|
+
env.PATH = [...additions, ...parts].join(p2.delimiter);
|
|
18126
18339
|
}
|
|
18127
18340
|
function resolveCursorAgentBinary(deps = {}) {
|
|
18128
18341
|
const existsSync29 = deps.existsSync ?? import_fs.default.existsSync;
|
|
@@ -21753,7 +21966,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
21753
21966
|
if (process.env.NODE_ENV === "test") return;
|
|
21754
21967
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
21755
21968
|
if (process.env.CI) return;
|
|
21756
|
-
const current2 = true ? "2.65.
|
|
21969
|
+
const current2 = true ? "2.65.12" : null;
|
|
21757
21970
|
if (!current2) return;
|
|
21758
21971
|
const cache = readCache();
|
|
21759
21972
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -21770,7 +21983,7 @@ function checkForUpdates() {
|
|
|
21770
21983
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
21771
21984
|
if (process.env.CI) return;
|
|
21772
21985
|
if (!process.stdout.isTTY) return;
|
|
21773
|
-
const current2 = true ? "2.65.
|
|
21986
|
+
const current2 = true ? "2.65.12" : null;
|
|
21774
21987
|
if (!current2) return;
|
|
21775
21988
|
const cache = readCache();
|
|
21776
21989
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -21790,7 +22003,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
|
|
|
21790
22003
|
var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
|
|
21791
22004
|
var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
|
|
21792
22005
|
function currentCliVersion() {
|
|
21793
|
-
return true ? "2.65.
|
|
22006
|
+
return true ? "2.65.12" : null;
|
|
21794
22007
|
}
|
|
21795
22008
|
function runCmd(cmd, args2, timeoutMs) {
|
|
21796
22009
|
return new Promise((resolve10) => {
|
|
@@ -26585,7 +26798,7 @@ var sessionTerminated = async (ctx, cmd) => {
|
|
|
26585
26798
|
} catch {
|
|
26586
26799
|
}
|
|
26587
26800
|
ctx.outputSvc.dispose();
|
|
26588
|
-
ctx.relay
|
|
26801
|
+
await stopRelayWithGoodbye(ctx.relay);
|
|
26589
26802
|
process.exit(0);
|
|
26590
26803
|
};
|
|
26591
26804
|
var shutdownSession = async (ctx, cmd) => {
|
|
@@ -26614,7 +26827,7 @@ var shutdownSession = async (ctx, cmd) => {
|
|
|
26614
26827
|
} catch {
|
|
26615
26828
|
}
|
|
26616
26829
|
ctx.outputSvc.dispose();
|
|
26617
|
-
ctx.relay
|
|
26830
|
+
await stopRelayWithGoodbye(ctx.relay);
|
|
26618
26831
|
process.exit(0);
|
|
26619
26832
|
};
|
|
26620
26833
|
var showInstallCommand = async (ctx, cmd, parsed) => {
|
|
@@ -33869,6 +34082,7 @@ var _acpStartSeam = {
|
|
|
33869
34082
|
var PROTOCOL_VERSION3 = 1;
|
|
33870
34083
|
var FATAL_STARTUP_RE = /IneligibleTierError|UNSUPPORTED_CLIENT|no longer supported for Gemini Code Assist|not eligible for Gemini Code Assist|Error authenticating|ProjectIdRequiredError/i;
|
|
33871
34084
|
var NEWSESSION_TIMEOUT_MS = 12e4;
|
|
34085
|
+
var LOADSESSION_TIMEOUT_MS = 6e4;
|
|
33872
34086
|
var PROMPT_IDLE_TIMEOUT_MS = 9e4;
|
|
33873
34087
|
var PROMPT_ACTIVE_IDLE_TIMEOUT_MS = 6e5;
|
|
33874
34088
|
var CLIENT_CAPABILITIES = {
|
|
@@ -34383,13 +34597,30 @@ var AcpClient = class {
|
|
|
34383
34597
|
log.info("acpClient", `loadSession \u2192 sessionId=${sessionId.slice(0, 8)}`);
|
|
34384
34598
|
this.opts.beginLoadReplay?.();
|
|
34385
34599
|
let loaded;
|
|
34600
|
+
let loadTimer;
|
|
34386
34601
|
try {
|
|
34387
|
-
loaded = await
|
|
34388
|
-
|
|
34389
|
-
|
|
34390
|
-
|
|
34391
|
-
|
|
34602
|
+
loaded = await Promise.race([
|
|
34603
|
+
this.connection.loadSession({
|
|
34604
|
+
sessionId,
|
|
34605
|
+
cwd: this.opts.cwd,
|
|
34606
|
+
mcpServers: this.opts.mcpServers ?? []
|
|
34607
|
+
}),
|
|
34608
|
+
new Promise((_2, reject) => {
|
|
34609
|
+
loadTimer = setTimeout(() => {
|
|
34610
|
+
const tail = this.recentStderr.slice(-4).join(" | ");
|
|
34611
|
+
reject(
|
|
34612
|
+
new Error(
|
|
34613
|
+
`ACP_LOAD_SESSION_TIMEOUT: ${this.opts.adapter.requiresAgentBinary} did not answer session/load for ${sessionId.slice(
|
|
34614
|
+
0,
|
|
34615
|
+
8
|
|
34616
|
+
)} within ${Math.round(LOADSESSION_TIMEOUT_MS / 1e3)}s${tail ? ` \u2014 last output: ${tail}` : ""}`
|
|
34617
|
+
)
|
|
34618
|
+
);
|
|
34619
|
+
}, LOADSESSION_TIMEOUT_MS);
|
|
34620
|
+
})
|
|
34621
|
+
]);
|
|
34392
34622
|
} finally {
|
|
34623
|
+
if (loadTimer) clearTimeout(loadTimer);
|
|
34393
34624
|
this.opts.endLoadReplay?.();
|
|
34394
34625
|
}
|
|
34395
34626
|
this.sessionId = sessionId;
|
|
@@ -38676,7 +38907,7 @@ async function sessionShutdownH(ctx) {
|
|
|
38676
38907
|
} catch {
|
|
38677
38908
|
}
|
|
38678
38909
|
}
|
|
38679
|
-
relay
|
|
38910
|
+
await stopRelayWithGoodbye(relay);
|
|
38680
38911
|
closeAllTerminals();
|
|
38681
38912
|
await client3.stop();
|
|
38682
38913
|
process.exit(0);
|
|
@@ -39975,7 +40206,7 @@ async function runAcpSession(opts) {
|
|
|
39975
40206
|
const shutdown = async (signal) => {
|
|
39976
40207
|
showInfo(`Shutting down ACP session (${signal})\u2026`);
|
|
39977
40208
|
clearTimeout(prewarmTimer);
|
|
39978
|
-
relay
|
|
40209
|
+
await stopRelayWithGoodbye(relay);
|
|
39979
40210
|
void fileWatcher.stop();
|
|
39980
40211
|
turnFiles.stop();
|
|
39981
40212
|
closeAllTerminals();
|
|
@@ -40444,7 +40675,27 @@ var OutputService = class _OutputService {
|
|
|
40444
40675
|
...lines.slice(banner.endIdx + 1)
|
|
40445
40676
|
];
|
|
40446
40677
|
}
|
|
40678
|
+
/**
|
|
40679
|
+
* Mute/unmute the CHAT-output publish path. Every frame this service would
|
|
40680
|
+
* emit (clear / user_message / new_turn / text / chrome_steps / selectors /
|
|
40681
|
+
* banner / input_suggestion) is dropped while muted; `push()` and the tick
|
|
40682
|
+
* still run, so the detection side-effects (session id, rate limit,
|
|
40683
|
+
* terminal-turn gate) are untouched.
|
|
40684
|
+
*
|
|
40685
|
+
* ⚠️ The one caller is the baton's {@link NativeTuiDriver}: while LOCAL_DRIVE
|
|
40686
|
+
* holds the baton, the mobile view is the read-only TRANSCRIPT mirror, never
|
|
40687
|
+
* a screen-scrape — piping the native TUI's PTY bytes through here published
|
|
40688
|
+
* raw Claude Code chrome (box-drawing rules, `❯`, "auto mode on (shift+tab to
|
|
40689
|
+
* cycle) · esc to interrupt") into the chat as if it were agent output.
|
|
40690
|
+
* Terminal-panel frames (`sendTerminalChunk`/`sendTerminalExit`) go straight
|
|
40691
|
+
* to the emitter and are deliberately NOT affected.
|
|
40692
|
+
*/
|
|
40693
|
+
setPublishSuppressed(suppressed) {
|
|
40694
|
+
this.publishSuppressed = suppressed;
|
|
40695
|
+
}
|
|
40696
|
+
publishSuppressed = false;
|
|
40447
40697
|
async send(body, opts = {}) {
|
|
40698
|
+
if (this.publishSuppressed) return;
|
|
40448
40699
|
const outcome = await this.emitter.send(body, opts);
|
|
40449
40700
|
if (outcome.dead && this.pty.isActive) {
|
|
40450
40701
|
this.dispose();
|
|
@@ -41135,11 +41386,18 @@ function provisionSkillsForStart(home = import_node_os13.default.homedir()) {
|
|
|
41135
41386
|
}
|
|
41136
41387
|
|
|
41137
41388
|
// src/baton/baton-controller.ts
|
|
41138
|
-
var BatonController = class {
|
|
41389
|
+
var BatonController = class _BatonController {
|
|
41139
41390
|
constructor(deps) {
|
|
41140
41391
|
this.deps = deps;
|
|
41141
41392
|
}
|
|
41142
41393
|
deps;
|
|
41394
|
+
/**
|
|
41395
|
+
* Default upper bound on a whole hand-off. A cold `newSession` on claude is
|
|
41396
|
+
* ~20 s, so 45 s leaves generous headroom for a healthy switch while still
|
|
41397
|
+
* failing FAST enough that the user gets an honest error instead of a
|
|
41398
|
+
* permanent "Switching…".
|
|
41399
|
+
*/
|
|
41400
|
+
static DEFAULT_SWITCH_TIMEOUT_MS = 45e3;
|
|
41143
41401
|
_state = "LOCAL_DRIVE";
|
|
41144
41402
|
_active = "local_tui";
|
|
41145
41403
|
_conversationId = null;
|
|
@@ -41190,6 +41448,21 @@ var BatonController = class {
|
|
|
41190
41448
|
this._conversationId = conversationId;
|
|
41191
41449
|
this.setState("LOCAL_DRIVE");
|
|
41192
41450
|
}
|
|
41451
|
+
/**
|
|
41452
|
+
* The native TUI SWITCHED conversation while driving (Claude `/clear` →
|
|
41453
|
+
* a new id, `/resume` → an existing one): the process and the pairing are
|
|
41454
|
+
* unchanged, but the conversation the user is in has another id. Re-point
|
|
41455
|
+
* the baton at it so the read-only mirror follows that transcript and a
|
|
41456
|
+
* later Take Control resumes THAT conversation instead of the abandoned one. Guarded to LOCAL_DRIVE — only
|
|
41457
|
+
* the terminal can switch its own conversation, and during a hand-off /
|
|
41458
|
+
* MOBILE_DRIVE the native watcher is torn down anyway (`stop()`). Same id →
|
|
41459
|
+
* no-op. Re-publishes LOCAL_DRIVE so the mirror re-arms + mobile learns the id.
|
|
41460
|
+
*/
|
|
41461
|
+
switchConversation(conversationId) {
|
|
41462
|
+
if (this._state !== "LOCAL_DRIVE" || this._conversationId === conversationId) return;
|
|
41463
|
+
this._conversationId = conversationId;
|
|
41464
|
+
this.setState("LOCAL_DRIVE");
|
|
41465
|
+
}
|
|
41193
41466
|
async takeControl() {
|
|
41194
41467
|
await this.switchDriver(
|
|
41195
41468
|
"LOCAL_DRIVE",
|
|
@@ -41216,24 +41489,79 @@ var BatonController = class {
|
|
|
41216
41489
|
this.setState("SWITCHING");
|
|
41217
41490
|
const priorActive = this._active;
|
|
41218
41491
|
const priorConversationId = this._conversationId;
|
|
41219
|
-
|
|
41492
|
+
const timeoutMs = this.deps.switchTimeoutMs ?? _BatonController.DEFAULT_SWITCH_TIMEOUT_MS;
|
|
41493
|
+
let stoppedCurrent = false;
|
|
41494
|
+
const handoff = (async () => {
|
|
41220
41495
|
await current2.whenSafeToYield();
|
|
41221
41496
|
await current2.stop();
|
|
41222
|
-
|
|
41497
|
+
stoppedCurrent = true;
|
|
41498
|
+
return next.start(priorConversationId ?? void 0);
|
|
41499
|
+
})();
|
|
41500
|
+
handoff.catch(() => void 0);
|
|
41501
|
+
try {
|
|
41502
|
+
const conversationId = await withDeadline(
|
|
41503
|
+
handoff,
|
|
41504
|
+
timeoutMs,
|
|
41505
|
+
`BATON_SWITCH_TIMEOUT: ${from} \u2192 ${to} hand-off did not complete within ${Math.round(
|
|
41506
|
+
timeoutMs / 1e3
|
|
41507
|
+
)}s`
|
|
41508
|
+
);
|
|
41509
|
+
this._conversationId = conversationId;
|
|
41223
41510
|
this._active = nextKind;
|
|
41224
41511
|
this.setState(to);
|
|
41225
41512
|
} catch (err) {
|
|
41513
|
+
await this.recoverFromFailedSwitch(
|
|
41514
|
+
current2,
|
|
41515
|
+
next,
|
|
41516
|
+
stoppedCurrent,
|
|
41517
|
+
priorConversationId,
|
|
41518
|
+
timeoutMs
|
|
41519
|
+
);
|
|
41226
41520
|
this._active = priorActive;
|
|
41227
|
-
this._conversationId = priorConversationId;
|
|
41228
41521
|
this.setState(from);
|
|
41229
41522
|
throw err;
|
|
41230
41523
|
}
|
|
41231
41524
|
}
|
|
41525
|
+
/**
|
|
41526
|
+
* Undo a half-done hand-off. The `next` driver may still be starting behind
|
|
41527
|
+
* the deadline — stop it so a second adapter/PTY can't race the revived one.
|
|
41528
|
+
* And when `current` was ALREADY stopped before the failure, reverting only
|
|
41529
|
+
* the STATE would leave the user staring at a dead terminal (or mobile at a
|
|
41530
|
+
* dead adapter), so bring it back on the same conversation. Both best-effort
|
|
41531
|
+
* and bounded: recovery must never itself hang the controller.
|
|
41532
|
+
*/
|
|
41533
|
+
async recoverFromFailedSwitch(current2, next, stoppedCurrent, priorConversationId, timeoutMs) {
|
|
41534
|
+
this._conversationId = priorConversationId;
|
|
41535
|
+
await withDeadline(next.stop(), timeoutMs, "BATON_RECOVERY_STOP_TIMEOUT").catch(
|
|
41536
|
+
() => void 0
|
|
41537
|
+
);
|
|
41538
|
+
if (!stoppedCurrent) return;
|
|
41539
|
+
try {
|
|
41540
|
+
const revived = await withDeadline(
|
|
41541
|
+
current2.start(priorConversationId ?? void 0),
|
|
41542
|
+
timeoutMs,
|
|
41543
|
+
"BATON_RECOVERY_START_TIMEOUT"
|
|
41544
|
+
);
|
|
41545
|
+
if (revived) this._conversationId = revived;
|
|
41546
|
+
} catch {
|
|
41547
|
+
}
|
|
41548
|
+
}
|
|
41232
41549
|
setState(state) {
|
|
41233
41550
|
this._state = state;
|
|
41234
41551
|
this.deps.publishState(state, this._active, this._conversationId);
|
|
41235
41552
|
}
|
|
41236
41553
|
};
|
|
41554
|
+
function withDeadline(promise, ms, message) {
|
|
41555
|
+
let timer;
|
|
41556
|
+
return Promise.race([
|
|
41557
|
+
promise,
|
|
41558
|
+
new Promise((_2, reject) => {
|
|
41559
|
+
timer = setTimeout(() => reject(new Error(message)), ms);
|
|
41560
|
+
})
|
|
41561
|
+
]).finally(() => {
|
|
41562
|
+
if (timer) clearTimeout(timer);
|
|
41563
|
+
});
|
|
41564
|
+
}
|
|
41237
41565
|
|
|
41238
41566
|
// src/baton/terminal.ts
|
|
41239
41567
|
var TUI_MODE_RESET = "\x1B[?1004l\x1B[?2004l\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?25h";
|
|
@@ -41271,6 +41599,7 @@ var NativeTuiDriver = class _NativeTuiDriver {
|
|
|
41271
41599
|
deps.opts.pluginAuthToken,
|
|
41272
41600
|
deps.runtime
|
|
41273
41601
|
);
|
|
41602
|
+
this.outputSvc.setPublishSuppressed(true);
|
|
41274
41603
|
this.keepAliveCtx = { inCodespace: false, codespaceName: void 0 };
|
|
41275
41604
|
this.setKeepAlive = buildKeepAlive(this.keepAliveCtx).apply;
|
|
41276
41605
|
}
|
|
@@ -41285,18 +41614,25 @@ var NativeTuiDriver = class _NativeTuiDriver {
|
|
|
41285
41614
|
static QUICK_DISCOVER_MS = 8e3;
|
|
41286
41615
|
outputSvc;
|
|
41287
41616
|
historySvc;
|
|
41617
|
+
/** Unsubscribe for the runtime's conversation-switch watcher (armed per
|
|
41618
|
+
* `start()`, torn down in `stop()`), or null when not armed. */
|
|
41619
|
+
unwatchSwitch = null;
|
|
41288
41620
|
setKeepAlive;
|
|
41289
41621
|
keepAliveCtx;
|
|
41290
41622
|
async start(resumeId) {
|
|
41291
41623
|
if (resumeId !== void 0) {
|
|
41292
41624
|
await this.deps.runtime.syncTranscriptForNativeResume?.(this.deps.opts.cwd, resumeId);
|
|
41293
41625
|
await this.agent.restart(resumeId, false);
|
|
41626
|
+
this.armSwitchWatch(resumeId);
|
|
41294
41627
|
return resumeId;
|
|
41295
41628
|
}
|
|
41296
41629
|
const spawnedAt = this.now();
|
|
41297
41630
|
await this.agent.spawn();
|
|
41298
41631
|
const preMinted = this.agent.spawnedSessionId;
|
|
41299
|
-
if (preMinted)
|
|
41632
|
+
if (preMinted) {
|
|
41633
|
+
this.armSwitchWatch(preMinted);
|
|
41634
|
+
return preMinted;
|
|
41635
|
+
}
|
|
41300
41636
|
const discover = this.deps.runtime.discoverSessionId;
|
|
41301
41637
|
if (!discover) {
|
|
41302
41638
|
throw new Error("NativeTuiDriver: agent did not expose a session id after spawn");
|
|
@@ -41312,7 +41648,26 @@ var NativeTuiDriver = class _NativeTuiDriver {
|
|
|
41312
41648
|
});
|
|
41313
41649
|
return null;
|
|
41314
41650
|
}
|
|
41651
|
+
/**
|
|
41652
|
+
* Follow the native TUI if it switches conversation mid-session (Claude
|
|
41653
|
+
* `/clear` / `/resume`): the runtime's event-driven watcher reports the id
|
|
41654
|
+
* now being driven, we point the PTY-side history at it and tell the
|
|
41655
|
+
* controller to rebind. Armed once per `start()` (fresh or resume) on the
|
|
41656
|
+
* id the TUI is driving; inert for agents without the hook.
|
|
41657
|
+
*/
|
|
41658
|
+
armSwitchWatch(currentId) {
|
|
41659
|
+
this.unwatchSwitch?.();
|
|
41660
|
+
this.unwatchSwitch = null;
|
|
41661
|
+
const watch3 = this.deps.runtime.watchConversationSwitch;
|
|
41662
|
+
if (!watch3) return;
|
|
41663
|
+
this.unwatchSwitch = watch3(this.deps.opts.cwd, { currentId }, (id) => {
|
|
41664
|
+
this.historySvc.setCurrentConversationId(id);
|
|
41665
|
+
this.deps.onConversationSwitch?.(id);
|
|
41666
|
+
});
|
|
41667
|
+
}
|
|
41315
41668
|
async stop() {
|
|
41669
|
+
this.unwatchSwitch?.();
|
|
41670
|
+
this.unwatchSwitch = null;
|
|
41316
41671
|
this.agent.kill();
|
|
41317
41672
|
parkTerminalForReadonly();
|
|
41318
41673
|
}
|
|
@@ -41375,10 +41730,17 @@ var AcpDriver = class {
|
|
|
41375
41730
|
session = null;
|
|
41376
41731
|
budgetReachedPosted = false;
|
|
41377
41732
|
async start(resumeId) {
|
|
41733
|
+
const resumable = resumeId !== void 0 && this.hasTranscript(resumeId);
|
|
41734
|
+
if (resumeId !== void 0 && !resumable) {
|
|
41735
|
+
log.info(
|
|
41736
|
+
"batonAcp",
|
|
41737
|
+
`resume id ${resumeId.slice(0, 8)} has no transcript on disk (zero turns yet) \u2014 starting a fresh ACP session instead of session/load`
|
|
41738
|
+
);
|
|
41739
|
+
}
|
|
41378
41740
|
let started;
|
|
41379
41741
|
try {
|
|
41380
41742
|
started = await this.deps.client.start();
|
|
41381
|
-
if (resumeId !== void 0) {
|
|
41743
|
+
if (resumable && resumeId !== void 0) {
|
|
41382
41744
|
await this.deps.runtime.syncTranscriptForAcpResume?.(this.deps.opts.cwd, resumeId);
|
|
41383
41745
|
this.deps.streaming.beginLoadReplay();
|
|
41384
41746
|
try {
|
|
@@ -41391,13 +41753,30 @@ var AcpDriver = class {
|
|
|
41391
41753
|
await this.deps.client.stop().catch(() => void 0);
|
|
41392
41754
|
throw err;
|
|
41393
41755
|
}
|
|
41394
|
-
const conversationId = resumeId !== void 0 ? resumeId : started.sessionId;
|
|
41756
|
+
const conversationId = resumable && resumeId !== void 0 ? resumeId : started.sessionId;
|
|
41395
41757
|
this.acpSessionId = conversationId;
|
|
41396
41758
|
this.agentCaps = started.initialize.agentCapabilities;
|
|
41397
41759
|
this.session = null;
|
|
41398
41760
|
this.budgetReachedPosted = false;
|
|
41399
41761
|
return conversationId;
|
|
41400
41762
|
}
|
|
41763
|
+
/**
|
|
41764
|
+
* Does the agent's own on-disk transcript for `conversationId` exist yet?
|
|
41765
|
+
* `resolveHistoryFile` returns null when the file isn't there (every baton
|
|
41766
|
+
* runtime `existsSync`-checks), which is exactly "the native TUI has run zero
|
|
41767
|
+
* turns on this id" — the only case where `session/load` has nothing to load.
|
|
41768
|
+
* A runtime without the hook can't be a baton runtime (see `gate.ts`), but if
|
|
41769
|
+
* one ever is, assume resumable so behaviour is unchanged.
|
|
41770
|
+
*/
|
|
41771
|
+
hasTranscript(conversationId) {
|
|
41772
|
+
const resolve10 = this.deps.runtime.resolveHistoryFile;
|
|
41773
|
+
if (typeof resolve10 !== "function") return true;
|
|
41774
|
+
try {
|
|
41775
|
+
return resolve10.call(this.deps.runtime, this.deps.opts.cwd, conversationId) !== null;
|
|
41776
|
+
} catch {
|
|
41777
|
+
return true;
|
|
41778
|
+
}
|
|
41779
|
+
}
|
|
41401
41780
|
async stop() {
|
|
41402
41781
|
await this.deps.client.stop();
|
|
41403
41782
|
this.acpSessionId = null;
|
|
@@ -41535,8 +41914,8 @@ var TranscriptMirror = class {
|
|
|
41535
41914
|
if (!file) return false;
|
|
41536
41915
|
this.attached = true;
|
|
41537
41916
|
this.emit(file);
|
|
41538
|
-
const
|
|
41539
|
-
this.unwatch =
|
|
41917
|
+
const watch3 = this.deps.watch ?? defaultWatch;
|
|
41918
|
+
this.unwatch = watch3(file, () => this.emit(file));
|
|
41540
41919
|
return true;
|
|
41541
41920
|
}
|
|
41542
41921
|
clearPoll() {
|
|
@@ -41732,8 +42111,7 @@ async function runBatonSession(opts) {
|
|
|
41732
42111
|
nativeDriver.handlePtyData(raw);
|
|
41733
42112
|
},
|
|
41734
42113
|
onExit(code) {
|
|
41735
|
-
teardown();
|
|
41736
|
-
process.exit(code);
|
|
42114
|
+
void teardown().finally(() => process.exit(code));
|
|
41737
42115
|
}
|
|
41738
42116
|
});
|
|
41739
42117
|
nativeDriver = new NativeTuiDriver({
|
|
@@ -41752,10 +42130,17 @@ async function runBatonSession(opts) {
|
|
|
41752
42130
|
// calls this once the user's first terminal turn creates the transcript.
|
|
41753
42131
|
// Safe forward-ref — `controller` is initialised long before `begin()` runs,
|
|
41754
42132
|
// and onLateBind only fires from inside `begin()`'s spawn.
|
|
41755
|
-
onLateBind: (id) => controller.rebindConversation(id)
|
|
42133
|
+
onLateBind: (id) => controller.rebindConversation(id),
|
|
42134
|
+
// The native TUI switched conversation mid-drive (Claude `/clear` mints a
|
|
42135
|
+
// new id + JSONL; `/resume` re-opens an existing one): rebind so the mirror
|
|
42136
|
+
// follows that transcript and a later Take Control resumes THAT
|
|
42137
|
+
// conversation. Owner report 2026-08-18: after `/clear` (+`/rename`) the
|
|
42138
|
+
// mobile went silent — the mirror kept tailing the abandoned file.
|
|
42139
|
+
onConversationSwitch: (id) => controller.switchConversation(id)
|
|
41756
42140
|
});
|
|
41757
42141
|
let mirror = null;
|
|
41758
42142
|
let firstLocalDrive = true;
|
|
42143
|
+
let lastPublished = null;
|
|
41759
42144
|
const startMirror = (conversationId, fresh) => {
|
|
41760
42145
|
mirror?.stop();
|
|
41761
42146
|
mirror = new TranscriptMirror({
|
|
@@ -41788,11 +42173,13 @@ async function runBatonSession(opts) {
|
|
|
41788
42173
|
publishState: (state, driver, conversationId) => {
|
|
41789
42174
|
publishBatonState(state, driver, conversationId);
|
|
41790
42175
|
if (state === "LOCAL_DRIVE" && conversationId) {
|
|
41791
|
-
|
|
42176
|
+
const switched = lastPublished === "LOCAL_DRIVE";
|
|
42177
|
+
startMirror(conversationId, firstLocalDrive || switched);
|
|
41792
42178
|
firstLocalDrive = false;
|
|
41793
42179
|
} else if (state !== "LOCAL_DRIVE") {
|
|
41794
42180
|
mirror?.stop();
|
|
41795
42181
|
}
|
|
42182
|
+
lastPublished = state;
|
|
41796
42183
|
}
|
|
41797
42184
|
});
|
|
41798
42185
|
const dispatchActive = (cmd) => controller.activeSessionDriver.dispatch(cmd);
|
|
@@ -41813,19 +42200,18 @@ async function runBatonSession(opts) {
|
|
|
41813
42200
|
publish: publishBatonState
|
|
41814
42201
|
})
|
|
41815
42202
|
);
|
|
41816
|
-
function teardown() {
|
|
42203
|
+
async function teardown() {
|
|
41817
42204
|
if (torn) return;
|
|
41818
42205
|
torn = true;
|
|
41819
42206
|
process.removeListener("SIGINT", onSignal);
|
|
41820
42207
|
process.removeListener("SIGTERM", onSignal);
|
|
41821
42208
|
process.removeListener("SIGHUP", onSignal);
|
|
41822
42209
|
mirror?.stop();
|
|
41823
|
-
relay.stop();
|
|
41824
42210
|
void controller.shutdown();
|
|
42211
|
+
await stopRelayWithGoodbye(relay);
|
|
41825
42212
|
}
|
|
41826
42213
|
const onSignal = () => {
|
|
41827
|
-
teardown();
|
|
41828
|
-
process.exit(0);
|
|
42214
|
+
void teardown().finally(() => process.exit(0));
|
|
41829
42215
|
};
|
|
41830
42216
|
process.once("SIGINT", onSignal);
|
|
41831
42217
|
process.once("SIGTERM", onSignal);
|
|
@@ -42212,12 +42598,12 @@ async function start(requestedAgent, presetSession) {
|
|
|
42212
42598
|
outputSvc.push(raw);
|
|
42213
42599
|
streamingEmitter?.push(raw);
|
|
42214
42600
|
},
|
|
42215
|
-
onExit(code) {
|
|
42601
|
+
async onExit(code) {
|
|
42216
42602
|
process.removeListener("SIGINT", sigintHandler);
|
|
42217
42603
|
process.removeListener("SIGTERM", sigintHandler);
|
|
42218
42604
|
process.removeListener("SIGHUP", sigintHandler);
|
|
42219
42605
|
outputSvc.dispose();
|
|
42220
|
-
relay
|
|
42606
|
+
await stopRelayWithGoodbye(relay);
|
|
42221
42607
|
void fileWatcher?.stop();
|
|
42222
42608
|
turnFiles?.stop();
|
|
42223
42609
|
void beads?.watcher.stop();
|
|
@@ -42272,7 +42658,7 @@ async function start(requestedAgent, presetSession) {
|
|
|
42272
42658
|
shuttingDown = true;
|
|
42273
42659
|
agent.kill();
|
|
42274
42660
|
outputSvc.dispose();
|
|
42275
|
-
relay
|
|
42661
|
+
await stopRelayWithGoodbye(relay);
|
|
42276
42662
|
void fileWatcher?.stop();
|
|
42277
42663
|
void beads?.watcher.stop();
|
|
42278
42664
|
void streamingEmitter?.stop();
|
|
@@ -44868,7 +45254,7 @@ function checkChokidar() {
|
|
|
44868
45254
|
}
|
|
44869
45255
|
async function doctor(args2 = []) {
|
|
44870
45256
|
const json = args2.includes("--json");
|
|
44871
|
-
const cliVersion = true ? "2.65.
|
|
45257
|
+
const cliVersion = true ? "2.65.12" : "0.0.0-dev";
|
|
44872
45258
|
const apiBase2 = resolveApiBaseUrl();
|
|
44873
45259
|
const diagnosticId = (0, import_node_crypto13.randomUUID)();
|
|
44874
45260
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -45259,7 +45645,7 @@ async function mcpRun(args2) {
|
|
|
45259
45645
|
// src/commands/version.ts
|
|
45260
45646
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
45261
45647
|
function version2() {
|
|
45262
|
-
const v = true ? "2.65.
|
|
45648
|
+
const v = true ? "2.65.12" : "unknown";
|
|
45263
45649
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
45264
45650
|
}
|
|
45265
45651
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.65.
|
|
3
|
+
"version": "2.65.12",
|
|
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",
|