mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
e57525002c
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
152 lines
3.6 KiB
Go
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}
|
|
}
|