diff --git a/src/components/microservices/code-queue/Dockerfile b/src/components/microservices/code-queue/Dockerfile index 11642ed2..75c9735e 100644 --- a/src/components/microservices/code-queue/Dockerfile +++ b/src/components/microservices/code-queue/Dockerfile @@ -45,7 +45,7 @@ RUN (command -v codex >/dev/null 2>&1 && command -v opencode >/dev/null 2>&1 && WORKDIR /app/src/components/microservices/code-queue COPY src/components/microservices/code-queue/package.json ./package.json -RUN test -d node_modules/postgres || bun install --production +RUN test -d node_modules/typescript || bun install COPY src/components/shared /app/src/components/shared COPY src/components/microservices/code-queue/tsconfig.json ./tsconfig.json COPY src/components/microservices/code-queue/src ./src diff --git a/src/components/microservices/code-queue/src/code-agent/codex.ts b/src/components/microservices/code-queue/src/code-agent/codex.ts index 114b4000..025f5050 100644 --- a/src/components/microservices/code-queue/src/code-agent/codex.ts +++ b/src/components/microservices/code-queue/src/code-agent/codex.ts @@ -5,7 +5,7 @@ import { existsSync, readFileSync } from "node:fs"; import * as readline from "node:readline"; import type { AppServerExit, CodexEventSummary, CodexRunResult, JsonValue, QueueTask, RuntimeConfig, SessionCommandOutput, TerminalStatus } from "../types"; import type { ActiveRun, CodeAgentClient } from "./common"; -import { extractRecord, extractString, terminalStatus, textInput } from "./common"; +import { extractRecord, extractString, terminalStatus, textInput, withCodeAgentGitConfigEnv } from "./common"; export interface CodexPortContext { config: Pick; @@ -96,14 +96,14 @@ function codexAppServerEnv(task: QueueTask): NodeJS.ProcessEnv { CODEX_HOME: ctx().config.codexHome, CODEX_INTERNAL_ORIGINATOR_OVERRIDE: "unidesk_code_queue", }; - if (!shouldRunCodexDirect()) return env; + if (!shouldRunCodexDirect()) return withCodeAgentGitConfigEnv(env); for (const key of codexProxyEnvKeys) delete env[key]; const directHosts = splitEnvList(process.env.CODE_QUEUE_CODEX_DIRECT_HOSTS, defaultCodexDirectHosts).join(","); env.NO_PROXY = [env.NO_PROXY, directHosts].filter((part) => part !== undefined && part.length > 0).join(","); env.no_proxy = [env.no_proxy, directHosts].filter((part) => part !== undefined && part.length > 0).join(","); ctx().appendOutput(task, "system", `codex app-server direct network enabled for ${directHosts}\n`, "codex/network"); ctx().logger("info", "codex_app_server_direct_network", { taskId: task.id, directHosts }); - return env; + return withCodeAgentGitConfigEnv(env); } class AppServerClient { diff --git a/src/components/microservices/code-queue/src/code-agent/common.ts b/src/components/microservices/code-queue/src/code-agent/common.ts index 151fbbeb..01677b88 100644 --- a/src/components/microservices/code-queue/src/code-agent/common.ts +++ b/src/components/microservices/code-queue/src/code-agent/common.ts @@ -26,12 +26,47 @@ export interface ActiveRunSlotWaiter { enqueuedAt: string; } +const gitProxyEnvKeys = ["CODE_QUEUE_EGRESS_PROXY_URL", "HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy"]; + export const minimaxM27Model = "minimax-m2.7"; export const defaultCodeModels = ["gpt-5.5", "gpt-5.4-mini", "gpt-5.4", minimaxM27Model]; export const opencodeNpmPackage = "opencode-ai@1.14.48"; export const defaultCodeExecutionMode: CodeExecutionMode = "default"; export const codeExecutionModes: CodeExecutionMode[] = ["default", "windows-native"]; +function proxyUrlFromEnv(env: NodeJS.ProcessEnv): string { + for (const key of gitProxyEnvKeys) { + const value = env[key]; + if (typeof value === "string" && value.trim().length > 0) return value.trim().replace(/\/+$/u, ""); + } + return ""; +} + +function gitConfigCount(env: NodeJS.ProcessEnv): number { + const raw = Number(env.GIT_CONFIG_COUNT); + return Number.isInteger(raw) && raw >= 0 ? raw : 0; +} + +export function codeAgentGitConfigEntries(proxyUrl = proxyUrlFromEnv(process.env)): Array<[string, string]> { + return [ + ["safe.directory", "*"], + ["http.proxy", proxyUrl], + ["https.proxy", proxyUrl], + ]; +} + +export function withCodeAgentGitConfigEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { + const next: NodeJS.ProcessEnv = { ...env }; + let index = gitConfigCount(next); + for (const [key, value] of codeAgentGitConfigEntries(proxyUrlFromEnv(next))) { + next[`GIT_CONFIG_KEY_${index}`] = key; + next[`GIT_CONFIG_VALUE_${index}`] = value; + index += 1; + } + next.GIT_CONFIG_COUNT = String(index); + return next; +} + export function normalizeCodeModel(value: string): string { const raw = String(value || "").trim(); if (raw.length === 0) return raw; diff --git a/src/components/microservices/code-queue/src/code-agent/opencode.ts b/src/components/microservices/code-queue/src/code-agent/opencode.ts index 1aec9f92..a4c810ad 100644 --- a/src/components/microservices/code-queue/src/code-agent/opencode.ts +++ b/src/components/microservices/code-queue/src/code-agent/opencode.ts @@ -6,7 +6,7 @@ import { resolve } from "node:path"; import * as readline from "node:readline"; import type { AppServerExit, CodexEventSummary, CodexRunResult, JsonValue, QueueTask, RuntimeConfig, TerminalStatus } from "../types"; import type { ActiveRun, CodeAgentClient } from "./common"; -import { extractRecord, minimaxM27Model, normalizeCodeModel, stripAnsi } from "./common"; +import { codeAgentGitConfigEntries, extractRecord, minimaxM27Model, normalizeCodeModel, stripAnsi, withCodeAgentGitConfigEnv } from "./common"; export interface OpenCodePortContext { config: Pick; @@ -106,14 +106,14 @@ function ensureOpenCodeXdgDirs(env: Record): void { function openCodeEnv(task: QueueTask): NodeJS.ProcessEnv { const xdgEnv = ctx().openCodeXdgEnv(openCodeTaskXdgRoot(task)); ensureOpenCodeXdgDirs(xdgEnv); - return { + return withCodeAgentGitConfigEnv({ ...process.env, ...xdgEnv, MINIMAX_API_KEY: ctx().config.minimaxApiKey, MINIMAX_API_BASE: ctx().config.minimaxApiBase, MINIMAX_MODEL: ctx().config.minimaxModel, OPENCODE_CONFIG_CONTENT: openCodeConfigContent(), - }; + }); } function shellJoin(args: string[]): string { @@ -130,11 +130,14 @@ function openCodeRunArgs(task: QueueTask, prompt: string): string[] { function remoteOpenCodeRunCommand(task: QueueTask, prompt: string): string { const plan = ctx().buildDevContainerPlan(task.providerId, { workdir: ctx().remoteHostWorkdirForTask(task) }); const xdgEnv = ctx().openCodeXdgEnv(resolve(plan.remoteOpencodeXdgDir, "tasks", task.id)); + const gitConfigEntries = codeAgentGitConfigEntries(""); const envExports = [ ...Object.entries(xdgEnv).map(([key, value]) => `export ${key}=${ctx().shellQuote(value)}`), `export MINIMAX_API_BASE=${ctx().shellQuote(ctx().config.minimaxApiBase)}`, `export MINIMAX_MODEL=${ctx().shellQuote(ctx().config.minimaxModel)}`, `export OPENCODE_CONFIG_CONTENT=${ctx().shellQuote(openCodeConfigContent())}`, + ...gitConfigEntries.map(([key, value], index) => `export GIT_CONFIG_KEY_$((\${GIT_CONFIG_COUNT:-0}+${index}))=${ctx().shellQuote(key)} GIT_CONFIG_VALUE_$((\${GIT_CONFIG_COUNT:-0}+${index}))=${ctx().shellQuote(value)}`), + `export GIT_CONFIG_COUNT=$((\${GIT_CONFIG_COUNT:-0}+${gitConfigEntries.length}))`, ].join("; "); const inner = [ "set -euo pipefail", diff --git a/src/components/microservices/code-queue/src/provider-runtime.ts b/src/components/microservices/code-queue/src/provider-runtime.ts index b2171db9..6b3150d2 100644 --- a/src/components/microservices/code-queue/src/provider-runtime.ts +++ b/src/components/microservices/code-queue/src/provider-runtime.ts @@ -3,7 +3,7 @@ import { spawn } from "node:child_process"; import { existsSync, readFileSync } from "node:fs"; import { resolve } from "node:path"; -import { codeExecutionModeInfo, opencodeNpmPackage } from "./code-agent/common"; +import { codeAgentGitConfigEntries, codeExecutionModeInfo, opencodeNpmPackage } from "./code-agent/common"; import type { DevContainerCommandLog, DevContainerPlan, JsonValue, QueueTask, RuntimeConfig } from "./types"; export interface ProviderRuntimeContext { @@ -997,12 +997,18 @@ INNER`; function remoteAppServerCommand(task: QueueTask): string { const plan = buildDevContainerPlan(task.providerId, { workdir: remoteHostWorkdirForTask(task) }); + const gitConfigEntries = codeAgentGitConfigEntries(""); + const gitConfigExports = [ + ...gitConfigEntries.map(([key, value], index) => `export GIT_CONFIG_KEY_$((\${GIT_CONFIG_COUNT:-0}+${index}))=${shellQuote(key)} GIT_CONFIG_VALUE_$((\${GIT_CONFIG_COUNT:-0}+${index}))=${shellQuote(value)}`), + `export GIT_CONFIG_COUNT=$((\${GIT_CONFIG_COUNT:-0}+${gitConfigEntries.length}))`, + ].join("; "); const inner = [ "set -euo pipefail", `mkdir -p ${shellQuote(task.cwd)}`, `cd ${shellQuote(task.cwd)}`, `export CODEX_HOME=${shellQuote(plan.remoteCodexHome)}`, "export CODEX_INTERNAL_ORIGINATOR_OVERRIDE=unidesk_code_queue", + gitConfigExports, "exec codex app-server --listen stdio://", ].join("; "); return `docker exec -i ${shellQuote(plan.containerName)} bash -lc ${shellQuote(inner)}`;