Files
maestro/src/executor/pipeline.ts
T

121 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Phase 3 多进程执行:worker 侧【纯执行管线】。
//
// 这是旧 `orchestrator.executeTask` 函数体「劈出来」的执行半边——只负责跑
// worktree→执行→verify→双复审→产出,并把每一步的进度/结果经 `emit` 写进 outbox 文件。
// 它【完全不碰 DB】(连读都不碰),也【不做任何重试 / needs_attention 决策】:
// - 失败(executor 报错 / verify 不过)→ emit failed,然后 emit done,结束(不抛错)。
// - 成功 → emit result(四要素),然后 emit done。
// 重试次数、退避、转终态等判断全部归 daemon(它读 outbox 后决策)。
//
// daemon 侧的 store.* 调用与本模块 emit 的对应关系(仅为说明,本模块不引用 store):
// store.transition(executing) / startRun(executor) → emit started(由 worker 在入口发,不在此)
// runner 失败 / verify 失败 finishRun(failed) → emit failed
// 双复审 + setResult(四字段) + transition(exec_review) → emit result
// worker 退出) → emit done
import type { JobSpec, OutboxPayload, ReviewReport } from './protocol.js';
import { createWorktree, worktreeDiff, type WorktreeDiff, type WorktreeInfo } from './worktree.js';
import { runTask, type RunnerFn } from './runner.js';
import { runVerify, type VerifyFn } from './verify.js';
import { reviewCode, reviewSecurity, type ReviewerFn } from './reviewer.js';
/** pipeline 的依赖注入面(便于单测 mock);默认值取真实现。 */
export interface PipelineDeps {
createWorktree: (repoPath: string, taskId: string, baseBranch: string) => Promise<WorktreeInfo>;
worktreeDiff: (repoPath: string, dir: string, branch: string, baseBranch: string) => Promise<WorktreeDiff>;
runTask: RunnerFn;
verify: VerifyFn;
reviewCode: ReviewerFn; // code reviewdaemon 落成 kind=reviewer 的 run
reviewSecurity: ReviewerFn; // 安全审计(daemon 落成 kind=security 的 run
}
/** 真实依赖(worker 生产用)。 */
export const realDeps: PipelineDeps = {
createWorktree,
worktreeDiff,
runTask,
verify: runVerify,
reviewCode,
reviewSecurity,
};
/** emit:把一条 OutboxPayload 交给 outboxworker 传 appendOutbox 包装,测试传收集器)。 */
export type Emit = (payload: OutboxPayload) => void;
/**
* 跑一个独立复审(code / security),复刻旧 `runOneReview` 的兜底:
* 任何失败折叠成 `{summary:'自动复审失败:'+msg, verdict:null, transcriptRef:null}`,不抛错、不挡任务。
* 成功时把 ReviewResult 映射成 ReviewReport(丢弃 sessionId——result 记录里复审只留 transcriptRef)。
*/
async function runOneReview(
fn: ReviewerFn,
job: JobSpec,
wt: WorktreeInfo,
reviewRunId: string,
executorReport: string,
): Promise<ReviewReport> {
try {
const rv = await fn(job.task, job.project, wt, reviewRunId, executorReport);
return { summary: rv.summary, verdict: rv.verdict, transcriptRef: rv.transcriptRef };
} catch (e) {
return { summary: `自动复审失败:${(e as Error).message}`, verdict: null, transcriptRef: null };
}
}
/**
* 纯执行管线。复刻旧 in-process 流程的【执行与产出】,但所有 store.* 改成 emit(outbox)
* 1. createWorktree
* 2. runTask!ok → emit failed{error,transcriptRef,sessionId} + done,返回(不抛)
* 3. runVerify!ok → emit failed{error,rr.transcriptRef,rr.sessionId} + done,返回
* 4. worktreeDiff
* 5. 双复审(顺序,任一失败不挡,见 runOneReview 兜底)
* 6. emit resultbranch/worktree/diffSummary/commits/executor/code/security+ done
*
* 不抛错(除非 emit 自身抛——那由 worker 顶层兜底)。全程总会 emit 一条终态(failed|result)后再 emit done。
*/
export async function runPipeline(job: JobSpec, deps: PipelineDeps = realDeps, emit: Emit): Promise<void> {
// 1. 建 worktree(其 dir/branch 应与 job.worktreeDir/branch 一致;以 deps 真建的为准)
const wt = await deps.createWorktree(job.project.repoPath, job.task.id, job.project.defaultBranch);
// 2. 执行
emit({ type: 'phase', phase: 'executing' });
const rr = await deps.runTask(job.task, job.project, wt, job.runId);
if (!rr.ok) {
emit({ type: 'failed', error: rr.error ?? '执行失败', transcriptRef: rr.transcriptRef, sessionId: rr.sessionId });
emit({ type: 'done' });
return;
}
// 3. 校验
emit({ type: 'phase', phase: 'verifying' });
const vr = await deps.verify(job.project, wt.dir, job.runId);
if (!vr.ok) {
// transcriptRef/sessionId 沿用 executor 的(verify 自身无转录)
emit({ type: 'failed', error: vr.error ?? 'verify 失败', transcriptRef: rr.transcriptRef, sessionId: rr.sessionId });
emit({ type: 'done' });
return;
}
// 4. diff
const diff = await deps.worktreeDiff(job.project.repoPath, wt.dir, wt.branch, job.project.defaultBranch);
// 5. 双复审(顺序;任一失败不挡)。runId 派生子 id,与旧实现一致(${runId}.review / ${runId}.security
emit({ type: 'phase', phase: 'reviewing' });
const report = rr.finalText ?? '';
const code = await runOneReview(deps.reviewCode, job, wt, `${job.runId}.review`, report);
const security = await runOneReview(deps.reviewSecurity, job, wt, `${job.runId}.security`, report);
// 6. 成功终态
emit({
type: 'result',
branch: wt.branch,
worktree: wt.dir,
diffSummary: diff.diffSummary,
commits: diff.commits,
executor: { transcriptRef: rr.transcriptRef, sessionId: rr.sessionId },
code,
security,
});
emit({ type: 'done' });
}