@buildautomaton/cli 0.1.15 → 0.1.17

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/cli.js CHANGED
@@ -973,8 +973,8 @@ var require_command = __commonJS({
973
973
  "../../node_modules/.pnpm/commander@12.1.0/node_modules/commander/lib/command.js"(exports) {
974
974
  var EventEmitter2 = __require("node:events").EventEmitter;
975
975
  var childProcess2 = __require("node:child_process");
976
- var path33 = __require("node:path");
977
- var fs30 = __require("node:fs");
976
+ var path34 = __require("node:path");
977
+ var fs31 = __require("node:fs");
978
978
  var process8 = __require("node:process");
979
979
  var { Argument: Argument2, humanReadableArgName } = require_argument();
980
980
  var { CommanderError: CommanderError2 } = require_error();
@@ -1906,11 +1906,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1906
1906
  let launchWithNode = false;
1907
1907
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1908
1908
  function findFile(baseDir, baseName) {
1909
- const localBin = path33.resolve(baseDir, baseName);
1910
- if (fs30.existsSync(localBin)) return localBin;
1911
- if (sourceExt.includes(path33.extname(baseName))) return void 0;
1909
+ const localBin = path34.resolve(baseDir, baseName);
1910
+ if (fs31.existsSync(localBin)) return localBin;
1911
+ if (sourceExt.includes(path34.extname(baseName))) return void 0;
1912
1912
  const foundExt = sourceExt.find(
1913
- (ext) => fs30.existsSync(`${localBin}${ext}`)
1913
+ (ext) => fs31.existsSync(`${localBin}${ext}`)
1914
1914
  );
1915
1915
  if (foundExt) return `${localBin}${foundExt}`;
1916
1916
  return void 0;
@@ -1922,21 +1922,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
1922
1922
  if (this._scriptPath) {
1923
1923
  let resolvedScriptPath;
1924
1924
  try {
1925
- resolvedScriptPath = fs30.realpathSync(this._scriptPath);
1925
+ resolvedScriptPath = fs31.realpathSync(this._scriptPath);
1926
1926
  } catch (err) {
1927
1927
  resolvedScriptPath = this._scriptPath;
1928
1928
  }
1929
- executableDir = path33.resolve(
1930
- path33.dirname(resolvedScriptPath),
1929
+ executableDir = path34.resolve(
1930
+ path34.dirname(resolvedScriptPath),
1931
1931
  executableDir
1932
1932
  );
1933
1933
  }
1934
1934
  if (executableDir) {
1935
1935
  let localFile = findFile(executableDir, executableFile);
1936
1936
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
1937
- const legacyName = path33.basename(
1937
+ const legacyName = path34.basename(
1938
1938
  this._scriptPath,
1939
- path33.extname(this._scriptPath)
1939
+ path34.extname(this._scriptPath)
1940
1940
  );
1941
1941
  if (legacyName !== this._name) {
1942
1942
  localFile = findFile(
@@ -1947,7 +1947,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1947
1947
  }
1948
1948
  executableFile = localFile || executableFile;
1949
1949
  }
1950
- launchWithNode = sourceExt.includes(path33.extname(executableFile));
1950
+ launchWithNode = sourceExt.includes(path34.extname(executableFile));
1951
1951
  let proc;
1952
1952
  if (process8.platform !== "win32") {
1953
1953
  if (launchWithNode) {
@@ -2787,7 +2787,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2787
2787
  * @return {Command}
2788
2788
  */
2789
2789
  nameFromFilename(filename) {
2790
- this._name = path33.basename(filename, path33.extname(filename));
2790
+ this._name = path34.basename(filename, path34.extname(filename));
2791
2791
  return this;
2792
2792
  }
2793
2793
  /**
@@ -2801,9 +2801,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2801
2801
  * @param {string} [path]
2802
2802
  * @return {(string|null|Command)}
2803
2803
  */
2804
- executableDir(path34) {
2805
- if (path34 === void 0) return this._executableDir;
2806
- this._executableDir = path34;
2804
+ executableDir(path35) {
2805
+ if (path35 === void 0) return this._executableDir;
2806
+ this._executableDir = path35;
2807
2807
  return this;
2808
2808
  }
2809
2809
  /**
@@ -5236,7 +5236,7 @@ var require_websocket = __commonJS({
5236
5236
  var http = __require("http");
5237
5237
  var net = __require("net");
5238
5238
  var tls = __require("tls");
5239
- var { randomBytes, createHash: createHash2 } = __require("crypto");
5239
+ var { randomBytes: randomBytes3, createHash: createHash3 } = __require("crypto");
5240
5240
  var { Duplex, Readable: Readable2 } = __require("stream");
5241
5241
  var { URL: URL2 } = __require("url");
5242
5242
  var PerMessageDeflate = require_permessage_deflate();
@@ -5766,7 +5766,7 @@ var require_websocket = __commonJS({
5766
5766
  }
5767
5767
  }
5768
5768
  const defaultPort = isSecure ? 443 : 80;
5769
- const key = randomBytes(16).toString("base64");
5769
+ const key = randomBytes3(16).toString("base64");
5770
5770
  const request = isSecure ? https2.request : http.request;
5771
5771
  const protocolSet = /* @__PURE__ */ new Set();
5772
5772
  let perMessageDeflate;
@@ -5896,7 +5896,7 @@ var require_websocket = __commonJS({
5896
5896
  abortHandshake(websocket, socket, "Invalid Upgrade header");
5897
5897
  return;
5898
5898
  }
5899
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
5899
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
5900
5900
  if (res.headers["sec-websocket-accept"] !== digest) {
5901
5901
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
5902
5902
  return;
@@ -6263,7 +6263,7 @@ var require_websocket_server = __commonJS({
6263
6263
  var EventEmitter2 = __require("events");
6264
6264
  var http = __require("http");
6265
6265
  var { Duplex } = __require("stream");
6266
- var { createHash: createHash2 } = __require("crypto");
6266
+ var { createHash: createHash3 } = __require("crypto");
6267
6267
  var extension = require_extension();
6268
6268
  var PerMessageDeflate = require_permessage_deflate();
6269
6269
  var subprotocol = require_subprotocol();
@@ -6564,7 +6564,7 @@ var require_websocket_server = __commonJS({
6564
6564
  );
6565
6565
  }
6566
6566
  if (this._state > RUNNING) return abortHandshake(socket, 503);
6567
- const digest = createHash2("sha1").update(key + GUID).digest("base64");
6567
+ const digest = createHash3("sha1").update(key + GUID).digest("base64");
6568
6568
  const headers = [
6569
6569
  "HTTP/1.1 101 Switching Protocols",
6570
6570
  "Upgrade: websocket",
@@ -7423,15 +7423,15 @@ var require_src2 = __commonJS({
7423
7423
  var fs_1 = __require("fs");
7424
7424
  var debug_1 = __importDefault(require_src());
7425
7425
  var log2 = debug_1.default("@kwsites/file-exists");
7426
- function check2(path33, isFile, isDirectory) {
7427
- log2(`checking %s`, path33);
7426
+ function check2(path34, isFile, isDirectory) {
7427
+ log2(`checking %s`, path34);
7428
7428
  try {
7429
- const stat2 = fs_1.statSync(path33);
7430
- if (stat2.isFile() && isFile) {
7429
+ const stat3 = fs_1.statSync(path34);
7430
+ if (stat3.isFile() && isFile) {
7431
7431
  log2(`[OK] path represents a file`);
7432
7432
  return true;
7433
7433
  }
7434
- if (stat2.isDirectory() && isDirectory) {
7434
+ if (stat3.isDirectory() && isDirectory) {
7435
7435
  log2(`[OK] path represents a directory`);
7436
7436
  return true;
7437
7437
  }
@@ -7446,8 +7446,8 @@ var require_src2 = __commonJS({
7446
7446
  throw e;
7447
7447
  }
7448
7448
  }
7449
- function exists2(path33, type = exports.READABLE) {
7450
- return check2(path33, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
7449
+ function exists2(path34, type = exports.READABLE) {
7450
+ return check2(path34, (type & exports.FILE) > 0, (type & exports.FOLDER) > 0);
7451
7451
  }
7452
7452
  exports.exists = exists2;
7453
7453
  exports.FILE = 1;
@@ -7922,8 +7922,8 @@ var init_parseUtil = __esm({
7922
7922
  init_errors();
7923
7923
  init_en();
7924
7924
  makeIssue = (params) => {
7925
- const { data, path: path33, errorMaps, issueData } = params;
7926
- const fullPath = [...path33, ...issueData.path || []];
7925
+ const { data, path: path34, errorMaps, issueData } = params;
7926
+ const fullPath = [...path34, ...issueData.path || []];
7927
7927
  const fullIssue = {
7928
7928
  ...issueData,
7929
7929
  path: fullPath
@@ -8231,11 +8231,11 @@ var init_types = __esm({
8231
8231
  init_parseUtil();
8232
8232
  init_util2();
8233
8233
  ParseInputLazyPath = class {
8234
- constructor(parent, value, path33, key) {
8234
+ constructor(parent, value, path34, key) {
8235
8235
  this._cachedPath = [];
8236
8236
  this.parent = parent;
8237
8237
  this.data = value;
8238
- this._path = path33;
8238
+ this._path = path34;
8239
8239
  this._key = key;
8240
8240
  }
8241
8241
  get path() {
@@ -11850,10 +11850,10 @@ function assignProp(target, prop, value) {
11850
11850
  configurable: true
11851
11851
  });
11852
11852
  }
11853
- function getElementAtPath(obj, path33) {
11854
- if (!path33)
11853
+ function getElementAtPath(obj, path34) {
11854
+ if (!path34)
11855
11855
  return obj;
11856
- return path33.reduce((acc, key) => acc?.[key], obj);
11856
+ return path34.reduce((acc, key) => acc?.[key], obj);
11857
11857
  }
11858
11858
  function promiseAllObject(promisesObj) {
11859
11859
  const keys = Object.keys(promisesObj);
@@ -12102,11 +12102,11 @@ function aborted(x, startIndex = 0) {
12102
12102
  }
12103
12103
  return false;
12104
12104
  }
12105
- function prefixIssues(path33, issues) {
12105
+ function prefixIssues(path34, issues) {
12106
12106
  return issues.map((iss) => {
12107
12107
  var _a2;
12108
12108
  (_a2 = iss).path ?? (_a2.path = []);
12109
- iss.path.unshift(path33);
12109
+ iss.path.unshift(path34);
12110
12110
  return iss;
12111
12111
  });
12112
12112
  }
@@ -12295,7 +12295,7 @@ function treeifyError(error40, _mapper) {
12295
12295
  return issue2.message;
12296
12296
  };
12297
12297
  const result = { errors: [] };
12298
- const processError = (error41, path33 = []) => {
12298
+ const processError = (error41, path34 = []) => {
12299
12299
  var _a2, _b;
12300
12300
  for (const issue2 of error41.issues) {
12301
12301
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -12305,7 +12305,7 @@ function treeifyError(error40, _mapper) {
12305
12305
  } else if (issue2.code === "invalid_element") {
12306
12306
  processError({ issues: issue2.issues }, issue2.path);
12307
12307
  } else {
12308
- const fullpath = [...path33, ...issue2.path];
12308
+ const fullpath = [...path34, ...issue2.path];
12309
12309
  if (fullpath.length === 0) {
12310
12310
  result.errors.push(mapper(issue2));
12311
12311
  continue;
@@ -12335,9 +12335,9 @@ function treeifyError(error40, _mapper) {
12335
12335
  processError(error40);
12336
12336
  return result;
12337
12337
  }
12338
- function toDotPath(path33) {
12338
+ function toDotPath(path34) {
12339
12339
  const segs = [];
12340
- for (const seg of path33) {
12340
+ for (const seg of path34) {
12341
12341
  if (typeof seg === "number")
12342
12342
  segs.push(`[${seg}]`);
12343
12343
  else if (typeof seg === "symbol")
@@ -24800,8 +24800,8 @@ var init_acp = __esm({
24800
24800
  this.#requestHandler = requestHandler;
24801
24801
  this.#notificationHandler = notificationHandler;
24802
24802
  this.#stream = stream;
24803
- this.#closedPromise = new Promise((resolve16) => {
24804
- this.#abortController.signal.addEventListener("abort", () => resolve16());
24803
+ this.#closedPromise = new Promise((resolve17) => {
24804
+ this.#abortController.signal.addEventListener("abort", () => resolve17());
24805
24805
  });
24806
24806
  this.#receive();
24807
24807
  }
@@ -24950,8 +24950,8 @@ var init_acp = __esm({
24950
24950
  }
24951
24951
  async sendRequest(method, params) {
24952
24952
  const id = this.#nextRequestId++;
24953
- const responsePromise = new Promise((resolve16, reject) => {
24954
- this.#pendingResponses.set(id, { resolve: resolve16, reject });
24953
+ const responsePromise = new Promise((resolve17, reject) => {
24954
+ this.#pendingResponses.set(id, { resolve: resolve17, reject });
24955
24955
  });
24956
24956
  await this.#sendMessage({ jsonrpc: "2.0", id, method, params });
24957
24957
  return responsePromise;
@@ -25046,10 +25046,6 @@ var init_acp = __esm({
25046
25046
  }
25047
25047
  });
25048
25048
 
25049
- // src/cli.ts
25050
- import * as fs29 from "node:fs";
25051
- import * as path32 from "node:path";
25052
-
25053
25049
  // ../../node_modules/.pnpm/commander@12.1.0/node_modules/commander/esm.mjs
25054
25050
  var import_index = __toESM(require_commander(), 1);
25055
25051
  var {
@@ -25067,6 +25063,14 @@ var {
25067
25063
  Help
25068
25064
  } = import_index.default;
25069
25065
 
25066
+ // src/cli/defaults.ts
25067
+ var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
25068
+ var DEFAULT_FIREHOSE_URL = "https://buildautomaton-firehose.fly.dev";
25069
+
25070
+ // src/cli/run-cli-action.ts
25071
+ import * as fs30 from "node:fs";
25072
+ import * as path33 from "node:path";
25073
+
25070
25074
  // src/config.ts
25071
25075
  import fs from "node:fs";
25072
25076
  import path from "node:path";
@@ -25145,15 +25149,291 @@ function clearConfigForApi(apiUrl) {
25145
25149
  }
25146
25150
  }
25147
25151
 
25148
- // src/files/cwd/bridge-workspace-directory.ts
25152
+ // src/e2e-certificates/certificates.ts
25153
+ import * as fs2 from "node:fs/promises";
25149
25154
  import * as path2 from "node:path";
25155
+
25156
+ // ../e2ee/src/constants.ts
25157
+ var E2EE_ALG = "A1";
25158
+ var E2EE_KEY_BYTES = 32;
25159
+ var E2EE_NONCE_BYTES = 12;
25160
+
25161
+ // ../e2ee/src/types.ts
25162
+ function isE2eeEnvelope(value) {
25163
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
25164
+ const o = value;
25165
+ return typeof o.k === "string" && typeof o.n === "string" && typeof o.c === "string";
25166
+ }
25167
+ function isE2eeKeyRecord(value) {
25168
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
25169
+ const o = value;
25170
+ return typeof o.id === "string" && o.id.length > 0 && typeof o.name === "string" && o.name.length > 0 && o.a === E2EE_ALG && typeof o.k === "string" && o.k.length > 0 && typeof o.createdAt === "string" && o.createdAt.length > 0;
25171
+ }
25172
+
25173
+ // ../e2ee/src/encoding.ts
25174
+ function base64UrlEncode(bytes) {
25175
+ let binary = "";
25176
+ for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
25177
+ const b64 = btoa(binary);
25178
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
25179
+ }
25180
+ function base64UrlDecode(value) {
25181
+ const b64 = value.replace(/-/g, "+").replace(/_/g, "/");
25182
+ const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
25183
+ const binary = atob(padded);
25184
+ const out = new Uint8Array(binary.length);
25185
+ for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
25186
+ return out;
25187
+ }
25188
+
25189
+ // ../e2ee/src/pem.ts
25190
+ var E2EE_PEM_LABEL = "BUILD AUTOMATON E2EE KEY";
25191
+ function pemHeaderValue(headers, name) {
25192
+ return headers[name.toLowerCase()];
25193
+ }
25194
+ function encodePemBase64(bytes) {
25195
+ const raw = base64UrlEncode(bytes).replace(/-/g, "+").replace(/_/g, "/");
25196
+ const padded = raw + "=".repeat((4 - raw.length % 4) % 4);
25197
+ return padded.replace(/.{1,64}/g, "$&\n").trimEnd();
25198
+ }
25199
+ function decodePemBase64(value) {
25200
+ const compact = value.replace(/\s+/g, "");
25201
+ const binary = atob(compact);
25202
+ const out = new Uint8Array(binary.length);
25203
+ for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
25204
+ return out;
25205
+ }
25206
+ function parseE2eeKeyRecordPem(raw, fallbackName) {
25207
+ const lines = raw.trim().split(/\r?\n/);
25208
+ if (lines[0]?.trim() !== `-----BEGIN ${E2EE_PEM_LABEL}-----`) {
25209
+ throw new Error("The E2EE key file is not a BuildAutomaton PEM key.");
25210
+ }
25211
+ const endIndex = lines.findIndex((line) => line.trim() === `-----END ${E2EE_PEM_LABEL}-----`);
25212
+ if (endIndex < 0) throw new Error("The E2EE key PEM is missing its end marker.");
25213
+ const headers = {};
25214
+ const bodyLines = [];
25215
+ let readingBody = false;
25216
+ for (const line of lines.slice(1, endIndex)) {
25217
+ const trimmed2 = line.trim();
25218
+ if (!trimmed2) {
25219
+ readingBody = true;
25220
+ continue;
25221
+ }
25222
+ const headerMatch = /^([A-Za-z0-9-]+):\s*(.*)$/.exec(trimmed2);
25223
+ if (!readingBody && headerMatch) {
25224
+ headers[headerMatch[1].toLowerCase()] = headerMatch[2].trim();
25225
+ continue;
25226
+ }
25227
+ readingBody = true;
25228
+ bodyLines.push(trimmed2);
25229
+ }
25230
+ const keyBytes = decodePemBase64(bodyLines.join(""));
25231
+ if (keyBytes.byteLength !== E2EE_KEY_BYTES) throw new Error("The E2EE key PEM does not contain a 256-bit key.");
25232
+ const key = {
25233
+ id: pemHeaderValue(headers, "Key-Id") ?? "",
25234
+ name: pemHeaderValue(headers, "Name") ?? fallbackName,
25235
+ a: pemHeaderValue(headers, "Algorithm") ?? E2EE_ALG,
25236
+ k: base64UrlEncode(keyBytes),
25237
+ createdAt: pemHeaderValue(headers, "Created-At") ?? (/* @__PURE__ */ new Date()).toISOString()
25238
+ };
25239
+ if (!isE2eeKeyRecord(key)) throw new Error("The E2EE key PEM has invalid metadata.");
25240
+ return {
25241
+ ...key,
25242
+ name: key.name.trim() || fallbackName
25243
+ };
25244
+ }
25245
+ function parseE2eeKeyRecord(raw, fallbackName) {
25246
+ return parseE2eeKeyRecordPem(raw.trim(), fallbackName);
25247
+ }
25248
+ function serializeE2eeKeyRecord(key) {
25249
+ return [
25250
+ `-----BEGIN ${E2EE_PEM_LABEL}-----`,
25251
+ `Key-Id: ${key.id}`,
25252
+ `Name: ${key.name}`,
25253
+ `Algorithm: ${key.a}`,
25254
+ `Created-At: ${key.createdAt}`,
25255
+ "",
25256
+ encodePemBase64(base64UrlDecode(key.k)),
25257
+ `-----END ${E2EE_PEM_LABEL}-----`,
25258
+ ""
25259
+ ].join("\n");
25260
+ }
25261
+
25262
+ // src/lib/e2ee/cli-e2ee-key.ts
25263
+ import { createHash, randomBytes } from "node:crypto";
25264
+ function createE2eeKeyRecord(name) {
25265
+ const raw = randomBytes(E2EE_KEY_BYTES);
25266
+ const digest = createHash("sha256").update(raw).digest();
25267
+ return {
25268
+ id: base64UrlEncode(digest.subarray(0, 16)),
25269
+ name: name.trim() || "BuildAutomaton E2EE key",
25270
+ a: E2EE_ALG,
25271
+ k: base64UrlEncode(raw),
25272
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
25273
+ };
25274
+ }
25275
+
25276
+ // src/lib/e2ee/cli-e2ee-runtime.ts
25277
+ import { createCipheriv, createDecipheriv, randomBytes as randomBytes2 } from "node:crypto";
25278
+ function nonceFromCounter(prefix, counter) {
25279
+ const nonce = Buffer.alloc(E2EE_NONCE_BYTES);
25280
+ prefix.copy(nonce, 0);
25281
+ nonce.writeBigUInt64BE(counter, 4);
25282
+ return nonce;
25283
+ }
25284
+ function createCliE2eeRuntime(key) {
25285
+ const rawKey = Buffer.from(base64UrlDecode(key.k));
25286
+ const prefix = randomBytes2(4);
25287
+ let counter = 0n;
25288
+ function nextNonce() {
25289
+ counter += 1n;
25290
+ return nonceFromCounter(prefix, counter);
25291
+ }
25292
+ function encryptObject(messageWithoutSensitiveFields, plaintext) {
25293
+ const nonce = nextNonce();
25294
+ const cipher = createCipheriv("aes-256-gcm", rawKey, nonce);
25295
+ const ciphertext = Buffer.concat([cipher.update(JSON.stringify(plaintext), "utf8"), cipher.final()]);
25296
+ const tag = cipher.getAuthTag();
25297
+ return {
25298
+ k: key.id,
25299
+ n: base64UrlEncode(nonce),
25300
+ c: base64UrlEncode(Buffer.concat([ciphertext, tag]))
25301
+ };
25302
+ }
25303
+ return {
25304
+ keyId: key.id,
25305
+ handshake: { k: key.id, a: key.a },
25306
+ encryptFields(message, fields) {
25307
+ const plaintext = {};
25308
+ const stripped = { ...message };
25309
+ let hasPlaintext = false;
25310
+ for (const field of fields) {
25311
+ if (Object.prototype.hasOwnProperty.call(stripped, field) && stripped[field] !== void 0) {
25312
+ plaintext[field] = stripped[field];
25313
+ delete stripped[field];
25314
+ hasPlaintext = true;
25315
+ }
25316
+ }
25317
+ if (!hasPlaintext) return message;
25318
+ stripped.ee = encryptObject(stripped, plaintext);
25319
+ return stripped;
25320
+ },
25321
+ decryptMessage(message) {
25322
+ const envelope = message.ee;
25323
+ if (!isE2eeEnvelope(envelope)) return message;
25324
+ if (envelope.k !== key.id) throw new Error(`E2EE key mismatch: ${envelope.k}`);
25325
+ const sealed = Buffer.from(base64UrlDecode(envelope.c));
25326
+ if (sealed.length < 16) throw new Error("Invalid E2EE payload.");
25327
+ const ciphertext = sealed.subarray(0, sealed.length - 16);
25328
+ const tag = sealed.subarray(sealed.length - 16);
25329
+ const nonce = Buffer.from(base64UrlDecode(envelope.n));
25330
+ const decipher = createDecipheriv("aes-256-gcm", rawKey, nonce);
25331
+ decipher.setAuthTag(tag);
25332
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
25333
+ const parsed = JSON.parse(decrypted);
25334
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
25335
+ throw new Error("E2EE payload did not decode to an object.");
25336
+ }
25337
+ const merged = { ...message, ...parsed };
25338
+ delete merged.ee;
25339
+ return merged;
25340
+ }
25341
+ };
25342
+ }
25343
+
25344
+ // src/e2e-certificates/certificates.ts
25345
+ var E2E_CERTIFICATE_ALGORITHM = "A1";
25346
+ var CERTIFICATE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".pem", ".key", ".crt"]);
25347
+ function certificateFromKey({
25348
+ raw,
25349
+ name,
25350
+ createdAt
25351
+ }) {
25352
+ try {
25353
+ const key = parseE2eeKeyRecord(raw, name);
25354
+ return {
25355
+ ...key,
25356
+ name: key.name || name,
25357
+ algorithm: E2E_CERTIFICATE_ALGORITHM,
25358
+ pemBundle: serializeE2eeKeyRecord({ ...key, createdAt: key.createdAt || createdAt })
25359
+ };
25360
+ } catch {
25361
+ return void 0;
25362
+ }
25363
+ }
25364
+ function baseNameForCertificate(fileName) {
25365
+ return path2.basename(fileName, path2.extname(fileName)) || "BuildAutomaton E2EE certificate";
25366
+ }
25367
+ async function readCertificateFiles(directory) {
25368
+ let entries;
25369
+ try {
25370
+ entries = await fs2.readdir(directory, { withFileTypes: true });
25371
+ } catch (err) {
25372
+ const nodeErr = err;
25373
+ if (nodeErr.code === "ENOENT") return [];
25374
+ throw err;
25375
+ }
25376
+ const certificates = [];
25377
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
25378
+ if (!entry.isFile() || !CERTIFICATE_FILE_EXTENSIONS.has(path2.extname(entry.name).toLowerCase())) continue;
25379
+ const filePath = path2.join(directory, entry.name);
25380
+ const raw = await fs2.readFile(filePath, "utf8").catch(() => void 0);
25381
+ if (!raw) continue;
25382
+ const stat3 = await fs2.stat(filePath).catch(() => void 0);
25383
+ const certificate = certificateFromKey({
25384
+ raw,
25385
+ name: baseNameForCertificate(entry.name),
25386
+ createdAt: stat3?.mtime?.toISOString() ?? (/* @__PURE__ */ new Date()).toISOString()
25387
+ });
25388
+ if (certificate) {
25389
+ certificates.push(certificate);
25390
+ }
25391
+ }
25392
+ return certificates;
25393
+ }
25394
+ function createCertificate(directory) {
25395
+ const baseName = path2.basename(directory) || "BuildAutomaton E2EE certificate";
25396
+ const key = createE2eeKeyRecord(baseName);
25397
+ return {
25398
+ ...key,
25399
+ algorithm: E2E_CERTIFICATE_ALGORITHM,
25400
+ pemBundle: serializeE2eeKeyRecord(key)
25401
+ };
25402
+ }
25403
+ async function loadOrCreateE2eCertificates(directory) {
25404
+ const resolved = path2.resolve(directory);
25405
+ await fs2.mkdir(resolved, { recursive: true, mode: 448 });
25406
+ const existing = await readCertificateFiles(resolved);
25407
+ if (existing.length > 0) {
25408
+ return {
25409
+ directory: resolved,
25410
+ certificates: existing,
25411
+ activeCertificate: existing[0],
25412
+ generated: false
25413
+ };
25414
+ }
25415
+ const certificate = createCertificate(resolved);
25416
+ const fileName = `buildautomaton-e2ee-key-${certificate.id.slice(0, 12)}.pem`;
25417
+ await fs2.writeFile(path2.join(resolved, fileName), certificate.pemBundle, {
25418
+ mode: 384
25419
+ });
25420
+ return {
25421
+ directory: resolved,
25422
+ certificates: [certificate],
25423
+ activeCertificate: certificate,
25424
+ generated: true
25425
+ };
25426
+ }
25427
+
25428
+ // src/files/cwd/bridge-workspace-directory.ts
25429
+ import * as path3 from "node:path";
25150
25430
  var bridgeWorkspaceDirectory = null;
25151
25431
  function initBridgeWorkspaceDirectory() {
25152
- bridgeWorkspaceDirectory = path2.resolve(process.cwd());
25432
+ bridgeWorkspaceDirectory = path3.resolve(process.cwd());
25153
25433
  }
25154
25434
  function getBridgeWorkspaceDirectory() {
25155
25435
  if (bridgeWorkspaceDirectory == null) {
25156
- bridgeWorkspaceDirectory = path2.resolve(process.cwd());
25436
+ bridgeWorkspaceDirectory = path3.resolve(process.cwd());
25157
25437
  }
25158
25438
  return bridgeWorkspaceDirectory;
25159
25439
  }
@@ -25324,30 +25604,30 @@ function sendWsMessage(ws, payload) {
25324
25604
  // ../../node_modules/.pnpm/open@10.2.0/node_modules/open/index.js
25325
25605
  import process7 from "node:process";
25326
25606
  import { Buffer as Buffer2 } from "node:buffer";
25327
- import path3 from "node:path";
25607
+ import path4 from "node:path";
25328
25608
  import { fileURLToPath } from "node:url";
25329
25609
  import { promisify as promisify5 } from "node:util";
25330
25610
  import childProcess from "node:child_process";
25331
- import fs6, { constants as fsConstants2 } from "node:fs/promises";
25611
+ import fs7, { constants as fsConstants2 } from "node:fs/promises";
25332
25612
 
25333
25613
  // ../../node_modules/.pnpm/wsl-utils@0.1.0/node_modules/wsl-utils/index.js
25334
25614
  import process3 from "node:process";
25335
- import fs5, { constants as fsConstants } from "node:fs/promises";
25615
+ import fs6, { constants as fsConstants } from "node:fs/promises";
25336
25616
 
25337
25617
  // ../../node_modules/.pnpm/is-wsl@3.1.1/node_modules/is-wsl/index.js
25338
25618
  import process2 from "node:process";
25339
25619
  import os2 from "node:os";
25340
- import fs4 from "node:fs";
25620
+ import fs5 from "node:fs";
25341
25621
 
25342
25622
  // ../../node_modules/.pnpm/is-inside-container@1.0.0/node_modules/is-inside-container/index.js
25343
- import fs3 from "node:fs";
25623
+ import fs4 from "node:fs";
25344
25624
 
25345
25625
  // ../../node_modules/.pnpm/is-docker@3.0.0/node_modules/is-docker/index.js
25346
- import fs2 from "node:fs";
25626
+ import fs3 from "node:fs";
25347
25627
  var isDockerCached;
25348
25628
  function hasDockerEnv() {
25349
25629
  try {
25350
- fs2.statSync("/.dockerenv");
25630
+ fs3.statSync("/.dockerenv");
25351
25631
  return true;
25352
25632
  } catch {
25353
25633
  return false;
@@ -25355,7 +25635,7 @@ function hasDockerEnv() {
25355
25635
  }
25356
25636
  function hasDockerCGroup() {
25357
25637
  try {
25358
- return fs2.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
25638
+ return fs3.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
25359
25639
  } catch {
25360
25640
  return false;
25361
25641
  }
@@ -25371,7 +25651,7 @@ function isDocker() {
25371
25651
  var cachedResult;
25372
25652
  var hasContainerEnv = () => {
25373
25653
  try {
25374
- fs3.statSync("/run/.containerenv");
25654
+ fs4.statSync("/run/.containerenv");
25375
25655
  return true;
25376
25656
  } catch {
25377
25657
  return false;
@@ -25396,12 +25676,12 @@ var isWsl = () => {
25396
25676
  return true;
25397
25677
  }
25398
25678
  try {
25399
- if (fs4.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft")) {
25679
+ if (fs5.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft")) {
25400
25680
  return !isInsideContainer();
25401
25681
  }
25402
25682
  } catch {
25403
25683
  }
25404
- if (fs4.existsSync("/proc/sys/fs/binfmt_misc/WSLInterop") || fs4.existsSync("/run/WSL")) {
25684
+ if (fs5.existsSync("/proc/sys/fs/binfmt_misc/WSLInterop") || fs5.existsSync("/run/WSL")) {
25405
25685
  return !isInsideContainer();
25406
25686
  }
25407
25687
  return false;
@@ -25419,14 +25699,14 @@ var wslDrivesMountPoint = /* @__PURE__ */ (() => {
25419
25699
  const configFilePath2 = "/etc/wsl.conf";
25420
25700
  let isConfigFileExists = false;
25421
25701
  try {
25422
- await fs5.access(configFilePath2, fsConstants.F_OK);
25702
+ await fs6.access(configFilePath2, fsConstants.F_OK);
25423
25703
  isConfigFileExists = true;
25424
25704
  } catch {
25425
25705
  }
25426
25706
  if (!isConfigFileExists) {
25427
25707
  return defaultMountPoint;
25428
25708
  }
25429
- const configContent = await fs5.readFile(configFilePath2, { encoding: "utf8" });
25709
+ const configContent = await fs6.readFile(configFilePath2, { encoding: "utf8" });
25430
25710
  const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
25431
25711
  if (!configMountPoint) {
25432
25712
  return defaultMountPoint;
@@ -25580,8 +25860,8 @@ async function defaultBrowser2() {
25580
25860
 
25581
25861
  // ../../node_modules/.pnpm/open@10.2.0/node_modules/open/index.js
25582
25862
  var execFile5 = promisify5(childProcess.execFile);
25583
- var __dirname = path3.dirname(fileURLToPath(import.meta.url));
25584
- var localXdgOpenPath = path3.join(__dirname, "xdg-open");
25863
+ var __dirname = path4.dirname(fileURLToPath(import.meta.url));
25864
+ var localXdgOpenPath = path4.join(__dirname, "xdg-open");
25585
25865
  var { platform, arch } = process7;
25586
25866
  async function getWindowsDefaultBrowserFromWsl() {
25587
25867
  const powershellPath = await powerShellPath();
@@ -25731,7 +26011,7 @@ var baseOpen = async (options) => {
25731
26011
  const isBundled = !__dirname || __dirname === "/";
25732
26012
  let exeLocalXdgOpen = false;
25733
26013
  try {
25734
- await fs6.access(localXdgOpenPath, fsConstants2.X_OK);
26014
+ await fs7.access(localXdgOpenPath, fsConstants2.X_OK);
25735
26015
  exeLocalXdgOpen = true;
25736
26016
  } catch {
25737
26017
  }
@@ -25754,14 +26034,14 @@ var baseOpen = async (options) => {
25754
26034
  }
25755
26035
  const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
25756
26036
  if (options.wait) {
25757
- return new Promise((resolve16, reject) => {
26037
+ return new Promise((resolve17, reject) => {
25758
26038
  subprocess.once("error", reject);
25759
26039
  subprocess.once("close", (exitCode) => {
25760
26040
  if (!options.allowNonzeroExitCode && exitCode > 0) {
25761
26041
  reject(new Error(`Exited with code ${exitCode}`));
25762
26042
  return;
25763
26043
  }
25764
- resolve16(subprocess);
26044
+ resolve17(subprocess);
25765
26045
  });
25766
26046
  });
25767
26047
  }
@@ -25845,8 +26125,11 @@ function isLocalApiUrl(apiUrl) {
25845
26125
  return false;
25846
26126
  }
25847
26127
  }
26128
+ function appUrlForApiUrl(apiUrl) {
26129
+ return apiUrl && isLocalApiUrl(apiUrl) ? process.env.BUILDAUTOMATON_APP_URL ?? "http://localhost:3000" : process.env.BUILDAUTOMATON_APP_URL ?? "https://app.buildautomaton.com";
26130
+ }
25848
26131
  async function openBrowser(connectionId, initialWorkspaceId, preferredBridgeName, apiUrl, logFn = log) {
25849
- const appUrl = apiUrl && isLocalApiUrl(apiUrl) ? process.env.BUILDAUTOMATON_APP_URL ?? "http://localhost:3000" : process.env.BUILDAUTOMATON_APP_URL ?? "https://app.buildautomaton.com";
26132
+ const appUrl = appUrlForApiUrl(apiUrl);
25850
26133
  let connectCliUrl = `${appUrl.replace(/\/$/, "")}/bridges/connect?connectionId=${connectionId}`;
25851
26134
  if (initialWorkspaceId) {
25852
26135
  try {
@@ -26054,8 +26337,8 @@ function runPendingAuth(options) {
26054
26337
  let hasOpenedBrowser = false;
26055
26338
  let resolved = false;
26056
26339
  let resolveAuth;
26057
- const authPromise = new Promise((resolve16) => {
26058
- resolveAuth = resolve16;
26340
+ const authPromise = new Promise((resolve17) => {
26341
+ resolveAuth = resolve17;
26059
26342
  });
26060
26343
  let reconnectAttempt = 0;
26061
26344
  const signInQuiet = createEmptyReconnectQuietSlot();
@@ -26177,7 +26460,7 @@ function runPendingAuth(options) {
26177
26460
  async function closeBridgeConnection(state, acpManager, devServerManager, log2) {
26178
26461
  const say = log2 ?? logImmediate;
26179
26462
  say("Cleaning up connections\u2026");
26180
- await new Promise((resolve16) => setImmediate(resolve16));
26463
+ await new Promise((resolve17) => setImmediate(resolve17));
26181
26464
  state.closedByUser = true;
26182
26465
  clearReconnectQuietTimer(state.mainQuiet);
26183
26466
  clearReconnectQuietTimer(state.firehoseQuiet);
@@ -26248,19 +26531,19 @@ function localAgentErrorSuggestsAuth(agentType, errorText) {
26248
26531
 
26249
26532
  // src/git/session-git-queue.ts
26250
26533
  import { execFile as execFile7 } from "node:child_process";
26251
- import { readFile, stat } from "node:fs/promises";
26534
+ import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
26252
26535
  import { promisify as promisify7 } from "node:util";
26253
- import * as path6 from "node:path";
26536
+ import * as path7 from "node:path";
26254
26537
 
26255
26538
  // src/git/pre-turn-snapshot.ts
26256
- import * as fs8 from "node:fs";
26257
- import * as path5 from "node:path";
26539
+ import * as fs9 from "node:fs";
26540
+ import * as path6 from "node:path";
26258
26541
  import { execFile as execFile6 } from "node:child_process";
26259
26542
  import { promisify as promisify6 } from "node:util";
26260
26543
 
26261
26544
  // src/git/discover-repos.ts
26262
- import * as fs7 from "node:fs";
26263
- import * as path4 from "node:path";
26545
+ import * as fs8 from "node:fs";
26546
+ import * as path5 from "node:path";
26264
26547
 
26265
26548
  // ../../node_modules/.pnpm/simple-git@3.32.3/node_modules/simple-git/dist/esm/index.js
26266
26549
  var import_file_exists = __toESM(require_dist(), 1);
@@ -26299,8 +26582,8 @@ function pathspec(...paths) {
26299
26582
  cache.set(key, paths);
26300
26583
  return key;
26301
26584
  }
26302
- function isPathSpec(path33) {
26303
- return path33 instanceof String && cache.has(path33);
26585
+ function isPathSpec(path34) {
26586
+ return path34 instanceof String && cache.has(path34);
26304
26587
  }
26305
26588
  function toPaths(pathSpec) {
26306
26589
  return cache.get(pathSpec) || [];
@@ -26389,8 +26672,8 @@ function toLinesWithContent(input = "", trimmed2 = true, separator = "\n") {
26389
26672
  function forEachLineWithContent(input, callback) {
26390
26673
  return toLinesWithContent(input, true).map((line) => callback(line));
26391
26674
  }
26392
- function folderExists(path33) {
26393
- return (0, import_file_exists.exists)(path33, import_file_exists.FOLDER);
26675
+ function folderExists(path34) {
26676
+ return (0, import_file_exists.exists)(path34, import_file_exists.FOLDER);
26394
26677
  }
26395
26678
  function append(target, item) {
26396
26679
  if (Array.isArray(target)) {
@@ -26794,8 +27077,8 @@ function checkIsRepoRootTask() {
26794
27077
  commands,
26795
27078
  format: "utf-8",
26796
27079
  onError,
26797
- parser(path33) {
26798
- return /^\.(git)?$/.test(path33.trim());
27080
+ parser(path34) {
27081
+ return /^\.(git)?$/.test(path34.trim());
26799
27082
  }
26800
27083
  };
26801
27084
  }
@@ -27229,11 +27512,11 @@ function parseGrep(grep) {
27229
27512
  const paths = /* @__PURE__ */ new Set();
27230
27513
  const results = {};
27231
27514
  forEachLineWithContent(grep, (input) => {
27232
- const [path33, line, preview] = input.split(NULL);
27233
- paths.add(path33);
27234
- (results[path33] = results[path33] || []).push({
27515
+ const [path34, line, preview] = input.split(NULL);
27516
+ paths.add(path34);
27517
+ (results[path34] = results[path34] || []).push({
27235
27518
  line: asNumber(line),
27236
- path: path33,
27519
+ path: path34,
27237
27520
  preview
27238
27521
  });
27239
27522
  });
@@ -27998,14 +28281,14 @@ var init_hash_object = __esm2({
27998
28281
  init_task();
27999
28282
  }
28000
28283
  });
28001
- function parseInit(bare, path33, text) {
28284
+ function parseInit(bare, path34, text) {
28002
28285
  const response = String(text).trim();
28003
28286
  let result;
28004
28287
  if (result = initResponseRegex.exec(response)) {
28005
- return new InitSummary(bare, path33, false, result[1]);
28288
+ return new InitSummary(bare, path34, false, result[1]);
28006
28289
  }
28007
28290
  if (result = reInitResponseRegex.exec(response)) {
28008
- return new InitSummary(bare, path33, true, result[1]);
28291
+ return new InitSummary(bare, path34, true, result[1]);
28009
28292
  }
28010
28293
  let gitDir = "";
28011
28294
  const tokens = response.split(" ");
@@ -28016,7 +28299,7 @@ function parseInit(bare, path33, text) {
28016
28299
  break;
28017
28300
  }
28018
28301
  }
28019
- return new InitSummary(bare, path33, /^re/i.test(response), gitDir);
28302
+ return new InitSummary(bare, path34, /^re/i.test(response), gitDir);
28020
28303
  }
28021
28304
  var InitSummary;
28022
28305
  var initResponseRegex;
@@ -28025,9 +28308,9 @@ var init_InitSummary = __esm2({
28025
28308
  "src/lib/responses/InitSummary.ts"() {
28026
28309
  "use strict";
28027
28310
  InitSummary = class {
28028
- constructor(bare, path33, existing, gitDir) {
28311
+ constructor(bare, path34, existing, gitDir) {
28029
28312
  this.bare = bare;
28030
- this.path = path33;
28313
+ this.path = path34;
28031
28314
  this.existing = existing;
28032
28315
  this.gitDir = gitDir;
28033
28316
  }
@@ -28039,7 +28322,7 @@ var init_InitSummary = __esm2({
28039
28322
  function hasBareCommand(command) {
28040
28323
  return command.includes(bareCommand);
28041
28324
  }
28042
- function initTask(bare = false, path33, customArgs) {
28325
+ function initTask(bare = false, path34, customArgs) {
28043
28326
  const commands = ["init", ...customArgs];
28044
28327
  if (bare && !hasBareCommand(commands)) {
28045
28328
  commands.splice(1, 0, bareCommand);
@@ -28048,7 +28331,7 @@ function initTask(bare = false, path33, customArgs) {
28048
28331
  commands,
28049
28332
  format: "utf-8",
28050
28333
  parser(text) {
28051
- return parseInit(commands.includes("--bare"), path33, text);
28334
+ return parseInit(commands.includes("--bare"), path34, text);
28052
28335
  }
28053
28336
  };
28054
28337
  }
@@ -28864,12 +29147,12 @@ var init_FileStatusSummary = __esm2({
28864
29147
  "use strict";
28865
29148
  fromPathRegex = /^(.+)\0(.+)$/;
28866
29149
  FileStatusSummary = class {
28867
- constructor(path33, index, working_dir) {
28868
- this.path = path33;
29150
+ constructor(path34, index, working_dir) {
29151
+ this.path = path34;
28869
29152
  this.index = index;
28870
29153
  this.working_dir = working_dir;
28871
29154
  if (index === "R" || working_dir === "R") {
28872
- const detail = fromPathRegex.exec(path33) || [null, path33, path33];
29155
+ const detail = fromPathRegex.exec(path34) || [null, path34, path34];
28873
29156
  this.from = detail[2] || "";
28874
29157
  this.path = detail[1] || "";
28875
29158
  }
@@ -28900,14 +29183,14 @@ function splitLine(result, lineStr) {
28900
29183
  default:
28901
29184
  return;
28902
29185
  }
28903
- function data(index, workingDir, path33) {
29186
+ function data(index, workingDir, path34) {
28904
29187
  const raw = `${index}${workingDir}`;
28905
29188
  const handler = parsers6.get(raw);
28906
29189
  if (handler) {
28907
- handler(result, path33);
29190
+ handler(result, path34);
28908
29191
  }
28909
29192
  if (raw !== "##" && raw !== "!!") {
28910
- result.files.push(new FileStatusSummary(path33, index, workingDir));
29193
+ result.files.push(new FileStatusSummary(path34, index, workingDir));
28911
29194
  }
28912
29195
  }
28913
29196
  }
@@ -29216,9 +29499,9 @@ var init_simple_git_api = __esm2({
29216
29499
  next
29217
29500
  );
29218
29501
  }
29219
- hashObject(path33, write) {
29502
+ hashObject(path34, write) {
29220
29503
  return this._runTask(
29221
- hashObjectTask(path33, write === true),
29504
+ hashObjectTask(path34, write === true),
29222
29505
  trailingFunctionArgument(arguments)
29223
29506
  );
29224
29507
  }
@@ -29571,8 +29854,8 @@ var init_branch = __esm2({
29571
29854
  }
29572
29855
  });
29573
29856
  function toPath(input) {
29574
- const path33 = input.trim().replace(/^["']|["']$/g, "");
29575
- return path33 && normalize(path33);
29857
+ const path34 = input.trim().replace(/^["']|["']$/g, "");
29858
+ return path34 && normalize(path34);
29576
29859
  }
29577
29860
  var parseCheckIgnore;
29578
29861
  var init_CheckIgnore = __esm2({
@@ -29886,8 +30169,8 @@ __export2(sub_module_exports, {
29886
30169
  subModuleTask: () => subModuleTask,
29887
30170
  updateSubModuleTask: () => updateSubModuleTask
29888
30171
  });
29889
- function addSubModuleTask(repo, path33) {
29890
- return subModuleTask(["add", repo, path33]);
30172
+ function addSubModuleTask(repo, path34) {
30173
+ return subModuleTask(["add", repo, path34]);
29891
30174
  }
29892
30175
  function initSubModuleTask(customArgs) {
29893
30176
  return subModuleTask(["init", ...customArgs]);
@@ -30220,8 +30503,8 @@ var require_git = __commonJS2({
30220
30503
  }
30221
30504
  return this._runTask(straightThroughStringTask2(command, this._trimmed), next);
30222
30505
  };
30223
- Git2.prototype.submoduleAdd = function(repo, path33, then) {
30224
- return this._runTask(addSubModuleTask2(repo, path33), trailingFunctionArgument2(arguments));
30506
+ Git2.prototype.submoduleAdd = function(repo, path34, then) {
30507
+ return this._runTask(addSubModuleTask2(repo, path34), trailingFunctionArgument2(arguments));
30225
30508
  };
30226
30509
  Git2.prototype.submoduleUpdate = function(args, then) {
30227
30510
  return this._runTask(
@@ -30847,20 +31130,20 @@ async function isGitRepoDirectory(dirPath) {
30847
31130
  // src/git/discover-repos.ts
30848
31131
  async function discoverGitRepos(cwd = getBridgeWorkspaceDirectory()) {
30849
31132
  const result = [];
30850
- const cwdResolved = path4.resolve(cwd);
31133
+ const cwdResolved = path5.resolve(cwd);
30851
31134
  if (await isGitRepoDirectory(cwdResolved)) {
30852
31135
  const remoteUrl = await getRemoteOriginUrl(cwdResolved);
30853
31136
  result.push({ absolutePath: cwdResolved, remoteUrl });
30854
31137
  }
30855
31138
  let entries;
30856
31139
  try {
30857
- entries = fs7.readdirSync(cwdResolved, { withFileTypes: true });
31140
+ entries = fs8.readdirSync(cwdResolved, { withFileTypes: true });
30858
31141
  } catch {
30859
31142
  return result;
30860
31143
  }
30861
31144
  for (const ent of entries) {
30862
31145
  if (!ent.isDirectory()) continue;
30863
- const childPath = path4.join(cwdResolved, ent.name);
31146
+ const childPath = path5.join(cwdResolved, ent.name);
30864
31147
  if (await isGitRepoDirectory(childPath)) {
30865
31148
  const remoteUrl = await getRemoteOriginUrl(childPath);
30866
31149
  result.push({ absolutePath: childPath, remoteUrl });
@@ -30869,22 +31152,22 @@ async function discoverGitRepos(cwd = getBridgeWorkspaceDirectory()) {
30869
31152
  return result;
30870
31153
  }
30871
31154
  async function discoverGitReposUnderRoot(rootAbs) {
30872
- const root = path4.resolve(rootAbs);
31155
+ const root = path5.resolve(rootAbs);
30873
31156
  const roots = [];
30874
31157
  async function walk(dir) {
30875
31158
  if (await isGitRepoDirectory(dir)) {
30876
- roots.push(path4.resolve(dir));
31159
+ roots.push(path5.resolve(dir));
30877
31160
  return;
30878
31161
  }
30879
31162
  let entries;
30880
31163
  try {
30881
- entries = fs7.readdirSync(dir, { withFileTypes: true });
31164
+ entries = fs8.readdirSync(dir, { withFileTypes: true });
30882
31165
  } catch {
30883
31166
  return;
30884
31167
  }
30885
31168
  for (const ent of entries) {
30886
31169
  if (!ent.isDirectory() || ent.name === ".git") continue;
30887
- await walk(path4.join(dir, ent.name));
31170
+ await walk(path5.join(dir, ent.name));
30888
31171
  }
30889
31172
  }
30890
31173
  await walk(root);
@@ -30900,7 +31183,7 @@ async function discoverGitReposUnderRoot(rootAbs) {
30900
31183
  // src/git/pre-turn-snapshot.ts
30901
31184
  var execFileAsync5 = promisify6(execFile6);
30902
31185
  function snapshotsDirForCwd(agentCwd) {
30903
- return path5.join(agentCwd, ".buildautomaton", "snapshots");
31186
+ return path6.join(agentCwd, ".buildautomaton", "snapshots");
30904
31187
  }
30905
31188
  async function gitStashCreate(repoRoot, log2) {
30906
31189
  try {
@@ -30929,7 +31212,7 @@ async function gitRun(repoRoot, args, log2, label) {
30929
31212
  async function resolveSnapshotRepoRoots(options) {
30930
31213
  const { worktreePaths, fallbackCwd, log: log2 } = options;
30931
31214
  if (worktreePaths?.length) {
30932
- const uniq = [...new Set(worktreePaths.map((p) => path5.resolve(p)))];
31215
+ const uniq = [...new Set(worktreePaths.map((p) => path6.resolve(p)))];
30933
31216
  return uniq;
30934
31217
  }
30935
31218
  try {
@@ -30952,7 +31235,7 @@ async function capturePreTurnSnapshot(options) {
30952
31235
  }
30953
31236
  const dir = snapshotsDirForCwd(agentCwd);
30954
31237
  try {
30955
- fs8.mkdirSync(dir, { recursive: true });
31238
+ fs9.mkdirSync(dir, { recursive: true });
30956
31239
  } catch (e) {
30957
31240
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
30958
31241
  }
@@ -30961,9 +31244,9 @@ async function capturePreTurnSnapshot(options) {
30961
31244
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
30962
31245
  repos
30963
31246
  };
30964
- const filePath = path5.join(dir, `${runId}.json`);
31247
+ const filePath = path6.join(dir, `${runId}.json`);
30965
31248
  try {
30966
- fs8.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
31249
+ fs9.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
30967
31250
  } catch (e) {
30968
31251
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
30969
31252
  }
@@ -30976,7 +31259,7 @@ async function capturePreTurnSnapshot(options) {
30976
31259
  async function applyPreTurnSnapshot(filePath, log2) {
30977
31260
  let data;
30978
31261
  try {
30979
- const raw = fs8.readFileSync(filePath, "utf8");
31262
+ const raw = fs9.readFileSync(filePath, "utf8");
30980
31263
  data = JSON.parse(raw);
30981
31264
  } catch (e) {
30982
31265
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
@@ -30999,7 +31282,7 @@ async function applyPreTurnSnapshot(filePath, log2) {
30999
31282
  return { ok: true };
31000
31283
  }
31001
31284
  function snapshotFilePath(agentCwd, runId) {
31002
- return path5.join(snapshotsDirForCwd(agentCwd), `${runId}.json`);
31285
+ return path6.join(snapshotsDirForCwd(agentCwd), `${runId}.json`);
31003
31286
  }
31004
31287
 
31005
31288
  // src/git/session-git-queue.ts
@@ -31007,9 +31290,9 @@ var execFileAsync6 = promisify7(execFile7);
31007
31290
  var MAX_FULL_FILE_TEXT_BYTES = 512 * 1024;
31008
31291
  async function readWorkspaceFileAsUtf8(absPath) {
31009
31292
  try {
31010
- const st = await stat(absPath);
31293
+ const st = await stat2(absPath);
31011
31294
  if (!st.isFile() || st.size > MAX_FULL_FILE_TEXT_BYTES) return void 0;
31012
- return await readFile(absPath, "utf8");
31295
+ return await readFile2(absPath, "utf8");
31013
31296
  } catch {
31014
31297
  return void 0;
31015
31298
  }
@@ -31019,7 +31302,7 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
31019
31302
  const filePath = snapshotFilePath(agentCwd, runId);
31020
31303
  let data;
31021
31304
  try {
31022
- const raw = await readFile(filePath, "utf8");
31305
+ const raw = await readFile2(filePath, "utf8");
31023
31306
  data = JSON.parse(raw);
31024
31307
  } catch (e) {
31025
31308
  log2(
@@ -31048,7 +31331,7 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
31048
31331
  continue;
31049
31332
  }
31050
31333
  const lines = namesRaw.split("\n").map((l) => l.trim()).filter(Boolean);
31051
- const slug = path6.basename(repo.path).replace(/[^\w.-]+/g, "_") || "repo";
31334
+ const slug = path7.basename(repo.path).replace(/[^\w.-]+/g, "_") || "repo";
31052
31335
  for (const rel of lines) {
31053
31336
  if (rel.includes("..")) continue;
31054
31337
  try {
@@ -31062,7 +31345,7 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
31062
31345
  );
31063
31346
  if (!patchContent.trim()) continue;
31064
31347
  const displayPath = multiRepo ? `${slug}/${rel}` : rel;
31065
- const absFile = path6.join(repo.path, rel);
31348
+ const absFile = path7.join(repo.path, rel);
31066
31349
  const newText = await readWorkspaceFileAsUtf8(absFile);
31067
31350
  sendSessionUpdate({
31068
31351
  type: "session_file_change",
@@ -31081,6 +31364,498 @@ async function collectTurnGitDiffFromPreTurnSnapshot(options) {
31081
31364
  }
31082
31365
  }
31083
31366
 
31367
+ // ../types/dist/index.js
31368
+ init_zod();
31369
+ init_zod();
31370
+ init_zod();
31371
+ init_zod();
31372
+ init_zod();
31373
+ init_zod();
31374
+ init_zod();
31375
+ init_zod();
31376
+ init_zod();
31377
+ init_zod();
31378
+ init_zod();
31379
+ init_zod();
31380
+ var WorkItemStatusSchema = external_exports.enum(["backlog", "in-progress", "completed"]);
31381
+ var WorkItemProgressSchema = external_exports.object({
31382
+ remainingCriteria: external_exports.array(external_exports.string()).default([]),
31383
+ openQuestions: external_exports.array(external_exports.string()).default([]),
31384
+ assignedTo: external_exports.enum(["agent", "human-product", "human-expert"]).optional()
31385
+ });
31386
+ var ChangeSchema = external_exports.object({
31387
+ id: external_exports.string(),
31388
+ description: external_exports.string(),
31389
+ buildingBlockId: external_exports.string(),
31390
+ buildingBlockType: external_exports.enum(["function", "workflow", "connector", "ui-component", "app-fragment", "application", "project"]),
31391
+ action: external_exports.enum(["create", "update", "split", "combine"])
31392
+ });
31393
+ var CompletionCriterionSchema = external_exports.object({
31394
+ id: external_exports.string(),
31395
+ description: external_exports.string(),
31396
+ type: external_exports.enum(["write-code", "write-tests", "verify-tests", "other"]),
31397
+ verified: external_exports.boolean().default(false)
31398
+ });
31399
+ var WorkItemPrioritySchema = external_exports.enum(["low", "medium", "high", "critical"]);
31400
+ var IterationPhaseSchema = external_exports.enum(["analysis", "implementation", "verify", "reprioritize", "completed"]);
31401
+ var WorkItemDependencySchema = external_exports.object({
31402
+ type: external_exports.enum(["work-item"]),
31403
+ id: external_exports.string()
31404
+ });
31405
+ var WorkItemSchema = external_exports.object({
31406
+ id: external_exports.string(),
31407
+ sessionId: external_exports.string().optional(),
31408
+ summary: external_exports.string().optional(),
31409
+ description: external_exports.string(),
31410
+ status: WorkItemStatusSchema,
31411
+ buildingBlockId: external_exports.string().optional(),
31412
+ buildingBlockType: external_exports.enum(["function", "workflow", "connector", "ui-component", "app-fragment", "application", "project"]),
31413
+ changes: external_exports.array(ChangeSchema).default([]),
31414
+ completionCriteria: external_exports.array(CompletionCriterionSchema).default([]),
31415
+ priority: WorkItemPrioritySchema.default("medium"),
31416
+ dependencies: external_exports.array(WorkItemDependencySchema).default([]),
31417
+ assignedToUserId: external_exports.string().optional()
31418
+ });
31419
+ var UserWorkspaceProfileSchema = external_exports.object({
31420
+ id: external_exports.string(),
31421
+ workspaceId: external_exports.string(),
31422
+ userId: external_exports.string(),
31423
+ roleDescription: external_exports.string().optional(),
31424
+ expertiseAreas: external_exports.array(external_exports.string()),
31425
+ preferences: external_exports.record(external_exports.unknown()).optional(),
31426
+ learnings: external_exports.array(external_exports.string())
31427
+ });
31428
+ var WorkspaceOwnerInfoSchema = external_exports.object({
31429
+ ownerId: external_exports.string(),
31430
+ ownerName: external_exports.string().optional(),
31431
+ ownerEmail: external_exports.string().optional(),
31432
+ ownerProfilePictureUrl: external_exports.string().optional()
31433
+ });
31434
+ var WorkspaceRuntimeEntrySchema = external_exports.object({
31435
+ workspaceId: external_exports.string(),
31436
+ path: external_exports.string(),
31437
+ name: external_exports.string().optional(),
31438
+ owner: WorkspaceOwnerInfoSchema.optional(),
31439
+ isOwner: external_exports.boolean().optional()
31440
+ });
31441
+ var ProjectContextSchema = external_exports.object({
31442
+ projectId: external_exports.string(),
31443
+ context: external_exports.record(external_exports.unknown()).default({}),
31444
+ updatedAt: external_exports.string()
31445
+ });
31446
+ var WebSocketMessageTypeSchema = external_exports.enum([
31447
+ "plan-update",
31448
+ "work-item-update",
31449
+ "work-item-added",
31450
+ "work-item-removed",
31451
+ "project-processing-start",
31452
+ "project-processing-update",
31453
+ "project-processing-complete",
31454
+ "project-processing-error",
31455
+ "file-tool-request",
31456
+ "file-tool-response",
31457
+ "file-generated"
31458
+ ]);
31459
+ var WebSocketMessageSchema = external_exports.object({
31460
+ type: WebSocketMessageTypeSchema,
31461
+ contextId: external_exports.string().optional(),
31462
+ data: external_exports.any().optional(),
31463
+ error: external_exports.string().optional()
31464
+ });
31465
+ var CheckpointKindSchema = external_exports.enum(["daily", "weekly", "overall"]);
31466
+ var CheckpointSummarySchema = external_exports.object({
31467
+ id: external_exports.string(),
31468
+ kind: CheckpointKindSchema,
31469
+ /** ISO date for daily (YYYY-MM-DD), ISO week for weekly, null for overall */
31470
+ periodKey: external_exports.string().nullable(),
31471
+ summary: external_exports.string(),
31472
+ createdAt: external_exports.string(),
31473
+ updatedAt: external_exports.string()
31474
+ });
31475
+ var ThreadMetaSchema = external_exports.object({
31476
+ threadId: external_exports.string(),
31477
+ workspaceId: external_exports.string(),
31478
+ /** External source (e.g. slack, discord); null if internal-only */
31479
+ externalSource: external_exports.string().nullable(),
31480
+ /** Id in the external system (e.g. channel_id + thread_ts) */
31481
+ externalId: external_exports.string().nullable(),
31482
+ title: external_exports.string().optional(),
31483
+ createdAt: external_exports.string(),
31484
+ updatedAt: external_exports.string()
31485
+ });
31486
+ var ThreadMessageSchema = external_exports.object({
31487
+ messageId: external_exports.string(),
31488
+ threadId: external_exports.string(),
31489
+ /** Role: user, assistant, system */
31490
+ role: external_exports.enum(["user", "assistant", "system"]),
31491
+ content: external_exports.string(),
31492
+ /** Optional reference to a ContentItem (e.g. doc, Notion page) */
31493
+ contentItemId: external_exports.string().nullable(),
31494
+ /** External message id if synced from external chat */
31495
+ externalId: external_exports.string().nullable(),
31496
+ createdAt: external_exports.string(),
31497
+ updatedAt: external_exports.string()
31498
+ });
31499
+ var ThreadCheckpointSummarySchema = CheckpointSummarySchema.extend({
31500
+ threadId: external_exports.string()
31501
+ });
31502
+ var ContentSourceSchema = external_exports.enum(["notion", "doc", "slack_thread", "other"]);
31503
+ var ContentItemMetaSchema = external_exports.object({
31504
+ contentId: external_exports.string(),
31505
+ workspaceId: external_exports.string(),
31506
+ source: ContentSourceSchema,
31507
+ /** Id in the external system (e.g. Notion page id, doc url) */
31508
+ externalId: external_exports.string(),
31509
+ /** If source is slack_thread, points to Thread DO id */
31510
+ threadId: external_exports.string().nullable(),
31511
+ title: external_exports.string().optional(),
31512
+ createdAt: external_exports.string(),
31513
+ updatedAt: external_exports.string()
31514
+ });
31515
+ var ContentStorageRefSchema = external_exports.object({
31516
+ storageKey: external_exports.string(),
31517
+ /** Optional: mime type or format hint */
31518
+ contentType: external_exports.string().optional()
31519
+ });
31520
+ var ContentCheckpointSummarySchema = CheckpointSummarySchema.extend({
31521
+ contentId: external_exports.string()
31522
+ });
31523
+ var StoryMetaSchema = external_exports.object({
31524
+ storyId: external_exports.string(),
31525
+ workspaceId: external_exports.string(),
31526
+ title: external_exports.string(),
31527
+ /** feature | bug | epic */
31528
+ kind: external_exports.enum(["feature", "bug", "epic"]).default("feature"),
31529
+ createdAt: external_exports.string(),
31530
+ updatedAt: external_exports.string()
31531
+ });
31532
+ var StoryContentItemRefSchema = external_exports.object({
31533
+ id: external_exports.string(),
31534
+ storyId: external_exports.string(),
31535
+ contentItemId: external_exports.string(),
31536
+ /** Snapshot summary when added to story (or updated) */
31537
+ summary: external_exports.string(),
31538
+ orderIndex: external_exports.number().default(0),
31539
+ createdAt: external_exports.string(),
31540
+ updatedAt: external_exports.string()
31541
+ });
31542
+ var StoryCheckpointSummarySchema = CheckpointSummarySchema.extend({
31543
+ storyId: external_exports.string()
31544
+ });
31545
+ var BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID = "__builtin_change_summary__";
31546
+ var SessionMetaSchema = external_exports.object({
31547
+ sessionId: external_exports.string(),
31548
+ workspaceId: external_exports.string(),
31549
+ title: external_exports.string().optional(),
31550
+ createdAt: external_exports.string(),
31551
+ updatedAt: external_exports.string()
31552
+ });
31553
+ var SessionPromptSchema = external_exports.object({
31554
+ id: external_exports.string(),
31555
+ sessionId: external_exports.string(),
31556
+ /** text | resource */
31557
+ type: external_exports.enum(["text", "resource"]).default("text"),
31558
+ text: external_exports.string().optional(),
31559
+ resourceUri: external_exports.string().optional(),
31560
+ createdAt: external_exports.string()
31561
+ });
31562
+ var SessionResponseSchema = external_exports.object({
31563
+ id: external_exports.string(),
31564
+ sessionId: external_exports.string(),
31565
+ promptId: external_exports.string(),
31566
+ /** message | completion */
31567
+ kind: external_exports.enum(["message", "completion"]),
31568
+ content: external_exports.string().optional(),
31569
+ /** For completion: stopReason etc. */
31570
+ stopReason: external_exports.string().optional(),
31571
+ createdAt: external_exports.string()
31572
+ });
31573
+ var SessionToolCallSchema = external_exports.object({
31574
+ id: external_exports.string(),
31575
+ sessionId: external_exports.string(),
31576
+ promptId: external_exports.string(),
31577
+ name: external_exports.string(),
31578
+ params: external_exports.record(external_exports.unknown()).optional(),
31579
+ result: external_exports.record(external_exports.unknown()).optional(),
31580
+ createdAt: external_exports.string()
31581
+ });
31582
+ var SessionThreadRefSchema = external_exports.object({
31583
+ sessionId: external_exports.string(),
31584
+ threadId: external_exports.string(),
31585
+ addedAt: external_exports.string()
31586
+ });
31587
+ function normalizeRepoRelativePath(p) {
31588
+ let t = p.trim().replace(/\\/g, "/");
31589
+ while (t.startsWith("./")) t = t.slice(2);
31590
+ return t.replace(/\/+/g, "/");
31591
+ }
31592
+ function resolveChangeSummaryPathAgainstAllowed(rawPath, allowed) {
31593
+ const trimmed2 = rawPath.trim();
31594
+ if (!trimmed2) return null;
31595
+ if (allowed.has(trimmed2)) return trimmed2;
31596
+ const n = normalizeRepoRelativePath(trimmed2);
31597
+ if (allowed.has(n)) return n;
31598
+ for (const a of allowed) {
31599
+ if (normalizeRepoRelativePath(a) === n) return a;
31600
+ }
31601
+ return null;
31602
+ }
31603
+ function clampSummaryToAtMostTwoLines(summary) {
31604
+ const lines = summary.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
31605
+ return lines.slice(0, 2).join("\n");
31606
+ }
31607
+ function parseChangeSummaryJson(raw, allowedPaths, options) {
31608
+ if (raw == null || raw.trim() === "") return [];
31609
+ let text = raw.trim();
31610
+ const fence = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
31611
+ if (fence?.[1]) text = fence[1].trim();
31612
+ let parsed;
31613
+ try {
31614
+ parsed = JSON.parse(text);
31615
+ } catch {
31616
+ const start = text.indexOf("[");
31617
+ const end = text.lastIndexOf("]");
31618
+ if (start < 0 || end <= start) return [];
31619
+ try {
31620
+ parsed = JSON.parse(text.slice(start, end + 1));
31621
+ } catch {
31622
+ return [];
31623
+ }
31624
+ }
31625
+ const rows = [];
31626
+ let arr = [];
31627
+ if (Array.isArray(parsed)) {
31628
+ arr = parsed;
31629
+ } else if (parsed && typeof parsed === "object" && Array.isArray(parsed.files)) {
31630
+ arr = parsed.files;
31631
+ }
31632
+ const skip = options?.skipPathAllowlist === true;
31633
+ for (const item of arr) {
31634
+ if (!item || typeof item !== "object") continue;
31635
+ const o = item;
31636
+ const rawPath = typeof o.path === "string" ? o.path.trim() : "";
31637
+ const summary = typeof o.summary === "string" ? o.summary.trim() : "";
31638
+ if (!rawPath || !summary) continue;
31639
+ const path34 = skip ? normalizeRepoRelativePath(rawPath) || rawPath : resolveChangeSummaryPathAgainstAllowed(rawPath, allowedPaths);
31640
+ if (!path34) continue;
31641
+ rows.push({ path: path34, summary: clampSummaryToAtMostTwoLines(summary) });
31642
+ }
31643
+ return rows;
31644
+ }
31645
+ var PATCH_PREVIEW_MAX = 12e3;
31646
+ function clip(s, max) {
31647
+ if (s.length <= max) return s;
31648
+ return `${s.slice(0, max)}
31649
+
31650
+ \u2026(truncated, ${s.length - max} more characters)`;
31651
+ }
31652
+ function buildSessionChangeSummaryPrompt(files) {
31653
+ const lines = [
31654
+ "You are the same agent that produced the changes below. Summarize **your own** edits so a reader can scan them quickly.",
31655
+ "",
31656
+ "Write in second person (you / your): what you changed in each path and why it matters.",
31657
+ "",
31658
+ "Each summary must be **very concise**: **one line** of plain text, or **at most two short lines** (use a single line break between the two if needed). No bullets, no paragraphs.",
31659
+ "",
31660
+ "## How to format your reply (machine parsing)",
31661
+ "",
31662
+ "- Put the machine-readable part **only** inside a **markdown fenced code block** whose opening fence is exactly ```json on its own line. Close the block with ``` on its own line after the JSON.",
31663
+ "- Inside that fence: **nothing except** one valid JSON value \u2014 the array described below. No trailing commentary inside the fence.",
31664
+ "- **Do not** attach prose to the JSON on the same line (wrong: `Only one file\u2026page.tsx.[{\u2026}]`). Wrong: any sentence that ends with `.` immediately before `[`. Put a blank line before the ```json line.",
31665
+ "- If you add optional plain English before the fence (e.g. one short sentence), keep it **separate**: end that sentence, blank line, then ```json.",
31666
+ '- In each `"summary"` string, avoid raw double-quote characters, or escape them as `\\"` so the JSON parses.',
31667
+ "",
31668
+ "JSON shape **inside the fence** (array only):",
31669
+ '[{"path":"<file path exactly as given>","summary":"<one line, or two short lines separated by \\n>"}]',
31670
+ "",
31671
+ "Rules:",
31672
+ "- Include **exactly one** object per file path listed below (same path strings).",
31673
+ "- If a path is a removed directory, state briefly what you removed.",
31674
+ "- Do not invent paths; use only the paths provided.",
31675
+ "",
31676
+ "## Files you changed",
31677
+ ""
31678
+ ];
31679
+ for (const f of files) {
31680
+ lines.push(`### ${f.path}`);
31681
+ if (f.directoryRemoved) {
31682
+ lines.push("(directory removed)");
31683
+ lines.push("");
31684
+ continue;
31685
+ }
31686
+ if (f.patchContent && f.patchContent.trim() !== "") {
31687
+ lines.push("```diff");
31688
+ lines.push(clip(f.patchContent.trim(), PATCH_PREVIEW_MAX));
31689
+ lines.push("```");
31690
+ } else if (f.oldText != null || f.newText != null) {
31691
+ const oldT = (f.oldText ?? "").trim();
31692
+ const newT = (f.newText ?? "").trim();
31693
+ lines.push("Previous snippet:", clip(oldT, 6e3));
31694
+ lines.push("New snippet:", clip(newT, 6e3));
31695
+ } else {
31696
+ lines.push("(no diff body stored for this path)");
31697
+ }
31698
+ lines.push("");
31699
+ }
31700
+ return lines.join("\n");
31701
+ }
31702
+ function defaultRichness(c) {
31703
+ const patch = typeof c.patchContent === "string" ? c.patchContent.length : 0;
31704
+ const nt = typeof c.newText === "string" ? c.newText.length : 0;
31705
+ const ot = typeof c.oldText === "string" ? c.oldText.length : 0;
31706
+ const dir = c.directoryRemoved === true ? 8 : 0;
31707
+ return (patch > 0 ? 4 : 0) + (nt > 0 ? 2 : 0) + (ot > 0 ? 1 : 0) + dir;
31708
+ }
31709
+ function dedupeSessionFileChangesByPath(items, richness = (item) => defaultRichness(item)) {
31710
+ const byPath = /* @__PURE__ */ new Map();
31711
+ for (const item of items) {
31712
+ const p = typeof item.path === "string" ? item.path.trim() : "";
31713
+ if (!p) continue;
31714
+ const prev = byPath.get(p);
31715
+ if (!prev || richness(item) >= richness(prev)) byPath.set(p, item);
31716
+ }
31717
+ return Array.from(byPath.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([, v]) => v);
31718
+ }
31719
+ var ArtifactMetaSchema = external_exports.object({
31720
+ artifactId: external_exports.string(),
31721
+ workspaceId: external_exports.string(),
31722
+ /** Slug for permalink: /workspaces/:wid/artifacts/:slug */
31723
+ permalinkSlug: external_exports.string(),
31724
+ title: external_exports.string(),
31725
+ /** e.g. summary_report, build_log */
31726
+ type: external_exports.string().default("report"),
31727
+ /** Optional session that produced this artifact */
31728
+ sessionId: external_exports.string().nullable(),
31729
+ createdAt: external_exports.string(),
31730
+ updatedAt: external_exports.string()
31731
+ });
31732
+ var TemplateMetaSchema = external_exports.object({
31733
+ templateId: external_exports.string(),
31734
+ workspaceId: external_exports.string(),
31735
+ name: external_exports.string(),
31736
+ /** e.g. summary_report, build_log */
31737
+ artifactType: external_exports.string().optional(),
31738
+ createdAt: external_exports.string(),
31739
+ updatedAt: external_exports.string()
31740
+ });
31741
+ var GitRepoMetaSchema = external_exports.object({
31742
+ /** Stable id for the repo (e.g. hash of normalized canonical URL). Used for DO idFromName. */
31743
+ repoId: external_exports.string(),
31744
+ /** Canonical external URL (e.g. https://github.com/org/repo). Normalize before storing. */
31745
+ canonicalUrl: external_exports.string().url(),
31746
+ /** Optional workspace this repo was first linked in. */
31747
+ workspaceId: external_exports.string().nullable(),
31748
+ displayName: external_exports.string().optional(),
31749
+ createdAt: external_exports.string(),
31750
+ updatedAt: external_exports.string()
31751
+ });
31752
+
31753
+ // src/agents/acp/put-summarize-change-summaries.ts
31754
+ async function putEncryptedChangeSummaryRows(params) {
31755
+ const base = params.apiBaseUrl.replace(/\/+$/, "");
31756
+ const entries = params.rows.map(({ path: path34, summary }) => {
31757
+ const enc = params.e2ee.encryptFields({ summary }, ["summary"]);
31758
+ return { path: path34, summary: JSON.stringify(enc) };
31759
+ });
31760
+ const res = await fetch(
31761
+ `${base}/api/sessions/${encodeURIComponent(params.sessionId)}/follow-ups/summarize-changes`,
31762
+ {
31763
+ method: "PUT",
31764
+ headers: {
31765
+ Authorization: `Bearer ${params.authToken}`,
31766
+ "Content-Type": "application/json"
31767
+ },
31768
+ body: JSON.stringify({ entries })
31769
+ }
31770
+ );
31771
+ if (!res.ok) {
31772
+ const t = await res.text();
31773
+ throw new Error(`PUT summarize-changes summaries failed ${res.status}: ${t.slice(0, 500)}`);
31774
+ }
31775
+ }
31776
+
31777
+ // src/agents/acp/maybe-upload-e2ee-session-change-summaries.ts
31778
+ async function maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess(params) {
31779
+ const {
31780
+ sessionId,
31781
+ runId,
31782
+ resultSuccess,
31783
+ output,
31784
+ e2ee,
31785
+ cloudApiBaseUrl,
31786
+ getCloudAccessToken,
31787
+ followUpCatalogPromptId,
31788
+ sessionChangeSummaryFilePaths,
31789
+ log: log2
31790
+ } = params;
31791
+ const outputStr = typeof output === "string" ? output : "";
31792
+ if (!sessionId) {
31793
+ return;
31794
+ }
31795
+ if (!runId) {
31796
+ return;
31797
+ }
31798
+ if (!resultSuccess) {
31799
+ return;
31800
+ }
31801
+ if (!e2ee) {
31802
+ return;
31803
+ }
31804
+ if (!cloudApiBaseUrl) {
31805
+ return;
31806
+ }
31807
+ if (!getCloudAccessToken) {
31808
+ return;
31809
+ }
31810
+ if (followUpCatalogPromptId !== BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID) {
31811
+ return;
31812
+ }
31813
+ if (!sessionChangeSummaryFilePaths || sessionChangeSummaryFilePaths.length === 0) {
31814
+ return;
31815
+ }
31816
+ if (outputStr.trim() === "") {
31817
+ return;
31818
+ }
31819
+ const allowed = /* @__PURE__ */ new Set();
31820
+ for (const p of sessionChangeSummaryFilePaths) {
31821
+ const t = p.trim();
31822
+ if (!t) continue;
31823
+ allowed.add(t);
31824
+ allowed.add(normalizeRepoRelativePath(t));
31825
+ }
31826
+ const rows = parseChangeSummaryJson(outputStr, allowed);
31827
+ if (rows.length === 0) {
31828
+ return;
31829
+ }
31830
+ const token = getCloudAccessToken();
31831
+ if (!token) {
31832
+ return;
31833
+ }
31834
+ try {
31835
+ await putEncryptedChangeSummaryRows({
31836
+ apiBaseUrl: cloudApiBaseUrl,
31837
+ authToken: token,
31838
+ sessionId,
31839
+ e2ee,
31840
+ rows
31841
+ });
31842
+ } catch (uploadErr) {
31843
+ log2(
31844
+ `[Agent] Encrypted change summary upload failed: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`
31845
+ );
31846
+ }
31847
+ }
31848
+
31849
+ // src/agents/acp/send-prompt-result-augment.ts
31850
+ function augmentPromptResultAuthFields(agentType, errorText) {
31851
+ const err = errorText ?? "";
31852
+ const at = agentType ?? null;
31853
+ const evaluated = Boolean(at && err.trim());
31854
+ const suggestsAuth = evaluated && at ? localAgentErrorSuggestsAuth(at, err) : false;
31855
+ if (!suggestsAuth || !agentType) return {};
31856
+ return { agentAuthRequired: true, agentType };
31857
+ }
31858
+
31084
31859
  // src/agents/acp/send-prompt-to-agent.ts
31085
31860
  async function sendPromptToAgent(options) {
31086
31861
  const {
@@ -31093,16 +31868,13 @@ async function sendPromptToAgent(options) {
31093
31868
  agentCwd,
31094
31869
  sendResult: sendResult2,
31095
31870
  sendSessionUpdate,
31096
- log: log2
31871
+ log: log2,
31872
+ followUpCatalogPromptId,
31873
+ sessionChangeSummaryFilePaths,
31874
+ cloudApiBaseUrl,
31875
+ getCloudAccessToken,
31876
+ e2ee
31097
31877
  } = options;
31098
- function augmentAuthFields(errorText) {
31099
- const err = errorText ?? "";
31100
- const at = agentType ?? null;
31101
- const evaluated = Boolean(at && err.trim());
31102
- const suggestsAuth = evaluated && at ? localAgentErrorSuggestsAuth(at, err) : false;
31103
- if (!suggestsAuth || !agentType) return {};
31104
- return { agentAuthRequired: true, agentType };
31105
- }
31106
31878
  try {
31107
31879
  const result = await handle.sendPrompt(promptText, {});
31108
31880
  if (sessionId && runId && sendSessionUpdate && agentCwd && result.success) {
@@ -31114,6 +31886,18 @@ async function sendPromptToAgent(options) {
31114
31886
  log: log2
31115
31887
  });
31116
31888
  }
31889
+ await maybeUploadE2eeSessionChangeSummariesAfterAgentSuccess({
31890
+ sessionId,
31891
+ runId,
31892
+ resultSuccess: result.success === true,
31893
+ output: result.output,
31894
+ e2ee,
31895
+ cloudApiBaseUrl,
31896
+ getCloudAccessToken,
31897
+ followUpCatalogPromptId,
31898
+ sessionChangeSummaryFilePaths,
31899
+ log: log2
31900
+ });
31117
31901
  const errStr = typeof result.error === "string" ? result.error : void 0;
31118
31902
  sendResult2({
31119
31903
  type: "prompt_result",
@@ -31121,7 +31905,8 @@ async function sendPromptToAgent(options) {
31121
31905
  ...sessionId ? { sessionId } : {},
31122
31906
  ...runId ? { runId } : {},
31123
31907
  ...result,
31124
- ...augmentAuthFields(errStr)
31908
+ ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
31909
+ ...augmentPromptResultAuthFields(agentType, errStr)
31125
31910
  });
31126
31911
  if (!result.success) {
31127
31912
  log2(
@@ -31138,14 +31923,15 @@ async function sendPromptToAgent(options) {
31138
31923
  ...runId ? { runId } : {},
31139
31924
  success: false,
31140
31925
  error: errMsg,
31141
- ...augmentAuthFields(errMsg)
31926
+ ...followUpCatalogPromptId != null && followUpCatalogPromptId !== "" ? { followUpCatalogPromptId } : {},
31927
+ ...augmentPromptResultAuthFields(agentType, errMsg)
31142
31928
  });
31143
31929
  }
31144
31930
  }
31145
31931
 
31146
31932
  // src/agents/acp/ensure-acp-client.ts
31147
- import * as fs9 from "node:fs";
31148
- import * as path10 from "node:path";
31933
+ import * as fs10 from "node:fs";
31934
+ import * as path11 from "node:path";
31149
31935
 
31150
31936
  // src/error-message.ts
31151
31937
  function errorMessage(err) {
@@ -31234,21 +32020,21 @@ function editSnippetToUnifiedDiff(filePath, oldText, newText) {
31234
32020
  }
31235
32021
 
31236
32022
  // src/agents/acp/safe-fs-path.ts
31237
- import * as path7 from "node:path";
32023
+ import * as path8 from "node:path";
31238
32024
  function resolveSafePathUnderCwd(cwd, filePath) {
31239
32025
  const trimmed2 = filePath.trim();
31240
32026
  if (!trimmed2) return null;
31241
- const normalizedCwd = path7.resolve(cwd);
31242
- const resolved = path7.isAbsolute(trimmed2) ? path7.normalize(trimmed2) : path7.resolve(normalizedCwd, trimmed2);
31243
- const rel = path7.relative(normalizedCwd, resolved);
31244
- if (rel.startsWith("..") || path7.isAbsolute(rel)) return null;
32027
+ const normalizedCwd = path8.resolve(cwd);
32028
+ const resolved = path8.isAbsolute(trimmed2) ? path8.normalize(trimmed2) : path8.resolve(normalizedCwd, trimmed2);
32029
+ const rel = path8.relative(normalizedCwd, resolved);
32030
+ if (rel.startsWith("..") || path8.isAbsolute(rel)) return null;
31245
32031
  return resolved;
31246
32032
  }
31247
32033
  function toDisplayPathRelativeToCwd(cwd, absolutePath) {
31248
- const normalizedCwd = path7.resolve(cwd);
31249
- const rel = path7.relative(normalizedCwd, path7.resolve(absolutePath));
31250
- if (!rel || rel === "") return path7.basename(absolutePath);
31251
- return rel.split(path7.sep).join("/");
32034
+ const normalizedCwd = path8.resolve(cwd);
32035
+ const rel = path8.relative(normalizedCwd, path8.resolve(absolutePath));
32036
+ if (!rel || rel === "") return path8.basename(absolutePath);
32037
+ return rel.split(path8.sep).join("/");
31252
32038
  }
31253
32039
 
31254
32040
  // src/agents/acp/clients/agent-stderr-capture.ts
@@ -31368,7 +32154,7 @@ async function createSdkStdioAcpClient(options) {
31368
32154
  child.once("close", (code, signal) => {
31369
32155
  onAgentSubprocessExit?.({ code, signal });
31370
32156
  });
31371
- return new Promise((resolve16, reject) => {
32157
+ return new Promise((resolve17, reject) => {
31372
32158
  let initSettled = false;
31373
32159
  const settleReject = (err) => {
31374
32160
  if (initSettled) return;
@@ -31382,7 +32168,7 @@ async function createSdkStdioAcpClient(options) {
31382
32168
  const settleResolve = (handle) => {
31383
32169
  if (initSettled) return;
31384
32170
  initSettled = true;
31385
- resolve16(handle);
32171
+ resolve17(handle);
31386
32172
  };
31387
32173
  child.on("error", (err) => {
31388
32174
  settleReject(new Error(formatSpawnError(err, command[0])));
@@ -31418,8 +32204,8 @@ async function createSdkStdioAcpClient(options) {
31418
32204
  });
31419
32205
  } catch {
31420
32206
  }
31421
- return await new Promise((resolve17) => {
31422
- pendingPermissionResolvers.set(requestId, resolve17);
32207
+ return await new Promise((resolve18) => {
32208
+ pendingPermissionResolvers.set(requestId, resolve18);
31423
32209
  });
31424
32210
  },
31425
32211
  async readTextFile(params) {
@@ -31527,9 +32313,9 @@ async function createSdkStdioAcpClient(options) {
31527
32313
  }
31528
32314
  },
31529
32315
  async cancel() {
31530
- for (const [id, resolve17] of [...pendingPermissionResolvers.entries()]) {
32316
+ for (const [id, resolve18] of [...pendingPermissionResolvers.entries()]) {
31531
32317
  pendingPermissionResolvers.delete(id);
31532
- resolve17({ outcome: { outcome: "cancelled" } });
32318
+ resolve18({ outcome: { outcome: "cancelled" } });
31533
32319
  }
31534
32320
  try {
31535
32321
  await connection.cancel({ sessionId });
@@ -31545,10 +32331,10 @@ async function createSdkStdioAcpClient(options) {
31545
32331
  }
31546
32332
  },
31547
32333
  resolveRequest(requestId, result) {
31548
- const resolve17 = pendingPermissionResolvers.get(requestId);
31549
- if (!resolve17) return;
32334
+ const resolve18 = pendingPermissionResolvers.get(requestId);
32335
+ if (!resolve18) return;
31550
32336
  pendingPermissionResolvers.delete(requestId);
31551
- resolve17(result);
32337
+ resolve18(result);
31552
32338
  },
31553
32339
  disconnect() {
31554
32340
  child.kill();
@@ -31689,7 +32475,7 @@ async function createCursorAcpClient(options) {
31689
32475
  onRequest,
31690
32476
  onFileChange
31691
32477
  } = options;
31692
- const dbgFs = process.env.BUILDAMATON_DEBUG_ACP_FS === "1";
32478
+ const dbgFs = process.env.BUILDAUTOMATON_DEBUG_ACP_FS === "1";
31693
32479
  const isWindows = process.platform === "win32";
31694
32480
  const child = spawn3(command[0], command.slice(1), {
31695
32481
  cwd,
@@ -31699,7 +32485,7 @@ async function createCursorAcpClient(options) {
31699
32485
  });
31700
32486
  const stderrCapture = createStderrCapture(child);
31701
32487
  child.stderr?.on("data", (chunk) => stderrCapture.append(chunk));
31702
- return new Promise((resolve16, reject) => {
32488
+ return new Promise((resolve17, reject) => {
31703
32489
  child.on("error", (err) => {
31704
32490
  child.kill();
31705
32491
  reject(new Error(formatSpawnError2(err, command[0])));
@@ -31886,7 +32672,7 @@ async function createCursorAcpClient(options) {
31886
32672
  const newResult = await send("session/new", { cwd, mcpServers: [] });
31887
32673
  const sessionId = newResult?.sessionId ?? "";
31888
32674
  if (!sessionId) throw new Error("Cursor ACP session/new did not return sessionId");
31889
- resolve16({
32675
+ resolve17({
31890
32676
  sessionId,
31891
32677
  async sendPrompt(prompt, _options) {
31892
32678
  promptOutputBuffer = "";
@@ -32077,16 +32863,16 @@ import { existsSync, statSync } from "node:fs";
32077
32863
 
32078
32864
  // src/git/get-git-repo-root-sync.ts
32079
32865
  import { execFileSync as execFileSync2 } from "node:child_process";
32080
- import * as path8 from "node:path";
32866
+ import * as path9 from "node:path";
32081
32867
  function getGitRepoRootSync(startDir) {
32082
32868
  try {
32083
32869
  const out = execFileSync2("git", ["rev-parse", "--show-toplevel"], {
32084
- cwd: path8.resolve(startDir),
32870
+ cwd: path9.resolve(startDir),
32085
32871
  encoding: "utf8",
32086
32872
  stdio: ["ignore", "pipe", "ignore"],
32087
32873
  maxBuffer: 1024 * 1024
32088
32874
  }).trim();
32089
- return out ? path8.resolve(out) : null;
32875
+ return out ? path9.resolve(out) : null;
32090
32876
  } catch {
32091
32877
  return null;
32092
32878
  }
@@ -32095,25 +32881,25 @@ function getGitRepoRootSync(startDir) {
32095
32881
  // src/agents/acp/workspace-files.ts
32096
32882
  import { execFileSync as execFileSync3 } from "node:child_process";
32097
32883
  import { readFileSync as readFileSync4 } from "node:fs";
32098
- import * as path9 from "node:path";
32884
+ import * as path10 from "node:path";
32099
32885
  function resolveWorkspaceFilePath(cwd, rawPath) {
32100
32886
  const trimmed2 = rawPath.trim();
32101
32887
  if (!trimmed2) return null;
32102
- const normalizedCwd = path9.resolve(cwd);
32888
+ const normalizedCwd = path10.resolve(cwd);
32103
32889
  let abs = resolveSafePathUnderCwd(cwd, trimmed2);
32104
32890
  if (!abs) {
32105
- const candidate = path9.isAbsolute(trimmed2) ? path9.normalize(trimmed2) : path9.normalize(path9.resolve(normalizedCwd, trimmed2));
32891
+ const candidate = path10.isAbsolute(trimmed2) ? path10.normalize(trimmed2) : path10.normalize(path10.resolve(normalizedCwd, trimmed2));
32106
32892
  const gitRoot2 = getGitRepoRootSync(cwd);
32107
32893
  if (!gitRoot2) return null;
32108
- const rel = path9.relative(gitRoot2, candidate);
32109
- if (rel.startsWith("..") || path9.isAbsolute(rel)) return null;
32894
+ const rel = path10.relative(gitRoot2, candidate);
32895
+ if (rel.startsWith("..") || path10.isAbsolute(rel)) return null;
32110
32896
  abs = candidate;
32111
32897
  }
32112
32898
  const gitRoot = getGitRepoRootSync(cwd);
32113
32899
  if (gitRoot) {
32114
- const relFromRoot = path9.relative(gitRoot, abs);
32115
- if (!relFromRoot.startsWith("..") && !path9.isAbsolute(relFromRoot)) {
32116
- return { abs, display: relFromRoot.split(path9.sep).join("/") };
32900
+ const relFromRoot = path10.relative(gitRoot, abs);
32901
+ if (!relFromRoot.startsWith("..") && !path10.isAbsolute(relFromRoot)) {
32902
+ return { abs, display: relFromRoot.split(path10.sep).join("/") };
32117
32903
  }
32118
32904
  }
32119
32905
  return { abs, display: toDisplayPathRelativeToCwd(cwd, abs) };
@@ -32122,9 +32908,9 @@ function readUtf8WorkspaceFile(cwd, displayPath) {
32122
32908
  if (!displayPath || displayPath.includes("..")) return "";
32123
32909
  const gitRoot = getGitRepoRootSync(cwd);
32124
32910
  if (gitRoot) {
32125
- const abs2 = path9.resolve(gitRoot, displayPath);
32126
- const rel = path9.relative(gitRoot, abs2);
32127
- if (!rel.startsWith("..") && !path9.isAbsolute(rel)) {
32911
+ const abs2 = path10.resolve(gitRoot, displayPath);
32912
+ const rel = path10.relative(gitRoot, abs2);
32913
+ if (!rel.startsWith("..") && !path10.isAbsolute(rel)) {
32128
32914
  try {
32129
32915
  return readFileSync4(abs2, "utf8");
32130
32916
  } catch {
@@ -32143,9 +32929,9 @@ function tryWorkspaceDisplayToAbs(cwd, displayPath) {
32143
32929
  if (!displayPath || displayPath.includes("..")) return null;
32144
32930
  const gitRoot = getGitRepoRootSync(cwd);
32145
32931
  if (gitRoot) {
32146
- const abs = path9.resolve(gitRoot, displayPath);
32147
- const rel = path9.relative(gitRoot, abs);
32148
- if (!rel.startsWith("..") && !path9.isAbsolute(rel)) return abs;
32932
+ const abs = path10.resolve(gitRoot, displayPath);
32933
+ const rel = path10.relative(gitRoot, abs);
32934
+ if (!rel.startsWith("..") && !path10.isAbsolute(rel)) return abs;
32149
32935
  }
32150
32936
  return resolveSafePathUnderCwd(cwd, displayPath);
32151
32937
  }
@@ -32713,10 +33499,10 @@ function buildAcpSessionBridgeHooks(opts) {
32713
33499
  // src/agents/acp/ensure-acp-client.ts
32714
33500
  async function ensureAcpClient(options) {
32715
33501
  const { state, preferredAgentType, mode, cwd, routing, sendSessionUpdate, sendRequest, log: log2 } = options;
32716
- const targetCwd = path10.resolve(
33502
+ const targetCwd = path11.resolve(
32717
33503
  cwd != null && String(cwd).trim() !== "" ? String(cwd).trim() : getBridgeWorkspaceDirectory()
32718
33504
  );
32719
- if (state.acpHandle && state.lastAcpCwd != null && path10.resolve(state.lastAcpCwd) !== path10.resolve(targetCwd)) {
33505
+ if (state.acpHandle && state.lastAcpCwd != null && path11.resolve(state.lastAcpCwd) !== path11.resolve(targetCwd)) {
32720
33506
  try {
32721
33507
  state.acpHandle.disconnect();
32722
33508
  } catch {
@@ -32748,7 +33534,7 @@ async function ensureAcpClient(options) {
32748
33534
  if (!state.acpStartPromise) {
32749
33535
  let statOk = false;
32750
33536
  try {
32751
- const st = fs9.statSync(targetCwd);
33537
+ const st = fs10.statSync(targetCwd);
32752
33538
  statOk = st.isDirectory();
32753
33539
  if (!statOk) {
32754
33540
  state.lastAcpStartError = `Agent cwd is not a directory: ${targetCwd}`;
@@ -32833,7 +33619,12 @@ async function createAcpManager(options) {
32833
33619
  agentType,
32834
33620
  cwd,
32835
33621
  sendResult: sendResult2,
32836
- sendSessionUpdate
33622
+ sendSessionUpdate,
33623
+ followUpCatalogPromptId,
33624
+ sessionChangeSummaryFilePaths,
33625
+ cloudApiBaseUrl,
33626
+ getCloudAccessToken,
33627
+ e2ee
32837
33628
  } = opts;
32838
33629
  const preferredForPrompt = agentType ?? backendFallbackAgentType ?? null;
32839
33630
  pendingCancelRunId = void 0;
@@ -32897,7 +33688,12 @@ async function createAcpManager(options) {
32897
33688
  agentCwd: cwd,
32898
33689
  sendResult: sendResult2,
32899
33690
  sendSessionUpdate,
32900
- log: log2
33691
+ log: log2,
33692
+ followUpCatalogPromptId,
33693
+ sessionChangeSummaryFilePaths,
33694
+ cloudApiBaseUrl,
33695
+ getCloudAccessToken,
33696
+ e2ee
32901
33697
  });
32902
33698
  }
32903
33699
  void run().finally(() => {
@@ -32944,12 +33740,12 @@ async function createAcpManager(options) {
32944
33740
  }
32945
33741
 
32946
33742
  // src/worktrees/session-worktree-manager.ts
32947
- import * as path16 from "node:path";
33743
+ import * as path17 from "node:path";
32948
33744
  import os4 from "node:os";
32949
33745
 
32950
33746
  // src/worktrees/prepare-new-session-worktrees.ts
32951
- import * as fs11 from "node:fs";
32952
- import * as path12 from "node:path";
33747
+ import * as fs12 from "node:fs";
33748
+ import * as path13 from "node:path";
32953
33749
 
32954
33750
  // src/git/worktree-add.ts
32955
33751
  async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch) {
@@ -32958,12 +33754,12 @@ async function gitWorktreeAddBranch(mainRepoPath, worktreePath, branch) {
32958
33754
  }
32959
33755
 
32960
33756
  // src/worktrees/worktree-layout-file.ts
32961
- import * as fs10 from "node:fs";
32962
- import * as path11 from "node:path";
33757
+ import * as fs11 from "node:fs";
33758
+ import * as path12 from "node:path";
32963
33759
  import os3 from "node:os";
32964
33760
  var LAYOUT_FILENAME = "worktree-launcher-layout.json";
32965
33761
  function defaultWorktreeLayoutPath() {
32966
- return path11.join(os3.homedir(), ".buildautomaton", LAYOUT_FILENAME);
33762
+ return path12.join(os3.homedir(), ".buildautomaton", LAYOUT_FILENAME);
32967
33763
  }
32968
33764
  function normalizeLoadedLayout(raw) {
32969
33765
  if (raw && typeof raw === "object" && "launcherCwds" in raw) {
@@ -32975,8 +33771,8 @@ function normalizeLoadedLayout(raw) {
32975
33771
  function loadWorktreeLayout() {
32976
33772
  try {
32977
33773
  const p = defaultWorktreeLayoutPath();
32978
- if (!fs10.existsSync(p)) return { launcherCwds: [] };
32979
- const raw = JSON.parse(fs10.readFileSync(p, "utf8"));
33774
+ if (!fs11.existsSync(p)) return { launcherCwds: [] };
33775
+ const raw = JSON.parse(fs11.readFileSync(p, "utf8"));
32980
33776
  return normalizeLoadedLayout(raw);
32981
33777
  } catch {
32982
33778
  return { launcherCwds: [] };
@@ -32984,18 +33780,18 @@ function loadWorktreeLayout() {
32984
33780
  }
32985
33781
  function saveWorktreeLayout(layout) {
32986
33782
  try {
32987
- const dir = path11.dirname(defaultWorktreeLayoutPath());
32988
- fs10.mkdirSync(dir, { recursive: true });
32989
- fs10.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
33783
+ const dir = path12.dirname(defaultWorktreeLayoutPath());
33784
+ fs11.mkdirSync(dir, { recursive: true });
33785
+ fs11.writeFileSync(defaultWorktreeLayoutPath(), JSON.stringify(layout, null, 2), "utf8");
32990
33786
  } catch {
32991
33787
  }
32992
33788
  }
32993
33789
  function baseNameSafe(abs) {
32994
- return path11.basename(abs).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
33790
+ return path12.basename(abs).replace(/[^a-zA-Z0-9._-]+/g, "-") || "cwd";
32995
33791
  }
32996
33792
  function allocateDirNameForLauncherCwd(layout, launcherCwdAbs) {
32997
- const norm = path11.resolve(launcherCwdAbs);
32998
- const existing = layout.launcherCwds.find((e) => path11.resolve(e.absolutePath) === norm);
33793
+ const norm = path12.resolve(launcherCwdAbs);
33794
+ const existing = layout.launcherCwds.find((e) => path12.resolve(e.absolutePath) === norm);
32999
33795
  if (existing) return existing.dirName;
33000
33796
  const base = baseNameSafe(norm);
33001
33797
  const used = new Set(layout.launcherCwds.map((e) => e.dirName));
@@ -33013,9 +33809,9 @@ function allocateDirNameForLauncherCwd(layout, launcherCwdAbs) {
33013
33809
  // src/worktrees/prepare-new-session-worktrees.ts
33014
33810
  async function prepareNewSessionWorktrees(options) {
33015
33811
  const { rootAbs, launcherCwd, sessionId, layout, log: log2 } = options;
33016
- const launcherResolved = path12.resolve(launcherCwd);
33812
+ const launcherResolved = path13.resolve(launcherCwd);
33017
33813
  const cwdKey = allocateDirNameForLauncherCwd(layout, launcherResolved);
33018
- const agentMirrorRoot = path12.join(rootAbs, cwdKey);
33814
+ const agentMirrorRoot = path13.join(rootAbs, cwdKey);
33019
33815
  const repos = await discoverGitReposUnderRoot(launcherResolved);
33020
33816
  if (repos.length === 0) {
33021
33817
  log2("[worktrees] No Git repositories under launcher working directory; skipping worktree creation.");
@@ -33023,13 +33819,13 @@ async function prepareNewSessionWorktrees(options) {
33023
33819
  }
33024
33820
  const branch = `session-${sessionId}`;
33025
33821
  const worktreePaths = [];
33026
- fs11.mkdirSync(agentMirrorRoot, { recursive: true });
33822
+ fs12.mkdirSync(agentMirrorRoot, { recursive: true });
33027
33823
  for (const repo of repos) {
33028
- let rel = path12.relative(launcherResolved, repo.absolutePath);
33029
- if (rel.startsWith("..") || path12.isAbsolute(rel)) continue;
33824
+ let rel = path13.relative(launcherResolved, repo.absolutePath);
33825
+ if (rel.startsWith("..") || path13.isAbsolute(rel)) continue;
33030
33826
  const relNorm = rel === "" ? "." : rel;
33031
- const wtPath = path12.join(agentMirrorRoot, relNorm, sessionId);
33032
- fs11.mkdirSync(path12.dirname(wtPath), { recursive: true });
33827
+ const wtPath = path13.join(agentMirrorRoot, relNorm, sessionId);
33828
+ fs12.mkdirSync(path13.dirname(wtPath), { recursive: true });
33033
33829
  try {
33034
33830
  await gitWorktreeAddBranch(repo.absolutePath, wtPath, branch);
33035
33831
  log2(`[worktrees] Added worktree ${wtPath} (branch ${branch}).`);
@@ -33066,23 +33862,23 @@ async function renameSessionWorktreeBranches(paths, newBranch, log2) {
33066
33862
  }
33067
33863
 
33068
33864
  // src/worktrees/remove-session-worktrees.ts
33069
- import * as fs14 from "node:fs";
33865
+ import * as fs15 from "node:fs";
33070
33866
 
33071
33867
  // src/git/worktree-remove.ts
33072
- import * as fs13 from "node:fs";
33868
+ import * as fs14 from "node:fs";
33073
33869
 
33074
33870
  // src/git/resolve-main-repo-from-git-file.ts
33075
- import * as fs12 from "node:fs";
33076
- import * as path13 from "node:path";
33871
+ import * as fs13 from "node:fs";
33872
+ import * as path14 from "node:path";
33077
33873
  function resolveMainRepoFromWorktreeGitFile(wt) {
33078
- const gitDirFile = path13.join(wt, ".git");
33079
- if (!fs12.existsSync(gitDirFile) || !fs12.statSync(gitDirFile).isFile()) return "";
33080
- const first2 = fs12.readFileSync(gitDirFile, "utf8").trim();
33874
+ const gitDirFile = path14.join(wt, ".git");
33875
+ if (!fs13.existsSync(gitDirFile) || !fs13.statSync(gitDirFile).isFile()) return "";
33876
+ const first2 = fs13.readFileSync(gitDirFile, "utf8").trim();
33081
33877
  const m = first2.match(/^gitdir:\s*(.+)$/im);
33082
33878
  if (!m) return "";
33083
- const gitWorktreePath = path13.resolve(wt, m[1].trim());
33084
- const gitDir = path13.dirname(path13.dirname(gitWorktreePath));
33085
- return path13.dirname(gitDir);
33879
+ const gitWorktreePath = path14.resolve(wt, m[1].trim());
33880
+ const gitDir = path14.dirname(path14.dirname(gitWorktreePath));
33881
+ return path14.dirname(gitDir);
33086
33882
  }
33087
33883
 
33088
33884
  // src/git/worktree-remove.ts
@@ -33091,7 +33887,7 @@ async function gitWorktreeRemoveForce(worktreePath) {
33091
33887
  if (mainRepo) {
33092
33888
  await simpleGit(mainRepo).raw(["worktree", "remove", "--force", worktreePath]);
33093
33889
  } else {
33094
- fs13.rmSync(worktreePath, { recursive: true, force: true });
33890
+ fs14.rmSync(worktreePath, { recursive: true, force: true });
33095
33891
  }
33096
33892
  }
33097
33893
 
@@ -33104,7 +33900,7 @@ async function removeSessionWorktrees(paths, log2) {
33104
33900
  } catch (e) {
33105
33901
  log2(`[worktrees] Remove failed for ${wt}: ${e instanceof Error ? e.message : String(e)}`);
33106
33902
  try {
33107
- fs14.rmSync(wt, { recursive: true, force: true });
33903
+ fs15.rmSync(wt, { recursive: true, force: true });
33108
33904
  } catch {
33109
33905
  }
33110
33906
  }
@@ -33324,7 +34120,7 @@ function formatRemoteDisplayLabel(remoteUrl) {
33324
34120
  }
33325
34121
 
33326
34122
  // src/git/working-directory/changes/get-working-tree-change-repo-details.ts
33327
- import * as path15 from "node:path";
34123
+ import * as path16 from "node:path";
33328
34124
 
33329
34125
  // src/git/working-directory/changes/parse-git-status.ts
33330
34126
  function parseNameStatusLines(lines) {
@@ -33444,8 +34240,8 @@ async function listChangedFilesForCommit(repoGitCwd, repoRelPath, commitSha) {
33444
34240
  }
33445
34241
 
33446
34242
  // src/git/working-directory/changes/list-changed-files-for-repo.ts
33447
- import * as fs16 from "node:fs";
33448
- import * as path14 from "node:path";
34243
+ import * as fs17 from "node:fs";
34244
+ import * as path15 from "node:path";
33449
34245
 
33450
34246
  // src/git/working-directory/changes/count-lines.ts
33451
34247
  import { createReadStream } from "node:fs";
@@ -33469,7 +34265,7 @@ async function countTextFileLines(absFile) {
33469
34265
  }
33470
34266
 
33471
34267
  // src/git/working-directory/changes/hydrate-patch.ts
33472
- import * as fs15 from "node:fs";
34268
+ import * as fs16 from "node:fs";
33473
34269
  var UNIFIED_HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
33474
34270
  var MAX_HYDRATE_LINES_PER_GAP = 8e3;
33475
34271
  var MAX_HYDRATE_LINES_PER_FILE = 8e4;
@@ -33484,7 +34280,7 @@ async function readGitBlobLines(repoCwd, pathInRepo) {
33484
34280
  }
33485
34281
  async function readWorktreeFileLines(abs) {
33486
34282
  try {
33487
- const raw = await fs15.promises.readFile(abs, "utf8");
34283
+ const raw = await fs16.promises.readFile(abs, "utf8");
33488
34284
  return raw.split(/\r?\n/);
33489
34285
  } catch {
33490
34286
  return null;
@@ -33619,7 +34415,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
33619
34415
  const rows = [];
33620
34416
  for (const pathInRepo of paths) {
33621
34417
  const relLauncher = posixJoinDirFile(repoRelPath, pathInRepo.replace(/\\/g, "/"));
33622
- const abs = path14.join(repoGitCwd, pathInRepo);
34418
+ const abs = path15.join(repoGitCwd, pathInRepo);
33623
34419
  const nums = numByPath.get(pathInRepo);
33624
34420
  let additions = nums?.additions ?? 0;
33625
34421
  let deletions = nums?.deletions ?? 0;
@@ -33632,7 +34428,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
33632
34428
  deletions = fromGit.deletions;
33633
34429
  } else {
33634
34430
  try {
33635
- const st = await fs16.promises.stat(abs);
34431
+ const st = await fs17.promises.stat(abs);
33636
34432
  if (st.isFile()) additions = await countTextFileLines(abs);
33637
34433
  else additions = 0;
33638
34434
  } catch {
@@ -33658,7 +34454,7 @@ async function listChangedFilesForRepo(repoGitCwd, repoRelPath) {
33658
34454
  } else {
33659
34455
  pathInRepo = row.pathRelLauncher;
33660
34456
  }
33661
- const absFile = path14.join(repoGitCwd, pathInRepo);
34457
+ const absFile = path15.join(repoGitCwd, pathInRepo);
33662
34458
  let patch = await unifiedDiffForFile(repoGitCwd, pathInRepo, row.change);
33663
34459
  if (patch) {
33664
34460
  patch = await hydrateUnifiedPatchWithFileContext(patch, absFile, repoGitCwd, pathInRepo, row.change);
@@ -33674,8 +34470,8 @@ function normRepoRel(p) {
33674
34470
  return x === "" ? "." : x;
33675
34471
  }
33676
34472
  async function getWorkingTreeChangeRepoDetails(options) {
33677
- const launcher = path15.resolve(getBridgeWorkspaceDirectory());
33678
- const mirror = options.agentMirrorRootAbs ? path15.resolve(options.agentMirrorRootAbs) : null;
34473
+ const launcher = path16.resolve(getBridgeWorkspaceDirectory());
34474
+ const mirror = options.agentMirrorRootAbs ? path16.resolve(options.agentMirrorRootAbs) : null;
33679
34475
  const out = [];
33680
34476
  const filter = options.repoFilterRelPath != null ? normRepoRel(options.repoFilterRelPath) : null;
33681
34477
  const basisInput = options.basis ?? { kind: "working" };
@@ -33687,7 +34483,7 @@ async function getWorkingTreeChangeRepoDetails(options) {
33687
34483
  }
33688
34484
  const basis = filter == null && basisInput.kind === "commit" ? { kind: "working" } : basisInput;
33689
34485
  for (const target of options.commitTargetAbsDirs) {
33690
- const t = path15.resolve(target);
34486
+ const t = path16.resolve(target);
33691
34487
  if (!await isGitRepoDirectory(t)) continue;
33692
34488
  const g = simpleGit(t);
33693
34489
  let branch = "HEAD";
@@ -33700,7 +34496,7 @@ async function getWorkingTreeChangeRepoDetails(options) {
33700
34496
  const remoteDisplay = formatRemoteDisplayLabel(remoteUrl);
33701
34497
  let repoRelPath;
33702
34498
  if (mirror) {
33703
- const relNorm = path15.relative(mirror, path15.dirname(t));
34499
+ const relNorm = path16.relative(mirror, path16.dirname(t));
33704
34500
  repoRelPath = relNorm === "" ? "." : relNorm.replace(/\\/g, "/");
33705
34501
  } else {
33706
34502
  let top = t;
@@ -33709,8 +34505,8 @@ async function getWorkingTreeChangeRepoDetails(options) {
33709
34505
  } catch {
33710
34506
  top = t;
33711
34507
  }
33712
- const rel = path15.relative(launcher, path15.resolve(top)).replace(/\\/g, "/") || ".";
33713
- repoRelPath = rel.startsWith("..") ? path15.basename(path15.resolve(top)) : rel;
34508
+ const rel = path16.relative(launcher, path16.resolve(top)).replace(/\\/g, "/") || ".";
34509
+ repoRelPath = rel.startsWith("..") ? path16.basename(path16.resolve(top)) : rel;
33714
34510
  }
33715
34511
  const norm = normRepoRel(repoRelPath === "" ? "." : repoRelPath);
33716
34512
  if (filter && norm !== filter) continue;
@@ -33802,7 +34598,7 @@ var SessionWorktreeManager = class {
33802
34598
  }
33803
34599
  if (!opts.isNewSession) {
33804
34600
  const agentCwd = this.sessionAgentCwd.get(sessionId);
33805
- if (agentCwd) return path16.resolve(agentCwd);
34601
+ if (agentCwd) return path17.resolve(agentCwd);
33806
34602
  return void 0;
33807
34603
  }
33808
34604
  const prep = await prepareNewSessionWorktrees({
@@ -33815,7 +34611,7 @@ var SessionWorktreeManager = class {
33815
34611
  if (!prep) return void 0;
33816
34612
  this.sessionPaths.set(sessionId, prep.worktreePaths);
33817
34613
  this.sessionAgentCwd.set(sessionId, prep.agentCwd);
33818
- return path16.resolve(prep.agentCwd);
34614
+ return path17.resolve(prep.agentCwd);
33819
34615
  }
33820
34616
  async renameSessionBranch(sessionId, newBranch) {
33821
34617
  const paths = this.sessionPaths.get(sessionId);
@@ -33836,7 +34632,7 @@ var SessionWorktreeManager = class {
33836
34632
  getAgentCwdForSession(sessionId) {
33837
34633
  if (!sessionId) return null;
33838
34634
  const c = this.sessionAgentCwd.get(sessionId);
33839
- return c ? path16.resolve(c) : null;
34635
+ return c ? path17.resolve(c) : null;
33840
34636
  }
33841
34637
  async removeSessionWorktrees(sessionId) {
33842
34638
  const paths = this.sessionPaths.get(sessionId);
@@ -33884,30 +34680,30 @@ var SessionWorktreeManager = class {
33884
34680
  }
33885
34681
  };
33886
34682
  function defaultWorktreesRootAbs() {
33887
- return path16.join(os4.homedir(), ".buildautomaton", "worktrees");
34683
+ return path17.join(os4.homedir(), ".buildautomaton", "worktrees");
33888
34684
  }
33889
34685
 
33890
34686
  // src/files/watch-file-index.ts
33891
34687
  import { watch } from "node:fs";
33892
- import path23 from "node:path";
34688
+ import path24 from "node:path";
33893
34689
 
33894
34690
  // src/files/index/build-file-index.ts
33895
- import path20 from "node:path";
34691
+ import path21 from "node:path";
33896
34692
 
33897
34693
  // src/runtime/yield-to-event-loop.ts
33898
34694
  function yieldToEventLoop() {
33899
- return new Promise((resolve16) => setImmediate(resolve16));
34695
+ return new Promise((resolve17) => setImmediate(resolve17));
33900
34696
  }
33901
34697
 
33902
34698
  // src/files/index/walk-workspace-tree.ts
33903
- import fs17 from "node:fs";
33904
- import path18 from "node:path";
34699
+ import fs18 from "node:fs";
34700
+ import path19 from "node:path";
33905
34701
 
33906
34702
  // src/files/index/constants.ts
33907
- import path17 from "node:path";
34703
+ import path18 from "node:path";
33908
34704
  import os5 from "node:os";
33909
34705
  var INDEX_WORK_YIELD_EVERY = 256;
33910
- var INDEX_DIR = path17.join(os5.homedir(), ".buildautomaton");
34706
+ var INDEX_DIR = path18.join(os5.homedir(), ".buildautomaton");
33911
34707
  var INDEX_HASH_LEN = 16;
33912
34708
  var INDEX_VERSION = 2;
33913
34709
  var INDEX_LOG_PREFIX = "[file-index]";
@@ -33916,23 +34712,23 @@ var INDEX_LOG_PREFIX = "[file-index]";
33916
34712
  function walkWorkspaceTreeSync(dir, baseDir, out) {
33917
34713
  let names;
33918
34714
  try {
33919
- names = fs17.readdirSync(dir);
34715
+ names = fs18.readdirSync(dir);
33920
34716
  } catch {
33921
34717
  return;
33922
34718
  }
33923
34719
  for (const name of names) {
33924
34720
  if (name.startsWith(".")) continue;
33925
- const full = path18.join(dir, name);
33926
- let stat2;
34721
+ const full = path19.join(dir, name);
34722
+ let stat3;
33927
34723
  try {
33928
- stat2 = fs17.statSync(full);
34724
+ stat3 = fs18.statSync(full);
33929
34725
  } catch {
33930
34726
  continue;
33931
34727
  }
33932
- const relative5 = path18.relative(baseDir, full).replace(/\\/g, "/");
33933
- if (stat2.isDirectory()) {
34728
+ const relative5 = path19.relative(baseDir, full).replace(/\\/g, "/");
34729
+ if (stat3.isDirectory()) {
33934
34730
  walkWorkspaceTreeSync(full, baseDir, out);
33935
- } else if (stat2.isFile()) {
34731
+ } else if (stat3.isFile()) {
33936
34732
  out.push(relative5);
33937
34733
  }
33938
34734
  }
@@ -33940,7 +34736,7 @@ function walkWorkspaceTreeSync(dir, baseDir, out) {
33940
34736
  async function walkWorkspaceTreeAsync(dir, baseDir, out, state) {
33941
34737
  let names;
33942
34738
  try {
33943
- names = await fs17.promises.readdir(dir);
34739
+ names = await fs18.promises.readdir(dir);
33944
34740
  } catch {
33945
34741
  return;
33946
34742
  }
@@ -33950,17 +34746,17 @@ async function walkWorkspaceTreeAsync(dir, baseDir, out, state) {
33950
34746
  await yieldToEventLoop();
33951
34747
  }
33952
34748
  state.n++;
33953
- const full = path18.join(dir, name);
33954
- let stat2;
34749
+ const full = path19.join(dir, name);
34750
+ let stat3;
33955
34751
  try {
33956
- stat2 = await fs17.promises.stat(full);
34752
+ stat3 = await fs18.promises.stat(full);
33957
34753
  } catch {
33958
34754
  continue;
33959
34755
  }
33960
- const relative5 = path18.relative(baseDir, full).replace(/\\/g, "/");
33961
- if (stat2.isDirectory()) {
34756
+ const relative5 = path19.relative(baseDir, full).replace(/\\/g, "/");
34757
+ if (stat3.isDirectory()) {
33962
34758
  await walkWorkspaceTreeAsync(full, baseDir, out, state);
33963
- } else if (stat2.isFile()) {
34759
+ } else if (stat3.isFile()) {
33964
34760
  out.push(relative5);
33965
34761
  }
33966
34762
  }
@@ -34038,22 +34834,22 @@ async function buildTrigramMapForPathsAsync(paths) {
34038
34834
  }
34039
34835
 
34040
34836
  // src/files/index/write-index-file.ts
34041
- import fs18 from "node:fs";
34837
+ import fs19 from "node:fs";
34042
34838
 
34043
34839
  // src/files/index/paths.ts
34044
- import path19 from "node:path";
34840
+ import path20 from "node:path";
34045
34841
  import crypto2 from "node:crypto";
34046
34842
  function getIndexPathForCwd(resolvedCwd) {
34047
34843
  const hash = crypto2.createHash("sha256").update(resolvedCwd).digest("hex").slice(0, INDEX_HASH_LEN);
34048
- return path19.join(INDEX_DIR, `.file-index-${hash}.json`);
34844
+ return path20.join(INDEX_DIR, `.file-index-${hash}.json`);
34049
34845
  }
34050
34846
 
34051
34847
  // src/files/index/write-index-file.ts
34052
34848
  function writeIndexFileSync(resolvedCwd, data) {
34053
34849
  const indexPath = getIndexPathForCwd(resolvedCwd);
34054
34850
  try {
34055
- if (!fs18.existsSync(INDEX_DIR)) fs18.mkdirSync(INDEX_DIR, { recursive: true });
34056
- fs18.writeFileSync(indexPath, JSON.stringify(data), "utf8");
34851
+ if (!fs19.existsSync(INDEX_DIR)) fs19.mkdirSync(INDEX_DIR, { recursive: true });
34852
+ fs19.writeFileSync(indexPath, JSON.stringify(data), "utf8");
34057
34853
  } catch (e) {
34058
34854
  console.error(`${INDEX_LOG_PREFIX} Failed to write index:`, e);
34059
34855
  }
@@ -34061,8 +34857,8 @@ function writeIndexFileSync(resolvedCwd, data) {
34061
34857
  async function writeIndexFileAsync(resolvedCwd, data) {
34062
34858
  const indexPath = getIndexPathForCwd(resolvedCwd);
34063
34859
  try {
34064
- await fs18.promises.mkdir(INDEX_DIR, { recursive: true });
34065
- await fs18.promises.writeFile(indexPath, JSON.stringify(data), "utf8");
34860
+ await fs19.promises.mkdir(INDEX_DIR, { recursive: true });
34861
+ await fs19.promises.writeFile(indexPath, JSON.stringify(data), "utf8");
34066
34862
  } catch (e) {
34067
34863
  console.error(`${INDEX_LOG_PREFIX} Failed to write index:`, e);
34068
34864
  }
@@ -34076,7 +34872,7 @@ function sortPaths(paths) {
34076
34872
  paths.sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
34077
34873
  }
34078
34874
  function buildFileIndex(cwd) {
34079
- const resolved = path20.resolve(cwd);
34875
+ const resolved = path21.resolve(cwd);
34080
34876
  const paths = [];
34081
34877
  walkWorkspaceTreeSync(resolved, resolved, paths);
34082
34878
  sortPaths(paths);
@@ -34086,7 +34882,7 @@ function buildFileIndex(cwd) {
34086
34882
  return data;
34087
34883
  }
34088
34884
  async function buildFileIndexAsync(cwd) {
34089
- const resolved = path20.resolve(cwd);
34885
+ const resolved = path21.resolve(cwd);
34090
34886
  const paths = [];
34091
34887
  await walkWorkspaceTreeAsync(resolved, resolved, paths, createWalkYieldState());
34092
34888
  await yieldToEventLoop();
@@ -34098,13 +34894,13 @@ async function buildFileIndexAsync(cwd) {
34098
34894
  }
34099
34895
 
34100
34896
  // src/files/index/load-file-index.ts
34101
- import fs19 from "node:fs";
34102
- import path21 from "node:path";
34897
+ import fs20 from "node:fs";
34898
+ import path22 from "node:path";
34103
34899
  function loadFileIndex(cwd) {
34104
- const resolved = path21.resolve(cwd);
34900
+ const resolved = path22.resolve(cwd);
34105
34901
  const indexPath = getIndexPathForCwd(resolved);
34106
34902
  try {
34107
- const raw = fs19.readFileSync(indexPath, "utf8");
34903
+ const raw = fs20.readFileSync(indexPath, "utf8");
34108
34904
  const parsed = JSON.parse(raw);
34109
34905
  if (parsed !== null && typeof parsed === "object" && Array.isArray(parsed.paths)) {
34110
34906
  const obj = parsed;
@@ -34123,9 +34919,9 @@ function loadFileIndex(cwd) {
34123
34919
  }
34124
34920
 
34125
34921
  // src/files/index/ensure-file-index.ts
34126
- import path22 from "node:path";
34922
+ import path23 from "node:path";
34127
34923
  async function ensureFileIndexAsync(cwd) {
34128
- const resolved = path22.resolve(cwd);
34924
+ const resolved = path23.resolve(cwd);
34129
34925
  const cached2 = loadFileIndex(resolved);
34130
34926
  if (cached2 !== null) return { data: cached2, fromCache: true };
34131
34927
  const data = await buildFileIndexAsync(resolved);
@@ -34208,7 +35004,7 @@ function createFsWatcher(resolved, schedule) {
34208
35004
  }
34209
35005
  }
34210
35006
  function startFileIndexWatcher(cwd = getBridgeWorkspaceDirectory()) {
34211
- const resolved = path23.resolve(cwd);
35007
+ const resolved = path24.resolve(cwd);
34212
35008
  void buildFileIndexAsync(resolved).catch((e) => {
34213
35009
  console.error("[file-index] Initial index build failed:", e);
34214
35010
  });
@@ -34236,7 +35032,7 @@ function startFileIndexWatcher(cwd = getBridgeWorkspaceDirectory()) {
34236
35032
  }
34237
35033
 
34238
35034
  // src/dev-servers/manager/dev-server-manager.ts
34239
- import { rm } from "node:fs/promises";
35035
+ import { rm as rm2 } from "node:fs/promises";
34240
35036
 
34241
35037
  // src/dev-servers/process/send-server-status.ts
34242
35038
  function sendDevServerStatus(getWs, serverId, status, options) {
@@ -34255,15 +35051,15 @@ function sendDevServerStatus(getWs, serverId, status, options) {
34255
35051
 
34256
35052
  // src/dev-servers/process/terminate-child-process.ts
34257
35053
  async function sigtermAndWaitForExit(proc, graceMs, log2, shortId) {
34258
- const exited = new Promise((resolve16) => {
34259
- proc.once("exit", () => resolve16());
35054
+ const exited = new Promise((resolve17) => {
35055
+ proc.once("exit", () => resolve17());
34260
35056
  });
34261
35057
  log2(`[dev-server] Sending SIGTERM to ${shortId} (pid=${proc.pid ?? "?"}).`);
34262
35058
  try {
34263
35059
  proc.kill("SIGTERM");
34264
35060
  } catch {
34265
35061
  }
34266
- await Promise.race([exited, new Promise((resolve16) => setTimeout(resolve16, graceMs))]);
35062
+ await Promise.race([exited, new Promise((resolve17) => setTimeout(resolve17, graceMs))]);
34267
35063
  }
34268
35064
  function forceKillChild(proc, log2, shortId, graceMs) {
34269
35065
  log2(
@@ -34277,7 +35073,7 @@ function forceKillChild(proc, log2, shortId, graceMs) {
34277
35073
  }
34278
35074
 
34279
35075
  // src/dev-servers/process/wire-dev-server-child-process.ts
34280
- import fs20 from "node:fs";
35076
+ import fs21 from "node:fs";
34281
35077
 
34282
35078
  // src/dev-servers/manager/forward-pipe.ts
34283
35079
  function forwardChildPipe(childReadable, terminal, onData) {
@@ -34313,7 +35109,7 @@ function wireDevServerChildProcess(d) {
34313
35109
  d.setPollInterval(void 0);
34314
35110
  return;
34315
35111
  }
34316
- fs20.readFile(d.mergedLogPath, (err, buf) => {
35112
+ fs21.readFile(d.mergedLogPath, (err, buf) => {
34317
35113
  if (err || (d.getSpawnGeneration() ?? 0) !== d.scheduledGen) return;
34318
35114
  if (buf.length <= d.mergedReadPos.value) return;
34319
35115
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
@@ -34351,7 +35147,7 @@ ${errTail}` : ""}`);
34351
35147
  d.sendStatus(code === 0 || code == null ? "stopped" : "error", detail, tails);
34352
35148
  };
34353
35149
  if (mergedPath) {
34354
- fs20.readFile(mergedPath, (err, buf) => {
35150
+ fs21.readFile(mergedPath, (err, buf) => {
34355
35151
  if (!err && buf.length > d.mergedReadPos.value) {
34356
35152
  const chunk = Buffer.from(buf.subarray(d.mergedReadPos.value));
34357
35153
  if (chunk.length > 0) {
@@ -34453,13 +35249,13 @@ function parseDevServerDefs(servers) {
34453
35249
  }
34454
35250
 
34455
35251
  // src/dev-servers/manager/shell-spawn/utils.ts
34456
- import fs21 from "node:fs";
35252
+ import fs22 from "node:fs";
34457
35253
  function isSpawnEbadf(e) {
34458
35254
  return typeof e === "object" && e !== null && "code" in e && e.code === "EBADF";
34459
35255
  }
34460
35256
  function rmDirQuiet(dir) {
34461
35257
  try {
34462
- fs21.rmSync(dir, { recursive: true, force: true });
35258
+ fs22.rmSync(dir, { recursive: true, force: true });
34463
35259
  } catch {
34464
35260
  }
34465
35261
  }
@@ -34467,7 +35263,7 @@ var cachedDevNullReadFd;
34467
35263
  function devNullReadFd() {
34468
35264
  if (cachedDevNullReadFd === void 0) {
34469
35265
  const devPath = process.platform === "win32" ? "nul" : "/dev/null";
34470
- cachedDevNullReadFd = fs21.openSync(devPath, "r");
35266
+ cachedDevNullReadFd = fs22.openSync(devPath, "r");
34471
35267
  }
34472
35268
  return cachedDevNullReadFd;
34473
35269
  }
@@ -34541,15 +35337,15 @@ function trySpawnShellTruePiped(command, env, cwd, devNullFd, signal) {
34541
35337
 
34542
35338
  // src/dev-servers/manager/shell-spawn/try-spawn-merged-log-file.ts
34543
35339
  import { spawn as spawn6 } from "node:child_process";
34544
- import fs22 from "node:fs";
35340
+ import fs23 from "node:fs";
34545
35341
  import { tmpdir } from "node:os";
34546
- import path24 from "node:path";
35342
+ import path25 from "node:path";
34547
35343
  function trySpawnMergedLogFile(command, env, cwd, signal) {
34548
- const tmpRoot = fs22.mkdtempSync(path24.join(tmpdir(), "ba-devsrv-log-"));
34549
- const logPath = path24.join(tmpRoot, "combined.log");
35344
+ const tmpRoot = fs23.mkdtempSync(path25.join(tmpdir(), "ba-devsrv-log-"));
35345
+ const logPath = path25.join(tmpRoot, "combined.log");
34550
35346
  let logFd;
34551
35347
  try {
34552
- logFd = fs22.openSync(logPath, "a");
35348
+ logFd = fs23.openSync(logPath, "a");
34553
35349
  } catch {
34554
35350
  rmDirQuiet(tmpRoot);
34555
35351
  return null;
@@ -34568,7 +35364,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
34568
35364
  } else {
34569
35365
  proc = spawn6("/bin/sh", ["-c", command], { env, cwd, stdio, ...signal ? { signal } : {} });
34570
35366
  }
34571
- fs22.closeSync(logFd);
35367
+ fs23.closeSync(logFd);
34572
35368
  return {
34573
35369
  proc,
34574
35370
  pipedStdoutStderr: true,
@@ -34577,7 +35373,7 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
34577
35373
  };
34578
35374
  } catch (e) {
34579
35375
  try {
34580
- fs22.closeSync(logFd);
35376
+ fs23.closeSync(logFd);
34581
35377
  } catch {
34582
35378
  }
34583
35379
  rmDirQuiet(tmpRoot);
@@ -34588,22 +35384,22 @@ function trySpawnMergedLogFile(command, env, cwd, signal) {
34588
35384
 
34589
35385
  // src/dev-servers/manager/shell-spawn/try-spawn-shell-script-log-redirect.ts
34590
35386
  import { spawn as spawn7 } from "node:child_process";
34591
- import fs23 from "node:fs";
35387
+ import fs24 from "node:fs";
34592
35388
  import { tmpdir as tmpdir2 } from "node:os";
34593
- import path25 from "node:path";
35389
+ import path26 from "node:path";
34594
35390
  function shSingleQuote(s) {
34595
35391
  return `'${s.replace(/'/g, `'\\''`)}'`;
34596
35392
  }
34597
35393
  function trySpawnShellScriptLogRedirectUnix(command, env, cwd, signal) {
34598
- const tmpRoot = fs23.mkdtempSync(path25.join(tmpdir2(), "ba-devsrv-sh-"));
34599
- const logPath = path25.join(tmpRoot, "combined.log");
34600
- const innerPath = path25.join(tmpRoot, "_cmd.sh");
34601
- const runnerPath = path25.join(tmpRoot, "_run.sh");
35394
+ const tmpRoot = fs24.mkdtempSync(path26.join(tmpdir2(), "ba-devsrv-sh-"));
35395
+ const logPath = path26.join(tmpRoot, "combined.log");
35396
+ const innerPath = path26.join(tmpRoot, "_cmd.sh");
35397
+ const runnerPath = path26.join(tmpRoot, "_run.sh");
34602
35398
  try {
34603
- fs23.writeFileSync(innerPath, `#!/bin/sh
35399
+ fs24.writeFileSync(innerPath, `#!/bin/sh
34604
35400
  ${command}
34605
35401
  `);
34606
- fs23.writeFileSync(
35402
+ fs24.writeFileSync(
34607
35403
  runnerPath,
34608
35404
  `#!/bin/sh
34609
35405
  cd ${shSingleQuote(cwd)}
@@ -34629,13 +35425,13 @@ cd ${shSingleQuote(cwd)}
34629
35425
  }
34630
35426
  }
34631
35427
  function trySpawnShellScriptLogRedirectWin(command, env, cwd, signal) {
34632
- const tmpRoot = fs23.mkdtempSync(path25.join(tmpdir2(), "ba-devsrv-sh-"));
34633
- const logPath = path25.join(tmpRoot, "combined.log");
34634
- const runnerPath = path25.join(tmpRoot, "_run.bat");
35428
+ const tmpRoot = fs24.mkdtempSync(path26.join(tmpdir2(), "ba-devsrv-sh-"));
35429
+ const logPath = path26.join(tmpRoot, "combined.log");
35430
+ const runnerPath = path26.join(tmpRoot, "_run.bat");
34635
35431
  const q = (p) => `"${p.replace(/"/g, '""')}"`;
34636
35432
  const com = process.env.ComSpec || "cmd.exe";
34637
35433
  try {
34638
- fs23.writeFileSync(
35434
+ fs24.writeFileSync(
34639
35435
  runnerPath,
34640
35436
  `@ECHO OFF\r
34641
35437
  CD /D ${q(cwd)}\r
@@ -34745,8 +35541,90 @@ var StreamTail = class {
34745
35541
  }
34746
35542
  };
34747
35543
 
34748
- // src/dev-servers/manager/dev-server-manager.ts
35544
+ // src/dev-servers/manager/dev-server-constants.ts
34749
35545
  var BRIDGE_SHUTDOWN_GRACE_MS = 8e3;
35546
+
35547
+ // src/dev-servers/manager/dev-server-firehose-messages.ts
35548
+ function buildFirehoseSnapshotMessage(params) {
35549
+ const payload = {
35550
+ type: "log_snapshot",
35551
+ serverId: params.serverId,
35552
+ viewerId: params.viewerId,
35553
+ stdoutTail: params.tails.stdout,
35554
+ stderrTail: params.tails.stderr
35555
+ };
35556
+ return params.e2ee ? params.e2ee.encryptFields(payload, ["stdoutTail", "stderrTail"]) : payload;
35557
+ }
35558
+ function buildFirehoseLogChunkMessage(params) {
35559
+ const payload = {
35560
+ type: "log_chunk",
35561
+ serverId: params.serverId,
35562
+ stream: params.stream,
35563
+ text: params.text
35564
+ };
35565
+ return params.e2ee ? params.e2ee.encryptFields(payload, ["text"]) : payload;
35566
+ }
35567
+
35568
+ // src/dev-servers/manager/dev-server-firehose-sink.ts
35569
+ var DevServerFirehoseSink = class {
35570
+ constructor(options) {
35571
+ this.options = options;
35572
+ }
35573
+ logViewerRefCountByServerId = /* @__PURE__ */ new Map();
35574
+ firehoseSend = null;
35575
+ attach(send) {
35576
+ this.firehoseSend = send;
35577
+ }
35578
+ detach() {
35579
+ this.firehoseSend = null;
35580
+ this.logViewerRefCountByServerId.clear();
35581
+ }
35582
+ openLogViewer(serverId, viewerId) {
35583
+ const next = (this.logViewerRefCountByServerId.get(serverId) ?? 0) + 1;
35584
+ this.logViewerRefCountByServerId.set(serverId, next);
35585
+ this.sendSnapshot(serverId, viewerId);
35586
+ }
35587
+ closeLogViewer(serverId) {
35588
+ const n = (this.logViewerRefCountByServerId.get(serverId) ?? 0) - 1;
35589
+ if (n <= 0) this.logViewerRefCountByServerId.delete(serverId);
35590
+ else this.logViewerRefCountByServerId.set(serverId, n);
35591
+ }
35592
+ pushLogChunk(serverId, stream, chunk) {
35593
+ if ((this.logViewerRefCountByServerId.get(serverId) ?? 0) <= 0) return;
35594
+ if (!this.options.isPipedCaptureEnabled(serverId)) return;
35595
+ if (!this.firehoseSend) return;
35596
+ const text = chunk.toString("utf8");
35597
+ setImmediate(() => {
35598
+ if (!this.firehoseSend) return;
35599
+ this.firehoseSend(buildFirehoseLogChunkMessage({ serverId, stream, text, e2ee: this.options.e2ee }));
35600
+ });
35601
+ }
35602
+ sendSnapshot(serverId, viewerId) {
35603
+ const payload = buildFirehoseSnapshotMessage({
35604
+ serverId,
35605
+ viewerId,
35606
+ tails: this.options.getTails(serverId),
35607
+ e2ee: this.options.e2ee
35608
+ });
35609
+ setImmediate(() => {
35610
+ const send = this.firehoseSend;
35611
+ if (!send) return;
35612
+ send(payload);
35613
+ });
35614
+ }
35615
+ };
35616
+
35617
+ // src/dev-servers/manager/cleanup-merged-log-dir.ts
35618
+ import { rm } from "node:fs/promises";
35619
+ function cleanupMergedLogDirForServer(map2, serverId) {
35620
+ const mergedDir = map2.get(serverId);
35621
+ if (!mergedDir) return;
35622
+ map2.delete(serverId);
35623
+ void rm(mergedDir, { recursive: true, force: true }).catch(() => {
35624
+ });
35625
+ }
35626
+
35627
+ // src/dev-servers/manager/dev-server-manager.ts
34750
35628
  var emptyTails = () => ({ stdout: [], stderr: [] });
34751
35629
  var DevServerManager = class {
34752
35630
  defsById = /* @__PURE__ */ new Map();
@@ -34754,66 +35632,36 @@ var DevServerManager = class {
34754
35632
  streamTailsByServerId = /* @__PURE__ */ new Map();
34755
35633
  spawnGenerationByServerId = /* @__PURE__ */ new Map();
34756
35634
  pipedCaptureByServerId = /* @__PURE__ */ new Map();
34757
- logViewerRefCountByServerId = /* @__PURE__ */ new Map();
34758
- firehoseSend = null;
34759
35635
  mergedLogPollByServerId = /* @__PURE__ */ new Map();
34760
35636
  mergedLogCleanupDirByServerId = /* @__PURE__ */ new Map();
34761
35637
  abortControllersByServerId = /* @__PURE__ */ new Map();
34762
35638
  getWs;
34763
35639
  log;
34764
35640
  getBridgeCwd;
35641
+ e2ee;
35642
+ firehoseSink;
34765
35643
  constructor(options) {
34766
35644
  this.getWs = options.getWs;
34767
35645
  this.log = options.log;
34768
35646
  this.getBridgeCwd = options.getBridgeCwd ?? (() => process.cwd());
35647
+ this.e2ee = options.e2ee;
35648
+ this.firehoseSink = new DevServerFirehoseSink({
35649
+ getTails: (serverId) => this.snapshotTails(serverId),
35650
+ isPipedCaptureEnabled: (serverId) => this.pipedCaptureByServerId.get(serverId) === true,
35651
+ e2ee: this.e2ee
35652
+ });
34769
35653
  }
34770
35654
  attachFirehose(send) {
34771
- this.firehoseSend = send;
35655
+ this.firehoseSink.attach(send);
34772
35656
  }
34773
35657
  detachFirehose() {
34774
- this.firehoseSend = null;
34775
- this.logViewerRefCountByServerId.clear();
35658
+ this.firehoseSink.detach();
34776
35659
  }
34777
35660
  handleFirehoseLogViewerOpen(serverId, _viewerId) {
34778
- const next = (this.logViewerRefCountByServerId.get(serverId) ?? 0) + 1;
34779
- this.logViewerRefCountByServerId.set(serverId, next);
34780
- this.sendSnapshotToFirehose(serverId, _viewerId);
35661
+ this.firehoseSink.openLogViewer(serverId, _viewerId);
34781
35662
  }
34782
35663
  handleFirehoseLogViewerClose(serverId, _viewerId) {
34783
- const n = (this.logViewerRefCountByServerId.get(serverId) ?? 0) - 1;
34784
- if (n <= 0) this.logViewerRefCountByServerId.delete(serverId);
34785
- else this.logViewerRefCountByServerId.set(serverId, n);
34786
- }
34787
- sendSnapshotToFirehose(serverId, viewerId) {
34788
- const tails = this.streamTailsByServerId.get(serverId);
34789
- const payload = {
34790
- type: "log_snapshot",
34791
- serverId,
34792
- viewerId,
34793
- stdoutTail: tails?.stdout.getTail() ?? [],
34794
- stderrTail: tails?.stderr.getTail() ?? []
34795
- };
34796
- setImmediate(() => {
34797
- const send = this.firehoseSend;
34798
- if (!send) return;
34799
- send(payload);
34800
- });
34801
- }
34802
- pushRemoteLogChunk(serverId, stream, chunk) {
34803
- if ((this.logViewerRefCountByServerId.get(serverId) ?? 0) <= 0) return;
34804
- if (!this.pipedCaptureByServerId.get(serverId)) return;
34805
- const send = this.firehoseSend;
34806
- if (!send) return;
34807
- const text = chunk.toString("utf8");
34808
- setImmediate(() => {
34809
- if (!this.firehoseSend) return;
34810
- this.firehoseSend({
34811
- type: "log_chunk",
34812
- serverId,
34813
- stream,
34814
- text
34815
- });
34816
- });
35664
+ this.firehoseSink.closeLogViewer(serverId);
34817
35665
  }
34818
35666
  applyConfig(servers) {
34819
35667
  this.defsById.clear();
@@ -34866,12 +35714,7 @@ var DevServerManager = class {
34866
35714
  }
34867
35715
  this.clearTails(serverId);
34868
35716
  this.pipedCaptureByServerId.delete(serverId);
34869
- const mergedDir = this.mergedLogCleanupDirByServerId.get(serverId);
34870
- if (mergedDir) {
34871
- this.mergedLogCleanupDirByServerId.delete(serverId);
34872
- void rm(mergedDir, { recursive: true, force: true }).catch(() => {
34873
- });
34874
- }
35717
+ cleanupMergedLogDirForServer(this.mergedLogCleanupDirByServerId, serverId);
34875
35718
  this.sendStatus(serverId, "stopped", void 0, tails);
34876
35719
  }
34877
35720
  start(serverId) {
@@ -34954,7 +35797,7 @@ var DevServerManager = class {
34954
35797
  log: this.log,
34955
35798
  stdoutTail,
34956
35799
  stderrTail,
34957
- pushRemoteLogChunk: (sid, stream, chunk) => this.pushRemoteLogChunk(sid, stream, chunk),
35800
+ pushRemoteLogChunk: (sid, stream, chunk) => this.firehoseSink.pushLogChunk(sid, stream, chunk),
34958
35801
  sendStatus: (status, detail, tails) => this.sendStatus(serverId, status, detail, tails),
34959
35802
  setPollInterval: (iv) => {
34960
35803
  if (iv) this.mergedLogPollByServerId.set(serverId, iv);
@@ -34968,7 +35811,7 @@ var DevServerManager = class {
34968
35811
  this.mergedLogCleanupDirByServerId.delete(serverId);
34969
35812
  },
34970
35813
  rmMergedCleanupDir: (dir) => {
34971
- void rm(dir, { recursive: true, force: true }).catch(() => {
35814
+ void rm2(dir, { recursive: true, force: true }).catch(() => {
34972
35815
  });
34973
35816
  },
34974
35817
  clearTailBuffers: () => this.clearTails(serverId)
@@ -35005,12 +35848,7 @@ var DevServerManager = class {
35005
35848
  this.processes.delete(serverId);
35006
35849
  this.clearPoll(serverId);
35007
35850
  this.pipedCaptureByServerId.delete(serverId);
35008
- const mergedDir = this.mergedLogCleanupDirByServerId.get(serverId);
35009
- if (mergedDir) {
35010
- this.mergedLogCleanupDirByServerId.delete(serverId);
35011
- void rm(mergedDir, { recursive: true, force: true }).catch(() => {
35012
- });
35013
- }
35851
+ cleanupMergedLogDirForServer(this.mergedLogCleanupDirByServerId, serverId);
35014
35852
  const tails = this.snapshotTails(serverId);
35015
35853
  this.clearTails(serverId);
35016
35854
  this.sendStatus(serverId, "unknown", "Bridge closed before process exited", tails);
@@ -35104,7 +35942,7 @@ async function proxyToLocal(request) {
35104
35942
  };
35105
35943
  const maxAttempts = isIdempotentProxyMethod(request.method) ? LOCAL_PREVIEW_FETCH_RETRY_DELAYS_MS.length + 1 : 1;
35106
35944
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
35107
- const once = await new Promise((resolve16) => {
35945
+ const once = await new Promise((resolve17) => {
35108
35946
  const req = mod.request(opts, (res) => {
35109
35947
  const chunks = [];
35110
35948
  res.on("data", (c) => chunks.push(c));
@@ -35115,7 +35953,7 @@ async function proxyToLocal(request) {
35115
35953
  if (typeof v === "string") headers[k] = v;
35116
35954
  else if (Array.isArray(v) && v[0]) headers[k] = v[0];
35117
35955
  }
35118
- resolve16({
35956
+ resolve17({
35119
35957
  id: request.id,
35120
35958
  statusCode: res.statusCode ?? 0,
35121
35959
  headers,
@@ -35124,7 +35962,7 @@ async function proxyToLocal(request) {
35124
35962
  });
35125
35963
  });
35126
35964
  req.on("error", (err) => {
35127
- resolve16({
35965
+ resolve17({
35128
35966
  id: request.id,
35129
35967
  statusCode: 0,
35130
35968
  headers: {},
@@ -35485,30 +36323,30 @@ function createOnBridgeIdentified(opts) {
35485
36323
  }
35486
36324
 
35487
36325
  // src/skills/discover-local-agent-skills.ts
35488
- import fs24 from "node:fs";
35489
- import path26 from "node:path";
36326
+ import fs25 from "node:fs";
36327
+ import path27 from "node:path";
35490
36328
  var SKILL_DISCOVERY_ROOTS = [".agents/skills", ".claude/skills", ".cursor/skills", "skills"];
35491
36329
  function discoverLocalSkills(cwd) {
35492
36330
  const out = [];
35493
36331
  const seenKeys = /* @__PURE__ */ new Set();
35494
36332
  for (const rel of SKILL_DISCOVERY_ROOTS) {
35495
- const base = path26.join(cwd, rel);
35496
- if (!fs24.existsSync(base) || !fs24.statSync(base).isDirectory()) continue;
36333
+ const base = path27.join(cwd, rel);
36334
+ if (!fs25.existsSync(base) || !fs25.statSync(base).isDirectory()) continue;
35497
36335
  let entries = [];
35498
36336
  try {
35499
- entries = fs24.readdirSync(base);
36337
+ entries = fs25.readdirSync(base);
35500
36338
  } catch {
35501
36339
  continue;
35502
36340
  }
35503
36341
  for (const name of entries) {
35504
- const dir = path26.join(base, name);
36342
+ const dir = path27.join(base, name);
35505
36343
  try {
35506
- if (!fs24.statSync(dir).isDirectory()) continue;
36344
+ if (!fs25.statSync(dir).isDirectory()) continue;
35507
36345
  } catch {
35508
36346
  continue;
35509
36347
  }
35510
- const skillMd = path26.join(dir, "SKILL.md");
35511
- if (!fs24.existsSync(skillMd)) continue;
36348
+ const skillMd = path27.join(dir, "SKILL.md");
36349
+ if (!fs25.existsSync(skillMd)) continue;
35512
36350
  const key = `${rel}/${name}`;
35513
36351
  if (seenKeys.has(key)) continue;
35514
36352
  seenKeys.add(key);
@@ -35520,23 +36358,23 @@ function discoverLocalSkills(cwd) {
35520
36358
  function discoverSkillLayoutRoots(cwd) {
35521
36359
  const roots = [];
35522
36360
  for (const rel of SKILL_DISCOVERY_ROOTS) {
35523
- const base = path26.join(cwd, rel);
35524
- if (!fs24.existsSync(base) || !fs24.statSync(base).isDirectory()) continue;
36361
+ const base = path27.join(cwd, rel);
36362
+ if (!fs25.existsSync(base) || !fs25.statSync(base).isDirectory()) continue;
35525
36363
  let entries = [];
35526
36364
  try {
35527
- entries = fs24.readdirSync(base);
36365
+ entries = fs25.readdirSync(base);
35528
36366
  } catch {
35529
36367
  continue;
35530
36368
  }
35531
36369
  const skills2 = [];
35532
36370
  for (const name of entries) {
35533
- const dir = path26.join(base, name);
36371
+ const dir = path27.join(base, name);
35534
36372
  try {
35535
- if (!fs24.statSync(dir).isDirectory()) continue;
36373
+ if (!fs25.statSync(dir).isDirectory()) continue;
35536
36374
  } catch {
35537
36375
  continue;
35538
36376
  }
35539
- if (!fs24.existsSync(path26.join(dir, "SKILL.md"))) continue;
36377
+ if (!fs25.existsSync(path27.join(dir, "SKILL.md"))) continue;
35540
36378
  const relPath = `${rel}/${name}`.replace(/\\/g, "/");
35541
36379
  skills2.push({ name, relPath });
35542
36380
  }
@@ -35638,7 +36476,7 @@ function reportGitRepos(getWs, log2) {
35638
36476
  var handleAuthToken = (msg, { log: log2 }) => {
35639
36477
  if (typeof msg.token !== "string") return;
35640
36478
  log2("Received auth token. Save it for future runs:");
35641
- log2(` export BUILDAMATON_AUTH_TOKEN="${msg.token}"`);
36479
+ log2(` export BUILDAUTOMATON_AUTH_TOKEN="${msg.token}"`);
35642
36480
  };
35643
36481
 
35644
36482
  // src/bridge/routing/handlers/bridge-identified.ts
@@ -35681,13 +36519,49 @@ var handleAgentConfigMessage = (msg, deps) => {
35681
36519
  };
35682
36520
 
35683
36521
  // src/agents/acp/from-bridge/handle-bridge-prompt.ts
35684
- import * as path28 from "node:path";
36522
+ import * as path29 from "node:path";
36523
+
36524
+ // src/agents/acp/from-bridge/bridge-prompt-wiring.ts
36525
+ function createBridgePromptSenders(deps, getWs) {
36526
+ const sendBridgeMessage = (message, encryptedFields = []) => {
36527
+ const s = getWs();
36528
+ if (!s) return false;
36529
+ const wire = deps.e2ee && encryptedFields.length > 0 ? deps.e2ee.encryptFields(message, encryptedFields) : message;
36530
+ sendWsMessage(s, wire);
36531
+ return true;
36532
+ };
36533
+ const sendResult2 = (result) => {
36534
+ const skipEncryptForChangeSummaryFollowUp = result.type === "prompt_result" && result.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
36535
+ const encryptedFields = result.type === "prompt_result" && !skipEncryptForChangeSummaryFollowUp ? ["output", "error"] : [];
36536
+ sendBridgeMessage(result, encryptedFields);
36537
+ };
36538
+ const sendSessionUpdate = (payload) => {
36539
+ const s = getWs();
36540
+ if (!s) {
36541
+ deps.log("[Bridge service] Session update not sent: not connected to the bridge.");
36542
+ return;
36543
+ }
36544
+ const p = payload;
36545
+ const wire = p.type === "session_update" && deps.e2ee ? deps.e2ee.encryptFields(payload, ["payload"]) : p.type === "session_file_change" && deps.e2ee ? deps.e2ee.encryptFields(payload, [
36546
+ "path",
36547
+ "oldText",
36548
+ "newText",
36549
+ "patchContent",
36550
+ "isDirectory",
36551
+ "directoryRemoved"
36552
+ ]) : payload;
36553
+ sendWsMessage(s, wire);
36554
+ };
36555
+ return { sendBridgeMessage, sendResult: sendResult2, sendSessionUpdate };
36556
+ }
36557
+
36558
+ // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
35685
36559
  import { execFile as execFile10 } from "node:child_process";
35686
36560
  import { promisify as promisify10 } from "node:util";
35687
36561
 
35688
36562
  // src/git/bridge-queue-key.ts
35689
- import * as path27 from "node:path";
35690
- import { createHash } from "node:crypto";
36563
+ import * as path28 from "node:path";
36564
+ import { createHash as createHash2 } from "node:crypto";
35691
36565
  function normalizeCanonicalGitUrl(url2) {
35692
36566
  let s = url2.trim();
35693
36567
  if (!s) return s;
@@ -35711,14 +36585,14 @@ function normalizeCanonicalGitUrl(url2) {
35711
36585
  }
35712
36586
  function canonicalUrlToRepoIdSync(url2) {
35713
36587
  const normalized = normalizeCanonicalGitUrl(url2);
35714
- return createHash("sha256").update(normalized).digest("hex").slice(0, 32);
36588
+ return createHash2("sha256").update(normalized).digest("hex").slice(0, 32);
35715
36589
  }
35716
36590
  function fallbackRepoIdFromPath(absPath) {
35717
- return createHash("sha256").update(path27.resolve(absPath)).digest("hex").slice(0, 32);
36591
+ return createHash2("sha256").update(path28.resolve(absPath)).digest("hex").slice(0, 32);
35718
36592
  }
35719
36593
  async function resolveBridgeQueueBindFields(options) {
35720
36594
  const { effectiveCwd, worktreePaths, primaryRepoRoots, log: log2 } = options;
35721
- const cwdAbs = worktreePaths.length > 0 ? path27.resolve(worktreePaths[0]) : path27.resolve(effectiveCwd);
36595
+ const cwdAbs = worktreePaths.length > 0 ? path28.resolve(worktreePaths[0]) : path28.resolve(effectiveCwd);
35722
36596
  if (!primaryRepoRoots.length) {
35723
36597
  log2("[Bridge service] Prompt queue bind skipped: no Git repository roots under the working directory.");
35724
36598
  return null;
@@ -35740,7 +36614,7 @@ async function resolveBridgeQueueBindFields(options) {
35740
36614
  return { canonicalQueueKey, repoId, cwdAbs };
35741
36615
  }
35742
36616
 
35743
- // src/agents/acp/from-bridge/handle-bridge-prompt.ts
36617
+ // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
35744
36618
  var execFileAsync9 = promisify10(execFile10);
35745
36619
  async function readGitBranch(cwd) {
35746
36620
  try {
@@ -35751,6 +36625,132 @@ async function readGitBranch(cwd) {
35751
36625
  return null;
35752
36626
  }
35753
36627
  }
36628
+ async function runBridgePromptPreamble(params) {
36629
+ const { getWs, log: log2, sessionWorktreeManager, sessionId, runId, effectiveCwd } = params;
36630
+ const s = getWs();
36631
+ const worktreePaths = sessionWorktreeManager.getWorktreePathsForSession(sessionId) ?? [];
36632
+ const repoRoots = await resolveSnapshotRepoRoots({
36633
+ worktreePaths,
36634
+ fallbackCwd: effectiveCwd,
36635
+ log: log2
36636
+ });
36637
+ if (s && sessionId) {
36638
+ const bind = await resolveBridgeQueueBindFields({
36639
+ effectiveCwd,
36640
+ worktreePaths,
36641
+ primaryRepoRoots: repoRoots,
36642
+ log: log2
36643
+ });
36644
+ if (bind) {
36645
+ sendWsMessage(s, {
36646
+ type: "bridge_queue_bind",
36647
+ sessionId,
36648
+ canonicalQueueKey: bind.canonicalQueueKey,
36649
+ repoId: bind.repoId,
36650
+ cwdAbs: bind.cwdAbs
36651
+ });
36652
+ }
36653
+ }
36654
+ if (s && sessionId) {
36655
+ const cliGitBranch = await readGitBranch(effectiveCwd);
36656
+ sendWsMessage(s, {
36657
+ type: "session_git_context_report",
36658
+ sessionId,
36659
+ cliGitBranch,
36660
+ agentUsesWorktree: sessionWorktreeManager.usesWorktreeSession(sessionId)
36661
+ });
36662
+ }
36663
+ if (s && sessionId && runId) {
36664
+ const cap = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false, error: "No git repos" };
36665
+ sendWsMessage(s, {
36666
+ type: "pre_turn_snapshot_report",
36667
+ sessionId,
36668
+ turnId: runId,
36669
+ captured: cap.ok
36670
+ });
36671
+ }
36672
+ }
36673
+ function parseChangeSummarySnapshots(raw) {
36674
+ if (!Array.isArray(raw) || raw.length === 0) return void 0;
36675
+ const out = [];
36676
+ for (const item of raw) {
36677
+ if (!item || typeof item !== "object") continue;
36678
+ const o = item;
36679
+ const path34 = typeof o.path === "string" && o.path.trim() !== "" ? o.path.trim() : "";
36680
+ if (!path34) continue;
36681
+ const row = { path: path34 };
36682
+ if (typeof o.patchContent === "string") row.patchContent = o.patchContent;
36683
+ if (typeof o.oldText === "string") row.oldText = o.oldText;
36684
+ if (typeof o.newText === "string") row.newText = o.newText;
36685
+ if (o.directoryRemoved === true) row.directoryRemoved = true;
36686
+ out.push(row);
36687
+ }
36688
+ return out.length > 0 ? out : void 0;
36689
+ }
36690
+ function parseFollowUpFieldsFromPromptMessage(msg) {
36691
+ const followUpCatalogPromptId = typeof msg.followUpCatalogPromptId === "string" && msg.followUpCatalogPromptId.trim() !== "" ? msg.followUpCatalogPromptId.trim() : null;
36692
+ const rawPaths = msg.sessionChangeSummaryFilePaths;
36693
+ const sessionChangeSummaryFilePaths = Array.isArray(rawPaths) ? rawPaths.filter((p) => typeof p === "string" && p.trim() !== "").map((p) => p.trim()) : void 0;
36694
+ const sessionChangeSummaryFileSnapshots = parseChangeSummarySnapshots(msg.sessionChangeSummaryFileSnapshots);
36695
+ return { followUpCatalogPromptId, sessionChangeSummaryFilePaths, sessionChangeSummaryFileSnapshots };
36696
+ }
36697
+
36698
+ // src/agents/acp/change-summary/decrypt-change-summary-file-input.ts
36699
+ function decryptChangeSummaryFileInput(row, e2ee) {
36700
+ if (!e2ee) return row;
36701
+ for (const field of ["path", "patchContent", "oldText", "newText"]) {
36702
+ const raw = row[field];
36703
+ if (typeof raw !== "string" || raw.trim() === "") continue;
36704
+ let o;
36705
+ try {
36706
+ o = JSON.parse(raw);
36707
+ } catch {
36708
+ continue;
36709
+ }
36710
+ if (!isE2eeEnvelope(o.ee)) continue;
36711
+ try {
36712
+ const d = e2ee.decryptMessage(o);
36713
+ const out = {
36714
+ path: typeof d.path === "string" ? d.path : row.path
36715
+ };
36716
+ if (d.directoryRemoved === true) out.directoryRemoved = true;
36717
+ else if (row.directoryRemoved === true) out.directoryRemoved = true;
36718
+ if (typeof d.patchContent === "string") out.patchContent = d.patchContent;
36719
+ else if (typeof row.patchContent === "string" && row.patchContent !== raw) out.patchContent = row.patchContent;
36720
+ if (typeof d.oldText === "string") out.oldText = d.oldText;
36721
+ else if (typeof row.oldText === "string") out.oldText = row.oldText;
36722
+ if (typeof d.newText === "string") out.newText = d.newText;
36723
+ else if (typeof row.newText === "string") out.newText = row.newText;
36724
+ return out;
36725
+ } catch {
36726
+ return row;
36727
+ }
36728
+ }
36729
+ return row;
36730
+ }
36731
+
36732
+ // src/agents/acp/change-summary/resolve-change-summary-prompt-for-agent.ts
36733
+ function hasSummarizePayload(f) {
36734
+ return f.directoryRemoved === true || f.patchContent != null && f.patchContent.trim() !== "" || f.oldText != null && f.oldText.trim() !== "" || f.newText != null && f.newText.trim() !== "";
36735
+ }
36736
+ function resolveChangeSummaryPromptForAgent(params) {
36737
+ const isBuiltin = params.followUpCatalogPromptId === BUILTIN_SESSION_CHANGE_SUMMARY_FOLLOW_UP_CATALOG_PROMPT_ID;
36738
+ const snaps = params.sessionChangeSummaryFileSnapshots;
36739
+ if (!isBuiltin || !snaps || snaps.length === 0) {
36740
+ return { promptText: params.bridgePromptText, sessionChangeSummaryFilePaths: void 0 };
36741
+ }
36742
+ const decrypted = dedupeSessionFileChangesByPath(snaps.map((row) => decryptChangeSummaryFileInput(row, params.e2ee)));
36743
+ const withPayload = decrypted.filter(hasSummarizePayload);
36744
+ if (withPayload.length === 0) {
36745
+ return { promptText: params.bridgePromptText, sessionChangeSummaryFilePaths: void 0 };
36746
+ }
36747
+ return {
36748
+ promptText: buildSessionChangeSummaryPrompt(withPayload),
36749
+ sessionChangeSummaryFilePaths: withPayload.map((f) => f.path)
36750
+ };
36751
+ }
36752
+
36753
+ // src/agents/acp/from-bridge/handle-bridge-prompt.ts
35754
36754
  function handleBridgePrompt(msg, deps) {
35755
36755
  const { getWs, log: log2, acpManager, sessionWorktreeManager } = deps;
35756
36756
  const rawPrompt = msg.prompt;
@@ -35758,21 +36758,22 @@ function handleBridgePrompt(msg, deps) {
35758
36758
  const sessionId = msg.sessionId;
35759
36759
  const runId = typeof msg.runId === "string" ? msg.runId : void 0;
35760
36760
  const promptId = typeof msg.id === "string" ? msg.id : void 0;
36761
+ const { sendBridgeMessage, sendResult: sendResult2, sendSessionUpdate } = createBridgePromptSenders(deps, getWs);
35761
36762
  if (!promptText.trim()) {
35762
36763
  log2(
35763
36764
  `[Bridge service] Prompt ignored: empty or missing prompt text (session ${typeof msg.sessionId === "string" ? msg.sessionId.slice(0, 8) : "\u2014"}\u2026, run ${typeof msg.runId === "string" ? msg.runId.slice(0, 8) : "\u2014"}\u2026).`
35764
36765
  );
35765
- const s = getWs();
35766
- if (s) {
35767
- sendWsMessage(s, {
36766
+ sendBridgeMessage(
36767
+ {
35768
36768
  type: "prompt_result",
35769
36769
  ...promptId ? { id: promptId } : {},
35770
36770
  ...sessionId ? { sessionId } : {},
35771
36771
  ...runId ? { runId } : {},
35772
36772
  success: false,
35773
36773
  error: "Empty or missing prompt text from the bridge; this turn was not sent to the agent."
35774
- });
35775
- }
36774
+ },
36775
+ ["error"]
36776
+ );
35776
36777
  return;
35777
36778
  }
35778
36779
  const isNewSession = msg.isNewSession === true;
@@ -35780,65 +36781,35 @@ function handleBridgePrompt(msg, deps) {
35780
36781
  const agentType = typeof msg.agentType === "string" && msg.agentType.trim() ? msg.agentType.trim() : void 0;
35781
36782
  const mode = typeof msg.mode === "string" && msg.mode.trim() ? msg.mode.trim() : void 0;
35782
36783
  acpManager.logPromptReceivedFromBridge({ agentType, mode });
35783
- const sendResult2 = (result) => {
35784
- const s = getWs();
35785
- if (s) sendWsMessage(s, result);
35786
- };
35787
- const sendSessionUpdate = (payload) => {
35788
- const s = getWs();
35789
- if (!s) {
35790
- log2("[Bridge service] Session update not sent: not connected to the bridge.");
35791
- return;
35792
- }
35793
- const p = payload;
35794
- sendWsMessage(s, payload);
35795
- };
35796
36784
  async function preambleAndPrompt(resolvedCwd) {
35797
- const s = getWs();
35798
- const effectiveCwd = path28.resolve(resolvedCwd ?? getBridgeWorkspaceDirectory());
35799
- const worktreePaths = sessionWorktreeManager.getWorktreePathsForSession(sessionId) ?? [];
35800
- const repoRoots = await resolveSnapshotRepoRoots({
35801
- worktreePaths,
35802
- fallbackCwd: effectiveCwd,
35803
- log: log2
36785
+ const effectiveCwd = path29.resolve(resolvedCwd ?? getBridgeWorkspaceDirectory());
36786
+ await runBridgePromptPreamble({
36787
+ getWs,
36788
+ log: log2,
36789
+ sessionWorktreeManager,
36790
+ sessionId,
36791
+ runId,
36792
+ effectiveCwd
35804
36793
  });
35805
- if (s && sessionId) {
35806
- const bind = await resolveBridgeQueueBindFields({
35807
- effectiveCwd,
35808
- worktreePaths,
35809
- primaryRepoRoots: repoRoots,
35810
- log: log2
35811
- });
35812
- if (bind) {
35813
- sendWsMessage(s, {
35814
- type: "bridge_queue_bind",
35815
- sessionId,
35816
- canonicalQueueKey: bind.canonicalQueueKey,
35817
- repoId: bind.repoId,
35818
- cwdAbs: bind.cwdAbs
35819
- });
35820
- }
35821
- }
35822
- if (s && sessionId) {
35823
- const cliGitBranch = await readGitBranch(effectiveCwd);
35824
- sendWsMessage(s, {
35825
- type: "session_git_context_report",
35826
- sessionId,
35827
- cliGitBranch,
35828
- agentUsesWorktree: sessionWorktreeManager.usesWorktreeSession(sessionId)
35829
- });
35830
- }
35831
- if (s && sessionId && runId) {
35832
- const cap = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false, error: "No git repos" };
35833
- sendWsMessage(s, {
35834
- type: "pre_turn_snapshot_report",
35835
- sessionId,
35836
- turnId: runId,
35837
- captured: cap.ok
35838
- });
36794
+ const {
36795
+ followUpCatalogPromptId,
36796
+ sessionChangeSummaryFilePaths: pathsFromBridge,
36797
+ sessionChangeSummaryFileSnapshots
36798
+ } = parseFollowUpFieldsFromPromptMessage(msg);
36799
+ const { promptText: resolvedPromptText, sessionChangeSummaryFilePaths } = resolveChangeSummaryPromptForAgent({
36800
+ followUpCatalogPromptId,
36801
+ sessionChangeSummaryFileSnapshots,
36802
+ bridgePromptText: promptText,
36803
+ e2ee: deps.e2ee
36804
+ });
36805
+ if (sessionChangeSummaryFileSnapshots && sessionChangeSummaryFileSnapshots.length > 0 && resolvedPromptText === promptText) {
36806
+ deps.log(
36807
+ "[Agent] Change-summary snapshots were present but the prompt was not rebuilt (decrypt failed or empty payloads); sending the bridge prompt as-is."
36808
+ );
35839
36809
  }
36810
+ const pathsForUpload = sessionChangeSummaryFilePaths ?? pathsFromBridge;
35840
36811
  acpManager.handlePrompt({
35841
- promptText,
36812
+ promptText: resolvedPromptText,
35842
36813
  promptId: msg.id,
35843
36814
  sessionId,
35844
36815
  runId,
@@ -35846,7 +36817,12 @@ function handleBridgePrompt(msg, deps) {
35846
36817
  agentType,
35847
36818
  cwd: effectiveCwd,
35848
36819
  sendResult: sendResult2,
35849
- sendSessionUpdate
36820
+ sendSessionUpdate,
36821
+ followUpCatalogPromptId,
36822
+ sessionChangeSummaryFilePaths: pathsForUpload,
36823
+ cloudApiBaseUrl: deps.cloudApiBaseUrl,
36824
+ getCloudAccessToken: deps.getCloudAccessToken,
36825
+ e2ee: deps.e2ee
35850
36826
  });
35851
36827
  }
35852
36828
  void sessionWorktreeManager.resolveCwdForPrompt(sessionId, { isNewSession, sessionWorktreesEnabled }).then((cwd) => preambleAndPrompt(cwd)).catch((err) => {
@@ -35903,8 +36879,8 @@ var PREVIEW_API_BASE_PATH = "/__preview";
35903
36879
  var PREVIEW_SECRET_HEADER = "X-Preview-Secret";
35904
36880
  var DEFAULT_PORT = 3e3;
35905
36881
  var DEFAULT_COMMAND = "npm run preview";
35906
- var PREVIEW_COMMAND_ENV = "BUILDAMATON_PREVIEW_COMMAND";
35907
- var PREVIEW_PORT_ENV = "BUILDAMATON_PREVIEW_PORT";
36882
+ var PREVIEW_COMMAND_ENV = "BUILDAUTOMATON_PREVIEW_COMMAND";
36883
+ var PREVIEW_PORT_ENV = "BUILDAUTOMATON_PREVIEW_PORT";
35908
36884
  var previewProcess = null;
35909
36885
  var previewPort = DEFAULT_PORT;
35910
36886
  var previewSecret = "";
@@ -35918,8 +36894,8 @@ function randomSecret() {
35918
36894
  }
35919
36895
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
35920
36896
  }
35921
- async function requestPreviewApi(port, secret, method, path33, body) {
35922
- const url2 = `http://127.0.0.1:${port}${path33}`;
36897
+ async function requestPreviewApi(port, secret, method, path34, body) {
36898
+ const url2 = `http://127.0.0.1:${port}${path34}`;
35923
36899
  const headers = {
35924
36900
  [PREVIEW_SECRET_HEADER]: secret,
35925
36901
  "Content-Type": "application/json"
@@ -35931,7 +36907,7 @@ async function requestPreviewApi(port, secret, method, path33, body) {
35931
36907
  });
35932
36908
  const data = await res.json().catch(() => ({}));
35933
36909
  if (!res.ok) {
35934
- throw new Error(data?.error ?? `Preview API ${method} ${path33}: ${res.status}`);
36910
+ throw new Error(data?.error ?? `Preview API ${method} ${path34}: ${res.status}`);
35935
36911
  }
35936
36912
  return data;
35937
36913
  }
@@ -35957,7 +36933,7 @@ var OPERATIONS = [
35957
36933
  var previewSkill = {
35958
36934
  id: "preview",
35959
36935
  name: "Preview",
35960
- description: "Start and manage a local preview server that implements the BuildAutomaton Preview Server API. Configure the command with BUILDAMATON_PREVIEW_COMMAND (default: npm run preview). The server receives PORT and PREVIEW_SECRET and must expose /__preview/status and /__preview/stop.",
36936
+ description: "Start and manage a local preview server that implements the BuildAutomaton Preview Server API. Configure the command with BUILDAUTOMATON_PREVIEW_COMMAND (default: npm run preview). The server receives PORT and PREVIEW_SECRET and must expose /__preview/status and /__preview/stop.",
35961
36937
  operations: OPERATIONS,
35962
36938
  async execute(operationId, params) {
35963
36939
  const command = getPreviewCommand();
@@ -36094,15 +37070,15 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
36094
37070
  };
36095
37071
 
36096
37072
  // src/files/list-dir.ts
36097
- import fs25 from "node:fs";
36098
- import path30 from "node:path";
37073
+ import fs26 from "node:fs";
37074
+ import path31 from "node:path";
36099
37075
 
36100
37076
  // src/files/ensure-under-cwd.ts
36101
- import path29 from "node:path";
37077
+ import path30 from "node:path";
36102
37078
  function ensureUnderCwd(relativePath, cwd = getBridgeWorkspaceDirectory()) {
36103
- const normalized = path29.normalize(relativePath).replace(/^(\.\/)+/, "");
36104
- const resolved = path29.resolve(cwd, normalized);
36105
- if (!resolved.startsWith(cwd + path29.sep) && resolved !== cwd) {
37079
+ const normalized = path30.normalize(relativePath).replace(/^(\.\/)+/, "");
37080
+ const resolved = path30.resolve(cwd, normalized);
37081
+ if (!resolved.startsWith(cwd + path30.sep) && resolved !== cwd) {
36106
37082
  return null;
36107
37083
  }
36108
37084
  return resolved;
@@ -36116,7 +37092,7 @@ async function listDirAsync(relativePath) {
36116
37092
  return { error: "Path is outside working directory" };
36117
37093
  }
36118
37094
  try {
36119
- const names = await fs25.promises.readdir(resolved, { withFileTypes: true });
37095
+ const names = await fs26.promises.readdir(resolved, { withFileTypes: true });
36120
37096
  const visible = names.filter((d) => !d.name.startsWith("."));
36121
37097
  const entries = [];
36122
37098
  for (let i = 0; i < visible.length; i++) {
@@ -36124,12 +37100,12 @@ async function listDirAsync(relativePath) {
36124
37100
  await yieldToEventLoop();
36125
37101
  }
36126
37102
  const d = visible[i];
36127
- const entryPath = path30.join(relativePath || ".", d.name).replace(/\\/g, "/");
36128
- const fullPath = path30.join(resolved, d.name);
37103
+ const entryPath = path31.join(relativePath || ".", d.name).replace(/\\/g, "/");
37104
+ const fullPath = path31.join(resolved, d.name);
36129
37105
  let isDir = d.isDirectory();
36130
37106
  if (d.isSymbolicLink()) {
36131
37107
  try {
36132
- const targetStat = await fs25.promises.stat(fullPath);
37108
+ const targetStat = await fs26.promises.stat(fullPath);
36133
37109
  isDir = targetStat.isDirectory();
36134
37110
  } catch {
36135
37111
  isDir = false;
@@ -36154,25 +37130,25 @@ async function listDirAsync(relativePath) {
36154
37130
  }
36155
37131
 
36156
37132
  // src/files/read-file.ts
36157
- import fs26 from "node:fs";
37133
+ import fs27 from "node:fs";
36158
37134
  import { StringDecoder } from "node:string_decoder";
36159
37135
  function resolveFilePath(relativePath) {
36160
37136
  const resolved = ensureUnderCwd(relativePath, getBridgeWorkspaceDirectory());
36161
37137
  if (!resolved) return { error: "Path is outside working directory" };
36162
37138
  let real;
36163
37139
  try {
36164
- real = fs26.realpathSync(resolved);
37140
+ real = fs27.realpathSync(resolved);
36165
37141
  } catch {
36166
37142
  real = resolved;
36167
37143
  }
36168
- const stat2 = fs26.statSync(real);
36169
- if (!stat2.isFile()) return { error: "Not a file" };
37144
+ const stat3 = fs27.statSync(real);
37145
+ if (!stat3.isFile()) return { error: "Not a file" };
36170
37146
  return real;
36171
37147
  }
36172
37148
  var LINE_CHUNK_SIZE = 64 * 1024;
36173
37149
  function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
36174
- const fileSize = fs26.statSync(filePath).size;
36175
- const fd = fs26.openSync(filePath, "r");
37150
+ const fileSize = fs27.statSync(filePath).size;
37151
+ const fd = fs27.openSync(filePath, "r");
36176
37152
  const bufSize = 64 * 1024;
36177
37153
  const buf = Buffer.alloc(bufSize);
36178
37154
  const decoder = new StringDecoder("utf8");
@@ -36185,7 +37161,7 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
36185
37161
  let line0Accum = "";
36186
37162
  try {
36187
37163
  let bytesRead;
36188
- while (!done && (bytesRead = fs26.readSync(fd, buf, 0, bufSize, null)) > 0) {
37164
+ while (!done && (bytesRead = fs27.readSync(fd, buf, 0, bufSize, null)) > 0) {
36189
37165
  const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
36190
37166
  partial2 = "";
36191
37167
  let lineStart = 0;
@@ -36320,10 +37296,10 @@ function readFileRange(filePath, startLine, endLine, lineOffsetIn, lineChunkSize
36320
37296
  }
36321
37297
  return { content: resultLines.join("\n"), size: fileSize };
36322
37298
  } finally {
36323
- fs26.closeSync(fd);
37299
+ fs27.closeSync(fd);
36324
37300
  }
36325
37301
  }
36326
- function readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
37302
+ function readFile3(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
36327
37303
  try {
36328
37304
  const result = resolveFilePath(relativePath);
36329
37305
  if (typeof result === "object") return result;
@@ -36331,43 +37307,45 @@ function readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize =
36331
37307
  if (hasRange) {
36332
37308
  return readFileRange(result, startLine, endLine, lineOffset, lineChunkSize);
36333
37309
  }
36334
- const stat2 = fs26.statSync(result);
36335
- const raw = fs26.readFileSync(result, "utf8");
37310
+ const stat3 = fs27.statSync(result);
37311
+ const raw = fs27.readFileSync(result, "utf8");
36336
37312
  const lines = raw.split(/\r?\n/);
36337
- return { content: raw, totalLines: lines.length, size: stat2.size };
37313
+ return { content: raw, totalLines: lines.length, size: stat3.size };
36338
37314
  } catch (err) {
36339
37315
  return { error: err instanceof Error ? err.message : String(err) };
36340
37316
  }
36341
37317
  }
36342
37318
  async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE) {
36343
37319
  await yieldToEventLoop();
36344
- return readFile2(relativePath, startLine, endLine, lineOffset, lineChunkSize);
37320
+ return readFile3(relativePath, startLine, endLine, lineOffset, lineChunkSize);
36345
37321
  }
36346
37322
 
36347
37323
  // src/files/handle-file-browser-search.ts
36348
37324
  var SEARCH_LIMIT = 100;
36349
- function handleFileBrowserSearch(msg, socket) {
37325
+ function handleFileBrowserSearch(msg, socket, e2ee) {
36350
37326
  void (async () => {
36351
37327
  await yieldToEventLoop();
36352
37328
  const q = typeof msg.q === "string" ? msg.q : "";
36353
37329
  const cwd = getBridgeWorkspaceDirectory();
36354
37330
  const index = loadFileIndex(cwd);
36355
37331
  if (index === null) {
36356
- sendWsMessage(socket, {
37332
+ const payload2 = {
36357
37333
  type: "file_browser_search_response",
36358
37334
  id: msg.id,
36359
37335
  paths: [],
36360
37336
  indexReady: false
36361
- });
37337
+ };
37338
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
36362
37339
  return;
36363
37340
  }
36364
37341
  const results = await searchFileIndexAsync(index, q, SEARCH_LIMIT);
36365
- sendWsMessage(socket, {
37342
+ const payload = {
36366
37343
  type: "file_browser_search_response",
36367
37344
  id: msg.id,
36368
37345
  paths: results,
36369
37346
  indexReady: true
36370
- });
37347
+ };
37348
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["paths"]) : payload);
36371
37349
  })();
36372
37350
  }
36373
37351
  function triggerFileIndexBuild() {
@@ -36379,7 +37357,10 @@ function triggerFileIndexBuild() {
36379
37357
  }
36380
37358
 
36381
37359
  // src/files/handle-file-browser-request.ts
36382
- function handleFileBrowserRequest(msg, socket) {
37360
+ function sendFileBrowserMessage(socket, e2ee, payload) {
37361
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
37362
+ }
37363
+ function handleFileBrowserRequest(msg, socket, e2ee) {
36383
37364
  void (async () => {
36384
37365
  const reqPath = msg.path.replace(/^\/+/, "") || ".";
36385
37366
  const op = msg.op === "read" ? "read" : "list";
@@ -36388,7 +37369,7 @@ function handleFileBrowserRequest(msg, socket) {
36388
37369
  if ("error" in result) {
36389
37370
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
36390
37371
  } else {
36391
- sendWsMessage(socket, { type: "file_browser_response", id: msg.id, entries: result.entries });
37372
+ sendFileBrowserMessage(socket, e2ee, { type: "file_browser_response", id: msg.id, entries: result.entries });
36392
37373
  if (reqPath === "." || reqPath === "") {
36393
37374
  triggerFileIndexBuild();
36394
37375
  }
@@ -36410,27 +37391,28 @@ function handleFileBrowserRequest(msg, socket) {
36410
37391
  size: result.size
36411
37392
  };
36412
37393
  if (result.lineOffset != null) payload.lineOffset = result.lineOffset;
36413
- sendWsMessage(socket, payload);
37394
+ sendFileBrowserMessage(socket, e2ee, payload);
36414
37395
  }
36415
37396
  }
36416
37397
  })();
36417
37398
  }
36418
37399
 
36419
37400
  // src/bridge/routing/handlers/file-browser-messages.ts
36420
- function handleFileBrowserRequestMessage(msg, { getWs }) {
37401
+ function handleFileBrowserRequestMessage(msg, { getWs, e2ee }) {
36421
37402
  if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
36422
37403
  const socket = getWs();
36423
37404
  if (!socket) return;
36424
37405
  handleFileBrowserRequest(
36425
37406
  msg,
36426
- socket
37407
+ socket,
37408
+ e2ee
36427
37409
  );
36428
37410
  }
36429
- function handleFileBrowserSearchMessage(msg, { getWs }) {
37411
+ function handleFileBrowserSearchMessage(msg, { getWs, e2ee }) {
36430
37412
  if (typeof msg.id !== "string") return;
36431
37413
  const socket = getWs();
36432
37414
  if (!socket) return;
36433
- handleFileBrowserSearch(msg, socket);
37415
+ handleFileBrowserSearch(msg, socket, e2ee);
36434
37416
  }
36435
37417
 
36436
37418
  // src/bridge/routing/handlers/skill-layout-request.ts
@@ -36442,8 +37424,8 @@ function handleSkillLayoutRequest(msg, deps) {
36442
37424
  }
36443
37425
 
36444
37426
  // src/skills/install-remote-skills.ts
36445
- import fs27 from "node:fs";
36446
- import path31 from "node:path";
37427
+ import fs28 from "node:fs";
37428
+ import path32 from "node:path";
36447
37429
  function installRemoteSkills(cwd, targetDir, items) {
36448
37430
  const installed2 = [];
36449
37431
  if (!Array.isArray(items)) {
@@ -36454,15 +37436,15 @@ function installRemoteSkills(cwd, targetDir, items) {
36454
37436
  if (typeof item.sourceId !== "string" || typeof item.skillName !== "string" || typeof item.versionHash !== "string" || !Array.isArray(item.files)) {
36455
37437
  continue;
36456
37438
  }
36457
- const skillDir = path31.join(cwd, targetDir, item.skillName);
37439
+ const skillDir = path32.join(cwd, targetDir, item.skillName);
36458
37440
  for (const f of item.files) {
36459
37441
  if (typeof f.path !== "string" || !f.text && !f.base64) continue;
36460
- const dest = path31.join(skillDir, f.path);
36461
- fs27.mkdirSync(path31.dirname(dest), { recursive: true });
37442
+ const dest = path32.join(skillDir, f.path);
37443
+ fs28.mkdirSync(path32.dirname(dest), { recursive: true });
36462
37444
  if (f.text !== void 0) {
36463
- fs27.writeFileSync(dest, f.text, "utf8");
37445
+ fs28.writeFileSync(dest, f.text, "utf8");
36464
37446
  } else if (f.base64) {
36465
- fs27.writeFileSync(dest, Buffer.from(f.base64, "base64"));
37447
+ fs28.writeFileSync(dest, Buffer.from(f.base64, "base64"));
36466
37448
  }
36467
37449
  }
36468
37450
  installed2.push({
@@ -36500,9 +37482,10 @@ var handleRefreshLocalSkills = (_msg, deps) => {
36500
37482
  };
36501
37483
 
36502
37484
  // src/bridge/routing/handlers/session-git-request.ts
36503
- function sendResult(ws, id, payload) {
37485
+ function sendResult(ws, id, payload, e2ee, encryptedFields = []) {
36504
37486
  if (!ws) return;
36505
- sendWsMessage(ws, { type: "session_git_result", id, ...payload });
37487
+ const message = { type: "session_git_result", id, ...payload };
37488
+ sendWsMessage(ws, e2ee && encryptedFields.length > 0 ? e2ee.encryptFields(message, encryptedFields) : message);
36506
37489
  }
36507
37490
  var handleSessionGitRequestMessage = (msg, deps) => {
36508
37491
  if (typeof msg.id !== "string") return;
@@ -36512,7 +37495,7 @@ var handleSessionGitRequestMessage = (msg, deps) => {
36512
37495
  return;
36513
37496
  void (async () => {
36514
37497
  const ws = deps.getWs();
36515
- const reply = (payload) => sendResult(ws, msg.id, payload);
37498
+ const reply = (payload, encryptedFields = []) => sendResult(ws, msg.id, payload, deps.e2ee, encryptedFields);
36516
37499
  try {
36517
37500
  if (action === "status") {
36518
37501
  const r = await deps.sessionWorktreeManager.getSessionWorkingTreeStatus(sessionId);
@@ -36538,7 +37521,7 @@ var handleSessionGitRequestMessage = (msg, deps) => {
36538
37521
  reply({
36539
37522
  ok: true,
36540
37523
  repos
36541
- });
37524
+ }, ["repos"]);
36542
37525
  return;
36543
37526
  }
36544
37527
  if (action === "push") {
@@ -36607,7 +37590,7 @@ var handleSessionDiscardedMessage = (msg, deps) => {
36607
37590
  };
36608
37591
 
36609
37592
  // src/bridge/routing/handlers/revert-turn-snapshot.ts
36610
- import * as fs28 from "node:fs";
37593
+ import * as fs29 from "node:fs";
36611
37594
  var handleRevertTurnSnapshotMessage = (msg, deps) => {
36612
37595
  const id = typeof msg.id === "string" ? msg.id : "";
36613
37596
  const sessionId = typeof msg.sessionId === "string" ? msg.sessionId : "";
@@ -36619,7 +37602,7 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
36619
37602
  if (!s) return;
36620
37603
  const agentBase = sessionWorktreeManager.getAgentCwdForSession(sessionId) ?? getBridgeWorkspaceDirectory();
36621
37604
  const file2 = snapshotFilePath(agentBase, turnId);
36622
- if (!fs28.existsSync(file2)) {
37605
+ if (!fs29.existsSync(file2)) {
36623
37606
  sendWsMessage(s, {
36624
37607
  type: "revert_turn_snapshot_result",
36625
37608
  id,
@@ -36640,8 +37623,15 @@ var handleRevertTurnSnapshotMessage = (msg, deps) => {
36640
37623
 
36641
37624
  // src/bridge/routing/handlers/dev-server-control.ts
36642
37625
  var handleDevServerControl = (msg, deps) => {
36643
- const serverId = typeof msg.serverId === "string" ? msg.serverId : "";
36644
- const action = msg.action === "start" || msg.action === "stop" ? msg.action : null;
37626
+ let wire = msg;
37627
+ try {
37628
+ wire = deps.e2ee ? deps.e2ee.decryptMessage(msg) : msg;
37629
+ } catch (e) {
37630
+ deps.log(`[E2EE] Could not decrypt dev server command: ${e instanceof Error ? e.message : String(e)}`);
37631
+ return;
37632
+ }
37633
+ const serverId = typeof wire.serverId === "string" ? wire.serverId : "";
37634
+ const action = wire.action === "start" || wire.action === "stop" ? wire.action : null;
36645
37635
  if (!serverId || !action) return;
36646
37636
  deps.devServerManager?.handleControl(serverId, action);
36647
37637
  };
@@ -36767,7 +37757,8 @@ function createMainBridgeWebSocketLifecycle(params) {
36767
37757
  messageDeps,
36768
37758
  tokens,
36769
37759
  persistTokens,
36770
- onAuthInvalid
37760
+ onAuthInvalid,
37761
+ e2ee
36771
37762
  } = params;
36772
37763
  let authRefreshInFlight = false;
36773
37764
  function handleOpen() {
@@ -36780,15 +37771,15 @@ function createMainBridgeWebSocketLifecycle(params) {
36780
37771
  }
36781
37772
  const socket = getWs();
36782
37773
  if (socket) {
36783
- sendWsMessage(socket, { type: "identify", role: "cli" });
37774
+ sendWsMessage(socket, { type: "identify", role: "cli", ...e2ee ? { e: e2ee.handshake } : {} });
36784
37775
  reportGitRepos(getWs, logFn);
36785
37776
  }
36786
37777
  if (justAuthenticated && socket) {
36787
37778
  logFn(
36788
37779
  "Save these for future runs (access token may rotate; refresh token is stored in ~/.buildautomaton/config.json when you use browser auth):"
36789
37780
  );
36790
- logFn(` export BUILDAMATON_AUTH_TOKEN="${tokens.accessToken}"`);
36791
- logFn(` export BUILDAMATON_WORKSPACE_ID="${workspaceId}"`);
37781
+ logFn(` export BUILDAUTOMATON_AUTH_TOKEN="${tokens.accessToken}"`);
37782
+ logFn(` export BUILDAUTOMATON_WORKSPACE_ID="${workspaceId}"`);
36792
37783
  }
36793
37784
  }
36794
37785
  function handleClose(code, reason) {
@@ -36903,7 +37894,8 @@ async function createBridgeConnection(options) {
36903
37894
  function getWs() {
36904
37895
  return state.currentWs;
36905
37896
  }
36906
- const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeCwd: getBridgeWorkspaceDirectory });
37897
+ const e2ee = options.e2eCertificate ? createCliE2eeRuntime(options.e2eCertificate) : void 0;
37898
+ const devServerManager = new DevServerManager({ getWs, log: logFn, getBridgeCwd: getBridgeWorkspaceDirectory, e2ee });
36907
37899
  const onBridgeIdentified = createOnBridgeIdentified({
36908
37900
  sessionWorktreeManager,
36909
37901
  devServerManager,
@@ -36922,7 +37914,10 @@ async function createBridgeConnection(options) {
36922
37914
  onBridgeIdentified,
36923
37915
  sendLocalSkillsReport,
36924
37916
  reportAutoDetectedAgents,
36925
- devServerManager
37917
+ devServerManager,
37918
+ e2ee,
37919
+ cloudApiBaseUrl: apiUrl,
37920
+ getCloudAccessToken: () => tokens.accessToken
36926
37921
  };
36927
37922
  const { connect } = createMainBridgeWebSocketLifecycle({
36928
37923
  state,
@@ -36934,7 +37929,8 @@ async function createBridgeConnection(options) {
36934
37929
  messageDeps,
36935
37930
  tokens,
36936
37931
  persistTokens,
36937
- onAuthInvalid
37932
+ onAuthInvalid,
37933
+ e2ee
36938
37934
  });
36939
37935
  connect();
36940
37936
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeWorkspaceDirectory());
@@ -36946,14 +37942,149 @@ async function createBridgeConnection(options) {
36946
37942
  };
36947
37943
  }
36948
37944
 
37945
+ // src/e2e-certificates/key-command.ts
37946
+ import * as readline3 from "node:readline";
37947
+ function installE2eCertificateKeyCommand({
37948
+ log: log2,
37949
+ onOpenCertificate,
37950
+ onInterrupt
37951
+ }) {
37952
+ if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== "function") {
37953
+ log2("[E2EE] Press c to import the E2EE key in a browser when running in an interactive terminal.");
37954
+ return () => {
37955
+ };
37956
+ }
37957
+ readline3.emitKeypressEvents(process.stdin);
37958
+ process.stdin.setRawMode(true);
37959
+ process.stdin.resume();
37960
+ const onKeypress = (str, key) => {
37961
+ if (key?.ctrl && key.name === "c") {
37962
+ onInterrupt();
37963
+ return;
37964
+ }
37965
+ if (!key?.ctrl && !key?.meta && (key?.name === "c" || str === "c")) {
37966
+ onOpenCertificate();
37967
+ }
37968
+ };
37969
+ process.stdin.on("keypress", onKeypress);
37970
+ log2("[E2EE] Press c to import the active E2EE key into the browser.");
37971
+ return () => {
37972
+ process.stdin.off("keypress", onKeypress);
37973
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === "function") {
37974
+ process.stdin.setRawMode(false);
37975
+ }
37976
+ };
37977
+ }
37978
+
37979
+ // src/e2e-certificates/open-import-url.ts
37980
+ async function openE2eCertificateImportUrl({
37981
+ apiUrl,
37982
+ workspaceId,
37983
+ certificate,
37984
+ log: log2
37985
+ }) {
37986
+ const appUrl = appUrlForApiUrl(apiUrl);
37987
+ const payload = encodeURIComponent(certificate.pemBundle);
37988
+ const url2 = `${appUrl.replace(/\/$/, "")}/w/${encodeURIComponent(workspaceId)}/settings/e2e-encryption?certificate=${payload}`;
37989
+ log2(`[E2EE] Opening browser to import key "${certificate.name}" (${certificate.id})...`);
37990
+ try {
37991
+ await open_default(url2, { wait: false });
37992
+ } catch {
37993
+ log2("[E2EE] Could not open browser. Open this URL manually:");
37994
+ log2(url2);
37995
+ }
37996
+ }
37997
+
37998
+ // src/run-bridge-connected.ts
37999
+ async function runConnectedBridge(options, restartWithoutAuth) {
38000
+ const {
38001
+ apiUrl,
38002
+ workspaceId,
38003
+ authToken,
38004
+ refreshToken,
38005
+ justAuthenticated,
38006
+ worktreesRootAbs,
38007
+ e2eCertificate
38008
+ } = options;
38009
+ const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
38010
+ let cleanupKeyCommand;
38011
+ const handle = await createBridgeConnection({
38012
+ apiUrl,
38013
+ workspaceId,
38014
+ authToken,
38015
+ refreshToken,
38016
+ firehoseServerUrl,
38017
+ justAuthenticated,
38018
+ worktreesRootAbs,
38019
+ e2eCertificate,
38020
+ log,
38021
+ persistTokens: (t) => {
38022
+ writeConfigForApi(apiUrl, {
38023
+ workspaceId,
38024
+ token: t.token,
38025
+ refreshToken: t.refreshToken
38026
+ });
38027
+ },
38028
+ onAuthInvalid: () => {
38029
+ cleanupKeyCommand?.();
38030
+ log("[Bridge service] Access token invalid or revoked; re-authenticating\u2026");
38031
+ clearConfigForApi(apiUrl);
38032
+ void handle.close().then(() => {
38033
+ void restartWithoutAuth({ apiUrl, firehoseServerUrl, worktreesRootAbs, e2eCertificate });
38034
+ });
38035
+ }
38036
+ });
38037
+ const onSignal = (kind) => {
38038
+ cleanupKeyCommand?.();
38039
+ logImmediate(
38040
+ kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
38041
+ );
38042
+ setImmediate(() => {
38043
+ void handle.close().then(() => {
38044
+ process.exit(0);
38045
+ });
38046
+ });
38047
+ };
38048
+ const onSigInt = () => onSignal("interrupt");
38049
+ const onSigTerm = () => onSignal("stop");
38050
+ process.on("SIGINT", onSigInt);
38051
+ process.on("SIGTERM", onSigTerm);
38052
+ if (e2eCertificate) {
38053
+ let openingCertificate = false;
38054
+ cleanupKeyCommand = installE2eCertificateKeyCommand({
38055
+ log,
38056
+ onInterrupt: onSigInt,
38057
+ onOpenCertificate: () => {
38058
+ if (openingCertificate) return;
38059
+ openingCertificate = true;
38060
+ void openE2eCertificateImportUrl({
38061
+ apiUrl,
38062
+ workspaceId,
38063
+ certificate: e2eCertificate,
38064
+ log
38065
+ }).finally(() => {
38066
+ openingCertificate = false;
38067
+ });
38068
+ }
38069
+ });
38070
+ }
38071
+ }
38072
+
36949
38073
  // src/run-bridge.ts
36950
38074
  async function runBridge(options) {
36951
38075
  installBridgeProcessResilience();
36952
- const { apiUrl, workspaceId, authToken, refreshToken, bridgeName, justAuthenticated, worktreesRootAbs } = options;
38076
+ const {
38077
+ apiUrl,
38078
+ workspaceId,
38079
+ authToken,
38080
+ bridgeName,
38081
+ worktreesRootAbs,
38082
+ e2eCertificate
38083
+ } = options;
36953
38084
  const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
36954
38085
  const hasAuth = workspaceId && authToken;
36955
38086
  if (!hasAuth) {
36956
- const handle2 = runPendingAuth({
38087
+ const handle = runPendingAuth({
36957
38088
  apiUrl,
36958
38089
  initialWorkspaceId: workspaceId,
36959
38090
  preferredBridgeName: bridgeName,
@@ -36961,23 +38092,23 @@ async function runBridge(options) {
36961
38092
  onAuth: (_auth) => {
36962
38093
  }
36963
38094
  });
36964
- const onSignal2 = (kind) => {
38095
+ const onSignal = (kind) => {
36965
38096
  logImmediate(
36966
38097
  kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
36967
38098
  );
36968
38099
  setImmediate(() => {
36969
- handle2.close();
38100
+ handle.close();
36970
38101
  process.exit(0);
36971
38102
  });
36972
38103
  };
36973
- const onSigInt2 = () => onSignal2("interrupt");
36974
- const onSigTerm2 = () => onSignal2("stop");
36975
- process.on("SIGINT", onSigInt2);
36976
- process.on("SIGTERM", onSigTerm2);
36977
- const auth = await handle2.authPromise;
36978
- process.off("SIGINT", onSigInt2);
36979
- process.off("SIGTERM", onSigTerm2);
36980
- handle2.close();
38104
+ const onSigInt = () => onSignal("interrupt");
38105
+ const onSigTerm = () => onSignal("stop");
38106
+ process.on("SIGINT", onSigInt);
38107
+ process.on("SIGTERM", onSigTerm);
38108
+ const auth = await handle.authPromise;
38109
+ process.off("SIGINT", onSigInt);
38110
+ process.off("SIGTERM", onSigTerm);
38111
+ handle.close();
36981
38112
  if (!auth) return;
36982
38113
  writeConfigForApi(apiUrl, {
36983
38114
  workspaceId: auth.workspaceId,
@@ -36992,111 +38123,91 @@ async function runBridge(options) {
36992
38123
  firehoseServerUrl,
36993
38124
  bridgeName,
36994
38125
  justAuthenticated: true,
36995
- worktreesRootAbs
38126
+ worktreesRootAbs,
38127
+ e2eCertificate
36996
38128
  });
36997
38129
  return;
36998
38130
  }
36999
- const handle = await createBridgeConnection({
38131
+ await runConnectedBridge(options, runBridge);
38132
+ }
38133
+
38134
+ // src/cli/run-cli-action.ts
38135
+ async function runCliAction(program2, opts) {
38136
+ const positionalUrl = program2.args?.[0];
38137
+ const urlFromPositional = typeof positionalUrl === "string" && /^https?:\/\//i.test(positionalUrl) ? positionalUrl : void 0;
38138
+ const apiUrlFromCli = opts.apiUrl ?? urlFromPositional;
38139
+ const apiUrl = apiUrlFromCli ?? DEFAULT_API_URL;
38140
+ let workspaceId = opts.workspaceId ?? "";
38141
+ let authToken = opts.token;
38142
+ const firehoseServerUrl = opts.firehoseUrl ?? opts.proxyUrl ?? process.env.BUILDAUTOMATON_FIREHOSE_URL ?? process.env.BUILDAUTOMATON_PROXY_URL ?? DEFAULT_FIREHOSE_URL;
38143
+ if (opts.cwd && typeof opts.cwd === "string" && opts.cwd.trim()) {
38144
+ const resolvedCwd = path33.resolve(process.cwd(), opts.cwd.trim());
38145
+ try {
38146
+ const st = fs30.statSync(resolvedCwd);
38147
+ if (!st.isDirectory()) {
38148
+ console.error(`--cwd is not a directory: ${resolvedCwd}`);
38149
+ process.exit(1);
38150
+ }
38151
+ } catch {
38152
+ console.error(`--cwd path does not exist or is not accessible: ${resolvedCwd}`);
38153
+ process.exit(1);
38154
+ }
38155
+ process.chdir(resolvedCwd);
38156
+ }
38157
+ initBridgeWorkspaceDirectory();
38158
+ let worktreesRootAbs;
38159
+ if (opts.worktreesRoot && opts.worktreesRoot.trim()) {
38160
+ worktreesRootAbs = path33.resolve(opts.worktreesRoot.trim());
38161
+ }
38162
+ const e2eCertificates = opts.e2eeCertificatesDir?.trim() ? await loadOrCreateE2eCertificates(opts.e2eeCertificatesDir.trim()) : void 0;
38163
+ if (e2eCertificates) {
38164
+ const action = e2eCertificates.generated ? "Generated" : "Loaded";
38165
+ console.log(
38166
+ `[E2EE] ${action} ${e2eCertificates.certificates.length} key${e2eCertificates.certificates.length === 1 ? "" : "s"} from ${e2eCertificates.directory}`
38167
+ );
38168
+ console.log(
38169
+ `[E2EE] Active key: ${e2eCertificates.activeCertificate.name} (${e2eCertificates.activeCertificate.id})`
38170
+ );
38171
+ }
38172
+ let refreshToken;
38173
+ if ((!workspaceId || !authToken) && opts.config !== false) {
38174
+ const saved = readConfigForApi(apiUrl);
38175
+ if (saved?.workspaceId && saved?.token) {
38176
+ if (!workspaceId) workspaceId = saved.workspaceId;
38177
+ if (!authToken) authToken = saved.token;
38178
+ refreshToken = saved.refreshToken;
38179
+ }
38180
+ }
38181
+ await runBridge({
37000
38182
  apiUrl,
37001
- workspaceId,
38183
+ workspaceId: workspaceId || void 0,
37002
38184
  authToken,
37003
38185
  refreshToken,
37004
38186
  firehoseServerUrl,
37005
- justAuthenticated,
38187
+ bridgeName: opts.name?.trim() || void 0,
37006
38188
  worktreesRootAbs,
37007
- log,
37008
- persistTokens: (t) => {
37009
- writeConfigForApi(apiUrl, {
37010
- workspaceId,
37011
- token: t.token,
37012
- refreshToken: t.refreshToken
37013
- });
37014
- },
37015
- onAuthInvalid: () => {
37016
- log("[Bridge service] Access token invalid or revoked; re-authenticating\u2026");
37017
- clearConfigForApi(apiUrl);
37018
- void handle.close().then(() => {
37019
- void runBridge({ apiUrl, firehoseServerUrl, worktreesRootAbs });
37020
- });
37021
- }
38189
+ e2eCertificate: e2eCertificates?.activeCertificate
37022
38190
  });
37023
- const onSignal = (kind) => {
37024
- logImmediate(
37025
- kind === "interrupt" ? "Keyboard interrupt (Ctrl+C) \u2014 stopping\u2026" : "Stop requested \u2014 shutting down\u2026"
37026
- );
37027
- setImmediate(() => {
37028
- void handle.close().then(() => {
37029
- process.exit(0);
37030
- });
37031
- });
37032
- };
37033
- const onSigInt = () => onSignal("interrupt");
37034
- const onSigTerm = () => onSignal("stop");
37035
- process.on("SIGINT", onSigInt);
37036
- process.on("SIGTERM", onSigTerm);
37037
38191
  }
37038
38192
 
37039
38193
  // src/cli.ts
37040
- var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
37041
- var DEFAULT_FIREHOSE_URL = "https://buildautomaton-firehose.fly.dev";
37042
38194
  async function main() {
37043
38195
  const program2 = new Command();
37044
- program2.name("buildautomaton").description("CLI for BuildAutomaton: ACP client, WebSocket bridge to backend, and skills (e.g. preview)").version("0.1.0").option("-u, --api-url <url>", "Backend API URL", process.env.BUILDAUTOMATON_API_URL ?? DEFAULT_API_URL).option("-w, --workspace-id <id>", "Workspace ID (or set BUILDAMATON_WORKSPACE_ID)", process.env.BUILDAMATON_WORKSPACE_ID).option("-t, --token <token>", "Auth token (or set BUILDAMATON_AUTH_TOKEN)", process.env.BUILDAMATON_AUTH_TOKEN).option(
38196
+ program2.name("buildautomaton").description("CLI for BuildAutomaton: ACP client, WebSocket bridge to backend, and skills (e.g. preview)").version("0.1.0").option("-u, --api-url <url>", "Backend API URL", process.env.BUILDAUTOMATON_API_URL ?? DEFAULT_API_URL).option("-w, --workspace-id <id>", "Workspace ID (or set BUILDAUTOMATON_WORKSPACE_ID)", process.env.BUILDAUTOMATON_WORKSPACE_ID).option("-t, --token <token>", "Auth token (or set BUILDAUTOMATON_AUTH_TOKEN)", process.env.BUILDAUTOMATON_AUTH_TOKEN).option(
37045
38197
  "--firehose-url <url>",
37046
- "Firehose server URL (default: Fly app; or BUILDAMATON_FIREHOSE_URL / legacy BUILDAMATON_PROXY_URL)",
37047
- process.env.BUILDAMATON_FIREHOSE_URL ?? process.env.BUILDAMATON_PROXY_URL ?? DEFAULT_FIREHOSE_URL
38198
+ "Firehose server URL (default: Fly app; or BUILDAUTOMATON_FIREHOSE_URL / legacy BUILDAUTOMATON_PROXY_URL)",
38199
+ process.env.BUILDAUTOMATON_FIREHOSE_URL ?? process.env.BUILDAUTOMATON_PROXY_URL ?? DEFAULT_FIREHOSE_URL
37048
38200
  ).option("--proxy-url <url>", "Deprecated alias for --firehose-url", void 0).option(
37049
38201
  "--cwd <path>",
37050
38202
  "Working directory for the bridge (absolute or relative to the current directory); affects skills, git, file index, and agent cwd"
37051
38203
  ).option("-n, --name <name>", "Bridge name when creating via browser (alphanumeric and underscores only)").option(
37052
38204
  "--worktrees-root <path>",
37053
38205
  "Root directory for per-session git worktrees (default: ~/.buildautomaton/worktrees). Whether worktrees are used is controlled in the cloud for each bridge token."
37054
- ).option("--no-config", "Ignore saved config at ~/.buildautomaton/config.json").action(async (opts) => {
37055
- const positionalUrl = program2.args?.[0];
37056
- const urlFromPositional = typeof positionalUrl === "string" && /^https?:\/\//i.test(positionalUrl) ? positionalUrl : void 0;
37057
- const apiUrlFromCli = opts.apiUrl ?? urlFromPositional;
37058
- let apiUrl = apiUrlFromCli ?? DEFAULT_API_URL;
37059
- let workspaceId = opts.workspaceId ?? "";
37060
- let authToken = opts.token;
37061
- const firehoseServerUrl = opts.firehoseUrl ?? opts.proxyUrl ?? process.env.BUILDAMATON_FIREHOSE_URL ?? process.env.BUILDAMATON_PROXY_URL ?? DEFAULT_FIREHOSE_URL;
37062
- if (opts.cwd && typeof opts.cwd === "string" && opts.cwd.trim()) {
37063
- const resolvedCwd = path32.resolve(process.cwd(), opts.cwd.trim());
37064
- try {
37065
- const st = fs29.statSync(resolvedCwd);
37066
- if (!st.isDirectory()) {
37067
- console.error(`--cwd is not a directory: ${resolvedCwd}`);
37068
- process.exit(1);
37069
- }
37070
- } catch {
37071
- console.error(`--cwd path does not exist or is not accessible: ${resolvedCwd}`);
37072
- process.exit(1);
37073
- }
37074
- process.chdir(resolvedCwd);
37075
- }
37076
- initBridgeWorkspaceDirectory();
37077
- let worktreesRootAbs;
37078
- if (opts.worktreesRoot && opts.worktreesRoot.trim()) {
37079
- worktreesRootAbs = path32.resolve(opts.worktreesRoot.trim());
37080
- }
37081
- let refreshToken;
37082
- if ((!workspaceId || !authToken) && opts.config !== false) {
37083
- const saved = readConfigForApi(apiUrl);
37084
- if (saved?.workspaceId && saved?.token) {
37085
- if (!workspaceId) workspaceId = saved.workspaceId;
37086
- if (!authToken) authToken = saved.token;
37087
- refreshToken = saved.refreshToken;
37088
- }
37089
- }
37090
- await runBridge({
37091
- apiUrl,
37092
- workspaceId: workspaceId || void 0,
37093
- authToken,
37094
- refreshToken,
37095
- firehoseServerUrl,
37096
- bridgeName: opts.name?.trim() || void 0,
37097
- worktreesRootAbs
37098
- });
37099
- });
38206
+ ).option(
38207
+ "--e2ee-certificates-dir <path>",
38208
+ "Directory to load or generate E2EE keys for sessions, files, and logs",
38209
+ process.env.BUILDAUTOMATON_E2EE_CERTIFICATES_DIR
38210
+ ).option("--no-config", "Ignore saved config at ~/.buildautomaton/config.json").action(async (opts) => runCliAction(program2, opts));
37100
38211
  await program2.parseAsync(process.argv);
37101
38212
  }
37102
38213
  main().catch((err) => {