fix: deduplicate progress at runner level, not host revert
Buffer intermediate assistant text in the runner. When result arrives with matching text, discard the buffered progress instead of emitting it. This prevents the duplicate from ever reaching Discord, eliminating the flash-then-revert behavior.
This commit is contained in:
@@ -452,6 +452,7 @@ async function runQuery(
|
|||||||
let messageCount = 0;
|
let messageCount = 0;
|
||||||
let resultCount = 0;
|
let resultCount = 0;
|
||||||
let terminalResultObserved = false;
|
let terminalResultObserved = false;
|
||||||
|
let pendingProgressText: string | null = null;
|
||||||
|
|
||||||
// Discover additional directories
|
// Discover additional directories
|
||||||
const extraDirs: string[] = [];
|
const extraDirs: string[] = [];
|
||||||
@@ -541,6 +542,18 @@ async function runQuery(
|
|||||||
const msgType = message.type === 'system' ? `system/${(message as { subtype?: string }).subtype}` : message.type;
|
const msgType = message.type === 'system' ? `system/${(message as { subtype?: string }).subtype}` : message.type;
|
||||||
log(`[msg #${messageCount}] type=${msgType}`);
|
log(`[msg #${messageCount}] type=${msgType}`);
|
||||||
|
|
||||||
|
// Flush pending progress on any non-assistant message (ensures it's
|
||||||
|
// emitted before tool-activity or other events that depend on it).
|
||||||
|
if (message.type !== 'assistant' && pendingProgressText) {
|
||||||
|
writeOutput({
|
||||||
|
status: 'success',
|
||||||
|
phase: 'progress',
|
||||||
|
result: pendingProgressText,
|
||||||
|
newSessionId,
|
||||||
|
});
|
||||||
|
pendingProgressText = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (message.type === 'system' && message.subtype === 'init') {
|
if (message.type === 'system' && message.subtype === 'init') {
|
||||||
newSessionId = message.session_id;
|
newSessionId = message.session_id;
|
||||||
log(`Session initialized: ${newSessionId}`);
|
log(`Session initialized: ${newSessionId}`);
|
||||||
@@ -609,6 +622,14 @@ async function runQuery(
|
|||||||
resultCount++;
|
resultCount++;
|
||||||
const textResult = 'result' in message ? (message as { result?: string }).result : null;
|
const textResult = 'result' in message ? (message as { result?: string }).result : null;
|
||||||
const isError = message.subtype?.startsWith('error');
|
const isError = message.subtype?.startsWith('error');
|
||||||
|
// Discard pending progress if it matches the final result (prevent duplicate)
|
||||||
|
if (pendingProgressText && textResult && pendingProgressText === textResult) {
|
||||||
|
log(`Discarding pending progress (matches result)`);
|
||||||
|
pendingProgressText = null;
|
||||||
|
} else if (pendingProgressText) {
|
||||||
|
writeOutput({ status: 'success', phase: 'progress', result: pendingProgressText, newSessionId });
|
||||||
|
pendingProgressText = null;
|
||||||
|
}
|
||||||
log(`Result #${resultCount}: subtype=${message.subtype}${textResult ? ` text=${textResult.slice(0, 200)}` : ''}`);
|
log(`Result #${resultCount}: subtype=${message.subtype}${textResult ? ` text=${textResult.slice(0, 200)}` : ''}`);
|
||||||
if (isError) {
|
if (isError) {
|
||||||
// Log full error details for debugging
|
// Log full error details for debugging
|
||||||
@@ -672,16 +693,23 @@ async function runQuery(
|
|||||||
stream.end();
|
stream.end();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Intermediate assistant text between tool calls → progress
|
// Intermediate assistant text between tool calls → buffer as pending progress.
|
||||||
|
// Don't emit immediately — if the next message is a result with the same text,
|
||||||
|
// this would cause a duplicate. The pending text is flushed when the next
|
||||||
|
// non-result message arrives, or discarded if result matches.
|
||||||
if (stopReason !== 'end_turn' && textResult) {
|
if (stopReason !== 'end_turn' && textResult) {
|
||||||
log(`Intermediate assistant text (${textResult.length} chars, stop=${stopReason})`);
|
// Flush any previous pending progress first
|
||||||
|
if (pendingProgressText) {
|
||||||
writeOutput({
|
writeOutput({
|
||||||
status: 'success',
|
status: 'success',
|
||||||
phase: 'progress',
|
phase: 'progress',
|
||||||
result: textResult,
|
result: pendingProgressText,
|
||||||
newSessionId,
|
newSessionId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
pendingProgressText = textResult;
|
||||||
|
log(`Intermediate assistant text buffered (${textResult.length} chars, stop=${stopReason})`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,18 +154,6 @@ export class MessageTurnController {
|
|||||||
// then discard the pending buffer so it never shows up.
|
// then discard the pending buffer so it never shows up.
|
||||||
if (text) {
|
if (text) {
|
||||||
await this.flushPendingProgress(text);
|
await this.flushPendingProgress(text);
|
||||||
// If the displayed progress heading matches the final text,
|
|
||||||
// revert to the previous heading so it doesn't show twice.
|
|
||||||
if (
|
|
||||||
this.latestProgressText === text &&
|
|
||||||
this.previousProgressText &&
|
|
||||||
this.progressMessageId &&
|
|
||||||
this.options.channel.editMessage
|
|
||||||
) {
|
|
||||||
this.latestProgressText = this.previousProgressText;
|
|
||||||
this.toolActivities = [];
|
|
||||||
await this.syncTrackedProgressMessage();
|
|
||||||
}
|
|
||||||
await this.finalizeProgressMessage();
|
await this.finalizeProgressMessage();
|
||||||
await this.deliverFinalText(text);
|
await this.deliverFinalText(text);
|
||||||
} else if (raw) {
|
} else if (raw) {
|
||||||
|
|||||||
Reference in New Issue
Block a user