@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/index.js CHANGED
@@ -2237,12 +2237,12 @@ var require_websocket = __commonJS({
2237
2237
  "use strict";
2238
2238
  var EventEmitter2 = __require("events");
2239
2239
  var https2 = __require("https");
2240
- var http2 = __require("http");
2240
+ var http3 = __require("http");
2241
2241
  var net = __require("net");
2242
2242
  var tls = __require("tls");
2243
- var { randomBytes: randomBytes2, createHash: createHash2 } = __require("crypto");
2243
+ var { randomBytes: randomBytes3, createHash: createHash2 } = __require("crypto");
2244
2244
  var { Duplex, Readable: Readable2 } = __require("stream");
2245
- var { URL: URL2 } = __require("url");
2245
+ var { URL: URL3 } = __require("url");
2246
2246
  var PerMessageDeflate = require_permessage_deflate();
2247
2247
  var Receiver2 = require_receiver();
2248
2248
  var Sender2 = require_sender();
@@ -2735,11 +2735,11 @@ var require_websocket = __commonJS({
2735
2735
  );
2736
2736
  }
2737
2737
  let parsedUrl;
2738
- if (address instanceof URL2) {
2738
+ if (address instanceof URL3) {
2739
2739
  parsedUrl = address;
2740
2740
  } else {
2741
2741
  try {
2742
- parsedUrl = new URL2(address);
2742
+ parsedUrl = new URL3(address);
2743
2743
  } catch (e) {
2744
2744
  throw new SyntaxError(`Invalid URL: ${address}`);
2745
2745
  }
@@ -2770,8 +2770,8 @@ var require_websocket = __commonJS({
2770
2770
  }
2771
2771
  }
2772
2772
  const defaultPort = isSecure ? 443 : 80;
2773
- const key = randomBytes2(16).toString("base64");
2774
- const request = isSecure ? https2.request : http2.request;
2773
+ const key = randomBytes3(16).toString("base64");
2774
+ const request = isSecure ? https2.request : http3.request;
2775
2775
  const protocolSet = /* @__PURE__ */ new Set();
2776
2776
  let perMessageDeflate;
2777
2777
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -2876,7 +2876,7 @@ var require_websocket = __commonJS({
2876
2876
  req.abort();
2877
2877
  let addr;
2878
2878
  try {
2879
- addr = new URL2(location, address);
2879
+ addr = new URL3(location, address);
2880
2880
  } catch (e) {
2881
2881
  const err = new SyntaxError(`Invalid URL: ${location}`);
2882
2882
  emitErrorAndClose(websocket, err);
@@ -3265,7 +3265,7 @@ var require_websocket_server = __commonJS({
3265
3265
  "../../node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js"(exports, module) {
3266
3266
  "use strict";
3267
3267
  var EventEmitter2 = __require("events");
3268
- var http2 = __require("http");
3268
+ var http3 = __require("http");
3269
3269
  var { Duplex } = __require("stream");
3270
3270
  var { createHash: createHash2 } = __require("crypto");
3271
3271
  var extension = require_extension();
@@ -3340,8 +3340,8 @@ var require_websocket_server = __commonJS({
3340
3340
  );
3341
3341
  }
3342
3342
  if (options.port != null) {
3343
- this._server = http2.createServer((req, res) => {
3344
- const body = http2.STATUS_CODES[426];
3343
+ this._server = http3.createServer((req, res) => {
3344
+ const body = http3.STATUS_CODES[426];
3345
3345
  res.writeHead(426, {
3346
3346
  "Content-Length": body.length,
3347
3347
  "Content-Type": "text/plain"
@@ -3628,7 +3628,7 @@ var require_websocket_server = __commonJS({
3628
3628
  this.destroy();
3629
3629
  }
3630
3630
  function abortHandshake(socket, code, message, headers) {
3631
- message = message || http2.STATUS_CODES[code];
3631
+ message = message || http3.STATUS_CODES[code];
3632
3632
  headers = {
3633
3633
  Connection: "close",
3634
3634
  "Content-Type": "text/html",
@@ -3637,7 +3637,7 @@ var require_websocket_server = __commonJS({
3637
3637
  };
3638
3638
  socket.once("finish", socket.destroy);
3639
3639
  socket.end(
3640
- `HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
3640
+ `HTTP/1.1 ${code} ${http3.STATUS_CODES[code]}\r
3641
3641
  ` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
3642
3642
  );
3643
3643
  }
@@ -22669,11 +22669,11 @@ var require_dist2 = __commonJS({
22669
22669
  });
22670
22670
 
22671
22671
  // src/files/ensure-under-cwd.ts
22672
- import path73 from "node:path";
22672
+ import path72 from "node:path";
22673
22673
  function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
22674
- const normalized = path73.normalize(relativePath).replace(/^(\.\/)+/, "");
22675
- const resolved = path73.resolve(cwd, normalized);
22676
- if (!resolved.startsWith(cwd + path73.sep) && resolved !== cwd) {
22674
+ const normalized = path72.normalize(relativePath).replace(/^(\.\/)+/, "");
22675
+ const resolved = path72.resolve(cwd, normalized);
22676
+ if (!resolved.startsWith(cwd + path72.sep) && resolved !== cwd) {
22677
22677
  return null;
22678
22678
  }
22679
22679
  return resolved;
@@ -22695,15 +22695,15 @@ var init_types2 = __esm({
22695
22695
  });
22696
22696
 
22697
22697
  // src/files/list-dir/map-dir-entry.ts
22698
- import path74 from "node:path";
22699
- import fs47 from "node:fs";
22698
+ import path73 from "node:path";
22699
+ import fs45 from "node:fs";
22700
22700
  async function mapDirEntry(d, relativePath, resolved) {
22701
- const entryPath = path74.join(relativePath || ".", d.name).replace(/\\/g, "/");
22702
- const fullPath = path74.join(resolved, d.name);
22701
+ const entryPath = path73.join(relativePath || ".", d.name).replace(/\\/g, "/");
22702
+ const fullPath = path73.join(resolved, d.name);
22703
22703
  let isDir = d.isDirectory();
22704
22704
  if (d.isSymbolicLink()) {
22705
22705
  try {
22706
- const targetStat = await fs47.promises.stat(fullPath);
22706
+ const targetStat = await fs45.promises.stat(fullPath);
22707
22707
  isDir = targetStat.isDirectory();
22708
22708
  } catch {
22709
22709
  isDir = false;
@@ -22740,7 +22740,7 @@ var list_dir_exports = {};
22740
22740
  __export(list_dir_exports, {
22741
22741
  listDirAsync: () => listDirAsync
22742
22742
  });
22743
- import fs48 from "node:fs";
22743
+ import fs46 from "node:fs";
22744
22744
  async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
22745
22745
  await yieldToEventLoop();
22746
22746
  const resolved = ensureUnderCwd(relativePath || ".", sessionParentPath);
@@ -22748,7 +22748,7 @@ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
22748
22748
  return { error: "Path is outside working directory" };
22749
22749
  }
22750
22750
  try {
22751
- const names = await fs48.promises.readdir(resolved, { withFileTypes: true });
22751
+ const names = await fs46.promises.readdir(resolved, { withFileTypes: true });
22752
22752
  const entries = [];
22753
22753
  for (let i = 0; i < names.length; i++) {
22754
22754
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
@@ -25326,7 +25326,7 @@ function installBridgeProcessResilience() {
25326
25326
  }
25327
25327
 
25328
25328
  // src/cli-version.ts
25329
- var CLI_VERSION = "0.1.64".length > 0 ? "0.1.64" : "0.0.0-dev";
25329
+ var CLI_VERSION = "0.1.66".length > 0 ? "0.1.66" : "0.0.0-dev";
25330
25330
 
25331
25331
  // src/connection/heartbeat/constants.ts
25332
25332
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -26185,7 +26185,7 @@ function clearMainBridgeReconnectQuietOnOpen(state, log2) {
26185
26185
  function scheduleMainBridgeReconnect(state, connect, log2, closeMeta) {
26186
26186
  if (state.closedByUser || state.currentWs != null) return;
26187
26187
  const meta = closeMeta ?? state.lastReconnectCloseMeta ?? void 0;
26188
- const delay3 = applyTieredReconnectPlanAndLog(
26188
+ const delay4 = applyTieredReconnectPlanAndLog(
26189
26189
  state.mainOutage,
26190
26190
  log2,
26191
26191
  BRIDGE_SERVICE_LABEL,
@@ -26194,7 +26194,7 @@ function scheduleMainBridgeReconnect(state, connect, log2, closeMeta) {
26194
26194
  meta?.reason
26195
26195
  );
26196
26196
  armReconnectDelayTimer({
26197
- delayMs: delay3,
26197
+ delayMs: delay4,
26198
26198
  bumpAttempt: () => {
26199
26199
  state.reconnectAttempt += 1;
26200
26200
  },
@@ -26396,10 +26396,10 @@ function runPendingAuth(options) {
26396
26396
  }
26397
26397
  if (resolved) return;
26398
26398
  beginDeferredPendingCloseLog(code, reason);
26399
- const delay3 = pendingAuthReconnectDelayMs(reconnectAttempt);
26399
+ const delay4 = pendingAuthReconnectDelayMs(reconnectAttempt);
26400
26400
  reconnectAttempt += 1;
26401
26401
  if (signInQuiet.verboseLogs) {
26402
- const delayLabel = formatReconnectDelayForLog(delay3);
26402
+ const delayLabel = formatReconnectDelayForLog(delay4);
26403
26403
  logFn(
26404
26404
  `[Bridge service] Next sign-in connection attempt in ${delayLabel} (attempt ${reconnectAttempt}).`
26405
26405
  );
@@ -26407,7 +26407,7 @@ function runPendingAuth(options) {
26407
26407
  reconnectTimeout = setTimeout(() => {
26408
26408
  reconnectTimeout = null;
26409
26409
  connect();
26410
- }, delay3);
26410
+ }, delay4);
26411
26411
  },
26412
26412
  onError: (err) => logCliWebSocketError(logFn, "[Bridge service]", err, "while waiting for sign-in"),
26413
26413
  onMessage: (data) => {
@@ -35877,7 +35877,9 @@ function createBridgePreviewStack(options) {
35877
35877
  };
35878
35878
  const identifyReportedPaths = {
35879
35879
  bridgeRootPath: path38.resolve(getBridgeRoot()),
35880
- worktreesRootPath: path38.resolve(options.worktreesRootPath)
35880
+ worktreesRootPath: path38.resolve(options.worktreesRootPath),
35881
+ localShortcutPort: 0,
35882
+ localShortcutToken: ""
35881
35883
  };
35882
35884
  return { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths };
35883
35885
  }
@@ -36236,7 +36238,7 @@ function createFirehoseReconnectScheduler(ctx, reconnect) {
36236
36238
  function scheduleFirehoseRetryAfterDrop(closeMeta) {
36237
36239
  if (state.closedByUser) return;
36238
36240
  const meta = closeMeta ?? state.lastFirehoseReconnectCloseMeta ?? void 0;
36239
- const delay3 = applyTieredReconnectPlanAndLog(
36241
+ const delay4 = applyTieredReconnectPlanAndLog(
36240
36242
  state.firehoseOutage,
36241
36243
  logFn,
36242
36244
  PREVIEW_TUNNEL_SERVICE_LABEL,
@@ -36245,7 +36247,7 @@ function createFirehoseReconnectScheduler(ctx, reconnect) {
36245
36247
  meta?.reason
36246
36248
  );
36247
36249
  armReconnectDelayTimer({
36248
- delayMs: delay3,
36250
+ delayMs: delay4,
36249
36251
  bumpAttempt: () => {
36250
36252
  state.firehoseReconnectAttempt += 1;
36251
36253
  },
@@ -36349,6 +36351,208 @@ function createOnBridgeIdentified(opts) {
36349
36351
  };
36350
36352
  }
36351
36353
 
36354
+ // src/connection/create-bridge-identified-with-heartbeat.ts
36355
+ function createBridgeIdentifiedWithHeartbeat(params) {
36356
+ const { bridgeHeartbeat, log: log2, ...identifiedOpts } = params;
36357
+ const onBridgeIdentified = createOnBridgeIdentified({ ...identifiedOpts, logFn: log2 });
36358
+ return (msg) => {
36359
+ onBridgeIdentified(msg);
36360
+ bridgeHeartbeat.start();
36361
+ };
36362
+ }
36363
+
36364
+ // src/connection/heartbeat/build-heartbeat-message.ts
36365
+ function buildBridgeHeartbeatMessage(seq, meanRttMs2) {
36366
+ return meanRttMs2 !== void 0 && Number.isFinite(meanRttMs2) ? { t: "h", s: seq, m: Math.round(meanRttMs2) } : { t: "h", s: seq };
36367
+ }
36368
+
36369
+ // src/connection/heartbeat/heartbeat-seq.ts
36370
+ function createHeartbeatSeqCursor() {
36371
+ let seqCursor = -1;
36372
+ return {
36373
+ next() {
36374
+ seqCursor = seqCursor >= BRIDGE_HEARTBEAT_SEQ_MAX ? 0 : seqCursor + 1;
36375
+ return seqCursor;
36376
+ },
36377
+ reset() {
36378
+ seqCursor = -1;
36379
+ }
36380
+ };
36381
+ }
36382
+
36383
+ // src/connection/heartbeat/handle-pending-heartbeat-miss.ts
36384
+ function handlePendingHeartbeatMiss(params) {
36385
+ const { awaitingSeq, missed, log: log2, ws, onReconnect } = params;
36386
+ if (awaitingSeq === null) return { missed, stopTick: false };
36387
+ const nextMissed = missed + 1;
36388
+ if (nextMissed < BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT) {
36389
+ return { missed: nextMissed, stopTick: false };
36390
+ }
36391
+ try {
36392
+ log2("[Bridge service] Heartbeat missed repeatedly; reconnecting\u2026");
36393
+ } catch {
36394
+ }
36395
+ onReconnect();
36396
+ safeCloseWebSocket(ws);
36397
+ return { missed: 0, stopTick: true };
36398
+ }
36399
+
36400
+ // src/connection/heartbeat/on-every-n-ticks.ts
36401
+ function createEveryNTicksRunner(onEveryNTicks) {
36402
+ let tickCount = 0;
36403
+ return {
36404
+ reset() {
36405
+ tickCount = 0;
36406
+ },
36407
+ onTick() {
36408
+ if (!onEveryNTicks || onEveryNTicks.n <= 0) return;
36409
+ tickCount++;
36410
+ if (tickCount % onEveryNTicks.n !== 0) return;
36411
+ try {
36412
+ onEveryNTicks.fn();
36413
+ } catch {
36414
+ }
36415
+ }
36416
+ };
36417
+ }
36418
+
36419
+ // src/connection/heartbeat/process-heartbeat-ack.ts
36420
+ function processHeartbeatAck(params) {
36421
+ const { seq, awaitingSeq, sentAtMs, recordRtt } = params;
36422
+ if (awaitingSeq === null) return { awaitingSeq, missed: 0 };
36423
+ if (!Number.isFinite(seq)) return { awaitingSeq, missed: 0 };
36424
+ const ack = Math.trunc(seq);
36425
+ const pending = awaitingSeq;
36426
+ if (ack < pending) return { awaitingSeq, missed: 0 };
36427
+ if (ack === pending) {
36428
+ recordRtt(Date.now() - sentAtMs);
36429
+ }
36430
+ return { awaitingSeq: null, missed: 0 };
36431
+ }
36432
+
36433
+ // src/connection/heartbeat/rtt-samples.ts
36434
+ function meanRttMs(samples) {
36435
+ if (samples.length === 0) return void 0;
36436
+ let sum = 0;
36437
+ for (const x of samples) sum += x;
36438
+ return sum / samples.length;
36439
+ }
36440
+ function createRttSampleBuffer(maxSamples = BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX) {
36441
+ const samples = [];
36442
+ return {
36443
+ push(rttMs) {
36444
+ if (!Number.isFinite(rttMs) || rttMs < 0 || rttMs >= 6e5) return;
36445
+ samples.push(rttMs);
36446
+ if (samples.length > maxSamples) {
36447
+ samples.splice(0, samples.length - maxSamples);
36448
+ }
36449
+ },
36450
+ clear() {
36451
+ samples.length = 0;
36452
+ },
36453
+ mean() {
36454
+ return meanRttMs(samples);
36455
+ }
36456
+ };
36457
+ }
36458
+
36459
+ // src/connection/heartbeat/controller.ts
36460
+ function createBridgeHeartbeatController(params) {
36461
+ const { getWs, log: log2, onEveryNTicks } = params;
36462
+ let interval = null;
36463
+ let awaitingSeq = null;
36464
+ let sentAtMs = 0;
36465
+ let missed = 0;
36466
+ const seqCursor = createHeartbeatSeqCursor();
36467
+ const rttSamples = createRttSampleBuffer();
36468
+ const everyNTicks = createEveryNTicksRunner(onEveryNTicks);
36469
+ function clearTimer() {
36470
+ if (interval != null) {
36471
+ clearInterval(interval);
36472
+ interval = null;
36473
+ }
36474
+ }
36475
+ function resetPendingState() {
36476
+ awaitingSeq = null;
36477
+ missed = 0;
36478
+ rttSamples.clear();
36479
+ }
36480
+ function tick() {
36481
+ const ws = getWs();
36482
+ if (!ws || ws.readyState !== wrapper_default.OPEN) return;
36483
+ const missResult = handlePendingHeartbeatMiss({
36484
+ awaitingSeq,
36485
+ missed,
36486
+ log: log2,
36487
+ ws,
36488
+ onReconnect: () => {
36489
+ clearTimer();
36490
+ resetPendingState();
36491
+ }
36492
+ });
36493
+ missed = missResult.missed;
36494
+ if (missResult.stopTick) return;
36495
+ const seq = seqCursor.next();
36496
+ sendWsMessage(ws, buildBridgeHeartbeatMessage(seq, rttSamples.mean()));
36497
+ awaitingSeq = seq;
36498
+ sentAtMs = Date.now();
36499
+ everyNTicks.onTick();
36500
+ }
36501
+ return {
36502
+ start() {
36503
+ clearTimer();
36504
+ everyNTicks.reset();
36505
+ resetPendingState();
36506
+ seqCursor.reset();
36507
+ interval = setInterval(tick, BRIDGE_APP_HEARTBEAT_INTERVAL_MS);
36508
+ },
36509
+ stop() {
36510
+ clearTimer();
36511
+ resetPendingState();
36512
+ },
36513
+ onAck(seq) {
36514
+ const result = processHeartbeatAck({
36515
+ seq,
36516
+ awaitingSeq,
36517
+ sentAtMs,
36518
+ recordRtt: rttSamples.push
36519
+ });
36520
+ awaitingSeq = result.awaitingSeq;
36521
+ missed = result.missed;
36522
+ }
36523
+ };
36524
+ }
36525
+
36526
+ // src/connection/send-bridge-identify.ts
36527
+ function sendBridgeIdentify(ws, params) {
36528
+ if (!ws) return;
36529
+ const { identifyReportedPaths, e2ee } = params;
36530
+ sendWsMessage(ws, {
36531
+ type: "identify",
36532
+ role: "cli",
36533
+ cliVersion: CLI_VERSION,
36534
+ bridgeRootPath: identifyReportedPaths.bridgeRootPath,
36535
+ worktreesRootPath: identifyReportedPaths.worktreesRootPath,
36536
+ localShortcutPort: identifyReportedPaths.localShortcutPort,
36537
+ localShortcutToken: identifyReportedPaths.localShortcutToken,
36538
+ ...e2ee ? { e: e2ee.handshake } : {}
36539
+ });
36540
+ }
36541
+ var BRIDGE_IDENTIFY_REFRESH_EVERY_TICKS = 6;
36542
+
36543
+ // src/connection/create-bridge-identify-heartbeat.ts
36544
+ function createBridgeIdentifyHeartbeat(params) {
36545
+ const { getWs, log: log2, identifyReportedPaths, e2ee } = params;
36546
+ return createBridgeHeartbeatController({
36547
+ getWs,
36548
+ log: log2,
36549
+ onEveryNTicks: {
36550
+ n: BRIDGE_IDENTIFY_REFRESH_EVERY_TICKS,
36551
+ fn: () => sendBridgeIdentify(getWs(), { identifyReportedPaths, e2ee })
36552
+ }
36553
+ });
36554
+ }
36555
+
36352
36556
  // src/connection/create-bridge-message-deps.ts
36353
36557
  function createBridgeMessageDeps(params) {
36354
36558
  const {
@@ -36515,98 +36719,11 @@ function createReportAutoDetectedAgents(getWs, logFn) {
36515
36719
  };
36516
36720
  }
36517
36721
 
36518
- // src/connection/heartbeat/controller.ts
36519
- function meanRttMs(samples) {
36520
- if (samples.length === 0) return void 0;
36521
- let sum = 0;
36522
- for (const x of samples) sum += x;
36523
- return sum / samples.length;
36524
- }
36525
- function createBridgeHeartbeatController(params) {
36526
- const { getWs, log: log2 } = params;
36527
- let interval = null;
36528
- let seqCursor = -1;
36529
- let awaitingSeq = null;
36530
- let sentAtMs = 0;
36531
- let missed = 0;
36532
- const rttSamples = [];
36533
- function clearTimer() {
36534
- if (interval != null) {
36535
- clearInterval(interval);
36536
- interval = null;
36537
- }
36538
- }
36539
- function nextSeq() {
36540
- seqCursor = seqCursor >= BRIDGE_HEARTBEAT_SEQ_MAX ? 0 : seqCursor + 1;
36541
- return seqCursor;
36542
- }
36543
- function tick() {
36544
- const ws = getWs();
36545
- if (!ws || ws.readyState !== wrapper_default.OPEN) return;
36546
- if (awaitingSeq !== null) {
36547
- missed++;
36548
- if (missed >= BRIDGE_HEARTBEAT_MISSED_ACKS_BEFORE_RECONNECT) {
36549
- try {
36550
- log2("[Bridge service] Heartbeat missed repeatedly; reconnecting\u2026");
36551
- } catch {
36552
- }
36553
- clearTimer();
36554
- awaitingSeq = null;
36555
- missed = 0;
36556
- rttSamples.length = 0;
36557
- safeCloseWebSocket(ws);
36558
- return;
36559
- }
36560
- }
36561
- const seq = nextSeq();
36562
- const mean = meanRttMs(rttSamples);
36563
- const payload = mean !== void 0 && Number.isFinite(mean) ? { t: "h", s: seq, m: Math.round(mean) } : { t: "h", s: seq };
36564
- sendWsMessage(ws, payload);
36565
- awaitingSeq = seq;
36566
- sentAtMs = Date.now();
36567
- }
36568
- return {
36569
- start() {
36570
- clearTimer();
36571
- awaitingSeq = null;
36572
- missed = 0;
36573
- seqCursor = -1;
36574
- rttSamples.length = 0;
36575
- interval = setInterval(tick, BRIDGE_APP_HEARTBEAT_INTERVAL_MS);
36576
- },
36577
- stop() {
36578
- clearTimer();
36579
- awaitingSeq = null;
36580
- missed = 0;
36581
- rttSamples.length = 0;
36582
- },
36583
- onAck(seq) {
36584
- if (awaitingSeq === null) return;
36585
- if (!Number.isFinite(seq)) return;
36586
- const ack = Math.trunc(seq);
36587
- const pending = awaitingSeq;
36588
- if (ack < pending) return;
36589
- if (ack === pending) {
36590
- const rtt = Date.now() - sentAtMs;
36591
- if (Number.isFinite(rtt) && rtt >= 0 && rtt < 6e5) {
36592
- rttSamples.push(rtt);
36593
- if (rttSamples.length > BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX) {
36594
- rttSamples.splice(0, rttSamples.length - BRIDGE_HEARTBEAT_RTT_SAMPLE_MAX);
36595
- }
36596
- }
36597
- }
36598
- awaitingSeq = null;
36599
- missed = 0;
36600
- }
36601
- };
36602
- }
36603
-
36604
- // src/connection/create-bridge-runtime-message-setup.ts
36605
- function createBridgeRuntimeMessageSetup(options) {
36722
+ // src/connection/create-bridge-runtime-message-deps.ts
36723
+ function createBridgeRuntimeMessageDeps(params) {
36606
36724
  const {
36607
36725
  apiUrl,
36608
36726
  workspaceId,
36609
- firehoseServerUrl,
36610
36727
  state,
36611
36728
  getWs,
36612
36729
  log: log2,
@@ -36615,23 +36732,11 @@ function createBridgeRuntimeMessageSetup(options) {
36615
36732
  sessionWorktreeManager,
36616
36733
  previewWorktreeManager,
36617
36734
  previewEnvironmentManager,
36618
- e2ee
36619
- } = options;
36620
- const bridgeHeartbeat = createBridgeHeartbeatController({ getWs, log: log2 });
36621
- const baseOnBridgeIdentified = createOnBridgeIdentified({
36622
- previewEnvironmentManager,
36623
- firehoseServerUrl,
36624
- workspaceId,
36625
- state,
36626
- logFn: log2
36627
- });
36628
- const onBridgeIdentified = (msg) => {
36629
- baseOnBridgeIdentified(msg);
36630
- bridgeHeartbeat.start();
36631
- };
36632
- const sendLocalSkillsReport = createSendLocalSkillsReport(getWs, log2);
36633
- const reportAutoDetectedAgents = createReportAutoDetectedAgents(getWs, log2);
36634
- const messageDeps = createBridgeMessageDeps({
36735
+ e2ee,
36736
+ onBridgeIdentified,
36737
+ bridgeHeartbeat
36738
+ } = params;
36739
+ return createBridgeMessageDeps({
36635
36740
  getWs,
36636
36741
  log: log2,
36637
36742
  acpManager,
@@ -36639,8 +36744,8 @@ function createBridgeRuntimeMessageSetup(options) {
36639
36744
  previewWorktreeManager,
36640
36745
  onBridgeIdentified,
36641
36746
  bridgeHeartbeat,
36642
- sendLocalSkillsReport,
36643
- reportAutoDetectedAgents,
36747
+ sendLocalSkillsReport: createSendLocalSkillsReport(getWs, log2),
36748
+ reportAutoDetectedAgents: createReportAutoDetectedAgents(getWs, log2),
36644
36749
  warmupAgentCapabilitiesOnConnect: createWarmupAgentCapabilitiesOnConnect({
36645
36750
  workspaceId,
36646
36751
  log: log2,
@@ -36656,6 +36761,54 @@ function createBridgeRuntimeMessageSetup(options) {
36656
36761
  cloudApiBaseUrl: apiUrl,
36657
36762
  getCloudAccessToken: () => tokens.accessToken
36658
36763
  });
36764
+ }
36765
+
36766
+ // src/connection/create-bridge-runtime-message-setup.ts
36767
+ function createBridgeRuntimeMessageSetup(options) {
36768
+ const {
36769
+ apiUrl,
36770
+ workspaceId,
36771
+ firehoseServerUrl,
36772
+ state,
36773
+ getWs,
36774
+ log: log2,
36775
+ tokens,
36776
+ acpManager,
36777
+ sessionWorktreeManager,
36778
+ previewWorktreeManager,
36779
+ previewEnvironmentManager,
36780
+ e2ee,
36781
+ identifyReportedPaths
36782
+ } = options;
36783
+ const bridgeHeartbeat = createBridgeIdentifyHeartbeat({
36784
+ getWs,
36785
+ log: log2,
36786
+ identifyReportedPaths,
36787
+ e2ee
36788
+ });
36789
+ const onBridgeIdentified = createBridgeIdentifiedWithHeartbeat({
36790
+ previewEnvironmentManager,
36791
+ firehoseServerUrl,
36792
+ workspaceId,
36793
+ state,
36794
+ log: log2,
36795
+ bridgeHeartbeat
36796
+ });
36797
+ const messageDeps = createBridgeRuntimeMessageDeps({
36798
+ apiUrl,
36799
+ workspaceId,
36800
+ state,
36801
+ getWs,
36802
+ log: log2,
36803
+ tokens,
36804
+ acpManager,
36805
+ sessionWorktreeManager,
36806
+ previewWorktreeManager,
36807
+ previewEnvironmentManager,
36808
+ e2ee,
36809
+ onBridgeIdentified,
36810
+ bridgeHeartbeat
36811
+ });
36659
36812
  return { bridgeHeartbeat, messageDeps };
36660
36813
  }
36661
36814
 
@@ -44092,300 +44245,1633 @@ function createBridgeWorktreeManagers(options) {
44092
44245
  };
44093
44246
  }
44094
44247
 
44095
- // src/connection/create-bridge-connection-runtime.ts
44096
- async function createBridgeConnectionRuntime(options, params) {
44097
- const { apiUrl, workspaceId } = options;
44098
- const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
44099
- const { state, getWs, logFn } = params;
44100
- const tokens = {
44101
- accessToken: options.authToken,
44102
- refreshToken: options.refreshToken
44103
- };
44104
- const { worktreesRootPath, sessionWorktreeManager, previewWorktreeManager } = createBridgeWorktreeManagers({ worktreesRootPath: options.worktreesRootPath, log: logFn });
44105
- const { e2ee, acpManager, bridgeAccessPromise } = await createBridgeAccessAndAcp({
44106
- apiUrl,
44107
- workspaceId,
44108
- tokens,
44109
- e2eCertificate: options.e2eCertificate,
44110
- getWs,
44111
- log: logFn
44112
- });
44113
- logFn("CLI running. Press Ctrl+C to exit.");
44114
- const { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths } = createBridgePreviewStack({
44115
- getWs,
44116
- log: logFn,
44117
- e2ee,
44118
- previewWorktreeManager,
44119
- worktreesRootPath
44120
- });
44121
- const { bridgeHeartbeat, messageDeps } = createBridgeRuntimeMessageSetup({
44122
- apiUrl,
44123
- workspaceId,
44124
- firehoseServerUrl,
44125
- state,
44126
- getWs,
44127
- log: logFn,
44128
- tokens,
44129
- acpManager,
44130
- sessionWorktreeManager,
44131
- previewWorktreeManager,
44132
- previewEnvironmentManager,
44133
- e2ee
44134
- });
44135
- return {
44136
- state,
44137
- getWs,
44138
- logFn,
44139
- tokens,
44140
- acpManager,
44141
- bridgeAccessPromise,
44142
- previewEnvironmentManager,
44143
- bridgeHeartbeat,
44144
- messageDeps,
44145
- identifyReportedPaths,
44146
- scheduleInitialIndexBuildsOnce,
44147
- e2ee
44148
- };
44248
+ // src/local-shortcut/lib/generate-token.ts
44249
+ import { randomBytes as randomBytes2 } from "node:crypto";
44250
+ function generateShortcutToken() {
44251
+ return randomBytes2(32).toString("base64url");
44149
44252
  }
44150
44253
 
44151
- // src/connection/create-main-bridge-reconnect-state.ts
44152
- function createMainBridgeReconnectState() {
44153
- const state = {
44154
- closedByUser: false,
44155
- reconnectAttempt: 0,
44156
- lastReconnectCloseMeta: null,
44157
- logBridgeOpenAsReconnect: false,
44158
- reconnectTimeout: null,
44159
- currentWs: null,
44160
- mainQuiet: createEmptyReconnectQuietSlot(),
44161
- mainOutage: createEmptyReconnectOutageTracker(),
44162
- duplicateBridgeFlap: createEmptyDuplicateBridgeFlapTracker(),
44163
- firehoseHandle: null,
44164
- lastFirehoseParams: null,
44165
- firehoseReconnectTimeout: null,
44166
- firehoseReconnectAttempt: 0,
44167
- firehoseGeneration: 0,
44168
- firehoseQuiet: createEmptyReconnectQuietSlot(),
44169
- firehoseOutage: createEmptyReconnectOutageTracker(),
44170
- lastFirehoseReconnectCloseMeta: null
44171
- };
44172
- return {
44173
- state,
44174
- getWs: () => state.currentWs
44175
- };
44176
- }
44254
+ // src/local-shortcut/lib/start-server.ts
44255
+ import * as http2 from "node:http";
44177
44256
 
44178
- // src/connection/ensure-bridge-connection-databases.ts
44179
- async function ensureBridgeConnectionDatabases(log2) {
44180
- await ensureCliSqliteInitialized({ logLegacyMigration: log2 });
44181
- await ensureCodeNavCacheSqliteInitialized();
44182
- }
44257
+ // src/local-shortcut/lib/server.ts
44258
+ import { URL as URL2 } from "node:url";
44183
44259
 
44184
- // src/connection/build-bridge-url.ts
44185
- function buildBridgeUrl(apiUrl, workspaceId, authToken) {
44186
- const base = apiUrl.startsWith("https") ? apiUrl.replace(/^https/, "wss") : apiUrl.replace(/^http/, "ws");
44187
- const params = new URLSearchParams({ workspaceId, token: authToken });
44188
- return `${base}/ws/bridge?${params.toString()}`;
44260
+ // src/local-shortcut/lib/cors.ts
44261
+ var LOCALHOST_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
44262
+ var FIXED_ALLOWED_ORIGINS = /* @__PURE__ */ new Set([
44263
+ "https://app.buildautomaton.com",
44264
+ "http://localhost:3000",
44265
+ "http://127.0.0.1:3000"
44266
+ ]);
44267
+ function resolveAllowedCorsOrigin(origin) {
44268
+ if (!origin) return null;
44269
+ if (FIXED_ALLOWED_ORIGINS.has(origin)) return origin;
44270
+ if (LOCALHOST_ORIGIN_RE.test(origin)) return origin;
44271
+ return null;
44189
44272
  }
44190
-
44191
- // src/types/api-to-bridge-messages.ts
44192
- var API_TO_BRIDGE_MESSAGE_TYPES = [
44193
- "auth_token",
44194
- "bridge_identified",
44195
- "ha",
44196
- "preview_environments_config",
44197
- "preview_environment_control",
44198
- "deploy_session_to_preview",
44199
- "agent_config",
44200
- "prompt_queue_state",
44201
- "prompt",
44202
- "session_git_request",
44203
- "rename_session_branch",
44204
- "session_archived",
44205
- "session_discarded",
44206
- "revert_turn_snapshot",
44207
- "cursor_request_response",
44208
- "skill_call",
44209
- "file_browser_request",
44210
- "file_browser_search",
44211
- "code_nav",
44212
- "skill_layout_request",
44213
- "install_skills",
44214
- "refresh_local_skills",
44215
- "bridge_git_context_request",
44216
- "list_repo_branches_request"
44217
- ];
44218
- var API_TO_BRIDGE_TYPE_SET = new Set(API_TO_BRIDGE_MESSAGE_TYPES);
44219
- function parseApiToBridgeMessage(data, log2) {
44220
- if (data === null || typeof data !== "object" || Array.isArray(data)) return null;
44221
- const t = data.type;
44222
- if (typeof t !== "string" || !API_TO_BRIDGE_TYPE_SET.has(t)) {
44223
- if (typeof t === "string") {
44224
- log2?.(`[Bridge service] unhandled message type: ${t}`);
44225
- }
44226
- return null;
44227
- }
44228
- if (t === "ha") {
44229
- const s = data.s;
44230
- if (typeof s !== "number" || !Number.isFinite(s)) return null;
44273
+ function applyCorsHeaders(req, res) {
44274
+ const origin = typeof req.headers.origin === "string" ? req.headers.origin : void 0;
44275
+ const allowed = resolveAllowedCorsOrigin(origin);
44276
+ if (allowed) {
44277
+ res.setHeader("Access-Control-Allow-Origin", allowed);
44278
+ res.setHeader("Vary", "Origin");
44279
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
44280
+ res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
44281
+ res.setHeader("Access-Control-Max-Age", "86400");
44231
44282
  }
44232
- return data;
44283
+ return allowed != null;
44233
44284
  }
44234
44285
 
44235
- // src/routing/handlers/auth-token.ts
44236
- var handleAuthToken = (msg, { log: log2 }) => {
44237
- if (typeof msg.token !== "string") return;
44238
- log2("Received auth token. Save it for future runs:");
44239
- log2(` export BUILDAUTOMATON_AUTH_TOKEN="${msg.token}"`);
44240
- };
44286
+ // src/local-shortcut/lib/auth.ts
44287
+ function readBearerToken(req) {
44288
+ const raw = req.headers.authorization;
44289
+ if (typeof raw !== "string") return null;
44290
+ const match = /^Bearer\s+(.+)$/i.exec(raw.trim());
44291
+ return match?.[1]?.trim() ? match[1].trim() : null;
44292
+ }
44293
+ function isAuthorizedShortcutRequest(req, expectedToken) {
44294
+ const token = readBearerToken(req);
44295
+ return Boolean(token && expectedToken && token === expectedToken);
44296
+ }
44241
44297
 
44242
- // src/routing/handlers/bridge-identified.ts
44243
- var handleBridgeIdentified = (msg, deps) => {
44244
- if (typeof msg.bridgeName !== "string") return;
44245
- deps.onBridgeIdentified({
44246
- bridgeName: msg.bridgeName,
44247
- proxyPorts: msg.proxyPorts,
44248
- previewEnvironments: msg.previewEnvironments
44249
- });
44250
- setImmediate(() => {
44251
- void (async () => {
44252
- cancelInFlightAgentCapabilityWarmup();
44253
- try {
44254
- await deps.reportAutoDetectedAgents?.();
44255
- } catch (e) {
44256
- deps.log(
44257
- `[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
44258
- );
44259
- }
44260
- try {
44261
- await deps.warmupAgentCapabilitiesOnConnect?.();
44262
- } catch (e) {
44263
- deps.log(
44264
- `[Bridge service] Agent capability warmup failed: ${e instanceof Error ? e.message : String(e)}`
44265
- );
44266
- }
44267
- })();
44268
- });
44269
- setImmediate(() => {
44270
- if (isCliImmediateShutdownRequested()) return;
44271
- try {
44272
- deps.sendLocalSkillsReport?.();
44273
- } catch (e) {
44274
- deps.log(
44275
- `[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
44276
- );
44277
- }
44278
- });
44279
- };
44298
+ // src/local-shortcut/lib/e2ee/file-browser-fields.ts
44299
+ var SHORTCUT_FILE_BROWSER_E2EE_FIELDS = [
44300
+ "entries",
44301
+ "content",
44302
+ "totalLines",
44303
+ "size",
44304
+ "lineOffset"
44305
+ ];
44280
44306
 
44281
- // src/connection/heartbeat/ack.ts
44282
- var handleBridgeHeartbeatAck = (msg, deps) => {
44283
- const raw = msg.s;
44284
- if (typeof raw !== "number" || !Number.isFinite(raw)) return;
44285
- deps.onBridgeHeartbeatAck?.(Math.trunc(raw));
44286
- };
44307
+ // src/local-shortcut/lib/e2ee/encrypt-file-browser-body.ts
44308
+ function encryptShortcutFileBrowserBody(body, e2ee) {
44309
+ if (!e2ee) return body;
44310
+ const encrypted = e2ee.encryptFields(body, SHORTCUT_FILE_BROWSER_E2EE_FIELDS);
44311
+ if (encrypted.ee != null && typeof encrypted.ee === "object") {
44312
+ return { ee: encrypted.ee };
44313
+ }
44314
+ return body;
44315
+ }
44287
44316
 
44288
- // src/agents/acp/from-bridge/handle-bridge-agent-config.ts
44289
- function handleBridgeAgentConfig(msg, { acpManager }) {
44290
- if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
44291
- acpManager.setPreferredAgentType(msg.agents[0].type);
44317
+ // src/local-shortcut/lib/response.ts
44318
+ function sendShortcutJson(res, status, body) {
44319
+ if (res.headersSent) return;
44320
+ res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
44321
+ res.end(JSON.stringify(body));
44322
+ }
44323
+ function sendShortcutFileBrowserJson(res, status, body, e2ee) {
44324
+ sendShortcutJson(res, status, encryptShortcutFileBrowserBody(body, e2ee));
44292
44325
  }
44293
44326
 
44294
- // src/routing/handlers/agent-config.ts
44295
- var handleAgentConfigMessage = (msg, deps) => {
44296
- handleBridgeAgentConfig(msg, deps);
44297
- };
44327
+ // src/local-shortcut/app/api/bridges/local-shortcut/health/route.ts
44328
+ async function GET(ctx) {
44329
+ sendShortcutJson(ctx.res, 200, { ok: true });
44330
+ }
44298
44331
 
44299
- // src/prompt-turn-queue/client-report.ts
44300
- function sendPromptQueueClientReport(ws, queues) {
44301
- if (!ws) return false;
44302
- const wireQueues = {};
44303
- for (const [queueKey, rows] of Object.entries(queues)) {
44304
- wireQueues[queueKey] = rows.map((r) => ({ turnId: r.turnId, clientState: r.cliState }));
44332
+ // src/local-shortcut/app/lib/assert-bridge-workspace.ts
44333
+ function assertBridgeWorkspace(ctx) {
44334
+ const reqWorkspaceId = ctx.url.searchParams.get("workspaceId")?.trim() ?? "";
44335
+ if (reqWorkspaceId !== ctx.workspaceId) {
44336
+ sendShortcutJson(ctx.res, 403, { error: "workspaceId mismatch" });
44337
+ return false;
44305
44338
  }
44306
- sendWsMessage(ws, { type: "prompt_queue_client_report", queues: wireQueues });
44307
44339
  return true;
44308
44340
  }
44309
44341
 
44310
- // src/prompt-turn-queue/disk-store.ts
44311
- var MERGEABLE_BRIDGE_SERVER_STATES = /* @__PURE__ */ new Set([
44312
- "queued",
44313
- "requeued",
44314
- "requeued_with_revert",
44315
- "cancel_requested",
44316
- "stopping",
44317
- "discarded"
44318
- ]);
44319
- async function readPersistedQueue(queueKey) {
44320
- return withCliSqlite((db) => {
44321
- const row = db.get("SELECT queue_key, updated_at, turns_json FROM prompt_queue WHERE queue_key = ?", [
44322
- queueKey
44323
- ]);
44324
- if (!row) return null;
44325
- try {
44326
- const turns = JSON.parse(row.turns_json);
44327
- if (!Array.isArray(turns)) return null;
44328
- return { queueKey: row.queue_key, updatedAt: row.updated_at, turns };
44329
- } catch {
44330
- return null;
44331
- }
44332
- });
44333
- }
44334
- async function writePersistedQueue(file2) {
44335
- await withCliSqlite((db) => {
44336
- db.run(
44337
- `INSERT INTO prompt_queue (queue_key, updated_at, turns_json) VALUES (?, ?, ?)
44338
- ON CONFLICT(queue_key) DO UPDATE SET updated_at = excluded.updated_at, turns_json = excluded.turns_json`,
44339
- [file2.queueKey, file2.updatedAt, JSON.stringify(file2.turns)]
44340
- );
44341
- });
44342
+ // src/files/browser/execute-file-browser-list.ts
44343
+ init_list_dir();
44344
+
44345
+ // src/git/tree/resolve-repo-abs-path.ts
44346
+ init_cwd();
44347
+ import * as path74 from "node:path";
44348
+ function resolveRepoAbsPathFromBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
44349
+ const bridgeResolved = path74.resolve(bridgeRoot);
44350
+ const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
44351
+ const repoPath = rel === "" ? bridgeResolved : path74.join(bridgeResolved, rel);
44352
+ const resolved = path74.resolve(repoPath);
44353
+ if (!resolved.startsWith(bridgeResolved + path74.sep) && resolved !== bridgeResolved) {
44354
+ return null;
44355
+ }
44356
+ return resolved;
44342
44357
  }
44343
- async function mergeBridgeServerQueueSnapshot(queueKey, serverTurns) {
44344
- const prev = await readPersistedQueue(queueKey);
44345
- const turns = [];
44346
- for (const raw of serverTurns) {
44347
- if (!raw || typeof raw !== "object") continue;
44348
- const o = raw;
44349
- const turnId = typeof o.turnId === "string" ? o.turnId : "";
44350
- const sessionId = typeof o.sessionId === "string" ? o.sessionId : "";
44351
- const turnOrd = typeof o.turnOrd === "number" ? o.turnOrd : Number(o.turnOrd) || 0;
44352
- const bridgeServerState = o.serverState ?? o.bridgeServerState;
44353
- const lastCliState = o.lastClientState ?? o.lastCliState ?? null;
44354
- const payload = o.payload && typeof o.payload === "object" ? o.payload : {};
44355
- if (!turnId || !sessionId) continue;
44356
- if (!MERGEABLE_BRIDGE_SERVER_STATES.has(String(bridgeServerState))) continue;
44357
- const old = prev?.turns.find((t) => t.turnId === turnId);
44358
- const mergedCli = old?.lastCliState === "running" && lastCliState == null ? "running" : lastCliState;
44359
- turns.push({
44360
- turnId,
44361
- sessionId,
44362
- turnOrd,
44363
- bridgeServerState,
44364
- lastCliState: mergedCli,
44365
- payload
44366
- });
44358
+
44359
+ // src/git/tree/resolve-branch-commit.ts
44360
+ async function resolveBranchCommit(repoAbsPath, branch) {
44361
+ const branchRef = branch.trim();
44362
+ if (!branchRef) return null;
44363
+ try {
44364
+ return (await cliSimpleGit(repoAbsPath).revparse([`${branchRef}^{commit}`])).trim();
44365
+ } catch {
44366
+ return null;
44367
44367
  }
44368
- turns.sort((a, b) => a.turnOrd - b.turnOrd);
44369
- return { queueKey, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), turns };
44370
44368
  }
44371
44369
 
44372
- // src/prompt-turn-queue/runner/dispatch-local-prompt.ts
44373
- function dispatchLocalPrompt(next, deps) {
44374
- const pl = next.payload;
44375
- const rawParent = pl["sessionParent"];
44376
- const sessionParent = rawParent === "bridge_root" || rawParent === "worktrees_root" ? rawParent : void 0;
44377
- const rawParentPath = pl["sessionParentPath"];
44378
- const sessionParentPath = typeof rawParentPath === "string" && rawParentPath.trim() !== "" ? rawParentPath.trim() : void 0;
44379
- const rawBaseBranches = pl["worktreeBaseBranches"];
44380
- const worktreeBaseBranches = rawBaseBranches != null && typeof rawBaseBranches === "object" && !Array.isArray(rawBaseBranches) ? rawBaseBranches : void 0;
44381
- const msg = {
44382
- type: "prompt",
44383
- sessionId: next.sessionId,
44384
- runId: next.turnId,
44385
- prompt: pl.prompt,
44386
- mode: typeof pl.mode === "string" ? pl.mode : "agent",
44387
- isNewSession: pl.isNewSession === true,
44388
- ...sessionParent ? { sessionParent } : {},
44370
+ // src/git/tree/list-git-tree-dir.ts
44371
+ init_yield_to_event_loop();
44372
+ init_types2();
44373
+ init_sort_entries();
44374
+ function parseLsTreeLine(line) {
44375
+ const tab = line.indexOf(" ");
44376
+ if (tab < 0) return null;
44377
+ const meta = line.slice(0, tab).trim().split(/\s+/);
44378
+ if (meta.length < 3) return null;
44379
+ const mode = meta[0];
44380
+ const objectType2 = meta[1];
44381
+ let name = line.slice(tab + 1);
44382
+ if (name.startsWith('"') && name.endsWith('"')) {
44383
+ name = name.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
44384
+ }
44385
+ return { mode, objectType: objectType2, name };
44386
+ }
44387
+ function childPath(relativePath, name) {
44388
+ const base = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
44389
+ if (!base || base === ".") return name;
44390
+ return `${base}/${name}`.replace(/\\/g, "/");
44391
+ }
44392
+ function lsTreeArgs(commit, relativePath) {
44393
+ const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
44394
+ if (!rel || rel === ".") return ["ls-tree", commit];
44395
+ return ["ls-tree", `${commit}:${rel}`];
44396
+ }
44397
+ async function listGitTreeDirAsync(repoAbsPath, branch, relativePath) {
44398
+ await yieldToEventLoop();
44399
+ const commit = await resolveBranchCommit(repoAbsPath, branch);
44400
+ if (!commit) return { error: "Branch not found" };
44401
+ try {
44402
+ const raw = await cliSimpleGit(repoAbsPath).raw(lsTreeArgs(commit, relativePath));
44403
+ await yieldToEventLoop();
44404
+ const lines = String(raw ?? "").split(/\r?\n/).filter((line) => line.trim().length > 0);
44405
+ const entries = [];
44406
+ for (let i = 0; i < lines.length; i++) {
44407
+ if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
44408
+ await yieldToEventLoop();
44409
+ }
44410
+ const parsed = parseLsTreeLine(lines[i]);
44411
+ if (!parsed) continue;
44412
+ const { mode, objectType: objectType2, name } = parsed;
44413
+ if (!name || name === "." || name === "..") continue;
44414
+ const isSymlink = mode === "120000";
44415
+ const isDir = objectType2 === "tree";
44416
+ entries.push({
44417
+ name,
44418
+ path: childPath(relativePath, name),
44419
+ isDir,
44420
+ ...isSymlink ? { isSymlink: true } : {}
44421
+ });
44422
+ }
44423
+ if (entries.length >= LIST_DIR_YIELD_EVERY) {
44424
+ await yieldToEventLoop();
44425
+ }
44426
+ return { entries: sortListEntries(entries) };
44427
+ } catch (err) {
44428
+ const message = err instanceof Error ? err.message : String(err);
44429
+ if (/not a tree object|does not exist|bad revision/i.test(message)) {
44430
+ return { error: "Path not found in branch" };
44431
+ }
44432
+ return { error: message };
44433
+ }
44434
+ }
44435
+
44436
+ // src/git/tree/file/index.ts
44437
+ init_yield_to_event_loop();
44438
+
44439
+ // src/files/read-file/types.ts
44440
+ var LINE_CHUNK_SIZE = 64 * 1024;
44441
+ var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
44442
+
44443
+ // src/files/read-file/guess-mime-type.ts
44444
+ var MIME_BY_EXT = {
44445
+ png: "image/png",
44446
+ jpg: "image/jpeg",
44447
+ jpeg: "image/jpeg",
44448
+ gif: "image/gif",
44449
+ bmp: "image/bmp",
44450
+ ico: "image/x-icon",
44451
+ webp: "image/webp",
44452
+ avif: "image/avif",
44453
+ svg: "image/svg+xml",
44454
+ pdf: "application/pdf",
44455
+ json: "application/json",
44456
+ html: "text/html",
44457
+ htm: "text/html",
44458
+ css: "text/css",
44459
+ js: "text/javascript",
44460
+ mjs: "text/javascript",
44461
+ ts: "text/typescript",
44462
+ txt: "text/plain",
44463
+ md: "text/markdown",
44464
+ xml: "application/xml",
44465
+ zip: "application/zip",
44466
+ gz: "application/gzip",
44467
+ wasm: "application/wasm"
44468
+ };
44469
+ function guessMimeType(filePath) {
44470
+ const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
44471
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
44472
+ }
44473
+
44474
+ // src/git/tree/file/git-blob-ref.ts
44475
+ init_yield_to_event_loop();
44476
+ async function gitBlobRef(repoAbsPath, branch, relativePath) {
44477
+ const commit = await resolveBranchCommit(repoAbsPath, branch);
44478
+ await yieldToEventLoop();
44479
+ if (!commit) return null;
44480
+ const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
44481
+ if (!rel || rel === ".") return null;
44482
+ return `${commit}:${rel}`;
44483
+ }
44484
+
44485
+ // src/git/tree/file/is-binary-buffer.ts
44486
+ function isBinaryBuffer(buf) {
44487
+ const sample = buf.subarray(0, Math.min(buf.length, 8192));
44488
+ for (let i = 0; i < sample.length; i++) {
44489
+ if (sample[i] === 0) return true;
44490
+ }
44491
+ return false;
44492
+ }
44493
+
44494
+ // src/git/tree/file/read-git-blob-text-async.ts
44495
+ init_yield_to_event_loop();
44496
+ import { StringDecoder } from "node:string_decoder";
44497
+ async function countGitBlobLinesAsync(buf) {
44498
+ if (buf.length === 0) return 1;
44499
+ let lines = 1;
44500
+ let bytesSinceYield = 0;
44501
+ for (let i = 0; i < buf.length; i++) {
44502
+ const b = buf[i];
44503
+ if (b === 10) {
44504
+ lines++;
44505
+ } else if (b === 13) {
44506
+ if (i + 1 < buf.length && buf[i + 1] === 10) {
44507
+ lines++;
44508
+ i++;
44509
+ } else {
44510
+ lines++;
44511
+ }
44512
+ }
44513
+ bytesSinceYield++;
44514
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
44515
+ await yieldToEventLoop();
44516
+ bytesSinceYield = 0;
44517
+ }
44518
+ }
44519
+ return lines;
44520
+ }
44521
+ async function readGitBlobLineRangeAsync(buf, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
44522
+ const bufSize = 64 * 1024;
44523
+ const decoder = new StringDecoder("utf8");
44524
+ let currentLine = 0;
44525
+ const resultLines = [];
44526
+ let partial2 = "";
44527
+ let done = false;
44528
+ let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
44529
+ let line0CharsReturned = 0;
44530
+ let line0Accum = "";
44531
+ let bytesSinceYield = 0;
44532
+ let position = 0;
44533
+ const finish = async () => {
44534
+ const totalLines = await countGitBlobLinesAsync(buf);
44535
+ return { content: resultLines.join("\n"), totalLines };
44536
+ };
44537
+ while (!done && position < buf.length) {
44538
+ const chunkEnd = Math.min(position + bufSize, buf.length);
44539
+ const bytesRead = chunkEnd - position;
44540
+ position = chunkEnd;
44541
+ bytesSinceYield += bytesRead;
44542
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
44543
+ await yieldToEventLoop();
44544
+ bytesSinceYield = 0;
44545
+ }
44546
+ const text = partial2 + decoder.write(buf.subarray(position - bytesRead, position));
44547
+ partial2 = "";
44548
+ let lineStart = 0;
44549
+ for (let i = 0; i < text.length; i++) {
44550
+ if (text[i] === "\n") {
44551
+ const lineContent = (() => {
44552
+ let lineEnd = i;
44553
+ if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
44554
+ return text.slice(lineStart, lineEnd);
44555
+ })();
44556
+ if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
44557
+ line0Accum += lineContent;
44558
+ const totalLine0 = line0Accum.length;
44559
+ if (skipLine0Chars > 0) {
44560
+ if (totalLine0 <= skipLine0Chars) {
44561
+ skipLine0Chars -= totalLine0;
44562
+ line0Accum = "";
44563
+ currentLine++;
44564
+ lineStart = i + 1;
44565
+ if (currentLine > endLine) {
44566
+ done = true;
44567
+ break;
44568
+ }
44569
+ continue;
44570
+ }
44571
+ const from = skipLine0Chars;
44572
+ const take = Math.min(lineChunkSize, totalLine0 - from);
44573
+ resultLines.push(line0Accum.slice(from, from + take));
44574
+ line0CharsReturned += take;
44575
+ if (from + take < totalLine0) {
44576
+ const totalLines = await countGitBlobLinesAsync(buf);
44577
+ return {
44578
+ content: resultLines.join("\n"),
44579
+ totalLines,
44580
+ lineOffset: lineOffsetIn + line0CharsReturned
44581
+ };
44582
+ }
44583
+ line0Accum = "";
44584
+ skipLine0Chars = 0;
44585
+ line0CharsReturned = 0;
44586
+ } else if (totalLine0 > lineChunkSize) {
44587
+ resultLines.push(line0Accum.slice(0, lineChunkSize));
44588
+ const totalLines = await countGitBlobLinesAsync(buf);
44589
+ return {
44590
+ content: resultLines.join("\n"),
44591
+ totalLines,
44592
+ lineOffset: lineChunkSize
44593
+ };
44594
+ } else {
44595
+ resultLines.push(line0Accum);
44596
+ line0Accum = "";
44597
+ }
44598
+ } else if (currentLine >= startLine && currentLine <= endLine) {
44599
+ resultLines.push(lineContent);
44600
+ }
44601
+ currentLine++;
44602
+ lineStart = i + 1;
44603
+ if (currentLine > endLine) {
44604
+ done = true;
44605
+ break;
44606
+ }
44607
+ }
44608
+ }
44609
+ if (!done) {
44610
+ const lineContent = text.slice(lineStart);
44611
+ if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
44612
+ line0Accum += lineContent;
44613
+ const totalLine0 = line0Accum.length;
44614
+ if (skipLine0Chars > 0) {
44615
+ if (totalLine0 <= skipLine0Chars) {
44616
+ skipLine0Chars -= totalLine0;
44617
+ line0Accum = "";
44618
+ } else {
44619
+ const from = skipLine0Chars;
44620
+ const take = Math.min(lineChunkSize, totalLine0 - from);
44621
+ resultLines.push(line0Accum.slice(from, from + take));
44622
+ const totalLines = await countGitBlobLinesAsync(buf);
44623
+ return {
44624
+ content: resultLines.join("\n"),
44625
+ totalLines,
44626
+ lineOffset: (lineOffsetIn ?? 0) + take
44627
+ };
44628
+ }
44629
+ } else if (totalLine0 > lineChunkSize) {
44630
+ resultLines.push(line0Accum.slice(0, lineChunkSize));
44631
+ const totalLines = await countGitBlobLinesAsync(buf);
44632
+ return {
44633
+ content: resultLines.join("\n"),
44634
+ totalLines,
44635
+ lineOffset: lineChunkSize
44636
+ };
44637
+ }
44638
+ }
44639
+ partial2 = text.slice(lineStart);
44640
+ }
44641
+ }
44642
+ if (!done) {
44643
+ const tail = partial2 + decoder.end();
44644
+ if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0) && tail.length > 0) {
44645
+ line0Accum += tail.endsWith("\r") ? tail.slice(0, -1) : tail;
44646
+ const totalLine0 = line0Accum.length;
44647
+ if (skipLine0Chars > 0) {
44648
+ if (totalLine0 <= skipLine0Chars) {
44649
+ return finish();
44650
+ }
44651
+ const from = skipLine0Chars;
44652
+ const take = Math.min(lineChunkSize, totalLine0 - from);
44653
+ resultLines.push(line0Accum.slice(from, from + take));
44654
+ line0CharsReturned += take;
44655
+ if (from + take < totalLine0) {
44656
+ const totalLines = await countGitBlobLinesAsync(buf);
44657
+ return {
44658
+ content: resultLines.join("\n"),
44659
+ totalLines,
44660
+ lineOffset: (lineOffsetIn ?? 0) + line0CharsReturned
44661
+ };
44662
+ }
44663
+ } else if (totalLine0 > lineChunkSize) {
44664
+ resultLines.push(line0Accum.slice(0, lineChunkSize));
44665
+ const totalLines = await countGitBlobLinesAsync(buf);
44666
+ return {
44667
+ content: resultLines.join("\n"),
44668
+ totalLines,
44669
+ lineOffset: lineChunkSize
44670
+ };
44671
+ } else {
44672
+ resultLines.push(line0Accum);
44673
+ }
44674
+ } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
44675
+ resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
44676
+ }
44677
+ }
44678
+ return finish();
44679
+ }
44680
+ async function decodeGitBlobUtf8Async(buf) {
44681
+ await yieldToEventLoop();
44682
+ const text = buf.toString("utf8");
44683
+ await yieldToEventLoop();
44684
+ return text;
44685
+ }
44686
+ async function readGitBlobUtf8FullAsync(buf) {
44687
+ const text = await decodeGitBlobUtf8Async(buf);
44688
+ const totalLines = await countGitBlobLinesAsync(buf);
44689
+ return { content: text, totalLines };
44690
+ }
44691
+
44692
+ // src/git/tree/file/index.ts
44693
+ async function readGitTreeFileAsync(repoAbsPath, branch, relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, encoding = "utf8") {
44694
+ await yieldToEventLoop();
44695
+ const branchRef = branch.trim();
44696
+ if (!branchRef) return { error: "branch required" };
44697
+ const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
44698
+ if (!rel || rel === ".") return { error: "file path required" };
44699
+ const blobRef = await gitBlobRef(repoAbsPath, branchRef, rel);
44700
+ if (!blobRef) return { error: "Branch not found" };
44701
+ const hasRange = typeof startLine === "number" && typeof endLine === "number";
44702
+ try {
44703
+ if (encoding === "base64") {
44704
+ if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
44705
+ const buf2 = await cliSimpleGit(repoAbsPath).showBuffer([blobRef]);
44706
+ await yieldToEventLoop();
44707
+ return {
44708
+ content: buf2.toString("base64"),
44709
+ size: buf2.length,
44710
+ mimeType: guessMimeType(rel)
44711
+ };
44712
+ }
44713
+ const buf = await cliSimpleGit(repoAbsPath).showBuffer([blobRef]);
44714
+ await yieldToEventLoop();
44715
+ const size = buf.length;
44716
+ if (isBinaryBuffer(buf)) {
44717
+ return { error: "Binary file cannot be displayed as text" };
44718
+ }
44719
+ if (hasRange) {
44720
+ const ranged = await readGitBlobLineRangeAsync(buf, startLine, endLine, lineOffset, lineChunkSize);
44721
+ return {
44722
+ content: ranged.content,
44723
+ totalLines: ranged.totalLines,
44724
+ size,
44725
+ ...ranged.lineOffset != null ? { lineOffset: ranged.lineOffset } : {}
44726
+ };
44727
+ }
44728
+ const full = await readGitBlobUtf8FullAsync(buf);
44729
+ return { content: full.content, totalLines: full.totalLines, size, mimeType: guessMimeType(rel) };
44730
+ } catch (err) {
44731
+ const message = err instanceof Error ? err.message : String(err);
44732
+ if (/does not exist|bad revision|exists on disk, but not in/i.test(message)) {
44733
+ return { error: "File not found in branch" };
44734
+ }
44735
+ return { error: message };
44736
+ }
44737
+ }
44738
+
44739
+ // src/files/browser/execute-file-browser-list.ts
44740
+ async function executeFileBrowserList(params) {
44741
+ const { reqPath, sessionParentPath, gitScope } = params;
44742
+ return gitScope ? await listGitTreeDirAsync(gitScope.repoAbsPath, gitScope.branch, reqPath) : await listDirAsync(reqPath, sessionParentPath);
44743
+ }
44744
+
44745
+ // src/files/browser/resolve-git-branch-scope.ts
44746
+ function resolveGitBranchScope(msg) {
44747
+ const gitBranchScope = msg.source === "git_branch" && typeof msg.repoRelPath === "string" && typeof msg.branch === "string" && msg.branch.trim().length > 0;
44748
+ if (!gitBranchScope) return null;
44749
+ const repoAbsPath = resolveRepoAbsPathFromBridge(msg.repoRelPath);
44750
+ if (!repoAbsPath) return null;
44751
+ return { repoAbsPath, branch: msg.branch.trim() };
44752
+ }
44753
+
44754
+ // src/local-shortcut/app/lib/file-browser/parse-query.ts
44755
+ function parseOptionalInt(raw) {
44756
+ if (raw == null) return void 0;
44757
+ const parsed = parseInt(raw, 10);
44758
+ return Number.isInteger(parsed) ? parsed : void 0;
44759
+ }
44760
+ function parseGitScopeFromUrl(url2) {
44761
+ const rawSource = url2.searchParams.get("source")?.trim();
44762
+ if (rawSource === "git_branch") {
44763
+ return {
44764
+ source: "git_branch",
44765
+ repoRelPath: url2.searchParams.get("repoRelPath")?.trim() ?? ".",
44766
+ branch: url2.searchParams.get("branch")?.trim() ?? ""
44767
+ };
44768
+ }
44769
+ return { source: "filesystem" };
44770
+ }
44771
+ function parseReadParams(url2) {
44772
+ const rawEncoding = url2.searchParams.get("encoding");
44773
+ return {
44774
+ startLine: parseOptionalInt(url2.searchParams.get("startLine")),
44775
+ endLine: parseOptionalInt(url2.searchParams.get("endLine")),
44776
+ lineOffset: parseOptionalInt(url2.searchParams.get("lineOffset")),
44777
+ lineChunkSize: parseOptionalInt(url2.searchParams.get("lineChunkSize")),
44778
+ encoding: rawEncoding === "base64" ? "base64" : "utf8"
44779
+ };
44780
+ }
44781
+ function parseListPath(url2) {
44782
+ return (url2.searchParams.get("path") ?? ".").replace(/^\/+/, "") || ".";
44783
+ }
44784
+ function parseReadPath(url2) {
44785
+ const raw = url2.searchParams.get("path")?.trim();
44786
+ if (!raw) return null;
44787
+ return raw.replace(/^\/+/, "") || ".";
44788
+ }
44789
+
44790
+ // src/files/resolve-file-browser-session-parent.ts
44791
+ init_normalize_resolved_path();
44792
+ init_cwd();
44793
+ async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
44794
+ const sid = sessionId?.trim();
44795
+ if (sid) {
44796
+ await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sid);
44797
+ const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
44798
+ if (worktreeRoot) return normalizeResolvedPath(worktreeRoot);
44799
+ }
44800
+ return getBridgeRoot();
44801
+ }
44802
+
44803
+ // src/local-shortcut/app/lib/file-browser/resolve-session-parent.ts
44804
+ async function resolveShortcutSessionParent(ctx, sessionId) {
44805
+ if (!ctx.sessionWorktreeManager || !sessionId?.trim()) return void 0;
44806
+ return resolveFileBrowserSessionParent(ctx.sessionWorktreeManager, sessionId.trim());
44807
+ }
44808
+
44809
+ // src/local-shortcut/app/lib/file-browser/handle-list.ts
44810
+ async function handleShortcutFileBrowserList(ctx, sessionId) {
44811
+ const reqPath = parseListPath(ctx.url);
44812
+ const gitMsg = parseGitScopeFromUrl(ctx.url);
44813
+ const gitScope = resolveGitBranchScope({ id: "shortcut", path: reqPath, ...gitMsg });
44814
+ if (gitMsg.source === "git_branch" && typeof gitMsg.repoRelPath === "string" && typeof gitMsg.branch === "string" && gitMsg.branch.trim().length > 0 && !gitScope) {
44815
+ sendShortcutJson(ctx.res, 400, { error: "Invalid repository path" });
44816
+ return;
44817
+ }
44818
+ const sessionParentPath = await resolveShortcutSessionParent(ctx, sessionId);
44819
+ const result = await executeFileBrowserList({ reqPath, sessionParentPath, gitScope });
44820
+ if ("error" in result) {
44821
+ sendShortcutJson(ctx.res, 400, { error: result.error });
44822
+ return;
44823
+ }
44824
+ sendShortcutFileBrowserJson(ctx.res, 200, { entries: result.entries }, ctx.e2ee);
44825
+ }
44826
+
44827
+ // src/local-shortcut/app/api/bridges/files/list/route.ts
44828
+ async function GET2(ctx) {
44829
+ if (!assertBridgeWorkspace(ctx)) return;
44830
+ await handleShortcutFileBrowserList(ctx);
44831
+ }
44832
+
44833
+ // src/files/browser/file-browser-read-queue.ts
44834
+ var readChain = Promise.resolve();
44835
+ function withFileBrowserReadQueue(fn) {
44836
+ const next = readChain.then(() => fn());
44837
+ readChain = next.then(
44838
+ () => void 0,
44839
+ () => void 0
44840
+ );
44841
+ return next;
44842
+ }
44843
+
44844
+ // src/files/read-file/index.ts
44845
+ init_cwd();
44846
+ init_yield_to_event_loop();
44847
+ import fs52 from "node:fs";
44848
+
44849
+ // src/files/read-file/resolve-file-path.ts
44850
+ init_ensure_under_cwd();
44851
+ init_cwd();
44852
+ import fs48 from "node:fs";
44853
+
44854
+ // src/files/read-file/resolve-file-cache.ts
44855
+ import fs47 from "node:fs";
44856
+ import path75 from "node:path";
44857
+ var RESOLVE_CACHE_MAX = 4096;
44858
+ var resolveCache = /* @__PURE__ */ new Map();
44859
+ var warmedDirectories = /* @__PURE__ */ new Set();
44860
+ function resolveCacheKey(sessionParentPath, relativePath) {
44861
+ return `${sessionParentPath}\0${relativePath}`;
44862
+ }
44863
+ function warmedDirectoryKey(sessionParentPath, directoryRelativePath) {
44864
+ const dir = directoryRelativePath.replace(/^\/+/, "") || ".";
44865
+ return `${path75.resolve(sessionParentPath)}\0${dir}`;
44866
+ }
44867
+ function pathStaysUnderParent(filePath, sessionParentPath) {
44868
+ const parent = path75.resolve(sessionParentPath);
44869
+ const resolved = path75.resolve(filePath);
44870
+ return resolved === parent || resolved.startsWith(`${parent}${path75.sep}`);
44871
+ }
44872
+ async function statMatchesCache(entry) {
44873
+ try {
44874
+ const stat2 = await fs47.promises.stat(entry.path);
44875
+ return stat2.isFile() && stat2.mtimeMs === entry.mtimeMs && stat2.size === entry.size;
44876
+ } catch {
44877
+ return false;
44878
+ }
44879
+ }
44880
+ function getResolveCacheEntry(cacheKey) {
44881
+ return resolveCache.get(cacheKey);
44882
+ }
44883
+ function deleteResolveCacheEntry(cacheKey) {
44884
+ resolveCache.delete(cacheKey);
44885
+ }
44886
+ function storeResolveCacheEntry(cacheKey, entry) {
44887
+ if (resolveCache.size >= RESOLVE_CACHE_MAX) resolveCache.clear();
44888
+ resolveCache.set(cacheKey, entry);
44889
+ return entry;
44890
+ }
44891
+ function hasResolveCacheEntry(cacheKey) {
44892
+ return resolveCache.has(cacheKey);
44893
+ }
44894
+ function markDirectoryWarmed(dirKey) {
44895
+ if (warmedDirectories.has(dirKey)) return false;
44896
+ warmedDirectories.add(dirKey);
44897
+ return true;
44898
+ }
44899
+ function unmarkDirectoryWarmed(dirKey) {
44900
+ warmedDirectories.delete(dirKey);
44901
+ }
44902
+
44903
+ // src/files/read-file/resolve-file-path.ts
44904
+ async function resolveFileForReadAsync(relativePath, sessionParentPath = getBridgeRoot()) {
44905
+ const cacheKey = resolveCacheKey(sessionParentPath, relativePath);
44906
+ const cached2 = getResolveCacheEntry(cacheKey);
44907
+ if (cached2 != null && await statMatchesCache(cached2)) {
44908
+ return { path: cached2.path, size: cached2.size };
44909
+ }
44910
+ if (cached2 != null) deleteResolveCacheEntry(cacheKey);
44911
+ const resolved = ensureUnderCwd(relativePath, sessionParentPath);
44912
+ if (!resolved) return { error: "Path is outside working directory" };
44913
+ let real;
44914
+ let size;
44915
+ let mtimeMs;
44916
+ try {
44917
+ const lstat = await fs48.promises.lstat(resolved);
44918
+ if (lstat.isSymbolicLink()) {
44919
+ real = await fs48.promises.realpath(resolved);
44920
+ if (!pathStaysUnderParent(real, sessionParentPath)) {
44921
+ return { error: "Path is outside working directory" };
44922
+ }
44923
+ const stat2 = await fs48.promises.stat(real);
44924
+ if (!stat2.isFile()) return { error: "Not a file" };
44925
+ size = stat2.size;
44926
+ mtimeMs = stat2.mtimeMs;
44927
+ } else if (lstat.isFile()) {
44928
+ real = resolved;
44929
+ size = lstat.size;
44930
+ mtimeMs = lstat.mtimeMs;
44931
+ } else {
44932
+ return { error: "Not a file" };
44933
+ }
44934
+ } catch (err) {
44935
+ return { error: err instanceof Error ? err.message : String(err) };
44936
+ }
44937
+ const entry = storeResolveCacheEntry(cacheKey, { path: real, size, mtimeMs });
44938
+ return { path: entry.path, size: entry.size };
44939
+ }
44940
+ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
44941
+ const result = await resolveFileForReadAsync(relativePath, sessionParentPath);
44942
+ if ("error" in result) return result;
44943
+ return result.path;
44944
+ }
44945
+
44946
+ // src/files/read-file/read-file-range-async.ts
44947
+ init_yield_to_event_loop();
44948
+ import fs49 from "node:fs";
44949
+ import { StringDecoder as StringDecoder2 } from "node:string_decoder";
44950
+
44951
+ // src/files/read-file/read-file-range-line-zero.ts
44952
+ function createLineZeroStreamState(lineOffsetIn) {
44953
+ return {
44954
+ line0Accum: "",
44955
+ skipLine0Chars: typeof lineOffsetIn === "number" ? lineOffsetIn : 0,
44956
+ line0CharsReturned: 0
44957
+ };
44958
+ }
44959
+ function shouldTrackLineZero(startLine, lineOffsetIn) {
44960
+ return startLine === 0 || lineOffsetIn !== void 0;
44961
+ }
44962
+ function stripCarriageReturn(lineContent) {
44963
+ return lineContent.endsWith("\r") ? lineContent.slice(0, -1) : lineContent;
44964
+ }
44965
+ function lineContentBeforeNewline(text, lineStart, newlineIndex) {
44966
+ let lineEnd = newlineIndex;
44967
+ if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
44968
+ return text.slice(lineStart, lineEnd);
44969
+ }
44970
+ function buildLineZeroChunkResult(resultLines, fileSize, lineOffset, totalLines = 1) {
44971
+ return {
44972
+ content: resultLines.join("\n"),
44973
+ size: fileSize,
44974
+ lineOffset,
44975
+ totalLines
44976
+ };
44977
+ }
44978
+ function tryFinishLineZeroOnCompleteLine(state, lineContent, resultLines, fileSize, lineChunkSize, lineOffsetIn, endLine, currentLine) {
44979
+ state.line0Accum += lineContent;
44980
+ const totalLine0 = state.line0Accum.length;
44981
+ if (state.skipLine0Chars > 0) {
44982
+ if (totalLine0 <= state.skipLine0Chars) {
44983
+ state.skipLine0Chars -= totalLine0;
44984
+ state.line0Accum = "";
44985
+ return currentLine + 1 > endLine ? "done" : "continue";
44986
+ }
44987
+ const from = state.skipLine0Chars;
44988
+ const take = Math.min(lineChunkSize, totalLine0 - from);
44989
+ resultLines.push(state.line0Accum.slice(from, from + take));
44990
+ state.line0CharsReturned += take;
44991
+ if (from + take < totalLine0) {
44992
+ return buildLineZeroChunkResult(
44993
+ resultLines,
44994
+ fileSize,
44995
+ (lineOffsetIn ?? 0) + state.line0CharsReturned
44996
+ );
44997
+ }
44998
+ state.line0Accum = "";
44999
+ state.skipLine0Chars = 0;
45000
+ state.line0CharsReturned = 0;
45001
+ return "continue";
45002
+ }
45003
+ if (totalLine0 > lineChunkSize) {
45004
+ resultLines.push(state.line0Accum.slice(0, lineChunkSize));
45005
+ return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
45006
+ }
45007
+ resultLines.push(state.line0Accum);
45008
+ state.line0Accum = "";
45009
+ return "continue";
45010
+ }
45011
+ function tryFinishLineZeroPartial(state, partialLine, resultLines, fileSize, lineChunkSize, lineOffsetIn) {
45012
+ state.line0Accum += partialLine;
45013
+ const totalLine0 = state.line0Accum.length;
45014
+ if (state.skipLine0Chars > 0) {
45015
+ if (totalLine0 <= state.skipLine0Chars) {
45016
+ state.skipLine0Chars -= totalLine0;
45017
+ state.line0Accum = "";
45018
+ return null;
45019
+ }
45020
+ const from = state.skipLine0Chars;
45021
+ const take = Math.min(lineChunkSize, totalLine0 - from);
45022
+ resultLines.push(state.line0Accum.slice(from, from + take));
45023
+ return buildLineZeroChunkResult(resultLines, fileSize, (lineOffsetIn ?? 0) + take);
45024
+ }
45025
+ if (totalLine0 > lineChunkSize) {
45026
+ resultLines.push(state.line0Accum.slice(0, lineChunkSize));
45027
+ return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
45028
+ }
45029
+ return null;
45030
+ }
45031
+ function finishLineZeroTail(state, tail, resultLines, fileSize, lineChunkSize, lineOffsetIn) {
45032
+ if (tail.length === 0) return null;
45033
+ state.line0Accum += stripCarriageReturn(tail);
45034
+ const totalLine0 = state.line0Accum.length;
45035
+ if (state.skipLine0Chars > 0) {
45036
+ if (totalLine0 <= state.skipLine0Chars) {
45037
+ return { content: resultLines.join("\n"), size: fileSize };
45038
+ }
45039
+ const from = state.skipLine0Chars;
45040
+ const take = Math.min(lineChunkSize, totalLine0 - from);
45041
+ resultLines.push(state.line0Accum.slice(from, from + take));
45042
+ state.line0CharsReturned += take;
45043
+ if (from + take < totalLine0) {
45044
+ return buildLineZeroChunkResult(
45045
+ resultLines,
45046
+ fileSize,
45047
+ (lineOffsetIn ?? 0) + state.line0CharsReturned
45048
+ );
45049
+ }
45050
+ return null;
45051
+ }
45052
+ if (totalLine0 > lineChunkSize) {
45053
+ resultLines.push(state.line0Accum.slice(0, lineChunkSize));
45054
+ return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
45055
+ }
45056
+ resultLines.push(state.line0Accum);
45057
+ return null;
45058
+ }
45059
+
45060
+ // src/files/read-file/read-file-range-async.ts
45061
+ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE, fileSizeIn) {
45062
+ const fileSize = fileSizeIn ?? (await fs49.promises.stat(filePath)).size;
45063
+ const fd = await fs49.promises.open(filePath, "r");
45064
+ const bufSize = 64 * 1024;
45065
+ const buf = Buffer.alloc(bufSize);
45066
+ const decoder = new StringDecoder2("utf8");
45067
+ let currentLine = 0;
45068
+ const resultLines = [];
45069
+ let partial2 = "";
45070
+ let done = false;
45071
+ const trackLineZero = shouldTrackLineZero(startLine, lineOffsetIn);
45072
+ const lineZeroState = createLineZeroStreamState(lineOffsetIn);
45073
+ let bytesSinceYield = 0;
45074
+ try {
45075
+ let position = 0;
45076
+ while (!done) {
45077
+ const { bytesRead } = await fd.read(buf, 0, bufSize, position);
45078
+ if (bytesRead === 0) break;
45079
+ position += bytesRead;
45080
+ bytesSinceYield += bytesRead;
45081
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
45082
+ await yieldToEventLoop();
45083
+ bytesSinceYield = 0;
45084
+ }
45085
+ const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
45086
+ partial2 = "";
45087
+ let lineStart = 0;
45088
+ for (let i = 0; i < text.length; i++) {
45089
+ if (text[i] !== "\n") continue;
45090
+ const lineContent2 = lineContentBeforeNewline(text, lineStart, i);
45091
+ if (currentLine === 0 && trackLineZero) {
45092
+ const lineZeroResult = tryFinishLineZeroOnCompleteLine(
45093
+ lineZeroState,
45094
+ lineContent2,
45095
+ resultLines,
45096
+ fileSize,
45097
+ lineChunkSize,
45098
+ lineOffsetIn,
45099
+ endLine,
45100
+ currentLine
45101
+ );
45102
+ if (lineZeroResult === "done") {
45103
+ done = true;
45104
+ break;
45105
+ }
45106
+ if (lineZeroResult !== "continue") return lineZeroResult;
45107
+ currentLine++;
45108
+ lineStart = i + 1;
45109
+ if (currentLine > endLine) {
45110
+ done = true;
45111
+ break;
45112
+ }
45113
+ continue;
45114
+ }
45115
+ if (currentLine >= startLine && currentLine <= endLine) {
45116
+ resultLines.push(lineContent2);
45117
+ }
45118
+ currentLine++;
45119
+ lineStart = i + 1;
45120
+ if (currentLine > endLine) {
45121
+ done = true;
45122
+ break;
45123
+ }
45124
+ }
45125
+ if (done) continue;
45126
+ const lineContent = text.slice(lineStart);
45127
+ if (currentLine === 0 && trackLineZero) {
45128
+ const partialResult = tryFinishLineZeroPartial(
45129
+ lineZeroState,
45130
+ lineContent,
45131
+ resultLines,
45132
+ fileSize,
45133
+ lineChunkSize,
45134
+ lineOffsetIn
45135
+ );
45136
+ if (partialResult != null) return partialResult;
45137
+ } else {
45138
+ partial2 = lineContent;
45139
+ }
45140
+ }
45141
+ if (!done) {
45142
+ const tail = partial2 + decoder.end();
45143
+ if (currentLine === 0 && trackLineZero) {
45144
+ const tailResult = finishLineZeroTail(
45145
+ lineZeroState,
45146
+ tail,
45147
+ resultLines,
45148
+ fileSize,
45149
+ lineChunkSize,
45150
+ lineOffsetIn
45151
+ );
45152
+ if (tailResult != null) return tailResult;
45153
+ } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
45154
+ resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
45155
+ }
45156
+ }
45157
+ return { content: resultLines.join("\n"), size: fileSize };
45158
+ } finally {
45159
+ await fd.close();
45160
+ }
45161
+ }
45162
+
45163
+ // src/files/read-file/read-small-file-range-async.ts
45164
+ import fs50 from "node:fs";
45165
+ var SMALL_FILE_RANGE_MAX_BYTES = 512 * 1024;
45166
+ function sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize) {
45167
+ if (startLine === 0 && lineOffsetIn == null && lines[0] != null && lines[0].length > lineChunkSize) {
45168
+ return {
45169
+ content: lines[0].slice(0, lineChunkSize),
45170
+ size: fileSize,
45171
+ lineOffset: lineChunkSize,
45172
+ totalLines: lines.length
45173
+ };
45174
+ }
45175
+ if (startLine === 0 && typeof lineOffsetIn === "number") {
45176
+ const line0 = lines[0] ?? "";
45177
+ const from = lineOffsetIn;
45178
+ const take = Math.min(lineChunkSize, Math.max(0, line0.length - from));
45179
+ return {
45180
+ content: line0.slice(from, from + take),
45181
+ size: fileSize,
45182
+ ...from + take < line0.length ? { lineOffset: from + take, totalLines: lines.length } : { totalLines: lines.length }
45183
+ };
45184
+ }
45185
+ const slice = lines.slice(startLine, endLine + 1);
45186
+ return {
45187
+ content: slice.join("\n"),
45188
+ size: fileSize,
45189
+ totalLines: lines.length
45190
+ };
45191
+ }
45192
+ async function readSmallFileRangeAsync(filePath, fileSize, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
45193
+ const raw = await fs50.promises.readFile(filePath, "utf8");
45194
+ const lines = raw.split(/\r?\n/);
45195
+ return sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize);
45196
+ }
45197
+
45198
+ // src/files/read-file/read-file-buffer-full-async.ts
45199
+ init_yield_to_event_loop();
45200
+ import fs51 from "node:fs";
45201
+ var READ_CHUNK_BYTES = 256 * 1024;
45202
+ async function readFileBufferFullAsync(filePath) {
45203
+ const stat2 = await fs51.promises.stat(filePath);
45204
+ const fd = await fs51.promises.open(filePath, "r");
45205
+ const chunks = [];
45206
+ let position = 0;
45207
+ let bytesSinceYield = 0;
45208
+ try {
45209
+ while (position < stat2.size) {
45210
+ const buf = Buffer.alloc(Math.min(READ_CHUNK_BYTES, stat2.size - position));
45211
+ const { bytesRead } = await fd.read(buf, 0, buf.length, position);
45212
+ if (bytesRead === 0) break;
45213
+ chunks.push(buf.subarray(0, bytesRead));
45214
+ position += bytesRead;
45215
+ bytesSinceYield += bytesRead;
45216
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
45217
+ await yieldToEventLoop();
45218
+ bytesSinceYield = 0;
45219
+ }
45220
+ }
45221
+ } finally {
45222
+ await fd.close();
45223
+ }
45224
+ return { buffer: Buffer.concat(chunks), size: stat2.size };
45225
+ }
45226
+
45227
+ // src/files/read-file/read-file-full-async.ts
45228
+ async function readFileFullAsync(filePath) {
45229
+ const { buffer, size } = await readFileBufferFullAsync(filePath);
45230
+ const raw = buffer.toString("utf8");
45231
+ const lines = raw.split(/\r?\n/);
45232
+ return { content: raw, totalLines: lines.length, size };
45233
+ }
45234
+
45235
+ // src/files/read-file/read-file-binary-full-async.ts
45236
+ async function readFileBinaryFullAsync(filePath) {
45237
+ const { buffer, size } = await readFileBufferFullAsync(filePath);
45238
+ return {
45239
+ content: buffer.toString("base64"),
45240
+ size,
45241
+ mimeType: guessMimeType(filePath)
45242
+ };
45243
+ }
45244
+
45245
+ // src/files/read-file/index.ts
45246
+ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, sessionParentPath = getBridgeRoot(), encoding = "utf8", options) {
45247
+ if (!options?.skipInitialYield) {
45248
+ await yieldToEventLoop();
45249
+ }
45250
+ try {
45251
+ let resolvedPath;
45252
+ let fileSize;
45253
+ if (options?.useResolvedSize) {
45254
+ const resolved = await resolveFileForReadAsync(relativePath, sessionParentPath);
45255
+ if ("error" in resolved) return resolved;
45256
+ resolvedPath = resolved.path;
45257
+ fileSize = resolved.size;
45258
+ } else {
45259
+ const result = await resolveFilePathAsync(relativePath, sessionParentPath);
45260
+ if (typeof result === "object") return result;
45261
+ resolvedPath = result;
45262
+ }
45263
+ const hasRange = typeof startLine === "number" && typeof endLine === "number";
45264
+ if (encoding === "base64") {
45265
+ if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
45266
+ const read2 = await readFileBinaryFullAsync(resolvedPath);
45267
+ return { ...read2, resolvedPath };
45268
+ }
45269
+ if (hasRange) {
45270
+ if (fileSize == null) {
45271
+ const stat2 = await fs52.promises.stat(resolvedPath);
45272
+ fileSize = stat2.size;
45273
+ }
45274
+ const read2 = fileSize <= SMALL_FILE_RANGE_MAX_BYTES ? await readSmallFileRangeAsync(
45275
+ resolvedPath,
45276
+ fileSize,
45277
+ startLine,
45278
+ endLine,
45279
+ lineOffset,
45280
+ lineChunkSize
45281
+ ) : await readFileRangeAsync(
45282
+ resolvedPath,
45283
+ startLine,
45284
+ endLine,
45285
+ lineOffset,
45286
+ lineChunkSize,
45287
+ fileSize
45288
+ );
45289
+ return read2;
45290
+ }
45291
+ const read = await readFileFullAsync(resolvedPath);
45292
+ return { ...read, resolvedPath };
45293
+ } catch (err) {
45294
+ return { error: err instanceof Error ? err.message : String(err) };
45295
+ }
45296
+ }
45297
+
45298
+ // src/files/browser/execute-file-browser-read.ts
45299
+ async function executeFileBrowserRead(params) {
45300
+ const { msg, reqPath, sessionParentPath, gitScope } = params;
45301
+ const startLine = typeof msg.startLine === "number" ? msg.startLine : void 0;
45302
+ const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
45303
+ const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
45304
+ const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
45305
+ const encoding = msg.encoding === "base64" ? "base64" : "utf8";
45306
+ return gitScope ? await readGitTreeFileAsync(
45307
+ gitScope.repoAbsPath,
45308
+ gitScope.branch,
45309
+ reqPath,
45310
+ startLine,
45311
+ endLine,
45312
+ lineOffset,
45313
+ lineChunkSize,
45314
+ encoding
45315
+ ) : await withFileBrowserReadQueue(
45316
+ () => readFileAsync(
45317
+ reqPath,
45318
+ startLine,
45319
+ endLine,
45320
+ lineOffset,
45321
+ lineChunkSize,
45322
+ sessionParentPath,
45323
+ encoding,
45324
+ { skipInitialYield: true, useResolvedSize: true }
45325
+ )
45326
+ );
45327
+ }
45328
+
45329
+ // src/local-shortcut/app/lib/file-browser/handle-read.ts
45330
+ async function handleShortcutFileBrowserRead(ctx, sessionId) {
45331
+ const reqPath = parseReadPath(ctx.url);
45332
+ if (reqPath == null) {
45333
+ sendShortcutJson(ctx.res, 400, { error: "path required" });
45334
+ return;
45335
+ }
45336
+ const gitMsg = parseGitScopeFromUrl(ctx.url);
45337
+ const gitScope = resolveGitBranchScope({ id: "shortcut", path: reqPath, ...gitMsg });
45338
+ if (gitMsg.source === "git_branch" && typeof gitMsg.repoRelPath === "string" && typeof gitMsg.branch === "string" && gitMsg.branch.trim().length > 0 && !gitScope) {
45339
+ sendShortcutJson(ctx.res, 400, { error: "Invalid repository path" });
45340
+ return;
45341
+ }
45342
+ const sessionParentPath = await resolveShortcutSessionParent(ctx, sessionId);
45343
+ const result = await executeFileBrowserRead({
45344
+ msg: parseReadParams(ctx.url),
45345
+ reqPath,
45346
+ sessionParentPath,
45347
+ gitScope
45348
+ });
45349
+ if ("error" in result) {
45350
+ sendShortcutJson(ctx.res, 400, { error: result.error });
45351
+ return;
45352
+ }
45353
+ const body = { content: result.content };
45354
+ if (result.totalLines != null) body.totalLines = result.totalLines;
45355
+ if (result.size != null) body.size = result.size;
45356
+ if (result.lineOffset != null) body.lineOffset = result.lineOffset;
45357
+ if (result.mimeType != null) body.mimeType = result.mimeType;
45358
+ if (result.resolvedPath != null) body.resolvedPath = result.resolvedPath;
45359
+ sendShortcutFileBrowserJson(ctx.res, 200, body, ctx.e2ee);
45360
+ }
45361
+
45362
+ // src/local-shortcut/app/api/bridges/files/read/route.ts
45363
+ async function GET3(ctx) {
45364
+ if (!assertBridgeWorkspace(ctx)) return;
45365
+ await handleShortcutFileBrowserRead(ctx);
45366
+ }
45367
+
45368
+ // src/local-shortcut/app/api/sessions/[sessionId]/files/list/route.ts
45369
+ async function GET4(ctx) {
45370
+ await handleShortcutFileBrowserList(ctx, ctx.params.sessionId);
45371
+ }
45372
+
45373
+ // src/local-shortcut/app/api/sessions/[sessionId]/files/read/route.ts
45374
+ async function GET5(ctx) {
45375
+ await handleShortcutFileBrowserRead(ctx, ctx.params.sessionId);
45376
+ }
45377
+
45378
+ // src/local-shortcut/lib/router.ts
45379
+ var routes = [];
45380
+ function segmentsFromAppPath(appPath) {
45381
+ const paramNames = [];
45382
+ const segments = appPath.split("/").filter(Boolean).map((seg) => {
45383
+ if (seg.startsWith("[") && seg.endsWith("]")) {
45384
+ const name = seg.slice(1, -1);
45385
+ paramNames.push(name);
45386
+ return `:${name}`;
45387
+ }
45388
+ return seg;
45389
+ });
45390
+ return { segments, paramNames };
45391
+ }
45392
+ function addAppRoute(method, appPath, handler) {
45393
+ const { segments, paramNames } = segmentsFromAppPath(appPath);
45394
+ routes.push({ method, segments, paramNames, handler });
45395
+ }
45396
+ addAppRoute("GET", "api/bridges/local-shortcut/health", GET);
45397
+ addAppRoute("GET", "api/bridges/files/list", GET2);
45398
+ addAppRoute("GET", "api/bridges/files/read", GET3);
45399
+ addAppRoute("GET", "api/sessions/[sessionId]/files/list", GET4);
45400
+ addAppRoute("GET", "api/sessions/[sessionId]/files/read", GET5);
45401
+ function matchShortcutRoute(pathname, method) {
45402
+ const pathSegments = pathname.split("/").filter(Boolean);
45403
+ for (const route of routes) {
45404
+ if (route.method !== method) continue;
45405
+ if (pathSegments.length !== route.segments.length) continue;
45406
+ const params = {};
45407
+ let matched = true;
45408
+ for (let i = 0; i < route.segments.length; i++) {
45409
+ const seg = route.segments[i];
45410
+ const value = pathSegments[i];
45411
+ if (seg.startsWith(":")) {
45412
+ params[seg.slice(1)] = decodeURIComponent(value);
45413
+ } else if (seg !== value) {
45414
+ matched = false;
45415
+ break;
45416
+ }
45417
+ }
45418
+ if (matched) return { params, handler: route.handler };
45419
+ }
45420
+ return null;
45421
+ }
45422
+
45423
+ // src/local-shortcut/lib/server.ts
45424
+ function shortcutErrorMessage(err) {
45425
+ return err instanceof Error ? err.message : "Internal shortcut server error";
45426
+ }
45427
+ function createLocalShortcutRequestHandler(ctx) {
45428
+ return (req, res) => {
45429
+ void (async () => {
45430
+ try {
45431
+ const corsOk = applyCorsHeaders(req, res);
45432
+ if (req.method === "OPTIONS") {
45433
+ res.writeHead(corsOk ? 204 : 403);
45434
+ res.end();
45435
+ return;
45436
+ }
45437
+ if (!corsOk) {
45438
+ sendShortcutJson(res, 403, { error: "Origin not allowed" });
45439
+ return;
45440
+ }
45441
+ if (req.method !== "GET") {
45442
+ sendShortcutJson(res, 405, { error: "Method not allowed" });
45443
+ return;
45444
+ }
45445
+ if (!isAuthorizedShortcutRequest(req, ctx.token)) {
45446
+ sendShortcutJson(res, 401, { error: "Unauthorized" });
45447
+ return;
45448
+ }
45449
+ const rawUrl = req.url ?? "/";
45450
+ const qIndex = rawUrl.indexOf("?");
45451
+ const pathname = qIndex >= 0 ? rawUrl.slice(0, qIndex) : rawUrl;
45452
+ const search = qIndex >= 0 ? rawUrl.slice(qIndex) : "";
45453
+ const url2 = new URL2(search || "", "http://local");
45454
+ const match = matchShortcutRoute(pathname, req.method);
45455
+ if (!match) {
45456
+ sendShortcutJson(res, 404, { error: "Not found" });
45457
+ return;
45458
+ }
45459
+ const handlerCtx = {
45460
+ req,
45461
+ res,
45462
+ params: match.params,
45463
+ url: url2,
45464
+ workspaceId: ctx.workspaceId,
45465
+ sessionWorktreeManager: ctx.sessionWorktreeManager,
45466
+ e2ee: ctx.e2ee
45467
+ };
45468
+ await match.handler(handlerCtx);
45469
+ } catch (err) {
45470
+ if (!res.headersSent) {
45471
+ sendShortcutJson(res, 500, { error: shortcutErrorMessage(err) });
45472
+ }
45473
+ }
45474
+ })().catch((err) => {
45475
+ if (!res.headersSent) {
45476
+ sendShortcutJson(res, 500, { error: shortcutErrorMessage(err) });
45477
+ }
45478
+ });
45479
+ };
45480
+ }
45481
+
45482
+ // src/local-shortcut/lib/start-server.ts
45483
+ var RESTART_DELAY_MS = 250;
45484
+ var MAX_RESTART_ATTEMPTS = 5;
45485
+ function listenOnPort(server, port) {
45486
+ return new Promise((resolve35, reject) => {
45487
+ server.once("error", reject);
45488
+ server.listen(port, "127.0.0.1", () => {
45489
+ const addr = server.address();
45490
+ if (!addr || typeof addr === "string") {
45491
+ reject(new Error("Local shortcut server did not bind"));
45492
+ return;
45493
+ }
45494
+ resolve35(addr.port);
45495
+ });
45496
+ });
45497
+ }
45498
+ function closeServer(server) {
45499
+ if (!server) return Promise.resolve();
45500
+ return new Promise((resolve35, reject) => {
45501
+ server.close((err) => err ? reject(err) : resolve35());
45502
+ });
45503
+ }
45504
+ function delay2(ms) {
45505
+ return new Promise((resolve35) => setTimeout(resolve35, ms));
45506
+ }
45507
+ function startLocalShortcutServer(ctx) {
45508
+ let closed = false;
45509
+ let restartAttempts = 0;
45510
+ let currentServer = null;
45511
+ let boundPort = null;
45512
+ let restarting = null;
45513
+ async function bindServer(port) {
45514
+ const server = http2.createServer(createLocalShortcutRequestHandler(ctx));
45515
+ server.on("error", (err) => {
45516
+ void restartAfterError(err);
45517
+ });
45518
+ currentServer = server;
45519
+ const listenPort = await listenOnPort(server, port);
45520
+ boundPort = listenPort;
45521
+ return listenPort;
45522
+ }
45523
+ async function restartAfterError(_reason) {
45524
+ if (closed || restarting) return;
45525
+ if (restartAttempts >= MAX_RESTART_ATTEMPTS) return;
45526
+ restartAttempts += 1;
45527
+ restarting = (async () => {
45528
+ await closeServer(currentServer).catch(() => {
45529
+ });
45530
+ currentServer = null;
45531
+ if (closed) return;
45532
+ await delay2(RESTART_DELAY_MS);
45533
+ if (closed) return;
45534
+ try {
45535
+ await bindServer(boundPort ?? 0);
45536
+ restartAttempts = 0;
45537
+ } catch {
45538
+ }
45539
+ })().finally(() => {
45540
+ restarting = null;
45541
+ });
45542
+ await restarting;
45543
+ }
45544
+ return bindServer(0).then((port) => ({
45545
+ port,
45546
+ token: ctx.token,
45547
+ close: async () => {
45548
+ closed = true;
45549
+ await closeServer(currentServer);
45550
+ currentServer = null;
45551
+ }
45552
+ }));
45553
+ }
45554
+
45555
+ // src/local-shortcut/lib/create.ts
45556
+ async function createLocalShortcut(params) {
45557
+ const token = generateShortcutToken();
45558
+ const handle = await startLocalShortcutServer({
45559
+ workspaceId: params.workspaceId,
45560
+ token,
45561
+ sessionWorktreeManager: params.sessionWorktreeManager,
45562
+ e2ee: params.e2ee
45563
+ });
45564
+ return {
45565
+ handle,
45566
+ identify: {
45567
+ localShortcutPort: handle.port,
45568
+ localShortcutToken: handle.token
45569
+ }
45570
+ };
45571
+ }
45572
+
45573
+ // src/connection/create-bridge-connection-runtime.ts
45574
+ async function createBridgeConnectionRuntime(options, params) {
45575
+ const { apiUrl, workspaceId } = options;
45576
+ const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
45577
+ const { state, getWs, logFn } = params;
45578
+ const tokens = {
45579
+ accessToken: options.authToken,
45580
+ refreshToken: options.refreshToken
45581
+ };
45582
+ const { worktreesRootPath, sessionWorktreeManager, previewWorktreeManager } = createBridgeWorktreeManagers({ worktreesRootPath: options.worktreesRootPath, log: logFn });
45583
+ const { e2ee, acpManager, bridgeAccessPromise } = await createBridgeAccessAndAcp({
45584
+ apiUrl,
45585
+ workspaceId,
45586
+ tokens,
45587
+ e2eCertificate: options.e2eCertificate,
45588
+ getWs,
45589
+ log: logFn
45590
+ });
45591
+ const localShortcut = await createLocalShortcut({
45592
+ workspaceId,
45593
+ sessionWorktreeManager,
45594
+ e2ee
45595
+ });
45596
+ logFn("CLI running. Press Ctrl+C to exit.");
45597
+ const { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths } = createBridgePreviewStack({
45598
+ getWs,
45599
+ log: logFn,
45600
+ e2ee,
45601
+ previewWorktreeManager,
45602
+ worktreesRootPath
45603
+ });
45604
+ Object.assign(identifyReportedPaths, localShortcut.identify);
45605
+ const { bridgeHeartbeat, messageDeps } = createBridgeRuntimeMessageSetup({
45606
+ apiUrl,
45607
+ workspaceId,
45608
+ firehoseServerUrl,
45609
+ state,
45610
+ getWs,
45611
+ log: logFn,
45612
+ tokens,
45613
+ acpManager,
45614
+ sessionWorktreeManager,
45615
+ previewWorktreeManager,
45616
+ previewEnvironmentManager,
45617
+ e2ee,
45618
+ identifyReportedPaths
45619
+ });
45620
+ return {
45621
+ state,
45622
+ getWs,
45623
+ logFn,
45624
+ tokens,
45625
+ acpManager,
45626
+ bridgeAccessPromise,
45627
+ previewEnvironmentManager,
45628
+ bridgeHeartbeat,
45629
+ messageDeps,
45630
+ identifyReportedPaths,
45631
+ scheduleInitialIndexBuildsOnce,
45632
+ e2ee,
45633
+ localShortcutPromise: Promise.resolve(localShortcut.handle)
45634
+ };
45635
+ }
45636
+
45637
+ // src/connection/create-main-bridge-reconnect-state.ts
45638
+ function createMainBridgeReconnectState() {
45639
+ const state = {
45640
+ closedByUser: false,
45641
+ reconnectAttempt: 0,
45642
+ lastReconnectCloseMeta: null,
45643
+ logBridgeOpenAsReconnect: false,
45644
+ reconnectTimeout: null,
45645
+ currentWs: null,
45646
+ mainQuiet: createEmptyReconnectQuietSlot(),
45647
+ mainOutage: createEmptyReconnectOutageTracker(),
45648
+ duplicateBridgeFlap: createEmptyDuplicateBridgeFlapTracker(),
45649
+ firehoseHandle: null,
45650
+ lastFirehoseParams: null,
45651
+ firehoseReconnectTimeout: null,
45652
+ firehoseReconnectAttempt: 0,
45653
+ firehoseGeneration: 0,
45654
+ firehoseQuiet: createEmptyReconnectQuietSlot(),
45655
+ firehoseOutage: createEmptyReconnectOutageTracker(),
45656
+ lastFirehoseReconnectCloseMeta: null
45657
+ };
45658
+ return {
45659
+ state,
45660
+ getWs: () => state.currentWs
45661
+ };
45662
+ }
45663
+
45664
+ // src/connection/ensure-bridge-connection-databases.ts
45665
+ async function ensureBridgeConnectionDatabases(log2) {
45666
+ await ensureCliSqliteInitialized({ logLegacyMigration: log2 });
45667
+ await ensureCodeNavCacheSqliteInitialized();
45668
+ }
45669
+
45670
+ // src/connection/build-bridge-url.ts
45671
+ function buildBridgeUrl(apiUrl, workspaceId, authToken) {
45672
+ const base = apiUrl.startsWith("https") ? apiUrl.replace(/^https/, "wss") : apiUrl.replace(/^http/, "ws");
45673
+ const params = new URLSearchParams({ workspaceId, token: authToken });
45674
+ return `${base}/ws/bridge?${params.toString()}`;
45675
+ }
45676
+
45677
+ // src/types/api-to-bridge-messages.ts
45678
+ var API_TO_BRIDGE_MESSAGE_TYPES = [
45679
+ "auth_token",
45680
+ "bridge_identified",
45681
+ "ha",
45682
+ "preview_environments_config",
45683
+ "preview_environment_control",
45684
+ "deploy_session_to_preview",
45685
+ "agent_config",
45686
+ "prompt_queue_state",
45687
+ "prompt",
45688
+ "session_git_request",
45689
+ "rename_session_branch",
45690
+ "session_archived",
45691
+ "session_discarded",
45692
+ "revert_turn_snapshot",
45693
+ "cursor_request_response",
45694
+ "skill_call",
45695
+ "file_browser_request",
45696
+ "file_browser_search",
45697
+ "code_nav",
45698
+ "skill_layout_request",
45699
+ "install_skills",
45700
+ "refresh_local_skills",
45701
+ "bridge_git_context_request",
45702
+ "list_repo_branches_request"
45703
+ ];
45704
+ var API_TO_BRIDGE_TYPE_SET = new Set(API_TO_BRIDGE_MESSAGE_TYPES);
45705
+ function parseApiToBridgeMessage(data, log2) {
45706
+ if (data === null || typeof data !== "object" || Array.isArray(data)) return null;
45707
+ const t = data.type;
45708
+ if (typeof t !== "string" || !API_TO_BRIDGE_TYPE_SET.has(t)) {
45709
+ if (typeof t === "string") {
45710
+ log2?.(`[Bridge service] unhandled message type: ${t}`);
45711
+ }
45712
+ return null;
45713
+ }
45714
+ if (t === "ha") {
45715
+ const s = data.s;
45716
+ if (typeof s !== "number" || !Number.isFinite(s)) return null;
45717
+ }
45718
+ return data;
45719
+ }
45720
+
45721
+ // src/routing/handlers/auth-token.ts
45722
+ var handleAuthToken = (msg, { log: log2 }) => {
45723
+ if (typeof msg.token !== "string") return;
45724
+ log2("Received auth token. Save it for future runs:");
45725
+ log2(` export BUILDAUTOMATON_AUTH_TOKEN="${msg.token}"`);
45726
+ };
45727
+
45728
+ // src/routing/handlers/bridge-identified.ts
45729
+ var handleBridgeIdentified = (msg, deps) => {
45730
+ if (typeof msg.bridgeName !== "string") return;
45731
+ deps.onBridgeIdentified({
45732
+ bridgeName: msg.bridgeName,
45733
+ proxyPorts: msg.proxyPorts,
45734
+ previewEnvironments: msg.previewEnvironments
45735
+ });
45736
+ setImmediate(() => {
45737
+ void (async () => {
45738
+ cancelInFlightAgentCapabilityWarmup();
45739
+ try {
45740
+ await deps.reportAutoDetectedAgents?.();
45741
+ } catch (e) {
45742
+ deps.log(
45743
+ `[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
45744
+ );
45745
+ }
45746
+ try {
45747
+ await deps.warmupAgentCapabilitiesOnConnect?.();
45748
+ } catch (e) {
45749
+ deps.log(
45750
+ `[Bridge service] Agent capability warmup failed: ${e instanceof Error ? e.message : String(e)}`
45751
+ );
45752
+ }
45753
+ })();
45754
+ });
45755
+ setImmediate(() => {
45756
+ if (isCliImmediateShutdownRequested()) return;
45757
+ try {
45758
+ deps.sendLocalSkillsReport?.();
45759
+ } catch (e) {
45760
+ deps.log(
45761
+ `[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
45762
+ );
45763
+ }
45764
+ });
45765
+ };
45766
+
45767
+ // src/connection/heartbeat/ack.ts
45768
+ var handleBridgeHeartbeatAck = (msg, deps) => {
45769
+ const raw = msg.s;
45770
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return;
45771
+ deps.onBridgeHeartbeatAck?.(Math.trunc(raw));
45772
+ };
45773
+
45774
+ // src/agents/acp/from-bridge/handle-bridge-agent-config.ts
45775
+ function handleBridgeAgentConfig(msg, { acpManager }) {
45776
+ if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
45777
+ acpManager.setPreferredAgentType(msg.agents[0].type);
45778
+ }
45779
+
45780
+ // src/routing/handlers/agent-config.ts
45781
+ var handleAgentConfigMessage = (msg, deps) => {
45782
+ handleBridgeAgentConfig(msg, deps);
45783
+ };
45784
+
45785
+ // src/prompt-turn-queue/client-report.ts
45786
+ function sendPromptQueueClientReport(ws, queues) {
45787
+ if (!ws) return false;
45788
+ const wireQueues = {};
45789
+ for (const [queueKey, rows] of Object.entries(queues)) {
45790
+ wireQueues[queueKey] = rows.map((r) => ({ turnId: r.turnId, clientState: r.cliState }));
45791
+ }
45792
+ sendWsMessage(ws, { type: "prompt_queue_client_report", queues: wireQueues });
45793
+ return true;
45794
+ }
45795
+
45796
+ // src/prompt-turn-queue/disk-store.ts
45797
+ var MERGEABLE_BRIDGE_SERVER_STATES = /* @__PURE__ */ new Set([
45798
+ "queued",
45799
+ "requeued",
45800
+ "requeued_with_revert",
45801
+ "cancel_requested",
45802
+ "stopping",
45803
+ "discarded"
45804
+ ]);
45805
+ async function readPersistedQueue(queueKey) {
45806
+ return withCliSqlite((db) => {
45807
+ const row = db.get("SELECT queue_key, updated_at, turns_json FROM prompt_queue WHERE queue_key = ?", [
45808
+ queueKey
45809
+ ]);
45810
+ if (!row) return null;
45811
+ try {
45812
+ const turns = JSON.parse(row.turns_json);
45813
+ if (!Array.isArray(turns)) return null;
45814
+ return { queueKey: row.queue_key, updatedAt: row.updated_at, turns };
45815
+ } catch {
45816
+ return null;
45817
+ }
45818
+ });
45819
+ }
45820
+ async function writePersistedQueue(file2) {
45821
+ await withCliSqlite((db) => {
45822
+ db.run(
45823
+ `INSERT INTO prompt_queue (queue_key, updated_at, turns_json) VALUES (?, ?, ?)
45824
+ ON CONFLICT(queue_key) DO UPDATE SET updated_at = excluded.updated_at, turns_json = excluded.turns_json`,
45825
+ [file2.queueKey, file2.updatedAt, JSON.stringify(file2.turns)]
45826
+ );
45827
+ });
45828
+ }
45829
+ async function mergeBridgeServerQueueSnapshot(queueKey, serverTurns) {
45830
+ const prev = await readPersistedQueue(queueKey);
45831
+ const turns = [];
45832
+ for (const raw of serverTurns) {
45833
+ if (!raw || typeof raw !== "object") continue;
45834
+ const o = raw;
45835
+ const turnId = typeof o.turnId === "string" ? o.turnId : "";
45836
+ const sessionId = typeof o.sessionId === "string" ? o.sessionId : "";
45837
+ const turnOrd = typeof o.turnOrd === "number" ? o.turnOrd : Number(o.turnOrd) || 0;
45838
+ const bridgeServerState = o.serverState ?? o.bridgeServerState;
45839
+ const lastCliState = o.lastClientState ?? o.lastCliState ?? null;
45840
+ const payload = o.payload && typeof o.payload === "object" ? o.payload : {};
45841
+ if (!turnId || !sessionId) continue;
45842
+ if (!MERGEABLE_BRIDGE_SERVER_STATES.has(String(bridgeServerState))) continue;
45843
+ const old = prev?.turns.find((t) => t.turnId === turnId);
45844
+ const mergedCli = old?.lastCliState === "running" && lastCliState == null ? "running" : lastCliState;
45845
+ turns.push({
45846
+ turnId,
45847
+ sessionId,
45848
+ turnOrd,
45849
+ bridgeServerState,
45850
+ lastCliState: mergedCli,
45851
+ payload
45852
+ });
45853
+ }
45854
+ turns.sort((a, b) => a.turnOrd - b.turnOrd);
45855
+ return { queueKey, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), turns };
45856
+ }
45857
+
45858
+ // src/prompt-turn-queue/runner/dispatch-local-prompt.ts
45859
+ function dispatchLocalPrompt(next, deps) {
45860
+ const pl = next.payload;
45861
+ const rawParent = pl["sessionParent"];
45862
+ const sessionParent = rawParent === "bridge_root" || rawParent === "worktrees_root" ? rawParent : void 0;
45863
+ const rawParentPath = pl["sessionParentPath"];
45864
+ const sessionParentPath = typeof rawParentPath === "string" && rawParentPath.trim() !== "" ? rawParentPath.trim() : void 0;
45865
+ const rawBaseBranches = pl["worktreeBaseBranches"];
45866
+ const worktreeBaseBranches = rawBaseBranches != null && typeof rawBaseBranches === "object" && !Array.isArray(rawBaseBranches) ? rawBaseBranches : void 0;
45867
+ const msg = {
45868
+ type: "prompt",
45869
+ sessionId: next.sessionId,
45870
+ runId: next.turnId,
45871
+ prompt: pl.prompt,
45872
+ mode: typeof pl.mode === "string" ? pl.mode : "agent",
45873
+ isNewSession: pl.isNewSession === true,
45874
+ ...sessionParent ? { sessionParent } : {},
44389
45875
  ...sessionParentPath ? { sessionParentPath } : {},
44390
45876
  ...worktreeBaseBranches && Object.keys(worktreeBaseBranches).length > 0 ? { worktreeBaseBranches } : {},
44391
45877
  ...typeof pl.followUpCatalogPromptId === "string" ? { followUpCatalogPromptId: pl.followUpCatalogPromptId } : {},
@@ -44433,15 +45919,15 @@ function syncRunningTurnQueueKeys(turns, queueKey) {
44433
45919
  }
44434
45920
 
44435
45921
  // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
44436
- import fs46 from "node:fs";
45922
+ import fs54 from "node:fs";
44437
45923
 
44438
45924
  // src/git/snapshot/capture.ts
44439
- import * as fs45 from "node:fs";
44440
- import * as path72 from "node:path";
45925
+ import * as fs53 from "node:fs";
45926
+ import * as path76 from "node:path";
44441
45927
  async function resolveSnapshotRepoRoots(options) {
44442
45928
  const { worktreePaths, fallbackCwd, sessionId, log: log2 } = options;
44443
45929
  if (worktreePaths?.length) {
44444
- const uniq = [...new Set(worktreePaths.map((p) => path72.resolve(p)))];
45930
+ const uniq = [...new Set(worktreePaths.map((p) => path76.resolve(p)))];
44445
45931
  return uniq;
44446
45932
  }
44447
45933
  try {
@@ -44449,7 +45935,7 @@ async function resolveSnapshotRepoRoots(options) {
44449
45935
  const mapped = repos.map((r) => r.absolutePath);
44450
45936
  const sid = sessionId?.trim();
44451
45937
  if (sid) {
44452
- const filtered = mapped.filter((root) => path72.basename(root) === sid);
45938
+ const filtered = mapped.filter((root) => path76.basename(root) === sid);
44453
45939
  if (filtered.length > 0) return filtered;
44454
45940
  }
44455
45941
  return mapped;
@@ -44471,7 +45957,7 @@ async function capturePreTurnSnapshot(options) {
44471
45957
  });
44472
45958
  const dir = snapshotsDirForCwd(agentCwd);
44473
45959
  try {
44474
- fs45.mkdirSync(dir, { recursive: true });
45960
+ fs53.mkdirSync(dir, { recursive: true });
44475
45961
  } catch (e) {
44476
45962
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
44477
45963
  }
@@ -44480,9 +45966,9 @@ async function capturePreTurnSnapshot(options) {
44480
45966
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
44481
45967
  repos
44482
45968
  };
44483
- const filePath = path72.join(dir, `${runId}.json`);
45969
+ const filePath = path76.join(dir, `${runId}.json`);
44484
45970
  try {
44485
- fs45.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
45971
+ fs53.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
44486
45972
  } catch (e) {
44487
45973
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
44488
45974
  }
@@ -44495,7 +45981,7 @@ async function capturePreTurnSnapshot(options) {
44495
45981
  async function applyPreTurnSnapshot(filePath, log2) {
44496
45982
  let data;
44497
45983
  try {
44498
- const raw = fs45.readFileSync(filePath, "utf8");
45984
+ const raw = fs53.readFileSync(filePath, "utf8");
44499
45985
  data = JSON.parse(raw);
44500
45986
  } catch (e) {
44501
45987
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
@@ -44536,7 +46022,7 @@ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
44536
46022
  const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
44537
46023
  const file2 = snapshotFilePath(agentBase, tid);
44538
46024
  try {
44539
- await fs46.promises.access(file2, fs46.constants.F_OK);
46025
+ await fs54.promises.access(file2, fs54.constants.F_OK);
44540
46026
  } catch {
44541
46027
  deps.log(
44542
46028
  `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
@@ -44948,416 +46434,6 @@ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
44948
46434
  );
44949
46435
  };
44950
46436
 
44951
- // src/files/resolve-file-browser-session-parent.ts
44952
- init_normalize_resolved_path();
44953
- init_cwd();
44954
- async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
44955
- const sid = sessionId?.trim();
44956
- if (sid) {
44957
- await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sid);
44958
- const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
44959
- if (worktreeRoot) return normalizeResolvedPath(worktreeRoot);
44960
- }
44961
- return getBridgeRoot();
44962
- }
44963
-
44964
- // src/files/browser/handle-file-browser-list.ts
44965
- init_list_dir();
44966
-
44967
- // src/git/tree/resolve-repo-abs-path.ts
44968
- init_cwd();
44969
- import * as path75 from "node:path";
44970
- function resolveRepoAbsPathFromBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
44971
- const bridgeResolved = path75.resolve(bridgeRoot);
44972
- const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
44973
- const repoPath = rel === "" ? bridgeResolved : path75.join(bridgeResolved, rel);
44974
- const resolved = path75.resolve(repoPath);
44975
- if (!resolved.startsWith(bridgeResolved + path75.sep) && resolved !== bridgeResolved) {
44976
- return null;
44977
- }
44978
- return resolved;
44979
- }
44980
-
44981
- // src/git/tree/resolve-branch-commit.ts
44982
- async function resolveBranchCommit(repoAbsPath, branch) {
44983
- const branchRef = branch.trim();
44984
- if (!branchRef) return null;
44985
- try {
44986
- return (await cliSimpleGit(repoAbsPath).revparse([`${branchRef}^{commit}`])).trim();
44987
- } catch {
44988
- return null;
44989
- }
44990
- }
44991
-
44992
- // src/git/tree/list-git-tree-dir.ts
44993
- init_yield_to_event_loop();
44994
- init_types2();
44995
- init_sort_entries();
44996
- function parseLsTreeLine(line) {
44997
- const tab = line.indexOf(" ");
44998
- if (tab < 0) return null;
44999
- const meta = line.slice(0, tab).trim().split(/\s+/);
45000
- if (meta.length < 3) return null;
45001
- const mode = meta[0];
45002
- const objectType2 = meta[1];
45003
- let name = line.slice(tab + 1);
45004
- if (name.startsWith('"') && name.endsWith('"')) {
45005
- name = name.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
45006
- }
45007
- return { mode, objectType: objectType2, name };
45008
- }
45009
- function childPath(relativePath, name) {
45010
- const base = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
45011
- if (!base || base === ".") return name;
45012
- return `${base}/${name}`.replace(/\\/g, "/");
45013
- }
45014
- function lsTreeArgs(commit, relativePath) {
45015
- const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
45016
- if (!rel || rel === ".") return ["ls-tree", commit];
45017
- return ["ls-tree", `${commit}:${rel}`];
45018
- }
45019
- async function listGitTreeDirAsync(repoAbsPath, branch, relativePath) {
45020
- await yieldToEventLoop();
45021
- const commit = await resolveBranchCommit(repoAbsPath, branch);
45022
- if (!commit) return { error: "Branch not found" };
45023
- try {
45024
- const raw = await cliSimpleGit(repoAbsPath).raw(lsTreeArgs(commit, relativePath));
45025
- await yieldToEventLoop();
45026
- const lines = String(raw ?? "").split(/\r?\n/).filter((line) => line.trim().length > 0);
45027
- const entries = [];
45028
- for (let i = 0; i < lines.length; i++) {
45029
- if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
45030
- await yieldToEventLoop();
45031
- }
45032
- const parsed = parseLsTreeLine(lines[i]);
45033
- if (!parsed) continue;
45034
- const { mode, objectType: objectType2, name } = parsed;
45035
- if (!name || name === "." || name === "..") continue;
45036
- const isSymlink = mode === "120000";
45037
- const isDir = objectType2 === "tree";
45038
- entries.push({
45039
- name,
45040
- path: childPath(relativePath, name),
45041
- isDir,
45042
- ...isSymlink ? { isSymlink: true } : {}
45043
- });
45044
- }
45045
- if (entries.length >= LIST_DIR_YIELD_EVERY) {
45046
- await yieldToEventLoop();
45047
- }
45048
- return { entries: sortListEntries(entries) };
45049
- } catch (err) {
45050
- const message = err instanceof Error ? err.message : String(err);
45051
- if (/not a tree object|does not exist|bad revision/i.test(message)) {
45052
- return { error: "Path not found in branch" };
45053
- }
45054
- return { error: message };
45055
- }
45056
- }
45057
-
45058
- // src/git/tree/file/index.ts
45059
- init_yield_to_event_loop();
45060
-
45061
- // src/files/read-file/types.ts
45062
- var LINE_CHUNK_SIZE = 64 * 1024;
45063
- var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
45064
-
45065
- // src/files/read-file/guess-mime-type.ts
45066
- var MIME_BY_EXT = {
45067
- png: "image/png",
45068
- jpg: "image/jpeg",
45069
- jpeg: "image/jpeg",
45070
- gif: "image/gif",
45071
- bmp: "image/bmp",
45072
- ico: "image/x-icon",
45073
- webp: "image/webp",
45074
- avif: "image/avif",
45075
- svg: "image/svg+xml",
45076
- pdf: "application/pdf",
45077
- json: "application/json",
45078
- html: "text/html",
45079
- htm: "text/html",
45080
- css: "text/css",
45081
- js: "text/javascript",
45082
- mjs: "text/javascript",
45083
- ts: "text/typescript",
45084
- txt: "text/plain",
45085
- md: "text/markdown",
45086
- xml: "application/xml",
45087
- zip: "application/zip",
45088
- gz: "application/gzip",
45089
- wasm: "application/wasm"
45090
- };
45091
- function guessMimeType(filePath) {
45092
- const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
45093
- return MIME_BY_EXT[ext] ?? "application/octet-stream";
45094
- }
45095
-
45096
- // src/git/tree/file/git-blob-ref.ts
45097
- init_yield_to_event_loop();
45098
- async function gitBlobRef(repoAbsPath, branch, relativePath) {
45099
- const commit = await resolveBranchCommit(repoAbsPath, branch);
45100
- await yieldToEventLoop();
45101
- if (!commit) return null;
45102
- const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
45103
- if (!rel || rel === ".") return null;
45104
- return `${commit}:${rel}`;
45105
- }
45106
-
45107
- // src/git/tree/file/is-binary-buffer.ts
45108
- function isBinaryBuffer(buf) {
45109
- const sample = buf.subarray(0, Math.min(buf.length, 8192));
45110
- for (let i = 0; i < sample.length; i++) {
45111
- if (sample[i] === 0) return true;
45112
- }
45113
- return false;
45114
- }
45115
-
45116
- // src/git/tree/file/read-git-blob-text-async.ts
45117
- init_yield_to_event_loop();
45118
- import { StringDecoder } from "node:string_decoder";
45119
- async function countGitBlobLinesAsync(buf) {
45120
- if (buf.length === 0) return 1;
45121
- let lines = 1;
45122
- let bytesSinceYield = 0;
45123
- for (let i = 0; i < buf.length; i++) {
45124
- const b = buf[i];
45125
- if (b === 10) {
45126
- lines++;
45127
- } else if (b === 13) {
45128
- if (i + 1 < buf.length && buf[i + 1] === 10) {
45129
- lines++;
45130
- i++;
45131
- } else {
45132
- lines++;
45133
- }
45134
- }
45135
- bytesSinceYield++;
45136
- if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
45137
- await yieldToEventLoop();
45138
- bytesSinceYield = 0;
45139
- }
45140
- }
45141
- return lines;
45142
- }
45143
- async function readGitBlobLineRangeAsync(buf, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
45144
- const bufSize = 64 * 1024;
45145
- const decoder = new StringDecoder("utf8");
45146
- let currentLine = 0;
45147
- const resultLines = [];
45148
- let partial2 = "";
45149
- let done = false;
45150
- let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
45151
- let line0CharsReturned = 0;
45152
- let line0Accum = "";
45153
- let bytesSinceYield = 0;
45154
- let position = 0;
45155
- const finish = async () => {
45156
- const totalLines = await countGitBlobLinesAsync(buf);
45157
- return { content: resultLines.join("\n"), totalLines };
45158
- };
45159
- while (!done && position < buf.length) {
45160
- const chunkEnd = Math.min(position + bufSize, buf.length);
45161
- const bytesRead = chunkEnd - position;
45162
- position = chunkEnd;
45163
- bytesSinceYield += bytesRead;
45164
- if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
45165
- await yieldToEventLoop();
45166
- bytesSinceYield = 0;
45167
- }
45168
- const text = partial2 + decoder.write(buf.subarray(position - bytesRead, position));
45169
- partial2 = "";
45170
- let lineStart = 0;
45171
- for (let i = 0; i < text.length; i++) {
45172
- if (text[i] === "\n") {
45173
- const lineContent = (() => {
45174
- let lineEnd = i;
45175
- if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
45176
- return text.slice(lineStart, lineEnd);
45177
- })();
45178
- if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
45179
- line0Accum += lineContent;
45180
- const totalLine0 = line0Accum.length;
45181
- if (skipLine0Chars > 0) {
45182
- if (totalLine0 <= skipLine0Chars) {
45183
- skipLine0Chars -= totalLine0;
45184
- line0Accum = "";
45185
- currentLine++;
45186
- lineStart = i + 1;
45187
- if (currentLine > endLine) {
45188
- done = true;
45189
- break;
45190
- }
45191
- continue;
45192
- }
45193
- const from = skipLine0Chars;
45194
- const take = Math.min(lineChunkSize, totalLine0 - from);
45195
- resultLines.push(line0Accum.slice(from, from + take));
45196
- line0CharsReturned += take;
45197
- if (from + take < totalLine0) {
45198
- const totalLines = await countGitBlobLinesAsync(buf);
45199
- return {
45200
- content: resultLines.join("\n"),
45201
- totalLines,
45202
- lineOffset: lineOffsetIn + line0CharsReturned
45203
- };
45204
- }
45205
- line0Accum = "";
45206
- skipLine0Chars = 0;
45207
- line0CharsReturned = 0;
45208
- } else if (totalLine0 > lineChunkSize) {
45209
- resultLines.push(line0Accum.slice(0, lineChunkSize));
45210
- const totalLines = await countGitBlobLinesAsync(buf);
45211
- return {
45212
- content: resultLines.join("\n"),
45213
- totalLines,
45214
- lineOffset: lineChunkSize
45215
- };
45216
- } else {
45217
- resultLines.push(line0Accum);
45218
- line0Accum = "";
45219
- }
45220
- } else if (currentLine >= startLine && currentLine <= endLine) {
45221
- resultLines.push(lineContent);
45222
- }
45223
- currentLine++;
45224
- lineStart = i + 1;
45225
- if (currentLine > endLine) {
45226
- done = true;
45227
- break;
45228
- }
45229
- }
45230
- }
45231
- if (!done) {
45232
- const lineContent = text.slice(lineStart);
45233
- if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
45234
- line0Accum += lineContent;
45235
- const totalLine0 = line0Accum.length;
45236
- if (skipLine0Chars > 0) {
45237
- if (totalLine0 <= skipLine0Chars) {
45238
- skipLine0Chars -= totalLine0;
45239
- line0Accum = "";
45240
- } else {
45241
- const from = skipLine0Chars;
45242
- const take = Math.min(lineChunkSize, totalLine0 - from);
45243
- resultLines.push(line0Accum.slice(from, from + take));
45244
- const totalLines = await countGitBlobLinesAsync(buf);
45245
- return {
45246
- content: resultLines.join("\n"),
45247
- totalLines,
45248
- lineOffset: (lineOffsetIn ?? 0) + take
45249
- };
45250
- }
45251
- } else if (totalLine0 > lineChunkSize) {
45252
- resultLines.push(line0Accum.slice(0, lineChunkSize));
45253
- const totalLines = await countGitBlobLinesAsync(buf);
45254
- return {
45255
- content: resultLines.join("\n"),
45256
- totalLines,
45257
- lineOffset: lineChunkSize
45258
- };
45259
- }
45260
- }
45261
- partial2 = text.slice(lineStart);
45262
- }
45263
- }
45264
- if (!done) {
45265
- const tail = partial2 + decoder.end();
45266
- if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0) && tail.length > 0) {
45267
- line0Accum += tail.endsWith("\r") ? tail.slice(0, -1) : tail;
45268
- const totalLine0 = line0Accum.length;
45269
- if (skipLine0Chars > 0) {
45270
- if (totalLine0 <= skipLine0Chars) {
45271
- return finish();
45272
- }
45273
- const from = skipLine0Chars;
45274
- const take = Math.min(lineChunkSize, totalLine0 - from);
45275
- resultLines.push(line0Accum.slice(from, from + take));
45276
- line0CharsReturned += take;
45277
- if (from + take < totalLine0) {
45278
- const totalLines = await countGitBlobLinesAsync(buf);
45279
- return {
45280
- content: resultLines.join("\n"),
45281
- totalLines,
45282
- lineOffset: (lineOffsetIn ?? 0) + line0CharsReturned
45283
- };
45284
- }
45285
- } else if (totalLine0 > lineChunkSize) {
45286
- resultLines.push(line0Accum.slice(0, lineChunkSize));
45287
- const totalLines = await countGitBlobLinesAsync(buf);
45288
- return {
45289
- content: resultLines.join("\n"),
45290
- totalLines,
45291
- lineOffset: lineChunkSize
45292
- };
45293
- } else {
45294
- resultLines.push(line0Accum);
45295
- }
45296
- } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
45297
- resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
45298
- }
45299
- }
45300
- return finish();
45301
- }
45302
- async function decodeGitBlobUtf8Async(buf) {
45303
- await yieldToEventLoop();
45304
- const text = buf.toString("utf8");
45305
- await yieldToEventLoop();
45306
- return text;
45307
- }
45308
- async function readGitBlobUtf8FullAsync(buf) {
45309
- const text = await decodeGitBlobUtf8Async(buf);
45310
- const totalLines = await countGitBlobLinesAsync(buf);
45311
- return { content: text, totalLines };
45312
- }
45313
-
45314
- // src/git/tree/file/index.ts
45315
- async function readGitTreeFileAsync(repoAbsPath, branch, relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, encoding = "utf8") {
45316
- await yieldToEventLoop();
45317
- const branchRef = branch.trim();
45318
- if (!branchRef) return { error: "branch required" };
45319
- const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
45320
- if (!rel || rel === ".") return { error: "file path required" };
45321
- const blobRef = await gitBlobRef(repoAbsPath, branchRef, rel);
45322
- if (!blobRef) return { error: "Branch not found" };
45323
- const hasRange = typeof startLine === "number" && typeof endLine === "number";
45324
- try {
45325
- if (encoding === "base64") {
45326
- if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
45327
- const buf2 = await cliSimpleGit(repoAbsPath).showBuffer([blobRef]);
45328
- await yieldToEventLoop();
45329
- return {
45330
- content: buf2.toString("base64"),
45331
- size: buf2.length,
45332
- mimeType: guessMimeType(rel)
45333
- };
45334
- }
45335
- const buf = await cliSimpleGit(repoAbsPath).showBuffer([blobRef]);
45336
- await yieldToEventLoop();
45337
- const size = buf.length;
45338
- if (isBinaryBuffer(buf)) {
45339
- return { error: "Binary file cannot be displayed as text" };
45340
- }
45341
- if (hasRange) {
45342
- const ranged = await readGitBlobLineRangeAsync(buf, startLine, endLine, lineOffset, lineChunkSize);
45343
- return {
45344
- content: ranged.content,
45345
- totalLines: ranged.totalLines,
45346
- size,
45347
- ...ranged.lineOffset != null ? { lineOffset: ranged.lineOffset } : {}
45348
- };
45349
- }
45350
- const full = await readGitBlobUtf8FullAsync(buf);
45351
- return { content: full.content, totalLines: full.totalLines, size, mimeType: guessMimeType(rel) };
45352
- } catch (err) {
45353
- const message = err instanceof Error ? err.message : String(err);
45354
- if (/does not exist|bad revision|exists on disk, but not in/i.test(message)) {
45355
- return { error: "File not found in branch" };
45356
- }
45357
- return { error: message };
45358
- }
45359
- }
45360
-
45361
46437
  // src/files/browser/send-file-browser-message.ts
45362
46438
  function sendFileBrowserMessage(socket, e2ee, payload) {
45363
46439
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
@@ -45366,103 +46442,6 @@ function sendFileBrowserMessage(socket, e2ee, payload) {
45366
46442
  // src/files/read-file/resolve-file-cache-warmup.ts
45367
46443
  init_yield_to_event_loop();
45368
46444
  import path77 from "node:path";
45369
-
45370
- // src/files/read-file/resolve-file-cache.ts
45371
- import fs49 from "node:fs";
45372
- import path76 from "node:path";
45373
- var RESOLVE_CACHE_MAX = 4096;
45374
- var resolveCache = /* @__PURE__ */ new Map();
45375
- var warmedDirectories = /* @__PURE__ */ new Set();
45376
- function resolveCacheKey(sessionParentPath, relativePath) {
45377
- return `${sessionParentPath}\0${relativePath}`;
45378
- }
45379
- function warmedDirectoryKey(sessionParentPath, directoryRelativePath) {
45380
- const dir = directoryRelativePath.replace(/^\/+/, "") || ".";
45381
- return `${path76.resolve(sessionParentPath)}\0${dir}`;
45382
- }
45383
- function pathStaysUnderParent(filePath, sessionParentPath) {
45384
- const parent = path76.resolve(sessionParentPath);
45385
- const resolved = path76.resolve(filePath);
45386
- return resolved === parent || resolved.startsWith(`${parent}${path76.sep}`);
45387
- }
45388
- async function statMatchesCache(entry) {
45389
- try {
45390
- const stat2 = await fs49.promises.stat(entry.path);
45391
- return stat2.isFile() && stat2.mtimeMs === entry.mtimeMs && stat2.size === entry.size;
45392
- } catch {
45393
- return false;
45394
- }
45395
- }
45396
- function getResolveCacheEntry(cacheKey) {
45397
- return resolveCache.get(cacheKey);
45398
- }
45399
- function deleteResolveCacheEntry(cacheKey) {
45400
- resolveCache.delete(cacheKey);
45401
- }
45402
- function storeResolveCacheEntry(cacheKey, entry) {
45403
- if (resolveCache.size >= RESOLVE_CACHE_MAX) resolveCache.clear();
45404
- resolveCache.set(cacheKey, entry);
45405
- return entry;
45406
- }
45407
- function hasResolveCacheEntry(cacheKey) {
45408
- return resolveCache.has(cacheKey);
45409
- }
45410
- function markDirectoryWarmed(dirKey) {
45411
- if (warmedDirectories.has(dirKey)) return false;
45412
- warmedDirectories.add(dirKey);
45413
- return true;
45414
- }
45415
- function unmarkDirectoryWarmed(dirKey) {
45416
- warmedDirectories.delete(dirKey);
45417
- }
45418
-
45419
- // src/files/read-file/resolve-file-path.ts
45420
- init_ensure_under_cwd();
45421
- init_cwd();
45422
- import fs50 from "node:fs";
45423
- async function resolveFileForReadAsync(relativePath, sessionParentPath = getBridgeRoot()) {
45424
- const cacheKey = resolveCacheKey(sessionParentPath, relativePath);
45425
- const cached2 = getResolveCacheEntry(cacheKey);
45426
- if (cached2 != null && await statMatchesCache(cached2)) {
45427
- return { path: cached2.path, size: cached2.size };
45428
- }
45429
- if (cached2 != null) deleteResolveCacheEntry(cacheKey);
45430
- const resolved = ensureUnderCwd(relativePath, sessionParentPath);
45431
- if (!resolved) return { error: "Path is outside working directory" };
45432
- let real;
45433
- let size;
45434
- let mtimeMs;
45435
- try {
45436
- const lstat = await fs50.promises.lstat(resolved);
45437
- if (lstat.isSymbolicLink()) {
45438
- real = await fs50.promises.realpath(resolved);
45439
- if (!pathStaysUnderParent(real, sessionParentPath)) {
45440
- return { error: "Path is outside working directory" };
45441
- }
45442
- const stat2 = await fs50.promises.stat(real);
45443
- if (!stat2.isFile()) return { error: "Not a file" };
45444
- size = stat2.size;
45445
- mtimeMs = stat2.mtimeMs;
45446
- } else if (lstat.isFile()) {
45447
- real = resolved;
45448
- size = lstat.size;
45449
- mtimeMs = lstat.mtimeMs;
45450
- } else {
45451
- return { error: "Not a file" };
45452
- }
45453
- } catch (err) {
45454
- return { error: err instanceof Error ? err.message : String(err) };
45455
- }
45456
- const entry = storeResolveCacheEntry(cacheKey, { path: real, size, mtimeMs });
45457
- return { path: entry.path, size: entry.size };
45458
- }
45459
- async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
45460
- const result = await resolveFileForReadAsync(relativePath, sessionParentPath);
45461
- if ("error" in result) return result;
45462
- return result.path;
45463
- }
45464
-
45465
- // src/files/read-file/resolve-file-cache-warmup.ts
45466
46445
  var WARMED_DIRECTORY_TTL_MS = 6e4;
45467
46446
  async function warmDirectoryResolveCache(sessionParentPath, directoryRelativePath) {
45468
46447
  const { listDirAsync: listDirAsync2 } = await Promise.resolve().then(() => (init_list_dir(), list_dir_exports));
@@ -45504,7 +46483,7 @@ function scheduleResolveCacheWarmupForListedEntries(sessionParentPath, listRelat
45504
46483
 
45505
46484
  // src/files/browser/handle-file-browser-list.ts
45506
46485
  async function handleFileBrowserList(socket, e2ee, id, reqPath, sessionParentPath, gitScope) {
45507
- const result = gitScope ? await listGitTreeDirAsync(gitScope.repoAbsPath, gitScope.branch, reqPath) : await listDirAsync(reqPath, sessionParentPath);
46486
+ const result = await executeFileBrowserList({ reqPath, sessionParentPath, gitScope });
45508
46487
  if ("error" in result) {
45509
46488
  sendWsMessage(socket, { type: "file_browser_response", id, error: result.error });
45510
46489
  return;
@@ -45518,403 +46497,8 @@ async function handleFileBrowserList(socket, e2ee, id, reqPath, sessionParentPat
45518
46497
  // src/files/browser/handle-file-browser-read.ts
45519
46498
  import path78 from "node:path";
45520
46499
  init_cwd();
45521
-
45522
- // src/files/read-file/index.ts
45523
- init_cwd();
45524
- init_yield_to_event_loop();
45525
- import fs54 from "node:fs";
45526
-
45527
- // src/files/read-file/read-file-range-async.ts
45528
- init_yield_to_event_loop();
45529
- import fs51 from "node:fs";
45530
- import { StringDecoder as StringDecoder2 } from "node:string_decoder";
45531
-
45532
- // src/files/read-file/read-file-range-line-zero.ts
45533
- function createLineZeroStreamState(lineOffsetIn) {
45534
- return {
45535
- line0Accum: "",
45536
- skipLine0Chars: typeof lineOffsetIn === "number" ? lineOffsetIn : 0,
45537
- line0CharsReturned: 0
45538
- };
45539
- }
45540
- function shouldTrackLineZero(startLine, lineOffsetIn) {
45541
- return startLine === 0 || lineOffsetIn !== void 0;
45542
- }
45543
- function stripCarriageReturn(lineContent) {
45544
- return lineContent.endsWith("\r") ? lineContent.slice(0, -1) : lineContent;
45545
- }
45546
- function lineContentBeforeNewline(text, lineStart, newlineIndex) {
45547
- let lineEnd = newlineIndex;
45548
- if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
45549
- return text.slice(lineStart, lineEnd);
45550
- }
45551
- function buildLineZeroChunkResult(resultLines, fileSize, lineOffset, totalLines = 1) {
45552
- return {
45553
- content: resultLines.join("\n"),
45554
- size: fileSize,
45555
- lineOffset,
45556
- totalLines
45557
- };
45558
- }
45559
- function tryFinishLineZeroOnCompleteLine(state, lineContent, resultLines, fileSize, lineChunkSize, lineOffsetIn, endLine, currentLine) {
45560
- state.line0Accum += lineContent;
45561
- const totalLine0 = state.line0Accum.length;
45562
- if (state.skipLine0Chars > 0) {
45563
- if (totalLine0 <= state.skipLine0Chars) {
45564
- state.skipLine0Chars -= totalLine0;
45565
- state.line0Accum = "";
45566
- return currentLine + 1 > endLine ? "done" : "continue";
45567
- }
45568
- const from = state.skipLine0Chars;
45569
- const take = Math.min(lineChunkSize, totalLine0 - from);
45570
- resultLines.push(state.line0Accum.slice(from, from + take));
45571
- state.line0CharsReturned += take;
45572
- if (from + take < totalLine0) {
45573
- return buildLineZeroChunkResult(
45574
- resultLines,
45575
- fileSize,
45576
- (lineOffsetIn ?? 0) + state.line0CharsReturned
45577
- );
45578
- }
45579
- state.line0Accum = "";
45580
- state.skipLine0Chars = 0;
45581
- state.line0CharsReturned = 0;
45582
- return "continue";
45583
- }
45584
- if (totalLine0 > lineChunkSize) {
45585
- resultLines.push(state.line0Accum.slice(0, lineChunkSize));
45586
- return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
45587
- }
45588
- resultLines.push(state.line0Accum);
45589
- state.line0Accum = "";
45590
- return "continue";
45591
- }
45592
- function tryFinishLineZeroPartial(state, partialLine, resultLines, fileSize, lineChunkSize, lineOffsetIn) {
45593
- state.line0Accum += partialLine;
45594
- const totalLine0 = state.line0Accum.length;
45595
- if (state.skipLine0Chars > 0) {
45596
- if (totalLine0 <= state.skipLine0Chars) {
45597
- state.skipLine0Chars -= totalLine0;
45598
- state.line0Accum = "";
45599
- return null;
45600
- }
45601
- const from = state.skipLine0Chars;
45602
- const take = Math.min(lineChunkSize, totalLine0 - from);
45603
- resultLines.push(state.line0Accum.slice(from, from + take));
45604
- return buildLineZeroChunkResult(resultLines, fileSize, (lineOffsetIn ?? 0) + take);
45605
- }
45606
- if (totalLine0 > lineChunkSize) {
45607
- resultLines.push(state.line0Accum.slice(0, lineChunkSize));
45608
- return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
45609
- }
45610
- return null;
45611
- }
45612
- function finishLineZeroTail(state, tail, resultLines, fileSize, lineChunkSize, lineOffsetIn) {
45613
- if (tail.length === 0) return null;
45614
- state.line0Accum += stripCarriageReturn(tail);
45615
- const totalLine0 = state.line0Accum.length;
45616
- if (state.skipLine0Chars > 0) {
45617
- if (totalLine0 <= state.skipLine0Chars) {
45618
- return { content: resultLines.join("\n"), size: fileSize };
45619
- }
45620
- const from = state.skipLine0Chars;
45621
- const take = Math.min(lineChunkSize, totalLine0 - from);
45622
- resultLines.push(state.line0Accum.slice(from, from + take));
45623
- state.line0CharsReturned += take;
45624
- if (from + take < totalLine0) {
45625
- return buildLineZeroChunkResult(
45626
- resultLines,
45627
- fileSize,
45628
- (lineOffsetIn ?? 0) + state.line0CharsReturned
45629
- );
45630
- }
45631
- return null;
45632
- }
45633
- if (totalLine0 > lineChunkSize) {
45634
- resultLines.push(state.line0Accum.slice(0, lineChunkSize));
45635
- return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
45636
- }
45637
- resultLines.push(state.line0Accum);
45638
- return null;
45639
- }
45640
-
45641
- // src/files/read-file/read-file-range-async.ts
45642
- async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE, fileSizeIn) {
45643
- const fileSize = fileSizeIn ?? (await fs51.promises.stat(filePath)).size;
45644
- const fd = await fs51.promises.open(filePath, "r");
45645
- const bufSize = 64 * 1024;
45646
- const buf = Buffer.alloc(bufSize);
45647
- const decoder = new StringDecoder2("utf8");
45648
- let currentLine = 0;
45649
- const resultLines = [];
45650
- let partial2 = "";
45651
- let done = false;
45652
- const trackLineZero = shouldTrackLineZero(startLine, lineOffsetIn);
45653
- const lineZeroState = createLineZeroStreamState(lineOffsetIn);
45654
- let bytesSinceYield = 0;
45655
- try {
45656
- let position = 0;
45657
- while (!done) {
45658
- const { bytesRead } = await fd.read(buf, 0, bufSize, position);
45659
- if (bytesRead === 0) break;
45660
- position += bytesRead;
45661
- bytesSinceYield += bytesRead;
45662
- if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
45663
- await yieldToEventLoop();
45664
- bytesSinceYield = 0;
45665
- }
45666
- const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
45667
- partial2 = "";
45668
- let lineStart = 0;
45669
- for (let i = 0; i < text.length; i++) {
45670
- if (text[i] !== "\n") continue;
45671
- const lineContent2 = lineContentBeforeNewline(text, lineStart, i);
45672
- if (currentLine === 0 && trackLineZero) {
45673
- const lineZeroResult = tryFinishLineZeroOnCompleteLine(
45674
- lineZeroState,
45675
- lineContent2,
45676
- resultLines,
45677
- fileSize,
45678
- lineChunkSize,
45679
- lineOffsetIn,
45680
- endLine,
45681
- currentLine
45682
- );
45683
- if (lineZeroResult === "done") {
45684
- done = true;
45685
- break;
45686
- }
45687
- if (lineZeroResult !== "continue") return lineZeroResult;
45688
- currentLine++;
45689
- lineStart = i + 1;
45690
- if (currentLine > endLine) {
45691
- done = true;
45692
- break;
45693
- }
45694
- continue;
45695
- }
45696
- if (currentLine >= startLine && currentLine <= endLine) {
45697
- resultLines.push(lineContent2);
45698
- }
45699
- currentLine++;
45700
- lineStart = i + 1;
45701
- if (currentLine > endLine) {
45702
- done = true;
45703
- break;
45704
- }
45705
- }
45706
- if (done) continue;
45707
- const lineContent = text.slice(lineStart);
45708
- if (currentLine === 0 && trackLineZero) {
45709
- const partialResult = tryFinishLineZeroPartial(
45710
- lineZeroState,
45711
- lineContent,
45712
- resultLines,
45713
- fileSize,
45714
- lineChunkSize,
45715
- lineOffsetIn
45716
- );
45717
- if (partialResult != null) return partialResult;
45718
- } else {
45719
- partial2 = lineContent;
45720
- }
45721
- }
45722
- if (!done) {
45723
- const tail = partial2 + decoder.end();
45724
- if (currentLine === 0 && trackLineZero) {
45725
- const tailResult = finishLineZeroTail(
45726
- lineZeroState,
45727
- tail,
45728
- resultLines,
45729
- fileSize,
45730
- lineChunkSize,
45731
- lineOffsetIn
45732
- );
45733
- if (tailResult != null) return tailResult;
45734
- } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
45735
- resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
45736
- }
45737
- }
45738
- return { content: resultLines.join("\n"), size: fileSize };
45739
- } finally {
45740
- await fd.close();
45741
- }
45742
- }
45743
-
45744
- // src/files/read-file/read-small-file-range-async.ts
45745
- import fs52 from "node:fs";
45746
- var SMALL_FILE_RANGE_MAX_BYTES = 512 * 1024;
45747
- function sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize) {
45748
- if (startLine === 0 && lineOffsetIn == null && lines[0] != null && lines[0].length > lineChunkSize) {
45749
- return {
45750
- content: lines[0].slice(0, lineChunkSize),
45751
- size: fileSize,
45752
- lineOffset: lineChunkSize,
45753
- totalLines: lines.length
45754
- };
45755
- }
45756
- if (startLine === 0 && typeof lineOffsetIn === "number") {
45757
- const line0 = lines[0] ?? "";
45758
- const from = lineOffsetIn;
45759
- const take = Math.min(lineChunkSize, Math.max(0, line0.length - from));
45760
- return {
45761
- content: line0.slice(from, from + take),
45762
- size: fileSize,
45763
- ...from + take < line0.length ? { lineOffset: from + take, totalLines: lines.length } : { totalLines: lines.length }
45764
- };
45765
- }
45766
- const slice = lines.slice(startLine, endLine + 1);
45767
- return {
45768
- content: slice.join("\n"),
45769
- size: fileSize,
45770
- totalLines: lines.length
45771
- };
45772
- }
45773
- async function readSmallFileRangeAsync(filePath, fileSize, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
45774
- const raw = await fs52.promises.readFile(filePath, "utf8");
45775
- const lines = raw.split(/\r?\n/);
45776
- return sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize);
45777
- }
45778
-
45779
- // src/files/read-file/read-file-buffer-full-async.ts
45780
- init_yield_to_event_loop();
45781
- import fs53 from "node:fs";
45782
- var READ_CHUNK_BYTES = 256 * 1024;
45783
- async function readFileBufferFullAsync(filePath) {
45784
- const stat2 = await fs53.promises.stat(filePath);
45785
- const fd = await fs53.promises.open(filePath, "r");
45786
- const chunks = [];
45787
- let position = 0;
45788
- let bytesSinceYield = 0;
45789
- try {
45790
- while (position < stat2.size) {
45791
- const buf = Buffer.alloc(Math.min(READ_CHUNK_BYTES, stat2.size - position));
45792
- const { bytesRead } = await fd.read(buf, 0, buf.length, position);
45793
- if (bytesRead === 0) break;
45794
- chunks.push(buf.subarray(0, bytesRead));
45795
- position += bytesRead;
45796
- bytesSinceYield += bytesRead;
45797
- if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
45798
- await yieldToEventLoop();
45799
- bytesSinceYield = 0;
45800
- }
45801
- }
45802
- } finally {
45803
- await fd.close();
45804
- }
45805
- return { buffer: Buffer.concat(chunks), size: stat2.size };
45806
- }
45807
-
45808
- // src/files/read-file/read-file-full-async.ts
45809
- async function readFileFullAsync(filePath) {
45810
- const { buffer, size } = await readFileBufferFullAsync(filePath);
45811
- const raw = buffer.toString("utf8");
45812
- const lines = raw.split(/\r?\n/);
45813
- return { content: raw, totalLines: lines.length, size };
45814
- }
45815
-
45816
- // src/files/read-file/read-file-binary-full-async.ts
45817
- async function readFileBinaryFullAsync(filePath) {
45818
- const { buffer, size } = await readFileBufferFullAsync(filePath);
45819
- return {
45820
- content: buffer.toString("base64"),
45821
- size,
45822
- mimeType: guessMimeType(filePath)
45823
- };
45824
- }
45825
-
45826
- // src/files/read-file/index.ts
45827
- async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, sessionParentPath = getBridgeRoot(), encoding = "utf8", options) {
45828
- if (!options?.skipInitialYield) {
45829
- await yieldToEventLoop();
45830
- }
45831
- try {
45832
- let resolvedPath;
45833
- let fileSize;
45834
- if (options?.useResolvedSize) {
45835
- const resolved = await resolveFileForReadAsync(relativePath, sessionParentPath);
45836
- if ("error" in resolved) return resolved;
45837
- resolvedPath = resolved.path;
45838
- fileSize = resolved.size;
45839
- } else {
45840
- const result = await resolveFilePathAsync(relativePath, sessionParentPath);
45841
- if (typeof result === "object") return result;
45842
- resolvedPath = result;
45843
- }
45844
- const hasRange = typeof startLine === "number" && typeof endLine === "number";
45845
- if (encoding === "base64") {
45846
- if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
45847
- const read2 = await readFileBinaryFullAsync(resolvedPath);
45848
- return { ...read2, resolvedPath };
45849
- }
45850
- if (hasRange) {
45851
- if (fileSize == null) {
45852
- const stat2 = await fs54.promises.stat(resolvedPath);
45853
- fileSize = stat2.size;
45854
- }
45855
- const read2 = fileSize <= SMALL_FILE_RANGE_MAX_BYTES ? await readSmallFileRangeAsync(
45856
- resolvedPath,
45857
- fileSize,
45858
- startLine,
45859
- endLine,
45860
- lineOffset,
45861
- lineChunkSize
45862
- ) : await readFileRangeAsync(
45863
- resolvedPath,
45864
- startLine,
45865
- endLine,
45866
- lineOffset,
45867
- lineChunkSize,
45868
- fileSize
45869
- );
45870
- return read2;
45871
- }
45872
- const read = await readFileFullAsync(resolvedPath);
45873
- return { ...read, resolvedPath };
45874
- } catch (err) {
45875
- return { error: err instanceof Error ? err.message : String(err) };
45876
- }
45877
- }
45878
-
45879
- // src/files/browser/file-browser-read-queue.ts
45880
- var readChain = Promise.resolve();
45881
- function withFileBrowserReadQueue(fn) {
45882
- const next = readChain.then(() => fn());
45883
- readChain = next.then(
45884
- () => void 0,
45885
- () => void 0
45886
- );
45887
- return next;
45888
- }
45889
-
45890
- // src/files/browser/handle-file-browser-read.ts
45891
46500
  async function handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPath, gitScope) {
45892
- const startLine = typeof msg.startLine === "number" ? msg.startLine : void 0;
45893
- const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
45894
- const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
45895
- const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
45896
- const encoding = msg.encoding === "base64" ? "base64" : "utf8";
45897
- const result = gitScope ? await readGitTreeFileAsync(
45898
- gitScope.repoAbsPath,
45899
- gitScope.branch,
45900
- reqPath,
45901
- startLine,
45902
- endLine,
45903
- lineOffset,
45904
- lineChunkSize,
45905
- encoding
45906
- ) : await withFileBrowserReadQueue(
45907
- () => readFileAsync(
45908
- reqPath,
45909
- startLine,
45910
- endLine,
45911
- lineOffset,
45912
- lineChunkSize,
45913
- sessionParentPath,
45914
- encoding,
45915
- { skipInitialYield: true, useResolvedSize: true }
45916
- )
45917
- );
46501
+ const result = await executeFileBrowserRead({ msg, reqPath, sessionParentPath, gitScope });
45918
46502
  if ("error" in result) {
45919
46503
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
45920
46504
  return;
@@ -45936,15 +46520,6 @@ async function handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPa
45936
46520
  }
45937
46521
  }
45938
46522
 
45939
- // src/files/browser/resolve-git-branch-scope.ts
45940
- function resolveGitBranchScope(msg) {
45941
- const gitBranchScope = msg.source === "git_branch" && typeof msg.repoRelPath === "string" && typeof msg.branch === "string" && msg.branch.trim().length > 0;
45942
- if (!gitBranchScope) return null;
45943
- const repoAbsPath = resolveRepoAbsPathFromBridge(msg.repoRelPath);
45944
- if (!repoAbsPath) return null;
45945
- return { repoAbsPath, branch: msg.branch.trim() };
45946
- }
45947
-
45948
46523
  // src/files/browser/index.ts
45949
46524
  function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
45950
46525
  beginFileBrowserRequest();
@@ -46134,14 +46709,14 @@ function codeNavSessionKey(sessionId, sessionParentPath) {
46134
46709
  // src/code-nav/handlers/wait-for-symbol-index-ready.ts
46135
46710
  var CODE_NAV_INDEX_READY_WAIT_MS = 75;
46136
46711
  var CODE_NAV_INDEX_READY_POLL_MS = 15;
46137
- function delay2(ms) {
46712
+ function delay3(ms) {
46138
46713
  return new Promise((resolve35) => setTimeout(resolve35, ms));
46139
46714
  }
46140
46715
  async function waitForSymbolIndexReady(sessionParentPath, reqPath, maxWaitMs = CODE_NAV_INDEX_READY_WAIT_MS) {
46141
46716
  const deadline = Date.now() + maxWaitMs;
46142
46717
  do {
46143
46718
  if (await symbolIndexFileIsReadyAsync(sessionParentPath, reqPath)) return true;
46144
- await delay2(CODE_NAV_INDEX_READY_POLL_MS);
46719
+ await delay3(CODE_NAV_INDEX_READY_POLL_MS);
46145
46720
  } while (Date.now() < deadline);
46146
46721
  return await symbolIndexFileIsReadyAsync(sessionParentPath, reqPath);
46147
46722
  }
@@ -47067,27 +47642,6 @@ function createMainBridgeCloseHandler(params, connect) {
47067
47642
  };
47068
47643
  }
47069
47644
 
47070
- // src/connection/report-git-repos.ts
47071
- function reportGitRepos(getWs, log2) {
47072
- setImmediate(() => {
47073
- discoverGitRepos().then((repos) => {
47074
- if (repos.length > 0) {
47075
- const socket = getWs();
47076
- if (socket) {
47077
- sendWsMessage(socket, {
47078
- type: "git_repos",
47079
- repos: repos.map((r) => ({ absolutePath: r.absolutePath, remoteUrl: r.remoteUrl }))
47080
- });
47081
- }
47082
- }
47083
- }).catch((err) => {
47084
- log2(
47085
- `[Bridge service] Git repository discovery failed: ${err instanceof Error ? err.message : String(err)}`
47086
- );
47087
- });
47088
- });
47089
- }
47090
-
47091
47645
  // src/connection/main-bridge-ws-open-handler.ts
47092
47646
  function createMainBridgeOpenHandler(params) {
47093
47647
  const {
@@ -47112,14 +47666,7 @@ function createMainBridgeOpenHandler(params) {
47112
47666
  }
47113
47667
  const socket = getWs();
47114
47668
  if (socket) {
47115
- sendWsMessage(socket, {
47116
- type: "identify",
47117
- role: "cli",
47118
- cliVersion: CLI_VERSION,
47119
- bridgeRootPath: identifyReportedPaths.bridgeRootPath,
47120
- worktreesRootPath: identifyReportedPaths.worktreesRootPath,
47121
- ...e2ee ? { e: e2ee.handshake } : {}
47122
- });
47669
+ sendBridgeIdentify(socket, { identifyReportedPaths, e2ee });
47123
47670
  reportGitRepos(getWs, logFn);
47124
47671
  try {
47125
47672
  onBridgeSocketOpen?.();
@@ -47244,6 +47791,7 @@ async function createBridgeConnection(options) {
47244
47791
  connect();
47245
47792
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeRoot());
47246
47793
  const bridgeAccess = await runtime.bridgeAccessPromise;
47794
+ const localShortcut = await runtime.localShortcutPromise;
47247
47795
  return {
47248
47796
  close: async () => {
47249
47797
  requestCliImmediateShutdown();
@@ -47256,6 +47804,7 @@ async function createBridgeConnection(options) {
47256
47804
  runtime.logFn
47257
47805
  );
47258
47806
  await bridgeAccess.close();
47807
+ await localShortcut.close();
47259
47808
  }
47260
47809
  };
47261
47810
  }