@buildautomaton/cli 0.1.64 → 0.1.66

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
@@ -5277,12 +5277,12 @@ var require_websocket = __commonJS({
5277
5277
  "use strict";
5278
5278
  var EventEmitter2 = __require("events");
5279
5279
  var https2 = __require("https");
5280
- var http2 = __require("http");
5280
+ var http3 = __require("http");
5281
5281
  var net = __require("net");
5282
5282
  var tls = __require("tls");
5283
- var { randomBytes: randomBytes3, createHash: createHash3 } = __require("crypto");
5283
+ var { randomBytes: randomBytes4, createHash: createHash3 } = __require("crypto");
5284
5284
  var { Duplex, Readable: Readable2 } = __require("stream");
5285
- var { URL: URL2 } = __require("url");
5285
+ var { URL: URL3 } = __require("url");
5286
5286
  var PerMessageDeflate = require_permessage_deflate();
5287
5287
  var Receiver2 = require_receiver();
5288
5288
  var Sender2 = require_sender();
@@ -5775,11 +5775,11 @@ var require_websocket = __commonJS({
5775
5775
  );
5776
5776
  }
5777
5777
  let parsedUrl;
5778
- if (address instanceof URL2) {
5778
+ if (address instanceof URL3) {
5779
5779
  parsedUrl = address;
5780
5780
  } else {
5781
5781
  try {
5782
- parsedUrl = new URL2(address);
5782
+ parsedUrl = new URL3(address);
5783
5783
  } catch (e) {
5784
5784
  throw new SyntaxError(`Invalid URL: ${address}`);
5785
5785
  }
@@ -5810,8 +5810,8 @@ var require_websocket = __commonJS({
5810
5810
  }
5811
5811
  }
5812
5812
  const defaultPort = isSecure ? 443 : 80;
5813
- const key = randomBytes3(16).toString("base64");
5814
- const request = isSecure ? https2.request : http2.request;
5813
+ const key = randomBytes4(16).toString("base64");
5814
+ const request = isSecure ? https2.request : http3.request;
5815
5815
  const protocolSet = /* @__PURE__ */ new Set();
5816
5816
  let perMessageDeflate;
5817
5817
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -5916,7 +5916,7 @@ var require_websocket = __commonJS({
5916
5916
  req.abort();
5917
5917
  let addr;
5918
5918
  try {
5919
- addr = new URL2(location, address);
5919
+ addr = new URL3(location, address);
5920
5920
  } catch (e) {
5921
5921
  const err = new SyntaxError(`Invalid URL: ${location}`);
5922
5922
  emitErrorAndClose(websocket, err);
@@ -6305,7 +6305,7 @@ var require_websocket_server = __commonJS({
6305
6305
  "../../node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js"(exports, module) {
6306
6306
  "use strict";
6307
6307
  var EventEmitter2 = __require("events");
6308
- var http2 = __require("http");
6308
+ var http3 = __require("http");
6309
6309
  var { Duplex } = __require("stream");
6310
6310
  var { createHash: createHash3 } = __require("crypto");
6311
6311
  var extension = require_extension();
@@ -6380,8 +6380,8 @@ var require_websocket_server = __commonJS({
6380
6380
  );
6381
6381
  }
6382
6382
  if (options.port != null) {
6383
- this._server = http2.createServer((req, res) => {
6384
- const body = http2.STATUS_CODES[426];
6383
+ this._server = http3.createServer((req, res) => {
6384
+ const body = http3.STATUS_CODES[426];
6385
6385
  res.writeHead(426, {
6386
6386
  "Content-Length": body.length,
6387
6387
  "Content-Type": "text/plain"
@@ -6668,7 +6668,7 @@ var require_websocket_server = __commonJS({
6668
6668
  this.destroy();
6669
6669
  }
6670
6670
  function abortHandshake(socket, code, message, headers) {
6671
- message = message || http2.STATUS_CODES[code];
6671
+ message = message || http3.STATUS_CODES[code];
6672
6672
  headers = {
6673
6673
  Connection: "close",
6674
6674
  "Content-Type": "text/html",
@@ -6677,7 +6677,7 @@ var require_websocket_server = __commonJS({
6677
6677
  };
6678
6678
  socket.once("finish", socket.destroy);
6679
6679
  socket.end(
6680
- `HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
6680
+ `HTTP/1.1 ${code} ${http3.STATUS_CODES[code]}\r
6681
6681
  ` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
6682
6682
  );
6683
6683
  }
@@ -25668,11 +25668,11 @@ var require_dist2 = __commonJS({
25668
25668
  });
25669
25669
 
25670
25670
  // src/files/ensure-under-cwd.ts
25671
- import path74 from "node:path";
25671
+ import path73 from "node:path";
25672
25672
  function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
25673
- const normalized = path74.normalize(relativePath).replace(/^(\.\/)+/, "");
25674
- const resolved = path74.resolve(cwd, normalized);
25675
- if (!resolved.startsWith(cwd + path74.sep) && resolved !== cwd) {
25673
+ const normalized = path73.normalize(relativePath).replace(/^(\.\/)+/, "");
25674
+ const resolved = path73.resolve(cwd, normalized);
25675
+ if (!resolved.startsWith(cwd + path73.sep) && resolved !== cwd) {
25676
25676
  return null;
25677
25677
  }
25678
25678
  return resolved;
@@ -25694,15 +25694,15 @@ var init_types2 = __esm({
25694
25694
  });
25695
25695
 
25696
25696
  // src/files/list-dir/map-dir-entry.ts
25697
- import path75 from "node:path";
25698
- import fs48 from "node:fs";
25697
+ import path74 from "node:path";
25698
+ import fs46 from "node:fs";
25699
25699
  async function mapDirEntry(d, relativePath, resolved) {
25700
- const entryPath = path75.join(relativePath || ".", d.name).replace(/\\/g, "/");
25701
- const fullPath = path75.join(resolved, d.name);
25700
+ const entryPath = path74.join(relativePath || ".", d.name).replace(/\\/g, "/");
25701
+ const fullPath = path74.join(resolved, d.name);
25702
25702
  let isDir = d.isDirectory();
25703
25703
  if (d.isSymbolicLink()) {
25704
25704
  try {
25705
- const targetStat = await fs48.promises.stat(fullPath);
25705
+ const targetStat = await fs46.promises.stat(fullPath);
25706
25706
  isDir = targetStat.isDirectory();
25707
25707
  } catch {
25708
25708
  isDir = false;
@@ -25739,7 +25739,7 @@ var list_dir_exports = {};
25739
25739
  __export(list_dir_exports, {
25740
25740
  listDirAsync: () => listDirAsync
25741
25741
  });
25742
- import fs49 from "node:fs";
25742
+ import fs47 from "node:fs";
25743
25743
  async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
25744
25744
  await yieldToEventLoop();
25745
25745
  const resolved = ensureUnderCwd(relativePath || ".", sessionParentPath);
@@ -25747,7 +25747,7 @@ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
25747
25747
  return { error: "Path is outside working directory" };
25748
25748
  }
25749
25749
  try {
25750
- const names = await fs49.promises.readdir(resolved, { withFileTypes: true });
25750
+ const names = await fs47.promises.readdir(resolved, { withFileTypes: true });
25751
25751
  const entries = [];
25752
25752
  for (let i = 0; i < names.length; i++) {
25753
25753
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
@@ -25791,7 +25791,7 @@ var {
25791
25791
  } = import_index.default;
25792
25792
 
25793
25793
  // src/cli-version.ts
25794
- var CLI_VERSION = "0.1.64".length > 0 ? "0.1.64" : "0.0.0-dev";
25794
+ var CLI_VERSION = "0.1.66".length > 0 ? "0.1.66" : "0.0.0-dev";
25795
25795
 
25796
25796
  // src/cli/defaults.ts
25797
25797
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
@@ -27309,7 +27309,7 @@ function clearMainBridgeReconnectQuietOnOpen(state, log2) {
27309
27309
  function scheduleMainBridgeReconnect(state, connect, log2, closeMeta) {
27310
27310
  if (state.closedByUser || state.currentWs != null) return;
27311
27311
  const meta = closeMeta ?? state.lastReconnectCloseMeta ?? void 0;
27312
- const delay3 = applyTieredReconnectPlanAndLog(
27312
+ const delay4 = applyTieredReconnectPlanAndLog(
27313
27313
  state.mainOutage,
27314
27314
  log2,
27315
27315
  BRIDGE_SERVICE_LABEL,
@@ -27318,7 +27318,7 @@ function scheduleMainBridgeReconnect(state, connect, log2, closeMeta) {
27318
27318
  meta?.reason
27319
27319
  );
27320
27320
  armReconnectDelayTimer({
27321
- delayMs: delay3,
27321
+ delayMs: delay4,
27322
27322
  bumpAttempt: () => {
27323
27323
  state.reconnectAttempt += 1;
27324
27324
  },
@@ -27520,10 +27520,10 @@ function runPendingAuth(options) {
27520
27520
  }
27521
27521
  if (resolved) return;
27522
27522
  beginDeferredPendingCloseLog(code, reason);
27523
- const delay3 = pendingAuthReconnectDelayMs(reconnectAttempt);
27523
+ const delay4 = pendingAuthReconnectDelayMs(reconnectAttempt);
27524
27524
  reconnectAttempt += 1;
27525
27525
  if (signInQuiet.verboseLogs) {
27526
- const delayLabel = formatReconnectDelayForLog(delay3);
27526
+ const delayLabel = formatReconnectDelayForLog(delay4);
27527
27527
  logFn(
27528
27528
  `[Bridge service] Next sign-in connection attempt in ${delayLabel} (attempt ${reconnectAttempt}).`
27529
27529
  );
@@ -27531,7 +27531,7 @@ function runPendingAuth(options) {
27531
27531
  reconnectTimeout = setTimeout(() => {
27532
27532
  reconnectTimeout = null;
27533
27533
  connect();
27534
- }, delay3);
27534
+ }, delay4);
27535
27535
  },
27536
27536
  onError: (err) => logCliWebSocketError(logFn, "[Bridge service]", err, "while waiting for sign-in"),
27537
27537
  onMessage: (data) => {
@@ -38692,7 +38692,9 @@ function createBridgePreviewStack(options) {
38692
38692
  };
38693
38693
  const identifyReportedPaths = {
38694
38694
  bridgeRootPath: path39.resolve(getBridgeRoot()),
38695
- worktreesRootPath: path39.resolve(options.worktreesRootPath)
38695
+ worktreesRootPath: path39.resolve(options.worktreesRootPath),
38696
+ localShortcutPort: 0,
38697
+ localShortcutToken: ""
38696
38698
  };
38697
38699
  return { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths };
38698
38700
  }
@@ -39287,7 +39289,7 @@ function createFirehoseReconnectScheduler(ctx, reconnect) {
39287
39289
  function scheduleFirehoseRetryAfterDrop(closeMeta) {
39288
39290
  if (state.closedByUser) return;
39289
39291
  const meta = closeMeta ?? state.lastFirehoseReconnectCloseMeta ?? void 0;
39290
- const delay3 = applyTieredReconnectPlanAndLog(
39292
+ const delay4 = applyTieredReconnectPlanAndLog(
39291
39293
  state.firehoseOutage,
39292
39294
  logFn,
39293
39295
  PREVIEW_TUNNEL_SERVICE_LABEL,
@@ -39296,7 +39298,7 @@ function createFirehoseReconnectScheduler(ctx, reconnect) {
39296
39298
  meta?.reason
39297
39299
  );
39298
39300
  armReconnectDelayTimer({
39299
- delayMs: delay3,
39301
+ delayMs: delay4,
39300
39302
  bumpAttempt: () => {
39301
39303
  state.firehoseReconnectAttempt += 1;
39302
39304
  },
@@ -39400,6 +39402,208 @@ function createOnBridgeIdentified(opts) {
39400
39402
  };
39401
39403
  }
39402
39404
 
39405
+ // src/connection/create-bridge-identified-with-heartbeat.ts
39406
+ function createBridgeIdentifiedWithHeartbeat(params) {
39407
+ const { bridgeHeartbeat, log: log2, ...identifiedOpts } = params;
39408
+ const onBridgeIdentified = createOnBridgeIdentified({ ...identifiedOpts, logFn: log2 });
39409
+ return (msg) => {
39410
+ onBridgeIdentified(msg);
39411
+ bridgeHeartbeat.start();
39412
+ };
39413
+ }
39414
+
39415
+ // src/connection/heartbeat/build-heartbeat-message.ts
39416
+ function buildBridgeHeartbeatMessage(seq, meanRttMs2) {
39417
+ return meanRttMs2 !== void 0 && Number.isFinite(meanRttMs2) ? { t: "h", s: seq, m: Math.round(meanRttMs2) } : { t: "h", s: seq };
39418
+ }
39419
+
39420
+ // src/connection/heartbeat/heartbeat-seq.ts
39421
+ function createHeartbeatSeqCursor() {
39422
+ let seqCursor = -1;
39423
+ return {
39424
+ next() {
39425
+ seqCursor = seqCursor >= BRIDGE_HEARTBEAT_SEQ_MAX ? 0 : seqCursor + 1;
39426
+ return seqCursor;
39427
+ },
39428
+ reset() {
39429
+ seqCursor = -1;
39430
+ }
39431
+ };
39432
+ }
39433
+
39434
+ // src/connection/heartbeat/handle-pending-heartbeat-miss.ts
39435
+ function handlePendingHeartbeatMiss(params) {
39436
+ const { awaitingSeq, missed, log: log2, ws, onReconnect } = params;
39437
+ if (awaitingSeq === null) return { missed, stopTick: false };
39438
+ const nextMissed = missed + 1;
39439
+ if (nextMissed < BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT) {
39440
+ return { missed: nextMissed, stopTick: false };
39441
+ }
39442
+ try {
39443
+ log2("[Bridge service] Heartbeat missed repeatedly; reconnecting\u2026");
39444
+ } catch {
39445
+ }
39446
+ onReconnect();
39447
+ safeCloseWebSocket(ws);
39448
+ return { missed: 0, stopTick: true };
39449
+ }
39450
+
39451
+ // src/connection/heartbeat/on-every-n-ticks.ts
39452
+ function createEveryNTicksRunner(onEveryNTicks) {
39453
+ let tickCount = 0;
39454
+ return {
39455
+ reset() {
39456
+ tickCount = 0;
39457
+ },
39458
+ onTick() {
39459
+ if (!onEveryNTicks || onEveryNTicks.n <= 0) return;
39460
+ tickCount++;
39461
+ if (tickCount % onEveryNTicks.n !== 0) return;
39462
+ try {
39463
+ onEveryNTicks.fn();
39464
+ } catch {
39465
+ }
39466
+ }
39467
+ };
39468
+ }
39469
+
39470
+ // src/connection/heartbeat/process-heartbeat-ack.ts
39471
+ function processHeartbeatAck(params) {
39472
+ const { seq, awaitingSeq, sentAtMs, recordRtt } = params;
39473
+ if (awaitingSeq === null) return { awaitingSeq, missed: 0 };
39474
+ if (!Number.isFinite(seq)) return { awaitingSeq, missed: 0 };
39475
+ const ack = Math.trunc(seq);
39476
+ const pending = awaitingSeq;
39477
+ if (ack < pending) return { awaitingSeq, missed: 0 };
39478
+ if (ack === pending) {
39479
+ recordRtt(Date.now() - sentAtMs);
39480
+ }
39481
+ return { awaitingSeq: null, missed: 0 };
39482
+ }
39483
+
39484
+ // src/connection/heartbeat/rtt-samples.ts
39485
+ function meanRttMs(samples) {
39486
+ if (samples.length === 0) return void 0;
39487
+ let sum = 0;
39488
+ for (const x of samples) sum += x;
39489
+ return sum / samples.length;
39490
+ }
39491
+ function createRttSampleBuffer(maxSamples = BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX) {
39492
+ const samples = [];
39493
+ return {
39494
+ push(rttMs) {
39495
+ if (!Number.isFinite(rttMs) || rttMs < 0 || rttMs >= 6e5) return;
39496
+ samples.push(rttMs);
39497
+ if (samples.length > maxSamples) {
39498
+ samples.splice(0, samples.length - maxSamples);
39499
+ }
39500
+ },
39501
+ clear() {
39502
+ samples.length = 0;
39503
+ },
39504
+ mean() {
39505
+ return meanRttMs(samples);
39506
+ }
39507
+ };
39508
+ }
39509
+
39510
+ // src/connection/heartbeat/controller.ts
39511
+ function createBridgeHeartbeatController(params) {
39512
+ const { getWs, log: log2, onEveryNTicks } = params;
39513
+ let interval = null;
39514
+ let awaitingSeq = null;
39515
+ let sentAtMs = 0;
39516
+ let missed = 0;
39517
+ const seqCursor = createHeartbeatSeqCursor();
39518
+ const rttSamples = createRttSampleBuffer();
39519
+ const everyNTicks = createEveryNTicksRunner(onEveryNTicks);
39520
+ function clearTimer() {
39521
+ if (interval != null) {
39522
+ clearInterval(interval);
39523
+ interval = null;
39524
+ }
39525
+ }
39526
+ function resetPendingState() {
39527
+ awaitingSeq = null;
39528
+ missed = 0;
39529
+ rttSamples.clear();
39530
+ }
39531
+ function tick() {
39532
+ const ws = getWs();
39533
+ if (!ws || ws.readyState !== wrapper_default.OPEN) return;
39534
+ const missResult = handlePendingHeartbeatMiss({
39535
+ awaitingSeq,
39536
+ missed,
39537
+ log: log2,
39538
+ ws,
39539
+ onReconnect: () => {
39540
+ clearTimer();
39541
+ resetPendingState();
39542
+ }
39543
+ });
39544
+ missed = missResult.missed;
39545
+ if (missResult.stopTick) return;
39546
+ const seq = seqCursor.next();
39547
+ sendWsMessage(ws, buildBridgeHeartbeatMessage(seq, rttSamples.mean()));
39548
+ awaitingSeq = seq;
39549
+ sentAtMs = Date.now();
39550
+ everyNTicks.onTick();
39551
+ }
39552
+ return {
39553
+ start() {
39554
+ clearTimer();
39555
+ everyNTicks.reset();
39556
+ resetPendingState();
39557
+ seqCursor.reset();
39558
+ interval = setInterval(tick, BRIDGE_APP_HEARTBEAT_INTERVAL_MS);
39559
+ },
39560
+ stop() {
39561
+ clearTimer();
39562
+ resetPendingState();
39563
+ },
39564
+ onAck(seq) {
39565
+ const result = processHeartbeatAck({
39566
+ seq,
39567
+ awaitingSeq,
39568
+ sentAtMs,
39569
+ recordRtt: rttSamples.push
39570
+ });
39571
+ awaitingSeq = result.awaitingSeq;
39572
+ missed = result.missed;
39573
+ }
39574
+ };
39575
+ }
39576
+
39577
+ // src/connection/send-bridge-identify.ts
39578
+ function sendBridgeIdentify(ws, params) {
39579
+ if (!ws) return;
39580
+ const { identifyReportedPaths, e2ee } = params;
39581
+ sendWsMessage(ws, {
39582
+ type: "identify",
39583
+ role: "cli",
39584
+ cliVersion: CLI_VERSION,
39585
+ bridgeRootPath: identifyReportedPaths.bridgeRootPath,
39586
+ worktreesRootPath: identifyReportedPaths.worktreesRootPath,
39587
+ localShortcutPort: identifyReportedPaths.localShortcutPort,
39588
+ localShortcutToken: identifyReportedPaths.localShortcutToken,
39589
+ ...e2ee ? { e: e2ee.handshake } : {}
39590
+ });
39591
+ }
39592
+ var BRIDGE_IDENTIFY_REFRESH_EVERY_TICKS = 6;
39593
+
39594
+ // src/connection/create-bridge-identify-heartbeat.ts
39595
+ function createBridgeIdentifyHeartbeat(params) {
39596
+ const { getWs, log: log2, identifyReportedPaths, e2ee } = params;
39597
+ return createBridgeHeartbeatController({
39598
+ getWs,
39599
+ log: log2,
39600
+ onEveryNTicks: {
39601
+ n: BRIDGE_IDENTIFY_REFRESH_EVERY_TICKS,
39602
+ fn: () => sendBridgeIdentify(getWs(), { identifyReportedPaths, e2ee })
39603
+ }
39604
+ });
39605
+ }
39606
+
39403
39607
  // src/connection/create-bridge-message-deps.ts
39404
39608
  function createBridgeMessageDeps(params) {
39405
39609
  const {
@@ -39566,98 +39770,11 @@ function createReportAutoDetectedAgents(getWs, logFn) {
39566
39770
  };
39567
39771
  }
39568
39772
 
39569
- // src/connection/heartbeat/controller.ts
39570
- function meanRttMs(samples) {
39571
- if (samples.length === 0) return void 0;
39572
- let sum = 0;
39573
- for (const x of samples) sum += x;
39574
- return sum / samples.length;
39575
- }
39576
- function createBridgeHeartbeatController(params) {
39577
- const { getWs, log: log2 } = params;
39578
- let interval = null;
39579
- let seqCursor = -1;
39580
- let awaitingSeq = null;
39581
- let sentAtMs = 0;
39582
- let missed = 0;
39583
- const rttSamples = [];
39584
- function clearTimer() {
39585
- if (interval != null) {
39586
- clearInterval(interval);
39587
- interval = null;
39588
- }
39589
- }
39590
- function nextSeq() {
39591
- seqCursor = seqCursor >= BRIDGE_HEARTBEAT_SEQ_MAX ? 0 : seqCursor + 1;
39592
- return seqCursor;
39593
- }
39594
- function tick() {
39595
- const ws = getWs();
39596
- if (!ws || ws.readyState !== wrapper_default.OPEN) return;
39597
- if (awaitingSeq !== null) {
39598
- missed++;
39599
- if (missed >= BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT) {
39600
- try {
39601
- log2("[Bridge service] Heartbeat missed repeatedly; reconnecting\u2026");
39602
- } catch {
39603
- }
39604
- clearTimer();
39605
- awaitingSeq = null;
39606
- missed = 0;
39607
- rttSamples.length = 0;
39608
- safeCloseWebSocket(ws);
39609
- return;
39610
- }
39611
- }
39612
- const seq = nextSeq();
39613
- const mean = meanRttMs(rttSamples);
39614
- const payload = mean !== void 0 && Number.isFinite(mean) ? { t: "h", s: seq, m: Math.round(mean) } : { t: "h", s: seq };
39615
- sendWsMessage(ws, payload);
39616
- awaitingSeq = seq;
39617
- sentAtMs = Date.now();
39618
- }
39619
- return {
39620
- start() {
39621
- clearTimer();
39622
- awaitingSeq = null;
39623
- missed = 0;
39624
- seqCursor = -1;
39625
- rttSamples.length = 0;
39626
- interval = setInterval(tick, BRIDGE_APP_HEARTBEAT_INTERVAL_MS);
39627
- },
39628
- stop() {
39629
- clearTimer();
39630
- awaitingSeq = null;
39631
- missed = 0;
39632
- rttSamples.length = 0;
39633
- },
39634
- onAck(seq) {
39635
- if (awaitingSeq === null) return;
39636
- if (!Number.isFinite(seq)) return;
39637
- const ack = Math.trunc(seq);
39638
- const pending = awaitingSeq;
39639
- if (ack < pending) return;
39640
- if (ack === pending) {
39641
- const rtt = Date.now() - sentAtMs;
39642
- if (Number.isFinite(rtt) && rtt >= 0 && rtt < 6e5) {
39643
- rttSamples.push(rtt);
39644
- if (rttSamples.length > BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX) {
39645
- rttSamples.splice(0, rttSamples.length - BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX);
39646
- }
39647
- }
39648
- }
39649
- awaitingSeq = null;
39650
- missed = 0;
39651
- }
39652
- };
39653
- }
39654
-
39655
- // src/connection/create-bridge-runtime-message-setup.ts
39656
- function createBridgeRuntimeMessageSetup(options) {
39773
+ // src/connection/create-bridge-runtime-message-deps.ts
39774
+ function createBridgeRuntimeMessageDeps(params) {
39657
39775
  const {
39658
39776
  apiUrl,
39659
39777
  workspaceId,
39660
- firehoseServerUrl,
39661
39778
  state,
39662
39779
  getWs,
39663
39780
  log: log2,
@@ -39666,23 +39783,11 @@ function createBridgeRuntimeMessageSetup(options) {
39666
39783
  sessionWorktreeManager,
39667
39784
  previewWorktreeManager,
39668
39785
  previewEnvironmentManager,
39669
- e2ee
39670
- } = options;
39671
- const bridgeHeartbeat = createBridgeHeartbeatController({ getWs, log: log2 });
39672
- const baseOnBridgeIdentified = createOnBridgeIdentified({
39673
- previewEnvironmentManager,
39674
- firehoseServerUrl,
39675
- workspaceId,
39676
- state,
39677
- logFn: log2
39678
- });
39679
- const onBridgeIdentified = (msg) => {
39680
- baseOnBridgeIdentified(msg);
39681
- bridgeHeartbeat.start();
39682
- };
39683
- const sendLocalSkillsReport = createSendLocalSkillsReport(getWs, log2);
39684
- const reportAutoDetectedAgents = createReportAutoDetectedAgents(getWs, log2);
39685
- const messageDeps = createBridgeMessageDeps({
39786
+ e2ee,
39787
+ onBridgeIdentified,
39788
+ bridgeHeartbeat
39789
+ } = params;
39790
+ return createBridgeMessageDeps({
39686
39791
  getWs,
39687
39792
  log: log2,
39688
39793
  acpManager,
@@ -39690,8 +39795,8 @@ function createBridgeRuntimeMessageSetup(options) {
39690
39795
  previewWorktreeManager,
39691
39796
  onBridgeIdentified,
39692
39797
  bridgeHeartbeat,
39693
- sendLocalSkillsReport,
39694
- reportAutoDetectedAgents,
39798
+ sendLocalSkillsReport: createSendLocalSkillsReport(getWs, log2),
39799
+ reportAutoDetectedAgents: createReportAutoDetectedAgents(getWs, log2),
39695
39800
  warmupAgentCapabilitiesOnConnect: createWarmupAgentCapabilitiesOnConnect({
39696
39801
  workspaceId,
39697
39802
  log: log2,
@@ -39707,6 +39812,54 @@ function createBridgeRuntimeMessageSetup(options) {
39707
39812
  cloudApiBaseUrl: apiUrl,
39708
39813
  getCloudAccessToken: () => tokens.accessToken
39709
39814
  });
39815
+ }
39816
+
39817
+ // src/connection/create-bridge-runtime-message-setup.ts
39818
+ function createBridgeRuntimeMessageSetup(options) {
39819
+ const {
39820
+ apiUrl,
39821
+ workspaceId,
39822
+ firehoseServerUrl,
39823
+ state,
39824
+ getWs,
39825
+ log: log2,
39826
+ tokens,
39827
+ acpManager,
39828
+ sessionWorktreeManager,
39829
+ previewWorktreeManager,
39830
+ previewEnvironmentManager,
39831
+ e2ee,
39832
+ identifyReportedPaths
39833
+ } = options;
39834
+ const bridgeHeartbeat = createBridgeIdentifyHeartbeat({
39835
+ getWs,
39836
+ log: log2,
39837
+ identifyReportedPaths,
39838
+ e2ee
39839
+ });
39840
+ const onBridgeIdentified = createBridgeIdentifiedWithHeartbeat({
39841
+ previewEnvironmentManager,
39842
+ firehoseServerUrl,
39843
+ workspaceId,
39844
+ state,
39845
+ log: log2,
39846
+ bridgeHeartbeat
39847
+ });
39848
+ const messageDeps = createBridgeRuntimeMessageDeps({
39849
+ apiUrl,
39850
+ workspaceId,
39851
+ state,
39852
+ getWs,
39853
+ log: log2,
39854
+ tokens,
39855
+ acpManager,
39856
+ sessionWorktreeManager,
39857
+ previewWorktreeManager,
39858
+ previewEnvironmentManager,
39859
+ e2ee,
39860
+ onBridgeIdentified,
39861
+ bridgeHeartbeat
39862
+ });
39710
39863
  return { bridgeHeartbeat, messageDeps };
39711
39864
  }
39712
39865
 
@@ -47143,23 +47296,1353 @@ function createBridgeWorktreeManagers(options) {
47143
47296
  };
47144
47297
  }
47145
47298
 
47146
- // src/connection/create-bridge-connection-runtime.ts
47147
- async function createBridgeConnectionRuntime(options, params) {
47148
- const { apiUrl, workspaceId } = options;
47149
- const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
47150
- const { state, getWs, logFn } = params;
47151
- const tokens = {
47152
- accessToken: options.authToken,
47153
- refreshToken: options.refreshToken
47299
+ // src/local-shortcut/lib/generate-token.ts
47300
+ import { randomBytes as randomBytes3 } from "node:crypto";
47301
+ function generateShortcutToken() {
47302
+ return randomBytes3(32).toString("base64url");
47303
+ }
47304
+
47305
+ // src/local-shortcut/lib/start-server.ts
47306
+ import * as http2 from "node:http";
47307
+
47308
+ // src/local-shortcut/lib/server.ts
47309
+ import { URL as URL2 } from "node:url";
47310
+
47311
+ // src/local-shortcut/lib/cors.ts
47312
+ var LOCALHOST_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
47313
+ var FIXED_ALLOWED_ORIGINS = /* @__PURE__ */ new Set([
47314
+ "https://app.buildautomaton.com",
47315
+ "http://localhost:3000",
47316
+ "http://127.0.0.1:3000"
47317
+ ]);
47318
+ function resolveAllowedCorsOrigin(origin) {
47319
+ if (!origin) return null;
47320
+ if (FIXED_ALLOWED_ORIGINS.has(origin)) return origin;
47321
+ if (LOCALHOST_ORIGIN_RE.test(origin)) return origin;
47322
+ return null;
47323
+ }
47324
+ function applyCorsHeaders(req, res) {
47325
+ const origin = typeof req.headers.origin === "string" ? req.headers.origin : void 0;
47326
+ const allowed = resolveAllowedCorsOrigin(origin);
47327
+ if (allowed) {
47328
+ res.setHeader("Access-Control-Allow-Origin", allowed);
47329
+ res.setHeader("Vary", "Origin");
47330
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
47331
+ res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
47332
+ res.setHeader("Access-Control-Max-Age", "86400");
47333
+ }
47334
+ return allowed != null;
47335
+ }
47336
+
47337
+ // src/local-shortcut/lib/auth.ts
47338
+ function readBearerToken(req) {
47339
+ const raw = req.headers.authorization;
47340
+ if (typeof raw !== "string") return null;
47341
+ const match = /^Bearer\s+(.+)$/i.exec(raw.trim());
47342
+ return match?.[1]?.trim() ? match[1].trim() : null;
47343
+ }
47344
+ function isAuthorizedShortcutRequest(req, expectedToken) {
47345
+ const token = readBearerToken(req);
47346
+ return Boolean(token && expectedToken && token === expectedToken);
47347
+ }
47348
+
47349
+ // src/local-shortcut/lib/e2ee/file-browser-fields.ts
47350
+ var SHORTCUT_FILE_BROWSER_E2EE_FIELDS = [
47351
+ "entries",
47352
+ "content",
47353
+ "totalLines",
47354
+ "size",
47355
+ "lineOffset"
47356
+ ];
47357
+
47358
+ // src/local-shortcut/lib/e2ee/encrypt-file-browser-body.ts
47359
+ function encryptShortcutFileBrowserBody(body, e2ee) {
47360
+ if (!e2ee) return body;
47361
+ const encrypted = e2ee.encryptFields(body, SHORTCUT_FILE_BROWSER_E2EE_FIELDS);
47362
+ if (encrypted.ee != null && typeof encrypted.ee === "object") {
47363
+ return { ee: encrypted.ee };
47364
+ }
47365
+ return body;
47366
+ }
47367
+
47368
+ // src/local-shortcut/lib/response.ts
47369
+ function sendShortcutJson(res, status, body) {
47370
+ if (res.headersSent) return;
47371
+ res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
47372
+ res.end(JSON.stringify(body));
47373
+ }
47374
+ function sendShortcutFileBrowserJson(res, status, body, e2ee) {
47375
+ sendShortcutJson(res, status, encryptShortcutFileBrowserBody(body, e2ee));
47376
+ }
47377
+
47378
+ // src/local-shortcut/app/api/bridges/local-shortcut/health/route.ts
47379
+ async function GET(ctx) {
47380
+ sendShortcutJson(ctx.res, 200, { ok: true });
47381
+ }
47382
+
47383
+ // src/local-shortcut/app/lib/assert-bridge-workspace.ts
47384
+ function assertBridgeWorkspace(ctx) {
47385
+ const reqWorkspaceId = ctx.url.searchParams.get("workspaceId")?.trim() ?? "";
47386
+ if (reqWorkspaceId !== ctx.workspaceId) {
47387
+ sendShortcutJson(ctx.res, 403, { error: "workspaceId mismatch" });
47388
+ return false;
47389
+ }
47390
+ return true;
47391
+ }
47392
+
47393
+ // src/files/browser/execute-file-browser-list.ts
47394
+ init_list_dir();
47395
+
47396
+ // src/git/tree/resolve-repo-abs-path.ts
47397
+ init_cwd();
47398
+ import * as path75 from "node:path";
47399
+ function resolveRepoAbsPathFromBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
47400
+ const bridgeResolved = path75.resolve(bridgeRoot);
47401
+ const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
47402
+ const repoPath = rel === "" ? bridgeResolved : path75.join(bridgeResolved, rel);
47403
+ const resolved = path75.resolve(repoPath);
47404
+ if (!resolved.startsWith(bridgeResolved + path75.sep) && resolved !== bridgeResolved) {
47405
+ return null;
47406
+ }
47407
+ return resolved;
47408
+ }
47409
+
47410
+ // src/git/tree/resolve-branch-commit.ts
47411
+ async function resolveBranchCommit(repoAbsPath, branch) {
47412
+ const branchRef = branch.trim();
47413
+ if (!branchRef) return null;
47414
+ try {
47415
+ return (await cliSimpleGit(repoAbsPath).revparse([`${branchRef}^{commit}`])).trim();
47416
+ } catch {
47417
+ return null;
47418
+ }
47419
+ }
47420
+
47421
+ // src/git/tree/list-git-tree-dir.ts
47422
+ init_yield_to_event_loop();
47423
+ init_types2();
47424
+ init_sort_entries();
47425
+ function parseLsTreeLine(line) {
47426
+ const tab = line.indexOf(" ");
47427
+ if (tab < 0) return null;
47428
+ const meta = line.slice(0, tab).trim().split(/\s+/);
47429
+ if (meta.length < 3) return null;
47430
+ const mode = meta[0];
47431
+ const objectType2 = meta[1];
47432
+ let name = line.slice(tab + 1);
47433
+ if (name.startsWith('"') && name.endsWith('"')) {
47434
+ name = name.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
47435
+ }
47436
+ return { mode, objectType: objectType2, name };
47437
+ }
47438
+ function childPath(relativePath, name) {
47439
+ const base = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
47440
+ if (!base || base === ".") return name;
47441
+ return `${base}/${name}`.replace(/\\/g, "/");
47442
+ }
47443
+ function lsTreeArgs(commit, relativePath) {
47444
+ const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
47445
+ if (!rel || rel === ".") return ["ls-tree", commit];
47446
+ return ["ls-tree", `${commit}:${rel}`];
47447
+ }
47448
+ async function listGitTreeDirAsync(repoAbsPath, branch, relativePath) {
47449
+ await yieldToEventLoop();
47450
+ const commit = await resolveBranchCommit(repoAbsPath, branch);
47451
+ if (!commit) return { error: "Branch not found" };
47452
+ try {
47453
+ const raw = await cliSimpleGit(repoAbsPath).raw(lsTreeArgs(commit, relativePath));
47454
+ await yieldToEventLoop();
47455
+ const lines = String(raw ?? "").split(/\r?\n/).filter((line) => line.trim().length > 0);
47456
+ const entries = [];
47457
+ for (let i = 0; i < lines.length; i++) {
47458
+ if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
47459
+ await yieldToEventLoop();
47460
+ }
47461
+ const parsed = parseLsTreeLine(lines[i]);
47462
+ if (!parsed) continue;
47463
+ const { mode, objectType: objectType2, name } = parsed;
47464
+ if (!name || name === "." || name === "..") continue;
47465
+ const isSymlink = mode === "120000";
47466
+ const isDir = objectType2 === "tree";
47467
+ entries.push({
47468
+ name,
47469
+ path: childPath(relativePath, name),
47470
+ isDir,
47471
+ ...isSymlink ? { isSymlink: true } : {}
47472
+ });
47473
+ }
47474
+ if (entries.length >= LIST_DIR_YIELD_EVERY) {
47475
+ await yieldToEventLoop();
47476
+ }
47477
+ return { entries: sortListEntries(entries) };
47478
+ } catch (err) {
47479
+ const message = err instanceof Error ? err.message : String(err);
47480
+ if (/not a tree object|does not exist|bad revision/i.test(message)) {
47481
+ return { error: "Path not found in branch" };
47482
+ }
47483
+ return { error: message };
47484
+ }
47485
+ }
47486
+
47487
+ // src/git/tree/file/index.ts
47488
+ init_yield_to_event_loop();
47489
+
47490
+ // src/files/read-file/types.ts
47491
+ var LINE_CHUNK_SIZE = 64 * 1024;
47492
+ var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
47493
+
47494
+ // src/files/read-file/guess-mime-type.ts
47495
+ var MIME_BY_EXT = {
47496
+ png: "image/png",
47497
+ jpg: "image/jpeg",
47498
+ jpeg: "image/jpeg",
47499
+ gif: "image/gif",
47500
+ bmp: "image/bmp",
47501
+ ico: "image/x-icon",
47502
+ webp: "image/webp",
47503
+ avif: "image/avif",
47504
+ svg: "image/svg+xml",
47505
+ pdf: "application/pdf",
47506
+ json: "application/json",
47507
+ html: "text/html",
47508
+ htm: "text/html",
47509
+ css: "text/css",
47510
+ js: "text/javascript",
47511
+ mjs: "text/javascript",
47512
+ ts: "text/typescript",
47513
+ txt: "text/plain",
47514
+ md: "text/markdown",
47515
+ xml: "application/xml",
47516
+ zip: "application/zip",
47517
+ gz: "application/gzip",
47518
+ wasm: "application/wasm"
47519
+ };
47520
+ function guessMimeType(filePath) {
47521
+ const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
47522
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
47523
+ }
47524
+
47525
+ // src/git/tree/file/git-blob-ref.ts
47526
+ init_yield_to_event_loop();
47527
+ async function gitBlobRef(repoAbsPath, branch, relativePath) {
47528
+ const commit = await resolveBranchCommit(repoAbsPath, branch);
47529
+ await yieldToEventLoop();
47530
+ if (!commit) return null;
47531
+ const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
47532
+ if (!rel || rel === ".") return null;
47533
+ return `${commit}:${rel}`;
47534
+ }
47535
+
47536
+ // src/git/tree/file/is-binary-buffer.ts
47537
+ function isBinaryBuffer(buf) {
47538
+ const sample = buf.subarray(0, Math.min(buf.length, 8192));
47539
+ for (let i = 0; i < sample.length; i++) {
47540
+ if (sample[i] === 0) return true;
47541
+ }
47542
+ return false;
47543
+ }
47544
+
47545
+ // src/git/tree/file/read-git-blob-text-async.ts
47546
+ init_yield_to_event_loop();
47547
+ import { StringDecoder } from "node:string_decoder";
47548
+ async function countGitBlobLinesAsync(buf) {
47549
+ if (buf.length === 0) return 1;
47550
+ let lines = 1;
47551
+ let bytesSinceYield = 0;
47552
+ for (let i = 0; i < buf.length; i++) {
47553
+ const b = buf[i];
47554
+ if (b === 10) {
47555
+ lines++;
47556
+ } else if (b === 13) {
47557
+ if (i + 1 < buf.length && buf[i + 1] === 10) {
47558
+ lines++;
47559
+ i++;
47560
+ } else {
47561
+ lines++;
47562
+ }
47563
+ }
47564
+ bytesSinceYield++;
47565
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
47566
+ await yieldToEventLoop();
47567
+ bytesSinceYield = 0;
47568
+ }
47569
+ }
47570
+ return lines;
47571
+ }
47572
+ async function readGitBlobLineRangeAsync(buf, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
47573
+ const bufSize = 64 * 1024;
47574
+ const decoder = new StringDecoder("utf8");
47575
+ let currentLine = 0;
47576
+ const resultLines = [];
47577
+ let partial2 = "";
47578
+ let done = false;
47579
+ let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
47580
+ let line0CharsReturned = 0;
47581
+ let line0Accum = "";
47582
+ let bytesSinceYield = 0;
47583
+ let position = 0;
47584
+ const finish = async () => {
47585
+ const totalLines = await countGitBlobLinesAsync(buf);
47586
+ return { content: resultLines.join("\n"), totalLines };
47154
47587
  };
47155
- const { worktreesRootPath, sessionWorktreeManager, previewWorktreeManager } = createBridgeWorktreeManagers({ worktreesRootPath: options.worktreesRootPath, log: logFn });
47156
- const { e2ee, acpManager, bridgeAccessPromise } = await createBridgeAccessAndAcp({
47157
- apiUrl,
47158
- workspaceId,
47159
- tokens,
47160
- e2eCertificate: options.e2eCertificate,
47161
- getWs,
47162
- log: logFn
47588
+ while (!done && position < buf.length) {
47589
+ const chunkEnd = Math.min(position + bufSize, buf.length);
47590
+ const bytesRead = chunkEnd - position;
47591
+ position = chunkEnd;
47592
+ bytesSinceYield += bytesRead;
47593
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
47594
+ await yieldToEventLoop();
47595
+ bytesSinceYield = 0;
47596
+ }
47597
+ const text = partial2 + decoder.write(buf.subarray(position - bytesRead, position));
47598
+ partial2 = "";
47599
+ let lineStart = 0;
47600
+ for (let i = 0; i < text.length; i++) {
47601
+ if (text[i] === "\n") {
47602
+ const lineContent = (() => {
47603
+ let lineEnd = i;
47604
+ if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
47605
+ return text.slice(lineStart, lineEnd);
47606
+ })();
47607
+ if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
47608
+ line0Accum += lineContent;
47609
+ const totalLine0 = line0Accum.length;
47610
+ if (skipLine0Chars > 0) {
47611
+ if (totalLine0 <= skipLine0Chars) {
47612
+ skipLine0Chars -= totalLine0;
47613
+ line0Accum = "";
47614
+ currentLine++;
47615
+ lineStart = i + 1;
47616
+ if (currentLine > endLine) {
47617
+ done = true;
47618
+ break;
47619
+ }
47620
+ continue;
47621
+ }
47622
+ const from = skipLine0Chars;
47623
+ const take = Math.min(lineChunkSize, totalLine0 - from);
47624
+ resultLines.push(line0Accum.slice(from, from + take));
47625
+ line0CharsReturned += take;
47626
+ if (from + take < totalLine0) {
47627
+ const totalLines = await countGitBlobLinesAsync(buf);
47628
+ return {
47629
+ content: resultLines.join("\n"),
47630
+ totalLines,
47631
+ lineOffset: lineOffsetIn + line0CharsReturned
47632
+ };
47633
+ }
47634
+ line0Accum = "";
47635
+ skipLine0Chars = 0;
47636
+ line0CharsReturned = 0;
47637
+ } else if (totalLine0 > lineChunkSize) {
47638
+ resultLines.push(line0Accum.slice(0, lineChunkSize));
47639
+ const totalLines = await countGitBlobLinesAsync(buf);
47640
+ return {
47641
+ content: resultLines.join("\n"),
47642
+ totalLines,
47643
+ lineOffset: lineChunkSize
47644
+ };
47645
+ } else {
47646
+ resultLines.push(line0Accum);
47647
+ line0Accum = "";
47648
+ }
47649
+ } else if (currentLine >= startLine && currentLine <= endLine) {
47650
+ resultLines.push(lineContent);
47651
+ }
47652
+ currentLine++;
47653
+ lineStart = i + 1;
47654
+ if (currentLine > endLine) {
47655
+ done = true;
47656
+ break;
47657
+ }
47658
+ }
47659
+ }
47660
+ if (!done) {
47661
+ const lineContent = text.slice(lineStart);
47662
+ if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
47663
+ line0Accum += lineContent;
47664
+ const totalLine0 = line0Accum.length;
47665
+ if (skipLine0Chars > 0) {
47666
+ if (totalLine0 <= skipLine0Chars) {
47667
+ skipLine0Chars -= totalLine0;
47668
+ line0Accum = "";
47669
+ } else {
47670
+ const from = skipLine0Chars;
47671
+ const take = Math.min(lineChunkSize, totalLine0 - from);
47672
+ resultLines.push(line0Accum.slice(from, from + take));
47673
+ const totalLines = await countGitBlobLinesAsync(buf);
47674
+ return {
47675
+ content: resultLines.join("\n"),
47676
+ totalLines,
47677
+ lineOffset: (lineOffsetIn ?? 0) + take
47678
+ };
47679
+ }
47680
+ } else if (totalLine0 > lineChunkSize) {
47681
+ resultLines.push(line0Accum.slice(0, lineChunkSize));
47682
+ const totalLines = await countGitBlobLinesAsync(buf);
47683
+ return {
47684
+ content: resultLines.join("\n"),
47685
+ totalLines,
47686
+ lineOffset: lineChunkSize
47687
+ };
47688
+ }
47689
+ }
47690
+ partial2 = text.slice(lineStart);
47691
+ }
47692
+ }
47693
+ if (!done) {
47694
+ const tail = partial2 + decoder.end();
47695
+ if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0) && tail.length > 0) {
47696
+ line0Accum += tail.endsWith("\r") ? tail.slice(0, -1) : tail;
47697
+ const totalLine0 = line0Accum.length;
47698
+ if (skipLine0Chars > 0) {
47699
+ if (totalLine0 <= skipLine0Chars) {
47700
+ return finish();
47701
+ }
47702
+ const from = skipLine0Chars;
47703
+ const take = Math.min(lineChunkSize, totalLine0 - from);
47704
+ resultLines.push(line0Accum.slice(from, from + take));
47705
+ line0CharsReturned += take;
47706
+ if (from + take < totalLine0) {
47707
+ const totalLines = await countGitBlobLinesAsync(buf);
47708
+ return {
47709
+ content: resultLines.join("\n"),
47710
+ totalLines,
47711
+ lineOffset: (lineOffsetIn ?? 0) + line0CharsReturned
47712
+ };
47713
+ }
47714
+ } else if (totalLine0 > lineChunkSize) {
47715
+ resultLines.push(line0Accum.slice(0, lineChunkSize));
47716
+ const totalLines = await countGitBlobLinesAsync(buf);
47717
+ return {
47718
+ content: resultLines.join("\n"),
47719
+ totalLines,
47720
+ lineOffset: lineChunkSize
47721
+ };
47722
+ } else {
47723
+ resultLines.push(line0Accum);
47724
+ }
47725
+ } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
47726
+ resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
47727
+ }
47728
+ }
47729
+ return finish();
47730
+ }
47731
+ async function decodeGitBlobUtf8Async(buf) {
47732
+ await yieldToEventLoop();
47733
+ const text = buf.toString("utf8");
47734
+ await yieldToEventLoop();
47735
+ return text;
47736
+ }
47737
+ async function readGitBlobUtf8FullAsync(buf) {
47738
+ const text = await decodeGitBlobUtf8Async(buf);
47739
+ const totalLines = await countGitBlobLinesAsync(buf);
47740
+ return { content: text, totalLines };
47741
+ }
47742
+
47743
+ // src/git/tree/file/index.ts
47744
+ async function readGitTreeFileAsync(repoAbsPath, branch, relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, encoding = "utf8") {
47745
+ await yieldToEventLoop();
47746
+ const branchRef = branch.trim();
47747
+ if (!branchRef) return { error: "branch required" };
47748
+ const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
47749
+ if (!rel || rel === ".") return { error: "file path required" };
47750
+ const blobRef = await gitBlobRef(repoAbsPath, branchRef, rel);
47751
+ if (!blobRef) return { error: "Branch not found" };
47752
+ const hasRange = typeof startLine === "number" && typeof endLine === "number";
47753
+ try {
47754
+ if (encoding === "base64") {
47755
+ if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
47756
+ const buf2 = await cliSimpleGit(repoAbsPath).showBuffer([blobRef]);
47757
+ await yieldToEventLoop();
47758
+ return {
47759
+ content: buf2.toString("base64"),
47760
+ size: buf2.length,
47761
+ mimeType: guessMimeType(rel)
47762
+ };
47763
+ }
47764
+ const buf = await cliSimpleGit(repoAbsPath).showBuffer([blobRef]);
47765
+ await yieldToEventLoop();
47766
+ const size = buf.length;
47767
+ if (isBinaryBuffer(buf)) {
47768
+ return { error: "Binary file cannot be displayed as text" };
47769
+ }
47770
+ if (hasRange) {
47771
+ const ranged = await readGitBlobLineRangeAsync(buf, startLine, endLine, lineOffset, lineChunkSize);
47772
+ return {
47773
+ content: ranged.content,
47774
+ totalLines: ranged.totalLines,
47775
+ size,
47776
+ ...ranged.lineOffset != null ? { lineOffset: ranged.lineOffset } : {}
47777
+ };
47778
+ }
47779
+ const full = await readGitBlobUtf8FullAsync(buf);
47780
+ return { content: full.content, totalLines: full.totalLines, size, mimeType: guessMimeType(rel) };
47781
+ } catch (err) {
47782
+ const message = err instanceof Error ? err.message : String(err);
47783
+ if (/does not exist|bad revision|exists on disk, but not in/i.test(message)) {
47784
+ return { error: "File not found in branch" };
47785
+ }
47786
+ return { error: message };
47787
+ }
47788
+ }
47789
+
47790
+ // src/files/browser/execute-file-browser-list.ts
47791
+ async function executeFileBrowserList(params) {
47792
+ const { reqPath, sessionParentPath, gitScope } = params;
47793
+ return gitScope ? await listGitTreeDirAsync(gitScope.repoAbsPath, gitScope.branch, reqPath) : await listDirAsync(reqPath, sessionParentPath);
47794
+ }
47795
+
47796
+ // src/files/browser/resolve-git-branch-scope.ts
47797
+ function resolveGitBranchScope(msg) {
47798
+ const gitBranchScope = msg.source === "git_branch" && typeof msg.repoRelPath === "string" && typeof msg.branch === "string" && msg.branch.trim().length > 0;
47799
+ if (!gitBranchScope) return null;
47800
+ const repoAbsPath = resolveRepoAbsPathFromBridge(msg.repoRelPath);
47801
+ if (!repoAbsPath) return null;
47802
+ return { repoAbsPath, branch: msg.branch.trim() };
47803
+ }
47804
+
47805
+ // src/local-shortcut/app/lib/file-browser/parse-query.ts
47806
+ function parseOptionalInt(raw) {
47807
+ if (raw == null) return void 0;
47808
+ const parsed = parseInt(raw, 10);
47809
+ return Number.isInteger(parsed) ? parsed : void 0;
47810
+ }
47811
+ function parseGitScopeFromUrl(url2) {
47812
+ const rawSource = url2.searchParams.get("source")?.trim();
47813
+ if (rawSource === "git_branch") {
47814
+ return {
47815
+ source: "git_branch",
47816
+ repoRelPath: url2.searchParams.get("repoRelPath")?.trim() ?? ".",
47817
+ branch: url2.searchParams.get("branch")?.trim() ?? ""
47818
+ };
47819
+ }
47820
+ return { source: "filesystem" };
47821
+ }
47822
+ function parseReadParams(url2) {
47823
+ const rawEncoding = url2.searchParams.get("encoding");
47824
+ return {
47825
+ startLine: parseOptionalInt(url2.searchParams.get("startLine")),
47826
+ endLine: parseOptionalInt(url2.searchParams.get("endLine")),
47827
+ lineOffset: parseOptionalInt(url2.searchParams.get("lineOffset")),
47828
+ lineChunkSize: parseOptionalInt(url2.searchParams.get("lineChunkSize")),
47829
+ encoding: rawEncoding === "base64" ? "base64" : "utf8"
47830
+ };
47831
+ }
47832
+ function parseListPath(url2) {
47833
+ return (url2.searchParams.get("path") ?? ".").replace(/^\/+/, "") || ".";
47834
+ }
47835
+ function parseReadPath(url2) {
47836
+ const raw = url2.searchParams.get("path")?.trim();
47837
+ if (!raw) return null;
47838
+ return raw.replace(/^\/+/, "") || ".";
47839
+ }
47840
+
47841
+ // src/files/resolve-file-browser-session-parent.ts
47842
+ init_normalize_resolved_path();
47843
+ init_cwd();
47844
+ async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
47845
+ const sid = sessionId?.trim();
47846
+ if (sid) {
47847
+ await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sid);
47848
+ const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
47849
+ if (worktreeRoot) return normalizeResolvedPath(worktreeRoot);
47850
+ }
47851
+ return getBridgeRoot();
47852
+ }
47853
+
47854
+ // src/local-shortcut/app/lib/file-browser/resolve-session-parent.ts
47855
+ async function resolveShortcutSessionParent(ctx, sessionId) {
47856
+ if (!ctx.sessionWorktreeManager || !sessionId?.trim()) return void 0;
47857
+ return resolveFileBrowserSessionParent(ctx.sessionWorktreeManager, sessionId.trim());
47858
+ }
47859
+
47860
+ // src/local-shortcut/app/lib/file-browser/handle-list.ts
47861
+ async function handleShortcutFileBrowserList(ctx, sessionId) {
47862
+ const reqPath = parseListPath(ctx.url);
47863
+ const gitMsg = parseGitScopeFromUrl(ctx.url);
47864
+ const gitScope = resolveGitBranchScope({ id: "shortcut", path: reqPath, ...gitMsg });
47865
+ if (gitMsg.source === "git_branch" && typeof gitMsg.repoRelPath === "string" && typeof gitMsg.branch === "string" && gitMsg.branch.trim().length > 0 && !gitScope) {
47866
+ sendShortcutJson(ctx.res, 400, { error: "Invalid repository path" });
47867
+ return;
47868
+ }
47869
+ const sessionParentPath = await resolveShortcutSessionParent(ctx, sessionId);
47870
+ const result = await executeFileBrowserList({ reqPath, sessionParentPath, gitScope });
47871
+ if ("error" in result) {
47872
+ sendShortcutJson(ctx.res, 400, { error: result.error });
47873
+ return;
47874
+ }
47875
+ sendShortcutFileBrowserJson(ctx.res, 200, { entries: result.entries }, ctx.e2ee);
47876
+ }
47877
+
47878
+ // src/local-shortcut/app/api/bridges/files/list/route.ts
47879
+ async function GET2(ctx) {
47880
+ if (!assertBridgeWorkspace(ctx)) return;
47881
+ await handleShortcutFileBrowserList(ctx);
47882
+ }
47883
+
47884
+ // src/files/browser/file-browser-read-queue.ts
47885
+ var readChain = Promise.resolve();
47886
+ function withFileBrowserReadQueue(fn) {
47887
+ const next = readChain.then(() => fn());
47888
+ readChain = next.then(
47889
+ () => void 0,
47890
+ () => void 0
47891
+ );
47892
+ return next;
47893
+ }
47894
+
47895
+ // src/files/read-file/index.ts
47896
+ init_cwd();
47897
+ init_yield_to_event_loop();
47898
+ import fs53 from "node:fs";
47899
+
47900
+ // src/files/read-file/resolve-file-path.ts
47901
+ init_ensure_under_cwd();
47902
+ init_cwd();
47903
+ import fs49 from "node:fs";
47904
+
47905
+ // src/files/read-file/resolve-file-cache.ts
47906
+ import fs48 from "node:fs";
47907
+ import path76 from "node:path";
47908
+ var RESOLVE_CACHE_MAX = 4096;
47909
+ var resolveCache = /* @__PURE__ */ new Map();
47910
+ var warmedDirectories = /* @__PURE__ */ new Set();
47911
+ function resolveCacheKey(sessionParentPath, relativePath) {
47912
+ return `${sessionParentPath}\0${relativePath}`;
47913
+ }
47914
+ function warmedDirectoryKey(sessionParentPath, directoryRelativePath) {
47915
+ const dir = directoryRelativePath.replace(/^\/+/, "") || ".";
47916
+ return `${path76.resolve(sessionParentPath)}\0${dir}`;
47917
+ }
47918
+ function pathStaysUnderParent(filePath, sessionParentPath) {
47919
+ const parent = path76.resolve(sessionParentPath);
47920
+ const resolved = path76.resolve(filePath);
47921
+ return resolved === parent || resolved.startsWith(`${parent}${path76.sep}`);
47922
+ }
47923
+ async function statMatchesCache(entry) {
47924
+ try {
47925
+ const stat3 = await fs48.promises.stat(entry.path);
47926
+ return stat3.isFile() && stat3.mtimeMs === entry.mtimeMs && stat3.size === entry.size;
47927
+ } catch {
47928
+ return false;
47929
+ }
47930
+ }
47931
+ function getResolveCacheEntry(cacheKey) {
47932
+ return resolveCache.get(cacheKey);
47933
+ }
47934
+ function deleteResolveCacheEntry(cacheKey) {
47935
+ resolveCache.delete(cacheKey);
47936
+ }
47937
+ function storeResolveCacheEntry(cacheKey, entry) {
47938
+ if (resolveCache.size >= RESOLVE_CACHE_MAX) resolveCache.clear();
47939
+ resolveCache.set(cacheKey, entry);
47940
+ return entry;
47941
+ }
47942
+ function hasResolveCacheEntry(cacheKey) {
47943
+ return resolveCache.has(cacheKey);
47944
+ }
47945
+ function markDirectoryWarmed(dirKey) {
47946
+ if (warmedDirectories.has(dirKey)) return false;
47947
+ warmedDirectories.add(dirKey);
47948
+ return true;
47949
+ }
47950
+ function unmarkDirectoryWarmed(dirKey) {
47951
+ warmedDirectories.delete(dirKey);
47952
+ }
47953
+
47954
+ // src/files/read-file/resolve-file-path.ts
47955
+ async function resolveFileForReadAsync(relativePath, sessionParentPath = getBridgeRoot()) {
47956
+ const cacheKey = resolveCacheKey(sessionParentPath, relativePath);
47957
+ const cached2 = getResolveCacheEntry(cacheKey);
47958
+ if (cached2 != null && await statMatchesCache(cached2)) {
47959
+ return { path: cached2.path, size: cached2.size };
47960
+ }
47961
+ if (cached2 != null) deleteResolveCacheEntry(cacheKey);
47962
+ const resolved = ensureUnderCwd(relativePath, sessionParentPath);
47963
+ if (!resolved) return { error: "Path is outside working directory" };
47964
+ let real;
47965
+ let size;
47966
+ let mtimeMs;
47967
+ try {
47968
+ const lstat = await fs49.promises.lstat(resolved);
47969
+ if (lstat.isSymbolicLink()) {
47970
+ real = await fs49.promises.realpath(resolved);
47971
+ if (!pathStaysUnderParent(real, sessionParentPath)) {
47972
+ return { error: "Path is outside working directory" };
47973
+ }
47974
+ const stat3 = await fs49.promises.stat(real);
47975
+ if (!stat3.isFile()) return { error: "Not a file" };
47976
+ size = stat3.size;
47977
+ mtimeMs = stat3.mtimeMs;
47978
+ } else if (lstat.isFile()) {
47979
+ real = resolved;
47980
+ size = lstat.size;
47981
+ mtimeMs = lstat.mtimeMs;
47982
+ } else {
47983
+ return { error: "Not a file" };
47984
+ }
47985
+ } catch (err) {
47986
+ return { error: err instanceof Error ? err.message : String(err) };
47987
+ }
47988
+ const entry = storeResolveCacheEntry(cacheKey, { path: real, size, mtimeMs });
47989
+ return { path: entry.path, size: entry.size };
47990
+ }
47991
+ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
47992
+ const result = await resolveFileForReadAsync(relativePath, sessionParentPath);
47993
+ if ("error" in result) return result;
47994
+ return result.path;
47995
+ }
47996
+
47997
+ // src/files/read-file/read-file-range-async.ts
47998
+ init_yield_to_event_loop();
47999
+ import fs50 from "node:fs";
48000
+ import { StringDecoder as StringDecoder2 } from "node:string_decoder";
48001
+
48002
+ // src/files/read-file/read-file-range-line-zero.ts
48003
+ function createLineZeroStreamState(lineOffsetIn) {
48004
+ return {
48005
+ line0Accum: "",
48006
+ skipLine0Chars: typeof lineOffsetIn === "number" ? lineOffsetIn : 0,
48007
+ line0CharsReturned: 0
48008
+ };
48009
+ }
48010
+ function shouldTrackLineZero(startLine, lineOffsetIn) {
48011
+ return startLine === 0 || lineOffsetIn !== void 0;
48012
+ }
48013
+ function stripCarriageReturn(lineContent) {
48014
+ return lineContent.endsWith("\r") ? lineContent.slice(0, -1) : lineContent;
48015
+ }
48016
+ function lineContentBeforeNewline(text, lineStart, newlineIndex) {
48017
+ let lineEnd = newlineIndex;
48018
+ if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
48019
+ return text.slice(lineStart, lineEnd);
48020
+ }
48021
+ function buildLineZeroChunkResult(resultLines, fileSize, lineOffset, totalLines = 1) {
48022
+ return {
48023
+ content: resultLines.join("\n"),
48024
+ size: fileSize,
48025
+ lineOffset,
48026
+ totalLines
48027
+ };
48028
+ }
48029
+ function tryFinishLineZeroOnCompleteLine(state, lineContent, resultLines, fileSize, lineChunkSize, lineOffsetIn, endLine, currentLine) {
48030
+ state.line0Accum += lineContent;
48031
+ const totalLine0 = state.line0Accum.length;
48032
+ if (state.skipLine0Chars > 0) {
48033
+ if (totalLine0 <= state.skipLine0Chars) {
48034
+ state.skipLine0Chars -= totalLine0;
48035
+ state.line0Accum = "";
48036
+ return currentLine + 1 > endLine ? "done" : "continue";
48037
+ }
48038
+ const from = state.skipLine0Chars;
48039
+ const take = Math.min(lineChunkSize, totalLine0 - from);
48040
+ resultLines.push(state.line0Accum.slice(from, from + take));
48041
+ state.line0CharsReturned += take;
48042
+ if (from + take < totalLine0) {
48043
+ return buildLineZeroChunkResult(
48044
+ resultLines,
48045
+ fileSize,
48046
+ (lineOffsetIn ?? 0) + state.line0CharsReturned
48047
+ );
48048
+ }
48049
+ state.line0Accum = "";
48050
+ state.skipLine0Chars = 0;
48051
+ state.line0CharsReturned = 0;
48052
+ return "continue";
48053
+ }
48054
+ if (totalLine0 > lineChunkSize) {
48055
+ resultLines.push(state.line0Accum.slice(0, lineChunkSize));
48056
+ return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
48057
+ }
48058
+ resultLines.push(state.line0Accum);
48059
+ state.line0Accum = "";
48060
+ return "continue";
48061
+ }
48062
+ function tryFinishLineZeroPartial(state, partialLine, resultLines, fileSize, lineChunkSize, lineOffsetIn) {
48063
+ state.line0Accum += partialLine;
48064
+ const totalLine0 = state.line0Accum.length;
48065
+ if (state.skipLine0Chars > 0) {
48066
+ if (totalLine0 <= state.skipLine0Chars) {
48067
+ state.skipLine0Chars -= totalLine0;
48068
+ state.line0Accum = "";
48069
+ return null;
48070
+ }
48071
+ const from = state.skipLine0Chars;
48072
+ const take = Math.min(lineChunkSize, totalLine0 - from);
48073
+ resultLines.push(state.line0Accum.slice(from, from + take));
48074
+ return buildLineZeroChunkResult(resultLines, fileSize, (lineOffsetIn ?? 0) + take);
48075
+ }
48076
+ if (totalLine0 > lineChunkSize) {
48077
+ resultLines.push(state.line0Accum.slice(0, lineChunkSize));
48078
+ return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
48079
+ }
48080
+ return null;
48081
+ }
48082
+ function finishLineZeroTail(state, tail, resultLines, fileSize, lineChunkSize, lineOffsetIn) {
48083
+ if (tail.length === 0) return null;
48084
+ state.line0Accum += stripCarriageReturn(tail);
48085
+ const totalLine0 = state.line0Accum.length;
48086
+ if (state.skipLine0Chars > 0) {
48087
+ if (totalLine0 <= state.skipLine0Chars) {
48088
+ return { content: resultLines.join("\n"), size: fileSize };
48089
+ }
48090
+ const from = state.skipLine0Chars;
48091
+ const take = Math.min(lineChunkSize, totalLine0 - from);
48092
+ resultLines.push(state.line0Accum.slice(from, from + take));
48093
+ state.line0CharsReturned += take;
48094
+ if (from + take < totalLine0) {
48095
+ return buildLineZeroChunkResult(
48096
+ resultLines,
48097
+ fileSize,
48098
+ (lineOffsetIn ?? 0) + state.line0CharsReturned
48099
+ );
48100
+ }
48101
+ return null;
48102
+ }
48103
+ if (totalLine0 > lineChunkSize) {
48104
+ resultLines.push(state.line0Accum.slice(0, lineChunkSize));
48105
+ return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
48106
+ }
48107
+ resultLines.push(state.line0Accum);
48108
+ return null;
48109
+ }
48110
+
48111
+ // src/files/read-file/read-file-range-async.ts
48112
+ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE, fileSizeIn) {
48113
+ const fileSize = fileSizeIn ?? (await fs50.promises.stat(filePath)).size;
48114
+ const fd = await fs50.promises.open(filePath, "r");
48115
+ const bufSize = 64 * 1024;
48116
+ const buf = Buffer.alloc(bufSize);
48117
+ const decoder = new StringDecoder2("utf8");
48118
+ let currentLine = 0;
48119
+ const resultLines = [];
48120
+ let partial2 = "";
48121
+ let done = false;
48122
+ const trackLineZero = shouldTrackLineZero(startLine, lineOffsetIn);
48123
+ const lineZeroState = createLineZeroStreamState(lineOffsetIn);
48124
+ let bytesSinceYield = 0;
48125
+ try {
48126
+ let position = 0;
48127
+ while (!done) {
48128
+ const { bytesRead } = await fd.read(buf, 0, bufSize, position);
48129
+ if (bytesRead === 0) break;
48130
+ position += bytesRead;
48131
+ bytesSinceYield += bytesRead;
48132
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
48133
+ await yieldToEventLoop();
48134
+ bytesSinceYield = 0;
48135
+ }
48136
+ const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
48137
+ partial2 = "";
48138
+ let lineStart = 0;
48139
+ for (let i = 0; i < text.length; i++) {
48140
+ if (text[i] !== "\n") continue;
48141
+ const lineContent2 = lineContentBeforeNewline(text, lineStart, i);
48142
+ if (currentLine === 0 && trackLineZero) {
48143
+ const lineZeroResult = tryFinishLineZeroOnCompleteLine(
48144
+ lineZeroState,
48145
+ lineContent2,
48146
+ resultLines,
48147
+ fileSize,
48148
+ lineChunkSize,
48149
+ lineOffsetIn,
48150
+ endLine,
48151
+ currentLine
48152
+ );
48153
+ if (lineZeroResult === "done") {
48154
+ done = true;
48155
+ break;
48156
+ }
48157
+ if (lineZeroResult !== "continue") return lineZeroResult;
48158
+ currentLine++;
48159
+ lineStart = i + 1;
48160
+ if (currentLine > endLine) {
48161
+ done = true;
48162
+ break;
48163
+ }
48164
+ continue;
48165
+ }
48166
+ if (currentLine >= startLine && currentLine <= endLine) {
48167
+ resultLines.push(lineContent2);
48168
+ }
48169
+ currentLine++;
48170
+ lineStart = i + 1;
48171
+ if (currentLine > endLine) {
48172
+ done = true;
48173
+ break;
48174
+ }
48175
+ }
48176
+ if (done) continue;
48177
+ const lineContent = text.slice(lineStart);
48178
+ if (currentLine === 0 && trackLineZero) {
48179
+ const partialResult = tryFinishLineZeroPartial(
48180
+ lineZeroState,
48181
+ lineContent,
48182
+ resultLines,
48183
+ fileSize,
48184
+ lineChunkSize,
48185
+ lineOffsetIn
48186
+ );
48187
+ if (partialResult != null) return partialResult;
48188
+ } else {
48189
+ partial2 = lineContent;
48190
+ }
48191
+ }
48192
+ if (!done) {
48193
+ const tail = partial2 + decoder.end();
48194
+ if (currentLine === 0 && trackLineZero) {
48195
+ const tailResult = finishLineZeroTail(
48196
+ lineZeroState,
48197
+ tail,
48198
+ resultLines,
48199
+ fileSize,
48200
+ lineChunkSize,
48201
+ lineOffsetIn
48202
+ );
48203
+ if (tailResult != null) return tailResult;
48204
+ } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
48205
+ resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
48206
+ }
48207
+ }
48208
+ return { content: resultLines.join("\n"), size: fileSize };
48209
+ } finally {
48210
+ await fd.close();
48211
+ }
48212
+ }
48213
+
48214
+ // src/files/read-file/read-small-file-range-async.ts
48215
+ import fs51 from "node:fs";
48216
+ var SMALL_FILE_RANGE_MAX_BYTES = 512 * 1024;
48217
+ function sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize) {
48218
+ if (startLine === 0 && lineOffsetIn == null && lines[0] != null && lines[0].length > lineChunkSize) {
48219
+ return {
48220
+ content: lines[0].slice(0, lineChunkSize),
48221
+ size: fileSize,
48222
+ lineOffset: lineChunkSize,
48223
+ totalLines: lines.length
48224
+ };
48225
+ }
48226
+ if (startLine === 0 && typeof lineOffsetIn === "number") {
48227
+ const line0 = lines[0] ?? "";
48228
+ const from = lineOffsetIn;
48229
+ const take = Math.min(lineChunkSize, Math.max(0, line0.length - from));
48230
+ return {
48231
+ content: line0.slice(from, from + take),
48232
+ size: fileSize,
48233
+ ...from + take < line0.length ? { lineOffset: from + take, totalLines: lines.length } : { totalLines: lines.length }
48234
+ };
48235
+ }
48236
+ const slice = lines.slice(startLine, endLine + 1);
48237
+ return {
48238
+ content: slice.join("\n"),
48239
+ size: fileSize,
48240
+ totalLines: lines.length
48241
+ };
48242
+ }
48243
+ async function readSmallFileRangeAsync(filePath, fileSize, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
48244
+ const raw = await fs51.promises.readFile(filePath, "utf8");
48245
+ const lines = raw.split(/\r?\n/);
48246
+ return sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize);
48247
+ }
48248
+
48249
+ // src/files/read-file/read-file-buffer-full-async.ts
48250
+ init_yield_to_event_loop();
48251
+ import fs52 from "node:fs";
48252
+ var READ_CHUNK_BYTES = 256 * 1024;
48253
+ async function readFileBufferFullAsync(filePath) {
48254
+ const stat3 = await fs52.promises.stat(filePath);
48255
+ const fd = await fs52.promises.open(filePath, "r");
48256
+ const chunks = [];
48257
+ let position = 0;
48258
+ let bytesSinceYield = 0;
48259
+ try {
48260
+ while (position < stat3.size) {
48261
+ const buf = Buffer.alloc(Math.min(READ_CHUNK_BYTES, stat3.size - position));
48262
+ const { bytesRead } = await fd.read(buf, 0, buf.length, position);
48263
+ if (bytesRead === 0) break;
48264
+ chunks.push(buf.subarray(0, bytesRead));
48265
+ position += bytesRead;
48266
+ bytesSinceYield += bytesRead;
48267
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
48268
+ await yieldToEventLoop();
48269
+ bytesSinceYield = 0;
48270
+ }
48271
+ }
48272
+ } finally {
48273
+ await fd.close();
48274
+ }
48275
+ return { buffer: Buffer.concat(chunks), size: stat3.size };
48276
+ }
48277
+
48278
+ // src/files/read-file/read-file-full-async.ts
48279
+ async function readFileFullAsync(filePath) {
48280
+ const { buffer, size } = await readFileBufferFullAsync(filePath);
48281
+ const raw = buffer.toString("utf8");
48282
+ const lines = raw.split(/\r?\n/);
48283
+ return { content: raw, totalLines: lines.length, size };
48284
+ }
48285
+
48286
+ // src/files/read-file/read-file-binary-full-async.ts
48287
+ async function readFileBinaryFullAsync(filePath) {
48288
+ const { buffer, size } = await readFileBufferFullAsync(filePath);
48289
+ return {
48290
+ content: buffer.toString("base64"),
48291
+ size,
48292
+ mimeType: guessMimeType(filePath)
48293
+ };
48294
+ }
48295
+
48296
+ // src/files/read-file/index.ts
48297
+ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, sessionParentPath = getBridgeRoot(), encoding = "utf8", options) {
48298
+ if (!options?.skipInitialYield) {
48299
+ await yieldToEventLoop();
48300
+ }
48301
+ try {
48302
+ let resolvedPath;
48303
+ let fileSize;
48304
+ if (options?.useResolvedSize) {
48305
+ const resolved = await resolveFileForReadAsync(relativePath, sessionParentPath);
48306
+ if ("error" in resolved) return resolved;
48307
+ resolvedPath = resolved.path;
48308
+ fileSize = resolved.size;
48309
+ } else {
48310
+ const result = await resolveFilePathAsync(relativePath, sessionParentPath);
48311
+ if (typeof result === "object") return result;
48312
+ resolvedPath = result;
48313
+ }
48314
+ const hasRange = typeof startLine === "number" && typeof endLine === "number";
48315
+ if (encoding === "base64") {
48316
+ if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
48317
+ const read2 = await readFileBinaryFullAsync(resolvedPath);
48318
+ return { ...read2, resolvedPath };
48319
+ }
48320
+ if (hasRange) {
48321
+ if (fileSize == null) {
48322
+ const stat3 = await fs53.promises.stat(resolvedPath);
48323
+ fileSize = stat3.size;
48324
+ }
48325
+ const read2 = fileSize <= SMALL_FILE_RANGE_MAX_BYTES ? await readSmallFileRangeAsync(
48326
+ resolvedPath,
48327
+ fileSize,
48328
+ startLine,
48329
+ endLine,
48330
+ lineOffset,
48331
+ lineChunkSize
48332
+ ) : await readFileRangeAsync(
48333
+ resolvedPath,
48334
+ startLine,
48335
+ endLine,
48336
+ lineOffset,
48337
+ lineChunkSize,
48338
+ fileSize
48339
+ );
48340
+ return read2;
48341
+ }
48342
+ const read = await readFileFullAsync(resolvedPath);
48343
+ return { ...read, resolvedPath };
48344
+ } catch (err) {
48345
+ return { error: err instanceof Error ? err.message : String(err) };
48346
+ }
48347
+ }
48348
+
48349
+ // src/files/browser/execute-file-browser-read.ts
48350
+ async function executeFileBrowserRead(params) {
48351
+ const { msg, reqPath, sessionParentPath, gitScope } = params;
48352
+ const startLine = typeof msg.startLine === "number" ? msg.startLine : void 0;
48353
+ const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
48354
+ const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
48355
+ const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
48356
+ const encoding = msg.encoding === "base64" ? "base64" : "utf8";
48357
+ return gitScope ? await readGitTreeFileAsync(
48358
+ gitScope.repoAbsPath,
48359
+ gitScope.branch,
48360
+ reqPath,
48361
+ startLine,
48362
+ endLine,
48363
+ lineOffset,
48364
+ lineChunkSize,
48365
+ encoding
48366
+ ) : await withFileBrowserReadQueue(
48367
+ () => readFileAsync(
48368
+ reqPath,
48369
+ startLine,
48370
+ endLine,
48371
+ lineOffset,
48372
+ lineChunkSize,
48373
+ sessionParentPath,
48374
+ encoding,
48375
+ { skipInitialYield: true, useResolvedSize: true }
48376
+ )
48377
+ );
48378
+ }
48379
+
48380
+ // src/local-shortcut/app/lib/file-browser/handle-read.ts
48381
+ async function handleShortcutFileBrowserRead(ctx, sessionId) {
48382
+ const reqPath = parseReadPath(ctx.url);
48383
+ if (reqPath == null) {
48384
+ sendShortcutJson(ctx.res, 400, { error: "path required" });
48385
+ return;
48386
+ }
48387
+ const gitMsg = parseGitScopeFromUrl(ctx.url);
48388
+ const gitScope = resolveGitBranchScope({ id: "shortcut", path: reqPath, ...gitMsg });
48389
+ if (gitMsg.source === "git_branch" && typeof gitMsg.repoRelPath === "string" && typeof gitMsg.branch === "string" && gitMsg.branch.trim().length > 0 && !gitScope) {
48390
+ sendShortcutJson(ctx.res, 400, { error: "Invalid repository path" });
48391
+ return;
48392
+ }
48393
+ const sessionParentPath = await resolveShortcutSessionParent(ctx, sessionId);
48394
+ const result = await executeFileBrowserRead({
48395
+ msg: parseReadParams(ctx.url),
48396
+ reqPath,
48397
+ sessionParentPath,
48398
+ gitScope
48399
+ });
48400
+ if ("error" in result) {
48401
+ sendShortcutJson(ctx.res, 400, { error: result.error });
48402
+ return;
48403
+ }
48404
+ const body = { content: result.content };
48405
+ if (result.totalLines != null) body.totalLines = result.totalLines;
48406
+ if (result.size != null) body.size = result.size;
48407
+ if (result.lineOffset != null) body.lineOffset = result.lineOffset;
48408
+ if (result.mimeType != null) body.mimeType = result.mimeType;
48409
+ if (result.resolvedPath != null) body.resolvedPath = result.resolvedPath;
48410
+ sendShortcutFileBrowserJson(ctx.res, 200, body, ctx.e2ee);
48411
+ }
48412
+
48413
+ // src/local-shortcut/app/api/bridges/files/read/route.ts
48414
+ async function GET3(ctx) {
48415
+ if (!assertBridgeWorkspace(ctx)) return;
48416
+ await handleShortcutFileBrowserRead(ctx);
48417
+ }
48418
+
48419
+ // src/local-shortcut/app/api/sessions/[sessionId]/files/list/route.ts
48420
+ async function GET4(ctx) {
48421
+ await handleShortcutFileBrowserList(ctx, ctx.params.sessionId);
48422
+ }
48423
+
48424
+ // src/local-shortcut/app/api/sessions/[sessionId]/files/read/route.ts
48425
+ async function GET5(ctx) {
48426
+ await handleShortcutFileBrowserRead(ctx, ctx.params.sessionId);
48427
+ }
48428
+
48429
+ // src/local-shortcut/lib/router.ts
48430
+ var routes = [];
48431
+ function segmentsFromAppPath(appPath) {
48432
+ const paramNames = [];
48433
+ const segments = appPath.split("/").filter(Boolean).map((seg) => {
48434
+ if (seg.startsWith("[") && seg.endsWith("]")) {
48435
+ const name = seg.slice(1, -1);
48436
+ paramNames.push(name);
48437
+ return `:${name}`;
48438
+ }
48439
+ return seg;
48440
+ });
48441
+ return { segments, paramNames };
48442
+ }
48443
+ function addAppRoute(method, appPath, handler) {
48444
+ const { segments, paramNames } = segmentsFromAppPath(appPath);
48445
+ routes.push({ method, segments, paramNames, handler });
48446
+ }
48447
+ addAppRoute("GET", "api/bridges/local-shortcut/health", GET);
48448
+ addAppRoute("GET", "api/bridges/files/list", GET2);
48449
+ addAppRoute("GET", "api/bridges/files/read", GET3);
48450
+ addAppRoute("GET", "api/sessions/[sessionId]/files/list", GET4);
48451
+ addAppRoute("GET", "api/sessions/[sessionId]/files/read", GET5);
48452
+ function matchShortcutRoute(pathname, method) {
48453
+ const pathSegments = pathname.split("/").filter(Boolean);
48454
+ for (const route of routes) {
48455
+ if (route.method !== method) continue;
48456
+ if (pathSegments.length !== route.segments.length) continue;
48457
+ const params = {};
48458
+ let matched = true;
48459
+ for (let i = 0; i < route.segments.length; i++) {
48460
+ const seg = route.segments[i];
48461
+ const value = pathSegments[i];
48462
+ if (seg.startsWith(":")) {
48463
+ params[seg.slice(1)] = decodeURIComponent(value);
48464
+ } else if (seg !== value) {
48465
+ matched = false;
48466
+ break;
48467
+ }
48468
+ }
48469
+ if (matched) return { params, handler: route.handler };
48470
+ }
48471
+ return null;
48472
+ }
48473
+
48474
+ // src/local-shortcut/lib/server.ts
48475
+ function shortcutErrorMessage(err) {
48476
+ return err instanceof Error ? err.message : "Internal shortcut server error";
48477
+ }
48478
+ function createLocalShortcutRequestHandler(ctx) {
48479
+ return (req, res) => {
48480
+ void (async () => {
48481
+ try {
48482
+ const corsOk = applyCorsHeaders(req, res);
48483
+ if (req.method === "OPTIONS") {
48484
+ res.writeHead(corsOk ? 204 : 403);
48485
+ res.end();
48486
+ return;
48487
+ }
48488
+ if (!corsOk) {
48489
+ sendShortcutJson(res, 403, { error: "Origin not allowed" });
48490
+ return;
48491
+ }
48492
+ if (req.method !== "GET") {
48493
+ sendShortcutJson(res, 405, { error: "Method not allowed" });
48494
+ return;
48495
+ }
48496
+ if (!isAuthorizedShortcutRequest(req, ctx.token)) {
48497
+ sendShortcutJson(res, 401, { error: "Unauthorized" });
48498
+ return;
48499
+ }
48500
+ const rawUrl = req.url ?? "/";
48501
+ const qIndex = rawUrl.indexOf("?");
48502
+ const pathname = qIndex >= 0 ? rawUrl.slice(0, qIndex) : rawUrl;
48503
+ const search = qIndex >= 0 ? rawUrl.slice(qIndex) : "";
48504
+ const url2 = new URL2(search || "", "http://local");
48505
+ const match = matchShortcutRoute(pathname, req.method);
48506
+ if (!match) {
48507
+ sendShortcutJson(res, 404, { error: "Not found" });
48508
+ return;
48509
+ }
48510
+ const handlerCtx = {
48511
+ req,
48512
+ res,
48513
+ params: match.params,
48514
+ url: url2,
48515
+ workspaceId: ctx.workspaceId,
48516
+ sessionWorktreeManager: ctx.sessionWorktreeManager,
48517
+ e2ee: ctx.e2ee
48518
+ };
48519
+ await match.handler(handlerCtx);
48520
+ } catch (err) {
48521
+ if (!res.headersSent) {
48522
+ sendShortcutJson(res, 500, { error: shortcutErrorMessage(err) });
48523
+ }
48524
+ }
48525
+ })().catch((err) => {
48526
+ if (!res.headersSent) {
48527
+ sendShortcutJson(res, 500, { error: shortcutErrorMessage(err) });
48528
+ }
48529
+ });
48530
+ };
48531
+ }
48532
+
48533
+ // src/local-shortcut/lib/start-server.ts
48534
+ var RESTART_DELAY_MS = 250;
48535
+ var MAX_RESTART_ATTEMPTS = 5;
48536
+ function listenOnPort(server, port) {
48537
+ return new Promise((resolve37, reject) => {
48538
+ server.once("error", reject);
48539
+ server.listen(port, "127.0.0.1", () => {
48540
+ const addr = server.address();
48541
+ if (!addr || typeof addr === "string") {
48542
+ reject(new Error("Local shortcut server did not bind"));
48543
+ return;
48544
+ }
48545
+ resolve37(addr.port);
48546
+ });
48547
+ });
48548
+ }
48549
+ function closeServer(server) {
48550
+ if (!server) return Promise.resolve();
48551
+ return new Promise((resolve37, reject) => {
48552
+ server.close((err) => err ? reject(err) : resolve37());
48553
+ });
48554
+ }
48555
+ function delay2(ms) {
48556
+ return new Promise((resolve37) => setTimeout(resolve37, ms));
48557
+ }
48558
+ function startLocalShortcutServer(ctx) {
48559
+ let closed = false;
48560
+ let restartAttempts = 0;
48561
+ let currentServer = null;
48562
+ let boundPort = null;
48563
+ let restarting = null;
48564
+ async function bindServer(port) {
48565
+ const server = http2.createServer(createLocalShortcutRequestHandler(ctx));
48566
+ server.on("error", (err) => {
48567
+ void restartAfterError(err);
48568
+ });
48569
+ currentServer = server;
48570
+ const listenPort = await listenOnPort(server, port);
48571
+ boundPort = listenPort;
48572
+ return listenPort;
48573
+ }
48574
+ async function restartAfterError(_reason) {
48575
+ if (closed || restarting) return;
48576
+ if (restartAttempts >= MAX_RESTART_ATTEMPTS) return;
48577
+ restartAttempts += 1;
48578
+ restarting = (async () => {
48579
+ await closeServer(currentServer).catch(() => {
48580
+ });
48581
+ currentServer = null;
48582
+ if (closed) return;
48583
+ await delay2(RESTART_DELAY_MS);
48584
+ if (closed) return;
48585
+ try {
48586
+ await bindServer(boundPort ?? 0);
48587
+ restartAttempts = 0;
48588
+ } catch {
48589
+ }
48590
+ })().finally(() => {
48591
+ restarting = null;
48592
+ });
48593
+ await restarting;
48594
+ }
48595
+ return bindServer(0).then((port) => ({
48596
+ port,
48597
+ token: ctx.token,
48598
+ close: async () => {
48599
+ closed = true;
48600
+ await closeServer(currentServer);
48601
+ currentServer = null;
48602
+ }
48603
+ }));
48604
+ }
48605
+
48606
+ // src/local-shortcut/lib/create.ts
48607
+ async function createLocalShortcut(params) {
48608
+ const token = generateShortcutToken();
48609
+ const handle = await startLocalShortcutServer({
48610
+ workspaceId: params.workspaceId,
48611
+ token,
48612
+ sessionWorktreeManager: params.sessionWorktreeManager,
48613
+ e2ee: params.e2ee
48614
+ });
48615
+ return {
48616
+ handle,
48617
+ identify: {
48618
+ localShortcutPort: handle.port,
48619
+ localShortcutToken: handle.token
48620
+ }
48621
+ };
48622
+ }
48623
+
48624
+ // src/connection/create-bridge-connection-runtime.ts
48625
+ async function createBridgeConnectionRuntime(options, params) {
48626
+ const { apiUrl, workspaceId } = options;
48627
+ const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
48628
+ const { state, getWs, logFn } = params;
48629
+ const tokens = {
48630
+ accessToken: options.authToken,
48631
+ refreshToken: options.refreshToken
48632
+ };
48633
+ const { worktreesRootPath, sessionWorktreeManager, previewWorktreeManager } = createBridgeWorktreeManagers({ worktreesRootPath: options.worktreesRootPath, log: logFn });
48634
+ const { e2ee, acpManager, bridgeAccessPromise } = await createBridgeAccessAndAcp({
48635
+ apiUrl,
48636
+ workspaceId,
48637
+ tokens,
48638
+ e2eCertificate: options.e2eCertificate,
48639
+ getWs,
48640
+ log: logFn
48641
+ });
48642
+ const localShortcut = await createLocalShortcut({
48643
+ workspaceId,
48644
+ sessionWorktreeManager,
48645
+ e2ee
47163
48646
  });
47164
48647
  logFn("CLI running. Press Ctrl+C to exit.");
47165
48648
  const { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths } = createBridgePreviewStack({
@@ -47169,6 +48652,7 @@ async function createBridgeConnectionRuntime(options, params) {
47169
48652
  previewWorktreeManager,
47170
48653
  worktreesRootPath
47171
48654
  });
48655
+ Object.assign(identifyReportedPaths, localShortcut.identify);
47172
48656
  const { bridgeHeartbeat, messageDeps } = createBridgeRuntimeMessageSetup({
47173
48657
  apiUrl,
47174
48658
  workspaceId,
@@ -47181,7 +48665,8 @@ async function createBridgeConnectionRuntime(options, params) {
47181
48665
  sessionWorktreeManager,
47182
48666
  previewWorktreeManager,
47183
48667
  previewEnvironmentManager,
47184
- e2ee
48668
+ e2ee,
48669
+ identifyReportedPaths
47185
48670
  });
47186
48671
  return {
47187
48672
  state,
@@ -47195,7 +48680,8 @@ async function createBridgeConnectionRuntime(options, params) {
47195
48680
  messageDeps,
47196
48681
  identifyReportedPaths,
47197
48682
  scheduleInitialIndexBuildsOnce,
47198
- e2ee
48683
+ e2ee,
48684
+ localShortcutPromise: Promise.resolve(localShortcut.handle)
47199
48685
  };
47200
48686
  }
47201
48687
 
@@ -47484,15 +48970,15 @@ function syncRunningTurnQueueKeys(turns, queueKey) {
47484
48970
  }
47485
48971
 
47486
48972
  // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
47487
- import fs47 from "node:fs";
48973
+ import fs55 from "node:fs";
47488
48974
 
47489
48975
  // src/git/snapshot/capture.ts
47490
- import * as fs46 from "node:fs";
47491
- import * as path73 from "node:path";
48976
+ import * as fs54 from "node:fs";
48977
+ import * as path77 from "node:path";
47492
48978
  async function resolveSnapshotRepoRoots(options) {
47493
48979
  const { worktreePaths, fallbackCwd, sessionId, log: log2 } = options;
47494
48980
  if (worktreePaths?.length) {
47495
- const uniq = [...new Set(worktreePaths.map((p) => path73.resolve(p)))];
48981
+ const uniq = [...new Set(worktreePaths.map((p) => path77.resolve(p)))];
47496
48982
  return uniq;
47497
48983
  }
47498
48984
  try {
@@ -47500,7 +48986,7 @@ async function resolveSnapshotRepoRoots(options) {
47500
48986
  const mapped = repos.map((r) => r.absolutePath);
47501
48987
  const sid = sessionId?.trim();
47502
48988
  if (sid) {
47503
- const filtered = mapped.filter((root) => path73.basename(root) === sid);
48989
+ const filtered = mapped.filter((root) => path77.basename(root) === sid);
47504
48990
  if (filtered.length > 0) return filtered;
47505
48991
  }
47506
48992
  return mapped;
@@ -47522,7 +49008,7 @@ async function capturePreTurnSnapshot(options) {
47522
49008
  });
47523
49009
  const dir = snapshotsDirForCwd(agentCwd);
47524
49010
  try {
47525
- fs46.mkdirSync(dir, { recursive: true });
49011
+ fs54.mkdirSync(dir, { recursive: true });
47526
49012
  } catch (e) {
47527
49013
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
47528
49014
  }
@@ -47531,9 +49017,9 @@ async function capturePreTurnSnapshot(options) {
47531
49017
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
47532
49018
  repos
47533
49019
  };
47534
- const filePath = path73.join(dir, `${runId}.json`);
49020
+ const filePath = path77.join(dir, `${runId}.json`);
47535
49021
  try {
47536
- fs46.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
49022
+ fs54.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
47537
49023
  } catch (e) {
47538
49024
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
47539
49025
  }
@@ -47546,7 +49032,7 @@ async function capturePreTurnSnapshot(options) {
47546
49032
  async function applyPreTurnSnapshot(filePath, log2) {
47547
49033
  let data;
47548
49034
  try {
47549
- const raw = fs46.readFileSync(filePath, "utf8");
49035
+ const raw = fs54.readFileSync(filePath, "utf8");
47550
49036
  data = JSON.parse(raw);
47551
49037
  } catch (e) {
47552
49038
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
@@ -47587,7 +49073,7 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
47587
49073
  const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
47588
49074
  const file2 = snapshotFilePath(agentBase, tid);
47589
49075
  try {
47590
- await fs47.promises.access(file2, fs47.constants.F_OK);
49076
+ await fs55.promises.access(file2, fs55.constants.F_OK);
47591
49077
  } catch {
47592
49078
  deps.log(
47593
49079
  `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
@@ -48174,416 +49660,6 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
48174
49660
  );
48175
49661
  };
48176
49662
 
48177
- // src/files/resolve-file-browser-session-parent.ts
48178
- init_normalize_resolved_path();
48179
- init_cwd();
48180
- async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
48181
- const sid = sessionId?.trim();
48182
- if (sid) {
48183
- await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sid);
48184
- const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
48185
- if (worktreeRoot) return normalizeResolvedPath(worktreeRoot);
48186
- }
48187
- return getBridgeRoot();
48188
- }
48189
-
48190
- // src/files/browser/handle-file-browser-list.ts
48191
- init_list_dir();
48192
-
48193
- // src/git/tree/resolve-repo-abs-path.ts
48194
- init_cwd();
48195
- import * as path76 from "node:path";
48196
- function resolveRepoAbsPathFromBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
48197
- const bridgeResolved = path76.resolve(bridgeRoot);
48198
- const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
48199
- const repoPath = rel === "" ? bridgeResolved : path76.join(bridgeResolved, rel);
48200
- const resolved = path76.resolve(repoPath);
48201
- if (!resolved.startsWith(bridgeResolved + path76.sep) && resolved !== bridgeResolved) {
48202
- return null;
48203
- }
48204
- return resolved;
48205
- }
48206
-
48207
- // src/git/tree/resolve-branch-commit.ts
48208
- async function resolveBranchCommit(repoAbsPath, branch) {
48209
- const branchRef = branch.trim();
48210
- if (!branchRef) return null;
48211
- try {
48212
- return (await cliSimpleGit(repoAbsPath).revparse([`${branchRef}^{commit}`])).trim();
48213
- } catch {
48214
- return null;
48215
- }
48216
- }
48217
-
48218
- // src/git/tree/list-git-tree-dir.ts
48219
- init_yield_to_event_loop();
48220
- init_types2();
48221
- init_sort_entries();
48222
- function parseLsTreeLine(line) {
48223
- const tab = line.indexOf(" ");
48224
- if (tab < 0) return null;
48225
- const meta = line.slice(0, tab).trim().split(/\s+/);
48226
- if (meta.length < 3) return null;
48227
- const mode = meta[0];
48228
- const objectType2 = meta[1];
48229
- let name = line.slice(tab + 1);
48230
- if (name.startsWith('"') && name.endsWith('"')) {
48231
- name = name.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
48232
- }
48233
- return { mode, objectType: objectType2, name };
48234
- }
48235
- function childPath(relativePath, name) {
48236
- const base = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
48237
- if (!base || base === ".") return name;
48238
- return `${base}/${name}`.replace(/\\/g, "/");
48239
- }
48240
- function lsTreeArgs(commit, relativePath) {
48241
- const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
48242
- if (!rel || rel === ".") return ["ls-tree", commit];
48243
- return ["ls-tree", `${commit}:${rel}`];
48244
- }
48245
- async function listGitTreeDirAsync(repoAbsPath, branch, relativePath) {
48246
- await yieldToEventLoop();
48247
- const commit = await resolveBranchCommit(repoAbsPath, branch);
48248
- if (!commit) return { error: "Branch not found" };
48249
- try {
48250
- const raw = await cliSimpleGit(repoAbsPath).raw(lsTreeArgs(commit, relativePath));
48251
- await yieldToEventLoop();
48252
- const lines = String(raw ?? "").split(/\r?\n/).filter((line) => line.trim().length > 0);
48253
- const entries = [];
48254
- for (let i = 0; i < lines.length; i++) {
48255
- if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
48256
- await yieldToEventLoop();
48257
- }
48258
- const parsed = parseLsTreeLine(lines[i]);
48259
- if (!parsed) continue;
48260
- const { mode, objectType: objectType2, name } = parsed;
48261
- if (!name || name === "." || name === "..") continue;
48262
- const isSymlink = mode === "120000";
48263
- const isDir = objectType2 === "tree";
48264
- entries.push({
48265
- name,
48266
- path: childPath(relativePath, name),
48267
- isDir,
48268
- ...isSymlink ? { isSymlink: true } : {}
48269
- });
48270
- }
48271
- if (entries.length >= LIST_DIR_YIELD_EVERY) {
48272
- await yieldToEventLoop();
48273
- }
48274
- return { entries: sortListEntries(entries) };
48275
- } catch (err) {
48276
- const message = err instanceof Error ? err.message : String(err);
48277
- if (/not a tree object|does not exist|bad revision/i.test(message)) {
48278
- return { error: "Path not found in branch" };
48279
- }
48280
- return { error: message };
48281
- }
48282
- }
48283
-
48284
- // src/git/tree/file/index.ts
48285
- init_yield_to_event_loop();
48286
-
48287
- // src/files/read-file/types.ts
48288
- var LINE_CHUNK_SIZE = 64 * 1024;
48289
- var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
48290
-
48291
- // src/files/read-file/guess-mime-type.ts
48292
- var MIME_BY_EXT = {
48293
- png: "image/png",
48294
- jpg: "image/jpeg",
48295
- jpeg: "image/jpeg",
48296
- gif: "image/gif",
48297
- bmp: "image/bmp",
48298
- ico: "image/x-icon",
48299
- webp: "image/webp",
48300
- avif: "image/avif",
48301
- svg: "image/svg+xml",
48302
- pdf: "application/pdf",
48303
- json: "application/json",
48304
- html: "text/html",
48305
- htm: "text/html",
48306
- css: "text/css",
48307
- js: "text/javascript",
48308
- mjs: "text/javascript",
48309
- ts: "text/typescript",
48310
- txt: "text/plain",
48311
- md: "text/markdown",
48312
- xml: "application/xml",
48313
- zip: "application/zip",
48314
- gz: "application/gzip",
48315
- wasm: "application/wasm"
48316
- };
48317
- function guessMimeType(filePath) {
48318
- const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
48319
- return MIME_BY_EXT[ext] ?? "application/octet-stream";
48320
- }
48321
-
48322
- // src/git/tree/file/git-blob-ref.ts
48323
- init_yield_to_event_loop();
48324
- async function gitBlobRef(repoAbsPath, branch, relativePath) {
48325
- const commit = await resolveBranchCommit(repoAbsPath, branch);
48326
- await yieldToEventLoop();
48327
- if (!commit) return null;
48328
- const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
48329
- if (!rel || rel === ".") return null;
48330
- return `${commit}:${rel}`;
48331
- }
48332
-
48333
- // src/git/tree/file/is-binary-buffer.ts
48334
- function isBinaryBuffer(buf) {
48335
- const sample = buf.subarray(0, Math.min(buf.length, 8192));
48336
- for (let i = 0; i < sample.length; i++) {
48337
- if (sample[i] === 0) return true;
48338
- }
48339
- return false;
48340
- }
48341
-
48342
- // src/git/tree/file/read-git-blob-text-async.ts
48343
- init_yield_to_event_loop();
48344
- import { StringDecoder } from "node:string_decoder";
48345
- async function countGitBlobLinesAsync(buf) {
48346
- if (buf.length === 0) return 1;
48347
- let lines = 1;
48348
- let bytesSinceYield = 0;
48349
- for (let i = 0; i < buf.length; i++) {
48350
- const b = buf[i];
48351
- if (b === 10) {
48352
- lines++;
48353
- } else if (b === 13) {
48354
- if (i + 1 < buf.length && buf[i + 1] === 10) {
48355
- lines++;
48356
- i++;
48357
- } else {
48358
- lines++;
48359
- }
48360
- }
48361
- bytesSinceYield++;
48362
- if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
48363
- await yieldToEventLoop();
48364
- bytesSinceYield = 0;
48365
- }
48366
- }
48367
- return lines;
48368
- }
48369
- async function readGitBlobLineRangeAsync(buf, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
48370
- const bufSize = 64 * 1024;
48371
- const decoder = new StringDecoder("utf8");
48372
- let currentLine = 0;
48373
- const resultLines = [];
48374
- let partial2 = "";
48375
- let done = false;
48376
- let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
48377
- let line0CharsReturned = 0;
48378
- let line0Accum = "";
48379
- let bytesSinceYield = 0;
48380
- let position = 0;
48381
- const finish = async () => {
48382
- const totalLines = await countGitBlobLinesAsync(buf);
48383
- return { content: resultLines.join("\n"), totalLines };
48384
- };
48385
- while (!done && position < buf.length) {
48386
- const chunkEnd = Math.min(position + bufSize, buf.length);
48387
- const bytesRead = chunkEnd - position;
48388
- position = chunkEnd;
48389
- bytesSinceYield += bytesRead;
48390
- if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
48391
- await yieldToEventLoop();
48392
- bytesSinceYield = 0;
48393
- }
48394
- const text = partial2 + decoder.write(buf.subarray(position - bytesRead, position));
48395
- partial2 = "";
48396
- let lineStart = 0;
48397
- for (let i = 0; i < text.length; i++) {
48398
- if (text[i] === "\n") {
48399
- const lineContent = (() => {
48400
- let lineEnd = i;
48401
- if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
48402
- return text.slice(lineStart, lineEnd);
48403
- })();
48404
- if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
48405
- line0Accum += lineContent;
48406
- const totalLine0 = line0Accum.length;
48407
- if (skipLine0Chars > 0) {
48408
- if (totalLine0 <= skipLine0Chars) {
48409
- skipLine0Chars -= totalLine0;
48410
- line0Accum = "";
48411
- currentLine++;
48412
- lineStart = i + 1;
48413
- if (currentLine > endLine) {
48414
- done = true;
48415
- break;
48416
- }
48417
- continue;
48418
- }
48419
- const from = skipLine0Chars;
48420
- const take = Math.min(lineChunkSize, totalLine0 - from);
48421
- resultLines.push(line0Accum.slice(from, from + take));
48422
- line0CharsReturned += take;
48423
- if (from + take < totalLine0) {
48424
- const totalLines = await countGitBlobLinesAsync(buf);
48425
- return {
48426
- content: resultLines.join("\n"),
48427
- totalLines,
48428
- lineOffset: lineOffsetIn + line0CharsReturned
48429
- };
48430
- }
48431
- line0Accum = "";
48432
- skipLine0Chars = 0;
48433
- line0CharsReturned = 0;
48434
- } else if (totalLine0 > lineChunkSize) {
48435
- resultLines.push(line0Accum.slice(0, lineChunkSize));
48436
- const totalLines = await countGitBlobLinesAsync(buf);
48437
- return {
48438
- content: resultLines.join("\n"),
48439
- totalLines,
48440
- lineOffset: lineChunkSize
48441
- };
48442
- } else {
48443
- resultLines.push(line0Accum);
48444
- line0Accum = "";
48445
- }
48446
- } else if (currentLine >= startLine && currentLine <= endLine) {
48447
- resultLines.push(lineContent);
48448
- }
48449
- currentLine++;
48450
- lineStart = i + 1;
48451
- if (currentLine > endLine) {
48452
- done = true;
48453
- break;
48454
- }
48455
- }
48456
- }
48457
- if (!done) {
48458
- const lineContent = text.slice(lineStart);
48459
- if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
48460
- line0Accum += lineContent;
48461
- const totalLine0 = line0Accum.length;
48462
- if (skipLine0Chars > 0) {
48463
- if (totalLine0 <= skipLine0Chars) {
48464
- skipLine0Chars -= totalLine0;
48465
- line0Accum = "";
48466
- } else {
48467
- const from = skipLine0Chars;
48468
- const take = Math.min(lineChunkSize, totalLine0 - from);
48469
- resultLines.push(line0Accum.slice(from, from + take));
48470
- const totalLines = await countGitBlobLinesAsync(buf);
48471
- return {
48472
- content: resultLines.join("\n"),
48473
- totalLines,
48474
- lineOffset: (lineOffsetIn ?? 0) + take
48475
- };
48476
- }
48477
- } else if (totalLine0 > lineChunkSize) {
48478
- resultLines.push(line0Accum.slice(0, lineChunkSize));
48479
- const totalLines = await countGitBlobLinesAsync(buf);
48480
- return {
48481
- content: resultLines.join("\n"),
48482
- totalLines,
48483
- lineOffset: lineChunkSize
48484
- };
48485
- }
48486
- }
48487
- partial2 = text.slice(lineStart);
48488
- }
48489
- }
48490
- if (!done) {
48491
- const tail = partial2 + decoder.end();
48492
- if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0) && tail.length > 0) {
48493
- line0Accum += tail.endsWith("\r") ? tail.slice(0, -1) : tail;
48494
- const totalLine0 = line0Accum.length;
48495
- if (skipLine0Chars > 0) {
48496
- if (totalLine0 <= skipLine0Chars) {
48497
- return finish();
48498
- }
48499
- const from = skipLine0Chars;
48500
- const take = Math.min(lineChunkSize, totalLine0 - from);
48501
- resultLines.push(line0Accum.slice(from, from + take));
48502
- line0CharsReturned += take;
48503
- if (from + take < totalLine0) {
48504
- const totalLines = await countGitBlobLinesAsync(buf);
48505
- return {
48506
- content: resultLines.join("\n"),
48507
- totalLines,
48508
- lineOffset: (lineOffsetIn ?? 0) + line0CharsReturned
48509
- };
48510
- }
48511
- } else if (totalLine0 > lineChunkSize) {
48512
- resultLines.push(line0Accum.slice(0, lineChunkSize));
48513
- const totalLines = await countGitBlobLinesAsync(buf);
48514
- return {
48515
- content: resultLines.join("\n"),
48516
- totalLines,
48517
- lineOffset: lineChunkSize
48518
- };
48519
- } else {
48520
- resultLines.push(line0Accum);
48521
- }
48522
- } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
48523
- resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
48524
- }
48525
- }
48526
- return finish();
48527
- }
48528
- async function decodeGitBlobUtf8Async(buf) {
48529
- await yieldToEventLoop();
48530
- const text = buf.toString("utf8");
48531
- await yieldToEventLoop();
48532
- return text;
48533
- }
48534
- async function readGitBlobUtf8FullAsync(buf) {
48535
- const text = await decodeGitBlobUtf8Async(buf);
48536
- const totalLines = await countGitBlobLinesAsync(buf);
48537
- return { content: text, totalLines };
48538
- }
48539
-
48540
- // src/git/tree/file/index.ts
48541
- async function readGitTreeFileAsync(repoAbsPath, branch, relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, encoding = "utf8") {
48542
- await yieldToEventLoop();
48543
- const branchRef = branch.trim();
48544
- if (!branchRef) return { error: "branch required" };
48545
- const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
48546
- if (!rel || rel === ".") return { error: "file path required" };
48547
- const blobRef = await gitBlobRef(repoAbsPath, branchRef, rel);
48548
- if (!blobRef) return { error: "Branch not found" };
48549
- const hasRange = typeof startLine === "number" && typeof endLine === "number";
48550
- try {
48551
- if (encoding === "base64") {
48552
- if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
48553
- const buf2 = await cliSimpleGit(repoAbsPath).showBuffer([blobRef]);
48554
- await yieldToEventLoop();
48555
- return {
48556
- content: buf2.toString("base64"),
48557
- size: buf2.length,
48558
- mimeType: guessMimeType(rel)
48559
- };
48560
- }
48561
- const buf = await cliSimpleGit(repoAbsPath).showBuffer([blobRef]);
48562
- await yieldToEventLoop();
48563
- const size = buf.length;
48564
- if (isBinaryBuffer(buf)) {
48565
- return { error: "Binary file cannot be displayed as text" };
48566
- }
48567
- if (hasRange) {
48568
- const ranged = await readGitBlobLineRangeAsync(buf, startLine, endLine, lineOffset, lineChunkSize);
48569
- return {
48570
- content: ranged.content,
48571
- totalLines: ranged.totalLines,
48572
- size,
48573
- ...ranged.lineOffset != null ? { lineOffset: ranged.lineOffset } : {}
48574
- };
48575
- }
48576
- const full = await readGitBlobUtf8FullAsync(buf);
48577
- return { content: full.content, totalLines: full.totalLines, size, mimeType: guessMimeType(rel) };
48578
- } catch (err) {
48579
- const message = err instanceof Error ? err.message : String(err);
48580
- if (/does not exist|bad revision|exists on disk, but not in/i.test(message)) {
48581
- return { error: "File not found in branch" };
48582
- }
48583
- return { error: message };
48584
- }
48585
- }
48586
-
48587
49663
  // src/files/browser/send-file-browser-message.ts
48588
49664
  function sendFileBrowserMessage(socket, e2ee, payload) {
48589
49665
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
@@ -48592,103 +49668,6 @@ function sendFileBrowserMessage(socket, e2ee, payload) {
48592
49668
  // src/files/read-file/resolve-file-cache-warmup.ts
48593
49669
  init_yield_to_event_loop();
48594
49670
  import path78 from "node:path";
48595
-
48596
- // src/files/read-file/resolve-file-cache.ts
48597
- import fs50 from "node:fs";
48598
- import path77 from "node:path";
48599
- var RESOLVE_CACHE_MAX = 4096;
48600
- var resolveCache = /* @__PURE__ */ new Map();
48601
- var warmedDirectories = /* @__PURE__ */ new Set();
48602
- function resolveCacheKey(sessionParentPath, relativePath) {
48603
- return `${sessionParentPath}\0${relativePath}`;
48604
- }
48605
- function warmedDirectoryKey(sessionParentPath, directoryRelativePath) {
48606
- const dir = directoryRelativePath.replace(/^\/+/, "") || ".";
48607
- return `${path77.resolve(sessionParentPath)}\0${dir}`;
48608
- }
48609
- function pathStaysUnderParent(filePath, sessionParentPath) {
48610
- const parent = path77.resolve(sessionParentPath);
48611
- const resolved = path77.resolve(filePath);
48612
- return resolved === parent || resolved.startsWith(`${parent}${path77.sep}`);
48613
- }
48614
- async function statMatchesCache(entry) {
48615
- try {
48616
- const stat3 = await fs50.promises.stat(entry.path);
48617
- return stat3.isFile() && stat3.mtimeMs === entry.mtimeMs && stat3.size === entry.size;
48618
- } catch {
48619
- return false;
48620
- }
48621
- }
48622
- function getResolveCacheEntry(cacheKey) {
48623
- return resolveCache.get(cacheKey);
48624
- }
48625
- function deleteResolveCacheEntry(cacheKey) {
48626
- resolveCache.delete(cacheKey);
48627
- }
48628
- function storeResolveCacheEntry(cacheKey, entry) {
48629
- if (resolveCache.size >= RESOLVE_CACHE_MAX) resolveCache.clear();
48630
- resolveCache.set(cacheKey, entry);
48631
- return entry;
48632
- }
48633
- function hasResolveCacheEntry(cacheKey) {
48634
- return resolveCache.has(cacheKey);
48635
- }
48636
- function markDirectoryWarmed(dirKey) {
48637
- if (warmedDirectories.has(dirKey)) return false;
48638
- warmedDirectories.add(dirKey);
48639
- return true;
48640
- }
48641
- function unmarkDirectoryWarmed(dirKey) {
48642
- warmedDirectories.delete(dirKey);
48643
- }
48644
-
48645
- // src/files/read-file/resolve-file-path.ts
48646
- init_ensure_under_cwd();
48647
- init_cwd();
48648
- import fs51 from "node:fs";
48649
- async function resolveFileForReadAsync(relativePath, sessionParentPath = getBridgeRoot()) {
48650
- const cacheKey = resolveCacheKey(sessionParentPath, relativePath);
48651
- const cached2 = getResolveCacheEntry(cacheKey);
48652
- if (cached2 != null && await statMatchesCache(cached2)) {
48653
- return { path: cached2.path, size: cached2.size };
48654
- }
48655
- if (cached2 != null) deleteResolveCacheEntry(cacheKey);
48656
- const resolved = ensureUnderCwd(relativePath, sessionParentPath);
48657
- if (!resolved) return { error: "Path is outside working directory" };
48658
- let real;
48659
- let size;
48660
- let mtimeMs;
48661
- try {
48662
- const lstat = await fs51.promises.lstat(resolved);
48663
- if (lstat.isSymbolicLink()) {
48664
- real = await fs51.promises.realpath(resolved);
48665
- if (!pathStaysUnderParent(real, sessionParentPath)) {
48666
- return { error: "Path is outside working directory" };
48667
- }
48668
- const stat3 = await fs51.promises.stat(real);
48669
- if (!stat3.isFile()) return { error: "Not a file" };
48670
- size = stat3.size;
48671
- mtimeMs = stat3.mtimeMs;
48672
- } else if (lstat.isFile()) {
48673
- real = resolved;
48674
- size = lstat.size;
48675
- mtimeMs = lstat.mtimeMs;
48676
- } else {
48677
- return { error: "Not a file" };
48678
- }
48679
- } catch (err) {
48680
- return { error: err instanceof Error ? err.message : String(err) };
48681
- }
48682
- const entry = storeResolveCacheEntry(cacheKey, { path: real, size, mtimeMs });
48683
- return { path: entry.path, size: entry.size };
48684
- }
48685
- async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
48686
- const result = await resolveFileForReadAsync(relativePath, sessionParentPath);
48687
- if ("error" in result) return result;
48688
- return result.path;
48689
- }
48690
-
48691
- // src/files/read-file/resolve-file-cache-warmup.ts
48692
49671
  var WARMED_DIRECTORY_TTL_MS = 6e4;
48693
49672
  async function warmDirectoryResolveCache(sessionParentPath, directoryRelativePath) {
48694
49673
  const { listDirAsync: listDirAsync2 } = await Promise.resolve().then(() => (init_list_dir(), list_dir_exports));
@@ -48730,7 +49709,7 @@ function scheduleResolveCacheWarmupForListedEntries(sessionParentPath, listRelat
48730
49709
 
48731
49710
  // src/files/browser/handle-file-browser-list.ts
48732
49711
  async function handleFileBrowserList(socket, e2ee, id, reqPath, sessionParentPath, gitScope) {
48733
- const result = gitScope ? await listGitTreeDirAsync(gitScope.repoAbsPath, gitScope.branch, reqPath) : await listDirAsync(reqPath, sessionParentPath);
49712
+ const result = await executeFileBrowserList({ reqPath, sessionParentPath, gitScope });
48734
49713
  if ("error" in result) {
48735
49714
  sendWsMessage(socket, { type: "file_browser_response", id, error: result.error });
48736
49715
  return;
@@ -48744,403 +49723,8 @@ async function handleFileBrowserList(socket, e2ee, id, reqPath, sessionParentPat
48744
49723
  // src/files/browser/handle-file-browser-read.ts
48745
49724
  import path79 from "node:path";
48746
49725
  init_cwd();
48747
-
48748
- // src/files/read-file/index.ts
48749
- init_cwd();
48750
- init_yield_to_event_loop();
48751
- import fs55 from "node:fs";
48752
-
48753
- // src/files/read-file/read-file-range-async.ts
48754
- init_yield_to_event_loop();
48755
- import fs52 from "node:fs";
48756
- import { StringDecoder as StringDecoder2 } from "node:string_decoder";
48757
-
48758
- // src/files/read-file/read-file-range-line-zero.ts
48759
- function createLineZeroStreamState(lineOffsetIn) {
48760
- return {
48761
- line0Accum: "",
48762
- skipLine0Chars: typeof lineOffsetIn === "number" ? lineOffsetIn : 0,
48763
- line0CharsReturned: 0
48764
- };
48765
- }
48766
- function shouldTrackLineZero(startLine, lineOffsetIn) {
48767
- return startLine === 0 || lineOffsetIn !== void 0;
48768
- }
48769
- function stripCarriageReturn(lineContent) {
48770
- return lineContent.endsWith("\r") ? lineContent.slice(0, -1) : lineContent;
48771
- }
48772
- function lineContentBeforeNewline(text, lineStart, newlineIndex) {
48773
- let lineEnd = newlineIndex;
48774
- if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
48775
- return text.slice(lineStart, lineEnd);
48776
- }
48777
- function buildLineZeroChunkResult(resultLines, fileSize, lineOffset, totalLines = 1) {
48778
- return {
48779
- content: resultLines.join("\n"),
48780
- size: fileSize,
48781
- lineOffset,
48782
- totalLines
48783
- };
48784
- }
48785
- function tryFinishLineZeroOnCompleteLine(state, lineContent, resultLines, fileSize, lineChunkSize, lineOffsetIn, endLine, currentLine) {
48786
- state.line0Accum += lineContent;
48787
- const totalLine0 = state.line0Accum.length;
48788
- if (state.skipLine0Chars > 0) {
48789
- if (totalLine0 <= state.skipLine0Chars) {
48790
- state.skipLine0Chars -= totalLine0;
48791
- state.line0Accum = "";
48792
- return currentLine + 1 > endLine ? "done" : "continue";
48793
- }
48794
- const from = state.skipLine0Chars;
48795
- const take = Math.min(lineChunkSize, totalLine0 - from);
48796
- resultLines.push(state.line0Accum.slice(from, from + take));
48797
- state.line0CharsReturned += take;
48798
- if (from + take < totalLine0) {
48799
- return buildLineZeroChunkResult(
48800
- resultLines,
48801
- fileSize,
48802
- (lineOffsetIn ?? 0) + state.line0CharsReturned
48803
- );
48804
- }
48805
- state.line0Accum = "";
48806
- state.skipLine0Chars = 0;
48807
- state.line0CharsReturned = 0;
48808
- return "continue";
48809
- }
48810
- if (totalLine0 > lineChunkSize) {
48811
- resultLines.push(state.line0Accum.slice(0, lineChunkSize));
48812
- return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
48813
- }
48814
- resultLines.push(state.line0Accum);
48815
- state.line0Accum = "";
48816
- return "continue";
48817
- }
48818
- function tryFinishLineZeroPartial(state, partialLine, resultLines, fileSize, lineChunkSize, lineOffsetIn) {
48819
- state.line0Accum += partialLine;
48820
- const totalLine0 = state.line0Accum.length;
48821
- if (state.skipLine0Chars > 0) {
48822
- if (totalLine0 <= state.skipLine0Chars) {
48823
- state.skipLine0Chars -= totalLine0;
48824
- state.line0Accum = "";
48825
- return null;
48826
- }
48827
- const from = state.skipLine0Chars;
48828
- const take = Math.min(lineChunkSize, totalLine0 - from);
48829
- resultLines.push(state.line0Accum.slice(from, from + take));
48830
- return buildLineZeroChunkResult(resultLines, fileSize, (lineOffsetIn ?? 0) + take);
48831
- }
48832
- if (totalLine0 > lineChunkSize) {
48833
- resultLines.push(state.line0Accum.slice(0, lineChunkSize));
48834
- return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
48835
- }
48836
- return null;
48837
- }
48838
- function finishLineZeroTail(state, tail, resultLines, fileSize, lineChunkSize, lineOffsetIn) {
48839
- if (tail.length === 0) return null;
48840
- state.line0Accum += stripCarriageReturn(tail);
48841
- const totalLine0 = state.line0Accum.length;
48842
- if (state.skipLine0Chars > 0) {
48843
- if (totalLine0 <= state.skipLine0Chars) {
48844
- return { content: resultLines.join("\n"), size: fileSize };
48845
- }
48846
- const from = state.skipLine0Chars;
48847
- const take = Math.min(lineChunkSize, totalLine0 - from);
48848
- resultLines.push(state.line0Accum.slice(from, from + take));
48849
- state.line0CharsReturned += take;
48850
- if (from + take < totalLine0) {
48851
- return buildLineZeroChunkResult(
48852
- resultLines,
48853
- fileSize,
48854
- (lineOffsetIn ?? 0) + state.line0CharsReturned
48855
- );
48856
- }
48857
- return null;
48858
- }
48859
- if (totalLine0 > lineChunkSize) {
48860
- resultLines.push(state.line0Accum.slice(0, lineChunkSize));
48861
- return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
48862
- }
48863
- resultLines.push(state.line0Accum);
48864
- return null;
48865
- }
48866
-
48867
- // src/files/read-file/read-file-range-async.ts
48868
- async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE, fileSizeIn) {
48869
- const fileSize = fileSizeIn ?? (await fs52.promises.stat(filePath)).size;
48870
- const fd = await fs52.promises.open(filePath, "r");
48871
- const bufSize = 64 * 1024;
48872
- const buf = Buffer.alloc(bufSize);
48873
- const decoder = new StringDecoder2("utf8");
48874
- let currentLine = 0;
48875
- const resultLines = [];
48876
- let partial2 = "";
48877
- let done = false;
48878
- const trackLineZero = shouldTrackLineZero(startLine, lineOffsetIn);
48879
- const lineZeroState = createLineZeroStreamState(lineOffsetIn);
48880
- let bytesSinceYield = 0;
48881
- try {
48882
- let position = 0;
48883
- while (!done) {
48884
- const { bytesRead } = await fd.read(buf, 0, bufSize, position);
48885
- if (bytesRead === 0) break;
48886
- position += bytesRead;
48887
- bytesSinceYield += bytesRead;
48888
- if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
48889
- await yieldToEventLoop();
48890
- bytesSinceYield = 0;
48891
- }
48892
- const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
48893
- partial2 = "";
48894
- let lineStart = 0;
48895
- for (let i = 0; i < text.length; i++) {
48896
- if (text[i] !== "\n") continue;
48897
- const lineContent2 = lineContentBeforeNewline(text, lineStart, i);
48898
- if (currentLine === 0 && trackLineZero) {
48899
- const lineZeroResult = tryFinishLineZeroOnCompleteLine(
48900
- lineZeroState,
48901
- lineContent2,
48902
- resultLines,
48903
- fileSize,
48904
- lineChunkSize,
48905
- lineOffsetIn,
48906
- endLine,
48907
- currentLine
48908
- );
48909
- if (lineZeroResult === "done") {
48910
- done = true;
48911
- break;
48912
- }
48913
- if (lineZeroResult !== "continue") return lineZeroResult;
48914
- currentLine++;
48915
- lineStart = i + 1;
48916
- if (currentLine > endLine) {
48917
- done = true;
48918
- break;
48919
- }
48920
- continue;
48921
- }
48922
- if (currentLine >= startLine && currentLine <= endLine) {
48923
- resultLines.push(lineContent2);
48924
- }
48925
- currentLine++;
48926
- lineStart = i + 1;
48927
- if (currentLine > endLine) {
48928
- done = true;
48929
- break;
48930
- }
48931
- }
48932
- if (done) continue;
48933
- const lineContent = text.slice(lineStart);
48934
- if (currentLine === 0 && trackLineZero) {
48935
- const partialResult = tryFinishLineZeroPartial(
48936
- lineZeroState,
48937
- lineContent,
48938
- resultLines,
48939
- fileSize,
48940
- lineChunkSize,
48941
- lineOffsetIn
48942
- );
48943
- if (partialResult != null) return partialResult;
48944
- } else {
48945
- partial2 = lineContent;
48946
- }
48947
- }
48948
- if (!done) {
48949
- const tail = partial2 + decoder.end();
48950
- if (currentLine === 0 && trackLineZero) {
48951
- const tailResult = finishLineZeroTail(
48952
- lineZeroState,
48953
- tail,
48954
- resultLines,
48955
- fileSize,
48956
- lineChunkSize,
48957
- lineOffsetIn
48958
- );
48959
- if (tailResult != null) return tailResult;
48960
- } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
48961
- resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
48962
- }
48963
- }
48964
- return { content: resultLines.join("\n"), size: fileSize };
48965
- } finally {
48966
- await fd.close();
48967
- }
48968
- }
48969
-
48970
- // src/files/read-file/read-small-file-range-async.ts
48971
- import fs53 from "node:fs";
48972
- var SMALL_FILE_RANGE_MAX_BYTES = 512 * 1024;
48973
- function sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize) {
48974
- if (startLine === 0 && lineOffsetIn == null && lines[0] != null && lines[0].length > lineChunkSize) {
48975
- return {
48976
- content: lines[0].slice(0, lineChunkSize),
48977
- size: fileSize,
48978
- lineOffset: lineChunkSize,
48979
- totalLines: lines.length
48980
- };
48981
- }
48982
- if (startLine === 0 && typeof lineOffsetIn === "number") {
48983
- const line0 = lines[0] ?? "";
48984
- const from = lineOffsetIn;
48985
- const take = Math.min(lineChunkSize, Math.max(0, line0.length - from));
48986
- return {
48987
- content: line0.slice(from, from + take),
48988
- size: fileSize,
48989
- ...from + take < line0.length ? { lineOffset: from + take, totalLines: lines.length } : { totalLines: lines.length }
48990
- };
48991
- }
48992
- const slice = lines.slice(startLine, endLine + 1);
48993
- return {
48994
- content: slice.join("\n"),
48995
- size: fileSize,
48996
- totalLines: lines.length
48997
- };
48998
- }
48999
- async function readSmallFileRangeAsync(filePath, fileSize, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
49000
- const raw = await fs53.promises.readFile(filePath, "utf8");
49001
- const lines = raw.split(/\r?\n/);
49002
- return sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize);
49003
- }
49004
-
49005
- // src/files/read-file/read-file-buffer-full-async.ts
49006
- init_yield_to_event_loop();
49007
- import fs54 from "node:fs";
49008
- var READ_CHUNK_BYTES = 256 * 1024;
49009
- async function readFileBufferFullAsync(filePath) {
49010
- const stat3 = await fs54.promises.stat(filePath);
49011
- const fd = await fs54.promises.open(filePath, "r");
49012
- const chunks = [];
49013
- let position = 0;
49014
- let bytesSinceYield = 0;
49015
- try {
49016
- while (position < stat3.size) {
49017
- const buf = Buffer.alloc(Math.min(READ_CHUNK_BYTES, stat3.size - position));
49018
- const { bytesRead } = await fd.read(buf, 0, buf.length, position);
49019
- if (bytesRead === 0) break;
49020
- chunks.push(buf.subarray(0, bytesRead));
49021
- position += bytesRead;
49022
- bytesSinceYield += bytesRead;
49023
- if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
49024
- await yieldToEventLoop();
49025
- bytesSinceYield = 0;
49026
- }
49027
- }
49028
- } finally {
49029
- await fd.close();
49030
- }
49031
- return { buffer: Buffer.concat(chunks), size: stat3.size };
49032
- }
49033
-
49034
- // src/files/read-file/read-file-full-async.ts
49035
- async function readFileFullAsync(filePath) {
49036
- const { buffer, size } = await readFileBufferFullAsync(filePath);
49037
- const raw = buffer.toString("utf8");
49038
- const lines = raw.split(/\r?\n/);
49039
- return { content: raw, totalLines: lines.length, size };
49040
- }
49041
-
49042
- // src/files/read-file/read-file-binary-full-async.ts
49043
- async function readFileBinaryFullAsync(filePath) {
49044
- const { buffer, size } = await readFileBufferFullAsync(filePath);
49045
- return {
49046
- content: buffer.toString("base64"),
49047
- size,
49048
- mimeType: guessMimeType(filePath)
49049
- };
49050
- }
49051
-
49052
- // src/files/read-file/index.ts
49053
- async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, sessionParentPath = getBridgeRoot(), encoding = "utf8", options) {
49054
- if (!options?.skipInitialYield) {
49055
- await yieldToEventLoop();
49056
- }
49057
- try {
49058
- let resolvedPath;
49059
- let fileSize;
49060
- if (options?.useResolvedSize) {
49061
- const resolved = await resolveFileForReadAsync(relativePath, sessionParentPath);
49062
- if ("error" in resolved) return resolved;
49063
- resolvedPath = resolved.path;
49064
- fileSize = resolved.size;
49065
- } else {
49066
- const result = await resolveFilePathAsync(relativePath, sessionParentPath);
49067
- if (typeof result === "object") return result;
49068
- resolvedPath = result;
49069
- }
49070
- const hasRange = typeof startLine === "number" && typeof endLine === "number";
49071
- if (encoding === "base64") {
49072
- if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
49073
- const read2 = await readFileBinaryFullAsync(resolvedPath);
49074
- return { ...read2, resolvedPath };
49075
- }
49076
- if (hasRange) {
49077
- if (fileSize == null) {
49078
- const stat3 = await fs55.promises.stat(resolvedPath);
49079
- fileSize = stat3.size;
49080
- }
49081
- const read2 = fileSize <= SMALL_FILE_RANGE_MAX_BYTES ? await readSmallFileRangeAsync(
49082
- resolvedPath,
49083
- fileSize,
49084
- startLine,
49085
- endLine,
49086
- lineOffset,
49087
- lineChunkSize
49088
- ) : await readFileRangeAsync(
49089
- resolvedPath,
49090
- startLine,
49091
- endLine,
49092
- lineOffset,
49093
- lineChunkSize,
49094
- fileSize
49095
- );
49096
- return read2;
49097
- }
49098
- const read = await readFileFullAsync(resolvedPath);
49099
- return { ...read, resolvedPath };
49100
- } catch (err) {
49101
- return { error: err instanceof Error ? err.message : String(err) };
49102
- }
49103
- }
49104
-
49105
- // src/files/browser/file-browser-read-queue.ts
49106
- var readChain = Promise.resolve();
49107
- function withFileBrowserReadQueue(fn) {
49108
- const next = readChain.then(() => fn());
49109
- readChain = next.then(
49110
- () => void 0,
49111
- () => void 0
49112
- );
49113
- return next;
49114
- }
49115
-
49116
- // src/files/browser/handle-file-browser-read.ts
49117
49726
  async function handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPath, gitScope) {
49118
- const startLine = typeof msg.startLine === "number" ? msg.startLine : void 0;
49119
- const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
49120
- const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
49121
- const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
49122
- const encoding = msg.encoding === "base64" ? "base64" : "utf8";
49123
- const result = gitScope ? await readGitTreeFileAsync(
49124
- gitScope.repoAbsPath,
49125
- gitScope.branch,
49126
- reqPath,
49127
- startLine,
49128
- endLine,
49129
- lineOffset,
49130
- lineChunkSize,
49131
- encoding
49132
- ) : await withFileBrowserReadQueue(
49133
- () => readFileAsync(
49134
- reqPath,
49135
- startLine,
49136
- endLine,
49137
- lineOffset,
49138
- lineChunkSize,
49139
- sessionParentPath,
49140
- encoding,
49141
- { skipInitialYield: true, useResolvedSize: true }
49142
- )
49143
- );
49727
+ const result = await executeFileBrowserRead({ msg, reqPath, sessionParentPath, gitScope });
49144
49728
  if ("error" in result) {
49145
49729
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
49146
49730
  return;
@@ -49162,15 +49746,6 @@ async function handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPa
49162
49746
  }
49163
49747
  }
49164
49748
 
49165
- // src/files/browser/resolve-git-branch-scope.ts
49166
- function resolveGitBranchScope(msg) {
49167
- const gitBranchScope = msg.source === "git_branch" && typeof msg.repoRelPath === "string" && typeof msg.branch === "string" && msg.branch.trim().length > 0;
49168
- if (!gitBranchScope) return null;
49169
- const repoAbsPath = resolveRepoAbsPathFromBridge(msg.repoRelPath);
49170
- if (!repoAbsPath) return null;
49171
- return { repoAbsPath, branch: msg.branch.trim() };
49172
- }
49173
-
49174
49749
  // src/files/browser/index.ts
49175
49750
  function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
49176
49751
  beginFileBrowserRequest();
@@ -49360,14 +49935,14 @@ function codeNavSessionKey(sessionId, sessionParentPath) {
49360
49935
  // src/code-nav/handlers/wait-for-symbol-index-ready.ts
49361
49936
  var CODE_NAV_INDEX_READY_WAIT_MS = 75;
49362
49937
  var CODE_NAV_INDEX_READY_POLL_MS = 15;
49363
- function delay2(ms) {
49938
+ function delay3(ms) {
49364
49939
  return new Promise((resolve37) => setTimeout(resolve37, ms));
49365
49940
  }
49366
49941
  async function waitForSymbolIndexReady(sessionParentPath, reqPath, maxWaitMs = CODE_NAV_INDEX_READY_WAIT_MS) {
49367
49942
  const deadline = Date.now() + maxWaitMs;
49368
49943
  do {
49369
49944
  if (await symbolIndexFileIsReadyAsync(sessionParentPath, reqPath)) return true;
49370
- await delay2(CODE_NAV_INDEX_READY_POLL_MS);
49945
+ await delay3(CODE_NAV_INDEX_READY_POLL_MS);
49371
49946
  } while (Date.now() < deadline);
49372
49947
  return await symbolIndexFileIsReadyAsync(sessionParentPath, reqPath);
49373
49948
  }
@@ -50293,27 +50868,6 @@ function createMainBridgeCloseHandler(params, connect) {
50293
50868
  };
50294
50869
  }
50295
50870
 
50296
- // src/connection/report-git-repos.ts
50297
- function reportGitRepos(getWs, log2) {
50298
- setImmediate(() => {
50299
- discoverGitRepos().then((repos) => {
50300
- if (repos.length > 0) {
50301
- const socket = getWs();
50302
- if (socket) {
50303
- sendWsMessage(socket, {
50304
- type: "git_repos",
50305
- repos: repos.map((r) => ({ absolutePath: r.absolutePath, remoteUrl: r.remoteUrl }))
50306
- });
50307
- }
50308
- }
50309
- }).catch((err) => {
50310
- log2(
50311
- `[Bridge service] Git repository discovery failed: ${err instanceof Error ? err.message : String(err)}`
50312
- );
50313
- });
50314
- });
50315
- }
50316
-
50317
50871
  // src/connection/main-bridge-ws-open-handler.ts
50318
50872
  function createMainBridgeOpenHandler(params) {
50319
50873
  const {
@@ -50338,14 +50892,7 @@ function createMainBridgeOpenHandler(params) {
50338
50892
  }
50339
50893
  const socket = getWs();
50340
50894
  if (socket) {
50341
- sendWsMessage(socket, {
50342
- type: "identify",
50343
- role: "cli",
50344
- cliVersion: CLI_VERSION,
50345
- bridgeRootPath: identifyReportedPaths.bridgeRootPath,
50346
- worktreesRootPath: identifyReportedPaths.worktreesRootPath,
50347
- ...e2ee ? { e: e2ee.handshake } : {}
50348
- });
50895
+ sendBridgeIdentify(socket, { identifyReportedPaths, e2ee });
50349
50896
  reportGitRepos(getWs, logFn);
50350
50897
  try {
50351
50898
  onBridgeSocketOpen?.();
@@ -50470,6 +51017,7 @@ async function createBridgeConnection(options) {
50470
51017
  connect();
50471
51018
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeRoot());
50472
51019
  const bridgeAccess = await runtime.bridgeAccessPromise;
51020
+ const localShortcut = await runtime.localShortcutPromise;
50473
51021
  return {
50474
51022
  close: async () => {
50475
51023
  requestCliImmediateShutdown();
@@ -50482,6 +51030,7 @@ async function createBridgeConnection(options) {
50482
51030
  runtime.logFn
50483
51031
  );
50484
51032
  await bridgeAccess.close();
51033
+ await localShortcut.close();
50485
51034
  }
50486
51035
  };
50487
51036
  }