codeam-cli 2.61.85 → 2.61.87

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.js +289 -210
  3. 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.61.86] — 2026-08-07
8
+
9
+ ### Fixed
10
+
11
+ - **cli:** Per-tools/call watchdog on the httpUrl MCP relay (agent hang → clean tool error) (#592)
12
+
13
+ ## [2.61.85] — 2026-08-06
14
+
15
+ ### Fixed
16
+
17
+ - **cli:** Resume the prior conversation on a warm re-launch into an existing workspace (#591)
18
+
7
19
  ## [2.61.84] — 2026-08-05
8
20
 
9
21
  ### Fixed
package/dist/index.js CHANGED
@@ -89,6 +89,212 @@ var require_src = __commonJS({
89
89
  }
90
90
  });
91
91
 
92
+ // src/integrations/stdio-proxy.ts
93
+ var import_node_child_process31, import_node_readline3, RESTART_CHECK_INTERVAL_MS, SIGKILL_ESCALATION_MS, TOOL_CALL_TIMEOUT_MS, REPLAY_INIT_ID, RestartableStdioProxy;
94
+ var init_stdio_proxy = __esm({
95
+ "src/integrations/stdio-proxy.ts"() {
96
+ "use strict";
97
+ import_node_child_process31 = require("child_process");
98
+ import_node_readline3 = __toESM(require("readline"));
99
+ RESTART_CHECK_INTERVAL_MS = 3e4;
100
+ SIGKILL_ESCALATION_MS = 2e3;
101
+ TOOL_CALL_TIMEOUT_MS = (() => {
102
+ const raw = Number(process.env.CODEAM_MCP_TOOL_TIMEOUT_MS);
103
+ return Number.isFinite(raw) && raw > 0 ? raw : 12e4;
104
+ })();
105
+ REPLAY_INIT_ID = "__codeam_replay_init__";
106
+ RestartableStdioProxy = class {
107
+ constructor(opts) {
108
+ this.opts = opts;
109
+ }
110
+ opts;
111
+ child = null;
112
+ childRl = null;
113
+ initializeLine = null;
114
+ inflight = /* @__PURE__ */ new Set();
115
+ /** Per-`tools/call` request-id watchdog timers (see TOOL_CALL_TIMEOUT_MS). */
116
+ toolTimers = /* @__PURE__ */ new Map();
117
+ stdout = null;
118
+ swapping = false;
119
+ pendingClientLines = [];
120
+ ended = () => void 0;
121
+ async start() {
122
+ const stdin = this.opts.stdin ?? process.stdin;
123
+ const stdout = this.opts.stdout ?? process.stdout;
124
+ this.stdout = stdout;
125
+ await this.spawnChild(stdout);
126
+ const rl = import_node_readline3.default.createInterface({ input: stdin, crlfDelay: Infinity });
127
+ rl.on("line", (line) => this.onClientLine(line));
128
+ rl.on("close", () => this.child?.stdin?.end());
129
+ const timer = setInterval(() => this.checkRestart(stdout), RESTART_CHECK_INTERVAL_MS);
130
+ timer.unref();
131
+ return new Promise((resolve9) => {
132
+ this.ended = (code) => {
133
+ clearInterval(timer);
134
+ process.exitCode = code;
135
+ resolve9();
136
+ };
137
+ });
138
+ }
139
+ onClientLine(line) {
140
+ if (this.swapping) {
141
+ this.pendingClientLines.push(line);
142
+ return;
143
+ }
144
+ try {
145
+ const msg = JSON.parse(line);
146
+ if (msg.method === "initialize" && this.initializeLine === null) {
147
+ this.initializeLine = line;
148
+ }
149
+ if (msg.method === "notifications/cancelled") {
150
+ const requestId = msg.params?.requestId;
151
+ if (requestId !== void 0) {
152
+ this.inflight.delete(requestId);
153
+ this.clearToolTimeout(requestId);
154
+ }
155
+ }
156
+ if (msg.id !== void 0 && msg.method !== void 0) {
157
+ this.inflight.add(msg.id);
158
+ if (msg.method === "tools/call") this.armToolTimeout(msg.id);
159
+ }
160
+ } catch {
161
+ }
162
+ this.child?.stdin?.write(line + "\n");
163
+ }
164
+ onChildLine(line, stdout) {
165
+ try {
166
+ const msg = JSON.parse(line);
167
+ if (msg.id !== void 0 && msg.method === void 0) {
168
+ if (msg.id === REPLAY_INIT_ID) {
169
+ return;
170
+ }
171
+ this.inflight.delete(msg.id);
172
+ this.clearToolTimeout(msg.id);
173
+ }
174
+ } catch {
175
+ }
176
+ stdout.write(line + "\n");
177
+ if (this.inflight.size === 0) this.checkRestart(stdout);
178
+ }
179
+ /**
180
+ * Arm a per-`tools/call` watchdog. If the server never answers this request
181
+ * id within {@link TOOL_CALL_TIMEOUT_MS}, synthesize a JSON-RPC error response
182
+ * to the client so the agent's turn unblocks (clean tool error) instead of
183
+ * hanging forever with no Stop. Idempotent per id.
184
+ */
185
+ armToolTimeout(id) {
186
+ const existing = this.toolTimers.get(id);
187
+ if (existing) clearTimeout(existing);
188
+ const t2 = setTimeout(() => {
189
+ this.toolTimers.delete(id);
190
+ if (!this.inflight.has(id)) return;
191
+ this.inflight.delete(id);
192
+ process.stderr.write(
193
+ `[codeam mcp-run] tools/call id=${String(id)} timed out after ${TOOL_CALL_TIMEOUT_MS}ms \u2014 server did not respond; failing the call so the turn can proceed
194
+ `
195
+ );
196
+ const errResponse = {
197
+ jsonrpc: "2.0",
198
+ id,
199
+ error: {
200
+ code: -32001,
201
+ message: `MCP tool call timed out after ${Math.round(TOOL_CALL_TIMEOUT_MS / 1e3)}s \u2014 the server did not respond. The target service/deployment may be unreachable (e.g. a Convex dev deployment is only reachable while \`convex dev\` is running \u2014 use a production deploy key or start \`convex dev\`).`
202
+ }
203
+ };
204
+ (this.stdout ?? process.stdout).write(JSON.stringify(errResponse) + "\n");
205
+ }, TOOL_CALL_TIMEOUT_MS);
206
+ t2.unref();
207
+ this.toolTimers.set(id, t2);
208
+ }
209
+ clearToolTimeout(id) {
210
+ const t2 = this.toolTimers.get(id);
211
+ if (t2) {
212
+ clearTimeout(t2);
213
+ this.toolTimers.delete(id);
214
+ }
215
+ }
216
+ clearAllToolTimeouts() {
217
+ for (const t2 of this.toolTimers.values()) clearTimeout(t2);
218
+ this.toolTimers.clear();
219
+ }
220
+ /**
221
+ * Failsafe wrapper for the fire-and-forget call sites (post-response check
222
+ * + the 30 s timer): `maybeRestart` handles the token-fetch failure itself,
223
+ * but nothing that escapes it may become an unhandled rejection — that
224
+ * would kill the whole shim process.
225
+ */
226
+ checkRestart(stdout) {
227
+ this.maybeRestart(stdout).catch((err) => {
228
+ process.stderr.write(
229
+ `[codeam mcp-run] restart check failed (will retry): ${err instanceof Error ? err.message : String(err)}
230
+ `
231
+ );
232
+ });
233
+ }
234
+ async maybeRestart(stdout) {
235
+ if (this.swapping || !this.opts.shouldRestartNow()) return;
236
+ if (this.inflight.size > 0 || this.initializeLine === null || !this.child) return;
237
+ this.swapping = true;
238
+ let spec;
239
+ try {
240
+ spec = await this.opts.spawnSpec();
241
+ } catch (err) {
242
+ process.stderr.write(
243
+ `[codeam mcp-run] token refresh failed, keeping current server (will retry): ${err instanceof Error ? err.message : String(err)}
244
+ `
245
+ );
246
+ this.swapping = false;
247
+ for (const l of this.pendingClientLines.splice(0)) this.onClientLine(l);
248
+ return;
249
+ }
250
+ const old = this.child;
251
+ const oldRl = this.childRl;
252
+ this.child = null;
253
+ this.childRl = null;
254
+ oldRl?.close();
255
+ old.kill("SIGTERM");
256
+ const escalation = setTimeout(() => {
257
+ if (!old.killed || old.exitCode === null) old.kill("SIGKILL");
258
+ }, SIGKILL_ESCALATION_MS);
259
+ escalation.unref();
260
+ try {
261
+ await this.spawnChild(stdout, spec);
262
+ const replayed = JSON.parse(this.initializeLine);
263
+ this.child.stdin.write(JSON.stringify({ ...replayed, id: REPLAY_INIT_ID }) + "\n");
264
+ this.child.stdin.write(
265
+ JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n"
266
+ );
267
+ } finally {
268
+ this.swapping = false;
269
+ for (const l of this.pendingClientLines.splice(0)) this.onClientLine(l);
270
+ }
271
+ }
272
+ async spawnChild(stdout, preResolved) {
273
+ const spec = preResolved ?? await this.opts.spawnSpec();
274
+ const spawn44 = this.opts.spawnImpl ?? import_node_child_process31.spawn;
275
+ const child = spawn44(spec.command, spec.args, {
276
+ env: { ...process.env, ...spec.env },
277
+ // env only — never argv
278
+ stdio: ["pipe", "pipe", "inherit"]
279
+ });
280
+ this.child = child;
281
+ child.stdin?.on("error", () => void 0);
282
+ const rl = import_node_readline3.default.createInterface({ input: child.stdout, crlfDelay: Infinity });
283
+ this.childRl = rl;
284
+ rl.on("line", (line) => {
285
+ if (child !== this.child) return;
286
+ this.onChildLine(line, stdout);
287
+ });
288
+ child.on("exit", (code) => {
289
+ if (child !== this.child) return;
290
+ this.clearAllToolTimeouts();
291
+ this.ended(code ?? 1);
292
+ });
293
+ }
294
+ };
295
+ }
296
+ });
297
+
92
298
  // src/integrations/convex-admin-mcp.ts
93
299
  var convex_admin_mcp_exports = {};
94
300
  __export(convex_admin_mcp_exports, {
@@ -248,6 +454,64 @@ var init_convex_admin_mcp = __esm({
248
454
  }
249
455
  });
250
456
 
457
+ // src/integrations/mcp-tool-watchdog.ts
458
+ function createToolCallWatchdog(deps) {
459
+ const setTimer = deps.setTimer ?? ((fn, ms) => {
460
+ const t2 = setTimeout(fn, ms);
461
+ t2.unref?.();
462
+ return t2;
463
+ });
464
+ const clearTimer = deps.clearTimer ?? ((t2) => clearTimeout(t2));
465
+ const timers = /* @__PURE__ */ new Map();
466
+ const answered = /* @__PURE__ */ new Set();
467
+ const clear = (id) => {
468
+ const t2 = timers.get(id);
469
+ if (t2) {
470
+ clearTimer(t2);
471
+ timers.delete(id);
472
+ }
473
+ };
474
+ return {
475
+ onClientMessage(msg) {
476
+ const m = msg;
477
+ if (m.method !== "tools/call" || m.id === void 0) return;
478
+ const id = m.id;
479
+ clear(id);
480
+ const timer = setTimer(() => {
481
+ timers.delete(id);
482
+ answered.add(id);
483
+ deps.sendToAgent({
484
+ jsonrpc: "2.0",
485
+ id,
486
+ error: {
487
+ code: -32001,
488
+ message: `The ${deps.integrationId} tool did not respond within ${Math.round(
489
+ deps.timeoutMs / 1e3
490
+ )}s (remote MCP unresponsive). The request was aborted \u2014 try again or use a different tool.`
491
+ }
492
+ });
493
+ }, deps.timeoutMs);
494
+ timers.set(id, timer);
495
+ },
496
+ onServerMessage(msg) {
497
+ const m = msg;
498
+ if (m.id === void 0 || m.method !== void 0) return false;
499
+ clear(m.id);
500
+ return answered.delete(m.id);
501
+ },
502
+ dispose() {
503
+ for (const t2 of timers.values()) clearTimer(t2);
504
+ timers.clear();
505
+ answered.clear();
506
+ }
507
+ };
508
+ }
509
+ var init_mcp_tool_watchdog = __esm({
510
+ "src/integrations/mcp-tool-watchdog.ts"() {
511
+ "use strict";
512
+ }
513
+ });
514
+
251
515
  // src/integrations/http-relay.ts
252
516
  var http_relay_exports = {};
253
517
  __export(http_relay_exports, {
@@ -280,21 +544,29 @@ async function runHttpRelay(delivery, client3, id) {
280
544
  const stdioTransport = new StdioServerTransport();
281
545
  await new Promise((resolve9, reject) => {
282
546
  let done = false;
547
+ const watchdog = createToolCallWatchdog({
548
+ timeoutMs: TOOL_CALL_TIMEOUT_MS,
549
+ integrationId: id,
550
+ sendToAgent: (m) => void stdioTransport.send(m).catch(() => void 0)
551
+ });
283
552
  const finish = (err) => {
284
553
  if (done) return;
285
554
  done = true;
555
+ watchdog.dispose();
286
556
  void httpTransport.close().catch(() => void 0);
287
557
  void stdioTransport.close().catch(() => void 0);
288
558
  if (err) reject(err);
289
559
  else resolve9();
290
560
  };
291
561
  stdioTransport.onmessage = (msg) => {
562
+ watchdog.onClientMessage(msg);
292
563
  void httpTransport.send(msg).catch((e) => {
293
564
  process.stderr.write(`[mcp-run http] send\u2192remote failed: ${String(e)}
294
565
  `);
295
566
  });
296
567
  };
297
568
  httpTransport.onmessage = (msg) => {
569
+ if (watchdog.onServerMessage(msg)) return;
298
570
  void stdioTransport.send(msg).catch(() => void 0);
299
571
  };
300
572
  stdioTransport.onclose = () => finish();
@@ -311,6 +583,8 @@ async function runHttpRelay(delivery, client3, id) {
311
583
  var init_http_relay = __esm({
312
584
  "src/integrations/http-relay.ts"() {
313
585
  "use strict";
586
+ init_stdio_proxy();
587
+ init_mcp_tool_watchdog();
314
588
  }
315
589
  });
316
590
 
@@ -7526,7 +7800,7 @@ function readAnonId() {
7526
7800
  }
7527
7801
  function superProperties() {
7528
7802
  return {
7529
- cliVersion: true ? "2.61.85" : "0.0.0-dev",
7803
+ cliVersion: true ? "2.61.87" : "0.0.0-dev",
7530
7804
  nodeVersion: process.version,
7531
7805
  platform: process.platform,
7532
7806
  arch: process.arch,
@@ -7707,7 +7981,7 @@ var os4 = __toESM(require("os"));
7707
7981
  // package.json
7708
7982
  var package_default = {
7709
7983
  name: "codeam-cli",
7710
- version: "2.61.85",
7984
+ version: "2.61.87",
7711
7985
  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.",
7712
7986
  type: "commonjs",
7713
7987
  main: "dist/index.js",
@@ -8983,7 +9257,7 @@ var CommandRelayService = class _CommandRelayService {
8983
9257
  // fresh + clear the "CLI update available" banner after a self-update
8984
9258
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
8985
9259
  // pair/reconnect). Older backends ignore the extra field.
8986
- ..."2.61.85" ? { ideVersion: "2.61.85" } : {}
9260
+ ..."2.61.87" ? { ideVersion: "2.61.87" } : {}
8987
9261
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
8988
9262
  }
8989
9263
  /**
@@ -20156,7 +20430,7 @@ async function autoUpgradeBeforeCriticalCommand() {
20156
20430
  if (process.env.NODE_ENV === "test") return;
20157
20431
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
20158
20432
  if (process.env.CI) return;
20159
- const current = true ? "2.61.85" : null;
20433
+ const current = true ? "2.61.87" : null;
20160
20434
  if (!current) return;
20161
20435
  const cache = readCache();
20162
20436
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -20173,7 +20447,7 @@ function checkForUpdates() {
20173
20447
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
20174
20448
  if (process.env.CI) return;
20175
20449
  if (!process.stdout.isTTY) return;
20176
- const current = true ? "2.61.85" : null;
20450
+ const current = true ? "2.61.87" : null;
20177
20451
  if (!current) return;
20178
20452
  const cache = readCache();
20179
20453
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -20193,7 +20467,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
20193
20467
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
20194
20468
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
20195
20469
  function currentCliVersion() {
20196
- return true ? "2.61.85" : null;
20470
+ return true ? "2.61.87" : null;
20197
20471
  }
20198
20472
  function runCmd(cmd, args2, timeoutMs) {
20199
20473
  return new Promise((resolve9) => {
@@ -20526,6 +20800,7 @@ function buildFleetBoxRunArgs(p2) {
20526
20800
  ];
20527
20801
  }
20528
20802
  var DOCKER_RUN_TIMEOUT_MS = 12e4;
20803
+ var DOCKER_RUN_WITH_PULL_TIMEOUT_MS = 6e5;
20529
20804
  var defaultDockerRunner = {
20530
20805
  run(args2, opts = {}) {
20531
20806
  return new Promise((resolve9) => {
@@ -20979,7 +21254,9 @@ var HostAgentSupervisor = class {
20979
21254
  }
20980
21255
  const args2 = buildFleetBoxRunArgs(payload);
20981
21256
  const res = await this.docker.run(args2, {
20982
- timeoutMs: DOCKER_RUN_TIMEOUT_MS,
21257
+ // `--pull=always` pulls the ~1 GB image inline — needs the pull bound, not
21258
+ // the 120 s fast bound (which SIGTERM'd the create mid-pull, code=143).
21259
+ timeoutMs: DOCKER_RUN_WITH_PULL_TIMEOUT_MS,
20983
21260
  env: { CODEAM_ENROLL_TOKEN: payload.enrollToken }
20984
21261
  });
20985
21262
  if (res.code === 0) {
@@ -21019,7 +21296,8 @@ var HostAgentSupervisor = class {
21019
21296
  limits: payload.limits
21020
21297
  });
21021
21298
  const res2 = await this.docker.run(args2, {
21022
- timeoutMs: DOCKER_RUN_TIMEOUT_MS,
21299
+ // Recreate also pulls `:latest` inline — use the pull bound, not 120 s.
21300
+ timeoutMs: DOCKER_RUN_WITH_PULL_TIMEOUT_MS,
21023
21301
  env: { CODEAM_ENROLL_TOKEN: payload.enrollToken }
21024
21302
  });
21025
21303
  if (res2.code === 0) {
@@ -41213,7 +41491,7 @@ function checkChokidar() {
41213
41491
  }
41214
41492
  async function doctor(args2 = []) {
41215
41493
  const json = args2.includes("--json");
41216
- const cliVersion = true ? "2.61.85" : "0.0.0-dev";
41494
+ const cliVersion = true ? "2.61.87" : "0.0.0-dev";
41217
41495
  const apiBase2 = resolveApiBaseUrl();
41218
41496
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
41219
41497
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -41465,207 +41743,8 @@ var IntegrationTokenClient = class {
41465
41743
  }
41466
41744
  };
41467
41745
 
41468
- // src/integrations/stdio-proxy.ts
41469
- var import_node_child_process31 = require("child_process");
41470
- var import_node_readline3 = __toESM(require("readline"));
41471
- var RESTART_CHECK_INTERVAL_MS = 3e4;
41472
- var SIGKILL_ESCALATION_MS = 2e3;
41473
- var TOOL_CALL_TIMEOUT_MS = (() => {
41474
- const raw = Number(process.env.CODEAM_MCP_TOOL_TIMEOUT_MS);
41475
- return Number.isFinite(raw) && raw > 0 ? raw : 12e4;
41476
- })();
41477
- var REPLAY_INIT_ID = "__codeam_replay_init__";
41478
- var RestartableStdioProxy = class {
41479
- constructor(opts) {
41480
- this.opts = opts;
41481
- }
41482
- opts;
41483
- child = null;
41484
- childRl = null;
41485
- initializeLine = null;
41486
- inflight = /* @__PURE__ */ new Set();
41487
- /** Per-`tools/call` request-id watchdog timers (see TOOL_CALL_TIMEOUT_MS). */
41488
- toolTimers = /* @__PURE__ */ new Map();
41489
- stdout = null;
41490
- swapping = false;
41491
- pendingClientLines = [];
41492
- ended = () => void 0;
41493
- async start() {
41494
- const stdin = this.opts.stdin ?? process.stdin;
41495
- const stdout = this.opts.stdout ?? process.stdout;
41496
- this.stdout = stdout;
41497
- await this.spawnChild(stdout);
41498
- const rl = import_node_readline3.default.createInterface({ input: stdin, crlfDelay: Infinity });
41499
- rl.on("line", (line) => this.onClientLine(line));
41500
- rl.on("close", () => this.child?.stdin?.end());
41501
- const timer = setInterval(() => this.checkRestart(stdout), RESTART_CHECK_INTERVAL_MS);
41502
- timer.unref();
41503
- return new Promise((resolve9) => {
41504
- this.ended = (code) => {
41505
- clearInterval(timer);
41506
- process.exitCode = code;
41507
- resolve9();
41508
- };
41509
- });
41510
- }
41511
- onClientLine(line) {
41512
- if (this.swapping) {
41513
- this.pendingClientLines.push(line);
41514
- return;
41515
- }
41516
- try {
41517
- const msg = JSON.parse(line);
41518
- if (msg.method === "initialize" && this.initializeLine === null) {
41519
- this.initializeLine = line;
41520
- }
41521
- if (msg.method === "notifications/cancelled") {
41522
- const requestId = msg.params?.requestId;
41523
- if (requestId !== void 0) {
41524
- this.inflight.delete(requestId);
41525
- this.clearToolTimeout(requestId);
41526
- }
41527
- }
41528
- if (msg.id !== void 0 && msg.method !== void 0) {
41529
- this.inflight.add(msg.id);
41530
- if (msg.method === "tools/call") this.armToolTimeout(msg.id);
41531
- }
41532
- } catch {
41533
- }
41534
- this.child?.stdin?.write(line + "\n");
41535
- }
41536
- onChildLine(line, stdout) {
41537
- try {
41538
- const msg = JSON.parse(line);
41539
- if (msg.id !== void 0 && msg.method === void 0) {
41540
- if (msg.id === REPLAY_INIT_ID) {
41541
- return;
41542
- }
41543
- this.inflight.delete(msg.id);
41544
- this.clearToolTimeout(msg.id);
41545
- }
41546
- } catch {
41547
- }
41548
- stdout.write(line + "\n");
41549
- if (this.inflight.size === 0) this.checkRestart(stdout);
41550
- }
41551
- /**
41552
- * Arm a per-`tools/call` watchdog. If the server never answers this request
41553
- * id within {@link TOOL_CALL_TIMEOUT_MS}, synthesize a JSON-RPC error response
41554
- * to the client so the agent's turn unblocks (clean tool error) instead of
41555
- * hanging forever with no Stop. Idempotent per id.
41556
- */
41557
- armToolTimeout(id) {
41558
- const existing = this.toolTimers.get(id);
41559
- if (existing) clearTimeout(existing);
41560
- const t2 = setTimeout(() => {
41561
- this.toolTimers.delete(id);
41562
- if (!this.inflight.has(id)) return;
41563
- this.inflight.delete(id);
41564
- process.stderr.write(
41565
- `[codeam mcp-run] tools/call id=${String(id)} timed out after ${TOOL_CALL_TIMEOUT_MS}ms \u2014 server did not respond; failing the call so the turn can proceed
41566
- `
41567
- );
41568
- const errResponse = {
41569
- jsonrpc: "2.0",
41570
- id,
41571
- error: {
41572
- code: -32001,
41573
- message: `MCP tool call timed out after ${Math.round(TOOL_CALL_TIMEOUT_MS / 1e3)}s \u2014 the server did not respond. The target service/deployment may be unreachable (e.g. a Convex dev deployment is only reachable while \`convex dev\` is running \u2014 use a production deploy key or start \`convex dev\`).`
41574
- }
41575
- };
41576
- (this.stdout ?? process.stdout).write(JSON.stringify(errResponse) + "\n");
41577
- }, TOOL_CALL_TIMEOUT_MS);
41578
- t2.unref();
41579
- this.toolTimers.set(id, t2);
41580
- }
41581
- clearToolTimeout(id) {
41582
- const t2 = this.toolTimers.get(id);
41583
- if (t2) {
41584
- clearTimeout(t2);
41585
- this.toolTimers.delete(id);
41586
- }
41587
- }
41588
- clearAllToolTimeouts() {
41589
- for (const t2 of this.toolTimers.values()) clearTimeout(t2);
41590
- this.toolTimers.clear();
41591
- }
41592
- /**
41593
- * Failsafe wrapper for the fire-and-forget call sites (post-response check
41594
- * + the 30 s timer): `maybeRestart` handles the token-fetch failure itself,
41595
- * but nothing that escapes it may become an unhandled rejection — that
41596
- * would kill the whole shim process.
41597
- */
41598
- checkRestart(stdout) {
41599
- this.maybeRestart(stdout).catch((err) => {
41600
- process.stderr.write(
41601
- `[codeam mcp-run] restart check failed (will retry): ${err instanceof Error ? err.message : String(err)}
41602
- `
41603
- );
41604
- });
41605
- }
41606
- async maybeRestart(stdout) {
41607
- if (this.swapping || !this.opts.shouldRestartNow()) return;
41608
- if (this.inflight.size > 0 || this.initializeLine === null || !this.child) return;
41609
- this.swapping = true;
41610
- let spec;
41611
- try {
41612
- spec = await this.opts.spawnSpec();
41613
- } catch (err) {
41614
- process.stderr.write(
41615
- `[codeam mcp-run] token refresh failed, keeping current server (will retry): ${err instanceof Error ? err.message : String(err)}
41616
- `
41617
- );
41618
- this.swapping = false;
41619
- for (const l of this.pendingClientLines.splice(0)) this.onClientLine(l);
41620
- return;
41621
- }
41622
- const old = this.child;
41623
- const oldRl = this.childRl;
41624
- this.child = null;
41625
- this.childRl = null;
41626
- oldRl?.close();
41627
- old.kill("SIGTERM");
41628
- const escalation = setTimeout(() => {
41629
- if (!old.killed || old.exitCode === null) old.kill("SIGKILL");
41630
- }, SIGKILL_ESCALATION_MS);
41631
- escalation.unref();
41632
- try {
41633
- await this.spawnChild(stdout, spec);
41634
- const replayed = JSON.parse(this.initializeLine);
41635
- this.child.stdin.write(JSON.stringify({ ...replayed, id: REPLAY_INIT_ID }) + "\n");
41636
- this.child.stdin.write(
41637
- JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\n"
41638
- );
41639
- } finally {
41640
- this.swapping = false;
41641
- for (const l of this.pendingClientLines.splice(0)) this.onClientLine(l);
41642
- }
41643
- }
41644
- async spawnChild(stdout, preResolved) {
41645
- const spec = preResolved ?? await this.opts.spawnSpec();
41646
- const spawn44 = this.opts.spawnImpl ?? import_node_child_process31.spawn;
41647
- const child = spawn44(spec.command, spec.args, {
41648
- env: { ...process.env, ...spec.env },
41649
- // env only — never argv
41650
- stdio: ["pipe", "pipe", "inherit"]
41651
- });
41652
- this.child = child;
41653
- child.stdin?.on("error", () => void 0);
41654
- const rl = import_node_readline3.default.createInterface({ input: child.stdout, crlfDelay: Infinity });
41655
- this.childRl = rl;
41656
- rl.on("line", (line) => {
41657
- if (child !== this.child) return;
41658
- this.onChildLine(line, stdout);
41659
- });
41660
- child.on("exit", (code) => {
41661
- if (child !== this.child) return;
41662
- this.clearAllToolTimeouts();
41663
- this.ended(code ?? 1);
41664
- });
41665
- }
41666
- };
41667
-
41668
41746
  // src/integrations/mcp-run.ts
41747
+ init_stdio_proxy();
41669
41748
  var RESTART_AHEAD_MS = 5 * 60 * 1e3;
41670
41749
  function resolveDelivery(id) {
41671
41750
  const fromRegistry = isKnownIntegrationId(id) ? getIntegration(id).delivery.mcp ?? null : null;
@@ -41803,7 +41882,7 @@ async function mcpRun(args2) {
41803
41882
  // src/commands/version.ts
41804
41883
  var import_picocolors15 = __toESM(require("picocolors"));
41805
41884
  function version2() {
41806
- const v = true ? "2.61.85" : "unknown";
41885
+ const v = true ? "2.61.87" : "unknown";
41807
41886
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
41808
41887
  }
41809
41888
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeam-cli",
3
- "version": "2.61.85",
3
+ "version": "2.61.87",
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",