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