Commit Graph
100 Commits
Author SHA1 Message Date
Kyle Carberry b7a7683ac0 fix(chatd): harden cross-replica relay for chat stream parts (#22533)
## Problem

Subscribers connecting to a different replica than the one running the
chat see full messages appear but no streaming partials (`message_part`
events). The relay mechanism that forwards ephemeral parts across
replicas had several bugs.

## Root Causes

1. **`openRelay()` blocked the event loop** — The WebSocket dial (TCP +
TLS + HTTP upgrade) to the worker replica ran synchronously inside the
select loop. While dialing, no events could be processed, channels
filled up, and parts were silently dropped.

2. **Relay drops were permanent** — When the relay WebSocket closed
mid-stream, `relayParts` was set to nil and never reopened. No status
notification would re-trigger it since the chat was still running on the
same worker.

3. **`drainInitial` snapshot race** — The `default` case in the initial
drain loop caused the snapshot to be empty if the remote hadn't flushed
data yet (common immediately after WebSocket connect).

4. **Duplicate event delivery** — The `preloaded` slice caused snapshot
events to be sent both in the return value and re-sent through the
channel goroutine.

## Fixes

### `coderd/chatd/chatd.go` (Subscribe method)
- **Async relay dial**: `openRelayAsync()` spawns a goroutine to dial
the remote replica. The result (channel + cancel func) is delivered on a
`relayReadyCh` channel that the select loop reads without blocking.
- **Relay reconnection**: When the relay channel closes, a 500ms timer
fires. The handler re-checks chat status from the DB and reopens the
relay if the chat is still running on a remote worker.
- **Snapshot parts via channel**: Relay snapshot + live parts are
wrapped into a single channel so they flow through the same path,
avoiding races with the select loop.

### `enterprise/coderd/chats.go` (newRemotePartsProvider)
- **Timer-based drain**: Replaced `default` with a 1-second timer. After
the first event, `Reset(0)` switches to non-blocking drain for remaining
buffered events.
- **Remove preloaded duplication**: The goroutine now only forwards new
events; snapshot events are returned to the caller directly.

## Testing

All existing tests pass:
- `TestInterruptChatBroadcastsStatusAcrossInstances`
- `TestSubscribeSnapshotIncludesStatusEvent`
- `TestSubscribeNoPubsubNoDuplicateMessageParts`
- `TestSubscribeAfterMessageID`
- `TestChatStreamRelay/RelayMessagePartsAcrossReplicas`
2026-03-02 19:57:13 -05:00
Kyle Carberry b8a74a4fcb feat: add confirmation dialog when archiving chat with workspace (#22524)
When archiving a chat that has an attached workspace, a dialog now pops
up asking whether to also delete the associated workspace.

## Changes

### New file: `ArchiveAgentDialog.tsx`
A Radix-based dialog component that appears when archiving a chat that
has a `workspace_id`. It provides:
- A checkbox to opt into deleting the associated workspace
- **Cancel** — closes without archiving
- **Archive only** — archives the chat, leaves the workspace intact
- **Archive & Delete Workspace** — archives the chat and triggers
workspace deletion (enabled only when checkbox is checked)

### Modified: `AgentsPage.tsx`
- Extracted archive logic into a `performArchive` helper
- `requestArchiveAgent` now checks if the chat has a `workspace_id`:
  - If yes, opens the `ArchiveAgentDialog`
  - If no, proceeds with archiving directly (existing behavior)
- Added `handleArchiveOnly`, `handleArchiveAndDeleteWorkspace`, and
`handleCloseArchiveDialog` handlers
- Renders the `<ArchiveAgentDialog>` at the page level

Chats without a workspace are archived immediately as before — no UX
change for those.
2026-03-02 18:52:51 -05:00
Kyle Carberry ddfe630757 refactor(chatd): replace fantasy.Agent with custom agent loop (#22507)
## Summary

Replaces fantasy's `Agent` abstraction with a direct step loop calling
`LanguageModel.Stream()`. Fantasy is retained as the provider
abstraction layer (streaming parsers, types, tool schema) but we no
longer use `fantasy.Agent`, `AgentStreamCall`, `AgentResult`, or
`StepResult`.

## Problems solved

| Problem | Before | After |
|---|---|---|
| **Sentinel prompt hack** | fantasy.Agent requires non-empty Prompt →
UUID sentinel generated and stripped in PrepareStep | Messages passed
directly to `model.Stream()` |
| **Discarded PersistStep errors** | `_ = opts.OnStepFinish(result)`
silently swallows errors | Errors propagate directly from
`PersistStep()` |
| **Shadow draft state** | ~160 LOC tracking content in parallel because
fantasy doesn't expose in-progress content on interruption |
`stepResult` owns content directly; `flushActiveState()` is trivial |
| **Nested retry layers** | fantasy's 2-attempt retry nested inside
chatretry's indefinite retry | Single `chatretry.Retry` layer |
| **Callback-mediated compaction** | Mutex + boolean flag + coordination
between OnStepFinish/PrepareStep callbacks | Inline `if` statement
between steps |
| **Duplicate compaction paths** | `compactStep()` + `maybeCompact()`
sharing ~80% logic | Single `tryCompact()` function |

## Changes

### `coderd/chatd/chatloop/chatloop.go` — Rewritten
- **Removed**: `fantasy.NewAgent()`, `AgentStreamCall`, sentinel prompt,
shadow draft state (~160 LOC of closures), `compactedMu`/`compacted`
flag, `PrepareStepResult`
- **Added**: `stepResult` struct, `processStepStream()` (stream
consumer), `executeTools()` (sequential tool execution),
`flushActiveState()` (interrupt handling), `buildToolDefinitions()`,
`toResponseMessages()`
- **Changed**: `Run()` return type from `(*fantasy.AgentResult, error)`
to `error` (callers already discarded the result)
- **Preserved**: Anthropic prompt caching, reasoning title extraction,
`extractContextLimit()`, `ErrInterrupted` semantics

### `coderd/chatd/chatloop/compaction.go` — Simplified
- Merged `compactStep()` + `maybeCompact()` → single `tryCompact()`
- Removed `[]StepResult` parameter from `generateCompactionSummary()`
(caller provides complete message list)
- Kept helper functions: `normalizedCompactionConfig`,
`contextTokensFromUsage`, `resolveContextLimit`, `shouldCompact`

### `coderd/chatd/chatd.go` — Caller updates
- Removed `AgentStreamCall` construction
- Changed `_, err = chatloop.Run(...)` to `err = chatloop.Run(...)`
- Model parameters moved from `AgentStreamCall` fields to `RunOptions`
fields

### Tests — 4 new tests
- `MidLoopCompactionReloadsMessages` — compaction fires mid-loop,
messages reloaded
- `PostRunCompactionSkippedAfterMidLoop` — no double compaction
- `MultiStepToolExecution` — tools execute between steps, results feed
next step
- `PersistStepErrorPropagates` — persistence errors propagate (was
silently discarded)
2026-03-02 18:51:57 -05:00
Kyle Carberry 7e0895a1ee fix(site): roll back optimistic message when server queues it (#22522)
## Problem

When a user sends a message while the agent is busy, the message appears
in the chat timeline as if it was sent and being processed (with the
"Thinking..." shimmer), instead of appearing in the queued messages list
above the input.

## Root Cause

`handleSend` in `AgentDetail.tsx` unconditionally injects an optimistic
user message into the conversation timeline and sets chat status to
`"pending"` **before** awaiting the server response. However, the server
can respond with `{ queued: true, queued_message: {...} }` (via
`CreateChatMessageResponse`) when the agent is already busy — meaning
the message was queued, not processed.

The client never inspected `response.queued` after the request
succeeded, so the optimistic message stayed in the timeline even though
the server queued it.

## Fix

After `sendMutation.mutateAsync(request)` resolves, check
`response.queued`. If true, roll back the optimistic message and restore
the previous chat status. The `queue_update` SSE event from the
WebSocket stream handles adding it to the queued messages list.

## Changes

- **`site/src/pages/AgentsPage/AgentDetail.tsx`**: Capture the response
from `sendMutation.mutateAsync` and roll back the optimistic message +
status when `response.queued === true`.
2026-03-02 16:31:12 -05:00
Kyle Carberry 5eebd3829f fix: use cursor-based query for chat stream notifications (#22510)
## Problem

The pubsub notification handler in `chatd` re-fetched **all** messages
from the DB on every new message notification, then filtered in Go with
`msg.ID > lastMessageID`. This grows linearly with conversation length —
every new message triggers a full table scan of that chat's history.

The `AfterMessageID` field in the pubsub notification payload was
clearly designed for cursor-based fetching, but no matching query
existed.

## Fix

- Add `GetChatMessagesByChatIDAfter` SQL query with `WHERE id >
@after_id`, so the database does the filtering instead of Go.
- Use it in the pubsub notification handler in `chatd.go`, passing
`lastMessageID` as the cursor.
- Implement the dbauthz wrapper (was a `panic("not implemented")` stub
from codegen) with the same read-check-on-parent-chat pattern as
adjacent methods.
- Add dbauthz test coverage for the new method.

**Not changed:** The initial snapshot in `Subscribe()` still loads all
messages — that's correct, since a newly-connecting client needs the
full conversation state. The waste was only in the ongoing notification
path.
2026-03-02 16:31:04 -05:00
Kyle Carberry e3c5d734ba fix(site): move gradient mask below title bar in agent detail (#22515)
The gradient mask overlay was positioned at the top of the parent
container (`absolute top-0`), causing it to overlap the title bar
instead of fading the scroll content beneath it.

**Changes:**
- Wrap the TopBar, archived banner, and gradient in a `relative z-10
shrink-0 overflow-visible` container
- Change the gradient from `top-0` to `top-full` so it anchors to the
bottom of the title bar and fades downward over the message area
2026-03-02 16:16:09 -05:00
Kyle CarberryandDanielle Maywood c4a4ad6008 feat(site): add smooth streaming text engine for LLM responses (#22503)
## Problem

LLM responses currently stream in bulk chunks — multiple `message_part`
events arrive per WebSocket frame, get batched into a single
`startTransition` state update, and render as a visual jump. This looks
janky compared to smooth character-by-character reveal.

## Solution

Port the jitter-buffer approach from
[coder/mux](https://github.com/coder/mux) into a single self-contained
file: `SmoothText.ts`.

### What's in the file

| Component | Purpose |
|---|---|
| `STREAM_SMOOTHING` constants | Tuning knobs (72–420 cps adaptive rate,
120 char max visual lag, 48 char frame cap) |
| `SmoothTextEngine` class | Pure state machine — two-clock model
(ingestion vs presentation) with budget-gated adaptive reveal |
| `useSmoothStreamingText` hook | React bridge via
`requestAnimationFrame` loop, single `useState<number>`, grapheme-safe
slicing |

### How the engine works

- **Adaptive rate:** Linear interpolation from 72 → 420 chars/sec based
on backlog pressure (how far behind the display is from ingested text)
- **Budget accumulation:** Fractional character budget accrues per RAF
tick. Only reveals when ≥1 whole character is ready. This makes it
frame-rate invariant — 60Hz and 240Hz displays reveal the same amount
over wall-clock time (tested to ≤2 char deviation)
- **Max visual lag:** Hard cap of 120 chars. If the gap exceeds this,
the visible pointer jumps forward immediately
- **Clean flush:** When streaming ends, remaining buffer appears
instantly — no trailing animation
- **Grapheme safety:** Uses `Intl.Segmenter` (with codepoint fallback)
to never split emoji mid-animation

### Integration

To wire this up, wrap the `<Response>` component in
`ConversationTimeline.tsx` with the hook:

```tsx
const SmoothedResponse: FC<{text: string; isStreaming: boolean; streamKey: string}> =
    ({ text, isStreaming, streamKey }) => {
        const { visibleText } = useSmoothStreamingText({
            fullText: text,
            isStreaming,
            bypassSmoothing: false,
            streamKey,
        });
        return <Response>{visibleText}</Response>;
    };
```

### Tests

8 engine tests covering: steady reveal, adaptive acceleration, max lag
cap, immediate flush on stream end, bypass mode, content shrink,
sub-char budget gating, and frame-rate invariance.

---------

Co-authored-by: Danielle Maywood <danielle@themaywoods.com>
2026-03-02 15:18:30 -05:00
Kyle Carberry 2f684002b8 fix(site): stay on archived chat instead of redirecting (#22505)
When archiving a chat, the frontend no longer navigates away to a
different chat. Instead it stays on the current chat and shows an
archived state.

## Changes

**AgentsPage.tsx** — Removed the redirect logic from
`requestArchiveAgent`. After a successful archive, invalidates the
individual chat query so the detail view picks up the `archived` flag
immediately.

**AgentDetail.tsx** — Detects `chatRecord.archived` and:
- Disables the chat input
- Shows a banner: "This agent has been archived and is read-only."
- Passes `isArchived` to the top bar
- Guards `handleArchiveAgentAction` against double-archiving

**AgentDetail/TopBar.tsx** — When `isArchived`:
- Shows an "Archived" badge next to the chat title
- Hides the "Archive Agent" dropdown menu item

**AgentDetail/TopBar.stories.tsx** — Added an `Archived` story variant.
2026-03-02 19:43:45 +00:00
Kyle Carberry bdc1a0e798 fix: raise diff panel drag handle z-index above sticky file headers (#22504)
## Problem

The drag handle (resize slider) on the diff right panel and the sticky
file headers inside `FilesChangedPanel` both had `z-index: 10`. Because
the sticky headers render later in the DOM and are positioned, they
painted on top of the drag handle — making it appear to go "below" the
headers when dragging.

## Fix

Bump the drag handle from `z-10` to `z-20` so it always stays above the
sticky `[data-diffs-header]` elements (`z-index: 10`).
2026-03-02 19:16:46 +00:00
Kyle Carberry 7aef0bf25e fix(chatd): increase title generation timeout from 10s to 30s (#22501)
## Problem

Production logs frequently show:

```
[debu] coderd.chats.chat-processor: failed to generate chat title
    error= generate title text: context deadline exceeded
```

## Root Cause

The title generation timeout in `maybeGenerateChatTitle` is 10 seconds.
Many LLM providers routinely exceed this under load (cold starts, rate
limits, large models). Since `chatretry` classifies `context deadline
exceeded` as non-retryable, the first timeout kills the entire attempt
with no retry.

## Fix

Increase the timeout from 10s to 30s. Title generation is async and
best-effort — it runs in a background goroutine and doesn't block the
chat response — so a longer timeout has no user-facing impact.
2026-03-02 14:11:25 -05:00
Kyle Carberry a33ca95df2 fix(chatd): prevent chat re-acquisition during server shutdown (#22497)
Fixes https://github.com/coder/internal/issues/1371

## Problem

`TestCloseDuringShutdownContextCanceledShouldRetryOnNewReplica` flakes
intermittently in CI. The observed failure is that the chat never
reaches `pending` status after `serverA.Close()`.

## Root cause

Race between context cancellation and the mock OpenAI server's stream
completion marker.

When `Close()` cancels the server context, the in-flight HTTP streaming
request is canceled. The mock server's handler detects this via
`req.Context().Done()` and closes its chunks channel. The mock's
`writeChatCompletionsStreaming` then writes `data: [DONE]` — the SSE
completion marker. On a loopback connection, this marker can reach the
client **before** the client's HTTP transport honors the context
cancellation.

When this happens:
1. The client sees a successful stream completion (not an error)
2. `chatloop.Run` returns `nil`
3. `processChat` falls through without error → status stays `waiting`
(the default)
4. The test expects `pending` → **flake**

## Fix

Skip writing the `[DONE]` marker when the request context is already
canceled, in both `writeChatCompletionsStreaming` and
`writeResponsesAPIStreaming`.
2026-03-02 18:00:21 +00:00
Kyle Carberry 0908505348 fix(chats): archive chat tree with single query instead of loop (#22496)
## Problem

When archiving an agent with subagents, the children briefly flash in
the sidebar as root-level items before disappearing. Two issues:

1. **Backend:** Archive used N+1 queries — a recursive DFS
(`archiveChatTree`, no transaction) or BFS loop (`chatd.ArchiveChat`,
N+1 queries in a tx) to walk the tree and archive each chat
individually.
2. **Frontend:** The SSE `deleted` event handler only filtered out the
parent chat from the cache. Children remained briefly, got promoted to
root-level by `buildChatTree`, then disappeared on the next re-fetch.

## Fix

**Backend:** Replace both tree-walk implementations with a single SQL
query:
```sql
UPDATE chats SET archived = true, updated_at = NOW()
WHERE id = @id OR root_chat_id = @id;
```
This leverages the existing `root_chat_id` column (already indexed) to
archive the entire tree atomically.

**Frontend:** When a `deleted` event arrives, also filter out any chats
whose `root_chat_id` matches the deleted chat, so children vanish from
the sidebar immediately with the parent.

## Changes

- `coderd/database/queries/chats.sql` — Added `ArchiveChatTreeByID`
query
- `coderd/chats.go` — Use single query, delete `archiveChatTree`
function
- `coderd/chatd/chatd.go` — Simplify `ArchiveChat` to use single query
- `coderd/database/dbauthz/dbauthz.go` — Auth wrapper for new query
- `coderd/chats_test.go` — Added `TestArchiveChat/ArchivesChildren`
subtest
- `site/src/pages/AgentsPage/AgentsPage.tsx` — Filter children in SSE
handler
- Generated files updated via `make gen`
2026-03-02 12:00:00 -05:00
Kyle CarberryandCoder ebe8c8a5b4 fix(site): scope chevron to icon hover when any child is running (#22456)
Follow-up to #22452. The previous fix only checked the chat's own
status, so a root chat in `waiting` status with actively running
sub-agents still showed the expand/collapse chevron on full-row hover.

## Problem

A root chat that's idle (`waiting`/`completed`) but has running
sub-agents would still swap its status icon for the `>` chevron on row
hover. The fix in #22452 only gated on `chat.status` being
`pending`/`running`, which doesn't cover the parent when sub-agents are
the ones executing.

## Fix

`isExecuting` now also checks whether **any direct child** is
`pending`/`running`:

```ts
const isExecuting =
  chat.status === "pending" ||
  chat.status === "running" ||
  (hasChildren &&
    childIDs.some((id) => {
      const c = chatById.get(id);
      return c?.status === "pending" || c?.status === "running";
    }));
```

When `isExecuting` is true, the chevron only appears on hover of the
icon area itself (`group-hover/icon`), not the entire row.

## New story

Added `IdleParentWithRunningChild` — verifies a `waiting` parent with a
`running` child uses icon-only hover scope for the toggle.

Co-authored-by: Coder <coder@users.noreply.github.com>
2026-03-02 09:20:46 -05:00
Kyle CarberryandCoder c9ed1e17fc feat(agents): add desktop notifications via VAPID web push (#22454)
## Summary

Wire VAPID web push notifications into the Agents (chat) system so users
get desktop notifications when an agent finishes running.

### Backend

- Add `webpush.Dispatcher` to `chatd.Server` and pass it through from
`coderd.Options.WebPushDispatcher`
- In `processChat()`'s deferred cleanup, dispatch a web push
notification when the chat reaches a terminal state:
  - **`waiting`** (success): "Agent has finished running."
- **`error`** (failure): the error message, or "Agent encountered an
error."
- Sub-agent chats (`ParentChatID.Valid`) are skipped to avoid
notification spam from internal delegation
- Gracefully no-ops when the dispatcher is nil (web push disabled)

### Frontend

- New `WebPushButton` component — a bell icon that uses the existing
`useWebpushNotifications` hook
  - Returns `null` when the `web-push` experiment is off
- Three states: loading spinner, green bell (subscribed), muted bell-off
(unsubscribed)
  - Tooltip + toast feedback on toggle
- Added to both the Agents page empty state top bar and the AgentDetail
top bar
- The Agents page has its own layout (no standard Navbar), so it needs
its own subscribe button

### End-to-end flow

1. User clicks the bell icon on `/agents` → browser subscribes via VAPID
2. User starts an agent chat → chat enters `running` status
3. Agent finishes → `processChat` defer sets status to `waiting`/`error`
→ dispatches web push
4. Browser service worker shows a desktop notification with the chat
title and status

---------

Co-authored-by: Coder <coder@users.noreply.github.com>
2026-02-28 23:40:17 -05:00
Kyle CarberryandCoder 68e4155fed feat(agent/filefinder): add plocate-lite file finder package (#22453)
Adds an in-memory trigram-indexed file finder package at
`agent/filefinder`, designed to power a future `FindFiles` HTTP handler
on the WorkspaceAgent.

## What it does

Fast fuzzy file search with VS Code-quality matching across millions of
files. Sub-millisecond search latency at 100K files.

## Architecture

- **Index**: append-only docs slice with trigram + prefix posting lists
- **Snapshot**: lock-free reader view via frozen slice headers +
shallow-copied deleted set
- **Search pipeline**: trigram intersection → fuzzy fallback (prefix
bucket + subsequence) → brute-force scan (capped at 5K docs)
- **Scoring**: subsequence match, basename prefix, boundary hits,
contiguous runs, depth/length penalties
- **Engine**: multi-root with fsnotify watcher (50ms batch coalescing),
atomic snapshot publishing

## Benchmarks (10K files)

| Query Type | Latency |
|---|---|
| exact_basename (`handler.go`) | ~43µs |
| short_query (`ha`) | ~7µs |
| fuzzy_basename (`hndlr`) | ~50µs |
| path_structured (`internal/handler`) | ~29µs |
| multi_token (`api handler`) | ~15µs |

## File inventory (11 files, 3273 lines)

| File | Lines | Purpose |
|---|---|---|
| `text.go` | 264 | Normalization, trigram extraction, scoring |
| `delta.go` | 128 | Index, Snapshot, CRUD operations |
| `query.go` | 272 | Query planning, search strategies, top-K merge |
| `engine.go` | 323 | Multi-root engine, watcher integration |
| `watcher_fs.go` | 201 | fsnotify wrapper with batch coalescing |
| `*_test.go` | 2085 | Unit tests, integration tests, benchmarks |

---------

Co-authored-by: Coder <coder@users.noreply.github.com>
2026-02-28 23:37:07 -05:00
Kyle CarberryandCoder 897f178a5c feat(site): replace Agent chat textarea with Lexical editor (#22449)
## Summary

Replaces the plain `<TextareaAutosize>` in the Agent chat input
(`AgentChatInput`) with a Lexical-based editor component, matching the
pattern used in [coder/blink](https://github.com/coder/blink).

## What changed

### New component: `ChatMessageInput`
`site/src/components/ChatMessageInput/ChatMessageInput.tsx`

A Lexical-powered text input that behaves as a plain-text editor with:
- **Enter** submits, **Shift+Enter** inserts newline
- Rich-text formatting disabled (Cmd+B/I/U blocked)
- Paste sanitization (strips formatting, inserts plain text)
- Undo/redo via HistoryPlugin
- Imperative ref API: `insertText()`, `clear()`, `focus()`, `getValue()`

### Updated components
- **`AgentChatInput.tsx`** — Swapped `<TextareaAutosize>` for
`<ChatMessageInput>`. Moved from controlled `value`/`onChange` to
ref-based pattern with `initialValue`/`onContentChange`.
- **`AgentDetail.tsx`** — Updated to use `useRef` for input value
tracking and `editorInitialValue` state for editor resets (edit/cancel
flows).
- **`AgentsPage.tsx`** — Updated to use `useRef` + `initialValue`
pattern.
- **`AgentChatInput.stories.tsx`** — Updated prop names.

### Why Lexical?
This lays the groundwork for features that a native `<textarea>` can't
support:
- Ghost text / inline autocomplete suggestions
- @-mentions and slash commands
- Programmatic text insertion (e.g. from speech-to-text)
- Custom inline decorators (chips, pills, badges)
- Syntax-highlighted code blocks

No adornments are added in this PR — it's a drop-in replacement that
matches existing behavior.

---------

Co-authored-by: Coder <coder@coder.com>
2026-02-28 22:18:36 -05:00
Kyle CarberryandCoder a6d2462076 fix(site): preserve running spinner on agents sidebar row hover (#22452)
When hovering over a running/pending chat in the agents sidebar, the
spinning status icon was being replaced by the expand/collapse chevron
button. This was disorienting because the spinner conveys important "in
progress" state.

## Changes

**`AgentsSidebar.tsx`**:
- Added `group/icon` scoped hover group to the icon container div
- When a chat is executing (`pending`/`running`), the chevron toggle
only appears on hover of the icon area itself, not the entire row
- Non-executing chats retain the original whole-row hover behavior (no
UX change)

**`AgentsSidebar.stories.tsx`**:
- Added `RunningChatPreservesSpinner` story verifying the spinner is
present and the toggle button starts invisible for running chats with
children

Co-authored-by: Coder <coder@users.noreply.github.com>
2026-02-28 22:05:18 -05:00
Kyle CarberryandCoder fb6bf3a568 fix(agent): wire updateCommandEnv into process manager (#22451)
## Problem

The `agentproc` process manager spawns processes with only
`os.Environ()`, missing agent-level environment variables like
`GIT_ASKPASS`, `CODER_*`, and `GIT_SSH_COMMAND` that are injected by the
agent's `updateCommandEnv` function. This means processes started
through the HTTP process API (used by chat tools) cannot authenticate
git operations via the Coder gitaskpass helper.

By contrast, SSH sessions get the full agent environment because the SSH
server calls `updateCommandEnv` via its `UpdateEnv` config hook.

## Fix

Wire the agent's `updateCommandEnv` hook into the process manager so all
spawned processes receive the full agent environment. The hook is:

- Passed as a parameter through `NewAPI` → `newManager`
- Called in `manager.start()` with `os.Environ()` as the base, producing
the same enriched env that SSH sessions get
- Gracefully falls back to `os.Environ()` if the hook returns an error

Request-level env vars (`req.Env`, set by chat tools) are still appended
last and take precedence.

## Changes

- `agent/agentproc/process.go`: Add `updateEnv` field to manager, call
it when building process env
- `agent/agentproc/api.go`: Accept `updateEnv` parameter in `NewAPI`
- `agent/agent.go`: Pass `a.updateCommandEnv` when creating the process
API
- `agent/agentproc/api_test.go`: Add `UpdateEnvHook` and
`UpdateEnvHookOverriddenByReqEnv` tests

Co-authored-by: Coder <coder@coder.com>
2026-02-28 21:58:59 -05:00
Kyle CarberryandCoder 533b90a3a4 fix: resolve chat title update race conditions and improve resilience (#22450)
## Problem

Chat titles sometimes don't update in the UI. The generated AI title
gets stuck as the fallback (first 6 words of the message) even though
the backend successfully generates a proper title.

## Root Causes

### 1. Cancelable context used during cleanup DB read (P0)
In `processChat`, the deferred cleanup re-reads the chat from the DB to
pick up the AI-generated title for the `status_change` pubsub event. But
it used the cancelable `ctx` instead of `cleanupCtx`:

```go
// Before — ctx may already be canceled here
if freshChat, readErr := p.db.GetChatByID(ctx, chat.ID); readErr == nil {
```

When the context is canceled, the DB read fails silently and the
`status_change` event carries the stale fallback title.

### 2. Title goroutine not tracked by inflight WaitGroup (P2)
The `maybeGenerateChatTitle` goroutine was fire-and-forget — not tracked
by `p.inflight`. During graceful shutdown, the server could exit before
the goroutine completes its DB write or pubsub publish.

### 3. No recovery when watchChats() WebSocket misses events
The frontend relies entirely on the `watchChats()` SSE connection for
title updates. If the connection drops or misses events, titles never
recover — the only fix was a full page reload.

## Fixes

1. **Use `cleanupCtx`** for the `GetChatByID` call and logger in the
deferred cleanup block.
2. **Track the title goroutine** with `p.inflight.Add(1)` / `defer
p.inflight.Done()` so shutdown waits for it.
3. **Invalidate chats query** on WebSocket open/close/error events so
missed updates are recovered via refetch. Also enable
`refetchOnWindowFocus` for the chats query.

Co-authored-by: Coder <coder@users.noreply.github.com>
2026-02-28 21:38:16 -05:00
Kyle Carberry 1c71fd69f6 fix: workspace auto-refresh during the chat flow (#22447) 2026-02-28 19:07:17 -05:00
Kyle CarberryandCoder 948fd0fc06 test(site): add comprehensive ChatContext store and integration tests (#22444)
## Summary

Export `createChatStore`, `ChatStore`, and `ChatStoreState` from
`ChatContext.ts` so the pure store logic can be unit tested directly
without React rendering overhead.

## Changes

### Production code (3-line change)
- Added `export` to `ChatStoreState`, `ChatStore`, and `createChatStore`
in `ChatContext.ts`

### chatStore.test.ts — 35 pure store unit tests (runs in ~6ms)
Covers every store method directly with synchronous, zero-React tests:
- `replaceMessages`: population, ordering, undefined handling,
dedup/no-emit
- `upsertDurableMessage`: insert, duplicate detection, value-change
update, optimistic placeholder removal (negative IDs), role-scoped
cleanup, in-place update without reorder
- `setChatStatus`: set, null clear, idempotency
- `setStreamError` / `clearStreamError`: set, clear, no-op guards, dedup
- `setRetryState` / `clearRetryState`: set, clear, no-op guard
- `setSubagentStatusOverride`: single, accumulation, dedup, overwrite
- `setQueuedMessages`: set, undefined handling, ID-based dedup
- `clearStreamState`: clear, no-op guard
- `applyMessagePart` / `applyMessageParts`: text, append, batch, empty
no-op
- `resetTransientState`: clears all transient state, preserves messages,
no-op guard
- `subscribe`: unsubscribe lifecycle, multiple subscribers

### ChatContext.test.tsx — 8 new integration tests
WebSocket event handling that was previously untested:
- **Error events**: sets chatStatus to error, populates streamError,
clears retryState, calls setChatErrorReason; uses fallback when error
has blank text
- **Retry events**: populates retryState; status transition to running
clears retryState
- **Subagent status overrides**: status events with different chat_id go
to subagentStatusOverrides, not main chatStatus
- **WebSocket disconnect**: sets streamError; preserves existing error
on disconnect
- **Status transitions**: clears chatErrorReason on non-error status

### Test infrastructure improvements
- Added `emitError()` helper to MockSocket for testing WebSocket
disconnect
- Added `vi.mocked(watchChat).mockReset()` to `afterEach` for reliable
test isolation between tests that use `mockReturnValueOnce`

## Test results
```
✓ chatStore.test.ts (35 tests) 6ms
✓ ChatContext.test.tsx (23 tests) 107ms
  58 passed (58)
```

---------

Co-authored-by: Coder <coder@users.noreply.github.com>
2026-02-28 17:14:58 -05:00
Kyle Carberry 2abe55549c fix: return in-flight chats to pending on server shutdown (#22443)
When a chatd server shuts down (`Close()`), the server context is
canceled. Previously, in-flight chats would be marked as `error` because
the `context.Canceled` error was not distinguished from actual
processing failures.

This adds `isShutdownCancellation()` to detect when the error is caused
by the server context being canceled (as opposed to a chat-specific
cancellation like `ErrInterrupted`). When detected, the chat status is
set to `pending` with no `last_error`, allowing another replica to pick
it up and retry.

Extracted from #22440 — only the context cancellation bug fix, no
chattest changes.
2026-02-28 17:14:11 -05:00
Kyle CarberryandKyle Carberry 5945febf06 feat(agent): add fuzzy whitespace matching to edit_files tool (#22446)
Inspired by openai/codex's `apply_patch` implementation, this changes
the `edit_files` search-and-replace to use a cascading match strategy
when the exact search string isn't found:

1. **Exact substring match** (byte-for-byte) — existing behavior,
unchanged
2. **Line-by-line match ignoring trailing whitespace** — handles
trailing spaces/tabs the LLM omits
3. **Line-by-line match ignoring all leading/trailing whitespace** —
handles tabs-vs-spaces and wrong indentation depth

## Problem

When the chat agent uses `edit_files`, it generates a search string that
must match the file content exactly. LLMs frequently get whitespace
wrong:
- Emitting spaces when the file uses tabs (or vice versa)
- Getting the indentation depth wrong by one or more levels
- Omitting trailing whitespace that exists in the file

When this happens, the edit silently does nothing, and the agent falls
into a retry loop using `cat -A` to diagnose the exact whitespace
characters.

## Solution

Adopted the same cascading fuzzy match strategy that [openai/codex uses
in
`seek_sequence.rs`](https://github.com/openai/codex/blob/main/codex-rs/apply-patch/src/seek_sequence.rs):

- Pass 1: exact match (existing behavior)
- Pass 2: `TrimRight` each line before comparing (trailing whitespace
tolerance)
- Pass 3: `TrimSpace` each line before comparing (full indentation
tolerance)

When a fuzzy match is found, the matched lines in the original file are
replaced with the replacement text. This preserves surrounding content
exactly.

## Changes

- `agent/agentfiles/files.go`: Replaced `icholy/replace` streaming
transformer with in-memory `fuzzyReplace` + helper functions
(`seekLines`, `spliceLines`)
- `agent/agentfiles/files_test.go`: Added 6 new test cases covering
trailing whitespace, tabs-vs-spaces, different indent depths, exact
match preference, no-match behavior, and mixed whitespace multiline
edits
- Removed `icholy/replace` dependency from go.mod/go.sum

---------

Co-authored-by: Kyle Carberry <kylecarbs@users.noreply.github.com>
2026-02-28 17:02:57 -05:00
Kyle Carberry 22d4539a7a fix(chatd): clear stream buffer after each step is persisted (#22445)
The in-memory stream buffer accumulated message-part events for the
entire duration of a chat run. Late-joining subscribers received all
buffered parts even though the backing messages had already been
committed to the database, wasting memory and potentially duplicating
content.

Clear the buffer at the end of each `persistStep` call so that only
in-flight (uncommitted) parts remain in the buffer.
2026-02-28 16:51:04 -05:00
Kyle CarberryandKyle Carberry 34d9392e37 chore(db): remove workspace_agent_id from chats table (#22442)
## Summary

Remove the `workspace_agent_id` column from the `chats` table and
dynamically look up the first workspace agent instead.

## Problem

When a workspace is stopped and restarted, the workspace agent gets a
new ID. The `workspace_agent_id` stored on the chat at creation time
becomes stale, making the agent unreachable. This caused chats to break
after workspace restarts.

## Solution

Instead of persisting the agent ID, dynamically look up the first agent
from the workspace's latest build via
`GetWorkspaceAgentsInLatestBuildByWorkspaceID` whenever an agent
connection is needed. The `workspace_id` on the chat remains stable
across restarts.

This behavior may be refined later (e.g., agent selection heuristics),
but picking the first agent resolves the immediate breakage.

## Changes

- **Migration 000425**: Drop `workspace_agent_id` column from `chats`
- **SQL queries**: Remove `workspace_agent_id` from `InsertChat` and
`UpdateChatWorkspace`
- **chatd.go**: `getWorkspaceConn` and `resolveInstructions` now look up
agents dynamically from workspace ID
- **chatd.go**: Remove `refreshChatWorkspaceSnapshot` (no longer needed)
- **createworkspace.go**: Stop persisting agent ID when associating
workspace with chat
- **subagent.go**: Stop passing agent ID to child chats
- **SDK/frontend**: Remove `WorkspaceAgentID` / `workspace_agent_id`
from Chat type

---------

Co-authored-by: Kyle Carberry <kylecarbs@gmail.com>
2026-02-28 16:46:51 -05:00
Kyle CarberryandCoder c316d0a3e7 fix(chatd): improve subagent tool descriptions and strip tools from child agents (#22441)
Two changes:

1. **Gate subagent tools behind `!chat.ParentChatID.Valid`** so child
agents never receive `spawn_agent`, `wait_agent`, `message_agent`, or
`close_agent`. Previously all 4 tools were given to every chat.
`spawn_agent` would fail at runtime ("delegated chats cannot create
child subagents") but the other 3 had no guard at all — meaning a child
could theoretically operate on sibling chats. Removing the tools
entirely is cleaner and saves context window.

2. **Rewrite tool descriptions to explain *when* to use them**, not just
what they do. `spawn_agent` now says to use it for clearly scoped,
independent, self-contained tasks (e.g. fixing a specific bug, writing a
single module, running a migration) and explicitly says *not* to use it
for simple operations you can handle with
`execute`/`read_file`/`write_file`. It also states that child agents
cannot spawn their own subagents. The other 3 tools get similar
guidance-oriented descriptions.

Co-authored-by: Coder <coder@users.noreply.github.com>
2026-02-28 16:30:25 -05:00
Kyle CarberryandCoder eec0b299e8 fix(site): add chromatic ignore to shimmer component (#22439)
The shimmer component has an infinitely repeating animation that causes
Chromatic snapshot diffs on every run. Adding `data-chromatic="ignore"`
to prevent false positives, consistent with how other animated
components in the codebase handle this (e.g. `Spinner`, `Alert`,
`SyntaxHighlighter`).

Co-authored-by: Coder <coder@users.noreply.github.com>
2026-02-28 15:17:04 -05:00
Kyle Carberry c5619746d1 fix(chat): fix stream state discrepancies between frontend and backend (#22437)
## Summary

Fixes four frontend↔backend discrepancies in chat stream state
management that could cause duplicate content, UI flicker, and stale
stream state.

### Backend fixes (`coderd/chatd/chatd.go`)

**1. No-pubsub path double-replayed message_part events**

`Subscribe()` built an `initialSnapshot` containing `message_part`
events from `localSnapshot`, then the no-pubsub goroutine replayed the
same `localSnapshot` into the `mergedEvents` channel. Since `streamChat`
sends the snapshot first then reads the channel, the frontend received
every `message_part` twice. `applyMessagePartToStreamState` doesn't
deduplicate — text gets concatenated, so content appeared doubled.

Fix: Only forward live `localParts` in the no-pubsub goroutine; the
snapshot already contains the historical events.

**2. Snapshot missing status event**

The initial snapshot never included a `status` event. The frontend's
`shouldApplyMessagePart()` gates on status (`pending`/`waiting`), but
the initial status came from a separate REST query via `useEffect`.
During the race window between snapshot arrival and REST resolution,
`message_part` events could be incorrectly accepted or rejected.

Fix: Prepend a `status` event to the snapshot after loading the chat
from DB, so the frontend has the authoritative status from the very
first batch.

### Frontend fixes (`ChatContext.ts`)

**3. Scheduled stream reset not canceled by subsequent message_parts**

When a `message` event arrived, `scheduleStreamReset()` queued
`clearStreamState` via `requestAnimationFrame`. If new `message_part`
events arrived in the next WebSocket frame before the rAF fired, they
were pushed to `pendingMessageParts` without canceling the scheduled
reset. The rAF would fire between frames, clearing stream state, then
the next flush would re-populate it — causing a visible flash.

Fix: Call `cancelScheduledStreamReset()` when accumulating
`message_part` events.

**4. startTransition race with synchronous clearStreamState**

`flushMessageParts` wrapped `applyMessageParts` in `startTransition`,
which React can defer. If a `status: "waiting"` event arrived in the
same batch after `message_part` events, the status handler cleared
stream state synchronously, but the deferred `applyMessageParts`
callback could fire afterward and re-populate it.

Fix: Re-check `shouldApplyMessagePart()` inside the `startTransition`
callback at execution time.

### Tests added

- **Go**: `TestSubscribeSnapshotIncludesStatusEvent` — asserts the first
snapshot event is a status event
- **Go**: `TestSubscribeNoPubsubNoDuplicateMessageParts` — asserts the
events channel doesn't replay snapshot events
- **TS**: `cancels scheduled stream reset when message_part arrives
after message` — verifies stream state survives a [message,
message_part] batch
- **TS**: `does not apply message parts after status changes to waiting`
— verifies deferred applyMessageParts respects status transitions
2026-02-28 13:35:23 -05:00
Kyle Carberry a621c3cb13 feat(agent): add process execution API and rewrite execute tool (#22416)
## Summary

Adds a new agent-side process management HTTP API and rewrites the chat
execute tool to use it instead of SSH sessions.

## What changed

### New agent/agentproc/ package

- **headtail.go** — Thread-safe io.Writer with bounded memory (16KB head
+ 16KB tail ring buffer). Provides LLM-ready output with truncation
metadata and long-line truncation at 2048 bytes.
- **headtail_test.go** — 16 tests including race detector coverage for
concurrent writes.
- **process.go** — Manager + Process types for lifecycle management
using agentexec.Execer for proper OOM/nice scores.
- **api.go** — HTTP API following the agentfiles chi router pattern. 4
endpoints: start, list, output, signal.

### Agent wiring (agent/agent.go, agent/api.go)

Mounts the process API at /api/v0/processes, mirroring how agentfiles is
mounted.

### SDK (codersdk/workspacesdk/agentconn.go)

4 new AgentConn interface methods + 7 request/response types:
- StartProcess, ListProcesses, ProcessOutput, SignalProcess

### Execute tool rewrite (coderd/chatd/chattool/execute.go)

- SSH to Agent API: conn.StartProcess() + conn.ProcessOutput() polling
- New parameters: workdir, run_in_background
- Structured response: success, exit_code, wall_duration_ms, error,
truncated, note, background_process_id
- Non-interactive env vars: GIT_EDITOR=true, TERM=dumb, NO_COLOR=1,
PAGER=cat, etc.
- Output truncation: HeadTailBuffer caps at 32KB for LLM consumption
- File-dump detection with advisory notes suggesting read_file
- Default timeout: 60s to 10s
- Foreground polling: 200ms intervals until exit or timeout

## Architecture

State lives on the agent, surviving coderd failover and instance
changes. Any coderd replica can query any agent via HTTP over tailnet.
2026-02-28 12:33:52 -05:00
Kyle Carberry 0ad2f9ecd7 feat(chatd): persist last_error on chats table (#22436)
Adds a nullable `last_error` column to the `chats` table so error
reasons survive page reloads.

**Backend:**
- Migration adds `last_error TEXT` (nullable) to chats
- `UpdateChatStatus` writes the error reason when status transitions to
`error`, clears it (NULL) on recovery
- `convertChat` maps `sql.NullString` to `*string` in the SDK

**Frontend:**
- Sidebar falls back to `chat.last_error` when no stream error reason is
cached
- Chat detail page does the same for `persistedErrorReason`
- Fixtures updated for new required field
2026-02-28 12:27:26 -05:00
Kyle Carberry 256284b7fe fix: add sticky headers to the git diff (#22425)
<!--

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.

-->
2026-02-27 19:03:11 -05:00
Kyle Carberry 2bdacae5f5 feat(chatd): add LLM stream retry with exponential backoff (#22418)
## Summary

Adds automatic retry with exponential backoff for transient LLM errors
during chat streaming and title generation. Inspired by
[coder/mux](https://github.com/coder/mux)'s retry mechanism.

## Key Behaviors

- **Infinite retries** with exponential backoff: 1s → 2s → 4s → ... →
60s cap
- **Deterministic delays** (no jitter)
- **Error classification**: retryable (429, 5xx, overloaded, rate limit,
network errors) vs non-retryable (auth, quota, context exceeded, model
not found, canceled)
- **Retry status published to SSE stream** so frontend can show
"Retrying in Xs..." UI
- **Title generation** retries silently (best-effort, nil onRetry
callback)

## New Package: `coderd/chatd/chatretry/`

| File | Purpose |
|------|---------|
| `classify.go` | `IsRetryable(err)` and `StatusCodeRetryable(code)` |
| `backoff.go` | `Delay(attempt)` — exponential doubling with 60s cap |
| `retry.go` | `Retry(ctx, fn, onRetry)` — infinite loop with
context-aware timer |

## Test Helpers: `coderd/chatd/chattest/errors.go`

Anthropic and OpenAI error response builders for use in chattest
providers:
- `AnthropicErrorResponse()`, `AnthropicOverloadedResponse()`,
`AnthropicRateLimitResponse()`
- `OpenAIErrorResponse()`, `OpenAIRateLimitResponse()`,
`OpenAIServerErrorResponse()`

## SDK Changes: `codersdk/chats.go`

- New `ChatStreamEventType: "retry"`
- New `ChatStreamRetry` struct with `Attempt`, `DelayMs`, `Error`,
`RetryingAt` fields
- TypeScript types auto-generated

## Changed Files

- `coderd/chatd/chatloop/chatloop.go` — wraps `agent.Stream()` in
`chatretry.Retry()`
- `coderd/chatd/chatd.go` — publishes retry events to SSE stream with
logging
- `coderd/chatd/title.go` — wraps `model.Generate()` in silent retry
- `coderd/chatd/chattest/anthropic.go` / `openai.go` — error injection
support

## Tests

42 tests covering classification (33), backoff (9), and retry scenarios
(8).
2026-02-27 18:34:33 -05:00
Kyle Carberry 4b5ec8a9a4 feat: add diff_status_change event to /chats/watch pubsub stream (#22419)
## Summary

Adds a new `diff_status_change` event kind to the `/chats/watch` pubsub
stream so the sidebar can update diff status (PR created, files changed,
branch info) without a full page reload.

### Problem

When a chat's diff status changes (e.g. PR created via GitHub, git
branch pushed), the sidebar didn't update because:
1. The backend `publishChatPubsubEvent` didn't include diff status data
2. The frontend watch handler only merged `status`, `title`, and
`updated_at` from events

### Solution

A **notify-only** approach: a new `ChatEventKindDiffStatusChange` event
kind tells the frontend "diff status changed for chat X" — the frontend
then invalidates the relevant React Query cache entries to re-fetch.

### Backend changes

- **`coderd/pubsub/chatevent.go`**: New `ChatEventKindDiffStatusChange =
"diff_status_change"` constant
- **`coderd/chatd/chatd.go`**: New `PublishDiffStatusChange(ctx,
chatID)` method on `Server`
- **`coderd/chats.go`**: New `publishChatDiffStatusEvent` helper.
Published from:
- `refreshWorkspaceChatDiffStatuses` — after each chat's diff status is
refreshed via GitHub API
- `storeChatGitRef` — after persisting git branch/origin info from
workspace agent

### Frontend changes

- **`AgentsPage.tsx`**: Handle `diff_status_change` event by
invalidating `chatDiffStatusKey` and `chatDiffContentsKey` queries
- **`ChatContext.ts`**: Remove redundant diff status invalidation that
fired on every chat status change (the new event kind handles this
properly)
2026-02-27 18:06:54 -05:00
Kyle Carberry b0c6a6dc25 fix(site): optimistic message on agent chat submit (#22422)
## Problem

When sending a message in the agent detail chat, the text lingered in
the input textarea while the HTTP POST round-tripped to the server. Only
after the server responded did the input clear and the message appear in
the timeline (via WebSocket). This created a noticeable delay where the
user couldn't start typing their next message.

## Solution

**Optimistic input clear** (`AgentChatInput.tsx`):
- Clear the textarea and editing state *immediately* on submit, before
awaiting the network call.
- Capture the input text beforehand so it can be restored in the `catch`
block if the request fails.

**Optimistic user bubble** (`AgentDetail.tsx`):
- Inject a temporary `ChatMessage` (with a negative ID) into the chat
store so the user's message bubble appears in the timeline instantly.
- Set chat status to `pending` and clear stream state, mirroring the
existing edit-message path.
- On error, roll back: remove the optimistic message and restore the
previous chat status.

The real message arrives via the WebSocket stream and
`upsertDurableMessage` replaces the optimistic entry naturally (the
server message has a positive ID, so it's inserted alongside; the
optimistic negative-ID message gets cleaned up when `replaceMessages` is
called with the authoritative message list from the next query
invalidation).

## Testing

- Type a message and press Enter — input clears and bubble appears
immediately.
- Simulate a network error — input text is restored, optimistic bubble
is removed.
- Edit an existing message — unchanged behavior (already had optimistic
updates).
- Queue a message while streaming — unchanged behavior.
2026-02-27 17:49:53 -05:00
Kyle Carberry 5fb644a6cd feat(site): add keyboard shortcuts to agents page (#22417)
Adds two keyboard shortcuts to the agents page:

- **Escape** — Interrupts the running agent when viewing a chat detail
page. Only fires when focus is outside text inputs/textareas so it
doesn't conflict with the existing edit-cancel Escape handler in the
chat input.
- **Ctrl+N / Cmd+N** — Navigates to create a new agent. Also skipped
when focus is in a text input/textarea.

Both keybindings are implemented in a new `useAgentsPageKeybindings.ts`
hook file:
- `useAgentsPageKeybindings` — used in `AgentsPage.tsx` for Ctrl+N
- `useAgentDetailKeybindings` — used in `AgentDetail.tsx` for Escape →
interrupt
2026-02-27 17:33:43 -05:00
Kyle Carberry 12083441e0 feat(chats): archive chats instead of hard-deleting them (#22406)
## Summary

The UI has always labeled the action as "Archive agent" but the backend
was performing a hard `DELETE`, permanently destroying chats and all
their messages.

This change replaces the hard delete with a soft archive, consistent
with the pattern used by template versions.

## Changes

### Database
- **Migration 000423**: Add `archived boolean DEFAULT false NOT NULL`
column to `chats` table
- Replace `DeleteChatByID` query with `ArchiveChatByID` (`UPDATE SET
archived = true`)
- Add `UnarchiveChatByID` query (`UPDATE SET archived = false`)
- Filter archived chats from `GetChatsByOwnerID` (`WHERE archived =
false`)

### API
- Remove `DELETE /api/experimental/chats/{chat}`
- Add `POST /api/experimental/chats/{chat}/archive` — archives a chat
and all its descendants
- Add `POST /api/experimental/chats/{chat}/unarchive` — unarchives a
single chat (API only, no UI yet)

### Backend
- `archiveChatTree()` recursively archives child chats (replaces
`deleteChatTree()` which hard-deleted)
- Chat daemon's `ArchiveChat()` archives the full chat tree in a
transaction
- Authorization uses `ActionUpdate` instead of `ActionDelete`

### SDK
- Replace `DeleteChat()` with `ArchiveChat()` and `UnarchiveChat()`
- Add `Archived` field to `Chat` struct

### Frontend
- `archiveChat` API call uses `POST .../archive` instead of `DELETE`
- No UI changes — the "Archive agent" button now actually archives
instead of deleting

## Design Decision

This follows the **template version archive pattern** (Pattern B in the
codebase):
- `archived boolean` column (not `deleted boolean`)
- Dedicated `POST .../archive` and `POST .../unarchive` routes (not
repurposing `DELETE`)
- Reversible — users can unarchive via the API (UI for this will come
later)
2026-02-27 16:46:19 -05:00
Kyle Carberry 52dad56462 fix(coderd): refresh OAuth token before GitHub API calls in chat diff (#22415)
## Problem

`resolveChatGitHubAccessToken` reads the `OAuthAccessToken` directly
from the database without refreshing it. When the token expires, GitHub
returns "bad credentials" and the chat diff features break.

## Fix

Call `config.RefreshToken()` before returning the token — the same code
path used by `provisionerdserver` when handing tokens to provisioners.

- Builds a map of provider ID → `*externalauth.Config` during the
existing config iteration
- After fetching the `ExternalAuthLink` from the DB, calls
`cfg.RefreshToken()` if a matching config exists
- On refresh failure, falls through to the existing token (GitHub tokens
without expiry still work) with a debug log
2026-02-27 16:37:17 -05:00
Kyle Carberry 360df1d84f fix(chatd): publish streaming message_part events during compaction (#22410)
## Problem

Context compaction in chatd persisted durable messages for the
`chat_summarized` tool call and result via `publishMessage`, but never
published `message_part` streaming events via `publishMessagePart`. This
meant connected clients had no streaming representation of the
compaction.

The client's `streamState` (built entirely from `message_part` events in
`streamState.ts`) never saw the compaction tool call, so:

- No **"Summarizing..."** running state was shown to the user during
summary generation (which can take up to 90s).
- The durable `message` events arrived after or interleaved with the
`status: waiting` event, causing the tool to appear as "Summarized" with
the chat appearing to just stop.

## Fix

### 1. `CompactionOptions.OnStart` callback (chatloop)

Added an `OnStart` callback to `CompactionOptions`, called in
`maybeCompact` right before `generateCompactionSummary` (the slow LLM
call). This gives `chatd` a hook to publish the tool-call `message_part`
immediately when compaction begins.

### 2. Tool-result streaming part (chatd)

`persistChatContextSummary` now publishes a tool-result `message_part`
before the durable `message` events, so clients transition from
"Summarizing..." to "Summarized" before the status change arrives.

### Event ordering is now:
1. `message_part` (tool call via `OnStart`) — client shows
"Summarizing..."
2. LLM generates summary (up to 90s)
3. `message_part` (tool result) — client shows "Summarized" in stream
state
4. `message` (assistant) — durable message persisted, stream state
resets
5. `message` (tool) — durable tool result persisted
6. `status: waiting` — chat transitions to idle

## Tests

- **`OnStartFiresBeforePersist`**: Verifies callback ordering is
`on_start` → `generate` → `persist`.
- **`OnStartNotCalledBelowThreshold`**: Verifies `OnStart` is not called
when context usage is below the compaction threshold.
2026-02-27 16:33:39 -05:00
Kyle Carberry 1a87e74574 fix(site): prevent race conditions when switching chats on agents page (#22404)
## Problem

When switching between chats on the agents page, stream parts could be
lost or applied to the wrong chat due to several race conditions in
`ChatContext.ts`:

1. **`startTransition` deferred parts escape cleanup** —
`startTransition(() => store.applyMessageParts(parts))` defers the state
update. If a chat switch happens between `flushMessageParts` being
called and the transition executing, old-chat parts could apply after
`resetTransientState()` has already cleared stream state for the new
chat.

2. **`message` event has no `chat_id` filter** — Unlike `message_part`,
`queue_update`, and `status` events, the `message` event handler did not
check `streamEvent.chat_id`. While the server-scoped WebSocket makes
this safe in practice, it's an inconsistency in defensive programming.

3. **Brief stale message window on switch** — Between `chatID` changing
and `replaceMessages()` firing (after the query resolves), the store
held old-chat messages while the new WebSocket was already connected.

## Changes

### `ChatContext.ts`
- Added `activeChatIDRef` to track the currently active chat ID
- Guard `startTransition` callback: check `activeChatIDRef` before
applying message parts, discarding them if the chat has switched
- Added `chat_id` filter to `message` event handler, matching the
pattern used by all other event types
- Added `store.replaceMessages([])` to the chatID-change effect so
messages are cleared immediately on switch

### `ChatContext.test.tsx`
Four new tests covering the chat-switch lifecycle:
- WebSocket closure and state reset when chatID changes
- `message` event filtering by `chat_id`
- `startTransition` deferred parts discarded after switch
- Messages cleared immediately before new query resolves

All 13 tests pass (8 existing + 4 new + 1 existing).
2026-02-27 15:33:16 -05:00
Kyle Carberry bb97ba727f fix(coderd): allow non-admin users to list chat model configs (#22407)
## Problem

Non-admin users of the Agents (chat) feature send `model_config_id:
"00000000-0000-0000-0000-000000000000"` (nil UUID) when creating chats,
because the `GET /api/experimental/chats/model-configs` endpoint
requires `policy.ActionRead` on `rbac.ResourceDeploymentConfig`, which
is only granted to admins.

The flow:
1. `AgentsPage.tsx` calls `useQuery(chatModelConfigs())` → hits
`listChatModelConfigs`
2. Non-admin users get a **403 Forbidden** response
3. `chatModelConfigsQuery.data` is `undefined`, so the
`modelConfigIDByModelID` map is empty
4. `handleCreateChat` falls back to `nilUUID` for `model_config_id`
5. The backend rejects the nil UUID: `"Invalid model config ID."`

## Fix

Changed `listChatModelConfigs` to allow all authenticated users to read
model configs:
- **Admin users** continue to see all configs (including disabled ones)
for management via `GetChatModelConfigs`
- **Non-admin users** now see only enabled configs via
`GetEnabledChatModelConfigs` with a system context, which is sufficient
for using the chat feature

This follows the same pattern as `listChatModels`, which already uses
`dbauthz.AsSystemRestricted(ctx)` to allow all authenticated users to
see available models.

Write endpoints (create/update/delete) retain their existing
`ResourceDeploymentConfig` authorization.

## Testing

- Updated `TestListChatModelConfigs/ForbiddenForOrganizationMember` →
`SuccessForOrganizationMember` to verify non-admin users can list
enabled model configs
- All existing chat tests continue to pass
2026-02-27 15:31:04 -05:00
Kyle Carberry f509c841cf fix(chatd): recover stale chats after coderd redeployment (#22405)
## Problem

When coderd instances are redeployed (e.g. rolling deployment on
dogfood), in-flight chats get stuck in `running` status permanently. The
UI shows them as "thinking" with a spinning indicator, but no worker is
actually processing them. They never error or resume.

## Root Cause

Two bugs combine to cause this:

### Bug 1: Shutdown cleanup uses a canceled context

The `processChat` defer block updates the chat status in the DB when
processing completes. But it uses `ctx`, which `Close()` cancels
*before* the defer runs. The DB transaction silently fails with
`context.Canceled`, leaving the chat in `status=running` with a dead
`worker_id`.

```go
// Close() calls p.cancel() which cancels ctx
// Then the defer tries to use the now-canceled ctx:
defer func() {
    err := p.db.InTx(func(tx database.Store) error {
        tx.GetChatByIDForUpdate(ctx, chat.ID) // FAILS
        tx.UpdateChatStatus(ctx, ...)          // FAILS
    }, nil)
}()
```

### Bug 2: Stale recovery runs only once at startup

`recoverStaleChats()` was called only once in `start()`, not
periodically. During a rolling deployment, the new instance starts while
the old one is still alive (fresh heartbeat). By the time the old
instance crashes, no one checks again.

## Fix

1. **Use `context.WithoutCancel(ctx)` in the processChat defer** — the
cleanup transaction now completes even during graceful shutdown.

2. **Run `recoverStaleChats` periodically** — a second ticker in the
`start()` loop checks for stale chats at `inFlightChatStaleAfter / 5`
intervals (default: every 1 minute). This catches orphaned chats even
when the instance that owns them crashes without clean shutdown.

## Tests

- `TestRecoverStaleChatsPeriodically` — Verifies chats orphaned *after*
startup are recovered by the periodic loop (not just the startup check).
- `TestNewReplicaRecoversStaleChatFromDeadReplica` — Verifies a new
replica recovers stale chats on startup.
- `TestWaitingChatsAreNotRecoveredAsStale` — Negative test: `waiting`
chats are not incorrectly modified by recovery.
2026-02-27 15:25:40 -05:00
Kyle Carberry b65c0766d2 feat: add line-based read_file tool with safety limits (#22400)
## Summary

Adds a new line-based file reading endpoint to the workspace agent,
replacing the unbounded byte-based approach for the `read_file` chat
tool and `coder_workspace_read_file` MCP tool.

**Problem**: The current `read_file` tool returns the entire file
contents with no limits, which can blow up LLM context windows and cause
OOM issues with large files.

**Solution**: Inspired by [`coder/mux`](https://github.com/coder/mux)
and [`openai/codex`](https://github.com/openai/codex), implement a
line-based reader with safety limits.

## Changes

### Agent (`agent/agentfiles/`)
- New `/read-file-lines` endpoint with `HandleReadFileLines` handler
- Line-based `offset` (1-based line number, default: 1) and `limit`
(line count, default: 2000)
- Safety constants:
  | Constant | Value | Purpose |
  |---|---|---|
  | `MaxFileSize` | 1 MB | Reject files larger than this at stat |
| `MaxLineBytes` | 1,024 | Per-line truncation with `... [truncated]`
marker |
  | `MaxResponseLines` | 2,000 | Max lines per response |
  | `MaxResponseBytes` | 32 KB | Max total response size |
  | `DefaultLineLimit` | 2,000 | Default when no limit specified |
- Line numbering format: `1\tcontent` (tab-separated)
- Structured JSON response: `{ success, file_size, total_lines,
lines_read, content, error }`
- Hard errors when limits exceeded — tells the LLM to use
`offset`/`limit`
- Existing byte-based `/read-file` endpoint preserved (used by
`instruction.go`)

### SDK (`codersdk/workspacesdk/`)
- `ReadFileLinesResponse` type added
- `ReadFileLines` method added to `AgentConn` interface
- Mock regenerated

### Chat tool (`coderd/chatd/chattool/`)
- `read_file` tool now uses `conn.ReadFileLines()` instead of
`conn.ReadFile()`
- Updated tool description to document line-based parameters
- Response includes `file_size`, `total_lines`, `lines_read` metadata

### MCP tool (`codersdk/toolsdk/`)
- `coder_workspace_read_file` updated to use line-based reading
- Schema descriptions updated for line-based offset/limit
- Removed `maxFileLimit` constant (agent handles limits now)

### Tests
- 13 new test cases for `TestReadFileLines`:
- Path validation (empty, relative, non-existent, directory, no
permissions)
  - Empty file handling
  - Basic read, offset, limit, offset+limit combinations
  - Offset beyond file length
  - Long line truncation (>1024 bytes)
  - Large file rejection (>1MB)
- All existing tests pass unchanged

## Design decisions

| Decision | Rationale |
|---|---|
| Line-based, not byte-based | Both coder/mux and openai/codex use
line-based — matches how LLMs reason about code |
| Default limit of 2000 | Matches codex; prevents accidental full-file
dumps while being generous |
| 32 KB response cap | Compromise between mux (16 KB) and codex (no cap)
|
| 1024 byte/line truncation with marker | More generous than codex
(500), marker helps LLM know data is missing |
| Hard errors on overflow | Matches mux; forces LLM to paginate rather
than getting partial data |
| Preserve byte-based endpoint | `instruction.go` needs raw byte access
for AGENTS.md |
2026-02-27 15:12:56 -05:00
Kyle Carberry ff687aa780 fix: re-read chat before publishing status event to preserve AI title (#22402)
## Problem

Chat titles revert to the fallback truncated title after briefly showing
the AI-generated title. Even reloading the page doesn't help — the
correct title flashes then gets overwritten.

## Root Cause

Single bug, two symptoms.

In `processChat` (`coderd/chatd/chatd.go`), the `chat` variable is
passed by value. The flow:

1. `processChat(ctx, chat)` receives `chat` with the initial fallback
title (truncated first message).
2. Inside `runChat`, `maybeGenerateChatTitle` generates an AI title,
writes it to the DB via `UpdateChatByID`, and publishes a `title_change`
event. **The DB has the correct title.** The client briefly displays it.
3. `runChat` returns. The **deferred cleanup** in `processChat`
publishes `publishChatPubsubEvent(chat, StatusChange)` — but `chat` here
is the original value copy that still has the **old fallback title**.
4. The frontend receives the `status_change` SSE event and
**unconditionally applies `title` from every event kind** (see
`AgentsPage.tsx` line ~305: `title: updatedChat.title`). This overwrites
the correct AI title with the stale fallback.

**Why reload doesn't help:** If the chat is still processing when the
page reloads, `listChats` loads the correct title from the DB, but then
the deferred `status_change` event arrives moments later and clobbers
it. The title was always in the DB — it was the pubsub event that kept
overwriting it.

## Fix

Re-read the chat from the database in the deferred cleanup before
publishing the final `status_change` event, so it carries the current
(AI-generated) title.
2026-02-27 15:06:36 -05:00
Kyle Carberry d4cfb24a4a fix: update document title when switching agents on the agents page (#22401)
When navigating to a specific agent on the Agents page, the browser tab
title now reflects the agent's chat title (e.g. `Fix login bug - Agents
- Coder`). When the title hasn't loaded yet or when navigating away, it
falls back to `Agents - Coder`.

**Changes:**
- Added a `useEffect` in `AgentDetail` that sets `document.title` via
the existing `pageTitle` utility whenever the chat title changes.
- The cleanup function resets the title back to `Agents - Coder` when
unmounting (navigating away from the agent).
2026-02-27 14:26:28 -05:00
Kyle Carberry 344d11fa22 feat: include OS and working directory in workspace agent prompt injection (#22399)
When injecting system instructions into the chat prompt, include:

1. **Operating system** and **working directory** from the
`workspace_agents` table
2. **Home-level instructions** from `~/.coder/AGENTS.md` (existing
behavior)
3. **Project-level instructions** from `<pwd>/AGENTS.md` (new)

The XML tag is renamed from `<coder-home-instructions>` to
`<system-instructions>` since it now carries more than just the home
instruction file.

### Example output (both files present)

```xml
<system-instructions>
Operating System: linux
Working Directory: /home/coder/coder

Source: /home/coder/.coder/AGENTS.md
... home instructions ...

Source: /home/coder/coder/AGENTS.md
... project instructions ...
</system-instructions>
```

### Example output (no AGENTS.md files)

```xml
<system-instructions>
Operating System: linux
Working Directory: /home/coder/coder
</system-instructions>
```

### Changes

- **`coderd/chatd/instruction.go`**:
- Renamed types: `homeInstructionContext` → `agentContext`, added
`instructionFile` struct
  - Extracted `readInstructionFileAtPath` shared helper
- Added `readWorkingDirectoryInstructionFile` to read `<pwd>/AGENTS.md`
- Replaced `formatHomeInstruction` with `formatInstructions` that
renders both files under `<system-instructions>`
- **`coderd/chatd/chatd.go`**:
- Renamed `resolveHomeInstruction` → `resolveInstructions`; now reads
both home and pwd instruction files
- `resolveAgentContext` returns `agentContext` (renamed from
`homeInstructionContext`)
- pwd file read is skipped gracefully if directory is empty or file
doesn't exist
- **`coderd/chatd/instruction_test.go`**:
- Added `TestReadWorkingDirectoryInstructionFile` (success, not-found,
empty-directory)
- Replaced `TestFormatHomeInstruction` with `TestFormatInstructions`
covering all combinations
- Added ordering test (`AgentContextBeforeFiles`) to verify OS/pwd
appear before file sources
2026-02-27 14:21:23 -05:00
Kyle Carberry 59cec5be65 feat: add pagination and popularity sorting to chattool list_templates (#22398)
## Summary

The `chattool` `list_templates` tool previously returned all templates
in a single response with no popularity signal. On deployments with many
templates (e.g. 71 on dogfood), this wastes tokens and makes it hard for
the AI to pick the right template for broad user questions.

## Changes

Single file: `coderd/chatd/chattool/listtemplates.go`

- **`page` parameter** — optional, 1-indexed, 10 results per page
- **Popularity sort** — queries
`GetWorkspaceUniqueOwnerCountByTemplateIDs` to get active developer
counts, then sorts descending (most popular first). The DB query returns
templates alphabetically, so this explicit sort is needed.
- **`active_developers`** — included on each template item so the agent
can see the signal
- **Pagination metadata** — `page`, `total_pages`, `total_count` in the
response so the agent knows there are more results
- **Updated tool description** — tells the agent that results are
ordered by popularity and paginated

## Frontend

No frontend changes needed. The renderer already reads `rec.templates`
and `rec.count` from the response — the new fields (`page`,
`total_pages`, `total_count`) are additive and safely ignored.
2026-02-27 14:06:22 -05:00
Kyle Carberry 7043e773cf fix: auto-scroll to bottom when switching chats on agents page (#22397)
When switching between chats on the agents page, the scroll position was
preserved from the previous chat instead of resetting to show the most
recent messages.

## Problem
Clicking a different chat in the sidebar loaded the new chat's messages
but kept the scroll container at whatever position the user had scrolled
to in the previous chat. This meant users often landed in the middle of
a conversation instead of at the bottom where the latest messages are.

## Fix
Added a `useEffect` in `AgentDetail` that resets `scrollTop` to `0`
whenever `agentId` changes. The scroll container uses
`flex-col-reverse`, so `scrollTop = 0` corresponds to the bottom (most
recent messages).
2026-02-27 14:00:50 -05:00
Kyle Carberry 0252205374 agents: do not use bridge config vars for models (#22392)
<!--

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.

-->
2026-02-27 12:24:38 -05:00
edee917d88 feat: add experimental agents support (#22290)
feat: add AI chat system with agent tools and chat UI

Introduce the chatd subsystem and Agents UI for AI-powered chat
within Coder workspaces.

- Add chatd package with chat loop, message compaction, prompt
  management, and LLM provider integration (OpenAI, Anthropic)
- Add agent tools: create workspace, list/read templates, read/write/
  edit files, execute commands
- Add chat API endpoints with streaming, message editing, and
  durable reconnection
- Add database schema and migrations for chats, chat messages, chat
  providers, and chat model configs
- Add RBAC policies and dbauthz enforcement for chat resources
- Add Agents UI pages with conversation timeline, queued messages
  list, diff viewer, and model configuration panel
- Add comprehensive test coverage including coderd integration tests,
  chatd unit tests, and Storybook stories
- Gate feature behind experiments flag

---------

Co-authored-by: Cian Johnston <cian@coder.com>
Co-authored-by: Danielle Maywood <danielle@themaywoods.com>
Co-authored-by: Jeremy Ruppel <jeremy@coder.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 16:50:56 +00:00
Kyle CarberryandCursor Agent c3ea544162 fix(site): use native thin scrollbar style for admin bar (#21825)
## Summary

The bottom admin bar (DeploymentBannerView) was showing a thick
scrollbar when content overflowed horizontally. This change applies the
native thin scrollbar style instead.

## Changes

- Added `[scrollbar-width:thin]` Tailwind CSS arbitrary value to the
deployment banner container

This uses the native CSS `scrollbar-width: thin` property which is
supported in modern browsers (Firefox, Chrome, Edge, Safari) and
provides a less obtrusive scrollbar when horizontal scrolling is needed.

## Testing

- The change is purely CSS and was verified with lint and format checks
passing

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Purely a CSS styling tweak with no behavioral, data, or security
impact; risk is limited to minor cross-browser appearance differences.
> 
> **Overview**
> Updates the dashboard `DeploymentBannerView` bottom admin bar styling
to use the native CSS `scrollbar-width: thin` via Tailwind
(`[scrollbar-width:thin]`), reducing scrollbar thickness when the banner
overflows horizontally.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
ba36e48d66. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

Co-authored-by: Cursor Agent <cursor@coder.com>
2026-02-02 01:03:07 -05:00
Kyle Carberry 7b273b0b8c fix: add blink-so to contributors bypass (#18215) 2025-06-03 17:45:28 +00:00
Kyle Carberry d44d8abcd1 fix: improve task report tool complete status (#18138) 2025-05-30 17:46:19 +00:00
Kyle Carberry bedeb4710b fix: improve task reporting tool description (#18119)
In my (albeit subjective) testing, this dramatically improved the
reporting ability - both in frequency and accuracy.
2025-05-30 00:00:12 +00:00
Kyle Carberry 57ddb3c615 fix: update ai code prompt parameter in start-workspace.yaml 2025-04-15 15:15:00 -04:00
Kyle Carberry 362dcfefdd fix: update start-workspace.yaml for dev.coder.com (#17407)
I added the secrets and removed the aidev env secrets.
2025-04-15 15:10:30 -04:00
Kyle Carberry f3e5bb9276 fix: convert workspace id in db2sdk.WorkspaceAppStatus (#17201)
This was causing no status to be rendered in the list, and
`latest_app_status` to always be nil.
2025-04-01 14:49:32 -04:00
Kyle Carberry 583a0c652f feat: add frontend for app statuses (#17178)
Check out the stories for the exacts...


![image](https://github.com/user-attachments/assets/a1e1b9b0-7b37-4e0d-b99e-64b4766519ef)


![image](https://github.com/user-attachments/assets/d3eb580d-071c-4caf-b393-a7e87da61f5e)
2025-04-01 12:35:58 -04:00
Kyle Carberry 8ea956fc11 feat: add app status tracking to the backend (#17163)
This does ~95% of the backend work required to integrate the AI work.

Most left to integrate from the tasks branch is just frontend, which
will be a lot smaller I believe.

The real difference between this branch and that one is the abstraction
-- this now attaches statuses to apps, and returns the latest status
reported as part of a workspace.

This change enables us to have a similar UX to in the tasks branch, but
for agents other than Claude Code as well. Any app can report status
now.
2025-03-31 10:55:44 -04:00
Kyle Carberry 6f9b3c1592 chore: sign the windows installer (#14353) 2024-08-19 20:33:37 -04:00
Kyle Carberry e2cec454bc fix: check for io.EOF error in derpmap to resolve flake (#14125)
See: https://github.com/coder/coder/actions/runs/10218717887/job/28275465405?pr=14045
2024-08-02 17:08:47 +00:00
Kyle CarberryandSteven Masley 6e36082b0f chore: add github.com user id association (#14045)
* chore: add github.com user id association

This will eventually be used to show an indicator in the UI
to star the repository if you've been using Coder for a while
and have not starred the repo.

If you have, we'll never show a thing!

* gen

* Fix model query

* Fix linting

* Ignore auditing github.com user id

* Add test

* Fix gh url var name

* Update migration

* Update coderd/database/dbauthz/dbauthz.go

Co-authored-by: Steven Masley <Emyrk@users.noreply.github.com>

* Fix updating to when the token changes

* Fix migration

---------

Co-authored-by: Steven Masley <Emyrk@users.noreply.github.com>
2024-08-02 12:49:36 -04:00
Kyle Carberry 5d42f4aa7b fix: run update-flake with PAT to allow workflow runs (#14067)
See the comment in the code.
2024-07-31 11:43:43 -04:00
Kyle Carberry 893169c83b fix: duplicate tags map in mutation to resolve race (#14047)
* fix: duplicate tags map in mutation to resolve race

See: https://github.com/coder/coder/actions/runs/10149619748/job/28064952716?pr=14046

* Fix deployment values race
2024-07-30 07:37:13 +00:00
Kyle Carberry b7102b39af chore: add script to update flake automatically (#14046) 2024-07-29 14:29:22 -04:00
Kyle Carberry 3e1fae7d3d chore: add Star the Repo to support links (#13924) 2024-07-17 15:39:03 -04:00
Kyle Carberry 54e8f30002 chore: remove failing_sections from healthcheck (#13426)
Closes #10854.
2024-06-21 14:49:02 -04:00
Kyle Carberry 3ef12ac284 fix: remove connected button (#13625)
It didn't make a lot of sense in current form. It will when we improve autostop.
2024-06-21 11:41:59 -04:00
Kyle Carberry 495eea452f fix: track login page correctly (#13618) 2024-06-20 21:33:47 +00:00
Kyle Carberry 57b38e5bb8 fix: allow coder.com in CSP if telemetry is enabled (#13615)
* fix: allow coder.com in CSP if telemetry is enabled

* Fix control couple lint
2024-06-20 16:05:22 -04:00
Kyle Carberry 0793a4b35b feat: add cross-origin reporting for telemetry in the dashboard (#13612)
* feat: add cross-origin reporting for telemetry in the dashboard

* Respect the telemetry flag

* Fix embedded metadata

* Fix compilation error

* Fix linting
2024-06-20 15:19:45 -04:00
Kyle Carberry 7049d7a881 fix: display trial errors in the dashboard (#13601)
* fix: display trial errors in the dashboard

The error was essentially being ignored before!

* Remove day mention in product of trial

* fmt
2024-06-19 12:02:51 -04:00
Kyle Carberry 3a1fa04590 fix: write server config to telemetry (#13590)
* fix: add external auth configs to telemetry

* Refactor telemetry to send the entire config

* gen

* Fix linting
2024-06-18 16:20:21 -04:00
Kyle CarberryandDean Sheather 1ca5dc0328 chore: always use the latest released version tag when building (#13556)
* chore: always use the latest released version tag when building

* Update version.sh

Co-authored-by: Dean Sheather <dean@deansheather.com>

---------

Co-authored-by: Dean Sheather <dean@deansheather.com>
2024-06-12 14:52:35 -04:00
Kyle Carberry c9cca9d56e fix: transform underscores to hyphens for github login (#13384)
Fixes #13339.
2024-06-11 13:34:05 +00:00
Kyle CarberryandSpike Curtis 7c081dcd6f fix: replace invalid utf-8 sequences in agent logs (#13436)
* fix: replace invalid utf-8 sequences in agent logs

Fixes #13433.

* fix: replace invalid UTF-8 with ❌, add regression

Signed-off-by: Spike Curtis <spike@coder.com>

---------

Signed-off-by: Spike Curtis <spike@coder.com>
Co-authored-by: Spike Curtis <spike@coder.com>
2024-06-10 15:27:11 +04:00
Kyle Carberry 79d73f77f5 chore: skip Azure TestExpiresSoon (#13385)
Adds some context to the test skip so it can be removed or enabled in the future.
2024-05-28 16:45:41 +00:00
Kyle CarberryandColin Adler 3364abecdd chore: generate terraform testdata with matching terraform version (#13343)
Terraform changed the default output of the `terraform graph` command. You must put `-type=plan` to keep the prior behavior.


Co-authored-by: Colin Adler <colin1adler@gmail.com>
2024-05-22 12:45:47 -05:00
Kyle CarberryandAsher 3e3118794f chore: add build targets to nix flake (#13186)
* chore: add build targets to nix flake

Enables `nix build github:coder/coder#main`!

* Fix all packages

* Add back pnpm

* Update flake.nix

Co-authored-by: Asher <ash@coder.com>

* Remove yarn

* fmt

---------

Co-authored-by: Asher <ash@coder.com>
2024-05-06 18:21:20 -04:00
Kyle Carberry 93d8812284 chore: remove codecov (#13124)
* chore: remove codecov

It wasn't being used anymore.

* Update actions packages
2024-05-01 21:47:25 +00:00
Kyle Carberry 47993e3fcf chore: update tailscale to fix leaking dns lookup (#13109)
See failure in: https://github.com/coder/coder/actions/runs/8887860105/job/24403798734#step:5:376
2024-04-30 12:36:16 +00:00
Kyle Carberry d302570091 chore: remove GITHUB_TOKEN from dogfood env vars (#13106)
This was stale all the time!
2024-04-30 01:26:58 +00:00
Kyle Carberry 4e5960660e chore: fix dependency review action (#13105)
See https://github.com/actions/dependency-review-action/issues/757
2024-04-29 20:51:01 -04:00
Kyle Carberry fbb98b950a chore: centralize build info for site (#13104)
The build info passed to the frontend via HTML was incorrect.
2024-04-29 20:50:49 -04:00
Kyle Carberry 1bda8a0856 feat: add deployment_id to the ui and licenses (#13096)
* feat: expose `deployment_id` in the user dropdown

* feat: add license deployment_id verification

* Ignore wireguard.com from mlc config
2024-04-29 16:50:11 -04:00
Kyle Carberry 1b3185c047 chore: lower azure expires soon (#13097)
They haven't provisioned new certificates yet.
2024-04-29 12:34:18 -04:00
Kyle Carberry d3f3ace220 chore: reduce dashboard requests from seeded data (#13034)
* chore: reduce requests the dashboard makes from seeded data

We already inject all of this content in `index.html`.

There was also a bug with displaying a loading indicator when
the workspace proxies endpoint 404s.

* Fix first user fetch

* Add util

* Add cached query for entitlements and experiments

* Fix authmethods unnecessary request

* Fix unnecessary region request

* Fix fmt

* Debug

* Fix test
2024-04-22 16:07:56 -04:00
Kyle Carberry 8d1220e0c8 chore: add generate script for azure instance identity (#13028)
* chore: add generate script for azure instance identity

This also adds new issuing certificates from:
https://learn.microsoft.com/en-us/azure/security/fundamentals/azure-ca-details?tabs=certificate-authority-chains

* Fix shell lint

* Fix shell fmt

* Fix RSA issuing certificate
2024-04-22 15:39:08 -04:00
Kyle Carberry 227e632053 fix: add grace period before showing replicas license error (#12989)
Fixes #8665.
2024-04-17 13:30:32 -04:00
Kyle Carberry 41ca6e4f7f chore: add created_at to workspace resource telemetry (#12969) 2024-04-15 20:06:59 +00:00
Kyle Carberry d3790bb5be fix: use provided username when fetching workspaces (#12955) 2024-04-13 14:39:57 -04:00
Kyle Carberry 7698cfda72 chore: remove unnecessary extraction library (#12847)
This was allocating ~256KB on init.
2024-04-02 11:19:54 -04:00
Kyle Carberry 94e82f9662 chore: use fork of chroma to remove unused inits (#12842)
* chore: use fork of chroma to remove unused inits

This seems fine to do since compilation errors would occur
if it were actually in use.

Everything seems fine here.

* Update validator
2024-04-02 14:14:38 +00:00
Kyle Carberry f5a70500d2 chore: update tailscale for to lazily load hostinfo (#12840)
Speeds up `init()` by ~10ms
2024-04-01 17:04:39 -04:00
Kyle Carberry 2a30194ed7 chore: use validator fork to fix 10ms of init time (#12837)
@ammario inspired me
2024-04-01 16:31:26 -04:00
Kyle Carberry d428c05694 chore: move log output message before logs begin streaming (#12836) 2024-04-01 20:02:50 +00:00
Kyle Carberry fd8010c26d chore: make build only run on main (#12753) 2024-03-25 19:15:18 +00:00
Kyle Carberry 03ab37b343 chore: remove middleware to request version and entitlement warnings (#12750)
This cleans up `root.go` a bit, adds tests for middleware HTTP transport
functions, and removes two HTTP requests we always always performed previously
when executing *any* client command.

It should improve CLI performance (especially for users with higher latency).
2024-03-25 15:01:42 -04:00
Kyle Carberry a6b8f381f0 feat: support windows containers in bootstrap script (#12662)
Fixes #7462.
2024-03-22 14:48:51 +00:00
Kyle Carberry d3c9aaf57b chore: update hero image for refreshed dashboard (#12712)
* chore: update hero image for refreshed dashboard

I used html.to.figma for this. It's in the following Figma:
https://www.figma.com/file/m4zkIU7e64BNyRlUShuluq/README-Screenshot?type=design&node-id=13-1993&mode=design&t=o2AhL8y7u8uFMVPI-0

I couldn't take screenshots directly, because I'm on Linux and the macOS
browser with the icons look much better.

* Try stacked images

* Try stacked inverted
2024-03-21 16:28:02 -04:00
Kyle Carberry 4ae1f40eee chore: add docs for adding e2e tests (#12677) 2024-03-19 18:25:05 +00:00