@@ -1964,18 +1964,41 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
19641964}
19651965
19661966appIDs := make ([]string ,0 )
1967+ agentIDByAppID := make (map [string ]uuid.UUID )
19671968agentTimeouts := make (map [time.Duration ]bool )// A set of agent timeouts.
19681969// This could be a bulk insert to improve performance.
19691970for _ ,protoResource := range jobType .WorkspaceBuild .Resources {
1970- for _ ,protoAgent := range protoResource .Agents {
1971+ for _ ,protoAgent := range protoResource .GetAgents () {
1972+ if protoAgent == nil {
1973+ continue
1974+ }
1975+ // By default InsertWorkspaceResource ignores the protoAgent.Id
1976+ // and generates a new one, but we will insert these using the
1977+ // InsertWorkspaceResourceWithAgentIDsFromProto option so that
1978+ // we can properly map agent IDs to app IDs. This is needed for
1979+ // task linking.
1980+ agentID := uuid .New ()
1981+ protoAgent .Id = agentID .String ()
1982+
19711983dur := time .Duration (protoAgent .GetConnectionTimeoutSeconds ())* time .Second
19721984agentTimeouts [dur ]= true
19731985for _ ,app := range protoAgent .GetApps () {
19741986appIDs = append (appIDs ,app .GetId ())
1987+ agentIDByAppID [app .GetId ()]= agentID
19751988}
19761989}
19771990
1978- err = InsertWorkspaceResource (ctx ,db ,job .ID ,workspaceBuild .Transition ,protoResource ,telemetrySnapshot )
1991+ err = InsertWorkspaceResource (
1992+ ctx ,
1993+ db ,
1994+ job .ID ,
1995+ workspaceBuild .Transition ,
1996+ protoResource ,
1997+ telemetrySnapshot ,
1998+ // Ensure that the agent IDs we set previously
1999+ // are written to the database.
2000+ InsertWorkspaceResourceWithAgentIDsFromProto (),
2001+ )
19792002if err != nil {
19802003return xerrors .Errorf ("insert provisioner job: %w" ,err )
19812004}
@@ -1987,6 +2010,7 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
19872010}
19882011
19892012var taskAppID uuid.NullUUID
2013+ var taskAgentID uuid.NullUUID
19902014var hasAITask bool
19912015var warnUnknownTaskAppID bool
19922016if tasks := jobType .WorkspaceBuild .GetAiTasks ();len (tasks )> 0 {
@@ -2014,6 +2038,9 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
20142038}
20152039
20162040taskAppID = uuid.NullUUID {UUID :id ,Valid :true }
2041+
2042+ agentID ,ok := agentIDByAppID [appID ]
2043+ taskAgentID = uuid.NullUUID {UUID :agentID ,Valid :ok }
20172044}
20182045
20192046// This is a hacky workaround for the issue with tasks 'disappearing' on stop:
@@ -2108,6 +2135,27 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro
21082135}
21092136}
21102137
2138+ if task ,err := db .GetTaskByWorkspaceID (ctx ,workspace .ID );err == nil {
2139+ // Irrespective of whether the agent or sidebar app is present,
2140+ // perform the upsert to ensure a link between the task and
2141+ // workspace build. Linking the task to the build is typically
2142+ // already established by wsbuilder.
2143+ _ ,err = db .UpsertTaskWorkspaceApp (
2144+ ctx ,
2145+ database.UpsertTaskWorkspaceAppParams {
2146+ TaskID :task .ID ,
2147+ WorkspaceBuildNumber :workspaceBuild .BuildNumber ,
2148+ WorkspaceAgentID :taskAgentID ,
2149+ WorkspaceAppID :taskAppID ,
2150+ },
2151+ )
2152+ if err != nil {
2153+ return xerrors .Errorf ("upsert task workspace app: %w" ,err )
2154+ }
2155+ }else if ! errors .Is (err ,sql .ErrNoRows ) {
2156+ return xerrors .Errorf ("get task by workspace id: %w" ,err )
2157+ }
2158+
21112159// Regardless of whether there is an AI task or not, update the field to indicate one way or the other since it
21122160// always defaults to nil. ONLY if has_ai_task=true MUST ai_task_sidebar_app_id be set.
21132161if err := db .UpdateWorkspaceBuildFlagsByID (ctx , database.UpdateWorkspaceBuildFlagsByIDParams {
@@ -2578,7 +2626,28 @@ func InsertWorkspacePresetAndParameters(ctx context.Context, db database.Store,
25782626return nil
25792627}
25802628
2581- func InsertWorkspaceResource (ctx context.Context ,db database.Store ,jobID uuid.UUID ,transition database.WorkspaceTransition ,protoResource * sdkproto.Resource ,snapshot * telemetry.Snapshot )error {
2629+ type insertWorkspaceResourceOptions struct {
2630+ useAgentIDsFromProto bool
2631+ }
2632+
2633+ // InsertWorkspaceResourceOption represents a functional option for
2634+ // InsertWorkspaceResource.
2635+ type InsertWorkspaceResourceOption func (* insertWorkspaceResourceOptions )
2636+
2637+ // InsertWorkspaceResourceWithAgentIDsFromProto allows inserting agents into the
2638+ // database using the agent IDs defined in the proto resource.
2639+ func InsertWorkspaceResourceWithAgentIDsFromProto ()InsertWorkspaceResourceOption {
2640+ return func (opts * insertWorkspaceResourceOptions ) {
2641+ opts .useAgentIDsFromProto = true
2642+ }
2643+ }
2644+
2645+ func InsertWorkspaceResource (ctx context.Context ,db database.Store ,jobID uuid.UUID ,transition database.WorkspaceTransition ,protoResource * sdkproto.Resource ,snapshot * telemetry.Snapshot ,opt ... InsertWorkspaceResourceOption )error {
2646+ opts := & insertWorkspaceResourceOptions {}
2647+ for _ ,o := range opt {
2648+ o (opts )
2649+ }
2650+
25822651resource ,err := db .InsertWorkspaceResource (ctx , database.InsertWorkspaceResourceParams {
25832652ID :uuid .New (),
25842653CreatedAt :dbtime .Now (),
@@ -2675,6 +2744,12 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
26752744}
26762745
26772746agentID := uuid .New ()
2747+ if opts .useAgentIDsFromProto {
2748+ agentID ,err = uuid .Parse (prAgent .Id )
2749+ if err != nil {
2750+ return xerrors .Errorf ("invalid agent ID format; must be uuid: %w" ,err )
2751+ }
2752+ }
26782753dbAgent ,err := db .InsertWorkspaceAgent (ctx , database.InsertWorkspaceAgentParams {
26792754ID :agentID ,
26802755ParentID : uuid.NullUUID {},