// 历史 transcript 回放:拉某任务指定 kind 的 run 的转录(/api/tasks/:id/runs → /api/runs/:id/transcript), // 渲染成结构化行(text / tool_use / tool_result / result)。挂 window.MaestroTranscript,供拆解评审「拆解轨迹」等处复用。 // 转录已是脱敏 + 截断的扁平 schema:{type, text?, name?, input?, isError?}(见 src/executor/transcript.ts)。 function TcLine({ m }) { if (m.type === 'tool_use') { return
⚙ {m.name}{m.input ? {m.input} : null}
; } if (m.type === 'tool_result') { return
↩ {m.text || '(空)'}
; } if (m.type === 'result') return
■ {m.text || 'done'}
; return
{m.text}
; } function MaestroTranscript({ taskId, kind }) { const [state, setState] = React.useState({ loading: true, msgs: null, err: false }); React.useEffect(() => { let alive = true; setState({ loading: true, msgs: null, err: false }); (async () => { try { const runs = await fetch('/api/tasks/' + taskId + '/runs').then((r) => r.ok ? r.json() : Promise.reject(new Error('http'))); const list = Array.isArray(runs) ? runs : []; const run = (kind ? list.filter((r) => r.kind === kind) : list) .slice().sort((a, b) => String(b.startedAt || '').localeCompare(String(a.startedAt || '')))[0] || list[0]; if (!run) { if (alive) setState({ loading: false, msgs: [], err: false }); return; } const d = await fetch('/api/runs/' + run.id + '/transcript').then((r) => r.ok ? r.json() : Promise.reject(new Error('http'))); const msgs = Array.isArray(d) ? d : (d.messages || []); if (alive) setState({ loading: false, msgs, err: false }); } catch (e) { if (alive) setState({ loading: false, msgs: null, err: true }); } })(); return () => { alive = false; }; }, [taskId, kind]); const hint = (txt) =>
{txt}
; if (state.loading) return hint('… 加载轨迹'); if (state.err) return hint('轨迹取不到'); if (!state.msgs || !state.msgs.length) return hint('无轨迹记录'); return (
{state.msgs.map((m, i) => )}
); } window.MaestroTranscript = MaestroTranscript;