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 }