syntax = "proto3";

package clud.v1;

// Foundation schema for the clud daemon wire migration. The current runtime
// still uses JSON-lines; these messages mirror the existing tagged JSON
// request and response variants so the prost path can be brought online
// without renaming the JSON contract.

message ClientToDaemon {
  oneof request {
    CreateRequest create = 1;
    SessionRequest session = 2;
    ListLiveCwdsRequest list_live_cwds = 3;
    TerminateRequest terminate = 4;
    InterruptRequest interrupt = 5;
    AdoptKillRequest adopt_kill = 6;
    GcRequest gc = 7;
    ShutdownRequest shutdown = 8;
    ReapOrphansRequest reap_orphans = 9;
    MetricsRequest metrics = 10;
    ProcSnapshotRequest proc_snapshot = 11;
    AcquireClientLeaseRequest acquire_client_lease = 12;
    ReleaseClientLeaseRequest release_client_lease = 13;
  }
  string request_id = 100;
}

message DaemonToClient {
  oneof response {
    CreatedResponse created = 1;
    SessionResponse session = 2;
    LiveCwdsResponse live_cwds = 3;
    TerminatedResponse terminated = 4;
    InterruptedResponse interrupted = 5;
    AdoptKillAckResponse adopt_kill_ack = 6;
    GcResponse gc = 7;
    ShutdownAckResponse shutdown_ack = 8;
    ErrorResponse error = 9;
    ReapOrphansAckResponse reap_orphans_ack = 10;
    MetricsResponse metrics = 11;
    ProcSnapshotResponse proc_snapshot = 12;
    ClientLeaseAcquiredResponse client_lease_acquired = 13;
    ClientLeaseReleasedResponse client_lease_released = 14;
  }
  string request_id = 100;
}

message WorkerClientEnvelope {
  oneof message {
    WorkerAttachRequest attach = 1;
    WorkerInputRequest input = 2;
    WorkerResizeRequest resize = 3;
    WorkerInterruptRequest interrupt = 4;
  }
}

message WorkerServerEnvelope {
  oneof message {
    WorkerAttachedResponse attached = 1;
    WorkerOutputResponse output = 2;
    WorkerExitedResponse exited = 3;
    WorkerErrorResponse error = 4;
  }
}

message CreateRequest {
  // JSON mirror of WorkerLaunchSpec. Kept opaque in the foundation PR so
  // LaunchPlan and graphics structs do not get split before the daemon starts
  // accepting prost frames.
  bytes spec_json = 1;
}

message SessionRequest {
  string session_id = 1;
}

message ListLiveCwdsRequest {}

message TerminateRequest {
  string session_id = 1;
}

message InterruptRequest {
  string session_id = 1;
  CtrlCProfile profile = 2;
}

message AdoptKillRequest {
  repeated uint32 pids = 1;
  optional string reason = 2;
}

message GcRequest {
  // JSON mirror of GcOp. This preserves every existing gc.* shape while
  // reserving one stable prost field number for the daemon envelope.
  bytes payload_json = 1;
}

message ShutdownRequest {}

// Issue: have-the-daemon-do-the-orphan-sweep. Fire-and-forget from the
// foreground CLI's exit hook: the daemon walks every CLUD-tagged process
// whose originator is no longer alive and reaps its tree. The ack is sent
// before the kill begins so the CLI does not block on shutdown.
message ReapOrphansRequest {}

message MetricsRequest {}

message ProcSnapshotRequest {
  uint64 include_dead_since_ms = 1;
}

message ProcessIdentity {
  uint32 pid = 1;
  uint64 start_time = 2;
}

message AcquireClientLeaseRequest {
  ProcessIdentity identity = 1;
}

message ReleaseClientLeaseRequest {
  ProcessIdentity identity = 1;
}

message ClientLeaseAcquiredResponse {
  uint32 lease_count = 1;
}

message ClientLeaseReleasedResponse {
  uint32 lease_count = 1;
}

message MetricsResponse {
  uint32 pid = 1;
  float cpu_pct = 2;
}

message ProcSnapshotResponse {
  // JSON mirror of ProcTreeSnapshot. Keeping this opaque preserves the
  // serde schema as the stable CLI contract while avoiding a large typed
  // proto split for the v1 observability surface.
  bytes snapshot_json = 1;
}

message ReapOrphansAckResponse {
  // Number of CLUD-tagged processes whose originator was already dead at
  // scan time. Always 0 in the fast-path ack; populated only by the
  // synchronous test path.
  uint32 found = 1;
  // Number actually reaped. Same caveat as `found`.
  uint32 reaped = 2;
}

// `session_json` (field 1) is a RETIRED JSON mirror of the typed
// snapshot: encoders stopped emitting it in clud 2.1.0 (running-process
// adoption, zackees/running-process#385 item 3) because no published
// release ever depended on it and same-version pairing (#192) makes a
// mixed wire impossible. Decoders still accept it as a defensive
// fallback; the field number stays reserved-in-place forever.
message CreatedResponse {
  bytes session_json = 1;
  SessionSnapshot session = 2;
}

message SessionResponse {
  bytes session_json = 1; // retired mirror; see CreatedResponse
  SessionSnapshot session = 2;
}

message LiveCwdsResponse {
  repeated string paths = 1;
}

message TerminatedResponse {
  bytes session_json = 1; // retired mirror; see CreatedResponse
  SessionSnapshot session = 2;
}

message InterruptedResponse {
  bytes session_json = 1; // retired mirror; see CreatedResponse
  SessionSnapshot session = 2;
}

message AdoptKillAckResponse {
  uint32 accepted = 1;
}

message GcResponse {
  // JSON mirror of GcReply.
  bytes reply_json = 1;
}

message ShutdownAckResponse {
  uint32 pid = 1;
}

message ErrorResponse {
  string message = 1;
}

message WorkerAttachRequest {
  optional bytes terminal_json = 1;
  optional uint32 rows = 2;
  optional uint32 cols = 3;
}

message WorkerInputRequest {
  string data_b64 = 1;
  bool submit = 2;
}

message WorkerResizeRequest {
  uint32 rows = 1;
  uint32 cols = 2;
}

message WorkerInterruptRequest {
  optional CtrlCProfile profile = 1;
}

message WorkerAttachedResponse {
  bytes session_json = 1; // retired mirror; see CreatedResponse
  SessionSnapshot session = 2;
}

message WorkerOutputResponse {
  string data_b64 = 1;
}

message WorkerExitedResponse {
  int32 exit_code = 1;
}

message WorkerErrorResponse {
  string message = 1;
}

message SessionSnapshot {
  string id = 1;
  SessionKind kind = 2;
  optional string cwd = 3;
  optional string name = 4;
  optional uint64 created_at = 5;
  optional bool detachable = 6;
  optional bool background = 7;
  optional bool attachable = 8;
  optional uint64 repeat_interval_secs = 9;
  optional uint64 repeat_next_run_at = 10;
  optional bool repeat_running = 11;
  uint32 daemon_pid = 12;
  uint32 worker_pid = 13;
  uint32 worker_port = 14;
  optional uint32 root_pid = 15;
  optional int32 exit_code = 16;
  optional CtrlCProfile ctrl_c = 17;
  optional string backend = 18;
  optional string launch_mode = 19;
  optional string repo_root = 20;
  repeated string command = 21;
  optional uint64 exited_at = 22;
  // Issue #558: OS start times pinned to daemon_pid/worker_pid/root_pid, so a
  // reader can distinguish those processes from whatever later inherits their
  // PIDs. Absent (0) from peers that predate the field.
  optional uint64 daemon_pid_start = 23;
  optional uint64 worker_pid_start = 24;
  optional uint64 root_pid_start = 25;
}

enum SessionKind {
  SESSION_KIND_UNSPECIFIED = 0;
  SESSION_KIND_SUBPROCESS = 1;
  SESSION_KIND_PTY = 2;
}

message CtrlCProfile {
  optional uint32 cli_pid = 1;
  optional uint64 cli_observed_at_ms = 2;
  optional uint64 cli_handoff_at_ms = 3;
  optional uint64 cli_return_ready_at_ms = 4;
  optional uint64 cli_handoff_ms = 5;
  optional uint64 daemon_received_at_ms = 6;
  optional uint64 daemon_kill_started_at_ms = 7;
  optional uint64 daemon_kill_finished_at_ms = 8;
  optional uint64 daemon_kill_ms = 9;
  bool fast_path = 10;
}
