@@ -11,7 +11,6 @@ import (
1111"strings"
1212"time"
1313
14- "github.com/go-chi/chi/v5"
1514"github.com/google/uuid"
1615
1716"cdr.dev/slog"
@@ -755,15 +754,7 @@ func (api *API) taskDelete(rw http.ResponseWriter, r *http.Request) {
755754// workspace and validate the sidebar app health.
756755func (api * API )taskSend (rw http.ResponseWriter ,r * http.Request ) {
757756ctx := r .Context ()
758-
759- idStr := chi .URLParam (r ,"id" )
760- taskID ,err := uuid .Parse (idStr )
761- if err != nil {
762- httpapi .Write (ctx ,rw ,http .StatusBadRequest , codersdk.Response {
763- Message :fmt .Sprintf ("Invalid UUID %q for task ID." ,idStr ),
764- })
765- return
766- }
757+ task := httpmw .TaskParam (r )
767758
768759var req codersdk.TaskSendRequest
769760if ! httpapi .Read (ctx ,rw ,r ,& req ) {
@@ -776,7 +767,7 @@ func (api *API) taskSend(rw http.ResponseWriter, r *http.Request) {
776767return
777768}
778769
779- if err = api .authAndDoWithTaskSidebarAppClient (r ,taskID ,func (ctx context.Context ,client * http.Client ,appURL * url.URL )error {
770+ if err : =api .authAndDoWithTaskAppClient (r ,task ,func (ctx context.Context ,client * http.Client ,appURL * url.URL )error {
780771agentAPIClient ,err := aiagentapi .NewClient (appURL .String (),aiagentapi .WithHTTPClient (client ))
781772if err != nil {
782773return httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
@@ -835,18 +826,10 @@ func (api *API) taskSend(rw http.ResponseWriter, r *http.Request) {
835826// We enforce ApplicationConnect RBAC on the workspace and validate the sidebar app health.
836827func (api * API )taskLogs (rw http.ResponseWriter ,r * http.Request ) {
837828ctx := r .Context ()
838-
839- idStr := chi .URLParam (r ,"id" )
840- taskID ,err := uuid .Parse (idStr )
841- if err != nil {
842- httpapi .Write (ctx ,rw ,http .StatusBadRequest , codersdk.Response {
843- Message :fmt .Sprintf ("Invalid UUID %q for task ID." ,idStr ),
844- })
845- return
846- }
829+ task := httpmw .TaskParam (r )
847830
848831var out codersdk.TaskLogsResponse
849- if err := api .authAndDoWithTaskSidebarAppClient (r ,taskID ,func (ctx context.Context ,client * http.Client ,appURL * url.URL )error {
832+ if err := api .authAndDoWithTaskAppClient (r ,task ,func (ctx context.Context ,client * http.Client ,appURL * url.URL )error {
850833agentAPIClient ,err := aiagentapi .NewClient (appURL .String (),aiagentapi .WithHTTPClient (client ))
851834if err != nil {
852835return httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
@@ -894,7 +877,7 @@ func (api *API) taskLogs(rw http.ResponseWriter, r *http.Request) {
894877httpapi .Write (ctx ,rw ,http .StatusOK ,out )
895878}
896879
897- //authAndDoWithTaskSidebarAppClient centralizes the shared logic to:
880+ //authAndDoWithTaskAppClient centralizes the shared logic to:
898881//
899882// - Fetch the task workspace
900883// - Authorize ApplicationConnect on the workspace
@@ -903,15 +886,31 @@ func (api *API) taskLogs(rw http.ResponseWriter, r *http.Request) {
903886//
904887// The provided callback receives the context, an HTTP client that dials via the
905888// agent, and the base app URL (as a value URL) to perform any request.
906- func (api * API )authAndDoWithTaskSidebarAppClient (
889+ func (api * API )authAndDoWithTaskAppClient (
907890r * http.Request ,
908- taskID uuid. UUID ,
891+ task database. Task ,
909892do func (ctx context.Context ,client * http.Client ,appURL * url.URL )error ,
910893)error {
911894ctx := r .Context ()
912895
913- workspaceID := taskID
914- workspace ,err := api .Database .GetWorkspaceByID (ctx ,workspaceID )
896+ if task .Status != database .TaskStatusActive {
897+ return httperror .NewResponseError (http .StatusBadRequest , codersdk.Response {
898+ Message :"Task status must be active." ,
899+ Detail :fmt .Sprintf ("Task status is %q, it must be %q to interact with the task." ,task .Status ,codersdk .TaskStatusActive ),
900+ })
901+ }
902+ if ! task .WorkspaceID .Valid {
903+ return httperror .NewResponseError (http .StatusBadRequest , codersdk.Response {
904+ Message :"Task does not have a workspace." ,
905+ })
906+ }
907+ if ! task .WorkspaceAppID .Valid {
908+ return httperror .NewResponseError (http .StatusBadRequest , codersdk.Response {
909+ Message :"Task does not have a workspace app." ,
910+ })
911+ }
912+
913+ workspace ,err := api .Database .GetWorkspaceByID (ctx ,task .WorkspaceID .UUID )
915914if err != nil {
916915if httpapi .Is404Error (err ) {
917916return httperror .ErrResourceNotFound
@@ -927,65 +926,30 @@ func (api *API) authAndDoWithTaskSidebarAppClient(
927926return httperror .ErrResourceNotFound
928927}
929928
930- data ,err := api .workspaceData (ctx ,[]database. Workspace { workspace } )
929+ apps ,err := api .Database . GetWorkspaceAppsByAgentID (ctx ,task . WorkspaceAgentID . UUID )
931930if err != nil {
932931return httperror .NewResponseError (http .StatusInternalServerError , codersdk.Response {
933932Message :"Internal error fetching workspace resources." ,
934933Detail :err .Error (),
935934})
936935}
937- if len (data .builds )== 0 || len (data .templates )== 0 {
938- return httperror .ErrResourceNotFound
939- }
940- build := data .builds [0 ]
941- if build .HasAITask == nil || ! * build .HasAITask || build .AITaskSidebarAppID == nil || * build .AITaskSidebarAppID == uuid .Nil {
942- return httperror .NewResponseError (http .StatusBadRequest , codersdk.Response {
943- Message :"Task is not configured with a sidebar app." ,
944- })
945- }
946936
947- // Find the sidebar app details to get the URL and validate app health.
948- sidebarAppID := * build .AITaskSidebarAppID
949- agentID ,sidebarApp ,ok := func () (uuid.UUID , codersdk.WorkspaceApp ,bool ) {
950- for _ ,res := range build .Resources {
951- for _ ,agent := range res .Agents {
952- for _ ,app := range agent .Apps {
953- if app .ID == sidebarAppID {
954- return agent .ID ,app ,true
955- }
956- }
957- }
937+ var app * database.WorkspaceApp
938+ for _ ,a := range apps {
939+ if a .ID == task .WorkspaceAppID .UUID {
940+ app = & a
941+ break
958942}
959- return uuid .Nil , codersdk.WorkspaceApp {},false
960- }()
961- if ! ok {
962- return httperror .NewResponseError (http .StatusBadRequest , codersdk.Response {
963- Message :"Task sidebar app not found in latest build." ,
964- })
965- }
966-
967- // Return an informative error if the app isn't healthy rather than trying
968- // and failing.
969- switch sidebarApp .Health {
970- case codersdk .WorkspaceAppHealthDisabled :
971- // No health check, pass through.
972- case codersdk .WorkspaceAppHealthInitializing :
973- return httperror .NewResponseError (http .StatusServiceUnavailable , codersdk.Response {
974- Message :"Task sidebar app is initializing. Try again shortly." ,
975- })
976- case codersdk .WorkspaceAppHealthUnhealthy :
977- return httperror .NewResponseError (http .StatusServiceUnavailable , codersdk.Response {
978- Message :"Task sidebar app is unhealthy." ,
979- })
980943}
981944
982945// Build the direct app URL and dial the agent.
983- if sidebarApp .URL == "" {
946+ appURL := app .Url .String
947+ if appURL == "" {
984948return httperror .NewResponseError (http .StatusInternalServerError , codersdk.Response {
985949Message :"Task sidebar app URL is not configured." ,
986950})
987951}
988- parsedURL ,err := url .Parse (sidebarApp . URL )
952+ parsedURL ,err := url .Parse (appURL )
989953if err != nil {
990954return httperror .NewResponseError (http .StatusInternalServerError , codersdk.Response {
991955Message :"Internal error parsing task app URL." ,
@@ -1000,7 +964,7 @@ func (api *API) authAndDoWithTaskSidebarAppClient(
1000964
1001965dialCtx ,dialCancel := context .WithTimeout (ctx ,time .Second * 30 )
1002966defer dialCancel ()
1003- agentConn ,release ,err := api .agentProvider .AgentConn (dialCtx ,agentID )
967+ agentConn ,release ,err := api .agentProvider .AgentConn (dialCtx ,task . WorkspaceAgentID . UUID )
1004968if err != nil {
1005969return httperror .NewResponseError (http .StatusBadGateway , codersdk.Response {
1006970Message :"Failed to reach task app endpoint." ,