feat(agent): ② exec 自动放行(autoApproveExec)+ 抽出共享合并流程
- 新增 exec-merge.acceptAndMerge:封装 exec_review 通过并合并的完整流程 (merge→冲突建补救任务/成功 decide+收口+回收),API decide 与 ingest 复用同一逻辑 - server.ts decide 改调 acceptAndMerge(去重 ~40 行内联逻辑) - ingest result:auto-approved + autoApproveExec + 双复审 approve → fire-and-forget 自动合并(git 异步不阻塞 tick);冲突则补救任务 + 留 exec_review 人审 - 测试:acceptAndMerge 可合并→done、冲突→补救+留审核闸(真实 git repo) 至此 plan ① ② ③ 三类优化 + 通用规则 + L1-L4 记忆注入代码侧全部落地 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+9
-44
@@ -8,9 +8,8 @@ import type { Project, Autonomy } from '../model/types.js';
|
||||
import { syncProject, hasTodoJson } from '../sync/todo-sync.js';
|
||||
import { resolvedExecutorModels } from '../executor/models.js';
|
||||
import { classifyComplexity, type ClassifierFn } from '../executor/classify.js';
|
||||
import { mergeBranch } from '../executor/merge.js';
|
||||
import { git, removeWorktree, createWorktree } from '../executor/worktree.js';
|
||||
import { cleanupTaskRunArtifacts } from '../executor/cleanup.js';
|
||||
import { acceptAndMerge } from '../executor/exec-merge.js';
|
||||
import { createWorktree } from '../executor/worktree.js';
|
||||
import { resolveLogo, LOGO_MIME } from './logo.js';
|
||||
import { readTranscript, TranscriptError } from '../executor/transcript.js';
|
||||
import { createReadStream, createWriteStream, mkdirSync, readFileSync, statSync } from 'node:fs';
|
||||
@@ -428,48 +427,14 @@ export function buildServer(opts: ApiOptions): FastifyInstance {
|
||||
const task = store.getTask(id);
|
||||
// merge:false = 仅通过不合并(逃生口:如目标分支正被用户工作区检出导致自动合并不可用)
|
||||
if (task && task.status === 'exec_review' && b.action === 'accept' && task.result?.branch && b.merge !== false) {
|
||||
const project = store.getProject(task.projectId);
|
||||
if (!project) throw new StoreError(`项目不存在: ${task.projectId}`);
|
||||
const { branch, worktree } = task.result;
|
||||
const mr = await mergeBranch(project.repoPath, branch, project.defaultBranch, task.id);
|
||||
if (!mr.ok) {
|
||||
// 自动合并失败(多为冲突)→ 建/复用最高优先级补救任务来完成合并,原任务留在审核闸
|
||||
const rem = store.ensureMergeRemediationTask(task.id, {
|
||||
branch, targetBranch: project.defaultBranch,
|
||||
conflictError: mr.error ?? '未知冲突', conflictFiles: mr.conflictFiles,
|
||||
});
|
||||
throw new StoreError(`合并失败:${mr.error}(原任务保留在审核闸;已建最高优先级补救任务 ${rem.id}「${rem.title}」来完成合并)`);
|
||||
const outcome = await acceptAndMerge(store, app.log, task, b.actor ?? 'user');
|
||||
if (!outcome.ok) {
|
||||
if (outcome.remediationTaskId) {
|
||||
const rem = store.getTask(outcome.remediationTaskId);
|
||||
throw new StoreError(`合并失败:${outcome.error}(原任务保留在审核闸;已建最高优先级补救任务 ${rem?.id}「${rem?.title}」来完成合并)`);
|
||||
}
|
||||
throw new StoreError(`合并失败:${outcome.error}`);
|
||||
}
|
||||
|
||||
store.decide(id, 'accept', b.actor ?? 'user', b.reason ?? null);
|
||||
// 合并产物记录:复用 prUrl 字段写 merged:<mergeCommit>
|
||||
store.setResult(id, { ...task.result, prUrl: `merged:${mr.mergeCommit}` });
|
||||
// 即时回收 runs/<runId>/(job/outbox/heartbeat 已无价值;转录保留供排查)。
|
||||
// 任务此刻已 done(终态),cleanupTaskRunArtifacts 内部据此放行;同步删(纯本地 fs,快)。
|
||||
const reclaimed = cleanupTaskRunArtifacts(store, id, app.log);
|
||||
if (reclaimed) app.log.info(`任务 ${id} 合并后回收 ${reclaimed} 个 run 工作目录`);
|
||||
// 补救完成自动收口:若本任务是某原任务的「合并冲突补救」任务,合并成功后把原任务也标 done
|
||||
const origin = store.findMergeOriginTask(id);
|
||||
if (origin) {
|
||||
try {
|
||||
store.remediateOrigin(origin.id, id, mr.mergeCommit);
|
||||
} catch (e) {
|
||||
app.log.error(`补救任务 ${id} 合并后收口原任务 ${origin.id} 失败(不影响本任务结果):${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
// 异步回收:执行 worktree + 已合并的任务分支(失败只记日志,不影响响应)
|
||||
void (async () => {
|
||||
try {
|
||||
if (worktree) await removeWorktree(project.repoPath, worktree);
|
||||
} catch (e) {
|
||||
app.log.error(`任务 ${id} 合并后清理 worktree 失败(不影响结果):${(e as Error).message}`);
|
||||
}
|
||||
try {
|
||||
await git(project.repoPath, ['branch', '-d', branch]);
|
||||
} catch (e) {
|
||||
app.log.error(`任务 ${id} 合并后删除分支 ${branch} 失败(不影响结果):${(e as Error).message}`);
|
||||
}
|
||||
})();
|
||||
return store.getTask(id);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user