fix(agent-pod): group sessions by repo, resume phase branches, fix empty-diff bug
- agent-manager spawn now gets --group repoId, so the TUI clusters planner/investigator/implementer/judge under one repo heading instead of 4 unrelated sessions. - runPhase was called with phaseBranch where it needed the true baseBranch, so every per-task judge review compared phaseBranch...HEAD -- always empty, since HEAD is phaseBranch while checked out. Judges only produced real verdicts anyway because they fell back to their own git log/show. - Every restart re-cloned baseBranch fresh and started a new phase branch, discarding whatever a prior run had already committed mid-phase. Now: fetch+resume an existing phase branch if origin has one, push after every task instead of only at phase-end, and delete the phase branch (local + origin) once its milestone squash-merges into base.
This commit is contained in:
@@ -260,7 +260,7 @@ data:
|
||||
const cwdReminder = `Your working directory for this task is ${cwd} -- if your shell isn't already there, run: cd ${cwd}\n\n`;
|
||||
|
||||
if (!target) {
|
||||
const spawnArgs = ["spawn", "--tool", "pi", "--cwd", cwd, "--name", label, "--prompt", cwdReminder + HARD_RULES + prompt];
|
||||
const spawnArgs = ["spawn", "--tool", "pi", "--cwd", cwd, "--name", label, "--group", repoId, "--prompt", cwdReminder + HARD_RULES + prompt];
|
||||
const { provider, model } = providerModelFor(role);
|
||||
if (provider) spawnArgs.push("--provider", provider);
|
||||
if (model) spawnArgs.push("--model", model);
|
||||
@@ -474,12 +474,21 @@ data:
|
||||
// judge/etc. exists per repo, so there is no per-task concurrency to have
|
||||
// here anymore (see REPO_CONCURRENCY below for where concurrency now
|
||||
// lives). No worktrees: every task commits directly onto phaseBranch in the
|
||||
// one shared cwd.
|
||||
async function runPhase(cwd, baseBranch, phaseTasks, pool, repoId, pipelineSession) {
|
||||
// one shared cwd. baseBranch here is the TRUE base (e.g. "main") -- judge
|
||||
// reviews `git diff baseBranch...HEAD`, not phaseBranch...HEAD, which would
|
||||
// always be empty since HEAD *is* phaseBranch while it's checked out.
|
||||
//
|
||||
// Pushes phaseBranch after every task, not just once at full-phase-end: the
|
||||
// pod is ephemeral and every restart re-clones baseBranch fresh (see
|
||||
// runRepoPipeline) -- without this, a redeploy mid-phase silently discards
|
||||
// every task committed so far, and the next run re-decides "is this done?"
|
||||
// from a clone that never saw any of that work.
|
||||
async function runPhase(cwd, baseBranch, phaseBranch, phaseTasks, pool, repoId, pipelineSession) {
|
||||
const entries = phaseTasks.map((t) => (typeof t === "string" ? { id: t, judgeOnly: false } : t));
|
||||
for (const entry of entries) {
|
||||
const result = await runTaskOnPool(cwd, baseBranch, entry.id, pool, repoId, pipelineSession, entry.judgeOnly);
|
||||
pipelineSession.taskResults.push(result);
|
||||
await runGit(cwd, ["push", "-u", "origin", phaseBranch]);
|
||||
logProgress(pipelineSession);
|
||||
}
|
||||
}
|
||||
@@ -584,13 +593,21 @@ data:
|
||||
const phaseLabel = phaseLabelFor(phaseTasks, i);
|
||||
const phaseBranch = branchName ? `${branchName}/${phaseLabel}` : `agent-run/${repoId}/${phaseLabel}`;
|
||||
|
||||
const branchResult = await runGit(cwd, ["checkout", "-b", phaseBranch]);
|
||||
// Resume a phase branch a prior (since-restarted) run already pushed,
|
||||
// instead of always branching fresh off baseBranch -- otherwise every
|
||||
// redeploy silently discards whatever tasks that prior run already
|
||||
// committed and pushed (see runPhase's per-task push below).
|
||||
const fetchExisting = await runGit(cwd, ["fetch", "origin", phaseBranch]);
|
||||
const resuming = fetchExisting.code === 0;
|
||||
const branchResult = resuming
|
||||
? await runGit(cwd, ["checkout", "-b", phaseBranch, "FETCH_HEAD"])
|
||||
: await runGit(cwd, ["checkout", "-b", phaseBranch]);
|
||||
if (branchResult.code !== 0) {
|
||||
pipelineSession.gitError = branchResult.out;
|
||||
return finish("branch-crashed");
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
if (i === 0 && !resuming) {
|
||||
const gitignoreAdditions = [
|
||||
"",
|
||||
"# agent-harness: build artifacts and vendored archives never belong in source control",
|
||||
@@ -616,7 +633,7 @@ data:
|
||||
await runGit(cwd, ["commit", "-m", "chore: broaden .gitignore for agent-run artifacts"]);
|
||||
}
|
||||
|
||||
await runPhase(cwd, phaseBranch, phaseTasks, pool, repoId, pipelineSession);
|
||||
await runPhase(cwd, baseBranch, phaseBranch, phaseTasks, pool, repoId, pipelineSession);
|
||||
|
||||
const phaseTaskIds = new Set(phaseTasks.map((t) => (typeof t === "string" ? t : t.id)));
|
||||
const phaseResults = pipelineSession.taskResults.filter((r) => phaseTaskIds.has(r.task));
|
||||
@@ -680,6 +697,15 @@ data:
|
||||
pipelineSession.gitError = push.out;
|
||||
return finish("squash-push-crashed");
|
||||
}
|
||||
|
||||
// Milestone's content now lives in baseBranch as one squashed commit --
|
||||
// the phase branch (and whatever a prior restart already pushed of it)
|
||||
// has no further reason to exist. Delete it both places so a future run
|
||||
// never tries to resume a phase that's already done, and so origin
|
||||
// doesn't accumulate one dangling branch per completed phase forever.
|
||||
await runGit(cwd, ["branch", "-D", phaseBranch]);
|
||||
await runGit(cwd, ["push", "origin", "--delete", phaseBranch]);
|
||||
|
||||
logProgress(pipelineSession);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user