feat(cloud): skill management + host-scoped sync REST endpoints

Adds the skills:admin router (cloud/sdk/skill_api.py) for cloud-skill CRUD
and per-host entitlement grant/revoke with CSRF/scope/audit, and a
host-scoped router serving incremental per-host sync deltas plus the
agent local-skill inventory report/readback. Both composed into the
Cloud API app. cloud-api suite green (46 passed).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 07:51:27 +08:00
co-authored by Claude Opus 4.6
parent 52e442790a
commit cbfdb2ae39
6 changed files with 524 additions and 7 deletions
@@ -285,3 +285,67 @@ class TaskPlannerDecisionItem(BaseModel):
class TaskPlannerDecisionListResponse(BaseModel):
items: list[TaskPlannerDecisionItem]
# ----------------------------------------------------------------------
# Cloud-managed skills
# ----------------------------------------------------------------------
class CloudSkillSummary(BaseModel):
id: str
name: str
kind: Literal["knowledge", "flow_template"]
description: str
tags: list[str] = Field(default_factory=list)
revision: int
created_at: datetime
updated_at: datetime
class CloudSkillCreateRequest(BaseModel):
name: str = Field(min_length=1, max_length=200)
kind: Literal["knowledge", "flow_template"]
description: str = ""
tags: list[str] = Field(default_factory=list)
content: str = ""
steps: list[dict[str, Any]] = Field(default_factory=list)
parameters: dict[str, dict[str, Any]] = Field(default_factory=dict)
class CloudSkillUpdateRequest(CloudSkillCreateRequest):
pass
class CloudSkillResponse(CloudSkillSummary):
content: str = ""
steps: list[dict[str, Any]] = Field(default_factory=list)
parameters: dict[str, dict[str, Any]] = Field(default_factory=dict)
class CloudSkillListResponse(BaseModel):
items: list[CloudSkillResponse]
class CloudSkillEntitlementListResponse(BaseModel):
skill_id: str
host_ids: list[str] = Field(default_factory=list)
class CloudSkillSyncResponse(BaseModel):
skills: list[CloudSkillResponse]
removed_ids: list[str] = Field(default_factory=list)
latest_version: int
is_full_replace: bool
class HostSkillInventoryRequest(BaseModel):
"""Agent-reported read-only local-skill inventory (metadata only)."""
skills: list[dict[str, Any]] = Field(default_factory=list)
class HostSkillInventoryResponse(BaseModel):
host_id: str
payload: list[dict[str, Any]] = Field(default_factory=list)
reported_at: datetime | None = None