mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-01 23:10:21 +00:00
fix: harden phase-aware assistant visibility
This commit is contained in:
@@ -254,7 +254,7 @@ describe("handleMessageUpdate", () => {
|
||||
expect(ctx.state.blockBuffer).toBe("");
|
||||
});
|
||||
|
||||
it("suppresses commentary partials until a final_answer partial arrives", () => {
|
||||
it("suppresses commentary partials even when they contain visible text", () => {
|
||||
const onAgentEvent = vi.fn();
|
||||
const ctx = {
|
||||
params: {
|
||||
@@ -313,6 +313,10 @@ describe("handleMessageUpdate", () => {
|
||||
},
|
||||
} as never);
|
||||
|
||||
expect(onAgentEvent).not.toHaveBeenCalled();
|
||||
expect(ctx.state.deltaBuffer).toBe("");
|
||||
expect(ctx.state.blockBuffer).toBe("");
|
||||
|
||||
handleMessageUpdate(ctx, {
|
||||
type: "message_update",
|
||||
message: { role: "assistant", content: [] },
|
||||
|
||||
@@ -291,7 +291,7 @@ export function handleMessageUpdate(
|
||||
: msg;
|
||||
const phaseAwareVisibleText = coerceText(extractAssistantVisibleText(partialAssistant)).trim();
|
||||
const deliveryPhase = resolveAssistantMessagePhase(partialAssistant);
|
||||
if (deliveryPhase === "commentary" && !phaseAwareVisibleText) {
|
||||
if (deliveryPhase === "commentary") {
|
||||
return;
|
||||
}
|
||||
const shouldUsePhaseAwareBlockReply = Boolean(deliveryPhase);
|
||||
|
||||
@@ -615,6 +615,23 @@ describe("extractAssistantVisibleText", () => {
|
||||
expect(extractAssistantVisibleText(msg)).toBe("");
|
||||
});
|
||||
|
||||
it("does not fall back to unphased legacy text when an empty final_answer block exists", () => {
|
||||
const msg = makeAssistantMessage({
|
||||
role: "assistant",
|
||||
content: [
|
||||
{ type: "text", text: "Legacy answer" },
|
||||
{
|
||||
type: "text",
|
||||
text: " ",
|
||||
textSignature: JSON.stringify({ v: 1, id: "item_final", phase: "final_answer" }),
|
||||
},
|
||||
],
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
expect(extractAssistantVisibleText(msg)).toBe("");
|
||||
});
|
||||
|
||||
it("falls back to legacy unphased text when phased text is absent", () => {
|
||||
const msg = makeAssistantMessage({
|
||||
role: "assistant",
|
||||
|
||||
@@ -249,6 +249,42 @@ function finalizeAssistantExtraction(msg: AssistantMessage, extracted: string):
|
||||
return sanitizeUserFacingText(extracted, { errorContext });
|
||||
}
|
||||
|
||||
function hasAssistantTextForPhase(msg: AssistantMessage, phase: AssistantPhase): boolean {
|
||||
const messagePhase = normalizeAssistantPhase((msg as { phase?: unknown }).phase);
|
||||
|
||||
if (typeof msg.content === "string") {
|
||||
return messagePhase === phase;
|
||||
}
|
||||
|
||||
if (!Array.isArray(msg.content)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const hasExplicitPhasedTextBlocks = msg.content.some((block) => {
|
||||
if (!block || typeof block !== "object") {
|
||||
return false;
|
||||
}
|
||||
const record = block as { type?: unknown; textSignature?: unknown };
|
||||
if (record.type !== "text") {
|
||||
return false;
|
||||
}
|
||||
return Boolean(parseAssistantTextSignature(record.textSignature)?.phase);
|
||||
});
|
||||
|
||||
return msg.content.some((block) => {
|
||||
if (!block || typeof block !== "object") {
|
||||
return false;
|
||||
}
|
||||
const record = block as { type?: unknown; textSignature?: unknown };
|
||||
if (record.type !== "text") {
|
||||
return false;
|
||||
}
|
||||
const signature = parseAssistantTextSignature(record.textSignature);
|
||||
const resolvedPhase = signature?.phase ?? (hasExplicitPhasedTextBlocks ? undefined : messagePhase);
|
||||
return resolvedPhase === phase;
|
||||
});
|
||||
}
|
||||
|
||||
function extractAssistantTextForPhase(msg: AssistantMessage, phase?: AssistantPhase): string {
|
||||
const messagePhase = normalizeAssistantPhase((msg as { phase?: unknown }).phase);
|
||||
const shouldIncludeContent = (resolvedPhase?: AssistantPhase) => {
|
||||
@@ -306,8 +342,8 @@ function extractAssistantTextForPhase(msg: AssistantMessage, phase?: AssistantPh
|
||||
|
||||
export function extractAssistantVisibleText(msg: AssistantMessage): string {
|
||||
const finalAnswerText = extractAssistantTextForPhase(msg, "final_answer");
|
||||
if (finalAnswerText.trim()) {
|
||||
return finalAnswerText;
|
||||
if (hasAssistantTextForPhase(msg, "final_answer")) {
|
||||
return finalAnswerText.trim() ? finalAnswerText : "";
|
||||
}
|
||||
|
||||
return extractAssistantTextForPhase(msg);
|
||||
|
||||
Reference in New Issue
Block a user