WebSocket protocol
A single JSON protocol powers file tree, editor, terminal, git, and chat. The UI never assumes it runs on the same machine as the server.
Message framing
Three message kinds, each one JSON object per text frame:
Requests (client → server)
{ "id": 1, "command": "read_file", "args": { "path": "/home/user/a.txt" } }Replies (server → client)
{ "id": 1, "ok": true, "result": { ... } }
{ "id": 1, "ok": false, "error": "File already exists" }
Commands run concurrently and replies may arrive out of order —
match them by
id. A malformed frame gets
error: "invalid request: …"; an unknown command gets
error: "unknown command: …".
Events (server → client)
| Event | Payload | Notes |
|---|---|---|
pty-output | session_id, data | Raw terminal bytes, ANSI escapes intact. |
pty-exit | session_id, code | code is null when unknown. |
chat-stream-chunk | id, stream, text | One line per chunk from the agent CLI. |
chat-stream-done | id, error? | Terminates a chat stream. |
Command reference
Terminal (PTY)
| Command | Args | Result |
|---|---|---|
pty_spawn | sessionId, shell?, cwd?, cols?, rows? | { id, pid } |
pty_write | sessionId, data | → null |
pty_resize | sessionId, cols, rows | → null |
pty_kill | sessionId | → null |
Shell / misc
| Command | Args | Result |
|---|---|---|
run_command | command | combined stdout+stderr string |
get_cwd | — | working directory string |
chat_check_install | command (agent id) | boolean |
File system
| Command | Args | Result |
|---|---|---|
read_file | path | file text |
read_file_base64 | path | base64 string (images etc.) |
write_file | path, content | → null |
upload_file | path, content (base64) | → null |
download_file | path | base64 string |
read_directory | path, maxDepth? | FileNode[] |
list_files | path | string[] recursive relative paths |
file_exists | path | boolean |
create_file | path, content? | → null |
create_directory | path | → null |
rename_entry | from, to | → null |
copy_entry | from, to | → null |
delete_entry | path | → null |
extract_zip | zipContent (base64), destDir | string[] extracted paths |
search_in_files | path, query, caseSensitive?, wholeWord?, regex? | SearchMatch[] |
Git
All git commands take path (the repo dir) plus command-specific args.
| Command | Extra args | Result |
|---|---|---|
get_git_status | — | GitStatus |
get_git_diff | file | unified diff string |
get_git_staged_diff | file | unified diff string |
get_git_head_file | file | file text at HEAD |
git_show_index | file | file text from the index |
git_add | file | output string |
git_stage_all | — | output string |
git_unstage | file | output string |
git_unstage_all | — | output string |
git_discard | file | output string |
git_discard_all | — | output string |
git_commit | message, amend? | output string |
git_undo_commit | — | output string |
git_log | limit? | GitCommitInfo[] |
git_commit_diff | commit | unified diff string |
git_stash_list | — | GitStashInfo[] |
git_stash | — | output string |
git_stash_pop | — | output string |
git_push | — | output string |
git_fetch | — | output string |
git_pull | — | output string |
git_revert | commit | output string |
git_init | — | output string |
git_branch_list | — | GitBranches |
git_checkout_branch | name, create? | output string |
git_merge | fromRef | output string |
git_merge_abort | — | output string |
git_resolve_conflict | file, side | output string |
Agent chat (kilo / opencode)
| Command | Args | Result |
|---|---|---|
chat_stream | command, args?, cwd?, apiKey?, mode?, attachments?, requestId? | fires chunk/done events; → null |
chat_cancel | id | { cancelled, reason? } |
chat_models | agent, apiKey? | { id, label }[] |
chat_stream runs a thin agent client against a long-lived
serve process the server manages per agent. The frontend
subscribes to events before invoking so no early chunk is
missed, matches chunks by requestId, and reconciles with
chat-stream-done.