fix(code-queue): harden codex submit prompt input
Host commander merge after read-only audit and post-#72 mergeability refresh. PR #71 is CLEAN/MERGEABLE and limited to UniDesk Code Queue CLI prompt input hardening, help/check docs, and submit prompt contract test. It does not touch HWLAB business code.
This commit is contained in:
@@ -44,7 +44,7 @@ CLI 可以从 `master` 快速演进,但必须兼容 `deploy.json` 固定的 CI
|
||||
- `ci install|status|run|publish-backend-core|publish-user-service|run-dev-e2e|logs` 管理 D601 原生 k3s 上的 Tekton CI。`run` 手动创建每 commit 检查和 Code Queue 只读性能门禁;`publish-backend-core` 与 `publish-user-service` 从 pushed Git commit 构建并发布 `127.0.0.1:5000/unidesk/<service>:<commit>` commit-pinned artifacts,输出 `artifactSummary`(含 `serviceId`、`sourceCommit`、`sourceRepo`、`dockerfile`、`imageRef`、`tag`、`digest`、`digestRef`),但不部署生产;`run-dev-e2e` 的 Git 控制 runner、短 launcher、host fetch 边界、临时 smoke namespace 和 no-CD 规则只在 `docs/reference/dev-ci-runner.md` 定义;Tekton CI 通用规则见 `docs/reference/ci.md`。
|
||||
- `schedule list|get|runs|run|retry-run|delete|upsert-pgdata-backup` 管理 backend-core 定时任务和运行历史。`schedule list`、`schedule get`、`schedule runs --limit N` 和 `schedule runs <scheduleId> --limit N` 是只读观察入口;`schedule run`、`schedule retry-run`、`schedule delete` 和 `schedule upsert-pgdata-backup` 会触发运行或写入配置,生产恢复时必须有明确授权。`schedule runs --limit N` 是全局历史视图,返回 `scope=global` 和 `scheduleId=null`;`schedule runs <scheduleId> --limit N` 是指定 schedule 历史视图,返回 `scope=schedule` 和对应 `scheduleId`。CLI 必须拒绝 `schedule runs 50` 这类纯数字位置参数,并提示使用 `schedule runs --limit 50`,避免把空数组误判成“没有历史 run”。`schedule run <id> --wait-ms N` 触发同一 schedule,并且即使 wait 超时也必须返回 `newRunId` 和 `observeCommand`;`schedule retry-run <failedRunId>` 只接受 failed run,从原 run 反查 `scheduleId` 后重触发同一 schedule,并输出 `originalRunId`、`scheduleId`、`newRunId` 和 `observeCommand`。当 backend-core 目标容器缺失或只观察到 verify-only 容器时,schedule/microservice 命令必须以非零退出并返回 `failureKind=target-stack-not-running`、`runnerDisposition=infra-blocked`、`readOnlyCommands` 和 `authorizationRequiredForRecovery`,不得把 Docker 的 `No such container` 当成成功的空历史。
|
||||
- `codex deploy <commitId>` 是旧 Code Queue 兼容部署入口,已禁用以防止维护通道直连 D601 部署 Code Queue;当前 dev 自动化只做 `ci run-dev-e2e` smoke,不提供 Code Queue CD,详细规则见 `docs/reference/codex-deploy.md`。
|
||||
- `codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue queueId] [--provider-id id] [--cwd path] [--model model] [--reasoning-effort effort] [--execution-mode mode] [--max-attempts N] [--reference-task-id id] [--dry-run]` 通过 backend-core 私有代理向稳定 `code-queue` 用户服务路径提交任务;prompt 必须且只能来自位置参数、文件或 stdin 之一,`--dry-run` 只返回结构化请求且不实际入队。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`,不能套用任务详情的预览截断策略,否则长任务 prompt 无法被人工验收。真实提交会经过本机本地串行化保护和短节流,避免同一指挥端并发 submit 把低内存主机或 `code-queue-mgr` 控制面打抖;返回值会附带 `submitConcurrencyGuard` 说明本次提交的锁与等待信息。backend-core 默认把提交、队列 CRUD、已读状态、历史摘要和轻量 Trace 读取分流到主 server `code-queue-mgr`,由它写入主 PostgreSQL;D601 scheduler 只轮询并执行已入库任务。
|
||||
- `codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue queueId] [--provider-id id] [--cwd path] [--model model] [--reasoning-effort effort] [--execution-mode mode] [--max-attempts N] [--reference-task-id id] [--dry-run]` 通过 backend-core 私有代理向稳定 `code-queue` 用户服务路径提交任务;prompt 必须且只能来自位置参数、文件或 stdin 之一,`--dry-run` 只返回结构化请求且不实际入队。长 prompt、多行 prompt、含引号/反引号/Markdown 表格/JSON/反斜杠的 prompt 必须优先用 `--prompt-stdin` 或 `--prompt-file`,不要拼进 shell 单个参数;位置参数只适合短单行 smoke prompt。stdin 推荐用 quoted heredoc:`cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue <id> --dry-run`,文件路径推荐 `bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue <id> --dry-run`,确认 dry-run 后移除 `--dry-run` 提交同一 payload。dry-run 会额外输出 `routingRecommendation`,包含推荐 route、runner、model、风险信号、prompt 自包含/issue 非唯一来源/prod-secret-DB 禁止/运行态或 release 禁止/证据要求/中等复杂度候选等 guard 状态;同时输出 `policyContract`,固定暴露 GPT-5.5、DeepSeek、MiniMax 的风险分层、并发上限和外部 provider 429 退避处置。该建议只用于指挥官 preflight,不会改写 payload,不改变 runtime admission,也不假设生产 MiniMax 或 DeepSeek 可用。提交确认和 dry-run 必须返回完整 prompt、字符数和 `truncated=false`,不能套用任务详情的预览截断策略,否则长任务 prompt 无法被人工验收。真实提交会经过本机本地串行化保护和短节流,避免同一指挥端并发 submit 把低内存主机或 `code-queue-mgr` 控制面打抖;返回值会附带 `submitConcurrencyGuard` 说明本次提交的锁与等待信息。backend-core 默认把提交、队列 CRUD、已读状态、历史摘要和轻量 Trace 读取分流到主 server `code-queue-mgr`,由它写入主 PostgreSQL;D601 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 能力。`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 task <taskId>` 通过 Code Queue 私有代理按任务 ID 查询结构化审阅摘要;默认只返回任务身份、执行 Provider、工作目录、attempt 计数、原始 prompt、最终 response、最后错误和渐进披露命令,适合指挥官审阅完成未读任务且避免上下文爆炸。需要旧式详细摘要时显式加 `--detail`;需要完整 prompt/response 文本时加 `--full`;需要工具调用、judge、attempt 全量摘要时使用 `--detail --full --tool-limit N`。该摘要读取默认由主 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` 是低噪声指挥官视图,只返回 `running`、`completedUnread`、`recentCompleted`、`queued` 和 `executionDiagnostics` 的紧凑行;prompt/body 只给短预览和原始字符数,`recentCompleted` 默认最多返回 5 条且不重复 `completedUnread` 未读终态,不嵌入完整 Trace、final response 或全量 overview。每个条目只保留 `commands.show/detail/trace/output/full/read` 作为精确展开入口,并带 `classification` 标记直接推进、部署修复、验证/报告噪声等类别,帮助指挥官按 #131 聚焦真实推进而不是被 Gate/报告/审查任务牵引。`--unread` 是 `--unread-only` 的别名,必须只保留未读终态;`--status` 必须真实过滤支持的状态,未知参数或未知状态必须结构化失败,不能静默忽略。需要更详细当前页任务行时显式使用 `--view full` 或 `--full`,仍受 `--limit` 和 `--before-id` 分页约束。
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
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`。这些字段写入任务 payload 后由 `code-queue-mgr` 入 PostgreSQL,核心任务字段包括 `queue_id`、`provider_id`、`execution_mode`、`model`、`cwd`、`prompt/base_prompt`、`reference_task_ids`、`reasoning_effort`、`max_attempts` 和 `task_json`;队列记录至少有 `id/name/created_at/updated_at`。模型治理应优先看任务 payload 和数据库字段,不靠 worker final response 自报。
|
||||
当前提交合同由 `bun scripts/cli.ts codex submit` 暴露:prompt 必须来自位置参数、`--prompt-file` 或 `--prompt-stdin`;可选字段包括 `--queue/--queue-id`、`--provider-id/--provider`、`--cwd/--workdir`、`--model`、`--reasoning-effort`、`--execution-mode/--mode`、`--max-attempts` 和 `--reference-task-id/--reference/--ref`。长 prompt、多行 prompt、含引号/反引号/Markdown 表格/JSON/反斜杠的 prompt 应使用 `--prompt-stdin` 或 `--prompt-file`,例如 `cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue <id> --dry-run` 或 `bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue <id> --dry-run`;位置参数只适合短单行 smoke prompt。提交前先用 `--dry-run` 检查完整 payload,确认后移除 `--dry-run`。这些字段写入任务 payload 后由 `code-queue-mgr` 入 PostgreSQL,核心任务字段包括 `queue_id`、`provider_id`、`execution_mode`、`model`、`cwd`、`prompt/base_prompt`、`reference_task_ids`、`reasoning_effort`、`max_attempts` 和 `task_json`;队列记录至少有 `id/name/created_at/updated_at`。模型治理应优先看任务 payload 和数据库字段,不靠 worker final response 自报。
|
||||
|
||||
运行态默认模型仍是 `gpt-5.5`。`CODE_QUEUE_MODELS` 当前长期合同至少包含 GPT-5.5、GPT-5.4、GPT-5.4 Mini、DeepSeek Chat 和 MiniMax M2.7;`deepseek`/`deepseek-chat` 与 `minimax-m2.7` 会走 OpenCode port,其余模型走 Codex port。只有当执行面 `/health` 或等价配置已经显示 DeepSeek 模型可用、并完成轻量 runner smoke 后,才允许真实提交 `--model deepseek-chat`。
|
||||
|
||||
|
||||
+37
-1
@@ -30,11 +30,47 @@ const commandName = displayCommandName(args);
|
||||
|
||||
function displayCommandName(parts: string[]): string {
|
||||
if (parts.length === 0) return "help";
|
||||
if (parts[0] === "codex" && (parts[1] === "submit" || parts[1] === "enqueue")) {
|
||||
const shown = ["codex", parts[1]];
|
||||
const shownValueOptions = new Set([
|
||||
"--prompt-file",
|
||||
"--file",
|
||||
"--queue",
|
||||
"--queue-id",
|
||||
"--provider",
|
||||
"--provider-id",
|
||||
"--cwd",
|
||||
"--workdir",
|
||||
"--model",
|
||||
"--reasoning-effort",
|
||||
"--execution-mode",
|
||||
"--mode",
|
||||
"--max-attempts",
|
||||
"--reference-task-id",
|
||||
"--reference",
|
||||
"--ref",
|
||||
]);
|
||||
const hasPromptFile = parts.includes("--prompt-file") || parts.includes("--file");
|
||||
const hasPromptStdin = parts.includes("--prompt-stdin") || parts.includes("--stdin");
|
||||
const hasHelp = parts.slice(2).some(isHelpToken);
|
||||
if (!hasPromptFile && !hasPromptStdin && !hasHelp) shown.push("<prompt:redacted>");
|
||||
for (let index = 2; index < parts.length; index += 1) {
|
||||
const part = parts[index] ?? "";
|
||||
if (!part.startsWith("--")) continue;
|
||||
shown.push(part);
|
||||
if (shownValueOptions.has(part)) {
|
||||
shown.push(parts[index + 1] ?? "<missing>");
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
return shown.join(" ");
|
||||
}
|
||||
if (parts[0] === "codex" && parts[1] === "steer" && parts[2] !== undefined) {
|
||||
const shown = ["codex", "steer", parts[2]];
|
||||
const hasPromptFile = parts.includes("--prompt-file") || parts.includes("--file");
|
||||
const hasPromptStdin = parts.includes("--prompt-stdin") || parts.includes("--stdin");
|
||||
if (!hasPromptFile && !hasPromptStdin) shown.push("<prompt:redacted>");
|
||||
const hasHelp = parts.slice(3).some(isHelpToken);
|
||||
if (!hasPromptFile && !hasPromptStdin && !hasHelp) shown.push("<prompt:redacted>");
|
||||
for (let index = 3; index < parts.length; index += 1) {
|
||||
const part = parts[index] ?? "";
|
||||
if (!part.startsWith("--")) continue;
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
|
||||
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
|
||||
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
||||
}
|
||||
|
||||
function runCli(args: string[], stdin?: string): { status: number | null; stdout: string; stderr: string; json: JsonRecord | null } {
|
||||
const result = spawnSync("bun", ["scripts/cli.ts", ...args], {
|
||||
cwd: process.cwd(),
|
||||
input: stdin,
|
||||
encoding: "utf8",
|
||||
});
|
||||
const stdout = String(result.stdout || "");
|
||||
let json: JsonRecord | null = null;
|
||||
try {
|
||||
json = JSON.parse(stdout) as JsonRecord;
|
||||
} catch {
|
||||
json = null;
|
||||
}
|
||||
return {
|
||||
status: result.status,
|
||||
stdout,
|
||||
stderr: String(result.stderr || ""),
|
||||
json,
|
||||
};
|
||||
}
|
||||
|
||||
function nestedRecord(value: unknown, path: string[]): JsonRecord {
|
||||
let current: unknown = value;
|
||||
for (const key of path) {
|
||||
assertCondition(current !== null && typeof current === "object" && !Array.isArray(current), "expected object while traversing JSON", { path, key, current });
|
||||
current = (current as JsonRecord)[key];
|
||||
}
|
||||
assertCondition(current !== null && typeof current === "object" && !Array.isArray(current), "expected nested object", { path, current });
|
||||
return current as JsonRecord;
|
||||
}
|
||||
|
||||
function stringArray(value: unknown): string[] {
|
||||
return Array.isArray(value) ? value.map((item) => String(item)) : [];
|
||||
}
|
||||
|
||||
function assertDryRunPrompt(response: JsonRecord, expectedText: string): void {
|
||||
assertCondition(response.ok === true, "submit dry-run should succeed", response);
|
||||
const data = nestedRecord(response.data, []);
|
||||
assertCondition(data.dryRun === true, "submit dry-run should expose dryRun=true", data);
|
||||
const request = nestedRecord(response.data, ["request"]);
|
||||
const prompt = nestedRecord(request, ["prompt"]);
|
||||
assertCondition(prompt.text === expectedText, "submit dry-run prompt text mismatch", prompt);
|
||||
assertCondition(prompt.chars === expectedText.length, "submit dry-run prompt char count mismatch", prompt);
|
||||
assertCondition(prompt.truncated === false, "submit dry-run prompt must expose the full prompt", prompt);
|
||||
}
|
||||
|
||||
export function runCodeQueueCliSubmitPromptContract(): JsonRecord {
|
||||
const multilinePrompt = [
|
||||
"Goal: verify stdin prompt path",
|
||||
"JSON: {\"quote\":\"'single' and \\\"double\\\"\"}",
|
||||
"Markdown: `code` | table | value",
|
||||
"Backslash: C:\\tmp\\prompt",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
const stdin = runCli(["codex", "submit", "--prompt-stdin", "--queue", "prompt-contract", "--dry-run"], multilinePrompt);
|
||||
assertDryRunPrompt(stdin.json ?? {}, multilinePrompt);
|
||||
assertCondition(String(stdin.json?.command || "") === "codex submit --prompt-stdin --queue prompt-contract --dry-run", "stdin command should list flags without echoing prompt", stdin.json ?? {});
|
||||
|
||||
const tmp = mkdtempSync(join(tmpdir(), "unidesk-code-queue-submit-"));
|
||||
const promptFile = join(tmp, "prompt.md");
|
||||
const filePrompt = `${multilinePrompt}file prompt tail\n`;
|
||||
writeFileSync(promptFile, filePrompt, "utf8");
|
||||
try {
|
||||
const fromFile = runCli(["codex", "submit", "--prompt-file", promptFile, "--queue", "prompt-contract", "--dry-run"]);
|
||||
assertDryRunPrompt(fromFile.json ?? {}, filePrompt);
|
||||
assertCondition(String(fromFile.json?.command || "").includes(`--prompt-file ${promptFile}`), "prompt-file command should retain file path for review", fromFile.json ?? {});
|
||||
assertCondition(!String(fromFile.json?.command || "").includes("file prompt tail"), "prompt-file command must not echo file prompt text", fromFile.json ?? {});
|
||||
} finally {
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
const positional = runCli(["codex", "submit", "short smoke prompt", "--dry-run"]);
|
||||
assertDryRunPrompt(positional.json ?? {}, "short smoke prompt");
|
||||
assertCondition(String(positional.json?.command || "").includes("<prompt:redacted>"), "outer command should redact positional submit prompt", positional.json ?? {});
|
||||
assertCondition(!String(positional.json?.command || "").includes("short smoke prompt"), "outer command must not echo positional submit prompt", positional.json ?? {});
|
||||
|
||||
const duplicateSource = runCli(["codex", "submit", "positional", "--prompt-stdin", "--dry-run"], "stdin\n");
|
||||
assertCondition(duplicateSource.status !== 0, "duplicate prompt source should fail", duplicateSource.json ?? { stdout: duplicateSource.stdout });
|
||||
const duplicateMessage = String(nestedRecord(duplicateSource.json, ["error"]).message || "");
|
||||
assertCondition(duplicateMessage.includes("exactly one prompt source"), "duplicate prompt source error should be explicit", { duplicateMessage });
|
||||
|
||||
const help = runCli(["codex", "submit", "--help"]);
|
||||
assertCondition(help.status === 0 && help.json?.ok === true, "codex submit help should succeed", help.json ?? { stdout: help.stdout });
|
||||
const data = nestedRecord(help.json?.data, []);
|
||||
const usage = stringArray(data.usage);
|
||||
const promptInput = nestedRecord(data, ["promptInput"]);
|
||||
const recommended = stringArray(promptInput.recommended);
|
||||
const examples = nestedRecord(data, ["examples"]);
|
||||
assertCondition(usage.some((line) => line.includes("--prompt-stdin")), "help usage should include --prompt-stdin", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("--prompt-file")), "help usage should include --prompt-file", { usage });
|
||||
assertCondition(usage.some((line) => line.includes("cat <<'PROMPT'")), "help usage should include a quoted heredoc example", { usage });
|
||||
assertCondition(recommended.includes("--prompt-stdin") && recommended.includes("--prompt-file"), "help should recommend stdin and file prompt sources", promptInput);
|
||||
assertCondition(String(promptInput.sourceRule || "").includes("Exactly one prompt source"), "help should document exact prompt source rule", promptInput);
|
||||
assertCondition(stringArray(examples.stdin).some((line) => line.includes("--prompt-stdin")), "help examples should include stdin command", examples);
|
||||
assertCondition(String(examples.file || "").includes("--prompt-file"), "help examples should include file command", examples);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
checks: [
|
||||
"submit --prompt-stdin preserves multiline quotes and newlines",
|
||||
"submit --prompt-file preserves reviewed file contents",
|
||||
"submit positional prompt is redacted from the outer command envelope",
|
||||
"duplicate submit prompt source fails explicitly",
|
||||
"codex submit help documents stdin/file recommendations and copyable examples",
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
process.stdout.write(`${JSON.stringify(runCodeQueueCliSubmitPromptContract(), null, 2)}\n`);
|
||||
}
|
||||
@@ -30,6 +30,7 @@ const syntaxFiles = [
|
||||
"scripts/host-codex-commander-no-daemon-smoke-contract-test.ts",
|
||||
"scripts/host-codex-commander-skeleton-contract-test.ts",
|
||||
"scripts/auth-broker-contract-test.ts",
|
||||
"scripts/code-queue-cli-submit-prompt-contract-test.ts",
|
||||
"scripts/code-queue-supervisor-disclosure-contract-test.ts",
|
||||
"src/components/frontend/src/index.ts",
|
||||
"src/components/frontend/src/app.tsx",
|
||||
@@ -341,6 +342,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
items.push(commandItem("code-queue:trace-summary-contract", ["bun", "scripts/code-queue-trace-summary-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("code-queue:pr-preflight-contract", ["bun", "scripts/code-queue-pr-preflight-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("code-queue:runner-skills-contract", ["bun", "scripts/code-queue-runner-skills-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("code-queue:submit-prompt-contract", ["bun", "scripts/code-queue-cli-submit-prompt-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("code-queue:submit-routing-contract", ["bun", "scripts/code-queue-submit-routing-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("code-queue:supervisor-disclosure-contract", ["bun", "scripts/code-queue-supervisor-disclosure-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("host-codex-commander:skeleton-contract", ["bun", "scripts/host-codex-commander-skeleton-contract-test.ts"], 30_000));
|
||||
@@ -367,6 +369,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
items.push(skippedItem("code-queue:trace-summary-contract", "Code Queue trace summary contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("code-queue:pr-preflight-contract", "Code Queue PR preflight contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("code-queue:runner-skills-contract", "Code Queue runner skill availability contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("code-queue:submit-prompt-contract", "Code Queue submit prompt contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("code-queue:submit-routing-contract", "Code Queue submit routing contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("code-queue:supervisor-disclosure-contract", "Code Queue supervisor disclosure contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("host-codex-commander:skeleton-contract", "host Codex commander skeleton contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
|
||||
+19
-1
@@ -248,7 +248,9 @@ function codexHelp(): unknown {
|
||||
output: "json",
|
||||
usage: [
|
||||
"bun scripts/cli.ts codex deploy <commitId> # disabled legacy deployment entry",
|
||||
"bun scripts/cli.ts codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue id] [--dry-run]",
|
||||
"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 <id> --dry-run",
|
||||
"bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue <id> --dry-run",
|
||||
"bun scripts/cli.ts codex task <taskId> [--detail] [--trace --tail|--from-start|--after-seq N|--before-seq N --limit N] [--full]",
|
||||
"bun scripts/cli.ts codex tasks [--view supervisor|full] [--queue id] [--status succeeded,running] [--unread|--unread-only] [--limit N] [--before-id id]",
|
||||
"bun scripts/cli.ts codex output <taskId> [--tail|--from-start|--after-seq N|--before-seq N --limit N] [--full-text]",
|
||||
@@ -260,6 +262,22 @@ function codexHelp(): unknown {
|
||||
"bun scripts/cli.ts codex interrupt|cancel <taskId>",
|
||||
"bun scripts/cli.ts codex queues [--full|--all] | queue create <queueId> | queue merge <sourceQueueId> --into <targetQueueId> | move <taskId> --queue <queueId>",
|
||||
],
|
||||
promptInput: {
|
||||
recommended: ["--prompt-stdin", "--prompt-file"],
|
||||
stdin: "Use a quoted heredoc or pipe when the prompt contains newlines, quotes, backticks, JSON, Markdown tables, or shell-sensitive text.",
|
||||
file: "Use --prompt-file for reviewed or reusable dispatch prompts; --file is an alias.",
|
||||
positional: "Keep positional prompt usage to short one-line smoke prompts only.",
|
||||
sourceRule: "Exactly one prompt source is accepted: positional prompt, --prompt-file, or --prompt-stdin.",
|
||||
},
|
||||
examples: {
|
||||
stdin: [
|
||||
"cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue <id> --dry-run",
|
||||
"<multi-line prompt body>",
|
||||
"PROMPT",
|
||||
],
|
||||
file: "bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue <id> --dry-run",
|
||||
dryRunThenSubmit: "Run with --dry-run first; remove --dry-run to submit exactly the same payload.",
|
||||
},
|
||||
description: "Operate Code Queue through the stable backend-core private proxy path.",
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user