SPEC-002 Phase 1 Task 5, code-reviewed APPROVED. An attended (support-code) session is invisible and inert to the technician until the end user accepts a consent prompt on their own machine. - proto: ConsentRequest / ConsentResponse + ConsentAccessMode enum (oneof fields 80/81; no existing field renumbered). - server: ConsentState on Session; attended -> Pending, managed -> NotRequired; join_session refuses viewers unless Granted/NotRequired (single chokepoint - StartStream only fires from join_session, so no frames or input flow pre- consent); run_consent_handshake sends ConsentRequest, 60s timeout, granted -> proceed, denied/timeout/disconnect -> teardown (end_session denied, machine offline, support code released). consent_state persisted; consent_requested/ granted/denied audited. - agent: Windows MessageBox (topmost/system-modal) on spawn_blocking; anything but an explicit Yes = deny; non-Windows build is a fail-closed stub. Not cargo-check-verified locally (no toolchain). Server verified on the build host; the Windows agent half is verified by CI build-agent (Pluto). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
417 lines
12 KiB
Protocol Buffer
417 lines
12 KiB
Protocol Buffer
syntax = "proto3";
|
|
package guruconnect;
|
|
|
|
// ============================================================================
|
|
// Session Management
|
|
// ============================================================================
|
|
|
|
message SessionRequest {
|
|
string agent_id = 1;
|
|
string session_token = 2;
|
|
SessionType session_type = 3;
|
|
string client_version = 4;
|
|
}
|
|
|
|
message SessionResponse {
|
|
bool success = 1;
|
|
string session_id = 2;
|
|
string error = 3;
|
|
DisplayInfo display_info = 4;
|
|
}
|
|
|
|
enum SessionType {
|
|
SCREEN_CONTROL = 0;
|
|
VIEW_ONLY = 1;
|
|
BACKSTAGE = 2;
|
|
FILE_TRANSFER = 3;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Display Information
|
|
// ============================================================================
|
|
|
|
message DisplayInfo {
|
|
repeated Display displays = 1;
|
|
int32 primary_display = 2;
|
|
}
|
|
|
|
message Display {
|
|
int32 id = 1;
|
|
string name = 2;
|
|
int32 x = 3;
|
|
int32 y = 4;
|
|
int32 width = 5;
|
|
int32 height = 6;
|
|
bool is_primary = 7;
|
|
}
|
|
|
|
message SwitchDisplay {
|
|
int32 display_id = 1;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Video Frames
|
|
// ============================================================================
|
|
|
|
message VideoFrame {
|
|
int64 timestamp = 1;
|
|
int32 display_id = 2;
|
|
int32 sequence = 3;
|
|
|
|
oneof encoding {
|
|
RawFrame raw = 10;
|
|
EncodedFrame vp9 = 11;
|
|
EncodedFrame h264 = 12;
|
|
EncodedFrame h265 = 13;
|
|
}
|
|
}
|
|
|
|
message RawFrame {
|
|
int32 width = 1;
|
|
int32 height = 2;
|
|
bytes data = 3; // Zstd compressed BGRA
|
|
bool compressed = 4;
|
|
repeated DirtyRect dirty_rects = 5;
|
|
bool is_keyframe = 6; // Full frame vs incremental
|
|
}
|
|
|
|
message DirtyRect {
|
|
int32 x = 1;
|
|
int32 y = 2;
|
|
int32 width = 3;
|
|
int32 height = 4;
|
|
}
|
|
|
|
message EncodedFrame {
|
|
bytes data = 1;
|
|
bool keyframe = 2;
|
|
int64 pts = 3;
|
|
int64 dts = 4;
|
|
}
|
|
|
|
message VideoAck {
|
|
int32 sequence = 1;
|
|
int64 timestamp = 2;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Cursor
|
|
// ============================================================================
|
|
|
|
message CursorShape {
|
|
uint64 id = 1;
|
|
int32 hotspot_x = 2;
|
|
int32 hotspot_y = 3;
|
|
int32 width = 4;
|
|
int32 height = 5;
|
|
bytes data = 6; // BGRA bitmap
|
|
}
|
|
|
|
message CursorPosition {
|
|
int32 x = 1;
|
|
int32 y = 2;
|
|
bool visible = 3;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Input Events
|
|
// ============================================================================
|
|
|
|
message MouseEvent {
|
|
int32 x = 1;
|
|
int32 y = 2;
|
|
MouseButtons buttons = 3;
|
|
int32 wheel_delta_x = 4;
|
|
int32 wheel_delta_y = 5;
|
|
MouseEventType event_type = 6;
|
|
}
|
|
|
|
enum MouseEventType {
|
|
MOUSE_MOVE = 0;
|
|
MOUSE_DOWN = 1;
|
|
MOUSE_UP = 2;
|
|
MOUSE_WHEEL = 3;
|
|
}
|
|
|
|
message MouseButtons {
|
|
bool left = 1;
|
|
bool right = 2;
|
|
bool middle = 3;
|
|
bool x1 = 4;
|
|
bool x2 = 5;
|
|
}
|
|
|
|
message KeyEvent {
|
|
bool down = 1; // true = key down, false = key up
|
|
KeyEventType key_type = 2;
|
|
uint32 vk_code = 3; // Virtual key code (Windows VK_*)
|
|
uint32 scan_code = 4; // Hardware scan code
|
|
string unicode = 5; // Unicode character (for text input)
|
|
Modifiers modifiers = 6;
|
|
}
|
|
|
|
enum KeyEventType {
|
|
KEY_VK = 0; // Virtual key code
|
|
KEY_SCAN = 1; // Scan code
|
|
KEY_UNICODE = 2; // Unicode character
|
|
}
|
|
|
|
message Modifiers {
|
|
bool ctrl = 1;
|
|
bool alt = 2;
|
|
bool shift = 3;
|
|
bool meta = 4; // Windows key
|
|
bool caps_lock = 5;
|
|
bool num_lock = 6;
|
|
}
|
|
|
|
message SpecialKeyEvent {
|
|
SpecialKey key = 1;
|
|
}
|
|
|
|
enum SpecialKey {
|
|
CTRL_ALT_DEL = 0;
|
|
LOCK_SCREEN = 1;
|
|
PRINT_SCREEN = 2;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Clipboard
|
|
// ============================================================================
|
|
|
|
message ClipboardData {
|
|
ClipboardFormat format = 1;
|
|
bytes data = 2;
|
|
string mime_type = 3;
|
|
}
|
|
|
|
enum ClipboardFormat {
|
|
CLIPBOARD_TEXT = 0;
|
|
CLIPBOARD_HTML = 1;
|
|
CLIPBOARD_RTF = 2;
|
|
CLIPBOARD_IMAGE = 3;
|
|
CLIPBOARD_FILES = 4;
|
|
}
|
|
|
|
message ClipboardRequest {
|
|
// Request current clipboard content
|
|
}
|
|
|
|
// ============================================================================
|
|
// Quality Control
|
|
// ============================================================================
|
|
|
|
message QualitySettings {
|
|
QualityPreset preset = 1;
|
|
int32 custom_fps = 2; // 1-60
|
|
int32 custom_bitrate = 3; // kbps
|
|
CodecPreference codec = 4;
|
|
}
|
|
|
|
enum QualityPreset {
|
|
QUALITY_AUTO = 0;
|
|
QUALITY_LOW = 1; // Low bandwidth
|
|
QUALITY_BALANCED = 2;
|
|
QUALITY_HIGH = 3; // Best quality
|
|
}
|
|
|
|
enum CodecPreference {
|
|
CODEC_AUTO = 0;
|
|
CODEC_RAW = 1; // Raw + Zstd (LAN)
|
|
CODEC_VP9 = 2;
|
|
CODEC_H264 = 3;
|
|
CODEC_H265 = 4;
|
|
}
|
|
|
|
message LatencyReport {
|
|
int64 rtt_ms = 1;
|
|
int32 fps = 2;
|
|
int32 bitrate_kbps = 3;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Chat Messages
|
|
// ============================================================================
|
|
|
|
message ChatMessage {
|
|
string id = 1; // Unique message ID
|
|
string sender = 2; // "technician" or "client"
|
|
string content = 3; // Message text
|
|
int64 timestamp = 4; // Unix timestamp
|
|
}
|
|
|
|
// ============================================================================
|
|
// Control Messages
|
|
// ============================================================================
|
|
|
|
message Heartbeat {
|
|
int64 timestamp = 1;
|
|
}
|
|
|
|
message HeartbeatAck {
|
|
int64 client_timestamp = 1;
|
|
int64 server_timestamp = 2;
|
|
}
|
|
|
|
message Disconnect {
|
|
string reason = 1;
|
|
}
|
|
|
|
// Server commands agent to start streaming video
|
|
message StartStream {
|
|
string viewer_id = 1; // ID of viewer requesting stream
|
|
int32 display_id = 2; // Which display to stream (0 = primary)
|
|
}
|
|
|
|
// Server commands agent to stop streaming
|
|
message StopStream {
|
|
string viewer_id = 1; // Which viewer disconnected
|
|
}
|
|
|
|
// Agent reports its status periodically when idle
|
|
message AgentStatus {
|
|
string hostname = 1;
|
|
string os_version = 2;
|
|
bool is_elevated = 3;
|
|
int64 uptime_secs = 4;
|
|
int32 display_count = 5;
|
|
bool is_streaming = 6;
|
|
string agent_version = 7; // Agent version (e.g., "0.1.0-abc123")
|
|
string organization = 8; // Company/organization name
|
|
string site = 9; // Site/location name
|
|
repeated string tags = 10; // Tags for categorization
|
|
}
|
|
|
|
// Server commands agent to uninstall itself
|
|
message AdminCommand {
|
|
AdminCommandType command = 1;
|
|
string reason = 2; // Why the command was issued
|
|
}
|
|
|
|
enum AdminCommandType {
|
|
ADMIN_UNINSTALL = 0; // Uninstall agent and remove from startup
|
|
ADMIN_RESTART = 1; // Restart the agent process
|
|
ADMIN_UPDATE = 2; // Download and install update
|
|
}
|
|
|
|
// ============================================================================
|
|
// Attended-mode Consent (Task 5)
|
|
// ============================================================================
|
|
//
|
|
// For an ATTENDED (support-code) session, the end user on the managed machine
|
|
// must explicitly accept a consent prompt before the technician's session goes
|
|
// live. The server holds the session in `consent_state = pending` and does NOT
|
|
// surface it to the technician (no viewer join / no streaming) until the agent
|
|
// returns a ConsentResponse with granted = true. A denial or timeout tears the
|
|
// session down. Managed/unattended sessions are `not_required` and never see a
|
|
// ConsentRequest (Phase-1 default policy).
|
|
|
|
// Server/relay -> Agent: ask the end user to allow this support session.
|
|
message ConsentRequest {
|
|
string session_id = 1; // Session awaiting consent (UUID string)
|
|
string technician_name = 2; // Display name of the requesting technician
|
|
ConsentAccessMode access_mode = 3; // View vs. control, so the prompt is honest
|
|
int32 timeout_secs = 4; // How long the agent should wait before auto-deny
|
|
}
|
|
|
|
// Agent -> Server: the end user's decision.
|
|
message ConsentResponse {
|
|
string session_id = 1; // Echoes the ConsentRequest session_id
|
|
bool granted = 2; // true = allow, false = deny (incl. dialog closed)
|
|
string reason = 3; // Optional human-readable reason (e.g. "timeout")
|
|
}
|
|
|
|
// Whether the technician is requesting view-only or full control. Mirrors the
|
|
// viewer access mode the server already enforces; used only to phrase the prompt.
|
|
enum ConsentAccessMode {
|
|
CONSENT_VIEW = 0; // Technician will VIEW the screen
|
|
CONSENT_CONTROL = 1; // Technician will VIEW and CONTROL
|
|
}
|
|
|
|
// ============================================================================
|
|
// Auto-Update Messages
|
|
// ============================================================================
|
|
|
|
// Update command details (sent with AdminCommand or standalone)
|
|
message UpdateInfo {
|
|
string version = 1; // Target version (e.g., "0.2.0")
|
|
string download_url = 2; // HTTPS URL to download new binary
|
|
string checksum_sha256 = 3; // SHA-256 hash for verification
|
|
bool mandatory = 4; // If true, agent must update immediately
|
|
}
|
|
|
|
// Update status report (agent -> server)
|
|
message UpdateStatus {
|
|
string current_version = 1; // Current running version
|
|
UpdateState state = 2; // Current update state
|
|
string error_message = 3; // Error details if state is FAILED
|
|
int32 progress_percent = 4; // Download progress (0-100)
|
|
}
|
|
|
|
enum UpdateState {
|
|
UPDATE_IDLE = 0; // No update in progress
|
|
UPDATE_CHECKING = 1; // Checking for updates
|
|
UPDATE_DOWNLOADING = 2; // Downloading new binary
|
|
UPDATE_VERIFYING = 3; // Verifying checksum
|
|
UPDATE_INSTALLING = 4; // Installing (rename/copy)
|
|
UPDATE_RESTARTING = 5; // About to restart
|
|
UPDATE_COMPLETE = 6; // Update successful (after restart)
|
|
UPDATE_FAILED = 7; // Update failed
|
|
}
|
|
|
|
// ============================================================================
|
|
// Top-Level Message Wrapper
|
|
// ============================================================================
|
|
|
|
message Message {
|
|
oneof payload {
|
|
// Session
|
|
SessionRequest session_request = 1;
|
|
SessionResponse session_response = 2;
|
|
|
|
// Video
|
|
VideoFrame video_frame = 10;
|
|
VideoAck video_ack = 11;
|
|
SwitchDisplay switch_display = 12;
|
|
|
|
// Cursor
|
|
CursorShape cursor_shape = 15;
|
|
CursorPosition cursor_position = 16;
|
|
|
|
// Input
|
|
MouseEvent mouse_event = 20;
|
|
KeyEvent key_event = 21;
|
|
SpecialKeyEvent special_key = 22;
|
|
|
|
// Clipboard
|
|
ClipboardData clipboard_data = 30;
|
|
ClipboardRequest clipboard_request = 31;
|
|
|
|
// Quality
|
|
QualitySettings quality_settings = 40;
|
|
LatencyReport latency_report = 41;
|
|
|
|
// Control
|
|
Heartbeat heartbeat = 50;
|
|
HeartbeatAck heartbeat_ack = 51;
|
|
Disconnect disconnect = 52;
|
|
StartStream start_stream = 53;
|
|
StopStream stop_stream = 54;
|
|
AgentStatus agent_status = 55;
|
|
|
|
// Chat
|
|
ChatMessage chat_message = 60;
|
|
|
|
// Admin commands (server -> agent)
|
|
AdminCommand admin_command = 70;
|
|
|
|
// Auto-update messages
|
|
UpdateInfo update_info = 75; // Server -> Agent: update available
|
|
UpdateStatus update_status = 76; // Agent -> Server: update progress
|
|
|
|
// Attended-mode consent (Task 5)
|
|
ConsentRequest consent_request = 80; // Server/relay -> Agent: prompt end user
|
|
ConsentResponse consent_response = 81; // Agent -> Server: end user's decision
|
|
}
|
|
}
|