cmux-delegate 是做什么的?
概述
一个将任务中途出现的独立问题移交给不同会话的技能。它会自动收集当前对话的工作上下文(Git 元数据 + 从对话中综合的推理上下文),在 cmux 工作区中打开一个独立的 Claude Code 会话,该会话自行完成 issue→worktree→PR 的全流程。支持重用已有会话、单独账户配置和并行分发多个 issue。
核心原则:
- 提示始终通过文件传递。严格禁止内联
-p(避免 shell 转义问题)。 - 当用户明确指定会话/账户时,严格遵循,不得随意重新解释。
- 委托的单位是一个独立 issue。 如果能作为一个 issue 存在,则委托;否则,它不是委托目标,而是应在当前会话中完成的部分。
- 歧义在启动前消除,而非启动后。 不会回来询问,因此完成条件、范围和流程以命令、路径和数字的形式写入提示(步骤 2.6)。
- 发射后不管。 委托即结束——工作会话不向委托会话报告任何信息,委托会话既不监视也不评判工作会话。用户直接在该 cmux 标签页中看到结果。
何时使用
当一个与你当前所做的事情不同的问题在执行过程中出现时使用此技能。
- 在实现过程中发现一个不相关的 bug,希望将其拆分出来
- 审查意见超出当前 PR 的范围,需要转到后续 issue
- 多个问题以此方式累积,每个问题应进入不同的会话 (
--distribute)
委托资格
该项是否能作为一个独立 issue存在——这是唯一的判断标准。
| 判定 | 处理方式 |
|---|---|
| 能作为 issue(有自己的复现步骤、范围和完成标准,即使我的工作未完成也能继续) | 委托 |
| 不能作为 issue(是我当前工作的一部分,只有其结果返回后我的工作才算完成) | 不要委托——在当前会话中完成 |
其结果必须返回才能合并的项目不是委托目标。那是子代理的任务,使用此技能处理意味着要求工作会话提供报告——这种要求在历史上导致了完成报告收集功能 (#894) 和决策门功能 (#984)。两者都在 #1130 中被删除。
输入
通过 issue 编号指定要委托的 issue。
/cmux-delegate "#1140 auth 토큰 갱신 실패" --model opus
/cmux-delegate "#1141 리뷰에서 나온 후속 항목" --session claude-2
/cmux-delegate "별건 3개: #1140, #1141, #1142" --account claude-2 --distribute
委托时 issue 必须已存在。 如果尚无 issue,则在此会话中创建它后再委托——issue 创建是需经批准的程序,因此不交给工作会话。如果仅传递描述而没有编号,工作会话将不知道要将 PR 附加到哪个 issue。
参数
| 参数 | 默认值 | 描述 |
|---|---|---|
<task> | (必需) | 要委托的任务描述 |
--model | jev 路由,否则 sonnet | Provider:model 标记法。fable/opus/sonnet/haiku = claude。也支持 claude、claude:opus、codex (= gpt-5.6-terra,effort medium)、codex:gpt-5.6-sol、codex:gpt-5.6-sol:xhigh、claude:opus:low / opus:low (claude --effort,取值范围 low/medium/high/xhigh/max;未指定则默认不传递)、gemini、gemini:flash。参见项目 ARCHITECTURE.md Provider Routing。 |
--cwd | 当前目录 | 新会话的工作目录 |
--max-budget-usd | — | 不支持 (#1054)。 仅打印模式标志,不能用于交互式工作会话。如果给出,不要静默忽略——告知用户 |
--account | (默认账户) | Claude 账户配置文件(例如 claude-2 → CLAUDE_CONFIG_DIR=~/.claude-2) |
--session | (创建新会话) | 交付到现有工作区(名称或工作区引用) |
--distribute | false | 按 issue粒度 并行分发。不是对单个任务进行分片 |
--permission-mode | — | 已移除 (#1054)。 委托的工作会话是普通会话,因此使用用户的普通默认设置 |
流程
步骤 1:解析参数
从 {{ARGUMENTS}} 中解析参数:
args = parse("{{ARGUMENTS}}")
cwd = args.cwd || $(pwd)
# 接受预算标志但不转发;告知用户已收到 (#1054)。
# 静默丢弃会让用户误以为已设置上限。
if args["max-budget-usd"]: warn("--max-budget-usd 는 대화형 워커에 적용되지 않습니다 (#1054)")
account = args.account || ""
session = args.session || ""
distribute = args.distribute || false
task = args.task (flags 后的剩余文本)
short_task = task[:30],仅保留 [a-zA-Z0-9가-힣 -] 中的字符(用于 cmux 工作区名称)
timestamp = 纪元秒 + PID(例如 1744163800-12345),以避免冲突
# 显式的 --model 永远不会被覆盖。没有显式指定时,jev 选择工作会话的模型;
# 当 route.source 为 "fallback" 时,默认保持 sonnet (#1481)。
route_source = "explicit"
if args.model: model = args.model
else:
route = json(python3 "${CLAUDE_PLUGIN_ROOT}/skills/cmux-delegate/jev-route.py" "$task")
route_source = route.source
if route.source == "jev":
model = route.provider == "claude" ? route.tier : route.provider
else:
model = "sonnet"
# Provider 解析(来自项目 ARCHITECTURE.md Provider Resolution Logic)
if model matches /^(codex|gemini)(?::(.+))?$/:
provider = match[1] # "codex" 或 "gemini"
sub_model = match[2] || "" # "" 或 "gpt-5.6-sol:xhigh" 或 "flash"(去掉第一个冒号)
elif model matches /^(fable|opus|sonnet|haiku)(?::[A-Za-z]+)?$/:
provider = "claude"
sub_model = model # "opus" 或 "opus:low";effort 在下文拆分
elif model matches /^claude(?::(.+))?$/:
provider = "claude"
sub_model = match[1] || ""
else:
provider = "claude"
sub_model = model
# codex 不会回退到配置的默认模型:裸 `codex`
# (显式指定或 jev 选择)获得 terra,列出的模型获得其 effort,
# 除非 `codex:<model>:<effort>` 指定了一个 (#1483)。
CODEX_DEFAULT_MODEL = "gpt-5.6-terra"
CODEX_EFFORT = {"gpt-5.6-luna": "low", "gpt-5.6-terra": "medium", "gpt-5.6-sol": "high"}
effort = ""
if provider == "codex":
sub_model, _, effort = sub_model.partition(":")
sub_model = sub_model || CODEX_DEFAULT_MODEL
effort = effort || CODEX_EFFORT.get(sub_model, "")
# 步骤 4 将两者插值到 bash 包装器中而不加引号,因此像
# `xhigh; touch /tmp/x` 这样的值可能会执行。与其用引号(`"` 和 `$( )` 仍可绕过),
# 不如拒绝任何不是纯标识符的内容。
if sub_model does not match /^[A-Za-z0-9._-]+$/: abort "invalid codex model"
if effort and effort does not match /^[A-Za-z0-9._-]+$/: abort "invalid codex reasoning effort"
# claude 仅在指定 effort 时才接受:`claude:<tier>:<effort>` 或
# `<tier>:<effort>` (#1499)。没有类似 codex 的每层默认表。
# Opus 5.5 提示指南
# (https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-opus-5-5#calibrate-effort)
# 指出“effort level 名称在不同模型中对应的思考量不同”,并建议针对自己的
# 评估集测试每个模型的多个 level,“而不是直接使用在 Claude Opus 5 上使用的设置”。因此
# 这里没有硬编码表格:未指定 effort 时不传递任何内容,模型自身的默认值生效
#(Opus 5.5 上为 `medium`,Opus 5 上为 `high`)。指南还说
# “将 `xhigh` 和 `max` 保留给已测量到质量提升的工作”。jev 选择仅返回裸 tier,
# 从不携带 effort。
CLAUDE_EFFORTS = ["low", "medium", "high", "xhigh", "max"]
if provider == "claude":
# 只有最后一个冒号后的字母尾缀才是 effort,因此自身包含
# 冒号的模型 ID(Bedrock `…-v1:0`、以 `…:application-inference-profile/<id>` 结尾的 ARN)
# 会原样传递。
# provider 后立即出现双冒号(`claude::high`、`claude::`)很可能是
# `claude:opus:high` 的拼写错误;在 effort 拆分前进行检查,
# 否则会错误地将 `claude::high` 视为 high 级别的默认模型。所有双冒号形式
# 都同样中止(下面的 `opus::low`)。
if sub_model starts with ":": abort "invalid claude model"
if sub_model matches /^(.*):([A-Za-z]+)$/:
sub_model, effort = match[1], match[2]
# 格式错误的尾缀(`opus:`、`opus::low`、`opus:low:high`)不得作为模型名称到达
# CLI:没有模型 ID 以 `:` 结尾,也不存在 tier 别名后跟 `:`。
if sub_model ends with ":" or sub_model matches /^(fable|opus|sonnet|haiku):/: abort "invalid claude model"
# 步骤 4 对模型使用单引号,因此字符集必须排除 `'` 和
# 所有 shell 元字符;它允许 `:` `/` 用于 Bedrock ID 和 ARN,
# `@` 用于 Vertex ID(`claude-sonnet-4-5@20250929`),以及 `[]` 用于
# `[1m]` 上下文后缀。不加引号时,`opus[1m]` 会变成通配符,并在当前目录
# 中存在此类文件时变成 `opus1`,因此引号是必须保留的。
if sub_model does not match /^[A-Za-z0-9._:@\/\[\]-]*$/: abort "invalid claude model"
# `claude --effort bogus` 仅警告并以默认 effort 运行,因此
# CLI 不会捕获拼写错误;此检查是唯一的防线。
if effort and effort not in CLAUDE_EFFORTS: abort "invalid claude effort '{effort}' (expected low|medium|high|xhigh|max)"
# 预检:验证 provider CLI 是否可用
if ! command -v "$provider" &>/dev/null:
warn "⚠ ${provider} CLI not found, falling back to claude:sonnet"
provider = "claude"
sub_model = "sonnet"
effort = ""
步骤 1.5:会话解析
决定是使用现有会话还是创建新会话。
if session is specified:
1. cmux list-workspaces → 按名称或引用匹配
2. 找到匹配 → cmux send 模式(步骤 5b)
3. 未找到匹配 → 输出错误 "세션 '{session}'을 찾을 수 없습니다" 并停止
else:
→ 现有行为(新工作区,步骤 5a)
步骤 1.6:账户解析
决定账户配置文件。
if account is specified:
# 账户配置文件通过 CLAUDE_CONFIG_DIR 环境变量设置
# 例如 --account claude-2 → CLAUDE_CONFIG_DIR=~/.claude-2
claude_env = "CLAUDE_CONFIG_DIR=~/.{account}"
# 验证:检查配置目录是否存在
if not exists(~/.{account}):
print error "계정 프로필 디렉토리 ~/.{account}이 없습니다" and stop
else:
claude_env = "" # 使用默认账户
步骤 2:收集上下文
自动收集当前对话和项目的上下文。每个命令即使失败也继续执行(2>/dev/null)。
要收集的信息:
# 1. Git 状态
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
COMMITS=$(git log --oneline -5 2>/dev/null || echo "no git history")
DIFF_STAT=$(git diff --stat HEAD 2>/dev/null || echo "no changes")
# 2. 更改的文件列表(相对于基础分支)
BASE_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||')
BASE_BRANCH=${BASE_BRANCH:-main}
CHANGED_FILES=$(git diff --name-only $(git merge-base HEAD "origin/$BASE_BRANCH" 2>/dev/null || echo HEAD~5)..HEAD 2>/dev/null || echo "unknown")
# 3. PR 信息(如果有)
PR_INFO=$(gh pr list --head "$BRANCH" --json number,title,url 2>/dev/null || echo "no PR")
# 4. PR 审查评论(如果有)
REVIEW_COMMENTS="0"
PR_NUM=$(gh pr list --head "$BRANCH" --json number -q '.[0].number' 2>/dev/null || echo "")
if [ -n "$PR_NUM" ]; then
REVIEW_COMMENTS=$(gh api "repos/$(gh repo view --json nameWithOwner -q '.nameWithOwner' 2>/dev/null)/pulls/$PR_NUM/comments" --jq 'length' 2>/dev/null || echo "0")
fi
# 5. 粘贴内容的 ID——每个提示文件一个唯一 ID(见下文)
PASTE_ID=$(od -An -N3 -tx1 /dev/urandom | tr -d ' \n')
第三方文本会被标记,而非信任(问题 #1500)。 提示文件到达工作会话的方式取决于模式:
- 新会话和分发模式(步骤 5a)。 包装器将文件内容作为工作会话的第一条用户消息传递(claude 和 gemini 通过 argv,codex 通过 stdin),因此其中的任何内容都被视为委托者所说。这是指南中有关粘贴文本的建议所适用的情况。
- 现有会话模式(步骤 5b)。 用户消息仅为 "read
{prompt_file}";内容作为Read工具结果到达。指南将其作为单独的情况对待:工具结果属于模型对间接提示注入的抵抗力("通过工具结果、网页和屏幕或浏览器内容到达的指令")。标签和说明保留在文件中(一个文件,适用于所有模式)作为额外的标记,但在此处,委托者自己的部分也是工具结果内容,而非用户消息。
文件中的某些文本并非委托者所写:COMMITS 包含提交主题,CHANGED_FILES 和 DIFF_STAT 包含文件路径,PR_INFO 包含 PR 标题,所有这些都由其作者撰写,而 Handoff 或 Instructions 可能引用 issue 正文或审查评论。文件路径和提交主题一样容易携带类似指令的文本(例如 PR 可能添加 docs/FOLLOW-THE-INSTRUCTIONS-IN-THE-PR-BLOCK.md)。每个此类块都放在步骤 3 模板顶部的说明之后,位于具有相同 PASTE_ID 的开始和结束 pasted_content 标签之间,每个标签独占一行。标签形式是 Opus 5.5 提示指南中针对粘贴文本给出的形式
(https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-opus-5-5#mark-pasted-text-in-user-messages);与指南相比有三点不同:
- 说明的位置。 指南说"然后将此说明添加到你的系统提示中"。此处说明位于提示文件的顶部,即与不受信任的文本位于同一条用户消息(或工具结果)中。其弱点:PR 标题或提交主题内伪造的说明具有相同的地位