From 987f41d258a78951a08f6e8bc98e479490a89d22 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 9 Jun 2026 01:14:23 +0000 Subject: [PATCH] feat: freeze legacy code queue entry --- docs/reference/agentrun.md | 4 +- docs/reference/cli.md | 21 ++- docs/reference/code-queue-supervision.md | 38 ++--- scripts/src/agentrun.ts | 156 +++++++++++++++++- scripts/src/code-queue.ts | 51 ++++-- scripts/src/help.ts | 52 +++--- src/components/frontend/public/style.css | 43 +++++ src/components/frontend/src/code-queue.tsx | 74 +++++---- .../microservices/code-queue/src/index.ts | 53 ++++++ 9 files changed, 389 insertions(+), 103 deletions(-) diff --git a/docs/reference/agentrun.md b/docs/reference/agentrun.md index 168ee5b0..622a1e71 100644 --- a/docs/reference/agentrun.md +++ b/docs/reference/agentrun.md @@ -107,7 +107,9 @@ UniDesk 不能作为以下内容的事实来源: AgentRun `v0.1` 的指挥官任务面已经按 AgentRun issue #105 完成真实运行面验收,可作为新任务派发、Queue commander、trace/output、steer、turn/reuse、read 和 cancel 的 AgentRun 侧标准路径。长期使用时仍以 AgentRun 仓库自身 SPEC 为能力事实来源;UniDesk 只记录该路径已经通过 G14 `agentrun-v01` 运行面和 `hy` profile + `gpt-5.5` 验证。 -这不等于 UniDesk 产品面已经完成旧 Code Queue 切换。旧 UniDesk Code Queue 的新任务入口冻结、前端接入、历史归档和只读排障边界必须在 UniDesk 仓库内单独验收;当前跟踪 issue 是 [pikasTech/unidesk#219](https://github.com/pikasTech/unidesk/issues/219)。关闭任何“全平台已切换”类事项前,必须同时具备 AgentRun 侧能力证据和 UniDesk 侧原入口证据,禁止用 AgentRun canary 或直接 manager CLI 代替 UniDesk 产品入口切换验收。 +UniDesk 指挥官新任务入口固定使用 `bun scripts/cli.ts agentrun v01 queue|sessions`。该入口是 G14 `/root/agentrun-v01` 中官方 `./scripts/agentrun --manager-url auto` CLI 的直接 bridge;本地 `--json-file`、`--prompt-file`、`--runner-json-file` 和 `--prompt-stdin` 只会被 materialize 到 G14 临时文件后传给官方 CLI,不在 UniDesk 内实现 AgentRun queue 协议,也不把任务 double-write 回旧 Code Queue。 + +旧 UniDesk Code Queue 只保留历史归档、只读排障和残留旧任务停止入口。`codex submit/enqueue`、`codex steer`、`codex resume`、`codex queue create/merge`、`codex move`、旧 Web 提交表单、旧队列管理和旧 workdir 管理都必须返回冻结状态或禁用;`codex task/tasks/output/read/unread/queues` 可继续读取历史,`codex interrupt|cancel` 只用于停止残留旧任务。旧 Code Queue history 不迁移到 AgentRun,也不提供 adapter、legacy mode、fallback 或双写路径。 ## AgentRun / HWLAB 协同职责边界 diff --git a/docs/reference/cli.md b/docs/reference/cli.md index ab4fe109..2260570b 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -50,7 +50,7 @@ CI/CD、GitOps、rollout、artifact 发布、PR 合并后的 runtime lane 滚动 - `dev-env validate [--manifest path] [--kubectl-dry-run]` 离线校验 D601 `unidesk-dev` namespace、dev PostgreSQL 底座和 dev workload manifest。默认检查 `src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-foundation.k8s.yaml`;也可显式校验 `src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-core.k8s.yaml` 或 `src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-code-queue.k8s.yaml`。所有 namespaced 对象必须只落到 `unidesk-dev`,foundation manifest 必须包含 `postgres-dev` StatefulSet/Service、dev secret/config、迁移 Job 和 DB URL guard,core manifest 必须包含 `backend-core-dev`/`frontend-dev` Deployment/Service,Code Queue dev manifest 必须包含 `code-queue-scheduler-dev`、`code-queue-read-dev`、`code-queue-write-dev`、dev provider egress proxy,以及只读挂载宿主 `/home/ubuntu/.agents/skills` 到容器 `/root/.agents/skills` 的 `skills-dir` volume。加 `--kubectl-dry-run` 时额外以 `KUBECONFIG=/etc/rancher/k3s/k3s.yaml` 执行 `kubectl apply --dry-run=client --validate=false -f `,仍不 apply 资源;默认 `docker-desktop` kubeconfig 不得作为 D601 dry-run 目标。 - `dev-env prewarm-images [--image image] [--provider-id D601] [--no-pull] [--proxy-url URL] [--pull-timeout-ms N] [--dry-run]` 创建异步 job,通过 UniDesk SSH 维护桥在 D601 上把开发底座依赖镜像从 Docker 缓存导入原生 k3s containerd。默认镜像是 `postgres:16-alpine` 和 `rancher/mirrored-library-busybox:1.36.1`,用于避免 `postgres-dev` 与 local-path helper pod 卡在外部 registry 拉取。该命令固定验证 `/etc/rancher/k3s/k3s.yaml` 指向的 native k3s 上下文,并输出 `dev_env_containerd_image_ready=...` 作为成功判据;它不 apply manifest、不修改生产 `unidesk` namespace。 - `artifact-registry plan|render|status|health|install|deploy-backend-core|deploy-service` 管理 D601 host-managed CNCF Distribution registry 的声明、安装、只读检查和 pull-only artifact CD。该 registry 固定为 D601 loopback `127.0.0.1:5000`,由 systemd + Docker Compose 管理,位于 native k3s 故障域外;`deploy-service` 只拉取 CI 已发布的 commit-pinned 镜像、retag/recreate 或导入 native k3s,并做 live commit 验证,不构建 runtime source。`deploy-backend-core` 是 deprecated 兼容名,标准 backend-core prod CD 入口是 `deploy apply --env prod --service backend-core`。长期规则见 `docs/reference/artifact-registry.md`。 -- `commander contract|plan --dry-run|smoke --dry-run|approval request --dry-run|prompt-lint --kind gpt55-pr` 是 host Codex 指挥官直管微服务 skeleton 入口。当前命令返回 `phase=source-contract`、service/API/state/bridge/prompt/trace/#20/#46/ClaudeQQ 审批边界、.state/commander/ 状态模型、dev 无 daemon smoke contract、dry-run 计划和 GPT-5.5 PR prompt 边界辅助 lint,不接 live bridge、不注入 prompt、不发送 ClaudeQQ。`approval request --dry-run` 会生成 200 字以内中文纯文本 ClaudeQQ 审批草案、`notification-path-unavailable` blocker 和授权后唯一可用的 `bun scripts/cli.ts microservice proxy claudeqq /api/push/text --method POST --body-json '' --raw` 命令;不得提示使用本机 ClaudeQQ skill、powershell 或本地 server。`prompt-lint` 支持 `--prompt-file` 与 `--stdin`,输出 `ok`、`missingClauses`、`riskLevel`、`suggestedPatchSnippet` 且不回显完整 prompt;它是 commander 辅助检查,不是业务 PR 门禁,也不改变 `codex submit` 默认行为。`plan`、`smoke` 与 `approval request` 必须带 `--dry-run`;缺少时返回 `error=dry-run-required`。长期规则见 `docs/reference/host-codex-commander.md`。 +- `commander contract|plan --dry-run|smoke --dry-run|approval request --dry-run|prompt-lint --kind gpt55-pr` 是 host Codex 指挥官直管微服务 skeleton 入口。当前命令返回 `phase=source-contract`、service/API/state/bridge/prompt/trace/#20/#46/ClaudeQQ 审批边界、.state/commander/ 状态模型、dev 无 daemon smoke contract、dry-run 计划和 GPT-5.5 PR prompt 边界辅助 lint,不接 live bridge、不注入 prompt、不发送 ClaudeQQ。`approval request --dry-run` 会生成 200 字以内中文纯文本 ClaudeQQ 审批草案、`notification-path-unavailable` blocker 和授权后唯一可用的 `bun scripts/cli.ts microservice proxy claudeqq /api/push/text --method POST --body-json '' --raw` 命令;不得提示使用本机 ClaudeQQ skill、powershell 或本地 server。`prompt-lint` 支持 `--prompt-file` 与 `--stdin`,输出 `ok`、`missingClauses`、`riskLevel`、`suggestedPatchSnippet` 且不回显完整 prompt;它是 commander 辅助检查,不是业务 PR 门禁;legacy `codex submit` 已冻结,新任务 payload 审查走 AgentRun Queue。`plan`、`smoke` 与 `approval request` 必须带 `--dry-run`;缺少时返回 `error=dry-run-required`。长期规则见 `docs/reference/host-codex-commander.md`。 - `hwlab g14 retirement status|plan|execute --confirm [--wait]` 是 legacy G14 DEV/PROD 的受控退役入口。`status` 只读报告 `argocd/hwlab-g14-dev`、`argocd/hwlab-g14-prod`、`hwlab-dev`、`hwlab-prod`、bounded legacy resource preview、受保护的 `hwlab-g14-v02`/`hwlab-node-v03` 和 `hwlab-v02`/`hwlab-v03`,以及 `.state/hwlab-g14/legacy-g14-retirement.json` marker。`plan` 是 dry-run,只列出 destructive targets 和 protected targets。`execute --confirm` 删除 legacy Argo Applications 与 legacy namespaces,取消本地 active `hwlab_g14_pr_monitor` job,并写 retirement marker 记录执行证据;`hwlab g14 monitor-prs --lane g14` 按退役合同固定结构化失败并指向 `retirement status` 和 `--lane v02`。该入口禁止触碰 v0.2/v0.3 Application、namespace、PipelineRun、Secret、Git mirror 或 FRP desired state。 - `hwlab g14 monitor-prs --lane v02` 是 HWLAB `v0.2` 的 PR -> CI -> CD 自动化入口。它只监控 base=`v0.2` 的 open PR:每轮先用 UniDesk `gh pr preflight` 读取 GitHub CI/checks、mergeability 和冲突状态;pending 时在 PR 下写等待评论,blocked/conflict 时写阻塞评论;ready 时直接用 UniDesk `gh pr merge` 合并,不因为其他 commit 的运行中 PipelineRun 阻塞 merge 或 CI 启动。合并后执行受控 `control-plane trigger-current --lane v02 --confirm --wait`、轮询定点 `control-plane status --lane v02 --source-commit `,必要时执行 `git-mirror flush --confirm --wait`。v0.2 CD 采用 latest-only:旧 PipelineRun 不取消、不等待,但 promotion 写 `v0.2-gitops` 前必须重新确认 source head,stale commit 只能以 superseded/no-op 收口,不能回滚 runtime。不管 CD 成功、superseded、失败或超时,都在原 PR 下用 `gh pr comment create --body-stdin <<'EOF'` 追加语义化状态,正文固定包含起止时间、总耗时、冲突状态、CI/preflight conclusion、source commit、PipelineRun、targetValidation、Argo/webAssets 和 git mirror pendingFlush/githubInSync。评论去重状态写入 `.state/hwlab-g14/v02-pr-comment-signatures.json`,同一状态签名不会重复刷评论;v0.2 monitor 指针使用 `.state/hwlab-g14/latest-v02-monitor-job.json`、`latest-v02-once-job.json`、`latest-v02-dry-run-job.json` 和 `latest-v02-once-dry-run-job.json`,不会覆盖默认 G14 monitor 指针。`--lane v02 --once --dry-run` 只做单轮 preflight/merge/CD/comment plan,不写 GitHub、不触发 CD。 - `agentrun v01 control-plane status|trigger-current|refresh|cleanup-runs|cleanup-released-pvs [--dry-run|--confirm]` 是 AgentRun `v0.1` 在 G14 k3s 的受控 Tekton/Argo 入口。`status` 只读汇总固定 source worktree commit、对应 commit-pinned PipelineRun、GitOps latest、Argo Application、`agentrun-v01` manager source commit、`planArtifacts.summary`、env image result 和 git mirror 摘要,并报告 manager/Argo/GitOps 是否对齐当前 source commit。默认输出是 compact commander 视图:`summary` 给出 source、PipelineRun、Argo、manager image、git mirror 和 `aligned` 结论;`timings` 给出 `sourceMs`、`runtimeMs`、`gitMirrorMs` 和 `totalMs`;远端 stdout/stderr tail 默认省略,失败时仍展开必要 tail,完整 tail 用 `--full`,原始 git mirror cache 用 `--raw`。`status` 聚合 source 后会并行读取 runtime 和 git mirror,并向 stderr 输出 `agentrun.control-plane.status.progress` JSON 事件,覆盖 `source`、`runtime`、`git-mirror` 的 started/succeeded/failed 和 elapsedMs,避免 10s 以上状态聚合期间无可见进展;`trigger-current` 先快进 `G14:/root/agentrun-v01` 到 `origin/v0.1`,检查 `devops-infra` mirror 的 `localV01` 是否等于目标 source commit,必要时先执行受控 mirror sync,再创建 `agentrun-v01-ci-` PipelineRun。confirmed trigger 只提交 CI/CD 工作并返回后续 `status` 命令,不等待完整 PipelineRun;同名 PipelineRun 运行中或已成功时拒绝重复触发,只允许失败态重建或首次创建。`refresh` 只对 `argocd/agentrun-g14-v01` 执行 hard refresh,用于 GitOps promotion 已完成但 Argo 仍停留旧 revision 时的受控同步入口;它不直接 patch runtime workload。`cleanup-runs` 只清理 `agentrun-ci` 中已完成且超过 `--min-age-minutes` 的 `agentrun-v01-ci-*` PipelineRun,通过 Tekton ownerRef 回收临时 workspace PVC;dry-run 必须输出候选 PipelineRun、owned PVC、active mount 保护、local-path 实际估算 bytes 和 confirm 命令。`cleanup-released-pvs` 只处理 `agentrun-ci`、`local-path`、`Delete` reclaim policy 的 `Released` PV,用于 PipelineRun 删除后残留 PV 的二次回收;它不触碰 runtime namespace、业务 PVC、Secret、registry storage 或 GitOps desired state。AgentRun 运行时和 SPEC 事实来源仍在 AgentRun 仓库,UniDesk 只维护受控运维入口。 @@ -71,7 +71,7 @@ CI/CD、GitOps、rollout、artifact 发布、PR 合并后的 runtime lane 滚动 - `trans gh:/owner/repo ...` 把 GitHub issue/PR 映射成只读/受控写入的虚拟文本目录,适合日报、PR 正文和 issue 正文的小补丁维护:`trans gh:/pikasTech/HWLAB ls` 展示 `pr/` 与 `issue/`,`trans gh:/pikasTech/HWLAB/pr ls [--limit N] [--full]` 和 `trans gh:/pikasTech/HWLAB/issue ls [--limit N] [--full]` 展示条目状态、楼层数、正文长度和标题,`trans gh:/pikasTech/HWLAB/pr/507 ls` 展示单个 PR 的一楼正文文件,`trans gh:/pikasTech/HWLAB/505/1 cat|rg|patch-apply` 兼容旧式 issue/PR number route。`patch-apply` 使用 UniDesk 默认 apply-patch v2 的虚拟文件 executor,把正文一楼映射为 `body.md`,写回仍走 `bun scripts/cli.ts gh issue/pr update` 的 guard/concurrency 规则;`rm` 对正文一楼结构化拒绝,避免误删 issue/PR 正文。大正文读取必须展开 UniDesk gh dump 文件,否则 `cat/rg/patch-apply` 会误读为空,这是 `gh:` 虚拟文件接口的 P0 可见性契约。 - `hwlab cd status|audit|preflight|apply --env dev [--dry-run]` 是旧 D601 HWLAB DEV CD 指挥侧 wrapper,仅用于显式 legacy 诊断和迁移对照。默认通过 UniDesk provider `host.ssh` 进入 D601,再调用 HWLAB repo-owned `scripts/dev-cd-apply.mjs`,不内嵌发布 kubectl 逻辑:`status` 汇总固定 CD mirror、Git clean/main/origin-main、`deploy/deploy.json`/artifact catalog/report、D601 native k3s guard 和 CD Lease lock,并用 `scripts/dev-cd-apply.mjs --status --skip-live-verify` 取得 target/promotion 摘要;`audit` 在 k3s/CD 恢复后做只读健康审计,返回有界 JSON 的 blocker 分类、D601 guard/node、SecretRef 存在性、registry 可达性、Lease phase/holder/staleness、deploy.json 与 artifact/workload image 收敛、current Deployment image/revision/rollout、16666/16667 public health commit/readiness 和 DB/runtime durability 摘要;`preflight` 进一步检查必需 SecretRef 对象/键存在性并运行 HWLAB `scripts/dev-cd-apply.mjs --dry-run --skip-live-verify` 受控事务摘要。完整远端 stdout/stderr 写入 D601 `~/.state/unidesk-hwlab-cd//` 和本地 `.state/hwlab-cd//` task dump,stdout 只返回有界摘要。默认 HWLAB CD repo 是 `/home/ubuntu/hwlab_cd`,`/home/ubuntu/hwlab` runner 历史目录不得作为发布真相。wrapper 强制 `KUBECONFIG=/etc/rancher/k3s/k3s.yaml` 并只以这个显式目标作为 gate;显式目标出现 `docker-desktop`、`desktop-control-plane` 或 `127.0.0.1:11700` 信号会结构化拒绝,audit/preflight/apply --dry-run 都必须观察到 node `d601`。真实 apply 只暴露 `scripts/dev-cd-apply.mjs --apply --confirm-dev --confirmed-non-production --write-report` 命令形状并标注 host-commander-only,本 runner 不执行 live apply、rollout、Lease mutation 或 DEV deploy apply。长期规则见 `docs/reference/hwlab.md`。 - `gh auth status [--repo owner/name]` 探测 GitHub 操作前置条件并输出脱敏 JSON:是否存在 `gh` binary、是否存在 `GH_TOKEN`/`GITHUB_TOKEN` 或可用 `gh auth token` fallback、REST API 是否可达、目标 repo 是否可见、issue 是否可读。degraded reason 必须归类为 `missing-binary`、`missing-token`、`auth-failed`、`github-transient`、`network-proxy-failed`、`permission-denied`、`repo-not-found`、`repo-forbidden`、`issue-not-found`、`pr-not-found`、`scope-insufficient`、`validation-failed`、`invalid-response` 或 `unsupported-command`,不得打印 token;失败对象必须包含 `runnerDisposition=infra-blocked|business-failed`,runner 应优先用该字段分流。`github-transient` 表示 GitHub DNS/API 连接在收到 HTTP 状态前失败,输出应带 `retryable=true` 或等价 commander action;这不是缺 token、认证失败、权限不足或 PR 语义失败。 -- `codex prompt-lint [prompt|--prompt-file path|--prompt-stdin]` 是派发/steer 前的本地 dry-run prompt lint。它只读取 prompt 文本,返回 `dryRun=true`、`mutation=false`、`declaredClass`、`effectiveClass`、`requiredClass`、`dispatchDisposition`、缺失或矛盾项和有界 evidence,不访问 live service、不提交任务、不打印完整 prompt。分级固定为 `read-only`、`live-read`、`live-mutating`;未声明时按 `read-only` 处理。`codex submit --dry-run` 与 `codex steer --dry-run` 会嵌入同一 `promptLint` 结果,帮助指挥官在 dispatch/steer 前发现缺失或矛盾的 live mutation 授权。长期规则见 `docs/reference/code-queue-supervision.md` 的 DEV 测试授权分级。 +- `codex prompt-lint [prompt|--prompt-file path|--prompt-stdin]` 是派发前的本地 dry-run prompt lint。它只读取 prompt 文本,返回 `dryRun=true`、`mutation=false`、`declaredClass`、`effectiveClass`、`requiredClass`、`dispatchDisposition`、缺失或矛盾项和有界 evidence,不访问 live service、不提交任务、不打印完整 prompt。分级固定为 `read-only`、`live-read`、`live-mutating`;未声明时按 `read-only` 处理。新任务走 AgentRun Queue,指挥官应把 lint 结果纳入 `agentrun v01 queue submit` payload 审查。长期规则见 `docs/reference/code-queue-supervision.md` 的 DEV 测试授权分级。 - `gh issue list [owner/repo] [--state open|closed|all] [--limit N] [--search text] [--label label[,label...]]... [--repo owner/name] [--json number,title,state,url,updatedAt,createdAt,author,labels] [--raw|--full]` 通过 GitHub REST 列出 issue,默认 `state=open`、`limit=30`,输出稳定 JSON 且不依赖系统 `gh` binary。`owner/repo` 位置参数是 `--repo owner/repo` 的兼容别名;若位置 repo 与 `--repo` 冲突,或位置参数不是 `owner/repo`,必须结构化失败,禁止静默 fallback 到默认 repo。`--limit` 是 CLI 返回上限,不等同 GitHub 单页 `per_page`:当 `--limit > 100` 或默认页中混入 PR 时,CLI 必须分页抓取 GitHub REST/Search page,过滤 PR 后再返回 issue,并在输出中披露 `pagination.fetchedPages/rawCount/hasMore`;`hasMore=true` 时只能说明当前有界扫描未穷尽,禁止把它当作“仓库没有更多 issue”。`--search` 使用 GitHub Search Issues API,并自动追加 `repo:/`、`type:issue` 和 state qualifier,用于创建新 issue 前做低摩擦查重;未知 state 或未知 `--json` 字段必须结构化失败并带 `runnerDisposition=business-failed`。`--label` 是 GitHub REST `labels=label1,label2` 或 Search `label:` 服务端过滤,支持重复 `--label` 和逗号分隔;filter 不在本命令上下文中使用(如 `issue read`、`pr list`)必须结构化失败并指明 `gh issue create/list/stale-close` 才是合法作用域。GitHub issues API 可能混入 PR,CLI 会从 `.data.issues` 中过滤 pull request。`--raw|--full` 在 `gh issue list` 上是绕过 20 KiB stdout 截断的显式开关:响应结果会带 `noDump=true`,`output.ts` 据此跳过 head/tail 替换并把完整数据 inline 输出;当响应未超阈值时 `--raw|--full` 行为等价默认。 - `gh issue lifecycle`:`--state` 只能作为 `gh issue list` / `gh issue board-row list` / `gh pr list` 的过滤参数;`gh issue update` / `gh issue edit` 只写 body/title,**不接受** `--state` 改 open/closed。把 `gh issue update --state closed` 落到错命令上时,CLI 必须返回 `validation-failed` 并显式提示 `gh issue close ` / `gh issue reopen `(PR 用 `gh pr close|reopen `),并把 5 条受支持命令放进 `supportedCommands`,禁止把"无 `--state` 改 issue 状态"的命令升级为"接受 `--state`"。`gh issue close|reopen` 成功输出默认是 compact issue 摘要,不得回显完整 `issue.body`;需要正文时后续使用返回的 `readCommands` 或 `gh issue view --json body|--full|--raw`。生命周期 close/reopen 的评论推荐用 `--comment-stdin <<'EOF'` 直接写 heredoc/stdin;短单行可用 `--comment`,已有复用文件才用 `--comment-file`。需要附长篇 CLI 验收证据时,先用 `gh issue comment create --body-stdin <<'EOF'` 写证据评论,再用 `gh issue close --comment <短引用>` 关闭。issue 硬删除走 `close`,PR 硬删除走 `close`,两者都没有"delete"语义。 - `gh issue comment create --repo owner/name --body-stdin`、`gh issue comment delete --repo owner/name`、`gh issue close --repo owner/name [--comment |--comment-stdin]`、`gh issue reopen --repo owner/name [--comment |--comment-stdin]`、`gh issue update --repo owner/name [--title ...] [--body-stdin]`、`gh issue edit ...`、`gh issue board-row get|update|add|move|delete|upsert --repo owner/name ...` 都接受与 `gh issue view|read`、`gh pr *` 一致的 `owner/repo#number` 位置 shorthand;shorthand 与显式 `--repo` 冲突时结构化失败并把两者都回显到错误对象里,避免静默改写目标 repo。`gh issue view|read`、`gh pr view|read|files|diff|preflight|closeout|comment create|comment delete|close|reopen|merge|edit|update` 已长期支持该 shorthand,issue 写命令对齐后整个 `gh` 子命令在 shorthand 行为上保持一致,不再需要把 `pikasTech/HWLAB#621` 拆成 `621 --repo pikasTech/HWLAB`。来源:HWLAB #621 CLI 验收 `gh issue comment create pikasTech/HWLAB#621` 摩擦改进。 @@ -90,9 +90,9 @@ CI/CD、GitOps、rollout、artifact 发布、PR 合并后的 runtime lane 滚动 - `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/:` 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 --limit N` 是只读观察入口;`schedule run`、`schedule retry-run`、`schedule delete` 和 `schedule upsert-pgdata-backup` 会触发运行或写入配置,生产恢复时必须有明确授权。`schedule runs --limit N` 是全局历史视图,返回 `scope=global` 和 `scheduleId=null`;`schedule runs --limit N` 是指定 schedule 历史视图,返回 `scope=schedule` 和对应 `scheduleId`。CLI 必须拒绝 `schedule runs 50` 这类纯数字位置参数,并提示使用 `schedule runs --limit 50`,避免把空数组误判成“没有历史 run”。`schedule run --wait-ms N` 触发同一 schedule,并且即使 wait 超时也必须返回 `newRunId` 和 `observeCommand`;`schedule retry-run ` 只接受 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 ` 是旧 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 --dry-run`,文件路径推荐 `bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue --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` 是有界预览对象,`countContext` 与 `counts` 是权威计数;`submitted.taskStates[]` 直接给出本次 task id、queue id、status 和 `state=queued|running|terminal|unknown`,其来源固定为 `response.tasks[].status`。当本次新任务仍是 queued/retry_wait,`queuedTaskIds.items` 必须包含该 id;当 counts 非零但 active/queued id 列表因为 split-brain-live、上游省略或默认有界披露而不可枚举时,预览必须设置 `idsUnavailable=true`、`itemsOmitted=true` 和 `itemsMeaning=not-enumerated-in-default-submit-output`,不得打印容易误读的 `items=[]`。`queue.activity.effectiveActiveTaskCount` 和 `queue.commanderConcurrency.activeRunnerCount` 是并发判断字段;`splitBrainLive=true` 时继续把 fresh heartbeat/database active 计入 active。需要原始 drill-down 时使用 `queue.listPreviewPolicy.rawCommand`,默认是 `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview?limit=30 --raw --full`。backend-core 默认把提交、队列 CRUD、已读状态、历史摘要和轻量 Trace 读取分流到主 server `code-queue-mgr`,由它写入主 PostgreSQL;D601 scheduler 只轮询并执行已入库任务。 -- `codex steer [prompt|--prompt-file path|--prompt-stdin] [--steer-id id] [--dry-run] [--no-retry|--retry-attempts N] [--full|--raw]` 向运行中的 Code Queue 任务发送纠偏 prompt。CLI 会为同一 task/prompt 生成稳定 `steerId`,也允许显式传入 `--steer-id`;所有 retry 都复用同一 `steerId`,支持后端按 key 抑制重复 trace 注入。真实成功只返回低噪声写入确认,不回显 prompt 或完整任务状态;输出包含 `steer.status`、`steer.deliveryState`、`steer.steerId`、`traceConfirmation` 和 `commands.traceConfirm`。失败默认只返回 `accepted=false`、原因、scope、retryable、attempt 摘要、operator guidance 和 task/read/submit/health drill-down 命令。`upstreamBodyPreview`、request 元数据和 raw upstream failure 必须显式加 `--full` 或 `--raw` 才输出。任务已终态时返回紧凑 `task-already-terminal`、`status=not_accepted`、`deliveryState=not_accepted`、task 状态、终态状态、更新时间、`retryable=false` 和 `codex task` / `codex read` / `codex submit --reference-task-id ` 后续命令。 -- `codex resume [prompt|--prompt-file path|--prompt-stdin] [--resume-id id] [--dry-run] [--full|--raw]` 对已终态或 awaiting-closeout 的原 Code Queue task 创建后续 turn,优先用于 PR 小修、冲突、rebase、补测和 reviewer feedback,保留原 task、attempt、branch/PR 上下文和 `codexThreadId`/OpenCode session。CLI 会为同一 task/prompt 生成稳定 `resumeId`,也允许显式传入;同一 `resumeId` 加同 prompt 返回 `duplicate_suppressed` 且不重复注入,同一 `resumeId` 加不同 prompt 返回 409 conflict。真实成功只返回 taskId、resumeId/turnId、`deliveryState`、是否复用原 `codexThreadId`、有界 trace confirmation 和 `codex task/detail/trace/output` 后续命令,不回显 prompt 或完整 task state。running/judging task 必须 fail closed 并给出 `disposition=use-steer-for-active-task` 与 `codex steer` 命令,不把 resume 伪装成新 task;不存在 task 返回结构化 not accepted。若 delivery timeout 或 trace 未确认,输出 `deliveryUnconfirmed` 和确认命令,调用方先查 `codex task --trace` 再用同一 `resumeId` 重试。 +- `agentrun v01 queue|sessions` 是当前指挥官新任务和 AgentRun session 控制入口。UniDesk CLI 通过 G14 `/root/agentrun-v01` 中官方 `./scripts/agentrun --manager-url auto` 执行:`queue commander` 查看指挥官队列,`queue submit --json-file ` 创建新任务,`queue dispatch --json-file ` 派发,`queue read/cancel` 标记和取消队列任务;`sessions trace/output/read/steer/cancel` 读取和控制已创建 session。本地 bridge 只负责把 `--json-file`、`--prompt-file`、`--runner-json-file` 或 `--prompt-stdin` materialize 到 G14 临时文件,再调用官方 AgentRun CLI;它不是旧 Code Queue adapter,不做双写,也不迁移旧历史。 +- `codex submit/enqueue`、`codex steer`、`codex resume`、`codex queue create`、`codex queue merge`、`codex move`、旧 Web 提交表单、旧队列管理和旧 workdir 管理是冻结的 legacy Code Queue 写入口。CLI 必须返回 `ok=false`、`frozen=true`、`degradedReason=legacy-code-queue-frozen` 和 AgentRun 替代命令;服务端旧 API 写入口必须返回 410。新任务、steer、trace/output、read 和 cancel 走 AgentRun Queue/Sessions。 +- 旧 Code Queue 只保留历史归档、只读排障和残留任务停止。`codex task/tasks/output/read/unread/queues` 继续通过 backend-core 私有代理读取旧 PostgreSQL 历史;`codex interrupt|cancel ` 只用于停止旧运行面残留任务。旧 `steer-confirm` 只作为历史 trace confirmation 查询,不是新任务控制入口。 - `codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/] [--pr-create-dry-run --pr-create-dry-run-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 授权,以及 guarded `gh pr merge --dry-run` 预检路径;系统 `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 ` 通过 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 commander|supervisor|full] [--queue id] [--status succeeded|running|queued|failed|canceled|judging|retry_wait[,..]] [--unread|--unread-only] [--limit N] [--before-id id]` 通过同一私有代理输出渐进式披露视图。host commander 轮询应优先使用 `--view commander`:它是低噪声 polling 入口,只返回有界 action map,包含 `activeRunners.count` 及来源/处置、少量 active item、queued/retry_wait 精确计数、terminal-unread 总数和省略行数、关键风险计数、HWLAB#645/#99/#116/#164/#317 与 UniDesk#20/#118 命中、确定性分类计数和集中式 `codex task/trace/output/read` drill-down 命令。默认 commander 不展开历史 terminal unread item details,也不嵌入 prompt preview、final response preview、trace、output 或 raw overview;terminal unread 详情必须通过 `codex unread`、`codex tasks --unread --view supervisor`、`--view full`、`--full` 或 per-task `codex read ` 获取。默认 `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` 才提示介入。确定性分类只在有强基础设施故障信号时输出 `infrastructure-blocker`;普通 runner/CLI/治理上下文归入 `infra-governance`、`workflow` 或 `unknown`,避免把历史任务误报为基础设施阻塞。`supervisor` prompt/body 只给短预览和原始字符数,`running`/`completedUnread`/`queued` 默认只返回一个有界小页并通过 section `commands.next` 继续分页,`recentCompleted` 默认限量且不重复 `completedUnread` 未读终态,不嵌入完整 Trace、final response 或全量 overview。`--limit` 在 commander/supervisor 中主要是扫描/分页预算,不是返回几十条肥行的开关;CLI 安全上限是 100,输出会在 `filters.requestedLimit`、`filters.effectiveLimit`、`filters.limitCapped` 和 disclosure 中说明显式请求是否被 capped;底层 overview 拉取预算独立显示在 `source.requestedLimit` / `source.effectiveLimit`,所以 `--limit 260` 应显示 requested=260、effective=100、source requested/effective=200,而不是只露出一个含糊的 `limit`。`--unread` 是 `--unread-only` 的别名,必须只保留未读终态;`--status` 必须真实过滤支持的状态,并接受常见 alias:`completed|complete|success|successful -> succeeded`,`cancelled -> canceled`,`retry-wait|retrying -> retry_wait`,`pending -> queued`。未知参数或未知状态必须结构化失败并给出支持值和 alias 建议,预期参数错误默认不输出 stack trace;显式 `UNIDESK_CLI_DEBUG=1` 可保留完整诊断。需要更详细当前页任务行时显式使用 `--view full` 或 `--full`,仍受 `--limit` 和 `--before-id` 分页约束。 @@ -102,12 +102,11 @@ CI/CD、GitOps、rollout、artifact 发布、PR 合并后的 runtime lane 滚动 - `codex read ` 在人工审阅后标记单个终态任务已读,并在同一次响应中返回稳定任务身份、执行元数据、终态 attempt 摘要、最后错误或 judge 信息和最终 response,避免标记已读后还要额外 drill-down 才能确认结果。该命令不返回完整 prompt、tool logs 或 feedback prompt,只返回字符数、计数和 `codex task/detail/trace/output` 渐进披露命令;列表、overview 和 supervisor 视图只返回这个命令字段,不得自动执行,也不得批量清空未读状态。 - `codex dev-ready` 查询 Code Queue `/api/dev-ready` 并返回有界 readiness 摘要,包括工具、Docker、Codex config、SSH 和 `devReady.skills`。`devReady.skills` 只暴露 `UNIDESK_SKILLS_PATH`、是否存在、是否只读、skillCount、`cli-spec` 是否可见和修复建议,不输出宿主 auth/token 文件内容。 - `codex judge --attempt N [--dry-run] [--include-prompt]` 通过 Code Queue 私有代理按指定 attempt 单步复现 judge;这是执行面诊断入口,仍依赖 D601 scheduler/runner 侧的真实 judge builder、MiniMax 调用路径和执行环境。默认会真实调用 MiniMax,`--dry-run` 只返回 prompt/payload 大小、attempt 窗口和重建来源诊断,`--include-prompt` 仅用于本地深度排查。 -- `codex steer [prompt|--prompt-file path|--prompt-stdin] [--steer-id id] [--dry-run] [--no-retry|--retry-attempts N] [--full|--raw]` 通过 Code Queue 私有代理向正在运行的 task 注入纠偏提示,正式替代底层 `microservice proxy code-queue /api/tasks//steer` 调用。prompt 必须且只能来自位置参数、文件或 stdin 之一;`--dry-run` 只输出 `method`、`path`、`stableProxyPath`、`steerId`、retry policy、prompt 字符数、截断预览、`commands.traceConfirm` 和 raw proxy 等价命令,不触碰运行中 session,也不得泄露超长 prompt 全文。真实执行是写入操作,成功只返回 `accepted=true`、`status=accepted`、`deliveryState`、`steerId`、task id、prompt 字符数、`promptOmitted=true`、有界 `traceConfirmation`、task/queue 确认、attempt summary 和后续查看命令,不回显 prompt 或完整 task state;路径固定为 `/api/microservices/code-queue/proxy/api/tasks//steer`,只能作用于 D601 scheduler 上存在 active steerable turn 的 running task。默认对 `stable-proxy-failed` 和 `backend-core-unreachable` 这类 retryable control-plane failures 做一次有界重试;`--retry-attempts N` 最大为 3,`--retry-delay-ms N` 最大为 5000,`--no-retry` 用于复现单次失败。 - `codex steer-confirm --steer-id [--raw]` 是只读 trace confirmation lookup。默认输出 `traceConfirmation.found/accepted/deliveryState/trace.seq/trace.at/promptChars/promptHash` 和 `delivery.status`,不回显 prompt;`--raw` 才附带原始 backend confirmation body。该命令用于处理 stable-proxy abort 后的 `deliveryUnconfirmed`,不要用重复 prompt 代替确认查询。 -- `codex steer` 非 dry-run 失败仍输出 JSON 且退出非零,除非后续 `steer-confirm` 证明 trace 已含该 `steerId`,此时返回 `ok=true`、`steer.status=accepted_response_timeout`、`deliveryState=accepted_response_timeout`,表示 backend 已接受但响应路径超时。`.data.diagnostics.reason` 用于 runner 分流,当前包括 `backend-core-unreachable`、`code-queue-microservice-unregistered`、`proxy-unauthorized`、`proxy-404`、`steer-endpoint-404`、`upstream-runtime-rejected`、`stable-proxy-failed` 和 `invalid-proxy-response`。`scope` 区分 `backend-core`、`stable-proxy`、`code-queue-runtime` 或 `unknown`,默认带 `status`、`exitCode`、`retryable`、`attempts`、`retryPolicy`、operator guidance 和推荐交叉验证命令,但不回显 steer prompt、不带 request body、不带 `upstreamBodyPreview`,也不带 raw upstream response;这些详细字段必须显式加 `--full` 或 `--raw` 才展开。若任务不在 running/active-turn 状态,通常归类为 `upstream-runtime-rejected` 并显示 `status=not_accepted` / `deliveryState=not_accepted`,不得静默成功。`502 provider HTTP tunnel failed`、`provider-gateway-http-fetch`、`The operation was aborted` 或约 30 秒 tunnel wait abort 会归类为 `stable-proxy-failed`,CLI 会先按 retry policy 重试并在失败后自动做一次 `steer-confirm` 查询;如果 `.data.steer.deliveryUnconfirmed=true` 仍为 true,指挥官应运行输出中的 `commands.traceConfirm`,确认仍 unknown 后才用输出中的 `commands.retry` 复用同一个 `--steer-id` 重试。后端已支持同 `steerId`+同 prompt 的重复请求返回 `duplicateSuppressed=true`,不会再次注入 trace;同 `steerId` 搭配不同 prompt 会返回 409 conflict。若 D601 返回的 409 已包含 terminal task state,CLI 默认改为紧凑终态响应:`reason=task-already-terminal`、`status=not_accepted`、`deliveryState=not_accepted`、task status、terminal status、`updatedAt`/`finishedAt`、`retryable=false`,并只给出 `codex task `、`codex read ` 和 `codex submit --prompt-file --reference-task-id ` 后续命令,不回显 steer prompt、完整 request body 或大 task object。 +- 旧 `codex steer` 已冻结;`codex steer-confirm` 只作为历史 trace confirmation lookup。新运行中纠偏使用 `bun scripts/cli.ts agentrun v01 sessions steer --prompt-file `,并用 `sessions trace/output/read` 观察。 - `codex interrupt|cancel ` 通过 Code Queue 私有代理请求中断;running/judging 任务会请求 D601 当前 agent run 停止,queued/retry_wait 任务的取消也必须保持与 WebUI 相同代理路径,返回有界 task 摘要和后续查询命令。任何需要接触 active run 的动作仍属于 D601 执行面。 -- Code Queue 多队列 lane 由 `codex` 命令命名空间管理:`queues [--full|--all] [--limit N] [--page N|--offset N]` 列表、`queue create ` 创建、`queue merge --into ` 合并、`move --queue ` 迁移;这些队列管理入口默认由主 server `code-queue-mgr` 直管 PostgreSQL,仍通过稳定 `code-queue` 用户服务代理路径访问。`codex queues` 默认只返回 active/nonempty/unread/runnable queue 摘要、activity、commanderConcurrency、全局 counts 和 execution diagnostics;`--full` 或 `--all` 只切换为完整队列行视图的一页,仍受 `--limit`/`--page`/`--offset` 分页约束,不再默认携带 deprecated full array。summary 和 full 的稳定机读路径都是 `.data.queues.items[]`,全局元数据固定在 `.data.queues.commanderConcurrency`、`.data.queues.activity`、`.data.queues.counts`、`.data.queues.executionDiagnostics`、`.data.queues.activeTaskIds` 和 `.data.queues.queuedTaskIds`;需要完整 upstream 时使用输出中的 raw command。`commanderConcurrency.activeRunnerCount` / `activity.effectiveActiveTaskCount` 是指挥官并发判断的有效活跃数,`schedulerLocalActiveQueueCount`/`activeQueueIds` 只描述本地 scheduler active-run slots,不能覆盖数据库 running 计数或 heartbeat-fresh runner 计数。旧 full 顶层数组语义已作为 deprecated 兼容信息记录,不再作为 `.data.queues` 主形态。同一个 queue 内部串行执行,不同 queue 之间并行执行。迁移只允许尚未被 scheduler claim 的 `queued`/`retry_wait` 任务,必须满足 `startedAt=null`、`currentAttempt=0` 且没有 active thread/turn;已进入 `running`/`judging` 或已有 claim 标记的任务返回 409,不得被 move/merge 回写成 queued。合并会移动可迁移任务归属并自动删除源 queue 记录,只保留合并后的目标 queue;若 source 或 target queue 存在 active/claimed 任务,合并整体返回 409。合并后的目标 queue 按任务原 `queueEnteredAt`/`createdAt` 时间顺序串行,成功迁移 queued/retry_wait 任务后由 D601 scheduler 轮询推进。 -- 所有 `codex` 查询和管理命令必须走与 WebUI 相同的 backend-core 私有代理路径 `/api/microservices/code-queue/proxy/...`;CLI 不得为了提交、移动、中断、取消或队列管理直接调用 D601 内部 Service、数据库、pod curl 或 k3sctl scheduler 子服务。若该路径失败,应先修复 CLI/backend/provider tunnel 链路,而不是绕过控制面。 +- 旧 Code Queue 多队列 lane 现在是归档视图:`codex queues [--full|--all] [--limit N] [--page N|--offset N]` 只读展示历史 queue 摘要、activity、commanderConcurrency、counts 和 execution diagnostics。`queue create`、`queue merge`、`move` 等旧队列写入口冻结并返回 `legacy-code-queue-frozen`;AgentRun 新任务的排队、派发和取消必须使用 `agentrun v01 queue`。 +- 所有旧 `codex` 历史查询、已读和残留 interrupt/cancel 命令必须走与 WebUI 相同的 backend-core 私有代理路径 `/api/microservices/code-queue/proxy/...`。旧 submit/steer/resume/queue mutation/move/workdir mutation 不得绕过冻结;若需要新任务或新 session 控制,使用 AgentRun Queue/Sessions。 - `job list [--limit N] [--include-command]` 与 `job status [--tail-bytes N]` 查询 `.state/jobs/` 文件系统状态,是异步命令的可观测入口。`job list` 默认只返回最新 50 条摘要,并为已知异步工作流返回轻量 `progress.summary` 与后续查询命令;`job status` 默认返回结构化 `progress`、stdout/stderr 末尾 12000 字节、`tailPolicy` 与完整日志路径。已知工作流应从有界日志尾部抽取阶段、关键对象名和下一步命令,避免为了判断当前阶段而手工打开完整 stdout/stderr。`hwlab_g14_v02_trigger_current` 的 progress 必须暴露 trigger 阶段、source commit 和 PipelineRun;`hwlab_g14_v02_pr_monitor` 的 progress 必须暴露 preflight、merge、source-head、cd-trigger、cd-status、git-mirror-flush 和 pr-comment 阶段,以及 PR、source commit、PipelineRun、targetValidation/pendingFlush 摘要;`hwlab_g14_git_mirror_sync|flush` 与 `agentrun_v01_git_mirror_sync|flush` 的 progress 必须暴露 sync/flush 状态、Job 名、pendingFlush 与 fetch/push/total/SSH timing,并给出对应 repo 的 mirror status 命令。 - `debug health`、`debug dispatch` 与 `debug task` 走真实内部 core、WebSocket、数据库、provider、系统指标、Docker 状态和 Host SSH 维护桥流程,只用于开发调试,不写入 `TEST.md` 的正式验收步骤。 - `e2e run [--only pattern[,pattern...]] [--skip pattern[,pattern...]]` 使用 publicHost 派生的公开 production frontend/dev frontend/provider ingress URL,并通过 Docker 内网验证 core API、PostgreSQL、provider self-connection、系统指标曲线、Docker 状态快照、provider.upgrade 预检和 Playwright 前端页面,是交付前的自动化 E2E 门禁;CLI 默认输出 check 状态摘要,完整诊断写入 `resultPath`,日常迭代应优先用 `--only` / `--skip` 跑最小必要集合。 @@ -133,7 +132,7 @@ UniDesk 仓库自带 `scripts/playwright-cli.ts` 作为 host commander 浏览器 定点状态查询优先使用后台 job 输出的稳定对象名:PipelineRun 用 `hwlab g14 control-plane status --lane v02 --pipeline-run `,已知 source commit 用 `--source-commit `。不要在 source branch 可能继续推进时用默认最新 head status 判定历史 run 成败;默认最新 head 口径只适合判断当前 lane 是否整体最新。`control-plane status` 会汇总 source、mirror、Tekton、Argo、runtime workload 和公网探针,可能比普通只读命令慢;高频轮询应先用 `job status` 或更窄的 status,完整 status 留给阶段收口和异常定位。 -`server rebuild` 与 `server start`、`server stop` 一样必须通过返回的 job id 确认结果;不要把连续 `server rebuild` 命令理解成“前一个重建已完成”,因为两个命令只是在快速创建异步 job。重建 frontend 只保留为维护/非标准路径;标准 frontend 发布必须先运行 `ci publish-user-service --service frontend --commit `,再运行 `deploy apply --env dev --service frontend` 和 `deploy apply --env prod --service frontend`,并验证 `/health.deploy.commit`。重建 dev 入口薄代理使用 `bun scripts/cli.ts server rebuild dev-frontend-proxy`,随后验证 `server status` 的 `urls.devFrontend` 和 `http://127.0.0.1:18083/health`;重建 Todo Note 后端使用 `bun scripts/cli.ts server rebuild todo-note`,随后用 `microservice health todo-note` 和 `microservice proxy todo-note /api/instances` 验证;重建 Code Queue Manager 使用 `bun scripts/cli.ts server rebuild code-queue-mgr`,随后用 `microservice health code-queue-mgr`、`microservice health code-queue` 和 `codex submit --dry-run` 验证主 server 控制面路径;重建 Project Manager 后端使用 `bun scripts/cli.ts server rebuild project-manager`,随后用 `microservice health project-manager` 和 `microservice proxy project-manager /api/projects` 验证;重建 Baidu Netdisk 后端使用 `bun scripts/cli.ts server rebuild baidu-netdisk`,随后用 `microservice health baidu-netdisk` 和 `microservice proxy baidu-netdisk /api/transfers` 验证,但该命令只保留为维护/非标准路径;重建 OA Event Flow 后端使用 `bun scripts/cli.ts server rebuild oa-event-flow`,随后用 `microservice health oa-event-flow` 和 `microservice proxy oa-event-flow /api/diagnostics` 验证。D601 Code Queue 执行面和 Decision Center 后端由 D601 k3s/k8s 控制面代管;persistent dev backend-core、frontend 和 Decision Center 都走 artifact consumer,当前 Code Queue 仍不得通过维护通道直连 D601 做部署。不得把 `docker rm` 手工兜底当成正式交付步骤。 +`server rebuild` 与 `server start`、`server stop` 一样必须通过返回的 job id 确认结果;不要把连续 `server rebuild` 命令理解成“前一个重建已完成”,因为两个命令只是在快速创建异步 job。重建 frontend 只保留为维护/非标准路径;标准 frontend 发布必须先运行 `ci publish-user-service --service frontend --commit `,再运行 `deploy apply --env dev --service frontend` 和 `deploy apply --env prod --service frontend`,并验证 `/health.deploy.commit`。重建 dev 入口薄代理使用 `bun scripts/cli.ts server rebuild dev-frontend-proxy`,随后验证 `server status` 的 `urls.devFrontend` 和 `http://127.0.0.1:18083/health`;重建 Todo Note 后端使用 `bun scripts/cli.ts server rebuild todo-note`,随后用 `microservice health todo-note` 和 `microservice proxy todo-note /api/instances` 验证;重建 Code Queue Manager 使用 `bun scripts/cli.ts server rebuild code-queue-mgr`,随后用 `microservice health code-queue-mgr`、`microservice health code-queue`、`codex tasks --view commander --limit 5` 和冻结的 `codex submit` 返回 `legacy-code-queue-frozen` 验证主 server 控制面路径;重建 Project Manager 后端使用 `bun scripts/cli.ts server rebuild project-manager`,随后用 `microservice health project-manager` 和 `microservice proxy project-manager /api/projects` 验证;重建 Baidu Netdisk 后端使用 `bun scripts/cli.ts server rebuild baidu-netdisk`,随后用 `microservice health baidu-netdisk` 和 `microservice proxy baidu-netdisk /api/transfers` 验证,但该命令只保留为维护/非标准路径;重建 OA Event Flow 后端使用 `bun scripts/cli.ts server rebuild oa-event-flow`,随后用 `microservice health oa-event-flow` 和 `microservice proxy oa-event-flow /api/diagnostics` 验证。D601 Code Queue 执行面和 Decision Center 后端由 D601 k3s/k8s 控制面代管;persistent dev backend-core、frontend 和 Decision Center 都走 artifact consumer,当前 Code Queue 仍不得通过维护通道直连 D601 做部署。不得把 `docker rm` 手工兜底当成正式交付步骤。 新部署入口优先使用 `deploy apply`。`deploy apply --env dev --service backend-core` 是 D601 k3s artifact consumer,消费 `ci publish-backend-core` 产出的成品镜像;`deploy apply --env dev|prod --service frontend`、`deploy apply --env dev|prod --service baidu-netdisk` 和 `deploy apply --env dev|prod --service decision-center` 是 artifact-consumer 样板;旧的 `codex deploy` 已禁用;后续 Code Queue 等 D601 服务部署应另行收敛到同一类受控 CD 路径,部署后用 live commit 校验证明不是旧服务。 @@ -442,7 +441,7 @@ PATCH `--main-server-ip` 是一个全局前缀,必须放在需要透传的命令同一次调用中,例如 `bun scripts/cli.ts --main-server-ip 74.48.78.17 debug health`。默认传输是公网 frontend:本地 CLI 读取本仓库 `config.json` 中的 frontend 登录账号密码,登录 `http://:/` 获取 HttpOnly session cookie,然后通过 frontend 的 `/api/*` 同源代理访问 backend-core 内网 API;因此计算节点只需要能访问公网 frontend,不需要主 server SSH key,也不需要打开 backend-core REST API 或 PostgreSQL 端口。 -默认 frontend 传输支持 `debug health`、`debug dispatch`、`debug task`、`artifact-registry status|health`、`ci publish-user-service --dry-run`、`microservice list/status/health/diagnostics/tunnel-self-test/proxy`、`decision upload/list/show/health`、`decision requirement list/upsert`、`decision diary import/list/history/months/show/edit/upsert`、`codex task `、`codex tasks`、`codex unread`、`codex queues`、`codex output `、`codex judge --attempt N` 和 `ssh `。`microservice status/health/diagnostics` 经 frontend 远程传输时也复用本地 CLI 的默认 compact summary,`microservice health code-queue` 只有显式 `--raw` 或 `--full` 才返回完整健康 body。运行中纠偏 `codex steer` 属于 active run write control,应在主 server 本机 CLI 或显式 SSH 传输上执行,避免公网 frontend 透传限制 stdin/body 审计语义。其中 `ssh` 的 remote frontend 传输使用 authenticated frontend `/ws/ssh` WebSocket 代理接入 backend-core SSH bridge,stdout/stderr 按字节流直通到调用端,不经过 `/api/dispatch`、`/api/tasks` 或 task JSON compact;frontend 运行时必须通过 `PROVIDER_TOKEN`/`UNIDESK_PROVIDER_TOKEN` 或 `PROVIDER_TOKEN_FILE`/`UNIDESK_PROVIDER_TOKEN_FILE` 读取 provider token,并且不能把 token 下发给 runner。因此 D601 Code Queue runner 内的 `tran G14 ...` 应与主 server 本机 `trans G14 ...` / `tran G14 ...` 在输出完整性上保持同一语义。非交互单进程命令优先 `trans D601 argv true`;`apply-patch`、stdin script、`py` 和旧 `apply-patch-v1` fallback 也走同一条 `/ws/ssh` 流式通道。交互式登录 shell 仍应在主 server 本机 CLI 使用,或显式切换到旧 SSH 传输后在主 server 上执行。当 backend-core、database、provider-dispatch 或 provider-host-ssh 缺失时,这些 read-only 预检必须返回结构化 `runnerDisposition=infra-blocked` 和缺失通道列表,而不是裸 `No such container`。若确实需要旧行为,可使用 `--main-server-key ` 或 `--main-server-transport ssh`,这时 CLI 会通过 SSH 登录主 server 的 `--main-server-root` 目录执行同一个 `bun scripts/cli.ts `。 +默认 frontend 传输支持 `debug health`、`debug dispatch`、`debug task`、`artifact-registry status|health`、`ci publish-user-service --dry-run`、`microservice list/status/health/diagnostics/tunnel-self-test/proxy`、`decision upload/list/show/health`、`decision requirement list/upsert`、`decision diary import/list/history/months/show/edit/upsert`、`codex task `、`codex tasks`、`codex unread`、`codex queues`、`codex output `、`codex judge --attempt N` 和 `ssh `。`microservice status/health/diagnostics` 经 frontend 远程传输时也复用本地 CLI 的默认 compact summary,`microservice health code-queue` 只有显式 `--raw` 或 `--full` 才返回完整健康 body。运行中纠偏已切到 AgentRun `sessions steer`;旧 `codex steer` 属于冻结写入口,不应通过 frontend 远程传输或旧 proxy 绕过。其中 `ssh` 的 remote frontend 传输使用 authenticated frontend `/ws/ssh` WebSocket 代理接入 backend-core SSH bridge,stdout/stderr 按字节流直通到调用端,不经过 `/api/dispatch`、`/api/tasks` 或 task JSON compact;frontend 运行时必须通过 `PROVIDER_TOKEN`/`UNIDESK_PROVIDER_TOKEN` 或 `PROVIDER_TOKEN_FILE`/`UNIDESK_PROVIDER_TOKEN_FILE` 读取 provider token,并且不能把 token 下发给 runner。因此 D601 Code Queue runner 内的 `tran G14 ...` 应与主 server 本机 `trans G14 ...` / `tran G14 ...` 在输出完整性上保持同一语义。非交互单进程命令优先 `trans D601 argv true`;`apply-patch`、stdin script、`py` 和旧 `apply-patch-v1` fallback 也走同一条 `/ws/ssh` 流式通道。交互式登录 shell 仍应在主 server 本机 CLI 使用,或显式切换到旧 SSH 传输后在主 server 上执行。当 backend-core、database、provider-dispatch 或 provider-host-ssh 缺失时,这些 read-only 预检必须返回结构化 `runnerDisposition=infra-blocked` 和缺失通道列表,而不是裸 `No such container`。若确实需要旧行为,可使用 `--main-server-key ` 或 `--main-server-transport ssh`,这时 CLI 会通过 SSH 登录主 server 的 `--main-server-root` 目录执行同一个 `bun scripts/cli.ts `。 计算节点可以用该入口测试自身的远程升级闭环,而不需要在计算节点公开 core REST API 或 database。标准顺序是:先运行 `bun scripts/cli.ts --main-server-ip 74.48.78.17 debug health` 确认主 server 看到当前 Provider 在线,且该 Provider labels 中 `unideskCapabilities` 包含 `host.ssh`、`hostSshConfigured=true`、`hostSshKeyPresent=true`;再运行 `bun scripts/cli.ts --main-server-ip 74.48.78.17 debug dispatch provider.upgrade --mode schedule --wait-ms 15000` 触发真实 `provider.upgrade`;随后再次运行 `debug health` 确认节点重新上线;最后运行 `bun scripts/cli.ts --main-server-ip 74.48.78.17 debug dispatch host.ssh --wait-ms 15000` 和 `bun scripts/cli.ts --main-server-ip 74.48.78.17 ssh hostname` 验证 SSH 透传能力。provider-gateway 新部署或升级后没有完成这组 remote CLI 自测,不能视为交付完成。 diff --git a/docs/reference/code-queue-supervision.md b/docs/reference/code-queue-supervision.md index 1153b0a9..44391ddf 100644 --- a/docs/reference/code-queue-supervision.md +++ b/docs/reference/code-queue-supervision.md @@ -36,7 +36,7 @@ HWLAB 业务目标、验收和实现优先级归 `pikasTech/HWLAB#7`;UniDesk 审阅 HWLAB runner 输出时,不能把 `SOURCE`、`LOCAL`、`DRY-RUN`、fixture 或只读报告误当成 `DEV-LIVE`。除非输出真的证明了 `res_boxsimu_1:DO1 -> hwlab-patch-panel -> res_boxsimu_2:DI1` 的真链路,并且带有 operation / audit / evidence 关联,否则只能归类为 support、diagnostics 或 contract。 -`split-brain live` 且 heartbeat/trace 新鲜时,指挥官必须继续监督,不把它当作服务中断。此类状态的优先动作是继续轮询、继续审阅、继续派单,而不是默认 interrupt 或 cancel。`codex submit` 的默认写入确认也遵守同一口径:如果 queue counts 显示 running/queued 非零,但 default summary 不能枚举 active/queued id 列表,CLI 必须返回 `idsUnavailable=true` / `itemsOmitted=true` 和 `stateDisclosure.idsUnavailableMeaning`,而不是输出看起来像“没有 active/queued 任务”的 `items=[]`。需要 raw drill-down 时使用返回的 `queue.listPreviewPolicy.rawCommand`,即 `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview?limit=30 --raw --full`。 +`split-brain live` 且 heartbeat/trace 新鲜时,指挥官必须继续监督,不把它当作服务中断。此类状态的优先动作是继续轮询、继续审阅和在 AgentRun Queue 中派发新任务,而不是默认 interrupt、cancel 或重启旧运行面。旧 `codex task/tasks/output/read/unread/queues` 的 bounded summary 仍用于历史监督;旧 `codex submit` 不再作为新任务写入确认来源。需要 raw drill-down 时使用返回的 `queue.listPreviewPolicy.rawCommand`,即 `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview?limit=30 --raw --full`。 `codex tasks --view commander --limit N` 是指挥官高频轮询入口,必须保持默认 stdout 严格有界。它优先展示 active runner 精确计数与少量 active item、queued/retry_wait count、terminal unread compact counts、关键 risk counts 和 drill-down commands;历史 terminal unread item details、prompt preview、final response preview、trace/output 和 raw overview 不在默认 commander 输出中展开。需要审阅 terminal unread 详情时使用 `bun scripts/cli.ts codex unread --limit N`、`bun scripts/cli.ts codex tasks --unread --view supervisor --limit N`、`bun scripts/cli.ts codex tasks --view full --limit N` 或 per-task `bun scripts/cli.ts codex read `。 @@ -91,16 +91,16 @@ HWLAB M3 口径使用同一分级:只读报告、fixture、LOCAL/DRY-RUN 和 d ## 模型和成本路由 -Code Queue 派单模型按成本、可信度和 blast radius 分层:GPT-5.5/Codex 处理高风险和复杂任务,DeepSeek/OpenCode 处理中等复杂度且边界清晰的任务,MiniMax/OpenCode 处理简单、低权限、可复核任务,生产重启、密钥、数据库手工写入和运行中任务控制保留给指挥官或人工。 +AgentRun 新派单和历史 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 --dry-run` 或 `bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue --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 ` 渐进展开。这些字段写入任务 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 目标。 +当前新任务派发合同由 `bun scripts/cli.ts agentrun v01 queue|sessions` 暴露:`queue commander` 查看 AgentRun 指挥官队列,`queue submit --json-file ` 创建任务,`queue dispatch --json-file ` 派发,`sessions trace/output/read/steer/cancel` 读取和控制 AgentRun session。本地 UniDesk bridge 只把 `--json-file`、`--prompt-file`、`--runner-json-file` 或 `--prompt-stdin` materialize 到 G14 `/root/agentrun-v01` 临时文件,再调用官方 `./scripts/agentrun --manager-url auto`;它不是旧 Code Queue adapter,不做双写,也不迁移旧历史。 +旧 `codex submit/enqueue`、`codex steer`、`codex resume`、旧 queue mutation、task move 和旧 workdir mutation 已冻结。CLI 必须返回 `ok=false`、`frozen=true`、`degradedReason=legacy-code-queue-frozen` 和 AgentRun 替代命令;服务端旧 API 写入口必须返回 410。旧 `codex task/tasks/output/read/unread/queues` 继续作为历史归档和只读排障入口,`codex interrupt|cancel` 只用于停止残留旧任务。 -运行态默认模型仍是 `gpt-5.5`。`CODE_QUEUE_MODELS` 当前长期合同至少包含 GPT-5.5、GPT-5.4、GPT-5.4 Mini、DeepSeek Chat、MiniMax M3 和 MiniMax M2.7 两路并行配置;`deepseek`/`deepseek-chat`、`minimax-m3` 与 `minimax-m2.7` 会走 OpenCode port,其余模型走 Codex port。PROD 集群把 `MINIMAX_MODEL` 切到 `MiniMax-M3`(M3 是新任务的默认 provider model),judge 与 opencode 跟随;M2.7 仍然作为并行配置存在,切换只需把 `MINIMAX_MODEL` 改成 `MiniMax-M2.7` 后 rollout restart。两者不存在自动 fallback 关系:M3 任务失败不会自动改派 M2.7,task 要用 M2.7 必须显式 `--model minimax-m2.7`。只有当执行面 `/health` 或等价配置已经显示 DeepSeek 模型可用、并完成轻量 runner smoke 后,才允许真实提交 `--model deepseek-chat`。 +新任务模型由 AgentRun queue payload 和 AgentRun runtime 配置决定;旧 Code Queue 的 `CODE_QUEUE_MODELS` 只作为历史任务审阅和残留运行面配置参考,长期合同至少包含 GPT-5.5、GPT-5.4、GPT-5.4 Mini、DeepSeek Chat、MiniMax M3 和 MiniMax M2.7 两路并行配置;`deepseek`/`deepseek-chat`、`minimax-m3` 与 `minimax-m2.7` 会走 OpenCode port,其余模型走 Codex port。PROD 集群把 `MINIMAX_MODEL` 切到 `MiniMax-M3`(M3 是新任务的默认 provider model),judge 与 opencode 跟随;M2.7 仍然作为并行配置存在,切换只需把 `MINIMAX_MODEL` 改成 `MiniMax-M2.7` 后 rollout restart。两者不存在自动 fallback 关系:M3 任务失败不会自动改派 M2.7,task 要用 M2.7 必须显式 `--model minimax-m2.7`。只有当执行面 `/health` 或等价配置已经显示 DeepSeek 模型可用、并完成轻量 runner smoke 后,才允许真实提交 `--model deepseek-chat`。 + +`codex prompt-lint [prompt|--prompt-file path|--prompt-stdin]` 仍是派单前的本地 dry-run guardrail,用于检查 runner prompt 是否声明 `DEV test class`、允许的 live mutation、禁止动作和 closeout 字段。它只返回分类、缺失或矛盾项和有界 evidence,不提交任务、不连接 live service、不打印完整 prompt;新派单时由指挥官把 lint 结果纳入 AgentRun `queue submit` payload 审查。 -`codex submit --dry-run` 是派单前的轻量 preflight。它输出 `routingRecommendation`、`policyContract` 和模型注册表,帮助指挥官看到推荐 runner/model、风险信号、缺失的 prompt guard、模型分层、并发上限、`opencodeModels` 和 `modelPorts`;它不会修改真实提交 payload,也不会替代指挥官判断。真实派单是否使用 `--model minimax-m3`、`--model minimax-m2.7`、`--model deepseek-chat` 或 `--model gpt-5.5` 仍由指挥官显式决定。 -`codex prompt-lint [prompt|--prompt-file path|--prompt-stdin]` 是同一套派单前 guardrail 的本地 dry-run 入口,用于检查 runner prompt 是否声明了 `DEV test class`、是否列出允许的 live mutation、禁止动作和 closeout 字段。它只返回分类、缺失或矛盾项和有界 evidence,不提交任务、不连接 live service、不打印完整 prompt。`codex submit --dry-run` 和 `codex steer --dry-run` 会嵌入同一 `promptLint` 结果;`dispatchDisposition=needs-authorization` 时,指挥官必须补齐授权或把 prompt 降到 `read-only` 范围后再派发/steer。 Device Pod 类 DS 派单必须把工具可用性设计进 prompt,而不是靠事后强制纠偏。prompt 应明确唯一 pod、workspace selector、目标工程/target、允许的 live mutation、禁止的 pod/BOOT/生产/密钥/数据库范围和 closeout 字段;文本源码修改默认要求 `hwpod ... workspace apply-patch`,新文件使用 `apply-patch --add-file`,整文件替换使用 `apply-patch --replace-file`,不要优先 `workspace put`。命令入口默认写短别名 `hwpod`,不要写长路径 `node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs`;`hwpod` 必须作为 Code Queue 镜像和 provider dev container 的 PATH 命令存在,并从 `DEVICE_POD_CLI`、`UNIDESK_SKILLS_PATH/device-pod-cli`、当前 workspace 的 `skills/device-pod-cli` 或 `tools/device-pod-cli.mjs` 解析真实 CLI。添加 Keil 源文件、build clean、download、UART/JSON-RPC smoke 也应走 `hwpod`。prompt 中只允许把 `/app/tools/tran.mjs`、`/app/tools/hwlab-gateway-tran.mjs`、临时 Python/PowerShell/JS 上传脚本列为禁止绕行;如果 DS 仍然需要这些绕行,指挥官应先把缺失能力补进 `device-pod-cli`/`hwpod`,再重置 workspace 让 DS 复测。 @@ -226,7 +226,7 @@ GPT-5.5 PR/收口类 prompt 在提交前可先用 host commander 辅助 lint 做 bun scripts/cli.ts commander prompt-lint --kind gpt55-pr --prompt-file /tmp/code-queue-prompt.md ``` -该检查只服务于指挥官补齐派单边界,不是业务 PR 门禁,也不改变 `codex submit` 默认行为。输出只包含 `ok`、`missingClauses`、`riskLevel`、`suggestedPatchSnippet` 和 prompt shape,不回显完整 prompt;`data.ok=false` 表示建议补齐 PR/自合并/rebase/update 授权、artifact build/publish 授权、host-owned DEV rollout、未显式 `ROLLOUT_OK` 时禁止 runner rollout、以及 PROD/secret/DB/破坏性回滚边界。 +该检查只服务于指挥官补齐派单边界,不是业务 PR 门禁;legacy `codex submit` 已冻结,新任务 payload 审查走 AgentRun Queue。输出只包含 `ok`、`missingClauses`、`riskLevel`、`suggestedPatchSnippet` 和 prompt shape,不回显完整 prompt;`data.ok=false` 表示建议补齐 PR/自合并/rebase/update 授权、artifact build/publish 授权、host-owned DEV rollout、未显式 `ROLLOUT_OK` 时禁止 runner rollout、以及 PROD/secret/DB/破坏性回滚边界。 Runner preflight 优先使用执行面诊断入口: @@ -275,11 +275,11 @@ bun scripts/cli.ts codex pr-preflight --remote --issue ### Runner Resume 收口 -PR 小修、冲突、rebase、补测和 reviewer feedback 默认优先 resume 原 Code Queue task,而不是派 replacement runner。正式入口是 `bun scripts/cli.ts codex resume --prompt-file [--resume-id id]`;prompt 仍必须来自位置参数、`--prompt-stdin` 或 `--prompt-file` 三选一,复杂多行内容优先文件或 stdin。真实 resume 成功只应返回 taskId、resumeId/turnId、`deliveryState`、是否复用原 `codexThreadId`/session、有界 trace confirmation 和后续 `codex task/detail/trace/output` 查询命令,不回显完整 prompt。 +PR 小修、冲突、rebase、补测和 reviewer feedback 的新执行入口是 AgentRun Queue/Sessions。仍在 AgentRun session 内的工作优先使用 `bun scripts/cli.ts agentrun v01 sessions steer --prompt-file ` 或 AgentRun reuse/turn 能力;已沉淀成新工作项时使用 `bun scripts/cli.ts agentrun v01 queue submit --json-file `。旧 `codex resume` 已冻结,不再作为 follow-up turn 入口。 -`codex resume` 只用于终态或 awaiting-closeout task。running/judging task 必须结构化失败并提示改用 `codex steer`;不存在 task、不可恢复状态或运行面不支持 follow-up turn 时必须 fail closed,不得伪装成新的 `codex submit`。同一个 `resumeId` 加同 prompt 必须 duplicate-suppressed;同一个 `resumeId` 加不同 prompt 必须 409 conflict;delivery timeout 或确认缺失时输出 `deliveryUnconfirmed` 和确认命令,指挥官先查 trace,再用同一 `resumeId` 重试。 +旧 Code Queue task 只保留历史审阅和残留停止;需要基于旧任务产出继续推进时,在 AgentRun payload 中显式引用旧 task id、PR/branch 和审阅结论,而不是把旧 task 重新入队、resume 或 double-write。 -replacement runner 只用于方向明显错误、质量不可接受、原 task 上下文不可恢复、原分支/PR 已废弃,或 resume 结构化 blocker 已证明无法继续的情况。关闭或替换旧 PR 时必须在 PR/body/final response 中说明 superseded/replacement 关系,避免 competing branch 扩散。 +replacement runner 只用于方向明显错误、质量不可接受、原 task 上下文不可恢复、原分支/PR 已废弃,或 AgentRun reuse/steer 已证明无法继续的情况。关闭或替换旧 PR 时必须在 PR/body/final response 中说明 superseded/replacement 关系,避免 competing branch 扩散。 ## 监控 @@ -288,16 +288,16 @@ replacement runner 只用于方向明显错误、质量不可接受、原 task 常用入口: - `bun scripts/cli.ts codex tasks --view commander --limit N`:host commander 轮询的推荐入口。输出是有界 action map,必须直接显示 `activeRunners.count`、计数来源、split-brain/heartbeat 处置、queued/retry_wait 精确计数、terminal-unread 总数和已省略行数、active 风险数、stale/heartbeat/trace gap、`finalResponse` 已出现但仍非终态的 awaiting terminal/judge、blocker-like final response、HWLAB#7/#99/#116/#164/#317 与 UniDesk#20/#118 命中、任务分类和下一步 drill-down 命令。默认不得输出完整 prompt、完整 final response、raw output、完整 trace 或 raw overview;需要详情只能按 task id 使用 `codex task`、`codex task --trace`、`codex output`、`codex read` 或 `rawOverview` 命令渐进展开。 -- `bun scripts/cli.ts codex tasks --view supervisor --limit N`:查看默认低噪声监督视图,包括 `activeRunning`、running、完成未读、少量最近完成、queued/runnable、activity、commanderConcurrency、execution diagnostics、任务分类和下一步 drill-down 命令。默认行只保留 task id、队列、短 prompt/body 预览和原始字符数;`--limit` 是扫描/分页预算,不是返回几十条肥行的开关,CLI effective limit 安全上限为 100,输出必须用 `filters.requestedLimit`、`filters.effectiveLimit`、`filters.limitCapped`、`source.requestedLimit` 和 `source.effectiveLimit` 区分用户请求、CLI cap 和 overview 源拉取预算;例如 `--limit 260` 应明确显示 requested=260、effective=100、source=200,`running.returned` 只是低噪声返回行数。`show/detail/trace/output/full/read` 放在 section template 中,避免每条任务重复刷屏,需要更多内容再按 taskId 展开。刚执行 `codex submit` 后也可以先读 submit 返回的 `submitted.taskStates[]`、`queue.countContext`、`queue.activity.effectiveActiveTaskCount` 和 `queue.stateDisclosure`;若某个 id preview 有 `idsUnavailable=true`,不要把它当成空队列,按 `queue.listPreviewPolicy.rawCommand` 或本 supervisor 命令继续查。 +- `bun scripts/cli.ts codex tasks --view supervisor --limit N`:查看旧 Code Queue 历史默认低噪声监督视图,包括 `activeRunning`、running、完成未读、少量最近完成、queued/runnable、activity、commanderConcurrency、execution diagnostics、任务分类和下一步 drill-down 命令。默认行只保留 task id、队列、短 prompt/body 预览和原始字符数;`--limit` 是扫描/分页预算,不是返回几十条肥行的开关,CLI effective limit 安全上限为 100,输出必须用 `filters.requestedLimit`、`filters.effectiveLimit`、`filters.limitCapped`、`source.requestedLimit` 和 `source.effectiveLimit` 区分用户请求、CLI cap 和 overview 源拉取预算。新任务派发和 follow-up 不再使用旧 `codex submit/resume/steer`,统一走 AgentRun Queue/Sessions。 - `bun scripts/cli.ts codex queues`:默认是 commander-first 队列态势摘要,`--commander` 是显式同义开关。输出前部固定使用 `.data.queues.commander`,先给出 `activeRunnerCount`、`source`、`target=15`、`slotDeficit`、`queuedCount`、`runningTasks`、`heartbeat.fresh`、`heartbeat.risk`、`heartbeat.staleRecoveryCandidates`、active/runnable queue 小页和 drill-down 命令;历史 queue item 列表保留在 `.data.queues.items[]`,但只是分页的次要行。需要完整队列行视图时加 `--full`,但 `--full` 仍默认分页,继续用 `--limit N`、`--page N` 或 `--offset N` 渐进展开。summary 和 full 都使用稳定 JSON path `.data.queues.items[]` 读取队列行,并从 `.data.queues.commander`、`.data.queues.commanderConcurrency`、`.data.queues.activity`、`.data.queues.counts` 与 `.data.queues.executionDiagnostics` 读取全局活跃计数和执行诊断;完整 upstream 只通过输出中的 raw command 显式获取。若 `/api/queues` 没有返回 task row,`runningTasks.items[].name` 会是 `null` 且 `nameSource=not-returned-by-api-queues`,此时按返回的 `codex task ` 或 supervisor 命令展开,不要假设任务没有名称。 -- `bun scripts/cli.ts codex execution-plane [--full|--raw]`:只读巡检 D601 原生 k3s `unidesk` namespace 下的正式执行面。该命令的 live collector 必须通过 UniDesk `tran`/`ssh` 维护桥访问 `D601:k3s` 和 `D601:/home/ubuntu/cq-deploy`,不得在 master server 本地调用 `kubectl`、读取本地 worktree 或把 master server 的工具缺失误报成 D601 阻塞。该命令强制使用 `KUBECONFIG=/etc/rancher/k3s/k3s.yaml` 并确认 node `d601`,默认低噪声返回 `summary.formalExecutionPlane`、`summary.deploymentDrift`、`summary.deprecatedComposeResidual`、`executionPlane.deployments[]`、`drift.status` 和 `residual.status`。它比较三类 Deployment:`code-queue` 必须是 scheduler,`code-queue-read` 必须是 read,`code-queue-write` 必须是 write;同时比较 deployment env/annotation commit、Pod `imageID` digest 和宿主 `/home/ubuntu/cq-deploy` HEAD。任何 commit/digest/worktree 不一致或缺少可比 marker 都必须输出 `deployment-drift`,不能写成 healthy。检测到 D601 上旧 Docker Compose `code-queue-backend` 或旧 `127.0.0.1:4222` 监听时必须输出 `deprecated-compose-residual`。该命令不再把 `/api/judge/probe` 或 behaviorVersion 作为门禁;Code Queue runner 能力必须用真实 `codex submit`/任务审阅和人工交互命令在目标运行面闭环验收。默认不打印完整 Kubernetes Deployment JSON、环境变量全集、SecretRef 值或命令 stdout;需要逐项展开时使用 `--full`,需要安全裁剪后的原始观察对象时使用 `--raw`。 +- `bun scripts/cli.ts codex execution-plane [--full|--raw]`:只读巡检 D601 原生 k3s `unidesk` namespace 下的旧 Code Queue 执行面。该命令用于历史归档和残留任务诊断,不作为新任务能力门禁;新任务真实运行面验收走 AgentRun `v0.1` queue/session 原入口。默认不打印完整 Kubernetes Deployment JSON、环境变量全集、SecretRef 值或命令 stdout;需要逐项展开时使用 `--full`,需要安全裁剪后的原始观察对象时使用 `--raw`。 - `bun scripts/cli.ts codex unread --limit N`:查看完成未读审阅积压的默认 triage,按 repo、issue、status 和 queue 汇总,并给出有界最新任务紧凑行;默认行只包含 task id、状态、queue、issues、updatedAt/finishedAt 和一条 `nextStep`,不重复每任务 `show/detail/trace/output/read` 命令,也不输出 raw prompt、final response、trace 或 output。完整 per-task 命令必须显式使用 `codex unread --full`、`codex unread --view full`、`codex unread list` 或单任务 `codex task `/`codex read ` 展开;默认输出必须保留一次性的模板命令和分页命令。 - `bun scripts/cli.ts codex unread mark-read --repo owner/name --issue N --limit N --confirm`:批量已读入口,必须显式 `mark-read` 和 `--confirm`,否则结构化失败且不 POST `/read`。 - `bun scripts/cli.ts codex tasks --unread --limit N`:兼容查看完成未读审阅积压;`--unread` 与 `--unread-only` 等价,不能被静默忽略。 - `bun scripts/cli.ts codex tasks --status succeeded --unread --limit N`:按具体终态过滤监督结果;不支持的 status filter 必须显式失败,不能扩大为未过滤结果。 - `bun scripts/cli.ts codex task `:默认只查看原始 prompt、最终 response、最后错误和 drill-down 命令,这是完成未读任务审阅的第一步。 - 当默认审阅摘要不足时,再逐级使用 `bun scripts/cli.ts codex task --detail`、`bun scripts/cli.ts codex task --trace --limit N` 或 `codex output`。 -- `bun scripts/cli.ts codex resume --prompt-file `:对已终态或 awaiting-closeout 的原 task 追加后续修正 turn,适合 PR 小修、冲突、rebase、补测和 review 修正;running/judging task 改用 `codex steer`。 +- `bun scripts/cli.ts agentrun v01 sessions steer --prompt-file `:对 AgentRun 中仍可继续的 session 追加修正;旧 `codex resume` 已冻结。 - 当 master 控制面状态和 D601 scheduler 状态看起来分裂时,使用 `docs/reference/observability.md` 中的活性规则判断。 默认 commander/supervisor 视图必须保持低噪声。commander 视图用于回答“现在需要处理什么”,supervisor 视图用于看分区小页和红线细节。commander 的 `activeRunners.count` 是指挥官 active runner 计数,supervisor 的 `activeRunning.count` 是 running+judging 状态计数;两者都必须标明 exact/source,不能把返回行数当成并发总数。`activeRunning.count` 来源是 queue summary 的 status counts 时 `activeRunning.exact=true`,用于 redline 判断;`activeRunning.rowPage.returned` / `running.returned` 只表示本次返回的紧凑任务行。`activeRunning.redline` 必须写明 `countField`、routine target、burst redline、hard redline、`state` 和 `decisionReady`;只有 `decisionReady=true` 时,才能直接用该 count 做红线/补派判断。commander 的 `attention.items` 只返回最需要处理的有界任务,`attention.total/returned/omitted` 必须保留省略计数;`sections.recentCompleted` 不得重复 `sections.terminalUnread` 的未读终态。`running`、`completedUnread` 和 `queued` 即使传入较大的 `--limit`,默认也只返回一个很小的有界页,并通过 section `commands.next` 继续分页;`--limit` 保留为扫描/分页预算和 full view 返回预算,不得让一次 commander/supervisor 调用输出几十条肥行。每个任务行只应带 task id 和必要摘要,`show`、`detail`、`trace`、`output`、`full`、`read` 使用 section template 或 row commands 表达,让下一步渐进披露动作明确且不重复;默认不得嵌入完整 queue 列表、完整 final response、raw output 页或完整 trace 行。`recentCompleted` 必须默认限量,且不得重复 `completedUnread` 里的未读终态,避免完成历史把当前 running、阻塞和未读审阅挤出视野;需要完整当前页时显式使用 `--view full`。`executionDiagnostics` 只能展示有界 task-id/reason 预览、总数、截断标记和 omitted counts;需要全量诊断时使用输出中的 raw command。`commands.read` 只是在人工审阅后的建议命令,listing 命令绝不能自动执行。 @@ -312,7 +312,7 @@ commander 视图的任务分类必须是确定性字段,至少区分 `user-fac 队列诊断中的 `split-brain` 表示控制面/执行面观测分裂,不自动证明任务已经死亡。只要任务 heartbeat 还在刷新、trace 仍在推进,就不能把它判成服务中断或要求立刻 stop;应把它视为 `splitBrainLive=true` 的 live 任务,继续监督并推进 #20 里的已排任务,而不是 interrupt、替换或把 backend 当成已经挂掉。队列摘要应显示 `effectiveLiveness=live`、`splitBrainLive=true` 和 `recommendedAction=continue-supervision`;compact 输出还应在 `executionDiagnostics.liveness` 中重复这些低噪声字段,并突出 `activeHeartbeatCount`、有界 `heartbeatFreshTaskIds`、`databaseActiveTaskCount` 和 `schedulerActiveRunSlotCount`。当 master/control-plane 的 `schedulerActiveRunSlotCount=0` 但 `heartbeatFreshTaskIds` 非空时,active 数应优先按 scheduler heartbeat 摘要解释为 live,而不是按 master 本地 slot 0 解释为执行停摆。只有 heartbeat expired/missing 或满足 stale-recovery 条件时,才应显示 `effectiveLiveness=at-risk` 并进入恢复判断。 -`codex submit` 成功后的 `queue` 是 submit-confirmation 即时 bounded snapshot,不是恢复判据。它必须同时区分 `submitted.taskStates[]` 中本次提交任务的 queued/running 状态、`queue.countContext.databaseActive` / `activity.databaseActiveTaskCount` 中 PostgreSQL active running、`activity.schedulerHeartbeatFreshness` 中 scheduler heartbeat freshness,以及 `activity.recovery` 中的 transient risk。若同一个提交回显里出现 `counts.running>0`、`queued>0`、`heartbeatRiskTaskCount>0` 或 `staleRecoveryCandidateTaskCount>0`,但 `lastObservedAgentEventAt` 明显早于 submit time 或还没有下一次 supervisor poll 确认,输出应保留候选可见性并给出 `re-poll supervisor before recovery`;这类单次 bounded snapshot 只能设置 `attentionRequired=true`,不得把 `commanderConcurrency.interventionRequired` 直接升级为高风险恢复。默认 drill-down 是重新运行 `codex tasks --view supervisor --limit 20` 或 raw overview,而不是 restart、cancel、interrupt 或 DB write。 +旧 Code Queue 的 bounded snapshot 只作为历史监督证据,不再作为新派单或恢复判据。新任务派发后应通过 `bun scripts/cli.ts agentrun v01 queue commander --reader-id `、`queue show`、`sessions trace` 和 `sessions output` 观察 AgentRun 队列与 session。 默认 supervisor poll 也遵循同一低噪声语义:heartbeat expired/missing、`heartbeatRiskTaskIds` 和 `staleRecoveryCandidateTaskIds` 必须可见,但第一次 poll 只表示 `transient-needs-repoll`,`activity.recovery.hint` 应为 `re-poll supervisor before recovery`。只有 repeated poll 仍确认 owner heartbeat expired、scheduler local no active run、database-active task 仍存在,并且输出显式带 `repeatedPollConfirmed=true` 或 confirmed stale candidate,才允许进入 bounded dry-run reconcile;真实恢复仍受高风险边界约束。 @@ -356,7 +356,7 @@ D601 artifact registry 的 systemd unit inactive 不等于 D601 全局离线。 每次新增 Code Queue 任务、补发 follow-up task,或处理一批完成未读任务后,都必须同步更新 GitHub 总看板 issue `#20` 的正文主表;如果发生实质态势变化,还要同步更新指挥简报 issue `#24` 的正文。看板更新应反映当前任务分布、关键 blocker 和粗略进度,不要只改聊天上下文或只改单个 issue,而让总态势图落后于实际调度状态。 -成本路由接入 #20 看板时,#20 的每个 Code Queue 任务行都应显式保留推荐或实际 runner/model、风险等级、验证证据和审阅状态。推荐列的语义只来自指挥官判断或 `codex submit --dry-run` 的 `routingRecommendation`,不能来自 worker 自评。MiniMax 和 DeepSeek 任务在 #20 中必须先保持“待指挥官审阅”状态;只有指挥官核验 diff、commit、轻量验证和未越界后,才更新为已验收并执行 `codex read `。若 dry-run 给出 `commander-human-only` 或 GPT-5.5 recommendation,#20 不应把该任务降级给 MiniMax/DeepSeek;若必须降级,必须在看板 progress 中写明人工接受的风险和额外审阅动作。 +成本路由接入 #20 看板时,#20 的每个 AgentRun/历史 Code Queue 任务行都应显式保留推荐或实际 runner/model、风险等级、验证证据和审阅状态。推荐列的语义来自指挥官判断或 AgentRun queue payload 审查,不能来自 worker 自评。MiniMax 和 DeepSeek 任务在 #20 中必须先保持“待指挥官审阅”状态;只有指挥官核验 diff、commit、轻量验证和未越界后,才更新为已验收并执行对应的 AgentRun/历史 read 标记。 ## 指挥工作流 @@ -376,10 +376,10 @@ D601 artifact registry 的 systemd unit inactive 不等于 D601 全局离线。 只有存在明确理由时才干预。 - 如果任务还在运行且 trace 或 scheduler heartbeat 新鲜,应引导而不是 interrupt。 -- 对运行中任务的引导应优先使用正式 CLI:`bun scripts/cli.ts codex steer --prompt-file `。该命令和 `codex task/tasks/read` 复用同一个 backend-core stable proxy helper;`--dry-run` 会显示 `method/path/stableProxyPath`、`steerId`、retry policy、prompt 摘要、`commands.traceConfirm` 和 raw proxy 等价命令但不发送。非 dry-run 默认对 `stable-proxy-failed` 和 `backend-core-unreachable` 做一次有界重试,且每次 retry 都复用同一个 `steerId`;后端按同 `steerId`+同 prompt 抑制重复 trace 注入,返回 `duplicateSuppressed=true`,同 `steerId` 搭配不同 prompt 返回 conflict。 +- 对 AgentRun 运行中 session 的引导应优先使用正式 CLI:`bun scripts/cli.ts agentrun v01 sessions steer --prompt-file `,再用 `sessions trace/output/read` 确认。旧 `codex steer` 已冻结,只保留历史 trace confirmation 查询。 - 真实 steer 输出必须保持低噪声:成功显示 `steer.status`、`steer.deliveryState`、`steer.steerId`、有界 `traceConfirmation` 和后续命令,不回显 prompt 或完整 task state;失败默认不带 request body、不带 upstream body preview,也不带 raw response,需要上游预览或原始失败对象时显式重跑 `--full` 或 `--raw`。`deliveryState=accepted` 表示 backend 已接受;`not_accepted` 表示任务状态/权限/输入未接受;`accepted_response_timeout` 表示 stable proxy 响应超时但 trace confirmation 找到该 `steerId`;`unknown` 表示响应路径失败且确认查询仍未证明接受。 -- `502 provider HTTP tunnel failed`、`The operation was aborted`、约 30 秒 provider tunnel wait abort 仍失败时,先看 `.data.diagnostics.reason`、`.data.diagnostics.attempts`、`.data.traceConfirmation` 和 `.data.commands.traceConfirm`。若 `.data.steer.deliveryUnconfirmed=true`,不要立刻复制同一纠偏 prompt 再发;先运行 `bun scripts/cli.ts codex steer-confirm --steer-id `。如果确认 accepted 或 accepted_response_timeout,停止重发并继续观察 `codex task ` / `codex task --trace --tail --limit 80`;如果仍 unknown,再用输出中的 retry 命令复用同一个 `--steer-id`。raw proxy 等价命令走同一条 tunnel,`rawProxyEquivalentIsFallback=false`,只能做诊断对照,不应作为正式 fallback。 -- 失败分流仍以 `.data.diagnostics.reason` 为准:`backend-core-unreachable` 属于本机到 core 的观察路径,`code-queue-microservice-unregistered`/`proxy-unauthorized`/`proxy-404` 属于 stable proxy 配置或权限,`steer-endpoint-404`/`upstream-runtime-rejected` 属于 D601 runtime 或任务状态,`stable-proxy-failed` 多为 provider/k3s/tunnel 链路问题。若任务已终态,CLI 返回紧凑 `task-already-terminal` 响应并给出 `bun scripts/cli.ts codex task `、`bun scripts/cli.ts codex read ` 和 `bun scripts/cli.ts codex submit --prompt-file --reference-task-id `;指挥官应提交 follow-up task,而不是继续 steer 终态任务。若正式 CLI 自身不可用,临时通过受控 microservice proxy 调用只能作为现场恢复手段;这类绕行必须记录到指挥简报 issue #24 主体的常驻观察,并创建正式 issue 补齐 CLI 能力,避免长期依赖隐式 API。 +- 旧 Code Queue 的 provider tunnel 失败只作为历史运行面诊断线索;新任务控制面失败优先按 AgentRun `queue show`、`sessions trace/output`、G14 `agentrun-v01` manager 和 runner job 证据分流。 +- 新 AgentRun 任务失败分流以 AgentRun queue/session/runner-job 返回字段为准。旧 Code Queue `.data.diagnostics.reason` 只用于历史任务和残留运行面,不再引导新 `codex submit/steer/resume`。 - 如果任务进入终态但缺少必要验收证据,应使用聚焦 continuation prompt retry 同一任务。 - 如果任务被可复用基础设施缺陷阻塞,应把该缺陷分配给合适的空闲或低风险队列,让原业务任务等待,或在修复后 retry。 - 如果基础设施缺陷影响 Code Queue 控制面可用性,指挥官可以执行恢复队列所需的最小受控部署,然后验证原任务能继续。 diff --git a/scripts/src/agentrun.ts b/scripts/src/agentrun.ts index bdfa082f..ce7ed338 100644 --- a/scripts/src/agentrun.ts +++ b/scripts/src/agentrun.ts @@ -1,3 +1,4 @@ +import { readFileSync } from "node:fs"; import type { UniDeskConfig } from "./config"; import { runSshCommandCapture, type SshCaptureResult } from "./ssh"; import { startJob } from "./jobs"; @@ -22,9 +23,17 @@ const mirrorToolsImage = "127.0.0.1:5000/hwlab/hwlab-ci-node-tools:node22-alpine export function agentRunHelp(): unknown { return { - command: "agentrun v01 control-plane status|trigger-current|refresh|cleanup-runs|cleanup-released-pvs | git-mirror status|sync|flush", + command: "agentrun v01 control-plane status|trigger-current|refresh|cleanup-runs|cleanup-released-pvs | git-mirror status|sync|flush | queue submit|list|show|stats|commander|read|cancel|dispatch|refresh | sessions ps|show|turn|steer|cancel|output|trace|read", output: "json", usage: [ + "bun scripts/cli.ts agentrun v01 queue commander --reader-id cli", + "bun scripts/cli.ts agentrun v01 queue submit --json-file ", + "bun scripts/cli.ts agentrun v01 queue dispatch --json-file ", + "bun scripts/cli.ts agentrun v01 queue cancel --reason ", + "bun scripts/cli.ts agentrun v01 sessions trace --after-seq 0 --limit 100", + "bun scripts/cli.ts agentrun v01 sessions output --after-seq 0 --limit 100", + "bun scripts/cli.ts agentrun v01 sessions steer --prompt-file ", + "bun scripts/cli.ts agentrun v01 sessions read --reader-id cli", "bun scripts/cli.ts agentrun v01 control-plane status", "bun scripts/cli.ts agentrun v01 control-plane status --full", "bun scripts/cli.ts agentrun v01 control-plane trigger-current --dry-run", @@ -40,7 +49,7 @@ export function agentRunHelp(): unknown { "bun scripts/cli.ts agentrun v01 git-mirror sync --confirm", "bun scripts/cli.ts agentrun v01 git-mirror flush --confirm", ], - description: "Operate AgentRun v0.1 Tekton/Argo control plane and devops-infra git mirror through G14 routes; status is read-only, trigger-current pre-syncs mirror refs before creating the PipelineRun, and cleanup-runs/cleanup-released-pvs provide controlled completed CI workspace retention.", + description: "Operate AgentRun v0.1 Queue and Sessions through the official G14 /root/agentrun-v01 CLI, plus bounded Tekton/Argo control-plane and devops-infra git mirror actions through UniDesk routes. Queue/session commands are direct AgentRun CLI calls, not a UniDesk Code Queue adapter or double-write path.", }; } @@ -62,6 +71,9 @@ export async function runAgentRunCommand(config: UniDeskConfig, args: string[]): return await runGitMirrorJob(config, action, options); } } + if (group === "queue" || group === "sessions") { + return await runOfficialAgentRunCli(config, group, args.slice(2)); + } return unsupported(args); } @@ -1420,6 +1432,144 @@ function startAsyncAgentRunJob(name: string, command: string[], note: string): R }; } +interface AgentRunCliMaterializedFile { + flag: string; + source: string; + remotePath: string; + bytes: number; + base64: string; +} + +interface PreparedAgentRunCliArgs { + args: string[]; + materializedFiles: AgentRunCliMaterializedFile[]; +} + +async function runOfficialAgentRunCli(config: UniDeskConfig, group: "queue" | "sessions", args: string[]): Promise> { + const prepared = prepareOfficialAgentRunCliArgs([group, ...args]); + const command = `agentrun v01 ${prepared.args.join(" ")}`.trim(); + const bridge = agentRunQueueBridgeMetadata(prepared.materializedFiles); + const script = officialAgentRunCliScript(prepared); + const result = await capture(config, g14SourceRoute, ["script", "--", script]); + const payload = captureJsonPayload(result); + if (result.exitCode === 0 && Object.keys(payload).length > 0) { + return { + ...payload, + command, + bridge, + }; + } + if (result.exitCode === 0) { + return { + ok: true, + command, + bridge, + stdout: result.stdout.trim(), + remote: compactCapture(result, { full: true, stdoutTailChars: 4000, stderrTailChars: 2000 }), + }; + } + return { + ok: false, + command, + degradedReason: "agentrun-cli-bridge-failed", + bridge, + agentrun: payload, + remote: compactCapture(result, { full: true, stdoutTailChars: 8000, stderrTailChars: 4000 }), + }; +} + +function prepareOfficialAgentRunCliArgs(args: string[]): PreparedAgentRunCliArgs { + const fileFlags = new Set(["--json-file", "--prompt-file", "--runner-json-file"]); + const remoteTmpDir = `/tmp/unidesk-agentrun-cli-${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2)}`; + const materializedFiles: AgentRunCliMaterializedFile[] = []; + const prepared: string[] = []; + let stdinBuffer: Buffer | null = null; + + const stdin = (): Buffer => { + if (stdinBuffer === null) stdinBuffer = readFileSync(0); + return stdinBuffer; + }; + + const materialize = (flag: string, source: string, buffer: Buffer): string => { + const remotePath = `${remoteTmpDir}/input-${materializedFiles.length}`; + materializedFiles.push({ + flag, + source, + remotePath, + bytes: buffer.length, + base64: buffer.toString("base64"), + }); + return remotePath; + }; + + for (let i = 0; i < args.length; i += 1) { + const arg = args[i] ?? ""; + const equalsFlag = Array.from(fileFlags).find((flag) => arg.startsWith(`${flag}=`)); + if (equalsFlag !== undefined) { + const source = arg.slice(equalsFlag.length + 1); + if (source.length === 0) throw new Error(`${equalsFlag} requires a path`); + const buffer = source === "-" ? stdin() : readFileSync(source); + prepared.push(equalsFlag, materialize(equalsFlag, source === "-" ? "stdin" : source, buffer)); + continue; + } + if (fileFlags.has(arg)) { + const source = args[i + 1]; + if (source === undefined || source.length === 0) throw new Error(`${arg} requires a path`); + const buffer = source === "-" ? stdin() : readFileSync(source); + prepared.push(arg, materialize(arg, source === "-" ? "stdin" : source, buffer)); + i += 1; + continue; + } + if (arg === "--prompt-stdin" || arg === "--stdin") { + prepared.push("--prompt-file", materialize("--prompt-stdin", "stdin", stdin())); + continue; + } + prepared.push(arg); + } + + return { args: prepared, materializedFiles }; +} + +function officialAgentRunCliScript(prepared: PreparedAgentRunCliArgs): string { + const setup = prepared.materializedFiles.length === 0 + ? [] + : [ + `tmp_dir=${shQuote(parentDir(prepared.materializedFiles[0]?.remotePath ?? "/tmp/unidesk-agentrun-cli"))}`, + "mkdir -p \"$tmp_dir\"", + "trap 'rm -rf \"$tmp_dir\"' EXIT", + ...prepared.materializedFiles.map((file) => `printf %s ${shQuote(file.base64)} | base64 -d > ${shQuote(file.remotePath)}`), + ]; + return [ + "set -euo pipefail", + ...setup, + "cd /root/agentrun-v01", + `./scripts/agentrun --manager-url auto ${prepared.args.map(shQuote).join(" ")}`, + ].join("\n"); +} + +function parentDir(pathValue: string): string { + const index = pathValue.lastIndexOf("/"); + return index > 0 ? pathValue.slice(0, index) : "."; +} + +function agentRunQueueBridgeMetadata(materializedFiles: AgentRunCliMaterializedFile[]): Record { + return { + route: g14SourceRoute, + sourceWorktree: "/root/agentrun-v01", + sourceBranch, + managerUrl: "auto", + officialCli: "./scripts/agentrun", + mode: "direct-official-cli", + compatibility: "no-code-queue-adapter-no-double-write", + fileFlagsMaterialized: materializedFiles.map((file) => ({ + flag: file.flag, + source: file.source, + remotePath: file.remotePath, + bytes: file.bytes, + })), + }; +} + async function capture(config: UniDeskConfig, target: string, args: string[]): Promise { return await runSshCommandCapture(config, target, args); } @@ -1568,6 +1718,6 @@ function unsupported(args: string[]): Record { ok: false, command: `agentrun ${args.join(" ")}`.trim(), degradedReason: "unsupported-command", - message: "supported commands: agentrun v01 control-plane status|trigger-current|refresh; agentrun v01 git-mirror status|sync|flush", + message: "supported commands: agentrun v01 queue submit|list|show|stats|commander|read|cancel|dispatch|refresh; agentrun v01 sessions ps|show|turn|steer|cancel|output|trace|read; agentrun v01 control-plane status|trigger-current|refresh; agentrun v01 git-mirror status|sync|flush", }; } diff --git a/scripts/src/code-queue.ts b/scripts/src/code-queue.ts index 7cd40f91..68712a89 100644 --- a/scripts/src/code-queue.ts +++ b/scripts/src/code-queue.ts @@ -64,6 +64,16 @@ const defaultSteerRetryDelayMs = 750; const maxSteerRetryDelayMs = 5_000; const codexTaskStatuses = ["queued", "running", "judging", "retry_wait", "succeeded", "failed", "canceled"] as const; const codexTerminalTaskStatuses = ["succeeded", "failed", "canceled"] as const; +const agentRunQueueReplacementCommands = { + queueCommander: "bun scripts/cli.ts agentrun v01 queue commander --reader-id cli", + queueSubmit: "bun scripts/cli.ts agentrun v01 queue submit --json-file ", + queueDispatch: "bun scripts/cli.ts agentrun v01 queue dispatch --json-file ", + sessionsTrace: "bun scripts/cli.ts agentrun v01 sessions trace --after-seq 0 --limit 100", + sessionsOutput: "bun scripts/cli.ts agentrun v01 sessions output --after-seq 0 --limit 100", + sessionsSteer: "bun scripts/cli.ts agentrun v01 sessions steer --prompt-file ", + sessionsRead: "bun scripts/cli.ts agentrun v01 sessions read --reader-id cli", + queueCancel: "bun scripts/cli.ts agentrun v01 queue cancel --reason ", +}; const codexTaskStatusAliases: Record = { completed: "succeeded", complete: "succeeded", @@ -87,6 +97,31 @@ const codexTaskStatusAliases: Record = { "retry-wait": "retry_wait", }; +function legacyCodeQueueFrozenMutation(command: string): Record { + return { + ok: false, + frozen: true, + mutation: false, + degradedReason: "legacy-code-queue-frozen", + error: "旧 UniDesk Code Queue 新任务和执行写入口已冻结;新任务使用 AgentRun Queue。", + command, + replacement: agentRunQueueReplacementCommands, + legacy: { + retainedFor: "historical archive/read-only troubleshooting; no new old-runtime work", + allowed: [ + "bun scripts/cli.ts codex tasks", + "bun scripts/cli.ts codex task ", + "bun scripts/cli.ts codex output ", + "bun scripts/cli.ts codex unread", + "bun scripts/cli.ts codex read ", + "bun scripts/cli.ts codex queues", + "bun scripts/cli.ts codex interrupt ", + ], + noDoubleWrite: true, + }, + }; +} + interface CodexTaskOptions { detail: boolean; trace: boolean; @@ -8366,7 +8401,7 @@ export async function runCodeQueueCommand(config: UniDeskConfig, args: string[]) return codexPromptLintTask(args.slice(1)); } if (action === "submit" || action === "enqueue") { - return codexSubmitTask(args.slice(1)); + return legacyCodeQueueFrozenMutation(`codex ${action}`); } if (action === "task" || action === "summary" || action === "show") { const taskId = requireTaskId(taskIdArg, `codex ${action}`); @@ -8401,27 +8436,23 @@ export async function runCodeQueueCommand(config: UniDeskConfig, args: string[]) if (action === "queue") { const sub = taskIdArg ?? "list"; if (sub === "list") return codeQueues(args.slice(2)); - if (sub === "create") return codexCreateQueue(requireQueueId(args.slice(2), "queue create")); + if (sub === "create") return legacyCodeQueueFrozenMutation("codex queue create"); if (sub === "merge") { - const mergeArgs = args.slice(2); - return codexMergeQueue(requireMergeSourceQueueId(mergeArgs, "queue merge"), requireMergeTargetQueueId(mergeArgs, "queue merge")); + return legacyCodeQueueFrozenMutation("codex queue merge"); } } if (action === "move") { - const taskId = requireTaskId(taskIdArg, "codex move"); - return codexMoveTask(taskId, requireQueueId(args.slice(2), "codex move")); + return legacyCodeQueueFrozenMutation("codex move"); } if (action === "interrupt" || action === "cancel") { const taskId = requireTaskId(taskIdArg, `codex ${action}`); return codexInterruptTask(taskId); } if (action === "steer") { - const taskId = requireTaskId(taskIdArg, "codex steer"); - return codexSteerTask(taskId, args.slice(2)); + return legacyCodeQueueFrozenMutation("codex steer"); } if (action === "resume") { - const taskId = requireTaskId(taskIdArg, "codex resume"); - return codexResumeTask(taskId, args.slice(2)); + return legacyCodeQueueFrozenMutation("codex resume"); } if (action === "steer-confirm" || action === "steer-confirmation") { const taskId = requireTaskId(taskIdArg, `codex ${action}`); diff --git a/scripts/src/help.ts b/scripts/src/help.ts index 87014453..f6485b38 100644 --- a/scripts/src/help.ts +++ b/scripts/src/help.ts @@ -61,14 +61,14 @@ export function rootHelp(): unknown { { command: "commander contract|plan --dry-run|smoke --dry-run|approval request --dry-run|prompt-lint --kind gpt55-pr", description: "Host Codex commander skeleton contract, no-daemon smoke plan, dry-run approval preview, and advisory GPT-5.5 PR prompt boundary lint without live bridges, message sends, or submit gating." }, { command: "hwlab nodes control-plane|git-mirror|secret --node G14 --lane v03", description: "Manage HWLAB node/lane runtime prerequisites for v0.3+ with the node identity passed as data instead of a command family." }, { command: "hwlab g14 monitor-prs | hwlab g14 control-plane status|apply|trigger-current|runtime-migration|cleanup-runs|cleanup-released-pvs | hwlab g14 git-mirror status|apply|sync|flush | hwlab g14 tools-image status|build", description: "Start the legacy G14 PR monitor, run bounded v0.2 Tekton/Argo control-plane, manual PipelineRun trigger, runtime migration, CI workspace retention, manual devops-infra git mirror/relay maintenance, or fixed HWLAB CI tools image actions; long confirmed trigger/sync/flush actions return async jobs by default." }, - { command: "agentrun v01 control-plane status|trigger-current|refresh|cleanup-runs|cleanup-released-pvs", description: "Run bounded AgentRun v0.1 Tekton/Argo status, manual PipelineRun trigger, Argo refresh, and completed CI workspace retention through UniDesk G14 routes." }, + { command: "agentrun v01 queue|sessions|control-plane|git-mirror", description: "Use AgentRun v0.1 Queue and Sessions as the active commander entry through the official G14 CLI bridge, plus bounded Tekton/Argo and git-mirror operations." }, { command: "hwlab cd audit --env dev | hwlab cd status --env dev | hwlab cd apply --env dev --dry-run", description: "Legacy D601 HWLAB DEV CD wrapper kept for explicit old-path diagnostics; current HWLAB rollout uses G14 GitOps." }, { command: "code-agent-sandbox", description: "Independent Code Agent Sandbox service skeleton for adapter, mode, and credential-boundary diagnostics." }, { command: "schedule list|get|runs|run|retry-run|delete", description: "Manage backend-core scheduled tasks and run history; schedule run supports --wait-ms N and retry-run reuses the failed run's schedule." }, { command: "schedule upsert-pgdata-backup [--time HH:MM] [--remote-base /SERVER_DATA/UNIDESK_PG_DATA]", description: "Create or update the daily PGDATA physical backup task that uploads monthly rotated archives to Baidu Netdisk." }, { command: "codex deploy [--provider-id D601] [--timeout-ms N]", description: "Disabled legacy Code Queue deploy path; use the dev-only artifact consumer instead." }, { command: "codex prompt-lint [prompt|--prompt-file path|--prompt-stdin]", description: "Dry-run lint a runner prompt for DEV test class read-only/live-read/live-mutating authorization without echoing prompt text or touching live services." }, - { command: "codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue queueId] [--provider-id id] [--cwd path] [--model model] [--execution-mode mode] [--max-attempts N] [--reference-task-id id] [--dry-run]", description: "Submit a Code Queue task through backend-core -> code-queue proxy; --dry-run shows the structured request, routing recommendation, and prompt live-authorization lint while real success only confirms the write and task id." }, + { command: "codex submit|steer|resume|queue create|queue merge|move", description: "Frozen legacy Code Queue write commands; use agentrun v01 queue and agentrun v01 sessions for new commander work. Historical codex task/tasks/output/read/unread/queues remain available for archive troubleshooting." }, { command: "codex skills-sync --dry-run [--full]", description: "Inspect the controlled runner skills hostPath lifecycle contract without copying files, restarting services, reading secrets, or mutating live runner paths." }, { command: "codex execution-plane [--full|--raw]", description: "Read-only D601 native k3s Code Queue execution-plane inspection; compares formal deployments, deprecated Compose residuals, commit markers, pod digest, and mounted worktree HEAD." }, { command: "codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/] [--pr-create-dry-run --pr-create-dry-run-head ] [--issue N] [--full|--raw]", description: "Read-only PR admission check with compact commander output by default; use --full or --raw to expand the full runtime preflight, tool, and observation payload." }, @@ -79,11 +79,10 @@ export function rootHelp(): unknown { { command: "codex read ", description: "Mark one reviewed terminal task read and return terminal metadata plus final response; prompt/tool logs stay behind drill-down commands." }, { command: "codex dev-ready", description: "Fetch execution-container readiness, including sanitized skill injection status from /api/dev-ready." }, { command: "codex judge --attempt N [--dry-run] [--include-prompt]", description: "Replay one stored Code Queue attempt through the same judge context builder and MiniMax judge call path used by the live queue worker." }, - { command: "codex steer [prompt|--prompt-file path|--prompt-stdin] [--steer-id id] [--dry-run] [--no-retry|--retry-attempts N] [--full|--raw]", description: "Push a corrective prompt into a running Code Queue task with a steerId/idempotency key; retryable tunnel aborts get bounded trace confirmation before any retry guidance." }, - { command: "codex resume [prompt|--prompt-file path|--prompt-stdin] [--resume-id id] [--dry-run] [--full|--raw]", description: "Queue a follow-up turn on a terminal Code Queue task, preserving the original task/thread/PR context and suppressing duplicate resumeId delivery without echoing the prompt." }, + { command: "codex steer / codex resume ", description: "Frozen legacy execution mutation entries; use agentrun v01 sessions steer/read/cancel/output/trace against AgentRun sessions instead." }, { command: "codex steer-confirm --steer-id [--raw]", description: "Read-only lookup for a steerId in task trace so deliveryUnconfirmed can be resolved without resending the corrective prompt." }, { command: "codex interrupt|cancel ", description: "Request interrupt for a running Code Queue task, or cancel a queued/retry_wait task, through the same private proxy." }, - { command: "codex (queues [--full|--all] | queue create | queue merge --into | move --queue )", description: "List low-noise queue summaries by default, including effective activity counts that distinguish scheduler-local queues, DB running tasks, and heartbeat-fresh runners; full queue rows require --full/--all." }, + { command: "codex queues [--full|--all] [--limit N] [--page N|--offset N]", description: "Read legacy Code Queue archive summaries. Legacy queue create/merge and move are frozen; use agentrun v01 queue for new work." }, { command: "job list [--limit N] [--include-command]", description: "List async jobs from .state/jobs with a bounded default page and progress summaries." }, { command: "job status [--tail-bytes N]", description: "Show job state with a structured progress summary and bounded stdout/stderr tails." }, { command: "job cancel ", description: "Cancel a queued/running async job through the .state/jobs control entry and keep a terminal canceled record." }, @@ -350,7 +349,7 @@ function commanderHelp(): unknown { "dry-run commands never open SSH, PTY, or stdio bridges", "high-risk actions only produce a <=200 char Chinese ClaudeQQ approval draft and notification-path-unavailable blocker", "authorized future sends must use backend-core /api/microservices/claudeqq/proxy, not local skill or powershell paths", - "prompt-lint is commander advisory output and does not change codex submit default behavior", + "prompt-lint is commander advisory output for AgentRun payload review; legacy codex submit is frozen", "token and secret values must never be printed", ], promptLint: { @@ -389,9 +388,10 @@ function codexHelp(): unknown { usage: [ "bun scripts/cli.ts codex deploy # disabled legacy deployment entry", "bun scripts/cli.ts codex prompt-lint [prompt|--prompt-file path|--prompt-stdin]", - "bun scripts/cli.ts codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue id] [--model model] [--dry-run]", - "cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue --dry-run", - "bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue --dry-run", + "bun scripts/cli.ts agentrun v01 queue commander --reader-id cli", + "bun scripts/cli.ts agentrun v01 queue submit --json-file ", + "bun scripts/cli.ts agentrun v01 sessions trace --after-seq 0 --limit 100", + "bun scripts/cli.ts codex submit # frozen legacy write entry; returns legacy-code-queue-frozen", "bun scripts/cli.ts codex task [--detail] [--trace --tail|--from-start|--after-seq N|--before-seq N --limit N] [--full]", "bun scripts/cli.ts codex tasks [--view commander|supervisor|full] [--queue id] [--status succeeded,running] [--unread|--unread-only] [--limit N] [--before-id id]", "bun scripts/cli.ts codex unread [--repo owner/name] [--issue N] [--limit N]", @@ -403,11 +403,12 @@ function codexHelp(): unknown { "bun scripts/cli.ts codex execution-plane [--full|--raw]", "bun scripts/cli.ts codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/] [--pr-create-dry-run --pr-create-dry-run-head ] [--issue N] [--full|--raw]", "bun scripts/cli.ts codex judge --attempt N [--dry-run] [--include-prompt]", - "bun scripts/cli.ts codex steer [prompt|--prompt-file path|--prompt-stdin] [--steer-id id] [--dry-run] [--no-retry|--retry-attempts N] [--full|--raw]", - "bun scripts/cli.ts codex resume [prompt|--prompt-file path|--prompt-stdin] [--resume-id id] [--dry-run] [--full|--raw]", + "bun scripts/cli.ts agentrun v01 sessions steer --prompt-file ", + "bun scripts/cli.ts codex steer # frozen legacy write entry", + "bun scripts/cli.ts codex resume # frozen legacy write entry", "bun scripts/cli.ts codex steer-confirm --steer-id [--raw]", "bun scripts/cli.ts codex interrupt|cancel ", - "bun scripts/cli.ts codex queues [--commander] [--full|--all] [--limit N] [--page N|--offset N] | queue create | queue merge --into | move --queue ", + "bun scripts/cli.ts codex queues [--commander] [--full|--all] [--limit N] [--page N|--offset N] # legacy archive read-only", ], promptInput: { recommended: ["--prompt-stdin", "--prompt-file"], @@ -418,14 +419,14 @@ function codexHelp(): unknown { }, 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.", + boundary: "Legacy Code Queue submit is frozen; new runtime placement is selected by AgentRun Queue payloads.", + permissionVisibility: "Legacy Code Queue read commands expose historical runner metadata only; new tasks use AgentRun Queue/Sessions.", }, submitSummary: { - default: "Real codex submit is a compact write confirmation: accepted status, submitted task ids, submitted status/source, queue ids, promptOmitted=true, queue counts, activity and drill-down commands.", - listSemantics: "Task id previews are bounded objects; if a count is nonzero but ids are omitted or unavailable, the field sets idsUnavailable=true and does not print items=[] as if the list were empty.", - splitBrainLive: "When split-brain-live heartbeat evidence is present, submit summaries keep the live disposition visible and continue to count heartbeat/database active tasks as active.", - rawDrillDown: "Use queue.listPreviewPolicy.rawCommand or bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview?limit=30 --raw --full for raw queue detail.", + default: "codex submit/enqueue now returns ok=false, frozen=true, degradedReason=legacy-code-queue-frozen and AgentRun replacement commands.", + replacement: "Use bun scripts/cli.ts agentrun v01 queue submit --json-file for new work.", + noDoubleWrite: "UniDesk does not mirror AgentRun submissions into old Code Queue and does not migrate old history into AgentRun.", + rawDrillDown: "Use codex tasks/task/output/read/unread/queues only for legacy archive inspection.", }, readOutput: { default: "codex read marks a terminal task read and returns terminal metadata, final response, last error/judge, counts, and drill-down commands.", @@ -453,13 +454,10 @@ function codexHelp(): unknown { }, examples: { promptLint: "bun scripts/cli.ts codex prompt-lint --prompt-file /tmp/code-queue-prompt.md", - stdin: [ - "cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue --dry-run", - "", - "PROMPT", - ], - file: "bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue --dry-run", - dryRunThenSubmit: "Run with --dry-run first; remove --dry-run to submit exactly the same payload.", + agentRunCommander: "bun scripts/cli.ts agentrun v01 queue commander --reader-id cli", + agentRunSubmit: "bun scripts/cli.ts agentrun v01 queue submit --json-file ", + agentRunTrace: "bun scripts/cli.ts agentrun v01 sessions trace --after-seq 0 --limit 100", + frozenLegacySubmit: "bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md", }, disclosure: { defaultPolicy: "low-noise JSON by default; write commands confirm persistence, list/detail/output commands return bounded summaries with drill-down commands", @@ -481,10 +479,10 @@ function codexHelp(): unknown { classes: ["read-only", "live-read", "live-mutating"], defaultWhenMissing: "read-only", command: "bun scripts/cli.ts codex prompt-lint --prompt-file ", - embeddedIn: ["codex submit --dry-run", "codex steer --dry-run"], + embeddedIn: [], reference: "docs/reference/code-queue-supervision.md#dev-测试授权分级", }, - description: "Operate Code Queue through the stable backend-core private proxy path with bounded activity summaries for queue and supervisor views. Real submit/steer success is a low-noise write confirmation and does not echo prompt text; steer output includes steerId, delivery state, and a bounded trace confirmation command; terminal steer rejection returns compact status plus codex task/read/submit follow-up commands, with upstream/raw details behind --full or --raw.", + description: "Operate legacy Code Queue as a read-only archive through bounded task/output/read/unread/queues views. New task dispatch, retry/resume, steer, queue mutation, move, and workdir mutation are frozen and replaced by AgentRun Queue/Sessions via bun scripts/cli.ts agentrun v01 queue|sessions.", }; } diff --git a/src/components/frontend/public/style.css b/src/components/frontend/public/style.css index cbe47120..0a0b945a 100644 --- a/src/components/frontend/public/style.css +++ b/src/components/frontend/public/style.css @@ -2754,6 +2754,49 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent-2); } opacity: 0.64; cursor: wait; } +.codex-task-form.is-frozen textarea, +.codex-task-form.is-frozen input, +.codex-task-form.is-frozen select { + opacity: 0.72; +} +.codex-task-form.is-frozen .primary-btn { + cursor: not-allowed; +} +.codex-freeze-notice { + display: grid; + gap: 6px; + padding: 10px; + border: 1px solid rgba(215, 161, 58, 0.36); + border-radius: 8px; + background: + linear-gradient(135deg, rgba(215, 161, 58, 0.10), rgba(78, 183, 168, 0.05)), + rgba(7, 16, 20, 0.72); + color: rgba(217, 238, 238, 0.78); + font-size: 12px; + line-height: 1.45; +} +.codex-freeze-notice strong { + color: var(--text); + font-size: 13px; +} +.codex-freeze-notice p { + margin: 0; +} +.codex-freeze-command-grid { + display: grid; + gap: 4px; +} +.codex-freeze-command-grid code { + display: block; + max-width: 100%; + padding: 5px 7px; + overflow-wrap: anywhere; + white-space: normal; + border: 1px solid rgba(78, 183, 168, 0.24); + border-radius: 6px; + background: rgba(0,0,0,0.22); + color: #dff8f3; +} .codex-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); diff --git a/src/components/frontend/src/code-queue.tsx b/src/components/frontend/src/code-queue.tsx index d1669eae..fe5299b3 100644 --- a/src/components/frontend/src/code-queue.tsx +++ b/src/components/frontend/src/code-queue.tsx @@ -2087,19 +2087,19 @@ function CodexStatsPanel({ stats, queueName: activeQueueName, onRaw }: AnyRecord ); } -function TaskQueueMoveControl({ task, queueRows, busy, onMove }: AnyRecord) { +function TaskQueueMoveControl({ task, queueRows, busy, frozen, onMove }: AnyRecord) { const taskId = String(task?.id || ""); const currentQueue = taskQueueLabel(task); const [targetQueue, setTargetQueue] = useState(currentQueue); useEffect(() => { setTargetQueue(currentQueue); }, [taskId, currentQueue]); - const disabled = !taskId || busy || ["running", "judging", "retry_wait"].includes(String(task?.status || "")); + const disabled = frozen || !taskId || busy || ["running", "judging", "retry_wait"].includes(String(task?.status || "")); return h("div", { className: "codex-task-move-control", "data-testid": "codex-task-queue-move-control" }, h("label", null, "任务 queue", h("select", { value: targetQueue, - disabled: !taskId || busy, + disabled: frozen || !taskId || busy, onChange: (event: any) => setTargetQueue(String(event.target.value || currentQueue)), "data-testid": "codex-task-queue-move-select", }, @@ -2111,7 +2111,7 @@ function TaskQueueMoveControl({ task, queueRows, busy, onMove }: AnyRecord) { className: "ghost-btn", disabled: disabled || targetQueue === currentQueue, onClick: () => onMove(targetQueue), - title: disabled ? "运行中 / judging / retry_wait 的任务不能移动;请先打断或等当前 turn 结束" : "移动已创建任务到另一个 queue", + title: frozen ? "旧 Code Queue 任务移动已冻结" : disabled ? "运行中 / judging / retry_wait 的任务不能移动;请先打断或等当前 turn 结束" : "移动已创建任务到另一个 queue", "data-testid": "codex-task-queue-move-button", }, "移动"), ); @@ -2584,7 +2584,8 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi }, [promptParts, repeatCount, referenceTaskId]); const enqueueCount = enqueueItems.length; const batchNeedsConfirmation = enqueueCount > 1 && !batchConfirmed; - const submitDisabled = submitting || busy || enqueueCount === 0 || batchNeedsConfirmation; + const legacyCodeQueueFrozen = true; + const submitDisabled = legacyCodeQueueFrozen || submitting || busy || enqueueCount === 0 || batchNeedsConfirmation; const codexModels = codexModelOptions(queue, model); const providerOptions = codexProviderOptions(queue, providerId); const executionModeRows = codexExecutionModeOptions(queue, executionMode); @@ -2592,10 +2593,10 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi const savedWorkdirs = Array.isArray(workdirData?.workdirs) ? workdirData.workdirs : []; const currentWorkdirOptions = workdirOptions(queue, savedWorkdirs, providerId, executionMode, cwd); const currentWorkdirSaved = currentWorkdirOptions.some((item: any) => item.source === "saved" && normalizedWorkdirPath(item.path) === normalizedWorkdirPath(cwd)); - const selectedCanSteer = selectedTask?.id && selectedTask?.activeTurnId && String(selectedTask?.status) === "running"; + const selectedCanSteer = !legacyCodeQueueFrozen && selectedTask?.id && selectedTask?.activeTurnId && String(selectedTask?.status) === "running"; const selectedCanInterrupt = selectedTask?.id && !["succeeded", "failed", "canceled"].includes(String(selectedTask?.status || "")); - const selectedCanRetry = selectedTask?.id && ["succeeded", "failed", "canceled"].includes(String(selectedTask?.status || "")); - const selectedCanEditPrompt = selectedTask?.id && taskPromptEditable(selectedTask); + const selectedCanRetry = !legacyCodeQueueFrozen && selectedTask?.id && ["succeeded", "failed", "canceled"].includes(String(selectedTask?.status || "")); + const selectedCanEditPrompt = !legacyCodeQueueFrozen && selectedTask?.id && taskPromptEditable(selectedTask); function setTasksData(nextOrUpdater: any): any { const next = typeof nextOrUpdater === "function" ? nextOrUpdater(tasksDataRef.current) : nextOrUpdater; @@ -4268,12 +4269,12 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi onClose: () => setMergeDialogOpen(false), actions: [ h("button", { key: "cancel", type: "button", className: "ghost-btn", onClick: () => setMergeDialogOpen(false), disabled: busy, "data-testid": "codex-merge-queue-cancel" }, "取消"), - h("button", { key: "confirm", type: "button", className: "primary-btn", onClick: () => void mergeQueue(), disabled: busy || submitting || !mergeSourceQueueId, "data-testid": "codex-merge-queue-confirm" }, busy ? "合并中..." : "确认合并"), + h("button", { key: "confirm", type: "button", className: "primary-btn", onClick: () => void mergeQueue(), disabled: legacyCodeQueueFrozen || busy || submitting || !mergeSourceQueueId, "data-testid": "codex-merge-queue-confirm" }, legacyCodeQueueFrozen ? "旧入口已冻结" : busy ? "合并中..." : "确认合并"), ], }, h("p", { className: "codex-merge-dialog-target" }, "目标 queue:", h("code", null, queueOptionLabel(mergeTargetQueueRow))), h("label", null, "源 queue", - h("select", { value: mergeSourceQueueId, disabled: busy || submitting, onChange: (event: any) => setMergeSourceQueueId(String(event.target.value || "")), "data-testid": "codex-merge-source-queue-select" }, + h("select", { value: mergeSourceQueueId, disabled: legacyCodeQueueFrozen || busy || submitting, onChange: (event: any) => setMergeSourceQueueId(String(event.target.value || "")), "data-testid": "codex-merge-source-queue-select" }, h("option", { value: "" }, "选择要合并进来的源 queue"), mergeSourceQueueRows.map((row: any) => h("option", { key: String(row?.id || ""), value: String(row?.id || "") }, queueOptionLabel(row))), ), @@ -4300,65 +4301,74 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi ), h("div", { className: "code-queue-layout" }, h("div", { className: "codex-left-rail" }, - h(Panel, { title: "提交任务", eyebrow: submitting ? "Submitting..." : enqueueItems.length > 1 ? `${enqueueItems.length} tasks` : "Single or Batch", className: "codex-compose-panel", loading: submitting }, - h("form", { className: `codex-task-form ${submitting ? "is-submitting" : ""}`, onSubmit: enqueue, "data-testid": "code-queue-task-form", "aria-busy": submitting ? "true" : "false" }, + h(Panel, { title: "旧 Code Queue 入口", eyebrow: "Frozen / Archive", className: "codex-compose-panel", loading: submitting }, + h("form", { className: `codex-task-form ${submitting ? "is-submitting" : ""} ${legacyCodeQueueFrozen ? "is-frozen" : ""}`, onSubmit: enqueue, "data-testid": "code-queue-task-form", "aria-busy": submitting ? "true" : "false", "aria-disabled": legacyCodeQueueFrozen ? "true" : "false" }, + h("div", { className: "codex-freeze-notice", "data-testid": "code-queue-freeze-notice" }, + h("strong", null, "旧 Code Queue 新任务入口已冻结"), + h("p", null, "新任务请使用 AgentRun Queue;本页保留历史任务、Trace 和排障查看。"), + h("div", { className: "codex-freeze-command-grid" }, + h("code", null, "bun scripts/cli.ts agentrun v01 queue commander --reader-id cli"), + h("code", null, "bun scripts/cli.ts agentrun v01 queue submit --json-file "), + h("code", null, "bun scripts/cli.ts agentrun v01 sessions trace --after-seq 0 --limit 100"), + ), + ), h("label", null, "Prompt / 多任务用单独一行 --- 分隔", - h("textarea", { value: prompt, rows: 8, disabled: submitting, onChange: (event: any) => setPrompt(event.target.value), placeholder: "写入 Codex 任务;多个任务之间用 --- 分隔。" }), + h("textarea", { value: prompt, rows: 8, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setPrompt(event.target.value), placeholder: "旧入口已冻结;新任务使用 AgentRun Queue。" }), ), h("label", { className: "codex-reference-field" }, "引用任务 ID(可选)", - h("input", { value: referenceTaskId, disabled: submitting, onChange: (event: any) => setReferenceTaskId(event.target.value), placeholder: "codex_...;支持空格/逗号分隔多个 ID", "data-testid": "codex-reference-task-id" }), + h("input", { value: referenceTaskId, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setReferenceTaskId(event.target.value), placeholder: "codex_...;支持空格/逗号分隔多个 ID", "data-testid": "codex-reference-task-id" }), parseReferenceTaskIds(referenceTaskId).length > 0 ? h("code", null, `后端将解析并注入:${parseReferenceTaskIds(referenceTaskId).join(" / ")}`) : null, ), h("div", { className: "codex-form-grid" }, h("label", { className: "codex-submit-queue-field" }, "Queue", h("div", { className: "codex-submit-queue-row" }, - h("select", { className: "codex-submit-queue-select", value: queueId, disabled: submitting, onChange: (event: any) => setQueueId(String(event.target.value || "default")), "data-testid": "code-queue-id-select" }, + h("select", { className: "codex-submit-queue-select", value: queueId, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setQueueId(String(event.target.value || "default")), "data-testid": "code-queue-id-select" }, queueRows.map((row: any) => h("option", { key: String(row?.id || ""), value: String(row?.id || "") }, queueOptionLabel(row))), ), - h("button", { type: "button", className: "ghost-btn codex-rename-queue-btn", onClick: () => void renameQueue(), disabled: busy || submitting || !queueId, title: "修改当前 queue 的显示名称,ID 不变", "data-testid": "codex-rename-queue-button" }, "改名"), - h("button", { type: "button", className: "ghost-btn codex-merge-queue-btn", onClick: () => openMergeQueueDialog(), disabled: busy || submitting || mergeSourceQueueRows.length === 0, title: "打开独立窗口选择源 queue 并合并到当前 queue;合并后自动删除源 queue", "data-testid": "codex-merge-queue-button" }, "合并 queue"), - h("button", { type: "button", className: "ghost-btn codex-create-queue-btn", onClick: () => void createQueue(), disabled: busy || submitting, "data-testid": "codex-create-queue-button" }, "创建 queue"), + h("button", { type: "button", className: "ghost-btn codex-rename-queue-btn", onClick: () => void renameQueue(), disabled: legacyCodeQueueFrozen || busy || submitting || !queueId, title: legacyCodeQueueFrozen ? "旧 Code Queue queue 管理已冻结" : "修改当前 queue 的显示名称,ID 不变", "data-testid": "codex-rename-queue-button" }, "改名"), + h("button", { type: "button", className: "ghost-btn codex-merge-queue-btn", onClick: () => openMergeQueueDialog(), disabled: legacyCodeQueueFrozen || busy || submitting || mergeSourceQueueRows.length === 0, title: legacyCodeQueueFrozen ? "旧 Code Queue queue 管理已冻结" : "打开独立窗口选择源 queue 并合并到当前 queue;合并后自动删除源 queue", "data-testid": "codex-merge-queue-button" }, "合并 queue"), + h("button", { type: "button", className: "ghost-btn codex-create-queue-btn", onClick: () => void createQueue(), disabled: legacyCodeQueueFrozen || busy || submitting, "data-testid": "codex-create-queue-button" }, "创建 queue"), ), ), h("label", null, "模型", - h("select", { value: model, disabled: submitting, onChange: (event: any) => setModel(event.target.value), "data-testid": "codex-model-select" }, + h("select", { value: model, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setModel(event.target.value), "data-testid": "codex-model-select" }, codexModels.map((name) => h("option", { key: name, value: name }, name)), ), ), h("label", null, "执行 Provider", - h("select", { value: providerId, disabled: submitting, onChange: (event: any) => changeSubmitProvider(String(event.target.value || "D601")), "data-testid": "codex-provider-select" }, + h("select", { value: providerId, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => changeSubmitProvider(String(event.target.value || "D601")), "data-testid": "codex-provider-select" }, providerOptions.map((provider: any) => h("option", { key: provider.id, value: provider.id }, `${provider.label || provider.id} · ${provider.defaultWorkdir || providerDefaultWorkdir(queue, provider.id)}${provider.supportsWindowsNativeCodex ? " · Windows native" : ""}`)), ), ), h("label", null, "执行模式", - h("select", { value: executionMode, disabled: submitting, onChange: (event: any) => changeSubmitExecutionMode(String(event.target.value || "default")), "data-testid": "codex-execution-mode-select" }, + h("select", { value: executionMode, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => changeSubmitExecutionMode(String(event.target.value || "default")), "data-testid": "codex-execution-mode-select" }, executionModeRows.map((mode: any) => h("option", { key: mode.id, value: mode.id }, `${mode.label || mode.id}${mode.id === "windows-native" ? " · 宿主 Codex" : ""}`)), ), ), h("label", { className: "codex-workdir-field" }, "工作目录", h("div", { className: "codex-workdir-row" }, - h("input", { value: cwd, disabled: submitting, onChange: (event: any) => setCwd(event.target.value), placeholder: currentProviderDefaultWorkdir || queue?.defaultWorkdir || "/workspace", "data-testid": "codex-cwd-input" }), - h("select", { value: normalizedWorkdirPath(cwd), disabled: submitting || workdirBusy, onChange: (event: any) => setCwd(String(event.target.value || "")), "data-testid": "codex-cwd-select" }, + h("input", { value: cwd, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setCwd(event.target.value), placeholder: currentProviderDefaultWorkdir || queue?.defaultWorkdir || "/workspace", "data-testid": "codex-cwd-input" }), + h("select", { value: normalizedWorkdirPath(cwd), disabled: legacyCodeQueueFrozen || submitting || workdirBusy, onChange: (event: any) => setCwd(String(event.target.value || "")), "data-testid": "codex-cwd-select" }, currentWorkdirOptions.map((item: any) => h("option", { key: `${item.providerId}:${item.executionMode}:${item.path}`, value: item.path }, `${item.path}${item.source === "default" ? " · 默认" : ""}`)), ), - h("button", { type: "button", className: "ghost-btn codex-workdir-create-btn", disabled: submitting || busy || workdirBusy, onClick: () => void createWorkdirPreset(), "data-testid": "codex-cwd-create-button" }, workdirBusy ? "处理中" : "新建"), - h("button", { type: "button", className: "ghost-btn codex-workdir-delete-btn", disabled: submitting || busy || workdirBusy || !currentWorkdirSaved, onClick: () => void deleteWorkdirPreset(), title: currentWorkdirSaved ? "从工作目录下拉菜单删除这个选项,不删除磁盘目录" : "当前工作目录尚未保存到下拉菜单", "data-testid": "codex-cwd-delete-button" }, "删除"), + h("button", { type: "button", className: "ghost-btn codex-workdir-create-btn", disabled: legacyCodeQueueFrozen || submitting || busy || workdirBusy, onClick: () => void createWorkdirPreset(), "data-testid": "codex-cwd-create-button" }, workdirBusy ? "处理中" : "新建"), + h("button", { type: "button", className: "ghost-btn codex-workdir-delete-btn", disabled: legacyCodeQueueFrozen || submitting || busy || workdirBusy || !currentWorkdirSaved, onClick: () => void deleteWorkdirPreset(), title: legacyCodeQueueFrozen ? "旧 Code Queue 工作目录管理已冻结" : currentWorkdirSaved ? "从工作目录下拉菜单删除这个选项,不删除磁盘目录" : "当前工作目录尚未保存到下拉菜单", "data-testid": "codex-cwd-delete-button" }, "删除"), ), ), - h("label", null, "最大尝试", h("input", { type: "number", min: 1, max: 99, value: maxAttempts, disabled: submitting, onChange: (event: any) => setMaxAttempts(Number(event.target.value)), "data-testid": "codex-max-attempts-input" })), - h("label", null, "入队份数", h("input", { type: "number", min: 1, max: 50, value: repeatCount, disabled: submitting, onChange: (event: any) => setRepeatCount(Number(event.target.value)), "data-testid": "codex-repeat-count-input" })), + h("label", null, "最大尝试", h("input", { type: "number", min: 1, max: 99, value: maxAttempts, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setMaxAttempts(Number(event.target.value)), "data-testid": "codex-max-attempts-input" })), + h("label", null, "入队份数", h("input", { type: "number", min: 1, max: 50, value: repeatCount, disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setRepeatCount(Number(event.target.value)), "data-testid": "codex-repeat-count-input" })), ), enqueueCount > 1 ? h("label", { className: `codex-batch-confirm ${batchConfirmed ? "confirmed" : ""}`, "data-testid": "codex-batch-confirm-row" }, h("input", { type: "checkbox", checked: batchConfirmed, - disabled: submitting, + disabled: legacyCodeQueueFrozen || submitting, onChange: (event: any) => setBatchConfirmed(Boolean(event.target.checked)), "data-testid": "codex-batch-confirm-checkbox", }), h("span", null, `确认批量入队 ${enqueueCount} 个任务(prompt 分段 ${promptParts.length} × 入队份数 ${repeatCountValue(repeatCount)})`), ) : null, - submitting ? h("div", { className: "codex-submit-wait", "data-testid": "codex-submit-wait" }, "正在提交到后端,已锁定输入以防重复提交...") : null, + legacyCodeQueueFrozen || submitting ? h("div", { className: "codex-submit-wait", "data-testid": "codex-submit-wait" }, legacyCodeQueueFrozen ? "旧入口冻结:提交、重试、steer、queue 管理不会再写入旧 Code Queue。" : "正在提交到后端,已锁定输入以防重复提交...") : null, h("div", { className: "codex-form-actions" }, h("button", { type: "button", @@ -4374,16 +4384,16 @@ export function CodeQueuePage({ microservices, onRaw, apiBaseUrl = "/api", initi }, "data-testid": "codex-clear-input-button", }, "清空输入"), - h("button", { type: "submit", className: "primary-btn", disabled: submitDisabled, "data-testid": "codex-enqueue-button" }, submitting ? "提交中,请等待..." : batchNeedsConfirmation ? `请确认批量入队 ${enqueueCount} 个任务` : enqueueItems.length > 1 ? `批量入队 ${enqueueItems.length} 个任务` : "入队并运行"), + h("button", { type: "submit", className: "primary-btn", disabled: submitDisabled, "data-testid": "codex-enqueue-button" }, legacyCodeQueueFrozen ? "已冻结,使用 AgentRun Queue" : submitting ? "提交中,请等待..." : batchNeedsConfirmation ? `请确认批量入队 ${enqueueCount} 个任务` : enqueueItems.length > 1 ? `批量入队 ${enqueueItems.length} 个任务` : "入队并运行"), ), ), ), ), h("div", { className: "codex-main-stage" }, h("div", { className: "codex-detail-grid" }, - h(Panel, { title: "运行控制", eyebrow: selectedCanEditPrompt ? "Queued prompt editable" : selectedCanSteer ? "Active turn steer" : "Steer when running", loading: busy }, + h(Panel, { title: "运行控制", eyebrow: legacyCodeQueueFrozen ? "Legacy write controls frozen" : selectedCanEditPrompt ? "Queued prompt editable" : selectedCanSteer ? "Active turn steer" : "Steer when running", loading: busy }, h("div", { className: "codex-run-control-stack" }, - h(TaskQueueMoveControl, { task: selectedTask, queueRows, busy, onMove: moveSelectedTaskQueue }), + h(TaskQueueMoveControl, { task: selectedTask, queueRows, busy, frozen: legacyCodeQueueFrozen, onMove: moveSelectedTaskQueue }), selectedTask?.id ? h("form", { className: "codex-steer-form codex-edit-prompt-form", onSubmit: editSelectedQueuedPrompt, "data-testid": "codex-edit-prompt-form" }, h("label", null, "编辑 queued 用户 prompt", h("textarea", { diff --git a/src/components/microservices/code-queue/src/index.ts b/src/components/microservices/code-queue/src/index.ts index e6213e3f..9b246291 100644 --- a/src/components/microservices/code-queue/src/index.ts +++ b/src/components/microservices/code-queue/src/index.ts @@ -4859,6 +4859,48 @@ function readOnlyRejectResponse(method: string, targetPath: string): Response { }, 405); } +function legacyQueueFrozenResponse(method: string, targetPath: string, action: string): Response { + return jsonResponse({ + ok: false, + frozen: true, + mutation: false, + error: "Legacy UniDesk Code Queue is frozen for new task and execution mutations; use AgentRun Queue for new commander work.", + reason: "legacy-code-queue-frozen", + serviceRole: config.serviceRole, + method, + path: targetPath, + action, + replacement: { + queueCommander: "bun scripts/cli.ts agentrun v01 queue commander --reader-id cli", + queueSubmit: "bun scripts/cli.ts agentrun v01 queue submit --json-file ", + queueDispatch: "bun scripts/cli.ts agentrun v01 queue dispatch --json-file ", + sessionsTrace: "bun scripts/cli.ts agentrun v01 sessions trace --after-seq 0 --limit 100", + sessionsOutput: "bun scripts/cli.ts agentrun v01 sessions output --after-seq 0 --limit 100", + sessionsSteer: "bun scripts/cli.ts agentrun v01 sessions steer --prompt-file ", + sessionsRead: "bun scripts/cli.ts agentrun v01 sessions read --reader-id cli", + queueCancel: "bun scripts/cli.ts agentrun v01 queue cancel --reason ", + }, + legacy: { + retainedFor: "historical archive/read-only troubleshooting", + allowedReadPaths: [ + "GET /api/tasks", + "GET /api/tasks/overview", + "GET /api/tasks/:id", + "GET /api/tasks/:id/output", + "GET /api/tasks/:id/trace-summary", + "GET /api/tasks/:id/trace-steps", + "GET /api/queues", + ], + cancellationPath: "POST /api/tasks/:id/interrupt remains available only to stop residual old runs", + noDoubleWrite: true, + }, + }, 410); +} + +function legacyQueueWritesFrozen(): boolean { + return true; +} + function databaseNotReadyResponse(method: string, targetPath: string): Response { return jsonResponse({ ok: false, @@ -4918,6 +4960,7 @@ async function listWorkdirs(url: URL): Promise { } async function createWorkdir(req: Request): Promise { + if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, "/api/workdirs", "workdir-create"); const notReady = requireDatabaseReadyForWrite(req.method, "/api/workdirs"); if (notReady !== null) return notReady; const body = await readJson(req); @@ -4948,6 +4991,7 @@ async function createWorkdir(req: Request): Promise { } async function deleteWorkdir(providerIdValue: string, executionModeValue: string, pathValue: string): Promise { + if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse("DELETE", `/api/workdirs/${providerIdValue}/${executionModeValue}/${pathValue}`, "workdir-delete"); const notReady = requireDatabaseReadyForWrite("DELETE", `/api/workdirs/${providerIdValue}/${executionModeValue}/${pathValue}`); if (notReady !== null) return notReady; const providerId = normalizeTaskProviderId(providerIdValue); @@ -4970,6 +5014,7 @@ async function deleteWorkdir(providerIdValue: string, executionModeValue: string } async function createTasks(req: Request): Promise { + if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, "/api/tasks", "task-submit"); if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, "/api/tasks"); const notReady = requireDatabaseReadyForWrite(req.method, "/api/tasks"); if (notReady !== null) return notReady; @@ -4998,6 +5043,7 @@ async function createTasks(req: Request): Promise { } async function steerTask(task: QueueTask, req: Request): Promise { + if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, `/api/tasks/${task.id}/steer`, "task-steer"); if (!serviceRoleAllowsScheduler(config.serviceRole)) return schedulerOnlyRejectResponse(req.method, `/api/tasks/${task.id}/steer`); const body = await readJson(req); const bodyRecord = typeof body === "object" && body !== null && !Array.isArray(body) ? body as Record : {}; @@ -5062,6 +5108,7 @@ async function steerTask(task: QueueTask, req: Request): Promise { } async function resumeTask(task: QueueTask, req: Request): Promise { + if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, `/api/tasks/${task.id}/resume`, "task-resume"); if (!serviceRoleAllowsScheduler(config.serviceRole)) return schedulerOnlyRejectResponse(req.method, `/api/tasks/${task.id}/resume`); const notReady = requireDatabaseReadyForWrite(req.method, `/api/tasks/${task.id}/resume`); if (notReady !== null) return notReady; @@ -5163,6 +5210,7 @@ async function resumeTask(task: QueueTask, req: Request): Promise { } async function editQueuedTaskPrompt(task: QueueTask, req: Request): Promise { + if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, `/api/tasks/${task.id}/edit`, "queued-prompt-edit"); if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${task.id}/edit`); const notReady = requireDatabaseReadyForWrite(req.method, `/api/tasks/${task.id}/edit`); if (notReady !== null) return notReady; @@ -5238,6 +5286,7 @@ async function interruptTask(task: QueueTask, method = "POST"): Promise { + if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, `/api/tasks/${task.id}/retry`, "task-retry"); if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${task.id}/retry`); const notReady = requireDatabaseReadyForWrite(req.method, `/api/tasks/${task.id}/retry`); if (notReady !== null) return notReady; @@ -5399,6 +5448,7 @@ async function markTerminalTasksRead(url: URL): Promise { } async function createQueue(req: Request): Promise { + if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, "/api/queues", "queue-create"); if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, "/api/queues"); const notReady = requireDatabaseReadyForWrite(req.method, "/api/queues"); if (notReady !== null) return notReady; @@ -5418,6 +5468,7 @@ async function createQueue(req: Request): Promise { } async function updateQueue(queueIdValue: string, req: Request): Promise { + if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, `/api/queues/${queueIdValue}`, "queue-update"); if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/queues/${queueIdValue}`); const notReady = requireDatabaseReadyForWrite(req.method, `/api/queues/${queueIdValue}`); if (notReady !== null) return notReady; @@ -5628,6 +5679,7 @@ function deleteQueuesFromState(queueIds: string[]): QueueRecord[] { } async function mergeQueues(targetQueueIdValue: string | null, req: Request): Promise { + if (legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, targetQueueIdValue === null ? "/api/queues/merge" : `/api/queues/${targetQueueIdValue}/merge`, "queue-merge"); if (!serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, targetQueueIdValue === null ? "/api/queues/merge" : `/api/queues/${targetQueueIdValue}/merge`); const mergePath = targetQueueIdValue === null ? "/api/queues/merge" : `/api/queues/${targetQueueIdValue}/merge`; const notReady = requireDatabaseReadyForWrite(req.method, mergePath); @@ -5726,6 +5778,7 @@ async function mergeQueues(targetQueueIdValue: string | null, req: Request): Pro } async function moveTaskToQueue(task: QueueTask, req: Request, options: { bypassRoleCheck?: boolean } = {}): Promise { + if (options.bypassRoleCheck !== true && legacyQueueWritesFrozen()) return legacyQueueFrozenResponse(req.method, `/api/tasks/${task.id}/move`, "task-move"); if (options.bypassRoleCheck !== true && !serviceRoleAllowsWrite(config.serviceRole)) return readOnlyRejectResponse(req.method, `/api/tasks/${task.id}/move`); const notReady = requireDatabaseReadyForWrite(req.method, `/api/tasks/${task.id}/move`); if (notReady !== null) return notReady;