visual-remote 0.3.1 → 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 +43 -8
- package/apps/cli/dist/direct-exec-mcp.js +108 -1
- package/apps/cli/dist/index.js +930 -352
- package/apps/cli/dist/next.js +661 -207
- package/apps/cli/dist/vite.js +651 -206
- package/package.json +9 -6
package/apps/cli/dist/next.js
CHANGED
|
@@ -25,27 +25,530 @@ var AgentCanceledError = class extends Error {
|
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
// ../../packages/bridge-core/src/agents/claude-event-parser.ts
|
|
29
|
+
function asRecord(value) {
|
|
30
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
31
|
+
}
|
|
32
|
+
function asText(value) {
|
|
33
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
34
|
+
}
|
|
35
|
+
function asNumber(value) {
|
|
36
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
37
|
+
}
|
|
38
|
+
function contentBlocks(record) {
|
|
39
|
+
const message = asRecord(record.message);
|
|
40
|
+
return Array.isArray(message?.content) ? message.content.flatMap((block) => {
|
|
41
|
+
const parsed = asRecord(block);
|
|
42
|
+
return parsed === void 0 ? [] : [parsed];
|
|
43
|
+
}) : [];
|
|
44
|
+
}
|
|
45
|
+
function toolSummary(name, input) {
|
|
46
|
+
return asText(input.description) ?? asText(input.command) ?? asText(input.file_path) ?? asText(input.path) ?? (name === "Bash" ? "Run command" : void 0);
|
|
47
|
+
}
|
|
48
|
+
function filePath(input) {
|
|
49
|
+
return asText(input.file_path) ?? asText(input.path) ?? asText(input.notebook_path);
|
|
50
|
+
}
|
|
51
|
+
var ClaudeEventParser = class {
|
|
52
|
+
#defaultCwd;
|
|
53
|
+
#tools = /* @__PURE__ */ new Map();
|
|
54
|
+
#sessionId;
|
|
55
|
+
constructor(defaultCwd = "") {
|
|
56
|
+
this.#defaultCwd = defaultCwd;
|
|
57
|
+
}
|
|
58
|
+
parse(line) {
|
|
59
|
+
const trimmed = line.trim();
|
|
60
|
+
if (!trimmed) return [];
|
|
61
|
+
let value;
|
|
62
|
+
try {
|
|
63
|
+
value = JSON.parse(trimmed);
|
|
64
|
+
} catch {
|
|
65
|
+
return [{ type: "warning", text: trimmed }];
|
|
66
|
+
}
|
|
67
|
+
const record = asRecord(value);
|
|
68
|
+
if (!record) return [{ type: "message", text: trimmed }];
|
|
69
|
+
const events = [];
|
|
70
|
+
const foundSession = asText(record.session_id) ?? asText(record.sessionId);
|
|
71
|
+
if (foundSession !== void 0 && foundSession !== this.#sessionId) {
|
|
72
|
+
this.#sessionId = foundSession;
|
|
73
|
+
events.push({ type: "session", sessionId: foundSession });
|
|
74
|
+
}
|
|
75
|
+
const type = asText(record.type) ?? "unknown";
|
|
76
|
+
if (type === "system") {
|
|
77
|
+
const subtype = asText(record.subtype);
|
|
78
|
+
if (subtype) events.push({ type: "phase", name: subtype });
|
|
79
|
+
return events;
|
|
80
|
+
}
|
|
81
|
+
if (type === "assistant") {
|
|
82
|
+
for (const block of contentBlocks(record)) {
|
|
83
|
+
if (block.type === "text") {
|
|
84
|
+
const text = asText(block.text);
|
|
85
|
+
if (text) events.push({ type: "message", text });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (block.type !== "tool_use") continue;
|
|
89
|
+
const name = asText(block.name) ?? "tool";
|
|
90
|
+
const id = asText(block.id);
|
|
91
|
+
const input = asRecord(block.input) ?? {};
|
|
92
|
+
if (id) this.#tools.set(id, name);
|
|
93
|
+
const summary = toolSummary(name, input);
|
|
94
|
+
events.push(
|
|
95
|
+
summary ? { type: "tool_start", name, summary } : { type: "tool_start", name }
|
|
96
|
+
);
|
|
97
|
+
const command = name === "Bash" ? asText(input.command) : void 0;
|
|
98
|
+
if (command) {
|
|
99
|
+
events.push({ type: "command", command, cwd: this.#defaultCwd });
|
|
100
|
+
}
|
|
101
|
+
const path = filePath(input);
|
|
102
|
+
if (path && ["Edit", "Write", "NotebookEdit"].includes(name)) {
|
|
103
|
+
events.push({ type: "file_hint", path });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return events;
|
|
107
|
+
}
|
|
108
|
+
if (type === "user") {
|
|
109
|
+
for (const block of contentBlocks(record)) {
|
|
110
|
+
if (block.type !== "tool_result") continue;
|
|
111
|
+
const id = asText(block.tool_use_id);
|
|
112
|
+
const name = (id ? this.#tools.get(id) : void 0) ?? "tool";
|
|
113
|
+
if (id) this.#tools.delete(id);
|
|
114
|
+
events.push({ type: "tool_end", name, ok: block.is_error !== true });
|
|
115
|
+
}
|
|
116
|
+
return events;
|
|
117
|
+
}
|
|
118
|
+
if (type === "result") {
|
|
119
|
+
const usage = asRecord(record.usage);
|
|
120
|
+
if (usage) {
|
|
121
|
+
const cachedInputTokens = asNumber(usage.cache_read_input_tokens);
|
|
122
|
+
events.push({
|
|
123
|
+
type: "usage",
|
|
124
|
+
inputTokens: asNumber(usage.input_tokens) + asNumber(usage.cache_creation_input_tokens) + cachedInputTokens,
|
|
125
|
+
outputTokens: asNumber(usage.output_tokens),
|
|
126
|
+
...cachedInputTokens > 0 ? { cachedInputTokens } : {}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
const result = asText(record.result);
|
|
130
|
+
const subtype = asText(record.subtype);
|
|
131
|
+
if (record.is_error === true || subtype?.startsWith("error") === true) {
|
|
132
|
+
events.push({ type: "error", text: result ?? "Claude reported an error" });
|
|
133
|
+
} else {
|
|
134
|
+
events.push(result ? { type: "complete", summary: result } : { type: "complete" });
|
|
135
|
+
}
|
|
136
|
+
return events;
|
|
137
|
+
}
|
|
138
|
+
return events;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// ../../packages/bridge-core/src/agents/claude-adapter.ts
|
|
143
|
+
import { execFile, spawn } from "node:child_process";
|
|
144
|
+
import { promisify } from "node:util";
|
|
145
|
+
|
|
146
|
+
// ../../packages/bridge-core/src/runtime/managed-process.ts
|
|
147
|
+
function safeChildProcessId(child) {
|
|
148
|
+
const pid = child.pid;
|
|
149
|
+
if (pid === void 0 || !Number.isSafeInteger(pid) || pid <= 1 || pid === process.pid) {
|
|
150
|
+
return void 0;
|
|
151
|
+
}
|
|
152
|
+
return pid;
|
|
153
|
+
}
|
|
154
|
+
function safeDetachedProcessGroupId(child) {
|
|
155
|
+
return process.platform === "win32" ? void 0 : safeChildProcessId(child);
|
|
156
|
+
}
|
|
157
|
+
function isMissingProcess(error) {
|
|
158
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ESRCH";
|
|
159
|
+
}
|
|
160
|
+
function processGroupIsAlive(processGroupId) {
|
|
161
|
+
try {
|
|
162
|
+
process.kill(-processGroupId, 0);
|
|
163
|
+
return true;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (isMissingProcess(error)) return false;
|
|
166
|
+
if (typeof error === "object" && error !== null && "code" in error && error.code === "EPERM") {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function childIsAlive(child) {
|
|
173
|
+
return child.exitCode === null && child.signalCode === null;
|
|
174
|
+
}
|
|
175
|
+
function signalChildProcessTree(child, signal) {
|
|
176
|
+
const pid = safeChildProcessId(child);
|
|
177
|
+
if (pid === void 0) return false;
|
|
178
|
+
try {
|
|
179
|
+
if (process.platform === "win32") {
|
|
180
|
+
if (!childIsAlive(child)) return false;
|
|
181
|
+
return child.kill(signal);
|
|
182
|
+
}
|
|
183
|
+
process.kill(-pid, signal);
|
|
184
|
+
return true;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (isMissingProcess(error)) return false;
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function installEmergencyChildExitHook(child, processLike = process) {
|
|
191
|
+
let removed = false;
|
|
192
|
+
const emergencyExit = () => {
|
|
193
|
+
removed = true;
|
|
194
|
+
try {
|
|
195
|
+
signalChildProcessTree(child, "SIGKILL");
|
|
196
|
+
} catch {
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
processLike.once("exit", emergencyExit);
|
|
200
|
+
return () => {
|
|
201
|
+
if (removed) return;
|
|
202
|
+
removed = true;
|
|
203
|
+
processLike.off("exit", emergencyExit);
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
async function waitForProcessTreeExit(child, processGroupId, timeoutMs) {
|
|
207
|
+
const deadline = Date.now() + timeoutMs;
|
|
208
|
+
while (processGroupId === void 0 ? childIsAlive(child) : processGroupIsAlive(processGroupId)) {
|
|
209
|
+
const remaining = deadline - Date.now();
|
|
210
|
+
if (remaining <= 0) return false;
|
|
211
|
+
await new Promise((resolve8) => {
|
|
212
|
+
setTimeout(resolve8, Math.min(25, remaining));
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
async function terminateChildProcessTree(child, killGraceMs = 3e3) {
|
|
218
|
+
const processGroupId = safeDetachedProcessGroupId(child);
|
|
219
|
+
if (processGroupId === void 0 && !childIsAlive(child)) return;
|
|
220
|
+
const sendSignal = (signal) => {
|
|
221
|
+
signalChildProcessTree(child, signal);
|
|
222
|
+
};
|
|
223
|
+
sendSignal("SIGTERM");
|
|
224
|
+
if (await waitForProcessTreeExit(
|
|
225
|
+
child,
|
|
226
|
+
processGroupId,
|
|
227
|
+
Math.max(0, killGraceMs)
|
|
228
|
+
)) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
sendSignal("SIGKILL");
|
|
232
|
+
await waitForProcessTreeExit(
|
|
233
|
+
child,
|
|
234
|
+
processGroupId,
|
|
235
|
+
Math.min(Math.max(0, killGraceMs), 1e3)
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ../../packages/bridge-core/src/agents/async-queue.ts
|
|
240
|
+
var AsyncQueue = class {
|
|
241
|
+
#values = [];
|
|
242
|
+
#waiters = [];
|
|
243
|
+
#ended = false;
|
|
244
|
+
#error;
|
|
245
|
+
push(value) {
|
|
246
|
+
if (this.#ended) return;
|
|
247
|
+
const waiter = this.#waiters.shift();
|
|
248
|
+
if (waiter) waiter.resolve({ value, done: false });
|
|
249
|
+
else this.#values.push(value);
|
|
250
|
+
}
|
|
251
|
+
end(error) {
|
|
252
|
+
if (this.#ended) return;
|
|
253
|
+
this.#ended = true;
|
|
254
|
+
this.#error = error;
|
|
255
|
+
for (const waiter of this.#waiters.splice(0)) {
|
|
256
|
+
if (error !== void 0) waiter.reject(error);
|
|
257
|
+
else waiter.resolve({ value: void 0, done: true });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
[Symbol.asyncIterator]() {
|
|
261
|
+
return {
|
|
262
|
+
next: async () => {
|
|
263
|
+
const value = this.#values.shift();
|
|
264
|
+
if (value !== void 0) return { value, done: false };
|
|
265
|
+
if (this.#ended) {
|
|
266
|
+
if (this.#error !== void 0) throw this.#error;
|
|
267
|
+
return { value: void 0, done: true };
|
|
268
|
+
}
|
|
269
|
+
return await new Promise((resolve8, reject) => {
|
|
270
|
+
this.#waiters.push({ resolve: resolve8, reject });
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// ../../packages/bridge-core/src/agents/claude-adapter.ts
|
|
278
|
+
var execFileAsync = promisify(execFile);
|
|
279
|
+
var INHERITED_ENVIRONMENT = [
|
|
280
|
+
"PATH",
|
|
281
|
+
"HOME",
|
|
282
|
+
"USER",
|
|
283
|
+
"LOGNAME",
|
|
284
|
+
"SHELL",
|
|
285
|
+
"LANG",
|
|
286
|
+
"LC_ALL",
|
|
287
|
+
"TERM",
|
|
288
|
+
"TMPDIR",
|
|
289
|
+
"XDG_CONFIG_HOME",
|
|
290
|
+
"XDG_DATA_HOME",
|
|
291
|
+
"XDG_STATE_HOME",
|
|
292
|
+
"ANTHROPIC_API_KEY",
|
|
293
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
294
|
+
"HTTPS_PROXY",
|
|
295
|
+
"HTTP_PROXY",
|
|
296
|
+
"NO_PROXY",
|
|
297
|
+
"USERPROFILE",
|
|
298
|
+
"APPDATA",
|
|
299
|
+
"LOCALAPPDATA",
|
|
300
|
+
"SystemRoot",
|
|
301
|
+
"COMSPEC",
|
|
302
|
+
"PATHEXT"
|
|
303
|
+
];
|
|
304
|
+
var EMPTY_MCP_CONFIG = JSON.stringify({ mcpServers: {} });
|
|
305
|
+
var CLAUDE_SETTINGS = JSON.stringify({
|
|
306
|
+
permissions: {
|
|
307
|
+
disableBypassPermissionsMode: "disable",
|
|
308
|
+
deny: [
|
|
309
|
+
"Read(./.env)",
|
|
310
|
+
"Read(./.env.*)",
|
|
311
|
+
"Read(./**/*.pem)",
|
|
312
|
+
"Read(./**/*.key)",
|
|
313
|
+
"Edit(./.git/**)",
|
|
314
|
+
"Edit(./.visualdev/runtime/**)",
|
|
315
|
+
"Edit(./node_modules/**)"
|
|
316
|
+
]
|
|
317
|
+
},
|
|
318
|
+
sandbox: {
|
|
319
|
+
enabled: true,
|
|
320
|
+
autoAllowBashIfSandboxed: true,
|
|
321
|
+
allowUnsandboxedCommands: false,
|
|
322
|
+
network: { strictAllowlist: true }
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
function processEnv(overrides) {
|
|
326
|
+
const environment = {};
|
|
327
|
+
for (const key of INHERITED_ENVIRONMENT) {
|
|
328
|
+
const value = process.env[key];
|
|
329
|
+
if (value !== void 0) environment[key] = value;
|
|
330
|
+
}
|
|
331
|
+
return { ...environment, ...overrides };
|
|
332
|
+
}
|
|
333
|
+
function splitLines(chunk, previous, onLine) {
|
|
334
|
+
const combined = previous + chunk.toString();
|
|
335
|
+
const lines = combined.split(/\r?\n/);
|
|
336
|
+
const remainder = lines.pop() ?? "";
|
|
337
|
+
for (const line of lines) onLine(line);
|
|
338
|
+
return remainder;
|
|
339
|
+
}
|
|
340
|
+
var ClaudeAdapter = class {
|
|
341
|
+
id = "claude";
|
|
342
|
+
#executable;
|
|
343
|
+
#killGraceMs;
|
|
344
|
+
#model;
|
|
345
|
+
#reasoningEffort;
|
|
346
|
+
#rtkExecutable;
|
|
347
|
+
#rtkVersion;
|
|
348
|
+
constructor(options = {}) {
|
|
349
|
+
this.#executable = options.executable ?? "claude";
|
|
350
|
+
this.#killGraceMs = options.killGraceMs ?? 2e3;
|
|
351
|
+
this.#model = options.model;
|
|
352
|
+
this.#reasoningEffort = options.reasoningEffort;
|
|
353
|
+
this.#rtkExecutable = options.rtkExecutable ?? "rtk";
|
|
354
|
+
}
|
|
355
|
+
#probeRtk(environment) {
|
|
356
|
+
if (this.#rtkExecutable === false) return Promise.resolve(void 0);
|
|
357
|
+
this.#rtkVersion ??= execFileAsync(this.#rtkExecutable, ["--version"], {
|
|
358
|
+
encoding: "utf8",
|
|
359
|
+
env: environment,
|
|
360
|
+
timeout: 1e3,
|
|
361
|
+
windowsHide: true,
|
|
362
|
+
maxBuffer: 16 * 1024
|
|
363
|
+
}).then(({ stdout }) => stdout.trim().split(/\r?\n/, 1)[0] || void 0).catch(() => void 0);
|
|
364
|
+
return this.#rtkVersion;
|
|
365
|
+
}
|
|
366
|
+
async #runtimePrompt(input, environment) {
|
|
367
|
+
if (this.#rtkExecutable === false) return input.prompt;
|
|
368
|
+
const guidance = await this.#probeRtk(environment).then(
|
|
369
|
+
(version) => version ? `RTK command proxy:
|
|
370
|
+
- ${version} is installed and available in this runtime.
|
|
371
|
+
- Prefix shell commands with RTK by default (for example: rtk git status, rtk rg <pattern>, rtk read <file>, rtk npm test).
|
|
372
|
+
- 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:
|
|
373
|
+
- RTK was not detected in this runtime. Use native repository commands directly and do not spend time searching for RTK.`
|
|
374
|
+
);
|
|
375
|
+
return `${input.prompt.trimEnd()}
|
|
376
|
+
|
|
377
|
+
${guidance}
|
|
378
|
+
`;
|
|
379
|
+
}
|
|
380
|
+
#baseArgs() {
|
|
381
|
+
return [
|
|
382
|
+
"-p",
|
|
383
|
+
"--output-format",
|
|
384
|
+
"stream-json",
|
|
385
|
+
"--verbose",
|
|
386
|
+
"--permission-mode",
|
|
387
|
+
"acceptEdits",
|
|
388
|
+
"--strict-mcp-config",
|
|
389
|
+
"--mcp-config",
|
|
390
|
+
EMPTY_MCP_CONFIG,
|
|
391
|
+
"--no-chrome",
|
|
392
|
+
"--tools",
|
|
393
|
+
"Read,Glob,Grep,Edit,Write,Bash",
|
|
394
|
+
"--settings",
|
|
395
|
+
CLAUDE_SETTINGS,
|
|
396
|
+
...this.#model === void 0 ? [] : ["--model", this.#model],
|
|
397
|
+
...this.#reasoningEffort === void 0 ? [] : ["--effort", this.#reasoningEffort]
|
|
398
|
+
];
|
|
399
|
+
}
|
|
400
|
+
async probe() {
|
|
401
|
+
return await new Promise((resolve8) => {
|
|
402
|
+
const child = spawn(this.#executable, ["--version"], {
|
|
403
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
404
|
+
shell: false
|
|
405
|
+
});
|
|
406
|
+
let output = "";
|
|
407
|
+
child.stdout?.on("data", (chunk) => {
|
|
408
|
+
output += chunk.toString();
|
|
409
|
+
});
|
|
410
|
+
child.once("error", () => {
|
|
411
|
+
resolve8({ available: false, supportsResume: true, structuredOutput: true });
|
|
412
|
+
});
|
|
413
|
+
child.once("close", (code) => {
|
|
414
|
+
const match = output.match(/(\d+\.\d+\.\d+(?:[-+][^\s]+)?)/);
|
|
415
|
+
const capabilities = {
|
|
416
|
+
available: code === 0,
|
|
417
|
+
supportsResume: true,
|
|
418
|
+
structuredOutput: true
|
|
419
|
+
};
|
|
420
|
+
if (match?.[1]) capabilities.version = match[1];
|
|
421
|
+
resolve8(capabilities);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
async *run(input, signal) {
|
|
426
|
+
yield* this.#execute(input, signal, this.#baseArgs());
|
|
427
|
+
}
|
|
428
|
+
async *resume(input, signal) {
|
|
429
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(input.sessionId)) {
|
|
430
|
+
throw new Error("Refusing to resume an invalid Claude session id");
|
|
431
|
+
}
|
|
432
|
+
yield* this.#execute(
|
|
433
|
+
input,
|
|
434
|
+
signal,
|
|
435
|
+
[...this.#baseArgs(), "--resume", input.sessionId]
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
async *#execute(input, signal, args) {
|
|
439
|
+
const queue = new AsyncQueue();
|
|
440
|
+
const environment = processEnv(input.environment);
|
|
441
|
+
const prompt = await this.#runtimePrompt(input, environment);
|
|
442
|
+
const parser = new ClaudeEventParser(input.workspaceRoot);
|
|
443
|
+
const child = spawn(this.#executable, args, {
|
|
444
|
+
cwd: input.workspaceRoot,
|
|
445
|
+
env: environment,
|
|
446
|
+
detached: process.platform !== "win32",
|
|
447
|
+
shell: false,
|
|
448
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
449
|
+
});
|
|
450
|
+
const removeEmergencyExitHook = installEmergencyChildExitHook(child);
|
|
451
|
+
let stdoutRemainder = "";
|
|
452
|
+
let stderrRemainder = "";
|
|
453
|
+
let timedOut = false;
|
|
454
|
+
let aborted = signal.aborted;
|
|
455
|
+
let termination;
|
|
456
|
+
const requestTermination = () => {
|
|
457
|
+
termination ??= terminateChildProcessTree(
|
|
458
|
+
child,
|
|
459
|
+
this.#killGraceMs
|
|
460
|
+
).finally(removeEmergencyExitHook);
|
|
461
|
+
return termination;
|
|
462
|
+
};
|
|
463
|
+
const timeout = setTimeout(() => {
|
|
464
|
+
timedOut = true;
|
|
465
|
+
void requestTermination();
|
|
466
|
+
}, Math.max(1, input.maxRunMs));
|
|
467
|
+
timeout.unref();
|
|
468
|
+
const abort = () => {
|
|
469
|
+
aborted = true;
|
|
470
|
+
void requestTermination();
|
|
471
|
+
};
|
|
472
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
473
|
+
if (signal.aborted) abort();
|
|
474
|
+
child.stdout.on("data", (chunk) => {
|
|
475
|
+
stdoutRemainder = splitLines(chunk, stdoutRemainder, (line) => {
|
|
476
|
+
for (const event of parser.parse(line)) queue.push(event);
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
child.stderr.on("data", (chunk) => {
|
|
480
|
+
stderrRemainder = splitLines(chunk, stderrRemainder, (line) => {
|
|
481
|
+
if (line.trim()) queue.push({ type: "warning", text: line });
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
child.once("error", (error) => queue.end(error));
|
|
485
|
+
child.once("close", (code, closeSignal) => {
|
|
486
|
+
clearTimeout(timeout);
|
|
487
|
+
signal.removeEventListener("abort", abort);
|
|
488
|
+
void (async () => {
|
|
489
|
+
await requestTermination();
|
|
490
|
+
if (stdoutRemainder.trim()) {
|
|
491
|
+
for (const event of parser.parse(stdoutRemainder)) queue.push(event);
|
|
492
|
+
}
|
|
493
|
+
if (stderrRemainder.trim()) queue.push({ type: "warning", text: stderrRemainder });
|
|
494
|
+
if (timedOut) queue.end(new AgentTimeoutError());
|
|
495
|
+
else if (aborted) queue.end(new AgentCanceledError());
|
|
496
|
+
else if (code !== 0) {
|
|
497
|
+
queue.end(
|
|
498
|
+
new AgentProcessError(
|
|
499
|
+
`Claude exited with code ${String(code)}`,
|
|
500
|
+
code,
|
|
501
|
+
closeSignal
|
|
502
|
+
)
|
|
503
|
+
);
|
|
504
|
+
} else {
|
|
505
|
+
queue.end();
|
|
506
|
+
}
|
|
507
|
+
})().catch((error) => queue.end(error));
|
|
508
|
+
});
|
|
509
|
+
child.stdin.on("error", (error) => {
|
|
510
|
+
if (error.code !== "EPIPE") queue.end(error);
|
|
511
|
+
});
|
|
512
|
+
child.stdin.end(prompt);
|
|
513
|
+
try {
|
|
514
|
+
for await (const event of queue) yield event;
|
|
515
|
+
} finally {
|
|
516
|
+
clearTimeout(timeout);
|
|
517
|
+
signal.removeEventListener("abort", abort);
|
|
518
|
+
try {
|
|
519
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
520
|
+
await requestTermination();
|
|
521
|
+
} else if (termination) {
|
|
522
|
+
await termination;
|
|
523
|
+
}
|
|
524
|
+
} finally {
|
|
525
|
+
removeEmergencyExitHook();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
|
|
28
531
|
// ../../packages/bridge-core/src/agents/codex-event-parser.ts
|
|
29
|
-
function
|
|
532
|
+
function asRecord2(value) {
|
|
30
533
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
31
534
|
}
|
|
32
|
-
function
|
|
535
|
+
function asText2(value) {
|
|
33
536
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
34
537
|
}
|
|
35
538
|
function sessionId(record) {
|
|
36
|
-
const thread =
|
|
37
|
-
return
|
|
539
|
+
const thread = asRecord2(record.thread);
|
|
540
|
+
return asText2(record.thread_id) ?? asText2(record.threadId) ?? asText2(record.session_id) ?? asText2(record.sessionId) ?? (thread ? asText2(thread.id) : void 0);
|
|
38
541
|
}
|
|
39
542
|
function itemFiles(item) {
|
|
40
543
|
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
41
544
|
const files = changes.flatMap((change) => {
|
|
42
|
-
const record =
|
|
545
|
+
const record = asRecord2(change);
|
|
43
546
|
if (!record) return [];
|
|
44
|
-
return [
|
|
547
|
+
return [asText2(record.path) ?? asText2(record.file_path) ?? asText2(record.filePath)].filter(
|
|
45
548
|
(path) => path !== void 0
|
|
46
549
|
);
|
|
47
550
|
});
|
|
48
|
-
const direct =
|
|
551
|
+
const direct = asText2(item.path) ?? asText2(item.file_path) ?? asText2(item.filePath);
|
|
49
552
|
if (direct) files.push(direct);
|
|
50
553
|
return [...new Set(files)];
|
|
51
554
|
}
|
|
@@ -59,28 +562,28 @@ function isDirectExecItem(item) {
|
|
|
59
562
|
return item.type === "mcp_tool_call" && item.server === "visual_remote_exec" && item.tool === "run_readonly";
|
|
60
563
|
}
|
|
61
564
|
function directExecSummary(item) {
|
|
62
|
-
const arguments_ =
|
|
565
|
+
const arguments_ = asRecord2(item.arguments);
|
|
63
566
|
const commands = Array.isArray(arguments_?.commands) ? arguments_.commands : [];
|
|
64
567
|
const summaries = commands.flatMap((candidate) => {
|
|
65
|
-
const command =
|
|
568
|
+
const command = asRecord2(candidate);
|
|
66
569
|
const argv = stringArray(command?.argv);
|
|
67
570
|
return argv === void 0 ? [] : [formatArgv(argv)];
|
|
68
571
|
});
|
|
69
572
|
return summaries.length === 0 ? void 0 : summaries.join(" \xB7 ");
|
|
70
573
|
}
|
|
71
574
|
function directExecResults(item, defaultCwd) {
|
|
72
|
-
const result =
|
|
73
|
-
const structured =
|
|
575
|
+
const result = asRecord2(item.result);
|
|
576
|
+
const structured = asRecord2(result?.structured_content ?? result?.structuredContent);
|
|
74
577
|
const results = Array.isArray(structured?.results) ? structured.results : [];
|
|
75
578
|
return results.flatMap((candidate) => {
|
|
76
|
-
const command =
|
|
579
|
+
const command = asRecord2(candidate);
|
|
77
580
|
const argv = stringArray(command?.argv);
|
|
78
581
|
if (argv === void 0) return [];
|
|
79
582
|
const exitCode = typeof command?.exitCode === "number" ? command.exitCode : void 0;
|
|
80
583
|
const durationMs = typeof command?.durationMs === "number" ? command.durationMs : void 0;
|
|
81
584
|
return [{
|
|
82
585
|
command: formatArgv(argv),
|
|
83
|
-
cwd:
|
|
586
|
+
cwd: asText2(command?.cwd) ?? defaultCwd,
|
|
84
587
|
ok: exitCode === 0,
|
|
85
588
|
...exitCode === void 0 ? {} : { exitCode },
|
|
86
589
|
...durationMs === void 0 ? {} : { durationMs },
|
|
@@ -91,8 +594,8 @@ function directExecResults(item, defaultCwd) {
|
|
|
91
594
|
});
|
|
92
595
|
}
|
|
93
596
|
function normalizedUsage(record) {
|
|
94
|
-
const result =
|
|
95
|
-
const usage =
|
|
597
|
+
const result = asRecord2(record.result);
|
|
598
|
+
const usage = asRecord2(record.usage) ?? (result ? asRecord2(result.usage) : void 0);
|
|
96
599
|
if (usage === void 0) return void 0;
|
|
97
600
|
const inputTokens = usage.input_tokens ?? usage.inputTokens;
|
|
98
601
|
const outputTokens = usage.output_tokens ?? usage.outputTokens;
|
|
@@ -116,17 +619,17 @@ function parseCodexJsonLine(line, defaultCwd = "") {
|
|
|
116
619
|
} catch {
|
|
117
620
|
return [{ type: "warning", text: trimmed }];
|
|
118
621
|
}
|
|
119
|
-
const record =
|
|
622
|
+
const record = asRecord2(value);
|
|
120
623
|
if (!record) return [{ type: "message", text: trimmed }];
|
|
121
|
-
const type =
|
|
624
|
+
const type = asText2(record.type) ?? "unknown";
|
|
122
625
|
const events = [];
|
|
123
626
|
const foundSession = sessionId(record);
|
|
124
627
|
if (foundSession) events.push({ type: "session", sessionId: foundSession });
|
|
125
628
|
if (type === "thread.started" || type === "thread.created") return events;
|
|
126
629
|
if (type === "turn.started") return [...events, { type: "phase", name: "turn.started" }];
|
|
127
630
|
if (type === "turn.completed") {
|
|
128
|
-
const result =
|
|
129
|
-
const summary =
|
|
631
|
+
const result = asRecord2(record.result);
|
|
632
|
+
const summary = asText2(record.summary) ?? (result ? asText2(result.summary) : void 0);
|
|
130
633
|
const usage = normalizedUsage(record);
|
|
131
634
|
return [
|
|
132
635
|
...events,
|
|
@@ -135,31 +638,31 @@ function parseCodexJsonLine(line, defaultCwd = "") {
|
|
|
135
638
|
];
|
|
136
639
|
}
|
|
137
640
|
if (type === "turn.failed" || type === "error") {
|
|
138
|
-
const error =
|
|
139
|
-
const text =
|
|
641
|
+
const error = asRecord2(record.error);
|
|
642
|
+
const text = asText2(record.message) ?? (error ? asText2(error.message) : void 0) ?? "Codex reported an error";
|
|
140
643
|
return [...events, { type: "error", text }];
|
|
141
644
|
}
|
|
142
|
-
const item =
|
|
645
|
+
const item = asRecord2(record.item);
|
|
143
646
|
if (type === "item.started" && item) {
|
|
144
|
-
const itemType =
|
|
647
|
+
const itemType = asText2(item.type) ?? "item";
|
|
145
648
|
const directExec = isDirectExecItem(item);
|
|
146
|
-
const summary = directExec ? directExecSummary(item) :
|
|
649
|
+
const summary = directExec ? directExecSummary(item) : asText2(item.command) ?? asText2(item.text);
|
|
147
650
|
const start = summary ? { type: "tool_start", name: directExec ? "direct_exec" : itemType, summary } : { type: "tool_start", name: directExec ? "direct_exec" : itemType };
|
|
148
651
|
return [...events, start];
|
|
149
652
|
}
|
|
150
653
|
if (type === "item.completed" && item) {
|
|
151
|
-
const itemType =
|
|
654
|
+
const itemType = asText2(item.type) ?? "item";
|
|
152
655
|
if (itemType === "agent_message") {
|
|
153
|
-
const text =
|
|
656
|
+
const text = asText2(item.text) ?? asText2(item.message);
|
|
154
657
|
return text ? [...events, { type: "message", text }] : events;
|
|
155
658
|
}
|
|
156
659
|
if (itemType === "command_execution") {
|
|
157
|
-
const command =
|
|
660
|
+
const command = asText2(item.command);
|
|
158
661
|
if (command) {
|
|
159
662
|
events.push({
|
|
160
663
|
type: "command",
|
|
161
664
|
command,
|
|
162
|
-
cwd:
|
|
665
|
+
cwd: asText2(item.cwd) ?? defaultCwd
|
|
163
666
|
});
|
|
164
667
|
}
|
|
165
668
|
}
|
|
@@ -185,151 +688,18 @@ function parseCodexJsonLine(line, defaultCwd = "") {
|
|
|
185
688
|
});
|
|
186
689
|
return events;
|
|
187
690
|
}
|
|
188
|
-
const message =
|
|
691
|
+
const message = asText2(record.message);
|
|
189
692
|
if (message) events.push({ type: "message", text: message });
|
|
190
693
|
return events;
|
|
191
694
|
}
|
|
192
695
|
|
|
193
696
|
// ../../packages/bridge-core/src/agents/codex-adapter.ts
|
|
194
|
-
import { execFile, spawn } from "node:child_process";
|
|
697
|
+
import { execFile as execFile2, spawn as spawn2 } from "node:child_process";
|
|
195
698
|
import { existsSync } from "node:fs";
|
|
196
699
|
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 = [
|
|
700
|
+
import { promisify as promisify2 } from "node:util";
|
|
701
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
702
|
+
var INHERITED_ENVIRONMENT2 = [
|
|
333
703
|
"PATH",
|
|
334
704
|
"HOME",
|
|
335
705
|
"USER",
|
|
@@ -354,15 +724,15 @@ var INHERITED_ENVIRONMENT = [
|
|
|
354
724
|
"COMSPEC",
|
|
355
725
|
"PATHEXT"
|
|
356
726
|
];
|
|
357
|
-
function
|
|
727
|
+
function processEnv2(overrides) {
|
|
358
728
|
const environment = {};
|
|
359
|
-
for (const key of
|
|
729
|
+
for (const key of INHERITED_ENVIRONMENT2) {
|
|
360
730
|
const value = process.env[key];
|
|
361
731
|
if (value !== void 0) environment[key] = value;
|
|
362
732
|
}
|
|
363
733
|
return { ...environment, ...overrides };
|
|
364
734
|
}
|
|
365
|
-
function
|
|
735
|
+
function splitLines2(chunk, previous, onLine) {
|
|
366
736
|
const combined = previous + chunk.toString();
|
|
367
737
|
const lines = combined.split(/\r?\n/);
|
|
368
738
|
const remainder = lines.pop() ?? "";
|
|
@@ -382,18 +752,24 @@ var CodexAdapter = class {
|
|
|
382
752
|
id = "codex";
|
|
383
753
|
#executable;
|
|
384
754
|
#killGraceMs;
|
|
755
|
+
#model;
|
|
756
|
+
#profile;
|
|
757
|
+
#reasoningEffort;
|
|
385
758
|
#rtkExecutable;
|
|
386
759
|
#directExecMcpScript;
|
|
387
760
|
#rtkVersion;
|
|
388
761
|
constructor(options = {}) {
|
|
389
762
|
this.#executable = options.executable ?? "codex";
|
|
390
763
|
this.#killGraceMs = options.killGraceMs ?? 2e3;
|
|
764
|
+
this.#model = options.model;
|
|
765
|
+
this.#profile = options.profile;
|
|
766
|
+
this.#reasoningEffort = options.reasoningEffort;
|
|
391
767
|
this.#rtkExecutable = options.rtkExecutable ?? "rtk";
|
|
392
768
|
this.#directExecMcpScript = options.directExecMcpScript === false ? void 0 : options.directExecMcpScript ?? defaultDirectExecMcpScript();
|
|
393
769
|
}
|
|
394
770
|
#probeRtk(environment) {
|
|
395
771
|
if (this.#rtkExecutable === false) return Promise.resolve(void 0);
|
|
396
|
-
this.#rtkVersion ??=
|
|
772
|
+
this.#rtkVersion ??= execFileAsync2(this.#rtkExecutable, ["--version"], {
|
|
397
773
|
encoding: "utf8",
|
|
398
774
|
env: environment,
|
|
399
775
|
timeout: 1e3,
|
|
@@ -436,9 +812,16 @@ ${guidance}
|
|
|
436
812
|
`mcp_servers.visual_remote_exec.args=${JSON.stringify(serverArgs)}`
|
|
437
813
|
];
|
|
438
814
|
}
|
|
815
|
+
#modelConfig() {
|
|
816
|
+
return [
|
|
817
|
+
...this.#profile === void 0 ? [] : ["--profile", this.#profile],
|
|
818
|
+
...this.#model === void 0 ? [] : ["--model", this.#model],
|
|
819
|
+
...this.#reasoningEffort === void 0 ? [] : ["-c", `model_reasoning_effort=${JSON.stringify(this.#reasoningEffort)}`]
|
|
820
|
+
];
|
|
821
|
+
}
|
|
439
822
|
async probe() {
|
|
440
823
|
return await new Promise((resolve8) => {
|
|
441
|
-
const child =
|
|
824
|
+
const child = spawn2(this.#executable, ["--version"], {
|
|
442
825
|
stdio: ["ignore", "pipe", "ignore"],
|
|
443
826
|
shell: false
|
|
444
827
|
});
|
|
@@ -471,6 +854,7 @@ ${guidance}
|
|
|
471
854
|
"workspace-write",
|
|
472
855
|
"-C",
|
|
473
856
|
input.workspaceRoot,
|
|
857
|
+
...this.#modelConfig(),
|
|
474
858
|
...this.#directExecConfig(input),
|
|
475
859
|
"-"
|
|
476
860
|
];
|
|
@@ -489,6 +873,7 @@ ${guidance}
|
|
|
489
873
|
"workspace-write",
|
|
490
874
|
"-C",
|
|
491
875
|
input.workspaceRoot,
|
|
876
|
+
...this.#modelConfig(),
|
|
492
877
|
...this.#directExecConfig(input),
|
|
493
878
|
"resume",
|
|
494
879
|
input.sessionId,
|
|
@@ -498,9 +883,9 @@ ${guidance}
|
|
|
498
883
|
}
|
|
499
884
|
async *#execute(input, signal, args) {
|
|
500
885
|
const queue = new AsyncQueue();
|
|
501
|
-
const environment =
|
|
886
|
+
const environment = processEnv2(input.environment);
|
|
502
887
|
const prompt = await this.#runtimePrompt(input, environment);
|
|
503
|
-
const child =
|
|
888
|
+
const child = spawn2(this.#executable, args, {
|
|
504
889
|
cwd: input.workspaceRoot,
|
|
505
890
|
env: environment,
|
|
506
891
|
detached: process.platform !== "win32",
|
|
@@ -532,12 +917,12 @@ ${guidance}
|
|
|
532
917
|
signal.addEventListener("abort", abort, { once: true });
|
|
533
918
|
if (signal.aborted) abort();
|
|
534
919
|
child.stdout.on("data", (chunk) => {
|
|
535
|
-
stdoutRemainder =
|
|
920
|
+
stdoutRemainder = splitLines2(chunk, stdoutRemainder, (line) => {
|
|
536
921
|
for (const event of parseCodexJsonLine(line, input.workspaceRoot)) queue.push(event);
|
|
537
922
|
});
|
|
538
923
|
});
|
|
539
924
|
child.stderr.on("data", (chunk) => {
|
|
540
|
-
stderrRemainder =
|
|
925
|
+
stderrRemainder = splitLines2(chunk, stderrRemainder, (line) => {
|
|
541
926
|
if (line.trim()) queue.push({ type: "warning", text: line });
|
|
542
927
|
});
|
|
543
928
|
});
|
|
@@ -615,6 +1000,7 @@ import { ZodError } from "zod";
|
|
|
615
1000
|
import { z } from "zod";
|
|
616
1001
|
var servicePortSchema = z.number().int().min(10001).max(65535);
|
|
617
1002
|
var commandSchema = z.array(z.string().min(1)).min(1);
|
|
1003
|
+
var environmentVariableSchema = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "Expected an environment variable name");
|
|
618
1004
|
var readySchema = z.object({
|
|
619
1005
|
path: z.string().startsWith("/").default("/"),
|
|
620
1006
|
timeoutMs: z.number().int().positive().default(6e4)
|
|
@@ -643,9 +1029,38 @@ var visualDevConfigSchema = z.object({
|
|
|
643
1029
|
}).strict(),
|
|
644
1030
|
agent: z.object({
|
|
645
1031
|
adapter: z.enum(["codex", "claude", "opencode"]),
|
|
1032
|
+
model: z.string().trim().min(1).optional(),
|
|
1033
|
+
reasoningEffort: z.enum(["minimal", "low", "medium", "high", "xhigh", "max"]).optional(),
|
|
1034
|
+
profile: z.string().trim().regex(
|
|
1035
|
+
/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/,
|
|
1036
|
+
"Expected a Codex profile name"
|
|
1037
|
+
).optional(),
|
|
1038
|
+
inheritEnv: z.array(environmentVariableSchema).default([]),
|
|
646
1039
|
maxRunMs: z.number().int().positive(),
|
|
647
1040
|
resumeMode: z.enum(["auto", "new"]).default("auto")
|
|
648
|
-
}).strict(),
|
|
1041
|
+
}).strict().superRefine((agent, context) => {
|
|
1042
|
+
if (agent.adapter === "claude" && agent.reasoningEffort === "minimal") {
|
|
1043
|
+
context.addIssue({
|
|
1044
|
+
code: "custom",
|
|
1045
|
+
path: ["reasoningEffort"],
|
|
1046
|
+
message: "minimal reasoning effort is only supported by the Codex adapter"
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
if (agent.adapter === "codex" && agent.reasoningEffort === "max") {
|
|
1050
|
+
context.addIssue({
|
|
1051
|
+
code: "custom",
|
|
1052
|
+
path: ["reasoningEffort"],
|
|
1053
|
+
message: "max reasoning effort is only supported by the Claude adapter"
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
if (agent.adapter !== "codex" && agent.profile !== void 0) {
|
|
1057
|
+
context.addIssue({
|
|
1058
|
+
code: "custom",
|
|
1059
|
+
path: ["profile"],
|
|
1060
|
+
message: "agent.profile is only supported by the Codex adapter"
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
}),
|
|
649
1064
|
queue: z.object({
|
|
650
1065
|
maxPending: z.number().int().positive()
|
|
651
1066
|
}).strict(),
|
|
@@ -689,6 +1104,7 @@ function createDefaultConfig(projectId) {
|
|
|
689
1104
|
},
|
|
690
1105
|
agent: {
|
|
691
1106
|
adapter: "codex",
|
|
1107
|
+
inheritEnv: [],
|
|
692
1108
|
maxRunMs: 9e5,
|
|
693
1109
|
resumeMode: "auto"
|
|
694
1110
|
},
|
|
@@ -761,17 +1177,17 @@ function mergeConfigValues(base, override) {
|
|
|
761
1177
|
}
|
|
762
1178
|
return merged;
|
|
763
1179
|
}
|
|
764
|
-
async function readYamlMapping(
|
|
1180
|
+
async function readYamlMapping(filePath2, required) {
|
|
765
1181
|
let source;
|
|
766
1182
|
try {
|
|
767
|
-
source = await readFile(
|
|
1183
|
+
source = await readFile(filePath2, "utf8");
|
|
768
1184
|
} catch (error) {
|
|
769
1185
|
if (!required && typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
|
|
770
1186
|
return void 0;
|
|
771
1187
|
}
|
|
772
|
-
throw new VisualDevConfigError(`Unable to read config: ${
|
|
1188
|
+
throw new VisualDevConfigError(`Unable to read config: ${filePath2}`, {
|
|
773
1189
|
cause: error,
|
|
774
|
-
filePath
|
|
1190
|
+
filePath: filePath2
|
|
775
1191
|
});
|
|
776
1192
|
}
|
|
777
1193
|
try {
|
|
@@ -784,9 +1200,9 @@ async function readYamlMapping(filePath, required) {
|
|
|
784
1200
|
}
|
|
785
1201
|
return value;
|
|
786
1202
|
} catch (error) {
|
|
787
|
-
throw new VisualDevConfigError(`Invalid YAML in ${
|
|
1203
|
+
throw new VisualDevConfigError(`Invalid YAML in ${filePath2}`, {
|
|
788
1204
|
cause: error,
|
|
789
|
-
filePath
|
|
1205
|
+
filePath: filePath2
|
|
790
1206
|
});
|
|
791
1207
|
}
|
|
792
1208
|
}
|
|
@@ -925,10 +1341,10 @@ var RevertConflictError = class extends RepositorySafetyError {
|
|
|
925
1341
|
};
|
|
926
1342
|
|
|
927
1343
|
// ../../packages/bridge-core/src/git/git-command.ts
|
|
928
|
-
import { spawn as
|
|
1344
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
929
1345
|
async function runGit(args, options) {
|
|
930
1346
|
return await new Promise((resolve8, reject) => {
|
|
931
|
-
const child =
|
|
1347
|
+
const child = spawn3("git", [...args], {
|
|
932
1348
|
cwd: options.cwd,
|
|
933
1349
|
env: { ...process.env, ...options.env ?? {} },
|
|
934
1350
|
shell: false,
|
|
@@ -1997,7 +2413,7 @@ import { relative as relative5, resolve as resolve6, sep as sep5 } from "node:pa
|
|
|
1997
2413
|
// ../../packages/bridge-core/src/source/path-normalizer.ts
|
|
1998
2414
|
import { access, readFile as readFile3, realpath as realpath6 } from "node:fs/promises";
|
|
1999
2415
|
import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve5, sep as sep4 } from "node:path";
|
|
2000
|
-
import { spawn as
|
|
2416
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
2001
2417
|
function toPosix(value) {
|
|
2002
2418
|
return value.split(sep4).join("/").replaceAll("\\", "/");
|
|
2003
2419
|
}
|
|
@@ -2032,7 +2448,7 @@ function isWithin2(root, candidate) {
|
|
|
2032
2448
|
}
|
|
2033
2449
|
async function gitFiles(repoRoot) {
|
|
2034
2450
|
return await new Promise((resolvePromise, reject) => {
|
|
2035
|
-
const child =
|
|
2451
|
+
const child = spawn4(
|
|
2036
2452
|
"git",
|
|
2037
2453
|
["ls-files", "--cached", "--others", "--exclude-standard", "-z"],
|
|
2038
2454
|
{
|
|
@@ -2102,8 +2518,8 @@ async function normalizeSourceLocation(input, repoRootInput) {
|
|
|
2102
2518
|
candidates: matches.slice(0, 20)
|
|
2103
2519
|
};
|
|
2104
2520
|
}
|
|
2105
|
-
const
|
|
2106
|
-
const absolutePath = resolve5(repoRoot,
|
|
2521
|
+
const filePath2 = matches[0];
|
|
2522
|
+
const absolutePath = resolve5(repoRoot, filePath2);
|
|
2107
2523
|
const canonical = await realpath6(absolutePath);
|
|
2108
2524
|
if (!isWithin2(repoRoot, canonical)) {
|
|
2109
2525
|
return { input, confidence: "unknown", candidates: [] };
|
|
@@ -2111,7 +2527,7 @@ async function normalizeSourceLocation(input, repoRootInput) {
|
|
|
2111
2527
|
const lineNumber = await boundedLine(canonical, input.lineNumber);
|
|
2112
2528
|
return {
|
|
2113
2529
|
input,
|
|
2114
|
-
filePath,
|
|
2530
|
+
filePath: filePath2,
|
|
2115
2531
|
absolutePath: canonical,
|
|
2116
2532
|
...lineNumber === void 0 ? {} : { lineNumber },
|
|
2117
2533
|
...input.columnNumber === void 0 ? {} : { columnNumber: input.columnNumber },
|
|
@@ -3039,11 +3455,11 @@ var TaskService = class {
|
|
|
3039
3455
|
for (const target of result.selection.targets) {
|
|
3040
3456
|
const sanitize = async (location) => {
|
|
3041
3457
|
try {
|
|
3042
|
-
const
|
|
3458
|
+
const filePath2 = await this.#git.pathPolicy.assertFilesystemPathAllowed(
|
|
3043
3459
|
location.filePath,
|
|
3044
3460
|
false
|
|
3045
3461
|
);
|
|
3046
|
-
return { ...location, filePath };
|
|
3462
|
+
return { ...location, filePath: filePath2 };
|
|
3047
3463
|
} catch {
|
|
3048
3464
|
return void 0;
|
|
3049
3465
|
}
|
|
@@ -3255,7 +3671,7 @@ var BrowserSessionManager = class {
|
|
|
3255
3671
|
};
|
|
3256
3672
|
|
|
3257
3673
|
// ../../packages/bridge-core/src/verification/commands.ts
|
|
3258
|
-
import { spawn as
|
|
3674
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
3259
3675
|
var MAX_OUTPUT_CHARS = 8e3;
|
|
3260
3676
|
var KILL_GRACE_MS = 250;
|
|
3261
3677
|
function appendOutput(current, chunk) {
|
|
@@ -3269,7 +3685,7 @@ async function runVerificationCommand(configured, cwd, signal) {
|
|
|
3269
3685
|
}
|
|
3270
3686
|
const startedAt = Date.now();
|
|
3271
3687
|
return await new Promise((resolveResult, rejectResult) => {
|
|
3272
|
-
const child =
|
|
3688
|
+
const child = spawn5(executable, arguments_, {
|
|
3273
3689
|
cwd,
|
|
3274
3690
|
detached: process.platform !== "win32",
|
|
3275
3691
|
shell: false,
|
|
@@ -3844,15 +4260,40 @@ ${output}` : ""}`
|
|
|
3844
4260
|
}
|
|
3845
4261
|
|
|
3846
4262
|
// ../../packages/bridge-core/src/bridge/default-control-service.ts
|
|
4263
|
+
function createAgentAdapter(agent) {
|
|
4264
|
+
if (agent.adapter === "claude") {
|
|
4265
|
+
if (agent.reasoningEffort === "minimal") {
|
|
4266
|
+
throw new Error("Claude does not support minimal reasoning effort");
|
|
4267
|
+
}
|
|
4268
|
+
return new ClaudeAdapter({
|
|
4269
|
+
...agent.model === void 0 ? {} : { model: agent.model },
|
|
4270
|
+
...agent.reasoningEffort === void 0 ? {} : { reasoningEffort: agent.reasoningEffort }
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
if (agent.adapter === "codex") {
|
|
4274
|
+
if (agent.reasoningEffort === "max") {
|
|
4275
|
+
throw new Error("Codex does not support max reasoning effort");
|
|
4276
|
+
}
|
|
4277
|
+
return new CodexAdapter({
|
|
4278
|
+
...agent.model === void 0 ? {} : { model: agent.model },
|
|
4279
|
+
...agent.reasoningEffort === void 0 ? {} : { reasoningEffort: agent.reasoningEffort },
|
|
4280
|
+
...agent.profile === void 0 ? {} : { profile: agent.profile }
|
|
4281
|
+
});
|
|
4282
|
+
}
|
|
4283
|
+
throw new Error(`Agent adapter ${agent.adapter} is not implemented in this build`);
|
|
4284
|
+
}
|
|
4285
|
+
function inheritedAgentEnvironment(names, environment) {
|
|
4286
|
+
return Object.fromEntries(
|
|
4287
|
+
names.flatMap((name) => {
|
|
4288
|
+
const value = environment[name];
|
|
4289
|
+
return value === void 0 ? [] : [[name, value]];
|
|
4290
|
+
})
|
|
4291
|
+
);
|
|
4292
|
+
}
|
|
3847
4293
|
async function createDefaultControlService(context, environment = process.env) {
|
|
3848
4294
|
const loaded = await loadVisualDevConfig(context.repoRoot, {
|
|
3849
4295
|
...context.configRoot === void 0 ? {} : { configRoot: context.configRoot }
|
|
3850
4296
|
});
|
|
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
4297
|
const git = await GitTransactionManager.open(context.repoRoot, {
|
|
3857
4298
|
allowed: rebaseWorkspacePatterns(
|
|
3858
4299
|
context.repoRoot,
|
|
@@ -3871,13 +4312,16 @@ async function createDefaultControlService(context, environment = process.env) {
|
|
|
3871
4312
|
projectId: context.projectId,
|
|
3872
4313
|
workspaceRoot: context.workspaceRoot,
|
|
3873
4314
|
upstreamUrl: context.upstreamUrl,
|
|
3874
|
-
adapter:
|
|
4315
|
+
adapter: createAgentAdapter(loaded.config.agent),
|
|
3875
4316
|
store,
|
|
3876
4317
|
git,
|
|
3877
4318
|
maxRunMs: loaded.config.agent.maxRunMs,
|
|
3878
4319
|
maxPending: loaded.config.queue.maxPending,
|
|
3879
4320
|
resumeMode: loaded.config.agent.resumeMode,
|
|
3880
|
-
environment:
|
|
4321
|
+
environment: inheritedAgentEnvironment(
|
|
4322
|
+
loaded.config.agent.inheritEnv,
|
|
4323
|
+
environment
|
|
4324
|
+
)
|
|
3881
4325
|
});
|
|
3882
4326
|
const controlService = createTaskControlService({
|
|
3883
4327
|
taskService,
|
|
@@ -4208,10 +4652,10 @@ async function acquireWorktreeLock(repositoryRoot, options = {}) {
|
|
|
4208
4652
|
}
|
|
4209
4653
|
|
|
4210
4654
|
// ../../packages/bridge-core/src/runtime/repository.ts
|
|
4211
|
-
import { execFile as
|
|
4655
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
4212
4656
|
import { realpath as realpath9 } from "node:fs/promises";
|
|
4213
|
-
import { promisify as
|
|
4214
|
-
var
|
|
4657
|
+
import { promisify as promisify3 } from "node:util";
|
|
4658
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
4215
4659
|
var GitWorktreeNotFoundError = class extends Error {
|
|
4216
4660
|
constructor(cwd, options = {}) {
|
|
4217
4661
|
super(
|
|
@@ -4223,7 +4667,7 @@ var GitWorktreeNotFoundError = class extends Error {
|
|
|
4223
4667
|
};
|
|
4224
4668
|
async function discoverGitWorktreeRoot(cwd = process.cwd()) {
|
|
4225
4669
|
try {
|
|
4226
|
-
const { stdout } = await
|
|
4670
|
+
const { stdout } = await execFileAsync3(
|
|
4227
4671
|
"git",
|
|
4228
4672
|
["-C", cwd, "rev-parse", "--show-toplevel"],
|
|
4229
4673
|
{
|
|
@@ -5253,7 +5697,7 @@ async function startBridgeCore(options, dependencies) {
|
|
|
5253
5697
|
try {
|
|
5254
5698
|
lock = options.lock ?? await acquireWorktreeLock(loadedConfig.repoRoot, { environment });
|
|
5255
5699
|
const host = options.host ?? loadedConfig.config.gateway.host;
|
|
5256
|
-
const configuredPublicUrl = options.publicUrl ?? loadedConfig.config.gateway.publicUrl;
|
|
5700
|
+
const configuredPublicUrl = options.publicUrl ?? loadedConfig.config.gateway.publicUrl ?? options.fallbackPublicUrl;
|
|
5257
5701
|
const publicUrl = configuredPublicUrl === void 0 ? void 0 : normalizePublicUrl(configuredPublicUrl);
|
|
5258
5702
|
const gatewayPort = await findAvailablePort(startPort(loadedConfig, options.listen), host);
|
|
5259
5703
|
const token = generatePairingToken();
|
|
@@ -5373,7 +5817,8 @@ async function startAttachBridge(options, dependencies = {}) {
|
|
|
5373
5817
|
upstreamUrl: normalizeUpstream(options.upstream),
|
|
5374
5818
|
...options.listen === void 0 ? {} : { listen: options.listen },
|
|
5375
5819
|
...options.host === void 0 ? {} : { host: options.host },
|
|
5376
|
-
...options.publicUrl === void 0 ? {} : { publicUrl: options.publicUrl }
|
|
5820
|
+
...options.publicUrl === void 0 ? {} : { publicUrl: options.publicUrl },
|
|
5821
|
+
...options.fallbackPublicUrl === void 0 ? {} : { fallbackPublicUrl: options.fallbackPublicUrl }
|
|
5377
5822
|
},
|
|
5378
5823
|
dependencies
|
|
5379
5824
|
);
|
|
@@ -5409,10 +5854,17 @@ function commandLinePort(argv) {
|
|
|
5409
5854
|
}
|
|
5410
5855
|
return void 0;
|
|
5411
5856
|
}
|
|
5857
|
+
function resolveNextPort(options, argv, environment) {
|
|
5858
|
+
return validPort(options.appPort) ?? commandLinePort(argv) ?? validPort(environment.PORT) ?? 3e3;
|
|
5859
|
+
}
|
|
5412
5860
|
function resolveNextUpstream(options = {}, argv = process.argv, environment = process.env) {
|
|
5413
|
-
const port =
|
|
5861
|
+
const port = resolveNextPort(options, argv, environment);
|
|
5414
5862
|
return `http://127.0.0.1:${port}`;
|
|
5415
5863
|
}
|
|
5864
|
+
function resolveNextPublicUrl(options = {}, argv = process.argv, environment = process.env) {
|
|
5865
|
+
const port = resolveNextPort(options, argv, environment);
|
|
5866
|
+
return `http://localhost:${port}`;
|
|
5867
|
+
}
|
|
5416
5868
|
function isNextDetachedTelemetryProcess(argv = process.argv) {
|
|
5417
5869
|
return argv.some(
|
|
5418
5870
|
(argument) => /[\\/]next[\\/]dist[\\/]telemetry[\\/]detached-flush\.js$/.test(argument)
|
|
@@ -5460,6 +5912,7 @@ async function startOrReuseBridge(options, cwd) {
|
|
|
5460
5912
|
const ownedBridge = await startAttachBridge(
|
|
5461
5913
|
{
|
|
5462
5914
|
upstream: resolveNextUpstream(options),
|
|
5915
|
+
fallbackPublicUrl: resolveNextPublicUrl(options),
|
|
5463
5916
|
...options.bridgeHost === void 0 ? {} : { host: options.bridgeHost },
|
|
5464
5917
|
...options.bridgePort === void 0 ? {} : { listen: options.bridgePort }
|
|
5465
5918
|
},
|
|
@@ -5531,6 +5984,7 @@ export {
|
|
|
5531
5984
|
next_default as default,
|
|
5532
5985
|
isNextDetachedTelemetryProcess,
|
|
5533
5986
|
mergeNextRewrites,
|
|
5987
|
+
resolveNextPublicUrl,
|
|
5534
5988
|
resolveNextUpstream,
|
|
5535
5989
|
withVisualRemote
|
|
5536
5990
|
};
|