fix(rectification): keep opening greeting across set-focus
Independent Staging Quality Gate / validate (push) Successful in 9m24s
Independent Staging Quality Gate / publish (push) Successful in 1m57s

set-focus only attaches the question stem. Retracting live Chinese before
it wiped the opening hello with an empty replace. Other public tools still
retract planning prose.

BUG-533.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-04 19:51:24 +08:00
co-authored by Cursor
parent be6cf145c2
commit a793edb1ad
4 changed files with 69 additions and 2 deletions
@@ -6,7 +6,8 @@
* Once the current step looks like the spoken reply — Chinese sentence end,
* or live-unlock after compare/offer/result tools — tokens may stream as
* `live`. A later public tool-call retracts that speculative stream so
* "Let me set" never stays in `answer.delta`.
* "Let me set" never stays in `answer.delta`. Exception: `rectification-set-focus`
* only attaches the question stem; live greeting/handoff in the same step stays.
*/
export type StepAnswerChunk = Readonly<{
@@ -45,6 +46,11 @@ const SPOKEN_LIVE_UNLOCK_TOOLS = new Set([
"rectification-read-diagnostics",
]);
/** set-focus writes the stem onto the same turn; it is not planning prose. */
const KEEP_LIVE_SPOKEN_TOOLS = new Set([
"rectification-set-focus",
]);
function resetBuffers(state: StepAnswerState): void {
state.text = "";
state.pieces = [];
@@ -153,14 +159,19 @@ export function applyStepAnswerChunk(
}
case "tool-call":
if (isPublicToolCall(chunk, isPublicTool)) {
const retracted = retractLive(state);
state.calledTool = true;
if (KEEP_LIVE_SPOKEN_TOOLS.has(toolName(chunk))) return { kind: "none" };
const retracted = retractLive(state);
return retracted.kind === "retract" ? retracted : { kind: "none" };
}
return { kind: "none" };
case "tool-result":
case "tool-error": {
if (isPublicToolCall(chunk, isPublicTool)) state.calledTool = true;
if (KEEP_LIVE_SPOKEN_TOOLS.has(toolName(chunk))) {
resetBuffers(state);
return { kind: "none" };
}
const discarded = state.calledTool || state.text.length > 0;
const retracted = retractLive(state);
resetBuffers(state);