codeam-cli 2.63.1 → 2.65.0

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 +27 -0
  2. package/dist/index.js +1379 -450
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2789,6 +2789,26 @@ function resolveApiBaseUrl() {
2789
2789
  return DEFAULT_API_BASE_URL;
2790
2790
  }
2791
2791
 
2792
+ // ../../packages/shared/src/types/agent-squad.ts
2793
+ var HANDOFF_FENCE_TAG = "codeam-handoff";
2794
+ var SQUAD_CONFIGURE_COMMAND = "squad_configure";
2795
+ var SQUAD_STATS_COMMAND = "squad_stats";
2796
+ var SQUAD_HOP_BUDGET_DEFAULT = 3;
2797
+ var SQUAD_HOP_BUDGET_MIN = 1;
2798
+ var SQUAD_HOP_BUDGET_MAX = 10;
2799
+ function clampHopBudget(value) {
2800
+ if (typeof value !== "number" || !Number.isFinite(value)) return SQUAD_HOP_BUDGET_DEFAULT;
2801
+ return Math.min(SQUAD_HOP_BUDGET_MAX, Math.max(SQUAD_HOP_BUDGET_MIN, Math.round(value)));
2802
+ }
2803
+ var SQUAD_SPECIALTIES = {
2804
+ claude: "deep reasoning, refactors, and multi-step architecture work",
2805
+ codex: "fast, focused implementation and test fixing",
2806
+ cursor: "multi-file edits and codebase-wide changes",
2807
+ gemini: "large-context analysis across big files and logs",
2808
+ kimi: "long-context code reading and summarization",
2809
+ opencode: "general implementation tasks"
2810
+ };
2811
+
2792
2812
  // ../../packages/shared/src/headroom/manifest.ts
2793
2813
  var HEADROOM_PROXY_PORT = 8787;
2794
2814
  var HEADROOM_PIP_COMPANIONS = [
@@ -2921,6 +2941,9 @@ var USER_EVENTS = {
2921
2941
  // the backend re-publishes them on the per-user SSE bus (mirrored in repo A).
2922
2942
  SWITCH_AGENT_PROGRESS: "switch_agent_progress",
2923
2943
  SWITCH_AGENT_STATUS: "switch_agent_status",
2944
+ // Agent Squad — the CLI posts these to /api/agent-switch/events; PRO-gated.
2945
+ HANDOFF_PROPOSED: "handoff_proposed",
2946
+ HANDOFF_RESOLVED: "handoff_resolved",
2924
2947
  // VCS / PR Command Center — the backend publishes this after an agent finishes
2925
2948
  // reviewing a PR (verdict + comment count + findings), driving the mobile
2926
2949
  // completion screen + push. Mirrored in repo A's app-shared events.ts.
@@ -3125,11 +3148,11 @@ function quiet(fn) {
3125
3148
  log.debug(TAG, "ignored sync error", err);
3126
3149
  }
3127
3150
  }
3128
- function rmIfExistsQuiet(path90) {
3151
+ function rmIfExistsQuiet(path91) {
3129
3152
  try {
3130
- fs2.rmSync(path90, { force: true });
3153
+ fs2.rmSync(path91, { force: true });
3131
3154
  } catch (err) {
3132
- log.debug(TAG, `rmIfExists failed for ${path90}`, err);
3155
+ log.debug(TAG, `rmIfExists failed for ${path91}`, err);
3133
3156
  }
3134
3157
  }
3135
3158
  function killQuiet(target, signal = "SIGTERM") {
@@ -3243,6 +3266,22 @@ function makeConfig(baseDir) {
3243
3266
  s.agent = agent;
3244
3267
  save(c2);
3245
3268
  }
3269
+ function setSquadAuto2(pluginId, value) {
3270
+ const stored = {
3271
+ enabled: value.enabled === true,
3272
+ hopBudget: clampHopBudget(value.hopBudget)
3273
+ };
3274
+ const c2 = load();
3275
+ const s = c2.sessions.find((x) => x.pluginId === pluginId);
3276
+ if (!s) return null;
3277
+ s.squadAuto = stored;
3278
+ save(c2);
3279
+ return stored;
3280
+ }
3281
+ function getSquadAuto2(pluginId) {
3282
+ const s = load().sessions.find((x) => x.pluginId === pluginId);
3283
+ return s?.squadAuto ?? null;
3284
+ }
3246
3285
  function clearAll2() {
3247
3286
  try {
3248
3287
  fs3.unlinkSync(file);
@@ -3255,7 +3294,7 @@ function makeConfig(baseDir) {
3255
3294
  function loadCliConfig2() {
3256
3295
  return load();
3257
3296
  }
3258
- return { getConfig: getConfig2, ensurePluginId: ensurePluginId2, addSession: addSession2, removeSession: removeSession2, setActiveSession: setActiveSession2, getActiveSession: getActiveSession2, getActiveSessionForAgent: getActiveSessionForAgent2, setDisable1mContext: setDisable1mContext2, setSessionAgent: setSessionAgent2, clearAll: clearAll2, saveCliConfig: saveCliConfig2, loadCliConfig: loadCliConfig2 };
3297
+ return { getConfig: getConfig2, ensurePluginId: ensurePluginId2, addSession: addSession2, removeSession: removeSession2, setActiveSession: setActiveSession2, getActiveSession: getActiveSession2, getActiveSessionForAgent: getActiveSessionForAgent2, setDisable1mContext: setDisable1mContext2, setSessionAgent: setSessionAgent2, setSquadAuto: setSquadAuto2, getSquadAuto: getSquadAuto2, clearAll: clearAll2, saveCliConfig: saveCliConfig2, loadCliConfig: loadCliConfig2 };
3259
3298
  }
3260
3299
  var CODESPACE_ENV_KEYS = [
3261
3300
  "PREVIEW_TUNNEL_TOKEN",
@@ -3279,7 +3318,7 @@ function loadCodespaceEnv() {
3279
3318
  }
3280
3319
  }
3281
3320
  var _default = makeConfig();
3282
- var { getConfig, ensurePluginId, addSession, removeSession, setActiveSession, getActiveSession, getActiveSessionForAgent, setDisable1mContext, setSessionAgent, clearAll, saveCliConfig, loadCliConfig } = _default;
3321
+ var { getConfig, ensurePluginId, addSession, removeSession, setActiveSession, getActiveSession, getActiveSessionForAgent, setDisable1mContext, setSessionAgent, setSquadAuto, getSquadAuto, clearAll, saveCliConfig, loadCliConfig } = _default;
3283
3322
 
3284
3323
  // src/commands/pair-auto.ts
3285
3324
  var fs63 = __toESM(require("fs"));
@@ -3320,8 +3359,8 @@ function createGetModuleFromFilename(basePath = process.argv[1] ? (0, import_pat
3320
3359
  return decodedFile;
3321
3360
  };
3322
3361
  }
3323
- function normalizeWindowsPath(path90) {
3324
- return path90.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
3362
+ function normalizeWindowsPath(path91) {
3363
+ return path91.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
3325
3364
  }
3326
3365
 
3327
3366
  // ../../node_modules/@posthog/core/dist/featureFlagUtils.mjs
@@ -5801,9 +5840,9 @@ async function addSourceContext(frames) {
5801
5840
  LRU_FILE_CONTENTS_CACHE.reduce();
5802
5841
  return frames;
5803
5842
  }
5804
- function getContextLinesFromFile(path90, ranges, output) {
5843
+ function getContextLinesFromFile(path91, ranges, output) {
5805
5844
  return new Promise((resolve9) => {
5806
- const stream = (0, import_node_fs.createReadStream)(path90);
5845
+ const stream = (0, import_node_fs.createReadStream)(path91);
5807
5846
  const lineReaded = (0, import_node_readline.createInterface)({
5808
5847
  input: stream
5809
5848
  });
@@ -5818,7 +5857,7 @@ function getContextLinesFromFile(path90, ranges, output) {
5818
5857
  let rangeStart = range[0];
5819
5858
  let rangeEnd = range[1];
5820
5859
  function onStreamError() {
5821
- LRU_FILE_CONTENTS_FS_READ_FAILED.set(path90, 1);
5860
+ LRU_FILE_CONTENTS_FS_READ_FAILED.set(path91, 1);
5822
5861
  lineReaded.close();
5823
5862
  lineReaded.removeAllListeners();
5824
5863
  destroyStreamAndResolve();
@@ -5879,8 +5918,8 @@ function clearLineContext(frame) {
5879
5918
  delete frame.context_line;
5880
5919
  delete frame.post_context;
5881
5920
  }
5882
- function shouldSkipContextLinesForFile(path90) {
5883
- return path90.startsWith("node:") || path90.endsWith(".min.js") || path90.endsWith(".min.cjs") || path90.endsWith(".min.mjs") || path90.startsWith("data:");
5921
+ function shouldSkipContextLinesForFile(path91) {
5922
+ return path91.startsWith("node:") || path91.endsWith(".min.js") || path91.endsWith(".min.cjs") || path91.endsWith(".min.mjs") || path91.startsWith("data:");
5884
5923
  }
5885
5924
  function shouldSkipContextLinesForFrame(frame) {
5886
5925
  if (void 0 !== frame.lineno && frame.lineno > MAX_CONTEXTLINES_LINENO) return true;
@@ -8034,7 +8073,7 @@ function readAnonId() {
8034
8073
  }
8035
8074
  function superProperties() {
8036
8075
  return {
8037
- cliVersion: true ? "2.63.1" : "0.0.0-dev",
8076
+ cliVersion: true ? "2.65.0" : "0.0.0-dev",
8038
8077
  nodeVersion: process.version,
8039
8078
  platform: process.platform,
8040
8079
  arch: process.arch,
@@ -8215,7 +8254,7 @@ var os4 = __toESM(require("os"));
8215
8254
  // package.json
8216
8255
  var package_default = {
8217
8256
  name: "codeam-cli",
8218
- version: "2.63.1",
8257
+ version: "2.65.0",
8219
8258
  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.",
8220
8259
  type: "commonjs",
8221
8260
  main: "dist/index.js",
@@ -8611,6 +8650,20 @@ async function fetchProvisionCredential(input) {
8611
8650
  return null;
8612
8651
  }
8613
8652
  }
8653
+ async function fetchSquadRoster(input) {
8654
+ try {
8655
+ const res = await _transport.postJsonAuthed(
8656
+ `${API_BASE}/api/plugin/agents/roster`,
8657
+ { sessionId: input.sessionId, pluginId: input.pluginId },
8658
+ input.pluginAuthToken
8659
+ );
8660
+ const data = res?.data;
8661
+ if (!data || !Array.isArray(data.agents)) return null;
8662
+ return { agents: data.agents, handoffsEnabled: data.handoffsEnabled === true };
8663
+ } catch {
8664
+ return null;
8665
+ }
8666
+ }
8614
8667
  async function postAgentSwitchEvent(input) {
8615
8668
  try {
8616
8669
  await _transport.postJsonAuthed(
@@ -8773,9 +8826,7 @@ async function _postJsonAuthed(url2, body, pluginAuthToken) {
8773
8826
  });
8774
8827
  res.on("end", () => {
8775
8828
  if (res.statusCode && res.statusCode >= 400) {
8776
- reject(
8777
- makeHttpError(res.statusCode, res.headers["retry-after"], responseBody)
8778
- );
8829
+ reject(makeHttpError(res.statusCode, res.headers["retry-after"], responseBody));
8779
8830
  return;
8780
8831
  }
8781
8832
  try {
@@ -9655,7 +9706,7 @@ var CommandRelayService = class _CommandRelayService {
9655
9706
  // fresh + clear the "CLI update available" banner after a self-update
9656
9707
  // (a codespace that reinstalls @latest reconnects via heartbeat, not
9657
9708
  // pair/reconnect). Older backends ignore the extra field.
9658
- ..."2.63.1" ? { ideVersion: "2.63.1" } : {}
9709
+ ..."2.65.0" ? { ideVersion: "2.65.0" } : {}
9659
9710
  }).then(() => log.trace("relay", `heartbeat ok online=${online}`)).catch((err) => log.trace("relay", `heartbeat failed online=${online}`, err));
9660
9711
  }
9661
9712
  /**
@@ -9680,7 +9731,8 @@ var CommandRelayService = class _CommandRelayService {
9680
9731
  ];
9681
9732
  _postJson(`${API_BASE2}/api/plugin/agents`, {
9682
9733
  pluginId: this.pluginId,
9683
- agents
9734
+ agents,
9735
+ capabilities: { squad: true }
9684
9736
  }).then(() => {
9685
9737
  this.agentsRegistered = true;
9686
9738
  }).catch(() => {
@@ -9899,10 +9951,10 @@ var WINDOWS_LEGACY_JUNCTIONS = [
9899
9951
  /[\\/]Start Menu([\\/]|$)/i,
9900
9952
  /[\\/]Templates([\\/]|$)/i
9901
9953
  ];
9902
- function isUnsafeWindowsWatchRoot(dir, homedir52) {
9954
+ function isUnsafeWindowsWatchRoot(dir, homedir53) {
9903
9955
  const norm = (p2) => p2.replace(/\//g, "\\").replace(/\\+$/, "").toLowerCase();
9904
9956
  const cwd = norm(dir);
9905
- const home = norm(homedir52);
9957
+ const home = norm(homedir53);
9906
9958
  if (cwd === home) return true;
9907
9959
  if (/^[a-z]:$/.test(cwd)) return true;
9908
9960
  const sysRoots = [
@@ -14558,10 +14610,10 @@ function buildForPlatform(platform3) {
14558
14610
  var import_node_crypto4 = require("crypto");
14559
14611
 
14560
14612
  // src/agents/claude/resolver.ts
14561
- function buildClaudeLaunch(extraArgs = [], os65 = createOsStrategy()) {
14562
- const found = os65.findInPath("claude") ?? os65.findInPath("claude-code");
14613
+ function buildClaudeLaunch(extraArgs = [], os66 = createOsStrategy()) {
14614
+ const found = os66.findInPath("claude") ?? os66.findInPath("claude-code");
14563
14615
  if (!found) return null;
14564
- return os65.buildLaunch(found, extraArgs);
14616
+ return os66.buildLaunch(found, extraArgs);
14565
14617
  }
14566
14618
 
14567
14619
  // src/agents/claude/installer.ts
@@ -15151,8 +15203,8 @@ var ClaudeRuntimeStrategy = class {
15151
15203
  meta = getAgent("claude");
15152
15204
  mode = "interactive";
15153
15205
  os;
15154
- constructor(os65) {
15155
- this.os = os65;
15206
+ constructor(os66) {
15207
+ this.os = os66;
15156
15208
  }
15157
15209
  /**
15158
15210
  * Claude Code's react-ink TUI enables bracketed-paste mode at
@@ -15932,8 +15984,8 @@ function codexCredentialLocator() {
15932
15984
  function codexLoginLauncher() {
15933
15985
  return {
15934
15986
  async ensureInstalled() {
15935
- const os65 = createOsStrategy();
15936
- return os65.findInPath("codex") !== null;
15987
+ const os66 = createOsStrategy();
15988
+ return os66.findInPath("codex") !== null;
15937
15989
  },
15938
15990
  launch() {
15939
15991
  return (0, import_node_child_process4.spawn)("codex", ["login"], { stdio: "inherit" });
@@ -15956,8 +16008,8 @@ var CodexRuntimeStrategy = class {
15956
16008
  meta = getAgent("codex");
15957
16009
  mode = "interactive";
15958
16010
  os;
15959
- constructor(os65) {
15960
- this.os = os65;
16011
+ constructor(os66) {
16012
+ this.os = os66;
15961
16013
  }
15962
16014
  async prepareLaunch() {
15963
16015
  let binary = this.os.findInPath("codex");
@@ -16066,12 +16118,12 @@ var CodexRuntimeStrategy = class {
16066
16118
  });
16067
16119
  }
16068
16120
  };
16069
- function resolveNpm(os65) {
16070
- return os65.id === "win32" ? "npm.cmd" : "npm";
16121
+ function resolveNpm(os66) {
16122
+ return os66.id === "win32" ? "npm.cmd" : "npm";
16071
16123
  }
16072
- async function installCodexViaNpm(os65) {
16124
+ async function installCodexViaNpm(os66) {
16073
16125
  return new Promise((resolve9, reject) => {
16074
- const proc = (0, import_node_child_process5.spawn)(resolveNpm(os65), ["install", "-g", "@openai/codex"], {
16126
+ const proc = (0, import_node_child_process5.spawn)(resolveNpm(os66), ["install", "-g", "@openai/codex"], {
16075
16127
  stdio: "inherit"
16076
16128
  });
16077
16129
  proc.on("close", (code) => {
@@ -16088,16 +16140,16 @@ async function installCodexViaNpm(os65) {
16088
16140
  });
16089
16141
  });
16090
16142
  }
16091
- function augmentNpmGlobalBin(os65) {
16143
+ function augmentNpmGlobalBin(os66) {
16092
16144
  try {
16093
- const result = (0, import_node_child_process5.spawnSync)(resolveNpm(os65), ["prefix", "-g"], {
16145
+ const result = (0, import_node_child_process5.spawnSync)(resolveNpm(os66), ["prefix", "-g"], {
16094
16146
  stdio: ["ignore", "pipe", "ignore"]
16095
16147
  });
16096
16148
  if (result.status !== 0) return;
16097
16149
  const prefix = result.stdout.toString().trim();
16098
16150
  if (!prefix) return;
16099
- const binDir = os65.id === "win32" ? prefix : path25.join(prefix, "bin");
16100
- os65.augmentPath([binDir]);
16151
+ const binDir = os66.id === "win32" ? prefix : path25.join(prefix, "bin");
16152
+ os66.augmentPath([binDir]);
16101
16153
  } catch {
16102
16154
  }
16103
16155
  }
@@ -16496,10 +16548,10 @@ function manualInstallHint(runner, tools) {
16496
16548
  const argv = osPackageInstallArgv(detectPackageManager(runner), [...tools]);
16497
16549
  return argv ? `\`sudo ${argv.join(" ")}\`` : `your package manager (e.g. \`${pkgs}\`)`;
16498
16550
  }
16499
- function writeUnzipShim(os65, runner) {
16551
+ function writeUnzipShim(os66, runner) {
16500
16552
  const python = ["python3", "python"].find((p2) => runner.which(p2));
16501
16553
  if (!python) return null;
16502
- const dir = os65.scratchPath("codeam-cr-prereq");
16554
+ const dir = os66.scratchPath("codeam-cr-prereq");
16503
16555
  const shim = path27.join(dir, "unzip");
16504
16556
  const script = `#!/bin/sh
16505
16557
  set -e
@@ -16521,9 +16573,9 @@ exec ${python} -m zipfile -e "$archive" "$dest"
16521
16573
  (0, import_node_fs5.chmodSync)(shim, 448);
16522
16574
  return dir;
16523
16575
  }
16524
- async function ensureInstallPrerequisites(os65, deps = {}) {
16576
+ async function ensureInstallPrerequisites(os66, deps = {}) {
16525
16577
  const runner = deps.runner ?? defaultHeadroomRunner;
16526
- const missing = REQUIRED_TOOLS.filter((t2) => os65.findInPath(t2) === null);
16578
+ const missing = REQUIRED_TOOLS.filter((t2) => os66.findInPath(t2) === null);
16527
16579
  if (missing.length === 0) return { ok: true, extraPath: [] };
16528
16580
  log.info(
16529
16581
  "coderabbit",
@@ -16542,10 +16594,10 @@ async function ensureInstallPrerequisites(os65, deps = {}) {
16542
16594
  );
16543
16595
  }
16544
16596
  }
16545
- const stillMissing = REQUIRED_TOOLS.filter((t2) => os65.findInPath(t2) === null);
16597
+ const stillMissing = REQUIRED_TOOLS.filter((t2) => os66.findInPath(t2) === null);
16546
16598
  if (stillMissing.length === 0) return { ok: true, extraPath: [] };
16547
16599
  if (stillMissing.length === 1 && stillMissing[0] === "unzip") {
16548
- const shimDir = writeUnzipShim(os65, runner);
16600
+ const shimDir = writeUnzipShim(os66, runner);
16549
16601
  if (shimDir) {
16550
16602
  log.info("coderabbit", "unzip is unavailable \u2014 using a scoped python zipfile shim");
16551
16603
  return { ok: true, extraPath: [shimDir] };
@@ -16608,15 +16660,15 @@ function summarizeInstallFailure(output) {
16608
16660
  }
16609
16661
  return lines.length > 0 ? lines[lines.length - 1] : null;
16610
16662
  }
16611
- async function ensureCoderabbitInstalled(os65, deps = {}) {
16612
- if (os65.findInPath("coderabbit")) return { ok: true };
16613
- if (os65.id === "win32") {
16663
+ async function ensureCoderabbitInstalled(os66, deps = {}) {
16664
+ if (os66.findInPath("coderabbit")) return { ok: true };
16665
+ if (os66.id === "win32") {
16614
16666
  return {
16615
16667
  ok: false,
16616
16668
  error: "CodeRabbit on Windows requires WSL. Install the CLI inside your WSL distribution (curl -fsSL https://cli.coderabbit.ai/install.sh | sh), then try again."
16617
16669
  };
16618
16670
  }
16619
- const prereq = await ensureInstallPrerequisites(os65, deps);
16671
+ const prereq = await ensureInstallPrerequisites(os66, deps);
16620
16672
  if (!prereq.ok) return { ok: false, error: prereq.error };
16621
16673
  const env = { ...process.env };
16622
16674
  if (prereq.extraPath.length > 0) {
@@ -16632,8 +16684,8 @@ async function ensureCoderabbitInstalled(os65, deps = {}) {
16632
16684
  error: detail ? `CodeRabbit CLI install failed: ${detail}` : "CodeRabbit CLI install failed \u2014 check this machine's network egress and try again."
16633
16685
  };
16634
16686
  }
16635
- os65.augmentPath([`${os65.homeDir()}/.local/bin`, "/opt/homebrew/bin"]);
16636
- if (os65.findInPath("coderabbit") === null) {
16687
+ os66.augmentPath([`${os66.homeDir()}/.local/bin`, "/opt/homebrew/bin"]);
16688
+ if (os66.findInPath("coderabbit") === null) {
16637
16689
  const detail = summarizeInstallFailure(output);
16638
16690
  return {
16639
16691
  ok: false,
@@ -16675,10 +16727,10 @@ function coderabbitCredentialLocator() {
16675
16727
  validate: validateNonEmptyCredential
16676
16728
  };
16677
16729
  }
16678
- function coderabbitLoginLauncher(os65) {
16730
+ function coderabbitLoginLauncher(os66) {
16679
16731
  return {
16680
16732
  async ensureInstalled() {
16681
- const result = await ensureCoderabbitInstalled(os65);
16733
+ const result = await ensureCoderabbitInstalled(os66);
16682
16734
  if (!result.ok && result.error) console.error(`
16683
16735
  \u2717 ${result.error}
16684
16736
  `);
@@ -16738,8 +16790,8 @@ function pickLine(obj) {
16738
16790
  function toHunk(raw, groupSeverity) {
16739
16791
  if (!raw || typeof raw !== "object") return null;
16740
16792
  const o = raw;
16741
- const path90 = asString(pick(o, ["file_path", "filePath", "file", "path", "filename", "fileName"])) ?? asString(pick(o, ["location"])?.path);
16742
- if (!path90) return null;
16793
+ const path91 = asString(pick(o, ["file_path", "filePath", "file", "path", "filename", "fileName"])) ?? asString(pick(o, ["location"])?.path);
16794
+ if (!path91) return null;
16743
16795
  const message = asString(
16744
16796
  pick(o, [
16745
16797
  "comment",
@@ -16756,7 +16808,7 @@ function toHunk(raw, groupSeverity) {
16756
16808
  const severity = normSeverity(pick(o, ["severity", "level", "priority", "impact"])) ?? normSeverity(groupSeverity);
16757
16809
  const locObj = pick(o, ["location"]) ?? o;
16758
16810
  return {
16759
- path: path90.trim(),
16811
+ path: path91.trim(),
16760
16812
  line: pickLine(o) ?? pickLine(locObj),
16761
16813
  severity,
16762
16814
  message: (title && message ? `${title}: ${message}` : title || message).trim() || "(no message)"
@@ -16839,10 +16891,10 @@ function parsePlain(stdout) {
16839
16891
  for (const line of stdout.split(/\r?\n/)) {
16840
16892
  const m = line.match(HUNK_LINE_RE);
16841
16893
  if (!m) continue;
16842
- const [, path90, lineNo, sevToken, message] = m;
16843
- if (!path90 || !lineNo || !message) continue;
16894
+ const [, path91, lineNo, sevToken, message] = m;
16895
+ if (!path91 || !lineNo || !message) continue;
16844
16896
  hunks.push({
16845
- path: path90.trim(),
16897
+ path: path91.trim(),
16846
16898
  line: Number(lineNo),
16847
16899
  severity: sevToken ? SEVERITY_MAP[sevToken.toLowerCase()] : void 0,
16848
16900
  message: message.trim().replace(/^[*-]\s+/, "")
@@ -16902,8 +16954,8 @@ var CoderabbitRuntimeStrategy = class {
16902
16954
  meta = getAgent("coderabbit");
16903
16955
  mode = "batch";
16904
16956
  os;
16905
- constructor(os65) {
16906
- this.os = os65;
16957
+ constructor(os66) {
16958
+ this.os = os66;
16907
16959
  }
16908
16960
  getDefaultArgs() {
16909
16961
  return ["review", "--agent"];
@@ -17174,10 +17226,10 @@ function cursorCredentialLocator() {
17174
17226
  validate: validateNonEmptyCredential
17175
17227
  };
17176
17228
  }
17177
- function cursorLoginLauncher(os65) {
17229
+ function cursorLoginLauncher(os66) {
17178
17230
  return {
17179
17231
  async ensureInstalled() {
17180
- if (os65.findInPath("cursor-agent")) return true;
17232
+ if (os66.findInPath("cursor-agent")) return true;
17181
17233
  console.error(
17182
17234
  "\n \u2717 cursor-agent binary not on PATH.\n Install Cursor (https://cursor.com/) and ensure the CLI\n plugin is enabled, then re-run `codeam link cursor`.\n"
17183
17235
  );
@@ -17241,8 +17293,8 @@ var CursorRuntimeStrategy = class {
17241
17293
  meta = getAgent("cursor");
17242
17294
  mode = "interactive";
17243
17295
  os;
17244
- constructor(os65) {
17245
- this.os = os65;
17296
+ constructor(os66) {
17297
+ this.os = os66;
17246
17298
  }
17247
17299
  async prepareLaunch() {
17248
17300
  const binary = this.os.findInPath("cursor-agent");
@@ -17458,10 +17510,10 @@ function aiderCredentialLocator() {
17458
17510
  validate: validateNonEmptyCredential
17459
17511
  };
17460
17512
  }
17461
- function aiderLoginLauncher(os65) {
17513
+ function aiderLoginLauncher(os66) {
17462
17514
  return {
17463
17515
  async ensureInstalled() {
17464
- if (os65.findInPath("aider")) return true;
17516
+ if (os66.findInPath("aider")) return true;
17465
17517
  console.error(
17466
17518
  "\n \u2717 aider binary not on PATH.\n Install Aider:\n pip install aider-chat\n then re-run `codeam link aider`.\n"
17467
17519
  );
@@ -17471,7 +17523,7 @@ function aiderLoginLauncher(os65) {
17471
17523
  console.error(
17472
17524
  "\n Aider has no interactive login flow.\n Set ANTHROPIC_API_KEY or OPENAI_API_KEY in your shell,\n or re-run `codeam link aider --api-key=<your-key>`.\n"
17473
17525
  );
17474
- return (0, import_node_child_process12.spawn)(os65.id === "win32" ? "cmd.exe" : "sh", os65.id === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"], {
17526
+ return (0, import_node_child_process12.spawn)(os66.id === "win32" ? "cmd.exe" : "sh", os66.id === "win32" ? ["/c", "exit", "0"] : ["-c", "exit 0"], {
17475
17527
  stdio: "ignore"
17476
17528
  });
17477
17529
  }
@@ -17543,8 +17595,8 @@ var AiderRuntimeStrategy = class {
17543
17595
  meta = getAgent("aider");
17544
17596
  mode = "interactive";
17545
17597
  os;
17546
- constructor(os65) {
17547
- this.os = os65;
17598
+ constructor(os66) {
17599
+ this.os = os66;
17548
17600
  }
17549
17601
  async prepareLaunch() {
17550
17602
  const binary = this.os.findInPath("aider");
@@ -17676,8 +17728,8 @@ function geminiCredentialLocator() {
17676
17728
  function geminiLoginLauncher() {
17677
17729
  return {
17678
17730
  async ensureInstalled() {
17679
- const os65 = createOsStrategy();
17680
- return os65.findInPath("gemini") !== null;
17731
+ const os66 = createOsStrategy();
17732
+ return os66.findInPath("gemini") !== null;
17681
17733
  },
17682
17734
  launch() {
17683
17735
  return (0, import_node_child_process13.spawn)("gemini", ["auth", "login"], { stdio: "inherit" });
@@ -17867,8 +17919,8 @@ var GeminiRuntimeStrategy = class {
17867
17919
  meta = getAgent("gemini");
17868
17920
  mode = "interactive";
17869
17921
  os;
17870
- constructor(os65) {
17871
- this.os = os65;
17922
+ constructor(os66) {
17923
+ this.os = os66;
17872
17924
  }
17873
17925
  async prepareLaunch() {
17874
17926
  const binary = this.os.findInPath("gemini");
@@ -18159,8 +18211,8 @@ var KimiRuntimeStrategy = class {
18159
18211
  meta = getAgent("kimi");
18160
18212
  mode = "interactive";
18161
18213
  os;
18162
- constructor(os65) {
18163
- this.os = os65;
18214
+ constructor(os66) {
18215
+ this.os = os66;
18164
18216
  }
18165
18217
  async prepareLaunch() {
18166
18218
  const binary = this.os.findInPath("kimi");
@@ -18302,8 +18354,8 @@ var OpencodeRuntimeStrategy = class {
18302
18354
  meta = getAgent("opencode");
18303
18355
  mode = "interactive";
18304
18356
  os;
18305
- constructor(os65) {
18306
- this.os = os65;
18357
+ constructor(os66) {
18358
+ this.os = os66;
18307
18359
  }
18308
18360
  async prepareLaunch() {
18309
18361
  const binary = this.os.findInPath("opencode");
@@ -18387,20 +18439,20 @@ var OpencodeRuntimeStrategy = class {
18387
18439
 
18388
18440
  // src/agents/registry.ts
18389
18441
  var runtimeBuilders = {
18390
- claude: (os65) => new ClaudeRuntimeStrategy(os65),
18391
- codex: (os65) => new CodexRuntimeStrategy(os65),
18392
- coderabbit: (os65) => new CoderabbitRuntimeStrategy(os65),
18393
- cursor: (os65) => new CursorRuntimeStrategy(os65),
18394
- aider: (os65) => new AiderRuntimeStrategy(os65),
18395
- gemini: (os65) => new GeminiRuntimeStrategy(os65),
18396
- kimi: (os65) => new KimiRuntimeStrategy(os65),
18397
- opencode: (os65) => new OpencodeRuntimeStrategy(os65)
18442
+ claude: (os66) => new ClaudeRuntimeStrategy(os66),
18443
+ codex: (os66) => new CodexRuntimeStrategy(os66),
18444
+ coderabbit: (os66) => new CoderabbitRuntimeStrategy(os66),
18445
+ cursor: (os66) => new CursorRuntimeStrategy(os66),
18446
+ aider: (os66) => new AiderRuntimeStrategy(os66),
18447
+ gemini: (os66) => new GeminiRuntimeStrategy(os66),
18448
+ kimi: (os66) => new KimiRuntimeStrategy(os66),
18449
+ opencode: (os66) => new OpencodeRuntimeStrategy(os66)
18398
18450
  };
18399
18451
  var deployBuilders = {
18400
18452
  claude: () => new ClaudeDeployStrategy(),
18401
18453
  codex: () => new CodexDeployStrategy()
18402
18454
  };
18403
- function createAgentStrategy(agent, os65 = createOsStrategy()) {
18455
+ function createAgentStrategy(agent, os66 = createOsStrategy()) {
18404
18456
  if (!AGENT_REGISTRY[agent]?.enabled) {
18405
18457
  throw new Error(
18406
18458
  `Agent "${agent}" is not supported in this codeam-cli version. Upgrade with 'npm i -g codeam-cli@latest'.`
@@ -18410,10 +18462,10 @@ function createAgentStrategy(agent, os65 = createOsStrategy()) {
18410
18462
  if (!build) {
18411
18463
  throw new Error(`No runtime strategy registered for agent "${agent}"`);
18412
18464
  }
18413
- return build(os65);
18465
+ return build(os66);
18414
18466
  }
18415
- function createInteractiveAgentStrategy(agent, os65 = createOsStrategy()) {
18416
- const s = createAgentStrategy(agent, os65);
18467
+ function createInteractiveAgentStrategy(agent, os66 = createOsStrategy()) {
18468
+ const s = createAgentStrategy(agent, os66);
18417
18469
  if (s.mode !== "interactive") {
18418
18470
  throw new Error(
18419
18471
  `Agent "${agent}" is a batch agent; use createAgentStrategy + .runOneShot for one-shot reviews.`
@@ -19091,8 +19143,8 @@ function collectChangedFiles(cwd) {
19091
19143
  }
19092
19144
  return [...byPath.values()];
19093
19145
  }
19094
- function restoreCoderabbitOauthBlob(os65, value) {
19095
- const dir = path38.join(os65.homeDir(), ".coderabbit");
19146
+ function restoreCoderabbitOauthBlob(os66, value) {
19147
+ const dir = path38.join(os66.homeDir(), ".coderabbit");
19096
19148
  (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
19097
19149
  let file = "auth.json";
19098
19150
  let contents = value.trim();
@@ -19112,26 +19164,26 @@ function installFailureMessage(result) {
19112
19164
  return result.error ?? "CodeRabbit CLI could not be installed";
19113
19165
  }
19114
19166
  async function configureCoderabbit(input, deps = {}) {
19115
- const os65 = deps.os ?? createOsStrategy();
19167
+ const os66 = deps.os ?? createOsStrategy();
19116
19168
  const ensureInstalled = deps.ensureInstalled ?? ensureCoderabbitInstalled;
19117
19169
  const isLoggedIn = deps.isLoggedIn ?? defaultIsLoggedIn;
19118
19170
  const runOAuth = deps.runOAuthLogin ?? runCoderabbitOAuthLogin;
19119
19171
  const snapshot = deps.snapshotDir ?? (() => snapshotCredentialDir());
19120
19172
  const capture2 = deps.captureCredential ?? ((b) => diffCapturedCredential(b));
19121
19173
  const loginWithApiKey = deps.loginWithApiKey ?? defaultLoginWithApiKey;
19122
- const home = os65.homeDir();
19123
- os65.augmentPath(
19124
- os65.id === "win32" ? [
19174
+ const home = os66.homeDir();
19175
+ os66.augmentPath(
19176
+ os66.id === "win32" ? [
19125
19177
  path38.join(home, ".local", "bin"),
19126
19178
  path38.join(process.env.APPDATA ?? path38.join(home, "AppData", "Roaming"), "npm"),
19127
19179
  path38.join(home, "scoop", "shims")
19128
19180
  ] : [path38.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
19129
19181
  );
19130
- const installed2 = os65.findInPath("coderabbit") !== null;
19182
+ const installed2 = os66.findInPath("coderabbit") !== null;
19131
19183
  const base = () => ({
19132
19184
  action: input.action,
19133
19185
  supported: true,
19134
- installed: os65.findInPath("coderabbit") !== null,
19186
+ installed: os66.findInPath("coderabbit") !== null,
19135
19187
  loggedIn: false
19136
19188
  });
19137
19189
  if (input.action === "status") {
@@ -19145,7 +19197,7 @@ async function configureCoderabbit(input, deps = {}) {
19145
19197
  const key = (input.apiKey ?? "").trim();
19146
19198
  if (!key) return { ...res2, error: "No API key provided" };
19147
19199
  if (!res2.installed) {
19148
- const inst = await ensureInstalled(os65);
19200
+ const inst = await ensureInstalled(os66);
19149
19201
  res2.installed = inst.ok;
19150
19202
  if (!inst.ok) return { ...res2, error: installFailureMessage(inst) };
19151
19203
  }
@@ -19169,7 +19221,7 @@ async function configureCoderabbit(input, deps = {}) {
19169
19221
  }
19170
19222
  if (!res2.installed) {
19171
19223
  deps.onEvent?.({ kind: "installing" });
19172
- const inst = await ensureInstalled(os65);
19224
+ const inst = await ensureInstalled(os66);
19173
19225
  res2.installed = inst.ok;
19174
19226
  if (!inst.ok) return { ...res2, error: installFailureMessage(inst) };
19175
19227
  }
@@ -19186,7 +19238,7 @@ async function configureCoderabbit(input, deps = {}) {
19186
19238
  return { ...res2, loggedIn: true, linked: true };
19187
19239
  }
19188
19240
  try {
19189
- restoreCoderabbitOauthBlob(os65, cred.credential);
19241
+ restoreCoderabbitOauthBlob(os66, cred.credential);
19190
19242
  } catch (err) {
19191
19243
  return {
19192
19244
  ...res2,
@@ -19210,7 +19262,7 @@ async function configureCoderabbit(input, deps = {}) {
19210
19262
  const res2 = base();
19211
19263
  if (!installed2) {
19212
19264
  deps.onEvent?.({ kind: "installing" });
19213
- const inst = await ensureInstalled(os65);
19265
+ const inst = await ensureInstalled(os66);
19214
19266
  res2.installed = inst.ok;
19215
19267
  if (!inst.ok) return { ...res2, error: installFailureMessage(inst) };
19216
19268
  }
@@ -19250,7 +19302,7 @@ async function configureCoderabbit(input, deps = {}) {
19250
19302
  }
19251
19303
  const res = base();
19252
19304
  if (!res.installed) {
19253
- const inst = await ensureInstalled(os65);
19305
+ const inst = await ensureInstalled(os66);
19254
19306
  res.installed = inst.ok;
19255
19307
  if (!inst.ok) return { ...res, error: installFailureMessage(inst) };
19256
19308
  }
@@ -21073,7 +21125,7 @@ async function autoUpgradeBeforeCriticalCommand() {
21073
21125
  if (process.env.NODE_ENV === "test") return;
21074
21126
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
21075
21127
  if (process.env.CI) return;
21076
- const current2 = true ? "2.63.1" : null;
21128
+ const current2 = true ? "2.65.0" : null;
21077
21129
  if (!current2) return;
21078
21130
  const cache = readCache();
21079
21131
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -21090,7 +21142,7 @@ function checkForUpdates() {
21090
21142
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
21091
21143
  if (process.env.CI) return;
21092
21144
  if (!process.stdout.isTTY) return;
21093
- const current2 = true ? "2.63.1" : null;
21145
+ const current2 = true ? "2.65.0" : null;
21094
21146
  if (!current2) return;
21095
21147
  const cache = readCache();
21096
21148
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
@@ -21110,7 +21162,7 @@ var SELF_UPDATE_INTERVAL_MS = 60 * 60 * 1e3;
21110
21162
  var SELF_UPDATE_VIEW_TIMEOUT_MS = 3e4;
21111
21163
  var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
21112
21164
  function currentCliVersion() {
21113
- return true ? "2.63.1" : null;
21165
+ return true ? "2.65.0" : null;
21114
21166
  }
21115
21167
  function runCmd(cmd, args2, timeoutMs) {
21116
21168
  return new Promise((resolve9) => {
@@ -22779,7 +22831,7 @@ async function defaultFetchJson(url2) {
22779
22831
  async function readUsageReport(deps = {}) {
22780
22832
  const port = deps.port ?? HEADROOM_PROXY_PORT;
22781
22833
  const base = `http://127.0.0.1:${port}`;
22782
- const get2 = deps.fetchJson ?? ((path90) => defaultFetchJson(`${base}${path90}`));
22834
+ const get2 = deps.fetchJson ?? ((path91) => defaultFetchJson(`${base}${path91}`));
22783
22835
  let history;
22784
22836
  try {
22785
22837
  history = await get2("/stats-history");
@@ -24696,11 +24748,11 @@ function resolveDoltInstallStrategy(platform3) {
24696
24748
  }
24697
24749
  var DOLT_RELEASE_BASE = "https://github.com/dolthub/dolt/releases/latest/download";
24698
24750
  function doltPlatformTuple(platform3, arch2) {
24699
- const os65 = platform3 === "win32" ? "windows" : platform3 === "darwin" ? "darwin" : "linux";
24751
+ const os66 = platform3 === "win32" ? "windows" : platform3 === "darwin" ? "darwin" : "linux";
24700
24752
  const a = arch2 === "x64" ? "amd64" : arch2 === "arm64" ? "arm64" : null;
24701
24753
  if (!a) return null;
24702
- if (os65 === "windows" && a !== "amd64") return null;
24703
- return `${os65}-${a}`;
24754
+ if (os66 === "windows" && a !== "amd64") return null;
24755
+ return `${os66}-${a}`;
24704
24756
  }
24705
24757
  function resolveDoltTarballStrategy(targetDir, platform3, arch2) {
24706
24758
  const tuple = doltPlatformTuple(platform3, arch2);
@@ -25761,8 +25813,14 @@ function dispatchPrompt(ctx, prompt) {
25761
25813
  ctx.outputSvc.newTurn();
25762
25814
  ctx.agent.sendCommand(prompt);
25763
25815
  }
25764
- var startTask = (ctx, _cmd, parsed) => {
25816
+ var startTask = async (ctx, cmd, parsed) => {
25765
25817
  const { prompt, files } = parsed;
25818
+ if (parsed.agentId && parsed.agentId !== ctx.agentId) {
25819
+ await ctx.relay.sendResult(cmd.id, "failed", {
25820
+ error: "Switching agents isn't supported on this session."
25821
+ });
25822
+ return;
25823
+ }
25766
25824
  const effectivePrompt = prompt ?? "";
25767
25825
  if (files && files.length > 0) {
25768
25826
  const paths = saveFilesTemp(files);
@@ -26346,7 +26404,7 @@ var vcsAgentReviewH = async (ctx, cmd, parsed) => {
26346
26404
  });
26347
26405
  const token = ctx.pluginAuthToken;
26348
26406
  void (async () => {
26349
- const os65 = createOsStrategy();
26407
+ const os66 = createOsStrategy();
26350
26408
  try {
26351
26409
  const report = await reviewPullRequest(
26352
26410
  {
@@ -26355,7 +26413,7 @@ var vcsAgentReviewH = async (ctx, cmd, parsed) => {
26355
26413
  baseBranch: parsed.baseBranch
26356
26414
  },
26357
26415
  {
26358
- runReview: (input) => new CoderabbitRuntimeStrategy(os65).runOneShot(input),
26416
+ runReview: (input) => new CoderabbitRuntimeStrategy(os66).runOneShot(input),
26359
26417
  runGh: (args2) => defaultRunGh(args2),
26360
26418
  postReport: async (r) => {
26361
26419
  if (!token) return;
@@ -27458,11 +27516,11 @@ function resolveTokenValue(args2) {
27458
27516
  }
27459
27517
  const fileFlag = args2.find((a) => a.startsWith("--token-file="));
27460
27518
  if (fileFlag) {
27461
- const path90 = fileFlag.slice("--token-file=".length);
27519
+ const path91 = fileFlag.slice("--token-file=".length);
27462
27520
  try {
27463
- const content = fs63.readFileSync(path90, "utf8").trim();
27464
- if (content.length === 0) fail(`--token-file ${path90} is empty`);
27465
- rmIfExistsQuiet(path90);
27521
+ const content = fs63.readFileSync(path91, "utf8").trim();
27522
+ if (content.length === 0) fail(`--token-file ${path91} is empty`);
27523
+ rmIfExistsQuiet(path91);
27466
27524
  return content;
27467
27525
  } catch (err) {
27468
27526
  fail(`Could not read --token-file: ${err.message}`);
@@ -28525,12 +28583,292 @@ async function resolveAcpAdapterWithRetry(agent, opts = {}) {
28525
28583
  var import_node_crypto11 = require("crypto");
28526
28584
 
28527
28585
  // src/services/history.service.ts
28528
- var fs65 = __toESM(require("fs"));
28529
- var path71 = __toESM(require("path"));
28530
- var os55 = __toESM(require("os"));
28586
+ var fs66 = __toESM(require("fs"));
28587
+ var path72 = __toESM(require("path"));
28588
+ var os56 = __toESM(require("os"));
28531
28589
  var https7 = __toESM(require("https"));
28532
28590
  var http6 = __toESM(require("http"));
28533
28591
  var import_zod2 = require("zod");
28592
+
28593
+ // src/agents/acp/squad-roster.ts
28594
+ var fs65 = __toESM(require("fs"));
28595
+ var os55 = __toESM(require("os"));
28596
+ var path71 = __toESM(require("path"));
28597
+ var PROMPT_MAX = 500;
28598
+ var REPLY_SUMMARY_MAX = 1e3;
28599
+ var PREAMBLE_MAX = 2e3;
28600
+ var BRIEFING_DEFAULT_MAX = 8e3;
28601
+ var GENERIC_SPECIALTY = "general implementation tasks";
28602
+ function clip(s, max) {
28603
+ return s.length > max ? s.slice(0, max) : s;
28604
+ }
28605
+ function defaultMember() {
28606
+ return {
28607
+ acpSessionId: null,
28608
+ provisioned: false,
28609
+ binaryVerified: false,
28610
+ lastTurnIndex: 0,
28611
+ contextTextFallback: false
28612
+ };
28613
+ }
28614
+ function journalPathFor(homeDir2, sessionId) {
28615
+ return path71.join(homeDir2, ".codeam", `squad-journal-${sessionId}.json`);
28616
+ }
28617
+ function loadJournal(journalPath) {
28618
+ try {
28619
+ const raw = JSON.parse(fs65.readFileSync(journalPath, "utf-8"));
28620
+ return Array.isArray(raw.turns) ? raw.turns : [];
28621
+ } catch {
28622
+ return [];
28623
+ }
28624
+ }
28625
+ var SquadState = class {
28626
+ /** Set by the caller after fetchSquadRoster; null until then. */
28627
+ roster = null;
28628
+ /**
28629
+ * Autonomous chained handoffs (P2-2, PRO). Seeded from the persisted
28630
+ * `SavedSession.squadAuto` at session start and rewritten by
28631
+ * `squad_configure`. Read-only to callers — mutate via {@link setAuto}, which
28632
+ * clamps the budget and re-arms the chain.
28633
+ */
28634
+ autoConfig = { enabled: false, hopBudget: SQUAD_HOP_BUDGET_DEFAULT };
28635
+ /**
28636
+ * Hops left in the CURRENT chain. Reset to the budget on every USER-initiated
28637
+ * prompt (so a user turn always interrupts and re-arms) and decremented by
28638
+ * each self-accepted handoff.
28639
+ */
28640
+ hopsLeft = 0;
28641
+ /** Lifetime handoff counters for `squad_stats` (in-memory, this process). */
28642
+ handoffCounters = { proposed: 0, accepted: 0, auto: 0 };
28643
+ journalPath;
28644
+ turns;
28645
+ members = /* @__PURE__ */ new Map();
28646
+ constructor(opts) {
28647
+ this.journalPath = journalPathFor(opts.homeDir ?? os55.homedir(), opts.sessionId);
28648
+ this.turns = loadJournal(this.journalPath);
28649
+ if (opts.auto) this.setAuto(opts.auto);
28650
+ }
28651
+ get auto() {
28652
+ return this.autoConfig;
28653
+ }
28654
+ /** Apply a new mode (clamping the budget) and re-arm the chain. */
28655
+ setAuto(value) {
28656
+ this.autoConfig = {
28657
+ enabled: value.enabled === true,
28658
+ hopBudget: clampHopBudget(value.hopBudget)
28659
+ };
28660
+ this.resetHops();
28661
+ return this.autoConfig;
28662
+ }
28663
+ hopsRemaining() {
28664
+ return this.hopsLeft;
28665
+ }
28666
+ /** Re-arm the chain — called at the start of every USER-initiated turn. */
28667
+ resetHops() {
28668
+ this.hopsLeft = this.autoConfig.enabled ? this.autoConfig.hopBudget : 0;
28669
+ }
28670
+ /** Spend one hop on a self-accepted handoff. */
28671
+ consumeHop() {
28672
+ if (this.hopsLeft > 0) this.hopsLeft -= 1;
28673
+ }
28674
+ countProposal(opts) {
28675
+ this.handoffCounters.proposed += 1;
28676
+ if (opts.auto) this.handoffCounters.auto += 1;
28677
+ }
28678
+ countAccepted() {
28679
+ this.handoffCounters.accepted += 1;
28680
+ }
28681
+ /**
28682
+ * Per-member activity for the `squad_stats` relay command, derived from the
28683
+ * journal (the same shared history the delta briefing reads) plus the
28684
+ * in-memory handoff counters. `filesTouched` counts DISTINCT paths — a member
28685
+ * that edited the same file across three turns touched ONE file.
28686
+ */
28687
+ stats() {
28688
+ const byAgent = /* @__PURE__ */ new Map();
28689
+ for (const t2 of this.turns) {
28690
+ let row = byAgent.get(t2.agentId);
28691
+ if (!row) {
28692
+ row = { turns: 0, files: /* @__PURE__ */ new Set() };
28693
+ byAgent.set(t2.agentId, row);
28694
+ }
28695
+ row.turns += 1;
28696
+ for (const f of t2.filesTouched) row.files.add(f);
28697
+ }
28698
+ const members = [...byAgent.entries()].map(([agentId, row]) => ({
28699
+ agentId,
28700
+ turns: row.turns,
28701
+ filesTouched: row.files.size
28702
+ }));
28703
+ return { members, handoffs: { ...this.handoffCounters }, sinceTurn: 1 };
28704
+ }
28705
+ /** Returns (creating on first access) the mutable per-agent state. */
28706
+ member(agentId) {
28707
+ let m = this.members.get(agentId);
28708
+ if (!m) {
28709
+ m = defaultMember();
28710
+ this.members.set(agentId, m);
28711
+ }
28712
+ return m;
28713
+ }
28714
+ /** Appends a journal entry and persists (fire-and-forget durability). */
28715
+ recordTurn(entry) {
28716
+ const turn = {
28717
+ turn: this.turns.length + 1,
28718
+ agentId: entry.agentId,
28719
+ prompt: clip(entry.prompt, PROMPT_MAX),
28720
+ replySummary: clip(entry.replySummary, REPLY_SUMMARY_MAX),
28721
+ filesTouched: entry.filesTouched
28722
+ };
28723
+ this.turns.push(turn);
28724
+ this.persist();
28725
+ }
28726
+ turnCount() {
28727
+ return this.turns.length;
28728
+ }
28729
+ entriesSince(turnIndex) {
28730
+ return this.turns.filter((t2) => t2.turn > turnIndex);
28731
+ }
28732
+ persist() {
28733
+ try {
28734
+ fs65.mkdirSync(path71.dirname(this.journalPath), { recursive: true, mode: 448 });
28735
+ fs65.writeFileSync(this.journalPath, JSON.stringify({ turns: this.turns }), { mode: 384 });
28736
+ } catch {
28737
+ }
28738
+ }
28739
+ };
28740
+ function specialtyFor(agentId) {
28741
+ return SQUAD_SPECIALTIES[agentId] ?? GENERIC_SPECIALTY;
28742
+ }
28743
+ var TEAM_PREAMBLE_MARKER = "[Team context]";
28744
+ var TEAM_PREAMBLE_LINES = [
28745
+ "[Team context] You are the active agent in a CodeAgent Mobile session where the user",
28746
+ "has a squad of agents and can pass work between them. Your available teammates:",
28747
+ "If a task clearly fits a teammate better than you, you MAY propose a handoff by ending",
28748
+ `your reply with a fenced code block tagged ${HANDOFF_FENCE_TAG} containing ONE JSON object:`,
28749
+ '{"to":"<teammate id>","reason":"<one sentence>","prompt":"<the prompt they should run>"}',
28750
+ "Propose at most one handoff per reply, only when genuinely better, and never announce",
28751
+ "the block in prose \u2014 the app renders it as a card the user can accept."
28752
+ ];
28753
+ var TEAM_PREAMBLE_BULLET_RE = /^- .+ — best at: /;
28754
+ var BRIEFING_MARKER = "[Team update]";
28755
+ var BRIEFING_HEADER = "[Team update] While you were away, other agents worked on this session:";
28756
+ var BRIEFING_FOOTER = "Continue from the CURRENT state of the working tree.";
28757
+ function buildTeamPreamble(roster, currentAgent, opts) {
28758
+ const others = roster.agents.filter((a) => a.agentId !== currentAgent);
28759
+ if (others.length === 0) return null;
28760
+ const lines = [
28761
+ TEAM_PREAMBLE_LINES[0],
28762
+ TEAM_PREAMBLE_LINES[1],
28763
+ ...others.map((a) => `- ${a.displayName} \u2014 best at: ${specialtyFor(a.agentId)}`)
28764
+ ];
28765
+ if (opts.handoffInstructions) {
28766
+ lines.push(...TEAM_PREAMBLE_LINES.slice(2));
28767
+ }
28768
+ return clip(lines.join("\n"), PREAMBLE_MAX);
28769
+ }
28770
+ function renderJournalEntry(e) {
28771
+ const filesClause = e.filesTouched.length > 0 ? ` (files: ${e.filesTouched.join(", ")})` : "";
28772
+ return `- turn ${e.turn} (${e.agentId}): ${e.prompt} \u2192 ${e.replySummary}${filesClause}`;
28773
+ }
28774
+ function buildDeltaBriefing(entries, maxChars = BRIEFING_DEFAULT_MAX) {
28775
+ if (entries.length === 0) return null;
28776
+ const header = BRIEFING_HEADER;
28777
+ const footer = BRIEFING_FOOTER;
28778
+ const envelope = header.length + 1 + footer.length + 1;
28779
+ const sorted = [...entries].sort((a, b) => a.turn - b.turn);
28780
+ const lines = [];
28781
+ let bodyLen = 0;
28782
+ for (let i = sorted.length - 1; i >= 0; i--) {
28783
+ const line = renderJournalEntry(sorted[i]);
28784
+ const addedLen = line.length + (lines.length > 0 ? 1 : 0);
28785
+ if (lines.length > 0 && envelope + bodyLen + addedLen > maxChars) break;
28786
+ lines.unshift(line);
28787
+ bodyLen += addedLen;
28788
+ }
28789
+ return [header, lines.join("\n"), footer].join("\n");
28790
+ }
28791
+
28792
+ // src/agents/acp/squad-context.ts
28793
+ var SQUAD_CONTEXT_URI = "codeam://squad-context";
28794
+ var HANDOFF_MARKER = "[Session handoff]";
28795
+ var HANDOFF_TERMINATOR = "--- End of handoff context ---";
28796
+ function buildSquadContextBlock(text) {
28797
+ return {
28798
+ type: "resource",
28799
+ resource: { uri: SQUAD_CONTEXT_URI, mimeType: "text/plain", text }
28800
+ };
28801
+ }
28802
+ function isSquadContextBlock(block) {
28803
+ return block.type === "resource" && block.resource.uri === SQUAD_CONTEXT_URI;
28804
+ }
28805
+ function looksLikeUnsupportedPromptShape(err) {
28806
+ const code = err?.code;
28807
+ if (code === -32602) return true;
28808
+ const message = err instanceof Error ? err.message : String(err ?? "");
28809
+ if (/invalid[ _]params|-32602/i.test(message)) return true;
28810
+ return /\bresource\b/i.test(message) && /unsupported|not supported/i.test(message);
28811
+ }
28812
+ function skipWhile(lines, start2, keepGoing) {
28813
+ let i = start2;
28814
+ while (i < lines.length && keepGoing(lines[i])) i++;
28815
+ return i;
28816
+ }
28817
+ function endOfTeamPreamble(lines, start2) {
28818
+ if (lines[start2] !== TEAM_PREAMBLE_LINES[0]) return -1;
28819
+ if (lines[start2 + 1] !== TEAM_PREAMBLE_LINES[1]) return -1;
28820
+ return skipWhile(
28821
+ lines,
28822
+ start2 + 2,
28823
+ (line) => TEAM_PREAMBLE_BULLET_RE.test(line) || TEAM_PREAMBLE_LINES.includes(line)
28824
+ );
28825
+ }
28826
+ function endOfTerminatedBlock(lines, start2, terminator) {
28827
+ for (let i = start2; i < lines.length; i++) {
28828
+ if (lines[i].trimEnd() === terminator) return i + 1;
28829
+ }
28830
+ return -1;
28831
+ }
28832
+ function stripSquadContext(text) {
28833
+ if (!text.includes(HANDOFF_MARKER) && !text.includes(BRIEFING_MARKER) && !text.includes(TEAM_PREAMBLE_MARKER)) {
28834
+ return text;
28835
+ }
28836
+ const lines = text.split("\n");
28837
+ const kept = [];
28838
+ let i = 0;
28839
+ let stripped = false;
28840
+ while (i < lines.length) {
28841
+ const line = lines[i];
28842
+ if (line.startsWith(HANDOFF_MARKER)) {
28843
+ const end = endOfTerminatedBlock(lines, i, HANDOFF_TERMINATOR);
28844
+ if (end !== -1) {
28845
+ i = end;
28846
+ stripped = true;
28847
+ continue;
28848
+ }
28849
+ } else if (line.startsWith(BRIEFING_MARKER)) {
28850
+ const end = endOfTerminatedBlock(lines, i, BRIEFING_FOOTER);
28851
+ if (end !== -1) {
28852
+ i = end;
28853
+ stripped = true;
28854
+ continue;
28855
+ }
28856
+ } else if (line.startsWith(TEAM_PREAMBLE_MARKER)) {
28857
+ const end = endOfTeamPreamble(lines, i);
28858
+ if (end !== -1) {
28859
+ i = end;
28860
+ stripped = true;
28861
+ continue;
28862
+ }
28863
+ }
28864
+ kept.push(line);
28865
+ i++;
28866
+ }
28867
+ if (!stripped) return text;
28868
+ return kept.join("\n").trim();
28869
+ }
28870
+
28871
+ // src/services/history.service.ts
28534
28872
  var historyRecordSchema = import_zod2.z.object({
28535
28873
  type: import_zod2.z.string().optional(),
28536
28874
  timestamp: import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number()]).optional(),
@@ -28550,11 +28888,24 @@ function extractText3(content) {
28550
28888
  return "";
28551
28889
  }
28552
28890
  var CONVERSATION_BATCH_SIZE = 30;
28891
+ function scrubSquadContext(messages) {
28892
+ const out2 = [];
28893
+ for (const m of messages) {
28894
+ const text = stripSquadContext(m.text);
28895
+ if (text === m.text) {
28896
+ out2.push(m);
28897
+ continue;
28898
+ }
28899
+ if (text.length === 0) continue;
28900
+ out2.push({ ...m, text });
28901
+ }
28902
+ return out2;
28903
+ }
28553
28904
  function parseJsonl(filePath) {
28554
28905
  const messages = [];
28555
28906
  let raw;
28556
28907
  try {
28557
- raw = fs65.readFileSync(filePath, "utf8");
28908
+ raw = fs66.readFileSync(filePath, "utf8");
28558
28909
  } catch (err) {
28559
28910
  if (err.code !== "ENOENT") {
28560
28911
  log.warn("history:parseJsonl", `read failed for ${filePath}`, err);
@@ -28695,7 +29046,7 @@ var HistoryService = class _HistoryService {
28695
29046
  return this._quotaPercent === null || Date.now() - this._quotaFetchedAt > ttlMs;
28696
29047
  }
28697
29048
  get projectDir() {
28698
- return this.runtime.resolveHistoryDir(this.cwd) ?? path71.join(os55.homedir(), ".claude", "projects", encodeCwd(this.cwd));
29049
+ return this.runtime.resolveHistoryDir(this.cwd) ?? path72.join(os56.homedir(), ".claude", "projects", encodeCwd(this.cwd));
28699
29050
  }
28700
29051
  /** Set the current Claude conversation ID (extracted from /cost command or session start) */
28701
29052
  setCurrentConversationId(id) {
@@ -28707,7 +29058,7 @@ var HistoryService = class _HistoryService {
28707
29058
  /** Return the current message count in the active conversation. */
28708
29059
  getCurrentMessageCount() {
28709
29060
  if (!this.currentConversationId) return 0;
28710
- const filePath = path71.join(this.projectDir, `${this.currentConversationId}.jsonl`);
29061
+ const filePath = path72.join(this.projectDir, `${this.currentConversationId}.jsonl`);
28711
29062
  return parseJsonl(filePath).length;
28712
29063
  }
28713
29064
  /**
@@ -28718,7 +29069,7 @@ var HistoryService = class _HistoryService {
28718
29069
  const deadline = Date.now() + timeoutMs;
28719
29070
  while (Date.now() < deadline) {
28720
29071
  if (!this.currentConversationId) return null;
28721
- const filePath = path71.join(this.projectDir, `${this.currentConversationId}.jsonl`);
29072
+ const filePath = path72.join(this.projectDir, `${this.currentConversationId}.jsonl`);
28722
29073
  const messages = parseJsonl(filePath);
28723
29074
  if (messages.length > previousCount) {
28724
29075
  for (let i = messages.length - 1; i >= previousCount; i--) {
@@ -28744,16 +29095,16 @@ var HistoryService = class _HistoryService {
28744
29095
  const dir = this.projectDir;
28745
29096
  const cutoff = this.bootTimeMs - _HistoryService.BIRTHTIME_GRACE_MS;
28746
29097
  try {
28747
- const files = fs65.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
29098
+ const files = fs66.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
28748
29099
  try {
28749
- const stat3 = fs65.statSync(path71.join(dir, e.name));
29100
+ const stat3 = fs66.statSync(path72.join(dir, e.name));
28750
29101
  return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
28751
29102
  } catch {
28752
29103
  return { name: e.name, mtime: 0, birthtime: 0 };
28753
29104
  }
28754
29105
  }).filter((f) => f.birthtime >= cutoff).sort((a, b) => b.mtime - a.mtime);
28755
29106
  if (files.length > 0) {
28756
- this.currentConversationId = path71.basename(files[0].name, ".jsonl");
29107
+ this.currentConversationId = path72.basename(files[0].name, ".jsonl");
28757
29108
  }
28758
29109
  } catch {
28759
29110
  }
@@ -28787,13 +29138,13 @@ var HistoryService = class _HistoryService {
28787
29138
  const cutoff = this.bootTimeMs - _HistoryService.BIRTHTIME_GRACE_MS;
28788
29139
  let entries;
28789
29140
  try {
28790
- entries = fs65.readdirSync(dir, { withFileTypes: true });
29141
+ entries = fs66.readdirSync(dir, { withFileTypes: true });
28791
29142
  } catch {
28792
29143
  return null;
28793
29144
  }
28794
29145
  const files = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => {
28795
29146
  try {
28796
- const stat3 = fs65.statSync(path71.join(dir, e.name));
29147
+ const stat3 = fs66.statSync(path72.join(dir, e.name));
28797
29148
  return { name: e.name, mtime: stat3.mtimeMs, birthtime: stat3.birthtimeMs };
28798
29149
  } catch {
28799
29150
  return { name: e.name, mtime: 0, birthtime: 0 };
@@ -28802,12 +29153,12 @@ var HistoryService = class _HistoryService {
28802
29153
  if (files.length === 0) return null;
28803
29154
  const targetFile = this.currentConversationId ? `${this.currentConversationId}.jsonl` : files[0].name;
28804
29155
  if (!files.some((f) => f.name === targetFile)) return null;
28805
- return this.extractUsageFromFile(path71.join(dir, targetFile));
29156
+ return this.extractUsageFromFile(path72.join(dir, targetFile));
28806
29157
  }
28807
29158
  extractUsageFromFile(filePath) {
28808
29159
  let raw;
28809
29160
  try {
28810
- raw = fs65.readFileSync(filePath, "utf8");
29161
+ raw = fs66.readFileSync(filePath, "utf8");
28811
29162
  } catch {
28812
29163
  return null;
28813
29164
  }
@@ -28852,9 +29203,9 @@ var HistoryService = class _HistoryService {
28852
29203
  let totalCost = 0;
28853
29204
  let files;
28854
29205
  try {
28855
- files = fs65.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).filter((f) => {
29206
+ files = fs66.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).filter((f) => {
28856
29207
  try {
28857
- return fs65.statSync(path71.join(projectDir, f)).mtimeMs >= monthStartMs;
29208
+ return fs66.statSync(path72.join(projectDir, f)).mtimeMs >= monthStartMs;
28858
29209
  } catch {
28859
29210
  return false;
28860
29211
  }
@@ -28865,7 +29216,7 @@ var HistoryService = class _HistoryService {
28865
29216
  for (const file of files) {
28866
29217
  let raw;
28867
29218
  try {
28868
- raw = fs65.readFileSync(path71.join(projectDir, file), "utf8");
29219
+ raw = fs66.readFileSync(path72.join(projectDir, file), "utf8");
28869
29220
  } catch {
28870
29221
  continue;
28871
29222
  }
@@ -28944,7 +29295,7 @@ var HistoryService = class _HistoryService {
28944
29295
  if (this.runtime.resolveHistoryFile) {
28945
29296
  return this.runtime.resolveHistoryFile(this.cwd, sessionId);
28946
29297
  }
28947
- return path71.join(this.projectDir, `${sessionId}.jsonl`);
29298
+ return path72.join(this.projectDir, `${sessionId}.jsonl`);
28948
29299
  }
28949
29300
  /**
28950
29301
  * Parse a conversation's messages from disk, agent-aware. Claude uses the
@@ -28956,6 +29307,10 @@ var HistoryService = class _HistoryService {
28956
29307
  * convention as parseJsonl.
28957
29308
  */
28958
29309
  readConversation(sessionId) {
29310
+ const agentId = this.runtime.id;
29311
+ return scrubSquadContext(this.readConversationRaw(sessionId)).map((m) => ({ ...m, agentId }));
29312
+ }
29313
+ readConversationRaw(sessionId) {
28959
29314
  if (this.runtime.resolveHistoryFile) {
28960
29315
  const filePath = this.runtime.resolveHistoryFile(this.cwd, sessionId);
28961
29316
  if (!filePath) return [];
@@ -28978,7 +29333,7 @@ var HistoryService = class _HistoryService {
28978
29333
  };
28979
29334
  });
28980
29335
  }
28981
- return parseJsonl(path71.join(this.projectDir, `${sessionId}.jsonl`));
29336
+ return parseJsonl(path72.join(this.projectDir, `${sessionId}.jsonl`));
28982
29337
  }
28983
29338
  async loadConversation(sessionId) {
28984
29339
  const messages = this.readConversation(sessionId);
@@ -29046,7 +29401,7 @@ var HistoryService = class _HistoryService {
29046
29401
  if (!filePath) return false;
29047
29402
  let mtimeMs;
29048
29403
  try {
29049
- mtimeMs = fs65.statSync(filePath).mtimeMs;
29404
+ mtimeMs = fs66.statSync(filePath).mtimeMs;
29050
29405
  } catch {
29051
29406
  return false;
29052
29407
  }
@@ -29121,10 +29476,10 @@ var HistoryService = class _HistoryService {
29121
29476
 
29122
29477
  // src/agents/acp/client.ts
29123
29478
  var import_node_child_process29 = require("child_process");
29124
- var fs66 = __toESM(require("fs/promises"));
29479
+ var fs67 = __toESM(require("fs/promises"));
29125
29480
  var fsSync = __toESM(require("fs"));
29126
- var os57 = __toESM(require("os"));
29127
- var path73 = __toESM(require("path"));
29481
+ var os58 = __toESM(require("os"));
29482
+ var path74 = __toESM(require("path"));
29128
29483
  var import_node_stream = require("stream");
29129
29484
 
29130
29485
  // ../../node_modules/@agentclientprotocol/sdk/dist/schema/index.js
@@ -33150,8 +33505,8 @@ function createIdleTimeout(idleMs, makeError, activeIdleMs = idleMs) {
33150
33505
  }
33151
33506
 
33152
33507
  // src/agents/acp/internal-paths.ts
33153
- var path72 = __toESM(require("path"));
33154
- var os56 = __toESM(require("os"));
33508
+ var path73 = __toESM(require("path"));
33509
+ var os57 = __toESM(require("os"));
33155
33510
  var INTERNAL_TOKENS = [".codeam", "house-claude"];
33156
33511
  var SELF_HOSTED_WORKSPACE_RE = /\.codeam[/\\]self-hosted/gi;
33157
33512
  function textReferencesInternal(text) {
@@ -33159,13 +33514,13 @@ function textReferencesInternal(text) {
33159
33514
  const scrubbed = text.replace(SELF_HOSTED_WORKSPACE_RE, " ");
33160
33515
  return INTERNAL_TOKENS.some((t2) => scrubbed.includes(t2));
33161
33516
  }
33162
- function pathIsInternal(p2, homeDir2 = os56.homedir()) {
33517
+ function pathIsInternal(p2, homeDir2 = os57.homedir()) {
33163
33518
  if (!p2) return false;
33164
- const abs = path72.resolve(p2);
33165
- const home = path72.resolve(homeDir2);
33166
- const within = (root) => abs === root || abs.startsWith(root + path72.sep);
33167
- if (within(path72.join(home, ".codeam", "self-hosted"))) return false;
33168
- return within(path72.join(home, ".codeam")) || within(path72.join(home, ".beads")) || abs === path72.join(home, ".codeam-host.log") || abs.includes(`${path72.sep}house-claude${path72.sep}`) || abs.endsWith(`${path72.sep}house-claude`);
33519
+ const abs = path73.resolve(p2);
33520
+ const home = path73.resolve(homeDir2);
33521
+ const within = (root) => abs === root || abs.startsWith(root + path73.sep);
33522
+ if (within(path73.join(home, ".codeam", "self-hosted"))) return false;
33523
+ return within(path73.join(home, ".codeam")) || within(path73.join(home, ".beads")) || abs === path73.join(home, ".codeam-host.log") || abs.includes(`${path73.sep}house-claude${path73.sep}`) || abs.endsWith(`${path73.sep}house-claude`);
33169
33524
  }
33170
33525
  function toolCallReferencesInternal(call) {
33171
33526
  if (textReferencesInternal(call.title)) return true;
@@ -34124,7 +34479,7 @@ var AcpClient = class {
34124
34479
  throw new RequestError(-32002, GUARDRAIL_SECRET_READ_BLOCK_REASON, { uri: params.path });
34125
34480
  }
34126
34481
  try {
34127
- const content = await fs66.readFile(params.path, "utf8");
34482
+ const content = await fs67.readFile(params.path, "utf8");
34128
34483
  return applyLineRange(content, params.line ?? null, params.limit ?? null);
34129
34484
  } catch (err) {
34130
34485
  const code = err.code;
@@ -34147,7 +34502,7 @@ var AcpClient = class {
34147
34502
  throw new RequestError(-32002, INTERNAL_BLOCK_REASON, { uri: params.path });
34148
34503
  }
34149
34504
  try {
34150
- await fs66.writeFile(params.path, params.content, "utf8");
34505
+ await fs67.writeFile(params.path, params.content, "utf8");
34151
34506
  return {};
34152
34507
  } catch (err) {
34153
34508
  const code = err.code;
@@ -34207,29 +34562,29 @@ function applyLineRange(content, line, limit) {
34207
34562
  return { content: lines.slice(start2, end).join("\n") };
34208
34563
  }
34209
34564
  function knownAgentBinaryDirs() {
34210
- const home = os57.homedir();
34565
+ const home = os58.homedir();
34211
34566
  const out2 = [];
34212
34567
  out2.push("/tmp/codeam-node20/bin");
34213
34568
  for (const root of [
34214
34569
  "/usr/local/share/nvm/versions/node",
34215
- path73.join(home, ".nvm/versions/node")
34570
+ path74.join(home, ".nvm/versions/node")
34216
34571
  ]) {
34217
34572
  try {
34218
34573
  for (const child of fsSync.readdirSync(root)) {
34219
- out2.push(path73.join(root, child, "bin"));
34574
+ out2.push(path74.join(root, child, "bin"));
34220
34575
  }
34221
34576
  } catch {
34222
34577
  }
34223
34578
  }
34224
- out2.push(path73.join(home, ".volta/bin"));
34579
+ out2.push(path74.join(home, ".volta/bin"));
34225
34580
  out2.push("/usr/local/bin");
34226
34581
  out2.push("/usr/bin");
34227
- out2.push(path73.join(home, ".local/bin"));
34228
- out2.push(path73.join(home, "bin"));
34582
+ out2.push(path74.join(home, ".local/bin"));
34583
+ out2.push(path74.join(home, "bin"));
34229
34584
  if (process.platform === "win32") {
34230
34585
  const { LOCALAPPDATA, APPDATA } = process.env;
34231
- if (LOCALAPPDATA) out2.push(path73.join(LOCALAPPDATA, "cursor-agent"));
34232
- if (APPDATA) out2.push(path73.join(APPDATA, "npm"));
34586
+ if (LOCALAPPDATA) out2.push(path74.join(LOCALAPPDATA, "cursor-agent"));
34587
+ if (APPDATA) out2.push(path74.join(APPDATA, "npm"));
34233
34588
  }
34234
34589
  return out2.filter((p2) => {
34235
34590
  try {
@@ -34241,7 +34596,7 @@ function knownAgentBinaryDirs() {
34241
34596
  }
34242
34597
  function expandPathForAgentBinaries(existingPath) {
34243
34598
  const existing = new Set(
34244
- existingPath.split(path73.delimiter).filter((p2) => p2.length > 0)
34599
+ existingPath.split(path74.delimiter).filter((p2) => p2.length > 0)
34245
34600
  );
34246
34601
  const additions = [];
34247
34602
  for (const dir of knownAgentBinaryDirs()) {
@@ -34251,7 +34606,7 @@ function expandPathForAgentBinaries(existingPath) {
34251
34606
  }
34252
34607
  }
34253
34608
  if (additions.length === 0) return existingPath;
34254
- return [...additions, existingPath].filter((p2) => p2.length > 0).join(path73.delimiter);
34609
+ return [...additions, existingPath].filter((p2) => p2.length > 0).join(path74.delimiter);
34255
34610
  }
34256
34611
 
34257
34612
  // src/agents/acp/headroom-budget-proxy.ts
@@ -34370,7 +34725,7 @@ function makeSerializedSwitchEmitter(post2) {
34370
34725
  return chain;
34371
34726
  };
34372
34727
  }
34373
- async function performAgentSwitch(deps, rawAgentId) {
34728
+ async function performAgentSwitch(deps, rawAgentId, fastPath = {}) {
34374
34729
  const from = deps.currentAgent();
34375
34730
  const target = resolveSwitchTarget(rawAgentId, from);
34376
34731
  if (!target.ok) {
@@ -34386,22 +34741,28 @@ async function performAgentSwitch(deps, rawAgentId) {
34386
34741
  };
34387
34742
  log.info("switchAgent", `switch requested ${from} \u2192 ${agentId}`);
34388
34743
  void emitStatus({ state: "switching", agentId, fromAgentId: from });
34389
- void emitStep("credential");
34390
- const cred = await deps.fetchCredential(agentId);
34391
- if (!cred) {
34392
- return fail2(
34393
- `No linked credential for ${displayName(agentId)}. Link it in Profile \u203A Agents first.`
34394
- );
34744
+ let installScript;
34745
+ if (!fastPath.skipProvision) {
34746
+ void emitStep("credential");
34747
+ const cred = await deps.fetchCredential(agentId);
34748
+ if (!cred) {
34749
+ return fail2(
34750
+ `No linked credential for ${displayName(agentId)}. Link it in Profile \u203A Agents first.`
34751
+ );
34752
+ }
34753
+ try {
34754
+ deps.provisionCredential(agentId, toAgentAuth(cred.method, cred.credential));
34755
+ } catch (err) {
34756
+ log.warn("switchAgent", `credential provisioning failed: ${err.message}`);
34757
+ return fail2(`Couldn't write the ${displayName(agentId)} credential on this machine.`);
34758
+ }
34759
+ installScript = cred.installScript;
34395
34760
  }
34396
- try {
34397
- deps.provisionCredential(agentId, toAgentAuth(cred.method, cred.credential));
34398
- } catch (err) {
34399
- log.warn("switchAgent", `credential provisioning failed: ${err.message}`);
34400
- return fail2(`Couldn't write the ${displayName(agentId)} credential on this machine.`);
34761
+ if (!fastPath.skipInstall) {
34762
+ void emitStep("install");
34763
+ const bin = await deps.ensureBinary(agentId, installScript);
34764
+ if (!bin.ok) return fail2(bin.error);
34401
34765
  }
34402
- void emitStep("install");
34403
- const bin = await deps.ensureBinary(agentId, cred.installScript);
34404
- if (!bin.ok) return fail2(bin.error);
34405
34766
  void emitStep("restart");
34406
34767
  try {
34407
34768
  await deps.swapRuntime(agentId);
@@ -34727,6 +35088,13 @@ var AcpPublisher = class {
34727
35088
  * `mode: 'replace'` so a re-send overrides any stale persisted
34728
35089
  * version — important for the ACP path because each turn we ship
34729
35090
  * the cumulative messages, not a delta.
35091
+ *
35092
+ * Each message may carry its OWN `agentId` — the agent that PRODUCED that
35093
+ * turn. The top-level `agentId` only keys the backend's per-agent
35094
+ * conversation bucket, so without the per-message field a multi-agent
35095
+ * session's reloaded history collapsed to whichever agent happened to be
35096
+ * active (the v1 limitation, codeagent-egai). Additive: older backends
35097
+ * ignore it, older CLIs simply omit it.
34730
35098
  */
34731
35099
  async pushConversation(args2) {
34732
35100
  const url2 = `${this.apiBase}/api/sessions/conversation`;
@@ -34873,17 +35241,106 @@ function commonPrefixLength(a, b) {
34873
35241
  return i;
34874
35242
  }
34875
35243
 
35244
+ // src/agents/acp/handoff-protocol.ts
35245
+ var REASON_MAX = 1e3;
35246
+ var PROMPT_MAX2 = 8e3;
35247
+ var FENCE_OPEN = "```" + HANDOFF_FENCE_TAG;
35248
+ var FENCE_RE = new RegExp("```" + HANDOFF_FENCE_TAG + "\\s*\\n([\\s\\S]*?)\\n?```", "g");
35249
+ var OUTER_FENCE_RE = /(`{4,})[\s\S]*?\1/g;
35250
+ var outerFencePlaceholder = (i) => `@@HANDOFF_MASK_${i}@@`;
35251
+ function maskOuterFences(text) {
35252
+ const spans = [];
35253
+ const masked = text.replace(OUTER_FENCE_RE, (m) => {
35254
+ const token = outerFencePlaceholder(spans.length);
35255
+ spans.push(m);
35256
+ return token;
35257
+ });
35258
+ const restore = (s) => spans.reduce((acc, span, i) => acc.split(outerFencePlaceholder(i)).join(span), s);
35259
+ return { masked, restore };
35260
+ }
35261
+ function parseProposal(raw, currentAgent, validTargets) {
35262
+ let parsed;
35263
+ try {
35264
+ parsed = JSON.parse(raw);
35265
+ } catch {
35266
+ log.debug("handoffProtocol", "dropped proposal: malformed JSON");
35267
+ return null;
35268
+ }
35269
+ if (typeof parsed !== "object" || parsed === null) {
35270
+ log.debug("handoffProtocol", "dropped proposal: not a JSON object");
35271
+ return null;
35272
+ }
35273
+ const { to, reason, prompt } = parsed;
35274
+ if (typeof to !== "string" || to.length === 0) {
35275
+ log.debug("handoffProtocol", 'dropped proposal: missing/invalid "to"');
35276
+ return null;
35277
+ }
35278
+ if (!validTargets.has(to)) {
35279
+ log.debug("handoffProtocol", `dropped proposal: unknown target "${to}"`);
35280
+ return null;
35281
+ }
35282
+ if (to === currentAgent) {
35283
+ log.debug("handoffProtocol", "dropped proposal: target is the current agent");
35284
+ return null;
35285
+ }
35286
+ if (typeof reason !== "string" || reason.length === 0 || reason.length > REASON_MAX) {
35287
+ log.debug("handoffProtocol", 'dropped proposal: invalid "reason"');
35288
+ return null;
35289
+ }
35290
+ if (typeof prompt !== "string" || prompt.length === 0 || prompt.length > PROMPT_MAX2) {
35291
+ log.debug("handoffProtocol", 'dropped proposal: invalid "prompt"');
35292
+ return null;
35293
+ }
35294
+ return { to, reason, prompt };
35295
+ }
35296
+ function stripFences(masked) {
35297
+ const stripped = masked.replace(FENCE_RE, "");
35298
+ return stripped.replace(/(?:\r?\n){3,}/g, "\n\n").trim();
35299
+ }
35300
+ function extractHandoffProposal(text, currentAgent, validTargets) {
35301
+ const { masked, restore } = maskOuterFences(text);
35302
+ const matches = [...masked.matchAll(FENCE_RE)];
35303
+ if (matches.length === 0) {
35304
+ return { cleanText: text, proposal: null };
35305
+ }
35306
+ const last = matches[matches.length - 1];
35307
+ const proposal = parseProposal(last[1].trim(), currentAgent, validTargets);
35308
+ const cleanText = restore(stripFences(masked));
35309
+ return { cleanText, proposal };
35310
+ }
35311
+ function stripHandoffFences(text) {
35312
+ const { masked, restore } = maskOuterFences(text);
35313
+ if (masked.search(FENCE_RE) === -1) {
35314
+ return text;
35315
+ }
35316
+ return restore(stripFences(masked));
35317
+ }
35318
+ function handoffFenceStartMasked(text) {
35319
+ const { masked } = maskOuterFences(text);
35320
+ if (masked.indexOf(FENCE_OPEN) === -1) return -1;
35321
+ const spans = [];
35322
+ for (const m of text.matchAll(OUTER_FENCE_RE)) {
35323
+ const start2 = m.index ?? 0;
35324
+ spans.push({ start: start2, end: start2 + m[0].length });
35325
+ }
35326
+ const insideSpan = (idx) => spans.some((s) => idx >= s.start && idx < s.end);
35327
+ for (let i = text.indexOf(FENCE_OPEN); i !== -1; i = text.indexOf(FENCE_OPEN, i + 1)) {
35328
+ if (!insideSpan(i)) return i;
35329
+ }
35330
+ return -1;
35331
+ }
35332
+
34876
35333
  // src/agents/acp/onboarding.ts
34877
35334
  var import_child_process28 = require("child_process");
34878
- var fs67 = __toESM(require("fs"));
34879
- var os58 = __toESM(require("os"));
34880
- var path74 = __toESM(require("path"));
35335
+ var fs68 = __toESM(require("fs"));
35336
+ var os59 = __toESM(require("os"));
35337
+ var path75 = __toESM(require("path"));
34881
35338
  var _onboardingSeam = {
34882
- markerPath: (sessionId) => path74.join(os58.homedir(), ".codeam", "welcomed", `${sessionId}.done`),
34883
- exists: (p2) => fs67.existsSync(p2),
35339
+ markerPath: (sessionId) => path75.join(os59.homedir(), ".codeam", "welcomed", `${sessionId}.done`),
35340
+ exists: (p2) => fs68.existsSync(p2),
34884
35341
  write: (p2) => {
34885
- fs67.mkdirSync(path74.dirname(p2), { recursive: true });
34886
- fs67.writeFileSync(p2, "");
35342
+ fs68.mkdirSync(path75.dirname(p2), { recursive: true });
35343
+ fs68.writeFileSync(p2, "");
34887
35344
  },
34888
35345
  disabled: () => {
34889
35346
  const v = process.env.CODEAM_ONBOARDING_DISABLED;
@@ -34920,7 +35377,7 @@ function resolveRepoName(cwd) {
34920
35377
  if (name) return name;
34921
35378
  }
34922
35379
  }
34923
- const base = path74.basename(cwd || "");
35380
+ const base = path75.basename(cwd || "");
34924
35381
  if (base && !isUuid(base)) return base;
34925
35382
  return "this project";
34926
35383
  }
@@ -35178,13 +35635,13 @@ var import_crypto5 = require("crypto");
35178
35635
 
35179
35636
  // src/services/turn-files/git-changeset.ts
35180
35637
  var import_child_process29 = require("child_process");
35181
- var fs69 = __toESM(require("fs/promises"));
35182
- var path76 = __toESM(require("path"));
35638
+ var fs70 = __toESM(require("fs/promises"));
35639
+ var path77 = __toESM(require("path"));
35183
35640
 
35184
35641
  // src/services/turn-files/review-ignore.ts
35185
35642
  var import_ignore2 = __toESM(require("ignore"));
35186
- var fs68 = __toESM(require("fs"));
35187
- var path75 = __toESM(require("path"));
35643
+ var fs69 = __toESM(require("fs"));
35644
+ var path76 = __toESM(require("path"));
35188
35645
  var CURATED_REVIEW_IGNORE = [
35189
35646
  // Google Cloud SDK (the incident) — installs a huge python tree.
35190
35647
  "google-cloud-sdk/",
@@ -35223,7 +35680,7 @@ var CURATED_REVIEW_IGNORE = [
35223
35680
  function makeReviewIgnore(repoRoot) {
35224
35681
  const ig = (0, import_ignore2.default)().add(CURATED_REVIEW_IGNORE);
35225
35682
  try {
35226
- const custom = fs68.readFileSync(path75.join(repoRoot, ".codeam", "reviewignore"), "utf8");
35683
+ const custom = fs69.readFileSync(path76.join(repoRoot, ".codeam", "reviewignore"), "utf8");
35227
35684
  ig.add(custom);
35228
35685
  } catch {
35229
35686
  }
@@ -35268,7 +35725,7 @@ async function collectRepoChangeset(opts) {
35268
35725
  let stats;
35269
35726
  if (!truncated && row.fileStatus === "added" && numstatEntry === void 0) {
35270
35727
  const lineCount = await readUntrackedLineCount(
35271
- path76.join(opts.repoRoot, row.filePath)
35728
+ path77.join(opts.repoRoot, row.filePath)
35272
35729
  );
35273
35730
  stats = { added: lineCount, removed: 0 };
35274
35731
  } else {
@@ -35299,7 +35756,7 @@ function readUntrackedLineCount(absPath) {
35299
35756
  }
35300
35757
  async function defaultReadUntrackedLineCount(absPath) {
35301
35758
  try {
35302
- const content = await fs69.readFile(absPath, "utf8");
35759
+ const content = await fs70.readFile(absPath, "utf8");
35303
35760
  let count = 0;
35304
35761
  let pos = -1;
35305
35762
  while ((pos = content.indexOf("\n", pos + 1)) !== -1) {
@@ -35391,7 +35848,7 @@ function defaultRunGit(cwd, args2) {
35391
35848
  });
35392
35849
  }
35393
35850
  async function discoverRepos(workingDir, maxDepth = 4) {
35394
- const fs79 = await import("fs/promises");
35851
+ const fs80 = await import("fs/promises");
35395
35852
  const out2 = [];
35396
35853
  await walk(workingDir, 0);
35397
35854
  return out2;
@@ -35399,7 +35856,7 @@ async function discoverRepos(workingDir, maxDepth = 4) {
35399
35856
  if (depth > maxDepth) return;
35400
35857
  let entries = [];
35401
35858
  try {
35402
- const dirents = await fs79.readdir(dir, { withFileTypes: true });
35859
+ const dirents = await fs80.readdir(dir, { withFileTypes: true });
35403
35860
  entries = dirents.filter((d3) => !d3.name.startsWith(".") || d3.name === ".git").map((d3) => ({ name: d3.name, isDirectory: d3.isDirectory() }));
35404
35861
  } catch {
35405
35862
  return;
@@ -35410,8 +35867,8 @@ async function discoverRepos(workingDir, maxDepth = 4) {
35410
35867
  if (hasGit) {
35411
35868
  out2.push({
35412
35869
  repoRoot: dir,
35413
- repoPath: path76.relative(workingDir, dir),
35414
- repoName: path76.basename(dir)
35870
+ repoPath: path77.relative(workingDir, dir),
35871
+ repoName: path77.basename(dir)
35415
35872
  });
35416
35873
  return;
35417
35874
  }
@@ -35419,14 +35876,14 @@ async function discoverRepos(workingDir, maxDepth = 4) {
35419
35876
  if (!entry.isDirectory) continue;
35420
35877
  if (entry.name === "node_modules") continue;
35421
35878
  if (entry.name === "dist" || entry.name === "build") continue;
35422
- await walk(path76.join(dir, entry.name), depth + 1);
35879
+ await walk(path77.join(dir, entry.name), depth + 1);
35423
35880
  }
35424
35881
  }
35425
35882
  }
35426
35883
 
35427
35884
  // src/services/turn-files/files-outbox.ts
35428
- var fs70 = __toESM(require("fs/promises"));
35429
- var path77 = __toESM(require("path"));
35885
+ var fs71 = __toESM(require("fs/promises"));
35886
+ var path78 = __toESM(require("path"));
35430
35887
  var import_os12 = require("os");
35431
35888
  var HOME_OUTBOX_DIR = ".codeam/outbox";
35432
35889
  var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
@@ -35459,16 +35916,16 @@ var FilesOutbox = class {
35459
35916
  backoffIndex = 0;
35460
35917
  stopped = false;
35461
35918
  constructor(opts) {
35462
- const base = opts.baseDir ?? path77.join(homeDir(), HOME_OUTBOX_DIR);
35463
- this.filePath = path77.join(base, `${opts.sessionId}.jsonl`);
35919
+ const base = opts.baseDir ?? path78.join(homeDir(), HOME_OUTBOX_DIR);
35920
+ this.filePath = path78.join(base, `${opts.sessionId}.jsonl`);
35464
35921
  this.post = opts.post;
35465
35922
  this.autoSchedule = opts.autoSchedule !== false;
35466
35923
  }
35467
35924
  /** Persist the entry to disk and trigger a flush. Returns once the
35468
35925
  * line is durable on disk (not once the POST succeeds). */
35469
35926
  async enqueue(entry) {
35470
- await fs70.mkdir(path77.dirname(this.filePath), { recursive: true });
35471
- await fs70.appendFile(this.filePath, JSON.stringify(entry) + "\n", "utf8");
35927
+ await fs71.mkdir(path78.dirname(this.filePath), { recursive: true });
35928
+ await fs71.appendFile(this.filePath, JSON.stringify(entry) + "\n", "utf8");
35472
35929
  this.backoffIndex = 0;
35473
35930
  if (this.autoSchedule) this.scheduleFlush(0);
35474
35931
  }
@@ -35559,7 +36016,7 @@ var FilesOutbox = class {
35559
36016
  async readAll() {
35560
36017
  let raw = "";
35561
36018
  try {
35562
- raw = await fs70.readFile(this.filePath, "utf8");
36019
+ raw = await fs71.readFile(this.filePath, "utf8");
35563
36020
  } catch {
35564
36021
  return [];
35565
36022
  }
@@ -35583,12 +36040,12 @@ var FilesOutbox = class {
35583
36040
  async rewrite(entries) {
35584
36041
  const tmpPath = `${this.filePath}.${process.pid}.tmp`;
35585
36042
  if (entries.length === 0) {
35586
- await fs70.unlink(this.filePath).catch(() => void 0);
36043
+ await fs71.unlink(this.filePath).catch(() => void 0);
35587
36044
  return;
35588
36045
  }
35589
36046
  const payload = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
35590
- await fs70.writeFile(tmpPath, payload, "utf8");
35591
- await fs70.rename(tmpPath, this.filePath);
36047
+ await fs71.writeFile(tmpPath, payload, "utf8");
36048
+ await fs71.rename(tmpPath, this.filePath);
35592
36049
  }
35593
36050
  };
35594
36051
  function applyJitter(ms) {
@@ -35650,11 +36107,34 @@ var TurnFileAggregator = class {
35650
36107
  */
35651
36108
  baselineByKey = /* @__PURE__ */ new Map();
35652
36109
  baselineCaptured = false;
36110
+ /**
36111
+ * The file paths (relative to their repo root) that the MOST RECENT
36112
+ * `flushTurn()` call found novel — i.e. what it just enqueued (or would
36113
+ * have enqueued had the batch not exactly matched the baseline). Updated
36114
+ * unconditionally on every flush, including a no-op one (empties back
36115
+ * out), so `peekTurnPaths()` never returns paths from more than one
36116
+ * flush cycle ago. Not touched by the baseline-capture flush (it returns
36117
+ * before `novel` is computed), which is correct — pre-pair files aren't
36118
+ * "this session's" changes.
36119
+ */
36120
+ lastTurnPaths = [];
35653
36121
  /** Stop the outbox scheduler. Idempotent. */
35654
36122
  stop() {
35655
36123
  this.stopped = true;
35656
36124
  this.outbox.stop();
35657
36125
  }
36126
+ /**
36127
+ * Non-destructive read of the paths the MOST RECENT `flushTurn()` call
36128
+ * found novel — does NOT trigger a scan or clear anything itself. The
36129
+ * squad journal (`recordSquadTurn` in `command-handlers.ts`) `await`s
36130
+ * `flushTurn()` for the turn it's about to record BEFORE calling this, so
36131
+ * the read reflects THAT turn's paths, not a stale one left over from
36132
+ * whatever flushed previously. Empty on a session's very first turn
36133
+ * (still capturing the pre-pair baseline) or a chat-only turn.
36134
+ */
36135
+ peekTurnPaths() {
36136
+ return [...this.lastTurnPaths];
36137
+ }
35658
36138
  /**
35659
36139
  * Run the discovery + git collection + POST pipeline for one
35660
36140
  * turn. Errors are swallowed (logged) so an agent never blocks on a
@@ -35695,6 +36175,7 @@ var TurnFileAggregator = class {
35695
36175
  if (!base) return true;
35696
36176
  return base.linesAdded !== f.linesAdded || base.linesRemoved !== f.linesRemoved || base.fileStatus !== f.fileStatus;
35697
36177
  });
36178
+ this.lastTurnPaths = novel.map((f) => f.filePath);
35698
36179
  if (novel.length === 0) {
35699
36180
  log.trace(
35700
36181
  "turnFiles",
@@ -35714,10 +36195,7 @@ var TurnFileAggregator = class {
35714
36195
  await this.outbox.enqueue(entry);
35715
36196
  }
35716
36197
  } catch (err) {
35717
- log.warn(
35718
- "turnFiles",
35719
- `flushTurn failed: ${err.message ?? String(err)}`
35720
- );
36198
+ log.warn("turnFiles", `flushTurn failed: ${err.message ?? String(err)}`);
35721
36199
  }
35722
36200
  }
35723
36201
  /**
@@ -36044,8 +36522,8 @@ async function postBudgetReached(opts, fetchImpl = fetch) {
36044
36522
 
36045
36523
  // src/packs/gates.ts
36046
36524
  var import_node_child_process30 = require("child_process");
36047
- var fs71 = __toESM(require("fs"));
36048
- var path78 = __toESM(require("path"));
36525
+ var fs72 = __toESM(require("fs"));
36526
+ var path79 = __toESM(require("path"));
36049
36527
  var defaultCommandRunner = (file, args2, cwd, timeoutMs) => new Promise((resolve9) => {
36050
36528
  (0, import_node_child_process30.execFile)(
36051
36529
  file,
@@ -36082,14 +36560,14 @@ var NO_TEST_PLACEHOLDER = 'echo "Error: no test specified"';
36082
36560
  var CHECKS_TIMEOUT_MS = 5 * 6e4;
36083
36561
  function detectChecksCommand(cwd) {
36084
36562
  try {
36085
- const cfg = JSON.parse(fs71.readFileSync(path78.join(cwd, ".codeam", "pack.json"), "utf8"));
36563
+ const cfg = JSON.parse(fs72.readFileSync(path79.join(cwd, ".codeam", "pack.json"), "utf8"));
36086
36564
  if (typeof cfg.checksCommand === "string" && cfg.checksCommand.trim().length > 0) {
36087
36565
  return cfg.checksCommand.trim();
36088
36566
  }
36089
36567
  } catch {
36090
36568
  }
36091
36569
  try {
36092
- const pkg = JSON.parse(fs71.readFileSync(path78.join(cwd, "package.json"), "utf8"));
36570
+ const pkg = JSON.parse(fs72.readFileSync(path79.join(cwd, "package.json"), "utf8"));
36093
36571
  const test = pkg.scripts?.test;
36094
36572
  if (typeof test === "string" && test.trim().length > 0 && !test.includes(NO_TEST_PLACEHOLDER)) {
36095
36573
  return "npm test";
@@ -36107,39 +36585,39 @@ ${res.stderr}`.trim();
36107
36585
  }
36108
36586
 
36109
36587
  // src/packs/run-store.ts
36110
- var fs72 = __toESM(require("fs"));
36111
- var path79 = __toESM(require("path"));
36588
+ var fs73 = __toESM(require("fs"));
36589
+ var path80 = __toESM(require("path"));
36112
36590
  var crypto5 = __toESM(require("crypto"));
36113
36591
  function packsDir(cwd) {
36114
- return path79.join(cwd, ".codeam", "packs");
36592
+ return path80.join(cwd, ".codeam", "packs");
36115
36593
  }
36116
36594
  function runDir(cwd, runId) {
36117
- return path79.join(packsDir(cwd), runId);
36595
+ return path80.join(packsDir(cwd), runId);
36118
36596
  }
36119
36597
  function newRunId() {
36120
36598
  return `pk_${Date.now().toString(36)}_${crypto5.randomBytes(4).toString("hex")}`;
36121
36599
  }
36122
36600
  function writeJsonAtomic(file, value) {
36123
- fs72.mkdirSync(path79.dirname(file), { recursive: true });
36601
+ fs73.mkdirSync(path80.dirname(file), { recursive: true });
36124
36602
  const tmp = `${file}.tmp`;
36125
- fs72.writeFileSync(tmp, JSON.stringify(value, null, 2));
36126
- fs72.renameSync(tmp, file);
36603
+ fs73.writeFileSync(tmp, JSON.stringify(value, null, 2));
36604
+ fs73.renameSync(tmp, file);
36127
36605
  }
36128
36606
  function saveRun(cwd, state) {
36129
- writeJsonAtomic(path79.join(runDir(cwd, state.runId), "run.json"), state);
36607
+ writeJsonAtomic(path80.join(runDir(cwd, state.runId), "run.json"), state);
36130
36608
  }
36131
36609
  function saveStageHandoff(cwd, runId, stageIndex, role, handoff) {
36132
36610
  const name = `${String(stageIndex + 1).padStart(2, "0")}-${role}.json`;
36133
- writeJsonAtomic(path79.join(runDir(cwd, runId), name), handoff);
36611
+ writeJsonAtomic(path80.join(runDir(cwd, runId), name), handoff);
36134
36612
  }
36135
36613
  function loadLatestRun(cwd) {
36136
36614
  try {
36137
36615
  const dir = packsDir(cwd);
36138
- const entries = fs72.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
36616
+ const entries = fs73.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
36139
36617
  for (let i = entries.length - 1; i >= 0; i--) {
36140
- const file = path79.join(dir, entries[i], "run.json");
36618
+ const file = path80.join(dir, entries[i], "run.json");
36141
36619
  try {
36142
- const parsed = JSON.parse(fs72.readFileSync(file, "utf8"));
36620
+ const parsed = JSON.parse(fs73.readFileSync(file, "utf8"));
36143
36621
  if (parsed && typeof parsed.runId === "string") return parsed;
36144
36622
  } catch {
36145
36623
  }
@@ -36150,17 +36628,17 @@ function loadLatestRun(cwd) {
36150
36628
  }
36151
36629
  function ensureLedgerIgnored(cwd) {
36152
36630
  try {
36153
- const gitDir = path79.join(cwd, ".git");
36154
- if (!fs72.existsSync(gitDir)) return;
36155
- const exclude = path79.join(gitDir, "info", "exclude");
36631
+ const gitDir = path80.join(cwd, ".git");
36632
+ if (!fs73.existsSync(gitDir)) return;
36633
+ const exclude = path80.join(gitDir, "info", "exclude");
36156
36634
  let existing = "";
36157
36635
  try {
36158
- existing = fs72.readFileSync(exclude, "utf8");
36636
+ existing = fs73.readFileSync(exclude, "utf8");
36159
36637
  } catch {
36160
36638
  }
36161
36639
  if (existing.includes(".codeam/packs/")) return;
36162
- fs72.mkdirSync(path79.dirname(exclude), { recursive: true });
36163
- fs72.writeFileSync(exclude, `${existing.trimEnd()}
36640
+ fs73.mkdirSync(path80.dirname(exclude), { recursive: true });
36641
+ fs73.writeFileSync(exclude, `${existing.trimEnd()}
36164
36642
  .codeam/packs/
36165
36643
  `.trimStart());
36166
36644
  } catch {
@@ -36713,6 +37191,56 @@ async function detectRepoStack(cwd, runtime) {
36713
37191
  }
36714
37192
  }
36715
37193
 
37194
+ // src/agents/acp/coderabbit-mention.ts
37195
+ var CODERABBIT_AGENT_ID = "coderabbit";
37196
+ var CODERABBIT_NOT_LINKED_MESSAGE = "Link CodeRabbit in Profile \u203A Your Squad first.";
37197
+ var CODERABBIT_NO_CUSTOM_INSTRUCTIONS_NOTICE = "CodeRabbit reviews your current changes \u2014 custom instructions aren't supported yet.";
37198
+ function hasCustomInstructions(prompt) {
37199
+ return prompt.replace(/@coderabbit\b/gi, "").trim().length > 0;
37200
+ }
37201
+ function composeReviewOutput(markdown, custom) {
37202
+ const body = markdown.trim().length > 0 ? markdown.trim() : "CodeRabbit found no issues to report.";
37203
+ return custom ? `${CODERABBIT_NO_CUSTOM_INSTRUCTIONS_NOTICE}
37204
+
37205
+ ${body}` : body;
37206
+ }
37207
+ function defaultRunReview(input) {
37208
+ return new CoderabbitRuntimeStrategy(createOsStrategy()).runOneShot(input);
37209
+ }
37210
+ async function runCoderabbitMentionReview(deps = {}) {
37211
+ const configure = deps.configure ?? configureCoderabbit;
37212
+ const runReview = deps.runReview ?? defaultRunReview;
37213
+ try {
37214
+ const status2 = await configure({ action: "status" });
37215
+ if (!status2.loggedIn) {
37216
+ const cred = deps.fetchCredential ? await deps.fetchCredential() : null;
37217
+ if (!cred) return { ok: false, error: CODERABBIT_NOT_LINKED_MESSAGE };
37218
+ const provisioned = await configure({
37219
+ action: "provision",
37220
+ provisionCredential: cred
37221
+ });
37222
+ if (!provisioned.loggedIn) {
37223
+ return { ok: false, error: provisioned.error ?? CODERABBIT_NOT_LINKED_MESSAGE };
37224
+ }
37225
+ }
37226
+ const result = await configure({ action: "review" }, { runReview });
37227
+ if (result.error) return { ok: false, error: result.error };
37228
+ return { ok: true, markdown: result.review?.markdown ?? "" };
37229
+ } catch (err) {
37230
+ return {
37231
+ ok: false,
37232
+ error: err instanceof Error ? err.message : "CodeRabbit review failed"
37233
+ };
37234
+ }
37235
+ }
37236
+ function logMentionOutcome(result) {
37237
+ if (result.ok) {
37238
+ log.info("acpRunner", `squad: coderabbit review completed (${result.markdown.length} chars)`);
37239
+ } else {
37240
+ log.warn("acpRunner", `squad: coderabbit review failed: ${result.error}`);
37241
+ }
37242
+ }
37243
+
36716
37244
  // src/agents/acp/command-handlers.ts
36717
37245
  var import_node_child_process31 = require("child_process");
36718
37246
 
@@ -36751,35 +37279,35 @@ function buildAcpPromptBlocks(payload) {
36751
37279
  }
36752
37280
 
36753
37281
  // src/agents/agent-standard.ts
36754
- var fs74 = __toESM(require("fs"));
36755
- var path81 = __toESM(require("path"));
36756
- var os59 = __toESM(require("os"));
36757
- function ensureAgentStandard(homeDir2 = os59.homedir()) {
37282
+ var fs75 = __toESM(require("fs"));
37283
+ var path82 = __toESM(require("path"));
37284
+ var os60 = __toESM(require("os"));
37285
+ function ensureAgentStandard(homeDir2 = os60.homedir()) {
36758
37286
  try {
36759
- const file = path81.join(homeDir2, ".claude", "CLAUDE.md");
37287
+ const file = path82.join(homeDir2, ".claude", "CLAUDE.md");
36760
37288
  let existing = "";
36761
37289
  try {
36762
- existing = fs74.readFileSync(file, "utf8");
37290
+ existing = fs75.readFileSync(file, "utf8");
36763
37291
  } catch {
36764
37292
  }
36765
37293
  if (existing.includes(AGENT_STANDARD_MARKER)) return;
36766
- fs74.mkdirSync(path81.dirname(file), { recursive: true });
37294
+ fs75.mkdirSync(path82.dirname(file), { recursive: true });
36767
37295
  const next = existing.trim() ? `${existing.trimEnd()}
36768
37296
 
36769
37297
  ${AGENT_STANDARD_BLOCK}
36770
37298
  ` : `${AGENT_STANDARD_BLOCK}
36771
37299
  `;
36772
- fs74.writeFileSync(file, next);
37300
+ fs75.writeFileSync(file, next);
36773
37301
  } catch {
36774
37302
  }
36775
37303
  }
36776
37304
  var _agentStandardSeam = {
36777
37305
  isLocalSession: () => isLocalSession(),
36778
- markerPath: (sessionId) => path81.join(os59.homedir(), ".codeam", "agent-standard", `${sessionId}.done`),
36779
- exists: (p2) => fs74.existsSync(p2),
37306
+ markerPath: (sessionId) => path82.join(os60.homedir(), ".codeam", "agent-standard", `${sessionId}.done`),
37307
+ exists: (p2) => fs75.existsSync(p2),
36780
37308
  write: (p2) => {
36781
- fs74.mkdirSync(path81.dirname(p2), { recursive: true });
36782
- fs74.writeFileSync(p2, "");
37309
+ fs75.mkdirSync(path82.dirname(p2), { recursive: true });
37310
+ fs75.writeFileSync(p2, "");
36783
37311
  }
36784
37312
  };
36785
37313
  function isClaude(agent) {
@@ -36883,27 +37411,228 @@ async function beadsActionH(ctx) {
36883
37411
  }
36884
37412
  return;
36885
37413
  }
37414
+ function rebindSessionHandles(ctx, outcome) {
37415
+ Object.assign(ctx, outcome.handles);
37416
+ return outcome;
37417
+ }
37418
+ async function routeSquadTask(ctx, target) {
37419
+ const { squad, routeToAgent } = ctx;
37420
+ if (!routeToAgent) {
37421
+ return { ok: false, error: "Routing to another agent is not supported on this session." };
37422
+ }
37423
+ const roster = squad?.roster;
37424
+ if (roster && !roster.agents.some((a) => a.agentId === target)) {
37425
+ return { ok: false, error: `Unknown agent '${target}' \u2014 not in your squad.` };
37426
+ }
37427
+ const member = squad?.member(target);
37428
+ const fastPathArmed = Boolean(member && (member.provisioned || member.binaryVerified));
37429
+ let { result } = rebindSessionHandles(ctx, await routeToAgent(target));
37430
+ if (!result.ok && fastPathArmed) {
37431
+ log.warn(
37432
+ "acpRunner",
37433
+ `squad: fast-path route to ${target} failed (${result.error}) \u2014 retrying full path`
37434
+ );
37435
+ ({ result } = rebindSessionHandles(ctx, await routeToAgent(target, { skipFastPath: true })));
37436
+ }
37437
+ if (!result.ok) {
37438
+ return { ok: false, error: result.error ?? `Couldn't switch to ${target}.` };
37439
+ }
37440
+ return { ok: true };
37441
+ }
37442
+ function collectSquadContext(ctx) {
37443
+ const pieces = [];
37444
+ const { squad, opts } = ctx;
37445
+ if (squad) {
37446
+ const member = squad.member(opts.agent);
37447
+ const roster = squad.roster;
37448
+ if (roster && member.lastTurnIndex === 0) {
37449
+ const preamble = buildTeamPreamble(roster, opts.agent, {
37450
+ handoffInstructions: roster.handoffsEnabled === true
37451
+ });
37452
+ if (preamble) pieces.push(preamble);
37453
+ }
37454
+ if (squad.turnCount() > member.lastTurnIndex) {
37455
+ const otherEntries = squad.entriesSince(member.lastTurnIndex).filter((e) => e.agentId !== opts.agent);
37456
+ const briefing = buildDeltaBriefing(otherEntries);
37457
+ if (briefing) pieces.push(briefing);
37458
+ }
37459
+ }
37460
+ if (ctx.pendingHandoff?.current) {
37461
+ pieces.push(ctx.pendingHandoff.current);
37462
+ ctx.pendingHandoff.current = null;
37463
+ }
37464
+ return pieces;
37465
+ }
37466
+ function squadContextMode(ctx) {
37467
+ return ctx.squad?.member(ctx.opts.agent).contextTextFallback === true ? "text" : "resource";
37468
+ }
37469
+ function applySquadContext(blocks, pieces, mode) {
37470
+ if (pieces.length === 0) return;
37471
+ if (mode === "resource") {
37472
+ blocks.unshift(buildSquadContextBlock(pieces.join("\n\n")));
37473
+ return;
37474
+ }
37475
+ for (let i = pieces.length - 1; i >= 0; i--) blocks.unshift({ type: "text", text: pieces[i] });
37476
+ }
37477
+ async function promptWithContextFallback(ctx, client3, blocks, pieces) {
37478
+ try {
37479
+ return await client3.prompt(blocks);
37480
+ } catch (err) {
37481
+ if (pieces.length === 0 || blocks.length === 0 || !isSquadContextBlock(blocks[0]) || !looksLikeUnsupportedPromptShape(err)) {
37482
+ throw err;
37483
+ }
37484
+ log.warn(
37485
+ "acpRunner",
37486
+ `squad: ${ctx.opts.agent} rejected the native squad-context resource block (${describeError(err)}) \u2014 retrying once with legacy text blocks`
37487
+ );
37488
+ if (ctx.squad) ctx.squad.member(ctx.opts.agent).contextTextFallback = true;
37489
+ blocks.shift();
37490
+ applySquadContext(blocks, pieces, "text");
37491
+ return await client3.prompt(blocks);
37492
+ }
37493
+ }
37494
+ function recordSquadTurn(ctx, prompt, replySummary) {
37495
+ const { squad, opts, turnFiles } = ctx;
37496
+ if (!squad) return;
37497
+ squad.recordTurn({
37498
+ agentId: opts.agent,
37499
+ prompt,
37500
+ replySummary,
37501
+ // TurnFileAggregator owns per-turn file changesets end-to-end (git diff →
37502
+ // outbox POST). The caller (`startTaskH`) AWAITS `turnFiles.flushTurn()`
37503
+ // for THIS turn before calling recordSquadTurn precisely so
37504
+ // `peekTurnPaths()` reflects THIS turn's novel files, not a stale read
37505
+ // of whatever the aggregator's PREVIOUS flush happened to find. Capped
37506
+ // so a pathological turn (mass refactor) doesn't bloat the journal.
37507
+ filesTouched: turnFiles.peekTurnPaths().slice(0, 20)
37508
+ });
37509
+ squad.member(opts.agent).lastTurnIndex = squad.turnCount();
37510
+ }
37511
+ function handoffTargets(ctx) {
37512
+ const roster = ctx.squad?.roster;
37513
+ if (!roster) return /* @__PURE__ */ new Set();
37514
+ return new Set(roster.agents.map((a) => a.agentId).filter((id) => id !== ctx.opts.agent));
37515
+ }
37516
+ function resolvePendingProposal(ctx, requestedAgentId) {
37517
+ const slot = ctx.pendingProposal;
37518
+ const pending = slot?.current;
37519
+ if (!slot || !pending) return;
37520
+ slot.current = null;
37521
+ const accepted = requestedAgentId === pending.toAgentId;
37522
+ if (accepted) ctx.squad?.countAccepted();
37523
+ log.info(
37524
+ "acpRunner",
37525
+ `squad: handoff ${pending.proposalId} ${accepted ? "accepted" : "declined"}`
37526
+ );
37527
+ const resolution = { proposalId: pending.proposalId, accepted };
37528
+ void ctx.postSquadEvent?.("handoff_resolved", { ...resolution });
37529
+ }
37530
+ function proposalIdFor(commandId, hop) {
37531
+ const id = hop <= 1 ? `hp-${commandId}` : `hp-${commandId}-h${hop}`;
37532
+ return id.length > 128 ? id.slice(0, 128) : id;
37533
+ }
37534
+ function emitHandoffProposal(ctx, proposal, hop) {
37535
+ const { squad, pendingProposal, postSquadEvent, opts, cmd } = ctx;
37536
+ if (!proposal || !pendingProposal || !postSquadEvent) return null;
37537
+ if (squad?.roster?.handoffsEnabled !== true) return null;
37538
+ if (pendingProposal.current) {
37539
+ log.info("acpRunner", "squad: dropping handoff proposal \u2014 one is already pending");
37540
+ return null;
37541
+ }
37542
+ const auto = squad.auto.enabled && squad.hopsRemaining() > 0;
37543
+ if (auto) squad.consumeHop();
37544
+ const record2 = {
37545
+ proposalId: proposalIdFor(cmd.id, hop),
37546
+ fromAgentId: opts.agent,
37547
+ toAgentId: proposal.to,
37548
+ reason: proposal.reason,
37549
+ prompt: proposal.prompt,
37550
+ ...auto ? { auto: true, hopsRemaining: squad.hopsRemaining() } : {}
37551
+ };
37552
+ squad.countProposal({ auto });
37553
+ log.info(
37554
+ "acpRunner",
37555
+ `squad: handoff proposed ${opts.agent} \u2192 ${record2.toAgentId}${auto ? " (auto)" : ""}`
37556
+ );
37557
+ void postSquadEvent("handoff_proposed", { ...record2 });
37558
+ if (!auto) {
37559
+ pendingProposal.current = record2;
37560
+ return null;
37561
+ }
37562
+ return record2;
37563
+ }
37564
+ function resolveAutoHandoff(ctx, record2) {
37565
+ ctx.squad?.countAccepted();
37566
+ const resolution = {
37567
+ proposalId: record2.proposalId,
37568
+ accepted: true,
37569
+ auto: true
37570
+ };
37571
+ void ctx.postSquadEvent?.("handoff_resolved", { ...resolution });
37572
+ }
37573
+ async function runCoderabbitMention(ctx, promptText) {
37574
+ const { cmd, relay, streaming, opts, turnFiles } = ctx;
37575
+ const userText = promptText.length > 0 ? promptText : `@${CODERABBIT_AGENT_ID}`;
37576
+ await streaming.beginTurn();
37577
+ ctx.history.appendUserPrompt(userText);
37578
+ log.info("acpRunner", `squad: coderabbit one-shot review id=${cmd.id.slice(0, 8)}`);
37579
+ const review = await runCoderabbitMentionReview({
37580
+ fetchCredential: () => fetchProvisionCredential({
37581
+ agentId: CODERABBIT_AGENT_ID,
37582
+ sessionId: opts.sessionId,
37583
+ pluginId: opts.pluginId,
37584
+ pluginAuthToken: opts.pluginAuthToken
37585
+ })
37586
+ });
37587
+ logMentionOutcome(review);
37588
+ if (!review.ok) {
37589
+ await streaming.closeWithBubble(review.error);
37590
+ ctx.history.appendAgentReply(review.error, CODERABBIT_AGENT_ID);
37591
+ void ctx.history.flush();
37592
+ await relay.sendResult(cmd.id, "failed", { error: review.error });
37593
+ return;
37594
+ }
37595
+ const output = composeReviewOutput(review.markdown, hasCustomInstructions(promptText));
37596
+ await streaming.closeWithBubble(output);
37597
+ ctx.history.appendAgentReply(output, CODERABBIT_AGENT_ID);
37598
+ void ctx.history.flush();
37599
+ await turnFiles.flushTurn().catch((err) => {
37600
+ log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
37601
+ });
37602
+ recordCoderabbitTurn(ctx, userText, output);
37603
+ await relay.sendResult(cmd.id, "completed", { agentId: CODERABBIT_AGENT_ID });
37604
+ }
37605
+ function recordCoderabbitTurn(ctx, prompt, replySummary) {
37606
+ ctx.squad?.recordTurn({
37607
+ agentId: CODERABBIT_AGENT_ID,
37608
+ prompt,
37609
+ replySummary,
37610
+ filesTouched: []
37611
+ });
37612
+ }
36886
37613
  async function startTaskH(ctx) {
36887
- const {
36888
- cmd,
36889
- client: client3,
36890
- relay,
36891
- streaming,
36892
- opts,
36893
- history,
36894
- turnFiles,
36895
- publisher,
36896
- recentStderr,
36897
- budgetRecovery,
36898
- budgetReachedFlag
36899
- } = ctx;
37614
+ const { cmd, relay, streaming, opts, turnFiles, publisher, recentStderr, budgetReachedFlag } = ctx;
36900
37615
  const payload = cmd.payload;
37616
+ const requestedAgentId = typeof payload?.agentId === "string" ? payload.agentId.trim() : "";
37617
+ resolvePendingProposal(ctx, requestedAgentId);
37618
+ if (requestedAgentId === CODERABBIT_AGENT_ID) {
37619
+ await runCoderabbitMention(ctx, (payload?.prompt ?? "").trim());
37620
+ return;
37621
+ }
36901
37622
  const blocks = buildAcpPromptBlocks(payload ?? {});
36902
37623
  if (blocks.length === 0) {
36903
37624
  log.warn("acpRunner", "start_task with empty prompt + no attachments; ignoring");
36904
37625
  await relay.sendResult(cmd.id, "failed", { error: "empty prompt" });
36905
37626
  return;
36906
37627
  }
37628
+ if (requestedAgentId.length > 0 && requestedAgentId !== opts.agent) {
37629
+ const routed = await routeSquadTask(ctx, requestedAgentId);
37630
+ if (!routed.ok) {
37631
+ log.warn("acpRunner", `start_task routing to ${requestedAgentId} failed: ${routed.error}`);
37632
+ await relay.sendResult(cmd.id, "failed", { error: routed.error });
37633
+ return;
37634
+ }
37635
+ }
36907
37636
  const promptText = blocks.filter((b) => b.type === "text").map((b) => b.text).join("\n");
36908
37637
  const imageCount = blocks.filter((b) => b.type === "image").length;
36909
37638
  log.info(
@@ -36915,129 +37644,214 @@ async function startTaskH(ctx) {
36915
37644
  showInfo(echoLine);
36916
37645
  }
36917
37646
  await streaming.beginTurn();
36918
- history.appendUserPrompt(promptText);
37647
+ ctx.history.appendUserPrompt(promptText);
36919
37648
  maybePrefaceAgentStandard(blocks, opts.agent, opts.sessionId);
36920
- if (ctx.pendingHandoff?.current) {
36921
- blocks.unshift({ type: "text", text: ctx.pendingHandoff.current });
36922
- ctx.pendingHandoff.current = null;
36923
- }
36924
- let turnClosed = false;
36925
- try {
36926
- const reply = await client3.prompt(blocks);
36927
- const finalText = streaming.getCurrentText();
36928
- if (agentHooks(opts.agent)?.classifyCompletedReply?.(finalText) === "upgrade_required") {
36929
- await streaming.closeWithBubble(CURSOR_UPGRADE_MESSAGE);
36930
- turnClosed = true;
36931
- history.appendAgentReply(CURSOR_UPGRADE_MESSAGE);
36932
- void history.flush();
36933
- log.info("acpRunner", `start_task \u2190 cursor-plan-upgrade-required id=${cmd.id.slice(0, 8)}`);
36934
- await relay.sendResult(cmd.id, "failed", { error: "cursor plan upgrade required" });
36935
- } else if (replyIsHouseAgentLimit(finalText)) {
36936
- const houseBubble = houseAgentLimitMessage(finalText);
36937
- await streaming.closeWithBubble(houseBubble);
36938
- turnClosed = true;
36939
- history.appendAgentReply(houseBubble);
36940
- void history.flush();
36941
- turnFiles.flushTurn().catch((err) => {
36942
- log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
36943
- });
36944
- log.info("acpRunner", `start_task \u2190 house-agent-limit id=${cmd.id.slice(0, 8)}`);
36945
- await relay.sendResult(cmd.id, "failed", {
36946
- error: "house agent usage ceiling / temporarily unavailable"
36947
- });
36948
- } else if (replyIsAuthFailure(finalText)) {
36949
- await streaming.closeWithBubble(AUTH_FAILURE_MESSAGE);
36950
- turnClosed = true;
36951
- history.appendAgentReply(AUTH_FAILURE_MESSAGE);
36952
- void history.flush();
36953
- turnFiles.flushTurn().catch((err) => {
36954
- log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
36955
- });
36956
- void reportCredentialInvalid(opts);
36957
- log.info("acpRunner", `start_task \u2190 auth-failure-in-reply id=${cmd.id.slice(0, 8)}`);
36958
- await relay.sendResult(cmd.id, "failed", { error: "agent reply reported auth failure" });
36959
- } else if (shouldOfferOneMRecovery({ detail: "", recentStderr: recentStderr.join("\n"), finalText })) {
36960
- await streaming.closeWithBubble(ONE_M_CREDITS_MESSAGE);
36961
- turnClosed = true;
36962
- history.appendAgentReply(ONE_M_CREDITS_MESSAGE);
36963
- void history.flush();
36964
- turnFiles.flushTurn().catch((err) => {
36965
- log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
36966
- });
36967
- void reportCredentialInvalid(opts);
36968
- log.info("acpRunner", `start_task \u2190 1m-credits-reconnect id=${cmd.id.slice(0, 8)}`);
36969
- await relay.sendResult(cmd.id, "failed", {
36970
- error: "agent reply reported 1M-context usage-credits gate"
36971
- });
36972
- } else {
36973
- await streaming.closeTurnWithInteractiveDetection();
36974
- turnClosed = true;
36975
- const replyLine = formatAgentReplyLine(finalText);
36976
- if (replyLine.length > 0) {
36977
- showInfo(replyLine);
36978
- }
36979
- history.appendAgentReply(finalText);
36980
- void history.flush();
36981
- void publisher.publishOutput({
36982
- type: "input_suggestion",
36983
- content: ACP_QUICK_REPLIES,
36984
- done: true
36985
- });
36986
- turnFiles.flushTurn().catch((err) => {
36987
- log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
36988
- });
36989
- log.info("acpRunner", `start_task \u2190 done stopReason=${reply.stopReason ?? "?"} id=${cmd.id.slice(0, 8)}`);
36990
- await relay.sendResult(cmd.id, "completed", { stopReason: reply.stopReason });
36991
- }
36992
- } catch (err) {
36993
- if (turnClosed) {
36994
- log.warn(
36995
- "acpRunner",
36996
- `post-close ack failed (turn already delivered) id=${cmd.id.slice(0, 8)}: ${describeError(err)}`
36997
- );
36998
- return;
36999
- }
37000
- const hadText = streaming.hasVisibleProgress();
37001
- const detail = describeError(err);
37002
- log.warn("acpRunner", `prompt failed: ${detail}`);
37003
- await cancelStuckTurn(client3);
37004
- if (looksLikeBudgetExceeded(`${detail}
37005
- ${recentStderr.join("\n")}`)) {
37006
- await streaming.closeAll();
37007
- if (!budgetReachedFlag.get()) {
37008
- budgetReachedFlag.set(true);
37009
- void postBudgetReached({
37010
- sessionId: opts.sessionId,
37011
- pluginId: opts.pluginId,
37012
- pluginAuthToken: opts.pluginAuthToken,
37013
- agent: opts.agent,
37014
- period: extractBudgetPeriod(`${detail}
37015
- ${recentStderr.join("\n")}`)
37649
+ const squadContext = collectSquadContext(ctx);
37650
+ applySquadContext(blocks, squadContext, squadContextMode(ctx));
37651
+ ctx.squad?.resetHops();
37652
+ let turnBlocks = blocks;
37653
+ let turnPieces = squadContext;
37654
+ let turnPrompt = promptText;
37655
+ let hop = 1;
37656
+ for (; ; ) {
37657
+ const { client: client3, history, budgetRecovery } = ctx;
37658
+ let turnClosed = false;
37659
+ try {
37660
+ const reply = await promptWithContextFallback(ctx, client3, turnBlocks, turnPieces);
37661
+ const finalText = streaming.getCurrentText();
37662
+ if (agentHooks(opts.agent)?.classifyCompletedReply?.(finalText) === "upgrade_required") {
37663
+ await streaming.closeWithBubble(CURSOR_UPGRADE_MESSAGE);
37664
+ turnClosed = true;
37665
+ history.appendAgentReply(CURSOR_UPGRADE_MESSAGE);
37666
+ void history.flush();
37667
+ log.info("acpRunner", `start_task \u2190 cursor-plan-upgrade-required id=${cmd.id.slice(0, 8)}`);
37668
+ await relay.sendResult(cmd.id, "failed", { error: "cursor plan upgrade required" });
37669
+ return;
37670
+ } else if (replyIsHouseAgentLimit(finalText)) {
37671
+ const houseBubble = houseAgentLimitMessage(finalText);
37672
+ await streaming.closeWithBubble(houseBubble);
37673
+ turnClosed = true;
37674
+ history.appendAgentReply(houseBubble);
37675
+ void history.flush();
37676
+ turnFiles.flushTurn().catch((err) => {
37677
+ log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
37678
+ });
37679
+ log.info("acpRunner", `start_task \u2190 house-agent-limit id=${cmd.id.slice(0, 8)}`);
37680
+ await relay.sendResult(cmd.id, "failed", {
37681
+ error: "house agent usage ceiling / temporarily unavailable"
37682
+ });
37683
+ return;
37684
+ } else if (replyIsAuthFailure(finalText)) {
37685
+ await streaming.closeWithBubble(AUTH_FAILURE_MESSAGE);
37686
+ turnClosed = true;
37687
+ history.appendAgentReply(AUTH_FAILURE_MESSAGE);
37688
+ void history.flush();
37689
+ turnFiles.flushTurn().catch((err) => {
37690
+ log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
37691
+ });
37692
+ void reportCredentialInvalid(opts);
37693
+ log.info("acpRunner", `start_task \u2190 auth-failure-in-reply id=${cmd.id.slice(0, 8)}`);
37694
+ await relay.sendResult(cmd.id, "failed", { error: "agent reply reported auth failure" });
37695
+ return;
37696
+ } else if (shouldOfferOneMRecovery({ detail: "", recentStderr: recentStderr.join("\n"), finalText })) {
37697
+ await streaming.closeWithBubble(ONE_M_CREDITS_MESSAGE);
37698
+ turnClosed = true;
37699
+ history.appendAgentReply(ONE_M_CREDITS_MESSAGE);
37700
+ void history.flush();
37701
+ turnFiles.flushTurn().catch((err) => {
37702
+ log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
37703
+ });
37704
+ void reportCredentialInvalid(opts);
37705
+ log.info("acpRunner", `start_task \u2190 1m-credits-reconnect id=${cmd.id.slice(0, 8)}`);
37706
+ await relay.sendResult(cmd.id, "failed", {
37707
+ error: "agent reply reported 1M-context usage-credits gate"
37708
+ });
37709
+ return;
37710
+ } else {
37711
+ await streaming.closeTurnWithInteractiveDetection();
37712
+ turnClosed = true;
37713
+ const { cleanText, proposal } = extractHandoffProposal(
37714
+ finalText,
37715
+ opts.agent,
37716
+ handoffTargets(ctx)
37717
+ );
37718
+ const replyLine = formatAgentReplyLine(cleanText);
37719
+ if (replyLine.length > 0) {
37720
+ showInfo(replyLine);
37721
+ }
37722
+ history.appendAgentReply(cleanText);
37723
+ void history.flush();
37724
+ const flush = turnFiles.flushTurn().catch((err) => {
37725
+ log.warn("acpRunner", `turnFiles.flushTurn failed: ${describeError(err)}`);
37016
37726
  });
37727
+ if (ctx.squad) await flush;
37728
+ recordSquadTurn(ctx, turnPrompt, cleanText);
37729
+ const autoHop = emitHandoffProposal(ctx, proposal, hop);
37730
+ if (autoHop) {
37731
+ const routed = await routeSquadTask(ctx, autoHop.toAgentId);
37732
+ if (routed.ok) {
37733
+ resolveAutoHandoff(ctx, autoHop);
37734
+ hop += 1;
37735
+ turnPrompt = autoHop.prompt;
37736
+ turnPieces = collectSquadContext(ctx);
37737
+ turnBlocks = [{ type: "text", text: turnPrompt }];
37738
+ maybePrefaceAgentStandard(turnBlocks, opts.agent, opts.sessionId);
37739
+ applySquadContext(turnBlocks, turnPieces, squadContextMode(ctx));
37740
+ await streaming.beginTurn();
37741
+ ctx.history.appendUserPrompt(turnPrompt);
37742
+ continue;
37743
+ }
37744
+ log.warn(
37745
+ "acpRunner",
37746
+ `squad: auto-handoff to ${autoHop.toAgentId} failed: ${routed.error}`
37747
+ );
37748
+ }
37749
+ void publisher.publishOutput({
37750
+ type: "input_suggestion",
37751
+ content: ACP_QUICK_REPLIES,
37752
+ done: true
37753
+ });
37754
+ log.info(
37755
+ "acpRunner",
37756
+ `start_task \u2190 done stopReason=${reply.stopReason ?? "?"} id=${cmd.id.slice(0, 8)}`
37757
+ );
37758
+ await relay.sendResult(cmd.id, "completed", { stopReason: reply.stopReason });
37759
+ return;
37760
+ }
37761
+ } catch (err) {
37762
+ if (turnClosed) {
37763
+ log.warn(
37764
+ "acpRunner",
37765
+ `post-close ack failed (turn already delivered) id=${cmd.id.slice(0, 8)}: ${describeError(err)}`
37766
+ );
37767
+ return;
37017
37768
  }
37018
- await budgetRecovery.offer(cmd.id, blocks, `${detail}
37769
+ const hadText = streaming.hasVisibleProgress();
37770
+ const detail = describeError(err);
37771
+ log.warn("acpRunner", `prompt failed: ${detail}`);
37772
+ await cancelStuckTurn(client3);
37773
+ if (looksLikeBudgetExceeded(`${detail}
37774
+ ${recentStderr.join("\n")}`)) {
37775
+ await streaming.closeAll();
37776
+ if (!budgetReachedFlag.get()) {
37777
+ budgetReachedFlag.set(true);
37778
+ void postBudgetReached({
37779
+ sessionId: opts.sessionId,
37780
+ pluginId: opts.pluginId,
37781
+ pluginAuthToken: opts.pluginAuthToken,
37782
+ agent: opts.agent,
37783
+ period: extractBudgetPeriod(`${detail}
37784
+ ${recentStderr.join("\n")}`)
37785
+ });
37786
+ }
37787
+ await budgetRecovery.offer(cmd.id, turnBlocks, `${detail}
37019
37788
  ${recentStderr.join("\n")}`);
37789
+ return;
37790
+ }
37791
+ const bubble = failureBubble({
37792
+ detail,
37793
+ recentStderr: recentStderr.join("\n"),
37794
+ hadText,
37795
+ agent: opts.agent
37796
+ });
37797
+ if (bubble) {
37798
+ await streaming.closeWithBubble(bubble);
37799
+ history.appendAgentReply(bubble);
37800
+ void history.flush();
37801
+ } else {
37802
+ await streaming.closeAll();
37803
+ }
37804
+ if (bubble === AUTH_FAILURE_MESSAGE || bubble === ONE_M_CREDITS_MESSAGE) {
37805
+ void reportCredentialInvalid(opts);
37806
+ }
37807
+ await relay.sendResult(cmd.id, "failed", { error: detail });
37020
37808
  return;
37021
37809
  }
37022
- const bubble = failureBubble({
37023
- detail,
37024
- recentStderr: recentStderr.join("\n"),
37025
- hadText,
37026
- agent: opts.agent
37810
+ }
37811
+ }
37812
+ async function squadConfigureH(ctx) {
37813
+ const { cmd, relay, opts, squad } = ctx;
37814
+ if (!squad) {
37815
+ await relay.sendResult(cmd.id, "failed", {
37816
+ error: "Agent Squad is not available on this session."
37027
37817
  });
37028
- if (bubble) {
37029
- await streaming.closeWithBubble(bubble);
37030
- history.appendAgentReply(bubble);
37031
- void history.flush();
37032
- } else {
37033
- await streaming.closeAll();
37034
- }
37035
- if (bubble === AUTH_FAILURE_MESSAGE || bubble === ONE_M_CREDITS_MESSAGE) {
37036
- void reportCredentialInvalid(opts);
37037
- }
37038
- await relay.sendResult(cmd.id, "failed", { error: detail });
37818
+ return;
37039
37819
  }
37040
- return;
37820
+ const payload = cmd.payload;
37821
+ if (payload?.action === "set") {
37822
+ const applied = squad.setAuto({
37823
+ enabled: payload.autoHandoffs === true,
37824
+ hopBudget: clampHopBudget(payload.hopBudget)
37825
+ });
37826
+ setSquadAuto(opts.pluginId, applied);
37827
+ log.info(
37828
+ "acpRunner",
37829
+ `squad: auto handoffs ${applied.enabled ? "ON" : "OFF"} budget=${applied.hopBudget}`
37830
+ );
37831
+ const result = { ...applied, hopsRemaining: squad.hopsRemaining() };
37832
+ await relay.sendResult(cmd.id, "completed", { ...result });
37833
+ return;
37834
+ }
37835
+ if (payload?.action === "status") {
37836
+ const result = {
37837
+ ...squad.auto,
37838
+ hopsRemaining: squad.hopsRemaining()
37839
+ };
37840
+ await relay.sendResult(cmd.id, "completed", { ...result });
37841
+ return;
37842
+ }
37843
+ await relay.sendResult(cmd.id, "failed", { error: "squad_configure: unknown action" });
37844
+ }
37845
+ async function squadStatsH(ctx) {
37846
+ const { cmd, relay, squad } = ctx;
37847
+ if (!squad) {
37848
+ await relay.sendResult(cmd.id, "failed", {
37849
+ error: "Agent Squad is not available on this session."
37850
+ });
37851
+ return;
37852
+ }
37853
+ const stats = squad.stats();
37854
+ await relay.sendResult(cmd.id, "completed", { ...stats });
37041
37855
  }
37042
37856
  async function groupMentionTaskH(ctx) {
37043
37857
  const { cmd, client: client3, relay, streaming, opts, history } = ctx;
@@ -37050,6 +37864,7 @@ async function groupMentionTaskH(ctx) {
37050
37864
  });
37051
37865
  return;
37052
37866
  }
37867
+ ctx.squad?.resetHops();
37053
37868
  await streaming.beginTurn();
37054
37869
  history.appendUserPrompt(promptText);
37055
37870
  let response = "";
@@ -37444,7 +38259,10 @@ async function integrationsSyncH(ctx) {
37444
38259
  attached: manifest.integrations.map((e) => e.id)
37445
38260
  });
37446
38261
  } catch (err) {
37447
- log.warn("acpRunner", `integrations_sync failed (tools apply next restart): ${describeError(err)}`);
38262
+ log.warn(
38263
+ "acpRunner",
38264
+ `integrations_sync failed (tools apply next restart): ${describeError(err)}`
38265
+ );
37448
38266
  await relay.sendResult(cmd.id, "completed", { synced: false, error: describeError(err) });
37449
38267
  }
37450
38268
  }
@@ -37514,7 +38332,9 @@ var ACP_COMMAND_HANDLERS = {
37514
38332
  skills_configure: skillsConfigureH2,
37515
38333
  pack_start: packStartH,
37516
38334
  pack_action: packActionH,
37517
- pack_status: packStatusH
38335
+ pack_status: packStatusH,
38336
+ [SQUAD_CONFIGURE_COMMAND]: squadConfigureH,
38337
+ [SQUAD_STATS_COMMAND]: squadStatsH
37518
38338
  };
37519
38339
  async function dispatchAcpCommand(ctx) {
37520
38340
  const handler = ACP_COMMAND_HANDLERS[ctx.cmd.type];
@@ -37776,14 +38596,18 @@ var StreamingState = class {
37776
38596
  }
37777
38597
  const cumulativeContent = reconcileCumulative(existing?.content ?? "", delta.delta);
37778
38598
  this.streamingChunks.set(chunkId, { kind: delta.kind, content: cumulativeContent });
38599
+ let fenceCut = -1;
37779
38600
  if (delta.kind === "text") {
37780
38601
  this.recomputeText();
37781
- void this.publisher.publishOutput({ type: "text", content: this.text, done: false });
38602
+ fenceCut = handoffFenceStartMasked(this.text);
38603
+ const visibleText = fenceCut === -1 ? this.text : this.text.slice(0, fenceCut).trimEnd();
38604
+ void this.publisher.publishOutput({ type: "text", content: visibleText, done: false });
37782
38605
  }
38606
+ const visibleChunkContent = delta.kind === "text" && fenceCut !== -1 ? cumulativeContent.slice(0, fenceCut).trimEnd() : cumulativeContent;
37783
38607
  void this.publisher.publishStreamingChunk({
37784
38608
  chunkId,
37785
38609
  kind: delta.kind,
37786
- content: cumulativeContent,
38610
+ content: visibleChunkContent,
37787
38611
  isFinal: false
37788
38612
  });
37789
38613
  }
@@ -37813,8 +38637,28 @@ var StreamingState = class {
37813
38637
  * spuriously and strand the runner with a free-form pending state
37814
38638
  * the user can't see / answer.
37815
38639
  */
38640
+ /**
38641
+ * Presentation-only view of a text buffer: everything BEFORE a
38642
+ * ```codeam-handoff fence. The fence is protocol litter the app renders as
38643
+ * a proposal card (see `handoff-protocol.ts`) — it must never reach the
38644
+ * user, and the TERMINAL frames (`done:true` / `isFinal:true`) are the ones
38645
+ * that persist, so suppressing it only while streaming would still leave it
38646
+ * pinned on the finished bubble. Internal state is untouched:
38647
+ * {@link getCurrentText} keeps returning the RAW text so the turn-close
38648
+ * extraction can parse the proposal out of it.
38649
+ *
38650
+ * Masked-aware (`stripHandoffFences`), same as the live-stream `append()`
38651
+ * cut (`handoffFenceStartMasked`): a TERMINAL frame is the one that
38652
+ * PERSISTS, so cutting on a fence quoted as an example inside a
38653
+ * 4+-backtick block here would permanently truncate the bubble — and an
38654
+ * unmasked live cut would truncate the LIVE view for the rest of the turn
38655
+ * the moment the quoted example's fence-open marker streams in.
38656
+ */
38657
+ visible(text) {
38658
+ return stripHandoffFences(text);
38659
+ }
37816
38660
  async closeAll() {
37817
- const finalText = this.text;
38661
+ const finalText = this.visible(this.text);
37818
38662
  this.text = "";
37819
38663
  await Promise.all([
37820
38664
  this.publisher.publishOutput({ type: "text", content: finalText, done: true }),
@@ -37858,7 +38702,9 @@ var StreamingState = class {
37858
38702
  ([chunkId, { kind, content }]) => this.publisher.publishStreamingChunk({
37859
38703
  chunkId,
37860
38704
  kind,
37861
- content,
38705
+ // Terminal frame — same fence suppression the live deltas apply, so
38706
+ // a proposal fence never survives on the finalised bubble.
38707
+ content: kind === "text" ? this.visible(content) : content,
37862
38708
  isFinal: true
37863
38709
  })
37864
38710
  )
@@ -37885,7 +38731,7 @@ var StreamingState = class {
37885
38731
  * text done:true chunk with the full cumulative).
37886
38732
  */
37887
38733
  async closeTurnWithInteractiveDetection() {
37888
- const finalText = this.text;
38734
+ const finalText = this.visible(this.text);
37889
38735
  this.text = "";
37890
38736
  const flushSc = this.flushStreamingChunks();
37891
38737
  const extracted = extractSelectPrompt(finalText);
@@ -37982,16 +38828,24 @@ var AcpHistory = class {
37982
38828
  id: (0, import_node_crypto11.randomUUID)(),
37983
38829
  role: "user",
37984
38830
  text,
37985
- timestamp: Date.now()
38831
+ timestamp: Date.now(),
38832
+ agentId: this.opts.agent
37986
38833
  });
37987
38834
  }
37988
- appendAgentReply(text) {
38835
+ /**
38836
+ * `agentId` overrides the producing agent for a turn this session's RESIDENT
38837
+ * agent did not author — today only the `@coderabbit` one-shot review, which
38838
+ * runs the batch reviewer inside the session without ever swapping. Defaults
38839
+ * to the resident agent.
38840
+ */
38841
+ appendAgentReply(text, agentId = this.opts.agent) {
37989
38842
  if (text.length === 0) return;
37990
38843
  this.messages.push({
37991
38844
  id: (0, import_node_crypto11.randomUUID)(),
37992
38845
  role: "agent",
37993
38846
  text,
37994
- timestamp: Date.now()
38847
+ timestamp: Date.now(),
38848
+ agentId
37995
38849
  });
37996
38850
  }
37997
38851
  /**
@@ -38331,6 +39185,8 @@ async function runAcpSession(opts) {
38331
39185
  pluginAuthToken: opts.pluginAuthToken,
38332
39186
  agentId: opts.agent
38333
39187
  });
39188
+ void turnFiles.flushTurn().catch(() => {
39189
+ });
38334
39190
  const REPO_DIRTY_FLUSH_DEBOUNCE_MS = 2e3;
38335
39191
  let repoDirtyTimer = null;
38336
39192
  const fileWatcher = new FileWatcherService({
@@ -38376,9 +39232,12 @@ async function runAcpSession(opts) {
38376
39232
  publisher,
38377
39233
  recentStderr,
38378
39234
  budgetRecovery,
38379
- { get: () => _budgetReachedPosted, set: (v) => {
38380
- _budgetReachedPosted = v;
38381
- } },
39235
+ {
39236
+ get: () => _budgetReachedPosted,
39237
+ set: (v) => {
39238
+ _budgetReachedPosted = v;
39239
+ }
39240
+ },
38382
39241
  // resume_session re-points the runner's active conversation: the
38383
39242
  // relay callback reads `acpSessionId` per command, so every FUTURE
38384
39243
  // get_conversation / upload / one-shot serves the RESUMED id — not
@@ -38388,13 +39247,35 @@ async function runAcpSession(opts) {
38388
39247
  acpSessionId = id;
38389
39248
  },
38390
39249
  switchAgentForSession,
38391
- pendingHandoff
39250
+ pendingHandoff,
39251
+ // Agent Squad: roster/journal state, the @-mention route, the
39252
+ // single-slot proposal, and the SAME serialized event chain the
39253
+ // switch uses (so proposal events can't overtake switch events).
39254
+ squad,
39255
+ routeToAgent,
39256
+ pendingProposal,
39257
+ emitSwitchEvent
38392
39258
  );
38393
39259
  },
38394
39260
  { id: opts.agent, name: opts.agent, displayName: opts.agent }
38395
39261
  );
38396
39262
  const HANDOFF_MAX_CHARS = 16e3;
38397
39263
  const pendingHandoff = { current: null };
39264
+ const squad = new SquadState({
39265
+ sessionId: opts.sessionId,
39266
+ auto: getSquadAuto(opts.pluginId)
39267
+ });
39268
+ const refreshSquadRoster = () => {
39269
+ void fetchSquadRoster({
39270
+ sessionId: opts.sessionId,
39271
+ pluginId: opts.pluginId,
39272
+ pluginAuthToken: opts.pluginAuthToken
39273
+ }).then((roster) => {
39274
+ if (roster) squad.roster = roster;
39275
+ });
39276
+ };
39277
+ refreshSquadRoster();
39278
+ const pendingProposal = { current: null };
38398
39279
  let switchCredentialEnv = {};
38399
39280
  const relaunchWith = async (nextAgent) => {
38400
39281
  try {
@@ -38404,6 +39285,7 @@ async function runAcpSession(opts) {
38404
39285
  await streaming.closeAll();
38405
39286
  const prevAgent = opts.agent;
38406
39287
  const transcript = history.recentTranscript(HANDOFF_MAX_CHARS);
39288
+ squad.member(prevAgent).acpSessionId = acpSessionId;
38407
39289
  await client3.stop();
38408
39290
  const adapter = await resolveAcpAdapterWithRetry(nextAgent);
38409
39291
  if (!adapter) throw new Error(`no ACP adapter available for ${nextAgent}`);
@@ -38453,31 +39335,74 @@ async function runAcpSession(opts) {
38453
39335
  payload
38454
39336
  })
38455
39337
  );
38456
- const switchAgentForSession = (rawAgentId) => performAgentSwitch(
38457
- {
38458
- currentAgent: () => opts.agent,
38459
- postEvent: emitSwitchEvent,
38460
- fetchCredential: (agentId) => fetchProvisionCredential({
38461
- agentId,
38462
- sessionId: opts.sessionId,
38463
- pluginId: opts.pluginId,
38464
- pluginAuthToken: opts.pluginAuthToken,
38465
- includeInstallScript: true
38466
- }),
38467
- provisionCredential: (agentId, auth) => {
38468
- switchCredentialEnv = provisionAgentCredentials(agentId, auth);
38469
- },
38470
- ensureBinary: (agentId, installScript) => ensureAgentBinaryForSwitch(agentId, installScript),
38471
- swapRuntime: relaunchWith,
38472
- revertRuntime: relaunchWith,
38473
- persistAgent: (agentId) => setSessionAgent(opts.pluginId, agentId),
38474
- reannounce: (agentId) => {
38475
- relay.setAgentMeta({ id: agentId, name: agentId, displayName: agentId });
38476
- relay.reannounceAgents();
38477
- }
39338
+ const switchDeps = {
39339
+ currentAgent: () => opts.agent,
39340
+ postEvent: emitSwitchEvent,
39341
+ fetchCredential: (agentId) => fetchProvisionCredential({
39342
+ agentId,
39343
+ sessionId: opts.sessionId,
39344
+ pluginId: opts.pluginId,
39345
+ pluginAuthToken: opts.pluginAuthToken,
39346
+ includeInstallScript: true
39347
+ }),
39348
+ provisionCredential: (agentId, auth) => {
39349
+ switchCredentialEnv = provisionAgentCredentials(agentId, auth);
38478
39350
  },
38479
- rawAgentId
38480
- );
39351
+ ensureBinary: (agentId, installScript) => ensureAgentBinaryForSwitch(agentId, installScript),
39352
+ swapRuntime: relaunchWith,
39353
+ revertRuntime: relaunchWith,
39354
+ persistAgent: (agentId) => setSessionAgent(opts.pluginId, agentId),
39355
+ reannounce: (agentId) => {
39356
+ relay.setAgentMeta({ id: agentId, name: agentId, displayName: agentId });
39357
+ relay.reannounceAgents();
39358
+ }
39359
+ };
39360
+ const switchAgentForSession = async (rawAgentId) => {
39361
+ const result = await performAgentSwitch(switchDeps, rawAgentId);
39362
+ if (result.ok) refreshSquadRoster();
39363
+ return result;
39364
+ };
39365
+ const sessionHandles = () => ({
39366
+ client: client3,
39367
+ acpSessionId,
39368
+ history,
39369
+ jsonlHistory,
39370
+ agentCaps,
39371
+ budgetRecovery
39372
+ });
39373
+ const routeToAgent = async (target, routeOpts = {}) => {
39374
+ const m = squad.member(target);
39375
+ const fast = routeOpts.skipFastPath !== true;
39376
+ const result = await performAgentSwitch(switchDeps, target, {
39377
+ skipProvision: fast && m.provisioned,
39378
+ skipInstall: fast && m.binaryVerified
39379
+ });
39380
+ if (!result.ok) {
39381
+ if (fast) {
39382
+ m.provisioned = false;
39383
+ m.binaryVerified = false;
39384
+ }
39385
+ return { result, handles: sessionHandles() };
39386
+ }
39387
+ m.provisioned = true;
39388
+ m.binaryVerified = true;
39389
+ if (m.acpSessionId && agentCaps?.loadSession) {
39390
+ try {
39391
+ await client3.loadSession(m.acpSessionId);
39392
+ acpSessionId = m.acpSessionId;
39393
+ history.switchActiveSession(m.acpSessionId);
39394
+ pendingHandoff.current = null;
39395
+ log.info(
39396
+ "acpRunner",
39397
+ `squad: resumed ${target}'s conversation ${m.acpSessionId.slice(0, 8)}`
39398
+ );
39399
+ } catch (err) {
39400
+ log.warn("acpRunner", `squad: resume for ${target} failed: ${describeError(err)}`);
39401
+ }
39402
+ }
39403
+ refreshSquadRoster();
39404
+ return { result, handles: sessionHandles() };
39405
+ };
38481
39406
  await onboardingWelcomeDone;
38482
39407
  relay.start();
38483
39408
  void createWakeCredentialProbe({
@@ -38508,7 +39433,7 @@ async function runAcpSession(opts) {
38508
39433
  await new Promise(() => {
38509
39434
  });
38510
39435
  }
38511
- async function handleCommand(cmd, client3, relay, acpSessionId, streaming, opts, history, jsonlHistory, agentCaps, turnFiles, getBeads, publisher, recentStderr, budgetRecovery, budgetReachedFlag, onActiveSessionChanged, switchAgent, pendingHandoff) {
39436
+ async function handleCommand(cmd, client3, relay, acpSessionId, streaming, opts, history, jsonlHistory, agentCaps, turnFiles, getBeads, publisher, recentStderr, budgetRecovery, budgetReachedFlag, onActiveSessionChanged, switchAgent, pendingHandoff, squad, routeToAgent, pendingProposal, postSquadEvent) {
38512
39437
  const session = {
38513
39438
  client: client3,
38514
39439
  relay,
@@ -38526,7 +39451,11 @@ async function handleCommand(cmd, client3, relay, acpSessionId, streaming, opts,
38526
39451
  budgetReachedFlag,
38527
39452
  onActiveSessionChanged,
38528
39453
  switchAgent,
38529
- pendingHandoff
39454
+ pendingHandoff,
39455
+ squad,
39456
+ routeToAgent,
39457
+ pendingProposal,
39458
+ postSquadEvent
38530
39459
  };
38531
39460
  await dispatchAcpCommand(assembleAcpCommandContext(session, cmd));
38532
39461
  }
@@ -39586,9 +40515,9 @@ function startClaudeCredentialSync(opts) {
39586
40515
  }
39587
40516
 
39588
40517
  // src/beads/workflow-hint.ts
39589
- var fs75 = __toESM(require("fs"));
39590
- var path82 = __toESM(require("path"));
39591
- var os60 = __toESM(require("os"));
40518
+ var fs76 = __toESM(require("fs"));
40519
+ var path83 = __toESM(require("path"));
40520
+ var os61 = __toESM(require("os"));
39592
40521
  var BEADS_HINT_MARKER = "<!-- codeam:beads-workflow -->";
39593
40522
  var BEADS_HINT = `${BEADS_HINT_MARKER}
39594
40523
  # Beads (bd) \u2014 task tracking + persistent memory (ALWAYS use it)
@@ -39602,22 +40531,22 @@ This environment uses **bd (beads)** for issue/task tracking and persistent memo
39602
40531
  - \`bd ready\` (available work) \xB7 \`bd show <id>\` \xB7 \`bd update <id> --claim\` \xB7 \`bd close <id>\`.
39603
40532
  - Use \`bd remember "..."\` for persistent knowledge \u2014 do NOT use MEMORY.md files.
39604
40533
  ${BEADS_HINT_MARKER}`;
39605
- function ensureBeadsWorkflowHint(homeDir2 = os60.homedir()) {
40534
+ function ensureBeadsWorkflowHint(homeDir2 = os61.homedir()) {
39606
40535
  try {
39607
- const file = path82.join(homeDir2, ".claude", "CLAUDE.md");
40536
+ const file = path83.join(homeDir2, ".claude", "CLAUDE.md");
39608
40537
  let existing = "";
39609
40538
  try {
39610
- existing = fs75.readFileSync(file, "utf8");
40539
+ existing = fs76.readFileSync(file, "utf8");
39611
40540
  } catch {
39612
40541
  }
39613
40542
  if (existing.includes(BEADS_HINT_MARKER)) return;
39614
- fs75.mkdirSync(path82.dirname(file), { recursive: true });
40543
+ fs76.mkdirSync(path83.dirname(file), { recursive: true });
39615
40544
  const next = existing.trim() ? `${existing.trimEnd()}
39616
40545
 
39617
40546
  ${BEADS_HINT}
39618
40547
  ` : `${BEADS_HINT}
39619
40548
  `;
39620
- fs75.writeFileSync(file, next);
40549
+ fs76.writeFileSync(file, next);
39621
40550
  } catch {
39622
40551
  }
39623
40552
  }
@@ -39989,7 +40918,7 @@ var AcpDriver = class {
39989
40918
  };
39990
40919
 
39991
40920
  // src/baton/transcript-mirror.ts
39992
- var fs76 = __toESM(require("fs"));
40921
+ var fs77 = __toESM(require("fs"));
39993
40922
  var TranscriptMirror = class {
39994
40923
  constructor(deps) {
39995
40924
  this.deps = deps;
@@ -40056,7 +40985,7 @@ var TranscriptMirror = class {
40056
40985
  }
40057
40986
  };
40058
40987
  function defaultWatch(file, onChange) {
40059
- const w3 = fs76.watch(file, { persistent: false }, () => onChange());
40988
+ const w3 = fs77.watch(file, { persistent: false }, () => onChange());
40060
40989
  return () => w3.close();
40061
40990
  }
40062
40991
 
@@ -40354,16 +41283,16 @@ function keepDeviceAwake(deps = {}) {
40354
41283
  }
40355
41284
 
40356
41285
  // src/agents/claude/onboarding.ts
40357
- var fs77 = __toESM(require("fs"));
40358
- var os62 = __toESM(require("os"));
40359
- var path83 = __toESM(require("path"));
41286
+ var fs78 = __toESM(require("fs"));
41287
+ var os63 = __toESM(require("os"));
41288
+ var path84 = __toESM(require("path"));
40360
41289
  var ONBOARDING_VERSION_SENTINEL = "9999.0.0";
40361
41290
  function ensureClaudeOnboarded(cwd) {
40362
41291
  try {
40363
- const file = path83.join(os62.homedir(), ".claude.json");
41292
+ const file = path84.join(os63.homedir(), ".claude.json");
40364
41293
  let config = {};
40365
41294
  try {
40366
- config = JSON.parse(fs77.readFileSync(file, "utf8"));
41295
+ config = JSON.parse(fs78.readFileSync(file, "utf8"));
40367
41296
  } catch {
40368
41297
  }
40369
41298
  let changed = false;
@@ -40388,8 +41317,8 @@ function ensureClaudeOnboarded(cwd) {
40388
41317
  }
40389
41318
  }
40390
41319
  if (!changed) return;
40391
- fs77.mkdirSync(path83.dirname(file), { recursive: true });
40392
- fs77.writeFileSync(file, JSON.stringify(config, null, 2));
41320
+ fs78.mkdirSync(path84.dirname(file), { recursive: true });
41321
+ fs78.writeFileSync(file, JSON.stringify(config, null, 2));
40393
41322
  log.info(
40394
41323
  "claude",
40395
41324
  `pre-completed Claude onboarding${cwd ? ` + trusted workspace ${cwd}` : ""}`
@@ -41121,7 +42050,7 @@ var import_picocolors11 = __toESM(require("picocolors"));
41121
42050
  var import_child_process30 = require("child_process");
41122
42051
  var import_util4 = require("util");
41123
42052
  var import_picocolors9 = __toESM(require("picocolors"));
41124
- var path84 = __toESM(require("path"));
42053
+ var path85 = __toESM(require("path"));
41125
42054
  var execFileP6 = (0, import_util4.promisify)(import_child_process30.execFile);
41126
42055
  var MAX_BUFFER = 8 * 1024 * 1024;
41127
42056
  function resetStdinForChild() {
@@ -41610,7 +42539,7 @@ var GitHubCodespacesProvider = class {
41610
42539
  });
41611
42540
  }
41612
42541
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
41613
- const remoteDir = path84.posix.dirname(remotePath);
42542
+ const remoteDir = path85.posix.dirname(remotePath);
41614
42543
  const parts = [
41615
42544
  `mkdir -p ${shellQuote(remoteDir)}`,
41616
42545
  `cat > ${shellQuote(remotePath)}`
@@ -41680,7 +42609,7 @@ function shellQuote(s) {
41680
42609
  // src/services/providers/gitpod.ts
41681
42610
  var import_child_process31 = require("child_process");
41682
42611
  var import_util5 = require("util");
41683
- var path85 = __toESM(require("path"));
42612
+ var path86 = __toESM(require("path"));
41684
42613
  var import_picocolors10 = __toESM(require("picocolors"));
41685
42614
  var execFileP7 = (0, import_util5.promisify)(import_child_process31.execFile);
41686
42615
  var MAX_BUFFER2 = 8 * 1024 * 1024;
@@ -41920,7 +42849,7 @@ var GitpodProvider = class {
41920
42849
  });
41921
42850
  }
41922
42851
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
41923
- const remoteDir = path85.posix.dirname(remotePath);
42852
+ const remoteDir = path86.posix.dirname(remotePath);
41924
42853
  const parts = [
41925
42854
  `mkdir -p ${shellQuote2(remoteDir)}`,
41926
42855
  `cat > ${shellQuote2(remotePath)}`
@@ -41956,7 +42885,7 @@ function shellQuote2(s) {
41956
42885
  // src/services/providers/gitlab-workspaces.ts
41957
42886
  var import_child_process32 = require("child_process");
41958
42887
  var import_util6 = require("util");
41959
- var path86 = __toESM(require("path"));
42888
+ var path87 = __toESM(require("path"));
41960
42889
  var execFileP8 = (0, import_util6.promisify)(import_child_process32.execFile);
41961
42890
  var MAX_BUFFER3 = 8 * 1024 * 1024;
41962
42891
  var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
@@ -42216,7 +43145,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
42216
43145
  }
42217
43146
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
42218
43147
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
42219
- const remoteDir = path86.posix.dirname(remotePath);
43148
+ const remoteDir = path87.posix.dirname(remotePath);
42220
43149
  const parts = [`mkdir -p ${shellQuote3(remoteDir)}`, `cat > ${shellQuote3(remotePath)}`];
42221
43150
  if (options.mode != null) {
42222
43151
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
@@ -42284,7 +43213,7 @@ function shellQuote3(s) {
42284
43213
  // src/services/providers/railway.ts
42285
43214
  var import_child_process33 = require("child_process");
42286
43215
  var import_util7 = require("util");
42287
- var path87 = __toESM(require("path"));
43216
+ var path88 = __toESM(require("path"));
42288
43217
  var execFileP9 = (0, import_util7.promisify)(import_child_process33.execFile);
42289
43218
  var MAX_BUFFER4 = 8 * 1024 * 1024;
42290
43219
  function resetStdinForChild4() {
@@ -42520,7 +43449,7 @@ var RailwayProvider = class {
42520
43449
  if (!projectId || !serviceId) {
42521
43450
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
42522
43451
  }
42523
- const remoteDir = path87.posix.dirname(remotePath);
43452
+ const remoteDir = path88.posix.dirname(remotePath);
42524
43453
  const parts = [`mkdir -p ${shellQuote4(remoteDir)}`, `cat > ${shellQuote4(remotePath)}`];
42525
43454
  if (options.mode != null) {
42526
43455
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
@@ -43166,8 +44095,8 @@ async function invite() {
43166
44095
  var import_node_dns = require("dns");
43167
44096
  var import_node_util5 = require("util");
43168
44097
  var import_node_crypto13 = require("crypto");
43169
- var fs78 = __toESM(require("fs"));
43170
- var path88 = __toESM(require("path"));
44098
+ var fs79 = __toESM(require("fs"));
44099
+ var path89 = __toESM(require("path"));
43171
44100
  var import_picocolors14 = __toESM(require("picocolors"));
43172
44101
  var dnsResolveP = (0, import_node_util5.promisify)(import_node_dns.resolve);
43173
44102
  async function checkDns(apiBase2) {
@@ -43223,13 +44152,13 @@ async function checkHealth(apiBase2) {
43223
44152
  }
43224
44153
  }
43225
44154
  function checkConfigDir() {
43226
- const dir = path88.join(require("os").homedir(), ".codeam");
44155
+ const dir = path89.join(require("os").homedir(), ".codeam");
43227
44156
  try {
43228
- fs78.mkdirSync(dir, { recursive: true, mode: 448 });
43229
- const probe = path88.join(dir, ".doctor-probe");
43230
- fs78.writeFileSync(probe, "ok", { mode: 384 });
43231
- const read2 = fs78.readFileSync(probe, "utf8");
43232
- fs78.unlinkSync(probe);
44157
+ fs79.mkdirSync(dir, { recursive: true, mode: 448 });
44158
+ const probe = path89.join(dir, ".doctor-probe");
44159
+ fs79.writeFileSync(probe, "ok", { mode: 384 });
44160
+ const read2 = fs79.readFileSync(probe, "utf8");
44161
+ fs79.unlinkSync(probe);
43233
44162
  if (read2 !== "ok") throw new Error("write/read round-trip mismatch");
43234
44163
  return {
43235
44164
  id: "config-dir",
@@ -43269,9 +44198,9 @@ function checkSessions() {
43269
44198
  }
43270
44199
  }
43271
44200
  function checkAgentBinaries() {
43272
- const os65 = createOsStrategy();
44201
+ const os66 = createOsStrategy();
43273
44202
  return getEnabledAgents().map((meta) => {
43274
- const found = os65.findInPath(meta.binaryName);
44203
+ const found = os66.findInPath(meta.binaryName);
43275
44204
  return {
43276
44205
  id: `agent-${meta.id}`,
43277
44206
  label: `Agent binary: ${meta.displayName} (${meta.binaryName})`,
@@ -43293,7 +44222,7 @@ function checkNodePty() {
43293
44222
  detail: "not required on this platform"
43294
44223
  };
43295
44224
  }
43296
- const vendoredPath = path88.join(__dirname, "vendor", "node-pty");
44225
+ const vendoredPath = path89.join(__dirname, "vendor", "node-pty");
43297
44226
  for (const target of [vendoredPath, "node-pty"]) {
43298
44227
  try {
43299
44228
  require(target);
@@ -43335,7 +44264,7 @@ function checkChokidar() {
43335
44264
  }
43336
44265
  async function doctor(args2 = []) {
43337
44266
  const json = args2.includes("--json");
43338
- const cliVersion = true ? "2.63.1" : "0.0.0-dev";
44267
+ const cliVersion = true ? "2.65.0" : "0.0.0-dev";
43339
44268
  const apiBase2 = resolveApiBaseUrl();
43340
44269
  const diagnosticId = (0, import_node_crypto13.randomUUID)();
43341
44270
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -43726,7 +44655,7 @@ async function mcpRun(args2) {
43726
44655
  // src/commands/version.ts
43727
44656
  var import_picocolors15 = __toESM(require("picocolors"));
43728
44657
  function version2() {
43729
- const v = true ? "2.63.1" : "unknown";
44658
+ const v = true ? "2.65.0" : "unknown";
43730
44659
  console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
43731
44660
  }
43732
44661
 
@@ -43875,10 +44804,10 @@ var EXIT_CODE_NAMES = {
43875
44804
  };
43876
44805
 
43877
44806
  // src/index.ts
43878
- var os64 = __toESM(require("os"));
44807
+ var os65 = __toESM(require("os"));
43879
44808
  if (!process.env.HOME) {
43880
44809
  try {
43881
- const home = os64.homedir();
44810
+ const home = os65.homedir();
43882
44811
  if (home) process.env.HOME = home;
43883
44812
  } catch {
43884
44813
  }