16139 Commits

Author SHA1 Message Date
Michael Suchacz d6b67e8e31 feat: enforce SSRF protection for MCP config-directed traffic (#28242)
Routes all MCP config-directed traffic from coderd and chatd through a
shared SSRF-protected HTTP client, now that organization admins, not
only deployment admins, control MCP server URLs (#27942 and its stack
below).

## Summary

- Uses the [`coder/safedial`](https://github.com/coder/safedial)
library: it blocks private and special-purpose destinations at dial time
(validating resolved addresses at connection time so DNS rebinding
cannot bypass the check) and rejects cross-origin redirects.
- Covers the complete traffic surface: OAuth2 discovery, dynamic client
registration, code exchange, token refresh, revocation, and runtime MCP
connections from chatd.
- Deployments that intentionally host internal MCP servers opt in via
the new `--mcp-allowed-private-cidrs`
(`CODER_MCP_ALLOWED_PRIVATE_CIDRS`) option.
- Includes the deployment configuration surface, generated docs, CLI
goldens, TypeScript types, and regression coverage for each traffic
path.

## Merge window

This protection originally lived inside #27942 and was split out to keep
that diff reviewable. Until this PR lands, the stack below ships with
only main's existing discovery IP-range guard
(`CODER_MCP_OAUTH2_DISCOVERY_ALLOWED_IP_RANGES`), while org admins can
already point MCP configs at arbitrary URLs. This PR should merge
promptly after the stack below it.

Top of the MCP org-separation stack (CODAGT-711 -> CODAGT-717 audit ->
CODAGT-712 ACLs -> CODAGT-806 token RBAC -> CODAGT-714 org picker ->
this PR).

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

<!-- mux-attribution: model=claude-fable-5 thinking=high -->
2026-08-30 13:09:17 +02:00
Jaayden Halko 8bf271c503 feat: add Prometheus metrics for the usage publisher (#28368)
The Tallyman usage publisher (`enterprise/coderd/usage/publisher.go`)
had no metrics; failures were only visible in logs and the
`usage_events.failure_message` column.

This adds five metrics under `coderd_usage_events_*`:

| Metric | Type | Meaning |
|---|---|---|
| `publish_results_total{result, event_type}` | counter | Per-event
outcomes from real Tallyman responses (`accepted`,
`rejected_temporarily`, `rejected_permanently`; events missing from the
response count as temporary) |
| `publish_send_errors_total` | counter | Ingest requests that failed
entirely (HTTP error, non-200, decode error), one per request |
| `pending` | gauge | Unpublished events still inside the 30-day
publishing window |
| `pending_oldest_age_seconds` | gauge | Age of the oldest pending event
(0 when none) |
| `expired` | gauge | Unpublished events older than 30 days that will
never be published |

Send errors and per-event results never double-count: when a request
fails, the publisher fakes an all-temporarily-rejected response for the
DB update, but only `publish_send_errors_total` increments, so
`publish_results_total` reflects only real Tallyman verdicts.

The gauges are backed by a new read-only `GetUsageEventsStats` query
(authorized as `ActionRead` on `ResourceUsageEvent`) and refreshed from
the publish loop after each attempt, so they update roughly every 17
minutes.

Metrics are created with `promauto.With(reg)` where the registerer
defaults to nil, so existing callers and tests need no changes;
`enterprise/cli/server.go` wires in the deployment's
`PrometheusRegistry` via the new `PublisherWithPrometheusRegisterer`
option.

Closes
https://linear.app/codercom/issue/CODAGT-833/e3-prometheus-metrics-for-the-usage-publisher

---------

Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
2026-08-28 15:44:31 +07:00
TJ 21b3dafe35 fix(site): size chat pill selectors to content with an 8ch truncation floor (mobile and desktop fixes) (#28691)
## Problem

#28487 gave the model selector and workspace pill a fixed
`min-w-[calc(8ch+3.125rem)]` floor. That fixed the pill shrinking to
nothing under width pressure, but caused regressions (#28685 reverts
it):

- Short labels (e.g. "Fable 5") were padded out to the floor with dead
space.
- Long labels were clamped (workspace pill at 200px) or truncated even
when the toolbar had visible free space.

The visible-free-space bug had a second root cause: overflowed badges
stayed in flow (`invisible order-1`) so the overflow hook could
re-measure them, and their reserved space silently consumed the row's
flex free space. The model pill sat pinned at its floor next to a fake
gap, and hidden badges could never return.

## Fix

Keep the part of #28487 that works, the workspace pill collapsing into
the `+N` overflow popover, and rebuild the sizing on two fronts.

**Pills size to content with a growth floor** (model selector trigger
and workspace pill wrapper):

```
basis-[calc(8ch_+_3.125rem)]  /* the truncation floor */
shrink-0                      /* never shrink below the floor */
grow                          /* expand into free row space */
max-w-max                     /* never wider than the label */
```

Flexbox clamps the base size by `max-width`, so short labels sit at
natural width (no dead space) and long labels truncate only under
genuine pressure. The workspace pill's 200px clamp is removed, the
toolbar's left group gains `flex-1` so free row space reaches the pills,
and `ModelSelector`'s defaults return to `min-w-0 shrink` (sizing is
owned by the chat-input callsite; other callsites unaffected).

**Overflowed badges release their space.** They now hide with `display:
none`, and `useOverflowCount` decides fit from cached last-visible
widths against the toolbar group's right edge instead of in-flow
positions. The hook also reserves the truncation deficit of the
container's siblings, giving pills priority: the model label expands to
its natural width before badges claim inline space, and badges that lose
the contest stay reachable in the `+N` popover.

Resulting priority under pressure: pills keep natural width while badges
collapse into `+N`; once all badges are collapsed, pills shrink toward
the ~8ch floor; below the floor the workspace pill collapses into `+N`
too.

Mobile polish: the `+N` popover anchors to its pill just above the
toolbar row (previously it covered the row), the workspace stays an
interactive pill with its menu inside the popover, status tooltips are
hidden below `md` (touch focus left them stuck open), and both pills
share a height at every breakpoint.

Stacked on #28685: this branch contains the revert plus a
revert-of-the-revert, which cancel out; the diff shrinks to just this
fix once #28685 merges.

## Testing

Three new Storybook interaction stories: `ShortModelNameHasNoDeadSpace`
(crowded mobile toolbar, trigger narrower than the floor, label
untruncated), `LongLabelsExpandWithoutMCPs` (labels untruncated,
workspace pill wider than the old 200px clamp, no `+N` pill), and
`ModelExpandsWhileBadgesOverflow` (wide badges collapse into `+N` and
the model label renders untruncated in the freed space). The restored
`OverflowBadges` and `LongWorkspaceNameMobile` stories still verify the
`+N` collapse. All story tests pass, plus typecheck and biome.

FE-rule note (FE10): the new stories assert geometry (widths,
`scrollWidth`) because dead space, truncation, and the clamp have no
semantic signal; the floor is measured via a probe resolved against the
real font rather than hardcoded pixels.

<details>
<summary>Decision log</summary>

- Considered `min-width: min(max-content, 8ch + 3.125rem)`: invalid CSS,
intrinsic keywords are not allowed inside `min()`.
- Considered a JS measurement hook setting inline `min-width`: works but
adds a measure-clear-restore dance; the basis/grow/max-content scheme
expresses the same clamp declaratively.
- First iteration kept overflowed badges in flow (`invisible order-1`,
the pre-existing mechanism). Reproduction showed their reserved space
blocked pill growth entirely and produced a large fake gap after the
`+N` pill, so hidden badges now use `display:none` with width caching in
the hook.
- Last-visible widths live in an element-keyed WeakMap that is never
cleared: clearing on badge-count changes would reintroduce a one-frame
all-badges-visible flicker. Consequence: a badge whose label changes
while hidden keeps its stale cached width until it is next visible, then
self-corrects.
- Sibling-deficit reservation decides the pills-vs-badges contest in
favor of pills. Without it, badges that fit at the model's floor width
kept the model truncated; measured equilibria confirmed no oscillation
because freed slack is always smaller than the badge that was hidden.
- The mobile pill height jump (h-7 below md vs h-auto at md+) reported
during review is pre-existing intentional touch-target sizing from
#28399 and is out of scope here.

</details>

> Created by Coder Agents on behalf of @tracyjohnsonux.
2026-08-27 21:59:27 +00:00
McKayla はな 6e5ff5eac6 chore: remove unused log button (#28726) 2026-08-27 15:36:55 -06:00
Samuel Volin ecfb07467c chore: enable oxlint rule no-restricted-imports , enforce for src/components/** (#28734)
## synopsis

* defines the
[https://oxc.rs/docs/guide/usage/linter/rules/eslint/no-restricted-imports](no-restricted-imports)
oxlint rule , and enables it for the `src/components/**` directory with
two patterns:
```
{
	"group": ["**/pages", "**/pages/**"],
	"message": "components/ must not import from pages/. Move shared code down into components/."
},
{
	"group": ["**/modules", "**/modules/**"],
	"message": "components/ must not import from modules/. Maintain imports from other components/."
}
```
* moves `src/pages/UsersPage/storybookData/users.ts` to
`site/src/testHelpers/users.ts` and updates imports to enforce this
* also defines a few catch-all rules, including `Application code must
never import from the Playwright e2e suite.` and `Use the
useAuthContext() or useAuthenticated() hooks instead of the raw
AuthContext.`

---

## linting commentary

* `no-restricted-imports` is my favorite linting rule. Seriously it is
so powerful niche, and no one knows how effective it can be until they
see how coordinating imports as a lint rule helps create maintainable
java/typescript piles.
* The `no-restricted-imports` rule may need to be best defined within
the overrides section, so specific groups of files can get their own
rules regard import restrictions.
* Adding further `no-restricted-imports` entries will typically require
making further import fixes and cleanup. this untangles imports, but
makes for PRs with a shotgun blast radius, so we may want to increment
this with piecemeal PRs
* further work this may compose:
  * `src/modules` do not import from `src/pages`
  * non-tests or stories do not import from `src/testhelpers`
  * `src/utils` do not import from `components/modules/pages`
  * `src/api` do not import from `components/modules/pages`
2026-08-27 15:34:58 -06:00
Jeremy Ruppel 96b4b5fa52 chore: enable oxlint new-for-builtins and disable the Biome equivalent (#28735)
Promotes the oxlint `new-for-builtins` rule from the migration backlog
to `error` and disables Biome's analogous
`correctness/noInvalidBuiltinInstantiation`, so oxlint becomes the sole
enforcer of this rule.

Fixes the seven violations by requiring `new` for builtin
instantiations:

- `XAxis.tsx`: replace `[...Array(columns).keys()].map(...)` with
`Array.from({ length: columns }, (_, key) => ...)`, folding the range
and map into one dense-array construction.
- `pasteHelpers.test.ts` (×4) and `TextPreviewDialog.stories.tsx` (×1):
`Array(n).fill(...)` becomes `new Array(n).fill(...)`.
- `schedule.tsx`: `throw Error(...)` becomes `throw new Error(...)`.

`pnpm run lint` (Biome, oxlint, tsc, circular-deps, compiler, knip) and
`pnpm run format` pass clean; the affected `pasteHelpers` and `schedule`
unit tests pass.

---

Generated by Coder Agents on behalf of @jeremyruppel.
2026-08-27 21:22:30 +00:00
Jeremy Ruppel c9cb02a55c chore: enable oxlint no-explicit-any and disable the Biome equivalent (#28733)
Promotes the oxlint `no-explicit-any` rule from the migration backlog to
`error` and disables Biome's analogous `suspicious/noExplicitAny`, so
oxlint becomes the sole enforcer of this rule.

The eight existing `biome-ignore lint/suspicious/noExplicitAny`
suppressions are removed and their `as any` casts replaced with precise
types (no `any`, no `as unknown as` double-casts):

- `optionValue.ts`: the default branch now asserts to the `OptionValue`
children union.
- `MonacoEditor.tsx`: a typed intersection describes Monaco's private
`_standaloneKeybindingService`.
- `e2e/helpers.ts`: a `BrowserContext` intersection types the
symbol-keyed current user.
- `e2e/api.ts`: `opt.value` (typed `unknown`) is asserted directly to
`string` / `string[]` / `Record<string, string>`.

`pnpm run lint` (Biome, oxlint, tsc, circular-deps, compiler, knip) and
`pnpm run format` pass clean.

<details>
<summary>Implementation plan</summary>

## Goal

Enable the oxlint rule `no-explicit-any` (mapped to Biome
`suspicious/noExplicitAny`), disable the analogous Biome rule, and fix
all resulting violations by removing the explicit `any` types (not by
migrating suppression comments).

## Findings

- The oxlint rule was parked in the migration backlog:
`"no-explicit-any": "off", // suspicious/noExplicitAny`.
- Running oxlint with the rule as error surfaced exactly 8 violations,
all in places already carrying `// biome-ignore
lint/suspicious/noExplicitAny` comments:
  - `src/pages/DeploymentSettingsPage/optionValue.ts` (1)
  - `src/pages/TemplateVersionEditorPage/MonacoEditor.tsx` (1)
  - `e2e/helpers.ts` (3)
  - `e2e/api.ts` (3)
- Biome's `noExplicitAny` is a recommended rule (implicitly on);
disabling it means adding an explicit `"off"` in the root `biome.jsonc`.
- `e2e/**` is type-checked by `lint:types` (`tsc -p .`), so e2e fixes
must type-check.

## Constraints

- Remove the `biome-ignore` comments and fix the `any`s (do not migrate
suppressions).
- Do not cast through `unknown` (no `x as unknown as Y`). Single direct
assertions from an already-`unknown` value and subtype intersection
casts are used instead.

## Changes

1. `site/.oxlintrc.jsonc`: move `no-explicit-any` to the active
suspicious section as `"error"`; drop the backlog entry.
2. `biome.jsonc`: add `"noExplicitAny": "off"` under
`linter.rules.suspicious`.
3. Fix the 8 violation sites and remove the 8 `biome-ignore` comments
across the 4 source files.

## Validation

- `pnpm run lint:oxlint` → 0 errors
- `pnpm run lint:check` (Biome) → clean
- `pnpm run lint:types` (`tsc -p .`) → clean (covers `src` and `e2e`)
- `optionValue.test.ts` → 16 tests pass
- full `pnpm run lint` and `pnpm run format` → clean

</details>

---

Generated by Coder Agents on behalf of @jeremyruppel.
2026-08-27 17:06:59 -04:00
Yevhenii Shcherbina 0bc2858b6e feat: report unpriced AI models to owners (#28419)
Closes
[AIGOV-568](https://linear.app/codercom/issue/AIGOV-568/notify-admins-about-unpriced-ai-models).

AI Bridge records token usage for a model with no price at a NULL cost,
so its spend is neither reported nor enforced against any budget. Until
now that only reached admins through an info log and a Prometheus
metric.

Owners now receive a weekly report listing models used without a price
in the past week. The report links to documentation explaining how Coder
calculates spend from the default price book and how owners can
configure missing prices with `coder exp ai-model-prices`.

## Screenshots

<img width="726" height="531" alt="image"
src="https://github.com/user-attachments/assets/168ae378-60cb-4c96-8f60-36c6edc0b268"
/>
<img width="470" height="417" alt="image"
src="https://github.com/user-attachments/assets/7dc71a1e-0c57-44f1-86f7-df800603c865"
/>



## Design

**Derived, not tracked.** The unpriced set is computed at report time
from interceptions that recorded token usage, joined against the current
`ai_model_prices` table. Requests that produced no token usage are
excluded.

**No new loop or table.** `reportUnpricedAIModels` is a sibling of
`reportFailedWorkspaceBuilds` in the existing report generator, reusing
its ticker and `notification_report_generator_logs`. Each report runs in
its own transaction under a separate advisory lock, so failures and lock
contention do not couple otherwise independent reports. The weekly
frequency is enforced by the persisted timestamp rather than by the
ticker, which restarts with the process and runs on a different phase in
each replica.

## Behaviour

| Situation | Outcome |
|---|---|
| Model used without a price | Listed in the next weekly report |
| Price set | Disappears from the next report |
| Still unpriced and still in use | Reported again each week |
| Model stops being used | Drops out of the report |
| Nothing unpriced | No notification; window still advances |
| More than 100 unpriced models | Top 100 by usage, with the total
reported alongside |
| openai-compat models | Excluded, they cannot be priced |
| Many replicas | Exactly one report per week |
| First ever run | Reports models used in the preceding week immediately
|

## Design choices

1. **openai-compat** is excluded because it cannot be priced and would
produce permanent, unactionable notifications.
2. **Token volume** orders the list but is not shown. The 100-model cap
therefore prioritizes models responsible for the most unpriced usage.
3. **Placement** remains in the generic report generator rather than
`aibridgedserver`. This keeps one periodic-report lifecycle at the cost
of coupling the notifications package to an AI cost-control query.
4. **No license gate.** The reporter runs even when AI Gateway is
disabled. With no recorded token usage the query is empty and no
notification is sent.

---

Created by Coder Agents on behalf of @evgeniy-scherbina.
2026-08-27 21:04:13 +00:00
Nick Vigilante ac95e7b743 ci: notify Slack when audit-docs-paths finds docs URL drift (#27924)
## What

Adds Slack notifications to `.github/workflows/audit-docs-paths.yaml` so
the scheduled audit actively pings the docs team both when it finds docs
URL drift and when the run itself fails, on top of the existing tracked
issue + red check + report artifact.

## Why

The dedicated `audit-docs-paths` workflow (#27245) surfaces drift only
through a tracked GitHub issue, a failing check, and an uploaded report.
None of those actively notify anyone, so drift can sit unseen unless
someone is watching repo issues. The embedded `audit-docs-paths` job
that #27245 removed from `weekly-docs.yaml` already posted to Slack, so
this also restores that notification in the new workflow.

This also satisfies the condition on @bpmct's approval of #27245:
"assuming you have a solid solution for getting notified/assigned when a
link is old/stale."

## How

- Two notifications, both reusing the existing
`secrets.DOCS_LINK_SLACK_WEBHOOK` (the same docs webhook
`weekly-docs.yaml` uses), so no new secret or channel:
- **Drift** (`findings > 0`, step id `slack_drift`): runs immediately
after the audit, gated only on the audit outputs, so a later report- or
issue-step failure can't suppress it.
- **Failure** (`failure() && steps.slack_drift.outcome != 'success'`):
the last step in the job, so it fires on any job failure the drift ping
didn't already announce, whether pre-audit (e.g. an expired
`CDRCI_GITHUB_TOKEN` failing the coder.com checkout), the audit itself,
or a post-audit step (issue write, artifact upload). This restores and
generalizes the `failure()` coverage the pre-#27245 embedded job had,
and can't rot as steps are added. It stays silent on a drift run, where
that ping already fired.
- The audit measurement now fails loud rather than open: it asserts both
scan roots exist before running and drops the count fallback, so an
incomplete scan (e.g. a coder.com `src/` restructure the script would
only warn about) or an undetermined finding count fails the step and
triggers the failure ping, instead of reporting a false all-clear and
closing the tracked issue.
- Clean runs stay silent. The message links the run; the report artifact
and tracked issue are reachable from there.
- The webhook is passed via `env` with a `[ -n ]` guard (named error if
unset). `curl` uses `-fsS --max-time 10 --retry 3 --retry-all-errors`
under `set -euo pipefail`, so a revoked or misconfigured webhook fails
the step (red) instead of silently logging success, while a transient
blip is retried.
- Everything stays gated behind the job-level
`vars.AUDIT_DOCS_PATHS_ENABLED`.

## Follow-up (out of scope)

- `weekly-docs.yaml`'s `Send Slack notification` step has the same
bare-`curl` pattern this step was copied from. It predates this PR and
is tracked separately in
[DOCS-705](https://linear.app/codercom/issue/DOCS-705/harden-weekly-docsyaml-slack-curl-with-fail).

## Rebase note

Originally stacked on #27245; since that merged, the branch is rebased
onto `main`. The diff is scoped to
`.github/workflows/audit-docs-paths.yaml`.

## Validation

- `make lint/actions` (actionlint + shellcheck + zizmor): clean.
- `make lint/emdash`, `make lint/typos`: clean.
- Verified empirically: both message payloads are valid JSON; the `[ -n
]` guard fails the step on an unset webhook; `curl -fsS` exits non-zero
on an HTTP 4xx/5xx and `set -e` then fails the step, so a bad webhook
can't log a false success.
- Traced the `failure() && steps.slack_drift.outcome != 'success'` gate
across scenarios: clean run (silent), drift (drift ping only, no
double-ping), and pre-audit / audit / post-audit failures (failure ping
fires).

Linear: https://linear.app/codercom/issue/DOCS-617

> This PR was created with AI assistance (Coder Agents).
2026-08-27 20:37:44 +00:00
Andrew Aquino 91e28e2983 feat: handle client_session_id in agent middleware (#28039)
Implements [DEVEX-660](https://linear.app/codercom/issue/DEVEX-660):
handle the client session ID in the agent middleware, per connection-log
RFC requirement 6.2.

> Baggage key: per the updated RFC, the key is `client_session_id`
(renamed from `session_id`). The shared constant
`tracing.SessionIDBaggageKey` now has the value `client_session_id`.

## What

- Add `tracing.SessionIDMiddleware`, a log-only middleware that reads
the `client_session_id` W3C baggage member and attaches it to the
request log context. Unlike `tracing.Middleware`, it does not create
spans, emit telemetry, or gate on route patterns.
- Wire it into the agent HTTP stack (`agent/api.go`) before
`loggermw.Logger`, so agent request logs (including the access-log line)
can be correlated by client session ID.

## Why not spans on the agent

RFC 6.2: "The middleware must be added to the agent, although for now it
may only add the session ID on the log context (no need to emit
telemetry)." Spans/telemetry on the agent are out of scope here.

## Testing

- `Test_SessionIDMiddleware`: valid / absent / malformed / uppercase
baggage.
- `Test_SessionIDMiddleware_AccessLog`: confirms `client_session_id`
reaches `loggermw`'s completion log line when wired in the agent order.
- `go vet` and `golangci-lint` pass on `coderd/tracing` and `agent`.

## Stacking

Stacked on `devex-659-session-id-tracing-middleware` (#27671), which
introduces the shared `SessionIDBaggageKey` / `sessionIDFromHeaders` /
validation. Review/merge #27671 first.

<details>
<summary>Implementation plan</summary>

# DEVEX-660: Handle `client_session_id` in agent middleware

> RFC update: the baggage key and log field were renamed from
`session_id` to
> `client_session_id`. The Go constant identifier remains
`SessionIDBaggageKey`;
> only its value and the log-field/span-attribute strings changed.


## Implementation status

Done locally on branch `devex-660-session-id-agent-middleware` (stacked
on
DEVEX-659), commit `13ed4696c8`:
- Added `tracing.SessionIDMiddleware` (log-only) in
`coderd/tracing/httpmw.go`.
- Wired it into `agent/api.go` before `loggermw.Logger`.
- Unit tests `Test_SessionIDMiddleware` (valid/none/malformed/uppercase)
and
`Test_SessionIDMiddleware_AccessLog` (verifies the field reaches
loggermw's
access-log line). Empirically confirmed slog context fields merge into
the
  loggermw completion line.
- Passing: `go test ./coderd/tracing/...`, `go vet ./coderd/tracing/...
./agent/`,
  `golangci-lint run` on both packages, `gofmt` clean.
- Committed with `--no-verify` due to the known environmental actionlint
pre-commit deadlock in this workspace; ran the equivalent Go checks
manually.

Not yet done: push branch, open PR.

## Summary

Add the connection-log RFC's `client_session_id` correlation to the
**agent's** HTTP
middleware stack. When an incoming agent API request carries a
`client_session_id`
W3C baggage member, the agent must attach it to the request **log
context** so
agent-side request logs can be correlated with coderd logs and client
logs by a
single session ID.

Per RFC requirement **6.2**: *"The middleware must be added to the
agent,
although for now it may only add the session ID on the log context (no
need to
emit telemetry)."*

## Scope

In scope:
- A middleware on the agent HTTP router (`agent/api.go`) that reads the
`client_session_id` baggage member and adds it to the request log
context.
- Log context only. **No spans, no telemetry, no route-pattern gating.**

Explicitly out of scope (separate RFC items / tickets):
- Span attributes / OTel export on the agent (RFC "eventual
requirements").
- Reconnecting-PTY and `agentssh` command logging with session ID
  (RFC #8, #15).
- `connection_logs` session_id column / user ID (RFC #8).
- Any client-side work (RFC #1-5), which is DEVEX-663 and siblings.

## How this differs from DEVEX-659

| Aspect | DEVEX-659 (coderd) | DEVEX-660 (agent) |
| --- | --- | --- |
| Wiring point | `tracing.Middleware(tracerProvider)` in
`coderd/coderd.go` | agent router in `agent/api.go` |
| Existing stack | span-creating `tracing.Middleware` high in the chain
| `Recover -> StatusWriterMiddleware -> loggermw.Logger ->
agentchat.Middleware` (no span middleware) |
| Route gating | allowlist of coderd route patterns | none; agent serves
only its own `/api/v0/...` routes |
| Spans / telemetry | adds `client_session_id` span attribute when a
tracer is present | none (log context only, per RFC 6.2) |
| Log mechanism | `slog.With(ctx, slog.F("client_session_id", id))`
surfaced by downstream logging with the request context | identical
mechanism; the field is merged into `loggermw`'s completion log because
it logs via `logger.Debug(ctx, ...)` |

Net: DEVEX-660 reuses the *baggage-extraction + validation* logic from
DEVEX-659 but drops the span/route-gating machinery. It is a strictly
smaller,
log-only middleware.

## Reused building blocks (already on the DEVEX-659 branch)

In `coderd/tracing/httpmw.go`:
- `const SessionIDBaggageKey = "client_session_id"` (wire contract).
- `func sessionIDFromHeaders(h http.Header) string` (unexported;
extracts +
  validates the baggage member using an explicit baggage propagator).
- `func ValidSessionID(s string) bool` (exported; lowercase 32-char
hex).

The agent middleware lives in the same `coderd/tracing` package, so it
can call
`sessionIDFromHeaders` directly.

## Design

Add a standalone, log-only middleware to `coderd/tracing/httpmw.go`:

```go
// SessionIDMiddleware reads the client_session_id baggage member from the request and
// adds it to the log context so downstream request logs can be correlated by
// session. Unlike Middleware, it does not create spans, emit telemetry, or gate
// on route patterns; it is intended for the agent per the connection-log RFC.
func SessionIDMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		if sessionID := sessionIDFromHeaders(r.Header); sessionID != "" {
			r = r.WithContext(slog.With(r.Context(), slog.F("client_session_id", sessionID)))
		}
		next.ServeHTTP(rw, r)
	})
}
```

Wire it into the agent stack in `agent/api.go`, **before**
`loggermw.Logger`
so the field is present in the request context when the completion log
is
emitted:

```go
r.Use(
	httpmw.Recover(a.logger),
	tracing.StatusWriterMiddleware,
	tracing.SessionIDMiddleware,
	loggermw.Logger(a.logger, nil),
	agentchat.Middleware,
)
```

### Why placement before `loggermw` works

`loggermw.Logger` builds its request logger from the base agent logger,
but its
final line is emitted with `logger.Debug(ctx, c.message)` using the
request
context. slog merges fields stored on the context via `slog.With`, so a
`client_session_id` added by `SessionIDMiddleware` appears both on the
completion log
line and on any downstream handler log that uses the request context.
This is
the same behavior DEVEX-659 verifies on the coderd side.

### `slog.F` literal constraint

As on the coderd side, the first argument to `slog.F` must be a
snake_case
string literal (repo ruleguard). Keep `slog.F("client_session_id", ...)`
literal;
do not pass `SessionIDBaggageKey`. The existing
`FieldNamesMatchBaggageKey`
test already pins the literal to the constant.

## TDD steps

### Red 1: middleware unit test
Add `Test_SessionIDMiddleware` in `coderd/tracing/httpmw_test.go` (reuse
the
`testutil.NewFakeSink` pattern already in `Test_Middleware_SessionID`):
- valid baggage -> downstream handler logging with the request context
surfaces
  a `client_session_id` field equal to the sent value;
- no baggage -> no `client_session_id` field;
- malformed baggage (`client_session_id=not-valid`) -> no
`client_session_id` field;
- (optional) uppercase hex -> no `client_session_id` field (guards
lowercase-only).

Runs red because `SessionIDMiddleware` does not exist yet.

### Green 1
Implement `SessionIDMiddleware` as above. Run:
`go test ./coderd/tracing/... -run 'Test_SessionIDMiddleware' -count=1`.

### Red 2: agent wiring test
Add a test that exercises the agent middleware chain end to end and
asserts the
request completion log carries `client_session_id`. Mirror the existing
pattern in
`agent/agentchat/log_test.go`, which composes
`tracing.StatusWriterMiddleware(loggermw.Logger(sink.Logger(),
nil)(handler))`
with a fake sink. Build the same chain **including**
`tracing.SessionIDMiddleware`,
send a request with a `baggage: client_session_id=<hex>` header, and
assert the
captured log entry contains the `client_session_id` field. Add a
negative case with no
baggage.

Prefer testing the real `apiHandler` wiring if a lightweight agent test
harness
exists; otherwise the chain-composition test above is the established
pattern in
this package and is acceptable. Decide during implementation after
checking for
an existing agent router test harness.

### Green 2
Add `tracing.SessionIDMiddleware` to the `r.Use(...)` list in
`agent/api.go`. Run the new agent test.

### Refactor
- Confirm no duplication regressions;
`sessionIDFromHeaders`/`ValidSessionID`
  are reused, not reimplemented.
- Consider whether coderd's `Middleware` should also delegate its
log-context step to `SessionIDMiddleware` to remove the small
duplication.
Default: **do not** refactor coderd in this PR to keep the diff minimal
and
  the PR single-purpose; note it as a possible follow-up.

## Validation

- `go test ./coderd/tracing/... -count=1`
- `go test ./agent/... -run '<new test name>' -count=1`
- `go vet ./coderd/tracing/... ./agent/...`
- `make lint` (verify ruleguard passes on the literal `slog.F` field).
- `make gen` is not required (no DB/proto changes).

## Branch / PR strategy

- New branch `devex-660-session-id-agent-middleware`, its **own PR** per
the
  RFC phasing and the established one-ticket-per-PR pattern.
- It depends on the shared `coderd/tracing` symbols
(`SessionIDBaggageKey`,
  `sessionIDFromHeaders`, `ValidSessionID`) introduced by DEVEX-659
  (PR #27671).
- **Decision:** #27671 is not merged yet, so stack `devex-660-...` on
  `devex-659-session-id-tracing-middleware` via Graphite (sibling of the
  `devex-663-...` frontend branch).
- Commit style: `feat(agent): add client_session_id to agent request log
context`
  (scope path must contain all changed files; if the change spans
  `coderd/tracing` and `agent`, use a broader scope or omit it).
- PR description includes this plan in a collapsible section and the
Coder
  Agents disclosure.

## Open questions / risks

1. ~~**Which base?**~~ Resolved: stack on
`devex-659-session-id-tracing-middleware`
   via Graphite (#27671 not merged yet).
2. **Agent test harness.** Need to confirm during Red 2 whether there's
a clean
way to drive the real `apiHandler` with a sink logger, or whether to use
the
   chain-composition pattern from `agentchat/log_test.go`.
3. **No live source of agent baggage yet for the web terminal.** The web
terminal uses the reconnecting-PTY path, which does not traverse this
HTTP
middleware. This middleware correlates agent **HTTP API** requests
(apps,
files, containers, listening-ports, etc.) whose clients send
`client_session_id`
baggage per RFC #3. Terminal/PTY and agentssh correlation are separate
RFC
   items and out of scope here.

</details>

_Opened by Coder Agents on behalf of @aqandrew._
2026-08-27 13:12:41 -07:00
Andrew Aquino 197c814070 feat: correlate web terminal sessions by client_session_id (#27677)
## What

Implements the web terminal client half of the
[Connection log collection and correlation
RFC](https://www.notion.so/coderhq/Connection-log-collection-and-correlation-36ed579be5928025a56cd11fe58661fb)
(`DEVEX-663`). Generates a per-session correlation ID and attaches it to
the
web terminal's requests and client logs so a single session can be
traced end
to end.

> Stacked on #27671 (`DEVEX-659`, the coderd tracing middleware that
reads the
> `client_session_id` baggage). Review/merge that first.

## Changes

**Session ID**
- 16-byte value encoded as a 32-character hex string, per RFC
requirement 1.
Generated with the `generateConnectionSessionId` function added in
#27935
- Minted once per web terminal session: on `TerminalPage` load and on
`AgentsPage` terminal panel mount. Unlike the reconnection token, it is
**not**
persisted in the URL, so a reload (or a new tab) is a new session,
matching the
  RFC's session definition.

**Propagation**
- **HTTP API request:** the reconnecting-pty signed-token request
carries the ID
via W3C baggage (`baggage: client_session_id=<hex>`), which the
DEVEX-659 middleware
  reads.
- **PTY WebSocket:** browsers cannot set the `baggage` header on a
WebSocket
handshake (the codebase already works around this for the session
token), so
the ID is sent as a `client_session_id` query parameter instead. The
reconnecting-pty
WebSocket handler reads and validates it and attaches it to the request
and PTY
  logs.

**Client logs**
- The terminal's connection-error `console.error` logs now include
`client_session_id`.

**Telemetry:** the web terminal emits none today, so there is nothing to
tag
(confirmed with the issue reporter).

## Testing

- `site`: unit tests for `generateSessionId` (format + uniqueness) and
`terminalWebsocketUrl` (query param). Updated `TerminalPage.test.tsx`
(mocks the
generator to a fixed ID and asserts the WebSocket URL includes
`client_session_id`).
  `tsc`, Biome, and the React Compiler check pass.
- `coderd`: `go test ./coderd/tracing/...` and `go vet
./coderd/workspaceapps/...`
  pass; new `ValidSessionID` export reused by the PTY handler.

<details>
<summary>Design notes / decision log</summary>

- **Session vs reconnection token.** The existing `reconnect` token is
deliberately persisted in the URL to survive reloads.
`client_session_id` is the
opposite: a fresh value per page load, matching the RFC (a reload is a
new
  session). They are separate identifiers.
- **Per-request baggage, not a global axios default.** The frontend
axios
instance is a singleton shared by the whole app; a global `baggage`
default
would tag unrelated requests. The header is attached only to the
terminal's
  signed-token request.
- **WebSocket uses a query param.** Browser `WebSocket` cannot send
custom
headers, so baggage is impossible on the PTY handshake. The
`client_session_id` query
  parameter is the counterpart, read server-side in `workspaceAgentPTY`.
- **Server-side scope.** Reading the query param is localized to the web
terminal's own WebSocket handler rather than broadening the shared
tracing
  middleware to trust query params on every route.
- **Validation.** Both the baggage and query-param paths validate the
value as a
32-char hex string (`tracing.ValidSessionID`) before logging, to avoid
logging
  arbitrary client-controlled input.
- **AgentsPage compiler constraint.** `AgentsPage` is React Compiler
optimized
(no `useMemo`/`useCallback`), so the panel mints its ID with `useState`
lazy
  init instead.
- **Out of scope (other RFC tickets):** agent-side middleware,
`connection_logs`
columns, Tailnet state-change logging, and the CLI
`CODER_TRACE_SESSION_ID`
  env var.

</details>

---

_Opened by Coder Agents on behalf of @aqandrew._

---------

Co-authored-by: Danielle Maywood <danielle@themaywoods.com>
2026-08-27 12:55:33 -07:00
TJ e5fa18b58e fix(site/src/pages/AgentsPage): adjust context usage indicator error states so it is readable (#28719)
Restyles the chat composer's context usage indicator and reworks its
attention states, per design review.

## Changes

- Ring: 20px diameter, 1.5px stroke, `stroke-border`
(`--border-default`) track, full-opacity `text-content-secondary`
progress arc. The arc always encodes real usage; usage tone shifts at
>=85% (warning) and >=95% (destructive).
- Attention states replace the old corner triangle badge with a centered
exclamation glyph (design-provided SVG) that recolors the whole
indicator:
  - red (`content-destructive`): context snapshot error
- orange (`content-warning`): drifted pin, or a pinned resource that
failed to load (previously invisible outside the popover)
- The glyph is drawn on a ring-sized SVG canvas with a computed center
transform so it cannot drift off center at fractional layout offsets.
- Accessible name announces every applicable state, composed (not
mutually exclusive): `Context error.`, `Context changed.`, `Some context
resources failed to load.`
- Composer balance: mic icon thinned to `strokeWidth={1.5}`, indicator
pulled left to equalize the visual gaps between mic / ring / send, and
both popover trigger wrappers are flex so the inline-flex button no
longer sits on the text baseline (which pushed the ring ~1px above
center).
- Popover refresh button downsized to `size="xs"`.

## Testing

- Component story tests: 8/8 pass; stories assert the announced state
for error, dirty, and resource-issue rings plus the negative case, via
`toHaveAccessibleName`.
- `pnpm check`, `pnpm lint:types` clean.
- Rendered states, vertical centering, and painted-pixel glyph centering
verified via Storybook screenshots and pixel measurement.

<details>
<summary>Implementation notes / decision log</summary>

- Error stays red, drift/resource issues stay orange, preserving the
severity distinction of the old corner badge. The design's `#FDBA74`
maps to `--content-warning` in the dark theme.
- Context state overrides the usage tone on the ring (a dirty pin at 96%
shows orange); reviewed and accepted.
- The arc showing partial usage in attention states is intentional
(Codex suggestion to color the full ring was declined): the glyph and
tone carry the attention signal, the arc keeps encoding usage.
- The exclamation glyph inherits color via `currentColor`; its design
export carried trailing viewBox whitespace, so it is re-centered
mathematically on the ring canvas.
- Ring sized against the mic glyph's visual height (~16px of its 18px
box) plus 2px all around; mic/ring line weights tuned as a pair (mic
~1.1px effective, ring 1.5px).
- Pre-existing behavior left as-is: the ring renders on an open chat
even before any usage or context exists, and high-usage rings (>=85%)
share the warning color without the glyph. Reviewed and deferred.

</details>

---

*This PR was generated by Coder Agents on behalf of @tracyjohnsonux.*
2026-08-27 12:10:58 -07:00
McKayla はな d7bf8abeb3 chore: remove orphaned components (#28690) 2026-08-27 11:33:44 -06:00
Nick Vigilante bc77c40296 docs: add front-matter titles to mechanical and no-H1 pages (Phase 3) (#28030)
## Summary

**Batch A of Phase 3** of the H1 → front-matter migration (`DOCS-484`;
parent `DOCS-477`). Adds a front-matter `title` to every navigable docs
page whose title can be migrated **mechanically**, with no editorial
judgment.

This is the content step that Phase 1 (renderers prefer front-matter
title, `DOCS-482`) and Phase 2 (tooling + generators front-matter-aware,
`DOCS-483`) unblocked. Both are merged; coder.com #964/#974 are merged
and live.

**Rendered no-op.** The renderers already resolve the page title from
the manifest and hide the leading body H1 (Phase 1), so no page changes
visually. This just moves the title into front matter where Fumadocs and
the migrated tooling can read it.

## What's in this batch

Dry-run on `main` (464 navigable pages) splits into:

| category | count | this PR |
|----------|-------|---------|
| already has front-matter title (Reference, from Phase 2 generators) |
196 | skipped (idempotent) |
| **mechanical** — leading body H1 equals the manifest label | 138 | 
add front-matter `title`, drop the duplicate H1 |
| **no body H1** — renders under the manifest label only | 4 |  add
front-matter `title` only |
| **mismatch** — body H1 differs from the manifest label | 126 | ⏭️
deferred (needs an editorial decision, see below) |

142 files changed, all under `docs/`.

## Deliberately out of scope: the 126 mismatches

Pages where the body H1 is richer than the short sidebar label (e.g.
label **Modules** / H1 *Contributing modules*, label **Install** / H1
*Installing Coder*) need a canonical-title decision, not a script. A few
even look like the body H1 is the redundant one (`install/cli.md` and
`install/index.md` both carry the H1 *Installing Coder*). These will
land in follow-up batches **by nav section** once the policy is set, so
each gets real review.

## Verification

AI was the primary author of this PR (see disclosure below); per the [AI
Contribution
Guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING)
here is the manual verification.

- Every added front-matter block parses as YAML and its `title`
round-trips to the manifest label (checked programmatically across all
142 files).
- Every removed line is a leading `# H1` that equalled the manifest
title; no body prose was reflowed. Front matter is correctly hoisted
above pre-existing `<!-- markdownlint-disable -->` comments on the two
pages that had them.
- `pnpm check-docs` (markdownlint-cli2 + table formatter) passes on the
changed set: `Summary: 0 error(s)`. `MD041` stays off (re-enabled in
Phase 4); `MD025` is not tripped because the duplicate body H1s are
removed.

```
$ pnpm exec markdownlint-cli2 $(git diff --name-only origin/main)
Linting: 142 file(s)
Summary: 0 error(s)
```

Linear: DOCS-484

> This PR was created with AI assistance (Coder Agents).
2026-08-27 12:34:13 -04:00
Nick Vigilante 47a77016c4 feat(docs/.style): enable Coder.SelectClick, use "select" over "click" (#28599)
Adds a Vale rule enforcing the existing "select, not click" style
guidance: "click" assumes the reader has a mouse, and touch, keyboard,
and assistive-technology users don't click.

`Coder.SelectClick` (warning severity) flags
`click`/`clicks`/`clicked`/`clicking` in prose, exempting:

- Literal mouse-button phrasing (`left-click`, `right-click`,
`middle-click`, `mouse click`, and combinations) — these name an actual
mouse action with no device-agnostic equivalent.
- `one-click`/`single-click` as a compound feature descriptor
("one-click install").

Reworded every existing instructional "click" in `docs/` to the matching
`select` form (`select`/`selects`/`selections`/`selected`/`selecting`)
so the rule ships with a clean baseline, per this repo's Vale-rule
doctrine.

Left alone, deliberately: two generated API reference pages
(`docs/reference/api/enterprise.md`, `schemas.md`) use "click" as an
analytics-event noun, tied to a Go source doc comment and an operation
ID (`report-a-premium-paywall-click`). Fixing those means editing
generated code and an API operation ID, which is out of scope for a
docs-style PR — see the rule's own comments for the reasoning. That
leaves 2 non-zero-baseline findings for this rule.

---
🤖 Built with AI assistance.

---------

Co-authored-by: Ian Evans <5760366+ianjevans@users.noreply.github.com>
2026-08-27 16:24:06 +00:00
Hank Hwang c9dedc65e6 fix: model nullable UUID fields as uuid strings in Swagger (#28684)
## TLDR
Updated `.swaggo` file to include new rule to replace all
`uuid.NullUUID` with a `type: string` and `format: uuid` in
`swagger.json`. One such property example is `context_file_agent_id`

## What

Nullable UUID fields (`uuid.NullUUID`) were rendered in the
Swagger/OpenAPI spec as nested objects (with `UUID` and `Valid`
sub-fields) instead of as a plain UUID string. This adds a swaggo
`replace` directive so `uuid.NullUUID` is modeled as a `string` with
uuid format, matching how these fields serialize over the wire and how
other UUID fields are already documented.

## Changes

- `.swaggo`: add `replace github.com/google/uuid.NullUUID string` (with
an explanatory comment), alongside the existing `NullTime` replacement.
- `coderd/apidoc/docs.go` and `coderd/apidoc/swagger.json`: regenerated
output reflecting the new modeling.

## Testing

- Generated docs via the standard `make gen` flow; `docs.go` and
`swagger.json` are the regenerated artifacts.

---

> This PR was created by Coder Agents on behalf of @hwang251.
2026-08-27 09:13:33 -07:00
Paweł Banaszewski b6505fe921 feat: add service tier to TokenUsage recording (#28709)
Adds `service_tier` information to token usage recordings made by OpenAI
and Anthropic interceptors.
2026-08-27 15:57:16 +00:00
Samuel Volin 0530785d14 feat: add userdropdown nav entry link to premium trial (#28610)
* adds a navigation entry to the userdropdown view for linking to the
premium trial page
* when a trial is active, it shows a countdown of days
* when no trial / license established, hidden from view

No license , no trial:
<img width="1169" height="663" alt="Screenshot 2026-08-25 at 4 12 23 PM"
src="https://github.com/user-attachments/assets/4a6b9511-48dc-45dc-a74c-214f2457b76d"
/>

trial countdown:
<img width="1174" height="647" alt="Screenshot 2026-08-25 at 4 07 49 PM"
src="https://github.com/user-attachments/assets/963b0f0e-17e5-4ddb-9e57-eb0844f66f21"
/>

license in place, not trialing:
<img width="1170" height="673" alt="Screenshot 2026-08-25 at 4 17 41 PM"
src="https://github.com/user-attachments/assets/2d22337b-f641-4355-8358-480be269e311"
/>
2026-08-27 09:55:43 -06:00
Jake Howell c51b03214f fix(site): stop back link overlapping the new workspace form (#28355) 2026-08-27 21:46:42 +07:00
Jaayden Halko c7bdf84798 fix: change the coder agents upgrade button from mailto to contact sales link (#28700)
<img width="1115" height="322" alt="Screenshot 2026-08-27 at 4 26 18 PM"
src="https://github.com/user-attachments/assets/84ca368f-05ed-4155-9b86-2eaaa8c35550"
/>
2026-08-27 21:36:39 +07:00
cdrci debe8e51bb chore: refresh AI model price book (#28711)
## Price book changes

36 models added, 27 models removed, 47 models changed.

<details>
<summary>Added</summary>

- openrouter/deepseek/deepseek-v4-flash-vision-exp
- openrouter/mancer/weaver
- openrouter/meta/muse-spark-1.2-contributor
- openrouter/minimax/minimax-m2.7:free
- openrouter/minimax/minimax-m3:free
- openrouter/mistralai/devstral-2512
- openrouter/mistralai/ministral-8b
- openrouter/qwen/qwen3.8-flash
- openrouter/tencent/hy-mt2-1.8b
- openrouter/tencent/hy-mt2-30b-a3b
- openrouter/tencent/hy-mt2-7b
- openrouter/thinkingmachines/inkling-small:free
- openrouter/thinkingmachines/inkling:free
- openrouter/z-ai/glm-5.3-flash
- vercel/alibaba/qwen3.8-flash
- vercel/deepseek/deepseek-v4-flash-vision-exp
- vercel/google/gemini-3.5-transcribe
- vercel/minimax/minimax-m2.7-free
- vercel/minimax/minimax-m3-free
- vercel/openai/gpt-oss-safeguard-120b
- vercel/spacexai/grok-4.1-fast-non-reasoning
- vercel/spacexai/grok-4.1-fast-reasoning
- vercel/spacexai/grok-4.20-multi-agent
- vercel/spacexai/grok-4.20-multi-agent-beta
- vercel/spacexai/grok-4.20-non-reasoning
- vercel/spacexai/grok-4.20-non-reasoning-beta
- vercel/spacexai/grok-4.20-reasoning
- vercel/spacexai/grok-4.20-reasoning-beta
- vercel/spacexai/grok-4.3
- vercel/spacexai/grok-4.5
- vercel/spacexai/grok-4.6
- vercel/spacexai/grok-build-0.1
- vercel/tencent/hy-mt2-lite
- vercel/tencent/hy-mt2-plus
- vercel/tencent/hy-mt2-pro
- vercel/zai/glm-5.3-flash
</details>

<details>
<summary>Removed</summary>

- openrouter/deepcogito/cogito-v2.1-671b
- openrouter/google/gemma-3n-e4b-it
- openrouter/inclusionai/ling-2.6-1t
- openrouter/inclusionai/ling-2.6-flash
- openrouter/inclusionai/ring-2.6-1t
- openrouter/nvidia/nemotron-3-nano-30b-a3b:free
- openrouter/nvidia/nemotron-nano-12b-v2-vl:free
- openrouter/nvidia/nemotron-nano-9b-v2:free
- openrouter/openai/gpt-oss-20b:free
- openrouter/qwen/qwen-plus-2025-07-28:thinking
- vercel/arcee-ai/trinity-mini
- vercel/mistral/magistral-medium
- vercel/mistral/magistral-small
- vercel/openai/gpt-4o-mini-search-preview
- vercel/openai/o3-deep-research
- vercel/xai/grok-4.1-fast-non-reasoning
- vercel/xai/grok-4.1-fast-reasoning
- vercel/xai/grok-4.20-multi-agent
- vercel/xai/grok-4.20-multi-agent-beta
- vercel/xai/grok-4.20-non-reasoning
- vercel/xai/grok-4.20-non-reasoning-beta
- vercel/xai/grok-4.20-reasoning
- vercel/xai/grok-4.20-reasoning-beta
- vercel/xai/grok-4.3
- vercel/xai/grok-4.5
- vercel/xai/grok-4.6
- vercel/xai/grok-build-0.1
</details>

<details>
<summary>Changed</summary>

- bedrock/global.openai.gpt-5.6-luna
- bedrock/global.openai.gpt-5.6-sol
- bedrock/global.openai.gpt-5.6-terra
- bedrock/openai.gpt-5.6-sol
- copilot/gpt-5.6-sol
- google/gemini-3.6-flash
- google/gemini-flash-latest
- google/gemini-flash-lite-latest
- openai/gpt-5.6
- openai/gpt-5.6-sol
- openrouter/deepseek/deepseek-chat-v3.1
- openrouter/deepseek/deepseek-v3.1-terminus
- openrouter/deepseek/deepseek-v3.2
- openrouter/deepseek/deepseek-v4-flash
- openrouter/deepseek/deepseek-v4-flash-0731
- openrouter/deepseek/deepseek-v4-pro
- openrouter/deepseek/deepseek-v4-pro-0813
- openrouter/meta-llama/llama-3.3-70b-instruct
- openrouter/meta-llama/llama-4-scout
- openrouter/minimax/minimax-m2.5
- openrouter/mistralai/mistral-small-3.2-24b-instruct
- openrouter/moonshotai/kimi-k2.5
- openrouter/moonshotai/kimi-k2.7-code
- openrouter/nvidia/nemotron-3-nano-30b-a3b
- openrouter/openai/gpt-5.6-sol
- openrouter/openai/gpt-5.6-sol-pro
- openrouter/openai/gpt-oss-120b
- openrouter/qwen/qwen2.5-vl-72b-instruct
- openrouter/qwen/qwen3-235b-a22b-2507
- openrouter/qwen/qwen3-30b-a3b
- openrouter/qwen/qwen3-next-80b-a3b-instruct
- openrouter/qwen/qwen3.6-27b
- openrouter/qwen/qwen3.6-35b-a3b
- openrouter/qwen/qwen3.8-27b
- openrouter/z-ai/glm-4.6
- openrouter/z-ai/glm-5.1
- openrouter/z-ai/glm-5.2
- openrouter/~deepseek/deepseek-v4-flash-latest
- openrouter/~moonshotai/kimi-latest
- openrouter/~openai/gpt-latest
- vercel/deepseek/deepseek-v4-flash-0731
- vercel/deepseek/deepseek-v4-pro-0813
- vercel/nvidia/nemotron-3.5-lightning
- vercel/openai/gpt-5.6-sol
- vercel/openai/gpt-5.6-sol-fast
- vercel/openai/gpt-oss-safeguard-20b
- vercel/tencent/hy3
</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/33072075172).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-27 15:03:51 +01:00
Mathias Fredriksson f208fc0c83 refactor: rename edit_files wire type to old_text/new_text (#28467)
The schema has advertised old_text/new_text since #25658, but the
shared wire type still encoded search/replace.

Decode accepts the deprecated keys only when both new fields are
empty, through the pre-rename tags, so old coderd and cached MCP
schemas keep working. Marshal emits both key sets so old agents keep
working after a coderd upgrade. Both shims are removed in the first
release after Coder Agents GA (2026-09).

The Go field rename is a deliberate source break: FileEdit carries
no codersdk stability guarantee.

Fixes CODAGT-523
Refs CODAGT-483
2026-08-27 16:06:21 +03:00
Rowan Smith 4830010c8e chore: update tailscale fork version used by coder to remove hairpin probes (#28682)
Updates the version of tailscale used by Coder to pull in
coder/tailscale/pull/132 in order to remove hairpin probes, a feature
long ago removed from upstream tailscale. Requested by a customer.

https://linear.app/codercom/issue/PLAT-537

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 14:40:37 +02:00
Michael Suchacz 3ce35cdfeb docs(coderd/x/chatd): document the /clear ClearContext transition and endpoint (#28710)
> [!NOTE]
> Xum acted on Mike's behalf in this pull request.
<!-- xum-attribution: model=claude-fable-5 thinking=high -->

Requested follow-up to #24745, which intentionally left `TODO(PR
author)` placeholders in `coderd/x/chatd/ARCHITECTURE.md`. Replaces them
with the actual documentation: the `ClearContext` entry in the HTTP
transition list, the `W --> W` / `E0 --> W` edges in the execution state
diagram, and the `POST /api/experimental/chats/{chat}/clear` endpoint
section.

Documentation only. Every claim was verified against the merged
implementation (`chatstate/transition.go`, `chatstate/transitions.go`,
`chatd.go`, `message_conversion.go`, `exp_chats.go`).
2026-08-27 14:21:13 +02:00
Susana Ferreira 849543d8de fix: enable Copilot HTTP transport fallback (#28494)
## Description

Copilot provider model metadata returned by `/models` can advertise
WebSocket support (for example, `ws:/responses`), so clients attempt
WebSocket inference even though AI Gateway supports HTTP transport only.
The Copilot CLI and Copilot in VS Code use different retry mechanisms,
and only the CLI fallback worked previously. See the investigation
details below for more information.

Authenticate every configured Copilot provider request with the Coder
token validated during CONNECT while preserving Copilot provider
credentials and preventing Coder credentials from being forwarded
upstream. Reject unsupported WebSocket upgrades on bridged inference
routes with `501 Not Implemented` so clients can fall back to HTTP.

## Changes

- Add the CONNECT-authenticated Coder token to every configured Copilot
provider request.
- Preserve Copilot provider credentials and strip Coder credentials from
provider credential headers.
- Pass through Copilot `/_ping` connectivity checks and `/auto`
model-selection requests.
- Return `501 Not Implemented` for WebSocket upgrades on bridged
inference routes.

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

These logs show the behavior before this change using `GPT-5.3-Codex`,
whose model metadata advertises `ws:/responses`.

**Copilot CLI**

1. The CLI attempts a WebSocket upgrade. AI Gateway treats the empty
`GET /responses` body as an inference request and returns 500.
```
2026-08-26 11:08:28.883 [info]  api: 2026-08-26 11:08:28.883 [warn]  coderd.ai-gateway.pool: failed to create interceptor  request_id=dcfc945c-fc10-4b8f-abc8-ae31aa21ad44  aibridgeproxy_id=6f651c82-68b2-45ee-b5e6-dc75445e2ef6  path=/copilot/responses ...
2026-08-26 11:08:28.883 [info]  api: error= unmarshal request body:
2026-08-26 11:08:28.883 [info]  api: github.com/coder/coder/v2/aibridge/provider.(*Copilot).CreateInterceptor
2026-08-26 11:08:28.883 [info]  api: /home/coder/coder/aibridge/provider/copilot.go:179
2026-08-26 11:08:28.883 [info]  api: - empty request body:
2026-08-26 11:08:28.883 [info]  api: github.com/coder/coder/v2/aibridge/intercept/responses.NewRequestPayload
2026-08-26 11:08:28.884 [info]  api: /home/coder/coder/aibridge/intercept/responses/reqpayload.go:48
2026-08-26 11:08:28.884 [info]  api: 2026-08-26 11:08:28.883 [warn]  coderd: GET  user_agent="copilot/1.0.80 (client/github/cli linux v24.18.1) term/unknown"  host=127.0.0.1:3000  received_host=127.0.0.1:3000  path=/api/v2/ai-gateway/copilot/responses  proto=HTTP/1.1  remote_addr=127.0.0.1  start="2026-08-26 11:08:28.880115732 +0000 UTC m=+402.868920240"  response_body="failed to create \"/copilot/responses\" interceptor\n"  took=3.644201ms  status_code=500  latency_ms=3  params_*=copilot/responses  request_id=dcfc945c-fc10-4b8f-abc8-ae31aa21ad44
2026-08-26 11:08:28.884 [info]  api: 2026-08-26 11:08:28.884 [erro]  coderd.aibridgeproxyd: received error response from aibridged  connect_id=17dda9cb-ae6a-416b-a812-b3b831755a2b  request_id=6f651c82-68b2-45ee-b5e6-dc75445e2ef6  provider=copilot  status=500  response_body="failed to create \"/copilot/responses\" interceptor\n"
```

2. The CLI immediately retries inference with `POST /responses`, which
succeeds.
```
2026-08-26 11:08:28.886 [info]  api: 2026-08-26 11:08:28.886 [debu]  coderd.aibridgeproxyd: request CONNECT authenticated  connect_id=7809a618-2c24-4f5f-a2f2-97af934b8c3e  host=api.business.githubcopilot.com:443  provider=copilot
2026-08-26 11:08:28.890 [info]  api: 2026-08-26 11:08:28.890 [info]  coderd.aibridgeproxyd: routing MITM request to AI Gateway  connect_id=7809a618-2c24-4f5f-a2f2-97af934b8c3e  request_id=eee98705-ffc2-4a00-8201-e316f997e19d  host=api.business.githubcopilot.com  method=POST  path=/responses  provider=copilot  gateway_target_url=http://127.0.0.1:3000/api/v2/ai-gateway/copilot/responses
2026-08-26 11:08:31.429 [info]  api: 2026-08-26 11:08:31.428 [debu]  coderd.aibridgeproxyd: received response from aibridged  connect_id=7809a618-2c24-4f5f-a2f2-97af934b8c3e  request_id=eee98705-ffc2-4a00-8201-e316f997e19d  provider=copilot  status=200
```

**Copilot in VS Code**
1. VS Code attempts a WebSocket upgrade. AI Gateway treats the empty
`GET /responses` body as an inference request and returns 500.
```
2026-08-26 11:09:20.736 [info]  api: 2026-08-26 11:09:20.736 [warn]  coderd.ai-gateway.pool: failed to create interceptor  request_id=4aab1c00-fb45-4533-b8c4-bcb150fd12f3  aibridgeproxy_id=d6867370-cefa-49bb-85ca-98dd4258e74c  path=/copilot/responses ...
2026-08-26 11:09:20.736 [info]  api: error= unmarshal request body:
2026-08-26 11:09:20.736 [info]  api: github.com/coder/coder/v2/aibridge/provider.(*Copilot).CreateInterceptor
2026-08-26 11:09:20.736 [info]  api: /home/coder/coder/aibridge/provider/copilot.go:179
2026-08-26 11:09:20.736 [info]  api: - empty request body:
2026-08-26 11:09:20.736 [info]  api: github.com/coder/coder/v2/aibridge/intercept/responses.NewRequestPayload
2026-08-26 11:09:20.736 [info]  api: /home/coder/coder/aibridge/intercept/responses/reqpayload.go:48
2026-08-26 11:09:20.736 [info]  api: 2026-08-26 11:09:20.736 [warn]  coderd: GET  user_agent=node  host=127.0.0.1:3000  received_host=127.0.0.1:3000  path=/api/v2/ai-gateway/copilot/responses  proto=HTTP/1.1  remote_addr=127.0.0.1  start="2026-08-26 11:09:20.732174884 +0000 UTC m=+454.720979381"  response_body="failed to create \"/copilot/responses\" interceptor\n"  took=4.225651ms  status_code=500  latency_ms=4  params_*=copilot/responses  request_id=4aab1c00-fb45-4533-b8c4-bcb150fd12f3
2026-08-26 11:09:20.737 [info]  api: 2026-08-26 11:09:20.736 [erro]  coderd.aibridgeproxyd: received error response from aibridged  connect_id=072a6497-7072-42d8-b52d-a3afe17d5a75  request_id=d6867370-cefa-49bb-85ca-98dd4258e74c  provider=copilot  status=500  response_body="failed to create \"/copilot/responses\" interceptor\n"
```

2. VS Code checks connectivity with `GET /_ping` before retrying
inference over HTTP. AI Gateway rejects the check because the proxy did
not forward the Coder token from the authenticated CONNECT session.
```
2026-08-26 11:09:21.813 [info]  api: 2026-08-26 11:09:21.812 [info]  coderd.aibridgeproxyd: routing MITM request to AI Gateway  connect_id=2dd5a340-7ad9-43a6-b99d-fd34b838e6a2  request_id=8fda1e3e-4ca8-4908-a2ef-23859743dc38  host=api.business.githubcopilot.com  method=GET  path=/_ping  provider=copilot  gateway_target_url=http://127.0.0.1:3000/api/v2/ai-gateway/copilot/_ping
2026-08-26 11:09:21.813 [info]  api: 2026-08-26 11:09:21.813 [warn]  coderd.ai-gateway: no auth key provided  method=GET  path=/copilot/_ping  aibridgeproxy_id=8fda1e3e-4ca8-4908-a2ef-23859743dc38  request_id=057e3288-afa9-4f8d-9698-e4a3b206b199  aibridgeproxy_id=8fda1e3e-4ca8-4908-a2ef-23859743dc38
2026-08-26 11:09:21.814 [info]  api: 2026-08-26 11:09:21.814 [warn]  coderd.aibridgeproxyd: received error response from aibridged  connect_id=2dd5a340-7ad9-43a6-b99d-fd34b838e6a2  request_id=8fda1e3e-4ca8-4908-a2ef-23859743dc38  provider=copilot  status=400  response_body="no authentication key provided\n"
```
</details>

Closes
https://linear.app/codercom/issue/AIGOV-629/ai-gateway-lacks-support-for-new-copilot-endpoints-behind-mitm

> [!NOTE]
> Generated by Coder Agents on behalf of @ssncferreira.
2026-08-27 13:10:34 +01:00
Michael Suchacz 9c42b14857 feat: add /clear chat context command (#24745)
> [!NOTE]
> Xum acted on Mike's behalf in this pull request.
<!-- xum-attribution: model=claude-fable-5 thinking=high -->

Adds a `/clear` slash command to Agents chats that resets the model
context without deleting anything: the transcript stays visible, and the
next prompt runs as if the chat were brand new. It mirrors `/compact`'s
UX (slash menu entry, exact-text interception, dedicated endpoint,
synthetic tool block in the transcript) but generates no summary and
makes no model call.

## How it works

`POST /api/experimental/chats/{chat}/clear` commits synchronously inside
the API transaction through a new `chatstate` transition,
`ClearContext`, allowed from `waiting` and from `error` without queued
messages (`W -> W`, `E0 -> W`). No worker round-trip, no status flicker
to `running`, no migration. Clearing an errored chat clears
`last_error`, and any pending manual compaction request is dropped
rather than left to run against the cleared context.

The endpoint persists the same compressed boundary shape `/compact`
uses: a hidden model-only sentinel row, a visible synthetic
`chat_cleared` tool call, and its result. Prompt assembly already
selects the latest compressed model-only row as the context cutoff, so
the query is unchanged. Boundary detection is generalized to recognize
both `chat_summarized` and `chat_cleared`, so `/compact` after `/clear`
(and the reverse) never reach across the newer boundary, and
auto-compaction does not fire off stale pre-clear usage.

Busy chats, errored chats with queued messages, and clears with no
conversation after the latest boundary return 409. Archived chats are
rejected. The endpoint is owner-only for symmetry with `/compact`.

## Frontend

`/clear` joins the slash command menu, and the previously compact-only
send interception becomes a small built-in command dispatch shared by
both commands, so a personal or workspace skill named `clear` still
takes precedence. Because the clear commits synchronously, the UI keeps
the chat in `waiting`, refetches on success, and surfaces conflicts with
the standard error toast. The transcript renders a "Context cleared"
card via a new `ChatClearedTool` renderer.

## Docs

The user-facing agents architecture page documents `/clear`.
`coderd/x/chatd/ARCHITECTURE.md` carries TODO notes in the affected
sections for the PR author to write.

## Tests

- `chatstate`: transition matrix entries plus dedicated `ClearContext`
coverage (error-state entry, queued-message rejection, boundary row
requirement).
- `chatd`: end-to-end manual clear (fresh next prompt, error recovery,
boundary interplay with compaction, stale-usage auto-compaction
regression, busy rejection) plus boundary and message-builder units.
- `coderd`: HTTP authorization and error mapping for the endpoint.
- Frontend: slash command availability and skill collision units,
stories for the submit path, the conflict toast, and the cleared-card
renderer.
2026-08-27 13:48:08 +02:00
Paweł Banaszewski 3ca2ba485e fix: add cache write accounting to OpenAI interceptors (#28567)
Adds cache write token accounting to OpenAI interceptors.
2026-08-27 12:49:10 +02:00
Ethan f5c7c63970 fix(site): let organization admins manage agent templates (#28636)
This is a frontend only fix.

## Before

Organization administrators could update Coder Agents access for their
templates through the API. However, the AI settings page required
deployment configuration permission, so these administrators could not
find or open the Templates page.

## After

Organization administrators can open the Templates page from AI settings
without deployment-wide access. The page shows only templates from
organizations where the user can update templates, while deployment
administrator behavior remains unchanged.

This pull request was generated by Coder Agents.
2026-08-27 11:23:52 +01:00
blinkagent[bot] 3797c4b785 revert: restore m-block mark for Xum icon (#28693) 2026-08-27 12:02:37 +02:00
Michael Suchacz fd35b59ecd fix(site): remove unused MCPConnectSummaryViewModel export (#28701) 2026-08-27 11:54:40 +02:00
Ethan 0f5c7b3154 refactor!: remove default organization model routes (#28632)
> [!IMPORTANT]
> Most of the added lines in this PR are generated API reference content
for publishing the organization-scoped `GET` and `POST
/api/v2/organizations/{organization}/chats/models` replacements. There
is no matching generated-doc deletion because the three removed
default-organization endpoints are experimental and are not present in
the generated public API reference on the current base. The removed
paths remain only as explicit 404 reservations, with no
default-organization shim or functional handler.

## Summary

Remove the unused default-organization chat model collection routes:

```text
GET  /api/experimental/chats/models
GET  /api/experimental/chats/model-configs
POST /api/experimental/chats/model-configs
```

Use the organization-scoped collection instead:

```text
GET  /api/v2/organizations/{organization}/chats/models
POST /api/v2/organizations/{organization}/chats/models
```

## Context

The intention of PR #28440 was to consolidate model availability into
the organization-scoped models collection for CODAGT-898 and remove the
superseded collection routes. That PR removed
`/organizations/{organization}/chats/models/available`, but it missed
these older default-organization routes and explicitly retained one of
them. This PR completes the intended #28440 cutover. The repository has
no SDK, CLI, or frontend consumer for the removed routes.

This change also prevents PR #28496 from promoting the missed
default-organization `/chats/models` route into the stable `/api/v2`
API.

This change is separate from the one-release `/api/experimental`
compatibility window in CODAGT-921. That window preserves experimental
versions of the intended stable API. It does not require compatibility
routes for unused deployment-scoped endpoints.

Refs CODAGT-898.

## Breaking change

Clients that call the removed routes must use the organization-scoped
model collection and provide an organization.

## Changelog

Remove unused default-organization Coder Agents model API routes. API
clients must use the organization-scoped chat models collection.

> [!NOTE]
> Coder Agents generated this pull request on Ethan Dickson's behalf.
2026-08-27 10:49:14 +01:00
Ethan d0e22343f0 fix: make chat model sharing work for sharers without directory access (#28542)
## Problem

This is a bug fix. Chat model sharing UAT (main @ 3c32408) found two
bugs when a delegated "Model Sharer" role holds only
`chat_model_config:read` + `chat_model_config:share`:

This state is reachable entirely through the UI in a normal
least-privilege setup:

1. An org admin restricts **Organization Settings → Workspace Sharing**
to `none` or `service_accounts`.
2. Under **Organization → Roles**, they create a custom role with only
the advanced permissions `chat_model_config:read` and
`chat_model_config:share`.
3. Under **Organization → Members**, they assign that role to the person
responsible for managing model access.
4. An admin creates a chat model and may initially share it with a
group.
5. The delegated sharer signs in, opens **AI Settings → Models → [model]
→ Share model**, where the failures below occur.

- **UAT-05 / UAT-08:** with workspace sharing set to `none`, the sharing
dialog errored out entirely ("Resource not found or you do not have
access to this resource"). The sharer couldn't view, add, or even remove
grants.
- **UAT-04:** with workspace sharing set to `service_accounts`, existing
group grants rendered as raw UUIDs, and groups didn't appear in the
add-principal search.

Both have the same cause. The ACL GET returned bare UUID→role maps, so
to display names (and to power its add-principal search) the dialog
called the generic org directory APIs: list organization members and
list groups. Those APIs require `organization_member:read` and
`group:read`, which the sharer role doesn't have. Ordinary members only
get them as a side effect of a *different* feature:
`OrgMemberPermissions` grants member read when the org's workspace
sharing mode isn't `none` (so users can pick coworkers to share a
workspace with), and group read only when it's `everyone`. The chat
dialog was silently borrowing those workspace-sharing permissions, so it
only worked when that unrelated org setting happened to grant them:

- `none`: no member read → dbauthz rejects the member listing → the
dialog's query chain 404s and everything blanks (UAT-05/08).
- `service_accounts`: member read but no group read → the group listing
silently filters to empty → group grants can't be resolved past their
UUIDs (UAT-04).

In short: an org setting about who can share workspaces was deciding
whether a delegated sharer could manage a chat model ACL.

## Fix

Stop borrowing directory permissions entirely; authorize everything
through the model's own share permission, using the two patterns the
repo already has for ACL editors:

- **Hydrate the ACL GET** to return resolved users and groups instead of
UUID maps, like `workspaceACL` and the per-chat ACL already do. Fixes
the raw UUIDs.
- **Add `GET .../acl/available`** returning assignable org members and
groups for the autocomplete, modeled directly on
`templateAvailablePermissions` (same query semantics, same
share-gate-then-system-context lookup that every sibling ACL endpoint
uses). Fixes candidate discovery. The new endpoint exists only because
the dialog has no permission-safe way to enumerate principals today; it
is plumbing for the fix, not a new feature.

This is exactly how template sharing already solves the same problem:
`templateACL` returns hydrated principals, and
`templateAvailablePermissions` gates on the template then does the
lookups under a system context because, per its own comment, "the caller
might not have permission to read all users". We differ from the
template version only where newer conventions exist:

| | Template | This PR |
|---|---|---|
| Gate | `ActionUpdate` on the template (predates `share`) |
`ActionShare` on the model, matching the workspace/chat/MCP ACL
endpoints |
| User candidates | Site-wide `GetUsers` | Non-system members of the
model's org only |
| Group hydration | Full member rosters | Member counts, batched in one
query |

The dialog now runs off the hydrated ACL plus the new endpoint, so all
three sharing modes take the same code path, and a discovery failure no
longer blanks existing grants. `OrgMemberPermissions`, the directory
APIs, and the PATCH (still a sparse UUID→role delta) are untouched.

## Diff breakdown

+1274/−191, but only about a third is product code:

- **Product (~450):** the two endpoints and hydration helpers
(`exp_chats_model_acl.go`, ~240), the dialog rework plus a new colocated
autocomplete component (~200), and SDK/site API plumbing (~60).
- **Tests (~625):** backend tests updated to hydrated shapes plus new
tests that reproduce the two failing UAT sharing modes end-to-end
(+418), rewritten and new story play tests (+198), and query/mock
updates.
- **Generated (~200):** swagger, apidocs, `typesGenerated.ts`, API
reference docs.

Depends on #28498 (stacked on the `/api/v2` chat API promotion; the new
endpoint is registered under both prefixes like its siblings).

---------

Co-authored-by: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com>
2026-08-27 10:49:13 +01:00
christin 26b9c8764f fix(site/src/pages): normalize chevron sizing in settings tables (#28457)
## Summary

The row-navigation `>` chevron in the settings tables was oversized
relative to the rest of the row. It used the class `size-icon-md`, which
is **not defined** in the Tailwind theme (`site/tailwind.config.js` only
defines `icon-xs`, `icon-sm`, `icon-lg`), so no size class was generated
and the icon fell back to Lucide's default 24px.

This replaces it with `size-icon-sm` (1.125rem) — the same chevron
sizing used for AI sessions (`ListSessionsRow.tsx`) — and applies it
consistently across the affected settings tables:

- AI Providers (`ProviderRow.tsx`)
- AI Models (`ModelRow.tsx`)
- MCP Servers (`MCPServerRow.tsx`, previously `size-5`)
- OAuth2 Apps (`OAuth2AppsSettingsPageView.tsx`)

The provisioner settings rows use a distinct rotating expand/collapse
chevron and are intentionally left out of scope.

Resolves ENG-3266.

## Testing

- `biome check` passes on all changed files.
- Change is CSS-class only.

---

_This PR was generated by Coder Agents on behalf of @chrifro._
2026-08-27 11:18:47 +02:00
Michael Suchacz 5cc2f2c103 feat: add MCP connect and generation-prep observability (#28402)
## Stack context

Follow-up to the MCP connect-stall incident (chats on dev.coder.com
stalled ~15 min/turn while a configured MCP server black-holed
requests). The stack: #28400 makes the 10s MCP connect budget actually
hold; this PR makes connect and preparation slowness observable so the
next incident shows up in logs and the chat debug UI instead of as a
silent timeline gap.

## Why

During the incident, each stalled turn looked like a silent gap before
the first debug step: no per-server connect durations anywhere, no
warning that preparation was slow, and the only signal was a generic
"connection failure" log without timing.

## Changes

- `mcpclient.ConnectAll` now returns per-server `ConnectSummary` values
(config ID, slug, outcome `connected`/`timeout`/`error`/`no_tools`,
duration, tool count, redacted error). Failure logs include the
duration, and successful connects slower than 5s log a `slow MCP server
connect` warning.
- Connect summaries are seeded into the chat debug run summary under an
`mcp_connect` key (seeded keys survive `FinalizeRun`'s aggregation), and
the debug panel's run card renders them as a per-server list with
outcome badges, durations, tool counts, and errors.
- `prepareGeneration` logs a `slow generation preparation` warning when
a turn's preparation exceeds 30s.

## Tests

- Go: connect summary assertions added to the budget tests
(outcome/duration/tool count for black-holed vs healthy servers); `go
test ./coderd/x/chatd/...` green.
- Frontend: new `coerceRunSummary` unit tests for `mcp_connect` coercion
(malformed entries dropped, non-array ignored) and a
`RunWithMCPConnectSummary` story whose play function expands the run and
asserts the rendered outcomes, durations, tool counts, and error text.
`pnpm check`, `lint:types`, unit and storybook projects green.

> 🤖 Mux authored this PR on Mike's behalf.

<!-- mux-attribution: model=anthropic:claude-opus-4-6 thinking=high -->
2026-08-27 10:22:33 +02:00
TJ 10b42756e2 revert(site): revert model selector 8-character floor (#28487) (#28685)
Reverts #28487 to remove the 8-character minimum width on model and
workspace pill selectors.

This was causing:
- Unwanted expansion of short labels (e.g., 'Fable 5') with dead space
- Clamping of longer labels even when space was available

Preparing for a comprehensive fix that addresses both concerns.

> Created by Coder Agents on behalf of @tracyjohnsonux.
2026-08-26 23:49:03 -07:00
Jake Howell 41794f9fc1 fix: remove unneccessary knip ignores (#28651)
I think this was a leftover from a stack of PRs in #23921. I've gone
ahead and removed these and resolved some unused imports 🙂
2026-08-27 02:40:25 +00:00
Jake Howell 496c5e36a6 fix(site): support Vite native config loading (#28616) 2026-08-27 09:25:26 +07:00
Jeremy Ruppel 205735fcdf feat(site): enable oxlint prefer-const rule (#28675)
## What

Chips one rule off the oxlint migration backlog: **`prefer-const`**
(Biome's `useConst`) is now enforced by oxlint.

Stacked on top of #28674.

## How

- Enabled `prefer-const` with options that match Biome's behavior: `{
"destructuring": "all", "ignoreReadBeforeAssign": true }`.
`destructuring: "all"` avoids flagging a `let { text, type }` where only
one member is reassignable (Biome's semantics).
- One genuine fix: combined a `let trackedSync; trackedSync = …`
declaration-then-assignment into a single `const` in
`AgentChatPage.tsx`. The `.finally()` callback references `trackedSync`,
which is safe because it runs after initialization.

## Verification

- `pnpm run lint:oxlint` → 0 warnings, 0 errors (127 rules).
- `biome lint --error-on-warnings` on the changed file → clean.
- `tsc -p .` → no new type errors.

---

🤖 Generated with Coder Agents.

---------

Co-authored-by: Coder Agents <noreply@coder.com>
2026-08-26 20:33:51 -04:00
Jeremy Ruppel 894538879e feat(site): add oxlint alongside biome for js/ts linting (#28674)
## What

Adds [oxlint](https://oxc.rs/docs/guide/usage/linter.html) as an
**additional** JS/TS lint check in `site/`, running alongside Biome.
Biome stays the authoritative JS/TS linter, the formatter, and the
CSS/JSON linter — this change removes none of its coverage.

oxlint enables only the rules whose behavior already matches Biome on
the current codebase (126 rules, verified 0 findings). Rules whose
oxlint implementation is stricter than Biome's equivalent are kept in
the config but disabled, forming a documented migration backlog
annotated with the Biome rule each maps to and an approximate effort
level (`small`/`medium`/`large`/`xl`).

## Why this shape (not a straight swap)

oxlint's rule *names* map to Biome's, but the *implementations* are
frequently stricter. A faithful full oxlint config produced ~190
findings across ~37 rules on a tree that Biome lints completely clean
(verified per-rule with `biome lint --only=<rule>`). Examples:
`no-use-before-define` is lexical while Biome's
`noInvalidUseBeforeDeclaration` is control-flow-aware (~1000 findings);
`rules-of-hooks` flags Storybook `render` callbacks Biome allows. A
big-bang swap would either drop ~45-50 Biome checks or require touching
~190 source spots. Adopting oxlint incrementally avoids both and keeps
CI green.

Additionally, oxlint cannot lint CSS or JSON (Biome does), and ~18 Biome
JS/TS rules have no oxlint equivalent — so Biome stays regardless.

## Changes

- `site/package.json`: add `oxlint@1.80.0`; add `lint:oxlint` script;
wire it into the `lint` chain after `lint:check`.
- `site/.oxlintrc.json` (new): `correctness` category off; 126 rules
explicitly enabled to mirror Biome where behavior matches; migration
backlog encoded inline as disabled rules with effort labels.
- `site/pnpm-lock.yaml`: oxlint dependency.

Biome config, formatting, `check`, and `lint:fix` are unchanged.

## How to chip away

Per follow-up PR: fix the code (or migrate the `biome-ignore`
suppression) for one backlog rule → flip it to `error` in
`.oxlintrc.json` → confirm oxlint stays green → optionally drop it from
Biome. Effort labels in the config indicate the size of each.

## Verification

- `pnpm run lint:oxlint` → 0 warnings, 0 errors (126 rules).
- `biome lint --error-on-warnings .` → still clean.
- No formatting changes.

<details>
<summary>Implementation plan &amp; decision log</summary>

### Approach

Biome remains the authoritative JS/TS linter (and formatter, and
CSS/JSON linter). oxlint runs alongside it as an additional CI check
enforcing only the rules whose oxlint behavior already matches Biome on
the current codebase. Divergent rules are kept in the config but
disabled, forming a documented backlog we chip away at over time. When a
backlog rule is fixed and green in oxlint it can eventually be dropped
from Biome.

This satisfies "preserve all Biome checks, and nothing more" today:
- **Nothing lost:** Biome still runs its full JS/TS rule set unchanged.
- **Nothing more:** oxlint only enables rules that produce zero new
findings versus Biome on the current tree (verified clean).

### Migration backlog (oxlint rule ← Biome rule, effort)

- `no-use-before-define` ← noInvalidUseBeforeDeclaration (xl, lexical vs
control-flow)
- `rules-of-hooks` ← useHookAtTopLevel (large, Storybook render
callbacks)
- `no-autofocus` ← noAutofocus (large)
- `jsx-no-useless-fragment` ← noUselessFragments (large)
- `no-invalid-void-type` ← noConfusingVoidType (large)
- `exhaustive-deps` ← useExhaustiveDependencies (large)
- `no-explicit-any` ← noExplicitAny (medium, biome-ignore migration)
- `no-redeclare` ← noRedeclare (medium, TS declaration merging)
- `new-for-builtins` ← noInvalidBuiltinInstantiation (medium)
- `no-empty-object-type` ← noBannedTypes (medium)
- `role-has-required-aria-props` ← useAriaPropsForRole (medium)
- `no-irregular-whitespace` ← noIrregularWhitespace (medium)
- `no-this-alias` ← noUselessThisAlias (medium)
- `no-inferrable-types` ← noInferrableTypes (medium, coder error rule)
- `consistent-type-imports` ← useImportType (medium)
- `jsx-curly-brace-presence` ← useConsistentCurlyBraces (medium, coder
error rule)
- `heading-has-content` ← useHeadingContent (medium)
- `anchor-ambiguous-text` ← noAmbiguousAnchorText (medium)
- `no-fallthrough` ← noFallthroughSwitchClause (medium)
- `role-supports-aria-props` ← useAriaPropsSupportedByRole (small)
- `click-events-have-key-events` ← useKeyWithClickEvents (small)
- `prefer-const` ← useConst (small)
- `no-unused-vars` ← noUnusedVariables/noUnusedImports (small,
dead-store semantics)
- plus small: prefer-function-type, no-extraneous-class,
no-empty-interface, no-danger, jsx-no-target-blank,
no-noninteractive-tabindex, media-has-caption,
interactive-supports-focus, iframe-has-title, prefer-template,
no-script-url, no-restricted-imports, no-console

### Rules with no oxlint equivalent (Biome-only permanently)

noEmptyTypeParameters, noFlatMapIdentity, noThisInStatic,
noUselessContinue, noUselessStringRaw, noUselessUndefinedInitialization,
useSimpleNumberKeys, noConstantMathMinMaxClamp, noPrivateImports,
noStringCaseMismatch, noDynamicNamespaceImportAccess, noImplicitAnyLet,
noMisrefactoredShorthandAssign, noOctalEscape, noRedundantUseStrict,
noSuspiciousSemicolonInJsx, noUnusedTemplateLiteral, noVoidTypeReturn —
plus all CSS/JSON linting.

</details>

---

🤖 Generated with Coder Agents.

---------

Co-authored-by: Coder Agents <noreply@coder.com>
2026-08-26 20:33:51 -04:00
Nick Vigilante c78dca5141 feat: add Coder Quickstart base to the template builder (#27247)
## Summary

Adds the **Coder Quickstart** template as a selectable base in the
[template builder](coderd/templatebuilder) guided wizard. It is a
Docker-based starter that lets a user pick languages and optionally
clone a repo.

## What changed

- **New base package** `coderd/templatebuilder/bases/quickstart/`:
- `base.json`: `id: "quickstart"`, `display_name: "Coder Quickstart"`,
`os: "linux"`.
- `main.tf.tmpl`: a Docker workspace with a language selector, a single
language-install script, an optional Git clone, and workspace presets.
Editors are added via the builder's module step rather than baked into
the base.
- `install-languages.sh.tftpl` and `README.md` (with prerequisites
markers so the builder can extract the prerequisites section).
- **Placement**: the bases endpoint returns a plain list sorted by
display name (with ID as a deterministic tiebreak). The wizard's
base-infra select step prioritizes the Quickstart and Docker starters to
the front on the client, mirroring the existing client-side module
prioritization in `ModuleSelectStep` (shared `sortByPriority` helper).
- **Base/module collision guard**: a base's bundled catalog modules are
derived from its rendered Terraform (`ExtractModuleNames`, cached with
`sync.OnceValues` and filtered to catalog IDs). `validateModules` seeds
its seen-set from that derived set, so a wizard-selected module the base
already renders is rejected with a clear error, and the modules endpoint
omits it so the wizard never offers a colliding module. The base's
Terraform is the single source of truth, so there is no separate
manifest list that can drift.

## Testing

- `go test ./coderd/` (template builder handler: bases ordering, the
`baseSpec` table, the modules-endpoint base filter): pass.
- `go test ./coderd/templatebuilder/` (all-bases render/snapshot, the
collision guard, `TestBaseIncludedModules`, the selector/install-script
drift test): pass.
- `gofmt`, `go vet`, `golangci-lint`: clean.
- Frontend `tsc`, `biome`, and `knip`: clean.

## Review decisions

- **Template scope**: trimmed to infrastructure, a language selector,
and an optional Git clone. The IDE selector was removed; editors are
added via the builder's module step.
- **Placement**: base ordering is presentational, so it lives on the
client base-infra select step rather than in the API.
- **git-clone in the base**: kept for the build-time clone affordance.
The base renders its module source verbatim, so it currently pins the
public registry and does not yet honor a deployment's registry mirror;
that fix is tracked in DOCS-610 and delivered as a stacked follow-up PR
(an in-code note documents the current behavior).
- **Container image**: left hardcoded
(`codercom/enterprise-base:ubuntu`). The quickstart base is the
opinionated path; the Docker base covers custom images. Revisiting a
`container_image` variable is tracked in DOCS-611.

## Review updates (@jeremyruppel)

- **Ordering moved to the client**: deleted the server-side
Quickstart-before-Docker grouping (and its position test); the
base-infra select step now prioritizes bases client-side via a shared
`sortByPriority` helper, following the `ModuleSelectStep` precedent. The
API just returns a stable, name-sorted list.
- **Derive included modules from render**: dropped the
`included_modules` manifest field and the `IncludedModules` struct
field; `BaseIncludedModules` now derives from the rendered base (single
source of truth). Removed `TestBaseIncludedModulesMatchRendered` and
added a direct `TestBaseIncludedModules`.
- **Kept the PR scoped to "add quickstart base"**: reverted
`ExtractAgentResourceName`/`ExtractModuleNames` to the pre-`hclsyntax`
regex extraction and removed the option/preset HCL extractor family; the
selector↔install-script drift test now uses a small regex. The broader
templatebuilder HCL migration and the preset-language subset guard are
tracked in DOCS-735.
- Branch rebased onto `origin/main` (no content changes beyond the
above).

---

Linear:
[DOCS-558](https://linear.app/codercom/issue/DOCS-558/add-coder-quickstart-template-to-template-builder)

> This PR was created with AI assistance (Coder Agents).
2026-08-26 20:29:20 -04:00
Michael Suchacz 017ab75d9b docs(coderd/x/chatd): document model configuration behavior (#28654)
## Stack Context

This single-PR stack documents organization-scoped model discovery,
model configuration write serialization, worker fallback, subagent
override precedence, and compaction override behavior in Chatd.

## Why

The Chatd architecture document still contained TODOs after the related
implementations shipped. This replaces them with the current API routes,
persistence invariants, precedence rules, and failure behavior.

Refs [CODAGT-709](https://linear.app/coder/issue/CODAGT-709)

Refs [CODAGT-872](https://linear.app/coder/issue/CODAGT-872)

> Xum acted on Mike's behalf.

<!-- xum-attribution: model=gpt-5.6-sol thinking=xhigh -->
2026-08-27 01:51:45 +02:00
Nick Vigilante 1bac215429 docs(docs/.style/style-guide): spell out zero and one, digits from six up (#28598)
Revises the numbers-and-dates style rule: `0` and `1` read oddly as
digits in ordinary prose ("more than 1", "1 or more"), so those two
values can now go either way (digit or word), matching how "more than
one"/"one or more" and "3 parameters"/"three parameters" both already
read fine.

Two exceptions carve back out of that flexibility:

- Inside "more than X" / "X or more" phrasing, X 0 through 5 always
spells out the word, never the digit.
- A literal technical value (an exit code, for example) always uses the
digit, because the digit is the actual value.

Numbers 6 and higher are unchanged: always digits.

No Vale rule changes here — `Coder.DigitsSixPlus` stays `planned`;
enabling it means sweeping the ~150-line corpus of spelled-out numbers
under the new policy, which is a separate, larger follow-up.

---
🤖 Built with AI assistance.
2026-08-26 21:53:17 +00:00
Nick Vigilante 9d973af2d3 docs(docs/.style/style-guide): add input-device-agnostic language guidance (#28600)
Adds a section to the accessibility page generalizing the existing
"select, not click" rule: don't assume the reader's input device (mouse,
trackpad, touchscreen, keyboard, or assistive technology) when
describing a UI interaction, unless the content is specifically about
that device.

Prompted by `docs/admin/integrations/island.md`, which lists "mouse
clicks" and "keystrokes" among the input a third-party DLP agent can
record — accurate there, since that's literally what the tool captures,
but it raised the question of when naming an input device is fine at
all. The new section states the general principle and the exception
(keyboard shortcut lists, a right-click context menu, third-party
software that records mouse/keyboard input by name), cross-linked with
the click-specific rule in word-choice.md.

Documentation-only, no Vale rule for the general principle — it's
judgment-bound the same way the existing "directional language" section
is.

---
🤖 Built with AI assistance.
2026-08-26 21:40:55 +00:00
Michael Suchacz 78d0571452 fix(site/src/testHelpers): point MCP server ACL msw handlers at v2 paths (#28659)
~~test-js is failing on main~~ Main is fixed: #28657 landed the same
`api.test.ts` expectation update, so this PR no longer unbreaks anything
and is not urgent.

What remains here is the other half of the original fix:
`site/src/testHelpers/handlers.ts` still registers the MCP server ACL
GET/PATCH msw handlers at `/api/experimental/...`, but since #28498 the
client calls `/api/v2/...`, so those handlers can never intercept a
request. Any future test or story that exercises the ACL endpoints
through the shared msw handlers would hit an unhandled-request error
instead of the mock. This points them at the v2 paths the client
actually uses.

Validation: `pnpm exec vitest run --project=unit src/api/api.test.ts`
passes 43/43 on this branch (merged with current main).

> Xum acted on Mike's behalf.
<!-- xum-attribution: model=claude-fable-5 thinking=high -->
2026-08-26 23:38:23 +02:00
Nick Vigilante 77743afdb8 docs: rewrite extending-agents MCP and skills discovery for pushed context (#28576)
This PR rewrites the MCP and skills discovery sections of
`docs/ai-coder/agents/extending-agents.md` to match the current
pushed-context architecture.

The page documented a removed mechanism: per-chat-turn `.mcp.json` reads
with a 5-second discovery timeout, first-turn scans of `.agents/skills/`
only, and no mention of pinning.

What changed:

- Adds a "How the workspace shares context with chats" section: the
agent scans and pushes a context snapshot, chats pin one snapshot, a
push marks already-pinned chats out of date, and **Refresh context**
re-pins. Explains why an in-flight chat can keep an older snapshot.
- Documents the readiness gate: only an empty snapshot is published
until startup scripts finish.
- Documents the scan roots (working directory, `~/.coder`,
`~/.coder/skills`, `~/.claude/plugins/cache`, user-declared sources) and
that discovery is shallow.
- Documents all four skill container directories (`skills`,
`.agents/skills`, `.claude/skills`, `.codex/skills`) instead of only
`.agents/skills`.
- Adds snapshot limits and failure modes: 64 KiB per resource emitted as
oversize with no content, 2 MiB aggregate and 500 resource caps emitted
as excluded, invalid skill frontmatter.
- Replaces the 5-second discovery claim with the 30-second per-server
connect timeout and the fsnotify watcher with a 250 ms debounce; notes
that tool calls are proxied through the agent, so a pinned chat can list
tools it cannot execute while the workspace is unreachable.
- Keeps the values that are still correct: 60 s per tool call,
`SKILL.md` <= 64 KB, supporting files <= 512 KB truncated, personal
skills single file <= 64 KB and <= 100 per user, `read_skill_file` path
restrictions.

Linear: DOCS-723 https://linear.app/codercom/issue/DOCS-723

<details><summary>Analysis evidence</summary>

Every claim was verified against code on this branch's merge base.

**Push and pin model**

- `agent/agentcontext/push.go`: `RunPush` ships the current snapshot and
every subsequent snapshot on change; it skips version 0 (pre-ready).
- `coderd/agentapi/context.go`: `PushContextState` persists the snapshot
and calls `ContextDirtyMarker.HydrateAndMarkChatsDirty` inside the
transaction; the doc comment states it "hydrates chats for the agent
that have no pinned hash yet ... and flips already-pinned chats whose
hash differs".
- `coderd/x/chatd/context_hydration.go`: `HydrateAndMarkChatsDirty`
stamps NULL-hash chats via `HydrateAgentChatsContext` and dirties
differing chats via `MarkChatsContextDirtyByAgent`; "The pinned hash on
dirtied chats is intentionally left unchanged; the refresh endpoint
re-pins it." `RefreshChatContext` backs `PUT /chats/{chat}/context`
(route registered in `coderd/coderd.go`), and
`site/src/pages/AgentsPage/components/ChatPageContent.tsx` wires the
**Refresh context** button to it. This confirms the pinning semantics
claim: a chat mid-turn keeps its older snapshot until refreshed.
- `coderd/x/chatd/chatd.go` (`resolveWorkspaceMCPTools` /
`pinnedWorkspaceMCPTools`) builds the turn's MCP tools from
`chat_context_resources`, and `coderd/x/chatd/context_prompt.go` builds
the instruction block and skill index from the same pinned rows. No
per-turn live discovery remains.

**Readiness gate**

- `agent/agentcontext/manager.go`: `resolveAndBroadcast` returns early
while `!m.ready`; `agent/agentcontext/doc.go` describes the version-0
empty snapshot. `agent/agent.go` calls `a.contextManager.SetReady()`
after the startup-script lifecycle transition, then reloads MCP servers.
(The gate lives on `agentcontext.Manager`, not `agentmcp.Manager`.)

**Scan roots and shallow discovery**

- `agent/agentcontext/manager.go` `scanRootsLocked`: user sources, then
`defaultBuiltinRoots()`, then the working directory.
- `agent/agentcontext/defaults.go`: built-in roots are `~/.coder`,
`~/.coder/skills`, `~/.claude/plugins/cache`.
- `agent/agentcontext/doc.go` and `resolve.go`: instruction files and
`.mcp.json` are read only at a scan root's top level; the resolver never
descends or climbs.

**Skills**

- `agent/agentcontext/resolve.go` `skillContainerRelPaths`: `skills`,
`.agents/skills`, `.claude/skills`, `.codex/skills`;
`skillContainersFor` also treats a root named `skills` as a container,
which is what makes `~/.coder/skills` work.
- `readSkillMeta`: frontmatter name must match the directory basename
and match `workspacesdk.SkillNamePattern` (`^[a-z0-9]+(-[a-z0-9]+)*$`),
otherwise `StatusInvalid`; oversize files get `StatusOversize` with no
payload.
- `coderd/x/chatd/chattool/skill.go`: `maxSkillMetaBytes =
workspacesdk.MaxSkillMetaBytes` (64 KiB), `maxSkillFileBytes = 512 *
1024` with truncation, `validateSkillFilePath` rejects absolute paths,
`..`, and hidden components. `loadPinnedWorkspaceSkillContent` serves
the body from the pin; `bestEffortSkillFiles` needs a live connection.
- `coderd/x/skills/skills.go`: `MaxPersonalSkillSizeBytes` = 64 KiB,
`MaxPersonalSkillsPerUser` = 100.

**Caps**

- `agent/agentcontext/resolve.go`: `DefaultMaxResourceBytes` 64 KiB
(oversize, empty payload), `DefaultMaxSnapshotBytes` 2 MiB and
`DefaultMaxResources` 500 (excluded, payload cleared).
`coderd/x/chatd/context_prompt.go` `pinnedContextResources` surfaces
non-OK rows with status and error.

**MCP timeouts and watcher**

- `agent/x/agentmcp/manager.go`: `connectTimeout = 30 * time.Second`,
`toolCallTimeout = 60 * time.Second`. No 5-second discovery timeout
exists anywhere in `coderd/x/chatd` or `agent/x/agentmcp`.
- `agent/x/agentmcp/configwatcher.go`: `defaultWatchDebounce = 250 *
time.Millisecond`; the watcher fires on create, write, remove, and
rename. `agent/agentcontext/watcher.go` uses the same 250 ms window.
- `agent/agent.go`: `a.mcpManager.SetOnReload(a.contextManager.Trigger)`
re-resolves and re-pushes when the catalog changes; the shared engine
owns one connection set for discovery and execution.
- `coderd/x/chatd/chattool/mcpworkspace.go`: tool calls proxy back
through the agent connection, so execution requires a reachable
workspace.

**Validation**

- `pnpm install --frozen-lockfile`, `pnpm run format-docs` (no changes),
`pnpm run lint-docs` (0 errors), `vale` on the page (0 errors; 1
pre-existing `Coder.GerundHeading` warning on the untouched H1).

</details>

Generated by Coder Agents on behalf of @nickvigilante.


<details><summary>CI flake note</summary>

The CI run for commit a60db5adee concluded with `startup_failure`
(workflow never started; runner infrastructure), marking Flake Check,
Storybook, and gen as failed on a markdown-only change. Unrelated to the
changed paths; both runs re-triggered.

</details>
2026-08-26 21:32:42 +00:00
TJ 769decbe21 fix(site): match sessions date/time picker icon to search field icon (#28478)
Makes the date/time range picker trigger on the AI Gateway sessions page
visually consistent with the adjacent search field and filter dropdowns.
Button size is unchanged (`lg`, h-10).

- **Calendar icon size:** now `size-icon-sm` (18px), identical to the
search icon. Previously the Button's `[&>svg]:size-icon-lg` +
`[&>svg]:p-0.5` child selectors (specificity 0-1-1) overrode the icon's
authored `size-4` class (0-1-0), inflating it to a 24px box at
`size="lg"`. The calendar's classes are marked important (`!size-icon-sm
!p-0`) to win that fight.
- **Calendar line weight:** at equal geometry the calendar reads heavier
than the magnifier because its glyph is denser, so its stroke is
`strokeWidth={1.75}` for optical parity with the search icon.
- **Chevron:** now `size-icon-sm` with no color class, inheriting the
button's `text-content-primary` — exactly matching `ComboboxButton`,
which backs the other filter dropdowns (previously it was
`text-content-secondary`).
- **Spacing:** trigger uses `gap-2 pr-1.5` like `ComboboxButton`, giving
8px icon-to-text (matching the search field's `pr-2` addon spacing) and
the same label-to-chevron gap and right padding as the other dropdowns.

**Scope:** `DateTimeRangePicker`'s only production consumer is the AI
Gateway sessions filter, so the change affects only this page (plus
Storybook).

---
*This PR was generated by Coder Agents on behalf of @tracyjohnsonux.*
2026-08-26 20:18:05 +00:00
TJ 2eb9e4fbe8 fix(site/src): use default combobox dropdown surface (#28613)
Combobox dropdowns were forcing the migrated menu styling in a few
places: secondary menu surfaces, surface-quaternary borders, square item
highlights, tertiary selected-row highlights, and a tertiary footer
hover in the workspace template dropdown.

Use the shared combobox defaults instead: primary menu surfaces,
`border-border-default`, padded lists so highlights do not run to the
edge, rounded item highlights, and the standard secondary selected-row
highlight. Remove callsite overrides that were preventing the shared
defaults from applying to workspace, filter, and version dropdowns.

> This PR was generated by Coder Agents on behalf of @tracyjohnsonux.
2026-08-26 12:40:49 -07:00
TJ f31b759776 fix(site/src): use md badges for provisioner tags and network call pills (#28527)
Several badges render at `text-2xs` (10px) via `Badge size="sm"` and
read as broken next to their surroundings, especially since the
MUI/Emotion removal (#27821) dropped the 14px `CssBaseline` body
typography that used to soften the contrast. Most visible on the
provisioner jobs page (Type and Tags columns) and the AI Gateway
sessions list (network call pills).

Rather than changing what `Badge sm` means globally (explored in #28517,
closed in favor of this), this switches the affected call sites to the
default `md` size (`text-xs`, 12px), matching the other badges around
them.

## Changes

- `ProvisionerTags.tsx`: `ProvisionerTag` and the `+N` overflow badge →
default `md`. Covers the provisioner jobs Tags column, job detail
expansion, and other provisioner tag consumers.
- `JobRow.tsx`: the job Type badge → default `md`.
- `NetworkCallBadges.tsx`: the total/blocked pills on the sessions list
→ default `md`, consistent with the token pills in #28477.

The `Badge` component itself is untouched; `sm` remains available for
intentionally dense contexts.

Related: #28477 (AI session token badges).

> 🤖 Generated by Coder Agents on behalf of @tracyjohnsonux
2026-08-26 15:37:13 -04:00
david-fraley fc35aaefff fix(site/src/pages/DeploymentSettingsPage): gate browser-only paywall on entitlement (#28660) 2026-08-26 14:27:56 -05:00
Jeremy Ruppel 580e974089 feat(site): enable autoFocus for emoji icon picker (#28655)
This auto-focuses the input in the emoji picker. This is already a
feature of emoji-mart, we just forgot to type it.

I also deliberately didn't include stories because that would be testing
emoji-mart functionality, no value


https://github.com/user-attachments/assets/74e95903-55e8-4302-83f9-a677baa39a41
2026-08-26 15:15:23 -04:00