visual-remote 0.3.1 → 0.3.3
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 +55 -8
- package/apps/cli/dist/direct-exec-mcp.js +108 -1
- package/apps/cli/dist/index.js +948 -351
- package/apps/cli/dist/next.js +692 -218
- package/apps/cli/dist/vite.js +764 -250
- package/package.json +8 -5
- package/packages/overlay/dist/client.js +5 -5
- package/packages/overlay/dist/viewer.js +3 -3
package/apps/cli/dist/vite.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// src/vite.ts
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
3
|
import { resolve as resolve7 } from "node:path";
|
|
3
4
|
|
|
4
5
|
// ../../packages/bridge-core/src/agents/types.ts
|
|
@@ -25,27 +26,530 @@ var AgentCanceledError = class extends Error {
|
|
|
25
26
|
}
|
|
26
27
|
};
|
|
27
28
|
|
|
29
|
+
// ../../packages/bridge-core/src/agents/claude-event-parser.ts
|
|
30
|
+
function asRecord(value) {
|
|
31
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
32
|
+
}
|
|
33
|
+
function asText(value) {
|
|
34
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
35
|
+
}
|
|
36
|
+
function asNumber(value) {
|
|
37
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
38
|
+
}
|
|
39
|
+
function contentBlocks(record) {
|
|
40
|
+
const message = asRecord(record.message);
|
|
41
|
+
return Array.isArray(message?.content) ? message.content.flatMap((block) => {
|
|
42
|
+
const parsed = asRecord(block);
|
|
43
|
+
return parsed === void 0 ? [] : [parsed];
|
|
44
|
+
}) : [];
|
|
45
|
+
}
|
|
46
|
+
function toolSummary(name, input) {
|
|
47
|
+
return asText(input.description) ?? asText(input.command) ?? asText(input.file_path) ?? asText(input.path) ?? (name === "Bash" ? "Run command" : void 0);
|
|
48
|
+
}
|
|
49
|
+
function filePath(input) {
|
|
50
|
+
return asText(input.file_path) ?? asText(input.path) ?? asText(input.notebook_path);
|
|
51
|
+
}
|
|
52
|
+
var ClaudeEventParser = class {
|
|
53
|
+
#defaultCwd;
|
|
54
|
+
#tools = /* @__PURE__ */ new Map();
|
|
55
|
+
#sessionId;
|
|
56
|
+
constructor(defaultCwd = "") {
|
|
57
|
+
this.#defaultCwd = defaultCwd;
|
|
58
|
+
}
|
|
59
|
+
parse(line) {
|
|
60
|
+
const trimmed = line.trim();
|
|
61
|
+
if (!trimmed) return [];
|
|
62
|
+
let value;
|
|
63
|
+
try {
|
|
64
|
+
value = JSON.parse(trimmed);
|
|
65
|
+
} catch {
|
|
66
|
+
return [{ type: "warning", text: trimmed }];
|
|
67
|
+
}
|
|
68
|
+
const record = asRecord(value);
|
|
69
|
+
if (!record) return [{ type: "message", text: trimmed }];
|
|
70
|
+
const events = [];
|
|
71
|
+
const foundSession = asText(record.session_id) ?? asText(record.sessionId);
|
|
72
|
+
if (foundSession !== void 0 && foundSession !== this.#sessionId) {
|
|
73
|
+
this.#sessionId = foundSession;
|
|
74
|
+
events.push({ type: "session", sessionId: foundSession });
|
|
75
|
+
}
|
|
76
|
+
const type = asText(record.type) ?? "unknown";
|
|
77
|
+
if (type === "system") {
|
|
78
|
+
const subtype = asText(record.subtype);
|
|
79
|
+
if (subtype) events.push({ type: "phase", name: subtype });
|
|
80
|
+
return events;
|
|
81
|
+
}
|
|
82
|
+
if (type === "assistant") {
|
|
83
|
+
for (const block of contentBlocks(record)) {
|
|
84
|
+
if (block.type === "text") {
|
|
85
|
+
const text = asText(block.text);
|
|
86
|
+
if (text) events.push({ type: "message", text });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (block.type !== "tool_use") continue;
|
|
90
|
+
const name = asText(block.name) ?? "tool";
|
|
91
|
+
const id = asText(block.id);
|
|
92
|
+
const input = asRecord(block.input) ?? {};
|
|
93
|
+
if (id) this.#tools.set(id, name);
|
|
94
|
+
const summary = toolSummary(name, input);
|
|
95
|
+
events.push(
|
|
96
|
+
summary ? { type: "tool_start", name, summary } : { type: "tool_start", name }
|
|
97
|
+
);
|
|
98
|
+
const command = name === "Bash" ? asText(input.command) : void 0;
|
|
99
|
+
if (command) {
|
|
100
|
+
events.push({ type: "command", command, cwd: this.#defaultCwd });
|
|
101
|
+
}
|
|
102
|
+
const path = filePath(input);
|
|
103
|
+
if (path && ["Edit", "Write", "NotebookEdit"].includes(name)) {
|
|
104
|
+
events.push({ type: "file_hint", path });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return events;
|
|
108
|
+
}
|
|
109
|
+
if (type === "user") {
|
|
110
|
+
for (const block of contentBlocks(record)) {
|
|
111
|
+
if (block.type !== "tool_result") continue;
|
|
112
|
+
const id = asText(block.tool_use_id);
|
|
113
|
+
const name = (id ? this.#tools.get(id) : void 0) ?? "tool";
|
|
114
|
+
if (id) this.#tools.delete(id);
|
|
115
|
+
events.push({ type: "tool_end", name, ok: block.is_error !== true });
|
|
116
|
+
}
|
|
117
|
+
return events;
|
|
118
|
+
}
|
|
119
|
+
if (type === "result") {
|
|
120
|
+
const usage = asRecord(record.usage);
|
|
121
|
+
if (usage) {
|
|
122
|
+
const cachedInputTokens = asNumber(usage.cache_read_input_tokens);
|
|
123
|
+
events.push({
|
|
124
|
+
type: "usage",
|
|
125
|
+
inputTokens: asNumber(usage.input_tokens) + asNumber(usage.cache_creation_input_tokens) + cachedInputTokens,
|
|
126
|
+
outputTokens: asNumber(usage.output_tokens),
|
|
127
|
+
...cachedInputTokens > 0 ? { cachedInputTokens } : {}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const result = asText(record.result);
|
|
131
|
+
const subtype = asText(record.subtype);
|
|
132
|
+
if (record.is_error === true || subtype?.startsWith("error") === true) {
|
|
133
|
+
events.push({ type: "error", text: result ?? "Claude reported an error" });
|
|
134
|
+
} else {
|
|
135
|
+
events.push(result ? { type: "complete", summary: result } : { type: "complete" });
|
|
136
|
+
}
|
|
137
|
+
return events;
|
|
138
|
+
}
|
|
139
|
+
return events;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// ../../packages/bridge-core/src/agents/claude-adapter.ts
|
|
144
|
+
import { execFile, spawn } from "node:child_process";
|
|
145
|
+
import { promisify } from "node:util";
|
|
146
|
+
|
|
147
|
+
// ../../packages/bridge-core/src/runtime/managed-process.ts
|
|
148
|
+
function safeChildProcessId(child) {
|
|
149
|
+
const pid = child.pid;
|
|
150
|
+
if (pid === void 0 || !Number.isSafeInteger(pid) || pid <= 1 || pid === process.pid) {
|
|
151
|
+
return void 0;
|
|
152
|
+
}
|
|
153
|
+
return pid;
|
|
154
|
+
}
|
|
155
|
+
function safeDetachedProcessGroupId(child) {
|
|
156
|
+
return process.platform === "win32" ? void 0 : safeChildProcessId(child);
|
|
157
|
+
}
|
|
158
|
+
function isMissingProcess(error) {
|
|
159
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ESRCH";
|
|
160
|
+
}
|
|
161
|
+
function processGroupIsAlive(processGroupId) {
|
|
162
|
+
try {
|
|
163
|
+
process.kill(-processGroupId, 0);
|
|
164
|
+
return true;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (isMissingProcess(error)) return false;
|
|
167
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "EPERM") {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function childIsAlive(child) {
|
|
174
|
+
return child.exitCode === null && child.signalCode === null;
|
|
175
|
+
}
|
|
176
|
+
function signalChildProcessTree(child, signal) {
|
|
177
|
+
const pid = safeChildProcessId(child);
|
|
178
|
+
if (pid === void 0) return false;
|
|
179
|
+
try {
|
|
180
|
+
if (process.platform === "win32") {
|
|
181
|
+
if (!childIsAlive(child)) return false;
|
|
182
|
+
return child.kill(signal);
|
|
183
|
+
}
|
|
184
|
+
process.kill(-pid, signal);
|
|
185
|
+
return true;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
if (isMissingProcess(error)) return false;
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function installEmergencyChildExitHook(child, processLike = process) {
|
|
192
|
+
let removed = false;
|
|
193
|
+
const emergencyExit = () => {
|
|
194
|
+
removed = true;
|
|
195
|
+
try {
|
|
196
|
+
signalChildProcessTree(child, "SIGKILL");
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
processLike.once("exit", emergencyExit);
|
|
201
|
+
return () => {
|
|
202
|
+
if (removed) return;
|
|
203
|
+
removed = true;
|
|
204
|
+
processLike.off("exit", emergencyExit);
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
async function waitForProcessTreeExit(child, processGroupId, timeoutMs) {
|
|
208
|
+
const deadline = Date.now() + timeoutMs;
|
|
209
|
+
while (processGroupId === void 0 ? childIsAlive(child) : processGroupIsAlive(processGroupId)) {
|
|
210
|
+
const remaining = deadline - Date.now();
|
|
211
|
+
if (remaining <= 0) return false;
|
|
212
|
+
await new Promise((resolve8) => {
|
|
213
|
+
setTimeout(resolve8, Math.min(25, remaining));
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
async function terminateChildProcessTree(child, killGraceMs = 3e3) {
|
|
219
|
+
const processGroupId = safeDetachedProcessGroupId(child);
|
|
220
|
+
if (processGroupId === void 0 && !childIsAlive(child)) return;
|
|
221
|
+
const sendSignal = (signal) => {
|
|
222
|
+
signalChildProcessTree(child, signal);
|
|
223
|
+
};
|
|
224
|
+
sendSignal("SIGTERM");
|
|
225
|
+
if (await waitForProcessTreeExit(
|
|
226
|
+
child,
|
|
227
|
+
processGroupId,
|
|
228
|
+
Math.max(0, killGraceMs)
|
|
229
|
+
)) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
sendSignal("SIGKILL");
|
|
233
|
+
await waitForProcessTreeExit(
|
|
234
|
+
child,
|
|
235
|
+
processGroupId,
|
|
236
|
+
Math.min(Math.max(0, killGraceMs), 1e3)
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ../../packages/bridge-core/src/agents/async-queue.ts
|
|
241
|
+
var AsyncQueue = class {
|
|
242
|
+
#values = [];
|
|
243
|
+
#waiters = [];
|
|
244
|
+
#ended = false;
|
|
245
|
+
#error;
|
|
246
|
+
push(value) {
|
|
247
|
+
if (this.#ended) return;
|
|
248
|
+
const waiter = this.#waiters.shift();
|
|
249
|
+
if (waiter) waiter.resolve({ value, done: false });
|
|
250
|
+
else this.#values.push(value);
|
|
251
|
+
}
|
|
252
|
+
end(error) {
|
|
253
|
+
if (this.#ended) return;
|
|
254
|
+
this.#ended = true;
|
|
255
|
+
this.#error = error;
|
|
256
|
+
for (const waiter of this.#waiters.splice(0)) {
|
|
257
|
+
if (error !== void 0) waiter.reject(error);
|
|
258
|
+
else waiter.resolve({ value: void 0, done: true });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
[Symbol.asyncIterator]() {
|
|
262
|
+
return {
|
|
263
|
+
next: async () => {
|
|
264
|
+
const value = this.#values.shift();
|
|
265
|
+
if (value !== void 0) return { value, done: false };
|
|
266
|
+
if (this.#ended) {
|
|
267
|
+
if (this.#error !== void 0) throw this.#error;
|
|
268
|
+
return { value: void 0, done: true };
|
|
269
|
+
}
|
|
270
|
+
return await new Promise((resolve8, reject) => {
|
|
271
|
+
this.#waiters.push({ resolve: resolve8, reject });
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// ../../packages/bridge-core/src/agents/claude-adapter.ts
|
|
279
|
+
var execFileAsync = promisify(execFile);
|
|
280
|
+
var INHERITED_ENVIRONMENT = [
|
|
281
|
+
"PATH",
|
|
282
|
+
"HOME",
|
|
283
|
+
"USER",
|
|
284
|
+
"LOGNAME",
|
|
285
|
+
"SHELL",
|
|
286
|
+
"LANG",
|
|
287
|
+
"LC_ALL",
|
|
288
|
+
"TERM",
|
|
289
|
+
"TMPDIR",
|
|
290
|
+
"XDG_CONFIG_HOME",
|
|
291
|
+
"XDG_DATA_HOME",
|
|
292
|
+
"XDG_STATE_HOME",
|
|
293
|
+
"ANTHROPIC_API_KEY",
|
|
294
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
295
|
+
"HTTPS_PROXY",
|
|
296
|
+
"HTTP_PROXY",
|
|
297
|
+
"NO_PROXY",
|
|
298
|
+
"USERPROFILE",
|
|
299
|
+
"APPDATA",
|
|
300
|
+
"LOCALAPPDATA",
|
|
301
|
+
"SystemRoot",
|
|
302
|
+
"COMSPEC",
|
|
303
|
+
"PATHEXT"
|
|
304
|
+
];
|
|
305
|
+
var EMPTY_MCP_CONFIG = JSON.stringify({ mcpServers: {} });
|
|
306
|
+
var CLAUDE_SETTINGS = JSON.stringify({
|
|
307
|
+
permissions: {
|
|
308
|
+
disableBypassPermissionsMode: "disable",
|
|
309
|
+
deny: [
|
|
310
|
+
"Read(./.env)",
|
|
311
|
+
"Read(./.env.*)",
|
|
312
|
+
"Read(./**/*.pem)",
|
|
313
|
+
"Read(./**/*.key)",
|
|
314
|
+
"Edit(./.git/**)",
|
|
315
|
+
"Edit(./.visualdev/runtime/**)",
|
|
316
|
+
"Edit(./node_modules/**)"
|
|
317
|
+
]
|
|
318
|
+
},
|
|
319
|
+
sandbox: {
|
|
320
|
+
enabled: true,
|
|
321
|
+
autoAllowBashIfSandboxed: true,
|
|
322
|
+
allowUnsandboxedCommands: false,
|
|
323
|
+
network: { strictAllowlist: true }
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
function processEnv(overrides) {
|
|
327
|
+
const environment = {};
|
|
328
|
+
for (const key of INHERITED_ENVIRONMENT) {
|
|
329
|
+
const value = process.env[key];
|
|
330
|
+
if (value !== void 0) environment[key] = value;
|
|
331
|
+
}
|
|
332
|
+
return { ...environment, ...overrides };
|
|
333
|
+
}
|
|
334
|
+
function splitLines(chunk, previous, onLine) {
|
|
335
|
+
const combined = previous + chunk.toString();
|
|
336
|
+
const lines = combined.split(/\r?\n/);
|
|
337
|
+
const remainder = lines.pop() ?? "";
|
|
338
|
+
for (const line of lines) onLine(line);
|
|
339
|
+
return remainder;
|
|
340
|
+
}
|
|
341
|
+
var ClaudeAdapter = class {
|
|
342
|
+
id = "claude";
|
|
343
|
+
#executable;
|
|
344
|
+
#killGraceMs;
|
|
345
|
+
#model;
|
|
346
|
+
#reasoningEffort;
|
|
347
|
+
#rtkExecutable;
|
|
348
|
+
#rtkVersion;
|
|
349
|
+
constructor(options = {}) {
|
|
350
|
+
this.#executable = options.executable ?? "claude";
|
|
351
|
+
this.#killGraceMs = options.killGraceMs ?? 2e3;
|
|
352
|
+
this.#model = options.model;
|
|
353
|
+
this.#reasoningEffort = options.reasoningEffort;
|
|
354
|
+
this.#rtkExecutable = options.rtkExecutable ?? "rtk";
|
|
355
|
+
}
|
|
356
|
+
#probeRtk(environment) {
|
|
357
|
+
if (this.#rtkExecutable === false) return Promise.resolve(void 0);
|
|
358
|
+
this.#rtkVersion ??= execFileAsync(this.#rtkExecutable, ["--version"], {
|
|
359
|
+
encoding: "utf8",
|
|
360
|
+
env: environment,
|
|
361
|
+
timeout: 1e3,
|
|
362
|
+
windowsHide: true,
|
|
363
|
+
maxBuffer: 16 * 1024
|
|
364
|
+
}).then(({ stdout }) => stdout.trim().split(/\r?\n/, 1)[0] || void 0).catch(() => void 0);
|
|
365
|
+
return this.#rtkVersion;
|
|
366
|
+
}
|
|
367
|
+
async #runtimePrompt(input, environment) {
|
|
368
|
+
if (this.#rtkExecutable === false) return input.prompt;
|
|
369
|
+
const guidance = await this.#probeRtk(environment).then(
|
|
370
|
+
(version) => version ? `RTK command proxy:
|
|
371
|
+
- ${version} is installed and available in this runtime.
|
|
372
|
+
- Prefix shell commands with RTK by default (for example: rtk git status, rtk rg <pattern>, rtk read <file>, rtk npm test).
|
|
373
|
+
- 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:
|
|
374
|
+
- RTK was not detected in this runtime. Use native repository commands directly and do not spend time searching for RTK.`
|
|
375
|
+
);
|
|
376
|
+
return `${input.prompt.trimEnd()}
|
|
377
|
+
|
|
378
|
+
${guidance}
|
|
379
|
+
`;
|
|
380
|
+
}
|
|
381
|
+
#baseArgs() {
|
|
382
|
+
return [
|
|
383
|
+
"-p",
|
|
384
|
+
"--output-format",
|
|
385
|
+
"stream-json",
|
|
386
|
+
"--verbose",
|
|
387
|
+
"--permission-mode",
|
|
388
|
+
"acceptEdits",
|
|
389
|
+
"--strict-mcp-config",
|
|
390
|
+
"--mcp-config",
|
|
391
|
+
EMPTY_MCP_CONFIG,
|
|
392
|
+
"--no-chrome",
|
|
393
|
+
"--tools",
|
|
394
|
+
"Read,Glob,Grep,Edit,Write,Bash",
|
|
395
|
+
"--settings",
|
|
396
|
+
CLAUDE_SETTINGS,
|
|
397
|
+
...this.#model === void 0 ? [] : ["--model", this.#model],
|
|
398
|
+
...this.#reasoningEffort === void 0 ? [] : ["--effort", this.#reasoningEffort]
|
|
399
|
+
];
|
|
400
|
+
}
|
|
401
|
+
async probe() {
|
|
402
|
+
return await new Promise((resolve8) => {
|
|
403
|
+
const child = spawn(this.#executable, ["--version"], {
|
|
404
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
405
|
+
shell: false
|
|
406
|
+
});
|
|
407
|
+
let output = "";
|
|
408
|
+
child.stdout?.on("data", (chunk) => {
|
|
409
|
+
output += chunk.toString();
|
|
410
|
+
});
|
|
411
|
+
child.once("error", () => {
|
|
412
|
+
resolve8({ available: false, supportsResume: true, structuredOutput: true });
|
|
413
|
+
});
|
|
414
|
+
child.once("close", (code) => {
|
|
415
|
+
const match = output.match(/(\d+\.\d+\.\d+(?:[-+][^\s]+)?)/);
|
|
416
|
+
const capabilities = {
|
|
417
|
+
available: code === 0,
|
|
418
|
+
supportsResume: true,
|
|
419
|
+
structuredOutput: true
|
|
420
|
+
};
|
|
421
|
+
if (match?.[1]) capabilities.version = match[1];
|
|
422
|
+
resolve8(capabilities);
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
async *run(input, signal) {
|
|
427
|
+
yield* this.#execute(input, signal, this.#baseArgs());
|
|
428
|
+
}
|
|
429
|
+
async *resume(input, signal) {
|
|
430
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(input.sessionId)) {
|
|
431
|
+
throw new Error("Refusing to resume an invalid Claude session id");
|
|
432
|
+
}
|
|
433
|
+
yield* this.#execute(
|
|
434
|
+
input,
|
|
435
|
+
signal,
|
|
436
|
+
[...this.#baseArgs(), "--resume", input.sessionId]
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
async *#execute(input, signal, args) {
|
|
440
|
+
const queue = new AsyncQueue();
|
|
441
|
+
const environment = processEnv(input.environment);
|
|
442
|
+
const prompt = await this.#runtimePrompt(input, environment);
|
|
443
|
+
const parser = new ClaudeEventParser(input.workspaceRoot);
|
|
444
|
+
const child = spawn(this.#executable, args, {
|
|
445
|
+
cwd: input.workspaceRoot,
|
|
446
|
+
env: environment,
|
|
447
|
+
detached: process.platform !== "win32",
|
|
448
|
+
shell: false,
|
|
449
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
450
|
+
});
|
|
451
|
+
const removeEmergencyExitHook = installEmergencyChildExitHook(child);
|
|
452
|
+
let stdoutRemainder = "";
|
|
453
|
+
let stderrRemainder = "";
|
|
454
|
+
let timedOut = false;
|
|
455
|
+
let aborted = signal.aborted;
|
|
456
|
+
let termination;
|
|
457
|
+
const requestTermination = () => {
|
|
458
|
+
termination ??= terminateChildProcessTree(
|
|
459
|
+
child,
|
|
460
|
+
this.#killGraceMs
|
|
461
|
+
).finally(removeEmergencyExitHook);
|
|
462
|
+
return termination;
|
|
463
|
+
};
|
|
464
|
+
const timeout = setTimeout(() => {
|
|
465
|
+
timedOut = true;
|
|
466
|
+
void requestTermination();
|
|
467
|
+
}, Math.max(1, input.maxRunMs));
|
|
468
|
+
timeout.unref();
|
|
469
|
+
const abort = () => {
|
|
470
|
+
aborted = true;
|
|
471
|
+
void requestTermination();
|
|
472
|
+
};
|
|
473
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
474
|
+
if (signal.aborted) abort();
|
|
475
|
+
child.stdout.on("data", (chunk) => {
|
|
476
|
+
stdoutRemainder = splitLines(chunk, stdoutRemainder, (line) => {
|
|
477
|
+
for (const event of parser.parse(line)) queue.push(event);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
child.stderr.on("data", (chunk) => {
|
|
481
|
+
stderrRemainder = splitLines(chunk, stderrRemainder, (line) => {
|
|
482
|
+
if (line.trim()) queue.push({ type: "warning", text: line });
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
child.once("error", (error) => queue.end(error));
|
|
486
|
+
child.once("close", (code, closeSignal) => {
|
|
487
|
+
clearTimeout(timeout);
|
|
488
|
+
signal.removeEventListener("abort", abort);
|
|
489
|
+
void (async () => {
|
|
490
|
+
await requestTermination();
|
|
491
|
+
if (stdoutRemainder.trim()) {
|
|
492
|
+
for (const event of parser.parse(stdoutRemainder)) queue.push(event);
|
|
493
|
+
}
|
|
494
|
+
if (stderrRemainder.trim()) queue.push({ type: "warning", text: stderrRemainder });
|
|
495
|
+
if (timedOut) queue.end(new AgentTimeoutError());
|
|
496
|
+
else if (aborted) queue.end(new AgentCanceledError());
|
|
497
|
+
else if (code !== 0) {
|
|
498
|
+
queue.end(
|
|
499
|
+
new AgentProcessError(
|
|
500
|
+
`Claude exited with code ${String(code)}`,
|
|
501
|
+
code,
|
|
502
|
+
closeSignal
|
|
503
|
+
)
|
|
504
|
+
);
|
|
505
|
+
} else {
|
|
506
|
+
queue.end();
|
|
507
|
+
}
|
|
508
|
+
})().catch((error) => queue.end(error));
|
|
509
|
+
});
|
|
510
|
+
child.stdin.on("error", (error) => {
|
|
511
|
+
if (error.code !== "EPIPE") queue.end(error);
|
|
512
|
+
});
|
|
513
|
+
child.stdin.end(prompt);
|
|
514
|
+
try {
|
|
515
|
+
for await (const event of queue) yield event;
|
|
516
|
+
} finally {
|
|
517
|
+
clearTimeout(timeout);
|
|
518
|
+
signal.removeEventListener("abort", abort);
|
|
519
|
+
try {
|
|
520
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
521
|
+
await requestTermination();
|
|
522
|
+
} else if (termination) {
|
|
523
|
+
await termination;
|
|
524
|
+
}
|
|
525
|
+
} finally {
|
|
526
|
+
removeEmergencyExitHook();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
|
|
28
532
|
// ../../packages/bridge-core/src/agents/codex-event-parser.ts
|
|
29
|
-
function
|
|
533
|
+
function asRecord2(value) {
|
|
30
534
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
31
535
|
}
|
|
32
|
-
function
|
|
536
|
+
function asText2(value) {
|
|
33
537
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
34
538
|
}
|
|
35
539
|
function sessionId(record) {
|
|
36
|
-
const thread =
|
|
37
|
-
return
|
|
540
|
+
const thread = asRecord2(record.thread);
|
|
541
|
+
return asText2(record.thread_id) ?? asText2(record.threadId) ?? asText2(record.session_id) ?? asText2(record.sessionId) ?? (thread ? asText2(thread.id) : void 0);
|
|
38
542
|
}
|
|
39
543
|
function itemFiles(item) {
|
|
40
544
|
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
41
545
|
const files = changes.flatMap((change) => {
|
|
42
|
-
const record =
|
|
546
|
+
const record = asRecord2(change);
|
|
43
547
|
if (!record) return [];
|
|
44
|
-
return [
|
|
548
|
+
return [asText2(record.path) ?? asText2(record.file_path) ?? asText2(record.filePath)].filter(
|
|
45
549
|
(path) => path !== void 0
|
|
46
550
|
);
|
|
47
551
|
});
|
|
48
|
-
const direct =
|
|
552
|
+
const direct = asText2(item.path) ?? asText2(item.file_path) ?? asText2(item.filePath);
|
|
49
553
|
if (direct) files.push(direct);
|
|
50
554
|
return [...new Set(files)];
|
|
51
555
|
}
|
|
@@ -59,28 +563,28 @@ function isDirectExecItem(item) {
|
|
|
59
563
|
return item.type === "mcp_tool_call" && item.server === "visual_remote_exec" && item.tool === "run_readonly";
|
|
60
564
|
}
|
|
61
565
|
function directExecSummary(item) {
|
|
62
|
-
const arguments_ =
|
|
566
|
+
const arguments_ = asRecord2(item.arguments);
|
|
63
567
|
const commands = Array.isArray(arguments_?.commands) ? arguments_.commands : [];
|
|
64
568
|
const summaries = commands.flatMap((candidate) => {
|
|
65
|
-
const command =
|
|
569
|
+
const command = asRecord2(candidate);
|
|
66
570
|
const argv = stringArray(command?.argv);
|
|
67
571
|
return argv === void 0 ? [] : [formatArgv(argv)];
|
|
68
572
|
});
|
|
69
573
|
return summaries.length === 0 ? void 0 : summaries.join(" \xB7 ");
|
|
70
574
|
}
|
|
71
575
|
function directExecResults(item, defaultCwd) {
|
|
72
|
-
const result =
|
|
73
|
-
const structured =
|
|
576
|
+
const result = asRecord2(item.result);
|
|
577
|
+
const structured = asRecord2(result?.structured_content ?? result?.structuredContent);
|
|
74
578
|
const results = Array.isArray(structured?.results) ? structured.results : [];
|
|
75
579
|
return results.flatMap((candidate) => {
|
|
76
|
-
const command =
|
|
580
|
+
const command = asRecord2(candidate);
|
|
77
581
|
const argv = stringArray(command?.argv);
|
|
78
582
|
if (argv === void 0) return [];
|
|
79
583
|
const exitCode = typeof command?.exitCode === "number" ? command.exitCode : void 0;
|
|
80
584
|
const durationMs = typeof command?.durationMs === "number" ? command.durationMs : void 0;
|
|
81
585
|
return [{
|
|
82
586
|
command: formatArgv(argv),
|
|
83
|
-
cwd:
|
|
587
|
+
cwd: asText2(command?.cwd) ?? defaultCwd,
|
|
84
588
|
ok: exitCode === 0,
|
|
85
589
|
...exitCode === void 0 ? {} : { exitCode },
|
|
86
590
|
...durationMs === void 0 ? {} : { durationMs },
|
|
@@ -91,8 +595,8 @@ function directExecResults(item, defaultCwd) {
|
|
|
91
595
|
});
|
|
92
596
|
}
|
|
93
597
|
function normalizedUsage(record) {
|
|
94
|
-
const result =
|
|
95
|
-
const usage =
|
|
598
|
+
const result = asRecord2(record.result);
|
|
599
|
+
const usage = asRecord2(record.usage) ?? (result ? asRecord2(result.usage) : void 0);
|
|
96
600
|
if (usage === void 0) return void 0;
|
|
97
601
|
const inputTokens = usage.input_tokens ?? usage.inputTokens;
|
|
98
602
|
const outputTokens = usage.output_tokens ?? usage.outputTokens;
|
|
@@ -116,17 +620,17 @@ function parseCodexJsonLine(line, defaultCwd = "") {
|
|
|
116
620
|
} catch {
|
|
117
621
|
return [{ type: "warning", text: trimmed }];
|
|
118
622
|
}
|
|
119
|
-
const record =
|
|
623
|
+
const record = asRecord2(value);
|
|
120
624
|
if (!record) return [{ type: "message", text: trimmed }];
|
|
121
|
-
const type =
|
|
625
|
+
const type = asText2(record.type) ?? "unknown";
|
|
122
626
|
const events = [];
|
|
123
627
|
const foundSession = sessionId(record);
|
|
124
628
|
if (foundSession) events.push({ type: "session", sessionId: foundSession });
|
|
125
629
|
if (type === "thread.started" || type === "thread.created") return events;
|
|
126
630
|
if (type === "turn.started") return [...events, { type: "phase", name: "turn.started" }];
|
|
127
631
|
if (type === "turn.completed") {
|
|
128
|
-
const result =
|
|
129
|
-
const summary =
|
|
632
|
+
const result = asRecord2(record.result);
|
|
633
|
+
const summary = asText2(record.summary) ?? (result ? asText2(result.summary) : void 0);
|
|
130
634
|
const usage = normalizedUsage(record);
|
|
131
635
|
return [
|
|
132
636
|
...events,
|
|
@@ -135,31 +639,31 @@ function parseCodexJsonLine(line, defaultCwd = "") {
|
|
|
135
639
|
];
|
|
136
640
|
}
|
|
137
641
|
if (type === "turn.failed" || type === "error") {
|
|
138
|
-
const error =
|
|
139
|
-
const text =
|
|
642
|
+
const error = asRecord2(record.error);
|
|
643
|
+
const text = asText2(record.message) ?? (error ? asText2(error.message) : void 0) ?? "Codex reported an error";
|
|
140
644
|
return [...events, { type: "error", text }];
|
|
141
645
|
}
|
|
142
|
-
const item =
|
|
646
|
+
const item = asRecord2(record.item);
|
|
143
647
|
if (type === "item.started" && item) {
|
|
144
|
-
const itemType =
|
|
648
|
+
const itemType = asText2(item.type) ?? "item";
|
|
145
649
|
const directExec = isDirectExecItem(item);
|
|
146
|
-
const summary = directExec ? directExecSummary(item) :
|
|
650
|
+
const summary = directExec ? directExecSummary(item) : asText2(item.command) ?? asText2(item.text);
|
|
147
651
|
const start = summary ? { type: "tool_start", name: directExec ? "direct_exec" : itemType, summary } : { type: "tool_start", name: directExec ? "direct_exec" : itemType };
|
|
148
652
|
return [...events, start];
|
|
149
653
|
}
|
|
150
654
|
if (type === "item.completed" && item) {
|
|
151
|
-
const itemType =
|
|
655
|
+
const itemType = asText2(item.type) ?? "item";
|
|
152
656
|
if (itemType === "agent_message") {
|
|
153
|
-
const text =
|
|
657
|
+
const text = asText2(item.text) ?? asText2(item.message);
|
|
154
658
|
return text ? [...events, { type: "message", text }] : events;
|
|
155
659
|
}
|
|
156
660
|
if (itemType === "command_execution") {
|
|
157
|
-
const command =
|
|
661
|
+
const command = asText2(item.command);
|
|
158
662
|
if (command) {
|
|
159
663
|
events.push({
|
|
160
664
|
type: "command",
|
|
161
665
|
command,
|
|
162
|
-
cwd:
|
|
666
|
+
cwd: asText2(item.cwd) ?? defaultCwd
|
|
163
667
|
});
|
|
164
668
|
}
|
|
165
669
|
}
|
|
@@ -185,151 +689,18 @@ function parseCodexJsonLine(line, defaultCwd = "") {
|
|
|
185
689
|
});
|
|
186
690
|
return events;
|
|
187
691
|
}
|
|
188
|
-
const message =
|
|
692
|
+
const message = asText2(record.message);
|
|
189
693
|
if (message) events.push({ type: "message", text: message });
|
|
190
694
|
return events;
|
|
191
695
|
}
|
|
192
696
|
|
|
193
697
|
// ../../packages/bridge-core/src/agents/codex-adapter.ts
|
|
194
|
-
import { execFile, spawn } from "node:child_process";
|
|
698
|
+
import { execFile as execFile2, spawn as spawn2 } from "node:child_process";
|
|
195
699
|
import { existsSync } from "node:fs";
|
|
196
700
|
import { fileURLToPath } from "node:url";
|
|
197
|
-
import { promisify } from "node:util";
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
var AsyncQueue = class {
|
|
201
|
-
#values = [];
|
|
202
|
-
#waiters = [];
|
|
203
|
-
#ended = false;
|
|
204
|
-
#error;
|
|
205
|
-
push(value) {
|
|
206
|
-
if (this.#ended) return;
|
|
207
|
-
const waiter = this.#waiters.shift();
|
|
208
|
-
if (waiter) waiter.resolve({ value, done: false });
|
|
209
|
-
else this.#values.push(value);
|
|
210
|
-
}
|
|
211
|
-
end(error) {
|
|
212
|
-
if (this.#ended) return;
|
|
213
|
-
this.#ended = true;
|
|
214
|
-
this.#error = error;
|
|
215
|
-
for (const waiter of this.#waiters.splice(0)) {
|
|
216
|
-
if (error !== void 0) waiter.reject(error);
|
|
217
|
-
else waiter.resolve({ value: void 0, done: true });
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
[Symbol.asyncIterator]() {
|
|
221
|
-
return {
|
|
222
|
-
next: async () => {
|
|
223
|
-
const value = this.#values.shift();
|
|
224
|
-
if (value !== void 0) return { value, done: false };
|
|
225
|
-
if (this.#ended) {
|
|
226
|
-
if (this.#error !== void 0) throw this.#error;
|
|
227
|
-
return { value: void 0, done: true };
|
|
228
|
-
}
|
|
229
|
-
return await new Promise((resolve8, reject) => {
|
|
230
|
-
this.#waiters.push({ resolve: resolve8, reject });
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
// ../../packages/bridge-core/src/runtime/managed-process.ts
|
|
238
|
-
function safeChildProcessId(child) {
|
|
239
|
-
const pid = child.pid;
|
|
240
|
-
if (pid === void 0 || !Number.isSafeInteger(pid) || pid <= 1 || pid === process.pid) {
|
|
241
|
-
return void 0;
|
|
242
|
-
}
|
|
243
|
-
return pid;
|
|
244
|
-
}
|
|
245
|
-
function safeDetachedProcessGroupId(child) {
|
|
246
|
-
return process.platform === "win32" ? void 0 : safeChildProcessId(child);
|
|
247
|
-
}
|
|
248
|
-
function isMissingProcess(error) {
|
|
249
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ESRCH";
|
|
250
|
-
}
|
|
251
|
-
function processGroupIsAlive(processGroupId) {
|
|
252
|
-
try {
|
|
253
|
-
process.kill(-processGroupId, 0);
|
|
254
|
-
return true;
|
|
255
|
-
} catch (error) {
|
|
256
|
-
if (isMissingProcess(error)) return false;
|
|
257
|
-
if (typeof error === "object" && error !== null && "code" in error && error.code === "EPERM") {
|
|
258
|
-
return true;
|
|
259
|
-
}
|
|
260
|
-
throw error;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
function childIsAlive(child) {
|
|
264
|
-
return child.exitCode === null && child.signalCode === null;
|
|
265
|
-
}
|
|
266
|
-
function signalChildProcessTree(child, signal) {
|
|
267
|
-
const pid = safeChildProcessId(child);
|
|
268
|
-
if (pid === void 0) return false;
|
|
269
|
-
try {
|
|
270
|
-
if (process.platform === "win32") {
|
|
271
|
-
if (!childIsAlive(child)) return false;
|
|
272
|
-
return child.kill(signal);
|
|
273
|
-
}
|
|
274
|
-
process.kill(-pid, signal);
|
|
275
|
-
return true;
|
|
276
|
-
} catch (error) {
|
|
277
|
-
if (isMissingProcess(error)) return false;
|
|
278
|
-
throw error;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
function installEmergencyChildExitHook(child, processLike = process) {
|
|
282
|
-
let removed = false;
|
|
283
|
-
const emergencyExit = () => {
|
|
284
|
-
removed = true;
|
|
285
|
-
try {
|
|
286
|
-
signalChildProcessTree(child, "SIGKILL");
|
|
287
|
-
} catch {
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
processLike.once("exit", emergencyExit);
|
|
291
|
-
return () => {
|
|
292
|
-
if (removed) return;
|
|
293
|
-
removed = true;
|
|
294
|
-
processLike.off("exit", emergencyExit);
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
async function waitForProcessTreeExit(child, processGroupId, timeoutMs) {
|
|
298
|
-
const deadline = Date.now() + timeoutMs;
|
|
299
|
-
while (processGroupId === void 0 ? childIsAlive(child) : processGroupIsAlive(processGroupId)) {
|
|
300
|
-
const remaining = deadline - Date.now();
|
|
301
|
-
if (remaining <= 0) return false;
|
|
302
|
-
await new Promise((resolve8) => {
|
|
303
|
-
setTimeout(resolve8, Math.min(25, remaining));
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
return true;
|
|
307
|
-
}
|
|
308
|
-
async function terminateChildProcessTree(child, killGraceMs = 3e3) {
|
|
309
|
-
const processGroupId = safeDetachedProcessGroupId(child);
|
|
310
|
-
if (processGroupId === void 0 && !childIsAlive(child)) return;
|
|
311
|
-
const sendSignal = (signal) => {
|
|
312
|
-
signalChildProcessTree(child, signal);
|
|
313
|
-
};
|
|
314
|
-
sendSignal("SIGTERM");
|
|
315
|
-
if (await waitForProcessTreeExit(
|
|
316
|
-
child,
|
|
317
|
-
processGroupId,
|
|
318
|
-
Math.max(0, killGraceMs)
|
|
319
|
-
)) {
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
sendSignal("SIGKILL");
|
|
323
|
-
await waitForProcessTreeExit(
|
|
324
|
-
child,
|
|
325
|
-
processGroupId,
|
|
326
|
-
Math.min(Math.max(0, killGraceMs), 1e3)
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// ../../packages/bridge-core/src/agents/codex-adapter.ts
|
|
331
|
-
var execFileAsync = promisify(execFile);
|
|
332
|
-
var INHERITED_ENVIRONMENT = [
|
|
701
|
+
import { promisify as promisify2 } from "node:util";
|
|
702
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
703
|
+
var INHERITED_ENVIRONMENT2 = [
|
|
333
704
|
"PATH",
|
|
334
705
|
"HOME",
|
|
335
706
|
"USER",
|
|
@@ -354,15 +725,15 @@ var INHERITED_ENVIRONMENT = [
|
|
|
354
725
|
"COMSPEC",
|
|
355
726
|
"PATHEXT"
|
|
356
727
|
];
|
|
357
|
-
function
|
|
728
|
+
function processEnv2(overrides) {
|
|
358
729
|
const environment = {};
|
|
359
|
-
for (const key of
|
|
730
|
+
for (const key of INHERITED_ENVIRONMENT2) {
|
|
360
731
|
const value = process.env[key];
|
|
361
732
|
if (value !== void 0) environment[key] = value;
|
|
362
733
|
}
|
|
363
734
|
return { ...environment, ...overrides };
|
|
364
735
|
}
|
|
365
|
-
function
|
|
736
|
+
function splitLines2(chunk, previous, onLine) {
|
|
366
737
|
const combined = previous + chunk.toString();
|
|
367
738
|
const lines = combined.split(/\r?\n/);
|
|
368
739
|
const remainder = lines.pop() ?? "";
|
|
@@ -382,18 +753,24 @@ var CodexAdapter = class {
|
|
|
382
753
|
id = "codex";
|
|
383
754
|
#executable;
|
|
384
755
|
#killGraceMs;
|
|
756
|
+
#model;
|
|
757
|
+
#profile;
|
|
758
|
+
#reasoningEffort;
|
|
385
759
|
#rtkExecutable;
|
|
386
760
|
#directExecMcpScript;
|
|
387
761
|
#rtkVersion;
|
|
388
762
|
constructor(options = {}) {
|
|
389
763
|
this.#executable = options.executable ?? "codex";
|
|
390
764
|
this.#killGraceMs = options.killGraceMs ?? 2e3;
|
|
765
|
+
this.#model = options.model;
|
|
766
|
+
this.#profile = options.profile;
|
|
767
|
+
this.#reasoningEffort = options.reasoningEffort;
|
|
391
768
|
this.#rtkExecutable = options.rtkExecutable ?? "rtk";
|
|
392
769
|
this.#directExecMcpScript = options.directExecMcpScript === false ? void 0 : options.directExecMcpScript ?? defaultDirectExecMcpScript();
|
|
393
770
|
}
|
|
394
771
|
#probeRtk(environment) {
|
|
395
772
|
if (this.#rtkExecutable === false) return Promise.resolve(void 0);
|
|
396
|
-
this.#rtkVersion ??=
|
|
773
|
+
this.#rtkVersion ??= execFileAsync2(this.#rtkExecutable, ["--version"], {
|
|
397
774
|
encoding: "utf8",
|
|
398
775
|
env: environment,
|
|
399
776
|
timeout: 1e3,
|
|
@@ -436,9 +813,16 @@ ${guidance}
|
|
|
436
813
|
`mcp_servers.visual_remote_exec.args=${JSON.stringify(serverArgs)}`
|
|
437
814
|
];
|
|
438
815
|
}
|
|
816
|
+
#modelConfig() {
|
|
817
|
+
return [
|
|
818
|
+
...this.#profile === void 0 ? [] : ["--profile", this.#profile],
|
|
819
|
+
...this.#model === void 0 ? [] : ["--model", this.#model],
|
|
820
|
+
...this.#reasoningEffort === void 0 ? [] : ["-c", `model_reasoning_effort=${JSON.stringify(this.#reasoningEffort)}`]
|
|
821
|
+
];
|
|
822
|
+
}
|
|
439
823
|
async probe() {
|
|
440
824
|
return await new Promise((resolve8) => {
|
|
441
|
-
const child =
|
|
825
|
+
const child = spawn2(this.#executable, ["--version"], {
|
|
442
826
|
stdio: ["ignore", "pipe", "ignore"],
|
|
443
827
|
shell: false
|
|
444
828
|
});
|
|
@@ -471,6 +855,7 @@ ${guidance}
|
|
|
471
855
|
"workspace-write",
|
|
472
856
|
"-C",
|
|
473
857
|
input.workspaceRoot,
|
|
858
|
+
...this.#modelConfig(),
|
|
474
859
|
...this.#directExecConfig(input),
|
|
475
860
|
"-"
|
|
476
861
|
];
|
|
@@ -489,6 +874,7 @@ ${guidance}
|
|
|
489
874
|
"workspace-write",
|
|
490
875
|
"-C",
|
|
491
876
|
input.workspaceRoot,
|
|
877
|
+
...this.#modelConfig(),
|
|
492
878
|
...this.#directExecConfig(input),
|
|
493
879
|
"resume",
|
|
494
880
|
input.sessionId,
|
|
@@ -498,9 +884,9 @@ ${guidance}
|
|
|
498
884
|
}
|
|
499
885
|
async *#execute(input, signal, args) {
|
|
500
886
|
const queue = new AsyncQueue();
|
|
501
|
-
const environment =
|
|
887
|
+
const environment = processEnv2(input.environment);
|
|
502
888
|
const prompt = await this.#runtimePrompt(input, environment);
|
|
503
|
-
const child =
|
|
889
|
+
const child = spawn2(this.#executable, args, {
|
|
504
890
|
cwd: input.workspaceRoot,
|
|
505
891
|
env: environment,
|
|
506
892
|
detached: process.platform !== "win32",
|
|
@@ -532,12 +918,12 @@ ${guidance}
|
|
|
532
918
|
signal.addEventListener("abort", abort, { once: true });
|
|
533
919
|
if (signal.aborted) abort();
|
|
534
920
|
child.stdout.on("data", (chunk) => {
|
|
535
|
-
stdoutRemainder =
|
|
921
|
+
stdoutRemainder = splitLines2(chunk, stdoutRemainder, (line) => {
|
|
536
922
|
for (const event of parseCodexJsonLine(line, input.workspaceRoot)) queue.push(event);
|
|
537
923
|
});
|
|
538
924
|
});
|
|
539
925
|
child.stderr.on("data", (chunk) => {
|
|
540
|
-
stderrRemainder =
|
|
926
|
+
stderrRemainder = splitLines2(chunk, stderrRemainder, (line) => {
|
|
541
927
|
if (line.trim()) queue.push({ type: "warning", text: line });
|
|
542
928
|
});
|
|
543
929
|
});
|
|
@@ -615,6 +1001,7 @@ import { ZodError } from "zod";
|
|
|
615
1001
|
import { z } from "zod";
|
|
616
1002
|
var servicePortSchema = z.number().int().min(10001).max(65535);
|
|
617
1003
|
var commandSchema = z.array(z.string().min(1)).min(1);
|
|
1004
|
+
var environmentVariableSchema = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "Expected an environment variable name");
|
|
618
1005
|
var readySchema = z.object({
|
|
619
1006
|
path: z.string().startsWith("/").default("/"),
|
|
620
1007
|
timeoutMs: z.number().int().positive().default(6e4)
|
|
@@ -643,9 +1030,38 @@ var visualDevConfigSchema = z.object({
|
|
|
643
1030
|
}).strict(),
|
|
644
1031
|
agent: z.object({
|
|
645
1032
|
adapter: z.enum(["codex", "claude", "opencode"]),
|
|
1033
|
+
model: z.string().trim().min(1).optional(),
|
|
1034
|
+
reasoningEffort: z.enum(["minimal", "low", "medium", "high", "xhigh", "max"]).optional(),
|
|
1035
|
+
profile: z.string().trim().regex(
|
|
1036
|
+
/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/,
|
|
1037
|
+
"Expected a Codex profile name"
|
|
1038
|
+
).optional(),
|
|
1039
|
+
inheritEnv: z.array(environmentVariableSchema).default([]),
|
|
646
1040
|
maxRunMs: z.number().int().positive(),
|
|
647
1041
|
resumeMode: z.enum(["auto", "new"]).default("auto")
|
|
648
|
-
}).strict(),
|
|
1042
|
+
}).strict().superRefine((agent, context) => {
|
|
1043
|
+
if (agent.adapter === "claude" && agent.reasoningEffort === "minimal") {
|
|
1044
|
+
context.addIssue({
|
|
1045
|
+
code: "custom",
|
|
1046
|
+
path: ["reasoningEffort"],
|
|
1047
|
+
message: "minimal reasoning effort is only supported by the Codex adapter"
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
if (agent.adapter === "codex" && agent.reasoningEffort === "max") {
|
|
1051
|
+
context.addIssue({
|
|
1052
|
+
code: "custom",
|
|
1053
|
+
path: ["reasoningEffort"],
|
|
1054
|
+
message: "max reasoning effort is only supported by the Claude adapter"
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
if (agent.adapter !== "codex" && agent.profile !== void 0) {
|
|
1058
|
+
context.addIssue({
|
|
1059
|
+
code: "custom",
|
|
1060
|
+
path: ["profile"],
|
|
1061
|
+
message: "agent.profile is only supported by the Codex adapter"
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
}),
|
|
649
1065
|
queue: z.object({
|
|
650
1066
|
maxPending: z.number().int().positive()
|
|
651
1067
|
}).strict(),
|
|
@@ -689,6 +1105,7 @@ function createDefaultConfig(projectId) {
|
|
|
689
1105
|
},
|
|
690
1106
|
agent: {
|
|
691
1107
|
adapter: "codex",
|
|
1108
|
+
inheritEnv: [],
|
|
692
1109
|
maxRunMs: 9e5,
|
|
693
1110
|
resumeMode: "auto"
|
|
694
1111
|
},
|
|
@@ -761,17 +1178,17 @@ function mergeConfigValues(base, override) {
|
|
|
761
1178
|
}
|
|
762
1179
|
return merged;
|
|
763
1180
|
}
|
|
764
|
-
async function readYamlMapping(
|
|
1181
|
+
async function readYamlMapping(filePath2, required) {
|
|
765
1182
|
let source;
|
|
766
1183
|
try {
|
|
767
|
-
source = await readFile(
|
|
1184
|
+
source = await readFile(filePath2, "utf8");
|
|
768
1185
|
} catch (error) {
|
|
769
1186
|
if (!required && typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
770
1187
|
return void 0;
|
|
771
1188
|
}
|
|
772
|
-
throw new VisualDevConfigError(`Unable to read config: ${
|
|
1189
|
+
throw new VisualDevConfigError(`Unable to read config: ${filePath2}`, {
|
|
773
1190
|
cause: error,
|
|
774
|
-
filePath
|
|
1191
|
+
filePath: filePath2
|
|
775
1192
|
});
|
|
776
1193
|
}
|
|
777
1194
|
try {
|
|
@@ -784,9 +1201,9 @@ async function readYamlMapping(filePath, required) {
|
|
|
784
1201
|
}
|
|
785
1202
|
return value;
|
|
786
1203
|
} catch (error) {
|
|
787
|
-
throw new VisualDevConfigError(`Invalid YAML in ${
|
|
1204
|
+
throw new VisualDevConfigError(`Invalid YAML in ${filePath2}`, {
|
|
788
1205
|
cause: error,
|
|
789
|
-
filePath
|
|
1206
|
+
filePath: filePath2
|
|
790
1207
|
});
|
|
791
1208
|
}
|
|
792
1209
|
}
|
|
@@ -925,10 +1342,10 @@ var RevertConflictError = class extends RepositorySafetyError {
|
|
|
925
1342
|
};
|
|
926
1343
|
|
|
927
1344
|
// ../../packages/bridge-core/src/git/git-command.ts
|
|
928
|
-
import { spawn as
|
|
1345
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
929
1346
|
async function runGit(args, options) {
|
|
930
1347
|
return await new Promise((resolve8, reject) => {
|
|
931
|
-
const child =
|
|
1348
|
+
const child = spawn3("git", [...args], {
|
|
932
1349
|
cwd: options.cwd,
|
|
933
1350
|
env: { ...process.env, ...options.env ?? {} },
|
|
934
1351
|
shell: false,
|
|
@@ -1997,7 +2414,7 @@ import { relative as relative5, resolve as resolve6, sep as sep5 } from "node:pa
|
|
|
1997
2414
|
// ../../packages/bridge-core/src/source/path-normalizer.ts
|
|
1998
2415
|
import { access, readFile as readFile3, realpath as realpath6 } from "node:fs/promises";
|
|
1999
2416
|
import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve5, sep as sep4 } from "node:path";
|
|
2000
|
-
import { spawn as
|
|
2417
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
2001
2418
|
function toPosix(value) {
|
|
2002
2419
|
return value.split(sep4).join("/").replaceAll("\\", "/");
|
|
2003
2420
|
}
|
|
@@ -2032,7 +2449,7 @@ function isWithin2(root, candidate) {
|
|
|
2032
2449
|
}
|
|
2033
2450
|
async function gitFiles(repoRoot) {
|
|
2034
2451
|
return await new Promise((resolvePromise, reject) => {
|
|
2035
|
-
const child =
|
|
2452
|
+
const child = spawn4(
|
|
2036
2453
|
"git",
|
|
2037
2454
|
["ls-files", "--cached", "--others", "--exclude-standard", "-z"],
|
|
2038
2455
|
{
|
|
@@ -2102,8 +2519,8 @@ async function normalizeSourceLocation(input, repoRootInput) {
|
|
|
2102
2519
|
candidates: matches.slice(0, 20)
|
|
2103
2520
|
};
|
|
2104
2521
|
}
|
|
2105
|
-
const
|
|
2106
|
-
const absolutePath = resolve5(repoRoot,
|
|
2522
|
+
const filePath2 = matches[0];
|
|
2523
|
+
const absolutePath = resolve5(repoRoot, filePath2);
|
|
2107
2524
|
const canonical = await realpath6(absolutePath);
|
|
2108
2525
|
if (!isWithin2(repoRoot, canonical)) {
|
|
2109
2526
|
return { input, confidence: "unknown", candidates: [] };
|
|
@@ -2111,7 +2528,7 @@ async function normalizeSourceLocation(input, repoRootInput) {
|
|
|
2111
2528
|
const lineNumber = await boundedLine(canonical, input.lineNumber);
|
|
2112
2529
|
return {
|
|
2113
2530
|
input,
|
|
2114
|
-
filePath,
|
|
2531
|
+
filePath: filePath2,
|
|
2115
2532
|
absolutePath: canonical,
|
|
2116
2533
|
...lineNumber === void 0 ? {} : { lineNumber },
|
|
2117
2534
|
...input.columnNumber === void 0 ? {} : { columnNumber: input.columnNumber },
|
|
@@ -2616,6 +3033,7 @@ var TaskService = class {
|
|
|
2616
3033
|
return publicTask(accepted);
|
|
2617
3034
|
}
|
|
2618
3035
|
async revert(id) {
|
|
3036
|
+
if (this.#closed) throw new TaskServiceError("SERVICE_CLOSED", "Task service is closed");
|
|
2619
3037
|
if (this.#activeTaskId || this.#recovering || this.#recoveryQueue.length > 0) {
|
|
2620
3038
|
throw new TaskServiceError(
|
|
2621
3039
|
"WRITER_BUSY",
|
|
@@ -2631,12 +3049,19 @@ var TaskService = class {
|
|
|
2631
3049
|
if (!latest || latest.id !== id) {
|
|
2632
3050
|
throw new TaskServiceError("NOT_LATEST_TASK", "Only the latest completed task can be reverted");
|
|
2633
3051
|
}
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
3052
|
+
this.#activeTaskId = id;
|
|
3053
|
+
try {
|
|
3054
|
+
await this.#git.revert(id, task.beforeRef, task.afterRef);
|
|
3055
|
+
const reverted = this.#transition(id, "reverted", {
|
|
3056
|
+
completedAt: this.#now().toISOString()
|
|
3057
|
+
});
|
|
3058
|
+
this.#emit("task.reverted", { task: publicTask(reverted) }, id);
|
|
3059
|
+
return publicTask(reverted);
|
|
3060
|
+
} finally {
|
|
3061
|
+
this.#activeTaskId = void 0;
|
|
3062
|
+
if (!this.#closed) void this.#drain();
|
|
3063
|
+
this.#resolveIdleIfNeeded();
|
|
3064
|
+
}
|
|
2640
3065
|
}
|
|
2641
3066
|
async waitForIdle() {
|
|
2642
3067
|
if (!this.#activeTaskId && this.#queue.length === 0 && this.#recoveryQueue.length === 0 && !this.#recovering && !this.#draining) {
|
|
@@ -2647,7 +3072,7 @@ var TaskService = class {
|
|
|
2647
3072
|
async close() {
|
|
2648
3073
|
if (this.#closed) return;
|
|
2649
3074
|
this.#closed = true;
|
|
2650
|
-
if (this.#activeTaskId &&
|
|
3075
|
+
if (this.#activeTaskId && this.#activeAbort) {
|
|
2651
3076
|
this.#cancelRequested.add(this.#activeTaskId);
|
|
2652
3077
|
this.#activeAbort?.abort(new AgentCanceledError("Task service is closing"));
|
|
2653
3078
|
}
|
|
@@ -2712,9 +3137,12 @@ var TaskService = class {
|
|
|
2712
3137
|
if (!task.beforeRef) {
|
|
2713
3138
|
throw new Error("Interrupted task is missing its before snapshot");
|
|
2714
3139
|
}
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
3140
|
+
let afterRef = task.afterRef;
|
|
3141
|
+
if (!afterRef) {
|
|
3142
|
+
afterRef = (await this.#git.createSnapshot(taskId, "after")).ref;
|
|
3143
|
+
this.#store.updateTask(taskId, { afterRef });
|
|
3144
|
+
}
|
|
3145
|
+
const diff = await this.#git.diff(task.beforeRef, afterRef);
|
|
2718
3146
|
this.#store.updateTask(taskId, {
|
|
2719
3147
|
diffText: diff.text,
|
|
2720
3148
|
changedFiles: diff.files
|
|
@@ -2786,7 +3214,7 @@ var TaskService = class {
|
|
|
2786
3214
|
}
|
|
2787
3215
|
}
|
|
2788
3216
|
async #drain() {
|
|
2789
|
-
if (this.#draining || this.#closed || this.#recovering || this.#recoveryQueue.length > 0) {
|
|
3217
|
+
if (this.#draining || this.#activeTaskId || this.#closed || this.#recovering || this.#recoveryQueue.length > 0) {
|
|
2790
3218
|
return;
|
|
2791
3219
|
}
|
|
2792
3220
|
this.#draining = true;
|
|
@@ -3039,11 +3467,11 @@ var TaskService = class {
|
|
|
3039
3467
|
for (const target of result.selection.targets) {
|
|
3040
3468
|
const sanitize = async (location) => {
|
|
3041
3469
|
try {
|
|
3042
|
-
const
|
|
3470
|
+
const filePath2 = await this.#git.pathPolicy.assertFilesystemPathAllowed(
|
|
3043
3471
|
location.filePath,
|
|
3044
3472
|
false
|
|
3045
3473
|
);
|
|
3046
|
-
return { ...location, filePath };
|
|
3474
|
+
return { ...location, filePath: filePath2 };
|
|
3047
3475
|
} catch {
|
|
3048
3476
|
return void 0;
|
|
3049
3477
|
}
|
|
@@ -3255,7 +3683,7 @@ var BrowserSessionManager = class {
|
|
|
3255
3683
|
};
|
|
3256
3684
|
|
|
3257
3685
|
// ../../packages/bridge-core/src/verification/commands.ts
|
|
3258
|
-
import { spawn as
|
|
3686
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
3259
3687
|
var MAX_OUTPUT_CHARS = 8e3;
|
|
3260
3688
|
var KILL_GRACE_MS = 250;
|
|
3261
3689
|
function appendOutput(current, chunk) {
|
|
@@ -3269,7 +3697,7 @@ async function runVerificationCommand(configured, cwd, signal) {
|
|
|
3269
3697
|
}
|
|
3270
3698
|
const startedAt = Date.now();
|
|
3271
3699
|
return await new Promise((resolveResult, rejectResult) => {
|
|
3272
|
-
const child =
|
|
3700
|
+
const child = spawn5(executable, arguments_, {
|
|
3273
3701
|
cwd,
|
|
3274
3702
|
detached: process.platform !== "win32",
|
|
3275
3703
|
shell: false,
|
|
@@ -3844,15 +4272,40 @@ ${output}` : ""}`
|
|
|
3844
4272
|
}
|
|
3845
4273
|
|
|
3846
4274
|
// ../../packages/bridge-core/src/bridge/default-control-service.ts
|
|
4275
|
+
function createAgentAdapter(agent) {
|
|
4276
|
+
if (agent.adapter === "claude") {
|
|
4277
|
+
if (agent.reasoningEffort === "minimal") {
|
|
4278
|
+
throw new Error("Claude does not support minimal reasoning effort");
|
|
4279
|
+
}
|
|
4280
|
+
return new ClaudeAdapter({
|
|
4281
|
+
...agent.model === void 0 ? {} : { model: agent.model },
|
|
4282
|
+
...agent.reasoningEffort === void 0 ? {} : { reasoningEffort: agent.reasoningEffort }
|
|
4283
|
+
});
|
|
4284
|
+
}
|
|
4285
|
+
if (agent.adapter === "codex") {
|
|
4286
|
+
if (agent.reasoningEffort === "max") {
|
|
4287
|
+
throw new Error("Codex does not support max reasoning effort");
|
|
4288
|
+
}
|
|
4289
|
+
return new CodexAdapter({
|
|
4290
|
+
...agent.model === void 0 ? {} : { model: agent.model },
|
|
4291
|
+
...agent.reasoningEffort === void 0 ? {} : { reasoningEffort: agent.reasoningEffort },
|
|
4292
|
+
...agent.profile === void 0 ? {} : { profile: agent.profile }
|
|
4293
|
+
});
|
|
4294
|
+
}
|
|
4295
|
+
throw new Error(`Agent adapter ${agent.adapter} is not implemented in this build`);
|
|
4296
|
+
}
|
|
4297
|
+
function inheritedAgentEnvironment(names, environment) {
|
|
4298
|
+
return Object.fromEntries(
|
|
4299
|
+
names.flatMap((name) => {
|
|
4300
|
+
const value = environment[name];
|
|
4301
|
+
return value === void 0 ? [] : [[name, value]];
|
|
4302
|
+
})
|
|
4303
|
+
);
|
|
4304
|
+
}
|
|
3847
4305
|
async function createDefaultControlService(context, environment = process.env) {
|
|
3848
4306
|
const loaded = await loadVisualDevConfig(context.repoRoot, {
|
|
3849
4307
|
...context.configRoot === void 0 ? {} : { configRoot: context.configRoot }
|
|
3850
4308
|
});
|
|
3851
|
-
if (loaded.config.agent.adapter !== "codex") {
|
|
3852
|
-
throw new Error(
|
|
3853
|
-
`Agent adapter ${loaded.config.agent.adapter} is not implemented in this MVP build`
|
|
3854
|
-
);
|
|
3855
|
-
}
|
|
3856
4309
|
const git = await GitTransactionManager.open(context.repoRoot, {
|
|
3857
4310
|
allowed: rebaseWorkspacePatterns(
|
|
3858
4311
|
context.repoRoot,
|
|
@@ -3871,13 +4324,16 @@ async function createDefaultControlService(context, environment = process.env) {
|
|
|
3871
4324
|
projectId: context.projectId,
|
|
3872
4325
|
workspaceRoot: context.workspaceRoot,
|
|
3873
4326
|
upstreamUrl: context.upstreamUrl,
|
|
3874
|
-
adapter:
|
|
4327
|
+
adapter: createAgentAdapter(loaded.config.agent),
|
|
3875
4328
|
store,
|
|
3876
4329
|
git,
|
|
3877
4330
|
maxRunMs: loaded.config.agent.maxRunMs,
|
|
3878
4331
|
maxPending: loaded.config.queue.maxPending,
|
|
3879
4332
|
resumeMode: loaded.config.agent.resumeMode,
|
|
3880
|
-
environment:
|
|
4333
|
+
environment: inheritedAgentEnvironment(
|
|
4334
|
+
loaded.config.agent.inheritEnv,
|
|
4335
|
+
environment
|
|
4336
|
+
)
|
|
3881
4337
|
});
|
|
3882
4338
|
const controlService = createTaskControlService({
|
|
3883
4339
|
taskService,
|
|
@@ -4208,10 +4664,10 @@ async function acquireWorktreeLock(repositoryRoot, options = {}) {
|
|
|
4208
4664
|
}
|
|
4209
4665
|
|
|
4210
4666
|
// ../../packages/bridge-core/src/runtime/repository.ts
|
|
4211
|
-
import { execFile as
|
|
4667
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
4212
4668
|
import { realpath as realpath9 } from "node:fs/promises";
|
|
4213
|
-
import { promisify as
|
|
4214
|
-
var
|
|
4669
|
+
import { promisify as promisify3 } from "node:util";
|
|
4670
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
4215
4671
|
var GitWorktreeNotFoundError = class extends Error {
|
|
4216
4672
|
constructor(cwd, options = {}) {
|
|
4217
4673
|
super(
|
|
@@ -4223,7 +4679,7 @@ var GitWorktreeNotFoundError = class extends Error {
|
|
|
4223
4679
|
};
|
|
4224
4680
|
async function discoverGitWorktreeRoot(cwd = process.cwd()) {
|
|
4225
4681
|
try {
|
|
4226
|
-
const { stdout } = await
|
|
4682
|
+
const { stdout } = await execFileAsync3(
|
|
4227
4683
|
"git",
|
|
4228
4684
|
["-C", cwd, "rev-parse", "--show-toplevel"],
|
|
4229
4685
|
{
|
|
@@ -5253,7 +5709,7 @@ async function startBridgeCore(options, dependencies) {
|
|
|
5253
5709
|
try {
|
|
5254
5710
|
lock = options.lock ?? await acquireWorktreeLock(loadedConfig.repoRoot, { environment });
|
|
5255
5711
|
const host = options.host ?? loadedConfig.config.gateway.host;
|
|
5256
|
-
const configuredPublicUrl = options.publicUrl ?? loadedConfig.config.gateway.publicUrl;
|
|
5712
|
+
const configuredPublicUrl = options.publicUrl ?? loadedConfig.config.gateway.publicUrl ?? options.fallbackPublicUrl;
|
|
5257
5713
|
const publicUrl = configuredPublicUrl === void 0 ? void 0 : normalizePublicUrl(configuredPublicUrl);
|
|
5258
5714
|
const gatewayPort = await findAvailablePort(startPort(loadedConfig, options.listen), host);
|
|
5259
5715
|
const token = generatePairingToken();
|
|
@@ -5271,6 +5727,13 @@ async function startBridgeCore(options, dependencies) {
|
|
|
5271
5727
|
controlService = await resolveControlService(dependencies, controlContext);
|
|
5272
5728
|
const allowedOrigins = new Set(loadedConfig.config.security.allowedOrigins);
|
|
5273
5729
|
if (publicUrl !== void 0) allowedOrigins.add(new URL(publicUrl).origin);
|
|
5730
|
+
if (options.fallbackLoopbackOrigins === true && options.publicUrl === void 0 && loadedConfig.config.gateway.publicUrl === void 0 && loadedConfig.config.security.allowedOrigins.length === 0 && publicUrl !== void 0) {
|
|
5731
|
+
const loopbackUrl = new URL(publicUrl);
|
|
5732
|
+
if (loopbackUrl.hostname === "localhost" || loopbackUrl.hostname === "127.0.0.1") {
|
|
5733
|
+
loopbackUrl.hostname = loopbackUrl.hostname === "localhost" ? "127.0.0.1" : "localhost";
|
|
5734
|
+
allowedOrigins.add(loopbackUrl.origin);
|
|
5735
|
+
}
|
|
5736
|
+
}
|
|
5274
5737
|
gateway = createGatewayServer({
|
|
5275
5738
|
upstream: options.upstreamUrl,
|
|
5276
5739
|
pairingToken: token,
|
|
@@ -5373,7 +5836,9 @@ async function startAttachBridge(options, dependencies = {}) {
|
|
|
5373
5836
|
upstreamUrl: normalizeUpstream(options.upstream),
|
|
5374
5837
|
...options.listen === void 0 ? {} : { listen: options.listen },
|
|
5375
5838
|
...options.host === void 0 ? {} : { host: options.host },
|
|
5376
|
-
...options.publicUrl === void 0 ? {} : { publicUrl: options.publicUrl }
|
|
5839
|
+
...options.publicUrl === void 0 ? {} : { publicUrl: options.publicUrl },
|
|
5840
|
+
...options.fallbackPublicUrl === void 0 ? {} : { fallbackPublicUrl: options.fallbackPublicUrl },
|
|
5841
|
+
...options.fallbackLoopbackOrigins === void 0 ? {} : { fallbackLoopbackOrigins: options.fallbackLoopbackOrigins }
|
|
5377
5842
|
},
|
|
5378
5843
|
dependencies
|
|
5379
5844
|
);
|
|
@@ -5387,6 +5852,49 @@ async function startAttachBridge(options, dependencies = {}) {
|
|
|
5387
5852
|
}
|
|
5388
5853
|
|
|
5389
5854
|
// src/vite.ts
|
|
5855
|
+
var bridgesKey = /* @__PURE__ */ Symbol.for("visual-remote.vite.bridges");
|
|
5856
|
+
var bridgeState = globalThis;
|
|
5857
|
+
var bridges = bridgeState[bridgesKey] ??= /* @__PURE__ */ new Map();
|
|
5858
|
+
var restartsKey = /* @__PURE__ */ Symbol.for("visual-remote.vite.restarts");
|
|
5859
|
+
var restartState = globalThis;
|
|
5860
|
+
var restarts = restartState[restartsKey] ??= new AsyncLocalStorage();
|
|
5861
|
+
async function acquireBridge(options, config) {
|
|
5862
|
+
const root = projectRoot(config, options.cwd);
|
|
5863
|
+
let shared = bridges.get(root);
|
|
5864
|
+
if (shared?.closing !== void 0) {
|
|
5865
|
+
await shared.closing;
|
|
5866
|
+
return acquireBridge(options, config);
|
|
5867
|
+
}
|
|
5868
|
+
if (shared === void 0) {
|
|
5869
|
+
shared = { bridge: startOrReuseBridge(options, config), users: 0 };
|
|
5870
|
+
bridges.set(root, shared);
|
|
5871
|
+
}
|
|
5872
|
+
const entry = shared;
|
|
5873
|
+
entry.users += 1;
|
|
5874
|
+
let releasePromise;
|
|
5875
|
+
const release = () => {
|
|
5876
|
+
releasePromise ??= (async () => {
|
|
5877
|
+
entry.users -= 1;
|
|
5878
|
+
if (entry.users !== 0) return;
|
|
5879
|
+
entry.closing = (async () => {
|
|
5880
|
+
try {
|
|
5881
|
+
const bridge = await entry.bridge.catch(() => void 0);
|
|
5882
|
+
await bridge?.ownedBridge?.close();
|
|
5883
|
+
} finally {
|
|
5884
|
+
bridges.delete(root);
|
|
5885
|
+
}
|
|
5886
|
+
})();
|
|
5887
|
+
await entry.closing;
|
|
5888
|
+
})();
|
|
5889
|
+
return releasePromise;
|
|
5890
|
+
};
|
|
5891
|
+
try {
|
|
5892
|
+
return { bridge: await entry.bridge, release };
|
|
5893
|
+
} catch (error) {
|
|
5894
|
+
await release();
|
|
5895
|
+
throw error;
|
|
5896
|
+
}
|
|
5897
|
+
}
|
|
5390
5898
|
function projectRoot(config, configuredRoot) {
|
|
5391
5899
|
if (configuredRoot !== void 0) return resolve7(configuredRoot);
|
|
5392
5900
|
return resolve7(process.cwd(), typeof config.root === "string" ? config.root : ".");
|
|
@@ -5424,32 +5932,11 @@ async function startOrReuseBridge(options, config) {
|
|
|
5424
5932
|
}
|
|
5425
5933
|
}
|
|
5426
5934
|
function visualRemote(options = {}) {
|
|
5427
|
-
let bridgePromise;
|
|
5428
5935
|
let pairingUrlAnnounced = false;
|
|
5429
|
-
const closeBridge = async () => {
|
|
5430
|
-
if (bridgePromise === void 0) return;
|
|
5431
|
-
const bridge = await bridgePromise.catch(() => void 0);
|
|
5432
|
-
await bridge?.ownedBridge?.close();
|
|
5433
|
-
};
|
|
5434
5936
|
return {
|
|
5435
5937
|
name: "visual-remote",
|
|
5436
5938
|
apply: "serve",
|
|
5437
5939
|
enforce: "pre",
|
|
5438
|
-
async config(config) {
|
|
5439
|
-
bridgePromise ??= startOrReuseBridge(options, config);
|
|
5440
|
-
const bridge = await bridgePromise;
|
|
5441
|
-
bridge.ownedBridge?.gateway.server.unref();
|
|
5442
|
-
return {
|
|
5443
|
-
server: {
|
|
5444
|
-
proxy: {
|
|
5445
|
-
"/_visual": {
|
|
5446
|
-
target: bridge.gatewayUrl,
|
|
5447
|
-
ws: true
|
|
5448
|
-
}
|
|
5449
|
-
}
|
|
5450
|
-
}
|
|
5451
|
-
};
|
|
5452
|
-
},
|
|
5453
5940
|
transformIndexHtml: {
|
|
5454
5941
|
order: "pre",
|
|
5455
5942
|
handler(html) {
|
|
@@ -5465,21 +5952,48 @@ function visualRemote(options = {}) {
|
|
|
5465
5952
|
];
|
|
5466
5953
|
}
|
|
5467
5954
|
},
|
|
5468
|
-
configureServer(server) {
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5955
|
+
async configureServer(server) {
|
|
5956
|
+
const candidate = { config: server.config, close: server.close };
|
|
5957
|
+
restarts.getStore()?.push(candidate);
|
|
5958
|
+
const restart = server.restart;
|
|
5959
|
+
server.restart = (forceOptimize) => {
|
|
5960
|
+
const candidates = [];
|
|
5961
|
+
return restarts.run(candidates, async () => {
|
|
5962
|
+
try {
|
|
5963
|
+
return await restart(forceOptimize);
|
|
5964
|
+
} finally {
|
|
5965
|
+
for (const replacement of candidates) {
|
|
5966
|
+
if (replacement.config !== server.config) await replacement.close();
|
|
5967
|
+
}
|
|
5968
|
+
}
|
|
5475
5969
|
});
|
|
5476
|
-
}
|
|
5970
|
+
};
|
|
5971
|
+
const serverLease = await acquireBridge(options, server.config);
|
|
5972
|
+
const close = server.close;
|
|
5973
|
+
server.close = async () => {
|
|
5974
|
+
try {
|
|
5975
|
+
await close();
|
|
5976
|
+
} finally {
|
|
5977
|
+
await serverLease.release();
|
|
5978
|
+
}
|
|
5979
|
+
};
|
|
5980
|
+
candidate.close = server.close;
|
|
5477
5981
|
server.httpServer?.once("close", () => {
|
|
5478
|
-
void
|
|
5982
|
+
void serverLease.release();
|
|
5479
5983
|
});
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5984
|
+
const { bridge } = serverLease;
|
|
5985
|
+
bridge.ownedBridge?.gateway.server.unref();
|
|
5986
|
+
server.config.server.proxy ??= {};
|
|
5987
|
+
server.config.server.proxy["/_visual"] = {
|
|
5988
|
+
target: bridge.gatewayUrl,
|
|
5989
|
+
ws: true
|
|
5990
|
+
};
|
|
5991
|
+
if (!pairingUrlAnnounced) {
|
|
5992
|
+
pairingUrlAnnounced = true;
|
|
5993
|
+
server.config.logger.info(
|
|
5994
|
+
bridge.openUrl === void 0 ? `[visual-remote] Reusing Bridge: ${bridge.gatewayUrl}` : `[visual-remote] Pair: ${bridge.openUrl}`
|
|
5995
|
+
);
|
|
5996
|
+
}
|
|
5483
5997
|
}
|
|
5484
5998
|
};
|
|
5485
5999
|
}
|