fix: harden v0.2 code agent transport
This commit is contained in:
@@ -1420,10 +1420,60 @@ function tomlString(value) {
|
||||
return JSON.stringify(String(value ?? ""));
|
||||
}
|
||||
|
||||
function spawnCodexAppServerProcess(command, args, options = {}) {
|
||||
const native = resolveCodexNativeSpawn(command, options.env ?? process.env);
|
||||
return spawn(native?.command ?? command, args, {
|
||||
...options,
|
||||
env: native?.env ?? options.env
|
||||
});
|
||||
}
|
||||
|
||||
function resolveCodexNativeSpawn(command, env = process.env) {
|
||||
const resolved = commandPathSync(command, env);
|
||||
if (!resolved) return null;
|
||||
let real = resolved;
|
||||
try {
|
||||
real = realpathSync(resolved);
|
||||
} catch {
|
||||
// Keep the commandPathSync result; spawn will surface any executable error.
|
||||
}
|
||||
const packageRoot = findPackageRoot(real);
|
||||
if (!packageRoot) return null;
|
||||
const candidates = codexNativeBinaryCandidates(packageRoot);
|
||||
const nativeCommand = candidates.find((candidate) => existsSync(candidate) && accessSyncBoolean(candidate, fsConstants.X_OK));
|
||||
if (!nativeCommand) return null;
|
||||
const pathDir = path.resolve(nativeCommand, "..", "..", "path");
|
||||
const envPath = String(env.PATH ?? process.env.PATH ?? "");
|
||||
return {
|
||||
command: nativeCommand,
|
||||
env: {
|
||||
...env,
|
||||
PATH: existsSync(pathDir) ? [pathDir, envPath].filter(Boolean).join(path.delimiter) : envPath,
|
||||
CODEX_MANAGED_BY_NPM: env.CODEX_MANAGED_BY_NPM ?? "1"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function codexNativeBinaryCandidates(packageRoot) {
|
||||
if (process.platform === "linux" && process.arch === "x64") {
|
||||
return [
|
||||
path.join(packageRoot, "..", "codex-linux-x64", "vendor", "x86_64-unknown-linux-musl", "codex", "codex"),
|
||||
path.join(packageRoot, "vendor", "x86_64-unknown-linux-musl", "codex", "codex")
|
||||
];
|
||||
}
|
||||
if (process.platform === "linux" && process.arch === "arm64") {
|
||||
return [
|
||||
path.join(packageRoot, "..", "codex-linux-arm64", "vendor", "aarch64-unknown-linux-musl", "codex", "codex"),
|
||||
path.join(packageRoot, "vendor", "aarch64-unknown-linux-musl", "codex", "codex")
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export function createCodexAppServerJsonLineClient({ command = DEFAULT_CODEX_STDIO_COMMAND, args = null, env = process.env, cwd = repoRoot, onNotification = null } = {}) {
|
||||
const spawnArgs = Array.isArray(args) ? args : codexAppServerArgs(env);
|
||||
const childDetached = process.platform !== "win32";
|
||||
const child = spawn(command, spawnArgs, {
|
||||
const child = spawnCodexAppServerProcess(command, spawnArgs, {
|
||||
cwd,
|
||||
env,
|
||||
detached: childDetached,
|
||||
@@ -1464,7 +1514,7 @@ export function createCodexAppServerJsonLineClient({ command = DEFAULT_CODEX_STD
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const id = typeof payload.id === "number" ? payload.id : null;
|
||||
const id = typeof payload.id === "number" || typeof payload.id === "string" ? payload.id : null;
|
||||
const method = typeof payload.method === "string" ? payload.method : null;
|
||||
if (id !== null && method === null) {
|
||||
const request = pending.get(id);
|
||||
@@ -1479,7 +1529,7 @@ export function createCodexAppServerJsonLineClient({ command = DEFAULT_CODEX_STD
|
||||
return;
|
||||
}
|
||||
if (id !== null && method !== null) {
|
||||
handleServerRequest(id, method);
|
||||
handleServerRequest(id, method, extractAppServerRecord(payload.params));
|
||||
return;
|
||||
}
|
||||
if (method !== null && notificationHandler) {
|
||||
@@ -1490,6 +1540,7 @@ export function createCodexAppServerJsonLineClient({ command = DEFAULT_CODEX_STD
|
||||
function closeWith(message, code, signal) {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
terminateChildProcessGroup(child, { detached: childDetached });
|
||||
for (const [id, request] of pending.entries()) {
|
||||
pending.delete(id);
|
||||
clearTimeout(request.timer);
|
||||
@@ -1547,14 +1598,44 @@ export function createCodexAppServerJsonLineClient({ command = DEFAULT_CODEX_STD
|
||||
child.stdin.write(`${JSON.stringify({ method, params })}\n`);
|
||||
}
|
||||
|
||||
function handleServerRequest(id, method) {
|
||||
if (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval") {
|
||||
child.stdin.write(`${JSON.stringify({ id, result: { decision: "decline" } })}\n`);
|
||||
function handleServerRequest(id, method, params = {}) {
|
||||
if (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval" || method === "item/permissions/requestApproval") {
|
||||
const approval = approvalResponseForServerRequest(params);
|
||||
notifyClientRequest(method, params, approval.decision);
|
||||
child.stdin.write(`${JSON.stringify({ id, result: approval })}\n`);
|
||||
return;
|
||||
}
|
||||
if (method === "item/tool/requestUserInput" || method === "mcpServer/elicitation/request") {
|
||||
notifyClientRequest(method, params, "denied");
|
||||
child.stdin.write(`${JSON.stringify({ id, result: { canceled: true, decision: "denied", message: "Non-interactive HWLAB Code Agent cannot request user input during a running turn." } })}\n`);
|
||||
return;
|
||||
}
|
||||
notifyClientRequest(method, params, "unsupported");
|
||||
child.stdin.write(`${JSON.stringify({ id, error: { code: -32601, message: `Unsupported client-side request: ${method}` } })}\n`);
|
||||
}
|
||||
|
||||
function approvalResponseForServerRequest(params = {}) {
|
||||
const available = Array.isArray(params?.availableDecisions) ? params.availableDecisions.map(String) : [];
|
||||
const decision = available.find((item) => item === "approved")
|
||||
?? available.find((item) => item === "approved_for_session")
|
||||
?? available.find((item) => item.startsWith("approved"))
|
||||
?? "approved";
|
||||
return { decision };
|
||||
}
|
||||
|
||||
function notifyClientRequest(method, params, decision) {
|
||||
notificationHandler?.({
|
||||
method: "client/request/handled",
|
||||
params: {
|
||||
method,
|
||||
decision,
|
||||
itemId: optionalId(params?.itemId ?? params?.item?.id),
|
||||
approvalId: optionalId(params?.approvalId ?? params?.id),
|
||||
targetItemId: optionalId(params?.targetItemId)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function startThread({ model, cwd: threadCwd, sandbox, approvalPolicy = "never", serviceName = "hwlab-cloud-api" } = {}, timeoutMs = 30000) {
|
||||
const result = await request("thread/start", dropEmpty({
|
||||
model,
|
||||
@@ -1616,6 +1697,10 @@ export function createCodexAppServerJsonLineClient({ command = DEFAULT_CODEX_STD
|
||||
}
|
||||
|
||||
function terminateChildProcess(child, { detached = false } = {}) {
|
||||
return terminateChildProcessGroup(child, { detached });
|
||||
}
|
||||
|
||||
function terminateChildProcessGroup(child, { detached = false } = {}) {
|
||||
if (!child || child.killed) return;
|
||||
try {
|
||||
if (detached && Number.isInteger(child.pid)) {
|
||||
|
||||
Reference in New Issue
Block a user