mirror of
https://github.com/coder/coder.git
synced 2026-09-21 20:51:01 +08:00
34e95c46bff797e4bbd9e87991b44a9dc73408cc
697
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
affeeaf9c8 |
feat: extend agent chat MCP tools for remote UAT evidence loops (#28233)
Extends the Agent-chat MCP tools so an unattended UAT evidence loop can
fetch artifacts, monitor long runs, and find prior runs without burning
model context.
## Backend
- New `chat_files_token` crypto key feature (migration 000571) with
rotator support and a dedicated signing keycache on coderd.
- `POST /api/experimental/chats/files/{file}/download-url`
(authenticated) mints a short-lived (5 min) signed URL and returns it
with `sha256`, `size_bytes`, `name`, `mime_type`, and `expires_at`.
- `GET /api/experimental/chats/files/{file}/download?token=` (no session
token) redeems the signed URL: verifies the JWS, requires the token's
`file_id` to match the path, and re-checks the minting user's RBAC
access live at redemption. Clients can `curl -o` artifacts with zero
credentials in the URL consumer.
- `ChatFileMetadata` gains `size_bytes` (via `octet_length`, no bytes
fetched).
## MCP tools (`codersdk/toolsdk`)
- `coder_download_chat_file`: by `file_id` or `chat_id`+`file_name`;
returns the signed URL plus checksum and size instead of base64.
- `coder_await_chat`: blocks (bounded `wait_secs`, 1-120) until a chat
leaves `running`/`interrupting`, using the existing watch stream with
subscribe-before-read.
- `coder_list_chats`: label, query, and limit filtering; chat
projections now include labels.
- `coder_get_chat_messages`: `after_id` forward cursor with
`next_after_id` (exact incremental reads), plus per-message `files`
metadata so artifact-bearing messages are identifiable.
- `coder_get_chat`: file listings now include `size_bytes` and
`created_at`.
- `coder_list_templates`: exposes `agents_allowed` for pre-flight
checks.
## Testing
- coderd: mint/redeem happy path with an unauthenticated client,
expired/tampered/file-mismatched tokens, auth still required on the
plain file endpoint, non-owner mint rejection.
- toolsdk: harness + integration coverage for all new/changed tools,
including signed-URL redemption with checksum verification,
forward-cursor exactness, await transition/timeout paths, and label
filtering.
- Remote dogfood UAT (dev.coder.com Coder Agent) passed all six
acceptance scenarios end to end over both MCP transports.
Note: `go test ./codersdk/toolsdk/` has a pre-existing goleak flake on
main (leaked `agentssh` non-PTY session goroutines from SSH exec tests;
reproduced 3/3 on clean `b4971bc49f1`). It is unrelated to this diff.
> Mux acted on Mike's behalf to create this PR.
<!-- mux-attribution: model=claude-sonnet-4-6 thinking=high -->
|
||
|
|
119f2b1dd9 |
feat: limit concurrent chat agents with pooled admission (#27902)
Limits concurrent chat generation on capped deployments to 5 root chats and 10 delegated subagent chats. The pools are deployment-wide and independent, so delegated work can continue while root capacity is full. The default caps live in AGPL code. Enterprise contributes only a licensing unlock, so unlicensed deployments stay capped and cannot fail open. Licensed deployments are uncapped while Agent Hours usage stays below an explicit hard limit. Deployments without a hard limit remain uncapped, and reaching the Agent Hours allocation only triggers warnings. Admission happens before a worker takes chat ownership. Capped deployments serialize admission across replicas with a transaction-scoped advisory lock and derive active and queued state from current ownership plus fresh runner heartbeats, rather than persisted queue markers or per-replica state. The acquisition query returns a bounded, pool-interleaved candidate set instead of ranking the whole backlog; a migration replaces the acquisition index with a pool-aware one. Refused chats stay running but unowned, and interrupt requests bypass admission so users can stop queued or over-cap chats. The single-chat API derives `queued_for_capacity` from live pool state; list endpoints do not report it. The UI polls that value every 5 seconds while a chat is running and shows a callout when the chat is waiting for capacity. Updates the administrator documentation and deployment-wide Prometheus gauges for active and queued agents. Replica-level values must be aggregated with `max`, not `sum`. > Mux updated this PR on Mike's behalf. |
||
|
|
1aa3553b52 |
fix(coderd): set Cache-Control: no-store on OAuth2 responses (#28143)
No response from the `/oauth2` route tree set `Cache-Control` at all, so
an intermediary cache or customer-operated reverse proxy was free to
apply a heuristic freshness lifetime to a response carrying a live
credential. RFC 6749 §5.1 and OAuth 2.1 §3.2.3 both make an affirmative
`no-store` directive a MUST for the authorization server.
Adds `httpmw.NoStore`, mounted on the `/oauth2` and
`/api/v2/oauth2-provider` trees, setting `Cache-Control: no-store` and
`Pragma: no-cache` on every response from them. OAuth 2.1 drops `Pragma`
because RFC 9111 §5.4 deprecates it as a request-only field, so sending
both is conformant under either reading. Not operator-configurable,
since both specs say MUST.
## Scope
- **Both trees, not just `POST /oauth2/tokens`.** The mount is one line
either way, and the wider scope also covers DCR registration, client
configuration read and update, the authorize 302 whose `Location` query
carries the code, and `POST /oauth2-provider/apps/{app}/secrets`, which
returns a plaintext client secret. A route added later inherits the
headers, which matters for PLAT-449.
- **A middleware, not a hook in `httpapi.Write`.** Three write paths
never call it: `POST /oauth2/revoke` and `DELETE
/oauth2/clients/{client_id}` write a bare status, and
`writeOAuth2RegistrationError` encodes its own JSON.
- **`/.well-known/*` deliberately excluded.** Public discovery metadata,
and RFC 9728 §5 asks for the opposite treatment. Assertions pin the
exclusion so a later hoist onto a higher router fails CI.
- **Session-credential routes left alone.** `/users/login`,
`/users/otp/change-password`, and `/users/{user}/keys/*` have the same
gap, but PLAT-448 is scoped to OAuth2 and reaching into session auth
changes the risk profile.
Every credential-returning route here is a `POST`, and RFC 9111 §3 bars
heuristic caching of `POST` responses, so this is defense-in-depth
against a non-conformant intermediary rather than a live caching bug.
Both specs say MUST regardless of what caches would actually do.
## Note for PLAT-498
`DELETE /oauth2/tokens` now carries `no-store` and is wrapped in
`apiKeyMiddleware`, which is mounted inside the `/oauth2` tree and
therefore runs after this middleware. It is the one route where both can
write `Cache-Control`, and PLAT-498's write must not replace `no-store`
with something weaker such as `private`. `POST /oauth2/tokens` cannot
overlap, since it deliberately has no `apiKeyMiddleware`.
## Two assumptions testing corrected
- `GET /oauth2/does-not-exist` returns **200**, not 404. Chi runs the
subrouter's middleware chain for unmatched paths, so both headers are
present, but the request falls through to the root router's SPA handler.
The test asserts the headers and deliberately not the status.
- The experiment-disabled case is unreachable from a test binary, since
`RequireExperimentWithDevBypass` short-circuits on `buildinfo.IsDev()`.
A unit test covers the consequence against the `RequireExperiment` it
delegates to.
No schema, `codersdk`, or serpent option changes, so `make gen` produces
no diff. Rollback is a revert.
Refs PLAT-448
|
||
|
|
37b3f11243 | fix(coderd): block SSRF in MCP OAuth2 discovery and client registration (#27989) | ||
|
|
d2f9280138 |
chore: remove legacy chat template allowlist (#27515)
Relates to CODAGT-713 Depends on #27514 Removes the legacy deployment-wide allowlist now that the API and frontend use per-template `agents_allowed`: the experimental `/template-allowlist` routes, SDK methods and generated types, site config queries, frontend bindings, and the now-unused `xjson` utility. Migration `000563` deletes the obsolete `agents_template_allowlist` value. It's irreversible for deployments that configured an allowlist, which I think is fine, since `000562` already drops `agents_allowed` on the way down, and this release ships `000548` and `000555` with the same property. Two side effects of the model change worth writing down, both from #27514 rather than here. The value used to need `ActionRead` on `ResourceDeploymentConfig` to read and deployment config update to write. `AgentsAllowed` is now a plain field on the template response, readable by anyone who can read the template, and it's set with a template update, so org admins manage it themselves. That's the delegation we wanted, and it's tracked in the audit log. The rest of the stack adds `--agents-allowed` to the CLI and updates the platform controls docs. |
||
|
|
4b9880afa6 |
feat: add --chat-hook-allow-insecure to allow plain HTTP chat hook URLs (#27896)
Adds a hidden `--chat-hook-allow-insecure` / `CODER_CHAT_HOOK_ALLOW_INSECURE` deployment option (default `false`) that allows the chat lifecycle hook URL to use plain HTTP for any host. The HTTPS requirement is enforced at two points, and the flag relaxes both: `DeploymentValues.Validate()` rejects `http` hook URLs at startup, and the hook dispatcher's `validateHookURL` allows `http` only for loopback hosts. With the flag set, any-host `http` is accepted; the host, fragment/userinfo, secret, and timeout checks are unchanged, and non-http(s) schemes still fail. This removes the need for an HTTPS reverse proxy when testing a hook consumer on a trusted network. Following security review feedback, the flag description and docs state that plain HTTP lets an on-path attacker forge hook responses (which control agent execution), and `coder server` logs a startup warning (with a redacted hook URL) when hooks run over plain HTTP. Docs, generated API types, and the server config golden are updated accordingly. > Mux acted on Mike's behalf to create this PR. |
||
|
|
6b8f820493 |
feat: remove native chat cost tracking in favor of AI Gateway cost data (#27330)
## Stack Context This stack makes AI Gateway data and budgets the source of truth for AI spend controls. 1. Re-back the per-chat cost endpoint with AI Gateway data (#27328, merged). 2. Remove native chat usage limits (#27329, merged). 3. **This PR, now based on `main`:** remove native chat cost tracking and its dedicated admin UI. ## Summary Removes native per-message price calculation, model pricing fields, cost persistence, aggregate cost queries, and admin cost API types. It also deletes the Analytics and Spend pages plus their legacy redirects. The AI Gateway-backed per-chat cost row and compact budget indicators remain. The spend documentation is renamed to `spend-management.md` and updated for the remaining surfaces, group budget APIs, CSV export, upgrade handling for native pricing and cost history, and the absence of a deployment-wide spend dashboard. The per-chat cost API documents that data follows AI Gateway retention and reports zero after all matching requests are purged. No schema is dropped in this release. `chat_messages.total_cost_micros` remains nullable and unwritten so replicas from the previous release can continue inserting messages during rolling upgrades. #27600 tracks removal after the compatibility window. > Mux prepared this PR on Mike's behalf. |
||
|
|
f0e6ac64b3 |
feat: remove native chat usage limits in favor of AI Gateway budgets (#27329)
## Stack Context This stack makes AI Gateway data and budgets the source of truth for AI spend controls. 1. Re-back the per-chat cost endpoint with AI Gateway data (#27328, merged). 2. **This PR:** remove native chat usage limits. 3. Remove native chat cost tracking and its dedicated admin UI (#27330). ## Summary Removes the native usage-limit API, SDK types, SQL, and chat enforcement for deployment, user, and group chat limits. Compact AI Gateway budget indicators remain in the Agents sidebar, user menu, and group settings. Gateway budget rejections and provider quota failures continue to classify as usage-limit errors, including a 409 response for synchronous title generation. Budget-period labels now use the API's UTC boundaries, so users see the same dates in every browser timezone. The documentation explains the AI Gateway replacement, its licensing requirements, and the differences from native limits. No schema is dropped in this release. The usage-limit table, index, user and group columns, constraints, audit mappings, and generated scan fields remain for mixed-version rolling upgrades. #27600 tracks their removal after the compatibility window. ## Breaking change Native day, week, and month chat spend limits are removed and are not migrated. AI Gateway budgets are month-based, group-scoped with per-user overrides, and require the AI Gateway entitlement. Deployments without that entitlement no longer have chat spend enforcement. > Mux prepared this PR on Mike's behalf. |
||
|
|
52423eb87b |
feat: promote MinimumImplicitMember experiment to GA (#27472)
Promotes the `minimum-implicit-member` experiment to GA and removes it.
## What changes
- The `minimum-implicit-member` experiment constant, its
`RoleOptions.MinimumImplicitMember` toggle, and the global
`rbac.MinimumImplicitMember()` accessor are deleted. The minimal-member
behavior is now the only behavior: `organization-member` and
`organization-service-account` carry only the floor (read-self records,
notifications, and similar) and grant **no workspace permissions**.
Workspace access lives exclusively on the
`organization-workspace-access` role.
- The experiment gate on customizing `default_org_member_roles` (`PATCH
/organizations/{org}`) is removed; the built-in-roles-only validation
remains.
- The dashboard's Default Roles section and the implied-roles display on
the members page are no longer experiment-gated.
- Admin docs: new "Default member roles" section in
`docs/admin/users/organizations.md`, cross-linked from
`groups-roles.md`.
## Why this is safe for existing deployments
Migration `000516` (shipped earlier) backfilled
`default_org_member_roles` with `['organization-workspace-access']` on
every organization. Members therefore keep exactly the effective
permissions they had with the experiment off; the workspace elevation
flows through the default role instead of being baked into
`organization-member`.
**Rollback caveat:** rolling back past this release restores the bundled
elevation, silently re-granting workspace access to members of
organizations that cleared their default roles.
## Review
Deep-review R1 findings are addressed in `chore: address deep-review
findings` (copy fixes, read-only Default Roles for viewers, removable
overlapping explicit grants, RBAC prose restoration, test
de-tautologizing, docs). Point-by-point disposition is in the PR
comments.
---
Generated by Coder Agents on behalf of @Emyrk.
|
||
|
|
7bd9f5ec93 |
fix: correct authorization header spelling in api docs (#27721)
Corrects the misspelled `Authorizaiton` Swagger header name to `Authorization` in the source annotation and checked-in generated API documentation. This prevents generated API specs and SCIM examples from documenting the wrong HTTP header name. |
||
|
|
c17bed25e0 |
feat: wire chat lifecycle hooks into chatd (#27429)
Wires chat lifecycle hooks into chatd, gated by the `agent-lifecycle-hooks` experiment. Part of the lifecycle hooks stack (#27401, #27428, #27430). See `docs/admin/setup/chat-lifecycle-hooks.md` for the consumer-facing contract. ## Summary When a hook URL is configured, chatd dispatches `session_start`, `user_prompt_submit`, `pre_tool_use`, `post_tool_use`, `pre_compact`, `post_compact`, and `stop` events to the consumer and applies its responses. ## Design - **Stateless**: Coder stores no hook dispatch or decision state. Delivery is at least once; consumers deduplicate on stable payload identifiers (chat ID, event type, tool-use ID) and answer duplicates with the same decision. - **Admission-time prompt effects**: `user_prompt_submit` dispatches exactly once per submission (create, send, queue, edit, subagent spawn) and folds its effects into the stored prompt as typed message parts: original-or-overridden user parts, then model-only `hook-context`, then a user-visible `hook-notice`. Hook context is stripped from every client-facing conversion; hook notices are excluded from model prompts. The server rejects hook parts in client-submitted content. - **Tool gating**: `pre_tool_use` allow can override tool input; deny becomes a synthetic denied tool result, with any returned model context persisted as a model-only transcript row so it never reaches clients. The denial text identifies an external policy (the deployment's lifecycle hook) as the source and marks the decision as persistent, so the model explains the denial instead of retrying it or misreporting it as an infrastructure failure. - **Fail closed**: a dispatch failure rejects the triggering request or moves the chat to the error state in the same transaction as the affected step, so a runnable state is never published with unapproved content. - **Admission before persistence**: `pre_tool_use` is dispatched for the calls the model produced, before the assistant message is stored. See "Staged tool admission" below. - **Fresh dispatch per tool call**: every non-provider-executed tool call is decided by its own `pre_tool_use` dispatch; Coder never reuses an earlier decision on the consumer's behalf. Retries re-dispatch the same logical event. ## Structure All hook dispatch flows through one seam: entry points build a `chathooks.Chat` (chat identity) and a `chathooks.Message` (event details) and call `Trigger.Trigger`, the only component that talks to the dispatcher. The integration lives in the `coderd/x/chatd/chathooks` subpackage, split by responsibility: - `trigger.go`: the trigger seam; builds the wire envelope per event, normalizes deny into a typed error, and holds the package's single enabled-check. - `effects.go`: pure conversion of hook results into transcript rows and prompt parts. - `errors.go`: failure classification (dispatch error messages, denial mapping, tool-result dispatch-failure scanning). - `tooluse.go`: the tool-call gate (`pre_tool_use` preflight, `post_tool_use` payloads, applying admitted input to the step). Server-bound glue stays in `coderd/x/chatd/hook_server.go`: the chat-parking dispatch error handlers, the step-commit row insertion wrappers, and the dynamic post-tool-use state loader, which depends on chatd validation types. This PR adopts the `codersdk/x/agenthooks` and `coderd/x/agenthooks/dispatch` import paths introduced at the tip of #27401; intermediate commits still reference the pre-move paths and are not individually buildable. ## Staged tool admission `pre_tool_use` originally ran at tool execution time, which is after the assistant message carrying the tool call was already committed. An `input_override` therefore had to rewrite stored message content in place. @hugodutka pointed out that chatd treats message content as immutable, and that the rewrite was a shortcut rather than a requirement. It was also a correctness problem in its own right: the rewrite only updated the database, so the transcript could show one input while a different one had executed. The hook now runs before the step is persisted: ```text provider stream ends (tool calls complete, in memory) -> pre_tool_use dispatch per call -> ONE transaction: assistant row with admitted inputs, synthetic denials, hook rows -> execute ``` The step is inserted once, carrying the input the tool runs with. `UpdateChatMessageContentByID` and `Tx.UpdateMessageContent` are deleted from #27428, so message content stays immutable. Two consequences, both intentional: - **Clients converge rather than wait.** Tool-call parts still stream live, so a rewritten call briefly shows the model's proposed input before the committed message replaces it. The chat store already clears stream state when an assistant message arrives, so the stored input wins with no frontend change and no added latency before tool cards appear. - **A call already in history was already admitted.** Execution consumes the stored input instead of dispatching a second decision, which keeps one dispatch and one set of hook effects per call. A consumer policy change between admission and execution applies to later calls, not to calls already admitted. The per-chat debug endpoint still records the provider's original tool input. Its purpose is to report provider behavior, and it requires an explicit per-chat debug flag; the invariant here covers the transcript. ## Configuration Adds `chat-hook-url`, `chat-hook-secret`, `chat-hook-timeout`, and `chat-hook-enabled` deployment options with startup validation. The flags are hidden from `coder server --help` while the feature is experimental; the setup guide documents them. ## Tool input validation Built-in tool arguments reach a consumer as raw JSON with key spelling preserved, but the tools decode those bytes with Go, which matches struct fields case-insensitively and keeps the last match. A policy reading `path` could therefore authorize one value while the tool executed another, and a lone case variant such as `{"PATH":"/secret"}` was invisible to a policy checking for `path`. Coder now rejects a built-in tool call whose input repeats a key or spells a schema property with different capitalization, before the `pre_tool_use` dispatch, so a consumer is never asked to authorize bytes whose meaning depends on the reader. Rejected calls produce an error result the model can retry; unambiguous calls in the same batch still run. A consumer-authored `input_override` is rechecked after the dispatch and fails the turn closed, because the model cannot correct it. Dynamic and MCP inputs are excluded because the client and the workspace agent execute those calls rather than coderd. Two paths needed more than a schema check. Execution resolves a deprecated tool name to its canonical tool, so validation resolves aliases first. The `edit_files` decoder also reads `search` and `replace`, which its schema does not advertise, so those aliases are now matched exactly and their case variants ignored. A hook denial now returns a structured 403 carrying `kind: "hook_denied"`, mirroring the dispatch-failure response that already carries its own kind. Without it a client cannot tell a policy decision apart from a generic failure, and the chat UI titled a denial "Request failed". Adding a kind needs no migration: `ChatErrorKind` is persisted only inside the JSONB `chats.last_error` column, whose decoder accepts unknown kinds. The hook docs also correct the tool-input convergence window. A batch dispatches sequentially before the assistant row commits, so the original input stays visible for a span that scales with the number of tool calls in the step rather than a single hook timeout. > This PR was written by Mux, an AI coding agent, on Mike's behalf. |
||
|
|
fbac602456 |
feat!: add admin-controlled dynamic client registration toggle (#27316)
`POST /oauth2/register` (RFC 7591 Dynamic Client Registration) has exactly one gate today: `ExperimentOAuth2`, a static, process-lifetime flag that wraps the entire `/oauth2/*` route tree as an all-or-nothing switch. That flag is scheduled for removal at GA, which would leave DCR with zero admin control at all once it is gone. Add a persistent, DCR-specific `oauth2_dcr_enabled` deployment setting, independent of the experiment system, so admin control over DCR survives GA. `POST /oauth2/register` checks the flag and rejects new registrations with an RFC 7591-shaped `403` when disabled; discovery metadata (`GET /.well-known/oauth-authorization-server`) conditionally omits `registration_endpoint`. A new audited `GET`/`PUT /api/v2/oauth2-provider/settings` endpoint lets an owner toggle it live, no restart required. The setting defaults to disabled, matching the canonical design proposal; disabling only stops new self-registrations, clients that already registered continue to authorize and exchange tokens normally. Address issue described in [ENG-3056](https://linear.app/codercom/issue/ENG-3056/oauth2-dcr-admin-configurable-enabledisable). ## Where this sits in the request path ```mermaid sequenceDiagram autonumber participant A as Admin participant S as coderd participant DB as site_configs<br/>(oauth2_dcr_enabled) participant C as OAuth2/MCP Client Note over A,S: Admin toggles DCR (new) A->>S: PUT /api/v2/oauth2-provider/settings<br/>{dynamic_client_registration_enabled: false} S->>S: authorizeContext(ActionUpdate, ResourceDeploymentConfig) S->>DB: UPSERT oauth2_dcr_enabled = false S-->>A: 200 OK (audited) Note over C,S: Client discovery + registration afterward C->>S: GET /.well-known/oauth-authorization-server S->>DB: GetOAuth2DCREnabled (system ctx, every request, no cache) DB-->>S: false S-->>C: 200 metadata, registration_endpoint omitted C->>S: POST /oauth2/register S->>DB: GetOAuth2DCREnabled (system ctx, every request, no cache) DB-->>S: false S-->>C: 403 invalid_request,<br/>"Dynamic client registration is disabled" Note over C,S: A client that registered before the change is unaffected C->>S: GET /oauth2/authorize?client_id=... Note over S: no DCR-enabled check on this path S-->>C: 200 (proceeds normally) C->>S: PUT/DELETE /oauth2/clients/{client_id} (RFC 7592 self-management) Note over S: no DCR-enabled check on this path either S-->>C: 200 (proceeds normally) ``` ## Files changed: manual vs. generated Reviewers should focus on the **manual** files. The **generated** ones are `make gen` output that follows mechanically from the manual changes and don't need direct review. <details> <summary><b>Manual files (26)</b> — click to expand, grouped the same way as "Suggested review order" below</summary> **1. Database** | File | What changed | |---|---| | `coderd/database/queries/siteconfig.sql` | New `GetOAuth2DCREnabled`/`UpsertOAuth2DCREnabled` query pair on the existing generic `site_configs` table. No schema change. | | `coderd/database/dbauthz/dbauthz.go` | RBAC check (`rbac.ResourceDeploymentConfig`) on the two new query methods; extends the `subjectSystemOAuth2` system-actor role with read-only `ResourceDeploymentConfig` access, needed so the public discovery/registration endpoints can read the flag via `dbauthz.AsSystemOAuth2`. | | `coderd/database/dbauthz/dbauthz_test.go` | RBAC assertion coverage for `GetOAuth2DCREnabled`/`UpsertOAuth2DCREnabled` in the method-coverage test suite. | **2. Request gating (the actual feature)** | File | What changed | |---|---| | `coderd/oauth2provider/registration.go` | The actual gate: `CreateDynamicClientRegistration` reads the flag first and returns an RFC 7591-shaped `403` when disabled (defaults disabled if never configured). | | `coderd/oauth2provider/registration_test.go` | New unit test, `TestCreateDynamicClientRegistration_DCREnabled`: calls the handler directly (no HTTP server), covering enabled / explicitly disabled / never-configured. | | `coderd/oauth2provider/metadata.go` | `GetAuthorizationServerMetadata` conditionally omits `registration_endpoint` from discovery metadata when DCR is disabled. | | `coderd/oauth2provider/metadata_test.go` | New unit test, `TestGetAuthorizationServerMetadata_DCREnabled`: same three states, for the discovery handler. | **3. Admin settings endpoint** | File | What changed | |---|---| | `codersdk/oauth2.go` | New `OAuth2ProviderSettings` SDK type plus `Client.OAuth2ProviderSettings`/`PutOAuth2ProviderSettings` methods. | | `coderd/oauth2.go` | New `oauth2ProviderSettings`/`putOAuth2ProviderSettings` admin handlers (audited via `audit.InitRequest`); updates the `GetAuthorizationServerMetadata` call site to pass `api.Database`. | | `coderd/coderd.go` | Registers `GET`/`PUT /api/v2/oauth2-provider/settings`. | | `coderd/oauth2_provider_settings_test.go` | New test file: admin `GET`/`PUT` round-trip, default-disabled-before-any-`PUT`, and `403` for a non-owner on both `GET` and `PUT`. | **4. Audit wiring** | File | What changed | |---|---| | `coderd/database/types.go` | New `database.OAuth2ProviderSettings` audit-only struct (mirrors `NotificationsSettings`). | | `coderd/audit/diff.go` | Adds the new struct to the `Auditable` type union. | | `coderd/audit/request.go` | Adds the new struct to all four dispatch switches (`ResourceTarget`, `ResourceID`, `ResourceType`, `ResourceRequiresOrgID`). | | `codersdk/audit.go` | New API-facing `ResourceTypeOAuth2ProviderSettings` constant and its `FriendlyString` case. | | `enterprise/audit/table.go` | Field-level audit action map (`ActionTrack`/`ActionIgnore`) for the new struct. | | `coderd/database/migrations/000546_audit_oauth2_provider_settings.up.sql` | Adds `oauth2_provider_settings` to the `resource_type` Postgres enum, required for the audit wiring above (`resource_type` is a real enum, not a Go-only value). | | `coderd/database/migrations/000546_audit_oauth2_provider_settings.down.sql` | No-op (`ALTER TYPE ... ADD VALUE` can't be reverted). | **5. Test-suite ripple from the disabled-by-default flip** | File | What changed | |---|---| | `coderd/oauth2provider/oauth2providertest/helpers.go` | New shared test helper, `EnableDCR`, since DCR now defaults to disabled and many pre-existing tests need it turned on to register a client. | | `coderd/oauth2_test.go` | Adds `TestOAuth2DynamicClientRegistrationDisabled` (registers a client, disables DCR, verifies new registration is rejected while the existing client's self-management, authorize, and token exchange all keep working); calls `EnableDCR` in every pre-existing test that registers a client. | | `coderd/oauth2_error_compliance_test.go` | Calls `EnableDCR` in every test that registers a client, so RFC-error-format assertions aren't masked by the new disabled-by-default gate. | | `coderd/oauth2_metadata_validation_test.go` | Same: `EnableDCR` added to every registration-dependent test. | | `coderd/oauth2_security_test.go` | Same. | | `coderd/oauth2provider/validation_test.go` | Same (near-duplicate of `oauth2_metadata_validation_test.go` in a different package). | | `coderd/oauth2provider/provider_test.go` | Same. | | `coderd/mcp/mcp_e2e_test.go` | Same, for the MCP end-to-end dynamic-registration flow test. | </details> <details> <summary><b>Generated files (12)</b> — from <code>make gen</code>, no need to review directly</summary> `coderd/apidoc/docs.go`, `coderd/apidoc/swagger.json`, `coderd/database/dbmetrics/querymetrics.go`, `coderd/database/dbmock/dbmock.go`, `coderd/database/dump.sql`, `coderd/database/models.go`, `coderd/database/querier.go`, `coderd/database/queries.sql.go`, `docs/admin/security/audit-logs.md`, `docs/reference/api/enterprise.md`, `docs/reference/api/schemas.md`, `site/src/api/typesGenerated.ts`. </details> ## Suggested review order ### 1. Database Establishes the persisted setting and its RBAC rule; everything else builds on `GetOAuth2DCREnabled`/`UpsertOAuth2DCREnabled`. 1. `coderd/database/queries/siteconfig.sql` — the two new queries. Same boolean-encoding pattern as the existing `oauth2_github_default_eligible` key right above them in the same file. 2. `coderd/database/dbauthz/dbauthz.go` — the RBAC wrapper for those two queries, plus the `subjectSystemOAuth2` role extension (search this file for `ResourceDeploymentConfig`, it appears in both spots). 3. `coderd/database/dbauthz/dbauthz_test.go` — asserts the RBAC checks from (2) actually fire. ### 2. Request gating (the actual feature) Where `POST /oauth2/register` and discovery metadata change behavior. 1. `coderd/oauth2provider/registration.go` — the primary gate. Read this first; it's the feature. 2. `coderd/oauth2provider/registration_test.go` — its new unit test, exercising the gate's three states directly against the handler. 3. `coderd/oauth2provider/metadata.go` — the same gating pattern applied to the discovery `GET` endpoint. 4. `coderd/oauth2provider/metadata_test.go` — its new unit test. ### 3. Admin settings endpoint How an owner flips the setting live. 1. `codersdk/oauth2.go` — the `OAuth2ProviderSettings` SDK type and `Client` methods first; this is the public contract everything below implements against. 2. `coderd/oauth2.go` — the `GET`/`PUT` handlers themselves. 3. `coderd/coderd.go` — route registration, to see where those handlers get wired in. 4. `coderd/oauth2_provider_settings_test.go` — round-trip and permission tests. ### 4. Audit wiring Plumbing required so step 3's `PUT` is auditable; mechanical except for (3). 1. `coderd/database/types.go` — the audit-only struct; everything else in this layer exists to plumb it through. 2. `coderd/audit/diff.go` — adds it to the `Auditable` type union (the compiler enforces this one). 3. `coderd/audit/request.go` — the four dispatch switches; the one part of this layer worth reading closely. 4. `codersdk/audit.go` — the API-facing resource type constant. 5. `enterprise/audit/table.go` — the field-action map. 6. `coderd/database/migrations/000546_audit_oauth2_provider_settings.{up,down}.sql` — read last; a consequence of needing a new `resource_type` enum value for (1)-(5), not a design decision of its own. ### 5. Test-suite ripple from the disabled-by-default flip 1. `coderd/oauth2provider/oauth2providertest/helpers.go` — the new `EnableDCR` helper. Read first to understand the fix pattern before seeing it applied repeatedly. 2. `coderd/oauth2_test.go` — next, since it also contains the new `TestOAuth2DynamicClientRegistrationDisabled`, not just `EnableDCR` call sites. 3. The rest, in any order, they're mechanical repeats of the same one-line addition: `coderd/oauth2_error_compliance_test.go`, `coderd/oauth2_metadata_validation_test.go`, `coderd/oauth2_security_test.go`, `coderd/oauth2provider/validation_test.go`, `coderd/oauth2provider/provider_test.go`, `coderd/mcp/mcp_e2e_test.go`. ## Explicitly out of scope Per the design proposal: rate limiting on `POST /oauth2/register` (tracked separately), retroactively affecting already-registered clients when DCR is disabled (this only gates new self-registration), and an Initial Access Token requirement (a separate, follow-up ticket). |
||
|
|
c351280a37 |
feat: add Prometheus metrics for AI Governance cost control (#27490)
## Description Adds Prometheus metrics for AI budget cost control, emitted by the aibridged server under the `cost_control` subsystem (full names are prefixed `coder_ai_gateway_`). - `blocked_requests_total` (counter) — labels: `group_id` - `blocked_users` (gauge) — labels: `group_id` - `unpriced_requests_total` (counter) — labels: `provider`, `model` - `enforcement_duration_seconds` (histogram) — labels: `outcome` ## Changes - Add `GetOverBudgetUsersPerGroup` query (plus dbauthz/dbmetrics/dbmock wiring) to count over-budget users per effective group. - Add a background collector that refreshes the `blocked_users` gauge on an interval, started only when Prometheus is enabled. - Wire `Metrics` through the aibridged server, coderd API, `cli/server.go`, and the enterprise AI gateway handler; recording is nil-safe when metrics are unset. Closes https://linear.app/codercom/issue/AIGOV-296/add-prometheus-metrics-for-cost-control > [!NOTE] > Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira |
||
|
|
51ac968d5a |
feat: wire up Template Builder session telemetry endpoint (#27124)
`TemplateBuilderSession` telemetry types and telemetry-server ingestion were added in earlier PRs (#25082, coder/coder-telemetry-server#41), but no code ever produced session events. This adds the missing producer. **Backend**: `POST /api/v2/templatebuilder/sessions` reports wizard entry and compose completion events directly via `api.Telemetry.Report()`, using the same inline pattern as `NetworkEvents` and `UserTailnetConnections`. No database migration or `createSnapshot()` changes needed. RBAC requires `policy.ActionCreate` on `ResourceTemplate.AnyOrganization()`, matching the compose endpoint. **Frontend**: The template builder wizard fires `wizard_entry` on page mount and `compose_completion` on create success or failure. A client-generated session ID (UUID) correlates the two events for the same wizard visit, enabling precise funnel analysis and abandonment detection in BigQuery. Duration is tracked via `Date.now()` in the wizard state. Closes https://linear.app/codercom/issue/DEVEX-599 <details> <summary>Implementation plan</summary> ## Root Cause Analysis The DEVEX-599 ticket diagnosis suggested missing DB tables, queries, and `eg.Go` blocks. That diagnosis assumes the DB-backed periodic snapshot path is required. It is not. Investigation shows two telemetry reporting patterns in the codebase: 1. **DB-backed periodic snapshots** (`createSnapshot()` with `eg.Go` blocks): Used for durable entities like workspaces, templates, users. 2. **Direct inline reporting** (`api.Telemetry.Report(&telemetry.Snapshot{...})`): Used for ephemeral events like `NetworkEvents`, `UserTailnetConnections`, `CLIInvocations`. Template builder sessions are ephemeral events, so the direct inline reporting pattern is the correct fit. ## Backend Changes - `codersdk/templatebuilder.go`: `TemplateBuilderSessionRequest` type with `SessionID`, `EventType` enum, `TemplateBuilderSession()` client method - `coderd/coderd.go`: Route registration in `/templatebuilder` group - `coderd/templatebuilder_handler.go`: Handler with RBAC check, request validation, session ID fallback, and inline telemetry report - `coderd/templatebuilder_handler_test.go`: Tests for wizard entry, compose completion, invalid event type, disabled feature, and member RBAC rejection ## Frontend Changes - `site/src/api/api.ts`: `recordTemplateBuilderSession` API method - `site/src/api/queries/templateBuilder.ts`: React Query mutation - `site/src/pages/TemplateBuilder/wizardState.ts`: `sessionId` and `enteredAt` fields, `createWizardState()` factory for per-mount initialization - `site/src/pages/TemplateBuilder/TemplateBuilderPageView.tsx`: `sessionId` prop, `useReducer` initializer form - `site/src/pages/TemplateBuilder/TemplateBuilderPage.tsx`: Telemetry calls for wizard entry (on mount) and compose completion (on create success/failure) </details> > 🤖 Generated by Coder Agents --------- Co-authored-by: Coder Agent <agent@coder.com> |
||
|
|
6f2011af88 |
feat: add chat summary tab in the right sidebar and per-chat cost endpoint (#26649)
Stacked on #26657 (the persisted whole-chat summary backend). Base branch is `chat-summary-62j9`; review/merge that first. Adds a reusable `ChatSummary` component. The summary text is the persisted whole-chat summary (`chat.summary`) introduced by #26657. It is generated asynchronously and may be `null` until the first summary is produced, in which case the popover renders a muted empty state. Live updates arrive via that PR's `chat_summary_change` watch event, which is already merged into the chat caches. Cost is served by a new per-chat endpoint, `GET /api/experimental/chats/{chat}/cost`, which rolls up assistant-message cost across a chat's root and child (subagent) chats and is authorized like the other `{chat}` routes (read on the chat, 404 otherwise). Visual and interaction coverage lives in `ChatSummary.stories.tsx` and `ChatSummaryPopover.stories.tsx` (including populated-summary, empty-state, and cost-loading cases). --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
d5a3963167 |
feat: add bulk user secret import endpoint and SDK client (PLAT-240) (#26724)
Adds `POST /api/v2/users/{user}/secrets/batch` and
`codersdk.Client.ImportUserSecrets` to import env, JSON, or YAML secrets
atomically. The endpoint validates each entry, rolls back the full batch
on conflicts or limits, omits secret values from responses and audit
logs, and imports keys that cannot be injected as environment variables
with an empty `env_name`.
Part of the [PLAT-240 bulk secret import
stack](https://linear.app/codercom/issue/PLAT-240). Reviewed and updated
by Coder Agents on behalf of @dylanhuff-at-coder.
|
||
|
|
9bd4cf2a2a |
test: use NATS in coderdtest by default (#27343)
Closes GRU-70 Enables NATS as the pubsub for `coderdtest` unless specifically overwritten by the test case. |
||
|
|
3227cac217 |
feat: add manual chat compaction via /compact (#27081)
Adds a user-triggered `/compact` action for Coder Agents chats: typing
`/compact` in the composer (or picking it from the `/` trigger menu)
summarizes the conversation so far to free up context window space.
## How it works
- New `POST /api/experimental/chats/{chat}/compact` endpoint
(owner-only, RBAC `ActionUpdate`, excluded from the public API reference
via `x-apidocgen skip`). It marks the chat with a durable one-shot
`chats.compaction_requested_at` signal and moves it `waiting -> running`
via a new `RequestCompaction` state transition; no message row is
inserted. AI Gateway attribution needs no per-request key: generation
preparation resolves the owner's synthetic API key (#27170) like any
other turn.
- `RequestCompaction` hands off chat ownership (clears
`worker_id`/`runner_id`) so a worker acquisition hint is published;
since the transition changes no history, the previous runner could
otherwise miss the request under reordered pubsub delivery.
- The background chat worker picks the chat up like any other turn. A
pending manual request takes precedence over turn completion in the
generation decision, and forces compaction even below the automatic
threshold (and when compaction is disabled via threshold=100). The
commit step consumes the request marker in the same transaction; any
transition that ends the turn clears stale markers.
- The summary triplet reuses the automatic-compaction path, now tagged
with a `source` (`automatic` | `manual`) that is plumbed through
streamed progress parts, persisted tool JSON, and the UI label
("Summarized (manual)").
- Validation order: busy chats reject with 409 (state-machine conflict),
empty/already-compacted chats with 409 "nothing to compact", archived
chats with 400; the owner usage-limit check runs last so no-op requests
surface the specific conflict instead of a limit error.
- Web UI: the `/` trigger menu now has a built-in "Commands" group
listing `/compact`; submit intercepts exactly `/compact` and calls the
endpoint instead of sending a message. A personal or workspace skill
named `compact` takes precedence over the built-in command; while skill
collisions are still resolving, an exact `/compact` submission is
blocked with a retryable hint instead of leaking as message text.
History and queued-message edits are never intercepted. After
compaction, the context usage indicator resets to its unknown state
until the next assistant response reports fresh usage, instead of
showing the stale pre-compaction number.
- codersdk: `ExperimentalClient.CompactChat`.
Worker-path execution (rather than compacting synchronously in the
handler) reuses the existing lock fencing, live "Summarizing..."
streaming, retry accounting, restart resilience, and debug-run
observability. Rationale documented in `coderd/x/chatd/ARCHITECTURE.md`.
## Testing
- State machine: transition-matrix coverage for `RequestCompaction`,
marker lifecycle tests (carried by lease renewals/queue appends, cleared
by terminal transitions, consumed by commit), ownership handoff +
acquisition hint assertions.
- Worker: decision-ordering and forced-compaction unit tests;
active-server end-to-end test (manual compact below threshold produces a
`source=manual` summary, returns to `waiting`, no assistant follow-up;
busy chat rejected).
- API: success, archived, non-owner, RBAC-denied, empty-chat, no-daemon
cases; usage-limit ordering (at-limit owners still get
state/nothing-to-compact conflicts for no-op requests, with marker
rollback).
- Frontend: Storybook play tests for the Commands menu group, submit
intercept, skill-name collision, queued-edit passthrough, and
manual/automatic tool rendering; unit tests for command availability
resolution and the post-compaction context usage reset.
> This PR was created by Mux, an AI coding agent, working on Mike's
behalf.
|
||
|
|
54fa4a087e |
chore: wire quartz.Clock into Acquirer (#27291)
- Wires quartz.Clock into provisionerdserver.Acquirer - Allows overriding Acquirer in coderd.Options - Updates existing tests to use an Acquirer driven by a quartz.Mock Before this change `enterprise/coderd/prebuilds` package tests would take ~60-70s to run. After this change, it's down to ~10s. > Generated by Coder agents, massaged by this human. |
||
|
|
ad29777cb2 | feat: NATS mTLS pubsub implementation (#26902) | ||
|
|
6af0f4d698 |
feat: add workspace restart functionality to API (#25757)
This models restart as durable orchestration of existing stop and start workspace builds instead of adding a new restart transition. Keeping restart as two existing transitions preserves the current build/provisioner model. The child start build is created only after the parent stop build succeeds, rather than being inserted immediately in a pending state. That keeps `workspace_builds` aligned with actual provisioner-ready work and avoids introducing a second pending-build lifecycle that the provisioner and build acquisition paths would need to understand. Refs: https://linear.app/codercom/issue/PLAT-143 |
||
|
|
d7ad85f7f6 |
feat: support multiple OIDC redirect URIs (#25408)
This PR adds a new opt-in setting, `CODER_OIDC_REDIRECT_ALLOWED_HOSTS`, that lets a single Coder deployment complete OIDC login on more than one hostname. When the allowlist is non-empty, Coder picks the OIDC `redirect_uri` based on the incoming request's Host header (validated against the list) instead of always using the static URL derived from `CODER_ACCESS_URL`. When unset, the (default) behavior is identical to today. The motivation is that a single Coder deployment is frequently reachable via multiple hostnames - for example, an internal hostname for users on a corporate VPN and a different hostname routed through a zero-trust gateway for users off-VPN - but OIDC login today only works on whichever single hostname `CODER_ACCESS_URL` points to, because the `redirect_uri` sent to the IdP is fixed at server startup. Users who reach the deployment on any other valid hostname can see the login page but fail the OIDC callback, since the IdP redirects them back to a hostname they can't reach (or whose cookies they don't have). |
||
|
|
4936ff9808 |
refactor: deprecate AIGatewayRoutingEnabled, remove direct chat routing (#26862)
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. |
||
|
|
6b8c38b5a4 | fix: gate chat advisor and virtual desktop behind experiments, delete experiments page (#26809) | ||
|
|
56373a09fc |
chore: rename user-facing AI Bridge strings to AI Gateway (#26700)
Rename user-facing "AI Bridge" strings to "AI Gateway" in deployment config, RBAC display names, log messages, error strings, docs style guide, and Grafana dashboard README. Deprecated option names and descriptions (the `--aibridge-*` block) are intentionally kept as "AI Bridge". The `Name` field cannot be renamed because `serpent` uses it as a unique key during JSON serialization; duplicating names causes `UnmarshalJSON` failures (e.g. in the support bundle). Descriptions also stay as "AI Bridge" to avoid confusion between the deprecated and primary options. Refs https://linear.app/codercom/issue/AIGOV-226 > Generated with the assistance of Coder Agents (@ssncferreira) |
||
|
|
6189d6e386 |
feat: add /api/v2/aibridge/serve endpoint (#26506)
Adds a new enterprise-only `GET /api/v2/ai-gateway/serve` endpoint that standalone AI Gateway replicas use to connect to `coderd` over a DRPC-over-WebSocket transport, mirroring the existing in-memory path used by the embedded AI Bridge daemon.
- The endpoint upgrades the HTTP connection to a WebSocket, multiplexes it with yamux, and finally serves the three DRPC services (Recorder, MCPConfigurator, Authorizer).
- The `X-AI-Governance-Gateway-Key` header is used for authentication.
- The key is looked up by its hashed secret
- Missing or revoked keys return `401`.
- API version negotiation is enforced via a new `aibridged/proto` version (`v1.0`).
- Incompatible versions return `400`.
- `FeatureAIBridge` entitlement is required.
- Key liveness (`last_used_at`) is recorded immediately on connection and refreshed every 60 seconds while the session remains open.
- When key liveness detects the key was deleted (no rows where updated) session is closed.
#### Small refactors
* The three DRPC service registrations are extracted into `aibridgedserver.Register`, shared by both the in-memory and WebSocket paths.
* The literal `256 * 1024` used as the yamux-aligned WebSocket read limit is replaced with the named constant `drpcsdk.YamuxDefaultStreamWindowSize` in all call sites.
* as noted in review comment https://github.com/coder/coder/pull/26506#discussion_r3461905223 order of `SetReadLimit` and `WebsocketNetConn` calls was fixed.
|
||
|
|
970bd73691 |
feat: add /api/v2/ai-gateway API route aliases (#26475)
## Description Registers `/api/v2/ai-gateway/*` as the new API path for AI Gateway, replacing `/api/v2/aibridge/*`. Both prefixes share the same route builder (`aiBridgeRoutes`) backed by a single in-memory handler, so existing `/aibridge` endpoints continue to work. New endpoints must be registered on the enterprise API handler under `/api/v2/ai-gateway` only. Swagger annotations now point to `/api/v2/ai-gateway` paths with a backward-compatibility note referencing `/aibridge`. The legacy `/aibridge` routes are skipped in the swagger documentation test. ## Changes - Store one raw handler (`aiGatewayHandler`) instead of two prefix-stripped handlers - Register `/ai-gateway` and `/ai-gateway/proxy` route aliases alongside legacy `/aibridge` routes - Move `/aibridge/keys` to `/ai-gateway/keys` - Update in-process transport to use `/api/v2/ai-gateway` prefix - Update SDK client URLs and proxy forwarding URL - Swap `@Router` and `@Tags` annotations from `aibridge`/`AI Bridge` to `ai-gateway`/`AI Gateway` - Rename user-facing error messages from "AI Bridge" to "AI Gateway" - Define consts for route prefixes (`AIGatewayRootPath`, `AIBridgeRootPath`) - Update tests and comments to use new paths Note: the following will be addressed in follow-up PRs: - Frontend API URLs - Frontend routes and redirects - Dogfood main.tf updates - Hand-written documentation URL updates - aibridge internal comments and nits - Scale tests path updates Refs https://linear.app/coder/issue/AIGOV-230 > Generated with the assistance of Coder Agents (@ssncferreira) |
||
|
|
cd56ab9e33 |
refactor: remove legacy live-read and injected-history chat context paths (#26585)
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. |
||
|
|
966dd89537 |
feat: add chat context source CLI and agent-token refresh (#26577)
Adds the `coder exp chat context` CLI for managing workspace context sources, plus the agent-token refresh endpoint the in-workspace refresh relies on. Part of breaking the "Workspace Context Sources for Coder Agents" RFC (#26466) into small, reviewable PRs. ## What this adds **CLI (`coder exp chat context`)**, talking to the agent's local IPC socket from inside the workspace: - `list` lists the registered scan roots (built-in defaults are not shown). - `show <path>` shows a source and the resources the agent resolves from it, including failures. - `add <path>` registers a path as an additional context source. With `--chat`, it keeps the legacy one-shot behavior (read context from the path once and inject it into a single chat). - `remove <path>` unregisters a source. - `refresh [<chat>]` re-pins chat context to the agent's latest snapshot. **Agent-token refresh path** for the no-argument `refresh`: - `refresh <chat>` uses the existing user-facing `ExperimentalClient.RefreshChatContext` (already on main) and works from anywhere. - `refresh` with no argument runs inside the workspace: it re-resolves the agent's sources over the context socket (catching freshly-cloned repos and startup-script writes), then asks the agent, authenticating with its own token, to re-pin every drifted chat. No `coder login` required. - This adds `agentsdk.RefreshChatContext` and `POST /api/v2/workspaceagents/me/experimental/chat-context/refresh` (`workspaceAgentRefreshChatContext`), mirroring the existing clear endpoint's agent-token auth model. ## Testing - `go test ./cli` (`TestExpChatContextAdd`, `TestParseChatID`, `TestResolveContextSourcePath`) - `go test ./coderd/x/chatd -run TestChatContextRefreshFromAgentToken` (end-to-end: echo-provisioned agent pushes a snapshot, drifts a bound chat, the agent-token refresh re-pins it, and an agent-less chat stays untouched) - `go build ./...`, `go vet`, `golangci-lint`, `make gen` (no generated changes; experimental commands are excluded from CLI golden/doc generation) <details> <summary>Design notes</summary> This is **Split 4** of #26466. Split sequence: 1. #26558 - prompt pin consumption (merged) 2. #26570 - `codersdk` context resource types (merged) 3. #26573 - the context indicator UI (merged) 4. **This PR** - the CLI + agent-token refresh. 5. The context diff (`changes`, `ChatContextResourceChange`, the changes dialog, `buildContentPatch`) - last. Key points: - The agent-local context subsystem (`agent/agentsocket` IPC for source CRUD, snapshot, resync), the user-facing `ExperimentalClient.RefreshChatContext`, and the per-chat `chatd.RefreshChatContext` all already exist on main, so this split is the CLI surface plus the small agent-token refresh endpoint that fans out per-chat refresh across an agent's drifted chats. - `add <path>` resolves relative paths to absolute before handing them to the agent (which requires canonical paths) but preserves a leading `~` for the agent to expand against its own home. `TestResolveContextSourcePath` covers this. - The agent endpoint is annotated `@x-apidocgen {"skip": true}`, matching the other agent-token chat-context endpoints. - No diff/changes rendering is involved; that lands in the final split. </details> *This PR was created by Coder Agents on behalf of @kylecarbs.* |
||
|
|
335d6bda1b |
feat: add GET /api/v2/agent-firewall/sessions/{id}/logs endpoint (#24816)
Add a `GET /api/v2/agent-firewall/sessions/{id}/logs` endpoint that
returns agent firewall audit logs for a given session, sorted by
sequence number ascending.
The endpoint supports `seq_after` and `seq_before` (exclusive bounds)
and `limit` query parameters. This enables the frontend to fetch exactly
the firewall events that fall between two AI Bridge interceptions within
a thread, as described in FR 4 of the Boundary/Bridge correlation RFC.
Authorization reuses the `boundary_log` RBAC resource (owner and auditor
can read; members cannot). Returns 404 for unauthorized users to avoid
leaking existence information.
The endpoint is enterprise-only, gated behind `FeatureBoundary`
entitlement, matching the session endpoint from #24814.
Depends on #24814
> [!NOTE]
> This PR was authored by Coder Agents.
|
||
|
|
8b970e7ff3 |
docs: clarify Agents vs Chats API reference pages (#26021)
## Problem The REST API reference page at [`/docs/reference/api/agents`](https://coder.com/docs/reference/api/agents) is confusing: by the name alone, a reader looking for the *AI Coder Agents* programmatic API would assume this is the right page. In fact, those endpoints are for the *workspace agent daemon* (the `coder_agent` Terraform resource / `workspaceagent` daemon). The actual AI Coder Agents API is documented at [`/docs/reference/api/chats`](https://coder.com/docs/reference/api/chats). Both pages compound the confusion by being rendered with a bare `# Agents` / `# Chats` heading and no descriptive intro. The sidebar entries are similarly ambiguous (`Agents` and `Chats` with no descriptions). ## Root cause The reference pages are generated by `scripts/apidocgen/generate.sh` (swag → widdershins → postprocess). The widdershins template (`scripts/apidocgen/markdown-template/main.dot`) already renders `data.resource.description` directly under each section heading: ``` <!-- APIDOCGEN: BEGIN SECTION --> {{= data.tags.section }}# {{= r}} {{? data.resource.description }}{{= data.resource.description}}{{?}} ``` …but the swag annotations in `coderd/coderd.go` never declared `@tag.name` / `@tag.description` for any tag, so the descriptions were always empty. ## Changes - `coderd/coderd.go`: add `@tag.name Agents` / `@tag.description …` and `@tag.name Chats` / `@tag.description …` annotations next to the existing `@title` / `@version` block. - `docs/manifest.json`: rename the sidebar entry `Agents` → `Workspace Agents` and add `description` fields to both API sidebar entries (every other top-level section in the manifest has descriptions; the API children did not). - Regenerate `coderd/apidoc/swagger.json`, `coderd/apidoc/docs.go`, `docs/reference/api/agents.md`, and `docs/reference/api/chats.md` via `scripts/apidocgen/generate.sh` + `pnpm exec markdownlint-cli2 --fix` + `pnpm exec markdown-table-formatter` + `scripts/biome_format.sh` (matching the Makefile's `coderd/apidoc/.gen` pipeline). Resulting diff is intentionally minimal — 6 files, 35 insertions / 3 deletions. ## After this PR The Agents page will render: > # Agents > > Workspace agent endpoints. These power the workspace agent daemon defined by the `coder_agent` Terraform resource (sometimes called the workspace daemon). This API is NOT the AI Coder Agents API. For programmatic access to AI Coder Agents (formerly Tasks), see the Chats API. The Chats page will render: > # Chats > > Programmatic API for Coder AI Agents (the user-facing "Coder Agents" / "Chats" product). Experimental. Use these endpoints to create, list, and manage AI coding agent sessions. For background and migration from the Tasks API, see the AI Coder docs. And the sidebar entry for the workspace-agent endpoints becomes `Workspace Agents` instead of `Agents`. ## Out of scope (potential follow-ups) - `docs/reference/api/chat.md` is a 7-byte stub — likely dead. Could be deleted in a follow-up. - Larger rename of the `Agents` Swagger tag (and/or the `coder_agent` Terraform resource) to something like `Workspace Agents` / `workspace_daemon` would more thoroughly fix the naming collision, but that's a much bigger change. Created on behalf of @mattvollmer. --------- Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com> Co-authored-by: Matt Vollmer <matthewjvollmer@outlook.com> Co-authored-by: Atif Ali <atif@coder.com> |
||
|
|
8d725969bf |
chore!: remove coder agents insights page (#26457)
Removes the coder agents PR Insights page (`/agents/settings/insights`) and all of its backend support. The page had previously been hidden and was only reachable via deep link. It had previously been hidden due to the dubious value provided in the current iteration. |
||
|
|
bca0ce04ca |
feat: integrate agent context snapshots into chats (#26389)
Makes the chat context foundation from #26385 live. That PR added the storage columns, writer queries, and a dormant `agentapi.ContextDirtyMarker` trigger with no production callers; this PR wires them together end to end. When a workspace agent pushes a context snapshot, bound chats now hydrate to that snapshot's hash, and a later push with a different hash flips already-pinned chats to dirty (emitting a `context_dirty` watch event after the transaction commits). Chat creation pins the agent's latest snapshot when one already exists. The experimental chat API exposes this as `Chat.Context` (`*ChatContext` with `dirty`, `dirty_since`, `error`), and a new `PUT /api/experimental/chats/{chat}/context` endpoint re-pins the agent's latest snapshot and clears the dirty marker. `context_dirty_resources` stays NULL (the resource-level diff is deferred to the UI phase) and the live per-turn context pull is unchanged. The end-to-end test provisions a workspace agent via the echo provisioner, connects it over the Agent API v2.10, and exercises the full path: an initial push hydrates a bound chat (clean), a second push with a different hash marks it dirty, the API reports the dirty state, and the refresh endpoint clears it. <details> <summary>Decision log</summary> - **API shape — sub-struct.** Dirty state is surfaced as `codersdk.Chat.Context *ChatContext { Dirty bool; DirtySince *time.Time; Error string }` rather than flat fields, matching the RFC's named `ChatContext` type and leaving room for future fields (resource diff, sources). `db2sdk.Chat` populates it when the chat is context-tracked (`len(ContextAggregateHash) > 0`), dirty, or carries a snapshot error, and leaves it nil (`omitempty`) otherwise. `Dirty` mirrors `context_dirty_since` being set. - **Marker wiring.** The chat daemon is injected directly as the `agentapi.ContextDirtyMarker`. It is unconditionally constructed (only its background worker is gated), so the marker is always non-nil and the wiring matches every other `api.chatDaemon` call site. `agentapi` still treats a nil marker as "chatd absent", so `PushContextState` stays a pure write path for any future caller that does not wire chatd in. - **Refresh is atomic.** `RefreshChatContext` reads the agent's latest snapshot and re-pins the chat in one repeatable-read transaction, so a concurrent push cannot land between the read and the write and leave the chat pinned to a stale hash with the dirty marker cleared. - **Hydrate + dirty run inside the push transaction.** The fan-out shares the push's transaction so a concurrent refresh cannot interleave with the version gate; `context_dirty` watch events publish only after commit. The pinned hash on dirtied chats is intentionally left unchanged — the refresh endpoint re-pins it. - **Dirtied chats keep their pinned hash.** Drift is advisory: a dirty chat stays usable, and refreshing is the only path that advances the pinned hash. - **Test binds `chats.agent_id` directly.** In production the binding is set lazily during a chat turn (`chatd.persistBuildAgentBinding`); the test sets it via `dbgen` so it exercises the context flow rather than turn resolution. Plan: `coderd/x/chatd` context integration + E2E (sub-struct API, create-time + push-time hydration, refresh endpoint; `context_dirty_resources` and the per-turn pull untouched). </details> 🤖 Generated by Coder Agents on behalf of @kylecarbs |
||
|
|
de31c7c18e |
feat: add TemplateBuilderCreateTemplate SDK types and client method (#26360)
Adds `POST /api/v2/templatebuilder/compose/template`, a synchronous endpoint that composes a template from a base and modules, validates it via a provisioner import job, and creates the template in a single request. The handler composes terraform files, bundles them as a tar, inserts the file with hash-based dedup, creates a template version with an import job, waits up to 2 minutes for the job to complete, classifies errors for known failure modes (network-unreachable registry, DNS failures), then creates the template on success. Canceled and failed jobs return appropriate error responses. Also adds `hclwrite.Format` to composed terraform output for canonical HCL formatting. Closes https://linear.app/codercom/issue/DEVEX-279 <details> <summary>Implementation notes</summary> - SDK types and client method in `codersdk/templatebuilder.go` with validation tags matching the standard template creation path (`template_display_name`, `lt=128`) - `ClassifyProvisionerError` in `coderd/templatebuilder/errors.go` detects DNS, connection refused, i/o timeout, and TLS handshake failures and returns actionable messages - `waitForProvisionerJob` polls with a ramp-up interval schedule (100ms, 200ms, 500ms, then 1s steady) and accepts an `onUpdate` callback for future SSE streaming - Audit logging for both template and template version creation - TOCTOU name uniqueness: early check for fast feedback, DB unique constraint catch for the race window (returns 409, not 500) - Swagger annotations for all error responses (400, 404, 409, 504) </details> > 🤖 Generated by Coder Agents |
||
|
|
b61b62f4b3 |
feat: add POST /api/v2/templatebuilder/compose endpoint (#26351)
> [!NOTE] > This PR was authored by Coder Agents on behalf of @jeremyruppel. Part 4 of DEVEX-277 (POST /api/v2/templatebuilder/compose). Adds the HTTP handler, route wiring, and integration tests for the compose endpoint. The handler accepts a JSON request with a base template ID and optional modules with variable overrides, renders them via `Compose`/`BundleTar`, and returns the tar archive directly with `Content-Type: application/x-tar`. The registry URL comes from the deployment config (`CODER_TEMPLATE_BUILDER_REGISTRY_URL`). RBAC uses `policy.ActionCreate` on `rbac.ResourceTemplate.AnyOrganization()`. Integration tests cover: base-only compose, base with modules, unknown base/module errors, missing base template ID, and feature-disabled 404. |
||
|
|
9a6e348f5d |
feat: add GET /api/v2/templatebuilder/modules endpoint (#26117)
Implement `GET /api/v2/templatebuilder/modules`, which returns the filtered list of modules available for a given base template. Reads from the bundled catalog via `LoadModules()` and applies OS-compatibility filtering based on the `base` query param. Computed variables (e.g. `agent_id`) are excluded from the API response at the `ToSDK()` conversion boundary since they are wired automatically by the builder. The `Computed` field is removed from the SDK type. Adds `CompatibleWithOS()` to `ModuleManifest` for OS filtering. Returns 400 for unknown base IDs and 404 when the template builder is disabled. Depends on #26116 > [!NOTE] > This PR was authored by Coder Agents on behalf of @jeremyruppel. |
||
|
|
776fbfa748 |
feat: add GET /api/v2/templatebuilder/bases endpoint (#26116)
Implement `GET /api/v2/templatebuilder/bases`, which returns the list of base templates available in the template builder. Reads from the bundled catalog by cross-referencing `templatebuilder.BaseTemplateIDs()` with `examples.List()`, enriching each entry with the OS from the `exampleID -> OS` map. The endpoint is gated behind the template builder feature flag (returns 404 when disabled) and requires `policy.ActionRead` on `rbac.ResourceTemplate`. Depends on #26115 > [!NOTE] > This PR was authored by Coder Agents on behalf of @jeremyruppel. |
||
|
|
84843e619a |
fix(coderd): disable chat worker in TestPostChatMessagesBusyInterrupt (#26331)
Addresses https://github.com/coder/internal/issues/1584 |
||
|
|
4debd23cbb |
fix: chatd refactor (#26270)
Implements the chatd stabilization RFC. Combines: - https://github.com/coder/coder/pull/25908 - https://github.com/coder/coder/pull/25923 - https://github.com/coder/coder/pull/26109 - https://github.com/coder/coder/pull/26110 - https://github.com/coder/coder/pull/26111 - https://github.com/coder/coder/pull/26112 |
||
|
|
b5ef700dd6 |
fix!: only trust x-forwarded-host from configured trusted proxies (#26204)
Subdomain app routing derived the app identity from httpapi.RequestHost, which returned the client-supplied X-Forwarded-Host header verbatim. No middleware validated or stripped that header, so a request from an untrusted peer could forge it. Since the application_connect cookie is scoped to the wildcard apps domain, JavaScript in a share=authenticated app could fetch() with a forged X-Forwarded-Host pointing at a victim's owner-only app; coderd routed and authorized the request as the victim and returned the private app response same-origin to the attacker. Replace RequestHost with httpmw.EffectiveHost, which honors X-Forwarded-Host only when the original socket peer is a configured trusted origin, otherwise falling back to the received Host header. This ties host trust to the same RealIPConfig model already used for X-Forwarded-For and -Proto. Wire it into HandleSubdomain for both coderd and wsproxy, and log both the effective host and the raw received_host. Add coverage: EffectiveHost unit tests assert the trust decision uses the socket peer rather than the spoofable forwarded client IP, and a HandleSubdomain test confirms a forged X-Forwarded-Host from an untrusted peer never reaches token resolution. Refs: https://linear.app/codercom/issue/PLAT-259 |
||
|
|
78a6ec293e |
revert: "fix: avoid an errant license warning banner on new deployments that d…" (#26240)
Reverts coder/coder#26239 We cannot disable a feature which was previously enabled; this is a BC break. This is also using `AIGatewayRoutingEnabled` which will be removed in the next release. |
||
|
|
d0e9c5eda5 |
fix: avoid an errant license warning banner on new deployments that d… (#26239)
Problem: CODER_AI_GATEWAY_ENABLED defaulted to true, which both started the in-memory gateway and enabled the licensed FeatureAIBridge. As a result, deployments that never configured AI Gateway saw a spurious "AI Governance add-on is required" warning whenever they had an older (non-add-on) Premium license, since the feature was enabled-and-entitled by default. Fix: Decouple "external AI Gateway API enabled" from "in-memory daemon running," so the external/licensed surface is off by default while Coder Agents retain access by default. |
||
|
|
f17f8392bd |
feat(coderd): gate org-member workspace elevation behind experiment (#26027)
Gates the workspace-ops elevation on `organization-member` and `organization-service-account` behind the `minimum-implicit-member` experiment. |
||
|
|
167ac7b879 | feat: add nats experiment (#25703) | ||
|
|
8b058dc949 |
feat: add coderd_api_websocket_probes_total metric (#25012)
Relates to CODAGT-115 Adds metric `coderd_api_websocket_probes_total`. Every successful heartbeat for a given path will increment the metric. Comparing this with `coderd_api_concurrent_websockets` will give an indication of how many websocket connections are open but in a 'wedged' state (when heartbeats stopped versus when we closed the connection). |
||
|
|
8b1705eb65 |
feat: route chatd provider traffic through aibridge (#25629)
## Summary Routes chatd model calls backed by concrete AI Provider rows through the in-process aibridge transport by default, with deployment options to use direct provider routing when AI Gateway is disabled or chat AI Gateway routing is disabled. - Splits model routing into common, direct provider, and AI Gateway paths behind a single deployment-mode entry point. - Builds chatd models through explicit request, route, and options data. Active API key attribution is passed explicitly instead of being hidden inside generic model construction. - For AI Gateway BYOK routes, resolves the user's provider key in chatd, forwards it through provider-specific auth headers, and sets `X-Coder-AI-Governance-Token` to the `delegated` marker so aibridge preserves those headers while still stripping Coder-specific metadata. - Keeps central provider credentials and deployment fallback credentials out of forwarded provider auth headers, so AI Gateway central policy remains authoritative. - Redacts delegated provider auth from default string formatting to avoid accidental plaintext logging of user BYOK credentials. - Covers selected chat models, advisor overrides, title and quickgen paths, subagent overrides, computer use model selection, and an integration-style chat turn through the aibridge transport path. - Persists initiating API key IDs on chat and queued user messages, including subagent child messages, and fails closed for AI Gateway-routed model builds without an active key. - Removes unused `api_key_id` indexes while keeping the persistence columns and foreign keys. - Keeps the deployment option available through config and env parsing, but hides it from CLI help and generated docs. - Stabilizes the subagent poll fallback test so background CreateChat processing cannot win the state transition under slower CI environments. ## Tests - `go test ./coderd/x/chatd -run 'TestAIGatewayProviderAuthForUser|TestAIGatewayProviderAuthRedactsFormatting|TestResolveModelRouteForConfigAIGatewayProviderAuth|TestAIGatewayModelForwardsProviderAuth|TestProcessChat_AIGatewayRoutingUsesDelegatedAPIKey|TestAwaitSubagentCompletion' -count=1` - `go test ./coderd/aibridged -run 'TestServeHTTP_DelegatedAPIKey|TestServeHTTP_StripCoderToken' -count=1` - `git diff --check HEAD~1..HEAD` - `make lint` > Mux working on behalf of Mike. |
||
|
|
5d40bac79f |
feat: add in-memory transport for chatd -> aibridge routing (#25576)
### TL;DR Introduces an in-process `TransportFactory` for aibridge so that chatd (coder-agent LLM traffic) can route requests through the aibridged handler without crossing the HTTP route or requiring a license entitlement check. ### What changed? - Added a new `coderd/aibridge` package with a `TransportFactory` interface and a `Source` type for tagging the call site on request contexts. `SourceAgents` is defined as the constant for coder-agent traffic. - Implemented `NewTransportFactory` in `coderd/aibridged/transport.go`, which returns an `http.RoundTripper` that dispatches requests to the aibridged handler in-process. The response body is streamed through an `io.Pipe` so SSE/NDJSON/chunked responses propagate token-by-token. Handler panics are recovered and surfaced as 500 responses, and context cancellation closes the pipe with the appropriate error. - `RegisterInMemoryAIBridgedHTTPHandler` now also constructs a `TransportFactory` from the registered handler and stores it on `API.AIBridgeTransportFactory` (an `atomic.Pointer`), making it available to chatd without going through the license-gated HTTP route. - Added `API.AIBridgeTransportFactory` as a public `atomic.Pointer[aibridge.TransportFactory]` field on `coderd.API`. ### How to test? - `coderd/aibridged/transport_test.go` covers: transport creation, nil-handler errors, source attachment to context, header/status passthrough, streaming (SSE-style chunked writes visible before handler completion), context cancellation closing the body with an error, concurrent requests, handler panics producing 500s, and handlers that return without writing. - `coderd/aibridge_test.go` verifies that `AIBridgeTransportFactory` starts as nil on AGPL coderd, can be stored and loaded atomically, and that the stored factory correctly dispatches requests through the stub handler. ### Why make this change? Chatd needs to send LLM requests through aibridge in-process rather than via the external HTTP route, which is license-gated. The `TransportFactory` abstraction provides a clean seam: the entitlement check remains on the HTTP route for external callers, while in-process coder-agent traffic bypasses it through the factory. The `Source` type allows downstream handlers and logs to attribute traffic without gating behavior on the caller identity. |
||
|
|
ddec110b0e |
refactor: move aibridged out of enterprise to AGPL (#25570)
In order to allow Coder Agents to use AI Gateway in OSS, we need to rehome the `aibridged`\-related code into the AGPL path. The HTTP API is only registered under enterprise so will still require the AI Governance Add-on to be present in order to use it, whereas Coder Agents uses an in-memory pipe to the same handlers. |
||
|
|
06526a5822 | feat: use AI provider chat APIs (#25415) | ||
|
|
5968c3dac7 | feat: use AI provider keys at runtime (#25414) |