Merge pull request #93 from pikasTech/fix/issue20-commander-pr-preflight

Compact codex PR preflight commander output
This commit is contained in:
Lyon
2026-05-23 16:02:37 +08:00
committed by GitHub
5 changed files with 329 additions and 72 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ CLI 可以从 `master` 快速演进,但必须兼容 `deploy.json` 固定的 CI
- `schedule list|get|runs|run|retry-run|delete|upsert-pgdata-backup` 管理 backend-core 定时任务和运行历史。`schedule list``schedule get``schedule runs --limit N``schedule runs <scheduleId> --limit N` 是只读观察入口;`schedule run``schedule retry-run``schedule delete``schedule upsert-pgdata-backup` 会触发运行或写入配置,生产恢复时必须有明确授权。`schedule runs --limit N` 是全局历史视图,返回 `scope=global``scheduleId=null``schedule runs <scheduleId> --limit N` 是指定 schedule 历史视图,返回 `scope=schedule` 和对应 `scheduleId`。CLI 必须拒绝 `schedule runs 50` 这类纯数字位置参数,并提示使用 `schedule runs --limit 50`,避免把空数组误判成“没有历史 run”。`schedule run <id> --wait-ms N` 触发同一 schedule,并且即使 wait 超时也必须返回 `newRunId``observeCommand``schedule retry-run <failedRunId>` 只接受 failed run,从原 run 反查 `scheduleId` 后重触发同一 schedule,并输出 `originalRunId``scheduleId``newRunId``observeCommand`。当 backend-core 目标容器缺失或只观察到 verify-only 容器时,schedule/microservice 命令必须以非零退出并返回 `failureKind=target-stack-not-running``runnerDisposition=infra-blocked``readOnlyCommands``authorizationRequiredForRecovery`,不得把 Docker 的 `No such container` 当成成功的空历史。
- `codex deploy <commitId>` 是旧 Code Queue 兼容部署入口,已禁用以防止维护通道直连 D601 部署 Code Queue;当前 dev 自动化只做 `ci run-dev-e2e` smoke,不提供 Code Queue CD,详细规则见 `docs/reference/codex-deploy.md`
- `codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue queueId] [--provider-id id] [--cwd path] [--model model] [--reasoning-effort effort] [--execution-mode mode] [--max-attempts N] [--reference-task-id id] [--dry-run]` 通过 backend-core 私有代理向稳定 `code-queue` 用户服务路径提交任务;prompt 必须且只能来自位置参数、文件或 stdin 之一,`--dry-run` 只返回结构化请求且不实际入队。长 prompt、多行 prompt、含引号/反引号/Markdown 表格/JSON/反斜杠的 prompt 必须优先用 `--prompt-stdin``--prompt-file`,不要拼进 shell 单个参数;位置参数只适合短单行 smoke prompt。stdin 推荐用 quoted heredoc`cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue <id> --dry-run`,文件路径推荐 `bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue <id> --dry-run`,确认 dry-run 后移除 `--dry-run` 提交同一 payload。dry-run 会额外输出 `routingRecommendation`,包含推荐 route、runner、model、风险信号、prompt 自包含/issue 非唯一来源/prod-secret-DB 禁止/运行态或 release 禁止/证据要求/中等复杂度候选等 guard 状态;同时输出 `policyContract`,固定暴露 GPT-5.5、DeepSeek、MiniMax 的风险分层、并发上限和外部 provider 429 退避处置。该建议只用于指挥官 preflight,不会改写 payload,不改变 runtime admission,也不假设生产 MiniMax 或 DeepSeek 可用。`--dry-run` 必须返回完整 prompt、字符数和 `truncated=false` 用于人工验收;真实提交是写入操作,默认只返回 `accepted=true`、task id、队列、写入保护摘要和后续查看命令,必须标记 `promptOmitted=true` 且不得回显 prompt 或 promptPreview。真实提交会经过本机本地串行化保护和短节流,避免同一指挥端并发 submit 把低内存主机或 `code-queue-mgr` 控制面打抖;返回值会附带低噪声 `submitConcurrencyGuard` 说明本次提交的锁与等待信息。backend-core 默认把提交、队列 CRUD、已读状态、历史摘要和轻量 Trace 读取分流到主 server `code-queue-mgr`,由它写入主 PostgreSQLD601 scheduler 只轮询并执行已入库任务。
- `codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/<name>] [--pr-create-dry-run --pr-create-dry-run-head <head>] [--issue N] [--full]` 通过稳定 `code-queue` proxy 请求 D601 scheduler `/api/runtime-preflight`,用于 PR 型派单 admission。输出会压缩展示 scheduler/runner 的 token 覆盖、Auth Broker source/capability/nextAction、工具、agent port、Git worktree、GitHub egress、repo/issue/PR 只读探测、可选 push dry-run,以及可选 PR body/create dry-run guard只报告 `GH_TOKEN`/`GITHUB_TOKEN` 是否存在和来源 key,不打印值。当 auth-broker 配置存在时,`tokenCoverage.source="auth-broker"``credentialSource="broker-issued-token"` 且 runner env token 不是成功前提;当仅 env token 存在时,`credentialSource="env-token"``preflight.authBroker.nextAction="use-env-token-until-auth-broker-live"`;两者都缺失时顶层 `ok=false``runnerDisposition=infra-blocked``degradedReason=auth-broker-needed``tokenCoverage.missing` 同时列出 `GH_TOKEN``GITHUB_TOKEN`,并输出 `preflight.authBroker.source="broker/auth-broker-needed"``capability.source="missing-token"`。该 `auth-missing` 的 scope 是 `scheduler-runner-env`,不能简化成“当前 active runner/dev container 不能创建 PR”;输出必须带 `scopeBoundary``activeRunnerDevContainer`,要求调用方另跑 `bun scripts/cli.ts gh auth status --repo pikasTech/unidesk` 与 PR dry-run 来确认当前 dev container 能力。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 语义失败。`preflight.prCapabilityContract` 是 runner-facing 合同摘要,必须包含目标分支、token/auth 来源、`systemGhBinaryRequiredForWrites=false`、UniDesk REST `bun scripts/cli.ts gh` 可用性、push dry-run/PR create dry-run 的 `writesRemote=false`、expected PR handoff、真实 PR 创建需要 commander 授权和 `gh pr merge``unsupported-command` 边界;系统 `gh` binary 缺失只进入 `tools.systemGhBinary`,不得误判为 UniDesk REST `gh` CLI 不可用。`--remote` 在 runner-like 环境里不再依赖本地 `unidesk-backend-core``unidesk-database``baidu-netdisk-backend` 容器存在;这些缺失只作为本地观测证据。若远程控制面可达,则继续走远程控制面结果;若远程控制面不可达,则结构化返回 `failureKind=control-plane-missing` / `degradedReason=remote-control-plane-unreachable`,而不是把本地 `backend-core-container-missing` 当作最终阻塞。`--pr-create-dry-run` 不 POST GitHub,只证明 runner 内 PR body 生成、`scripts/cli.ts gh pr create --dry-run` 和 branch 参数形态可用;服务端创建权限仍以 token/auth broker、repo/issue/PR read、push dry-run 和最终授权后的真实 PR 创建结果为准。
- `codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/<name>] [--pr-create-dry-run --pr-create-dry-run-head <head>] [--issue N] [--full|--raw]` 通过稳定 `code-queue` proxy 请求 D601 scheduler `/api/runtime-preflight`,用于 PR 型派单 admission。默认输出是紧凑 commander 视图,显式分出 `schedulerPreflight``activeRunnerPrCapability`,并附带 `commands``disclosure`,方便先看 scheduler auth 缺口、再看当前 runner/dev container 的 `gh auth status``gh pr create --dry-run` 能力;`--full``--raw` 才展开完整 `preflight`、工具、agent port、Git worktree、GitHub egress、repo/issue/PR 只读探测和观测原文。只报告 `GH_TOKEN`/`GITHUB_TOKEN` 是否存在和来源 key,不打印值。当 auth-broker 配置存在时,`tokenCoverage.source="auth-broker"``credentialSource="broker-issued-token"` 且 runner env token 不是成功前提;当仅 env token 存在时,`credentialSource="env-token"``authBroker.nextAction="use-env-token-until-auth-broker-live"`;两者都缺失时顶层 `ok=false``runnerDisposition=infra-blocked``degradedReason=auth-broker-needed``tokenCoverage.missing` 同时列出 `GH_TOKEN``GITHUB_TOKEN`,并输出 `authBroker.source="broker/auth-broker-needed"``capability.source="missing-token"`。该 `auth-missing` 的 scope 是 `scheduler-runner-env`,不能简化成“当前 active runner/dev container 不能创建 PR”;默认视图必须带 `scopeBoundary``activeRunnerPrCapability`。GitHub DNS/API 连接失败应归类为 `failureKind=github-transient``degradedReason=github-dns-api-transient`,并带 `retryable=true``commanderAction=retry-backoff-or-keep-running-if-heartbeat-fresh` 和有界 `githubTransient.failedProbes`;调用方应重试/退避,且在任务 heartbeat/trace 新鲜时继续监督,不把它当成 auth 缺失或 PR 语义失败。`prCapability` 是 runner-facing 合同摘要,必须包含目标分支、token/auth 来源、`systemGhBinaryRequiredForWrites=false`、UniDesk REST `bun scripts/cli.ts gh` 可用性、push dry-run/PR create dry-run 的 `writesRemote=false`、expected PR handoff、真实 PR 创建需要 commander 授权和 `gh pr merge``unsupported-command` 边界;系统 `gh` binary 缺失只进入 `tools.systemGhBinary`,不得误判为 UniDesk REST `gh` CLI 不可用。`--remote` 在 runner-like 环境里不再依赖本地 `unidesk-backend-core``unidesk-database``baidu-netdisk-backend` 容器存在;这些缺失只作为本地观测证据。若远程控制面可达,则继续走远程控制面结果;若远程控制面不可达,则结构化返回 `failureKind=control-plane-missing` / `degradedReason=remote-control-plane-unreachable`,而不是把本地 `backend-core-container-missing` 当作最终阻塞。`--pr-create-dry-run` 不 POST GitHub,只证明 runner 内 PR body 生成、`scripts/cli.ts gh pr create --dry-run` 和 branch 参数形态可用;服务端创建权限仍以 token/auth broker、repo/issue/PR read、push dry-run 和最终授权后的真实 PR 创建结果为准。
- `codex task <taskId>` 通过 Code Queue 私有代理按任务 ID 查询结构化审阅摘要;默认只返回任务身份、执行 Provider、工作目录、attempt 计数、原始 prompt、最终 response、最后错误和渐进披露命令,适合指挥官审阅完成未读任务且避免上下文爆炸。`--detail` 仍是有界详细摘要:默认只返回少量 attempt/tool 行、短 prompt/response/stderr/feedback 预览和 omitted/truncated 元数据;需要完整 prompt/response 文本或更多 tool/attempt 细节时再显式加 `--full``--tool-limit N``--trace``codex output`。该摘要读取默认由主 server `code-queue-mgr` 从 PostgreSQL 返回,不依赖 D601 `code-queue-read` Service 可用。
- `codex tasks [--view supervisor|full] [--queue id] [--status succeeded|running|queued|failed|canceled|judging|retry_wait[,..]] [--unread|--unread-only] [--limit N] [--before-id id]` 通过同一私有代理输出渐进式披露视图。默认 `supervisor` 是低噪声指挥官视图,只返回 `activeRunning``running``completedUnread``recentCompleted``queued``activity``commanderConcurrency``executionDiagnostics` 的紧凑行;`activeRunning.count` 是 running+judging 的状态计数,`exact=true` 时来自 queue summary counts`running.returned``activeRunning.rowPage.returned` 只是本次返回的紧凑行数。`commanderConcurrency.activeRunnerCount` 是并发策略应使用的 active/running 计数,等于 `activity.effectiveActiveTaskCount`15 并发策略按 `15 - activeRunnerCount` 计算剩余窗口。`commanderConcurrency.splitBrainDisposition=live-count-as-active` 表示 split-brain 有 fresh heartbeat 证据,应继续监督并计入 active;`interventionRequired=true` 才提示介入。prompt/body 只给短预览和原始字符数,`running`/`completedUnread`/`queued` 默认只返回一个有界小页并通过 section `commands.next` 继续分页,`recentCompleted` 默认限量且不重复 `completedUnread` 未读终态,不嵌入完整 Trace、final response 或全量 overview。`--limit` 在 supervisor 中主要是扫描/分页预算,不是返回几十条肥行的开关;CLI 安全上限是 100,输出会在 `filters.requestedLimit``filters.effectiveLimit``filters.limitCapped``disclosure.limitPolicy` 说明显式请求是否被 capped;底层 overview 拉取预算独立显示在 `source.requestedLimit` / `source.effectiveLimit`,所以 `--limit 260` 应显示 requested=260、effective=100、source requested/effective=200,而不是只露出一个含糊的 `limit``--unread``--unread-only` 的别名,必须只保留未读终态;`--status` 必须真实过滤支持的状态,未知参数或未知状态必须结构化失败。需要更详细当前页任务行时显式使用 `--view full``--full`,仍受 `--limit``--before-id` 分页约束。
- `codex task <taskId> --trace --tail|--from-start|--after-seq N|--before-seq N --limit N` 按页拉取 Code Queue 的逻辑 trace;响应会返回 `nextAfterSeq``previousBeforeSeq``hasMore``hasBefore` 和下一页/上一页命令,默认 `--trace` 取最新一页,且仍以分页 trace 为主;需要完整 prompt/最终 response 时加 `--full`,需要详细 task 摘要时加 `--detail`
@@ -347,10 +347,11 @@ async function main(): Promise<void> {
assertCondition(asRecord(fallback.disclosure).fullObservationsOmitted === true, "healthy remote fallback should disclose omitted full observations", fallback.disclosure);
assertCondition(asRecord(fallback.localObservationSummary).failureKind === "target-stack-not-running", "healthy remote fallback should keep bounded local observation summary", fallback.localObservationSummary);
assertCondition(asRecord(fallback.remoteObservationSummary).ok === true, "healthy remote fallback should keep bounded remote observation summary", fallback.remoteObservationSummary);
const fallbackPreflight = asRecord(fallback.preflight);
assertCondition(fallbackPreflight.ok === true, "remote fallback preflight should stay ready", fallbackPreflight);
assertCondition(asRecord(fallbackPreflight.tokenCoverage).source === "GH_TOKEN", "token source should be GH_TOKEN", fallbackPreflight.tokenCoverage);
assertCondition(asRecord(fallbackPreflight.prCapabilityContract).targetBranch === "master", "target branch should stay master", fallbackPreflight.prCapabilityContract);
assertCondition(fallback.preflight === undefined, "remote fallback default output should omit detailed preflight", fallback);
const fallbackSchedulerPreflight = asRecord(fallback.schedulerPreflight);
assertCondition(fallbackSchedulerPreflight.authReady === true, "remote fallback scheduler summary should stay ready", fallbackSchedulerPreflight);
assertCondition(fallbackSchedulerPreflight.authSource === "GH_TOKEN", "token source should be GH_TOKEN", fallbackSchedulerPreflight);
assertCondition(asRecord(fallback.prCapability).targetBranch === "master", "target branch should stay master", fallback.prCapability);
const authMissing = await codexPrPreflightQueryForTest(["--remote"], {
config: null,
@@ -406,23 +407,26 @@ async function main(): Promise<void> {
assertCondition(directAuthObservationGap.kind === "runner-local-observation-gap", "auth missing after remote fallback should keep local backend-core absence scoped as runner-local observation gap", directAuthObservationGap);
const directAuthSummary = asRecord(directAuthMissingRecord.authScopeSummary);
const directAuthScopeBoundary = asRecord(directAuthMissingRecord.scopeBoundary);
const directAuthActiveRunner = asRecord(directAuthMissingRecord.activeRunnerDevContainer);
const directAuthActiveRunner = asRecord(directAuthMissingRecord.activeRunnerPrCapability);
const directAuthRecommendedActions = Array.isArray(directAuthMissingRecord.recommendedActions) ? directAuthMissingRecord.recommendedActions : [];
assertCondition(directAuthSummary.schedulerAuthMissingIsScoped === true, "remote auth-missing should lead with scheduler-scoped auth summary", directAuthSummary);
assertCondition(String(directAuthSummary.interpretation ?? "").includes("does not prove"), "remote auth summary must not imply active runner PR incapability", directAuthSummary);
assertCondition(directAuthScopeBoundary.scopesAreIndependent === true, "remote auth-missing must distinguish scheduler env from active runner dev container", directAuthScopeBoundary);
assertCondition(directAuthScopeBoundary.schedulerAuthMissingDoesNotMeanActiveRunnerCannotCreatePr === true, "remote auth-missing should expose the explicit PR capability boundary", directAuthScopeBoundary);
assertCondition(String(directAuthScopeBoundary.authMissingInterpretation ?? "").includes("do not simplify"), "remote auth-missing must warn against overbroad interpretation", directAuthScopeBoundary);
assertCondition(directAuthActiveRunner.notEquivalentToSchedulerEnv === true, "active runner token capability must be a separate scope", directAuthActiveRunner);
assertCondition(directAuthActiveRunner.independentOfSchedulerPreflight === true, "active runner token capability must be a separate scope", directAuthActiveRunner);
assertCondition(Array.isArray(directAuthMissingRecord.recommendedActions), "remote auth-missing should expose bounded recommended actions", directAuthMissingRecord.recommendedActions);
assertCondition(directAuthRecommendedActions.length === 3, "remote auth-missing recommended actions should stay bounded", directAuthRecommendedActions);
assertCondition(directAuthRecommendedActions.some((action) => asRecord(action).action === "verify-current-runner-auth"), "remote auth-missing should recommend active runner auth status first", directAuthRecommendedActions);
assertCondition(directAuthRecommendedActions.some((action) => String(asRecord(action).command ?? "").includes("gh pr create") && asRecord(action).writesRemote === false), "remote auth-missing should recommend PR create dry-run", directAuthRecommendedActions);
assertCondition(directAuthMissingRecord.localObservation === undefined, "auth-missing remote fallback default output should omit full local observation", directAuthMissingRecord);
assertCondition(directAuthMissingRecord.remoteObservation === undefined, "auth-missing remote fallback default output should omit full remote observation", directAuthMissingRecord);
assertCondition(directAuthMissingRecord.preflight === undefined, "auth-missing remote fallback default output should omit detailed preflight", directAuthMissingRecord);
assertCondition(asRecord(directAuthMissingRecord.disclosure).fullObservationsOmitted === true, "auth-missing remote fallback should disclose omitted full observations", directAuthMissingRecord.disclosure);
assertCondition(asRecord(directAuthMissingRecord.disclosure).fullDetailOmitted === true, "auth-missing remote fallback should disclose omitted full detail", directAuthMissingRecord.disclosure);
assertCondition(asRecord(directAuthMissingRecord.localObservationSummary).failureKind === "target-stack-not-running", "auth-missing remote fallback should keep bounded local observation summary", directAuthMissingRecord.localObservationSummary);
assertCondition(asRecord(asRecord(directAuthMissingRecord.remoteObservationSummary).tokenCoverage).scope === "scheduler-runner-env", "auth-missing remote fallback should keep bounded remote token scope", directAuthMissingRecord.remoteObservationSummary);
assertCondition(JSON.stringify(directAuthMissingRecord).length < 12000, "auth-missing remote fallback default output should stay compact", { chars: JSON.stringify(directAuthMissingRecord).length });
const directAuthMissingFull = await codexPrPreflightQueryForTest(["--remote", "--full"], {
config: { network: { publicHost: "74.48.78.17", frontend: { port: 18081 } } } as unknown as UniDeskConfig,
@@ -599,15 +603,63 @@ async function main(): Promise<void> {
assertCondition(githubTransientRecord.runnerDisposition === "infra-blocked", "GitHub transient keeps infra-blocked disposition for legacy callers", githubTransientRecord);
assertCondition(githubTransientRecord.retryable === true, "GitHub transient should expose top-level retryable=true", githubTransientRecord);
assertCondition(githubTransientRecord.commanderAction === "retry-backoff-or-keep-running-if-heartbeat-fresh", "GitHub transient should expose top-level commander action", githubTransientRecord);
const githubTransientPreflight = asRecord(githubTransientRecord.preflight);
assertCondition(githubTransientPreflight.retryable === true, "GitHub transient preflight should be retryable", githubTransientPreflight);
assertCondition(githubTransientPreflight.commanderAction === "retry-backoff-or-keep-running-if-heartbeat-fresh", "GitHub transient should expose bounded commander action", githubTransientPreflight);
const githubTransient = asRecord(githubTransientPreflight.githubTransient);
assertCondition(githubTransientRecord.preflight === undefined, "GitHub transient default output should omit detailed preflight", githubTransientRecord);
const githubTransient = asRecord(githubTransientRecord.githubTransient);
assertCondition(githubTransient.kind === "github-transient", "GitHub transient evidence should identify kind", githubTransient);
assertCondition(githubTransient.notAuthMissing === true, "GitHub transient must not be auth-missing", githubTransient);
assertCondition(githubTransient.notPrSemanticFailure === true, "GitHub transient must not be PR semantic failure", githubTransient);
assertCondition(Array.isArray(githubTransient.failedProbes) && githubTransient.failedProbes.length <= 4, "GitHub transient evidence should stay bounded", githubTransient);
assertCondition(String(githubTransient.commanderAction ?? "").includes("keep the task running"), "GitHub transient action should preserve fresh-heartbeat tasks", githubTransient);
const githubTransientFullRecord = asRecord(await codexPrPreflightQueryForTest(["--remote", "--full"], {
config: null,
coreFetch: () => ({
ok: true,
status: 200,
body: {
runtimePreflight: rawRuntimePreflightFixture({
ok: false,
pullRequestDelivery: {
...asRecord(rawRuntimePreflightFixture().pullRequestDelivery),
ok: false,
credentials: {
ghTokenPresent: true,
githubTokenPresent: false,
ghHostsConfigPresent: false,
gitCredentialsPresent: false,
},
egress: {
proxy: {
selectedProxyHost: "d601-provider-egress-proxy.unidesk.svc.cluster.local",
selectedProxyPort: "18789",
selectedProxyHostResolvable: true,
},
githubDefault: { command: "curl", args: ["-IsS", "https://github.com"], ok: false, exitCode: 6, signal: null, error: null, stdout: "", stderr: "curl: (6) Could not resolve host: github.com" },
apiDefault: { command: "curl", args: ["-IsS", "https://api.github.com"], ok: false, exitCode: 6, signal: null, error: null, stdout: "", stderr: "curl: (6) Could not resolve host: api.github.com" },
issueApi: null,
},
remote: {
gitLsRemote: { command: "git", args: ["ls-remote", "--heads", "origin", "master"], ok: false, exitCode: 128, signal: null, error: null, stdout: "", stderr: "ssh: Could not resolve hostname github.com: Temporary failure in name resolution" },
gitHttpsLsRemote: null,
githubSshAuthenticated: false,
ghAuthStatus: { command: "gh", args: ["auth", "status"], ok: false, exitCode: 1, signal: null, error: null, stdout: "", stderr: "error connecting to api.github.com" },
ghRepoView: null,
ghIssueView: null,
ghPrReadOnly: null,
},
limitations: [
"GitHub HTTPS probe failed with the default environment/proxy",
"GitHub API probe failed with the default environment/proxy",
"git ls-remote origin master failed",
],
risks: [],
},
}),
},
}),
}), "github transient full record");
const githubTransientPreflight = asRecord(githubTransientFullRecord.preflight);
assertCondition(githubTransientPreflight.retryable === true, "GitHub transient full preflight should be retryable", githubTransientPreflight);
assertCondition(githubTransientPreflight.commanderAction === "retry-backoff-or-keep-running-if-heartbeat-fresh", "GitHub transient full preflight should expose bounded commander action", githubTransientPreflight);
let observedDryRunPath = "";
const dryRunContract = await codexPrPreflightQueryForTest([
@@ -707,24 +759,23 @@ async function main(): Promise<void> {
assertCondition(observedDryRunPath === "/api/microservices/code-queue/proxy/api/runtime-preflight?remote=1&pushDryRun=1&pushDryRunRef=refs%2Fheads%2Fprobe%2Fcode-queue-pr-capability&prCreateDryRun=1&prCreateDryRunHead=code-queue%2Fissue-35-pr-dry-run-probe&issue=20", "combined dry-run query should pass all requested guards", { observedDryRunPath });
const dryRunRecord = asRecord(dryRunContract);
assertCondition(dryRunRecord.failureKind === "auth-missing", "missing runner token should remain auth-missing even when system gh is absent", dryRunRecord);
const dryRunPreflight = asRecord(dryRunRecord.preflight);
const dryRunAuthBroker = asRecord(dryRunPreflight.authBroker);
const dryRunScopeBoundary = asRecord(dryRunPreflight.scopeBoundary);
const dryRunActiveRunner = asRecord(dryRunPreflight.activeRunnerDevContainer);
assertCondition(dryRunRecord.preflight === undefined, "combined dry-run default output should omit detailed preflight", dryRunRecord);
const dryRunSchedulerPreflight = asRecord(dryRunRecord.schedulerPreflight);
const dryRunAuthBroker = asRecord(dryRunSchedulerPreflight.authBroker);
const dryRunScopeBoundary = asRecord(dryRunRecord.scopeBoundary);
const dryRunActiveRunner = asRecord(dryRunRecord.activeRunnerPrCapability);
assertCondition(dryRunAuthBroker.source === "broker/auth-broker-needed", "missing runner token should expose broker/auth-broker-needed", dryRunAuthBroker);
assertCondition(dryRunAuthBroker.degradedReason === "auth-broker-needed", "auth broker degraded reason should be explicit", dryRunAuthBroker);
assertCondition(dryRunSchedulerPreflight.degradedReason === "auth-broker-needed", "auth broker degraded reason should be explicit", dryRunSchedulerPreflight);
assertCondition(dryRunScopeBoundary.scopesAreIndependent === true, "local compact preflight should expose independent auth scopes", dryRunScopeBoundary);
assertCondition(dryRunActiveRunner.scope === "current-cli-process", "local compact preflight should expose current CLI process capability", dryRunActiveRunner);
const dryRunBrokerEvidence = asRecord(dryRunAuthBroker.evidence);
assertCondition(dryRunBrokerEvidence.systemGhBinaryOk === false, "system gh absence should be reported separately", dryRunBrokerEvidence);
assertCondition(dryRunBrokerEvidence.unideskGhCliOk === true, "UniDesk REST gh CLI should not be marked unavailable because system gh is missing", dryRunBrokerEvidence);
assertCondition(dryRunBrokerEvidence.systemGhMissingMisclassifiedAsUniDeskCliMissing === false, "system gh absence must not be misclassified", dryRunBrokerEvidence);
const dryRunPrContract = asRecord(dryRunPreflight.prCapabilityContract);
assertCondition(dryRunActiveRunner.scope === "current-cli-process", "compact default should expose current CLI process capability", dryRunActiveRunner);
const dryRunPrContract = asRecord(dryRunRecord.prCapability);
assertCondition(dryRunPrContract.systemGhBinaryRequiredForWrites === false, "system gh absence must not be required for UniDesk REST gh writes", dryRunPrContract);
assertCondition(dryRunPrContract.unideskGhCliOk === true, "UniDesk REST gh CLI should not be marked unavailable because system gh is missing", dryRunPrContract);
assertCondition(asRecord(dryRunPrContract.pushDryRun).requested === true, "push dry-run should be requested", dryRunPrContract);
assertCondition(asRecord(dryRunPrContract.pushDryRun).writesRemote === false, "push dry-run must be marked non-writing", dryRunPrContract);
assertCondition(asRecord(dryRunPrContract.prCreateDryRun).requested === true, "PR create dry-run should be requested", dryRunPrContract);
assertCondition(asRecord(dryRunPrContract.prCreateDryRun).writesRemote === false, "PR create dry-run must be marked non-writing", dryRunPrContract);
assertCondition(asRecord(dryRunPrContract.prCreateDryRun).headBranch === "code-queue/issue-35-pr-dry-run-probe", "PR dry-run head should come from the option", dryRunPrContract);
assertCondition(dryRunPrContract.sourceBranch === "code-queue/issue-35-pr-dry-run-probe", "PR dry-run head should come from the option", dryRunPrContract);
const brokerIssuedContract = await codexPrPreflightQueryForTest(["--remote"], {
config: null,
@@ -768,18 +819,17 @@ async function main(): Promise<void> {
});
const brokerIssuedRecord = asRecord(brokerIssuedContract);
assertCondition(brokerIssuedRecord.ok === true, "broker-issued token branch should be ready", brokerIssuedRecord);
const brokerIssuedPreflight = asRecord(brokerIssuedRecord.preflight);
const brokerIssuedTokenCoverage = asRecord(brokerIssuedPreflight.tokenCoverage);
const brokerIssuedAuthBroker = asRecord(brokerIssuedPreflight.authBroker);
const brokerIssuedCapability = asRecord(brokerIssuedAuthBroker.capability);
const brokerIssuedPrContract = asRecord(brokerIssuedPreflight.prCapabilityContract);
assertCondition(brokerIssuedTokenCoverage.source === "auth-broker", "broker-issued branch should use auth-broker token coverage", brokerIssuedTokenCoverage);
assertCondition(brokerIssuedTokenCoverage.credentialSource === "broker-issued-token", "broker-issued branch should expose broker-issued-token capability", brokerIssuedTokenCoverage);
assertCondition(brokerIssuedRecord.preflight === undefined, "broker-issued default output should omit detailed preflight", brokerIssuedRecord);
const brokerIssuedScheduler = asRecord(brokerIssuedRecord.schedulerPreflight);
const brokerIssuedAuthBroker = asRecord(brokerIssuedScheduler.authBroker);
const brokerIssuedPrContract = asRecord(brokerIssuedRecord.prCapability);
assertCondition(brokerIssuedScheduler.authSource === "auth-broker", "broker-issued branch should use auth-broker token coverage", brokerIssuedScheduler);
assertCondition(brokerIssuedScheduler.credentialSource === "broker-issued-token", "broker-issued branch should expose broker-issued-token capability", brokerIssuedScheduler);
assertCondition(brokerIssuedAuthBroker.source === "auth-broker", "broker-issued branch should expose authBroker.source", brokerIssuedAuthBroker);
assertCondition(brokerIssuedAuthBroker.nextAction === "use-auth-broker", "broker-issued branch should expose nextAction", brokerIssuedAuthBroker);
assertCondition(brokerIssuedCapability.systemGhBinaryRequiredForWrites === false, "broker-issued branch should not require system gh binary", brokerIssuedCapability);
assertCondition(brokerIssuedCapability.realPrCreateRequiresCommanderAuthorization === true, "real PR creation should still require commander authorization", brokerIssuedCapability);
assertCondition(asRecord(brokerIssuedPrContract.authBroker).source === "auth-broker", "PR capability should include broker source", brokerIssuedPrContract);
assertCondition(brokerIssuedAuthBroker.capabilitySource === "broker-issued-token", "broker-issued branch should expose broker-issued capability", brokerIssuedAuthBroker);
assertCondition(brokerIssuedPrContract.systemGhBinaryRequiredForWrites === false, "broker-issued branch should not require system gh binary", brokerIssuedPrContract);
assertCondition(brokerIssuedPrContract.realPrCreateRequiresCommanderAuthorization === true, "real PR creation should still require commander authorization", brokerIssuedPrContract);
const envTokenContract = await codexPrPreflightQueryForTest(["--remote"], {
config: null,
@@ -829,11 +879,11 @@ async function main(): Promise<void> {
});
const envTokenRecord = asRecord(envTokenContract);
assertCondition(envTokenRecord.ok === true, "env token branch should be ready", envTokenRecord);
const envTokenPreflight = asRecord(envTokenRecord.preflight);
const envTokenCoverage = asRecord(envTokenPreflight.tokenCoverage);
const envTokenAuthBroker = asRecord(envTokenPreflight.authBroker);
assertCondition(envTokenCoverage.source === "GH_TOKEN", "env token branch should expose GH_TOKEN source", envTokenCoverage);
assertCondition(envTokenCoverage.credentialSource === "env-token", "env token branch should expose env-token capability", envTokenCoverage);
assertCondition(envTokenRecord.preflight === undefined, "env-token default output should omit detailed preflight", envTokenRecord);
const envTokenScheduler = asRecord(envTokenRecord.schedulerPreflight);
const envTokenAuthBroker = asRecord(envTokenScheduler.authBroker);
assertCondition(envTokenScheduler.authSource === "GH_TOKEN", "env token branch should expose GH_TOKEN source", envTokenScheduler);
assertCondition(envTokenScheduler.credentialSource === "env-token", "env token branch should expose env-token capability", envTokenScheduler);
assertCondition(envTokenAuthBroker.source === "GH_TOKEN", "env token branch should not pretend broker is configured", envTokenAuthBroker);
assertCondition(envTokenAuthBroker.nextAction === "use-env-token-until-auth-broker-live", "env token branch should still point at broker migration", envTokenAuthBroker);
@@ -852,25 +902,25 @@ async function main(): Promise<void> {
const missingTokenTopSummary = asRecord(missingTokenRecord.authScopeSummary);
const missingTokenTopScopeBoundary = asRecord(missingTokenRecord.scopeBoundary);
const missingTokenTopActions = Array.isArray(missingTokenRecord.recommendedActions) ? missingTokenRecord.recommendedActions : [];
const missingTokenPreflight = asRecord(missingTokenRecord.preflight);
const missingTokenAuthBroker = asRecord(missingTokenPreflight.authBroker);
const missingTokenSummary = asRecord(missingTokenPreflight.authScopeSummary);
const missingTokenScopeBoundary = asRecord(missingTokenPreflight.scopeBoundary);
const missingTokenActiveRunner = asRecord(missingTokenPreflight.activeRunnerDevContainer);
const missingTokenActions = Array.isArray(missingTokenPreflight.recommendedActions) ? missingTokenPreflight.recommendedActions : [];
const missingTokenCapability = asRecord(missingTokenAuthBroker.capability);
assertCondition(missingTokenRecord.preflight === undefined, "missing-token default output should omit detailed preflight", missingTokenRecord);
const missingTokenScheduler = asRecord(missingTokenRecord.schedulerPreflight);
const missingTokenAuthBroker = asRecord(missingTokenScheduler.authBroker);
const missingTokenScopeBoundary = asRecord(missingTokenRecord.scopeBoundary);
const missingTokenActiveRunner = asRecord(missingTokenRecord.activeRunnerPrCapability);
const missingTokenActions = Array.isArray(missingTokenRecord.recommendedActions) ? missingTokenRecord.recommendedActions : [];
const missingTokenPrCapability = asRecord(missingTokenRecord.prCapability);
assertCondition(missingTokenTopSummary.schedulerAuthMissingIsScoped === true, "missing-token top-level summary should expose scoped scheduler auth missing", missingTokenTopSummary);
assertCondition(missingTokenTopScopeBoundary.schedulerAuthMissingDoesNotMeanActiveRunnerCannotCreatePr === true, "missing-token top-level boundary should be prominent", missingTokenTopScopeBoundary);
assertCondition(Array.isArray(missingTokenRecord.recommendedActions) && missingTokenTopActions.length === 3, "missing-token top-level recommended actions should stay bounded", missingTokenRecord.recommendedActions);
assertCondition(missingTokenAuthBroker.source === "broker/auth-broker-needed", "missing-token branch should expose broker/auth-broker-needed", missingTokenAuthBroker);
assertCondition(missingTokenAuthBroker.nextAction === "configure-auth-broker-or-env-token", "missing-token branch should expose nextAction", missingTokenAuthBroker);
assertCondition(missingTokenCapability.source === "missing-token", "missing-token branch should expose missing-token capability", missingTokenCapability);
assertCondition(missingTokenCapability.systemGhBinaryRequiredForWrites === false, "missing-token branch should still not require system gh binary for UniDesk gh CLI", missingTokenCapability);
assertCondition(missingTokenSummary.schedulerAuthMissingIsScoped === true, "missing-token compact preflight should expose scoped scheduler auth missing", missingTokenSummary);
assertCondition(missingTokenAuthBroker.capabilitySource === "missing-token", "missing-token branch should expose missing-token capability", missingTokenAuthBroker);
assertCondition(missingTokenPrCapability.systemGhBinaryRequiredForWrites === false, "missing-token branch should still not require system gh binary for UniDesk gh CLI", missingTokenPrCapability);
assertCondition(missingTokenTopSummary.schedulerAuthMissingIsScoped === true, "missing-token compact summary should expose scoped scheduler auth missing", missingTokenTopSummary);
assertCondition(String(missingTokenScopeBoundary.currentRunnerCheck ?? "").includes("gh auth status"), "missing-token branch should point to active runner auth check", missingTokenScopeBoundary);
assertCondition(String(missingTokenScopeBoundary.currentRunnerCheck ?? "").includes("gh pr create --dry-run"), "missing-token branch should point to active runner PR create dry-run", missingTokenScopeBoundary);
assertCondition(missingTokenActiveRunner.relationToRemotePreflight === "independent-scope; scheduler-runner-env auth-missing does not prove the active runner/dev container lacks GitHub PR capability", "missing-token branch should not overstate active runner PR capability", missingTokenActiveRunner);
assertCondition(Array.isArray(missingTokenPreflight.recommendedActions) && missingTokenActions.length === 3, "missing-token compact recommended actions should stay bounded", missingTokenPreflight.recommendedActions);
assertCondition(missingTokenActiveRunner.independentOfSchedulerPreflight === true, "missing-token branch should not overstate active runner PR capability", missingTokenActiveRunner);
assertCondition(Array.isArray(missingTokenRecord.recommendedActions) && missingTokenActions.length === 3, "missing-token compact recommended actions should stay bounded", missingTokenRecord.recommendedActions);
assertCondition(missingTokenActions.some((action) => String(asRecord(action).command ?? "").includes("gh auth status")), "missing-token branch should recommend gh auth status", missingTokenActions);
assertCondition(missingTokenActions.some((action) => String(asRecord(action).command ?? "").includes("gh pr create") && asRecord(action).writesRemote === false), "missing-token branch should recommend PR create dry-run without writes", missingTokenActions);
@@ -118,7 +118,7 @@ assertCondition(microserviceCli.includes("compactSkillSync"), "microservice heal
assertCondition(helpSource.includes("codex skills-sync --dry-run"), "CLI help must document the skills sync dry-run command");
assertCondition(docsReference.includes("codex skills-sync --dry-run"), "reference docs must document the skills sync dry-run command");
const preflightSummary = asRecord(codexPrPreflightQueryForTest(["--remote"], {
const skillsPreflightTransport = {
config: null,
coreFetch: () => ({
ok: true,
@@ -169,18 +169,26 @@ const preflightSummary = asRecord(codexPrPreflightQueryForTest(["--remote"], {
},
},
}),
}), "preflight summary");
};
const defaultPreflightSummary = asRecord(codexPrPreflightQueryForTest(["--remote"], skillsPreflightTransport), "default preflight summary");
assertCondition(defaultPreflightSummary.failureKind === "runner-skills-blocker", "default preflight should classify missing skills as runner-skills-blocker", defaultPreflightSummary);
assertCondition(defaultPreflightSummary.degradedReason === "unapproved-target", "default preflight degraded reason should use the skills sync blocker first", defaultPreflightSummary);
assertCondition(defaultPreflightSummary.preflight === undefined, "default PR preflight should omit detailed preflight internals", defaultPreflightSummary);
assertCondition(asRecord(defaultPreflightSummary.disclosure, "defaultPreflightSummary.disclosure").fullDetailOmitted === true, "default PR preflight should disclose full detail omission", defaultPreflightSummary.disclosure);
assertCondition(String(asRecord(defaultPreflightSummary.disclosure, "defaultPreflightSummary.disclosure").expandWith ?? "").includes("--full"), "default PR preflight should point to --full expansion", defaultPreflightSummary.disclosure);
const preflightSummary = asRecord(codexPrPreflightQueryForTest(["--remote", "--full"], skillsPreflightTransport), "full preflight summary");
const preflight = asRecord(preflightSummary.preflight, "preflight");
const preflightSkills = asRecord(preflight.skills, "preflight.skills");
const preflightSkillsSync = asRecord(preflight.skillsSync, "preflight.skillsSync");
assertCondition(preflightSummary.failureKind === "runner-skills-blocker", "compact preflight should classify missing skills as runner-skills-blocker", preflightSummary);
assertCondition(preflightSummary.degradedReason === "unapproved-target", "compact preflight degraded reason should use the skills sync blocker first", preflightSummary);
assertCondition(preflightSkills.target === "/path/that/does/not/exist/for-code-queue-skills-test", "compact preflight must show skills target", preflightSkills);
assertCondition(preflightSkillsSync.dryRun === true && preflightSkillsSync.mutation === false, "compact preflight must show non-mutating skills sync dry-run", preflightSkillsSync);
assertCondition(asRecord(preflightSkillsSync.counts, "preflight.skillsSync.counts").missingTargetSkills === 2, "compact preflight must show missing target count", preflightSkillsSync);
assertCondition(asRecord(preflightSkillsSync.plannedActions, "preflight.skillsSync.plannedActions").copy === false, "compact preflight must show no copy action", preflightSkillsSync);
assertCondition(preflightSkillsSync.valuesPrinted === false, "compact preflight skills sync must declare valuesPrinted=false", preflightSkillsSync);
assertCondition(!JSON.stringify(preflightSkillsSync).includes(forbiddenPathLiteral), "compact preflight must not propagate misspelled path literal");
assertCondition(preflightSummary.failureKind === "runner-skills-blocker", "full preflight should classify missing skills as runner-skills-blocker", preflightSummary);
assertCondition(preflightSummary.degradedReason === "unapproved-target", "full preflight degraded reason should use the skills sync blocker first", preflightSummary);
assertCondition(preflightSkills.target === "/path/that/does/not/exist/for-code-queue-skills-test", "full preflight must show skills target", preflightSkills);
assertCondition(preflightSkillsSync.dryRun === true && preflightSkillsSync.mutation === false, "full preflight must show non-mutating skills sync dry-run", preflightSkillsSync);
assertCondition(asRecord(preflightSkillsSync.counts, "preflight.skillsSync.counts").missingTargetSkills === 2, "full preflight must show missing target count", preflightSkillsSync);
assertCondition(asRecord(preflightSkillsSync.plannedActions, "preflight.skillsSync.plannedActions").copy === false, "full preflight must show no copy action", preflightSkillsSync);
assertCondition(preflightSkillsSync.valuesPrinted === false, "full preflight skills sync must declare valuesPrinted=false", preflightSkillsSync);
assertCondition(!JSON.stringify(preflightSkillsSync).includes(forbiddenPathLiteral), "full preflight must not propagate misspelled path literal");
const healthSummary = asRecord(summarizeMicroserviceObservation("health", "code-queue", {
ok: true,
+209 -10
View File
@@ -4180,9 +4180,193 @@ function prPreflightAuthScopeSummary(tokenCoverage: Record<string, unknown> | nu
};
}
function prPreflightTokenCoverage(record: Record<string, unknown>): Record<string, unknown> | null {
const preflight = asRecord(record.preflight);
const schedulerPreflight = asRecord(record.schedulerPreflight);
const schedulerAuth = asRecord(schedulerPreflight?.auth);
const schedulerSummary = schedulerPreflight === null ? null : {
ok: schedulerPreflight.authReady ?? null,
source: schedulerPreflight.authSource ?? null,
credentialSource: schedulerPreflight.credentialSource ?? null,
scope: schedulerPreflight.scope ?? null,
missing: Array.isArray(schedulerPreflight.missing) && schedulerPreflight.missing.length > 0
? schedulerPreflight.missing.map(String)
: schedulerPreflight.authReady === false
? ["GH_TOKEN", "GITHUB_TOKEN"]
: [],
runnerDisposition: schedulerPreflight.authReady === true ? "ready" : "infra-blocked",
};
return asRecord(record.tokenCoverage)
?? asRecord(preflight?.tokenCoverage)
?? schedulerAuth
?? schedulerSummary
?? null;
}
function prPreflightAuthBroker(record: Record<string, unknown>): Record<string, unknown> | null {
const preflight = asRecord(record.preflight);
const schedulerPreflight = asRecord(record.schedulerPreflight);
return asRecord(record.authBroker)
?? asRecord(preflight?.authBroker)
?? asRecord(schedulerPreflight?.authBroker)
?? null;
}
function prPreflightCapabilityContract(record: Record<string, unknown>): Record<string, unknown> | null {
const preflight = asRecord(record.preflight);
const prCapability = asRecord(record.prCapability);
return asRecord(record.prCapabilityContract)
?? asRecord(preflight?.prCapabilityContract)
?? prCapability
?? null;
}
function prPreflightCommandSet(record: Record<string, unknown>, options: CodexPrPreflightOptions): Record<string, unknown> {
const preflight = asRecord(record.preflight);
const commands = asRecord(record.commands) ?? asRecord(preflight?.commands) ?? {};
const activeRunnerDevContainer = asRecord(record.activeRunnerDevContainer) ?? activeRunnerDevContainerCapability();
const activeCommands = asRecord(activeRunnerDevContainer.commands) ?? {};
const remoteFlag = options.remote ? " --remote" : "";
const fullDetail = `bun scripts/cli.ts codex pr-preflight${remoteFlag} --full`;
return {
verifyActiveRunnerAuth: activeCommands.authStatus ?? commands.local ?? "bun scripts/cli.ts gh auth status --repo pikasTech/unidesk",
verifyActiveRunnerPrCreateDryRun: activeCommands.prCreateDryRun ?? "bun scripts/cli.ts gh pr create --repo pikasTech/unidesk --title <title> --body-file <file> --base master --head <head> --dry-run",
rerunSchedulerPreflight: commands.runner ?? `bun scripts/cli.ts codex pr-preflight${remoteFlag}`,
fullDetail,
rawProxy: commands.rawProxy ?? "bun scripts/cli.ts microservice proxy code-queue /api/runtime-preflight?remote=1 --raw --full",
schedulerAuthSource: "configure auth-broker or inject GH_TOKEN/GITHUB_TOKEN into the scheduler runtime secret",
};
}
function compactRecommendedActions(record: Record<string, unknown>): Record<string, unknown>[] {
const actions = Array.isArray(record.recommendedActions) ? record.recommendedActions : [];
if (actions.length > 0) {
return actions.slice(0, 3).map((item) => {
const action = asRecord(item) ?? {};
return {
scope: action.scope ?? null,
priority: action.priority ?? null,
action: action.action ?? null,
command: action.command ?? null,
writesRemote: action.writesRemote ?? false,
};
});
}
const tokenCoverage = prPreflightTokenCoverage(record);
return prPreflightRecommendedActions(tokenCoverage);
}
function compactPrPreflightCommanderView(record: Record<string, unknown>, options: CodexPrPreflightOptions): Record<string, unknown> {
if (options.full) return record;
const tokenCoverage = prPreflightTokenCoverage(record);
const authBroker = prPreflightAuthBroker(record);
const capability = prPreflightCapabilityContract(record);
const activeRunnerDevContainer = asRecord(record.activeRunnerDevContainer) ?? activeRunnerDevContainerCapability();
const activeCommands = asRecord(activeRunnerDevContainer.commands) ?? {};
const authBrokerCapability = asRecord(authBroker?.capability);
const expectedPrHandoff = asRecord(capability?.expectedPrHandoff);
const unideskGhCli = asRecord(capability?.unideskGhCli);
const pushDryRun = asRecord(capability?.pushDryRun);
const prCreateDryRun = asRecord(capability?.prCreateDryRun);
const unsupportedMergeBoundary = asRecord(capability?.unsupportedMergeBoundary);
const commands = prPreflightCommandSet(record, options);
const schedulerAuthReady = tokenCoverage?.ok === true;
const activeRunnerTokenCandidatePresent = activeRunnerDevContainer.ok === true;
const failureKind = record.failureKind ?? (schedulerAuthReady ? null : "auth-missing");
const degradedReason = record.degradedReason ?? (schedulerAuthReady ? null : "auth-broker-needed");
const githubTransient = asRecord(record.githubTransient);
return {
ok: record.ok === true,
runnerDisposition: record.runnerDisposition ?? (record.ok === true ? "ready" : "infra-blocked"),
failureKind,
degradedReason,
...(record.retryable === true ? { retryable: true } : {}),
...(typeof record.commanderAction === "string" ? { commanderAction: record.commanderAction } : {}),
...(githubTransient === null ? {} : { githubTransient }),
summary: {
status: record.ok === true ? "ready" : "blocked",
schedulerPreflightAuthReady: schedulerAuthReady,
schedulerPreflightScope: tokenCoverage?.scope ?? "scheduler-runner-env",
activeRunnerTokenCandidatePresent,
activeRunnerScope: activeRunnerDevContainer.scope ?? "current-cli-process",
interpretation: schedulerAuthReady
? "scheduler preflight auth is ready; still use active-runner dry-runs before writes"
: "scheduler auth is missing for preflight admission only; this does not prove the active runner/dev container cannot create or comment PRs",
},
schedulerPreflight: {
scope: tokenCoverage?.scope ?? "scheduler-runner-env",
authReady: schedulerAuthReady,
authSource: tokenCoverage?.source ?? null,
credentialSource: tokenCoverage?.credentialSource ?? null,
missing: Array.isArray(tokenCoverage?.missing) ? tokenCoverage.missing.map(String) : [],
failureKind: schedulerAuthReady ? null : failureKind,
degradedReason: schedulerAuthReady ? null : degradedReason,
authBroker: authBroker === null ? null : {
ok: authBroker.ok ?? schedulerAuthReady,
source: authBroker.source ?? null,
configured: authBroker.configured ?? null,
needed: authBroker.needed ?? !schedulerAuthReady,
nextAction: authBroker.nextAction ?? null,
capabilitySource: authBrokerCapability?.source ?? tokenCoverage?.credentialSource ?? null,
},
},
activeRunnerPrCapability: {
scope: activeRunnerDevContainer.scope ?? "current-cli-process",
tokenCandidatePresent: activeRunnerTokenCandidatePresent,
credentialSource: activeRunnerDevContainer.credentialSource ?? null,
ghTokenPresent: activeRunnerDevContainer.ghTokenPresent ?? false,
githubTokenPresent: activeRunnerDevContainer.githubTokenPresent ?? false,
independentOfSchedulerPreflight: activeRunnerDevContainer.notEquivalentToSchedulerEnv ?? true,
verifyCommands: {
authStatus: activeCommands.authStatus ?? commands.verifyActiveRunnerAuth,
prCreateDryRun: activeCommands.prCreateDryRun ?? commands.verifyActiveRunnerPrCreateDryRun,
},
},
prCapability: capability === null ? null : {
targetBranch: capability.targetBranch ?? "master",
sourceBranch: expectedPrHandoff?.sourceBranch ?? prCreateDryRun?.headBranch ?? null,
unideskGhCliOk: unideskGhCli?.ok ?? null,
systemGhBinaryRequiredForWrites: capability.systemGhBinaryRequiredForWrites ?? false,
realPrCreateRequiresCommanderAuthorization: capability.realPrCreateRequiresCommanderAuthorization ?? true,
pushDryRun: pushDryRun === null ? null : {
requested: pushDryRun.requested ?? false,
writesRemote: pushDryRun.writesRemote ?? false,
commandShape: pushDryRun.commandShape ?? null,
},
prCreateDryRun: prCreateDryRun === null ? null : {
requested: prCreateDryRun.requested ?? false,
writesRemote: prCreateDryRun.writesRemote ?? false,
commandShape: prCreateDryRun.commandShape ?? null,
},
mergeSupported: unsupportedMergeBoundary?.supported ?? false,
mergeCommand: unsupportedMergeBoundary?.command ?? "bun scripts/cli.ts gh pr merge <number> --repo pikasTech/unidesk",
},
authScopeSummary: record.authScopeSummary ?? prPreflightAuthScopeSummary(tokenCoverage, activeRunnerDevContainer),
scopeBoundary: record.scopeBoundary ?? prPreflightScopeBoundary(tokenCoverage),
recommendedActions: compactRecommendedActions(record),
upstream: record.upstream ?? null,
controlPlane: record.controlPlane ?? null,
observationGap: record.observationGap ?? undefined,
localObservationGap: record.localObservationGap ?? undefined,
localObservationSummary: record.localObservationSummary ?? undefined,
remoteObservationSummary: record.remoteObservationSummary ?? undefined,
commands,
disclosure: {
defaultView: "commander-compact",
fullDetailOmitted: true,
fullObservationsOmitted: asRecord(record.disclosure)?.fullObservationsOmitted ?? true,
expandWith: commands.fullDetail,
rawProxy: commands.rawProxy,
detailFieldsOmitted: ["preflight", "tools", "agentPorts", "git", "egress", "remote", "limitations", "risks", "rawRuntimePreflight"],
},
};
}
function decoratePrPreflightScopeBoundary(record: Record<string, unknown>): Record<string, unknown> {
const preflight = asRecord(record.preflight);
const tokenCoverage = asRecord(record.tokenCoverage) ?? asRecord(preflight?.tokenCoverage);
const tokenCoverage = prPreflightTokenCoverage(record);
const scopeBoundary = prPreflightScopeBoundary(tokenCoverage);
const activeRunnerDevContainer = activeRunnerDevContainerCapability();
const authScopeSummary = prPreflightAuthScopeSummary(tokenCoverage, activeRunnerDevContainer);
@@ -4224,7 +4408,20 @@ function prPreflightObservationGap(kind: Exclude<CodeQueueObservationGapKind, nu
function prPreflightObservationSummary(record: Record<string, unknown> | null): Record<string, unknown> | null {
if (record === null) return null;
const preflight = asRecord(record.preflight);
const tokenCoverage = asRecord(record.tokenCoverage) ?? asRecord(preflight?.tokenCoverage);
const schedulerPreflight = asRecord(record.schedulerPreflight);
const tokenCoverage = asRecord(record.tokenCoverage)
?? asRecord(preflight?.tokenCoverage)
?? (schedulerPreflight === null ? null : {
ok: schedulerPreflight.authReady ?? null,
source: schedulerPreflight.authSource ?? null,
credentialSource: schedulerPreflight.credentialSource ?? null,
scope: schedulerPreflight.scope ?? null,
missing: Array.isArray(schedulerPreflight.missing) && schedulerPreflight.missing.length > 0
? schedulerPreflight.missing.map(String)
: schedulerPreflight.authReady === false
? ["GH_TOKEN", "GITHUB_TOKEN"]
: [],
});
const controlPlane = asRecord(record.controlPlane);
const targetStack = asRecord(record.targetStack);
return {
@@ -4561,7 +4758,7 @@ function codeQueuePrPreflight(optionArgs: string[] = [], transport: CodeQueuePrP
const remoteRecord = asRecord(remoteResponse);
if (remoteRecord !== null) {
if (remoteRecord.ok === false) {
return decoratePrPreflightScopeBoundary({
return compactPrPreflightCommanderView(decoratePrPreflightScopeBoundary({
...remoteRecord,
observationGap: prPreflightObservationGap(
remoteRecord.failureKind === "control-plane-missing" ? "control-plane-observation-gap" : "runner-local-observation-gap",
@@ -4584,9 +4781,9 @@ function codeQueuePrPreflight(optionArgs: string[] = [], transport: CodeQueuePrP
remoteFallbackUsed: true,
},
...maybeFullPrPreflightObservations(options, localRecord, remoteRecord),
});
}), options);
}
return decoratePrPreflightScopeBoundary({
return compactPrPreflightCommanderView(decoratePrPreflightScopeBoundary({
...remoteRecord,
localObservationGap: prPreflightObservationGap("runner-local-observation-gap", {
reason: "local backend-core target-stack absence was bypassed by healthy remote control-plane fallback",
@@ -4603,7 +4800,7 @@ function codeQueuePrPreflight(optionArgs: string[] = [], transport: CodeQueuePrP
remoteFallbackUsed: true,
},
...maybeFullPrPreflightObservations(options, localRecord, remoteRecord),
});
}), options);
}
}
if (localRecord?.ok !== true) {
@@ -4693,13 +4890,14 @@ function codeQueuePrPreflight(optionArgs: string[] = [], transport: CodeQueuePrP
};
}
const compact = compactPrRuntimePreflight(preflight, options);
return {
return compactPrPreflightCommanderView({
ok: compact.ok,
runnerDisposition: compact.runnerDisposition,
failureKind: compact.failureKind ?? null,
degradedReason: compact.degradedReason ?? null,
...(compact.retryable === true ? { retryable: true } : {}),
...(typeof compact.commanderAction === "string" ? { commanderAction: compact.commanderAction } : {}),
...(asRecord(compact.githubTransient) === null ? {} : { githubTransient: compact.githubTransient }),
authScopeSummary: compact.authScopeSummary,
scopeBoundary: compact.scopeBoundary,
activeRunnerDevContainer: compact.activeRunnerDevContainer,
@@ -4711,7 +4909,7 @@ function codeQueuePrPreflight(optionArgs: string[] = [], transport: CodeQueuePrP
remoteFallbackUsed: false,
},
preflight: compact,
};
}, options);
}
export function codexPrPreflightQueryForTest(optionArgs: string[], transport: CodeQueuePrPreflightTransport = {}): unknown {
@@ -4751,13 +4949,14 @@ export async function codexPrPreflightQueryAsync(optionArgs: string[], fetcher:
};
}
const compact = compactPrRuntimePreflight(preflight, options);
return {
return compactPrPreflightCommanderView({
ok: compact.ok,
runnerDisposition: compact.runnerDisposition,
failureKind: compact.failureKind ?? null,
degradedReason: compact.degradedReason ?? null,
...(compact.retryable === true ? { retryable: true } : {}),
...(typeof compact.commanderAction === "string" ? { commanderAction: compact.commanderAction } : {}),
...(asRecord(compact.githubTransient) === null ? {} : { githubTransient: compact.githubTransient }),
authScopeSummary: compact.authScopeSummary,
scopeBoundary: compact.scopeBoundary,
activeRunnerDevContainer: compact.activeRunnerDevContainer,
@@ -4769,7 +4968,7 @@ export async function codexPrPreflightQueryAsync(optionArgs: string[], fetcher:
remoteFallbackUsed: false,
},
preflight: compact,
};
}, options);
}
export function codexSubmitRoutingRecommendationForTest(prompt: string, model?: string): SubmitRoutingRecommendation {
+2 -2
View File
@@ -54,7 +54,7 @@ export function rootHelp(): unknown {
{ command: "codex deploy <commitId> [--provider-id D601] [--timeout-ms N]", description: "Disabled legacy Code Queue deploy path; use the dev-only artifact consumer instead." },
{ 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, while real success only confirms the write and task id." },
{ 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 pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/<name>] [--pr-create-dry-run --pr-create-dry-run-head <head>] [--issue N]", description: "Read-only PR admission check against the D601 scheduler/runner token, GitHub egress, repo visibility, skills lifecycle health, optional push dry-run, and PR body/create dry-run guard." },
{ command: "codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/<name>] [--pr-create-dry-run --pr-create-dry-run-head <head>] [--issue N] [--full|--raw]", 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." },
{ command: "codex task <taskId> [--detail] [--trace --tail|--from-start|--after-seq N|--before-seq N --limit N] [--full]", description: "Fetch the bounded review view by default; --detail is still capped, while --full/trace/output explicitly expand evidence." },
{ command: "codex tasks [--view supervisor|full] [--queue id] [--status status[,status]] [--unread|--unread-only] [--limit N] [--before-id id]", description: "Show the low-noise supervisor view by default: compact task rows, tiny local sections, activity counts, diagnostics, and drill-down commands; use --view full for detailed rows." },
{ command: "codex output <taskId> [--tail|--from-start|--after-seq N|--before-seq N --limit N] [--full-text]", description: "Fetch paged raw Code Queue output records; default caps large limits/text previews, --full-text explicitly expands one seq window." },
@@ -258,7 +258,7 @@ function codexHelp(): unknown {
"bun scripts/cli.ts codex read <taskId>",
"bun scripts/cli.ts codex dev-ready",
"bun scripts/cli.ts codex skills-sync --dry-run [--full]",
"bun scripts/cli.ts codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/<name>] [--pr-create-dry-run --pr-create-dry-run-head <head>] [--issue N]",
"bun scripts/cli.ts codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/<name>] [--pr-create-dry-run --pr-create-dry-run-head <head>] [--issue N] [--full|--raw]",
"bun scripts/cli.ts codex judge <taskId> --attempt N [--dry-run] [--include-prompt]",
"bun scripts/cli.ts codex steer <taskId> [prompt|--prompt-file path|--prompt-stdin] [--dry-run] [--no-retry|--retry-attempts N]",
"bun scripts/cli.ts codex interrupt|cancel <taskId>",