feat: agent 实时输出流(SSE)

B-② SSE 实时流(⑧ ★ GET /api/tasks/:id/stream):

后端:
- GET /api/tasks/:id/stream:SSE 跟随任务最近一条 run 的 transcript 文件,
  新写入的 agent 消息逐行推送(活跃 run=实时;已结束=回放后 end);
  500ms 轮询 tail(按 offset 增量读),run 转非 started 即结束流,客户端断开即停

前端:
- StreamModal(EventSource 消费 SSE,summarizeMessage 把 SDK 消息→可读行:
  助手文本/⚙工具调用/■终态/·maestro 元事件,自动滚动到底)
- AgentSection agent 行可点击 → 打开实时流模态;adaptActiveAgents 带出 taskId

验证:typecheck 干净;前端 build 通过;dev 渲染 0 console 错误。
(实时效果需有运行中的 agent;端到端随 daemon 重启验证。)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-24 17:01:58 +08:00
parent 9b17d3e24f
commit a688b6977e
4 changed files with 105 additions and 5 deletions
+6 -2
View File
@@ -154,7 +154,7 @@ function ConfigPanel({ project, onSave, onClose, onSync, t, lang }) {
);
}
function AgentSection({ agents, quota, t }) {
function AgentSection({ agents, quota, t, onOpenStream }) {
const { SectionHead, QuotaMeter } = window.MaestroDesignSystem_a6a290;
return (
<section>
@@ -176,7 +176,11 @@ function AgentSection({ agents, quota, t }) {
</div>
<div style={{ flex: 1, display: 'grid', gap: 10, minWidth: 0, alignContent: 'center' }}>
{agents.map((a) => (
<div key={a.id} style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: 12, padding: '3px 0' }}>
<div key={a.id} onClick={() => onOpenStream && a.taskId && onOpenStream(a)}
title={onOpenStream ? '点击查看实时输出' : undefined}
style={{ display: 'flex', gap: 8, alignItems: 'center', fontSize: 12, padding: '3px 4px', borderRadius: 4, cursor: onOpenStream && a.taskId ? 'pointer' : 'default' }}
onMouseEnter={(e) => { if (onOpenStream && a.taskId) e.currentTarget.style.background = 'var(--panel-2)'; }}
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}>
<span style={{ flex: 'none', width: 6, height: 6, borderRadius: '50%', background: 'var(--cyan)', boxShadow: '0 0 8px var(--cyan)', animation: 'maestro-pulse .9s infinite' }}></span>
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{a.title}</span>
<span style={{ flex: 'none', fontSize: 10, letterSpacing: '.1em', color: 'var(--cyan)', border: '1px solid var(--cyan-dim)', padding: '0 6px', borderRadius: 3 }}>{a.kind}</span>