mirror of
https://github.com/coder/coder.git
synced 2026-08-31 01:03:45 +08:00
main
16139 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 --> |
||
|
|
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>
|
||
|
|
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. |
||
|
|
6e5ff5eac6 | chore: remove unused log button (#28726) | ||
|
|
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` |
||
|
|
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.
|
||
|
|
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. |
||
|
|
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. |
||
|
|
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). |
||
|
|
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._ |
||
|
|
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> |
||
|
|
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.*
|
||
|
|
d7bf8abeb3 | chore: remove orphaned components (#28690) | ||
|
|
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). |
||
|
|
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>
|
||
|
|
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. |
||
|
|
b6505fe921 |
feat: add service tier to TokenUsage recording (#28709)
Adds `service_tier` information to token usage recordings made by OpenAI and Anthropic interceptors. |
||
|
|
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" /> |
||
|
|
c51b03214f | fix(site): stop back link overlapping the new workspace form (#28355) | ||
|
|
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" /> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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> |
||
|
|
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`). |
||
|
|
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. |
||
|
|
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.
|
||
|
|
3ca2ba485e |
fix: add cache write accounting to OpenAI interceptors (#28567)
Adds cache write token accounting to OpenAI interceptors. |
||
|
|
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. |
||
|
|
3797c4b785 | revert: restore m-block mark for Xum icon (#28693) | ||
|
|
fd35b59ecd | fix(site): remove unused MCPConnectSummaryViewModel export (#28701) | ||
|
|
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.
|
||
|
|
d0e22343f0 |
fix: make chat model sharing work for sharers without directory access (#28542)
## Problem
This is a bug fix. Chat model sharing UAT (main @
|
||
|
|
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._ |
||
|
|
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 --> |
||
|
|
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. |
||
|
|
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 🙂 |
||
|
|
496c5e36a6 | fix(site): support Vite native config loading (#28616) | ||
|
|
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> |
||
|
|
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 & 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> |
||
|
|
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). |
||
|
|
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 --> |
||
|
|
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.
|
||
|
|
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.
|
||
|
|
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 --> |
||
|
|
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
|
||
|
|
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.*
|
||
|
|
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. |
||
|
|
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 |
||
|
|
fc35aaefff | fix(site/src/pages/DeploymentSettingsPage): gate browser-only paywall on entitlement (#28660) | ||
|
|
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 |