feat: reconcile tool result echoes

This commit is contained in:
2026-08-03 16:19:18 +08:00
parent 10d95c4a5e
commit b3c33f69e7
2 changed files with 29 additions and 2 deletions
+23 -2
View File
@@ -35,6 +35,7 @@ export type TaskInteractionHandlers = {
export class TrustedDOMRendererContext {
private thinking: HTMLElement | undefined;
private readonly userRows = new Map<string, HTMLElement>();
private readonly toolCards = new Map<string, { card: HTMLElement; status: HTMLElement; controls: readonly (HTMLButtonElement | HTMLInputElement | HTMLTextAreaElement)[] }>();
private readonly taskCards = new Map<string, { card: HTMLElement; title: HTMLElement; progress: HTMLProgressElement; status: HTMLElement; cancel: HTMLButtonElement }>();
private readonly expirationTimers = new Set<number>();
@@ -48,6 +49,7 @@ export class TrustedDOMRendererContext {
public reset(): void {
this.thinking = undefined;
this.userRows.clear();
this.toolCards.clear();
this.taskCards.clear();
for (const timer of this.expirationTimers) window.clearTimeout(timer);
this.expirationTimers.clear();
@@ -107,11 +109,15 @@ export class TrustedDOMRendererContext {
}
public renderToolResult(item: Extract<ConversationItem, { kind: "tool-result" }>): void {
this.renderSystemMessage(`收到工具调用结果(${item.result.call_id} · ${item.result.status})。`);
const entry = this.toolCards.get(item.result.call_id);
if (!entry) return;
this.updateToolCardFinal(entry, item.result.status, item.result.error);
}
public renderToolCancel(item: Extract<ConversationItem, { kind: "tool-cancel" }>): void {
this.renderSystemMessage(`工具调用已取消(${item.cancel.call_id})。`);
const entry = this.toolCards.get(item.cancel.call_id);
if (!entry) return;
this.updateToolCardFinal(entry, "cancelled", item.cancel.reason);
}
public renderSurface(item: Extract<ConversationItem, { kind: "surface" }>): void {
@@ -286,6 +292,7 @@ export class TrustedDOMRendererContext {
card.append(heading, detail, actions);
if (group.mode === "multi-choice") card.append(submitButton);
card.append(status);
this.toolCards.set(call.call_id, { card, status, controls: buttons });
this.messages.append(card);
this.scheduleExpiry(card, status, call, buttons);
this.scrollLatest();
@@ -323,6 +330,7 @@ export class TrustedDOMRendererContext {
});
actions.append(approve, cancel);
card.append(heading, detail, actions, status);
this.toolCards.set(call.call_id, { card, status, controls: buttons });
this.messages.append(card);
this.scheduleExpiry(card, status, call, buttons);
this.scrollLatest();
@@ -387,6 +395,7 @@ export class TrustedDOMRendererContext {
actions.append(submit, cancel);
nativeForm.append(errors, actions);
card.append(heading, detail, nativeForm, status);
this.toolCards.set(call.call_id, { card, status, controls });
this.messages.append(card);
this.scheduleExpiry(card, status, call, controls);
this.scrollLatest();
@@ -432,6 +441,18 @@ export class TrustedDOMRendererContext {
status.textContent = "该交互已结束或已提交";
}
private updateToolCardFinal(entry: { card: HTMLElement; status: HTMLElement; controls: readonly (HTMLButtonElement | HTMLInputElement | HTMLTextAreaElement)[] }, finalStatus: "completed" | "failed" | "cancelled" | "expired", error?: string): void {
entry.card.dataset.status = finalStatus;
entry.status.textContent = finalStatus === "completed"
? "已完成"
: finalStatus === "cancelled"
? "已取消"
: finalStatus === "expired"
? "该交互已过期"
: `处理失败${error ? `${error}` : ""}`;
for (const control of entry.controls) control.disabled = true;
}
private showThinking(detail: string): void {
this.thinking?.remove();
this.thinking = this.appendBubble("agent", detail);