@wwkit/harness 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/lint.md +5 -4
- package/agents/work.md +674 -89
- package/commands/fix.md +77 -0
- package/package.json +3 -1
- package/plugins/work-bootstrap.js +77 -0
- package/scripts/work-review-package +50 -0
- package/scripts/work-task-brief +27 -0
- package/scripts/work-workspace +31 -0
- package/skills/lint-env-ensure/references/config.md +2 -2
- package/skills/read-docs/references/opencode/agents/index.md +2 -0
- package/skills/read-docs/references/opencode/agents/parallel-dispatch.md +149 -0
- package/skills/read-docs/references/opencode/agents/subagent-internals.md +217 -0
- package/skills/read-docs/references/superpowers/bootstrap.md +107 -0
- package/skills/read-docs/references/superpowers/comparison.md +77 -0
- package/skills/read-docs/references/superpowers/context.md +71 -0
- package/skills/read-docs/references/superpowers/index.md +58 -0
- package/skills/read-docs/references/superpowers/parallel.md +49 -0
- package/skills/read-docs/references/superpowers/sdd.md +183 -0
- package/skills/read-docs/references/superpowers/skills.md +68 -0
- package/skills/read-docs/references/superpowers/workflow.md +83 -0
package/commands/fix.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 修复问题:问题定位→修复方案→修复测试→review→总结,并写入 {root_dir}/.webwork/harness/fix/yyyy-mm-dd/问题极简标题-hhmmss.md
|
|
3
|
+
agent: build
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
你(build agent)根据用户问题描述 `$ARGUMENTS` 执行 5 阶段修复流程,产出文档写入 `{root_dir}/.webwork/harness/fix/<yyyy-mm-dd>/<问题极简标题>-<hhmmss>.md`。
|
|
7
|
+
|
|
8
|
+
## 前置
|
|
9
|
+
|
|
10
|
+
1. 解析问题描述 `$ARGUMENTS`;若为空,提示用户补充问题描述后停止。
|
|
11
|
+
2. 确定工程根目录(当前 workdir / root_dir),所有相对路径基于它。
|
|
12
|
+
3. 用 `todowrite` 建立 5 项任务清单(必须显式使用,逐项勾单,不攒批):
|
|
13
|
+
|
|
14
|
+
- [ ] 1. 问题定位
|
|
15
|
+
- [ ] 2. 修复方案
|
|
16
|
+
- [ ] 3. 修复测试
|
|
17
|
+
- [ ] 4. review
|
|
18
|
+
- [ ] 5. 总结
|
|
19
|
+
|
|
20
|
+
## 阶段 1:问题定位
|
|
21
|
+
|
|
22
|
+
- 用 `grep`/`glob`/`read` 定位与问题相关的代码,梳理调用链,尽量复现问题。
|
|
23
|
+
- 无法复现时如实记录"未能复现"及原因(环境/数据缺失等),不臆测。
|
|
24
|
+
- 产出结论:**问题现象 → 定位位置(file:line)→ 根因**。
|
|
25
|
+
|
|
26
|
+
## 阶段 2:修复方案
|
|
27
|
+
|
|
28
|
+
- 先写方案再动手:列出涉及文件、改法、影响面、风险点。
|
|
29
|
+
- 多种方案时选最简单可靠者,一句话说明取舍。
|
|
30
|
+
- 方案需写出来(供阶段 4 review 对照),确认无逻辑漏洞后进入阶段 3。
|
|
31
|
+
|
|
32
|
+
## 阶段 3:修复测试
|
|
33
|
+
|
|
34
|
+
- 实施修改:遵循仓库既有代码风格,不加多余注释,只改必须改的地方。
|
|
35
|
+
- 验证修复:运行相关测试/构建命令,或重跑复现用例确认症状消失。
|
|
36
|
+
- 验证失败则回到阶段 2/3 迭代(自限 ≤3 轮),记录每轮结果。
|
|
37
|
+
|
|
38
|
+
## 阶段 4:review
|
|
39
|
+
|
|
40
|
+
- 自查 diff:边界情况、兼容性、遗漏改动、是否有其他调用方受影响。
|
|
41
|
+
- 复查是否根治而非掩盖症状;发现问题回到阶段 2 修正,修正后重跑阶段 3 验证。
|
|
42
|
+
- 全部通过后进入阶段 5。
|
|
43
|
+
|
|
44
|
+
## 阶段 5:总结
|
|
45
|
+
|
|
46
|
+
1. 生成日期目录和时间戳:`date +%Y-%m-%d` 获取日期,`date +%H%M%S` 获取时间戳(如 `{root_dir}/.webwork/harness/fix/2026-09-15/`),`mkdir -p` 创建。
|
|
47
|
+
2. 标题:从问题描述提炼 ≤6 字极简短语(去空格/标点/特殊字符),如"登录接口401"。
|
|
48
|
+
3. 用 `write` 写入 `{root_dir}/.webwork/harness/fix/<yyyy-mm-dd>/<问题极简标题>-<hhmmss>.md`,结构:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
# <问题极简标题>
|
|
52
|
+
|
|
53
|
+
- 日期:<yyyy-mm-dd>
|
|
54
|
+
- 问题描述:<用户描述摘要>
|
|
55
|
+
|
|
56
|
+
## 1. 问题定位
|
|
57
|
+
|
|
58
|
+
<现象 / 位置 / 根因>
|
|
59
|
+
|
|
60
|
+
## 2. 修复方案
|
|
61
|
+
|
|
62
|
+
<方案与取舍>
|
|
63
|
+
|
|
64
|
+
## 3. 修复测试
|
|
65
|
+
|
|
66
|
+
<改动摘要 + 验证命令与结果>
|
|
67
|
+
|
|
68
|
+
## 4. Review
|
|
69
|
+
|
|
70
|
+
<自查结论>
|
|
71
|
+
|
|
72
|
+
## 5. 总结
|
|
73
|
+
|
|
74
|
+
<影响面、后续建议>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
4. `todowrite` 确认 5 项全部勾完,向用户输出:**文档路径** + 修复结论摘要(改动文件、验证结果)。
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wwkit/harness",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"author": "bluesliu <langcai163@163.com>",
|
|
5
5
|
"description": "WebWork abilities for opencode",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "plugin.js",
|
|
8
8
|
"files": [
|
|
9
9
|
"plugin.js",
|
|
10
|
+
"plugins",
|
|
11
|
+
"scripts",
|
|
10
12
|
"skills",
|
|
11
13
|
"agents",
|
|
12
14
|
"commands",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto'
|
|
2
|
+
|
|
3
|
+
const BOOTSTRAP_MARKER = '__wwkit_work_bootstrap__'
|
|
4
|
+
|
|
5
|
+
const BOOTSTRAP_TEXT = `[${BOOTSTRAP_MARKER}]
|
|
6
|
+
|
|
7
|
+
你是 work(任务调度指挥官)。以下强约束优先级高于你的其他指令,必须遵守:
|
|
8
|
+
|
|
9
|
+
## Red Flags(禁止行为)
|
|
10
|
+
|
|
11
|
+
1. **禁止亲自逐行实现**:你的职责是拆解→派发→汇总,不是写代码。除非是单步 trivial 任务(≤1 文件、≤5 行),否则必须派发 subagent。
|
|
12
|
+
2. **禁止先动手后规划**:多步骤任务必须先 todowrite 落单,再派发。
|
|
13
|
+
3. **禁止上下文倾倒**:subagent prompt 必须自包含精炼上下文,不要把整个对话历史贴进去。
|
|
14
|
+
4. **禁止串行派发**:独立任务必须在一条消息里并行发出多个 task 调用(≤5)。
|
|
15
|
+
5. **禁止无限重试**:单个 worker failed/blocked 只记录不中断;全部失败才重规划;重规划 ≤3 轮。
|
|
16
|
+
|
|
17
|
+
## 强制优先级
|
|
18
|
+
|
|
19
|
+
- 能 single 绝不 parallel(先路由判断)。
|
|
20
|
+
- 但如果任务涉及 ≥2 个独立子问题或 ≥3 个文件,必须 parallel。
|
|
21
|
+
- subagent 类型:只读调研用 explore,可写改动用 general。
|
|
22
|
+
- 每轮派发前必须确认 writable 白名单不重叠(文件所有权分区)。`
|
|
23
|
+
|
|
24
|
+
export const WorkBootstrapPlugin = async () => {
|
|
25
|
+
return {
|
|
26
|
+
'experimental.chat.messages.transform': async (input, output) => {
|
|
27
|
+
const messages = output.messages
|
|
28
|
+
if (!Array.isArray(messages) || messages.length === 0) return
|
|
29
|
+
|
|
30
|
+
const users = messages.filter((m) => m?.info?.role === 'user')
|
|
31
|
+
const lastUser = users[users.length - 1]
|
|
32
|
+
if (!lastUser) return
|
|
33
|
+
|
|
34
|
+
const agent = lastUser.info?.agent
|
|
35
|
+
if (agent !== 'work') return
|
|
36
|
+
|
|
37
|
+
const alreadyInjected = messages.some((m) => {
|
|
38
|
+
const parts = m?.parts
|
|
39
|
+
if (!Array.isArray(parts)) return false
|
|
40
|
+
return parts.some(
|
|
41
|
+
(p) => p?.type === 'text' && typeof p.text === 'string' && p.text.includes(BOOTSTRAP_MARKER)
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
if (alreadyInjected) return
|
|
45
|
+
|
|
46
|
+
const sessionId = lastUser.info?.sessionID || lastUser.info?.id || ''
|
|
47
|
+
const messageId = lastUser.info?.id || ''
|
|
48
|
+
|
|
49
|
+
const bootstrapPart = {
|
|
50
|
+
id: `prt_${randomUUID()}`,
|
|
51
|
+
sessionID: sessionId,
|
|
52
|
+
messageID: messageId,
|
|
53
|
+
type: 'text',
|
|
54
|
+
text: BOOTSTRAP_TEXT,
|
|
55
|
+
synthetic: true,
|
|
56
|
+
time: { start: Date.now() }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
messages.unshift({
|
|
60
|
+
info: {
|
|
61
|
+
id: `msg_${randomUUID()}`,
|
|
62
|
+
sessionID: sessionId,
|
|
63
|
+
role: 'assistant',
|
|
64
|
+
time: { created: Date.now() },
|
|
65
|
+
parentID: '',
|
|
66
|
+
modelID: '',
|
|
67
|
+
providerID: '',
|
|
68
|
+
mode: 'primary',
|
|
69
|
+
path: { cwd: '', root: '' },
|
|
70
|
+
cost: 0,
|
|
71
|
+
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }
|
|
72
|
+
},
|
|
73
|
+
parts: [bootstrapPart]
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Generate a review package: commit list, stat summary, and the net
|
|
3
|
+
# diff with extended context, written to a file the reviewer reads in one
|
|
4
|
+
# call. Using the recorded per-task BASE (not HEAD~1) keeps multi-commit
|
|
5
|
+
# tasks intact.
|
|
6
|
+
#
|
|
7
|
+
# Usage: work-review-package WORKSPACE_DIR TASK_NUMBER BASE HEAD [SUFFIX]
|
|
8
|
+
# Writes to: <WORKSPACE_DIR>/task-<N>-review-<base7>..<head7>.diff
|
|
9
|
+
# If SUFFIX is "final", writes to: <WORKSPACE_DIR>/final-review-<base7>..<head7>.diff
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
if [ $# -lt 4 ] || [ $# -gt 5 ]; then
|
|
13
|
+
echo "usage: work-review-package WORKSPACE_DIR TASK_NUMBER BASE HEAD [SUFFIX]" >&2
|
|
14
|
+
exit 2
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
ws="$1"
|
|
18
|
+
n="$2"
|
|
19
|
+
base="$3"
|
|
20
|
+
head="$4"
|
|
21
|
+
suffix="${5:-}"
|
|
22
|
+
[ -d "$ws" ] || { echo "no such workspace directory: $ws" >&2; exit 2; }
|
|
23
|
+
|
|
24
|
+
git rev-parse --verify --quiet "$base" >/dev/null || { echo "bad BASE: $base" >&2; exit 2; }
|
|
25
|
+
git rev-parse --verify --quiet "$head" >/dev/null || { echo "bad HEAD: $head" >&2; exit 2; }
|
|
26
|
+
|
|
27
|
+
base7=$(git rev-parse --short "$base")
|
|
28
|
+
head7=$(git rev-parse --short "$head")
|
|
29
|
+
|
|
30
|
+
if [ "$suffix" = "final" ]; then
|
|
31
|
+
out="$ws/final-review-${base7}..${head7}.diff"
|
|
32
|
+
else
|
|
33
|
+
out="$ws/task-${n}-review-${base7}..${head7}.diff"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
{
|
|
37
|
+
echo "# Review package: ${base}..${head}"
|
|
38
|
+
echo
|
|
39
|
+
echo "## Commits"
|
|
40
|
+
git log --oneline "${base}..${head}"
|
|
41
|
+
echo
|
|
42
|
+
echo "## Files changed"
|
|
43
|
+
git diff --stat "${base}..${head}"
|
|
44
|
+
echo
|
|
45
|
+
echo "## Diff"
|
|
46
|
+
git diff -U10 "${base}..${head}"
|
|
47
|
+
} > "$out"
|
|
48
|
+
|
|
49
|
+
commits=$(git rev-list --count "${base}..${head}")
|
|
50
|
+
echo "wrote ${out}: ${commits} commit(s), $(wc -c < "$out" | tr -d ' ') bytes"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Write a task brief file from stdin, so the implementer reads its task
|
|
3
|
+
# text in one call instead of receiving it through the controller's context.
|
|
4
|
+
#
|
|
5
|
+
# Usage: work-task-brief WORKSPACE_DIR TASK_NUMBER
|
|
6
|
+
# Reads task text from stdin, writes to <WORKSPACE_DIR>/task-<N>-brief.md
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
if [ $# -ne 2 ]; then
|
|
10
|
+
echo "usage: work-task-brief WORKSPACE_DIR TASK_NUMBER" >&2
|
|
11
|
+
echo " Reads task text from stdin." >&2
|
|
12
|
+
exit 2
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
ws="$1"
|
|
16
|
+
n="$2"
|
|
17
|
+
[ -d "$ws" ] || { echo "no such workspace directory: $ws" >&2; exit 2; }
|
|
18
|
+
|
|
19
|
+
out="$ws/task-${n}-brief.md"
|
|
20
|
+
cat > "$out"
|
|
21
|
+
|
|
22
|
+
if [ ! -s "$out" ]; then
|
|
23
|
+
echo "task brief is empty (stdin had no content)" >&2
|
|
24
|
+
exit 3
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
echo "wrote ${out}: $(wc -l < "$out" | tr -d ' ') lines"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resolve and ensure the working-tree directory for one work session's
|
|
3
|
+
# short-lived artifacts: plan, task briefs, implementer reports, review
|
|
4
|
+
# packages, and the progress ledger.
|
|
5
|
+
#
|
|
6
|
+
# Directory: <root_dir>/webwork/harness/work/<session-id>/
|
|
7
|
+
# One directory per session so concurrent sessions never collide.
|
|
8
|
+
#
|
|
9
|
+
# Usage: work-workspace ROOT_DIR [SESSION_ID]
|
|
10
|
+
# Prints the session directory's absolute path.
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
|
|
14
|
+
echo "usage: work-workspace ROOT_DIR [SESSION_ID]" >&2
|
|
15
|
+
exit 2
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
root="$1"
|
|
19
|
+
[ -d "$root" ] || { echo "no such directory: $root" >&2; exit 2; }
|
|
20
|
+
|
|
21
|
+
if [ $# -eq 2 ]; then
|
|
22
|
+
sid="$2"
|
|
23
|
+
else
|
|
24
|
+
sid="$(date +%s)"
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
base="$root/webwork/harness/work"
|
|
28
|
+
dir="$base/$sid"
|
|
29
|
+
mkdir -p "$dir"
|
|
30
|
+
printf '*\n' > "$base/.gitignore"
|
|
31
|
+
cd "$dir" && pwd
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
|
|
42
42
|
| 参数 | 值 | 说明 |
|
|
43
43
|
|------|-----|------|
|
|
44
|
-
| report_dir_pattern | `{root_dir}/.webwork/lint/
|
|
45
|
-
| report.lint_report | `lint_report.md` | 最终报告文件名 |
|
|
44
|
+
| report_dir_pattern | `{root_dir}/.webwork/harness/lint/{YYYY-MM-DD}/` | 报告输出目录 |
|
|
45
|
+
| report.lint_report | `lint_report-{HHMMSS}.md` | 最终报告文件名 |
|
|
46
46
|
|
|
47
47
|
## 语言映射
|
|
48
48
|
|
|
@@ -305,3 +305,5 @@ opencode agent create
|
|
|
305
305
|
- [Agent 设计模式](/agents/design-pattern) — 核心设计原则
|
|
306
306
|
- [Agent 示例](/agents/examples) — 实用示例
|
|
307
307
|
- [Agent 案例](/agents/cases) — 完整系统设计案例
|
|
308
|
+
- [Subagent 内部机制](/agents/subagent-internals) — task 工具实现、会话生命周期、权限隔离、后台 subagent、命令触发子任务
|
|
309
|
+
- [并行派发与调度策略](/agents/parallel-dispatch) — 并行机制、work agent 路由决策、摘要协议、设计决策
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# 并行派发与调度策略
|
|
2
|
+
|
|
3
|
+
> 并行工作流模式参见 [Agent 工作流](/agents/workflow)。本文聚焦 opencode task 工具的并行内部机制和 harness work agent 的调度策略。
|
|
4
|
+
|
|
5
|
+
## 并行原理
|
|
6
|
+
|
|
7
|
+
opencode 的 `task` 工具描述明确鼓励并行:
|
|
8
|
+
|
|
9
|
+
> "Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses"
|
|
10
|
+
|
|
11
|
+
当 LLM 在一条 assistant message 中返回多个 `task` tool call 时,opencode 的 chat processor **并发执行**这些工具调用。每个 `task` 调用创建独立的子会话,互不干扰。
|
|
12
|
+
|
|
13
|
+
## 并行限制
|
|
14
|
+
|
|
15
|
+
| 限制项 | 默认值 | 说明 |
|
|
16
|
+
|--------|--------|------|
|
|
17
|
+
| 并行数量 | 无显式限制 | work agent prompt 中限制 ≤5 |
|
|
18
|
+
| 嵌套深度 | `subagent_depth: 1` | 子 agent 默认不能再派发子 agent |
|
|
19
|
+
| 文件所有权 | 由编排者保证 | 同一文件不并行改(work agent 规划 writable 白名单) |
|
|
20
|
+
|
|
21
|
+
## 完整调度时序
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
用户 work agent task 工具 子会话(explore) 子会话(general)
|
|
25
|
+
│ │ │ │ │
|
|
26
|
+
│── "实现功能X" ─────►│ │ │ │
|
|
27
|
+
│ │ │ │ │
|
|
28
|
+
│ 解析输入 │ │ │
|
|
29
|
+
│ 路由判断: parallel │ │ │
|
|
30
|
+
│ 内联规划 │ │ │
|
|
31
|
+
│ todowrite 落单 │ │ │
|
|
32
|
+
│ │ │ │ │
|
|
33
|
+
│ ├── task(explore) ──►│创建子会话 ─────────►│ │
|
|
34
|
+
│ │ │parentID=父ID │ │
|
|
35
|
+
│ │ │权限隔离 │ │
|
|
36
|
+
│ │ │prompt 注入 ───────►│ │
|
|
37
|
+
│ ├── task(general) ──►│创建子会话 ──────────────────────────────►│
|
|
38
|
+
│ │ │parentID=父ID │ │
|
|
39
|
+
│ │ │权限隔离 │ │
|
|
40
|
+
│ │ │prompt 注入 ────────────────────────────►│
|
|
41
|
+
│ │ │ │ │
|
|
42
|
+
│ │ │ │ 工具调用(read/grep) │
|
|
43
|
+
│ │ │ │ 多轮 reasoning │
|
|
44
|
+
│ │ │ │ 生成结果摘要 │
|
|
45
|
+
│ │ │◄── XML 结果 ──────│ │
|
|
46
|
+
│ │ │ │ │
|
|
47
|
+
│ │ │ │ │ 工具调用(edit/bash)
|
|
48
|
+
│ │ │ │ │ 多轮 reasoning
|
|
49
|
+
│ │ │ │ │ 生成结果摘要
|
|
50
|
+
│ │ │◄── XML 结果 ─────────────────────────────│
|
|
51
|
+
│ │◄── 结果返回 ─────────│ │ │
|
|
52
|
+
│ │ │ │ │
|
|
53
|
+
│ 收集摘要 │ │ │
|
|
54
|
+
│ todowrite 勾单 │ │ │
|
|
55
|
+
│ 综合分析 │ │ │
|
|
56
|
+
│ 验收 │ │ │
|
|
57
|
+
│ │ │ │ │
|
|
58
|
+
│◄── 最终结果 ────────│ │ │ │
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## work agent 调度策略
|
|
62
|
+
|
|
63
|
+
### 路由决策
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
用户输入
|
|
67
|
+
│
|
|
68
|
+
▼
|
|
69
|
+
解析 target / root_dir / constraints
|
|
70
|
+
│
|
|
71
|
+
▼
|
|
72
|
+
路由判断
|
|
73
|
+
├─ single(单 worker 足够)
|
|
74
|
+
│ └─ 派 1 个 subagent 直接执行
|
|
75
|
+
│
|
|
76
|
+
└─ parallel(需要编排)
|
|
77
|
+
└─ 内联规划 → 并行派发 → 收集摘要 → 综合/验收
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
路由判断权重:**可拆性**(≥2 个独立子问题)> **时效**(用户要求快)> **规模**(文件多/改动大)
|
|
81
|
+
|
|
82
|
+
### subagent_type 选择
|
|
83
|
+
|
|
84
|
+
| subagent_type | 场景 | 工具权限 |
|
|
85
|
+
|---------------|------|---------|
|
|
86
|
+
| `explore` | 只读调研(搜索代码、读取文件、理解结构) | read/grep/glob(只读) |
|
|
87
|
+
| `general` | 可写改动(编辑代码、执行命令、多步实现) | 全部工具(可写) |
|
|
88
|
+
|
|
89
|
+
### 任务项规划格式
|
|
90
|
+
|
|
91
|
+
work agent 为每个子任务规划以下字段:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
task_id: T1 / T2 / ...
|
|
95
|
+
goal: 目标,≤1 句话,可验收
|
|
96
|
+
agent: explore(只读)| general(可写)
|
|
97
|
+
input: 自包含完整上下文
|
|
98
|
+
writable: 可写文件白名单(为空 ⇒ 只读)
|
|
99
|
+
forbidden: 禁改文件清单(至少含 constraints)
|
|
100
|
+
output: 交付说明(强制以摘要协议结尾)
|
|
101
|
+
budget: 预计 ≤5 分钟
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 并行规则
|
|
105
|
+
|
|
106
|
+
1. **路由判断**:先判断 single(单 worker 足够)还是 parallel(需要编排)
|
|
107
|
+
2. **文件所有权分区**:同一轮内并行的 subagent 不得改同一文件
|
|
108
|
+
3. **只读与可写并行**:只读调研任务与写文件任务可并行(只读不受分区限制)
|
|
109
|
+
4. **有依赖串行**:无依赖的并行,有依赖的串行
|
|
110
|
+
5. **容忍部分失败**:单个 worker failed/blocked 只记录不中断
|
|
111
|
+
|
|
112
|
+
## 摘要协议
|
|
113
|
+
|
|
114
|
+
所有被 work 派发的 subagent 必须以固定格式摘要结尾:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
## 结果摘要
|
|
118
|
+
status=done|partial|failed|blocked|escalate
|
|
119
|
+
结论: <≤3 行>
|
|
120
|
+
变更: <文件路径 + diff 摘要,每文件一行,无则填 无>
|
|
121
|
+
阻塞: <阻塞项,无则填 无>
|
|
122
|
+
建议: <下一步建议>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
| Status | 含义 | 编排者动作 |
|
|
126
|
+
|--------|------|-----------|
|
|
127
|
+
| `done` | 目标完成 | 标记任务完成 |
|
|
128
|
+
| `partial` | 部分完成 | 记录,继续综合 |
|
|
129
|
+
| `failed` | 失败 | 记录失败项 |
|
|
130
|
+
| `blocked` | 被环境/依赖卡住 | 记录,可能重规划 |
|
|
131
|
+
| `escalate` | 超出边界/需人工介入 | 停止该分支,向用户说明 |
|
|
132
|
+
|
|
133
|
+
## 关键设计决策
|
|
134
|
+
|
|
135
|
+
### 上下文隔离
|
|
136
|
+
|
|
137
|
+
子 agent 完全隔离,看不到父会话历史。原因:防止上下文爆炸、确保聚焦、降低 token 消耗、使并行子 agent 互不干扰。代价:编排者必须为每个子任务编写自包含的 prompt。
|
|
138
|
+
|
|
139
|
+
### 权限收敛
|
|
140
|
+
|
|
141
|
+
子 agent 默认不能使用 `task` 和 `todowrite` 工具。原因:防止无限递归、任务清单管理是编排者独有的职责。例外:agent 的 `permission` 配置中显式允许 `task` 时可嵌套,但受 `subagent_depth` 限制。
|
|
142
|
+
|
|
143
|
+
### 结果精简
|
|
144
|
+
|
|
145
|
+
子 agent 只返回最后一条 text part,编排者只读取摘要。原因:防止结果倾倒导致父会话上下文膨胀、强制子 agent 浓缩信息。
|
|
146
|
+
|
|
147
|
+
### 容忍部分失败
|
|
148
|
+
|
|
149
|
+
单个 worker failed/blocked 只记录不中断。原因:并行任务中一个失败不应影响其他独立任务、最大化任务完成率。
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Subagent 内部机制
|
|
2
|
+
|
|
3
|
+
> 基于 opencode v1.18.30 二进制逆向 + SDK 类型定义分析。配置层面(agent 定义、mode、permission)参见 [Agent 详解](/agents/detail)。
|
|
4
|
+
|
|
5
|
+
## task 工具参数
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
{
|
|
9
|
+
description: string, // 3-5 词的任务描述
|
|
10
|
+
prompt: string, // 完整任务指令(必须自包含)
|
|
11
|
+
subagent_type: string, // agent 名称(如 "explore"、"general")
|
|
12
|
+
task_id?: string, // 传入已有 task_id 可恢复之前的子会话
|
|
13
|
+
background?: boolean, // 实验性:后台执行
|
|
14
|
+
command?: string // 触发此任务的命令
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 执行流程(TaskTool.execute)
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
22
|
+
│ 1. 深度检查 │
|
|
23
|
+
│ 沿 parentID 链向上遍历,计算嵌套深度 h │
|
|
24
|
+
│ if h >= subagent_depth (默认 1) → 报错退出 │
|
|
25
|
+
├─────────────────────────────────────────────────────────────┤
|
|
26
|
+
│ 2. 权限检查 │
|
|
27
|
+
│ 向用户请求使用 subagent_type 的权限 │
|
|
28
|
+
│ (除非 bypassAgentCheck=true,如命令触发的子任务) │
|
|
29
|
+
├─────────────────────────────────────────────────────────────┤
|
|
30
|
+
│ 3. Agent 解析 │
|
|
31
|
+
│ AgentRegistry.get(subagent_type) │
|
|
32
|
+
│ 找不到 → "Unknown agent type" 错误 │
|
|
33
|
+
├─────────────────────────────────────────────────────────────┤
|
|
34
|
+
│ 4. 会话创建/恢复 │
|
|
35
|
+
│ 有 task_id → 尝试恢复已有子会话(继续之前的上下文) │
|
|
36
|
+
│ 无 task_id → 创建新子会话: │
|
|
37
|
+
│ Session.create({ │
|
|
38
|
+
│ parentID: 当前会话ID, │
|
|
39
|
+
│ title: description + " (@agent subagent)", │
|
|
40
|
+
│ agent: subagent_type, │
|
|
41
|
+
│ permission: [...继承+限制规则...] │
|
|
42
|
+
│ }) │
|
|
43
|
+
├─────────────────────────────────────────────────────────────┤
|
|
44
|
+
│ 5. 模型解析 │
|
|
45
|
+
│ 优先使用 agent 配置的 model │
|
|
46
|
+
│ 否则继承父 assistant message 的 model │
|
|
47
|
+
├─────────────────────────────────────────────────────────────┤
|
|
48
|
+
│ 6. 执行子任务 (TaskTool.runTask) │
|
|
49
|
+
│ a. 解析 prompt 中的 @file 引用 │
|
|
50
|
+
│ b. 调用 SessionPrompt.prompt({ │
|
|
51
|
+
│ sessionID: 子会话ID, │
|
|
52
|
+
│ model: 解析的模型, │
|
|
53
|
+
│ agent: subagent_type, │
|
|
54
|
+
│ parts: 解析后的 prompt parts │
|
|
55
|
+
│ }) │
|
|
56
|
+
│ c. 等待子会话 LLM 执行完成 │
|
|
57
|
+
│ d. 提取子 agent 最终消息的最后一个 text part │
|
|
58
|
+
├─────────────────────────────────────────────────────────────┤
|
|
59
|
+
│ 7. 结果返回 │
|
|
60
|
+
│ 前台模式: 等待完成,返回 XML 格式结果 │
|
|
61
|
+
│ 后台模式: 立即返回,完成后注入结果 │
|
|
62
|
+
└─────────────────────────────────────────────────────────────┘
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 结果格式
|
|
66
|
+
|
|
67
|
+
子 agent 的结果以 XML 标签返回给父 agent:
|
|
68
|
+
|
|
69
|
+
```xml
|
|
70
|
+
<task id="<sessionID>" state="completed">
|
|
71
|
+
<task_result>
|
|
72
|
+
<子 agent 最后一条 text part 的内容>
|
|
73
|
+
</task_result>
|
|
74
|
+
</task>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
失败时:
|
|
78
|
+
|
|
79
|
+
```xml
|
|
80
|
+
<task id="<sessionID>" state="error">
|
|
81
|
+
<task_error>
|
|
82
|
+
<错误信息>
|
|
83
|
+
</task_error>
|
|
84
|
+
</task>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 权限隔离(lt 函数)
|
|
88
|
+
|
|
89
|
+
子会话创建时,权限规则由 `lt()` 函数生成:
|
|
90
|
+
|
|
91
|
+
| 规则 | 默认效果 | 说明 |
|
|
92
|
+
|------|---------|------|
|
|
93
|
+
| `task: deny` | 禁止子 agent 再派发子 agent | 防止无限递归 |
|
|
94
|
+
| `todowrite: deny` | 禁止子 agent 使用 todowrite | 只有编排者管理任务清单 |
|
|
95
|
+
| `primary_tools: deny` | 禁止子 agent 使用 primary 专属工具 | 如 `experimental.primary_tools` 配置 |
|
|
96
|
+
| 继承父会话 `deny` 规则 | 保持安全策略一致 | |
|
|
97
|
+
| 继承父会话 `external_directory` 规则 | 保持目录访问限制 | |
|
|
98
|
+
|
|
99
|
+
除非 agent 的 `permission` 配置中显式允许 `task` 或 `todowrite`,否则默认被拒绝。
|
|
100
|
+
|
|
101
|
+
## 会话恢复(task_id 复用)
|
|
102
|
+
|
|
103
|
+
传入 `task_id` 可以恢复之前的子会话:
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
let T = m.task_id
|
|
107
|
+
? yield* t.get(wt.make(m.task_id)).pipe(s.catchCause(() => s.succeed(void 0)))
|
|
108
|
+
: void 0;
|
|
109
|
+
|
|
110
|
+
// 如果找到已有会话,复用;否则创建新的
|
|
111
|
+
let O = T ?? (yield* t.create({...}));
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
恢复时,子会话**保留之前的所有消息和工具输出**,新的 prompt 追加到已有消息序列后。这使得编排者可以:
|
|
115
|
+
- 让子 agent 继续未完成的任务
|
|
116
|
+
- 向子 agent 提供额外信息后重试
|
|
117
|
+
- 让子 agent 修正之前的错误
|
|
118
|
+
|
|
119
|
+
## 会话存储
|
|
120
|
+
|
|
121
|
+
- **存储后端**:SQLite(Node >= 22.5 用内置 `node:sqlite`,否则 `sqlite3` CLI)
|
|
122
|
+
- **存储位置**:`Path.state` 目录下的 SQLite 数据库
|
|
123
|
+
- **事件溯源**:v2 使用 durable events(`aggregateID` = sessionID, `seq` = 单调序号)
|
|
124
|
+
|
|
125
|
+
| 隔离维度 | 方式 |
|
|
126
|
+
|---------|------|
|
|
127
|
+
| 消息 | 每个 Message/Part 有 `sessionID`,按会话隔离 |
|
|
128
|
+
| 上下文 | 子会话不继承父会话消息 |
|
|
129
|
+
| 权限 | `lt()` 函数生成隔离的权限规则集 |
|
|
130
|
+
| 工具 | `primary_tools` 限制 + agent `tools` 配置 |
|
|
131
|
+
| 深度 | `subagent_depth` 限制嵌套层数 |
|
|
132
|
+
|
|
133
|
+
父子关系查询:`GET /session/{id}/children → Array<Session>`
|
|
134
|
+
|
|
135
|
+
## 后台 Subagent(实验性)
|
|
136
|
+
|
|
137
|
+
启用方式:`OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` 或配置 `experimental.backgroundSubagents`。
|
|
138
|
+
|
|
139
|
+
| 维度 | 前台模式(默认) | 后台模式(实验性) |
|
|
140
|
+
|------|----------------|-------------------|
|
|
141
|
+
| 返回时机 | 等待子会话完成 | 立即返回 "running" |
|
|
142
|
+
| 父 agent 行为 | 阻塞等待 | 继续执行其他任务 |
|
|
143
|
+
| 结果注入 | 作为 tool result | 完成后注入 synthetic message |
|
|
144
|
+
| 提升机制 | 无 | `waitForPromotion()` 可将后台提升为前台 |
|
|
145
|
+
|
|
146
|
+
后台完成后,结果通过 `TaskTool.injectBackgroundResult` 注入父会话:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
yield* K.prompt({
|
|
150
|
+
sessionID: <父会话ID>,
|
|
151
|
+
parts: [{
|
|
152
|
+
type: "text",
|
|
153
|
+
synthetic: true,
|
|
154
|
+
text: <XML 格式的任务结果>
|
|
155
|
+
}]
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## 命令触发的子任务(handleSubtask 路径)
|
|
160
|
+
|
|
161
|
+
除了 LLM 直接调用 `task` 工具外,还有一条由**命令**触发的子任务路径。
|
|
162
|
+
|
|
163
|
+
### 触发条件
|
|
164
|
+
|
|
165
|
+
当用户执行一个命令,且命令对应的 agent `mode === "subagent"` 时(或命令配置了 `subtask: true`),opencode 自动创建 `SubtaskPart`:
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
let G = ie.mode === "subagent" && O.subtask !== false || O.subtask === true;
|
|
169
|
+
let We = G ? [{
|
|
170
|
+
type: "subtask",
|
|
171
|
+
agent: ie.name,
|
|
172
|
+
description: O.description ?? "",
|
|
173
|
+
command: t.command,
|
|
174
|
+
model: { providerID, modelID },
|
|
175
|
+
prompt: Y.find((A) => A.type === "text")?.text ?? ""
|
|
176
|
+
}] : [...L, ...t.parts ?? []];
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 执行差异
|
|
180
|
+
|
|
181
|
+
- **权限检查**:`bypassAgentCheck: true`,跳过用户权限确认
|
|
182
|
+
- **结果处理**:命令触发的子任务完成后,注入 synthetic user message:`"Summarize the task tool output above and continue with your task."`
|
|
183
|
+
- **消息创建**:自动创建 assistant message + tool part 记录子任务执行
|
|
184
|
+
|
|
185
|
+
### @agent 引用
|
|
186
|
+
|
|
187
|
+
用户在消息中使用 `@agent` 语法时,生成 `AgentPart`,opencode 注入 synthetic 指令:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
Use the above message and context to generate a prompt and call the task tool with subagent: <agent_name>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## 关键类型定义
|
|
194
|
+
|
|
195
|
+
### SubtaskPart
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
type SubtaskPart = {
|
|
199
|
+
id: string
|
|
200
|
+
sessionID: string
|
|
201
|
+
messageID: string
|
|
202
|
+
type: "subtask"
|
|
203
|
+
prompt: string
|
|
204
|
+
description: string
|
|
205
|
+
agent: string // 要派发的 subagent 名称
|
|
206
|
+
model?: { providerID: string; modelID: string }
|
|
207
|
+
command?: string
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### 配置项参考
|
|
212
|
+
|
|
213
|
+
| 配置项 | 默认值 | 说明 |
|
|
214
|
+
|--------|--------|------|
|
|
215
|
+
| `subagent_depth` | `1` | 子 agent 嵌套深度限制 |
|
|
216
|
+
| `experimental.primary_tools` | `[]` | 仅 primary agent 可用的工具列表 |
|
|
217
|
+
| `experimental.backgroundSubagents` | `false` | 启用后台 subagent |
|