fix: expose codex submit execution mode mapping

This commit is contained in:
unidesk-code-queue-runner
2026-05-23 08:27:23 +00:00
parent ac04e61937
commit f878841857
12 changed files with 287 additions and 7 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ UniDesk 是一个以主 server 为统一入口的分布式工作平台;本文
- `bun scripts/cli.ts commander contract|plan --dry-run|smoke --dry-run|approval request --dry-run`:查看 host Codex 指挥官直管微服务 skeleton 的 source/contract、无 daemon smoke 验证计划、.state/commander/ 状态模型、trace summary 聚合和 ClaudeQQ 高风险请示草案;当前只返回 dry-run 计划,不接 live bridge、不接管人工指挥官,不发送消息,规则见 `docs/reference/host-codex-commander.md`
- `bun scripts/cli.ts ci install/status/run/publish-backend-core/publish-user-service/run-dev-e2e/logs`:在 D601 原生 k3s 上安装和运行 Tekton CI,支持每 commit 检查、Code Queue 只读性能门禁、`CI.json` catalog 驱动的 backend-core 与 user-service commit-pinned 镜像发布和手动触发的 `origin/master:deploy.json#environments.dev` 临时 namespace e2ecatalog/producer/consumer 分工见 `docs/reference/cicd-standardization.md``run-dev-e2e` 的 Git 控制 runner、短 launcher 和 no-CD 边界见 `docs/reference/dev-ci-runner.md`Tekton 规则见 `docs/reference/ci.md`
- `bun scripts/cli.ts codex deploy <commitId>`:旧 Code Queue 兼容部署入口已禁用,原因是它会绕过受控部署边界直连 D601 部署 Code Queue;规则见 `docs/reference/codex-deploy.md`
- `bun scripts/cli.ts codex prompt-lint [prompt|--prompt-file path|--prompt-stdin]` / `codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue <id>]` / `codex pr-preflight [--remote]``prompt-lint` 在派发/steer 前 dry-run 检查 runner prompt 的 DEV 测试授权分级(`read-only`/`live-read`/`live-mutating`)且不回显 prompt`submit --dry-run` 同时给出 MiniMax/GPT/人工路由建议该 lint 结果但不改写 payload,真实提交成功只返回写入确认、task id 和后续查看命令,不回显 prompt;`pr-preflight` 只读检查 D601 scheduler/runner 的 GitHub token、egress 和 PR 能力,PR 型派单前必须使用,规则见 `docs/reference/cli.md``docs/reference/code-queue-supervision.md`
- `bun scripts/cli.ts codex prompt-lint [prompt|--prompt-file path|--prompt-stdin]` / `codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue <id>]` / `codex pr-preflight [--remote]``prompt-lint` 在派发/steer 前 dry-run 检查 runner prompt 的 DEV 测试授权分级(`read-only`/`live-read`/`live-mutating`)且不回显 prompt`submit --dry-run` 同时给出 MiniMax/GPT/人工路由建议该 lint 结果和 requested/effective execution mode;真实提交成功只返回写入确认、task id、服务级 runnerPermissions 和后续查看命令,不回显 prompt;`pr-preflight` 只读检查 D601 scheduler/runner 的 GitHub token、egress 和 PR 能力,PR 型派单前必须使用,规则见 `docs/reference/cli.md``docs/reference/code-queue-supervision.md`
- `bun scripts/cli.ts codex task <taskId>`:按 Code Queue 任务 ID 查询默认审阅摘要,只返回原始 prompt、最终 response、最后错误和渐进披露命令;`--detail``codex output` 和 supervisor 大 `--limit` 仍默认有界,完整内容需显式 `--full`/`--full-text`/分页展开;`codex queues [--full] [--limit N] [--page N|--offset N]` 默认分页低噪声输出队列摘要,完整 upstream 只通过 raw command 显式获取。
- `bun scripts/cli.ts codex unread [--repo owner/name] [--issue N] [--limit N]`:只读汇总完成未读积压并给出 repo/issue/status/queue 计数和 drill-down/read 命令;批量已读必须显式 `codex unread mark-read ... --confirm`,规则见 `docs/reference/cli.md`
- `bun scripts/cli.ts codex judge <taskId> --attempt <n> [--dry-run]`:按指定 task/attempt 用与队列 worker 相同的上下文构建和 MiniMax judge 调用路径单步复现完成判定;`--dry-run` 只输出 prompt/payload 诊断。
+1 -1
View File
@@ -45,7 +45,7 @@ CLI 可以从 `master` 快速演进,但必须兼容 `deploy.json` 固定的 CI
- `ci install|status|run|publish-backend-core|publish-user-service|run-dev-e2e|logs` 管理 D601 原生 k3s 上的 Tekton CI。`run` 手动创建每 commit 检查和 Code Queue 只读性能门禁;`publish-backend-core``publish-user-service` 从 pushed Git commit 构建并发布 `127.0.0.1:5000/unidesk/<service>:<commit>` commit-pinned artifacts,输出 `artifactSummary`(含 `serviceId``sourceCommit``sourceRepo``dockerfile``imageRef``tag``digest``digestRef`),但不部署生产;`run-dev-e2e` 的 Git 控制 runner、短 launcher、host fetch 边界、临时 smoke namespace 和 no-CD 规则只在 `docs/reference/dev-ci-runner.md` 定义;Tekton CI 通用规则见 `docs/reference/ci.md`
- `schedule list|get|runs|run|retry-run|delete|upsert-pgdata-backup` 管理 backend-core 定时任务和运行历史。`schedule list``schedule get``schedule runs --limit N``schedule runs <scheduleId> --limit N` 是只读观察入口;`schedule run``schedule retry-run``schedule delete``schedule upsert-pgdata-backup` 会触发运行或写入配置,生产恢复时必须有明确授权。`schedule runs --limit N` 是全局历史视图,返回 `scope=global``scheduleId=null``schedule runs <scheduleId> --limit N` 是指定 schedule 历史视图,返回 `scope=schedule` 和对应 `scheduleId`。CLI 必须拒绝 `schedule runs 50` 这类纯数字位置参数,并提示使用 `schedule runs --limit 50`,避免把空数组误判成“没有历史 run”。`schedule run <id> --wait-ms N` 触发同一 schedule,并且即使 wait 超时也必须返回 `newRunId``observeCommand``schedule retry-run <failedRunId>` 只接受 failed run,从原 run 反查 `scheduleId` 后重触发同一 schedule,并输出 `originalRunId``scheduleId``newRunId``observeCommand`。当 backend-core 目标容器缺失或只观察到 verify-only 容器时,schedule/microservice 命令必须以非零退出并返回 `failureKind=target-stack-not-running``runnerDisposition=infra-blocked``readOnlyCommands``authorizationRequiredForRecovery`,不得把 Docker 的 `No such container` 当成成功的空历史。
- `codex deploy <commitId>` 是旧 Code Queue 兼容部署入口,已禁用以防止维护通道直连 D601 部署 Code Queue;当前 dev 自动化只做 `ci run-dev-e2e` smoke,不提供 Code Queue CD,详细规则见 `docs/reference/codex-deploy.md`
- `codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue queueId] [--provider-id id] [--cwd path] [--model model] [--reasoning-effort effort] [--execution-mode mode] [--max-attempts N] [--reference-task-id id] [--dry-run]` 通过 backend-core 私有代理向稳定 `code-queue` 用户服务路径提交任务;prompt 必须且只能来自位置参数、文件或 stdin 之一,`--dry-run` 只返回结构化请求且不实际入队。长 prompt、多行 prompt、含引号/反引号/Markdown 表格/JSON/反斜杠的 prompt 必须优先用 `--prompt-stdin``--prompt-file`,不要拼进 shell 单个参数;位置参数只适合短单行 smoke prompt。stdin 推荐用 quoted heredoc`cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue <id> --dry-run`,文件路径推荐 `bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue <id> --dry-run`,确认 dry-run 后移除 `--dry-run` 提交同一 payload。dry-run 会额外输出 `routingRecommendation`,包含推荐 route、runner、model、风险信号、prompt 自包含/issue 非唯一来源/prod-secret-DB 禁止/运行态或 release 禁止/证据要求/中等复杂度候选等 guard 状态;同时输出 `policyContract`,固定暴露 GPT-5.5、DeepSeek、MiniMax 的风险分层、并发上限和外部 provider 429 退避处置。该建议只用于指挥官 preflight,不会改写 payload,不改变 runtime admission,也不假设生产 MiniMax 或 DeepSeek 可用。`--dry-run` 必须返回完整 prompt、字符数和 `truncated=false` 用于人工验收;真实提交是写入操作,默认只返回 `accepted=true`、task id、队列、写入保护摘要和后续查看命令,必须标记 `promptOmitted=true` 且不得回显 prompt 或 promptPreview。真实提交会经过本机本地串行化保护和短节流,避免同一指挥端并发 submit 把低内存主机或 `code-queue-mgr` 控制面打抖;返回值会附带低噪声 `submitConcurrencyGuard` 说明本次提交的锁与等待信息。真实提交的 `queue` 摘要保持低噪声:`submittedTaskIds``queuedTaskIds``activeTaskIds``databaseActiveTaskIds` 是带 `items/count/returned/omitted/truncated/source` 的有界预览对象,`queuedTaskIds.items` 必须包含本次新入队的 queued/retry_wait 任务,`countContext``counts` 是权威计数;当预览被省略或截断时,`listPreviewPolicy` 必须写明 omitted counts 和 raw 查看命令。backend-core 默认把提交、队列 CRUD、已读状态、历史摘要和轻量 Trace 读取分流到主 server `code-queue-mgr`,由它写入主 PostgreSQLD601 scheduler 只轮询并执行已入库任务。
- `codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue queueId] [--provider-id id] [--cwd path] [--model model] [--reasoning-effort effort] [--execution-mode mode] [--max-attempts N] [--reference-task-id id] [--dry-run]` 通过 backend-core 私有代理向稳定 `code-queue` 用户服务路径提交任务;prompt 必须且只能来自位置参数、文件或 stdin 之一,`--dry-run` 只返回结构化请求且不实际入队。长 prompt、多行 prompt、含引号/反引号/Markdown 表格/JSON/反斜杠的 prompt 必须优先用 `--prompt-stdin``--prompt-file`,不要拼进 shell 单个参数;位置参数只适合短单行 smoke prompt。stdin 推荐用 quoted heredoc`cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue <id> --dry-run`,文件路径推荐 `bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue <id> --dry-run`,确认 dry-run 后移除 `--dry-run` 提交同一 payload。dry-run 会额外输出 `routingRecommendation`,包含推荐 route、runner、model、风险信号、prompt 自包含/issue 非唯一来源/prod-secret-DB 禁止/运行态或 release 禁止/证据要求/中等复杂度候选等 guard 状态;同时输出 `policyContract`,固定暴露 GPT-5.5、DeepSeek、MiniMax 的风险分层、并发上限和外部 provider 429 退避处置。该建议只用于指挥官 preflight,不会改写 payload,不改变 runtime admission,也不假设生产 MiniMax 或 DeepSeek 可用。`--dry-run` 必须返回完整 prompt、字符数和 `truncated=false` 用于人工验收;真实提交是写入操作,默认只返回 `accepted=true`、task id、队列、写入保护摘要和后续查看命令,必须标记 `promptOmitted=true` 且不得回显 prompt 或 promptPreview。真实提交会经过本机本地串行化保护和短节流,避免同一指挥端并发 submit 把低内存主机或 `code-queue-mgr` 控制面打抖;返回值会附带 `executionMode``runnerPermissions` 和低噪声 `submitConcurrencyGuard`,显式说明 requested/effective mode、服务级 runner sandbox/approvalPolicy、锁与等待信息。`--execution-mode` 是 Code Queue runtime placement,不是 Codex sandbox 权限;有效模式是 `default``windows-native``--execution-mode full-access` 等 sandbox-like 值会保留 requested 值并显示 effective `default`,同时提示当前不支持每任务 sandbox override。真实提交的 `queue` 摘要保持低噪声:`submittedTaskIds``queuedTaskIds``activeTaskIds``databaseActiveTaskIds` 是带 `items/count/returned/omitted/truncated/source` 的有界预览对象,`queuedTaskIds.items` 必须包含本次新入队的 queued/retry_wait 任务,`countContext``counts` 是权威计数;当预览被省略或截断时,`listPreviewPolicy` 必须写明 omitted counts 和 raw 查看命令。backend-core 默认把提交、队列 CRUD、已读状态、历史摘要和轻量 Trace 读取分流到主 server `code-queue-mgr`,由它写入主 PostgreSQLD601 scheduler 只轮询并执行已入库任务。
- `codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/<name>] [--pr-create-dry-run --pr-create-dry-run-head <head>] [--issue N] [--full|--raw]` 通过稳定 `code-queue` proxy 请求 D601 scheduler `/api/runtime-preflight`,用于 PR 型派单 admission。默认输出是紧凑 commander 视图,显式分出 `schedulerPreflight``activeRunnerPrCapability`,并附带 `commands``disclosure`,方便先看 scheduler auth 缺口、再看当前 runner/dev container 的 `gh auth status``gh pr create --dry-run` 能力;`--full``--raw` 才展开完整 `preflight`、工具、agent port、Git worktree、GitHub egress、repo/issue/PR 只读探测和观测原文。只报告 `GH_TOKEN`/`GITHUB_TOKEN` 是否存在和来源 key,不打印值。当 auth-broker 配置存在时,`tokenCoverage.source="auth-broker"``credentialSource="broker-issued-token"` 且 runner env token 不是成功前提;当仅 env token 存在时,`credentialSource="env-token"``authBroker.nextAction="use-env-token-until-auth-broker-live"`;两者都缺失时顶层 `ok=false``runnerDisposition=infra-blocked``degradedReason=auth-broker-needed``tokenCoverage.missing` 同时列出 `GH_TOKEN``GITHUB_TOKEN`,并输出 `authBroker.source="broker/auth-broker-needed"``capability.source="missing-token"`。该 `auth-missing` 的 scope 是 `scheduler-runner-env`,不能简化成“当前 active runner/dev container 不能创建 PR”;默认视图必须带 `scopeBoundary``activeRunnerPrCapability`。GitHub DNS/API 连接失败应归类为 `failureKind=github-transient``degradedReason=github-dns-api-transient`,并带 `retryable=true``commanderAction=retry-backoff-or-keep-running-if-heartbeat-fresh` 和有界 `githubTransient.failedProbes`;调用方应重试/退避,且在任务 heartbeat/trace 新鲜时继续监督,不把它当成 auth 缺失或 PR 语义失败。`prCapability` 是 runner-facing 合同摘要,必须包含目标分支、token/auth 来源、`systemGhBinaryRequiredForWrites=false`、UniDesk REST `bun scripts/cli.ts gh` 可用性、push dry-run/PR create dry-run 的 `writesRemote=false`、expected PR handoff、真实 PR 创建需要 commander 授权和 `gh pr merge``unsupported-command` 边界;系统 `gh` binary 缺失只进入 `tools.systemGhBinary`,不得误判为 UniDesk REST `gh` CLI 不可用。`--remote` 在 runner-like 环境里不再依赖本地 `unidesk-backend-core``unidesk-database``baidu-netdisk-backend` 容器存在;这些缺失只作为本地观测证据。若远程控制面可达,则继续走远程控制面结果;若远程控制面不可达,则结构化返回 `failureKind=control-plane-missing` / `degradedReason=remote-control-plane-unreachable`,而不是把本地 `backend-core-container-missing` 当作最终阻塞。`--pr-create-dry-run` 不 POST GitHub,只证明 runner 内 PR body 生成、`scripts/cli.ts gh pr create --dry-run` 和 branch 参数形态可用;服务端创建权限仍以 token/auth broker、repo/issue/PR read、push dry-run 和最终授权后的真实 PR 创建结果为准。
- `codex task <taskId>` 通过 Code Queue 私有代理按任务 ID 查询结构化审阅摘要;默认只返回任务身份、执行 Provider、工作目录、attempt 计数、原始 prompt、最终 response、最后错误和渐进披露命令,适合指挥官审阅完成未读任务且避免上下文爆炸。`--detail` 仍是有界详细摘要:默认只返回少量 attempt/tool 行、短 prompt/response/stderr/feedback 预览和 omitted/truncated 元数据;需要完整 prompt/response 文本或更多 tool/attempt 细节时再显式加 `--full``--tool-limit N``--trace``codex output`。该摘要读取默认由主 server `code-queue-mgr` 从 PostgreSQL 返回,不依赖 D601 `code-queue-read` Service 可用。
- `codex tasks [--view supervisor|full] [--queue id] [--status succeeded|running|queued|failed|canceled|judging|retry_wait[,..]] [--unread|--unread-only] [--limit N] [--before-id id]` 通过同一私有代理输出渐进式披露视图。默认 `supervisor` 是低噪声指挥官视图,只返回 `activeRunning``running``completedUnread``recentCompleted``queued``activity``commanderConcurrency``executionDiagnostics` 的紧凑行;`activeRunning.count` 是 running+judging 的状态计数,`exact=true` 时来自 queue summary counts`running.returned``activeRunning.rowPage.returned` 只是本次返回的紧凑行数。`commanderConcurrency.activeRunnerCount` 是并发策略应使用的 active/running 计数,等于 `activity.effectiveActiveTaskCount`15 并发策略按 `15 - activeRunnerCount` 计算剩余窗口。`commanderConcurrency.splitBrainDisposition=live-count-as-active` 表示 split-brain 有 fresh heartbeat 证据,应继续监督并计入 active;`interventionRequired=true` 才提示介入。prompt/body 只给短预览和原始字符数,`running`/`completedUnread`/`queued` 默认只返回一个有界小页并通过 section `commands.next` 继续分页,`recentCompleted` 默认限量且不重复 `completedUnread` 未读终态,不嵌入完整 Trace、final response 或全量 overview。`--limit` 在 supervisor 中主要是扫描/分页预算,不是返回几十条肥行的开关;CLI 安全上限是 100,输出会在 `filters.requestedLimit``filters.effectiveLimit``filters.limitCapped``disclosure.limitPolicy` 说明显式请求是否被 capped;底层 overview 拉取预算独立显示在 `source.requestedLimit` / `source.effectiveLimit`,所以 `--limit 260` 应显示 requested=260、effective=100、source requested/effective=200,而不是只露出一个含糊的 `limit``--unread``--unread-only` 的别名,必须只保留未读终态;`--status` 必须真实过滤支持的状态,未知参数或未知状态必须结构化失败。需要更详细当前页任务行时显式使用 `--view full``--full`,仍受 `--limit``--before-id` 分页约束。
+1 -1
View File
@@ -91,7 +91,7 @@ HWLAB M3 口径使用同一分级:只读报告、fixture、LOCAL/DRY-RUN 和 d
Code Queue 派单模型按成本、可信度和 blast radius 分层:GPT-5.5/Codex 处理高风险和复杂任务,DeepSeek/OpenCode 处理中等复杂度且边界清晰的任务,MiniMax/OpenCode 处理简单、低权限、可复核任务,生产重启、密钥、数据库手工写入和运行中任务控制保留给指挥官或人工。
当前提交合同由 `bun scripts/cli.ts codex submit` 暴露:prompt 必须来自位置参数、`--prompt-file``--prompt-stdin`;可选字段包括 `--queue/--queue-id``--provider-id/--provider``--cwd/--workdir``--model``--reasoning-effort``--execution-mode/--mode``--max-attempts``--reference-task-id/--reference/--ref`。长 prompt、多行 prompt、含引号/反引号/Markdown 表格/JSON/反斜杠的 prompt 应使用 `--prompt-stdin``--prompt-file`,例如 `cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue <id> --dry-run``bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue <id> --dry-run`;位置参数只适合短单行 smoke prompt。提交前先用 `--dry-run` 检查完整 payload,确认后移除 `--dry-run`。真实提交成功只返回低噪声写入确认、task id、队列和后续查看命令,必须标记 `promptOmitted=true` 且不得回显 prompt;需要复核正文时用返回的 `codex task <taskId>` 渐进展开。这些字段写入任务 payload 后由 `code-queue-mgr` 入 PostgreSQL,核心任务字段包括 `queue_id``provider_id``execution_mode``model``cwd``prompt/base_prompt``reference_task_ids``reasoning_effort``max_attempts``task_json`;队列记录至少有 `id/name/created_at/updated_at`。模型治理应优先看任务 payload 和数据库字段,不靠 worker final response 自报。
当前提交合同由 `bun scripts/cli.ts codex submit` 暴露:prompt 必须来自位置参数、`--prompt-file``--prompt-stdin`;可选字段包括 `--queue/--queue-id``--provider-id/--provider``--cwd/--workdir``--model``--reasoning-effort``--execution-mode/--mode``--max-attempts``--reference-task-id/--reference/--ref`。长 prompt、多行 prompt、含引号/反引号/Markdown 表格/JSON/反斜杠的 prompt 应使用 `--prompt-stdin``--prompt-file`,例如 `cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue <id> --dry-run``bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue <id> --dry-run`;位置参数只适合短单行 smoke prompt。提交前先用 `--dry-run` 检查完整 payload,确认后移除 `--dry-run``--execution-mode` 只表示 Code Queue runtime placement,有效值是 `default``windows-native`;像 `full-access` 这类 sandbox-like 值必须在 response 中显示 requested/effective mapping,并提示真实权限看服务级 `runnerPermissions.sandbox` / `approvalPolicy`,当前不支持每任务 sandbox override。真实提交成功只返回低噪声写入确认、task id、队列和后续查看命令,必须标记 `promptOmitted=true` 且不得回显 prompt;需要复核正文时用返回的 `codex task <taskId>` 渐进展开。这些字段写入任务 payload 后由 `code-queue-mgr` 入 PostgreSQL,核心任务字段包括 `queue_id``provider_id``execution_mode``model``cwd``prompt/base_prompt``reference_task_ids``reasoning_effort``max_attempts``task_json``task_json` 还保留 `requestedExecutionMode` 以便审计 requested/effective 差异;队列记录至少有 `id/name/created_at/updated_at`。模型治理应优先看任务 payload 和数据库字段,不靠 worker final response 自报。
真实 `codex submit` 确认输出的 `queue` 是低噪声监督摘要:`queuedTaskIds.items` 必须强制包含本次新建且仍为 queued/retry_wait 的任务 ID`activeTaskIds` 在主 server 控制面 `activeTaskIds=[]``counts.running/judging>0` 时必须回退到 PostgreSQL `databaseActiveTaskIds` 或执行诊断中的 active IDs;这些 ID 列表都只能作为带 `count/returned/omitted/truncated/source` 的有界预览,权威并发口径来自 `counts``countContext`。当预览没有展开所有 ID 时,`listPreviewPolicy` 必须明确说明 omitted counts 和 raw 查看命令,避免指挥侧误判 15-runner 目标。
运行态默认模型仍是 `gpt-5.5``CODE_QUEUE_MODELS` 当前长期合同至少包含 GPT-5.5、GPT-5.4、GPT-5.4 Mini、DeepSeek Chat 和 MiniMax M2.7`deepseek`/`deepseek-chat``minimax-m2.7` 会走 OpenCode port,其余模型走 Codex port。只有当执行面 `/health` 或等价配置已经显示 DeepSeek 模型可用、并完成轻量 runner smoke 后,才允许真实提交 `--model deepseek-chat`
@@ -0,0 +1,152 @@
import { spawnSync } from "node:child_process";
import {
normalizeCodeExecutionMode,
normalizeRequestedCodeExecutionMode,
requestedCodeExecutionModeIsRecognized,
} from "../src/components/microservices/code-queue/src/code-agent/common";
import { compactSubmitSuccessResponseForTest } from "./src/code-queue";
type JsonRecord = Record<string, unknown>;
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}
function runCli(args: string[]): { status: number | null; stdout: string; stderr: string; json: JsonRecord | null } {
const result = spawnSync("bun", ["scripts/cli.ts", ...args], {
cwd: process.cwd(),
encoding: "utf8",
});
const stdout = String(result.stdout || "");
let json: JsonRecord | null = null;
try {
json = JSON.parse(stdout) as JsonRecord;
} catch {
json = null;
}
return {
status: result.status,
stdout,
stderr: String(result.stderr || ""),
json,
};
}
function nestedRecord(value: unknown, path: string[]): JsonRecord {
let current: unknown = value;
for (const key of path) {
assertCondition(current !== null && typeof current === "object" && !Array.isArray(current), "expected object while traversing JSON", { path, key, current });
current = (current as JsonRecord)[key];
}
assertCondition(current !== null && typeof current === "object" && !Array.isArray(current), "expected nested object", { path, current });
return current as JsonRecord;
}
function asArray(value: unknown): unknown[] {
assertCondition(Array.isArray(value), "expected JSON array", { value });
return value as unknown[];
}
function assertSecretFree(output: string): void {
const forbidden = ["GH_TOKEN=", "GITHUB_TOKEN=", "OPENAI_API_KEY=", "CRS_OAI_KEY=", "DEEPSEEK_API_KEY=", "MINIMAX_API_KEY="];
for (const needle of forbidden) {
assertCondition(!output.includes(needle), "submit execution-mode contract must not print credential assignments", { needle });
}
}
export function runCodeQueueSubmitExecutionModeContract(): JsonRecord {
assertCondition(normalizeRequestedCodeExecutionMode("full-access") === "full-access", "shared parser should preserve short requested mode ids");
assertCondition(normalizeCodeExecutionMode("full-access") === "default", "shared execution-mode normalizer should keep full-access on effective default");
assertCondition(requestedCodeExecutionModeIsRecognized("full-access") === false, "shared recognition helper should reject full-access as a runtime mode");
assertCondition(requestedCodeExecutionModeIsRecognized("default") === true, "shared recognition helper should accept default mode");
const defaultMode = runCli(["codex", "submit", "execution mode default smoke", "--dry-run"]);
assertCondition(defaultMode.status === 0 && defaultMode.json?.ok === true, "default submit dry-run should succeed", defaultMode.json ?? { stdout: defaultMode.stdout, stderr: defaultMode.stderr });
assertSecretFree(defaultMode.stdout);
const defaultData = nestedRecord(defaultMode.json?.data, []);
const defaultRequest = nestedRecord(defaultData, ["request"]);
const defaultExecutionMode = nestedRecord(defaultData, ["executionMode"]);
const defaultPermissions = nestedRecord(defaultData, ["runnerPermissions"]);
assertCondition(defaultRequest.executionMode === undefined, "default payload should omit executionMode so service default is authoritative", defaultRequest);
assertCondition(defaultExecutionMode.requested === null, "default mode should show no explicit requested mode", defaultExecutionMode);
assertCondition(defaultExecutionMode.effective === "default", "default mode should expose effective default", defaultExecutionMode);
assertCondition(defaultExecutionMode.normalized === false, "default mode should not be reported as normalized", defaultExecutionMode);
assertCondition(defaultExecutionMode.recognized === true, "default mode should be recognized", defaultExecutionMode);
assertCondition(defaultPermissions.observed === false && defaultPermissions.perTaskOverrideSupported === false, "dry-run should mark runner permissions unobserved and non per-task", defaultPermissions);
const fullAccess = runCli(["codex", "submit", "execution mode full access smoke", "--execution-mode", "full-access", "--dry-run"]);
assertCondition(fullAccess.status === 0 && fullAccess.json?.ok === true, "full-access submit dry-run should succeed", fullAccess.json ?? { stdout: fullAccess.stdout, stderr: fullAccess.stderr });
assertSecretFree(fullAccess.stdout);
const fullData = nestedRecord(fullAccess.json?.data, []);
const fullRequest = nestedRecord(fullData, ["request"]);
const fullExecutionMode = nestedRecord(fullData, ["executionMode"]);
assertCondition(fullRequest.executionMode === "full-access", "payload should preserve the requested executionMode value for backend visibility", fullRequest);
assertCondition(fullExecutionMode.requested === "full-access", "full-access request should be visible", fullExecutionMode);
assertCondition(fullExecutionMode.effective === "default", "full-access should normalize to the effective default runtime mode", fullExecutionMode);
assertCondition(fullExecutionMode.recognized === false, "full-access should not be treated as a recognized Code Queue execution mode", fullExecutionMode);
assertCondition(fullExecutionMode.normalized === true, "full-access should explicitly show normalization", fullExecutionMode);
assertCondition(fullExecutionMode.requestedLooksLikeSandbox === true, "full-access should be classified as a sandbox-like request", fullExecutionMode);
assertCondition(String(fullExecutionMode.permissionBoundary || "").includes("runnerPermissions.sandbox"), "permission boundary should point at runnerPermissions.sandbox", fullExecutionMode);
assertCondition(String(fullExecutionMode.warning || "").includes("not applied"), "full-access warning should say it is not a per-task sandbox override", fullExecutionMode);
const promptText = "submitted full-access prompt body must stay omitted";
const submitted = compactSubmitSuccessResponseForTest({
tasks: [{
id: "codex_exec_mode_contract",
queueId: "commander-efficiency",
status: "queued",
providerId: "D601",
model: "gpt-5.5",
cwd: "/workspace",
prompt: promptText,
executionMode: "default",
requestedExecutionMode: "full-access",
maxAttempts: 99,
createdAt: "2026-05-23T00:00:00.000Z",
updatedAt: "2026-05-23T00:00:00.000Z",
}],
queue: {
total: 1,
queueCount: 1,
counts: { queued: 1 },
queuedTaskIds: ["codex_exec_mode_contract"],
runnerPermissions: {
observed: true,
scope: "code-queue-service-config",
sandbox: "danger-full-access",
approvalPolicy: "never",
perTaskOverrideSupported: false,
secretsPrinted: false,
},
},
}, { ok: true, status: 200 }, { mode: "local-atomic-directory-submit-serialization", acquiredAfterMs: 1, heldMs: 2, throttleMs: 2000 });
const submittedExecutionMode = nestedRecord(submitted, ["executionMode"]);
const submittedPermissions = nestedRecord(submitted, ["runnerPermissions"]);
const firstTask = nestedRecord(asArray(nestedRecord(submitted, ["submitted"]).tasks)[0], []);
const taskExecutionMode = nestedRecord(firstTask, ["executionModeRequest"]);
const queuePermissions = nestedRecord(submitted, ["queue", "runnerPermissions"]);
const submittedJson = JSON.stringify(submitted);
assertCondition(submittedExecutionMode.requested === "full-access" && submittedExecutionMode.effective === "default", "real submit summary should show requested/effective mode", submittedExecutionMode);
assertCondition(submittedPermissions.observed === true && submittedPermissions.sandbox === "danger-full-access" && submittedPermissions.approvalPolicy === "never", "real submit summary should expose observed service-level runner permissions", submittedPermissions);
assertCondition(submittedPermissions.perTaskOverrideSupported === false, "real submit summary should not imply per-task sandbox override", submittedPermissions);
assertCondition(firstTask.requestedExecutionMode === "full-access" && firstTask.executionMode === "default", "submitted task should carry requested and effective mode", firstTask);
assertCondition(taskExecutionMode.warning === submittedExecutionMode.warning, "task-level execution mode summary should match top-level warning", { taskExecutionMode, submittedExecutionMode });
assertCondition(queuePermissions.sandbox === "danger-full-access", "queue summary should keep runner permissions visible", queuePermissions);
assertCondition(!submittedJson.includes(promptText), "real submit summary must keep prompt text omitted", submitted);
assertCondition(!submittedJson.includes("promptPreview"), "real submit summary must not reintroduce promptPreview", submitted);
return {
ok: true,
checks: [
"default codex submit dry-run omits executionMode, reports effective default, and marks runner permissions unobserved",
"--execution-mode full-access preserves requested mode, reports effective default, and warns that sandbox permissions are service-level",
"real submit summary fixture exposes requested/effective mode plus observed runnerPermissions without prompt echo",
"shared execution-mode helpers preserve requested full-access while normalizing effective runtime to default",
"execution-mode dry-run output does not print credential assignments",
],
};
}
if (import.meta.main) {
process.stdout.write(`${JSON.stringify(runCodeQueueSubmitExecutionModeContract(), null, 2)}\n`);
}
+3
View File
@@ -34,6 +34,7 @@ const syntaxFiles = [
"scripts/code-queue-prompt-lint-contract-test.ts",
"scripts/code-queue-cli-steer-test.ts",
"scripts/code-queue-cli-submit-prompt-contract-test.ts",
"scripts/code-queue-submit-execution-mode-contract-test.ts",
"scripts/code-queue-submit-summary-contract-test.ts",
"scripts/code-queue-cli-read-terminal-contract-test.ts",
"scripts/code-queue-gh-auth-redaction-contract-test.ts",
@@ -362,6 +363,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(commandItem("code-queue:cli-steer-contract", ["bun", "scripts/code-queue-cli-steer-test.ts"], 30_000));
items.push(commandItem("code-queue:read-terminal-contract", ["bun", "scripts/code-queue-cli-read-terminal-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:submit-prompt-contract", ["bun", "scripts/code-queue-cli-submit-prompt-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:submit-execution-mode-contract", ["bun", "scripts/code-queue-submit-execution-mode-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:submit-summary-contract", ["bun", "scripts/code-queue-submit-summary-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:submit-routing-contract", ["bun", "scripts/code-queue-submit-routing-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:gh-auth-redaction-contract", ["bun", "scripts/code-queue-gh-auth-redaction-contract-test.ts"], 30_000));
@@ -396,6 +398,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(skippedItem("code-queue:cli-steer-contract", "Code Queue steer CLI contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:read-terminal-contract", "Code Queue terminal read contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:submit-prompt-contract", "Code Queue submit prompt contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:submit-execution-mode-contract", "Code Queue submit execution-mode contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:submit-summary-contract", "Code Queue submit summary contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:submit-routing-contract", "Code Queue submit routing contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:gh-auth-redaction-contract", "Code Queue GitHub auth output redaction contract is opt-in with script checks", "--scripts-typecheck or --full"));
+84 -2
View File
@@ -3,7 +3,16 @@ import { runCommand } from "./command";
import { type UniDeskConfig, repoRoot, rootPath } from "./config";
import { coreInternalFetch } from "./microservices";
import { previewJson } from "./preview";
import { codeAgentPortForModel, codeModelPorts as sharedCodeModelPorts, defaultCodeModels as sharedDefaultCodeModels, opencodeModels as sharedOpencodeModels } from "../../src/components/microservices/code-queue/src/code-agent/common";
import {
codeAgentPortForModel,
codeExecutionModes,
codeModelPorts as sharedCodeModelPorts,
defaultCodeModels as sharedDefaultCodeModels,
normalizeCodeExecutionMode,
normalizeRequestedCodeExecutionMode,
opencodeModels as sharedOpencodeModels,
requestedCodeExecutionModeIsRecognized,
} from "../../src/components/microservices/code-queue/src/code-agent/common";
const defaultToolLimit = 3;
const defaultTraceLimit = 80;
@@ -25,6 +34,7 @@ const diagnosticsIdPreviewLimit = 3;
const diagnosticsReasonPreviewLimit = 2;
const mutationQueueIdPreviewLimit = 15;
const steerPromptPreviewChars = 320;
const sandboxLikeExecutionModes = new Set(["full-access", "danger-full-access", "workspace-write", "read-only"]);
const detailAttemptReturnedLimit = 3;
const detailInitialPromptPreviewChars = 1200;
const detailBasePromptPreviewChars = 800;
@@ -3921,6 +3931,61 @@ function referenceTaskIdsFromOptions(args: string[]): string[] {
return ids;
}
function parseRequestedExecutionMode(value: string | undefined): string | undefined {
if (value === undefined) return undefined;
const requested = normalizeRequestedCodeExecutionMode(value);
if (requested === null) throw new Error("--execution-mode must be a short mode identifier");
return requested;
}
function executionModeSummary(requestedValue: string | null | undefined, effectiveValue?: unknown): Record<string, unknown> {
const requested = normalizeRequestedCodeExecutionMode(requestedValue);
const effective = typeof effectiveValue === "string" && effectiveValue.trim().length > 0
? normalizeCodeExecutionMode(effectiveValue)
: normalizeCodeExecutionMode(requested);
const requestedLooksLikeSandbox = requested !== null && sandboxLikeExecutionModes.has(requested);
const recognized = requestedCodeExecutionModeIsRecognized(requested);
return {
requested,
effective,
recognized,
normalized: requested !== null && requested !== effective,
availableModes: codeExecutionModes,
requestedLooksLikeSandbox,
permissionBoundary: "--execution-mode selects the Code Queue runtime mode; Codex sandbox permissions come from runnerPermissions.sandbox.",
...(requestedLooksLikeSandbox ? { warning: `${requested} is not a Code Queue execution mode and is not applied as a per-task sandbox override.` } : {}),
};
}
function dryRunRunnerPermissionsSummary(): Record<string, unknown> {
return {
observed: false,
scope: "code-queue-service-config",
sandbox: null,
approvalPolicy: null,
perTaskOverrideSupported: false,
note: "Dry-run does not contact Code Queue; real submit responses include observed runnerPermissions from the service.",
secretsPrinted: false,
};
}
function compactRunnerPermissions(value: unknown): Record<string, unknown> | null {
const record = asRecord(value);
if (record === null) return null;
return {
observed: record.observed ?? true,
scope: record.scope ?? "code-queue-service-config",
sandbox: record.sandbox ?? null,
approvalPolicy: record.approvalPolicy ?? null,
perTaskOverrideSupported: record.perTaskOverrideSupported ?? false,
secretsPrinted: false,
};
}
function compactTaskExecutionModeRequest(record: Record<string, unknown>): Record<string, unknown> {
return executionModeSummary(asString(record.requestedExecutionMode) || null, record.executionMode);
}
function parseSubmitOptions(args: string[]): CodexSubmitOptions {
assertKnownOptions(args, {
flags: ["--prompt-stdin", "--stdin", "--dry-run"],
@@ -3953,7 +4018,7 @@ function parseSubmitOptions(args: string[]): CodexSubmitOptions {
cwd: optionValue(args, ["--cwd", "--workdir"]),
model: optionValue(args, ["--model"]),
reasoningEffort: optionValue(args, ["--reasoning-effort"]),
executionMode: optionValue(args, ["--execution-mode", "--mode"]),
executionMode: parseRequestedExecutionMode(optionValue(args, ["--execution-mode", "--mode"])),
maxAttempts,
referenceTaskIds: referenceTaskIdsFromOptions(args),
dryRun: hasFlag(args, "--dry-run"),
@@ -4014,6 +4079,8 @@ function compactTaskMutationResponse(task: unknown, options: CompactTaskMutation
reasoningEffort: record.reasoningEffort ?? null,
cwd: record.cwd ?? null,
executionMode: record.executionMode ?? null,
requestedExecutionMode: record.requestedExecutionMode ?? null,
executionModeRequest: compactTaskExecutionModeRequest(record),
maxAttempts: record.maxAttempts ?? null,
currentAttempt: record.currentAttempt ?? null,
cancelRequested: record.cancelRequested ?? null,
@@ -4048,6 +4115,8 @@ function compactSubmitTaskConfirmation(task: unknown): Record<string, unknown> {
reasoningEffort: record.reasoningEffort ?? null,
cwd: record.cwd ?? null,
executionMode: record.executionMode ?? null,
requestedExecutionMode: record.requestedExecutionMode ?? null,
executionModeRequest: compactTaskExecutionModeRequest(record),
maxAttempts: record.maxAttempts ?? null,
createdAt: record.createdAt ?? null,
updatedAt: record.updatedAt ?? null,
@@ -4220,6 +4289,7 @@ function compactSubmitQueueConfirmation(value: unknown, options: CompactSubmitQu
databaseActiveTaskIds: databaseActivePreview,
queuedTaskIds: queuedPreview,
executionDiagnostics: compactQueueExecutionDiagnostics(record.executionDiagnostics),
runnerPermissions: compactRunnerPermissions(record.runnerPermissions),
...(submittedTasks.length === 0 ? {} : { submittedTaskIds: submittedPreview }),
countContext: {
queued: countForStatus(counts, "queued"),
@@ -4265,9 +4335,14 @@ function compactSubmitSuccessResponse(body: Record<string, unknown>, upstream: R
const queueIds = Array.from(new Set(tasks.map((task) => asString(task.queueId)).filter(Boolean))).sort();
const firstTaskId = taskIds[0] ?? null;
const firstQueueId = queueIds[0] ?? null;
const firstSubmittedTask = submittedTasks[0] ?? {};
const queueRecord = asRecord(body.queue);
const runnerPermissions = compactRunnerPermissions(queueRecord?.runnerPermissions);
return {
ok: true,
upstream,
executionMode: executionModeSummary(asString(firstSubmittedTask.requestedExecutionMode) || null, firstSubmittedTask.executionMode),
runnerPermissions,
submitted: {
accepted: true,
taskCount: allTasks.length,
@@ -4393,6 +4468,11 @@ function compactTerminalReadTask(summary: unknown, readTask: unknown, taskId: st
readAt,
providerId: summaryRecord.providerId ?? readRecord.providerId ?? null,
executionMode: summaryRecord.executionMode ?? readRecord.executionMode ?? null,
requestedExecutionMode: summaryRecord.requestedExecutionMode ?? readRecord.requestedExecutionMode ?? null,
executionModeRequest: compactTaskExecutionModeRequest({
requestedExecutionMode: summaryRecord.requestedExecutionMode ?? readRecord.requestedExecutionMode ?? null,
executionMode: summaryRecord.executionMode ?? readRecord.executionMode ?? null,
}),
executionModeInfo: summaryRecord.executionModeInfo ?? readRecord.executionModeInfo ?? null,
model: summaryRecord.model ?? readRecord.model ?? null,
agentPort: summaryRecord.agentPort ?? readRecord.agentPort ?? null,
@@ -5848,6 +5928,8 @@ function codexSubmitTask(args: string[]): unknown {
ok: true,
dryRun: true,
promptLint,
executionMode: executionModeSummary(options.executionMode),
runnerPermissions: dryRunRunnerPermissionsSummary(),
routingRecommendation: submitRoutingRecommendation(options),
modelRegistry: submitModelRegistry(),
request: {
+5
View File
@@ -276,6 +276,11 @@ function codexHelp(): unknown {
positional: "Keep positional prompt usage to short one-line smoke prompts only.",
sourceRule: "Exactly one prompt source is accepted: positional prompt, --prompt-file, or --prompt-stdin.",
},
executionMode: {
validModes: ["default", "windows-native"],
boundary: "--execution-mode selects Code Queue runtime placement, not Codex sandbox permissions.",
permissionVisibility: "Submit dry-runs show requested/effective mode; real submit responses include service runnerPermissions.sandbox and approvalPolicy.",
},
readOutput: {
default: "codex read marks a terminal task read and returns terminal metadata, final response, last error/judge, counts, and drill-down commands.",
disclosure: "Full prompt, tool logs, and feedback prompts are not printed by codex read; use codex task/detail/trace/output for progressive disclosure.",
@@ -38,6 +38,7 @@ export const defaultCodeModels = ["gpt-5.5", "gpt-5.4-mini", "gpt-5.4", deepseek
export const opencodeNpmPackage = "opencode-ai@1.14.48";
export const defaultCodeExecutionMode: CodeExecutionMode = "default";
export const codeExecutionModes: CodeExecutionMode[] = ["default", "windows-native"];
const codeExecutionModeAliases = new Set(["default", "windows-native", "windows", "win32", "native-windows"]);
export interface CodeModelProviderSourceConfig {
codeModels: string[];
@@ -104,6 +105,18 @@ export function normalizeCodeExecutionMode(value: unknown): CodeExecutionMode {
return defaultCodeExecutionMode;
}
export function normalizeRequestedCodeExecutionMode(value: unknown): string | null {
const raw = typeof value === "string" ? value.trim().toLowerCase() : "";
if (raw.length === 0) return null;
if (!/^[a-z0-9][a-z0-9-]{0,63}$/u.test(raw)) return null;
return raw;
}
export function requestedCodeExecutionModeIsRecognized(value: unknown): boolean {
const requested = normalizeRequestedCodeExecutionMode(value);
return requested === null || codeExecutionModeAliases.has(requested);
}
export function codeExecutionModeInfo(mode: CodeExecutionMode): Record<string, JsonValue> {
if (mode === "windows-native") {
return {
@@ -37,6 +37,7 @@ import {
codeModelProviderSourceContract,
normalizeCodeExecutionMode,
normalizeCodeModel,
normalizeRequestedCodeExecutionMode,
terminalStatus,
} from "./code-agent/common";
import type { ActiveRun, ActiveRunSlotWaiter } from "./code-agent/common";
@@ -1024,6 +1025,7 @@ function normalizeTask(task: QueueTask): QueueTask {
task.providerId = normalizeTaskProviderId(task.providerId);
task.model ||= config.defaultModel;
task.executionMode = normalizeCodeExecutionMode(task.executionMode);
task.requestedExecutionMode = normalizeRequestedCodeExecutionMode(task.requestedExecutionMode);
task.cwd = resolveTaskCwd(task.providerId, task.cwd);
task.reasoningEffort = resolveReasoningEffort(task.model, task.reasoningEffort);
task.maxAttempts = clampTaskAttempts(task.maxAttempts || config.defaultMaxAttempts);
@@ -2573,7 +2575,12 @@ function normalizeRequest(value: unknown): QueueTaskRequest {
if (typeof record.cwd === "string" && record.cwd.length > 0) request.cwd = record.cwd;
if (typeof record.model === "string" && record.model.length > 0) request.model = record.model;
if (typeof record.reasoningEffort === "string" && record.reasoningEffort.length > 0) request.reasoningEffort = record.reasoningEffort;
if (typeof record.executionMode === "string" && record.executionMode.length > 0) request.executionMode = normalizeCodeExecutionMode(record.executionMode);
if (typeof record.executionMode === "string" && record.executionMode.length > 0) {
const requestedExecutionMode = normalizeRequestedCodeExecutionMode(record.executionMode);
if (requestedExecutionMode === null) throw new Error("executionMode must be a short mode identifier");
request.requestedExecutionMode = requestedExecutionMode;
request.executionMode = normalizeCodeExecutionMode(requestedExecutionMode);
}
if (typeof record.maxAttempts === "number" && Number.isInteger(record.maxAttempts) && record.maxAttempts > 0) request.maxAttempts = clampTaskAttempts(record.maxAttempts);
const referenceTaskIds = collectReferenceTaskIds(record, record.prompt);
if (referenceTaskIds.length > 0) request.referenceTaskIds = referenceTaskIds;
@@ -2594,6 +2601,7 @@ function createTask(request: QueueTaskRequest): QueueTask {
const providerId = normalizeTaskProviderId(request.providerId);
const model = normalizeCodeModel(request.model ?? config.defaultModel);
const executionMode = normalizeCodeExecutionMode(request.executionMode);
const requestedExecutionMode = normalizeRequestedCodeExecutionMode(request.requestedExecutionMode);
const cwd = resolveTaskCwd(providerId, request.cwd);
validateExecutionModeForTask(providerId, cwd, model, executionMode);
rememberWorkdir(providerId, executionMode, cwd, at);
@@ -2612,6 +2620,7 @@ function createTask(request: QueueTaskRequest): QueueTask {
model,
reasoningEffort: resolveReasoningEffort(model, request.reasoningEffort),
executionMode,
requestedExecutionMode,
maxAttempts: request.maxAttempts ?? config.defaultMaxAttempts,
status: "queued",
createdAt: at,
@@ -14,7 +14,7 @@ import type { ActiveRun, ActiveRunSlotWaiter } from "./code-agent/common";
import type { JsonValue, QueueRecord, QueuedStatusReason, QueueTask, RuntimeConfig, TaskStatus, TranscriptLine } from "./types";
export interface QueueApiContext {
config: Pick<RuntimeConfig, "codeModels" | "codexModels" | "codexSqliteLogExportBatchSize" | "codexSqliteLogExportEnabled" | "codexSqliteLogExportIntervalMs" | "codexSqliteLogMaxBytes" | "deepseekApiBase" | "deepseekApiKey" | "deepseekModel" | "defaultModel" | "defaultReasoningEffort" | "defaultWorkdir" | "judgeMaxTokens" | "judgeRepairAttempts" | "mainProviderId" | "maxActiveQueues" | "maxInMemoryEventRecords" | "maxInMemoryOutputRecords" | "minimaxApiBase" | "minimaxApiKey" | "minimaxModel" | "modelReasoningEfforts" | "notifyClaudeQqBaseUrl" | "notifyClaudeQqEnabled" | "notifyClaudeQqMaxResponseChars" | "notifyClaudeQqRetryIntervalMs" | "notifyClaudeQqSendAttempts" | "notifyClaudeQqTargetType" | "notifyClaudeQqTimeoutMs" | "outputArchiveDir" | "remoteDefaultWorkdir" | "schedulerEnabled" | "schedulerPollIntervalMs" | "windowsNativeCodexDefaultWorkdir">;
config: Pick<RuntimeConfig, "approvalPolicy" | "codeModels" | "codexModels" | "codexSqliteLogExportBatchSize" | "codexSqliteLogExportEnabled" | "codexSqliteLogExportIntervalMs" | "codexSqliteLogMaxBytes" | "deepseekApiBase" | "deepseekApiKey" | "deepseekModel" | "defaultModel" | "defaultReasoningEffort" | "defaultWorkdir" | "judgeMaxTokens" | "judgeRepairAttempts" | "mainProviderId" | "maxActiveQueues" | "maxInMemoryEventRecords" | "maxInMemoryOutputRecords" | "minimaxApiBase" | "minimaxApiKey" | "minimaxModel" | "modelReasoningEfforts" | "notifyClaudeQqBaseUrl" | "notifyClaudeQqEnabled" | "notifyClaudeQqMaxResponseChars" | "notifyClaudeQqRetryIntervalMs" | "notifyClaudeQqSendAttempts" | "notifyClaudeQqTargetType" | "notifyClaudeQqTimeoutMs" | "outputArchiveDir" | "remoteDefaultWorkdir" | "sandbox" | "schedulerEnabled" | "schedulerPollIntervalMs" | "windowsNativeCodexDefaultWorkdir">;
activeRunSlotQueueIds: () => string[];
activeRunSlotWaiterSummaries: () => JsonValue[];
activeRuns: Map<string, ActiveRun>;
@@ -268,6 +268,7 @@ function taskForListResponse(task: QueueTask, lite = false, queueTasks?: QueueTa
},
providerId: task.providerId,
executionMode: task.executionMode,
requestedExecutionMode: task.requestedExecutionMode ?? null,
executionModeInfo: codeExecutionModeInfo(task.executionMode),
cwd: task.cwd,
model: task.model,
@@ -325,6 +326,7 @@ function taskForListResponse(task: QueueTask, lite = false, queueTasks?: QueueTa
referenceInjection: task.referenceInjection,
providerId: task.providerId,
executionMode: task.executionMode,
requestedExecutionMode: task.requestedExecutionMode ?? null,
executionModeInfo: codeExecutionModeInfo(task.executionMode),
cwd: task.cwd,
model: task.model,
@@ -483,6 +485,14 @@ function queueSummary(includeDevReady = true, tasks: QueueTask[] = ctx().tasks()
modelProviderConfig: codeModelProviderSourceContract(ctx().config) as unknown as JsonValue,
executionModes: executionModeOptions(),
executionModeInfo: Object.fromEntries(codeExecutionModes.map((mode) => [mode, codeExecutionModeInfo(mode)])) as unknown as JsonValue,
runnerPermissions: {
observed: true,
scope: "code-queue-service-config",
sandbox: ctx().config.sandbox,
approvalPolicy: ctx().config.approvalPolicy,
perTaskOverrideSupported: false,
secretsPrinted: false,
} as unknown as JsonValue,
agentPorts: {
codex: codeAgentPortInfo("codex"),
opencode: codeAgentPortInfo("opencode"),
@@ -1447,6 +1447,7 @@ function taskForMetaResponse(task: QueueTask): JsonValue {
referenceInjection: task.referenceInjection,
providerId: task.providerId,
executionMode: task.executionMode,
requestedExecutionMode: task.requestedExecutionMode ?? null,
executionModeInfo: codeExecutionModeInfo(task.executionMode),
cwd: task.cwd,
model: task.model,
@@ -1518,6 +1519,7 @@ function taskForCompactMetaResponse(task: QueueTask): JsonValue {
},
providerId: task.providerId,
executionMode: task.executionMode,
requestedExecutionMode: task.requestedExecutionMode ?? null,
executionModeInfo: codeExecutionModeInfo(task.executionMode),
cwd: task.cwd,
model: task.model,
@@ -2391,6 +2393,7 @@ function taskTraceSummaryResponse(task: QueueTask, oaTraceStats: JsonValue | nul
status: task.status,
providerId: task.providerId,
executionMode: task.executionMode,
requestedExecutionMode: task.requestedExecutionMode ?? null,
executionModeInfo: codeExecutionModeInfo(task.executionMode),
model: task.model,
agentPort: codeAgentPortForModel(task.model),
@@ -2552,6 +2555,7 @@ function taskSummaryResponse(task: QueueTask, url: URL): JsonValue {
status: task.status,
providerId: task.providerId,
executionMode: task.executionMode,
requestedExecutionMode: task.requestedExecutionMode ?? null,
executionModeInfo: codeExecutionModeInfo(task.executionMode),
model: task.model,
agentPort: codeAgentPortForModel(task.model),
@@ -191,6 +191,7 @@ export interface QueueTaskRequest {
model?: string;
reasoningEffort?: string;
executionMode?: CodeExecutionMode;
requestedExecutionMode?: string | null;
maxAttempts?: number;
referenceTaskIds?: string[];
basePrompt?: string;
@@ -394,6 +395,7 @@ export interface QueueTask {
model: string;
reasoningEffort: string | null;
executionMode: CodeExecutionMode;
requestedExecutionMode?: string | null;
maxAttempts: number;
status: TaskStatus;
createdAt: string;