visual-remote 0.3.0 → 0.3.2
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/README.md +63 -8
- package/apps/cli/dist/direct-exec-mcp.js +976 -0
- package/apps/cli/dist/index.js +1353 -254
- package/apps/cli/dist/next.js +3616 -2683
- package/apps/cli/dist/vite.js +5115 -4187
- package/package.json +7 -3
- package/packages/overlay/dist/client.js +115 -7
- package/packages/overlay/dist/viewer.js +4 -4
package/apps/cli/dist/index.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { realpathSync } from "node:fs";
|
|
4
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
5
5
|
import { resolve as resolve8 } from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import { Command, InvalidArgumentError } from "commander";
|
|
8
8
|
|
|
9
|
+
// src/bridge.ts
|
|
10
|
+
import { createConnection } from "node:net";
|
|
11
|
+
|
|
9
12
|
// ../../packages/bridge-core/src/bridge/control-service.ts
|
|
10
13
|
var ControlServiceError = class extends Error {
|
|
11
14
|
statusCode;
|
|
@@ -161,6 +164,11 @@ function pairingTokensMatch(expected, candidate) {
|
|
|
161
164
|
const candidateBytes = Buffer.from(candidate);
|
|
162
165
|
return expectedBytes.length === candidateBytes.length && timingSafeEqual(expectedBytes, candidateBytes);
|
|
163
166
|
}
|
|
167
|
+
function createPairingUrl(baseUrl, token) {
|
|
168
|
+
const url = new URL(baseUrl);
|
|
169
|
+
url.hash = `visual-pair=${encodeURIComponent(token)}`;
|
|
170
|
+
return url.toString();
|
|
171
|
+
}
|
|
164
172
|
|
|
165
173
|
// ../../packages/bridge-core/src/runtime/ports.ts
|
|
166
174
|
import { createServer } from "node:net";
|
|
@@ -284,7 +292,7 @@ function originAllowed(request, allowedOrigins) {
|
|
|
284
292
|
}
|
|
285
293
|
}
|
|
286
294
|
function tokenAccess(token, controlToken, viewerSessions, now) {
|
|
287
|
-
if (token === void 0 || token.length === 0) return
|
|
295
|
+
if (token === void 0 || token.length === 0) return void 0;
|
|
288
296
|
if (pairingTokensMatch(controlToken, token)) return "control";
|
|
289
297
|
const expiresAt = viewerSessions.get(token);
|
|
290
298
|
if (expiresAt !== void 0) {
|
|
@@ -689,7 +697,12 @@ function createGatewayServer(options) {
|
|
|
689
697
|
const access3 = requestAccess(request, options.pairingToken, viewerSessions);
|
|
690
698
|
if (access3 === void 0) {
|
|
691
699
|
response.setHeader("www-authenticate", "Bearer");
|
|
692
|
-
writeApiError(
|
|
700
|
+
writeApiError(
|
|
701
|
+
response,
|
|
702
|
+
401,
|
|
703
|
+
"unauthorized",
|
|
704
|
+
"A valid control or viewer token is required"
|
|
705
|
+
);
|
|
693
706
|
return;
|
|
694
707
|
}
|
|
695
708
|
try {
|
|
@@ -958,131 +971,124 @@ var AgentCanceledError = class extends Error {
|
|
|
958
971
|
}
|
|
959
972
|
};
|
|
960
973
|
|
|
961
|
-
// ../../packages/bridge-core/src/agents/
|
|
974
|
+
// ../../packages/bridge-core/src/agents/claude-event-parser.ts
|
|
962
975
|
function asRecord(value) {
|
|
963
976
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
964
977
|
}
|
|
965
978
|
function asText(value) {
|
|
966
979
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
967
980
|
}
|
|
968
|
-
function
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
const
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
981
|
+
function asNumber(value) {
|
|
982
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
983
|
+
}
|
|
984
|
+
function contentBlocks(record) {
|
|
985
|
+
const message = asRecord(record.message);
|
|
986
|
+
return Array.isArray(message?.content) ? message.content.flatMap((block) => {
|
|
987
|
+
const parsed = asRecord(block);
|
|
988
|
+
return parsed === void 0 ? [] : [parsed];
|
|
989
|
+
}) : [];
|
|
990
|
+
}
|
|
991
|
+
function toolSummary(name, input) {
|
|
992
|
+
return asText(input.description) ?? asText(input.command) ?? asText(input.file_path) ?? asText(input.path) ?? (name === "Bash" ? "Run command" : void 0);
|
|
993
|
+
}
|
|
994
|
+
function filePath(input) {
|
|
995
|
+
return asText(input.file_path) ?? asText(input.path) ?? asText(input.notebook_path);
|
|
996
|
+
}
|
|
997
|
+
var ClaudeEventParser = class {
|
|
998
|
+
#defaultCwd;
|
|
999
|
+
#tools = /* @__PURE__ */ new Map();
|
|
1000
|
+
#sessionId;
|
|
1001
|
+
constructor(defaultCwd = "") {
|
|
1002
|
+
this.#defaultCwd = defaultCwd;
|
|
1003
|
+
}
|
|
1004
|
+
parse(line) {
|
|
1005
|
+
const trimmed = line.trim();
|
|
1006
|
+
if (!trimmed) return [];
|
|
1007
|
+
let value;
|
|
1008
|
+
try {
|
|
1009
|
+
value = JSON.parse(trimmed);
|
|
1010
|
+
} catch {
|
|
1011
|
+
return [{ type: "warning", text: trimmed }];
|
|
1012
|
+
}
|
|
1013
|
+
const record = asRecord(value);
|
|
1014
|
+
if (!record) return [{ type: "message", text: trimmed }];
|
|
1015
|
+
const events = [];
|
|
1016
|
+
const foundSession = asText(record.session_id) ?? asText(record.sessionId);
|
|
1017
|
+
if (foundSession !== void 0 && foundSession !== this.#sessionId) {
|
|
1018
|
+
this.#sessionId = foundSession;
|
|
1019
|
+
events.push({ type: "session", sessionId: foundSession });
|
|
1020
|
+
}
|
|
1021
|
+
const type = asText(record.type) ?? "unknown";
|
|
1022
|
+
if (type === "system") {
|
|
1023
|
+
const subtype = asText(record.subtype);
|
|
1024
|
+
if (subtype) events.push({ type: "phase", name: subtype });
|
|
1025
|
+
return events;
|
|
1026
|
+
}
|
|
1027
|
+
if (type === "assistant") {
|
|
1028
|
+
for (const block of contentBlocks(record)) {
|
|
1029
|
+
if (block.type === "text") {
|
|
1030
|
+
const text = asText(block.text);
|
|
1031
|
+
if (text) events.push({ type: "message", text });
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
1034
|
+
if (block.type !== "tool_use") continue;
|
|
1035
|
+
const name = asText(block.name) ?? "tool";
|
|
1036
|
+
const id = asText(block.id);
|
|
1037
|
+
const input = asRecord(block.input) ?? {};
|
|
1038
|
+
if (id) this.#tools.set(id, name);
|
|
1039
|
+
const summary = toolSummary(name, input);
|
|
1040
|
+
events.push(
|
|
1041
|
+
summary ? { type: "tool_start", name, summary } : { type: "tool_start", name }
|
|
1042
|
+
);
|
|
1043
|
+
const command = name === "Bash" ? asText(input.command) : void 0;
|
|
1044
|
+
if (command) {
|
|
1045
|
+
events.push({ type: "command", command, cwd: this.#defaultCwd });
|
|
1046
|
+
}
|
|
1047
|
+
const path = filePath(input);
|
|
1048
|
+
if (path && ["Edit", "Write", "NotebookEdit"].includes(name)) {
|
|
1049
|
+
events.push({ type: "file_hint", path });
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return events;
|
|
1053
|
+
}
|
|
1054
|
+
if (type === "user") {
|
|
1055
|
+
for (const block of contentBlocks(record)) {
|
|
1056
|
+
if (block.type !== "tool_result") continue;
|
|
1057
|
+
const id = asText(block.tool_use_id);
|
|
1058
|
+
const name = (id ? this.#tools.get(id) : void 0) ?? "tool";
|
|
1059
|
+
if (id) this.#tools.delete(id);
|
|
1060
|
+
events.push({ type: "tool_end", name, ok: block.is_error !== true });
|
|
1061
|
+
}
|
|
1062
|
+
return events;
|
|
1024
1063
|
}
|
|
1025
|
-
if (
|
|
1026
|
-
const
|
|
1027
|
-
if (
|
|
1064
|
+
if (type === "result") {
|
|
1065
|
+
const usage = asRecord(record.usage);
|
|
1066
|
+
if (usage) {
|
|
1067
|
+
const cachedInputTokens = asNumber(usage.cache_read_input_tokens);
|
|
1028
1068
|
events.push({
|
|
1029
|
-
type: "
|
|
1030
|
-
|
|
1031
|
-
|
|
1069
|
+
type: "usage",
|
|
1070
|
+
inputTokens: asNumber(usage.input_tokens) + asNumber(usage.cache_creation_input_tokens) + cachedInputTokens,
|
|
1071
|
+
outputTokens: asNumber(usage.output_tokens),
|
|
1072
|
+
...cachedInputTokens > 0 ? { cachedInputTokens } : {}
|
|
1032
1073
|
});
|
|
1033
1074
|
}
|
|
1075
|
+
const result = asText(record.result);
|
|
1076
|
+
const subtype = asText(record.subtype);
|
|
1077
|
+
if (record.is_error === true || subtype?.startsWith("error") === true) {
|
|
1078
|
+
events.push({ type: "error", text: result ?? "Claude reported an error" });
|
|
1079
|
+
} else {
|
|
1080
|
+
events.push(result ? { type: "complete", summary: result } : { type: "complete" });
|
|
1081
|
+
}
|
|
1082
|
+
return events;
|
|
1034
1083
|
}
|
|
1035
|
-
for (const path of itemFiles(item)) events.push({ type: "file_hint", path });
|
|
1036
|
-
const exitCode = typeof item.exit_code === "number" ? item.exit_code : void 0;
|
|
1037
|
-
events.push({ type: "tool_end", name: itemType, ok: exitCode === void 0 || exitCode === 0 });
|
|
1038
1084
|
return events;
|
|
1039
1085
|
}
|
|
1040
|
-
const message = asText(record.message);
|
|
1041
|
-
if (message) events.push({ type: "message", text: message });
|
|
1042
|
-
return events;
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
// ../../packages/bridge-core/src/agents/codex-adapter.ts
|
|
1046
|
-
import { spawn as spawn2 } from "node:child_process";
|
|
1047
|
-
|
|
1048
|
-
// ../../packages/bridge-core/src/agents/async-queue.ts
|
|
1049
|
-
var AsyncQueue = class {
|
|
1050
|
-
#values = [];
|
|
1051
|
-
#waiters = [];
|
|
1052
|
-
#ended = false;
|
|
1053
|
-
#error;
|
|
1054
|
-
push(value) {
|
|
1055
|
-
if (this.#ended) return;
|
|
1056
|
-
const waiter = this.#waiters.shift();
|
|
1057
|
-
if (waiter) waiter.resolve({ value, done: false });
|
|
1058
|
-
else this.#values.push(value);
|
|
1059
|
-
}
|
|
1060
|
-
end(error) {
|
|
1061
|
-
if (this.#ended) return;
|
|
1062
|
-
this.#ended = true;
|
|
1063
|
-
this.#error = error;
|
|
1064
|
-
for (const waiter of this.#waiters.splice(0)) {
|
|
1065
|
-
if (error !== void 0) waiter.reject(error);
|
|
1066
|
-
else waiter.resolve({ value: void 0, done: true });
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
[Symbol.asyncIterator]() {
|
|
1070
|
-
return {
|
|
1071
|
-
next: async () => {
|
|
1072
|
-
const value = this.#values.shift();
|
|
1073
|
-
if (value !== void 0) return { value, done: false };
|
|
1074
|
-
if (this.#ended) {
|
|
1075
|
-
if (this.#error !== void 0) throw this.#error;
|
|
1076
|
-
return { value: void 0, done: true };
|
|
1077
|
-
}
|
|
1078
|
-
return await new Promise((resolve9, reject) => {
|
|
1079
|
-
this.#waiters.push({ resolve: resolve9, reject });
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
};
|
|
1083
|
-
}
|
|
1084
1086
|
};
|
|
1085
1087
|
|
|
1088
|
+
// ../../packages/bridge-core/src/agents/claude-adapter.ts
|
|
1089
|
+
import { execFile, spawn as spawn2 } from "node:child_process";
|
|
1090
|
+
import { promisify } from "node:util";
|
|
1091
|
+
|
|
1086
1092
|
// ../../packages/bridge-core/src/runtime/managed-process.ts
|
|
1087
1093
|
import { spawn } from "node:child_process";
|
|
1088
1094
|
function replacePortPlaceholder(value, port) {
|
|
@@ -1184,65 +1190,527 @@ async function startManagedProcess(options) {
|
|
|
1184
1190
|
if (executable === void 0) {
|
|
1185
1191
|
throw new Error("Managed dev command is empty");
|
|
1186
1192
|
}
|
|
1187
|
-
const detached = process.platform !== "win32";
|
|
1188
|
-
const child = spawn(
|
|
1189
|
-
replacePortPlaceholder(executable, options.upstreamPort),
|
|
1190
|
-
rawArguments.map((argument) => replacePortPlaceholder(argument, options.upstreamPort)),
|
|
1191
|
-
{
|
|
1192
|
-
cwd: options.cwd,
|
|
1193
|
-
env: {
|
|
1194
|
-
...options.environment ?? process.env,
|
|
1195
|
-
HOST: "0.0.0.0",
|
|
1196
|
-
PORT: String(options.upstreamPort)
|
|
1197
|
-
},
|
|
1198
|
-
detached,
|
|
1199
|
-
stdio: [
|
|
1200
|
-
"inherit",
|
|
1201
|
-
options.stdout === void 0 ? "inherit" : "pipe",
|
|
1202
|
-
options.stderr === void 0 ? "inherit" : "pipe"
|
|
1203
|
-
],
|
|
1204
|
-
windowsHide: true
|
|
1193
|
+
const detached = process.platform !== "win32";
|
|
1194
|
+
const child = spawn(
|
|
1195
|
+
replacePortPlaceholder(executable, options.upstreamPort),
|
|
1196
|
+
rawArguments.map((argument) => replacePortPlaceholder(argument, options.upstreamPort)),
|
|
1197
|
+
{
|
|
1198
|
+
cwd: options.cwd,
|
|
1199
|
+
env: {
|
|
1200
|
+
...options.environment ?? process.env,
|
|
1201
|
+
HOST: "0.0.0.0",
|
|
1202
|
+
PORT: String(options.upstreamPort)
|
|
1203
|
+
},
|
|
1204
|
+
detached,
|
|
1205
|
+
stdio: [
|
|
1206
|
+
"inherit",
|
|
1207
|
+
options.stdout === void 0 ? "inherit" : "pipe",
|
|
1208
|
+
options.stderr === void 0 ? "inherit" : "pipe"
|
|
1209
|
+
],
|
|
1210
|
+
windowsHide: true
|
|
1211
|
+
}
|
|
1212
|
+
);
|
|
1213
|
+
const removeEmergencyExitHook = installEmergencyChildExitHook(child);
|
|
1214
|
+
const exit = new Promise(
|
|
1215
|
+
(resolve9) => {
|
|
1216
|
+
child.once("exit", (code, signal) => {
|
|
1217
|
+
resolve9({ code, signal });
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
);
|
|
1221
|
+
if (child.stdout !== null && options.stdout !== void 0) {
|
|
1222
|
+
child.stdout.pipe(options.stdout, { end: false });
|
|
1223
|
+
}
|
|
1224
|
+
if (child.stderr !== null && options.stderr !== void 0) {
|
|
1225
|
+
child.stderr.pipe(options.stderr, { end: false });
|
|
1226
|
+
}
|
|
1227
|
+
try {
|
|
1228
|
+
await new Promise((resolve9, reject) => {
|
|
1229
|
+
child.once("spawn", resolve9);
|
|
1230
|
+
child.once("error", reject);
|
|
1231
|
+
});
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
removeEmergencyExitHook();
|
|
1234
|
+
throw error;
|
|
1235
|
+
}
|
|
1236
|
+
let stopPromise;
|
|
1237
|
+
return {
|
|
1238
|
+
child,
|
|
1239
|
+
exit,
|
|
1240
|
+
stop() {
|
|
1241
|
+
stopPromise ??= terminateChildProcessTree(
|
|
1242
|
+
child,
|
|
1243
|
+
options.killGraceMs
|
|
1244
|
+
).finally(removeEmergencyExitHook);
|
|
1245
|
+
return stopPromise;
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// ../../packages/bridge-core/src/agents/async-queue.ts
|
|
1251
|
+
var AsyncQueue = class {
|
|
1252
|
+
#values = [];
|
|
1253
|
+
#waiters = [];
|
|
1254
|
+
#ended = false;
|
|
1255
|
+
#error;
|
|
1256
|
+
push(value) {
|
|
1257
|
+
if (this.#ended) return;
|
|
1258
|
+
const waiter = this.#waiters.shift();
|
|
1259
|
+
if (waiter) waiter.resolve({ value, done: false });
|
|
1260
|
+
else this.#values.push(value);
|
|
1261
|
+
}
|
|
1262
|
+
end(error) {
|
|
1263
|
+
if (this.#ended) return;
|
|
1264
|
+
this.#ended = true;
|
|
1265
|
+
this.#error = error;
|
|
1266
|
+
for (const waiter of this.#waiters.splice(0)) {
|
|
1267
|
+
if (error !== void 0) waiter.reject(error);
|
|
1268
|
+
else waiter.resolve({ value: void 0, done: true });
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
[Symbol.asyncIterator]() {
|
|
1272
|
+
return {
|
|
1273
|
+
next: async () => {
|
|
1274
|
+
const value = this.#values.shift();
|
|
1275
|
+
if (value !== void 0) return { value, done: false };
|
|
1276
|
+
if (this.#ended) {
|
|
1277
|
+
if (this.#error !== void 0) throw this.#error;
|
|
1278
|
+
return { value: void 0, done: true };
|
|
1279
|
+
}
|
|
1280
|
+
return await new Promise((resolve9, reject) => {
|
|
1281
|
+
this.#waiters.push({ resolve: resolve9, reject });
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
|
|
1288
|
+
// ../../packages/bridge-core/src/agents/claude-adapter.ts
|
|
1289
|
+
var execFileAsync = promisify(execFile);
|
|
1290
|
+
var INHERITED_ENVIRONMENT = [
|
|
1291
|
+
"PATH",
|
|
1292
|
+
"HOME",
|
|
1293
|
+
"USER",
|
|
1294
|
+
"LOGNAME",
|
|
1295
|
+
"SHELL",
|
|
1296
|
+
"LANG",
|
|
1297
|
+
"LC_ALL",
|
|
1298
|
+
"TERM",
|
|
1299
|
+
"TMPDIR",
|
|
1300
|
+
"XDG_CONFIG_HOME",
|
|
1301
|
+
"XDG_DATA_HOME",
|
|
1302
|
+
"XDG_STATE_HOME",
|
|
1303
|
+
"ANTHROPIC_API_KEY",
|
|
1304
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
1305
|
+
"HTTPS_PROXY",
|
|
1306
|
+
"HTTP_PROXY",
|
|
1307
|
+
"NO_PROXY",
|
|
1308
|
+
"USERPROFILE",
|
|
1309
|
+
"APPDATA",
|
|
1310
|
+
"LOCALAPPDATA",
|
|
1311
|
+
"SystemRoot",
|
|
1312
|
+
"COMSPEC",
|
|
1313
|
+
"PATHEXT"
|
|
1314
|
+
];
|
|
1315
|
+
var EMPTY_MCP_CONFIG = JSON.stringify({ mcpServers: {} });
|
|
1316
|
+
var CLAUDE_SETTINGS = JSON.stringify({
|
|
1317
|
+
permissions: {
|
|
1318
|
+
disableBypassPermissionsMode: "disable",
|
|
1319
|
+
deny: [
|
|
1320
|
+
"Read(./.env)",
|
|
1321
|
+
"Read(./.env.*)",
|
|
1322
|
+
"Read(./**/*.pem)",
|
|
1323
|
+
"Read(./**/*.key)",
|
|
1324
|
+
"Edit(./.git/**)",
|
|
1325
|
+
"Edit(./.visualdev/runtime/**)",
|
|
1326
|
+
"Edit(./node_modules/**)"
|
|
1327
|
+
]
|
|
1328
|
+
},
|
|
1329
|
+
sandbox: {
|
|
1330
|
+
enabled: true,
|
|
1331
|
+
autoAllowBashIfSandboxed: true,
|
|
1332
|
+
allowUnsandboxedCommands: false,
|
|
1333
|
+
network: { strictAllowlist: true }
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
function processEnv(overrides) {
|
|
1337
|
+
const environment = {};
|
|
1338
|
+
for (const key of INHERITED_ENVIRONMENT) {
|
|
1339
|
+
const value = process.env[key];
|
|
1340
|
+
if (value !== void 0) environment[key] = value;
|
|
1341
|
+
}
|
|
1342
|
+
return { ...environment, ...overrides };
|
|
1343
|
+
}
|
|
1344
|
+
function splitLines(chunk, previous, onLine) {
|
|
1345
|
+
const combined = previous + chunk.toString();
|
|
1346
|
+
const lines = combined.split(/\r?\n/);
|
|
1347
|
+
const remainder = lines.pop() ?? "";
|
|
1348
|
+
for (const line of lines) onLine(line);
|
|
1349
|
+
return remainder;
|
|
1350
|
+
}
|
|
1351
|
+
var ClaudeAdapter = class {
|
|
1352
|
+
id = "claude";
|
|
1353
|
+
#executable;
|
|
1354
|
+
#killGraceMs;
|
|
1355
|
+
#model;
|
|
1356
|
+
#reasoningEffort;
|
|
1357
|
+
#rtkExecutable;
|
|
1358
|
+
#rtkVersion;
|
|
1359
|
+
constructor(options = {}) {
|
|
1360
|
+
this.#executable = options.executable ?? "claude";
|
|
1361
|
+
this.#killGraceMs = options.killGraceMs ?? 2e3;
|
|
1362
|
+
this.#model = options.model;
|
|
1363
|
+
this.#reasoningEffort = options.reasoningEffort;
|
|
1364
|
+
this.#rtkExecutable = options.rtkExecutable ?? "rtk";
|
|
1365
|
+
}
|
|
1366
|
+
#probeRtk(environment) {
|
|
1367
|
+
if (this.#rtkExecutable === false) return Promise.resolve(void 0);
|
|
1368
|
+
this.#rtkVersion ??= execFileAsync(this.#rtkExecutable, ["--version"], {
|
|
1369
|
+
encoding: "utf8",
|
|
1370
|
+
env: environment,
|
|
1371
|
+
timeout: 1e3,
|
|
1372
|
+
windowsHide: true,
|
|
1373
|
+
maxBuffer: 16 * 1024
|
|
1374
|
+
}).then(({ stdout }) => stdout.trim().split(/\r?\n/, 1)[0] || void 0).catch(() => void 0);
|
|
1375
|
+
return this.#rtkVersion;
|
|
1376
|
+
}
|
|
1377
|
+
async #runtimePrompt(input, environment) {
|
|
1378
|
+
if (this.#rtkExecutable === false) return input.prompt;
|
|
1379
|
+
const guidance = await this.#probeRtk(environment).then(
|
|
1380
|
+
(version) => version ? `RTK command proxy:
|
|
1381
|
+
- ${version} is installed and available in this runtime.
|
|
1382
|
+
- Prefix shell commands with RTK by default (for example: rtk git status, rtk rg <pattern>, rtk read <file>, rtk npm test).
|
|
1383
|
+
- Use the native command only when RTK has no suitable proxy or RTK execution fails. Do not spend time rediscovering or reinstalling RTK.` : `RTK command proxy:
|
|
1384
|
+
- RTK was not detected in this runtime. Use native repository commands directly and do not spend time searching for RTK.`
|
|
1385
|
+
);
|
|
1386
|
+
return `${input.prompt.trimEnd()}
|
|
1387
|
+
|
|
1388
|
+
${guidance}
|
|
1389
|
+
`;
|
|
1390
|
+
}
|
|
1391
|
+
#baseArgs() {
|
|
1392
|
+
return [
|
|
1393
|
+
"-p",
|
|
1394
|
+
"--output-format",
|
|
1395
|
+
"stream-json",
|
|
1396
|
+
"--verbose",
|
|
1397
|
+
"--permission-mode",
|
|
1398
|
+
"acceptEdits",
|
|
1399
|
+
"--strict-mcp-config",
|
|
1400
|
+
"--mcp-config",
|
|
1401
|
+
EMPTY_MCP_CONFIG,
|
|
1402
|
+
"--no-chrome",
|
|
1403
|
+
"--tools",
|
|
1404
|
+
"Read,Glob,Grep,Edit,Write,Bash",
|
|
1405
|
+
"--settings",
|
|
1406
|
+
CLAUDE_SETTINGS,
|
|
1407
|
+
...this.#model === void 0 ? [] : ["--model", this.#model],
|
|
1408
|
+
...this.#reasoningEffort === void 0 ? [] : ["--effort", this.#reasoningEffort]
|
|
1409
|
+
];
|
|
1410
|
+
}
|
|
1411
|
+
async probe() {
|
|
1412
|
+
return await new Promise((resolve9) => {
|
|
1413
|
+
const child = spawn2(this.#executable, ["--version"], {
|
|
1414
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1415
|
+
shell: false
|
|
1416
|
+
});
|
|
1417
|
+
let output2 = "";
|
|
1418
|
+
child.stdout?.on("data", (chunk) => {
|
|
1419
|
+
output2 += chunk.toString();
|
|
1420
|
+
});
|
|
1421
|
+
child.once("error", () => {
|
|
1422
|
+
resolve9({ available: false, supportsResume: true, structuredOutput: true });
|
|
1423
|
+
});
|
|
1424
|
+
child.once("close", (code) => {
|
|
1425
|
+
const match = output2.match(/(\d+\.\d+\.\d+(?:[-+][^\s]+)?)/);
|
|
1426
|
+
const capabilities = {
|
|
1427
|
+
available: code === 0,
|
|
1428
|
+
supportsResume: true,
|
|
1429
|
+
structuredOutput: true
|
|
1430
|
+
};
|
|
1431
|
+
if (match?.[1]) capabilities.version = match[1];
|
|
1432
|
+
resolve9(capabilities);
|
|
1433
|
+
});
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
async *run(input, signal) {
|
|
1437
|
+
yield* this.#execute(input, signal, this.#baseArgs());
|
|
1438
|
+
}
|
|
1439
|
+
async *resume(input, signal) {
|
|
1440
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(input.sessionId)) {
|
|
1441
|
+
throw new Error("Refusing to resume an invalid Claude session id");
|
|
1442
|
+
}
|
|
1443
|
+
yield* this.#execute(
|
|
1444
|
+
input,
|
|
1445
|
+
signal,
|
|
1446
|
+
[...this.#baseArgs(), "--resume", input.sessionId]
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
async *#execute(input, signal, args) {
|
|
1450
|
+
const queue = new AsyncQueue();
|
|
1451
|
+
const environment = processEnv(input.environment);
|
|
1452
|
+
const prompt = await this.#runtimePrompt(input, environment);
|
|
1453
|
+
const parser = new ClaudeEventParser(input.workspaceRoot);
|
|
1454
|
+
const child = spawn2(this.#executable, args, {
|
|
1455
|
+
cwd: input.workspaceRoot,
|
|
1456
|
+
env: environment,
|
|
1457
|
+
detached: process.platform !== "win32",
|
|
1458
|
+
shell: false,
|
|
1459
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1460
|
+
});
|
|
1461
|
+
const removeEmergencyExitHook = installEmergencyChildExitHook(child);
|
|
1462
|
+
let stdoutRemainder = "";
|
|
1463
|
+
let stderrRemainder = "";
|
|
1464
|
+
let timedOut = false;
|
|
1465
|
+
let aborted = signal.aborted;
|
|
1466
|
+
let termination;
|
|
1467
|
+
const requestTermination = () => {
|
|
1468
|
+
termination ??= terminateChildProcessTree(
|
|
1469
|
+
child,
|
|
1470
|
+
this.#killGraceMs
|
|
1471
|
+
).finally(removeEmergencyExitHook);
|
|
1472
|
+
return termination;
|
|
1473
|
+
};
|
|
1474
|
+
const timeout = setTimeout(() => {
|
|
1475
|
+
timedOut = true;
|
|
1476
|
+
void requestTermination();
|
|
1477
|
+
}, Math.max(1, input.maxRunMs));
|
|
1478
|
+
timeout.unref();
|
|
1479
|
+
const abort = () => {
|
|
1480
|
+
aborted = true;
|
|
1481
|
+
void requestTermination();
|
|
1482
|
+
};
|
|
1483
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
1484
|
+
if (signal.aborted) abort();
|
|
1485
|
+
child.stdout.on("data", (chunk) => {
|
|
1486
|
+
stdoutRemainder = splitLines(chunk, stdoutRemainder, (line) => {
|
|
1487
|
+
for (const event of parser.parse(line)) queue.push(event);
|
|
1488
|
+
});
|
|
1489
|
+
});
|
|
1490
|
+
child.stderr.on("data", (chunk) => {
|
|
1491
|
+
stderrRemainder = splitLines(chunk, stderrRemainder, (line) => {
|
|
1492
|
+
if (line.trim()) queue.push({ type: "warning", text: line });
|
|
1493
|
+
});
|
|
1494
|
+
});
|
|
1495
|
+
child.once("error", (error) => queue.end(error));
|
|
1496
|
+
child.once("close", (code, closeSignal) => {
|
|
1497
|
+
clearTimeout(timeout);
|
|
1498
|
+
signal.removeEventListener("abort", abort);
|
|
1499
|
+
void (async () => {
|
|
1500
|
+
await requestTermination();
|
|
1501
|
+
if (stdoutRemainder.trim()) {
|
|
1502
|
+
for (const event of parser.parse(stdoutRemainder)) queue.push(event);
|
|
1503
|
+
}
|
|
1504
|
+
if (stderrRemainder.trim()) queue.push({ type: "warning", text: stderrRemainder });
|
|
1505
|
+
if (timedOut) queue.end(new AgentTimeoutError());
|
|
1506
|
+
else if (aborted) queue.end(new AgentCanceledError());
|
|
1507
|
+
else if (code !== 0) {
|
|
1508
|
+
queue.end(
|
|
1509
|
+
new AgentProcessError(
|
|
1510
|
+
`Claude exited with code ${String(code)}`,
|
|
1511
|
+
code,
|
|
1512
|
+
closeSignal
|
|
1513
|
+
)
|
|
1514
|
+
);
|
|
1515
|
+
} else {
|
|
1516
|
+
queue.end();
|
|
1517
|
+
}
|
|
1518
|
+
})().catch((error) => queue.end(error));
|
|
1519
|
+
});
|
|
1520
|
+
child.stdin.on("error", (error) => {
|
|
1521
|
+
if (error.code !== "EPIPE") queue.end(error);
|
|
1522
|
+
});
|
|
1523
|
+
child.stdin.end(prompt);
|
|
1524
|
+
try {
|
|
1525
|
+
for await (const event of queue) yield event;
|
|
1526
|
+
} finally {
|
|
1527
|
+
clearTimeout(timeout);
|
|
1528
|
+
signal.removeEventListener("abort", abort);
|
|
1529
|
+
try {
|
|
1530
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
1531
|
+
await requestTermination();
|
|
1532
|
+
} else if (termination) {
|
|
1533
|
+
await termination;
|
|
1534
|
+
}
|
|
1535
|
+
} finally {
|
|
1536
|
+
removeEmergencyExitHook();
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
|
|
1542
|
+
// ../../packages/bridge-core/src/agents/codex-event-parser.ts
|
|
1543
|
+
function asRecord2(value) {
|
|
1544
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1545
|
+
}
|
|
1546
|
+
function asText2(value) {
|
|
1547
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1548
|
+
}
|
|
1549
|
+
function sessionId(record) {
|
|
1550
|
+
const thread = asRecord2(record.thread);
|
|
1551
|
+
return asText2(record.thread_id) ?? asText2(record.threadId) ?? asText2(record.session_id) ?? asText2(record.sessionId) ?? (thread ? asText2(thread.id) : void 0);
|
|
1552
|
+
}
|
|
1553
|
+
function itemFiles(item) {
|
|
1554
|
+
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
1555
|
+
const files = changes.flatMap((change) => {
|
|
1556
|
+
const record = asRecord2(change);
|
|
1557
|
+
if (!record) return [];
|
|
1558
|
+
return [asText2(record.path) ?? asText2(record.file_path) ?? asText2(record.filePath)].filter(
|
|
1559
|
+
(path) => path !== void 0
|
|
1560
|
+
);
|
|
1561
|
+
});
|
|
1562
|
+
const direct = asText2(item.path) ?? asText2(item.file_path) ?? asText2(item.filePath);
|
|
1563
|
+
if (direct) files.push(direct);
|
|
1564
|
+
return [...new Set(files)];
|
|
1565
|
+
}
|
|
1566
|
+
function stringArray(value) {
|
|
1567
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : void 0;
|
|
1568
|
+
}
|
|
1569
|
+
function formatArgv(argv) {
|
|
1570
|
+
return argv.map((argument) => /^[A-Za-z0-9_./:=@%+,-]+$/u.test(argument) ? argument : JSON.stringify(argument)).join(" ");
|
|
1571
|
+
}
|
|
1572
|
+
function isDirectExecItem(item) {
|
|
1573
|
+
return item.type === "mcp_tool_call" && item.server === "visual_remote_exec" && item.tool === "run_readonly";
|
|
1574
|
+
}
|
|
1575
|
+
function directExecSummary(item) {
|
|
1576
|
+
const arguments_ = asRecord2(item.arguments);
|
|
1577
|
+
const commands = Array.isArray(arguments_?.commands) ? arguments_.commands : [];
|
|
1578
|
+
const summaries = commands.flatMap((candidate) => {
|
|
1579
|
+
const command = asRecord2(candidate);
|
|
1580
|
+
const argv = stringArray(command?.argv);
|
|
1581
|
+
return argv === void 0 ? [] : [formatArgv(argv)];
|
|
1582
|
+
});
|
|
1583
|
+
return summaries.length === 0 ? void 0 : summaries.join(" \xB7 ");
|
|
1584
|
+
}
|
|
1585
|
+
function directExecResults(item, defaultCwd) {
|
|
1586
|
+
const result = asRecord2(item.result);
|
|
1587
|
+
const structured = asRecord2(result?.structured_content ?? result?.structuredContent);
|
|
1588
|
+
const results = Array.isArray(structured?.results) ? structured.results : [];
|
|
1589
|
+
return results.flatMap((candidate) => {
|
|
1590
|
+
const command = asRecord2(candidate);
|
|
1591
|
+
const argv = stringArray(command?.argv);
|
|
1592
|
+
if (argv === void 0) return [];
|
|
1593
|
+
const exitCode = typeof command?.exitCode === "number" ? command.exitCode : void 0;
|
|
1594
|
+
const durationMs = typeof command?.durationMs === "number" ? command.durationMs : void 0;
|
|
1595
|
+
return [{
|
|
1596
|
+
command: formatArgv(argv),
|
|
1597
|
+
cwd: asText2(command?.cwd) ?? defaultCwd,
|
|
1598
|
+
ok: exitCode === 0,
|
|
1599
|
+
...exitCode === void 0 ? {} : { exitCode },
|
|
1600
|
+
...durationMs === void 0 ? {} : { durationMs },
|
|
1601
|
+
...typeof command?.usedRtk === "boolean" ? { usedRtk: command.usedRtk } : {},
|
|
1602
|
+
...typeof command?.timedOut === "boolean" ? { timedOut: command.timedOut } : {},
|
|
1603
|
+
...typeof command?.truncated === "boolean" ? { truncated: command.truncated } : {}
|
|
1604
|
+
}];
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
function normalizedUsage(record) {
|
|
1608
|
+
const result = asRecord2(record.result);
|
|
1609
|
+
const usage = asRecord2(record.usage) ?? (result ? asRecord2(result.usage) : void 0);
|
|
1610
|
+
if (usage === void 0) return void 0;
|
|
1611
|
+
const inputTokens = usage.input_tokens ?? usage.inputTokens;
|
|
1612
|
+
const outputTokens = usage.output_tokens ?? usage.outputTokens;
|
|
1613
|
+
const cachedInputTokens = usage.cached_input_tokens ?? usage.cachedInputTokens;
|
|
1614
|
+
if (typeof inputTokens !== "number" || typeof outputTokens !== "number") {
|
|
1615
|
+
return void 0;
|
|
1616
|
+
}
|
|
1617
|
+
return {
|
|
1618
|
+
type: "usage",
|
|
1619
|
+
inputTokens,
|
|
1620
|
+
outputTokens,
|
|
1621
|
+
...typeof cachedInputTokens === "number" ? { cachedInputTokens } : {}
|
|
1622
|
+
};
|
|
1623
|
+
}
|
|
1624
|
+
function parseCodexJsonLine(line, defaultCwd = "") {
|
|
1625
|
+
const trimmed = line.trim();
|
|
1626
|
+
if (!trimmed) return [];
|
|
1627
|
+
let value;
|
|
1628
|
+
try {
|
|
1629
|
+
value = JSON.parse(trimmed);
|
|
1630
|
+
} catch {
|
|
1631
|
+
return [{ type: "warning", text: trimmed }];
|
|
1632
|
+
}
|
|
1633
|
+
const record = asRecord2(value);
|
|
1634
|
+
if (!record) return [{ type: "message", text: trimmed }];
|
|
1635
|
+
const type = asText2(record.type) ?? "unknown";
|
|
1636
|
+
const events = [];
|
|
1637
|
+
const foundSession = sessionId(record);
|
|
1638
|
+
if (foundSession) events.push({ type: "session", sessionId: foundSession });
|
|
1639
|
+
if (type === "thread.started" || type === "thread.created") return events;
|
|
1640
|
+
if (type === "turn.started") return [...events, { type: "phase", name: "turn.started" }];
|
|
1641
|
+
if (type === "turn.completed") {
|
|
1642
|
+
const result = asRecord2(record.result);
|
|
1643
|
+
const summary = asText2(record.summary) ?? (result ? asText2(result.summary) : void 0);
|
|
1644
|
+
const usage = normalizedUsage(record);
|
|
1645
|
+
return [
|
|
1646
|
+
...events,
|
|
1647
|
+
...usage === void 0 ? [] : [usage],
|
|
1648
|
+
summary ? { type: "complete", summary } : { type: "complete" }
|
|
1649
|
+
];
|
|
1650
|
+
}
|
|
1651
|
+
if (type === "turn.failed" || type === "error") {
|
|
1652
|
+
const error = asRecord2(record.error);
|
|
1653
|
+
const text = asText2(record.message) ?? (error ? asText2(error.message) : void 0) ?? "Codex reported an error";
|
|
1654
|
+
return [...events, { type: "error", text }];
|
|
1655
|
+
}
|
|
1656
|
+
const item = asRecord2(record.item);
|
|
1657
|
+
if (type === "item.started" && item) {
|
|
1658
|
+
const itemType = asText2(item.type) ?? "item";
|
|
1659
|
+
const directExec = isDirectExecItem(item);
|
|
1660
|
+
const summary = directExec ? directExecSummary(item) : asText2(item.command) ?? asText2(item.text);
|
|
1661
|
+
const start = summary ? { type: "tool_start", name: directExec ? "direct_exec" : itemType, summary } : { type: "tool_start", name: directExec ? "direct_exec" : itemType };
|
|
1662
|
+
return [...events, start];
|
|
1663
|
+
}
|
|
1664
|
+
if (type === "item.completed" && item) {
|
|
1665
|
+
const itemType = asText2(item.type) ?? "item";
|
|
1666
|
+
if (itemType === "agent_message") {
|
|
1667
|
+
const text = asText2(item.text) ?? asText2(item.message);
|
|
1668
|
+
return text ? [...events, { type: "message", text }] : events;
|
|
1205
1669
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1670
|
+
if (itemType === "command_execution") {
|
|
1671
|
+
const command = asText2(item.command);
|
|
1672
|
+
if (command) {
|
|
1673
|
+
events.push({
|
|
1674
|
+
type: "command",
|
|
1675
|
+
command,
|
|
1676
|
+
cwd: asText2(item.cwd) ?? defaultCwd
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
const directResults = isDirectExecItem(item) ? directExecResults(item, defaultCwd) : [];
|
|
1681
|
+
for (const result of directResults) {
|
|
1682
|
+
events.push({
|
|
1683
|
+
type: "command",
|
|
1684
|
+
command: result.command,
|
|
1685
|
+
cwd: result.cwd,
|
|
1686
|
+
...result.exitCode === void 0 ? {} : { exitCode: result.exitCode },
|
|
1687
|
+
...result.durationMs === void 0 ? {} : { durationMs: result.durationMs },
|
|
1688
|
+
...result.usedRtk === void 0 ? {} : { usedRtk: result.usedRtk },
|
|
1689
|
+
...result.timedOut === void 0 ? {} : { timedOut: result.timedOut },
|
|
1690
|
+
...result.truncated === void 0 ? {} : { truncated: result.truncated }
|
|
1212
1691
|
});
|
|
1213
1692
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
}
|
|
1221
|
-
try {
|
|
1222
|
-
await new Promise((resolve9, reject) => {
|
|
1223
|
-
child.once("spawn", resolve9);
|
|
1224
|
-
child.once("error", reject);
|
|
1693
|
+
for (const path of itemFiles(item)) events.push({ type: "file_hint", path });
|
|
1694
|
+
const exitCode = typeof item.exit_code === "number" ? item.exit_code : void 0;
|
|
1695
|
+
events.push({
|
|
1696
|
+
type: "tool_end",
|
|
1697
|
+
name: isDirectExecItem(item) ? "direct_exec" : itemType,
|
|
1698
|
+
ok: directResults.length > 0 ? directResults.every((result) => result.ok) : exitCode === void 0 || exitCode === 0
|
|
1225
1699
|
});
|
|
1226
|
-
|
|
1227
|
-
removeEmergencyExitHook();
|
|
1228
|
-
throw error;
|
|
1700
|
+
return events;
|
|
1229
1701
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
exit,
|
|
1234
|
-
stop() {
|
|
1235
|
-
stopPromise ??= terminateChildProcessTree(
|
|
1236
|
-
child,
|
|
1237
|
-
options.killGraceMs
|
|
1238
|
-
).finally(removeEmergencyExitHook);
|
|
1239
|
-
return stopPromise;
|
|
1240
|
-
}
|
|
1241
|
-
};
|
|
1702
|
+
const message = asText2(record.message);
|
|
1703
|
+
if (message) events.push({ type: "message", text: message });
|
|
1704
|
+
return events;
|
|
1242
1705
|
}
|
|
1243
1706
|
|
|
1244
1707
|
// ../../packages/bridge-core/src/agents/codex-adapter.ts
|
|
1245
|
-
|
|
1708
|
+
import { execFile as execFile2, spawn as spawn3 } from "node:child_process";
|
|
1709
|
+
import { existsSync } from "node:fs";
|
|
1710
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1711
|
+
import { promisify as promisify2 } from "node:util";
|
|
1712
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
1713
|
+
var INHERITED_ENVIRONMENT2 = [
|
|
1246
1714
|
"PATH",
|
|
1247
1715
|
"HOME",
|
|
1248
1716
|
"USER",
|
|
@@ -1267,32 +1735,104 @@ var INHERITED_ENVIRONMENT = [
|
|
|
1267
1735
|
"COMSPEC",
|
|
1268
1736
|
"PATHEXT"
|
|
1269
1737
|
];
|
|
1270
|
-
function
|
|
1738
|
+
function processEnv2(overrides) {
|
|
1271
1739
|
const environment = {};
|
|
1272
|
-
for (const key of
|
|
1740
|
+
for (const key of INHERITED_ENVIRONMENT2) {
|
|
1273
1741
|
const value = process.env[key];
|
|
1274
1742
|
if (value !== void 0) environment[key] = value;
|
|
1275
1743
|
}
|
|
1276
1744
|
return { ...environment, ...overrides };
|
|
1277
1745
|
}
|
|
1278
|
-
function
|
|
1746
|
+
function splitLines2(chunk, previous, onLine) {
|
|
1279
1747
|
const combined = previous + chunk.toString();
|
|
1280
1748
|
const lines = combined.split(/\r?\n/);
|
|
1281
1749
|
const remainder = lines.pop() ?? "";
|
|
1282
1750
|
for (const line of lines) onLine(line);
|
|
1283
1751
|
return remainder;
|
|
1284
1752
|
}
|
|
1753
|
+
function defaultDirectExecMcpScript() {
|
|
1754
|
+
const candidates = [
|
|
1755
|
+
fileURLToPath2(new URL("./direct-exec-mcp.js", import.meta.url)),
|
|
1756
|
+
fileURLToPath2(
|
|
1757
|
+
new URL("../../../../apps/cli/dist/direct-exec-mcp.js", import.meta.url)
|
|
1758
|
+
)
|
|
1759
|
+
];
|
|
1760
|
+
return candidates.find((candidate) => existsSync(candidate));
|
|
1761
|
+
}
|
|
1285
1762
|
var CodexAdapter = class {
|
|
1286
1763
|
id = "codex";
|
|
1287
1764
|
#executable;
|
|
1288
1765
|
#killGraceMs;
|
|
1766
|
+
#model;
|
|
1767
|
+
#profile;
|
|
1768
|
+
#reasoningEffort;
|
|
1769
|
+
#rtkExecutable;
|
|
1770
|
+
#directExecMcpScript;
|
|
1771
|
+
#rtkVersion;
|
|
1289
1772
|
constructor(options = {}) {
|
|
1290
1773
|
this.#executable = options.executable ?? "codex";
|
|
1291
1774
|
this.#killGraceMs = options.killGraceMs ?? 2e3;
|
|
1775
|
+
this.#model = options.model;
|
|
1776
|
+
this.#profile = options.profile;
|
|
1777
|
+
this.#reasoningEffort = options.reasoningEffort;
|
|
1778
|
+
this.#rtkExecutable = options.rtkExecutable ?? "rtk";
|
|
1779
|
+
this.#directExecMcpScript = options.directExecMcpScript === false ? void 0 : options.directExecMcpScript ?? defaultDirectExecMcpScript();
|
|
1780
|
+
}
|
|
1781
|
+
#probeRtk(environment) {
|
|
1782
|
+
if (this.#rtkExecutable === false) return Promise.resolve(void 0);
|
|
1783
|
+
this.#rtkVersion ??= execFileAsync2(this.#rtkExecutable, ["--version"], {
|
|
1784
|
+
encoding: "utf8",
|
|
1785
|
+
env: environment,
|
|
1786
|
+
timeout: 1e3,
|
|
1787
|
+
windowsHide: true,
|
|
1788
|
+
maxBuffer: 16 * 1024
|
|
1789
|
+
}).then(({ stdout }) => stdout.trim().split(/\r?\n/, 1)[0] || void 0).catch(() => void 0);
|
|
1790
|
+
return this.#rtkVersion;
|
|
1791
|
+
}
|
|
1792
|
+
async #runtimePrompt(input, environment) {
|
|
1793
|
+
const commandGuidance = this.#rtkExecutable === false ? "" : await this.#probeRtk(environment).then((version) => version ? `RTK command proxy:
|
|
1794
|
+
- ${version} is installed and available in this runtime.
|
|
1795
|
+
- Prefix shell commands with RTK by default (for example: rtk git status, rtk rg <pattern>, rtk read <file>, rtk npm test).
|
|
1796
|
+
- Use the native command only when RTK has no suitable proxy or RTK execution fails. Do not spend time rediscovering or reinstalling RTK.` : `RTK command proxy:
|
|
1797
|
+
- RTK was not detected in this runtime. Use native repository commands directly and do not spend time searching for RTK.`);
|
|
1798
|
+
const directExecGuidance = this.#directExecMcpScript === void 0 ? "" : `Direct read-only command runner:
|
|
1799
|
+
- Use the visual_remote_exec run_readonly MCP tool (mcp__visual_remote_exec__run_readonly) for repository inspection by default: pwd, version checks, file listing/reading/search, and read-only Git status/diff/log/show.
|
|
1800
|
+
- Send argv arrays, batch independent reads in one tool call, and keep cwd at the registered workspace unless a known subdirectory is required.
|
|
1801
|
+
- The tool executes without a shell and applies RTK automatically when supported.
|
|
1802
|
+
- Use command_execution only for edits, tests/builds, or commands that genuinely require shell syntax. Do not retry a policy-rejected command through another shell unless the requested work requires that non-read-only operation.`;
|
|
1803
|
+
const guidance = [directExecGuidance, commandGuidance].filter(Boolean).join("\n\n");
|
|
1804
|
+
return guidance.length === 0 ? input.prompt : `${input.prompt.trimEnd()}
|
|
1805
|
+
|
|
1806
|
+
${guidance}
|
|
1807
|
+
`;
|
|
1808
|
+
}
|
|
1809
|
+
#directExecConfig(input) {
|
|
1810
|
+
if (this.#directExecMcpScript === void 0) return [];
|
|
1811
|
+
const serverArgs = [
|
|
1812
|
+
this.#directExecMcpScript,
|
|
1813
|
+
"--repo-root",
|
|
1814
|
+
input.repoRoot,
|
|
1815
|
+
"--workspace-root",
|
|
1816
|
+
input.workspaceRoot,
|
|
1817
|
+
...this.#rtkExecutable === false ? ["--no-rtk"] : ["--rtk", this.#rtkExecutable]
|
|
1818
|
+
];
|
|
1819
|
+
return [
|
|
1820
|
+
"-c",
|
|
1821
|
+
`mcp_servers.visual_remote_exec.command=${JSON.stringify(process.execPath)}`,
|
|
1822
|
+
"-c",
|
|
1823
|
+
`mcp_servers.visual_remote_exec.args=${JSON.stringify(serverArgs)}`
|
|
1824
|
+
];
|
|
1825
|
+
}
|
|
1826
|
+
#modelConfig() {
|
|
1827
|
+
return [
|
|
1828
|
+
...this.#profile === void 0 ? [] : ["--profile", this.#profile],
|
|
1829
|
+
...this.#model === void 0 ? [] : ["--model", this.#model],
|
|
1830
|
+
...this.#reasoningEffort === void 0 ? [] : ["-c", `model_reasoning_effort=${JSON.stringify(this.#reasoningEffort)}`]
|
|
1831
|
+
];
|
|
1292
1832
|
}
|
|
1293
1833
|
async probe() {
|
|
1294
1834
|
return await new Promise((resolve9) => {
|
|
1295
|
-
const child =
|
|
1835
|
+
const child = spawn3(this.#executable, ["--version"], {
|
|
1296
1836
|
stdio: ["ignore", "pipe", "ignore"],
|
|
1297
1837
|
shell: false
|
|
1298
1838
|
});
|
|
@@ -1324,7 +1864,9 @@ var CodexAdapter = class {
|
|
|
1324
1864
|
"-s",
|
|
1325
1865
|
"workspace-write",
|
|
1326
1866
|
"-C",
|
|
1327
|
-
input.
|
|
1867
|
+
input.workspaceRoot,
|
|
1868
|
+
...this.#modelConfig(),
|
|
1869
|
+
...this.#directExecConfig(input),
|
|
1328
1870
|
"-"
|
|
1329
1871
|
];
|
|
1330
1872
|
yield* this.#execute(input, signal, args);
|
|
@@ -1341,7 +1883,9 @@ var CodexAdapter = class {
|
|
|
1341
1883
|
"-s",
|
|
1342
1884
|
"workspace-write",
|
|
1343
1885
|
"-C",
|
|
1344
|
-
input.
|
|
1886
|
+
input.workspaceRoot,
|
|
1887
|
+
...this.#modelConfig(),
|
|
1888
|
+
...this.#directExecConfig(input),
|
|
1345
1889
|
"resume",
|
|
1346
1890
|
input.sessionId,
|
|
1347
1891
|
"-"
|
|
@@ -1350,9 +1894,11 @@ var CodexAdapter = class {
|
|
|
1350
1894
|
}
|
|
1351
1895
|
async *#execute(input, signal, args) {
|
|
1352
1896
|
const queue = new AsyncQueue();
|
|
1353
|
-
const
|
|
1354
|
-
|
|
1355
|
-
|
|
1897
|
+
const environment = processEnv2(input.environment);
|
|
1898
|
+
const prompt = await this.#runtimePrompt(input, environment);
|
|
1899
|
+
const child = spawn3(this.#executable, args, {
|
|
1900
|
+
cwd: input.workspaceRoot,
|
|
1901
|
+
env: environment,
|
|
1356
1902
|
detached: process.platform !== "win32",
|
|
1357
1903
|
shell: false,
|
|
1358
1904
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -1382,12 +1928,12 @@ var CodexAdapter = class {
|
|
|
1382
1928
|
signal.addEventListener("abort", abort, { once: true });
|
|
1383
1929
|
if (signal.aborted) abort();
|
|
1384
1930
|
child.stdout.on("data", (chunk) => {
|
|
1385
|
-
stdoutRemainder =
|
|
1386
|
-
for (const event of parseCodexJsonLine(line)) queue.push(event);
|
|
1931
|
+
stdoutRemainder = splitLines2(chunk, stdoutRemainder, (line) => {
|
|
1932
|
+
for (const event of parseCodexJsonLine(line, input.workspaceRoot)) queue.push(event);
|
|
1387
1933
|
});
|
|
1388
1934
|
});
|
|
1389
1935
|
child.stderr.on("data", (chunk) => {
|
|
1390
|
-
stderrRemainder =
|
|
1936
|
+
stderrRemainder = splitLines2(chunk, stderrRemainder, (line) => {
|
|
1391
1937
|
if (line.trim()) queue.push({ type: "warning", text: line });
|
|
1392
1938
|
});
|
|
1393
1939
|
});
|
|
@@ -1398,7 +1944,9 @@ var CodexAdapter = class {
|
|
|
1398
1944
|
void (async () => {
|
|
1399
1945
|
await requestTermination();
|
|
1400
1946
|
if (stdoutRemainder.trim()) {
|
|
1401
|
-
for (const event of parseCodexJsonLine(stdoutRemainder))
|
|
1947
|
+
for (const event of parseCodexJsonLine(stdoutRemainder, input.workspaceRoot)) {
|
|
1948
|
+
queue.push(event);
|
|
1949
|
+
}
|
|
1402
1950
|
}
|
|
1403
1951
|
if (stderrRemainder.trim()) queue.push({ type: "warning", text: stderrRemainder });
|
|
1404
1952
|
if (timedOut) queue.end(new AgentTimeoutError());
|
|
@@ -1419,7 +1967,7 @@ var CodexAdapter = class {
|
|
|
1419
1967
|
child.stdin.on("error", (error) => {
|
|
1420
1968
|
if (error.code !== "EPIPE") queue.end(error);
|
|
1421
1969
|
});
|
|
1422
|
-
child.stdin.end(
|
|
1970
|
+
child.stdin.end(prompt);
|
|
1423
1971
|
try {
|
|
1424
1972
|
for await (const event of queue) yield event;
|
|
1425
1973
|
} finally {
|
|
@@ -1438,6 +1986,9 @@ var CodexAdapter = class {
|
|
|
1438
1986
|
}
|
|
1439
1987
|
};
|
|
1440
1988
|
|
|
1989
|
+
// ../../packages/bridge-core/src/agents/direct-exec.ts
|
|
1990
|
+
var DEFAULT_OUTPUT_BYTES = 64 * 1024;
|
|
1991
|
+
|
|
1441
1992
|
// ../../packages/bridge-core/src/config/loader.ts
|
|
1442
1993
|
import { readFile, realpath, stat as stat2 } from "node:fs/promises";
|
|
1443
1994
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
@@ -1448,6 +1999,7 @@ import { ZodError } from "zod";
|
|
|
1448
1999
|
import { z } from "zod";
|
|
1449
2000
|
var servicePortSchema = z.number().int().min(10001).max(65535);
|
|
1450
2001
|
var commandSchema = z.array(z.string().min(1)).min(1);
|
|
2002
|
+
var environmentVariableSchema = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "Expected an environment variable name");
|
|
1451
2003
|
var readySchema = z.object({
|
|
1452
2004
|
path: z.string().startsWith("/").default("/"),
|
|
1453
2005
|
timeoutMs: z.number().int().positive().default(6e4)
|
|
@@ -1476,9 +2028,38 @@ var visualDevConfigSchema = z.object({
|
|
|
1476
2028
|
}).strict(),
|
|
1477
2029
|
agent: z.object({
|
|
1478
2030
|
adapter: z.enum(["codex", "claude", "opencode"]),
|
|
2031
|
+
model: z.string().trim().min(1).optional(),
|
|
2032
|
+
reasoningEffort: z.enum(["minimal", "low", "medium", "high", "xhigh", "max"]).optional(),
|
|
2033
|
+
profile: z.string().trim().regex(
|
|
2034
|
+
/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/,
|
|
2035
|
+
"Expected a Codex profile name"
|
|
2036
|
+
).optional(),
|
|
2037
|
+
inheritEnv: z.array(environmentVariableSchema).default([]),
|
|
1479
2038
|
maxRunMs: z.number().int().positive(),
|
|
1480
2039
|
resumeMode: z.enum(["auto", "new"]).default("auto")
|
|
1481
|
-
}).strict(),
|
|
2040
|
+
}).strict().superRefine((agent, context) => {
|
|
2041
|
+
if (agent.adapter === "claude" && agent.reasoningEffort === "minimal") {
|
|
2042
|
+
context.addIssue({
|
|
2043
|
+
code: "custom",
|
|
2044
|
+
path: ["reasoningEffort"],
|
|
2045
|
+
message: "minimal reasoning effort is only supported by the Codex adapter"
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
if (agent.adapter === "codex" && agent.reasoningEffort === "max") {
|
|
2049
|
+
context.addIssue({
|
|
2050
|
+
code: "custom",
|
|
2051
|
+
path: ["reasoningEffort"],
|
|
2052
|
+
message: "max reasoning effort is only supported by the Claude adapter"
|
|
2053
|
+
});
|
|
2054
|
+
}
|
|
2055
|
+
if (agent.adapter !== "codex" && agent.profile !== void 0) {
|
|
2056
|
+
context.addIssue({
|
|
2057
|
+
code: "custom",
|
|
2058
|
+
path: ["profile"],
|
|
2059
|
+
message: "agent.profile is only supported by the Codex adapter"
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
}),
|
|
1482
2063
|
queue: z.object({
|
|
1483
2064
|
maxPending: z.number().int().positive()
|
|
1484
2065
|
}).strict(),
|
|
@@ -1522,6 +2103,7 @@ function createDefaultConfig(projectId) {
|
|
|
1522
2103
|
},
|
|
1523
2104
|
agent: {
|
|
1524
2105
|
adapter: "codex",
|
|
2106
|
+
inheritEnv: [],
|
|
1525
2107
|
maxRunMs: 9e5,
|
|
1526
2108
|
resumeMode: "auto"
|
|
1527
2109
|
},
|
|
@@ -1594,17 +2176,17 @@ function mergeConfigValues(base, override) {
|
|
|
1594
2176
|
}
|
|
1595
2177
|
return merged;
|
|
1596
2178
|
}
|
|
1597
|
-
async function readYamlMapping(
|
|
2179
|
+
async function readYamlMapping(filePath2, required) {
|
|
1598
2180
|
let source;
|
|
1599
2181
|
try {
|
|
1600
|
-
source = await readFile(
|
|
2182
|
+
source = await readFile(filePath2, "utf8");
|
|
1601
2183
|
} catch (error) {
|
|
1602
2184
|
if (!required && typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
1603
2185
|
return void 0;
|
|
1604
2186
|
}
|
|
1605
|
-
throw new VisualDevConfigError(`Unable to read config: ${
|
|
2187
|
+
throw new VisualDevConfigError(`Unable to read config: ${filePath2}`, {
|
|
1606
2188
|
cause: error,
|
|
1607
|
-
filePath
|
|
2189
|
+
filePath: filePath2
|
|
1608
2190
|
});
|
|
1609
2191
|
}
|
|
1610
2192
|
try {
|
|
@@ -1617,9 +2199,9 @@ async function readYamlMapping(filePath, required) {
|
|
|
1617
2199
|
}
|
|
1618
2200
|
return value;
|
|
1619
2201
|
} catch (error) {
|
|
1620
|
-
throw new VisualDevConfigError(`Invalid YAML in ${
|
|
2202
|
+
throw new VisualDevConfigError(`Invalid YAML in ${filePath2}`, {
|
|
1621
2203
|
cause: error,
|
|
1622
|
-
filePath
|
|
2204
|
+
filePath: filePath2
|
|
1623
2205
|
});
|
|
1624
2206
|
}
|
|
1625
2207
|
}
|
|
@@ -1758,10 +2340,10 @@ var RevertConflictError = class extends RepositorySafetyError {
|
|
|
1758
2340
|
};
|
|
1759
2341
|
|
|
1760
2342
|
// ../../packages/bridge-core/src/git/git-command.ts
|
|
1761
|
-
import { spawn as
|
|
2343
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
1762
2344
|
async function runGit(args, options) {
|
|
1763
2345
|
return await new Promise((resolve9, reject) => {
|
|
1764
|
-
const child =
|
|
2346
|
+
const child = spawn4("git", [...args], {
|
|
1765
2347
|
cwd: options.cwd,
|
|
1766
2348
|
env: { ...process.env, ...options.env ?? {} },
|
|
1767
2349
|
shell: false,
|
|
@@ -1867,6 +2449,20 @@ function isWithin(root, candidate) {
|
|
|
1867
2449
|
const path = relative2(root, candidate);
|
|
1868
2450
|
return path === "" || !path.startsWith(`..${sep2}`) && path !== ".." && !isAbsolute2(path);
|
|
1869
2451
|
}
|
|
2452
|
+
function rebaseWorkspacePatterns(repoRoot, workspaceRoot, patterns) {
|
|
2453
|
+
const repository = resolve2(repoRoot);
|
|
2454
|
+
const workspace = resolve2(workspaceRoot);
|
|
2455
|
+
if (!isWithin(repository, workspace)) {
|
|
2456
|
+
throw new PathSafetyError(
|
|
2457
|
+
"PATH_OUTSIDE_REPOSITORY",
|
|
2458
|
+
workspaceRoot,
|
|
2459
|
+
`Workspace is outside the repository: ${workspaceRoot}`
|
|
2460
|
+
);
|
|
2461
|
+
}
|
|
2462
|
+
const prefix = repositoryRelative(repository, workspace);
|
|
2463
|
+
if (!prefix) return [...patterns];
|
|
2464
|
+
return patterns.map((pattern) => normalizeSlashes(`${prefix}/${pattern}`));
|
|
2465
|
+
}
|
|
1870
2466
|
var PathPolicy = class {
|
|
1871
2467
|
repoRoot;
|
|
1872
2468
|
allowedPatterns;
|
|
@@ -2710,6 +3306,9 @@ var TERMINAL = /* @__PURE__ */ new Set(["accepted", "reverted", "failed", "cance
|
|
|
2710
3306
|
function isActiveTaskStatus(status) {
|
|
2711
3307
|
return status !== "queued" && status !== "review" && !TERMINAL.has(status);
|
|
2712
3308
|
}
|
|
3309
|
+
function isWorkingTaskStatus(status) {
|
|
3310
|
+
return status === "queued" || isActiveTaskStatus(status);
|
|
3311
|
+
}
|
|
2713
3312
|
function isTerminalTaskStatus(status) {
|
|
2714
3313
|
return TERMINAL.has(status);
|
|
2715
3314
|
}
|
|
@@ -2764,14 +3363,18 @@ Follow-up context:
|
|
|
2764
3363
|
- Previous request: ${options.parent.requestText}
|
|
2765
3364
|
- Previous diff summary: ${options.parent.diffSummary || "No file changes"}
|
|
2766
3365
|
` : "";
|
|
2767
|
-
return `
|
|
2768
|
-
|
|
3366
|
+
return `Target service context (authoritative):
|
|
3367
|
+
- Repository worktree: ${options.repoRoot}
|
|
3368
|
+
- Workspace: ${options.workspaceRoot}
|
|
3369
|
+
- Browser URL: ${options.context.page.url}
|
|
3370
|
+
${options.upstreamUrl ? `- Local upstream URL: ${options.upstreamUrl}
|
|
3371
|
+
` : ""}
|
|
3372
|
+
Treat the workspace above as the already-resolved service directory and the repository worktree as its safety boundary. Run project commands from the workspace; do not search parent directories or run directory-discovery commands to locate the project again.
|
|
2769
3373
|
|
|
2770
3374
|
User request:
|
|
2771
3375
|
${options.context.request.text}
|
|
2772
3376
|
|
|
2773
3377
|
Selected UI context:
|
|
2774
|
-
- URL: ${options.context.page.url}
|
|
2775
3378
|
- Route: ${options.context.page.pathname}
|
|
2776
3379
|
- Selection mode: ${options.context.selection.mode}
|
|
2777
3380
|
${targets || "- No concrete target; use the page context and repository search."}
|
|
@@ -2809,7 +3412,7 @@ import { relative as relative5, resolve as resolve6, sep as sep5 } from "node:pa
|
|
|
2809
3412
|
// ../../packages/bridge-core/src/source/path-normalizer.ts
|
|
2810
3413
|
import { access, readFile as readFile3, realpath as realpath6 } from "node:fs/promises";
|
|
2811
3414
|
import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve5, sep as sep4 } from "node:path";
|
|
2812
|
-
import { spawn as
|
|
3415
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
2813
3416
|
function toPosix(value) {
|
|
2814
3417
|
return value.split(sep4).join("/").replaceAll("\\", "/");
|
|
2815
3418
|
}
|
|
@@ -2844,7 +3447,7 @@ function isWithin2(root, candidate) {
|
|
|
2844
3447
|
}
|
|
2845
3448
|
async function gitFiles(repoRoot) {
|
|
2846
3449
|
return await new Promise((resolvePromise, reject) => {
|
|
2847
|
-
const child =
|
|
3450
|
+
const child = spawn5(
|
|
2848
3451
|
"git",
|
|
2849
3452
|
["ls-files", "--cached", "--others", "--exclude-standard", "-z"],
|
|
2850
3453
|
{
|
|
@@ -2914,8 +3517,8 @@ async function normalizeSourceLocation(input, repoRootInput) {
|
|
|
2914
3517
|
candidates: matches.slice(0, 20)
|
|
2915
3518
|
};
|
|
2916
3519
|
}
|
|
2917
|
-
const
|
|
2918
|
-
const absolutePath = resolve5(repoRoot,
|
|
3520
|
+
const filePath2 = matches[0];
|
|
3521
|
+
const absolutePath = resolve5(repoRoot, filePath2);
|
|
2919
3522
|
const canonical = await realpath6(absolutePath);
|
|
2920
3523
|
if (!isWithin2(repoRoot, canonical)) {
|
|
2921
3524
|
return { input, confidence: "unknown", candidates: [] };
|
|
@@ -2923,7 +3526,7 @@ async function normalizeSourceLocation(input, repoRootInput) {
|
|
|
2923
3526
|
const lineNumber = await boundedLine(canonical, input.lineNumber);
|
|
2924
3527
|
return {
|
|
2925
3528
|
input,
|
|
2926
|
-
filePath,
|
|
3529
|
+
filePath: filePath2,
|
|
2927
3530
|
absolutePath: canonical,
|
|
2928
3531
|
...lineNumber === void 0 ? {} : { lineNumber },
|
|
2929
3532
|
...input.columnNumber === void 0 ? {} : { columnNumber: input.columnNumber },
|
|
@@ -3246,6 +3849,7 @@ function repositoryGuardMessage(result) {
|
|
|
3246
3849
|
var TaskService = class {
|
|
3247
3850
|
#projectId;
|
|
3248
3851
|
#workspaceRoot;
|
|
3852
|
+
#upstreamUrl;
|
|
3249
3853
|
#adapter;
|
|
3250
3854
|
#store;
|
|
3251
3855
|
#git;
|
|
@@ -3269,6 +3873,7 @@ var TaskService = class {
|
|
|
3269
3873
|
constructor(options) {
|
|
3270
3874
|
this.#projectId = options.projectId;
|
|
3271
3875
|
this.#workspaceRoot = resolve6(options.workspaceRoot ?? options.git.repoRoot);
|
|
3876
|
+
this.#upstreamUrl = options.upstreamUrl;
|
|
3272
3877
|
if (!isWithin3(options.git.repoRoot, this.#workspaceRoot)) {
|
|
3273
3878
|
throw new TaskServiceError(
|
|
3274
3879
|
"WORKSPACE_OUTSIDE_REPOSITORY",
|
|
@@ -3655,6 +4260,7 @@ var TaskService = class {
|
|
|
3655
4260
|
const prompt = buildAgentPrompt({
|
|
3656
4261
|
repoRoot: this.#git.repoRoot,
|
|
3657
4262
|
workspaceRoot: this.#workspaceRoot,
|
|
4263
|
+
...this.#upstreamUrl === void 0 ? {} : { upstreamUrl: this.#upstreamUrl },
|
|
3658
4264
|
contextBundlePath: contextPath,
|
|
3659
4265
|
context,
|
|
3660
4266
|
allowedPatterns: this.#git.pathPolicy.allowedPatterns,
|
|
@@ -3848,11 +4454,11 @@ var TaskService = class {
|
|
|
3848
4454
|
for (const target of result.selection.targets) {
|
|
3849
4455
|
const sanitize = async (location) => {
|
|
3850
4456
|
try {
|
|
3851
|
-
const
|
|
4457
|
+
const filePath2 = await this.#git.pathPolicy.assertFilesystemPathAllowed(
|
|
3852
4458
|
location.filePath,
|
|
3853
4459
|
false
|
|
3854
4460
|
);
|
|
3855
|
-
return { ...location, filePath };
|
|
4461
|
+
return { ...location, filePath: filePath2 };
|
|
3856
4462
|
} catch {
|
|
3857
4463
|
return void 0;
|
|
3858
4464
|
}
|
|
@@ -3887,6 +4493,18 @@ var TaskService = class {
|
|
|
3887
4493
|
};
|
|
3888
4494
|
|
|
3889
4495
|
// ../../packages/bridge-core/src/verification/browser-sessions.ts
|
|
4496
|
+
function errorSignature(event) {
|
|
4497
|
+
return `${event.level}\0${event.message}`;
|
|
4498
|
+
}
|
|
4499
|
+
function samePage(left, right) {
|
|
4500
|
+
try {
|
|
4501
|
+
const leftUrl = new URL(left);
|
|
4502
|
+
const rightUrl = new URL(right);
|
|
4503
|
+
return leftUrl.origin === rightUrl.origin && leftUrl.pathname === rightUrl.pathname && leftUrl.search === rightUrl.search;
|
|
4504
|
+
} catch {
|
|
4505
|
+
return left === right;
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
3890
4508
|
var BrowserSessionManager = class {
|
|
3891
4509
|
#sessions = /* @__PURE__ */ new Map();
|
|
3892
4510
|
#maxConsoleEvents;
|
|
@@ -3962,7 +4580,13 @@ var BrowserSessionManager = class {
|
|
|
3962
4580
|
return {
|
|
3963
4581
|
browserSessionId: id,
|
|
3964
4582
|
renderRevision: session.renderRevision,
|
|
3965
|
-
startedAt: now.toISOString()
|
|
4583
|
+
startedAt: now.toISOString(),
|
|
4584
|
+
url: session.url,
|
|
4585
|
+
knownErrorSignatures: [
|
|
4586
|
+
...new Set(
|
|
4587
|
+
session.consoleEvents.filter((event) => event.level === "error" || event.level === "unhandled").map(errorSignature)
|
|
4588
|
+
)
|
|
4589
|
+
]
|
|
3966
4590
|
};
|
|
3967
4591
|
}
|
|
3968
4592
|
verify(baseline, options = {}) {
|
|
@@ -3975,10 +4599,16 @@ var BrowserSessionManager = class {
|
|
|
3975
4599
|
summary: "Origin browser session is disconnected."
|
|
3976
4600
|
};
|
|
3977
4601
|
}
|
|
3978
|
-
const
|
|
3979
|
-
|
|
3980
|
-
|
|
4602
|
+
const knownErrors = new Set(baseline.knownErrorSignatures);
|
|
4603
|
+
const newErrors = [
|
|
4604
|
+
...new Map(
|
|
4605
|
+
session.consoleEvents.filter(
|
|
4606
|
+
(event) => event.createdAt > baseline.startedAt && (event.level === "error" || event.level === "unhandled") && !knownErrors.has(errorSignature(event))
|
|
4607
|
+
).map((event) => [errorSignature(event), event])
|
|
4608
|
+
).values()
|
|
4609
|
+
];
|
|
3981
4610
|
const renderChanged = session.renderRevision > baseline.renderRevision;
|
|
4611
|
+
const pageUnchanged = samePage(baseline.url, session.url);
|
|
3982
4612
|
const targetResult = options.taskId === void 0 ? void 0 : session.targetResults.filter(
|
|
3983
4613
|
(result) => result.taskId === options.taskId && result.createdAt >= baseline.startedAt
|
|
3984
4614
|
).at(-1);
|
|
@@ -3991,6 +4621,15 @@ var BrowserSessionManager = class {
|
|
|
3991
4621
|
summary: `${newErrors.length} new browser error${newErrors.length === 1 ? "" : "s"} detected.`
|
|
3992
4622
|
};
|
|
3993
4623
|
}
|
|
4624
|
+
if (!pageUnchanged) {
|
|
4625
|
+
return {
|
|
4626
|
+
status: "partial",
|
|
4627
|
+
renderChanged,
|
|
4628
|
+
newErrors: [],
|
|
4629
|
+
...targetResult ? { targetResult } : {},
|
|
4630
|
+
summary: "Origin browser navigated to a different page during verification."
|
|
4631
|
+
};
|
|
4632
|
+
}
|
|
3994
4633
|
if (options.targetEvidenceRequired) {
|
|
3995
4634
|
const targetChanged = targetResult?.state === "found-and-changed" && targetResult.targetCount > 0 && targetResult.foundCount === targetResult.targetCount && targetResult.changedCount > 0 && targetResult.renderRevision > baseline.renderRevision;
|
|
3996
4635
|
if (targetChanged) {
|
|
@@ -4031,7 +4670,7 @@ var BrowserSessionManager = class {
|
|
|
4031
4670
|
};
|
|
4032
4671
|
|
|
4033
4672
|
// ../../packages/bridge-core/src/verification/commands.ts
|
|
4034
|
-
import { spawn as
|
|
4673
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
4035
4674
|
var MAX_OUTPUT_CHARS = 8e3;
|
|
4036
4675
|
var KILL_GRACE_MS = 250;
|
|
4037
4676
|
function appendOutput(current, chunk) {
|
|
@@ -4045,7 +4684,7 @@ async function runVerificationCommand(configured, cwd, signal) {
|
|
|
4045
4684
|
}
|
|
4046
4685
|
const startedAt = Date.now();
|
|
4047
4686
|
return await new Promise((resolveResult, rejectResult) => {
|
|
4048
|
-
const child =
|
|
4687
|
+
const child = spawn6(executable, arguments_, {
|
|
4049
4688
|
cwd,
|
|
4050
4689
|
detached: process.platform !== "win32",
|
|
4051
4690
|
shell: false,
|
|
@@ -4579,7 +5218,7 @@ ${output2}` : ""}`
|
|
|
4579
5218
|
status: "ok",
|
|
4580
5219
|
bridge: "online",
|
|
4581
5220
|
projectId: options.project.id,
|
|
4582
|
-
activeTask: taskService.list().find((task) =>
|
|
5221
|
+
activeTask: taskService.list().find((task) => isWorkingTaskStatus(task.status))?.id ?? null
|
|
4583
5222
|
}),
|
|
4584
5223
|
project: () => ({
|
|
4585
5224
|
id: options.project.id,
|
|
@@ -4620,33 +5259,70 @@ ${output2}` : ""}`
|
|
|
4620
5259
|
}
|
|
4621
5260
|
|
|
4622
5261
|
// ../../packages/bridge-core/src/bridge/default-control-service.ts
|
|
5262
|
+
function createAgentAdapter(agent) {
|
|
5263
|
+
if (agent.adapter === "claude") {
|
|
5264
|
+
if (agent.reasoningEffort === "minimal") {
|
|
5265
|
+
throw new Error("Claude does not support minimal reasoning effort");
|
|
5266
|
+
}
|
|
5267
|
+
return new ClaudeAdapter({
|
|
5268
|
+
...agent.model === void 0 ? {} : { model: agent.model },
|
|
5269
|
+
...agent.reasoningEffort === void 0 ? {} : { reasoningEffort: agent.reasoningEffort }
|
|
5270
|
+
});
|
|
5271
|
+
}
|
|
5272
|
+
if (agent.adapter === "codex") {
|
|
5273
|
+
if (agent.reasoningEffort === "max") {
|
|
5274
|
+
throw new Error("Codex does not support max reasoning effort");
|
|
5275
|
+
}
|
|
5276
|
+
return new CodexAdapter({
|
|
5277
|
+
...agent.model === void 0 ? {} : { model: agent.model },
|
|
5278
|
+
...agent.reasoningEffort === void 0 ? {} : { reasoningEffort: agent.reasoningEffort },
|
|
5279
|
+
...agent.profile === void 0 ? {} : { profile: agent.profile }
|
|
5280
|
+
});
|
|
5281
|
+
}
|
|
5282
|
+
throw new Error(`Agent adapter ${agent.adapter} is not implemented in this build`);
|
|
5283
|
+
}
|
|
5284
|
+
function inheritedAgentEnvironment(names, environment) {
|
|
5285
|
+
return Object.fromEntries(
|
|
5286
|
+
names.flatMap((name) => {
|
|
5287
|
+
const value = environment[name];
|
|
5288
|
+
return value === void 0 ? [] : [[name, value]];
|
|
5289
|
+
})
|
|
5290
|
+
);
|
|
5291
|
+
}
|
|
4623
5292
|
async function createDefaultControlService(context, environment = process.env) {
|
|
4624
5293
|
const loaded = await loadVisualDevConfig(context.repoRoot, {
|
|
4625
5294
|
...context.configRoot === void 0 ? {} : { configRoot: context.configRoot }
|
|
4626
5295
|
});
|
|
4627
|
-
if (loaded.config.agent.adapter !== "codex") {
|
|
4628
|
-
throw new Error(
|
|
4629
|
-
`Agent adapter ${loaded.config.agent.adapter} is not implemented in this MVP build`
|
|
4630
|
-
);
|
|
4631
|
-
}
|
|
4632
5296
|
const git = await GitTransactionManager.open(context.repoRoot, {
|
|
4633
|
-
allowed:
|
|
4634
|
-
|
|
5297
|
+
allowed: rebaseWorkspacePatterns(
|
|
5298
|
+
context.repoRoot,
|
|
5299
|
+
context.workspaceRoot,
|
|
5300
|
+
loaded.config.paths.allowed
|
|
5301
|
+
),
|
|
5302
|
+
denied: rebaseWorkspacePatterns(
|
|
5303
|
+
context.repoRoot,
|
|
5304
|
+
context.workspaceRoot,
|
|
5305
|
+
loaded.config.paths.denied
|
|
5306
|
+
)
|
|
4635
5307
|
});
|
|
4636
5308
|
const storagePaths = await resolveStoragePaths(context.repoRoot, environment);
|
|
4637
5309
|
const store = new SqliteTaskStore(storagePaths.databasePath);
|
|
4638
5310
|
const taskService = new TaskService({
|
|
4639
5311
|
projectId: context.projectId,
|
|
4640
5312
|
workspaceRoot: context.workspaceRoot,
|
|
4641
|
-
|
|
5313
|
+
upstreamUrl: context.upstreamUrl,
|
|
5314
|
+
adapter: createAgentAdapter(loaded.config.agent),
|
|
4642
5315
|
store,
|
|
4643
5316
|
git,
|
|
4644
5317
|
maxRunMs: loaded.config.agent.maxRunMs,
|
|
4645
5318
|
maxPending: loaded.config.queue.maxPending,
|
|
4646
5319
|
resumeMode: loaded.config.agent.resumeMode,
|
|
4647
|
-
environment:
|
|
5320
|
+
environment: inheritedAgentEnvironment(
|
|
5321
|
+
loaded.config.agent.inheritEnv,
|
|
5322
|
+
environment
|
|
5323
|
+
)
|
|
4648
5324
|
});
|
|
4649
|
-
|
|
5325
|
+
const controlService = createTaskControlService({
|
|
4650
5326
|
taskService,
|
|
4651
5327
|
hmrWaitMs: loaded.config.verification.hmrWaitMs,
|
|
4652
5328
|
verificationCommands: loaded.config.verification.commands,
|
|
@@ -4658,10 +5334,32 @@ async function createDefaultControlService(context, environment = process.env) {
|
|
|
4658
5334
|
upstreamUrl: context.upstreamUrl
|
|
4659
5335
|
}
|
|
4660
5336
|
});
|
|
5337
|
+
const reportRuntimeState = () => {
|
|
5338
|
+
const activeTask = taskService.list().find((task) => isWorkingTaskStatus(task.status));
|
|
5339
|
+
context.onRuntimeState?.({
|
|
5340
|
+
status: activeTask === void 0 ? "idle" : "working",
|
|
5341
|
+
...activeTask === void 0 ? {} : { activeTaskId: activeTask.id }
|
|
5342
|
+
});
|
|
5343
|
+
};
|
|
5344
|
+
const unsubscribeRuntime = taskService.subscribe(reportRuntimeState);
|
|
5345
|
+
reportRuntimeState();
|
|
5346
|
+
return {
|
|
5347
|
+
...controlService,
|
|
5348
|
+
close: async () => {
|
|
5349
|
+
unsubscribeRuntime();
|
|
5350
|
+
await controlService.close?.();
|
|
5351
|
+
}
|
|
5352
|
+
};
|
|
4661
5353
|
}
|
|
4662
5354
|
|
|
4663
5355
|
// ../../packages/bridge-core/src/runtime/registry.ts
|
|
4664
5356
|
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
5357
|
+
import {
|
|
5358
|
+
readFileSync,
|
|
5359
|
+
realpathSync,
|
|
5360
|
+
rmSync,
|
|
5361
|
+
unlinkSync
|
|
5362
|
+
} from "node:fs";
|
|
4665
5363
|
import {
|
|
4666
5364
|
mkdir as mkdir2,
|
|
4667
5365
|
link,
|
|
@@ -4702,6 +5400,11 @@ async function repositoryKey(repositoryRoot) {
|
|
|
4702
5400
|
const canonicalRoot = await realpath8(repositoryRoot);
|
|
4703
5401
|
return createHash3("sha256").update(canonicalRoot).digest("hex");
|
|
4704
5402
|
}
|
|
5403
|
+
function runtimeDirectoryForSync(repositoryRoot, options) {
|
|
5404
|
+
const canonicalRoot = realpathSync(repositoryRoot);
|
|
5405
|
+
const repoKey = createHash3("sha256").update(canonicalRoot).digest("hex");
|
|
5406
|
+
return join3(runtimeRoot(options), repoKey);
|
|
5407
|
+
}
|
|
4705
5408
|
async function runtimeDirectoryFor(repositoryRoot, options = {}) {
|
|
4706
5409
|
return join3(runtimeRoot(options), await repositoryKey(repositoryRoot));
|
|
4707
5410
|
}
|
|
@@ -4740,6 +5443,13 @@ async function readJson(path) {
|
|
|
4740
5443
|
return void 0;
|
|
4741
5444
|
}
|
|
4742
5445
|
}
|
|
5446
|
+
function readJsonSync(path) {
|
|
5447
|
+
try {
|
|
5448
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
5449
|
+
} catch {
|
|
5450
|
+
return void 0;
|
|
5451
|
+
}
|
|
5452
|
+
}
|
|
4743
5453
|
async function readInstance(repositoryRoot, options = {}) {
|
|
4744
5454
|
const directory = await runtimeDirectoryFor(repositoryRoot, options);
|
|
4745
5455
|
const value = await readJson(join3(directory, "instance.json"));
|
|
@@ -4757,6 +5467,21 @@ async function writeInstance(repositoryRoot, instance, options = {}) {
|
|
|
4757
5467
|
});
|
|
4758
5468
|
await rename(temporaryPath, path);
|
|
4759
5469
|
}
|
|
5470
|
+
async function updateInstance(repositoryRoot, expectedPid, update, options = {}) {
|
|
5471
|
+
const current = await readInstance(repositoryRoot, options);
|
|
5472
|
+
if (current === void 0 || current.pid !== expectedPid) return void 0;
|
|
5473
|
+
const next = {
|
|
5474
|
+
...current,
|
|
5475
|
+
...update.status === void 0 ? {} : { status: update.status }
|
|
5476
|
+
};
|
|
5477
|
+
if (update.activeTaskId === null) {
|
|
5478
|
+
delete next.activeTaskId;
|
|
5479
|
+
} else if (update.activeTaskId !== void 0) {
|
|
5480
|
+
next.activeTaskId = update.activeTaskId;
|
|
5481
|
+
}
|
|
5482
|
+
await writeInstance(repositoryRoot, next, options);
|
|
5483
|
+
return next;
|
|
5484
|
+
}
|
|
4760
5485
|
async function removeInstance(repositoryRoot, expectedPid, options = {}) {
|
|
4761
5486
|
const directory = await runtimeDirectoryFor(repositoryRoot, options);
|
|
4762
5487
|
const path = join3(directory, "instance.json");
|
|
@@ -4768,6 +5493,17 @@ async function removeInstance(repositoryRoot, expectedPid, options = {}) {
|
|
|
4768
5493
|
}
|
|
4769
5494
|
await rm3(path, { force: true });
|
|
4770
5495
|
}
|
|
5496
|
+
function removeInstanceSync(repositoryRoot, expectedPid, options = {}) {
|
|
5497
|
+
const directory = runtimeDirectoryForSync(repositoryRoot, options);
|
|
5498
|
+
const path = join3(directory, "instance.json");
|
|
5499
|
+
if (expectedPid !== void 0) {
|
|
5500
|
+
const current = readJsonSync(path);
|
|
5501
|
+
if (!isBridgeInstanceRecord(current) || current.pid !== expectedPid) {
|
|
5502
|
+
return;
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5505
|
+
rmSync(path, { force: true });
|
|
5506
|
+
}
|
|
4771
5507
|
async function acquireWorktreeLock(repositoryRoot, options = {}) {
|
|
4772
5508
|
const repoRoot = await realpath8(repositoryRoot);
|
|
4773
5509
|
const repoKey = await repositoryKey(repoRoot);
|
|
@@ -4799,23 +5535,43 @@ async function acquireWorktreeLock(repositoryRoot, options = {}) {
|
|
|
4799
5535
|
await rm3(candidatePath, { force: true });
|
|
4800
5536
|
}
|
|
4801
5537
|
let released = false;
|
|
5538
|
+
let releasePromise;
|
|
4802
5539
|
return {
|
|
4803
5540
|
repoKey,
|
|
4804
5541
|
runtimeDirectory,
|
|
4805
5542
|
lockPath,
|
|
4806
|
-
|
|
5543
|
+
release() {
|
|
4807
5544
|
if (released) {
|
|
5545
|
+
return Promise.resolve();
|
|
5546
|
+
}
|
|
5547
|
+
releasePromise ??= (async () => {
|
|
5548
|
+
const current = await readJson(lockPath);
|
|
5549
|
+
if (isLockRecord(current) && current.ownerId === ownerId) {
|
|
5550
|
+
await unlink(lockPath).catch((error) => {
|
|
5551
|
+
if (error.code !== "ENOENT") {
|
|
5552
|
+
throw error;
|
|
5553
|
+
}
|
|
5554
|
+
});
|
|
5555
|
+
}
|
|
5556
|
+
released = true;
|
|
5557
|
+
})();
|
|
5558
|
+
return releasePromise;
|
|
5559
|
+
},
|
|
5560
|
+
releaseSync() {
|
|
5561
|
+
if (released || releasePromise !== void 0) {
|
|
4808
5562
|
return;
|
|
4809
5563
|
}
|
|
4810
|
-
|
|
4811
|
-
const current = await readJson(lockPath);
|
|
5564
|
+
const current = readJsonSync(lockPath);
|
|
4812
5565
|
if (isLockRecord(current) && current.ownerId === ownerId) {
|
|
4813
|
-
|
|
4814
|
-
|
|
5566
|
+
try {
|
|
5567
|
+
unlinkSync(lockPath);
|
|
5568
|
+
} catch (error) {
|
|
5569
|
+
if (typeof error !== "object" || error === null || !("code" in error) || error.code !== "ENOENT") {
|
|
4815
5570
|
throw error;
|
|
4816
5571
|
}
|
|
4817
|
-
}
|
|
5572
|
+
}
|
|
4818
5573
|
}
|
|
5574
|
+
released = true;
|
|
4819
5575
|
}
|
|
4820
5576
|
};
|
|
4821
5577
|
} catch (error) {
|
|
@@ -4835,10 +5591,10 @@ async function acquireWorktreeLock(repositoryRoot, options = {}) {
|
|
|
4835
5591
|
}
|
|
4836
5592
|
|
|
4837
5593
|
// ../../packages/bridge-core/src/runtime/repository.ts
|
|
4838
|
-
import { execFile } from "node:child_process";
|
|
5594
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
4839
5595
|
import { realpath as realpath9 } from "node:fs/promises";
|
|
4840
|
-
import { promisify } from "node:util";
|
|
4841
|
-
var
|
|
5596
|
+
import { promisify as promisify3 } from "node:util";
|
|
5597
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
4842
5598
|
var GitWorktreeNotFoundError = class extends Error {
|
|
4843
5599
|
constructor(cwd, options = {}) {
|
|
4844
5600
|
super(
|
|
@@ -4850,7 +5606,7 @@ var GitWorktreeNotFoundError = class extends Error {
|
|
|
4850
5606
|
};
|
|
4851
5607
|
async function discoverGitWorktreeRoot(cwd = process.cwd()) {
|
|
4852
5608
|
try {
|
|
4853
|
-
const { stdout } = await
|
|
5609
|
+
const { stdout } = await execFileAsync3(
|
|
4854
5610
|
"git",
|
|
4855
5611
|
["-C", cwd, "rev-parse", "--show-toplevel"],
|
|
4856
5612
|
{
|
|
@@ -4869,6 +5625,91 @@ async function discoverGitWorktreeRoot(cwd = process.cwd()) {
|
|
|
4869
5625
|
}
|
|
4870
5626
|
|
|
4871
5627
|
// src/bridge.ts
|
|
5628
|
+
var DEFAULT_UPSTREAM_MONITOR_INTERVAL_MS = 1e3;
|
|
5629
|
+
var DEFAULT_UPSTREAM_CONNECT_TIMEOUT_MS = 500;
|
|
5630
|
+
var DEFAULT_UPSTREAM_FAILURE_GRACE_MS = 5e3;
|
|
5631
|
+
function positiveMilliseconds(value, fallback) {
|
|
5632
|
+
return value === void 0 || !Number.isFinite(value) || value <= 0 ? fallback : Math.max(1, Math.floor(value));
|
|
5633
|
+
}
|
|
5634
|
+
async function probeUpstream(upstreamUrl, timeoutMs) {
|
|
5635
|
+
const upstream = new URL(upstreamUrl);
|
|
5636
|
+
const hostname = upstream.hostname.startsWith("[") ? upstream.hostname.slice(1, -1) : upstream.hostname;
|
|
5637
|
+
const port = Number(
|
|
5638
|
+
upstream.port || (upstream.protocol === "https:" ? 443 : 80)
|
|
5639
|
+
);
|
|
5640
|
+
return await new Promise((resolve9) => {
|
|
5641
|
+
let settled = false;
|
|
5642
|
+
let timeout;
|
|
5643
|
+
const socket = createConnection({ host: hostname, port });
|
|
5644
|
+
socket.unref();
|
|
5645
|
+
const finish = (reachable) => {
|
|
5646
|
+
if (settled) return;
|
|
5647
|
+
settled = true;
|
|
5648
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
5649
|
+
socket.destroy();
|
|
5650
|
+
resolve9(reachable);
|
|
5651
|
+
};
|
|
5652
|
+
socket.once("connect", () => finish(true));
|
|
5653
|
+
socket.once("error", () => finish(false));
|
|
5654
|
+
timeout = setTimeout(() => finish(false), timeoutMs);
|
|
5655
|
+
timeout.unref();
|
|
5656
|
+
});
|
|
5657
|
+
}
|
|
5658
|
+
function monitorUpstream(bridge, options) {
|
|
5659
|
+
const intervalMs = positiveMilliseconds(
|
|
5660
|
+
options.intervalMs,
|
|
5661
|
+
DEFAULT_UPSTREAM_MONITOR_INTERVAL_MS
|
|
5662
|
+
);
|
|
5663
|
+
const connectTimeoutMs = positiveMilliseconds(
|
|
5664
|
+
options.connectTimeoutMs,
|
|
5665
|
+
DEFAULT_UPSTREAM_CONNECT_TIMEOUT_MS
|
|
5666
|
+
);
|
|
5667
|
+
const initialTimeoutMs = positiveMilliseconds(
|
|
5668
|
+
options.initialTimeoutMs,
|
|
5669
|
+
6e4
|
|
5670
|
+
);
|
|
5671
|
+
const failureGraceMs = positiveMilliseconds(
|
|
5672
|
+
options.failureGraceMs,
|
|
5673
|
+
DEFAULT_UPSTREAM_FAILURE_GRACE_MS
|
|
5674
|
+
);
|
|
5675
|
+
let connected = false;
|
|
5676
|
+
let unavailableSince;
|
|
5677
|
+
let stopped = false;
|
|
5678
|
+
let timer;
|
|
5679
|
+
const stop = () => {
|
|
5680
|
+
stopped = true;
|
|
5681
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
5682
|
+
};
|
|
5683
|
+
const schedule = () => {
|
|
5684
|
+
if (stopped) return;
|
|
5685
|
+
timer = setTimeout(() => {
|
|
5686
|
+
void check().catch(() => void 0);
|
|
5687
|
+
}, intervalMs);
|
|
5688
|
+
timer.unref();
|
|
5689
|
+
};
|
|
5690
|
+
const check = async () => {
|
|
5691
|
+
if (stopped) return;
|
|
5692
|
+
const reachable = await probeUpstream(bridge.upstreamUrl, connectTimeoutMs);
|
|
5693
|
+
if (stopped) return;
|
|
5694
|
+
const now = Date.now();
|
|
5695
|
+
if (reachable) {
|
|
5696
|
+
connected = true;
|
|
5697
|
+
unavailableSince = void 0;
|
|
5698
|
+
schedule();
|
|
5699
|
+
return;
|
|
5700
|
+
}
|
|
5701
|
+
unavailableSince ??= now;
|
|
5702
|
+
const timeoutMs = connected ? failureGraceMs : initialTimeoutMs;
|
|
5703
|
+
if (now - unavailableSince >= timeoutMs) {
|
|
5704
|
+
stop();
|
|
5705
|
+
await bridge.close();
|
|
5706
|
+
return;
|
|
5707
|
+
}
|
|
5708
|
+
schedule();
|
|
5709
|
+
};
|
|
5710
|
+
void bridge.closed.then(stop);
|
|
5711
|
+
void check().catch(() => void 0);
|
|
5712
|
+
}
|
|
4872
5713
|
function normalizeUpstream(value) {
|
|
4873
5714
|
const url = new URL(value);
|
|
4874
5715
|
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
@@ -4905,10 +5746,30 @@ async function startBridgeCore(options, dependencies) {
|
|
|
4905
5746
|
let gateway;
|
|
4906
5747
|
let controlService;
|
|
4907
5748
|
let registryWritten = false;
|
|
5749
|
+
let registryUpdate = Promise.resolve();
|
|
5750
|
+
let latestRuntimeState = { status: "idle" };
|
|
5751
|
+
const updateRuntimeState = (status, activeTaskId) => {
|
|
5752
|
+
latestRuntimeState = {
|
|
5753
|
+
status,
|
|
5754
|
+
...activeTaskId === void 0 ? {} : { activeTaskId }
|
|
5755
|
+
};
|
|
5756
|
+
if (!registryWritten) return;
|
|
5757
|
+
registryUpdate = registryUpdate.then(async () => {
|
|
5758
|
+
await updateInstance(
|
|
5759
|
+
loadedConfig.repoRoot,
|
|
5760
|
+
process.pid,
|
|
5761
|
+
{
|
|
5762
|
+
status,
|
|
5763
|
+
activeTaskId: activeTaskId ?? null
|
|
5764
|
+
},
|
|
5765
|
+
{ environment }
|
|
5766
|
+
);
|
|
5767
|
+
}).catch(() => void 0);
|
|
5768
|
+
};
|
|
4908
5769
|
try {
|
|
4909
5770
|
lock = options.lock ?? await acquireWorktreeLock(loadedConfig.repoRoot, { environment });
|
|
4910
5771
|
const host = options.host ?? loadedConfig.config.gateway.host;
|
|
4911
|
-
const configuredPublicUrl = options.publicUrl ?? loadedConfig.config.gateway.publicUrl;
|
|
5772
|
+
const configuredPublicUrl = options.publicUrl ?? loadedConfig.config.gateway.publicUrl ?? options.fallbackPublicUrl;
|
|
4912
5773
|
const publicUrl = configuredPublicUrl === void 0 ? void 0 : normalizePublicUrl(configuredPublicUrl);
|
|
4913
5774
|
const gatewayPort = await findAvailablePort(startPort(loadedConfig, options.listen), host);
|
|
4914
5775
|
const token = generatePairingToken();
|
|
@@ -4918,7 +5779,10 @@ async function startBridgeCore(options, dependencies) {
|
|
|
4918
5779
|
repoRoot: loadedConfig.repoRoot,
|
|
4919
5780
|
configRoot: loadedConfig.configRoot,
|
|
4920
5781
|
workspaceRoot: loadedConfig.workspaceRoot,
|
|
4921
|
-
upstreamUrl: options.upstreamUrl
|
|
5782
|
+
upstreamUrl: options.upstreamUrl,
|
|
5783
|
+
onRuntimeState: (state) => {
|
|
5784
|
+
updateRuntimeState(state.status, state.activeTaskId);
|
|
5785
|
+
}
|
|
4922
5786
|
};
|
|
4923
5787
|
controlService = await resolveControlService(dependencies, controlContext);
|
|
4924
5788
|
const allowedOrigins = new Set(loadedConfig.config.security.allowedOrigins);
|
|
@@ -4933,20 +5797,33 @@ async function startBridgeCore(options, dependencies) {
|
|
|
4933
5797
|
allowedOrigins: [...allowedOrigins]
|
|
4934
5798
|
});
|
|
4935
5799
|
const address = await gateway.start();
|
|
4936
|
-
const openUrl = publicUrl ?? address.url;
|
|
5800
|
+
const openUrl = createPairingUrl(publicUrl ?? address.url, token);
|
|
4937
5801
|
const instance = {
|
|
4938
5802
|
projectId: loadedConfig.config.project.id,
|
|
4939
5803
|
repoRoot: loadedConfig.repoRoot,
|
|
4940
5804
|
pid: process.pid,
|
|
4941
5805
|
gatewayUrl: address.url,
|
|
4942
5806
|
upstreamUrl: options.upstreamUrl,
|
|
4943
|
-
status:
|
|
5807
|
+
status: latestRuntimeState.status,
|
|
4944
5808
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5809
|
+
...latestRuntimeState.activeTaskId === void 0 ? {} : { activeTaskId: latestRuntimeState.activeTaskId },
|
|
4945
5810
|
...publicUrl === void 0 ? {} : { publicUrl }
|
|
4946
5811
|
};
|
|
4947
5812
|
await writeInstance(loadedConfig.repoRoot, instance, { environment });
|
|
4948
5813
|
registryWritten = true;
|
|
4949
|
-
|
|
5814
|
+
const emergencyExitCleanup = () => {
|
|
5815
|
+
try {
|
|
5816
|
+
removeInstanceSync(loadedConfig.repoRoot, process.pid, { environment });
|
|
5817
|
+
lock?.releaseSync();
|
|
5818
|
+
} catch {
|
|
5819
|
+
}
|
|
5820
|
+
};
|
|
5821
|
+
process.once("exit", emergencyExitCleanup);
|
|
5822
|
+
let resolveClosed = () => void 0;
|
|
5823
|
+
const closed = new Promise((resolve9) => {
|
|
5824
|
+
resolveClosed = resolve9;
|
|
5825
|
+
});
|
|
5826
|
+
let closePromise;
|
|
4950
5827
|
return {
|
|
4951
5828
|
mode: options.mode,
|
|
4952
5829
|
projectId: loadedConfig.config.project.id,
|
|
@@ -4958,18 +5835,33 @@ async function startBridgeCore(options, dependencies) {
|
|
|
4958
5835
|
openUrl,
|
|
4959
5836
|
gateway,
|
|
4960
5837
|
...options.managedProcess === void 0 ? {} : { managedProcess: options.managedProcess },
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
5838
|
+
closed,
|
|
5839
|
+
close() {
|
|
5840
|
+
closePromise ??= (async () => {
|
|
5841
|
+
process.off("exit", emergencyExitCleanup);
|
|
5842
|
+
try {
|
|
5843
|
+
await registryUpdate;
|
|
5844
|
+
await updateInstance(
|
|
5845
|
+
loadedConfig.repoRoot,
|
|
5846
|
+
process.pid,
|
|
5847
|
+
{ status: "stopping", activeTaskId: null },
|
|
5848
|
+
{ environment }
|
|
5849
|
+
);
|
|
5850
|
+
await Promise.allSettled([
|
|
5851
|
+
gateway?.close(),
|
|
5852
|
+
options.managedProcess?.stop(),
|
|
5853
|
+
controlService?.close?.()
|
|
5854
|
+
]);
|
|
5855
|
+
await removeInstance(loadedConfig.repoRoot, process.pid, { environment });
|
|
5856
|
+
} finally {
|
|
5857
|
+
try {
|
|
5858
|
+
await lock?.release();
|
|
5859
|
+
} finally {
|
|
5860
|
+
resolveClosed();
|
|
5861
|
+
}
|
|
5862
|
+
}
|
|
5863
|
+
})();
|
|
5864
|
+
return closePromise;
|
|
4973
5865
|
}
|
|
4974
5866
|
};
|
|
4975
5867
|
} catch (error) {
|
|
@@ -4990,17 +5882,25 @@ async function startAttachBridge(options, dependencies = {}) {
|
|
|
4990
5882
|
const repoRoot = await discoverGitWorktreeRoot(cwd);
|
|
4991
5883
|
const configRoot = await discoverVisualDevConfigRoot(cwd, repoRoot);
|
|
4992
5884
|
const loadedConfig = await loadVisualDevConfig(repoRoot, { configRoot });
|
|
4993
|
-
|
|
5885
|
+
const bridge = await startBridgeCore(
|
|
4994
5886
|
{
|
|
4995
5887
|
mode: "attach",
|
|
4996
5888
|
loadedConfig,
|
|
4997
5889
|
upstreamUrl: normalizeUpstream(options.upstream),
|
|
4998
5890
|
...options.listen === void 0 ? {} : { listen: options.listen },
|
|
4999
5891
|
...options.host === void 0 ? {} : { host: options.host },
|
|
5000
|
-
...options.publicUrl === void 0 ? {} : { publicUrl: options.publicUrl }
|
|
5892
|
+
...options.publicUrl === void 0 ? {} : { publicUrl: options.publicUrl },
|
|
5893
|
+
...options.fallbackPublicUrl === void 0 ? {} : { fallbackPublicUrl: options.fallbackPublicUrl }
|
|
5001
5894
|
},
|
|
5002
5895
|
dependencies
|
|
5003
5896
|
);
|
|
5897
|
+
if (dependencies.upstreamMonitor !== false) {
|
|
5898
|
+
monitorUpstream(bridge, {
|
|
5899
|
+
initialTimeoutMs: loadedConfig.config.upstream.ready.timeoutMs,
|
|
5900
|
+
...dependencies.upstreamMonitor
|
|
5901
|
+
});
|
|
5902
|
+
}
|
|
5903
|
+
return bridge;
|
|
5004
5904
|
}
|
|
5005
5905
|
async function startManagedBridge(options = {}, dependencies = {}) {
|
|
5006
5906
|
const cwd = dependencies.cwd ?? process.cwd();
|
|
@@ -5102,6 +6002,7 @@ async function runBridgeUntilSignal(bridge, processLike = process, gracefulTimeo
|
|
|
5102
6002
|
};
|
|
5103
6003
|
for (const signal of signals) processLike.once(signal, shutdown);
|
|
5104
6004
|
void bridge.managedProcess?.exit.then(shutdown);
|
|
6005
|
+
void bridge.closed?.then(shutdown);
|
|
5105
6006
|
});
|
|
5106
6007
|
}
|
|
5107
6008
|
function formatBridgeSummary(bridge) {
|
|
@@ -5120,9 +6021,9 @@ function formatBridgeSummary(bridge) {
|
|
|
5120
6021
|
import { constants } from "node:fs";
|
|
5121
6022
|
import { access as access2, stat as stat3 } from "node:fs/promises";
|
|
5122
6023
|
import { delimiter, isAbsolute as isAbsolute5, join as join4, relative as relative6, resolve as resolve7 } from "node:path";
|
|
5123
|
-
import { execFile as
|
|
5124
|
-
import { promisify as
|
|
5125
|
-
var
|
|
6024
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
6025
|
+
import { promisify as promisify4 } from "node:util";
|
|
6026
|
+
var execFileAsync4 = promisify4(execFile4);
|
|
5126
6027
|
async function fileExists(path) {
|
|
5127
6028
|
try {
|
|
5128
6029
|
await stat3(path);
|
|
@@ -5136,7 +6037,7 @@ async function fileExists(path) {
|
|
|
5136
6037
|
}
|
|
5137
6038
|
async function isIgnored(repoRoot, path) {
|
|
5138
6039
|
try {
|
|
5139
|
-
await
|
|
6040
|
+
await execFileAsync4("git", ["-C", repoRoot, "check-ignore", "--quiet", "--", path], {
|
|
5140
6041
|
windowsHide: true
|
|
5141
6042
|
});
|
|
5142
6043
|
return true;
|
|
@@ -5178,6 +6079,7 @@ async function runDoctor(dependencies = {}) {
|
|
|
5178
6079
|
repoRoot
|
|
5179
6080
|
);
|
|
5180
6081
|
const loaded = await loadVisualDevConfig(repoRoot, { configRoot });
|
|
6082
|
+
const environment = dependencies.environment ?? process.env;
|
|
5181
6083
|
checks.push({
|
|
5182
6084
|
name: "config",
|
|
5183
6085
|
status: loaded.loadedFiles.length === 0 ? "warning" : "pass",
|
|
@@ -5198,7 +6100,7 @@ async function runDoctor(dependencies = {}) {
|
|
|
5198
6100
|
const available = executable !== void 0 && await executableAvailable(
|
|
5199
6101
|
executable,
|
|
5200
6102
|
loaded.workspaceRoot,
|
|
5201
|
-
|
|
6103
|
+
environment
|
|
5202
6104
|
);
|
|
5203
6105
|
checks.push({
|
|
5204
6106
|
name: "dev-command",
|
|
@@ -5206,6 +6108,93 @@ async function runDoctor(dependencies = {}) {
|
|
|
5206
6108
|
message: available ? `${executable} is executable.` : `${executable ?? "<empty>"} was not found or is not executable.`
|
|
5207
6109
|
});
|
|
5208
6110
|
}
|
|
6111
|
+
const adapter = loaded.config.agent.adapter;
|
|
6112
|
+
const adapterSupported = adapter === "codex" || adapter === "claude";
|
|
6113
|
+
const agentAvailable = await executableAvailable(
|
|
6114
|
+
adapter,
|
|
6115
|
+
loaded.workspaceRoot,
|
|
6116
|
+
environment
|
|
6117
|
+
);
|
|
6118
|
+
checks.push({
|
|
6119
|
+
name: "agent",
|
|
6120
|
+
status: adapterSupported && agentAvailable ? "pass" : "fail",
|
|
6121
|
+
message: !adapterSupported ? `${adapter} is configured but is not implemented in this build.` : agentAvailable ? `${adapter} is executable.` : `${adapter} was not found or is not executable.`
|
|
6122
|
+
});
|
|
6123
|
+
if (loaded.config.agent.inheritEnv.length > 0) {
|
|
6124
|
+
const missing = loaded.config.agent.inheritEnv.filter(
|
|
6125
|
+
(name) => environment[name] === void 0
|
|
6126
|
+
);
|
|
6127
|
+
checks.push({
|
|
6128
|
+
name: "agent-environment",
|
|
6129
|
+
status: missing.length === 0 ? "pass" : "fail",
|
|
6130
|
+
message: missing.length === 0 ? `${loaded.config.agent.inheritEnv.length} agent environment variable(s) are available.` : `Missing agent environment variable(s): ${missing.join(", ")}.`
|
|
6131
|
+
});
|
|
6132
|
+
}
|
|
6133
|
+
if (adapter === "claude") {
|
|
6134
|
+
const sandboxDependencies = await Promise.all(
|
|
6135
|
+
["bwrap", "socat"].map(async (executable) => ({
|
|
6136
|
+
executable,
|
|
6137
|
+
available: await executableAvailable(
|
|
6138
|
+
executable,
|
|
6139
|
+
loaded.workspaceRoot,
|
|
6140
|
+
environment
|
|
6141
|
+
)
|
|
6142
|
+
}))
|
|
6143
|
+
);
|
|
6144
|
+
const missing = sandboxDependencies.filter(({ available }) => !available).map(({ executable }) => executable);
|
|
6145
|
+
checks.push({
|
|
6146
|
+
name: "claude-sandbox",
|
|
6147
|
+
status: missing.length === 0 ? "pass" : "warning",
|
|
6148
|
+
message: missing.length === 0 ? "Claude Bash sandbox dependencies are available." : `Claude Bash sandbox is unavailable without: ${missing.join(", ")}.`
|
|
6149
|
+
});
|
|
6150
|
+
}
|
|
6151
|
+
const rtkAvailable = await executableAvailable(
|
|
6152
|
+
"rtk",
|
|
6153
|
+
loaded.workspaceRoot,
|
|
6154
|
+
environment
|
|
6155
|
+
);
|
|
6156
|
+
checks.push({
|
|
6157
|
+
name: "rtk",
|
|
6158
|
+
status: rtkAvailable ? "pass" : "warning",
|
|
6159
|
+
message: rtkAvailable ? "rtk is available for token-efficient command output." : "rtk was not found; agent commands will use their native output."
|
|
6160
|
+
});
|
|
6161
|
+
const verificationCommands = loaded.config.verification.commands;
|
|
6162
|
+
if (verificationCommands.length === 0) {
|
|
6163
|
+
checks.push({
|
|
6164
|
+
name: "verification",
|
|
6165
|
+
status: "warning",
|
|
6166
|
+
message: "No verification commands are configured."
|
|
6167
|
+
});
|
|
6168
|
+
} else {
|
|
6169
|
+
const availability = await Promise.all(
|
|
6170
|
+
verificationCommands.map(async ({ command: command2, name }) => ({
|
|
6171
|
+
name,
|
|
6172
|
+
available: await executableAvailable(
|
|
6173
|
+
command2[0] ?? "",
|
|
6174
|
+
loaded.workspaceRoot,
|
|
6175
|
+
environment
|
|
6176
|
+
)
|
|
6177
|
+
}))
|
|
6178
|
+
);
|
|
6179
|
+
const missing = availability.filter(({ available }) => !available).map(({ name }) => name);
|
|
6180
|
+
checks.push({
|
|
6181
|
+
name: "verification",
|
|
6182
|
+
status: missing.length === 0 ? "pass" : "fail",
|
|
6183
|
+
message: missing.length === 0 ? `${verificationCommands.length} verification command(s) are ready.` : `Missing executable for: ${missing.join(", ")}.`
|
|
6184
|
+
});
|
|
6185
|
+
}
|
|
6186
|
+
const publicUrl = loaded.config.gateway.publicUrl;
|
|
6187
|
+
checks.push({
|
|
6188
|
+
name: "public-url",
|
|
6189
|
+
status: publicUrl === void 0 ? "warning" : "pass",
|
|
6190
|
+
message: publicUrl === void 0 ? "No gateway.publicUrl is configured; pairing links will use the local gateway URL." : `Pairing links will use ${publicUrl}.`
|
|
6191
|
+
});
|
|
6192
|
+
const allowedOrigins = loaded.config.security.allowedOrigins;
|
|
6193
|
+
checks.push({
|
|
6194
|
+
name: "allowed-origins",
|
|
6195
|
+
status: allowedOrigins.length > 0 || publicUrl !== void 0 ? "pass" : "warning",
|
|
6196
|
+
message: allowedOrigins.length > 0 ? `${allowedOrigins.length} browser origin(s) are explicitly allowed.` : publicUrl !== void 0 ? "The configured public URL origin will be allowed automatically." : "No browser origins are configured; add security.allowedOrigins before remote access."
|
|
6197
|
+
});
|
|
5209
6198
|
} catch (error) {
|
|
5210
6199
|
checks.push({
|
|
5211
6200
|
name: "config",
|
|
@@ -5223,7 +6212,7 @@ function formatDoctorChecks(checks) {
|
|
|
5223
6212
|
}
|
|
5224
6213
|
|
|
5225
6214
|
// src/init.ts
|
|
5226
|
-
import { spawn as
|
|
6215
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
5227
6216
|
import { readFile as readFile5, mkdir as mkdir3, realpath as realpath10, stat as stat4, writeFile as writeFile3 } from "node:fs/promises";
|
|
5228
6217
|
import { basename as basename2, dirname as dirname4, join as join5, relative as relative7 } from "node:path";
|
|
5229
6218
|
import { stringify as stringifyYaml } from "yaml";
|
|
@@ -5543,7 +6532,7 @@ function installCommand(request) {
|
|
|
5543
6532
|
async function installPackage(request) {
|
|
5544
6533
|
const { command, args } = installCommand(request);
|
|
5545
6534
|
await new Promise((resolvePromise, reject) => {
|
|
5546
|
-
const child =
|
|
6535
|
+
const child = spawn7(command, args, {
|
|
5547
6536
|
cwd: request.cwd,
|
|
5548
6537
|
env: process.env,
|
|
5549
6538
|
stdio: "inherit",
|
|
@@ -5666,15 +6655,124 @@ function formatBridgeStatus(status) {
|
|
|
5666
6655
|
return "No Visual Bridge is running for this worktree.";
|
|
5667
6656
|
}
|
|
5668
6657
|
const { instance } = status;
|
|
5669
|
-
|
|
6658
|
+
const rows = [
|
|
5670
6659
|
`Project: ${instance.projectId}`,
|
|
5671
6660
|
`Status: ${instance.status}`,
|
|
5672
6661
|
`PID: ${instance.pid}`,
|
|
5673
6662
|
`Gateway: ${instance.gatewayUrl}`,
|
|
5674
6663
|
`Upstream: ${instance.upstreamUrl}`
|
|
5675
|
-
]
|
|
6664
|
+
];
|
|
6665
|
+
if (instance.publicUrl !== void 0) rows.push(`Public: ${instance.publicUrl}`);
|
|
6666
|
+
if (instance.activeTaskId !== void 0) {
|
|
6667
|
+
rows.push(`Active: ${instance.activeTaskId}`);
|
|
6668
|
+
}
|
|
6669
|
+
return rows.join("\n");
|
|
5676
6670
|
}
|
|
5677
6671
|
|
|
6672
|
+
// ../../package.json
|
|
6673
|
+
var package_default = {
|
|
6674
|
+
name: "visual-remote",
|
|
6675
|
+
version: "0.3.2",
|
|
6676
|
+
description: "Visual bridge from a running web UI to a coding agent in its Git worktree",
|
|
6677
|
+
type: "module",
|
|
6678
|
+
packageManager: "pnpm@10.34.5",
|
|
6679
|
+
repository: {
|
|
6680
|
+
type: "git",
|
|
6681
|
+
url: "git+https://github.com/elicie/visual-remote.git"
|
|
6682
|
+
},
|
|
6683
|
+
homepage: "https://github.com/elicie/visual-remote#readme",
|
|
6684
|
+
bugs: {
|
|
6685
|
+
url: "https://github.com/elicie/visual-remote/issues"
|
|
6686
|
+
},
|
|
6687
|
+
files: [
|
|
6688
|
+
"apps/cli/dist/index.js",
|
|
6689
|
+
"apps/cli/dist/direct-exec-mcp.js",
|
|
6690
|
+
"apps/cli/dist/vite.js",
|
|
6691
|
+
"apps/cli/dist/next.js",
|
|
6692
|
+
"apps/cli/dist/next-client.js",
|
|
6693
|
+
"apps/cli/vite.d.ts",
|
|
6694
|
+
"apps/cli/next.d.ts",
|
|
6695
|
+
"apps/cli/next-client.d.ts",
|
|
6696
|
+
"packages/overlay/dist/client.js",
|
|
6697
|
+
"packages/overlay/dist/viewer.js"
|
|
6698
|
+
],
|
|
6699
|
+
bin: {
|
|
6700
|
+
visual: "./apps/cli/dist/index.js",
|
|
6701
|
+
"visual-remote": "./apps/cli/dist/index.js"
|
|
6702
|
+
},
|
|
6703
|
+
exports: {
|
|
6704
|
+
"./vite": {
|
|
6705
|
+
types: "./apps/cli/vite.d.ts",
|
|
6706
|
+
import: "./apps/cli/dist/vite.js"
|
|
6707
|
+
},
|
|
6708
|
+
"./next": {
|
|
6709
|
+
types: "./apps/cli/next.d.ts",
|
|
6710
|
+
import: "./apps/cli/dist/next.js",
|
|
6711
|
+
default: "./apps/cli/dist/next.js"
|
|
6712
|
+
},
|
|
6713
|
+
"./next/client": {
|
|
6714
|
+
types: "./apps/cli/next-client.d.ts",
|
|
6715
|
+
import: "./apps/cli/dist/next-client.js",
|
|
6716
|
+
default: "./apps/cli/dist/next-client.js"
|
|
6717
|
+
}
|
|
6718
|
+
},
|
|
6719
|
+
publishConfig: {
|
|
6720
|
+
access: "public",
|
|
6721
|
+
registry: "https://registry.npmjs.org"
|
|
6722
|
+
},
|
|
6723
|
+
engines: {
|
|
6724
|
+
node: ">=24"
|
|
6725
|
+
},
|
|
6726
|
+
scripts: {
|
|
6727
|
+
build: "corepack pnpm run build:overlay && corepack pnpm run build:server",
|
|
6728
|
+
"build:overlay": "corepack pnpm --filter @visual-remote/overlay build",
|
|
6729
|
+
"build:server": "corepack pnpm --filter @visual-remote/cli build",
|
|
6730
|
+
dev: "corepack pnpm run build:overlay && tsx apps/cli/src/index.ts",
|
|
6731
|
+
test: "vitest run",
|
|
6732
|
+
"test:e2e": "corepack pnpm build && corepack pnpm exec playwright test --config tests/e2e/playwright.config.ts",
|
|
6733
|
+
"test:watch": "vitest",
|
|
6734
|
+
typecheck: "corepack pnpm -r --if-present typecheck && tsc --noEmit -p tsconfig.tests.json",
|
|
6735
|
+
prepack: "corepack pnpm build"
|
|
6736
|
+
},
|
|
6737
|
+
dependencies: {
|
|
6738
|
+
commander: "^15.0.0",
|
|
6739
|
+
"http-proxy": "^1.18.1",
|
|
6740
|
+
ws: "^8.21.1",
|
|
6741
|
+
yaml: "^2.9.0",
|
|
6742
|
+
zod: "^4.4.3"
|
|
6743
|
+
},
|
|
6744
|
+
peerDependencies: {
|
|
6745
|
+
vite: ">=5"
|
|
6746
|
+
},
|
|
6747
|
+
peerDependenciesMeta: {
|
|
6748
|
+
vite: {
|
|
6749
|
+
optional: true
|
|
6750
|
+
}
|
|
6751
|
+
},
|
|
6752
|
+
devDependencies: {
|
|
6753
|
+
"@playwright/test": "^1.62.1",
|
|
6754
|
+
"@types/http-proxy": "^1.17.17",
|
|
6755
|
+
"@types/node": "^26.1.2",
|
|
6756
|
+
"@types/ws": "^8.18.1",
|
|
6757
|
+
"@visual-remote/bridge-core": "workspace:*",
|
|
6758
|
+
"@visual-remote/cli": "workspace:*",
|
|
6759
|
+
"@visual-remote/gateway": "workspace:*",
|
|
6760
|
+
"@visual-remote/overlay": "workspace:*",
|
|
6761
|
+
"@visual-remote/protocol": "workspace:*",
|
|
6762
|
+
esbuild: "^0.28.1",
|
|
6763
|
+
next: "15.5.16",
|
|
6764
|
+
react: "19.1.0",
|
|
6765
|
+
"react-dom": "19.1.0",
|
|
6766
|
+
tsx: "^4.23.1",
|
|
6767
|
+
typescript: "^7.0.2",
|
|
6768
|
+
vite: "^8.1.5",
|
|
6769
|
+
vitest: "^4.1.10"
|
|
6770
|
+
}
|
|
6771
|
+
};
|
|
6772
|
+
|
|
6773
|
+
// src/version.ts
|
|
6774
|
+
var VISUAL_REMOTE_VERSION = package_default.version;
|
|
6775
|
+
|
|
5678
6776
|
// src/index.ts
|
|
5679
6777
|
function parsePort(value) {
|
|
5680
6778
|
const port = Number(value);
|
|
@@ -5697,7 +6795,7 @@ function setExitCode(dependencies, code) {
|
|
|
5697
6795
|
}
|
|
5698
6796
|
}
|
|
5699
6797
|
function createCli(dependencies = {}) {
|
|
5700
|
-
const program = new Command().name("visual").description("Visual Remote Dev Bridge").version(
|
|
6798
|
+
const program = new Command().name("visual").description("Visual Remote Dev Bridge").version(VISUAL_REMOTE_VERSION);
|
|
5701
6799
|
program.command("init").description("Configure Visual Remote for the current Vite or Next.js project").action(async () => {
|
|
5702
6800
|
const result = await initializeVisualDev(dependencies);
|
|
5703
6801
|
output(dependencies, formatInitResult(result));
|
|
@@ -5749,7 +6847,7 @@ async function main(argv = process.argv, dependencies = {}) {
|
|
|
5749
6847
|
}
|
|
5750
6848
|
function isDirectEntry(entryPath2) {
|
|
5751
6849
|
try {
|
|
5752
|
-
return pathToFileURL(
|
|
6850
|
+
return pathToFileURL(realpathSync2(resolve8(entryPath2))).href === import.meta.url;
|
|
5753
6851
|
} catch {
|
|
5754
6852
|
return false;
|
|
5755
6853
|
}
|
|
@@ -5765,6 +6863,7 @@ if (entryPath !== void 0 && isDirectEntry(entryPath)) {
|
|
|
5765
6863
|
});
|
|
5766
6864
|
}
|
|
5767
6865
|
export {
|
|
6866
|
+
VISUAL_REMOTE_VERSION,
|
|
5768
6867
|
createCli,
|
|
5769
6868
|
formatBridgeStatus,
|
|
5770
6869
|
formatBridgeSummary,
|