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