diff --git a/README.md b/README.md index 3da8b94..46db90d 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,7 @@ All endpoints require authentication unless noted. Full reference below. | `GET` | `/api/agents` | viewer | List agents with task stats | | `POST` | `/api/agents` | operator | Register/update agent | | `GET` | `/api/agents/[id]` | viewer | Agent details | +| `GET` | `/api/agents/[id]/attribution` | viewer | Self-scope attribution/audit/cost report (`?privileged=1` admin override) | | `POST` | `/api/agents/sync` | operator | Sync agents from openclaw.json | | `GET/PUT` | `/api/agents/[id]/soul` | operator | Agent SOUL content (reads from workspace, writes to both) | | `GET/POST` | `/api/agents/comms` | operator | Agent inter-agent communication | @@ -225,6 +226,14 @@ All endpoints require authentication unless noted. Full reference below. +### Attribution Contract (`/api/agents/[id]/attribution`) + +- Self-scope by default: requester identity must match target agent via `x-agent-name` (or matching authenticated username). +- Admin override requires explicit `?privileged=1`. +- Query params: + - `hours`: integer window `1..720` (default `24`) + - `section`: comma-separated subset of `identity,audit,mutations,cost` (default all) +
Monitoring diff --git a/openapi.json b/openapi.json index 094522f..26b9c74 100644 --- a/openapi.json +++ b/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "Mission Control API", - "version": "1.2.0", + "version": "1.3.0", "description": "AI Agent Orchestration Platform API" }, "servers": [ @@ -75,9 +75,1656 @@ { "name": "Connections", "description": "Direct CLI tool connections" + }, + { + "name": "Projects", + "description": "Project management and task grouping" + }, + { + "name": "Mentions", + "description": "User and agent mention autocomplete" + }, + { + "name": "Quality", + "description": "Quality review gate for tasks" + }, + { + "name": "Releases", + "description": "Release and version checking" + }, + { + "name": "Docs", + "description": "API documentation" } ], "paths": { + "/api/activities": { + "get": { + "tags": [ + "Monitoring" + ], + "summary": "List activities", + "operationId": "listActivities", + "parameters": [ + { + "name": "type", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "actor", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "entity_type", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Activity list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "activities": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "entity_type": { + "type": "string" + }, + "entity_id": { + "type": "integer" + }, + "actor": { + "type": "string" + }, + "description": { + "type": "string" + }, + "metadata": { + "type": "object" + }, + "created_at": { + "type": "integer" + } + } + } + }, + "total": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + } + }, + "/api/agents": { + "get": { + "tags": [ + "Agents" + ], + "summary": "List agents", + "operationId": "listAgents", + "parameters": [ + { + "name": "status", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "online", + "offline", + "busy", + "idle", + "error" + ] + } + }, + { + "name": "role", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50, + "maximum": 200 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Paginated agent list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agents": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Agent" + } + }, + "total": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "limit": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "post": { + "tags": [ + "Agents" + ], + "summary": "Create agent", + "operationId": "createAgent", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "role" + ], + "properties": { + "name": { + "type": "string" + }, + "role": { + "type": "string" + }, + "session_key": { + "type": "string" + }, + "soul_content": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "online", + "offline", + "busy", + "idle", + "error" + ], + "default": "offline" + }, + "config": { + "type": "object" + }, + "template": { + "type": "string" + }, + "gateway_config": { + "type": "object" + }, + "write_to_gateway": { + "type": "boolean" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Agent created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agent": { + "$ref": "#/components/schemas/Agent" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "409": { + "description": "Agent name already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + } + }, + "put": { + "tags": [ + "Agents" + ], + "summary": "Update agent by name", + "operationId": "updateAgentByName", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "online", + "offline", + "busy", + "idle", + "error" + ] + }, + "last_activity": { + "type": "string" + }, + "config": { + "type": "object" + }, + "session_key": { + "type": "string" + }, + "soul_content": { + "type": "string" + }, + "role": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Agent updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + } + } + }, + "/api/agents/comms": { + "get": { + "tags": [ + "Agents" + ], + "summary": "Get agent communications", + "operationId": "getAgentComms", + "parameters": [ + { + "name": "agent", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50 + } + } + ], + "responses": { + "200": { + "description": "Agent communication log", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "from_agent": { + "type": "string" + }, + "to_agent": { + "type": "string" + }, + "content": { + "type": "string" + }, + "type": { + "type": "string" + }, + "created_at": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + } + }, + "/api/agents/message": { + "post": { + "tags": [ + "Agents" + ], + "summary": "Send message between agents", + "operationId": "sendAgentMessage", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "from", + "to", + "content" + ], + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + }, + "content": { + "type": "string" + }, + "type": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Message sent", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "id": { + "type": "integer" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/agents/sync": { + "post": { + "tags": [ + "Agents" + ], + "summary": "Sync agents from gateway config", + "operationId": "syncAgents", + "responses": { + "200": { + "description": "Sync results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "synced": { + "type": "integer" + }, + "created": { + "type": "integer" + }, + "updated": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/agents/{id}": { + "get": { + "tags": [ + "Agents" + ], + "summary": "Get agent by ID", + "operationId": "getAgent", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Agent details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agent": { + "$ref": "#/components/schemas/Agent" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "tags": [ + "Agents" + ], + "summary": "Update agent by ID", + "operationId": "updateAgent", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "role": { + "type": "string" + }, + "status": { + "type": "string" + }, + "config": { + "type": "object" + }, + "session_key": { + "type": "string" + }, + "soul_content": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Agent updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agent": { + "$ref": "#/components/schemas/Agent" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + } + }, + "delete": { + "tags": [ + "Agents" + ], + "summary": "Delete agent", + "operationId": "deleteAgent", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Agent deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/api/agents/{id}/heartbeat": { + "get": { + "tags": [ + "Agents" + ], + "summary": "Check agent work items", + "operationId": "getAgentHeartbeat", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Heartbeat data with pending work items", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agent": { + "type": "string" + }, + "pending_tasks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Task" + } + }, + "messages": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "post": { + "tags": [ + "Agents" + ], + "summary": "Trigger manual heartbeat", + "operationId": "triggerAgentHeartbeat", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Heartbeat triggered", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/api/agents/{id}/memory": { + "get": { + "tags": [ + "Agents" + ], + "summary": "Get agent memory", + "operationId": "getAgentMemory", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Agent memory data", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "tags": [ + "Agents" + ], + "summary": "Update agent memory", + "operationId": "updateAgentMemory", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Memory updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/api/agents/{id}/soul": { + "get": { + "tags": [ + "Agents" + ], + "summary": "Get agent soul config", + "operationId": "getAgentSoul", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Soul configuration", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "soul_content": { + "type": "string" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "put": { + "tags": [ + "Agents" + ], + "summary": "Update agent soul config", + "operationId": "updateAgentSoul", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "soul_content" + ], + "properties": { + "soul_content": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Soul updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/api/agents/{id}/wake": { + "post": { + "tags": [ + "Agents" + ], + "summary": "Wake an agent", + "operationId": "wakeAgent", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reason": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Agent woken", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/api/alerts": { + "get": { + "tags": [ + "Alerts" + ], + "summary": "List alert rules", + "operationId": "listAlertRules", + "responses": { + "200": { + "description": "Alert rule list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "rules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AlertRule" + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "post": { + "tags": [ + "Alerts" + ], + "summary": "Create alert rule or evaluate rules", + "operationId": "createAlertRule", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "title": "CreateRule", + "required": [ + "name", + "entity_type", + "condition_field", + "condition_operator", + "condition_value" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "entity_type": { + "type": "string", + "enum": [ + "agent", + "task", + "session", + "activity" + ] + }, + "condition_field": { + "type": "string" + }, + "condition_operator": { + "type": "string", + "enum": [ + "equals", + "not_equals", + "greater_than", + "less_than", + "contains", + "count_above", + "count_below", + "age_minutes_above" + ] + }, + "condition_value": { + "type": "string" + }, + "action_type": { + "type": "string", + "default": "notification" + }, + "action_config": { + "type": "object" + }, + "cooldown_minutes": { + "type": "integer", + "default": 60 + } + } + }, + { + "type": "object", + "title": "EvaluateRules", + "required": [ + "action" + ], + "properties": { + "action": { + "type": "string", + "const": "evaluate" + } + } + } + ] + } + } + } + }, + "responses": { + "201": { + "description": "Rule created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "rule": { + "$ref": "#/components/schemas/AlertRule" + } + } + } + } + } + }, + "200": { + "description": "Rules evaluated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "evaluated": { + "type": "integer" + }, + "triggered": { + "type": "integer" + }, + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "rule_id": { + "type": "integer" + }, + "rule_name": { + "type": "string" + }, + "triggered": { + "type": "boolean" + }, + "reason": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + } + }, + "put": { + "tags": [ + "Alerts" + ], + "summary": "Update alert rule", + "operationId": "updateAlertRule", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "entity_type": { + "type": "string" + }, + "condition_field": { + "type": "string" + }, + "condition_operator": { + "type": "string" + }, + "condition_value": { + "type": "string" + }, + "action_type": { + "type": "string" + }, + "action_config": { + "type": "object" + }, + "cooldown_minutes": { + "type": "integer" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Rule updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "rule": { + "$ref": "#/components/schemas/AlertRule" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + } + }, + "delete": { + "tags": [ + "Alerts" + ], + "summary": "Delete alert rule", + "operationId": "deleteAlertRule", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Rule deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "deleted": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + } + } + }, + "/api/audit": { + "get": { + "tags": [ + "Monitoring" + ], + "summary": "Get audit log", + "operationId": "getAuditLog", + "parameters": [ + { + "name": "action", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "actor", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 100 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Audit log entries", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entries": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "action": { + "type": "string" + }, + "actor": { + "type": "string" + }, + "target_type": { + "type": "string" + }, + "target_id": { + "type": "integer" + }, + "detail": { + "type": "object" + }, + "ip_address": { + "type": "string" + }, + "created_at": { + "type": "integer" + } + } + } + }, + "total": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/auth/access-requests": { + "get": { + "tags": [ + "Auth" + ], + "summary": "List access requests", + "operationId": "listAccessRequests", + "responses": { + "200": { + "description": "Access request list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "requests": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "username": { + "type": "string" + }, + "email": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "approved", + "rejected" + ] + }, + "created_at": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + }, + "post": { + "tags": [ + "Auth" + ], + "summary": "Approve or reject access request", + "operationId": "handleAccessRequest", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "id", + "action" + ], + "properties": { + "id": { + "type": "integer" + }, + "action": { + "type": "string", + "enum": [ + "approve", + "reject" + ] + }, + "role": { + "type": "string", + "enum": [ + "admin", + "operator", + "viewer" + ] + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Request processed", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/auth/google": { + "get": { + "tags": [ + "Auth" + ], + "summary": "Google OAuth callback", + "operationId": "googleOAuthCallback", + "security": [], + "parameters": [ + { + "name": "code", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "state", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "302": { + "description": "Redirects to dashboard after successful auth" + }, + "400": { + "$ref": "#/components/responses/BadRequest" + } + } + } + }, "/api/auth/login": { "post": { "tags": [ @@ -434,22 +2081,60 @@ } } }, - "/api/auth/access-requests": { - "get": { + "/api/backup": { + "post": { "tags": [ - "Auth" + "Admin" ], - "summary": "List access requests", - "operationId": "listAccessRequests", + "summary": "Trigger backup", + "operationId": "triggerBackup", "responses": { "200": { - "description": "Access request list", + "description": "Backup completed", "content": { "application/json": { "schema": { "type": "object", "properties": { - "requests": { + "success": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "size_bytes": { + "type": "integer" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/chat/conversations": { + "get": { + "tags": [ + "Chat" + ], + "summary": "List conversations", + "operationId": "listConversations", + "responses": { + "200": { + "description": "Conversation list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "conversations": { "type": "array", "items": { "type": "object", @@ -457,23 +2142,473 @@ "id": { "type": "integer" }, - "username": { + "participants": { + "type": "array", + "items": { + "type": "string" + } + }, + "last_message": { "type": "string" }, - "email": { + "updated_at": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + } + }, + "/api/chat/messages": { + "get": { + "tags": [ + "Chat" + ], + "summary": "List messages", + "operationId": "listMessages", + "parameters": [ + { + "name": "conversation_id", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50 + } + }, + { + "name": "offset", + "in": "query", + "schema": { + "type": "integer", + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "Message list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "conversation_id": { + "type": "integer" + }, + "sender": { "type": "string" }, - "reason": { + "content": { "type": "string" }, + "created_at": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "post": { + "tags": [ + "Chat" + ], + "summary": "Send message", + "operationId": "sendMessage", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "conversation_id": { + "type": "integer" + }, + "content": { + "type": "string" + }, + "recipient": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Message sent", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "object" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/chat/messages/{id}": { + "get": { + "tags": [ + "Chat" + ], + "summary": "Get message by ID", + "operationId": "getMessage", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Message details", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "delete": { + "tags": [ + "Chat" + ], + "summary": "Delete message", + "operationId": "deleteMessage", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Message deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/api/claude/sessions": { + "get": { + "tags": [ + "Sessions" + ], + "summary": "List Claude CLI sessions", + "operationId": "listClaudeSessions", + "responses": { + "200": { + "description": "Session list" + } + } + }, + "post": { + "tags": [ + "Sessions" + ], + "summary": "Register a Claude CLI session", + "operationId": "registerClaudeSession", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "session_id": { + "type": "string" + }, + "agent_name": { + "type": "string" + }, + "model": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Session registered" + } + } + } + }, + "/api/cleanup": { + "post": { + "tags": [ + "Admin" + ], + "summary": "Trigger cleanup of old data", + "operationId": "triggerCleanup", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "older_than_days": { + "type": "integer", + "default": 30 + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Cleanup results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "deleted": { + "type": "object" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/connect": { + "post": { + "tags": [ + "Connections" + ], + "summary": "Register a direct CLI connection", + "description": "Registers a CLI tool directly without a gateway. Auto-creates agent if name doesn't exist.", + "operationId": "registerConnection", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "tool_name", + "agent_name" + ], + "properties": { + "tool_name": { + "type": "string" + }, + "tool_version": { + "type": "string" + }, + "agent_name": { + "type": "string" + }, + "agent_role": { + "type": "string" + }, + "metadata": { + "type": "object" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Connection registered", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "connection_id": { + "type": "string", + "format": "uuid" + }, + "agent_id": { + "type": "integer" + }, + "agent_name": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "connected" + ] + }, + "sse_url": { + "type": "string" + }, + "heartbeat_url": { + "type": "string" + }, + "token_report_url": { + "type": "string" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "get": { + "tags": [ + "Connections" + ], + "summary": "List all direct connections", + "operationId": "listConnections", + "responses": { + "200": { + "description": "List of connections", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "connections": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "agent_id": { + "type": "integer" + }, + "agent_name": { + "type": "string" + }, + "tool_name": { + "type": "string" + }, + "tool_version": { + "type": "string" + }, + "connection_id": { + "type": "string", + "format": "uuid" + }, "status": { "type": "string", "enum": [ - "pending", - "approved", - "rejected" + "connected", + "disconnected" ] }, + "last_heartbeat": { + "type": "integer" + }, "created_at": { "type": "integer" } @@ -485,6 +2620,127 @@ } } }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "delete": { + "tags": [ + "Connections" + ], + "summary": "Disconnect a CLI connection", + "operationId": "disconnectConnection", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "connection_id" + ], + "properties": { + "connection_id": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Disconnected", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "disconnected" + ] + }, + "connection_id": { + "type": "string" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/api/cron": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Get cron jobs", + "operationId": "getCronJobs", + "parameters": [ + { + "name": "action", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "list", + "logs" + ], + "default": "list" + } + } + ], + "responses": { + "200": { + "description": "Cron job list or logs", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jobs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "schedule": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "last_run": { + "type": "integer" + }, + "last_status": { + "type": "string" + } + } + } + } + } + } + } + } + }, "401": { "$ref": "#/components/responses/Unauthorized" }, @@ -495,10 +2751,10 @@ }, "post": { "tags": [ - "Auth" + "Admin" ], - "summary": "Approve or reject access request", - "operationId": "handleAccessRequest", + "summary": "Manage cron jobs", + "operationId": "manageCronJobs", "requestBody": { "required": true, "content": { @@ -506,225 +2762,28 @@ "schema": { "type": "object", "required": [ - "id", "action" ], "properties": { - "id": { - "type": "integer" - }, "action": { "type": "string", "enum": [ - "approve", - "reject" + "toggle", + "trigger", + "add", + "remove" ] }, - "role": { - "type": "string", - "enum": [ - "admin", - "operator", - "viewer" - ] - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Request processed", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/auth/google": { - "get": { - "tags": [ - "Auth" - ], - "summary": "Google OAuth callback", - "operationId": "googleOAuthCallback", - "security": [], - "parameters": [ - { - "name": "code", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "state", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "302": { - "description": "Redirects to dashboard after successful auth" - }, - "400": { - "$ref": "#/components/responses/BadRequest" - } - } - } - }, - "/api/agents": { - "get": { - "tags": [ - "Agents" - ], - "summary": "List agents", - "operationId": "listAgents", - "parameters": [ - { - "name": "status", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "online", - "offline", - "busy", - "idle", - "error" - ] - } - }, - { - "name": "role", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "default": 50, - "maximum": 200 - } - }, - { - "name": "offset", - "in": "query", - "schema": { - "type": "integer", - "default": 0 - } - } - ], - "responses": { - "200": { - "description": "Paginated agent list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "agents": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Agent" - } - }, - "total": { - "type": "integer" - }, - "page": { - "type": "integer" - }, - "limit": { - "type": "integer" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - }, - "post": { - "tags": [ - "Agents" - ], - "summary": "Create agent", - "operationId": "createAgent", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "name", - "role" - ], - "properties": { "name": { "type": "string" }, - "role": { + "schedule": { "type": "string" }, - "session_key": { + "command": { "type": "string" }, - "soul_content": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "online", - "offline", - "busy", - "idle", - "error" - ], - "default": "offline" - }, - "config": { - "type": "object" - }, - "template": { - "type": "string" - }, - "gateway_config": { - "type": "object" - }, - "write_to_gateway": { + "enabled": { "type": "boolean" } } @@ -732,98 +2791,9 @@ } } }, - "responses": { - "201": { - "description": "Agent created", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "agent": { - "$ref": "#/components/schemas/Agent" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "409": { - "description": "Agent name already exists", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - } - }, - "put": { - "tags": [ - "Agents" - ], - "summary": "Update agent by name", - "operationId": "updateAgentByName", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "online", - "offline", - "busy", - "idle", - "error" - ] - }, - "last_activity": { - "type": "string" - }, - "config": { - "type": "object" - }, - "session_key": { - "type": "string" - }, - "soul_content": { - "type": "string" - }, - "role": { - "type": "string" - } - } - } - } - } - }, "responses": { "200": { - "description": "Agent updated", + "description": "Action applied", "content": { "application/json": { "schema": { @@ -845,216 +2815,100 @@ }, "403": { "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "429": { - "$ref": "#/components/responses/RateLimited" } } } }, - "/api/agents/{id}": { + "/api/docs": { "get": { "tags": [ - "Agents" - ], - "summary": "Get agent by ID", - "operationId": "getAgent", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } + "Docs" ], + "summary": "Get OpenAPI specification", + "operationId": "getOpenApiSpec", + "security": [], "responses": { "200": { - "description": "Agent details", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "agent": { - "$ref": "#/components/schemas/Agent" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - }, - "put": { - "tags": [ - "Agents" - ], - "summary": "Update agent by ID", - "operationId": "updateAgent", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "role": { - "type": "string" - }, - "status": { - "type": "string" - }, - "config": { - "type": "object" - }, - "session_key": { - "type": "string" - }, - "soul_content": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Agent updated", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "agent": { - "$ref": "#/components/schemas/Agent" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - } - }, - "delete": { - "tags": [ - "Agents" - ], - "summary": "Delete agent", - "operationId": "deleteAgent", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Agent deleted", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" + "description": "OpenAPI 3.1 JSON spec" } } } }, - "/api/agents/{id}/heartbeat": { + "/api/workload": { "get": { "tags": [ - "Agents" - ], - "summary": "Check agent work items", - "operationId": "getAgentHeartbeat", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } + "Monitoring" ], + "summary": "Get real-time workload recommendation", + "description": "Returns system workload metrics and an actionable recommendation: `normal`, `throttle`, `shed`, or `pause`. Thresholds are runtime-configurable via `MC_WORKLOAD_*` environment variables.", + "operationId": "getWorkloadSignals", "responses": { "200": { - "description": "Heartbeat data with pending work items", + "description": "Workload snapshot and recommendation", "content": { "application/json": { "schema": { "type": "object", "properties": { - "agent": { - "type": "string" - }, - "pending_tasks": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Task" + "timestamp": { "type": "integer" }, + "workspace_id": { "type": "integer" }, + "capacity": { + "type": "object", + "properties": { + "active_tasks": { "type": "integer" }, + "tasks_last_5m": { "type": "integer" }, + "errors_last_5m": { "type": "integer" }, + "error_rate_5m": { "type": "number", "minimum": 0, "maximum": 1 }, + "completions_last_hour": { "type": "integer" }, + "avg_completion_rate_per_hour": { "type": "number" } } }, - "messages": { - "type": "array", - "items": { - "type": "object" + "queue": { + "type": "object", + "properties": { + "total_pending": { "type": "integer" }, + "by_status": { "type": "object", "additionalProperties": { "type": "integer" } }, + "by_priority": { "type": "object", "additionalProperties": { "type": "integer" } }, + "oldest_pending_age_seconds": { "type": ["integer", "null"] }, + "estimated_wait_seconds": { "type": ["integer", "null"] }, + "estimated_wait_confidence": { "type": "string", "enum": ["calculated", "unknown"] } } + }, + "agents": { + "type": "object", + "properties": { + "total": { "type": "integer" }, + "online": { "type": "integer" }, + "busy": { "type": "integer" }, + "idle": { "type": "integer" }, + "offline": { "type": "integer" }, + "busy_ratio": { "type": "number", "minimum": 0, "maximum": 1 }, + "load_distribution": { + "type": "array", + "items": { + "type": "object", + "properties": { + "agent": { "type": "string" }, + "assigned": { "type": "integer" }, + "in_progress": { "type": "integer" } + } + } + } + } + }, + "recommendation": { + "type": "object", + "properties": { + "action": { "type": "string", "enum": ["normal", "throttle", "shed", "pause"] }, + "reason": { "type": "string" }, + "details": { "type": "array", "items": { "type": "string" } }, + "submit_ok": { "type": "boolean" }, + "suggested_delay_ms": { "type": "integer" } + } + }, + "thresholds": { + "type": "object", + "description": "Effective runtime thresholds after environment overrides." } } } @@ -1063,82 +2917,80 @@ }, "401": { "$ref": "#/components/responses/Unauthorized" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - }, - "post": { - "tags": [ - "Agents" - ], - "summary": "Trigger manual heartbeat", - "operationId": "triggerAgentHeartbeat", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Heartbeat triggered", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "404": { - "$ref": "#/components/responses/NotFound" } } } }, - "/api/agents/{id}/soul": { + "/api/events": { "get": { "tags": [ - "Agents" + "Monitoring" ], - "summary": "Get agent soul config", - "operationId": "getAgentSoul", + "summary": "SSE stream for real-time events", + "operationId": "getEventStream", + "responses": { + "200": { + "description": "Server-Sent Events stream", + "content": { + "text/event-stream": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + } + }, + "/api/export": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Export data", + "operationId": "exportData", "parameters": [ { - "name": "id", - "in": "path", - "required": true, + "name": "type", + "in": "query", "schema": { - "type": "integer" + "type": "string", + "enum": [ + "tasks", + "agents", + "activities", + "all" + ] + } + }, + { + "name": "format", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "json", + "csv" + ], + "default": "json" } } ], "responses": { "200": { - "description": "Soul configuration", + "description": "Exported data", "content": { "application/json": { "schema": { - "type": "object", - "properties": { - "soul_content": { - "type": "string" - } - } + "type": "object" + } + }, + "text/csv": { + "schema": { + "type": "string" } } } @@ -1146,60 +2998,79 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, - "404": { - "$ref": "#/components/responses/NotFound" + "403": { + "$ref": "#/components/responses/Forbidden" } } - }, - "put": { + } + }, + "/api/agents/{id}/attribution": { + "get": { "tags": [ "Agents" ], - "summary": "Update agent soul config", - "operationId": "updateAgentSoul", + "summary": "Get attribution report for an agent", + "description": "Self-scope by default. Requester must match target agent (`x-agent-name` or username), unless admin uses `?privileged=1`.", + "operationId": "getAgentAttribution", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { - "type": "integer" + "type": "string" + } + }, + { + "name": "hours", + "in": "query", + "required": false, + "description": "Time window in hours, integer range 1..720. Defaults to 24.", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 720, + "default": 24 + } + }, + { + "name": "section", + "in": "query", + "required": false, + "description": "Comma-separated subset of identity,audit,mutations,cost. Defaults to all.", + "schema": { + "type": "string", + "example": "identity,audit" + } + }, + { + "name": "privileged", + "in": "query", + "required": false, + "description": "Set to 1 for admin override of self-scope checks.", + "schema": { + "type": "string", + "enum": [ + "1" + ] + } + }, + { + "name": "x-agent-name", + "in": "header", + "required": false, + "description": "Attribution identity header used for self-scope authorization.", + "schema": { + "type": "string" } } ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "soul_content" - ], - "properties": { - "soul_content": { - "type": "string" - } - } - } - } - } - }, "responses": { "200": { - "description": "Soul updated", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } + "description": "Attribution report" + }, + "400": { + "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" @@ -1213,26 +3084,16 @@ } } }, - "/api/agents/{id}/memory": { + "/api/gateway-config": { "get": { "tags": [ - "Agents" - ], - "summary": "Get agent memory", - "operationId": "getAgentMemory", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } + "Admin" ], + "summary": "Read gateway config", + "operationId": "getGatewayConfig", "responses": { "200": { - "description": "Agent memory data", + "description": "Gateway configuration", "content": { "application/json": { "schema": { @@ -1244,27 +3105,17 @@ "401": { "$ref": "#/components/responses/Unauthorized" }, - "404": { - "$ref": "#/components/responses/NotFound" + "403": { + "$ref": "#/components/responses/Forbidden" } } }, - "put": { + "post": { "tags": [ - "Agents" - ], - "summary": "Update agent memory", - "operationId": "updateAgentMemory", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } + "Admin" ], + "summary": "Update gateway config", + "operationId": "updateGatewayConfig", "requestBody": { "required": true, "content": { @@ -1275,6 +3126,559 @@ } } }, + "responses": { + "200": { + "description": "Config updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/gateways": { + "get": { + "tags": [ + "Admin" + ], + "summary": "List gateways", + "operationId": "listGateways", + "responses": { + "200": { + "description": "Gateway list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "gateways": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "status": { + "type": "string" + }, + "last_health_check": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "post": { + "tags": [ + "Admin" + ], + "summary": "Add gateway", + "operationId": "addGateway", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "url" + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Gateway added", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + }, + "put": { + "tags": [ + "Admin" + ], + "summary": "Update gateway", + "operationId": "updateGateway", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Gateway updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "delete": { + "tags": [ + "Admin" + ], + "summary": "Delete gateway", + "operationId": "deleteGateway", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Gateway deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/gateways/health": { + "post": { + "tags": [ + "Admin" + ], + "summary": "Probe gateway health", + "operationId": "probeGatewayHealth", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "gateway_id": { + "type": "integer" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Health check results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "healthy": { + "type": "boolean" + }, + "latency_ms": { + "type": "number" + }, + "details": { + "type": "object" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/github": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Get GitHub integration status", + "operationId": "getGithubStatus", + "responses": { + "200": { + "description": "GitHub integration status" + } + } + }, + "post": { + "tags": [ + "Admin" + ], + "summary": "Sync GitHub issues", + "operationId": "syncGithubIssues", + "responses": { + "200": { + "description": "Sync result" + } + } + } + }, + "/api/integrations": { + "get": { + "tags": [ + "Admin" + ], + "summary": "List integrations", + "operationId": "listIntegrations", + "responses": { + "200": { + "description": "Integration list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "integrations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "config": { + "type": "object" + } + } + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "post": { + "tags": [ + "Admin" + ], + "summary": "Integration actions (enable, disable, test, configure)", + "operationId": "integrationAction", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "action", + "id" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "enable", + "disable", + "test", + "configure" + ] + }, + "id": { + "type": "string" + }, + "config": { + "type": "object" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Action applied", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/logs": { + "get": { + "tags": [ + "Monitoring" + ], + "summary": "Get system logs", + "operationId": "getSystemLogs", + "parameters": [ + { + "name": "level", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "info", + "warn", + "error", + "debug" + ] + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 100 + } + } + ], + "responses": { + "200": { + "description": "Log entries", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "logs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "timestamp": { + "type": "string" + }, + "level": { + "type": "string" + }, + "message": { + "type": "string" + }, + "source": { + "type": "string" + } + } + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/memory": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Get memory files", + "operationId": "getMemoryFiles", + "parameters": [ + { + "name": "path", + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Memory file contents", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "post": { + "tags": [ + "Admin" + ], + "summary": "Update memory file", + "operationId": "updateMemoryFile", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "path", + "content" + ], + "properties": { + "path": { + "type": "string" + }, + "content": { + "type": "string" + } + } + } + } + } + }, "responses": { "200": { "description": "Memory updated", @@ -1296,37 +3700,167 @@ }, "403": { "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" } } } }, - "/api/agents/{id}/wake": { - "post": { + "/api/mentions": { + "get": { "tags": [ - "Agents" + "Mentions" ], - "summary": "Wake an agent", - "operationId": "wakeAgent", + "summary": "Get mention autocomplete targets", + "operationId": "getMentionTargets", "parameters": [ { - "name": "id", - "in": "path", - "required": true, + "name": "q", + "in": "query", "schema": { - "type": "integer" + "type": "string" + }, + "description": "Search query" + }, + { + "name": "type", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "user", + "agent" + ] + }, + "description": "Filter by type" + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 20 + }, + "description": "Max results" + } + ], + "responses": { + "200": { + "description": "Mention targets list" + } + } + } + }, + "/api/notifications": { + "get": { + "tags": [ + "Monitoring" + ], + "summary": "List notifications", + "operationId": "listNotifications", + "parameters": [ + { + "name": "recipient", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "unread", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 50 } } ], + "responses": { + "200": { + "description": "Notification list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "notifications": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "recipient": { + "type": "string" + }, + "type": { + "type": "string" + }, + "title": { + "type": "string" + }, + "message": { + "type": "string" + }, + "read": { + "type": "boolean" + }, + "source_type": { + "type": "string" + }, + "source_id": { + "type": "integer" + }, + "created_at": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "post": { + "tags": [ + "Monitoring" + ], + "summary": "Notification actions (mark read, dismiss)", + "operationId": "notificationAction", "requestBody": { + "required": true, "content": { "application/json": { "schema": { "type": "object", + "required": [ + "action" + ], "properties": { - "reason": { + "action": { + "type": "string", + "enum": [ + "mark_read", + "mark_all_read", + "dismiss" + ] + }, + "id": { + "type": "integer" + }, + "recipient": { "type": "string" } } @@ -1336,7 +3870,7 @@ }, "responses": { "200": { - "description": "Agent woken", + "description": "Action applied", "content": { "application/json": { "schema": { @@ -1350,6 +3884,123 @@ } } }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + } + }, + "/api/agents/{id}/diagnostics": { + "get": { + "tags": [ + "Agents" + ], + "summary": "Get self diagnostics for an agent", + "description": "Self-scoped diagnostics by default. Cross-agent access requires `privileged=1` with admin credentials. Trend alerts are informational signals derived from current-vs-previous window deltas (error spikes, throughput drops, activity stalls).", + "operationId": "getAgentDiagnostics", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "Agent numeric ID or name." + }, + { + "name": "hours", + "in": "query", + "required": false, + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 720, + "default": 24 + }, + "description": "Diagnostics window in hours." + }, + { + "name": "section", + "in": "query", + "required": false, + "schema": { + "type": "string" + }, + "description": "Comma-separated sections: summary,tasks,errors,activity,trends,tokens." + }, + { + "name": "privileged", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "1" + ] + }, + "description": "Set to `1` to allow explicit admin cross-agent diagnostics access." + } + ], + "responses": { + "200": { + "description": "Diagnostics payload", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agent": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "role": { "type": "string" }, + "status": { "type": "string" } + } + }, + "timeframe": { + "type": "object", + "properties": { + "hours": { "type": "integer" }, + "since": { "type": "integer" }, + "until": { "type": "integer" } + } + }, + "summary": { "type": "object" }, + "tasks": { "type": "object" }, + "errors": { "type": "object" }, + "activity": { "type": "object" }, + "trends": { + "type": "object", + "properties": { + "current_period": { "type": "object" }, + "previous_period": { "type": "object" }, + "change": { "type": "object" }, + "alerts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "level": { "type": "string", "enum": ["info", "warning"] }, + "message": { "type": "string" } + } + } + } + } + }, + "tokens": { "type": "object" } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, "401": { "$ref": "#/components/responses/Unauthorized" }, @@ -1362,13 +4013,13 @@ } } }, - "/api/agents/message": { + "/api/notifications/deliver": { "post": { "tags": [ - "Agents" + "Monitoring" ], - "summary": "Send message between agents", - "operationId": "sendAgentMessage", + "summary": "Deliver notification to agent", + "operationId": "deliverNotification", "requestBody": { "required": true, "content": { @@ -1376,18 +4027,18 @@ "schema": { "type": "object", "required": [ - "from", - "to", - "content" + "agent", + "title", + "message" ], "properties": { - "from": { + "agent": { "type": "string" }, - "to": { + "title": { "type": "string" }, - "content": { + "message": { "type": "string" }, "type": { @@ -1400,7 +4051,7 @@ }, "responses": { "200": { - "description": "Message sent", + "description": "Notification delivered", "content": { "application/json": { "schema": { @@ -1408,9 +4059,6 @@ "properties": { "success": { "type": "boolean" - }, - "id": { - "type": "integer" } } } @@ -1429,13 +4077,793 @@ } } }, - "/api/agents/comms": { + "/api/pipelines": { "get": { "tags": [ - "Agents" + "Pipelines" ], - "summary": "Get agent communications", - "operationId": "getAgentComms", + "summary": "List pipelines", + "operationId": "listPipelines", + "responses": { + "200": { + "description": "Pipeline list with enriched step data and run counts", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pipelines": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "steps": { + "type": "array", + "items": { + "type": "object", + "properties": { + "template_id": { + "type": "integer" + }, + "template_name": { + "type": "string" + }, + "on_failure": { + "type": "string", + "enum": [ + "stop", + "continue" + ] + } + } + } + }, + "created_by": { + "type": "string" + }, + "use_count": { + "type": "integer" + }, + "runs": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "completed": { + "type": "integer" + }, + "failed": { + "type": "integer" + }, + "running": { + "type": "integer" + } + } + }, + "created_at": { + "type": "integer" + }, + "updated_at": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + }, + "post": { + "tags": [ + "Pipelines" + ], + "summary": "Create pipeline", + "operationId": "createPipeline", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name", + "steps" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "steps": { + "type": "array", + "items": { + "type": "object", + "required": [ + "template_id" + ], + "properties": { + "template_id": { + "type": "integer" + }, + "on_failure": { + "type": "string", + "enum": [ + "stop", + "continue" + ], + "default": "stop" + } + } + } + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Pipeline created", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pipeline": { + "type": "object" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "429": { + "$ref": "#/components/responses/RateLimited" + } + } + }, + "put": { + "tags": [ + "Pipelines" + ], + "summary": "Update pipeline", + "operationId": "updatePipeline", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "steps": { + "type": "array", + "items": { + "type": "object" + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Pipeline updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "pipeline": { + "type": "object" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + }, + "delete": { + "tags": [ + "Pipelines" + ], + "summary": "Delete pipeline", + "operationId": "deletePipeline", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "integer" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Pipeline deleted", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/pipelines/run": { + "post": { + "tags": [ + "Pipelines" + ], + "summary": "Run a pipeline", + "operationId": "runPipeline", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "pipeline_id" + ], + "properties": { + "pipeline_id": { + "type": "integer" + }, + "params": { + "type": "object" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Pipeline run started", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "run_id": { + "type": "integer" + }, + "status": { + "type": "string" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/api/projects": { + "get": { + "tags": [ + "Projects" + ], + "summary": "List all projects", + "operationId": "listProjects", + "responses": { + "200": { + "description": "Project list" + } + } + }, + "post": { + "tags": [ + "Projects" + ], + "summary": "Create a new project", + "operationId": "createProject", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Project created" + } + } + } + }, + "/api/projects/{id}": { + "get": { + "tags": [ + "Projects" + ], + "summary": "Get project by ID", + "operationId": "getProject", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Project details" + } + } + }, + "patch": { + "tags": [ + "Projects" + ], + "summary": "Update a project", + "operationId": "updateProject", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Updated" + } + } + }, + "delete": { + "tags": [ + "Projects" + ], + "summary": "Delete a project", + "operationId": "deleteProject", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Deleted" + } + } + } + }, + "/api/projects/{id}/tasks": { + "get": { + "tags": [ + "Projects" + ], + "summary": "List tasks in a project", + "operationId": "listProjectTasks", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Tasks in project" + } + } + } + }, + "/api/quality-review": { + "get": { + "tags": [ + "Quality" + ], + "summary": "List quality reviews", + "operationId": "listQualityReviews", + "parameters": [ + { + "name": "task_id", + "in": "query", + "schema": { + "type": "integer" + }, + "description": "Filter by task" + } + ], + "responses": { + "200": { + "description": "Quality review list" + } + } + }, + "post": { + "tags": [ + "Quality" + ], + "summary": "Submit a quality review", + "operationId": "submitQualityReview", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "task_id", + "status" + ], + "properties": { + "task_id": { + "type": "integer" + }, + "status": { + "type": "string", + "enum": [ + "approved", + "rejected" + ] + }, + "notes": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Review submitted" + } + } + } + }, + "/api/releases/check": { + "get": { + "tags": [ + "Releases" + ], + "summary": "Check for new releases", + "operationId": "checkReleases", + "security": [], + "responses": { + "200": { + "description": "Release info with version comparison" + } + } + } + }, + "/api/scheduler": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Get scheduler status", + "operationId": "getSchedulerStatus", + "responses": { + "200": { + "description": "Scheduler status and registered tasks", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "running": { + "type": "boolean" + }, + "tasks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "last_run": { + "type": "integer" + }, + "next_run": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + }, + "post": { + "tags": [ + "Admin" + ], + "summary": "Trigger scheduled task", + "operationId": "triggerScheduledTask", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "task" + ], + "properties": { + "task": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Task triggered", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "result": { + "type": "object" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/search": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Full-text search", + "operationId": "search", + "parameters": [ + { + "name": "q", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "schema": { + "type": "string", + "enum": [ + "tasks", + "agents", + "activities", + "all" + ] + } + }, + { + "name": "limit", + "in": "query", + "schema": { + "type": "integer", + "default": 20 + } + } + ], + "responses": { + "200": { + "description": "Search results", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "snippet": { + "type": "string" + }, + "score": { + "type": "number" + } + } + } + }, + "total": { + "type": "integer" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + } + }, + "/api/sessions": { + "get": { + "tags": [ + "Sessions" + ], + "summary": "List gateway sessions", + "operationId": "listSessions", "parameters": [ { "name": "agent", @@ -1455,33 +4883,33 @@ ], "responses": { "200": { - "description": "Agent communication log", + "description": "Session list", "content": { "application/json": { "schema": { "type": "object", "properties": { - "messages": { + "sessions": { "type": "array", "items": { "type": "object", "properties": { - "id": { + "key": { + "type": "string" + }, + "agent": { + "type": "string" + }, + "model": { + "type": "string" + }, + "status": { + "type": "string" + }, + "totalTokens": { "type": "integer" }, - "from_agent": { - "type": "string" - }, - "to_agent": { - "type": "string" - }, - "content": { - "type": "string" - }, - "type": { - "type": "string" - }, - "created_at": { + "updatedAt": { "type": "integer" } } @@ -1498,29 +4926,94 @@ } } }, - "/api/agents/sync": { + "/api/sessions/{id}/control": { "post": { "tags": [ - "Agents" + "Sessions" ], - "summary": "Sync agents from gateway config", - "operationId": "syncAgents", + "summary": "Control session (pause/resume/kill)", + "operationId": "controlSession", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "action" + ], + "properties": { + "action": { + "type": "string", + "enum": [ + "pause", + "resume", + "kill" + ] + } + } + } + } + } + }, "responses": { "200": { - "description": "Sync results", + "description": "Action applied", "content": { "application/json": { "schema": { "type": "object", "properties": { - "synced": { - "type": "integer" - }, - "created": { - "type": "integer" - }, - "updated": { - "type": "integer" + "success": { + "type": "boolean" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/api/settings": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Get application settings", + "operationId": "getSettings", + "responses": { + "200": { + "description": "Current settings", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "settings": { + "type": "object" } } } @@ -1534,6 +5027,596 @@ "$ref": "#/components/responses/Forbidden" } } + }, + "post": { + "tags": [ + "Admin" + ], + "summary": "Update settings", + "operationId": "updateSettings", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Settings updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/spawn": { + "post": { + "tags": [ + "Admin" + ], + "summary": "Spawn agent process", + "operationId": "spawnAgent", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "agent" + ], + "properties": { + "agent": { + "type": "string" + }, + "task": { + "type": "string" + }, + "params": { + "type": "object" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Agent spawned", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "session_id": { + "type": "string" + } + } + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/standup": { + "get": { + "tags": [ + "Admin" + ], + "summary": "Get standup report", + "operationId": "getStandupReport", + "parameters": [ + { + "name": "date", + "in": "query", + "schema": { + "type": "string", + "format": "date" + } + } + ], + "responses": { + "200": { + "description": "Standup report", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "date": { + "type": "string" + }, + "agents": { + "type": "array", + "items": { + "type": "object" + } + }, + "tasks_completed": { + "type": "integer" + }, + "tasks_in_progress": { + "type": "integer" + }, + "highlights": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + } + } + } + }, + "/api/status": { + "get": { + "tags": [ + "Monitoring" + ], + "summary": "Get system status", + "operationId": "getSystemStatus", + "security": [], + "responses": { + "200": { + "description": "System health status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": [ + "ok", + "degraded", + "down" + ] + }, + "version": { + "type": "string" + }, + "uptime": { + "type": "integer" + }, + "agents": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "online": { + "type": "integer" + } + } + }, + "tasks": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "in_progress": { + "type": "integer" + } + } + } + } + } + } + } + } + } + } + }, + "/api/super/provision-jobs": { + "get": { + "tags": [ + "Super Admin" + ], + "summary": "List provision jobs", + "operationId": "listProvisionJobs", + "responses": { + "200": { + "description": "Provision job list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jobs": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "tenant_id": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "status": { + "type": "string", + "enum": [ + "pending", + "running", + "completed", + "failed" + ] + }, + "output": { + "type": "string" + }, + "created_at": { + "type": "integer" + }, + "completed_at": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + }, + "post": { + "tags": [ + "Super Admin" + ], + "summary": "Create provision job", + "operationId": "createProvisionJob", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "tenant_id", + "type" + ], + "properties": { + "tenant_id": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "params": { + "type": "object" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Job created", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + } + }, + "/api/super/provision-jobs/{id}": { + "get": { + "tags": [ + "Super Admin" + ], + "summary": "Get provision job details", + "operationId": "getProvisionJob", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Job details", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/api/super/provision-jobs/{id}/run": { + "post": { + "tags": [ + "Super Admin" + ], + "summary": "Run provision job", + "operationId": "runProvisionJob", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Job started", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "status": { + "type": "string" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, + "/api/super/tenants": { + "get": { + "tags": [ + "Super Admin" + ], + "summary": "List tenants", + "operationId": "listTenants", + "responses": { + "200": { + "description": "Tenant list", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "tenants": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "linux_user": { + "type": "string" + }, + "created_at": { + "type": "integer" + } + } + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + } + } + }, + "post": { + "tags": [ + "Super Admin" + ], + "summary": "Create tenant and bootstrap job", + "operationId": "createTenant", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "slug", + "name" + ], + "properties": { + "slug": { + "type": "string" + }, + "name": { + "type": "string" + }, + "linux_user": { + "type": "string" + }, + "config": { + "type": "object" + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Tenant created with bootstrap job", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "400": { + "$ref": "#/components/responses/BadRequest" + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "409": { + "description": "Tenant slug or linux user already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + } + } + }, + "/api/super/tenants/{id}/decommission": { + "post": { + "tags": [ + "Super Admin" + ], + "summary": "Decommission tenant", + "operationId": "decommissionTenant", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Tenant decommissioned", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } } }, "/api/tasks": { @@ -1991,6 +6074,77 @@ } } }, + "/api/tasks/{id}/broadcast": { + "post": { + "tags": [ + "Tasks" + ], + "summary": "Broadcast task to agents", + "operationId": "broadcastTask", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "agents": { + "type": "array", + "items": { + "type": "string" + } + }, + "message": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Task broadcast sent", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "delivered_to": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/Unauthorized" + }, + "403": { + "$ref": "#/components/responses/Forbidden" + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + } + } + }, "/api/tasks/{id}/comments": { "get": { "tags": [ @@ -2120,337 +6274,6 @@ } } }, - "/api/tasks/{id}/broadcast": { - "post": { - "tags": [ - "Tasks" - ], - "summary": "Broadcast task to agents", - "operationId": "broadcastTask", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "agents": { - "type": "array", - "items": { - "type": "string" - } - }, - "message": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Task broadcast sent", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "delivered_to": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - } - }, - "/api/chat/conversations": { - "get": { - "tags": [ - "Chat" - ], - "summary": "List conversations", - "operationId": "listConversations", - "responses": { - "200": { - "description": "Conversation list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "conversations": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "participants": { - "type": "array", - "items": { - "type": "string" - } - }, - "last_message": { - "type": "string" - }, - "updated_at": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - } - }, - "/api/chat/messages": { - "get": { - "tags": [ - "Chat" - ], - "summary": "List messages", - "operationId": "listMessages", - "parameters": [ - { - "name": "conversation_id", - "in": "query", - "schema": { - "type": "integer" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "default": 50 - } - }, - { - "name": "offset", - "in": "query", - "schema": { - "type": "integer", - "default": 0 - } - } - ], - "responses": { - "200": { - "description": "Message list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "messages": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "conversation_id": { - "type": "integer" - }, - "sender": { - "type": "string" - }, - "content": { - "type": "string" - }, - "created_at": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - }, - "post": { - "tags": [ - "Chat" - ], - "summary": "Send message", - "operationId": "sendMessage", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "content" - ], - "properties": { - "conversation_id": { - "type": "integer" - }, - "content": { - "type": "string" - }, - "recipient": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Message sent", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "object" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/chat/messages/{id}": { - "get": { - "tags": [ - "Chat" - ], - "summary": "Get message by ID", - "operationId": "getMessage", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Message details", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - }, - "delete": { - "tags": [ - "Chat" - ], - "summary": "Delete message", - "operationId": "deleteMessage", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Message deleted", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - } - }, "/api/tokens": { "get": { "tags": [ @@ -2677,146 +6500,6 @@ } } }, - "/api/sessions": { - "get": { - "tags": [ - "Sessions" - ], - "summary": "List gateway sessions", - "operationId": "listSessions", - "parameters": [ - { - "name": "agent", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "default": 50 - } - } - ], - "responses": { - "200": { - "description": "Session list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "sessions": { - "type": "array", - "items": { - "type": "object", - "properties": { - "key": { - "type": "string" - }, - "agent": { - "type": "string" - }, - "model": { - "type": "string" - }, - "status": { - "type": "string" - }, - "totalTokens": { - "type": "integer" - }, - "updatedAt": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - } - }, - "/api/sessions/{id}/control": { - "post": { - "tags": [ - "Sessions" - ], - "summary": "Control session (pause/resume/kill)", - "operationId": "controlSession", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "action" - ], - "properties": { - "action": { - "type": "string", - "enum": [ - "pause", - "resume", - "kill" - ] - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Action applied", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - } - }, "/api/webhooks": { "get": { "tags": [ @@ -3157,6 +6840,38 @@ } } }, + "/api/webhooks/retry": { + "post": { + "tags": [ + "Webhooks" + ], + "summary": "Retry a failed webhook delivery", + "operationId": "retryWebhookDelivery", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "delivery_id" + ], + "properties": { + "delivery_id": { + "type": "integer" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Delivery retried" + } + } + } + }, "/api/webhooks/test": { "post": { "tags": [ @@ -3216,331 +6931,16 @@ } } }, - "/api/alerts": { + "/api/webhooks/verify-docs": { "get": { "tags": [ - "Alerts" + "Webhooks" ], - "summary": "List alert rules", - "operationId": "listAlertRules", + "summary": "Get webhook verification documentation", + "operationId": "getWebhookVerifyDocs", "responses": { "200": { - "description": "Alert rule list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "rules": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AlertRule" - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - }, - "post": { - "tags": [ - "Alerts" - ], - "summary": "Create alert rule or evaluate rules", - "operationId": "createAlertRule", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "oneOf": [ - { - "type": "object", - "title": "CreateRule", - "required": [ - "name", - "entity_type", - "condition_field", - "condition_operator", - "condition_value" - ], - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "entity_type": { - "type": "string", - "enum": [ - "agent", - "task", - "session", - "activity" - ] - }, - "condition_field": { - "type": "string" - }, - "condition_operator": { - "type": "string", - "enum": [ - "equals", - "not_equals", - "greater_than", - "less_than", - "contains", - "count_above", - "count_below", - "age_minutes_above" - ] - }, - "condition_value": { - "type": "string" - }, - "action_type": { - "type": "string", - "default": "notification" - }, - "action_config": { - "type": "object" - }, - "cooldown_minutes": { - "type": "integer", - "default": 60 - } - } - }, - { - "type": "object", - "title": "EvaluateRules", - "required": [ - "action" - ], - "properties": { - "action": { - "type": "string", - "const": "evaluate" - } - } - } - ] - } - } - } - }, - "responses": { - "201": { - "description": "Rule created", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "rule": { - "$ref": "#/components/schemas/AlertRule" - } - } - } - } - } - }, - "200": { - "description": "Rules evaluated", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "evaluated": { - "type": "integer" - }, - "triggered": { - "type": "integer" - }, - "results": { - "type": "array", - "items": { - "type": "object", - "properties": { - "rule_id": { - "type": "integer" - }, - "rule_name": { - "type": "string" - }, - "triggered": { - "type": "boolean" - }, - "reason": { - "type": "string" - } - } - } - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - } - }, - "put": { - "tags": [ - "Alerts" - ], - "summary": "Update alert rule", - "operationId": "updateAlertRule", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "entity_type": { - "type": "string" - }, - "condition_field": { - "type": "string" - }, - "condition_operator": { - "type": "string" - }, - "condition_value": { - "type": "string" - }, - "action_type": { - "type": "string" - }, - "action_config": { - "type": "object" - }, - "cooldown_minutes": { - "type": "integer" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Rule updated", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "rule": { - "$ref": "#/components/schemas/AlertRule" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - } - }, - "delete": { - "tags": [ - "Alerts" - ], - "summary": "Delete alert rule", - "operationId": "deleteAlertRule", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "type": "integer" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Rule deleted", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "deleted": { - "type": "boolean" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "429": { - "$ref": "#/components/responses/RateLimited" + "description": "Verification guide and examples" } } } @@ -3797,2771 +7197,6 @@ } } } - }, - "/api/pipelines": { - "get": { - "tags": [ - "Pipelines" - ], - "summary": "List pipelines", - "operationId": "listPipelines", - "responses": { - "200": { - "description": "Pipeline list with enriched step data and run counts", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "pipelines": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "steps": { - "type": "array", - "items": { - "type": "object", - "properties": { - "template_id": { - "type": "integer" - }, - "template_name": { - "type": "string" - }, - "on_failure": { - "type": "string", - "enum": [ - "stop", - "continue" - ] - } - } - } - }, - "created_by": { - "type": "string" - }, - "use_count": { - "type": "integer" - }, - "runs": { - "type": "object", - "properties": { - "total": { - "type": "integer" - }, - "completed": { - "type": "integer" - }, - "failed": { - "type": "integer" - }, - "running": { - "type": "integer" - } - } - }, - "created_at": { - "type": "integer" - }, - "updated_at": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - }, - "post": { - "tags": [ - "Pipelines" - ], - "summary": "Create pipeline", - "operationId": "createPipeline", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "name", - "steps" - ], - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "steps": { - "type": "array", - "items": { - "type": "object", - "required": [ - "template_id" - ], - "properties": { - "template_id": { - "type": "integer" - }, - "on_failure": { - "type": "string", - "enum": [ - "stop", - "continue" - ], - "default": "stop" - } - } - } - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Pipeline created", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "pipeline": { - "type": "object" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "429": { - "$ref": "#/components/responses/RateLimited" - } - } - }, - "put": { - "tags": [ - "Pipelines" - ], - "summary": "Update pipeline", - "operationId": "updatePipeline", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "steps": { - "type": "array", - "items": { - "type": "object" - } - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Pipeline updated", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "pipeline": { - "type": "object" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - }, - "delete": { - "tags": [ - "Pipelines" - ], - "summary": "Delete pipeline", - "operationId": "deletePipeline", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "type": "integer" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Pipeline deleted", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/pipelines/run": { - "post": { - "tags": [ - "Pipelines" - ], - "summary": "Run a pipeline", - "operationId": "runPipeline", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "pipeline_id" - ], - "properties": { - "pipeline_id": { - "type": "integer" - }, - "params": { - "type": "object" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Pipeline run started", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "run_id": { - "type": "integer" - }, - "status": { - "type": "string" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - } - }, - "/api/activities": { - "get": { - "tags": [ - "Monitoring" - ], - "summary": "List activities", - "operationId": "listActivities", - "parameters": [ - { - "name": "type", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "actor", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "entity_type", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "default": 50 - } - }, - { - "name": "offset", - "in": "query", - "schema": { - "type": "integer", - "default": 0 - } - } - ], - "responses": { - "200": { - "description": "Activity list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "activities": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "type": { - "type": "string" - }, - "entity_type": { - "type": "string" - }, - "entity_id": { - "type": "integer" - }, - "actor": { - "type": "string" - }, - "description": { - "type": "string" - }, - "metadata": { - "type": "object" - }, - "created_at": { - "type": "integer" - } - } - } - }, - "total": { - "type": "integer" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - } - }, - "/api/audit": { - "get": { - "tags": [ - "Monitoring" - ], - "summary": "Get audit log", - "operationId": "getAuditLog", - "parameters": [ - { - "name": "action", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "actor", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "default": 100 - } - }, - { - "name": "offset", - "in": "query", - "schema": { - "type": "integer", - "default": 0 - } - } - ], - "responses": { - "200": { - "description": "Audit log entries", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "entries": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "action": { - "type": "string" - }, - "actor": { - "type": "string" - }, - "target_type": { - "type": "string" - }, - "target_id": { - "type": "integer" - }, - "detail": { - "type": "object" - }, - "ip_address": { - "type": "string" - }, - "created_at": { - "type": "integer" - } - } - } - }, - "total": { - "type": "integer" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/logs": { - "get": { - "tags": [ - "Monitoring" - ], - "summary": "Get system logs", - "operationId": "getSystemLogs", - "parameters": [ - { - "name": "level", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "info", - "warn", - "error", - "debug" - ] - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "default": 100 - } - } - ], - "responses": { - "200": { - "description": "Log entries", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "logs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "timestamp": { - "type": "string" - }, - "level": { - "type": "string" - }, - "message": { - "type": "string" - }, - "source": { - "type": "string" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/notifications": { - "get": { - "tags": [ - "Monitoring" - ], - "summary": "List notifications", - "operationId": "listNotifications", - "parameters": [ - { - "name": "recipient", - "in": "query", - "schema": { - "type": "string" - } - }, - { - "name": "unread", - "in": "query", - "schema": { - "type": "boolean" - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "default": 50 - } - } - ], - "responses": { - "200": { - "description": "Notification list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "notifications": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "recipient": { - "type": "string" - }, - "type": { - "type": "string" - }, - "title": { - "type": "string" - }, - "message": { - "type": "string" - }, - "read": { - "type": "boolean" - }, - "source_type": { - "type": "string" - }, - "source_id": { - "type": "integer" - }, - "created_at": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - }, - "post": { - "tags": [ - "Monitoring" - ], - "summary": "Notification actions (mark read, dismiss)", - "operationId": "notificationAction", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "action" - ], - "properties": { - "action": { - "type": "string", - "enum": [ - "mark_read", - "mark_all_read", - "dismiss" - ] - }, - "id": { - "type": "integer" - }, - "recipient": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Action applied", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - } - }, - "/api/notifications/deliver": { - "post": { - "tags": [ - "Monitoring" - ], - "summary": "Deliver notification to agent", - "operationId": "deliverNotification", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "agent", - "title", - "message" - ], - "properties": { - "agent": { - "type": "string" - }, - "title": { - "type": "string" - }, - "message": { - "type": "string" - }, - "type": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Notification delivered", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/workload": { - "get": { - "tags": [ - "Monitoring" - ], - "summary": "Get real-time workload recommendation", - "description": "Returns system workload metrics and an actionable recommendation: `normal`, `throttle`, `shed`, or `pause`. Thresholds are runtime-configurable via `MC_WORKLOAD_*` environment variables.", - "operationId": "getWorkloadSignals", - "responses": { - "200": { - "description": "Workload snapshot and recommendation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "timestamp": { "type": "integer" }, - "workspace_id": { "type": "integer" }, - "capacity": { - "type": "object", - "properties": { - "active_tasks": { "type": "integer" }, - "tasks_last_5m": { "type": "integer" }, - "errors_last_5m": { "type": "integer" }, - "error_rate_5m": { "type": "number", "minimum": 0, "maximum": 1 }, - "completions_last_hour": { "type": "integer" }, - "avg_completion_rate_per_hour": { "type": "number" } - } - }, - "queue": { - "type": "object", - "properties": { - "total_pending": { "type": "integer" }, - "by_status": { "type": "object", "additionalProperties": { "type": "integer" } }, - "by_priority": { "type": "object", "additionalProperties": { "type": "integer" } }, - "oldest_pending_age_seconds": { "type": ["integer", "null"] }, - "estimated_wait_seconds": { "type": ["integer", "null"] }, - "estimated_wait_confidence": { "type": "string", "enum": ["calculated", "unknown"] } - } - }, - "agents": { - "type": "object", - "properties": { - "total": { "type": "integer" }, - "online": { "type": "integer" }, - "busy": { "type": "integer" }, - "idle": { "type": "integer" }, - "offline": { "type": "integer" }, - "busy_ratio": { "type": "number", "minimum": 0, "maximum": 1 }, - "load_distribution": { - "type": "array", - "items": { - "type": "object", - "properties": { - "agent": { "type": "string" }, - "assigned": { "type": "integer" }, - "in_progress": { "type": "integer" } - } - } - } - } - }, - "recommendation": { - "type": "object", - "properties": { - "action": { "type": "string", "enum": ["normal", "throttle", "shed", "pause"] }, - "reason": { "type": "string" }, - "details": { "type": "array", "items": { "type": "string" } }, - "submit_ok": { "type": "boolean" }, - "suggested_delay_ms": { "type": "integer" } - } - }, - "thresholds": { - "type": "object", - "description": "Effective runtime thresholds after environment overrides." - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - } - }, - "/api/events": { - "get": { - "tags": [ - "Monitoring" - ], - "summary": "SSE stream for real-time events", - "operationId": "getEventStream", - "responses": { - "200": { - "description": "Server-Sent Events stream", - "content": { - "text/event-stream": { - "schema": { - "type": "string" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - } - }, - "/api/status": { - "get": { - "tags": [ - "Monitoring" - ], - "summary": "Get system status", - "operationId": "getSystemStatus", - "security": [], - "responses": { - "200": { - "description": "System health status", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "ok", - "degraded", - "down" - ] - }, - "version": { - "type": "string" - }, - "uptime": { - "type": "integer" - }, - "agents": { - "type": "object", - "properties": { - "total": { - "type": "integer" - }, - "online": { - "type": "integer" - } - } - }, - "tasks": { - "type": "object", - "properties": { - "total": { - "type": "integer" - }, - "in_progress": { - "type": "integer" - } - } - } - } - } - } - } - } - } - } - }, - "/api/settings": { - "get": { - "tags": [ - "Admin" - ], - "summary": "Get application settings", - "operationId": "getSettings", - "responses": { - "200": { - "description": "Current settings", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "settings": { - "type": "object" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - }, - "post": { - "tags": [ - "Admin" - ], - "summary": "Update settings", - "operationId": "updateSettings", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "responses": { - "200": { - "description": "Settings updated", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/scheduler": { - "get": { - "tags": [ - "Admin" - ], - "summary": "Get scheduler status", - "operationId": "getSchedulerStatus", - "responses": { - "200": { - "description": "Scheduler status and registered tasks", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "running": { - "type": "boolean" - }, - "tasks": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "interval": { - "type": "string" - }, - "last_run": { - "type": "integer" - }, - "next_run": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - }, - "post": { - "tags": [ - "Admin" - ], - "summary": "Trigger scheduled task", - "operationId": "triggerScheduledTask", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "task" - ], - "properties": { - "task": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Task triggered", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "result": { - "type": "object" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/cron": { - "get": { - "tags": [ - "Admin" - ], - "summary": "Get cron jobs", - "operationId": "getCronJobs", - "parameters": [ - { - "name": "action", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "list", - "logs" - ], - "default": "list" - } - } - ], - "responses": { - "200": { - "description": "Cron job list or logs", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "jobs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "schedule": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "last_run": { - "type": "integer" - }, - "last_status": { - "type": "string" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - }, - "post": { - "tags": [ - "Admin" - ], - "summary": "Manage cron jobs", - "operationId": "manageCronJobs", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "action" - ], - "properties": { - "action": { - "type": "string", - "enum": [ - "toggle", - "trigger", - "add", - "remove" - ] - }, - "name": { - "type": "string" - }, - "schedule": { - "type": "string" - }, - "command": { - "type": "string" - }, - "enabled": { - "type": "boolean" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Action applied", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/gateways": { - "get": { - "tags": [ - "Admin" - ], - "summary": "List gateways", - "operationId": "listGateways", - "responses": { - "200": { - "description": "Gateway list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "gateways": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "url": { - "type": "string" - }, - "status": { - "type": "string" - }, - "last_health_check": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - }, - "post": { - "tags": [ - "Admin" - ], - "summary": "Add gateway", - "operationId": "addGateway", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "name", - "url" - ], - "properties": { - "name": { - "type": "string" - }, - "url": { - "type": "string", - "format": "uri" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Gateway added", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - }, - "put": { - "tags": [ - "Admin" - ], - "summary": "Update gateway", - "operationId": "updateGateway", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - }, - "url": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Gateway updated", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - }, - "delete": { - "tags": [ - "Admin" - ], - "summary": "Delete gateway", - "operationId": "deleteGateway", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "id" - ], - "properties": { - "id": { - "type": "integer" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Gateway deleted", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/gateways/health": { - "post": { - "tags": [ - "Admin" - ], - "summary": "Probe gateway health", - "operationId": "probeGatewayHealth", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "gateway_id": { - "type": "integer" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Health check results", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "healthy": { - "type": "boolean" - }, - "latency_ms": { - "type": "number" - }, - "details": { - "type": "object" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/gateway-config": { - "get": { - "tags": [ - "Admin" - ], - "summary": "Read gateway config", - "operationId": "getGatewayConfig", - "responses": { - "200": { - "description": "Gateway configuration", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - }, - "post": { - "tags": [ - "Admin" - ], - "summary": "Update gateway config", - "operationId": "updateGatewayConfig", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "responses": { - "200": { - "description": "Config updated", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/integrations": { - "get": { - "tags": [ - "Admin" - ], - "summary": "List integrations", - "operationId": "listIntegrations", - "responses": { - "200": { - "description": "Integration list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "integrations": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "enabled": { - "type": "boolean" - }, - "config": { - "type": "object" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - }, - "post": { - "tags": [ - "Admin" - ], - "summary": "Integration actions (enable, disable, test, configure)", - "operationId": "integrationAction", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "action", - "id" - ], - "properties": { - "action": { - "type": "string", - "enum": [ - "enable", - "disable", - "test", - "configure" - ] - }, - "id": { - "type": "string" - }, - "config": { - "type": "object" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Action applied", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/spawn": { - "post": { - "tags": [ - "Admin" - ], - "summary": "Spawn agent process", - "operationId": "spawnAgent", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "agent" - ], - "properties": { - "agent": { - "type": "string" - }, - "task": { - "type": "string" - }, - "params": { - "type": "object" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Agent spawned", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "session_id": { - "type": "string" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/standup": { - "get": { - "tags": [ - "Admin" - ], - "summary": "Get standup report", - "operationId": "getStandupReport", - "parameters": [ - { - "name": "date", - "in": "query", - "schema": { - "type": "string", - "format": "date" - } - } - ], - "responses": { - "200": { - "description": "Standup report", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "date": { - "type": "string" - }, - "agents": { - "type": "array", - "items": { - "type": "object" - } - }, - "tasks_completed": { - "type": "integer" - }, - "tasks_in_progress": { - "type": "integer" - }, - "highlights": { - "type": "array", - "items": { - "type": "string" - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - } - }, - "/api/memory": { - "get": { - "tags": [ - "Admin" - ], - "summary": "Get memory files", - "operationId": "getMemoryFiles", - "parameters": [ - { - "name": "path", - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Memory file contents", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - }, - "post": { - "tags": [ - "Admin" - ], - "summary": "Update memory file", - "operationId": "updateMemoryFile", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "path", - "content" - ], - "properties": { - "path": { - "type": "string" - }, - "content": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Memory updated", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/search": { - "get": { - "tags": [ - "Admin" - ], - "summary": "Full-text search", - "operationId": "search", - "parameters": [ - { - "name": "q", - "in": "query", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "type", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "tasks", - "agents", - "activities", - "all" - ] - } - }, - { - "name": "limit", - "in": "query", - "schema": { - "type": "integer", - "default": 20 - } - } - ], - "responses": { - "200": { - "description": "Search results", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "results": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "title": { - "type": "string" - }, - "snippet": { - "type": "string" - }, - "score": { - "type": "number" - } - } - } - }, - "total": { - "type": "integer" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - } - }, - "/api/backup": { - "post": { - "tags": [ - "Admin" - ], - "summary": "Trigger backup", - "operationId": "triggerBackup", - "responses": { - "200": { - "description": "Backup completed", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "path": { - "type": "string" - }, - "size_bytes": { - "type": "integer" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/cleanup": { - "post": { - "tags": [ - "Admin" - ], - "summary": "Trigger cleanup of old data", - "operationId": "triggerCleanup", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "older_than_days": { - "type": "integer", - "default": 30 - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Cleanup results", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "deleted": { - "type": "object" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/export": { - "get": { - "tags": [ - "Admin" - ], - "summary": "Export data", - "operationId": "exportData", - "parameters": [ - { - "name": "type", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "tasks", - "agents", - "activities", - "all" - ] - } - }, - { - "name": "format", - "in": "query", - "schema": { - "type": "string", - "enum": [ - "json", - "csv" - ], - "default": "json" - } - } - ], - "responses": { - "200": { - "description": "Exported data", - "content": { - "application/json": { - "schema": { - "type": "object" - } - }, - "text/csv": { - "schema": { - "type": "string" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/super/tenants": { - "get": { - "tags": [ - "Super Admin" - ], - "summary": "List tenants", - "operationId": "listTenants", - "responses": { - "200": { - "description": "Tenant list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "tenants": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "slug": { - "type": "string" - }, - "name": { - "type": "string" - }, - "status": { - "type": "string" - }, - "linux_user": { - "type": "string" - }, - "created_at": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - }, - "post": { - "tags": [ - "Super Admin" - ], - "summary": "Create tenant and bootstrap job", - "operationId": "createTenant", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "slug", - "name" - ], - "properties": { - "slug": { - "type": "string" - }, - "name": { - "type": "string" - }, - "linux_user": { - "type": "string" - }, - "config": { - "type": "object" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Tenant created with bootstrap job", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "409": { - "description": "Tenant slug or linux user already exists", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - } - } - }, - "/api/super/tenants/{id}/decommission": { - "post": { - "tags": [ - "Super Admin" - ], - "summary": "Decommission tenant", - "operationId": "decommissionTenant", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Tenant decommissioned", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - } - }, - "/api/super/provision-jobs": { - "get": { - "tags": [ - "Super Admin" - ], - "summary": "List provision jobs", - "operationId": "listProvisionJobs", - "responses": { - "200": { - "description": "Provision job list", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "jobs": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "tenant_id": { - "type": "integer" - }, - "type": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "pending", - "running", - "completed", - "failed" - ] - }, - "output": { - "type": "string" - }, - "created_at": { - "type": "integer" - }, - "completed_at": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - }, - "post": { - "tags": [ - "Super Admin" - ], - "summary": "Create provision job", - "operationId": "createProvisionJob", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "tenant_id", - "type" - ], - "properties": { - "tenant_id": { - "type": "integer" - }, - "type": { - "type": "string" - }, - "params": { - "type": "object" - } - } - } - } - } - }, - "responses": { - "201": { - "description": "Job created", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - } - } - } - }, - "/api/super/provision-jobs/{id}": { - "get": { - "tags": [ - "Super Admin" - ], - "summary": "Get provision job details", - "operationId": "getProvisionJob", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Job details", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - } - }, - "/api/super/provision-jobs/{id}/run": { - "post": { - "tags": [ - "Super Admin" - ], - "summary": "Run provision job", - "operationId": "runProvisionJob", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Job started", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "success": { - "type": "boolean" - }, - "status": { - "type": "string" - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "403": { - "$ref": "#/components/responses/Forbidden" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - } - }, - "/api/connect": { - "post": { - "tags": [ - "Connections" - ], - "summary": "Register a direct CLI connection", - "description": "Registers a CLI tool directly without a gateway. Auto-creates agent if name doesn't exist.", - "operationId": "registerConnection", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "tool_name", - "agent_name" - ], - "properties": { - "tool_name": { - "type": "string" - }, - "tool_version": { - "type": "string" - }, - "agent_name": { - "type": "string" - }, - "agent_role": { - "type": "string" - }, - "metadata": { - "type": "object" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Connection registered", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "connection_id": { - "type": "string", - "format": "uuid" - }, - "agent_id": { - "type": "integer" - }, - "agent_name": { - "type": "string" - }, - "status": { - "type": "string", - "enum": [ - "connected" - ] - }, - "sse_url": { - "type": "string" - }, - "heartbeat_url": { - "type": "string" - }, - "token_report_url": { - "type": "string" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - }, - "get": { - "tags": [ - "Connections" - ], - "summary": "List all direct connections", - "operationId": "listConnections", - "responses": { - "200": { - "description": "List of connections", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "connections": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "agent_id": { - "type": "integer" - }, - "agent_name": { - "type": "string" - }, - "tool_name": { - "type": "string" - }, - "tool_version": { - "type": "string" - }, - "connection_id": { - "type": "string", - "format": "uuid" - }, - "status": { - "type": "string", - "enum": [ - "connected", - "disconnected" - ] - }, - "last_heartbeat": { - "type": "integer" - }, - "created_at": { - "type": "integer" - } - } - } - } - } - } - } - } - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - } - } - }, - "delete": { - "tags": [ - "Connections" - ], - "summary": "Disconnect a CLI connection", - "operationId": "disconnectConnection", - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "connection_id" - ], - "properties": { - "connection_id": { - "type": "string", - "format": "uuid" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Disconnected", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "status": { - "type": "string", - "enum": [ - "disconnected" - ] - }, - "connection_id": { - "type": "string" - } - } - } - } - } - }, - "400": { - "$ref": "#/components/responses/BadRequest" - }, - "401": { - "$ref": "#/components/responses/Unauthorized" - }, - "404": { - "$ref": "#/components/responses/NotFound" - } - } - } } }, "components": { @@ -6909,6 +7544,26 @@ "type": "integer" } } + }, + "Project": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "created_at": { + "type": "integer" + }, + "updated_at": { + "type": "integer" + } + } } }, "responses": { diff --git a/src/app/api/agents/[id]/attribution/route.ts b/src/app/api/agents/[id]/attribution/route.ts new file mode 100644 index 0000000..53f3456 --- /dev/null +++ b/src/app/api/agents/[id]/attribution/route.ts @@ -0,0 +1,356 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDatabase } from '@/lib/db'; +import { requireRole } from '@/lib/auth'; +import { logger } from '@/lib/logger'; + +const ALLOWED_SECTIONS = new Set(['identity', 'audit', 'mutations', 'cost']); + +/** + * GET /api/agents/[id]/attribution - Agent-Level Identity & Attribution + * + * Returns a comprehensive audit trail and cost attribution report for + * a specific agent. Enables per-agent observability, debugging, and + * cost analysis in multi-agent environments. + * + * Query params: + * hours - Time window (default: 24, max: 720) + * section - Comma-separated: audit,cost,mutations,identity (default: all) + * + * Response: + * identity - Agent profile, status, and session info + * audit - Full audit trail of agent actions + * mutations - Task/memory/soul changes attributed to this agent + * cost - Token usage and cost breakdown per model + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'viewer'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const resolvedParams = await params; + const agentId = resolvedParams.id; + const workspaceId = auth.user.workspace_id ?? 1; + + // Resolve agent + let agent: any; + if (/^\d+$/.test(agentId)) { + agent = db.prepare('SELECT * FROM agents WHERE id = ? AND workspace_id = ?').get(Number(agentId), workspaceId); + } else { + agent = db.prepare('SELECT * FROM agents WHERE name = ? AND workspace_id = ?').get(agentId, workspaceId); + } + + if (!agent) { + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }); + } + + const { searchParams } = new URL(request.url); + const privileged = searchParams.get('privileged') === '1'; + const isSelfByHeader = auth.user.agent_name === agent.name; + const isSelfByUsername = auth.user.username === agent.name; + const isSelf = isSelfByHeader || isSelfByUsername; + const isPrivileged = auth.user.role === 'admin' && privileged; + if (!isSelf && !isPrivileged) { + return NextResponse.json( + { error: 'Forbidden: attribution is self-scope by default. Admin can use ?privileged=1 override.' }, + { status: 403 } + ); + } + + const hoursRaw = searchParams.get('hours'); + const hours = parseHours(hoursRaw); + if (!hours) { + return NextResponse.json({ error: 'Invalid hours. Expected integer 1..720.' }, { status: 400 }); + } + + const sections = parseSections(searchParams.get('section')); + if ('error' in sections) { + return NextResponse.json({ error: sections.error }, { status: 400 }); + } + + const now = Math.floor(Date.now() / 1000); + const since = now - hours * 3600; + + const result: Record = { + agent_name: agent.name, + timeframe: { hours, since, until: now }, + access_scope: isSelf ? 'self' : 'privileged', + }; + + if (sections.sections.has('identity')) { + result.identity = buildIdentity(db, agent, workspaceId); + } + + if (sections.sections.has('audit')) { + result.audit = buildAuditTrail(db, agent.name, workspaceId, since); + } + + if (sections.sections.has('mutations')) { + result.mutations = buildMutations(db, agent.name, workspaceId, since); + } + + if (sections.sections.has('cost')) { + result.cost = buildCostAttribution(db, agent.name, workspaceId, since); + } + + return NextResponse.json(result); + } catch (error) { + logger.error({ err: error }, 'GET /api/agents/[id]/attribution error'); + return NextResponse.json({ error: 'Failed to fetch attribution data' }, { status: 500 }); + } +} + +/** Agent identity and profile info */ +function buildIdentity(db: any, agent: any, workspaceId: number) { + const config = safeParseJson(agent.config, {}); + + // Count total tasks ever assigned + const taskStats = db.prepare(` + SELECT + COUNT(*) as total, + SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) as completed, + SUM(CASE WHEN status IN ('assigned', 'in_progress') THEN 1 ELSE 0 END) as active + FROM tasks WHERE assigned_to = ? AND workspace_id = ? + `).get(agent.name, workspaceId) as any; + + // Count comments authored + const commentCount = (db.prepare( + `SELECT COUNT(*) as c FROM comments WHERE author = ? AND workspace_id = ?` + ).get(agent.name, workspaceId) as any).c; + + return { + id: agent.id, + name: agent.name, + role: agent.role, + status: agent.status, + last_seen: agent.last_seen, + last_activity: agent.last_activity, + created_at: agent.created_at, + session_key: agent.session_key ? '***' : null, // Masked for security + has_soul: !!agent.soul_content, + config_keys: Object.keys(config), + lifetime_stats: { + tasks_total: taskStats?.total || 0, + tasks_completed: taskStats?.completed || 0, + tasks_active: taskStats?.active || 0, + comments_authored: commentCount, + }, + }; +} + +/** Audit trail — all activities attributed to this agent */ +function buildAuditTrail(db: any, agentName: string, workspaceId: number, since: number) { + // Activities where this agent is the actor + const activities = db.prepare(` + SELECT id, type, entity_type, entity_id, description, data, created_at + FROM activities + WHERE actor = ? AND workspace_id = ? AND created_at >= ? + ORDER BY created_at DESC + LIMIT 200 + `).all(agentName, workspaceId, since) as any[]; + + // Audit log entries (system-wide, may reference agent) + let auditEntries: any[] = []; + try { + auditEntries = db.prepare(` + SELECT id, action, actor, detail, created_at + FROM audit_log + WHERE (actor = ? OR detail LIKE ?) AND created_at >= ? + ORDER BY created_at DESC + LIMIT 100 + `).all(agentName, `%${agentName}%`, since) as any[]; + } catch { + // audit_log table may not exist + } + + // Group activities by type for summary + const byType: Record = {}; + for (const a of activities) { + byType[a.type] = (byType[a.type] || 0) + 1; + } + + return { + total_activities: activities.length, + by_type: byType, + activities: activities.map(a => ({ + ...a, + data: safeParseJson(a.data, null), + })), + audit_log_entries: auditEntries.map(e => ({ + ...e, + detail: safeParseJson(e.detail, null), + })), + }; +} + +/** Mutations — task changes, comments, status transitions */ +function buildMutations(db: any, agentName: string, workspaceId: number, since: number) { + // Task mutations (created, updated, status changes) + const taskMutations = db.prepare(` + SELECT id, type, entity_type, entity_id, description, data, created_at + FROM activities + WHERE actor = ? AND workspace_id = ? AND created_at >= ? + AND entity_type = 'task' + AND type IN ('task_created', 'task_updated', 'task_status_change', 'task_assigned') + ORDER BY created_at DESC + LIMIT 100 + `).all(agentName, workspaceId, since) as any[]; + + // Comments authored + const comments = db.prepare(` + SELECT c.id, c.task_id, c.content, c.created_at, c.mentions, t.title as task_title + FROM comments c + LEFT JOIN tasks t ON c.task_id = t.id AND t.workspace_id = ? + WHERE c.author = ? AND c.workspace_id = ? AND c.created_at >= ? + ORDER BY c.created_at DESC + LIMIT 50 + `).all(workspaceId, agentName, workspaceId, since) as any[]; + + // Agent status changes (by heartbeat or others) + const statusChanges = db.prepare(` + SELECT id, type, description, data, created_at + FROM activities + WHERE entity_type = 'agent' AND workspace_id = ? + AND created_at >= ? + AND (actor = ? OR description LIKE ?) + ORDER BY created_at DESC + LIMIT 50 + `).all(workspaceId, since, agentName, `%${agentName}%`) as any[]; + + return { + task_mutations: taskMutations.map(m => ({ + ...m, + data: safeParseJson(m.data, null), + })), + comments: comments.map(c => ({ + ...c, + mentions: safeParseJson(c.mentions, []), + content_preview: c.content?.substring(0, 200) || '', + })), + status_changes: statusChanges.map(s => ({ + ...s, + data: safeParseJson(s.data, null), + })), + summary: { + task_mutations_count: taskMutations.length, + comments_count: comments.length, + status_changes_count: statusChanges.length, + }, + }; +} + +/** Cost attribution — token usage per model */ +function buildCostAttribution(db: any, agentName: string, workspaceId: number, since: number) { + try { + const byModel = db.prepare(` + SELECT model, + COUNT(*) as request_count, + SUM(input_tokens) as input_tokens, + SUM(output_tokens) as output_tokens + FROM token_usage + WHERE session_id = ? AND workspace_id = ? AND created_at >= ? + GROUP BY model + ORDER BY (input_tokens + output_tokens) DESC + `).all(agentName, workspaceId, since) as Array<{ + model: string; request_count: number; input_tokens: number; output_tokens: number + }>; + + // Also check session IDs that contain the agent name (e.g. "agentname:cli") + const byModelAlt = db.prepare(` + SELECT model, + COUNT(*) as request_count, + SUM(input_tokens) as input_tokens, + SUM(output_tokens) as output_tokens + FROM token_usage + WHERE session_id LIKE ? AND session_id != ? AND workspace_id = ? AND created_at >= ? + GROUP BY model + ORDER BY (input_tokens + output_tokens) DESC + `).all(`${agentName}:%`, agentName, workspaceId, since) as Array<{ + model: string; request_count: number; input_tokens: number; output_tokens: number + }>; + + // Merge results + const merged = new Map(); + for (const row of [...byModel, ...byModelAlt]) { + const existing = merged.get(row.model); + if (existing) { + existing.request_count += row.request_count; + existing.input_tokens += row.input_tokens; + existing.output_tokens += row.output_tokens; + } else { + merged.set(row.model, { ...row }); + } + } + + const models = Array.from(merged.values()); + const total = models.reduce((acc, r) => ({ + input_tokens: acc.input_tokens + r.input_tokens, + output_tokens: acc.output_tokens + r.output_tokens, + requests: acc.requests + r.request_count, + }), { input_tokens: 0, output_tokens: 0, requests: 0 }); + + // Daily breakdown for trend + const daily = db.prepare(` + SELECT (created_at / 86400) * 86400 as day_bucket, + SUM(input_tokens) as input_tokens, + SUM(output_tokens) as output_tokens, + COUNT(*) as requests + FROM token_usage + WHERE (session_id = ? OR session_id LIKE ?) AND workspace_id = ? AND created_at >= ? + GROUP BY day_bucket + ORDER BY day_bucket ASC + `).all(agentName, `${agentName}:%`, workspaceId, since) as any[]; + + return { + by_model: models, + total, + daily_trend: daily.map(d => ({ + date: new Date(d.day_bucket * 1000).toISOString().split('T')[0], + ...d, + })), + }; + } catch { + return { by_model: [], total: { input_tokens: 0, output_tokens: 0, requests: 0 }, daily_trend: [] }; + } +} + +function parseHours(hoursRaw: string | null): number | null { + if (!hoursRaw || hoursRaw.trim() === '') return 24; + if (!/^\d+$/.test(hoursRaw)) return null; + const hours = Number(hoursRaw); + if (!Number.isInteger(hours) || hours < 1 || hours > 720) return null; + return hours; +} + +function parseSections( + sectionRaw: string | null +): { sections: Set } | { error: string } { + const value = (sectionRaw || 'identity,audit,mutations,cost').trim(); + const parsed = value + .split(',') + .map((section) => section.trim()) + .filter(Boolean); + + if (parsed.length === 0) { + return { error: 'Invalid section. Expected one or more of identity,audit,mutations,cost.' }; + } + + const invalid = parsed.filter((section) => !ALLOWED_SECTIONS.has(section)); + if (invalid.length > 0) { + return { error: `Invalid section value(s): ${invalid.join(', ')}` }; + } + + return { sections: new Set(parsed) }; +} + +function safeParseJson(raw: string | null | undefined, fallback: T): T { + if (!raw) return fallback; + try { + return JSON.parse(raw) as T; + } catch { + return fallback; + } +} diff --git a/src/lib/auth.ts b/src/lib/auth.ts index e1517e7..49a553c 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -31,6 +31,8 @@ export interface User { created_at: number updated_at: number last_login_at: number | null + /** Agent name when request is made on behalf of a specific agent (via X-Agent-Name header) */ + agent_name?: string | null } export interface UserSession { @@ -268,12 +270,15 @@ export function deleteUser(id: number): boolean { * For API key auth, returns a synthetic "api" user. */ export function getUserFromRequest(request: Request): User | null { + // Extract agent identity header (optional, for attribution) + const agentName = (request.headers.get('x-agent-name') || '').trim() || null + // Check session cookie const cookieHeader = request.headers.get('cookie') || '' const sessionToken = parseCookie(cookieHeader, 'mc-session') if (sessionToken) { const user = validateSession(sessionToken) - if (user) return user + if (user) return { ...user, agent_name: agentName } } // Check API key - return synthetic user @@ -289,6 +294,7 @@ export function getUserFromRequest(request: Request): User | null { created_at: 0, updated_at: 0, last_login_at: null, + agent_name: agentName, } } diff --git a/tests/agent-attribution.spec.ts b/tests/agent-attribution.spec.ts new file mode 100644 index 0000000..30af759 --- /dev/null +++ b/tests/agent-attribution.spec.ts @@ -0,0 +1,77 @@ +import { expect, test } from '@playwright/test' +import { API_KEY_HEADER, createTestAgent, deleteTestAgent } from './helpers' + +test.describe('Agent Attribution API', () => { + const cleanup: number[] = [] + + test.afterEach(async ({ request }) => { + for (const id of cleanup) { + await deleteTestAgent(request, id).catch(() => {}) + } + cleanup.length = 0 + }) + + test('allows self-scope access using x-agent-name attribution header', async ({ request }) => { + const { id, name } = await createTestAgent(request) + cleanup.push(id) + + const res = await request.get(`/api/agents/${id}/attribution`, { + headers: { ...API_KEY_HEADER, 'x-agent-name': name }, + }) + expect(res.status()).toBe(200) + const body = await res.json() + expect(body.agent_name).toBe(name) + expect(body.access_scope).toBe('self') + }) + + test('denies cross-agent attribution access by default', async ({ request }) => { + const primary = await createTestAgent(request) + const other = await createTestAgent(request) + cleanup.push(primary.id, other.id) + + const res = await request.get(`/api/agents/${primary.id}/attribution`, { + headers: { ...API_KEY_HEADER, 'x-agent-name': other.name }, + }) + + expect(res.status()).toBe(403) + }) + + test('allows privileged override for admin caller', async ({ request }) => { + const primary = await createTestAgent(request) + const other = await createTestAgent(request) + cleanup.push(primary.id, other.id) + + const res = await request.get(`/api/agents/${primary.id}/attribution?privileged=1`, { + headers: { ...API_KEY_HEADER, 'x-agent-name': other.name }, + }) + expect(res.status()).toBe(200) + const body = await res.json() + expect(body.access_scope).toBe('privileged') + }) + + test('validates section parameter and timeframe hours', async ({ request }) => { + const { id, name } = await createTestAgent(request) + cleanup.push(id) + + const sectionRes = await request.get(`/api/agents/${id}/attribution?section=identity&hours=48`, { + headers: { ...API_KEY_HEADER, 'x-agent-name': name }, + }) + expect(sectionRes.status()).toBe(200) + const sectionBody = await sectionRes.json() + expect(sectionBody.timeframe.hours).toBe(48) + expect(sectionBody.identity).toBeDefined() + expect(sectionBody.audit).toBeUndefined() + expect(sectionBody.mutations).toBeUndefined() + expect(sectionBody.cost).toBeUndefined() + + const invalidSection = await request.get(`/api/agents/${id}/attribution?section=unknown`, { + headers: { ...API_KEY_HEADER, 'x-agent-name': name }, + }) + expect(invalidSection.status()).toBe(400) + + const invalidHours = await request.get(`/api/agents/${id}/attribution?hours=0`, { + headers: { ...API_KEY_HEADER, 'x-agent-name': name }, + }) + expect(invalidHours.status()).toBe(400) + }) +})