feat(ack): support OMP workers
This commit is contained in:
@@ -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 黑盒复测并记录证据。
|
||||
+2
-2
@@ -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 配置目录中完成黑盒演练。
|
||||
|
||||
## 路径权限
|
||||
|
||||
+309
-3
@@ -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
|
||||
|
||||
@@ -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`。
|
||||
|
||||
+1
-1
@@ -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` 时,不直接重试;先按
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.17.1
|
||||
0.18.0
|
||||
|
||||
@@ -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 指令文件。
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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`。
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user