feat: add notification for task status (#19965)

## Description

Send a notification to the workspace owner when an AI task’s app state
becomes `Working` or `Idle`.
An AI task is identified by a workspace build with `HasAITask = true`
and `AITaskSidebarAppID` matching the agent app’s ID.

## Changes

* Add `TemplateTaskWorking` notification template.
* Add `TemplateTaskIdle` notification template.
* Add `GetLatestWorkspaceAppStatusesByAppID` SQL query to get the
workspace app statuses ordered by latest first.
* Update `PATCH /workspaceagents/me/app-status` to enqueue:
  * `TemplateTaskWorking` when state transitions to `working`
  * `TemplateTaskIdle` when state transitions to `idle`
* Notification labels include:
  * `task`: task initial prompt
  * `workspace`: workspace name
* Notification dedupe: include a minute-bucketed timestamp (UTC
truncated to the minute) in the enqueue data to allow identical content
to resend within the same day (but not more than once per minute).

Closes: https://github.com/coder/coder/issues/19776
This commit is contained in:
Susana Ferreira
2025-09-29 16:44:53 +01:00
committed by GitHub
parent abdea7213a
commit fdb0267e5d
18 changed files with 689 additions and 1 deletions
+15
View File
@@ -2054,6 +2054,21 @@ func (mr *MockStoreMockRecorder) GetLatestCryptoKeyByFeature(ctx, feature any) *
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestCryptoKeyByFeature", reflect.TypeOf((*MockStore)(nil).GetLatestCryptoKeyByFeature), ctx, feature)
}
// GetLatestWorkspaceAppStatusesByAppID mocks base method.
func (m *MockStore) GetLatestWorkspaceAppStatusesByAppID(ctx context.Context, appID uuid.UUID) ([]database.WorkspaceAppStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetLatestWorkspaceAppStatusesByAppID", ctx, appID)
ret0, _ := ret[0].([]database.WorkspaceAppStatus)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetLatestWorkspaceAppStatusesByAppID indicates an expected call of GetLatestWorkspaceAppStatusesByAppID.
func (mr *MockStoreMockRecorder) GetLatestWorkspaceAppStatusesByAppID(ctx, appID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestWorkspaceAppStatusesByAppID", reflect.TypeOf((*MockStore)(nil).GetLatestWorkspaceAppStatusesByAppID), ctx, appID)
}
// GetLatestWorkspaceAppStatusesByWorkspaceIDs mocks base method.
func (m *MockStore) GetLatestWorkspaceAppStatusesByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceAppStatus, error) {
m.ctrl.T.Helper()