Version: 0.9.32.dev.260419

后端:
1. 会话历史接口切换为统一时间线读取,并兼容 extra.resume 恢复协议
  - api/agent.go:新增 resume->confirm_action 映射(approve/reject/cancel),恢复请求缺 conversation_id 时拦截;GetConversationHistory 改为 GetConversationTimeline
  - routers/routers.go:路由从 GET /conversation-history 切换为 GET /conversation-timeline
  - model/agent.go:删除 GetConversationHistoryItem 旧 DTO
2. 新增会话时间线持久化链路(MySQL + Redis)
  - 新增 model/agent_timeline.go:定义 timeline kind、AgentTimelineEvent、持久化/返回结构
  - 新增 dao/agent_timeline.go:写入事件、按 seq 查询、查询 max seq
  - inits/mysql.go:AutoMigrate 增加 AgentTimelineEvent
  - dao/cache.go:新增 timeline list/seq key,支持 incr/set seq、append/list、全量回填与删除
  - 新增 service/agentsvc/agent_timeline.go:时间线读写编排(Redis 优先、DB 回源、seq 分配与冲突重试、extra 事件映射)
3. 聊天主链路改为写入 timeline,旧 history 服务下线
  - service/agentsvc/agent.go:普通聊天用户/助手消息改为 appendConversationTimelineEvent
  - service/agentsvc/agent_newagent.go:透传 resume_interaction_id;注入 emitter extra hook 持久化卡片事件;正文写入 timeline
  - 删除 service/agentsvc/agent_history.go:下线 conversation-history 旧缓存编排
4. newAgent 恢复与确认防串单增强
  - newAgent/model/graph_run_state.go:AgentGraphRequest 新增 ResumeInteractionID
  - newAgent/node/agent_nodes.go:透传 ResumeInteractionID
  - newAgent/node/chat.go:增加 stale_resume 校验;accept/reject 兼容 approve/cancel;非法动作返回 invalid_confirm_action
  - newAgent/stream/emitter.go:新增 extraEventHook / SetExtraEventHook,在 extra-only 与 confirm 事件触发
5. 日程暂存后同步刷新预览缓存,避免读到拖拽前旧数据
  - service/agentsvc/agent_schedule_state.go:Save 后重建并覆盖 preview 缓存,保留 trace/candidate 等字段
6. 缓存失效策略调整到 timeline 口径
  - middleware/cache_deleter.go:移除 conversation-history 失效逻辑;ChatHistory/AgentChat/AgentTimelineEvent 加入忽略集合

前端:
7. 新增时间线接口与类型定义
  - frontend/src/api/schedule_agent.ts:新增 TimelineEvent/TimelineToolPayload/TimelineConfirmPayload 与 getConversationTimeline
8. AssistantPanel 全面对接 timeline 重建消息与卡片
  - frontend/src/components/dashboard/AssistantPanel.vue:移除旧 history merge/normalize,新增 rebuildStateFromTimeline;支持 execution mode(always_execute);支持 resume-only 发送;修复 confirm 弹层手动关闭后重复弹出;会话标题显示放宽;流式中隐藏 action bar
9. 精排弹窗健壮性与交互动效优化
  - frontend/src/components/assistant/ScheduleFineTuneModal.vue:previewData 支持 nullable,新增 visible 控制与 watch 初始化,补齐空值保护并调整弹窗动画

仓库:
10. 新增前端时间线接入说明文档
  - docs/frontend/newagent_timeline_对接说明.md:接口、kind、payload、刷新重建与迁移建议
This commit is contained in:
Losita
2026-04-19 19:03:41 +08:00
parent 668af5f6c0
commit 0f749e9f5a
22 changed files with 1565 additions and 540 deletions

View File

@@ -159,14 +159,19 @@ func (s *AgentService) runNewAgentGraph(
}
// 6. 构造 AgentGraphRequest。
var confirmAction string
var (
confirmAction string
resumeInteractionID string
)
if len(extra) > 0 {
confirmAction = readAgentExtraString(extra, "confirm_action")
resumeInteractionID = readAgentExtraString(extra, "resume_interaction_id")
}
graphRequest := newagentmodel.AgentGraphRequest{
UserInput: userMessage,
ConfirmAction: confirmAction,
AlwaysExecute: readAgentExtraBool(extra, "always_execute"),
UserInput: userMessage,
ConfirmAction: confirmAction,
ResumeInteractionID: resumeInteractionID,
AlwaysExecute: readAgentExtraBool(extra, "always_execute"),
}
graphRequest.Normalize()
@@ -181,6 +186,10 @@ func (s *AgentService) runNewAgentGraph(
// 8. 适配 SSE emitter。
sseEmitter := newagentstream.NewSSEPayloadEmitter(outChan)
chunkEmitter := newagentstream.NewChunkEmitter(sseEmitter, traceID, resolvedModelName, requestStart.Unix())
// 关键卡片事件走统一时间线持久化,保证刷新后可重建。
chunkEmitter.SetExtraEventHook(func(extra *newagentstream.OpenAIChunkExtra) {
s.persistNewAgentTimelineExtraEvent(context.Background(), userID, chatID, extra)
})
// 9. 构造 AgentGraphDeps由 cmd/start.go 注入的依赖)。
deps := newagentmodel.AgentGraphDeps{
@@ -466,19 +475,33 @@ func (s *AgentService) persistNewAgentConversationMessage(
return err
}
now := time.Now()
s.appendConversationHistoryCacheOptimistically(
// 统一写入会话时间线,保证正文与卡片可按单一 seq 顺序重建。
timelineKind := model.AgentTimelineKindAssistantText
switch role {
case "user":
timelineKind = model.AgentTimelineKindUserText
case "assistant":
timelineKind = model.AgentTimelineKindAssistantText
}
timelinePayload := map[string]any{}
if persistPayload.ReasoningContent != "" {
timelinePayload["reasoning_content"] = persistPayload.ReasoningContent
}
if reasoningDurationSeconds > 0 {
timelinePayload["reasoning_duration_seconds"] = reasoningDurationSeconds
}
if _, err := s.appendConversationTimelineEvent(
ctx,
userID,
chatID,
buildOptimisticConversationHistoryItem(
role,
content,
persistPayload.ReasoningContent,
reasoningDurationSeconds,
now,
),
)
timelineKind,
role,
content,
timelinePayload,
tokensConsumed,
); err != nil {
return err
}
return nil
}