feat: reconcile tool result echoes
This commit is contained in:
@@ -74,6 +74,12 @@ M0-01~M0-08 已完成。Mac 经 Linux Tailscale Web Host `http://100.121.118.1
|
|||||||
|
|
||||||
Conversation Store v5 持久化任务快照,刷新或重新登录后仍只显示任务最新状态。尚未创建取消 outbox、尚未发送任何网络事件、尚未实现 result echo 或 Hermes Adapter 契约。`npm test` 当前为 4 个文件 / 22 个测试通过,`npm run build` 通过。
|
Conversation Store v5 持久化任务快照,刷新或重新登录后仍只显示任务最新状态。尚未创建取消 outbox、尚未发送任何网络事件、尚未实现 result echo 或 Hermes Adapter 契约。`npm test` 当前为 4 个文件 / 22 个测试通过,`npm run build` 通过。
|
||||||
|
|
||||||
|
### M1-05 `tool.result` 回声(已完成)
|
||||||
|
|
||||||
|
每张可信 Tool Call 卡片均以 `call_id` 建立展示索引。同步到达的合法 `tool.result` 或 `tool.cancel` 只会更新同一张 choice / confirm / input 卡片的最终状态,并禁用控件;它们不会再被渲染为独立系统消息或“不支持的 Agent 内容”。未关联到本地卡片的 result/cancel 保持无展示副作用。
|
||||||
|
|
||||||
|
本项没有接入 Adapter 的实际发送、Agent 侧幂等消费或审计;这些是 M1-06 的唯一范围。`npm test` 当前为 4 个文件 / 22 个测试通过,`npm run build` 通过。
|
||||||
|
|
||||||
### 当前结构债务(M0 的改造对象)
|
### 当前结构债务(M0 的改造对象)
|
||||||
|
|
||||||
`src/main.ts` 目前包含 App Shell、页面 DOM、会话 cursor、同步循环与 Runtime 编排;M0-05 已将登录、发送与同步 HTTP `fetch` 迁入 `src/runtime/transport-adapter.ts`,M0-06 已将所有可信 DOM renderer 迁入 `src/runtime/trusted-dom-renderers.ts`。在 M0 准入前不新增 Surface 或 Capability 功能。
|
`src/main.ts` 目前包含 App Shell、页面 DOM、会话 cursor、同步循环与 Runtime 编排;M0-05 已将登录、发送与同步 HTTP `fetch` 迁入 `src/runtime/transport-adapter.ts`,M0-06 已将所有可信 DOM renderer 迁入 `src/runtime/trusted-dom-renderers.ts`。在 M0 准入前不新增 Surface 或 Capability 功能。
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export type TaskInteractionHandlers = {
|
|||||||
export class TrustedDOMRendererContext {
|
export class TrustedDOMRendererContext {
|
||||||
private thinking: HTMLElement | undefined;
|
private thinking: HTMLElement | undefined;
|
||||||
private readonly userRows = new Map<string, HTMLElement>();
|
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 taskCards = new Map<string, { card: HTMLElement; title: HTMLElement; progress: HTMLProgressElement; status: HTMLElement; cancel: HTMLButtonElement }>();
|
||||||
private readonly expirationTimers = new Set<number>();
|
private readonly expirationTimers = new Set<number>();
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ export class TrustedDOMRendererContext {
|
|||||||
public reset(): void {
|
public reset(): void {
|
||||||
this.thinking = undefined;
|
this.thinking = undefined;
|
||||||
this.userRows.clear();
|
this.userRows.clear();
|
||||||
|
this.toolCards.clear();
|
||||||
this.taskCards.clear();
|
this.taskCards.clear();
|
||||||
for (const timer of this.expirationTimers) window.clearTimeout(timer);
|
for (const timer of this.expirationTimers) window.clearTimeout(timer);
|
||||||
this.expirationTimers.clear();
|
this.expirationTimers.clear();
|
||||||
@@ -107,11 +109,15 @@ export class TrustedDOMRendererContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public renderToolResult(item: Extract<ConversationItem, { kind: "tool-result" }>): void {
|
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 {
|
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 {
|
public renderSurface(item: Extract<ConversationItem, { kind: "surface" }>): void {
|
||||||
@@ -286,6 +292,7 @@ export class TrustedDOMRendererContext {
|
|||||||
card.append(heading, detail, actions);
|
card.append(heading, detail, actions);
|
||||||
if (group.mode === "multi-choice") card.append(submitButton);
|
if (group.mode === "multi-choice") card.append(submitButton);
|
||||||
card.append(status);
|
card.append(status);
|
||||||
|
this.toolCards.set(call.call_id, { card, status, controls: buttons });
|
||||||
this.messages.append(card);
|
this.messages.append(card);
|
||||||
this.scheduleExpiry(card, status, call, buttons);
|
this.scheduleExpiry(card, status, call, buttons);
|
||||||
this.scrollLatest();
|
this.scrollLatest();
|
||||||
@@ -323,6 +330,7 @@ export class TrustedDOMRendererContext {
|
|||||||
});
|
});
|
||||||
actions.append(approve, cancel);
|
actions.append(approve, cancel);
|
||||||
card.append(heading, detail, actions, status);
|
card.append(heading, detail, actions, status);
|
||||||
|
this.toolCards.set(call.call_id, { card, status, controls: buttons });
|
||||||
this.messages.append(card);
|
this.messages.append(card);
|
||||||
this.scheduleExpiry(card, status, call, buttons);
|
this.scheduleExpiry(card, status, call, buttons);
|
||||||
this.scrollLatest();
|
this.scrollLatest();
|
||||||
@@ -387,6 +395,7 @@ export class TrustedDOMRendererContext {
|
|||||||
actions.append(submit, cancel);
|
actions.append(submit, cancel);
|
||||||
nativeForm.append(errors, actions);
|
nativeForm.append(errors, actions);
|
||||||
card.append(heading, detail, nativeForm, status);
|
card.append(heading, detail, nativeForm, status);
|
||||||
|
this.toolCards.set(call.call_id, { card, status, controls });
|
||||||
this.messages.append(card);
|
this.messages.append(card);
|
||||||
this.scheduleExpiry(card, status, call, controls);
|
this.scheduleExpiry(card, status, call, controls);
|
||||||
this.scrollLatest();
|
this.scrollLatest();
|
||||||
@@ -432,6 +441,18 @@ export class TrustedDOMRendererContext {
|
|||||||
status.textContent = "该交互已结束或已提交";
|
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 {
|
private showThinking(detail: string): void {
|
||||||
this.thinking?.remove();
|
this.thinking?.remove();
|
||||||
this.thinking = this.appendBubble("agent", detail);
|
this.thinking = this.appendBubble("agent", detail);
|
||||||
|
|||||||
Reference in New Issue
Block a user