fix: fall back to local git watcher for chat diff drawer (#24512)

The Ctrl+D diff drawer in `coder exp agents` only rendered PR-backed
diffs returned by `/api/experimental/chats/{id}/diff`. Local working
tree changes in a chat's workspace returned an empty diff, so the
drawer showed "No diff contents" with no file summary.

Centralise diff loading behind a single `fetchChatDiffContents` helper
that first hits `/diff`, then falls back to the chat git watcher
WebSocket (`/stream/git`) when the remote diff is empty. Aggregate the
agent's `WorkspaceAgentRepoChanges` into a `ChatDiffContents` value so
the drawer can derive the file summary and styled body from the local
unified diff. Missing workspaces, missing agents, and watcher timeouts
are treated as graceful fallbacks that render the empty-diff
placeholder instead of a hard error.

> Mux is opening this PR on Mike's behalf.
This commit is contained in:
Michael Suchacz
2026-04-22 18:08:02 +02:00
committed by GitHub
parent c23abc691f
commit 7904bed947
12 changed files with 1914 additions and 101 deletions
+4 -4
View File
@@ -1711,7 +1711,7 @@ func (api *API) authorizeChatWorkspaceExec(
workspace, err := api.Database.GetWorkspaceByID(ctx, chat.WorkspaceID.UUID)
if httpapi.Is404Error(err) {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Chat workspace not found.",
Message: codersdk.ChatGitWatchWorkspaceNotFoundMessage,
})
return database.Workspace{}, false
}
@@ -1742,7 +1742,7 @@ func (api *API) watchChatGit(rw http.ResponseWriter, r *http.Request) {
logger = api.Logger.Named("chat_git_watcher").With(slog.F("chat_id", chat.ID))
)
if _, ok := api.authorizeChatWorkspaceExec(rw, r, chat, "Chat has no workspace to watch."); !ok {
if _, ok := api.authorizeChatWorkspaceExec(rw, r, chat, codersdk.ChatGitWatchNoWorkspaceMessage); !ok {
return
}
@@ -1756,7 +1756,7 @@ func (api *API) watchChatGit(rw http.ResponseWriter, r *http.Request) {
}
if len(agents) == 0 {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: "Chat workspace has no agents.",
Message: codersdk.ChatGitWatchWorkspaceNoAgentsMessage,
})
return
}
@@ -1780,7 +1780,7 @@ func (api *API) watchChatGit(rw http.ResponseWriter, r *http.Request) {
}
if apiAgent.Status != codersdk.WorkspaceAgentConnected {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: fmt.Sprintf("Agent state is %q, it must be in the %q state.", apiAgent.Status, codersdk.WorkspaceAgentConnected),
Message: codersdk.ChatGitWatchAgentStateMessage(apiAgent.Status),
})
return
}