fetchWorkspaceContext retrieves fresh instruction files and skills from the workspace agent without persisting. It handles agent connection, context configuration fetching, content sanitization, and metadata stamping. Returns the workspace agent, the stamped parts, discovered skills, and whether the
( ctx context.Context, chat database.Chat, getWorkspaceAgent func(context.Context) (database.WorkspaceAgent, error), getWorkspaceConn func(context.Context) (workspacesdk.AgentConn, error), )
| 8938 | // no valid workspace or the agent lookup failed; |
| 8939 | // workspaceConnOK is false in that case. |
| 8940 | func (p *Server) fetchWorkspaceContext( |
| 8941 | ctx context.Context, |
| 8942 | chat database.Chat, |
| 8943 | getWorkspaceAgent func(context.Context) (database.WorkspaceAgent, error), |
| 8944 | getWorkspaceConn func(context.Context) (workspacesdk.AgentConn, error), |
| 8945 | ) (agent *database.WorkspaceAgent, agentParts []codersdk.ChatMessagePart, discoveredSkills []chattool.SkillMeta, workspaceConnOK bool) { |
| 8946 | if !chat.WorkspaceID.Valid || getWorkspaceAgent == nil { |
| 8947 | return nil, nil, nil, false |
| 8948 | } |
| 8949 | |
| 8950 | loadedAgent, agentErr := getWorkspaceAgent(ctx) |
| 8951 | if agentErr != nil { |
| 8952 | return nil, nil, nil, false |
| 8953 | } |
| 8954 | |
| 8955 | directory := loadedAgent.ExpandedDirectory |
| 8956 | if directory == "" { |
| 8957 | directory = loadedAgent.Directory |
| 8958 | } |
| 8959 | |
| 8960 | // Fetch context configuration from the agent. Parts |
| 8961 | // arrive pre-populated with context-file and skill entries |
| 8962 | // so we don't need additional round-trips. |
| 8963 | if getWorkspaceConn != nil { |
| 8964 | instructionCtx, cancel := context.WithTimeout(ctx, p.instructionLookupTimeout) |
| 8965 | defer cancel() |
| 8966 | |
| 8967 | conn, connErr := getWorkspaceConn(instructionCtx) |
| 8968 | if connErr != nil { |
| 8969 | p.logger.Debug(ctx, "failed to resolve workspace connection for instruction files", |
| 8970 | slog.F("chat_id", chat.ID), |
| 8971 | slog.Error(connErr), |
| 8972 | ) |
| 8973 | } else { |
| 8974 | workspaceConnOK = true |
| 8975 | |
| 8976 | agentCfg, cfgErr := conn.ContextConfig(instructionCtx) |
| 8977 | if cfgErr != nil { |
| 8978 | p.logger.Debug(ctx, "failed to fetch context config from agent", |
| 8979 | slog.F("chat_id", chat.ID), slog.Error(cfgErr)) |
| 8980 | // Treat a transient ContextConfig failure the |
| 8981 | // same as a failed connection so no sentinel is |
| 8982 | // persisted. The next turn will retry. |
| 8983 | workspaceConnOK = false |
| 8984 | } else { |
| 8985 | agentParts = agentCfg.Parts |
| 8986 | } |
| 8987 | } |
| 8988 | } |
| 8989 | |
| 8990 | // Stamp server-side fields and sanitize content. The |
| 8991 | // agent cannot know its own UUID, OS metadata, or |
| 8992 | // directory — those are added here at the trust boundary. |
| 8993 | agentID := uuid.NullUUID{UUID: loadedAgent.ID, Valid: true} |
| 8994 | |
| 8995 | for i := range agentParts { |
| 8996 | agentParts[i].ContextFileAgentID = agentID |
| 8997 | switch agentParts[i].Type { |
no test coverage detected