codeam-cli 2.20.3 → 2.21.1

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 +13 -0
  2. package/dist/index.js +867 -258
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -441,7 +441,7 @@ var import_qrcode_terminal = __toESM(require("qrcode-terminal"));
441
441
  // package.json
442
442
  var package_default = {
443
443
  name: "codeam-cli",
444
- version: "2.20.3",
444
+ version: "2.21.1",
445
445
  description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
446
446
  type: "commonjs",
447
447
  main: "dist/index.js",
@@ -735,7 +735,7 @@ async function postLinkCredential(input) {
735
735
  }
736
736
  }
737
737
  async function _postJsonAuthed(url, body, pluginAuthToken) {
738
- return new Promise((resolve4, reject) => {
738
+ return new Promise((resolve5, reject) => {
739
739
  const data = JSON.stringify(body);
740
740
  const u2 = new URL(url);
741
741
  const transport = u2.protocol === "https:" ? https : http;
@@ -767,9 +767,9 @@ async function _postJsonAuthed(url, body, pluginAuthToken) {
767
767
  return;
768
768
  }
769
769
  try {
770
- resolve4(JSON.parse(responseBody));
770
+ resolve5(JSON.parse(responseBody));
771
771
  } catch {
772
- resolve4(null);
772
+ resolve5(null);
773
773
  }
774
774
  });
775
775
  }
@@ -784,7 +784,7 @@ async function _postJsonAuthed(url, body, pluginAuthToken) {
784
784
  });
785
785
  }
786
786
  async function _postJson(url, body) {
787
- return new Promise((resolve4, reject) => {
787
+ return new Promise((resolve5, reject) => {
788
788
  const data = JSON.stringify(body);
789
789
  const u2 = new URL(url);
790
790
  const transport = u2.protocol === "https:" ? https : http;
@@ -813,9 +813,9 @@ async function _postJson(url, body) {
813
813
  return;
814
814
  }
815
815
  try {
816
- resolve4(JSON.parse(body2));
816
+ resolve5(JSON.parse(body2));
817
817
  } catch {
818
- resolve4(null);
818
+ resolve5(null);
819
819
  }
820
820
  });
821
821
  }
@@ -830,7 +830,7 @@ async function _postJson(url, body) {
830
830
  });
831
831
  }
832
832
  async function _getJson(url) {
833
- return new Promise((resolve4, reject) => {
833
+ return new Promise((resolve5, reject) => {
834
834
  const u2 = new URL(url);
835
835
  const transport = u2.protocol === "https:" ? https : http;
836
836
  const req = transport.request(
@@ -854,9 +854,9 @@ async function _getJson(url) {
854
854
  return;
855
855
  }
856
856
  try {
857
- resolve4(JSON.parse(body));
857
+ resolve5(JSON.parse(body));
858
858
  } catch {
859
- resolve4(null);
859
+ resolve5(null);
860
860
  }
861
861
  });
862
862
  }
@@ -1026,8 +1026,8 @@ function createGetModuleFromFilename(basePath = process.argv[1] ? (0, import_pat
1026
1026
  return decodedFile;
1027
1027
  };
1028
1028
  }
1029
- function normalizeWindowsPath(path37) {
1030
- return path37.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
1029
+ function normalizeWindowsPath(path40) {
1030
+ return path40.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
1031
1031
  }
1032
1032
 
1033
1033
  // ../../node_modules/@posthog/core/dist/featureFlagUtils.mjs
@@ -3507,15 +3507,15 @@ async function addSourceContext(frames) {
3507
3507
  LRU_FILE_CONTENTS_CACHE.reduce();
3508
3508
  return frames;
3509
3509
  }
3510
- function getContextLinesFromFile(path37, ranges, output) {
3511
- return new Promise((resolve4) => {
3512
- const stream = (0, import_node_fs.createReadStream)(path37);
3510
+ function getContextLinesFromFile(path40, ranges, output) {
3511
+ return new Promise((resolve5) => {
3512
+ const stream = (0, import_node_fs.createReadStream)(path40);
3513
3513
  const lineReaded = (0, import_node_readline.createInterface)({
3514
3514
  input: stream
3515
3515
  });
3516
3516
  function destroyStreamAndResolve() {
3517
3517
  stream.destroy();
3518
- resolve4();
3518
+ resolve5();
3519
3519
  }
3520
3520
  let lineNumber = 0;
3521
3521
  let currentRangeIndex = 0;
@@ -3524,7 +3524,7 @@ function getContextLinesFromFile(path37, ranges, output) {
3524
3524
  let rangeStart = range[0];
3525
3525
  let rangeEnd = range[1];
3526
3526
  function onStreamError() {
3527
- LRU_FILE_CONTENTS_FS_READ_FAILED.set(path37, 1);
3527
+ LRU_FILE_CONTENTS_FS_READ_FAILED.set(path40, 1);
3528
3528
  lineReaded.close();
3529
3529
  lineReaded.removeAllListeners();
3530
3530
  destroyStreamAndResolve();
@@ -3585,8 +3585,8 @@ function clearLineContext(frame) {
3585
3585
  delete frame.context_line;
3586
3586
  delete frame.post_context;
3587
3587
  }
3588
- function shouldSkipContextLinesForFile(path37) {
3589
- return path37.startsWith("node:") || path37.endsWith(".min.js") || path37.endsWith(".min.cjs") || path37.endsWith(".min.mjs") || path37.startsWith("data:");
3588
+ function shouldSkipContextLinesForFile(path40) {
3589
+ return path40.startsWith("node:") || path40.endsWith(".min.js") || path40.endsWith(".min.cjs") || path40.endsWith(".min.mjs") || path40.startsWith("data:");
3590
3590
  }
3591
3591
  function shouldSkipContextLinesForFrame(frame) {
3592
3592
  if (void 0 !== frame.lineno && frame.lineno > MAX_CONTEXTLINES_LINENO) return true;
@@ -4802,9 +4802,9 @@ var PostHogBackendClient = class extends PostHogCoreStateless {
4802
4802
  if (!waitUntil) return;
4803
4803
  if (this.disabled || this.optedOut) return;
4804
4804
  if (!this._waitUntilCycle) {
4805
- let resolve4;
4805
+ let resolve5;
4806
4806
  const promise = new Promise((r) => {
4807
- resolve4 = r;
4807
+ resolve5 = r;
4808
4808
  });
4809
4809
  try {
4810
4810
  waitUntil(promise);
@@ -4812,7 +4812,7 @@ var PostHogBackendClient = class extends PostHogCoreStateless {
4812
4812
  return;
4813
4813
  }
4814
4814
  this._waitUntilCycle = {
4815
- resolve: resolve4,
4815
+ resolve: resolve5,
4816
4816
  startedAt: Date.now(),
4817
4817
  timer: void 0
4818
4818
  };
@@ -4836,12 +4836,12 @@ var PostHogBackendClient = class extends PostHogCoreStateless {
4836
4836
  return cycle?.resolve;
4837
4837
  }
4838
4838
  async resolveWaitUntilFlush() {
4839
- const resolve4 = this._consumeWaitUntilCycle();
4839
+ const resolve5 = this._consumeWaitUntilCycle();
4840
4840
  try {
4841
4841
  await super.flush();
4842
4842
  } catch {
4843
4843
  } finally {
4844
- resolve4?.();
4844
+ resolve5?.();
4845
4845
  }
4846
4846
  }
4847
4847
  getPersistedProperty(key) {
@@ -4933,15 +4933,15 @@ var PostHogBackendClient = class extends PostHogCoreStateless {
4933
4933
  async waitForLocalEvaluationReady(timeoutMs = THIRTY_SECONDS) {
4934
4934
  if (this.isLocalEvaluationReady()) return true;
4935
4935
  if (void 0 === this.featureFlagsPoller) return false;
4936
- return new Promise((resolve4) => {
4936
+ return new Promise((resolve5) => {
4937
4937
  const timeout = setTimeout(() => {
4938
4938
  cleanup();
4939
- resolve4(false);
4939
+ resolve5(false);
4940
4940
  }, timeoutMs);
4941
4941
  const cleanup = this._events.on("localEvaluationFlagsLoaded", (count) => {
4942
4942
  clearTimeout(timeout);
4943
4943
  cleanup();
4944
- resolve4(count > 0);
4944
+ resolve5(count > 0);
4945
4945
  });
4946
4946
  });
4947
4947
  }
@@ -5378,13 +5378,13 @@ var PostHogBackendClient = class extends PostHogCoreStateless {
5378
5378
  this.context?.enter(data, options);
5379
5379
  }
5380
5380
  async _shutdown(shutdownTimeoutMs) {
5381
- const resolve4 = this._consumeWaitUntilCycle();
5381
+ const resolve5 = this._consumeWaitUntilCycle();
5382
5382
  await this.featureFlagsPoller?.stopPoller(shutdownTimeoutMs);
5383
5383
  this.errorTracking.shutdown();
5384
5384
  try {
5385
5385
  return await super._shutdown(shutdownTimeoutMs);
5386
5386
  } finally {
5387
- resolve4?.();
5387
+ resolve5?.();
5388
5388
  }
5389
5389
  }
5390
5390
  async _requestRemoteConfigPayload(flagKey) {
@@ -5740,7 +5740,7 @@ function readAnonId() {
5740
5740
  }
5741
5741
  function superProperties() {
5742
5742
  return {
5743
- cliVersion: true ? "2.20.3" : "0.0.0-dev",
5743
+ cliVersion: true ? "2.21.1" : "0.0.0-dev",
5744
5744
  nodeVersion: process.version,
5745
5745
  platform: process.platform,
5746
5746
  arch: process.arch,
@@ -8874,15 +8874,15 @@ function runInstaller() {
8874
8874
  "-Command",
8875
8875
  "irm https://claude.ai/install.ps1 | iex"
8876
8876
  ] : ["-c", "curl -fsSL https://claude.ai/install.sh | bash"];
8877
- return new Promise((resolve4) => {
8877
+ return new Promise((resolve5) => {
8878
8878
  const proc = (0, import_child_process4.spawn)(cmd, args2, { stdio: "inherit" });
8879
8879
  proc.on("error", (err) => {
8880
8880
  console.error(`
8881
8881
  \u2717 Installer failed to launch: ${err.message}`);
8882
- resolve4(false);
8882
+ resolve5(false);
8883
8883
  });
8884
8884
  proc.on("exit", (code) => {
8885
- resolve4(code === 0);
8885
+ resolve5(code === 0);
8886
8886
  });
8887
8887
  });
8888
8888
  }
@@ -9045,17 +9045,17 @@ function parseUsageOutput(raw) {
9045
9045
  return { percent, resetAt };
9046
9046
  }
9047
9047
  async function fetchClaudeQuota() {
9048
- return new Promise((resolve4) => {
9048
+ return new Promise((resolve5) => {
9049
9049
  const claudeCmd = findInPath("claude") ? "claude" : "claude-code";
9050
9050
  if (!claudeCmd) {
9051
- resolve4(null);
9051
+ resolve5(null);
9052
9052
  return;
9053
9053
  }
9054
9054
  const helperPath = path11.join(os10.tmpdir(), "codeam-quota-helper.py");
9055
9055
  fs8.writeFileSync(helperPath, HELPER_SCRIPT, { mode: 420 });
9056
9056
  const python = findInPath("python3") ?? findInPath("python");
9057
9057
  if (!python) {
9058
- resolve4(null);
9058
+ resolve5(null);
9059
9059
  return;
9060
9060
  }
9061
9061
  const proc = (0, import_child_process5.spawn)(python, [helperPath, claudeCmd, "--tools", ""], {
@@ -9082,13 +9082,13 @@ async function fetchClaudeQuota() {
9082
9082
  fs8.unlinkSync(helperPath);
9083
9083
  } catch {
9084
9084
  }
9085
- resolve4(result);
9085
+ resolve5(result);
9086
9086
  }, 5e3);
9087
9087
  }, 8e3);
9088
9088
  setTimeout(() => {
9089
9089
  if (!resolved) {
9090
9090
  resolved = true;
9091
- resolve4(null);
9091
+ resolve5(null);
9092
9092
  }
9093
9093
  try {
9094
9094
  proc.kill();
@@ -10428,12 +10428,12 @@ function resolveNpm(os26) {
10428
10428
  return os26.id === "win32" ? "npm.cmd" : "npm";
10429
10429
  }
10430
10430
  async function installCodexViaNpm(os26) {
10431
- return new Promise((resolve4, reject) => {
10431
+ return new Promise((resolve5, reject) => {
10432
10432
  const proc = (0, import_node_child_process4.spawn)(resolveNpm(os26), ["install", "-g", "@openai/codex"], {
10433
10433
  stdio: "inherit"
10434
10434
  });
10435
10435
  proc.on("close", (code) => {
10436
- if (code === 0) resolve4();
10436
+ if (code === 0) resolve5();
10437
10437
  else reject(new Error(`npm install -g @openai/codex exited ${code}`));
10438
10438
  });
10439
10439
  proc.on("error", (err) => {
@@ -10548,12 +10548,12 @@ async function ensureCoderabbitInstalled(os26) {
10548
10548
  return false;
10549
10549
  }
10550
10550
  console.log("\n CodeRabbit CLI not found \u2014 installing via the official script\u2026\n");
10551
- const ok = await new Promise((resolve4) => {
10551
+ const ok = await new Promise((resolve5) => {
10552
10552
  const proc = (0, import_node_child_process5.spawn)("sh", ["-c", `curl -fsSL ${INSTALL_URL} | sh`], {
10553
10553
  stdio: "inherit"
10554
10554
  });
10555
- proc.on("close", (code) => resolve4(code === 0));
10556
- proc.on("error", () => resolve4(false));
10555
+ proc.on("close", (code) => resolve5(code === 0));
10556
+ proc.on("error", () => resolve5(false));
10557
10557
  });
10558
10558
  if (!ok) return false;
10559
10559
  os26.augmentPath([`${os26.homeDir()}/.local/bin`, "/opt/homebrew/bin"]);
@@ -10610,11 +10610,11 @@ function parseReview(stdout) {
10610
10610
  for (const line of lines) {
10611
10611
  const m = line.match(HUNK_LINE_RE);
10612
10612
  if (!m) continue;
10613
- const [, path37, lineNo, sevToken, message] = m;
10614
- if (!path37 || !lineNo || !message) continue;
10613
+ const [, path40, lineNo, sevToken, message] = m;
10614
+ if (!path40 || !lineNo || !message) continue;
10615
10615
  const cleanedMessage = message.trim().replace(/^[*-]\s+/, "");
10616
10616
  hunks.push({
10617
- path: path37.trim(),
10617
+ path: path40.trim(),
10618
10618
  line: Number(lineNo),
10619
10619
  severity: sevToken ? SEVERITY_MAP[sevToken.toLowerCase()] : void 0,
10620
10620
  message: cleanedMessage
@@ -10672,7 +10672,7 @@ var CoderabbitRuntimeStrategy = class {
10672
10672
  }
10673
10673
  async runOneShot(input) {
10674
10674
  const launch = await this.prepareInvocation(input);
10675
- return new Promise((resolve4, reject) => {
10675
+ return new Promise((resolve5, reject) => {
10676
10676
  const stdoutBuf = [];
10677
10677
  const stderrBuf = [];
10678
10678
  const proc = (0, import_node_child_process7.spawn)(launch.cmd, launch.args, {
@@ -10683,7 +10683,7 @@ var CoderabbitRuntimeStrategy = class {
10683
10683
  proc.stderr?.on("data", (b) => stderrBuf.push(b));
10684
10684
  proc.on("error", (err) => reject(err));
10685
10685
  proc.on("close", (code) => {
10686
- resolve4(
10686
+ resolve5(
10687
10687
  this.parseOutput({
10688
10688
  exitCode: code ?? 0,
10689
10689
  stdout: Buffer.concat(stdoutBuf).toString("utf8"),
@@ -11216,14 +11216,14 @@ var ChunkEmitter = class {
11216
11216
  "chunkEmitter",
11217
11217
  `send type=${body.type ?? "(clear)"} bytes=${payload.length} done=${body.done === true}`
11218
11218
  );
11219
- return new Promise((resolve4) => {
11219
+ return new Promise((resolve5) => {
11220
11220
  const attempt = (attemptsLeft) => {
11221
11221
  _transport2.post(this.url, this.headers, payload).then(({ statusCode, body: resBody }) => {
11222
11222
  const tookMs = Date.now() - t0;
11223
11223
  if (statusCode === 410 || statusCode === 404 && /SESSION_NOT_FOUND|SESSION_GONE/.test(resBody)) {
11224
11224
  process.stderr.write("[codeam] session was deleted/disconnected \u2014 stopping output stream.\n");
11225
11225
  log.info("chunkEmitter", `dead status=${statusCode} took=${tookMs}ms`);
11226
- resolve4({ dead: true });
11226
+ resolve5({ dead: true });
11227
11227
  return;
11228
11228
  }
11229
11229
  if (statusCode >= 400) {
@@ -11233,7 +11233,7 @@ var ChunkEmitter = class {
11233
11233
  } else {
11234
11234
  log.info("chunkEmitter", `ok status=${statusCode} took=${tookMs}ms`);
11235
11235
  }
11236
- resolve4({ dead: false });
11236
+ resolve5({ dead: false });
11237
11237
  }).catch((err) => {
11238
11238
  log.warn(
11239
11239
  "chunkEmitter",
@@ -11244,7 +11244,7 @@ var ChunkEmitter = class {
11244
11244
  const delay = 200 * (maxRetries - attemptsLeft + 1);
11245
11245
  setTimeout(() => attempt(attemptsLeft - 1), delay);
11246
11246
  } else {
11247
- resolve4({ dead: false });
11247
+ resolve5({ dead: false });
11248
11248
  }
11249
11249
  });
11250
11250
  };
@@ -11256,7 +11256,7 @@ var _transport2 = {
11256
11256
  post: _post
11257
11257
  };
11258
11258
  function _post(url, headers, payload) {
11259
- return new Promise((resolve4, reject) => {
11259
+ return new Promise((resolve5, reject) => {
11260
11260
  let settled = false;
11261
11261
  const u2 = new URL(url);
11262
11262
  const transport = u2.protocol === "https:" ? https3 : http3;
@@ -11280,7 +11280,7 @@ function _post(url, headers, payload) {
11280
11280
  res.on("end", () => {
11281
11281
  if (settled) return;
11282
11282
  settled = true;
11283
- resolve4({ statusCode: res.statusCode ?? 0, body: resData });
11283
+ resolve5({ statusCode: res.statusCode ?? 0, body: resData });
11284
11284
  });
11285
11285
  }
11286
11286
  );
@@ -11781,7 +11781,7 @@ function parseJsonl(filePath) {
11781
11781
  return messages;
11782
11782
  }
11783
11783
  function post(endpoint, body) {
11784
- return new Promise((resolve4) => {
11784
+ return new Promise((resolve5) => {
11785
11785
  const payload = JSON.stringify(body);
11786
11786
  const u2 = new URL(`${API_BASE4}${endpoint}`);
11787
11787
  const transport = u2.protocol === "https:" ? https4 : http4;
@@ -11802,17 +11802,17 @@ function post(endpoint, body) {
11802
11802
  res.resume();
11803
11803
  const ok = res.statusCode !== void 0 && res.statusCode >= 200 && res.statusCode < 300;
11804
11804
  if (!ok) log.warn("history:post", `${endpoint} \u2192 HTTP ${res.statusCode}`);
11805
- resolve4(ok);
11805
+ resolve5(ok);
11806
11806
  }
11807
11807
  );
11808
11808
  req.on("error", (err) => {
11809
11809
  log.warn("history:post", `${endpoint} network error`, err);
11810
- resolve4(false);
11810
+ resolve5(false);
11811
11811
  });
11812
11812
  req.on("timeout", () => {
11813
11813
  log.warn("history:post", `${endpoint} timeout after 15s`);
11814
11814
  req.destroy();
11815
- resolve4(false);
11815
+ resolve5(false);
11816
11816
  });
11817
11817
  req.write(payload);
11818
11818
  req.end();
@@ -12296,7 +12296,7 @@ var _transport3 = {
12296
12296
  post: _post2
12297
12297
  };
12298
12298
  function _post2(url, headers, payload) {
12299
- return new Promise((resolve4, reject) => {
12299
+ return new Promise((resolve5, reject) => {
12300
12300
  let settled = false;
12301
12301
  const u2 = new URL(url);
12302
12302
  const lib = u2.protocol === "https:" ? https5 : http5;
@@ -12321,7 +12321,7 @@ function _post2(url, headers, payload) {
12321
12321
  res.on("end", () => {
12322
12322
  if (settled) return;
12323
12323
  settled = true;
12324
- resolve4({ statusCode: res.statusCode ?? 0, body });
12324
+ resolve5({ statusCode: res.statusCode ?? 0, body });
12325
12325
  });
12326
12326
  }
12327
12327
  );
@@ -12576,6 +12576,7 @@ var FileWatcherService = class {
12576
12576
  );
12577
12577
  return;
12578
12578
  }
12579
+ this.opts.onRepoDirty?.(gitRoot);
12579
12580
  const relPathInRepo = path25.relative(gitRoot, absPath);
12580
12581
  if (!relPathInRepo || relPathInRepo.startsWith("..")) return;
12581
12582
  const repoPath = path25.relative(this.opts.workingDir, gitRoot);
@@ -12690,6 +12691,7 @@ var FileWatcherService = class {
12690
12691
  "X-Plugin-Auth-Token": this.opts.pluginAuthToken
12691
12692
  };
12692
12693
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
12694
+ if (this.stopped) return;
12693
12695
  try {
12694
12696
  const { statusCode, body: resBody } = await _transport3.post(url, headers, payload);
12695
12697
  if (statusCode >= 200 && statusCode < 300) {
@@ -12738,12 +12740,12 @@ var _gitSeam = {
12738
12740
  run: _runGitImpl
12739
12741
  };
12740
12742
  async function _runGitImpl(cwd, args2, opts = {}) {
12741
- return new Promise((resolve4) => {
12743
+ return new Promise((resolve5) => {
12742
12744
  let proc;
12743
12745
  try {
12744
12746
  proc = (0, import_child_process7.spawn)("git", args2, { cwd, env: process.env });
12745
12747
  } catch {
12746
- resolve4(null);
12748
+ resolve5(null);
12747
12749
  return;
12748
12750
  }
12749
12751
  let stdout = "";
@@ -12754,13 +12756,13 @@ async function _runGitImpl(cwd, args2, opts = {}) {
12754
12756
  proc.stderr?.on("data", (c2) => {
12755
12757
  stderr += c2.toString();
12756
12758
  });
12757
- proc.on("error", () => resolve4(null));
12759
+ proc.on("error", () => resolve5(null));
12758
12760
  proc.on("close", (code) => {
12759
12761
  if (code === 0 || opts.allowNonZeroExit) {
12760
- resolve4(stdout);
12762
+ resolve5(stdout);
12761
12763
  } else {
12762
12764
  log.trace("fileWatcher", `git ${args2.join(" ")} exited ${code} stderr=${stderr.slice(0, 200)}`);
12763
- resolve4(null);
12765
+ resolve5(null);
12764
12766
  }
12765
12767
  });
12766
12768
  });
@@ -12769,9 +12771,493 @@ function _runGit(cwd, args2, opts = {}) {
12769
12771
  return _gitSeam.run(cwd, args2, opts);
12770
12772
  }
12771
12773
 
12772
- // src/services/streaming-emitter.service.ts
12774
+ // src/services/turn-files/turn-file-aggregator.ts
12773
12775
  var import_crypto2 = require("crypto");
12774
12776
 
12777
+ // src/services/turn-files/git-changeset.ts
12778
+ var import_child_process8 = require("child_process");
12779
+ var path26 = __toESM(require("path"));
12780
+ async function collectRepoChangeset(opts) {
12781
+ const status2 = await runGit2(opts.repoRoot, ["status", "--porcelain=v1", "-z"]);
12782
+ if (status2 === null) return null;
12783
+ const numstatRaw = await runGit2(opts.repoRoot, [
12784
+ "diff",
12785
+ "--numstat",
12786
+ "-z",
12787
+ "HEAD"
12788
+ ]).catch(() => null);
12789
+ const numstat = parseNumstat(numstatRaw ?? "");
12790
+ const entries = [];
12791
+ for (const row of parseStatus(status2)) {
12792
+ const stats = numstat.get(row.filePath) ?? { added: 0, removed: 0 };
12793
+ entries.push({
12794
+ filePath: row.filePath,
12795
+ fileStatus: row.fileStatus,
12796
+ linesAdded: stats.added,
12797
+ linesRemoved: stats.removed,
12798
+ // hunkCount isn't surfaced by --numstat. For the rail / drawer
12799
+ // it's only a count badge; defaulting to 1 when the file has
12800
+ // any non-zero stat is good enough until we wire a follow-up
12801
+ // per-file `git diff --shortstat` if we ever want exact hunks.
12802
+ hunkCount: stats.added + stats.removed > 0 ? 1 : 0,
12803
+ repoPath: opts.repoPath,
12804
+ repoName: opts.repoName
12805
+ });
12806
+ }
12807
+ return entries;
12808
+ }
12809
+ function parseStatus(raw) {
12810
+ const tokens = raw.split("\0");
12811
+ const rows = [];
12812
+ for (let i = 0; i < tokens.length; i++) {
12813
+ const token = tokens[i];
12814
+ if (!token || token.length < 3) continue;
12815
+ const code = token.slice(0, 2);
12816
+ const filePath = token.slice(3);
12817
+ if (!filePath) continue;
12818
+ const indexCode = code[0];
12819
+ const worktreeCode = code[1];
12820
+ if (indexCode === "R" || worktreeCode === "R") {
12821
+ rows.push({ filePath, fileStatus: "renamed" });
12822
+ i += 1;
12823
+ continue;
12824
+ }
12825
+ if (code === "??" || indexCode === "A" || worktreeCode === "A") {
12826
+ rows.push({ filePath, fileStatus: "added" });
12827
+ continue;
12828
+ }
12829
+ if (indexCode === "D" || worktreeCode === "D") {
12830
+ rows.push({ filePath, fileStatus: "deleted" });
12831
+ continue;
12832
+ }
12833
+ rows.push({ filePath, fileStatus: "modified" });
12834
+ }
12835
+ return rows;
12836
+ }
12837
+ function parseNumstat(raw) {
12838
+ const out2 = /* @__PURE__ */ new Map();
12839
+ for (const record of raw.split("\0")) {
12840
+ if (!record) continue;
12841
+ const parts = record.split(" ");
12842
+ if (parts.length < 3) continue;
12843
+ const added = parts[0] === "-" ? 0 : parseInt(parts[0], 10) || 0;
12844
+ const removed = parts[1] === "-" ? 0 : parseInt(parts[1], 10) || 0;
12845
+ const filePath = parts.slice(2).join(" ");
12846
+ if (!filePath) continue;
12847
+ out2.set(filePath, { added, removed });
12848
+ }
12849
+ return out2;
12850
+ }
12851
+ var _runGitImpl2 = {
12852
+ run: defaultRunGit
12853
+ };
12854
+ function runGit2(cwd, args2) {
12855
+ return _runGitImpl2.run(cwd, args2);
12856
+ }
12857
+ function defaultRunGit(cwd, args2) {
12858
+ return new Promise((resolve5) => {
12859
+ let proc;
12860
+ try {
12861
+ proc = (0, import_child_process8.spawn)("git", args2, { cwd, env: process.env });
12862
+ } catch {
12863
+ resolve5(null);
12864
+ return;
12865
+ }
12866
+ let stdout = "";
12867
+ let stderr = "";
12868
+ proc.stdout?.on("data", (c2) => {
12869
+ stdout += c2.toString();
12870
+ });
12871
+ proc.stderr?.on("data", (c2) => {
12872
+ stderr += c2.toString();
12873
+ });
12874
+ proc.on("error", () => resolve5(null));
12875
+ proc.on("close", (code) => {
12876
+ if (code === 0) {
12877
+ resolve5(stdout);
12878
+ } else {
12879
+ log.trace(
12880
+ "turnFiles",
12881
+ `git ${args2.join(" ")} exited ${code} stderr=${stderr.slice(0, 200)}`
12882
+ );
12883
+ resolve5(null);
12884
+ }
12885
+ });
12886
+ });
12887
+ }
12888
+ async function discoverRepos(workingDir, maxDepth = 4) {
12889
+ const fs31 = await import("fs/promises");
12890
+ const out2 = [];
12891
+ await walk(workingDir, 0);
12892
+ return out2;
12893
+ async function walk(dir, depth) {
12894
+ if (depth > maxDepth) return;
12895
+ let entries = [];
12896
+ try {
12897
+ const dirents = await fs31.readdir(dir, { withFileTypes: true });
12898
+ entries = dirents.filter((d3) => !d3.name.startsWith(".") || d3.name === ".git").map((d3) => ({ name: d3.name, isDirectory: d3.isDirectory() }));
12899
+ } catch {
12900
+ return;
12901
+ }
12902
+ const hasGit = entries.some(
12903
+ (e) => e.name === ".git" && (e.isDirectory || true)
12904
+ );
12905
+ if (hasGit) {
12906
+ out2.push({
12907
+ repoRoot: dir,
12908
+ repoPath: path26.relative(workingDir, dir),
12909
+ repoName: path26.basename(dir)
12910
+ });
12911
+ return;
12912
+ }
12913
+ for (const entry of entries) {
12914
+ if (!entry.isDirectory) continue;
12915
+ if (entry.name === "node_modules") continue;
12916
+ if (entry.name === "dist" || entry.name === "build") continue;
12917
+ await walk(path26.join(dir, entry.name), depth + 1);
12918
+ }
12919
+ }
12920
+ }
12921
+
12922
+ // src/services/turn-files/files-outbox.ts
12923
+ var fs22 = __toESM(require("fs/promises"));
12924
+ var path27 = __toESM(require("path"));
12925
+ var import_os6 = require("os");
12926
+ var HOME_OUTBOX_DIR = ".codeam/outbox";
12927
+ var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
12928
+ var BACKOFF_STEPS_MS = [
12929
+ 1e3,
12930
+ // 1 s
12931
+ 2e3,
12932
+ // 2 s
12933
+ 4e3,
12934
+ // 4 s
12935
+ 8e3,
12936
+ // 8 s
12937
+ 16e3,
12938
+ // 16 s
12939
+ 32e3,
12940
+ // 32 s
12941
+ 6e4,
12942
+ // 1 min
12943
+ 12e4,
12944
+ // 2 min
12945
+ 3e5
12946
+ // 5 min — cap
12947
+ ];
12948
+ var FilesOutbox = class {
12949
+ filePath;
12950
+ post;
12951
+ autoSchedule;
12952
+ flushTimer = null;
12953
+ flushing = false;
12954
+ backoffIndex = 0;
12955
+ stopped = false;
12956
+ constructor(opts) {
12957
+ const base = opts.baseDir ?? path27.join(homeDir(), HOME_OUTBOX_DIR);
12958
+ this.filePath = path27.join(base, `${opts.sessionId}.jsonl`);
12959
+ this.post = opts.post;
12960
+ this.autoSchedule = opts.autoSchedule !== false;
12961
+ }
12962
+ /** Persist the entry to disk and trigger a flush. Returns once the
12963
+ * line is durable on disk (not once the POST succeeds). */
12964
+ async enqueue(entry) {
12965
+ await fs22.mkdir(path27.dirname(this.filePath), { recursive: true });
12966
+ await fs22.appendFile(this.filePath, JSON.stringify(entry) + "\n", "utf8");
12967
+ this.backoffIndex = 0;
12968
+ if (this.autoSchedule) this.scheduleFlush(0);
12969
+ }
12970
+ /** Stop the scheduler. Idempotent. The on-disk file is left alone
12971
+ * so the next process pickup can flush whatever's pending. */
12972
+ stop() {
12973
+ this.stopped = true;
12974
+ if (this.flushTimer) {
12975
+ clearTimeout(this.flushTimer);
12976
+ this.flushTimer = null;
12977
+ }
12978
+ }
12979
+ /** Visible for tests. Forces a flush attempt right now. */
12980
+ async _flushNow() {
12981
+ return this.flush();
12982
+ }
12983
+ scheduleFlush(delayMs) {
12984
+ if (this.stopped) return;
12985
+ if (this.flushTimer) clearTimeout(this.flushTimer);
12986
+ const jittered = delayMs === 0 ? 0 : applyJitter(delayMs);
12987
+ this.flushTimer = setTimeout(() => {
12988
+ this.flushTimer = null;
12989
+ void this.flush();
12990
+ }, jittered);
12991
+ }
12992
+ async flush() {
12993
+ if (this.stopped) return;
12994
+ if (this.flushing) {
12995
+ return;
12996
+ }
12997
+ this.flushing = true;
12998
+ try {
12999
+ const entries = await this.readAll();
13000
+ if (entries.length === 0) return;
13001
+ const now = Date.now();
13002
+ const fresh = entries.filter((e) => now - e.enqueuedAt <= MAX_AGE_MS);
13003
+ if (fresh.length < entries.length) {
13004
+ log.warn(
13005
+ "turnFiles",
13006
+ `dropping ${entries.length - fresh.length} outbox entries older than ${MAX_AGE_MS / 1e3}s`
13007
+ );
13008
+ }
13009
+ const stillPending = [];
13010
+ let anyFailed = false;
13011
+ for (const entry of fresh) {
13012
+ if (this.stopped) {
13013
+ stillPending.push(entry);
13014
+ continue;
13015
+ }
13016
+ try {
13017
+ const res = await this.post(entry);
13018
+ if (res.ok) continue;
13019
+ if (res.statusCode === 404 || res.statusCode === 410) {
13020
+ log.warn(
13021
+ "turnFiles",
13022
+ `session dead (status=${res.statusCode}); dropping turnId=${entry.turnId.slice(0, 8)}`
13023
+ );
13024
+ continue;
13025
+ }
13026
+ anyFailed = true;
13027
+ stillPending.push(entry);
13028
+ } catch (err) {
13029
+ anyFailed = true;
13030
+ stillPending.push(entry);
13031
+ log.trace(
13032
+ "turnFiles",
13033
+ `outbox post threw for turnId=${entry.turnId.slice(0, 8)}: ${err.message}`
13034
+ );
13035
+ }
13036
+ }
13037
+ await this.rewrite(stillPending);
13038
+ if (anyFailed) {
13039
+ const delay = BACKOFF_STEPS_MS[Math.min(this.backoffIndex, BACKOFF_STEPS_MS.length - 1)];
13040
+ this.backoffIndex = Math.min(
13041
+ this.backoffIndex + 1,
13042
+ BACKOFF_STEPS_MS.length - 1
13043
+ );
13044
+ this.scheduleFlush(delay);
13045
+ } else {
13046
+ this.backoffIndex = 0;
13047
+ }
13048
+ } finally {
13049
+ this.flushing = false;
13050
+ }
13051
+ }
13052
+ async readAll() {
13053
+ let raw = "";
13054
+ try {
13055
+ raw = await fs22.readFile(this.filePath, "utf8");
13056
+ } catch {
13057
+ return [];
13058
+ }
13059
+ const out2 = [];
13060
+ for (const line of raw.split("\n")) {
13061
+ const trimmed = line.trim();
13062
+ if (!trimmed) continue;
13063
+ try {
13064
+ out2.push(JSON.parse(trimmed));
13065
+ } catch {
13066
+ }
13067
+ }
13068
+ return out2;
13069
+ }
13070
+ /**
13071
+ * Atomic compaction: write to `<file>.tmp`, fsync, rename over the
13072
+ * original. A crash between any of these steps leaves either the
13073
+ * original (no progress) or the new file (clean compaction) — never
13074
+ * a torn write.
13075
+ */
13076
+ async rewrite(entries) {
13077
+ const tmpPath = `${this.filePath}.${process.pid}.tmp`;
13078
+ if (entries.length === 0) {
13079
+ await fs22.unlink(this.filePath).catch(() => void 0);
13080
+ return;
13081
+ }
13082
+ const payload = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
13083
+ await fs22.writeFile(tmpPath, payload, "utf8");
13084
+ await fs22.rename(tmpPath, this.filePath);
13085
+ }
13086
+ };
13087
+ function applyJitter(ms) {
13088
+ const factor = 0.8 + Math.random() * 0.4;
13089
+ return Math.round(ms * factor);
13090
+ }
13091
+ function homeDir() {
13092
+ return process.env.HOME ?? process.env.USERPROFILE ?? (0, import_os6.tmpdir)();
13093
+ }
13094
+
13095
+ // src/services/turn-files/turn-file-aggregator.ts
13096
+ var API_BASE6 = resolveApiBaseUrl();
13097
+ var ENDPOINT = "/api/files/batch";
13098
+ var MAX_BATCH_SIZE = 1e3;
13099
+ var TurnFileAggregator = class {
13100
+ constructor(opts) {
13101
+ this.opts = opts;
13102
+ this.apiBase = opts.apiBaseUrl ?? API_BASE6;
13103
+ this.outbox = new FilesOutbox({
13104
+ sessionId: opts.sessionId,
13105
+ baseDir: opts.outboxDir,
13106
+ post: (entry) => this.postEntry(entry),
13107
+ autoSchedule: opts.outboxAutoSchedule
13108
+ });
13109
+ this.discovering = discoverRepos(opts.workingDir).then((repos) => {
13110
+ this.repos = repos;
13111
+ opts.dirtyTracker?.markAllDirty(repos);
13112
+ log.info(
13113
+ "turnFiles",
13114
+ `discovered ${repos.length} repo(s) under ${opts.workingDir}: ${repos.map((r) => r.repoName || "<root>").join(", ")}`
13115
+ );
13116
+ });
13117
+ }
13118
+ opts;
13119
+ apiBase;
13120
+ outbox;
13121
+ repos = [];
13122
+ discovering = null;
13123
+ stopped = false;
13124
+ /** Stop the outbox scheduler. Idempotent. */
13125
+ stop() {
13126
+ this.stopped = true;
13127
+ this.outbox.stop();
13128
+ }
13129
+ /**
13130
+ * Run the discovery + git collection + POST pipeline for one
13131
+ * turn. Errors are swallowed (logged) so an agent never blocks on a
13132
+ * file-changeset failure; the outbox covers the network half.
13133
+ */
13134
+ async flushTurn() {
13135
+ if (this.stopped) return;
13136
+ try {
13137
+ if (this.discovering) {
13138
+ await this.discovering;
13139
+ this.discovering = null;
13140
+ }
13141
+ if (this.repos.length === 0) {
13142
+ log.trace("turnFiles", "no repos discovered \u2014 skipping flush");
13143
+ return;
13144
+ }
13145
+ const reposToScan = this.opts.dirtyTracker ? this.filterByDirty(this.opts.dirtyTracker) : this.repos;
13146
+ if (reposToScan.length === 0) {
13147
+ log.trace("turnFiles", "dirty set empty \u2014 skipping flush");
13148
+ return;
13149
+ }
13150
+ const files = [];
13151
+ for (const repo of reposToScan) {
13152
+ const entries = await collectRepoChangeset(repo);
13153
+ if (entries) files.push(...entries);
13154
+ }
13155
+ if (files.length === 0) {
13156
+ log.trace("turnFiles", "no changes detected this turn \u2014 skipping POST");
13157
+ return;
13158
+ }
13159
+ const chunks = chunkArray(files, MAX_BATCH_SIZE);
13160
+ for (const chunk of chunks) {
13161
+ const entry = {
13162
+ turnId: (0, import_crypto2.randomUUID)(),
13163
+ sessionId: this.opts.sessionId,
13164
+ pluginId: this.opts.pluginId,
13165
+ enqueuedAt: Date.now(),
13166
+ files: chunk
13167
+ };
13168
+ await this.outbox.enqueue(entry);
13169
+ }
13170
+ } catch (err) {
13171
+ log.warn(
13172
+ "turnFiles",
13173
+ `flushTurn failed: ${err.message ?? String(err)}`
13174
+ );
13175
+ }
13176
+ }
13177
+ /**
13178
+ * Consume the tracker's dirty set and intersect with the
13179
+ * discovered repos so we never spawn git for a path the watcher
13180
+ * marked outside our discovered set (e.g. a sibling repo that
13181
+ * appeared after construction). Unknown roots get dropped on the
13182
+ * floor — they'll re-mark themselves on the next event if they're
13183
+ * inside `workingDir`.
13184
+ */
13185
+ filterByDirty(tracker) {
13186
+ const dirty = tracker.consume();
13187
+ if (dirty.size === 0) return [];
13188
+ return this.repos.filter((repo) => dirty.has(repo.repoRoot));
13189
+ }
13190
+ async postEntry(entry) {
13191
+ const url = `${this.apiBase}${ENDPOINT}`;
13192
+ const headers = {
13193
+ "Content-Type": "application/json",
13194
+ "X-Codeam-Protocol-Version": PROTOCOL_VERSION,
13195
+ "X-Plugin-Auth-Token": this.opts.pluginAuthToken
13196
+ };
13197
+ const body = JSON.stringify({
13198
+ sessionId: entry.sessionId,
13199
+ pluginId: entry.pluginId,
13200
+ turnId: entry.turnId,
13201
+ files: entry.files
13202
+ });
13203
+ try {
13204
+ const res = await _transport3.post(url, headers, body);
13205
+ return { ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode };
13206
+ } catch (err) {
13207
+ log.trace(
13208
+ "turnFiles",
13209
+ `batch POST threw turnId=${entry.turnId.slice(0, 8)}: ${err.message}`
13210
+ );
13211
+ return { ok: false, statusCode: 0 };
13212
+ }
13213
+ }
13214
+ };
13215
+ function chunkArray(arr, size) {
13216
+ if (arr.length <= size) return [arr];
13217
+ const out2 = [];
13218
+ for (let i = 0; i < arr.length; i += size) {
13219
+ out2.push(arr.slice(i, i + size));
13220
+ }
13221
+ return out2;
13222
+ }
13223
+
13224
+ // src/services/turn-files/repo-dirty-tracker.ts
13225
+ var RepoDirtyTracker = class {
13226
+ dirty = /* @__PURE__ */ new Set();
13227
+ /** Add a repo root to the dirty set. Idempotent. */
13228
+ markDirty(repoRoot) {
13229
+ this.dirty.add(repoRoot);
13230
+ }
13231
+ /** Seed every known repo as dirty — used right after `discoverRepos`
13232
+ * returns so the first end-of-turn flush captures worktree state
13233
+ * that predates the pairing. */
13234
+ markAllDirty(repoRoots) {
13235
+ for (const r of repoRoots) this.dirty.add(r.repoRoot);
13236
+ }
13237
+ /** Snapshot the current dirty set without clearing — useful for
13238
+ * diagnostic logs / tests. */
13239
+ peek() {
13240
+ return new Set(this.dirty);
13241
+ }
13242
+ /** Atomically read AND clear the dirty set. The aggregator calls
13243
+ * this on each `done:true`; subsequent filesystem events
13244
+ * re-populate it for the next turn. */
13245
+ consume() {
13246
+ const snapshot = new Set(this.dirty);
13247
+ this.dirty.clear();
13248
+ return snapshot;
13249
+ }
13250
+ /** True when the set is non-empty. Cheap pre-flight gate so the
13251
+ * aggregator can early-return on chat-only turns without
13252
+ * touching the dirty set. */
13253
+ hasDirty() {
13254
+ return this.dirty.size > 0;
13255
+ }
13256
+ };
13257
+
13258
+ // src/services/streaming-emitter.service.ts
13259
+ var import_crypto3 = require("crypto");
13260
+
12775
13261
  // src/services/streaming/transport.ts
12776
13262
  var http6 = __toESM(require("http"));
12777
13263
  var https6 = __toESM(require("https"));
@@ -12780,7 +13266,7 @@ var _transport4 = {
12780
13266
  get: _get
12781
13267
  };
12782
13268
  function _post3(url, headers, payload) {
12783
- return new Promise((resolve4, reject) => {
13269
+ return new Promise((resolve5, reject) => {
12784
13270
  let settled = false;
12785
13271
  const u2 = new URL(url);
12786
13272
  const lib = u2.protocol === "https:" ? https6 : http6;
@@ -12805,7 +13291,7 @@ function _post3(url, headers, payload) {
12805
13291
  res.on("end", () => {
12806
13292
  if (settled) return;
12807
13293
  settled = true;
12808
- resolve4({ statusCode: res.statusCode ?? 0, body });
13294
+ resolve5({ statusCode: res.statusCode ?? 0, body });
12809
13295
  });
12810
13296
  }
12811
13297
  );
@@ -12822,7 +13308,7 @@ function _post3(url, headers, payload) {
12822
13308
  });
12823
13309
  }
12824
13310
  function _get(url, headers) {
12825
- return new Promise((resolve4, reject) => {
13311
+ return new Promise((resolve5, reject) => {
12826
13312
  let settled = false;
12827
13313
  const u2 = new URL(url);
12828
13314
  const lib = u2.protocol === "https:" ? https6 : http6;
@@ -12846,7 +13332,7 @@ function _get(url, headers) {
12846
13332
  res.on("end", () => {
12847
13333
  if (settled) return;
12848
13334
  settled = true;
12849
- resolve4({ statusCode: res.statusCode ?? 0, body });
13335
+ resolve5({ statusCode: res.statusCode ?? 0, body });
12850
13336
  });
12851
13337
  }
12852
13338
  );
@@ -12863,7 +13349,7 @@ function _get(url, headers) {
12863
13349
  }
12864
13350
 
12865
13351
  // src/services/streaming-emitter.service.ts
12866
- var API_BASE6 = resolveApiBaseUrl();
13352
+ var API_BASE7 = resolveApiBaseUrl();
12867
13353
  var TICK_MS = 50;
12868
13354
  var ANSWER_POLL_MS = 1500;
12869
13355
  var SELECTOR_STABLE_MS = 800;
@@ -12874,7 +13360,7 @@ var TAIL_KEEP_BYTES2 = 1.5 * 1024 * 1024;
12874
13360
  var StreamingEmitterService = class {
12875
13361
  constructor(opts) {
12876
13362
  this.opts = opts;
12877
- this.apiBase = opts.apiBaseUrl ?? API_BASE6;
13363
+ this.apiBase = opts.apiBaseUrl ?? API_BASE7;
12878
13364
  this.headers = {
12879
13365
  "Content-Type": "application/json",
12880
13366
  "X-Codeam-Protocol-Version": "2.0.0",
@@ -12993,7 +13479,7 @@ var StreamingEmitterService = class {
12993
13479
  });
12994
13480
  }
12995
13481
  this.activeChunk = {
12996
- chunkId: (0, import_crypto2.randomUUID)(),
13482
+ chunkId: (0, import_crypto3.randomUUID)(),
12997
13483
  kind: last.kind,
12998
13484
  emittedContent: "",
12999
13485
  currentContent: last.content,
@@ -13061,7 +13547,7 @@ var StreamingEmitterService = class {
13061
13547
  }
13062
13548
  if (this.pendingAnswer) return;
13063
13549
  if (now - this.selectorFirstSeenAt < SELECTOR_STABLE_MS) return;
13064
- const questionId = (0, import_crypto2.randomUUID)();
13550
+ const questionId = (0, import_crypto3.randomUUID)();
13065
13551
  this.pendingAnswer = {
13066
13552
  questionId,
13067
13553
  options: selector.options.length > 0 ? selector.options : void 0,
@@ -13224,13 +13710,13 @@ function fetchQuotaUsage(runtime, historySvc) {
13224
13710
  }
13225
13711
 
13226
13712
  // src/commands/start/keep-alive.ts
13227
- var import_child_process8 = require("child_process");
13713
+ var import_child_process9 = require("child_process");
13228
13714
  function buildKeepAlive(ctx) {
13229
13715
  let timer = null;
13230
13716
  async function setIdleTimeout(minutes) {
13231
13717
  if (!ctx.inCodespace || !ctx.codespaceName) return;
13232
- await new Promise((resolve4) => {
13233
- const proc = (0, import_child_process8.spawn)(
13718
+ await new Promise((resolve5) => {
13719
+ const proc = (0, import_child_process9.spawn)(
13234
13720
  "gh",
13235
13721
  [
13236
13722
  "api",
@@ -13243,8 +13729,8 @@ function buildKeepAlive(ctx) {
13243
13729
  { stdio: "ignore", detached: true }
13244
13730
  );
13245
13731
  proc.unref();
13246
- proc.on("exit", () => resolve4());
13247
- proc.on("error", () => resolve4());
13732
+ proc.on("exit", () => resolve5());
13733
+ proc.on("error", () => resolve5());
13248
13734
  });
13249
13735
  }
13250
13736
  return {
@@ -13267,11 +13753,11 @@ function buildKeepAlive(ctx) {
13267
13753
  }
13268
13754
 
13269
13755
  // src/commands/start/handlers.ts
13270
- var fs24 = __toESM(require("fs"));
13756
+ var fs26 = __toESM(require("fs"));
13271
13757
  var os23 = __toESM(require("os"));
13272
- var path29 = __toESM(require("path"));
13273
- var import_crypto4 = require("crypto");
13274
- var import_child_process11 = require("child_process");
13758
+ var path32 = __toESM(require("path"));
13759
+ var import_crypto5 = require("crypto");
13760
+ var import_child_process13 = require("child_process");
13275
13761
 
13276
13762
  // src/lib/payload.ts
13277
13763
  var import_zod2 = require("zod");
@@ -13319,7 +13805,15 @@ var startCommandSchema = import_zod2.z.object({
13319
13805
  data: import_zod2.z.string().max(64 * 1024).optional(),
13320
13806
  cwd: import_zod2.z.string().max(4096).optional(),
13321
13807
  cols: import_zod2.z.number().int().min(1).max(500).optional(),
13322
- rows: import_zod2.z.number().int().min(1).max(200).optional()
13808
+ rows: import_zod2.z.number().int().min(1).max(200).optional(),
13809
+ // `apply_file_review` (Epic B follow-up — backend pushes this when
13810
+ // the user clicks APPROVE_CHANGES / REJECT_CHANGES in the diff
13811
+ // drawer). `filePath` is relative to the enclosing git repo; the
13812
+ // handler walks up from it to find `.git/` and runs `git add` or
13813
+ // `git restore` from there. `action='approved'` stages the edit,
13814
+ // `action='rejected'` discards every worktree change on the file.
13815
+ filePath: import_zod2.z.string().min(1).max(4096).optional(),
13816
+ action: import_zod2.z.enum(["approved", "rejected"]).optional()
13323
13817
  });
13324
13818
  function parsePayload2(schema, raw) {
13325
13819
  const result = schema.safeParse(raw);
@@ -13327,8 +13821,8 @@ function parsePayload2(schema, raw) {
13327
13821
  }
13328
13822
 
13329
13823
  // src/services/file-ops.service.ts
13330
- var fs22 = __toESM(require("fs/promises"));
13331
- var path26 = __toESM(require("path"));
13824
+ var fs23 = __toESM(require("fs/promises"));
13825
+ var path28 = __toESM(require("path"));
13332
13826
  var MAX_FILE_BYTES = 5 * 1024 * 1024;
13333
13827
  var MAX_WALK_DEPTH = 6;
13334
13828
  var MAX_VISITED_DIRS = 5e3;
@@ -13363,12 +13857,12 @@ var SUBDIR_IGNORE = /* @__PURE__ */ new Set([
13363
13857
  "__pycache__"
13364
13858
  ]);
13365
13859
  function isUnder(parent, candidate) {
13366
- const rel = path26.relative(parent, candidate);
13367
- return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
13860
+ const rel = path28.relative(parent, candidate);
13861
+ return rel === "" || !rel.startsWith("..") && !path28.isAbsolute(rel);
13368
13862
  }
13369
13863
  async function isExistingFile(absPath) {
13370
13864
  try {
13371
- const stat3 = await fs22.stat(absPath);
13865
+ const stat3 = await fs23.stat(absPath);
13372
13866
  return stat3.isFile();
13373
13867
  } catch {
13374
13868
  return false;
@@ -13381,13 +13875,13 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
13381
13875
  ctx.visited++;
13382
13876
  let entries = [];
13383
13877
  try {
13384
- entries = await fs22.readdir(dir, { withFileTypes: true });
13878
+ entries = await fs23.readdir(dir, { withFileTypes: true });
13385
13879
  } catch {
13386
13880
  return;
13387
13881
  }
13388
13882
  for (const e of entries) {
13389
13883
  if (!e.isFile()) continue;
13390
- const full = path26.join(dir, e.name);
13884
+ const full = path28.join(dir, e.name);
13391
13885
  if (needleVariants.some((needle) => full.endsWith(needle))) {
13392
13886
  ctx.matches.push(full);
13393
13887
  if (ctx.matches.length >= ctx.cap) return;
@@ -13397,21 +13891,21 @@ async function walkForSuffix(dir, needleVariants, depth, ctx) {
13397
13891
  if (!e.isDirectory()) continue;
13398
13892
  if (SUBDIR_IGNORE.has(e.name)) continue;
13399
13893
  if (e.name.startsWith(".") && SUBDIR_IGNORE.has(e.name)) continue;
13400
- await walkForSuffix(path26.join(dir, e.name), needleVariants, depth + 1, ctx);
13894
+ await walkForSuffix(path28.join(dir, e.name), needleVariants, depth + 1, ctx);
13401
13895
  if (ctx.matches.length >= ctx.cap) return;
13402
13896
  }
13403
13897
  }
13404
13898
  async function findFile(rawPath) {
13405
13899
  const cwd = process.cwd();
13406
- if (path26.isAbsolute(rawPath)) {
13407
- const abs = path26.normalize(rawPath);
13900
+ if (path28.isAbsolute(rawPath)) {
13901
+ const abs = path28.normalize(rawPath);
13408
13902
  if (isUnder(cwd, abs) && await isExistingFile(abs)) return abs;
13409
13903
  }
13410
- const direct = path26.resolve(cwd, rawPath);
13904
+ const direct = path28.resolve(cwd, rawPath);
13411
13905
  if (isUnder(cwd, direct) && await isExistingFile(direct)) return direct;
13412
- const normalized = path26.normalize(rawPath).replace(/^[./\\]+/, "");
13906
+ const normalized = path28.normalize(rawPath).replace(/^[./\\]+/, "");
13413
13907
  const needles = [
13414
- `${path26.sep}${normalized}`,
13908
+ `${path28.sep}${normalized}`,
13415
13909
  `/${normalized}`
13416
13910
  ].filter((v, i, a) => a.indexOf(v) === i);
13417
13911
  const ctx = { visited: 0, matches: [], cap: 16 };
@@ -13425,7 +13919,7 @@ async function findWriteTarget(rawPath) {
13425
13919
  const found = await findFile(rawPath);
13426
13920
  if (found) return found;
13427
13921
  const cwd = process.cwd();
13428
- const fallback = path26.isAbsolute(rawPath) ? path26.normalize(rawPath) : path26.resolve(cwd, rawPath);
13922
+ const fallback = path28.isAbsolute(rawPath) ? path28.normalize(rawPath) : path28.resolve(cwd, rawPath);
13429
13923
  if (!isUnder(cwd, fallback)) return null;
13430
13924
  return fallback;
13431
13925
  }
@@ -13442,11 +13936,11 @@ async function readProjectFile(rawPath) {
13442
13936
  if (!abs) {
13443
13937
  return { error: `File not found in the project tree: ${rawPath}` };
13444
13938
  }
13445
- const stat3 = await fs22.stat(abs);
13939
+ const stat3 = await fs23.stat(abs);
13446
13940
  if (stat3.size > MAX_FILE_BYTES) {
13447
13941
  return { error: `File too large (${(stat3.size / 1024 / 1024).toFixed(1)} MB > ${MAX_FILE_BYTES / 1024 / 1024} MB).` };
13448
13942
  }
13449
- const buf = await fs22.readFile(abs);
13943
+ const buf = await fs23.readFile(abs);
13450
13944
  if (looksBinary(buf)) {
13451
13945
  return { error: "Binary file \u2014 refusing to open in a code editor." };
13452
13946
  }
@@ -13465,8 +13959,8 @@ async function writeProjectFile(rawPath, content) {
13465
13959
  if (Buffer.byteLength(content, "utf-8") > MAX_FILE_BYTES) {
13466
13960
  return { error: "Content too large." };
13467
13961
  }
13468
- await fs22.mkdir(path26.dirname(abs), { recursive: true });
13469
- await fs22.writeFile(abs, content, "utf-8");
13962
+ await fs23.mkdir(path28.dirname(abs), { recursive: true });
13963
+ await fs23.writeFile(abs, content, "utf-8");
13470
13964
  return { ok: true };
13471
13965
  } catch (e) {
13472
13966
  const msg = e instanceof Error ? e.message : "Write failed";
@@ -13475,11 +13969,11 @@ async function writeProjectFile(rawPath, content) {
13475
13969
  }
13476
13970
 
13477
13971
  // src/services/project-ops.service.ts
13478
- var import_child_process9 = require("child_process");
13972
+ var import_child_process10 = require("child_process");
13479
13973
  var import_util2 = require("util");
13480
- var fs23 = __toESM(require("fs/promises"));
13481
- var path27 = __toESM(require("path"));
13482
- var execFileP3 = (0, import_util2.promisify)(import_child_process9.execFile);
13974
+ var fs24 = __toESM(require("fs/promises"));
13975
+ var path29 = __toESM(require("path"));
13976
+ var execFileP3 = (0, import_util2.promisify)(import_child_process10.execFile);
13483
13977
  var PROJECT_IGNORE = /* @__PURE__ */ new Set([
13484
13978
  "node_modules",
13485
13979
  ".git",
@@ -13526,7 +14020,7 @@ async function listProjectFiles(opts = {}) {
13526
14020
  }
13527
14021
  let entries = [];
13528
14022
  try {
13529
- entries = await fs23.readdir(dir, { withFileTypes: true });
14023
+ entries = await fs24.readdir(dir, { withFileTypes: true });
13530
14024
  } catch {
13531
14025
  return;
13532
14026
  }
@@ -13536,18 +14030,18 @@ async function listProjectFiles(opts = {}) {
13536
14030
  return;
13537
14031
  }
13538
14032
  if (PROJECT_IGNORE.has(e.name)) continue;
13539
- const full = path27.join(dir, e.name);
14033
+ const full = path29.join(dir, e.name);
13540
14034
  if (e.isDirectory()) {
13541
14035
  if (depth >= 12) continue;
13542
14036
  await walk(full, depth + 1);
13543
14037
  } else if (e.isFile()) {
13544
- const rel = path27.relative(root, full);
14038
+ const rel = path29.relative(root, full);
13545
14039
  if (q2 && !rel.toLowerCase().includes(q2) && !e.name.toLowerCase().includes(q2)) {
13546
14040
  continue;
13547
14041
  }
13548
14042
  let size = 0;
13549
14043
  try {
13550
- const st3 = await fs23.stat(full);
14044
+ const st3 = await fs24.stat(full);
13551
14045
  size = st3.size;
13552
14046
  } catch {
13553
14047
  }
@@ -13649,8 +14143,8 @@ async function gitStatus(cwd) {
13649
14143
  let hasMergeInProgress = false;
13650
14144
  try {
13651
14145
  const gitDir = (await git(["rev-parse", "--git-dir"], root)).stdout.trim();
13652
- const mergeHead = path27.isAbsolute(gitDir) ? path27.join(gitDir, "MERGE_HEAD") : path27.join(root, gitDir, "MERGE_HEAD");
13653
- await fs23.access(mergeHead);
14146
+ const mergeHead = path29.isAbsolute(gitDir) ? path29.join(gitDir, "MERGE_HEAD") : path29.join(root, gitDir, "MERGE_HEAD");
14147
+ await fs24.access(mergeHead);
13654
14148
  hasMergeInProgress = true;
13655
14149
  } catch {
13656
14150
  }
@@ -13796,7 +14290,7 @@ async function jsSearchFiles(opts, cwd, cap) {
13796
14290
  }
13797
14291
  let content = "";
13798
14292
  try {
13799
- content = await fs23.readFile(path27.join(cwd, f.path), "utf8");
14293
+ content = await fs24.readFile(path29.join(cwd, f.path), "utf8");
13800
14294
  } catch {
13801
14295
  continue;
13802
14296
  }
@@ -13819,8 +14313,8 @@ async function jsSearchFiles(opts, cwd, cap) {
13819
14313
  }
13820
14314
 
13821
14315
  // src/services/terminal-ops.service.ts
13822
- var import_child_process10 = require("child_process");
13823
- var import_crypto3 = require("crypto");
14316
+ var import_child_process11 = require("child_process");
14317
+ var import_crypto4 = require("crypto");
13824
14318
  var import_path3 = __toESM(require("path"));
13825
14319
  var MAX_CONCURRENT_SESSIONS = 4;
13826
14320
  var nodePtyModule;
@@ -13941,7 +14435,7 @@ function createPythonSession(id, shell, cwd, env, cols, rows) {
13941
14435
  }
13942
14436
  let child;
13943
14437
  try {
13944
- child = (0, import_child_process10.spawn)(python, ["-c", PYTHON_TERMINAL_HELPER, shell], {
14438
+ child = (0, import_child_process11.spawn)(python, ["-c", PYTHON_TERMINAL_HELPER, shell], {
13945
14439
  cwd,
13946
14440
  env: { ...env, COLUMNS: String(cols), LINES: String(rows) },
13947
14441
  stdio: ["pipe", "pipe", "pipe"]
@@ -13996,7 +14490,7 @@ function openTerminal(opts) {
13996
14490
  };
13997
14491
  const cols = Math.max(1, Math.min(opts.cols ?? 80, 500));
13998
14492
  const rows = Math.max(1, Math.min(opts.rows ?? 24, 200));
13999
- const id = (0, import_crypto3.randomUUID)();
14493
+ const id = (0, import_crypto4.randomUUID)();
14000
14494
  const ptyMod = loadNodePty2();
14001
14495
  if (ptyMod) {
14002
14496
  try {
@@ -14075,12 +14569,96 @@ function closeAllTerminals() {
14075
14569
  for (const id of Array.from(sessions.keys())) closeTerminal(id);
14076
14570
  }
14077
14571
 
14572
+ // src/services/apply-file-review.service.ts
14573
+ var import_child_process12 = require("child_process");
14574
+ var fs25 = __toESM(require("fs"));
14575
+ var path31 = __toESM(require("path"));
14576
+ async function applyFileReview(workingDir, filePath, action) {
14577
+ if (filePath.includes("..") || path31.isAbsolute(filePath)) {
14578
+ return { ok: false, action, filePath, error: "invalid file path" };
14579
+ }
14580
+ const absFile = path31.resolve(workingDir, filePath);
14581
+ const repoRoot = findGitRoot2(path31.dirname(absFile));
14582
+ if (!repoRoot) {
14583
+ return {
14584
+ ok: false,
14585
+ action,
14586
+ filePath,
14587
+ error: `no enclosing git repo for ${filePath}`
14588
+ };
14589
+ }
14590
+ const relInRepo = path31.relative(repoRoot, absFile);
14591
+ if (!relInRepo || relInRepo.startsWith("..")) {
14592
+ return { ok: false, action, filePath, error: "path escapes repo root" };
14593
+ }
14594
+ const args2 = action === "approved" ? ["add", "--", relInRepo] : ["restore", "--", relInRepo];
14595
+ const result = await runGit3(repoRoot, args2);
14596
+ if (!result.ok) {
14597
+ return {
14598
+ ok: false,
14599
+ action,
14600
+ filePath,
14601
+ repoRoot,
14602
+ error: result.stderr.slice(0, 500) || `git ${args2[0]} exited ${result.code}`
14603
+ };
14604
+ }
14605
+ log.info(
14606
+ "reviewApply",
14607
+ `git ${args2[0]} ${relInRepo} in ${repoRoot} \u2014 ok`
14608
+ );
14609
+ return { ok: true, action, filePath, repoRoot };
14610
+ }
14611
+ function runGit3(cwd, args2) {
14612
+ return new Promise((resolve5) => {
14613
+ let proc;
14614
+ try {
14615
+ proc = (0, import_child_process12.spawn)("git", args2, { cwd, env: process.env });
14616
+ } catch (err) {
14617
+ resolve5({ ok: false, code: -1, stdout: "", stderr: err.message });
14618
+ return;
14619
+ }
14620
+ let stdout = "";
14621
+ let stderr = "";
14622
+ proc.stdout?.on("data", (c2) => {
14623
+ stdout += c2.toString();
14624
+ });
14625
+ proc.stderr?.on("data", (c2) => {
14626
+ stderr += c2.toString();
14627
+ });
14628
+ proc.on(
14629
+ "error",
14630
+ (err) => resolve5({ ok: false, code: -1, stdout, stderr: stderr + err.message })
14631
+ );
14632
+ proc.on(
14633
+ "close",
14634
+ (code) => resolve5({ ok: code === 0, code: code ?? -1, stdout, stderr })
14635
+ );
14636
+ });
14637
+ }
14638
+ function findGitRoot2(startDir) {
14639
+ let dir = path31.resolve(startDir);
14640
+ const seen = /* @__PURE__ */ new Set();
14641
+ for (let i = 0; i < 256; i++) {
14642
+ if (seen.has(dir)) return null;
14643
+ seen.add(dir);
14644
+ try {
14645
+ const stat3 = fs25.statSync(path31.join(dir, ".git"), { throwIfNoEntry: false });
14646
+ if (stat3 && (stat3.isDirectory() || stat3.isFile())) return dir;
14647
+ } catch {
14648
+ }
14649
+ const parent = path31.dirname(dir);
14650
+ if (parent === dir) return null;
14651
+ dir = parent;
14652
+ }
14653
+ return null;
14654
+ }
14655
+
14078
14656
  // src/commands/start/handlers.ts
14079
14657
  var pendingAttachmentFiles = /* @__PURE__ */ new Set();
14080
14658
  function cleanupAttachmentTempFiles() {
14081
14659
  for (const p2 of pendingAttachmentFiles) {
14082
14660
  try {
14083
- fs24.unlinkSync(p2);
14661
+ fs26.unlinkSync(p2);
14084
14662
  } catch {
14085
14663
  }
14086
14664
  }
@@ -14089,8 +14667,8 @@ function cleanupAttachmentTempFiles() {
14089
14667
  function saveFilesTemp(files) {
14090
14668
  return files.filter(({ base64 }) => base64 && base64.length > 0).map(({ filename, base64 }) => {
14091
14669
  const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 80);
14092
- const tmpPath = path29.join(os23.tmpdir(), `codeam-${(0, import_crypto4.randomUUID)()}-${safeName}`);
14093
- fs24.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
14670
+ const tmpPath = path32.join(os23.tmpdir(), `codeam-${(0, import_crypto5.randomUUID)()}-${safeName}`);
14671
+ fs26.writeFileSync(tmpPath, Buffer.from(base64, "base64"));
14094
14672
  pendingAttachmentFiles.add(tmpPath);
14095
14673
  return tmpPath;
14096
14674
  });
@@ -14110,7 +14688,7 @@ var startTask = (ctx, _cmd, parsed) => {
14110
14688
  setTimeout(() => {
14111
14689
  for (const p2 of paths) {
14112
14690
  try {
14113
- fs24.unlinkSync(p2);
14691
+ fs26.unlinkSync(p2);
14114
14692
  } catch {
14115
14693
  }
14116
14694
  pendingAttachmentFiles.delete(p2);
@@ -14223,7 +14801,7 @@ var sessionTerminated = (ctx) => {
14223
14801
  } catch {
14224
14802
  }
14225
14803
  try {
14226
- const proc = (0, import_child_process11.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
14804
+ const proc = (0, import_child_process13.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
14227
14805
  detached: true,
14228
14806
  stdio: "ignore"
14229
14807
  });
@@ -14245,7 +14823,7 @@ var shutdownSession = async (ctx, cmd) => {
14245
14823
  }
14246
14824
  if (ctx.keepAliveCtx.inCodespace && ctx.keepAliveCtx.codespaceName) {
14247
14825
  try {
14248
- const stopProc = (0, import_child_process11.spawn)(
14826
+ const stopProc = (0, import_child_process13.spawn)(
14249
14827
  "bash",
14250
14828
  ["-lc", `sleep 1; gh codespace stop -c ${JSON.stringify(ctx.keepAliveCtx.codespaceName)} >/dev/null 2>&1 || true`],
14251
14829
  { detached: true, stdio: "ignore" }
@@ -14255,7 +14833,7 @@ var shutdownSession = async (ctx, cmd) => {
14255
14833
  }
14256
14834
  }
14257
14835
  try {
14258
- const proc = (0, import_child_process11.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
14836
+ const proc = (0, import_child_process13.spawn)("bash", ["-lc", "pm2 delete codeam-pair >/dev/null 2>&1 || true"], {
14259
14837
  detached: true,
14260
14838
  stdio: "ignore"
14261
14839
  });
@@ -14266,7 +14844,7 @@ var shutdownSession = async (ctx, cmd) => {
14266
14844
  ctx.relay.stop();
14267
14845
  process.exit(0);
14268
14846
  };
14269
- var readFile3 = async (ctx, cmd, parsed) => {
14847
+ var readFile4 = async (ctx, cmd, parsed) => {
14270
14848
  if (!parsed.path) {
14271
14849
  await ctx.relay.sendResult(cmd.id, "failed", { error: "Missing path" });
14272
14850
  return;
@@ -14274,7 +14852,7 @@ var readFile3 = async (ctx, cmd, parsed) => {
14274
14852
  const result = await readProjectFile(parsed.path);
14275
14853
  await ctx.relay.sendResult(cmd.id, "completed", result);
14276
14854
  };
14277
- var writeFile2 = async (ctx, cmd, parsed) => {
14855
+ var writeFile3 = async (ctx, cmd, parsed) => {
14278
14856
  if (!parsed.path || typeof parsed.content !== "string") {
14279
14857
  await ctx.relay.sendResult(cmd.id, "failed", { error: "Missing path or content" });
14280
14858
  return;
@@ -14378,6 +14956,24 @@ var gitResolveH = async (ctx, cmd, parsed) => {
14378
14956
  const result = await gitResolve(parsed.path, parsed.side);
14379
14957
  await ctx.relay.sendResult(cmd.id, "completed", result);
14380
14958
  };
14959
+ var applyFileReviewH = async (ctx, cmd, parsed) => {
14960
+ if (!parsed.filePath || !parsed.action) {
14961
+ await ctx.relay.sendResult(cmd.id, "failed", {
14962
+ error: "Missing filePath or action"
14963
+ });
14964
+ return;
14965
+ }
14966
+ const result = await applyFileReview(
14967
+ process.cwd(),
14968
+ parsed.filePath,
14969
+ parsed.action
14970
+ );
14971
+ await ctx.relay.sendResult(
14972
+ cmd.id,
14973
+ result.ok ? "completed" : "failed",
14974
+ result
14975
+ );
14976
+ };
14381
14977
  var handlers = {
14382
14978
  start_task: startTask,
14383
14979
  provide_input: provideInput,
@@ -14393,8 +14989,8 @@ var handlers = {
14393
14989
  set_keep_alive: setKeepAlive,
14394
14990
  session_terminated: sessionTerminated,
14395
14991
  shutdown_session: shutdownSession,
14396
- read_file: readFile3,
14397
- write_file: writeFile2,
14992
+ read_file: readFile4,
14993
+ write_file: writeFile3,
14398
14994
  list_files: listFiles,
14399
14995
  search_files: searchFilesH,
14400
14996
  terminal_open: terminalOpenH,
@@ -14408,7 +15004,8 @@ var handlers = {
14408
15004
  git_commit: gitCommitH,
14409
15005
  git_push: gitPushH,
14410
15006
  git_pull: gitPullH,
14411
- git_resolve: gitResolveH
15007
+ git_resolve: gitResolveH,
15008
+ apply_file_review: applyFileReviewH
14412
15009
  };
14413
15010
  async function dispatchCommand(ctx, cmd) {
14414
15011
  const parsed = parsePayload2(startCommandSchema, cmd.payload);
@@ -14480,6 +15077,8 @@ async function start(requestedAgent) {
14480
15077
  historySvc.uploadDelta().catch(() => {
14481
15078
  });
14482
15079
  }, 400);
15080
+ turnFiles?.flushTurn().catch(() => {
15081
+ });
14483
15082
  },
14484
15083
  () => {
14485
15084
  const prevCount = historySvc.getCurrentMessageCount();
@@ -14488,11 +15087,20 @@ async function start(requestedAgent) {
14488
15087
  session.pluginAuthToken,
14489
15088
  runtime
14490
15089
  );
15090
+ const dirtyTracker = session.pluginAuthToken ? new RepoDirtyTracker() : null;
14491
15091
  const fileWatcher = session.pluginAuthToken ? new FileWatcherService({
14492
15092
  workingDir: cwd,
14493
15093
  sessionId: session.id,
14494
15094
  pluginId,
14495
- pluginAuthToken: session.pluginAuthToken
15095
+ pluginAuthToken: session.pluginAuthToken,
15096
+ onRepoDirty: dirtyTracker ? (repoRoot) => dirtyTracker.markDirty(repoRoot) : void 0
15097
+ }) : null;
15098
+ const turnFiles = session.pluginAuthToken ? new TurnFileAggregator({
15099
+ workingDir: cwd,
15100
+ sessionId: session.id,
15101
+ pluginId,
15102
+ pluginAuthToken: session.pluginAuthToken,
15103
+ dirtyTracker: dirtyTracker ?? void 0
14496
15104
  }) : null;
14497
15105
  let streamingEmitter = null;
14498
15106
  const agent = new AgentService(
@@ -14510,6 +15118,7 @@ async function start(requestedAgent) {
14510
15118
  outputSvc.dispose();
14511
15119
  relay.stop();
14512
15120
  void fileWatcher?.stop();
15121
+ turnFiles?.stop();
14513
15122
  void streamingEmitter?.stop();
14514
15123
  closeAllTerminals();
14515
15124
  cleanupAttachmentTempFiles();
@@ -14577,7 +15186,7 @@ async function start(requestedAgent) {
14577
15186
  }
14578
15187
 
14579
15188
  // src/commands/pair.ts
14580
- var import_crypto5 = require("crypto");
15189
+ var import_crypto6 = require("crypto");
14581
15190
  var import_picocolors3 = __toESM(require("picocolors"));
14582
15191
 
14583
15192
  // src/ui/prompts.ts
@@ -14641,7 +15250,7 @@ async function pair(args2 = []) {
14641
15250
  const flagAgent = parseAgentFlag(args2);
14642
15251
  const agentId = dryRun ? flagAgent ?? config.preferredAgent ?? "claude" : flagAgent ?? await promptForAgent(config.preferredAgent ?? "claude");
14643
15252
  showIntro();
14644
- const pluginId = (0, import_crypto5.randomUUID)();
15253
+ const pluginId = (0, import_crypto6.randomUUID)();
14645
15254
  capture("pair_started", { agentId, pluginId, dryRun });
14646
15255
  const spin = dist_exports.spinner();
14647
15256
  spin.start("Requesting pairing code...");
@@ -14676,7 +15285,7 @@ async function pair(args2 = []) {
14676
15285
  waitSpin.message(waitMessage());
14677
15286
  }, 1e3);
14678
15287
  countdownInterval.unref?.();
14679
- await new Promise((resolve4) => {
15288
+ await new Promise((resolve5) => {
14680
15289
  let stopPolling = null;
14681
15290
  function sigintHandler() {
14682
15291
  clearInterval(countdownInterval);
@@ -14716,7 +15325,7 @@ async function pair(args2 = []) {
14716
15325
  });
14717
15326
  showSuccess(`Paired with ${info.userName} (${info.plan})`);
14718
15327
  console.log("");
14719
- resolve4();
15328
+ resolve5();
14720
15329
  },
14721
15330
  () => {
14722
15331
  clearInterval(countdownInterval);
@@ -14732,10 +15341,10 @@ async function pair(args2 = []) {
14732
15341
  }
14733
15342
 
14734
15343
  // src/commands/pair-auto.ts
14735
- var fs25 = __toESM(require("fs"));
15344
+ var fs27 = __toESM(require("fs"));
14736
15345
  var os24 = __toESM(require("os"));
14737
- var import_crypto6 = require("crypto");
14738
- var API_BASE7 = resolveApiBaseUrl();
15346
+ var import_crypto7 = require("crypto");
15347
+ var API_BASE8 = resolveApiBaseUrl();
14739
15348
  function fail(msg) {
14740
15349
  console.error(`
14741
15350
  ${msg}
@@ -14750,12 +15359,12 @@ function readTokenFromArgs(args2) {
14750
15359
  }
14751
15360
  const fileFlag = args2.find((a) => a.startsWith("--token-file="));
14752
15361
  if (fileFlag) {
14753
- const path37 = fileFlag.slice("--token-file=".length);
15362
+ const path40 = fileFlag.slice("--token-file=".length);
14754
15363
  try {
14755
- const content = fs25.readFileSync(path37, "utf8").trim();
14756
- if (content.length === 0) fail(`--token-file ${path37} is empty`);
15364
+ const content = fs27.readFileSync(path40, "utf8").trim();
15365
+ if (content.length === 0) fail(`--token-file ${path40} is empty`);
14757
15366
  try {
14758
- fs25.unlinkSync(path37);
15367
+ fs27.unlinkSync(path40);
14759
15368
  } catch {
14760
15369
  }
14761
15370
  return content;
@@ -14775,7 +15384,7 @@ function networkError(msg, cause) {
14775
15384
  return err;
14776
15385
  }
14777
15386
  async function claimOnce(token, pluginId) {
14778
- const url = `${API_BASE7}/api/pairing/claim-auto-token`;
15387
+ const url = `${API_BASE8}/api/pairing/claim-auto-token`;
14779
15388
  const body = {
14780
15389
  token,
14781
15390
  pluginId,
@@ -14839,7 +15448,7 @@ async function claim(token, pluginId) {
14839
15448
  }
14840
15449
  async function pairAuto(args2) {
14841
15450
  const token = readTokenFromArgs(args2);
14842
- const pluginId = (0, import_crypto6.randomUUID)();
15451
+ const pluginId = (0, import_crypto7.randomUUID)();
14843
15452
  capture("pair_auto_started", { pluginId });
14844
15453
  console.log(" Claiming pairing token\u2026");
14845
15454
  const claimed = await claim(token, pluginId);
@@ -14965,7 +15574,7 @@ function status() {
14965
15574
 
14966
15575
  // src/commands/logout.ts
14967
15576
  var import_picocolors6 = __toESM(require("picocolors"));
14968
- var API_BASE8 = resolveApiBaseUrl();
15577
+ var API_BASE9 = resolveApiBaseUrl();
14969
15578
  async function notifyBackendOffline() {
14970
15579
  const cfg = loadCliConfig();
14971
15580
  const pluginIds = /* @__PURE__ */ new Set([
@@ -14977,7 +15586,7 @@ async function notifyBackendOffline() {
14977
15586
  try {
14978
15587
  await Promise.all(
14979
15588
  Array.from(pluginIds).map(
14980
- (pluginId) => _postJson(`${API_BASE8}/api/plugin/heartbeat`, {
15589
+ (pluginId) => _postJson(`${API_BASE9}/api/plugin/heartbeat`, {
14981
15590
  pluginId,
14982
15591
  online: false
14983
15592
  }).catch((err) => {
@@ -15005,11 +15614,11 @@ async function logout() {
15005
15614
  var import_picocolors9 = __toESM(require("picocolors"));
15006
15615
 
15007
15616
  // src/services/providers/github-codespaces.ts
15008
- var import_child_process12 = require("child_process");
15617
+ var import_child_process14 = require("child_process");
15009
15618
  var import_util3 = require("util");
15010
15619
  var import_picocolors7 = __toESM(require("picocolors"));
15011
- var path30 = __toESM(require("path"));
15012
- var execFileP4 = (0, import_util3.promisify)(import_child_process12.execFile);
15620
+ var path33 = __toESM(require("path"));
15621
+ var execFileP4 = (0, import_util3.promisify)(import_child_process14.execFile);
15013
15622
  var MAX_BUFFER = 8 * 1024 * 1024;
15014
15623
  function resetStdinForChild() {
15015
15624
  if (process.stdin.isTTY) {
@@ -15052,12 +15661,12 @@ var GitHubCodespacesProvider = class {
15052
15661
  }
15053
15662
  if (!isAuthed) {
15054
15663
  resetStdinForChild();
15055
- await new Promise((resolve4, reject) => {
15056
- const proc = (0, import_child_process12.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
15664
+ await new Promise((resolve5, reject) => {
15665
+ const proc = (0, import_child_process14.spawn)("gh", ["auth", "login", "-s", "codespace,repo,read:user"], {
15057
15666
  stdio: "inherit"
15058
15667
  });
15059
15668
  proc.on("exit", (code) => {
15060
- if (code === 0) resolve4();
15669
+ if (code === 0) resolve5();
15061
15670
  else reject(new Error("gh auth login failed."));
15062
15671
  });
15063
15672
  proc.on("error", reject);
@@ -15086,13 +15695,13 @@ var GitHubCodespacesProvider = class {
15086
15695
  }
15087
15696
  wt(noteLines.join("\n"), "One more permission needed");
15088
15697
  resetStdinForChild();
15089
- const refreshCode = await new Promise((resolve4, reject) => {
15090
- const proc = (0, import_child_process12.spawn)(
15698
+ const refreshCode = await new Promise((resolve5, reject) => {
15699
+ const proc = (0, import_child_process14.spawn)(
15091
15700
  "gh",
15092
15701
  ["auth", "refresh", "-h", "github.com", "-s", "codespace"],
15093
15702
  { stdio: "inherit" }
15094
15703
  );
15095
- proc.on("exit", (code) => resolve4(code ?? 1));
15704
+ proc.on("exit", (code) => resolve5(code ?? 1));
15096
15705
  proc.on("error", reject);
15097
15706
  });
15098
15707
  if (refreshCode !== 0) {
@@ -15236,10 +15845,10 @@ var GitHubCodespacesProvider = class {
15236
15845
  if (q(proceed) || !proceed) return;
15237
15846
  O2.step(`Installing gh via ${installCmd.describe}\u2026`);
15238
15847
  resetStdinForChild();
15239
- const ok = await new Promise((resolve4) => {
15240
- const proc = (0, import_child_process12.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
15241
- proc.on("exit", (code) => resolve4(code === 0));
15242
- proc.on("error", () => resolve4(false));
15848
+ const ok = await new Promise((resolve5) => {
15849
+ const proc = (0, import_child_process14.spawn)(installCmd.exe, installCmd.args, { stdio: "inherit" });
15850
+ proc.on("exit", (code) => resolve5(code === 0));
15851
+ proc.on("error", () => resolve5(false));
15243
15852
  });
15244
15853
  if (ok) O2.success("gh installed");
15245
15854
  else O2.error("gh install failed");
@@ -15263,14 +15872,14 @@ var GitHubCodespacesProvider = class {
15263
15872
  "Expanding GitHub scopes"
15264
15873
  );
15265
15874
  resetStdinForChild();
15266
- await new Promise((resolve4, reject) => {
15267
- const proc = (0, import_child_process12.spawn)(
15875
+ await new Promise((resolve5, reject) => {
15876
+ const proc = (0, import_child_process14.spawn)(
15268
15877
  "gh",
15269
15878
  ["auth", "refresh", "-h", "github.com", "-s", "repo,read:org"],
15270
15879
  { stdio: "inherit" }
15271
15880
  );
15272
15881
  proc.on("exit", (code) => {
15273
- if (code === 0) resolve4();
15882
+ if (code === 0) resolve5();
15274
15883
  else reject(new Error(
15275
15884
  "gh auth refresh failed. Re-run `gh auth refresh -h github.com -s repo,read:org` manually."
15276
15885
  ));
@@ -15441,13 +16050,13 @@ var GitHubCodespacesProvider = class {
15441
16050
  }
15442
16051
  async streamCommand(workspaceId, command2) {
15443
16052
  resetStdinForChild();
15444
- return new Promise((resolve4, reject) => {
15445
- const proc = (0, import_child_process12.spawn)(
16053
+ return new Promise((resolve5, reject) => {
16054
+ const proc = (0, import_child_process14.spawn)(
15446
16055
  "gh",
15447
16056
  ["codespace", "ssh", "-c", workspaceId, "--", "-tt", command2],
15448
16057
  { stdio: "inherit" }
15449
16058
  );
15450
- proc.on("exit", (code) => resolve4({ code: code ?? 0 }));
16059
+ proc.on("exit", (code) => resolve5({ code: code ?? 0 }));
15451
16060
  proc.on("error", reject);
15452
16061
  });
15453
16062
  }
@@ -15468,12 +16077,12 @@ var GitHubCodespacesProvider = class {
15468
16077
  "--",
15469
16078
  `mkdir -p ${shellQuote(remoteDir)} && tar -xzf - -C ${shellQuote(remoteDir)}`
15470
16079
  ];
15471
- await new Promise((resolve4, reject) => {
15472
- const tar = (0, import_child_process12.spawn)("tar", tarArgs, {
16080
+ await new Promise((resolve5, reject) => {
16081
+ const tar = (0, import_child_process14.spawn)("tar", tarArgs, {
15473
16082
  stdio: ["ignore", "pipe", "pipe"],
15474
16083
  env: tarEnv
15475
16084
  });
15476
- const ssh = (0, import_child_process12.spawn)("gh", sshArgs, {
16085
+ const ssh = (0, import_child_process14.spawn)("gh", sshArgs, {
15477
16086
  stdio: [tar.stdout, "pipe", "pipe"]
15478
16087
  });
15479
16088
  let tarErr = "";
@@ -15488,7 +16097,7 @@ var GitHubCodespacesProvider = class {
15488
16097
  ssh.on("error", reject);
15489
16098
  ssh.on("exit", (code) => {
15490
16099
  if (code === 0) {
15491
- resolve4();
16100
+ resolve5();
15492
16101
  } else {
15493
16102
  const reason = (sshErr || tarErr || `exit ${code}`).trim().slice(0, 500);
15494
16103
  reject(new Error(`Remote tar failed: ${reason}`));
@@ -15497,7 +16106,7 @@ var GitHubCodespacesProvider = class {
15497
16106
  });
15498
16107
  }
15499
16108
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
15500
- const remoteDir = path30.posix.dirname(remotePath);
16109
+ const remoteDir = path33.posix.dirname(remotePath);
15501
16110
  const parts = [
15502
16111
  `mkdir -p ${shellQuote(remoteDir)}`,
15503
16112
  `cat > ${shellQuote(remotePath)}`
@@ -15506,8 +16115,8 @@ var GitHubCodespacesProvider = class {
15506
16115
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote(remotePath)}`);
15507
16116
  }
15508
16117
  const cmd = parts.join(" && ");
15509
- await new Promise((resolve4, reject) => {
15510
- const proc = (0, import_child_process12.spawn)(
16118
+ await new Promise((resolve5, reject) => {
16119
+ const proc = (0, import_child_process14.spawn)(
15511
16120
  "gh",
15512
16121
  ["codespace", "ssh", "-c", workspaceId, "--", cmd],
15513
16122
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -15518,7 +16127,7 @@ var GitHubCodespacesProvider = class {
15518
16127
  });
15519
16128
  proc.on("error", reject);
15520
16129
  proc.on("exit", (code) => {
15521
- if (code === 0) resolve4();
16130
+ if (code === 0) resolve5();
15522
16131
  else reject(new Error(`Remote write failed: ${(stderr || `exit ${code}`).trim().slice(0, 500)}`));
15523
16132
  });
15524
16133
  proc.stdin?.write(contents);
@@ -15565,11 +16174,11 @@ function shellQuote(s) {
15565
16174
  }
15566
16175
 
15567
16176
  // src/services/providers/gitpod.ts
15568
- var import_child_process13 = require("child_process");
16177
+ var import_child_process15 = require("child_process");
15569
16178
  var import_util4 = require("util");
15570
- var path31 = __toESM(require("path"));
16179
+ var path34 = __toESM(require("path"));
15571
16180
  var import_picocolors8 = __toESM(require("picocolors"));
15572
- var execFileP5 = (0, import_util4.promisify)(import_child_process13.execFile);
16181
+ var execFileP5 = (0, import_util4.promisify)(import_child_process15.execFile);
15573
16182
  var MAX_BUFFER2 = 8 * 1024 * 1024;
15574
16183
  function resetStdinForChild2() {
15575
16184
  if (process.stdin.isTTY) {
@@ -15608,10 +16217,10 @@ var GitpodProvider = class {
15608
16217
  "Authenticating Gitpod"
15609
16218
  );
15610
16219
  resetStdinForChild2();
15611
- await new Promise((resolve4, reject) => {
15612
- const proc = (0, import_child_process13.spawn)("gitpod", ["login"], { stdio: "inherit" });
16220
+ await new Promise((resolve5, reject) => {
16221
+ const proc = (0, import_child_process15.spawn)("gitpod", ["login"], { stdio: "inherit" });
15613
16222
  proc.on("exit", (code) => {
15614
- if (code === 0) resolve4();
16223
+ if (code === 0) resolve5();
15615
16224
  else reject(new Error("gitpod login failed."));
15616
16225
  });
15617
16226
  proc.on("error", reject);
@@ -15760,13 +16369,13 @@ var GitpodProvider = class {
15760
16369
  }
15761
16370
  async streamCommand(workspaceId, command2) {
15762
16371
  resetStdinForChild2();
15763
- return new Promise((resolve4, reject) => {
15764
- const proc = (0, import_child_process13.spawn)(
16372
+ return new Promise((resolve5, reject) => {
16373
+ const proc = (0, import_child_process15.spawn)(
15765
16374
  "gitpod",
15766
16375
  ["workspace", "ssh", workspaceId, "--", "-tt", command2],
15767
16376
  { stdio: "inherit" }
15768
16377
  );
15769
- proc.on("exit", (code) => resolve4({ code: code ?? 0 }));
16378
+ proc.on("exit", (code) => resolve5({ code: code ?? 0 }));
15770
16379
  proc.on("error", reject);
15771
16380
  });
15772
16381
  }
@@ -15780,12 +16389,12 @@ var GitpodProvider = class {
15780
16389
  tarArgs.push(".");
15781
16390
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
15782
16391
  const remoteCmd = `mkdir -p ${shellQuote2(remoteDir)} && tar -xzf - -C ${shellQuote2(remoteDir)}`;
15783
- await new Promise((resolve4, reject) => {
15784
- const tar = (0, import_child_process13.spawn)("tar", tarArgs, {
16392
+ await new Promise((resolve5, reject) => {
16393
+ const tar = (0, import_child_process15.spawn)("tar", tarArgs, {
15785
16394
  stdio: ["ignore", "pipe", "pipe"],
15786
16395
  env: tarEnv
15787
16396
  });
15788
- const ssh = (0, import_child_process13.spawn)(
16397
+ const ssh = (0, import_child_process15.spawn)(
15789
16398
  "gitpod",
15790
16399
  ["workspace", "ssh", workspaceId, "--", remoteCmd],
15791
16400
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -15801,13 +16410,13 @@ var GitpodProvider = class {
15801
16410
  tar.on("error", reject);
15802
16411
  ssh.on("error", reject);
15803
16412
  ssh.on("exit", (code) => {
15804
- if (code === 0) resolve4();
16413
+ if (code === 0) resolve5();
15805
16414
  else reject(new Error(`Remote tar failed: ${(sshErr || tarErr || `exit ${code}`).trim().slice(0, 500)}`));
15806
16415
  });
15807
16416
  });
15808
16417
  }
15809
16418
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
15810
- const remoteDir = path31.posix.dirname(remotePath);
16419
+ const remoteDir = path34.posix.dirname(remotePath);
15811
16420
  const parts = [
15812
16421
  `mkdir -p ${shellQuote2(remoteDir)}`,
15813
16422
  `cat > ${shellQuote2(remotePath)}`
@@ -15816,8 +16425,8 @@ var GitpodProvider = class {
15816
16425
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote2(remotePath)}`);
15817
16426
  }
15818
16427
  const cmd = parts.join(" && ");
15819
- await new Promise((resolve4, reject) => {
15820
- const proc = (0, import_child_process13.spawn)(
16428
+ await new Promise((resolve5, reject) => {
16429
+ const proc = (0, import_child_process15.spawn)(
15821
16430
  "gitpod",
15822
16431
  ["workspace", "ssh", workspaceId, "--", cmd],
15823
16432
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -15828,7 +16437,7 @@ var GitpodProvider = class {
15828
16437
  });
15829
16438
  proc.on("error", reject);
15830
16439
  proc.on("exit", (code) => {
15831
- if (code === 0) resolve4();
16440
+ if (code === 0) resolve5();
15832
16441
  else reject(new Error(`Remote write failed: ${(stderr || `exit ${code}`).trim().slice(0, 500)}`));
15833
16442
  });
15834
16443
  proc.stdin?.write(contents);
@@ -15841,10 +16450,10 @@ function shellQuote2(s) {
15841
16450
  }
15842
16451
 
15843
16452
  // src/services/providers/gitlab-workspaces.ts
15844
- var import_child_process14 = require("child_process");
16453
+ var import_child_process16 = require("child_process");
15845
16454
  var import_util5 = require("util");
15846
- var path32 = __toESM(require("path"));
15847
- var execFileP6 = (0, import_util5.promisify)(import_child_process14.execFile);
16455
+ var path35 = __toESM(require("path"));
16456
+ var execFileP6 = (0, import_util5.promisify)(import_child_process16.execFile);
15848
16457
  var MAX_BUFFER3 = 8 * 1024 * 1024;
15849
16458
  var GITLAB_API_BASE = process.env.CODEAM_GITLAB_API_URL ?? "https://gitlab.com/api/v4";
15850
16459
  function resetStdinForChild3() {
@@ -15885,14 +16494,14 @@ var GitLabWorkspacesProvider = class {
15885
16494
  "Authenticating GitLab"
15886
16495
  );
15887
16496
  resetStdinForChild3();
15888
- await new Promise((resolve4, reject) => {
15889
- const proc = (0, import_child_process14.spawn)(
16497
+ await new Promise((resolve5, reject) => {
16498
+ const proc = (0, import_child_process16.spawn)(
15890
16499
  "glab",
15891
16500
  ["auth", "login", "--scopes", "api,read_user,read_repository"],
15892
16501
  { stdio: "inherit" }
15893
16502
  );
15894
16503
  proc.on("exit", (code) => {
15895
- if (code === 0) resolve4();
16504
+ if (code === 0) resolve5();
15896
16505
  else reject(new Error("glab auth login failed."));
15897
16506
  });
15898
16507
  proc.on("error", reject);
@@ -16057,13 +16666,13 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
16057
16666
  async streamCommand(workspaceId, command2) {
16058
16667
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
16059
16668
  resetStdinForChild3();
16060
- return new Promise((resolve4, reject) => {
16061
- const proc = (0, import_child_process14.spawn)(
16669
+ return new Promise((resolve5, reject) => {
16670
+ const proc = (0, import_child_process16.spawn)(
16062
16671
  "ssh",
16063
16672
  ["-tt", "-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, command2],
16064
16673
  { stdio: "inherit" }
16065
16674
  );
16066
- proc.on("exit", (code) => resolve4({ code: code ?? 0 }));
16675
+ proc.on("exit", (code) => resolve5({ code: code ?? 0 }));
16067
16676
  proc.on("error", reject);
16068
16677
  });
16069
16678
  }
@@ -16078,9 +16687,9 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
16078
16687
  tarArgs.push(".");
16079
16688
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
16080
16689
  const remoteCmd = `mkdir -p ${shellQuote3(remoteDir)} && tar -xzf - -C ${shellQuote3(remoteDir)}`;
16081
- await new Promise((resolve4, reject) => {
16082
- const tar = (0, import_child_process14.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
16083
- const ssh = (0, import_child_process14.spawn)(
16690
+ await new Promise((resolve5, reject) => {
16691
+ const tar = (0, import_child_process16.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
16692
+ const ssh = (0, import_child_process16.spawn)(
16084
16693
  "ssh",
16085
16694
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, remoteCmd],
16086
16695
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -16096,21 +16705,21 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
16096
16705
  tar.on("error", reject);
16097
16706
  ssh.on("error", reject);
16098
16707
  ssh.on("exit", (code) => {
16099
- if (code === 0) resolve4();
16708
+ if (code === 0) resolve5();
16100
16709
  else reject(new Error(`Remote tar failed: ${(sshErr || tarErr || `exit ${code}`).trim().slice(0, 500)}`));
16101
16710
  });
16102
16711
  });
16103
16712
  }
16104
16713
  async uploadFile(workspaceId, remotePath, contents, options = {}) {
16105
16714
  const sshHost = process.env.CODEAM_GITLAB_SSH_HOST ?? "workspaces.gitlab.com";
16106
- const remoteDir = path32.posix.dirname(remotePath);
16715
+ const remoteDir = path35.posix.dirname(remotePath);
16107
16716
  const parts = [`mkdir -p ${shellQuote3(remoteDir)}`, `cat > ${shellQuote3(remotePath)}`];
16108
16717
  if (options.mode != null) {
16109
16718
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote3(remotePath)}`);
16110
16719
  }
16111
16720
  const cmd = parts.join(" && ");
16112
- await new Promise((resolve4, reject) => {
16113
- const proc = (0, import_child_process14.spawn)(
16721
+ await new Promise((resolve5, reject) => {
16722
+ const proc = (0, import_child_process16.spawn)(
16114
16723
  "ssh",
16115
16724
  ["-o", "StrictHostKeyChecking=accept-new", `${workspaceId}@${sshHost}`, cmd],
16116
16725
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -16121,7 +16730,7 @@ Docs: https://docs.gitlab.com/ee/user/workspace/configuration.html`
16121
16730
  });
16122
16731
  proc.on("error", reject);
16123
16732
  proc.on("exit", (code) => {
16124
- if (code === 0) resolve4();
16733
+ if (code === 0) resolve5();
16125
16734
  else reject(new Error(`Remote write failed: ${(stderr || `exit ${code}`).trim().slice(0, 500)}`));
16126
16735
  });
16127
16736
  proc.stdin?.write(contents);
@@ -16169,10 +16778,10 @@ function shellQuote3(s) {
16169
16778
  }
16170
16779
 
16171
16780
  // src/services/providers/railway.ts
16172
- var import_child_process15 = require("child_process");
16781
+ var import_child_process17 = require("child_process");
16173
16782
  var import_util6 = require("util");
16174
- var path33 = __toESM(require("path"));
16175
- var execFileP7 = (0, import_util6.promisify)(import_child_process15.execFile);
16783
+ var path36 = __toESM(require("path"));
16784
+ var execFileP7 = (0, import_util6.promisify)(import_child_process17.execFile);
16176
16785
  var MAX_BUFFER4 = 8 * 1024 * 1024;
16177
16786
  function resetStdinForChild4() {
16178
16787
  if (process.stdin.isTTY) {
@@ -16212,10 +16821,10 @@ var RailwayProvider = class {
16212
16821
  "Authenticating Railway"
16213
16822
  );
16214
16823
  resetStdinForChild4();
16215
- await new Promise((resolve4, reject) => {
16216
- const proc = (0, import_child_process15.spawn)("railway", ["login"], { stdio: "inherit" });
16824
+ await new Promise((resolve5, reject) => {
16825
+ const proc = (0, import_child_process17.spawn)("railway", ["login"], { stdio: "inherit" });
16217
16826
  proc.on("exit", (code) => {
16218
- if (code === 0) resolve4();
16827
+ if (code === 0) resolve5();
16219
16828
  else reject(new Error("railway login failed."));
16220
16829
  });
16221
16830
  proc.on("error", reject);
@@ -16355,13 +16964,13 @@ var RailwayProvider = class {
16355
16964
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
16356
16965
  }
16357
16966
  resetStdinForChild4();
16358
- return new Promise((resolve4, reject) => {
16359
- const proc = (0, import_child_process15.spawn)(
16967
+ return new Promise((resolve5, reject) => {
16968
+ const proc = (0, import_child_process17.spawn)(
16360
16969
  "railway",
16361
16970
  ["shell", "--project", projectId, "--service", serviceId, "--command", command2],
16362
16971
  { stdio: "inherit" }
16363
16972
  );
16364
- proc.on("exit", (code) => resolve4({ code: code ?? 0 }));
16973
+ proc.on("exit", (code) => resolve5({ code: code ?? 0 }));
16365
16974
  proc.on("error", reject);
16366
16975
  });
16367
16976
  }
@@ -16379,9 +16988,9 @@ var RailwayProvider = class {
16379
16988
  tarArgs.push(".");
16380
16989
  const tarEnv = { ...process.env, COPYFILE_DISABLE: "1" };
16381
16990
  const remoteCmd = `mkdir -p ${shellQuote4(remoteDir)} && tar -xzf - -C ${shellQuote4(remoteDir)}`;
16382
- await new Promise((resolve4, reject) => {
16383
- const tar = (0, import_child_process15.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
16384
- const sh = (0, import_child_process15.spawn)(
16991
+ await new Promise((resolve5, reject) => {
16992
+ const tar = (0, import_child_process17.spawn)("tar", tarArgs, { stdio: ["ignore", "pipe", "pipe"], env: tarEnv });
16993
+ const sh = (0, import_child_process17.spawn)(
16385
16994
  "railway",
16386
16995
  ["shell", "--project", projectId, "--service", serviceId, "--command", remoteCmd],
16387
16996
  { stdio: [tar.stdout, "pipe", "pipe"] }
@@ -16397,7 +17006,7 @@ var RailwayProvider = class {
16397
17006
  tar.on("error", reject);
16398
17007
  sh.on("error", reject);
16399
17008
  sh.on("exit", (code) => {
16400
- if (code === 0) resolve4();
17009
+ if (code === 0) resolve5();
16401
17010
  else reject(new Error(`Remote tar failed: ${(shErr || tarErr || `exit ${code}`).trim().slice(0, 500)}`));
16402
17011
  });
16403
17012
  });
@@ -16407,14 +17016,14 @@ var RailwayProvider = class {
16407
17016
  if (!projectId || !serviceId) {
16408
17017
  throw new Error("Invalid Railway workspace id (expected projectId/serviceId).");
16409
17018
  }
16410
- const remoteDir = path33.posix.dirname(remotePath);
17019
+ const remoteDir = path36.posix.dirname(remotePath);
16411
17020
  const parts = [`mkdir -p ${shellQuote4(remoteDir)}`, `cat > ${shellQuote4(remotePath)}`];
16412
17021
  if (options.mode != null) {
16413
17022
  parts.push(`chmod ${options.mode.toString(8)} ${shellQuote4(remotePath)}`);
16414
17023
  }
16415
17024
  const cmd = parts.join(" && ");
16416
- await new Promise((resolve4, reject) => {
16417
- const proc = (0, import_child_process15.spawn)(
17025
+ await new Promise((resolve5, reject) => {
17026
+ const proc = (0, import_child_process17.spawn)(
16418
17027
  "railway",
16419
17028
  ["shell", "--project", projectId, "--service", serviceId, "--command", cmd],
16420
17029
  { stdio: ["pipe", "pipe", "pipe"] }
@@ -16425,7 +17034,7 @@ var RailwayProvider = class {
16425
17034
  });
16426
17035
  proc.on("error", reject);
16427
17036
  proc.on("exit", (code) => {
16428
- if (code === 0) resolve4();
17037
+ if (code === 0) resolve5();
16429
17038
  else reject(new Error(`Remote write failed: ${(stderr || `exit ${code}`).trim().slice(0, 500)}`));
16430
17039
  });
16431
17040
  proc.stdin?.write(contents);
@@ -16948,8 +17557,8 @@ async function stopWorkspaceFromLocal(target) {
16948
17557
 
16949
17558
  // src/commands/link.ts
16950
17559
  var import_node_crypto4 = require("crypto");
16951
- var fs26 = __toESM(require("fs"));
16952
- var path34 = __toESM(require("path"));
17560
+ var fs28 = __toESM(require("fs"));
17561
+ var path37 = __toESM(require("path"));
16953
17562
  var import_chokidar = __toESM(require("chokidar"));
16954
17563
  var import_picocolors11 = __toESM(require("picocolors"));
16955
17564
  function buildLinkContext(agentId) {
@@ -16987,7 +17596,7 @@ function parseLinkArgs(args2) {
16987
17596
  if (apiKeyFileArg) {
16988
17597
  const filePath = apiKeyFileArg.slice("--api-key-file=".length);
16989
17598
  try {
16990
- apiKey = fs26.readFileSync(path34.resolve(filePath), "utf8").trim();
17599
+ apiKey = fs28.readFileSync(path37.resolve(filePath), "utf8").trim();
16991
17600
  } catch (err) {
16992
17601
  throw new Error(`Could not read --api-key-file ${filePath}: ${err.message}`);
16993
17602
  }
@@ -17032,7 +17641,7 @@ async function link(args2 = []) {
17032
17641
  waitSpin.start(waitMsg());
17033
17642
  const countdown = setInterval(() => waitSpin.message(waitMsg()), 1e3);
17034
17643
  countdown.unref?.();
17035
- const paired = await new Promise((resolve4, reject) => {
17644
+ const paired = await new Promise((resolve5, reject) => {
17036
17645
  let stopPoll = null;
17037
17646
  const sigint = () => {
17038
17647
  clearInterval(countdown);
@@ -17045,7 +17654,7 @@ async function link(args2 = []) {
17045
17654
  process.removeListener("SIGINT", sigint);
17046
17655
  clearInterval(countdown);
17047
17656
  waitSpin.stop("Paired");
17048
- resolve4(info);
17657
+ resolve5(info);
17049
17658
  },
17050
17659
  () => {
17051
17660
  clearInterval(countdown);
@@ -17081,7 +17690,7 @@ async function link(args2 = []) {
17081
17690
  return;
17082
17691
  }
17083
17692
  if (parsed.tokenFile) {
17084
- const credential = fs26.readFileSync(path34.resolve(parsed.tokenFile), "utf8").trim();
17693
+ const credential = fs28.readFileSync(path37.resolve(parsed.tokenFile), "utf8").trim();
17085
17694
  if (!credential) {
17086
17695
  showError(`--token-file ${parsed.tokenFile} is empty.`);
17087
17696
  process.exit(1);
@@ -17151,14 +17760,14 @@ async function captureFreshCredentials(ctx) {
17151
17760
  }
17152
17761
  };
17153
17762
  try {
17154
- const token = await new Promise((resolve4, reject) => {
17763
+ const token = await new Promise((resolve5, reject) => {
17155
17764
  let settled = false;
17156
17765
  const tryExtract = async () => {
17157
17766
  if (settled) return;
17158
17767
  const t2 = await ctx.locator.extract();
17159
17768
  if (t2 && !settled) {
17160
17769
  settled = true;
17161
- resolve4(t2);
17770
+ resolve5(t2);
17162
17771
  }
17163
17772
  };
17164
17773
  watcher.on("add", () => void tryExtract());
@@ -17270,8 +17879,8 @@ async function linkDryRunPreflight(ctx) {
17270
17879
  var import_node_dns = require("dns");
17271
17880
  var import_node_util4 = require("util");
17272
17881
  var import_node_crypto5 = require("crypto");
17273
- var fs27 = __toESM(require("fs"));
17274
- var path35 = __toESM(require("path"));
17882
+ var fs29 = __toESM(require("fs"));
17883
+ var path38 = __toESM(require("path"));
17275
17884
  var import_picocolors12 = __toESM(require("picocolors"));
17276
17885
  var dnsResolveP = (0, import_node_util4.promisify)(import_node_dns.resolve);
17277
17886
  async function checkDns(apiBase) {
@@ -17327,13 +17936,13 @@ async function checkHealth(apiBase) {
17327
17936
  }
17328
17937
  }
17329
17938
  function checkConfigDir() {
17330
- const dir = path35.join(require("os").homedir(), ".codeam");
17939
+ const dir = path38.join(require("os").homedir(), ".codeam");
17331
17940
  try {
17332
- fs27.mkdirSync(dir, { recursive: true, mode: 448 });
17333
- const probe = path35.join(dir, ".doctor-probe");
17334
- fs27.writeFileSync(probe, "ok", { mode: 384 });
17335
- const read = fs27.readFileSync(probe, "utf8");
17336
- fs27.unlinkSync(probe);
17941
+ fs29.mkdirSync(dir, { recursive: true, mode: 448 });
17942
+ const probe = path38.join(dir, ".doctor-probe");
17943
+ fs29.writeFileSync(probe, "ok", { mode: 384 });
17944
+ const read = fs29.readFileSync(probe, "utf8");
17945
+ fs29.unlinkSync(probe);
17337
17946
  if (read !== "ok") throw new Error("write/read round-trip mismatch");
17338
17947
  return {
17339
17948
  id: "config-dir",
@@ -17397,7 +18006,7 @@ function checkNodePty() {
17397
18006
  detail: "not required on this platform"
17398
18007
  };
17399
18008
  }
17400
- const vendoredPath = path35.join(__dirname, "vendor", "node-pty");
18009
+ const vendoredPath = path38.join(__dirname, "vendor", "node-pty");
17401
18010
  for (const target of [vendoredPath, "node-pty"]) {
17402
18011
  try {
17403
18012
  require(target);
@@ -17439,7 +18048,7 @@ function checkChokidar() {
17439
18048
  }
17440
18049
  async function doctor(args2 = []) {
17441
18050
  const json = args2.includes("--json");
17442
- const cliVersion = true ? "2.20.3" : "0.0.0-dev";
18051
+ const cliVersion = true ? "2.21.1" : "0.0.0-dev";
17443
18052
  const apiBase = resolveApiBaseUrl();
17444
18053
  const diagnosticId = (0, import_node_crypto5.randomUUID)();
17445
18054
  log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
@@ -17638,7 +18247,7 @@ async function completion(args2) {
17638
18247
  // src/commands/version.ts
17639
18248
  var import_picocolors13 = __toESM(require("picocolors"));
17640
18249
  function version2() {
17641
- const v = true ? "2.20.3" : "unknown";
18250
+ const v = true ? "2.21.1" : "unknown";
17642
18251
  console.log(`${import_picocolors13.default.bold("codeam-cli")} ${import_picocolors13.default.cyan(v)}`);
17643
18252
  }
17644
18253
 
@@ -17766,9 +18375,9 @@ function tryShowSubcommandHelp(cmd, args2) {
17766
18375
  var _subcommandHelpKeys = Object.keys(HELPS);
17767
18376
 
17768
18377
  // src/lib/updateNotifier.ts
17769
- var fs28 = __toESM(require("fs"));
18378
+ var fs30 = __toESM(require("fs"));
17770
18379
  var os25 = __toESM(require("os"));
17771
- var path36 = __toESM(require("path"));
18380
+ var path39 = __toESM(require("path"));
17772
18381
  var https7 = __toESM(require("https"));
17773
18382
  var import_picocolors16 = __toESM(require("picocolors"));
17774
18383
  var PKG_NAME = "codeam-cli";
@@ -17776,12 +18385,12 @@ var REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}/latest`;
17776
18385
  var TTL_MS = 24 * 60 * 60 * 1e3;
17777
18386
  var REQUEST_TIMEOUT_MS = 1500;
17778
18387
  function cachePath() {
17779
- const dir = path36.join(os25.homedir(), ".codeam");
17780
- return path36.join(dir, "update-check.json");
18388
+ const dir = path39.join(os25.homedir(), ".codeam");
18389
+ return path39.join(dir, "update-check.json");
17781
18390
  }
17782
18391
  function readCache() {
17783
18392
  try {
17784
- const raw = fs28.readFileSync(cachePath(), "utf8");
18393
+ const raw = fs30.readFileSync(cachePath(), "utf8");
17785
18394
  const parsed = JSON.parse(raw);
17786
18395
  if (typeof parsed.fetchedAt !== "number" || typeof parsed.latest !== "string") return null;
17787
18396
  return parsed;
@@ -17792,10 +18401,10 @@ function readCache() {
17792
18401
  function writeCache(cache) {
17793
18402
  try {
17794
18403
  const file = cachePath();
17795
- fs28.mkdirSync(path36.dirname(file), { recursive: true });
18404
+ fs30.mkdirSync(path39.dirname(file), { recursive: true });
17796
18405
  const tmp = `${file}.${process.pid}.tmp`;
17797
- fs28.writeFileSync(tmp, JSON.stringify(cache));
17798
- fs28.renameSync(tmp, file);
18406
+ fs30.writeFileSync(tmp, JSON.stringify(cache));
18407
+ fs30.renameSync(tmp, file);
17799
18408
  } catch {
17800
18409
  }
17801
18410
  }
@@ -17813,14 +18422,14 @@ function compareSemver(a, b) {
17813
18422
  return 0;
17814
18423
  }
17815
18424
  function fetchLatest() {
17816
- return new Promise((resolve4) => {
18425
+ return new Promise((resolve5) => {
17817
18426
  const req = https7.get(
17818
18427
  REGISTRY_URL,
17819
18428
  { headers: { Accept: "application/json" }, timeout: REQUEST_TIMEOUT_MS },
17820
18429
  (res) => {
17821
18430
  if (res.statusCode !== 200) {
17822
18431
  res.resume();
17823
- resolve4(null);
18432
+ resolve5(null);
17824
18433
  return;
17825
18434
  }
17826
18435
  let buf = "";
@@ -17832,21 +18441,21 @@ function fetchLatest() {
17832
18441
  try {
17833
18442
  const json = JSON.parse(buf);
17834
18443
  if (typeof json.version === "string") {
17835
- resolve4(json.version);
18444
+ resolve5(json.version);
17836
18445
  } else {
17837
- resolve4(null);
18446
+ resolve5(null);
17838
18447
  }
17839
18448
  } catch {
17840
- resolve4(null);
18449
+ resolve5(null);
17841
18450
  }
17842
18451
  });
17843
18452
  }
17844
18453
  );
17845
18454
  req.on("timeout", () => {
17846
18455
  req.destroy();
17847
- resolve4(null);
18456
+ resolve5(null);
17848
18457
  });
17849
- req.on("error", () => resolve4(null));
18458
+ req.on("error", () => resolve5(null));
17850
18459
  });
17851
18460
  }
17852
18461
  function notifyIfStale(currentVersion, latest) {
@@ -17866,7 +18475,7 @@ function checkForUpdates() {
17866
18475
  if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
17867
18476
  if (process.env.CI) return;
17868
18477
  if (!process.stdout.isTTY) return;
17869
- const current = true ? "2.20.3" : null;
18478
+ const current = true ? "2.21.1" : null;
17870
18479
  if (!current) return;
17871
18480
  const cache = readCache();
17872
18481
  const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;