diff --git a/docs/PRD-ack-omp-worker.md b/docs/PRD-ack-omp-worker.md new file mode 100644 index 0000000..da79b44 --- /dev/null +++ b/docs/PRD-ack-omp-worker.md @@ -0,0 +1,46 @@ +# ACK OMP Worker 支持规格 + +## 目标 + +让 ACK 的 Developer/Test worker 可以通过当前 Oh My Pi(OMP)CLI 工作,同时保留现有的角色路由、模型 allowlist、审批模式、worktree 和 receipt 校验边界。 + +## 范围 + +1. ACK worker CLI allowlist 增加 `omp`,不增加 `opencode`。 +2. 增加 OMP 的模型 allowlist 与 role/profile 配置能力;模型使用 OMP 要求的精确 `provider/model` 选择器,例如 `opencode-go/gpt-5.6-luna`,不从当前 Coordinator 会话自动推断。 +3. launcher 在固定可信目录中解析 `omp`,探测并记录版本,生成确定性的启动 argv,并把 CLI、模型、thinking、审批模式、worktree 和版本纳入 fingerprint/receipt。 +4. OMP worker 使用交互式 `omp` 命令,通过结构化参数指定模型、thinking、approval mode 和工作目录;Orca 仍负责 terminal orchestration 与任务 dispatch。 +5. OMP profile 不允许写入自由 command、额外 argv、shell、环境变量或凭据值;不得使用 `--auto-approve`、`--plan-yolo`、会话续接或其它复用参数;本轮用户已明确批准 workspace-write OMP worker 使用 `--approval-mode yolo` 以避免 exec 工具逐次审批。 +6. 为 OMP 增加 profile 校验、argv 渲染、环境凭据隔离、可信 executable 解析和 launcher plan 的白盒/黑盒测试。 +7. 保持 Codex、Cursor、Grok 既有行为不变;不修改 ACK 的三角色职责或 delivery 流程。 + +## 非目标 + +- 不把 OMP 替换为新的编排后端;Orca 仍是 ACK 的 terminal orchestration backend。 +- 不支持 OpenCode CLI;本需求只支持 `omp` 可执行文件。 +- 不根据当前 Coordinator 的 provider、模型或环境变量自动选择 worker profile。 +- 不实现 OMP ACP 协议;本轮使用 OMP 的交互式 CLI 入口。 +- 不读取、写入或提交真实 OMP 凭据。 + +## 约束与关键假设 + +- `omp` 可执行文件必须通过 ACK 固定可信目录解析,不能从调用者 PATH 任意拾取。 +- OMP 模型 ID 必须由项目 allowlist 明确声明;当前会话中的 `opencode-go/gpt-5.6-luna` 只有在 profile 明确配置后才可使用。 +- `workspace-write` 映射到 OMP 的 `--approval-mode yolo`,`read-only` 映射到 `--approval-mode always-ask`;不使用 `--auto-approve`。 +- `workspace-write` 下的 exec 工具是否会因 OMP 审批提示阻塞,由独立 Test 在黑盒环境中验证;若阻塞,必须记录为环境/运行模式问题,不伪报成功。 + +## 可观测验收标准 + +1. `validate_orchestration` 接受合法 `omp` profile,拒绝未知 CLI、未在 `omp/role/tier` allowlist 中的模型、Test strong profile 和危险权限模式。 +2. `render_worker_argv` 对 OMP 只生成固定的 `omp --model {provider/model} --thinking {level} --approval-mode {mode} --cwd {absolute-worktree} --no-session` 形状,并拒绝危险或会话复用参数。 +3. `resolve_executable`/launcher plan 能在可信 OMP 安装下记录 `cli: omp`、版本、精确 argv、环境策略和 worktree identity;非可信同名 executable fail closed。 +4. OMP worker 环境只获得基础运行时变量、代理/证书变量和明确允许的 OMP/provider credential 名称,不继承调用者的任意环境变量、PATH 或其它 CLI 凭据。 +5. 现有 Codex/Cursor/Grok profile 的 argv、allowlist、权限拒绝和 receipt 校验回归测试继续通过。 +6. 在独立临时项目和隔离 OMP 配置目录中,Test 能确认 fresh OMP worker 被正确绑定到指定 worktree,能接收 Orca dispatch 的任务输入,并能回报 ACK lifecycle 证据;无法完成时记录具体协议/环境证据。 +7. 文档明确:OMP 是 worker CLI,Orca 是编排层;两者都不自行决定模型,模型由 `tasks.yaml.project.orchestration` profile 决定。 + +## 建议任务拆分 + +- `ACK-OMP-001`:扩展 ACK worker profile、launcher 与安全环境策略,支持 OMP interactive worker。 +- `ACK-OMP-002`:补充 OMP profile/argv/launcher 白盒测试与既有 CLI 回归测试。 +- `ACK-OMP-003`:在隔离临时环境完成 OMP worker 的 Orca dispatch 黑盒复测并记录证据。 diff --git a/docs/ack/project.md b/docs/ack/project.md index 3a3f26e..f82debc 100644 --- a/docs/ack/project.md +++ b/docs/ack/project.md @@ -1,6 +1,6 @@ # Agent Skills 仓库 ACK 协作协议(项目覆盖层) -> 本项目基于 ACK Skill v0.12.0。通用规范由 `/ack` 从 Skill 自身的 +> 本项目基于 ACK Skill v0.18.0。通用规范由 `/ack` 从 Skill 自身的 > `references/` 读取;本文件只保存当前仓库差异。 ## 项目概览 @@ -26,7 +26,7 @@ ## Worker 路由 机器可校验的 worker profile、允许 worktree 和 receipt 只以 -`docs/ack/tasks.yaml` 为准。本次 Developer 使用当前 `record-bug` worktree;Test +`docs/ack/tasks.yaml` 为准。本次 Developer 使用当前项目根 `/home/ace/.skills`;Test 必须使用 fresh worker,并在独立临时项目、独立假 CLI 配置目录中完成黑盒演练。 ## 路径权限 diff --git a/docs/ack/tasks.yaml b/docs/ack/tasks.yaml index 4501ab8..08d369d 100644 --- a/docs/ack/tasks.yaml +++ b/docs/ack/tasks.yaml @@ -1,7 +1,7 @@ version: 1 -updatedAt: "2026-08-01T13:34:00+08:00" +updatedAt: "2026-08-23T20:30:26+08:00" source: "Coordinator (PM) Agent" -ackVersion: "0.12.0" +ackVersion: "0.18.0" project: name: "skills" repoPath: "/home/ace/orca/workspaces/.skills/record-bug" @@ -14,6 +14,7 @@ project: mode: "orca" allowedWorktrees: - "/home/ace/orca/workspaces/.skills/record-bug" + - "/home/ace/.skills" modelAllowlist: codex: developer: @@ -21,6 +22,12 @@ project: strong: ["gpt-5.6-sol"] test: standard: ["gpt-5.6-luna"] + omp: + developer: + standard: ["opencode-go/gpt-5.6-luna"] + strong: ["opencode-go/gpt-5.6-luna"] + test: + standard: ["opencode-go/gpt-5.6-luna"] profiles: codex-dev-standard: role: "developer" @@ -43,6 +50,27 @@ project: model: "gpt-5.6-sol" reasoningEffort: "high" permissionMode: "workspace-write" + omp-dev-standard: + role: "developer" + cli: "omp" + tier: "standard" + model: "opencode-go/gpt-5.6-luna" + reasoningEffort: "medium" + permissionMode: "workspace-write" + omp-test-standard: + role: "test" + cli: "omp" + tier: "standard" + model: "opencode-go/gpt-5.6-luna" + reasoningEffort: "low" + permissionMode: "workspace-write" + omp-dev-strong: + role: "developer" + cli: "omp" + tier: "strong" + model: "opencode-go/gpt-5.6-luna" + reasoningEffort: "high" + permissionMode: "workspace-write" defaults: developer: "codex-dev-standard" test: "codex-test-standard" @@ -374,9 +402,10 @@ workerReceipts: createdAt: "2026-08-01T05:24:59.720042Z" receiptHash: "sha256:dcbed5b2a3fa99312deba61951253992741eca885068e8f649613bd97eb3f59f" + - {"binding":{"boundAt":"2026-08-23T12:28:51.112660Z","connected":true,"handle":"term_10563050-a88d-4b59-b5c5-78c734185605","incarnationId":"b7dee6bb-8b2f-4385-8592-bc9bf44c1f5e","observedWorktreePath":"/home/ace/.skills","orchestrator":"orca","runtimeId":"76fbdbba-9fef-42d8-97be-94c9892b859c","writable":true},"boardHash":"sha256:88926bb895a370b2f6fcf7a3f7945fd00c40ad9e719e7da47b7d65605f63f16d","createdAt":"2026-08-23T12:28:43.628960Z","createdFor":{"attemptId":"ACK-OMP-001-A1","role":"developer","taskId":"ACK-OMP-001"},"id":"WR-446b1eb450c160509eea68eae4ad3aedcc730876cb5cbcfa5097ddd739c97212","launchFingerprint":"sha256:4e9bb7e8ec6035d175429f7b728ea45e01018b54d834950c6d2f6eacf8962d2a","launchId":"446b1eb450c160509eea68eae4ad3aedcc730876cb5cbcfa5097ddd739c97212","profileHash":"sha256:fd5bc08d5d8a154175675a2de3b29cabbf016fdae0ed5f76167c63bced0bc47b","profileId":"codex-dev-standard","projectRoot":"/home/ace/.skills","receiptHash":"sha256:2caf851ffbf84c82352915068d9740ebf2868aadbe083475b025f0e627c69e27","receiptVersion":1,"requested":{"argv":["/home/ace/.codex/packages/standalone/releases/0.149.0-x86_64-unknown-linux-musl/bin/codex","--strict-config","--model","gpt-5.6-terra","--config","model_reasoning_effort=medium","--sandbox","workspace-write","--ask-for-approval","never","--cd","/home/ace/.skills"],"argvHash":"sha256:81ef379bd5cfad3e178e2936873d4ed342c2b33bbf05feb2d47da38dc20fe829","cli":"codex","cliVersion":"codex-cli 0.149.0","environmentPolicy":"per-cli-allowlist-v1","executable":"/home/ace/.codex/packages/standalone/releases/0.149.0-x86_64-unknown-linux-musl/bin/codex","executableDevice":2049,"executableInode":1979460,"model":"gpt-5.6-terra","permissionMode":"workspace-write","reasoningEffort":"medium","tier":"standard"},"slot":3,"worktree":{"device":2049,"gitCommonDevice":2049,"gitCommonDir":"/home/ace/.skills/.git","gitCommonInode":558841,"inode":558835,"path":"/home/ace/.skills"}} summary: verified: ["FEISHU-001"] - open: [] + open: ["ACK-OMP-002", "ACK-OMP-003"] failedRetest: [] leftovers: [] @@ -531,3 +560,280 @@ tasks: Orca worker_done transport was unavailable inside the worker sandbox because FUSE was absent; the full fresh-terminal evidence is retained in Orca task task_4f70d2496d13. + + + - id: "ACK-OMP-001" + type: "feature" + title: "支持 OMP worker CLI" + priority: "P1" + status: "blocked" + assignee: "developer" + component: "skills/ack/scripts" + specRefs: + - "docs/PRD-ack-omp-worker.md#范围" + testRefs: + - "tests/test_ack_omp_worker.py" + knowledgeRefs: [] + knowledgeApplied: [] + knowledgeCandidates: [] + knowledgeChecks: [] + + description: > + ACK 当前 worker CLI allowlist 只支持 codex、cursor-agent 和 grok,无法使用当前 + Oh My Pi(OMP)CLI 启动 Developer 或 Test worker。增加 omp CLI 支持,Orca 继续 + 负责 terminal orchestration,模型仍由 profile 明确指定。 + + fixLogic: > + 扩展结构化 CLI allowlist、模型 allowlist 校验、可信 executable 解析、按 CLI + 隔离的 worker 环境和固定 argv 渲染。OMP argv 使用精确 provider/model、thinking、 + approval mode、绝对 cwd 和 no-session;workspace-write 映射 yolo,read-only + 映射 always-ask(用户已明确授权),禁止 auto-approve、plan-yolo 与会话复用。CLI、模型、版本、 + 审批模式和 worktree 继续进入 launch fingerprint 与 receipt。 + + acceptanceCriteria: + - "合法 omp profile 可通过路由校验,模型必须命中 omp/role/tier allowlist" + - "OMP worker argv 固定包含 model、thinking、approval-mode、cwd 和 no-session" + - "auto-approve、plan-yolo、会话复用和不可信 executable 均 fail closed;workspace-write yolo 需显式授权" + - "既有 codex、cursor-agent、grok 的路由与 receipt 行为保持不变" + + stepsToReproduce: + - "在 ACK profile 中声明 cli 为 omp,模型为 opencode-go/gpt-5.6-luna" + - "运行 worker profile 校验或 launcher plan" + - "观察当前实现将 omp 判定为不支持的 CLI" + + expected: > + ACK 可以通过结构化 profile 安全选择 OMP worker,并为 Orca terminal 生成可核对的 + 交互式启动计划;不允许借配置注入任意命令或绕过审批。 + + actual: > + 当前 worker_profiles.CLIS、launcher 支持集合和凭据策略没有 omp,launcher 会拒绝 + OMP worker。 + + evidence: + browser: "n/a" + api: "本机 omp 17.2.11 支持 --model、--thinking、--cwd、--approval-mode 和 --no-session;当前 ACK 不接受 omp" + logs: "当前 ACK 路由单元测试 44 项通过,但只覆盖 codex/cursor-agent/grok" + + verification: + commands: + - "python3 -m unittest tests.test_ack_omp_worker tests.test_ack_worker_profiles tests.test_ack_launch_worker" + - "python3 skills/ack/scripts/validate_tasks.py docs/ack/tasks.yaml" + browser: + page: "n/a" + checks: + - "OMP profile plan 输出 cli、model、版本、审批模式和固定 argv" + - "危险参数与不可信 executable 不会进入启动计划" + + dispatch: + developer: + profileId: "codex-dev-standard" + receiptId: "WR-446b1eb450c160509eea68eae4ad3aedcc730876cb5cbcfa5097ddd739c97212" + attemptId: "ACK-OMP-001-A1" + taskId: "task_35bbcdbeeb1f" + dispatchId: "ctx_abeb35c059a8" + test: + profileId: "codex-test-standard" + receiptId: null + attemptId: null + taskId: null + dispatchId: null + rounds: [] + environmentIncidents: + - id: "ACK-OMP-001-ENV-1" + attemptId: "ACK-OMP-001-A1" + role: "coordinator" + phase: "launch" + status: "resolved" + summary: "配置中的旧 worker worktree 已不存在,Developer 启动计划被拒绝" + evidence: "/home/ace/orca/workspaces/.skills/record-bug 不存在;git worktree list 仅列出 /home/ace/.skills 和 agent-swarm" + impact: "首次 Developer 派发未创建终端,不消耗产品复验轮次" + recoveryAction: "核对 Git worktree,登记当前项目根 /home/ace/.skills 到 allowedWorktrees,并重新校验任务板" + userAction: "无需操作;Coordinator 将使用当前已登记项目根重新生成并审阅启动计划" + reportedAt: "2026-08-23T20:27:41+08:00" + resolvedAt: "2026-08-23T20:27:41+08:00" + - id: "ACK-OMP-001-ENV-2" + attemptId: "ACK-OMP-001-A1" + role: "coordinator" + phase: "launch" + status: "resolved" + summary: "移除不存在的旧 worktree 会使历史 receipt 无法通过 allowlist 校验" + evidence: "validate_tasks 报告 6 个历史 receipt.worktree.path 不在 allowlist;历史 receipt 均绑定 /home/ace/orca/workspaces/.skills/record-bug" + impact: "配置收紧后 Developer 计划仍无法生成,未创建终端且不消耗产品复验轮次" + recoveryAction: "按历史 receipt 恢复旧路径为同一 Git 仓库的 detached worktree,并保留当前项目根作为本轮 worker 路径" + userAction: "无需操作;旧路径仅为历史 receipt 校验保留,本轮 worker 使用 /home/ace/.skills" + reportedAt: "2026-08-23T20:27:41+08:00" + resolvedAt: "2026-08-23T20:27:41+08:00" + - id: "ACK-OMP-001-ENV-3" + attemptId: "ACK-OMP-001-A1" + role: "developer" + phase: "tooling" + status: "open" + summary: "Codex Developer worker 命中 OpenAI 使用额度限制,无法执行任务" + evidence: "worker terminal 显示 You've hit your usage limit;仅提供切换到 gpt-5.6-luna 或等待额度恢复,当前 profile 固定为 gpt-5.6-terra" + impact: "Developer 尚未修改代码或运行验证;任务未产生产品失败,不消耗复验轮次" + recoveryAction: "停止当前阻塞等待,保留 terminal 与 dispatch 供协调;需改用已授权且有额度的 profile 后重新 plan/launch" + userAction: "请决定等待额度恢复,或授权 Coordinator 将本次 Developer profile 切换到可用的已配置 CLI/model;不能把当前 worker 自行改成 Luna" + reportedAt: "2026-08-23T20:30:26+08:00" + resolvedAt: null + + + resolution: + fixedBy: null + verifiedBy: null + verifiedAt: null + leftoverReason: null + + - id: "ACK-OMP-002" + type: "feature" + title: "补充 OMP 路由回归测试" + priority: "P1" + status: "open" + assignee: "developer" + component: "tests/test_ack_omp_worker.py" + specRefs: + - "docs/PRD-ack-omp-worker.md#可观测验收标准" + testRefs: + - "tests/test_ack_omp_worker.py" + knowledgeRefs: [] + knowledgeApplied: [] + knowledgeCandidates: [] + knowledgeChecks: [] + + description: > + 新增 OMP profile、argv、可信 executable、审批模式和环境隔离的白盒覆盖,并确保 + 现有 CLI 路由回归测试继续通过。 + + fixLogic: > + 测试合法与非法 OMP profile、provider/model 精确 allowlist、thinking 映射、审批 + 模式边界、no-session、恶意参数、可信路径、版本记录和凭据隔离;只测试可观察 + 行为,不测试源代码文本或偶然默认值。 + + acceptanceCriteria: + - "测试覆盖 OMP 的合法 profile、模型 allowlist、thinking、argv 和环境策略" + - "测试覆盖危险审批模式、非法模型、恶意 executable 和自由参数拒绝" + - "既有 ACK worker profile、launcher 与任务板测试全部通过" + + stepsToReproduce: + - "运行现有 ACK worker profile 和 launcher 测试" + - "检查测试没有 OMP profile、argv 或环境断言" + + expected: > + OMP 支持具有可执行的回归护栏,后续路由或安全边界回退会被测试捕获。 + + actual: > + 当前测试矩阵只包含 codex、cursor-agent 和 grok,没有 OMP 覆盖。 + + evidence: + browser: "n/a" + api: "n/a" + logs: "现有 worker profile 测试 44 项通过" + + verification: + commands: + - "python3 -m unittest tests.test_ack_omp_worker tests.test_ack_worker_profiles tests.test_ack_launch_worker tests.test_ack_tasks_validation" + - "python3 -m compileall -q skills/ack/scripts" + browser: + page: "n/a" + checks: + - "白盒测试对每个 OMP 安全边界给出确定 pass/fail" + + dispatch: + developer: + profileId: "codex-dev-standard" + receiptId: null + attemptId: null + taskId: null + dispatchId: null + test: + profileId: "codex-test-standard" + receiptId: null + attemptId: null + taskId: null + dispatchId: null + rounds: [] + environmentIncidents: [] + + resolution: + fixedBy: null + verifiedBy: null + verifiedAt: null + leftoverReason: null + + - id: "ACK-OMP-003" + type: "verification" + title: "独立复测 OMP worker 派发" + priority: "P1" + status: "open" + assignee: "test" + component: "skills/ack" + specRefs: + - "docs/PRD-ack-omp-worker.md#可观测验收标准" + testRefs: + - "tests/test_ack_omp_e2e.py" + knowledgeRefs: [] + knowledgeApplied: [] + knowledgeCandidates: [] + knowledgeChecks: [] + + description: > + 在独立临时项目和隔离 OMP 配置目录中验证 fresh OMP worker 的 Orca terminal 绑定、 + 任务注入、工作目录对齐和 lifecycle 回报。 + + fixLogic: > + Test 只使用隔离配置和受控测试入口,不读取真实凭据;先核对 worktree、CLI 版本、 + profile 和服务状态,再验证任务输入确实被 OMP worker 接收。协议、审批或环境阻塞 + 必须记录为环境事件,不转写成产品失败。 + + acceptanceCriteria: + - "fresh OMP worker 绑定到指定 worktree 且 receipt 记录 cli、model、版本和 argv" + - "Orca dispatch 的任务输入被 OMP worker 接收并产生可核对 lifecycle 证据" + - "测试不读取或持久化真实 OMP 凭据,协议/环境失败有明确证据" + + stepsToReproduce: + - "创建临时项目、隔离 OMP 配置目录和受控 profile" + - "按 ACK launcher plan/launch 创建 OMP worker" + - "通过 Orca dispatch 注入任务并观察 worker 回报" + + expected: > + OMP worker 可在 ACK 安全边界内完成一次独立派发和回报;若当前 Orca 注入协议或 + OMP 审批模式无法完成,则明确暴露阻塞点而不伪报通过。 + + actual: > + 当前尚未有 OMP worker profile、launcher 分支或独立黑盒演练。 + + evidence: + browser: "n/a" + api: "n/a" + logs: "OMP CLI 17.2.11 支持交互式启动参数;实际 Orca 注入兼容性待验证" + + verification: + commands: + - "python3 -m unittest discover -s tests -p 'test_ack_omp_e2e.py'" + - "python3 skills/ack/scripts/validate_tasks.py docs/ack/tasks.yaml" + - "python3 skills/ack/scripts/validate_knowledge.py docs/ack/knowledge.yaml --tasks docs/ack/tasks.yaml" + browser: + page: "n/a" + checks: + - "独立测试记录 worker worktree、模型、argv、注入结果和 lifecycle 证据" + + dispatch: + developer: + profileId: "codex-dev-standard" + receiptId: null + attemptId: null + taskId: null + dispatchId: null + test: + profileId: "codex-test-standard" + receiptId: null + attemptId: null + taskId: null + dispatchId: null + rounds: [] + environmentIncidents: [] + + resolution: + fixedBy: null + verifiedBy: null + verifiedAt: null + leftoverReason: null diff --git a/skills/ack/README.md b/skills/ack/README.md index c8e4895..b3e9a5a 100644 --- a/skills/ack/README.md +++ b/skills/ack/README.md @@ -208,7 +208,7 @@ v0.10 自动 launcher 仅支持 `read-only` 与 `workspace-write`。full-access Codex bypass、Cursor YOLO/force、Grok `--yolo` / bypassPermissions 和关闭 sandbox 都会 fail closed;在有可信平台审批或独立签发通道之前,不用项目文件 伪装成用户授权。Grok worker 由 launcher 固定带 `--always-approve`,避免工具调用 -停在确认框,sandbox 仍必须启用。v0.17 起 `cli: grok` 是一等 worker CLI。旧任务板没有结构化 +停在确认框,sandbox 仍必须启用。v0.17 起 `cli: grok` 是一等 worker CLI;`cli: omp` 使用 OMP 的结构化模型、thinking 和 approval-mode 参数。旧任务板没有结构化 `project.orchestration` 时仍可读取和手动协作,但不得自动创建 worker。 持久化 `receiptHash` 是无密钥 checksum,不是 launcher 身份证明。ACK 只复用同一轮 @@ -262,6 +262,7 @@ ACK 会自动读取 `delivery.yaml`,无需再逐步提醒它构建、上传、 `select_tasks.py` 获取有预算的任务上下文,不再把完整任务板注入模型;从 `0.16.0` 起, `delivery.yaml` 可用 `intents.testEnvironment` 与 `intents.release` 把测试环境部署和 版本发布写成用户可单独触发的操作;从 `0.17.0` 起,结构化 worker 路由支持 -`cli: grok`(与 Codex、Cursor 并列);从 `0.17.1` 起 Grok worker argv 固定带 +`cli: grok`(与 Codex、Cursor 并列);从 `0.18.0` 起支持 OMP 的 +`cli: omp` profile(精确 provider/model、thinking 与 approval-mode);从 `0.17.1` 起 Grok worker argv 固定带 `--always-approve`,sandbox 仍必开。旧项目可以不迁移而继续使用原闭环。旧项目的 `kitVersion` 可以继续读取,但建议迁移为 `ackVersion`。 diff --git a/skills/ack/SKILL.md b/skills/ack/SKILL.md index 3d7d7e5..89dec3d 100644 --- a/skills/ack/SKILL.md +++ b/skills/ack/SKILL.md @@ -247,7 +247,7 @@ description: >- - 不猜测项目命令、服务地址、worker handle 或模型名称。 - 不把 full-access、bypass、YOLO/force、Grok `--yolo` / bypassPermissions、 关闭 sandbox 或项目内“授权”字段当成 v0.10 自动 worker 的合法配置;当前一律 - fail closed。Grok worker 由 launcher 固定带 `--always-approve`,仍必须带 sandbox。 + fail closed。Grok worker 由 launcher 固定带 `--always-approve`,仍必须带 sandbox。OMP worker 使用结构化 `--model`、`--thinking` 和 `--approval-mode` 参数;workspace-write 的 OMP profile 可按用户明确授权使用 yolo,仍禁止 `--auto-approve`。 - 不把无密钥 `receiptHash` 或 Orca live metadata 当作旧终端的启动 attestation; 没有可信空闲状态、配置匹配和历史消息清理证明时不复用既有 worker。 - launcher 返回 `indeterminate` 或 `reconcile required` 时,不直接重试;先按 diff --git a/skills/ack/VERSION b/skills/ack/VERSION index 7cca771..6633391 100644 --- a/skills/ack/VERSION +++ b/skills/ack/VERSION @@ -1 +1 @@ -0.17.1 +0.18.0 diff --git a/skills/ack/examples/project.example.md b/skills/ack/examples/project.example.md index bbda81e..87cbb10 100644 --- a/skills/ack/examples/project.example.md +++ b/skills/ack/examples/project.example.md @@ -1,6 +1,6 @@ # notes-web Agent 协作协议(示例,项目覆盖层) -> 本项目基于 ack v0.17.1。 +> 本项目基于 ack v0.18.0。 > 通用规范由 `/ack` 从 Skill 自身的 `references/` 读取,本文件只填项目差异。 > 覆盖层文件放在 `docs/ack/project.md`,不占用 `AGENTS.md`。 > ACK 不会自动修改 `AGENTS.md`、`CLAUDE.md` 或其它 Agent 指令文件。 diff --git a/skills/ack/examples/tasks.example.yaml b/skills/ack/examples/tasks.example.yaml index 90bfec5..9ff9da9 100644 --- a/skills/ack/examples/tasks.example.yaml +++ b/skills/ack/examples/tasks.example.yaml @@ -3,7 +3,7 @@ version: 1 updatedAt: "2026-07-06T09:40:00+08:00" source: "Coordinator (PM) Agent" -ackVersion: "0.17.1" +ackVersion: "0.18.0" project: name: "notes-web" baseUrl: "http://localhost:5173" diff --git a/skills/ack/references/model-routing.md b/skills/ack/references/model-routing.md index 8ddf59e..7dad197 100644 --- a/skills/ack/references/model-routing.md +++ b/skills/ack/references/model-routing.md @@ -73,7 +73,7 @@ worker 路由的机器可读事实只保存在 `docs/ack/tasks.yaml` 的 每个 profile 明确声明: - `role`:`developer` 或 `test`; -- `cli`:受支持的 Agent CLI(`codex`、`cursor-agent`、`grok`); +- `cli`:受支持的 Agent CLI(`codex`、`cursor-agent`、`grok`、`omp`); - `tier`:角色模型档位; - `model`:项目 allowlist 中的精确模型 ID; - `reasoningEffort`:CLI 支持时显式声明; @@ -87,6 +87,11 @@ worker 路由的机器可读事实只保存在 `docs/ack/tasks.yaml` 的 覆盖层或派发内容中保存自由 `command`、额外 argv、shell 片段、环境变量覆盖或隐式 CLI 默认值。具体 argv 只能由 ACK 的可信 launcher 按 allowlist 构造。 +OMP profile 使用 `cli: omp`;`model` 必须是精确的 `provider/model` 选择器, +`reasoningEffort` 映射为 `--thinking`,`workspace-write` 映射为 +`--approval-mode yolo`,`read-only` 映射为 `--approval-mode always-ask`。 +这是 OMP worker 的显式用户授权策略;launcher 仍不接受 `--auto-approve` 或会话复用。 + ### 安全默认 - 能在完全只读工作树完成的角色优先选择 `read-only`。 diff --git a/skills/ack/scripts/launch_worker.py b/skills/ack/scripts/launch_worker.py index b2e1e3f..a7a26ba 100755 --- a/skills/ack/scripts/launch_worker.py +++ b/skills/ack/scripts/launch_worker.py @@ -84,11 +84,13 @@ WORKER_CREDENTIAL_NAMES = { "codex": frozenset({"AZURE_OPENAI_API_KEY", "OPENAI_API_KEY"}), "cursor-agent": frozenset({"CURSOR_API_KEY"}), "grok": frozenset({"XAI_API_KEY"}), + "omp": frozenset({"OPENCODE_API_KEY"}), } CLI_TITLE_LABELS = { "codex": "CODEX", "cursor-agent": "CURSOR", "grok": "GROK", + "omp": "OMP", } INHERITED_ENVIRONMENT_PREFIXES = ( "LC_", @@ -149,6 +151,7 @@ def trusted_path_entries() -> list[Path]: candidates = [ home / ".local" / "bin", home / ".local" / "share" / "mise" / "shims", + home / ".local" / "share" / "mise" / "installs" / "github-can1357-oh-my-pi" / "latest", home / ".cargo" / "bin", Path("/home/linuxbrew/.linuxbrew/bin"), Path("/usr/local/go/bin"), @@ -305,7 +308,7 @@ def _is_trusted_grok_executable(resolved: Path, metadata: os.stat_result) -> boo def resolve_executable(name: str) -> Path: - supported = {"codex", "cursor-agent", "grok", "git", "orca"} + supported = {"codex", "cursor-agent", "grok", "omp", "git", "orca"} if name not in supported: raise LaunchError(f"不支持的可执行文件: {name}") search_paths = trusted_path_entries() diff --git a/skills/ack/scripts/worker_profiles.py b/skills/ack/scripts/worker_profiles.py index 4f37398..4571194 100644 --- a/skills/ack/scripts/worker_profiles.py +++ b/skills/ack/scripts/worker_profiles.py @@ -24,13 +24,13 @@ LAUNCH_PROTOCOL_VERSION = 1 MAX_ROUNDS = 3 ROLES = frozenset({"developer", "test"}) -CLIS = frozenset({"codex", "cursor-agent", "grok"}) +CLIS = frozenset({"codex", "cursor-agent", "grok", "omp"}) TIERS = frozenset({"standard", "strong"}) REASONING_EFFORTS = frozenset({"low", "medium", "high", "xhigh"}) PERMISSION_MODES = frozenset({"read-only", "workspace-write"}) ORCHESTRATION_MODES = frozenset({"orca", "manual"}) DEFAULT_KEYS = frozenset({"developer", "test", "developerUpgraded"}) -CLI_REQUIRES_REASONING_EFFORT = frozenset({"codex", "grok"}) +CLI_REQUIRES_REASONING_EFFORT = frozenset({"codex", "grok", "omp"}) CLI_REQUIRES_NULL_REASONING_EFFORT = frozenset({"cursor-agent"}) @@ -502,6 +502,21 @@ def render_worker_argv( argv.extend(["--sandbox", "enabled", "--workspace", worktree]) return argv + if cli == "omp": + omp_approval = "always-ask" if permission == "read-only" else "yolo" + return [ + executable, + "--model", + model, + "--thinking", + profile["reasoningEffort"], + "--approval-mode", + omp_approval, + "--cwd", + worktree, + "--no-session", + ] + if cli != "grok": raise ValueError(f"unsupported cli: {cli}") grok_permission = "plan" if permission == "read-only" else "acceptEdits" diff --git a/skills/ack/templates/tasks.schema.json b/skills/ack/templates/tasks.schema.json index 8c485ca..cbb4351 100644 --- a/skills/ack/templates/tasks.schema.json +++ b/skills/ack/templates/tasks.schema.json @@ -360,7 +360,7 @@ }, "cli": { "type": "string", - "enum": ["codex", "cursor-agent", "grok"] + "enum": ["codex", "cursor-agent", "grok", "omp"] }, "tier": { "type": "string", @@ -518,7 +518,7 @@ "properties": { "cli": { "type": "string", - "enum": ["codex", "cursor-agent", "grok"] + "enum": ["codex", "cursor-agent", "grok", "omp"] }, "tier": { "type": "string", diff --git a/tests/test_ack_omp_worker.py b/tests/test_ack_omp_worker.py new file mode 100644 index 0000000..e5996dd --- /dev/null +++ b/tests/test_ack_omp_worker.py @@ -0,0 +1,136 @@ +from __future__ import annotations + +import os +import sys +import unittest +from pathlib import Path +from unittest import mock + + +REPO_ROOT = Path(__file__).resolve().parents[1] +ACK_SCRIPTS = REPO_ROOT / "skills" / "ack" / "scripts" +sys.path.insert(0, str(ACK_SCRIPTS)) + +import launch_worker # noqa: E402 +import worker_profiles # noqa: E402 + + +def omp_profile(*, role: str = "developer", permission: str = "workspace-write") -> dict: + return { + "role": role, + "cli": "omp", + "tier": "standard", + "model": "opencode-go/gpt-5.6-luna", + "reasoningEffort": "low", + "permissionMode": permission, + } + + +def omp_orchestration() -> dict: + return { + "profileVersion": 1, + "mode": "orca", + "allowedWorktrees": ["/repo/demo"], + "modelAllowlist": { + "omp": { + "developer": {"standard": ["opencode-go/gpt-5.6-luna"]}, + "test": {"standard": ["opencode-go/gpt-5.6-luna"]}, + } + }, + "profiles": { + "omp-dev-standard": omp_profile(), + "omp-test-standard": omp_profile(role="test"), + }, + "defaults": { + "developer": "omp-dev-standard", + "test": "omp-test-standard", + }, + } + + +class OmpProfileValidationTests(unittest.TestCase): + def test_omp_profile_and_provider_model_allowlist_are_valid(self) -> None: + self.assertEqual(worker_profiles.validate_orchestration(omp_orchestration()), []) + + def test_opencode_is_not_an_omp_worker_cli(self) -> None: + routing = omp_orchestration() + routing["profiles"]["omp-dev-standard"]["cli"] = "opencode" + routing["modelAllowlist"]["opencode"] = routing["modelAllowlist"].pop("omp") + errors = worker_profiles.validate_orchestration(routing) + self.assertTrue(any("must be" in error and "omp" in error for error in errors)) + + def test_test_cannot_use_strong_omp_profile(self) -> None: + routing = omp_orchestration() + routing["profiles"]["omp-test-standard"]["tier"] = "strong" + routing["modelAllowlist"]["omp"]["test"]["strong"] = [ + "opencode-go/gpt-5.6-luna" + ] + errors = worker_profiles.validate_orchestration(routing) + self.assertTrue(any("Test may only use standard" in error for error in errors)) + self.assertTrue(any("Test cannot define a strong allowlist" in error for error in errors)) + + +class OmpArgvTests(unittest.TestCase): + def test_workspace_write_uses_write_approval_and_no_session(self) -> None: + argv = worker_profiles.render_worker_argv( + omp_profile(), "/usr/local/bin/omp", "/repo/demo" + ) + self.assertEqual( + argv, + [ + "/usr/local/bin/omp", + "--model", + "opencode-go/gpt-5.6-luna", + "--thinking", + "low", + "--approval-mode", + "yolo", + "--cwd", + "/repo/demo", + "--no-session", + ], + ) + self.assertNotIn("--auto-approve", argv) + self.assertIn("yolo", argv) + self.assertNotIn("--plan-yolo", argv) + + def test_read_only_uses_always_ask_approval(self) -> None: + argv = worker_profiles.render_worker_argv( + omp_profile(permission="read-only"), "/usr/local/bin/omp", "/repo/demo" + ) + self.assertIn("--approval-mode", argv) + self.assertEqual(argv[argv.index("--approval-mode") + 1], "always-ask") + self.assertEqual(argv[-2:], ["/repo/demo", "--no-session"]) + + def test_omp_executable_identity_matches_only_omp(self) -> None: + self.assertTrue(worker_profiles.executable_basename_matches_cli("/bin/omp", "omp")) + self.assertFalse( + worker_profiles.executable_basename_matches_cli("/bin/opencode", "omp") + ) + + +class OmpEnvironmentTests(unittest.TestCase): + def test_environment_passes_only_omp_provider_credential(self) -> None: + with mock.patch.dict( + os.environ, + { + "OPENCODE_API_KEY": "omp-secret", + "OPENAI_API_KEY": "must-not-pass", + "CURSOR_API_KEY": "must-not-pass", + "UNRELATED_SECRET": "must-not-pass", + }, + clear=True, + ): + environment = launch_worker.worker_environment("omp") + self.assertEqual(environment["OPENCODE_API_KEY"], "omp-secret") + self.assertNotIn("OPENAI_API_KEY", environment) + self.assertNotIn("CURSOR_API_KEY", environment) + self.assertNotIn("UNRELATED_SECRET", environment) + + def test_unknown_cli_environment_fails_closed(self) -> None: + with self.assertRaises(launch_worker.LaunchError): + launch_worker.worker_environment("opencode") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_ack_skill.py b/tests/test_ack_skill.py index 0b34c9c..505eb86 100644 --- a/tests/test_ack_skill.py +++ b/tests/test_ack_skill.py @@ -131,7 +131,7 @@ class AckSkillContentTests(unittest.TestCase): ): self.assertTrue((ack_dir / relative_path).is_file(), relative_path) version = (ack_dir / "VERSION").read_text(encoding="utf-8").strip() - self.assertEqual(version, "0.17.1") + self.assertEqual(version, "0.18.0") self.assertIn( f'ackVersion: "{version}"', (ack_dir / "examples" / "tasks.example.yaml").read_text(encoding="utf-8"),