feat(backthink): 后台思考页接 WS 实时——ws.ts 处理 backthink 事件头插 store
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

后端每次思考落库发 ws.backthink(bus→hub→WS),此前 ws.ts 无 case、addBackthink 是死方法。
新增 case `backthink`→parseBackthinkItem→s.addBackthink 头插;parseBackthinkItem 兼容
Go默认字段(ID/Trigger/At/Conclusion/ShouldPush/PushResult)与admin camelCase。
配合页面(行key=id)实现增量插入、不整页重渲。build 99模块绿。
This commit is contained in:
Aska committed 2026-09-07 18:53:26 +08:00
1 parent 3386679d45
commit 7a81071ec8
2 files changed
+34 -1

No files matched your search

+9
View File
@@ -5,6 +5,7 @@ import {
parseAdminChannelMessage,
parseAgent,
parseAwakenTool,
parseBackthinkItem,
parseGraph,
parsePersistedMessage,
parseTool,
@@ -95,6 +96,14 @@ function dispatch(ev: { type: string; data: unknown }): void {
if (msg) s.pushChannelEvent(msg);
break;
}
case "backthink": {
// design/12 §4 — a background-think pass result was persisted. Head-insert into
// the store so the admin backthink panel appends it in realtime (rows keyed by
// id → React reconciles incrementally, no whole-page re-render).
const item = parseBackthinkItem(r);
if (item) s.addBackthink(item);
break;
}
case "thinking.start": {
pendingThinking = "";
break;
+25 -1
View File
@@ -1,4 +1,4 @@
import { MODES, type AdminChannelMessage, type AgentInfo, type ChatMessage, type FlowGraph, type Mode, type ToolUsed } from "./types";
import { MODES, type AdminChannelMessage, type AgentInfo, type BackthinkItem, type ChatMessage, type FlowGraph, type Mode, type ToolUsed } from "./types";
import { now, uid } from "./util";
/**
@@ -183,3 +183,27 @@ export function parseAdminChannelMessage(v: unknown): AdminChannelMessage | null
createdAt: typeof r.createdAt === "number" ? r.createdAt : now(),
};
}
/** Decode a background-think `backthink` WS payload into a BackthinkItem. The bus
* publishes the raw `backthink.Record` (Go default field names ID/Trigger/At/
* Conclusion/ShouldPush/PushResult); the admin wire uses camelCase, so this reads
* both defensively and returns null when the id cannot be resolved. */
export function parseBackthinkItem(v: unknown): BackthinkItem | null {
const r = toRecord(v);
if (!r) return null;
const idRaw = r.ID ?? r.id ?? r.record_id ?? r.recordId;
if (idRaw === undefined || idRaw === null) return null;
const atRaw = r.At ?? r.at;
let at = 0;
if (typeof atRaw === "number") at = atRaw;
else if (typeof atRaw === "string") {
const t = Date.parse(atRaw);
if (!Number.isNaN(t)) at = t;
}
const trigger = typeof r.Trigger === "string" ? r.Trigger : typeof r.trigger === "string" ? r.trigger : "";
const conclusion = typeof r.Conclusion === "string" ? r.Conclusion : typeof r.conclusion === "string" ? r.conclusion : "";
const pr = toRecord(r.PushResult) ?? {};
const shouldNotify = (r.ShouldPush ?? r.shouldNotify ?? r.should_push) === true;
const pushed = (pr.Pushed ?? pr.pushed ?? r.pushed ?? r.push_pushed) === true;
return { id: String(idRaw), trigger, conclusion, shouldNotify, pushed, at };
}