Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
502 changes: 345 additions & 157 deletions crates/agent-gateway/internal/proto/v1/gateway.pb.go

Large diffs are not rendered by default.

62 changes: 62 additions & 0 deletions crates/agent-gateway/internal/server/websocket_fs_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,57 @@ func (c *websocketConnection) handleFsList(req websocketRequest) {
_ = c.writeResponse(req.ID, websocketFsListResponsePayload(resp))
}

func (c *websocketConnection) handleFsReadEditableText(req websocketRequest) {
type payload struct {
Workdir string `json:"workdir"`
Path string `json:"path"`
}

var body payload
if err := decodeWebSocketPayload(req.Payload, &body); err != nil {
_ = c.writeError(req.ID, "invalid fs.read_editable_text payload")
return
}

workdir := strings.TrimSpace(body.Workdir)
path := strings.TrimSpace(body.Path)
if workdir == "" {
_ = c.writeError(req.ID, "workdir is required")
return
}
if path == "" {
_ = c.writeError(req.ID, "path is required")
return
}

response, err := c.awaitAgentResponse(req.ID, &gatewayv1.GatewayEnvelope{
RequestId: req.ID,
Timestamp: time.Now().Unix(),
Payload: &gatewayv1.GatewayEnvelope_FsReadEditableText{
FsReadEditableText: &gatewayv1.FsReadEditableTextRequest{
Workdir: workdir,
Path: path,
},
},
})
if err != nil {
_ = c.writeError(req.ID, websocketErrorMessage(err))
return
}
if errResp := response.GetError(); errResp != nil {
_ = c.writeError(req.ID, errResp.GetMessage())
return
}

resp := response.GetFsReadEditableTextResp()
if resp == nil {
_ = c.writeError(req.ID, "unexpected agent response")
return
}

_ = c.writeResponse(req.ID, websocketFsReadEditableTextResponsePayload(resp))
}

func (c *websocketConnection) handleFsWriteText(req websocketRequest) {
type payload struct {
Workdir string `json:"workdir"`
Expand Down Expand Up @@ -499,6 +550,17 @@ func websocketFsListResponsePayload(resp *gatewayv1.FsListResponse) map[string]a
}
}

func websocketFsReadEditableTextResponsePayload(resp *gatewayv1.FsReadEditableTextResponse) map[string]any {
return map[string]any{
"path": resp.GetPath(),
"content": resp.GetContent(),
"mtimeMs": resp.GetMtimeMs(),
"contentHash": resp.GetContentHash(),
"sizeBytes": resp.GetSizeBytes(),
"totalLines": resp.GetTotalLines(),
}
}

func websocketFsWriteTextResponsePayload(resp *gatewayv1.FsWriteTextResponse) map[string]any {
return map[string]any{
"path": resp.GetPath(),
Expand Down
15 changes: 15 additions & 0 deletions crates/agent-gateway/internal/server/websocket_payload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,21 @@ func TestWebsocketFsPayloadsUseFrontendFieldNames(t *testing.T) {
t.Fatalf("fs.list first entry = %#v", entries[0])
}

readEditable := websocketFsReadEditableTextResponsePayload(&gatewayv1.FsReadEditableTextResponse{
Path: "src/main.ts",
Content: "export {};\n",
MtimeMs: 42,
ContentHash: "hash",
SizeBytes: 11,
TotalLines: 1,
})
if readEditable["content"] != "export {};\n" {
t.Fatalf("fs.read_editable_text content = %#v", readEditable["content"])
}
if readEditable["sizeBytes"] != uint64(11) {
t.Fatalf("fs.read_editable_text sizeBytes = %#v, want 11", readEditable["sizeBytes"])
}

write := websocketFsWriteTextResponsePayload(&gatewayv1.FsWriteTextResponse{
Path: "src/new.ts",
Mode: "rewrite",
Expand Down
1 change: 1 addition & 0 deletions crates/agent-gateway/internal/server/websocket_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var websocketRequestHandlers = map[string]websocketRequestHandler{
"fs.list_dirs": (*websocketConnection).handleFsListDirs,
"fs.create_project_folder": (*websocketConnection).handleFsCreateProjectFolder,
"fs.list": (*websocketConnection).handleFsList,
"fs.read_editable_text": (*websocketConnection).handleFsReadEditableText,
"fs.write_text": (*websocketConnection).handleFsWriteText,
"fs.create_dir": (*websocketConnection).handleFsCreateDir,
"fs.rename": (*websocketConnection).handleFsRename,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ func TestWebsocketRequestHandlersCoverKnownProtocolTypes(t *testing.T) {
"fs.list_dirs",
"fs.create_project_folder",
"fs.list",
"fs.read_editable_text",
"fs.write_text",
"fs.create_dir",
"fs.rename",
Expand Down
16 changes: 16 additions & 0 deletions crates/agent-gateway/proto/v1/gateway.proto
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ message GatewayEnvelope {
FsRenameRequest fs_rename = 59;
FsDeleteRequest fs_delete = 60;
GitRequest git_request = 61;
FsReadEditableTextRequest fs_read_editable_text = 62;
}
}

Expand Down Expand Up @@ -106,6 +107,7 @@ message AgentEnvelope {
FsRenameResponse fs_rename_resp = 62;
FsDeleteResponse fs_delete_resp = 63;
GitResponse git_response = 64;
FsReadEditableTextResponse fs_read_editable_text_resp = 65;
ErrorResponse error = 99;
}
}
Expand Down Expand Up @@ -549,6 +551,20 @@ message FsListResponse {
repeated FsListEntry entries = 8;
}

message FsReadEditableTextRequest {
string workdir = 1;
string path = 2;
}

message FsReadEditableTextResponse {
string path = 1;
string content = 2;
uint64 mtime_ms = 3;
string content_hash = 4;
uint64 size_bytes = 5;
uint64 total_lines = 6;
}

message FsWriteTextRequest {
string workdir = 1;
string path = 2;
Expand Down
16 changes: 16 additions & 0 deletions crates/agent-gateway/test/webui/web-remote-input.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createWebModuleLoader } from "../helpers/load-web-module.mjs";

const loader = createWebModuleLoader();
const remoteInput = loader.loadModule("src/pages/settings/remoteInput.ts");

test("web remote integer drafts stay editable while preserving valid values", () => {
assert.equal(remoteInput.normalizeIntegerDraftInput(":50051"), "50051");
assert.equal(remoteInput.normalizeIntegerDraftInput(" 12abc34 "), "1234");

assert.equal(remoteInput.parseIntegerDraftValue("", { min: 1, max: 65_535 }), null);
assert.equal(remoteInput.parseIntegerDraftValue("0", { min: 1, max: 65_535 }), null);
assert.equal(remoteInput.parseIntegerDraftValue("443", { min: 1, max: 65_535 }), 443);
assert.equal(remoteInput.parseIntegerDraftValue("65536", { min: 1, max: 65_535 }), 65_535);
});
5 changes: 5 additions & 0 deletions crates/agent-gateway/test/webui/web-settings.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,11 @@ test("web remote settings normalize single-slash http gateway URLs", () => {
assert.equal(remote.gatewayUrl, "https://gateway.example");
assert.equal(remote.grpcEndpoint, "https://grpc.example");
assert.equal(remote.token, "token");

const remoteWithOversizedPort = settings.normalizeRemoteSettings({
grpcPort: "70000",
});
assert.equal(remoteWithOversizedPort.grpcPort, 65_535);
});

test("web cron task normalization preserves finite and exhausted run counts", () => {
Expand Down
1 change: 1 addition & 0 deletions crates/agent-gateway/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"katex": "^0.16.45",
"monaco-editor": "^0.55.1",
"react": "^19.2.4",
"react-complex-tree": "^2.6.1",
"react-dom": "^19.2.4",
Expand Down
25 changes: 25 additions & 0 deletions crates/agent-gateway/web/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading