Files
weichen b820e0070d fix(taskrun): 刷新后恢复进行中任务并纠正进度问询
进行中的 run 提前写下消息锚点,并按会话列出活跃任务;进度疑问句改为查询真实状态,交付物已落盘且不是传输失败时不再被空结果否决。
2026-09-28 06:09:42 +00:00

168 lines
4.7 KiB
Go

package app
import (
"context"
"chat-agent-system/internal/app/chatflow"
"chat-agent-system/internal/app/intentflow"
"chat-agent-system/internal/bizdev"
"chat-agent-system/internal/chat"
"chat-agent-system/internal/coordinator"
"chat-agent-system/internal/ctxmgr"
"chat-agent-system/internal/taskrun"
)
// AdaptChatMemory wraps chatflow.ChatLog as the chat-owned ChatMemory port.
func AdaptChatMemory(log chatflow.ChatLog) chat.ChatMemory {
if log == nil {
return nil
}
return chatMemoryAdapter{inner: log}
}
type chatMemoryAdapter struct {
inner chatflow.ChatLog
}
func (a chatMemoryAdapter) SearchMemory(ctx context.Context, userID int64, q chat.MemoryQuery) ([]ctxmgr.RecalledMemoryItem, error) {
return a.inner.SearchMemory(ctx, chatflow.Principal{UserID: userID, Action: chatflow.ActionAssemble}, chatflow.MemoryQuery{
Query: q.Query,
ProjectID: q.ProjectID,
SessionID: q.SessionID,
ExcludeSessionID: q.ExcludeSessionID,
TopK: q.TopK,
MinScore: q.MinScore,
})
}
// AdaptChatIntentPlans wraps intentflow.Service as the chat-owned IntentPlans port.
func AdaptChatIntentPlans(svc intentflow.Service) chat.IntentPlans {
if svc == nil {
return nil
}
return chatIntentPlansAdapter{inner: svc}
}
type chatIntentPlansAdapter struct {
inner intentflow.Service
}
func (a chatIntentPlansAdapter) HandleTurn(ctx context.Context, in chat.IntentTurnInput) (*chat.IntentTurnResult, error) {
tr, err := a.inner.HandleTurn(ctx, intentflow.TurnInput{
Principal: intentflow.Principal{
UserID: in.UserID,
Environment: in.Environment,
ProjectID: in.ProjectID,
SessionID: in.SessionID,
},
Text: in.Text,
ClientTurnID: in.ClientTurnID,
RequestDigest: in.RequestDigest,
})
if err != nil {
return nil, err
}
if tr == nil {
return &chat.IntentTurnResult{}, nil
}
return &chat.IntentTurnResult{Plan: mapIntentPlanView(tr.Plan)}, nil
}
func mapIntentPlanView(p *intentflow.PlanView) *chat.IntentPlanView {
if p == nil {
return nil
}
out := &chat.IntentPlanView{
PlanID: p.PlanID,
Revision: p.Revision,
Status: string(p.Status),
ExecutionMode: string(p.ExecutionMode),
ExpiresAt: p.ExpiresAt,
SafeSummary: p.SafeSummary,
}
if len(p.Steps) > 0 {
out.Steps = make([]chat.IntentStepView, 0, len(p.Steps))
for _, s := range p.Steps {
out.Steps = append(out.Steps, chat.IntentStepView{
StepID: s.StepID,
ActionKey: s.ActionKey,
Status: string(s.Status),
ExecutionMode: string(s.ExecutionMode),
DependsOn: append([]string(nil), s.DependsOn...),
SafeSummary: s.SafeSummary,
})
}
}
return out
}
// AdaptChatTaskStarter wraps taskrun.Manager as the chat-owned TaskStarter port.
func AdaptChatTaskStarter(m *taskrun.Manager) chat.TaskStarter {
if m == nil {
return nil
}
return chatTaskStarter{inner: m}
}
type chatTaskStarter struct {
inner *taskrun.Manager
}
func (a chatTaskStarter) CreateAndStart(ctx context.Context, runID string, record *taskrun.RunRecord, req chat.TaskStartRequest, resumeDir string) (*taskrun.RunRecord, error) {
return a.inner.CreateAndStart(ctx, runID, record, bizdev.TaskRunRequest{
TaskKey: req.TaskKey,
ProjectName: req.ProjectName,
UserID: req.UserID,
ProjectID: req.ProjectID,
SessionID: req.SessionID,
}, bizdev.StageRunRequest{}, resumeDir)
}
func (a chatTaskStarter) BindAssistantMsgID(ctx context.Context, runID string, assistantMsgID int64) error {
return a.inner.BindAssistantMsgID(ctx, runID, assistantMsgID)
}
// BindChatSession attaches composition-root adapters onto a chat session.
// Callers that keep a mutable intentflow.Service (e.g. server test hooks) should
// prefer AdaptChat* helpers with their own fields so swaps stay visible.
func (rt *Runtime) BindChatSession(sess *chat.Session, env string) {
if rt == nil || sess == nil {
return
}
if env == "" {
env = "dev"
if rt.Cfg != nil && rt.Cfg.Env != "" {
env = rt.Cfg.Env
}
}
sess.ChatLog = AdaptChatMemory(rt.ChatLog)
sess.Actions = &chat.ActionServices{
DocGov: rt.Gov,
TaskRuns: AdaptChatTaskStarter(rt.TaskRuns),
IntentPlans: AdaptChatIntentPlans(rt.IntentPlans),
Environment: env,
}
}
type sessionRunAdapter struct {
mgr *taskrun.Manager
}
func (a sessionRunAdapter) LatestSessionRun(ctx context.Context, sessionID string) (*coordinator.SessionRunSnapshot, error) {
if a.mgr == nil {
return nil, nil
}
rec, err := a.mgr.LatestSessionRun(ctx, sessionID)
if err != nil || rec == nil {
return nil, err
}
return &coordinator.SessionRunSnapshot{
RunID: rec.RunID,
TaskKey: rec.TaskKey,
Status: rec.Status,
WorkspaceID: rec.WorkspaceID,
ErrorMessage: rec.ErrorMessage,
UpdatedAt: rec.UpdatedAt,
}, nil
}