feat: tighten code queue supervision cli

This commit is contained in:
Codex
2026-05-20 04:15:18 +00:00
parent 6e86d3600c
commit f9b190945d
11 changed files with 531 additions and 40 deletions
@@ -2,6 +2,7 @@ ARG CODE_QUEUE_BASE_IMAGE=oven/bun:1-debian
FROM ${CODE_QUEUE_BASE_IMAGE}
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
ENV UNIDESK_SKILLS_PATH=/root/.agents/skills
RUN (command -v codex >/dev/null 2>&1 && command -v opencode >/dev/null 2>&1 && command -v docker >/dev/null 2>&1 && command -v rg >/dev/null 2>&1 && command -v cargo >/dev/null 2>&1 && command -v rustc >/dev/null 2>&1 && command -v rustfmt >/dev/null 2>&1) \
|| (apt-get update \
@@ -57,6 +58,7 @@ COPY src/components/shared /app/src/components/shared
WORKDIR /app/src/components/microservices/code-queue
COPY src/components/microservices/code-queue/tsconfig.json ./tsconfig.json
COPY src/components/microservices/code-queue/src ./src
RUN mkdir -p /root/.agents/skills
EXPOSE 4222
ENTRYPOINT ["tini", "--"]
@@ -23,6 +23,7 @@ services:
CODE_QUEUE_SCHEDULER_ENABLED: "${CODE_QUEUE_SCHEDULER_ENABLED:-true}"
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_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"
@@ -81,6 +82,7 @@ services:
- ${CODE_QUEUE_WORKSPACE_PATH:-/home/ubuntu}:/workspace
- ${CODE_QUEUE_CODEX_CONFIG_PATH:-/home/ubuntu/.codex/config.toml}:/root/.codex/config.toml:ro
- ${CODE_QUEUE_CODEX_AUTH_PATH:-/home/ubuntu/.codex/auth.json}:/root/.codex/auth.json:ro
- ${CODE_QUEUE_SKILLS_PATH:-/home/ubuntu/.agents/skills}:/root/.agents/skills:ro
- ${CODE_QUEUE_SSH_DIR:-/home/ubuntu/.ssh}:/root/.ssh:ro
- ${CODE_QUEUE_LOG_DIR:-../../../../.state/code-queue/logs}:/var/log/unidesk
- ${CODE_QUEUE_STATE_DIR:-../../../../.state/code-queue}:/var/lib/unidesk/code-queue
@@ -384,6 +384,7 @@ function readConfig(): RuntimeConfig {
remoteDefaultWorkdir,
executionProviderIds,
remoteCodexEnvKeys: envList("CODE_QUEUE_REMOTE_CODEX_ENV_KEYS", ["OPENAI_API_KEY", "CRS_OAI_KEY", "OPENAI_BASE_URL", "OPENAI_API_BASE", "MINIMAX_API_KEY", "MINIMAX_API_BASE", "MINIMAX_MODEL"]),
skillsPath: envString("UNIDESK_SKILLS_PATH", "/root/.agents/skills"),
codexHome: envString("CODE_QUEUE_CODEX_HOME", "/var/lib/unidesk/code-queue/codex-home"),
opencodeXdgDir: envString("CODE_QUEUE_OPENCODE_XDG_DIR", resolve(dataDir, "opencode-xdg")),
sourceCodexConfig: envString("CODE_QUEUE_SOURCE_CODEX_CONFIG", "/root/.codex/config.toml"),
@@ -2413,6 +2414,74 @@ function runProbe(command: string, args: string[], timeout = 3_000): { ok: boole
return { ok: result.status === 0, output };
}
function decodeMountInfoPath(value: string): string {
return value.replace(/\\([0-7]{3})/gu, (_match, octal: string) => String.fromCharCode(Number.parseInt(octal, 8)));
}
function mountInfoForPath(path: string): { mountPoint: string | null; readonly: boolean | null } {
try {
const target = resolve(path);
let best: { mountPoint: string; readonly: boolean } | null = null;
for (const line of readFileSync("/proc/self/mountinfo", "utf8").split(/\r?\n/u)) {
if (line.trim().length === 0) continue;
const fields = line.split(" ");
const mountPoint = decodeMountInfoPath(fields[4] ?? "");
const options = (fields[5] ?? "").split(",");
if (mountPoint.length === 0) continue;
const matches = target === mountPoint || target.startsWith(mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`);
if (!matches) continue;
if (best === null || mountPoint.length > best.mountPoint.length) best = { mountPoint, readonly: options.includes("ro") };
}
return best ?? { mountPoint: null, readonly: null };
} catch {
return { mountPoint: null, readonly: null };
}
}
function collectSkillsStatus(): JsonValue {
const path = config.skillsPath;
const exists = existsSync(path);
const mountInfo = mountInfoForPath(path);
let directory = false;
let skillCount = 0;
let cliSpecAvailable = false;
let readonly = mountInfo.readonly === true;
let error: string | null = null;
if (exists) {
try {
const stat = statSync(path);
directory = stat.isDirectory();
if (directory) {
const entries = readdirSync(path, { withFileTypes: true });
skillCount = entries.filter((entry) => entry.isDirectory()).length;
cliSpecAvailable = existsSync(resolve(path, "cli-spec", "SKILL.md"));
if (mountInfo.readonly === null) {
const writeProbe = runProbe("sh", ["-lc", `test ! -w ${shellQuote(path)}`], 2_000);
readonly = writeProbe.ok;
}
}
} catch (probeError) {
error = probeError instanceof Error ? probeError.message : String(probeError);
}
}
const available = exists && directory;
return {
path,
mountPoint: mountInfo.mountPoint,
exists,
directory,
available,
readonly,
skillCount,
cliSpecAvailable,
expectedMount: "host ~/.agents/skills mounted read-only to UNIDESK_SKILLS_PATH",
repairHint: available && readonly && cliSpecAvailable
? null
: "DEV code-queue should mount /home/ubuntu/.agents/skills read-only at /root/.agents/skills and set UNIDESK_SKILLS_PATH=/root/.agents/skills.",
error,
} as unknown as JsonValue;
}
function collectDevReady(): JsonValue {
const now = Date.now();
if (devReadyCache !== null && now - devReadyCache.checkedAtMs < 30_000) return devReadyCache.value;
@@ -2457,7 +2526,9 @@ function collectDevReady(): JsonValue {
const sshKeyProbe = runProbe("sh", ["-lc", "test -d /root/.ssh && find /root/.ssh -maxdepth 1 -type f \\( -name 'id_*' ! -name '*.pub' \\) -perm -400 -print -quit"]);
const githubKnownHostProbe = runProbe("ssh-keygen", ["-F", "github.com", "-f", "/root/.ssh/known_hosts"]);
const sshSharedReady = existsSync("/root/.ssh") && sshKeyProbe.ok && sshKeyProbe.output.trim().length > 0;
const ok = missingTools.length === 0 && dockerProbe.ok && composeProbe.ok && workdirExists && dockerSocketExists && codexConfigReady && sshSharedReady;
const skills = collectSkillsStatus() as Record<string, JsonValue>;
const skillsReady = skills.available === true && skills.readonly === true && skills.cliSpecAvailable === true;
const ok = missingTools.length === 0 && dockerProbe.ok && composeProbe.ok && workdirExists && dockerSocketExists && codexConfigReady && sshSharedReady && skillsReady;
const value: JsonValue = {
ok,
missingTools,
@@ -2489,6 +2560,7 @@ function collectDevReady(): JsonValue {
githubKnownHostPresent: githubKnownHostProbe.ok,
ready: sshSharedReady,
},
skills,
};
devReadyCache = { checkedAtMs: now, value };
return value;
@@ -5174,6 +5246,7 @@ async function route(req: Request): Promise<Response> {
status: "starting",
databaseReady,
databaseLastError,
skills: collectSkillsStatus(),
startedAt: serviceStartedAt,
}, 503);
return jsonResponse({
@@ -5190,6 +5263,7 @@ async function route(req: Request): Promise<Response> {
schedulerPollIntervalMs: config.schedulerPollIntervalMs,
queue: queueSummary(false, state.tasks),
executionDiagnostics: executionDiagnosticsForTasks(state.tasks),
skills: collectSkillsStatus(),
egressProxy: await providerGatewayEgressProxyStatus(),
oaEventPublisher: oaEventPublisherStatus(),
startedAt: serviceStartedAt,
@@ -8,6 +8,7 @@ export const codeQueueEnvironmentHintTitle = "# Code Queue 运行环境提示";
export const codeQueueEnvironmentHint = [
codeQueueEnvironmentHintTitle,
"如果当前 Code Queue Docker 容器缺少完成任务所需的环境、系统包或语言依赖,可以先在容器内临时安装以推进当前任务;同时必须把该依赖补到 `src/components/microservices/code-queue/Dockerfile`,让后续任务重建镜像后可直接使用。",
"任务可通过 `UNIDESK_SKILLS_PATH`(默认 `/root/.agents/skills`)读取注入的宿主 skills;若需要确认注入状态,查询 Code Queue `/api/dev-ready` 的 `devReady.skills`,缺失时报告该字段的修复建议,不要读取或输出宿主 token/auth 配置。",
].join("\n");
export function stripAutoReferenceHint(prompt: string): string {
@@ -123,6 +123,7 @@ export interface RuntimeConfig {
remoteDefaultWorkdir: string;
executionProviderIds: string[];
remoteCodexEnvKeys: string[];
skillsPath: string;
codexHome: string;
opencodeXdgDir: string;
sourceCodexConfig: string;
@@ -329,6 +329,8 @@ spec:
value: danger-full-access
- name: CODE_QUEUE_APPROVAL_POLICY
value: never
- name: UNIDESK_SKILLS_PATH
value: /root/.agents/skills
- name: CODE_QUEUE_EGRESS_PROXY_ENABLED
value: "true"
- name: CODE_QUEUE_EGRESS_PROXY_URL
@@ -377,6 +379,9 @@ spec:
- name: codex-auth
mountPath: /root/.codex/auth.json
readOnly: true
- name: skills-dir
mountPath: /root/.agents/skills
readOnly: true
- name: ssh-dir
mountPath: /root/.ssh
readOnly: true
@@ -433,6 +438,10 @@ spec:
hostPath:
path: /home/ubuntu/.codex/auth.json
type: File
- name: skills-dir
hostPath:
path: /home/ubuntu/.agents/skills
type: Directory
- name: ssh-dir
hostPath:
path: /home/ubuntu/.ssh
@@ -531,6 +540,8 @@ spec:
value: "2"
- name: CODE_QUEUE_EGRESS_PROXY_ENABLED
value: "false"
- name: UNIDESK_SKILLS_PATH
value: /root/.agents/skills
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED
value: "false"
- name: CODE_QUEUE_CODEX_SQLITE_LOG_EXPORT_ENABLED
@@ -543,6 +554,9 @@ spec:
- name: repo
mountPath: /root/unidesk
readOnly: true
- name: skills-dir
mountPath: /root/.agents/skills
readOnly: true
- name: logs
mountPath: /var/log/unidesk-dev
- name: state
@@ -580,6 +594,10 @@ spec:
hostPath:
path: /home/ubuntu/unidesk-dev-code-queue-deploy/code-queue
type: Directory
- name: skills-dir
hostPath:
path: /home/ubuntu/.agents/skills
type: Directory
- name: logs
hostPath:
path: /home/ubuntu/unidesk-dev-code-queue-deploy/state/logs
@@ -674,6 +692,8 @@ spec:
value: "2"
- name: CODE_QUEUE_EGRESS_PROXY_ENABLED
value: "false"
- name: UNIDESK_SKILLS_PATH
value: /root/.agents/skills
- name: CODE_QUEUE_NOTIFY_CLAUDEQQ_ENABLED
value: "false"
- name: CODE_QUEUE_CODEX_SQLITE_LOG_EXPORT_ENABLED
@@ -686,6 +706,9 @@ spec:
- name: repo
mountPath: /root/unidesk
readOnly: true
- name: skills-dir
mountPath: /root/.agents/skills
readOnly: true
- name: logs
mountPath: /var/log/unidesk-dev
- name: state
@@ -723,6 +746,10 @@ spec:
hostPath:
path: /home/ubuntu/unidesk-dev-code-queue-deploy/code-queue
type: Directory
- name: skills-dir
hostPath:
path: /home/ubuntu/.agents/skills
type: Directory
- name: logs
hostPath:
path: /home/ubuntu/unidesk-dev-code-queue-deploy/state/logs