> Mux is opening this PR on behalf of Mike.
Updates agent chat thinking disclosures to include the first Markdown
heading or leading header-like reasoning paragraph, rendering titles
like `Thinking about configuring model settings` while preserving
`Thinking` when no heading is present.
Existing chat logs store many thinking section titles as bold standalone
paragraphs, such as `**Checking tool execution**`. This handles that
format too, and removes the displayed heading from the expanded thinking
body so it does not appear twice. Adds focused title/body extraction
coverage and updates the conversation timeline story for the heading
title behavior.
> Mux updated this PR on behalf of Mike.
## Context
PR #25066 has merged. This branch is rebased onto `main` and now
contains only the personal skills slash menu UI changes.
## Summary
- Add a `/` slash-trigger menu in the agent chat composer that filters
personal skills by name and description.
- Insert `/<skill-name>` on click, Enter, or Tab selection while
preserving normal composer behavior when the menu is closed.
- Keep Escape dismissal and post-selection suppression scoped to the
current slash trigger, with menu anchor refresh on editor scroll and
resize.
- Share personal skill trigger formatting and parsing helpers with unit
coverage.
- Add Storybook coverage for open, filter, click, keyboard selection,
Escape, error, empty, and filtered-empty states.
## Validation
- pre-commit hook
- `cd site && pnpm exec vitest run --project=unit
src/pages/AgentsPage/components/ChatMessageInput/ChatMessageInput.test.tsx
src/pages/AgentsPage/utils/personalSkills.test.ts`
- `cd site && pnpm lint:types`
- `cd site && pnpm lint:check`
> Mux updated this PR on behalf of Mike.
## Summary
- Add experimental personal skills API helpers and an Agents settings UI
for listing, creating, editing, deleting, and importing SKILL.md
content.
- Add docs, Storybook coverage, and unit tests for backend-compatible
SKILL.md parsing.
- Address review feedback by simplifying frontmatter scalar parsing,
clarifying the UI parser scope, defaulting personal skill queries to
`me`, and patching React Query caches after create, update, and delete.
- Merge latest `main` and resolve the Agents sidebar refactor conflicts.
## Validation
- pre-commit hook
- `go test ./codersdk/workspacesdk -run TestParseSkillFrontmatter
-count=1`
- `go test ./coderd/x/chatd/chattool -run 'Test' -count=1`
- `cd site && pnpm test --
src/pages/AgentsPage/utils/personalSkills.test.ts
src/api/queries/userSkills.test.ts src/utils/fileSize.test.ts
--runInBand`
- `cd site && pnpm lint:types`
- `cd site && pnpm lint:check`
Removes the coder_secret Terraform integration: the data.coder_secret
consumption path through provisionerdserver → provisioner.proto →
provisioner/terraform, the dynamic-parameter secret-requirement
validation, and the workspace-update / resolve-autostart surfaces that
depended on it. This is being done due to a product/feature direction
change (see PLAT-243). User-secret CRUD (DB, REST, CLI, UI, telemetry, audit)
and the agent-manifest secret-injection path are untouched.
The provisionerd API is bumped from v1.17 to v1.18 rather than rolled
back: v1.17 shipped in v2.33.x, so user_secrets field numbers are
reserved and the changelog documents both versions.
Generated with assistance from Coder Agents.
1. truncates search results
2. display a loading spinner when retrieving new search results when
existing results are displayed
3. Improve dialog resizing behavior
Adds options matching new AI Gateway naming.
New options are added as alias for old options. Old options are still
working.
Old options have deprecated message.
No conflict detection was added.
Updated documentation so it mentions only new options. Added note about
old options still working.
> Various AI tools where used to create this PR
When the execute tool runs a chained shell command, the UI previously
rendered the raw string. Long chains like "cd /repo && git pull &&
git add . && git commit -m fix" were hard to scan.
A new ChatMessagePart.ParsedCommands [][]string field on tool-call
parts carries one entry per simple command, parsed in chatd from args
via mvdan.cc/sh/v3/syntax. The frontend renders the joined list ("cd,
git pull, git add, git commit") in place of the raw command, and falls
back to the raw command when the field is absent.
Closes CODAGT-446
Updates the connect via SSH menu shown in workspaces to redirect to
`/install` on the local deployment instead of
`https://coder.com/docs/<version>/install`. This ensures consistency
with the user account dropdown menu which also references the local
deployment.
End goal is to ensure the user running install script receives the same
CLI version as is running on the Coder deployment.
Follow-up to #25429, which wrapped `shortRelativeTime(chat.updated_at)`
in `<span data-chromatic="ignore">` to stop the chat row timestamp from
drifting `"46m" → "5m" → "now"` across Chromatic runs. Chromatic kept
flagging a sliver of change just to the left of the rendered text, even
though the area looks empty.
[Chromatic's docs](https://www.chromatic.com/docs/ignoring-elements/)
explain why: `data-chromatic="ignore"` masks pixel diffs **inside the
element's bounding rectangle**, but dimension changes of that rectangle
still trigger a diff. The wrapper span has no explicit width, so it
sizes to its text — `"46m"` (≈22px) shrinks to `"now"` (≈19px), the mask
shrinks by ~3px on its left edge, and that exposed strip is what
Chromatic was reporting.
Fix: pin the wrapper to `inline-block w-7 text-right`. `w-7` matches the
surrounding `w-7` column, and `text-right` keeps the trailing edge
anchored so the bounding rect is identical regardless of which
`shortRelativeTime` branch fired. The unread-dot branch is untouched and
keeps its own Chromatic coverage.
The workspace quota meter in the Agents chat sidebar previously only
refreshed on a full page reload, while the AI cost-usage meter rendered
next to it already polled every 60 seconds.
This change gives the Agents usage indicator the same 60s
workspace-quota polling and refreshes derived workspace caches
immediately when workspace-affecting chat tools complete.
`create_workspace`, `start_workspace`, and `stop_workspace` now
invalidate the workspace quota and `workspaces` query family, so both
the credit numbers and the workspace-count detail stay in sync.
`create_workspace` still invalidates the chat record to resolve
workspace bindings, and archive-and-delete uses the shared workspace
mutation invalidation helper so deleting an agent workspace refreshes
the same derived workspace data.
Closes CODAGT-444
Tested manually and it works perfectly.
> Mux updated this PR on behalf of Mike.
## Stack Context
This PR is the storage, permissions, API, and SDK layer for experimental
personal skills. #25362 has landed on `main`, so this branch is
restacked directly on `main`.
Stack order:
1. #25363 storage, permissions, API, and SDK
2. #25365 API test coverage
3. #25366 chattool and chatd integration
4. #25066 settings UI and docs
5. #25386 personal skills slash menu
## What?
Adds the `user_skills` database table, generated queries, RBAC resources
and scopes, audit resource handling, experimental user-scoped CRUD
endpoints, SDK types, and generated API/site types.
Follow-up review and restack fixes:
- Enforce a bounded personal skill description in parser and database
constraints.
- Return `403 Forbidden` for unauthorized create and update attempts.
- Return explicit conflict responses when soft-deleted users are
targeted.
- Keep user admins out of personal skills, while site owners can read
and delete but not create or update.
- Document trigger-raised constraint names and keep schema constants
covered by tests.
- Reuse `UserSkillMetadata` in the full `UserSkill` SDK response type.
- Generate user skill IDs in Go instead of relying on a database
default.
- Rebase on latest `main` and renumber the user skills migration to
`000502_user_skills`.
## Why?
Personal skills need durable user-owned storage with owner
authorization, limited site-owner moderation, and a hidden API surface
before chatd can consume them.
## Validation
- `make gen`
- `go test ./coderd/database -run '^TestUserSkillSchemaConstants$'
-count=1`
- `go test ./coderd/database/dbauthz -run
'^TestMethodTestSuite/TestUserSkills$' -count=1`
- `go test ./coderd -run '^TestPatchUserSkill$' -count=1`
- `go test ./codersdk ./coderd/database/db2sdk`
- `make lint`
- pre-commit hook on `97fd58108d`
> Mux opened this PR on behalf of Mike.
Fixes CODAGT-451
Adds optional `model_intent` metadata to the built-in execute tool
schema so tool calls can carry a short user-facing intent label without
duplicating the command or duration.
The Agents UI now composes that intent with the existing execute command
and duration fields, displaying labels like `Checking repository state
using git fetch origin for 2.3s` while keeping the shell command visible
as the audit-relevant action.
Existing execute calls without an intent keep the previous `Ran
<command>` fallback label, so only intent-bearing calls get the new
composed label.
Add frontend API methods, mocks, and form helpers for user secrets CRUD. The new client methods cover list, get, create, update, and delete requests, including URL encoding for secret names used in route paths.
Add user secret form utilities for create and update payload construction, required create field checks, and structured API validation error mapping back to form fields. User secret name validation now lives in codersdk with tests, and coderd returns field-level validation errors for create, update, and uniqueness conflicts so the frontend can show backend-owned validation results consistently.
Replace the original 13-person roster with the full 47-person list,
sorted alphabetically by first name.
cc @DanielleMaywood for review
> [!NOTE]
> This PR was authored by Coder Agents.
Records reasoning start and end times on persisted reasoning
`ChatMessagePart`s so reasoning duration can be computed for stored
chats. Backend-only: no SSE changes and no frontend rendering ship in
this PR.
The `created_at` field on `ChatMessagePart` is extended to also be
present on `reasoning` parts (it previously appeared only on `tool-call`
and `tool-result`), and a new `completed_at` field is added for
`reasoning` parts.
### How timestamps are recorded
- `StreamPartTypeReasoningStart`: stamp `startedAt = dbtime.Now()` on
the active reasoning state.
- `StreamPartTypeReasoningEnd`: stamp `completedAt = dbtime.Now()` and
append both into parallel `[]time.Time` slices on `stepResult`.
- Persistence reads the slices in occurrence order (reasoning has no
provider-side ID) and applies them to the matching `ChatMessagePart` via
`buildAssistantPartsForPersist`. The first reasoning block's stamps go
onto the first reasoning part, and so on.
- `flushActiveState` flushes partial reasoning interrupted before
`StreamPartTypeReasoningEnd` with `startedAt` from the active state and
`completedAt = dbtime.Now()` at the interruption.
### Why two fields, not one?
Tool calls and results are point events. The frontend computes their
duration by subtracting the call's `created_at` from the result's
`created_at`. Reasoning is one assistant part that brackets a span, so
we record both endpoints on the part itself.
### Why not stamp in `PartFromContent`?
Same rationale as #24101: `PartFromContent` is called during both SSE
publishing and persistence. Stamping there would yield incorrect
persistence-time timestamps for reasoning blocks that finished much
earlier in the step. Instead we capture in the chatloop and apply during
persistence.
<details><summary>Implementation plan</summary>
- `codersdk/chats.go`: extend `CreatedAt`'s `variants` to include
`reasoning?`; add `CompletedAt *time.Time` with `variants:"reasoning?"`.
- `coderd/x/chatd/chatloop/chatloop.go`: extend `reasoningState` with
`startedAt`; extend `stepResult` and `PersistedStep` with parallel
`[]time.Time` reasoning slices; stamp on
`ReasoningStart`/`ReasoningEnd`; thread the slices through all
`PersistStep` call sites including the interrupt-safe path; record
partial reasoning in `flushActiveState`.
- `coderd/x/chatd/attachments.go`: walk reasoning parts in occurrence
order and apply `step.ReasoningStartedAt[i]` to `part.CreatedAt` and
`step.ReasoningCompletedAt[i]` to `part.CompletedAt`.
### Tests
- `codersdk/chats_test.go` round-trips `created_at` + `completed_at` on
reasoning parts and verifies omission when absent and partial
interrupted parts.
- `coderd/x/chatd/chatprompt/chatprompt_test.go` asserts
`PartFromContent(ReasoningContent{})` does NOT stamp timestamps.
- `coderd/x/chatd/chatloop/chatloop_test.go`
`TestRun_ReasoningTimestamps` drives a stream with two reasoning blocks
and verifies parallel slices, monotonicity, ordering, non-zero values,
and content-block ordering.
`TestRun_InterruptedReasoningFlushesTimestamps` cancels mid-reasoning
and verifies `flushActiveState` records a non-zero pair.
- `coderd/x/chatd/attachments_test.go` covers
`buildAssistantPartsForPersist` for normal interleaved reasoning,
partial (zero `completed_at`), and missing slices.
</details>
> Generated by Coder Agents.
Co-authored-by: Coder Agent <agent@coder.com>
The `SectionHeadersCollapse` story (and every other
Chromatic-snapshotted story rendering the agents sidebar) was flaky
because each chat row renders `shortRelativeTime(chat.updated_at)` —
`"now"`, `"5m"`, `"7h"`, `"1d"`, etc. The values are computed against
the live wall clock from fixed fixture timestamps, so the rendered text
drifts on every run (e.g. `"7h"` → `"now"` mid-day) and Chromatic diffs
the change.
This wraps the timestamp in `<span data-chromatic="ignore">`, the
dominant codebase convention for time-varying text.
`data-chromatic="ignore"` appears 35 times across 20 files in
`site/src`, including `utils/schedule.tsx:139,153`,
`components/LastSeen/LastSeen.tsx:42`,
`modules/provisioners/Provisioner.tsx:80`,
`pages/OrganizationSettingsPage/.../ProvisionerRow.tsx:119`,
`JobRow.tsx:139`, `ProvisionerKeyRow.tsx:81`,
`pages/AIBridgePage/RequestLogsPage/RequestLogsRow/RequestLogsRow.tsx`
(multiple rows), `pages/HealthPage/WorkspaceProxyPage.tsx:144`, and
`pages/UserSettingsPage/TokensPage/TokensPageView.tsx:117`. By
comparison, only one component (`AppStatuses`) plumbs a reference date
through props, and there is no `Date.now`-stubbing precedent that would
actually stabilize `dayjs()`-based output like `shortRelativeTime`.
The wrap is scoped tightly: only the text node returned by
`shortRelativeTime` is ignored, so the sibling unread-indicator dot and
surrounding layout still participate in snapshots.
Filenames from the OS (e.g. `Screen Shot 2025-01-01 at 10.00.00 AM.png`
or `My Report (final).pdf`) flow unchanged through the chat-attach hooks
into chip labels, the persisted-attachment localStorage records, the
upload `Content-Disposition` header, and downstream LLM prompts.
Characters such as parentheses, brackets, quotes, shell or URL or path
metacharacters, whitespace, and control codes are all valid in HTTP
transport today but tend to break things further down the line (LLM tool
calls that quote the name, audit logs, any future S3/path interpolation,
shell-quoted tooling).
Sanitize at the boundary in `useFileAttachments.handleAttach` and
`useChatDraftAttachments.handleAttach` by mapping each incoming `File`
through a new `renameChatFileForUpload` helper. The helper replaces
`()[]{}<>'\"\`;,:*?|&#$\\/`, whitespace, and control characters with
`_`, collapses adjacent underscores, trims leading or trailing `_`, `.`,
or whitespace, and falls back to `"file"` if the result is empty. ASCII
alphanumerics, `.`, `-`, `_`, and all other Unicode letters and symbols
(CJK, emoji, accented Latin) are preserved so localized names remain
readable. Already-safe names return the same `File` reference; the chat
UI keys preview-URL, upload-state, and text-content Maps on the `File`
object, so identity must be stable.
The server's existing `chatfiles.NormalizeStoredFileName` (control-char
strip plus 255-byte truncate) is untouched. This is a client-only
hardening pass.
Add prev/next chevron buttons to the action row under each user message
in the agent chat transcript. Clicking jumps the scroll container to the
neighbouring user prompt's sticky sentinel (smooth scroll, no composer
mutation). Arrows disable rather than wrap when at the ends.
## Why
When a chat gets long, scrolling back to a previous prompt to see the
question that produced an answer is annoying. The transcript already has
a stable per-prompt anchor (`data-user-sentinel`) used by the
sticky-message logic, so reusing it for navigation is cheap and
consistent with the existing scroll model.
## Implementation
- `ChatMessageItem` accepts three optional props (`prevUserMessageId`,
`nextUserMessageId`, `onJumpToUserMessage`) and renders the two chevron
buttons inside the existing `message-actions` row when the message is a
user role.
- `StickyUserMessage` forwards the props to both copies of
`ChatMessageItem` (flow + sticky overlay).
- `ConversationTimeline` derives the ordered list of visible user
message IDs using the same `deriveMessageDisplayState` predicate that
controls visibility, builds a neighbour map, and supplies the jump
handler. The handler resolves the target via
`[data-user-sentinel][data-user-message-id="..."]` and smooth-scrolls
the closest `.overflow-y-auto` ancestor by the sentinel's offset
(mirroring the existing edit-flow scroll helper).
- New `data-user-message-id` attribute on the sentinel `div` to make the
lookup direct.
- New Storybook story `UserMessageJumpArrows` covers: arrow counts,
disabled-at-ends, and that clicking Next scrolls the next user sentinel
to the top of the scroller. JSDOM doesn't animate smooth scroll, so the
play function monkey-patches `scrollBy` to apply the requested top
offset synchronously.
No API, DB, or audit-table changes. Frontend only.
## Test
- `pnpm test:storybook
src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.stories.tsx`
— 46 passed (incl. new story).
- `pnpm test:storybook
src/pages/AgentsPage/components/ChatConversation/` — 67 passed.
- `pnpm lint:types`, `pnpm lint:fix`, `pnpm lint:compiler`, `pnpm
format:check` — all clean.
- Local `make pre-commit` ran via the pre-commit hook on commit.
<details>
<summary>Implementation plan</summary>
Plan lives at
`/home/coder/.coder/plans/PLAN-41b442d8-05bc-4b62-b1ba-155a7cef09bc.md`
in the agent workspace. Summary:
1. Add three optional props (`prevUserMessageId`, `nextUserMessageId`,
`onJumpToUserMessage`) to `ChatMessageItem` and render
`ChevronLeft`/`ChevronRight` buttons inside the existing actions row
when the message is a user role. Disable each button when its neighbour
is undefined.
2. Forward those props through `StickyUserMessage` to both
`ChatMessageItem` instances (flow + sticky overlay).
3. In `ConversationTimeline`, build the ordered list of visible user IDs
using the same `deriveMessageDisplayState` predicate, derive a neighbour
map, and implement `handleJumpToUserMessage` that looks up
`[data-user-sentinel][data-user-message-id="${id}"]`, finds the closest
`.overflow-y-auto` ancestor, and smooth-scrolls by the sentinel's
offset.
4. Add `data-user-message-id` to the sentinel so the lookup is direct.
5. Cover the behaviour with a `UserMessageJumpArrows` Storybook play
function.
</details>
---
*This PR was authored by Coder Agents on behalf of @ibetitsmike.*
## Summary
- Wire the Appearance settings page to the new theme mode dropdown and
sync or single theme selectors.
- Update Appearance page tests and stories for theme mode behavior.
- Update the user settings e2e test to exercise single theme selection.
## Dependencies
- Depends on #25076, #25180, #25181, and #25182.
- This PR targets helper branch `pr25077/05-theme-mode-dropdown-base`,
which contains dependency commits only, so this PR diff stays focused on
final dropdown wiring. Rebase and retarget after the dependency PRs
merge.
## Validation
- `pnpm -C site exec vitest run --project=unit
src/pages/UserSettingsPage/AppearancePage/AppearancePage.test.tsx
src/theme/themeMode.test.ts src/api/queries/users.test.ts`
- `pnpm -C site lint:types`
- `pnpm -C site storybook:ci`
- `pnpm -C site build`
- `pnpm -C site playwright:test -- e2e/tests/users/userSettings.spec.ts`
- Pre-commit hook passed on the branch commit.
## Summary
- Add appearance mutation context so optimistic cache updates can be
rolled back on failure.
- Restore previous appearance settings, or remove the optimistic cache
entry when no previous data exists.
- Merge successful server responses back into the appearance cache while
preserving request fields when responses are partial.
## Dependencies
- Stacked on #25076 because the tests and generated types use the new
appearance theme mode fields.
## Validation
- `pnpm -C site exec vitest run --project=unit
src/api/queries/users.test.ts`
- `pnpm -C site lint:types`
- Pre-commit hook passed on the branch commit.
Closes
https://linear.app/codercom/issue/AIGOV-284/add-group-budgets-table-and-crud-api
## Summary
Adds the `group_ai_budgets` table and the following endpoints:
- `GET /api/v2/groups/{group}/ai/budget`
- `PUT /api/v2/groups/{group}/ai/budget`
- `DELETE /api/v2/groups/{group}/ai/budget`
Each group may have at most one budget row. If no row exists, no budget
is enforced.
### Feature gate
Added `RequireFeatureMW(FeatureAIBridge)` on the `/ai/budget` sub-route.
## RBAC
Authorization reuses `rbac.ResourceGroup` with the existing
`.InOrganization(...).WithID(...)` scoping model.
The `dbauthz` wrappers load the parent `groups` row and authorize
against it.
No new resource type is introduced. As a result, anyone with
`group:update` permissions (Owner, OrgAdmin, or UserAdmin within the
organization) can manage AI budgets for that group.
## Read access for group members
`database.Group.RBACObject()` grants `policy.ActionRead` to all members
of the group through the group ACL:
```go
func (g Group) RBACObject() rbac.Object {
return rbac.ResourceGroup.WithID(g.ID).
InOrg(g.OrganizationID).
// Group members can read the group.
WithGroupACL(map[string][]policy.Action{
g.ID.String(): {
policy.ActionRead,
},
})
}
```
Because the `GET` endpoint authorizes against the same loaded `Group`
object, any group member can call:
```text
GET /api/v2/groups/{group}/ai/budget
```
`PUT` and `DELETE` remain admin-only. The group ACL grants only
`ActionRead`, so write operations continue to require role-based
`group:update` permissions.
## Alternative considered
A dedicated `rbac.ResourceGroupAiBudget` resource would allow budget
management to be separated from general group administration.
We decided not to add that complexity for now.
Move the "Read the docs" button from the Audit Logging subsection up to
the primary Observability header's `actions` prop, matching the layout
pattern used by General, Network, and other deployment settings pages.
Also updates the docs URL from `/admin/security/audit-logs` to
`/admin/monitoring` to reflect the page-level scope.
> Generated by Coder Agents on behalf of @designertyler
---------
Co-authored-by: TJ <tracy@coder.com>