Files
kandev/docs/plans/plugins/HOST-DATA-API.proto
T

309 lines
14 KiB
Protocol Buffer

// kandev.plugin.v1 Host data API — folded into
// apps/backend/proto/kandev/plugin/v1/plugin.proto and fully wired (reads and
// writes both implemented). This file is the frozen design draft kept for
// reference alongside the ADRs: a point-in-time SNAPSHOT that has NOT tracked
// the contract since it was written. For the live shape, read
// apps/backend/proto/kandev/plugin/v1/plugin.proto (wire contract) and
// apps/backend/pkg/pluginsdk/data_types.go (Go SDK mirror) directly — do not
// infer current fields from this file. Examples of known-diverged messages
// (not exhaustive — other messages in this file may also have drifted):
// `Task` (17 fields here vs 29 live), `WorkflowStep` (5 here vs 10 live —
// missing `color`, `is_start_step`, `wip_limit`, `agent_profile_id`,
// `on_enter_action_types`), and `Message` (absent here; the whole
// `ListMessages` read path from ADR 0047 postdates this draft).
//
// Extends the existing capability-gated Host service so plugins read AND write
// kandev data through a typed, versioned contract instead of touching the
// SQLite file directly. These messages are a HAND-DEFINED public contract: the
// backend maps internal models → these DTOs explicitly. Do NOT generate them
// from domain structs — the mapping IS the decoupling.
//
// Enforcement (host side): each RPC checks the caller plugin's manifest
// capabilities. Reads require `api_read: ["<resource>"]`; writes require
// `api_write: ["<resource>"]`. Undeclared → gRPC PermissionDenied
// "capability 'api_read:tasks' not declared". Writes go through the service
// layer (which publishes task.* events), never the repository.
syntax = "proto3";
package kandev.plugin.v1;
import "google/protobuf/struct.proto";
// ── Added to the existing `service Host` (state/secrets/emit already there) ──
service HostData {
// Reads — capability api_read:<resource>
rpc ListTasks(ListTasksRequest) returns (ListTasksResponse);
rpc GetTask(GetTaskRequest) returns (GetTaskResponse);
rpc ListWorkspaces(ListWorkspacesRequest) returns (ListWorkspacesResponse);
rpc ListWorkflows(ListWorkflowsRequest) returns (ListWorkflowsResponse);
rpc ListWorkflowSteps(ListWorkflowStepsRequest) returns (ListWorkflowStepsResponse);
rpc ListAgentProfiles(ListAgentProfilesRequest) returns (ListAgentProfilesResponse);
rpc ListExecutorProfiles(ListExecutorProfilesRequest) returns (ListExecutorProfilesResponse);
rpc ListRepositories(ListRepositoriesRequest) returns (ListRepositoriesResponse);
// Sessions + code stats — capability api_read:sessions. Driven by a real
// plugin (kandev-plugin-agent-stats) that otherwise read task_sessions,
// task_session_commits, and task_session_git_snapshots straight from the
// SQLite file. SessionCodeStats is a COMPUTED, stable DTO — deliberately not
// the raw commit/snapshot rows, whose JSON schema churns.
rpc ListSessions(ListSessionsRequest) returns (ListSessionsResponse);
rpc ListSessionCodeStats(ListSessionCodeStatsRequest) returns (ListSessionCodeStatsResponse);
// Writes — capability api_write:<resource>. Route through service methods so
// the corresponding task.* events fire; this is the whole reason not to let
// plugins write the DB.
// `ListExecutorProfiles` requires `api_read:executor_profiles`; preview/delete
// capability is implicit in plugin-owned task provenance and is never conferred by
// a task link stored in plugin state.
rpc CreateTask(CreateTaskRequest) returns (CreateTaskResponse);
rpc UpdateTask(UpdateTaskRequest) returns (UpdateTaskResponse);
rpc SendMessage(SendMessageRequest) returns (SendMessageResponse);
rpc PreviewPluginOwnedTaskTree(PreviewPluginOwnedTaskTreeRequest) returns (PreviewPluginOwnedTaskTreeResponse);
rpc DeletePluginOwnedTaskTree(DeletePluginOwnedTaskTreeRequest) returns (DeletePluginOwnedTaskTreeResponse);
}
// ── Common ──────────────────────────────────────────────────────────────────
// Pagination is opaque-cursor based so the server can change the underlying
// ordering/store without breaking plugins.
message Page {
int32 limit = 1; // 0 → server default (e.g. 50); server caps the max
string cursor = 2; // empty → first page; echo next_cursor to continue
}
message PageInfo { string next_cursor = 1; bool has_more = 2; }
// Timestamps are RFC3339 strings (matches the JSON API + the Event envelope
// convention), not protobuf Timestamp, to keep one time representation across
// the whole plugin contract. Optional string fields use `optional` so absent
// (NULL) is distinguishable from empty.
// ── Task ────────────────────────────────────────────────────────────────────
message Task {
string id = 1;
string workspace_id = 2;
string workflow_id = 3;
string title = 4;
string description = 5;
string state = 6; // TaskState enum value as a string, stable
string priority = 7;
string created_by = 8;
string created_at = 9; // RFC3339
string updated_at = 10;
optional string started_at = 11;
optional string completed_at = 12;
optional string parent_id = 13;
string identifier = 14;
bool is_ephemeral = 15;
repeated TaskRepository repositories = 16;
google.protobuf.Struct metadata = 17; // free-form; plugin-tolerant
repeated string labels = 23 [deprecated = true]; // API v1 compatibility only.
}
message TaskRepository {
string id = 1;
string repository_id = 2;
string base_branch = 3;
int32 position = 4;
string checkout_branch = 5;
}
message TaskFilter {
repeated string workspace_ids = 1; // empty → all the plugin may see
repeated string workflow_ids = 2;
repeated string states = 3;
optional string parent_id = 4;
bool include_ephemeral = 5; // default false (kanban-visible only)
}
message ListTasksRequest { TaskFilter filter = 1; Page page = 2; }
message ListTasksResponse { repeated Task tasks = 1; PageInfo page_info = 2; }
message GetTaskRequest { string id = 1; }
message GetTaskResponse { Task task = 1; }
// ── Workspace / Workflow / Steps ─────────────────────────────────────────────
message Workspace {
string id = 1;
string name = 2;
optional string description = 3;
string owner_id = 4;
optional string default_executor_id = 5;
optional string default_agent_profile_id = 6;
string created_at = 7;
string updated_at = 8;
}
message ListWorkspacesRequest { Page page = 1; }
message ListWorkspacesResponse { repeated Workspace workspaces = 1; PageInfo page_info = 2; }
message Workflow {
string id = 1;
string workspace_id = 2;
string name = 3;
optional string description = 4;
int32 sort_order = 5;
string created_at = 6;
string updated_at = 7;
}
message ListWorkflowsRequest { string workspace_id = 1; Page page = 2; }
message ListWorkflowsResponse { repeated Workflow workflows = 1; PageInfo page_info = 2; }
// DIVERGED from the live contract — see canonical
// apps/backend/proto/kandev/plugin/v1/plugin.proto for fields 6-10
// (color, is_start_step, wip_limit, agent_profile_id, on_enter_action_types).
message WorkflowStep {
string id = 1;
string workflow_id = 2;
string name = 3;
int32 position = 4;
string stage_type = 5; // work | review | approval | custom
}
message ListWorkflowStepsRequest { string workflow_id = 1; }
message ListWorkflowStepsResponse { repeated WorkflowStep steps = 1; }
// ── Agent profiles / Repositories (read-only context) ────────────────────────
message AgentProfile {
string id = 1;
string agent_id = 2;
string display_name = 3;
string name = 4;
string model = 5;
string mode = 6;
}
message ListAgentProfilesRequest { Page page = 1; }
message ListAgentProfilesResponse { repeated AgentProfile profiles = 1; PageInfo page_info = 2; }
message ExecutorProfile {
string id = 1;
string display_name = 2;
string executor_type = 3;
}
message ListExecutorProfilesRequest { Page page = 1; }
message ListExecutorProfilesResponse { repeated ExecutorProfile profiles = 1; PageInfo page_info = 2; }
message Repository {
string id = 1;
string workspace_id = 2;
string name = 3; // owner/repo slug when known, else name
optional string default_branch = 4;
// Credential-free provider origin identity. Never expose local paths,
// scripts, or credentials through this Host DTO.
string source_type = 5;
string provider_id = 6;
string provider_repository_id = 7;
string provider_host = 8;
string owner_or_project = 9;
string provider_name = 10;
string remote_url = 11;
// Opaque provider-defined connection scope. Together with the immutable
// provider_repository_id, this is the durable upstream identity.
string provider_scope = 12;
}
message ListRepositoriesRequest { string workspace_id = 1; Page page = 2; }
message ListRepositoriesResponse { repeated Repository repositories = 1; PageInfo page_info = 2; }
// ── Sessions ─────────────────────────────────────────────────────────────────
// Identity + agent context for one agent run on a task. acp_session_id is the
// join key an external usage tool (e.g. tokscale) uses to attribute tokens — so
// kandev exposes it and stays out of the token business entirely.
message Session {
string id = 1;
string task_id = 2;
string agent_profile_id = 3;
string agent_display_name = 4; // from the profile snapshot at run time
string model = 5; // resolved model at run time
string acp_session_id = 6; // external usage-attribution join key
string state = 7;
string started_at = 8;
optional string ended_at = 9;
string agent_profile_name = 10; // profile name from the snapshot at run time
}
message SessionFilter {
repeated string task_ids = 1;
repeated string workspace_ids = 2;
repeated string states = 3;
}
message ListSessionsRequest { SessionFilter filter = 1; Page page = 2; }
message ListSessionsResponse { repeated Session sessions = 1; PageInfo page_info = 2; }
// Computed code metrics per session — the aggregate the agent-stats plugin
// re-derived by hand from commits + git snapshots. Exposed as a stable shape so
// plugins never touch task_session_commits / task_session_git_snapshots.
message SessionCodeStats {
string session_id = 1;
int64 lines_added_committed = 2; // SUM of commit insertions
int64 lines_deleted_committed = 3; // SUM of commit deletions
int64 lines_added_peak_pending = 4; // peak uncommitted diff additions across snapshots
int64 lines_deleted_peak_pending = 5;
}
message ListSessionCodeStatsRequest { SessionFilter filter = 1; Page page = 2; }
message ListSessionCodeStatsResponse { repeated SessionCodeStats stats = 1; PageInfo page_info = 2; }
// ── Writes (api_write) ───────────────────────────────────────────────────────
message CreateTaskRequest {
string workspace_id = 1;
string workflow_id = 2;
optional string workflow_step_id = 3;
string title = 4;
string description = 5;
optional string parent_id = 6;
bool start_agent = 7;
// kandev stamps source = "plugin:<id>" server-side; not settable here.
repeated PluginTaskRepository repositories = 8;
optional PluginTaskLaunchOptions launch = 9;
google.protobuf.Struct metadata = 10; // plugin namespace only; host reserves source.
string priority = 11; // empty defaults to medium
}
message PluginTaskRepository {
string repository_id = 1; // existing Kandev repository, or empty with remote below
optional RemoteRepositoryDescriptor remote = 2;
optional string base_branch = 3;
optional string checkout_branch = 4;
optional int64 pull_request_number = 5;
}
message RemoteRepositoryDescriptor {
string provider_id = 1;
string provider_host = 2;
string owner_or_project = 3;
string provider_repository_id = 4;
string name = 5;
string clone_url = 6; // exact credential-free URL; host never reconstructs it
optional string default_branch = 7;
optional string base_branch = 8;
optional string head_branch = 9;
optional int64 pull_request_number = 10;
string provider_scope = 11; // credential-free configured provider instance
}
message PluginTaskLaunchOptions {
optional string agent_profile_id = 1;
optional string executor_profile_id = 2;
optional string prompt = 3;
optional string plan_mode = 4;
}
message CreateTaskResponse { Task task = 1; }
message UpdateTaskRequest {
string id = 1;
optional string title = 2;
optional string description = 3;
optional string state = 4;
optional string workflow_step_id = 5;
optional string priority = 6;
}
message UpdateTaskResponse { Task task = 1; }
// SendMessage delivers a prompt to a task session (api_write:messages) through
// the orchestrator — the same delivery path message_task uses. The server
// records a user message stamped source = "plugin:<id>" and resolves session_id
// (empty → the task's primary session), queueing when the session is running
// and resuming/starting it otherwise.
message SendMessageRequest {
string task_id = 1;
string session_id = 2; // empty → the task's primary session
string text = 3;
}
message SendMessageResponse {
string session_id = 1; // the session the message was delivered to
string status = 2; // "queued" | "sent" | "started"
}
// Deletion is owner-scoped. Host compares metadata.source with calling plugin ID; links
// in plugin state are not deletion authority. Preview always precedes destructive UI.
message PreviewPluginOwnedTaskTreeRequest { string root_task_id = 1; }
message PreviewPluginOwnedTaskTreeResponse { repeated Task tasks = 1; }
message DeletePluginOwnedTaskTreeRequest { string root_task_id = 1; }
message DeletePluginOwnedTaskTreeResponse { repeated string deleted_task_ids = 1; }