mirror of
https://github.com/coder/coder.git
synced 2026-10-31 19:36:41 +08:00
351bb1403250c834d5d065a01252b3283b238d72
2810
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
351bb14032 |
feat: mount chat API routes under /api/v2 (#28496)
## Stack context This is the base of a 3-PR stack promoting the chat API from `/api/experimental` to `/api/v2`: server compatibility mounts (this PR), codersdk promotion (#28497), and frontend path updates (#28498). ## Summary Double-mount the stable chat and MCP handlers under `/api/v2` while retaining the existing experimental routes for the one-release compatibility window decided in CODAGT-921. CODAGT-922 tracks removing the compatibility mounts. The shared route builders preserve existing authentication and middleware behavior. Experiment-gated, debug, tombstone, and legacy default-organization model routes remain experimental-only. Signed file URLs, external OAuth callback URLs, and mixed-version replica relays also remain on the experimental prefix during the transition. Update Swagger and the generated API reference for the promoted routes, including the workspace lookup and a runnable raw-body chat file upload example. Retain internal endpoints outside the published reference, share chat-file rate limits across both prefixes, enable CORS for the v2 MCP routes, and cover dual mounts plus exclusions with compatibility tests. Remote dogfood UAT passed for the promoted chat, model, MCP, and file flows. > [!NOTE] > Xum acted on Mike's behalf in this pull request. <!-- xum-attribution: model=claude-fable-5 thinking=high --> |
||
|
|
845790e652 |
feat: enable Coder Agents for organization members (#28186)
## Summary - make Coder Agents available to organization members without a separate built-in role - remove the obsolete role from backend and frontend role surfaces - update authorization, UI, documentation, and regression coverage ## Database note This PR intentionally ships no migration so it backports cleanly across diverged migration numbers. Stale `agents-access` grants may remain in user role arrays and org default member roles; the retired name is treated as a grant of nothing (role expansion drops it, assignment validation ignores it, and the name stays reserved). A follow-up PR will add the data cleanup migration. ## Validation - `make gen` - `make lint` - repository pre-commit checks - targeted RBAC, migration, chat API, enterprise, site, Storybook, and race tests - `git diff --check` ## Rollback note Rolling back the deployment restores the previous behavior directly: no data changed, and older binaries still resolve any lingering `agents-access` grants. > Mux created this pull request on Mike's behalf. |
||
|
|
56c614422e |
fix: clarify AI provider collision warnings (#28549)
AI provider collision warnings were only partially asserted, and the update coverage did not exercise a provider changing from a distinct hostname to a colliding hostname. Assert the complete warning consistently across create, get, list, and update responses, cover collisions introduced by base URL and enabled-state updates, and only return the proxy-specific warning when AI Gateway Proxy is enabled. Clarify that lifecycle status values are reported by the provider metrics. Follow-up to #28208. > [!NOTE] > Generated by Coder Agents for @ssncferreira. |
||
|
|
c9fc5b7ea1 |
fix: canonicalize API key scope aliases at ingress (#28322)
`rbac.IsExternalScope` accepts `all` and `application_connect`, neither
of which is an `api_key_scope` enum member. The plural `Scopes` field
stored the name as given, so `{"scopes":["all"]}` passed validation and
then 500'd inside `apikey.Generate` with `invalid API key scope: "all"`.
Reachable from `coder tokens create --scope all`, from the spellings
`docs/admin/users/sessions-tokens.md` taught, and from the `codersdk`
constants exported for exactly this purpose.
- Canonicalize inside `apikey.Generate`, in the loop that already
validates each name. One statement covers every caller and all three
input paths: plural `Scopes`, deprecated singular `Scope`, and the
default. Replaces two open-coded switches and the handler's own
per-element copy, so one place decides the stored spelling and the
handler only decides what may be requested.
- Deduplicate in the same pass. An alias and its canonical spelling are
two names going in and one name in the column.
- `ExternalScopeNames()` lists every name `IsExternalScope` accepts,
instead of dropping catalog entries that fail to parse. A curated entry
that cannot be stored used to pass every test and 500 at runtime; it now
fails two.
- Split the 400. A misspelled name and an internal `api_key_scope`
member need different words, since no re-spelling makes the second
requestable. Both rejection sites share one helper that names the case
and links the docs.
- Add reject-path tests for `not_a_real_scope` and `debug_info:read`.
The second is a valid enum member the rbac catalog treats as internal,
so before this both handler guards could be deleted with the suite still
green. A further case pins that plural `Scopes` wins when a caller sets
both fields.
- `TestExternalScopesAreStorable` pins the class rather than the two
known instances: every public rbac scope name must be storable.
`coderd/rbac` cannot assert this itself, since `database` imports `rbac`
and not the reverse.
- Docs use the canonical spellings, link the `codersdk.APIKeyScope`
schema, and say which scopes a token cannot request.
Egress is unchanged: `convertAPIKey` still derives a legacy singular
name on the way out. `ExternalScopeNames()` returns the same 57 names as
before, and `codersdk/apikey_scopes_gen.go` regenerates identically.
`rbac.CanonicalScopeName` merged with #28167, so this applies to `main`
and reviews on its own. The OAuth2 provider ignores scopes entirely
today (`authorize.go:237`, `tokens.go:377`, `tokens.go:520`), so it is
unaffected until those TODOs resolve.
Deferred from review: PLAT-528, PLAT-529, PLAT-530, and PLAT-532.
PLAT-532 covers `{"scopes":[]}` and `{"allow_list":[]}` defaulting open,
which predates this PR.
---------
Co-authored-by: McKayla はな <mckayla@hey.com>
|
||
|
|
895c5c77a1 | feat: add a template parameter settings page (#27559) | ||
|
|
ce59a86a81 |
docs: fix Coder Agents tool tables, search filters, attachments, template ranking (#28575)
Corrects four independent factual errors in the Coder Agents documentation, each verified against the current implementation. - **Missing tools in the tool tables.** `stop_workspace` was absent from the built-in tool table in `docs/ai-coder/agents/index.md` and the platform tool table in `docs/ai-coder/agents/architecture.md`, and from the root-chat-only tool list. `find_tools` was undocumented entirely; both docs now list it with its `mcp-tool-search` experiment gating. - **Missing `source:` search filter.** `docs/ai-coder/agents/chat-search-syntax.md` documented every chat filter except `source:`, while also stating that unrecognized keys return HTTP 400. Adds the filter row and an example. - **Attachments described as images only.** The attachments section claimed PNG, JPEG, GIF, and WebP up to 10 MB. The upload allowlist also accepts plain text, Markdown, CSV, JSON, and PDF; the cap is 10 MiB per upload with at most 50 attachments per conversation, and Anthropic models cap inline images at 5 MiB. Also notes that providers differ in accepted file types and that rejected parts are downgraded to text. - **Template ranking described as popularity sort.** `docs/ai-coder/agents/platform-controls/template-optimization.md` said templates are sorted by active developer count. `list_templates` ranks by query relevance tiers first, then by an affinity score that weights the user's own recent usage far above organization popularity. Wording now matches `docs/ai-coder/agents/tools/index.md`, which already documents the formula, and links to it. Linear: DOCS-716 https://linear.app/codercom/issue/DOCS-716 <details><summary>Analysis evidence</summary> | Claim | Source | |-------|--------| | `stop_workspace` is a root-chat platform tool registered alongside `create_workspace` and `start_workspace` | `coderd/x/chatd/chattool/stopworkspace.go`; registration in `coderd/x/chatd/chatd.go` (`chattool.StopWorkspace`), root-only gate in `builtinPlanToolAllowed`, disabled for explore sub-agents in `allowedExploreToolNames` | | `find_tools` requires the `mcp-tool-search` experiment and deferrable MCP candidates | `coderd/x/chatd/chattool/findtools.go`; `decideMCPToolSearch` in `coderd/x/chatd/mcp_tool_search.go` returns false without `codersdk.ExperimentMCPToolSearch` or with no candidates; wired in `coderd/x/chatd/generation_preparer.go` | | `source:` accepts `created_by_me` and `shared_with_me`; passing both returns owned plus shared; default is owned only | `coderd/searchquery/search.go` (`Chats`): filter defaults to `OwnedOnly: true`; the `source` parser rejects any other value, and both values together set `OwnedOnly` and `SharedOnly`. The literal value `all` appears in the doc comment but is not accepted by the parser, so it is not documented. | | Attachment allowlist, size caps, and per-conversation limit | `codersdk/chats.go`: `AllChatAttachmentMediaTypes` covers `application/json`, `application/pdf`, `image/gif`, `image/jpeg`, `image/png`, `image/webp`, `text/csv`, `text/markdown`, `text/plain`; `MaxChatFileSizeBytes = 10 * 1024 * 1024`; `MaxChatFileIDs = 50`; `AnthropicInlineImageCapBytes = 5 * 1024 * 1024` | | Anthropic and Bedrock share the inline image cap; rejected file parts are downgraded to text | `coderd/x/chatd/chatprovider/chatprovider.go`: `InlineImageCapBytes` and `Model.AcceptsFilePartMediaType` | | Template ranking is relevance tiers then a composite affinity score | `coderd/x/chatd/chattool/listtemplates.go`: `rankTemplates` sorts by query score first; `computeAffinityScore` adds recency-decayed personal usage at `listTemplatesPersonalWeight = 10.0` to `ln(1 + active_developers)` at `listTemplatesOrgWeight = 1.0` | Note: `MaxChatFileIDs` is enforced per chat, not per message (`coderd/exp_chats.go`: "A chat can reference at most %d attachments"), so the docs say per conversation. Validation: `pnpm run format-docs` and `pnpm run lint-docs` both pass (0 errors). The formatter widened the touched tables, which accounts for the reformatted rows in the diff. </details> Generated by Coder Agents on behalf of @nickvigilante. |
||
|
|
06c0d3918a |
docs: correct plan mode MCP tool availability (#28569)
`docs/ai-coder/agents/index.md` claimed that MCP tools are blocked in plan mode. Root plan-mode chats actually receive external MCP tools for each server configuration an administrator approved for plan mode; only workspace MCP tools and plan-mode sub-agents get none. Corrects the plan-mode tool-availability wording in both places it appears in `index.md` so it matches `docs/ai-coder/agents/architecture.md` and the implementation. The tool table and attachment wording are out of scope here. Linear: DOCS-724 https://linear.app/codercom/issue/DOCS-724 <details><summary>Analysis evidence</summary> `coderd/x/chatd/chatd.go`, `filterExternalMCPConfigsForTurn`: in plan mode, a chat with a parent gets no external MCP configs ("Plan-mode subagents do not receive external MCP tools"); a root chat keeps every config whose `AllowInPlanMode` flag is set and returns those IDs as approved. `coderd/x/chatd/generation_preparer.go`: the returned `approvedPlanMCPConfigIDs` flows into `filterToolsForTurn` and `activeToolNamesForTurn`, so approved external MCP tools remain exposed for the turn. `coderd/x/chatd/chatd.go`, `toolAllowedForTurn`: in plan mode a tool passes only if it is on the built-in plan allowlist or is an MCP tool whose `MCPServerConfigID()` is in the approved set. Workspace MCP tools are not in the external config set, so they stay unavailable. `docs/ai-coder/agents/architecture.md` already documents this behavior; `index.md` now uses the same framing. </details> Generated by Coder Agents on behalf of @nickvigilante. |
||
|
|
b146e6f262 |
docs: align Coder Agents docs with GA framing and licensing terms (#28571)
Removes early-access-program framing from the Coder Agents docs, drops a stale `early access` manifest tag, and standardizes the licensing wording on "Premium license with the Agent Hours entitlement". - `docs/ai-coder/agents/getting-started.md`: replaces the "collaborative evaluation between your team and Coder" and customer-specific Slack channel feedback path with GitHub issues plus normal Coder support channels, and rewrites the remaining evaluation-period wording to GA tone. Rollout guidance (pilot group, cost planning, concurrency) is kept. - `docs/manifest.json`: removes `"state": ["early access"]` from `ai-coder/agents/platform-controls/chat-debug-logging.md`, which is not experiment-gated. - Licensing terminology aligned across `getting-started.md`, `licensing-usage.md`, and `platform-controls/index.md`. No changes to `docs/reference/api/**` and no API path changes. <details><summary>Analysis evidence</summary> **Manifest state tag decisions (all `ai-coder/agents` pages)** | Page | Tag before | Tag after | Why | |------|-----------|-----------|-----| | `platform-controls/chat-debug-logging.md` | `["early access"]` | none | Routes at `coderd/coderd.go` (`GET`/`PUT /debug-logging`, `GET`/`PUT /user-debug-logging`) sit outside any `RequireExperimentWithDevBypass` group, there is no matching experiment in the `codersdk.Experiment` list, and the page body already states the feature is not experiment-gated. | | `platform-controls/advisor.md` | `["early access"]` | unchanged | Gated by `codersdk.ExperimentChatAdvisor` (`chat-advisor`); routes wrapped in `RequireExperimentWithDevBypass`. | | `platform-controls/virtual-desktop.md` | `["early access"]` | unchanged | Gated by `codersdk.ExperimentChatVirtualDesktop` (`chat-virtual-desktop`); routes wrapped in `RequireExperimentWithDevBypass`. | | `platform-controls/spend-management.md` | `["premium"]` | unchanged | `premium` is a licensing tag, not a maturity tag, and the page states budget endpoints require a license that includes AI Gateway. Flagging for awareness rather than changing it. | | All other `ai-coder/agents` pages | none | none | No maturity tags to review. | **Licensing mechanics** `enterprise/coderd/license/license.go` decodes `agent_runtime_hours_allocation`, `agent_runtime_hours_limit_soft`, and `agent_runtime_hours_limit_hard` into `codersdk.FeatureAgentRuntimeHours` on Premium licenses. `enterprise/coderd/x/chatd/agentadmission.go` lifts the concurrency cap only when that feature is enabled and usage is below the hard limit. So the entitlement, not the license tier alone, is what removes the cap. Wording is now "Premium license with the Agent Hours entitlement". `licensing-usage.md` keeps "Agent Time" as the name of the measured usage metric (matching `runtime_ms` reporting) and now says explicitly that Agent Time draws down the Agent Hours entitlement. Renaming the metric itself was left out of scope; flagging in case the product team wants one name for both. **Validation** `pnpm install --frozen-lockfile`, `pnpm run format-docs` (no table changes), `pnpm run lint-docs` (499 files, 0 errors). </details> Linear: DOCS-718 https://linear.app/codercom/issue/DOCS-718 Generated by Coder Agents on behalf of @nickvigilante. |
||
|
|
29995c9ef8 | feat: add a per-template workspace rename setting (#27558) | ||
|
|
827c11f43a |
docs: update Coder Agents admin navigation to AI Settings (#28572)
## Summary Fixes stale admin navigation on the three Coder Agents platform-controls pages not covered by #28482: the retention pages pointed at tabs that do not exist ("Behavior", the old Agents settings "Lifecycle" tab), and template optimization pointed at the removed **Agents** > **Settings** > **Manage Agents** navigation. Breadcrumbs follow the **Admin settings** > **AI** > **Coder Agents** convention established by #28482. ## Changes - `docs/ai-coder/agents/platform-controls/chat-retention.md`: replace the nonexistent **Behavior** tab with **Admin settings** > **AI** > **Coder Agents** > **Lifecycle**. - `docs/ai-coder/agents/platform-controls/chat-debug-retention.md`: same page, same convention, aligned with chat-retention.md. - `docs/ai-coder/agents/platform-controls/template-optimization.md`: replace **Agents** > **Settings** > **Manage Agents** > **Templates** with **Admin settings** > **AI** > **Coder Agents** > **Templates**. ## Scope note The first revision also fixed `models.md`, `advisor.md`, `chat-debug-logging.md`, and `platform-controls/index.md`. Those pages are covered by #28482 with the same corrections, so this PR reverted them to avoid conflicting edits and adopted that PR's breadcrumb convention instead. Linear: DOCS-715 https://linear.app/codercom/issue/DOCS-715 <details><summary>Analysis evidence</summary> - `site/src/router.tsx` (~735-780): `/ai/settings/{lifecycle,templates,coder-agents}` are the live routes; `/agents/settings/{admin pages}` (~833-890) are `Navigate`-only redirects. - No "Behavior" tab exists in any router revision; `RetentionPeriodSettings` and `DebugRetentionSettings` both render in `LifecyclePageView.tsx`. - Breadcrumb convention **Admin settings** > **AI** verified against `AdminSettings.tsx` / `AISettingsSidebarView.tsx` in #28482. </details> Generated by Coder Agents on behalf of @nickvigilante. |
||
|
|
31a976b288 |
chore(docs): update release docs for v2.35.6 (#28577)
Automated docs update for v2.35.6 release. Created by `releasetui`. |
||
|
|
409049d1d8 |
docs: document org-scoped admin configuration for agents (#28482)
## Summary Documents the organization scoping of Coder Agents admin configuration, and corrects the navigation paths, access wording, and default-model instructions that no longer matched the product. Documentation only, no product code changes. Follows #28473, which moved the organization model overrides into an **Organization settings** section on **Admin settings > AI > Coder Agents**. #28473 is merged, so this targets `main`. Closes CODAGT-715 ## Changes - **New page** `docs/ai-coder/agents/platform-controls/organizations.md`, registered in `docs/manifest.json`. Covers the deployment/organization scope split, what an upgrade changes, how an organization without models or MCP servers behaves, who configures each scope, and how model and MCP server sharing works. - **`models.md`**: models are organization-scoped while providers and their credentials stay deployment-wide. Each organization has one default model, and the first model added becomes it. Documents the **Default** badge and the **Set as Coder Agents default model** form option in place of the star action that no longer exists, and points model overrides at **Organization settings**. - **`getting-started.md`**: replaces the Owner-only claim with the actual access model, adds an organization selection step, and uses the **Add model** label. - **`platform-controls/index.md`**: separates deployment-wide controls from organization-scoped controls, documents personal model overrides alongside the model configuration section rather than as an enforcement caveat, and adds `--agents-allowed` plus the `agents-allowed:` search filter. - **`platform-controls/advisor.md`** and **`platform-controls/virtual-desktop.md`**: the Advisor and Virtual desktop settings are cards in the **Deployment settings** section of the **Coder Agents** page, not sidebar entries. **Providers** is a sidebar item, not a tab. - **`platform-controls/mcp-servers.md`**: slug uniqueness is per organization, the **Add server** label is correct, and the page is no longer described as admin-only. - **`architecture.md`**, **`index.md`**, **`tasks-to-chats-migration.md`**: organization scope corrections and organization-scoped model routes. - Breadcrumbs standardized to **Admin settings > AI > ...**, verified against `AdminSettings.tsx` and `AISettingsSidebarView.tsx`. <details> <summary>Review follow-ups</summary> The first revision leaked implementation detail into user-facing pages and contained several claims that did not match the UI. The follow-up commit removed and corrected the following. Removed as implementation detail: - The `chat_model_config` RBAC resource, its actions, and the token scopes. - The `available`, `unavailable_reason`, and `unsupported_providers` response fields, along with the `missing_api_key`, `fetch_failed`, and `user_api_key_required` values. - Role and permission tables, ACL storage mechanics, audit internals, and JSON error bodies on the new organizations page. - Migration mechanics with no effect on what a user does. Corrected against the product: - The star action does not exist. The list shows a **Default** badge and the add/edit form offers **Set as Coder Agents default model** (`ModelRow.tsx`, `ModelFormFields.tsx`). - The first model added to an organization becomes its default automatically (`coderd/exp_chats.go`). An organization without a stored default does not fail every chat, so that claim is gone. - Advisor and Virtual desktop are cards under **Deployment settings** (`CoderAgentsPageView.tsx`). - The MCP servers page does not require deployment configuration edit permission (`AISettingsSidebarView.tsx`). - Button labels: **Add model** and **Add server**. - The organization query parameter is `org`, confirmed by `modelOrganizationSearchParam`. Left alone deliberately: pre-existing defects outside the introduced text, including the `/api/v2/chats` route prefixes, create-chat examples missing `organization_id`, and the pre-existing MCP permissions table. </details> ## Testing - `pnpm run format-docs` and `pnpm run lint-docs` pass. - `make lint/emdash` passes. No emdash or endash added. - `grep -rn "Defaults & overrides" docs/` returns nothing. - Every navigation path, button label, and access claim checked against the corresponding file under `site/src/pages/AISettingsPage/` and `site/src/modules/management/`. --- This pull request was generated by Coder Agents on behalf of @ethanndickson. |
||
|
|
c07bde80c4 |
fix: use medium badges and shorter shared key label on agent API keys page (#28557)
On `/agents/settings/api-keys`, bumps the provider key status badge and the enabled model badges from `sm`/`xs` to the medium (`md`) size, and shortens the status label "Using shared key" to "Shared key". Updates the matching Storybook assertion and the status indicator name in `docs/ai-coder/agents/models.md`. Verified with `tsc` and the page's Storybook tests (15 passing). --- *Created by Coder Agents on behalf of @tracyjohnsonux.* |
||
|
|
79856ebd15 |
docs: reference Coder Agents instead of Coder Tasks in style guide (#28539)
## Summary Updates the Caitlin the Citizen Developer persona in the style guide to reference Coder Agents instead of the deprecated Coder Tasks product. ### Changes * `docs/.style/style-guide/audience-and-scope.md`: replaced both "Coder Tasks" mentions with "Coder Agents" in the Caitlin persona description and Coder surface list. <details><summary>Context</summary> Coder Tasks is deprecated as of v2.34 and replaced by Coder Agents. This change keeps the internal style guide's persona reference aligned with current product naming. </details> --- This PR was drafted by a Coder Agent on behalf of Matt. |
||
|
|
7cf24b1a37 |
docs(docs/ai-coder/agents): document the chat search: full-text filter (#28474)
Documents the `search:` full-text filter in `docs/ai-coder/agents/chat-search-syntax.md` and drafts the release notes copy for chat history search. Describes `main` plus #28319, which must merge first: stemming and stopword removal apply to message content only; titles and PR titles keep `simple` whole-word matching. All claims verified against the implementation. Two statements in CODAGT-727 were inaccurate and are documented as the code behaves: - A value that tokenizes to no searchable words returns an **empty list**, not HTTP 400 (`TestListChats_Search/NoSearchableWordsReturnsEmpty`). HTTP 400 covers only an empty `search:` value, unsupported keys, bare terms, or combining `search:` with `title:`, `pr_title:`, or `pr:` (`coderd/searchquery/search.go`). - Results are **paginated** with the standard chat list cursor (default 50), not a bounded non-paginated set (`GetChats`, `listChats`). Ordering is pinned first, then most recently updated. Also verified: message bodies are indexed by the dbpurge sweep (10-minute tick, up to 5 x 10,000 rows, newest first) while titles and PR titles are searchable immediately; numeric values match exact PR numbers; `title:`, `pr_title:`, and `repo:` retain `ILIKE` semantics. `pnpm run format-docs` and `pnpm run lint-docs` pass. Closes [CODAGT-727](https://linear.app/codercom/issue/CODAGT-727/chat-search-documentation-and-release-notes) <details> <summary>Release notes draft</summary> ### Chat history search You can now search your chat history by what was said, not just by title. The chat list `q` parameter accepts a new `search:` filter that performs full-text search across chat titles, pull request titles, PR numbers, and message content, for example `?q=search:"kubernetes restart"`. Matching is case-insensitive and supports quoted phrases, `OR`, and `-` negation. Message content additionally matches English word stems (`refactor` finds a message containing `refactoring`). `search:` cannot be combined with the `title:`, `pr_title:`, or `pr:` filters; the existing substring filters are unchanged. **Upgrading with existing chat history:** the message search index is populated by a background job (every 10 minutes, up to 50,000 messages per run, newest first). Upgrades are not blocked, and chat reads and writes are unaffected while the index fills. Older messages appear in search results progressively; messages indexed before the stemming upgrade match exact word forms until re-indexed. Titles and PR titles are searchable immediately. </details> --- 🤖 This PR was generated by Coder Agents on behalf of @f0ssel. |
||
|
|
378ac91b49 |
fix: stem chat search terms with the english text search config (#28319)
Chat message search used the `simple` text search config, which folds case but does no stemming, so `refactor` did not match messages containing `refactoring`. Message bodies now index and query with the `english` config, which stems both sides. Titles, PR titles, and all other filters are unchanged. Migration `000585` adds a `chat_messages.search_tsv_config` enum column recording which config produced each stored vector and stamps existing vectors `'simple'` (a column-only update; vectors and indexes are untouched, since rewriting `search_tsv` would block message writes on large tables). The dbpurge sweep gains a bounded `ReindexStaleChatMessagesSearchTsv` pass that rewrites stale vectors newest first and stops for the process lifetime once drained. Until a row is rewritten, `GetChats` matches it with the config that produced it, so pre-migration exact-form searches keep working during the drain. Vectors written by old binaries mid rolling upgrade cannot stamp the config, stay pending, and self-heal on the next upgraded sweep. Steady-state DB load is unchanged. The down migration resets `english` vectors to `NULL` so the parent version's existing sweep rewrites them with `simple`. Closes [CODAGT-867](https://linear.app/codercom/issue/CODAGT-867/backend-search-improvements-to-handle-partial-matches) <details> <summary>Decision log</summary> - **`english` stemming over prefix matching.** On a 300k-row corpus a stemmed lookup is a single GIN entry-tree probe, while `term:*` prefix matching unions posting lists of every lexeme sharing the prefix (up to a full scan for short prefixes), is asymmetric, and loses the `websearch_to_tsquery` phrase/OR/negation UX. `pg_trgm` rejected as a heavier index with different semantics. - **Scope to message bodies only** (review). Titles and PR titles keep their `simple` FTS expressions and indexes, identical to main. - **Config-versioned queue over migration-time reset** (review). An unqualified `UPDATE ... SET search_tsv = NULL` rewrites every indexed row in the migration transaction, and an old replica could backfill reset rows with `simple` vectors that permanently leave the `search_tsv IS NULL` queue. The config column makes staleness explicit and both problems disappear. - **Enum over `text`** (review) for the config column. - **Trade-offs.** English stopwords are dropped from message search (stopword-only queries match no messages); stemming is English-specific; titles do not stem; prefix matching remains out of scope. </details> --- 🤖 This PR was generated by Coder Agents on behalf of @f0ssel. |
||
|
|
eedc0ece47 | fix: avoid 500 when generating chat title from rename dialog and remove unused endpoint (#28306) | ||
|
|
26de6140fb | feat: add flag to disable workspace agent context sync (#28522) | ||
|
|
b6d7653231 |
fix: apply MCP server selection when editing a chat message (#28471)
## Problem
Editing a user message in an Agent chat silently dropped the MCP server
selection. The composer renders the MCP picker in edit mode and toggles
update local client state, so the picker displayed the new selection,
but the edit request omitted `mcp_server_ids` at every layer (frontend
request builder, `codersdk.EditChatMessageRequest`, the `PATCH
/chats/{chat}/messages/{message}` handler, and `chatd.EditMessage`). The
chat's persisted selection never changed and the regenerated turn ran
without the newly enabled MCP tools. Two dogfood users hit this within
hours; there is no error anywhere and the UI shows the opposite of the
server state.
## Changes
- `codersdk`: add `MCPServerIDs *[]uuid.UUID` to
`EditChatMessageRequest`, mirroring `CreateChatMessageRequest` (nil
preserves the current selection).
- `chatd`: extract the send path's MCP update block into one shared
`applyRequestedMCPServerIDs` helper (explore-subagent snapshot
immutability guard plus Force On enforcement, Cure53 CDM-02-010) and
call it from both `SendMessage` and `EditMessage`, so enforcement cannot
drift between the two paths.
- `coderd`: extract the send handler's request validation (dedupe,
enabled-in-organization check, persisted-ID exemption) into
`normalizeRequestedChatMCPServerIDs` and wire it into the edit handler,
which now threads the selection into `EditMessageOptions`.
- Frontend: the edit request now includes `mcp_server_ids:
[...effectiveMCPServerIds]`, exactly like the send path, making the
picker's displayed state real.
- `make gen` artifacts (swagger, API docs, `typesGenerated.ts`).
## Tests
Each layer is covered and was proven with independent red toggles
(removing one layer's wiring fails only that layer's tests):
- chatd (`TestEditMessage_MCPServerIDs`): edit applies a provided
selection, nil preserves it, an emptied list cannot remove a `force_on`
server, and explore subagent chats keep the spawn-time snapshot.
- API (`TestPatchChatMessage/MCPServerIDsApplied`,
`MCPServerIDsInvalidRejected`): persistence via the endpoint, omission
preserves, unknown IDs get the same 400 as the send path.
- Storybook (`EditAppliesMCPServerSelection`): toggling a server on
during an edit puts it in the edit request payload.
Note: the `AgentChatPage.stories.tsx` story "Queued For Capacity After
Polling" fails locally on current main as well (verified against the
main baseline with this branch's changes reverted); it is unrelated to
this diff.
Remote dogfood UAT ran on the exact head and passed, including proof
that the regenerated turn actually gains the newly enabled MCP server's
tools.
> 🤖 Xum acted on Mike's (@ibetitsmike) behalf. • Model:
`anthropic:claude-fable-5`
<!-- xum-attribution: model=anthropic:claude-fable-5 -->
|
||
|
|
76962273d8 |
feat: add coder organizations edit for default member roles (#28227)
`default_org_member_roles` was only settable through Terraform or a raw
`PATCH /api/v2/organizations/{organization}`. This adds a CLI
equivalent.
```console
coder organizations edit --org acme --default-org-member-roles organization-workspace-access,organization-template-admin
coder organizations edit --org acme --default-org-member-roles "" # grant no roles
```
- The flag replaces the organization's current list rather than adding
to it. It accepts a comma-separated list, may be repeated, and
de-duplicates. An empty value removes every role.
- Removing a role prompts for confirmation, listing the roles being
removed, unless `-y` is passed. Adding roles applies without a prompt.
- The resulting set is printed, so no follow-up command is needed to
confirm what was applied.
- The organization is chosen with `-O/--org` or `$CODER_ORGANIZATION`,
and is implicit when the caller belongs to exactly one organization.
The org update endpoint is behind `FeatureMultipleOrganizations`, so the
command is enterprise/premium only. Non-built-in roles are rejected by
the server, and `dbauthz` already prevents a caller from setting a
default they could not assign to a member individually.
<details>
<summary>Implementation notes</summary>
## Shape
`codersdk.UpdateOrganizationRequest.DefaultOrgMemberRoles *[]string` and
the enterprise `patchOrganization` handler already existed; only the CLI
was missing, and nothing in `cli/` called `UpdateOrganization` at all.
No server or SDK changes are needed.
`organizations edit` follows the partial-update pattern of `coder groups
edit`: check `ParsedFlags().Changed(...)`, populate only the fields the
user set, and leave the rest nil so the handler preserves them. Passing
no flags is an error.
Two earlier shapes were tried and dropped:
- **A `default-org-member-roles` entry in the `organizations settings`
registry.** Every sibling entry unmarshals an SDK type that maps 1:1 to
a dedicated endpoint; this field has no such endpoint. Reusing
`UpdateOrganizationRequest` for the piped JSON meant a stray `name`
field could rename the org, and the documented `show > file`, edit, `set
< file` workflow failed on the default organization, whose name is
reserved.
- **A `--default-org-member-roles` flag on `organizations create`.**
`create` cannot set display name, description, or icon either, so a
roles-only flag there is asymmetric. It would also require
`CreateOrganizationRequest` to grow the field plus server-side
validation.
## Known sharp edge
`serpent.StringArray` resets its slice when any flag value is empty, so
`--default-org-member-roles a --default-org-member-roles ""` clears the
list rather than keeping `a`. This is pinned by a test and called out in
a code comment. The confirmation prompt catches it interactively; a
script passing `-y` would not. A custom flag value that ignores empty
occurrences was prototyped and reverted as more machinery than the
behavior warranted.
## Validation
Manually verified against a local dev deployment with a premium license:
setting, adding, clearing, prompt accepted and declined, duplicate and
whitespace handling, invalid role rejection, and a plain member being
denied. Confirmed with `/authcheck` that a member loses workspace-create
access when the roles are cleared and regains it when restored, and that
the change is recorded in the audit log.
## Follow-ups worth considering
- `--name`, `--display-name`, `--description`, and `--icon` on
`organizations edit`; all already exist on `UpdateOrganizationRequest`.
Rename deserves its own discussion.
- Setting the defaults to `organization-admin` makes every member an org
admin, including the ability to edit the org further, and those users
still render as plain members in member listings. Reachable today via
the API, UI, and Terraform; a warning on privileged roles would help.
- `dbauthz.InsertOrganization` has no `canAssignRoles` check, unlike
`UpdateOrganization`. Harmless while the only caller passes the
deployment default constant.
- `docs/admin/users/organizations.md` still points users at the
dashboard or a raw `PATCH`.
</details>
---
Created by Coder Agents on behalf of @jscottmiller.
|
||
|
|
3bdd395faa |
docs(docs/admin/templates/extending-templates): clarify coder_env merge_strategy does not include host environment (#28466)
`merge_strategy` on `coder_env` only combines values across `coder_env` resources targeting the same name. It does not know about the value already set by the workspace's image or host, such as the base `PATH`. A user hit this in #27501: appending to `PATH` with three `coder_env` resources replaced the host's `PATH` entirely, since none of them referenced the existing value. Clarify this in the docs, and update the `PATH` example to show referencing `$PATH` in one of the resources so the host's original value is kept. Refs #27501 <details> <summary>Investigation notes</summary> - `coderd/provisionerdserver/provisionerdserver.go` builds the merged env starting from an empty map (`MergeExtraEnvs`), only combining `coder_env` resources with each other. - `agent/agent.go`'s `updateCommandEnv` applies the result with `os.ExpandEnv(v)`, which expands `$VARNAME` against the agent's real process environment. - Confirmed with a standalone repro that `value = "$PATH:/usr/local/cuda/bin"` preserves the host's real `PATH` and appends the extra directory, and that this composes correctly across multiple `coder_env` resources with `merge_strategy = "append"` as long as one of them references `$PATH`. - A companion PR updates the `terraform-provider-coder` schema description and example that feed the registry docs: https://github.com/coder/terraform-provider-coder (branch `bpmct/docs-env-merge-host`). </details> --- *Opened by Coder Agents on behalf of @bpmct.* --------- Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com> |
||
|
|
042421694f |
fix: surface duplicate AI provider hostname as warning, not error (#28208)
Two live `ai_providers` rows can share the same `base_url` hostname.
This is an intended configuration (AIGOV-97): the direct routing path
(`/api/v2/ai-gateway/{name}/...`) routes by provider name and fully
supports it. Only the MITM proxy path (`aibridgeproxyd`) cannot
disambiguate, because its `nameByHost` map is 1:1.
`classifyProviderRow` marked the second provider as
`ProviderStatusError`, which surfaced as a misleading log warning and a
`status=error` Prometheus label. The provider is not actually broken; it
is fully routable via the direct path. The error was only visible in
server logs and Prometheus, not in the API or UI.
This change:
- Extracts `BaseURLHostname` into `coderd/aibridged` and eliminates
three duplicate implementations (proxy classifier, chatprovider, and the
API layer now uses it for status checks).
- Downgrades the proxy classifier from `ProviderStatusError` to a new
`ProviderStatusProxyExcluded` status. The provider is not broken; it is
just not reachable via the MITM proxy path.
- Adds `Status *AIProviderStatus` to `codersdk.AIProvider` with
`Warnings []string`, matching the `WorkspaceProxy` precedent.
- Populates warnings on create, update, GET, and list when a provider's
hostname collides with another enabled provider.
- Adds an integration test proving that a duplicate provider is routable
via the direct path but not via the proxy path.
Refs AIGOV-596
Generated by Coder Agents
https://github.com/user-attachments/assets/bf323837-5121-4d1b-ab19-593f1d32374e
|
||
|
|
defd5d48a1 |
docs: clarify that Coder server should run on a separate machine (#27468)
Closes [DOCS-600](https://linear.app/codercom/issue/DOCS-600/make-it-clear-in-the-quickstart-that-you-should-install-the-coder). The Quickstart guide has readers install and run `coder server` on the same machine they're working from, without calling out that this is only for a quick trial. Coder's actual value comes from remote development, so hosting the Coder server on a separate machine has a lot more power than running it locally. This adds two notes to `docs/get-started/index.md`: - In **Prerequisites**, explaining that this quickstart runs the server locally for convenience, but a real deployment should host it on a separate machine, and linking to the [Install](https://coder.com/docs/install) page. - In **Step 2: Install and start Coder**, a shorter reminder right before the install commands. ---- Created using Coder Agents 🤖 --------- Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com> |
||
|
|
df6a33ad37 |
docs: fix broken extending-templates examples (dynamic-parameters, docker-in-workspaces) (#28100)
## Summary Fixes broken examples on the `extending-templates` pages surfaced by the DOCS-637 runtime drift sweep, and completes the dangling `external-auth` reference. Verified against `main` and the CLI source (`coder templates init --id`, `coder templates push`). ## Changes | Page | Fix | |------|-----| | `dynamic-parameters.md` | The **Dynamic Validation** snippet referenced an undefined `show_cpu_cores` through a stray `count = data.coder_parameter.show_cpu_cores.value ? 1 : 0` line copied from the earlier Hide/Show Options example. Standalone copy failed `terraform validate`. Removed the `count` line (and its misleading "Only show this parameter" comment) so the snippet is self-contained. The valid occurrence in the Hide/Show example (where `show_cpu_cores` is defined) is untouched. | | `docker-in-workspaces.md` | The rootless Podman step used `echo "kubernetes-with-podman" \| coder templates init`, but `kubernetes-with-podman` isn't a built-in starter id, so the command scaffolds nothing. Step 4 now starts from the built-in `kubernetes` starter (`coder templates init --id kubernetes`) and shows the minimal Podman delta inline: the maintained `codercom/example-podman:ubuntu` base image, the `container.apparmor.security.beta.kubernetes.io/dev = unconfined` pod annotation, and a `github.com/fuse` device limit. The starter already runs as UID/GID 1000 with `run_as_non_root`, which rootless Podman needs. Also switches the deprecated `coder templates create` to `coder templates push`. Follow-up from review: swapped `ghcr.io/coder/podman` for the maintained `codercom/example-podman:ubuntu` image and linked its source, added a note that the AppArmor annotation is deprecated on Kubernetes 1.30+ (`securityContext.appArmorProfile`), and added an EKS Auto Mode caveat to the Bottlerocket section. | | `external-auth.md` | The **External Auth Providers** sentence dead-ended at "...in the Coder Control Plane. See" with no link. Completed it with a link to the admin External Authentication page (`../../external-auth/index.md`), the target chosen in review. | ## Dependency The `docker-in-workspaces.md` example now references `codercom/example-podman:ubuntu`, published by coder/images#339. That PR is approved but should merge (and publish the image to Docker Hub) before this PR merges, so the docs don't point at an image that doesn't exist yet. Linear: https://linear.app/codercom/issue/DOCS-645 > This PR was created with AI assistance (Coder Agents). --------- Co-authored-by: Ben Potter <me@bpmct.net> Co-authored-by: Ben Potter <ben@coder.com> |
||
|
|
54d4ae9aca | fix: detect Xum client in aibridge after Mux rename (#28448) | ||
|
|
c275327fb7 |
feat: audit operational agent settings (#28369)
Depends on #28394. Audit changes to seven operational agent settings through a typed `chat_operational_settings` resource. Each handler serializes baseline capture and mutation with a per-setting advisory lock, keeps both operations in one transaction, and records field-specific diffs. Effective no-op requests now skip both the database upsert and the success audit, including absent rows whose effective value is already the default. The change also retains the allowlisted raw `site_configs` query, updates generated audit and API surfaces, adds failure and concurrency coverage, and reuses the existing AuditPage resource-filter story pattern. Relates to CODAGT-720. |
||
|
|
0ef331fcd0 |
feat!: scope chat model override settings to organizations (#28442)
Moves chat model override settings from deployment scope to organization scope, following the org-scoping of chat model configs in #28440. Depends on #28440 (based on `ethan/chat-models-availability-consolidation`). Remote dogfood UAT passed on this change, including multi-organization scenarios on a licensed deployment with real models. ## Problem Chat model configs are now owned by organizations, but the override settings that reference them stayed deployment-wide: admin subagent/title/compaction overrides in `site_configs`, personal overrides in `user_configs`, and the advisor model embedded in the advisor runtime JSON. A single deployment-level override can only point at one organization's model config, so overrides effectively worked for the default organization only, and title generation had to soften its hard-failure contract to tolerate unusable overrides. ## Fix Admin overrides (general, explore, title generation, compaction, advisor) and personal overrides (root, general, explore) become organization-scoped rows in two new typed tables, `chat_organization_model_overrides` and `chat_user_model_overrides`. Composite foreign keys on `(organization_id, model_config_id)` make cross-organization model references unrepresentable. The migration deletes the legacy serialized `site_configs`/`user_configs` override keys instead of migrating them; admins and users re-select models in the new organization-scoped settings. New endpoints live under `/api/experimental/organizations/{organization}/chats/model-overrides` (admin defaults) and `/api/experimental/organizations/{organization}/members/{user}/chats/model-overrides` (personal overrides); the legacy deployment-level routes are removed. chatd resolves overrides via the chat's organization, restoring the hard-failure contract for configured-but-unusable title generation overrides. The deployment-level `AllowUsers` switch and advisor runtime limits stay deployment-scoped. The Coder Agents settings UI gains a per-organization "Defaults & overrides" page under AI Settings > Models, and the personal overrides page gains an organization selector for users in multiple organizations. Advisor telemetry now reports per-organization override rows. **BREAKING CHANGE**: Existing deployment-level overrides are dropped, not migrated. Every organization starts with no overrides after upgrade, and model selections must be re-applied in the new settings. The legacy deployment-level override endpoints are removed. Closes https://linear.app/coder/issue/CODAGT-872 > [!NOTE] > Xum (AI agent) authored this PR on behalf of @ibetitsmike. <!-- xum-attribution: model=claude-opus-4-6 thinking=high --> --------- Co-authored-by: Ethan Dickson <ethan@coder.com> |
||
|
|
3809314b31 |
refactor!: consolidate chat model availability (#28440)
## Summary Consolidate chat model management and runtime availability into a single organization model collection contract. The organization model response now includes provider availability, unavailable reasons, and unsupported-provider details, so callers no longer need a separate availability request. Depends on #28439. ## Problem The organization-scoped API exposes two overlapping representations of the same chat models: ```text GET /api/experimental/organizations/{organization}/chats/models GET /api/experimental/organizations/{organization}/chats/models/available ``` The collection route returns organization-owned model configurations and redacted provider descriptors. The availability route returns a second catalog-oriented response with provider availability and synthetic catalog entries. Callers must fetch, reconcile, and cache both responses even though they describe the same effective set of models. This creates duplicated server logic and separate SDK, OpenAPI, and frontend types. It can also produce inconsistent client state when one request succeeds while the other fails or when the two responses are refreshed at different times. ## Fix Return all model-management and runtime-availability information from the organization model collection route: ```text GET /api/experimental/organizations/{organization}/chats/models ``` `OrganizationChatModelsResponse` continues to return the caller-readable organization model configurations and redacted provider descriptors. Provider descriptors now also include: - `available` - `unavailable_reason` The response additionally includes `unsupported_providers`, allowing clients to explain configurations that the Agents harness cannot use. Frontend model settings, agent creation, existing chat recovery, and model override surfaces now consume this single collection query. This keeps model ownership, caller visibility, provider availability, and unsupported-provider guidance in one cache entry and one authorization path. The default-organization compatibility route remains available: ```text GET /api/experimental/chats/models ``` It now returns the same consolidated `OrganizationChatModelsResponse` contract instead of the former availability response. ## Breaking change This removes the separate organization model availability endpoint: ```text GET /api/experimental/organizations/{organization}/chats/models/available ``` Clients using that endpoint must migrate to: ```text GET /api/experimental/organizations/{organization}/chats/models ``` This PR also removes the corresponding public experimental client and schema surface: - `ExperimentalClient.ChatModelAvailability` - `ChatModelAvailabilityResponse` - `ChatModelProvider` - `ChatModelCatalogEntry` - The generated OpenAPI schema and frontend API/query types for the availability endpoint Callers must use `ExperimentalClient.ChatModels` and `OrganizationChatModelsResponse` instead. Availability is now reported on each `ChatModelProviderDescriptor`, and unsupported providers are returned in `OrganizationChatModelsResponse.UnsupportedProviders`. The default-organization compatibility route retains its URL, but its response schema changes from `ChatModelAvailabilityResponse` to `OrganizationChatModelsResponse`. Direct HTTP clients and generated clients that decode the previous response type must be updated even if they do not use the removed organization-scoped `/available` route. |
||
|
|
a53c6e6366 |
feat: audit chat model config changes (#28439)
Adds audit logging for chat model config creates, updates, deletes, ACL changes, and automatic default-model transitions. Audit records use the config display name or ID as the target and are emitted only after successful transactions. Depends on #27960. |
||
|
|
75ad45109d |
feat: use organization-scoped chat models (#27960)
Move chat model settings to `/ai/settings/organizations/:organization/models`, add an organization switcher, and redirect legacy settings paths. The pages use organization permissions for read, create, update, and delete controls. They use redacted provider descriptors. Agent creation loads models for the selected organization. Existing chats load models for the chat organization. Chat views warn about unavailable historical models, select a usable local recovery model, and disable generation when none exists. Settings that aggregate organizations retain successful results when one request fails. Depends on #27959 _This pull request description was generated by Coder Agents._ |
||
|
|
aefc761fcf |
feat!: enforce organization scope for chat models (#27959)
## Summary Move chat model management and runtime availability to organization-scoped API routes. The API now identifies the organization in every model-management operation, applies chat model RBAC and ACLs within that organization, and returns only models the caller can use there. Depends on #27958. ## Problem Chat model configurations now belong to organizations, but the existing experimental management and discovery APIs are deployment-shaped. Their routes do not identify an organization, SDK methods do not accept an organization ID, and item operations address a model configuration by ID alone. That contract cannot safely expose organization-owned model configurations. It also does not provide the organization-specific provider information, authorization behavior, ACL management, or recovery guidance needed by the settings and chat clients later in the stack. ## Fix Add organization-scoped chat model endpoints under: ```text /api/experimental/organizations/{organization}/chats/models ``` The new API provides: - Organization-scoped model listing and creation. - Model get, update, and delete operations scoped by both organization and model ID. - Model ACL read and update operations. - Organization-scoped runtime availability and provider status. - Redacted provider descriptors for model-management clients. - Chat model RBAC and row ACL enforcement for writes, runtime discovery, and Chatd model selection. - Recovery guidance when a chat's organization has no usable local model. Historical chats can retain a model reference owned by another organization, but runtime selection uses an authorized local default when one is available. Foreign, inaccessible, disabled, and otherwise unusable models are not exposed as usable selections for the organization. The existing collection and availability routes remain as default-organization compatibility endpoints in this layer: ```text GET /api/experimental/chats/model-configs POST /api/experimental/chats/model-configs GET /api/experimental/chats/models ``` These compatibility routes do not provide access to models in non-default organizations. The previous unscoped item update and delete routes are removed because item operations must include the owning organization. ## Breaking change This changes the experimental HTTP API and Go SDK contracts for chat model management. Clients must migrate model-management and availability requests to the organization-scoped routes and provide an organization identifier. In the Go SDK, the following methods now require an organization ID: - `ChatModelAvailability` - `ChatModels` - `CreateChatModel` - `UpdateChatModel` - `DeleteChatModel` `ChatModels` also changes its return value from `[]ChatModel` to `OrganizationChatModelsResponse`, which includes the organization models and the redacted provider descriptors required by management clients. New item and ACL methods likewise require both the organization ID and model ID. The default-organization compatibility routes reduce the immediate impact for collection and availability callers, but they do not preserve the previous item API or SDK method signatures. Generated clients and direct consumers of the experimental API must be updated before adopting this change. _This pull request description was generated by Coder Agents._ |
||
|
|
9bd8899359 |
refactor: clarify chat model resource names (#27958)
Rename the admin-managed SDK resource from `ChatModelConfig` to `ChatModel`. Rename catalog entries from `ChatModel` to `ChatModelCatalogEntry` and replace `ChatModelsResponse` with `ChatModelAvailabilityResponse`. API routes and JSON fields do not change. Experimental SDK and OpenAPI schema names do change. Depends on #27957 _This pull request description was generated by Coder Agents._ |
||
|
|
cbe24a3da5 |
feat: add chat model config RBAC resource (#27957)
Add the `chat_model_config` RBAC resource, actions, and API key scopes. This layer includes the scope migration and generated API types. Model list queries apply token scopes, SQL authorization filters, and row ACLs. Write and default-management handlers keep deployment configuration authorization until the next API layer replaces it. Depends on #27956 _This pull request description was generated by Coder Agents._ |
||
|
|
4ce8e8ed56 |
docs: address AI Gateway documentation review feedback (#28309)
Applies the review feedback from #27794. Prose only. --------- Co-authored-by: Nick Vigilante <nickvigilante@users.noreply.github.com> |
||
|
|
cb52f5339d | feat: show a premium paywall on the external auth settings page (#28435) | ||
|
|
a2afb458c5 |
feat: add premium paywall conversion telemetry (#28425)
**TLDR:** When someone using free Coder taps a "Start trial for free" button, we now write down which page they tapped it on, and whether they went on to actually start the trial. That tells us which locked features make people want Premium. ## Brief summary - Records two things: the button being clicked, and a trial actually being started. - Each click gets a random ID that travels with the person to the trial form, so we can tell who finished from who walked away. - Records a short label for the page (like `appearance` or `audit_log`), never the web address, so no customer names are collected. #28226 added the trial form and has since merged, so this now targets `main`. ## Description Today the "Start trial for free" button just moves you to the Premium page and nothing is recorded, so there is no way to know which feature convinced someone. This adds two events: | Event | Happens when | |---|---| | `cta_click` | Someone clicks "Start trial for free" | | `trial_signup` | A trial licence is actually issued | The click generates a random ID. It rides along to the trial form and gets saved on the signup, so a click and a signup can be matched up later. Clicks with no matching signup are the people who gave up. Trials started without a paywall (for example straight from the sidebar) are labelled `direct`, so "no label" never gets confused with "broken". A few small choices worth knowing: - The ID is kept in browser session storage, not in the web address, so the URL stays clean. It expires after 30 minutes. - The trial signup event is recorded by the server, not the browser, so nobody can fake a signup. - The browser cannot say which kind of event it is sending. It can only report a click; the server labels it. - Only people allowed to start a trial are counted. The server rejects the rest, which matters because a few paywalls show their button to everyone. - Nothing is recorded at all when telemetry is turned off. There is deliberately no "paywall was shown" event. Every admin page visit would report one, which is a lot of noise for little insight. Every paywall in the [Premium Copy Updates](https://www.notion.so/coderhq/Premium-Copy-Updates-3c1d579be59280a59557cabd54fd8a1f) table has a label. The rows that do not are badges and alerts with no button to click. ## Proof Ran a local Coder with no licence and clicked the paywall button on the Appearance page. What the browser sent, and the reply: ```json POST /api/v2/deployment/premium-funnel-events -> 204 No Content {"id":"92d96afa-f975-4789-b8a7-b1fd19d4df85","source":"appearance","variant":"premium"} ``` What the browser saved for the trial form, same ID: ```json {"id":"92d96afa-f975-4789-b8a7-b1fd19d4df85","source":"appearance","createdAt":1787268520857} ``` The click then landed on the Premium page with the trial form. Screenshots of the network panel, the saved value, and the Premium page are attached below. The form itself was not submitted, since that would contact the real licence server. ## Testing - Backend tests: a click is recorded with the server's own label, an unknown page label is refused, and a normal member is refused. - Storybook tests: button clicked and ID saved on all three paywall styles, plus the case where the viewer only sees guidance text and has nothing to click. - Unit tests for the saved ID: expiry, bad data, unknown page label, and the `direct` fallback. - `make gen` is clean, and lint, types, and formatting pass. Two stories fail on `main` today and fail the same way here: `ExternalAuthSettingsPageView > Page` and `PremiumPageView > No License`. Neither file is touched by this branch; both were verified failing on `main` with this branch checked out elsewhere. ## Follow-up coder/coder-telemetry-server#45 saves these events for reporting. It merges after this one. <details> <summary>Plan and decisions</summary> **Goal:** find out which Premium paywall drives trial signups, and tell abandons apart from completions. **Options considered and dropped:** 1. Put `?from=appearance` in the URL. Rejected: messy for users. 2. Use router state. Rejected: lost if the page is refreshed. 3. Let the browser report the signup. Rejected: it could be faked or lost during the redirect. 4. Work out the page from the URL. Rejected: URLs contain organization and template names. 5. Report every paywall impression. Rejected: one event per admin page visit is noise, and the click is the signal. 6. Separate event types and tables. Rejected: makes the matching query harder for no gain. **Event shape:** ```go type PremiumFunnelEvent struct { ID uuid.UUID // event ID; for a click this is also the matching ID EventType string // cta_click | trial_signup, set by the server Source string // appearance, audit_log, ..., direct Variant string // which paywall design was shown AttributionID uuid.UUID // the click this signup came from UserID uuid.UUID CreatedAt time.Time } ``` **Where the code lives:** nothing under `components/` imports from `modules/`, so the paywall components stay presentational and only gain an `onCTAClick` prop. New wrappers in `modules/paywall/` own the reporting, one per paywall style, each taking a fixed `source`. **Self-review:** the `frontend-review` skill caught that reading permissions inside the wrapper forced an auth context into presentational page views, which broke their stories. Every call site already passes `canViewPremium={permissions.viewAllLicenses}` and the button only renders when that is true, so the wrapper no longer reads permissions and the server stays the gate. It also flagged one unattributed paywall on the Security page, now wired up as `browser_only`. </details> > Generated with [Coder Agents](https://coder.com/agents) on behalf of @david-fraley |
||
|
|
f44a3b59d7 |
feat: Premium Page CTA updates and form handling (#28226)
* Premium page has form for signing up for a trial * Input validation and normalization for trial form fields * api endpoint definition to make this processes seamless * license page updates to access `?success=true` queryparam and confetti <img width="1512" height="833" alt="Screenshot 2026-08-17 at 10 08 41 PM" src="https://github.com/user-attachments/assets/1766e724-c63a-4da9-b2bd-a98e6b66cc9b" /> <img width="1510" height="874" alt="Aug-17-2026 22-12-38" src="https://github.com/user-attachments/assets/3a2a0f35-1176-4290-aa0d-b3f7d66c15d6" /> ```mermaid sequenceDiagram autonumber actor Owner participant UI as UI<br/>TrialRequestForm + PremiumPage participant API as API<br/>api.ts + coderd middleware participant Coderd as Coderd<br/>postTrialLicense + trialer participant Licensor as Licensor<br/>v2-licensor + Postgres + pubsub + cache Note over UI: mutation.status = "idle" Owner->>UI: fills 10 fields, checks acknowledgement UI->>UI: Yup validate, strip "acknowledged", submit UI->>API: POST /api/v2/licenses/trial<br/>cookie + X-CSRF-TOKEN Note over UI: mutation.status = "pending" API->>API: verify CSRF, apiRateLimiter,<br/>apiKeyMiddleware sets actor API->>Coderd: dispatch Coderd->>Coderd: audit.InitRequest, authorize license:create,<br/>httpapi.Read validates, HasLicense is false Coderd->>Licensor: POST trial request<br/>deployment_id unspoofable, 10s deadline Licensor-->>Coderd: 200, raw signed JWT Coderd->>Coderd: ParseClaimsIgnoreNbf, uuid.Parse Coderd->>Licensor: InsertLicense, updateEntitlements,<br/>publish PubsubEventLicense, commit audit log Licensor-->>Coderd: database.License Coderd-->>API: 201 codersdk.License API-->>UI: 201 codersdk.License Note over UI: mutation.status = "success" UI->>Licensor: invalidate entitlements + licenses keys UI->>Owner: navigate /deployment/premium?success=true Note over Owner: confetti fires on the success param ``` --------- Co-authored-by: Matt Vollmer <matthewjvollmer@outlook.com> |
||
|
|
6348c83142 |
feat: register public clients without a secret (#28046)
Third in the stack, split out of #27873. This is where dynamic client registration first produces a public client. - A registration requesting token_endpoint_auth_method `none` issues no secret and persists the client_type derived in #28043. The response omits client_secret entirely, per RFC 7591 §3.2.1. - Discovery still advertises only the two secret-based methods. `none` is withheld until the token endpoint honors it, so a conforming client is not told to attempt an exchange that would be rejected. - The app and its secret are written in one transaction. They were two independent inserts, so a failed second one left a committed app that could never authenticate while still holding a registration access token. Pre-existing, but a public client's missing secret row makes the orphaned case unspottable by inspection. - registration_client_uri uses url.JoinPath, fixing a trailing-slash access URL producing //oauth2/clients/{id}. - Docs state both limits above, so the page does not describe a flow that returns 400 until #28047 lands. A client registered here cannot obtain a token yet; #28047 adds that. Dynamic client registration is off by default, so none of this is user-visible until then. Refs https://linear.app/codercom/issue/ENG-3029/oauth2-support-public-client |
||
|
|
c6deadbc6c |
docs: fix broken .style/ link and lift details block out of list item [DOCS-661] (#28180)
## What Two small docs fixes, split out of the offlinedocs Fumadocs migration (#27390) so that PR stays a focused tooling change and these land on their own review track. - `docs/about/contributing/documentation.md`: the Vale style-guide link pointed at `docs/.style/style-guide/`, which is excluded from the published docs corpus, so the relative link 404s on the live site. Repointed to an absolute github.com URL. - `docs/ai-coder/ai-gateway/monitoring.md`: lifted the "Possible client values" `<details>` block out of the list item so it renders as a top-level accordion, and restored a dropped blockquote continuation marker (`>`). ## Why Both are genuine docs bugs on their own: - The `.style/` link is broken on the live site today, because that directory is intentionally excluded from the published corpus. - The `<details>` nested inside a list item renders inconsistently; lifting it to a sibling block is portable across renderers. They are also prerequisites for the offlinedocs Fumadocs migration, whose stricter build-time sync hard-fails on unmapped inter-doc links and only converts a `<details>` to an accordion when it is a list sibling. Landing them here first lets the migration PR drop these two files on its next rebase. ## Validation - `make fmt/markdown` and `make lint/markdown`: clean (via pre-commit-light). - Docs-only change; no CI or build config touched. > This PR was created with AI assistance (Coder Agents). |
||
|
|
d989ae36cd |
docs: clarify who can set AI model prices and how they are keyed (#28401)
The docs named the `ai_model_price:update` permission but never said which role holds it, and nothing anywhere explained that a price is keyed by provider type and model, so every configured provider of that type shares it. This names the Owner role, documents the keying and why `openai-compat` cannot be priced, and repeats both in the CLI's `--provider` descriptions and `update` help. > [!NOTE] > Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira |
||
|
|
d3b3aeaabc |
docs: refresh workspace-management screenshots with current UI (#28356)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell. Refreshes the six stale screenshots on the [Workspaces](https://coder.com/docs/user-guides/workspace-management) page (`docs/user-guides/workspace-management.md`) with the current Coder UI, recreating equivalent dummy data (matching template and workspace names) so the images stay recognizable. Linear: DOCS-55 ## Updated images | Image | Section | |-------|---------| | `creating-workspace-ui.png` | Creating workspaces | | `workspace-update.png` | Updating workspaces | | `workspace-automatic-updates.png` | Automatic updates | | `user-guides/workspace-bulk-actions.png` | Bulk operations | | `schedule.png` | Starting and stopping | | `templates/template-variables.png` | Repairing workspaces | Prose is unchanged; this is a screenshot-only refresh. ## Notes / limitations The originals came from production-like deployments with AWS/Azure/Kubernetes templates, multiple users, and aged data. These were reproduced on a local `scripts/develop.sh` (Docker) instance, so: - Template/workspace names, states (Running/Stopped/Failed), the "Selected 3 of 5" bulk selection, `use_kubeconfig=True`/`namespace=default`, and the auto-update = `Always` setting all match. - Instance-derived values cannot match: "used by" counts show `0 developers`, build times show `Unknown`, the connection-latency pill has no value, and the workspace name field is disabled by the template. <details> <summary>How these were produced (decision log)</summary> Followed the `dogfood` skill: 1. Started a local instance with `./scripts/develop.sh` (`v2.36.0-devel`, matching the docs version). 2. Created Docker-backed templates named to match the originals: `data-warehouse`, `analytics`, `api-services`, `frontend-web`, `pod-share-jupyter`, `aws-linux-ephemeral`, `azure-aks-pod`, plus `kubernetes2` ("Develop in Kubernetes"). The JetBrains module was dropped from the Docker template to avoid a plan-time error; `kubernetes2` uses a Coder-only variant declaring the real `namespace` and `use_kubeconfig` variables so it imports with `use_kubeconfig=true`. 3. Created workspaces: `admin/my-frontend` (outdated → update available, with a schedule), `admin/dev` (auto-update = Always), and `kirby/{data-science, aws-large, azure-space, ephem-demo (Failed), kirbs (Stopped)}`. 4. Captured each page with a headless Playwright script, hiding dev-only chrome (the sticky deployment banner, the navbar `::before` hazard stripe, and the `-devel` version chip) and cropping settings pages to the form. Images are 2x for crispness. 5. Each screenshot was visually verified against the original for content parity. </details> |
||
|
|
d58788cb23 |
fix: clarify AI model price configuration (#28372)
## Description Model prices displayed in the Agents model form are read-only because they are managed by AI Gateway. Add guidance and a link to the model price configuration documentation, and rename the related documentation subsection to **Configure model prices**. > [!NOTE] > Generated with Coder Agents by @ssncferreira. |
||
|
|
50ecd6959f |
feat: add a source filter to the AI model price list (#28329)
## Description With prices from the price book and prices set through the API both in the table, `list` showed only the price in effect and gave no way to tell where it came from. There was also no way to see the price book's entry for a model that had been overridden, since the listing resolves to the custom price. This adds a `source` column to the output and a `--source` filter for narrowing to one source, or to every row with `all`. Note: `--source` selects rows rather than models. A model carrying both a price book price and a custom one appears under either value, so the two filtered listings do not sum to the unfiltered one. `--source all` reports both rows at once. ## Changes - Add `Source` to `codersdk.AIModelPrice` and `AIModelPricesFilter`, and map it in `db2sdk`, which previously dropped it. - Filter `GetAIModelPrices` by source before resolution, so the price book's row for an overridden model stays reachable. - Accept `all` as a source, which reports every price a model holds instead of resolving to one. - Accept a `source` query parameter on the list endpoint, rejected with a 400 when outside the enum. - Add `--source` to the list command and a `source` column to its output. - Document the column and the filter, and lead the price section with `coder exp ai-model-prices list`. - Distinguish a deployment's default prices from the price book that ships with each release. Closes https://linear.app/codercom/issue/AIGOV-593 > [!NOTE] > Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira |
||
|
|
19d1ee9fc8 |
feat: support custom prices for models in the price book (#28328)
## Description The experimental CLI could only price models the price book does not cover, so an operator with a negotiated rate for a model Coder already prices had no way to record it. The restriction existed because a single row served both the price book and prices set through the API, and the startup seeder would overwrite an override on the next restart. Each source now keeps its own row (https://github.com/coder/coder/pull/28204), so that no longer applies. This removes the restriction: a price set through the CLI takes effect over the price book for any model, and the book stays underneath it. ## Changes - Remove the validation that rejected models the price book covers. - Delete `prices.IsDefaultPriced` and the index behind it, which existed only for that check. - Generalize the `coder exp ai-model-prices update` help. - Document that a custom price overrides the price book and does not follow its updates. - Rework the spend approximation callout around what a custom price can and cannot correct. Closes https://linear.app/codercom/issue/AIGOV-594 > [!NOTE] > Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira |
||
|
|
2f996d765a |
feat: remove task management MCP tools (#28354)
Removes the six task-management tools from the MCP tool surface in `codersdk/toolsdk`: - `coder_create_task` - `coder_delete_task` - `coder_list_tasks` - `coder_get_task_status` - `coder_send_task_input` - `coder_get_task_logs` Both the coderd-hosted MCP server (`coderd/mcp`) and the CLI `coder exp mcp server` derive their tool list from `toolsdk.All`, so removing the registry entries removes the tools from both servers with no server-side changes. The MCP server documentation (`docs/ai-coder/mcp-server.md`) is updated to drop the task-management entry from the tool overview. ## What stays - `coder_report_task` is intentionally kept: it is the agent-side status-reporting mechanism (`WithTaskReporter`), already excluded from the hosted MCP server, and required by `coder exp mcp server`, Claude Code configuration, and scaletest. - The `codersdk` Task SDK methods (`CreateTask`, `DeleteTask`, `Tasks`, `TaskLogs`, etc.) and the Tasks feature (CLI, UI, API) are untouched; this PR only removes MCP tool registration. - The task-only `taskIDDescription` helper is deleted; `userDescription` stays (used by `coder_create_workspace`). The code change is deletion-only: 970 removed lines across `toolsdk.go` and its test file, no added behavior. ## Validation - `go build ./...`, `go test ./coderd/mcp/`, and targeted `golangci-lint` pass. `go test ./codersdk/toolsdk/` passes all tests; the package-level goleak failure (agentssh goroutines) reproduces identically on unmodified `main` and is unrelated to this diff. - `pnpm run lint-docs` passes on the docs change. - Remote dogfood UAT ran against the toolsdk change and passed: `tools/list` on both the hosted MCP endpoint and `coder exp mcp server` confirms the six tools are absent and the remaining surface is intact (34 hosted tools; CLI adds `search`/`fetch`), a `tools/call` of `coder_get_authenticated_user` succeeds, and calling a removed tool returns `-32602 unknown tool`. `coder_report_task` still registers in the CLI server when `CODER_MCP_APP_STATUS_SLUG` is set. - The failing `Pixel / Review` commit status is unrelated: this PR contains no `site/` changes, and the same status fails on the last 8 `main` commits including this PR's merge base (`676b9bb06bf`). > Mux acted on Mike's behalf to create this pull request. |
||
|
|
2529a54cfc |
chore(docs): update release docs for v2.36.1 (#28357)
Automated docs update for v2.36.1 release. Created by `releasetui`. |
||
|
|
24b83dc727 |
feat: add organization picker to MCP server settings pages (#28150)
Adds an organization picker to the MCP server settings pages so
multi-org deployments can manage every organization's MCP servers
through the UI, not just the default organization's. Closes CODAGT-714
(scoped to MCP servers only; model configs stay deployment-wide until
CODAGT-709 lands).
## Stack Context
Top of the CODAGT-711/717/712/806 stack: MCP server configs are
org-scoped with RBAC, audit logging, per-config ACLs, and owner-scoped
user tokens. This layer makes the admin UI org-aware, resolving the
"default org only" limitation documented in the bottom PR.
## Summary
- The MCP servers list and add pages resolve the working organization
from an `org` search param (falling back to the default org), and render
an organization picker built on the existing `OrganizationAutocomplete`.
The picker only appears when more than one organization is visible, so
single-org deployments see no change.
- Selecting an organization rewrites the search param, refetches the
list for that org, and threads through to the add page so creation posts
to the selected organization.
- The update page now loads the server by ID (new organization-scoped
`GET /api/experimental/organizations/{organization}/mcp-servers/{id}`
frontend binding) instead of listing the default org and searching
client-side, so servers from any organization can be edited. Post-save
navigation returns to the owning organization's list via the server's
`organization_id`.
- Removes the now-unused `getDefaultOrganizationId` helper.
- Review hardening: organization display labels are disambiguated with
the unique org name when effective display names collide (picker
options, trigger text, and accessible name). Access is no longer
restricted to deployment admins: the pages, sidebar item, and nav entry
accept org-scoped `mcp_server_config` permissions, the picker lists only
organizations the user can view, content and affordances (add, edit,
delete) are gated on the selected organization's permissions, and
deployment admins keep full access everywhere.
## Testing
Storybook interaction stories cover: picker hidden with one org,
switching orgs refetches and swaps list contents, add posting to the
URL-selected org, add posting after an in-form org switch (asserting
typed form state survives the switch), and update loading by ID.
Red-green verified by breaking the search-param resolution and observing
the new stories fail.
> Mux acted on Mike's behalf for this PR.
|
||
|
|
8203b2ebaf | feat!: hide Coder Tasks behind the enable-ai-tasks flag (#28008) | ||
|
|
7ca7c30f40 |
feat: add group and user ACLs to MCP server configs (#27944)
Adds group and user ACLs to org-scoped MCP server configs so
organizations can restrict specific MCP servers to subsets of members,
mirroring the template ACL pattern.
## Summary
- Migration adds `group_acl`/`user_acl` JSONB columns (nested `{"<id>":
{"permissions": [...]}}` shape) and seeds every existing config with its
organization's Everyone group read entry (the Everyone group ID equals
the org ID), so member access is unchanged by default. Creation seeds
the same entry.
- The blanket org-member read grant from the base PR is replaced by ACL
evaluation: Rego requires org membership for every ACL grant, and
`GetAuthorizedMCPServerConfigs` compiles ACL-aware SQL filters.
- New `ActionShare` (org admins) gates `GET|PATCH
/api/experimental/organizations/{organization}/mcp-servers/{mcpserverconfig}/acl`
(nested under the organization like the rest of the config surface).
PATCH validates principals against the config's organization, merges
sparse updates under a row lock, stamps `updated_by`/`updated_at`, and
is audited as a Write with `Old` captured before authorization. A config
deleted concurrently between the middleware fetch and the locked
re-fetch is concealed as 404, matching the update and delete handlers.
ACL columns are tracked in the audit table.
- ACL management is available in all editions (no enterprise
entitlement), documented in the MCP servers page. Revoking an ACL does
not retro-strip already-selected configs from existing chats; new
selection is blocked at chat create.
- Force On respects the ACL: the forced set is loaded as the chat owner,
so a `force_on` server whose ACL denies the owner never attaches at
create, send, or generation time.
- No rolling-upgrade machinery: upgrades run in scheduled maintenance
downtime, so the migration only backfills existing rows; the API sets
the Everyone read grant explicitly on every insert.
Stacked on #27943. Part of the MCP org-separation stack.
Closes https://linear.app/codercom/issue/CODAGT-712
UAT: verified on a dogfood instance with two members and a custom group:
Everyone-seed default visibility, group grant with Everyone removal
(non-member loses list/fetch/selection), foreign-principal rejection,
non-admin share denial (audited 403 / concealed 404), user_acl restore,
and audited ACL diffs.
> Mux (AI agent) authored this PR on Mike's behalf.
<!-- mux-attribution: model=claude-fable-5 thinking=high -->
|
||
|
|
299e72ad30 |
feat: audit MCP server config changes (#27943)
Adds enterprise audit logging for MCP server config create, update, and
delete, with strict secret redaction. MCP configs hold credentials
(OAuth2 client secrets, API keys, custom headers), so admin changes to
them need an audit trail.
## Summary
- `enterprise/audit/table.go` gains an `MCPServerConfig` entry
enumerating every column: `oauth2_client_secret`, `api_key_value`, and
`custom_headers` are `ActionSecret` (never appear in diffs); dbcrypt
`*_key_id` bookkeeping, IDs, and timestamps are ignored; the remaining
config fields, including the endpoint URL fields, are tracked so
auditors can see which endpoints a config points at.
- Type registration in `coderd/audit` (diff, request, resource target
with org attribution), `codersdk/audit.go`, and a `resource_type` enum
migration.
- Handlers wire `audit.InitRequest`: create records `New`; update and
delete record `Old` from the param middleware before the
write-authorization check, so a readable-but-not-writable caller
produces an audited 403 while read-denied callers stay concealed as
unaudited 404s.
- Tests: create/update/delete audit entries, write-denied and
delete-denied 403 auditing, cross-org concealment producing zero
entries, and a serializer-level regression test proving none of the
three secret classes can reach a serialized diff.
- Review round: MCP config audit entries link to
`/ai/settings/mcp-servers/{id}`, audit table comments are trimmed per
review, and a fault-injection test pins that a config row surviving a
failed post-discovery credential update still gets its creation audit
entry.
Stacked on #27942 (org-scoped MCP configs). Part of the MCP
org-separation stack.
Closes https://linear.app/codercom/issue/CODAGT-717
UAT: verified on a trial-licensed dogfood instance: audit entries for
the full CRUD lifecycle with correct actor/org/target, redacted secrets
in the update and OAuth2 create diffs, and a full plaintext scan of the
audit dump finding zero secret leaks.
> Mux (AI agent) authored this PR on Mike's behalf.
<!-- mux-attribution: model=claude-fable-5 thinking=high -->
|