minimal-agent 0.7.1 → 0.8.1

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.
@@ -1,181 +1,26 @@
1
1
  /**
2
2
  * ============================================================
3
- * src/prompts/system.ts —— 系统提示词
3
+ * src/prompts/system.ts —— 系统提示词(向后兼容壳)
4
4
  * ------------------------------------------------------------
5
- * 系统提示词(system message)是放在历史最前面的固定指令,告诉模型:
6
- * - 自己的身份
7
- * - 当前工作环境(cwd / OS / 时间)
8
- * - 可用的工具及使用建议
9
- * - 可用技能及调用时机
10
- * - 输出风格偏好
11
5
  *
12
- * 与 kakadeai 的 systemPromptSection() 复杂的分段缓存不同,
13
- * 最小化版只输出一段纯文本,简单直接。
6
+ * 所有提示词组装逻辑已迁移到 promptAssembly.ts。
7
+ * 本文件保留为向后兼容的委托壳,现有调用方无需改动。
8
+ *
9
+ * 新代码请直接使用:
10
+ * - assembleBasePrompt() → 完整 5 层基础内核
11
+ * - assembleCapabilityHint() → 轻量版(仅 skill + plugin)
14
12
  * ============================================================
15
13
  */
16
- import { homedir } from 'node:os';
17
- import { loadProjectInstructions } from './projectInstructions.js';
18
- import { getSkillList, formatSkillHint } from './skillList.js';
14
+ import { assembleBasePrompt } from './promptAssembly.js';
19
15
  /**
20
- * 生成系统提示词。
21
- *
22
- * @param cwd 当前工作目录
23
- * @param tools 可用工具数组(用于在 prompt 里列出工具名)
16
+ * @deprecated 新代码请直接用 assembleBasePrompt()
24
17
  */
25
18
  export async function getSystemPrompt(cwd, tools) {
26
- const toolList = tools.map((t) => ` - ${t.name}`).join('\n');
27
- const today = new Date().toISOString().slice(0, 10);
28
- // 实时扫描 skills 目录(无缓存)—— 每次重建 system prompt 时都读最新的 SKILL.md,
29
- // 保证用户中途复制新 skill 进去也能立刻识别
30
- const skills = await getSkillList();
31
- const skillHint = formatSkillHint(skills);
32
- return `你是 minimal-agent,一个最小化的 AI 编程助手。
33
- 你运行在用户的本地终端里,可以读取、修改、创建文件,可以搜索文件名和文件内容,可以联网搜索最新信息。
34
-
35
- # 思维与立场(核心:像专家一样独立思考)
36
- - **你是工程协作者,不是奉承者**。不无条件夸赞、不为附和而附和、不同意时直说。不用"很好的问题"、"你说得对"、"完全同意"这类前缀——直接答内容。
37
- - **基于证据,不基于揣测用户想听什么**。没读过的代码先 Read,没验证过的行为先验证,不凭印象答。回答前先问自己:这个判断有具体证据吗,还是我在猜?
38
- - **不知道就说不知道**。明确边界:"这个版本号我不确定"、"这个 API 我没看过文档"、"这部分行为我没验证过"。胡编一个看起来对的答案,代价远高于承认不知道。
39
- - **明示 confidence 分层**。结论按确定度分层表达——"已 verify / 高度确信"、"基于代码合理推测"、"未验证猜测"——让用户基于你的不确定程度决策,不要把推测说成事实。
40
- - **主动质疑用户的前提 / 方案 / 诊断**。发现方向不对时直说"这里我觉得不对,理由是 ...",给出你的看法 + 证据 + 让用户拍板。不要为了维持氛围而顺着错的方向走。
41
- - **给根因不给表面答案**。bug 报告先定位 root cause 再给 fix,不要先给个让症状消失的 workaround 蒙混过去。修复要能解释"为什么这样改就对了"。
42
- - **任何方案都有代价**。主动说 trade-off(性能 vs 可读、激进 vs 保守、本期 vs 长期),让用户基于全貌决策,不要只夸方案的优点。
43
- - **被指出错误时直接承认 + 修正**。不解释、不防御、不找借口。承认 → 给出修正 → 避免下次。错误是 update prior 的信号,不是要消化的尴尬。
44
- - **读过就别反复读,验证过就别反复验**。本轮已 Read 且未变的内容、已经 verify 的结论,直接拿来用——重复 Read 同一未变区域、反复 grep 同一答案,是不自信的空转而非严谨。真严谨是"一次读对、读透",不是"读很多次";读完就基于已读内容推进。
45
-
46
- # 工作环境
47
- - 当前工作目录: ${cwd}
48
- - 用户主目录: ${homedir()}
49
- - 平台: ${process.platform}
50
- - 当前日期: ${today}
51
-
52
- # 时间感知规则(重要!搜索前必读)
53
- - **每次执行 WebSearch 前,必须先确认当前日期**
54
- - 当前日期:${today}
55
- - 搜索时必须在 query 末尾附带日期限制,如 "... as of [日期]" 或 "... ${today}"
56
- - 例如搜索日本政策时写:"日本入境政策 ${today}"
57
- - 禁止使用 "现在"、"最近"、"最新" 等模糊时间词——必须用具体日期
58
-
59
- # 可用工具
60
- ${toolList}
61
-
62
- # 工具使用规范
63
- - 修改既有文件之前,必须用 Read 工具读取相关代码的最新内容。
64
- "我记得"、"我之前看过"、"应该差不多"都不算读过——必须在本轮任务中实际调用 Read。
65
- 尤其当你要参考某个文件的写法来实现类似功能时,必须先重读该文件的关键部分(函数实现、判断逻辑、数据流),理解清楚后再动手。
66
- 反过来:**本轮已 Read 过、且你没用 Edit/Write 改动过的区域,不要重复 Read**——直接基于已读内容操作。如果你重复读取同一未变区域,结果头部会出现"已读过该区域"的提示,看到就停止重读、立刻用已有内容推进(重复读既费轮次又是死循环前兆)。
67
- - Edit / MultiEdit 拦截 fallback:如果你忘了先 Read 就直接调 Edit/MultiEdit,错误响应里会**自动附带当前文件前 200 行 / 32KB 以内的 snippet**(带行号)。这种情况下**直接基于 snippet 重新构造 old_string 再发一次 Edit 即可,不要再额外调 Read**。只有当 snippet 显示"未注入内容"(文件过大或二进制)时才需要显式 Read。Write 不附带 snippet——整文件覆盖语义下必须先显式 Read,避免基于片段重写导致截断范围外内容丢失。
68
- - Edit 工具的 old_string 必须在文件中唯一;不唯一时请扩大上下文或显式 replace_all=true。
69
- - 同一文件需要修改多处(3 处及以上)时优先用 MultiEdit:所有 edit 按顺序在内存中应用,**全部成功才落盘**;任一失败磁盘不动,避免中间状态污染。单点修改继续用 Edit。
70
- - Write 覆盖既有文件前同样必须先 Read(与 Edit 对称,未先 Read 会被拒绝并提示"请先 Read");写新文件无此要求。
71
- - 创建新文件用 Write,或 Edit 时 old_string 传空字符串。
72
- - 找文件用 Glob("**/*.ts"),找文件内容用 Grep(基于 ripgrep)。
73
- - 当用户问到训练截止后才出现的信息(最新版本号、近期新闻、第三方 API 文档)时用 WebSearch;优先精确的自然语言查询。
74
- - 当需要执行终端命令(安装依赖、运行测试/构建/lint、git 操作、文件系统操作等)时用 Bash。
75
- ⚠️ **Bash 是万能工具,但必须作为最后手段**:当 Read/Edit/Write/Glob/Grep 等专用工具无法完成任务时,才考虑用 Bash。
76
- 不要用 Bash 执行 find/grep/cat/head/tail/sed/awk/echo 等命令来替代专用工具——专用工具提供更好的用户体验和可审计性。
77
- Bash 有安全黑名单(rm -rf /、mkfs、shutdown 等危险命令会被拦截),但仍需谨慎:
78
- 先确认命令无害再执行,避免不可逆操作(如 git push --force、git reset --hard)。
79
- 长时间运行的命令(npm install、bun test)注意超时设置;需要交互输入的命令不要用 Bash(用 Write 写脚本代替)。
80
- Bash 已识别"信息性退出码":grep/rg/find/diff/test 的 exit=1(无匹配 / 部分不可访问 / 有差异 / 条件假)会自动判为成功,不要因 1 而重试。
81
- 破坏性命令(git reset --hard / git push -f / rm -rf 等)会被 Bash 主动在输出头部加 ⚠️ 警告(不拦截),看到时应向用户确认意图。
82
- - 当需要获取网页静态文本内容(抓取文档、读取文章)时用 WebFetch。
83
- WebBrowser 依赖可选包(playwright-core + chromium)—— **默认假定未安装**,
84
- 仅在 WebFetch 明确无法满足(如需要 JS 渲染后的内容、点击按钮、填表单、截图)时再尝试。
85
- 如果 WebBrowser 报"无法启动浏览器/缺少依赖",不要重试,改回 WebFetch 或告知用户安装依赖。
86
- - 一次回答里可以并行调用多个只读工具(Read/Glob/Grep/WebSearch/WebFetch),写工具(Edit/Write/Bash)请逐个执行。
87
- - 需要**用户拍板**时用 AskUserQuestion 提结构化选择题(最多 4 题、每题 2-4 选项;第一个选项放你的推荐并在 label 末尾标「(推荐)」):
88
- ① 需求/方案有取舍、没有绝对对错(多个合理方案各有利弊)→ 让用户选;② 关键信息缺失或模糊、继续做风险大 → 让用户补充。
89
- 能自己从代码/上下文/常识判断出答案的别问(免打断);界面会自动附「其他(自由输入)」逃生项,无需你手动加。
90
-
91
- # 技能系统(积极使用)
92
- ${skillHint}
93
-
94
- # 插件系统(被动触发)
95
- - minimal-agent 在 \`plugins/\` 目录下加载用户安装的插件,每个插件可暴露形如 \`/<cmd>\` 的命令。
96
- 与 skill 不同,**插件命令必须由用户主动输入才触发**——你**不要**主动调用或模拟这些命令,
97
- 也不要在工具调用里假装在跑插件。
98
- - 当某个插件命令在执行中(你会在当轮用户消息里看到该插件注入的上下文 / 阶段标记 / 哨兵约定),
99
- 请严格按照该插件给出的指引行动,不要绕开它的契约。
100
- - 在**常规对话**(无插件注入的阶段标记 / 哨兵约定)中,不要凭空使用这些标记或哨兵词。
101
-
102
- # 文件读取规范(重要!防止上下文污染)
103
- ## 读取策略(按场景选择)
104
- ### 场景 1:不确定文件内容
105
- → 先 Grep("关键字", path="文件路径") 定位
106
- → 再 Read(file_path, offset=X, limit=Y) 读指定区域
107
-
108
- ### 场景 2:知道要改哪个函数
109
- → 直接 Read(file_path, offset=1, limit=400) 先读前 400 行
110
- → 或者用 Grep 找到行号后 Read(file_path, offset=行号, limit=200)
111
-
112
- ### 场景 3:大文件(>256KB)
113
- → 不要一次性读,用 Grep 定位关键部分
114
- → 分多次小范围 Read,每次不超过 200 行
115
- → 例如:Read(file, offset=1, limit=100) → Read(file, offset=101, limit=100)
116
-
117
- ### 场景 4:需要看 imports/exports
118
- → Read(file, offset=1, limit=50) 只读文件头部
119
-
120
- ## 警惕信号
121
- - 遇到 "文件过大" 错误 → 说明你读了不该读的大文件,改用分段读
122
- - 遇到 "输出超过 30000 字符" → 说明单次读太多,改小 limit
123
-
124
- # 输出风格
125
- - 用中文回答用户。
126
- - 简明扼要,避免重复解释代码做了什么 —— 让代码自身说话。
127
- - 报错时给出根因,不要只复述错误消息。
128
- - 不主动跑测试或 typecheck,除非用户要求。
129
-
130
- # 行为约束
131
- - 没有用户许可不要执行破坏性操作(rm -rf / git reset --hard / git push --force 等)。
132
- - 如果用户的请求不清晰、或有多个合理方向需要取舍:优先用 AskUserQuestion 让用户拍板(带推荐项),而不是替他猜着干;只缺一点小信息时一句话澄清即可。
133
- - 任务完成后用一两句话总结改了什么,不要长篇大论。
134
-
135
- # 修改 vs 讨论(动手边界,重要)
136
- 先判断用户要的是"动手改"还是"聊一聊",再决定要不要写盘——默认克制、被要求才行动:
137
- - **默认是讨论/分析/给方案**:除非判定用户要你改,否则只回答、只给建议,**不调用 Edit/Write/MultiEdit、不跑会改状态或破坏性的 Bash**。未经允许的修改和"不读就改"一样是越界。
138
- - **只读工具不受此限**:Read/Grep/Glob/WebSearch/WebFetch 任何时候都能直接用——读代码、查资料是"理解"不是"动手",讨论也要先把事实读准。
139
- - **命中修改意图就果断改,别反问试探**:祈使式动词(改/加/删/修/修复/重构/实现/优化/重命名/迁移/接上/补全…)、明确的"直接改/帮我写/动手/落地"、或一个可执行的 bug 修复请求——都是放行信号,直接干。别再用"要不要我帮你改"来回打转,那是讨好型的犹豫。
140
- - **纯讨论意图就别擅自改**:以"为什么/是不是/能不能/看看/评估下/解释下/你怎么看/可行吗/有没有问题"为主、没有修改祈使的,给分析和建议即可,不要顺手把代码改了。
141
- - **只有"讨论还是修改"真拿不准时**,才用一句话澄清("你是想我直接改,还是先讨论方案?")再停;能从措辞判断的就别问。
142
-
143
- # 中断支持
144
- - 用户可以随时按 ESC 或 Ctrl+C 中断你的工作。
145
- - 中断后等待用户输入新任务,不要自行继续执行被中断的操作。
146
- - 如果用户输入了新任务,直接响应新任务,不必提及之前被中断的操作。
147
-
148
- # 任务边界与上下文关联(每轮开工前自检)
149
- 用户不会一问一会话,历史里常混着已结束的旧任务。每轮开工前先答三个问题,再决定怎么用上下文:
150
- 1. **这是新任务还是上一任务的延续?**——"继续/接着做/再加一个/还有"=延续,保持当前任务上下文;"帮我做 X/这次做 Y/另外"=多半是新任务;"重新开始/不算了,做 X"=立即放下旧任务。
151
- 2. **历史里哪些和当前问题相关?**——只把与当前问题直接相关的消息、文件、工具结果当活动上下文;无关的旧任务细节当背景,**不要拿旧任务的结论/文件/方案硬套当前问题**(典型翻车:上个任务在改 A,这次问的是 B,却惯性去翻 A)。
152
- 3. **判不准边界就先确认**:一句话澄清"你是继续刚才的 X,还是开始新的?",别默默假设。
153
- - 当前用户消息 + 紧邻的最近几条消息,权重永远高于更早历史;越早、越不相关的内容参考价值越低。`;
19
+ return assembleBasePrompt({ cwd, tools });
154
20
  }
155
21
  /**
156
- * 生成完整系统提示词:getSystemPrompt + 项目级 minimal-agent.md 指令。
157
- *
158
- * 合并到**单条** system message,避免某些 provider(如 MiniMax)多 system
159
- * message 时返回 400 "invalid chat setting"。启动时(main.tsx buildInitialHistory)
160
- * 和 /new(useChat.clearHistory)都用这个 helper,确保两处行为一致。
22
+ * @deprecated 新代码请直接用 assembleBasePrompt({ includeProjectInstructions: true })
161
23
  */
162
24
  export async function buildFullSystemPrompt(cwd, tools) {
163
- let content = await getSystemPrompt(cwd, tools);
164
- const projectInstructions = await loadProjectInstructions(cwd);
165
- if (projectInstructions) {
166
- content += [
167
- '',
168
- '--- 项目指令(minimal-agent.md) ---',
169
- '',
170
- '⚠️ 时效性声明:',
171
- '- 以下内容来源于项目根目录的 `minimal-agent.md` 文件,具有时效性,可能已过时。',
172
- '- 它是参考线索,不是绝对真理。当它与实际代码或文件内容冲突时,以代码为准。',
173
- '- 涉及代码和项目文件的任何操作前,必须先用 Read / Grep 工具确认当前代码的实际状态,再基于最新信息做决策,不要仅凭此文档的描述操作。',
174
- '- 同步规则:仅当你在**本轮任务中实际用 Write/Edit/MultiEdit 改动了代码或项目结构**、且这些改动使 `minimal-agent.md` 某处描述过时时,才在本轮结束的总结里用一句话**指出过时的具体位置**(哪一节 / 哪条),并询问用户是否要同步、或建议运行 `/init` 重建。',
175
- '- 除非用户已明确授权,**不要擅自 Write 改写 `minimal-agent.md`**(它通常已纳入 git,盲目重写会污染 diff;任务进行中代码尚未定型,过早同步会把暂态写进文档、反而加重误导)。纯讨论 / 只读 / 搜索类的轮次不做任何同步动作。',
176
- '',
177
- projectInstructions,
178
- ].join('\n');
179
- }
180
- return content;
25
+ return assembleBasePrompt({ cwd, tools, includeProjectInstructions: true });
181
26
  }
package/src/ui/App.js CHANGED
@@ -190,7 +190,7 @@ export function App({ provider, initialHistory }) {
190
190
  // 它必须是兄弟节点中第一个,否则 above-sibling 会跟 Static commit 一起被重打
191
191
  // 到 stdout(实测看到 banner 反复打印 N 次)。
192
192
  const bannerText = `minimal-agent · ${provider.name}/${provider.model} · /new 清空 · /compact 压缩 · /exit 退出 · Ctrl+C 中断`;
193
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(MessageList, { history: deferredHistory, compactGeneration: chat.compactGeneration, bannerText: bannerText, terminalRows: terminalRows }), _jsxs(Box, { flexDirection: "column", width: Math.max(1, terminalCols - 1), children: [_jsx(LiveArea, { progressStage: chat.progressStage, reasoningPreview: chat.reasoningPreview, streamingText: chat.streamingText, runningTools: chat.runningTools, pluginProgress: chat.pluginProgress, terminalCols: terminalCols, terminalRows: terminalRows }), chat.error && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "red", children: ["\u26A0 ", chat.error] }) })), chat.interrupted && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "yellow", children: "\u26A1 \u64CD\u4F5C\u88AB\u7528\u6237\u4E2D\u65AD\uFF0C\u7B49\u5F85\u65B0\u7684\u4EFB\u52A1\u8F93\u5165..." }) })), chat.pendingAsk ? (_jsx(AskQuestion, { questions: chat.pendingAsk, onAnswer: chat.answerAsk, onCancel: chat.abort })) : (
193
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(MessageList, { history: deferredHistory, compactGeneration: chat.compactGeneration, bannerText: bannerText, terminalRows: terminalRows }), _jsxs(Box, { flexDirection: "column", width: Math.max(1, terminalCols - 1), children: [_jsx(LiveArea, { progressStage: chat.progressStage, reasoningPreview: chat.reasoningPreview, streamingText: chat.streamingText, runningTools: chat.runningTools, pluginProgress: chat.pluginProgress, terminalCols: terminalCols, terminalRows: terminalRows }), chat.error && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "red", children: ["\u26A0 ", chat.error] }) })), chat.interrupted && (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: "yellow", children: "\u26A1 \u64CD\u4F5C\u88AB\u7528\u6237\u4E2D\u65AD\uFF0C\u7B49\u5F85\u65B0\u7684\u4EFB\u52A1\u8F93\u5165..." }), _jsx(Text, { dimColor: true, children: " \u8C03\u6574\u4E0A\u4E00\u6B21\u8BA1\u5212\uFF1A\u76F4\u63A5\u8BF4\u8C03\u6574\u5185\u5BB9 / \u91CD\u65B0\u8DD1 /plan \u76EE\u6807 / \u65B0\u4EFB\u52A1\u76F4\u63A5\u8BF4" })] })), chat.pendingAsk ? (_jsx(AskQuestion, { questions: chat.pendingAsk, onAnswer: chat.answerAsk, onCancel: chat.abort })) : (
194
194
  /* handleSubmit 包装做 slash 命令拦截,非命令透传 chat.submit */
195
195
  _jsx(InputBox, { onSubmit: handleSubmit, disabled: chat.isLoading, onAbort: chat.abort, onClear: handleClear, onCompact: chat.compactNow })), _jsx(StatusLine, { provider: provider, history: chat.history, pluginProgress: chat.pluginProgress })] })] }));
196
196
  }
@@ -1,29 +1,22 @@
1
1
  # ============================================================
2
- # workflows/plan.yaml —— 复刻 Claude Code「plan」流程(Clarify→Analyze→Verify→Synthesize→Critic→Gate)
2
+ # workflows/plan.yaml —— 零交互计划流程(Analyze→Verify→Synthesize→Critic→Gate→Execute→Summary)
3
3
  # ------------------------------------------------------------
4
- # 设计目标:① 跨基座模型稳(弱模型也能跑完,不依赖强 agent 多轮工具循环);
5
- # ② 提问只在「开头一次性」发生,之后全自主无打断,直到产出可提交的 PLAN.md。
4
+ # 设计目标:① 跨模型稳(弱模型也能跑完);② 零用户交互——全程自主;
5
+ # ③ 计划详细程度从 goal 自动推断,不提问。
6
6
  # 全用现有 StepKind(不改引擎):
7
- # Clarify : clarify_depth(AskUserQuestion 开头一次性问深度——全流程唯一的人机交互点)
8
7
  # Analyze : analyze(agent 只读探索) → extract_findings(llm+schema 固化成可验证 claims)
9
8
  # Verify : verify_findings(type:vote 红队对每条 finding 反向提问证伪,存活的才进下游)
10
9
  # Synthesize: draft_plan(llm 纯文本出计划正文) → read_existing_plan + write_plan(tool 确定性落盘)
11
10
  # Critic : critic_plan(skill:adversarial-review 反向提问审稿,追加进 PLAN.md)
12
11
  # Gate : verify(assert 守门确认 PLAN.md 生成)
12
+ # Execute : execute_plan(agent 按计划执行,产出最终交付物)
13
+ # Summary : exit_summary(llm 退出摘要)
13
14
  # ------------------------------------------------------------
14
- # 模型兼容性要点(为什么这样切分——上一版在火山 GLM-5.1 上两处翻车的根治):
15
- # 1. 写盘用 "llm 出正文 + tool:Write 落盘" 而非 agent。原 agent+Write 写法在弱模型上
16
- # 会反复调 Write 触发 miniReAct 重复检测 halt;真因还叠加 Write 的"覆盖前须先 Read"
17
- # 守卫——PLAN.md 已存在时首个 Write 被拒、模型重试同参 Write→3 次→halt。
18
- # 故 write 前置一个 tool:Read(onError:continue,首跑文件不存在则略过)满足守卫。
19
- # 2. 结构化输出(extract_findings / vote)由 client.ts 自动退化:provider 不支持
20
- # response_format.json_schema(火山 GLM)时去掉它、靠 prompt 内嵌 schema + 容错解析。
21
- # 3. 仍是 agent 的只剩 analyze(只读探索)与 critic(审稿,onError:continue 兜底),
22
- # 它们在弱模型上最坏只是 halt/跳过,不破坏已产出的计划。
23
- # ------------------------------------------------------------
24
- # 提问节奏:唯一提问点 clarify_depth 放最前——问完之后全程自主无打断。任务专属、
25
- # 本工作流没问的点(受众/时长/范围等)由 draft 自做合理默认假设并在正文开头列出,
26
- # 绝不中途打断(契合"明确有优劣自动判断、只有真取舍才问、开头问完即自主")。
15
+ # 提问策略:本工作流**零交互**。计划深度从用户 goal 自动推断:
16
+ # - 目标含"概览/快速/简单/简短" → 快速概览
17
+ # - 目标含"详细/完整/分步/彻底/带风险" → 带风险回滚评估
18
+ # - 其他 → 详细分步(默认推荐)
19
+ # 不询问用户选择深度——自行判断。
27
20
  # ------------------------------------------------------------
28
21
  # 注:critic 用 skill: 节点 dogfood 仓库自带的 adversarial-review skill。
29
22
  # skill 双源查找(cwd 优先、packageRoot fallback);异地 cwd 缺该 skill 时
@@ -31,10 +24,10 @@
31
24
  # ============================================================
32
25
  name: plan
33
26
  description: |
34
- 受工作流指导 + 红队对抗验证 + 反向提问审稿的动态计划流程(跨模型稳、开头一次性提问后全自主)。
35
- Clarify(开头问深度) → Analyze(探索+固化) → Verify(vote 红队证伪) → Synthesize(出正文+落盘)
36
- → Critic(反向提问审稿追加) → Gate(assert 守门)。minimal-agent 的旗舰演示。
37
- version: "0.3"
27
+ 零交互计划流程(跨模型稳、全程自主、自动推断深度)。
28
+ Analyze(探索+固化) → Verify(vote 红队证伪) → Synthesize(出正文+落盘)
29
+ → Critic(反向提问审稿追加) → Gate(assert 守门) → Execute(按计划执行) → Summary(退出摘要)。
30
+ version: "0.4"
38
31
 
39
32
  inputs:
40
33
  - name: goal
@@ -43,26 +36,6 @@ inputs:
43
36
  description: "要做的事 / 目标"
44
37
 
45
38
  steps:
46
- # ===================== Clarify(开头唯一提问点)=====================
47
- # [Clarify] 一次性问"计划深度"(这是真有取舍、无统一最优的点;第一项=推荐,非交互自动选它)。
48
- # 放最前:问完之后全程自主。其它任务专属细节(受众/时长/范围)由 draft 自做合理默认并列出,
49
- # 不再中途打断——契合"明确有优劣自动判断、只有真取舍才问"。
50
- - id: clarify_depth
51
- tool: AskUserQuestion
52
- args:
53
- questions:
54
- - header: "计划深度"
55
- question: "希望这份实现计划做到多详细?"
56
- options:
57
- - label: "详细分步 (推荐)"
58
- description: "拆成可执行的有序步骤,每步说清动作、涉及文件与验收点"
59
- - label: "快速概览"
60
- description: "只给高层方向与关键决策,不展开到每一步"
61
- - label: "带风险/回滚评估"
62
- description: "在详细分步之外,额外标注每步风险与回滚方案"
63
- capture:
64
- content: depth_answer
65
-
66
39
  # ===================== Analyze =====================
67
40
  # [Analyze-1] 只读探索:agent 自主用 Read/Grep/Glob 摸清现状(不写盘、不副作用)
68
41
  - id: analyze
@@ -128,17 +101,18 @@ steps:
128
101
  —— 仍未决的问题 ——
129
102
  ${open_questions}
130
103
 
131
- —— 用户选择的计划深度 ——
132
- ${depth_answer}
104
+ ## 计划详细程度(自动推断,不询问用户)
105
+ 根据用户目标「${inputs.goal}」中的关键词自动判断:
106
+ - 如果目标包含"概览"、"快速"、"简单"、"简短"、"简要"、"大致"等词 → 输出快速概览(高层方向与关键决策,不展开到每一步)
107
+ - 如果目标包含"详细"、"完整"、"分步"、"彻底"、"风险"、"回滚"、"全面"等词 → 输出带风险/回滚评估的详细计划
108
+ - 其他情况 → 默认输出**详细分步**计划(可执行的有序步骤,每步说清动作、涉及文件与验收点)
133
109
 
134
110
  要求:
135
- - 详细程度遵循用户选择的深度;
111
+ - 按上述推断的详细程度输出;
136
112
  - "存活结论"为空时不要硬编:按目标常识给出合理的计划骨架;
137
113
  - 凡需按需求拍板、但本工作流未询问的点(如受众/时长/范围),**自行做合理默认假设并继续**,
138
114
  在正文开头用「## 假设(如需调整请告知)」一节列出假设,**绝不中断提问**。
139
115
  capture:
140
- # llm 步(无 output_schema)的 raw 只有 {text,result},没有 content(那是 tool 步的字段)。
141
- # 用错字段会 capture 到 undefined → 下游 Write 写 0 字节(已被端到端测试抓到)。
142
116
  text: plan_text
143
117
 
144
118
  # [Synthesize-2] 满足 Write 的"覆盖前须先 Read"守卫:PLAN.md 已存在则先 Read;
@@ -149,7 +123,7 @@ steps:
149
123
  file_path: ./PLAN.md
150
124
  onError: continue
151
125
 
152
- # [Synthesize-3] 落盘:tool:Write 确定性写入(不经 agent 循环,弱模型也稳)
126
+ # [Synthesis-3] 落盘:tool:Write 确定性写入(不经 agent 循环,弱模型也稳)
153
127
  - id: write_plan
154
128
  tool: Write
155
129
  args:
@@ -174,3 +148,41 @@ steps:
174
148
  type: assert
175
149
  condition: 'fileExists("./PLAN.md")'
176
150
  onFail: "计划文件 ./PLAN.md 未生成,检查 draft_plan/write_plan 步是否成功"
151
+
152
+ # ===================== Execute(按计划执行)=====================
153
+ # [Execute] 读取已落盘的 PLAN.md,agent 自主按计划步骤逐一执行,
154
+ # 直到完成所有任务并产出最终交付物。
155
+ # onError: recover —— 执行中撞上预料之外的错误就就地降级为 agent 自适应恢复。
156
+ - id: execute_plan
157
+ agent: |
158
+ 下面是一份已经过完整工作流验证的实现计划(./PLAN.md)。
159
+ 请先 Read ./PLAN.md 了解完整计划内容和步骤。
160
+ 然后严格按照计划的步骤逐一执行,直到完成所有任务并产出最终交付物。
161
+ 执行过程中:
162
+ 1. 如果计划中提到使用某个 skill(如 pptx/pdf/docx),先用 Read 读取对应 skills/ 目录下的 SKILL.md 了解用法
163
+ 2. 每完成一个主要步骤后简要汇报进度
164
+ 3. 如果遇到计划中未预见的情况,自行判断合理方案并继续(不要中断等待)
165
+ 4. 全部完成后用一两句话总结产出物位置和内容
166
+ allowed_tools: [Read, Write, Edit, Bash, Grep, Glob, WebSearch, WebFetch]
167
+ maxTurns: 40
168
+ onError: recover
169
+ capture:
170
+ content: execution_result
171
+
172
+ # ===================== Exit Summary(退出摘要)=====================
173
+ # [Exit Summary] 用 llm 产出一句话摘要,让用户知道"做了什么、产物在哪、如何继续"。
174
+ # plan 工作流到此彻底结束,之后回到正常 TUI 对话模式。
175
+ - id: exit_summary
176
+ llm: |
177
+ 用户的目标是「${inputs.goal}」。
178
+ 计划已制定(PLAN.md)并执行完毕。
179
+ 执行结果:${execution_result}
180
+
181
+ 请用简洁中文写一段最终摘要(3-5句话),包含:
182
+ 1. 完成了什么
183
+ 2. 最终产物路径(文件名和目录)
184
+ 3. 如有后续建议也简短提及
185
+
186
+ 直接输出摘要正文,不要寒暄。
187
+ capture:
188
+ text: summary_text