// A protobuf-based API for communication between // the Warp client and the remote server binary. // // Messages are length-prefixed: [4-byte little-endian length][protobuf bytes]. syntax = "proto3"; package remote_server; import "diff_state.proto"; // ── Top-level envelopes ─────────────────────────────────────────── // Top-level envelope for all client → server messages. message ClientMessage { string request_id = 1; oneof message { HostScopedRequest host_scoped = 2; SessionScopedRequest session_scoped = 3; Notification notification = 4; } } // Request that targets the host, not a specific connection. // The daemon may deliver the response on any open connection // if the originating connection disconnects before the response // is ready. message HostScopedRequest { // 4 (open_buffer) and 7 (get_diff_state) moved to SessionScopedRequest: // they establish per-connection subscription state, so they must be // delivered on (and answered by) a specific connection, never failed over. reserved 4, 7; oneof message { WriteFile write_file = 1; DeleteFile delete_file = 2; ReadFileContextRequest read_file_context = 3; SaveBuffer save_buffer = 5; ResolveConflict resolve_conflict = 6; DiscardFilesRequest discard_files = 8; IndexCodebase index_codebase = 9; DropCodebaseIndex drop_codebase_index = 10; GetFragmentMetadataFromHash get_fragment_metadata_from_hash = 11; GetBranches get_branches = 12; ResyncCodebase resync_codebase = 13; UploadHandoffSnapshot upload_handoff_snapshot = 14; GitCommitChainRequest git_commit_chain = 15; GitPushRequest git_push = 16; GitCreatePrRequest git_create_pr = 17; GitGenerateCommitMessageRequest git_generate_commit_message = 18; GitGetCommittedBranchFilesRequest git_get_committed_branch_files = 19; RipgrepSearchRequest ripgrep_search = 21; } } // Request that targets a specific connection/session. // The response is always delivered on the originating connection. message SessionScopedRequest { oneof message { Initialize initialize = 1; NavigatedToDirectory navigated_to_directory = 2; LoadRepoMetadataDirectory load_repo_metadata_directory = 3; RunCommandRequest run_command = 4; // Subscription-establishing requests: the daemon registers per-connection // state (buffer / diff-state subscriptions) keyed to the originating // connection, so the response must come back on that same connection. OpenBuffer open_buffer = 5; GetDiffState get_diff_state = 6; } } // Fire-and-forget message. No response is expected. // The daemon processes these inline and does not track them. message Notification { oneof message { Abort abort = 1; Authenticate authenticate = 2; UpdatePreferences update_preferences = 3; SessionBootstrapped session_bootstrapped = 4; BufferEdit buffer_edit = 5; CloseBuffer close_buffer = 6; UnsubscribeDiffState unsubscribe_diff_state = 7; UpdateGitStatus update_git_status = 8; UpdateGitHubPrInfo update_github_pr_info = 9; UpdateGitHubRepoInfo update_github_repo_info = 10; } } // Top-level envelope for all server → client messages. // Push messages use an empty request_id to distinguish them from // request/response pairs. message ServerMessage { string request_id = 1; oneof message { InitializeResponse initialize_response = 2; ErrorResponse error = 3; NavigatedToDirectoryResponse navigated_to_directory_response = 4; RepoMetadataSnapshot repo_metadata_snapshot = 5; RepoMetadataUpdatePush repo_metadata_update = 6; LoadRepoMetadataDirectoryResponse load_repo_metadata_directory_response = 7; WriteFileResponse write_file_response = 8; DeleteFileResponse delete_file_response = 9; RunCommandResponse run_command_response = 10; ReadFileContextResponse read_file_context_response = 11; CodebaseIndexStatusesSnapshot codebase_index_statuses_snapshot = 12; CodebaseIndexStatusUpdated codebase_index_status_updated = 13; OpenBufferResponse open_buffer_response = 14; BufferUpdatedPush buffer_updated = 15; SaveBufferResponse save_buffer_response = 16; ResolveConflictResponse resolve_conflict_response = 17; GetDiffStateResponse get_diff_state_response = 18; DiffStateSnapshot diff_state_snapshot = 19; DiffStateMetadataUpdate diff_state_metadata_update = 20; DiffStateFileDelta diff_state_file_delta = 21; DiscardFilesResponse discard_files_response = 22; BufferConflictDetected buffer_conflict_detected = 23; GetFragmentMetadataFromHashResponse get_fragment_metadata_from_hash_response = 24; GetBranchesResponse get_branches_response = 25; UploadHandoffSnapshotResponse upload_handoff_snapshot_response = 26; GitCommitChainResponse git_commit_chain_response = 27; GitPushResponse git_push_response = 28; GitCreatePrResponse git_create_pr_response = 29; GitGenerateCommitMessageResponse git_generate_commit_message_response = 31; GitGetCommittedBranchFilesResponse git_get_committed_branch_files_response = 32; GitStatusPush git_status_push = 34; GitHubPrInfoPush github_pr_info_push = 35; GitHubRepositoryInfoPush github_repository_info_push = 36; RipgrepSearchResponse ripgrep_search_response = 37; RemoteAgentContextSnapshot remote_agent_context_snapshot = 33; } } // ── Initialize handshake // Sent by the client immediately after connecting to negotiate the protocol. message Initialize { // Optional bearer token used by the daemon for Warp-server requests. // Empty means no credential was available and does not clear an existing // daemon credential. string auth_token = 1; // User identity for Sentry crash reports. Empty when not logged in. string user_id = 2; string user_email = 3; // Whether the user has enabled crash reporting in their privacy settings. bool crash_reporting_enabled = 4; // Client-resolved codebase index limits for parity with local indexing. optional CodebaseIndexLimits codebase_index_limits = 5; } // Sent by the client when its bearer credential rotates after initialization. // This is a notification (fire-and-forget) — the server does not send a response. message Authenticate { // Optional bearer token used by the daemon for Warp-server requests. // Empty means no credential was available and does not clear an existing // daemon credential. string auth_token = 1; } // Sent by the client when the user's privacy preferences change. // This is a notification (fire-and-forget) — the server does not send a response. message UpdatePreferences { bool crash_reporting_enabled = 1; optional CodebaseIndexLimits codebase_index_limits = 2; } message CodebaseIndexLimits { // Empty means unlimited. optional uint64 max_indices_allowed = 1; uint64 max_files_per_repo = 2; uint64 embedding_generation_batch_size = 3; } // Sent by the client to cancel an in-progress request. // This is a notification (fire-and-forget) — the server does not send a response. message Abort { string request_id_to_abort = 1; } // Returned by the server in response to Initialize. message InitializeResponse { string server_version = 1; string host_id = 2; } // Sent by the client after the SSH session has been bootstrapped. // This is a notification (fire-and-forget) — the server does not send a // response. The server uses this to create a per-session // LocalCommandExecutor matching the bootstrapped shell. message SessionBootstrapped { uint64 session_id = 1; string shell_type = 2; // The full path to the shell binary (e.g. "/usr/bin/zsh"). // When present, the server uses this directly instead of doing a PATH lookup. optional string shell_path = 3; } // Sent by the client to execute a shell command on the remote host. message RunCommandRequest { string command = 1; // Working directory for the command. If empty, uses the server's default. optional string working_directory = 2; // Environment variables to set for the command. map environment_variables = 3; // The session whose shell executor should run this command. uint64 session_id = 4; } // Specific error codes for RunCommandRequest failures. enum RunCommandErrorCode { RUN_COMMAND_ERROR_CODE_UNSPECIFIED = 0; // The session ID has no associated executor (session was never bootstrapped). SESSION_NOT_FOUND = 1; // The command could not be executed (e.g. the shell process failed to spawn). EXECUTION_FAILED = 2; } message RunCommandError { RunCommandErrorCode code = 1; string message = 2; } message RunCommandSuccess { bytes stdout = 1; bytes stderr = 2; // Absent when the process was killed by a signal (Unix). optional int32 exit_code = 3; } // Returned by the server with the result of a RunCommandRequest. message RunCommandResponse { oneof result { RunCommandSuccess success = 1; RunCommandError error = 2; } } // ── Shared error response ───────────────────────────────────────── enum ErrorCode { ERROR_CODE_UNSPECIFIED = 0; // The request was malformed (e.g. missing oneof variant). INVALID_REQUEST = 1; // An unexpected server-side failure. INTERNAL = 2; } message ErrorResponse { ErrorCode code = 1; string message = 2; } // ── Remote Agent Mode context ───────────────────────────────────── // Metadata specific to a skill bundled with Warp. message BundledSkillMetadata { // Directory-based skill ID, unique within the bundled catalog. string id = 1; // When set, the skill is only active while the named MCP integration // is running on the client (e.g. "figma"). Evaluated client-side. optional string requires_mcp = 2; } // Marker metadata for a file-based skill from the daemon host's home directory. message HomeSkillMetadata {} // One already-read remote skill published by the daemon. message RemoteSkillProto { // Absolute path to the file on this host. string path = 1; string content = 2; oneof source { BundledSkillMetadata bundled = 3; HomeSkillMetadata home = 4; } } // One already-read remote context file published by the daemon. message RemoteContextFileProto { // Absolute path to the file on this host. string path = 1; string content = 2; } // Revisioned full replacement of the daemon host's Agent Mode context. Source // discovery and reading happen daemon-side before this snapshot is published. message RemoteAgentContextSnapshot { uint64 revision = 1; string home_dir = 2; repeated RemoteSkillProto skills = 3; repeated RemoteContextFileProto global_rules = 4; } // ── Shared repo metadata sub-messages ───────────────────────────── // Mirror the Rust types in repo_metadata/src/file_tree_update.rs. // Mirrors RepoNodeMetadata in Rust. message RepoNodeMetadata { oneof node { DirectoryNodeMetadata directory = 1; FileNodeMetadata file = 2; } } // Mirrors DirectoryNodeMetadata in Rust. message DirectoryNodeMetadata { string path = 1; bool ignored = 2; bool loaded = 3; } // Mirrors FileNodeMetadata in Rust. message FileNodeMetadata { string path = 1; optional string extension = 2; bool ignored = 3; } // Mirrors FileTreeEntryUpdate in Rust. Describes a subtree patch rooted // at a parent directory. message RepoMetadataEntryUpdate { string parent_path_to_replace = 1; repeated RepoNodeMetadata subtree_metadata = 2; } message StandingQueryContent { string path = 1; bool is_directory = 2; } message StandingQueryResultsDelta { repeated StandingQueryContent upserted_project_skills = 1; repeated StandingQueryContent removed_project_skills = 2; repeated StandingQueryContent upserted_project_rules = 3; repeated StandingQueryContent removed_project_rules = 4; } // ── NavigatedToDirectory ────────────────────────────────────────── // Client → server: "I navigated to this directory, please index it." message NavigatedToDirectory { string path = 1; } // Response after the server has run git detection on the requested path. // When is_git is true, indexed_path is the git repo root and full indexing // runs in the background. A RepoMetadataSnapshot push will follow. // When is_git is false, the directory was lazily indexed at first level. // A RepoMetadataSnapshot push with the lazy tree data will follow. message NavigatedToDirectoryResponse { string indexed_path = 1; bool is_git = 2; } // ── LoadRepoMetadataDirectory ───────────────────────────────────── // Client → server: load the next level of a subdirectory within an // already-tracked repo (lazy expand). message LoadRepoMetadataDirectory { string repo_path = 1; string dir_path = 2; } // Response with the loaded subtree entries for the requested directory. message LoadRepoMetadataDirectoryResponse { string repo_path = 1; string dir_path = 2; repeated RepoMetadataEntryUpdate entries = 3; } // ── File write/delete operations ────────────────────────────────── // Shared error type for file operations (read/write/delete). message FileOperationError { string message = 1; } // Client → server: write content to a file, creating parent dirs if needed. message WriteFile { string path = 1; string content = 2; } // Server → client: result of a WriteFile request. message WriteFileResponse { oneof result { WriteFileSuccess success = 1; FileOperationError error = 2; } } message WriteFileSuccess {} // Client → server: delete a file. message DeleteFile { string path = 1; } // Server → client: result of a DeleteFile request. message DeleteFileResponse { oneof result { DeleteFileSuccess success = 1; FileOperationError error = 2; } } message DeleteFileSuccess {} // ── Read file context (batch) ───────────────────────────────────── // A single file to read, with optional line ranges. message ReadFileContextFile { string path = 1; // 1-indexed line ranges (start..end). Empty = read entire file. repeated LineRange line_ranges = 2; } message LineRange { uint32 start = 1; uint32 end = 2; } // Client → server: batch read multiple files with full context. message ReadFileContextRequest { repeated ReadFileContextFile files = 1; // Per-file byte limit. Absent = use server default. optional uint32 max_file_bytes = 2; // Cumulative byte budget across all files. Absent = no batch limit. optional uint32 max_batch_bytes = 3; } // Server → client: result of a ReadFileContextRequest. // Per-file failures are reported in `failed_files`, not as a top-level error. // Catastrophic server errors (malformed request, etc.) use the generic ErrorResponse. message ReadFileContextResponse { repeated FileContextProto file_contexts = 1; repeated FailedFileRead failed_files = 2; } message FailedFileRead { string path = 1; FileOperationError error = 2; } message FileContextProto { string file_name = 1; oneof content { string text_content = 2; bytes binary_content = 3; } // Optional 1-indexed line range this segment covers. optional uint32 line_range_start = 4; optional uint32 line_range_end = 5; optional uint64 last_modified_epoch_millis = 6; uint32 line_count = 7; } // ── Ripgrep search (global search) ──────────────────────────────── // Client → server: run a ripgrep search over the given root directories // on the host. Used by global search for remote sessions. message RipgrepSearchRequest { // Regex pattern. Already escaped client-side for literal (non-regex) // searches, matching local global search behavior. string pattern = 1; // Absolute directories on the host to search. repeated string roots = 2; bool ignore_case = 3; bool multiline = 4; // Maximum number of matched lines to return. The server also applies its // own match and approximate payload caps. uint32 max_matches = 5; } // Server → client: result of a RipgrepSearchRequest. message RipgrepSearchResponse { oneof result { RipgrepSearchSuccess success = 1; RipgrepSearchError error = 2; } } message RipgrepSearchSuccess { repeated RipgrepSearchMatch matches = 1; // True when the search stopped early because the match cap was reached. bool capped = 2; } message RipgrepSearchError { string message = 1; } // A single matched line in a file. A line may contain multiple submatches; // the client expands them into per-submatch result rows. message RipgrepSearchMatch { string file_path = 1; uint32 line_number = 2; string line_text = 3; repeated RipgrepSearchSubmatch submatches = 4; } // Byte offsets into `line_text` for one submatch (end exclusive). message RipgrepSearchSubmatch { uint64 byte_start = 1; uint64 byte_end = 2; } // ── Remote codebase indexing status ─────────────────────────────── message IndexCodebase { string repo_path = 1; string auth_token = 2; } message ResyncCodebase { string repo_path = 1; string auth_token = 2; CodebaseResyncMode mode = 3; } enum CodebaseResyncMode { CODEBASE_RESYNC_MODE_FULL = 0; CODEBASE_RESYNC_MODE_INCREMENTAL = 1; } message DropCodebaseIndex { string repo_path = 1; string auth_token = 2; } message GetFragmentMetadataFromHash { string repo_path = 1; string root_hash = 2; repeated string content_hashes = 3; } message FragmentMetadata { string content_hash = 1; string path = 2; uint32 start_line = 3; uint32 end_line = 4; uint64 byte_start = 5; uint64 byte_end = 6; } message MissingFragmentMetadata { string content_hash = 1; FileOperationError error = 2; } enum FragmentMetadataLookupErrorCode { FRAGMENT_METADATA_LOOKUP_ERROR_CODE_UNSPECIFIED = 0; REMOTE_CODEBASE_INDEXING_NOT_ENABLED = 1; INVALID_REPO_PATH = 2; INVALID_ROOT_HASH = 3; INDEX_NOT_FOUND = 4; INDEX_NOT_SYNCED = 5; ROOT_HASH_MISMATCH = 6; } message FragmentMetadataLookupError { FragmentMetadataLookupErrorCode code = 1; string message = 2; optional string current_root_hash = 3; } message GetFragmentMetadataFromHashSuccess { repeated FragmentMetadata fragments = 1; repeated MissingFragmentMetadata missing_hashes = 2; } message GetFragmentMetadataFromHashResponse { oneof result { GetFragmentMetadataFromHashSuccess success = 1; FragmentMetadataLookupError error = 2; } } enum CodebaseIndexStatusState { CODEBASE_INDEX_STATUS_STATE_UNSPECIFIED = 0; CODEBASE_INDEX_STATUS_STATE_NOT_ENABLED = 1; CODEBASE_INDEX_STATUS_STATE_UNAVAILABLE = 2; CODEBASE_INDEX_STATUS_STATE_DISABLED = 3; CODEBASE_INDEX_STATUS_STATE_QUEUED = 4; CODEBASE_INDEX_STATUS_STATE_INDEXING = 5; CODEBASE_INDEX_STATUS_STATE_READY = 6; CODEBASE_INDEX_STATUS_STATE_STALE = 7; CODEBASE_INDEX_STATUS_STATE_FAILED = 8; } message CodebaseIndexStatus { string repo_path = 1; CodebaseIndexStatusState state = 2; optional uint64 last_updated_epoch_millis = 3; optional uint64 progress_completed = 4; optional uint64 progress_total = 5; optional string failure_message = 6; optional string root_hash = 7; } message CodebaseIndexStatusesSnapshot { repeated CodebaseIndexStatus statuses = 1; } message CodebaseIndexStatusUpdated { CodebaseIndexStatus status = 1; } // ── Server → client push messages ───────────────────────────────── // Full or lazy-loaded repo metadata snapshot. Pushed by the server after // NavigatedToDirectory completes indexing (either lazy or full git). message RepoMetadataSnapshot { string repo_path = 1; repeated RepoMetadataEntryUpdate entries = 2; bool sync_complete = 3; StandingQueryResultsDelta standing_results = 4; } // Incremental repo metadata update. Mirrors RepoMetadataUpdate in Rust. message RepoMetadataUpdatePush { string repo_path = 1; repeated string remove_entries = 2; repeated RepoMetadataEntryUpdate update_entries = 3; StandingQueryResultsDelta standing_results_delta = 4; } // ── Buffer syncing ──────────────────────────────────────────────── // Client → server: open a buffer for bidirectional syncing. // The server reads the file, starts watching it, and returns the content. message OpenBuffer { string path = 1; // When true, the server discards any in-memory buffer state and re-reads // the file from disk. Used by the client to resolve conflicts ("accept // server" / discard local edits). Other connections that have the buffer // open receive a BufferUpdatedPush with the fresh content. bool force_reload = 2; } // Server → client: response to OpenBuffer with the initial file content. message OpenBufferResponse { oneof result { OpenBufferSuccess success = 1; FileOperationError error = 2; } } message OpenBufferSuccess { string content = 1; uint64 server_version = 2; } // Client → server: push an incremental edit (fire-and-forget notification). // If the server accepts (expected_server_version matches its local version), // it applies the edit silently — no response needed since the client already // has the edit applied locally. // If the server rejects (version mismatch), it pushes a BufferUpdatedPush // with its current state. message BufferEdit { string path = 1; uint64 expected_server_version = 2; uint64 new_client_version = 3; repeated TextEdit edits = 4; } // A single text edit within a buffer, using 1-indexed character offsets (matching CharOffset). message TextEdit { uint64 start_offset = 1; uint64 end_offset = 2; string text = 3; } // Server → client push: file changed on disk. // The client uses expected_client_version to detect conflicts. // Carries only incremental edits — full content is served by OpenBufferResponse. message BufferUpdatedPush { string path = 1; uint64 new_server_version = 2; uint64 expected_client_version = 3; repeated TextEdit edits = 4; } // Client → server: request the daemon gather a handoff snapshot and upload it. // All paths must be absolute paths on the remote host's filesystem. message UploadHandoffSnapshot { // Absolute paths the agent touched (file edits, working directories). // Non-absolute or empty entries are rejected by the daemon. repeated string paths = 1; } // Daemon → client: result of the handoff snapshot upload. message UploadHandoffSnapshotResponse { optional string initial_snapshot_token = 1; bool success = 2; optional string error = 3; } // Client → server: close a buffer (stop watching). message CloseBuffer { string path = 1; } // Client → server: persist the current in-memory buffer to disk. message SaveBuffer { string path = 1; } // Server → client: result of a SaveBuffer request. message SaveBufferResponse { oneof result { SaveBufferSuccess success = 1; FileOperationError error = 2; } } message SaveBufferSuccess {} // Client → server: resolve a conflict by accepting the client's content. // The server replaces its in-memory buffer with client_content, // updates the clock to {acknowledged_server_version, current_client_version}, // and persists to disk (updates base_content_version to avoid re-triggering). // For "accept server", the client simply re-sends OpenBuffer instead. message ResolveConflict { string path = 1; uint64 acknowledged_server_version = 2; string client_content = 3; uint64 current_client_version = 4; } // Server → client: result of a ResolveConflict request. message ResolveConflictResponse { oneof result { ResolveConflictSuccess success = 1; FileOperationError error = 2; } } message ResolveConflictSuccess {} // Server → client push: the file changed on disk while the client had // unsaved edits. The server does NOT apply the disk change to its buffer; // the client should show a conflict resolution banner. message BufferConflictDetected { string path = 1; }