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

152 lines
3.6 KiB
Go

package cli_test
import (
"context"
"os"
"runtime"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/pty/ptytest"
"github.com/coder/coder/v2/testutil"
)
func agentsPtr[T any](v T) *T {
return &v
}
func setupAgentsBackend(t *testing.T) (*codersdk.Client, *codersdk.ExperimentalClient, uuid.UUID) {
t.Helper()
values := coderdtest.DeploymentValues(t)
client := coderdtest.New(t, &coderdtest.Options{
DeploymentValues: values,
})
firstUser := coderdtest.CreateFirstUser(t, client)
expClient := codersdk.NewExperimentalClient(client)
ctx := testutil.Context(t, testutil.WaitLong)
_, err := expClient.CreateChatProvider(ctx, codersdk.CreateChatProviderConfigRequest{
Provider: "openai",
APIKey: "test-api-key",
})
require.NoError(t, err)
_, err = expClient.CreateChatModelConfig(ctx, codersdk.CreateChatModelConfigRequest{
Provider: "openai",
Model: "gpt-4o-mini",
ContextLimit: agentsPtr(int64(4096)),
IsDefault: agentsPtr(true),
})
require.NoError(t, err)
return client, expClient, firstUser.OrganizationID
}
//nolint:revive // Test helper signature keeps t first for consistency with other helpers.
func seedChat(t *testing.T, ctx context.Context, expClient *codersdk.ExperimentalClient, orgID uuid.UUID, seed string) codersdk.Chat {
t.Helper()
chat, err := expClient.CreateChat(ctx, codersdk.CreateChatRequest{
OrganizationID: orgID,
Content: []codersdk.ChatInputPart{
{
Type: codersdk.ChatInputPartTypeText,
Text: seed,
},
},
})
require.NoError(t, err)
return chat
}
type agentsSession struct {
t *testing.T
pty *ptytest.PTY
errCh <-chan error
}
func (s *agentsSession) expect(ctx context.Context, text string) {
s.t.Helper()
s.pty.ExpectMatchContext(ctx, text)
}
func (s *agentsSession) wait(ctx context.Context) error {
s.t.Helper()
return testutil.RequireReceive(ctx, s.t, s.errCh)
}
//nolint:unused // Kept as a small PTY helper for future multi-character input.
func (s *agentsSession) write(text string) {
s.t.Helper()
s.pty.WriteLine(text)
}
func (s *agentsSession) writeRune(r rune) {
s.t.Helper()
_, err := s.pty.Input().Write([]byte(string(r)))
require.NoError(s.t, err)
}
func (s *agentsSession) enter() {
s.t.Helper()
_, err := s.pty.Input().Write([]byte("\r"))
require.NoError(s.t, err)
}
func (s *agentsSession) esc() {
s.t.Helper()
_, err := s.pty.Input().Write([]byte("\x1b"))
require.NoError(s.t, err)
}
func (s *agentsSession) ctrlC() {
s.t.Helper()
_, err := s.pty.Input().Write([]byte{3})
require.NoError(s.t, err)
}
func (s *agentsSession) quit() {
s.t.Helper()
s.writeRune('q')
}
//nolint:revive // Test helper signature keeps t first for consistency with other helpers.
func startAgentsSession(t *testing.T, ctx context.Context, client *codersdk.Client, args ...string) *agentsSession {
t.Helper()
// Reading to / writing from the PTY is flaky on non-linux systems.
if runtime.GOOS != "linux" {
t.Skip("skipping on non-linux")
}
fullArgs := append([]string{"agents"}, args...)
inv, root := clitest.New(t, fullArgs...)
clitest.SetupConfig(t, client, root)
pty := ptytest.New(t)
tty, err := os.OpenFile(pty.Name(), os.O_RDWR, 0)
require.NoError(t, err)
t.Cleanup(func() {
_ = tty.Close()
})
inv.Stdin = tty
inv.Stdout = tty
inv.Stderr = tty
errCh := make(chan error, 1)
tGo(t, func() {
errCh <- inv.WithContext(ctx).Run()
})
return &agentsSession{t: t, pty: pty, errCh: errCh}
}