@buildautomaton/cli 0.1.64 → 0.1.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -5277,12 +5277,12 @@ var require_websocket = __commonJS({
5277
5277
  "use strict";
5278
5278
  var EventEmitter2 = __require("events");
5279
5279
  var https2 = __require("https");
5280
- var http2 = __require("http");
5280
+ var http3 = __require("http");
5281
5281
  var net = __require("net");
5282
5282
  var tls = __require("tls");
5283
- var { randomBytes: randomBytes3, createHash: createHash3 } = __require("crypto");
5283
+ var { randomBytes: randomBytes4, createHash: createHash3 } = __require("crypto");
5284
5284
  var { Duplex, Readable: Readable2 } = __require("stream");
5285
- var { URL: URL2 } = __require("url");
5285
+ var { URL: URL3 } = __require("url");
5286
5286
  var PerMessageDeflate = require_permessage_deflate();
5287
5287
  var Receiver2 = require_receiver();
5288
5288
  var Sender2 = require_sender();
@@ -5775,11 +5775,11 @@ var require_websocket = __commonJS({
5775
5775
  );
5776
5776
  }
5777
5777
  let parsedUrl;
5778
- if (address instanceof URL2) {
5778
+ if (address instanceof URL3) {
5779
5779
  parsedUrl = address;
5780
5780
  } else {
5781
5781
  try {
5782
- parsedUrl = new URL2(address);
5782
+ parsedUrl = new URL3(address);
5783
5783
  } catch (e) {
5784
5784
  throw new SyntaxError(`Invalid URL: ${address}`);
5785
5785
  }
@@ -5810,8 +5810,8 @@ var require_websocket = __commonJS({
5810
5810
  }
5811
5811
  }
5812
5812
  const defaultPort = isSecure ? 443 : 80;
5813
- const key = randomBytes3(16).toString("base64");
5814
- const request = isSecure ? https2.request : http2.request;
5813
+ const key = randomBytes4(16).toString("base64");
5814
+ const request = isSecure ? https2.request : http3.request;
5815
5815
  const protocolSet = /* @__PURE__ */ new Set();
5816
5816
  let perMessageDeflate;
5817
5817
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -5916,7 +5916,7 @@ var require_websocket = __commonJS({
5916
5916
  req.abort();
5917
5917
  let addr;
5918
5918
  try {
5919
- addr = new URL2(location, address);
5919
+ addr = new URL3(location, address);
5920
5920
  } catch (e) {
5921
5921
  const err = new SyntaxError(`Invalid URL: ${location}`);
5922
5922
  emitErrorAndClose(websocket, err);
@@ -6305,7 +6305,7 @@ var require_websocket_server = __commonJS({
6305
6305
  "../../node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js"(exports, module) {
6306
6306
  "use strict";
6307
6307
  var EventEmitter2 = __require("events");
6308
- var http2 = __require("http");
6308
+ var http3 = __require("http");
6309
6309
  var { Duplex } = __require("stream");
6310
6310
  var { createHash: createHash3 } = __require("crypto");
6311
6311
  var extension = require_extension();
@@ -6380,8 +6380,8 @@ var require_websocket_server = __commonJS({
6380
6380
  );
6381
6381
  }
6382
6382
  if (options.port != null) {
6383
- this._server = http2.createServer((req, res) => {
6384
- const body = http2.STATUS_CODES[426];
6383
+ this._server = http3.createServer((req, res) => {
6384
+ const body = http3.STATUS_CODES[426];
6385
6385
  res.writeHead(426, {
6386
6386
  "Content-Length": body.length,
6387
6387
  "Content-Type": "text/plain"
@@ -6668,7 +6668,7 @@ var require_websocket_server = __commonJS({
6668
6668
  this.destroy();
6669
6669
  }
6670
6670
  function abortHandshake(socket, code, message, headers) {
6671
- message = message || http2.STATUS_CODES[code];
6671
+ message = message || http3.STATUS_CODES[code];
6672
6672
  headers = {
6673
6673
  Connection: "close",
6674
6674
  "Content-Type": "text/html",
@@ -6677,7 +6677,7 @@ var require_websocket_server = __commonJS({
6677
6677
  };
6678
6678
  socket.once("finish", socket.destroy);
6679
6679
  socket.end(
6680
- `HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
6680
+ `HTTP/1.1 ${code} ${http3.STATUS_CODES[code]}\r
6681
6681
  ` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
6682
6682
  );
6683
6683
  }
@@ -25668,11 +25668,11 @@ var require_dist2 = __commonJS({
25668
25668
  });
25669
25669
 
25670
25670
  // src/files/ensure-under-cwd.ts
25671
- import path74 from "node:path";
25671
+ import path73 from "node:path";
25672
25672
  function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
25673
- const normalized = path74.normalize(relativePath).replace(/^(\.\/)+/, "");
25674
- const resolved = path74.resolve(cwd, normalized);
25675
- if (!resolved.startsWith(cwd + path74.sep) && resolved !== cwd) {
25673
+ const normalized = path73.normalize(relativePath).replace(/^(\.\/)+/, "");
25674
+ const resolved = path73.resolve(cwd, normalized);
25675
+ if (!resolved.startsWith(cwd + path73.sep) && resolved !== cwd) {
25676
25676
  return null;
25677
25677
  }
25678
25678
  return resolved;
@@ -25694,15 +25694,15 @@ var init_types2 = __esm({
25694
25694
  });
25695
25695
 
25696
25696
  // src/files/list-dir/map-dir-entry.ts
25697
- import path75 from "node:path";
25698
- import fs48 from "node:fs";
25697
+ import path74 from "node:path";
25698
+ import fs46 from "node:fs";
25699
25699
  async function mapDirEntry(d, relativePath, resolved) {
25700
- const entryPath = path75.join(relativePath || ".", d.name).replace(/\\/g, "/");
25701
- const fullPath = path75.join(resolved, d.name);
25700
+ const entryPath = path74.join(relativePath || ".", d.name).replace(/\\/g, "/");
25701
+ const fullPath = path74.join(resolved, d.name);
25702
25702
  let isDir = d.isDirectory();
25703
25703
  if (d.isSymbolicLink()) {
25704
25704
  try {
25705
- const targetStat = await fs48.promises.stat(fullPath);
25705
+ const targetStat = await fs46.promises.stat(fullPath);
25706
25706
  isDir = targetStat.isDirectory();
25707
25707
  } catch {
25708
25708
  isDir = false;
@@ -25739,7 +25739,7 @@ var list_dir_exports = {};
25739
25739
  __export(list_dir_exports, {
25740
25740
  listDirAsync: () => listDirAsync
25741
25741
  });
25742
- import fs49 from "node:fs";
25742
+ import fs47 from "node:fs";
25743
25743
  async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
25744
25744
  await yieldToEventLoop();
25745
25745
  const resolved = ensureUnderCwd(relativePath || ".", sessionParentPath);
@@ -25747,7 +25747,7 @@ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
25747
25747
  return { error: "Path is outside working directory" };
25748
25748
  }
25749
25749
  try {
25750
- const names = await fs49.promises.readdir(resolved, { withFileTypes: true });
25750
+ const names = await fs47.promises.readdir(resolved, { withFileTypes: true });
25751
25751
  const entries = [];
25752
25752
  for (let i = 0; i < names.length; i++) {
25753
25753
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
@@ -25791,7 +25791,7 @@ var {
25791
25791
  } = import_index.default;
25792
25792
 
25793
25793
  // src/cli-version.ts
25794
- var CLI_VERSION = "0.1.64".length > 0 ? "0.1.64" : "0.0.0-dev";
25794
+ var CLI_VERSION = "0.1.65".length > 0 ? "0.1.65" : "0.0.0-dev";
25795
25795
 
25796
25796
  // src/cli/defaults.ts
25797
25797
  var DEFAULT_API_URL = process.env.BUILDAUTOMATON_API_URL ?? "https://api.buildautomaton.com";
@@ -27309,7 +27309,7 @@ function clearMainBridgeReconnectQuietOnOpen(state, log2) {
27309
27309
  function scheduleMainBridgeReconnect(state, connect, log2, closeMeta) {
27310
27310
  if (state.closedByUser || state.currentWs != null) return;
27311
27311
  const meta = closeMeta ?? state.lastReconnectCloseMeta ?? void 0;
27312
- const delay3 = applyTieredReconnectPlanAndLog(
27312
+ const delay4 = applyTieredReconnectPlanAndLog(
27313
27313
  state.mainOutage,
27314
27314
  log2,
27315
27315
  BRIDGE_SERVICE_LABEL,
@@ -27318,7 +27318,7 @@ function scheduleMainBridgeReconnect(state, connect, log2, closeMeta) {
27318
27318
  meta?.reason
27319
27319
  );
27320
27320
  armReconnectDelayTimer({
27321
- delayMs: delay3,
27321
+ delayMs: delay4,
27322
27322
  bumpAttempt: () => {
27323
27323
  state.reconnectAttempt += 1;
27324
27324
  },
@@ -27520,10 +27520,10 @@ function runPendingAuth(options) {
27520
27520
  }
27521
27521
  if (resolved) return;
27522
27522
  beginDeferredPendingCloseLog(code, reason);
27523
- const delay3 = pendingAuthReconnectDelayMs(reconnectAttempt);
27523
+ const delay4 = pendingAuthReconnectDelayMs(reconnectAttempt);
27524
27524
  reconnectAttempt += 1;
27525
27525
  if (signInQuiet.verboseLogs) {
27526
- const delayLabel = formatReconnectDelayForLog(delay3);
27526
+ const delayLabel = formatReconnectDelayForLog(delay4);
27527
27527
  logFn(
27528
27528
  `[Bridge service] Next sign-in connection attempt in ${delayLabel} (attempt ${reconnectAttempt}).`
27529
27529
  );
@@ -27531,7 +27531,7 @@ function runPendingAuth(options) {
27531
27531
  reconnectTimeout = setTimeout(() => {
27532
27532
  reconnectTimeout = null;
27533
27533
  connect();
27534
- }, delay3);
27534
+ }, delay4);
27535
27535
  },
27536
27536
  onError: (err) => logCliWebSocketError(logFn, "[Bridge service]", err, "while waiting for sign-in"),
27537
27537
  onMessage: (data) => {
@@ -38692,7 +38692,9 @@ function createBridgePreviewStack(options) {
38692
38692
  };
38693
38693
  const identifyReportedPaths = {
38694
38694
  bridgeRootPath: path39.resolve(getBridgeRoot()),
38695
- worktreesRootPath: path39.resolve(options.worktreesRootPath)
38695
+ worktreesRootPath: path39.resolve(options.worktreesRootPath),
38696
+ localShortcutPort: 0,
38697
+ localShortcutToken: ""
38696
38698
  };
38697
38699
  return { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths };
38698
38700
  }
@@ -39287,7 +39289,7 @@ function createFirehoseReconnectScheduler(ctx, reconnect) {
39287
39289
  function scheduleFirehoseRetryAfterDrop(closeMeta) {
39288
39290
  if (state.closedByUser) return;
39289
39291
  const meta = closeMeta ?? state.lastFirehoseReconnectCloseMeta ?? void 0;
39290
- const delay3 = applyTieredReconnectPlanAndLog(
39292
+ const delay4 = applyTieredReconnectPlanAndLog(
39291
39293
  state.firehoseOutage,
39292
39294
  logFn,
39293
39295
  PREVIEW_TUNNEL_SERVICE_LABEL,
@@ -39296,7 +39298,7 @@ function createFirehoseReconnectScheduler(ctx, reconnect) {
39296
39298
  meta?.reason
39297
39299
  );
39298
39300
  armReconnectDelayTimer({
39299
- delayMs: delay3,
39301
+ delayMs: delay4,
39300
39302
  bumpAttempt: () => {
39301
39303
  state.firehoseReconnectAttempt += 1;
39302
39304
  },
@@ -47143,477 +47145,1809 @@ function createBridgeWorktreeManagers(options) {
47143
47145
  };
47144
47146
  }
47145
47147
 
47146
- // src/connection/create-bridge-connection-runtime.ts
47147
- async function createBridgeConnectionRuntime(options, params) {
47148
- const { apiUrl, workspaceId } = options;
47149
- const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
47150
- const { state, getWs, logFn } = params;
47151
- const tokens = {
47152
- accessToken: options.authToken,
47153
- refreshToken: options.refreshToken
47154
- };
47155
- const { worktreesRootPath, sessionWorktreeManager, previewWorktreeManager } = createBridgeWorktreeManagers({ worktreesRootPath: options.worktreesRootPath, log: logFn });
47156
- const { e2ee, acpManager, bridgeAccessPromise } = await createBridgeAccessAndAcp({
47157
- apiUrl,
47158
- workspaceId,
47159
- tokens,
47160
- e2eCertificate: options.e2eCertificate,
47161
- getWs,
47162
- log: logFn
47163
- });
47164
- logFn("CLI running. Press Ctrl+C to exit.");
47165
- const { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths } = createBridgePreviewStack({
47166
- getWs,
47167
- log: logFn,
47168
- e2ee,
47169
- previewWorktreeManager,
47170
- worktreesRootPath
47171
- });
47172
- const { bridgeHeartbeat, messageDeps } = createBridgeRuntimeMessageSetup({
47173
- apiUrl,
47174
- workspaceId,
47175
- firehoseServerUrl,
47176
- state,
47177
- getWs,
47178
- log: logFn,
47179
- tokens,
47180
- acpManager,
47181
- sessionWorktreeManager,
47182
- previewWorktreeManager,
47183
- previewEnvironmentManager,
47184
- e2ee
47185
- });
47186
- return {
47187
- state,
47188
- getWs,
47189
- logFn,
47190
- tokens,
47191
- acpManager,
47192
- bridgeAccessPromise,
47193
- previewEnvironmentManager,
47194
- bridgeHeartbeat,
47195
- messageDeps,
47196
- identifyReportedPaths,
47197
- scheduleInitialIndexBuildsOnce,
47198
- e2ee
47199
- };
47148
+ // src/local-shortcut/lib/generate-token.ts
47149
+ import { randomBytes as randomBytes3 } from "node:crypto";
47150
+ function generateShortcutToken() {
47151
+ return randomBytes3(32).toString("base64url");
47200
47152
  }
47201
47153
 
47202
- // src/connection/create-main-bridge-reconnect-state.ts
47203
- function createMainBridgeReconnectState() {
47204
- const state = {
47205
- closedByUser: false,
47206
- reconnectAttempt: 0,
47207
- lastReconnectCloseMeta: null,
47208
- logBridgeOpenAsReconnect: false,
47209
- reconnectTimeout: null,
47210
- currentWs: null,
47211
- mainQuiet: createEmptyReconnectQuietSlot(),
47212
- mainOutage: createEmptyReconnectOutageTracker(),
47213
- duplicateBridgeFlap: createEmptyDuplicateBridgeFlapTracker(),
47214
- firehoseHandle: null,
47215
- lastFirehoseParams: null,
47216
- firehoseReconnectTimeout: null,
47217
- firehoseReconnectAttempt: 0,
47218
- firehoseGeneration: 0,
47219
- firehoseQuiet: createEmptyReconnectQuietSlot(),
47220
- firehoseOutage: createEmptyReconnectOutageTracker(),
47221
- lastFirehoseReconnectCloseMeta: null
47222
- };
47223
- return {
47224
- state,
47225
- getWs: () => state.currentWs
47226
- };
47227
- }
47154
+ // src/local-shortcut/lib/start-server.ts
47155
+ import * as http2 from "node:http";
47228
47156
 
47229
- // src/connection/ensure-bridge-connection-databases.ts
47230
- async function ensureBridgeConnectionDatabases(log2) {
47231
- await ensureCliSqliteInitialized({ logLegacyMigration: log2 });
47232
- await ensureCodeNavCacheSqliteInitialized();
47233
- }
47157
+ // src/local-shortcut/lib/server.ts
47158
+ import { URL as URL2 } from "node:url";
47234
47159
 
47235
- // src/connection/build-bridge-url.ts
47236
- function buildBridgeUrl(apiUrl, workspaceId, authToken) {
47237
- const base = apiUrl.startsWith("https") ? apiUrl.replace(/^https/, "wss") : apiUrl.replace(/^http/, "ws");
47238
- const params = new URLSearchParams({ workspaceId, token: authToken });
47239
- return `${base}/ws/bridge?${params.toString()}`;
47160
+ // src/local-shortcut/lib/cors.ts
47161
+ var LOCALHOST_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
47162
+ var FIXED_ALLOWED_ORIGINS = /* @__PURE__ */ new Set([
47163
+ "https://app.buildautomaton.com",
47164
+ "http://localhost:3000",
47165
+ "http://127.0.0.1:3000"
47166
+ ]);
47167
+ function resolveAllowedCorsOrigin(origin) {
47168
+ if (!origin) return null;
47169
+ if (FIXED_ALLOWED_ORIGINS.has(origin)) return origin;
47170
+ if (LOCALHOST_ORIGIN_RE.test(origin)) return origin;
47171
+ return null;
47240
47172
  }
47241
-
47242
- // src/types/api-to-bridge-messages.ts
47243
- var API_TO_BRIDGE_MESSAGE_TYPES = [
47244
- "auth_token",
47245
- "bridge_identified",
47246
- "ha",
47247
- "preview_environments_config",
47248
- "preview_environment_control",
47249
- "deploy_session_to_preview",
47250
- "agent_config",
47251
- "prompt_queue_state",
47252
- "prompt",
47253
- "session_git_request",
47254
- "rename_session_branch",
47255
- "session_archived",
47256
- "session_discarded",
47257
- "revert_turn_snapshot",
47258
- "cursor_request_response",
47259
- "skill_call",
47260
- "file_browser_request",
47261
- "file_browser_search",
47262
- "code_nav",
47263
- "skill_layout_request",
47264
- "install_skills",
47265
- "refresh_local_skills",
47266
- "bridge_git_context_request",
47267
- "list_repo_branches_request"
47268
- ];
47269
- var API_TO_BRIDGE_TYPE_SET = new Set(API_TO_BRIDGE_MESSAGE_TYPES);
47270
- function parseApiToBridgeMessage(data, log2) {
47271
- if (data === null || typeof data !== "object" || Array.isArray(data)) return null;
47272
- const t = data.type;
47273
- if (typeof t !== "string" || !API_TO_BRIDGE_TYPE_SET.has(t)) {
47274
- if (typeof t === "string") {
47275
- log2?.(`[Bridge service] unhandled message type: ${t}`);
47276
- }
47277
- return null;
47278
- }
47279
- if (t === "ha") {
47280
- const s = data.s;
47281
- if (typeof s !== "number" || !Number.isFinite(s)) return null;
47173
+ function applyCorsHeaders(req, res) {
47174
+ const origin = typeof req.headers.origin === "string" ? req.headers.origin : void 0;
47175
+ const allowed = resolveAllowedCorsOrigin(origin);
47176
+ if (allowed) {
47177
+ res.setHeader("Access-Control-Allow-Origin", allowed);
47178
+ res.setHeader("Vary", "Origin");
47179
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
47180
+ res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
47181
+ res.setHeader("Access-Control-Max-Age", "86400");
47282
47182
  }
47283
- return data;
47183
+ return allowed != null;
47284
47184
  }
47285
47185
 
47286
- // src/routing/handlers/auth-token.ts
47287
- var handleAuthToken = (msg, { log: log2 }) => {
47288
- if (typeof msg.token !== "string") return;
47289
- log2("Received auth token. Save it for future runs:");
47290
- log2(` export BUILDAUTOMATON_AUTH_TOKEN="${msg.token}"`);
47291
- };
47292
-
47293
- // src/routing/handlers/bridge-identified.ts
47294
- var handleBridgeIdentified = (msg, deps) => {
47295
- if (typeof msg.bridgeName !== "string") return;
47296
- deps.onBridgeIdentified({
47297
- bridgeName: msg.bridgeName,
47298
- proxyPorts: msg.proxyPorts,
47299
- previewEnvironments: msg.previewEnvironments
47300
- });
47301
- setImmediate(() => {
47302
- void (async () => {
47303
- cancelInFlightAgentCapabilityWarmup();
47304
- try {
47305
- await deps.reportAutoDetectedAgents?.();
47306
- } catch (e) {
47307
- deps.log(
47308
- `[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
47309
- );
47310
- }
47311
- try {
47312
- await deps.warmupAgentCapabilitiesOnConnect?.();
47313
- } catch (e) {
47314
- deps.log(
47315
- `[Bridge service] Agent capability warmup failed: ${e instanceof Error ? e.message : String(e)}`
47316
- );
47317
- }
47318
- })();
47319
- });
47320
- setImmediate(() => {
47321
- if (isCliImmediateShutdownRequested()) return;
47322
- try {
47323
- deps.sendLocalSkillsReport?.();
47324
- } catch (e) {
47325
- deps.log(
47326
- `[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
47327
- );
47328
- }
47329
- });
47330
- };
47331
-
47332
- // src/connection/heartbeat/ack.ts
47333
- var handleBridgeHeartbeatAck = (msg, deps) => {
47334
- const raw = msg.s;
47335
- if (typeof raw !== "number" || !Number.isFinite(raw)) return;
47336
- deps.onBridgeHeartbeatAck?.(Math.trunc(raw));
47337
- };
47338
-
47339
- // src/agents/acp/from-bridge/handle-bridge-agent-config.ts
47340
- function handleBridgeAgentConfig(msg, { acpManager }) {
47341
- if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
47342
- acpManager.setPreferredAgentType(msg.agents[0].type);
47186
+ // src/local-shortcut/lib/auth.ts
47187
+ function readBearerToken(req) {
47188
+ const raw = req.headers.authorization;
47189
+ if (typeof raw !== "string") return null;
47190
+ const match = /^Bearer\s+(.+)$/i.exec(raw.trim());
47191
+ return match?.[1]?.trim() ? match[1].trim() : null;
47192
+ }
47193
+ function isAuthorizedShortcutRequest(req, expectedToken) {
47194
+ const token = readBearerToken(req);
47195
+ return Boolean(token && expectedToken && token === expectedToken);
47343
47196
  }
47344
47197
 
47345
- // src/routing/handlers/agent-config.ts
47346
- var handleAgentConfigMessage = (msg, deps) => {
47347
- handleBridgeAgentConfig(msg, deps);
47348
- };
47198
+ // src/local-shortcut/lib/e2ee/file-browser-fields.ts
47199
+ var SHORTCUT_FILE_BROWSER_E2EE_FIELDS = [
47200
+ "entries",
47201
+ "content",
47202
+ "totalLines",
47203
+ "size",
47204
+ "lineOffset"
47205
+ ];
47349
47206
 
47350
- // src/prompt-turn-queue/client-report.ts
47351
- function sendPromptQueueClientReport(ws, queues) {
47352
- if (!ws) return false;
47353
- const wireQueues = {};
47354
- for (const [queueKey, rows] of Object.entries(queues)) {
47355
- wireQueues[queueKey] = rows.map((r) => ({ turnId: r.turnId, clientState: r.cliState }));
47207
+ // src/local-shortcut/lib/e2ee/encrypt-file-browser-body.ts
47208
+ function encryptShortcutFileBrowserBody(body, e2ee) {
47209
+ if (!e2ee) return body;
47210
+ const encrypted = e2ee.encryptFields(body, SHORTCUT_FILE_BROWSER_E2EE_FIELDS);
47211
+ if (encrypted.ee != null && typeof encrypted.ee === "object") {
47212
+ return { ee: encrypted.ee };
47356
47213
  }
47357
- sendWsMessage(ws, { type: "prompt_queue_client_report", queues: wireQueues });
47358
- return true;
47214
+ return body;
47359
47215
  }
47360
47216
 
47361
- // src/prompt-turn-queue/disk-store.ts
47362
- var MERGEABLE_BRIDGE_SERVER_STATES = /* @__PURE__ */ new Set([
47363
- "queued",
47364
- "requeued",
47365
- "requeued_with_revert",
47366
- "cancel_requested",
47367
- "stopping",
47368
- "discarded"
47369
- ]);
47370
- async function readPersistedQueue(queueKey) {
47371
- return withCliSqlite((db) => {
47372
- const row = db.get("SELECT queue_key, updated_at, turns_json FROM prompt_queue WHERE queue_key = ?", [
47373
- queueKey
47374
- ]);
47375
- if (!row) return null;
47376
- try {
47377
- const turns = JSON.parse(row.turns_json);
47378
- if (!Array.isArray(turns)) return null;
47379
- return { queueKey: row.queue_key, updatedAt: row.updated_at, turns };
47380
- } catch {
47381
- return null;
47382
- }
47383
- });
47384
- }
47385
- async function writePersistedQueue(file2) {
47386
- await withCliSqlite((db) => {
47387
- db.run(
47388
- `INSERT INTO prompt_queue (queue_key, updated_at, turns_json) VALUES (?, ?, ?)
47389
- ON CONFLICT(queue_key) DO UPDATE SET updated_at = excluded.updated_at, turns_json = excluded.turns_json`,
47390
- [file2.queueKey, file2.updatedAt, JSON.stringify(file2.turns)]
47391
- );
47392
- });
47217
+ // src/local-shortcut/lib/response.ts
47218
+ function sendShortcutJson(res, status, body) {
47219
+ if (res.headersSent) return;
47220
+ res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
47221
+ res.end(JSON.stringify(body));
47393
47222
  }
47394
- async function mergeBridgeServerQueueSnapshot(queueKey, serverTurns) {
47395
- const prev = await readPersistedQueue(queueKey);
47396
- const turns = [];
47397
- for (const raw of serverTurns) {
47398
- if (!raw || typeof raw !== "object") continue;
47399
- const o = raw;
47400
- const turnId = typeof o.turnId === "string" ? o.turnId : "";
47401
- const sessionId = typeof o.sessionId === "string" ? o.sessionId : "";
47402
- const turnOrd = typeof o.turnOrd === "number" ? o.turnOrd : Number(o.turnOrd) || 0;
47403
- const bridgeServerState = o.serverState ?? o.bridgeServerState;
47404
- const lastCliState = o.lastClientState ?? o.lastCliState ?? null;
47405
- const payload = o.payload && typeof o.payload === "object" ? o.payload : {};
47406
- if (!turnId || !sessionId) continue;
47407
- if (!MERGEABLE_BRIDGE_SERVER_STATES.has(String(bridgeServerState))) continue;
47408
- const old = prev?.turns.find((t) => t.turnId === turnId);
47409
- const mergedCli = old?.lastCliState === "running" && lastCliState == null ? "running" : lastCliState;
47410
- turns.push({
47411
- turnId,
47412
- sessionId,
47413
- turnOrd,
47414
- bridgeServerState,
47415
- lastCliState: mergedCli,
47416
- payload
47417
- });
47418
- }
47419
- turns.sort((a, b) => a.turnOrd - b.turnOrd);
47420
- return { queueKey, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), turns };
47223
+ function sendShortcutFileBrowserJson(res, status, body, e2ee) {
47224
+ sendShortcutJson(res, status, encryptShortcutFileBrowserBody(body, e2ee));
47421
47225
  }
47422
47226
 
47423
- // src/prompt-turn-queue/runner/dispatch-local-prompt.ts
47424
- function dispatchLocalPrompt(next, deps) {
47425
- const pl = next.payload;
47426
- const rawParent = pl["sessionParent"];
47427
- const sessionParent = rawParent === "bridge_root" || rawParent === "worktrees_root" ? rawParent : void 0;
47428
- const rawParentPath = pl["sessionParentPath"];
47429
- const sessionParentPath = typeof rawParentPath === "string" && rawParentPath.trim() !== "" ? rawParentPath.trim() : void 0;
47430
- const rawBaseBranches = pl["worktreeBaseBranches"];
47431
- const worktreeBaseBranches = rawBaseBranches != null && typeof rawBaseBranches === "object" && !Array.isArray(rawBaseBranches) ? rawBaseBranches : void 0;
47432
- const msg = {
47433
- type: "prompt",
47434
- sessionId: next.sessionId,
47435
- runId: next.turnId,
47436
- prompt: pl.prompt,
47437
- mode: typeof pl.mode === "string" ? pl.mode : "agent",
47438
- isNewSession: pl.isNewSession === true,
47439
- ...sessionParent ? { sessionParent } : {},
47440
- ...sessionParentPath ? { sessionParentPath } : {},
47441
- ...worktreeBaseBranches && Object.keys(worktreeBaseBranches).length > 0 ? { worktreeBaseBranches } : {},
47442
- ...typeof pl.followUpCatalogPromptId === "string" ? { followUpCatalogPromptId: pl.followUpCatalogPromptId } : {},
47443
- ...typeof pl.agentType === "string" && pl.agentType.trim() ? { agentType: pl.agentType.trim() } : {},
47444
- ...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {},
47445
- ...Array.isArray(pl.attachments) && pl.attachments.length > 0 ? { attachments: pl.attachments } : {}
47446
- };
47447
- handleBridgePrompt(msg, deps);
47227
+ // src/local-shortcut/app/api/bridges/local-shortcut/health/route.ts
47228
+ async function GET(ctx) {
47229
+ sendShortcutJson(ctx.res, 200, { ok: true });
47448
47230
  }
47449
47231
 
47450
- // src/prompt-turn-queue/runner/queue-selection.ts
47451
- function pickNextRunnableTurn(turns) {
47452
- for (const t of turns) {
47453
- if (t.bridgeServerState === "discarded" || t.bridgeServerState === "stopping") continue;
47454
- if (t.bridgeServerState === "cancel_requested") continue;
47455
- if (!isRunnableBridgePromptTurnServerState(t.bridgeServerState)) continue;
47456
- if (t.lastCliState === "running" || t.lastCliState === "stopped" || t.lastCliState === "failed" || t.lastCliState === "cancelled") {
47457
- continue;
47458
- }
47459
- return t;
47232
+ // src/local-shortcut/app/lib/assert-bridge-workspace.ts
47233
+ function assertBridgeWorkspace(ctx) {
47234
+ const reqWorkspaceId = ctx.url.searchParams.get("workspaceId")?.trim() ?? "";
47235
+ if (reqWorkspaceId !== ctx.workspaceId) {
47236
+ sendShortcutJson(ctx.res, 403, { error: "workspaceId mismatch" });
47237
+ return false;
47460
47238
  }
47461
- return null;
47462
- }
47463
- function hasRunningTurn(turns) {
47464
- return turns.some((t) => t.lastCliState === "running");
47239
+ return true;
47465
47240
  }
47466
47241
 
47467
- // src/prompt-turn-queue/runner/run-id-queue-key-map.ts
47468
- var runIdToQueueKey = /* @__PURE__ */ new Map();
47469
- function getRunIdQueueKey(runId) {
47470
- return runIdToQueueKey.get(runId);
47471
- }
47472
- function setRunIdQueueKey(runId, queueKey) {
47473
- runIdToQueueKey.set(runId, queueKey);
47474
- }
47475
- function deleteRunIdQueueKey(runId) {
47476
- const queueKey = runIdToQueueKey.get(runId);
47477
- runIdToQueueKey.delete(runId);
47478
- return queueKey;
47479
- }
47480
- function syncRunningTurnQueueKeys(turns, queueKey) {
47481
- for (const running of turns.filter((t) => t.lastCliState === "running")) {
47482
- runIdToQueueKey.set(running.turnId, queueKey);
47242
+ // src/files/browser/execute-file-browser-list.ts
47243
+ init_list_dir();
47244
+
47245
+ // src/git/tree/resolve-repo-abs-path.ts
47246
+ init_cwd();
47247
+ import * as path75 from "node:path";
47248
+ function resolveRepoAbsPathFromBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
47249
+ const bridgeResolved = path75.resolve(bridgeRoot);
47250
+ const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
47251
+ const repoPath = rel === "" ? bridgeResolved : path75.join(bridgeResolved, rel);
47252
+ const resolved = path75.resolve(repoPath);
47253
+ if (!resolved.startsWith(bridgeResolved + path75.sep) && resolved !== bridgeResolved) {
47254
+ return null;
47483
47255
  }
47256
+ return resolved;
47484
47257
  }
47485
47258
 
47486
- // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
47487
- import fs47 from "node:fs";
47488
-
47489
- // src/git/snapshot/capture.ts
47490
- import * as fs46 from "node:fs";
47491
- import * as path73 from "node:path";
47492
- async function resolveSnapshotRepoRoots(options) {
47493
- const { worktreePaths, fallbackCwd, sessionId, log: log2 } = options;
47494
- if (worktreePaths?.length) {
47495
- const uniq = [...new Set(worktreePaths.map((p) => path73.resolve(p)))];
47496
- return uniq;
47497
- }
47259
+ // src/git/tree/resolve-branch-commit.ts
47260
+ async function resolveBranchCommit(repoAbsPath, branch) {
47261
+ const branchRef = branch.trim();
47262
+ if (!branchRef) return null;
47498
47263
  try {
47499
- const repos = await discoverGitReposUnderRoot(fallbackCwd);
47500
- const mapped = repos.map((r) => r.absolutePath);
47501
- const sid = sessionId?.trim();
47502
- if (sid) {
47503
- const filtered = mapped.filter((root) => path73.basename(root) === sid);
47504
- if (filtered.length > 0) return filtered;
47505
- }
47506
- return mapped;
47507
- } catch (e) {
47508
- log2(`[snapshot] Discover repositories failed: ${e instanceof Error ? e.message : String(e)}`);
47509
- return [];
47264
+ return (await cliSimpleGit(repoAbsPath).revparse([`${branchRef}^{commit}`])).trim();
47265
+ } catch {
47266
+ return null;
47510
47267
  }
47511
47268
  }
47512
- async function capturePreTurnSnapshot(options) {
47513
- const { runId, repoRoots, agentCwd, log: log2 } = options;
47514
- if (!runId || !repoRoots.length) {
47515
- return { ok: false, error: "No git repos to snapshot" };
47516
- }
47517
- const repos = [];
47518
- await forEachWithGitYield(repoRoots, async (root) => {
47519
- const stashSha = await gitStashCreate(root, log2);
47520
- const untrackedPaths = await gitUntrackedPaths(root, log2);
47521
- repos.push({ path: root, stashSha, untrackedPaths });
47522
- });
47523
- const dir = snapshotsDirForCwd(agentCwd);
47524
- try {
47525
- fs46.mkdirSync(dir, { recursive: true });
47526
- } catch (e) {
47527
- return { ok: false, error: e instanceof Error ? e.message : String(e) };
47528
- }
47529
- const payload = {
47530
- runId,
47531
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
47532
- repos
47533
- };
47534
- const filePath = path73.join(dir, `${runId}.json`);
47535
- try {
47536
- fs46.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
47537
- } catch (e) {
47538
- return { ok: false, error: e instanceof Error ? e.message : String(e) };
47269
+
47270
+ // src/git/tree/list-git-tree-dir.ts
47271
+ init_yield_to_event_loop();
47272
+ init_types2();
47273
+ init_sort_entries();
47274
+ function parseLsTreeLine(line) {
47275
+ const tab = line.indexOf(" ");
47276
+ if (tab < 0) return null;
47277
+ const meta = line.slice(0, tab).trim().split(/\s+/);
47278
+ if (meta.length < 3) return null;
47279
+ const mode = meta[0];
47280
+ const objectType2 = meta[1];
47281
+ let name = line.slice(tab + 1);
47282
+ if (name.startsWith('"') && name.endsWith('"')) {
47283
+ name = name.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
47539
47284
  }
47540
- const repoList = repos.map((r) => r.path).join(", ");
47541
- log2(
47542
- `[snapshot] Saved pre-turn snapshot ${runId.slice(0, 8)}\u2026 (${repos.length} repo(s)): ${repoList}`
47543
- );
47544
- return { ok: true, filePath, repos };
47285
+ return { mode, objectType: objectType2, name };
47545
47286
  }
47546
- async function applyPreTurnSnapshot(filePath, log2) {
47547
- let data;
47287
+ function childPath(relativePath, name) {
47288
+ const base = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
47289
+ if (!base || base === ".") return name;
47290
+ return `${base}/${name}`.replace(/\\/g, "/");
47291
+ }
47292
+ function lsTreeArgs(commit, relativePath) {
47293
+ const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
47294
+ if (!rel || rel === ".") return ["ls-tree", commit];
47295
+ return ["ls-tree", `${commit}:${rel}`];
47296
+ }
47297
+ async function listGitTreeDirAsync(repoAbsPath, branch, relativePath) {
47298
+ await yieldToEventLoop();
47299
+ const commit = await resolveBranchCommit(repoAbsPath, branch);
47300
+ if (!commit) return { error: "Branch not found" };
47548
47301
  try {
47549
- const raw = fs46.readFileSync(filePath, "utf8");
47550
- data = JSON.parse(raw);
47551
- } catch (e) {
47552
- return { ok: false, error: e instanceof Error ? e.message : String(e) };
47553
- }
47554
- if (!Array.isArray(data.repos)) {
47555
- return { ok: false, error: "Invalid snapshot file" };
47556
- }
47557
- let applyError = null;
47558
- await forEachWithGitYield(data.repos, async (r) => {
47559
- if (applyError || !r.path) return;
47560
- const reset = await gitRun(r.path, ["reset", "--hard", "HEAD"], log2, "reset --hard");
47561
- if (!reset.ok) {
47562
- applyError = reset;
47563
- return;
47302
+ const raw = await cliSimpleGit(repoAbsPath).raw(lsTreeArgs(commit, relativePath));
47303
+ await yieldToEventLoop();
47304
+ const lines = String(raw ?? "").split(/\r?\n/).filter((line) => line.trim().length > 0);
47305
+ const entries = [];
47306
+ for (let i = 0; i < lines.length; i++) {
47307
+ if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
47308
+ await yieldToEventLoop();
47309
+ }
47310
+ const parsed = parseLsTreeLine(lines[i]);
47311
+ if (!parsed) continue;
47312
+ const { mode, objectType: objectType2, name } = parsed;
47313
+ if (!name || name === "." || name === "..") continue;
47314
+ const isSymlink = mode === "120000";
47315
+ const isDir = objectType2 === "tree";
47316
+ entries.push({
47317
+ name,
47318
+ path: childPath(relativePath, name),
47319
+ isDir,
47320
+ ...isSymlink ? { isSymlink: true } : {}
47321
+ });
47564
47322
  }
47565
- const clean = await gitRun(r.path, ["clean", "-fd"], log2, "clean -fd");
47566
- if (!clean.ok) {
47567
- applyError = clean;
47568
- return;
47323
+ if (entries.length >= LIST_DIR_YIELD_EVERY) {
47324
+ await yieldToEventLoop();
47569
47325
  }
47570
- if (r.stashSha) {
47571
- const ap = await gitRun(r.path, ["stash", "apply", r.stashSha], log2, "stash apply");
47572
- if (!ap.ok) applyError = ap;
47326
+ return { entries: sortListEntries(entries) };
47327
+ } catch (err) {
47328
+ const message = err instanceof Error ? err.message : String(err);
47329
+ if (/not a tree object|does not exist|bad revision/i.test(message)) {
47330
+ return { error: "Path not found in branch" };
47573
47331
  }
47574
- });
47575
- if (applyError) return applyError;
47576
- log2(`[snapshot] Restored pre-turn state for ${data.runId.slice(0, 8)}\u2026`);
47577
- return { ok: true };
47578
- }
47579
-
47580
- // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
47581
- init_cwd();
47582
- async function runLocalRevertBeforeQueuedPrompt(next, deps) {
47583
- if (next.bridgeServerState !== "requeued_with_revert") return true;
47584
- const sid = next.sessionId;
47585
- const pl = next.payload;
47586
- const tid = typeof pl.snapshotRevertTurnId === "string" && pl.snapshotRevertTurnId.trim() !== "" ? pl.snapshotRevertTurnId.trim() : next.turnId;
47587
- const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
47588
- const file2 = snapshotFilePath(agentBase, tid);
47589
- try {
47590
- await fs47.promises.access(file2, fs47.constants.F_OK);
47591
- } catch {
47592
- deps.log(
47593
- `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
47594
- );
47595
- return true;
47596
- }
47597
- const res = await applyPreTurnSnapshot(file2, deps.log);
47598
- if (!res.ok) {
47599
- deps.log(`[Queue] requeued_with_revert: snapshot apply failed: ${res.error ?? "unknown"}`);
47332
+ return { error: message };
47600
47333
  }
47601
- return res.ok;
47602
47334
  }
47603
47335
 
47604
- // src/prompt-turn-queue/runner/finalize-prompt-turn-on-bridge.ts
47605
- async function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
47606
- if (!runId) return false;
47607
- const queueKey = deleteRunIdQueueKey(runId);
47608
- if (!queueKey) return false;
47609
- const f = await readPersistedQueue(queueKey);
47610
- if (!f) return false;
47611
- const t = f.turns.find((x) => x.turnId === runId);
47612
- if (!t) return false;
47613
- t.lastCliState = opts?.terminalCliState ?? (success2 ? "stopped" : "failed");
47614
- await writePersistedQueue(f);
47615
- sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: runId, cliState: t.lastCliState }] });
47616
- return true;
47336
+ // src/git/tree/file/index.ts
47337
+ init_yield_to_event_loop();
47338
+
47339
+ // src/files/read-file/types.ts
47340
+ var LINE_CHUNK_SIZE = 64 * 1024;
47341
+ var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
47342
+
47343
+ // src/files/read-file/guess-mime-type.ts
47344
+ var MIME_BY_EXT = {
47345
+ png: "image/png",
47346
+ jpg: "image/jpeg",
47347
+ jpeg: "image/jpeg",
47348
+ gif: "image/gif",
47349
+ bmp: "image/bmp",
47350
+ ico: "image/x-icon",
47351
+ webp: "image/webp",
47352
+ avif: "image/avif",
47353
+ svg: "image/svg+xml",
47354
+ pdf: "application/pdf",
47355
+ json: "application/json",
47356
+ html: "text/html",
47357
+ htm: "text/html",
47358
+ css: "text/css",
47359
+ js: "text/javascript",
47360
+ mjs: "text/javascript",
47361
+ ts: "text/typescript",
47362
+ txt: "text/plain",
47363
+ md: "text/markdown",
47364
+ xml: "application/xml",
47365
+ zip: "application/zip",
47366
+ gz: "application/gzip",
47367
+ wasm: "application/wasm"
47368
+ };
47369
+ function guessMimeType(filePath) {
47370
+ const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
47371
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
47372
+ }
47373
+
47374
+ // src/git/tree/file/git-blob-ref.ts
47375
+ init_yield_to_event_loop();
47376
+ async function gitBlobRef(repoAbsPath, branch, relativePath) {
47377
+ const commit = await resolveBranchCommit(repoAbsPath, branch);
47378
+ await yieldToEventLoop();
47379
+ if (!commit) return null;
47380
+ const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
47381
+ if (!rel || rel === ".") return null;
47382
+ return `${commit}:${rel}`;
47383
+ }
47384
+
47385
+ // src/git/tree/file/is-binary-buffer.ts
47386
+ function isBinaryBuffer(buf) {
47387
+ const sample = buf.subarray(0, Math.min(buf.length, 8192));
47388
+ for (let i = 0; i < sample.length; i++) {
47389
+ if (sample[i] === 0) return true;
47390
+ }
47391
+ return false;
47392
+ }
47393
+
47394
+ // src/git/tree/file/read-git-blob-text-async.ts
47395
+ init_yield_to_event_loop();
47396
+ import { StringDecoder } from "node:string_decoder";
47397
+ async function countGitBlobLinesAsync(buf) {
47398
+ if (buf.length === 0) return 1;
47399
+ let lines = 1;
47400
+ let bytesSinceYield = 0;
47401
+ for (let i = 0; i < buf.length; i++) {
47402
+ const b = buf[i];
47403
+ if (b === 10) {
47404
+ lines++;
47405
+ } else if (b === 13) {
47406
+ if (i + 1 < buf.length && buf[i + 1] === 10) {
47407
+ lines++;
47408
+ i++;
47409
+ } else {
47410
+ lines++;
47411
+ }
47412
+ }
47413
+ bytesSinceYield++;
47414
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
47415
+ await yieldToEventLoop();
47416
+ bytesSinceYield = 0;
47417
+ }
47418
+ }
47419
+ return lines;
47420
+ }
47421
+ async function readGitBlobLineRangeAsync(buf, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
47422
+ const bufSize = 64 * 1024;
47423
+ const decoder = new StringDecoder("utf8");
47424
+ let currentLine = 0;
47425
+ const resultLines = [];
47426
+ let partial2 = "";
47427
+ let done = false;
47428
+ let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
47429
+ let line0CharsReturned = 0;
47430
+ let line0Accum = "";
47431
+ let bytesSinceYield = 0;
47432
+ let position = 0;
47433
+ const finish = async () => {
47434
+ const totalLines = await countGitBlobLinesAsync(buf);
47435
+ return { content: resultLines.join("\n"), totalLines };
47436
+ };
47437
+ while (!done && position < buf.length) {
47438
+ const chunkEnd = Math.min(position + bufSize, buf.length);
47439
+ const bytesRead = chunkEnd - position;
47440
+ position = chunkEnd;
47441
+ bytesSinceYield += bytesRead;
47442
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
47443
+ await yieldToEventLoop();
47444
+ bytesSinceYield = 0;
47445
+ }
47446
+ const text = partial2 + decoder.write(buf.subarray(position - bytesRead, position));
47447
+ partial2 = "";
47448
+ let lineStart = 0;
47449
+ for (let i = 0; i < text.length; i++) {
47450
+ if (text[i] === "\n") {
47451
+ const lineContent = (() => {
47452
+ let lineEnd = i;
47453
+ if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
47454
+ return text.slice(lineStart, lineEnd);
47455
+ })();
47456
+ if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
47457
+ line0Accum += lineContent;
47458
+ const totalLine0 = line0Accum.length;
47459
+ if (skipLine0Chars > 0) {
47460
+ if (totalLine0 <= skipLine0Chars) {
47461
+ skipLine0Chars -= totalLine0;
47462
+ line0Accum = "";
47463
+ currentLine++;
47464
+ lineStart = i + 1;
47465
+ if (currentLine > endLine) {
47466
+ done = true;
47467
+ break;
47468
+ }
47469
+ continue;
47470
+ }
47471
+ const from = skipLine0Chars;
47472
+ const take = Math.min(lineChunkSize, totalLine0 - from);
47473
+ resultLines.push(line0Accum.slice(from, from + take));
47474
+ line0CharsReturned += take;
47475
+ if (from + take < totalLine0) {
47476
+ const totalLines = await countGitBlobLinesAsync(buf);
47477
+ return {
47478
+ content: resultLines.join("\n"),
47479
+ totalLines,
47480
+ lineOffset: lineOffsetIn + line0CharsReturned
47481
+ };
47482
+ }
47483
+ line0Accum = "";
47484
+ skipLine0Chars = 0;
47485
+ line0CharsReturned = 0;
47486
+ } else if (totalLine0 > lineChunkSize) {
47487
+ resultLines.push(line0Accum.slice(0, lineChunkSize));
47488
+ const totalLines = await countGitBlobLinesAsync(buf);
47489
+ return {
47490
+ content: resultLines.join("\n"),
47491
+ totalLines,
47492
+ lineOffset: lineChunkSize
47493
+ };
47494
+ } else {
47495
+ resultLines.push(line0Accum);
47496
+ line0Accum = "";
47497
+ }
47498
+ } else if (currentLine >= startLine && currentLine <= endLine) {
47499
+ resultLines.push(lineContent);
47500
+ }
47501
+ currentLine++;
47502
+ lineStart = i + 1;
47503
+ if (currentLine > endLine) {
47504
+ done = true;
47505
+ break;
47506
+ }
47507
+ }
47508
+ }
47509
+ if (!done) {
47510
+ const lineContent = text.slice(lineStart);
47511
+ if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
47512
+ line0Accum += lineContent;
47513
+ const totalLine0 = line0Accum.length;
47514
+ if (skipLine0Chars > 0) {
47515
+ if (totalLine0 <= skipLine0Chars) {
47516
+ skipLine0Chars -= totalLine0;
47517
+ line0Accum = "";
47518
+ } else {
47519
+ const from = skipLine0Chars;
47520
+ const take = Math.min(lineChunkSize, totalLine0 - from);
47521
+ resultLines.push(line0Accum.slice(from, from + take));
47522
+ const totalLines = await countGitBlobLinesAsync(buf);
47523
+ return {
47524
+ content: resultLines.join("\n"),
47525
+ totalLines,
47526
+ lineOffset: (lineOffsetIn ?? 0) + take
47527
+ };
47528
+ }
47529
+ } else if (totalLine0 > lineChunkSize) {
47530
+ resultLines.push(line0Accum.slice(0, lineChunkSize));
47531
+ const totalLines = await countGitBlobLinesAsync(buf);
47532
+ return {
47533
+ content: resultLines.join("\n"),
47534
+ totalLines,
47535
+ lineOffset: lineChunkSize
47536
+ };
47537
+ }
47538
+ }
47539
+ partial2 = text.slice(lineStart);
47540
+ }
47541
+ }
47542
+ if (!done) {
47543
+ const tail = partial2 + decoder.end();
47544
+ if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0) && tail.length > 0) {
47545
+ line0Accum += tail.endsWith("\r") ? tail.slice(0, -1) : tail;
47546
+ const totalLine0 = line0Accum.length;
47547
+ if (skipLine0Chars > 0) {
47548
+ if (totalLine0 <= skipLine0Chars) {
47549
+ return finish();
47550
+ }
47551
+ const from = skipLine0Chars;
47552
+ const take = Math.min(lineChunkSize, totalLine0 - from);
47553
+ resultLines.push(line0Accum.slice(from, from + take));
47554
+ line0CharsReturned += take;
47555
+ if (from + take < totalLine0) {
47556
+ const totalLines = await countGitBlobLinesAsync(buf);
47557
+ return {
47558
+ content: resultLines.join("\n"),
47559
+ totalLines,
47560
+ lineOffset: (lineOffsetIn ?? 0) + line0CharsReturned
47561
+ };
47562
+ }
47563
+ } else if (totalLine0 > lineChunkSize) {
47564
+ resultLines.push(line0Accum.slice(0, lineChunkSize));
47565
+ const totalLines = await countGitBlobLinesAsync(buf);
47566
+ return {
47567
+ content: resultLines.join("\n"),
47568
+ totalLines,
47569
+ lineOffset: lineChunkSize
47570
+ };
47571
+ } else {
47572
+ resultLines.push(line0Accum);
47573
+ }
47574
+ } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
47575
+ resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
47576
+ }
47577
+ }
47578
+ return finish();
47579
+ }
47580
+ async function decodeGitBlobUtf8Async(buf) {
47581
+ await yieldToEventLoop();
47582
+ const text = buf.toString("utf8");
47583
+ await yieldToEventLoop();
47584
+ return text;
47585
+ }
47586
+ async function readGitBlobUtf8FullAsync(buf) {
47587
+ const text = await decodeGitBlobUtf8Async(buf);
47588
+ const totalLines = await countGitBlobLinesAsync(buf);
47589
+ return { content: text, totalLines };
47590
+ }
47591
+
47592
+ // src/git/tree/file/index.ts
47593
+ async function readGitTreeFileAsync(repoAbsPath, branch, relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, encoding = "utf8") {
47594
+ await yieldToEventLoop();
47595
+ const branchRef = branch.trim();
47596
+ if (!branchRef) return { error: "branch required" };
47597
+ const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
47598
+ if (!rel || rel === ".") return { error: "file path required" };
47599
+ const blobRef = await gitBlobRef(repoAbsPath, branchRef, rel);
47600
+ if (!blobRef) return { error: "Branch not found" };
47601
+ const hasRange = typeof startLine === "number" && typeof endLine === "number";
47602
+ try {
47603
+ if (encoding === "base64") {
47604
+ if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
47605
+ const buf2 = await cliSimpleGit(repoAbsPath).showBuffer([blobRef]);
47606
+ await yieldToEventLoop();
47607
+ return {
47608
+ content: buf2.toString("base64"),
47609
+ size: buf2.length,
47610
+ mimeType: guessMimeType(rel)
47611
+ };
47612
+ }
47613
+ const buf = await cliSimpleGit(repoAbsPath).showBuffer([blobRef]);
47614
+ await yieldToEventLoop();
47615
+ const size = buf.length;
47616
+ if (isBinaryBuffer(buf)) {
47617
+ return { error: "Binary file cannot be displayed as text" };
47618
+ }
47619
+ if (hasRange) {
47620
+ const ranged = await readGitBlobLineRangeAsync(buf, startLine, endLine, lineOffset, lineChunkSize);
47621
+ return {
47622
+ content: ranged.content,
47623
+ totalLines: ranged.totalLines,
47624
+ size,
47625
+ ...ranged.lineOffset != null ? { lineOffset: ranged.lineOffset } : {}
47626
+ };
47627
+ }
47628
+ const full = await readGitBlobUtf8FullAsync(buf);
47629
+ return { content: full.content, totalLines: full.totalLines, size, mimeType: guessMimeType(rel) };
47630
+ } catch (err) {
47631
+ const message = err instanceof Error ? err.message : String(err);
47632
+ if (/does not exist|bad revision|exists on disk, but not in/i.test(message)) {
47633
+ return { error: "File not found in branch" };
47634
+ }
47635
+ return { error: message };
47636
+ }
47637
+ }
47638
+
47639
+ // src/files/browser/execute-file-browser-list.ts
47640
+ async function executeFileBrowserList(params) {
47641
+ const { reqPath, sessionParentPath, gitScope } = params;
47642
+ return gitScope ? await listGitTreeDirAsync(gitScope.repoAbsPath, gitScope.branch, reqPath) : await listDirAsync(reqPath, sessionParentPath);
47643
+ }
47644
+
47645
+ // src/files/browser/resolve-git-branch-scope.ts
47646
+ function resolveGitBranchScope(msg) {
47647
+ const gitBranchScope = msg.source === "git_branch" && typeof msg.repoRelPath === "string" && typeof msg.branch === "string" && msg.branch.trim().length > 0;
47648
+ if (!gitBranchScope) return null;
47649
+ const repoAbsPath = resolveRepoAbsPathFromBridge(msg.repoRelPath);
47650
+ if (!repoAbsPath) return null;
47651
+ return { repoAbsPath, branch: msg.branch.trim() };
47652
+ }
47653
+
47654
+ // src/local-shortcut/app/lib/file-browser/parse-query.ts
47655
+ function parseOptionalInt(raw) {
47656
+ if (raw == null) return void 0;
47657
+ const parsed = parseInt(raw, 10);
47658
+ return Number.isInteger(parsed) ? parsed : void 0;
47659
+ }
47660
+ function parseGitScopeFromUrl(url2) {
47661
+ const rawSource = url2.searchParams.get("source")?.trim();
47662
+ if (rawSource === "git_branch") {
47663
+ return {
47664
+ source: "git_branch",
47665
+ repoRelPath: url2.searchParams.get("repoRelPath")?.trim() ?? ".",
47666
+ branch: url2.searchParams.get("branch")?.trim() ?? ""
47667
+ };
47668
+ }
47669
+ return { source: "filesystem" };
47670
+ }
47671
+ function parseReadParams(url2) {
47672
+ const rawEncoding = url2.searchParams.get("encoding");
47673
+ return {
47674
+ startLine: parseOptionalInt(url2.searchParams.get("startLine")),
47675
+ endLine: parseOptionalInt(url2.searchParams.get("endLine")),
47676
+ lineOffset: parseOptionalInt(url2.searchParams.get("lineOffset")),
47677
+ lineChunkSize: parseOptionalInt(url2.searchParams.get("lineChunkSize")),
47678
+ encoding: rawEncoding === "base64" ? "base64" : "utf8"
47679
+ };
47680
+ }
47681
+ function parseListPath(url2) {
47682
+ return (url2.searchParams.get("path") ?? ".").replace(/^\/+/, "") || ".";
47683
+ }
47684
+ function parseReadPath(url2) {
47685
+ const raw = url2.searchParams.get("path")?.trim();
47686
+ if (!raw) return null;
47687
+ return raw.replace(/^\/+/, "") || ".";
47688
+ }
47689
+
47690
+ // src/files/resolve-file-browser-session-parent.ts
47691
+ init_normalize_resolved_path();
47692
+ init_cwd();
47693
+ async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
47694
+ const sid = sessionId?.trim();
47695
+ if (sid) {
47696
+ await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sid);
47697
+ const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
47698
+ if (worktreeRoot) return normalizeResolvedPath(worktreeRoot);
47699
+ }
47700
+ return getBridgeRoot();
47701
+ }
47702
+
47703
+ // src/local-shortcut/app/lib/file-browser/resolve-session-parent.ts
47704
+ async function resolveShortcutSessionParent(ctx, sessionId) {
47705
+ if (!ctx.sessionWorktreeManager || !sessionId?.trim()) return void 0;
47706
+ return resolveFileBrowserSessionParent(ctx.sessionWorktreeManager, sessionId.trim());
47707
+ }
47708
+
47709
+ // src/local-shortcut/app/lib/file-browser/handle-list.ts
47710
+ async function handleShortcutFileBrowserList(ctx, sessionId) {
47711
+ const reqPath = parseListPath(ctx.url);
47712
+ const gitMsg = parseGitScopeFromUrl(ctx.url);
47713
+ const gitScope = resolveGitBranchScope({ id: "shortcut", path: reqPath, ...gitMsg });
47714
+ if (gitMsg.source === "git_branch" && typeof gitMsg.repoRelPath === "string" && typeof gitMsg.branch === "string" && gitMsg.branch.trim().length > 0 && !gitScope) {
47715
+ sendShortcutJson(ctx.res, 400, { error: "Invalid repository path" });
47716
+ return;
47717
+ }
47718
+ const sessionParentPath = await resolveShortcutSessionParent(ctx, sessionId);
47719
+ const result = await executeFileBrowserList({ reqPath, sessionParentPath, gitScope });
47720
+ if ("error" in result) {
47721
+ sendShortcutJson(ctx.res, 400, { error: result.error });
47722
+ return;
47723
+ }
47724
+ sendShortcutFileBrowserJson(ctx.res, 200, { entries: result.entries }, ctx.e2ee);
47725
+ }
47726
+
47727
+ // src/local-shortcut/app/api/bridges/files/list/route.ts
47728
+ async function GET2(ctx) {
47729
+ if (!assertBridgeWorkspace(ctx)) return;
47730
+ await handleShortcutFileBrowserList(ctx);
47731
+ }
47732
+
47733
+ // src/files/browser/file-browser-read-queue.ts
47734
+ var readChain = Promise.resolve();
47735
+ function withFileBrowserReadQueue(fn) {
47736
+ const next = readChain.then(() => fn());
47737
+ readChain = next.then(
47738
+ () => void 0,
47739
+ () => void 0
47740
+ );
47741
+ return next;
47742
+ }
47743
+
47744
+ // src/files/read-file/index.ts
47745
+ init_cwd();
47746
+ init_yield_to_event_loop();
47747
+ import fs53 from "node:fs";
47748
+
47749
+ // src/files/read-file/resolve-file-path.ts
47750
+ init_ensure_under_cwd();
47751
+ init_cwd();
47752
+ import fs49 from "node:fs";
47753
+
47754
+ // src/files/read-file/resolve-file-cache.ts
47755
+ import fs48 from "node:fs";
47756
+ import path76 from "node:path";
47757
+ var RESOLVE_CACHE_MAX = 4096;
47758
+ var resolveCache = /* @__PURE__ */ new Map();
47759
+ var warmedDirectories = /* @__PURE__ */ new Set();
47760
+ function resolveCacheKey(sessionParentPath, relativePath) {
47761
+ return `${sessionParentPath}\0${relativePath}`;
47762
+ }
47763
+ function warmedDirectoryKey(sessionParentPath, directoryRelativePath) {
47764
+ const dir = directoryRelativePath.replace(/^\/+/, "") || ".";
47765
+ return `${path76.resolve(sessionParentPath)}\0${dir}`;
47766
+ }
47767
+ function pathStaysUnderParent(filePath, sessionParentPath) {
47768
+ const parent = path76.resolve(sessionParentPath);
47769
+ const resolved = path76.resolve(filePath);
47770
+ return resolved === parent || resolved.startsWith(`${parent}${path76.sep}`);
47771
+ }
47772
+ async function statMatchesCache(entry) {
47773
+ try {
47774
+ const stat3 = await fs48.promises.stat(entry.path);
47775
+ return stat3.isFile() && stat3.mtimeMs === entry.mtimeMs && stat3.size === entry.size;
47776
+ } catch {
47777
+ return false;
47778
+ }
47779
+ }
47780
+ function getResolveCacheEntry(cacheKey) {
47781
+ return resolveCache.get(cacheKey);
47782
+ }
47783
+ function deleteResolveCacheEntry(cacheKey) {
47784
+ resolveCache.delete(cacheKey);
47785
+ }
47786
+ function storeResolveCacheEntry(cacheKey, entry) {
47787
+ if (resolveCache.size >= RESOLVE_CACHE_MAX) resolveCache.clear();
47788
+ resolveCache.set(cacheKey, entry);
47789
+ return entry;
47790
+ }
47791
+ function hasResolveCacheEntry(cacheKey) {
47792
+ return resolveCache.has(cacheKey);
47793
+ }
47794
+ function markDirectoryWarmed(dirKey) {
47795
+ if (warmedDirectories.has(dirKey)) return false;
47796
+ warmedDirectories.add(dirKey);
47797
+ return true;
47798
+ }
47799
+ function unmarkDirectoryWarmed(dirKey) {
47800
+ warmedDirectories.delete(dirKey);
47801
+ }
47802
+
47803
+ // src/files/read-file/resolve-file-path.ts
47804
+ async function resolveFileForReadAsync(relativePath, sessionParentPath = getBridgeRoot()) {
47805
+ const cacheKey = resolveCacheKey(sessionParentPath, relativePath);
47806
+ const cached2 = getResolveCacheEntry(cacheKey);
47807
+ if (cached2 != null && await statMatchesCache(cached2)) {
47808
+ return { path: cached2.path, size: cached2.size };
47809
+ }
47810
+ if (cached2 != null) deleteResolveCacheEntry(cacheKey);
47811
+ const resolved = ensureUnderCwd(relativePath, sessionParentPath);
47812
+ if (!resolved) return { error: "Path is outside working directory" };
47813
+ let real;
47814
+ let size;
47815
+ let mtimeMs;
47816
+ try {
47817
+ const lstat = await fs49.promises.lstat(resolved);
47818
+ if (lstat.isSymbolicLink()) {
47819
+ real = await fs49.promises.realpath(resolved);
47820
+ if (!pathStaysUnderParent(real, sessionParentPath)) {
47821
+ return { error: "Path is outside working directory" };
47822
+ }
47823
+ const stat3 = await fs49.promises.stat(real);
47824
+ if (!stat3.isFile()) return { error: "Not a file" };
47825
+ size = stat3.size;
47826
+ mtimeMs = stat3.mtimeMs;
47827
+ } else if (lstat.isFile()) {
47828
+ real = resolved;
47829
+ size = lstat.size;
47830
+ mtimeMs = lstat.mtimeMs;
47831
+ } else {
47832
+ return { error: "Not a file" };
47833
+ }
47834
+ } catch (err) {
47835
+ return { error: err instanceof Error ? err.message : String(err) };
47836
+ }
47837
+ const entry = storeResolveCacheEntry(cacheKey, { path: real, size, mtimeMs });
47838
+ return { path: entry.path, size: entry.size };
47839
+ }
47840
+ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
47841
+ const result = await resolveFileForReadAsync(relativePath, sessionParentPath);
47842
+ if ("error" in result) return result;
47843
+ return result.path;
47844
+ }
47845
+
47846
+ // src/files/read-file/read-file-range-async.ts
47847
+ init_yield_to_event_loop();
47848
+ import fs50 from "node:fs";
47849
+ import { StringDecoder as StringDecoder2 } from "node:string_decoder";
47850
+
47851
+ // src/files/read-file/read-file-range-line-zero.ts
47852
+ function createLineZeroStreamState(lineOffsetIn) {
47853
+ return {
47854
+ line0Accum: "",
47855
+ skipLine0Chars: typeof lineOffsetIn === "number" ? lineOffsetIn : 0,
47856
+ line0CharsReturned: 0
47857
+ };
47858
+ }
47859
+ function shouldTrackLineZero(startLine, lineOffsetIn) {
47860
+ return startLine === 0 || lineOffsetIn !== void 0;
47861
+ }
47862
+ function stripCarriageReturn(lineContent) {
47863
+ return lineContent.endsWith("\r") ? lineContent.slice(0, -1) : lineContent;
47864
+ }
47865
+ function lineContentBeforeNewline(text, lineStart, newlineIndex) {
47866
+ let lineEnd = newlineIndex;
47867
+ if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
47868
+ return text.slice(lineStart, lineEnd);
47869
+ }
47870
+ function buildLineZeroChunkResult(resultLines, fileSize, lineOffset, totalLines = 1) {
47871
+ return {
47872
+ content: resultLines.join("\n"),
47873
+ size: fileSize,
47874
+ lineOffset,
47875
+ totalLines
47876
+ };
47877
+ }
47878
+ function tryFinishLineZeroOnCompleteLine(state, lineContent, resultLines, fileSize, lineChunkSize, lineOffsetIn, endLine, currentLine) {
47879
+ state.line0Accum += lineContent;
47880
+ const totalLine0 = state.line0Accum.length;
47881
+ if (state.skipLine0Chars > 0) {
47882
+ if (totalLine0 <= state.skipLine0Chars) {
47883
+ state.skipLine0Chars -= totalLine0;
47884
+ state.line0Accum = "";
47885
+ return currentLine + 1 > endLine ? "done" : "continue";
47886
+ }
47887
+ const from = state.skipLine0Chars;
47888
+ const take = Math.min(lineChunkSize, totalLine0 - from);
47889
+ resultLines.push(state.line0Accum.slice(from, from + take));
47890
+ state.line0CharsReturned += take;
47891
+ if (from + take < totalLine0) {
47892
+ return buildLineZeroChunkResult(
47893
+ resultLines,
47894
+ fileSize,
47895
+ (lineOffsetIn ?? 0) + state.line0CharsReturned
47896
+ );
47897
+ }
47898
+ state.line0Accum = "";
47899
+ state.skipLine0Chars = 0;
47900
+ state.line0CharsReturned = 0;
47901
+ return "continue";
47902
+ }
47903
+ if (totalLine0 > lineChunkSize) {
47904
+ resultLines.push(state.line0Accum.slice(0, lineChunkSize));
47905
+ return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
47906
+ }
47907
+ resultLines.push(state.line0Accum);
47908
+ state.line0Accum = "";
47909
+ return "continue";
47910
+ }
47911
+ function tryFinishLineZeroPartial(state, partialLine, resultLines, fileSize, lineChunkSize, lineOffsetIn) {
47912
+ state.line0Accum += partialLine;
47913
+ const totalLine0 = state.line0Accum.length;
47914
+ if (state.skipLine0Chars > 0) {
47915
+ if (totalLine0 <= state.skipLine0Chars) {
47916
+ state.skipLine0Chars -= totalLine0;
47917
+ state.line0Accum = "";
47918
+ return null;
47919
+ }
47920
+ const from = state.skipLine0Chars;
47921
+ const take = Math.min(lineChunkSize, totalLine0 - from);
47922
+ resultLines.push(state.line0Accum.slice(from, from + take));
47923
+ return buildLineZeroChunkResult(resultLines, fileSize, (lineOffsetIn ?? 0) + take);
47924
+ }
47925
+ if (totalLine0 > lineChunkSize) {
47926
+ resultLines.push(state.line0Accum.slice(0, lineChunkSize));
47927
+ return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
47928
+ }
47929
+ return null;
47930
+ }
47931
+ function finishLineZeroTail(state, tail, resultLines, fileSize, lineChunkSize, lineOffsetIn) {
47932
+ if (tail.length === 0) return null;
47933
+ state.line0Accum += stripCarriageReturn(tail);
47934
+ const totalLine0 = state.line0Accum.length;
47935
+ if (state.skipLine0Chars > 0) {
47936
+ if (totalLine0 <= state.skipLine0Chars) {
47937
+ return { content: resultLines.join("\n"), size: fileSize };
47938
+ }
47939
+ const from = state.skipLine0Chars;
47940
+ const take = Math.min(lineChunkSize, totalLine0 - from);
47941
+ resultLines.push(state.line0Accum.slice(from, from + take));
47942
+ state.line0CharsReturned += take;
47943
+ if (from + take < totalLine0) {
47944
+ return buildLineZeroChunkResult(
47945
+ resultLines,
47946
+ fileSize,
47947
+ (lineOffsetIn ?? 0) + state.line0CharsReturned
47948
+ );
47949
+ }
47950
+ return null;
47951
+ }
47952
+ if (totalLine0 > lineChunkSize) {
47953
+ resultLines.push(state.line0Accum.slice(0, lineChunkSize));
47954
+ return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
47955
+ }
47956
+ resultLines.push(state.line0Accum);
47957
+ return null;
47958
+ }
47959
+
47960
+ // src/files/read-file/read-file-range-async.ts
47961
+ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE, fileSizeIn) {
47962
+ const fileSize = fileSizeIn ?? (await fs50.promises.stat(filePath)).size;
47963
+ const fd = await fs50.promises.open(filePath, "r");
47964
+ const bufSize = 64 * 1024;
47965
+ const buf = Buffer.alloc(bufSize);
47966
+ const decoder = new StringDecoder2("utf8");
47967
+ let currentLine = 0;
47968
+ const resultLines = [];
47969
+ let partial2 = "";
47970
+ let done = false;
47971
+ const trackLineZero = shouldTrackLineZero(startLine, lineOffsetIn);
47972
+ const lineZeroState = createLineZeroStreamState(lineOffsetIn);
47973
+ let bytesSinceYield = 0;
47974
+ try {
47975
+ let position = 0;
47976
+ while (!done) {
47977
+ const { bytesRead } = await fd.read(buf, 0, bufSize, position);
47978
+ if (bytesRead === 0) break;
47979
+ position += bytesRead;
47980
+ bytesSinceYield += bytesRead;
47981
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
47982
+ await yieldToEventLoop();
47983
+ bytesSinceYield = 0;
47984
+ }
47985
+ const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
47986
+ partial2 = "";
47987
+ let lineStart = 0;
47988
+ for (let i = 0; i < text.length; i++) {
47989
+ if (text[i] !== "\n") continue;
47990
+ const lineContent2 = lineContentBeforeNewline(text, lineStart, i);
47991
+ if (currentLine === 0 && trackLineZero) {
47992
+ const lineZeroResult = tryFinishLineZeroOnCompleteLine(
47993
+ lineZeroState,
47994
+ lineContent2,
47995
+ resultLines,
47996
+ fileSize,
47997
+ lineChunkSize,
47998
+ lineOffsetIn,
47999
+ endLine,
48000
+ currentLine
48001
+ );
48002
+ if (lineZeroResult === "done") {
48003
+ done = true;
48004
+ break;
48005
+ }
48006
+ if (lineZeroResult !== "continue") return lineZeroResult;
48007
+ currentLine++;
48008
+ lineStart = i + 1;
48009
+ if (currentLine > endLine) {
48010
+ done = true;
48011
+ break;
48012
+ }
48013
+ continue;
48014
+ }
48015
+ if (currentLine >= startLine && currentLine <= endLine) {
48016
+ resultLines.push(lineContent2);
48017
+ }
48018
+ currentLine++;
48019
+ lineStart = i + 1;
48020
+ if (currentLine > endLine) {
48021
+ done = true;
48022
+ break;
48023
+ }
48024
+ }
48025
+ if (done) continue;
48026
+ const lineContent = text.slice(lineStart);
48027
+ if (currentLine === 0 && trackLineZero) {
48028
+ const partialResult = tryFinishLineZeroPartial(
48029
+ lineZeroState,
48030
+ lineContent,
48031
+ resultLines,
48032
+ fileSize,
48033
+ lineChunkSize,
48034
+ lineOffsetIn
48035
+ );
48036
+ if (partialResult != null) return partialResult;
48037
+ } else {
48038
+ partial2 = lineContent;
48039
+ }
48040
+ }
48041
+ if (!done) {
48042
+ const tail = partial2 + decoder.end();
48043
+ if (currentLine === 0 && trackLineZero) {
48044
+ const tailResult = finishLineZeroTail(
48045
+ lineZeroState,
48046
+ tail,
48047
+ resultLines,
48048
+ fileSize,
48049
+ lineChunkSize,
48050
+ lineOffsetIn
48051
+ );
48052
+ if (tailResult != null) return tailResult;
48053
+ } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
48054
+ resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
48055
+ }
48056
+ }
48057
+ return { content: resultLines.join("\n"), size: fileSize };
48058
+ } finally {
48059
+ await fd.close();
48060
+ }
48061
+ }
48062
+
48063
+ // src/files/read-file/read-small-file-range-async.ts
48064
+ import fs51 from "node:fs";
48065
+ var SMALL_FILE_RANGE_MAX_BYTES = 512 * 1024;
48066
+ function sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize) {
48067
+ if (startLine === 0 && lineOffsetIn == null && lines[0] != null && lines[0].length > lineChunkSize) {
48068
+ return {
48069
+ content: lines[0].slice(0, lineChunkSize),
48070
+ size: fileSize,
48071
+ lineOffset: lineChunkSize,
48072
+ totalLines: lines.length
48073
+ };
48074
+ }
48075
+ if (startLine === 0 && typeof lineOffsetIn === "number") {
48076
+ const line0 = lines[0] ?? "";
48077
+ const from = lineOffsetIn;
48078
+ const take = Math.min(lineChunkSize, Math.max(0, line0.length - from));
48079
+ return {
48080
+ content: line0.slice(from, from + take),
48081
+ size: fileSize,
48082
+ ...from + take < line0.length ? { lineOffset: from + take, totalLines: lines.length } : { totalLines: lines.length }
48083
+ };
48084
+ }
48085
+ const slice = lines.slice(startLine, endLine + 1);
48086
+ return {
48087
+ content: slice.join("\n"),
48088
+ size: fileSize,
48089
+ totalLines: lines.length
48090
+ };
48091
+ }
48092
+ async function readSmallFileRangeAsync(filePath, fileSize, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
48093
+ const raw = await fs51.promises.readFile(filePath, "utf8");
48094
+ const lines = raw.split(/\r?\n/);
48095
+ return sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize);
48096
+ }
48097
+
48098
+ // src/files/read-file/read-file-buffer-full-async.ts
48099
+ init_yield_to_event_loop();
48100
+ import fs52 from "node:fs";
48101
+ var READ_CHUNK_BYTES = 256 * 1024;
48102
+ async function readFileBufferFullAsync(filePath) {
48103
+ const stat3 = await fs52.promises.stat(filePath);
48104
+ const fd = await fs52.promises.open(filePath, "r");
48105
+ const chunks = [];
48106
+ let position = 0;
48107
+ let bytesSinceYield = 0;
48108
+ try {
48109
+ while (position < stat3.size) {
48110
+ const buf = Buffer.alloc(Math.min(READ_CHUNK_BYTES, stat3.size - position));
48111
+ const { bytesRead } = await fd.read(buf, 0, buf.length, position);
48112
+ if (bytesRead === 0) break;
48113
+ chunks.push(buf.subarray(0, bytesRead));
48114
+ position += bytesRead;
48115
+ bytesSinceYield += bytesRead;
48116
+ if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
48117
+ await yieldToEventLoop();
48118
+ bytesSinceYield = 0;
48119
+ }
48120
+ }
48121
+ } finally {
48122
+ await fd.close();
48123
+ }
48124
+ return { buffer: Buffer.concat(chunks), size: stat3.size };
48125
+ }
48126
+
48127
+ // src/files/read-file/read-file-full-async.ts
48128
+ async function readFileFullAsync(filePath) {
48129
+ const { buffer, size } = await readFileBufferFullAsync(filePath);
48130
+ const raw = buffer.toString("utf8");
48131
+ const lines = raw.split(/\r?\n/);
48132
+ return { content: raw, totalLines: lines.length, size };
48133
+ }
48134
+
48135
+ // src/files/read-file/read-file-binary-full-async.ts
48136
+ async function readFileBinaryFullAsync(filePath) {
48137
+ const { buffer, size } = await readFileBufferFullAsync(filePath);
48138
+ return {
48139
+ content: buffer.toString("base64"),
48140
+ size,
48141
+ mimeType: guessMimeType(filePath)
48142
+ };
48143
+ }
48144
+
48145
+ // src/files/read-file/index.ts
48146
+ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, sessionParentPath = getBridgeRoot(), encoding = "utf8", options) {
48147
+ if (!options?.skipInitialYield) {
48148
+ await yieldToEventLoop();
48149
+ }
48150
+ try {
48151
+ let resolvedPath;
48152
+ let fileSize;
48153
+ if (options?.useResolvedSize) {
48154
+ const resolved = await resolveFileForReadAsync(relativePath, sessionParentPath);
48155
+ if ("error" in resolved) return resolved;
48156
+ resolvedPath = resolved.path;
48157
+ fileSize = resolved.size;
48158
+ } else {
48159
+ const result = await resolveFilePathAsync(relativePath, sessionParentPath);
48160
+ if (typeof result === "object") return result;
48161
+ resolvedPath = result;
48162
+ }
48163
+ const hasRange = typeof startLine === "number" && typeof endLine === "number";
48164
+ if (encoding === "base64") {
48165
+ if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
48166
+ const read2 = await readFileBinaryFullAsync(resolvedPath);
48167
+ return { ...read2, resolvedPath };
48168
+ }
48169
+ if (hasRange) {
48170
+ if (fileSize == null) {
48171
+ const stat3 = await fs53.promises.stat(resolvedPath);
48172
+ fileSize = stat3.size;
48173
+ }
48174
+ const read2 = fileSize <= SMALL_FILE_RANGE_MAX_BYTES ? await readSmallFileRangeAsync(
48175
+ resolvedPath,
48176
+ fileSize,
48177
+ startLine,
48178
+ endLine,
48179
+ lineOffset,
48180
+ lineChunkSize
48181
+ ) : await readFileRangeAsync(
48182
+ resolvedPath,
48183
+ startLine,
48184
+ endLine,
48185
+ lineOffset,
48186
+ lineChunkSize,
48187
+ fileSize
48188
+ );
48189
+ return read2;
48190
+ }
48191
+ const read = await readFileFullAsync(resolvedPath);
48192
+ return { ...read, resolvedPath };
48193
+ } catch (err) {
48194
+ return { error: err instanceof Error ? err.message : String(err) };
48195
+ }
48196
+ }
48197
+
48198
+ // src/files/browser/execute-file-browser-read.ts
48199
+ async function executeFileBrowserRead(params) {
48200
+ const { msg, reqPath, sessionParentPath, gitScope } = params;
48201
+ const startLine = typeof msg.startLine === "number" ? msg.startLine : void 0;
48202
+ const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
48203
+ const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
48204
+ const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
48205
+ const encoding = msg.encoding === "base64" ? "base64" : "utf8";
48206
+ return gitScope ? await readGitTreeFileAsync(
48207
+ gitScope.repoAbsPath,
48208
+ gitScope.branch,
48209
+ reqPath,
48210
+ startLine,
48211
+ endLine,
48212
+ lineOffset,
48213
+ lineChunkSize,
48214
+ encoding
48215
+ ) : await withFileBrowserReadQueue(
48216
+ () => readFileAsync(
48217
+ reqPath,
48218
+ startLine,
48219
+ endLine,
48220
+ lineOffset,
48221
+ lineChunkSize,
48222
+ sessionParentPath,
48223
+ encoding,
48224
+ { skipInitialYield: true, useResolvedSize: true }
48225
+ )
48226
+ );
48227
+ }
48228
+
48229
+ // src/local-shortcut/app/lib/file-browser/handle-read.ts
48230
+ async function handleShortcutFileBrowserRead(ctx, sessionId) {
48231
+ const reqPath = parseReadPath(ctx.url);
48232
+ if (reqPath == null) {
48233
+ sendShortcutJson(ctx.res, 400, { error: "path required" });
48234
+ return;
48235
+ }
48236
+ const gitMsg = parseGitScopeFromUrl(ctx.url);
48237
+ const gitScope = resolveGitBranchScope({ id: "shortcut", path: reqPath, ...gitMsg });
48238
+ if (gitMsg.source === "git_branch" && typeof gitMsg.repoRelPath === "string" && typeof gitMsg.branch === "string" && gitMsg.branch.trim().length > 0 && !gitScope) {
48239
+ sendShortcutJson(ctx.res, 400, { error: "Invalid repository path" });
48240
+ return;
48241
+ }
48242
+ const sessionParentPath = await resolveShortcutSessionParent(ctx, sessionId);
48243
+ const result = await executeFileBrowserRead({
48244
+ msg: parseReadParams(ctx.url),
48245
+ reqPath,
48246
+ sessionParentPath,
48247
+ gitScope
48248
+ });
48249
+ if ("error" in result) {
48250
+ sendShortcutJson(ctx.res, 400, { error: result.error });
48251
+ return;
48252
+ }
48253
+ const body = { content: result.content };
48254
+ if (result.totalLines != null) body.totalLines = result.totalLines;
48255
+ if (result.size != null) body.size = result.size;
48256
+ if (result.lineOffset != null) body.lineOffset = result.lineOffset;
48257
+ if (result.mimeType != null) body.mimeType = result.mimeType;
48258
+ if (result.resolvedPath != null) body.resolvedPath = result.resolvedPath;
48259
+ sendShortcutFileBrowserJson(ctx.res, 200, body, ctx.e2ee);
48260
+ }
48261
+
48262
+ // src/local-shortcut/app/api/bridges/files/read/route.ts
48263
+ async function GET3(ctx) {
48264
+ if (!assertBridgeWorkspace(ctx)) return;
48265
+ await handleShortcutFileBrowserRead(ctx);
48266
+ }
48267
+
48268
+ // src/local-shortcut/app/api/sessions/[sessionId]/files/list/route.ts
48269
+ async function GET4(ctx) {
48270
+ await handleShortcutFileBrowserList(ctx, ctx.params.sessionId);
48271
+ }
48272
+
48273
+ // src/local-shortcut/app/api/sessions/[sessionId]/files/read/route.ts
48274
+ async function GET5(ctx) {
48275
+ await handleShortcutFileBrowserRead(ctx, ctx.params.sessionId);
48276
+ }
48277
+
48278
+ // src/local-shortcut/lib/router.ts
48279
+ var routes = [];
48280
+ function segmentsFromAppPath(appPath) {
48281
+ const paramNames = [];
48282
+ const segments = appPath.split("/").filter(Boolean).map((seg) => {
48283
+ if (seg.startsWith("[") && seg.endsWith("]")) {
48284
+ const name = seg.slice(1, -1);
48285
+ paramNames.push(name);
48286
+ return `:${name}`;
48287
+ }
48288
+ return seg;
48289
+ });
48290
+ return { segments, paramNames };
48291
+ }
48292
+ function addAppRoute(method, appPath, handler) {
48293
+ const { segments, paramNames } = segmentsFromAppPath(appPath);
48294
+ routes.push({ method, segments, paramNames, handler });
48295
+ }
48296
+ addAppRoute("GET", "api/bridges/local-shortcut/health", GET);
48297
+ addAppRoute("GET", "api/bridges/files/list", GET2);
48298
+ addAppRoute("GET", "api/bridges/files/read", GET3);
48299
+ addAppRoute("GET", "api/sessions/[sessionId]/files/list", GET4);
48300
+ addAppRoute("GET", "api/sessions/[sessionId]/files/read", GET5);
48301
+ function matchShortcutRoute(pathname, method) {
48302
+ const pathSegments = pathname.split("/").filter(Boolean);
48303
+ for (const route of routes) {
48304
+ if (route.method !== method) continue;
48305
+ if (pathSegments.length !== route.segments.length) continue;
48306
+ const params = {};
48307
+ let matched = true;
48308
+ for (let i = 0; i < route.segments.length; i++) {
48309
+ const seg = route.segments[i];
48310
+ const value = pathSegments[i];
48311
+ if (seg.startsWith(":")) {
48312
+ params[seg.slice(1)] = decodeURIComponent(value);
48313
+ } else if (seg !== value) {
48314
+ matched = false;
48315
+ break;
48316
+ }
48317
+ }
48318
+ if (matched) return { params, handler: route.handler };
48319
+ }
48320
+ return null;
48321
+ }
48322
+
48323
+ // src/local-shortcut/lib/server.ts
48324
+ function shortcutErrorMessage(err) {
48325
+ return err instanceof Error ? err.message : "Internal shortcut server error";
48326
+ }
48327
+ function createLocalShortcutRequestHandler(ctx) {
48328
+ return (req, res) => {
48329
+ void (async () => {
48330
+ try {
48331
+ const corsOk = applyCorsHeaders(req, res);
48332
+ if (req.method === "OPTIONS") {
48333
+ res.writeHead(corsOk ? 204 : 403);
48334
+ res.end();
48335
+ return;
48336
+ }
48337
+ if (!corsOk) {
48338
+ sendShortcutJson(res, 403, { error: "Origin not allowed" });
48339
+ return;
48340
+ }
48341
+ if (req.method !== "GET") {
48342
+ sendShortcutJson(res, 405, { error: "Method not allowed" });
48343
+ return;
48344
+ }
48345
+ if (!isAuthorizedShortcutRequest(req, ctx.token)) {
48346
+ sendShortcutJson(res, 401, { error: "Unauthorized" });
48347
+ return;
48348
+ }
48349
+ const rawUrl = req.url ?? "/";
48350
+ const qIndex = rawUrl.indexOf("?");
48351
+ const pathname = qIndex >= 0 ? rawUrl.slice(0, qIndex) : rawUrl;
48352
+ const search = qIndex >= 0 ? rawUrl.slice(qIndex) : "";
48353
+ const url2 = new URL2(search || "", "http://local");
48354
+ const match = matchShortcutRoute(pathname, req.method);
48355
+ if (!match) {
48356
+ sendShortcutJson(res, 404, { error: "Not found" });
48357
+ return;
48358
+ }
48359
+ const handlerCtx = {
48360
+ req,
48361
+ res,
48362
+ params: match.params,
48363
+ url: url2,
48364
+ workspaceId: ctx.workspaceId,
48365
+ sessionWorktreeManager: ctx.sessionWorktreeManager,
48366
+ e2ee: ctx.e2ee
48367
+ };
48368
+ await match.handler(handlerCtx);
48369
+ } catch (err) {
48370
+ if (!res.headersSent) {
48371
+ sendShortcutJson(res, 500, { error: shortcutErrorMessage(err) });
48372
+ }
48373
+ }
48374
+ })().catch((err) => {
48375
+ if (!res.headersSent) {
48376
+ sendShortcutJson(res, 500, { error: shortcutErrorMessage(err) });
48377
+ }
48378
+ });
48379
+ };
48380
+ }
48381
+
48382
+ // src/local-shortcut/lib/start-server.ts
48383
+ var RESTART_DELAY_MS = 250;
48384
+ var MAX_RESTART_ATTEMPTS = 5;
48385
+ function listenOnPort(server, port) {
48386
+ return new Promise((resolve37, reject) => {
48387
+ server.once("error", reject);
48388
+ server.listen(port, "127.0.0.1", () => {
48389
+ const addr = server.address();
48390
+ if (!addr || typeof addr === "string") {
48391
+ reject(new Error("Local shortcut server did not bind"));
48392
+ return;
48393
+ }
48394
+ resolve37(addr.port);
48395
+ });
48396
+ });
48397
+ }
48398
+ function closeServer(server) {
48399
+ if (!server) return Promise.resolve();
48400
+ return new Promise((resolve37, reject) => {
48401
+ server.close((err) => err ? reject(err) : resolve37());
48402
+ });
48403
+ }
48404
+ function delay2(ms) {
48405
+ return new Promise((resolve37) => setTimeout(resolve37, ms));
48406
+ }
48407
+ function startLocalShortcutServer(ctx) {
48408
+ let closed = false;
48409
+ let restartAttempts = 0;
48410
+ let currentServer = null;
48411
+ let boundPort = null;
48412
+ let restarting = null;
48413
+ async function bindServer(port) {
48414
+ const server = http2.createServer(createLocalShortcutRequestHandler(ctx));
48415
+ server.on("error", (err) => {
48416
+ void restartAfterError(err);
48417
+ });
48418
+ currentServer = server;
48419
+ const listenPort = await listenOnPort(server, port);
48420
+ boundPort = listenPort;
48421
+ return listenPort;
48422
+ }
48423
+ async function restartAfterError(_reason) {
48424
+ if (closed || restarting) return;
48425
+ if (restartAttempts >= MAX_RESTART_ATTEMPTS) return;
48426
+ restartAttempts += 1;
48427
+ restarting = (async () => {
48428
+ await closeServer(currentServer).catch(() => {
48429
+ });
48430
+ currentServer = null;
48431
+ if (closed) return;
48432
+ await delay2(RESTART_DELAY_MS);
48433
+ if (closed) return;
48434
+ try {
48435
+ await bindServer(boundPort ?? 0);
48436
+ restartAttempts = 0;
48437
+ } catch {
48438
+ }
48439
+ })().finally(() => {
48440
+ restarting = null;
48441
+ });
48442
+ await restarting;
48443
+ }
48444
+ return bindServer(0).then((port) => ({
48445
+ port,
48446
+ token: ctx.token,
48447
+ close: async () => {
48448
+ closed = true;
48449
+ await closeServer(currentServer);
48450
+ currentServer = null;
48451
+ }
48452
+ }));
48453
+ }
48454
+
48455
+ // src/local-shortcut/lib/create.ts
48456
+ async function createLocalShortcut(params) {
48457
+ const token = generateShortcutToken();
48458
+ const handle = await startLocalShortcutServer({
48459
+ workspaceId: params.workspaceId,
48460
+ token,
48461
+ sessionWorktreeManager: params.sessionWorktreeManager,
48462
+ e2ee: params.e2ee
48463
+ });
48464
+ return {
48465
+ handle,
48466
+ identify: {
48467
+ localShortcutPort: handle.port,
48468
+ localShortcutToken: handle.token
48469
+ }
48470
+ };
48471
+ }
48472
+
48473
+ // src/connection/create-bridge-connection-runtime.ts
48474
+ async function createBridgeConnectionRuntime(options, params) {
48475
+ const { apiUrl, workspaceId } = options;
48476
+ const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
48477
+ const { state, getWs, logFn } = params;
48478
+ const tokens = {
48479
+ accessToken: options.authToken,
48480
+ refreshToken: options.refreshToken
48481
+ };
48482
+ const { worktreesRootPath, sessionWorktreeManager, previewWorktreeManager } = createBridgeWorktreeManagers({ worktreesRootPath: options.worktreesRootPath, log: logFn });
48483
+ const { e2ee, acpManager, bridgeAccessPromise } = await createBridgeAccessAndAcp({
48484
+ apiUrl,
48485
+ workspaceId,
48486
+ tokens,
48487
+ e2eCertificate: options.e2eCertificate,
48488
+ getWs,
48489
+ log: logFn
48490
+ });
48491
+ const localShortcut = await createLocalShortcut({
48492
+ workspaceId,
48493
+ sessionWorktreeManager,
48494
+ e2ee
48495
+ });
48496
+ logFn("CLI running. Press Ctrl+C to exit.");
48497
+ const { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths } = createBridgePreviewStack({
48498
+ getWs,
48499
+ log: logFn,
48500
+ e2ee,
48501
+ previewWorktreeManager,
48502
+ worktreesRootPath
48503
+ });
48504
+ Object.assign(identifyReportedPaths, localShortcut.identify);
48505
+ const { bridgeHeartbeat, messageDeps } = createBridgeRuntimeMessageSetup({
48506
+ apiUrl,
48507
+ workspaceId,
48508
+ firehoseServerUrl,
48509
+ state,
48510
+ getWs,
48511
+ log: logFn,
48512
+ tokens,
48513
+ acpManager,
48514
+ sessionWorktreeManager,
48515
+ previewWorktreeManager,
48516
+ previewEnvironmentManager,
48517
+ e2ee
48518
+ });
48519
+ return {
48520
+ state,
48521
+ getWs,
48522
+ logFn,
48523
+ tokens,
48524
+ acpManager,
48525
+ bridgeAccessPromise,
48526
+ previewEnvironmentManager,
48527
+ bridgeHeartbeat,
48528
+ messageDeps,
48529
+ identifyReportedPaths,
48530
+ scheduleInitialIndexBuildsOnce,
48531
+ e2ee,
48532
+ localShortcutPromise: Promise.resolve(localShortcut.handle)
48533
+ };
48534
+ }
48535
+
48536
+ // src/connection/create-main-bridge-reconnect-state.ts
48537
+ function createMainBridgeReconnectState() {
48538
+ const state = {
48539
+ closedByUser: false,
48540
+ reconnectAttempt: 0,
48541
+ lastReconnectCloseMeta: null,
48542
+ logBridgeOpenAsReconnect: false,
48543
+ reconnectTimeout: null,
48544
+ currentWs: null,
48545
+ mainQuiet: createEmptyReconnectQuietSlot(),
48546
+ mainOutage: createEmptyReconnectOutageTracker(),
48547
+ duplicateBridgeFlap: createEmptyDuplicateBridgeFlapTracker(),
48548
+ firehoseHandle: null,
48549
+ lastFirehoseParams: null,
48550
+ firehoseReconnectTimeout: null,
48551
+ firehoseReconnectAttempt: 0,
48552
+ firehoseGeneration: 0,
48553
+ firehoseQuiet: createEmptyReconnectQuietSlot(),
48554
+ firehoseOutage: createEmptyReconnectOutageTracker(),
48555
+ lastFirehoseReconnectCloseMeta: null
48556
+ };
48557
+ return {
48558
+ state,
48559
+ getWs: () => state.currentWs
48560
+ };
48561
+ }
48562
+
48563
+ // src/connection/ensure-bridge-connection-databases.ts
48564
+ async function ensureBridgeConnectionDatabases(log2) {
48565
+ await ensureCliSqliteInitialized({ logLegacyMigration: log2 });
48566
+ await ensureCodeNavCacheSqliteInitialized();
48567
+ }
48568
+
48569
+ // src/connection/build-bridge-url.ts
48570
+ function buildBridgeUrl(apiUrl, workspaceId, authToken) {
48571
+ const base = apiUrl.startsWith("https") ? apiUrl.replace(/^https/, "wss") : apiUrl.replace(/^http/, "ws");
48572
+ const params = new URLSearchParams({ workspaceId, token: authToken });
48573
+ return `${base}/ws/bridge?${params.toString()}`;
48574
+ }
48575
+
48576
+ // src/types/api-to-bridge-messages.ts
48577
+ var API_TO_BRIDGE_MESSAGE_TYPES = [
48578
+ "auth_token",
48579
+ "bridge_identified",
48580
+ "ha",
48581
+ "preview_environments_config",
48582
+ "preview_environment_control",
48583
+ "deploy_session_to_preview",
48584
+ "agent_config",
48585
+ "prompt_queue_state",
48586
+ "prompt",
48587
+ "session_git_request",
48588
+ "rename_session_branch",
48589
+ "session_archived",
48590
+ "session_discarded",
48591
+ "revert_turn_snapshot",
48592
+ "cursor_request_response",
48593
+ "skill_call",
48594
+ "file_browser_request",
48595
+ "file_browser_search",
48596
+ "code_nav",
48597
+ "skill_layout_request",
48598
+ "install_skills",
48599
+ "refresh_local_skills",
48600
+ "bridge_git_context_request",
48601
+ "list_repo_branches_request"
48602
+ ];
48603
+ var API_TO_BRIDGE_TYPE_SET = new Set(API_TO_BRIDGE_MESSAGE_TYPES);
48604
+ function parseApiToBridgeMessage(data, log2) {
48605
+ if (data === null || typeof data !== "object" || Array.isArray(data)) return null;
48606
+ const t = data.type;
48607
+ if (typeof t !== "string" || !API_TO_BRIDGE_TYPE_SET.has(t)) {
48608
+ if (typeof t === "string") {
48609
+ log2?.(`[Bridge service] unhandled message type: ${t}`);
48610
+ }
48611
+ return null;
48612
+ }
48613
+ if (t === "ha") {
48614
+ const s = data.s;
48615
+ if (typeof s !== "number" || !Number.isFinite(s)) return null;
48616
+ }
48617
+ return data;
48618
+ }
48619
+
48620
+ // src/routing/handlers/auth-token.ts
48621
+ var handleAuthToken = (msg, { log: log2 }) => {
48622
+ if (typeof msg.token !== "string") return;
48623
+ log2("Received auth token. Save it for future runs:");
48624
+ log2(` export BUILDAUTOMATON_AUTH_TOKEN="${msg.token}"`);
48625
+ };
48626
+
48627
+ // src/routing/handlers/bridge-identified.ts
48628
+ var handleBridgeIdentified = (msg, deps) => {
48629
+ if (typeof msg.bridgeName !== "string") return;
48630
+ deps.onBridgeIdentified({
48631
+ bridgeName: msg.bridgeName,
48632
+ proxyPorts: msg.proxyPorts,
48633
+ previewEnvironments: msg.previewEnvironments
48634
+ });
48635
+ setImmediate(() => {
48636
+ void (async () => {
48637
+ cancelInFlightAgentCapabilityWarmup();
48638
+ try {
48639
+ await deps.reportAutoDetectedAgents?.();
48640
+ } catch (e) {
48641
+ deps.log(
48642
+ `[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
48643
+ );
48644
+ }
48645
+ try {
48646
+ await deps.warmupAgentCapabilitiesOnConnect?.();
48647
+ } catch (e) {
48648
+ deps.log(
48649
+ `[Bridge service] Agent capability warmup failed: ${e instanceof Error ? e.message : String(e)}`
48650
+ );
48651
+ }
48652
+ })();
48653
+ });
48654
+ setImmediate(() => {
48655
+ if (isCliImmediateShutdownRequested()) return;
48656
+ try {
48657
+ deps.sendLocalSkillsReport?.();
48658
+ } catch (e) {
48659
+ deps.log(
48660
+ `[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
48661
+ );
48662
+ }
48663
+ });
48664
+ };
48665
+
48666
+ // src/connection/heartbeat/ack.ts
48667
+ var handleBridgeHeartbeatAck = (msg, deps) => {
48668
+ const raw = msg.s;
48669
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return;
48670
+ deps.onBridgeHeartbeatAck?.(Math.trunc(raw));
48671
+ };
48672
+
48673
+ // src/agents/acp/from-bridge/handle-bridge-agent-config.ts
48674
+ function handleBridgeAgentConfig(msg, { acpManager }) {
48675
+ if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
48676
+ acpManager.setPreferredAgentType(msg.agents[0].type);
48677
+ }
48678
+
48679
+ // src/routing/handlers/agent-config.ts
48680
+ var handleAgentConfigMessage = (msg, deps) => {
48681
+ handleBridgeAgentConfig(msg, deps);
48682
+ };
48683
+
48684
+ // src/prompt-turn-queue/client-report.ts
48685
+ function sendPromptQueueClientReport(ws, queues) {
48686
+ if (!ws) return false;
48687
+ const wireQueues = {};
48688
+ for (const [queueKey, rows] of Object.entries(queues)) {
48689
+ wireQueues[queueKey] = rows.map((r) => ({ turnId: r.turnId, clientState: r.cliState }));
48690
+ }
48691
+ sendWsMessage(ws, { type: "prompt_queue_client_report", queues: wireQueues });
48692
+ return true;
48693
+ }
48694
+
48695
+ // src/prompt-turn-queue/disk-store.ts
48696
+ var MERGEABLE_BRIDGE_SERVER_STATES = /* @__PURE__ */ new Set([
48697
+ "queued",
48698
+ "requeued",
48699
+ "requeued_with_revert",
48700
+ "cancel_requested",
48701
+ "stopping",
48702
+ "discarded"
48703
+ ]);
48704
+ async function readPersistedQueue(queueKey) {
48705
+ return withCliSqlite((db) => {
48706
+ const row = db.get("SELECT queue_key, updated_at, turns_json FROM prompt_queue WHERE queue_key = ?", [
48707
+ queueKey
48708
+ ]);
48709
+ if (!row) return null;
48710
+ try {
48711
+ const turns = JSON.parse(row.turns_json);
48712
+ if (!Array.isArray(turns)) return null;
48713
+ return { queueKey: row.queue_key, updatedAt: row.updated_at, turns };
48714
+ } catch {
48715
+ return null;
48716
+ }
48717
+ });
48718
+ }
48719
+ async function writePersistedQueue(file2) {
48720
+ await withCliSqlite((db) => {
48721
+ db.run(
48722
+ `INSERT INTO prompt_queue (queue_key, updated_at, turns_json) VALUES (?, ?, ?)
48723
+ ON CONFLICT(queue_key) DO UPDATE SET updated_at = excluded.updated_at, turns_json = excluded.turns_json`,
48724
+ [file2.queueKey, file2.updatedAt, JSON.stringify(file2.turns)]
48725
+ );
48726
+ });
48727
+ }
48728
+ async function mergeBridgeServerQueueSnapshot(queueKey, serverTurns) {
48729
+ const prev = await readPersistedQueue(queueKey);
48730
+ const turns = [];
48731
+ for (const raw of serverTurns) {
48732
+ if (!raw || typeof raw !== "object") continue;
48733
+ const o = raw;
48734
+ const turnId = typeof o.turnId === "string" ? o.turnId : "";
48735
+ const sessionId = typeof o.sessionId === "string" ? o.sessionId : "";
48736
+ const turnOrd = typeof o.turnOrd === "number" ? o.turnOrd : Number(o.turnOrd) || 0;
48737
+ const bridgeServerState = o.serverState ?? o.bridgeServerState;
48738
+ const lastCliState = o.lastClientState ?? o.lastCliState ?? null;
48739
+ const payload = o.payload && typeof o.payload === "object" ? o.payload : {};
48740
+ if (!turnId || !sessionId) continue;
48741
+ if (!MERGEABLE_BRIDGE_SERVER_STATES.has(String(bridgeServerState))) continue;
48742
+ const old = prev?.turns.find((t) => t.turnId === turnId);
48743
+ const mergedCli = old?.lastCliState === "running" && lastCliState == null ? "running" : lastCliState;
48744
+ turns.push({
48745
+ turnId,
48746
+ sessionId,
48747
+ turnOrd,
48748
+ bridgeServerState,
48749
+ lastCliState: mergedCli,
48750
+ payload
48751
+ });
48752
+ }
48753
+ turns.sort((a, b) => a.turnOrd - b.turnOrd);
48754
+ return { queueKey, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), turns };
48755
+ }
48756
+
48757
+ // src/prompt-turn-queue/runner/dispatch-local-prompt.ts
48758
+ function dispatchLocalPrompt(next, deps) {
48759
+ const pl = next.payload;
48760
+ const rawParent = pl["sessionParent"];
48761
+ const sessionParent = rawParent === "bridge_root" || rawParent === "worktrees_root" ? rawParent : void 0;
48762
+ const rawParentPath = pl["sessionParentPath"];
48763
+ const sessionParentPath = typeof rawParentPath === "string" && rawParentPath.trim() !== "" ? rawParentPath.trim() : void 0;
48764
+ const rawBaseBranches = pl["worktreeBaseBranches"];
48765
+ const worktreeBaseBranches = rawBaseBranches != null && typeof rawBaseBranches === "object" && !Array.isArray(rawBaseBranches) ? rawBaseBranches : void 0;
48766
+ const msg = {
48767
+ type: "prompt",
48768
+ sessionId: next.sessionId,
48769
+ runId: next.turnId,
48770
+ prompt: pl.prompt,
48771
+ mode: typeof pl.mode === "string" ? pl.mode : "agent",
48772
+ isNewSession: pl.isNewSession === true,
48773
+ ...sessionParent ? { sessionParent } : {},
48774
+ ...sessionParentPath ? { sessionParentPath } : {},
48775
+ ...worktreeBaseBranches && Object.keys(worktreeBaseBranches).length > 0 ? { worktreeBaseBranches } : {},
48776
+ ...typeof pl.followUpCatalogPromptId === "string" ? { followUpCatalogPromptId: pl.followUpCatalogPromptId } : {},
48777
+ ...typeof pl.agentType === "string" && pl.agentType.trim() ? { agentType: pl.agentType.trim() } : {},
48778
+ ...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {},
48779
+ ...Array.isArray(pl.attachments) && pl.attachments.length > 0 ? { attachments: pl.attachments } : {}
48780
+ };
48781
+ handleBridgePrompt(msg, deps);
48782
+ }
48783
+
48784
+ // src/prompt-turn-queue/runner/queue-selection.ts
48785
+ function pickNextRunnableTurn(turns) {
48786
+ for (const t of turns) {
48787
+ if (t.bridgeServerState === "discarded" || t.bridgeServerState === "stopping") continue;
48788
+ if (t.bridgeServerState === "cancel_requested") continue;
48789
+ if (!isRunnableBridgePromptTurnServerState(t.bridgeServerState)) continue;
48790
+ if (t.lastCliState === "running" || t.lastCliState === "stopped" || t.lastCliState === "failed" || t.lastCliState === "cancelled") {
48791
+ continue;
48792
+ }
48793
+ return t;
48794
+ }
48795
+ return null;
48796
+ }
48797
+ function hasRunningTurn(turns) {
48798
+ return turns.some((t) => t.lastCliState === "running");
48799
+ }
48800
+
48801
+ // src/prompt-turn-queue/runner/run-id-queue-key-map.ts
48802
+ var runIdToQueueKey = /* @__PURE__ */ new Map();
48803
+ function getRunIdQueueKey(runId) {
48804
+ return runIdToQueueKey.get(runId);
48805
+ }
48806
+ function setRunIdQueueKey(runId, queueKey) {
48807
+ runIdToQueueKey.set(runId, queueKey);
48808
+ }
48809
+ function deleteRunIdQueueKey(runId) {
48810
+ const queueKey = runIdToQueueKey.get(runId);
48811
+ runIdToQueueKey.delete(runId);
48812
+ return queueKey;
48813
+ }
48814
+ function syncRunningTurnQueueKeys(turns, queueKey) {
48815
+ for (const running of turns.filter((t) => t.lastCliState === "running")) {
48816
+ runIdToQueueKey.set(running.turnId, queueKey);
48817
+ }
48818
+ }
48819
+
48820
+ // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
48821
+ import fs55 from "node:fs";
48822
+
48823
+ // src/git/snapshot/capture.ts
48824
+ import * as fs54 from "node:fs";
48825
+ import * as path77 from "node:path";
48826
+ async function resolveSnapshotRepoRoots(options) {
48827
+ const { worktreePaths, fallbackCwd, sessionId, log: log2 } = options;
48828
+ if (worktreePaths?.length) {
48829
+ const uniq = [...new Set(worktreePaths.map((p) => path77.resolve(p)))];
48830
+ return uniq;
48831
+ }
48832
+ try {
48833
+ const repos = await discoverGitReposUnderRoot(fallbackCwd);
48834
+ const mapped = repos.map((r) => r.absolutePath);
48835
+ const sid = sessionId?.trim();
48836
+ if (sid) {
48837
+ const filtered = mapped.filter((root) => path77.basename(root) === sid);
48838
+ if (filtered.length > 0) return filtered;
48839
+ }
48840
+ return mapped;
48841
+ } catch (e) {
48842
+ log2(`[snapshot] Discover repositories failed: ${e instanceof Error ? e.message : String(e)}`);
48843
+ return [];
48844
+ }
48845
+ }
48846
+ async function capturePreTurnSnapshot(options) {
48847
+ const { runId, repoRoots, agentCwd, log: log2 } = options;
48848
+ if (!runId || !repoRoots.length) {
48849
+ return { ok: false, error: "No git repos to snapshot" };
48850
+ }
48851
+ const repos = [];
48852
+ await forEachWithGitYield(repoRoots, async (root) => {
48853
+ const stashSha = await gitStashCreate(root, log2);
48854
+ const untrackedPaths = await gitUntrackedPaths(root, log2);
48855
+ repos.push({ path: root, stashSha, untrackedPaths });
48856
+ });
48857
+ const dir = snapshotsDirForCwd(agentCwd);
48858
+ try {
48859
+ fs54.mkdirSync(dir, { recursive: true });
48860
+ } catch (e) {
48861
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
48862
+ }
48863
+ const payload = {
48864
+ runId,
48865
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
48866
+ repos
48867
+ };
48868
+ const filePath = path77.join(dir, `${runId}.json`);
48869
+ try {
48870
+ fs54.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
48871
+ } catch (e) {
48872
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
48873
+ }
48874
+ const repoList = repos.map((r) => r.path).join(", ");
48875
+ log2(
48876
+ `[snapshot] Saved pre-turn snapshot ${runId.slice(0, 8)}\u2026 (${repos.length} repo(s)): ${repoList}`
48877
+ );
48878
+ return { ok: true, filePath, repos };
48879
+ }
48880
+ async function applyPreTurnSnapshot(filePath, log2) {
48881
+ let data;
48882
+ try {
48883
+ const raw = fs54.readFileSync(filePath, "utf8");
48884
+ data = JSON.parse(raw);
48885
+ } catch (e) {
48886
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
48887
+ }
48888
+ if (!Array.isArray(data.repos)) {
48889
+ return { ok: false, error: "Invalid snapshot file" };
48890
+ }
48891
+ let applyError = null;
48892
+ await forEachWithGitYield(data.repos, async (r) => {
48893
+ if (applyError || !r.path) return;
48894
+ const reset = await gitRun(r.path, ["reset", "--hard", "HEAD"], log2, "reset --hard");
48895
+ if (!reset.ok) {
48896
+ applyError = reset;
48897
+ return;
48898
+ }
48899
+ const clean = await gitRun(r.path, ["clean", "-fd"], log2, "clean -fd");
48900
+ if (!clean.ok) {
48901
+ applyError = clean;
48902
+ return;
48903
+ }
48904
+ if (r.stashSha) {
48905
+ const ap = await gitRun(r.path, ["stash", "apply", r.stashSha], log2, "stash apply");
48906
+ if (!ap.ok) applyError = ap;
48907
+ }
48908
+ });
48909
+ if (applyError) return applyError;
48910
+ log2(`[snapshot] Restored pre-turn state for ${data.runId.slice(0, 8)}\u2026`);
48911
+ return { ok: true };
48912
+ }
48913
+
48914
+ // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
48915
+ init_cwd();
48916
+ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
48917
+ if (next.bridgeServerState !== "requeued_with_revert") return true;
48918
+ const sid = next.sessionId;
48919
+ const pl = next.payload;
48920
+ const tid = typeof pl.snapshotRevertTurnId === "string" && pl.snapshotRevertTurnId.trim() !== "" ? pl.snapshotRevertTurnId.trim() : next.turnId;
48921
+ const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
48922
+ const file2 = snapshotFilePath(agentBase, tid);
48923
+ try {
48924
+ await fs55.promises.access(file2, fs55.constants.F_OK);
48925
+ } catch {
48926
+ deps.log(
48927
+ `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
48928
+ );
48929
+ return true;
48930
+ }
48931
+ const res = await applyPreTurnSnapshot(file2, deps.log);
48932
+ if (!res.ok) {
48933
+ deps.log(`[Queue] requeued_with_revert: snapshot apply failed: ${res.error ?? "unknown"}`);
48934
+ }
48935
+ return res.ok;
48936
+ }
48937
+
48938
+ // src/prompt-turn-queue/runner/finalize-prompt-turn-on-bridge.ts
48939
+ async function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
48940
+ if (!runId) return false;
48941
+ const queueKey = deleteRunIdQueueKey(runId);
48942
+ if (!queueKey) return false;
48943
+ const f = await readPersistedQueue(queueKey);
48944
+ if (!f) return false;
48945
+ const t = f.turns.find((x) => x.turnId === runId);
48946
+ if (!t) return false;
48947
+ t.lastCliState = opts?.terminalCliState ?? (success2 ? "stopped" : "failed");
48948
+ await writePersistedQueue(f);
48949
+ sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: runId, cliState: t.lastCliState }] });
48950
+ return true;
47617
48951
  }
47618
48952
 
47619
48953
  // src/prompt-turn-queue/runner/apply-prompt-queue-state-from-server.ts
@@ -48147,443 +49481,33 @@ function getSkill(id) {
48147
49481
  async function callSkill(skillId, operationId, params) {
48148
49482
  const skill = getSkill(skillId);
48149
49483
  if (!skill) throw new Error(`Skill not found: ${skillId}`);
48150
- return skill.execute(operationId, params);
48151
- }
48152
-
48153
- // src/skills/handle-skill-call.ts
48154
- function handleSkillCall(msg, socket, log2) {
48155
- callSkill(msg.skillId, msg.operationId, msg.params ?? {}).then((result) => {
48156
- sendWsMessage(socket, { type: "skill_result", id: msg.id, result });
48157
- }).catch((err) => {
48158
- sendWsMessage(socket, { type: "skill_result", id: msg.id, error: String(err) });
48159
- log2(`[Bridge service] Skill invocation failed (${msg.skillId}/${msg.operationId}): ${err}`);
48160
- });
48161
- }
48162
-
48163
- // src/routing/handlers/skill-call.ts
48164
- var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
48165
- const skillId = typeof msg.skillId === "string" ? msg.skillId : "";
48166
- const operationId = typeof msg.operationId === "string" ? msg.operationId : "";
48167
- if (!skillId || !operationId) return;
48168
- const socket = getWs();
48169
- if (!socket) return;
48170
- handleSkillCall(
48171
- { id: msg.id, skillId, operationId, params: msg.params },
48172
- socket,
48173
- log2
48174
- );
48175
- };
48176
-
48177
- // src/files/resolve-file-browser-session-parent.ts
48178
- init_normalize_resolved_path();
48179
- init_cwd();
48180
- async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
48181
- const sid = sessionId?.trim();
48182
- if (sid) {
48183
- await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sid);
48184
- const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
48185
- if (worktreeRoot) return normalizeResolvedPath(worktreeRoot);
48186
- }
48187
- return getBridgeRoot();
48188
- }
48189
-
48190
- // src/files/browser/handle-file-browser-list.ts
48191
- init_list_dir();
48192
-
48193
- // src/git/tree/resolve-repo-abs-path.ts
48194
- init_cwd();
48195
- import * as path76 from "node:path";
48196
- function resolveRepoAbsPathFromBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
48197
- const bridgeResolved = path76.resolve(bridgeRoot);
48198
- const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
48199
- const repoPath = rel === "" ? bridgeResolved : path76.join(bridgeResolved, rel);
48200
- const resolved = path76.resolve(repoPath);
48201
- if (!resolved.startsWith(bridgeResolved + path76.sep) && resolved !== bridgeResolved) {
48202
- return null;
48203
- }
48204
- return resolved;
48205
- }
48206
-
48207
- // src/git/tree/resolve-branch-commit.ts
48208
- async function resolveBranchCommit(repoAbsPath, branch) {
48209
- const branchRef = branch.trim();
48210
- if (!branchRef) return null;
48211
- try {
48212
- return (await cliSimpleGit(repoAbsPath).revparse([`${branchRef}^{commit}`])).trim();
48213
- } catch {
48214
- return null;
48215
- }
48216
- }
48217
-
48218
- // src/git/tree/list-git-tree-dir.ts
48219
- init_yield_to_event_loop();
48220
- init_types2();
48221
- init_sort_entries();
48222
- function parseLsTreeLine(line) {
48223
- const tab = line.indexOf(" ");
48224
- if (tab < 0) return null;
48225
- const meta = line.slice(0, tab).trim().split(/\s+/);
48226
- if (meta.length < 3) return null;
48227
- const mode = meta[0];
48228
- const objectType2 = meta[1];
48229
- let name = line.slice(tab + 1);
48230
- if (name.startsWith('"') && name.endsWith('"')) {
48231
- name = name.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
48232
- }
48233
- return { mode, objectType: objectType2, name };
48234
- }
48235
- function childPath(relativePath, name) {
48236
- const base = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
48237
- if (!base || base === ".") return name;
48238
- return `${base}/${name}`.replace(/\\/g, "/");
48239
- }
48240
- function lsTreeArgs(commit, relativePath) {
48241
- const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
48242
- if (!rel || rel === ".") return ["ls-tree", commit];
48243
- return ["ls-tree", `${commit}:${rel}`];
48244
- }
48245
- async function listGitTreeDirAsync(repoAbsPath, branch, relativePath) {
48246
- await yieldToEventLoop();
48247
- const commit = await resolveBranchCommit(repoAbsPath, branch);
48248
- if (!commit) return { error: "Branch not found" };
48249
- try {
48250
- const raw = await cliSimpleGit(repoAbsPath).raw(lsTreeArgs(commit, relativePath));
48251
- await yieldToEventLoop();
48252
- const lines = String(raw ?? "").split(/\r?\n/).filter((line) => line.trim().length > 0);
48253
- const entries = [];
48254
- for (let i = 0; i < lines.length; i++) {
48255
- if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
48256
- await yieldToEventLoop();
48257
- }
48258
- const parsed = parseLsTreeLine(lines[i]);
48259
- if (!parsed) continue;
48260
- const { mode, objectType: objectType2, name } = parsed;
48261
- if (!name || name === "." || name === "..") continue;
48262
- const isSymlink = mode === "120000";
48263
- const isDir = objectType2 === "tree";
48264
- entries.push({
48265
- name,
48266
- path: childPath(relativePath, name),
48267
- isDir,
48268
- ...isSymlink ? { isSymlink: true } : {}
48269
- });
48270
- }
48271
- if (entries.length >= LIST_DIR_YIELD_EVERY) {
48272
- await yieldToEventLoop();
48273
- }
48274
- return { entries: sortListEntries(entries) };
48275
- } catch (err) {
48276
- const message = err instanceof Error ? err.message : String(err);
48277
- if (/not a tree object|does not exist|bad revision/i.test(message)) {
48278
- return { error: "Path not found in branch" };
48279
- }
48280
- return { error: message };
48281
- }
48282
- }
48283
-
48284
- // src/git/tree/file/index.ts
48285
- init_yield_to_event_loop();
48286
-
48287
- // src/files/read-file/types.ts
48288
- var LINE_CHUNK_SIZE = 64 * 1024;
48289
- var READ_RANGE_YIELD_EVERY_BYTES = 256 * 1024;
48290
-
48291
- // src/files/read-file/guess-mime-type.ts
48292
- var MIME_BY_EXT = {
48293
- png: "image/png",
48294
- jpg: "image/jpeg",
48295
- jpeg: "image/jpeg",
48296
- gif: "image/gif",
48297
- bmp: "image/bmp",
48298
- ico: "image/x-icon",
48299
- webp: "image/webp",
48300
- avif: "image/avif",
48301
- svg: "image/svg+xml",
48302
- pdf: "application/pdf",
48303
- json: "application/json",
48304
- html: "text/html",
48305
- htm: "text/html",
48306
- css: "text/css",
48307
- js: "text/javascript",
48308
- mjs: "text/javascript",
48309
- ts: "text/typescript",
48310
- txt: "text/plain",
48311
- md: "text/markdown",
48312
- xml: "application/xml",
48313
- zip: "application/zip",
48314
- gz: "application/gzip",
48315
- wasm: "application/wasm"
48316
- };
48317
- function guessMimeType(filePath) {
48318
- const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
48319
- return MIME_BY_EXT[ext] ?? "application/octet-stream";
48320
- }
48321
-
48322
- // src/git/tree/file/git-blob-ref.ts
48323
- init_yield_to_event_loop();
48324
- async function gitBlobRef(repoAbsPath, branch, relativePath) {
48325
- const commit = await resolveBranchCommit(repoAbsPath, branch);
48326
- await yieldToEventLoop();
48327
- if (!commit) return null;
48328
- const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
48329
- if (!rel || rel === ".") return null;
48330
- return `${commit}:${rel}`;
48331
- }
48332
-
48333
- // src/git/tree/file/is-binary-buffer.ts
48334
- function isBinaryBuffer(buf) {
48335
- const sample = buf.subarray(0, Math.min(buf.length, 8192));
48336
- for (let i = 0; i < sample.length; i++) {
48337
- if (sample[i] === 0) return true;
48338
- }
48339
- return false;
48340
- }
48341
-
48342
- // src/git/tree/file/read-git-blob-text-async.ts
48343
- init_yield_to_event_loop();
48344
- import { StringDecoder } from "node:string_decoder";
48345
- async function countGitBlobLinesAsync(buf) {
48346
- if (buf.length === 0) return 1;
48347
- let lines = 1;
48348
- let bytesSinceYield = 0;
48349
- for (let i = 0; i < buf.length; i++) {
48350
- const b = buf[i];
48351
- if (b === 10) {
48352
- lines++;
48353
- } else if (b === 13) {
48354
- if (i + 1 < buf.length && buf[i + 1] === 10) {
48355
- lines++;
48356
- i++;
48357
- } else {
48358
- lines++;
48359
- }
48360
- }
48361
- bytesSinceYield++;
48362
- if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
48363
- await yieldToEventLoop();
48364
- bytesSinceYield = 0;
48365
- }
48366
- }
48367
- return lines;
48368
- }
48369
- async function readGitBlobLineRangeAsync(buf, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
48370
- const bufSize = 64 * 1024;
48371
- const decoder = new StringDecoder("utf8");
48372
- let currentLine = 0;
48373
- const resultLines = [];
48374
- let partial2 = "";
48375
- let done = false;
48376
- let skipLine0Chars = typeof lineOffsetIn === "number" ? lineOffsetIn : 0;
48377
- let line0CharsReturned = 0;
48378
- let line0Accum = "";
48379
- let bytesSinceYield = 0;
48380
- let position = 0;
48381
- const finish = async () => {
48382
- const totalLines = await countGitBlobLinesAsync(buf);
48383
- return { content: resultLines.join("\n"), totalLines };
48384
- };
48385
- while (!done && position < buf.length) {
48386
- const chunkEnd = Math.min(position + bufSize, buf.length);
48387
- const bytesRead = chunkEnd - position;
48388
- position = chunkEnd;
48389
- bytesSinceYield += bytesRead;
48390
- if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
48391
- await yieldToEventLoop();
48392
- bytesSinceYield = 0;
48393
- }
48394
- const text = partial2 + decoder.write(buf.subarray(position - bytesRead, position));
48395
- partial2 = "";
48396
- let lineStart = 0;
48397
- for (let i = 0; i < text.length; i++) {
48398
- if (text[i] === "\n") {
48399
- const lineContent = (() => {
48400
- let lineEnd = i;
48401
- if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
48402
- return text.slice(lineStart, lineEnd);
48403
- })();
48404
- if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
48405
- line0Accum += lineContent;
48406
- const totalLine0 = line0Accum.length;
48407
- if (skipLine0Chars > 0) {
48408
- if (totalLine0 <= skipLine0Chars) {
48409
- skipLine0Chars -= totalLine0;
48410
- line0Accum = "";
48411
- currentLine++;
48412
- lineStart = i + 1;
48413
- if (currentLine > endLine) {
48414
- done = true;
48415
- break;
48416
- }
48417
- continue;
48418
- }
48419
- const from = skipLine0Chars;
48420
- const take = Math.min(lineChunkSize, totalLine0 - from);
48421
- resultLines.push(line0Accum.slice(from, from + take));
48422
- line0CharsReturned += take;
48423
- if (from + take < totalLine0) {
48424
- const totalLines = await countGitBlobLinesAsync(buf);
48425
- return {
48426
- content: resultLines.join("\n"),
48427
- totalLines,
48428
- lineOffset: lineOffsetIn + line0CharsReturned
48429
- };
48430
- }
48431
- line0Accum = "";
48432
- skipLine0Chars = 0;
48433
- line0CharsReturned = 0;
48434
- } else if (totalLine0 > lineChunkSize) {
48435
- resultLines.push(line0Accum.slice(0, lineChunkSize));
48436
- const totalLines = await countGitBlobLinesAsync(buf);
48437
- return {
48438
- content: resultLines.join("\n"),
48439
- totalLines,
48440
- lineOffset: lineChunkSize
48441
- };
48442
- } else {
48443
- resultLines.push(line0Accum);
48444
- line0Accum = "";
48445
- }
48446
- } else if (currentLine >= startLine && currentLine <= endLine) {
48447
- resultLines.push(lineContent);
48448
- }
48449
- currentLine++;
48450
- lineStart = i + 1;
48451
- if (currentLine > endLine) {
48452
- done = true;
48453
- break;
48454
- }
48455
- }
48456
- }
48457
- if (!done) {
48458
- const lineContent = text.slice(lineStart);
48459
- if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0)) {
48460
- line0Accum += lineContent;
48461
- const totalLine0 = line0Accum.length;
48462
- if (skipLine0Chars > 0) {
48463
- if (totalLine0 <= skipLine0Chars) {
48464
- skipLine0Chars -= totalLine0;
48465
- line0Accum = "";
48466
- } else {
48467
- const from = skipLine0Chars;
48468
- const take = Math.min(lineChunkSize, totalLine0 - from);
48469
- resultLines.push(line0Accum.slice(from, from + take));
48470
- const totalLines = await countGitBlobLinesAsync(buf);
48471
- return {
48472
- content: resultLines.join("\n"),
48473
- totalLines,
48474
- lineOffset: (lineOffsetIn ?? 0) + take
48475
- };
48476
- }
48477
- } else if (totalLine0 > lineChunkSize) {
48478
- resultLines.push(line0Accum.slice(0, lineChunkSize));
48479
- const totalLines = await countGitBlobLinesAsync(buf);
48480
- return {
48481
- content: resultLines.join("\n"),
48482
- totalLines,
48483
- lineOffset: lineChunkSize
48484
- };
48485
- }
48486
- }
48487
- partial2 = text.slice(lineStart);
48488
- }
48489
- }
48490
- if (!done) {
48491
- const tail = partial2 + decoder.end();
48492
- if (currentLine === 0 && (startLine === 0 || lineOffsetIn !== void 0) && tail.length > 0) {
48493
- line0Accum += tail.endsWith("\r") ? tail.slice(0, -1) : tail;
48494
- const totalLine0 = line0Accum.length;
48495
- if (skipLine0Chars > 0) {
48496
- if (totalLine0 <= skipLine0Chars) {
48497
- return finish();
48498
- }
48499
- const from = skipLine0Chars;
48500
- const take = Math.min(lineChunkSize, totalLine0 - from);
48501
- resultLines.push(line0Accum.slice(from, from + take));
48502
- line0CharsReturned += take;
48503
- if (from + take < totalLine0) {
48504
- const totalLines = await countGitBlobLinesAsync(buf);
48505
- return {
48506
- content: resultLines.join("\n"),
48507
- totalLines,
48508
- lineOffset: (lineOffsetIn ?? 0) + line0CharsReturned
48509
- };
48510
- }
48511
- } else if (totalLine0 > lineChunkSize) {
48512
- resultLines.push(line0Accum.slice(0, lineChunkSize));
48513
- const totalLines = await countGitBlobLinesAsync(buf);
48514
- return {
48515
- content: resultLines.join("\n"),
48516
- totalLines,
48517
- lineOffset: lineChunkSize
48518
- };
48519
- } else {
48520
- resultLines.push(line0Accum);
48521
- }
48522
- } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
48523
- resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
48524
- }
48525
- }
48526
- return finish();
48527
- }
48528
- async function decodeGitBlobUtf8Async(buf) {
48529
- await yieldToEventLoop();
48530
- const text = buf.toString("utf8");
48531
- await yieldToEventLoop();
48532
- return text;
48533
- }
48534
- async function readGitBlobUtf8FullAsync(buf) {
48535
- const text = await decodeGitBlobUtf8Async(buf);
48536
- const totalLines = await countGitBlobLinesAsync(buf);
48537
- return { content: text, totalLines };
48538
- }
48539
-
48540
- // src/git/tree/file/index.ts
48541
- async function readGitTreeFileAsync(repoAbsPath, branch, relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, encoding = "utf8") {
48542
- await yieldToEventLoop();
48543
- const branchRef = branch.trim();
48544
- if (!branchRef) return { error: "branch required" };
48545
- const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
48546
- if (!rel || rel === ".") return { error: "file path required" };
48547
- const blobRef = await gitBlobRef(repoAbsPath, branchRef, rel);
48548
- if (!blobRef) return { error: "Branch not found" };
48549
- const hasRange = typeof startLine === "number" && typeof endLine === "number";
48550
- try {
48551
- if (encoding === "base64") {
48552
- if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
48553
- const buf2 = await cliSimpleGit(repoAbsPath).showBuffer([blobRef]);
48554
- await yieldToEventLoop();
48555
- return {
48556
- content: buf2.toString("base64"),
48557
- size: buf2.length,
48558
- mimeType: guessMimeType(rel)
48559
- };
48560
- }
48561
- const buf = await cliSimpleGit(repoAbsPath).showBuffer([blobRef]);
48562
- await yieldToEventLoop();
48563
- const size = buf.length;
48564
- if (isBinaryBuffer(buf)) {
48565
- return { error: "Binary file cannot be displayed as text" };
48566
- }
48567
- if (hasRange) {
48568
- const ranged = await readGitBlobLineRangeAsync(buf, startLine, endLine, lineOffset, lineChunkSize);
48569
- return {
48570
- content: ranged.content,
48571
- totalLines: ranged.totalLines,
48572
- size,
48573
- ...ranged.lineOffset != null ? { lineOffset: ranged.lineOffset } : {}
48574
- };
48575
- }
48576
- const full = await readGitBlobUtf8FullAsync(buf);
48577
- return { content: full.content, totalLines: full.totalLines, size, mimeType: guessMimeType(rel) };
48578
- } catch (err) {
48579
- const message = err instanceof Error ? err.message : String(err);
48580
- if (/does not exist|bad revision|exists on disk, but not in/i.test(message)) {
48581
- return { error: "File not found in branch" };
48582
- }
48583
- return { error: message };
48584
- }
49484
+ return skill.execute(operationId, params);
49485
+ }
49486
+
49487
+ // src/skills/handle-skill-call.ts
49488
+ function handleSkillCall(msg, socket, log2) {
49489
+ callSkill(msg.skillId, msg.operationId, msg.params ?? {}).then((result) => {
49490
+ sendWsMessage(socket, { type: "skill_result", id: msg.id, result });
49491
+ }).catch((err) => {
49492
+ sendWsMessage(socket, { type: "skill_result", id: msg.id, error: String(err) });
49493
+ log2(`[Bridge service] Skill invocation failed (${msg.skillId}/${msg.operationId}): ${err}`);
49494
+ });
48585
49495
  }
48586
49496
 
49497
+ // src/routing/handlers/skill-call.ts
49498
+ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
49499
+ const skillId = typeof msg.skillId === "string" ? msg.skillId : "";
49500
+ const operationId = typeof msg.operationId === "string" ? msg.operationId : "";
49501
+ if (!skillId || !operationId) return;
49502
+ const socket = getWs();
49503
+ if (!socket) return;
49504
+ handleSkillCall(
49505
+ { id: msg.id, skillId, operationId, params: msg.params },
49506
+ socket,
49507
+ log2
49508
+ );
49509
+ };
49510
+
48587
49511
  // src/files/browser/send-file-browser-message.ts
48588
49512
  function sendFileBrowserMessage(socket, e2ee, payload) {
48589
49513
  sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
@@ -48592,103 +49516,6 @@ function sendFileBrowserMessage(socket, e2ee, payload) {
48592
49516
  // src/files/read-file/resolve-file-cache-warmup.ts
48593
49517
  init_yield_to_event_loop();
48594
49518
  import path78 from "node:path";
48595
-
48596
- // src/files/read-file/resolve-file-cache.ts
48597
- import fs50 from "node:fs";
48598
- import path77 from "node:path";
48599
- var RESOLVE_CACHE_MAX = 4096;
48600
- var resolveCache = /* @__PURE__ */ new Map();
48601
- var warmedDirectories = /* @__PURE__ */ new Set();
48602
- function resolveCacheKey(sessionParentPath, relativePath) {
48603
- return `${sessionParentPath}\0${relativePath}`;
48604
- }
48605
- function warmedDirectoryKey(sessionParentPath, directoryRelativePath) {
48606
- const dir = directoryRelativePath.replace(/^\/+/, "") || ".";
48607
- return `${path77.resolve(sessionParentPath)}\0${dir}`;
48608
- }
48609
- function pathStaysUnderParent(filePath, sessionParentPath) {
48610
- const parent = path77.resolve(sessionParentPath);
48611
- const resolved = path77.resolve(filePath);
48612
- return resolved === parent || resolved.startsWith(`${parent}${path77.sep}`);
48613
- }
48614
- async function statMatchesCache(entry) {
48615
- try {
48616
- const stat3 = await fs50.promises.stat(entry.path);
48617
- return stat3.isFile() && stat3.mtimeMs === entry.mtimeMs && stat3.size === entry.size;
48618
- } catch {
48619
- return false;
48620
- }
48621
- }
48622
- function getResolveCacheEntry(cacheKey) {
48623
- return resolveCache.get(cacheKey);
48624
- }
48625
- function deleteResolveCacheEntry(cacheKey) {
48626
- resolveCache.delete(cacheKey);
48627
- }
48628
- function storeResolveCacheEntry(cacheKey, entry) {
48629
- if (resolveCache.size >= RESOLVE_CACHE_MAX) resolveCache.clear();
48630
- resolveCache.set(cacheKey, entry);
48631
- return entry;
48632
- }
48633
- function hasResolveCacheEntry(cacheKey) {
48634
- return resolveCache.has(cacheKey);
48635
- }
48636
- function markDirectoryWarmed(dirKey) {
48637
- if (warmedDirectories.has(dirKey)) return false;
48638
- warmedDirectories.add(dirKey);
48639
- return true;
48640
- }
48641
- function unmarkDirectoryWarmed(dirKey) {
48642
- warmedDirectories.delete(dirKey);
48643
- }
48644
-
48645
- // src/files/read-file/resolve-file-path.ts
48646
- init_ensure_under_cwd();
48647
- init_cwd();
48648
- import fs51 from "node:fs";
48649
- async function resolveFileForReadAsync(relativePath, sessionParentPath = getBridgeRoot()) {
48650
- const cacheKey = resolveCacheKey(sessionParentPath, relativePath);
48651
- const cached2 = getResolveCacheEntry(cacheKey);
48652
- if (cached2 != null && await statMatchesCache(cached2)) {
48653
- return { path: cached2.path, size: cached2.size };
48654
- }
48655
- if (cached2 != null) deleteResolveCacheEntry(cacheKey);
48656
- const resolved = ensureUnderCwd(relativePath, sessionParentPath);
48657
- if (!resolved) return { error: "Path is outside working directory" };
48658
- let real;
48659
- let size;
48660
- let mtimeMs;
48661
- try {
48662
- const lstat = await fs51.promises.lstat(resolved);
48663
- if (lstat.isSymbolicLink()) {
48664
- real = await fs51.promises.realpath(resolved);
48665
- if (!pathStaysUnderParent(real, sessionParentPath)) {
48666
- return { error: "Path is outside working directory" };
48667
- }
48668
- const stat3 = await fs51.promises.stat(real);
48669
- if (!stat3.isFile()) return { error: "Not a file" };
48670
- size = stat3.size;
48671
- mtimeMs = stat3.mtimeMs;
48672
- } else if (lstat.isFile()) {
48673
- real = resolved;
48674
- size = lstat.size;
48675
- mtimeMs = lstat.mtimeMs;
48676
- } else {
48677
- return { error: "Not a file" };
48678
- }
48679
- } catch (err) {
48680
- return { error: err instanceof Error ? err.message : String(err) };
48681
- }
48682
- const entry = storeResolveCacheEntry(cacheKey, { path: real, size, mtimeMs });
48683
- return { path: entry.path, size: entry.size };
48684
- }
48685
- async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeRoot()) {
48686
- const result = await resolveFileForReadAsync(relativePath, sessionParentPath);
48687
- if ("error" in result) return result;
48688
- return result.path;
48689
- }
48690
-
48691
- // src/files/read-file/resolve-file-cache-warmup.ts
48692
49519
  var WARMED_DIRECTORY_TTL_MS = 6e4;
48693
49520
  async function warmDirectoryResolveCache(sessionParentPath, directoryRelativePath) {
48694
49521
  const { listDirAsync: listDirAsync2 } = await Promise.resolve().then(() => (init_list_dir(), list_dir_exports));
@@ -48730,7 +49557,7 @@ function scheduleResolveCacheWarmupForListedEntries(sessionParentPath, listRelat
48730
49557
 
48731
49558
  // src/files/browser/handle-file-browser-list.ts
48732
49559
  async function handleFileBrowserList(socket, e2ee, id, reqPath, sessionParentPath, gitScope) {
48733
- const result = gitScope ? await listGitTreeDirAsync(gitScope.repoAbsPath, gitScope.branch, reqPath) : await listDirAsync(reqPath, sessionParentPath);
49560
+ const result = await executeFileBrowserList({ reqPath, sessionParentPath, gitScope });
48734
49561
  if ("error" in result) {
48735
49562
  sendWsMessage(socket, { type: "file_browser_response", id, error: result.error });
48736
49563
  return;
@@ -48744,403 +49571,8 @@ async function handleFileBrowserList(socket, e2ee, id, reqPath, sessionParentPat
48744
49571
  // src/files/browser/handle-file-browser-read.ts
48745
49572
  import path79 from "node:path";
48746
49573
  init_cwd();
48747
-
48748
- // src/files/read-file/index.ts
48749
- init_cwd();
48750
- init_yield_to_event_loop();
48751
- import fs55 from "node:fs";
48752
-
48753
- // src/files/read-file/read-file-range-async.ts
48754
- init_yield_to_event_loop();
48755
- import fs52 from "node:fs";
48756
- import { StringDecoder as StringDecoder2 } from "node:string_decoder";
48757
-
48758
- // src/files/read-file/read-file-range-line-zero.ts
48759
- function createLineZeroStreamState(lineOffsetIn) {
48760
- return {
48761
- line0Accum: "",
48762
- skipLine0Chars: typeof lineOffsetIn === "number" ? lineOffsetIn : 0,
48763
- line0CharsReturned: 0
48764
- };
48765
- }
48766
- function shouldTrackLineZero(startLine, lineOffsetIn) {
48767
- return startLine === 0 || lineOffsetIn !== void 0;
48768
- }
48769
- function stripCarriageReturn(lineContent) {
48770
- return lineContent.endsWith("\r") ? lineContent.slice(0, -1) : lineContent;
48771
- }
48772
- function lineContentBeforeNewline(text, lineStart, newlineIndex) {
48773
- let lineEnd = newlineIndex;
48774
- if (lineEnd > lineStart && text[lineEnd - 1] === "\r") lineEnd--;
48775
- return text.slice(lineStart, lineEnd);
48776
- }
48777
- function buildLineZeroChunkResult(resultLines, fileSize, lineOffset, totalLines = 1) {
48778
- return {
48779
- content: resultLines.join("\n"),
48780
- size: fileSize,
48781
- lineOffset,
48782
- totalLines
48783
- };
48784
- }
48785
- function tryFinishLineZeroOnCompleteLine(state, lineContent, resultLines, fileSize, lineChunkSize, lineOffsetIn, endLine, currentLine) {
48786
- state.line0Accum += lineContent;
48787
- const totalLine0 = state.line0Accum.length;
48788
- if (state.skipLine0Chars > 0) {
48789
- if (totalLine0 <= state.skipLine0Chars) {
48790
- state.skipLine0Chars -= totalLine0;
48791
- state.line0Accum = "";
48792
- return currentLine + 1 > endLine ? "done" : "continue";
48793
- }
48794
- const from = state.skipLine0Chars;
48795
- const take = Math.min(lineChunkSize, totalLine0 - from);
48796
- resultLines.push(state.line0Accum.slice(from, from + take));
48797
- state.line0CharsReturned += take;
48798
- if (from + take < totalLine0) {
48799
- return buildLineZeroChunkResult(
48800
- resultLines,
48801
- fileSize,
48802
- (lineOffsetIn ?? 0) + state.line0CharsReturned
48803
- );
48804
- }
48805
- state.line0Accum = "";
48806
- state.skipLine0Chars = 0;
48807
- state.line0CharsReturned = 0;
48808
- return "continue";
48809
- }
48810
- if (totalLine0 > lineChunkSize) {
48811
- resultLines.push(state.line0Accum.slice(0, lineChunkSize));
48812
- return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
48813
- }
48814
- resultLines.push(state.line0Accum);
48815
- state.line0Accum = "";
48816
- return "continue";
48817
- }
48818
- function tryFinishLineZeroPartial(state, partialLine, resultLines, fileSize, lineChunkSize, lineOffsetIn) {
48819
- state.line0Accum += partialLine;
48820
- const totalLine0 = state.line0Accum.length;
48821
- if (state.skipLine0Chars > 0) {
48822
- if (totalLine0 <= state.skipLine0Chars) {
48823
- state.skipLine0Chars -= totalLine0;
48824
- state.line0Accum = "";
48825
- return null;
48826
- }
48827
- const from = state.skipLine0Chars;
48828
- const take = Math.min(lineChunkSize, totalLine0 - from);
48829
- resultLines.push(state.line0Accum.slice(from, from + take));
48830
- return buildLineZeroChunkResult(resultLines, fileSize, (lineOffsetIn ?? 0) + take);
48831
- }
48832
- if (totalLine0 > lineChunkSize) {
48833
- resultLines.push(state.line0Accum.slice(0, lineChunkSize));
48834
- return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
48835
- }
48836
- return null;
48837
- }
48838
- function finishLineZeroTail(state, tail, resultLines, fileSize, lineChunkSize, lineOffsetIn) {
48839
- if (tail.length === 0) return null;
48840
- state.line0Accum += stripCarriageReturn(tail);
48841
- const totalLine0 = state.line0Accum.length;
48842
- if (state.skipLine0Chars > 0) {
48843
- if (totalLine0 <= state.skipLine0Chars) {
48844
- return { content: resultLines.join("\n"), size: fileSize };
48845
- }
48846
- const from = state.skipLine0Chars;
48847
- const take = Math.min(lineChunkSize, totalLine0 - from);
48848
- resultLines.push(state.line0Accum.slice(from, from + take));
48849
- state.line0CharsReturned += take;
48850
- if (from + take < totalLine0) {
48851
- return buildLineZeroChunkResult(
48852
- resultLines,
48853
- fileSize,
48854
- (lineOffsetIn ?? 0) + state.line0CharsReturned
48855
- );
48856
- }
48857
- return null;
48858
- }
48859
- if (totalLine0 > lineChunkSize) {
48860
- resultLines.push(state.line0Accum.slice(0, lineChunkSize));
48861
- return buildLineZeroChunkResult(resultLines, fileSize, lineChunkSize);
48862
- }
48863
- resultLines.push(state.line0Accum);
48864
- return null;
48865
- }
48866
-
48867
- // src/files/read-file/read-file-range-async.ts
48868
- async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE, fileSizeIn) {
48869
- const fileSize = fileSizeIn ?? (await fs52.promises.stat(filePath)).size;
48870
- const fd = await fs52.promises.open(filePath, "r");
48871
- const bufSize = 64 * 1024;
48872
- const buf = Buffer.alloc(bufSize);
48873
- const decoder = new StringDecoder2("utf8");
48874
- let currentLine = 0;
48875
- const resultLines = [];
48876
- let partial2 = "";
48877
- let done = false;
48878
- const trackLineZero = shouldTrackLineZero(startLine, lineOffsetIn);
48879
- const lineZeroState = createLineZeroStreamState(lineOffsetIn);
48880
- let bytesSinceYield = 0;
48881
- try {
48882
- let position = 0;
48883
- while (!done) {
48884
- const { bytesRead } = await fd.read(buf, 0, bufSize, position);
48885
- if (bytesRead === 0) break;
48886
- position += bytesRead;
48887
- bytesSinceYield += bytesRead;
48888
- if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
48889
- await yieldToEventLoop();
48890
- bytesSinceYield = 0;
48891
- }
48892
- const text = partial2 + decoder.write(buf.subarray(0, bytesRead));
48893
- partial2 = "";
48894
- let lineStart = 0;
48895
- for (let i = 0; i < text.length; i++) {
48896
- if (text[i] !== "\n") continue;
48897
- const lineContent2 = lineContentBeforeNewline(text, lineStart, i);
48898
- if (currentLine === 0 && trackLineZero) {
48899
- const lineZeroResult = tryFinishLineZeroOnCompleteLine(
48900
- lineZeroState,
48901
- lineContent2,
48902
- resultLines,
48903
- fileSize,
48904
- lineChunkSize,
48905
- lineOffsetIn,
48906
- endLine,
48907
- currentLine
48908
- );
48909
- if (lineZeroResult === "done") {
48910
- done = true;
48911
- break;
48912
- }
48913
- if (lineZeroResult !== "continue") return lineZeroResult;
48914
- currentLine++;
48915
- lineStart = i + 1;
48916
- if (currentLine > endLine) {
48917
- done = true;
48918
- break;
48919
- }
48920
- continue;
48921
- }
48922
- if (currentLine >= startLine && currentLine <= endLine) {
48923
- resultLines.push(lineContent2);
48924
- }
48925
- currentLine++;
48926
- lineStart = i + 1;
48927
- if (currentLine > endLine) {
48928
- done = true;
48929
- break;
48930
- }
48931
- }
48932
- if (done) continue;
48933
- const lineContent = text.slice(lineStart);
48934
- if (currentLine === 0 && trackLineZero) {
48935
- const partialResult = tryFinishLineZeroPartial(
48936
- lineZeroState,
48937
- lineContent,
48938
- resultLines,
48939
- fileSize,
48940
- lineChunkSize,
48941
- lineOffsetIn
48942
- );
48943
- if (partialResult != null) return partialResult;
48944
- } else {
48945
- partial2 = lineContent;
48946
- }
48947
- }
48948
- if (!done) {
48949
- const tail = partial2 + decoder.end();
48950
- if (currentLine === 0 && trackLineZero) {
48951
- const tailResult = finishLineZeroTail(
48952
- lineZeroState,
48953
- tail,
48954
- resultLines,
48955
- fileSize,
48956
- lineChunkSize,
48957
- lineOffsetIn
48958
- );
48959
- if (tailResult != null) return tailResult;
48960
- } else if (tail.length > 0 && currentLine >= startLine && currentLine <= endLine) {
48961
- resultLines.push(tail.endsWith("\r") ? tail.slice(0, -1) : tail);
48962
- }
48963
- }
48964
- return { content: resultLines.join("\n"), size: fileSize };
48965
- } finally {
48966
- await fd.close();
48967
- }
48968
- }
48969
-
48970
- // src/files/read-file/read-small-file-range-async.ts
48971
- import fs53 from "node:fs";
48972
- var SMALL_FILE_RANGE_MAX_BYTES = 512 * 1024;
48973
- function sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize) {
48974
- if (startLine === 0 && lineOffsetIn == null && lines[0] != null && lines[0].length > lineChunkSize) {
48975
- return {
48976
- content: lines[0].slice(0, lineChunkSize),
48977
- size: fileSize,
48978
- lineOffset: lineChunkSize,
48979
- totalLines: lines.length
48980
- };
48981
- }
48982
- if (startLine === 0 && typeof lineOffsetIn === "number") {
48983
- const line0 = lines[0] ?? "";
48984
- const from = lineOffsetIn;
48985
- const take = Math.min(lineChunkSize, Math.max(0, line0.length - from));
48986
- return {
48987
- content: line0.slice(from, from + take),
48988
- size: fileSize,
48989
- ...from + take < line0.length ? { lineOffset: from + take, totalLines: lines.length } : { totalLines: lines.length }
48990
- };
48991
- }
48992
- const slice = lines.slice(startLine, endLine + 1);
48993
- return {
48994
- content: slice.join("\n"),
48995
- size: fileSize,
48996
- totalLines: lines.length
48997
- };
48998
- }
48999
- async function readSmallFileRangeAsync(filePath, fileSize, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
49000
- const raw = await fs53.promises.readFile(filePath, "utf8");
49001
- const lines = raw.split(/\r?\n/);
49002
- return sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize);
49003
- }
49004
-
49005
- // src/files/read-file/read-file-buffer-full-async.ts
49006
- init_yield_to_event_loop();
49007
- import fs54 from "node:fs";
49008
- var READ_CHUNK_BYTES = 256 * 1024;
49009
- async function readFileBufferFullAsync(filePath) {
49010
- const stat3 = await fs54.promises.stat(filePath);
49011
- const fd = await fs54.promises.open(filePath, "r");
49012
- const chunks = [];
49013
- let position = 0;
49014
- let bytesSinceYield = 0;
49015
- try {
49016
- while (position < stat3.size) {
49017
- const buf = Buffer.alloc(Math.min(READ_CHUNK_BYTES, stat3.size - position));
49018
- const { bytesRead } = await fd.read(buf, 0, buf.length, position);
49019
- if (bytesRead === 0) break;
49020
- chunks.push(buf.subarray(0, bytesRead));
49021
- position += bytesRead;
49022
- bytesSinceYield += bytesRead;
49023
- if (bytesSinceYield >= READ_RANGE_YIELD_EVERY_BYTES) {
49024
- await yieldToEventLoop();
49025
- bytesSinceYield = 0;
49026
- }
49027
- }
49028
- } finally {
49029
- await fd.close();
49030
- }
49031
- return { buffer: Buffer.concat(chunks), size: stat3.size };
49032
- }
49033
-
49034
- // src/files/read-file/read-file-full-async.ts
49035
- async function readFileFullAsync(filePath) {
49036
- const { buffer, size } = await readFileBufferFullAsync(filePath);
49037
- const raw = buffer.toString("utf8");
49038
- const lines = raw.split(/\r?\n/);
49039
- return { content: raw, totalLines: lines.length, size };
49040
- }
49041
-
49042
- // src/files/read-file/read-file-binary-full-async.ts
49043
- async function readFileBinaryFullAsync(filePath) {
49044
- const { buffer, size } = await readFileBufferFullAsync(filePath);
49045
- return {
49046
- content: buffer.toString("base64"),
49047
- size,
49048
- mimeType: guessMimeType(filePath)
49049
- };
49050
- }
49051
-
49052
- // src/files/read-file/index.ts
49053
- async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineChunkSize = LINE_CHUNK_SIZE, sessionParentPath = getBridgeRoot(), encoding = "utf8", options) {
49054
- if (!options?.skipInitialYield) {
49055
- await yieldToEventLoop();
49056
- }
49057
- try {
49058
- let resolvedPath;
49059
- let fileSize;
49060
- if (options?.useResolvedSize) {
49061
- const resolved = await resolveFileForReadAsync(relativePath, sessionParentPath);
49062
- if ("error" in resolved) return resolved;
49063
- resolvedPath = resolved.path;
49064
- fileSize = resolved.size;
49065
- } else {
49066
- const result = await resolveFilePathAsync(relativePath, sessionParentPath);
49067
- if (typeof result === "object") return result;
49068
- resolvedPath = result;
49069
- }
49070
- const hasRange = typeof startLine === "number" && typeof endLine === "number";
49071
- if (encoding === "base64") {
49072
- if (hasRange) return { error: "base64 encoding requires a full file read (no line range)" };
49073
- const read2 = await readFileBinaryFullAsync(resolvedPath);
49074
- return { ...read2, resolvedPath };
49075
- }
49076
- if (hasRange) {
49077
- if (fileSize == null) {
49078
- const stat3 = await fs55.promises.stat(resolvedPath);
49079
- fileSize = stat3.size;
49080
- }
49081
- const read2 = fileSize <= SMALL_FILE_RANGE_MAX_BYTES ? await readSmallFileRangeAsync(
49082
- resolvedPath,
49083
- fileSize,
49084
- startLine,
49085
- endLine,
49086
- lineOffset,
49087
- lineChunkSize
49088
- ) : await readFileRangeAsync(
49089
- resolvedPath,
49090
- startLine,
49091
- endLine,
49092
- lineOffset,
49093
- lineChunkSize,
49094
- fileSize
49095
- );
49096
- return read2;
49097
- }
49098
- const read = await readFileFullAsync(resolvedPath);
49099
- return { ...read, resolvedPath };
49100
- } catch (err) {
49101
- return { error: err instanceof Error ? err.message : String(err) };
49102
- }
49103
- }
49104
-
49105
- // src/files/browser/file-browser-read-queue.ts
49106
- var readChain = Promise.resolve();
49107
- function withFileBrowserReadQueue(fn) {
49108
- const next = readChain.then(() => fn());
49109
- readChain = next.then(
49110
- () => void 0,
49111
- () => void 0
49112
- );
49113
- return next;
49114
- }
49115
-
49116
- // src/files/browser/handle-file-browser-read.ts
49117
49574
  async function handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPath, gitScope) {
49118
- const startLine = typeof msg.startLine === "number" ? msg.startLine : void 0;
49119
- const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
49120
- const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
49121
- const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
49122
- const encoding = msg.encoding === "base64" ? "base64" : "utf8";
49123
- const result = gitScope ? await readGitTreeFileAsync(
49124
- gitScope.repoAbsPath,
49125
- gitScope.branch,
49126
- reqPath,
49127
- startLine,
49128
- endLine,
49129
- lineOffset,
49130
- lineChunkSize,
49131
- encoding
49132
- ) : await withFileBrowserReadQueue(
49133
- () => readFileAsync(
49134
- reqPath,
49135
- startLine,
49136
- endLine,
49137
- lineOffset,
49138
- lineChunkSize,
49139
- sessionParentPath,
49140
- encoding,
49141
- { skipInitialYield: true, useResolvedSize: true }
49142
- )
49143
- );
49575
+ const result = await executeFileBrowserRead({ msg, reqPath, sessionParentPath, gitScope });
49144
49576
  if ("error" in result) {
49145
49577
  sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
49146
49578
  return;
@@ -49162,15 +49594,6 @@ async function handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPa
49162
49594
  }
49163
49595
  }
49164
49596
 
49165
- // src/files/browser/resolve-git-branch-scope.ts
49166
- function resolveGitBranchScope(msg) {
49167
- const gitBranchScope = msg.source === "git_branch" && typeof msg.repoRelPath === "string" && typeof msg.branch === "string" && msg.branch.trim().length > 0;
49168
- if (!gitBranchScope) return null;
49169
- const repoAbsPath = resolveRepoAbsPathFromBridge(msg.repoRelPath);
49170
- if (!repoAbsPath) return null;
49171
- return { repoAbsPath, branch: msg.branch.trim() };
49172
- }
49173
-
49174
49597
  // src/files/browser/index.ts
49175
49598
  function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
49176
49599
  beginFileBrowserRequest();
@@ -49360,14 +49783,14 @@ function codeNavSessionKey(sessionId, sessionParentPath) {
49360
49783
  // src/code-nav/handlers/wait-for-symbol-index-ready.ts
49361
49784
  var CODE_NAV_INDEX_READY_WAIT_MS = 75;
49362
49785
  var CODE_NAV_INDEX_READY_POLL_MS = 15;
49363
- function delay2(ms) {
49786
+ function delay3(ms) {
49364
49787
  return new Promise((resolve37) => setTimeout(resolve37, ms));
49365
49788
  }
49366
49789
  async function waitForSymbolIndexReady(sessionParentPath, reqPath, maxWaitMs = CODE_NAV_INDEX_READY_WAIT_MS) {
49367
49790
  const deadline = Date.now() + maxWaitMs;
49368
49791
  do {
49369
49792
  if (await symbolIndexFileIsReadyAsync(sessionParentPath, reqPath)) return true;
49370
- await delay2(CODE_NAV_INDEX_READY_POLL_MS);
49793
+ await delay3(CODE_NAV_INDEX_READY_POLL_MS);
49371
49794
  } while (Date.now() < deadline);
49372
49795
  return await symbolIndexFileIsReadyAsync(sessionParentPath, reqPath);
49373
49796
  }
@@ -50344,6 +50767,8 @@ function createMainBridgeOpenHandler(params) {
50344
50767
  cliVersion: CLI_VERSION,
50345
50768
  bridgeRootPath: identifyReportedPaths.bridgeRootPath,
50346
50769
  worktreesRootPath: identifyReportedPaths.worktreesRootPath,
50770
+ localShortcutPort: identifyReportedPaths.localShortcutPort,
50771
+ localShortcutToken: identifyReportedPaths.localShortcutToken,
50347
50772
  ...e2ee ? { e: e2ee.handshake } : {}
50348
50773
  });
50349
50774
  reportGitRepos(getWs, logFn);
@@ -50470,6 +50895,7 @@ async function createBridgeConnection(options) {
50470
50895
  connect();
50471
50896
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeRoot());
50472
50897
  const bridgeAccess = await runtime.bridgeAccessPromise;
50898
+ const localShortcut = await runtime.localShortcutPromise;
50473
50899
  return {
50474
50900
  close: async () => {
50475
50901
  requestCliImmediateShutdown();
@@ -50482,6 +50908,7 @@ async function createBridgeConnection(options) {
50482
50908
  runtime.logFn
50483
50909
  );
50484
50910
  await bridgeAccess.close();
50911
+ await localShortcut.close();
50485
50912
  }
50486
50913
  };
50487
50914
  }