feat: add global chat workspace TTL setting (#23265)

- Add `agents_workspace_ttl` site config (default: whatever the template
says a.k.a. `0s`)
- Expose via GET/PUT `/api/experimental/chats/config/workspace-ttl`
- Chat tool reads setting and passes `TTLMillis` on workspace creation
- Existing autostop infrastructure handles the rest (zero changes to
LifecycleExecutor, CalculateAutostop, or activity bumping)
- ⚠️ Template-level `UserAutostopEnabled=false` overrides this global
default. Not touching this.
- Frontend: "Workspace Lifetime" control in /agents/settings Behavior
tab (admin-only)

> This PR was created with the help of Coder Agents, and has been
reviewed by several humans and robots. 🤖🤝🧑‍💻
This commit is contained in:
Cian Johnston
2026-03-20 17:38:39 +00:00
committed by GitHub
parent e388a88592
commit ff8dcca2c7
19 changed files with 789 additions and 1 deletions
+29
View File
@@ -2224,6 +2224,21 @@ func (mr *MockStoreMockRecorder) GetChatUsageLimitUserOverride(ctx, userID any)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatUsageLimitUserOverride", reflect.TypeOf((*MockStore)(nil).GetChatUsageLimitUserOverride), ctx, userID)
}
// GetChatWorkspaceTTL mocks base method.
func (m *MockStore) GetChatWorkspaceTTL(ctx context.Context) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetChatWorkspaceTTL", ctx)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetChatWorkspaceTTL indicates an expected call of GetChatWorkspaceTTL.
func (mr *MockStoreMockRecorder) GetChatWorkspaceTTL(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChatWorkspaceTTL", reflect.TypeOf((*MockStore)(nil).GetChatWorkspaceTTL), ctx)
}
// GetChats mocks base method.
func (m *MockStore) GetChats(ctx context.Context, arg database.GetChatsParams) ([]database.Chat, error) {
m.ctrl.T.Helper()
@@ -8909,6 +8924,20 @@ func (mr *MockStoreMockRecorder) UpsertChatUsageLimitUserOverride(ctx, arg any)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertChatUsageLimitUserOverride", reflect.TypeOf((*MockStore)(nil).UpsertChatUsageLimitUserOverride), ctx, arg)
}
// UpsertChatWorkspaceTTL mocks base method.
func (m *MockStore) UpsertChatWorkspaceTTL(ctx context.Context, workspaceTtl string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpsertChatWorkspaceTTL", ctx, workspaceTtl)
ret0, _ := ret[0].(error)
return ret0
}
// UpsertChatWorkspaceTTL indicates an expected call of UpsertChatWorkspaceTTL.
func (mr *MockStoreMockRecorder) UpsertChatWorkspaceTTL(ctx, workspaceTtl any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertChatWorkspaceTTL", reflect.TypeOf((*MockStore)(nil).UpsertChatWorkspaceTTL), ctx, workspaceTtl)
}
// UpsertConnectionLog mocks base method.
func (m *MockStore) UpsertConnectionLog(ctx context.Context, arg database.UpsertConnectionLogParams) (database.ConnectionLog, error) {
m.ctrl.T.Helper()