Files
coder/cli/agents_styles.go
T
Dean Sheather e57525002c chore: remove agents experiment flag and mark feature as beta (#24432)
Remove the `ExperimentAgents` feature flag so the Agents feature is
always available without requiring `--experiments=agents`. The feature
is now in beta.

Existing deployments that still pass `--experiments=agents` will get a
harmless "ignoring unknown experiment" warning on startup.

### Changes

**Backend:**
- Remove `RequireExperimentWithDevBypass` middleware from chat and MCP
server routes
- Always include `AgentsAccessRole` in assignable site roles (later
refactored to org-scoped on main; rebase keeps that)
- Always set `AgentsTabVisible = true`, then drop the entire dead
`AgentsTabVisible` metadata pipeline (Go htmlState field,
populateHTMLState goroutine, HTML meta tag, useEmbeddedMetadata
registration, mock); no production consumer reads it. `AgentsNavItem`
already gates on `permissions.createChat`.
- Make `blob:` CSP `img-src` addition unconditional
- Remove `ExperimentAgents` constant, `DisplayName` case, and
`ExperimentsKnown` entry

**CLI:**
- Graduate the agents TUI from `coder exp agents` to `coder agents`
(moved from `AGPLExperimental()` to `CoreSubcommands()`)
- Drop the `agent` alias so it does not collide with the hidden
workspace-agent command
- Rename implementation files `cli/exp_agents_*.go` -> `cli/agents_*.go`
and internal identifiers (`expChatsTUIModel` -> `chatsTUIModel`,
`newExpChatsTUIModel` -> `newChatsTUIModel`, `setupExpAgentsBackend` ->
`setupAgentsBackend`, `startExpAgentsSession` -> `startAgentsSession`,
`expAgentsPtr` -> `agentsPtr`, `expAgentsSession` -> `agentsSession`,
`TestExpAgents*` -> `TestAgents*`). `expClient` (the
`*codersdk.ExperimentalClient` local) is kept; `coderd/exp_chats*.go`
and other still-experimental `cli/exp_*.go` commands are intentionally
untouched.

**Frontend:**
- Remove experiment check from `AgentsNavItem` - render when
`canCreateChat` is true
- Remove `agentsEnabled` experiment check from `WorkspacesPage`, then
gate `chatsByWorkspace` on `permissions.createChat` so users without
chat access don't trigger the per-page DB query (Copilot review
feedback)
- Add `FeatureStageBadge` (beta) next to the Coder logo in the Agents
sidebar (desktop + mobile)

**Docs:**
- Remove experiment flag setup instructions from `early-access.md` and
`getting-started.md` (and rename `early-access.md`'s "Enable Coder
Agents" heading to "Set up Coder Agents", since there is no enablement
step left)
- Update `chats-api.md` and `getting-started.md`'s Chats API note to say
"beta" instead of "experimental"
- `docs/manifest.json`: drop "experimental" from the Chats API sidebar
description
- `make gen` regenerated `docs/reference/cli/agents.md` and the CLI
index
- `scripts/check_emdash.sh`: exclude `cli/testdata/*.golden` and
`enterprise/cli/testdata/*.golden` from the new repo-wide emdash lint,
since serpent emits emdash borders in every generated `--help` golden
file

**Tests:**
- Remove `ExperimentAgents` setup from all test files (14 occurrences
across 7 files)
- Update stale "with the agents experiment" comments in
`coderd/x/chatd/integration_test.go` and `coderd/mcp_test.go`


<img width="1185" height="900" alt="image"
src="https://github.com/user-attachments/assets/b420bc8f-41d6-42c6-abd8-ad572533d651"
/>


> 🤖 Generated by Coder Agents
2026-05-01 01:49:00 +10:00

99 lines
3.4 KiB
Go

package cli
import (
"github.com/charmbracelet/lipgloss"
"github.com/coder/coder/v2/codersdk"
)
type tuiStyles struct {
title lipgloss.Style
subtitle lipgloss.Style
statusBar lipgloss.Style
statusBadge lipgloss.Style
selectedItem lipgloss.Style
selectedBlock lipgloss.Style
normalItem lipgloss.Style
dimmedText lipgloss.Style
errorText lipgloss.Style
searchInput lipgloss.Style
separator lipgloss.Style
helpText lipgloss.Style
modeBadgeExec lipgloss.Style
modeBadgePlan lipgloss.Style
userMessage lipgloss.Style
assistantMsg lipgloss.Style
reasoning lipgloss.Style
toolCallStyle lipgloss.Style
toolPending lipgloss.Style
toolSuccess lipgloss.Style
compaction lipgloss.Style
warningText lipgloss.Style
criticalText lipgloss.Style
overlayBorder lipgloss.Style
composerStyle lipgloss.Style
}
func newTUIStyles(renderers ...*lipgloss.Renderer) tuiStyles {
renderer := lipgloss.DefaultRenderer()
if len(renderers) > 0 && renderers[0] != nil {
renderer = renderers[0]
}
return tuiStyles{
title: renderer.NewStyle().Bold(true),
subtitle: renderer.NewStyle().Faint(true),
statusBar: renderer.NewStyle(),
statusBadge: renderer.NewStyle().Padding(0, 1),
selectedItem: renderer.NewStyle().Bold(true),
selectedBlock: renderer.NewStyle().
BorderLeft(true).
BorderStyle(lipgloss.NormalBorder()).
BorderForeground(lipgloss.AdaptiveColor{Light: "63", Dark: "63"}).
PaddingLeft(1),
normalItem: renderer.NewStyle(),
dimmedText: renderer.NewStyle().Faint(true),
errorText: renderer.NewStyle().Foreground(lipgloss.Color("1")),
searchInput: renderer.NewStyle().
BorderStyle(lipgloss.NormalBorder()).
BorderBottom(true),
separator: renderer.NewStyle().Faint(true),
helpText: renderer.NewStyle().Faint(true),
modeBadgeExec: renderer.NewStyle().Bold(true).Foreground(lipgloss.AdaptiveColor{Light: "22", Dark: "42"}),
modeBadgePlan: renderer.NewStyle().Bold(true).Foreground(lipgloss.AdaptiveColor{Light: "130", Dark: "214"}),
userMessage: renderer.NewStyle().Bold(true).Foreground(lipgloss.Color("6")),
assistantMsg: renderer.NewStyle(),
reasoning: renderer.NewStyle().Faint(true).Italic(true),
toolCallStyle: renderer.NewStyle().Foreground(lipgloss.Color("3")),
toolPending: renderer.NewStyle().Faint(true).Foreground(lipgloss.Color("3")),
toolSuccess: renderer.NewStyle().Foreground(lipgloss.Color("2")),
compaction: renderer.NewStyle().Bold(true).Foreground(lipgloss.Color("5")),
warningText: renderer.NewStyle().Foreground(lipgloss.Color("3")),
criticalText: renderer.NewStyle().Foreground(lipgloss.Color("1")).Bold(true),
overlayBorder: renderer.NewStyle().BorderStyle(lipgloss.RoundedBorder()).Padding(1),
composerStyle: renderer.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderTop(true),
}
}
func (s tuiStyles) statusColor(status codersdk.ChatStatus) lipgloss.Style {
color := lipgloss.Color("7")
switch status {
case codersdk.ChatStatusWaiting, codersdk.ChatStatusPending:
color = lipgloss.Color("3")
case codersdk.ChatStatusRunning:
color = lipgloss.Color("4")
case codersdk.ChatStatusPaused:
color = lipgloss.Color("5")
case codersdk.ChatStatusCompleted:
color = lipgloss.Color("2")
case codersdk.ChatStatusError:
color = lipgloss.Color("1")
}
return s.statusBadge.Foreground(color)
}
func (s tuiStyles) truncate(text string, maxWidth int) string {
_ = s
return truncateText(text, maxWidth, "", 3)
}