fix: project runner skills into dev containers

This commit is contained in:
Codex
2026-05-24 03:08:21 +00:00
parent 99e91c1c83
commit 9145c063f9
10 changed files with 112 additions and 7 deletions
@@ -24,6 +24,7 @@ services:
CODE_QUEUE_STARTUP_OA_BACKFILL_ENABLED: "${CODE_QUEUE_STARTUP_OA_BACKFILL_ENABLED:-false}"
CODE_QUEUE_WORKDIR: "${CODE_QUEUE_WORKDIR:-/workspace}"
UNIDESK_SKILLS_PATH: "${UNIDESK_SKILLS_PATH:-/root/.agents/skills}"
CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH: "${CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH:-/home/ubuntu/.agents/skills}"
CODE_QUEUE_CODEX_HOME: "/var/lib/unidesk/code-queue/codex-home"
CODE_QUEUE_OPENCODE_XDG_DIR: "/var/lib/unidesk/code-queue/opencode-xdg"
CODE_QUEUE_SOURCE_CODEX_CONFIG: "/root/.codex/config.toml"
@@ -166,6 +166,7 @@ function remoteOpenCodeRunCommand(task: QueueTask, prompt: string): string {
`export MINIMAX_API_BASE=${ctx().shellQuote(ctx().config.minimaxApiBase)}`,
`export MINIMAX_MODEL=${ctx().shellQuote(ctx().config.minimaxModel)}`,
`export OPENCODE_CONFIG_CONTENT=${ctx().shellQuote(openCodeConfigContent())}`,
`export UNIDESK_SKILLS_PATH=${ctx().shellQuote(ctx().config.resolvedRunnerSkillsPath())}`,
...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("; ");
@@ -402,6 +402,7 @@ function readConfig(): RuntimeConfig {
executionProviderIds,
remoteCodexEnvKeys: envList("CODE_QUEUE_REMOTE_CODEX_ENV_KEYS", ["OPENAI_API_KEY", "CRS_OAI_KEY", "OPENAI_BASE_URL", "OPENAI_API_BASE", "DEEPSEEK_API_KEY", "DEEPSEEK_API_BASE", "DEEPSEEK_MODEL", "MINIMAX_API_KEY", "MINIMAX_API_BASE", "MINIMAX_MODEL", "GH_TOKEN", "GITHUB_TOKEN", "GH_HOST", "GITHUB_API_URL", "GH_REPO"]),
skillsPath: envString("UNIDESK_SKILLS_PATH", "/root/.agents/skills"),
runnerSkillsSourcePath: envString("CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH", "/home/ubuntu/.agents/skills"),
resolvedRunnerSkillsPath,
codexHome: envString("CODE_QUEUE_CODEX_HOME", "/var/lib/unidesk/code-queue/codex-home"),
opencodeXdgDir: envString("CODE_QUEUE_OPENCODE_XDG_DIR", resolve(dataDir, "opencode-xdg")),
@@ -8,7 +8,7 @@ export const codeQueueEnvironmentHintTitle = "# Code Queue 运行环境提示";
export const codeQueueEnvironmentHint = [
codeQueueEnvironmentHintTitle,
"如果当前 Code Queue Docker 容器缺少完成任务所需的环境、系统包或语言依赖,可以先在容器内临时安装以推进当前任务;同时必须把该依赖补到 `src/components/microservices/code-queue/Dockerfile`,让后续任务重建镜像后可直接使用。",
"任务可通过 `UNIDESK_SKILLS_PATH` 读取注入的宿主 skills;默认目标是 `/root/.agents/skills`如果该挂载暂缺但 approved source `/home/ubuntu/.agents/skills` 可读runner 会把 `UNIDESK_SKILLS_PATH` 解析到可用 source 并在 `/api/dev-ready` 的 `devReady.skills.resolution` 中报告需要 host rollout 的合同退化,不要读取或输出宿主 token/auth 配置。",
"任务可通过 `UNIDESK_SKILLS_PATH` 读取注入的宿主 skills;默认目标是 `/root/.agents/skills`如果该路径不可读或缺少必需 skillsrunner/preflight 会以 infra blocker 暴露 target projection 缺口;不要读取或输出宿主 token/auth 配置。",
].join("\n");
export function stripAutoReferenceHint(prompt: string): string {
@@ -8,7 +8,7 @@ import type { DevContainerCommandLog, DevContainerPlan, JsonValue, QueueTask, Ru
export interface ProviderRuntimeContext {
config: Pick<RuntimeConfig,
"codexHome" | "defaultWorkdir" | "devContainerDefaultProviderId" | "devContainerImage" | "devContainerMasterHost" | "devContainerWorkdir" | "executionProviderIds" | "mainProviderId" | "remoteCodexEnvKeys" | "remoteDefaultWorkdir" | "sourceCodexConfig" | "windowsNativeCodexBridgeDir" | "windowsNativeCodexCommand" | "windowsNativeCodexConnectHost" | "windowsNativeCodexDefaultWorkdir" | "windowsNativeCodexIdleTimeoutMs"
"codexHome" | "defaultWorkdir" | "devContainerDefaultProviderId" | "devContainerImage" | "devContainerMasterHost" | "devContainerWorkdir" | "executionProviderIds" | "mainProviderId" | "remoteCodexEnvKeys" | "remoteDefaultWorkdir" | "runnerSkillsSourcePath" | "skillsPath" | "sourceCodexConfig" | "windowsNativeCodexBridgeDir" | "windowsNativeCodexCommand" | "windowsNativeCodexConnectHost" | "windowsNativeCodexDefaultWorkdir" | "windowsNativeCodexIdleTimeoutMs"
>;
safePreview: (value: string, max?: number) => string;
}
@@ -432,12 +432,20 @@ REQUESTED_IMAGE=${shellQuote(plan.image)}
KEY_DIR=${shellQuote(plan.keyDir)}
WORKDIR=${shellQuote(plan.workdir)}
CONTAINER_WORKDIR=${shellQuote(plan.containerWorkdir)}
SKILLS_SOURCE=${shellQuote(ctx().config.runnerSkillsSourcePath)}
SKILLS_TARGET=${shellQuote(ctx().config.skillsPath)}
IMAGE="$REQUESTED_IMAGE"
SSH_DIR="\${UNIDESK_HOST_ROOT_SSH_DIR:-$HOME/.ssh}"
SSH_MOUNT_ARGS=()
if [ -d "$SSH_DIR" ]; then SSH_MOUNT_ARGS=(-v "$SSH_DIR":/root/.ssh:ro); fi
HOST_PATH_MOUNT_ARGS=()
if [ -d /mnt ]; then HOST_PATH_MOUNT_ARGS+=(-v /mnt:/mnt); fi
SKILLS_MOUNT_ARGS=()
if [ ! -d "$SKILLS_SOURCE" ]; then
echo "missing runner skills source $SKILLS_SOURCE; expected read-only projection to $SKILLS_TARGET" >&2
exit 1
fi
SKILLS_MOUNT_ARGS=(-v "$SKILLS_SOURCE":"$SKILLS_TARGET":ro)
if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then
echo "missing dev container image $REQUESTED_IMAGE" >&2
exit 1
@@ -451,7 +459,7 @@ if [ ${forceRecreate ? "1" : "0"} -eq 0 ] && docker inspect "$CONTAINER" >/dev/n
STATE=$(docker inspect "$CONTAINER" --format '{{.State.Status}}' 2>/dev/null || true)
LABEL_WORKDIR=$(docker inspect "$CONTAINER" --format '{{ index .Config.Labels "unidesk.workdir" }}' 2>/dev/null || true)
if [ "$STATE" = "running" ] && [ "$LABEL_WORKDIR" = "$WORKDIR" ]; then
docker exec "$CONTAINER" bash -lc 'echo reuse_ready; command -v bash; test -d /run/unidesk-dev-proxy; test -d /var/lib/unidesk/code-queue/codex-home; test -d /var/lib/unidesk/code-queue/opencode-xdg' >/dev/null
docker exec "$CONTAINER" bash -lc 'echo reuse_ready; command -v bash; test "$UNIDESK_SKILLS_PATH" = "/root/.agents/skills"; test -r "$UNIDESK_SKILLS_PATH/docs-spec/SKILL.md"; test -r "$UNIDESK_SKILLS_PATH/cli-spec/SKILL.md"; test -r "$UNIDESK_SKILLS_PATH/frontend-design/SKILL.md"; if [ -r "$UNIDESK_SKILLS_PATH/playwright-cli/SKILL.md" ]; then :; else test -r "$UNIDESK_SKILLS_PATH/playwright/SKILL.md"; fi; test -d /run/unidesk-dev-proxy; test -d /var/lib/unidesk/code-queue/codex-home; test -d /var/lib/unidesk/code-queue/opencode-xdg' >/dev/null
echo "remote_container_reused container=$CONTAINER image=$(docker inspect "$CONTAINER" --format '{{.Config.Image}}') workdir=$WORKDIR"
exit 0
fi
@@ -473,6 +481,7 @@ cid=$(docker run -d \\
--env-file "$KEY_DIR/codex-env" \\
-e CODEX_HOME=${shellQuote(plan.remoteCodexHome)} \\
-e CODEX_INTERNAL_ORIGINATOR_OVERRIDE=unidesk_code_queue \\
-e UNIDESK_SKILLS_PATH="$SKILLS_TARGET" \\
-e XDG_DATA_HOME=${shellQuote(resolve(plan.remoteOpencodeXdgDir, "data"))} \\
-e XDG_CONFIG_HOME=${shellQuote(resolve(plan.remoteOpencodeXdgDir, "config"))} \\
-e XDG_CACHE_HOME=${shellQuote(resolve(plan.remoteOpencodeXdgDir, "cache"))} \\
@@ -484,14 +493,15 @@ cid=$(docker run -d \\
-v "$KEY_DIR/opencode-xdg":${shellQuote(plan.remoteOpencodeXdgDir)} \\
"\${SSH_MOUNT_ARGS[@]}" \\
"\${HOST_PATH_MOUNT_ARGS[@]}" \\
"\${SKILLS_MOUNT_ARGS[@]}" \\
-v "$WORKDIR":"$CONTAINER_WORKDIR" \\
-v "$WORKDIR":/root/unidesk \\
-w "$CONTAINER_WORKDIR" \\
"$IMAGE" \\
bash -lc 'mkdir -p /tmp/unidesk-tools; ln -sf /usr/local/bin/busybox /tmp/unidesk-tools/ip; ln -sf /usr/local/bin/busybox /tmp/unidesk-tools/ping; export PATH=/tmp/unidesk-tools:$PATH; echo ready; sleep infinity')
docker exec "$CONTAINER" bash -lc 'mkdir -p /tmp/unidesk-tools; ln -sf /usr/local/bin/busybox /tmp/unidesk-tools/ip; ln -sf /usr/local/bin/busybox /tmp/unidesk-tools/ping; export PATH=/tmp/unidesk-tools:$PATH; echo container=$(hostname); pwd; ip route show default; ls -ld /dev/net/tun /run/unidesk-dev-proxy/id_ed25519 /var/lib/unidesk/code-queue/codex-home /var/lib/unidesk/code-queue/opencode-xdg; command -v ssh; command -v ip; command -v ping'
docker exec "$CONTAINER" bash -lc 'mkdir -p /tmp/unidesk-tools; ln -sf /usr/local/bin/busybox /tmp/unidesk-tools/ip; ln -sf /usr/local/bin/busybox /tmp/unidesk-tools/ping; export PATH=/tmp/unidesk-tools:$PATH; echo container=$(hostname); pwd; ip route show default; test "$UNIDESK_SKILLS_PATH" = "/root/.agents/skills"; test -d "$UNIDESK_SKILLS_PATH"; test -r "$UNIDESK_SKILLS_PATH/docs-spec/SKILL.md"; test -r "$UNIDESK_SKILLS_PATH/cli-spec/SKILL.md"; test -r "$UNIDESK_SKILLS_PATH/frontend-design/SKILL.md"; if [ -r "$UNIDESK_SKILLS_PATH/playwright-cli/SKILL.md" ]; then :; else test -r "$UNIDESK_SKILLS_PATH/playwright/SKILL.md"; fi; ls -ld /dev/net/tun /run/unidesk-dev-proxy/id_ed25519 /var/lib/unidesk/code-queue/codex-home /var/lib/unidesk/code-queue/opencode-xdg "$UNIDESK_SKILLS_PATH"; command -v ssh; command -v ip; command -v ping'
docker exec "$CONTAINER" bash -lc 'command -v iptables; iptables --version'
echo "remote_container_ready container=$CONTAINER cid=$cid image=$IMAGE workdir=$WORKDIR"`;
echo "remote_container_ready container=$CONTAINER cid=$cid image=$IMAGE workdir=$WORKDIR skillsTarget=$SKILLS_TARGET"`;
}
function masterProxyPrepareScript(plan: DevContainerPlan): string {
@@ -1016,6 +1026,7 @@ function remoteAppServerCommand(task: QueueTask): string {
`cd ${shellQuote(task.cwd)}`,
`export CODEX_HOME=${shellQuote(plan.remoteCodexHome)}`,
"export CODEX_INTERNAL_ORIGINATOR_OVERRIDE=unidesk_code_queue",
`export UNIDESK_SKILLS_PATH=${shellQuote(ctx().config.skillsPath)}`,
gitConfigExports,
"exec codex app-server --listen stdio://",
].join("; ");
@@ -1093,3 +1104,8 @@ export {
throwIfCommandFailed,
windowsNativeAppServerCommand,
};
export const providerRuntimeForTest = {
remoteContainerStartScript,
remoteAppServerCommand,
};
@@ -124,6 +124,7 @@ export interface RuntimeConfig {
executionProviderIds: string[];
remoteCodexEnvKeys: string[];
skillsPath: string;
runnerSkillsSourcePath: string;
resolvedRunnerSkillsPath: () => string;
codexHome: string;
opencodeXdgDir: string;
@@ -82,6 +82,8 @@ spec:
value: "/root/.codex/config.toml"
- name: UNIDESK_SKILLS_PATH
value: "/root/.agents/skills"
- name: CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH
value: "/home/ubuntu/.agents/skills"
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
@@ -328,6 +330,8 @@ spec:
value: "/root/.codex/config.toml"
- name: UNIDESK_SKILLS_PATH
value: "/root/.agents/skills"
- name: CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH
value: "/home/ubuntu/.agents/skills"
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
@@ -1033,6 +1037,8 @@ spec:
value: "/root/.codex/config.toml"
- name: UNIDESK_SKILLS_PATH
value: "/root/.agents/skills"
- name: CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH
value: "/home/ubuntu/.agents/skills"
- name: CODE_QUEUE_DEFAULT_MODEL
value: "gpt-5.5"
- name: CODE_QUEUE_MODELS
@@ -331,6 +331,8 @@ spec:
value: never
- name: UNIDESK_SKILLS_PATH
value: /root/.agents/skills
- name: CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH
value: /home/ubuntu/.agents/skills
- name: CODE_QUEUE_EGRESS_PROXY_ENABLED
value: "true"
- name: CODE_QUEUE_EGRESS_PROXY_URL
@@ -542,6 +544,8 @@ spec:
value: "false"
- name: UNIDESK_SKILLS_PATH
value: /root/.agents/skills
- name: CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH
value: /home/ubuntu/.agents/skills
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED
value: "false"
- name: CODE_QUEUE_CODEX_SQLITE_LOG_EXPORT_ENABLED
@@ -694,6 +698,8 @@ spec:
value: "false"
- name: UNIDESK_SKILLS_PATH
value: /root/.agents/skills
- name: CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH
value: /home/ubuntu/.agents/skills
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED
value: "false"
- name: CODE_QUEUE_CODEX_SQLITE_LOG_EXPORT_ENABLED