@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/index.js CHANGED
@@ -2237,12 +2237,12 @@ var require_websocket = __commonJS({
2237
2237
  "use strict";
2238
2238
  var EventEmitter2 = __require("events");
2239
2239
  var https2 = __require("https");
2240
- var http2 = __require("http");
2240
+ var http3 = __require("http");
2241
2241
  var net = __require("net");
2242
2242
  var tls = __require("tls");
2243
- var { randomBytes: randomBytes2, createHash: createHash2 } = __require("crypto");
2243
+ var { randomBytes: randomBytes3, createHash: createHash2 } = __require("crypto");
2244
2244
  var { Duplex, Readable: Readable2 } = __require("stream");
2245
- var { URL: URL2 } = __require("url");
2245
+ var { URL: URL3 } = __require("url");
2246
2246
  var PerMessageDeflate = require_permessage_deflate();
2247
2247
  var Receiver2 = require_receiver();
2248
2248
  var Sender2 = require_sender();
@@ -2735,11 +2735,11 @@ var require_websocket = __commonJS({
2735
2735
  );
2736
2736
  }
2737
2737
  let parsedUrl;
2738
- if (address instanceof URL2) {
2738
+ if (address instanceof URL3) {
2739
2739
  parsedUrl = address;
2740
2740
  } else {
2741
2741
  try {
2742
- parsedUrl = new URL2(address);
2742
+ parsedUrl = new URL3(address);
2743
2743
  } catch (e) {
2744
2744
  throw new SyntaxError(`Invalid URL: ${address}`);
2745
2745
  }
@@ -2770,8 +2770,8 @@ var require_websocket = __commonJS({
2770
2770
  }
2771
2771
  }
2772
2772
  const defaultPort = isSecure ? 443 : 80;
2773
- const key = randomBytes2(16).toString("base64");
2774
- const request = isSecure ? https2.request : http2.request;
2773
+ const key = randomBytes3(16).toString("base64");
2774
+ const request = isSecure ? https2.request : http3.request;
2775
2775
  const protocolSet = /* @__PURE__ */ new Set();
2776
2776
  let perMessageDeflate;
2777
2777
  opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
@@ -2876,7 +2876,7 @@ var require_websocket = __commonJS({
2876
2876
  req.abort();
2877
2877
  let addr;
2878
2878
  try {
2879
- addr = new URL2(location, address);
2879
+ addr = new URL3(location, address);
2880
2880
  } catch (e) {
2881
2881
  const err = new SyntaxError(`Invalid URL: ${location}`);
2882
2882
  emitErrorAndClose(websocket, err);
@@ -3265,7 +3265,7 @@ var require_websocket_server = __commonJS({
3265
3265
  "../../node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js"(exports, module) {
3266
3266
  "use strict";
3267
3267
  var EventEmitter2 = __require("events");
3268
- var http2 = __require("http");
3268
+ var http3 = __require("http");
3269
3269
  var { Duplex } = __require("stream");
3270
3270
  var { createHash: createHash2 } = __require("crypto");
3271
3271
  var extension = require_extension();
@@ -3340,8 +3340,8 @@ var require_websocket_server = __commonJS({
3340
3340
  );
3341
3341
  }
3342
3342
  if (options.port != null) {
3343
- this._server = http2.createServer((req, res) => {
3344
- const body = http2.STATUS_CODES[426];
3343
+ this._server = http3.createServer((req, res) => {
3344
+ const body = http3.STATUS_CODES[426];
3345
3345
  res.writeHead(426, {
3346
3346
  "Content-Length": body.length,
3347
3347
  "Content-Type": "text/plain"
@@ -3628,7 +3628,7 @@ var require_websocket_server = __commonJS({
3628
3628
  this.destroy();
3629
3629
  }
3630
3630
  function abortHandshake(socket, code, message, headers) {
3631
- message = message || http2.STATUS_CODES[code];
3631
+ message = message || http3.STATUS_CODES[code];
3632
3632
  headers = {
3633
3633
  Connection: "close",
3634
3634
  "Content-Type": "text/html",
@@ -3637,7 +3637,7 @@ var require_websocket_server = __commonJS({
3637
3637
  };
3638
3638
  socket.once("finish", socket.destroy);
3639
3639
  socket.end(
3640
- `HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
3640
+ `HTTP/1.1 ${code} ${http3.STATUS_CODES[code]}\r
3641
3641
  ` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
3642
3642
  );
3643
3643
  }
@@ -22669,11 +22669,11 @@ var require_dist2 = __commonJS({
22669
22669
  });
22670
22670
 
22671
22671
  // src/files/ensure-under-cwd.ts
22672
- import path73 from "node:path";
22672
+ import path72 from "node:path";
22673
22673
  function ensureUnderCwd(relativePath, cwd = getBridgeRoot()) {
22674
- const normalized = path73.normalize(relativePath).replace(/^(\.\/)+/, "");
22675
- const resolved = path73.resolve(cwd, normalized);
22676
- if (!resolved.startsWith(cwd + path73.sep) && resolved !== cwd) {
22674
+ const normalized = path72.normalize(relativePath).replace(/^(\.\/)+/, "");
22675
+ const resolved = path72.resolve(cwd, normalized);
22676
+ if (!resolved.startsWith(cwd + path72.sep) && resolved !== cwd) {
22677
22677
  return null;
22678
22678
  }
22679
22679
  return resolved;
@@ -22695,15 +22695,15 @@ var init_types2 = __esm({
22695
22695
  });
22696
22696
 
22697
22697
  // src/files/list-dir/map-dir-entry.ts
22698
- import path74 from "node:path";
22699
- import fs47 from "node:fs";
22698
+ import path73 from "node:path";
22699
+ import fs45 from "node:fs";
22700
22700
  async function mapDirEntry(d, relativePath, resolved) {
22701
- const entryPath = path74.join(relativePath || ".", d.name).replace(/\\/g, "/");
22702
- const fullPath = path74.join(resolved, d.name);
22701
+ const entryPath = path73.join(relativePath || ".", d.name).replace(/\\/g, "/");
22702
+ const fullPath = path73.join(resolved, d.name);
22703
22703
  let isDir = d.isDirectory();
22704
22704
  if (d.isSymbolicLink()) {
22705
22705
  try {
22706
- const targetStat = await fs47.promises.stat(fullPath);
22706
+ const targetStat = await fs45.promises.stat(fullPath);
22707
22707
  isDir = targetStat.isDirectory();
22708
22708
  } catch {
22709
22709
  isDir = false;
@@ -22740,7 +22740,7 @@ var list_dir_exports = {};
22740
22740
  __export(list_dir_exports, {
22741
22741
  listDirAsync: () => listDirAsync
22742
22742
  });
22743
- import fs48 from "node:fs";
22743
+ import fs46 from "node:fs";
22744
22744
  async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
22745
22745
  await yieldToEventLoop();
22746
22746
  const resolved = ensureUnderCwd(relativePath || ".", sessionParentPath);
@@ -22748,7 +22748,7 @@ async function listDirAsync(relativePath, sessionParentPath = getBridgeRoot()) {
22748
22748
  return { error: "Path is outside working directory" };
22749
22749
  }
22750
22750
  try {
22751
- const names = await fs48.promises.readdir(resolved, { withFileTypes: true });
22751
+ const names = await fs46.promises.readdir(resolved, { withFileTypes: true });
22752
22752
  const entries = [];
22753
22753
  for (let i = 0; i < names.length; i++) {
22754
22754
  if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
@@ -25326,7 +25326,7 @@ function installBridgeProcessResilience() {
25326
25326
  }
25327
25327
 
25328
25328
  // src/cli-version.ts
25329
- var CLI_VERSION = "0.1.64".length > 0 ? "0.1.64" : "0.0.0-dev";
25329
+ var CLI_VERSION = "0.1.65".length > 0 ? "0.1.65" : "0.0.0-dev";
25330
25330
 
25331
25331
  // src/connection/heartbeat/constants.ts
25332
25332
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -26185,7 +26185,7 @@ function clearMainBridgeReconnectQuietOnOpen(state, log2) {
26185
26185
  function scheduleMainBridgeReconnect(state, connect, log2, closeMeta) {
26186
26186
  if (state.closedByUser || state.currentWs != null) return;
26187
26187
  const meta = closeMeta ?? state.lastReconnectCloseMeta ?? void 0;
26188
- const delay3 = applyTieredReconnectPlanAndLog(
26188
+ const delay4 = applyTieredReconnectPlanAndLog(
26189
26189
  state.mainOutage,
26190
26190
  log2,
26191
26191
  BRIDGE_SERVICE_LABEL,
@@ -26194,7 +26194,7 @@ function scheduleMainBridgeReconnect(state, connect, log2, closeMeta) {
26194
26194
  meta?.reason
26195
26195
  );
26196
26196
  armReconnectDelayTimer({
26197
- delayMs: delay3,
26197
+ delayMs: delay4,
26198
26198
  bumpAttempt: () => {
26199
26199
  state.reconnectAttempt += 1;
26200
26200
  },
@@ -26396,10 +26396,10 @@ function runPendingAuth(options) {
26396
26396
  }
26397
26397
  if (resolved) return;
26398
26398
  beginDeferredPendingCloseLog(code, reason);
26399
- const delay3 = pendingAuthReconnectDelayMs(reconnectAttempt);
26399
+ const delay4 = pendingAuthReconnectDelayMs(reconnectAttempt);
26400
26400
  reconnectAttempt += 1;
26401
26401
  if (signInQuiet.verboseLogs) {
26402
- const delayLabel = formatReconnectDelayForLog(delay3);
26402
+ const delayLabel = formatReconnectDelayForLog(delay4);
26403
26403
  logFn(
26404
26404
  `[Bridge service] Next sign-in connection attempt in ${delayLabel} (attempt ${reconnectAttempt}).`
26405
26405
  );
@@ -26407,7 +26407,7 @@ function runPendingAuth(options) {
26407
26407
  reconnectTimeout = setTimeout(() => {
26408
26408
  reconnectTimeout = null;
26409
26409
  connect();
26410
- }, delay3);
26410
+ }, delay4);
26411
26411
  },
26412
26412
  onError: (err) => logCliWebSocketError(logFn, "[Bridge service]", err, "while waiting for sign-in"),
26413
26413
  onMessage: (data) => {
@@ -35877,7 +35877,9 @@ function createBridgePreviewStack(options) {
35877
35877
  };
35878
35878
  const identifyReportedPaths = {
35879
35879
  bridgeRootPath: path38.resolve(getBridgeRoot()),
35880
- worktreesRootPath: path38.resolve(options.worktreesRootPath)
35880
+ worktreesRootPath: path38.resolve(options.worktreesRootPath),
35881
+ localShortcutPort: 0,
35882
+ localShortcutToken: ""
35881
35883
  };
35882
35884
  return { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths };
35883
35885
  }
@@ -36236,7 +36238,7 @@ function createFirehoseReconnectScheduler(ctx, reconnect) {
36236
36238
  function scheduleFirehoseRetryAfterDrop(closeMeta) {
36237
36239
  if (state.closedByUser) return;
36238
36240
  const meta = closeMeta ?? state.lastFirehoseReconnectCloseMeta ?? void 0;
36239
- const delay3 = applyTieredReconnectPlanAndLog(
36241
+ const delay4 = applyTieredReconnectPlanAndLog(
36240
36242
  state.firehoseOutage,
36241
36243
  logFn,
36242
36244
  PREVIEW_TUNNEL_SERVICE_LABEL,
@@ -36245,7 +36247,7 @@ function createFirehoseReconnectScheduler(ctx, reconnect) {
36245
36247
  meta?.reason
36246
36248
  );
36247
36249
  armReconnectDelayTimer({
36248
- delayMs: delay3,
36250
+ delayMs: delay4,
36249
36251
  bumpAttempt: () => {
36250
36252
  state.firehoseReconnectAttempt += 1;
36251
36253
  },
@@ -44092,966 +44094,191 @@ function createBridgeWorktreeManagers(options) {
44092
44094
  };
44093
44095
  }
44094
44096
 
44095
- // src/connection/create-bridge-connection-runtime.ts
44096
- async function createBridgeConnectionRuntime(options, params) {
44097
- const { apiUrl, workspaceId } = options;
44098
- const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
44099
- const { state, getWs, logFn } = params;
44100
- const tokens = {
44101
- accessToken: options.authToken,
44102
- refreshToken: options.refreshToken
44103
- };
44104
- const { worktreesRootPath, sessionWorktreeManager, previewWorktreeManager } = createBridgeWorktreeManagers({ worktreesRootPath: options.worktreesRootPath, log: logFn });
44105
- const { e2ee, acpManager, bridgeAccessPromise } = await createBridgeAccessAndAcp({
44106
- apiUrl,
44107
- workspaceId,
44108
- tokens,
44109
- e2eCertificate: options.e2eCertificate,
44110
- getWs,
44111
- log: logFn
44112
- });
44113
- logFn("CLI running. Press Ctrl+C to exit.");
44114
- const { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths } = createBridgePreviewStack({
44115
- getWs,
44116
- log: logFn,
44117
- e2ee,
44118
- previewWorktreeManager,
44119
- worktreesRootPath
44120
- });
44121
- const { bridgeHeartbeat, messageDeps } = createBridgeRuntimeMessageSetup({
44122
- apiUrl,
44123
- workspaceId,
44124
- firehoseServerUrl,
44125
- state,
44126
- getWs,
44127
- log: logFn,
44128
- tokens,
44129
- acpManager,
44130
- sessionWorktreeManager,
44131
- previewWorktreeManager,
44132
- previewEnvironmentManager,
44133
- e2ee
44134
- });
44135
- return {
44136
- state,
44137
- getWs,
44138
- logFn,
44139
- tokens,
44140
- acpManager,
44141
- bridgeAccessPromise,
44142
- previewEnvironmentManager,
44143
- bridgeHeartbeat,
44144
- messageDeps,
44145
- identifyReportedPaths,
44146
- scheduleInitialIndexBuildsOnce,
44147
- e2ee
44148
- };
44097
+ // src/local-shortcut/lib/generate-token.ts
44098
+ import { randomBytes as randomBytes2 } from "node:crypto";
44099
+ function generateShortcutToken() {
44100
+ return randomBytes2(32).toString("base64url");
44149
44101
  }
44150
44102
 
44151
- // src/connection/create-main-bridge-reconnect-state.ts
44152
- function createMainBridgeReconnectState() {
44153
- const state = {
44154
- closedByUser: false,
44155
- reconnectAttempt: 0,
44156
- lastReconnectCloseMeta: null,
44157
- logBridgeOpenAsReconnect: false,
44158
- reconnectTimeout: null,
44159
- currentWs: null,
44160
- mainQuiet: createEmptyReconnectQuietSlot(),
44161
- mainOutage: createEmptyReconnectOutageTracker(),
44162
- duplicateBridgeFlap: createEmptyDuplicateBridgeFlapTracker(),
44163
- firehoseHandle: null,
44164
- lastFirehoseParams: null,
44165
- firehoseReconnectTimeout: null,
44166
- firehoseReconnectAttempt: 0,
44167
- firehoseGeneration: 0,
44168
- firehoseQuiet: createEmptyReconnectQuietSlot(),
44169
- firehoseOutage: createEmptyReconnectOutageTracker(),
44170
- lastFirehoseReconnectCloseMeta: null
44171
- };
44172
- return {
44173
- state,
44174
- getWs: () => state.currentWs
44175
- };
44176
- }
44103
+ // src/local-shortcut/lib/start-server.ts
44104
+ import * as http2 from "node:http";
44177
44105
 
44178
- // src/connection/ensure-bridge-connection-databases.ts
44179
- async function ensureBridgeConnectionDatabases(log2) {
44180
- await ensureCliSqliteInitialized({ logLegacyMigration: log2 });
44181
- await ensureCodeNavCacheSqliteInitialized();
44182
- }
44106
+ // src/local-shortcut/lib/server.ts
44107
+ import { URL as URL2 } from "node:url";
44183
44108
 
44184
- // src/connection/build-bridge-url.ts
44185
- function buildBridgeUrl(apiUrl, workspaceId, authToken) {
44186
- const base = apiUrl.startsWith("https") ? apiUrl.replace(/^https/, "wss") : apiUrl.replace(/^http/, "ws");
44187
- const params = new URLSearchParams({ workspaceId, token: authToken });
44188
- return `${base}/ws/bridge?${params.toString()}`;
44109
+ // src/local-shortcut/lib/cors.ts
44110
+ var LOCALHOST_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
44111
+ var FIXED_ALLOWED_ORIGINS = /* @__PURE__ */ new Set([
44112
+ "https://app.buildautomaton.com",
44113
+ "http://localhost:3000",
44114
+ "http://127.0.0.1:3000"
44115
+ ]);
44116
+ function resolveAllowedCorsOrigin(origin) {
44117
+ if (!origin) return null;
44118
+ if (FIXED_ALLOWED_ORIGINS.has(origin)) return origin;
44119
+ if (LOCALHOST_ORIGIN_RE.test(origin)) return origin;
44120
+ return null;
44189
44121
  }
44190
-
44191
- // src/types/api-to-bridge-messages.ts
44192
- var API_TO_BRIDGE_MESSAGE_TYPES = [
44193
- "auth_token",
44194
- "bridge_identified",
44195
- "ha",
44196
- "preview_environments_config",
44197
- "preview_environment_control",
44198
- "deploy_session_to_preview",
44199
- "agent_config",
44200
- "prompt_queue_state",
44201
- "prompt",
44202
- "session_git_request",
44203
- "rename_session_branch",
44204
- "session_archived",
44205
- "session_discarded",
44206
- "revert_turn_snapshot",
44207
- "cursor_request_response",
44208
- "skill_call",
44209
- "file_browser_request",
44210
- "file_browser_search",
44211
- "code_nav",
44212
- "skill_layout_request",
44213
- "install_skills",
44214
- "refresh_local_skills",
44215
- "bridge_git_context_request",
44216
- "list_repo_branches_request"
44217
- ];
44218
- var API_TO_BRIDGE_TYPE_SET = new Set(API_TO_BRIDGE_MESSAGE_TYPES);
44219
- function parseApiToBridgeMessage(data, log2) {
44220
- if (data === null || typeof data !== "object" || Array.isArray(data)) return null;
44221
- const t = data.type;
44222
- if (typeof t !== "string" || !API_TO_BRIDGE_TYPE_SET.has(t)) {
44223
- if (typeof t === "string") {
44224
- log2?.(`[Bridge service] unhandled message type: ${t}`);
44225
- }
44226
- return null;
44227
- }
44228
- if (t === "ha") {
44229
- const s = data.s;
44230
- if (typeof s !== "number" || !Number.isFinite(s)) return null;
44122
+ function applyCorsHeaders(req, res) {
44123
+ const origin = typeof req.headers.origin === "string" ? req.headers.origin : void 0;
44124
+ const allowed = resolveAllowedCorsOrigin(origin);
44125
+ if (allowed) {
44126
+ res.setHeader("Access-Control-Allow-Origin", allowed);
44127
+ res.setHeader("Vary", "Origin");
44128
+ res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
44129
+ res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
44130
+ res.setHeader("Access-Control-Max-Age", "86400");
44231
44131
  }
44232
- return data;
44132
+ return allowed != null;
44233
44133
  }
44234
44134
 
44235
- // src/routing/handlers/auth-token.ts
44236
- var handleAuthToken = (msg, { log: log2 }) => {
44237
- if (typeof msg.token !== "string") return;
44238
- log2("Received auth token. Save it for future runs:");
44239
- log2(` export BUILDAUTOMATON_AUTH_TOKEN="${msg.token}"`);
44240
- };
44241
-
44242
- // src/routing/handlers/bridge-identified.ts
44243
- var handleBridgeIdentified = (msg, deps) => {
44244
- if (typeof msg.bridgeName !== "string") return;
44245
- deps.onBridgeIdentified({
44246
- bridgeName: msg.bridgeName,
44247
- proxyPorts: msg.proxyPorts,
44248
- previewEnvironments: msg.previewEnvironments
44249
- });
44250
- setImmediate(() => {
44251
- void (async () => {
44252
- cancelInFlightAgentCapabilityWarmup();
44253
- try {
44254
- await deps.reportAutoDetectedAgents?.();
44255
- } catch (e) {
44256
- deps.log(
44257
- `[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
44258
- );
44259
- }
44260
- try {
44261
- await deps.warmupAgentCapabilitiesOnConnect?.();
44262
- } catch (e) {
44263
- deps.log(
44264
- `[Bridge service] Agent capability warmup failed: ${e instanceof Error ? e.message : String(e)}`
44265
- );
44266
- }
44267
- })();
44268
- });
44269
- setImmediate(() => {
44270
- if (isCliImmediateShutdownRequested()) return;
44271
- try {
44272
- deps.sendLocalSkillsReport?.();
44273
- } catch (e) {
44274
- deps.log(
44275
- `[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
44276
- );
44277
- }
44278
- });
44279
- };
44280
-
44281
- // src/connection/heartbeat/ack.ts
44282
- var handleBridgeHeartbeatAck = (msg, deps) => {
44283
- const raw = msg.s;
44284
- if (typeof raw !== "number" || !Number.isFinite(raw)) return;
44285
- deps.onBridgeHeartbeatAck?.(Math.trunc(raw));
44286
- };
44287
-
44288
- // src/agents/acp/from-bridge/handle-bridge-agent-config.ts
44289
- function handleBridgeAgentConfig(msg, { acpManager }) {
44290
- if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
44291
- acpManager.setPreferredAgentType(msg.agents[0].type);
44135
+ // src/local-shortcut/lib/auth.ts
44136
+ function readBearerToken(req) {
44137
+ const raw = req.headers.authorization;
44138
+ if (typeof raw !== "string") return null;
44139
+ const match = /^Bearer\s+(.+)$/i.exec(raw.trim());
44140
+ return match?.[1]?.trim() ? match[1].trim() : null;
44141
+ }
44142
+ function isAuthorizedShortcutRequest(req, expectedToken) {
44143
+ const token = readBearerToken(req);
44144
+ return Boolean(token && expectedToken && token === expectedToken);
44292
44145
  }
44293
44146
 
44294
- // src/routing/handlers/agent-config.ts
44295
- var handleAgentConfigMessage = (msg, deps) => {
44296
- handleBridgeAgentConfig(msg, deps);
44297
- };
44147
+ // src/local-shortcut/lib/e2ee/file-browser-fields.ts
44148
+ var SHORTCUT_FILE_BROWSER_E2EE_FIELDS = [
44149
+ "entries",
44150
+ "content",
44151
+ "totalLines",
44152
+ "size",
44153
+ "lineOffset"
44154
+ ];
44298
44155
 
44299
- // src/prompt-turn-queue/client-report.ts
44300
- function sendPromptQueueClientReport(ws, queues) {
44301
- if (!ws) return false;
44302
- const wireQueues = {};
44303
- for (const [queueKey, rows] of Object.entries(queues)) {
44304
- wireQueues[queueKey] = rows.map((r) => ({ turnId: r.turnId, clientState: r.cliState }));
44156
+ // src/local-shortcut/lib/e2ee/encrypt-file-browser-body.ts
44157
+ function encryptShortcutFileBrowserBody(body, e2ee) {
44158
+ if (!e2ee) return body;
44159
+ const encrypted = e2ee.encryptFields(body, SHORTCUT_FILE_BROWSER_E2EE_FIELDS);
44160
+ if (encrypted.ee != null && typeof encrypted.ee === "object") {
44161
+ return { ee: encrypted.ee };
44305
44162
  }
44306
- sendWsMessage(ws, { type: "prompt_queue_client_report", queues: wireQueues });
44307
- return true;
44163
+ return body;
44308
44164
  }
44309
44165
 
44310
- // src/prompt-turn-queue/disk-store.ts
44311
- var MERGEABLE_BRIDGE_SERVER_STATES = /* @__PURE__ */ new Set([
44312
- "queued",
44313
- "requeued",
44314
- "requeued_with_revert",
44315
- "cancel_requested",
44316
- "stopping",
44317
- "discarded"
44318
- ]);
44319
- async function readPersistedQueue(queueKey) {
44320
- return withCliSqlite((db) => {
44321
- const row = db.get("SELECT queue_key, updated_at, turns_json FROM prompt_queue WHERE queue_key = ?", [
44322
- queueKey
44323
- ]);
44324
- if (!row) return null;
44325
- try {
44326
- const turns = JSON.parse(row.turns_json);
44327
- if (!Array.isArray(turns)) return null;
44328
- return { queueKey: row.queue_key, updatedAt: row.updated_at, turns };
44329
- } catch {
44330
- return null;
44331
- }
44332
- });
44333
- }
44334
- async function writePersistedQueue(file2) {
44335
- await withCliSqlite((db) => {
44336
- db.run(
44337
- `INSERT INTO prompt_queue (queue_key, updated_at, turns_json) VALUES (?, ?, ?)
44338
- ON CONFLICT(queue_key) DO UPDATE SET updated_at = excluded.updated_at, turns_json = excluded.turns_json`,
44339
- [file2.queueKey, file2.updatedAt, JSON.stringify(file2.turns)]
44340
- );
44341
- });
44166
+ // src/local-shortcut/lib/response.ts
44167
+ function sendShortcutJson(res, status, body) {
44168
+ if (res.headersSent) return;
44169
+ res.writeHead(status, { "Content-Type": "application/json", "Cache-Control": "no-store" });
44170
+ res.end(JSON.stringify(body));
44342
44171
  }
44343
- async function mergeBridgeServerQueueSnapshot(queueKey, serverTurns) {
44344
- const prev = await readPersistedQueue(queueKey);
44345
- const turns = [];
44346
- for (const raw of serverTurns) {
44347
- if (!raw || typeof raw !== "object") continue;
44348
- const o = raw;
44349
- const turnId = typeof o.turnId === "string" ? o.turnId : "";
44350
- const sessionId = typeof o.sessionId === "string" ? o.sessionId : "";
44351
- const turnOrd = typeof o.turnOrd === "number" ? o.turnOrd : Number(o.turnOrd) || 0;
44352
- const bridgeServerState = o.serverState ?? o.bridgeServerState;
44353
- const lastCliState = o.lastClientState ?? o.lastCliState ?? null;
44354
- const payload = o.payload && typeof o.payload === "object" ? o.payload : {};
44355
- if (!turnId || !sessionId) continue;
44356
- if (!MERGEABLE_BRIDGE_SERVER_STATES.has(String(bridgeServerState))) continue;
44357
- const old = prev?.turns.find((t) => t.turnId === turnId);
44358
- const mergedCli = old?.lastCliState === "running" && lastCliState == null ? "running" : lastCliState;
44359
- turns.push({
44360
- turnId,
44361
- sessionId,
44362
- turnOrd,
44363
- bridgeServerState,
44364
- lastCliState: mergedCli,
44365
- payload
44366
- });
44367
- }
44368
- turns.sort((a, b) => a.turnOrd - b.turnOrd);
44369
- return { queueKey, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), turns };
44172
+ function sendShortcutFileBrowserJson(res, status, body, e2ee) {
44173
+ sendShortcutJson(res, status, encryptShortcutFileBrowserBody(body, e2ee));
44370
44174
  }
44371
44175
 
44372
- // src/prompt-turn-queue/runner/dispatch-local-prompt.ts
44373
- function dispatchLocalPrompt(next, deps) {
44374
- const pl = next.payload;
44375
- const rawParent = pl["sessionParent"];
44376
- const sessionParent = rawParent === "bridge_root" || rawParent === "worktrees_root" ? rawParent : void 0;
44377
- const rawParentPath = pl["sessionParentPath"];
44378
- const sessionParentPath = typeof rawParentPath === "string" && rawParentPath.trim() !== "" ? rawParentPath.trim() : void 0;
44379
- const rawBaseBranches = pl["worktreeBaseBranches"];
44380
- const worktreeBaseBranches = rawBaseBranches != null && typeof rawBaseBranches === "object" && !Array.isArray(rawBaseBranches) ? rawBaseBranches : void 0;
44381
- const msg = {
44382
- type: "prompt",
44383
- sessionId: next.sessionId,
44384
- runId: next.turnId,
44385
- prompt: pl.prompt,
44386
- mode: typeof pl.mode === "string" ? pl.mode : "agent",
44387
- isNewSession: pl.isNewSession === true,
44388
- ...sessionParent ? { sessionParent } : {},
44389
- ...sessionParentPath ? { sessionParentPath } : {},
44390
- ...worktreeBaseBranches && Object.keys(worktreeBaseBranches).length > 0 ? { worktreeBaseBranches } : {},
44391
- ...typeof pl.followUpCatalogPromptId === "string" ? { followUpCatalogPromptId: pl.followUpCatalogPromptId } : {},
44392
- ...typeof pl.agentType === "string" && pl.agentType.trim() ? { agentType: pl.agentType.trim() } : {},
44393
- ...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {},
44394
- ...Array.isArray(pl.attachments) && pl.attachments.length > 0 ? { attachments: pl.attachments } : {}
44395
- };
44396
- handleBridgePrompt(msg, deps);
44176
+ // src/local-shortcut/app/api/bridges/local-shortcut/health/route.ts
44177
+ async function GET(ctx) {
44178
+ sendShortcutJson(ctx.res, 200, { ok: true });
44397
44179
  }
44398
44180
 
44399
- // src/prompt-turn-queue/runner/queue-selection.ts
44400
- function pickNextRunnableTurn(turns) {
44401
- for (const t of turns) {
44402
- if (t.bridgeServerState === "discarded" || t.bridgeServerState === "stopping") continue;
44403
- if (t.bridgeServerState === "cancel_requested") continue;
44404
- if (!isRunnableBridgePromptTurnServerState(t.bridgeServerState)) continue;
44405
- if (t.lastCliState === "running" || t.lastCliState === "stopped" || t.lastCliState === "failed" || t.lastCliState === "cancelled") {
44406
- continue;
44407
- }
44408
- return t;
44181
+ // src/local-shortcut/app/lib/assert-bridge-workspace.ts
44182
+ function assertBridgeWorkspace(ctx) {
44183
+ const reqWorkspaceId = ctx.url.searchParams.get("workspaceId")?.trim() ?? "";
44184
+ if (reqWorkspaceId !== ctx.workspaceId) {
44185
+ sendShortcutJson(ctx.res, 403, { error: "workspaceId mismatch" });
44186
+ return false;
44409
44187
  }
44410
- return null;
44411
- }
44412
- function hasRunningTurn(turns) {
44413
- return turns.some((t) => t.lastCliState === "running");
44188
+ return true;
44414
44189
  }
44415
44190
 
44416
- // src/prompt-turn-queue/runner/run-id-queue-key-map.ts
44417
- var runIdToQueueKey = /* @__PURE__ */ new Map();
44418
- function getRunIdQueueKey(runId) {
44419
- return runIdToQueueKey.get(runId);
44420
- }
44421
- function setRunIdQueueKey(runId, queueKey) {
44422
- runIdToQueueKey.set(runId, queueKey);
44423
- }
44424
- function deleteRunIdQueueKey(runId) {
44425
- const queueKey = runIdToQueueKey.get(runId);
44426
- runIdToQueueKey.delete(runId);
44427
- return queueKey;
44428
- }
44429
- function syncRunningTurnQueueKeys(turns, queueKey) {
44430
- for (const running of turns.filter((t) => t.lastCliState === "running")) {
44431
- runIdToQueueKey.set(running.turnId, queueKey);
44191
+ // src/files/browser/execute-file-browser-list.ts
44192
+ init_list_dir();
44193
+
44194
+ // src/git/tree/resolve-repo-abs-path.ts
44195
+ init_cwd();
44196
+ import * as path74 from "node:path";
44197
+ function resolveRepoAbsPathFromBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
44198
+ const bridgeResolved = path74.resolve(bridgeRoot);
44199
+ const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
44200
+ const repoPath = rel === "" ? bridgeResolved : path74.join(bridgeResolved, rel);
44201
+ const resolved = path74.resolve(repoPath);
44202
+ if (!resolved.startsWith(bridgeResolved + path74.sep) && resolved !== bridgeResolved) {
44203
+ return null;
44432
44204
  }
44205
+ return resolved;
44433
44206
  }
44434
44207
 
44435
- // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
44436
- import fs46 from "node:fs";
44437
-
44438
- // src/git/snapshot/capture.ts
44439
- import * as fs45 from "node:fs";
44440
- import * as path72 from "node:path";
44441
- async function resolveSnapshotRepoRoots(options) {
44442
- const { worktreePaths, fallbackCwd, sessionId, log: log2 } = options;
44443
- if (worktreePaths?.length) {
44444
- const uniq = [...new Set(worktreePaths.map((p) => path72.resolve(p)))];
44445
- return uniq;
44446
- }
44208
+ // src/git/tree/resolve-branch-commit.ts
44209
+ async function resolveBranchCommit(repoAbsPath, branch) {
44210
+ const branchRef = branch.trim();
44211
+ if (!branchRef) return null;
44447
44212
  try {
44448
- const repos = await discoverGitReposUnderRoot(fallbackCwd);
44449
- const mapped = repos.map((r) => r.absolutePath);
44450
- const sid = sessionId?.trim();
44451
- if (sid) {
44452
- const filtered = mapped.filter((root) => path72.basename(root) === sid);
44453
- if (filtered.length > 0) return filtered;
44454
- }
44455
- return mapped;
44456
- } catch (e) {
44457
- log2(`[snapshot] Discover repositories failed: ${e instanceof Error ? e.message : String(e)}`);
44458
- return [];
44213
+ return (await cliSimpleGit(repoAbsPath).revparse([`${branchRef}^{commit}`])).trim();
44214
+ } catch {
44215
+ return null;
44459
44216
  }
44460
44217
  }
44461
- async function capturePreTurnSnapshot(options) {
44462
- const { runId, repoRoots, agentCwd, log: log2 } = options;
44463
- if (!runId || !repoRoots.length) {
44464
- return { ok: false, error: "No git repos to snapshot" };
44465
- }
44466
- const repos = [];
44467
- await forEachWithGitYield(repoRoots, async (root) => {
44468
- const stashSha = await gitStashCreate(root, log2);
44469
- const untrackedPaths = await gitUntrackedPaths(root, log2);
44470
- repos.push({ path: root, stashSha, untrackedPaths });
44471
- });
44472
- const dir = snapshotsDirForCwd(agentCwd);
44473
- try {
44474
- fs45.mkdirSync(dir, { recursive: true });
44475
- } catch (e) {
44476
- return { ok: false, error: e instanceof Error ? e.message : String(e) };
44477
- }
44478
- const payload = {
44479
- runId,
44480
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
44481
- repos
44482
- };
44483
- const filePath = path72.join(dir, `${runId}.json`);
44484
- try {
44485
- fs45.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
44486
- } catch (e) {
44487
- return { ok: false, error: e instanceof Error ? e.message : String(e) };
44218
+
44219
+ // src/git/tree/list-git-tree-dir.ts
44220
+ init_yield_to_event_loop();
44221
+ init_types2();
44222
+ init_sort_entries();
44223
+ function parseLsTreeLine(line) {
44224
+ const tab = line.indexOf(" ");
44225
+ if (tab < 0) return null;
44226
+ const meta = line.slice(0, tab).trim().split(/\s+/);
44227
+ if (meta.length < 3) return null;
44228
+ const mode = meta[0];
44229
+ const objectType2 = meta[1];
44230
+ let name = line.slice(tab + 1);
44231
+ if (name.startsWith('"') && name.endsWith('"')) {
44232
+ name = name.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
44488
44233
  }
44489
- const repoList = repos.map((r) => r.path).join(", ");
44490
- log2(
44491
- `[snapshot] Saved pre-turn snapshot ${runId.slice(0, 8)}\u2026 (${repos.length} repo(s)): ${repoList}`
44492
- );
44493
- return { ok: true, filePath, repos };
44234
+ return { mode, objectType: objectType2, name };
44494
44235
  }
44495
- async function applyPreTurnSnapshot(filePath, log2) {
44496
- let data;
44236
+ function childPath(relativePath, name) {
44237
+ const base = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
44238
+ if (!base || base === ".") return name;
44239
+ return `${base}/${name}`.replace(/\\/g, "/");
44240
+ }
44241
+ function lsTreeArgs(commit, relativePath) {
44242
+ const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
44243
+ if (!rel || rel === ".") return ["ls-tree", commit];
44244
+ return ["ls-tree", `${commit}:${rel}`];
44245
+ }
44246
+ async function listGitTreeDirAsync(repoAbsPath, branch, relativePath) {
44247
+ await yieldToEventLoop();
44248
+ const commit = await resolveBranchCommit(repoAbsPath, branch);
44249
+ if (!commit) return { error: "Branch not found" };
44497
44250
  try {
44498
- const raw = fs45.readFileSync(filePath, "utf8");
44499
- data = JSON.parse(raw);
44500
- } catch (e) {
44501
- return { ok: false, error: e instanceof Error ? e.message : String(e) };
44502
- }
44503
- if (!Array.isArray(data.repos)) {
44504
- return { ok: false, error: "Invalid snapshot file" };
44505
- }
44506
- let applyError = null;
44507
- await forEachWithGitYield(data.repos, async (r) => {
44508
- if (applyError || !r.path) return;
44509
- const reset = await gitRun(r.path, ["reset", "--hard", "HEAD"], log2, "reset --hard");
44510
- if (!reset.ok) {
44511
- applyError = reset;
44512
- return;
44251
+ const raw = await cliSimpleGit(repoAbsPath).raw(lsTreeArgs(commit, relativePath));
44252
+ await yieldToEventLoop();
44253
+ const lines = String(raw ?? "").split(/\r?\n/).filter((line) => line.trim().length > 0);
44254
+ const entries = [];
44255
+ for (let i = 0; i < lines.length; i++) {
44256
+ if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
44257
+ await yieldToEventLoop();
44258
+ }
44259
+ const parsed = parseLsTreeLine(lines[i]);
44260
+ if (!parsed) continue;
44261
+ const { mode, objectType: objectType2, name } = parsed;
44262
+ if (!name || name === "." || name === "..") continue;
44263
+ const isSymlink = mode === "120000";
44264
+ const isDir = objectType2 === "tree";
44265
+ entries.push({
44266
+ name,
44267
+ path: childPath(relativePath, name),
44268
+ isDir,
44269
+ ...isSymlink ? { isSymlink: true } : {}
44270
+ });
44513
44271
  }
44514
- const clean = await gitRun(r.path, ["clean", "-fd"], log2, "clean -fd");
44515
- if (!clean.ok) {
44516
- applyError = clean;
44517
- return;
44272
+ if (entries.length >= LIST_DIR_YIELD_EVERY) {
44273
+ await yieldToEventLoop();
44518
44274
  }
44519
- if (r.stashSha) {
44520
- const ap = await gitRun(r.path, ["stash", "apply", r.stashSha], log2, "stash apply");
44521
- if (!ap.ok) applyError = ap;
44275
+ return { entries: sortListEntries(entries) };
44276
+ } catch (err) {
44277
+ const message = err instanceof Error ? err.message : String(err);
44278
+ if (/not a tree object|does not exist|bad revision/i.test(message)) {
44279
+ return { error: "Path not found in branch" };
44522
44280
  }
44523
- });
44524
- if (applyError) return applyError;
44525
- log2(`[snapshot] Restored pre-turn state for ${data.runId.slice(0, 8)}\u2026`);
44526
- return { ok: true };
44527
- }
44528
-
44529
- // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
44530
- init_cwd();
44531
- async function runLocalRevertBeforeQueuedPrompt(next, deps) {
44532
- if (next.bridgeServerState !== "requeued_with_revert") return true;
44533
- const sid = next.sessionId;
44534
- const pl = next.payload;
44535
- const tid = typeof pl.snapshotRevertTurnId === "string" && pl.snapshotRevertTurnId.trim() !== "" ? pl.snapshotRevertTurnId.trim() : next.turnId;
44536
- const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
44537
- const file2 = snapshotFilePath(agentBase, tid);
44538
- try {
44539
- await fs46.promises.access(file2, fs46.constants.F_OK);
44540
- } catch {
44541
- deps.log(
44542
- `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
44543
- );
44544
- return true;
44545
- }
44546
- const res = await applyPreTurnSnapshot(file2, deps.log);
44547
- if (!res.ok) {
44548
- deps.log(`[Queue] requeued_with_revert: snapshot apply failed: ${res.error ?? "unknown"}`);
44549
- }
44550
- return res.ok;
44551
- }
44552
-
44553
- // src/prompt-turn-queue/runner/finalize-prompt-turn-on-bridge.ts
44554
- async function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
44555
- if (!runId) return false;
44556
- const queueKey = deleteRunIdQueueKey(runId);
44557
- if (!queueKey) return false;
44558
- const f = await readPersistedQueue(queueKey);
44559
- if (!f) return false;
44560
- const t = f.turns.find((x) => x.turnId === runId);
44561
- if (!t) return false;
44562
- t.lastCliState = opts?.terminalCliState ?? (success2 ? "stopped" : "failed");
44563
- await writePersistedQueue(f);
44564
- sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: runId, cliState: t.lastCliState }] });
44565
- return true;
44566
- }
44567
-
44568
- // src/prompt-turn-queue/runner/apply-prompt-queue-state-from-server.ts
44569
- async function applyPromptQueueStateFromServer(msg, deps) {
44570
- const raw = msg.queues;
44571
- if (!raw || typeof raw !== "object") return;
44572
- const getWs = deps.getWs;
44573
- for (const [queueKey, serverTurns] of Object.entries(raw)) {
44574
- if (!Array.isArray(serverTurns)) continue;
44575
- const file2 = await mergeBridgeServerQueueSnapshot(queueKey, serverTurns);
44576
- await writePersistedQueue(file2);
44577
- }
44578
- for (const [queueKey, serverTurns] of Object.entries(raw)) {
44579
- if (!Array.isArray(serverTurns)) continue;
44580
- const file2 = await readPersistedQueue(queueKey);
44581
- if (!file2) continue;
44582
- syncRunningTurnQueueKeys(file2.turns, queueKey);
44583
- }
44584
- for (const [queueKey, serverTurns] of Object.entries(raw)) {
44585
- if (!Array.isArray(serverTurns)) continue;
44586
- const file2 = await readPersistedQueue(queueKey);
44587
- if (!file2) continue;
44588
- const cancelRow = file2.turns.find((t) => t.bridgeServerState === "cancel_requested" && t.lastCliState === "running");
44589
- if (cancelRow) {
44590
- const localCancelHandled = await deps.acpManager.cancelRun(cancelRow.turnId);
44591
- if (!localCancelHandled) {
44592
- deps.log(
44593
- `[Queue] bridge server cancel_requested for ${cancelRow.turnId.slice(0, 8)}\u2026 but no local agent run is active (e.g. after CLI restart); marking cancelled and notifying bridge.`
44594
- );
44595
- await finalizePromptTurnOnBridge(deps.getWs, cancelRow.turnId, false, { terminalCliState: "cancelled" });
44596
- const ws = deps.getWs();
44597
- if (ws && cancelRow.sessionId) {
44598
- sendWsMessage(ws, {
44599
- type: "prompt_result",
44600
- sessionId: cancelRow.sessionId,
44601
- runId: cancelRow.turnId,
44602
- success: false,
44603
- error: "Stopped by user",
44604
- stopReason: "cancelled"
44605
- });
44606
- }
44607
- }
44608
- }
44609
- }
44610
- const report = {};
44611
- const startedThisTick = /* @__PURE__ */ new Set();
44612
- for (const [queueKey, serverTurns] of Object.entries(raw)) {
44613
- if (!Array.isArray(serverTurns)) continue;
44614
- const file2 = await readPersistedQueue(queueKey);
44615
- if (!file2) continue;
44616
- if (hasRunningTurn(file2.turns)) continue;
44617
- const next = pickNextRunnableTurn(file2.turns);
44618
- if (!next) continue;
44619
- if (!await runLocalRevertBeforeQueuedPrompt(next, deps)) {
44620
- next.lastCliState = "failed";
44621
- await writePersistedQueue(file2);
44622
- sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: next.turnId, cliState: "failed" }] });
44623
- continue;
44624
- }
44625
- next.lastCliState = "running";
44626
- await writePersistedQueue(file2);
44627
- setRunIdQueueKey(next.turnId, queueKey);
44628
- startedThisTick.add(next.turnId);
44629
- report[queueKey] = [{ turnId: next.turnId, cliState: "running" }];
44630
- }
44631
- if (Object.keys(report).length > 0) {
44632
- sendPromptQueueClientReport(getWs(), report);
44633
- }
44634
- for (const [queueKey, serverTurns] of Object.entries(raw)) {
44635
- if (!Array.isArray(serverTurns)) continue;
44636
- const file2 = await readPersistedQueue(queueKey);
44637
- if (!file2) continue;
44638
- const running = file2.turns.find((t) => t.lastCliState === "running");
44639
- if (!running || !startedThisTick.has(running.turnId)) continue;
44640
- if (getRunIdQueueKey(running.turnId) !== queueKey) continue;
44641
- dispatchLocalPrompt(running, deps);
44642
- }
44643
- }
44644
-
44645
- // src/agents/acp/from-bridge/bridge-prompt-wiring.ts
44646
- function createBridgePromptSenders(deps, getWs) {
44647
- const sendBridgeMessage = (message, encryptedFields = []) => {
44648
- const s = getWs();
44649
- if (!s) return false;
44650
- const wire = deps.e2ee && encryptedFields.length > 0 ? deps.e2ee.encryptFields(message, encryptedFields) : message;
44651
- sendWsMessage(s, wire);
44652
- return true;
44653
- };
44654
- const sendResult = (result) => {
44655
- const encryptedFields = result.type === "prompt_result" ? ["output", "error"] : [];
44656
- sendBridgeMessage(result, encryptedFields);
44657
- if (result.type === "prompt_result") {
44658
- const pr = result;
44659
- const cancelled = pr.stopReason === "cancelled";
44660
- void finalizePromptTurnOnBridge(
44661
- getWs,
44662
- typeof pr.runId === "string" ? pr.runId : void 0,
44663
- pr.success === true,
44664
- cancelled ? { terminalCliState: "cancelled" } : void 0
44665
- ).catch(() => {
44666
- });
44667
- }
44668
- };
44669
- const sendSessionUpdate = (payload) => {
44670
- const s = getWs();
44671
- if (!s) {
44672
- deps.log("[Bridge service] Session update not sent: not connected to the bridge.");
44673
- return;
44674
- }
44675
- const p = payload;
44676
- const wire = p.type === "session_update" && deps.e2ee ? deps.e2ee.encryptFields(payload, ["payload"]) : p.type === "session_file_change" && deps.e2ee ? deps.e2ee.encryptFields(payload, [
44677
- "path",
44678
- "oldText",
44679
- "newText",
44680
- "patchContent",
44681
- "isDirectory",
44682
- "directoryRemoved"
44683
- ]) : payload;
44684
- sendWsMessage(s, wire);
44685
- };
44686
- return { sendBridgeMessage, sendResult, sendSessionUpdate };
44687
- }
44688
-
44689
- // src/agents/acp/from-bridge/handle-bridge-prompt/parse-bridge-attachments.ts
44690
- function parseBridgeAttachments(msg) {
44691
- const raw = msg.attachments;
44692
- if (!Array.isArray(raw)) return [];
44693
- const out = [];
44694
- for (const x of raw) {
44695
- if (x === null || typeof x !== "object" || Array.isArray(x)) continue;
44696
- const o = x;
44697
- const id = typeof o.attachmentId === "string" ? o.attachmentId.trim() : "";
44698
- if (!id) continue;
44699
- const mt = typeof o.mimeType === "string" && o.mimeType.trim() ? o.mimeType.trim() : "application/octet-stream";
44700
- out.push({ attachmentId: id, mimeType: mt });
44701
- }
44702
- return out;
44703
- }
44704
-
44705
- // src/agents/acp/from-bridge/handle-bridge-prompt/parse-worktree-base-branches.ts
44706
- function parseWorktreeBaseBranches(msg) {
44707
- const raw = msg.worktreeBaseBranches;
44708
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
44709
- const out = {};
44710
- for (const [k, v] of Object.entries(raw)) {
44711
- if (typeof k !== "string" || typeof v !== "string") continue;
44712
- const rel = k.trim();
44713
- const branch = v.trim();
44714
- if (rel && branch) out[rel] = branch;
44715
- }
44716
- return Object.keys(out).length > 0 ? out : void 0;
44717
- }
44718
-
44719
- // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
44720
- init_cwd();
44721
- import { execFile as execFile8 } from "node:child_process";
44722
- import { promisify as promisify9 } from "node:util";
44723
- var execFileAsync7 = promisify9(execFile8);
44724
- async function readGitBranch(cwd) {
44725
- try {
44726
- const { stdout } = await execFileAsync7("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
44727
- const b = stdout.trim();
44728
- return b || null;
44729
- } catch {
44730
- return null;
44731
- }
44732
- }
44733
- async function runBridgePromptPreamble(params) {
44734
- const { getWs, log: log2, sessionWorktreeManager, sessionId, runId, effectiveCwd } = params;
44735
- const s = getWs();
44736
- const repoCheckoutPaths = await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sessionId);
44737
- const repoRoots = await resolveSnapshotRepoRoots({
44738
- worktreePaths: repoCheckoutPaths,
44739
- fallbackCwd: effectiveCwd,
44740
- sessionId: sessionId?.trim() || void 0,
44741
- log: log2
44742
- });
44743
- if (s && sessionId) {
44744
- const cliGitBranch = await readGitBranch(effectiveCwd);
44745
- const usesWt = sessionWorktreeManager.usesWorktreeSession(sessionId);
44746
- const isolatedSessionParentPath = sessionWorktreeManager.getIsolatedSessionParentPathForSession(sessionId);
44747
- sendWsMessage(s, {
44748
- type: "session_git_context_report",
44749
- sessionId,
44750
- cliGitBranch,
44751
- agentUsesWorktree: usesWt,
44752
- sessionParent: usesWt ? "worktrees_root" : "bridge_root",
44753
- sessionParentPath: usesWt ? isolatedSessionParentPath ?? effectiveCwd : getBridgeRoot()
44754
- });
44755
- }
44756
- if (s && sessionId && runId) {
44757
- const cap = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false, error: "No git repos" };
44758
- sendWsMessage(s, {
44759
- type: "pre_turn_snapshot_report",
44760
- sessionId,
44761
- turnId: runId,
44762
- captured: cap.ok
44763
- });
44764
- }
44765
- }
44766
- function parseFollowUpFieldsFromPromptMessage(msg) {
44767
- const followUpCatalogPromptId = typeof msg.followUpCatalogPromptId === "string" && msg.followUpCatalogPromptId.trim() !== "" ? msg.followUpCatalogPromptId.trim() : null;
44768
- return { followUpCatalogPromptId };
44769
- }
44770
-
44771
- // src/agents/acp/from-bridge/handle-bridge-prompt/run-preamble-and-prompt.ts
44772
- async function runPreambleAndPrompt(params) {
44773
- const {
44774
- deps,
44775
- msg,
44776
- getWs,
44777
- log: log2,
44778
- sessionWorktreeManager,
44779
- sessionId,
44780
- runId,
44781
- promptText,
44782
- attachments,
44783
- mode,
44784
- agentType,
44785
- agentId,
44786
- agentConfig,
44787
- resolvedCwd,
44788
- senders: { sendResult, sendSessionUpdate }
44789
- } = params;
44790
- const effectiveCwd = resolveSessionParentPathForAgentProcess(resolvedCwd);
44791
- await runBridgePromptPreamble({
44792
- getWs,
44793
- log: log2,
44794
- sessionWorktreeManager,
44795
- sessionId,
44796
- runId,
44797
- effectiveCwd
44798
- });
44799
- const { followUpCatalogPromptId } = parseFollowUpFieldsFromPromptMessage(msg);
44800
- deps.acpManager.handlePrompt({
44801
- promptText,
44802
- promptId: msg.id,
44803
- sessionId,
44804
- runId,
44805
- mode,
44806
- agentType,
44807
- agentId,
44808
- agentConfig,
44809
- sessionParentPath: effectiveCwd,
44810
- sendResult,
44811
- sendSessionUpdate,
44812
- followUpCatalogPromptId,
44813
- cloudApiBaseUrl: deps.cloudApiBaseUrl,
44814
- getCloudAccessToken: deps.getCloudAccessToken,
44815
- e2ee: deps.e2ee,
44816
- ...attachments.length > 0 ? { attachments } : {},
44817
- isNewSession: msg.isNewSession === true
44818
- });
44819
- }
44820
-
44821
- // src/agents/acp/from-bridge/handle-bridge-prompt/handle-bridge-prompt.ts
44822
- function handleBridgePrompt(msg, deps) {
44823
- const { getWs, log: log2, acpManager, sessionWorktreeManager } = deps;
44824
- const rawPrompt = msg.prompt;
44825
- const promptText = typeof rawPrompt === "string" ? rawPrompt : rawPrompt != null ? String(rawPrompt) : "";
44826
- const attachments = parseBridgeAttachments(msg);
44827
- const sessionId = msg.sessionId;
44828
- const runId = typeof msg.runId === "string" ? msg.runId : void 0;
44829
- const promptId = typeof msg.id === "string" ? msg.id : void 0;
44830
- const senders = createBridgePromptSenders(deps, getWs);
44831
- if (!promptText.trim() && attachments.length === 0) {
44832
- log2(
44833
- `[Bridge service] Prompt ignored: empty or missing prompt text (session ${typeof msg.sessionId === "string" ? msg.sessionId.slice(0, 8) : "\u2014"}\u2026, run ${typeof msg.runId === "string" ? msg.runId.slice(0, 8) : "\u2014"}\u2026).`
44834
- );
44835
- senders.sendBridgeMessage(
44836
- {
44837
- type: "prompt_result",
44838
- ...promptId ? { id: promptId } : {},
44839
- ...sessionId ? { sessionId } : {},
44840
- ...runId ? { runId } : {},
44841
- success: false,
44842
- error: "Empty or missing prompt text from the bridge; this turn was not sent to the agent."
44843
- },
44844
- ["error"]
44845
- );
44846
- return;
44847
- }
44848
- const isNewSession = msg.isNewSession === true;
44849
- const rawParent = msg.sessionParent;
44850
- const sessionParent = rawParent === "bridge_root" || rawParent === "worktrees_root" ? rawParent : rawParent === "session_worktrees_root" ? "worktrees_root" : null;
44851
- const sessionParentPath = typeof msg.sessionParentPath === "string" && msg.sessionParentPath.trim() ? msg.sessionParentPath.trim() : null;
44852
- const agentType = typeof msg.agentType === "string" && msg.agentType.trim() ? msg.agentType.trim() : void 0;
44853
- const rawAgentId = msg.agentId;
44854
- const agentId = typeof rawAgentId === "string" && rawAgentId.trim() !== "" ? rawAgentId.trim() : null;
44855
- const mode = typeof msg.mode === "string" && msg.mode.trim() ? msg.mode.trim() : void 0;
44856
- const agentConfig = msg.agentConfig != null && typeof msg.agentConfig === "object" && !Array.isArray(msg.agentConfig) ? msg.agentConfig : void 0;
44857
- const worktreeBaseBranches = parseWorktreeBaseBranches(msg);
44858
- acpManager.logPromptReceivedFromBridge({ agentType, mode });
44859
- void sessionWorktreeManager.resolveSessionParentPathForPrompt(sessionId, {
44860
- isNewSession,
44861
- sessionParent,
44862
- sessionParentPath,
44863
- ...worktreeBaseBranches ? { worktreeBaseBranches } : {}
44864
- }).then(
44865
- (cwd) => runPreambleAndPrompt({
44866
- deps,
44867
- msg,
44868
- getWs,
44869
- log: log2,
44870
- sessionWorktreeManager,
44871
- sessionId,
44872
- runId,
44873
- promptText,
44874
- attachments,
44875
- mode,
44876
- agentType,
44877
- agentId,
44878
- agentConfig,
44879
- resolvedCwd: cwd,
44880
- senders
44881
- })
44882
- ).catch((err) => {
44883
- log2(`[Agent] Session parent path resolve failed: ${err instanceof Error ? err.message : String(err)}`);
44884
- void runPreambleAndPrompt({
44885
- deps,
44886
- msg,
44887
- getWs,
44888
- log: log2,
44889
- sessionWorktreeManager,
44890
- sessionId,
44891
- runId,
44892
- promptText,
44893
- attachments,
44894
- mode,
44895
- agentType,
44896
- agentId,
44897
- agentConfig,
44898
- resolvedCwd: void 0,
44899
- senders
44900
- });
44901
- });
44902
- }
44903
-
44904
- // src/routing/handlers/prompt.ts
44905
- var handlePromptMessage = (msg, deps) => {
44906
- handleBridgePrompt(msg, deps);
44907
- };
44908
-
44909
- // src/routing/handlers/prompt-queue-state.ts
44910
- var handlePromptQueueStateMessage = (msg, deps) => {
44911
- void applyPromptQueueStateFromServer(msg, deps).catch((err) => {
44912
- deps.log(`[Queue] applyPromptQueueStateFromServer failed: ${err instanceof Error ? err.message : String(err)}`);
44913
- });
44914
- };
44915
-
44916
- // src/agents/acp/from-bridge/handle-bridge-session-request-response.ts
44917
- function handleBridgeSessionRequestResponse(msg, { acpManager }) {
44918
- if (typeof msg.requestId !== "string") return;
44919
- acpManager.resolveRequest(msg.requestId, msg.result ?? {});
44920
- }
44921
-
44922
- // src/routing/handlers/session-request-response.ts
44923
- var handleSessionRequestResponseMessage = (msg, deps) => {
44924
- handleBridgeSessionRequestResponse(msg, deps);
44925
- };
44926
-
44927
- // src/skills/handle-skill-call.ts
44928
- function handleSkillCall(msg, socket, log2) {
44929
- callSkill(msg.skillId, msg.operationId, msg.params ?? {}).then((result) => {
44930
- sendWsMessage(socket, { type: "skill_result", id: msg.id, result });
44931
- }).catch((err) => {
44932
- sendWsMessage(socket, { type: "skill_result", id: msg.id, error: String(err) });
44933
- log2(`[Bridge service] Skill invocation failed (${msg.skillId}/${msg.operationId}): ${err}`);
44934
- });
44935
- }
44936
-
44937
- // src/routing/handlers/skill-call.ts
44938
- var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
44939
- const skillId = typeof msg.skillId === "string" ? msg.skillId : "";
44940
- const operationId = typeof msg.operationId === "string" ? msg.operationId : "";
44941
- if (!skillId || !operationId) return;
44942
- const socket = getWs();
44943
- if (!socket) return;
44944
- handleSkillCall(
44945
- { id: msg.id, skillId, operationId, params: msg.params },
44946
- socket,
44947
- log2
44948
- );
44949
- };
44950
-
44951
- // src/files/resolve-file-browser-session-parent.ts
44952
- init_normalize_resolved_path();
44953
- init_cwd();
44954
- async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
44955
- const sid = sessionId?.trim();
44956
- if (sid) {
44957
- await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sid);
44958
- const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
44959
- if (worktreeRoot) return normalizeResolvedPath(worktreeRoot);
44960
- }
44961
- return getBridgeRoot();
44962
- }
44963
-
44964
- // src/files/browser/handle-file-browser-list.ts
44965
- init_list_dir();
44966
-
44967
- // src/git/tree/resolve-repo-abs-path.ts
44968
- init_cwd();
44969
- import * as path75 from "node:path";
44970
- function resolveRepoAbsPathFromBridge(repoRelPath, bridgeRoot = getBridgeRoot()) {
44971
- const bridgeResolved = path75.resolve(bridgeRoot);
44972
- const rel = repoRelPath.trim() === "." ? "" : repoRelPath.trim().replace(/\\/g, "/");
44973
- const repoPath = rel === "" ? bridgeResolved : path75.join(bridgeResolved, rel);
44974
- const resolved = path75.resolve(repoPath);
44975
- if (!resolved.startsWith(bridgeResolved + path75.sep) && resolved !== bridgeResolved) {
44976
- return null;
44977
- }
44978
- return resolved;
44979
- }
44980
-
44981
- // src/git/tree/resolve-branch-commit.ts
44982
- async function resolveBranchCommit(repoAbsPath, branch) {
44983
- const branchRef = branch.trim();
44984
- if (!branchRef) return null;
44985
- try {
44986
- return (await cliSimpleGit(repoAbsPath).revparse([`${branchRef}^{commit}`])).trim();
44987
- } catch {
44988
- return null;
44989
- }
44990
- }
44991
-
44992
- // src/git/tree/list-git-tree-dir.ts
44993
- init_yield_to_event_loop();
44994
- init_types2();
44995
- init_sort_entries();
44996
- function parseLsTreeLine(line) {
44997
- const tab = line.indexOf(" ");
44998
- if (tab < 0) return null;
44999
- const meta = line.slice(0, tab).trim().split(/\s+/);
45000
- if (meta.length < 3) return null;
45001
- const mode = meta[0];
45002
- const objectType2 = meta[1];
45003
- let name = line.slice(tab + 1);
45004
- if (name.startsWith('"') && name.endsWith('"')) {
45005
- name = name.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
45006
- }
45007
- return { mode, objectType: objectType2, name };
45008
- }
45009
- function childPath(relativePath, name) {
45010
- const base = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
45011
- if (!base || base === ".") return name;
45012
- return `${base}/${name}`.replace(/\\/g, "/");
45013
- }
45014
- function lsTreeArgs(commit, relativePath) {
45015
- const rel = relativePath.replace(/^\/+/, "").replace(/\\/g, "/");
45016
- if (!rel || rel === ".") return ["ls-tree", commit];
45017
- return ["ls-tree", `${commit}:${rel}`];
45018
- }
45019
- async function listGitTreeDirAsync(repoAbsPath, branch, relativePath) {
45020
- await yieldToEventLoop();
45021
- const commit = await resolveBranchCommit(repoAbsPath, branch);
45022
- if (!commit) return { error: "Branch not found" };
45023
- try {
45024
- const raw = await cliSimpleGit(repoAbsPath).raw(lsTreeArgs(commit, relativePath));
45025
- await yieldToEventLoop();
45026
- const lines = String(raw ?? "").split(/\r?\n/).filter((line) => line.trim().length > 0);
45027
- const entries = [];
45028
- for (let i = 0; i < lines.length; i++) {
45029
- if (i > 0 && i % LIST_DIR_YIELD_EVERY === 0) {
45030
- await yieldToEventLoop();
45031
- }
45032
- const parsed = parseLsTreeLine(lines[i]);
45033
- if (!parsed) continue;
45034
- const { mode, objectType: objectType2, name } = parsed;
45035
- if (!name || name === "." || name === "..") continue;
45036
- const isSymlink = mode === "120000";
45037
- const isDir = objectType2 === "tree";
45038
- entries.push({
45039
- name,
45040
- path: childPath(relativePath, name),
45041
- isDir,
45042
- ...isSymlink ? { isSymlink: true } : {}
45043
- });
45044
- }
45045
- if (entries.length >= LIST_DIR_YIELD_EVERY) {
45046
- await yieldToEventLoop();
45047
- }
45048
- return { entries: sortListEntries(entries) };
45049
- } catch (err) {
45050
- const message = err instanceof Error ? err.message : String(err);
45051
- if (/not a tree object|does not exist|bad revision/i.test(message)) {
45052
- return { error: "Path not found in branch" };
45053
- }
45054
- return { error: message };
44281
+ return { error: message };
45055
44282
  }
45056
44283
  }
45057
44284
 
@@ -45358,36 +44585,142 @@ async function readGitTreeFileAsync(repoAbsPath, branch, relativePath, startLine
45358
44585
  }
45359
44586
  }
45360
44587
 
45361
- // src/files/browser/send-file-browser-message.ts
45362
- function sendFileBrowserMessage(socket, e2ee, payload) {
45363
- sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
44588
+ // src/files/browser/execute-file-browser-list.ts
44589
+ async function executeFileBrowserList(params) {
44590
+ const { reqPath, sessionParentPath, gitScope } = params;
44591
+ return gitScope ? await listGitTreeDirAsync(gitScope.repoAbsPath, gitScope.branch, reqPath) : await listDirAsync(reqPath, sessionParentPath);
45364
44592
  }
45365
44593
 
45366
- // src/files/read-file/resolve-file-cache-warmup.ts
45367
- init_yield_to_event_loop();
45368
- import path77 from "node:path";
45369
-
45370
- // src/files/read-file/resolve-file-cache.ts
45371
- import fs49 from "node:fs";
45372
- import path76 from "node:path";
45373
- var RESOLVE_CACHE_MAX = 4096;
45374
- var resolveCache = /* @__PURE__ */ new Map();
45375
- var warmedDirectories = /* @__PURE__ */ new Set();
45376
- function resolveCacheKey(sessionParentPath, relativePath) {
45377
- return `${sessionParentPath}\0${relativePath}`;
44594
+ // src/files/browser/resolve-git-branch-scope.ts
44595
+ function resolveGitBranchScope(msg) {
44596
+ const gitBranchScope = msg.source === "git_branch" && typeof msg.repoRelPath === "string" && typeof msg.branch === "string" && msg.branch.trim().length > 0;
44597
+ if (!gitBranchScope) return null;
44598
+ const repoAbsPath = resolveRepoAbsPathFromBridge(msg.repoRelPath);
44599
+ if (!repoAbsPath) return null;
44600
+ return { repoAbsPath, branch: msg.branch.trim() };
45378
44601
  }
45379
- function warmedDirectoryKey(sessionParentPath, directoryRelativePath) {
45380
- const dir = directoryRelativePath.replace(/^\/+/, "") || ".";
45381
- return `${path76.resolve(sessionParentPath)}\0${dir}`;
44602
+
44603
+ // src/local-shortcut/app/lib/file-browser/parse-query.ts
44604
+ function parseOptionalInt(raw) {
44605
+ if (raw == null) return void 0;
44606
+ const parsed = parseInt(raw, 10);
44607
+ return Number.isInteger(parsed) ? parsed : void 0;
45382
44608
  }
45383
- function pathStaysUnderParent(filePath, sessionParentPath) {
45384
- const parent = path76.resolve(sessionParentPath);
45385
- const resolved = path76.resolve(filePath);
45386
- return resolved === parent || resolved.startsWith(`${parent}${path76.sep}`);
44609
+ function parseGitScopeFromUrl(url2) {
44610
+ const rawSource = url2.searchParams.get("source")?.trim();
44611
+ if (rawSource === "git_branch") {
44612
+ return {
44613
+ source: "git_branch",
44614
+ repoRelPath: url2.searchParams.get("repoRelPath")?.trim() ?? ".",
44615
+ branch: url2.searchParams.get("branch")?.trim() ?? ""
44616
+ };
44617
+ }
44618
+ return { source: "filesystem" };
44619
+ }
44620
+ function parseReadParams(url2) {
44621
+ const rawEncoding = url2.searchParams.get("encoding");
44622
+ return {
44623
+ startLine: parseOptionalInt(url2.searchParams.get("startLine")),
44624
+ endLine: parseOptionalInt(url2.searchParams.get("endLine")),
44625
+ lineOffset: parseOptionalInt(url2.searchParams.get("lineOffset")),
44626
+ lineChunkSize: parseOptionalInt(url2.searchParams.get("lineChunkSize")),
44627
+ encoding: rawEncoding === "base64" ? "base64" : "utf8"
44628
+ };
44629
+ }
44630
+ function parseListPath(url2) {
44631
+ return (url2.searchParams.get("path") ?? ".").replace(/^\/+/, "") || ".";
44632
+ }
44633
+ function parseReadPath(url2) {
44634
+ const raw = url2.searchParams.get("path")?.trim();
44635
+ if (!raw) return null;
44636
+ return raw.replace(/^\/+/, "") || ".";
44637
+ }
44638
+
44639
+ // src/files/resolve-file-browser-session-parent.ts
44640
+ init_normalize_resolved_path();
44641
+ init_cwd();
44642
+ async function resolveFileBrowserSessionParent(sessionWorktreeManager, sessionId) {
44643
+ const sid = sessionId?.trim();
44644
+ if (sid) {
44645
+ await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sid);
44646
+ const worktreeRoot = sessionWorktreeManager.getSessionWorktreeRootForSession(sid);
44647
+ if (worktreeRoot) return normalizeResolvedPath(worktreeRoot);
44648
+ }
44649
+ return getBridgeRoot();
44650
+ }
44651
+
44652
+ // src/local-shortcut/app/lib/file-browser/resolve-session-parent.ts
44653
+ async function resolveShortcutSessionParent(ctx, sessionId) {
44654
+ if (!ctx.sessionWorktreeManager || !sessionId?.trim()) return void 0;
44655
+ return resolveFileBrowserSessionParent(ctx.sessionWorktreeManager, sessionId.trim());
44656
+ }
44657
+
44658
+ // src/local-shortcut/app/lib/file-browser/handle-list.ts
44659
+ async function handleShortcutFileBrowserList(ctx, sessionId) {
44660
+ const reqPath = parseListPath(ctx.url);
44661
+ const gitMsg = parseGitScopeFromUrl(ctx.url);
44662
+ const gitScope = resolveGitBranchScope({ id: "shortcut", path: reqPath, ...gitMsg });
44663
+ if (gitMsg.source === "git_branch" && typeof gitMsg.repoRelPath === "string" && typeof gitMsg.branch === "string" && gitMsg.branch.trim().length > 0 && !gitScope) {
44664
+ sendShortcutJson(ctx.res, 400, { error: "Invalid repository path" });
44665
+ return;
44666
+ }
44667
+ const sessionParentPath = await resolveShortcutSessionParent(ctx, sessionId);
44668
+ const result = await executeFileBrowserList({ reqPath, sessionParentPath, gitScope });
44669
+ if ("error" in result) {
44670
+ sendShortcutJson(ctx.res, 400, { error: result.error });
44671
+ return;
44672
+ }
44673
+ sendShortcutFileBrowserJson(ctx.res, 200, { entries: result.entries }, ctx.e2ee);
44674
+ }
44675
+
44676
+ // src/local-shortcut/app/api/bridges/files/list/route.ts
44677
+ async function GET2(ctx) {
44678
+ if (!assertBridgeWorkspace(ctx)) return;
44679
+ await handleShortcutFileBrowserList(ctx);
44680
+ }
44681
+
44682
+ // src/files/browser/file-browser-read-queue.ts
44683
+ var readChain = Promise.resolve();
44684
+ function withFileBrowserReadQueue(fn) {
44685
+ const next = readChain.then(() => fn());
44686
+ readChain = next.then(
44687
+ () => void 0,
44688
+ () => void 0
44689
+ );
44690
+ return next;
44691
+ }
44692
+
44693
+ // src/files/read-file/index.ts
44694
+ init_cwd();
44695
+ init_yield_to_event_loop();
44696
+ import fs52 from "node:fs";
44697
+
44698
+ // src/files/read-file/resolve-file-path.ts
44699
+ init_ensure_under_cwd();
44700
+ init_cwd();
44701
+ import fs48 from "node:fs";
44702
+
44703
+ // src/files/read-file/resolve-file-cache.ts
44704
+ import fs47 from "node:fs";
44705
+ import path75 from "node:path";
44706
+ var RESOLVE_CACHE_MAX = 4096;
44707
+ var resolveCache = /* @__PURE__ */ new Map();
44708
+ var warmedDirectories = /* @__PURE__ */ new Set();
44709
+ function resolveCacheKey(sessionParentPath, relativePath) {
44710
+ return `${sessionParentPath}\0${relativePath}`;
44711
+ }
44712
+ function warmedDirectoryKey(sessionParentPath, directoryRelativePath) {
44713
+ const dir = directoryRelativePath.replace(/^\/+/, "") || ".";
44714
+ return `${path75.resolve(sessionParentPath)}\0${dir}`;
44715
+ }
44716
+ function pathStaysUnderParent(filePath, sessionParentPath) {
44717
+ const parent = path75.resolve(sessionParentPath);
44718
+ const resolved = path75.resolve(filePath);
44719
+ return resolved === parent || resolved.startsWith(`${parent}${path75.sep}`);
45387
44720
  }
45388
44721
  async function statMatchesCache(entry) {
45389
44722
  try {
45390
- const stat2 = await fs49.promises.stat(entry.path);
44723
+ const stat2 = await fs47.promises.stat(entry.path);
45391
44724
  return stat2.isFile() && stat2.mtimeMs === entry.mtimeMs && stat2.size === entry.size;
45392
44725
  } catch {
45393
44726
  return false;
@@ -45417,9 +44750,6 @@ function unmarkDirectoryWarmed(dirKey) {
45417
44750
  }
45418
44751
 
45419
44752
  // src/files/read-file/resolve-file-path.ts
45420
- init_ensure_under_cwd();
45421
- init_cwd();
45422
- import fs50 from "node:fs";
45423
44753
  async function resolveFileForReadAsync(relativePath, sessionParentPath = getBridgeRoot()) {
45424
44754
  const cacheKey = resolveCacheKey(sessionParentPath, relativePath);
45425
44755
  const cached2 = getResolveCacheEntry(cacheKey);
@@ -45433,13 +44763,13 @@ async function resolveFileForReadAsync(relativePath, sessionParentPath = getBrid
45433
44763
  let size;
45434
44764
  let mtimeMs;
45435
44765
  try {
45436
- const lstat = await fs50.promises.lstat(resolved);
44766
+ const lstat = await fs48.promises.lstat(resolved);
45437
44767
  if (lstat.isSymbolicLink()) {
45438
- real = await fs50.promises.realpath(resolved);
44768
+ real = await fs48.promises.realpath(resolved);
45439
44769
  if (!pathStaysUnderParent(real, sessionParentPath)) {
45440
44770
  return { error: "Path is outside working directory" };
45441
44771
  }
45442
- const stat2 = await fs50.promises.stat(real);
44772
+ const stat2 = await fs48.promises.stat(real);
45443
44773
  if (!stat2.isFile()) return { error: "Not a file" };
45444
44774
  size = stat2.size;
45445
44775
  mtimeMs = stat2.mtimeMs;
@@ -45462,71 +44792,9 @@ async function resolveFilePathAsync(relativePath, sessionParentPath = getBridgeR
45462
44792
  return result.path;
45463
44793
  }
45464
44794
 
45465
- // src/files/read-file/resolve-file-cache-warmup.ts
45466
- var WARMED_DIRECTORY_TTL_MS = 6e4;
45467
- async function warmDirectoryResolveCache(sessionParentPath, directoryRelativePath) {
45468
- const { listDirAsync: listDirAsync2 } = await Promise.resolve().then(() => (init_list_dir(), list_dir_exports));
45469
- const result = await listDirAsync2(directoryRelativePath, sessionParentPath);
45470
- if ("error" in result) return;
45471
- for (const entry of result.entries) {
45472
- if (entry.isDir) continue;
45473
- if (fileBrowserRequestInFlight()) return;
45474
- const cacheKey = resolveCacheKey(sessionParentPath, entry.path);
45475
- if (hasResolveCacheEntry(cacheKey)) continue;
45476
- await resolveFileForReadAsync(entry.path, sessionParentPath).catch(() => void 0);
45477
- await yieldToEventLoop();
45478
- }
45479
- }
45480
- function scheduleDirectoryResolveCacheWarmup(sessionParentPath, directoryRelativePath) {
45481
- const parent = path77.resolve(sessionParentPath);
45482
- const dirKey = warmedDirectoryKey(parent, directoryRelativePath);
45483
- if (!markDirectoryWarmed(dirKey)) return;
45484
- setTimeout(() => unmarkDirectoryWarmed(dirKey), WARMED_DIRECTORY_TTL_MS);
45485
- setImmediate(() => {
45486
- void warmDirectoryResolveCache(parent, directoryRelativePath.replace(/^\/+/, "") || ".");
45487
- });
45488
- }
45489
- function scheduleResolveCacheWarmupForListedEntries(sessionParentPath, listRelativePath, entries) {
45490
- scheduleDirectoryResolveCacheWarmup(sessionParentPath, listRelativePath);
45491
- setImmediate(() => {
45492
- void (async () => {
45493
- for (const entry of entries) {
45494
- if (entry.isDir) continue;
45495
- if (fileBrowserRequestInFlight()) return;
45496
- const cacheKey = resolveCacheKey(sessionParentPath, entry.path);
45497
- if (hasResolveCacheEntry(cacheKey)) continue;
45498
- await resolveFileForReadAsync(entry.path, sessionParentPath).catch(() => void 0);
45499
- await yieldToEventLoop();
45500
- }
45501
- })();
45502
- });
45503
- }
45504
-
45505
- // src/files/browser/handle-file-browser-list.ts
45506
- async function handleFileBrowserList(socket, e2ee, id, reqPath, sessionParentPath, gitScope) {
45507
- const result = gitScope ? await listGitTreeDirAsync(gitScope.repoAbsPath, gitScope.branch, reqPath) : await listDirAsync(reqPath, sessionParentPath);
45508
- if ("error" in result) {
45509
- sendWsMessage(socket, { type: "file_browser_response", id, error: result.error });
45510
- return;
45511
- }
45512
- sendFileBrowserMessage(socket, e2ee, { type: "file_browser_response", id, entries: result.entries });
45513
- if (!gitScope && sessionParentPath) {
45514
- scheduleResolveCacheWarmupForListedEntries(sessionParentPath, reqPath, result.entries);
45515
- }
45516
- }
45517
-
45518
- // src/files/browser/handle-file-browser-read.ts
45519
- import path78 from "node:path";
45520
- init_cwd();
45521
-
45522
- // src/files/read-file/index.ts
45523
- init_cwd();
45524
- init_yield_to_event_loop();
45525
- import fs54 from "node:fs";
45526
-
45527
44795
  // src/files/read-file/read-file-range-async.ts
45528
44796
  init_yield_to_event_loop();
45529
- import fs51 from "node:fs";
44797
+ import fs49 from "node:fs";
45530
44798
  import { StringDecoder as StringDecoder2 } from "node:string_decoder";
45531
44799
 
45532
44800
  // src/files/read-file/read-file-range-line-zero.ts
@@ -45640,8 +44908,8 @@ function finishLineZeroTail(state, tail, resultLines, fileSize, lineChunkSize, l
45640
44908
 
45641
44909
  // src/files/read-file/read-file-range-async.ts
45642
44910
  async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE, fileSizeIn) {
45643
- const fileSize = fileSizeIn ?? (await fs51.promises.stat(filePath)).size;
45644
- const fd = await fs51.promises.open(filePath, "r");
44911
+ const fileSize = fileSizeIn ?? (await fs49.promises.stat(filePath)).size;
44912
+ const fd = await fs49.promises.open(filePath, "r");
45645
44913
  const bufSize = 64 * 1024;
45646
44914
  const buf = Buffer.alloc(bufSize);
45647
44915
  const decoder = new StringDecoder2("utf8");
@@ -45742,7 +45010,7 @@ async function readFileRangeAsync(filePath, startLine, endLine, lineOffsetIn, li
45742
45010
  }
45743
45011
 
45744
45012
  // src/files/read-file/read-small-file-range-async.ts
45745
- import fs52 from "node:fs";
45013
+ import fs50 from "node:fs";
45746
45014
  var SMALL_FILE_RANGE_MAX_BYTES = 512 * 1024;
45747
45015
  function sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize) {
45748
45016
  if (startLine === 0 && lineOffsetIn == null && lines[0] != null && lines[0].length > lineChunkSize) {
@@ -45771,18 +45039,18 @@ function sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize,
45771
45039
  };
45772
45040
  }
45773
45041
  async function readSmallFileRangeAsync(filePath, fileSize, startLine, endLine, lineOffsetIn, lineChunkSize = LINE_CHUNK_SIZE) {
45774
- const raw = await fs52.promises.readFile(filePath, "utf8");
45042
+ const raw = await fs50.promises.readFile(filePath, "utf8");
45775
45043
  const lines = raw.split(/\r?\n/);
45776
45044
  return sliceLineRange(lines, startLine, endLine, lineOffsetIn, lineChunkSize, fileSize);
45777
45045
  }
45778
45046
 
45779
45047
  // src/files/read-file/read-file-buffer-full-async.ts
45780
45048
  init_yield_to_event_loop();
45781
- import fs53 from "node:fs";
45049
+ import fs51 from "node:fs";
45782
45050
  var READ_CHUNK_BYTES = 256 * 1024;
45783
45051
  async function readFileBufferFullAsync(filePath) {
45784
- const stat2 = await fs53.promises.stat(filePath);
45785
- const fd = await fs53.promises.open(filePath, "r");
45052
+ const stat2 = await fs51.promises.stat(filePath);
45053
+ const fd = await fs51.promises.open(filePath, "r");
45786
45054
  const chunks = [];
45787
45055
  let position = 0;
45788
45056
  let bytesSinceYield = 0;
@@ -45849,7 +45117,7 @@ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineC
45849
45117
  }
45850
45118
  if (hasRange) {
45851
45119
  if (fileSize == null) {
45852
- const stat2 = await fs54.promises.stat(resolvedPath);
45120
+ const stat2 = await fs52.promises.stat(resolvedPath);
45853
45121
  fileSize = stat2.size;
45854
45122
  }
45855
45123
  const read2 = fileSize <= SMALL_FILE_RANGE_MAX_BYTES ? await readSmallFileRangeAsync(
@@ -45876,25 +45144,15 @@ async function readFileAsync(relativePath, startLine, endLine, lineOffset, lineC
45876
45144
  }
45877
45145
  }
45878
45146
 
45879
- // src/files/browser/file-browser-read-queue.ts
45880
- var readChain = Promise.resolve();
45881
- function withFileBrowserReadQueue(fn) {
45882
- const next = readChain.then(() => fn());
45883
- readChain = next.then(
45884
- () => void 0,
45885
- () => void 0
45886
- );
45887
- return next;
45888
- }
45889
-
45890
- // src/files/browser/handle-file-browser-read.ts
45891
- async function handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPath, gitScope) {
45147
+ // src/files/browser/execute-file-browser-read.ts
45148
+ async function executeFileBrowserRead(params) {
45149
+ const { msg, reqPath, sessionParentPath, gitScope } = params;
45892
45150
  const startLine = typeof msg.startLine === "number" ? msg.startLine : void 0;
45893
45151
  const endLine = typeof msg.endLine === "number" ? msg.endLine : void 0;
45894
45152
  const lineOffset = typeof msg.lineOffset === "number" ? msg.lineOffset : void 0;
45895
45153
  const lineChunkSize = typeof msg.lineChunkSize === "number" ? msg.lineChunkSize : void 0;
45896
45154
  const encoding = msg.encoding === "base64" ? "base64" : "utf8";
45897
- const result = gitScope ? await readGitTreeFileAsync(
45155
+ return gitScope ? await readGitTreeFileAsync(
45898
45156
  gitScope.repoAbsPath,
45899
45157
  gitScope.branch,
45900
45158
  reqPath,
@@ -45915,233 +45173,1398 @@ async function handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPa
45915
45173
  { skipInitialYield: true, useResolvedSize: true }
45916
45174
  )
45917
45175
  );
45176
+ }
45177
+
45178
+ // src/local-shortcut/app/lib/file-browser/handle-read.ts
45179
+ async function handleShortcutFileBrowserRead(ctx, sessionId) {
45180
+ const reqPath = parseReadPath(ctx.url);
45181
+ if (reqPath == null) {
45182
+ sendShortcutJson(ctx.res, 400, { error: "path required" });
45183
+ return;
45184
+ }
45185
+ const gitMsg = parseGitScopeFromUrl(ctx.url);
45186
+ const gitScope = resolveGitBranchScope({ id: "shortcut", path: reqPath, ...gitMsg });
45187
+ if (gitMsg.source === "git_branch" && typeof gitMsg.repoRelPath === "string" && typeof gitMsg.branch === "string" && gitMsg.branch.trim().length > 0 && !gitScope) {
45188
+ sendShortcutJson(ctx.res, 400, { error: "Invalid repository path" });
45189
+ return;
45190
+ }
45191
+ const sessionParentPath = await resolveShortcutSessionParent(ctx, sessionId);
45192
+ const result = await executeFileBrowserRead({
45193
+ msg: parseReadParams(ctx.url),
45194
+ reqPath,
45195
+ sessionParentPath,
45196
+ gitScope
45197
+ });
45918
45198
  if ("error" in result) {
45919
- sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
45199
+ sendShortcutJson(ctx.res, 400, { error: result.error });
45920
45200
  return;
45921
45201
  }
45922
- const payload = {
45923
- type: "file_browser_response",
45924
- id: msg.id,
45925
- content: result.content,
45926
- totalLines: result.totalLines,
45927
- size: result.size
45928
- };
45929
- if (result.lineOffset != null) payload.lineOffset = result.lineOffset;
45930
- if (result.mimeType != null) payload.mimeType = result.mimeType;
45931
- if (result.resolvedPath != null) payload.resolvedPath = result.resolvedPath;
45932
- sendFileBrowserMessage(socket, e2ee, payload);
45933
- if (!gitScope) {
45934
- const parentDir = path78.dirname(reqPath.replace(/^\/+/, "") || ".");
45935
- scheduleDirectoryResolveCacheWarmup(sessionParentPath ?? getBridgeRoot(), parentDir === "" ? "." : parentDir);
45202
+ const body = { content: result.content };
45203
+ if (result.totalLines != null) body.totalLines = result.totalLines;
45204
+ if (result.size != null) body.size = result.size;
45205
+ if (result.lineOffset != null) body.lineOffset = result.lineOffset;
45206
+ if (result.mimeType != null) body.mimeType = result.mimeType;
45207
+ if (result.resolvedPath != null) body.resolvedPath = result.resolvedPath;
45208
+ sendShortcutFileBrowserJson(ctx.res, 200, body, ctx.e2ee);
45209
+ }
45210
+
45211
+ // src/local-shortcut/app/api/bridges/files/read/route.ts
45212
+ async function GET3(ctx) {
45213
+ if (!assertBridgeWorkspace(ctx)) return;
45214
+ await handleShortcutFileBrowserRead(ctx);
45215
+ }
45216
+
45217
+ // src/local-shortcut/app/api/sessions/[sessionId]/files/list/route.ts
45218
+ async function GET4(ctx) {
45219
+ await handleShortcutFileBrowserList(ctx, ctx.params.sessionId);
45220
+ }
45221
+
45222
+ // src/local-shortcut/app/api/sessions/[sessionId]/files/read/route.ts
45223
+ async function GET5(ctx) {
45224
+ await handleShortcutFileBrowserRead(ctx, ctx.params.sessionId);
45225
+ }
45226
+
45227
+ // src/local-shortcut/lib/router.ts
45228
+ var routes = [];
45229
+ function segmentsFromAppPath(appPath) {
45230
+ const paramNames = [];
45231
+ const segments = appPath.split("/").filter(Boolean).map((seg) => {
45232
+ if (seg.startsWith("[") && seg.endsWith("]")) {
45233
+ const name = seg.slice(1, -1);
45234
+ paramNames.push(name);
45235
+ return `:${name}`;
45236
+ }
45237
+ return seg;
45238
+ });
45239
+ return { segments, paramNames };
45240
+ }
45241
+ function addAppRoute(method, appPath, handler) {
45242
+ const { segments, paramNames } = segmentsFromAppPath(appPath);
45243
+ routes.push({ method, segments, paramNames, handler });
45244
+ }
45245
+ addAppRoute("GET", "api/bridges/local-shortcut/health", GET);
45246
+ addAppRoute("GET", "api/bridges/files/list", GET2);
45247
+ addAppRoute("GET", "api/bridges/files/read", GET3);
45248
+ addAppRoute("GET", "api/sessions/[sessionId]/files/list", GET4);
45249
+ addAppRoute("GET", "api/sessions/[sessionId]/files/read", GET5);
45250
+ function matchShortcutRoute(pathname, method) {
45251
+ const pathSegments = pathname.split("/").filter(Boolean);
45252
+ for (const route of routes) {
45253
+ if (route.method !== method) continue;
45254
+ if (pathSegments.length !== route.segments.length) continue;
45255
+ const params = {};
45256
+ let matched = true;
45257
+ for (let i = 0; i < route.segments.length; i++) {
45258
+ const seg = route.segments[i];
45259
+ const value = pathSegments[i];
45260
+ if (seg.startsWith(":")) {
45261
+ params[seg.slice(1)] = decodeURIComponent(value);
45262
+ } else if (seg !== value) {
45263
+ matched = false;
45264
+ break;
45265
+ }
45266
+ }
45267
+ if (matched) return { params, handler: route.handler };
45936
45268
  }
45269
+ return null;
45937
45270
  }
45938
45271
 
45939
- // src/files/browser/resolve-git-branch-scope.ts
45940
- function resolveGitBranchScope(msg) {
45941
- const gitBranchScope = msg.source === "git_branch" && typeof msg.repoRelPath === "string" && typeof msg.branch === "string" && msg.branch.trim().length > 0;
45942
- if (!gitBranchScope) return null;
45943
- const repoAbsPath = resolveRepoAbsPathFromBridge(msg.repoRelPath);
45944
- if (!repoAbsPath) return null;
45945
- return { repoAbsPath, branch: msg.branch.trim() };
45272
+ // src/local-shortcut/lib/server.ts
45273
+ function shortcutErrorMessage(err) {
45274
+ return err instanceof Error ? err.message : "Internal shortcut server error";
45946
45275
  }
45947
-
45948
- // src/files/browser/index.ts
45949
- function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
45950
- beginFileBrowserRequest();
45951
- void (async () => {
45952
- try {
45953
- const reqPath = msg.path.replace(/^\/+/, "") || ".";
45954
- const sessionParentPath = sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
45955
- const gitScope = resolveGitBranchScope(msg);
45956
- if (msg.source === "git_branch" && typeof msg.repoRelPath === "string" && typeof msg.branch === "string" && msg.branch.trim().length > 0 && !gitScope) {
45957
- sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: "Invalid repository path" });
45958
- return;
45276
+ function createLocalShortcutRequestHandler(ctx) {
45277
+ return (req, res) => {
45278
+ void (async () => {
45279
+ try {
45280
+ const corsOk = applyCorsHeaders(req, res);
45281
+ if (req.method === "OPTIONS") {
45282
+ res.writeHead(corsOk ? 204 : 403);
45283
+ res.end();
45284
+ return;
45285
+ }
45286
+ if (!corsOk) {
45287
+ sendShortcutJson(res, 403, { error: "Origin not allowed" });
45288
+ return;
45289
+ }
45290
+ if (req.method !== "GET") {
45291
+ sendShortcutJson(res, 405, { error: "Method not allowed" });
45292
+ return;
45293
+ }
45294
+ if (!isAuthorizedShortcutRequest(req, ctx.token)) {
45295
+ sendShortcutJson(res, 401, { error: "Unauthorized" });
45296
+ return;
45297
+ }
45298
+ const rawUrl = req.url ?? "/";
45299
+ const qIndex = rawUrl.indexOf("?");
45300
+ const pathname = qIndex >= 0 ? rawUrl.slice(0, qIndex) : rawUrl;
45301
+ const search = qIndex >= 0 ? rawUrl.slice(qIndex) : "";
45302
+ const url2 = new URL2(search || "", "http://local");
45303
+ const match = matchShortcutRoute(pathname, req.method);
45304
+ if (!match) {
45305
+ sendShortcutJson(res, 404, { error: "Not found" });
45306
+ return;
45307
+ }
45308
+ const handlerCtx = {
45309
+ req,
45310
+ res,
45311
+ params: match.params,
45312
+ url: url2,
45313
+ workspaceId: ctx.workspaceId,
45314
+ sessionWorktreeManager: ctx.sessionWorktreeManager,
45315
+ e2ee: ctx.e2ee
45316
+ };
45317
+ await match.handler(handlerCtx);
45318
+ } catch (err) {
45319
+ if (!res.headersSent) {
45320
+ sendShortcutJson(res, 500, { error: shortcutErrorMessage(err) });
45321
+ }
45959
45322
  }
45960
- if (msg.op === "read") {
45961
- await handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPath, gitScope);
45962
- } else {
45963
- await handleFileBrowserList(socket, e2ee, msg.id, reqPath, sessionParentPath, gitScope);
45323
+ })().catch((err) => {
45324
+ if (!res.headersSent) {
45325
+ sendShortcutJson(res, 500, { error: shortcutErrorMessage(err) });
45964
45326
  }
45965
- } finally {
45966
- endFileBrowserRequest();
45967
- }
45968
- })();
45327
+ });
45328
+ };
45969
45329
  }
45970
45330
 
45971
- // src/files/handle-file-browser-search.ts
45972
- init_cwd();
45973
- init_yield_to_event_loop();
45974
- import path79 from "node:path";
45975
- var SEARCH_LIMIT = 100;
45976
- function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
45977
- void (async () => {
45978
- await yieldToEventLoop();
45979
- const q = typeof msg.q === "string" ? msg.q : "";
45980
- const sessionParentPath = path79.resolve(
45981
- sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
45982
- );
45983
- if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
45984
- triggerFileIndexBuild(sessionParentPath);
45985
- const payload2 = {
45986
- type: "file_browser_search_response",
45987
- id: msg.id,
45988
- paths: [],
45989
- indexReady: false
45990
- };
45991
- sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
45992
- return;
45993
- }
45994
- const results = await searchBridgeFilePathsAsync(sessionParentPath, q, SEARCH_LIMIT);
45995
- const payload = {
45996
- type: "file_browser_search_response",
45997
- id: msg.id,
45998
- paths: results,
45999
- indexReady: true
46000
- };
46001
- sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["paths"]) : payload);
46002
- })();
46003
- }
46004
- function triggerFileIndexBuild(sessionParentPath = getBridgeRoot()) {
46005
- setImmediate(() => {
46006
- void ensureFileIndexAsync(sessionParentPath).catch((e) => {
46007
- console.error("[file-index] Background build failed:", e);
45331
+ // src/local-shortcut/lib/start-server.ts
45332
+ var RESTART_DELAY_MS = 250;
45333
+ var MAX_RESTART_ATTEMPTS = 5;
45334
+ function listenOnPort(server, port) {
45335
+ return new Promise((resolve35, reject) => {
45336
+ server.once("error", reject);
45337
+ server.listen(port, "127.0.0.1", () => {
45338
+ const addr = server.address();
45339
+ if (!addr || typeof addr === "string") {
45340
+ reject(new Error("Local shortcut server did not bind"));
45341
+ return;
45342
+ }
45343
+ resolve35(addr.port);
46008
45344
  });
46009
45345
  });
46010
45346
  }
46011
-
46012
- // src/routing/handlers/file-browser-messages.ts
46013
- function handleFileBrowserRequestMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
46014
- if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
46015
- const socket = getWs();
46016
- if (!socket) return;
46017
- handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager);
45347
+ function closeServer(server) {
45348
+ if (!server) return Promise.resolve();
45349
+ return new Promise((resolve35, reject) => {
45350
+ server.close((err) => err ? reject(err) : resolve35());
45351
+ });
46018
45352
  }
46019
- function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
46020
- if (typeof msg.id !== "string") return;
46021
- const socket = getWs();
46022
- if (!socket) return;
46023
- handleFileBrowserSearch(
46024
- msg,
46025
- socket,
46026
- e2ee,
46027
- sessionWorktreeManager
46028
- );
45353
+ function delay2(ms) {
45354
+ return new Promise((resolve35) => setTimeout(resolve35, ms));
46029
45355
  }
46030
-
46031
- // src/code-nav/handlers/send-code-nav-response.ts
46032
- var ENCRYPTED_FIELDS = ["definition", "definitions", "references", "symbols", "confidentTarget"];
46033
- function sendCodeNavResponse(socket, e2ee, payload) {
46034
- const fieldsToEncrypt = ENCRYPTED_FIELDS.filter((field) => field in payload && payload[field] !== void 0);
46035
- sendWsMessage(
46036
- socket,
46037
- e2ee && fieldsToEncrypt.length > 0 ? e2ee.encryptFields(payload, [...fieldsToEncrypt]) : payload
46038
- );
45356
+ function startLocalShortcutServer(ctx) {
45357
+ let closed = false;
45358
+ let restartAttempts = 0;
45359
+ let currentServer = null;
45360
+ let boundPort = null;
45361
+ let restarting = null;
45362
+ async function bindServer(port) {
45363
+ const server = http2.createServer(createLocalShortcutRequestHandler(ctx));
45364
+ server.on("error", (err) => {
45365
+ void restartAfterError(err);
45366
+ });
45367
+ currentServer = server;
45368
+ const listenPort = await listenOnPort(server, port);
45369
+ boundPort = listenPort;
45370
+ return listenPort;
45371
+ }
45372
+ async function restartAfterError(_reason) {
45373
+ if (closed || restarting) return;
45374
+ if (restartAttempts >= MAX_RESTART_ATTEMPTS) return;
45375
+ restartAttempts += 1;
45376
+ restarting = (async () => {
45377
+ await closeServer(currentServer).catch(() => {
45378
+ });
45379
+ currentServer = null;
45380
+ if (closed) return;
45381
+ await delay2(RESTART_DELAY_MS);
45382
+ if (closed) return;
45383
+ try {
45384
+ await bindServer(boundPort ?? 0);
45385
+ restartAttempts = 0;
45386
+ } catch {
45387
+ }
45388
+ })().finally(() => {
45389
+ restarting = null;
45390
+ });
45391
+ await restarting;
45392
+ }
45393
+ return bindServer(0).then((port) => ({
45394
+ port,
45395
+ token: ctx.token,
45396
+ close: async () => {
45397
+ closed = true;
45398
+ await closeServer(currentServer);
45399
+ currentServer = null;
45400
+ }
45401
+ }));
46039
45402
  }
46040
45403
 
46041
- // src/code-nav/handlers/handle-definition-op.ts
46042
- async function handleDefinitionOp(ctx) {
46043
- const definition = await queryDefinitionAt(ctx.sessionParentPath, ctx.reqPath, ctx.line, ctx.column);
46044
- sendCodeNavResponse(ctx.socket, ctx.e2ee, {
46045
- type: "code_nav_response",
46046
- id: ctx.msg.id,
46047
- indexReady: true,
46048
- definition
45404
+ // src/local-shortcut/lib/create.ts
45405
+ async function createLocalShortcut(params) {
45406
+ const token = generateShortcutToken();
45407
+ const handle = await startLocalShortcutServer({
45408
+ workspaceId: params.workspaceId,
45409
+ token,
45410
+ sessionWorktreeManager: params.sessionWorktreeManager,
45411
+ e2ee: params.e2ee
46049
45412
  });
45413
+ return {
45414
+ handle,
45415
+ identify: {
45416
+ localShortcutPort: handle.port,
45417
+ localShortcutToken: handle.token
45418
+ }
45419
+ };
46050
45420
  }
46051
45421
 
46052
- // src/code-nav/handlers/handle-definitions-op.ts
46053
- async function handleDefinitionsOp(ctx) {
46054
- const { definitions, confidentTarget } = await queryDefinitionsNavigation(
46055
- ctx.sessionParentPath,
46056
- ctx.reqPath,
46057
- ctx.line,
46058
- ctx.column
46059
- );
46060
- sendCodeNavResponse(ctx.socket, ctx.e2ee, {
46061
- type: "code_nav_response",
46062
- id: ctx.msg.id,
46063
- indexReady: true,
46064
- definitions,
46065
- confidentTarget
45422
+ // src/connection/create-bridge-connection-runtime.ts
45423
+ async function createBridgeConnectionRuntime(options, params) {
45424
+ const { apiUrl, workspaceId } = options;
45425
+ const firehoseServerUrl = options.firehoseServerUrl ?? options.proxyServerUrl;
45426
+ const { state, getWs, logFn } = params;
45427
+ const tokens = {
45428
+ accessToken: options.authToken,
45429
+ refreshToken: options.refreshToken
45430
+ };
45431
+ const { worktreesRootPath, sessionWorktreeManager, previewWorktreeManager } = createBridgeWorktreeManagers({ worktreesRootPath: options.worktreesRootPath, log: logFn });
45432
+ const { e2ee, acpManager, bridgeAccessPromise } = await createBridgeAccessAndAcp({
45433
+ apiUrl,
45434
+ workspaceId,
45435
+ tokens,
45436
+ e2eCertificate: options.e2eCertificate,
45437
+ getWs,
45438
+ log: logFn
46066
45439
  });
46067
- }
46068
-
46069
- // src/code-nav/handlers/handle-references-op.ts
46070
- async function handleReferencesOp(ctx) {
46071
- const references = await queryReferencesAt(ctx.sessionParentPath, ctx.reqPath, ctx.line, ctx.column);
46072
- sendCodeNavResponse(ctx.socket, ctx.e2ee, {
46073
- type: "code_nav_response",
46074
- id: ctx.msg.id,
46075
- indexReady: true,
46076
- references
45440
+ const localShortcut = await createLocalShortcut({
45441
+ workspaceId,
45442
+ sessionWorktreeManager,
45443
+ e2ee
46077
45444
  });
46078
- }
46079
-
46080
- // src/code-nav/handlers/handle-symbols-op.ts
46081
- async function handleSymbolsOp(ctx) {
46082
- const symbols = await querySymbolsInFile(ctx.sessionParentPath, ctx.reqPath);
46083
- sendCodeNavResponse(ctx.socket, ctx.e2ee, {
46084
- type: "code_nav_response",
46085
- id: ctx.msg.id,
46086
- indexReady: true,
46087
- symbols
45445
+ logFn("CLI running. Press Ctrl+C to exit.");
45446
+ const { previewEnvironmentManager, scheduleInitialIndexBuildsOnce, identifyReportedPaths } = createBridgePreviewStack({
45447
+ getWs,
45448
+ log: logFn,
45449
+ e2ee,
45450
+ previewWorktreeManager,
45451
+ worktreesRootPath
45452
+ });
45453
+ Object.assign(identifyReportedPaths, localShortcut.identify);
45454
+ const { bridgeHeartbeat, messageDeps } = createBridgeRuntimeMessageSetup({
45455
+ apiUrl,
45456
+ workspaceId,
45457
+ firehoseServerUrl,
45458
+ state,
45459
+ getWs,
45460
+ log: logFn,
45461
+ tokens,
45462
+ acpManager,
45463
+ sessionWorktreeManager,
45464
+ previewWorktreeManager,
45465
+ previewEnvironmentManager,
45466
+ e2ee
46088
45467
  });
45468
+ return {
45469
+ state,
45470
+ getWs,
45471
+ logFn,
45472
+ tokens,
45473
+ acpManager,
45474
+ bridgeAccessPromise,
45475
+ previewEnvironmentManager,
45476
+ bridgeHeartbeat,
45477
+ messageDeps,
45478
+ identifyReportedPaths,
45479
+ scheduleInitialIndexBuildsOnce,
45480
+ e2ee,
45481
+ localShortcutPromise: Promise.resolve(localShortcut.handle)
45482
+ };
46089
45483
  }
46090
45484
 
46091
- // src/code-nav/handlers/prepare-code-nav-request.ts
46092
- init_yield_to_event_loop();
46093
-
46094
- // src/code-nav/handlers/deferred-code-nav-index.ts
46095
- var latestSeqBySession = /* @__PURE__ */ new Map();
46096
- var pendingBySession = /* @__PURE__ */ new Map();
46097
- var flushPromise = null;
46098
- function scheduleDeferredCodeNavIndex(sessionKey, sessionParentPath, reqPath) {
46099
- const seq = (latestSeqBySession.get(sessionKey) ?? 0) + 1;
46100
- latestSeqBySession.set(sessionKey, seq);
46101
- pendingBySession.set(sessionKey, { sessionParentPath, reqPath, seq });
46102
- scheduleFlushWhenIdle();
46103
- }
46104
- function scheduleFlushWhenIdle() {
46105
- if (flushPromise != null) return;
46106
- flushPromise = runFlushWhenIdle().finally(() => {
46107
- flushPromise = null;
46108
- if (pendingBySession.size > 0) scheduleFlushWhenIdle();
46109
- });
45485
+ // src/connection/create-main-bridge-reconnect-state.ts
45486
+ function createMainBridgeReconnectState() {
45487
+ const state = {
45488
+ closedByUser: false,
45489
+ reconnectAttempt: 0,
45490
+ lastReconnectCloseMeta: null,
45491
+ logBridgeOpenAsReconnect: false,
45492
+ reconnectTimeout: null,
45493
+ currentWs: null,
45494
+ mainQuiet: createEmptyReconnectQuietSlot(),
45495
+ mainOutage: createEmptyReconnectOutageTracker(),
45496
+ duplicateBridgeFlap: createEmptyDuplicateBridgeFlapTracker(),
45497
+ firehoseHandle: null,
45498
+ lastFirehoseParams: null,
45499
+ firehoseReconnectTimeout: null,
45500
+ firehoseReconnectAttempt: 0,
45501
+ firehoseGeneration: 0,
45502
+ firehoseQuiet: createEmptyReconnectQuietSlot(),
45503
+ firehoseOutage: createEmptyReconnectOutageTracker(),
45504
+ lastFirehoseReconnectCloseMeta: null
45505
+ };
45506
+ return {
45507
+ state,
45508
+ getWs: () => state.currentWs
45509
+ };
46110
45510
  }
46111
- async function runFlushWhenIdle() {
46112
- await yieldUntilFileBrowserIdle();
46113
- const entries = [];
46114
- for (const [sessionKey, entry] of pendingBySession) {
46115
- pendingBySession.delete(sessionKey);
46116
- if (entry.seq === latestSeqBySession.get(sessionKey)) entries.push(entry);
46117
- }
46118
- for (const entry of entries) {
46119
- scheduleSymbolIndexFile(entry.sessionParentPath, entry.reqPath);
46120
- }
45511
+
45512
+ // src/connection/ensure-bridge-connection-databases.ts
45513
+ async function ensureBridgeConnectionDatabases(log2) {
45514
+ await ensureCliSqliteInitialized({ logLegacyMigration: log2 });
45515
+ await ensureCodeNavCacheSqliteInitialized();
46121
45516
  }
46122
- registerFileBrowserActivityListener(() => {
46123
- if (pendingBySession.size > 0 && !isBridgeBrowseInteractiveActive()) {
46124
- scheduleFlushWhenIdle();
46125
- }
46126
- });
46127
45517
 
46128
- // src/code-nav/handlers/code-nav-session-key.ts
46129
- function codeNavSessionKey(sessionId, sessionParentPath) {
46130
- const trimmed2 = sessionId?.trim();
46131
- return trimmed2 ? trimmed2 : sessionParentPath;
45518
+ // src/connection/build-bridge-url.ts
45519
+ function buildBridgeUrl(apiUrl, workspaceId, authToken) {
45520
+ const base = apiUrl.startsWith("https") ? apiUrl.replace(/^https/, "wss") : apiUrl.replace(/^http/, "ws");
45521
+ const params = new URLSearchParams({ workspaceId, token: authToken });
45522
+ return `${base}/ws/bridge?${params.toString()}`;
46132
45523
  }
46133
45524
 
46134
- // src/code-nav/handlers/wait-for-symbol-index-ready.ts
46135
- var CODE_NAV_INDEX_READY_WAIT_MS = 75;
46136
- var CODE_NAV_INDEX_READY_POLL_MS = 15;
46137
- function delay2(ms) {
45525
+ // src/types/api-to-bridge-messages.ts
45526
+ var API_TO_BRIDGE_MESSAGE_TYPES = [
45527
+ "auth_token",
45528
+ "bridge_identified",
45529
+ "ha",
45530
+ "preview_environments_config",
45531
+ "preview_environment_control",
45532
+ "deploy_session_to_preview",
45533
+ "agent_config",
45534
+ "prompt_queue_state",
45535
+ "prompt",
45536
+ "session_git_request",
45537
+ "rename_session_branch",
45538
+ "session_archived",
45539
+ "session_discarded",
45540
+ "revert_turn_snapshot",
45541
+ "cursor_request_response",
45542
+ "skill_call",
45543
+ "file_browser_request",
45544
+ "file_browser_search",
45545
+ "code_nav",
45546
+ "skill_layout_request",
45547
+ "install_skills",
45548
+ "refresh_local_skills",
45549
+ "bridge_git_context_request",
45550
+ "list_repo_branches_request"
45551
+ ];
45552
+ var API_TO_BRIDGE_TYPE_SET = new Set(API_TO_BRIDGE_MESSAGE_TYPES);
45553
+ function parseApiToBridgeMessage(data, log2) {
45554
+ if (data === null || typeof data !== "object" || Array.isArray(data)) return null;
45555
+ const t = data.type;
45556
+ if (typeof t !== "string" || !API_TO_BRIDGE_TYPE_SET.has(t)) {
45557
+ if (typeof t === "string") {
45558
+ log2?.(`[Bridge service] unhandled message type: ${t}`);
45559
+ }
45560
+ return null;
45561
+ }
45562
+ if (t === "ha") {
45563
+ const s = data.s;
45564
+ if (typeof s !== "number" || !Number.isFinite(s)) return null;
45565
+ }
45566
+ return data;
45567
+ }
45568
+
45569
+ // src/routing/handlers/auth-token.ts
45570
+ var handleAuthToken = (msg, { log: log2 }) => {
45571
+ if (typeof msg.token !== "string") return;
45572
+ log2("Received auth token. Save it for future runs:");
45573
+ log2(` export BUILDAUTOMATON_AUTH_TOKEN="${msg.token}"`);
45574
+ };
45575
+
45576
+ // src/routing/handlers/bridge-identified.ts
45577
+ var handleBridgeIdentified = (msg, deps) => {
45578
+ if (typeof msg.bridgeName !== "string") return;
45579
+ deps.onBridgeIdentified({
45580
+ bridgeName: msg.bridgeName,
45581
+ proxyPorts: msg.proxyPorts,
45582
+ previewEnvironments: msg.previewEnvironments
45583
+ });
45584
+ setImmediate(() => {
45585
+ void (async () => {
45586
+ cancelInFlightAgentCapabilityWarmup();
45587
+ try {
45588
+ await deps.reportAutoDetectedAgents?.();
45589
+ } catch (e) {
45590
+ deps.log(
45591
+ `[Bridge service] Auto-detect agents failed: ${e instanceof Error ? e.message : String(e)}`
45592
+ );
45593
+ }
45594
+ try {
45595
+ await deps.warmupAgentCapabilitiesOnConnect?.();
45596
+ } catch (e) {
45597
+ deps.log(
45598
+ `[Bridge service] Agent capability warmup failed: ${e instanceof Error ? e.message : String(e)}`
45599
+ );
45600
+ }
45601
+ })();
45602
+ });
45603
+ setImmediate(() => {
45604
+ if (isCliImmediateShutdownRequested()) return;
45605
+ try {
45606
+ deps.sendLocalSkillsReport?.();
45607
+ } catch (e) {
45608
+ deps.log(
45609
+ `[Bridge service] Local skills report failed: ${e instanceof Error ? e.message : String(e)}`
45610
+ );
45611
+ }
45612
+ });
45613
+ };
45614
+
45615
+ // src/connection/heartbeat/ack.ts
45616
+ var handleBridgeHeartbeatAck = (msg, deps) => {
45617
+ const raw = msg.s;
45618
+ if (typeof raw !== "number" || !Number.isFinite(raw)) return;
45619
+ deps.onBridgeHeartbeatAck?.(Math.trunc(raw));
45620
+ };
45621
+
45622
+ // src/agents/acp/from-bridge/handle-bridge-agent-config.ts
45623
+ function handleBridgeAgentConfig(msg, { acpManager }) {
45624
+ if (!Array.isArray(msg.agents) || msg.agents.length === 0) return;
45625
+ acpManager.setPreferredAgentType(msg.agents[0].type);
45626
+ }
45627
+
45628
+ // src/routing/handlers/agent-config.ts
45629
+ var handleAgentConfigMessage = (msg, deps) => {
45630
+ handleBridgeAgentConfig(msg, deps);
45631
+ };
45632
+
45633
+ // src/prompt-turn-queue/client-report.ts
45634
+ function sendPromptQueueClientReport(ws, queues) {
45635
+ if (!ws) return false;
45636
+ const wireQueues = {};
45637
+ for (const [queueKey, rows] of Object.entries(queues)) {
45638
+ wireQueues[queueKey] = rows.map((r) => ({ turnId: r.turnId, clientState: r.cliState }));
45639
+ }
45640
+ sendWsMessage(ws, { type: "prompt_queue_client_report", queues: wireQueues });
45641
+ return true;
45642
+ }
45643
+
45644
+ // src/prompt-turn-queue/disk-store.ts
45645
+ var MERGEABLE_BRIDGE_SERVER_STATES = /* @__PURE__ */ new Set([
45646
+ "queued",
45647
+ "requeued",
45648
+ "requeued_with_revert",
45649
+ "cancel_requested",
45650
+ "stopping",
45651
+ "discarded"
45652
+ ]);
45653
+ async function readPersistedQueue(queueKey) {
45654
+ return withCliSqlite((db) => {
45655
+ const row = db.get("SELECT queue_key, updated_at, turns_json FROM prompt_queue WHERE queue_key = ?", [
45656
+ queueKey
45657
+ ]);
45658
+ if (!row) return null;
45659
+ try {
45660
+ const turns = JSON.parse(row.turns_json);
45661
+ if (!Array.isArray(turns)) return null;
45662
+ return { queueKey: row.queue_key, updatedAt: row.updated_at, turns };
45663
+ } catch {
45664
+ return null;
45665
+ }
45666
+ });
45667
+ }
45668
+ async function writePersistedQueue(file2) {
45669
+ await withCliSqlite((db) => {
45670
+ db.run(
45671
+ `INSERT INTO prompt_queue (queue_key, updated_at, turns_json) VALUES (?, ?, ?)
45672
+ ON CONFLICT(queue_key) DO UPDATE SET updated_at = excluded.updated_at, turns_json = excluded.turns_json`,
45673
+ [file2.queueKey, file2.updatedAt, JSON.stringify(file2.turns)]
45674
+ );
45675
+ });
45676
+ }
45677
+ async function mergeBridgeServerQueueSnapshot(queueKey, serverTurns) {
45678
+ const prev = await readPersistedQueue(queueKey);
45679
+ const turns = [];
45680
+ for (const raw of serverTurns) {
45681
+ if (!raw || typeof raw !== "object") continue;
45682
+ const o = raw;
45683
+ const turnId = typeof o.turnId === "string" ? o.turnId : "";
45684
+ const sessionId = typeof o.sessionId === "string" ? o.sessionId : "";
45685
+ const turnOrd = typeof o.turnOrd === "number" ? o.turnOrd : Number(o.turnOrd) || 0;
45686
+ const bridgeServerState = o.serverState ?? o.bridgeServerState;
45687
+ const lastCliState = o.lastClientState ?? o.lastCliState ?? null;
45688
+ const payload = o.payload && typeof o.payload === "object" ? o.payload : {};
45689
+ if (!turnId || !sessionId) continue;
45690
+ if (!MERGEABLE_BRIDGE_SERVER_STATES.has(String(bridgeServerState))) continue;
45691
+ const old = prev?.turns.find((t) => t.turnId === turnId);
45692
+ const mergedCli = old?.lastCliState === "running" && lastCliState == null ? "running" : lastCliState;
45693
+ turns.push({
45694
+ turnId,
45695
+ sessionId,
45696
+ turnOrd,
45697
+ bridgeServerState,
45698
+ lastCliState: mergedCli,
45699
+ payload
45700
+ });
45701
+ }
45702
+ turns.sort((a, b) => a.turnOrd - b.turnOrd);
45703
+ return { queueKey, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), turns };
45704
+ }
45705
+
45706
+ // src/prompt-turn-queue/runner/dispatch-local-prompt.ts
45707
+ function dispatchLocalPrompt(next, deps) {
45708
+ const pl = next.payload;
45709
+ const rawParent = pl["sessionParent"];
45710
+ const sessionParent = rawParent === "bridge_root" || rawParent === "worktrees_root" ? rawParent : void 0;
45711
+ const rawParentPath = pl["sessionParentPath"];
45712
+ const sessionParentPath = typeof rawParentPath === "string" && rawParentPath.trim() !== "" ? rawParentPath.trim() : void 0;
45713
+ const rawBaseBranches = pl["worktreeBaseBranches"];
45714
+ const worktreeBaseBranches = rawBaseBranches != null && typeof rawBaseBranches === "object" && !Array.isArray(rawBaseBranches) ? rawBaseBranches : void 0;
45715
+ const msg = {
45716
+ type: "prompt",
45717
+ sessionId: next.sessionId,
45718
+ runId: next.turnId,
45719
+ prompt: pl.prompt,
45720
+ mode: typeof pl.mode === "string" ? pl.mode : "agent",
45721
+ isNewSession: pl.isNewSession === true,
45722
+ ...sessionParent ? { sessionParent } : {},
45723
+ ...sessionParentPath ? { sessionParentPath } : {},
45724
+ ...worktreeBaseBranches && Object.keys(worktreeBaseBranches).length > 0 ? { worktreeBaseBranches } : {},
45725
+ ...typeof pl.followUpCatalogPromptId === "string" ? { followUpCatalogPromptId: pl.followUpCatalogPromptId } : {},
45726
+ ...typeof pl.agentType === "string" && pl.agentType.trim() ? { agentType: pl.agentType.trim() } : {},
45727
+ ...pl.agentConfig != null && typeof pl.agentConfig === "object" && !Array.isArray(pl.agentConfig) && Object.keys(pl.agentConfig).length > 0 ? { agentConfig: pl.agentConfig } : {},
45728
+ ...Array.isArray(pl.attachments) && pl.attachments.length > 0 ? { attachments: pl.attachments } : {}
45729
+ };
45730
+ handleBridgePrompt(msg, deps);
45731
+ }
45732
+
45733
+ // src/prompt-turn-queue/runner/queue-selection.ts
45734
+ function pickNextRunnableTurn(turns) {
45735
+ for (const t of turns) {
45736
+ if (t.bridgeServerState === "discarded" || t.bridgeServerState === "stopping") continue;
45737
+ if (t.bridgeServerState === "cancel_requested") continue;
45738
+ if (!isRunnableBridgePromptTurnServerState(t.bridgeServerState)) continue;
45739
+ if (t.lastCliState === "running" || t.lastCliState === "stopped" || t.lastCliState === "failed" || t.lastCliState === "cancelled") {
45740
+ continue;
45741
+ }
45742
+ return t;
45743
+ }
45744
+ return null;
45745
+ }
45746
+ function hasRunningTurn(turns) {
45747
+ return turns.some((t) => t.lastCliState === "running");
45748
+ }
45749
+
45750
+ // src/prompt-turn-queue/runner/run-id-queue-key-map.ts
45751
+ var runIdToQueueKey = /* @__PURE__ */ new Map();
45752
+ function getRunIdQueueKey(runId) {
45753
+ return runIdToQueueKey.get(runId);
45754
+ }
45755
+ function setRunIdQueueKey(runId, queueKey) {
45756
+ runIdToQueueKey.set(runId, queueKey);
45757
+ }
45758
+ function deleteRunIdQueueKey(runId) {
45759
+ const queueKey = runIdToQueueKey.get(runId);
45760
+ runIdToQueueKey.delete(runId);
45761
+ return queueKey;
45762
+ }
45763
+ function syncRunningTurnQueueKeys(turns, queueKey) {
45764
+ for (const running of turns.filter((t) => t.lastCliState === "running")) {
45765
+ runIdToQueueKey.set(running.turnId, queueKey);
45766
+ }
45767
+ }
45768
+
45769
+ // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
45770
+ import fs54 from "node:fs";
45771
+
45772
+ // src/git/snapshot/capture.ts
45773
+ import * as fs53 from "node:fs";
45774
+ import * as path76 from "node:path";
45775
+ async function resolveSnapshotRepoRoots(options) {
45776
+ const { worktreePaths, fallbackCwd, sessionId, log: log2 } = options;
45777
+ if (worktreePaths?.length) {
45778
+ const uniq = [...new Set(worktreePaths.map((p) => path76.resolve(p)))];
45779
+ return uniq;
45780
+ }
45781
+ try {
45782
+ const repos = await discoverGitReposUnderRoot(fallbackCwd);
45783
+ const mapped = repos.map((r) => r.absolutePath);
45784
+ const sid = sessionId?.trim();
45785
+ if (sid) {
45786
+ const filtered = mapped.filter((root) => path76.basename(root) === sid);
45787
+ if (filtered.length > 0) return filtered;
45788
+ }
45789
+ return mapped;
45790
+ } catch (e) {
45791
+ log2(`[snapshot] Discover repositories failed: ${e instanceof Error ? e.message : String(e)}`);
45792
+ return [];
45793
+ }
45794
+ }
45795
+ async function capturePreTurnSnapshot(options) {
45796
+ const { runId, repoRoots, agentCwd, log: log2 } = options;
45797
+ if (!runId || !repoRoots.length) {
45798
+ return { ok: false, error: "No git repos to snapshot" };
45799
+ }
45800
+ const repos = [];
45801
+ await forEachWithGitYield(repoRoots, async (root) => {
45802
+ const stashSha = await gitStashCreate(root, log2);
45803
+ const untrackedPaths = await gitUntrackedPaths(root, log2);
45804
+ repos.push({ path: root, stashSha, untrackedPaths });
45805
+ });
45806
+ const dir = snapshotsDirForCwd(agentCwd);
45807
+ try {
45808
+ fs53.mkdirSync(dir, { recursive: true });
45809
+ } catch (e) {
45810
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
45811
+ }
45812
+ const payload = {
45813
+ runId,
45814
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
45815
+ repos
45816
+ };
45817
+ const filePath = path76.join(dir, `${runId}.json`);
45818
+ try {
45819
+ fs53.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
45820
+ } catch (e) {
45821
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
45822
+ }
45823
+ const repoList = repos.map((r) => r.path).join(", ");
45824
+ log2(
45825
+ `[snapshot] Saved pre-turn snapshot ${runId.slice(0, 8)}\u2026 (${repos.length} repo(s)): ${repoList}`
45826
+ );
45827
+ return { ok: true, filePath, repos };
45828
+ }
45829
+ async function applyPreTurnSnapshot(filePath, log2) {
45830
+ let data;
45831
+ try {
45832
+ const raw = fs53.readFileSync(filePath, "utf8");
45833
+ data = JSON.parse(raw);
45834
+ } catch (e) {
45835
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
45836
+ }
45837
+ if (!Array.isArray(data.repos)) {
45838
+ return { ok: false, error: "Invalid snapshot file" };
45839
+ }
45840
+ let applyError = null;
45841
+ await forEachWithGitYield(data.repos, async (r) => {
45842
+ if (applyError || !r.path) return;
45843
+ const reset = await gitRun(r.path, ["reset", "--hard", "HEAD"], log2, "reset --hard");
45844
+ if (!reset.ok) {
45845
+ applyError = reset;
45846
+ return;
45847
+ }
45848
+ const clean = await gitRun(r.path, ["clean", "-fd"], log2, "clean -fd");
45849
+ if (!clean.ok) {
45850
+ applyError = clean;
45851
+ return;
45852
+ }
45853
+ if (r.stashSha) {
45854
+ const ap = await gitRun(r.path, ["stash", "apply", r.stashSha], log2, "stash apply");
45855
+ if (!ap.ok) applyError = ap;
45856
+ }
45857
+ });
45858
+ if (applyError) return applyError;
45859
+ log2(`[snapshot] Restored pre-turn state for ${data.runId.slice(0, 8)}\u2026`);
45860
+ return { ok: true };
45861
+ }
45862
+
45863
+ // src/prompt-turn-queue/runner/run-local-revert-before-queued-prompt.ts
45864
+ init_cwd();
45865
+ async function runLocalRevertBeforeQueuedPrompt(next, deps) {
45866
+ if (next.bridgeServerState !== "requeued_with_revert") return true;
45867
+ const sid = next.sessionId;
45868
+ const pl = next.payload;
45869
+ const tid = typeof pl.snapshotRevertTurnId === "string" && pl.snapshotRevertTurnId.trim() !== "" ? pl.snapshotRevertTurnId.trim() : next.turnId;
45870
+ const agentBase = deps.sessionWorktreeManager.getSessionWorktreeRootForSession(sid) ?? getBridgeRoot();
45871
+ const file2 = snapshotFilePath(agentBase, tid);
45872
+ try {
45873
+ await fs54.promises.access(file2, fs54.constants.F_OK);
45874
+ } catch {
45875
+ deps.log(
45876
+ `[Queue] requeued_with_revert: no pre-turn snapshot for ${tid.slice(0, 8)}\u2026; continuing without revert.`
45877
+ );
45878
+ return true;
45879
+ }
45880
+ const res = await applyPreTurnSnapshot(file2, deps.log);
45881
+ if (!res.ok) {
45882
+ deps.log(`[Queue] requeued_with_revert: snapshot apply failed: ${res.error ?? "unknown"}`);
45883
+ }
45884
+ return res.ok;
45885
+ }
45886
+
45887
+ // src/prompt-turn-queue/runner/finalize-prompt-turn-on-bridge.ts
45888
+ async function finalizePromptTurnOnBridge(getWs, runId, success2, opts) {
45889
+ if (!runId) return false;
45890
+ const queueKey = deleteRunIdQueueKey(runId);
45891
+ if (!queueKey) return false;
45892
+ const f = await readPersistedQueue(queueKey);
45893
+ if (!f) return false;
45894
+ const t = f.turns.find((x) => x.turnId === runId);
45895
+ if (!t) return false;
45896
+ t.lastCliState = opts?.terminalCliState ?? (success2 ? "stopped" : "failed");
45897
+ await writePersistedQueue(f);
45898
+ sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: runId, cliState: t.lastCliState }] });
45899
+ return true;
45900
+ }
45901
+
45902
+ // src/prompt-turn-queue/runner/apply-prompt-queue-state-from-server.ts
45903
+ async function applyPromptQueueStateFromServer(msg, deps) {
45904
+ const raw = msg.queues;
45905
+ if (!raw || typeof raw !== "object") return;
45906
+ const getWs = deps.getWs;
45907
+ for (const [queueKey, serverTurns] of Object.entries(raw)) {
45908
+ if (!Array.isArray(serverTurns)) continue;
45909
+ const file2 = await mergeBridgeServerQueueSnapshot(queueKey, serverTurns);
45910
+ await writePersistedQueue(file2);
45911
+ }
45912
+ for (const [queueKey, serverTurns] of Object.entries(raw)) {
45913
+ if (!Array.isArray(serverTurns)) continue;
45914
+ const file2 = await readPersistedQueue(queueKey);
45915
+ if (!file2) continue;
45916
+ syncRunningTurnQueueKeys(file2.turns, queueKey);
45917
+ }
45918
+ for (const [queueKey, serverTurns] of Object.entries(raw)) {
45919
+ if (!Array.isArray(serverTurns)) continue;
45920
+ const file2 = await readPersistedQueue(queueKey);
45921
+ if (!file2) continue;
45922
+ const cancelRow = file2.turns.find((t) => t.bridgeServerState === "cancel_requested" && t.lastCliState === "running");
45923
+ if (cancelRow) {
45924
+ const localCancelHandled = await deps.acpManager.cancelRun(cancelRow.turnId);
45925
+ if (!localCancelHandled) {
45926
+ deps.log(
45927
+ `[Queue] bridge server cancel_requested for ${cancelRow.turnId.slice(0, 8)}\u2026 but no local agent run is active (e.g. after CLI restart); marking cancelled and notifying bridge.`
45928
+ );
45929
+ await finalizePromptTurnOnBridge(deps.getWs, cancelRow.turnId, false, { terminalCliState: "cancelled" });
45930
+ const ws = deps.getWs();
45931
+ if (ws && cancelRow.sessionId) {
45932
+ sendWsMessage(ws, {
45933
+ type: "prompt_result",
45934
+ sessionId: cancelRow.sessionId,
45935
+ runId: cancelRow.turnId,
45936
+ success: false,
45937
+ error: "Stopped by user",
45938
+ stopReason: "cancelled"
45939
+ });
45940
+ }
45941
+ }
45942
+ }
45943
+ }
45944
+ const report = {};
45945
+ const startedThisTick = /* @__PURE__ */ new Set();
45946
+ for (const [queueKey, serverTurns] of Object.entries(raw)) {
45947
+ if (!Array.isArray(serverTurns)) continue;
45948
+ const file2 = await readPersistedQueue(queueKey);
45949
+ if (!file2) continue;
45950
+ if (hasRunningTurn(file2.turns)) continue;
45951
+ const next = pickNextRunnableTurn(file2.turns);
45952
+ if (!next) continue;
45953
+ if (!await runLocalRevertBeforeQueuedPrompt(next, deps)) {
45954
+ next.lastCliState = "failed";
45955
+ await writePersistedQueue(file2);
45956
+ sendPromptQueueClientReport(getWs(), { [queueKey]: [{ turnId: next.turnId, cliState: "failed" }] });
45957
+ continue;
45958
+ }
45959
+ next.lastCliState = "running";
45960
+ await writePersistedQueue(file2);
45961
+ setRunIdQueueKey(next.turnId, queueKey);
45962
+ startedThisTick.add(next.turnId);
45963
+ report[queueKey] = [{ turnId: next.turnId, cliState: "running" }];
45964
+ }
45965
+ if (Object.keys(report).length > 0) {
45966
+ sendPromptQueueClientReport(getWs(), report);
45967
+ }
45968
+ for (const [queueKey, serverTurns] of Object.entries(raw)) {
45969
+ if (!Array.isArray(serverTurns)) continue;
45970
+ const file2 = await readPersistedQueue(queueKey);
45971
+ if (!file2) continue;
45972
+ const running = file2.turns.find((t) => t.lastCliState === "running");
45973
+ if (!running || !startedThisTick.has(running.turnId)) continue;
45974
+ if (getRunIdQueueKey(running.turnId) !== queueKey) continue;
45975
+ dispatchLocalPrompt(running, deps);
45976
+ }
45977
+ }
45978
+
45979
+ // src/agents/acp/from-bridge/bridge-prompt-wiring.ts
45980
+ function createBridgePromptSenders(deps, getWs) {
45981
+ const sendBridgeMessage = (message, encryptedFields = []) => {
45982
+ const s = getWs();
45983
+ if (!s) return false;
45984
+ const wire = deps.e2ee && encryptedFields.length > 0 ? deps.e2ee.encryptFields(message, encryptedFields) : message;
45985
+ sendWsMessage(s, wire);
45986
+ return true;
45987
+ };
45988
+ const sendResult = (result) => {
45989
+ const encryptedFields = result.type === "prompt_result" ? ["output", "error"] : [];
45990
+ sendBridgeMessage(result, encryptedFields);
45991
+ if (result.type === "prompt_result") {
45992
+ const pr = result;
45993
+ const cancelled = pr.stopReason === "cancelled";
45994
+ void finalizePromptTurnOnBridge(
45995
+ getWs,
45996
+ typeof pr.runId === "string" ? pr.runId : void 0,
45997
+ pr.success === true,
45998
+ cancelled ? { terminalCliState: "cancelled" } : void 0
45999
+ ).catch(() => {
46000
+ });
46001
+ }
46002
+ };
46003
+ const sendSessionUpdate = (payload) => {
46004
+ const s = getWs();
46005
+ if (!s) {
46006
+ deps.log("[Bridge service] Session update not sent: not connected to the bridge.");
46007
+ return;
46008
+ }
46009
+ const p = payload;
46010
+ const wire = p.type === "session_update" && deps.e2ee ? deps.e2ee.encryptFields(payload, ["payload"]) : p.type === "session_file_change" && deps.e2ee ? deps.e2ee.encryptFields(payload, [
46011
+ "path",
46012
+ "oldText",
46013
+ "newText",
46014
+ "patchContent",
46015
+ "isDirectory",
46016
+ "directoryRemoved"
46017
+ ]) : payload;
46018
+ sendWsMessage(s, wire);
46019
+ };
46020
+ return { sendBridgeMessage, sendResult, sendSessionUpdate };
46021
+ }
46022
+
46023
+ // src/agents/acp/from-bridge/handle-bridge-prompt/parse-bridge-attachments.ts
46024
+ function parseBridgeAttachments(msg) {
46025
+ const raw = msg.attachments;
46026
+ if (!Array.isArray(raw)) return [];
46027
+ const out = [];
46028
+ for (const x of raw) {
46029
+ if (x === null || typeof x !== "object" || Array.isArray(x)) continue;
46030
+ const o = x;
46031
+ const id = typeof o.attachmentId === "string" ? o.attachmentId.trim() : "";
46032
+ if (!id) continue;
46033
+ const mt = typeof o.mimeType === "string" && o.mimeType.trim() ? o.mimeType.trim() : "application/octet-stream";
46034
+ out.push({ attachmentId: id, mimeType: mt });
46035
+ }
46036
+ return out;
46037
+ }
46038
+
46039
+ // src/agents/acp/from-bridge/handle-bridge-prompt/parse-worktree-base-branches.ts
46040
+ function parseWorktreeBaseBranches(msg) {
46041
+ const raw = msg.worktreeBaseBranches;
46042
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
46043
+ const out = {};
46044
+ for (const [k, v] of Object.entries(raw)) {
46045
+ if (typeof k !== "string" || typeof v !== "string") continue;
46046
+ const rel = k.trim();
46047
+ const branch = v.trim();
46048
+ if (rel && branch) out[rel] = branch;
46049
+ }
46050
+ return Object.keys(out).length > 0 ? out : void 0;
46051
+ }
46052
+
46053
+ // src/agents/acp/from-bridge/bridge-prompt-preamble.ts
46054
+ init_cwd();
46055
+ import { execFile as execFile8 } from "node:child_process";
46056
+ import { promisify as promisify9 } from "node:util";
46057
+ var execFileAsync7 = promisify9(execFile8);
46058
+ async function readGitBranch(cwd) {
46059
+ try {
46060
+ const { stdout } = await execFileAsync7("git", ["branch", "--show-current"], { cwd, maxBuffer: 64 * 1024 });
46061
+ const b = stdout.trim();
46062
+ return b || null;
46063
+ } catch {
46064
+ return null;
46065
+ }
46066
+ }
46067
+ async function runBridgePromptPreamble(params) {
46068
+ const { getWs, log: log2, sessionWorktreeManager, sessionId, runId, effectiveCwd } = params;
46069
+ const s = getWs();
46070
+ const repoCheckoutPaths = await sessionWorktreeManager.ensureRepoCheckoutPathsForSessionAsync(sessionId);
46071
+ const repoRoots = await resolveSnapshotRepoRoots({
46072
+ worktreePaths: repoCheckoutPaths,
46073
+ fallbackCwd: effectiveCwd,
46074
+ sessionId: sessionId?.trim() || void 0,
46075
+ log: log2
46076
+ });
46077
+ if (s && sessionId) {
46078
+ const cliGitBranch = await readGitBranch(effectiveCwd);
46079
+ const usesWt = sessionWorktreeManager.usesWorktreeSession(sessionId);
46080
+ const isolatedSessionParentPath = sessionWorktreeManager.getIsolatedSessionParentPathForSession(sessionId);
46081
+ sendWsMessage(s, {
46082
+ type: "session_git_context_report",
46083
+ sessionId,
46084
+ cliGitBranch,
46085
+ agentUsesWorktree: usesWt,
46086
+ sessionParent: usesWt ? "worktrees_root" : "bridge_root",
46087
+ sessionParentPath: usesWt ? isolatedSessionParentPath ?? effectiveCwd : getBridgeRoot()
46088
+ });
46089
+ }
46090
+ if (s && sessionId && runId) {
46091
+ const cap = repoRoots.length > 0 ? await capturePreTurnSnapshot({ runId, repoRoots, agentCwd: effectiveCwd, log: log2 }) : { ok: false, error: "No git repos" };
46092
+ sendWsMessage(s, {
46093
+ type: "pre_turn_snapshot_report",
46094
+ sessionId,
46095
+ turnId: runId,
46096
+ captured: cap.ok
46097
+ });
46098
+ }
46099
+ }
46100
+ function parseFollowUpFieldsFromPromptMessage(msg) {
46101
+ const followUpCatalogPromptId = typeof msg.followUpCatalogPromptId === "string" && msg.followUpCatalogPromptId.trim() !== "" ? msg.followUpCatalogPromptId.trim() : null;
46102
+ return { followUpCatalogPromptId };
46103
+ }
46104
+
46105
+ // src/agents/acp/from-bridge/handle-bridge-prompt/run-preamble-and-prompt.ts
46106
+ async function runPreambleAndPrompt(params) {
46107
+ const {
46108
+ deps,
46109
+ msg,
46110
+ getWs,
46111
+ log: log2,
46112
+ sessionWorktreeManager,
46113
+ sessionId,
46114
+ runId,
46115
+ promptText,
46116
+ attachments,
46117
+ mode,
46118
+ agentType,
46119
+ agentId,
46120
+ agentConfig,
46121
+ resolvedCwd,
46122
+ senders: { sendResult, sendSessionUpdate }
46123
+ } = params;
46124
+ const effectiveCwd = resolveSessionParentPathForAgentProcess(resolvedCwd);
46125
+ await runBridgePromptPreamble({
46126
+ getWs,
46127
+ log: log2,
46128
+ sessionWorktreeManager,
46129
+ sessionId,
46130
+ runId,
46131
+ effectiveCwd
46132
+ });
46133
+ const { followUpCatalogPromptId } = parseFollowUpFieldsFromPromptMessage(msg);
46134
+ deps.acpManager.handlePrompt({
46135
+ promptText,
46136
+ promptId: msg.id,
46137
+ sessionId,
46138
+ runId,
46139
+ mode,
46140
+ agentType,
46141
+ agentId,
46142
+ agentConfig,
46143
+ sessionParentPath: effectiveCwd,
46144
+ sendResult,
46145
+ sendSessionUpdate,
46146
+ followUpCatalogPromptId,
46147
+ cloudApiBaseUrl: deps.cloudApiBaseUrl,
46148
+ getCloudAccessToken: deps.getCloudAccessToken,
46149
+ e2ee: deps.e2ee,
46150
+ ...attachments.length > 0 ? { attachments } : {},
46151
+ isNewSession: msg.isNewSession === true
46152
+ });
46153
+ }
46154
+
46155
+ // src/agents/acp/from-bridge/handle-bridge-prompt/handle-bridge-prompt.ts
46156
+ function handleBridgePrompt(msg, deps) {
46157
+ const { getWs, log: log2, acpManager, sessionWorktreeManager } = deps;
46158
+ const rawPrompt = msg.prompt;
46159
+ const promptText = typeof rawPrompt === "string" ? rawPrompt : rawPrompt != null ? String(rawPrompt) : "";
46160
+ const attachments = parseBridgeAttachments(msg);
46161
+ const sessionId = msg.sessionId;
46162
+ const runId = typeof msg.runId === "string" ? msg.runId : void 0;
46163
+ const promptId = typeof msg.id === "string" ? msg.id : void 0;
46164
+ const senders = createBridgePromptSenders(deps, getWs);
46165
+ if (!promptText.trim() && attachments.length === 0) {
46166
+ log2(
46167
+ `[Bridge service] Prompt ignored: empty or missing prompt text (session ${typeof msg.sessionId === "string" ? msg.sessionId.slice(0, 8) : "\u2014"}\u2026, run ${typeof msg.runId === "string" ? msg.runId.slice(0, 8) : "\u2014"}\u2026).`
46168
+ );
46169
+ senders.sendBridgeMessage(
46170
+ {
46171
+ type: "prompt_result",
46172
+ ...promptId ? { id: promptId } : {},
46173
+ ...sessionId ? { sessionId } : {},
46174
+ ...runId ? { runId } : {},
46175
+ success: false,
46176
+ error: "Empty or missing prompt text from the bridge; this turn was not sent to the agent."
46177
+ },
46178
+ ["error"]
46179
+ );
46180
+ return;
46181
+ }
46182
+ const isNewSession = msg.isNewSession === true;
46183
+ const rawParent = msg.sessionParent;
46184
+ const sessionParent = rawParent === "bridge_root" || rawParent === "worktrees_root" ? rawParent : rawParent === "session_worktrees_root" ? "worktrees_root" : null;
46185
+ const sessionParentPath = typeof msg.sessionParentPath === "string" && msg.sessionParentPath.trim() ? msg.sessionParentPath.trim() : null;
46186
+ const agentType = typeof msg.agentType === "string" && msg.agentType.trim() ? msg.agentType.trim() : void 0;
46187
+ const rawAgentId = msg.agentId;
46188
+ const agentId = typeof rawAgentId === "string" && rawAgentId.trim() !== "" ? rawAgentId.trim() : null;
46189
+ const mode = typeof msg.mode === "string" && msg.mode.trim() ? msg.mode.trim() : void 0;
46190
+ const agentConfig = msg.agentConfig != null && typeof msg.agentConfig === "object" && !Array.isArray(msg.agentConfig) ? msg.agentConfig : void 0;
46191
+ const worktreeBaseBranches = parseWorktreeBaseBranches(msg);
46192
+ acpManager.logPromptReceivedFromBridge({ agentType, mode });
46193
+ void sessionWorktreeManager.resolveSessionParentPathForPrompt(sessionId, {
46194
+ isNewSession,
46195
+ sessionParent,
46196
+ sessionParentPath,
46197
+ ...worktreeBaseBranches ? { worktreeBaseBranches } : {}
46198
+ }).then(
46199
+ (cwd) => runPreambleAndPrompt({
46200
+ deps,
46201
+ msg,
46202
+ getWs,
46203
+ log: log2,
46204
+ sessionWorktreeManager,
46205
+ sessionId,
46206
+ runId,
46207
+ promptText,
46208
+ attachments,
46209
+ mode,
46210
+ agentType,
46211
+ agentId,
46212
+ agentConfig,
46213
+ resolvedCwd: cwd,
46214
+ senders
46215
+ })
46216
+ ).catch((err) => {
46217
+ log2(`[Agent] Session parent path resolve failed: ${err instanceof Error ? err.message : String(err)}`);
46218
+ void runPreambleAndPrompt({
46219
+ deps,
46220
+ msg,
46221
+ getWs,
46222
+ log: log2,
46223
+ sessionWorktreeManager,
46224
+ sessionId,
46225
+ runId,
46226
+ promptText,
46227
+ attachments,
46228
+ mode,
46229
+ agentType,
46230
+ agentId,
46231
+ agentConfig,
46232
+ resolvedCwd: void 0,
46233
+ senders
46234
+ });
46235
+ });
46236
+ }
46237
+
46238
+ // src/routing/handlers/prompt.ts
46239
+ var handlePromptMessage = (msg, deps) => {
46240
+ handleBridgePrompt(msg, deps);
46241
+ };
46242
+
46243
+ // src/routing/handlers/prompt-queue-state.ts
46244
+ var handlePromptQueueStateMessage = (msg, deps) => {
46245
+ void applyPromptQueueStateFromServer(msg, deps).catch((err) => {
46246
+ deps.log(`[Queue] applyPromptQueueStateFromServer failed: ${err instanceof Error ? err.message : String(err)}`);
46247
+ });
46248
+ };
46249
+
46250
+ // src/agents/acp/from-bridge/handle-bridge-session-request-response.ts
46251
+ function handleBridgeSessionRequestResponse(msg, { acpManager }) {
46252
+ if (typeof msg.requestId !== "string") return;
46253
+ acpManager.resolveRequest(msg.requestId, msg.result ?? {});
46254
+ }
46255
+
46256
+ // src/routing/handlers/session-request-response.ts
46257
+ var handleSessionRequestResponseMessage = (msg, deps) => {
46258
+ handleBridgeSessionRequestResponse(msg, deps);
46259
+ };
46260
+
46261
+ // src/skills/handle-skill-call.ts
46262
+ function handleSkillCall(msg, socket, log2) {
46263
+ callSkill(msg.skillId, msg.operationId, msg.params ?? {}).then((result) => {
46264
+ sendWsMessage(socket, { type: "skill_result", id: msg.id, result });
46265
+ }).catch((err) => {
46266
+ sendWsMessage(socket, { type: "skill_result", id: msg.id, error: String(err) });
46267
+ log2(`[Bridge service] Skill invocation failed (${msg.skillId}/${msg.operationId}): ${err}`);
46268
+ });
46269
+ }
46270
+
46271
+ // src/routing/handlers/skill-call.ts
46272
+ var handleSkillCallMessage = (msg, { getWs, log: log2 }) => {
46273
+ const skillId = typeof msg.skillId === "string" ? msg.skillId : "";
46274
+ const operationId = typeof msg.operationId === "string" ? msg.operationId : "";
46275
+ if (!skillId || !operationId) return;
46276
+ const socket = getWs();
46277
+ if (!socket) return;
46278
+ handleSkillCall(
46279
+ { id: msg.id, skillId, operationId, params: msg.params },
46280
+ socket,
46281
+ log2
46282
+ );
46283
+ };
46284
+
46285
+ // src/files/browser/send-file-browser-message.ts
46286
+ function sendFileBrowserMessage(socket, e2ee, payload) {
46287
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["entries", "content", "totalLines", "size", "lineOffset"]) : payload);
46288
+ }
46289
+
46290
+ // src/files/read-file/resolve-file-cache-warmup.ts
46291
+ init_yield_to_event_loop();
46292
+ import path77 from "node:path";
46293
+ var WARMED_DIRECTORY_TTL_MS = 6e4;
46294
+ async function warmDirectoryResolveCache(sessionParentPath, directoryRelativePath) {
46295
+ const { listDirAsync: listDirAsync2 } = await Promise.resolve().then(() => (init_list_dir(), list_dir_exports));
46296
+ const result = await listDirAsync2(directoryRelativePath, sessionParentPath);
46297
+ if ("error" in result) return;
46298
+ for (const entry of result.entries) {
46299
+ if (entry.isDir) continue;
46300
+ if (fileBrowserRequestInFlight()) return;
46301
+ const cacheKey = resolveCacheKey(sessionParentPath, entry.path);
46302
+ if (hasResolveCacheEntry(cacheKey)) continue;
46303
+ await resolveFileForReadAsync(entry.path, sessionParentPath).catch(() => void 0);
46304
+ await yieldToEventLoop();
46305
+ }
46306
+ }
46307
+ function scheduleDirectoryResolveCacheWarmup(sessionParentPath, directoryRelativePath) {
46308
+ const parent = path77.resolve(sessionParentPath);
46309
+ const dirKey = warmedDirectoryKey(parent, directoryRelativePath);
46310
+ if (!markDirectoryWarmed(dirKey)) return;
46311
+ setTimeout(() => unmarkDirectoryWarmed(dirKey), WARMED_DIRECTORY_TTL_MS);
46312
+ setImmediate(() => {
46313
+ void warmDirectoryResolveCache(parent, directoryRelativePath.replace(/^\/+/, "") || ".");
46314
+ });
46315
+ }
46316
+ function scheduleResolveCacheWarmupForListedEntries(sessionParentPath, listRelativePath, entries) {
46317
+ scheduleDirectoryResolveCacheWarmup(sessionParentPath, listRelativePath);
46318
+ setImmediate(() => {
46319
+ void (async () => {
46320
+ for (const entry of entries) {
46321
+ if (entry.isDir) continue;
46322
+ if (fileBrowserRequestInFlight()) return;
46323
+ const cacheKey = resolveCacheKey(sessionParentPath, entry.path);
46324
+ if (hasResolveCacheEntry(cacheKey)) continue;
46325
+ await resolveFileForReadAsync(entry.path, sessionParentPath).catch(() => void 0);
46326
+ await yieldToEventLoop();
46327
+ }
46328
+ })();
46329
+ });
46330
+ }
46331
+
46332
+ // src/files/browser/handle-file-browser-list.ts
46333
+ async function handleFileBrowserList(socket, e2ee, id, reqPath, sessionParentPath, gitScope) {
46334
+ const result = await executeFileBrowserList({ reqPath, sessionParentPath, gitScope });
46335
+ if ("error" in result) {
46336
+ sendWsMessage(socket, { type: "file_browser_response", id, error: result.error });
46337
+ return;
46338
+ }
46339
+ sendFileBrowserMessage(socket, e2ee, { type: "file_browser_response", id, entries: result.entries });
46340
+ if (!gitScope && sessionParentPath) {
46341
+ scheduleResolveCacheWarmupForListedEntries(sessionParentPath, reqPath, result.entries);
46342
+ }
46343
+ }
46344
+
46345
+ // src/files/browser/handle-file-browser-read.ts
46346
+ import path78 from "node:path";
46347
+ init_cwd();
46348
+ async function handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPath, gitScope) {
46349
+ const result = await executeFileBrowserRead({ msg, reqPath, sessionParentPath, gitScope });
46350
+ if ("error" in result) {
46351
+ sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: result.error });
46352
+ return;
46353
+ }
46354
+ const payload = {
46355
+ type: "file_browser_response",
46356
+ id: msg.id,
46357
+ content: result.content,
46358
+ totalLines: result.totalLines,
46359
+ size: result.size
46360
+ };
46361
+ if (result.lineOffset != null) payload.lineOffset = result.lineOffset;
46362
+ if (result.mimeType != null) payload.mimeType = result.mimeType;
46363
+ if (result.resolvedPath != null) payload.resolvedPath = result.resolvedPath;
46364
+ sendFileBrowserMessage(socket, e2ee, payload);
46365
+ if (!gitScope) {
46366
+ const parentDir = path78.dirname(reqPath.replace(/^\/+/, "") || ".");
46367
+ scheduleDirectoryResolveCacheWarmup(sessionParentPath ?? getBridgeRoot(), parentDir === "" ? "." : parentDir);
46368
+ }
46369
+ }
46370
+
46371
+ // src/files/browser/index.ts
46372
+ function handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager) {
46373
+ beginFileBrowserRequest();
46374
+ void (async () => {
46375
+ try {
46376
+ const reqPath = msg.path.replace(/^\/+/, "") || ".";
46377
+ const sessionParentPath = sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : void 0;
46378
+ const gitScope = resolveGitBranchScope(msg);
46379
+ if (msg.source === "git_branch" && typeof msg.repoRelPath === "string" && typeof msg.branch === "string" && msg.branch.trim().length > 0 && !gitScope) {
46380
+ sendWsMessage(socket, { type: "file_browser_response", id: msg.id, error: "Invalid repository path" });
46381
+ return;
46382
+ }
46383
+ if (msg.op === "read") {
46384
+ await handleFileBrowserRead(socket, e2ee, msg, reqPath, sessionParentPath, gitScope);
46385
+ } else {
46386
+ await handleFileBrowserList(socket, e2ee, msg.id, reqPath, sessionParentPath, gitScope);
46387
+ }
46388
+ } finally {
46389
+ endFileBrowserRequest();
46390
+ }
46391
+ })();
46392
+ }
46393
+
46394
+ // src/files/handle-file-browser-search.ts
46395
+ init_cwd();
46396
+ init_yield_to_event_loop();
46397
+ import path79 from "node:path";
46398
+ var SEARCH_LIMIT = 100;
46399
+ function handleFileBrowserSearch(msg, socket, e2ee, sessionWorktreeManager) {
46400
+ void (async () => {
46401
+ await yieldToEventLoop();
46402
+ const q = typeof msg.q === "string" ? msg.q : "";
46403
+ const sessionParentPath = path79.resolve(
46404
+ sessionWorktreeManager != null ? await resolveFileBrowserSessionParent(sessionWorktreeManager, msg.sessionId) : getBridgeRoot()
46405
+ );
46406
+ if (!await bridgeFileIndexIsPopulated(sessionParentPath)) {
46407
+ triggerFileIndexBuild(sessionParentPath);
46408
+ const payload2 = {
46409
+ type: "file_browser_search_response",
46410
+ id: msg.id,
46411
+ paths: [],
46412
+ indexReady: false
46413
+ };
46414
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload2, ["paths"]) : payload2);
46415
+ return;
46416
+ }
46417
+ const results = await searchBridgeFilePathsAsync(sessionParentPath, q, SEARCH_LIMIT);
46418
+ const payload = {
46419
+ type: "file_browser_search_response",
46420
+ id: msg.id,
46421
+ paths: results,
46422
+ indexReady: true
46423
+ };
46424
+ sendWsMessage(socket, e2ee ? e2ee.encryptFields(payload, ["paths"]) : payload);
46425
+ })();
46426
+ }
46427
+ function triggerFileIndexBuild(sessionParentPath = getBridgeRoot()) {
46428
+ setImmediate(() => {
46429
+ void ensureFileIndexAsync(sessionParentPath).catch((e) => {
46430
+ console.error("[file-index] Background build failed:", e);
46431
+ });
46432
+ });
46433
+ }
46434
+
46435
+ // src/routing/handlers/file-browser-messages.ts
46436
+ function handleFileBrowserRequestMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
46437
+ if (typeof msg.id !== "string" || typeof msg.path !== "string") return;
46438
+ const socket = getWs();
46439
+ if (!socket) return;
46440
+ handleFileBrowserRequest(msg, socket, e2ee, sessionWorktreeManager);
46441
+ }
46442
+ function handleFileBrowserSearchMessage(msg, { getWs, e2ee, sessionWorktreeManager }) {
46443
+ if (typeof msg.id !== "string") return;
46444
+ const socket = getWs();
46445
+ if (!socket) return;
46446
+ handleFileBrowserSearch(
46447
+ msg,
46448
+ socket,
46449
+ e2ee,
46450
+ sessionWorktreeManager
46451
+ );
46452
+ }
46453
+
46454
+ // src/code-nav/handlers/send-code-nav-response.ts
46455
+ var ENCRYPTED_FIELDS = ["definition", "definitions", "references", "symbols", "confidentTarget"];
46456
+ function sendCodeNavResponse(socket, e2ee, payload) {
46457
+ const fieldsToEncrypt = ENCRYPTED_FIELDS.filter((field) => field in payload && payload[field] !== void 0);
46458
+ sendWsMessage(
46459
+ socket,
46460
+ e2ee && fieldsToEncrypt.length > 0 ? e2ee.encryptFields(payload, [...fieldsToEncrypt]) : payload
46461
+ );
46462
+ }
46463
+
46464
+ // src/code-nav/handlers/handle-definition-op.ts
46465
+ async function handleDefinitionOp(ctx) {
46466
+ const definition = await queryDefinitionAt(ctx.sessionParentPath, ctx.reqPath, ctx.line, ctx.column);
46467
+ sendCodeNavResponse(ctx.socket, ctx.e2ee, {
46468
+ type: "code_nav_response",
46469
+ id: ctx.msg.id,
46470
+ indexReady: true,
46471
+ definition
46472
+ });
46473
+ }
46474
+
46475
+ // src/code-nav/handlers/handle-definitions-op.ts
46476
+ async function handleDefinitionsOp(ctx) {
46477
+ const { definitions, confidentTarget } = await queryDefinitionsNavigation(
46478
+ ctx.sessionParentPath,
46479
+ ctx.reqPath,
46480
+ ctx.line,
46481
+ ctx.column
46482
+ );
46483
+ sendCodeNavResponse(ctx.socket, ctx.e2ee, {
46484
+ type: "code_nav_response",
46485
+ id: ctx.msg.id,
46486
+ indexReady: true,
46487
+ definitions,
46488
+ confidentTarget
46489
+ });
46490
+ }
46491
+
46492
+ // src/code-nav/handlers/handle-references-op.ts
46493
+ async function handleReferencesOp(ctx) {
46494
+ const references = await queryReferencesAt(ctx.sessionParentPath, ctx.reqPath, ctx.line, ctx.column);
46495
+ sendCodeNavResponse(ctx.socket, ctx.e2ee, {
46496
+ type: "code_nav_response",
46497
+ id: ctx.msg.id,
46498
+ indexReady: true,
46499
+ references
46500
+ });
46501
+ }
46502
+
46503
+ // src/code-nav/handlers/handle-symbols-op.ts
46504
+ async function handleSymbolsOp(ctx) {
46505
+ const symbols = await querySymbolsInFile(ctx.sessionParentPath, ctx.reqPath);
46506
+ sendCodeNavResponse(ctx.socket, ctx.e2ee, {
46507
+ type: "code_nav_response",
46508
+ id: ctx.msg.id,
46509
+ indexReady: true,
46510
+ symbols
46511
+ });
46512
+ }
46513
+
46514
+ // src/code-nav/handlers/prepare-code-nav-request.ts
46515
+ init_yield_to_event_loop();
46516
+
46517
+ // src/code-nav/handlers/deferred-code-nav-index.ts
46518
+ var latestSeqBySession = /* @__PURE__ */ new Map();
46519
+ var pendingBySession = /* @__PURE__ */ new Map();
46520
+ var flushPromise = null;
46521
+ function scheduleDeferredCodeNavIndex(sessionKey, sessionParentPath, reqPath) {
46522
+ const seq = (latestSeqBySession.get(sessionKey) ?? 0) + 1;
46523
+ latestSeqBySession.set(sessionKey, seq);
46524
+ pendingBySession.set(sessionKey, { sessionParentPath, reqPath, seq });
46525
+ scheduleFlushWhenIdle();
46526
+ }
46527
+ function scheduleFlushWhenIdle() {
46528
+ if (flushPromise != null) return;
46529
+ flushPromise = runFlushWhenIdle().finally(() => {
46530
+ flushPromise = null;
46531
+ if (pendingBySession.size > 0) scheduleFlushWhenIdle();
46532
+ });
46533
+ }
46534
+ async function runFlushWhenIdle() {
46535
+ await yieldUntilFileBrowserIdle();
46536
+ const entries = [];
46537
+ for (const [sessionKey, entry] of pendingBySession) {
46538
+ pendingBySession.delete(sessionKey);
46539
+ if (entry.seq === latestSeqBySession.get(sessionKey)) entries.push(entry);
46540
+ }
46541
+ for (const entry of entries) {
46542
+ scheduleSymbolIndexFile(entry.sessionParentPath, entry.reqPath);
46543
+ }
46544
+ }
46545
+ registerFileBrowserActivityListener(() => {
46546
+ if (pendingBySession.size > 0 && !isBridgeBrowseInteractiveActive()) {
46547
+ scheduleFlushWhenIdle();
46548
+ }
46549
+ });
46550
+
46551
+ // src/code-nav/handlers/code-nav-session-key.ts
46552
+ function codeNavSessionKey(sessionId, sessionParentPath) {
46553
+ const trimmed2 = sessionId?.trim();
46554
+ return trimmed2 ? trimmed2 : sessionParentPath;
46555
+ }
46556
+
46557
+ // src/code-nav/handlers/wait-for-symbol-index-ready.ts
46558
+ var CODE_NAV_INDEX_READY_WAIT_MS = 75;
46559
+ var CODE_NAV_INDEX_READY_POLL_MS = 15;
46560
+ function delay3(ms) {
46138
46561
  return new Promise((resolve35) => setTimeout(resolve35, ms));
46139
46562
  }
46140
46563
  async function waitForSymbolIndexReady(sessionParentPath, reqPath, maxWaitMs = CODE_NAV_INDEX_READY_WAIT_MS) {
46141
46564
  const deadline = Date.now() + maxWaitMs;
46142
46565
  do {
46143
46566
  if (await symbolIndexFileIsReadyAsync(sessionParentPath, reqPath)) return true;
46144
- await delay2(CODE_NAV_INDEX_READY_POLL_MS);
46567
+ await delay3(CODE_NAV_INDEX_READY_POLL_MS);
46145
46568
  } while (Date.now() < deadline);
46146
46569
  return await symbolIndexFileIsReadyAsync(sessionParentPath, reqPath);
46147
46570
  }
@@ -47118,6 +47541,8 @@ function createMainBridgeOpenHandler(params) {
47118
47541
  cliVersion: CLI_VERSION,
47119
47542
  bridgeRootPath: identifyReportedPaths.bridgeRootPath,
47120
47543
  worktreesRootPath: identifyReportedPaths.worktreesRootPath,
47544
+ localShortcutPort: identifyReportedPaths.localShortcutPort,
47545
+ localShortcutToken: identifyReportedPaths.localShortcutToken,
47121
47546
  ...e2ee ? { e: e2ee.handshake } : {}
47122
47547
  });
47123
47548
  reportGitRepos(getWs, logFn);
@@ -47244,6 +47669,7 @@ async function createBridgeConnection(options) {
47244
47669
  connect();
47245
47670
  const stopFileIndexWatcher = startFileIndexWatcher(getBridgeRoot());
47246
47671
  const bridgeAccess = await runtime.bridgeAccessPromise;
47672
+ const localShortcut = await runtime.localShortcutPromise;
47247
47673
  return {
47248
47674
  close: async () => {
47249
47675
  requestCliImmediateShutdown();
@@ -47256,6 +47682,7 @@ async function createBridgeConnection(options) {
47256
47682
  runtime.logFn
47257
47683
  );
47258
47684
  await bridgeAccess.close();
47685
+ await localShortcut.close();
47259
47686
  }
47260
47687
  };
47261
47688
  }