@buildautomaton/cli 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2236,7 +2236,7 @@ var require_websocket = __commonJS({
2236
2236
  "../../node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket.js"(exports, module) {
2237
2237
  "use strict";
2238
2238
  var EventEmitter3 = __require("events");
2239
- var https2 = __require("https");
2239
+ var https3 = __require("https");
2240
2240
  var http = __require("http");
2241
2241
  var net = __require("net");
2242
2242
  var tls = __require("tls");
@@ -2771,7 +2771,7 @@ var require_websocket = __commonJS({
2771
2771
  }
2772
2772
  const defaultPort = isSecure ? 443 : 80;
2773
2773
  const key = randomBytes(16).toString("base64");
2774
- const request = isSecure ? https2.request : http.request;
2774
+ const request = isSecure ? https3.request : http.request;
2775
2775
  const protocolSet = /* @__PURE__ */ new Set();
2776
2776
  let perMessageDeflate;
2777
2777
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -4065,8 +4065,8 @@ var init_parseUtil = __esm({
4065
4065
  init_errors();
4066
4066
  init_en();
4067
4067
  makeIssue = (params) => {
4068
- const { data, path: path23, errorMaps, issueData } = params;
4069
- const fullPath = [...path23, ...issueData.path || []];
4068
+ const { data, path: path24, errorMaps, issueData } = params;
4069
+ const fullPath = [...path24, ...issueData.path || []];
4070
4070
  const fullIssue = {
4071
4071
  ...issueData,
4072
4072
  path: fullPath
@@ -4374,11 +4374,11 @@ var init_types = __esm({
4374
4374
  init_parseUtil();
4375
4375
  init_util();
4376
4376
  ParseInputLazyPath = class {
4377
- constructor(parent, value, path23, key) {
4377
+ constructor(parent, value, path24, key) {
4378
4378
  this._cachedPath = [];
4379
4379
  this.parent = parent;
4380
4380
  this.data = value;
4381
- this._path = path23;
4381
+ this._path = path24;
4382
4382
  this._key = key;
4383
4383
  }
4384
4384
  get path() {
@@ -7993,10 +7993,10 @@ function assignProp(target, prop, value) {
7993
7993
  configurable: true
7994
7994
  });
7995
7995
  }
7996
- function getElementAtPath(obj, path23) {
7997
- if (!path23)
7996
+ function getElementAtPath(obj, path24) {
7997
+ if (!path24)
7998
7998
  return obj;
7999
- return path23.reduce((acc, key) => acc?.[key], obj);
7999
+ return path24.reduce((acc, key) => acc?.[key], obj);
8000
8000
  }
8001
8001
  function promiseAllObject(promisesObj) {
8002
8002
  const keys = Object.keys(promisesObj);
@@ -8245,11 +8245,11 @@ function aborted(x, startIndex = 0) {
8245
8245
  }
8246
8246
  return false;
8247
8247
  }
8248
- function prefixIssues(path23, issues) {
8248
+ function prefixIssues(path24, issues) {
8249
8249
  return issues.map((iss) => {
8250
8250
  var _a2;
8251
8251
  (_a2 = iss).path ?? (_a2.path = []);
8252
- iss.path.unshift(path23);
8252
+ iss.path.unshift(path24);
8253
8253
  return iss;
8254
8254
  });
8255
8255
  }
@@ -8438,7 +8438,7 @@ function treeifyError(error40, _mapper) {
8438
8438
  return issue2.message;
8439
8439
  };
8440
8440
  const result = { errors: [] };
8441
- const processError = (error41, path23 = []) => {
8441
+ const processError = (error41, path24 = []) => {
8442
8442
  var _a2, _b;
8443
8443
  for (const issue2 of error41.issues) {
8444
8444
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -8448,7 +8448,7 @@ function treeifyError(error40, _mapper) {
8448
8448
  } else if (issue2.code === "invalid_element") {
8449
8449
  processError({ issues: issue2.issues }, issue2.path);
8450
8450
  } else {
8451
- const fullpath = [...path23, ...issue2.path];
8451
+ const fullpath = [...path24, ...issue2.path];
8452
8452
  if (fullpath.length === 0) {
8453
8453
  result.errors.push(mapper(issue2));
8454
8454
  continue;
@@ -8478,9 +8478,9 @@ function treeifyError(error40, _mapper) {
8478
8478
  processError(error40);
8479
8479
  return result;
8480
8480
  }
8481
- function toDotPath(path23) {
8481
+ function toDotPath(path24) {
8482
8482
  const segs = [];
8483
- for (const seg of path23) {
8483
+ for (const seg of path24) {
8484
8484
  if (typeof seg === "number")
8485
8485
  segs.push(`[${seg}]`);
8486
8486
  else if (typeof seg === "symbol")
@@ -21963,10 +21963,10 @@ var require_src2 = __commonJS({
21963
21963
  var fs_1 = __require("fs");
21964
21964
  var debug_1 = __importDefault(require_src());
21965
21965
  var log2 = debug_1.default("@kwsites/file-exists");
21966
- function check2(path23, isFile, isDirectory) {
21967
- log2(`checking %s`, path23);
21966
+ function check2(path24, isFile, isDirectory) {
21967
+ log2(`checking %s`, path24);
21968
21968
  try {
21969
- const stat4 = fs_1.statSync(path23);
21969
+ const stat4 = fs_1.statSync(path24);
21970
21970
  if (stat4.isFile() && isFile) {
21971
21971
  log2(`[OK] path represents a file`);
21972
21972
  return true;
@@ -21986,8 +21986,8 @@ var require_src2 = __commonJS({
21986
21986
  throw e;
21987
21987
  }
21988
21988
  }
21989
- function exists2(path23, type = exports.READABLE) {
21990
- return check2(path23, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
21989
+ function exists2(path24, type = exports.READABLE) {
21990
+ return check2(path24, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
21991
21991
  }
21992
21992
  exports.exists = exists2;
21993
21993
  exports.FILE = 1;
@@ -22114,48 +22114,24 @@ function sendWsMessage(ws, payload) {
22114
22114
 
22115
22115
  // src/acp/clients/acp-client.ts
22116
22116
  import { spawn } from "node:child_process";
22117
- import path from "node:path";
22118
22117
  import { Readable, Writable } from "node:stream";
22119
-
22120
- // src/acp/format-acp-rpc-error.ts
22121
- function formatAcpRpcError(err) {
22122
- if (err instanceof Error) return err.message;
22123
- if (err != null && typeof err === "object") {
22124
- const o = err;
22125
- if (typeof o.message === "string") {
22126
- const code = typeof o.code === "number" ? ` (code ${o.code})` : "";
22127
- return `${o.message}${code}`;
22128
- }
22129
- }
22130
- if (typeof err === "string") return err;
22131
- try {
22132
- return JSON.stringify(err);
22133
- } catch {
22134
- return String(err);
22135
- }
22136
- }
22137
- function isAcpAuthenticationRequiredError(err) {
22138
- if (err == null || typeof err !== "object") return false;
22139
- const o = err;
22140
- if (o.code === -32e3 && typeof o.message === "string") {
22141
- const m2 = o.message.toLowerCase();
22142
- if (m2.includes("authentication") || m2.includes("auth")) return true;
22143
- }
22144
- const m = typeof o.message === "string" ? o.message.toLowerCase() : "";
22145
- return m.includes("authentication required") || m.includes("auth_required");
22146
- }
22147
-
22148
- // src/acp/clients/acp-client.ts
22149
22118
  function formatSpawnError(err, command) {
22150
22119
  if (err.code === "ENOENT") {
22151
22120
  return `Command "${command}" not found. Install the agent (e.g. Cursor CLI) or add it to PATH.`;
22152
22121
  }
22153
22122
  return err.message || String(err);
22154
22123
  }
22124
+ function toErrorMessage(err) {
22125
+ if (err instanceof Error) return err.message;
22126
+ if (err != null && typeof err === "object" && "message" in err)
22127
+ return String(err.message);
22128
+ if (typeof err === "string") return err;
22129
+ if (err != null && typeof err === "object") return JSON.stringify(err);
22130
+ return String(err);
22131
+ }
22155
22132
  async function createAcpClient(options) {
22156
- const { ClientSideConnection: ClientSideConnection2, ndJsonStream: ndJsonStream2, PROTOCOL_VERSION: PROTOCOL_VERSION2 } = await Promise.resolve().then(() => (init_acp(), acp_exports));
22133
+ const { ClientSideConnection: ClientSideConnection2, ndJsonStream: ndJsonStream2 } = await Promise.resolve().then(() => (init_acp(), acp_exports));
22157
22134
  const { command, cwd: cwd3 = process.cwd(), onSessionUpdate } = options;
22158
- const sessionCwd = path.resolve(cwd3);
22159
22135
  const isWindows2 = process.platform === "win32";
22160
22136
  const child = spawn(command[0], command.slice(1), {
22161
22137
  cwd: cwd3,
@@ -22186,13 +22162,11 @@ async function createAcpClient(options) {
22186
22162
  child.kill();
22187
22163
  });
22188
22164
  await connection.initialize({
22189
- protocolVersion: PROTOCOL_VERSION2,
22165
+ protocolVersion: "0.1.0",
22166
+ capabilities: {},
22190
22167
  clientInfo: { name: "buildautomaton-cli", version: "0.1.0" }
22191
22168
  });
22192
- const newSessionRes = await connection.newSession({
22193
- cwd: sessionCwd,
22194
- mcpServers: []
22195
- });
22169
+ const newSessionRes = await connection.newSession({ workingDirectory: cwd3 });
22196
22170
  const sessionId = newSessionRes.sessionId;
22197
22171
  resolve16({
22198
22172
  sessionId,
@@ -22200,7 +22174,7 @@ async function createAcpClient(options) {
22200
22174
  try {
22201
22175
  const response = await connection.prompt({
22202
22176
  sessionId,
22203
- prompt: [{ type: "text", text: prompt }]
22177
+ prompt: { type: "text", text: prompt }
22204
22178
  });
22205
22179
  const r = response;
22206
22180
  const cancelled = (r?.stopReason ?? "").toLowerCase() === "cancelled";
@@ -22213,8 +22187,7 @@ async function createAcpClient(options) {
22213
22187
  } catch (err) {
22214
22188
  return {
22215
22189
  success: false,
22216
- error: formatAcpRpcError(err),
22217
- ...isAcpAuthenticationRequiredError(err) ? { authRequired: true } : {}
22190
+ error: err instanceof Error ? err.message : String(err)
22218
22191
  };
22219
22192
  }
22220
22193
  },
@@ -22232,13 +22205,13 @@ async function createAcpClient(options) {
22232
22205
  });
22233
22206
  } catch (err) {
22234
22207
  child.kill();
22235
- reject(new Error(formatAcpRpcError(err)));
22208
+ reject(new Error(toErrorMessage(err)));
22236
22209
  }
22237
22210
  })();
22238
22211
  });
22239
22212
  }
22240
22213
 
22241
- // src/proxy/local-proxy.ts
22214
+ // src/firehose/proxy/local-proxy.ts
22242
22215
  var ALLOWED_HOSTS = ["localhost", "127.0.0.1", "::1"];
22243
22216
  function isAllowedHost(host) {
22244
22217
  const h = host.replace(/^\[|\]$/g, "");
@@ -22366,8 +22339,8 @@ function randomSecret() {
22366
22339
  }
22367
22340
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
22368
22341
  }
22369
- async function requestPreviewApi(port, secret, method, path23, body) {
22370
- const url2 = `http://127.0.0.1:${port}${path23}`;
22342
+ async function requestPreviewApi(port, secret, method, path24, body) {
22343
+ const url2 = `http://127.0.0.1:${port}${path24}`;
22371
22344
  const headers = {
22372
22345
  [PREVIEW_SECRET_HEADER]: secret,
22373
22346
  "Content-Type": "application/json"
@@ -22379,7 +22352,7 @@ async function requestPreviewApi(port, secret, method, path23, body) {
22379
22352
  });
22380
22353
  const data = await res.json().catch(() => ({}));
22381
22354
  if (!res.ok) {
22382
- throw new Error(data?.error ?? `Preview API ${method} ${path23}: ${res.status}`);
22355
+ throw new Error(data?.error ?? `Preview API ${method} ${path24}: ${res.status}`);
22383
22356
  }
22384
22357
  return data;
22385
22358
  }
@@ -22527,14 +22500,19 @@ function log(line) {
22527
22500
  const time3 = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
22528
22501
  console.log(`[${time3}] ${line}`);
22529
22502
  }
22503
+ function logImmediate(line) {
22504
+ const time3 = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
22505
+ process.stdout.write(`[${time3}] ${line}
22506
+ `);
22507
+ }
22530
22508
 
22531
22509
  // src/config.ts
22532
22510
  import fs from "node:fs";
22533
- import path2 from "node:path";
22511
+ import path from "node:path";
22534
22512
  import os from "node:os";
22535
22513
  function getConfigPath() {
22536
- const dir = path2.join(os.homedir(), ".buildautomaton");
22537
- return path2.join(dir, "config.json");
22514
+ const dir = path.join(os.homedir(), ".buildautomaton");
22515
+ return path.join(dir, "config.json");
22538
22516
  }
22539
22517
  function normalizeApiUrl(url2) {
22540
22518
  return url2.replace(/\/$/, "");
@@ -22550,7 +22528,7 @@ function readRawConfig() {
22550
22528
  }
22551
22529
  function writeConfigForApi(apiUrl, auth) {
22552
22530
  const p = getConfigPath();
22553
- const dir = path2.dirname(p);
22531
+ const dir = path.dirname(p);
22554
22532
  const key = normalizeApiUrl(apiUrl);
22555
22533
  const prev = readRawConfig() ?? {};
22556
22534
  const servers = { ...prev.servers ?? {}, [key]: { ...auth } };
@@ -22621,11 +22599,41 @@ function openBrowser(connectionId, initialWorkspaceId, preferredBridgeName, apiU
22621
22599
  }
22622
22600
  }
22623
22601
 
22602
+ // src/bridge/connection/ws-close-diagnostics.ts
22603
+ function describeWebSocketCloseCode(code) {
22604
+ const known = {
22605
+ 1e3: "normal closure",
22606
+ 1001: "going away",
22607
+ 1002: "protocol error",
22608
+ 1003: "unsupported data",
22609
+ 1005: "no status (reserved)",
22610
+ 1006: "abnormal closure \u2014 often network loss, proxy, or idle timeout (no close frame)",
22611
+ 1007: "invalid payload data",
22612
+ 1008: "policy violation",
22613
+ 1009: "message too big",
22614
+ 1010: "mandatory extension",
22615
+ 1011: "internal server error",
22616
+ 1012: "service restart",
22617
+ 1013: "try again later",
22618
+ 1014: "bad gateway",
22619
+ 1015: "TLS handshake failed"
22620
+ };
22621
+ if (known[code]) return known[code];
22622
+ if (code >= 3e3 && code <= 3999) return "registered (framework)";
22623
+ if (code >= 4e3 && code <= 4999) return "application-specific";
22624
+ return "non-standard";
22625
+ }
22626
+ function formatWebSocketClose(label, code, reason, extra) {
22627
+ const r = reason.trim();
22628
+ const reasonPart = r ? ` reason="${r}"` : "";
22629
+ const extraPart = extra ? ` ${extra}` : "";
22630
+ return `${label} closed: code=${code} (${describeWebSocketCloseCode(code)})${reasonPart}${extraPart}`;
22631
+ }
22632
+
22624
22633
  // src/auth/run-pending-auth.ts
22625
22634
  var PENDING_RECONNECT_MS = 2e3;
22626
22635
  var PENDING_KEEPALIVE_MS = 25e3;
22627
22636
  var BROWSER_OPEN_FALLBACK_MS = 4e3;
22628
- var WAITING_REMINDER_MS = 15e3;
22629
22637
  function buildPendingBridgeUrl(apiUrl, connectionId) {
22630
22638
  const base = apiUrl.startsWith("https") ? apiUrl.replace(/^https/, "wss") : apiUrl.replace(/^http/, "ws");
22631
22639
  const params = new URLSearchParams({ connectionId });
@@ -22638,7 +22646,6 @@ function runPendingAuth(options) {
22638
22646
  let reconnectTimeout = null;
22639
22647
  let keepaliveInterval = null;
22640
22648
  let browserFallback = null;
22641
- let waitingReminderInterval = null;
22642
22649
  const connectionId = crypto.randomUUID();
22643
22650
  let hasOpenedBrowser = false;
22644
22651
  let resolved = false;
@@ -22659,10 +22666,6 @@ function runPendingAuth(options) {
22659
22666
  clearInterval(keepaliveInterval);
22660
22667
  keepaliveInterval = null;
22661
22668
  }
22662
- if (waitingReminderInterval) {
22663
- clearInterval(waitingReminderInterval);
22664
- waitingReminderInterval = null;
22665
- }
22666
22669
  if (ws) {
22667
22670
  ws.removeAllListeners();
22668
22671
  ws.close();
@@ -22674,7 +22677,6 @@ function runPendingAuth(options) {
22674
22677
  ws = createWsBridge({
22675
22678
  url: url2,
22676
22679
  onOpen: () => {
22677
- logFn("WebSocket connected (pending auth)");
22678
22680
  sendWsMessage(ws, { type: "identify", role: "cli" });
22679
22681
  keepaliveInterval = setInterval(() => {
22680
22682
  if (resolved || !ws || ws.readyState !== 1) return;
@@ -22694,21 +22696,21 @@ function runPendingAuth(options) {
22694
22696
  clearInterval(keepaliveInterval);
22695
22697
  keepaliveInterval = null;
22696
22698
  }
22697
- logFn(`WebSocket closed: ${code} ${reason}`);
22698
22699
  if (resolved) return;
22699
- logFn("Reconnecting in 2s\u2026");
22700
+ logFn(
22701
+ formatWebSocketClose("[Bridge service]", code, reason, "pending sign-in; will reconnect in 2s")
22702
+ );
22700
22703
  reconnectTimeout = setTimeout(() => {
22701
22704
  reconnectTimeout = null;
22702
22705
  connect();
22703
22706
  }, PENDING_RECONNECT_MS);
22704
22707
  },
22705
- onError: (err) => logFn(`WebSocket error: ${err.message}`),
22708
+ onError: (err) => logFn(`[Bridge service] WebSocket error (pending sign-in): ${err.message}`),
22706
22709
  onMessage: (data) => {
22707
22710
  const msg = data;
22708
22711
  if (msg.type === "auth_token" && typeof msg.token === "string") {
22709
22712
  resolved = true;
22710
22713
  const workspaceId = typeof msg.workspaceId === "string" ? msg.workspaceId : "";
22711
- logFn("Received auth token; reconnecting as bridge for workspace\u2026");
22712
22714
  cleanup();
22713
22715
  const refreshToken = typeof msg.refreshToken === "string" ? msg.refreshToken : void 0;
22714
22716
  const result = { workspaceId, token: msg.token, refreshToken };
@@ -22718,7 +22720,6 @@ function runPendingAuth(options) {
22718
22720
  }
22719
22721
  });
22720
22722
  }
22721
- logFn("Connecting to backend (pending auth)\u2026");
22722
22723
  browserFallback = setTimeout(() => {
22723
22724
  browserFallback = null;
22724
22725
  if (!hasOpenedBrowser) {
@@ -22726,10 +22727,6 @@ function runPendingAuth(options) {
22726
22727
  openBrowser(connectionId, initialWorkspaceId, preferredBridgeName, apiUrl, logFn);
22727
22728
  }
22728
22729
  }, BROWSER_OPEN_FALLBACK_MS);
22729
- waitingReminderInterval = setInterval(() => {
22730
- if (resolved) return;
22731
- logFn("Waiting for you to link the CLI in the browser\u2026");
22732
- }, WAITING_REMINDER_MS);
22733
22730
  connect();
22734
22731
  return {
22735
22732
  close: () => {
@@ -22740,69 +22737,6 @@ function runPendingAuth(options) {
22740
22737
  };
22741
22738
  }
22742
22739
 
22743
- // src/skills/discover-local-agent-skills.ts
22744
- import fs2 from "node:fs";
22745
- import path3 from "node:path";
22746
- var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
22747
- function discoverLocalSkills(cwd3) {
22748
- const out = [];
22749
- const seenKeys = /* @__PURE__ */ new Set();
22750
- for (const rel of SKILL_DISCOVERY_ROOTS) {
22751
- const base = path3.join(cwd3, rel);
22752
- if (!fs2.existsSync(base) || !fs2.statSync(base).isDirectory()) continue;
22753
- let entries = [];
22754
- try {
22755
- entries = fs2.readdirSync(base);
22756
- } catch {
22757
- continue;
22758
- }
22759
- for (const name of entries) {
22760
- const dir = path3.join(base, name);
22761
- try {
22762
- if (!fs2.statSync(dir).isDirectory()) continue;
22763
- } catch {
22764
- continue;
22765
- }
22766
- const skillMd = path3.join(dir, "SKILL.md");
22767
- if (!fs2.existsSync(skillMd)) continue;
22768
- const key = `${rel}/${name}`;
22769
- if (seenKeys.has(key)) continue;
22770
- seenKeys.add(key);
22771
- out.push({ skillKey: name, path: `${rel}/${name}`.replace(/\\/g, "/") });
22772
- }
22773
- }
22774
- return out;
22775
- }
22776
- function discoverSkillLayoutRoots(cwd3) {
22777
- const roots = [];
22778
- for (const rel of SKILL_DISCOVERY_ROOTS) {
22779
- const base = path3.join(cwd3, rel);
22780
- if (!fs2.existsSync(base) || !fs2.statSync(base).isDirectory()) continue;
22781
- let entries = [];
22782
- try {
22783
- entries = fs2.readdirSync(base);
22784
- } catch {
22785
- continue;
22786
- }
22787
- const skills2 = [];
22788
- for (const name of entries) {
22789
- const dir = path3.join(base, name);
22790
- try {
22791
- if (!fs2.statSync(dir).isDirectory()) continue;
22792
- } catch {
22793
- continue;
22794
- }
22795
- if (!fs2.existsSync(path3.join(dir, "SKILL.md"))) continue;
22796
- const relPath = `${rel}/${name}`.replace(/\\/g, "/");
22797
- skills2.push({ name, relPath });
22798
- }
22799
- if (skills2.length > 0) {
22800
- roots.push({ path: rel.replace(/\\/g, "/"), skills: skills2 });
22801
- }
22802
- }
22803
- return roots;
22804
- }
22805
-
22806
22740
  // src/bridge/connection/build-bridge-url.ts
22807
22741
  function buildBridgeUrl(apiUrl, workspaceId, authToken) {
22808
22742
  const base = apiUrl.startsWith("https") ? apiUrl.replace(/^https/, "wss") : apiUrl.replace(/^http/, "ws");
@@ -22811,8 +22745,8 @@ function buildBridgeUrl(apiUrl, workspaceId, authToken) {
22811
22745
  }
22812
22746
 
22813
22747
  // src/git/discover-repos.ts
22814
- import * as fs3 from "node:fs";
22815
- import * as path4 from "node:path";
22748
+ import * as fs2 from "node:fs";
22749
+ import * as path2 from "node:path";
22816
22750
 
22817
22751
  // ../../node_modules/.pnpm/simple-git@3.32.3/node_modules/simple-git/dist/esm/index.js
22818
22752
  var import_file_exists = __toESM(require_dist(), 1);
@@ -22851,8 +22785,8 @@ function pathspec(...paths) {
22851
22785
  cache.set(key, paths);
22852
22786
  return key;
22853
22787
  }
22854
- function isPathSpec(path23) {
22855
- return path23 instanceof String && cache.has(path23);
22788
+ function isPathSpec(path24) {
22789
+ return path24 instanceof String && cache.has(path24);
22856
22790
  }
22857
22791
  function toPaths(pathSpec) {
22858
22792
  return cache.get(pathSpec) || [];
@@ -22941,8 +22875,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
22941
22875
  function forEachLineWithContent(input, callback) {
22942
22876
  return toLinesWithContent(input, true).map((line) => callback(line));
22943
22877
  }
22944
- function folderExists(path23) {
22945
- return (0, import_file_exists.exists)(path23, import_file_exists.FOLDER);
22878
+ function folderExists(path24) {
22879
+ return (0, import_file_exists.exists)(path24, import_file_exists.FOLDER);
22946
22880
  }
22947
22881
  function append(target, item) {
22948
22882
  if (Array.isArray(target)) {
@@ -23346,8 +23280,8 @@ function checkIsRepoRootTask() {
23346
23280
  commands,
23347
23281
  format: "utf-8",
23348
23282
  onError,
23349
- parser(path23) {
23350
- return /^\.(git)?$/.test(path23.trim());
23283
+ parser(path24) {
23284
+ return /^\.(git)?$/.test(path24.trim());
23351
23285
  }
23352
23286
  };
23353
23287
  }
@@ -23781,11 +23715,11 @@ function parseGrep(grep) {
23781
23715
  const paths = /* @__PURE__ */ new Set();
23782
23716
  const results = {};
23783
23717
  forEachLineWithContent(grep, (input) => {
23784
- const [path23, line, preview] = input.split(NULL);
23785
- paths.add(path23);
23786
- (results[path23] = results[path23] || []).push({
23718
+ const [path24, line, preview] = input.split(NULL);
23719
+ paths.add(path24);
23720
+ (results[path24] = results[path24] || []).push({
23787
23721
  line: asNumber(line),
23788
- path: path23,
23722
+ path: path24,
23789
23723
  preview
23790
23724
  });
23791
23725
  });
@@ -24550,14 +24484,14 @@ var init_hash_object = __esm2({
24550
24484
  init_task();
24551
24485
  }
24552
24486
  });
24553
- function parseInit(bare, path23, text) {
24487
+ function parseInit(bare, path24, text) {
24554
24488
  const response = String(text).trim();
24555
24489
  let result;
24556
24490
  if (result = initResponseRegex.exec(response)) {
24557
- return new InitSummary(bare, path23, false, result[1]);
24491
+ return new InitSummary(bare, path24, false, result[1]);
24558
24492
  }
24559
24493
  if (result = reInitResponseRegex.exec(response)) {
24560
- return new InitSummary(bare, path23, true, result[1]);
24494
+ return new InitSummary(bare, path24, true, result[1]);
24561
24495
  }
24562
24496
  let gitDir = "";
24563
24497
  const tokens = response.split(" ");
@@ -24568,7 +24502,7 @@ function parseInit(bare, path23, text) {
24568
24502
  break;
24569
24503
  }
24570
24504
  }
24571
- return new InitSummary(bare, path23, /^re/i.test(response), gitDir);
24505
+ return new InitSummary(bare, path24, /^re/i.test(response), gitDir);
24572
24506
  }
24573
24507
  var InitSummary;
24574
24508
  var initResponseRegex;
@@ -24577,9 +24511,9 @@ var init_InitSummary = __esm2({
24577
24511
  "src/lib/responses/InitSummary.ts"() {
24578
24512
  "use strict";
24579
24513
  InitSummary = class {
24580
- constructor(bare, path23, existing, gitDir) {
24514
+ constructor(bare, path24, existing, gitDir) {
24581
24515
  this.bare = bare;
24582
- this.path = path23;
24516
+ this.path = path24;
24583
24517
  this.existing = existing;
24584
24518
  this.gitDir = gitDir;
24585
24519
  }
@@ -24591,7 +24525,7 @@ var init_InitSummary = __esm2({
24591
24525
  function hasBareCommand(command) {
24592
24526
  return command.includes(bareCommand);
24593
24527
  }
24594
- function initTask(bare = false, path23, customArgs) {
24528
+ function initTask(bare = false, path24, customArgs) {
24595
24529
  const commands = ["init", ...customArgs];
24596
24530
  if (bare && !hasBareCommand(commands)) {
24597
24531
  commands.splice(1, 0, bareCommand);
@@ -24600,7 +24534,7 @@ function initTask(bare = false, path23, customArgs) {
24600
24534
  commands,
24601
24535
  format: "utf-8",
24602
24536
  parser(text) {
24603
- return parseInit(commands.includes("--bare"), path23, text);
24537
+ return parseInit(commands.includes("--bare"), path24, text);
24604
24538
  }
24605
24539
  };
24606
24540
  }
@@ -25416,12 +25350,12 @@ var init_FileStatusSummary = __esm2({
25416
25350
  "use strict";
25417
25351
  fromPathRegex = /^(.+)\0(.+)$/;
25418
25352
  FileStatusSummary = class {
25419
- constructor(path23, index, working_dir) {
25420
- this.path = path23;
25353
+ constructor(path24, index, working_dir) {
25354
+ this.path = path24;
25421
25355
  this.index = index;
25422
25356
  this.working_dir = working_dir;
25423
25357
  if (index === "R" || working_dir === "R") {
25424
- const detail = fromPathRegex.exec(path23) || [null, path23, path23];
25358
+ const detail = fromPathRegex.exec(path24) || [null, path24, path24];
25425
25359
  this.from = detail[2] || "";
25426
25360
  this.path = detail[1] || "";
25427
25361
  }
@@ -25452,14 +25386,14 @@ function splitLine(result, lineStr) {
25452
25386
  default:
25453
25387
  return;
25454
25388
  }
25455
- function data(index, workingDir, path23) {
25389
+ function data(index, workingDir, path24) {
25456
25390
  const raw = `${index}${workingDir}`;
25457
25391
  const handler = parsers6.get(raw);
25458
25392
  if (handler) {
25459
- handler(result, path23);
25393
+ handler(result, path24);
25460
25394
  }
25461
25395
  if (raw !== "##" && raw !== "!!") {
25462
- result.files.push(new FileStatusSummary(path23, index, workingDir));
25396
+ result.files.push(new FileStatusSummary(path24, index, workingDir));
25463
25397
  }
25464
25398
  }
25465
25399
  }
@@ -25768,9 +25702,9 @@ var init_simple_git_api = __esm2({
25768
25702
  next
25769
25703
  );
25770
25704
  }
25771
- hashObject(path23, write) {
25705
+ hashObject(path24, write) {
25772
25706
  return this._runTask(
25773
- hashObjectTask(path23, write === true),
25707
+ hashObjectTask(path24, write === true),
25774
25708
  trailingFunctionArgument(arguments)
25775
25709
  );
25776
25710
  }
@@ -26123,8 +26057,8 @@ var init_branch = __esm2({
26123
26057
  }
26124
26058
  });
26125
26059
  function toPath(input) {
26126
- const path23 = input.trim().replace(/^["']|["']$/g, "");
26127
- return path23 && normalize(path23);
26060
+ const path24 = input.trim().replace(/^["']|["']$/g, "");
26061
+ return path24 && normalize(path24);
26128
26062
  }
26129
26063
  var parseCheckIgnore;
26130
26064
  var init_CheckIgnore = __esm2({
@@ -26438,8 +26372,8 @@ __export2(sub_module_exports, {
26438
26372
  subModuleTask: () => subModuleTask,
26439
26373
  updateSubModuleTask: () => updateSubModuleTask
26440
26374
  });
26441
- function addSubModuleTask(repo, path23) {
26442
- return subModuleTask(["add", repo, path23]);
26375
+ function addSubModuleTask(repo, path24) {
26376
+ return subModuleTask(["add", repo, path24]);
26443
26377
  }
26444
26378
  function initSubModuleTask(customArgs) {
26445
26379
  return subModuleTask(["init", ...customArgs]);
@@ -26772,8 +26706,8 @@ var require_git = __commonJS2({
26772
26706
  }
26773
26707
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
26774
26708
  };
26775
- Git2.prototype.submoduleAdd = function(repo, path23, then) {
26776
- return this._runTask(addSubModuleTask2(repo, path23), trailingFunctionArgument2(arguments));
26709
+ Git2.prototype.submoduleAdd = function(repo, path24, then) {
26710
+ return this._runTask(addSubModuleTask2(repo, path24), trailingFunctionArgument2(arguments));
26777
26711
  };
26778
26712
  Git2.prototype.submoduleUpdate = function(args, then) {
26779
26713
  return this._runTask(
@@ -27399,20 +27333,20 @@ async function isGitRepoDirectory(dirPath) {
27399
27333
  // src/git/discover-repos.ts
27400
27334
  async function discoverGitRepos(cwd3 = process.cwd()) {
27401
27335
  const result = [];
27402
- const cwdResolved = path4.resolve(cwd3);
27336
+ const cwdResolved = path2.resolve(cwd3);
27403
27337
  if (await isGitRepoDirectory(cwdResolved)) {
27404
27338
  const remoteUrl = await getRemoteOriginUrl(cwdResolved);
27405
27339
  result.push({ absolutePath: cwdResolved, remoteUrl });
27406
27340
  }
27407
27341
  let entries;
27408
27342
  try {
27409
- entries = fs3.readdirSync(cwdResolved, { withFileTypes: true });
27343
+ entries = fs2.readdirSync(cwdResolved, { withFileTypes: true });
27410
27344
  } catch {
27411
27345
  return result;
27412
27346
  }
27413
27347
  for (const ent of entries) {
27414
27348
  if (!ent.isDirectory()) continue;
27415
- const childPath = path4.join(cwdResolved, ent.name);
27349
+ const childPath = path2.join(cwdResolved, ent.name);
27416
27350
  if (await isGitRepoDirectory(childPath)) {
27417
27351
  const remoteUrl = await getRemoteOriginUrl(childPath);
27418
27352
  result.push({ absolutePath: childPath, remoteUrl });
@@ -27421,22 +27355,22 @@ async function discoverGitRepos(cwd3 = process.cwd()) {
27421
27355
  return result;
27422
27356
  }
27423
27357
  async function discoverGitReposUnderRoot(rootAbs) {
27424
- const root = path4.resolve(rootAbs);
27358
+ const root = path2.resolve(rootAbs);
27425
27359
  const roots = [];
27426
27360
  async function walk(dir) {
27427
27361
  if (await isGitRepoDirectory(dir)) {
27428
- roots.push(path4.resolve(dir));
27362
+ roots.push(path2.resolve(dir));
27429
27363
  return;
27430
27364
  }
27431
27365
  let entries;
27432
27366
  try {
27433
- entries = fs3.readdirSync(dir, { withFileTypes: true });
27367
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
27434
27368
  } catch {
27435
27369
  return;
27436
27370
  }
27437
27371
  for (const ent of entries) {
27438
27372
  if (!ent.isDirectory() || ent.name === ".git") continue;
27439
- await walk(path4.join(dir, ent.name));
27373
+ await walk(path2.join(dir, ent.name));
27440
27374
  }
27441
27375
  }
27442
27376
  await walk(root);
@@ -27460,11 +27394,10 @@ function reportGitRepos(getWs, log2) {
27460
27394
  type: "git_repos",
27461
27395
  repos: repos.map((r) => ({ absolutePath: r.absolutePath, remoteUrl: r.remoteUrl }))
27462
27396
  });
27463
- log2(`Reported ${repos.length} git repo(s) to bridge`);
27464
27397
  }
27465
27398
  }
27466
27399
  }).catch((err) => {
27467
- log2(`Git repo discovery failed: ${err instanceof Error ? err.message : String(err)}`);
27400
+ log2(`[Bridge service] git repo discovery failed: ${err instanceof Error ? err.message : String(err)}`);
27468
27401
  });
27469
27402
  });
27470
27403
  }
@@ -27479,7 +27412,7 @@ function scheduleReconnect(state, connect, log2) {
27479
27412
  RECONNECT_MAX_MS
27480
27413
  );
27481
27414
  state.reconnectAttempt += 1;
27482
- log2(`Reconnecting in ${delay2 / 1e3}s\u2026`);
27415
+ log2(`[Bridge service] reconnect attempt ${state.reconnectAttempt} in ${delay2 / 1e3}s\u2026`);
27483
27416
  state.reconnectTimeout = setTimeout(() => {
27484
27417
  state.reconnectTimeout = null;
27485
27418
  connect();
@@ -27487,18 +27420,33 @@ function scheduleReconnect(state, connect, log2) {
27487
27420
  }
27488
27421
 
27489
27422
  // src/bridge/connection/close-bridge-connection.ts
27490
- function closeBridgeConnection(state, acpManager) {
27423
+ async function closeBridgeConnection(state, acpManager, devServerManager, log2) {
27424
+ log2?.("Shutting down\u2026");
27425
+ await new Promise((resolve16) => setImmediate(resolve16));
27426
+ if (devServerManager) {
27427
+ log2?.("Requesting dev server processes to stop\u2026");
27428
+ await devServerManager.shutdownAllGraceful();
27429
+ }
27491
27430
  state.closedByUser = true;
27492
27431
  if (state.reconnectTimeout != null) {
27432
+ log2?.("Cancelling reconnect timer\u2026");
27493
27433
  clearTimeout(state.reconnectTimeout);
27494
27434
  state.reconnectTimeout = null;
27495
27435
  }
27496
- if (state.proxyHandle) {
27497
- state.proxyHandle.close();
27498
- state.proxyHandle = null;
27436
+ if (state.firehoseReconnectTimeout != null) {
27437
+ log2?.("[Proxy and log service] cancelling reconnect timer\u2026");
27438
+ clearTimeout(state.firehoseReconnectTimeout);
27439
+ state.firehoseReconnectTimeout = null;
27499
27440
  }
27441
+ if (state.firehoseHandle) {
27442
+ log2?.("[Proxy and log service] closing connection (CLI shutdown)\u2026");
27443
+ state.firehoseHandle.close();
27444
+ state.firehoseHandle = null;
27445
+ }
27446
+ log2?.("Disconnecting local agents (ACP)\u2026");
27500
27447
  acpManager.disconnect();
27501
27448
  if (state.currentWs) {
27449
+ log2?.("[Bridge service] closing connection (CLI shutdown)\u2026");
27502
27450
  state.currentWs.removeAllListeners();
27503
27451
  const wsState = state.currentWs.readyState;
27504
27452
  if (wsState === 1 || wsState === 2) {
@@ -27513,119 +27461,10 @@ function closeBridgeConnection(state, acpManager) {
27513
27461
  }
27514
27462
  }
27515
27463
 
27516
- // src/proxy/connect-proxy.ts
27517
- var PROXY_ID_BYTES = 36;
27518
- function buildProxyWsUrl(proxyServerUrl) {
27519
- const base = proxyServerUrl.startsWith("https") ? proxyServerUrl.replace(/^https/, "wss") : proxyServerUrl.replace(/^http/, "ws");
27520
- return `${base.replace(/\/$/, "")}/ws`;
27521
- }
27522
- function connectProxy(options) {
27523
- const { proxyServerUrl, workspaceId, bridgeName, proxyPorts, log: log2, onClose } = options;
27524
- const proxyUrl = buildProxyWsUrl(proxyServerUrl);
27525
- log2(`Proxy: connecting to ${proxyUrl} (workspace: ${workspaceId}, bridge: ${bridgeName}, ports: [${proxyPorts.join(", ")}])`);
27526
- const ws = new wrapper_default(proxyUrl);
27527
- const pendingProxyBody = /* @__PURE__ */ new Map();
27528
- function startStreamingProxy(pr) {
27529
- proxyToLocalStreaming(pr, {
27530
- onStart: (statusCode, headers) => {
27531
- const forwardedHeaders = { ...headers };
27532
- const ce = "content-encoding";
27533
- const cl = "content-length";
27534
- const keys = Object.keys(forwardedHeaders).filter(
27535
- (k) => k.toLowerCase() !== ce && k.toLowerCase() !== cl
27536
- );
27537
- const filtered = {};
27538
- for (const k of keys) if (forwardedHeaders[k] != null) filtered[k] = forwardedHeaders[k];
27539
- sendWsMessage(ws, { type: "proxy_result_start", id: pr.id, statusCode, headers: filtered });
27540
- },
27541
- onChunk: (chunk) => {
27542
- const idBuf = Buffer.from(pr.id, "utf8");
27543
- ws.send(Buffer.concat([idBuf, Buffer.from(chunk)]), { binary: true });
27544
- },
27545
- onEnd: () => sendWsMessage(ws, { type: "proxy_result_end", id: pr.id }),
27546
- onError: (error40) => {
27547
- log2(`[proxy] ${pr.id.slice(0, 8)}\u2026 (stream) -> error: ${error40}`);
27548
- sendWsMessage(ws, { type: "proxy_result_error", id: pr.id, error: error40 });
27549
- }
27550
- });
27551
- }
27552
- ws.on("open", () => {
27553
- sendWsMessage(ws, { type: "identify", workspaceId, bridgeName, proxyPorts });
27554
- });
27555
- ws.on("message", (raw) => {
27556
- if (Buffer.isBuffer(raw) && raw.length >= PROXY_ID_BYTES) {
27557
- const id = raw.slice(0, PROXY_ID_BYTES).toString("utf8");
27558
- const pending = pendingProxyBody.get(id);
27559
- if (pending) {
27560
- pendingProxyBody.delete(id);
27561
- const body = raw.slice(PROXY_ID_BYTES);
27562
- startStreamingProxy({
27563
- ...pending.pr,
27564
- body: body.length > 0 ? new Uint8Array(body) : void 0
27565
- });
27566
- return;
27567
- }
27568
- }
27569
- try {
27570
- const data = JSON.parse(Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw));
27571
- if (data.type === "proxy" && data.proxy && typeof data.proxy === "object") {
27572
- const pr = data.proxy;
27573
- if (pr.streamResponse) {
27574
- const bodyLength = pr.bodyLength ?? 0;
27575
- if (bodyLength > 0) {
27576
- pendingProxyBody.set(pr.id, {
27577
- pr: {
27578
- id: pr.id,
27579
- method: pr.method,
27580
- url: pr.url,
27581
- headers: pr.headers,
27582
- streamResponse: true
27583
- }
27584
- });
27585
- } else {
27586
- startStreamingProxy({
27587
- id: pr.id,
27588
- method: pr.method,
27589
- url: pr.url,
27590
- headers: pr.headers,
27591
- streamResponse: true
27592
- });
27593
- }
27594
- } else {
27595
- proxyToLocal(pr).then((res) => {
27596
- if (res.error) log2(`[proxy] ${pr.id.slice(0, 8)}\u2026 -> error: ${res.error}`);
27597
- sendWsMessage(ws, { type: "proxy_result", ...res, id: pr.id });
27598
- }).catch((err) => {
27599
- log2(`[proxy] ${pr.id.slice(0, 8)}\u2026 -> error: ${err instanceof Error ? err.message : String(err)}`);
27600
- sendWsMessage(ws, { type: "proxy_result", id: pr.id, error: String(err) });
27601
- });
27602
- }
27603
- }
27604
- } catch {
27605
- }
27606
- });
27607
- ws.on("close", (_code, reason) => {
27608
- if (reason?.length) log2(`Proxy: WebSocket closed (reason: ${reason.toString()})`);
27609
- onClose?.();
27610
- });
27611
- ws.on("error", (err) => {
27612
- log2(`Proxy: WebSocket error connecting to ${proxyUrl}: ${err.message}`);
27613
- });
27614
- return {
27615
- close() {
27616
- try {
27617
- ws.removeAllListeners();
27618
- ws.close();
27619
- } catch {
27620
- }
27621
- }
27622
- };
27623
- }
27624
-
27625
27464
  // src/git/session-git-queue.ts
27626
27465
  import { execFile } from "node:child_process";
27627
27466
  import { promisify } from "node:util";
27628
- import * as path5 from "node:path";
27467
+ import * as path3 from "node:path";
27629
27468
  var execFileAsync = promisify(execFile);
27630
27469
  var sessionBoundaryBySessionId = /* @__PURE__ */ new Map();
27631
27470
  async function gitStashCreate(repoRoot, log2) {
@@ -27676,7 +27515,7 @@ async function collectSessionDiffAndNotify(options) {
27676
27515
  continue;
27677
27516
  }
27678
27517
  const lines = namesRaw.split("\n").map((l) => l.trim()).filter(Boolean);
27679
- const slug = path5.basename(repo.path).replace(/[^\w.-]+/g, "_") || "repo";
27518
+ const slug = path3.basename(repo.path).replace(/[^\w.-]+/g, "_") || "repo";
27680
27519
  for (const rel of lines) {
27681
27520
  if (rel.includes("..")) continue;
27682
27521
  try {
@@ -27715,10 +27554,8 @@ async function sendPromptToAgent(options) {
27715
27554
  sendResult,
27716
27555
  sendSessionUpdate,
27717
27556
  collectSessionDiffAfterTurn,
27718
- agentType,
27719
27557
  log: log2
27720
27558
  } = options;
27721
- log2("[prompt] Sending to agent\u2026");
27722
27559
  try {
27723
27560
  const result = await handle.sendPrompt(promptText, {});
27724
27561
  if (collectSessionDiffAfterTurn && sessionId && runId && sendSessionUpdate && result.success) {
@@ -27729,41 +27566,29 @@ async function sendPromptToAgent(options) {
27729
27566
  id: promptId,
27730
27567
  ...sessionId ? { sessionId } : {},
27731
27568
  ...runId ? { runId } : {},
27732
- success: result.success,
27733
- ...typeof result.stopReason === "string" ? { stopReason: result.stopReason } : {},
27734
- ...typeof result.output === "string" ? { output: result.output } : {},
27735
- ...typeof result.error === "string" ? { error: result.error } : {},
27736
- ...result.authRequired ? { authRequired: true } : {},
27737
- ...agentType ? { agentType } : {}
27738
- });
27739
- if (result.success) {
27740
- const out = result.output ?? "";
27741
- const preview = out.length > 400 ? out.slice(0, 400) + "\u2026" : out;
27742
- log2(`[prompt] Done; sent response (${out.length} chars)`);
27743
- if (preview) log2(`[prompt] Response: ${preview}`);
27744
- } else {
27745
- log2(`[prompt] Agent error: ${result.error ?? "(unknown)"}`);
27569
+ ...result
27570
+ });
27571
+ if (!result.success) {
27572
+ log2(`[agent] ${result.error ?? "error"}`);
27746
27573
  }
27747
27574
  } catch (err) {
27748
- const errMsg = formatAcpRpcError(err);
27749
- const authRequired = isAcpAuthenticationRequiredError(err);
27750
- log2(`[prompt] Send failed: ${errMsg}`);
27751
- if (err instanceof Error && err.stack) log2(`[prompt] ${err.stack}`);
27575
+ const errMsg = err instanceof Error ? err.message : String(err);
27576
+ log2(`[agent] send failed: ${errMsg}`);
27577
+ if (err instanceof Error && err.stack) log2(`[agent] ${err.stack}`);
27752
27578
  sendResult({
27753
27579
  type: "prompt_result",
27754
27580
  id: promptId,
27755
27581
  ...sessionId ? { sessionId } : {},
27756
27582
  ...runId ? { runId } : {},
27757
27583
  success: false,
27758
- error: errMsg,
27759
- ...authRequired ? { authRequired: true } : {},
27760
- ...agentType ? { agentType } : {}
27584
+ error: errMsg
27761
27585
  });
27762
27586
  }
27763
27587
  }
27764
27588
 
27765
27589
  // src/acp/ensure-acp-client.ts
27766
- import * as path9 from "node:path";
27590
+ import * as fs3 from "node:fs";
27591
+ import * as path7 from "node:path";
27767
27592
 
27768
27593
  // src/error-message.ts
27769
27594
  function errorMessage(err) {
@@ -27775,17 +27600,6 @@ function errorMessage(err) {
27775
27600
  return String(err);
27776
27601
  }
27777
27602
 
27778
- // src/acp/clients/claude-agent-acp-client.ts
27779
- var DEFAULT_CLAUDE_AGENT_ACP_COMMAND = ["npx", "--yes", "@agentclientprotocol/claude-agent-acp"];
27780
- function isClaudeAgentAcpCommand(command) {
27781
- const i = command.indexOf("@agentclientprotocol/claude-agent-acp");
27782
- return i >= 0 && (i === 0 || command[i - 1] === "npx" || command[i - 1] === "bunx");
27783
- }
27784
- async function createClaudeAgentAcpClient(options) {
27785
- const command = options.command?.length && options.command.some((a) => a.includes("claude-agent-acp")) ? options.command : [...DEFAULT_CLAUDE_AGENT_ACP_COMMAND];
27786
- return createAcpClient({ ...options, command });
27787
- }
27788
-
27789
27603
  // src/acp/clients/codex-acp-client.ts
27790
27604
  var DEFAULT_CODEX_ACP_COMMAND = ["npx", "--yes", "@zed-industries/codex-acp"];
27791
27605
  function isCodexAcpCommand(command) {
@@ -27804,21 +27618,21 @@ import { spawn as spawn4 } from "node:child_process";
27804
27618
  import * as readline from "node:readline";
27805
27619
 
27806
27620
  // src/acp/safe-fs-path.ts
27807
- import * as path6 from "node:path";
27621
+ import * as path4 from "node:path";
27808
27622
  function resolveSafePathUnderCwd(cwd3, filePath) {
27809
27623
  const trimmed2 = filePath.trim();
27810
27624
  if (!trimmed2) return null;
27811
- const normalizedCwd = path6.resolve(cwd3);
27812
- const resolved = path6.isAbsolute(trimmed2) ? path6.normalize(trimmed2) : path6.resolve(normalizedCwd, trimmed2);
27813
- const rel = path6.relative(normalizedCwd, resolved);
27814
- if (rel.startsWith("..") || path6.isAbsolute(rel)) return null;
27625
+ const normalizedCwd = path4.resolve(cwd3);
27626
+ const resolved = path4.isAbsolute(trimmed2) ? path4.normalize(trimmed2) : path4.resolve(normalizedCwd, trimmed2);
27627
+ const rel = path4.relative(normalizedCwd, resolved);
27628
+ if (rel.startsWith("..") || path4.isAbsolute(rel)) return null;
27815
27629
  return resolved;
27816
27630
  }
27817
27631
  function toDisplayPathRelativeToCwd(cwd3, absolutePath) {
27818
- const normalizedCwd = path6.resolve(cwd3);
27819
- const rel = path6.relative(normalizedCwd, path6.resolve(absolutePath));
27820
- if (!rel || rel === "") return path6.basename(absolutePath);
27821
- return rel.split(path6.sep).join("/");
27632
+ const normalizedCwd = path4.resolve(cwd3);
27633
+ const rel = path4.relative(normalizedCwd, path4.resolve(absolutePath));
27634
+ if (!rel || rel === "") return path4.basename(absolutePath);
27635
+ return rel.split(path4.sep).join("/");
27822
27636
  }
27823
27637
 
27824
27638
  // src/files/diff/unified-diff.ts
@@ -28120,7 +27934,7 @@ async function createCursorAcpClient(options) {
28120
27934
  var AGENT_TYPE_DEFAULT_COMMANDS = {
28121
27935
  "cursor-cli": ["agent", "acp"],
28122
27936
  "codex-acp": [...DEFAULT_CODEX_ACP_COMMAND],
28123
- "claude-code": [...DEFAULT_CLAUDE_AGENT_ACP_COMMAND]
27937
+ "claude-code": ["npx", "--yes", "@anthropic-ai/claude-code"]
28124
27938
  };
28125
27939
  function useCursorAcp(agentType, command) {
28126
27940
  if (agentType === "cursor-cli") return true;
@@ -28130,17 +27944,12 @@ function useCodexAcp(agentType, command) {
28130
27944
  if (agentType === "codex-acp") return true;
28131
27945
  return isCodexAcpCommand(command);
28132
27946
  }
28133
- function useClaudeAgentAcp(agentType, command) {
28134
- if (agentType === "claude-code") return true;
28135
- return isClaudeAgentAcpCommand(command);
28136
- }
28137
- function resolveAgentCommand(agentCommand, preferredAgentType) {
28138
- const explicitCmd = agentCommand?.length ? agentCommand : void 0;
28139
- const typeCmd = preferredAgentType ? AGENT_TYPE_DEFAULT_COMMANDS[preferredAgentType] : void 0;
28140
- const command = explicitCmd ?? typeCmd;
27947
+ function resolveAgentCommand(preferredAgentType) {
27948
+ if (!preferredAgentType) return null;
27949
+ const command = AGENT_TYPE_DEFAULT_COMMANDS[preferredAgentType];
28141
27950
  if (!command?.length) return null;
28142
- const createClient = useCursorAcp(preferredAgentType, command) ? createCursorAcpClient : useCodexAcp(preferredAgentType, command) ? createCodexAcpClient : useClaudeAgentAcp(preferredAgentType, command) ? createClaudeAgentAcpClient : createAcpClient;
28143
- const label = preferredAgentType ?? "--agent";
27951
+ const createClient = useCursorAcp(preferredAgentType, command) ? createCursorAcpClient : useCodexAcp(preferredAgentType, command) ? createCodexAcpClient : createAcpClient;
27952
+ const label = preferredAgentType;
28144
27953
  return { command, label, createClient };
28145
27954
  }
28146
27955
 
@@ -28150,16 +27959,16 @@ import { existsSync, statSync } from "node:fs";
28150
27959
 
28151
27960
  // src/git/get-git-repo-root-sync.ts
28152
27961
  import { execFileSync } from "node:child_process";
28153
- import * as path7 from "node:path";
27962
+ import * as path5 from "node:path";
28154
27963
  function getGitRepoRootSync(startDir) {
28155
27964
  try {
28156
27965
  const out = execFileSync("git", ["rev-parse", "--show-toplevel"], {
28157
- cwd: path7.resolve(startDir),
27966
+ cwd: path5.resolve(startDir),
28158
27967
  encoding: "utf8",
28159
27968
  stdio: ["ignore", "pipe", "ignore"],
28160
27969
  maxBuffer: 1024 * 1024
28161
27970
  }).trim();
28162
- return out ? path7.resolve(out) : null;
27971
+ return out ? path5.resolve(out) : null;
28163
27972
  } catch {
28164
27973
  return null;
28165
27974
  }
@@ -28168,25 +27977,25 @@ function getGitRepoRootSync(startDir) {
28168
27977
  // src/acp/workspace-files.ts
28169
27978
  import { execFileSync as execFileSync2 } from "node:child_process";
28170
27979
  import { readFileSync as readFileSync2 } from "node:fs";
28171
- import * as path8 from "node:path";
27980
+ import * as path6 from "node:path";
28172
27981
  function resolveWorkspaceFilePath(cwd3, rawPath) {
28173
27982
  const trimmed2 = rawPath.trim();
28174
27983
  if (!trimmed2) return null;
28175
- const normalizedCwd = path8.resolve(cwd3);
27984
+ const normalizedCwd = path6.resolve(cwd3);
28176
27985
  let abs = resolveSafePathUnderCwd(cwd3, trimmed2);
28177
27986
  if (!abs) {
28178
- const candidate = path8.isAbsolute(trimmed2) ? path8.normalize(trimmed2) : path8.normalize(path8.resolve(normalizedCwd, trimmed2));
27987
+ const candidate = path6.isAbsolute(trimmed2) ? path6.normalize(trimmed2) : path6.normalize(path6.resolve(normalizedCwd, trimmed2));
28179
27988
  const gitRoot2 = getGitRepoRootSync(cwd3);
28180
27989
  if (!gitRoot2) return null;
28181
- const rel = path8.relative(gitRoot2, candidate);
28182
- if (rel.startsWith("..") || path8.isAbsolute(rel)) return null;
27990
+ const rel = path6.relative(gitRoot2, candidate);
27991
+ if (rel.startsWith("..") || path6.isAbsolute(rel)) return null;
28183
27992
  abs = candidate;
28184
27993
  }
28185
27994
  const gitRoot = getGitRepoRootSync(cwd3);
28186
27995
  if (gitRoot) {
28187
- const relFromRoot = path8.relative(gitRoot, abs);
28188
- if (!relFromRoot.startsWith("..") && !path8.isAbsolute(relFromRoot)) {
28189
- return { abs, display: relFromRoot.split(path8.sep).join("/") };
27996
+ const relFromRoot = path6.relative(gitRoot, abs);
27997
+ if (!relFromRoot.startsWith("..") && !path6.isAbsolute(relFromRoot)) {
27998
+ return { abs, display: relFromRoot.split(path6.sep).join("/") };
28190
27999
  }
28191
28000
  }
28192
28001
  return { abs, display: toDisplayPathRelativeToCwd(cwd3, abs) };
@@ -28195,9 +28004,9 @@ function readUtf8WorkspaceFile(cwd3, displayPath) {
28195
28004
  if (!displayPath || displayPath.includes("..")) return "";
28196
28005
  const gitRoot = getGitRepoRootSync(cwd3);
28197
28006
  if (gitRoot) {
28198
- const abs2 = path8.resolve(gitRoot, displayPath);
28199
- const rel = path8.relative(gitRoot, abs2);
28200
- if (!rel.startsWith("..") && !path8.isAbsolute(rel)) {
28007
+ const abs2 = path6.resolve(gitRoot, displayPath);
28008
+ const rel = path6.relative(gitRoot, abs2);
28009
+ if (!rel.startsWith("..") && !path6.isAbsolute(rel)) {
28201
28010
  try {
28202
28011
  return readFileSync2(abs2, "utf8");
28203
28012
  } catch {
@@ -28216,9 +28025,9 @@ function tryWorkspaceDisplayToAbs(cwd3, displayPath) {
28216
28025
  if (!displayPath || displayPath.includes("..")) return null;
28217
28026
  const gitRoot = getGitRepoRootSync(cwd3);
28218
28027
  if (gitRoot) {
28219
- const abs = path8.resolve(gitRoot, displayPath);
28220
- const rel = path8.relative(gitRoot, abs);
28221
- if (!rel.startsWith("..") && !path8.isAbsolute(rel)) return abs;
28028
+ const abs = path6.resolve(gitRoot, displayPath);
28029
+ const rel = path6.relative(gitRoot, abs);
28030
+ if (!rel.startsWith("..") && !path6.isAbsolute(rel)) return abs;
28222
28031
  }
28223
28032
  return resolveSafePathUnderCwd(cwd3, displayPath);
28224
28033
  }
@@ -28277,7 +28086,7 @@ function createBridgeOnFileChange(opts) {
28277
28086
  const sessionId = routing.sessionId;
28278
28087
  const send = getSendSessionUpdate();
28279
28088
  if (!send || !runId || !sessionId) {
28280
- log2(`[file-change] ACP fs/write dropped path=${evt.path} (bridge or session/run not wired)`);
28089
+ log2(`[Bridge service] file change not forwarded path=${evt.path} (session/run not wired)`);
28281
28090
  return;
28282
28091
  }
28283
28092
  const cwd3 = process.cwd();
@@ -28295,7 +28104,7 @@ function createBridgeOnFileChange(opts) {
28295
28104
  directoryRemoved: dirFlags.directoryRemoved
28296
28105
  });
28297
28106
  } catch (err) {
28298
- log2(`[file-change] session_file_change (fs/write) failed ${evt.path}: ${errorMessage(err)}`);
28107
+ log2(`[Bridge service] session_file_change failed ${evt.path}: ${errorMessage(err)}`);
28299
28108
  }
28300
28109
  };
28301
28110
  }
@@ -28322,7 +28131,7 @@ function createBridgeOnRequest(opts) {
28322
28131
  }
28323
28132
  });
28324
28133
  } catch (err) {
28325
- log2(`[bridge] ACP request forward failed ${request.method}: ${errorMessage(err)}`);
28134
+ log2(`[Bridge service] ACP request forward failed (${request.method}): ${errorMessage(err)}`);
28326
28135
  }
28327
28136
  };
28328
28137
  }
@@ -28512,11 +28321,6 @@ function isCompletedToolStatus(status) {
28512
28321
  const s = status.toLowerCase();
28513
28322
  return s === "completed" || s === "complete" || s === "succeeded" || s === "success";
28514
28323
  }
28515
- function isTerminalToolStatus(status) {
28516
- if (typeof status !== "string") return false;
28517
- const s = status.toLowerCase();
28518
- return isCompletedToolStatus(status) || s === "failed" || s === "cancelled" || s === "canceled";
28519
- }
28520
28324
  function accumulateToolPaths(toolKey, paths, acc) {
28521
28325
  if (!toolKey || paths.length === 0) return;
28522
28326
  let s = acc.get(toolKey);
@@ -28614,7 +28418,7 @@ var PathSnapshotTracker = class {
28614
28418
  });
28615
28419
  sentPaths.add(displayPath);
28616
28420
  } catch (err) {
28617
- log2(`[file-change] session_file_change (path snapshot) failed ${displayPath}: ${errorMessage(err)}`);
28421
+ log2(`[Bridge service] session_file_change failed ${displayPath}: ${errorMessage(err)}`);
28618
28422
  }
28619
28423
  }
28620
28424
  }
@@ -28668,7 +28472,7 @@ function sendExtractedDiffsAsSessionFileChanges(diffs, send, cwd3, sessionId, ru
28668
28472
  });
28669
28473
  sentPaths.add(d.path);
28670
28474
  } catch (err) {
28671
- log2(`[file-change] session_file_change (ACP diff) failed ${d.path}: ${errorMessage(err)}`);
28475
+ log2(`[Bridge service] session_file_change failed ${d.path}: ${errorMessage(err)}`);
28672
28476
  }
28673
28477
  }
28674
28478
  }
@@ -28694,7 +28498,7 @@ function sendGitHeadVsWorkspaceForToolPaths(mergedPaths, sentPaths, send, cwd3,
28694
28498
  });
28695
28499
  sentPaths.add(displayPath);
28696
28500
  } catch (err) {
28697
- log2(`[file-change] session_file_change (git fallback) failed ${displayPath}: ${errorMessage(err)}`);
28501
+ log2(`[Bridge service] session_file_change failed ${displayPath}: ${errorMessage(err)}`);
28698
28502
  }
28699
28503
  }
28700
28504
  }
@@ -28742,7 +28546,9 @@ function createBridgeOnSessionUpdate(opts) {
28742
28546
  if (diffs.length > 0 && send && runId && sessionId) {
28743
28547
  sendExtractedDiffsAsSessionFileChanges(diffs, send, cwd3, sessionId, runId, sentFileChangePaths, log2);
28744
28548
  } else if (diffs.length > 0) {
28745
- log2(`[file-change] ACP structured diff(s) dropped (${diffs.length}): bridge or session/run not wired`);
28549
+ log2(
28550
+ `[Bridge service] ACP file diff(s) not forwarded (${diffs.length}): session/run not wired to bridge`
28551
+ );
28746
28552
  }
28747
28553
  if (isCompletedToolCallUpdate && send && runId && sessionId) {
28748
28554
  const acc = pathTracker.accumulatedPathsByToolKey.get(toolKey);
@@ -28760,7 +28566,7 @@ function createBridgeOnSessionUpdate(opts) {
28760
28566
  payload: params
28761
28567
  });
28762
28568
  } catch (err) {
28763
- log2(`[bridge] session_update send failed kind=${updateKind}: ${errorMessage(err)}`);
28569
+ log2(`[Bridge service] session_update send failed (${updateKind}): ${errorMessage(err)}`);
28764
28570
  }
28765
28571
  }
28766
28572
  };
@@ -28777,9 +28583,11 @@ function buildAcpSessionBridgeHooks(opts) {
28777
28583
 
28778
28584
  // src/acp/ensure-acp-client.ts
28779
28585
  async function ensureAcpClient(options) {
28780
- const { state, agentCommand, preferredAgentType, mode, cwd: cwd3, routing, sendSessionUpdate, sendRequest, log: log2 } = options;
28781
- const targetCwd = cwd3 ?? process.cwd();
28782
- if (state.acpHandle && state.lastAcpCwd != null && path9.resolve(state.lastAcpCwd) !== path9.resolve(targetCwd)) {
28586
+ const { state, preferredAgentType, mode, cwd: cwd3, routing, sendSessionUpdate, sendRequest, log: log2 } = options;
28587
+ const targetCwd = path7.resolve(
28588
+ cwd3 != null && String(cwd3).trim() !== "" ? String(cwd3).trim() : process.cwd()
28589
+ );
28590
+ if (state.acpHandle && state.lastAcpCwd != null && path7.resolve(state.lastAcpCwd) !== path7.resolve(targetCwd)) {
28783
28591
  try {
28784
28592
  state.acpHandle.disconnect();
28785
28593
  } catch {
@@ -28788,9 +28596,12 @@ async function ensureAcpClient(options) {
28788
28596
  state.acpStartPromise = null;
28789
28597
  state.acpAgentKey = null;
28790
28598
  }
28791
- const resolved = resolveAgentCommand(agentCommand, preferredAgentType);
28599
+ const resolved = resolveAgentCommand(preferredAgentType);
28792
28600
  if (!resolved) {
28793
- log2("[prompt] No agent type from backend; use --agent or add local agents on this bridge in the app");
28601
+ log2(
28602
+ `[agent] No local agent type (${preferredAgentType === null ? "none" : `"${preferredAgentType}"`}). Send agentType on prompts or agent_config after identify.`
28603
+ );
28604
+ state.lastAcpStartError = "No agent type: ensure the app sends agentType on prompts or agent_config for this bridge.";
28794
28605
  return null;
28795
28606
  }
28796
28607
  const agentKey = `${resolved.label}::${resolved.command.join("\0")}`;
@@ -28805,9 +28616,23 @@ async function ensureAcpClient(options) {
28805
28616
  }
28806
28617
  if (state.acpHandle) return state.acpHandle;
28807
28618
  if (!state.acpStartPromise) {
28619
+ let statOk = false;
28620
+ try {
28621
+ const st = fs3.statSync(targetCwd);
28622
+ statOk = st.isDirectory();
28623
+ if (!statOk) {
28624
+ state.lastAcpStartError = `Agent cwd is not a directory: ${targetCwd}`;
28625
+ log2(`[agent] ${state.lastAcpStartError}`);
28626
+ }
28627
+ } catch {
28628
+ state.lastAcpStartError = `Agent cwd missing or inaccessible: ${targetCwd}`;
28629
+ log2(`[agent] ${state.lastAcpStartError}`);
28630
+ }
28631
+ if (!statOk) {
28632
+ return null;
28633
+ }
28808
28634
  const modeFlag = mode && ["ask", "plan"].includes(mode) ? ["--mode", mode] : [];
28809
28635
  const fullCmd = [...resolved.command, ...modeFlag];
28810
- log2(`[prompt] Starting agent (${resolved.label}) on first request\u2026`);
28811
28636
  const hooks = buildAcpSessionBridgeHooks({
28812
28637
  routing,
28813
28638
  getSendSessionUpdate: () => sendSessionUpdate,
@@ -28823,11 +28648,10 @@ async function ensureAcpClient(options) {
28823
28648
  state.acpHandle = h;
28824
28649
  state.lastAcpCwd = targetCwd;
28825
28650
  state.acpAgentKey = agentKey;
28826
- log2(`ACP client connected (${resolved.label})`);
28827
28651
  return h;
28828
28652
  }).catch((err) => {
28829
28653
  state.lastAcpStartError = errorMessage(err);
28830
- log2(`ACP client failed: ${state.lastAcpStartError}`);
28654
+ log2(`[agent] failed to start: ${state.lastAcpStartError}`);
28831
28655
  state.acpStartPromise = null;
28832
28656
  state.acpAgentKey = null;
28833
28657
  return null;
@@ -28838,7 +28662,7 @@ async function ensureAcpClient(options) {
28838
28662
 
28839
28663
  // src/acp/create-acp-manager.ts
28840
28664
  async function createAcpManager(options) {
28841
- const { agentCommand, log: log2 } = options;
28665
+ const { log: log2 } = options;
28842
28666
  const state = {
28843
28667
  acpHandle: null,
28844
28668
  acpStartPromise: null,
@@ -28853,7 +28677,6 @@ async function createAcpManager(options) {
28853
28677
  function setPreferredAgentType(agentType) {
28854
28678
  if (!backendFallbackAgentType) {
28855
28679
  backendFallbackAgentType = agentType;
28856
- log2(`Agent type from backend (fallback): ${backendFallbackAgentType}`);
28857
28680
  }
28858
28681
  }
28859
28682
  function handlePrompt(opts) {
@@ -28869,7 +28692,7 @@ async function createAcpManager(options) {
28869
28692
  sendSessionUpdate,
28870
28693
  collectSessionDiffAfterTurn
28871
28694
  } = opts;
28872
- const preferredForPrompt = agentType ?? backendFallbackAgentType;
28695
+ const preferredForPrompt = agentType ?? backendFallbackAgentType ?? null;
28873
28696
  pendingCancelRunId = void 0;
28874
28697
  promptRouting.sessionId = sessionId;
28875
28698
  promptRouting.runId = runId;
@@ -28877,7 +28700,6 @@ async function createAcpManager(options) {
28877
28700
  async function run() {
28878
28701
  const handle = await ensureAcpClient({
28879
28702
  state,
28880
- agentCommand,
28881
28703
  preferredAgentType: preferredForPrompt,
28882
28704
  mode,
28883
28705
  cwd: cwd3,
@@ -28893,7 +28715,7 @@ async function createAcpManager(options) {
28893
28715
  ...sessionId ? { sessionId } : {},
28894
28716
  ...runId ? { runId } : {},
28895
28717
  success: false,
28896
- error: state.lastAcpStartError || "No agent configured. Register local agents on this bridge in the app, or use --agent for a fixed CLI command."
28718
+ error: state.lastAcpStartError || "No agent configured. Register local agents on this bridge in the app."
28897
28719
  });
28898
28720
  return;
28899
28721
  }
@@ -28921,7 +28743,6 @@ async function createAcpManager(options) {
28921
28743
  promptId,
28922
28744
  sessionId,
28923
28745
  runId,
28924
- agentType: preferredForPrompt,
28925
28746
  sendResult,
28926
28747
  sendSessionUpdate,
28927
28748
  collectSessionDiffAfterTurn,
@@ -28941,15 +28762,13 @@ async function createAcpManager(options) {
28941
28762
  if (handle?.cancel) {
28942
28763
  try {
28943
28764
  await handle.cancel();
28944
- log2(`[prompt] ACP session/cancel sent for run ${runId.slice(0, 8)}\u2026`);
28945
28765
  return true;
28946
28766
  } catch (err) {
28947
- log2(`[prompt] Cancel failed: ${err instanceof Error ? err.message : String(err)}`);
28767
+ log2(`[agent] cancel failed: ${err instanceof Error ? err.message : String(err)}`);
28948
28768
  return false;
28949
28769
  }
28950
28770
  }
28951
28771
  pendingCancelRunId = runId;
28952
- log2(`[prompt] Cancel queued for run ${runId.slice(0, 8)}\u2026 (agent still starting)`);
28953
28772
  return true;
28954
28773
  }
28955
28774
  function resolveRequest(requestId, result) {
@@ -28964,57 +28783,55 @@ async function createAcpManager(options) {
28964
28783
  return { setPreferredAgentType, handlePrompt, cancelRun, resolveRequest, disconnect };
28965
28784
  }
28966
28785
 
28967
- // src/bridge/routing/message-router.ts
28968
- function createMessageRouter() {
28969
- const handlers = /* @__PURE__ */ new Map();
28970
- return {
28971
- register(type, handler) {
28972
- handlers.set(type, handler);
28973
- },
28974
- dispatch(msg, deps) {
28975
- const type = msg.type;
28976
- if (!type) return;
28977
- const handler = handlers.get(type);
28978
- if (handler) handler(msg, deps);
28979
- }
28980
- };
28981
- }
28982
-
28983
28786
  // src/bridge/routing/handlers/auth-token.ts
28984
28787
  var handleAuthToken = (msg, { log: log2 }) => {
28985
- if (typeof msg.token !== "string") return false;
28788
+ if (typeof msg.token !== "string") return;
28986
28789
  log2("Received auth token. Save it for future runs:");
28987
28790
  log2(` export BUILDAMATON_AUTH_TOKEN="${msg.token}"`);
28988
- return true;
28989
28791
  };
28990
28792
 
28991
28793
  // src/bridge/routing/handlers/bridge-identified.ts
28992
- var handleBridgeIdentified = (msg, { onBridgeIdentified, sendLocalSkillsReport }) => {
28993
- if (typeof msg.bridgeName !== "string") return false;
28994
- onBridgeIdentified(
28794
+ var handleBridgeIdentified = (msg, deps) => {
28795
+ if (typeof msg.bridgeName !== "string") return;
28796
+ deps.onBridgeIdentified(
28995
28797
  msg
28996
28798
  );
28997
- sendLocalSkillsReport?.();
28998
- return true;
28799
+ setImmediate(() => {
28800
+ void (async () => {
28801
+ try {
28802
+ await deps.reportAutoDetectedAgents?.();
28803
+ } catch (e) {
28804
+ deps.log(`[Bridge service] auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`);
28805
+ }
28806
+ })();
28807
+ });
28808
+ setImmediate(() => {
28809
+ try {
28810
+ deps.sendLocalSkillsReport?.();
28811
+ } catch (e) {
28812
+ deps.log(`[Bridge service] local skills report failed: ${e instanceof Error ? e.message : String(e)}`);
28813
+ }
28814
+ });
28999
28815
  };
29000
28816
 
29001
28817
  // src/acp/from-bridge/handle-bridge-agent-config.ts
29002
28818
  function handleBridgeAgentConfig(msg, { acpManager }) {
29003
- if (!Array.isArray(msg.agents) || msg.agents.length === 0) return false;
28819
+ if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
29004
28820
  acpManager.setPreferredAgentType(msg.agents[0].type);
29005
- return true;
29006
28821
  }
29007
28822
 
29008
28823
  // src/bridge/routing/handlers/agent-config.ts
29009
- var handleAgentConfigMessage = (msg, deps) => handleBridgeAgentConfig(msg, deps);
28824
+ var handleAgentConfigMessage = (msg, deps) => {
28825
+ handleBridgeAgentConfig(msg, deps);
28826
+ };
29010
28827
 
29011
28828
  // src/acp/from-bridge/handle-bridge-prompt.ts
29012
- import * as path12 from "node:path";
28829
+ import * as path10 from "node:path";
29013
28830
  import { execFile as execFile3 } from "node:child_process";
29014
28831
  import { promisify as promisify3 } from "node:util";
29015
28832
 
29016
28833
  // src/git/bridge-queue-key.ts
29017
- import * as path10 from "node:path";
28834
+ import * as path8 from "node:path";
29018
28835
  import { createHash } from "node:crypto";
29019
28836
  function normalizeCanonicalGitUrl(url2) {
29020
28837
  let s = url2.trim();
@@ -29042,13 +28859,13 @@ function canonicalUrlToRepoIdSync(url2) {
29042
28859
  return createHash("sha256").update(normalized).digest("hex").slice(0, 32);
29043
28860
  }
29044
28861
  function fallbackRepoIdFromPath(absPath) {
29045
- return createHash("sha256").update(path10.resolve(absPath)).digest("hex").slice(0, 32);
28862
+ return createHash("sha256").update(path8.resolve(absPath)).digest("hex").slice(0, 32);
29046
28863
  }
29047
28864
  async function resolveBridgeQueueBindFields(options) {
29048
28865
  const { effectiveCwd, worktreePaths, primaryRepoRoots, log: log2 } = options;
29049
- const cwdAbs = worktreePaths.length > 0 ? path10.resolve(worktreePaths[0]) : path10.resolve(effectiveCwd);
28866
+ const cwdAbs = worktreePaths.length > 0 ? path8.resolve(worktreePaths[0]) : path8.resolve(effectiveCwd);
29050
28867
  if (!primaryRepoRoots.length) {
29051
- log2("[bridge-queue] bind skipped: no git repo roots");
28868
+ log2("[Bridge service] prompt queue bind skipped: no git repo roots under cwd");
29052
28869
  return null;
29053
28870
  }
29054
28871
  let primaryRoot = primaryRepoRoots[0];
@@ -29070,12 +28887,12 @@ async function resolveBridgeQueueBindFields(options) {
29070
28887
 
29071
28888
  // src/git/pre-turn-snapshot.ts
29072
28889
  import * as fs4 from "node:fs";
29073
- import * as path11 from "node:path";
28890
+ import * as path9 from "node:path";
29074
28891
  import { execFile as execFile2 } from "node:child_process";
29075
28892
  import { promisify as promisify2 } from "node:util";
29076
28893
  var execFileAsync2 = promisify2(execFile2);
29077
28894
  function snapshotsDirForCwd(agentCwd) {
29078
- return path11.join(agentCwd, ".buildautomaton", "snapshots");
28895
+ return path9.join(agentCwd, ".buildautomaton", "snapshots");
29079
28896
  }
29080
28897
  async function gitStashCreate2(repoRoot, log2) {
29081
28898
  try {
@@ -29102,7 +28919,7 @@ async function gitRun(repoRoot, args, log2, label) {
29102
28919
  async function resolveSnapshotRepoRoots(options) {
29103
28920
  const { worktreePaths, fallbackCwd, log: log2 } = options;
29104
28921
  if (worktreePaths?.length) {
29105
- const uniq = [...new Set(worktreePaths.map((p) => path11.resolve(p)))];
28922
+ const uniq = [...new Set(worktreePaths.map((p) => path9.resolve(p)))];
29106
28923
  return uniq;
29107
28924
  }
29108
28925
  try {
@@ -29134,7 +28951,7 @@ async function capturePreTurnSnapshot(options) {
29134
28951
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
29135
28952
  repos
29136
28953
  };
29137
- const filePath = path11.join(dir, `${runId}.json`);
28954
+ const filePath = path9.join(dir, `${runId}.json`);
29138
28955
  try {
29139
28956
  fs4.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
29140
28957
  } catch (e) {
@@ -29169,7 +28986,7 @@ async function applyPreTurnSnapshot(filePath, log2) {
29169
28986
  return { ok: true };
29170
28987
  }
29171
28988
  function snapshotFilePath(agentCwd, runId) {
29172
- return path11.join(snapshotsDirForCwd(agentCwd), `${runId}.json`);
28989
+ return path9.join(snapshotsDirForCwd(agentCwd), `${runId}.json`);
29173
28990
  }
29174
28991
 
29175
28992
  // src/acp/from-bridge/handle-bridge-prompt.ts
@@ -29184,13 +29001,15 @@ async function readGitBranch(cwd3) {
29184
29001
  }
29185
29002
  }
29186
29003
  function handleBridgePrompt(msg, deps) {
29187
- if (!msg.prompt) return false;
29188
29004
  const { getWs, log: log2, acpManager, sessionWorktreeManager } = deps;
29189
- const promptText = msg.prompt;
29190
- const mode = msg.mode || "agent";
29191
- log2(
29192
- `[prompt] Received (mode=${mode}): ${String(promptText).slice(0, 80)}${String(promptText).length > 80 ? "\u2026" : ""}${msg.runId ? ` (runId: ${msg.runId.slice(0, 8)}\u2026)` : ""}`
29193
- );
29005
+ const rawPrompt = msg.prompt;
29006
+ const promptText = typeof rawPrompt === "string" ? rawPrompt : rawPrompt != null ? String(rawPrompt) : "";
29007
+ if (!promptText.trim()) {
29008
+ log2(
29009
+ `[Bridge service] prompt ignored: empty or missing prompt field (sessionId=${typeof msg.sessionId === "string" ? msg.sessionId.slice(0, 8) : "\u2014"}\u2026 runId=${typeof msg.runId === "string" ? msg.runId.slice(0, 8) : "\u2014"}\u2026)`
29010
+ );
29011
+ return;
29012
+ }
29194
29013
  const sessionId = msg.sessionId;
29195
29014
  const isNewSession = msg.isNewSession === true;
29196
29015
  const sessionWorktreesEnabled = msg.sessionWorktreesEnabled === true;
@@ -29205,31 +29024,15 @@ function handleBridgePrompt(msg, deps) {
29205
29024
  const sendSessionUpdate = (payload) => {
29206
29025
  const s = getWs();
29207
29026
  if (!s) {
29208
- log2("[bridge] session_update skipped: WebSocket to API not connected");
29027
+ log2("[Bridge service] session_update not sent: not connected");
29209
29028
  return;
29210
29029
  }
29211
29030
  const p = payload;
29212
- if (p.type === "session_file_change" && typeof p.path === "string") {
29213
- log2(
29214
- `[bridge\u2192ws] session_file_change runId=${p.runId?.slice(0, 8) ?? "?"}\u2026 path=${p.path}`
29215
- );
29216
- } else if (p.type === "session_update" && (p.kind === "tool_call_update" || p.kind === "tool_call")) {
29217
- const inner = p.payload && typeof p.payload === "object" ? p.payload : null;
29218
- const status = inner?.status;
29219
- if (isTerminalToolStatus(status)) {
29220
- const tc = inner?.toolCall ?? inner?.tool_call;
29221
- const toolName = typeof tc?.name === "string" ? tc.name : "";
29222
- const st = typeof status === "string" ? status : String(status);
29223
- log2(
29224
- `[bridge\u2192ws] tool finished runId=${p.runId?.slice(0, 8) ?? "?"}\u2026 tool=${toolName || "?"} status=${st}`
29225
- );
29226
- }
29227
- }
29228
29031
  sendWsMessage(s, payload);
29229
29032
  };
29230
29033
  async function preambleAndPrompt(resolvedCwd) {
29231
29034
  const s = getWs();
29232
- const effectiveCwd = path12.resolve(resolvedCwd ?? process.cwd());
29035
+ const effectiveCwd = path10.resolve(resolvedCwd ?? process.cwd());
29233
29036
  const worktreePaths = sessionWorktreeManager.getWorktreePathsForSession(sessionId) ?? [];
29234
29037
  const repoRoots = await resolveSnapshotRepoRoots({
29235
29038
  worktreePaths,
@@ -29281,84 +29084,84 @@ function handleBridgePrompt(msg, deps) {
29281
29084
  runId,
29282
29085
  mode: msg.mode,
29283
29086
  agentType,
29284
- cwd: resolvedCwd,
29087
+ cwd: effectiveCwd,
29285
29088
  sendResult,
29286
29089
  sendSessionUpdate,
29287
29090
  collectSessionDiffAfterTurn
29288
29091
  });
29289
29092
  }
29290
29093
  void sessionWorktreeManager.resolveCwdForPrompt(sessionId, { isNewSession, sessionWorktreesEnabled }).then((cwd3) => preambleAndPrompt(cwd3)).catch((err) => {
29291
- log2(`[prompt] worktree resolve failed: ${err instanceof Error ? err.message : String(err)}`);
29094
+ log2(`[agent] worktree resolve failed: ${err instanceof Error ? err.message : String(err)}`);
29292
29095
  void preambleAndPrompt(void 0);
29293
29096
  });
29294
- return true;
29295
29097
  }
29296
29098
 
29297
29099
  // src/bridge/routing/handlers/prompt.ts
29298
- var handlePromptMessage = (msg, deps) => handleBridgePrompt(msg, deps);
29100
+ var handlePromptMessage = (msg, deps) => {
29101
+ handleBridgePrompt(msg, deps);
29102
+ };
29299
29103
 
29300
29104
  // src/acp/from-bridge/handle-bridge-cancel-run.ts
29301
29105
  function handleBridgeCancelRun(msg, { log: log2, acpManager }) {
29302
29106
  const runId = msg.runId;
29303
- if (!runId) return false;
29107
+ if (!runId) return;
29304
29108
  void acpManager.cancelRun(runId).then((sent) => {
29305
29109
  if (!sent) {
29306
29110
  log2(
29307
- `[prompt] Cancel requested for run ${runId.slice(0, 8)}\u2026 but no matching active run or ACP cancel not available`
29111
+ `[agent] cancel ignored for run ${runId.slice(0, 8)}\u2026 (no active run or cancel not available)`
29308
29112
  );
29309
29113
  }
29310
29114
  });
29311
- return true;
29312
29115
  }
29313
29116
 
29314
29117
  // src/bridge/routing/handlers/cancel-run.ts
29315
- var handleCancelRunMessage = (msg, deps) => handleBridgeCancelRun(msg, deps);
29118
+ var handleCancelRunMessage = (msg, deps) => {
29119
+ handleBridgeCancelRun(msg, deps);
29120
+ };
29316
29121
 
29317
29122
  // src/acp/from-bridge/handle-bridge-cursor-request-response.ts
29318
- function handleBridgeCursorRequestResponse(msg, { acpManager, log: log2 }) {
29319
- if (typeof msg.requestId !== "string") return false;
29123
+ function handleBridgeCursorRequestResponse(msg, { acpManager }) {
29124
+ if (typeof msg.requestId !== "string") return;
29320
29125
  acpManager.resolveRequest(msg.requestId, msg.result ?? {});
29321
- log2(`[prompt] Resolved Cursor request ${msg.requestId}`);
29322
- return true;
29323
29126
  }
29324
29127
 
29325
29128
  // src/bridge/routing/handlers/cursor-request-response.ts
29326
- var handleCursorRequestResponseMessage = (msg, deps) => handleBridgeCursorRequestResponse(msg, deps);
29129
+ var handleCursorRequestResponseMessage = (msg, deps) => {
29130
+ handleBridgeCursorRequestResponse(msg, deps);
29131
+ };
29327
29132
 
29328
29133
  // src/skills/handle-skill-call.ts
29329
29134
  function handleSkillCall(msg, socket, log2) {
29330
29135
  callSkill(msg.skillId, msg.operationId, msg.params ?? {}).then((result) => {
29331
29136
  sendWsMessage(socket, { type: "skill_result", id: msg.id, result });
29332
- log2(`Skill ${msg.skillId}/${msg.operationId} ok`);
29333
29137
  }).catch((err) => {
29334
29138
  sendWsMessage(socket, { type: "skill_result", id: msg.id, error: String(err) });
29335
- log2(`Skill error: ${err}`);
29139
+ log2(`[Bridge service] skill_call failed (${msg.skillId}/${msg.operationId}): ${err}`);
29336
29140
  });
29337
29141
  }
29338
29142
 
29339
29143
  // src/bridge/routing/handlers/skill-call.ts
29340
29144
  var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
29341
- if (!msg.skillId || msg.operationId === void 0) return false;
29145
+ if (!msg.skillId || msg.operationId === void 0) return;
29342
29146
  const socket = getWs();
29343
- if (!socket) return false;
29147
+ if (!socket) return;
29344
29148
  handleSkillCall(
29345
29149
  msg,
29346
29150
  socket,
29347
29151
  log2
29348
29152
  );
29349
- return true;
29350
29153
  };
29351
29154
 
29352
29155
  // src/files/list-dir.ts
29353
29156
  import fs5 from "node:fs";
29354
- import path14 from "node:path";
29157
+ import path12 from "node:path";
29355
29158
 
29356
29159
  // src/files/ensure-under-cwd.ts
29357
- import path13 from "node:path";
29160
+ import path11 from "node:path";
29358
29161
  function ensureUnderCwd(relativePath, cwd3 = process.cwd()) {
29359
- const normalized = path13.normalize(relativePath).replace(/^(\.\/)+/, "");
29360
- const resolved = path13.resolve(cwd3, normalized);
29361
- if (!resolved.startsWith(cwd3 + path13.sep) && resolved !== cwd3) {
29162
+ const normalized = path11.normalize(relativePath).replace(/^(\.\/)+/, "");
29163
+ const resolved = path11.resolve(cwd3, normalized);
29164
+ if (!resolved.startsWith(cwd3 + path11.sep) && resolved !== cwd3) {
29362
29165
  return null;
29363
29166
  }
29364
29167
  return resolved;
@@ -29374,8 +29177,8 @@ function listDir(relativePath) {
29374
29177
  try {
29375
29178
  const names = fs5.readdirSync(resolved, { withFileTypes: true });
29376
29179
  const entries = names.filter((d) => !d.name.startsWith(".")).map((d) => {
29377
- const entryPath = path14.join(relativePath || ".", d.name).replace(/\\/g, "/");
29378
- const fullPath = path14.join(resolved, d.name);
29180
+ const entryPath = path12.join(relativePath || ".", d.name).replace(/\\/g, "/");
29181
+ const fullPath = path12.join(resolved, d.name);
29379
29182
  let isDir = d.isDirectory();
29380
29183
  if (d.isSymbolicLink()) {
29381
29184
  try {
@@ -29592,15 +29395,15 @@ function readFile(relativePath, startLine, endLine, lineOffset, lineChunkSize =
29592
29395
 
29593
29396
  // src/files/file-index.ts
29594
29397
  import fs7 from "node:fs";
29595
- import path15 from "node:path";
29398
+ import path13 from "node:path";
29596
29399
  import os2 from "node:os";
29597
29400
  import crypto2 from "node:crypto";
29598
- var INDEX_DIR = path15.join(os2.homedir(), ".buildautomaton");
29401
+ var INDEX_DIR = path13.join(os2.homedir(), ".buildautomaton");
29599
29402
  var HASH_LEN = 16;
29600
29403
  var INDEX_VERSION = 2;
29601
29404
  function getIndexPath(cwd3) {
29602
29405
  const hash = crypto2.createHash("sha256").update(cwd3).digest("hex").slice(0, HASH_LEN);
29603
- return path15.join(INDEX_DIR, `.file-index-${hash}.json`);
29406
+ return path13.join(INDEX_DIR, `.file-index-${hash}.json`);
29604
29407
  }
29605
29408
  function getTrigrams(s) {
29606
29409
  const lower = s.toLowerCase();
@@ -29644,14 +29447,14 @@ function walkDir(dir, baseDir, out) {
29644
29447
  }
29645
29448
  for (const name of names) {
29646
29449
  if (name.startsWith(".")) continue;
29647
- const full = path15.join(dir, name);
29450
+ const full = path13.join(dir, name);
29648
29451
  let stat4;
29649
29452
  try {
29650
29453
  stat4 = fs7.statSync(full);
29651
29454
  } catch {
29652
29455
  continue;
29653
29456
  }
29654
- const relative6 = path15.relative(baseDir, full).replace(/\\/g, "/");
29457
+ const relative6 = path13.relative(baseDir, full).replace(/\\/g, "/");
29655
29458
  if (stat4.isDirectory()) {
29656
29459
  walkDir(full, baseDir, out);
29657
29460
  } else if (stat4.isFile()) {
@@ -29660,7 +29463,7 @@ function walkDir(dir, baseDir, out) {
29660
29463
  }
29661
29464
  }
29662
29465
  function buildFileIndex(cwd3) {
29663
- const resolved = path15.resolve(cwd3);
29466
+ const resolved = path13.resolve(cwd3);
29664
29467
  const paths = [];
29665
29468
  walkDir(resolved, resolved, paths);
29666
29469
  paths.sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
@@ -29686,7 +29489,7 @@ function buildFileIndex(cwd3) {
29686
29489
  return data;
29687
29490
  }
29688
29491
  function loadFileIndex(cwd3) {
29689
- const resolved = path15.resolve(cwd3);
29492
+ const resolved = path13.resolve(cwd3);
29690
29493
  const indexPath = getIndexPath(resolved);
29691
29494
  try {
29692
29495
  const raw = fs7.readFileSync(indexPath, "utf8");
@@ -29707,7 +29510,7 @@ function loadFileIndex(cwd3) {
29707
29510
  }
29708
29511
  }
29709
29512
  function ensureFileIndex(cwd3) {
29710
- const resolved = path15.resolve(cwd3);
29513
+ const resolved = path13.resolve(cwd3);
29711
29514
  const cached2 = loadFileIndex(resolved);
29712
29515
  if (cached2 !== null) return { data: cached2, fromCache: true };
29713
29516
  const data = buildFileIndex(resolved);
@@ -29810,62 +29613,116 @@ function handleFileBrowserRequest(msg, socket) {
29810
29613
  }
29811
29614
  }
29812
29615
 
29813
- // src/skills/handle-skill-layout-request.ts
29814
- var handleSkillLayoutRequest = (msg, deps) => {
29815
- if (msg.type !== "skill_layout_request") return false;
29816
- const socket = deps.getWs();
29817
- const id = typeof msg.id === "string" ? msg.id : "";
29818
- const roots = discoverSkillLayoutRoots(process.cwd());
29819
- socket?.send(JSON.stringify({ type: "skill_layout_response", id, roots }));
29820
- return true;
29821
- };
29822
-
29823
- // src/bridge/routing/handlers/register-file-browser-handlers.ts
29616
+ // src/bridge/routing/handlers/file-browser-messages.ts
29824
29617
  function handleFileBrowserRequestMessage(msg, { getWs }) {
29825
- if (typeof msg.id !== "string" || typeof msg.path !== "string") return false;
29618
+ if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
29826
29619
  const socket = getWs();
29827
- if (!socket) return false;
29620
+ if (!socket) return;
29828
29621
  handleFileBrowserRequest(
29829
29622
  msg,
29830
29623
  socket
29831
29624
  );
29832
- return true;
29833
29625
  }
29834
29626
  function handleFileBrowserSearchMessage(msg, { getWs }) {
29835
- if (typeof msg.id !== "string") return false;
29627
+ if (typeof msg.id !== "string") return;
29836
29628
  const socket = getWs();
29837
- if (!socket) return false;
29629
+ if (!socket) return;
29838
29630
  handleFileBrowserSearch(msg, socket);
29839
- return true;
29840
- }
29841
- function registerFileBrowserHandlers(router2) {
29842
- router2.register("file_browser_request", handleFileBrowserRequestMessage);
29843
- router2.register("file_browser_search", handleFileBrowserSearchMessage);
29844
- router2.register("skill_layout_request", handleSkillLayoutRequest);
29845
29631
  }
29846
29632
 
29847
- // src/skills/install-remote-skills.ts
29633
+ // src/skills/discover-local-agent-skills.ts
29848
29634
  import fs8 from "node:fs";
29849
- import path16 from "node:path";
29850
- function installRemoteSkills(cwd3, targetDir, items) {
29851
- const installed = [];
29852
- if (!Array.isArray(items)) {
29853
- return { success: false, error: "Invalid items" };
29854
- }
29855
- try {
29856
- for (const item of items) {
29857
- if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
29635
+ import path14 from "node:path";
29636
+ var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
29637
+ function discoverLocalSkills(cwd3) {
29638
+ const out = [];
29639
+ const seenKeys = /* @__PURE__ */ new Set();
29640
+ for (const rel of SKILL_DISCOVERY_ROOTS) {
29641
+ const base = path14.join(cwd3, rel);
29642
+ if (!fs8.existsSync(base) || !fs8.statSync(base).isDirectory()) continue;
29643
+ let entries = [];
29644
+ try {
29645
+ entries = fs8.readdirSync(base);
29646
+ } catch {
29647
+ continue;
29648
+ }
29649
+ for (const name of entries) {
29650
+ const dir = path14.join(base, name);
29651
+ try {
29652
+ if (!fs8.statSync(dir).isDirectory()) continue;
29653
+ } catch {
29858
29654
  continue;
29859
29655
  }
29860
- const skillDir = path16.join(cwd3, targetDir, item.skillName);
29656
+ const skillMd = path14.join(dir, "SKILL.md");
29657
+ if (!fs8.existsSync(skillMd)) continue;
29658
+ const key = `${rel}/${name}`;
29659
+ if (seenKeys.has(key)) continue;
29660
+ seenKeys.add(key);
29661
+ out.push({ skillKey: name, path: `${rel}/${name}`.replace(/\\/g, "/") });
29662
+ }
29663
+ }
29664
+ return out;
29665
+ }
29666
+ function discoverSkillLayoutRoots(cwd3) {
29667
+ const roots = [];
29668
+ for (const rel of SKILL_DISCOVERY_ROOTS) {
29669
+ const base = path14.join(cwd3, rel);
29670
+ if (!fs8.existsSync(base) || !fs8.statSync(base).isDirectory()) continue;
29671
+ let entries = [];
29672
+ try {
29673
+ entries = fs8.readdirSync(base);
29674
+ } catch {
29675
+ continue;
29676
+ }
29677
+ const skills2 = [];
29678
+ for (const name of entries) {
29679
+ const dir = path14.join(base, name);
29680
+ try {
29681
+ if (!fs8.statSync(dir).isDirectory()) continue;
29682
+ } catch {
29683
+ continue;
29684
+ }
29685
+ if (!fs8.existsSync(path14.join(dir, "SKILL.md"))) continue;
29686
+ const relPath = `${rel}/${name}`.replace(/\\/g, "/");
29687
+ skills2.push({ name, relPath });
29688
+ }
29689
+ if (skills2.length > 0) {
29690
+ roots.push({ path: rel.replace(/\\/g, "/"), skills: skills2 });
29691
+ }
29692
+ }
29693
+ return roots;
29694
+ }
29695
+
29696
+ // src/bridge/routing/handlers/skill-layout-request.ts
29697
+ function handleSkillLayoutRequest(msg, deps) {
29698
+ const socket = deps.getWs();
29699
+ const id = typeof msg.id === "string" ? msg.id : "";
29700
+ const roots = discoverSkillLayoutRoots(process.cwd());
29701
+ socket?.send(JSON.stringify({ type: "skill_layout_response", id, roots }));
29702
+ }
29703
+
29704
+ // src/skills/install-remote-skills.ts
29705
+ import fs9 from "node:fs";
29706
+ import path15 from "node:path";
29707
+ function installRemoteSkills(cwd3, targetDir, items) {
29708
+ const installed = [];
29709
+ if (!Array.isArray(items)) {
29710
+ return { success: false, error: "Invalid items" };
29711
+ }
29712
+ try {
29713
+ for (const item of items) {
29714
+ if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
29715
+ continue;
29716
+ }
29717
+ const skillDir = path15.join(cwd3, targetDir, item.skillName);
29861
29718
  for (const f of item.files) {
29862
29719
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
29863
- const dest = path16.join(skillDir, f.path);
29864
- fs8.mkdirSync(path16.dirname(dest), { recursive: true });
29720
+ const dest = path15.join(skillDir, f.path);
29721
+ fs9.mkdirSync(path15.dirname(dest), { recursive: true });
29865
29722
  if (f.text !== void 0) {
29866
- fs8.writeFileSync(dest, f.text, "utf8");
29723
+ fs9.writeFileSync(dest, f.text, "utf8");
29867
29724
  } else if (f.base64) {
29868
- fs8.writeFileSync(dest, Buffer.from(f.base64, "base64"));
29725
+ fs9.writeFileSync(dest, Buffer.from(f.base64, "base64"));
29869
29726
  }
29870
29727
  }
29871
29728
  installed.push({
@@ -29882,7 +29739,6 @@ function installRemoteSkills(cwd3, targetDir, items) {
29882
29739
 
29883
29740
  // src/bridge/routing/handlers/install-skills.ts
29884
29741
  var handleInstallSkillsMessage = (msg, deps) => {
29885
- if (msg.type !== "install_skills") return false;
29886
29742
  const socket = deps.getWs();
29887
29743
  const id = typeof msg.id === "string" ? msg.id : "";
29888
29744
  const targetDir = typeof msg.targetDir === "string" && msg.targetDir.trim() ? msg.targetDir.trim() : ".agents/skills";
@@ -29891,30 +29747,27 @@ var handleInstallSkillsMessage = (msg, deps) => {
29891
29747
  const result = installRemoteSkills(cwd3, targetDir, rawItems);
29892
29748
  if (!result.success) {
29893
29749
  const err = result.error ?? "Invalid items";
29894
- deps.log(`install_skills failed: ${err}`);
29750
+ deps.log(`[Bridge service] install_skills failed: ${err}`);
29895
29751
  socket?.send(JSON.stringify({ type: "install_skills_result", id, success: false, error: err }));
29896
- return true;
29752
+ return;
29897
29753
  }
29898
- deps.log(`Installed ${result.installed?.length ?? 0} remote skill(s) into ${targetDir}`);
29899
29754
  socket?.send(JSON.stringify({ type: "install_skills_result", id, success: true, installed: result.installed }));
29900
- return true;
29901
29755
  };
29902
29756
 
29903
29757
  // src/bridge/routing/handlers/refresh-local-skills.ts
29904
29758
  var handleRefreshLocalSkills = (_msg, deps) => {
29905
29759
  deps.sendLocalSkillsReport?.();
29906
- return true;
29907
29760
  };
29908
29761
 
29909
29762
  // src/bridge/routing/handlers/commit-session-request.ts
29910
29763
  var handleCommitSessionRequestMessage = (msg, deps) => {
29911
- if (msg.type !== "commit_session_request" || typeof msg.id !== "string") return false;
29764
+ if (typeof msg.id !== "string") return;
29912
29765
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
29913
29766
  const branch = typeof msg.branch === "string" ? msg.branch : "";
29914
29767
  const message = typeof msg.message === "string" ? msg.message : "";
29915
29768
  const push = msg.push === true;
29916
29769
  const endSession = msg.endSession === true;
29917
- if (!sessionId || !branch || !message) return false;
29770
+ if (!sessionId || !branch || !message) return;
29918
29771
  void deps.sessionWorktreeManager.commitSession({ sessionId, branch, message, push }).then((r) => {
29919
29772
  const s = deps.getWs();
29920
29773
  if (s) {
@@ -29938,53 +29791,45 @@ var handleCommitSessionRequestMessage = (msg, deps) => {
29938
29791
  });
29939
29792
  }
29940
29793
  });
29941
- return true;
29942
29794
  };
29943
29795
 
29944
29796
  // src/bridge/routing/handlers/rename-session-branch.ts
29945
29797
  var handleRenameSessionBranchMessage = (msg, deps) => {
29946
- if (msg.type !== "rename_session_branch") return false;
29947
29798
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
29948
29799
  const newBranch = typeof msg.newBranch === "string" ? msg.newBranch : "";
29949
- if (!sessionId || !newBranch) return false;
29800
+ if (!sessionId || !newBranch) return;
29950
29801
  void deps.sessionWorktreeManager.renameSessionBranch(sessionId, newBranch);
29951
- return true;
29952
29802
  };
29953
29803
 
29954
29804
  // src/bridge/routing/handlers/session-archived.ts
29955
29805
  var handleSessionArchivedMessage = (msg, deps) => {
29956
- if (msg.type !== "session_archived") return false;
29957
29806
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
29958
- if (!sessionId) return false;
29807
+ if (!sessionId) return;
29959
29808
  void deps.sessionWorktreeManager.removeSessionWorktrees(sessionId);
29960
- return true;
29961
29809
  };
29962
29810
 
29963
29811
  // src/bridge/routing/handlers/session-discarded.ts
29964
29812
  var handleSessionDiscardedMessage = (msg, deps) => {
29965
- if (msg.type !== "session_discarded") return false;
29966
29813
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
29967
- if (!sessionId) return false;
29814
+ if (!sessionId) return;
29968
29815
  void deps.sessionWorktreeManager.removeSessionWorktrees(sessionId);
29969
- return true;
29970
29816
  };
29971
29817
 
29972
29818
  // src/bridge/routing/handlers/revert-turn-snapshot.ts
29973
- import * as fs9 from "node:fs";
29974
- import * as path17 from "node:path";
29819
+ import * as fs10 from "node:fs";
29820
+ import * as path16 from "node:path";
29975
29821
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
29976
- if (msg.type !== "revert_turn_snapshot") return false;
29977
29822
  const id = typeof msg.id === "string" ? msg.id : "";
29978
29823
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
29979
29824
  const turnId = typeof msg.turnId === "string" ? msg.turnId : "";
29980
- if (!id || !sessionId || !turnId) return true;
29825
+ if (!id || !sessionId || !turnId) return;
29981
29826
  const { getWs, log: log2, sessionWorktreeManager } = deps;
29982
29827
  void (async () => {
29983
29828
  const s = getWs();
29984
29829
  if (!s) return;
29985
- const agentBase = sessionWorktreeManager.getAgentCwdForSession(sessionId) ?? path17.resolve(process.cwd());
29830
+ const agentBase = sessionWorktreeManager.getAgentCwdForSession(sessionId) ?? path16.resolve(process.cwd());
29986
29831
  const file2 = snapshotFilePath(agentBase, turnId);
29987
- if (!fs9.existsSync(file2)) {
29832
+ if (!fs10.existsSync(file2)) {
29988
29833
  sendWsMessage(s, {
29989
29834
  type: "revert_turn_snapshot_result",
29990
29835
  id,
@@ -30001,40 +29846,118 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
30001
29846
  ...res.error ? { error: res.error } : {}
30002
29847
  });
30003
29848
  })();
30004
- return true;
30005
29849
  };
30006
29850
 
29851
+ // src/bridge/routing/handlers/dev-server-control.ts
29852
+ var handleDevServerControl = (msg, deps) => {
29853
+ const serverId = typeof msg.serverId === "string" ? msg.serverId : "";
29854
+ const action = msg.action === "start" || msg.action === "stop" ? msg.action : null;
29855
+ if (!serverId || !action) return;
29856
+ deps.devServerManager?.handleControl(serverId, action);
29857
+ };
29858
+
29859
+ // src/bridge/routing/handlers/dev-servers-config.ts
29860
+ var handleDevServersConfig = (msg, deps) => {
29861
+ const devServers = msg.devServers;
29862
+ deps.devServerManager?.applyConfig(devServers ?? []);
29863
+ };
29864
+
29865
+ // src/bridge/routing/dispatch-bridge-message.ts
29866
+ function dispatchBridgeMessage(msg, deps) {
29867
+ const type = msg.type;
29868
+ if (typeof type !== "string") return;
29869
+ switch (type) {
29870
+ case "auth_token":
29871
+ handleAuthToken(msg, deps);
29872
+ break;
29873
+ case "bridge_identified":
29874
+ handleBridgeIdentified(msg, deps);
29875
+ break;
29876
+ case "dev_servers_config":
29877
+ handleDevServersConfig(msg, deps);
29878
+ break;
29879
+ case "server_control":
29880
+ handleDevServerControl(msg, deps);
29881
+ break;
29882
+ case "agent_config":
29883
+ handleAgentConfigMessage(msg, deps);
29884
+ break;
29885
+ case "prompt":
29886
+ handlePromptMessage(msg, deps);
29887
+ break;
29888
+ case "commit_session_request":
29889
+ handleCommitSessionRequestMessage(msg, deps);
29890
+ break;
29891
+ case "rename_session_branch":
29892
+ handleRenameSessionBranchMessage(msg, deps);
29893
+ break;
29894
+ case "session_archived":
29895
+ handleSessionArchivedMessage(msg, deps);
29896
+ break;
29897
+ case "session_discarded":
29898
+ handleSessionDiscardedMessage(msg, deps);
29899
+ break;
29900
+ case "revert_turn_snapshot":
29901
+ handleRevertTurnSnapshotMessage(msg, deps);
29902
+ break;
29903
+ case "cancel_run":
29904
+ handleCancelRunMessage(msg, deps);
29905
+ break;
29906
+ case "cursor_request_response":
29907
+ handleCursorRequestResponseMessage(msg, deps);
29908
+ break;
29909
+ case "skill_call":
29910
+ handleSkillCallMessage(msg, deps);
29911
+ break;
29912
+ case "file_browser_request":
29913
+ handleFileBrowserRequestMessage(msg, deps);
29914
+ break;
29915
+ case "file_browser_search":
29916
+ handleFileBrowserSearchMessage(msg, deps);
29917
+ break;
29918
+ case "skill_layout_request":
29919
+ handleSkillLayoutRequest(msg, deps);
29920
+ break;
29921
+ case "install_skills":
29922
+ handleInstallSkillsMessage(msg, deps);
29923
+ break;
29924
+ case "refresh_local_skills":
29925
+ handleRefreshLocalSkills(msg, deps);
29926
+ break;
29927
+ default:
29928
+ deps.log?.(`[Bridge service] unhandled message type: ${type}`);
29929
+ }
29930
+ }
29931
+
30007
29932
  // src/bridge/routing/handle-bridge-message.ts
30008
- var router = createMessageRouter();
30009
- router.register("auth_token", handleAuthToken);
30010
- router.register("bridge_identified", handleBridgeIdentified);
30011
- router.register("agent_config", handleAgentConfigMessage);
30012
- router.register("prompt", handlePromptMessage);
30013
- router.register("commit_session_request", handleCommitSessionRequestMessage);
30014
- router.register("rename_session_branch", handleRenameSessionBranchMessage);
30015
- router.register("session_archived", handleSessionArchivedMessage);
30016
- router.register("session_discarded", handleSessionDiscardedMessage);
30017
- router.register("revert_turn_snapshot", handleRevertTurnSnapshotMessage);
30018
- router.register("cancel_run", handleCancelRunMessage);
30019
- router.register("cursor_request_response", handleCursorRequestResponseMessage);
30020
- router.register("skill_call", handleSkillCallMessage);
30021
- registerFileBrowserHandlers(router);
30022
- router.register("install_skills", handleInstallSkillsMessage);
30023
- router.register("refresh_local_skills", handleRefreshLocalSkills);
29933
+ var DEFERRED_INBOUND_TYPES = /* @__PURE__ */ new Set([
29934
+ "server_control",
29935
+ "prompt",
29936
+ "install_skills",
29937
+ "refresh_local_skills",
29938
+ "dev_servers_config"
29939
+ ]);
30024
29940
  function handleBridgeMessage(data, deps) {
30025
29941
  const msg = data;
30026
29942
  const socket = deps.getWs();
30027
29943
  if (!socket) return;
30028
- router.dispatch(msg, deps);
29944
+ const type = msg.type;
29945
+ if (typeof type === "string" && DEFERRED_INBOUND_TYPES.has(type)) {
29946
+ setImmediate(() => {
29947
+ dispatchBridgeMessage(msg, deps);
29948
+ });
29949
+ return;
29950
+ }
29951
+ dispatchBridgeMessage(msg, deps);
30029
29952
  }
30030
29953
 
30031
29954
  // src/worktrees/session-worktree-manager.ts
30032
- import * as path21 from "node:path";
29955
+ import * as path20 from "node:path";
30033
29956
  import os4 from "node:os";
30034
29957
 
30035
29958
  // src/worktrees/prepare-new-session-worktrees.ts
30036
- import * as fs11 from "node:fs";
30037
- import * as path19 from "node:path";
29959
+ import * as fs12 from "node:fs";
29960
+ import * as path18 from "node:path";
30038
29961
 
30039
29962
  // src/git/worktree-add.ts
30040
29963
  async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch) {
@@ -30043,12 +29966,12 @@ async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch) {
30043
29966
  }
30044
29967
 
30045
29968
  // src/worktrees/worktree-layout-file.ts
30046
- import * as fs10 from "node:fs";
30047
- import * as path18 from "node:path";
29969
+ import * as fs11 from "node:fs";
29970
+ import * as path17 from "node:path";
30048
29971
  import os3 from "node:os";
30049
29972
  var LAYOUT_FILENAME = "worktree-launcher-layout.json";
30050
29973
  function defaultWorktreeLayoutPath() {
30051
- return path18.join(os3.homedir(), ".buildautomaton", LAYOUT_FILENAME);
29974
+ return path17.join(os3.homedir(), ".buildautomaton", LAYOUT_FILENAME);
30052
29975
  }
30053
29976
  function normalizeLoadedLayout(raw) {
30054
29977
  if (raw && typeof raw === "object" && "launcherCwds" in raw) {
@@ -30060,8 +29983,8 @@ function normalizeLoadedLayout(raw) {
30060
29983
  function loadWorktreeLayout() {
30061
29984
  try {
30062
29985
  const p = defaultWorktreeLayoutPath();
30063
- if (!fs10.existsSync(p)) return { launcherCwds: [] };
30064
- const raw = JSON.parse(fs10.readFileSync(p, "utf8"));
29986
+ if (!fs11.existsSync(p)) return { launcherCwds: [] };
29987
+ const raw = JSON.parse(fs11.readFileSync(p, "utf8"));
30065
29988
  return normalizeLoadedLayout(raw);
30066
29989
  } catch {
30067
29990
  return { launcherCwds: [] };
@@ -30069,18 +29992,18 @@ function loadWorktreeLayout() {
30069
29992
  }
30070
29993
  function saveWorktreeLayout(layout) {
30071
29994
  try {
30072
- const dir = path18.dirname(defaultWorktreeLayoutPath());
30073
- fs10.mkdirSync(dir, { recursive: true });
30074
- fs10.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
29995
+ const dir = path17.dirname(defaultWorktreeLayoutPath());
29996
+ fs11.mkdirSync(dir, { recursive: true });
29997
+ fs11.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
30075
29998
  } catch {
30076
29999
  }
30077
30000
  }
30078
30001
  function baseNameSafe(abs) {
30079
- return path18.basename(abs).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
30002
+ return path17.basename(abs).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
30080
30003
  }
30081
30004
  function allocateDirNameForLauncherCwd(layout, launcherCwdAbs) {
30082
- const norm = path18.resolve(launcherCwdAbs);
30083
- const existing = layout.launcherCwds.find((e) => path18.resolve(e.absolutePath) === norm);
30005
+ const norm = path17.resolve(launcherCwdAbs);
30006
+ const existing = layout.launcherCwds.find((e) => path17.resolve(e.absolutePath) === norm);
30084
30007
  if (existing) return existing.dirName;
30085
30008
  const base = baseNameSafe(norm);
30086
30009
  const used = new Set(layout.launcherCwds.map((e) => e.dirName));
@@ -30098,9 +30021,9 @@ function allocateDirNameForLauncherCwd(layout, launcherCwdAbs) {
30098
30021
  // src/worktrees/prepare-new-session-worktrees.ts
30099
30022
  async function prepareNewSessionWorktrees(options) {
30100
30023
  const { rootAbs, launcherCwd, sessionId, layout, log: log2 } = options;
30101
- const launcherResolved = path19.resolve(launcherCwd);
30024
+ const launcherResolved = path18.resolve(launcherCwd);
30102
30025
  const cwdKey = allocateDirNameForLauncherCwd(layout, launcherResolved);
30103
- const agentMirrorRoot = path19.join(rootAbs, cwdKey);
30026
+ const agentMirrorRoot = path18.join(rootAbs, cwdKey);
30104
30027
  const repos = await discoverGitReposUnderRoot(launcherResolved);
30105
30028
  if (repos.length === 0) {
30106
30029
  log2("[worktrees] No git repos under launcher cwd; skipping worktree creation");
@@ -30108,13 +30031,13 @@ async function prepareNewSessionWorktrees(options) {
30108
30031
  }
30109
30032
  const branch = `session-${sessionId}`;
30110
30033
  const worktreePaths = [];
30111
- fs11.mkdirSync(agentMirrorRoot, { recursive: true });
30034
+ fs12.mkdirSync(agentMirrorRoot, { recursive: true });
30112
30035
  for (const repo of repos) {
30113
- let rel = path19.relative(launcherResolved, repo.absolutePath);
30114
- if (rel.startsWith("..") || path19.isAbsolute(rel)) continue;
30036
+ let rel = path18.relative(launcherResolved, repo.absolutePath);
30037
+ if (rel.startsWith("..") || path18.isAbsolute(rel)) continue;
30115
30038
  const relNorm = rel === "" ? "." : rel;
30116
- const wtPath = path19.join(agentMirrorRoot, relNorm, sessionId);
30117
- fs11.mkdirSync(path19.dirname(wtPath), { recursive: true });
30039
+ const wtPath = path18.join(agentMirrorRoot, relNorm, sessionId);
30040
+ fs12.mkdirSync(path18.dirname(wtPath), { recursive: true });
30118
30041
  try {
30119
30042
  await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch);
30120
30043
  log2(`[worktrees] Added worktree ${wtPath} (branch ${branch})`);
@@ -30149,23 +30072,23 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
30149
30072
  }
30150
30073
 
30151
30074
  // src/worktrees/remove-session-worktrees.ts
30152
- import * as fs14 from "node:fs";
30075
+ import * as fs15 from "node:fs";
30153
30076
 
30154
30077
  // src/git/worktree-remove.ts
30155
- import * as fs13 from "node:fs";
30078
+ import * as fs14 from "node:fs";
30156
30079
 
30157
30080
  // src/git/resolve-main-repo-from-git-file.ts
30158
- import * as fs12 from "node:fs";
30159
- import * as path20 from "node:path";
30081
+ import * as fs13 from "node:fs";
30082
+ import * as path19 from "node:path";
30160
30083
  function resolveMainRepoFromWorktreeGitFile(wt) {
30161
- const gitDirFile = path20.join(wt, ".git");
30162
- if (!fs12.existsSync(gitDirFile) || !fs12.statSync(gitDirFile).isFile()) return "";
30163
- const first2 = fs12.readFileSync(gitDirFile, "utf8").trim();
30084
+ const gitDirFile = path19.join(wt, ".git");
30085
+ if (!fs13.existsSync(gitDirFile) || !fs13.statSync(gitDirFile).isFile()) return "";
30086
+ const first2 = fs13.readFileSync(gitDirFile, "utf8").trim();
30164
30087
  const m = first2.match(/^gitdir:\s*(.+)$/im);
30165
30088
  if (!m) return "";
30166
- const gitWorktreePath = path20.resolve(wt, m[1].trim());
30167
- const gitDir = path20.dirname(path20.dirname(gitWorktreePath));
30168
- return path20.dirname(gitDir);
30089
+ const gitWorktreePath = path19.resolve(wt, m[1].trim());
30090
+ const gitDir = path19.dirname(path19.dirname(gitWorktreePath));
30091
+ return path19.dirname(gitDir);
30169
30092
  }
30170
30093
 
30171
30094
  // src/git/worktree-remove.ts
@@ -30174,7 +30097,7 @@ async function gitWorktreeRemoveForce(worktreePath) {
30174
30097
  if (mainRepo) {
30175
30098
  await simpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
30176
30099
  } else {
30177
- fs13.rmSync(worktreePath, { recursive: true, force: true });
30100
+ fs14.rmSync(worktreePath, { recursive: true, force: true });
30178
30101
  }
30179
30102
  }
30180
30103
 
@@ -30187,7 +30110,7 @@ async function removeSessionWorktrees(paths, log2) {
30187
30110
  } catch (e) {
30188
30111
  log2(`[worktrees] remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
30189
30112
  try {
30190
- fs14.rmSync(wt, { recursive: true, force: true });
30113
+ fs15.rmSync(wt, { recursive: true, force: true });
30191
30114
  } catch {
30192
30115
  }
30193
30116
  }
@@ -30199,10 +30122,21 @@ async function gitCommitAllIfDirty(repoDir, message, options) {
30199
30122
  const g = simpleGit(repoDir);
30200
30123
  const st = await g.status();
30201
30124
  if (!st.files?.length) return;
30125
+ const branch = options.branch.trim();
30126
+ if (!branch) {
30127
+ throw new Error("Branch name is required");
30128
+ }
30129
+ const branches = await g.branchLocal();
30130
+ const localNames = new Set(branches.all.map((b) => b.replace(/^\*\s*/, "").trim()));
30131
+ if (!localNames.has(branch)) {
30132
+ await g.checkoutLocalBranch(branch);
30133
+ } else {
30134
+ await g.checkout(branch);
30135
+ }
30202
30136
  await g.add(".");
30203
30137
  await g.commit(message);
30204
30138
  if (options.push) {
30205
- await g.push("origin", options.branch);
30139
+ await g.push(["-u", "origin", branch]);
30206
30140
  }
30207
30141
  }
30208
30142
 
@@ -30248,7 +30182,7 @@ var SessionWorktreeManager = class {
30248
30182
  }
30249
30183
  if (!opts.isNewSession) {
30250
30184
  const agentCwd = this.sessionAgentCwd.get(sessionId);
30251
- if (agentCwd) return path21.resolve(agentCwd);
30185
+ if (agentCwd) return path20.resolve(agentCwd);
30252
30186
  return void 0;
30253
30187
  }
30254
30188
  const prep = await prepareNewSessionWorktrees({
@@ -30261,7 +30195,7 @@ var SessionWorktreeManager = class {
30261
30195
  if (!prep) return void 0;
30262
30196
  this.sessionPaths.set(sessionId, prep.worktreePaths);
30263
30197
  this.sessionAgentCwd.set(sessionId, prep.agentCwd);
30264
- return path21.resolve(prep.agentCwd);
30198
+ return path20.resolve(prep.agentCwd);
30265
30199
  }
30266
30200
  async renameSessionBranch(sessionId, newBranch) {
30267
30201
  const paths = this.sessionPaths.get(sessionId);
@@ -30282,7 +30216,7 @@ var SessionWorktreeManager = class {
30282
30216
  getAgentCwdForSession(sessionId) {
30283
30217
  if (!sessionId) return null;
30284
30218
  const c = this.sessionAgentCwd.get(sessionId);
30285
- return c ? path21.resolve(c) : null;
30219
+ return c ? path20.resolve(c) : null;
30286
30220
  }
30287
30221
  async removeSessionWorktrees(sessionId) {
30288
30222
  const paths = this.sessionPaths.get(sessionId);
@@ -30303,7 +30237,7 @@ var SessionWorktreeManager = class {
30303
30237
  }
30304
30238
  };
30305
30239
  function defaultWorktreesRootAbs() {
30306
- return path21.join(os4.homedir(), ".buildautomaton", "worktrees");
30240
+ return path20.join(os4.homedir(), ".buildautomaton", "worktrees");
30307
30241
  }
30308
30242
 
30309
30243
  // src/auth/refresh-bridge-tokens.ts
@@ -30334,7 +30268,7 @@ async function refreshBridgeTokens(params) {
30334
30268
  }
30335
30269
 
30336
30270
  // src/files/watch-file-index.ts
30337
- import path22 from "node:path";
30271
+ import path21 from "node:path";
30338
30272
 
30339
30273
  // ../../node_modules/.pnpm/chokidar@4.0.3/node_modules/chokidar/esm/index.js
30340
30274
  import { stat as statcb } from "fs";
@@ -30412,7 +30346,7 @@ var ReaddirpStream = class extends Readable2 {
30412
30346
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
30413
30347
  const statMethod = opts.lstat ? lstat : stat;
30414
30348
  if (wantBigintFsStats) {
30415
- this._stat = (path23) => statMethod(path23, { bigint: true });
30349
+ this._stat = (path24) => statMethod(path24, { bigint: true });
30416
30350
  } else {
30417
30351
  this._stat = statMethod;
30418
30352
  }
@@ -30437,8 +30371,8 @@ var ReaddirpStream = class extends Readable2 {
30437
30371
  const par = this.parent;
30438
30372
  const fil = par && par.files;
30439
30373
  if (fil && fil.length > 0) {
30440
- const { path: path23, depth } = par;
30441
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path23));
30374
+ const { path: path24, depth } = par;
30375
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path24));
30442
30376
  const awaited = await Promise.all(slice);
30443
30377
  for (const entry of awaited) {
30444
30378
  if (!entry)
@@ -30478,20 +30412,20 @@ var ReaddirpStream = class extends Readable2 {
30478
30412
  this.reading = false;
30479
30413
  }
30480
30414
  }
30481
- async _exploreDir(path23, depth) {
30415
+ async _exploreDir(path24, depth) {
30482
30416
  let files;
30483
30417
  try {
30484
- files = await readdir(path23, this._rdOptions);
30418
+ files = await readdir(path24, this._rdOptions);
30485
30419
  } catch (error40) {
30486
30420
  this._onError(error40);
30487
30421
  }
30488
- return { files, depth, path: path23 };
30422
+ return { files, depth, path: path24 };
30489
30423
  }
30490
- async _formatEntry(dirent, path23) {
30424
+ async _formatEntry(dirent, path24) {
30491
30425
  let entry;
30492
30426
  const basename6 = this._isDirent ? dirent.name : dirent;
30493
30427
  try {
30494
- const fullPath = presolve(pjoin(path23, basename6));
30428
+ const fullPath = presolve(pjoin(path24, basename6));
30495
30429
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename6 };
30496
30430
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
30497
30431
  } catch (err) {
@@ -30891,16 +30825,16 @@ var delFromSet = (main, prop, item) => {
30891
30825
  };
30892
30826
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
30893
30827
  var FsWatchInstances = /* @__PURE__ */ new Map();
30894
- function createFsWatchInstance(path23, options, listener, errHandler, emitRaw) {
30828
+ function createFsWatchInstance(path24, options, listener, errHandler, emitRaw) {
30895
30829
  const handleEvent = (rawEvent, evPath) => {
30896
- listener(path23);
30897
- emitRaw(rawEvent, evPath, { watchedPath: path23 });
30898
- if (evPath && path23 !== evPath) {
30899
- fsWatchBroadcast(sysPath.resolve(path23, evPath), KEY_LISTENERS, sysPath.join(path23, evPath));
30830
+ listener(path24);
30831
+ emitRaw(rawEvent, evPath, { watchedPath: path24 });
30832
+ if (evPath && path24 !== evPath) {
30833
+ fsWatchBroadcast(sysPath.resolve(path24, evPath), KEY_LISTENERS, sysPath.join(path24, evPath));
30900
30834
  }
30901
30835
  };
30902
30836
  try {
30903
- return fs_watch(path23, {
30837
+ return fs_watch(path24, {
30904
30838
  persistent: options.persistent
30905
30839
  }, handleEvent);
30906
30840
  } catch (error40) {
@@ -30916,12 +30850,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
30916
30850
  listener(val1, val2, val3);
30917
30851
  });
30918
30852
  };
30919
- var setFsWatchListener = (path23, fullPath, options, handlers) => {
30853
+ var setFsWatchListener = (path24, fullPath, options, handlers) => {
30920
30854
  const { listener, errHandler, rawEmitter } = handlers;
30921
30855
  let cont = FsWatchInstances.get(fullPath);
30922
30856
  let watcher;
30923
30857
  if (!options.persistent) {
30924
- watcher = createFsWatchInstance(path23, options, listener, errHandler, rawEmitter);
30858
+ watcher = createFsWatchInstance(path24, options, listener, errHandler, rawEmitter);
30925
30859
  if (!watcher)
30926
30860
  return;
30927
30861
  return watcher.close.bind(watcher);
@@ -30932,7 +30866,7 @@ var setFsWatchListener = (path23, fullPath, options, handlers) => {
30932
30866
  addAndConvert(cont, KEY_RAW, rawEmitter);
30933
30867
  } else {
30934
30868
  watcher = createFsWatchInstance(
30935
- path23,
30869
+ path24,
30936
30870
  options,
30937
30871
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
30938
30872
  errHandler,
@@ -30947,7 +30881,7 @@ var setFsWatchListener = (path23, fullPath, options, handlers) => {
30947
30881
  cont.watcherUnusable = true;
30948
30882
  if (isWindows && error40.code === "EPERM") {
30949
30883
  try {
30950
- const fd = await open(path23, "r");
30884
+ const fd = await open(path24, "r");
30951
30885
  await fd.close();
30952
30886
  broadcastErr(error40);
30953
30887
  } catch (err) {
@@ -30978,7 +30912,7 @@ var setFsWatchListener = (path23, fullPath, options, handlers) => {
30978
30912
  };
30979
30913
  };
30980
30914
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
30981
- var setFsWatchFileListener = (path23, fullPath, options, handlers) => {
30915
+ var setFsWatchFileListener = (path24, fullPath, options, handlers) => {
30982
30916
  const { listener, rawEmitter } = handlers;
30983
30917
  let cont = FsWatchFileInstances.get(fullPath);
30984
30918
  const copts = cont && cont.options;
@@ -31000,7 +30934,7 @@ var setFsWatchFileListener = (path23, fullPath, options, handlers) => {
31000
30934
  });
31001
30935
  const currmtime = curr.mtimeMs;
31002
30936
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
31003
- foreach(cont.listeners, (listener2) => listener2(path23, curr));
30937
+ foreach(cont.listeners, (listener2) => listener2(path24, curr));
31004
30938
  }
31005
30939
  })
31006
30940
  };
@@ -31028,13 +30962,13 @@ var NodeFsHandler = class {
31028
30962
  * @param listener on fs change
31029
30963
  * @returns closer for the watcher instance
31030
30964
  */
31031
- _watchWithNodeFs(path23, listener) {
30965
+ _watchWithNodeFs(path24, listener) {
31032
30966
  const opts = this.fsw.options;
31033
- const directory = sysPath.dirname(path23);
31034
- const basename6 = sysPath.basename(path23);
30967
+ const directory = sysPath.dirname(path24);
30968
+ const basename6 = sysPath.basename(path24);
31035
30969
  const parent = this.fsw._getWatchedDir(directory);
31036
30970
  parent.add(basename6);
31037
- const absolutePath = sysPath.resolve(path23);
30971
+ const absolutePath = sysPath.resolve(path24);
31038
30972
  const options = {
31039
30973
  persistent: opts.persistent
31040
30974
  };
@@ -31044,12 +30978,12 @@ var NodeFsHandler = class {
31044
30978
  if (opts.usePolling) {
31045
30979
  const enableBin = opts.interval !== opts.binaryInterval;
31046
30980
  options.interval = enableBin && isBinaryPath(basename6) ? opts.binaryInterval : opts.interval;
31047
- closer = setFsWatchFileListener(path23, absolutePath, options, {
30981
+ closer = setFsWatchFileListener(path24, absolutePath, options, {
31048
30982
  listener,
31049
30983
  rawEmitter: this.fsw._emitRaw
31050
30984
  });
31051
30985
  } else {
31052
- closer = setFsWatchListener(path23, absolutePath, options, {
30986
+ closer = setFsWatchListener(path24, absolutePath, options, {
31053
30987
  listener,
31054
30988
  errHandler: this._boundHandleError,
31055
30989
  rawEmitter: this.fsw._emitRaw
@@ -31071,7 +31005,7 @@ var NodeFsHandler = class {
31071
31005
  let prevStats = stats;
31072
31006
  if (parent.has(basename6))
31073
31007
  return;
31074
- const listener = async (path23, newStats) => {
31008
+ const listener = async (path24, newStats) => {
31075
31009
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file2, 5))
31076
31010
  return;
31077
31011
  if (!newStats || newStats.mtimeMs === 0) {
@@ -31085,11 +31019,11 @@ var NodeFsHandler = class {
31085
31019
  this.fsw._emit(EV.CHANGE, file2, newStats2);
31086
31020
  }
31087
31021
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
31088
- this.fsw._closeFile(path23);
31022
+ this.fsw._closeFile(path24);
31089
31023
  prevStats = newStats2;
31090
31024
  const closer2 = this._watchWithNodeFs(file2, listener);
31091
31025
  if (closer2)
31092
- this.fsw._addPathCloser(path23, closer2);
31026
+ this.fsw._addPathCloser(path24, closer2);
31093
31027
  } else {
31094
31028
  prevStats = newStats2;
31095
31029
  }
@@ -31121,7 +31055,7 @@ var NodeFsHandler = class {
31121
31055
  * @param item basename of this item
31122
31056
  * @returns true if no more processing is needed for this entry.
31123
31057
  */
31124
- async _handleSymlink(entry, directory, path23, item) {
31058
+ async _handleSymlink(entry, directory, path24, item) {
31125
31059
  if (this.fsw.closed) {
31126
31060
  return;
31127
31061
  }
@@ -31131,7 +31065,7 @@ var NodeFsHandler = class {
31131
31065
  this.fsw._incrReadyCount();
31132
31066
  let linkPath;
31133
31067
  try {
31134
- linkPath = await fsrealpath(path23);
31068
+ linkPath = await fsrealpath(path24);
31135
31069
  } catch (e) {
31136
31070
  this.fsw._emitReady();
31137
31071
  return true;
@@ -31141,12 +31075,12 @@ var NodeFsHandler = class {
31141
31075
  if (dir.has(item)) {
31142
31076
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
31143
31077
  this.fsw._symlinkPaths.set(full, linkPath);
31144
- this.fsw._emit(EV.CHANGE, path23, entry.stats);
31078
+ this.fsw._emit(EV.CHANGE, path24, entry.stats);
31145
31079
  }
31146
31080
  } else {
31147
31081
  dir.add(item);
31148
31082
  this.fsw._symlinkPaths.set(full, linkPath);
31149
- this.fsw._emit(EV.ADD, path23, entry.stats);
31083
+ this.fsw._emit(EV.ADD, path24, entry.stats);
31150
31084
  }
31151
31085
  this.fsw._emitReady();
31152
31086
  return true;
@@ -31175,9 +31109,9 @@ var NodeFsHandler = class {
31175
31109
  return;
31176
31110
  }
31177
31111
  const item = entry.path;
31178
- let path23 = sysPath.join(directory, item);
31112
+ let path24 = sysPath.join(directory, item);
31179
31113
  current.add(item);
31180
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path23, item)) {
31114
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path24, item)) {
31181
31115
  return;
31182
31116
  }
31183
31117
  if (this.fsw.closed) {
@@ -31186,8 +31120,8 @@ var NodeFsHandler = class {
31186
31120
  }
31187
31121
  if (item === target || !target && !previous.has(item)) {
31188
31122
  this.fsw._incrReadyCount();
31189
- path23 = sysPath.join(dir, sysPath.relative(dir, path23));
31190
- this._addToNodeFs(path23, initialAdd, wh, depth + 1);
31123
+ path24 = sysPath.join(dir, sysPath.relative(dir, path24));
31124
+ this._addToNodeFs(path24, initialAdd, wh, depth + 1);
31191
31125
  }
31192
31126
  }).on(EV.ERROR, this._boundHandleError);
31193
31127
  return new Promise((resolve16, reject) => {
@@ -31256,13 +31190,13 @@ var NodeFsHandler = class {
31256
31190
  * @param depth Child path actually targeted for watch
31257
31191
  * @param target Child path actually targeted for watch
31258
31192
  */
31259
- async _addToNodeFs(path23, initialAdd, priorWh, depth, target) {
31193
+ async _addToNodeFs(path24, initialAdd, priorWh, depth, target) {
31260
31194
  const ready = this.fsw._emitReady;
31261
- if (this.fsw._isIgnored(path23) || this.fsw.closed) {
31195
+ if (this.fsw._isIgnored(path24) || this.fsw.closed) {
31262
31196
  ready();
31263
31197
  return false;
31264
31198
  }
31265
- const wh = this.fsw._getWatchHelpers(path23);
31199
+ const wh = this.fsw._getWatchHelpers(path24);
31266
31200
  if (priorWh) {
31267
31201
  wh.filterPath = (entry) => priorWh.filterPath(entry);
31268
31202
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -31278,8 +31212,8 @@ var NodeFsHandler = class {
31278
31212
  const follow = this.fsw.options.followSymlinks;
31279
31213
  let closer;
31280
31214
  if (stats.isDirectory()) {
31281
- const absPath = sysPath.resolve(path23);
31282
- const targetPath = follow ? await fsrealpath(path23) : path23;
31215
+ const absPath = sysPath.resolve(path24);
31216
+ const targetPath = follow ? await fsrealpath(path24) : path24;
31283
31217
  if (this.fsw.closed)
31284
31218
  return;
31285
31219
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -31289,29 +31223,29 @@ var NodeFsHandler = class {
31289
31223
  this.fsw._symlinkPaths.set(absPath, targetPath);
31290
31224
  }
31291
31225
  } else if (stats.isSymbolicLink()) {
31292
- const targetPath = follow ? await fsrealpath(path23) : path23;
31226
+ const targetPath = follow ? await fsrealpath(path24) : path24;
31293
31227
  if (this.fsw.closed)
31294
31228
  return;
31295
31229
  const parent = sysPath.dirname(wh.watchPath);
31296
31230
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
31297
31231
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
31298
- closer = await this._handleDir(parent, stats, initialAdd, depth, path23, wh, targetPath);
31232
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path24, wh, targetPath);
31299
31233
  if (this.fsw.closed)
31300
31234
  return;
31301
31235
  if (targetPath !== void 0) {
31302
- this.fsw._symlinkPaths.set(sysPath.resolve(path23), targetPath);
31236
+ this.fsw._symlinkPaths.set(sysPath.resolve(path24), targetPath);
31303
31237
  }
31304
31238
  } else {
31305
31239
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
31306
31240
  }
31307
31241
  ready();
31308
31242
  if (closer)
31309
- this.fsw._addPathCloser(path23, closer);
31243
+ this.fsw._addPathCloser(path24, closer);
31310
31244
  return false;
31311
31245
  } catch (error40) {
31312
31246
  if (this.fsw._handleError(error40)) {
31313
31247
  ready();
31314
- return path23;
31248
+ return path24;
31315
31249
  }
31316
31250
  }
31317
31251
  }
@@ -31354,26 +31288,26 @@ function createPattern(matcher) {
31354
31288
  }
31355
31289
  return () => false;
31356
31290
  }
31357
- function normalizePath(path23) {
31358
- if (typeof path23 !== "string")
31291
+ function normalizePath(path24) {
31292
+ if (typeof path24 !== "string")
31359
31293
  throw new Error("string expected");
31360
- path23 = sysPath2.normalize(path23);
31361
- path23 = path23.replace(/\\/g, "/");
31294
+ path24 = sysPath2.normalize(path24);
31295
+ path24 = path24.replace(/\\/g, "/");
31362
31296
  let prepend = false;
31363
- if (path23.startsWith("//"))
31297
+ if (path24.startsWith("//"))
31364
31298
  prepend = true;
31365
31299
  const DOUBLE_SLASH_RE2 = /\/\//;
31366
- while (path23.match(DOUBLE_SLASH_RE2))
31367
- path23 = path23.replace(DOUBLE_SLASH_RE2, "/");
31300
+ while (path24.match(DOUBLE_SLASH_RE2))
31301
+ path24 = path24.replace(DOUBLE_SLASH_RE2, "/");
31368
31302
  if (prepend)
31369
- path23 = "/" + path23;
31370
- return path23;
31303
+ path24 = "/" + path24;
31304
+ return path24;
31371
31305
  }
31372
31306
  function matchPatterns(patterns, testString, stats) {
31373
- const path23 = normalizePath(testString);
31307
+ const path24 = normalizePath(testString);
31374
31308
  for (let index = 0; index < patterns.length; index++) {
31375
31309
  const pattern = patterns[index];
31376
- if (pattern(path23, stats)) {
31310
+ if (pattern(path24, stats)) {
31377
31311
  return true;
31378
31312
  }
31379
31313
  }
@@ -31413,19 +31347,19 @@ var toUnix = (string4) => {
31413
31347
  }
31414
31348
  return str;
31415
31349
  };
31416
- var normalizePathToUnix = (path23) => toUnix(sysPath2.normalize(toUnix(path23)));
31417
- var normalizeIgnored = (cwd3 = "") => (path23) => {
31418
- if (typeof path23 === "string") {
31419
- return normalizePathToUnix(sysPath2.isAbsolute(path23) ? path23 : sysPath2.join(cwd3, path23));
31350
+ var normalizePathToUnix = (path24) => toUnix(sysPath2.normalize(toUnix(path24)));
31351
+ var normalizeIgnored = (cwd3 = "") => (path24) => {
31352
+ if (typeof path24 === "string") {
31353
+ return normalizePathToUnix(sysPath2.isAbsolute(path24) ? path24 : sysPath2.join(cwd3, path24));
31420
31354
  } else {
31421
- return path23;
31355
+ return path24;
31422
31356
  }
31423
31357
  };
31424
- var getAbsolutePath = (path23, cwd3) => {
31425
- if (sysPath2.isAbsolute(path23)) {
31426
- return path23;
31358
+ var getAbsolutePath = (path24, cwd3) => {
31359
+ if (sysPath2.isAbsolute(path24)) {
31360
+ return path24;
31427
31361
  }
31428
- return sysPath2.join(cwd3, path23);
31362
+ return sysPath2.join(cwd3, path24);
31429
31363
  };
31430
31364
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
31431
31365
  var DirEntry = class {
@@ -31480,10 +31414,10 @@ var DirEntry = class {
31480
31414
  var STAT_METHOD_F = "stat";
31481
31415
  var STAT_METHOD_L = "lstat";
31482
31416
  var WatchHelper = class {
31483
- constructor(path23, follow, fsw) {
31417
+ constructor(path24, follow, fsw) {
31484
31418
  this.fsw = fsw;
31485
- const watchPath = path23;
31486
- this.path = path23 = path23.replace(REPLACER_RE, "");
31419
+ const watchPath = path24;
31420
+ this.path = path24 = path24.replace(REPLACER_RE, "");
31487
31421
  this.watchPath = watchPath;
31488
31422
  this.fullWatchPath = sysPath2.resolve(watchPath);
31489
31423
  this.dirParts = [];
@@ -31605,20 +31539,20 @@ var FSWatcher = class extends EventEmitter2 {
31605
31539
  this._closePromise = void 0;
31606
31540
  let paths = unifyPaths(paths_);
31607
31541
  if (cwd3) {
31608
- paths = paths.map((path23) => {
31609
- const absPath = getAbsolutePath(path23, cwd3);
31542
+ paths = paths.map((path24) => {
31543
+ const absPath = getAbsolutePath(path24, cwd3);
31610
31544
  return absPath;
31611
31545
  });
31612
31546
  }
31613
- paths.forEach((path23) => {
31614
- this._removeIgnoredPath(path23);
31547
+ paths.forEach((path24) => {
31548
+ this._removeIgnoredPath(path24);
31615
31549
  });
31616
31550
  this._userIgnored = void 0;
31617
31551
  if (!this._readyCount)
31618
31552
  this._readyCount = 0;
31619
31553
  this._readyCount += paths.length;
31620
- Promise.all(paths.map(async (path23) => {
31621
- const res = await this._nodeFsHandler._addToNodeFs(path23, !_internal, void 0, 0, _origAdd);
31554
+ Promise.all(paths.map(async (path24) => {
31555
+ const res = await this._nodeFsHandler._addToNodeFs(path24, !_internal, void 0, 0, _origAdd);
31622
31556
  if (res)
31623
31557
  this._emitReady();
31624
31558
  return res;
@@ -31640,17 +31574,17 @@ var FSWatcher = class extends EventEmitter2 {
31640
31574
  return this;
31641
31575
  const paths = unifyPaths(paths_);
31642
31576
  const { cwd: cwd3 } = this.options;
31643
- paths.forEach((path23) => {
31644
- if (!sysPath2.isAbsolute(path23) && !this._closers.has(path23)) {
31577
+ paths.forEach((path24) => {
31578
+ if (!sysPath2.isAbsolute(path24) && !this._closers.has(path24)) {
31645
31579
  if (cwd3)
31646
- path23 = sysPath2.join(cwd3, path23);
31647
- path23 = sysPath2.resolve(path23);
31580
+ path24 = sysPath2.join(cwd3, path24);
31581
+ path24 = sysPath2.resolve(path24);
31648
31582
  }
31649
- this._closePath(path23);
31650
- this._addIgnoredPath(path23);
31651
- if (this._watched.has(path23)) {
31583
+ this._closePath(path24);
31584
+ this._addIgnoredPath(path24);
31585
+ if (this._watched.has(path24)) {
31652
31586
  this._addIgnoredPath({
31653
- path: path23,
31587
+ path: path24,
31654
31588
  recursive: true
31655
31589
  });
31656
31590
  }
@@ -31714,38 +31648,38 @@ var FSWatcher = class extends EventEmitter2 {
31714
31648
  * @param stats arguments to be passed with event
31715
31649
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
31716
31650
  */
31717
- async _emit(event, path23, stats) {
31651
+ async _emit(event, path24, stats) {
31718
31652
  if (this.closed)
31719
31653
  return;
31720
31654
  const opts = this.options;
31721
31655
  if (isWindows)
31722
- path23 = sysPath2.normalize(path23);
31656
+ path24 = sysPath2.normalize(path24);
31723
31657
  if (opts.cwd)
31724
- path23 = sysPath2.relative(opts.cwd, path23);
31725
- const args = [path23];
31658
+ path24 = sysPath2.relative(opts.cwd, path24);
31659
+ const args = [path24];
31726
31660
  if (stats != null)
31727
31661
  args.push(stats);
31728
31662
  const awf = opts.awaitWriteFinish;
31729
31663
  let pw;
31730
- if (awf && (pw = this._pendingWrites.get(path23))) {
31664
+ if (awf && (pw = this._pendingWrites.get(path24))) {
31731
31665
  pw.lastChange = /* @__PURE__ */ new Date();
31732
31666
  return this;
31733
31667
  }
31734
31668
  if (opts.atomic) {
31735
31669
  if (event === EVENTS.UNLINK) {
31736
- this._pendingUnlinks.set(path23, [event, ...args]);
31670
+ this._pendingUnlinks.set(path24, [event, ...args]);
31737
31671
  setTimeout(() => {
31738
- this._pendingUnlinks.forEach((entry, path24) => {
31672
+ this._pendingUnlinks.forEach((entry, path25) => {
31739
31673
  this.emit(...entry);
31740
31674
  this.emit(EVENTS.ALL, ...entry);
31741
- this._pendingUnlinks.delete(path24);
31675
+ this._pendingUnlinks.delete(path25);
31742
31676
  });
31743
31677
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
31744
31678
  return this;
31745
31679
  }
31746
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path23)) {
31680
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path24)) {
31747
31681
  event = EVENTS.CHANGE;
31748
- this._pendingUnlinks.delete(path23);
31682
+ this._pendingUnlinks.delete(path24);
31749
31683
  }
31750
31684
  }
31751
31685
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -31763,16 +31697,16 @@ var FSWatcher = class extends EventEmitter2 {
31763
31697
  this.emitWithAll(event, args);
31764
31698
  }
31765
31699
  };
31766
- this._awaitWriteFinish(path23, awf.stabilityThreshold, event, awfEmit);
31700
+ this._awaitWriteFinish(path24, awf.stabilityThreshold, event, awfEmit);
31767
31701
  return this;
31768
31702
  }
31769
31703
  if (event === EVENTS.CHANGE) {
31770
- const isThrottled = !this._throttle(EVENTS.CHANGE, path23, 50);
31704
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path24, 50);
31771
31705
  if (isThrottled)
31772
31706
  return this;
31773
31707
  }
31774
31708
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
31775
- const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path23) : path23;
31709
+ const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path24) : path24;
31776
31710
  let stats2;
31777
31711
  try {
31778
31712
  stats2 = await stat3(fullPath);
@@ -31803,23 +31737,23 @@ var FSWatcher = class extends EventEmitter2 {
31803
31737
  * @param timeout duration of time to suppress duplicate actions
31804
31738
  * @returns tracking object or false if action should be suppressed
31805
31739
  */
31806
- _throttle(actionType, path23, timeout) {
31740
+ _throttle(actionType, path24, timeout) {
31807
31741
  if (!this._throttled.has(actionType)) {
31808
31742
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
31809
31743
  }
31810
31744
  const action = this._throttled.get(actionType);
31811
31745
  if (!action)
31812
31746
  throw new Error("invalid throttle");
31813
- const actionPath = action.get(path23);
31747
+ const actionPath = action.get(path24);
31814
31748
  if (actionPath) {
31815
31749
  actionPath.count++;
31816
31750
  return false;
31817
31751
  }
31818
31752
  let timeoutObject;
31819
31753
  const clear = () => {
31820
- const item = action.get(path23);
31754
+ const item = action.get(path24);
31821
31755
  const count = item ? item.count : 0;
31822
- action.delete(path23);
31756
+ action.delete(path24);
31823
31757
  clearTimeout(timeoutObject);
31824
31758
  if (item)
31825
31759
  clearTimeout(item.timeoutObject);
@@ -31827,7 +31761,7 @@ var FSWatcher = class extends EventEmitter2 {
31827
31761
  };
31828
31762
  timeoutObject = setTimeout(clear, timeout);
31829
31763
  const thr = { timeoutObject, clear, count: 0 };
31830
- action.set(path23, thr);
31764
+ action.set(path24, thr);
31831
31765
  return thr;
31832
31766
  }
31833
31767
  _incrReadyCount() {
@@ -31841,44 +31775,44 @@ var FSWatcher = class extends EventEmitter2 {
31841
31775
  * @param event
31842
31776
  * @param awfEmit Callback to be called when ready for event to be emitted.
31843
31777
  */
31844
- _awaitWriteFinish(path23, threshold, event, awfEmit) {
31778
+ _awaitWriteFinish(path24, threshold, event, awfEmit) {
31845
31779
  const awf = this.options.awaitWriteFinish;
31846
31780
  if (typeof awf !== "object")
31847
31781
  return;
31848
31782
  const pollInterval = awf.pollInterval;
31849
31783
  let timeoutHandler;
31850
- let fullPath = path23;
31851
- if (this.options.cwd && !sysPath2.isAbsolute(path23)) {
31852
- fullPath = sysPath2.join(this.options.cwd, path23);
31784
+ let fullPath = path24;
31785
+ if (this.options.cwd && !sysPath2.isAbsolute(path24)) {
31786
+ fullPath = sysPath2.join(this.options.cwd, path24);
31853
31787
  }
31854
31788
  const now = /* @__PURE__ */ new Date();
31855
31789
  const writes = this._pendingWrites;
31856
31790
  function awaitWriteFinishFn(prevStat) {
31857
31791
  statcb(fullPath, (err, curStat) => {
31858
- if (err || !writes.has(path23)) {
31792
+ if (err || !writes.has(path24)) {
31859
31793
  if (err && err.code !== "ENOENT")
31860
31794
  awfEmit(err);
31861
31795
  return;
31862
31796
  }
31863
31797
  const now2 = Number(/* @__PURE__ */ new Date());
31864
31798
  if (prevStat && curStat.size !== prevStat.size) {
31865
- writes.get(path23).lastChange = now2;
31799
+ writes.get(path24).lastChange = now2;
31866
31800
  }
31867
- const pw = writes.get(path23);
31801
+ const pw = writes.get(path24);
31868
31802
  const df = now2 - pw.lastChange;
31869
31803
  if (df >= threshold) {
31870
- writes.delete(path23);
31804
+ writes.delete(path24);
31871
31805
  awfEmit(void 0, curStat);
31872
31806
  } else {
31873
31807
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
31874
31808
  }
31875
31809
  });
31876
31810
  }
31877
- if (!writes.has(path23)) {
31878
- writes.set(path23, {
31811
+ if (!writes.has(path24)) {
31812
+ writes.set(path24, {
31879
31813
  lastChange: now,
31880
31814
  cancelWait: () => {
31881
- writes.delete(path23);
31815
+ writes.delete(path24);
31882
31816
  clearTimeout(timeoutHandler);
31883
31817
  return event;
31884
31818
  }
@@ -31889,8 +31823,8 @@ var FSWatcher = class extends EventEmitter2 {
31889
31823
  /**
31890
31824
  * Determines whether user has asked to ignore this path.
31891
31825
  */
31892
- _isIgnored(path23, stats) {
31893
- if (this.options.atomic && DOT_RE.test(path23))
31826
+ _isIgnored(path24, stats) {
31827
+ if (this.options.atomic && DOT_RE.test(path24))
31894
31828
  return true;
31895
31829
  if (!this._userIgnored) {
31896
31830
  const { cwd: cwd3 } = this.options;
@@ -31900,17 +31834,17 @@ var FSWatcher = class extends EventEmitter2 {
31900
31834
  const list = [...ignoredPaths.map(normalizeIgnored(cwd3)), ...ignored];
31901
31835
  this._userIgnored = anymatch(list, void 0);
31902
31836
  }
31903
- return this._userIgnored(path23, stats);
31837
+ return this._userIgnored(path24, stats);
31904
31838
  }
31905
- _isntIgnored(path23, stat4) {
31906
- return !this._isIgnored(path23, stat4);
31839
+ _isntIgnored(path24, stat4) {
31840
+ return !this._isIgnored(path24, stat4);
31907
31841
  }
31908
31842
  /**
31909
31843
  * Provides a set of common helpers and properties relating to symlink handling.
31910
31844
  * @param path file or directory pattern being watched
31911
31845
  */
31912
- _getWatchHelpers(path23) {
31913
- return new WatchHelper(path23, this.options.followSymlinks, this);
31846
+ _getWatchHelpers(path24) {
31847
+ return new WatchHelper(path24, this.options.followSymlinks, this);
31914
31848
  }
31915
31849
  // Directory helpers
31916
31850
  // -----------------
@@ -31942,63 +31876,63 @@ var FSWatcher = class extends EventEmitter2 {
31942
31876
  * @param item base path of item/directory
31943
31877
  */
31944
31878
  _remove(directory, item, isDirectory) {
31945
- const path23 = sysPath2.join(directory, item);
31946
- const fullPath = sysPath2.resolve(path23);
31947
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path23) || this._watched.has(fullPath);
31948
- if (!this._throttle("remove", path23, 100))
31879
+ const path24 = sysPath2.join(directory, item);
31880
+ const fullPath = sysPath2.resolve(path24);
31881
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path24) || this._watched.has(fullPath);
31882
+ if (!this._throttle("remove", path24, 100))
31949
31883
  return;
31950
31884
  if (!isDirectory && this._watched.size === 1) {
31951
31885
  this.add(directory, item, true);
31952
31886
  }
31953
- const wp = this._getWatchedDir(path23);
31887
+ const wp = this._getWatchedDir(path24);
31954
31888
  const nestedDirectoryChildren = wp.getChildren();
31955
- nestedDirectoryChildren.forEach((nested) => this._remove(path23, nested));
31889
+ nestedDirectoryChildren.forEach((nested) => this._remove(path24, nested));
31956
31890
  const parent = this._getWatchedDir(directory);
31957
31891
  const wasTracked = parent.has(item);
31958
31892
  parent.remove(item);
31959
31893
  if (this._symlinkPaths.has(fullPath)) {
31960
31894
  this._symlinkPaths.delete(fullPath);
31961
31895
  }
31962
- let relPath = path23;
31896
+ let relPath = path24;
31963
31897
  if (this.options.cwd)
31964
- relPath = sysPath2.relative(this.options.cwd, path23);
31898
+ relPath = sysPath2.relative(this.options.cwd, path24);
31965
31899
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
31966
31900
  const event = this._pendingWrites.get(relPath).cancelWait();
31967
31901
  if (event === EVENTS.ADD)
31968
31902
  return;
31969
31903
  }
31970
- this._watched.delete(path23);
31904
+ this._watched.delete(path24);
31971
31905
  this._watched.delete(fullPath);
31972
31906
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
31973
- if (wasTracked && !this._isIgnored(path23))
31974
- this._emit(eventName, path23);
31975
- this._closePath(path23);
31907
+ if (wasTracked && !this._isIgnored(path24))
31908
+ this._emit(eventName, path24);
31909
+ this._closePath(path24);
31976
31910
  }
31977
31911
  /**
31978
31912
  * Closes all watchers for a path
31979
31913
  */
31980
- _closePath(path23) {
31981
- this._closeFile(path23);
31982
- const dir = sysPath2.dirname(path23);
31983
- this._getWatchedDir(dir).remove(sysPath2.basename(path23));
31914
+ _closePath(path24) {
31915
+ this._closeFile(path24);
31916
+ const dir = sysPath2.dirname(path24);
31917
+ this._getWatchedDir(dir).remove(sysPath2.basename(path24));
31984
31918
  }
31985
31919
  /**
31986
31920
  * Closes only file-specific watchers
31987
31921
  */
31988
- _closeFile(path23) {
31989
- const closers = this._closers.get(path23);
31922
+ _closeFile(path24) {
31923
+ const closers = this._closers.get(path24);
31990
31924
  if (!closers)
31991
31925
  return;
31992
31926
  closers.forEach((closer) => closer());
31993
- this._closers.delete(path23);
31927
+ this._closers.delete(path24);
31994
31928
  }
31995
- _addPathCloser(path23, closer) {
31929
+ _addPathCloser(path24, closer) {
31996
31930
  if (!closer)
31997
31931
  return;
31998
- let list = this._closers.get(path23);
31932
+ let list = this._closers.get(path24);
31999
31933
  if (!list) {
32000
31934
  list = [];
32001
- this._closers.set(path23, list);
31935
+ this._closers.set(path24, list);
32002
31936
  }
32003
31937
  list.push(closer);
32004
31938
  }
@@ -32037,7 +31971,7 @@ function shouldIgnoreRelative(rel) {
32037
31971
  return false;
32038
31972
  }
32039
31973
  function startFileIndexWatcher(cwd3 = process.cwd()) {
32040
- const resolved = path22.resolve(cwd3);
31974
+ const resolved = path21.resolve(cwd3);
32041
31975
  let timer = null;
32042
31976
  const runRebuild = () => {
32043
31977
  try {
@@ -32058,7 +31992,7 @@ function startFileIndexWatcher(cwd3 = process.cwd()) {
32058
31992
  ignoreInitial: true,
32059
31993
  persistent: true,
32060
31994
  ignored: (p) => {
32061
- const rel = path22.isAbsolute(p) ? path22.relative(resolved, p).replace(/\\/g, "/") : p.replace(/\\/g, "/");
31995
+ const rel = path21.isAbsolute(p) ? path21.relative(resolved, p).replace(/\\/g, "/") : p.replace(/\\/g, "/");
32062
31996
  return shouldIgnoreRelative(rel || ".");
32063
31997
  },
32064
31998
  awaitWriteFinish: { stabilityThreshold: 250, pollInterval: 100 }
@@ -32077,9 +32011,1149 @@ function startFileIndexWatcher(cwd3 = process.cwd()) {
32077
32011
  };
32078
32012
  }
32079
32013
 
32014
+ // src/dev-servers/manager/dev-server-manager.ts
32015
+ import { rm } from "node:fs/promises";
32016
+
32017
+ // src/dev-servers/process/send-server-status.ts
32018
+ function sendDevServerStatus(getWs, serverId, status, options) {
32019
+ const payload = { type: "server_status", serverId, status };
32020
+ if (options?.detail) payload.detail = options.detail;
32021
+ if (options?.tails) {
32022
+ payload.stdoutTail = options.tails.stdout;
32023
+ payload.stderrTail = options.tails.stderr;
32024
+ }
32025
+ setImmediate(() => {
32026
+ const ws = getWs();
32027
+ if (!ws) return;
32028
+ sendWsMessage(ws, payload);
32029
+ });
32030
+ }
32031
+
32032
+ // src/dev-servers/process/terminate-child-process.ts
32033
+ async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
32034
+ const exited = new Promise((resolve16) => {
32035
+ proc.once("exit", () => resolve16());
32036
+ });
32037
+ log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"})\u2026`);
32038
+ try {
32039
+ proc.kill("SIGTERM");
32040
+ } catch {
32041
+ }
32042
+ await Promise.race([exited, new Promise((resolve16) => setTimeout(resolve16, graceMs))]);
32043
+ }
32044
+ function forceKillChild(proc, log2, shortId, graceMs) {
32045
+ log2(`[dev-server] ${shortId} did not exit within ${graceMs}ms; sending SIGKILL (pid=${proc.pid ?? "?"})\u2026`);
32046
+ proc.removeAllListeners();
32047
+ try {
32048
+ proc.kill("SIGKILL");
32049
+ } catch {
32050
+ }
32051
+ }
32052
+
32053
+ // src/dev-servers/process/wire-dev-server-child-process.ts
32054
+ import fs16 from "node:fs";
32055
+
32056
+ // src/dev-servers/manager/forward-pipe.ts
32057
+ function forwardChildPipe(childReadable, terminal, onData) {
32058
+ if (!childReadable) return;
32059
+ childReadable.on("data", (chunk) => {
32060
+ onData(chunk);
32061
+ if (!terminal || terminal.writableEnded) return;
32062
+ const ok = terminal.write(chunk);
32063
+ if (!ok) {
32064
+ childReadable.pause();
32065
+ terminal.once("drain", () => {
32066
+ childReadable.resume();
32067
+ });
32068
+ }
32069
+ });
32070
+ }
32071
+
32072
+ // src/dev-servers/process/wire-dev-server-child-process.ts
32073
+ function wireDevServerChildProcess(d) {
32074
+ const { proc, serverId, title } = d;
32075
+ forwardChildPipe(proc.stdout, null, (chunk) => {
32076
+ d.stdoutTail.push(chunk);
32077
+ d.pushRemoteLogChunk(serverId, "stdout", chunk);
32078
+ });
32079
+ forwardChildPipe(proc.stderr, null, (chunk) => {
32080
+ d.stderrTail.push(chunk);
32081
+ d.pushRemoteLogChunk(serverId, "stderr", chunk);
32082
+ });
32083
+ if (d.mergedLogPath && d.mergedCleanupDir) {
32084
+ const pollIv = setInterval(() => {
32085
+ if ((d.getSpawnGeneration() ?? 0) !== d.scheduledGen) {
32086
+ clearInterval(pollIv);
32087
+ d.setPollInterval(void 0);
32088
+ return;
32089
+ }
32090
+ fs16.readFile(d.mergedLogPath, (err, buf) => {
32091
+ if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
32092
+ if (buf.length <= d.mergedReadPos.value) return;
32093
+ const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
32094
+ d.mergedReadPos.value = buf.length;
32095
+ if (chunk.length === 0) return;
32096
+ d.stdoutTail.push(chunk);
32097
+ d.pushRemoteLogChunk(serverId, "stdout", chunk);
32098
+ });
32099
+ }, 100);
32100
+ d.setPollInterval(pollIv);
32101
+ }
32102
+ proc.on("exit", (code, signal) => {
32103
+ const poll = d.getPollInterval();
32104
+ if (poll) {
32105
+ clearInterval(poll);
32106
+ d.setPollInterval(void 0);
32107
+ }
32108
+ const cleanupDir = d.mergedCleanupDir;
32109
+ const mergedPath = d.mergedLogPath;
32110
+ const finishExit = () => {
32111
+ if (cleanupDir) {
32112
+ d.rmMergedCleanupDir(cleanupDir);
32113
+ }
32114
+ if (signal) {
32115
+ d.log(`[dev-server] ${title} stopped (signal ${String(signal)})`);
32116
+ } else if (code !== null && code !== 0) {
32117
+ const errTail = d.stderrTail.getTail().slice(-3).join("\n");
32118
+ d.log(`[dev-server] ${title} exited with code ${code}${errTail ? `
32119
+ ${errTail}` : ""}`);
32120
+ }
32121
+ d.detachProcessFromManager();
32122
+ const tails = { stdout: d.stdoutTail.getTail(), stderr: d.stderrTail.getTail() };
32123
+ d.clearTailBuffers();
32124
+ const detail = signal ? `signal=${String(signal)}` : code != null && code !== 0 ? `exit=${code}` : void 0;
32125
+ d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
32126
+ };
32127
+ if (mergedPath) {
32128
+ fs16.readFile(mergedPath, (err, buf) => {
32129
+ if (!err && buf.length > d.mergedReadPos.value) {
32130
+ const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
32131
+ if (chunk.length > 0) {
32132
+ d.stdoutTail.push(chunk);
32133
+ d.pushRemoteLogChunk(serverId, "stdout", chunk);
32134
+ }
32135
+ }
32136
+ finishExit();
32137
+ });
32138
+ } else {
32139
+ finishExit();
32140
+ }
32141
+ });
32142
+ proc.on("error", (err) => {
32143
+ const poll = d.getPollInterval();
32144
+ if (poll) {
32145
+ clearInterval(poll);
32146
+ d.setPollInterval(void 0);
32147
+ }
32148
+ const cleanupDir = d.mergedCleanupDir;
32149
+ if (cleanupDir) {
32150
+ d.rmMergedCleanupDir(cleanupDir);
32151
+ }
32152
+ d.detachProcessFromManager();
32153
+ const tails = { stdout: d.stdoutTail.getTail(), stderr: d.stderrTail.getTail() };
32154
+ d.clearTailBuffers();
32155
+ const msg = err instanceof Error ? err.message : String(err);
32156
+ const errno = typeof err === "object" && err && "code" in err ? String(err.code) : "";
32157
+ d.log(`[dev-server] ${title} process error: ${msg}${errno ? ` (${errno})` : ""}`);
32158
+ d.sendStatus("error", msg, tails);
32159
+ });
32160
+ }
32161
+
32162
+ // src/dev-servers/manager/dev-server-env.ts
32163
+ function substituteCommand(cmd, env) {
32164
+ return cmd.replace(/\$\{([^}]+)\}/g, (_, key) => env[key] != null ? String(env[key]) : "");
32165
+ }
32166
+ function envForSpawn(base, userEnv, ports) {
32167
+ const userKeys = new Set(
32168
+ userEnv.map((e) => e.name.trim()).filter((name) => Boolean(name))
32169
+ );
32170
+ const out = {};
32171
+ for (const key of Object.keys(base)) {
32172
+ const v = base[key];
32173
+ if (typeof v === "string") {
32174
+ out[key] = v;
32175
+ }
32176
+ }
32177
+ for (const { name, value } of userEnv) {
32178
+ if (name) out[name] = value;
32179
+ }
32180
+ ports.forEach((p, i) => {
32181
+ out[`PORT.${i}`] = String(p);
32182
+ });
32183
+ if (ports.length > 0) {
32184
+ out.PORTS = ports.join(",");
32185
+ }
32186
+ if (!userKeys.has("NO_COLOR")) {
32187
+ delete out.NO_COLOR;
32188
+ }
32189
+ if (out.FORCE_COLOR === void 0) {
32190
+ out.FORCE_COLOR = "1";
32191
+ }
32192
+ if (out.CLICOLOR_FORCE === void 0) {
32193
+ out.CLICOLOR_FORCE = "1";
32194
+ }
32195
+ return out;
32196
+ }
32197
+
32198
+ // src/dev-servers/manager/parse-config.ts
32199
+ function parseDevServerDefs(servers) {
32200
+ const out = [];
32201
+ if (!Array.isArray(servers)) return out;
32202
+ for (const s of servers) {
32203
+ if (!s || typeof s !== "object") continue;
32204
+ const o = s;
32205
+ const serverId = typeof o.serverId === "string" ? o.serverId : "";
32206
+ if (!serverId) continue;
32207
+ const envRaw = o.env;
32208
+ const env = Array.isArray(envRaw) ? envRaw.map((e) => {
32209
+ if (!e || typeof e !== "object") return null;
32210
+ const r = e;
32211
+ return {
32212
+ name: typeof r.name === "string" ? r.name : "",
32213
+ value: typeof r.value === "string" ? r.value : ""
32214
+ };
32215
+ }).filter((e) => e != null && e.name.length > 0) : [];
32216
+ const portsRaw = o.ports;
32217
+ const ports = Array.isArray(portsRaw) ? portsRaw.filter((p) => typeof p === "number" && Number.isInteger(p) && p > 0 && p < 65536) : [];
32218
+ out.push({
32219
+ serverId,
32220
+ name: typeof o.name === "string" ? o.name : "",
32221
+ command: typeof o.command === "string" ? o.command : "",
32222
+ env,
32223
+ ports
32224
+ });
32225
+ }
32226
+ return out;
32227
+ }
32228
+
32229
+ // src/dev-servers/manager/shell-spawn/utils.ts
32230
+ import fs17 from "node:fs";
32231
+ function isSpawnEbadf(e) {
32232
+ return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
32233
+ }
32234
+ function rmDirQuiet(dir) {
32235
+ try {
32236
+ fs17.rmSync(dir, { recursive: true, force: true });
32237
+ } catch {
32238
+ }
32239
+ }
32240
+ var cachedDevNullReadFd;
32241
+ function devNullReadFd() {
32242
+ if (cachedDevNullReadFd === void 0) {
32243
+ const devPath = process.platform === "win32" ? "nul" : "/dev/null";
32244
+ cachedDevNullReadFd = fs17.openSync(devPath, "r");
32245
+ }
32246
+ return cachedDevNullReadFd;
32247
+ }
32248
+ function pipedStdoutStderrFor(attemptStdio) {
32249
+ return attemptStdio !== "inherit" && Array.isArray(attemptStdio) && attemptStdio[1] === "pipe" && attemptStdio[2] === "pipe";
32250
+ }
32251
+
32252
+ // src/dev-servers/manager/shell-spawn/try-spawn-piped-via-sh.ts
32253
+ import { spawn as spawn5 } from "node:child_process";
32254
+ function trySpawnPipedViaSh(command, env, cwd3, signal) {
32255
+ const attempts = [
32256
+ { stdio: [devNullReadFd(), "pipe", "pipe"], endStdin: false },
32257
+ { stdio: ["ignore", "pipe", "pipe"], endStdin: true },
32258
+ { stdio: ["pipe", "pipe", "pipe"], endStdin: true }
32259
+ ];
32260
+ let lastErr;
32261
+ for (let i = 0; i < attempts.length; i++) {
32262
+ const attempt = attempts[i];
32263
+ const opts = {
32264
+ env,
32265
+ cwd: cwd3,
32266
+ stdio: attempt.stdio,
32267
+ ...signal ? { signal } : {}
32268
+ };
32269
+ try {
32270
+ let proc;
32271
+ if (process.platform === "win32") {
32272
+ opts.windowsHide = true;
32273
+ const com = process.env.ComSpec || "cmd.exe";
32274
+ proc = spawn5(com, ["/d", "/s", "/c", command], opts);
32275
+ } else {
32276
+ proc = spawn5("/bin/sh", ["-c", command], opts);
32277
+ }
32278
+ if (attempt.endStdin) {
32279
+ proc.stdin?.end();
32280
+ }
32281
+ return {
32282
+ ok: true,
32283
+ result: { proc, pipedStdoutStderr: pipedStdoutStderrFor(attempt.stdio) }
32284
+ };
32285
+ } catch (e) {
32286
+ lastErr = e;
32287
+ if (!isSpawnEbadf(e)) throw e;
32288
+ if (i < attempts.length - 1) continue;
32289
+ break;
32290
+ }
32291
+ }
32292
+ return { ok: false, lastErr };
32293
+ }
32294
+
32295
+ // src/dev-servers/manager/shell-spawn/try-spawn-shell-true-piped.ts
32296
+ import { spawn as spawn6 } from "node:child_process";
32297
+ function trySpawnShellTruePiped(command, env, cwd3, devNullFd, signal) {
32298
+ try {
32299
+ const opts = {
32300
+ env,
32301
+ cwd: cwd3,
32302
+ stdio: [devNullFd, "pipe", "pipe"],
32303
+ shell: true,
32304
+ ...signal ? { signal } : {}
32305
+ };
32306
+ if (process.platform === "win32") {
32307
+ opts.windowsHide = true;
32308
+ }
32309
+ return spawn6(command, opts);
32310
+ } catch (e) {
32311
+ if (isSpawnEbadf(e)) return null;
32312
+ throw e;
32313
+ }
32314
+ }
32315
+
32316
+ // src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
32317
+ import { spawn as spawn7 } from "node:child_process";
32318
+ import fs18 from "node:fs";
32319
+ import { tmpdir } from "node:os";
32320
+ import path22 from "node:path";
32321
+ function trySpawnMergedLogFile(command, env, cwd3, signal) {
32322
+ const tmpRoot = fs18.mkdtempSync(path22.join(tmpdir(), "ba-devsrv-log-"));
32323
+ const logPath = path22.join(tmpRoot, "combined.log");
32324
+ let logFd;
32325
+ try {
32326
+ logFd = fs18.openSync(logPath, "a");
32327
+ } catch {
32328
+ rmDirQuiet(tmpRoot);
32329
+ return null;
32330
+ }
32331
+ const stdio = ["ignore", logFd, logFd];
32332
+ try {
32333
+ let proc;
32334
+ if (process.platform === "win32") {
32335
+ proc = spawn7(process.env.ComSpec || "cmd.exe", ["/d", "/s", "/c", command], {
32336
+ env,
32337
+ cwd: cwd3,
32338
+ stdio,
32339
+ windowsHide: true,
32340
+ ...signal ? { signal } : {}
32341
+ });
32342
+ } else {
32343
+ proc = spawn7("/bin/sh", ["-c", command], { env, cwd: cwd3, stdio, ...signal ? { signal } : {} });
32344
+ }
32345
+ fs18.closeSync(logFd);
32346
+ return {
32347
+ proc,
32348
+ pipedStdoutStderr: true,
32349
+ mergedLogPath: logPath,
32350
+ mergedLogCleanupDir: tmpRoot
32351
+ };
32352
+ } catch (e) {
32353
+ try {
32354
+ fs18.closeSync(logFd);
32355
+ } catch {
32356
+ }
32357
+ rmDirQuiet(tmpRoot);
32358
+ if (isSpawnEbadf(e)) return null;
32359
+ throw e;
32360
+ }
32361
+ }
32362
+
32363
+ // src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
32364
+ import { spawn as spawn8 } from "node:child_process";
32365
+ import fs19 from "node:fs";
32366
+ import { tmpdir as tmpdir2 } from "node:os";
32367
+ import path23 from "node:path";
32368
+ function shSingleQuote(s) {
32369
+ return `'${s.replace(/'/g, `'\\''`)}'`;
32370
+ }
32371
+ function trySpawnShellScriptLogRedirectUnix(command, env, cwd3, signal) {
32372
+ const tmpRoot = fs19.mkdtempSync(path23.join(tmpdir2(), "ba-devsrv-sh-"));
32373
+ const logPath = path23.join(tmpRoot, "combined.log");
32374
+ const innerPath = path23.join(tmpRoot, "_cmd.sh");
32375
+ const runnerPath = path23.join(tmpRoot, "_run.sh");
32376
+ try {
32377
+ fs19.writeFileSync(innerPath, `#!/bin/sh
32378
+ ${command}
32379
+ `);
32380
+ fs19.writeFileSync(
32381
+ runnerPath,
32382
+ `#!/bin/sh
32383
+ cd ${shSingleQuote(cwd3)}
32384
+ /bin/sh ${shSingleQuote(innerPath)} >>${shSingleQuote(logPath)} 2>&1
32385
+ `
32386
+ );
32387
+ const proc = spawn8("/bin/sh", [runnerPath], {
32388
+ env,
32389
+ cwd: tmpRoot,
32390
+ stdio: "ignore",
32391
+ ...signal ? { signal } : {}
32392
+ });
32393
+ return {
32394
+ proc,
32395
+ pipedStdoutStderr: true,
32396
+ mergedLogPath: logPath,
32397
+ mergedLogCleanupDir: tmpRoot
32398
+ };
32399
+ } catch (e) {
32400
+ rmDirQuiet(tmpRoot);
32401
+ if (isSpawnEbadf(e)) return null;
32402
+ throw e;
32403
+ }
32404
+ }
32405
+ function trySpawnShellScriptLogRedirectWin(command, env, cwd3, signal) {
32406
+ const tmpRoot = fs19.mkdtempSync(path23.join(tmpdir2(), "ba-devsrv-sh-"));
32407
+ const logPath = path23.join(tmpRoot, "combined.log");
32408
+ const runnerPath = path23.join(tmpRoot, "_run.bat");
32409
+ const q = (p) => `"${p.replace(/"/g, '""')}"`;
32410
+ const com = process.env.ComSpec || "cmd.exe";
32411
+ try {
32412
+ fs19.writeFileSync(
32413
+ runnerPath,
32414
+ `@ECHO OFF\r
32415
+ CD /D ${q(cwd3)}\r
32416
+ ${command} >> ${q(logPath)} 2>&1\r
32417
+ `
32418
+ );
32419
+ const proc = spawn8(com, ["/d", "/s", "/c", q(runnerPath)], {
32420
+ env,
32421
+ cwd: tmpRoot,
32422
+ stdio: "ignore",
32423
+ windowsHide: true,
32424
+ ...signal ? { signal } : {}
32425
+ });
32426
+ return {
32427
+ proc,
32428
+ pipedStdoutStderr: true,
32429
+ mergedLogPath: logPath,
32430
+ mergedLogCleanupDir: tmpRoot
32431
+ };
32432
+ } catch (e) {
32433
+ rmDirQuiet(tmpRoot);
32434
+ if (isSpawnEbadf(e)) return null;
32435
+ throw e;
32436
+ }
32437
+ }
32438
+
32439
+ // src/dev-servers/manager/shell-spawn/try-spawn-inherit.ts
32440
+ import { spawn as spawn9 } from "node:child_process";
32441
+ function trySpawnInheritStdio(command, env, cwd3, signal) {
32442
+ const opts = {
32443
+ env,
32444
+ cwd: cwd3,
32445
+ stdio: "inherit",
32446
+ ...signal ? { signal } : {}
32447
+ };
32448
+ let proc;
32449
+ if (process.platform === "win32") {
32450
+ opts.windowsHide = true;
32451
+ const com = process.env.ComSpec || "cmd.exe";
32452
+ proc = spawn9(com, ["/d", "/s", "/c", command], opts);
32453
+ } else {
32454
+ proc = spawn9("/bin/sh", ["-c", command], opts);
32455
+ }
32456
+ return { proc, pipedStdoutStderr: false };
32457
+ }
32458
+
32459
+ // src/dev-servers/manager/shell-spawn/shell-spawn.ts
32460
+ function shellSpawn(command, env, cwd3, options) {
32461
+ const signal = options?.signal;
32462
+ const piped = trySpawnPipedViaSh(command, env, cwd3, signal);
32463
+ if (piped.ok) {
32464
+ return piped.result;
32465
+ }
32466
+ let lastErr = piped.lastErr;
32467
+ const shellTrueProc = trySpawnShellTruePiped(command, env, cwd3, devNullReadFd(), signal);
32468
+ if (shellTrueProc) {
32469
+ return { proc: shellTrueProc, pipedStdoutStderr: true };
32470
+ }
32471
+ const fileCapture = trySpawnMergedLogFile(command, env, cwd3, signal);
32472
+ if (fileCapture) {
32473
+ return fileCapture;
32474
+ }
32475
+ const scriptCapture = process.platform === "win32" ? trySpawnShellScriptLogRedirectWin(command, env, cwd3, signal) : trySpawnShellScriptLogRedirectUnix(command, env, cwd3, signal);
32476
+ if (scriptCapture) {
32477
+ return scriptCapture;
32478
+ }
32479
+ try {
32480
+ return trySpawnInheritStdio(command, env, cwd3, signal);
32481
+ } catch (e) {
32482
+ throw lastErr instanceof Error ? lastErr : e instanceof Error ? e : new Error(String(e));
32483
+ }
32484
+ }
32485
+
32486
+ // src/dev-servers/manager/stream-tail.ts
32487
+ var MAX_TAIL_LINES = 80;
32488
+ var MAX_LINE_CHARS = 8192;
32489
+ var MAX_PENDING_CHARS = 65536;
32490
+ function truncateLine(s) {
32491
+ return s.length > MAX_LINE_CHARS ? `${s.slice(0, MAX_LINE_CHARS)}\u2026` : s;
32492
+ }
32493
+ var StreamTail = class {
32494
+ pending = "";
32495
+ lines = [];
32496
+ push(chunk) {
32497
+ this.pending += chunk.toString("utf8");
32498
+ if (this.pending.length > MAX_PENDING_CHARS) {
32499
+ this.pending = this.pending.slice(-MAX_PENDING_CHARS);
32500
+ }
32501
+ const parts = this.pending.split("\n");
32502
+ this.pending = parts.pop() ?? "";
32503
+ if (this.pending.length > MAX_PENDING_CHARS) {
32504
+ this.pending = this.pending.slice(-MAX_PENDING_CHARS);
32505
+ }
32506
+ for (const line of parts) {
32507
+ this.lines.push(truncateLine(line));
32508
+ if (this.lines.length > MAX_TAIL_LINES) this.lines.shift();
32509
+ }
32510
+ }
32511
+ /** Up to last 80 lines; any incomplete final line (no trailing newline) is included as the last entry. */
32512
+ getTail() {
32513
+ const out = [...this.lines];
32514
+ if (this.pending !== "") {
32515
+ out.push(truncateLine(this.pending));
32516
+ }
32517
+ if (out.length > MAX_TAIL_LINES) return out.slice(-MAX_TAIL_LINES);
32518
+ return out;
32519
+ }
32520
+ };
32521
+
32522
+ // src/dev-servers/manager/dev-server-manager.ts
32523
+ var BRIDGE_SHUTDOWN_GRACE_MS = 8e3;
32524
+ var emptyTails = () => ({ stdout: [], stderr: [] });
32525
+ var DevServerManager = class {
32526
+ defsById = /* @__PURE__ */ new Map();
32527
+ processes = /* @__PURE__ */ new Map();
32528
+ streamTailsByServerId = /* @__PURE__ */ new Map();
32529
+ spawnGenerationByServerId = /* @__PURE__ */ new Map();
32530
+ pipedCaptureByServerId = /* @__PURE__ */ new Map();
32531
+ logViewerRefCountByServerId = /* @__PURE__ */ new Map();
32532
+ firehoseSend = null;
32533
+ mergedLogPollByServerId = /* @__PURE__ */ new Map();
32534
+ mergedLogCleanupDirByServerId = /* @__PURE__ */ new Map();
32535
+ abortControllersByServerId = /* @__PURE__ */ new Map();
32536
+ getWs;
32537
+ log;
32538
+ constructor(options) {
32539
+ this.getWs = options.getWs;
32540
+ this.log = options.log;
32541
+ }
32542
+ attachFirehose(send) {
32543
+ this.firehoseSend = send;
32544
+ }
32545
+ detachFirehose() {
32546
+ this.firehoseSend = null;
32547
+ this.logViewerRefCountByServerId.clear();
32548
+ }
32549
+ handleFirehoseLogViewerOpen(serverId, _viewerId) {
32550
+ const next = (this.logViewerRefCountByServerId.get(serverId) ?? 0) + 1;
32551
+ this.logViewerRefCountByServerId.set(serverId, next);
32552
+ this.sendSnapshotToFirehose(serverId, _viewerId);
32553
+ }
32554
+ handleFirehoseLogViewerClose(serverId, _viewerId) {
32555
+ const n = (this.logViewerRefCountByServerId.get(serverId) ?? 0) - 1;
32556
+ if (n <= 0) this.logViewerRefCountByServerId.delete(serverId);
32557
+ else this.logViewerRefCountByServerId.set(serverId, n);
32558
+ }
32559
+ sendSnapshotToFirehose(serverId, viewerId) {
32560
+ const tails = this.streamTailsByServerId.get(serverId);
32561
+ const payload = {
32562
+ type: "log_snapshot",
32563
+ serverId,
32564
+ viewerId,
32565
+ stdoutTail: tails?.stdout.getTail() ?? [],
32566
+ stderrTail: tails?.stderr.getTail() ?? []
32567
+ };
32568
+ setImmediate(() => {
32569
+ const send = this.firehoseSend;
32570
+ if (!send) return;
32571
+ send(payload);
32572
+ });
32573
+ }
32574
+ pushRemoteLogChunk(serverId, stream, chunk) {
32575
+ if ((this.logViewerRefCountByServerId.get(serverId) ?? 0) <= 0) return;
32576
+ if (!this.pipedCaptureByServerId.get(serverId)) return;
32577
+ const send = this.firehoseSend;
32578
+ if (!send) return;
32579
+ const text = chunk.toString("utf8");
32580
+ setImmediate(() => {
32581
+ if (!this.firehoseSend) return;
32582
+ this.firehoseSend({
32583
+ type: "log_chunk",
32584
+ serverId,
32585
+ stream,
32586
+ text
32587
+ });
32588
+ });
32589
+ }
32590
+ applyConfig(servers) {
32591
+ this.defsById.clear();
32592
+ for (const d of parseDevServerDefs(servers)) {
32593
+ this.defsById.set(d.serverId, d);
32594
+ }
32595
+ }
32596
+ snapshotTails(serverId) {
32597
+ const t = this.streamTailsByServerId.get(serverId);
32598
+ if (!t) return { stdout: [], stderr: [] };
32599
+ return { stdout: t.stdout.getTail(), stderr: t.stderr.getTail() };
32600
+ }
32601
+ clearTails(serverId) {
32602
+ this.streamTailsByServerId.delete(serverId);
32603
+ }
32604
+ sendStatus(serverId, status, detail, tails) {
32605
+ sendDevServerStatus(this.getWs, serverId, status, { detail, tails });
32606
+ }
32607
+ bumpGeneration(serverId) {
32608
+ const nextGen = (this.spawnGenerationByServerId.get(serverId) ?? 0) + 1;
32609
+ this.spawnGenerationByServerId.set(serverId, nextGen);
32610
+ }
32611
+ clearPoll(serverId) {
32612
+ const poll = this.mergedLogPollByServerId.get(serverId);
32613
+ if (poll) {
32614
+ clearInterval(poll);
32615
+ this.mergedLogPollByServerId.delete(serverId);
32616
+ }
32617
+ }
32618
+ abortSpawn(serverId) {
32619
+ this.abortControllersByServerId.get(serverId)?.abort();
32620
+ this.abortControllersByServerId.delete(serverId);
32621
+ }
32622
+ stop(serverId) {
32623
+ const tails = this.snapshotTails(serverId);
32624
+ if (this.processes.has(serverId)) {
32625
+ this.sendStatus(serverId, "stopping", void 0, tails);
32626
+ }
32627
+ this.bumpGeneration(serverId);
32628
+ this.abortSpawn(serverId);
32629
+ this.clearPoll(serverId);
32630
+ const proc = this.processes.get(serverId);
32631
+ if (proc) {
32632
+ proc.removeAllListeners();
32633
+ try {
32634
+ proc.kill("SIGTERM");
32635
+ } catch {
32636
+ }
32637
+ this.processes.delete(serverId);
32638
+ }
32639
+ this.clearTails(serverId);
32640
+ this.pipedCaptureByServerId.delete(serverId);
32641
+ const mergedDir = this.mergedLogCleanupDirByServerId.get(serverId);
32642
+ if (mergedDir) {
32643
+ this.mergedLogCleanupDirByServerId.delete(serverId);
32644
+ void rm(mergedDir, { recursive: true, force: true }).catch(() => {
32645
+ });
32646
+ }
32647
+ this.sendStatus(serverId, "stopped", void 0, tails);
32648
+ }
32649
+ start(serverId) {
32650
+ const def = this.defsById.get(serverId);
32651
+ if (!def) {
32652
+ this.log(`[dev-server] Unknown server id ${serverId.slice(0, 8)}\u2026`);
32653
+ this.sendStatus(serverId, "error", "Unknown server", emptyTails());
32654
+ return;
32655
+ }
32656
+ if (!def.command.trim()) {
32657
+ this.sendStatus(serverId, "idle", "No command configured", emptyTails());
32658
+ return;
32659
+ }
32660
+ this.stop(serverId);
32661
+ this.sendStatus(serverId, "starting", void 0, emptyTails());
32662
+ const ac = new AbortController();
32663
+ this.abortControllersByServerId.set(serverId, ac);
32664
+ const cwd3 = process.cwd();
32665
+ const childEnv = envForSpawn(process.env, def.env, def.ports);
32666
+ const cmd = substituteCommand(def.command.trim(), childEnv);
32667
+ const title = def.name.trim() || serverId.slice(0, 8);
32668
+ this.log(`[dev-server] Starting ${title}: ${cmd.slice(0, 120)}${cmd.length > 120 ? "\u2026" : ""}`);
32669
+ const scheduledGen = this.spawnGenerationByServerId.get(serverId) ?? 0;
32670
+ setImmediate(() => {
32671
+ if ((this.spawnGenerationByServerId.get(serverId) ?? 0) !== scheduledGen) {
32672
+ return;
32673
+ }
32674
+ let proc;
32675
+ let pipedStdoutStderr;
32676
+ let mergedLogPath;
32677
+ let mergedCleanupDir;
32678
+ try {
32679
+ const spawned = shellSpawn(cmd, childEnv, cwd3, {
32680
+ signal: ac.signal
32681
+ });
32682
+ proc = spawned.proc;
32683
+ pipedStdoutStderr = spawned.pipedStdoutStderr;
32684
+ mergedLogPath = spawned.mergedLogPath;
32685
+ mergedCleanupDir = spawned.mergedLogCleanupDir;
32686
+ } catch (e) {
32687
+ const msg = e instanceof Error ? e.message : String(e);
32688
+ this.log(`[dev-server] ${title} failed to start: ${msg}`);
32689
+ this.abortControllersByServerId.delete(serverId);
32690
+ this.sendStatus(serverId, "error", msg, emptyTails());
32691
+ return;
32692
+ }
32693
+ if ((this.spawnGenerationByServerId.get(serverId) ?? 0) !== scheduledGen) {
32694
+ try {
32695
+ proc.removeAllListeners();
32696
+ proc.kill("SIGTERM");
32697
+ } catch {
32698
+ }
32699
+ return;
32700
+ }
32701
+ if (!pipedStdoutStderr) {
32702
+ this.log(`[dev-server] ${title} running (inherited stdio; bridge log preview disabled)`);
32703
+ } else if (mergedLogPath && mergedCleanupDir) {
32704
+ this.log(
32705
+ `[dev-server] ${title} running (logs captured via temp file; open the browser log viewer for live output)`
32706
+ );
32707
+ }
32708
+ this.pipedCaptureByServerId.set(serverId, pipedStdoutStderr);
32709
+ const stdoutTail = new StreamTail();
32710
+ const stderrTail = new StreamTail();
32711
+ this.streamTailsByServerId.set(serverId, { stdout: stdoutTail, stderr: stderrTail });
32712
+ if (mergedLogPath && mergedCleanupDir) {
32713
+ this.mergedLogCleanupDirByServerId.set(serverId, mergedCleanupDir);
32714
+ }
32715
+ const mergedReadPos = { value: 0 };
32716
+ this.processes.set(serverId, proc);
32717
+ wireDevServerChildProcess({
32718
+ serverId,
32719
+ title,
32720
+ proc,
32721
+ mergedLogPath,
32722
+ mergedCleanupDir,
32723
+ mergedReadPos,
32724
+ scheduledGen,
32725
+ getSpawnGeneration: () => this.spawnGenerationByServerId.get(serverId) ?? 0,
32726
+ log: this.log,
32727
+ stdoutTail,
32728
+ stderrTail,
32729
+ pushRemoteLogChunk: (sid, stream, chunk) => this.pushRemoteLogChunk(sid, stream, chunk),
32730
+ sendStatus: (status, detail, tails) => this.sendStatus(serverId, status, detail, tails),
32731
+ setPollInterval: (iv) => {
32732
+ if (iv) this.mergedLogPollByServerId.set(serverId, iv);
32733
+ else this.mergedLogPollByServerId.delete(serverId);
32734
+ },
32735
+ getPollInterval: () => this.mergedLogPollByServerId.get(serverId),
32736
+ detachProcessFromManager: () => {
32737
+ this.processes.delete(serverId);
32738
+ this.pipedCaptureByServerId.delete(serverId);
32739
+ this.abortControllersByServerId.delete(serverId);
32740
+ this.mergedLogCleanupDirByServerId.delete(serverId);
32741
+ },
32742
+ rmMergedCleanupDir: (dir) => {
32743
+ void rm(dir, { recursive: true, force: true }).catch(() => {
32744
+ });
32745
+ },
32746
+ clearTailBuffers: () => this.clearTails(serverId)
32747
+ });
32748
+ this.sendStatus(serverId, "running", `pid=${proc.pid ?? "?"}`, {
32749
+ stdout: stdoutTail.getTail(),
32750
+ stderr: stderrTail.getTail()
32751
+ });
32752
+ });
32753
+ }
32754
+ handleControl(serverId, action) {
32755
+ if (action === "stop") {
32756
+ this.stop(serverId);
32757
+ return;
32758
+ }
32759
+ this.start(serverId);
32760
+ }
32761
+ async shutdownAllGraceful() {
32762
+ const pairs = [...this.processes.entries()];
32763
+ if (pairs.length === 0) return;
32764
+ this.log(`[dev-server] Stopping ${pairs.length} dev server process(es) (bridge shutting down)\u2026`);
32765
+ await Promise.all(pairs.map(([serverId, proc]) => this.gracefulTerminateOrUnknown(serverId, proc)));
32766
+ }
32767
+ async gracefulTerminateOrUnknown(serverId, proc) {
32768
+ const shortId = `${serverId.slice(0, 8)}\u2026`;
32769
+ await sigtermAndWaitForExit(proc, BRIDGE_SHUTDOWN_GRACE_MS, this.log, shortId);
32770
+ if (!this.processes.has(serverId) || this.processes.get(serverId) !== proc) {
32771
+ return;
32772
+ }
32773
+ this.bumpGeneration(serverId);
32774
+ forceKillChild(proc, this.log, shortId, BRIDGE_SHUTDOWN_GRACE_MS);
32775
+ this.processes.delete(serverId);
32776
+ this.clearPoll(serverId);
32777
+ this.pipedCaptureByServerId.delete(serverId);
32778
+ const mergedDir = this.mergedLogCleanupDirByServerId.get(serverId);
32779
+ if (mergedDir) {
32780
+ this.mergedLogCleanupDirByServerId.delete(serverId);
32781
+ void rm(mergedDir, { recursive: true, force: true }).catch(() => {
32782
+ });
32783
+ }
32784
+ const tails = this.snapshotTails(serverId);
32785
+ this.clearTails(serverId);
32786
+ this.sendStatus(serverId, "unknown", "Bridge closed before process exited", tails);
32787
+ }
32788
+ };
32789
+
32790
+ // src/firehose/connect-firehose.ts
32791
+ import https2 from "node:https";
32792
+
32793
+ // src/firehose/proxy/start-streaming-proxy.ts
32794
+ function startStreamingProxy(ws, log2, pr) {
32795
+ proxyToLocalStreaming(pr, {
32796
+ onStart: (statusCode, headers) => {
32797
+ const forwardedHeaders = { ...headers };
32798
+ const ce = "content-encoding";
32799
+ const cl = "content-length";
32800
+ const keys = Object.keys(forwardedHeaders).filter(
32801
+ (k) => k.toLowerCase() !== ce && k.toLowerCase() !== cl
32802
+ );
32803
+ const filtered = {};
32804
+ for (const k of keys) if (forwardedHeaders[k] != null) filtered[k] = forwardedHeaders[k];
32805
+ sendWsMessage(ws, { type: "proxy_result_start", id: pr.id, statusCode, headers: filtered });
32806
+ },
32807
+ onChunk: (chunk) => {
32808
+ const idBuf = Buffer.from(pr.id, "utf8");
32809
+ ws.send(Buffer.concat([idBuf, Buffer.from(chunk)]), { binary: true });
32810
+ },
32811
+ onEnd: () => sendWsMessage(ws, { type: "proxy_result_end", id: pr.id }),
32812
+ onError: (error40) => {
32813
+ log2(`[Proxy and log service] streaming preview failed: ${error40}`);
32814
+ sendWsMessage(ws, { type: "proxy_result_error", id: pr.id, error: error40 });
32815
+ }
32816
+ });
32817
+ }
32818
+
32819
+ // src/firehose/build-cli-ws-url.ts
32820
+ function buildFirehoseCliWsUrl(baseUrl) {
32821
+ const base = baseUrl.startsWith("https") ? baseUrl.replace(/^https/, "wss") : baseUrl.replace(/^http/, "ws");
32822
+ return `${base.replace(/\/$/, "")}/ws`;
32823
+ }
32824
+
32825
+ // src/firehose/routing/firehose-message-router.ts
32826
+ function createFirehoseMessageRouter() {
32827
+ const handlers = /* @__PURE__ */ new Map();
32828
+ return {
32829
+ register(type, handler) {
32830
+ handlers.set(type, handler);
32831
+ },
32832
+ dispatch(msg, deps) {
32833
+ const type = msg.type;
32834
+ if (!type) return;
32835
+ const handler = handlers.get(type);
32836
+ if (handler) handler(msg, deps);
32837
+ }
32838
+ };
32839
+ }
32840
+
32841
+ // src/firehose/routing/handlers/dev-server-logs-viewer-open.ts
32842
+ var handleDevServerLogsViewerOpen = (msg, deps) => {
32843
+ const serverId = typeof msg.serverId === "string" ? msg.serverId : "";
32844
+ const viewerId = typeof msg.viewerId === "string" ? msg.viewerId : "";
32845
+ if (!serverId || !viewerId) return;
32846
+ deps.devServerManager.handleFirehoseLogViewerOpen(serverId, viewerId);
32847
+ };
32848
+
32849
+ // src/firehose/routing/handlers/dev-server-logs-viewer-close.ts
32850
+ var handleDevServerLogsViewerClose = (msg, deps) => {
32851
+ const serverId = typeof msg.serverId === "string" ? msg.serverId : "";
32852
+ const viewerId = typeof msg.viewerId === "string" ? msg.viewerId : "";
32853
+ if (!serverId || !viewerId) return;
32854
+ deps.devServerManager.handleFirehoseLogViewerClose(serverId, viewerId);
32855
+ };
32856
+
32857
+ // src/firehose/routing/handlers/proxy-message.ts
32858
+ var handleProxyMessage = (msg, deps) => {
32859
+ const proxy = msg.proxy;
32860
+ if (!proxy || typeof proxy !== "object") return;
32861
+ const pr = proxy;
32862
+ if (pr.streamResponse) {
32863
+ const bodyLength = pr.bodyLength ?? 0;
32864
+ if (bodyLength > 0) {
32865
+ deps.pendingProxyBody.set(pr.id, {
32866
+ pr: {
32867
+ id: pr.id,
32868
+ method: pr.method,
32869
+ url: pr.url,
32870
+ headers: pr.headers,
32871
+ streamResponse: true
32872
+ }
32873
+ });
32874
+ } else {
32875
+ deps.startStreamingProxy({
32876
+ id: pr.id,
32877
+ method: pr.method,
32878
+ url: pr.url,
32879
+ headers: pr.headers,
32880
+ streamResponse: true
32881
+ });
32882
+ }
32883
+ return;
32884
+ }
32885
+ void proxyToLocal(pr).then((res) => {
32886
+ if (res.error) deps.log(`[Proxy and log service] preview proxy failed: ${res.error}`);
32887
+ sendWsMessage(deps.ws, { type: "proxy_result", ...res, id: pr.id });
32888
+ }).catch((err) => {
32889
+ deps.log(
32890
+ `[Proxy and log service] preview proxy failed: ${err instanceof Error ? err.message : String(err)}`
32891
+ );
32892
+ sendWsMessage(deps.ws, { type: "proxy_result", id: pr.id, error: String(err) });
32893
+ });
32894
+ };
32895
+
32896
+ // src/firehose/routing/handlers/identified.ts
32897
+ var handleFirehoseIdentified = (_msg, _deps) => {
32898
+ };
32899
+
32900
+ // src/firehose/routing/dispatch-firehose-message.ts
32901
+ var router = createFirehoseMessageRouter();
32902
+ router.register("identified", handleFirehoseIdentified);
32903
+ router.register("log_viewer_open", handleDevServerLogsViewerOpen);
32904
+ router.register("log_viewer_close", handleDevServerLogsViewerClose);
32905
+ router.register("proxy", handleProxyMessage);
32906
+ function dispatchFirehoseJsonMessage(msg, deps) {
32907
+ router.dispatch(msg, deps);
32908
+ }
32909
+
32910
+ // src/firehose/routing/try-consume-binary-proxy-body.ts
32911
+ var PROXY_ID_BYTES = 36;
32912
+ function tryConsumeBinaryProxyBody(raw, deps) {
32913
+ if (raw.length < PROXY_ID_BYTES) return false;
32914
+ const id = raw.slice(0, PROXY_ID_BYTES).toString("utf8");
32915
+ const pending = deps.pendingProxyBody.get(id);
32916
+ if (!pending) return false;
32917
+ deps.pendingProxyBody.delete(id);
32918
+ const body = raw.slice(PROXY_ID_BYTES);
32919
+ deps.startStreamingProxy({
32920
+ ...pending.pr,
32921
+ body: body.length > 0 ? new Uint8Array(body) : void 0
32922
+ });
32923
+ return true;
32924
+ }
32925
+
32926
+ // src/firehose/connect-firehose.ts
32927
+ function connectFirehose(options) {
32928
+ const { firehoseServerUrl, workspaceId, bridgeName, proxyPorts, log: log2, devServerManager, onOpen, onClose } = options;
32929
+ const wsUrl = buildFirehoseCliWsUrl(firehoseServerUrl);
32930
+ const wsOptions = { perMessageDeflate: false };
32931
+ if (wsUrl.startsWith("wss://")) {
32932
+ wsOptions.agent = new https2.Agent({ rejectUnauthorized: false });
32933
+ }
32934
+ const ws = new wrapper_default(wsUrl, wsOptions);
32935
+ const firehoseSend = (payload) => {
32936
+ sendWsMessage(ws, payload);
32937
+ };
32938
+ const pendingProxyBody = /* @__PURE__ */ new Map();
32939
+ const deps = {
32940
+ ws,
32941
+ log: log2,
32942
+ devServerManager,
32943
+ pendingProxyBody,
32944
+ startStreamingProxy: (pr) => startStreamingProxy(ws, log2, pr)
32945
+ };
32946
+ ws.on("open", () => {
32947
+ onOpen?.();
32948
+ devServerManager.attachFirehose(firehoseSend);
32949
+ sendWsMessage(ws, { type: "identify", workspaceId, bridgeName, proxyPorts });
32950
+ });
32951
+ ws.on("message", (raw) => {
32952
+ setImmediate(() => {
32953
+ if (Buffer.isBuffer(raw) && tryConsumeBinaryProxyBody(raw, deps)) {
32954
+ return;
32955
+ }
32956
+ try {
32957
+ const text = Buffer.isBuffer(raw) ? raw.toString("utf8") : String(raw);
32958
+ dispatchFirehoseJsonMessage(JSON.parse(text), deps);
32959
+ } catch {
32960
+ }
32961
+ });
32962
+ });
32963
+ ws.on("close", (code, reason) => {
32964
+ devServerManager.detachFirehose();
32965
+ log2(
32966
+ formatWebSocketClose(
32967
+ "[Proxy and log service]",
32968
+ code,
32969
+ typeof reason === "string" ? reason : reason.toString(),
32970
+ "reconnects automatically if the bridge service stays connected"
32971
+ )
32972
+ );
32973
+ onClose?.();
32974
+ });
32975
+ ws.on("error", (err) => {
32976
+ log2(`[Proxy and log service] WebSocket error: ${err.message}`);
32977
+ });
32978
+ return {
32979
+ close() {
32980
+ devServerManager.detachFirehose();
32981
+ try {
32982
+ ws.removeAllListeners();
32983
+ ws.close();
32984
+ } catch {
32985
+ }
32986
+ }
32987
+ };
32988
+ }
32989
+
32990
+ // src/bridge/connection/create-bridge-identified-handler.ts
32991
+ var FH_RECONNECT_BASE_MS = 2e3;
32992
+ var FH_RECONNECT_MAX_MS = 3e4;
32993
+ function createOnBridgeIdentified(opts) {
32994
+ const { sessionWorktreeManager, devServerManager, firehoseServerUrl, workspaceId, state, logFn } = opts;
32995
+ function clearFirehoseReconnectTimer() {
32996
+ if (state.firehoseReconnectTimeout != null) {
32997
+ clearTimeout(state.firehoseReconnectTimeout);
32998
+ state.firehoseReconnectTimeout = null;
32999
+ }
33000
+ }
33001
+ function attachFirehose(params) {
33002
+ state.lastFirehoseParams = params;
33003
+ clearFirehoseReconnectTimer();
33004
+ state.firehoseGeneration += 1;
33005
+ const myGen = state.firehoseGeneration;
33006
+ if (state.firehoseHandle) {
33007
+ state.firehoseHandle.close();
33008
+ state.firehoseHandle = null;
33009
+ }
33010
+ state.firehoseHandle = connectFirehose({
33011
+ firehoseServerUrl: params.firehoseServerUrl,
33012
+ workspaceId: params.workspaceId,
33013
+ bridgeName: params.bridgeName,
33014
+ proxyPorts: params.proxyPorts,
33015
+ log: logFn,
33016
+ devServerManager,
33017
+ onOpen: () => {
33018
+ if (myGen !== state.firehoseGeneration) return;
33019
+ state.firehoseReconnectAttempt = 0;
33020
+ },
33021
+ onClose: () => {
33022
+ if (myGen !== state.firehoseGeneration) return;
33023
+ state.firehoseHandle = null;
33024
+ if (state.closedByUser) return;
33025
+ const main = state.currentWs;
33026
+ if (!main || main.readyState !== wrapper_default.OPEN) {
33027
+ logFn("[Proxy and log service] not reconnecting: bridge service socket is not open");
33028
+ return;
33029
+ }
33030
+ clearFirehoseReconnectTimer();
33031
+ const delay2 = Math.min(
33032
+ FH_RECONNECT_BASE_MS * 2 ** state.firehoseReconnectAttempt,
33033
+ FH_RECONNECT_MAX_MS
33034
+ );
33035
+ state.firehoseReconnectAttempt += 1;
33036
+ logFn(
33037
+ `[Proxy and log service] reconnect attempt ${state.firehoseReconnectAttempt} in ${delay2 / 1e3}s\u2026`
33038
+ );
33039
+ state.firehoseReconnectTimeout = setTimeout(() => {
33040
+ state.firehoseReconnectTimeout = null;
33041
+ if (state.closedByUser) return;
33042
+ const w = state.currentWs;
33043
+ if (!w || w.readyState !== wrapper_default.OPEN) {
33044
+ logFn("[Proxy and log service] reconnect skipped: bridge service socket closed");
33045
+ return;
33046
+ }
33047
+ const p = state.lastFirehoseParams;
33048
+ if (!p) {
33049
+ logFn("[Proxy and log service] reconnect skipped: no stored connection params");
33050
+ return;
33051
+ }
33052
+ attachFirehose(p);
33053
+ }, delay2);
33054
+ }
33055
+ });
33056
+ }
33057
+ return (msg) => {
33058
+ sessionWorktreeManager.setBridgeSessionWorktrees(msg.sessionWorktreesEnabled === true);
33059
+ const bridgeName = msg.bridgeName;
33060
+ const proxyPorts = Array.isArray(msg.proxyPorts) ? msg.proxyPorts : [];
33061
+ const devServers = msg.devServers ?? [];
33062
+ setImmediate(() => {
33063
+ devServerManager.applyConfig(devServers);
33064
+ if (!firehoseServerUrl || typeof bridgeName !== "string" || !bridgeName) return;
33065
+ state.firehoseReconnectAttempt = 0;
33066
+ attachFirehose({
33067
+ firehoseServerUrl,
33068
+ workspaceId,
33069
+ bridgeName,
33070
+ proxyPorts
33071
+ });
33072
+ });
33073
+ };
33074
+ }
33075
+
33076
+ // src/dev-servers/detect-local-agent-types.ts
33077
+ import { execFile as execFile4 } from "node:child_process";
33078
+ import { promisify as promisify4 } from "node:util";
33079
+ var execFileAsync4 = promisify4(execFile4);
33080
+ var CHECKS = {
33081
+ "cursor-cli": async () => {
33082
+ try {
33083
+ await execFileAsync4("which", ["agent"], { timeout: 4e3 });
33084
+ return true;
33085
+ } catch {
33086
+ return false;
33087
+ }
33088
+ },
33089
+ "codex-acp": async () => {
33090
+ try {
33091
+ await execFileAsync4("which", ["codex"], { timeout: 4e3 });
33092
+ return true;
33093
+ } catch {
33094
+ return false;
33095
+ }
33096
+ },
33097
+ "claude-code": async () => {
33098
+ try {
33099
+ await execFileAsync4("which", ["claude"], { timeout: 4e3 });
33100
+ return true;
33101
+ } catch {
33102
+ try {
33103
+ await execFileAsync4("npx", ["--yes", "@anthropic-ai/claude-code", "--version"], { timeout: 25e3 });
33104
+ return true;
33105
+ } catch {
33106
+ return false;
33107
+ }
33108
+ }
33109
+ }
33110
+ };
33111
+ async function detectLocalAgentTypes() {
33112
+ try {
33113
+ const out = [];
33114
+ for (const [type, check2] of Object.entries(CHECKS)) {
33115
+ try {
33116
+ if (await check2()) out.push(type);
33117
+ } catch {
33118
+ }
33119
+ }
33120
+ return out;
33121
+ } catch {
33122
+ return [];
33123
+ }
33124
+ }
33125
+
33126
+ // src/bridge/connection/create-bridge-local-reports.ts
33127
+ function createSendLocalSkillsReport(getWs, logFn) {
33128
+ return () => {
33129
+ try {
33130
+ const socket = getWs();
33131
+ if (!socket || socket.readyState !== wrapper_default.OPEN) return;
33132
+ const skills2 = discoverLocalSkills(process.cwd());
33133
+ socket.send(JSON.stringify({ type: "local_skills", skills: skills2 }));
33134
+ } catch (e) {
33135
+ logFn(`[Bridge service] local_skills report failed: ${e instanceof Error ? e.message : String(e)}`);
33136
+ }
33137
+ };
33138
+ }
33139
+ function createReportAutoDetectedAgents(getWs, logFn) {
33140
+ return async () => {
33141
+ try {
33142
+ const types = await detectLocalAgentTypes();
33143
+ const socket = getWs();
33144
+ if (socket && socket.readyState === wrapper_default.OPEN) {
33145
+ sendWsMessage(socket, { type: "auto_detected_agents_report", agentTypes: types });
33146
+ }
33147
+ } catch (e) {
33148
+ logFn(`[Bridge service] auto_detected_agents report failed: ${e instanceof Error ? e.message : String(e)}`);
33149
+ }
33150
+ };
33151
+ }
33152
+
32080
33153
  // src/bridge/connection/create-bridge-connection.ts
32081
33154
  async function createBridgeConnection(options) {
32082
- const { apiUrl, workspaceId, proxyServerUrl, agentCommand, justAuthenticated, onAuthInvalid, persistTokens } = options;
33155
+ const { apiUrl, workspaceId, justAuthenticated, onAuthInvalid, persistTokens } = options;
33156
+ const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
32083
33157
  const logFn = options.log ?? log;
32084
33158
  let accessToken = options.authToken;
32085
33159
  let refreshTok = options.refreshToken;
@@ -32089,25 +33163,37 @@ async function createBridgeConnection(options) {
32089
33163
  reconnectAttempt: 0,
32090
33164
  reconnectTimeout: null,
32091
33165
  currentWs: null,
32092
- proxyHandle: null
33166
+ firehoseHandle: null,
33167
+ lastFirehoseParams: null,
33168
+ firehoseReconnectTimeout: null,
33169
+ firehoseReconnectAttempt: 0,
33170
+ firehoseGeneration: 0
32093
33171
  };
32094
33172
  const worktreesRootAbs = options.worktreesRootAbs ?? defaultWorktreesRootAbs();
32095
33173
  const sessionWorktreeManager = new SessionWorktreeManager({
32096
33174
  worktreesRootAbs,
32097
33175
  log: logFn
32098
33176
  });
32099
- const acpManager = await createAcpManager({ agentCommand, log: logFn });
32100
- const skills2 = getSkills();
32101
- logFn(`Skills: ${skills2.map((s) => s.id).join(", ")}`);
32102
- logFn("Connected to backend. Press Ctrl+C to exit.");
33177
+ const acpManager = await createAcpManager({ log: logFn });
33178
+ logFn("CLI running. Press Ctrl+C to exit.");
32103
33179
  function getWs() {
32104
33180
  return state.currentWs;
32105
33181
  }
33182
+ const devServerManager = new DevServerManager({ getWs, log: logFn });
33183
+ const onBridgeIdentified = createOnBridgeIdentified({
33184
+ sessionWorktreeManager,
33185
+ devServerManager,
33186
+ firehoseServerUrl,
33187
+ workspaceId,
33188
+ state,
33189
+ logFn
33190
+ });
33191
+ const sendLocalSkillsReport = createSendLocalSkillsReport(getWs, logFn);
33192
+ const reportAutoDetectedAgents = createReportAutoDetectedAgents(getWs, logFn);
32106
33193
  function handleOpen() {
32107
33194
  state.reconnectAttempt = 0;
32108
33195
  const socket = getWs();
32109
33196
  if (socket) {
32110
- logFn("WebSocket connected");
32111
33197
  sendWsMessage(socket, { type: "identify", role: "cli" });
32112
33198
  reportGitRepos(getWs, logFn);
32113
33199
  }
@@ -32121,49 +33207,23 @@ async function createBridgeConnection(options) {
32121
33207
  const was = state.currentWs;
32122
33208
  state.currentWs = null;
32123
33209
  if (was) was.removeAllListeners();
32124
- logFn(`WebSocket closed: ${code} ${reason}`);
32125
- if (!state.closedByUser) {
33210
+ const willReconnect = !state.closedByUser;
33211
+ logFn(
33212
+ formatWebSocketClose("[Bridge service]", code, reason, willReconnect ? "will schedule reconnect" : "not reconnecting (CLI shutting down)")
33213
+ );
33214
+ if (willReconnect) {
32126
33215
  scheduleReconnect(state, connect, logFn);
32127
33216
  }
32128
33217
  }
32129
- function onBridgeIdentified(msg) {
32130
- sessionWorktreeManager.setBridgeSessionWorktrees(msg.sessionWorktreesEnabled === true);
32131
- const bridgeName = msg.bridgeName;
32132
- const proxyPorts = Array.isArray(msg.proxyPorts) ? msg.proxyPorts : [];
32133
- if (proxyServerUrl && bridgeName) {
32134
- logFn(`Proxy: setting up connection (server=${proxyServerUrl}, bridge=${bridgeName}, ports=[${proxyPorts.join(", ")}])`);
32135
- if (state.proxyHandle) {
32136
- state.proxyHandle.close();
32137
- state.proxyHandle = null;
32138
- }
32139
- state.proxyHandle = connectProxy({
32140
- proxyServerUrl,
32141
- workspaceId,
32142
- bridgeName,
32143
- proxyPorts,
32144
- log: logFn,
32145
- onClose: () => {
32146
- if (state.proxyHandle) state.proxyHandle = null;
32147
- }
32148
- });
32149
- }
32150
- }
32151
- function sendLocalSkillsReport() {
32152
- const socket = getWs();
32153
- if (!socket || socket.readyState !== wrapper_default.OPEN) return;
32154
- const skills3 = discoverLocalSkills(process.cwd());
32155
- socket.send(JSON.stringify({ type: "local_skills", skills: skills3 }));
32156
- if (skills3.length > 0) {
32157
- logFn(`Local agent skills (cwd): ${skills3.map((s) => s.path).join(", ")}`);
32158
- }
32159
- }
32160
33218
  const messageDeps = {
32161
33219
  getWs,
32162
33220
  log: logFn,
32163
33221
  acpManager,
32164
33222
  sessionWorktreeManager,
32165
33223
  onBridgeIdentified,
32166
- sendLocalSkillsReport
33224
+ sendLocalSkillsReport,
33225
+ reportAutoDetectedAgents,
33226
+ devServerManager
32167
33227
  };
32168
33228
  function connect() {
32169
33229
  if (state.closedByUser) return;
@@ -32177,7 +33237,6 @@ async function createBridgeConnection(options) {
32177
33237
  state.currentWs = null;
32178
33238
  }
32179
33239
  const url2 = buildBridgeUrl(apiUrl, workspaceId, accessToken);
32180
- logFn(`Connecting to backend at ${apiUrl}`);
32181
33240
  state.currentWs = createWsBridge({
32182
33241
  url: url2,
32183
33242
  onAuthInvalid: () => {
@@ -32191,7 +33250,7 @@ async function createBridgeConnection(options) {
32191
33250
  accessToken = next.token;
32192
33251
  refreshTok = next.refreshToken;
32193
33252
  persistTokens?.({ token: accessToken, refreshToken: refreshTok });
32194
- logFn("Bridge access token refreshed; reconnecting\u2026");
33253
+ logFn("[Bridge service] access token refreshed; reconnecting\u2026");
32195
33254
  state.reconnectAttempt = 0;
32196
33255
  authRefreshInFlight = false;
32197
33256
  connect();
@@ -32210,23 +33269,24 @@ async function createBridgeConnection(options) {
32210
33269
  },
32211
33270
  onOpen: handleOpen,
32212
33271
  onClose: handleClose,
32213
- onError: (err) => logFn(`WebSocket error: ${err.message}`),
33272
+ onError: (err) => logFn(`[Bridge service] WebSocket error: ${err.message}`),
32214
33273
  onMessage: (data) => handleBridgeMessage(data, messageDeps)
32215
33274
  });
32216
33275
  }
32217
33276
  connect();
32218
33277
  const stopFileIndexWatcher = startFileIndexWatcher(process.cwd());
32219
33278
  return {
32220
- close: () => {
33279
+ close: async () => {
32221
33280
  stopFileIndexWatcher();
32222
- closeBridgeConnection(state, acpManager);
33281
+ await closeBridgeConnection(state, acpManager, devServerManager, logFn);
32223
33282
  }
32224
33283
  };
32225
33284
  }
32226
33285
 
32227
33286
  // src/run-bridge.ts
32228
33287
  async function runBridge(options) {
32229
- const { apiUrl, workspaceId, authToken, refreshToken, proxyServerUrl, agentCommand, bridgeName, justAuthenticated, worktreesRootAbs } = options;
33288
+ const { apiUrl, workspaceId, authToken, refreshToken, bridgeName, justAuthenticated, worktreesRootAbs } = options;
33289
+ const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
32230
33290
  const hasAuth = workspaceId && authToken;
32231
33291
  if (!hasAuth) {
32232
33292
  const handle2 = runPendingAuth({
@@ -32237,15 +33297,20 @@ async function runBridge(options) {
32237
33297
  onAuth: (_auth) => {
32238
33298
  }
32239
33299
  });
32240
- const onSignal2 = () => {
32241
- handle2.close();
32242
- process.exit(0);
33300
+ const onSignal2 = (signal) => {
33301
+ logImmediate(`Received ${signal}; shutting down\u2026`);
33302
+ setImmediate(() => {
33303
+ handle2.close();
33304
+ process.exit(0);
33305
+ });
32243
33306
  };
32244
- process.on("SIGINT", onSignal2);
32245
- process.on("SIGTERM", onSignal2);
33307
+ const onSigInt2 = () => onSignal2("SIGINT");
33308
+ const onSigTerm2 = () => onSignal2("SIGTERM");
33309
+ process.on("SIGINT", onSigInt2);
33310
+ process.on("SIGTERM", onSigTerm2);
32246
33311
  const auth = await handle2.authPromise;
32247
- process.off("SIGINT", onSignal2);
32248
- process.off("SIGTERM", onSignal2);
33312
+ process.off("SIGINT", onSigInt2);
33313
+ process.off("SIGTERM", onSigTerm2);
32249
33314
  handle2.close();
32250
33315
  if (!auth) return;
32251
33316
  writeConfigForApi(apiUrl, {
@@ -32258,8 +33323,7 @@ async function runBridge(options) {
32258
33323
  workspaceId: auth.workspaceId,
32259
33324
  authToken: auth.token,
32260
33325
  refreshToken: auth.refreshToken,
32261
- proxyServerUrl,
32262
- agentCommand,
33326
+ firehoseServerUrl,
32263
33327
  bridgeName,
32264
33328
  justAuthenticated: true,
32265
33329
  worktreesRootAbs
@@ -32271,8 +33335,7 @@ async function runBridge(options) {
32271
33335
  workspaceId,
32272
33336
  authToken,
32273
33337
  refreshToken,
32274
- proxyServerUrl,
32275
- agentCommand,
33338
+ firehoseServerUrl,
32276
33339
  justAuthenticated,
32277
33340
  worktreesRootAbs,
32278
33341
  log,
@@ -32284,18 +33347,25 @@ async function runBridge(options) {
32284
33347
  });
32285
33348
  },
32286
33349
  onAuthInvalid: () => {
32287
- log("Stored token was invalid or revoked and could not be refreshed. Re-authenticating\u2026");
33350
+ log("[Bridge service] token invalid or revoked; re-authenticating\u2026");
32288
33351
  clearConfigForApi(apiUrl);
32289
- handle.close();
32290
- void runBridge({ apiUrl, proxyServerUrl, agentCommand, worktreesRootAbs });
33352
+ void handle.close().then(() => {
33353
+ void runBridge({ apiUrl, firehoseServerUrl, worktreesRootAbs });
33354
+ });
32291
33355
  }
32292
33356
  });
32293
- const onSignal = () => {
32294
- handle.close();
32295
- process.exit(0);
33357
+ const onSignal = (signal) => {
33358
+ logImmediate(`Received ${signal}; shutting down\u2026`);
33359
+ setImmediate(() => {
33360
+ void handle.close().then(() => {
33361
+ process.exit(0);
33362
+ });
33363
+ });
32296
33364
  };
32297
- process.on("SIGINT", onSignal);
32298
- process.on("SIGTERM", onSignal);
33365
+ const onSigInt = () => onSignal("SIGINT");
33366
+ const onSigTerm = () => onSignal("SIGTERM");
33367
+ process.on("SIGINT", onSigInt);
33368
+ process.on("SIGTERM", onSigTerm);
32299
33369
  }
32300
33370
  export {
32301
33371
  callSkill,