feat(flow): 实时全链路流程可视化 P1——trace→graph_update→前端全链路流程页(design/19)
后端: trace/graph.go(RunGraph 图构建器,kind/status 映射,父子优先连线,脱敏白名单+截断);
loop/flow.go(FlowPublisher 按会话增量发布 ws.graph_update,仅主人web会话); loop span
start/finish 折入图; run.go StartRound/EndRound; wire.go 组装+注入.
前端: Flow.tsx(全链路流程页:FlowGraphView+会话/节点/开始/耗时卡片+状态计数+节点点开日志);
FlowGraph 组件加 onNodeClick; types/wsParse 扩展; router/sections/AdminApp 注册 flow;
api-client.md §3.5 graph_update 契约.
验证: 后端 build/vet/test 全绿+flow_test.go 集成(span→graph→ws.graph_update主干); 前端 build 绿.
红线: 单二进制零新依赖; 工具参数/结果脱敏; 仅主人web会话; 身份靠actor元数据; 只读不虚构.
P2(并行子代理分支/泳道/节点实时日志)后置.
This commit is contained in:
1 parent
7a81071ec8
commit
44e7a5c2c5
18 files changed
+1126
-11
No files matched your search
@@ -156,6 +156,12 @@ export interface FlowNode {
|
||||
label: string;
|
||||
kind?: string;
|
||||
status?: AgentStatus;
|
||||
/** Optional structured metadata (params/result summaries) for the node detail panel. */
|
||||
props?: Record<string, unknown>;
|
||||
/** Optional accumulated span events (log) for the node detail panel. */
|
||||
log?: TraceEvent[];
|
||||
startedAt?: string;
|
||||
endedAt?: string;
|
||||
}
|
||||
export interface FlowEdge {
|
||||
from: string;
|
||||
@@ -165,6 +171,11 @@ export interface FlowGraph {
|
||||
id: string;
|
||||
nodes: FlowNode[];
|
||||
edges: FlowEdge[];
|
||||
/** The graph's owning session id (backend `sessionId`). */
|
||||
sessionId?: string;
|
||||
/** Round start/end times (RFC3339), when the backend provides them. */
|
||||
startedAt?: string;
|
||||
endedAt?: string;
|
||||
}
|
||||
|
||||
export interface SessionRecord {
|
||||
|
||||
@@ -109,8 +109,11 @@ export function parseAgent(payload: unknown): AgentInfo | null {
|
||||
|
||||
export function parseGraph(payload: unknown): FlowGraph | null {
|
||||
const r = toRecord(payload);
|
||||
if (!r || !Array.isArray(r.nodes)) return null;
|
||||
const nodes = r.nodes
|
||||
if (!r) return null;
|
||||
// Support both a bare FlowGraph and a {sessionId, graph} envelope.
|
||||
const graphRec = toRecord(r.graph) ?? r;
|
||||
if (!Array.isArray(graphRec.nodes)) return null;
|
||||
const nodes = graphRec.nodes
|
||||
.map((n) => toRecord(n))
|
||||
.filter((n): n is Record<string, unknown> => n !== null)
|
||||
.map((n) => ({
|
||||
@@ -121,9 +124,22 @@ export function parseGraph(payload: unknown): FlowGraph | null {
|
||||
typeof n.status === "string"
|
||||
? (n.status as FlowGraph["nodes"][number]["status"])
|
||||
: undefined,
|
||||
startedAt: typeof n.startedAt === "string" ? n.startedAt : undefined,
|
||||
endedAt: typeof n.endedAt === "string" ? n.endedAt : undefined,
|
||||
props: toRecord(n.props) ?? undefined,
|
||||
log: Array.isArray(n.log)
|
||||
? n.log
|
||||
.map((e) => toRecord(e))
|
||||
.filter((e): e is Record<string, unknown> => e !== null)
|
||||
.map((e) => ({
|
||||
time: typeof e.time === "string" ? e.time : "",
|
||||
type: typeof e.type === "string" ? e.type : "",
|
||||
props: toRecord(e.props) ?? undefined,
|
||||
}))
|
||||
: undefined,
|
||||
}));
|
||||
const edges = Array.isArray(r.edges)
|
||||
? r.edges
|
||||
const edges = Array.isArray(graphRec.edges)
|
||||
? graphRec.edges
|
||||
.map((e) => toRecord(e))
|
||||
.filter((e): e is Record<string, unknown> => e !== null)
|
||||
.map((e) => ({
|
||||
@@ -131,7 +147,16 @@ export function parseGraph(payload: unknown): FlowGraph | null {
|
||||
to: typeof e.to === "string" ? e.to : "",
|
||||
}))
|
||||
: [];
|
||||
return { id: typeof r.id === "string" ? r.id : "graph", nodes, edges };
|
||||
return {
|
||||
id: typeof graphRec.id === "string" ? graphRec.id : "graph",
|
||||
nodes,
|
||||
edges,
|
||||
sessionId:
|
||||
(typeof r.sessionId === "string" ? r.sessionId : undefined) ??
|
||||
(typeof graphRec.sessionId === "string" ? graphRec.sessionId : undefined),
|
||||
startedAt: typeof graphRec.startedAt === "string" ? graphRec.startedAt : undefined,
|
||||
endedAt: typeof graphRec.endedAt === "string" ? graphRec.endedAt : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/** Build a ChatMessage from a `message_persisted` payload (design/16 §3.3/§4.2):
|
||||
|
||||
Reference in new issue
Block a user