runtime: suppress stale owner progress flush

This commit is contained in:
ejclaw
2026-04-12 14:18:22 +09:00
parent 4275f4f984
commit 1f4cd861af
2 changed files with 66 additions and 0 deletions

View File

@@ -472,6 +472,54 @@ describe('MessageTurnController outbound audit logging', () => {
);
});
it('does not flush buffered progress when final delivery is disallowed for a stale owner turn', async () => {
const channel = makeChannel();
const deliverFinalText = vi.fn().mockResolvedValue(true);
const controller = new MessageTurnController({
chatJid: 'dc:test-room',
group: makeGroup(),
runId: 'run-stale-owner-final-with-buffered-progress',
channel,
idleTimeout: 1_000,
failureFinalText: '실패',
isClaudeCodeAgent: true,
clearSession: vi.fn(),
requestClose: vi.fn(),
deliverFinalText,
canDeliverFinalText: () => false,
deliveryRole: 'owner',
pairedTurnIdentity: {
turnId: 'paired-task:2026-04-10T00:00:00.000Z:finalize-owner-turn',
taskId: 'paired-task',
taskUpdatedAt: '2026-04-10T00:00:00.000Z',
intentKind: 'finalize-owner-turn',
role: 'owner',
},
});
await controller.start();
await controller.handleOutput({
status: 'success',
phase: 'progress',
result: '버퍼된 진행 상황',
output: { visibility: 'public', text: '버퍼된 진행 상황' },
} as any);
await controller.handleOutput({
status: 'success',
phase: 'final',
result: 'DONE 최종 답변',
output: { visibility: 'public', text: 'DONE 최종 답변' },
} as any);
const finishResult = await controller.finish('success');
expect(finishResult.deliverySucceeded).toBe(true);
expect(channel.sendAndTrack).not.toHaveBeenCalled();
expect(channel.sendMessage).not.toHaveBeenCalled();
expect(deliverFinalText).not.toHaveBeenCalled();
});
it('replaces the tracked progress message when finish() publishes a failure final', async () => {
const channel = {
...makeChannel(),

View File

@@ -517,6 +517,24 @@ export class MessageTurnController {
* pending text differs from the final text.
*/
private async flushPendingProgress(finalText: string): Promise<void> {
if (
this.options.canDeliverFinalText &&
!this.options.canDeliverFinalText()
) {
this.log.info(
{
runId: this.options.runId,
deliveryRole: this.options.deliveryRole ?? null,
turnId: this.options.pairedTurnIdentity?.turnId ?? null,
pendingLength: this.pendingProgressText?.length ?? 0,
finalLength: finalText.length,
},
'Skipped flushing pending progress because this run no longer owns the active paired turn attempt',
);
this.pendingProgressText = null;
return;
}
if (
this.pendingProgressText &&
(this.options.pairedTurnIdentity?.role === 'reviewer' ||