The chatd state machine only recognizes `waiting`, `running`, `error`,
`requires_action`, and `interrupting`. Remove the unused `pending`,
`paused`, and `completed` values from the database enum, backend, SDK,
frontend, generated queries, and API docs.
Migration `000543_chat_status_remove_unused` remaps existing `pending`
rows to `running`, remaps `paused` and `completed` rows to `waiting`,
drops the obsolete `idx_chats_pending` index, and recreates
`chats_expanded` around the enum swap. It also removes the dead
`AcquireChats` query and all remaining query literals for the deleted
statuses.
**NOTE**: The enum swap can break chat queries from older replicas
during a mixed-version rollout because they still reference
`'pending'::chat_status`. Chats are experimental, so this PR accepts
that limited rollout window instead of adding a two-release expand and
contract sequence.
> This PR was authored by Mux (AI agent) on Mike's behalf.
This PR removes the now-dead direct-routing code:
- Deletes the direct routing implementation.
- Collapses the resolvedModelRoute discriminated union into aiGatewayModelRoute.
- Removes the dead providerKeys cascade.
- Deletes the preferredShortTextCandidates quickgen function.
- Simplifies the advisor override error handling.
- Deprecates the AIGatewayRoutingEnabled deployment option. It is now a no-op so as to not break existing deployments on upgrade.
Once direct routing was gone, the AI Gateway became mandatory for chat, which surfaced gaps in how the product behaves with the gateway disabled:
- Exposes ai-gateway-enabled to the frontend via embedded page metadata.
- Disables the chat composer via the existing AgentSetupNotice when the gateway is disabled, for both new and existing chats.
- Fixes nil/typed-nil chatDaemon panics on startup and shutdown when gateway is disabled.
- Fixes chat WebSocket from retrying the still-gated stream endpoint forever when the gateway is disabled.
The provider type already lives authoritatively in ai_providers.type,
reachable on every active row through ai_provider_id, which the
chat_model_configs_ai_provider_required_when_active CHECK makes
mandatory. The stored provider string was a denormalized copy the system
kept in sync with a startup backfill and no longer needs.
Every surface now derives provider type from the linked ai_providers
row. Telemetry is the one exception: it keeps emitting provider, now
sourced from ai_providers.type via a JOIN, so the BigQuery column and the
Nexus dashboards that read it are unaffected. The experimental HTTP/SDK
response drops provider and makes ai_provider_id required, since those
endpoints return only active configs; consumers resolve provider type
from ai_provider_id and the AI providers listing.
This ships in a single release with no compatibility window: production
reads the table via SELECT *, so a pre-drop binary fails config reads the
moment the column is gone. Operators must scale to zero before upgrading,
and there is no rollback.
Closes CODAGT-599
- `site/src/pages/AISettingsPage/ProvidersPage/components/providerFormApiMap.ts`):
`providerFormValuesToCreate` returns `type: "bedrock"` when the form
type is `bedrock`, instead of `type: "anthropic"` with Bedrock settings.
- Updated `providerFormApiMap.test.ts` to expect `type:
"bedrock"` and `MockAIProviderBedrock` fixture in `entities.ts` to
`type: "bedrock"`. Added Bedrock fixtures to `ModelsPageView` stories
asserting the AWS Bedrock icon renders correctly.
Refs: [CODAGT-549](https://linear.app/codercom/issue/CODAGT-549/bedrock-models-use-anthropic-styling-and-icons)
> 🤖
Renames the `last_used_at` column to `last_heartbeat_at` in `ai_gateway_keys` table.
`ai_gateway_keys` table has not been released yet.
All references updated.
Adds AI budget and Budget type columns to the group members table, shown when
aibridge is enabled and the ai-gateway-cost-control experiment is on. A member's
spend, limit, and source come from an ai_cost_control object embedded in the
group members and groups responses, so no extra request is made.
- Add AI budget and Budget type columns, gated by the aibridge feature and the
ai-gateway-cost-control experiment
- Read ai_cost_control inline from the group and member lists instead of calling
a separate spend endpoint
- Share an AIBudgetUsage component (spend vs budget with severity colors) and an
InfoIconTooltip for the column headers
- When another group governs a member's budget, grey the spend and name that
group in a tooltip; otherwise render the spend (severity-colored) against a
white limit
- Resolve a member's effective group in the AI budget override dialog, marking
only the governing group "(default)" and none when no group governs them
- Defer the override's custom-budget error until the field is touched
Closes AIGOV-291
Adds an AI budget column to the organization Groups list showing each
group's current AI spend against its configured limit, or "unlimited"
when no limit is set.
- Column and its spend request are gated behind `aibridge` visibility
and the `ai-gateway-cost-control` experiment
- Shows loading placeholders while spend is fetched from
`/api/v2/organizations/{org}/groups/ai/spend`
- Spend severity thresholds extracted into shared `utils/budget.ts`:
warning at 85%, destructive at or above the limit
- Response type defined locally with a TODO to replace with the
generated type once the backend endpoint exists
Closes AIGOV-290
This PR makes the agent-pushed pinned snapshot
(`chat_context_resources`) the sole source of workspace context for
chats, completing the "Release 5" cleanup. It removes legacy mechanisms
now superseded by the snapshot that agents push over dRPC
(`PushContextState`) and refresh via `chat-context/refresh`.
Removed:
- **Live-read at turn time.** MCP tool discovery, skill live-body reads,
and the instruction/skill history fallback that dialed the workspace on
every turn.
- **Context injected as message history.** The
`persist_workspace_context` generation action and its decision-loop
guard.
- **The legacy write path.** `POST`/`DELETE
/api/v2/workspaceagents/me/experimental/chat-context`, the agentsdk
`AddChatContext`/`ClearChatContext` methods, and the CLI one-shot
writer.
- **The `chats.last_injected_context` column** and all of its plumbing
(migration `000529`, queries, `db2sdk`, `dbauthz`, audit table, and the
frontend `ContextUsageIndicator` fallback).
Subagent context inheritance no longer copies parent context messages;
children now hydrate the parent's pinned `chat_context_resources` on
create, which yields an identical pin for the same workspace and agent.
What stays (still served by the live agent connection, not the
snapshot): `read_skill_file` supporting-file reads, `read_skill`
supporting-file listing, and MCP tool execution.
> [!NOTE]
> Migration `000529` drops `chats.last_injected_context` and recreates
the `chats_expanded` view without it. The down migration restores both.
<details>
<summary>Decision log (D1-D5)</summary>
- **D1 (subagent inheritance):** Re-point inheritance from the legacy
message copy to a pinned hydrate. Children call
`hydrateChatContextOnCreate` instead of copying parent context messages.
- **D2 (`persist_workspace_context`):** Remove the generation action
entirely along with the decision-loop guard it existed to satisfy, since
context is never injected into history anymore.
- **D3 (legacy HTTP + CLI):** Remove the experimental `chat-context`
POST/DELETE endpoints, the agentsdk methods, and the CLI one-shot. The
dRPC push + `chat-context/refresh` replace them.
- **D4 (frontend fallback):** Remove the `last_injected_context`
fallback in `ContextUsageIndicator`; pinned `resources` are the sole
source.
- **D5 (sequencing):** Ship as a single PR rather than a stacked pair.
</details>
---
Coder Agents generated on behalf of @kylecarbs.
Adds the workspace-context indicator UI for agent chats, part of
breaking the "Workspace Context Sources for Coder Agents" RFC (#26466)
into small, reviewable PRs.
## What this adds
- **Pinned context popover**: the context-usage indicator now lists the
chat's pinned resources, instruction files, skills, and MCP servers with
their tools. Unusable resources (invalid skill, unreadable/oversize
file) are surfaced in an "Issues" section with their error rather than
dropped silently.
- **Drift and error states**: when the pinned context differs from the
agent's latest snapshot, the ring shows a warning marker and the popover
explains the drift. A snapshot-level error gets a distinct treatment.
- **Refresh context**: a button re-pins the chat to the agent's latest
snapshot via `PUT /api/experimental/chats/{id}/context`.
- **Live updates**: `context_dirty` watch events apply the lightweight
dirty flags across the cached chat lists and refetch the open chat so
the full pinned detail loads.
## Not included
The context **changes/diff** view (the "View changes" affordance and its
dialog) is intentionally deferred to a later split, so this PR contains
no diff rendering.
## Testing
- `pnpm lint` (types, biome, circular deps, React Compiler, knip)
- `pnpm test src/api/queries/chats.test.ts` (cache-merge unit tests,
including the new `context_dirty` cases)
- `pnpm test:storybook
src/pages/AgentsPage/components/ContextUsageIndicator.stories.tsx` (4
stories)
- `pnpm format`
<details>
<summary>Design notes</summary>
This is **Split 3** of #26466. Split sequence:
1. #26558 - prompt pin consumption (merged)
2. #26570 - `codersdk` context resource types (merged). During review
the type was renamed `ChatContextMCPTool` -> `ChatContextTool` and the
field `mcp_tools` -> `tools`; this PR consumes those merged names.
3. **This PR** - the UI.
4. CLI `coder exp chat context` source CRUD + `refresh` (next).
5. The context diff (`changes`, `ChatContextResourceChange`, the changes
dialog, and `buildContentPatch`) - last.
Key decisions:
- The backend already publishes `ChatWatchEventKindContextDirty` and
exposes the refresh endpoint (#26389). Watch/pubsub payloads stay
lightweight: they carry only the `dirty`/`dirty_since`/`error` flags and
omit `resources`. So `mergeWatchedChatSummary` merges (not replaces) the
cached context to preserve the pinned `resources` a single-chat GET
populated, and the `AgentsPage` watch handler refetches only the open
chat to pull the full pinned detail.
- The indicator prefers the chat's pinned `resources`; while they have
not loaded it falls back to the agent's `last_injected_context`,
skipping the empty context-file placeholder so it never renders a
nameless row.
- All diff/changes rendering is excluded here and lands in the final
split to keep this PR focused on the read-only pinned view and the
refresh action.
</details>
*This PR was created by Coder Agents on behalf of @kylecarbs.*
fixes#22420
ref DEVEX-369
ref DEVEX-269
The bug on `CreateWorkspacePage`, where clicking one external auth
provider login button disabled all providers' login buttons, was caused
by providers all sharing a single polling status (`"idle" | "polling" |
"abandoned"`) in the `useExternalAuth` hook.
## changes
- Instead of setting one status across all providers, the polling status
in `useExternalAuth` is now tracked for each provider in a record whose
keys are the providers' IDs.
- The biggest diff is a new Storybook file
CreateWorkspacePage.stories.tsx which reproduces the bug behavior from
the issue.
- Until now we've only had CreateWorkspacePageView.stories.tsx, which
isn't able to model the user interactions / API responses needed to
verify the bugfix. This file is unchanged.
- Also deletes `CreateWorkspacePage`'s `useExternalAuth` hook in favor
of the global `useExternalAuth` hook (see #26310)
(co-written with Coder Agents)
## Summary
Replaces 9 stale docs paths and 2 stale doc anchors in `site/src/`, and
adds a TS/TSX audit script (`site/scripts/audit-docs-paths.mjs`) plus
unit tests that scan the codebase for paths that resolve via
`coder.com/redirects.json`.
### Redirect-target updates (9)
Each of these paths' `/docs/...` source matches a Next.js redirect rule,
so requests today produce a 302 on `coder.com`. The audit script
identifies them by cross-referencing against `redirects.json`.
- Five update product-code references to `/ai-coder/ai-bridge` (renamed
to `/ai-coder/ai-gateway` in v2.33).
- One updates a commented-out reference to
`/templates#template-filtering` in `TemplatesFilter.tsx`.
- Three update notification-template mock data in
`testHelpers/entities.ts` that pointed at the renamed
`/docs/templates/schedule`.
### Anchor-only updates (2)
These paths are still live on `coder.com` (no redirect), but their
`#fragment` no longer matches a heading on the destination page.
Fragments are evaluated client-side and never sent to the server, so the
audit script does not catch them. Found and verified manually against
the current docs.
- `AuditFilter.tsx`: `/admin/security/audit-logs#filtering-logs` →
`/admin/security/audit-logs#how-to-filter-audit-logs`. The current
heading is `## How to Filter Audit Logs` in
[`docs/admin/security/audit-logs.md`](https://github.com/coder/coder/blob/main/docs/admin/security/audit-logs.md).
- `UserAuthSettingsPageView.tsx`: drops the stale `#openid-connect`
anchor; the path itself (`/admin/users/oidc-auth`) is unchanged. The
page H1 is now `# OpenID Connect`, so the bare path lands at the same
place the anchor used to.
None are user-visible label changes; only the doc target URLs change.
## Audit script
`site/scripts/audit-docs-paths.mjs` cross-references TS/TSX docs-URL
references against `coder.com/redirects.json` and reports anything that
resolves via a redirect (which means stale source). It catches four
forms:
- `docs("/...")` / `docs('/...')` / `` docs(`/...`) ``
- `` docs(`/.../${expr}/...`) `` (literal prefix, flagged as dynamic)
- `"https://coder.com/docs/..."` and other quoted forms
- `](https://coder.com/docs/...)` and `](/docs/...)` markdown-link forms
The full audit (26 findings: 9 in `coder/coder/site/`, 17 in
`coder/coder.com/src/`) lives in [DOCS-253 on
Linear](https://linear.app/codercom/issue/DOCS-253) rather than being
committed to the repo. Re-run locally with:
```bash
node site/scripts/audit-docs-paths.mjs \
--redirects=/path/to/coder.com/redirects.json \
--roots=/path/to/coder/site/src,/path/to/coder.com/src
```
Default output goes to `docs/.audit/redirects-audit-YYYY-MM-DD.md`,
which is gitignored.
## Tests
`site/scripts/audit-docs-paths.test.mjs` has 59 cases covering the four
regexes, `matchRedirect` (exact, `:path*`, `:slug(.*)`, miss),
`findMatchingRedirect`, `stripQueryAndFragment`, `literalPrefix`,
`extractReferences` end-to-end with line numbers and multi-line `docs()`
calls, `buildReport` (empty input, repo grouping and sort order,
fragment annotation, unclassified section), `walk` against a real temp
filesystem (recursion, extension filter, `SKIP_DIRS` pruning,
missing/file inputs, seeded results), and `runCli` (missing-root
warning, real-but-empty root). Run with `pnpm exec vitest run
scripts/audit-docs-paths.test.mjs --project=unit` from `site/`.
## Notes
- User-visible "AI Bridge" label text is not changed here. Renaming the
product surface from "AI Bridge" to "AI Gateway" is tracked separately
by the AI Governance team in AIGOV-233.
- A vitest assertion that no literal path in `site/src/` resolves via a
redirect will land in a follow-up PR (DOCS-257), so future drift fails
fast in CI.
- A generated `DocsPath` type for the `docs()` helper is planned in
DOCS-254.
- The `/docs/templates/schedule` drift in `entities.ts` also appears in
`coderd/notifications/testdata/*.golden` test fixtures and in historical
SQL migrations under `coderd/database/migrations/`. Those are tracked
under DOCS-256 (A2: non-TS audit) and not in scope here.
## Related work
- Linear: DOCS-253 (this PR), parent DOCS-209.
- Companion redirect rule on the coder.com side: coder/coder.com#826.
- Companion fixes for the 17 coder.com findings: coder/coder.com#876
(supersedes the closedcoder/coder.com#827, which was made redundant by
coder/coder.com#832).
<details>
<summary>Implementation plan (Linear DOCS-209)</summary>
| Phase | Scope | Linear | Status |
|---|---|---|---|
| D | Versioned redirect for `/docs/@v2.33.x/ai-coder/ai-bridge` in
`coder.com/redirects.json` | DOCS-255 | coder/coder.com#826 open |
| A1 | TS/TSX audit + autofix in `coder/coder/site/` | DOCS-253 | This
PR |
| A1 follow-up | Same autofix in `coder/coder.com/src/` (3 remaining
findings after coder/coder.com#832) | DOCS-281 | coder/coder.com#876
open |
| A2 | Non-TS audit in `coder/coder` (Go, comments, markdown) | DOCS-256
| Backlog |
| A3 | code-server audit | DOCS-252 | Backlog |
| B | vitest assertion against `redirects.json` | DOCS-257 | Blocked by
A1 |
| C | Generated `DocsPath` type | DOCS-254 | Blocked by B |
</details>
---
Generated by Coder Agent on behalf of @nickvigilante.
---------
Co-authored-by: Coder <coder@users.noreply.github.com>
There was inconsistency with what the form showed and what actually was
sent to the backend. I opted to make it so that explicitly blank values
are always sent rather than have blank values silently changing to the
default value.
Previously, there could be a gap where the web socket has been connected
and gets the initial message but the build parameters request was still
in flight. This caused two issues:
1. Because we only send initial parameters as a response to a message,
when the message comes first and the build parameters have not resolved
yet, we end up not sending the initial parameters, meaning the form
could be stale until the next edit the user makes.
2. And if the user does make an edit, once we get that response back we
would then send the initial parameters, essentially reverting back to
the initial state since the initial params do not include the user's
edits. So the user would need a second edit to finally sync up.
To resolve both issues, we ignore the web socket's initial message until
we get the build parameters, at which point we decide whether we can use
that initial message (when there are no build params) or if we need to
continue ignoring it and send the initial parameters to get the correct
state then finally render the form.
> Vibe-coded using Coder Agents, author with limited frontend knowledge,
manually tested.
Adds an `AI Gateway Keys` page under `Admin settings > AI > AI Gateway
Keys` for key management of keys used by standalone AI Gateway replicas
to authenticate into `coderd`.
The page is shown to users with `viewAIGatewayKeys` permission and a
Premium license with AI Gateway enabled.
Adds Storybook coverage.
---------
Co-authored-by: Jake Howell <jacob@coder.com>
Callers can now choose when to open and emit the initial message. This
will enable finer testing for some incoming bug fixes related to the
timing of dynamic parameter sockets and requests.
Add a callback to preserve the current behavior for existing tests and make
the transition easier. Future tests can omit the callback and emit the events
under whatever condition they need.
The only behavioral changes are:
- the web socket error test now emits a close error without first
opening to accurately simulate a failure to connect at all.
- add some missing `diagnostics` to some responses (just to be
thorough).
- change one of the IDs to match in two tests (for consistency).
## Summary
Removes the deprecated `/api/v2/aibridge/interceptions` endpoint and the
Request Logs frontend page, both replaced by the session-based view.
Closes https://linear.app/codercom/issue/AIGOV-266
Closes https://linear.app/codercom/issue/AIGOV-324
## Changes
### Backend
- Remove `GET /api/v2/aibridge/interceptions` HTTP handler and route
- Remove SDK types and client method (`AIBridgeInterception`,
`AIBridgeTokenUsage`, `AIBridgeUserPrompt`, `AIBridgeToolUsage`,
`AIBridgeListInterceptionsResponse`, `AIBridgeListInterceptionsFilter`)
- Remove SQL queries `CountAIBridgeInterceptions` and
`ListAIBridgeInterceptions`
- Remove `searchquery.AIBridgeInterceptions` parser
- Remove dbauthz wrappers, in-memory implementations, metrics, and mocks
for the interceptions list queries
- Remove the `coder aibridge interceptions list` CLI command and golden
files
- Regenerate API docs, swagger, mocks, and metrics
The `/models`, `/clients`, and `/sessions` endpoints stay; the sessions
list page still consumes all three.
### Frontend
- Delete the entire `RequestLogsPage/` directory (page, view, row,
filter, stories, tests)
- Remove the `/aibridge/request-logs` route and its lazy import
- Remove the `getAIBridgeInterceptions` API method,
`paginatedInterceptions` query, and mock interception entities
- `git mv` the shared filter and icon components used by the sessions
pages:
- `RequestLogsPage/RequestLogsFilter/{Client,Model,Provider}Filter.tsx`
→ `AIBridgePage/filters/`
- `RequestLogsPage/icons/AIBridge{Client,Model,Provider}Icon.tsx` →
`AIBridgePage/icons/`
- Drop the `getProviderIconName` hack and the duplicate `anthropic-neue`
icon case now that the FIXME no longer applies
## Commits
1. `refactor: remove interceptions API and request logs view` — the bulk
removal, with explicit renames for the shared filter/icon files.
2. `refactor(site/src/pages/AIBridgePage): drop getProviderIconName
hack` — cleanup of the FIXME that depended on RequestLogsPage existing.
> [!NOTE]
> Generated by Coder Agents on behalf of @dannykopping
Refs #25936.
Adds a configurable per-org default member role set. Unioned into each member's effective roles at read time.
<sub>with Coder Agents on behalf of @Emyrk.</sub>
This allows a Coder Agents chat to detach from its linked workspace
without deleting or changing the workspace, so a different workspace can
be linked later. It adds detach controls wherever the linked workspace
appears, including the workspace pill menu, fallback workspace badges,
and the workspace picker. The workspace selection state now updates
consistently across desktop and mobile.
Running workspace:
<img width="453" height="296" alt="image"
src="https://github.com/user-attachments/assets/ac5197a7-f0f4-4123-bbea-d3ddaca7a3e4"
/>
Stopped workspace:
<img width="389" height="203" alt="image"
src="https://github.com/user-attachments/assets/f5a8a90c-4bb0-405a-ade3-791146687b2d"
/>
Closes CODAGT-510
Copilot is the only AI provider type that could not be added through the `/ai/settings` UI. The aibridge runtime and the env-var seeding path already supported it, but the runtime CRUD API rejected `type=copilot` and the UI omitted it entirely. The root cause is that Copilot's auth model (a per-request GitHub OAuth token, with no pre-shared key) does not fit the credential-centric add-provider flow that every other provider uses.
## Backend
Allow `type=copilot` in `CreateAIProviderRequest.Validate()`, and reject `api_keys` for Copilot on both create (validation) and update (handler sentinel), mirroring the existing Bedrock guards. Copilot carries no stored credential.
## Frontend
Add Copilot to the provider type picker (with the `github-copilot.svg` icon) and give the form a credential-free branch: name, display name, and a free-text endpoint defaulting to `https://api.business.githubcopilot.com`, with copy explaining that authentication happens via the user's GitHub token at request time. Copilot maps to the distinct `copilot` wire type rather than collapsing to `openai`, and the edit flow recovers it correctly.
The endpoint stays required with a business-tier default; users on the individual or enterprise endpoints edit the field.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Adds the account settings UI for managing user secrets, including the
table, add/edit/delete dialog, Storybook coverage, and route/sidebar
entry.
Also updates the shared `FeatureStageBadge` beta variant with
dedicated beta styling, sizing, and label casing for the Secrets
page.
Stacked on #25370.
_This PR was generated by Coder Agents._
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.
Linear: [DEVEX-355](https://linear.app/coder/issue/DEVEX-355)
Fourth PR in a 5-PR stack splitting #25328. Wires the new `/ai/settings`
provider management UI.
- `AISettingsLayout` hosts the section under `/ai/settings` with a
sidebar outlet.
- `AISettingsSidebar(View)` shows a single "Providers" nav entry. The
remaining sidebar entries arrive with the broader AI settings section
reshuffle in the next PR.
- `ProvidersPage` lists configured AI providers via the queries added in
PR 2.
- `AddProviderPage` walks through provider-type selection and form
submission, with type-specific credential fields.
- `UpdateProviderPage` edits an existing provider with the same form
components.
- Storybook stories cover each view's loading, empty, populated, error,
and form states using the mock providers from `testHelpers/entities.ts`.
- `router.tsx` mounts the new `/ai/settings` layout with index, `add`,
and `:providerId` child routes. The `governance` child route lands
together with the dashboard navigation changes in the next PR.
Removes the now-unused knip ignore entries for
`src/api/queries/aiProviders.ts` and
`src/pages/AISettingsPage/ProvidersPage/components/addableProviderTypes.ts`,
and drops the matching `@lintignore` tags on `getProviderIcon` and
`MockAIProviders` since the pages and page stories now consume them.
<details>
<summary>Stack</summary>
1. #25579 jakehwll/DEVEX-355/01-primitives, primitives
2. #25580 jakehwll/DEVEX-355/02-api, API client and query layer
3. #25581 jakehwll/DEVEX-355/03-components, provider form components
4. **jakehwll/DEVEX-355/04-pages, pages and routes (this PR)**
5. jakehwll/DEVEX-355/05-section, section reshuffle
Replaces #25328 once the stack lands.
</details>
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.
Linear: [DEVEX-355](https://linear.app/coder/issue/DEVEX-355)
Third PR in a 5-PR stack splitting #25328. Adds the component-level
pieces used by the provider management pages landing in the next PR of
the stack.
- `ProviderForm` + `CredentialField` + a provider type-to-form mapping
for reading and editing the per-type credential and config fields, with
the form API map covered by unit tests.
- `ProviderIcon` resolves the bundled per-provider SVG icons and falls
back to a building glyph for unknown types.
- `ProviderRow` renders a single provider entry for the list view.
- `useUnsavedChangesPrompt` hook intercepts unsaved-form navigation.
- Storybook stories for `ProviderForm`, `ProviderIcon`, and
`ProviderRow` exercise each provider type and form state and consume the
mock providers from PR 2.
Stories now consume `MockAIProviderOpenAI` / `Anthropic` / `Bedrock` so
their per-mock `@lintignore` tags are removed; the `MockAIProviders`
aggregate and the `addableProviderTypes` / `aiProviders` query modules
keep their exclusions for the page stories in the next PR.
<details>
<summary>Stack</summary>
1. #25579 jakehwll/DEVEX-355/01-primitives, primitives
2. #25580 jakehwll/DEVEX-355/02-api, API client and query layer
3. **jakehwll/DEVEX-355/03-components, provider form components (this
PR)**
4. jakehwll/DEVEX-355/04-pages, pages and routes
5. jakehwll/DEVEX-355/05-section, section reshuffle
Replaces #25328 once the stack lands.
</details>
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.
Linear: [DEVEX-355](https://linear.app/coder/issue/DEVEX-355)
Second PR in a 5-PR stack splitting #25328. Adds the frontend layer that
talks to the existing `/api/v2/ai/providers` endpoints already shipped
on `main`:
- API client: `getAIProviders`, `getAIProvider`, `createAIProvider`,
`updateAIProvider`, `deleteAIProvider`.
- React Query wrappers in `queries/aiProviders.ts` with a shared key
helper and matching cache invalidations.
- Mock fixtures for OpenAI, Anthropic, and Bedrock providers in
`testHelpers/entities.ts` for stories and unit tests.
- `viewAnyAIProvider` registered in `permissions.json` so the existing
permissions hook can read it.
- `viewAnyAIProvider` added to `canViewDeploymentSettings` so admins who
can only manage providers still see the deployment dropdown.
The `aiProviders` query module and the per-provider mocks are
temporarily added to the `knip` ignore list / annotated with
`@lintignore`; the next PRs in the stack consume them and remove the
exclusions.
<details>
<summary>Stack</summary>
1. #25579 jakehwll/DEVEX-355/01-primitives, primitives
2. **jakehwll/DEVEX-355/02-api, API client and query layer (this PR)**
3. jakehwll/DEVEX-355/03-components, provider form components
4. jakehwll/DEVEX-355/04-pages, pages and routes
5. jakehwll/DEVEX-355/05-section, section reshuffle
Replaces #25328 once the stack lands.
</details>
Add frontend API methods, mocks, and form helpers for user secrets CRUD. The new client methods cover list, get, create, update, and delete requests, including URL encoding for secret names used in route paths.
Add user secret form utilities for create and update payload construction, required create field checks, and structured API validation error mapping back to form fields. User secret name validation now lives in codersdk with tests, and coderd returns field-level validation errors for create, update, and uniqueness conflicts so the frontend can show backend-owned validation results consistently.
Part 1: Backend portion of a change broken into 2 PRs.
Part 2: #25077
Adds three new UserAppearanceSettings fields (theme_mode, theme_light,
theme_dark) on top of the existing theme_preference and terminal_font.
Replaces GetUserThemePreference and GetUserTerminalFont with a single
GetUserAppearanceSettings aggregate query. The PUT handler is wrapped in
db.InTx so sync-mode's mode + slot writes can never half-apply.
Previously we only showed an error when the startup script failed. Now:
- Add a warning icon to each failed script.
- Sort the failed script tabs first.
- Modify agent health messages to pull the errors notices from the new
location instead of from the single agent lifecycle enum. This means
errors are shown for each script and the count is accurate.
Adds a `<Spinner />` next to the log count during agent startup.
Also, there was some complexity in sizing the spinner because `<Badge
/>` automatically sizes any `svg`s it contains to `size-icon-xs`. In
order to maintain the `svg` sizing inside existing Badges across the
site, this introduces a new `svgSize` prop that defaults to `xs`.
Existing consumers will still get `[&_svg]:size-icon-xs` regardless of
the Badge size, but can now be overridden to `sm` or `lg` (there is no
`md`).
Also also fixes a tiny spacing issue with the warning triangle
🕵️♂️
<img width="1203" height="624" alt="Screenshot 2026-04-29 at 3 24 35 PM"
src="https://github.com/user-attachments/assets/e4fc4a3a-e88f-4253-a697-195f8a347230"
/>
Remove the `ExperimentAgents` feature flag so the Agents feature is
always available without requiring `--experiments=agents`. The feature
is now in beta.
Existing deployments that still pass `--experiments=agents` will get a
harmless "ignoring unknown experiment" warning on startup.
### Changes
**Backend:**
- Remove `RequireExperimentWithDevBypass` middleware from chat and MCP
server routes
- Always include `AgentsAccessRole` in assignable site roles (later
refactored to org-scoped on main; rebase keeps that)
- Always set `AgentsTabVisible = true`, then drop the entire dead
`AgentsTabVisible` metadata pipeline (Go htmlState field,
populateHTMLState goroutine, HTML meta tag, useEmbeddedMetadata
registration, mock); no production consumer reads it. `AgentsNavItem`
already gates on `permissions.createChat`.
- Make `blob:` CSP `img-src` addition unconditional
- Remove `ExperimentAgents` constant, `DisplayName` case, and
`ExperimentsKnown` entry
**CLI:**
- Graduate the agents TUI from `coder exp agents` to `coder agents`
(moved from `AGPLExperimental()` to `CoreSubcommands()`)
- Drop the `agent` alias so it does not collide with the hidden
workspace-agent command
- Rename implementation files `cli/exp_agents_*.go` -> `cli/agents_*.go`
and internal identifiers (`expChatsTUIModel` -> `chatsTUIModel`,
`newExpChatsTUIModel` -> `newChatsTUIModel`, `setupExpAgentsBackend` ->
`setupAgentsBackend`, `startExpAgentsSession` -> `startAgentsSession`,
`expAgentsPtr` -> `agentsPtr`, `expAgentsSession` -> `agentsSession`,
`TestExpAgents*` -> `TestAgents*`). `expClient` (the
`*codersdk.ExperimentalClient` local) is kept; `coderd/exp_chats*.go`
and other still-experimental `cli/exp_*.go` commands are intentionally
untouched.
**Frontend:**
- Remove experiment check from `AgentsNavItem` - render when
`canCreateChat` is true
- Remove `agentsEnabled` experiment check from `WorkspacesPage`, then
gate `chatsByWorkspace` on `permissions.createChat` so users without
chat access don't trigger the per-page DB query (Copilot review
feedback)
- Add `FeatureStageBadge` (beta) next to the Coder logo in the Agents
sidebar (desktop + mobile)
**Docs:**
- Remove experiment flag setup instructions from `early-access.md` and
`getting-started.md` (and rename `early-access.md`'s "Enable Coder
Agents" heading to "Set up Coder Agents", since there is no enablement
step left)
- Update `chats-api.md` and `getting-started.md`'s Chats API note to say
"beta" instead of "experimental"
- `docs/manifest.json`: drop "experimental" from the Chats API sidebar
description
- `make gen` regenerated `docs/reference/cli/agents.md` and the CLI
index
- `scripts/check_emdash.sh`: exclude `cli/testdata/*.golden` and
`enterprise/cli/testdata/*.golden` from the new repo-wide emdash lint,
since serpent emits emdash borders in every generated `--help` golden
file
**Tests:**
- Remove `ExperimentAgents` setup from all test files (14 occurrences
across 7 files)
- Update stale "with the agents experiment" comments in
`coderd/x/chatd/integration_test.go` and `coderd/mcp_test.go`
<img width="1185" height="900" alt="image"
src="https://github.com/user-attachments/assets/b420bc8f-41d6-42c6-abd8-ad572533d651"
/>
> 🤖 Generated by Coder Agents
Adds a role selector to the create user form so admins can assign
site-level roles at creation time rather than navigating to the user
afterward.
The `POST /api/v2/users` endpoint now accepts an optional `roles` field,
wiring it through to the existing `RBACRoles` field on the internal
`CreateUserRequest`. No database changes are needed since roles are
already stored inline on the user row.
On the frontend, a `RoleSelector` component renders the assignable roles
as a scrollable multiselect checklist with the non-assignable Member
role pinned as a non-interactive footer. The selector appears once a
login type is chosen.
Also adds a `condensed` size (690px) to `Margins` between the existing
`small` (460px) and `medium` (1080px), and exposes a `size` prop on
`FullPageForm`. The create user form uses `condensed` to give the role
selector more breathing room. Also fixes `MockUserAdminRole` and
`MockTemplateAdminRole` in test helpers to use hyphenated names
(`user-admin`, `template-admin`) matching the canonical names in the Go
RBAC layer.
Fixes `sortRolesByAccessLevel` in `UserRoleCell` to sort unranked roles
(e.g. `member`) after all known roles. Previously, `indexOf` returned -1
for unknown names, placing them first; now they receive
`POSITIVE_INFINITY` as their rank.
🤖 Generated with [Claude Code](<https://claude.ai/claude-code>)
---
https://github.com/user-attachments/assets/75e7c8c5-d0d2-481d-86e8-1fcfb574517c
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Depends on #24642
Adds per-owner digest notifications onto the chat auto-archive
subsystem.
Each tick's archived rows are grouped by owner, the top 25 titles per
owner are rendered into a new `Chats Auto-Archived` notification
template, and any remainder surfaces as `and N more`. Each digest is
per-tick, so users with large amounts of purgeable data may get multiple
notifications in sequence (one per user per tick).
The template body branches on `retention_days`: when retention is
disabled (`retention_days=0`), users are told archived chats are kept
indefinitely rather than falsely claiming imminent deletion.
### Changes
- migration `000XXX_chat_auto_archive_notification_template` adds new
notification template
- `dbpurge`: threads `notifications.Enqueuer` through `New`; and
enqueues notification message.
- `cli/server.go`: passes `options.NotificationsEnqueuer` into
`dbpurge.New`.
- `coderd/notifications/events.go`: new `TemplateChatAutoArchiveDigest`
UUID.
- `coderd/inboxnotifications.go`: inbox registration.
- Docs: adds a `Notifications` section to `chat-auto-archive.md`.
> 🤖
Once a user has touched a field, it is better to leave it alone and display explicit validation errors over silently overwriting their inputs. Same for auto-filled values (whether from query parameters or a previous build).
The agents-access role previously granted chat permissions at user
scope, but chats are org-scoped objects. Rego skips user-level perms
when org_owner is set, making the grants invisible. Handler-level
band-aids used synthetic non-org-scoped objects as a workaround.
- Migrates agents-access from users.rbac_roles (site-level) to
organization_members.roles (org-scoped) via DB migration
- Redefines agents-access as a predefined org-scoped builtin role
alongside organization-admin, organization-auditor, etc., with
Member permissions granting chat create/read/update
- Excludes ResourceChat from OrgMemberPermissions so org membership
alone no longer grants chat access
- Fixes handler Authorize checks to use org-scoped objects with
semantically correct actions (ActionUpdate for message/tool operations)
- Grants org admins the ability to assign agents-access
Closes#24250
Fixes CODAGT-174
Note: this does not update the "Usage" endpoints. Tracked by CODAGT-161.
> 🤖
Previously, the sessions list sorted by `MIN(started_at)` across
interceptions, so sessions with old start times but recent activity
would sink to the bottom of the list regardless of how recently they
were used.
`ListAIBridgeSessions` now sorts by `COALESCE(MAX(prompt.created_at),
MIN(started_at)) DESC`, exposed as the non-nullable `last_active_at`
field. Sessions with prompts surface by last activity; sessions with no
prompts fall back to their start time.
The original implementation used two separate columns (`last_active_at`
as a nullable prompt timestamp and `sort_at` as the non-nullable cursor
key). This revision collapses them into a single `last_active_at` that
is always set — simplifying the SQL, the Go conversion, the API type,
and the frontend.
🤖 Generated with [Claude Code](https://claude.ai/claude-code)
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Two new columns added to aibridge_token_usages:
- cache_read_input_tokens (BIGINT, default 0)
- cache_write_input_tokens (BIGINT, default 0)
Migration backfills existing rows by extracting values from the metadata
JSONB column (cache_read_input, input_cached, prompt_cached for reads
(max value selected since only 1 should be set), cache_creation_input
for writes).
All references to data from metadata were updated to reference new
columns. No other changes then changing where data is extracted from.
Requires aibridge library version bump to include:
https://github.com/coder/aibridge/pull/229
Fixes: https://github.com/coder/aibridge/issues/150
## Description
Adds `provider_name` to aibridge interceptions to store the provider
instance name alongside the provider type. This allows distinguishing
between multiple instances of the same provider type (e.g. `copilot` vs
`copilot-business`).
## Changes
* Add `provider_name` column to `aibridge_interceptions` table with
backfill from `provider`.
* Add `provider_name` field to the proto `RecordInterceptionRequest`
message.
* Add `ProviderName` to the `codersdk.AIBridgeInterception` API
response.
_Disclaimer: initially produced by Claude Opus 4.6, modified and
reviewed by @ssncferreira ._
Replaces the generic red `ErrorAlert` ("Forbidden.") with a proactive
permission check and friendly info alert when a user lacks the
`agents-access` role.
- Add `createChat` permission check to `permissions.json` using
`owner_id: "me"`
- Handle `"me"` owner substitution in `renderPermissions` (SSR path)
- Pass `canCreateChat` from `useAuthenticated().permissions` into
`AgentCreateForm`
- Show `ChatAccessDeniedAlert` and disable input immediately (no need to
trigger a 403 first)
- Also catch 403 errors as a fallback in case permissions aren't yet
loaded
- Add `ForbiddenNoAgentsRole` Storybook story with `play` assertions
- Add `TestRenderPermissionsResolvesMe` Go test to pin the `"me"`
sentinel substitution
<details><summary>Implementation plan & decision log</summary>
- Uses the existing `permissions.json` + `checkAuthorization` system
rather than a separate API call
- `owner_id: "me"` is resolved to the actor's ID by both the auth-check
API endpoint and the SSR `renderPermissions` function
- Go test uses a real `rbac.StrictCachingAuthorizer` (not a mock) so it
verifies both the sentinel substitution and the RBAC role evaluation
end-to-end
- Alert follows the exact same `Alert` pattern as the 409 usage-limit
block
- Uses `severity="info"` and links to the getting-started docs Step 3
- Textarea is disabled proactively so the user never sees the scary
generic error
</details>
> 🤖 Created by a Coder Agent and will be reviewed by a human.