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:
@@ -21,7 +21,9 @@ import (
|
||||
type AgentGraphRequest struct {
|
||||
UserInput string
|
||||
ConfirmAction string // "accept" / "reject" / "",仅 confirm 恢复场景由前端传入
|
||||
AlwaysExecute bool // true 时写工具跳过确认闸门直接执行,适合前端已展示预览、用户无需逐步确认的场景
|
||||
// ResumeInteractionID 用于校验“本次恢复请求”是否命中了当前 pending 交互,避免旧卡片误恢复。
|
||||
ResumeInteractionID string
|
||||
AlwaysExecute bool // true 时写工具跳过确认闸门直接执行,适合前端已展示预览、用户无需逐步确认的场景
|
||||
}
|
||||
|
||||
// Normalize 统一清洗请求级输入中的字符串字段。
|
||||
@@ -31,6 +33,7 @@ func (r *AgentGraphRequest) Normalize() {
|
||||
}
|
||||
r.UserInput = strings.TrimSpace(r.UserInput)
|
||||
r.ConfirmAction = strings.TrimSpace(r.ConfirmAction)
|
||||
r.ResumeInteractionID = strings.TrimSpace(r.ResumeInteractionID)
|
||||
}
|
||||
|
||||
// RoughBuildPlacement 是粗排算法返回的单条放置结果。
|
||||
|
||||
@@ -39,6 +39,7 @@ func (n *AgentNodes) Chat(ctx context.Context, st *newagentmodel.AgentGraphState
|
||||
ConversationContext: st.EnsureConversationContext(),
|
||||
UserInput: st.Request.UserInput,
|
||||
ConfirmAction: st.Request.ConfirmAction,
|
||||
ResumeInteractionID: st.Request.ResumeInteractionID,
|
||||
Client: st.Deps.ResolveChatClient(),
|
||||
ChunkEmitter: st.EnsureChunkEmitter(),
|
||||
CompactionStore: st.Deps.CompactionStore,
|
||||
|
||||
@@ -49,6 +49,7 @@ type ChatNodeInput struct {
|
||||
ConversationContext *newagentmodel.ConversationContext
|
||||
UserInput string
|
||||
ConfirmAction string
|
||||
ResumeInteractionID string
|
||||
Client *infrallm.Client
|
||||
ChunkEmitter *newagentstream.ChunkEmitter
|
||||
CompactionStore newagentmodel.CompactionStore // 上下文压缩持久化
|
||||
@@ -679,6 +680,14 @@ func handleChatResume(
|
||||
pending := runtimeState.PendingInteraction
|
||||
flowState := runtimeState.EnsureCommonState()
|
||||
|
||||
if isMismatchedResumeInteraction(input.ResumeInteractionID, pending) {
|
||||
_ = emitter.EmitStatus(
|
||||
chatStatusBlockID, chatStageName,
|
||||
"stale_resume", "当前确认已过期,请刷新后重试。", false,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 用户输入在 service 层进入 graph 前已经统一追加到 ConversationContext。
|
||||
// 这里不再二次写入,避免 pending 恢复路径把同一轮 user message 追加两次。
|
||||
|
||||
@@ -715,10 +724,18 @@ func handleConfirmResume(
|
||||
pending *newagentmodel.PendingInteraction,
|
||||
emitter *newagentstream.ChunkEmitter,
|
||||
) error {
|
||||
if isMismatchedResumeInteraction(input.ResumeInteractionID, pending) {
|
||||
_ = emitter.EmitStatus(
|
||||
chatStatusBlockID, chatStageName,
|
||||
"stale_resume", "当前确认已过期,请刷新后重试。", false,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
action := strings.ToLower(strings.TrimSpace(input.ConfirmAction))
|
||||
|
||||
switch action {
|
||||
case "accept":
|
||||
case "accept", "approve":
|
||||
// 恢复前保存待执行工具,Execute 节点需要它。
|
||||
pendingTool := pending.PendingTool
|
||||
runtimeState.ResumeFromPending()
|
||||
@@ -733,7 +750,7 @@ func handleConfirmResume(
|
||||
"confirmed", "已确认,开始执行。", false,
|
||||
)
|
||||
|
||||
case "reject":
|
||||
case "reject", "cancel":
|
||||
runtimeState.ResumeFromPending()
|
||||
if pending.PendingTool != nil {
|
||||
// 工具确认被拒 → 回到 executing 换策略。
|
||||
@@ -748,17 +765,26 @@ func handleConfirmResume(
|
||||
)
|
||||
|
||||
default:
|
||||
// 无合法 confirm action → 保守:等同于 reject。
|
||||
runtimeState.ResumeFromPending()
|
||||
if pending.PendingTool != nil {
|
||||
flowState.Phase = newagentmodel.PhaseExecuting
|
||||
} else {
|
||||
flowState.RejectPlan()
|
||||
}
|
||||
_ = emitter.EmitStatus(
|
||||
chatStatusBlockID, chatStageName,
|
||||
"invalid_confirm_action", "未识别确认动作,请重试。", false,
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isMismatchedResumeInteraction(resumeInteractionID string, pending *newagentmodel.PendingInteraction) bool {
|
||||
if pending == nil {
|
||||
return false
|
||||
}
|
||||
resumeID := strings.TrimSpace(resumeInteractionID)
|
||||
pendingID := strings.TrimSpace(pending.InteractionID)
|
||||
if resumeID == "" || pendingID == "" {
|
||||
return false
|
||||
}
|
||||
return resumeID != pendingID
|
||||
}
|
||||
|
||||
// prepareChatNodeInput 校验并准备聊天节点的运行态依赖。
|
||||
func prepareChatNodeInput(input ChatNodeInput) (
|
||||
*newagentmodel.AgentRuntimeState,
|
||||
|
||||
@@ -62,6 +62,11 @@ type ChunkEmitter struct {
|
||||
RequestID string
|
||||
ModelName string
|
||||
Created int64
|
||||
// extraEventHook 用于把关键结构化事件同步给上层做持久化。
|
||||
// 1. hook 失败不能影响 SSE 主链路;
|
||||
// 2. hook 只接收 extra 结构,避免 emitter 反向依赖业务层;
|
||||
// 3. 不注入时保持空实现,兼容旧调用路径。
|
||||
extraEventHook func(extra *OpenAIChunkExtra)
|
||||
}
|
||||
|
||||
// NoopPayloadEmitter 返回一个空实现,便于骨架期安全占位。
|
||||
@@ -109,6 +114,14 @@ func NewChunkEmitter(emit PayloadEmitter, requestID, modelName string, created i
|
||||
}
|
||||
}
|
||||
|
||||
// SetExtraEventHook 设置结构化事件回调。
|
||||
func (e *ChunkEmitter) SetExtraEventHook(hook func(extra *OpenAIChunkExtra)) {
|
||||
if e == nil {
|
||||
return
|
||||
}
|
||||
e.extraEventHook = hook
|
||||
}
|
||||
|
||||
// EmitReasoningText 输出一段 reasoning 文字,并附带 reasoning_text extra。
|
||||
func (e *ChunkEmitter) EmitReasoningText(blockID, stage, text string, includeRole bool) error {
|
||||
if e == nil || e.emit == nil {
|
||||
@@ -233,6 +246,7 @@ func (e *ChunkEmitter) emitExtraOnly(extra *OpenAIChunkExtra) error {
|
||||
if e == nil || e.emit == nil {
|
||||
return nil
|
||||
}
|
||||
e.emitExtraEventHook(extra)
|
||||
payload, err := ToOpenAIStreamWithExtra(
|
||||
nil,
|
||||
e.RequestID,
|
||||
@@ -250,6 +264,13 @@ func (e *ChunkEmitter) emitExtraOnly(extra *OpenAIChunkExtra) error {
|
||||
return e.emit(payload)
|
||||
}
|
||||
|
||||
func (e *ChunkEmitter) emitExtraEventHook(extra *OpenAIChunkExtra) {
|
||||
if e == nil || e.extraEventHook == nil || extra == nil {
|
||||
return
|
||||
}
|
||||
e.extraEventHook(extra)
|
||||
}
|
||||
|
||||
// EmitConfirmRequest 输出一次待确认事件。
|
||||
//
|
||||
// 当前展示策略:
|
||||
@@ -263,6 +284,7 @@ func (e *ChunkEmitter) EmitConfirmRequest(ctx context.Context, blockID, stage, i
|
||||
|
||||
text := buildConfirmAssistantText(title, summary)
|
||||
extra := NewConfirmRequestExtra(blockID, stage, interactionID, title, summary)
|
||||
e.emitExtraEventHook(extra)
|
||||
return e.emitPseudoText(
|
||||
ctx,
|
||||
text,
|
||||
|
||||
Reference in New Issue
Block a user