Commit Graph

13345 Commits

Author SHA1 Message Date
Jaayden Halko 86c3983fc0 feat: add AI Governance seat capacity banners (#23411)
## Summary

Add site-wide banners for AI Governance seat usage thresholds:

1. **90% capacity warning (admin-only):** When actual AI Governance
seats are ≥90% and <100% of the license limit, admins see:
   > "You have used 90% of your AI governance add-on seats."

2. **Over-limit banner (admin-only):** When actual seats exceed the
license limit, admins see a prominent warning:
> "Your organization is using {actual} / {limit} AI Governance user
seats ({X}% over the limit). Contact sales@coder.com"
   - Uses floor whole percentage (Go int division / `Math.floor`)
   - Includes a clickable `mailto:sales@coder.com` link
2026-03-27 05:51:51 +00:00
Michael Suchacz 2312e5c428 feat: add manual chat title regeneration (#23633)
## Summary

Adds a "Generate new title" action that lets users manually regenerate a
chat's title using richer conversation context than the automatic
first-message title path.

## Changes

### Backend
- **New endpoint:** `POST
/api/experimental/chats/{chatID}/title/regenerate` returns the updated
Chat with a regenerated title
- **Manual title algorithm:** Extracts useful user/assistant text turns
→ selects first user turn + last 3 turns → builds context with gap
markers → renders prompt with anti-recency guidance → calls lightweight
model → normalizes output
- **Helpers:** `extractManualTitleTurns`,
`selectManualTitleTurnIndexes`, `buildManualTitleContext`,
`renderManualTitlePrompt`, `generateManualTitle` — all private, with the
public `Server.RegenerateChatTitle` method
- **SDK:** `ExperimentalClient.RegenerateChatTitle(ctx, chatID) (Chat,
error)`
- Persists title via existing `UpdateChatByID` and broadcasts
`ChatEventKindTitleChange`

### Frontend
- API client method + React Query mutation with cache invalidation
- "Generate new title" menu item (with wand icon) in both TopBar and
Sidebar dropdown menus
- Loading/disabled state while regeneration is in-flight
- Error toast on failure
- Stories updated for both menus

### Tests
- `quickgen_test.go`: Table-driven tests for all 4 helper functions
(turn extraction, index selection, context building, prompt rendering)
- `exp_chats_test.go`: Handler tests (ChatNotFound,
NotFoundForDifferentUser, NoDaemon)

## Design notes
- The existing auto-title path (`maybeGenerateChatTitle`, `titleInput`)
is completely unchanged
- Manual regeneration uses richer context (first user turn + last 3
turns + gap markers) vs the auto path's single first message
- Endpoint is experimental and marked with `@x-apidocgen {"skip": true}`
2026-03-27 01:47:19 +01:00
Michael Suchacz f35f2a28e6 refactor(site/src/pages/AgentsPage): normalize transcript scrolling to top-to-bottom flow (#23668)
Converts the Agents page transcript from reverse-scroll
(\`flex-col-reverse\`) to normal top-to-bottom document flow with
explicit auto-scroll pinning.

The previous \`flex-col-reverse\` approach (from #23451) required
browser-specific workarounds for Chrome vs Firefox \`scrollTop\` sign
conventions, and compensation logic that could fight user scroll intent
during streaming. This replaces it with standard scroll math
(\`scrollHeight - scrollTop - clientHeight\`) while keeping the proven
\`ScrollAnchoredContainer\` observer machinery.

Changes:
- \`AgentDetailView.tsx\`: layout flip to \`flex-col\`, standard bottom
detection, explicit prepend restoration via \`pendingPrependRef\`
snapshot, sentinel moved inside content wrapper, initial mount bottom
pin, \`scrollToBottomRef\` imperative API for send/edit, user-interrupt
guard with \`isNearBottom\` check
- \`AgentDetailView.stories.tsx\`: all scroll stories updated for
normal-flow semantics, new \`ScrollAnchorPreservedOnOlderHistoryLoad\`
story with deterministic \`IntersectionObserver\` mock
- \`AgentsSkeletons.tsx\` + \`AgentDetailLoadingView\`: removed
\`flex-col-reverse\` so loading state matches the real transcript layout
- \`AgentDetail.tsx\`: replaced \`scrollTop = 0\` on send/edit with
imperative \`scrollToBottomRef\` call

Supersedes #23576 (which was reverted in #23638). Same behavioral goals
but keeps scroll logic local to \`ScrollAnchoredContainer\` rather than
extracting a separate hook.
2026-03-27 01:31:17 +01:00
Kayla はな 4fab372bdc chore: modernize utils/ imports (#23698) 2026-03-26 16:20:25 -06:00
Kayla はな aa81238cd0 chore: modernize all typescript imports (#23511) 2026-03-26 16:04:24 -06:00
Hugo Dutka f87d6e6e82 feat(site): rich preview for the spawn_computer_use_agent tool (#23684)
Adds preview cards for the `spawn_computer_use_agent` tool. Spawning
that agent now renders a rich saying "Spawning computer use sub-agent".
The "waiting for" tool now displays an inline desktop preview which can
be clicked to reveal the desktop in the sidebar.


https://github.com/user-attachments/assets/e486ca0e-a569-4142-bb12-db3b707967b8
2026-03-26 21:52:03 +00:00
Matt Vollmer 113aaa79a0 feat: add pinned chats with drag-to-reorder (#23615)
https://github.com/user-attachments/assets/bd5d12a1-61b3-4b7d-83b6-317bdfb60b3c

## Summary

Adds pinned chats to the agents page sidebar with server-side
persistence and drag-to-reorder. Users can pin/unpin chats via the
context menu, and pinned chats appear in a dedicated "Pinned" section
above the time-grouped list.

## Database

Migration `000453_chat_pin_order`: adds `pin_order integer DEFAULT 0 NOT
NULL` column on `chats` (0 = unpinned, 1+ = pinned in display order).
Three SQL queries handle pin operations server-side using CTEs with
`ROW_NUMBER()`:

- `PinChatByID`: normalizes existing orders and appends to end
- `UnpinChatByID`: sets target to 0 and compacts remaining pins
- `UpdateChatPinOrder`: shifts neighbors, clamps to `[1, pinned_count]`

All queries exclude archived chats. `ArchiveChatByID` clears `pin_order`
on archive. The handler rejects pinning archived chats with 400.

## Backend

Pin/unpin/reorder go through the existing `PATCH
/api/experimental/chats/{chat}` via the `pin_order` field on
`UpdateChatRequest`. The handler routes based on current pin state:
`pin_order == 0` unpins, `> 0` on an already-pinned chat reorders, `> 0`
on an unpinned chat appends to end.

## Frontend

- `pinChat` / `unpinChat` / `reorderPinnedChat` optimistic mutations
using shared `isChatListQuery` predicate
- Sidebar renders Pinned section above time groups, excludes pinned
chats from time groups
- Pin/Unpin context menu items (hidden for child/delegated chats)
- `@dnd-kit/core` + `@dnd-kit/sortable` for drag-to-reorder with
`MouseSensor`, `TouchSensor`, and `KeyboardSensor`
- Local pin-order override prevents flash on drop; click blocker
prevents NavLink navigation after drag

---
*PR generated with Coder Agents*
2026-03-26 16:52:02 -04:00
Mathias Fredriksson f3a8096ff6 perf(site): move useSpeechRecognition into compiled path (#23689)
The hook lived at src/hooks/, outside the React Compiler scope.
It returned a new object literal every render, causing three
handler guards and two downstream JSX guards in AgentChatInput
(233 cache slots) to always miss.

Move the hook to src/pages/AgentsPage/hooks/ where the compiler
processes it. The compiler auto-memoizes the return object, so
manual useMemo is unnecessary.

Also replace ctorRef.current render-time access with a useState
lazy initializer. The ref access caused a CompileError that
would have prevented compilation. Browser API availability is
constant, so useState captures it once.
2026-03-26 22:34:34 +02:00
Jeremy Ruppel beece6d351 fix(site): make AI Bridge sessions query key more specific (#23687)
Seeing what appears to be a react-query caching issue on dogfood. Not
able to repro on my workspace, so this is sort of a shot in the dark 🤷
2026-03-26 16:13:02 -04:00
Jeremy Ruppel 58f744a5c1 fix(site): remove noisy tooltips from AI Bridge session row (#23690)
The provider and client icons both had a tooltip containing the exact
same content as the badge. This removes the tooltips
2026-03-26 15:58:10 -04:00
Kyle Carberry 0f86c4237e feat: add workspace MCP tool discovery and proxying for chat (#23680)
Coder's chat (chatd) can now discover and use MCP servers configured in
a workspace's `.mcp.json` file. This brings project-specific tooling
(GitHub, databases, docs servers, etc.) into the chat without any manual
configuration.

## How it works

The workspace agent reads `.mcp.json` from the workspace directory (same
format Claude Code uses), connects to the declared MCP servers —
spawning child processes for stdio servers and connecting over the
network for HTTP/SSE — and caches their tool lists. Two new agent HTTP
endpoints expose this:

- `GET /api/v0/mcp/tools` returns the cached tool list (supports
`?refresh=true`)
- `POST /api/v0/mcp/call-tool` proxies calls to the correct server

On each chat turn, chatd calls `ListMCPTools` through the existing
`AgentConn` tailnet connection, wraps each tool as a
`fantasy.AgentTool`, and adds them to the LLM's tool set alongside
built-in and admin-configured MCP tools. Tool names are prefixed with
the server name (`github__create_issue`) to avoid collisions.

Failed server connections are logged and skipped — they never block the
agent or break the chat. Child stdio processes are terminated on agent
shutdown.
2026-03-26 19:57:02 +00:00
Jeremy Ruppel 02b58534a0 fix: use TokenBadges for session list row (#23619)
The sessions list row uses a bespoke token badge instead of the
`<TokenBadge />` component, so this fixes that.
2026-03-26 15:50:32 -04:00
Atif Ali e35fa8b9ee fix(site): use restartWorkspace instead of startWorkspace in schedule dialog (#23658) 2026-03-27 00:45:16 +05:00
Jeremy Ruppel 1358233c83 fix(site): decrease chevron size in AI Bridge session row (#23688)
The Sessions table row chevron icon is too big. Make it smaller 🤏
2026-03-26 15:10:41 -04:00
Asher ea4070c0ce feat: add multi-user dialog select for adding group members (#23396)
Instead of the single-user dropdown we had before.
2026-03-26 10:42:04 -08:00
Hugo Dutka 1b2fab8306 feat(site): enable copy and paste in agents desktop (#23686) 2026-03-26 19:32:48 +01:00
Mathias Fredriksson 94e5de22f7 perf(site): fix compiler memoization gap in AgentDetailInput (#23683)
The React Compiler failed to memoize the messages derivation
chain because a useDashboard() hook call sat between the
messages computation and its consumer (getLatestContextUsage).
An IIFE around the context usage logic also fragmented the
dependency chain.

Replacing the IIFE with a ternary and reordering the non-hook
computation before the hook call lets the compiler group
messages + getLatestContextUsage into a single cache guard
keyed on messagesByID and orderedMessageIDs.
2026-03-26 18:30:06 +00:00
Mathias Fredriksson 6cbb7c6da7 fix(provisioner/terraform): regenerate fixtures with current provider (#23685)
Two test fixtures (devcontainer-resources, multiple-agents-multiple-envs)
were generated before terraform-provider-coder v2.15.0 added the
merge_strategy attribute to coder_env. Running generate.sh with the
current provider adds merge_strategy: "replace" (the default) to all
coder_env resources, causing unstable diffs on every regeneration.
2026-03-26 18:22:45 +00:00
Jeremy Ruppel fc60a6bf9b feat(site): add AIBridge Sessions to deployment menu (#23679)
Adds AI Bridge Sessions link to the deployment menu.
2026-03-26 13:56:51 -04:00
Atif Ali a52153968d fix(site): use Anthropic icon instead of Claude icon for provider (#23661) 2026-03-26 22:25:55 +05:00
Danielle Maywood d18e700699 fix(site): collapse chat toolbar badges fluidly on overflow (#23663) 2026-03-26 17:21:01 +00:00
Mathias Fredriksson 0234e8fffd perf(site): narrow buildStreamTools compiler cache guard dependencies (#23677)
The React Compiler guarded buildStreamTools on the whole
streamState ref, which changes on every text chunk. Refactoring
the function to accept toolCalls and toolResults directly lets
the compiler guard on those sub-fields, which are stable during
text-only streaming.

Before: $[0] !== streamState (misses every text chunk)
After:  $[0] !== toolCalls || $[1] !== toolResults (passes when
        only blocks change)

Verified: 181 functions compile, 0 diagnostics. Reference
stability tests confirm toolCalls/toolResults retain identity
across text-part updates and change when tool data updates.
2026-03-26 17:18:35 +00:00
david-fraley fea4560a64 fix(site): use docs() helper for hardcoded documentation URLs (#23606) 2026-03-26 17:10:47 +00:00
Danielle Maywood 6dee7cf11d perf(site/src/pages/AgentsPage): convert renderBlockList to BlockList component (#23673) 2026-03-26 17:07:36 +00:00
Danielle Maywood d4fc4e0837 fix(site): fix StreamingCodeFence storybook flake (#23681) 2026-03-26 17:00:47 +00:00
david-fraley 8da45c14bc fix(site): fix grammar in batch update description (#23605) 2026-03-26 11:59:46 -05:00
Cian Johnston bfee7e6245 fix: populate all chat fields in pubsub events (#23664)
*Problem:* `publishChatPubsubEvent` was constructing a partial
`codersdk.Chat` that omitted `LastModelConfigID` and other fields. Go's
zero-value UUID caused the sidebar to show "Default model" for chats
received via SSE.

*Solution:*
- Extracted `convertChat`/`convertChats` from `exp_chats.go` into
`db2sdk.Chat`/`db2sdk.Chats`, alongside existing `ChatMessage`,
`ChatQueuedMessage`, and `ChatDiffStatus` converters.
`publishChatPubsubEvent` now calls `db2sdk.Chat(chat, nil)` instead of
maintaining its own copy of the conversion logic
- Added backend integration test
`TestWatchChats/CreatedEventIncludesAllChatFields`
- Added frontend regression tests for nil-UUID and valid model config ID
cases

> 🤖 Created by Coder Agents, reviewed by this human.
2026-03-26 16:49:26 +00:00
Danielle Maywood 52b5d5fdc6 fix(site): match date range picker button height on template insights page (#23667) 2026-03-26 16:36:51 +00:00
Mathias Fredriksson cc4cca90fd perf(site): memo-wrap SmoothedResponse and ReasoningDisclosure to skip completed blocks during streaming (#23674)
During streaming, StreamingOutput's compiler cache guard misses
every chunk because streamState.blocks and streamTools are new
references. This causes renderBlockList to recreate all child JSX
elements, and React calls every child function even for blocks
that finished streaming.

Wrapping SmoothedResponse and ReasoningDisclosure in React.memo
lets React skip the function call entirely when props are stable.
For N completed response blocks and M completed thinking blocks,
this reduces per-chunk function calls from N+M+1 to 1. The
compiler still compiles both inner functions cleanly (6 and 12
cache slots respectively, zero diagnostics).
2026-03-26 16:35:12 +00:00
Hugo Dutka 081d91982a fix(site): fix desktop visibility glitch (#23678)
If the desktop viewer component was hidden, for example after collapsing
the sidebar, the next time it was shown the viewer would be blank. This
PR fixes that.
2026-03-26 17:20:16 +01:00
Danielle Maywood 00cd7b7346 fix: match workspace picker font size to plus menu dropdown (#23670) 2026-03-26 16:12:24 +00:00
Danny Kopping 801e57d430 feat: session detail API (#23203) 2026-03-26 18:09:53 +02:00
Michael Suchacz e937f89081 feat: add enabled toggle to chat model admin panel (#23665)
Adds an `enabled` toggle to the chat model admin create/edit form so
admins
can disable a model without soft-deleting it. Disabled models stay
visible
in admin settings but stop appearing in user-facing model selectors.

The backend already supported this (`chat_model_configs.enabled` column,
filtered queries, and SDK fields). This change wires it into the admin
UI
and adds coverage on both sides.

**Backend:** three new subtests in `coderd/exp_chats_test.go` verifying
the visibility contract (admin sees disabled models, non-admin doesn't,
update-to-disabled preserves the record).

**Frontend:** `enabled` field added to form logic and seeded from the
existing model (defaults to `true` for new models). A Switch+Tooltip
control renders in the form header, matching the MCP Server panel
pattern.
Two interaction stories cover the create-disabled and toggle-existing
flows.
2026-03-26 17:07:20 +01:00
Danielle Maywood 5c7057a67f fix(site): enable streaming-mode Streamdown for live chat output (#23676) 2026-03-26 15:22:54 +00:00
Ehab Younes 249ef7c567 feat(site): harden Agents embed frame communication and add theme sync (#23574)
Add theme synchronization, navigation blocking, scroll-to-bottom
handling, and chat-ready signaling to the agent embed page. The parent
frame can now set light/dark theme via postMessage or query param, and
ThemeProvider skips its own class manipulation when the embed marker is
present. Navigation attempts that leave the embed route are intercepted
and forwarded to the parent frame. The scroll container ref is lifted
to the layout so the parent can request scroll-to-bottom.
2026-03-26 18:03:30 +03:00
Cian Johnston 81fe7543b4 chore: set tls.VersionTLS12 MinVersion in cli/server.go to address gosec warning (#23646)
I was investigating `//nolint` comments and this one popped up.
It raised my eyebrows enough to warrant its own PR.
2026-03-26 14:53:47 +00:00
Kyle Carberry 61d2a4a9b8 fix(site): preserve streaming output when queued message is sent (#23595)
## Problem

When the user sends a message while the agent is actively streaming a
response, `handleSend` called `store.clearStreamState()`
**unconditionally before** the POST request. If the server queues the
message (`response.queued = true` because the agent is busy), the
in-progress stream output is immediately wiped from the UI. The full
text only reappears once the agent finishes and the durable message
arrives via WebSocket — causing a visible cutoff mid-stream.

## Fix

Move `clearStreamState()` from before the POST to **after** the
response, gated behind `!response.queued`:

- **Queued sends** (`response.queued === true`): `clearStreamState()` is
never called. The stream continues uninterrupted. The WebSocket `status`
handler already clears stream state when the chat transitions to
`"pending"` / `"waiting"` after the queued message is dequeued.
- **Non-queued sends** (`response.queued === false`):
`clearStreamState()` + `upsertDurableMessage()` fire immediately after
the POST, same net behavior as before.
- **Edit and promote paths**: Unchanged — those are intentional
interruptions where eager clearing is correct.

### Additional behavior changes (both improvements)

1. **Failed sends no longer wipe stream state.** Previously
`clearStreamState()` ran before the `try` block, so a network error
still wiped the agent's in-progress output. Now the `catch` re-throws
before reaching `clearStreamState()`, preserving the stream on failure.

2. **`clearStreamState()` fires for all non-queued responses**, not just
those with a `message` body. The original guard was `!response.queued &&
response.message`; now `clearStreamState()` is under `!response.queued`
while `upsertDurableMessage` retains the `response.message` check. The
server always sets `message` for non-queued responses, so this is a
no-op in practice but is semantically correct.

## Testing

**AgentDetail.stories.tsx**: New `StreamingSurvivesQueuedSend` story
exercises the full flow — mocks `createChatMessage` to return `{ queued:
true }`, delivers streaming text via WebSocket, sends a message through
the UI, and asserts the streaming text remains visible.
2026-03-26 10:35:31 -04:00
Mathias Fredriksson b23c07cf23 perf(site): use lazy iteration in sliceAtGraphemeBoundary (#23671)
Array.from(graphemeSegmenter.segment(text)) materializes the
entire text into an array before iterating, even though the loop
breaks early at the visible prefix length. During streaming at
60fps, this makes each frame O(full text) instead of O(prefix).

Benchmark on 5000-char text with 200-char prefix: 22.6x faster
(1.44ms to 0.06ms per call, saving 8.3% of the frame budget).
The fallback codepoint path had the same issue with Array.from.
2026-03-26 16:33:48 +02:00
Ethan 87aafd4ae2 fix(site): stabilize date-dependent storybook snapshots (#23657)
_Generated by mux but reviewed by a human_

Several stories computed dates relative to `dayjs()` / `new Date()` at
render time, causing snapshot text to shift daily. I ran into this on my
PRs.

This adds an optional `now` prop to `DateRangePicker`,
`TemplateInsightsControls`, and `CreateTokenForm` so stories can inject
a deterministic clock without global mocking. License stories replace
the misleadingly-named `FIXED_NOW = dayjs().startOf("day")` with
absolute timestamps. All fixed timestamps use noon UTC to avoid timezone
boundary issues.

Affected stories:
- `AgentSettingsPageView`: Usage Date Filter, Usage Date Filter Refetch
Overlay
- `LicenseCard`: Expired/future AI Governance variants, Not Yet Valid
- `LicensesSettingsPage`: Shows Addon Ui For Future License Before Nbf
- `TemplateInsightsControls`: Day
- `CreateTokenPage`: Default
2026-03-27 01:21:52 +11:00
Ethan 4d74603045 fix(coderd/x/chatd): respect provider Retry-After headers in chat retry loop (#23351)
> **PR Stack**
> 1. **#23351** ← `#23282` *(you are here)*
> 2. #23282 ← `#23275`
> 3. #23275 ← `#23349`
> 4. #23349 ← `main`

---

## Summary

`chatretry.Retry()` used pure exponential backoff (1 s, 2 s, 4 s, …) and
never consulted provider `Retry-After` headers. Fantasy's
`ProviderError` carries `ResponseHeaders` including `Retry-After`, but
`chaterror.Classify()` only parsed error text and silently dropped the
structured transport metadata.

This makes `Retry-After` a first-class signal in the classification →
retry pipeline.

<img width="853" height="346" alt="image"
src="https://github.com/user-attachments/assets/65f012b6-8173-43d2-957e-ab9faddea525"
/>


## Changes

### `coderd/chatd/chaterror/classify.go`

- Added `RetryAfter time.Duration` field to `ClassifiedError` — a
normalized minimum retry delay derived from provider response metadata.
- `Classify()` now calls `extractProviderErrorDetails()` before falling
back to text heuristics. Structured `ProviderError.StatusCode` takes
priority over regex extraction.
- `normalizeClassification()` preserves and clamps `RetryAfter`.

### `coderd/chatd/chaterror/provider_error.go` (new)

Provider-specific extraction, isolated from the text-based
classification logic:

- `extractProviderErrorDetails()` unwraps `*fantasy.ProviderError` from
the error chain via `errors.As`.
- `retryAfterFromHeaders()` parses headers in priority order:
  1. `retry-after-ms` (OpenAI-specific, millisecond precision)
  2. `retry-after` (standard HTTP — integer seconds or HTTP-date)
- Case-insensitive header key lookup.

### `coderd/chatd/chatretry/chatretry.go`

- `effectiveDelay(attempt, classified)` computes `max(Delay(attempt),
classified.RetryAfter)` — the provider hint acts as a floor without
weakening the local exponential backoff.
- `Retry()` now uses `effectiveDelay` and passes the effective delay to
both `onRetry(...)` and the sleep timer, so downstream payloads, logs,
and the frontend countdown stay aligned automatically.

### Tests

- `classify_test.go`: Structured provider status + `Retry-After`
extraction, `retry-after-ms` priority, HTTP-date parsing, invalid header
fallback, `WithProvider` preservation.
- `chatretry_test.go`: Retry-after-as-floor semantics — longer hint
wins, shorter hint keeps base delay.

## Design notes

- **No SDK/API/frontend changes needed.** `codersdk.ChatStreamRetry`
already carries `DelayMs` and `RetryingAt`, and the frontend already
consumes them. The fix is purely in the server-side delay computation.
- **Existing retryability rules unchanged.** This fixes *when* we sleep,
not *whether* an error is retryable.
- **Provider hint is a floor:** `max(baseDelay, RetryAfter)` ensures we
never retry earlier than the provider asks, and never weaken our own
backoff curve.
2026-03-27 01:20:46 +11:00
Cian Johnston 847a88c6ca chore: clean up stale and dangerous //nolint comments (#23643)
## Changes

- **Commit 1**: Remove 17 unnecessary `//nolint` directives:
  - `//nolint:varnamelen` — linter not active
  - `//nolint:unused` on exported `SlimUnsupported`
  - `//nolint:govet` in `coderd/httpmw/csrf` — no longer fires
  - `//nolint:revive` on functions refactored since the nolint was added
- `//nolint:paralleltest` citing Go 1.22 loop variable capture
(obsolete)
- Bare `//nolint` narrowed to specific `//nolint:gocritic` with
justification

- **Commit 2**: Fix root causes behind 5 dangerous nolint suppressions:
- Add `MinVersion: tls.VersionTLS12` to TLS client config (removes
`gosec` G402)
- Delete trivial unexported wrappers `apiKey()`/`normalizeProvider()` in
chatprovider (removes `revive` confusing-naming)
- Add doc comments to `StartWithAssert` and `Router` (removes `revive`
exported)
  - Rename unused parameters to `_` in integration test helpers

> 🤖 This PR was created using Coder Agents and reviewed by me.
2026-03-26 14:13:53 +00:00
Jeremy Ruppel a0283ff775 fix(site): use toLocaleString for pagination offsets (#23669)
The Pagination widget localizes the number format of the total results
but not the page offsets.

Before

<img width="620" height="78" alt="Screenshot 2026-03-26 at 09 18 01"
src="https://github.com/user-attachments/assets/7ac0ad9a-7baa-4b30-b3d0-0e0325f8433b"
/>

After

<img width="297" height="42" alt="Screenshot 2026-03-26 at 9 41 22 AM"
src="https://github.com/user-attachments/assets/79c68366-95fa-4012-8419-5cd6f6e10ae3"
/>
2026-03-26 09:50:49 -04:00
Cian Johnston f164463c6a fix(scripts/metricsdocgen): shush the prometheus scanner in CI (#23642)
- Suppress informational `log.Printf` messages from the metrics scanner
when stdout is not a TTY (i.e. piped via `atomic_write` in `make gen` or
CI)
- Genuine warnings (`warnf`) still print unconditionally so real
problems remain visible
- `log.Fatalf` for fatal errors is unchanged

> 🤖 Created by Coder Agents and reviewed by a human
2026-03-26 12:58:02 +00:00
Michael Suchacz 4f063cdc47 feat: separate default and additional Coder Agents system prompts (#23616)
Admins can now control whether the built-in Coder Agents default system
prompt is prepended to their custom instructions, rather than having the
custom prompt silently replace the default.

**Changes:**
- New `include_default_system_prompt` boolean toggle (defaults to `true`
for existing deployments) stored as a site config key — no migration
needed.
- GET `/api/experimental/chats/config/system-prompt` returns the toggle
state, the custom prompt, and a preview of the built-in default.
- PUT persists both the toggle and custom prompt atomically in a single
transaction.
- `resolvedChatSystemPrompt()` composes `[default?, custom?]` joined by
`\n\n`, falling back to the built-in default on DB errors.
- Settings UI adds a Switch toggle with conditional helper text and a
"Preview" button that shows the built-in default prompt via the existing
`TextPreviewDialog`.
- Comprehensive test coverage: 15 subtests covering toggle behavior,
prompt composition matrix, auth boundaries, and integration with chat
creation.
2026-03-26 13:32:41 +01:00
Cian Johnston d175e799da feat: show agent badge on workspace list (#23453)
- Adds `GET /api/experimental/chats/by-workspace` endpoint that returns
workspace_id → latest chat_id mapping
- Modifies FE to fetch this alongside the workspace list, gated on
`agents` experiment and render an "Agent" badge similar to the existing
"Task" badge in `WorkspacesTable`
- Badge links to the "latest chat" linked to the given workspace.

Notes:
- Intentionally uses `fetchWithPostFilter` for RBAC to decouple from
workspaces API — will migrate to `workspaces_expanded` view later.
- If users have multiple chats linked to the same workspace, the badge
will link to the most recently updated one.

> 🤖 This PR was created with the help of Coder Agents, and has been
reviewed by my human. 🧑‍💻
2026-03-26 11:30:12 +00:00
Jaayden Halko 3fb7c6264f feat: display the AI add-on column in the UI on the Users and Organization Members tables (#23291)
## Summary

Adds an entitlement-gated **AI add-on** column to both the **Users**
table and the **Organization Members** table. When
`ai_governance_user_limit` is entitled, each row shows whether the user
is consuming an AI seat.

## Background

The AI governance add-on tracks which users are consuming AI seats.
Admins need visibility into per-user seat consumption directly from the
user management tables. This change surfaces that information through
both the site-wide Users table and the per-organization Members table,
gated behind the `ai_governance_user_limit` entitlement so the column
only appears when the feature is licensed.

## Implementation

### Backend
- **New SQL query** `GetUserAISeatStates`
(`coderd/database/queries/aiseatstate.sql`) — returns user IDs consuming
an AI seat, derived from:
  - Users with entries in `aibridge_interceptions` (AI Bridge usage)
- Users who own workspaces with `has_ai_task = true` builds (AI Tasks
usage)
- **SDK types** — added `has_ai_seat: boolean` to `codersdk.User` and
`codersdk.OrganizationMemberWithUserData`
- **Handler wiring** — both the Users list endpoint (`coderd/users.go`)
and all Members endpoints (`coderd/members.go`) query AI seat state per
page of user IDs and populate the response field
- **dbauthz** — per-user `ActionRead` checks on `ResourceUserObject`

### Frontend
- **Shared `AISeatCell` component**
(`site/src/modules/users/AISeatCell.tsx`) — green `CircleCheck` for
consuming, gray `X` for non-consuming
- **`TableColumnHelpTooltip`** — extended with `ai_addon` variant with
tooltip: *"Users with access to AI features like AI Bridge, Boundary, or
Tasks who are actively consuming a seat."*
- **Column visibility** gated behind
`useFeatureVisibility().ai_governance_user_limit`

## Validation

- Backend: dbauthz full method suite (`TestMethodTestSuite`) passes
including new `GetUserAISeatStates` test
- Backend: `TestGetUsers`, `TestUsersFilter`, CLI golden file tests pass
- Frontend: 7/7 tests pass across `UsersPage.test.tsx` and
`OrganizationMembersPage.test.tsx` (column visibility gating both
directions)
- `go build ./coderd/...` compiles clean
- `pnpm --dir site run lint:types` passes
- `make gen` clean

## Risks

- **Pagination performance**: The AI seat query is scoped to the current
page's user IDs (not a full table scan), keeping it efficient for
paginated views.
- **Semantic scope**: The workspace-side AI seat derivation uses "any
build with `has_ai_task = true`" rather than "latest build only". If the
product intent is latest-build-only, this can be tightened in a
follow-up.

---

_Generated with `mux` • Model: `anthropic:claude-opus-4-6` • Thinking:
`xhigh` • Cost: `$27.25`_

<!-- mux-attribution: model=anthropic:claude-opus-4-6 thinking=xhigh
costs=27.25 -->
2026-03-26 10:36:40 +00:00
Danny Kopping 09d2588e2a docs: AI session auditing (#23660)
_Disclaimer: produced with the help of Claude Opus 4.6, heavily modified
by me._

Closes https://github.com/coder/internal/issues/1341

---------

Signed-off-by: Danny Kopping <danny@coder.com>
2026-03-26 09:49:53 +00:00
Danny Kopping 8eade29e68 chore: update AI Bridge warning to require AI Governance Add-On (#23662)
*Disclaimer: implemented by a Coder Agent using Claude Opus 4.6,
reviewed by me.*

Replace the transitional soft warning message:

> AI Bridge is now Generally Available in v2.30. In a future Coder
version, your deployment will require the AI Governance Add-On to
continue using this feature. Please reach out to your account team or
sales@coder.com to learn more.

with the definitive requirement message:

> The AI Governance Add-On is required to use AI Bridge. Please reach
out to your account team or sales@coder.com to learn more.

Updated in:
- `enterprise/coderd/license/license.go`
- `enterprise/coderd/license/license_test.go` (2 occurrences)
2026-03-26 11:10:53 +02:00
Ethan 15f2fa55c6 perf(coderd/x/chatd): add process-wide config cache for hot DB queries (#23272)
## Summary

Adds a process-wide cache for three hot database queries in `chatd` that
were hitting Postgres on **every chat turn** despite returning
rarely-changing configuration data:

| Query | Before (50k turns) | After | Reduction |
|---|---|---|---|
| `GetEnabledChatProviders` | ~98.6k calls | ~500-1000 | ~99% |
| `GetChatModelConfigByID` | ~49.2k calls | ~500-1000 | ~98% |
| `GetUserChatCustomPrompt` | ~46.7k calls | ~1000-2000 | ~97% |

These were identified via `coder exp scaletest chat` (5000 concurrent
chats × 10 turns) as the dominant source of Postgres load during chat
processing.

## Design

Follows the established **webpush subscription cache pattern**
(`coderd/webpush/webpush.go`):
- `sync.RWMutex` + `tailscale.com/util/singleflight` (generic) +
generation-based stale prevention + TTL
- 10s TTL for provider/model config, 5s TTL for user prompts
- Negative caching for `sql.ErrNoRows` on user prompts (the common case
— most users don't set custom prompts)
- Deep-clones `ChatModelConfig.Options` (`json.RawMessage` = `[]byte`)
on both store and read paths

### Invalidation

Single pubsub channel (`chat:config_change`) with kind discriminator for
cross-replica cache invalidation. Seven publish points in
`coderd/chats.go` cover all admin mutation endpoints
(create/update/delete for providers and model configs, put for user
prompts).

_This PR was generated with mux and was reviewed by a human_
2026-03-26 18:04:53 +11:00
Danny Kopping 2ff329b68a feat(site): add banner on request-logs page directing users to sessions (#23629)
*Disclaimer: implemented by a Coder Agent using Claude Opus 4.6*

Adds an info banner on the `/aibridge/request-logs` page encouraging
users to visit `/aibridge/sessions` for an improved audit experience.

This allows us to validate whether customers still find the raw request
logs view useful before removing it in a future release.

Fixes #23563
2026-03-26 11:57:50 +05:00