codeam-cli 2.23.3 → 2.23.5
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 +12 -0
- package/dist/index.js +94 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ 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.23.4] — 2026-05-25
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **cli+workflow:** Unblock prompt flow + restore PostHog telemetry (#192)
|
|
12
|
+
|
|
13
|
+
## [2.23.3] — 2026-05-25
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **cli:** Reap in-flight spawnAndCapture children on sigint/exit (#191)
|
|
18
|
+
|
|
7
19
|
## [2.23.2] — 2026-05-25
|
|
8
20
|
|
|
9
21
|
### Fixed
|
package/dist/index.js
CHANGED
|
@@ -441,7 +441,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
|
|
|
441
441
|
// package.json
|
|
442
442
|
var package_default = {
|
|
443
443
|
name: "codeam-cli",
|
|
444
|
-
version: "2.23.
|
|
444
|
+
version: "2.23.5",
|
|
445
445
|
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.",
|
|
446
446
|
type: "commonjs",
|
|
447
447
|
main: "dist/index.js",
|
|
@@ -5768,7 +5768,7 @@ function readAnonId() {
|
|
|
5768
5768
|
}
|
|
5769
5769
|
function superProperties() {
|
|
5770
5770
|
return {
|
|
5771
|
-
cliVersion: true ? "2.23.
|
|
5771
|
+
cliVersion: true ? "2.23.5" : "0.0.0-dev",
|
|
5772
5772
|
nodeVersion: process.version,
|
|
5773
5773
|
platform: process.platform,
|
|
5774
5774
|
arch: process.arch,
|
|
@@ -5781,7 +5781,7 @@ function initTelemetry() {
|
|
|
5781
5781
|
log.trace("telemetry", "opted out (CODEAM_TELEMETRY / CI / NODE_ENV=test)");
|
|
5782
5782
|
return false;
|
|
5783
5783
|
}
|
|
5784
|
-
const apiKey = true ? "" : "";
|
|
5784
|
+
const apiKey = true ? "phc_Otx6LUf0KeabkbTnPuLPLg6d6r8319k8bKDYqoXyeb4" : "";
|
|
5785
5785
|
if (!apiKey) {
|
|
5786
5786
|
log.trace("telemetry", "no PostHog API key baked into build \u2014 disabled");
|
|
5787
5787
|
return false;
|
|
@@ -5860,6 +5860,8 @@ function maybePrintFirstRunBanner() {
|
|
|
5860
5860
|
|
|
5861
5861
|
// src/services/command-relay.service.ts
|
|
5862
5862
|
var API_BASE2 = resolveApiBaseUrl();
|
|
5863
|
+
var SSE_LIVENESS_TIMEOUT_MS = 45e3;
|
|
5864
|
+
var SSE_WATCHDOG_INTERVAL_MS = 1e4;
|
|
5863
5865
|
var CommandRelayService = class {
|
|
5864
5866
|
constructor(pluginId, onCommand, agentMeta) {
|
|
5865
5867
|
this.pluginId = pluginId;
|
|
@@ -5889,10 +5891,25 @@ var CommandRelayService = class {
|
|
|
5889
5891
|
/** Reconnect backoff state for the SSE stream. */
|
|
5890
5892
|
sseFailures = 0;
|
|
5891
5893
|
sseReconnectTimer = null;
|
|
5894
|
+
/**
|
|
5895
|
+
* Liveness watchdog for the SSE socket. The server pings every 30 s
|
|
5896
|
+
* (`pending-stream.controller.ts` interval(30_000)). If we go more
|
|
5897
|
+
* than `SSE_LIVENESS_TIMEOUT_MS` without ANY bytes, we assume the
|
|
5898
|
+
* TCP peer died half-open (Cloud Run scaled / redeployed mid-stream,
|
|
5899
|
+
* NAT dropped the conntrack entry, laptop slept) and force a
|
|
5900
|
+
* reconnect. Without this the kernel can keep the socket in a
|
|
5901
|
+
* zombie ESTABLISHED state for HOURS before the default TCP
|
|
5902
|
+
* keepalive triggers — observed live in #190 where a 17:18 redeploy
|
|
5903
|
+
* left CLIs subscribed for >30 minutes without ever receiving a
|
|
5904
|
+
* pushed command.
|
|
5905
|
+
*/
|
|
5906
|
+
sseWatchdog = null;
|
|
5907
|
+
sseLastByteAt = 0;
|
|
5892
5908
|
start() {
|
|
5893
5909
|
this.cleanup();
|
|
5894
5910
|
this._running = true;
|
|
5895
5911
|
this.agentsRegistered = false;
|
|
5912
|
+
log.info("relay", `start pluginId=${this.pluginId.slice(0, 8)} agent=${this.agentMeta.id}`);
|
|
5896
5913
|
this.sendHeartbeat(true);
|
|
5897
5914
|
this.heartbeatTimer = setInterval(() => this.sendHeartbeat(true), 2e4);
|
|
5898
5915
|
this.agentsTimer = setInterval(() => {
|
|
@@ -5900,6 +5917,7 @@ var CommandRelayService = class {
|
|
|
5900
5917
|
}, 5e3);
|
|
5901
5918
|
this.reportAgents();
|
|
5902
5919
|
if (process.env.NODE_ENV === "test" || process.env.CODEAM_DISABLE_SSE_PULL === "1") {
|
|
5920
|
+
log.info("relay", "SSE disabled \u2014 using polling fallback");
|
|
5903
5921
|
this.startPollingFallback();
|
|
5904
5922
|
} else {
|
|
5905
5923
|
this.connectSSE();
|
|
@@ -5921,7 +5939,7 @@ var CommandRelayService = class {
|
|
|
5921
5939
|
const url = new URL(`${API_BASE2}/api/commands/pending/stream`);
|
|
5922
5940
|
url.searchParams.set("pluginId", this.pluginId);
|
|
5923
5941
|
const transport = url.protocol === "https:" ? https2 : http2;
|
|
5924
|
-
log.
|
|
5942
|
+
log.info("relay", `sse connect pluginId=${this.pluginId.slice(0, 8)}`);
|
|
5925
5943
|
const req = transport.request(
|
|
5926
5944
|
{
|
|
5927
5945
|
hostname: url.hostname,
|
|
@@ -5933,11 +5951,11 @@ var CommandRelayService = class {
|
|
|
5933
5951
|
},
|
|
5934
5952
|
(res) => {
|
|
5935
5953
|
if (res.statusCode !== 200) {
|
|
5936
|
-
log.
|
|
5954
|
+
log.info("relay", `sse status=${res.statusCode} \u2014 backing off`);
|
|
5937
5955
|
res.resume();
|
|
5938
5956
|
this.sseFailures += 1;
|
|
5939
5957
|
if (this.sseFailures >= 2) {
|
|
5940
|
-
log.
|
|
5958
|
+
log.info("relay", "sse unavailable \u2014 falling back to polling");
|
|
5941
5959
|
capture("sse_fallback_to_poll", {
|
|
5942
5960
|
pluginId: this.pluginId,
|
|
5943
5961
|
agentId: this.agentMeta.id,
|
|
@@ -5950,10 +5968,13 @@ var CommandRelayService = class {
|
|
|
5950
5968
|
this.scheduleSseReconnect();
|
|
5951
5969
|
return;
|
|
5952
5970
|
}
|
|
5971
|
+
log.info("relay", "sse connected");
|
|
5953
5972
|
this.sseFailures = 0;
|
|
5973
|
+
this.armSseWatchdog();
|
|
5954
5974
|
let buffer = "";
|
|
5955
5975
|
res.setEncoding("utf8");
|
|
5956
5976
|
res.on("data", (chunk) => {
|
|
5977
|
+
this.sseLastByteAt = Date.now();
|
|
5957
5978
|
buffer += chunk;
|
|
5958
5979
|
let frameEnd;
|
|
5959
5980
|
while ((frameEnd = buffer.indexOf("\n\n")) !== -1) {
|
|
@@ -5963,15 +5984,26 @@ var CommandRelayService = class {
|
|
|
5963
5984
|
}
|
|
5964
5985
|
});
|
|
5965
5986
|
res.on("end", () => {
|
|
5987
|
+
log.info("relay", "sse end \u2014 reconnecting");
|
|
5988
|
+
this.disarmSseWatchdog();
|
|
5966
5989
|
if (this._running) this.scheduleSseReconnect();
|
|
5967
5990
|
});
|
|
5968
|
-
res.on("error", () => {
|
|
5991
|
+
res.on("error", (err) => {
|
|
5992
|
+
log.info("relay", `sse res error \u2014 reconnecting (${err.message})`);
|
|
5993
|
+
this.disarmSseWatchdog();
|
|
5969
5994
|
if (this._running) this.scheduleSseReconnect();
|
|
5970
5995
|
});
|
|
5971
5996
|
}
|
|
5972
5997
|
);
|
|
5998
|
+
req.on("socket", (socket) => {
|
|
5999
|
+
try {
|
|
6000
|
+
socket.setKeepAlive(true, 3e4);
|
|
6001
|
+
} catch {
|
|
6002
|
+
}
|
|
6003
|
+
});
|
|
5973
6004
|
req.on("error", (err) => {
|
|
5974
|
-
log.
|
|
6005
|
+
log.info("relay", `sse req error \u2014 ${err.message}`);
|
|
6006
|
+
this.disarmSseWatchdog();
|
|
5975
6007
|
this.sseFailures += 1;
|
|
5976
6008
|
if (this.sseFailures >= 2) {
|
|
5977
6009
|
capture("sse_fallback_to_poll", {
|
|
@@ -5986,11 +6018,43 @@ var CommandRelayService = class {
|
|
|
5986
6018
|
this.scheduleSseReconnect();
|
|
5987
6019
|
});
|
|
5988
6020
|
req.on("timeout", () => {
|
|
6021
|
+
log.info("relay", "sse req timeout \u2014 destroying + reconnecting");
|
|
5989
6022
|
req.destroy();
|
|
5990
6023
|
});
|
|
5991
6024
|
req.end();
|
|
5992
6025
|
this.sseRequest = req;
|
|
5993
6026
|
}
|
|
6027
|
+
// ─── SSE liveness watchdog ───────────────────────────────────────
|
|
6028
|
+
//
|
|
6029
|
+
// The server pings every 30 s; if more than SSE_LIVENESS_TIMEOUT_MS
|
|
6030
|
+
// pass without a byte, we assume the TCP peer is dead-zombie and
|
|
6031
|
+
// force the request to destroy → triggers `error`/`end` → standard
|
|
6032
|
+
// reconnect path. Without this, observed in the field: half-open
|
|
6033
|
+
// socket sits ESTABLISHED for 30 + minutes (until macOS default
|
|
6034
|
+
// 2 h TCP keepalive kicks in) and the CLI silently misses every
|
|
6035
|
+
// pushed command.
|
|
6036
|
+
armSseWatchdog() {
|
|
6037
|
+
this.disarmSseWatchdog();
|
|
6038
|
+
this.sseLastByteAt = Date.now();
|
|
6039
|
+
this.sseWatchdog = setInterval(() => {
|
|
6040
|
+
const idle = Date.now() - this.sseLastByteAt;
|
|
6041
|
+
if (idle > SSE_LIVENESS_TIMEOUT_MS) {
|
|
6042
|
+
log.info("relay", `sse watchdog \u2014 ${idle}ms since last byte, forcing reconnect`);
|
|
6043
|
+
try {
|
|
6044
|
+
this.sseRequest?.destroy();
|
|
6045
|
+
} catch {
|
|
6046
|
+
}
|
|
6047
|
+
this.disarmSseWatchdog();
|
|
6048
|
+
}
|
|
6049
|
+
}, SSE_WATCHDOG_INTERVAL_MS);
|
|
6050
|
+
this.sseWatchdog.unref?.();
|
|
6051
|
+
}
|
|
6052
|
+
disarmSseWatchdog() {
|
|
6053
|
+
if (this.sseWatchdog) {
|
|
6054
|
+
clearInterval(this.sseWatchdog);
|
|
6055
|
+
this.sseWatchdog = null;
|
|
6056
|
+
}
|
|
6057
|
+
}
|
|
5994
6058
|
handleSseFrame(frame) {
|
|
5995
6059
|
let event = "message";
|
|
5996
6060
|
let data = "";
|
|
@@ -6003,10 +6067,13 @@ var CommandRelayService = class {
|
|
|
6003
6067
|
const parsed = JSON.parse(data);
|
|
6004
6068
|
const commands = parsed.commands ?? [];
|
|
6005
6069
|
if (commands.length === 0) return;
|
|
6006
|
-
log.
|
|
6070
|
+
log.info(
|
|
6071
|
+
"relay",
|
|
6072
|
+
`sse received ${commands.length} command(s) types=[${commands.map((c2) => c2.type).join(",")}]`
|
|
6073
|
+
);
|
|
6007
6074
|
void this.dispatchCommands(commands);
|
|
6008
6075
|
} catch (err) {
|
|
6009
|
-
log.
|
|
6076
|
+
log.info("relay", "sse parse error", err);
|
|
6010
6077
|
}
|
|
6011
6078
|
}
|
|
6012
6079
|
scheduleSseReconnect() {
|
|
@@ -6064,7 +6131,8 @@ var CommandRelayService = class {
|
|
|
6064
6131
|
await _postJson(`${API_BASE2}/api/plugin/heartbeat`, {
|
|
6065
6132
|
pluginId: this.pluginId,
|
|
6066
6133
|
online,
|
|
6067
|
-
agentId: this.agentMeta.id
|
|
6134
|
+
agentId: this.agentMeta.id,
|
|
6135
|
+
branch: detectCurrentBranch()
|
|
6068
6136
|
}).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
|
|
6069
6137
|
}
|
|
6070
6138
|
reportAgents() {
|
|
@@ -6101,6 +6169,7 @@ var CommandRelayService = class {
|
|
|
6101
6169
|
clearTimeout(this.sseReconnectTimer);
|
|
6102
6170
|
this.sseReconnectTimer = null;
|
|
6103
6171
|
}
|
|
6172
|
+
this.disarmSseWatchdog();
|
|
6104
6173
|
if (this.sseRequest) {
|
|
6105
6174
|
try {
|
|
6106
6175
|
this.sseRequest.destroy();
|
|
@@ -12601,11 +12670,17 @@ var FileWatcherService = class {
|
|
|
12601
12670
|
} : {},
|
|
12602
12671
|
awaitWriteFinish: {
|
|
12603
12672
|
// Coalesces rapid sequential writes (npm install spam, build
|
|
12604
|
-
// tools emitting bursts).
|
|
12605
|
-
//
|
|
12606
|
-
//
|
|
12607
|
-
|
|
12608
|
-
|
|
12673
|
+
// tools emitting bursts). Tuned for monorepo workloads after
|
|
12674
|
+
// observing a CLI process spike to 126% CPU on the codeagent
|
|
12675
|
+
// monorepo: pollInterval=50ms caused thousands of fs.stat
|
|
12676
|
+
// calls per second when many files were being edited
|
|
12677
|
+
// concurrently (agent edits + AI summary subprocess writes +
|
|
12678
|
+
// git status outputs all touching files in the watched tree).
|
|
12679
|
+
// 200ms is still well below the typical user perception
|
|
12680
|
+
// threshold for "the file appeared in the Files screen" while
|
|
12681
|
+
// keeping the polling cost bounded.
|
|
12682
|
+
stabilityThreshold: 300,
|
|
12683
|
+
pollInterval: 200
|
|
12609
12684
|
}
|
|
12610
12685
|
});
|
|
12611
12686
|
watcher.on("add", (filePath) => this.schedule(filePath, "add"));
|
|
@@ -18326,7 +18401,7 @@ function checkChokidar() {
|
|
|
18326
18401
|
}
|
|
18327
18402
|
async function doctor(args2 = []) {
|
|
18328
18403
|
const json = args2.includes("--json");
|
|
18329
|
-
const cliVersion = true ? "2.23.
|
|
18404
|
+
const cliVersion = true ? "2.23.5" : "0.0.0-dev";
|
|
18330
18405
|
const apiBase = resolveApiBaseUrl();
|
|
18331
18406
|
const diagnosticId = (0, import_node_crypto5.randomUUID)();
|
|
18332
18407
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -18525,7 +18600,7 @@ async function completion(args2) {
|
|
|
18525
18600
|
// src/commands/version.ts
|
|
18526
18601
|
var import_picocolors13 = __toESM(require("picocolors"));
|
|
18527
18602
|
function version2() {
|
|
18528
|
-
const v = true ? "2.23.
|
|
18603
|
+
const v = true ? "2.23.5" : "unknown";
|
|
18529
18604
|
console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
|
|
18530
18605
|
}
|
|
18531
18606
|
|
|
@@ -18753,7 +18828,7 @@ function checkForUpdates() {
|
|
|
18753
18828
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
18754
18829
|
if (process.env.CI) return;
|
|
18755
18830
|
if (!process.stdout.isTTY) return;
|
|
18756
|
-
const current = true ? "2.23.
|
|
18831
|
+
const current = true ? "2.23.5" : null;
|
|
18757
18832
|
if (!current) return;
|
|
18758
18833
|
const cache = readCache();
|
|
18759
18834
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeam-cli",
|
|
3
|
-
"version": "2.23.
|
|
3
|
+
"version": "2.23.5",
|
|
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",
|