chore: 版本号 0.14.3 → 0.14.4

refactor: Plan Mode 进度仅变化时注入(避免每轮冗余)
refactor: Plan Mode 执行规则精简 20行→3行
refactor: 断点续传提示词合并
refactor: PlanAutoTrack Hook 去除模糊匹配,仅 AI 手动 plan_track 精确标记
refactor: buildHistoryMessages 取 user+assistant,不取 system
refactor: 附件传递改为 JSON 结构化(文件/视频 Base64,图片仅 images[])
refactor: 用户文字始终排在附件 JSON 之后
fix: onDone 双重渲染导致 AI 消息卡片重复(正常路径+重试路径)
This commit is contained in:
thzxx
2026-06-28 20:42:50 +08:00
parent 3ddd493847
commit adff2f36eb
8 changed files with 98 additions and 170 deletions
+15 -38
View File
@@ -773,11 +773,8 @@ async function handleInit(
savePlanTracker(tracker);
// 清空恢复数据,避免重复恢复
state.set('_planResumeData', null);
// 注入恢复状态提示
systemPromptParts.push(`[Plan Mode 断点续传] 上一轮计划未完成,已自动恢复。当前进度: ${tracker.done}/${tracker.total}。已完成步骤将继续保持完成状态。继续执行剩余步骤,跳过已完成的步骤。`);
logInfo(`Plan Mode 断点续传: ${tracker.done}/${tracker.total} 步骤已恢复,继续执行剩余 ${tracker.total - tracker.done}`);
// 跳过"输出计划"约束,直接告诉 AI 继续执行
systemPromptParts.push(`⚠️ 计划已在上一轮生成并批准,现在直接继续执行剩余步骤。不需要重新输出计划。`);
systemPromptParts.push(`[Plan Mode 断点续传] 上一轮计划未完成已自动恢复,当前进度 ${tracker.done}/${tracker.total}。直接继续执行剩余 ${tracker.total - tracker.done} 步,跳过已完成步骤,不要重新输出计划。`);
logInfo(`Plan Mode 断点续传: ${tracker.done}/${tracker.total}`);
} else {
// 清除可能的残留
state.set('_planResumeData', null);
@@ -786,36 +783,12 @@ async function handleInit(
// ── Plan Mode 系统提示词 — 告知 AI plan_track 工具和追踪规则 ──
if (ctx.mode === 'plan') {
systemPromptParts.push(`[Plan Mode 执行规则]
当前处于 Plan Mode先规划后执行)。重要规则:
systemPromptParts.push(`[Plan Mode 执行规则]
当前处于先规划后执行模式。核心规则:
**输出格式(必须严格遵守)**
你的第一轮回复必须是一个清晰的执行计划,使用以下格式。不要输出其他无关内容
## 任务分析
[1-2 句话概述任务,展示你的理解]
## 执行计划
1. **步骤名称** — 工具: tool_name — 简要描述这一步做什么,完成后预期结果是什么
2. **步骤名称** — 无需工具 — 如果这一步只需推理/分析/总结,标注"无需工具"并描述分析要点
...(根据任务复杂度,通常 2-6 步)
## 预期结果
[完成所有步骤后的最终产出]
**关键规则**
- 需要调用工具的步骤:必须写"工具: xxx"(如 工具: web_search、工具: write_file
- 纯分析/推理/总结的步骤:写"无需工具",描述你要分析和思考的要点
- 禁止模糊描述如"分析需求"——即使是分析步骤,也要写清楚分析什么、关注什么
- 如果用户已上传图片或文件,第一步必须是分析附件(写"无需工具"即可,图片你直接能看到)
- 如果整个任务完全不需要工具(如纯翻译、润色、总结对话),可以全部步骤都是"无需工具"
- 输出计划后等待用户批准,**不要**在输出计划的同时调用工具
**计划批准后**
- 需要工具的步骤:调用对应工具完成任务
- 无需工具的步骤:直接基于已有信息给出分析结论
- 系统会自动追踪工具执行进度
- 所有步骤完成后直接给出最终回答。`);
1. 首轮必须先输出执行计划(Markdown 格式,每步标注是否需要工具),等待用户批准,**不要**同时调用工具。
2. 批准后按计划逐步执行,每完成一步可调用 plan_track mark_done 标记进度
3. 全部完成后输出最终回答。`);
// 将用户原始任务描述固化到系统提示词中(不会被压缩或清理)
const taskDesc = userContent?.slice(0, 200) || '';
@@ -1145,13 +1118,17 @@ async function handleThinking(
ctx.thinking = '';
ctx.toolCalls.length = 0;
// ── Plan Mode 执行进度注入 — 每次思考前展示当前状态 ──
// ── Plan Mode 执行进度注入 — 仅在进度变化时注入,避免每轮重复 ──
if (ctx.mode === 'plan' && ctx.loopCount >= 1) {
const tracker = getPlanTracker();
if (tracker.active && tracker.steps.length > 0) {
const status = formatPlanStatus();
if (status) {
ctx.messages.push({ role: 'user', content: status, ephemeral: true });
const lastDone = state.get<number>('_planLastInjectedDone', -1);
if (tracker.done !== lastDone) {
const status = formatPlanStatus();
if (status) {
ctx.messages.push({ role: 'user', content: status, ephemeral: true });
state.set('_planLastInjectedDone', tracker.done);
}
}
}
}
+4 -58
View File
@@ -247,9 +247,9 @@ export const fileWriteDedupHook: HarnessHook = {
};
/**
* PlanAutoTrackHook — Plan Mode 步骤自动追踪(post_tool 非阻断级)
* 工具执行成功后自动匹配 Plan 步骤标记完成,不再依赖模型手动调用 plan_track。
* write_file 成功后还会自动标记所有"设计/构建/编码"类步骤
* PlanAutoTrackHook — Plan Mode 步骤追踪(post_tool 非阻断级)
* 不自动标记步骤(避免模糊匹配误标),仅由 AI 通过 plan_track 工具精确标记
* 此处只维护 write_file 路径记录供 FileWriteDedupHook 使用
*/
export const planAutoTrackHook: HarnessHook = {
name: 'PlanAutoTrack',
@@ -260,63 +260,9 @@ export const planAutoTrackHook: HarnessHook = {
if (ctx.mode !== 'plan') return { passed: true, message: '' };
const toolName = data.toolName || '';
const toolResult = data.toolResult as Record<string, unknown> | undefined;
if (!toolResult?.success) return { passed: true, message: '' };
// 延迟导入避免循环依赖
const { getPlanTracker, savePlanTracker } = await import('./tool-registry.js');
const tracker = getPlanTracker();
if (!tracker.active || tracker.steps.length === 0) return { passed: true, message: '' };
// 工具名 → 计划步骤关键词映射
const TOOL_STEP_KEYWORDS: Record<string, string[]> = {
web_search: ['搜索', '查找', '检索', 'search', '查询'],
web_fetch: ['抓取', 'fetch', '获取', '浏览', '提取内容'],
read_file: ['读取', 'read', '查看', '检查'],
write_file: ['写入', 'write', '生成.*文件', '创建.*文件', '文件.*生成', '保存.*文件', '输出.*文件'],
edit_file: ['编辑', '修改', 'edit'],
list_directory: ['列出', 'list', '浏览.*目录', '查看.*结构'],
tree: ['目录树', 'tree', '结构'],
run_command: ['运行', '执行', 'run', '命令', '编译', '测试'],
git: ['git', '提交', '推送', '暂存', 'commit'],
browser_open: ['打开.*网页', '浏览器', 'browser'],
};
const keywords = TOOL_STEP_KEYWORDS[toolName] || [];
let matched = false;
if (keywords.length > 0) {
for (const step of tracker.steps) {
if (step.done) continue;
for (const kw of keywords) {
if (new RegExp(kw, 'i').test(step.label)) {
step.done = true;
tracker.done = tracker.steps.filter(s => s.done).length;
savePlanTracker(tracker);
logInfo(`Plan 自动追踪: ${toolName} → 步骤${step.index} "${step.label.slice(0, 40)}" (${tracker.done}/${tracker.total})`);
matched = true;
break;
}
}
if (matched) break;
}
}
// write_file 成功后,自动标记所有"设计/构建/编码"类步骤
if (toolName === 'write_file') {
const DESIGN_KEYWORDS = /HTML|CSS|构建|编码|设计|代码|前端|布局|排版|样式|视觉|coding|design|style/i;
for (const step of tracker.steps) {
if (step.done) continue;
if (DESIGN_KEYWORDS.test(step.label)) {
step.done = true;
tracker.done = tracker.steps.filter(s => s.done).length;
savePlanTracker(tracker);
logInfo(`Plan 自动追踪: write_file → 设计步骤${step.index} "${step.label.slice(0, 40)}" (${tracker.done}/${tracker.total})`);
}
}
}
// write_file 成功后记录文件路径,供 FileWriteDedupHook 拦截
if (toolName === 'write_file' && data.toolArgs?.path) {
if (toolName === 'write_file' && toolResult?.success && data.toolArgs?.path) {
_writtenFilesThisSession.add(String(data.toolArgs.path));
}