Merge terminal replay card fix

Owner-authorized dev follow-up for stale Finalizing cards. Full CI and review continue.
This commit is contained in:
Anton Razzhigaev 2026-09-13 21:41:04 +03:00 • committed by GitHub
commit 87cac11408
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 42 additions and 0 deletions

View file

@ -2583,8 +2583,12 @@ export function createChatInstance({
}
for (const msg of messages) learnSubagentLineage(msg);
const historicalTerminalProjections = new Set();
for (const msg of messages) if (msg.historical_terminal && msg.task_id) {
historicalTerminals.set(msg.task_id, msg.historical_terminal);
if (msg.summary_kind === 'terminal_root_projection' || msg.outcome_final === true) {
historicalTerminalProjections.add(msg.task_id);
}
}
// First pass builds card state without DOM insertion.
_syncPass1Active = true;
@ -2779,6 +2783,26 @@ export function createChatInstance({
}
_historyRow = null;
// A terminal-root projection is the durable result authority,
// even though it is an intentionally hidden history row. Do
// not let a later authored_root_summary (which may still say
// `finalizing`) leave the card open after restart. Resolve it
// only after replay has seen all narrative rows so the
// terminal fact cannot itself be downgraded by later prose.
for (const [tid, historicalTerminal] of historicalTerminals) {
if (!historicalTerminalProjections.has(tid)) continue;
const terminalRecord = { ...historicalTerminal, task_id: tid };
if (!taskDoneIsTerminal(terminalRecord)) continue;
if (subagentChildParents.has(tid)) {
routeSubagentTerminalToCard(tid, terminalRecord);
continue;
}
const rec = liveCardRecords.get(tid);
if (!rec) continue;
insertCardIfNeeded(tid);
finishLiveCard(tid, taskTerminalPhase(terminalRecord));
}
// Append disconnected visible cards after mid-task reload; skip trivial placeholders.
for (const [tid, rec] of liveCardRecords) {
reorderDirtyCardIfNeeded(rec);

View file

@ -58,6 +58,24 @@ test(' retained cancelled fact outranks untyped historical answer after result q
assert.equal(fx.card('past').querySelector('[data-live-phase]').textContent, 'Cancelled');
} finally {fx.instance.destroy(); restoreDom(fx.prior);}
});
test('terminal-root history projection settles a card after a later finalizing summary', async () => {
const terminal = {status: 'completed', phase: 'done', ts: '2026-09-09T09:02:00Z',
provenance: 'canonical_task_result_after_finalization'};
const fx = makeInstance([
{task_id: 'replayed', is_progress: true, text: 'Working', ts: '2026-09-09T09:00:00Z'},
{task_id: 'replayed', summary_kind: 'terminal_root_projection', historical_terminal: terminal,
role: 'system', system_type: 'task_summary', outcome_final: true, ts: '2026-09-09T09:02:00Z'},
{task_id: 'replayed', summary_kind: 'authored_root_summary', role: 'system',
system_type: 'task_summary', task_phase: 'finalizing', outcome_final: false,
status: 'completed', ts: '2026-09-09T09:03:00Z'},
]);
try {
await fx.instance.refreshHistory({revision: 1});
assert.equal(fx.card('replayed').dataset.finished, '1');
assert.equal(fx.card('replayed').querySelector('[data-live-phase]').textContent, 'Done');
} finally { fx.instance.destroy(); restoreDom(fx.prior); }
});
for (const via of ['log', 'detail']) test(`child terminal ${via} retains the producer model observation`, async () => {
const observation = {source: 'usable_solve_response', used_model: 'fallback', requested_model: 'initial',
used_local: false, requested_use_local: false, llm_call_id: 'call', provider: 'openrouter'};