Files
coder/cli/exp_chat_test.go
T
Kyle Carberry 966dd89537 feat: add chat context source CLI and agent-token refresh (#26577)
Adds the `coder exp chat context` CLI for managing workspace context
sources, plus the agent-token refresh endpoint the in-workspace refresh
relies on. Part of breaking the "Workspace Context Sources for Coder
Agents" RFC (#26466) into small, reviewable PRs.

## What this adds

**CLI (`coder exp chat context`)**, talking to the agent's local IPC
socket from inside the workspace:

- `list` lists the registered scan roots (built-in defaults are not
shown).
- `show <path>` shows a source and the resources the agent resolves from
it, including failures.
- `add <path>` registers a path as an additional context source. With
`--chat`, it keeps the legacy one-shot behavior (read context from the
path once and inject it into a single chat).
- `remove <path>` unregisters a source.
- `refresh [<chat>]` re-pins chat context to the agent's latest
snapshot.

**Agent-token refresh path** for the no-argument `refresh`:

- `refresh <chat>` uses the existing user-facing
`ExperimentalClient.RefreshChatContext` (already on main) and works from
anywhere.
- `refresh` with no argument runs inside the workspace: it re-resolves
the agent's sources over the context socket (catching freshly-cloned
repos and startup-script writes), then asks the agent, authenticating
with its own token, to re-pin every drifted chat. No `coder login`
required.
- This adds `agentsdk.RefreshChatContext` and `POST
/api/v2/workspaceagents/me/experimental/chat-context/refresh`
(`workspaceAgentRefreshChatContext`), mirroring the existing clear
endpoint's agent-token auth model.

## Testing

- `go test ./cli` (`TestExpChatContextAdd`, `TestParseChatID`,
`TestResolveContextSourcePath`)
- `go test ./coderd/x/chatd -run TestChatContextRefreshFromAgentToken`
(end-to-end: echo-provisioned agent pushes a snapshot, drifts a bound
chat, the agent-token refresh re-pins it, and an agent-less chat stays
untouched)
- `go build ./...`, `go vet`, `golangci-lint`, `make gen` (no generated
changes; experimental commands are excluded from CLI golden/doc
generation)

<details>
<summary>Design notes</summary>

This is **Split 4** of #26466. Split sequence:

1. #26558 - prompt pin consumption (merged)
2. #26570 - `codersdk` context resource types (merged)
3. #26573 - the context indicator UI (merged)
4. **This PR** - the CLI + agent-token refresh.
5. The context diff (`changes`, `ChatContextResourceChange`, the changes
dialog, `buildContentPatch`) - last.

Key points:

- The agent-local context subsystem (`agent/agentsocket` IPC for source
CRUD, snapshot, resync), the user-facing
`ExperimentalClient.RefreshChatContext`, and the per-chat
`chatd.RefreshChatContext` all already exist on main, so this split is
the CLI surface plus the small agent-token refresh endpoint that fans
out per-chat refresh across an agent's drifted chats.
- `add <path>` resolves relative paths to absolute before handing them
to the agent (which requires canonical paths) but preserves a leading
`~` for the agent to expand against its own home.
`TestResolveContextSourcePath` covers this.
- The agent endpoint is annotated `@x-apidocgen {"skip": true}`,
matching the other agent-token chat-context endpoints.
- No diff/changes rendering is involved; that lands in the final split.

</details>

*This PR was created by Coder Agents on behalf of @kylecarbs.*
2026-06-22 12:15:27 -06:00

43 lines
1.2 KiB
Go

package cli_test
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/clitest"
)
func TestExpChatContextAdd(t *testing.T) {
t.Parallel()
t.Run("RequiresPathArgument", func(t *testing.T) {
t.Parallel()
// `add` registers a context source identified by <path>, so the path
// argument is required and a bare invocation is a usage error.
inv, _ := clitest.New(t, "exp", "chat", "context", "add")
err := inv.Run()
require.Error(t, err)
require.Contains(t, err.Error(), "wanted 1 args but got 0")
})
t.Run("RequiresWorkspaceSocket", func(t *testing.T) {
t.Parallel()
// Source registration talks to the agent over its local socket, so
// outside a workspace it fails to connect rather than silently doing
// nothing. Point at a socket path that does not exist so the dial
// fails deterministically (and never touches a real agent socket).
missingSocket := filepath.Join(t.TempDir(), "agent.sock")
inv, _ := clitest.New(t, "exp", "chat", "context", "add", t.TempDir(),
"--socket-path", missingSocket)
err := inv.Run()
require.Error(t, err)
require.Contains(t, err.Error(), "inside the workspace")
})
}