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