mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: agents git watch backend (#22565)
Adds real-time git status watching for workspace agents, so the frontend
can subscribe over WebSocket and show
git file changes in near real-time.
1. Subscription is scoped to a **chat** via `GET
/api/experimental/chats/{chat}/git/watch`.
2. The workspace agent automatically determines which paths to watch
based on tool calls made by the chat (and its ancestor chats).
3. Workspace agent polls subscribed repo working trees on a 30s
interval, on tools calls, and on explicit `refresh` from the client.
4. Scans are rate-limited to at most once per second.
5. Edited paths are tracked **in-memory** inside the workspace agent.
There is no database persistence — state is lost on agent restart. This
will be addresses in a future PR.
6. Messages sent over WebSocket include a full-repo snapshot (unified
diff, branch, origin). A new message is emitted only when the snapshot
changes.
This PR was implemented with AI with me closely controlling what it's
doing. The code follows a plan file that was updated continuously during
implementation. Here's the file if you'd like to see it:
[project.md](https://gist.github.com/hugodutka/8722cf80c92f8a56555f7bc595b770e2).
It reflects the current state of the PR.
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
context "context"
|
||||
io "io"
|
||||
net "net"
|
||||
http "net/http"
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
codersdk "github.com/coder/coder/v2/codersdk"
|
||||
healthsdk "github.com/coder/coder/v2/codersdk/healthsdk"
|
||||
workspacesdk "github.com/coder/coder/v2/codersdk/workspacesdk"
|
||||
wsjson "github.com/coder/coder/v2/codersdk/wsjson"
|
||||
tailnet "github.com/coder/coder/v2/tailnet"
|
||||
uuid "github.com/google/uuid"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
@@ -431,6 +433,18 @@ func (mr *MockAgentConnMockRecorder) SSHOnPort(ctx, port any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSHOnPort", reflect.TypeOf((*MockAgentConn)(nil).SSHOnPort), ctx, port)
|
||||
}
|
||||
|
||||
// SetExtraHeaders mocks base method.
|
||||
func (m *MockAgentConn) SetExtraHeaders(h http.Header) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetExtraHeaders", h)
|
||||
}
|
||||
|
||||
// SetExtraHeaders indicates an expected call of SetExtraHeaders.
|
||||
func (mr *MockAgentConnMockRecorder) SetExtraHeaders(h any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetExtraHeaders", reflect.TypeOf((*MockAgentConn)(nil).SetExtraHeaders), h)
|
||||
}
|
||||
|
||||
// SignalProcess mocks base method.
|
||||
func (m *MockAgentConn) SignalProcess(ctx context.Context, id, signal string) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -505,6 +519,21 @@ func (mr *MockAgentConnMockRecorder) WatchContainers(ctx, logger any) *gomock.Ca
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WatchContainers", reflect.TypeOf((*MockAgentConn)(nil).WatchContainers), ctx, logger)
|
||||
}
|
||||
|
||||
// WatchGit mocks base method.
|
||||
func (m *MockAgentConn) WatchGit(ctx context.Context, logger slog.Logger, chatID uuid.UUID) (*wsjson.Stream[codersdk.WorkspaceAgentGitServerMessage, codersdk.WorkspaceAgentGitClientMessage], error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "WatchGit", ctx, logger, chatID)
|
||||
ret0, _ := ret[0].(*wsjson.Stream[codersdk.WorkspaceAgentGitServerMessage, codersdk.WorkspaceAgentGitClientMessage])
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// WatchGit indicates an expected call of WatchGit.
|
||||
func (mr *MockAgentConnMockRecorder) WatchGit(ctx, logger, chatID any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WatchGit", reflect.TypeOf((*MockAgentConn)(nil).WatchGit), ctx, logger, chatID)
|
||||
}
|
||||
|
||||
// WriteFile mocks base method.
|
||||
func (m *MockAgentConn) WriteFile(ctx context.Context, path string, reader io.Reader) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Reference in New Issue
Block a user