fix: replan-slice infinite loop, non-standard finish_reason crash, fork-resilient test (#866)

This commit is contained in:
Tom Boucher 2026-03-17 13:47:07 -04:00 committed by GitHub
parent 2dd7e256f0
commit 5d86159ea8
2 changed files with 48 additions and 4 deletions

View file

@ -747,10 +747,13 @@ function mapStopReason(reason: ChatCompletionChunk.Choice["finish_reason"]): Sto
return "toolUse";
case "content_filter":
return "error";
default: {
const _exhaustive: never = reason;
throw new Error(`Unhandled stop reason: ${_exhaustive}`);
}
default:
// Third-party and community models (e.g. Qwen GGUF quants) may emit
// non-standard finish_reason values like "eos_token", "eos", or
// "end_of_turn". The OpenAI spec defines finish_reason as a string,
// so we treat unrecognized values as a normal stop rather than
// throwing — which would abort in-flight tool calls (#863).
return "stop";
}
}

View file

@ -493,4 +493,45 @@ console.log('\n=== doctor: no blocker → no blocker_discovered_no_replan issue
rmSync(base, { recursive: true, force: true });
}
// ═══════════════════════════════════════════════════════════════════════════
// Artifact Resolution: resolveExpectedArtifactPath for replan-slice (#858)
// ═══════════════════════════════════════════════════════════════════════════
import { resolveExpectedArtifactPath, verifyExpectedArtifact } from '../auto-recovery.ts';
console.log('\n=== artifact: resolveExpectedArtifactPath returns REPLAN.md path for replan-slice ===');
{
const base = createFixtureBase();
writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
const path = resolveExpectedArtifactPath('replan-slice', 'M001/S01', base);
assertTrue(path !== null, 'resolveExpectedArtifactPath returns non-null for replan-slice');
assertTrue(path!.endsWith('S01-REPLAN.md'), 'path ends with S01-REPLAN.md');
rmSync(base, { recursive: true, force: true });
}
console.log('\n=== artifact: verifyExpectedArtifact fails when REPLAN.md missing (#858) ===');
{
const base = createFixtureBase();
writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
const result = verifyExpectedArtifact('replan-slice', 'M001/S01', base);
assertEq(result, false, 'verifyExpectedArtifact returns false when REPLAN.md is missing');
rmSync(base, { recursive: true, force: true });
}
console.log('\n=== artifact: verifyExpectedArtifact passes when REPLAN.md exists (#858) ===');
{
const base = createFixtureBase();
writeRoadmap(base, 'M001', ROADMAP_ONE_SLICE);
writePlan(base, 'M001', 'S01', makePlanT01DoneT02Pending());
writeReplanFile(base, 'M001', 'S01', '# Replan\n\nBlocker addressed.');
const result = verifyExpectedArtifact('replan-slice', 'M001/S01', base);
assertEq(result, true, 'verifyExpectedArtifact returns true when REPLAN.md exists');
rmSync(base, { recursive: true, force: true });
}
report();