codeam-cli 2.39.22 → 2.39.24

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 +16 -0
  2. package/dist/index.js +55 -768
  3. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ 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.39.23] — 2026-06-17
8
+
9
+ ### Chore
10
+
11
+ - **cli:** Raise engines.node floor to >=20, bump which to ^6 (#345)
12
+
13
+ ### Agent
14
+
15
+ - Waiting for your CLI... (#348)
16
+
17
+ ## [2.39.22] — 2026-06-16
18
+
19
+ ### Fixed
20
+
21
+ - **cli:** Stop the onboarding turn from wiping the welcome banner card (#344)
22
+
7
23
  ## [2.39.21] — 2026-06-16
8
24
 
9
25
  ### Fixed
package/dist/index.js CHANGED
@@ -498,7 +498,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
498
498
  // package.json
499
499
  var package_default = {
500
500
  name: "codeam-cli",
501
- version: "2.39.22",
501
+ version: "2.39.24",
502
502
  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.",
503
503
  type: "commonjs",
504
504
  main: "dist/index.js",
@@ -565,7 +565,7 @@ var package_default = {
565
565
  url: "https://github.com/sponsors/edgar-durand"
566
566
  },
567
567
  engines: {
568
- node: ">=18.0.0"
568
+ node: ">=20.0.0"
569
569
  },
570
570
  dependencies: {
571
571
  "@agentclientprotocol/claude-agent-acp": "^0.42.0",
@@ -576,7 +576,7 @@ var package_default = {
576
576
  ignore: "^5.3.2",
577
577
  picocolors: "^1.1.0",
578
578
  "qrcode-terminal": "^0.12.0",
579
- which: "^5.0.0",
579
+ which: "^6.0.0",
580
580
  ws: "^8.18.0",
581
581
  zod: "^4.3.6"
582
582
  },
@@ -5908,7 +5908,7 @@ function readAnonId() {
5908
5908
  }
5909
5909
  function superProperties() {
5910
5910
  return {
5911
- cliVersion: true ? "2.39.22" : "0.0.0-dev",
5911
+ cliVersion: true ? "2.39.24" : "0.0.0-dev",
5912
5912
  nodeVersion: process.version,
5913
5913
  platform: process.platform,
5914
5914
  arch: process.arch,
@@ -9748,300 +9748,6 @@ function listResumableSessions(cwd) {
9748
9748
  return out2;
9749
9749
  }
9750
9750
 
9751
- // src/agents/claude/parsing.ts
9752
- function filterChrome(lines) {
9753
- const result = [];
9754
- let skipEchoContinuation = false;
9755
- for (const line of lines) {
9756
- const t2 = line.trim();
9757
- if (!t2) {
9758
- skipEchoContinuation = false;
9759
- continue;
9760
- }
9761
- if (/^[─━—═─\-]{3,}$/.test(t2)) {
9762
- skipEchoContinuation = false;
9763
- continue;
9764
- }
9765
- if (/^[●⏺]\s/.test(t2)) skipEchoContinuation = false;
9766
- if (/^[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]\s/.test(t2)) continue;
9767
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) continue;
9768
- if (/high\s*[·•]\s*\/effort/i.test(t2)) continue;
9769
- if (/^[❯>]\s*$/.test(t2)) continue;
9770
- if (/^\(thinking\)\s*$/.test(t2)) continue;
9771
- if (/^\?\s.*shortcut/i.test(t2)) continue;
9772
- if (/spending limit|usage limit/i.test(t2) && t2.length < 80) continue;
9773
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) continue;
9774
- if (t2.replace(/\s/g, "").length === 1) continue;
9775
- if ((t2.match(/─/g)?.length ?? 0) >= 6) continue;
9776
- if (/ctrl\+?o\s+to\s+expand/i.test(t2)) continue;
9777
- if (/^•\s+(?:Read(?:ing)?|Edit(?:ing)?|Writ(?:e|ing)|Bash|Runn(?:ing)?|Search(?:ing)?|Glob(?:bing)?|Grep(?:ping)?|Creat(?:e|ing)|Execut(?:e|ing)|Task|Agent|NotebookEdit)\b/i.test(
9778
- t2
9779
- ))
9780
- continue;
9781
- if (/^└\s/.test(t2)) continue;
9782
- if (/^\+\s/.test(t2) && /\d+\s*s\s*[·•]|\bthought\s+for\b|\d+\s*tokens|\(thinking\)/i.test(t2)) continue;
9783
- if (/^↓\s*\d+\s*tokens/i.test(t2)) continue;
9784
- if (/^\bthought\s+for\s+\d+/i.test(t2)) continue;
9785
- const stripped = t2.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "");
9786
- if (/^[❯>]\s+\S/.test(stripped) && !/^[❯>]\s*\d+\./.test(stripped)) {
9787
- skipEchoContinuation = true;
9788
- continue;
9789
- }
9790
- if (skipEchoContinuation) continue;
9791
- result.push(line);
9792
- }
9793
- return result;
9794
- }
9795
- var SPINNER_RE = /^(?:[✳✢✶✻✽✴✷✸✹⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏◐◑◒◓▁▂▃▄▅▆▇█]|🔴|🟠|🟡|🟢|🔵|🟣|🟤|⚫|⚪|🌀|💭|✨)\s/u;
9796
- var BULLET_TOOL_RE = /^•\s+(?:Read(?:ing)?|Edit(?:ing)?|Writ(?:e|ing)|Bash|Runn(?:ing)?|Search(?:ing)?|Glob(?:bing)?|Grep(?:ping)?|Creat(?:e|ing)|Execut(?:e|ing)|Task|Agent|NotebookEdit)\b/i;
9797
- var TREE_LINE_RE = /^└\s/;
9798
- var STATUS_LINE_RE = /^(?:\+|[🔴🟠🟡🟢🔵🟣🟤⚫⚪🌀💭✨])\s/u;
9799
- function isChromeLine(line) {
9800
- const t2 = line.replace(/️/g, "").trim();
9801
- if (!t2) return false;
9802
- if (/^[─━—═─\-]{3,}$/.test(t2)) return true;
9803
- if (SPINNER_RE.test(t2)) return true;
9804
- if (BULLET_TOOL_RE.test(t2)) return true;
9805
- if (TREE_LINE_RE.test(t2)) return true;
9806
- if (STATUS_LINE_RE.test(t2) && /\d+\s*s\s*[·•]|\bthought\s+for\b|\d+\s*tokens|\(thinking\)/i.test(t2)) return true;
9807
- if (/^↓\s*\d+\s*tokens/i.test(t2)) return true;
9808
- if (/^\bthought\s+for\s+\d+/i.test(t2)) return true;
9809
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) return true;
9810
- if (/high\s*[·•]\s*\/effort/i.test(t2)) return true;
9811
- if (/^[❯>]\s*$/.test(t2)) return true;
9812
- if (/^\(thinking\)\s*$/.test(t2)) return true;
9813
- if (/^\?\s.*shortcut/i.test(t2)) return true;
9814
- if (/spending limit|usage limit/i.test(t2) && t2.length < 80) return true;
9815
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) return true;
9816
- if (t2.replace(/\s/g, "").length === 1) return true;
9817
- if ((t2.match(/─/g)?.length ?? 0) >= 6) return true;
9818
- if (/ctrl\+?o\s+to\s+expand/i.test(t2)) return true;
9819
- const hasBoxPrefix = /^[│╭╰╮╯┌└┐┘├┤┬┴┼]/.test(t2);
9820
- const stripped = t2.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "");
9821
- if (hasBoxPrefix && /^[❯>]\s+\S/.test(stripped) && !/^[❯>]\s*\d+\./.test(stripped)) return true;
9822
- return false;
9823
- }
9824
- function parseChromeLine(line) {
9825
- const t2 = line.replace(/️/g, "").trim();
9826
- if (!t2) return null;
9827
- if (/^[─━—═─\-]{3,}$/.test(t2)) return null;
9828
- if (/^[❯>]\s*$/.test(t2)) return null;
9829
- if (t2.replace(/\s/g, "").length === 1) return null;
9830
- if ((t2.match(/─/g)?.length ?? 0) >= 6) return null;
9831
- if (/esc.{0,5}to.{0,5}interrupt/i.test(t2)) return null;
9832
- if (/high\s*[·•]\s*\/effort/i.test(t2)) return null;
9833
- if (/↑\s*\/?\s*↓\s*to\s*navigate/i.test(t2)) return null;
9834
- if (/ctrl\+?o\s+to\s+expand/i.test(t2)) return null;
9835
- if (/spending limit|usage limit/i.test(t2)) return null;
9836
- if (/^\(thinking\)\s*$/.test(t2)) {
9837
- return { tool: "thinking", label: "Thinking\u2026", status: "running" };
9838
- }
9839
- if (TREE_LINE_RE.test(t2)) return null;
9840
- if (STATUS_LINE_RE.test(t2)) {
9841
- const label = t2.slice(2).replace(/….*/s, "").trim() || "Thinking\u2026";
9842
- return { tool: "thinking", label, status: "running" };
9843
- }
9844
- let text = t2;
9845
- if (SPINNER_RE.test(t2)) {
9846
- text = t2.slice(2).trim().replace(/….*/s, "").trim();
9847
- } else if (BULLET_TOOL_RE.test(t2)) {
9848
- text = t2.slice(2).trim();
9849
- text = text.replace(/\s*\(ctrl\+?o[^)]*\)/gi, "").replace(/,\s*reading\s+\d+\s+files?\s*…?/gi, "").replace(/,\s*\d+\s+files?\s*…?/gi, "").replace(/…$/, "").trim();
9850
- }
9851
- if (!text) return null;
9852
- return classifyStep(text);
9853
- }
9854
- function classifyStep(text) {
9855
- if (/^Read(?:ing)?\s+/i.test(text)) {
9856
- const label2 = text.replace(/^Read(?:ing)?\s+/i, "").replace(/\.\.\.$/, "").trim();
9857
- return { tool: "read", label: label2, status: "running" };
9858
- }
9859
- if (/^Edit(?:ing)?\s+|^Writ(?:e|ing|ing to)\s+|^Creat(?:e|ing)\s+/i.test(text)) {
9860
- const label2 = text.replace(/^(?:Edit(?:ing)?|Writ(?:e|ing(?: to)?)|Creat(?:e|ing))\s+/i, "").replace(/\.\.\.$/, "").trim();
9861
- return { tool: "edit", label: label2, status: "running" };
9862
- }
9863
- if (/^Runn(?:ing)?\s+|^Execut(?:e|ing)\s+|^Bash(?:ing)?\s*:|^\$\s+/i.test(text)) {
9864
- const label2 = text.replace(/^(?:Runn(?:ing)?|Execut(?:e|ing)|Bash(?:ing)?:|\$)\s+/i, "").replace(/\.\.\.$/, "").trim();
9865
- return { tool: "bash", label: label2, status: "running" };
9866
- }
9867
- if (/^Search(?:ing)?\s+for\s+|^Grep(?:ping)?\s*:/i.test(text)) {
9868
- const label2 = text.replace(/^(?:Search(?:ing)?\s+for|Grep(?:ping)?:)\s+/i, "").replace(/\.\.\.$/, "").trim();
9869
- return { tool: "search", label: label2, status: "running" };
9870
- }
9871
- const label = text.replace(/\.\.\.$/, "").trim();
9872
- return { tool: "other", label, status: "running" };
9873
- }
9874
- function detectSelector(lines) {
9875
- if (lines.some((l) => /\?\s+for\s+shortcuts/i.test(l.trim()))) return null;
9876
- const clean = lines.map(
9877
- (l) => l.replace(/^[│╭╰╮╯┌└┐┘├┤┬┴┼]\s?/, "").replace(/\s*[│╭╰╮╯┌└┐┘├┤┬┴┼─━═]+\s*$/, "")
9878
- );
9879
- const hasCursor = clean.some((l) => /^[❯>]\s*\d+\./.test(l.trim()));
9880
- const looksLikeTrust = clean.some(
9881
- (l) => /\b(?:trust\s+the\s+files|trust\s+this\s+folder|safety\s+check)\b/i.test(l)
9882
- );
9883
- if (!hasCursor && !looksLikeTrust) return null;
9884
- const OPTION_RE = /^(?:[❯>]\s*)?(\d+)\.(\s+|(?=\D))(.+)/;
9885
- let optionStartIdx = -1;
9886
- for (let i = 0; i < clean.length; i++) {
9887
- if (OPTION_RE.test(clean[i].trim())) {
9888
- optionStartIdx = i;
9889
- break;
9890
- }
9891
- }
9892
- if (optionStartIdx === -1) return null;
9893
- const questionParts = [];
9894
- for (let i = 0; i < optionStartIdx; i++) {
9895
- const t2 = clean[i].trim();
9896
- if (!t2) continue;
9897
- if (/^[─━—═\-]{3,}$/.test(t2)) continue;
9898
- if (/^\[.*\]$/.test(t2)) continue;
9899
- if (/^[>❯]\s/.test(t2)) continue;
9900
- if (!t2.includes(" ") && t2.length > 15) continue;
9901
- questionParts.push(t2);
9902
- }
9903
- const question = questionParts.filter((line, i, arr) => !arr.some((other, j2) => j2 !== i && other.includes(line))).join("\n").trim();
9904
- const optionLabels = /* @__PURE__ */ new Map();
9905
- const optionDescs = /* @__PURE__ */ new Map();
9906
- let currentNum = -1;
9907
- for (let i = optionStartIdx; i < clean.length; i++) {
9908
- const t2 = clean[i].trim();
9909
- if (!t2) continue;
9910
- const m = t2.match(OPTION_RE);
9911
- if (m) {
9912
- const num = parseInt(m[1], 10);
9913
- if (!optionLabels.has(num)) {
9914
- optionLabels.set(num, m[3].trim());
9915
- optionDescs.set(num, []);
9916
- }
9917
- currentNum = num;
9918
- } else if (currentNum !== -1 && !/^Enter to/i.test(t2) && !/^[─━—═\-]{3,}$/.test(t2) && !/↑.*↓.*navigate/i.test(t2) && !/Esc to/i.test(t2)) {
9919
- optionDescs.get(currentNum)?.push(t2);
9920
- }
9921
- }
9922
- const keys = [...optionLabels.keys()].sort((a, b) => a - b);
9923
- if (keys.length < 2 || keys[0] !== 1) return null;
9924
- return {
9925
- question,
9926
- options: keys.map((k2) => optionLabels.get(k2)),
9927
- optionDescriptions: keys.map((k2) => (optionDescs.get(k2) ?? []).join(" ").trim()),
9928
- currentIndex: 0
9929
- };
9930
- }
9931
- function detectInputSuggestion(lines) {
9932
- let hintIdx = -1;
9933
- for (let i = lines.length - 1; i >= 0; i--) {
9934
- if (/\?\s+for\s+shortcuts/i.test(lines[i].trim())) {
9935
- hintIdx = i;
9936
- break;
9937
- }
9938
- }
9939
- if (hintIdx === -1) return null;
9940
- if (lines.some((l) => /^[❯>]\s*\d+\./.test(l.trim()))) return null;
9941
- const windowStart = Math.max(0, hintIdx - 5);
9942
- for (let i = hintIdx - 1; i >= windowStart; i--) {
9943
- const t2 = lines[i].trim();
9944
- if (!t2) continue;
9945
- if (/^[─━═│┌┐└┘├┤┬┴┼]+$/u.test(t2)) continue;
9946
- const m = t2.match(/^[❯>]\s+(\S.*)$/);
9947
- if (!m) return null;
9948
- if (/^\d+\.\s/.test(m[1])) return null;
9949
- if (/^for\s/i.test(m[1])) return null;
9950
- const text = m[1].trim();
9951
- if (text.length === 0) return null;
9952
- return text;
9953
- }
9954
- return null;
9955
- }
9956
- function detectListSelector(lines) {
9957
- if (!lines.some((l) => /[↑↓].*navigate/i.test(l.trim()))) return null;
9958
- if (lines.some((l) => /^❯\s*\d+\./.test(l.trim()))) return null;
9959
- if (!lines.some((l) => /^\s+❯\s+\S/.test(l))) return null;
9960
- const isSelected = (line) => /^\s+❯\s+\S/.test(line);
9961
- const isUnselected = (line) => /^ \S/.test(line);
9962
- const isItem = (line) => isSelected(line) || isUnselected(line);
9963
- let optionStartIdx = -1;
9964
- for (let i = 0; i < lines.length; i++) {
9965
- if (isItem(lines[i])) {
9966
- optionStartIdx = i;
9967
- break;
9968
- }
9969
- }
9970
- if (optionStartIdx === -1) return null;
9971
- const questionParts = [];
9972
- for (let i = 0; i < optionStartIdx; i++) {
9973
- const t2 = lines[i].trim();
9974
- if (!t2) continue;
9975
- if (/^[─━—═\-]{3,}$/.test(t2)) continue;
9976
- if (/[┌└│┐┘├┤┬┴┼]/.test(t2)) {
9977
- const inner = t2.replace(/[│┌└┐┘├┤┬┴┼─]/g, "").trim();
9978
- if (inner) questionParts.push(inner);
9979
- continue;
9980
- }
9981
- if (/^[>❯]\s/.test(t2)) continue;
9982
- if (/[↑↓].*navigate/i.test(t2)) continue;
9983
- if (!t2.includes(" ") && t2.length > 15) continue;
9984
- questionParts.push(t2);
9985
- }
9986
- const question = questionParts.filter((line, i, arr) => !arr.some((other, j2) => j2 !== i && other.includes(line))).join("\n").trim();
9987
- const options = [];
9988
- let currentIndex = 0;
9989
- for (const line of lines.slice(optionStartIdx)) {
9990
- const t2 = line.trim();
9991
- if (!t2) continue;
9992
- if (/[↑↓].*navigate/i.test(t2)) break;
9993
- if (/^[─━—═\-]{3,}$/.test(t2)) continue;
9994
- if (isSelected(line)) {
9995
- currentIndex = options.length;
9996
- options.push(t2.replace(/^❯\s+/, "").trim());
9997
- } else if (isUnselected(line)) {
9998
- options.push(t2);
9999
- }
10000
- }
10001
- if (options.length < 2) return null;
10002
- return {
10003
- question,
10004
- options,
10005
- optionDescriptions: options.map(() => ""),
10006
- currentIndex
10007
- };
10008
- }
10009
- var BANNER_ART_RE = /[█▀▄▌▐▝▘▛▜▙▟▖▗▔▕▮▯▰▱▓▒░◆◇]/;
10010
- var BANNER_META_RE = /(?:Sonnet|Opus|Haiku|Claude)(?:\s|·|-|\(|$)/i;
10011
- function detectStartupBanner(lines) {
10012
- for (let i = 0; i + 2 < lines.length; i++) {
10013
- if (!/▐▛[█]+▜▌/.test(lines[i])) continue;
10014
- if (!/▝▜[█]+▛▘/.test(lines[i + 1])) continue;
10015
- if (!lines[i + 2].includes("\u2598\u2598")) continue;
10016
- const inArtTitle = lines[i].replace(/^▐▛[█]+▜▌\s*/, "").trim();
10017
- const inArtSubtitle = lines[i + 1].replace(/^▝▜[█]+▛▘\s*/, "").trim();
10018
- const inArtPath = lines[i + 2].replace(/.*▝▝\s*/, "").trim();
10019
- return {
10020
- title: inArtTitle === lines[i].trim() ? "" : inArtTitle,
10021
- subtitle: inArtSubtitle === lines[i + 1].trim() ? "" : inArtSubtitle,
10022
- path: inArtPath === lines[i + 2].trim() ? "" : inArtPath,
10023
- startIdx: i,
10024
- endIdx: i + 2
10025
- };
10026
- }
10027
- const metaIdx = lines.findIndex(
10028
- (l) => BANNER_META_RE.test(l) && /(?:Claude|API|Console)/i.test(l) && !BANNER_ART_RE.test(l)
10029
- );
10030
- if (metaIdx === -1) return null;
10031
- let artStart = metaIdx;
10032
- while (artStart > 0 && BANNER_ART_RE.test(lines[artStart - 1])) artStart--;
10033
- if (metaIdx - artStart < 2) return null;
10034
- const pathLine = (lines[metaIdx + 1] ?? "").trim();
10035
- const path55 = pathLine && !BANNER_ART_RE.test(pathLine) ? pathLine : "";
10036
- return {
10037
- title: "",
10038
- subtitle: lines[metaIdx].trim(),
10039
- path: path55,
10040
- startIdx: artStart,
10041
- endIdx: metaIdx + (path55 ? 1 : 0)
10042
- };
10043
- }
10044
-
10045
9751
  // src/agents/claude/runtime.ts
10046
9752
  var ClaudeRuntimeStrategy = class {
10047
9753
  id = "claude";
@@ -10152,24 +9858,17 @@ var ClaudeRuntimeStrategy = class {
10152
9858
  return { ptyInput: "/compact\r" };
10153
9859
  }
10154
9860
  // ─── TUI parser strategy methods ─────────────────────────────────
10155
- parseTuiChrome(line) {
10156
- if (!isChromeLine(line)) return null;
10157
- return parseChromeLine(line);
10158
- }
9861
+ // Claude runs ACP-only (see the `requiresAcp` dispatch in start.ts),
9862
+ // so the legacy PTY-spawn pipeline never reaches these. They remain
9863
+ // as inert stubs purely to satisfy the InteractiveAgentStrategy
9864
+ // contract; the React-Ink TUI parsers were removed with the PTY path.
9865
+ // The optional TUI hooks (parseTuiChrome / detectReadyPrompt /
9866
+ // detectStartupBanner / detectInputSuggestion) are dropped entirely.
10159
9867
  filterTuiOutput(lines) {
10160
- return filterChrome(lines);
10161
- }
10162
- detectInteractivePrompt(lines) {
10163
- return detectSelector(lines) ?? detectListSelector(lines);
10164
- }
10165
- detectReadyPrompt(lines) {
10166
- return lines.some((l) => /^\?\s.*shortcut/i.test(l.trim()));
10167
- }
10168
- detectStartupBanner(lines) {
10169
- return detectStartupBanner(lines);
9868
+ return lines;
10170
9869
  }
10171
- detectInputSuggestion(lines) {
10172
- return detectInputSuggestion(lines);
9870
+ detectInteractivePrompt(_lines) {
9871
+ return null;
10173
9872
  }
10174
9873
  credentialLocator() {
10175
9874
  return claudeCredentialLocator();
@@ -10603,410 +10302,6 @@ function getCurrentUsage2(historyDir) {
10603
10302
  };
10604
10303
  }
10605
10304
 
10606
- // src/agents/codex/parsing.ts
10607
- var BOX_DRAW_RE = /^[╭─╮│╰╯]/u;
10608
- var BULLET_CHARS = "\u2022\xB7\u2027\u2219\u22C5";
10609
- var CODEX_AGENT_REPLY_RE = new RegExp(`^[${BULLET_CHARS}]\\s`, "u");
10610
- var STRIP_BULLET_RE = new RegExp(`^(\\s*)[${BULLET_CHARS}]\\s`, "u");
10611
- var CODEX_USER_ECHO_RE = /^[›>]\s+(?!\d+\.\s)\S/u;
10612
- var TIP_RE = /^\s*Tip:\s/i;
10613
- var LEARN_MORE_RE = /^\s*Learn more:\s/i;
10614
- var CODEX_STATUS_FOOTER_RE = /\bdefault\s+[·•]\s+\S+/i;
10615
- var CODEAM_BANNER_RES = [
10616
- // Bullet-prefixed banner entries (any role / launch label).
10617
- new RegExp(`^[${BULLET_CHARS}]\\s+(Launching|Edgar|PRO|FREE|ENTERPRISE)\\b`, "i"),
10618
- /^Paired\b/,
10619
- /^codeam\b\s+v\d/,
10620
- /^✓\s+Paired/,
10621
- /^◇\s+Paired/
10622
- ];
10623
- function filterCodexChrome(lines) {
10624
- const out2 = [];
10625
- for (const line of lines) {
10626
- const t2 = line.trimEnd();
10627
- const trimmed = t2.trimStart();
10628
- if (!trimmed) continue;
10629
- if (BOX_DRAW_RE.test(trimmed)) continue;
10630
- if (/^OpenAI Codex\b/i.test(trimmed) || /^>_\s+OpenAI Codex\b/i.test(trimmed) || /^model:\s/i.test(trimmed) || /^directory:\s/i.test(trimmed)) continue;
10631
- if (TIP_RE.test(t2) || LEARN_MORE_RE.test(t2)) continue;
10632
- if (CODEX_STATUS_FOOTER_RE.test(trimmed)) continue;
10633
- if (CODEAM_BANNER_RES.some((re2) => re2.test(trimmed))) continue;
10634
- if (CODEX_USER_ECHO_RE.test(trimmed)) continue;
10635
- if (CODEX_AGENT_REPLY_RE.test(trimmed)) {
10636
- out2.push(t2.replace(STRIP_BULLET_RE, "$1"));
10637
- continue;
10638
- }
10639
- out2.push(t2);
10640
- }
10641
- const dedented = dedentCodexStructuredLines(out2);
10642
- const wrapped = wrapCodexCodeBlocks(dedented);
10643
- const hasRealInput = lines.some((l) => /\w/.test(l));
10644
- if (out2.length > 0 || hasRealInput) {
10645
- const sampleIn = lines.slice(-50).map((l, i) => ` in[${i}] ${JSON.stringify(l)}`).join("\n");
10646
- const sampleOut = dedented.map((l, i) => ` out[${i}] ${JSON.stringify(l)}`).join("\n");
10647
- log.info("codex-parse", `in=${lines.length} out=${dedented.length}
10648
- ${sampleIn}
10649
- ---
10650
- ${sampleOut}`);
10651
- } else {
10652
- log.trace("codex-parse", `filterCodexChrome in=${lines.length} out=${out2.length}`);
10653
- }
10654
- return wrapped;
10655
- }
10656
- var CODE_CHAR_RE = /[;{}]|=>|^\s*(?:import|public|private|static|class|function|interface|type|const|let|var|def|return|if|else|for|while)\b/;
10657
- var DIFF_HUNK_RE = /^@@\s+-\d+(?:,\d+)?\s+\+\d+(?:,\d+)?\s+@@/;
10658
- var DIFF_GIT_RE = /^diff\s+--git\s+/;
10659
- var DIFF_OLD_RE = /^---\s+(?:a\/)?\S/;
10660
- var DIFF_NEW_RE = /^\+\+\+\s+(?:b\/)?\S/;
10661
- var COMMIT_HEAD_RE = /^\[[\w./@-]+\s+[0-9a-f]{7,40}\]\s+/;
10662
- var COMMIT_STATS_RE = /\d+\s+files?\s+changed/;
10663
- var PUSH_TO_RE = /^To\s+(?:https?:\/\/|git@)/;
10664
- var PUSH_NEW_RE = /\[new branch\]\s+\S+\s*->\s*\S+/;
10665
- var PUSH_UPDATE_RE = /^\s*[0-9a-f]{7,40}\.\.[0-9a-f]{7,40}\s+\S+\s*->\s*\S+/;
10666
- var MERGE_UPD_RE = /^Updating\s+[0-9a-f]{7,40}\.\.[0-9a-f]{7,40}/;
10667
- var MERGE_FF_RE = /^Fast-forward\s*$/;
10668
- var PR_TITLE_RE = /^title:\s+\S/;
10669
- var PR_STATE_RE = /^state:\s+(?:OPEN|CLOSED|MERGED|DRAFT)/i;
10670
- var PR_URL_RE = /https?:\/\/github\.com\/[\w.-]+\/[\w.-]+\/pull\/\d+/;
10671
- var PR_BANNER_RE = /^\s*[✓✔]?\s*Pull request created\s*$/i;
10672
- function dedentCodexStructuredLines(lines) {
10673
- const MARKER_RE = /^( +)(?:diff --git |@@ |--- |\+\+\+ |Updating [0-9a-f]|Fast-forward|Merge made by |To (?:https?:\/\/|git@|github\.com|[\w.-]+[:/])|From (?:https?:\/\/|git@|github\.com|[\w.-]+[:/])|\[[\w./@-]+\s+[0-9a-f]{7,40}\])/;
10674
- let margin = -1;
10675
- for (const line of lines) {
10676
- const m = line.match(MARKER_RE);
10677
- if (m) {
10678
- const w3 = m[1].length;
10679
- if (margin === -1 || w3 < margin) margin = w3;
10680
- }
10681
- }
10682
- if (margin <= 0) return lines;
10683
- return lines.map((line) => {
10684
- if (line.length === 0) return line;
10685
- const lead = line.match(/^ */)?.[0].length ?? 0;
10686
- const strip = Math.min(margin, lead);
10687
- return strip > 0 ? line.slice(strip) : line;
10688
- });
10689
- }
10690
- function isStructuredBlock(block) {
10691
- if (block.some((l) => DIFF_HUNK_RE.test(l))) return true;
10692
- if (block.some((l) => DIFF_GIT_RE.test(l))) return true;
10693
- if (block.some((l) => DIFF_OLD_RE.test(l)) && block.some((l) => DIFF_NEW_RE.test(l))) return true;
10694
- if (block.some((l) => COMMIT_HEAD_RE.test(l))) return true;
10695
- if (block.some((l) => COMMIT_STATS_RE.test(l))) return true;
10696
- if (block.some((l) => PUSH_TO_RE.test(l))) return true;
10697
- if (block.some((l) => PUSH_NEW_RE.test(l))) return true;
10698
- if (block.some((l) => PUSH_UPDATE_RE.test(l))) return true;
10699
- if (block.some((l) => MERGE_UPD_RE.test(l))) return true;
10700
- if (block.some((l) => MERGE_FF_RE.test(l))) return true;
10701
- if (block.some((l) => PR_TITLE_RE.test(l)) && block.some((l) => PR_STATE_RE.test(l))) return true;
10702
- if (block.some((l) => PR_URL_RE.test(l))) return true;
10703
- if (block.some((l) => PR_BANNER_RE.test(l))) return true;
10704
- return false;
10705
- }
10706
- function inferLanguage(block) {
10707
- const head = block.slice(0, 10).join("\n");
10708
- if (/\bpublic\s+(?:static\s+)?(?:class|void|int|String)\b|System\.out\.println|\bjava\.util/.test(head)) return "java";
10709
- if (/\b(?:interface|type)\s+\w+\s*=?\s*[{<]|\bas\s+(?:string|number|boolean)\b|\b(?:string|number|boolean)\s*[;,)\]]/.test(head)) return "typescript";
10710
- if (/\bimport\s+\w+\s+from\s+['"]|=>\s*[{(]|\bconst\s+\w+\s*=\s*(?:async\s+)?\(/.test(head)) return "javascript";
10711
- if (/^\s*def\s+\w+\(|^\s*from\s+\w+\s+import|print\(/m.test(head)) return "python";
10712
- if (/^\s*package\s+\w+|^\s*func\s+\w+\(|\binterface\s*{/m.test(head)) return "go";
10713
- if (/^\s*fn\s+\w+\(|^\s*use\s+\w+::|^\s*impl\s+/m.test(head)) return "rust";
10714
- if (/#include\s*<|int\s+main\s*\(/.test(head)) return "cpp";
10715
- return "";
10716
- }
10717
- function wrapCodexCodeBlocks(lines) {
10718
- if (isStructuredBlock(lines)) {
10719
- return lines;
10720
- }
10721
- const result = [];
10722
- let i = 0;
10723
- while (i < lines.length) {
10724
- const line = lines[i];
10725
- if (!CODE_CHAR_RE.test(line)) {
10726
- result.push(line);
10727
- i++;
10728
- continue;
10729
- }
10730
- const start2 = i;
10731
- let end = i;
10732
- let j2 = i + 1;
10733
- while (j2 < lines.length) {
10734
- const l = lines[j2];
10735
- if (CODE_CHAR_RE.test(l)) {
10736
- end = j2;
10737
- j2++;
10738
- continue;
10739
- }
10740
- if (l.trim() === "") {
10741
- let k2 = j2 + 1;
10742
- while (k2 < lines.length && lines[k2].trim() === "") k2++;
10743
- if (k2 < lines.length && CODE_CHAR_RE.test(lines[k2])) {
10744
- end = k2;
10745
- j2 = k2 + 1;
10746
- continue;
10747
- }
10748
- break;
10749
- }
10750
- if (/^\s{2,}\S/.test(l)) {
10751
- end = j2;
10752
- j2++;
10753
- continue;
10754
- }
10755
- break;
10756
- }
10757
- const runLen = end - start2 + 1;
10758
- const body = lines.slice(start2, end + 1);
10759
- if (isStructuredBlock(body)) {
10760
- for (const l of body) result.push(l);
10761
- i = end + 1;
10762
- continue;
10763
- }
10764
- const codeShapedCount = body.filter((l) => CODE_CHAR_RE.test(l)).length;
10765
- if (codeShapedCount >= 3) {
10766
- const lang = inferLanguage(body);
10767
- result.push("```" + lang);
10768
- for (const l of body) result.push(l);
10769
- while (result.length > 0 && result[result.length - 1].trim() === "") {
10770
- result.pop();
10771
- }
10772
- result.push("```");
10773
- i = end + 1;
10774
- } else {
10775
- for (let k2 = start2; k2 <= end && k2 < lines.length; k2++) result.push(lines[k2]);
10776
- i = end + 1;
10777
- if (i === start2) i++;
10778
- }
10779
- void runLen;
10780
- }
10781
- return result;
10782
- }
10783
- function parseCodexChrome(_line) {
10784
- return null;
10785
- }
10786
- var CODEX_OPTION_RE = /^\s*([>›]\s+)?(\d+)\.\s+(.+)/;
10787
- var CODEX_OPTION_START_RE = /^\s*(?:[>›]\s+)?\d+\.\s/;
10788
- var CODEX_FOOTER_RE = /\bpress\s+enter\s+to\s+(?:confirm|continue|select)\b/i;
10789
- function detectCodexSelector(lines) {
10790
- let optionStartIdx = -1;
10791
- for (let i = 0; i < lines.length; i++) {
10792
- if (CODEX_OPTION_START_RE.test(lines[i])) {
10793
- optionStartIdx = i;
10794
- break;
10795
- }
10796
- }
10797
- if (optionStartIdx === -1) return null;
10798
- const optionLabels = /* @__PURE__ */ new Map();
10799
- let cursorIndex = 0;
10800
- let hasCursor = false;
10801
- let footerAfterOptions = false;
10802
- let lastOptionLineIdx = optionStartIdx;
10803
- for (let i = optionStartIdx; i < lines.length; i++) {
10804
- const raw = lines[i];
10805
- const t2 = raw.trim();
10806
- if (!t2) continue;
10807
- if (CODEX_FOOTER_RE.test(t2)) {
10808
- footerAfterOptions = true;
10809
- break;
10810
- }
10811
- const m = t2.match(CODEX_OPTION_RE);
10812
- if (!m) {
10813
- if (i - lastOptionLineIdx > 2) break;
10814
- continue;
10815
- }
10816
- const num = parseInt(m[2], 10);
10817
- if (!optionLabels.has(num)) {
10818
- optionLabels.set(num, m[3].trim());
10819
- if (m[1]) {
10820
- cursorIndex = optionLabels.size - 1;
10821
- hasCursor = true;
10822
- }
10823
- }
10824
- lastOptionLineIdx = i;
10825
- }
10826
- const keys = [...optionLabels.keys()].sort((a, b) => a - b);
10827
- if (keys.length < 2 || keys[0] !== 1) return null;
10828
- const questionParts = [];
10829
- for (let i = 0; i < optionStartIdx; i++) {
10830
- const t2 = lines[i].trim();
10831
- if (!t2) continue;
10832
- if (/^[>›]\s*$/.test(t2)) continue;
10833
- questionParts.push(t2);
10834
- }
10835
- const question = questionParts.join("\n").trim();
10836
- const questionEndsWithQuery = /\?\s*$/.test(question);
10837
- if (!hasCursor && !(questionEndsWithQuery && footerAfterOptions)) {
10838
- return null;
10839
- }
10840
- return {
10841
- question,
10842
- options: keys.map((k2) => optionLabels.get(k2)),
10843
- optionDescriptions: keys.map(() => ""),
10844
- currentIndex: hasCursor ? cursorIndex : 0
10845
- };
10846
- }
10847
-
10848
- // src/agents/codex/renderer.ts
10849
- function renderCodexBuffer(raw) {
10850
- const scrollback = [];
10851
- const screen = [""];
10852
- let row = 0;
10853
- let col = 0;
10854
- let scrollTop = null;
10855
- let scrollBottom = null;
10856
- function ensureRow() {
10857
- while (screen.length <= row) screen.push("");
10858
- }
10859
- function writeChar(ch) {
10860
- ensureRow();
10861
- if (col < screen[row].length) {
10862
- screen[row] = screen[row].slice(0, col) + ch + screen[row].slice(col + 1);
10863
- } else {
10864
- while (screen[row].length < col) screen[row] += " ";
10865
- screen[row] += ch;
10866
- }
10867
- col++;
10868
- }
10869
- function scrollRegionUp() {
10870
- if (scrollTop === null || scrollBottom === null) {
10871
- ensureRow();
10872
- return;
10873
- }
10874
- while (screen.length <= scrollBottom) screen.push("");
10875
- if (screen[scrollTop].trim() !== "") {
10876
- scrollback.push(screen[scrollTop]);
10877
- }
10878
- for (let r = scrollTop; r < scrollBottom; r++) {
10879
- screen[r] = screen[r + 1];
10880
- }
10881
- screen[scrollBottom] = "";
10882
- }
10883
- function scrollRegionDown() {
10884
- if (scrollTop === null || scrollBottom === null) {
10885
- return;
10886
- }
10887
- while (screen.length <= scrollBottom) screen.push("");
10888
- if (screen[scrollBottom].trim() !== "") {
10889
- scrollback.push(screen[scrollBottom]);
10890
- }
10891
- for (let r = scrollBottom; r > scrollTop; r--) {
10892
- screen[r] = screen[r - 1];
10893
- }
10894
- screen[scrollTop] = "";
10895
- }
10896
- let i = 0;
10897
- while (i < raw.length) {
10898
- const ch = raw[i];
10899
- if (ch === "\x1B") {
10900
- i++;
10901
- if (i >= raw.length) break;
10902
- if (raw[i] === "[") {
10903
- i++;
10904
- let param = "";
10905
- while (i < raw.length && !/[@-~]/.test(raw[i])) param += raw[i++];
10906
- const cmd = raw[i] ?? "";
10907
- const n = parseInt(param) || 1;
10908
- if (cmd === "A") {
10909
- row = Math.max(0, row - n);
10910
- } else if (cmd === "B") {
10911
- row += n;
10912
- ensureRow();
10913
- } else if (cmd === "C") {
10914
- col += n;
10915
- } else if (cmd === "D") {
10916
- col = Math.max(0, col - n);
10917
- } else if (cmd === "G") {
10918
- col = Math.max(0, n - 1);
10919
- } else if (cmd === "H" || cmd === "f") {
10920
- const p2 = param.split(";");
10921
- row = Math.max(0, (parseInt(p2[0] ?? "1") || 1) - 1);
10922
- col = Math.max(0, (parseInt(p2[1] ?? "1") || 1) - 1);
10923
- ensureRow();
10924
- } else if (cmd === "J") {
10925
- if (param === "2" || param === "3") {
10926
- for (let r = 0; r < screen.length; r++) {
10927
- if (screen[r].trim() !== "") scrollback.push(screen[r]);
10928
- }
10929
- screen.length = 1;
10930
- screen[0] = "";
10931
- row = 0;
10932
- col = 0;
10933
- } else if (param === "1") {
10934
- for (let r = 0; r < row; r++) screen[r] = "";
10935
- screen[row] = " ".repeat(col) + screen[row].slice(col);
10936
- } else {
10937
- screen[row] = screen[row].slice(0, col);
10938
- screen.splice(row + 1);
10939
- }
10940
- } else if (cmd === "K") {
10941
- ensureRow();
10942
- if (param === "" || param === "0") screen[row] = screen[row].slice(0, col);
10943
- else if (param === "1") screen[row] = " ".repeat(col) + screen[row].slice(col);
10944
- else if (param === "2") screen[row] = "";
10945
- } else if (cmd === "r") {
10946
- if (param === "" || param === ";") {
10947
- scrollTop = null;
10948
- scrollBottom = null;
10949
- } else {
10950
- const p2 = param.split(";");
10951
- const top = parseInt(p2[0] ?? "1") || 1;
10952
- const bot = parseInt(p2[1] ?? "1") || 1;
10953
- scrollTop = Math.max(0, top - 1);
10954
- scrollBottom = Math.max(scrollTop, bot - 1);
10955
- }
10956
- }
10957
- } else if (raw[i] === "]") {
10958
- i++;
10959
- while (i < raw.length) {
10960
- if (raw[i] === "\x07") break;
10961
- if (raw[i] === "\x1B" && i + 1 < raw.length && raw[i + 1] === "\\") {
10962
- i++;
10963
- break;
10964
- }
10965
- i++;
10966
- }
10967
- } else if (raw[i] === "M") {
10968
- if (scrollTop !== null && row === scrollTop) {
10969
- scrollRegionDown();
10970
- } else {
10971
- row = Math.max(0, row - 1);
10972
- }
10973
- } else if (raw[i] === "D") {
10974
- if (scrollBottom !== null && row === scrollBottom) {
10975
- scrollRegionUp();
10976
- } else {
10977
- row++;
10978
- ensureRow();
10979
- }
10980
- }
10981
- } else if (ch === "\r") {
10982
- if (i + 1 < raw.length && raw[i + 1] === "\n") {
10983
- if (scrollBottom !== null && row === scrollBottom) {
10984
- scrollRegionUp();
10985
- } else {
10986
- row++;
10987
- ensureRow();
10988
- }
10989
- col = 0;
10990
- i++;
10991
- } else {
10992
- col = 0;
10993
- }
10994
- } else if (ch === "\n") {
10995
- if (scrollBottom !== null && row === scrollBottom) {
10996
- scrollRegionUp();
10997
- } else {
10998
- row++;
10999
- ensureRow();
11000
- }
11001
- col = 0;
11002
- } else if (ch >= " " || ch === " ") {
11003
- writeChar(ch);
11004
- }
11005
- i++;
11006
- }
11007
- return [...scrollback, ...screen];
11008
- }
11009
-
11010
10305
  // src/agents/codex/link.ts
11011
10306
  var import_node_child_process3 = require("child_process");
11012
10307
 
@@ -11172,27 +10467,17 @@ var CodexRuntimeStrategy = class {
11172
10467
  return { ptyInput: "/compact\r" };
11173
10468
  }
11174
10469
  // ─── TUI parser strategy methods ─────────────────────────────────
11175
- /**
11176
- * Codex needs its own virtual terminal because the Codex CLI uses
11177
- * DECSTBM scroll regions (`\x1B[1;31r`) and Reverse Index (`\x1BM`)
11178
- * to scroll chat history within a fixed top zone — bytes the shared
11179
- * renderer drops, leaving the mobile feed with only the most recent
11180
- * frame instead of the full reply. See ./renderer.ts.
11181
- */
11182
- renderToLines(buffer) {
11183
- return renderCodexBuffer(buffer);
11184
- }
11185
- parseTuiChrome(line) {
11186
- return parseCodexChrome(line);
11187
- }
10470
+ // Codex runs ACP-only (see the `requiresAcp` dispatch in start.ts);
10471
+ // the legacy PTY-spawn pipeline never reaches these. Inert stubs to
10472
+ // satisfy the InteractiveAgentStrategy contract the Codex ratatui
10473
+ // parsers + scroll-region renderer were removed with the PTY path.
10474
+ // The optional TUI hooks (renderToLines / parseTuiChrome /
10475
+ // detectReadyPrompt) are dropped entirely.
11188
10476
  filterTuiOutput(lines) {
11189
- return filterCodexChrome(lines);
11190
- }
11191
- detectInteractivePrompt(lines) {
11192
- return detectCodexSelector(lines);
10477
+ return lines;
11193
10478
  }
11194
- detectReadyPrompt(lines) {
11195
- return lines.some((l) => /[│┃]\s*›/u.test(l));
10479
+ detectInteractivePrompt(_lines) {
10480
+ return null;
11196
10481
  }
11197
10482
  credentialLocator() {
11198
10483
  return codexCredentialLocator();
@@ -11977,7 +11262,7 @@ var GeminiRuntimeStrategy = class {
11977
11262
  const binary = this.os.findInPath("gemini");
11978
11263
  if (!binary) {
11979
11264
  throw new Error(
11980
- "Gemini CLI is not on PATH. Install it with:\n npm install -g @google/gemini-cli\n Then run `codeam pair` again.\n\n Tip: set CODEAM_ACP_ENABLED=1 before pairing to use the\n ACP runtime \u2014 it gives mobile typed messages instead of\n raw PTY output for Gemini."
11265
+ "Gemini CLI is not on PATH. Install it with:\n npm install -g @google/gemini-cli\n Then run `codeam pair` again."
11981
11266
  );
11982
11267
  }
11983
11268
  return this.os.buildLaunch(binary);
@@ -12018,11 +11303,10 @@ var GeminiRuntimeStrategy = class {
12018
11303
  return { ptyInput: "/compress\r" };
12019
11304
  }
12020
11305
  /**
12021
- * Pass-through filter. Gemini's TUI chrome isn't worth
12022
- * hand-detectingusers who want clean output should use ACP
12023
- * (`CODEAM_ACP_ENABLED=1`) instead. Returning the input verbatim
12024
- * satisfies the contract's idempotency assertion and keeps mobile
12025
- * showing whatever the REPL prints.
11306
+ * Pass-through filter. Gemini runs over ACP, so this is never hit
11307
+ * in production it exists only to satisfy the contract's
11308
+ * idempotency assertion. Returning the input verbatim keeps the
11309
+ * stub trivially idempotent.
12026
11310
  */
12027
11311
  filterTuiOutput(lines) {
12028
11312
  return lines;
@@ -12172,8 +11456,8 @@ var REGISTRY = {
12172
11456
  // },
12173
11457
  //
12174
11458
  // Until then `getAcpAdapter('cursor')` returns null and the dispatch
12175
- // in start.ts falls back to the legacy PTY runtime — same behaviour
12176
- // cursor users had before ACP was added.
11459
+ // in start.ts runs cursor over the legacy PTY runtime — same
11460
+ // behaviour cursor users had before ACP was added.
12177
11461
  // Gemini speaks ACP natively via `gemini --acp` — no npm adapter
12178
11462
  // package, just the user-installed `gemini` binary on PATH. Same
12179
11463
  // {@link AdapterSpec} shape; the only difference is `command` is
@@ -12196,6 +11480,9 @@ function getAcpAdapter(agent) {
12196
11480
  const factory = REGISTRY[agent];
12197
11481
  return factory ? factory() : null;
12198
11482
  }
11483
+ function requiresAcp(agent) {
11484
+ return getAcpAdapter(agent) !== null;
11485
+ }
12199
11486
 
12200
11487
  // src/agents/acp/runner.ts
12201
11488
  var import_node_crypto7 = require("crypto");
@@ -24041,28 +23328,28 @@ async function start(requestedAgent) {
24041
23328
  `agent-spawn gate released \u2014 beads ${beads ? "ready" : "pending"}; project deps provisioned`
24042
23329
  );
24043
23330
  }
24044
- const acpDisabled = process.env.CODEAM_ACP_DISABLED === "1";
24045
- if (!acpDisabled && session.pluginAuthToken) {
23331
+ if (requiresAcp(session.agent)) {
24046
23332
  const adapter = getAcpAdapter(session.agent);
24047
- if (adapter) {
24048
- await runAcpSession({
24049
- agent: session.agent,
24050
- sessionId: session.id,
24051
- pluginId,
24052
- pluginAuthToken: session.pluginAuthToken,
24053
- adapter,
24054
- cwd,
24055
- getBeads,
24056
- // AUTO mode in a headless GitHub Codespace: no human at the phone to
24057
- // answer permission prompts, so auto-approve them rather than stall the
24058
- // turn (the agent-agnostic equivalent of --dangerously-skip-permissions).
24059
- autoApprovePermissions: process.env.CODESPACES === "true"
24060
- });
24061
- return;
23333
+ if (!adapter || !session.pluginAuthToken) {
23334
+ showError(
23335
+ `${AGENT_REGISTRY[session.agent].displayName} requires a paired session with an auth token. Re-pair with \`codeam pair\` to continue.`
23336
+ );
23337
+ process.exit(1);
24062
23338
  }
24063
- }
24064
- if (acpDisabled) {
24065
- showInfo("CODEAM_ACP_DISABLED is set \u2014 running the legacy PTY pipeline.");
23339
+ await runAcpSession({
23340
+ agent: session.agent,
23341
+ sessionId: session.id,
23342
+ pluginId,
23343
+ pluginAuthToken: session.pluginAuthToken,
23344
+ adapter,
23345
+ cwd,
23346
+ getBeads,
23347
+ // AUTO mode in a headless GitHub Codespace: no human at the phone to
23348
+ // answer permission prompts, so auto-approve them rather than stall the
23349
+ // turn (the agent-agnostic equivalent of --dangerously-skip-permissions).
23350
+ autoApprovePermissions: process.env.CODESPACES === "true"
23351
+ });
23352
+ return;
24066
23353
  }
24067
23354
  const runtime = createRuntimeStrategy(session.agent);
24068
23355
  const historySvc = new HistoryService(runtime, pluginId, cwd, {
@@ -27069,7 +26356,7 @@ function checkChokidar() {
27069
26356
  }
27070
26357
  async function doctor(args2 = []) {
27071
26358
  const json = args2.includes("--json");
27072
- const cliVersion = true ? "2.39.22" : "0.0.0-dev";
26359
+ const cliVersion = true ? "2.39.24" : "0.0.0-dev";
27073
26360
  const apiBase = resolveApiBaseUrl();
27074
26361
  const diagnosticId = (0, import_node_crypto8.randomUUID)();
27075
26362
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -27268,7 +26555,7 @@ async function completion(args2) {
27268
26555
  // src/commands/version.ts
27269
26556
  var import_picocolors13 = __toESM(require("picocolors"));
27270
26557
  function version2() {
27271
- const v = true ? "2.39.22" : "unknown";
26558
+ const v = true ? "2.39.24" : "unknown";
27272
26559
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
27273
26560
  }
27274
26561
 
@@ -27554,7 +26841,7 @@ function checkForUpdates() {
27554
26841
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
27555
26842
  if (process.env.CI) return;
27556
26843
  if (!process.stdout.isTTY) return;
27557
- const current = true ? "2.39.22" : null;
26844
+ const current = true ? "2.39.24" : null;
27558
26845
  if (!current) return;
27559
26846
  const cache = readCache();
27560
26847
  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.39.22",
3
+ "version": "2.39.24",
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",
@@ -67,7 +67,7 @@
67
67
  "url": "https://github.com/sponsors/edgar-durand"
68
68
  },
69
69
  "engines": {
70
- "node": ">=18.0.0"
70
+ "node": ">=20.0.0"
71
71
  },
72
72
  "dependencies": {
73
73
  "@agentclientprotocol/claude-agent-acp": "^0.42.0",
@@ -78,7 +78,7 @@
78
78
  "ignore": "^5.3.2",
79
79
  "picocolors": "^1.1.0",
80
80
  "qrcode-terminal": "^0.12.0",
81
- "which": "^5.0.0",
81
+ "which": "^6.0.0",
82
82
  "ws": "^8.18.0",
83
83
  "zod": "^4.3.6"
84
84
  },