Commit Graph
5881 Commits
Author SHA1 Message Date
Michael Suchacz 9d6ef1e701 docs: streamline agent instructions (#28243)
## Summary

Streamlines the agent instruction files, cutting per-session token
overhead by roughly 65%.

The core design change: `AGENTS.md` no longer `@`-imports WORKFLOWS.md,
package.json, the eight detailed guides, or `AGENTS.local.md` into every
session. Guidance moves from guaranteed-in-context to load-on-demand
through a routing table, betting that one-line guardrail summaries plus
links are enough to make agents pull the right doc when a task needs it.

- `AGENTS.md` (251 -> 80 lines): a routing table to the canonical docs
in `.claude/docs/`, the essential command table, and one-line guardrails
for the architectural footguns (SQLC workflow plus audit table, OAuth2
error conformance, chatd architecture-doc rule, Swagger annotations,
`InTx` discipline, test concurrency, workflow `permissions: {}`,
mandatory git hooks, commit/PR title format).
- `site/AGENTS.md` (364 -> 89 lines): keeps the FE1-FE10 contract
summary, TypeScript LSP navigation, pnpm command table, React Compiler
boundaries, accessibility rules, and the pre-PR `frontend-review`
requirement. Drops the generic React tutorial sections that restate
framework documentation.
- The persona and relationship prose ("Our relationship", foundational
rules) is deliberately removed. The behavioral gates worth keeping for
every agent survive as one-liners: get explicit permission before
excepting a rule, discuss architectural decisions before implementing,
and answer questions instead of jumping to implementation.
- Unique operational content is relocated rather than deleted:
Playwright failure-artifact paths and the CI `playwright-artifacts-`
naming convention move to `.claude/docs/AGENT_FAILURES.md`; the
`React.useId()` rule, the `key={String(booleanState)}` remount footgun,
date-as-prop Storybook determinism, and the suppression-comment rule
(updated from `eslint-disable` to `biome-ignore` to match current
tooling) move to `.claude/docs/FRONTEND_PATTERNS.md`.

Content reflects current main: the chatd rule says agents leave TODOs
and the human PR author writes the architecture doc, and MUI/Emotion are
described as removed, not deprecated.

Note for Claude Code users: `CLAUDE.md` symlinks to `AGENTS.md`, so
detailed workflow docs (hook internals, the new-feature checklist,
`AGENTS.local.md`) are no longer auto-loaded into every session; agents
must follow the links.

## Validation

- `pnpm exec markdownlint-cli2` on all four touched files: 0 errors
- `pnpm exec markdown-table-formatter --check` on all four touched
files: pass
- `make lint/emdash`: pass
- `make lint/agents`: pass
- All relative links verified to resolve

> Mux created this PR and description on behalf of Mike.
2026-08-20 21:05:08 +02:00
Michael Suchacz 7970cd6d11 fix(site/src/pages/AgentsPage/components/ChatElements/tools): stop completed process_output rows animating forever (#28414)
Since #28300, `process_output` tool rows in the Agents chat transcript
fed the result payload's `running: true` flag into the row's active
state. That flag is a frozen snapshot of whether the polled process was
still alive, so for long-lived background processes (a dev server that
never exits) every completed poll kept its shimmer label and spinner
animating forever, even after the agent went idle.

`ToolCall.Root` now receives the actual tool-call status, so the shimmer
and spinner stop when the poll completes. The `running: true` snapshot
still controls what it was meant to: label tense ("Checking npm start"
while alive vs "Checked npm start" after exit) and kill-signal badge
visibility. The existing `ProcessOutputStillRunningResult` story now
also asserts the running spinner is absent on a completed poll (verified
red-green: reintroducing the bug fails exactly that story), and the
intent story asserts the spinner is present while the tool call is
genuinely running.

> [!NOTE]
> Mux (AI agent) authored this PR on behalf of @ibetitsmike.

<!-- mux-attribution: model=claude-opus-4-6 thinking=high -->
2026-08-20 20:51:59 +02:00
Danielle Maywood c940284e18 fix(site): freeze motion animations during Pixel captures (#28396) 2026-08-20 17:43:32 +00:00
Michael Suchacz b14bce12bf fix(site/src/pages/AgentsPage/components): fix model selector trigger layout on form-style pages (#28399)
Selecting a model on form-style pages (deployment AI settings such as
`/ai/settings/coder-agents`, the personal agent settings override rows,
and the advisor override) rendered a broken trigger: the provider icon
pinned to the far-left edge, the model name floating in the center, and
the chevron on the right. At desktop widths the field also collapsed to
content height (~22px) next to the 40px Clear/Save buttons.

Two defects in the shared `ModelSelector` trigger:

1. #28330 added the provider icon as a third direct flex child of the
trigger button. Form call sites override the trigger with
`justify-between` plus a width wider than its content, which spread
icon, label, and chevron apart. The icon and label now form a single
flex child so they stay adjacent under any `justify-*` override; the
chevron stays a direct child because the `[&>svg]` trigger styles target
it, and the label still truncates via `min-w-0`.
2. The base classes carried composer-pill responsive variants
(`md:h-auto`, `md:w-auto`, `md:shrink-0`). tailwind-merge resolves
conflicts per modifier, so `md:h-auto` silently defeated the call sites'
`h-10` at the `md` breakpoint (and `md:w-auto` defeated `w-[22rem]` on
the advisor row). The pill's responsive sizing now lives at its call
site, so plain `h-10`/width overrides win everywhere. The composer pill
is visually unchanged.

Also fixes the provider icon shrinking next to long model names: the
icon's `span` wrapper was a plain flex item whose automatic minimum size
collapses (preflight's `img { max-width: 100% }` scales the icon down as
the wrapper shrinks). The wrapper is now a `shrink-0` flex container, so
the label truncates while the icon keeps its 12px size (verified
red-green with a temporary probe: 7.3px without the fix).

Adds `FormFieldTriggerKeepsIconAndLabelAdjacent` and
`FormFieldTriggerTruncatesLongModelName` stories reproducing the
form-style trigger for visual snapshot coverage. Both defects were
verified red-green during development with temporary geometry probes
(icon-label gap 63.5px and height 30px without the fixes; gap 4px and
height 40px with them); per FE10 the committed story carries no geometry
assertions and the pixel snapshot guards the layout.

Also recolors the advisor "Uses / turn" and "Max tokens" field labels
from `content-placeholder` to `content-secondary` on the same settings
surface.

Remote dogfood UAT ran against this branch across desktop and narrow
viewports and passed.

> 🤖 Mux acted on behalf of @ibetitsmike to create this PR.
2026-08-20 19:24:10 +02:00
TJ 93d5f82849 fix(site/src/pages/AgentsPage/components/ChatsSidebar): hide kebab while archiving spinner shows (#28393)
Since #28326 made the row actions trigger (kebab) persistently visible
on the selected chat, it overlapped the archiving spinner: both render
right-aligned in the same 28px box (the kebab is `absolute inset-0` over
the timestamp/spinner slot), so archiving the selected chat produced a
garbled spinner-plus-dots glyph. The overlap could previously only occur
transiently on hover; the persistent kebab made it guaranteed on the
active row.

Render the kebab only when the row is not showing the archiving spinner,
restoring the intent of the existing `ArchivingShowsSpinnerOnly` story
(which only covers a non-selected row, so it never caught this).

<details>
<summary>Diagnosis and validation</summary>

**Diagnosis**

- The only right-side loader in a sidebar row is the `Spinner` rendered
by `ChatTreeNode` when `isArchiving && archivingChatId === chat.id`.
- #28326 added `isActiveChat && "opacity-100"` to the kebab trigger,
which shares the same `w-7` box as the spinner. The spinner branch never
suppressed the trigger, only the timestamp span had the hover/active
`hidden` classes.
- Result: on the selected chat, `EllipsisVerticalIcon` and the 8-leaf
`Spinner` render on top of each other for the entire archive mutation.

**Fix**

- `ChatTreeNode.tsx`: gate the `DropdownMenu` on `!isArchivingThisChat`
so the spinner and trigger are mutually exclusive. Row context-menu
actions remain available and are already disabled while archiving.

**Validation**

- `pnpm test:storybook
src/pages/AgentsPage/components/ChatsSidebar/ChatsSidebar.stories.tsx`:
53 passed
- `make pre-commit`: pass (lint/ts, lint/go, gen, fmt, build)

</details>

_Generated by Coder Agents on behalf of @tracyjohnsonux._
2026-08-20 16:42:35 +00:00
TJ b605446016 feat(site/src/pages/AgentsPage): dim unselected sidebar chat titles (#28382)
## Summary

Subtle visual tweak to the agents sidebar chat list. Chat titles remain
`text-content-primary`, but titles for chats that are not currently
selected now
render at 85% opacity. The selected chat keeps full opacity (and its
existing
`font-medium` weight), making the active chat stand out more clearly.

## Changes

- `ChatTreeNode.tsx`: apply `opacity-85` to the title span when the chat
is not
  active, keeping `font-medium` for the active chat.

`opacity-85` is a valid Tailwind 3.4 utility and is already used
elsewhere in
the codebase.

---

_This PR was generated by Coder Agents on behalf of @tracyjohnsonux._
2026-08-20 09:35:42 -07:00
TJandCoder Agents e58a603949 fix(site): prevent duplicate menu when right-clicking chat kebab (#28391)
Right-clicking the kebab (⋮) button on a chat in the agents sidebar was
showing two menus: the kebab's own dropdown **and** the row-level
right-click context menu.

The kebab lives inside the row's \`ContextMenuTrigger\`, so the
\`contextmenu\` event on the button bubbles up and opens the row's
context menu on top of the dropdown that the button already owns.

Fix: swallow \`contextmenu\` on the kebab \`Button\` so only its
dropdown is associated with the button. Right-clicking anywhere else on
the row still opens the row context menu as before.

> Generated with Coder Agents on behalf of @tracyjohnsonux.

---------

Co-authored-by: Coder Agents <noreply@coder.com>
2026-08-20 15:56:36 +00:00
Jake Howell 05b78a0902 fix(site): scroll per-script logs selector on overflow (#28353)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.

Resolves
[DEVEX-721](https://linear.app/codercom/issue/DEVEX-721/the-logs-selector-per-startup-script-needs-to-scroll-on-overflow).

Cap the overflow menu height and let it scroll, matching the existing
pattern already used by `DownloadSelectedAgentLogsButton` (`max-h-56
overflow-y-auto`). No component swap needed, the `DropdownMenu` was the
right primitive, it just lacked height/scroll constraints.

```diff
-<DropdownMenuContent align="end">
+<DropdownMenuContent align="end" className="max-h-56 overflow-y-auto">
```

<img width="504" height="325" alt="image"
src="https://github.com/user-attachments/assets/363d9bc0-e3c3-4355-8d05-abb8c650a37a"
/>

<details>
<summary>Investigation / decision log</summary>

- The "logs selector per startup script" is the overflow kebab menu in
`site/src/modules/resources/AgentRow.tsx`. When per-script log tabs
don't fit horizontally, `useKebabMenu` moves the extras into a Radix
`DropdownMenu` rendered as `DropdownMenuRadioItem`s.
- `menuContentClass` (`site/src/components/DropdownMenu/menuClasses.ts`)
is `z-50 min-w-48 overflow-hidden ...` with no max-height, so an
overlong menu is clipped instead of scrolling.
- `DownloadSelectedAgentLogsButton.tsx` already solves the identical
problem on its own menu with `className="max-h-56 overflow-y-auto"`, so
we reused that for consistency.
- Considered switching to the `Select` component (which has
`SelectScrollUpButton`/`SelectScrollDownButton` chevrons) but that's a
larger re-model of the trigger + items and reconciliation with the
`Tabs`/`useKebabMenu` wiring. Deferred in favor of the minimal fix.

</details>
2026-08-20 22:22:32 +07:00
Tyler 376217423d fix(site/src/pages): use RotateCcwIcon for update actions (#28183)
Closes
[DES-22067](https://linear.app/codercom/issue/DES-22067/update-and-restart-action-uses-a-cloud-icon).

The "Update and restart" / "Update and start" actions used `CloudIcon`,
which reads as a cloud/network concept instead of an update-and-restart
build action.

Switch them to `RotateCcwIcon`, which the UI already uses for:

- the `Restart` button on the workspace page
- the `Update` action in `WorkspaceOutdatedTooltip`,
`AgentOutdatedTooltip`, and `SubAgentOutdatedTooltip`

Call sites changed:

- `WorkspacePage/WorkspaceActions/Buttons.tsx` - `UpdateButton`
- `WorkspacesPage/WorkspacesTable.tsx` - "Update and start workspace"
and "Update and restart workspace" primary actions
- `WorkspacesPage/WorkspacesPageView.tsx` - batch "Update" menu item

The `requireActiveVersion` variant still uses `PlayIcon`, since that
path starts the workspace on the active version rather than offering a
choice. Worth a separate design pass if that should change too.

<details>
<summary>Investigation notes</summary>

Verified against `origin/main` (`af90d8e2be`): no prior PR referenced
DES-22067 and all three `CloudIcon` usages were still present. `grep`
confirms no `CloudIcon` usages remain in `site/src` after this change.

Icon conventions found in the codebase:

- `RotateCcwIcon`: restart, update (outdated tooltips), retry
(RetryButton, TasksTable, TaskPage, HealthLayout)
- `RefreshCwIcon` / `RefreshCcwIcon`: softer "reload data" flows
(terminal, secrets, prebuilds invalidate, inbox, git panel)

`data-testid="bulk-action-update"` is preserved on the batch update
item, so e2e coverage is unaffected.

</details>

---

Generated by Coder Agents on behalf of @designertyler.
2026-08-20 10:17:03 -05:00
Jake Howell 47ffa87fa5 fix: <BuildingTemplateLoader /> icon sizes (#27733) 2026-08-20 21:44:27 +07:00
Michael Suchacz 8eb1e43f0b feat: apply reasoning effort and surface thinking blocks for Google chat models (#28273)
## Problem

Selecting a reasoning effort on a Google (Gemini) chat model either got
dropped or broke the request outright, and even when the request
succeeded, Gemini chats never showed thinking blocks:

- The native `fantasygoogle` path had no case in `applyReasoningEffort`
at all, so effort was silently ignored. With only `include_thoughts`
configured, Gemini 3+ models default to no thinking, so chats produced
zero reasoning tokens.
- The path chat generation actually uses today routes Google providers
through aibridge as openai-compat clients and forwarded the raw effort
string as `reasoning_effort`. Google's OpenAI-compatible endpoint
validates that value against the model's supported thinking levels
instead of clamping, so out-of-range efforts fail the whole request with
HTTP 400. Verified against the live Gemini API: `gemini-3.1-pro-preview`
accepts low/medium/high and rejects none/minimal/xhigh/max. Because the
config's default effort is sent whenever the user selects nothing, a
config whose default falls outside the model's supported set failed
every no-selection message.
- Google's OpenAI-compatible endpoint never emits thought text for
`reasoning_effort` requests. Thoughts require
`extra_body.google.thinking_config` with `include_thoughts`, which is
mutually exclusive with `reasoning_effort`, and even then the thought
deltas come back inline in `content` (marked
`extra_content.google.thought`, wrapped in `<thought>` markers) rather
than in the `reasoning_content` field OpenAI-compatible clients parse.
So no reasoning parts were ever produced, persisted, or rendered for
Gemini chats (verified live in UAT).

## Fix

Reasoning effort:

- Map the global reasoning effort scale onto Google's `thinking_level`
in `applyReasoningEffort`, gated to Gemini 3+ models and clamped to each
model's supported subset (3.0 Pro launched with LOW/HIGH only, 3.1 Pro
added MEDIUM, Flash accepts all four through 3.6 while 3.7 and the
latest flash aliases drop MINIMAL, image models accept HIGH plus MINIMAL
for flash). Pre-Gemini-3 models reject `thinking_level`, so effort
degrades to a no-op for them and a config-pinned level is dropped at
call time instead of failing every generation.
- Add `thinking_level` to `ChatModelGoogleThinkingConfig` so admins can
pin a level for configs without user-selectable effort. A resolved
per-turn effort overrides the pinned level; an explicit
`thinking_budget` wins over both since the Google API rejects requests
that set `thinking_budget` and `thinking_level` together, and config
validation now rejects that combination up front. The admin panel picks
up the new field automatically through the generated model-options
schema.

Thinking blocks on the openai-compat path (the path chat generation
uses), translated in both directions at the existing Gemini transport
seam:

- chatd's OpenAI-compat request patch swaps `reasoning_effort` for an
equivalent `extra_body.google.thinking_config` with `include_thoughts`
enabled: `thinking_level` on Gemini 3+ (clamped to the model's supported
subset), `thinking_budget` on earlier models (effort `none` maps to
budget 0, which disables thinking). Non-Gemini models and non-Google
routes are untouched.
- The same transport rewrites Google's responses so thought output
surfaces as `reasoning_content`, which fantasy already converts into
reasoning parts: streaming bodies are rewritten per SSE line (preserving
streaming latency), JSON bodies in place, with the
`<thought>`/`</thought>` markers stripped at the transitions.
- aibridge's chat-completions interception preserves the `extra_body`
passthrough that openai-go's typed params silently drop, forwarding it
to Google upstreams only, so the chatd -> aibridge -> Google route
carries the thinking config end to end.

## Validation

- `go test ./coderd/x/chatd/... ./aibridge/... ./internal/googleopenai
./codersdk` pass; golangci-lint clean on touched packages.
- New unit tests cover the effort-to-level mapping for every effort
value, per-model clamping on both paths, the effort-to-thinking_config
request swap (level, budget, pinned-config precedence, non-Gemini
passthrough), SSE and JSON thought-response rewriting (fixtures mirror
live captures, including tool-call and no-thought streams), an
end-to-end fake-SSE test through the fantasy pipeline proving reasoning
deltas emerge with markers stripped, and aibridge `extra_body`
preservation (including proof the typed round trip drops it).
- Verified live against `generativelanguage.googleapis.com` on
`gemini-3-flash-preview` and `gemini-2.5-flash` through the real
chatprovider stack: reasoning deltas stream through with markers
stripped and answer text intact.
- Live UAT on a dogfood deployment (remote Coder Agents chat, 4 rounds):
effort clamping verified end to end (rounds 1-2), the missing-thoughts
limitation isolated (round 3), and the fix validated (round 4):
expandable, readable thinking blocks render for
`gemini-3.1-pro-preview`, `gemini-3-flash-preview`, and
`gemini-2.5-flash` at effort=high; no `<thought>` marker leaks;
effort=none produces no block and no 400; multi-turn history replay
works with a thinking block on each turn.
- `make gen` regenerated `typesGenerated.ts` and
`chatModelOptionsGenerated.json`; `pnpm -C site lint:types` passes.

> 🤖 Mux acted on Mike's behalf to create this pull request.

<!-- mux-attribution: model=claude-opus-4-6 thinking=high -->
2026-08-20 16:36:57 +02:00
Susana Ferreira ab6601086a fix: gate group settings page and fix unauthorized group patch (#28314)
## Problem

A user without `group:update` (e.g. the `Coder Agents User` role) could
open the group settings page by direct URL and edit the budget. Saving
then failed with a misleading error:

> Failed to add or remove non-existent group member — execute
transaction: update group by ID: unauthorized: rbac: forbidden.

The authorization failure was misclassified as a missing group member,
and the settings page was not gated on the required permission.

## Changes

- Return the standard vague 404 for authorization failures in the group
PATCH handler, so the response is accurate and does not leak resource
existence.
- Use a generic message for the remaining 400 branch, since it covers
various transaction failures rather than only member add/remove.
- Gate the group settings page on `group:update`. The settings tab is
already hidden without the permission, but the page was reachable by
direct URL and rendered an editable form that always failed on save. It
now shows the standard permission dialog.

Fixes
[AIGOV-603](https://linear.app/codercom/issue/AIGOV-603/hide-group-budget-controls-without-permission).

> [!NOTE]
> Generated by Coder Agents, reviewed by @ssncferreira.
2026-08-20 14:51:27 +01:00
Danielle Maywood 015d6a6e06 feat(site): add provider icon to chat model selector (#28330) 2026-08-20 13:35:53 +00:00
Susana Ferreira 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.
2026-08-20 12:55:51 +00:00
Susana Ferreira 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
2026-08-20 11:56:58 +01:00
Danielle Maywood 4a7a0a3348 feat: label process_output rows with the process command (#28300) 2026-08-20 11:56:14 +01:00
Danielle Maywood eed420a0eb fix(site/src/pages/AgentsPage): clear streamed message when a chat errors (#28277) 2026-08-20 11:56:06 +01:00
cdrciandgithub-actions[bot] 5e0710de92 chore: refresh AI model price book (#28365)
## Price book changes

61 models added, 14 models removed, 51 models changed.

<details>
<summary>Added</summary>

- azure/claude-mythos-5
- azure/claude-opus-4-7
- azure/kimi-k2.7-code
- bedrock/global.openai.gpt-5.6-luna
- bedrock/global.openai.gpt-5.6-sol
- bedrock/global.openai.gpt-5.6-terra
- bedrock/xai.grok-4.6
- copilot/gemini-3.7-flash
- copilot/grok-4.6
- copilot/kimi-k3
- copilot/mai-code-1.1-flash
- google/gemini-3.7-flash
- openrouter/bytedance-seed/seed-2-1-turbo
- openrouter/bytedance-seed/seed-2.0-code
- openrouter/deepseek/deepseek-v4-pro-0813
- openrouter/dots-studio/dots-3-note-preview:free
- openrouter/google/gemini-3.7-flash
- openrouter/inclusionai/ling-3.0-flash
- openrouter/liquid/lfm-2.5-2.6b:free
- openrouter/meta/muse-glimmer-30b
- openrouter/meta/muse-spark-1.2
- openrouter/nvidia/nemotron-3.5-lightning
- openrouter/nvidia/nemotron-3.5-lightning:free
- openrouter/qwen/qwen3.8-2.4t-a95b
- openrouter/qwen/qwen3.8-27b
- openrouter/sakana/sakana-namazu
- openrouter/upstage/solar-pro4
- openrouter/x-ai/grok-4.6
- openrouter/z-ai/glm-5.2:free
- openrouter/z-ai/glm-5.3
- openrouter/~z-ai/glm-latest
- vercel/alibaba/qwen3.8-2.4t-a95b
- vercel/alibaba/qwen3.8-27b
- vercel/deepseek/deepseek-v4-pro-0813
- vercel/google/gemini-3.7-flash
- vercel/inclusionai/ling-3.0-flash
- vercel/meta/muse-glimmer-30b
- vercel/meta/muse-spark-1.2
- vercel/meta/muse-spark-1.2-contributor
- vercel/nvidia/nemotron-3.5-lightning
- vercel/openai/gpt-4.1-fast
- vercel/openai/gpt-4.1-mini-fast
- vercel/openai/gpt-4.1-nano-fast
- vercel/openai/gpt-4o-fast
- vercel/openai/gpt-4o-mini-fast
- vercel/openai/gpt-5-fast
- vercel/openai/gpt-5-mini-fast
- vercel/openai/gpt-5.1-thinking-fast
- vercel/openai/gpt-5.2-fast
- vercel/openai/gpt-5.3-codex-fast
- vercel/openai/gpt-5.4-fast
- vercel/openai/gpt-5.4-mini-fast
- vercel/openai/gpt-5.5-fast
- vercel/openai/gpt-5.6-luna-fast
- vercel/openai/gpt-5.6-sol-fast
- vercel/openai/gpt-5.6-terra-fast
- vercel/openai/o3-fast
- vercel/openai/o4-mini-fast
- vercel/sakana/namazu
- vercel/xai/grok-4.6
- vercel/zai/glm-5.3
</details>

<details>
<summary>Removed</summary>

- anthropic/claude-opus-4-1
- anthropic/claude-opus-4-1-20250805
- google/gemini-2.0-flash
- google/gemini-2.0-flash-lite
- google/gemini-3-pro-preview
- openrouter/ai21/jamba-large-1.7
- openrouter/inclusionai/ling-3.0-flash:free
- openrouter/mancer/weaver
- openrouter/openai/gpt-5.3-chat
- vercel/anthropic/claude-opus-4.1
- vercel/inclusionai/ling-3.0-flash-free
- vercel/openai/gpt-5.1-instant
- vercel/openai/gpt-5.3-chat
- vercel/zai/glm-4.6v
</details>

<details>
<summary>Changed</summary>

- azure/gpt-5.6-luna
- azure/gpt-5.6-sol
- azure/gpt-5.6-terra
- bedrock/openai.gpt-5.6-sol
- openrouter/deepseek/deepseek-chat-v3-0324
- openrouter/deepseek/deepseek-v3.1-terminus
- openrouter/deepseek/deepseek-v4-flash
- openrouter/deepseek/deepseek-v4-flash-0731
- openrouter/deepseek/deepseek-v4-pro
- openrouter/google/gemini-3.6-flash
- openrouter/google/gemma-4-31b-it
- openrouter/gryphe/mythomax-l2-13b
- openrouter/minimax/minimax-m2.5
- openrouter/minimax/minimax-m2.7
- openrouter/mistralai/mistral-small-3.2-24b-instruct
- openrouter/moonshotai/kimi-k2.5
- openrouter/moonshotai/kimi-k2.6
- openrouter/moonshotai/kimi-k2.7-code
- openrouter/openai/gpt-5.6-luna
- openrouter/openai/gpt-5.6-luna-pro
- openrouter/openai/gpt-5.6-sol
- openrouter/openai/gpt-5.6-sol-pro
- openrouter/openai/gpt-5.6-terra
- openrouter/openai/gpt-5.6-terra-pro
- openrouter/openai/gpt-oss-120b
- openrouter/qwen/qwen-plus-2025-07-28:thinking
- openrouter/qwen/qwen2.5-vl-72b-instruct
- openrouter/qwen/qwen3-14b
- openrouter/qwen/qwen3-235b-a22b-2507
- openrouter/qwen/qwen3-30b-a3b
- openrouter/qwen/qwen3-coder-30b-a3b-instruct
- openrouter/qwen/qwen3-vl-235b-a22b-thinking
- openrouter/qwen/qwen3-vl-30b-a3b-instruct
- openrouter/qwen/qwen3.5-35b-a3b
- openrouter/qwen/qwen3.6-27b
- openrouter/qwen/qwen3.6-35b-a3b
- openrouter/tencent/hy3-preview
- openrouter/thinkingmachines/inkling
- openrouter/thinkingmachines/inkling-small
- openrouter/z-ai/glm-5
- openrouter/z-ai/glm-5.2
- openrouter/~deepseek/deepseek-v4-flash-latest
- openrouter/~google/gemini-flash-latest
- openrouter/~moonshotai/kimi-latest
- openrouter/~openai/gpt-latest
- openrouter/~x-ai/grok-latest
- vercel/deepseek/deepseek-v4-flash
- vercel/deepseek/deepseek-v4-pro
- vercel/google/gemini-3.6-flash
- vercel/openai/gpt-5.6-sol
- vercel/zai/glm-5.2
</details>

## Review notes

Regenerated by `make gen/aibridge-prices` from the live
[models.dev](https://models.dev) catalog. Both artifacts come from one
snapshot, so they ship together:

- `coderd/aibridge/prices/data/prices.json`
-
`site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.json`

These are customer-visible cost numbers taken from upstream data, so
this PR is never merged automatically. The summary above lists what
moved; check the diff for exact figures before approving.

Opened automatically by the [aigateway-prices-refresh
workflow](https://github.com/coder/coder/actions/runs/32352560765).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-20 10:46:48 +01:00
Jaayden Halko e5deb57079 feat(site): show total agent hours consumption on the licenses page (#27953)
<img width="1195" height="165" alt="Screenshot 2026-08-19 at 5 31 17 PM"
src="https://github.com/user-attachments/assets/d1e49e5f-1af6-4221-87fe-ae40997f0ae7"
/>

Adds a "Total agent hours" consumption panel to the Licenses page,
surfacing the `agent_runtime_hours` entitlement introduced by the #27983
/ #27984 / #27985 stack.

The new `TotalAgentHoursCard` renders full width directly above the
Agent Workspace Builds panel. It shows a usage bar with Used/Limit
labels and an info tooltip next to the title. The bar color and tooltip
copy track the same thresholds the backend uses for license warnings:
green below the soft limit, orange once usage reaches the soft limit,
and red once usage reaches the purchased allocation (values at or beyond
the threshold count as reached, matching
`appendAgentRuntimeHoursWarning`). When a soft limit exists, the tooltip
states the actual soft-limit percentage computed from `soft_limit /
limit`. The panel is hidden entirely when the feature is disabled, which
is how the backend reports a zero-hour allocation.

The usage period dates from `Feature.usage_period` render above the bar
(start left, end right), matching the Agent Workspace Builds panel, so
admins can tell which license period the consumption covers. The backend
always attaches a period to an enabled feature; a missing or unparsable
one just omits the dates row. The CODAGT-855 link to the per-user usage
view is deferred: that view (F3, CODAGT-861) and the API it needs (F2,
CODAGT-857) are not built yet, so there is no route to target. The link
lands with F3.

An enabled feature with `limit` omitted is the unlimited allocation
(license claim `-1`, coder/license#49): the card shows a full neutral
bar with "Limit: Unlimited" and the tooltip without threshold copy,
mirroring `SeatUsageBarCard`'s unlimited state. The invalid-limits error
is kept for negative limits, which can only come from a decoding bug.

Storybook stories cover the green/orange/red states, the capped
over-allocation bar, missing usage data, the missing usage period, the
hidden state, the unlimited state, the invalid-limit error state, and
each tooltip message via play interactions. The page view story verifies
the panel renders above Agent Workspace Builds.

Usage renders with exactly one decimal (e.g. `435.8`, `1,000.0`),
derived from #27985's `actual_ms` field and floored to tenths with
integer math. The same floored value drives the reached states, so the
displayed number and the bar color flip at the same instant; a fraction
past the allocation now trips the red state (`1,000.1 >= 1,000`), pinned
by the `ReachedAllocationByFraction` story. The limit and hard cap
labels stay whole because the license claims are whole hours.

Stacked on #27985.

closes
[CODAGT-855](https://linear.app/codercom/issue/CODAGT-855/f1-runtime-hours-consumption-on-the-licenses-page).
2026-08-20 12:42:51 +07:00
DevCats 64f26ef387 chore: add supabase icon (#28327)
Adds the Supabase icon to the built-in icons for use with external auth
and modules.

- Adds `site/static/icon/supabase.svg` (official Supabase logo)
- Registers icon in `site/src/theme/icons.json`

The icon is full-color with good contrast on both light and dark
backgrounds, so no special handling in `externalImages.ts` is needed.
2026-08-19 16:35:00 -05:00
TJ 5be2fa7974 feat(site/src/pages/AgentsPage/components/ChatsSidebar): move PR icon to diff line, center unread dot, persist kebab (#28326)
Three chat-sidebar row updates cherry-picked from #28317 (that PR stays
open as the full riff), plus one removal:

- **PR icon moves to the diff line.** The linked pull request icon (open
/ draft / merged / closed) no longer replaces the status icon when the
chat is idle. It renders on the second line next to the `+additions
-deletions` stats, in both the sidebar tree and the chat search results,
regardless of chat status.
- **Unread dot alignment.** The unread dot is centered in a box matching
the kebab's width, so it sits on the same axis as the actions trigger
instead of shifting when the hover swap happens.
- **Persistent kebab on the selected chat.** The row actions trigger
stays visible on the active chat (no hover required, reachable on touch
devices); the timestamp is hidden there. Other rows keep the hover swap.
- **Subagent count badge removed.** The `N` + bot icon added to the
metadata line in #28234 no longer renders, making room for the PR icon
and diff stats. The "Show subagents" menu toggle and expand behavior are
unchanged.

Status and PR icons carry `role="img"` with accessible labels ("Idle",
"Pull request merged", ...).

<details>
<summary>Implementation notes</summary>

- `statusConfig.ts`: `getChatDisplayConfig(chat)` now returns the status
icon plus a separate `prIcon` config (icon, className, accessible label)
instead of letting the PR icon take precedence over the status. Labels
added for all status and PR states.
- `ChatTreeNode.tsx` / `ChatSearchResults.tsx`: render `prIcon` on the
second line before the line stats.
- Kebab persistence reuses the existing `isActiveChat` value:
`opacity-100` on the trigger, `hidden` on the timestamp for the active
row.
- `ChatTreeNode.tsx`: the on-row subagent count badge (`childIDs.length`
+ `BotIcon`) is removed; the `SubagentsMenuToggle` story only asserts
the menu toggle, so it still passes.
- Not included from #28317: the subagent status icon variants
(`CheckCheckIcon` / `SubagentsLoaderIcon`, the `hasSubagents` param, and
the `WithSubagentStatusIcons` story).

Validation:

- `pnpm run lint` (biome, tsc, knip, circular deps, compiler): pass
- Story tests: 96 passed (4 files) including the updated
`WithPRStateIcons` label assertions and the new
`ActiveChatKebabPersistent` story
- Unit suite for the sidebar area: 65 passed
- `make pre-commit` (via git hook): pass

</details>

_Generated by Coder Agents on behalf of @tracyjohnsonux._
2026-08-19 21:02:11 +00:00
Michael Suchacz 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.
2026-08-19 19:46:23 +00:00
david-fraley 8203b2ebaf feat!: hide Coder Tasks behind the enable-ai-tasks flag (#28008) 2026-08-19 14:46:10 -05:00
Michael Suchacz 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 -->
2026-08-19 19:25:18 +00:00
Michael Suchacz 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 -->
2026-08-19 19:11:52 +00:00
Danielle Maywood 29c129e2d0 fix(site): fix price threshold display and debounce model price lookup (#28298) 2026-08-19 20:10:14 +01:00
TJ dd43574990 refactor: remove AI add-on badge, seat column, and add-on wording leftovers (#28004)
Removes all "AI add-on" labeling from the UI, plus two leftover "AI
Governance add-on" wording items from the licensing repackaging (AI
Governance is now included with Premium).

- Remove the **AI add-on** badge from the group settings AI budget
section and the AI Governance add-on card on the Licenses page.
- Remove the **AI add-on** column (check icon showing AI seat
consumption) from the Users and Organization Members tables, along with
its help popover, the `AISeatCell` component, and the
`shouldShowAISeatColumn` entitlement helper.
- Update related Storybook stories.
- Drop "add-on" from the AI cost control route comments in
`enterprise/coderd/coderd.go` and the `coder exp ai-model-prices`
prerequisite in `docs/ai-coder/ai-gateway/cost-controls.md` (leftovers
not covered by #28075/#28077/#28268).

The `has_ai_seat` API field is left intact.

<details>
<summary>Removal scope</summary>

Badge usages removed:

- `site/src/pages/GroupsPage/GroupSettingsPageView.tsx`
-
`site/src/pages/DeploymentSettingsPage/LicensesSettingsPage/AIGovernanceAddOnCard.tsx`

AI seat column plumbing removed:

- `site/src/pages/UsersPage/UsersTable.tsx` and `UsersPage.tsx`
- `site/src/pages/OrganizationSettingsPage/OrganizationMembersTable.tsx`
and `OrganizationMembersPage.tsx`
- `site/src/modules/users/AISeatCell.tsx` (deleted)
- `AiAddonHelpPopover` in `site/src/modules/users/UserHelpPopovers.tsx`
- `shouldShowAISeatColumn` in
`site/src/modules/dashboard/entitlements.ts`

Add-on wording leftovers:

- `enterprise/coderd/coderd.go`: 7 route comments (comments only, no
behavior change)
- `docs/ai-coder/ai-gateway/cost-controls.md`: model prices CLI
prerequisite

</details>

---

🤖 This pull request was generated by Coder Agents on behalf of
@tracyjohnsonux.
2026-08-19 11:43:27 -07:00
Michael SuchaczandMathias Fredriksson 443e3b9b80 feat!: org-scope MCP server configs with RBAC (#27942)
Moves `mcp_server_configs` from deployment scope to organization scope
so each organization fully controls the MCP servers its members can use
with Coder Agents.

## Summary

- Migration: adds `organization_id` (NOT NULL, FK) and keeps existing
rows as the default organization's originals with credentials intact.
Other organizations start with no MCP servers and configure their own;
nothing is copied across organizations. Chats outside the default
organization keep any now-cross-organization `mcp_server_ids` entries;
the runtime already ignores IDs that do not resolve in the chat's
organization, so no data rewrite is needed. Slug uniqueness becomes
`(organization_id, slug)`.
- RBAC: new org-scoped `ResourceMCPServerConfig` with regosql converter
and `GetAuthorizedMCPServerConfigs`; org admins get in-org CRUD, org
members get read (replaced by ACL evaluation in the follow-up ACL PR in
this stack).
- API: all config routes nest under the organization, matching
templates: `POST|GET
/api/experimental/organizations/{organization}/mcp-servers` and
`GET|PATCH|DELETE .../mcp-servers/{mcpserverconfig}` (plus
`oauth2/connect`), resolved by a read-only param middleware that
conceals read-denied and cross-organization access as 404. Two routes
stay on the frozen `/api/experimental/mcp/servers/{mcpServer}` block:
the OAuth2 callback (the redirect URI baked into existing AS-side client
registrations) and `oauth2/disconnect`, which must remain reachable by
users removed from the organization so they can still revoke their token
grant.
- Chat runtime: selection validation and generation resolve configs
strictly by IDs, enabled state, and the chat's organization in SQL;
requested duplicates are normalized; invalid or cross-org IDs are
rejected with the precise ID list. IDs already persisted on a chat are
exempt from message-time rejection so disabling a selected server never
blocks sends; generation skips servers that are no longer usable.
- Frontend: API layer and admin settings pages target the new endpoints.
The admin page manages the default organization's servers; the org
picker is tracked separately (CODAGT-714).

- Security hardening from review: OAuth user grants are additionally
bound to `oauth2_revocation_url` (changing it invalidates grants, and a
racing OAuth callback gets 409 instead of recreating a grant).
Stack-wide SSRF protection for MCP config-directed traffic was split
into its own PR at the top of this stack (#28242) to keep this diff
reviewable; this PR keeps main's existing discovery IP-range guard.
OAuth2 auto-discovery now completes before the config row is inserted: a
failed discovery persists nothing, and there is no provisional row that
concurrent updates could race against.

Two follow-up PRs in this stack were split out to keep this diff
reviewable: #28064 completes the swagger annotations for the moved
routes (main already ships these experimental MCP handlers unannotated),
and #28065 carries hardening fixes and regression pins on top of the
cutover.

- Force On enforcement (landed on main mid-review) is org-scoped: the
forced set is read per chat organization
(`GetForcedMCPServerConfigsByOrganization`), so another organization's
`force_on` server never attaches to a chat.

## Breaking changes (experimental API)

The MCP server config endpoints move from the deployment-scoped
`/api/experimental/mcp/servers` block to organization-nested paths:
`POST|GET /api/experimental/organizations/{organization}/mcp-servers`
and `GET|PATCH|DELETE
/api/experimental/organizations/{organization}/mcp-servers/{mcpserverconfig}`
(plus `oauth2/connect`). The old paths are removed, so API consumers
must supply an organization. Two routes intentionally stay on the frozen
`/api/experimental/mcp/servers/{mcpServer}` block: the OAuth2 callback
(its redirect URI is baked into existing AS-side client registrations)
and `oauth2/disconnect` (must remain reachable by users removed from the
organization). These endpoints are under `/api/experimental`, so no
deprecation window is provided.

## Rolling upgrades

During a rolling deploy, an old replica creating an MCP config can fail
the new `NOT NULL organization_id` constraint until it is upgraded
(reads are unaffected: old binaries' generated queries select their own
column lists). This matches the repo's existing precedent for additive
NOT NULL migrations (000562) and affects only the admin config-create
path in the upgrade window.

Upgrades are expected to run in scheduled maintenance downtime with the
database locked during migration, so the migration ships no
rolling-upgrade compatibility machinery. The down migration deletes
organization-created configs (their chat references are cleaned by the
000510 delete trigger) and restores deployment-wide slug uniqueness.

Part of the MCP org-separation stack (CODAGT-711 -> CODAGT-717 audit ->
CODAGT-712 ACLs -> CODAGT-806 token RBAC).

Closes https://linear.app/codercom/issue/CODAGT-711

UAT: validated end to end on a two-org dogfood deployment, including a
real pre-migration to post-migration upgrade, cross-org isolation
(404s), same-slug-two-orgs, chat selection gating, and a live MCP tool
call through the org-scoped generation path. The migration was later
revised to keep existing rows in the default organization only (no
per-organization copies); that revision is covered by the migration test
suite.

> Mux (AI agent) authored this PR on Mike's behalf.

<!-- mux-attribution: model=claude-fable-5 thinking=high -->

---------

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2026-08-19 18:04:13 +00:00
Atif Ali 7ff1278ab3 feat: track Junie as an AI Gateway client (#28266)
## Problem

Junie CLI works with AI Gateway, but every Junie session is recorded as
`Unknown`. Junie sends `User-Agent: Junie:SNAPSHOT` (observed directly
from the CLI), which `GuessClient` does not match, so usage, spend, and
audit views cannot attribute Junie traffic. There were also no docs for
pointing Junie at AI Gateway.

## Fix

- Detect the observed `junie:` user-agent prefix and add the `Junie`
client value.
- Add the Junie icon so sessions render with a logo instead of the
fallback question mark.
- Add a Junie client page and list it in the compatibility table.
- Add `Roo Code` and `Charm Crush` to the documented `client` filter
values. Both were already implemented but missing from the list.

## Supported configurations

| Provider  | API type          | Endpoint                 |
|-----------|-------------------|--------------------------|
| OpenAI    | `OpenAIResponses` | `/openai/v1/responses`   |
| Anthropic | `Anthropic`       | `/anthropic/v1/messages` |

BYOK works on both through `extraHeaders` with
`X-Coder-AI-Governance-Token`.

Preview:
https://coder.com/docs/@matifali-junie-ai-gateway-client/ai-coder/ai-gateway/clients/junie

> 🤖 This PR was created with the help of Coder Agents, and needs a human
review. 🧑‍💻
2026-08-19 21:13:49 +05:00
Phorcys 🐾 1bb0978361 fix(site): centralize UUID generation and fix non-secure crash (#28285)
Fixes OSS-38: `crypto.randomUUID is not a function` when accessing Coder
over plain HTTP.

### Root cause
`crypto.randomUUID` is only defined in secure contexts (HTTPS or
localhost). It is `undefined` when Coder is served over plain HTTP. The
`uuid` package's `v4()` short-circuits to `crypto.randomUUID()` when it
is truthy, and in the production bundle that path compiles down to a
direct `crypto.randomUUID()` call, so `uuidv4()` throws over HTTP.
Observed crash (Template Builder, in the `useMemo` that mints the
telemetry session ID):

```
TypeError: crypto.randomUUID is not a function
  Rt/h<@.../assets/TemplateBuilderPage-*.js
  useMemo@...
```

The same `uuidv4()` path backed the Terminal reconnection token and the
Agent chat tab IDs / reconnection tokens, so those flows were affected
too.

### Fix
Add a single `generateUUID` helper in `site/src/utils/random.ts` that
derives the UUID from `crypto.getRandomValues` (the one `Crypto` member
available in insecure contexts) and only calls `crypto.randomUUID` when
it actually exists. Route all client-side UUID generation through it, so
every call site is fixed at once and future ones stay safe.

### Changes

| Change | Route | Human tested/verified |
|---|---|---|
| `utils/random.ts` — new `generateUUID()` helper | N/A | N/A |
| `TemplateBuilderPage.tsx` — telemetry session ID |
`/templates/new/builder` | [x] |
| `TerminalPage.tsx` — reconnection token |
`/:username/:workspace/terminal` | [x] |
| `AgentChatPageView.tsx` — tab IDs + reconnection tokens |
`/agents/:agentId` | [ ] |
| `useChatDraftAttachments.ts` — draft client IDs | `/agents/:agentId` |
[x] |
| `TaskApps.stories.tsx` — mock app IDs (Storybook) |
`/tasks/:username/:taskId` | [ ] |
| `WorkspaceSharingIndicator.stories.tsx` — mock actor IDs (Storybook) |
`/workspaces` | [ ] |
| Removed unused `uuid` / `@types/uuid` direct deps | N/A | N/A |

`uuid` is still pinned via a `pnpm.overrides` entry because
`mermaid`/`streamdown` depend on it transitively.

### Tests
- `random.test.ts` covers a valid UUID, uniqueness, and the
insecure-context path (with `crypto.randomUUID` stubbed to `undefined`).
- `TerminalPage.test.tsx` now mocks `#/utils/random` instead of the
`uuid` module.

### Validation
- `make fmt`, `biome check`, `tsc --noEmit` clean
- `vitest run` green: `random.test.ts` (6), `TerminalPage.test.tsx`
(16), `useChatDraftAttachments.test.ts` (17), both changed stories (12)
- Reproduced the original crash over HTTP on a production build, top
frame `uuidv4()` in Template Builder.

---

_This PR was generated by Coder Agents on behalf of @phorcys420._
2026-08-19 15:51:42 +00:00
Cian Johnston 023f61626d feat: default AI Gateway sessions list to a 24h time range (#28256)
The AI Gateway sessions list page takes 5-13 seconds to load because
`ListAIBridgeSessions` scans all `aibridge_interceptions` rows (~1M on
dogfood) when no time filter is set. This PR defaults the list to the
last 24 hours of sessions, reducing the scan by roughly two orders of
magnitude without any backend or migration changes: the
`started_after`/`started_before` filters already exist end-to-end in
SQL, searchquery, and the API.

The default range is held in component state (not the URL) and merged
into every query payload including prefetches, so the unbounded query
never runs on page load. A time range filter in the filter bar lets
users override the window explicitly, which doubles as a forensic tool.
Picking a range writes quoted RFC 3339 timestamps into the existing
filter query. There are no presets and no unbounded "all time" mode, so
the fast path is the only path. Existing "All sessions"/"My sessions"
presets reset the filter query, which resets the time window to the
default 24 hours; that is intentional.

Also makes the five filter triggers a uniform width and left-aligns the
new picker so the search input keeps room on wide viewports.

Depends on #28255 (the DateTimeRangeFilter component) and #28254
(filterQuery serialization).


Part of
[AIGOV-580](https://linear.app/codercom/issue/AIGOV-580/ai-gateway-sessions-page-takes-5-10-seconds-to-load)

---
_Generated by Coder Agents on behalf of @johnstcn._$

---

**Stack:** #28254 (filterQuery fix) \u2192 #28255 (component) \u2192
#28256 (sessions page)
2026-08-19 16:42:43 +01:00
Jake Howell 46c0dba1e9 fix: standardize EmptyState typography (#27720) 2026-08-19 22:01:58 +07:00
TJ 97cb722fb8 fix: show no budget instead of unlimited for empty group AI budget (#27993)
An empty per-member AI budget on the group settings page previously
displayed "unlimited budget" with a "Members in this group have no
spending cap." alert. Unlimited spend only applies when the everyone
group is the sole group, so this messaging was misleading everywhere
else.

## Changes

- Empty budget now shows "This group has **no budget** set. View docs"
with no info alert. "View docs" links to [Effective group
resolution](https://coder.com/docs/ai-coder/ai-gateway/cost-controls#effective-group-resolution)
using the versioned `docs()` helper.
- Removed the "Members in this group have no spending cap." alert
entirely.
- Input placeholder changed from `unlimited` to `no budget`.
- Updated the `AIBudgetUncapped` story expectations to match.

## Unchanged

- Entering an explicit `$0` still shows the "A $0 limit disables AI
access for this group." alert, as before.
- Saving with an empty field still sends `null` to the budget API;
backend semantics are untouched.

Story tests pass: `pnpm test:storybook
src/pages/GroupsPage/GroupSettingsPageView.stories.tsx` (7/7).

---

*This PR was generated by Coder Agents on behalf of @tracyjohnsonux.*
2026-08-19 06:15:06 -07:00
Cian Johnston da99941099 feat(site): add shared DateTimeRangeFilter component (#28255)
A text-expression datetime range picker with From/To inputs accepting
`now`, a clock time (current day), a date (midnight), or a date with a
clock time. Invalid text gets inline errors, underspecified expressions
resolve on blur, and out-of-order boundaries clamp against the other
one. The trigger derives a concise label ("Last 24 hours", "Apr 10",
"Aug 11 - Today", "Apr 17 - 19").

Placed in `site/src/components/DateTimeRangeFilter/` so other pages can
reuse it. Expression parsing and trigger-label derivation live in
`timeRange.ts` next to the component; both are generic over `Date`
pairs. Depends on #28254 for the shared `filterQuery` serialization
helpers.

Part of
[AIGOV-580](https://linear.app/codercom/issue/AIGOV-580/ai-gateway-sessions-page-takes-5-10-seconds-to-load)

---
_Generated by Coder Agents on behalf of @johnstcn._$

---

**Stack:** #28254 (filterQuery fix) \u2192 #28255 (component) \u2192
#28256 (sessions page)
2026-08-19 13:47:06 +01:00
Danielle Maywood 8405bbb26c feat(site): show best-effort model pricing on the model form (#28290) 2026-08-19 11:45:30 +01:00
Bobby Ho 663f41ffa9 feat: derive OAuth2 client type from token_endpoint_auth_method (#28043)
Adds an OAuth2 client type (public vs confidential, RFC 7591 §2) derived
from the requested auth method instead of hardcoded confidential. The
type is stored and guarded here, but no endpoint enforces on it yet;
public behavior at the token endpoint follows in the next PR in the
stack.

- Client type is derived once and reused by both registration and
redirect URI validation, so they can't disagree
- IsPublic() fails closed: an unrecognized or missing value reads as
confidential
- RFC 7592 update (PUT) now rejects moving a client between public and
confidential (400) instead of silently flipping it when the auth method
is omitted
- Discovery still doesn't advertise "none"; follows once the token
endpoint honors it

### Behavior by client shape

`client_type` is derived from `token_endpoint_auth_method` at POST and
pinned at PUT. RFC 7592 GET/PUT authenticate with the registration
access token, not the client secret, so neither endpoint reads a secret.

| Registered with | Stored `client_type` / method | GET reports | PUT
that flips the method |

|------------------------------------|---------------------------------------|-----------------------|-----------------------------------------|
| omitted, or `client_secret_basic` | `confidential` /
`client_secret_basic` | `client_secret_basic` | `none` → 400
`invalid_client_metadata` |
| `none` (new) | `public` / `none` | `none` | `client_secret_*` → 400
`invalid_client_metadata` |
| `none` (before this PR) | `confidential` / `none` | `none` | either →
200, type stays `confidential` |

- PUT still replaces every other RFC 7591 field. `client_type` is the
only pinned one; the method may move within a type
(`client_secret_basic` ↔ `client_secret_post`).
- Row 3 is the only shape where the two columns disagree. The guard
fires only on a method change that crosses the type line, so those
clients keep managing themselves instead of being locked out of their
own configuration endpoint.
- The token endpoint does not consult `client_type` yet, so every client
still authenticates with a secret and registration still issues one.

Split out of #27873, second in the stack (on top of #28041).
Refs
https://linear.app/codercom/issue/ENG-3029/oauth2-support-public-client
2026-08-18 21:12:44 -07:00
Jake Howell 4d13bef74d feat(site): add Install Coder Desktop item to UserDropdown (#28244)
> 🤖 This PR was modified by Coder Agents on behalf of Jake Howell.

Resolves
[DEVEX-722](https://linear.app/codercom/issue/DEVEX-722/add-install-coder-desktop-button-to-userdropdown).

Adds an **Install Coder Desktop** item to `UserDropdown`, sitting
directly above the existing **Install CLI** option. Because
`UserDropdownContent` is shared, it appears in both the navbar dropdown
and the agents-page sidebar footer.

### Screenshots

macOS/Windows shots are captured with `navigator.platform` overridden
accordingly; the Linux shot is the real workspace platform, where the
Coder Desktop item is hidden. Install Coder Desktop uses a monitor
glyph, and Install CLI now uses a terminal glyph (it was a monitor on
`main`).

| `main` (before) | This PR · macOS/Windows | This PR · Linux / iPad OS
|
| --- | --- | --- |
| ![UserDropdown on
main](https://raw.githubusercontent.com/coder/coder/jakehwll/devex-722-assets/.github/assets/devex-722-userdropdown-main.png)
| ![UserDropdown with Install Coder
Desktop](https://raw.githubusercontent.com/coder/coder/jakehwll/devex-722-assets/.github/assets/devex-722-userdropdown-new.png)
| ![UserDropdown on Linux without the Desktop
item](https://raw.githubusercontent.com/coder/coder/jakehwll/devex-722-assets/.github/assets/devex-722-userdropdown-linux.png)
|

### Behaviour
- **Platform-gated visibility:** shown only on macOS and Windows (the
platforms Coder Desktop ships for), hidden on Linux/other. The Linux
client
([coder/coder-desktop-linux](https://github.com/coder/coder-desktop-linux))
is experimental and not advertised yet, so it stays hidden there.
- **Links to the docs** (`https://coder.com/docs/user-guides/desktop`)
rather than a single install method. The docs page is the canonical hub
covering Homebrew, WinGet, manual release downloads, and source, and it
stays current without frontend changes. The `coder-desktop-*` repo
READMEs don't enumerate install methods; they defer to these same docs.
- **No installed-detection:** consistent with the Install CLI item, we
don't try to detect whether Coder Desktop is already present.

### Changes
- `site/src/utils/platform.ts`: add `isWindows()` and
`supportsCoderDesktop()`.
- `UserDropdownContent.tsx`: render the platform-gated item
(`MonitorIcon`) and switch Install CLI to `TerminalIcon`.
- Unit tests for the platform helpers and Storybook coverage for the
dropdown item (present + correct href on macOS/Windows, absent on
Linux/iPadOS).


Implementation plan & decision log

**Ticket open questions and decisions**

1. *Shown to all users or only supported platforms?* Only supported
platforms (macOS, Windows); hidden elsewhere. The Linux client is
experimental and not advertised yet, so hiding it there is intentional.
2. *Where should it link?* The Coder Desktop docs page. Users install
via many methods (brew, winget, releases, source); the docs are the
single hub that lists them all and are maintained in-repo. The GitHub
release pages / repo READMEs only cover downloads and defer back to the
docs.
3. *Hide when already installed?* No. There is no reliable browser-side
way to detect a native app install, and it mirrors how Install CLI
behaves.

**Implementation**
- Extend `site/src/utils/platform.ts` with `isWindows()` and
`supportsCoderDesktop()` (reusing the existing `isMac()`).
- In `UserDropdownContent.tsx`, conditionally render an `Install Coder
Desktop` `DropdownMenuItem` (external link, `MonitorIcon`, opens in a
new tab) above `Install CLI` when `supportsCoderDesktop()` is true.
- Tests: `platform.test.ts` (OS detection via `vi.stubGlobal`) and
`UserDropdown.stories.tsx` (visibility + href per platform, mocking
platform detection with `spyOn`).



Left as a **draft** pending review, let me know when you'd like it
opened.
2026-08-19 09:11:07 +07:00
McKayla はな 60722bb653 fix: link workspaces empty state to template builder (#28280) 2026-08-18 18:30:28 -06:00
Jon Ayers 821d91fabd fix: log tailnet tunnel authorization decisions (#27819) 2026-08-18 18:21:50 -05:00
TJ b3f05a23fc feat(site/src/pages/AgentsPage): surface subagents in the chat sidebar (#28234)
## What

Makes subagents more discoverable in the agent chat sidebar, without
disturbing the existing status icon or layout.

- **Parent chat rows show a subagent count** with a bot icon (e.g. `3
🤖`) at the start of the metadata line, next to the diff stats. Only
shown when a chat has subagents; uses lucide `BotIcon`, the same icon
this codebase already uses to denote agents. Passive indicator (the
leading status icon, timestamp, and kebab are untouched).
- **The chat actions menu gains a "Show subagents (N)" / "Hide
subagents" toggle**, grouped directly under "Rename chat" in both the
kebab (⋮) and the right-click context menu. It expands/collapses the
row's subagents and is only rendered when the chat has subagents.

Expansion still uses the existing sidebar expand state, so the hover
chevron, the menu toggle, and the count all stay in sync.

## Notes / decisions

- Subagent children are capped at depth 1, so the count and toggle only
appear on parent chats.
- The menu toggle lives in the shared `ChatActionsMenuItems`, so it
appears in both the kebab and the right-click menu; the top-bar kebab
intentionally does not pass the props, so it stays hidden there.
- The on-row `N 🤖` indicator is currently passive (clicking the row
opens the chat as before). Can be made a click-to-expand control if
wanted.

## Testing

Validated in Storybook (`ChatsSidebar` stories) with 0 TypeScript
errors. Added a `SubagentsMenuToggle` story with a `play` function
asserting the label swaps between "Show subagents (3)" and "Hide
subagents" and that children expand. Existing `ChatsSidebar.test.tsx`
does not assert menu contents and is unaffected.

---

*This PR was generated by Coder Agents on behalf of @tracyjohnsonux.*
2026-08-18 13:25:42 -07:00
Danielle Maywood 72a6c8ae72 fix(site): stop the new-turn scroll snap on agent chat (#28213) 2026-08-18 21:01:43 +01:00
Mathias FredrikssonandMichael Suchacz d3f08b1983 feat: audit chat system instructions changes (#27668)
Adds an audit record for administrative events on the deployment-wide
chat instruction settings (system prompt, the include-default toggle,
and the plan-mode instructions), per CODAGT-719 and operator decision
D5. Each endpoint records under a stable identity: resource type
`chat_instruction_settings`, a fixed resource ID and a human-readable
target ("System prompt", "Plan mode instructions"), so two changes to
one setting share an ID and history-by-setting works. A real change
exports a Write entry with the old-to-new text visible; a
value-identical PUT still upserts and still returns 204 but records
nothing.

Attempts are recorded, not only transitions. Identity is assigned before
the authorization check, so a denied PUT exports a 403 row with an empty
diff (no request content reaches it), a validation failure exports a 400
row, and a write failure exports a 500 row, each with an empty diff; an
operator can tell "nothing changed" from "something changed and capture
degraded" by the status code.

The write path stays authoritative. The advisory lock and, on plan-mode,
the transaction exist only to serve change-detection; if any of that
machinery fails (lock, begin, commit, rollback), the handler runs main's
idempotent write path directly and derives the response from it, so a
member-visible failure of audit-only infrastructure can never replace
main's successful response. Accepted consequence: when the lock cannot
be taken, two concurrent identical writes can produce two rows instead
of one. That is audit degradation, which is allowed; changing a member's
response is not. Write failures keep the exact response the endpoint
produced before this wiring (transaction error for the system prompt,
which was always transactional; the raw write error for plan mode, which
was not), and the full transaction error is logged so rollback failures
cannot vanish.

<details>
<summary>CODAGT-66 plan entry: S1 (verbatim)</summary>

**S1 `feat: audit chat system instructions changes`** (CODAGT-719; base:
main)

- Struct: `database.ChatSystemPromptSettings{ID uuid.UUID; SystemPrompt
string; IncludeDefaultSystemPrompt bool; PlanModeInstructions string}`
in `coderd/database/types.go` (ticket-sketched shape; one struct, both
endpoints).
- Registration: union entry (diff.go), table.go entry (`id`
ActionIgnore, other three ActionTrack), `AuditActionMap` Write-only;
four request.go cases (`ResourceTarget` "", `ResourceID` from struct,
`ResourceType` new enum value `chat_system_prompt_settings`,
`ResourceRequiresOrgID` false with the "Artificial ID / deployment
singleton" comment convention).
- Migration: `ALTER TYPE resource_type ADD VALUE IF NOT EXISTS
'chat_system_prompt_settings';` comment-only no-op down (000558 shape);
number picked at push per the numbering constraint.
- codersdk: constant + prose `FriendlyString` ("chat system prompt
settings"); `TestAuditDBEnumsCovered` forces both. `coderd/audit.go`
presentation switches: rely on safe defaults (no link, generic
description); no FE changes (filter label falls back to capitalized
value; acceptable per precedent).
- Wiring `putChatSystemPrompt` and `putChatPlanModeInstructions`:
InitRequest with Action Write; artificial `ID: uuid.New()` on `New` only
when a change is detected; no-op suppression by leaving both aReq sides
unset (nil resource IDs skip the log, request.go skip rule); the write
path itself stays byte-identical (upserts still run unconditionally).
- `putChatSystemPrompt` (writes two keys conditionally in one existing
tx): inside that tx, read the pair via `GetChatSystemPromptConfig` for
`Old`, perform the conditional writes exactly as today, then RE-READ the
pair for `New`. The re-read is load-bearing:
`include_default_system_prompt` is computed from the toggle row AND the
prompt, so a prompt-only write can flip the effective value without the
request carrying the pointer. `PlanModeInstructions` stays zero on both
sides.
- `putChatPlanModeInstructions` (no tx exists today): wrap its
read-upsert in `InTx` (behavior-preserving: same single write);
`Old`/`New` populate only `PlanModeInstructions`; the two system-prompt
fields stay zero on both sides; no cross-key reads.
- Change detection compares the populated payload fields only (never the
artificial ID).
- Tests: handler-level coderdtest with `audit.NewMock()` asserting Write
entry on change and NO entry on a value-identical PUT, for both
endpoints (this also exercises `ResourceRequiresOrgID` end to end); the
fallback-flip case (no explicit include-default row, nonempty prompt set
to empty, effective boolean flips: entry emitted with the boolean diff);
diff assertions (old->new prompt text tracked, not secret) in
`enterprise/audit/diff_internal_test.go`; `TestAuditableResources`
passes by construction.
- Bookkeeping at PR open: correct CODAGT-719's no-op premise ("matches
the existing 204-on-unchanged behavior" does not exist on main;
suppression is new, write path unchanged).
- Review focus: Old capture and the New re-read inside the tx (three of
four existing singletons never set Old; do not copy them; and the
computed include-default value makes a naive New construction wrong);
the skip-on-no-op mechanism; prompt text deliberately visible in diffs.

</details>

Note: the plan excerpt above predates operator decision D5 (2026-07-30),
which this PR implements: the resource type is
`chat_instruction_settings` (not `chat_system_prompt_settings`), each
setting carries a stable ID and a display-name target (not a per-write
artificial ID and an empty target), no-op suppression runs through
`InitRequestWithCancel` (not the nil-ID skip), and attempts (denied,
failed, capture-degraded) record rows with real statuses and empty
diffs. Ticket bookkeeping for CODAGT-719 was corrected on Linear at
kickoff: the ticket's "matches the existing 204-on-unchanged behavior"
premise does not exist on main; suppression is new, and the write path
is unchanged.

> 🤖 This PR was created with the help of Coder Agents, and _will be_
reviewed by a human. 🏂🏻

---------

Co-authored-by: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com>
2026-08-18 20:03:06 +02:00
Michael Suchacz ba5717dc67 fix(site/src/pages/AgentsPage): disallow queued message edits (#28265)
Closes CODAGT-230.

Selecting "Edit queued message" in Agent Chat did not stop the queued
message from sending: edit mode was purely client-side state, so at turn
end the backend promoted the queued message and sent it anyway, leaving
the UI stuck in a stale "Editing queued message" state with the send
button showing "Save".

This removes queued-message editing entirely instead of adding backend
locking. Queued messages can still be promoted ("Send now") and deleted
("Remove from queue"); editing already-sent history messages is
unchanged.

## Changes

- Remove the Edit pencil from queued message rows
(`QueuedMessagesList`), the "Editing queued message" banner and "Save"
button label (`AgentChatInput`), and the queue-edit state, handlers, and
prop plumbing (`AgentChatPage`, `AgentChatPageView`, `ChatPageContent`).
- Remove the queued-edit save flow that deleted the original queued row
and re-queued a new message, plus the now-unused `rawText`/`fileBlocks`
fields of `getQueuedMessageInfo`.
- Drop the redundant `isEditingHistoryMessage` prop on `ChatPageInput`,
which had become identical to `isEditing`, and stop discarding the
delete/promote promises so `QueuedMessagesList` busy state works again.
- Replace the queued-edit stories and tests with an `ActionsExcludeEdit`
regression story asserting queued rows expose "Send now" and "Remove
from queue" but no "Edit" button.

## Testing

- `pnpm -C site check`, `pnpm -C site lint:types`.
- Unit: `AgentChatPage.test.ts`, `QueuedMessagesList.test.ts` (92
tests). Storybook: all four touched story files (102 tests). A
pre-existing unhandled xterm error in `AgentChatPageView.stories.tsx`
reproduces identically at the merge base.
- Remote dogfood UAT passed on a dev.coder.com workspace with a real
model: no edit affordance on queued rows, queued delete and turn-end
promotion still work, history-message editing and Escape behavior
unaffected.

> Mux acted on behalf of Mike for this pull request.
2026-08-18 19:34:40 +02:00
Michael Suchacz 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 -->
2026-08-18 19:15:30 +02:00
Michael Suchacz 7724ee281a feat: defer MCP tool schemas behind a find_tools search (#28225)
## Summary

When the `mcp-tool-search` experiment is enabled, chatd stops inlining
connected MCP tool schemas into every generation. It instead exposes a
built-in `find_tools` tool whose description carries a compact catalog
of the deferred tools, and only ships full JSON schemas for tools the
model has activated by searching or by calling them directly.

Closes [CODAGT-760](https://linear.app/coder/issue/CODAGT-760).

## Problem

Tool-heavy agent configurations (GitHub, Linear, Notion, and dev-tooling
MCP servers) inline over 100k tokens of tool schema definitions into
every generation. Initial uncached requests reached ~216k tokens with
time-to-first-token close to nine minutes, while the model typically
invokes only a handful of tools per turn.

## How it works

- `decideMCPToolSearch` defers external and workspace `.mcp.json` MCP
tools whenever the experiment is enabled. Native, dynamic, provider,
skill, and transport tools are never deferred.
- `find_tools` embeds a server-grouped catalog in its tool description
(degrading to names-only, then counts-only, then a constant-size summary
past a context-scaled size cap) and scores keyword matches across tool
names, descriptions, parameter schemas, and server metadata. Queries can
scope to one server with a `server:` prefix, and exact `names` arguments
always activate.
- Activation state is ephemeral: it is re-derived each generation from
surviving chat history (`find_tools` results and direct calls to
deferred tools), so activations naturally lapse when compaction
summarizes them away. Aggregate activated schema weight is capped at 10%
of the context window, shedding the least recently activated schemas
first; `find_tools` shares that budget across parallel calls in one
step. No new persistence.
- Deferred tools stay registered for execution, so the model can call a
cataloged tool directly without searching first; the schema is activated
for subsequent steps.
- Fail-open: the experiment being disabled, an empty candidate set, or
an MCP tool named `find_tools` all disable deferral, leaving today's
behavior byte-identical on the wire.
- Prometheus counters/histograms track `find_tools` calls, matches,
activations, and deferred token weight.
- The conversation timeline renders `find_tools` calls with a collapsed
search summary and expandable match list, falling back to the generic
renderer on malformed payloads.

## Validation

- Unit tests for the catalog, matcher, experiment-gated decision, and
activation derivation; end-to-end chatd generation tests covering
search-then-call, direct-call activation, experiment-off wire parity,
compaction lapse, and subagent tool gating.
- Storybook interaction tests for the timeline rendering and
malformed-payload fallback.
- Remote dogfood UAT on dev.coder.com passed: deferral with a real MCP
server and Anthropic model, direct calls without prior search,
activation persistence across turns, experiment-off parity, and clean
UI/console.

> Disclosure: Mux (AI agent) authored this PR on Mike's behalf.
2026-08-18 19:12:47 +02:00
Cian Johnston 962366ffc6 fix(site): quote colon-containing values in filter serialization (#28254)
`stringifyFilter` in the shared `Filter` component only quoted values
containing spaces. Filter values like RFC 3339 timestamps
(`2026-08-16T20:42:00Z`) contain colons but no spaces, so when any
filter was edited and the whole query re-serialized, the timestamp went
out unquoted and the backend `searchTerms` parser rejected it (`Query
element ... can only contain 1 ':'`).

Quote values containing colons as well; they were never valid unquoted
because the backend parser already rejects them. Extract
`parseFilterQuery`/`stringifyFilter` into `filterQuery.ts` with unit
tests, including a round-trip of quoted timestamps.

Part of
[AIGOV-580](https://linear.app/codercom/issue/AIGOV-580/ai-gateway-sessions-page-takes-5-10-seconds-to-load)

---
_Generated by Coder Agents on behalf of @johnstcn._$

---

**Stack:** #28254 (filterQuery fix) \u2192 #28255 (component) \u2192
#28256 (sessions page)
2026-08-18 17:01:42 +01:00
Michael Suchacz 5af08a1a09 fix(site): repair the locally-run storybook vitest suite (#28261) 2026-08-18 17:07:39 +02:00
Michael Suchacz 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.
2026-08-18 16:55:43 +02:00
TJ cb0a9ebbbf fix(site/src/pages/AgentsPage): remove sidebar nav bottom divider (#28239)
## What

Removes the horizontal divider line that appears directly under the
**Search** item in the Agents chats sidebar.

The line was the `border-b border-border-default` on the sidebar `<nav>`
that wraps the **New chat** and **Search** items. Since Search is the
last item in that nav, the border rendered as a stray line beneath it.

## Change

`site/src/pages/AgentsPage/components/ChatsSidebar/chats/ChatsPanel.tsx`

```diff
-className="hidden border-b border-border-default px-2 py-1.5 sm:flex sm:flex-col sm:gap-0.5"
+className="hidden px-2 py-1.5 sm:flex sm:flex-col sm:gap-0.5"
```

The bottom border is dropped entirely. Note: this was a Tailwind border,
not MUI.

---

*This PR was generated by Coder Agents on behalf of @tracyjohnsonux.*
2026-08-18 14:39:37 +00:00
TJ 16ae996b93 fix(site/src/pages/AgentsPage/components): clean up agents composer borders and normalize dropdown pills (#28230)
Audit pass on the Agents chat composer: borders, the history-edit
divider, and making the model selector and workspace pill visually
consistent with each other and the left chat-list chevron.

## Borders

1. **Remove the composer outline** — drop `border
border-border-default/80` from the `chat-composer` container in
`AgentChatInput.tsx`. Focus ring, drag-over ring, history-edit warning
shadow, rounded corners, background, and `shadow-sm` are unchanged.
2. **Match the skeleton** — drop the same border from
`ChatInputSkeleton` in `AgentsSkeletons.tsx` so the loading placeholder
stays consistent with the loaded composer.
3. **Neutral history-edit divider** — the divider under the "Editing
will delete all subsequent messages..." warning header used
`border-border-warning/50` (a harsh, light gold line in dark mode).
Switched to `border-border-default/70`, matching the sibling "editing
queued message" divider. The warning text/icon keep `content-warning`.

## Model selector <-> workspace pill consistency

The model selector (`ModelSelector.tsx`) and workspace pill
(`WorkspacePill.tsx`) rendered inconsistently. Normalized both, using
the left `ChatSectionHeader` chevron (`size-3.5`) as the reference:

4. **Chevron size** — model was `size-icon-sm` (18px) and forced to 24px
by the shared `Button` `cva` (`[&>svg]:size-icon-lg`); workspace was
`size-3` (12px). Both now render `size-3.5` (14px). The model override
uses the repo's `[&>svg]:!size-3.5 [&>svg]:p-0` convention since the
`Button` variant otherwise wins on specificity.
5. **Chevron color** — removed `opacity-60` from the workspace chevron
so both are full-opacity `content-secondary` (and
`hover:content-primary`).
6. **Chevron rotation** — the model chevron snapped instead of animating
because the Button's `[&>svg]:transition-colors` overrode the icon's
`transition-transform`. Switched to `[&>svg]:transition` (covers
transform and color) so it rotates smoothly like the workspace/sidebar
chevrons.
7. **Pill shape + fill + height** — gave the model selector
`bg-surface-secondary` (hover `bg-surface-tertiary`) and `rounded-full`
to match the workspace pill, and replaced the fixed `h-8` with the
pill's height mechanism (`h-7` mobile, `h-auto` + `py-0.5` at desktop).

## Notes

- The composer borders pre-existed in the markup; they became more
visually apparent after the recent MUI/Emotion removal (#27821) changed
the global baseline/theming layer.
- Out of scope: `DiffViewer/CommentableDiffViewer.tsx` uses the same
`border border-border-default/80` pattern for the "Add a comment" box on
diffs. Separate surface, left unchanged.

---

> This PR was generated by Coder Agents on behalf of @tracyjohnsonux.
2026-08-18 07:21:08 -07:00