syntax = "proto3";

package agent;

// The Cortex Server exposes this service
service AgentOrchestrator {
  // 1. Control Channel: Sync policies and settings (Unary)
  rpc SyncConfiguration(RegistrationRequest) returns (RegistrationResponse);

  // 2. Task Channel: Bidirectional work dispatch and reporting (Persistent)
  rpc TaskStream(stream ClientTaskMessage) returns (stream ServerTaskMessage);

  // 3. Health Channel: Dedicated Ping-Pong / Heartbeat (Persistent)
  rpc ReportHealth(stream Heartbeat) returns (stream HealthCheckResponse);
}

// --- Channel 1: Registration & Policy ---
message RegistrationRequest {
  string node_id = 1;
  string version = 2;
  string auth_token = 3;
  string node_description = 4; // AI-readable description of this node's role
  map<string, string> capabilities = 5; // e.g. "gpu": "nvidia-3080", "os": "ubuntu-22.04"
}

message SandboxPolicy {
  enum Mode {
    STRICT = 0;
    PERMISSIVE = 1;
  }
  Mode mode = 1;
  repeated string allowed_commands = 2;
  repeated string denied_commands = 3;
  repeated string sensitive_commands = 4;
  string working_dir_jail = 5;
  string skill_config_json = 6; // NEW: Map of skill settings (e.g. {"shell": {"cwd_jail": "/tmp"}})
}

message RegistrationResponse {
  bool success = 1;
  string error_message = 2;
  string session_id = 3;
  SandboxPolicy policy = 4;
}

// --- Channel 2: Tasks & Collaboration ---
message ClientTaskMessage {
    oneof payload {
        TaskResponse task_response = 1;
        TaskClaimRequest task_claim = 2;
        NodeAnnounce announce = 4; // NEW: Identification on stream connect
        FileSyncMessage file_sync = 5; // NEW: Ghost Mirror Sync
        SkillEvent skill_event = 6;     // NEW: Persistent real-time skill data
    }
}

message SkillEvent {
    string session_id = 1;
    string task_id = 2;
    oneof data {
        string terminal_out = 3;   // Raw stdout/stderr chunks
        string prompt = 4;         // Interactive prompt (like password)
        bool keep_alive = 5;       // Session preservation
    }
}

message NodeAnnounce {
    string node_id = 1;
}

message ServerTaskMessage {
  oneof payload {
      TaskRequest task_request = 1;    
      WorkPoolUpdate work_pool_update = 2; 
      TaskClaimResponse claim_status = 3;
      TaskCancelRequest task_cancel = 4;
      FileSyncMessage file_sync = 5; // NEW: Ghost Mirror Sync
      SandboxPolicy policy_update = 6; // NEW: Live Policy Update
  }
  string signature = 7; // NEW: Unified Signature
}

message TaskCancelRequest {
    string task_id = 1;
    string session_id = 2; // NEW: Cancel all tasks in this session
}

message TaskRequest {
  string task_id = 1;
  string task_type = 2; 
  oneof payload {
      string payload_json = 3;     // For legacy shell/fallback
  }
  int32 timeout_ms = 4;
  string trace_id = 5;
  string signature = 6;
  string session_id = 8; // NEW: Map execution to a sync workspace
}

message TaskResponse {
  string task_id = 1;
  enum Status {
    SUCCESS = 0;
    ERROR = 1;
    TIMEOUT = 2;
    CANCELLED = 3;
  }
  Status status = 2;
  string stdout = 3;
  string stderr = 4;
  string trace_id = 5;
  map<string, bytes> artifacts = 6;
}

message WorkPoolUpdate {
  repeated string available_task_ids = 1;
}

message TaskClaimRequest {
  string task_id = 1;
  string node_id = 2;
}

message TaskClaimResponse {
  string task_id = 1;
  bool granted = 2;
  string reason = 3;
}

// --- Channel 3: Health & Observation ---
message Heartbeat {
  string node_id = 1;
  float cpu_usage_percent = 2;
  float memory_usage_percent = 3;
  int32 active_worker_count = 4;
  int32 max_worker_capacity = 5;
  string status_message = 6;
  repeated string running_task_ids = 7;
  int32 cpu_count = 8;
  float memory_used_gb = 9;
  float memory_total_gb = 10;

  // Rich Metrics (M6)
  repeated float cpu_usage_per_core = 11;
  float cpu_freq_mhz = 12;
  float memory_available_gb = 13;
  repeated float load_avg = 14; // [1min, 5min, 15min]
}


message HealthCheckResponse {
  int64 server_time_ms = 1;
}

// --- Channel 4: Ghost Mirror File Sync ---
message FileSyncMessage {
    string session_id = 1;
    oneof payload {
        DirectoryManifest manifest = 2;
        FilePayload file_data = 3;
        SyncStatus status = 4;
        SyncControl control = 5;
    }
    string task_id = 6; // NEW: Correlation ID for FS operations
}

message SyncControl {
    enum Action {
        START_WATCHING = 0;
        STOP_WATCHING = 1;
        LOCK = 2;           // Server -> Node: Disable user-side edits
        UNLOCK = 3;         // Server -> Node: Enable user-side edits
        REFRESH_MANIFEST = 4; // Server -> Node: Request a full manifest from node
        RESYNC = 5;         // Server -> Node: Force a hash-based reconciliation
        
        // FS Operations (Modular Explorer)
        LIST = 6;           // Server -> Node: List directory contents (returns manifest)
        READ = 7;           // Server -> Node: Read file content (returns file_data)
        WRITE = 8;          // Server -> Node: Write/Create file
        DELETE = 9;         // Server -> Node: Delete file or directory
        PURGE = 10;         // Server -> Node: Purge local sync directory entirely
        CLEANUP = 11;       // Server -> Node: Purge any session dirs not in request_paths
    }
    Action action = 1;
    string path = 2;
    repeated string request_paths = 3; // NEW: Specific files requested for pull
    bytes content = 4;                 // NEW: For WRITE operation
    bool is_dir = 5;                  // NEW: For TOUCH/WRITE operation
}

message DirectoryManifest {
    string root_path = 1;
    repeated FileInfo files = 2;
    int32 chunk_index = 3; // NEW: For paginated manifest
    bool is_final = 4;     // NEW: For paginated manifest
}

message FileInfo {
    string path = 1;
    int64 size = 2;
    string hash = 3; // For drift detection
    bool is_dir = 4;
}

message FilePayload {
    string path = 1;
    bytes chunk = 2;
    int32 chunk_index = 3;
    bool is_final = 4;
    string hash = 5; // Full file hash for verification on final chunk
    int64 offset = 6; // NEW: Byte offset for random-access parallel writes
    bool compressed = 7; // NEW: Whether the chunk is compressed (zlib)
    int32 total_chunks = 8; // NEW: Total number of chunks expected
    int64 total_size = 9;   // NEW: Total file size in bytes
}

message SyncStatus {
    enum Code {
        OK = 0;
        ERROR = 1;
        RECONCILE_REQUIRED = 2;
        IN_PROGRESS = 3;
    }
    Code code = 1;
    string message = 2;
    repeated string reconcile_paths = 3; // NEW: Files needing immediate re-sync
}
