mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(coderd): bump workspace deadline on AI agent activity (#21584)
AI agents report status via patchWorkspaceAgentAppStatus, but this wasn't extending workspace deadlines. This prevented proper task auto-pause behavior, causing tasks to pause mid-execution when there were no human connections. Now we call ActivityBumpWorkspace when agents report status, using the same logic as SSH/IDE connections. We bump when transitioning to or from the working state. Closes coder/internal#1251
This commit is contained in:
@@ -425,6 +425,174 @@ func TestWorkspaceAgentAppStatus(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspaceAgentAppStatus_ActivityBump(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
prevState *codersdk.WorkspaceAppStatusState // nil means no previous state
|
||||
newState codersdk.WorkspaceAppStatusState
|
||||
shouldBump bool
|
||||
}{
|
||||
{
|
||||
name: "FirstStatusBumps",
|
||||
prevState: nil,
|
||||
newState: codersdk.WorkspaceAppStatusStateWorking,
|
||||
shouldBump: true,
|
||||
},
|
||||
{
|
||||
name: "WorkingToIdleBumps",
|
||||
prevState: ptr.Ref(codersdk.WorkspaceAppStatusStateWorking),
|
||||
newState: codersdk.WorkspaceAppStatusStateIdle,
|
||||
shouldBump: true,
|
||||
},
|
||||
{
|
||||
name: "WorkingToCompleteBumps",
|
||||
prevState: ptr.Ref(codersdk.WorkspaceAppStatusStateWorking),
|
||||
newState: codersdk.WorkspaceAppStatusStateComplete,
|
||||
shouldBump: true,
|
||||
},
|
||||
{
|
||||
name: "CompleteToIdleNoBump",
|
||||
prevState: ptr.Ref(codersdk.WorkspaceAppStatusStateComplete),
|
||||
newState: codersdk.WorkspaceAppStatusStateIdle,
|
||||
shouldBump: false,
|
||||
},
|
||||
{
|
||||
name: "CompleteToCompleteNoBump",
|
||||
prevState: ptr.Ref(codersdk.WorkspaceAppStatusStateComplete),
|
||||
newState: codersdk.WorkspaceAppStatusStateComplete,
|
||||
shouldBump: false,
|
||||
},
|
||||
{
|
||||
name: "FailureToIdleNoBump",
|
||||
prevState: ptr.Ref(codersdk.WorkspaceAppStatusStateFailure),
|
||||
newState: codersdk.WorkspaceAppStatusStateIdle,
|
||||
shouldBump: false,
|
||||
},
|
||||
{
|
||||
name: "FailureToFailureNoBump",
|
||||
prevState: ptr.Ref(codersdk.WorkspaceAppStatusStateFailure),
|
||||
newState: codersdk.WorkspaceAppStatusStateFailure,
|
||||
shouldBump: false,
|
||||
},
|
||||
{
|
||||
name: "CompleteToWorkingBumps",
|
||||
prevState: ptr.Ref(codersdk.WorkspaceAppStatusStateComplete),
|
||||
newState: codersdk.WorkspaceAppStatusStateWorking,
|
||||
shouldBump: true,
|
||||
},
|
||||
{
|
||||
name: "FailureToCompleteNoBump",
|
||||
prevState: ptr.Ref(codersdk.WorkspaceAppStatusStateFailure),
|
||||
newState: codersdk.WorkspaceAppStatusStateComplete,
|
||||
shouldBump: false,
|
||||
},
|
||||
{
|
||||
name: "WorkingToFailureBumps",
|
||||
prevState: ptr.Ref(codersdk.WorkspaceAppStatusStateWorking),
|
||||
newState: codersdk.WorkspaceAppStatusStateFailure,
|
||||
shouldBump: true,
|
||||
},
|
||||
{
|
||||
name: "IdleToIdleNoBump",
|
||||
prevState: ptr.Ref(codersdk.WorkspaceAppStatusStateIdle),
|
||||
newState: codersdk.WorkspaceAppStatusStateIdle,
|
||||
shouldBump: false,
|
||||
},
|
||||
{
|
||||
name: "IdleToWorkingBumps",
|
||||
prevState: ptr.Ref(codersdk.WorkspaceAppStatusStateIdle),
|
||||
newState: codersdk.WorkspaceAppStatusStateWorking,
|
||||
shouldBump: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create workspace with agent and app.
|
||||
r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
|
||||
OrganizationID: user.OrganizationID,
|
||||
OwnerID: user.UserID,
|
||||
}).WithAgent(func(a []*proto.Agent) []*proto.Agent {
|
||||
a[0].Apps = []*proto.App{{Slug: "test-app"}}
|
||||
return a
|
||||
}).Do()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
|
||||
// Configure template with activity_bump to enable deadline bumping.
|
||||
_, err := client.UpdateTemplateMeta(ctx, r.Template.ID, codersdk.UpdateTemplateMeta{
|
||||
ActivityBumpMillis: time.Hour.Milliseconds(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set the workspace build deadline to the past to ensure the 5%
|
||||
// threshold is met for activity bumping.
|
||||
pastDeadline := dbtime.Now().Add(-30 * time.Minute)
|
||||
err = db.UpdateWorkspaceBuildDeadlineByID(dbauthz.AsSystemRestricted(ctx), database.UpdateWorkspaceBuildDeadlineByIDParams{
|
||||
ID: r.Build.ID,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
Deadline: pastDeadline,
|
||||
MaxDeadline: time.Time{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
agentClient := agentsdk.New(client.URL, agentsdk.WithFixedToken(r.AgentToken))
|
||||
|
||||
// If there's a previous state, report it first.
|
||||
if tt.prevState != nil {
|
||||
err := agentClient.PatchAppStatus(ctx, agentsdk.PatchAppStatus{
|
||||
AppSlug: "test-app",
|
||||
State: *tt.prevState,
|
||||
Message: "previous state",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Reset deadline to past again to meet 5% threshold for next bump.
|
||||
err = db.UpdateWorkspaceBuildDeadlineByID(dbauthz.AsSystemRestricted(ctx), database.UpdateWorkspaceBuildDeadlineByIDParams{
|
||||
ID: r.Build.ID,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
Deadline: pastDeadline,
|
||||
MaxDeadline: time.Time{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Get the deadline before the new status report.
|
||||
beforeBuild, err := db.GetWorkspaceBuildByID(dbauthz.AsSystemRestricted(ctx), r.Build.ID)
|
||||
require.NoError(t, err)
|
||||
beforeDeadline := beforeBuild.Deadline
|
||||
|
||||
// Report the new state.
|
||||
err = agentClient.PatchAppStatus(ctx, agentsdk.PatchAppStatus{
|
||||
AppSlug: "test-app",
|
||||
State: tt.newState,
|
||||
Message: "new state",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check if deadline changed.
|
||||
afterBuild, err := db.GetWorkspaceBuildByID(dbauthz.AsSystemRestricted(ctx), r.Build.ID)
|
||||
require.NoError(t, err)
|
||||
afterDeadline := afterBuild.Deadline
|
||||
|
||||
didBump := afterDeadline.After(beforeDeadline)
|
||||
if tt.shouldBump {
|
||||
require.True(t, didBump, "wanted deadline to bump but it didn't")
|
||||
} else {
|
||||
require.False(t, didBump, "wanted deadline not to bump but it did")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkspaceAgentConnectRPC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user