## Summary
Add comprehensive Storybook stories covering the Debug panel in various states: loading, empty, single/multiple runs, expanded steps, tool calls, error states, and streaming indicators.
This is PR 9/9 in the chat debug logging stack.
### Changes
- **DebugPanel stories** (`site/src/pages/AgentsPage/components/RightPanel/DebugPanel/DebugPanel.stories.tsx`): Storybook stories with `play` functions for interaction testing, covering:
- Loading state
- Empty / disabled state
- Single-step successful run
- Multi-step run with tool calls
- Error state with redacted headers
- Compaction and title generation run kind badges
- Long raw request/response payload rendering
- Streaming / in-progress indicators
### Stack overview
1. Database schema & SDK types
2. Types, context, and model normalization
3. Recorder, transport, and redaction
4. Service and summary aggregation
5. Chat lifecycle wiring
6. HTTP handlers and API docs
7. Frontend API layer and panel utilities
8. Debug panel components and settings
9. **→ Storybook stories** (this PR)
---
_Generated with [`mux`](https://github.com/coder/mux) • Model: `anthropic:claude-opus-4-6` • Thinking: `xhigh`_
## Summary
Add the Debug panel UI components: run list, run cards, step cards with transcript and tool-call rendering, attempt accordions, and shared primitives. Wire the panel into the AgentChatPage sidebar and add the per-chat debug logging toggle in the behavior settings page.
This is PR 8/9 in the chat debug logging stack.
### Screenshots
Settings Page
<img width="4608" height="2348" alt="CleanShot 2026-04-21 at 21 19 36@2x" src="https://github.com/user-attachments/assets/69391465-4c56-468a-9923-59576d326963" />
Conditional Debug tab
<img width="4608" height="2348" alt="CleanShot 2026-04-21 at 21 19 58@2x" src="https://github.com/user-attachments/assets/bc1e07cb-21d9-40e7-8928-6fd9a7ec7f57" />
Last request's tools and schema
<img width="4608" height="2348" alt="CleanShot 2026-04-21 at 21 20 03@2x" src="https://github.com/user-attachments/assets/401f26af-98ce-443f-a586-424d3636d98b" />
"Raw" JSON request bodies
<img width="4608" height="2348" alt="CleanShot 2026-04-21 at 21 20 35@2x" src="https://github.com/user-attachments/assets/3605a373-9e29-4183-89e7-8b2704ff9333" />
### Changes
- **DebugPanel** (`site/src/pages/AgentsPage/components/RightPanel/DebugPanel/DebugPanel.tsx`): top-level panel component owning data fetching and subscription lifecycle.
- **DebugRunCard**: compact single-row header with capitalized provider name, status badge, compact duration (`1.3s`), and token summary (`3→5 tok`). Expandable to show child step cards.
- **DebugStepCard**: step inspector with normalized transcript rendering — system prompts, assistant text with 160-char clamping + independent "see more/less" toggle, tool calls with fully-expanded JSON payloads in `CopyableCodeBlock`.
- **DebugAttemptAccordion**: nested accordion for HTTP-level attempt details showing request/response headers and bodies.
- **Shared primitives**: `CopyableCodeBlock`, `MessageRow`, `ToolPayloadDisclosure`, `StatusBadge`.
- **Sidebar wiring** (`AgentChatPageView.tsx`): adds the Debug tab to the right panel when debug logging is enabled.
- **Behavior settings**: deployment-wide and per-user debug logging toggles on the settings page.
### Stack overview
1. Database schema & SDK types
2. Types, context, and model normalization
3. Recorder, transport, and redaction
4. Service and summary aggregation
5. Chat lifecycle wiring
6. HTTP handlers and API docs
7. Frontend API layer and panel utilities
8. **→ Debug panel components and settings** (this PR)
9. Storybook stories
---
_Generated with [`mux`](https://github.com/coder/mux) • Model: `anthropic:claude-opus-4-6` • Thinking: `xhigh`_
## Summary
Add API client methods, React Query builders, and unit tests for the chat debug endpoints. Add `debugPanelUtils` with coercion helpers that transform raw debug step data into structured display models for the Debug panel, and wire debug run streaming into the chat store.
This is PR 7/9 in the chat debug logging stack.
### Changes
- **API client** (`site/src/api/api.ts`): typed methods for all debug endpoints — list runs, list steps, get/set deployment logging, get/set user logging, set per-chat override.
- **React Query builders** (`site/src/api/queries/chats.ts`): `chatDebugRuns`, `chatDebugSteps`, `chatDebugLoggingConfig`, `userDebugLoggingConfig` query/mutation factories with `refetchInterval: 5000` for live polling.
- **Debug panel utilities** (`site/src/pages/AgentsPage/components/RightPanel/DebugPanel/debugPanelUtils.ts`): `coerceStepRequest` / `coerceStepResponse` that recursively parse nested JSONB into `StepRequestViewModel` / `StepResponseViewModel` with `MessagePart`, `ToolDef`, and `ToolCallPart` types. Includes `formatTokenSummary` (compact `3→5 tok` notation) and `compactDuration` helpers.
- **Unit tests**: coverage for coercion edge cases and formatting utilities.
### Stack overview
1. Database schema & SDK types
2. Types, context, and model normalization
3. Recorder, transport, and redaction
4. Service and summary aggregation
5. Chat lifecycle wiring
6. HTTP handlers and API docs
7. **→ Frontend API layer and panel utilities** (this PR)
8. Debug panel components and settings
9. Storybook stories
---
_Generated with [`mux`](https://github.com/coder/mux) • Model: `anthropic:claude-opus-4-6` • Thinking: `xhigh`_
> This PR was authored by Mux on behalf of Mike.
Adds AWS Bedrock ambient credential support to the Agents provider path.
Bedrock providers can now be saved without a stored API key and
authenticated via the standard AWS SDK credential chain on the Coder
server (IAM roles, `AWS_ACCESS_KEY_ID`, etc.). Also fixes missing `Base
URL` forwarding for Bedrock.
## Changes
**Backend runtime** (`coderd/x/chatd/chatprovider/chatprovider.go`):
- New `ProviderAllowsAmbientCredentials(provider)` helper. Currently
returns true only for Bedrock.
- `ModelFromConfig` no longer errors on an empty API key when the
provider is in the ambient-allowed set AND was explicitly resolved via
`ByProvider`. This preserves the policy gate: unresolvable providers
(disabled central key, user-key-required without a user key) still
error.
- `setResolvedProviderAPIKey` internalizes the ambient-credentials
contract via `ProviderAllowsAmbientCredentials`, so a
resolved-but-keyless Bedrock provider is represented as an empty
`ByProvider` entry rather than a post-hoc sentinel patch in the caller.
- `WithAPIKey` is only appended when a token is present.
- `WithBaseURL(baseURL)` is now forwarded for Bedrock (was previously
missing).
**Backend admin API** (`coderd/exp_chats.go`):
- `validateChatProviderCentralAPIKey` exempts Bedrock from requiring a
stored API key when central credentials are enabled.
- AI Gateway separation (`ChatProviderAPIKeysFromDeploymentValues`) is
unchanged. No silent reuse of `CODER_AIBRIDGE_BEDROCK_*` flags.
**Frontend**
(`site/src/pages/AgentsPage/components/ChatModelAdminPanel/*`):
- API Key field is optional for Bedrock when central credentials are
enabled.
- Bedrock-specific descriptions on API Key and Base URL fields
(bearer-token vs ambient modes, `AWS_REGION` guidance).
- Right-aligned "Clear stored token" action switches an existing Bedrock
provider back to ambient mode.
- `hasEffectiveAPIKey` treats Bedrock with central credentials enabled
as configured, so the provider list shows the correct status icon.
- Three new stories: `ProviderFormBedrockAmbientCredentials`,
`ProviderFormBedrockBearerToken`, `ProviderFormBedrockClearBearerToken`.
**Docs** (`docs/ai-coder/agents/models.md`,
`docs/ai-coder/ai-gateway/setup.md`):
- New "Configuring AWS Bedrock" section covering both credential modes,
region resolution, and the Base URL override.
- Explicit note that the `us-east-1` region fallback only applies to
bearer-token mode; ambient credentials require a region from the
standard AWS SDK chain.
- Cross-reference in AI Gateway docs clarifying that
`CODER_AIBRIDGE_BEDROCK_*` flags are a separate configuration path from
Agents.
## Not in scope
- Reusing AI Gateway Bedrock flags as an implicit Agents fallback.
- Per-provider AWS access key, secret, or region fields (would need a
migration and audit-table review).
- IMDS or network-backed credential probes in admin/listing request
paths.
## Related
Dogfood deployment integration:
https://github.com/coder/dogfood/pull/324
Renders the durable file attachments introduced in #24280 in the chat
interface. Without this, attachments were stored and served correctly
but the UI showed raw file parts with no previews or download UX.
Every attachment gets a download affordance, split into three rendering
tiers:
- **Images** — thumbnail with a hover/focus overlay containing a
download link. `onFocusCapture`/`onBlurCapture` with
`contains(relatedTarget)` keeps the overlay open while tabbing between
the image and its download link.
- **Text-like files** (`text/*`, `application/json`) — expandable
preview button with loading + error-with-retry states and the same
download overlay. Preview fetches throw a typed
`FetchTextAttachmentError` with a `.status` field instead of a
stringly-typed error.
- **Everything else** — compact `FileCard` with extension badge,
filename, and download link.
User-side and assistant-side rendering now share `AttachmentBlocks.tsx`
(`AttachmentPreviewFrame`, `TextAttachmentButton`,
`ImageAttachmentButton`, `FileCard`, plus
`getAttachmentHref`/`getAttachmentName`) instead of two near-duplicate
implementations. The text-attachment overlay anchors to the preview
surface so the download button stays pinned even when a loading/error
status line widens the row below.
`ComputerRenderer` detects when a screenshot was stored as a durable
attachment (`attachment_file_id`) and suppresses the stale base64
rendering — the screenshot appears as a proper file part instead.
`ToolLabel` shows the attached filename for `attach_file` tool calls.
Storybook coverage in `ConversationTimeline.stories.tsx` was expanded to
cover every tier (single/multiple images, inline + file-id text, JSON,
download-only files, fetch-failure retry, mixed attachments + file
references) with play-function assertions.
<img width="811" height="150" alt="image"
src="https://github.com/user-attachments/assets/27c71081-3502-4e80-92a7-d8adf1ff9323"
/>
## Cleanup
Per Mathias' post-merge suggestion on #24280, this PR also relocates
`coderd/chatfiles` → `coderd/x/chatfiles` so the durable-attachment
helpers live beside the rest of the `chatd` experimental surface.
Closes CODAGT-91
Renames the `--border-hover` design token to `--border-secondary` and
updates the color values to work well on `surface-secondary`
backgrounds, preparing for the template creation flow.
- Light theme: `240 5% 65%` (`#A1A1AA`)
- Dark theme: `240 5% 26%` (`#3F3F46`)
All 7 component usages updated to use the new token name.
<details>
<summary>Context</summary>
The previous `border-hover` token used a single value (`#52525B`) for
both themes, which didn't provide enough contrast on `surface-secondary`
backgrounds. The rename to `border-secondary` better reflects its
semantic role as a secondary border color rather than a hover-specific
one, and the updated values give proper contrast in both light and dark
themes. This change is a prerequisite for the upcoming template creation
flow work.
</details>
> 🤖 Generated by Coder Agents
closes CODAGT-125
Assistant messages that show only the fallback text ("Message has no
renderable content.") were missing bottom spacing before the next user
bubble, because `needsAssistantBottomSpacer` only covered reasoning-only
and sources-only cases.
Extend the spacer predicate to also trigger when
`!hasRenderableContent`, and add a `data-testid` to the spacer element
for testability. A new Storybook story
(`NoRenderableContentFallbackSpacing`) covers this regression.
Closes CODAGT-216
## Problem
`dbpurge` deletes `chat_files` rows after the deployment's configured
retention window, but `chat_messages.content` can still contain
`file_id` references to those files. On replay, that left the Anthropic
provider with an empty file payload and a `400 image cannot be empty`
error. In the UI, the same missing file showed up as a broken image.
## Fix
- Backend: when replay hits a `file_id` whose bytes are gone, replace it
with a short text placeholder instead of emitting an empty file part. We
could also drop the missing attachment entirely, but that would silently
remove context from the replay and make the conversation harder for the
model to interpret. The placeholder keeps the request valid while still
telling the model that a file used to be there and is no longer
available.
- Frontend: classify chat image failures instead of treating every
broken image the same.
- `404` file fetches render `Image expired`, with a tooltip explaining
that chat attachments are deleted after the retention window set for the
deployment.
- Other remote failures render `Image failed to load`, with a tooltip
that surfaces server/network detail when available.
- Invalid inline image data still renders `Image failed to load` without
a probe.
When a workspace agent's startup script fails, restarting the workspace
will not resolve the issue since the script will keep failing.
Previously all unhealthy workspaces showed the same generic notification
with a Restart button regardless of cause.
Now, when every failing agent has `lifecycle_state=start_error`, the
workspace-level notification shows "A startup script has failed" and
guides the user to contact their template admin instead of offering a
Restart action.
> Code written by Claude 🤖 reviewed by yours truly
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Anthropic HTTP 400 responses (e.g. "image exceeds 5 MB maximum") were
collapsed in the chat UI to the generic headline "Anthropic returned an
unexpected error (HTTP 400)." with no actionable detail — the upstream
message survived to the processor log but was dropped before reaching
the client.
Add a new optional `Detail` field on `codersdk.ChatStreamError` that
carries the upstream provider message alongside the existing normalized
headline. The backend extracts `error.message` from
`fantasy.ProviderError.ResponseBody` (the JSON envelope shared by
Anthropic and OpenAI), falls back to the trimmed provider message when
the body is absent or unparseable, and caps the result at 500 runes. The
frontend threads `Detail` through `useChatStore`, `liveStatusModel`, and
`ChatStatusCallout`, rendering it as a muted secondary line inside the
existing `AlertDescription`.
Before:
<img width="1552" height="185" alt="image"
src="https://github.com/user-attachments/assets/524b588e-3cee-4fad-bc15-6bf3aec0899d"
/>
After:
<img width="814" height="173" alt="image"
src="https://github.com/user-attachments/assets/eae82a89-3ac1-4a33-8d18-ef9f77263d89"
/>
## Persistence
`Detail` is **not** persisted — it disappears on refresh. Persisting it
would require a DB change (today `chats.last_error` is a single nullable
`TEXT` column), and the shape of persisted chat errors is worth a more
deliberate rethink — e.g. promoting `last_error` to `JSONB` so we can
also retain structured fields like `kind`, `statusCode`, `provider`, and
`retryable` instead of only the normalized headline string. That's a
bigger design discussion than this PR should carry.
In the meantime, seeing the upstream error reason *immediately on
failure* is already a large UX improvement over the status quo, and this
PR gets us there without prejudicing the eventual persistence design.
Tracking persistence in CODAGT-239.
Closes CODAGT-235
> 🤖 This PR was written by Coder Agent on behalf of Jake Howell
Fixes https://github.com/coder/internal/issues/1419
The `createTemplate` e2e helper clicks Save immediately after
`setInputFiles`, but the file upload API call is fire-and-forget through
the React component chain (`FileUpload.onChange` →
`CreateTemplateForm.onUpload` → `UploadTemplateView.onUpload` — none of
which `await` the returned promise up the chain).
If the upload mutation has not settled when the form submits,
`uploadedFile` (`uploadFileMutation.data`) is still `undefined`, and the
`uploadedFile!.hash` non-null assertion throws a `TypeError`. Formik
silently swallows the rejected promise, so the page stays on
`/templates/new` and the URL assertion times out.
The fix waits for the "Remove file" button to appear after
`setInputFiles`. That button only renders when `!isPending && variables`
— i.e. the upload mutation has succeeded and the hash is available.
Unify the three subagent spawn tools (`spawn_agent`,
`spawn_explore_agent`, `spawn_computer_use_agent`) behind a single
`spawn_subagent` tool keyed by a `subagent_type` discriminant
(`general`, `explore`, `computer_use`). Mirrors the single-entry-point
pattern already used by `task` in mux while keeping `wait_agent`,
`message_agent`, and `close_agent` as separate lifecycle tools.
A new backend subagent definition catalog
(`coderd/x/chatd/subagent_catalog.go`) is the source of truth for tool
description, prompt guidance, availability rules (plan mode,
desktop/Anthropic gating), and child-chat option building.
`spawn_subagent` advertises only the types available in the current
context and validates `subagent_type` server-side; context inheritance
still flows through the existing `createChildSubagentChatWithOptions`
path. `wait_agent`, `message_agent`, and `close_agent` responses now
include a server-derived `subagent_type` so the UI stops inferring
lifecycle state from tool names.
The frontend gets a shared normalization helper
(`site/src/pages/AgentsPage/components/ChatElements/tools/subagentDescriptor.ts`)
that maps either legacy tool names or new `spawn_subagent` args into a
common descriptor (action, variant, icon, fallback copy). Legacy
transcripts still render identically; `Tool.tsx`, `SubagentTool.tsx`,
`ToolLabel.tsx`, `ToolIcon.tsx`, and `messageParsing.ts` now key off the
descriptor instead of hard-coded names. Existing UI copy is preserved
(`Spawning Explore agent...`, `Using the computer...`, computer-use
monitor icon and Open Desktop affordance).
> This PR was opened by Mux working on Mike's behalf.
Add a lint check that prevents introduction of Unicode emdash (U+2014)
and endash (U+2013) characters. These are almost exclusively introduced
by AI agents and conflict with the project writing style.
The lint script (scripts/check_emdash.sh) checks only added lines in
the current diff by default, so existing violations do not block CI.
Pass --all to scan the entire repo for auditing.
Agent instructions in AGENTS.md, site/AGENTS.md, and the docs style
guide now explicitly ban emdash, endash, and " -- " as punctuation,
with guidance to use commas, semicolons, or periods instead.
## Summary
Allow root plan-mode chats to use MCP tools from external servers that
an admin has explicitly approved for plan mode. Workspace MCP and
plan-mode subagents remain blocked.
## Problem
`chatd.go` excluded every MCP tool when `isPlanModeTurn` was true, so
planning had no access to tools like docs search, ticketing, etc.
Lifting that guard wholesale was unsafe: `mcp_server_configs` already
has centralized admin governance, but workspace-local MCP (discovered
from agent `.mcp.json`) does not, and subagents use a narrower trust
boundary.
## Fix
Add an admin-controlled per-server `allow_in_plan_mode` flag (default
`false`) and gate plan-mode MCP access on it.
### Backend / schema
- New migration `000472_mcp_server_allow_in_plan_mode.{up,down}.sql` and
matching fixture update.
- `mcpserverconfigs.sql` + generated code: persist and read the new
column.
- `codersdk/mcp.go`: thread the field through `MCPServerConfig`,
`Create*`, and `Update*` request types.
- `coderd/mcp.go`: validate, persist, and return the flag in
get/list/create/update handlers.
### chatd
- `coderd/x/chatd/chatd.go`: pre-filter selected external MCP configs by
`AllowInPlanMode` before calling `mcpclient.ConnectAll` on plan-mode
root turns. Workspace MCP discovery is skipped entirely on plan-mode
turns.
- Single helper decides whether a tool is available in plan mode, used
both at construction and for active-tool filtering (defense in depth).
Plan-mode subagents, dynamic tools, provider-native tools, computer-use,
and workspace MCP stay unchanged.
- `coderd/x/chatd/prompt.go`: update the root plan-mode overlay text to
match the new boundary.
### UI
- `MCPServerAdminPanel.tsx`: add an explicit toggle ("Allow all tools
from this MCP server in root plan mode") next to the existing governance
controls.
- Regenerated `site/src/api/typesGenerated.ts`.
### Docs
- `docs/ai-coder/agents/architecture.md`: replace the blanket "MCP is
unavailable in plan mode" note with the new root-only, external-only,
admin-approved policy. Explicitly call out that workspace MCP and
plan-mode subagents are still excluded.
### Tests
- Plan-mode visibility (approved vs non-approved external server).
- Plan-mode invocation of an approved external MCP tool.
- End-to-end plan-mode workflow that uses an approved MCP tool and then
reaches `propose_plan`.
- Regressions: workspace MCP still excluded in plan mode; plan-mode
subagents still on the restricted tool boundary; existing tool
allow/deny list filtering still applies.
## Policy precedence
`allow_in_plan_mode` is an **additional** requirement on top of existing
`enabled`, availability, chat-selected / forced server IDs, and tool
allow/deny lists. It approves **all tools on that server** for root plan
mode; a per-tool plan allowlist is deliberately deferred.
## Follow-ups (explicitly out of scope)
- Whether plan-mode subagents should inherit approved external MCP
tools.
- Workspace-local MCP safety model (agent-side `.mcp.json` schema vs. a
coderd-managed workspace MCP config).
## Validation
- `go vet ./coderd/x/chatd/...`
- `go test ./coderd/x/chatd -run 'TestPlan.*|TestMCP.*' -count=1`
- `go test ./coderd/x/chatd -count=1 -timeout 5m` (full chatd suite)
- `make fmt` (no diff)
> Mux opened this PR on Mike's behalf.
Polishes the agent logs panel in the workspace resources UI: consistent
padding, clearer behavior when switching log source tabs, and a more
usable download menu for long source lists.
- Use symmetric vertical padding on the logs container (`py-4` instead
of top-only padding).
- Add optional `showSourceIcons` on `AgentLogs` (defaults on);
`AgentRow` turns it off unless the **All** tab is active so filtered
tabs are not cluttered with redundant source icons.
- Anchor the download-logs dropdown below the trigger and cap menu
height to 6 items with scrolling so many sources do not overflow the
viewport.
This removes the CSS props of various components found in our workspaces
pages. Eventually, we'd like to remove the MUI-specific components in
here, but its a good start!
This branch tightens import hygiene and editor guidance to reduce
accidental use of legacy or discouraged patterns.
It also updates consumers too, by propagating the new `lucide-react`
import convention across the existing UI surfaces that reference those
icons.
- Updated `.vscode/settings.json` to prefer non-relative imports and
improve TypeScript auto-import behavior.
- Re-enabled and expanded Biome restricted-import enforcement in
`biome.jsonc` for migration guardrails.
- Added/used `lucide-react` `-Icon` naming conventions for clarity and
consistency.
- Updated consumers too across components, modules, and pages so the new
import rules are applied end-to-end.
> 🤖 This PR was written by Coder Agent on behalf of Jake Howell
Replace the single-select inline UserAutocomplete form with a
multi-select Dialog (matching the GroupMembersPage pattern from #24287).
Changes:
- Replace AddOrganizationMember inline form with AddUsersDialog using
MultiUserSelect for multi-user selection in a modal
- Batch-add multiple users via Promise.all in the page callback
- Remove isAddingMember prop (dialog manages its own loading state)
- Update stories to match new interface
This pull-request takes our `<CodeExample />` component and removes the
MUI specific styles.
---------
Co-authored-by: Jeremy Ruppel <jeremyruppel@users.noreply.github.com>
Fixes alignment issues in the workspace proxies table.
## Changes
- **Status column alignment**: Removed `text-right` from the Status
column header and the `justify-end` flex wrapper from status cells.
Status indicator dots now align consistently across rows regardless of
text width ("Healthy" vs "Not reachable").
- **Error/warning text alignment**: Changed padding from `px-12` (48px)
to `px-14` (56px) so error/warning messages align with proxy name text,
which starts after cell padding (12px) + avatar (32px) + gap (12px) =
56px.
- **colSpan fix**: Corrected `colSpan={4}` to `colSpan={3}` to match the
actual number of table columns.
Relates to DES-22000
> 🤖 Generated by Coder Agents
This pull-request takes our `<Avatar />` and `<AvatarCard />` component
and removes the bespoke styles to them. As an added bonus I took care of
the `getExternalImageStylesFromUrl` helper util and cleaned out the
MUI-specific code to it.
There are some remnants of `@emotion/react` in
`getExternalImageStylesFromUrl` however we don't have a theme switcher
that could appropriately handle it yet.
> 🤖 This PR was modified by Coder Agent on behalf of Jake Howell
Removes the bottom-line navbar effect in `<NavbarView />` and cleans up
prerelease CSS.
- Remove `relative` from `<NavbarView />` as it was redundant with the
`sticky` class.
- Refactor `getPrereleaseFlag` to simplify version classification.
- Clean up unused prerelease CSS from `index.css`.
<img width="920" height="83" alt="image"
src="https://github.com/user-attachments/assets/84e351e6-d7a2-4fe2-a331-27c651266256"
/>
- replace the timer-driven permitted-orgs story wrapper with
deterministic API stubs
- keep the empty/subset story assertions focused on org picker
visibility and submitted organization IDs
- clean up the stale story comment and simplify the shared story setup
> 🤖
The `app` e2e test flakes in CI with `waitForEvent("page")` or
`isVisible()` timeouts when opening a workspace app in a new tab.
The test had several timing hazards: the HTTP server address was read
before the socket finished binding, the popup event listener was
registered after the click, and content visibility used a point-in-time
check with the default 5s timeout.
Rewritten to wait for the server to listen, register the popup promise
before clicking, use `getByRole("link")` for a precise locator, and
replace `isVisible()` with a retrying `toBeVisible({ timeout: 30_000 })`
assertion that tolerates the intermediate `about:blank` page and
app-proxy startup delay. The test body is wrapped in `try/finally` to
ensure the HTTP server is always closed.
Fixescoder/internal#577
> Generated by Coder Agents
---
someone wise once said
> and I really don't know what we could do to make it more reliable
other than maybe trying to just rewrite it from scratch.
Adds the devcontainers icon sourced from [Microsoft Fluent UI
`ic_fluent_cube_32_filled`](https://github.com/microsoft/fluentui-system-icons/blob/78c9587b995299d5bfc007a0077773556ecb0994/assets/Cube/SVG/ic_fluent_cube_32_filled.svg).
The registry module `devcontainers-cli` already references
`/icon/devcontainers.svg` — this adds the missing icon.
## Changes
- `site/static/icon/devcontainers.svg` — new icon
- `site/src/theme/icons.json` — auto-generated entry
- `site/src/theme/externalImages.ts` — registers icon as `monochrome` so
it renders correctly on both dark and light themes
> 🤖 This PR was created with the help of Coder Agents, and needs a human
review. 🧑💻
---------
Co-authored-by: Jake Howell <jake@hwll.me>
GetChildChatsByParentIDs sorted created_at ASC, but the cache
helper appended new children to the end. On refetch the API and
cache agreed on oldest-first, putting the just-created child at
the bottom. Users expect newest first, matching the root-chat
sidebar convention.
- SQL: change child sort to created_at DESC, id DESC.
- Cache: prepend instead of append in addChildToParentInCache
(renamed from appendChildToParentInCache to avoid leaking
position semantics).
- Test: update ordering assertion to expect newest-first.
Refs #24404
GetChats now returns only root chats (parent_chat_id IS NULL).
A new GetChildChatsByParentIDs query fetches children for visible
roots and embeds them in each parent's Children field. The
singular getChat endpoint does the same.
Archive invariant is one-way: parent archived implies child
archived. Parent archive/unarchive cascades via root_chat_id.
Individual child archive is permitted; child unarchive while the
parent is archived is rejected atomically (row lock on child,
re-read parent inside the transaction). Embedded children are
filtered by the caller's archive state so individually-archived
children stay hidden from active-parent views.
Gitsync MarkStale uses GetChatsByWorkspaceIDs directly;
MarkStaleParams.OwnerID removed (dead after the switch).
Frontend: buildChatTree reads from the embedded children field,
WebSocket handlers route child events into the parent's children
array, and archiving a child strips it from the parent cache.
Modifies chatloop error classification behaviour to treat the following as retryable:
* HTTP/2 `force closed`
* GOAWAY
* use of closed network connection
* Modfies user-facing retry banner to show "<provider> is temporarily
unavailable."
Relates to CODAGT-212.
> 🤖
The workspace pill in the chat box was shrinking smaller than the size
of its fixed content (icon, chevron, padding), causing the workspace
icon and chevron to become distorted or overflow.
Agents can already see workspace files and take screenshots, but users could not download those artifacts from chat. This PR adds durable chat attachments to chatd. `attach_file`, explicit `computer` screenshot actions (not the automatic post-action screenshots), and `propose_plan` now fetch bytes over the agent connection, store them in `chat_files`, link them to the chat, and carry attachment metadata in tool responses so `buildAssistantPartsForPersist` can materialize ordinary `type:"file"` assistant parts that the chat file APIs serve.
The same storage helpers are reused for other artifact-producing paths. `wait_agent` recordings and thumbnails are stored as chat files and linked back to the parent chat, with best-effort relinking so parent chats retain those artifacts without leaving orphaned rows when chat-file caps reject links. `storeChatAttachment` wraps insert + link in one transaction, files are capped at 10 MB each and 20 per chat, and serving defaults to `Content-Disposition: attachment` with an explicit inline-safe allowlist.
This PR also consolidates chat-file media policy in `coderd/chatfiles`. Uploads and tool-generated attachments share byte-based MIME detection, SVG blocking, inline-safety rules, and compatible `text/plain` refinement for JSON, CSV, and Markdown. Prompt construction still only inlines synthetic pasted text for model consumption; assistant-created attachments are persisted for the user and intentionally not replayed into later LLM turns.
UI follow-up lives in #24281.
Relates to CODAGT-91
The `DateRangePicker` accepts a `now` prop that stories and tests use to
pin preset ranges and future-date disabling to a deterministic clock,
but it never forwarded that value to the underlying `Calendar`.
`react-day-picker` then computed its own `today` from wall-clock time,
which drives both the "today" highlight modifier and the initial visible
month — causing stories like `TemplateInsightsControls:Day` to _flake_
as real time advanced.
Thread `currentTime` into `react-day-picker`'s `today` prop. When `now`
is omitted (production usage), `currentTime` falls back to `new Date()`,
which matches `react-day-picker`'s existing default, so there is no
runtime behavior change.
Fixes three classes of edit_files bugs and adds structured per-file
diff output for tool callers:
- New IncludeDiff flag on FileEditRequest; when set, the agent
returns FileEditResponse.Files[]{Path, Diff} with unified diffs
computed via go-udiff v0.4.1 Lines + ToUnified (not Unified,
which calls log.Fatalf on internal error).
- Fuzzy match comparators split each line into leading whitespace,
body, trailing whitespace, and ending. The splice substitutes at
each position: on agreement between search and replace the file's
bytes win; on disagreement the replacement's bytes are spliced
verbatim. Carve-outs for empty-body lines, multi-line EOF splices,
and level-aware indent translation for inserted lines.
- Indent-unit detection (GCD for spaces, tab-priority) lets a 4sp
LLM search insert correctly into tab or 2sp files. Falls back to
the previous cLead-inheritance path when units can't be detected
cleanly.
- Empty search is rejected with "search string must not be empty".
- Duplicate file paths in one request are rejected; symlink aliases
resolved via api.resolvePath before the dedup check.
- Frontend EditFilesRenderer consumes the structured files array by
explicit path (no label munging) with per-file synthetic fallback
for older agents or mismatched paths. On error, no diff is
rendered so the synthetic fallback doesn't misrepresent a
rejected edit as applied.
Breaking change: AgentConn.EditFiles changes from (ctx, req) error
to (ctx, req) (FileEditResponse, error) in codersdk/workspacesdk.
Source-breaking for external Go consumers; no compat shim per plan
owner.
Out of scope (tracked in CODAGT-214): level-aware indent for
middle-substituted splice lines. Locked in
TestEditFiles_FuzzyIndent_InsertionLevelAware's Lock_* cases plus
TestEditFiles_ReplaceAll_FuzzyIndentGap.
The shadcn-compatible CSS aliases (`--background`, `--foreground`,
`--muted`,
`--muted-foreground`, `--primary`, `--primary-foreground`) were added as
part
of the Coder agents work with hardcoded HSL values that duplicated
existing
semantic design tokens. These non-standard color classes (`bg-muted`,
`text-muted-foreground`, `text-foreground`) had started spreading
through
components like Table, MultiUserSelect, and WorkspacesTable.
This PR makes two changes:
1. **CSS aliases now derive from canonical tokens via `var()`
references**
instead of duplicating HSL values. The aliases remain for `streamdown`
and other external consumers, but `--background` now resolves to
`--surface-primary` (`#FFFFFF` in light, was `#FAFAFA`), and the rest
map to their semantic equivalents (`--content-primary`,
`--surface-secondary`, `--content-secondary`, `--content-link`).
2. **Component classes replaced with semantic equivalents:**
- `bg-muted` → `bg-surface-secondary`
- `text-muted-foreground` → `text-content-secondary`
- `text-foreground` → `text-content-primary`
- `text-amber-400` → `text-content-warning`
- `hsl(var(--background))` → `hsl(var(--surface-primary))`
- `hsl(var(--muted-foreground))` → `hsl(var(--content-secondary))`
> This PR was initially created by Claude Opus 4.
Co-authored-by: Jaayden Halko <jaayden@coder.com>
Co-authored-by: Jaayden Halko <jaayden.halko@gmail.com>
After password login, `navigate('/')` performed a client-side SPA
navigation, leaving the pre-authentication `<meta>` tags (including
`userAppearance`) empty in the DOM. The `ThemeProvider` read empty
metadata and fell through to `DEFAULT_THEME` until the API query
resolved, causing a visible flash of the wrong theme. A page refresh
fixed it because the server re-rendered HTML with the session cookie
present.
Replace `navigate('/')` with `window.location.href` so the server
re-renders HTML with all metadata tags populated (`userAppearance`,
`user`, `permissions`, etc.) via the new session cookie. This matches
the pattern already used for API route redirects on the same page. Also
uses `redirectTo` instead of hardcoded "/" so the redirect query
parameter is respected for password login.
Fixes https://github.com/coder/coder/issues/20050
> [!NOTE]
> Generated by Coder Agents
<details><summary>Decision log</summary>
- Chose `window.location.href` over invalidating React Query caches
because a hard reload is the only way to get fresh server-rendered meta
tags. The API query approach still has a flash between mount and
response.
- Sanitized redirect URL with `redirectUrl.pathname` (same as existing
`<Navigate>` path) to prevent open redirects via absolute URLs.
- Removed unused `useNavigate` import.
</details>
---------
Co-authored-by: Kayla はな <kayla@tree.camp>
> This PR was authored by Mux on behalf of Mike.
Introduce Explore mode, a read-only subagent modality for delegated
discovery and code investigation.
## What
Adds a `spawn_explore_agent` tool that creates child chats restricted to
read-only operations. An admin can optionally configure a
deployment-wide
model override so Explore subagents use a model optimized for large
context
or reasoning without changing the root chat's model.
### Backend
- New `ChatModeExplore` enum value (migration 000471).
- `spawn_explore_agent` tool definition with read-only allowlist:
`read_file`, `execute`, `process_output`, `read_skill`,
`read_skill_file`.
Write tools, file editors, and nested subagent spawning are blocked.
- Deployment config storage for the Explore model override
(`agents_chat_explore_model_override` in `site_configs`).
- Model resolution hierarchy: configured override, then current turn
model,
then global default. Silent fallback with warning log when the override
becomes unavailable.
- RBAC: `AsChatd` for daemon reads, `ActionRead` and `ActionUpdate` on
`ResourceDeploymentConfig` for admin API calls.
- Plan mode root chats can use `spawn_explore_agent` for read-only
research,
matching the planning prompt guidance.
- The Explore override config API now reports malformed saved overrides
as
"treated as unset" so admins can clear them explicitly.
### Frontend
- `ExploreModelOverrideSettings` component in admin agent behavior
settings.
Uses `ModelSelector`, handles unavailable model warnings, and supports
explicit Save and Clear actions.
- Malformed saved overrides show a warning and require an explicit Save
to
clear, instead of Clear auto-submitting behind the scenes.
### Tests
- Integration: `TestExploreSubagentIsReadOnly` (full spawn flow, tool
verification, prompt overlay, DB state).
- Unit: tool allowlist tests for explore, plan, and default modes.
- Internal: model override resolution with valid, invalid UUID,
disabled, and
unconfigured override scenarios.
- RBAC: `dbauthz_test.go` for `GetChatExploreModelOverride` and
`UpsertChatExploreModelOverride`.
- API: admin set and clear, malformed stored override reporting,
disabled
model rejection, non-admin denial.
Relates to https://linear.app/codercom/issue/CODAGT-103
- Add `keywords={[option.label]}` to `CommandItem` in
`MultiSelectCombobox` so cmdk's default filter matches against the
visible label text, not just the value (UUID)
- Extend `OpenCombobox` story with type-to-filter assertions
- Add "search filters by display name" step to `TemplateAllowlist` story
> 🤖
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Add a `chat_client_type` enum (`ui` | `api`) and `client_type` column to
the `chats` table. The column defaults to `api` for new rows so API
callers don't need to set it explicitly. Existing rows are backfilled to
`ui`.
The field flows through `CreateChatRequest`, `chatd.CreateOptions`,
`InsertChat`, and is returned in the `Chat` response via `db2sdk`.
<details>
<summary>Implementation notes (Coder Agents generated)</summary>
### Changes
**Database migration (000469)**
- New enum `chat_client_type` with values `ui`, `api`.
- New `client_type` column, `NOT NULL DEFAULT 'api'`.
- Backfill: `UPDATE chats SET client_type = 'ui'`.
**SQL query** — `InsertChat` now includes `client_type`.
**SDK** — `ChatClientType` type added; `ClientType` field added to both
`CreateChatRequest` (optional, defaults server-side to `api`) and `Chat`
response.
**Handler** — `postChats` maps the request field (defaulting to `api`)
and passes it through `chatd.CreateOptions`.
**Sub-agent** — Child chats inherit their parent's `client_type`.
**db2sdk** — Maps the database value to the SDK type.
### Decision log
- Default is `api` (not `ui`) so existing API integrations get the
correct value without code changes.
- Backfill sets existing rows to `ui` per requirement.
- Child chats inherit `client_type` from parent rather than defaulting.
</details>