mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
efd5ce366808643b49bf4a263f02be515e3491d4
680
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
87494c4f88 |
test(agent): de-flake TestAgent_Session_EnvironmentVariables (#27803)
The environment-variable and secret-injection tests reused one long-lived SSH shell, so a closed channel caused later checks to fail with `EOF` and could allow scan-based assertions to pass without finding a value. They now share only the SSH client and use a fresh, deadline-bound session for each variable, isolating failures while preserving environment-precedence coverage. Closes https://linear.app/codercom/issue/PLAT-310. Reviewed and updated by Coder Agents on behalf of @dylanhuff-at-coder. |
||
|
|
8a4adeec32 |
fix(agent): detect IPv6-bound listening ports (#27765)
Closes https://github.com/coder/coder/issues/15675 Dev servers that bind to the IPv6 wildcard address (Next.js, Node's default `http.Server`, Go's `net.Listen` on wildcard addresses, and others) never showed up in the dashboard Ports panel, even though they were listening and reachable. The agent's port scanner only called `netstat.TCPSocks`, which reads `/proc/net/tcp` (IPv4 only); an `[::]` dual-stack socket lives exclusively in `/proc/net/tcp6`, so the panel reported "No open ports were detected." Also scan `netstat.TCP6Socks` and merge the results into the existing dedupe loop. The IPv6 scan failure is non-fatal so hosts with IPv6 disabled (missing `/proc/net/tcp6`, Windows with the v6 stack off) keep their IPv4 results instead of the Ports panel 500ing. Both captures below: `python3 -m http.server 5123 --bind ::` running in the same workspace, built from main vs this branch ([full recordings](https://github.com/coder/coder/tree/recordings/recordings/ipv6-listening-ports)). ## Before  ## After  <details> <summary>Debugging and decision log</summary> - Reproduced on a dogfood workspace: `next-server` listening on `*:3000` per `ss -tlnp`, `curl` returning 200, but `GET /api/v2/workspaceagents/{agent}/listening-ports` returning `{"ports": []}`. Port 3000 (hex `0BB8`) present in `/proc/net/tcp6`, absent from `/proc/net/tcp`. - Confirmed `agent/ports_supported.go` only calls `netstat.TCPSocks`; the library's `TCP6Socks` (parses `/proc/net/tcp6` on Linux, `GetTcp6Table2` on Windows) was never referenced. - Control test: an IPv4-bound (`0.0.0.0`) listener was detected correctly, isolating the bug to the missing IPv6 scan. - Made the IPv6 scan failure non-fatal after review: returning an error would break the entire ports endpoint (HTTP 500 on every poll) on IPv6-disabled hosts, a regression from the current behavior of "IPv4 ports only." Silent fallback matches the spirit of `ports_unsupported.go`, which returns an empty list rather than erroring. - The fix lives in the OS-backed `osListeningPortsGetter` behind the `ListeningPortsGetter` interface from #20842, so the fake used by coderd tests is unaffected. - Dedupe by port in the existing `seen` map covers dual-stack sockets that appear in both tables on Windows. - `TestOSListeningPortsGetter_IPv6` listens on `[::]:0` and asserts detection; it skips when the host can't bind IPv6. Verified it fails against the pre-fix code. </details> > 🤖 This PR was opened by [Coder Agents](https://coder.com/docs/ai-coder/agents) on behalf of @bpmct. |
||
|
|
dc31791c88 |
test: don't use ptytest for client side of SSH session tests (#27681)
In our initial batches of test refactors, I left the SSH session tests using `ptytest` because I (erroneously) thought that we still needed a client side PTY when the SSH server creates a PTY. This is incorrect and plain in-process IO is fine on the client side. closes https://github.com/coder/internal/issues/1400 (again)<!-- If you have used AI to produce some or all of this PR, please ensure you have read our [AI Contribution guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING) before submitting. --> |
||
|
|
66e6f40b8b |
chore: rename agent firewall flags (#27231)
* Alias --boundary-log-proxy-socket-path to --agent-firewall-log-proxy-socket-path * Also alias CODER_AGENT_BOUNDARY_LOG_PROXY_SOCKET_PATH to CODER_AGENT_FIREWALL_LOG_PROXY_SOCKET_PATH * Also Rename related variables and symbols to reflect the new name. <!-- If you have used AI to produce some or all of this PR, please ensure you have read our [AI Contribution guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING) before submitting. --> |
||
|
|
35ade9e3d2 |
feat: collect workspace logs in support bundles (#26694)
Add workspace-side file collection to `coder support bundle` via repeatable --workspace-file flags. The agent resolves the requested paths or globs inside the remote workspace and streams back a tar with a manifest and the collected files; nothing is read from the machine running the command. - Add POST /api/v0/bundle-files to the agent's agentfiles package. - Expand env vars in the agent's environment; paths must then be absolute or start with ~/ (the agent user's home directory). - Support ** globs and tail oversized files. - Record requested patterns, per-path errors, truncation, and the applied limits in a manifest. - Unpack the archive into the bundle under agent/workspace_files/, recording dropped entries in collection_errors.txt. - Write a manifest-only archive marking collection as unsupported for agents that predate the endpoint. - Bound collection: 64 KB request body, 10000 files, 10 MiB per file, 100 MiB total including archive overhead, 110 MiB client-side read cap, 5 minute timeout. Closes #26020 |
||
|
|
b33ff2d851 | fix: redact env var values in agent debug manifest endpoint (#26904) | ||
|
|
953091c7bc |
refactor: use sync.WaitGroup.Go in tests (#26671)
Migrate `wg.Add(1); go func() { defer wg.Done(); ... }()` to
`wg.Go(func() { ... })` in tests.
Where the prior pattern passed the loop variable explicitly via a
closure parameter (`go func(id int) { ... }(i)`), drop the parameter and
reference the loop variable directly. Per-iteration loop variables since
Go 1.22 make this safe.
|
||
|
|
1742003785 |
fix(agent): gate workspace context collection until the agent is ready (#26715)
## Problem
Workspace context surfaced in chat (Coder Agents) is incomplete and racy
on a fresh boot:
- The context panel is missing personal skills (only repo-level skills
under `.claude/skills` show up).
- The MCP section lists `.mcp.json` files but no MCP servers are
registered.
- The Issues panel reports instruction files as unreadable, e.g.
`CLAUDE.md (file: unreadable)` and `.cursorrules (file: unreadable)`
with `symlink resolve: lstat .../AGENTS.md: no such file or directory`.
## Root cause
`agentcontext.Manager` collected and pushed context too eagerly:
- `NewManager` ran an eager resolve at agent `init()`.
- `RunPush` starts as a normal connection routine (`startAgentAPI210`)
with no lifecycle gating, so the first snapshot was pushed
(`Initial=true`) as soon as the agent API connected.
Both happened **before startup scripts finish** and before the lifecycle
reaches `ready`. At that point:
- `CLAUDE.md` / `.cursorrules` symlinks to `AGENTS.md` don't resolve
yet, so `EvalSymlinks` fails and the resolver emits `StatusUnreadable`
"symlink resolve" issues.
- Personal skills haven't synced yet, so they're missing.
- MCP servers connect via `mcpManager.Reload(...)` only **after**
`ready`, so only `.mcp.json` configs appear, with no servers.
That partial, error-laden snapshot is persisted by coderd and can
hydrate a chat.
## Fix
Gate `agentcontext.Manager` until the agent is ready, unconditionally:
- The Manager always starts gated. `NewManager` leaves the zero-value
(version 0) snapshot in place and never walks the filesystem; `RunPush`
withholds version-0 snapshots, so nothing reaches coderd.
- The agent calls `Manager.SetReady()` from the lifecycle transition in
`handleManifest`, right after startup scripts finish (`ready`, or
terminal `start_error` / `start_timeout` so a failed startup still
surfaces whatever context exists).
- On `SetReady`, the Manager performs the first real resolve (version 1)
and broadcasts it; `RunPush` ships it with `Initial=true`. Later changes
(MCP connect, skill edits) re-resolve and push as before.
Eager resolution before `ready` was the bug, not a mode worth
preserving, so the gate is always on rather than an opt-in option. This
aligns the agent-side push with chatd, which already waits for agent
readiness before loading context. No proto/coderd/DB changes: coderd
simply never receives a pre-ready snapshot.
<details>
<summary>Design notes & decisions</summary>
- **Unconditional, not opt-in.** An earlier iteration added the gate as
an opt-in `ManagerOptions.GateUntilReady`. Since the eager
resolve-on-construct was the defect, the option, the eager first
resolve, and the now-dead `resolveLocked` helper were all removed; the
Manager is always gated until `SetReady`.
- **Version 0 is the pre-ready sentinel.** The gated placeholder is just
the zero-value snapshot (version 0); the first real resolve is version
1, so the push loop withholds anything at version 0. An earlier revision
carried a dedicated `Snapshot.Initializing` bool plus an HTTP `/resync`
field, but the push loop was the only consumer and nothing read the HTTP
field, so both were dropped.
- **Defer, don't retry symlinks.** Transient "unreadable" symlinks are
an artifact of collecting before checkout. Deferring until `ready` fixes
all three symptom classes at once and avoids masking genuine post-ready
errors (a broken symlink at `ready` is still reported).
- **Release on terminal startup states too** (`start_error`,
`start_timeout`), so a failed startup still surfaces whatever context
exists instead of gating forever. On reconnect the Manager instance is
reused and stays ready.
</details>
## Tests
- `agentcontext.TestManager_WithholdsCollectionUntilReady` simulates
collection running before startup finishes (broken `CLAUDE.md` /
`.cursorrules` -> `AGENTS.md` symlinks): asserts the gated snapshot is
the empty version-0 placeholder with no resources and no `unreadable`
issues, and that after `SetReady` (target now present) the inventory
resolves cleanly to a single instruction file with no spurious issues.
- `agentcontext.TestRunPush_WaitsForReady` asserts the push loop ships
nothing while gated even when content exists, then ships the full
inventory with `Initial=true` after `SetReady`.
- `agentcontext.TestManager_SetReadyIsIdempotent` covers the version-0
placeholder before ready, the single resolve to version 1 on `SetReady`,
and idempotency across repeated calls.
- Updated `agent.TestAgent_ContextStatePushed`: the first push now
already contains `AGENTS.md` with `Initial=true` and no `UNREADABLE`
resources (no pre-startup empty/partial push).
Validated on the changed packages: `go test -race
./agent/agentcontext/...`, `go test ./agent/ -run
TestAgent_ContextStatePushed`, `golangci-lint run`, `go vet`, `gofmt`
(all clean).
---
🤖 Generated by Coder Agents on behalf of @kylecarbs.
|
||
|
|
bafc86310c |
fix(agent/agentcontext): identify context sources by lexical path (#26616)
## What Identify agent workspace-context **sources** by their lexical (configured) path so `coder exp chat context list` no longer shows the same directory twice, and so a source is shown as the path the operator actually configured. ## Why (the bug) Source identity was the canonical path from `CanonicalizePath`, which resolves symlinks via `EvalSymlinks` **only when the target exists**. That makes canonicalization time-dependent: - At boot the agent seeds sources from `CODER_AGENT_EXP_*_DIRS`. If `~/.coder/skills -> ~/my-agent/agent-rules/skills` and the target does not exist yet (a startup script creates it later), `~/.coder/skills` canonicalizes to the lexical `/home/coder/.coder/skills`. - After the manifest lands (or the target is added directly), the same configured source canonicalizes to the resolved `/home/coder/my-agent/agent-rules/skills`. The same configured source produced two different strings, so dedupe keyed on the string registered both and the list showed one directory twice. Resolving symlinks for identity is also misleading on its own (per @mafredri's review): a source added by a symlink path appears in the list as its resolved target, as if that target had been added explicitly. ## How Source identity is now the **lexical** path: cleaned, `~`-expanded, absolute, with symlinks **not** resolved (new `lexicalPath`; `CanonicalizePath` is refactored to build on it). `AddSource`, `SeedSources`, `HasSource`, `RemoveSource`, and boot seeding all key on this stable identity. `AddSource` still **validates** the resolved (`CanonicalizePath`) path against the allowed roots, so a symlink cannot escape them. Only the identity/display path changed. This replaces the earlier `os.SameFile`/inode dedupe, which was unstable and failed on Windows runners. ## Testing - `go test ./agent/agentcontext/` (full package) and `go vet` pass; `gofmt` clean. - `TestManager_SourceIdentityIsLexicalAndStable`: adds the same symlinked source before and after its target exists and asserts one source whose path is the lexical link (skipped on Windows, matching the package's other symlink tests). - Existing `TestCanonicalizePath_FollowsSymlinks` and `TestValidateSourcePath_*` confirm symlink resolution and the security boundary are unchanged. <details> <summary>Related review findings</summary> Fixes the "duplicate symlinked paths in `context list`" issue from the chat-context system review and the dedupe-ordering question (lexical identity preserves first-come-first-served order). Showing the configured path for **resources** (not just sources) and restoring scope-based skill precedence are separate, larger changes tracked elsewhere. </details> --- *This PR was created by Coder Agents on behalf of @kylecarbs.* |
||
|
|
a1ec9df345 |
test(agent/agentcontext): use resolved temp dirs in symlink tests (#26602)
These symlink resolver tests build expected paths from `t.TempDir()`, but the resolver canonicalizes symlink targets with `filepath.EvalSymlinks`. On macOS, `/var` resolves to `/private/var`, so the raw temp dir path can disagree with the resolver output and flake. Use `testutil.TempDirResolved` in the symlink-sensitive tests so the expectations are canonicalized the same way. Closes https://github.com/coder/internal/issues/1574 Closes ENG-2849 |
||
|
|
27ecd17991 |
refactor: consolidate agent MCP onto a single persistent engine (#26599)
Two MCP code paths both spawned the servers declared in a workspace's `.mcp.json`: the persistent engine in `agent/x/agentmcp` (which owns tool-call execution via `CallTool`) and an ephemeral one-shot runner in `agent/agentcontext` (`mcprunner.go`) that connected, listed tools, and immediately closed each server purely for discovery. Every declared server was launched twice, and the discovery path duplicated the engine's `.mcp.json` parse, transport-build, env-resolve, and connect logic. This makes `agent/x/agentmcp` the single persistent MCP engine. The `agentcontext` manager now reads that engine's per-server catalog in-process through an injected `MCPCatalog` option and surfaces each server as a `KindMCPServer` resource. The engine wires `SetOnReload` to the manager's `Trigger`, so a reload (startup connect or `.mcp.json` edit) re-resolves and re-pushes the pinned resources. Tool-call execution is unchanged: it still flows through the engine's `CallTool` over `POST /api/v0/mcp/call-tool`. The now-dead HTTP discovery surface is removed: the agent `GET /api/v0/mcp/tools` route with `agentmcp.API.handleListTools`, and `workspacesdk.AgentConn.ListMCPTools` with `ListMCPToolsResponse` (mock regenerated). The change nets roughly `-1370` lines, mostly the deleted duplicate runner and its tests. <details> <summary>Decision log</summary> The merge of #26585 made pinned `chat_context_resources` the sole source of workspace context, which surfaced the duplicate spawning. Two options were considered: - **Option A + dependency injection (chosen):** keep `agent/x/agentmcp` as the single persistent engine; `agentcontext` consumes its catalog in-process and stays the orchestrator/owner at the API boundary (it still pushes `KindMCPServer` resources). This is low-risk because `agentcontext` already exposed the `resolver.MCPResources` seam, so the change just rebinds it from the ephemeral runner to the shared engine. - **Option B (rejected):** reimplement persistent pooling, reconnect, singleflight, and race handling inside `agentcontext` and delete `agentmcp`. Too broad, and it discards the engine's tested lifecycle for no behavioral gain. `agentcontext`'s discovery was never what kept servers alive; its runner closed each server immediately after listing tools. The component holding persistent connections was always `agentmcp`, which is why execution already lived there. Consolidating onto it removes the duplicated stack rather than a whole package: both packages survive with distinct roles (`agentmcp` is the engine, `agentcontext` is the orchestrator/owner). </details> Coder Agents generated on behalf of @kylecarbs |
||
|
|
0f37522e6f |
refactor(agent/agentcontext): fixed-location shallow context discovery (#26596)
## Problem
`agent/agentcontext` resolved workspace context by walking the working
directory recursively (depth 8) and matching files by basename. This
over-injected context:
- Instruction-file matching was case-insensitive, so the generated
`docs/reference/api/agents.md` was treated as an instruction file.
- Symlinked instruction files (`CLAUDE.md`, `.cursorrules` ->
`AGENTS.md`) shipped as duplicate resources.
- Nested `AGENTS.md` (e.g. `site/AGENTS.md`) were collected from
anywhere in the tree.
- Skills were discovered from *any* `skills/` directory anywhere in the
tree, and `.mcp.json` from any depth.
Resolving the repo root produced six instruction sources for what was
effectively one file of guidance, plus skills/MCP found by an open-ended
walk.
## What changed
Replace the recursive scan with **fixed-location, shallow** discovery.
Each scan root is inspected at its top level only: the resolver never
descends into subdirectories and never climbs to a parent. Additional
directories are added explicitly as sources (HTTP API) or via the
`CODER_AGENT_EXP_*_DIRS` seeding env vars.
- **Single working-dir scan root.** The working directory is one scan
root. Instruction files and `.mcp.json` are read only at its top level.
- **Fixed-location skills.** Skills are discovered only from `skills`,
`.agents/skills`, `.claude/skills`, `.codex/skills` (one skill per
immediate subdir with a `SKILL.md`), not from arbitrary `skills/`
directories.
- **Case-sensitive instruction names.** Exact
`AGENTS.md`/`CLAUDE.md`/`.cursorrules`; a lower-case `agents.md` is
ignored.
- **Symlink dedup.** Resources are attributed to their resolved target,
so symlinked `CLAUDE.md`/`.cursorrules` collapse into the single
`AGENTS.md`.
- **Watcher** mirrors the same fixed-location set instead of recursively
watching every scan root (no more walking `node_modules`).
- The recursive `walkDir`, `skipDirNames`, `MaxScanDepth`, and
`isSkillsContainer` are removed.
Resolving the repo root now yields `AGENTS.md`, `.mcp.json`, and the
`.agents/skills`/`.claude/skills` skills, with no nested
instruction-file noise.
## Behavior change
`site/AGENTS.md` is no longer auto-injected when the working dir is the
repo root. It loads when the working dir **is** `site/` (its top level),
or when `site/` is added as an explicit source. There is intentionally
**no walk-up** to a `.git` project root: an agent started in a
subdirectory does not auto-inherit ancestor `AGENTS.md`; those
directories are added explicitly.
<details>
<summary>Verification & decision log</summary>
**codex research (confirmed via source).** Instruction files: codex
walks up to the first `.git` ancestor and reads root->cwd, one file
per directory, exact-cased names (`codex-rs/core/src/agents_md.rs`).
Skills: fixed roots (`.agents/skills`, `.codex/skills`,
`$CODEX_HOME/skills`, ...) with bounded in-root recursion
(`core-skills/src/loader.rs`). MCP: `.codex/config.toml` via walk-up; a
project `.mcp.json` is not a runtime source in codex. No resource type
triggers an unbounded downward walk.
**Decisions.**
- Adopt codex's fixed-location, shallow discovery (case-sensitive names,
symlink dedup, top-level-only files, container-only skills).
- **Deliberately omit codex's walk-up to the `.git` project root.** In
Coder the working dir is the scan root and extra directories are added
explicitly (HTTP API / `CODER_AGENT_EXP_*_DIRS`), so the implicit
ancestor climb added surprise without benefit (e.g. `context add ./site`
should scan `./site`, not the repo root).
- Keep `.mcp.json` (codex uses `config.toml`, intentionally not added).
- Include `.claude/skills` and `.codex/skills` in the container list so
the repo's existing `.claude/skills` skills are not regressed; skills
recurse one level inside a container.
**Tests.** `TestManager_WorkingDirScannedShallow` (working dir read at
top level; ancestor root and nested subdir both excluded);
`TestResolver_SkillsOnlyFromFixedContainers`,
`TestResolver_MCPConfigOnlyAtScanRoot`,
`TestResolver_SymlinkedInstructionFilesDeduplicated`,
`TestResolver_InstructionFilesOnlyAtScanRoot`,
`TestResolver_InstructionNamesAreCaseSensitive`. Cap tests use multiple
scan roots.
**Local checks.** `gofmt`, `go vet`, `golangci-lint`, `go test -race`,
and `make lint/emdash` pass for the package.
</details>
---
🤖 Generated by Coder Agents on behalf of @kylecarbs.
|
||
|
|
f5cb2e547e |
feat: include rotated agent logs in support bundles (#26055)
Support bundles previously captured only the active coder-agent.log, losing history across agent restarts. Add an optional `after` filter to the agent's /debug/logs endpoint: without it the endpoint is unchanged (active log only, 10 MiB cap); with it the response includes the active log plus rotated coder-agent-*.log files modified after the cutoff, newest first. Support bundles request the last 24h. Closes #25395 |
||
|
|
2f8bba792a |
feat: push MCP server context and tools from agentcontext (#26533)
## What Live MCP servers and their tools now flow into the `agentcontext` snapshot and are pushed to coderd via `PushContextState`, stored alongside instruction files and skills. Previously the resolver's MCP seam was unimplemented, so live MCP tool lists never reached the pushed snapshot. `agentcontext` is now **fully self-contained** for MCP: it connects to the MCP servers declared in the `.mcp.json` files its own watcher already discovers, lists their tools, and emits `KindMCPServer` resources. It does **not** depend on or modify `agent/x/agentmcp` — that package is left pristine and keeps serving the agent's MCP HTTP API. The two MCP paths run independently, which means the legacy package can be deleted later without touching this code. ## How - **Self-contained runner** (`agentcontext/mcprunner.go`): a one-shot MCP client (connect → initialize → list tools → close) with its own `.mcp.json` parser. A Manager goroutine (`runMCPSync`) reloads it whenever the discovered `KindMCPConfig` `path:contenthash` set changes, then re-resolves so the new tools are published. Per-server connects run in parallel (bounded) with a per-server timeout; a server that fails to connect is recorded as a failure rather than aborting the batch. Each connect also force-kills its subprocess on close, because mcp-go's stdio `Close()` closes stdin and then blocks on `cmd.Wait()` with no kill — a server that ignores stdin-close would otherwise stall the whole reload loop. - **Resource production** (`agentcontext/mcp.go`): `buildMCPServerResources` turns the runner's non-blocking per-server snapshot into `KindMCPServer` resources. Connected servers carry their sorted tools (`StatusOK`); failed servers surface as `StatusUnreadable` issues instead of vanishing; connected-but-no-tools-yet are skipped until a later reload. The content hash is tool-set sensitive. The resolver consumes this through a plain `MCPResources func() []Resource` field (no `MCPProvider` interface). - **Tool names**: emitted exactly as the server reports them. Flattening into a single namespace (e.g. `server__tool`) is left to the control plane in the next step, since each resource already carries the server name. - **Drift**: MCP resources are excluded from the snapshot aggregate/drift hash (`driftResources`). MCP servers connect asynchronously after boot; without this, a server finishing its connect would dirty every hydrated chat even though nothing the user pinned changed. - **Wiring** (`agent.go`): the manager is given `ManagerOptions.MCPExecer`/`MCPUpdateEnv`; `agent/x/agentmcp` is untouched. - **Config validation**: a structurally broken `.mcp.json` surfaces as `StatusInvalid` rather than silently dropping all its servers. coderd already persists `mcp_server`/`mcp_config` resource bodies (including tools), so no coderd or proto changes were required. ## Testing - **Unit**: `buildMCPServerResources` (grouping/sort/skip/failed/hash sensitivity), MCP resources applied via the resolver seam, MCP exclusion from the aggregate hash, `.mcp.json` parsing (transport inference, env expansion), `toolInputSchema`, and `mcpConfigSet` change detection. - **Proto serialization** (`TestDRPCPusher_HappyPathSerializesAllFields`): a `KindMCPServer` resource (tools + input schema) round-trips through `PushContextState` into the `MCPServerBody` wire form, asserting the server name, tool name/description, and the decoded `input_schema`. - **Manager-level, real subprocess** (`TestManager_MCPServerToolsInSnapshot`): a `.mcp.json` points at a re-exec'd fake stdio MCP server; the runner connects it and its `echo` tool surfaces as a `KindMCPServer` resource in the Manager snapshot — the same snapshot pushed to coderd — exercising `runMCPSync` and the resolver wiring end to end. - **Regression** (`TestManager_MCPServerHangingCloseDoesNotStall`): the fake server ignores stdin-close; the test asserts its tool still surfaces, proving the runner force-kills the subprocess instead of stalling the reload. Verified to fail without the fix. - All pass under `-race`; `go build ./...`, `go vet`, and `golangci-lint` are clean on the touched packages. ## Scope / follow-ups This is the agent-side production+push half. The chatd consumer (reading the pinned MCP resources for prompt/tool injection, including any server-prefix flattening of tool names) and removing the legacy `workspaceMCPToolsCache` pull path remain follow-ups, per the RFC rollout. While both `agent/x/agentmcp` and `agentcontext` exist, stdio MCP servers are spawned by both; this is intentional and temporary until `agentmcp` is removed. <details> <summary>Implementation plan and decisions</summary> **Goal:** produce live MCP server resources (with tools) from `agentcontext` and push them to coderd. **Starting state (main):** proto (`PushContextState`, `MCPServerBody`, `MCPTool`), the drpc adapter, coderd storage (`workspace_agent_context_resources`, body kind `mcp_server`), and the resolver's MCP seam already existed; nothing implemented the seam or fed live tools into the snapshot. **Decision (agentcontext fully separate from agentmcp):** `agentcontext` starts and lists its own MCP servers using only the connect-and-list half of an mcp-go client, driven by the `.mcp.json` files its existing watcher discovers. It shares no state with `agent/x/agentmcp` and does not import it. Two earlier revisions of this branch were discarded: (1) relocating `agentmcp` into `agentcontext` (rejected — it duplicates config parsing and file watching `agentcontext` already does); (2) reading `agentmcp`'s cached server snapshot via new accessors (rejected — unnecessary coupling between two packages that should simply run independently while one is being retired). The temporary double-spawn of stdio servers is the accepted cost of keeping the two paths cleanly separated until `agentmcp` is removed. **Decision (no tool-name prefixing, no MCPProvider interface):** the agent pushes raw, unflattened data — server name plus verbatim tool names — and lets the control plane own any `server__tool` flattening. With a single self-contained producer, the `MCPProvider` interface was collapsed into a `func() []Resource` field on the resolver. **Invariants held:** no secrets (env/headers) in pushed resources, only server/tool metadata; MCP excluded from the drift hash; the seam is non-blocking so the resolver never stalls on MCP I/O. </details> --- *This PR was created by Coder Agents on behalf of @kylecarbs.* |
||
|
|
d5ec26beac |
chore: replace testing.Testing with flag lookup (#26552)
In our codebase we have an existing convention of using
`flag.Lookup("test.v")` instead of `testing.Testing()`. This avoids
pulling in the entire `testing` package. Another consequence: some of
our custom linters trigger upon import of the `testing` package which
can lead to unexpected linter errors.
|
||
|
|
992b1ffed1 |
feat(agent): serve context sources over the agent socket (#26526)
## Overview Split from #26466, scoped to **agent-only** changes. This PR exposes the agent's context sources and snapshots over the existing agent socket. There are no changes outside `agent/`. ## What's included - **agentsocket**: context source CRUD (`ContextSources`, `GetContextSource`, `AddContextSource`, `RemoveContextSource`) plus `GetContextSnapshot` and `ResyncContext` RPCs, with matching client methods and proto. The server receives the context `Manager` via `WithContextManager` and returns a clean error when it is absent. - **agentcontext**: the resync JSON response now carries the per-resource `Name`, keeping the HTTP resync payload in sync with the drpc `PushContextState` path in `agentsocket`. - **agent**: passes the context `Manager` to the socket server via `WithContextManager`. ## What's intentionally NOT here - No MCP wiring. There are no MCP additions in `agent.go` or `agentcontext`. MCP ownership will land later in `agentcontext`; this PR does not build on `agent/x/agentmcp`. - No changes to `agent/x/agentmcp` or the `agentcontext` resolver. The socket serves whatever context resources the `Manager` already resolves. <details> <summary>Context for reviewers</summary> This is one of several PRs split out of #26466. Earlier revisions also wired live MCP servers through the socket; that scope was removed so this PR stays purely socket + context plumbing inside `agent/`. The agentcontext resolver, `agent/x/agentmcp`, and `agent.go` MCP startup behavior are unchanged from `main`. </details> --- _Created by Coder Agents on behalf of @kylecarbs._ |
||
|
|
7d95153bf4 |
feat: add coder exp sync list command (#26443)
Add a new subcommand to list all registered sync units and their current
statuses. This provides a quick overview of the dependency coordination
state in a workspace without needing to query each unit individually.
The command supports both table (default) and JSON output formats.
```
$ coder exp sync list
UNIT STATUS READY
unit-a started true
unit-b completed true
unit-c pending false
$ coder exp sync list --output json
[
{
"unit_name": "my-unit",
"status": "started",
"is_ready": true
}
]
```
When no units are registered, the command prints `No units registered`.
<details><summary>Changes across layers</summary>
- `agent/unit`: add `Manager.ListUnits()` method
- `agent/agentsocket/proto`: add `SyncList` RPC, bump API to v1.2
- `agent/agentsocket`: add service and client implementations
- `cli`: add `sync_list.go` command, register in `sync.go`
- Tests: three golden-file test cases (empty list, multiple units, JSON)
</details>
> Generated by Coder Agents on behalf of @SasSwart
---------
Co-authored-by: Cian Johnston <cian@coder.com>
|
||
|
|
b3a95be733 |
fix(agent/agentcontainers): skip TestAPI/Watch on Windows (#26370)
The test does not inject a devcontainerCLI mock, so the updater loop calls the real devcontainer CLI with Unix paths. On Windows this fails, stalling the mock clock advancement. Refs CODAGT-608 |
||
|
|
b439b06ee6 |
feat: persist agent-pushed workspace context snapshots in coderd (#26145)
Replaces the v2.10 `PushContextState` stub with a real coderd write path. Phase 1 of the chat-side persistence story; nothing reads these rows yet. Follows [#25983](https://github.com/coder/coder/pull/25983) and unblocks [CODAGT-569](https://linear.app/codercom/issue/CODAGT-569/enable-agent-api-v210-pushcontextstate-bump-currentminor-wire-coderd). ## What ships ### Schema (`000517_workspace_agent_context.{up,down}.sql`) Two new tables plus `api_key_scope` enum extensions: - `workspace_agent_context_snapshots` (PK `workspace_agent_id` to `workspace_agents(id) ON DELETE CASCADE`): one row per agent, overwritten per push. Holds `version`, `schema_version`, `aggregate_hash`, `snapshot_error`, `received_at`. - `workspace_agent_context_resources` (PK `(workspace_agent_id, source)`): per-resource state. `body_kind` and `status` are `TEXT` + `CHECK` so adding new wire kinds (the RFC's reserved PLUGIN/HOOK/SUBAGENT/COMMAND) is a one-line CHECK update plus a Go switch case. ### SQLC queries (`coderd/database/queries/workspaceagentcontext.sql`) - `UpsertWorkspaceAgentContextSnapshot` - `UpsertWorkspaceAgentContextResource` - `DeleteStaleWorkspaceAgentContextResources` (delete-where-source-not-in) - `GetLatestWorkspaceAgentContextSnapshot` - `ListWorkspaceAgentContextResources` ### Handler (`coderd/agentapi/context.go`) `ContextAPI` is a new sub-API. `PushContextState`: 1. Rejects `schema_version > 1` with a non-`Unimplemented` error so a forward-incompatible agent fails loudly during rollout instead of slipping into the permanent fallback path the `Unimplemented` translation reserves for old coderd deployments. 2. Validates resources: no empty/duplicate sources, every variant maps to a known body kind, every status maps to a known enum value, the `Body` oneof is set (even when status is non-OK, mirroring the wire guarantee so coderd can attribute failures to a known kind). 3. Inside `Database.InTx`, reads the existing snapshot. If the push is not `initial` and `version` is not strictly greater, returns `accepted = false` and leaves stored state untouched. Otherwise upserts the snapshot row, upserts each resource, then runs the stale-source prune so the snapshot and resource rows always agree. 4. Returns `accepted = true` on success. Resource bodies are stored as `protojson(body oneof variant)` in `body JSONB` with `body_kind` as the discriminator. Adding a new field to an existing variant is zero work since `protojson` tolerates new fields; adding a new variant is a CHECK + switch case. ### RBAC + dbauthz - New `ResourceWorkspaceAgentContext` (Create/Read/Update/Delete). - New `SubjectTypeAgentContext` plus `subjectAgentContext` system role and `dbauthz.AsAgentContext` helper. The push handler elevates to this subject; the agent's own role does not get direct write access to the table. - New `workspace_agent_context:*` API key scopes registered in the enum migration; internal-only (not added to `externalLowLevel`). ### Audit These rows are agent-pushed state, not user-authored. They are intentionally not added to `AuditActionMap` and not enumerated in `enterprise/audit/table.go`, matching `boundary_logs`, `workspace_agent_memory_resource_monitor`, etc. `enterprise/audit` tests pass unchanged. ## Tests - `coderd/agentapi/context_test.go`: 12 subtests covering accepts/rejects (schema version, empty/duplicate source, unknown status, missing body), version semantics (stale dropped, same-version replay dropped, `initial=true` overwrites lower version), variant coverage, non-OK status persistence, and the empty-active-set prune case. - `coderd/database/dbauthz/dbauthz_test.go`: 5 `MethodTestSuite` cases covering the new queries. - `coderd/rbac/roles_test.go`: `WorkspaceAgentContext` permission row asserting no human role currently has access. - `coderd/database/migrations/testdata/fixtures/000517_workspace_agent_context.up.sql`: one snapshot + one resource per known body kind plus a non-OK status, so the migration test suite never lands with these tables empty. ## Out of scope (later phases) - Chat hydration (`chats.context_aggregate_hash`, `last_injected_context`). - Dirty-bit fan-out and `PUT /chats/{id}/context`. - Agent-side `POST /api/v0/context/resync` barrier and the `coder exp chat context` CLI. - `codersdk` chat-context wire types and the dashboard Sources drawer. - Removal of the chatd per-turn pull fallback. ## Compat property This is a pure write path. If anything here returns errors the agent's `RunPush` loop backs off, no chat behavior changes, and the workspace keeps behaving exactly like it did before v2.10. <details> <summary>Implementation plan and decision log</summary> Key design calls: 1. **Concurrency**: Accept iff `req.Initial || req.Version > existing.Version`. The strict RFC reading ("version comparison is authoritative") locks restarted agents out because their per-process counter resets to 1; honoring `initial=true` reflects the real reboot reality while still rejecting steady-state replays/out-of-order pushes. 2. **Body encoding**: `protojson` over the oneof variant body proto, stored in JSONB with `body_kind` discriminator. Structured at the API/Go layer, schema-tolerant at the storage layer, and Phase 2 readers round-trip back via `protojson.Unmarshal`. 3. **Schema version rejection**: returns a normal error, not `Unimplemented`. The agent's `RunPush` loop only short-circuits on `Unimplemented`; that escape hatch is reserved for old coderd deployments. A forward-incompatible agent should retry-and-back-off, not flip the connection into permanent fallback. 4. **Validation strictness**: empty sources, duplicate sources, `STATUS_UNSPECIFIED`, and missing `Body` oneof variants are rejected before any write so a misbehaving agent cannot poison the snapshot table. Phase 2 readers can trust every row maps to a known proto variant. </details> _This PR was authored by Coder Agents on Kyle Carberry's behalf._ |
||
|
|
f0ac52e83c |
feat: persist boundary logs (#24812)
Add database persistence to `ReportBoundaryLogs`. On first log for a session, the handler lazy-creates a `boundary_sessions` row, then batch-inserts all `BoundaryLog` entries into `boundary_logs`. Structured logging and usage tracking are preserved. Old boundary clients (no `session_id`) fall back to log-only mode. > [!NOTE] > This PR was authored by Coder Agents. |
||
|
|
112c921235 |
fix(agent/agentcontainers): prevent command injection in shell execer (#26235)
commandEnvExecer.prepare rebuilt commands into a single shell string
using `fmt.Sprintf("%q", arg)`, which produces Go string literals, not
shell-quoted tokens. Go's %q does not escape `$`, backticks, or other
metacharacters that remain active inside double quotes, so an argument
such as `$(...)` was evaluated by the shell as command substitution.
Arguments flow from devcontainer config and workspace-folder, making
this exploitable.
Pass the command to the shell as positional parameters and run `"$@"` so
the shell forwards argv verbatim without re-parsing it.
The Windows previous handling is not required because Coder doesn't
support devcontainers on Windows, so it is removed.
|
||
|
|
b5ef700dd6 |
fix!: only trust x-forwarded-host from configured trusted proxies (#26204)
Subdomain app routing derived the app identity from httpapi.RequestHost, which returned the client-supplied X-Forwarded-Host header verbatim. No middleware validated or stripped that header, so a request from an untrusted peer could forge it. Since the application_connect cookie is scoped to the wildcard apps domain, JavaScript in a share=authenticated app could fetch() with a forged X-Forwarded-Host pointing at a victim's owner-only app; coderd routed and authorized the request as the victim and returned the private app response same-origin to the attacker. Replace RequestHost with httpmw.EffectiveHost, which honors X-Forwarded-Host only when the original socket peer is a configured trusted origin, otherwise falling back to the received Host header. This ties host trust to the same RealIPConfig model already used for X-Forwarded-For and -Proto. Wire it into HandleSubdomain for both coderd and wsproxy, and log both the effective host and the raw received_host. Add coverage: EffectiveHost unit tests assert the trust decision uses the socket peer rather than the spoofable forwarded client IP, and a HandleSubdomain test confirms a forged X-Forwarded-Host from an untrusted peer never reaches token resolution. Refs: https://linear.app/codercom/issue/PLAT-259 |
||
|
|
cfb03f52db |
fix: update stale docs URLs across non-TS files (#25750)
Closes [DOCS-256](https://linear.app/coder/issue/DOCS-256). Sibling to [DOCS-253](https://linear.app/coder/issue/DOCS-253) (#25740). Updates docs URL references across the non-TypeScript surface of `coder/coder` to match the current docs site structure. Source-of-truth for redirects is `coder/coder.com/redirects.json` (parent ticket [DOCS-209](https://linear.app/coder/issue/DOCS-209)). ## What changed | Area | Files | URL mapping | |---|---|---| | Top-level README | `README.md` | `/docs/workspaces` -> `/docs/user-guides/workspace-management`, `/docs/templates` -> `/docs/admin/templates`, `/docs/ides` -> `/docs/user-guides/workspace-access` | | Docs source | `docs/admin/security/0001_user_apikeys_invalidation.md` | `/docs/admin/audit-logs` -> `/docs/admin/security/audit-logs` | | Docs source | `docs/install/cloud/azure-vm.md` | `/docs/coder-oss/latest/install` -> `/docs/install` | | Dogfood | `dogfood/coder/guide.md` | `/docs/ides` -> `/docs/user-guides/workspace-access` | | Helm | `helm/coder/values.yaml` | `/docs/admin/workspace-proxies` -> `/docs/admin/networking/workspace-proxies` | | Enterprise coderd | `enterprise/coderd/coderd.go` | `/docs/admin/encryption` -> `/docs/admin/security/database-encryption` (error message) | | Release tooling | `scripts/release/main_internal_test.go` | `/docs/admin/upgrade` -> `/docs/install/upgrade` (test fixture, matches `generate_release_notes.sh`) | | AI bridge | `aibridge/client.go` | repinned to current `main` SHA on renamed `docs/ai-coder/ai-gateway/monitoring.md`, line range `#L47-L57` | | Example templates | 12 `examples/templates/*/README.md`, `examples/parameters/*`, `examples/parameters-dynamic-options/README.md`, `examples/workspace-tags/README.md`, `examples/parameters/main.tf`, `examples/examples.gen.json` (regenerated) | `/docs/workspaces` -> `/docs/user-guides/workspace-management`, `/docs/templates/parameters` -> `/docs/admin/templates/extending-templates/parameters`, `/docs/templates/dev-containers` -> `/docs/admin/integrations/devcontainers`, `/docs/dotfiles` -> `/docs/user-guides/workspace-dotfiles`, `/docs/about/architecture#agents` -> `/docs/admin/infrastructure/architecture#agents` | | Live notification templates (DB) | New migration `000510_fix_dormancy_notification_docs_urls.up.sql` and `.down.sql` plus the four regenerated SMTP/webhook goldens under `coderd/notifications/testdata/rendered-templates/` | `/docs/templates/schedule#dormancy-threshold-enterprise` -> `/docs/admin/templates/managing-templates/schedule#dormancy-threshold`, `/docs/templates/schedule#dormancy-auto-deletion-enterprise` -> `/docs/admin/templates/managing-templates/schedule#dormancy-auto-deletion` | The migration uses `REPLACE(body_template, ...)` scoped by template id and `LIKE '%/docs/templates/schedule%'`, so it works regardless of which intermediate state (`000232`, `000262`, `000305`, or `000311`) is currently in the row. ## What did not change Historical SQL migrations `000232`, `000262`, `000305`, and `000311` are not modified because migrations are immutable history. The 18 remaining stale URL references in those files are superseded at runtime by migration `000510`. This decision matches the pattern used in the A1 sister PR (#25740). ## Verification - `go test ./coderd/database/migrations/... -count=1` (UP+DOWN) - `go test ./coderd/notifications/ -run TestNotificationTemplates_Golden -update -count=1` to regenerate the four `.golden` files - `go test ./scripts/release/ -run Test_removeMainlineBlurb -count=1` - `make pre-commit` (gen + fmt + lint + slim build) ran clean as part of the commit hook I also fixed a pre-existing emdash on line 35 of `examples/templates/azure-linux/README.md` that the lint flagged once the file entered my diff. The line was already in `main`, but `make gen` rewrites `examples/examples.gen.json` whenever a `README.md` changes, so the line came back as a `+` in the diff against `origin/main` and the `lint/emdash` step refused it. <details> <summary>Pre-mortem</summary> | Risk | Mitigation | |---|---| | Migration overwrites future template edits | Used `REPLACE` instead of full body overwrite. `WHERE id IN (...) AND body_template LIKE '%/docs/templates/schedule%'` further scopes the write | | Goldens drift from migrated body | Regenerated goldens via `-update` after the migration was in place, so the goldens reflect the post-migration state | | Down migration leaves stale URLs | Down migration reverses the REPLACE so a rollback restores the prior URLs | | Fragment loss when redirect strips fragment | Verified the destination `schedule.md` contains `## Dormancy threshold` and `## Dormancy auto-deletion` anchors | | Terraform parse breakage in `examples/parameters/main.tf` | Only comments changed; Terraform parser is unaffected | | Test fixtures in `scripts/release` diverging from `generate_release_notes.sh` | Updated to match the script, which already emits `/docs/install/upgrade` | </details> --- Generated by Coder Agent on behalf of @nickvigilante. |
||
|
|
cc9f10fbf2 | fix(agent/agentcontext): canonicalize scan root in symlink boundary check (#26175) | ||
|
|
cd3692c0c2 |
feat: add agent-side workspace context sources and Agent API v2.10 PushContextState (#25983)
Adds the agent half of the workspace context sources RFC. The agent now resolves instruction files, skills, and MCP configs into a typed `Snapshot`, watches the relevant paths recursively, exposes the source list over a workspace-agent HTTP API, and pushes each `Snapshot` to coderd over a new `PushContextState` RPC on Agent API v2.10. The coderd-side handler is a stub returning `Unimplemented` for now. Real persistence to `workspace_agent_context`, chatd hydration on dirty events, and the `KindMCPServer` MCP provider are tracked by [CODAGT-569](https://linear.app/codercom/issue/CODAGT-569/enable-agent-api-v210-pushcontextstate-bump-currentminor-wire-coderd). This matches the pattern used for v2.7 `ReportBoundaryLogs` in [#21293](https://github.com/coder/coder/pull/21293), which bumped the version and shipped a stub server so the wire and client could iterate before the persistence layer landed. ## What ships ### agent/agentcontext (new package) - `Source`, `Resource` (kinds `instruction_file`, `skill`, `mcp_config`, `mcp_server` plus reserved `plugin`/`hook`/`subagent`/`command`), `ResourceStatus`, `Snapshot`, `ComputeAggregateHash`. - `Manager` owns the in-memory source list, performs the initial resolve synchronously in `NewManager`, runs a re-resolve/watcher loop in `Run`, exposes `AddSource`/`RemoveSource`/`Sources`/`HasSource`/`Snapshot`/`SubscribeChanges`/`Resync`/`SeedSources`/`Close`. - `Resolver` walks scan roots, classifies recognized files, enforces 64 KiB per-resource, 2 MiB aggregate, and 500-resource caps with `StatusOversize`/`StatusExcluded`/`StatusUnreadable`/`StatusInvalid` outcomes, skips `node_modules`/`vendor`/etc., validates symlink targets stay inside the scan root, stamps `SourcePath` on user-derived resources, and optionally pulls MCP server tool lists via an `MCPProvider` interface. MCP config resources ship metadata only (size, hash) so secrets in env blocks never leave the agent. - `Watcher` is a recursive `fsnotify` wrapper with a 250 ms debounce, dynamic arming of newly created directories, and an ENOSPC-tolerant degraded mode that no-ops further syncs until the manager resyncs explicitly. - HTTP API for `GET/POST /sources`, `GET/DELETE /sources/{path}`, `POST /resync` mounted at `/api/v0/context`. - `Pusher` interface plus `RunPush` goroutine with exponential backoff capped at 30 s. `DRPCPusher` adapts the generated `DRPCAgentClient210` to `Pusher` and translates `drpcerr.Unimplemented` to `ErrPushUnimplemented` so the push loop exits cleanly when talking to coderd deployments that have not enabled the real handler. ### agent/proto (v2.10) - New messages `ContextResource`, `PushContextStateRequest`, `PushContextStateResponse` and the `PushContextState` RPC on `service Agent`. - Generated `DRPCAgentClient210` interface and `codersdk/agentsdk.Client.ConnectRPC210` / `ConnectRPC210WithRole`. - `tailnet/proto.CurrentMinor` bumped from `9` to `10`. ### Agent wiring - `agent.Options.Client` declares both v2.9 and v2.10 connectors; `run()` dials with `ConnectRPC210WithRole`. - `apiConnRoutineManager` holds a `DRPCAgentClient210`. Existing v2.8 routines keep their narrower `DRPCAgentClient28` signature thanks to interface embedding. - `startAgentAPI210` is the v2.10 counterpart to `startAgentAPI` for routines that need the new client. The push context state routine uses it. - A `contextManager` is constructed in `agent.init()`, seeded from the existing `CODER_AGENT_EXP_*_DIRS` env vars, started in its own goroutine under `gracefulCtx`, and closed in `agent.Close`. - `handleManifest` calls `Manager.SeedSources` for sources rooted at the manifest directory, then `Resync` after `manifest.Swap`, so the snapshot reflects the workspace working directory immediately instead of waiting for the next filesystem event. - HTTP routes mounted at `/api/v0/context` when the manager is up. ### Coderd stub `coderd/agentapi/context.go` returns `drpcerr.Unimplemented` for `PushContextState`. The real handler that persists `workspace_agent_context` rows, hydrates chats, and emits dirty events lives in CODAGT-569. ## Tests 24 tests across `agent/agentcontext` cover types, paths, resolver behavior with file caps, skill containers, MCP secret omission, symlink target validation, the recursive watcher firing on real fsnotify events, manager source CRUD / `Resync` / `SeedSources` / `Run` lifetime, the HTTP API, the DRPC adapter, and the push retry / initial-flag / unimplemented paths. Passes `go test -race -count=2`. `TestAgent_ContextStatePushed` boots a full agent against `agenttest.FakeAgentAPI` (which now records `PushContextState` traffic) and asserts the seeded `AGENTS.md` appears in a snapshot push with `schema_version = 1`. <details> <summary>Notes for reviewers</summary> - Source CRUD is workspace-agent-token only; coderd is not in the path for source mutation. - Per-resource cap 64 KiB, aggregate 2 MiB, count cap 500; resources past the cap ship with `StatusExcluded` and an empty payload so the aggregate hash still detects content edits. MCP-emitted resources enforce both a per-provider count cap and the aggregate byte cap. - Symlinks inside the scan root are followed; symlinks pointing outside (or broken) are rejected with `StatusExcluded` so credentials reachable via a stray symlink stay off the wire. - The initial push gates `lifecycle = ready` in the eventual full design. For this PR the `SeedSources` plus `handleManifest`-driven `Resync` keeps the snapshot fresh; the live push loop ships now and DRPCPusher translates the coderd `Unimplemented` stub into a clean exit. - The `PLUGIN`/`HOOK`/`SUBAGENT`/`COMMAND` kinds are reserved in proto and Go enums but unused; the Claude Code plugin resolver ships in a follow-up that does not need a schema migration. - Two follow-ups remain, both tracked by [CODAGT-569](https://linear.app/codercom/issue/CODAGT-569/enable-agent-api-v210-pushcontextstate-bump-currentminor-wire-coderd): (1) the chatd-side handler that persists snapshots and dirties chats; (2) the `coder exp chat context` CLI command set for `list`/`show`/`add`/`remove`/`refresh`. </details> _This PR was authored by Coder Agents on Kyle Carberry's behalf._ |
||
|
|
3955df796e |
fix(agent): unify working directory resolution (#26102)
agentssh's CommandEnv, sftpHandler, and agentproc each resolved the session working directory on their own and had drifted: sftpHandler used the configured directory without checking it exists and bypassed the injected EnvInfoer, while the others stat-checked and fell back to home. Home and shell lookups could also skip the EnvInfoer seam through the exported usershell.HomeDir and Get. Resolve through a single usershell.ResolveWorkingDirectory and confine host home and shell lookups to usershell, so SSH sessions, the process API, and tests can no longer diverge and the injected environment is always honored. This also fixes SFTP landing in a configured directory that no longer exists. Refs coder/coder#26099 |
||
|
|
d00ffbd828 |
feat(agent): unify session env via EnvInfoer (#26099)
The agent resolved the session home directory two ways. agentssh went through usershell, while agentproc called os.UserHomeDir directly and skipped its user.Current fallback. Routing both through a single usershell.EnvInfoer makes the resolution consistent, and agentproc now gets the same fallback as the rest of the agent. The shared seam is injectable, so SSH session tests can drive environment resolution without touching real system state. |
||
|
|
45475b803e |
test(agent): remove race in TestAgent_Session_TTY_QuietLogin/Hushlogin (#25865)
The subtest previously called session.Shell(), wrote "exit 0" through a client-side PTY, and then waited indefinitely on session.Wait(). Under the race detector the byte stream occasionally arrived at the agent before the remote shell was in its read loop and was silently discarded; the shell never exited, session.Wait() blocked until the go-test watchdog kicked in and killed the test binary. The agent writes the message of the day announcement banner synchronously in agentssh.startPTYSession before forking the user shell. The subtest now repeatedly sends "exit 0" and the writes/waiting on session.Wait are time bound. Also fixes a pre-existing test bug where the empty bytes intended to create ~/.hushlogin were written to the MOTD path. The previous test passed only because the MOTD file ended up empty, not because the hushlogin code path was exercised. With the file now placed at the correct path, the assertion genuinely validates isQuietLogin. Generated with assistance from Coder Agents. |
||
|
|
20d678b886 |
fix(agent): install connstats callback at statsReporter creation (#25819)
The stats reporter only installed the connstats callback on the TUN device after the report loop negotiated an interval with the server. Traffic that flowed before that point (e.g. an SSH handshake) was silently dropped because the TUN wrapper's stats.Load() returned nil. We now install the connstats callback immediately and we no longer re-install the callback every interval unless the interval changed. Fixes flaky TestAgent_Stats_SSH, TestAgent_Stats_ReconnectingPTY, and TestAgent_Stats_Magic by ensuring the connstats callback is always installed before network traffic can flow. Closes coder/internal#505 Closes CODAGT-517 |
||
|
|
5b692bf1cc |
test: rename ExpectMatchContext to ExpectMatch (#25998)
Cleans the last few instances of ExpectMatch that didn't use the new `(ctx, ...)` variant, then deletes the deprecated method and renames `ExpectMatchContext` to drop the `Context` suffix. |
||
|
|
8b058dc949 |
feat: add coderd_api_websocket_probes_total metric (#25012)
Relates to CODAGT-115 Adds metric `coderd_api_websocket_probes_total`. Every successful heartbeat for a given path will increment the metric. Comparing this with `coderd_api_concurrent_websockets` will give an indication of how many websocket connections are open but in a 'wedged' state (when heartbeats stopped versus when we closed the connection). |
||
|
|
82752844bc |
fix: isolate MCP HTTP transports from DefaultTransport in tests (#25821)
Use testing.Testing() inside createTransport to automatically clone http.DefaultTransport when running in tests. In production, DefaultTransport is used as-is (efficient connection pooling). This fixes the CloseIdleConnections flake class: httptest.Server.Close() calls http.DefaultTransport.CloseIdleConnections(), which disrupts any MCP client sharing that transport. The testing.Testing() check means every MCP transport created during tests gets isolation automatically, with no caller changes needed. Closes coder/internal#1016 Closes PLAT-291 |
||
|
|
7a9125b953 |
fix(agent/agentfiles): merge duplicate file paths instead of rejecting (#25767)
When a caller sends multiple entries for the same literal path, merge their edits into a single entry rather than returning 400. Symlink aliases (different paths, same real file) are still rejected. |
||
|
|
52e73b1343 |
test(agent/agentcontextconfig): isolate TestContextPartsFromDir from host HOME (#25649)
ContextPartsFromDir scans ~/.coder/skills via DefaultSkillsDir. On machines with real skills installed, these leaked into test results. Set HOME/USERPROFILE to temp dirs on the parent test so subtests run in a clean environment. |
||
|
|
c8359d8598 |
fix(agent/agentproc): read process info before output to prevent TOCTOU (#25646)
handleProcessOutput read proc.output() then proc.info() using separate locks. Between the two reads the exit goroutine could finish I/O and set running=false, pairing stale output with final status. On Windows CI this caused OutputExceedsBuffer to flake when the buffer snapshot caught mid-write data (OmittedBytes=0) but info reported the process as exited. Swap the read order so info is read first. The exit goroutine completes cmd.Wait (draining all pipe data) before setting running=false, so seeing Running=false guarantees the subsequent output read reflects the final buffer state. Closes CODAGT-399 |
||
|
|
c650aabbef |
chore: standardize on *_internal_test.go for white-box tests (#25601)
My agent added `//nolint:testpackage` to a test file on one of my PRs. Again. This PR cleans it up across the entire repo and updates the in-repo conventions so future agents stop doing it. The repo already has a precedent for white-box tests that need to touch unexported symbols: `*_internal_test.go` (145+ existing files). The `testpackage` linter's default `skip-regexp` exempts that filename suffix, so the `//nolint:testpackage` directive is unnecessary in every case where someone reached for it. This PR renames 51 such files to `*_internal_test.go` via `git mv` so blame and history follow, and strips the dead directive from 2 files that were already correctly named (`coderd/oauth2provider/authorize_internal_test.go`, `coderd/x/chatd/advisor_internal_test.go`). `.claude/docs/TESTING.md` now documents the rule explicitly under *Test Package Naming*, which is imported into the root `AGENTS.md` via `@.claude/docs/TESTING.md`. The rule: prefer `package foo_test`; if you need internal access, rename the file to `*_internal_test.go` rather than adding a nolint directive. |
||
|
|
cd54861e4f |
fix(agent): set utf8 locale for tmux terminals (#25530)
> Mux is updating this PR on behalf of Mike. ## Summary - Set a UTF-8 `LC_CTYPE` fallback for reconnecting PTYs when no effective UTF-8 locale is present. - Preserve non-empty `LC_ALL` so explicit user locale choices still win. - Add tmux glyph regression coverage for reconnecting PTYs, plus unit coverage for the env helper. - Stabilize the tmux regression by keeping the pane alive until the glyph output is observed. - Keep the env helper unit test expectations OS-aware for Windows and cover unhyphenated UTF8 locales. ## Validation - `go test ./agent/reconnectingpty -run TestWithTerminalEnv -count=1` - `go test ./agent -run '^TestAgent_ReconnectingPTY$/Buffered$' -count=1` - `go test ./agent -run '^TestAgent_ReconnectingPTY$' -count=1` - `make lint` - `git commit` pre-commit hook - `git push` pre-push hook |
||
|
|
7ffeac711c |
fix: correct web terminal glyph rendering and tmux display (#25059)
The web terminal was rendering Claude Code and Codex incorrectly because xterm's custom glyph renderer draws block and quadrant characters with its own geometry. The reconnecting PTY screen backend also exposed `screen.xterm-256color` to the user's shell, which made tmux rendering issues harder to reason about. This PR: * Disables xterm custom glyph rendering so the selected terminal font draws block and quadrant glyphs. * Adds a tiny Powerline-only terminal symbol fallback font so common prompt separators still render when custom glyphs are disabled. * Configures the screen backend to keep the inner shell `TERM` aligned with the browser terminal emulator, including background color erase behavior. * Tightens reconnecting PTY tests around prompt synchronization and `TERM` assertions. <!-- linear:table-colwidths:200,200 --> | Before | After | | -- | -- | | <img src="https://uploads.linear.app/e62091d9-44f5-421c-8e5c-df481fc99003/3c45efce-9d7e-43b4-b24f-88d4d23d294a/ba68155e-949e-4961-b0b2-124757cb07bb?signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXRoIjoiL2U2MjA5MWQ5LTQ0ZjUtNDIxYy04ZTVjLWRmNDgxZmM5OTAwMy8zYzQ1ZWZjZS05ZDdlLTQzYjQtYjI0Zi04OGQ0ZDIzZDI5NGEvYmE2ODE1NWUtOTQ5ZS00OTYxLWIwYjItMTI0NzU3Y2IwN2JiIiwiaWF0IjoxNzc4MTgxNjUwLCJleHAiOjE4MDk3NTIyMTB9.45f1ZzBpWOF5OCJV0xHfICdpyRQ1UoGMbJjLYPqeAkg " alt="Before: Claude Code logo rendering is distorted in the web terminal outside and inside tmux" width="640" /> | <img src="https://uploads.linear.app/e62091d9-44f5-421c-8e5c-df481fc99003/26b0a109-5e21-4000-b1b5-ddac87c409d4/46a301c2-a815-419a-92d2-c51cecdefe40?signature=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwYXRoIjoiL2U2MjA5MWQ5LTQ0ZjUtNDIxYy04ZTVjLWRmNDgxZmM5OTAwMy8yNmIwYTEwOS01ZTIxLTQwMDAtYjFiNS1kZGFjODdjNDA5ZDQvNDZhMzAxYzItYTgxNS00MTlhLTkyZDItYzUxY2VjZGVmZTQwIiwiaWF0IjoxNzc4MTgxNjUwLCJleHAiOjE4MDk3NTIyMTB9.SQVwUbtaf2OrpjRJPkRH3uc0nPqad0bNBVvcRyuR6NQ " alt="After: Claude Code logo renders correctly in the web terminal outside and inside tmux" width="640" /> | ## Validation * `go test ./agent -run '^TestAgent_ReconnectingPTY$' -count=1` * `pnpm --dir site test -- src/theme/constants.test.ts` * `pnpm --dir site lint:types` * `pnpm --dir site check` * `pnpm --dir site build` * `git commit` pre-commit hook passed * `git push` pre-push hook ran and printed the repo CI monitoring hint > Mux worked on this PR on Mike's behalf. --------- Co-authored-by: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> |
||
|
|
51b531f5b3 |
chore: 'go generate' mockgen to use go tool wrapper (#25490)
Calling `mockgen` relies on the executable in the `$PATH`. Using `go tool` uses the one defined in `go.mod` |
||
|
|
1afc6d4fd0 |
feat: structured disconnect attribution for agent logs (#25191)
Implements [PLAT-60](https://linear.app/codercom/issue/PLAT-60/enhance-disconnect-logs-with-structured-reason-attribution): adds structured disconnect attribution to disconnect logs throughout the agent and tailnet packages. Every disconnect log site now carries structured slog fields. All existing logs remain; existing messages are preserved with the fields added alongside. New fields on disconnect log lines: - `connect_type` — which layer disconnected: `server_to_agent`, `agent_to_client`, or `client_to_server` - `disconnect_reason` — categorical reason: `graceful`, `network_error`, `server_shutdown`, etc. - `disconnect_expected` — whether the disconnect is normal operation (`true`) or should be investigated (`false`) - `disconnect_initiator` — who started it: `client`, `agent`, `server`, or `network` (control-plane sites only) - `disconnect_detail` — free-form supplemental info (where useful) ## What's covered **Control plane (`server_to_agent`):** coordination RPC, DERP map subscriber, agent runLoop, agent Close, `BasicCoordination.Close`, `Controller.run`. **Data plane (`agent_to_client`):** SSH sessions, reconnecting PTY, JetBrains port-forwarding. <details> <summary>Control-plane sites</summary> | Site | Reason | Initiator | |---|---|---| | `agent/agent.go` `runLoop` EOF | `network_error` | `network` | | `agent/agent.go` `runCoordinator` deferred exit | `server_shutdown` / `graceful` / `network_error` | `agent` / `server` / `network` | | `agent/agent.go` `runDERPMapSubscriber` deferred exit | same (shared `classifyCoordinatorRPCExit`) | same | | `agent/agent.go` `Close` shutdown timeout | `server_shutdown` + detail | `agent` | | `agent/agent.go` `Close` clean coord disconnect | `server_shutdown` | `agent` | | `tailnet/controllers.go` `BasicCoordination.Close` | `graceful` or `network_error` | `c.initiator` | | `tailnet/controllers.go` `Controller.run` `net.ErrClosed` | `network_error` | `network` | </details> <details> <summary>Data-plane sites</summary> | Site | Reason | Notes | |---|---|---| | `agent/agentssh/agentssh.go` SSH session closed | free-form (`graceful`, `process exited with error status: N`, etc.) | Also sets `closeCause("normal exit")` for clean exits so coderd's `connection_log.DisconnectReason` is no longer empty | | `agent/reconnectingpty/server.go` PTY closed | `server_shutdown`, error string, or `graceful` | | | `agent/agentssh/jetbrainstrack.go` channel closed | `normal close` or error string | Previously passed empty reason | </details> <details> <summary>Bug fix</summary> The deferred `disconnected from coordination RPC` log no longer fires when the initial `Coordinate()` RPC call fails before any connection is established. </details> Refs PLAT-60. --- _This PR was prepared by Coder Agents on behalf of @Emyrk._ **Manually QA'd a lot of common disconnects** --------- Co-authored-by: Coder Agents <noreply@coder.com> |
||
|
|
792f0b4902 |
feat: add personal skill resolver (#25362)
> Mux updated this PR on behalf of Mike. ## Stack Context This stack splits experimental personal skills into smaller reviewable PRs. Personal skills are user-owned `SKILL.md` files stored by Coder and injected into chatd alongside workspace skills. Stack order: 1. #25362 personal skill resolver 2. #25363 storage, permissions, API, and SDK 3. #25365 API test coverage 4. #25366 chattool and chatd integration 5. #25066 settings UI and docs 6. #25386 personal skills slash menu ## What? Adds the shared personal skill parser and resolver package, plus reusable skill-name validation exported from `workspacesdk`. The parser enforces the full personal skill contract: max raw size, kebab-case name, max name length, and non-empty body. ## Why? The rest of the stack needs one source-aware resolver for personal and workspace skills, including collision handling and qualified aliases. Keeping personal skill constraints in the parser prevents callers from accidentally parsing invalid personal skills. ## Validation - `go test ./coderd/x/skills ./codersdk/workspacesdk` - pre-commit hooks on this branch |
||
|
|
5e701d3075 |
test: fix TestWatcher_SharedParentRefcount on macOS (#25379)
`TestWatcher_SharedParentRefcount` was deterministically broken on macOS: `t.TempDir()` lives under `/var` which is a symlink to `/private/var`, but the watcher canonicalizes paths via `filepath.EvalSymlinks` before storing them, so the test's `w.dirs[dir]` lookup missed and returned `0` instead of `2`. Adds `testutil.TempDirResolved`, a shared helper that returns `t.TempDir()` with symlinks resolved and falls back to the raw temp dir on error (Windows-friendly). Migrates the matching inline `EvalSymlinks(t.TempDir())` callsites in `agent/agentgit/agentgit_test.go` to use it. Closes https://github.com/coder/internal/issues/1531 |
||
|
|
5b87d7b74f |
feat(agent/agentcontextconfig): discover skills from ~/.coder/skills (#25271)
The default skills lookup only scanned the project-relative .agents/skills directory, so personal skills had to be repeated per project or wired in via CODER_AGENT_EXP_SKILLS_DIRS. Now the default is the comma-separated list ~/.coder/skills,.agents/skills, which lets discoverSkills's existing first-occurrence-wins policy prefer home-scoped skills over project ones with the same name. The change is additive when ~/.coder/skills is absent (missing directories are silently skipped in discoverSkills) and unaffects users who set the env var explicitly. Closes CODAGT-403 |
||
|
|
5be959e111 | fix(agent): retry devcontainer sub-agent rejection test (#25187) | ||
|
|
147f50c5e8 |
fix(agent/x/agentmcp): watch MCP config files for late-appearing or rewritten config (#25172)
## Bug `agent/x/agentmcp/Manager` resolves its config paths once at boot, calls `Reload`, and then only re-stats them lazily when a `GET /api/v0/mcp/tools` request arrives (PR #24700). If any of the manager's MCP config files (`~/.mcp.json` by default, or whatever paths `agentcontextconfig.MCPConfigFiles()` resolves from `CODER_AGENT_EXP_MCP_CONFIG_FILES`) is created, atomically rewritten, or removed _after_ that initial `Reload` and _before_ the next tools HTTP request, the manager keeps serving the stale (often empty) snapshot. `parseAndDedup` silently swallows `fs.ErrNotExist`, so a late-appearing file looks indistinguishable from "no config" until something pokes the manager again. This affects any workspace where the file lands after `MarkStartupSettled` fires, including: - a startup script that writes `~/.mcp.json` after MCP init - a user creating or editing the file mid-session - an installer, dotfiles step, or sync tool writing the file later in startup - another agent process (Claude Code, etc.) producing the file out-of-band - editor rewrites that land as `Write + Chmod + Rename` bursts ### Concrete repro (dual-agent workspace) The race is easiest to reproduce on dual-agent workspaces (inner sandbox + outer host), where the inner agent's `scriptRunner` has `script_count=0` and `mcpManager.Reload` fires at ~t+0.3 s while the host agent writes `~/.mcp.json` ~21 s later. Timeline from `workspace-otto-aa16`: - agent up `20:23:38.918` - lifecycle Ready `20:23:39.200` - MCP config file Birth `20:24:00.460` (~21 s gap) - no MCP log lines for 8 minutes - first `GET /api/v0/mcp/tools` at `20:32:11.812` logs `[warn] mcp: mcp reload canceled by caller`, takes 4946 ms; subsequent turns are cached at 2 ms. The single-agent case has the same race; it's just usually narrow enough that the next HTTP request masks it, at the cost of a multi-second stall on the first call that has to do the lazy reload itself. PR #25034's `MarkStartupSettled` does not help: "settled" fires before the file is necessarily on disk. ## Fix Add an fsnotify-backed `configWatcher` to `agent/x/agentmcp/Manager`. The watcher consumes whatever paths the manager is told to reload, which is the same `[]string` returned by `agentcontextconfig.MCPConfigFiles()`. For each path the watcher: - Watches the **parent directory** of the path, not the file itself. This handles late creation, atomic rewrite (rename + create), and deletion uniformly because inotify watches on individual non-existent files return `ENOENT` and are lost across renames. The pattern matches `agent/agentcontainers/watcher`. - Walks up to the first existing **ancestor directory** when the parent does not yet exist, and re-arms deeper on `Create` events that promote an unrealized path. - Refcounts directory watches so multiple configured paths sharing a parent dir only register one inotify watch. - Resolves symlinks **once at arming time** via `filepath.EvalSymlinks`; never chases arbitrary symlink targets on events. - Debounces multi-event editor writes through a single `quartz.AfterFunc` timer so a `Write + Chmod + Rename` burst produces one reload. - Fires a debounced callback that calls `Manager.Reload`, which routes through the existing singleflight so concurrent triggers coalesce. - Re-syncs on every `Reload` call so a future path-list change is picked up. Lifecycle: the watcher is armed lazily on the first `Reload` (no goroutine cost for unit tests that never reload). `Manager.Close` marks the manager closed and closes its `closedCh` before tearing down the watcher, so any in-flight watcher-driven reload observes the close via `waitReload` and returns `ErrManagerClosed` instead of blocking `firesWG.Wait()` on a stuck connect. The watcher then waits for its goroutine and any in-flight debounced `fire` callback before returning. `parseAndDedup` behavior is unchanged: `fs.ErrNotExist` still records an empty snapshot. With the watcher armed before that snapshot is committed, any `Create` event that races `parseAndDedup` is still delivered. This is the agent-side complement to PR #25169, which fixes chatd's mid-turn workspace MCP discovery. `MarkStartupSettled` semantics are not changed. ## Tests (`agent/x/agentmcp/configwatcher_internal_test.go`) All new tests pass with the fix and fail without it. No `time.Sleep`; synchronization uses `testutil.Eventually` for fsnotify-driven assertions and the quartz mock clock for debounce assertions. - `TestWatcher_LateFileTriggersReload` - the late-file regression: empty dir, settle startup, `Reload` sees no file, write the file later, watcher reloads, tools appear. - `TestWatcher_RewriteTriggersReload` - existing file overwritten with a new server list, watcher reloads, cache reflects new server. - `TestWatcher_RemovalTransitionsToEmpty` - delete the file, watcher reloads, manager transitions to empty cleanly. - `TestWatcher_DebouncesBurst` - quartz mock clock; three back-to-back `scheduleFire` calls produce exactly one `onChange` after `AdvanceNext`. - `TestWatcher_CloseStopsGoroutine` - construct/Reload/Close five times to surface goroutine or fd leaks under `-race`. - `TestWatcher_DualAgentHTTPNoStall` - integration: write file after `Reload`, wait for watcher reload, then `GET /tools` returns the MCP tools in less than `testutil.WaitShort` instead of the multi-second "reload canceled" stall. - `TestWatcher_LateParentDirTriggersReload` - parent dir doesn't exist at `Reload` time; create the dir then the file; watcher re-arms deeper and reloads. - `TestWatcher_SharedParentRefcount` - two configured paths share a parent dir; only one inotify watch is registered and both reload on changes. - `TestWatcher_CloseDoesNotStallOnInFlightReload` - installs a `connectStartedHook` to block a watcher-driven reload mid-`connectAll`, then asserts `Close()` returns within `WaitMedium`. Regression-verified: reverting the close-ordering causes the test to time out. ## Acceptance checklist - [x] `go test ./agent/x/agentmcp/... -race -count=1` passes (also `-count=5`). - [x] All `./agent/...` tests pass under `-race -short`. - [x] No emdash, endash, or ` -- `; `scripts/check_emdash.sh` clean. - [x] No `time.Sleep` in tests. - [x] New tests fail without the fix and pass with it (verified by temporarily disabling `m.armWatcher(paths)` and by reverting `Close()` ordering). ## Out of scope No changes to chatd's mid-turn workspace MCP discovery (PR #25169) or `MarkStartupSettled` semantics. --- <sub>This pull request was prepared by a [Coder Agents](https://coder.com/docs/admin/ai-coder) run.</sub> |
||
|
|
3986aa8a51 |
feat(agent/agentfiles): add post-fail diagnostic hints for edit_files (#25092)
When fuzzyReplace exhausts its passes, append a hint to the generic "search string not found" error. Inversion: if search did not match but replace does, list the lines where replace appears. Miscount: when a search line agrees with a file line except for the count of one repeated rune, name the codepoint and counts. Miscount takes precedence; both firing could direct an agent to swap fields and corrupt the inversion anchor. Did you swap "search" and "replace"? Your replace string appears at line 12, 47, 89. Your search has 32 "─" (U+2500); the file has 37 at line 182. Closes CODAGT-330 |
||
|
|
ca6450cf94 |
fix(agent): gate MCP tool discovery on startup (#25034)
The first `/mcp/tools` request could race workspace startup and return an empty tool list before startup scripts had a chance to write `.mcp.json`. Chatd may only discover tools once for a turn, so that empty response could hide workspace MCP tools even though the agent loaded them later. Make the manager wait for startup to settle before treating missing MCP config files as a real empty state. Tool listing now goes through one manager-owned path that starts reload work independently of caller cancellation; caller contexts only bound that caller's wait. After the first reload body settles, transient reload errors return cached tools with the error so the HTTP handler can degrade to the last known tool set instead of returning `[]`. The handler is intentionally thin: it asks the manager for tools, logs any degraded path, and still returns the tool response shape callers already expect. Tests cover startup gating, caller-canceled waits, manager close, reload timeout via quartz, and cached-tool fallback after a later reload error. |
||
|
|
3a9080fff6 |
feat: tag chat-originating agent logs with chat_id (#25019)
Workspace-agent logs emitted while serving chatd-driven requests were not correlated with the originating chat, making agent logs hard to attribute to the corresponding/originating chat. This adds agent-side chat context middleware that parses `Coder-Chat-Id` once, enriches agent access logs and structured handler/background logs, and adds a chatd bridge log when chat headers are attached to an agent connection. Closes CODAGT-324 |
||
|
|
1ba7139f21 |
feat: add session correlation fields to BoundaryLog proto (#24809)
1 of 9 [next >>](https://github.com/coder/coder/pull/24811) RFC: [Bridge ↔ Boundaries Correlation RFC](https://www.notion.so/Bridge-Boundaries-Correlation-313d579be59281f3b4efdbfd6896775a) Adds three new proto fields for boundary session correlation. **`ReportBoundaryLogsRequest`** - `session_id` (string, field 2) — UUID generated by boundary at startup, shared across all batches from a single run. - `confined_process` (string, field 3) — name of the confined process (e.g. `claude-code`, `codex`, `copilot`). **`BoundaryLog`** - `sequence_number` (uint64, field 4) — monotonically increasing counter per session, primary ordering key when boundary is in use. `BoundaryLog.time` already existed at field 2; no change needed there. API version bumped to v2.9. No behaviour change in coderd or the agent. This is a pure schema bump that the boundary repo will consume in its own stack. > Generated by Coder Agents |
||
|
|
0bb09935bc |
feat: add computer-use provider selection for AI agents (#24772)
Adds a deployment-wide setting to select the computer-use provider (Anthropic or OpenAI) for AI agents, plus the OpenAI computer-use runner needed to honor that selection. The setting is stored in `site_configs` under `agents_computer_use_provider`, defaults to Anthropic when unset, and is exposed via experimental GET/PUT endpoints under `/api/experimental/chats/config/computer-use-provider`. The chatd computer-use tool now dispatches to either `runAnthropicComputerUse` or `runOpenAIComputerUse` based on the resolved provider, with provider-specific result metadata for OpenAI screenshots. Frontend adds a provider dropdown to the Agents Experiments settings page nested under the virtual desktop toggle, with disabled state handling while virtual desktop is off and skeleton loaders while config queries are in flight. Hugo and Codex review follow-up: - Uses shared provider validation and clearer computer-use constant names. - Removes stale OpenAI pending-safety-checks commentary. - Documents why provider result metadata is needed for OpenAI screenshots. - Keeps the computer-use subagent visible when provider credentials are missing, then returns a clear spawn-time configuration error. - Uses OpenAI's recommended 1600x900 screenshot geometry to preserve the native 16:9 aspect ratio. - Moves OpenAI-specific computer-use helpers into `coderd/x/chatd/chatopenai/computeruse` after rebasing onto the provider package refactor in `main`. - Converts OpenAI pixel scroll deltas to Coder desktop wheel-click amounts. - Preserves OpenAI pointer modifiers with key down/up desktop actions and rejects unsupported non-left double-click buttons explicitly. - Maps OpenAI back/forward side-button clicks to browser navigation key actions. - Defaults omitted OpenAI click buttons to left-click. - Retries mouse release cleanup if the final OpenAI drag release fails. - Keeps computer-use subagent availability messages stable when provider config cannot be loaded, while logging the backend error. - Releases remaining OpenAI modifier keys if a synthetic key-up cleanup action fails. - Updates Storybook interaction stories so provider snapshots show the selected final provider. > Mux updated this PR description on behalf of Mike. |