Files
coder/cli/agents_e2e_test.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

94 lines
2.6 KiB
Go

package cli_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/testutil"
)
func TestAgentsE2E(t *testing.T) {
t.Parallel()
t.Run("EmptyStateBoot", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, _, _ := setupAgentsBackend(t)
session := startAgentsSession(t, ctx, client)
session.expect(ctx, "No chats yet. Press n to start a new chat.")
session.quit()
require.NoError(t, session.wait(ctx))
})
t.Run("ListAndNavigate", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, expClient, orgID := setupAgentsBackend(t)
_ = seedChat(t, ctx, expClient, orgID, "alpha nav seed")
_ = seedChat(t, ctx, expClient, orgID, "bravo nav seed")
_ = seedChat(t, ctx, expClient, orgID, "charlie nav seed")
session := startAgentsSession(t, ctx, client)
session.expect(ctx, "charlie nav seed")
session.expect(ctx, "enter: open")
session.enter()
session.expect(ctx, "esc")
session.esc()
session.expect(ctx, "enter: open")
session.quit()
require.NoError(t, session.wait(ctx))
})
t.Run("SearchFilter", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, expClient, orgID := setupAgentsBackend(t)
_ = seedChat(t, ctx, expClient, orgID, "alpha filter seed")
_ = seedChat(t, ctx, expClient, orgID, "zulu filter seed")
session := startAgentsSession(t, ctx, client)
session.expect(ctx, "alpha filter seed")
session.expect(ctx, "enter: open")
session.writeRune('/')
session.expect(ctx, "/ ")
for _, r := range "zzzznotamatch" {
session.writeRune(r)
}
session.expect(ctx, "No matches.")
session.ctrlC()
require.NoError(t, session.wait(ctx))
})
t.Run("ExistingChatHistory", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, expClient, orgID := setupAgentsBackend(t)
chat := seedChat(t, ctx, expClient, orgID, "direct open seed")
session := startAgentsSession(t, ctx, client, chat.ID.String())
// The initial render contains both the chat title/content
// and the status bar in a single frame. Their relative
// order in the PTY byte stream depends on async title
// generation, so matching them with separate sequential
// expects is racy. Instead, just confirm the seed text is
// visible (proving we are in the chat view), then verify
// esc navigates back to the list.
session.expect(ctx, "direct open seed")
session.esc()
session.expect(ctx, "enter: open")
session.quit()
require.NoError(t, session.wait(ctx))
})
}