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