From 9145c063f97c4da8d535115ccce6bf8aeeb962ab Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 24 May 2026 03:08:21 +0000 Subject: [PATCH] fix: project runner skills into dev containers --- docs/reference/code-queue-supervision.md | 4 +- .../code-queue-runner-skills-contract-test.ts | 73 +++++++++++++++++++ .../code-queue/docker-compose.d601.yml | 1 + .../code-queue/src/code-agent/opencode.ts | 1 + .../microservices/code-queue/src/index.ts | 1 + .../microservices/code-queue/src/prompts.ts | 2 +- .../code-queue/src/provider-runtime.ts | 24 +++++- .../microservices/code-queue/src/types.ts | 1 + .../k3sctl-adapter/k3s/code-queue.k8s.yaml | 6 ++ .../k3s/dev/unidesk-dev-code-queue.k8s.yaml | 6 ++ 10 files changed, 112 insertions(+), 7 deletions(-) diff --git a/docs/reference/code-queue-supervision.md b/docs/reference/code-queue-supervision.md index 703d9cf9..0aee65c2 100644 --- a/docs/reference/code-queue-supervision.md +++ b/docs/reference/code-queue-supervision.md @@ -238,13 +238,13 @@ bun scripts/code-queue-pr-preflight-example.ts --repo pikasTech/unidesk --base m ### Runner Skill 可用性 -D601 Code Queue runner 的长期 skills source of truth 是宿主 `/home/ubuntu/.agents/skills`,生产和 dev Code Queue Pod 都必须只读挂载到容器内 `/root/.agents/skills`,并设置 `UNIDESK_SKILLS_PATH=/root/.agents/skills`。如果 target 暂缺但 approved source 存在且必需 skill 可读,runtime 必须把 `UNIDESK_SKILLS_PATH` 解析为可用 source 传给 runner,并在 health/preflight 中标记 `runnerUsable=true`、`contractOk=false`、`resolvedPathSource=source-fallback`、`degradedReason=skills-target-missing` 与 `resolution.hostRolloutRequired=true`;这表示任务执行能力可用,但 hostPath 投影仍需 host rollout 修复。不要使用或传播任何拼错的 skills 路径;如果配置的 source 或 target 命中拼写错误路径,health/preflight 必须直接返回 `blocker=forbidden-skills-path-configured`,并在 `pathSpelling.forbiddenPathConfigured=true` 与 `pathSpelling.forbiddenPathRoles` 中标明是 source、target 或两者,而不是降级成普通 missing path。 +D601 Code Queue runner 的长期 skills source of truth 是宿主 `/home/ubuntu/.agents/skills`,生产和 dev Code Queue Pod 都必须只读挂载到容器内 `/root/.agents/skills`,并设置 `UNIDESK_SKILLS_PATH=/root/.agents/skills`。provider dev container 由 scheduler 通过 Docker 启动时,也必须把同一宿主 source 只读 bind 到同一 target,并把 `UNIDESK_SKILLS_PATH` 显式传给 Codex/OpenCode 进程。target 缺失时即使 approved source 可读也不能 fallback 给 runner;health/preflight 必须标记 `runnerUsable=false`、`contractOk=false`、`resolvedPathSource=missing`、`degradedReason=skills-target-missing` 与 `resolution.hostRolloutRequired=true`。不要使用或传播任何拼错的 skills 路径;如果配置的 source 或 target 命中拼写错误路径,health/preflight 必须直接返回 `blocker=forbidden-skills-path-configured`,并在 `pathSpelling.forbiddenPathConfigured=true` 与 `pathSpelling.forbiddenPathRoles` 中标明是 source、target 或两者,而不是降级成普通 missing path。 执行面 `/health`、`/api/dev-ready` 和 `/api/runtime-preflight` 必须输出同一份只读 skill availability report。稳定字段包括 `source`、`target`、`resolvedPath`、`resolvedPathSource`、`resolution`、`requiredSkills`、`missingSkills`、`sourceSkillCount`、`targetSkillCount`、`degraded`、`degradedReason`、`blocker`、`pathSpelling` 和 `valuesPrinted=false`。`requiredSkills` 至少覆盖 `docs-spec`、`cli-spec`、`frontend-design` 与 `playwright-cli`;如果 source 与 target 都缺失、必需 skill 均不可用、拼写错误路径被配置或拼写错误路径已存在,报告必须显示 `ok=false` 和结构化 `blocker`,不能把 runner 能力缺口伪装成业务任务失败。target symlink 到 approved source 属于可接受的兼容修复,必须显示 `resolvedPathSource=target-symlink` 和有界 skill count。 执行面还必须提供 dry-run skills sync/preflight 合同:稳定入口是 `GET /api/skills-sync?dryRun=1`,CLI 入口是 `bun scripts/cli.ts codex skills-sync --dry-run [--full]`。该合同只描述受控 hostPath 生命周期,不复制文件、不从任意路径静默加载、不重启服务、不 rollout Pod、不读取 Secret。默认输出保持紧凑,必须报告 source、target、expected env/mount、required skill 列表、source/target skill counts、missing source/target skills、permission failure count、plannedActions 和修复指令;逐 skill 细节、完整 permission failure 和原始报告只能通过 `--full` 显式展开。非 dry-run 请求必须失败。 -受控生命周期是更新宿主 `/home/ubuntu/.agents/skills` 这一 approved source,然后让生产和 dev Code Queue Pod 通过 manifest 中的 read-only hostPath 挂载读取 `/root/.agents/skills`。在线热更新只能作为临时恢复手段,长期验收必须以 manifest/source-of-truth、dry-run sync contract、结构化 health/preflight 和合同测试为准。需要验证时优先运行: +受控生命周期是更新宿主 `/home/ubuntu/.agents/skills` 这一 approved source,然后让生产和 dev Code Queue Pod 通过 manifest 中的 read-only hostPath 挂载读取 `/root/.agents/skills`;provider dev container 还必须通过启动脚本把同一 source bind 到同一 target。在线热更新只能作为临时恢复手段,长期验收必须以 manifest/source-of-truth、runner container bind、dry-run sync contract、结构化 health/preflight 和合同测试为准。需要验证时优先运行: ```bash bun scripts/cli.ts codex skills-sync --dry-run diff --git a/scripts/code-queue-runner-skills-contract-test.ts b/scripts/code-queue-runner-skills-contract-test.ts index 868fa4d8..c4e6c7fe 100644 --- a/scripts/code-queue-runner-skills-contract-test.ts +++ b/scripts/code-queue-runner-skills-contract-test.ts @@ -3,6 +3,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { spawnSync } from "node:child_process"; import { collectSkillAvailability, collectSkillSyncPreflight } from "../src/components/microservices/code-queue/src/skill-availability"; +import { buildDevContainerPlan, configureProviderRuntime, providerRuntimeForTest } from "../src/components/microservices/code-queue/src/provider-runtime"; import { codexPrPreflightQueryForTest } from "./src/code-queue"; import { summarizeMicroserviceObservation } from "./src/microservices"; @@ -71,6 +72,9 @@ assertCondition(!docsReference.includes(forbiddenPathLiteral), "reference docs m assertCondition(countOccurrences(productionManifest, "name: UNIDESK_SKILLS_PATH") === 3, "production read/write/scheduler must set UNIDESK_SKILLS_PATH", { count: countOccurrences(productionManifest, "name: UNIDESK_SKILLS_PATH"), }); +assertCondition(countOccurrences(productionManifest, "name: CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH") === 3, "production read/write/scheduler must expose the approved runner skills source path", { + count: countOccurrences(productionManifest, "name: CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH"), +}); assertCondition(countOccurrences(productionManifest, "mountPath: /root/.agents/skills") === 3, "production read/write/scheduler must mount skills target", { count: countOccurrences(productionManifest, "mountPath: /root/.agents/skills"), }); @@ -81,6 +85,9 @@ assertCondition(countOccurrences(productionManifest, "name: skills-dir") >= 6, " count: countOccurrences(productionManifest, "name: skills-dir"), }); assertCondition(devManifest.includes("path: /home/ubuntu/.agents/skills"), "dev manifest should keep the same hostPath source of truth"); +assertCondition(countOccurrences(devManifest, "name: CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH") === 3, "dev read/write/scheduler must expose the approved runner skills source path", { + count: countOccurrences(devManifest, "name: CODE_QUEUE_RUNNER_SKILLS_SOURCE_PATH"), +}); const devCodeQueueDeploy = (deployJson.environments?.dev?.services ?? []).find((service) => service.id === "code-queue"); assertCondition(devCodeQueueDeploy !== undefined, "deploy.json dev environment must include code-queue"); @@ -107,6 +114,71 @@ assertCondition(pinnedRuntimePreflight.includes("skills.contractOk && ports.code assertCondition(pinnedIndexSource.includes("skills.contractOk === true"), "deploy.json dev code-queue commit dev-ready must require target projection contract"); assertCondition(pinnedIndexSource.includes("return config.skillsPath"), "deploy.json dev code-queue commit must keep runner UNIDESK_SKILLS_PATH on the configured target"); +configureProviderRuntime({ + config: { + codexHome: "/var/lib/unidesk/code-queue/codex-home", + defaultWorkdir: "/workspace", + devContainerDefaultProviderId: "D601", + devContainerImage: "unidesk-code-queue:d601", + devContainerMasterHost: "74.48.78.17", + devContainerWorkdir: "/home/ubuntu", + executionProviderIds: ["D601"], + mainProviderId: "D601-main", + remoteCodexEnvKeys: [], + remoteDefaultWorkdir: "/home/ubuntu", + runnerSkillsSourcePath: "/home/ubuntu/.agents/skills", + skillsPath: "/root/.agents/skills", + sourceCodexConfig: "/root/.codex/config.toml", + windowsNativeCodexBridgeDir: "/home/ubuntu/.unidesk/code-queue/windows-native-codex", + windowsNativeCodexCommand: "codex app-server --listen stdio://", + windowsNativeCodexConnectHost: "host.docker.internal", + windowsNativeCodexDefaultWorkdir: "/mnt/f/Work/ConStart", + windowsNativeCodexIdleTimeoutMs: 600_000, + }, + safePreview: (value: string, max = 1000) => value.slice(0, max), +}); +const devContainerPlan = buildDevContainerPlan("D601", { workdir: "/home/ubuntu" }); +const devContainerStartScript = providerRuntimeForTest.remoteContainerStartScript(devContainerPlan, false); +assertCondition(devContainerStartScript.includes("SKILLS_SOURCE='/home/ubuntu/.agents/skills'"), "provider dev container start must use the D601 host skills source", devContainerStartScript); +assertCondition(devContainerStartScript.includes("SKILLS_TARGET='/root/.agents/skills'"), "provider dev container start must use the runner target skills path", devContainerStartScript); +assertCondition(devContainerStartScript.includes('-v "$SKILLS_SOURCE":"$SKILLS_TARGET":ro'), "provider dev container must bind source skills read-only to target", devContainerStartScript); +assertCondition(devContainerStartScript.includes('-e UNIDESK_SKILLS_PATH="$SKILLS_TARGET"'), "provider dev container must export UNIDESK_SKILLS_PATH in docker run", devContainerStartScript); +assertCondition(devContainerStartScript.includes('test -r "$UNIDESK_SKILLS_PATH/docs-spec/SKILL.md"'), "provider dev container readiness must verify required docs-spec skill at target", devContainerStartScript); +assertCondition(devContainerStartScript.includes('test -r "$UNIDESK_SKILLS_PATH/cli-spec/SKILL.md"'), "provider dev container readiness must verify required cli-spec skill at target", devContainerStartScript); +assertCondition(devContainerStartScript.includes('test -r "$UNIDESK_SKILLS_PATH/frontend-design/SKILL.md"'), "provider dev container readiness must verify required frontend-design skill at target", devContainerStartScript); +assertCondition(devContainerStartScript.includes("playwright-cli/SKILL.md") && devContainerStartScript.includes("playwright/SKILL.md"), "provider dev container readiness must accept the playwright-cli alias", devContainerStartScript); +assertCondition(devContainerStartScript.includes("reuse_ready") && devContainerStartScript.includes('test "$UNIDESK_SKILLS_PATH" = "/root/.agents/skills"'), "provider dev container reuse must revalidate target skills before keeping an old container", devContainerStartScript); +const remoteCodexCommand = providerRuntimeForTest.remoteAppServerCommand({ + id: "task", + queueId: "default", + prompt: "", + status: "running", + cwd: "/home/ubuntu", + providerId: "D601", + model: "gpt-5.5", + executionMode: "default", + currentAttempt: 1, + attempts: 1, + maxAttempts: 1, + codexThreadId: null, + reasoningEffort: null, + createdAt: "2026-05-24T00:00:00.000Z", + updatedAt: "2026-05-24T00:00:00.000Z", + startedAt: "2026-05-24T00:00:00.000Z", + completedAt: null, + lastActivityAt: "2026-05-24T00:00:00.000Z", + branch: null, + commitSha: null, + title: null, + error: null, + judge: null, + outputSeq: 0, + outputs: [], + events: [], + metadata: {}, +} as never); +assertCondition(remoteCodexCommand.includes("export UNIDESK_SKILLS_PATH=") && remoteCodexCommand.includes("/root/.agents/skills"), "remote codex app-server must receive the target skills env", remoteCodexCommand); + const available = collectSkillAvailability({ source: "/home/ubuntu/.agents/skills", target: "/home/ubuntu/.agents/skills", @@ -446,6 +518,7 @@ process.stdout.write(`${JSON.stringify({ ok: true, checks: [ "production Code Queue mounts /home/ubuntu/.agents/skills read-only at /root/.agents/skills", + "provider dev containers bind /home/ubuntu/.agents/skills read-only at /root/.agents/skills and pass UNIDESK_SKILLS_PATH to Codex/OpenCode", "deploy.json dev Code Queue pins a commit whose manifest and runtime require the target skills projection", "skill availability report exposes source, target, requiredSkills, missingSkills, version fingerprint/mtime, degraded/blocker and valuesPrinted=false", "skills sync dry-run reports source, target, counts, version fingerprint/mtime, missing skills, permission failures, instructions and no-copy actions", diff --git a/src/components/microservices/code-queue/docker-compose.d601.yml b/src/components/microservices/code-queue/docker-compose.d601.yml index fc600edd..76f73674 100644 --- a/src/components/microservices/code-queue/docker-compose.d601.yml +++ b/src/components/microservices/code-queue/docker-compose.d601.yml @@ -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" 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 9c72f6c2..992b539b 100644 --- a/src/components/microservices/code-queue/src/code-agent/opencode.ts +++ b/src/components/microservices/code-queue/src/code-agent/opencode.ts @@ -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("; "); diff --git a/src/components/microservices/code-queue/src/index.ts b/src/components/microservices/code-queue/src/index.ts index 9a5cc9d7..3d83e4d7 100644 --- a/src/components/microservices/code-queue/src/index.ts +++ b/src/components/microservices/code-queue/src/index.ts @@ -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")), diff --git a/src/components/microservices/code-queue/src/prompts.ts b/src/components/microservices/code-queue/src/prompts.ts index 93f4a9ad..59bcc507 100644 --- a/src/components/microservices/code-queue/src/prompts.ts +++ b/src/components/microservices/code-queue/src/prompts.ts @@ -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`。如果该路径不可读或缺少必需 skills,runner/preflight 会以 infra blocker 暴露 target projection 缺口;不要读取或输出宿主 token/auth 配置。", ].join("\n"); export function stripAutoReferenceHint(prompt: string): string { diff --git a/src/components/microservices/code-queue/src/provider-runtime.ts b/src/components/microservices/code-queue/src/provider-runtime.ts index d64efc7c..bb8a7e87 100644 --- a/src/components/microservices/code-queue/src/provider-runtime.ts +++ b/src/components/microservices/code-queue/src/provider-runtime.ts @@ -8,7 +8,7 @@ import type { DevContainerCommandLog, DevContainerPlan, JsonValue, QueueTask, Ru export interface ProviderRuntimeContext { config: Pick; 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, +}; diff --git a/src/components/microservices/code-queue/src/types.ts b/src/components/microservices/code-queue/src/types.ts index b0b8292a..e379c9de 100644 --- a/src/components/microservices/code-queue/src/types.ts +++ b/src/components/microservices/code-queue/src/types.ts @@ -124,6 +124,7 @@ export interface RuntimeConfig { executionProviderIds: string[]; remoteCodexEnvKeys: string[]; skillsPath: string; + runnerSkillsSourcePath: string; resolvedRunnerSkillsPath: () => string; codexHome: string; opencodeXdgDir: string; diff --git a/src/components/microservices/k3sctl-adapter/k3s/code-queue.k8s.yaml b/src/components/microservices/k3sctl-adapter/k3s/code-queue.k8s.yaml index 450f803c..5052000b 100644 --- a/src/components/microservices/k3sctl-adapter/k3s/code-queue.k8s.yaml +++ b/src/components/microservices/k3sctl-adapter/k3s/code-queue.k8s.yaml @@ -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 diff --git a/src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-code-queue.k8s.yaml b/src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-code-queue.k8s.yaml index 9689de1b..cd85e325 100644 --- a/src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-code-queue.k8s.yaml +++ b/src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-code-queue.k8s.yaml @@ -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