diff --git a/mateclaw-server/src/main/resources/docs/en/ambient-ai.md b/mateclaw-server/src/main/resources/docs/en/ambient-ai.md index 891e2fa0..a9459bc3 100644 --- a/mateclaw-server/src/main/resources/docs/en/ambient-ai.md +++ b/mateclaw-server/src/main/resources/docs/en/ambient-ai.md @@ -153,11 +153,15 @@ curl http://localhost:18088/api/v1/cron-jobs \ -H "Authorization: Bearer " # Run once now (doesn't affect the next scheduled run) -curl -X POST http://localhost:18088/api/v1/cron-jobs/{id}/run-now \ +curl -X POST http://localhost:18088/api/v1/cron-jobs/{id}/run \ -H "Authorization: Bearer " -# View execution history -curl http://localhost:18088/api/v1/cron-jobs/{id}/runs \ +# View execution history for one cron job +curl http://localhost:18088/api/v1/dashboard/cron-runs/{id} \ + -H "Authorization: Bearer " + +# View recent execution history in the current workspace +curl http://localhost:18088/api/v1/dashboard/cron-runs \ -H "Authorization: Bearer " ``` diff --git a/mateclaw-server/src/main/resources/docs/en/api.md b/mateclaw-server/src/main/resources/docs/en/api.md index 79679bb1..e3e01bf6 100644 --- a/mateclaw-server/src/main/resources/docs/en/api.md +++ b/mateclaw-server/src/main/resources/docs/en/api.md @@ -1,35 +1,42 @@ # API Reference -Every REST endpoint is prefixed `/api/v1/`. Every response follows the same envelope: +This page is source-aligned with the Spring MVC controllers under `mateclaw-server/src/main/java`. The route inventory below was rebuilt from controller annotations; when it conflicts with an older feature page, this page and the source code are the contract. + +## Contract + +All application REST endpoints use the `/api/v1` prefix unless explicitly noted. Most JSON responses use the project envelope: ```json { "code": 200, - "message": "success", - "data": { } + "msg": "success", + "data": {} } ``` -Every endpoint except `/api/v1/auth/login` requires a JWT in the `Authorization` header: +Important exceptions: -``` -Authorization: Bearer -``` +- Streaming endpoints (`text/event-stream`) send SSE frames instead of the JSON envelope. +- Download endpoints such as `/api/v1/files/generated/{id}`, chat uploads, and wiki raw downloads return bytes or `ResponseEntity` bodies. +- A few conflict/error flows may return a small structured object outside `R` when the client must branch on the HTTP status. -For deep behavior, read the feature page — [Chat & Messaging](./chat), [Agents](./agents), [Tools](./tools), [Security & Approval](./security), [LLM Wiki](./wiki), [Multimodal](./multimodal), [Memory](./memory), [Channels](./channels), [Models](./models), [Workspaces](./workspaces), [Goals](./goals), [Doctor](./doctor). - ---- +IDs are Snowflake `Long` values serialized as JSON strings by the backend. Frontends and third-party clients should keep IDs as strings. ## Authentication -``` -POST /api/v1/auth/login # Login, get JWT -GET /api/v1/users/me # Current user profile -PUT /api/v1/users/me # Update profile -PUT /api/v1/users/me/password # Change password +`POST /api/v1/auth/login` returns the JWT. Send protected requests with: + +```text +Authorization: Bearer ``` -**Login example:** +Public routes from `SecurityConfig` include login, first-run setup, webhook/webchat callbacks, chat stream/stop routes, agent stream route, talk WebSocket, `GET /api/v1/settings/language`, and `/api/v1/files/generated/**` one-time generated-file downloads. Role annotations such as `@RequireWorkspaceRole` and `@RequireGlobalAdmin` still apply after authentication. + +Workspace-scoped APIs usually accept `X-Workspace-Id`. If omitted, many handlers fall back to workspace `1` for desktop/local compatibility. + +## Frequently Used APIs + +### Login ```bash curl -X POST http://localhost:18088/api/v1/auth/login \ @@ -37,581 +44,640 @@ curl -X POST http://localhost:18088/api/v1/auth/login \ -d '{"username":"admin","password":"admin123"}' ``` -Response: - -```json -{ - "code": 200, - "data": { - "token": "eyJhbGciOiJIUzI1NiJ9...", - "tokenType": "Bearer", - "expiresIn": 86400 - } -} -``` - ---- - -## Chat - -``` -POST /api/v1/chat?agentId={id} # Send a message (sync; agentId is a query param) -POST /api/v1/chat/stream # SSE streaming (POST; agentId in the JSON body) -POST /api/v1/chat/{conversationId}/stop # Stop an in-flight stream -POST /api/v1/chat/{conversationId}/interrupt # Interrupt the agent loop -POST /api/v1/chat/upload # Upload a chat attachment (multipart/form-data) -GET /api/v1/chat/files/{conversationId}/{storedName} # Read an uploaded attachment -GET /api/v1/chat/{conversationId}/pending-approvals # List waiting approvals -``` - -**Send message:** +### Chat ```bash -curl -X POST 'http://localhost:18088/api/v1/chat?agentId=1' \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"message":"Hello, what can you do?", "conversationId":"conv-abc123"}' -``` - -Request body fields: `message` (required), `conversationId` (optional, defaults to `default`), `contentParts` (optional structured content parts for attachments). - -**SSE stream example:** - -The SSE endpoint is **POST with a JSON body** — browser-native `EventSource` only supports GET, so integrators should use `fetch()` and read the response stream (see the frontend's `composables/chat/useChat.ts`). - -```bash -curl -N -X POST 'http://localhost:18088/api/v1/chat/stream' \ - -H "Authorization: Bearer YOUR_TOKEN" \ +curl -N -X POST http://localhost:18088/api/v1/chat/stream \ + -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: text/event-stream" \ - -d '{"agentId":1, "message":"Hello", "conversationId":"conv-abc123"}' + -d '{"agentId":"1","message":"Hello","conversationId":"conv-abc123"}' ``` -Event types and schema are documented in [Chat & Messaging](./chat). +Use `fetch()` with a streaming reader for `/chat/stream`; browser `EventSource` cannot send POST bodies. + +### Tool Approval + +There is no `POST /api/v1/approvals/{id}/resolve` REST endpoint. Web approval and denial go through the chat stream by sending `/approve` or `/deny` in the waiting conversation. Read-only hydration remains `GET /api/v1/chat/{conversationId}/pending-approvals`. Auto-approval policies are managed under `/api/v1/approval/grants`. + +### Doctor / Health + +The current backend health surface is `GET /api/v1/system/health`. The old `/api/v1/doctor/*` endpoints are not implemented in the current source tree. + +### Multimodal Generation + +Image, video, music, and 3D generation are agent tools (`image_generate`, `video_generate`, `music_generate`, `model3d_generate`), not standalone `/api/v1/image`, `/api/v1/video`, or `/api/v1/music` REST controllers. REST surfaces that do exist here are TTS/STT and generated-file download. + +### Non-REST Endpoint + +`/api/v1/talk/ws` is registered by `WebSocketConfig` for Talk Mode. It is intentionally listed in `SecurityConfig` as a public WebSocket route, but it is not counted in the controller route inventory below. + +## Source-Aligned Route Inventory + +Total routes extracted: 406. + +### Authentication + +| Method | Path | Purpose / handler | +|---|---|---| +| `POST` | `/api/v1/auth/login` | `Login` | +| `GET` | `/api/v1/auth/tokens` | `List my PATs (metadata only — plaintext is never returned after creation)` | +| `POST` | `/api/v1/auth/tokens` | `Mint a new PAT — returned plaintext is shown once and cannot be recovered` | +| `DELETE` | `/api/v1/auth/tokens/{id}` | `Revoke a PAT — soft-delete; further auth attempts with this token will fail` | +| `GET` | `/api/v1/auth/users` | `List Users` | +| `POST` | `/api/v1/auth/users` | `Create User` | +| `PUT` | `/api/v1/auth/users/{id}/password` | `Change Password` | + +### Chat + +| Method | Path | Purpose / handler | +|---|---|---| +| `POST` | `/api/v1/chat` | `Chat` | +| `GET` | `/api/v1/chat/files/{conversationId}/{storedName:.+}` | `Read Uploaded File` | +| `POST` | `/api/v1/chat/stream` | `Chat Stream` | +| `POST` | `/api/v1/chat/upload` | `Upload` | +| `POST` | `/api/v1/chat/{conversationId}/interrupt` | `Interrupt Stream` | +| `GET` | `/api/v1/chat/{conversationId}/pending-approvals` | `Get Pending Approvals` | +| `POST` | `/api/v1/chat/{conversationId}/stop` | `Stop Stream` | ### Conversations -``` -GET /api/v1/conversations # List (?page&size&agentId) -GET /api/v1/conversations/page?page=&size=&keyword= # Paginated sessions (with keyword search) -GET /api/v1/conversations/{id}/messages # Get messages -PUT /api/v1/conversations/{id}/model # Set the model used by this conversation -DELETE /api/v1/conversations/{id} # Delete -DELETE /api/v1/conversations/{id}/messages # Clear messages -GET /api/v1/conversations/{id}/status # Conversation status -``` - ---- - -## Agents - -``` -GET /api/v1/agents # List (paginated) -GET /api/v1/agents/{id} # Get -POST /api/v1/agents # Create -PUT /api/v1/agents/{id} # Update (partial) -DELETE /api/v1/agents/{id} # Soft delete - -GET /api/v1/agents/{id}/chat/stream?message=...&conversationId=... # Streaming chat - -GET /api/v1/agents/{id}/workspace/files # List files -GET /api/v1/agents/{id}/workspace/files/{filename} # Get content -PUT /api/v1/agents/{id}/workspace/files/{filename} # Write -DELETE /api/v1/agents/{id}/workspace/files/{filename} # Delete -GET /api/v1/agents/{id}/workspace/prompt-files # Which files are injected -PUT /api/v1/agents/{id}/workspace/prompt-files # Set prompt file list - -GET /api/v1/agents/{agentId}/workspace/memory/export # Export memory snapshot -POST /api/v1/agents/{agentId}/workspace/memory/import/preview # Preview import (no writes) -POST /api/v1/agents/{agentId}/workspace/memory/import # Import memory snapshot - -GET /api/v1/agents/templates # List templates -POST /api/v1/agents/templates/{id} # Create from template -``` - -### Field: `primaryKbId` (1.5.0+) - -Every employee can declare a **primary knowledge base** to act as the default target for wiki tools. The field is typed `string | null` (Snowflake ID, always handled as a string on the frontend). - -`PUT /api/v1/agents/{id}` is **three-state**: - -| Request body has | Behavior | -|------------------|----------| -| no `primaryKbId` key | leave the current value unchanged | -| `"primaryKbId": ""` | set to the specified KB | -| `"primaryKbId": null` | clear it (wiki tools then fall back to the workspace's default KB) | - -The server distinguishes "field missing" from "explicit null" via `body.containsKey("primaryKbId")`; the entity carries `@TableField(updateStrategy = FieldStrategy.ALWAYS)` so a null actually reaches the database (MyBatis-Plus's default `NOT_NULL` strategy would otherwise silently skip it). - -Design intent: **KBs are workspace-shared. `primaryKbId` only chooses the default target for *this* employee's wiki tools — it does not change KB ownership or visibility.** Multiple employees can pick the same KB as primary without interfering. - -Examples: - -```bash -# Set the primary KB -curl -X PUT http://localhost:18088/api/v1/agents/2055639185675730946 \ - -H "Authorization: Bearer $TOKEN" \ - -H "X-Workspace-Id: 1" \ - -H "Content-Type: application/json" \ - -d '{"primaryKbId": "2054907618529591298", ...other fields}' - -# Clear it -curl -X PUT http://localhost:18088/api/v1/agents/2055639185675730946 \ - -H "Authorization: Bearer $TOKEN" \ - -H "X-Workspace-Id: 1" \ - -H "Content-Type: application/json" \ - -d '{"primaryKbId": null, ...other fields}' -``` - ---- - -## Tools - -``` -GET /api/v1/tools # List -PUT /api/v1/tools/{id} # Update -PUT /api/v1/tools/{id}/toggle?enabled={bool} # Toggle -PUT /api/v1/tools/{id}/disclosure-tier # Set disclosure tier (core / extension) -POST /api/v1/tools/{name}/test # Test directly -``` - ---- - -## Skills - -``` -GET /api/v1/skills # List (?type=builtin|custom|mcp&tag=...) -GET /api/v1/skills/{id} # Get -POST /api/v1/skills # Create -PUT /api/v1/skills/{id} # Update -DELETE /api/v1/skills/{id} # Delete -PUT /api/v1/skills/{id}/toggle?enabled={bool} # Toggle -GET /api/v1/skills/runtime/active # Currently active skills -GET /api/v1/skills/runtime/status # Runtime status -POST /api/v1/skills/runtime/refresh # Reload runtime -``` - ---- - -## MCP Servers - -``` -GET /api/v1/mcp/servers # List -GET /api/v1/mcp/servers/{id} # Get -POST /api/v1/mcp/servers # Create -PUT /api/v1/mcp/servers/{id} # Update (PATCH semantics) -DELETE /api/v1/mcp/servers/{id} # Delete -PUT /api/v1/mcp/servers/{id}/toggle?enabled={bool} # Toggle -POST /api/v1/mcp/servers/{id}/test # Test connection -POST /api/v1/mcp/servers/refresh # Refresh all -``` - -See [MCP](./mcp) for body schemas and examples. - ---- - -## LLM Wiki - -``` -GET /api/v1/wiki/kbs # List knowledge bases -POST /api/v1/wiki/kbs # Create KB -GET /api/v1/wiki/kbs/{id} # Get KB detail -PUT /api/v1/wiki/kbs/{id} # Update KB -DELETE /api/v1/wiki/kbs/{id} # Delete KB - -POST /api/v1/wiki/kbs/{kbId}/raw # Upload raw material -GET /api/v1/wiki/kbs/{kbId}/raw # List raw materials -DELETE /api/v1/wiki/raw/{id} # Delete raw material -POST /api/v1/wiki/raw/{id}/reprocess # Re-digest - -GET /api/v1/wiki/kbs/{kbId}/pages # List pages -GET /api/v1/wiki/pages/{id} # Get page -PUT /api/v1/wiki/pages/{id} # Edit page -DELETE /api/v1/wiki/pages/{id} # Delete page -POST /api/v1/wiki/pages/{id}/lock # Lock page -POST /api/v1/wiki/pages/{id}/unlock # Unlock page - -GET /api/v1/wiki/kbs/{kbId}/search?q=... # Full-text search -GET /api/v1/wiki/pages/{id}/backlinks # Backlinks -``` - -Agent-callable wiki tools (`wiki_search`, `wiki_read`, `wiki_backlinks`) resolve `kbId` automatically. - -### Per-agent primary knowledge base (1.5.0+) - -PR #237 / migration V130 introduced the per-employee "primary knowledge base" mechanism. New endpoint: - -``` -GET /api/v1/wiki/knowledge-bases/bindable # List KBs in the current workspace that can be picked as primary -``` - -This returns **every** KB in the workspace, including ones already picked as primary by other employees — the binding semantics are "which one do I default to," not "I own this one." The shape matches `GET /api/v1/wiki/knowledge-bases` (list-by-workspace); the dedicated name exists to be self-documenting in the UI. - -The bind action itself **does not** go through the wiki API — it's written to the agent entity: - -``` -PUT /api/v1/agents/{id} # body carries the primaryKbId field -``` - -Field semantics and three-state behavior: see the [`primaryKbId` section under Agents](#field-primarykbid-150) above. - -::: warning Legacy `kb.agentId` field -Versions before 1.5.0 stored the binding on `mate_wiki_knowledge_base.agent_id` (one-to-one, exclusive). The V130 migration backfills those values into `agent.primary_kb_id`; the old column is kept as a read-only fallback — **`PUT /api/v1/wiki/knowledge-bases/{id}` no longer processes the `agentId` field** and silently ignores it if sent. New code should drive the binding only through `agent.primaryKbId`. -::: - ---- - -## Multimodal - -``` -POST /api/v1/image/generate # Generate image -POST /api/v1/image/edit # Edit image -POST /api/v1/video/generate # Generate video -POST /api/v1/video/from-image # Image-to-video -POST /api/v1/music/generate # Generate music -POST /api/v1/tts/synthesize # Text-to-speech -POST /api/v1/stt/transcribe # Speech-to-text - -GET /api/v1/image/jobs/{id} # Async image job status -GET /api/v1/video/jobs/{id} # Async video job status -``` - -See [Multimodal](./multimodal). - ---- - -## Memory - -``` -POST /api/v1/memory/{agentId}/emergence # Manually trigger consolidation -POST /api/v1/memory/{agentId}/summarize/{conversationId} # Trigger extraction -GET /api/v1/memory/{agentId}/dreaming/status # Last/next run + latest DREAMS.md entry -``` - ---- - -## Security & Approval - -### Tool Guard rules - -``` -GET /api/v1/security/guard/config # Global config -PUT /api/v1/security/guard/config # Update global config -GET /api/v1/security/guard/rules # List custom rules -GET /api/v1/security/guard/rules/builtin # List builtin rules -POST /api/v1/security/guard/rules # Create rule -PUT /api/v1/security/guard/rules/{id} # Update rule -DELETE /api/v1/security/guard/rules/{id} # Delete rule -PUT /api/v1/security/guard/rules/{id}/toggle?enabled={bool} # Toggle rule -``` - -### File Guard - -``` -GET /api/v1/security/guard/config/file-guard # Get config -PUT /api/v1/security/guard/config/file-guard # Update config -``` - -### Approvals - -``` -GET /api/v1/approvals?status=pending # List pending approvals -POST /api/v1/approvals/{id}/resolve # Approve or reject -``` - -Body: - -```json -{ "decision": "approved" } -``` - -or - -```json -{ "decision": "rejected", "notes": "Reason" } -``` - -### Audit log - -``` -GET /api/v1/security/audit/logs # Query (?toolName, ?decision, ?from, ?to) -GET /api/v1/security/audit/stats # Stats -GET /api/v1/audit/events # Full audit event query -``` - ---- - -## Models - -``` -GET /api/v1/models # List models -GET /api/v1/models/enabled # Enabled only -GET /api/v1/models/default # Default model -GET /api/v1/models/active # Active model -PUT /api/v1/models/active # Set active -POST /api/v1/models # Create model config -PUT /api/v1/models/{id} # Update -DELETE /api/v1/models/{id} # Delete -POST /api/v1/models/{id}/default # Set as default - -PUT /api/v1/models/{providerId}/config # Update provider config -POST /api/v1/models/custom-providers # Create custom provider -DELETE /api/v1/models/custom-providers/{providerId} # Delete custom provider - -POST /api/v1/models/{providerId}/models # Add model to provider -DELETE /api/v1/models/{providerId}/models/{modelId} # Remove model - -POST /api/v1/models/{providerId}/discover # Discover models -POST /api/v1/models/{providerId}/discover/apply # Apply discovered -POST /api/v1/models/{providerId}/test-connection # Test provider -POST /api/v1/models/{providerId}/models/{modelId}/test # Test a single model -``` - -### Legacy endpoints - -``` -GET /api/v1/model-providers # Legacy — prefer /api/v1/models -POST /api/v1/model-providers -PUT /api/v1/model-providers/{id} -DELETE /api/v1/model-providers/{id} - -GET /api/v1/model-configs # Legacy — prefer /api/v1/models -POST /api/v1/model-configs -PUT /api/v1/model-configs/{id} -DELETE /api/v1/model-configs/{id} -``` - ---- - -## Channels - -``` -GET /api/v1/channels # List -POST /api/v1/channels # Create -PUT /api/v1/channels/{id} # Update -DELETE /api/v1/channels/{id} # Delete -PUT /api/v1/channels/{id}/toggle?enabled={bool} # Toggle -GET /api/v1/channels/status # Per-channel connection status -GET /api/v1/channels/health # Aggregate health view - -GET /api/v1/channels/webhook/weixin/qrcode # WeChat iLink QR code -GET /api/v1/channels/webhook/weixin/qrcode/status # QR scan status - -POST /api/v1/channels/qrcode/qq/begin # Begin QQ scan-to-bind -GET /api/v1/channels/qrcode/qq/status # QQ scan-to-bind status -``` - -### Channel webhook callbacks - -| Channel | Callback URL | -|---------|--------------| -| DingTalk | `POST /api/v1/channels/webhook/dingtalk` | -| Feishu | `POST /api/v1/channels/webhook/feishu` | -| WeCom | `POST /api/v1/channels/webhook/wecom` | -| Telegram | `POST /api/v1/channels/webhook/telegram` | -| Discord | *(Gateway — no webhook)* | -| QQ | `POST /api/v1/channels/webhook/qq` | -| Slack | `POST /api/v1/channels/webhook/slack` | -| WeChat Personal | `POST /api/v1/channels/webhook/weixin` | - ---- - -## Cron jobs - -``` -GET /api/v1/cron-jobs # List -POST /api/v1/cron-jobs # Create -PUT /api/v1/cron-jobs/{id} # Update -DELETE /api/v1/cron-jobs/{id} # Delete -PUT /api/v1/cron-jobs/{id}/toggle?enabled={bool} # Toggle -POST /api/v1/cron-jobs/{id}/run # Run immediately -``` - ---- - -## Workflows (1.3.0+) - -Full field reference, step modes, and Pebble syntax in [Workflow](./workflow). - -``` -GET /api/v1/workflows # List -GET /api/v1/workflows/{id} # Fetch (published revision + draft) -POST /api/v1/workflows # Create -PUT /api/v1/workflows/{id} # Update metadata (name / description / enabled) -PUT /api/v1/workflows/{id}/draft # Save draft (graph_json, no compile) -POST /api/v1/workflows/{id}/publish # Publish draft as a new revision -DELETE /api/v1/workflows/{id} # Delete - -POST /api/v1/workflows/{id}/compile # Compile saved draft + diagnostics, no publish -POST /api/v1/workflows/draft/generate # Natural-language → graph_json draft -POST /api/v1/workflows/draft/preview-compile # Compile arbitrary draft JSON (no persist; template/generator preview) -GET /api/v1/workflows/draft/templates # Templates the generator can apply directly - -GET /api/v1/workflows/{id}/runs # Run list (limit, default 50) -GET /api/v1/workflows/runs/paused # All paused runs in the workspace (operator entry) -GET /api/v1/workflows/runs/{runId} # Run detail + per-step input/output/tokens/duration -POST /api/v1/workflows/runs/{runId}/resume # Resume after await_approval -``` - -> v0 has no manual "start run / cancel run" endpoint — a workflow actually starts only via a [Trigger](./triggers) or an `await_approval` resume (`/runs/{runId}/resume`). For a dry run use `/draft/preview-compile` (compile only, no persist, no execution). A manual run endpoint is planned for a later release. - ---- - -## Triggers (1.3.0+) - -Six pattern types, event governance, cross-instance consistency in [Triggers](./triggers). - -``` -GET /api/v1/triggers # List -GET /api/v1/triggers/{id} # Fetch -POST /api/v1/triggers # Create -PUT /api/v1/triggers/{id} # Update (includes enabled toggle; changing the cron expr bumps pattern_version) -DELETE /api/v1/triggers/{id} # Delete - -POST /api/v1/triggers/events # Generic event ingress (webhook / external bridge); scoped by X-Workspace-Id - # → dedup / rate-limit / bot-self, then dispatches synchronously and returns each trigger's fire/drop result -``` - ---- - -## Goals (1.4.0+) - -Goal-completion scoring and auto-followup behavior in [Goals](./goals). - -``` -POST /api/v1/goals # Create goal -GET /api/v1/goals/{id} # Get goal -PATCH /api/v1/goals/{id} # Update goal (partial) -GET /api/v1/goals/{id}/events # Evaluation event history for this goal -``` - ---- - -## Token usage - -``` -GET /api/v1/token-usage?startDate=&endDate=&modelName=&providerId= -``` - ---- - -## System settings - -``` -GET /api/v1/settings # All settings -PUT /api/v1/settings # Update multiple -GET /api/v1/settings/language # Current language -PUT /api/v1/settings/language # Update language -PUT /api/v1/settings/{key} # Update a single key -``` - ---- - -## Dashboard - -``` -GET /api/v1/dashboard/summary # Usage summary cards -GET /api/v1/dashboard/trends # Trend charts (?range=7d|30d|90d) -GET /api/v1/dashboard/top-agents # Top-used agents -GET /api/v1/dashboard/top-tools # Top-used tools -``` - ---- - -## Workspaces - -``` -GET /api/v1/workspaces # List -GET /api/v1/workspaces/{id} # Get -POST /api/v1/workspaces # Create -PUT /api/v1/workspaces/{id} # Update -DELETE /api/v1/workspaces/{id} # Delete (owner only) -GET /api/v1/workspaces/{id}/access # Caller's access info (see below) -``` - -### Members & RBAC (1.4.0+) - -`/access` returns the caller's effective permissions in the workspace; the frontend uses it to render routes and menus: - -```json -{ - "memberRole": "editor", - "isGlobalAdmin": false, - "effectiveRole": "editor", - "capabilities": ["workspace.read", "conversation.write", "..."] -} -``` - -``` -GET /api/v1/workspaces/{id}/members # List members -POST /api/v1/workspaces/{id}/members # Add member -PUT /api/v1/workspaces/{id}/members/{memberId} # Update member (role, etc.) -DELETE /api/v1/workspaces/{id}/members/{memberId} # Remove member -``` - ---- - -## Doctor (health check) - -``` -GET /api/v1/doctor/run # Run all checks -GET /api/v1/doctor/checks # Cached check results -``` - ---- - -## Error responses - -```json -{ - "code": 400, - "message": "Validation failed: name is required" -} -``` - -### Common status codes - -| Code | Meaning | -|------|---------| -| 200 | Success | -| 400 | Bad request — validation failed or missing params | -| 401 | Unauthorized — token missing, expired, or invalid | -| 403 | Forbidden — insufficient permissions | -| 404 | Not found | -| 500 | Internal server error | - ---- - -## Pagination - -List endpoints return a consistent shape: - -```json -{ - "code": 200, - "data": { - "records": [ ], - "total": 42, - "current": 1, - "size": 20, - "pages": 3 - } -} -``` - -| Field | Purpose | -|-------|---------| -| `records` | Array of items on the current page | -| `total` | Total items | -| `current` | Current page (1-based) | -| `size` | Items per page | -| `pages` | Total pages | - ---- - -## Next - -- [Quick Start](./quickstart) — get the server running -- [Security & Approval](./security) — JWT + approval flow -- [Chat & Messaging](./chat) — SSE event format -- [LLM Wiki](./wiki) — wiki endpoint behaviors +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/conversations` | `List` | +| `POST` | `/api/v1/conversations/batch-delete` | `Batch Delete` | +| `GET` | `/api/v1/conversations/page` | `Page` | +| `DELETE` | `/api/v1/conversations/{conversationId}` | `Delete` | +| `DELETE` | `/api/v1/conversations/{conversationId}/messages` | `Clear Messages` | +| `GET` | `/api/v1/conversations/{conversationId}/messages` | `List Messages` | +| `PUT` | `/api/v1/conversations/{conversationId}/model` | `Set Model` | +| `PUT` | `/api/v1/conversations/{conversationId}/pin` | `Set Pinned` | +| `GET` | `/api/v1/conversations/{conversationId}/status` | `Get Stream Status` | +| `PUT` | `/api/v1/conversations/{conversationId}/title` | `Rename` | + +### Agents + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/agents` | `List` | +| `POST` | `/api/v1/agents` | `Create` | +| `GET` | `/api/v1/agents/{agentId}/provider-preferences` | `List Provider Preferences` | +| `PUT` | `/api/v1/agents/{agentId}/provider-preferences` | `Set Provider Preferences` | +| `GET` | `/api/v1/agents/{agentId}/skills` | `List Skills` | +| `PUT` | `/api/v1/agents/{agentId}/skills` | `Set Skills` | +| `DELETE` | `/api/v1/agents/{agentId}/skills/{skillId}` | `Unbind Skill` | +| `POST` | `/api/v1/agents/{agentId}/skills/{skillId}` | `Bind Skill` | +| `GET` | `/api/v1/agents/{agentId}/tools` | `List Tools` | +| `PUT` | `/api/v1/agents/{agentId}/tools` | `Set Tools` | +| `GET` | `/api/v1/agents/{agentId}/workspace/files` | `List Files` | +| `DELETE` | `/api/v1/agents/{agentId}/workspace/files/**` | `Delete File` | +| `GET` | `/api/v1/agents/{agentId}/workspace/files/**` | `Get File` | +| `PUT` | `/api/v1/agents/{agentId}/workspace/files/**` | `Save File` | +| `GET` | `/api/v1/agents/{agentId}/workspace/memory/export` | `Export Memory` | +| `POST` | `/api/v1/agents/{agentId}/workspace/memory/import` | `Import Memory` | +| `POST` | `/api/v1/agents/{agentId}/workspace/memory/import/preview` | `Preview Import Memory` | +| `GET` | `/api/v1/agents/{agentId}/workspace/prompt-files` | `Get Prompt Files` | +| `PUT` | `/api/v1/agents/{agentId}/workspace/prompt-files` | `Set Prompt Files` | +| `DELETE` | `/api/v1/agents/{id}` | `Delete` | +| `GET` | `/api/v1/agents/{id}` | `Get` | +| `PUT` | `/api/v1/agents/{id}` | `Update` | +| `GET` | `/api/v1/agents/{id}/capabilities` | `Capabilities` | +| `POST` | `/api/v1/agents/{id}/chat` | `Chat` | +| `GET` | `/api/v1/agents/{id}/chat/stream` | `Chat Stream` | +| `POST` | `/api/v1/agents/{id}/execute` | `Execute` | +| `GET` | `/api/v1/agents/{id}/state` | `Get State` | + +### Agent Templates + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/templates` | `List` | +| `POST` | `/api/v1/templates/{id}/apply` | `Apply` | + +### Sub-agents + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/subagents/active` | `List active sub-agents in a conversation's delegation tree` | +| `POST` | `/api/v1/subagents/spawn-pause` | `Set sub-agent spawn-pause for a conversation` | +| `POST` | `/api/v1/subagents/{subagentId}/interrupt` | `Interrupt a running sub-agent` | + +### Admin Runtime + +| Method | Path | Purpose / handler | +|---|---|---| +| `POST` | `/api/v1/admin/agent-runtime/runs/{conversationId}/recycle` | `Force recycle — dispose flux + drop RunState; use after friendly stop ignored` | +| `POST` | `/api/v1/admin/agent-runtime/runs/{conversationId}/stop` | `Friendly stop — request the run to wind down at its next checkpoint` | +| `GET` | `/api/v1/admin/agent-runtime/snapshot` | `Snapshot of every in-flight agent turn` | +| `POST` | `/api/v1/admin/agent-runtime/subagents/{subagentId}/interrupt` | `Interrupt one sub-agent (admin override of ownership check)` | +| `POST` | `/api/v1/admin/agent-runtime/sweep` | `Recycle every run currently flagged as stuck` | + +### Approval Grants + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/approval/grants` | `List` | +| `POST` | `/api/v1/approval/grants` | `Create` | +| `GET` | `/api/v1/approval/grants/active` | `Active Summary` | +| `DELETE` | `/api/v1/approval/grants/{id}` | `Revoke` | +| `GET` | `/api/v1/approval/resolutions` | `List Resolutions` | + +### Security and Tool Guard + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/security/approvals` | `List Approvals` | +| `GET` | `/api/v1/security/audit/logs` | `List Audit Logs` | +| `GET` | `/api/v1/security/audit/stats` | `Get Audit Stats` | +| `GET` | `/api/v1/security/guard/config` | `Get Guard Config` | +| `PUT` | `/api/v1/security/guard/config` | `Update Guard Config` | +| `GET` | `/api/v1/security/guard/config/file-guard` | `Get File Guard Config` | +| `PUT` | `/api/v1/security/guard/config/file-guard` | `Update File Guard Config` | +| `GET` | `/api/v1/security/guard/rules` | `List Rules` | +| `POST` | `/api/v1/security/guard/rules` | `Create Rule` | +| `GET` | `/api/v1/security/guard/rules/builtin` | `List Builtin Rules` | +| `DELETE` | `/api/v1/security/guard/rules/by-id/{id}` | `Delete Rule By Pk` | +| `GET` | `/api/v1/security/guard/rules/export` | `Export Rules` | +| `POST` | `/api/v1/security/guard/rules/import` | `Import Rules` | +| `DELETE` | `/api/v1/security/guard/rules/{ruleId}` | `Delete Rule` | +| `PUT` | `/api/v1/security/guard/rules/{ruleId}` | `Update Rule` | +| `PUT` | `/api/v1/security/guard/rules/{ruleId}/toggle` | `Toggle Rule` | + +### Audit + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/audit/events` | `List Events` | + +### Activity + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/activity/feed` | `Unified activity feed (audit + approval + tool calls)` | + +### Notifications + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/notifications/summary` | `Aggregated counts for the sidebar attention badges` | + +### Workspaces + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/workspaces` | `List` | +| `POST` | `/api/v1/workspaces` | `Create` | +| `DELETE` | `/api/v1/workspaces/{id}` | `Delete` | +| `GET` | `/api/v1/workspaces/{id}` | `Get` | +| `PUT` | `/api/v1/workspaces/{id}` | `Update` | +| `GET` | `/api/v1/workspaces/{id}/access` | `Get Access` | +| `GET` | `/api/v1/workspaces/{id}/members` | `List Members` | +| `POST` | `/api/v1/workspaces/{id}/members` | `Add Member` | +| `DELETE` | `/api/v1/workspaces/{id}/members/{targetUserId}` | `Remove Member` | +| `PUT` | `/api/v1/workspaces/{id}/members/{targetUserId}` | `Update Member Role` | + +### Settings + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/settings` | `Get Settings` | +| `PUT` | `/api/v1/settings` | `Save Settings` | +| `GET` | `/api/v1/settings/language` | `Get Language` | +| `PUT` | `/api/v1/settings/language` | `Save Language` | +| `PUT` | `/api/v1/settings/sidecar` | `Save Sidecar` | + +### First-run Setup + +| Method | Path | Purpose / handler | +|---|---|---| +| `POST` | `/api/v1/setup/init` | `Init` | +| `GET` | `/api/v1/setup/onboarding-status` | `Get Onboarding Status` | +| `GET` | `/api/v1/setup/status` | `Get Status` | + +### System Health + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/system/browser-health` | `Browser launch diagnostics` | +| `GET` | `/api/v1/system/health` | `System health check` | + +### Dashboard + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/dashboard/cron-runs` | `Recent Runs` | +| `GET` | `/api/v1/dashboard/cron-runs/{cronJobId}` | `Cron Job Runs` | +| `GET` | `/api/v1/dashboard/overview` | `Overview` | +| `GET` | `/api/v1/dashboard/trend` | `Trend` | + +### Token Usage + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/token-usage` | `Get Summary` | + +### Models + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/models` | `List` | +| `POST` | `/api/v1/models` | `Create` | +| `GET` | `/api/v1/models/active` | `Get Active Model` | +| `PUT` | `/api/v1/models/active` | `Set Active Model` | +| `GET` | `/api/v1/models/by-type` | `List By Type` | +| `GET` | `/api/v1/models/catalog` | `Catalog` | +| `DELETE` | `/api/v1/models/custom-providers` | `Delete Custom Provider By Query` | +| `POST` | `/api/v1/models/custom-providers` | `Create Custom Provider` | +| `DELETE` | `/api/v1/models/custom-providers/{providerId}` | `Delete Custom Provider` | +| `GET` | `/api/v1/models/default` | `Get Default Model` | +| `GET` | `/api/v1/models/embedding/default` | `Get Default Embedding` | +| `POST` | `/api/v1/models/embedding/default` | `Set Default Embedding` | +| `POST` | `/api/v1/models/embedding/{modelId}/test` | `Test Embedding` | +| `GET` | `/api/v1/models/enabled` | `List Enabled` | +| `DELETE` | `/api/v1/models/{id}` | `Delete` | +| `GET` | `/api/v1/models/{id}` | `Get` | +| `PUT` | `/api/v1/models/{id}` | `Update` | +| `POST` | `/api/v1/models/{id}/default` | `Set Default` | +| `PUT` | `/api/v1/models/{providerId}/config` | `Update Provider Config` | +| `POST` | `/api/v1/models/{providerId}/disable` | `Disable Provider` | +| `POST` | `/api/v1/models/{providerId}/discover` | `Discover Models` | +| `POST` | `/api/v1/models/{providerId}/discover/apply` | `Apply Discovered Models` | +| `POST` | `/api/v1/models/{providerId}/enable` | `Enable Provider` | +| `DELETE` | `/api/v1/models/{providerId}/models` | `Remove Provider Model` | +| `POST` | `/api/v1/models/{providerId}/models` | `Add Provider Model` | +| `POST` | `/api/v1/models/{providerId}/models/test` | `Test Model` | +| `POST` | `/api/v1/models/{providerId}/test-connection` | `Test Connection` | + +### OAuth + +| Method | Path | Purpose / handler | +|---|---|---| +| `POST` | `/api/v1/oauth/anthropic/reload` | `Force re-detect credentials and refresh if near expiry` | +| `GET` | `/api/v1/oauth/anthropic/status` | `Read current Claude Code OAuth credential status from local disk` | +| `GET` | `/api/v1/oauth/openai/authorize` | `Authorize` | +| `POST` | `/api/v1/oauth/openai/callback-paste` | `Callback Paste` | +| `POST` | `/api/v1/oauth/openai/device/cancel` | `Device flow: cancel a pending session` | +| `POST` | `/api/v1/oauth/openai/device/poll` | `Device flow: poll for completion` | +| `POST` | `/api/v1/oauth/openai/device/start` | `Device flow: start — request user_code` | +| `POST` | `/api/v1/oauth/openai/refresh` | `Refresh` | +| `DELETE` | `/api/v1/oauth/openai/revoke` | `Revoke` | +| `GET` | `/api/v1/oauth/openai/status` | `Status` | + +### LLM Runtime + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/llm/provider-pool` | `Snapshot` | +| `POST` | `/api/v1/llm/provider-pool/{providerId}/reprobe` | `Reprobe` | + +### Tools + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/tools` | `List` | +| `POST` | `/api/v1/tools` | `Create` | +| `GET` | `/api/v1/tools/available` | `List Available` | +| `GET` | `/api/v1/tools/enabled` | `List Enabled` | +| `DELETE` | `/api/v1/tools/{id}` | `Delete` | +| `GET` | `/api/v1/tools/{id}` | `Get` | +| `PUT` | `/api/v1/tools/{id}` | `Update` | +| `PUT` | `/api/v1/tools/{id}/disclosure-tier` | `Set Disclosure Tier` | +| `PUT` | `/api/v1/tools/{id}/toggle` | `Toggle` | + +### MCP Servers + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/mcp/servers` | `List` | +| `POST` | `/api/v1/mcp/servers` | `Create` | +| `POST` | `/api/v1/mcp/servers/refresh` | `Refresh` | +| `DELETE` | `/api/v1/mcp/servers/{id}` | `Delete` | +| `GET` | `/api/v1/mcp/servers/{id}` | `Get` | +| `PUT` | `/api/v1/mcp/servers/{id}` | `Update` | +| `PUT` | `/api/v1/mcp/servers/{id}/disclosure-tier` | `Set Disclosure Tier` | +| `POST` | `/api/v1/mcp/servers/{id}/test` | `Test` | +| `PUT` | `/api/v1/mcp/servers/{id}/toggle` | `Toggle` | +| `GET` | `/api/v1/mcp/servers/{id}/tools` | `List Tools` | + +### ACP Endpoints + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/acp/endpoints` | `List ACP endpoints` | +| `POST` | `/api/v1/acp/endpoints` | `Create a custom ACP endpoint` | +| `DELETE` | `/api/v1/acp/endpoints/{id}` | `Delete an ACP endpoint (builtins are protected)` | +| `GET` | `/api/v1/acp/endpoints/{id}` | `Get ACP endpoint by id` | +| `PUT` | `/api/v1/acp/endpoints/{id}` | `Update an ACP endpoint` | +| `POST` | `/api/v1/acp/endpoints/{id}/test` | `Test ACP endpoint connection (initialize handshake)` | +| `PUT` | `/api/v1/acp/endpoints/{id}/toggle` | `Enable / disable an ACP endpoint` | + +### Skills + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/skills` | `List` | +| `POST` | `/api/v1/skills` | `Create` | +| `GET` | `/api/v1/skills/counts` | `Counts` | +| `POST` | `/api/v1/skills/curator/activate` | `Curator Activate` | +| `POST` | `/api/v1/skills/curator/dry-run` | `Curator Dry Run` | +| `POST` | `/api/v1/skills/curator/pause` | `Curator Pause` | +| `GET` | `/api/v1/skills/curator/reports` | `Curator Reports` | +| `GET` | `/api/v1/skills/curator/reports/{runId}` | `Curator Report` | +| `POST` | `/api/v1/skills/curator/resume` | `Curator Resume` | +| `GET` | `/api/v1/skills/curator/status` | `Curator Status` | +| `GET` | `/api/v1/skills/enabled` | `List Enabled` | +| `POST` | `/api/v1/skills/install/cancel/{taskId}` | `Cancel` | +| `GET` | `/api/v1/skills/install/hub/search` | `Search Hub` | +| `POST` | `/api/v1/skills/install/start` | `Start Install` | +| `GET` | `/api/v1/skills/install/status/{taskId}` | `Get Status` | +| `POST` | `/api/v1/skills/install/upload` | `Upload Zip` | +| `DELETE` | `/api/v1/skills/install/{skillName}` | `Uninstall` | +| `GET` | `/api/v1/skills/prompt-preview` | `Prompt Preview` | +| `GET` | `/api/v1/skills/runtime/active` | `Get Active Skills` | +| `POST` | `/api/v1/skills/runtime/refresh` | `Refresh Runtime` | +| `GET` | `/api/v1/skills/runtime/status` | `Get Runtime Status` | +| `GET` | `/api/v1/skills/summary` | `Summary` | +| `POST` | `/api/v1/skills/sync-files` | `Re-sync every skill's bundle files (admin)` | +| `POST` | `/api/v1/skills/synthesize-from-conversation` | `Synthesize From Conversation` | +| `GET` | `/api/v1/skills/type/{skillType}` | `List By Type` | +| `DELETE` | `/api/v1/skills/{id}` | `Delete` | +| `GET` | `/api/v1/skills/{id}` | `Get` | +| `PUT` | `/api/v1/skills/{id}` | `Update` | +| `POST` | `/api/v1/skills/{id}/archive` | `Archive` | +| `GET` | `/api/v1/skills/{id}/employees` | `List agents that can use this skill (RFC-090 §14.2)` | +| `POST` | `/api/v1/skills/{id}/export-workspace` | `Export To Workspace` | +| `GET` | `/api/v1/skills/{id}/lessons` | `Read per-skill LESSONS.md (RFC-090 §11.4)` | +| `POST` | `/api/v1/skills/{id}/lessons/clear` | `Clear all lessons for a skill (RFC-090 §11.4)` | +| `POST` | `/api/v1/skills/{id}/pin` | `Pin` | +| `GET` | `/api/v1/skills/{id}/requirements` | `Pre-flight requirement statuses for a skill (RFC-090)` | +| `POST` | `/api/v1/skills/{id}/rescan` | `Rescan` | +| `POST` | `/api/v1/skills/{id}/restore` | `Restore` | +| `POST` | `/api/v1/skills/{id}/sync-files` | `Re-sync this skill's bundle files from DB → local workspace cache` | +| `PUT` | `/api/v1/skills/{id}/toggle` | `Toggle` | +| `GET` | `/api/v1/skills/{id}/workspace` | `Get Workspace Info` | +| `GET` | `/api/v1/skills/{skillId}/secrets` | `List secret keys + masked previews for a skill` | +| `POST` | `/api/v1/skills/{skillId}/secrets` | `Upsert a secret value (empty value deletes it)` | +| `DELETE` | `/api/v1/skills/{skillId}/secrets/{key}` | `Delete a single secret by key` | + +### Skill Templates + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/skill-templates` | `List skill templates (RFC-091)` | +| `GET` | `/api/v1/skill-templates/{id}` | `Get a single skill template` | +| `POST` | `/api/v1/skill-templates/{id}/instantiate` | `Instantiate a template into a skill` | + +### Plugins + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/plugins` | `List all plugins` | +| `GET` | `/api/v1/plugins/{name}` | `Get plugin detail` | +| `PUT` | `/api/v1/plugins/{name}/config` | `Update plugin configuration` | +| `POST` | `/api/v1/plugins/{name}/disable` | `Disable a plugin` | +| `POST` | `/api/v1/plugins/{name}/enable` | `Enable a plugin` | + +### LLM Wiki + +| Method | Path | Purpose / handler | +|---|---|---| +| `POST` | `/api/v1/wiki/admin/backfill-tokens` | `Force-run the token-count backfill batch now` | +| `POST` | `/api/v1/wiki/admin/kb/{kbId}/rebuild-overview` | `Ensure overview/log scaffold + rebuild overview stats now` | +| `GET` | `/api/v1/wiki/chunks/{chunkId}/pages` | `Pages By Chunk Id` | +| `DELETE` | `/api/v1/wiki/hot-cache/{kbId}` | `Soft-delete the hot cache row` | +| `GET` | `/api/v1/wiki/hot-cache/{kbId}` | `Get the current hot cache snapshot for a KB` | +| `POST` | `/api/v1/wiki/hot-cache/{kbId}/regenerate` | `Schedule a manual rebuild of the hot cache` | +| `GET` | `/api/v1/wiki/kb/{kbId}/jobs` | `Get Jobs` | +| `GET` | `/api/v1/wiki/kb/{kbId}/pages/{pageId}/citations` | `Page Citations` | +| `GET` | `/api/v1/wiki/kb/{kbId}/pages/{slugA}/relation/{slugB}` | `Explain Relation` | +| `POST` | `/api/v1/wiki/kb/{kbId}/pages/{slug}/enrich` | `Enrich Page` | +| `GET` | `/api/v1/wiki/kb/{kbId}/pages/{slug}/related` | `Related Pages` | +| `POST` | `/api/v1/wiki/kb/{kbId}/pages/{slug}/repair` | `Repair Page` | +| `POST` | `/api/v1/wiki/kb/{kbId}/search-preview` | `Search Preview` | +| `GET` | `/api/v1/wiki/kb/{kbId}/stats` | `Kb Stats` | +| `GET` | `/api/v1/wiki/knowledge-bases` | `List KBs` | +| `POST` | `/api/v1/wiki/knowledge-bases` | `Create KB` | +| `GET` | `/api/v1/wiki/knowledge-bases/agent/{agentId}` | `List KBs By Agent` | +| `GET` | `/api/v1/wiki/knowledge-bases/bindable` | `List Bindable KBs` | +| `DELETE` | `/api/v1/wiki/knowledge-bases/{id}` | `Delete KB` | +| `GET` | `/api/v1/wiki/knowledge-bases/{id}` | `Get KB` | +| `PUT` | `/api/v1/wiki/knowledge-bases/{id}` | `Update KB` | +| `GET` | `/api/v1/wiki/knowledge-bases/{id}/config` | `Get Config` | +| `PUT` | `/api/v1/wiki/knowledge-bases/{id}/config` | `Update Config` | +| `GET` | `/api/v1/wiki/knowledge-bases/{id}/page-type-profile` | `Get Page Type Profile` | +| `PUT` | `/api/v1/wiki/knowledge-bases/{id}/page-type-profile` | `Save Page Type Profile` | +| `POST` | `/api/v1/wiki/knowledge-bases/{id}/page-type-profile/reset-default` | `Reset Page Type Profile` | +| `POST` | `/api/v1/wiki/knowledge-bases/{id}/page-type-profile/validate` | `Validate Page Type Profile` | +| `POST` | `/api/v1/wiki/knowledge-bases/{id}/scan` | `Scan Directory` | +| `PUT` | `/api/v1/wiki/knowledge-bases/{id}/source-directory` | `Set Source Directory` | +| `GET` | `/api/v1/wiki/knowledge-bases/{id}/source-watcher` | `Get Source Watcher` | +| `POST` | `/api/v1/wiki/knowledge-bases/{id}/source-watcher/scan` | `Trigger Source Watcher` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/agents/{agentId}/page-type-permissions` | `List Page Type Permissions` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/agents/{agentId}/page-type-permissions` | `Save Page Type Permission` | +| `DELETE` | `/api/v1/wiki/knowledge-bases/{kbId}/agents/{agentId}/page-type-permissions/{id}` | `Delete Page Type Permission` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/lint/broken-links` | `Get Broken Links Report` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/lint/broken-links` | `Start Broken Links Scan` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/lint/broken-links/jobs/{jobId}` | `Get Broken Links Job` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pages` | `List Pages` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/archived` | `List Archived Pages` | +| `DELETE` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/batch` | `Batch Delete Pages` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/refs` | `List Page Refs` | +| `DELETE` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}` | `Delete Page` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}` | `Get Page` | +| `PUT` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}` | `Update Page` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}/archive` | `Archive Page` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}/backlinks` | `Get Backlinks` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}/rename` | `Rename Page` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}/unarchive` | `Unarchive Page` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pipeline-runs/{runId}` | `Get Pipeline Run` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pipelines` | `List Pipelines` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/pipelines` | `Save Pipeline` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/pipelines/validate` | `Validate Pipeline` | +| `DELETE` | `/api/v1/wiki/knowledge-bases/{kbId}/pipelines/{id}` | `Delete Pipeline` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pipelines/{id}/runs` | `List Pipeline Runs` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/process` | `Process KB` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/processing-status` | `Get Processing Status` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/progress` | `Subscribe Progress` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/raw` | `List Raw` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/raw/text` | `Add Raw Text` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/raw/upload` | `Upload Raw` | +| `DELETE` | `/api/v1/wiki/knowledge-bases/{kbId}/raw/{rawId}` | `Delete Raw` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/raw/{rawId}/cancel` | `Cancel Raw` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/raw/{rawId}/download` | `Download Raw` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/raw/{rawId}/reprocess` | `Reprocess Raw` | +| `GET` | `/api/v1/wiki/pages/lookup` | `Lookup Pages` | +| `GET` | `/api/v1/wiki/raw/{rawId}/pages` | `Pages By Raw Id` | +| `POST` | `/api/v1/wiki/research/start` | `Start Research` | +| `GET` | `/api/v1/wiki/research/stream/{sessionId}` | `Stream` | +| `GET` | `/api/v1/wiki/transformations` | `List transformations available to a KB` | +| `POST` | `/api/v1/wiki/transformations` | `Create` | +| `GET` | `/api/v1/wiki/transformations/runs` | `List Runs` | +| `DELETE` | `/api/v1/wiki/transformations/runs/{runId}` | `Delete Run` | +| `GET` | `/api/v1/wiki/transformations/runs/{runId}` | `Get Run` | +| `POST` | `/api/v1/wiki/transformations/runs/{runId}/cancel` | `Cancel a still-running transformation run` | +| `POST` | `/api/v1/wiki/transformations/runs/{runId}/save-as-page` | `Save a completed run's output as a synthesis wiki page` | +| `DELETE` | `/api/v1/wiki/transformations/{id}` | `Delete` | +| `GET` | `/api/v1/wiki/transformations/{id}` | `Get` | +| `PUT` | `/api/v1/wiki/transformations/{id}` | `Update` | +| `POST` | `/api/v1/wiki/transformations/{id}/aggregate` | `Aggregate all completed runs of a template into one KB-level synthesis page` | +| `POST` | `/api/v1/wiki/transformations/{id}/apply` | `Run a transformation against a raw material or wiki page` | + +### Memory + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/memory/{agentId}/dream/events` | `Subscribe to dream events (SSE)` | +| `GET` | `/api/v1/memory/{agentId}/dream/morning-card` | `Get morning card for current user + agent` | +| `POST` | `/api/v1/memory/{agentId}/dream/morning-card/seen` | `Mark morning card as seen` | +| `GET` | `/api/v1/memory/{agentId}/dream/reports` | `List dream reports (paginated, newest first)` | +| `GET` | `/api/v1/memory/{agentId}/dream/reports/{reportId}` | `Get a single dream report by ID` | +| `POST` | `/api/v1/memory/{agentId}/dream/reports/{reportId}/entries/{key}/confirm` | `Confirm a memory entry (no-op acknowledgment)` | +| `POST` | `/api/v1/memory/{agentId}/dream/reports/{reportId}/entries/{key}/edit` | `Edit a memory entry — writes back to the target memory file with user-edited metadata` | +| `GET` | `/api/v1/memory/{agentId}/dreaming/candidates` | `Get Dreaming Candidates` | +| `GET` | `/api/v1/memory/{agentId}/dreaming/dreams` | `Get Dreams` | +| `POST` | `/api/v1/memory/{agentId}/dreaming/focused` | `Trigger Focused Dream` | +| `GET` | `/api/v1/memory/{agentId}/dreaming/status` | `Get Dreaming Status` | +| `POST` | `/api/v1/memory/{agentId}/emergence` | `Trigger Emergence` | +| `GET` | `/api/v1/memory/{agentId}/facts` | `List facts for an agent` | +| `GET` | `/api/v1/memory/{agentId}/facts/contradictions` | `List unresolved contradictions` | +| `POST` | `/api/v1/memory/{agentId}/facts/contradictions/{contradictionId}/resolve` | `Resolve a contradiction (KEEP_A / KEEP_B / MERGE / IGNORE)` | +| `POST` | `/api/v1/memory/{agentId}/facts/{factId}/feedback` | `Submit feedback on a fact (HELPFUL/UNHELPFUL)` | +| `POST` | `/api/v1/memory/{agentId}/facts/{factId}/forget` | `Forget a fact — writes canonical metadata, rebuilds projection` | +| `POST` | `/api/v1/memory/{agentId}/summarize/{conversationId}` | `Trigger Summarize` | + +### Goals + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/goals` | `List goals (optionally filtered by status)` | +| `POST` | `/api/v1/goals` | `Create a persistent goal for a conversation` | +| `GET` | `/api/v1/goals/by-conversation/{conversationId}` | `Get the active goal bound to a conversation (or null)` | +| `GET` | `/api/v1/goals/{id}` | `Get goal detail by id` | +| `PATCH` | `/api/v1/goals/{id}` | `Sparse update of a non-terminal goal` | +| `POST` | `/api/v1/goals/{id}/abandon` | `Abandon a goal (terminal)` | +| `POST` | `/api/v1/goals/{id}/criteria` | `Append a sub-criterion to an active goal` | +| `GET` | `/api/v1/goals/{id}/events` | `Get the event timeline for a goal` | +| `POST` | `/api/v1/goals/{id}/pause` | `Pause an active goal` | +| `POST` | `/api/v1/goals/{id}/resume` | `Resume a paused goal` | + +### Cron Jobs + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/cron-jobs` | `List` | +| `POST` | `/api/v1/cron-jobs` | `Create` | +| `GET` | `/api/v1/cron-jobs/active-runs` | `Active Runs` | +| `DELETE` | `/api/v1/cron-jobs/{id}` | `Delete` | +| `GET` | `/api/v1/cron-jobs/{id}` | `Get` | +| `PUT` | `/api/v1/cron-jobs/{id}` | `Update` | +| `POST` | `/api/v1/cron-jobs/{id}/run` | `Run Now` | +| `PUT` | `/api/v1/cron-jobs/{id}/toggle` | `Toggle` | + +### Triggers + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/triggers` | `List triggers in the caller's workspace.` | +| `POST` | `/api/v1/triggers` | `Create a trigger; if enabled, registers it with the scheduler.` | +| `POST` | `/api/v1/triggers/events` | `Ingest one event envelope; returns per-trigger fire / drop summary.` | +| `DELETE` | `/api/v1/triggers/{id}` | `Delete a trigger and unregister its schedule.` | +| `GET` | `/api/v1/triggers/{id}` | `Get a trigger by id, scoped to the caller's workspace.` | +| `PUT` | `/api/v1/triggers/{id}` | `Update a trigger; pattern_version bumps when the cron expression changes.` | + +### Workflows + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/workflows` | `List workflows in the workspace` | +| `POST` | `/api/v1/workflows` | `Create a workflow row (draft starts empty).` | +| `POST` | `/api/v1/workflows/draft/generate` | `Generate a workflow draft from a natural-language description.` | +| `POST` | `/api/v1/workflows/draft/preview-compile` | `Compile arbitrary draft JSON without persisting — used by the template picker / generator preview to surface real ACL + schema diagnostics before a workflow row exists.` | +| `GET` | `/api/v1/workflows/draft/templates` | `List the canonical workflow templates the generator can apply directly.` | +| `GET` | `/api/v1/workflows/runs/paused` | `List paused runs across the workspace so operators can resume them.` | +| `GET` | `/api/v1/workflows/runs/{runId}` | `Inspect a single run with its step rows for replay / debugging.` | +| `POST` | `/api/v1/workflows/runs/{runId}/resume` | `Resume a paused workflow run with the given outcome.` | +| `DELETE` | `/api/v1/workflows/{id}` | `Soft-delete a workflow row.` | +| `GET` | `/api/v1/workflows/{id}` | `Get a workflow by id (includes inline draft + latest published graph).` | +| `PUT` | `/api/v1/workflows/{id}` | `Update workflow metadata (name / description / enabled).` | +| `POST` | `/api/v1/workflows/{id}/compile` | `Compile the draft and surface diagnostics without persisting a revision.` | +| `PUT` | `/api/v1/workflows/{id}/draft` | `Save the inline draft graph_json without compiling.` | +| `POST` | `/api/v1/workflows/{id}/publish` | `Compile the draft and persist a new revision pointed at by latest_revision_id.` | +| `GET` | `/api/v1/workflows/{id}/runs` | `List the most recent runs for a workflow.` | + +### Channels + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/channels` | `List` | +| `POST` | `/api/v1/channels` | `Create` | +| `GET` | `/api/v1/channels/health` | `Health All` | +| `POST` | `/api/v1/channels/preflight` | `Pre-flight: validate draft channel config without persisting` | +| `POST` | `/api/v1/channels/qrcode/{channelType}/begin` | `Begin` | +| `GET` | `/api/v1/channels/qrcode/{channelType}/status` | `Status` | +| `GET` | `/api/v1/channels/status` | `Status` | +| `GET` | `/api/v1/channels/type/{channelType}` | `List By Type` | +| `GET` | `/api/v1/channels/webchat/config` | `Get Config` | +| `POST` | `/api/v1/channels/webchat/stream` | `Chat Stream` | +| `POST` | `/api/v1/channels/webhook/dingtalk` | `Dingtalk Webhook` | +| `POST` | `/api/v1/channels/webhook/dingtalk/register/begin` | `Dingtalk Register Begin` | +| `GET` | `/api/v1/channels/webhook/dingtalk/register/status` | `Dingtalk Register Status` | +| `POST` | `/api/v1/channels/webhook/discord` | `Discord Webhook` | +| `POST` | `/api/v1/channels/webhook/feishu` | `Feishu Webhook` | +| `POST` | `/api/v1/channels/webhook/feishu/register/begin` | `Feishu Register Begin` | +| `GET` | `/api/v1/channels/webhook/feishu/register/status` | `Feishu Register Status` | +| `POST` | `/api/v1/channels/webhook/slack` | `Slack Webhook` | +| `GET` | `/api/v1/channels/webhook/status` | `Status` | +| `POST` | `/api/v1/channels/webhook/telegram` | `Telegram Webhook` | +| `POST` | `/api/v1/channels/webhook/wecom` | `Wecom Webhook` | +| `GET` | `/api/v1/channels/webhook/weixin/qrcode` | `Weixin Qrcode` | +| `GET` | `/api/v1/channels/webhook/weixin/qrcode/status` | `Weixin Qrcode Status` | +| `DELETE` | `/api/v1/channels/{id}` | `Delete` | +| `GET` | `/api/v1/channels/{id}` | `Get` | +| `PUT` | `/api/v1/channels/{id}` | `Update` | +| `GET` | `/api/v1/channels/{id}/health` | `Health` | +| `PUT` | `/api/v1/channels/{id}/toggle` | `Toggle` | + +### Datasources + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/datasources` | `List` | +| `POST` | `/api/v1/datasources` | `Create` | +| `DELETE` | `/api/v1/datasources/{id}` | `Delete` | +| `GET` | `/api/v1/datasources/{id}` | `Get` | +| `PUT` | `/api/v1/datasources/{id}` | `Update` | +| `POST` | `/api/v1/datasources/{id}/test` | `Test Connection` | +| `PUT` | `/api/v1/datasources/{id}/toggle` | `Toggle` | + +### Speech to Text + +| Method | Path | Purpose / handler | +|---|---|---| +| `POST` | `/api/v1/stt/transcribe` | `Transcribe` | + +### Text to Speech + +| Method | Path | Purpose / handler | +|---|---|---| +| `POST` | `/api/v1/tts/synthesize` | `Synthesize` | +| `GET` | `/api/v1/tts/voices` | `List Voices` | + +### Generated Files + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/files/generated/{id}` | `Download a tool-generated file by its one-time id` | + +### Plans + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/plans` | `List By Agent` | +| `GET` | `/api/v1/plans/{id}` | `Get Plan` | + +### Feature Flags + +| Method | Path | Purpose / handler | +|---|---|---| +| `GET` | `/api/v1/feature-flags` | `List` | +| `PUT` | `/api/v1/feature-flags/{flagKey}` | `Update` | diff --git a/mateclaw-server/src/main/resources/docs/en/architecture.md b/mateclaw-server/src/main/resources/docs/en/architecture.md index 594fa940..319f94ec 100644 --- a/mateclaw-server/src/main/resources/docs/en/architecture.md +++ b/mateclaw-server/src/main/resources/docs/en/architecture.md @@ -151,7 +151,7 @@ This is the most important thing to know if you're contributing to the backend. ### Goal-evaluation node (1.4.0+) -The graph (both ReAct and Plan-Execute) now runs a `GoalEvaluationNode` after `FinalAnswerNode` has streamed the final answer: it scores how completely the goal was met and can optionally inject an auto-followup message to keep pushing any unmet goals forward. +The graph (both ReAct and Plan-Execute) now runs a `GoalEvaluationNode` after `FinalAnswerNode` has streamed the final answer: since 1.5.0 it judges the goal's checklist criterion by criterion (bootstrap / verdict modes), treats the goal as complete **only when every criterion passes**, and can optionally inject an auto-followup message targeting the remaining criteria to keep pushing any unmet goal forward. ### Other 1.4.0 runtime changes diff --git a/mateclaw-server/src/main/resources/docs/en/channels.md b/mateclaw-server/src/main/resources/docs/en/channels.md index 317d2b92..327536ea 100644 --- a/mateclaw-server/src/main/resources/docs/en/channels.md +++ b/mateclaw-server/src/main/resources/docs/en/channels.md @@ -35,6 +35,11 @@ v1.4.0 makes Feishu a first-class channel — interactive cards, streaming cards Feishu specifics are spelled out in the [Feishu](#feishu-lark) section below. ::: +::: tip 1.5.0 channel improvements +- **Shared inbound media pipeline** — **WeChat and WeCom** are currently wired onto a shared inbound-media downloader + magic-byte type detection + exponential-backoff retry (other IM channels to follow). File types are decided from content bytes (no more hardcoded `image/*`); HEIC / WEBP / DOCX / XLSX and friends are detected correctly, with automatic retry on download failure. +- **Feishu: follow-up text auto-carries recent files (#201)** — send a file in a Feishu chat first (even without @-mentioning the employee), then a text message, and the cached files are auto-attached as content parts for the employee — 5 files per chat, 60-minute TTL. +::: + --- ## The nine channels diff --git a/mateclaw-server/src/main/resources/docs/en/chat.md b/mateclaw-server/src/main/resources/docs/en/chat.md index 4c23db3c..b7eb1a8c 100644 --- a/mateclaw-server/src/main/resources/docs/en/chat.md +++ b/mateclaw-server/src/main/resources/docs/en/chat.md @@ -50,6 +50,8 @@ One of the questions MateClaw tries to answer with its chat UI is: **should you Trust is earned by showing the work. MateClaw shows the work. +**Execution-plan & tool-call detail viewer (1.5.0).** Every plan step and every tool-call row gets a "view details" icon on the right. Click it for a frosted-glass dialog showing the **full request arguments and response output** — the parts the inline preview truncates — with copy buttons for request and response, and a status badge (in progress / completed / failed / pending). The data lives in message metadata, so plan steps and tool calls stay readable after a page reload. + --- ## Multi-channel realtime sync @@ -86,6 +88,10 @@ Upload limits, default: Images handed to a vision-capable model get attached for visual understanding. PDFs and DOCX files go through text extraction (with OCR fallback for scanned material). Everything the agent reads lands in its context for that turn. +::: tip Tool-generated files: download links survive restarts (1.5.0, #243) +Files a worker generates via tools (documents / images / audio…) are now **persisted to disk** under `data/generated-files/`, with a 7-day retention window + a 6-hour cleanup sweep and an in-memory LRU on top — download links keep working after a restart and are no longer bounded by the old 10-minute in-memory window. The frontend intercepts `/api/v1/files/generated/{id}` downloads via a global click delegator: success goes through an authenticated fetch → blob download; failure (404/410/expired) just shows a toast, **so a dead link no longer wedges the whole page**. +::: + ### Primary model can't see images? "Multimodal sidecar" routing ::: tip Added in 1.3.0 diff --git a/mateclaw-server/src/main/resources/docs/en/console.md b/mateclaw-server/src/main/resources/docs/en/console.md index 5bb80269..dbf072af 100644 --- a/mateclaw-server/src/main/resources/docs/en/console.md +++ b/mateclaw-server/src/main/resources/docs/en/console.md @@ -111,7 +111,7 @@ Features: - `POST /api/v1/chat/stream` — SSE streaming (native fetch) - `POST /api/v1/chat/upload` - `POST /api/v1/chat/{conversationId}/stop` -- `POST /api/v1/approvals/{id}/resolve` +- approval resolution is sent as `/approve` or `/deny` through `POST /api/v1/chat/stream` - `GET /api/v1/chat/{conversationId}/pending-approvals` - `GET /api/v1/conversations` — list - `GET /api/v1/conversations/{id}/messages` diff --git a/mateclaw-server/src/main/resources/docs/en/desktop.md b/mateclaw-server/src/main/resources/docs/en/desktop.md index 36565129..adcfc8a1 100644 --- a/mateclaw-server/src/main/resources/docs/en/desktop.md +++ b/mateclaw-server/src/main/resources/docs/en/desktop.md @@ -147,7 +147,8 @@ cd ../mateclaw-server mvn clean package -DskipTests # 3. Copy JAR to desktop resources -cp target/mateclaw-server.jar ../mateclaw-desktop/resources/app.jar +JAR_FILE=$(ls -1 target/mateclaw-server-*.jar | grep -v sources | head -n 1) +cp "$JAR_FILE" ../mateclaw-desktop/resources/app.jar # 4. Download platform-specific JRE cd ../mateclaw-desktop diff --git a/mateclaw-server/src/main/resources/docs/en/doctor.md b/mateclaw-server/src/main/resources/docs/en/doctor.md index 1b91c503..9d63152c 100644 --- a/mateclaw-server/src/main/resources/docs/en/doctor.md +++ b/mateclaw-server/src/main/resources/docs/en/doctor.md @@ -1,233 +1,71 @@ # Doctor -**The Doctor page answers one question: is this thing actually working right now?** +Doctor is the in-app health drawer. It reports the current local instance status from the backend health service; it is not a separate scheduled diagnostics subsystem. -MateClaw has a lot of moving parts — the backend, the database, model providers, MCP servers, IM channels, cron jobs, memory consolidation, wiki digestion. When something goes sideways, the symptom ("my agent isn't responding") usually has a specific cause ("the DashScope API key expired yesterday") buried several layers away from where you'd notice. Doctor is a single page that runs every check at once and tells you what's green, what's yellow, and what's red. +Open it from the layout status button / Settings area. The drawer calls the backend each time it opens or when you click refresh. -Open it with `Settings → Doctor` or just navigate to `/doctor`. +## Current Backend API ---- - -## What it checks - -Each check runs independently and reports one of three states: - -- **✅ OK** — everything is working as expected -- **⚠️ Warning** — working but degraded (e.g., using a fallback provider, nearing a quota, a non-critical cron job is paused) -- **❌ Error** — broken in a way you need to fix - -### Core infrastructure - -| Check | What it verifies | -|-------|-----------------| -| **Backend version** | MateClaw is running and reports its version | -| **Database connection** | The configured datasource is reachable and queries succeed | -| **Database schema** | All expected `mate_*` tables exist; migration state is clean | -| **Disk usage** | The data directory has enough free space (warns under 20%, errors under 5%) | -| **H2 console exposure** | Warns if the H2 console is enabled in production profile | -| **JWT secret strength** | Warns if the default JWT secret is still in use | - -### Models - -| Check | What it verifies | -|-------|-----------------| -| **Active model** | A default model config exists and is enabled | -| **Provider connectivity** | Each enabled provider has passed a recent connection test | -| **API key presence** | Keys are configured for every cloud provider marked enabled | -| **Ollama reachability** | If Ollama is configured, the local instance is reachable | - -### Agents & tools - -| Check | What it verifies | -|-------|-----------------| -| **Tool registry** | Built-in and MCP tools are loaded without errors | -| **Tool Guard config** | At least one Tool Guard rule exists (warns if `default-policy: allow` is used) | -| **Default agent** | The default agent exists and is enabled | -| **Agent templates** | Built-in templates are present and loadable | - -### Memory & wiki - -| Check | What it verifies | -|-------|-----------------| -| **Memory consolidation cron** | Per-agent consolidation cron jobs exist and are enabled | -| **Last consolidation run** | Warns if no consolidation has run in the past 7 days | -| **Wiki digestion queue** | No stuck `pending` or `processing` raw materials | -| **Wiki schema** | `mate_wiki_*` tables exist and are queryable | - -### Channels - -| Check | What it verifies | -|-------|-----------------| -| **Channel health monitor** | Every enabled channel reports `connected` or is actively reconnecting | -| **Per-channel status** | For each IM channel, connection state and last error | -| **Webhook URL reachability** | Warns if a webhook-mode channel has no public URL configured in production | - -### MCP - -| Check | What it verifies | -|-------|-----------------| -| **Enabled MCP servers** | Every enabled MCP server is `connected` | -| **Tool count** | Each connected server reports at least one tool | -| **Orphaned subprocesses** | No stdio subprocesses outlive their parent client | - -### Cron & async - -| Check | What it verifies | -|-------|-----------------| -| **Cron engine** | The scheduled-task executor is running | -| **Overdue jobs** | Warns if any job is more than 24 hours overdue | -| **Async task queue** | `mate_async_task` queue length is within normal bounds | - ---- - -## How checks run - -Doctor runs two ways: - -### On demand - -Click **Run All Checks** on the Doctor page. The button fires off every check in parallel; the UI streams results back as each finishes. Most checks complete in under a second; the slowest (MCP server connection tests) can take 10–30 seconds. - -### On a schedule - -Doctor also runs **automatically every 15 minutes** in the background. Results are cached in memory and persisted to `mate_doctor_check` so the page loads instantly when you open it — you're seeing the last cached state until you click **Run All Checks**. - -You can tune the schedule in `application.yml`: - -```yaml -mateclaw: - doctor: - enabled: true - schedule-minutes: 15 - cache-ttl-minutes: 10 +```bash +curl http://localhost:18088/api/v1/system/health \ + -H "Authorization: Bearer " ``` ---- - -## Reading results - -Each check returns: +Response shape: ```json { - "name": "DashScope Provider Connectivity", - "category": "Models", - "status": "ok", - "message": "Connection test succeeded (latency: 240ms)", - "lastChecked": "2026-04-11T14:30:22", - "details": { - "provider": "dashscope", - "baseUrl": "https://dashscope.aliyuncs.com", - "latencyMs": 240 - }, - "fixUrl": "/settings/models" + "code": 200, + "msg": "success", + "data": { + "overall": "healthy", + "checks": [ + { + "name": "default-model", + "status": "healthy", + "message": "Default model: qwen-plus", + "action": null + } + ] + } } ``` -The UI renders: +`overall` is one of `healthy`, `warning`, or `error`. Each check has: -- **Category tabs** at the top — Infrastructure, Models, Agents, Memory, Wiki, Channels, MCP, Cron -- **Status counters** — green / yellow / red -- **Check list** — name, status, message, time since last check, "View details" expand, optional "Fix" button that navigates to the relevant settings page -- **History graph** — (for each check) a sparkline of the last 50 runs so you can see flapping checks at a glance +| Field | Meaning | +|---|---| +| `name` | Stable check key such as `default-model`, `database`, `browser`, `provider:`, `mcp:` | +| `status` | `healthy`, `warning`, or `error` | +| `message` | Short diagnostic text shown in the drawer | +| `action` | Optional `{ label, route }` hint for where to fix the issue | ---- +## What It Checks Today -## Fix buttons +The current `SystemHealthService` checks: -For actionable checks, the Doctor row includes a **Fix** button that navigates directly to the relevant settings page: +| Check | What it verifies | Typical action | +|---|---|---| +| Default model | A default model is configured and loadable | `/settings/models` | +| Providers | API-key providers are configured when required | `/settings/models` | +| Enabled MCP servers | Enabled MCP servers have a successful connection result | `/settings/mcp-servers` | +| Database initialization | First-run bootstrap has completed | `/setup` | +| Browser diagnostics | Browser launch pre-flight for browser tooling | `/api/v1/system/browser-health` | -- Model provider failure → `Settings → Models` -- Tool Guard `default-policy: allow` → `Settings → Security & Approval` -- H2 console in production → `Settings → System` (or show a config snippet to copy) -- JWT default secret → `Settings → System` (or show a config snippet) -- MCP server disconnected → `Tools → MCP Servers` -- Stuck wiki digestion → `Wiki → [KB] → Raw Material` - -Clicking Fix takes you to the exact page where you can address the issue. When possible, the target page is pre-filtered to highlight the failing item. - ---- - -## Doctor API +There is also a direct browser diagnostics endpoint: ```bash -# Run all checks (synchronous) -curl http://localhost:18088/api/v1/doctor/run \ - -H "Authorization: Bearer " - -# Get the cached check results -curl http://localhost:18088/api/v1/doctor/checks \ - -H "Authorization: Bearer " - -# Run a specific category only -curl http://localhost:18088/api/v1/doctor/run?category=models \ - -H "Authorization: Bearer " - -# Historical results -curl "http://localhost:18088/api/v1/doctor/history?check=dashscope-connectivity&limit=50" \ +curl http://localhost:18088/api/v1/system/browser-health \ -H "Authorization: Bearer " ``` ---- +## Not Implemented In The Current Source Tree -## Using Doctor in operations +Older docs mentioned `/api/v1/doctor/run`, `/api/v1/doctor/checks`, `/api/v1/doctor/history`, scheduled background Doctor runs, `mate_doctor_check`, and `mate_doctor_check_history`. Those endpoints and tables are not present in the current backend source. Use `/api/v1/system/health` for the current health surface. -### As a health endpoint for uptime monitoring +## Related Pages -Point your external uptime monitor (UptimeRobot, Pingdom, internal Prometheus) at: - -``` -GET /api/v1/doctor/checks -``` - -The endpoint returns HTTP 200 with JSON summary — aggregate pass/fail counts and per-category breakdown. Your monitor should alert when `errorCount > 0`. - -For a simpler health check, use: - -``` -GET /actuator/health -``` - -which follows Spring Boot's standard format. - -### During upgrades - -After deploying a new MateClaw version, run Doctor to verify nothing regressed: - -1. Open `/doctor` -2. Click **Run All Checks** -3. Look for any yellows or reds that weren't there before -4. Pay special attention to **Database schema** — a mismatched schema after an upgrade usually means a migration didn't run - -### When something's broken - -Doctor is the first place to look when a user reports "it's not working". Open the page, see which check is red, click **Fix**, solve the problem. If no check is red but the user still has an issue, it's probably something Doctor doesn't cover yet — file it as a [GitHub issue](https://github.com/matevip/mateclaw/issues) so we can add a check. - ---- - -## Data model - -**`mate_doctor_check`** - -| Column | Purpose | -|--------|---------| -| `id` | Primary key | -| `name` | Check name | -| `category` | Check category | -| `status` | `ok` / `warning` / `error` | -| `message` | Human-readable message | -| `details` | JSON blob of extra detail | -| `last_checked` | When it last ran | -| `run_duration_ms` | How long the check took | -| `workspace_id` | Scoping (nullable for global checks) | - -Historical results go into `mate_doctor_check_history` with the same columns plus a retention cleanup job. - ---- - -## Next - -- [Admin Console](./console) — the UI Doctor lives in -- [Configuration](./config) — things you might configure based on Doctor warnings -- [Security & Approval](./security) — what Doctor checks in Tool Guard -- [Contributing](./contributing) — add a new Doctor check if something's missing +- [API Reference](./api) - source-aligned route inventory +- [Models](./models) - model/provider setup +- [MCP](./mcp) - MCP server setup +- [Security & Approval](./security) - Tool Guard and approval behavior diff --git a/mateclaw-server/src/main/resources/docs/en/faq.md b/mateclaw-server/src/main/resources/docs/en/faq.md index 524bf628..a6e628e4 100644 --- a/mateclaw-server/src/main/resources/docs/en/faq.md +++ b/mateclaw-server/src/main/resources/docs/en/faq.md @@ -215,9 +215,10 @@ Edit `PROFILE.md` or `MEMORY.md` directly in the agent workspace view. Lock page ### I approved a tool call but the agent didn't resume 1. Is `AWAITING_APPROVAL` still set? (`GET /api/v1/agents/{id}`) -2. Did the approval actually persist? (`GET /api/v1/approvals/{id}`) +2. Does the waiting conversation still have a pending approval? (`GET /api/v1/chat/{conversationId}/pending-approvals`) 3. Are there errors in the agent log around the replay attempt? -4. If replay failed, the agent should surface an error in the chat +4. Did the approve/reject message go through the same conversation via `POST /api/v1/chat/stream`? +5. If replay failed, the agent should surface an error in the chat ### I want to batch-approve future tool calls from this agent diff --git a/mateclaw-server/src/main/resources/docs/en/goals.md b/mateclaw-server/src/main/resources/docs/en/goals.md index 73c5aa6c..5ded56fc 100644 --- a/mateclaw-server/src/main/resources/docs/en/goals.md +++ b/mateclaw-server/src/main/resources/docs/en/goals.md @@ -65,8 +65,6 @@ For automation and external scripts, the endpoint is direct: POST /api/v1/goals { "conversationId": "conv-xxx", - "agentId": "1000000001", - "workspaceId": 1, "title": "Deploy blog to fly.io", "description": "...", "exitCriteria": "DNS + SSL + healthcheck + tests pass", @@ -76,7 +74,7 @@ POST /api/v1/goals } ``` -Full surface in the [API reference](./api). +> `agentId` / `workspaceId` are derived server-side from `conversationId` — **don't send them** (they're ignored if you do). Full surface in the [API reference](./api). --- @@ -114,13 +112,59 @@ After every turn, a backend evaluator node runs: When `autoFollowupEnabled=true` and this turn's evaluator decision is "continue", the backend: 1. Writes a `followup_injected` event to the timeline -2. APPENDs a user message to the conversation: *"Continue working on the goal. Still missing: {gap}. Take the next concrete step."* +2. APPENDs a user message to the conversation. **Since 1.5.0, if the goal has a checklist, that message explicitly lists the criteria still open** — *"5/8 done, remaining: ① … ② …, take the next step on these"*; with no checklist it falls back to the generic *"Continue working on the goal. Still missing: {gap}."* 3. Re-enters the reasoning loop — the next assistant reply lands right after the first Feels like: the worker answers a segment → pauses a beat → **keeps going** — like a person who finished one step, thought for a second, and continued. --- +## A goal is a checklist (1.5.0+) + +In 1.4.0 the evaluator gave a completion score (0–1) and a one-line "what's missing" each turn. The problem: **what does 0.8 mean** — which boxes are done, which aren't? You couldn't see it. + +1.5.0 replaces that with a **checklist**: a goal = a set of **independently verifiable** criteria. + +**The evaluator has two modes:** + +| Mode | When | What it does | +|---|---|---| +| **bootstrap** | No criteria yet | Decomposes the goal into a checklist; each starts "not passed" | +| **verdict** | Criteria exist | Judges each one: satisfied? with evidence | + +Both modes use **structured output** — the evaluator returns a typed object (criterion `id` + `passed` + `evidence`), not free text we have to parse. + +**Completion is deterministic.** Only when **every criterion passes** is the goal done. 19 of 20 passed (a 0.95 score) is still "continue" — miss one and one is missing, no fuzzy threshold. + +**Three ways to add a checklist:** + +- **At creation** — pass `criteria: ["DNS resolves", "SSL valid", "tests green"]` to the `setGoal` tool, or `criteria` to `POST /api/v1/goals`. Skips the bootstrap round. +- **Let the evaluator decompose** — pass no criteria and the first evaluation bootstraps the checklist. +- **Append at runtime** — the `addGoalCriterion` tool or `POST /api/v1/goals/{id}/criteria` adds one to a live goal without restarting. + +**What a criterion looks like:** + +```json +{ "id": "C1", "text": "DNS resolves to fly.io", "passed": false, "evidence": "" } +``` + +`id` is server-assigned (C1, C2…), `text` is a sentence a human reads and an LLM judges, `passed` is the evaluator's verdict, `evidence` is the justification it gives. The checklist lives in the `mate_agent_goal.criteria` column (JSON) and is delivered parsed as `GoalResponse.criteria`, never as a raw JSON string. + +### The ring, on hover, is a checklist card + +- **No checklist** — a one-line tooltip: title + the gap text the evaluator wrote. +- **With a checklist** — a card: title + `X/Y` progress, then each criterion prefixed by `○` (open) or `✓` (green, done, struck through). + +While evaluating, a sand-gold breathing halo surrounds the avatar; on completion a green ring shows briefly then disappears; on budget exhaustion the ring turns rust. + +### Evaluator SPI + +The evaluation logic implements Spring AI's `Evaluator` interface: it does goal-specific checklist verdicts (bootstrap / verdict) and can be reused as a generic evaluator (wrapping a single objective as one criterion in verdict mode). Failed evaluator calls **still count against the LLM budget**, so the accounting stays honest. + +> The 1.4.0 goal was "the worker remembers what it's doing." The 1.5.0 goal is "the worker knows **exactly which boxes are still open**." From a score to a checklist you can tick. + +--- + ## Four built-in tools (worker-callable) These four ship as agent-wide system tools — no binding setup needed: @@ -132,7 +176,7 @@ These four ship as agent-wide system tools — no binding setup needed: | **completeGoal** | Explicitly mark done | "All items done — call completeGoal" | | **getGoalStatus** | Inspect current state | "How are we doing?" | -On completion (`completeGoal` or evaluator score ≥ 0.95), the worker forwards a summary to its [long-term memory](./memory) so future conversations can recall it. +On completion (`completeGoal`, or the evaluator judging **every criterion passed**), the worker forwards a summary to its [long-term memory](./memory) so future conversations can recall it. --- @@ -168,7 +212,7 @@ Your options: ↓ ↑ paused - active ──evaluator score≥0.95 / completeGoal──→ completed (terminal) + active ──all criteria passed / completeGoal──→ completed (terminal) ↓ active ──turns_used / llm_calls exhausted ────→ exhausted (terminal) ↓ @@ -188,7 +232,7 @@ A few deliberate non-features: - **No nested goals / goal trees** — one goal per conversation, no OKR stack - **No "goal templates"** — every goal is hand-written - **No cross-conversation goal migration** — use a [workflow](./workflow) for that -- **No completion score in the UI** — `completionScore` is an internal engineering protocol, not user vocabulary. The UI speaks via a ring; hover reveals the natural-language gap the evaluator wrote. The numeric score stays in logs and the API for debugging +- **No completion score in the UI** — `completionScore` is an internal engineering protocol, not user vocabulary. The UI speaks via a ring; on hover it shows the box-by-box checklist card when there's a checklist, or the natural-language gap text the evaluator wrote when there isn't. The numeric score stays in logs and the API for debugging --- @@ -219,12 +263,18 @@ mateclaw: goal: # Master switch; when off, the graph node passes through for every call. enabled: true + # Create-time default for autoFollowupEnabled when the caller leaves it unset. + default-auto-followup: true + # Runtime master switch; when off, no goal injects a followup regardless of its per-goal flag. + allow-auto-followup: true # Default turn budget when the user doesn't override. default-turn-budget: 20 # Default combined (agent + evaluator) LLM call budget. default-llm-call-budget: 200 # Minimum seconds between two consecutive auto-followups. auto-followup-cooldown-seconds: 0 + # Hard cap on auto-followups within a single graph run (per-message safety net; overall budget is turnBudget). + max-followups-per-run: 8 # Model used by the evaluator. Empty = same model as the chat agent. # Recommended: a cheap model like qwen-turbo / glm-4-flash. evaluator-model: "" diff --git a/mateclaw-server/src/main/resources/docs/en/mcp.md b/mateclaw-server/src/main/resources/docs/en/mcp.md index df8aff4a..19008718 100644 --- a/mateclaw-server/src/main/resources/docs/en/mcp.md +++ b/mateclaw-server/src/main/resources/docs/en/mcp.md @@ -101,7 +101,7 @@ Earlier HTTP transport using SSE for server-to-client push. Legacy compatibility - **URL** (streamable_http/sse) — server endpoint - **HTTP Headers** (streamable_http/sse) — JSON object (e.g., `{"Authorization": "Bearer token"}`) - **Connect timeout** — default 30s -- **Read timeout** — default 30s +- **Read timeout** — default **60s** (raised from 30s in 1.5.0, #247; a single callTool round-trip that legitimately runs longer no longer gets cut off. Each server is tunable 5–300s) Save. If enabled, MateClaw auto-attempts to connect and discover tools. @@ -361,7 +361,7 @@ After each connection operation, results persist: | `cwd` | VARCHAR(512) | NULL | Working directory | | `enabled` | BOOLEAN | TRUE | On/off | | `connect_timeout_seconds` | INT | 30 | HTTP connect timeout | -| `read_timeout_seconds` | INT | 30 | Request response timeout | +| `read_timeout_seconds` | INT | 60 | Request response timeout (default 60 since 1.5.0, was 30) | | `last_status` | VARCHAR(32) | `disconnected` | Last connection status | | `last_error` | TEXT | NULL | Last error message | | `last_connected_time` | DATETIME | NULL | Last successful connection | diff --git a/mateclaw-server/src/main/resources/docs/en/memory.md b/mateclaw-server/src/main/resources/docs/en/memory.md index 3c9ed46b..9e0911a7 100644 --- a/mateclaw-server/src/main/resources/docs/en/memory.md +++ b/mateclaw-server/src/main/resources/docs/en/memory.md @@ -65,6 +65,55 @@ Each layer operates at a different timescale. Short-term is *this turn*. Extract --- +## Memory knows who's who: per-owner isolation (1.5.0) + +Before, an employee's memory was **shared**: whether it was you logged into the web, a colleague in a Feishu group, or an end user coming in through a third-party API, the memory piled into the same `MEMORY.md`. One employee serving multiple people would cross wires. + +1.5.0 gives every memory an **owner** and a **visibility scope**. + +### A unified owner_key + +Whatever the identity source, it normalizes to one prefixed string: + +| Source | owner_key | +|---|---| +| Web console | `user:` | +| IM channel (Feishu / DingTalk / WeCom…) | `:` | +| Third-party API (with endUserId) | `api:` | +| System / cron | `system` | + +### Three visibility scopes + +| scope | Who reads it | Typical content | +|---|---|---| +| **PERSONAL** | Only the matching owner | Memory extracted from conversations defaults here | +| **TEAM** | Everyone using this employee | Agent config files (AGENTS.md / SOUL.md / PROFILE.md), backfilled legacy data | +| **GLOBAL** | Always visible across employees / workspaces | Preset facts, system reference material | + +### Recall prefers personal memory + +The system prompt bakes in only the shared TEAM/GLOBAL memory (cacheable); each turn then **prefetches** that owner's personal memory by owner_key. So when someone asks "what stack does my project use," the employee recalls *that person's* private memory files first, not generic KB material. + +> On the structured "fact" layer: the **fact recall query itself supports owner-visibility filtering** (PERSONAL is owner-only, TEAM/GLOBAL shared). But the current **automatic fact projection** is built mainly from shared memory files and doesn't set `ownerKey/scope` on insert — so personalization shows up more in the personal-memory-file prefetch; per-owner facts are still being filled in. + +### Third-party APIs pass through an end-user identity + +`/api/v1/chat` and `/api/v1/chat/stream` request bodies gain an optional **`endUserId`** field (a string, to preserve large-integer precision). One PAT-authenticated integration represents one MateClaw user but can pass a distinct `endUserId` per end user, and memory isolates per end user automatically. + +### It's a feature flag + +The master switch is `mate.memory.lifecycle-mediator-enabled`. + +::: warning Mind the default +The Java property's bare default is `false`, but the `application.yml` **shipped with the release sets it to `true`** — so per-owner isolation is **on by default in a default install**. To go back to the old shared behavior (all writes to TEAM), set it to `false` explicitly in your config. +::: + +When on: conversation extraction writes to the owner's PERSONAL memory and recall filters by owner_key; when off, all writes fall back to shared TEAM. Multi-tenant instances stay on; single-user deployments can turn it off. + +Under the hood: migration `V137` adds `owner_key` + `scope` columns to `mate_workspace_file` / `mate_memory_recall` / `mate_fact`, backfilling legacy rows as `TEAM` (so no memory gets hidden on upgrade). Memory tools like `remember` resolve owner_key from the current request context — when the flag is on they write to that owner's PERSONAL memory, when off they fall back to shared writes. + +--- + ## Multi-layer memory with pluggable providers The memory layer is not one hard-coded implementation. It's an **interface** — the multi-layer architecture lets you stack providers: @@ -415,6 +464,12 @@ mate: # --- Consolidation / dreaming --- emergence-enabled: true emergence-day-range: 7 + + # --- per-owner memory isolation (1.5.0) --- + # The value shipped with the release is true (on): conversation extraction writes to the owner's + # PERSONAL memory and recall filters by owner_key. Set false for the old shared behavior (all writes + # to TEAM). The bare Java-property default is false. + lifecycle-mediator-enabled: true ``` Prefix: `mate.memory`. diff --git a/mateclaw-server/src/main/resources/docs/en/models.md b/mateclaw-server/src/main/resources/docs/en/models.md index 2d036e52..9effb6f1 100644 --- a/mateclaw-server/src/main/resources/docs/en/models.md +++ b/mateclaw-server/src/main/resources/docs/en/models.md @@ -17,8 +17,8 @@ MateClaw doesn't care which LLM you use. It talks to every mainstream provider t | **Bailian Token Plan** | Bailian token-bundle plan | dashscope | 7 seeded models; long tokens supported | | **OpenAI** | GPT-4o, GPT-4o-mini, GPT-5.5, o1, o3, o4-mini | openai | Standard OpenAI API | | **OpenAI OAuth (ChatGPT Plus/Pro)** | GPT-4o, o3, o4-mini via subscription | openai | Browser-based OAuth — no API key | -| **Anthropic** | Claude 4.7, Claude 4.6 Sonnet, Claude 4.5 Haiku | anthropic | Native Messages API | -| **Anthropic Claude Code OAuth** | Claude 4.7 / 4.6 via Claude Pro/Max/Team subscription | anthropic | Browser OAuth + manual-paste flow — no API key | +| **Anthropic** | **Claude Opus 4.8 / 4.8 Fast** (1.5.0+), Claude 4.7, Claude 4.6 Sonnet, Claude 4.5 Haiku | anthropic | Native Messages API; both 4.8 variants support the `xhigh` thinking tier | +| **Anthropic Claude Code OAuth** | Claude Opus 4.8 / 4.7 / 4.6 via Claude Pro/Max/Team subscription | anthropic | Browser OAuth + manual-paste flow — no API key | | **Google Gemini** _(native)_ | gemini-2.5-flash, gemini-3-pro-image-preview, gemini-2.5-flash-image | gemini | Native `generateContent` API (not OpenAI-compatible) — see "Native Gemini" below | | **xAI / Grok** | Grok 3, Grok 4 | openai | OpenAI-compatible (base URL + API key); xAI brand icon in the UI | | **DeepSeek** | deepseek-chat, deepseek-coder, **DeepSeek V4 flash + pro** (thinking-mode) | openai | OpenAI-compatible | @@ -161,13 +161,13 @@ Enter the user code in your browser, authorize, and the dialog closes itself the If `local` mode can't bind a loopback port (port in use, sandbox refused), it falls through to `manual_paste` automatically. -**Backend endpoints** (`/api/v1/oauth/openai/device`): +**Backend endpoints:** | Method | Path | Purpose | |---|---|---| -| `POST` | `/start` | Begin a session — returns `deviceAuthId`, `userCode`, `verificationUrl`, `intervalSeconds`, `expiresInSeconds` | -| `POST` | `/poll` | Poll one session by `deviceAuthId` — returns `PENDING` / `COMPLETED` / `EXPIRED` | -| `POST` | `/cancel` | Drop the session (e.g. user closed the dialog) | +| `POST` | `/api/v1/oauth/openai/device/start` | Begin a session — returns `deviceAuthId`, `userCode`, `verificationUrl`, `intervalSeconds`, `expiresInSeconds` | +| `POST` | `/api/v1/oauth/openai/device/poll` | Poll one session by `deviceAuthId` — returns `PENDING` / `COMPLETED` / `EXPIRED` | +| `POST` | `/api/v1/oauth/openai/device/cancel` | Drop the session (e.g. user closed the dialog) | The frontend respects the `intervalSeconds` OpenAI returns (typically 5 s); the server enforces a min poll interval (default 3 s) to keep load bounded. Expired sessions are swept every 5 minutes. @@ -392,6 +392,17 @@ Every provider you add joins an `AvailableProviderPool` that's probed at startup - **Egress sanitizer** — provider-specific options (e.g., `reasoning_effort` for OpenAI reasoning models) are stripped at egress when failing over to a provider that doesn't support them, so leaked options can't 400 the fallback - **UI distinguishes 401 from session expiry** — provider auth errors and user session expiry now show different messages with different remediation +### Preferred provider drives the primary model (1.5.0) + +Before 1.5.0, "per-agent priority" only affected the **failover order** — the primary model was still the global default. 1.5.0 makes that preference **actually decide primary-model selection**. The full precedence is: + +1. **A conversation-pinned model wins** — the chat-header ModelSelector bound a model to this conversation, so it's used (see [per-conversation model selection](./chat#per-conversation-model-selection)) +2. **then the per-agent model override (`modelName`)** — the employee has a model pinned on it +3. **then the global default model** +4. **only when none of those are set does preferred-provider routing kick in** — picking the preferred provider's primary model + +Preferred-provider routing has a **capability gate**: if the employee's bound skills declare a need like `requires-model: vision`, routing first picks a provider that can satisfy those modalities; only if none can does it fall back unconstrained. Preferences are stored in `mate_agent_provider_preference` (ascending `sortOrder` = higher priority). + --- ## Configuration via API diff --git a/mateclaw-server/src/main/resources/docs/en/releases.md b/mateclaw-server/src/main/resources/docs/en/releases.md index d29afa68..e297cb4c 100644 --- a/mateclaw-server/src/main/resources/docs/en/releases.md +++ b/mateclaw-server/src/main/resources/docs/en/releases.md @@ -10,6 +10,7 @@ For historical diffs, check the corresponding git tag. For the "why" behind a fe | Version | Date | Highlights | |---------|------|------------| +| [v1.5.0](./releases/1.5.0) | 2026-06-04 | Goals grew a checklist — from "a score" to "ticked boxes" (checklist + Evaluator SPI + deterministic completion) · The Wiki learned to maintain itself (`[[wikilinks]]` + cascade rename/delete link-fix + broken-link lint · fact/experience layers + staleness propagation · pageType profiles & per-agent permissions · processing pipelines · local-directory knowledge source with scheduled incremental sync) · Per-owner memory isolation (owner_key + personal/team/global scopes + third-party endUserId passthrough) · Each employee binds a primary KB · Preferred provider drives the primary model + Claude Opus 4.8 | | [v1.4.0](./releases/1.4.0) | 2026-05-23 | Persistent Goals — an employee locks a goal and follows it to done on its own · Subagent delegation became a tree (recursive 3 levels + async + digital-employee builder) · Progressive tool/skill disclosure (`enable_tool` + `load_skill`) · Workspace RBAC (4 roles + capability gating) · Feishu as a first-class citizen (interactive / approval / streaming cards + voice / file / audio / video + channel-native tools) | | [v1.3.0](./releases/1.3.0) | 2026-05-13 | Year one of workflow — 7 step modes assemble employees into business processes · 6 trigger patterns make events drive workflows · Wiki promoted from search index to processing pipeline (user templates + cross-material aggregator + reverse citations) · Per-agent MCP tool binding + multimodal sidecar routing · 4 JVM-native document generation tools + image edit | | [v1.2.0](./releases/1.2.0) | 2026-05-05 | Agents renamed "digital employees" (role / goal / backstory + 5 career templates) · Skills became the skeleton (manifest + template wizard + LESSONS self-evolution) · ACP integration: Claude Code / Codex now show up as your employees · Admin Runtime Console lets you see every employee working in real time | diff --git a/mateclaw-server/src/main/resources/docs/en/security.md b/mateclaw-server/src/main/resources/docs/en/security.md index 4404f97c..b5a4ea32 100644 --- a/mateclaw-server/src/main/resources/docs/en/security.md +++ b/mateclaw-server/src/main/resources/docs/en/security.md @@ -88,8 +88,8 @@ mateclaw: | Code | Meaning | Response | |------|---------|----------| -| 401 | Token missing, expired, or invalid | `{"code": 401, "message": "Unauthorized"}` | -| 403 | Valid token but insufficient permissions | `{"code": 403, "message": "Forbidden"}` | +| 401 | Token missing, expired, or invalid | `{"code":401,"msg":"Token expired or invalid","data":null}` | +| 403 | Valid token but insufficient permissions | `{"code":403,"msg":"Forbidden","data":null}` | Frontend handles both uniformly — redirect to login, clear stored tokens. @@ -100,8 +100,8 @@ MateClaw ships with `admin` / `admin123`. **Change this immediately in any deplo ### Spring Security config - **Stateless sessions** — no server-side session; all state in the JWT -- **Public endpoints** — `/api/v1/auth/login`, `/h2-console/**`, `/swagger-ui/**` -- **Protected endpoints** — everything else under `/api/v1/**` +- **Public API endpoints** — `GET /api/v1/settings/language`, `/api/v1/auth/login`, `/api/v1/chat/stream`, `/api/v1/chat/*/stop`, `/api/v1/agents/*/chat/stream`, `/api/v1/setup/**`, `/api/v1/channels/webhook/**`, `/api/v1/channels/webchat/**`, `/api/v1/talk/ws`, `/api/v1/files/generated/**` +- **Protected endpoints** — everything else under `/api/**` - **CSRF disabled** — not needed for stateless JWT --- @@ -247,7 +247,7 @@ Frontend shows approval card User clicks Approve or Reject │ ▼ -POST /api/v1/approvals/{id}/resolve +POST /api/v1/chat/stream with /approve or /deny │ ├─ Approved → reload agent, replay tool call, continue reasoning └─ Rejected → send rejection as observation, continue reasoning @@ -255,6 +255,8 @@ POST /api/v1/approvals/{id}/resolve The "replay" mechanism is important. When the agent resumes, it **doesn't re-reason from scratch** — it skips straight to the approved tool call, executes it, and continues from the observation. No duplicate LLM calls, no wasted tokens. +The current web path has no write-style `POST /api/v1/approvals/{id}/resolve` endpoint. Approval and denial use the same SSE channel as normal chat so replay, persistence, and cancellation all stay on one lifecycle. + ### The `mate_tool_approval` table | Column | Purpose | @@ -283,24 +285,28 @@ Pending approvals expire after a configurable timeout (default: 10 minutes). Exp MateClaw can notify through `channel/notification/` adapters — email, in-app alert, DingTalk/Feishu push. Configure in `Settings → Security & Approval → Notifications`. -### Resolving via API +### Current API surface ```bash -# List pending -curl http://localhost:18088/api/v1/approvals?status=pending \ +# Hydrate pending approvals after a page refresh +curl http://localhost:18088/api/v1/chat/{conversationId}/pending-approvals \ -H "Authorization: Bearer " -# Approve -curl -X POST http://localhost:18088/api/v1/approvals/123/resolve \ +# Approve in the waiting conversation +curl -N -X POST http://localhost:18088/api/v1/chat/stream \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ - -d '{"decision": "approved"}' + -d '{"agentId":"1","conversationId":"conv-abc123","message":"/approve"}' -# Reject with reason -curl -X POST http://localhost:18088/api/v1/approvals/123/resolve \ +# Reject in the waiting conversation +curl -N -X POST http://localhost:18088/api/v1/chat/stream \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ - -d '{"decision": "rejected", "notes": "Not appropriate for this workspace"}' + -d '{"agentId":"1","conversationId":"conv-abc123","message":"/deny"}' + +# Manage auto-approval grants +curl http://localhost:18088/api/v1/approval/grants \ + -H "Authorization: Bearer " ``` --- diff --git a/mateclaw-server/src/main/resources/docs/en/skills.md b/mateclaw-server/src/main/resources/docs/en/skills.md index 6820b64b..71dbb98b 100644 --- a/mateclaw-server/src/main/resources/docs/en/skills.md +++ b/mateclaw-server/src/main/resources/docs/en/skills.md @@ -532,6 +532,18 @@ When disabled, the catalog guidance points at `readSkillFile` instead and `load_ --- +## The `/skill` slash menu in chat (new in 1.5.0) + +Don't want to prompt the employee in natural language about which skill to use? Type `/` in the chat composer to open a **searchable skill picker**: + +- ↑↓ to move, Enter/Tab to select, Esc to close; typing filters the enabled skills live (up to 8 shown). +- The list comes from `GET /api/v1/skills/enabled` — real skills plus MCP/ACP-derived virtual skills (a real skill shadows a same-named virtual one). Cached per workspace for 30 seconds so reopening doesn't re-fetch. +- Selecting a skill inserts a directive into the box: `Use the "skill name" skill: `, cursor at the end, ready for you to add context and send. The employee sees the directive in message history and runs `load_skill` to pull it. + +The menu shows whenever **an employee is selected and that employee hasn't disabled skills** (the frontend checks `currentAgent && !skillsDisabled`) — it is unrelated to the global progressive-disclosure switch. Setting `mateclaw.skill.disclosure.load-skill-tool.enabled` to `false` globally only stops the backend from registering the `load_skill` tool; the menu still opens (the employee just falls back to pulling skills via `readSkillFile` and similar). + +--- + ## Skill lifecycle curator (new in v1.4) Agents that synthesize skills accumulate cruft — a one-off skill from three weeks ago is still in the catalog, eating a slot. The **curator** is a daily sweep that ages idle, **agent-created** skills through `active → stale → archived` and gets them out of the way without deleting anything. diff --git a/mateclaw-server/src/main/resources/docs/en/tools.md b/mateclaw-server/src/main/resources/docs/en/tools.md index 7e3a8c43..c454cf51 100644 --- a/mateclaw-server/src/main/resources/docs/en/tools.md +++ b/mateclaw-server/src/main/resources/docs/en/tools.md @@ -324,14 +324,14 @@ curl -X PUT http://localhost:18088/api/v1/tools/1 \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -d '{"enabled": false}' -# Test a tool directly -curl -X POST http://localhost:18088/api/v1/tools/WebSearchTool/test \ +# Set disclosure tier for a builtin or channel tool +curl -X PUT http://localhost:18088/api/v1/tools/1/disclosure-tier \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ - -d '{"query": "Spring AI"}' + -d '{"tier": "core"}' ``` -Every provider-backed tool has a test button in the Tools page so you can verify API keys before shipping. +The current REST API manages tool rows, enabled state, and disclosure tier. Direct execution of builtin tools happens through the agent runtime, not through a `/tools/{name}/test` endpoint. --- diff --git a/mateclaw-server/src/main/resources/docs/en/wiki.md b/mateclaw-server/src/main/resources/docs/en/wiki.md index 18601792..2c1a90ee 100644 --- a/mateclaw-server/src/main/resources/docs/en/wiki.md +++ b/mateclaw-server/src/main/resources/docs/en/wiki.md @@ -258,6 +258,8 @@ Bind an agent to a knowledge base from `Agents → [your agent] → Knowledge`. | `wiki_related_pages` | Related-page discovery across four signals (shared chunks, shared raws, direct links, semantic neighbors). | | `wiki_explain_relation` | Score breakdown for the relationship between two pages. | | `wiki_create_page` / `wiki_delete_page` | Direct page management; deletion respects `locked` / `system`. | +| `wiki_update_page` | **1.5.0**: in-place edit of a page (keeps the slug), gated by the pageType "update" permission. | +| `wiki_stale_pages` | **1.5.0**: list every page currently flagged for review (`stale`). | | `wiki_archive_page` / `wiki_unarchive_page` | Soft-archive: hide a page from default list/search/related results without destroying it. Citations and source lineage survive; recoverable. System pages can't be archived. | | `wiki_list_transformations` | List the transformation templates available to this KB (name, intent, whether apply-default is on). | | `wiki_apply_transformation` | Run a template against one **raw material**; returns the output, run id, and saved-page info. | @@ -299,13 +301,11 @@ The injection is gated by the `wiki.hot_cache.enabled` feature flag (off → emp #### Operator endpoints -Base path `/api/v1/wiki/hot-cache`: - | Method | Path | What it does | |---|---|---| -| `GET` | `/{kbId}` | Current snapshot + meta | -| `POST` | `/{kbId}/regenerate` | Manual rebuild (async, ignores debounce) | -| `DELETE` | `/{kbId}` | Soft-delete; rebuilds on next event | +| `GET` | `/api/v1/wiki/hot-cache/{kbId}` | Current snapshot + meta | +| `POST` | `/api/v1/wiki/hot-cache/{kbId}/regenerate` | Manual rebuild (async, ignores debounce) | +| `DELETE` | `/api/v1/wiki/hot-cache/{kbId}` | Soft-delete; rebuilds on next event | The hot cache lives in `mate_wiki_hot_cache` — see the **Data model** section below for the exact columns. @@ -464,10 +464,86 @@ than getting silently redirected to a similarly-named page. | 4 | Cascade delete and rename rewrite referrers in-transaction; audit log; feature flag | | 5 | Analyze stage emits a `related_pages` slug whitelist (validated server-side); enrich applier skips code blocks and gates on the whitelist | -Full design + live verification: `rfcs/202605/55-wiki-link-resolution-overhaul.md` -and `mateclaw-server/src/test/resources/e2e/wiki-link-overhaul-verification.md` -(6 e2e passes, 50+ live assertions, 3 bugs caught and fixed during the -test). +Full design and live verification live in the matching design doc and +end-to-end verification record in the repository. + +--- + +## The knowledge base maintains itself (1.5.0) + +1.5.0 pushes the Wiki from "a searchable knowledge base" into "a knowledge engine that maintains its own consistency, layers itself, runs its own pipelines, and can mount a local directory." The management surface for all of this is the **Wiki advanced panel** in the admin console (five sub-pages: page-type profile / layers & staleness / permissions / source watcher / pipelines). + +### Knowledge layers: fact vs experience + +Each page can carry a **knowledge layer**: + +- **`fact`** — "what is": foundational fact pages. Unlabeled defaults to fact. +- **`experience`** — "what it means": synthesis, analysis, insight, which **depends on** a set of fact pages. + +**Staleness propagates.** An experience page declares which fact pages it depends on (edges stored by page **id**, so renames don't break them). When a fact page is updated during ingest, every experience page depending on it is auto-marked `stale` (needs review) + a reason. The `wiki_stale_pages` tool lists everything currently flagged; search can **filter by knowledge layer** (facts only / experience only / all). + +Under the hood: `mate_wiki_page` gains `knowledge_layer` / `depends_on_json` / `stale` / `stale_reason_json` columns (migration V135), with dependency edges in `mate_wiki_page_dependency` and a reverse index dedicated to stale propagation. + +### Page-type profiles (pageType profile) + +Define which **page types** a KB has (e.g. "concept / tutorial / decision record"), each carrying: + +- A structured-field **schema** — page metadata is validated against it on save, with the validation status recorded (valid / invalid + details) +- **route / create / merge**-stage prompts — injected into the corresponding LLM call +- A **Markdown template** — the skeleton used when generating the page + +At most one **enabled** profile per KB; unconfigured KBs use a **built-in default**. Profiles are written in YAML or JSON, with "validate (no save)" and "reset to default" actions. Stored in `mate_wiki_page_type_profile` (migration V134); the page metadata columns (`metadata_json` / `metadata_validation_status` / `template_key` / `profile_version`) are added to `mate_wiki_page` in the same migration. + +### Page-type permissions (per-agent) + +For "**this agent + this KB + this page type**" you can set read / create / update / delete flags plus a **write policy**: + +| Write policy | Meaning | +|---|---| +| `allow` | Write immediately | +| `approval_required` | Write is held pending [approval](./security) | +| `deny` | Blocked | + +`page_type='*'` is the KB-wide default; **exact matches beat the wildcard**. + +**Read and write fall back differently** — keep them distinct: + +- **Read** — when no rule matches, read falls back to the **KB-level default read policy** `defaultReadPolicy` (`allow_all` unless the KB sets `deny_all`). So existing KBs stay fully readable after upgrade. Read gating filters lists and search results; an unreadable type is treated as nonexistent (no existence leak). +- **Write** — write is opt-in tightened. An agent with **no rules** for a KB writes `allow` (old behavior); add **any** rule and that KB enters "locked down" mode — page types with no matching rule resolve to `deny` (fail-safe). + +Stored in `mate_wiki_agent_page_type_permission` (migration V133). + +### Processing pipelines (Wiki Pipeline) + +Define a processing flow for a KB, fired automatically by **page events**: + +- **Triggers**: `page_type_count` (a page-type count crosses a threshold), `page_created` (a page of a given type is created), `stale_marked` (pages get flagged stale) +- **Step executors**: + - `llm` — run input through the model; the output becomes the step result + - `skill` — run a skill from a **restricted set**, as the owner agent + +Definitions are written in YAML or JSON, with CRUD + validate endpoints. Every run and every step is persisted and queryable, deduplicated by `(definition, trigger, subject, bucket)` for idempotency. Tables: `mate_wiki_pipeline_definition` / `mate_wiki_pipeline_run` / `mate_wiki_pipeline_step_run` (migration V136). + +### Mount a local directory as a knowledge source — pluggable + scheduled incremental + +Knowledge sources are a **pluggable SPI** (`WikiIngestSourceProvider`) with a built-in filesystem provider: give a KB a `source_directory` and files in it get ingested. + +- **Scheduled incremental sync** — a background scheduler (with a distributed lock so only one node runs per cycle) scans periodically, detects changes **by content hash**, and re-ingests only new/modified files (text and binary). +- **Fail-closed security** — paths are normalized then symlink-resolved (closing TOCTOU) and validated against an allowed-roots allowlist; under the production profile an empty allowlist rejects everything. Set the `mate.wiki.allowed-source-roots` allowlist. +- **Status + manual trigger** — `GET .../source-watcher` shows status, `POST .../source-watcher/scan` runs a scan immediately. + +Relevant config (`application.yml`): + +```yaml +mate: + wiki: + watcher-enabled: false # master switch for the source watcher + watcher-interval-ms: 300000 # scan interval (default 5 min) + allowed-source-roots: [] # allowed source-directory roots (allowlist) + require-allowed-roots: false # production: set true so an empty allowlist rejects everything +``` + +All new REST endpoints are in the [API Reference](./api#llm-wiki). --- diff --git a/mateclaw-server/src/main/resources/docs/en/workspaces.md b/mateclaw-server/src/main/resources/docs/en/workspaces.md index f0628f0b..8ff94ad4 100644 --- a/mateclaw-server/src/main/resources/docs/en/workspaces.md +++ b/mateclaw-server/src/main/resources/docs/en/workspaces.md @@ -261,7 +261,7 @@ curl -X POST http://localhost:18088/api/v1/workspaces/1/members \ curl -X DELETE http://localhost:18088/api/v1/workspaces/1/members/42 \ -H "Authorization: Bearer " -curl -X PUT http://localhost:18088/api/v1/workspaces/1/members/42/role \ +curl -X PUT http://localhost:18088/api/v1/workspaces/1/members/42 \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"role": "admin"}' diff --git a/mateclaw-server/src/main/resources/docs/zh/ambient-ai.md b/mateclaw-server/src/main/resources/docs/zh/ambient-ai.md index ec7cf952..f5e8cdc9 100644 --- a/mateclaw-server/src/main/resources/docs/zh/ambient-ai.md +++ b/mateclaw-server/src/main/resources/docs/zh/ambient-ai.md @@ -153,11 +153,15 @@ curl http://localhost:18088/api/v1/cron-jobs \ -H "Authorization: Bearer " # 立刻试跑一次(不影响下次定时触发) -curl -X POST http://localhost:18088/api/v1/cron-jobs/{id}/run-now \ +curl -X POST http://localhost:18088/api/v1/cron-jobs/{id}/run \ -H "Authorization: Bearer " -# 看历史执行 -curl http://localhost:18088/api/v1/cron-jobs/{id}/runs \ +# 查看单个定时任务的执行历史 +curl http://localhost:18088/api/v1/dashboard/cron-runs/{id} \ + -H "Authorization: Bearer " + +# 查看当前工作区最近执行历史 +curl http://localhost:18088/api/v1/dashboard/cron-runs \ -H "Authorization: Bearer " ``` diff --git a/mateclaw-server/src/main/resources/docs/zh/api.md b/mateclaw-server/src/main/resources/docs/zh/api.md index 88d8a9b2..6f3cace5 100644 --- a/mateclaw-server/src/main/resources/docs/zh/api.md +++ b/mateclaw-server/src/main/resources/docs/zh/api.md @@ -1,35 +1,42 @@ # API 参考 -所有 REST 端点前缀 `/api/v1/`。所有响应遵循同一个信封格式: +本页以 `mateclaw-server/src/main/java` 下的 Spring MVC Controller 注解为准。下面的路由索引由源码注解重建;如果它和旧功能页冲突,以本页和源码为接口契约。 + +## 全局契约 + +应用 REST 端点默认使用 `/api/v1` 前缀。大多数 JSON 响应使用项目统一信封: ```json { "code": 200, - "message": "success", - "data": { } + "msg": "success", + "data": {} } ``` -除了 `/api/v1/auth/login`,所有端点都需要 `Authorization` header 里带 JWT: +例外: -``` -Authorization: Bearer -``` +- 流式端点(`text/event-stream`)返回 SSE frame,不走 JSON 信封。 +- 下载类端点,例如 `/api/v1/files/generated/{id}`、聊天附件、Wiki 原始材料下载,返回字节或 `ResponseEntity`。 +- 少量需要客户端按 HTTP 状态码分支的冲突/确认流程会返回独立结构体。 -深入的行为细节去读对应的功能页——[聊天与消息](./chat)、[Agent 引擎](./agents)、[工具系统](./tools)、[安全与审批](./security)、[LLM Wiki](./wiki)、[多模态创作](./multimodal)、[记忆系统](./memory)、[多渠道接入](./channels)、[模型配置](./models)、[工作空间](./workspaces)、[目标](./goals)、[Doctor](./doctor)。 - ---- +后端 Snowflake `Long` ID 会序列化成 JSON 字符串。前端和第三方客户端都应把 ID 全程当字符串处理。 ## 认证 -``` -POST /api/v1/auth/login # 登录,获取 JWT -GET /api/v1/users/me # 获取当前用户 -PUT /api/v1/users/me # 更新个人资料 -PUT /api/v1/users/me/password # 修改密码 +`POST /api/v1/auth/login` 返回 JWT。受保护接口请求头: + +```text +Authorization: Bearer ``` -**登录示例:** +`SecurityConfig` 中放行的公共路径包括登录、首次初始化、webhook/webchat 回调、chat stream/stop、agent stream、talk WebSocket、`GET /api/v1/settings/language`,以及 `/api/v1/files/generated/**` 一次性生成文件下载。认证通过后,`@RequireWorkspaceRole`、`@RequireGlobalAdmin` 等角色约束仍会继续生效。 + +工作空间接口通常接受 `X-Workspace-Id`。省略时,很多 handler 会为了桌面/本地兼容回退到 workspace `1`。 + +## 常用接口 + +### 登录 ```bash curl -X POST http://localhost:18088/api/v1/auth/login \ @@ -37,581 +44,640 @@ curl -X POST http://localhost:18088/api/v1/auth/login \ -d '{"username":"admin","password":"admin123"}' ``` -响应: - -```json -{ - "code": 200, - "data": { - "token": "eyJhbGciOiJIUzI1NiJ9...", - "tokenType": "Bearer", - "expiresIn": 86400 - } -} -``` - ---- - -## 聊天 - -``` -POST /api/v1/chat?agentId={id} # 发送消息(同步,agentId 是 query 参数) -POST /api/v1/chat/stream # SSE 流式(POST,agentId 在 body 里) -POST /api/v1/chat/{conversationId}/stop # 停止进行中的流 -POST /api/v1/chat/{conversationId}/interrupt # 中断 Agent 循环 -POST /api/v1/chat/upload # 上传聊天附件(multipart/form-data) -GET /api/v1/chat/files/{conversationId}/{storedName} # 读取已上传附件 -GET /api/v1/chat/{conversationId}/pending-approvals # 列出等待的审批 -``` - -**发送消息:** +### 聊天 ```bash -curl -X POST 'http://localhost:18088/api/v1/chat?agentId=1' \ - -H "Authorization: Bearer YOUR_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"message":"你好,你能做什么?", "conversationId":"conv-abc123"}' -``` - -请求体字段:`message`(必填)、`conversationId`(可选,省略则用 `default`)、`contentParts`(可选,结构化内容片段,附件场景使用)。 - -**SSE 流式示例:** - -SSE 端点是 **POST + 请求体**,浏览器原生 `EventSource` 不支持 POST,集成时请用 `fetch()` 读流(参考前端 `composables/chat/useChat.ts`)。 - -```bash -curl -N -X POST 'http://localhost:18088/api/v1/chat/stream' \ - -H "Authorization: Bearer YOUR_TOKEN" \ +curl -N -X POST http://localhost:18088/api/v1/chat/stream \ + -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: text/event-stream" \ - -d '{"agentId":1, "message":"你好", "conversationId":"conv-abc123"}' + -d '{"agentId":"1","message":"你好","conversationId":"conv-abc123"}' ``` -事件类型和 schema 在 [聊天与消息](./chat) 里。 +`/chat/stream` 是 POST SSE;浏览器原生 `EventSource` 不能带 POST body,请用 `fetch()` 读取流。 + +### 工具审批 + +当前没有 `POST /api/v1/approvals/{id}/resolve` REST 端点。Web 端批准/拒绝通过等待中的会话发送 `/approve` 或 `/deny`,走 chat stream replay 流程。刷新页面后的只读补水接口仍是 `GET /api/v1/chat/{conversationId}/pending-approvals`。自动批准策略在 `/api/v1/approval/grants` 下管理。 + +### Doctor / 健康检查 + +当前后端健康接口是 `GET /api/v1/system/health`。旧文档里的 `/api/v1/doctor/*` 端点在当前源码中没有实现。 + +### 多模态生成 + +图片、视频、音乐、3D 生成是 Agent 工具(`image_generate`、`video_generate`、`music_generate`、`model3d_generate`),不是独立的 `/api/v1/image`、`/api/v1/video`、`/api/v1/music` REST Controller。当前存在的相关 REST 面是 TTS/STT 和生成文件下载。 + +### 非 REST 端点 + +`/api/v1/talk/ws` 由 `WebSocketConfig` 注册,用于 Talk Mode。它会出现在 `SecurityConfig` 的公共 WebSocket 路由里,但不计入下面的 controller 路由索引。 + +## 源码对齐路由索引 + +抽取到的路由总数:406。 + +### 认证 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `POST` | `/api/v1/auth/login` | `用户登录` | +| `GET` | `/api/v1/auth/tokens` | `List my PATs (metadata only — plaintext is never returned after creation)` | +| `POST` | `/api/v1/auth/tokens` | `Mint a new PAT — returned plaintext is shown once and cannot be recovered` | +| `DELETE` | `/api/v1/auth/tokens/{id}` | `Revoke a PAT — soft-delete; further auth attempts with this token will fail` | +| `GET` | `/api/v1/auth/users` | `获取用户列表` | +| `POST` | `/api/v1/auth/users` | `创建用户` | +| `PUT` | `/api/v1/auth/users/{id}/password` | `修改密码` | + +### 聊天 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `POST` | `/api/v1/chat` | `同步对话` | +| `GET` | `/api/v1/chat/files/{conversationId}/{storedName:.+}` | `读取聊天附件` | +| `POST` | `/api/v1/chat/stream` | `结构化 SSE 流式对话(支持重连)` | +| `POST` | `/api/v1/chat/upload` | `上传聊天附件` | +| `POST` | `/api/v1/chat/{conversationId}/interrupt` | `排队后续消息(不打断当前流)` | +| `GET` | `/api/v1/chat/{conversationId}/pending-approvals` | `查询待审批记录` | +| `POST` | `/api/v1/chat/{conversationId}/stop` | `停止流式生成` | ### 会话 -``` -GET /api/v1/conversations # 列表(?page&size&agentId) -GET /api/v1/conversations/page?page=&size=&keyword= # 分页会话(带关键词搜索) -GET /api/v1/conversations/{id}/messages # 取消息 -PUT /api/v1/conversations/{id}/model # 设置该会话使用的模型 -DELETE /api/v1/conversations/{id} # 删除 -DELETE /api/v1/conversations/{id}/messages # 清空消息 -GET /api/v1/conversations/{id}/status # 会话状态 -``` - ---- - -## Agent - -``` -GET /api/v1/agents # 列表(分页) -GET /api/v1/agents/{id} # 获取 -POST /api/v1/agents # 创建 -PUT /api/v1/agents/{id} # 更新(部分) -DELETE /api/v1/agents/{id} # 软删除 - -GET /api/v1/agents/{id}/chat/stream?message=...&conversationId=... # 流式对话 - -GET /api/v1/agents/{id}/workspace/files # 列文件 -GET /api/v1/agents/{id}/workspace/files/{filename} # 取内容 -PUT /api/v1/agents/{id}/workspace/files/{filename} # 写入 -DELETE /api/v1/agents/{id}/workspace/files/{filename} # 删除 -GET /api/v1/agents/{id}/workspace/prompt-files # 哪些文件被注入 -PUT /api/v1/agents/{id}/workspace/prompt-files # 设置注入的文件列表 - -GET /api/v1/agents/{agentId}/workspace/memory/export # 导出记忆快照 -POST /api/v1/agents/{agentId}/workspace/memory/import/preview # 预览导入(不落库) -POST /api/v1/agents/{agentId}/workspace/memory/import # 导入记忆快照 - -GET /api/v1/agents/templates # 列出模板 -POST /api/v1/agents/templates/{id} # 从模板创建 -``` - -### 字段:`primaryKbId`(1.5.0+) - -每个员工可以指定一个**主知识库**作为 wiki 工具的默认目标。字段类型 `string | null`(雪花 ID,前端始终按字符串处理)。 - -`PUT /api/v1/agents/{id}` 的语义是**三态**的: - -| 请求体里 | 行为 | -|---------|------| -| 不带 `primaryKbId` 这个字段 | 保留原值,不动 | -| `"primaryKbId": ""` | 设为指定 KB | -| `"primaryKbId": null` | 清空(之后 wiki 工具按 workspace 默认 KB 回退) | - -服务端用 `body.containsKey("primaryKbId")` 区分"字段缺失"和"显式 null",entity 上配 `@TableField(updateStrategy = FieldStrategy.ALWAYS)` 保证 null 真的写到数据库(不会被 MyBatis-Plus 默认 `NOT_NULL` 策略静默跳过)。 - -设计语义:**KB 是工作空间共享的,`primaryKbId` 只决定该员工 wiki 工具的默认目标,不改变 KB 的归属或可见性。** 多个员工可以选同一个 KB 作主库,互不影响。 - -请求示例: - -```bash -# 设为某个 KB -curl -X PUT http://localhost:18088/api/v1/agents/2055639185675730946 \ - -H "Authorization: Bearer $TOKEN" \ - -H "X-Workspace-Id: 1" \ - -H "Content-Type: application/json" \ - -d '{"primaryKbId": "2054907618529591298", ...其余字段}' - -# 清空绑定 -curl -X PUT http://localhost:18088/api/v1/agents/2055639185675730946 \ - -H "Authorization: Bearer $TOKEN" \ - -H "X-Workspace-Id: 1" \ - -H "Content-Type: application/json" \ - -d '{"primaryKbId": null, ...其余字段}' -``` - ---- - -## 工具 - -``` -GET /api/v1/tools # 列表 -PUT /api/v1/tools/{id} # 更新 -PUT /api/v1/tools/{id}/toggle?enabled={bool} # 开关 -PUT /api/v1/tools/{id}/disclosure-tier # 设置披露层级(core / extension) -POST /api/v1/tools/{name}/test # 直接测试 -``` - ---- - -## 技能 - -``` -GET /api/v1/skills # 列表(?type=builtin|custom|mcp&tag=...) -GET /api/v1/skills/{id} # 获取 -POST /api/v1/skills # 创建 -PUT /api/v1/skills/{id} # 更新 -DELETE /api/v1/skills/{id} # 删除 -PUT /api/v1/skills/{id}/toggle?enabled={bool} # 开关 -GET /api/v1/skills/runtime/active # 当前活跃的技能 -GET /api/v1/skills/runtime/status # 运行时状态 -POST /api/v1/skills/runtime/refresh # 重载运行时 -``` - ---- - -## MCP 服务 - -``` -GET /api/v1/mcp/servers # 列表 -GET /api/v1/mcp/servers/{id} # 获取 -POST /api/v1/mcp/servers # 创建 -PUT /api/v1/mcp/servers/{id} # 更新(PATCH 语义) -DELETE /api/v1/mcp/servers/{id} # 删除 -PUT /api/v1/mcp/servers/{id}/toggle?enabled={bool} # 开关 -POST /api/v1/mcp/servers/{id}/test # 测试连接 -POST /api/v1/mcp/servers/refresh # 刷新所有 -``` - -请求体 schema 见 [MCP 协议](./mcp)。 - ---- - -## LLM Wiki - -``` -GET /api/v1/wiki/kbs # 列知识库 -POST /api/v1/wiki/kbs # 创建 KB -GET /api/v1/wiki/kbs/{id} # 获取 KB 详情 -PUT /api/v1/wiki/kbs/{id} # 更新 KB -DELETE /api/v1/wiki/kbs/{id} # 删除 KB - -POST /api/v1/wiki/kbs/{kbId}/raw # 上传原始材料 -GET /api/v1/wiki/kbs/{kbId}/raw # 列原始材料 -DELETE /api/v1/wiki/raw/{id} # 删除原始材料 -POST /api/v1/wiki/raw/{id}/reprocess # 重新消化 - -GET /api/v1/wiki/kbs/{kbId}/pages # 列页面 -GET /api/v1/wiki/pages/{id} # 获取页面 -PUT /api/v1/wiki/pages/{id} # 编辑页面 -DELETE /api/v1/wiki/pages/{id} # 删除页面 -POST /api/v1/wiki/pages/{id}/lock # 锁定页面 -POST /api/v1/wiki/pages/{id}/unlock # 解锁 - -GET /api/v1/wiki/kbs/{kbId}/search?q=... # 全文搜索 -GET /api/v1/wiki/pages/{id}/backlinks # 反向链接 -``` - -Agent 可调的 wiki 工具(`wiki_search`、`wiki_read`、`wiki_backlinks`)自动解析 `kbId`。 - -### 员工绑定主知识库(1.5.0+) - -PR #237 / V130 迁移引入了员工的"主知识库"机制。新的端点: - -``` -GET /api/v1/wiki/knowledge-bases/bindable # 列当前 workspace 可绑定为主库的 KB -``` - -返回的是当前 workspace 的**全部** KB(包含已被其他员工选作主库的),因为绑定语义是"我默认查哪一个"——不是独占。返回 shape 跟 `GET /api/v1/wiki/knowledge-bases`(按 workspace 列出)一致,单独命名只是为了在 UI 语义上更清晰。 - -绑定动作本身**不走** wiki 接口,而是写在员工实体上: - -``` -PUT /api/v1/agents/{id} # body 里带 primaryKbId 字段 -``` - -字段语义、三态行为见上面 [Agent 段的 `primaryKbId` 说明](#字段-primarykbid150)。 - -::: warning 旧字段 `kb.agentId` 的去留 -1.5.0 之前的版本曾把绑定关系写在 `mate_wiki_knowledge_base.agent_id` 上(一对一独占)。V130 迁移把旧值回填到了 `agent.primary_kb_id`,老字段保留作 fallback 读取——**`PUT /api/v1/wiki/knowledge-bases/{id}` 不再处理 `agentId` 字段**,传上去会被忽略。新代码请只通过 `agent.primaryKbId` 控制绑定。 -::: - ---- - -## 多模态 - -``` -POST /api/v1/image/generate # 生成图像 -POST /api/v1/image/edit # 编辑图像 -POST /api/v1/video/generate # 生成视频 -POST /api/v1/video/from-image # 图生视频 -POST /api/v1/music/generate # 生成音乐 -POST /api/v1/tts/synthesize # 文本转语音 -POST /api/v1/stt/transcribe # 语音转文本 - -GET /api/v1/image/jobs/{id} # 查异步图像任务状态 -GET /api/v1/video/jobs/{id} # 查异步视频任务状态 -``` - -见 [多模态创作](./multimodal)。 - ---- - -## 记忆 - -``` -POST /api/v1/memory/{agentId}/emergence # 手动触发整合 -POST /api/v1/memory/{agentId}/summarize/{conversationId} # 手动触发提取 -GET /api/v1/memory/{agentId}/dreaming/status # 上次/下次运行 + 最新 DREAMS.md 条目 -``` - ---- - -## 安全与审批 - -### Tool Guard 规则 - -``` -GET /api/v1/security/guard/config # 全局配置 -PUT /api/v1/security/guard/config # 更新全局配置 -GET /api/v1/security/guard/rules # 列自定义规则 -GET /api/v1/security/guard/rules/builtin # 列内置规则 -POST /api/v1/security/guard/rules # 创建规则 -PUT /api/v1/security/guard/rules/{id} # 更新规则 -DELETE /api/v1/security/guard/rules/{id} # 删除规则 -PUT /api/v1/security/guard/rules/{id}/toggle?enabled={bool} # 开关规则 -``` - -### File Guard - -``` -GET /api/v1/security/guard/config/file-guard # 获取配置 -PUT /api/v1/security/guard/config/file-guard # 更新配置 -``` - -### 审批 - -``` -GET /api/v1/approvals?status=pending # 列 pending 审批 -POST /api/v1/approvals/{id}/resolve # 批准或拒绝 -``` - -请求体: - -```json -{ "decision": "approved" } -``` - -或 - -```json -{ "decision": "rejected", "notes": "原因" } -``` - -### 审计日志 - -``` -GET /api/v1/security/audit/logs # 查询(?toolName, ?decision, ?from, ?to) -GET /api/v1/security/audit/stats # 统计 -GET /api/v1/audit/events # 完整审计事件查询 -``` - ---- - -## 模型 - -``` -GET /api/v1/models # 列出模型 -GET /api/v1/models/enabled # 仅列已启用 -GET /api/v1/models/default # 默认模型 -GET /api/v1/models/active # 活跃模型 -PUT /api/v1/models/active # 设置活跃 -POST /api/v1/models # 创建模型配置 -PUT /api/v1/models/{id} # 更新 -DELETE /api/v1/models/{id} # 删除 -POST /api/v1/models/{id}/default # 设为默认 - -PUT /api/v1/models/{providerId}/config # 更新供应商配置 -POST /api/v1/models/custom-providers # 创建自定义供应商 -DELETE /api/v1/models/custom-providers/{providerId} # 删除自定义供应商 - -POST /api/v1/models/{providerId}/models # 往供应商加模型 -DELETE /api/v1/models/{providerId}/models/{modelId} # 移除模型 - -POST /api/v1/models/{providerId}/discover # 发现模型 -POST /api/v1/models/{providerId}/discover/apply # 应用已发现 -POST /api/v1/models/{providerId}/test-connection # 测试供应商连接 -POST /api/v1/models/{providerId}/models/{modelId}/test # 测试单个模型 -``` - -### 遗留端点 - -``` -GET /api/v1/model-providers # 遗留——优先用 /api/v1/models -POST /api/v1/model-providers -PUT /api/v1/model-providers/{id} -DELETE /api/v1/model-providers/{id} - -GET /api/v1/model-configs # 遗留——优先用 /api/v1/models -POST /api/v1/model-configs -PUT /api/v1/model-configs/{id} -DELETE /api/v1/model-configs/{id} -``` - ---- - -## 渠道 - -``` -GET /api/v1/channels # 列表 -POST /api/v1/channels # 创建 -PUT /api/v1/channels/{id} # 更新 -DELETE /api/v1/channels/{id} # 删除 -PUT /api/v1/channels/{id}/toggle?enabled={bool} # 开关 -GET /api/v1/channels/status # 每个渠道的连接状态 -GET /api/v1/channels/health # 聚合健康视图 - -GET /api/v1/channels/webhook/weixin/qrcode # 微信 iLink 二维码 -GET /api/v1/channels/webhook/weixin/qrcode/status # 扫码状态 - -POST /api/v1/channels/qrcode/qq/begin # 发起 QQ 扫码绑定 -GET /api/v1/channels/qrcode/qq/status # QQ 扫码绑定状态 -``` - -### 渠道 webhook 回调 - -| 渠道 | 回调 URL | -|------|----------| -| 钉钉 | `POST /api/v1/channels/webhook/dingtalk` | -| 飞书 | `POST /api/v1/channels/webhook/feishu` | -| 企业微信 | `POST /api/v1/channels/webhook/wecom` | -| Telegram | `POST /api/v1/channels/webhook/telegram` | -| Discord | *(Gateway——无 webhook)* | -| QQ | `POST /api/v1/channels/webhook/qq` | -| Slack | `POST /api/v1/channels/webhook/slack` | -| 微信 | `POST /api/v1/channels/webhook/weixin` | - ---- - -## 定时任务 - -``` -GET /api/v1/cron-jobs # 列表 -POST /api/v1/cron-jobs # 创建 -PUT /api/v1/cron-jobs/{id} # 更新 -DELETE /api/v1/cron-jobs/{id} # 删除 -PUT /api/v1/cron-jobs/{id}/toggle?enabled={bool} # 开关 -POST /api/v1/cron-jobs/{id}/run # 立即执行 -``` - ---- - -## 工作流(1.3.0+) - -完整字段、step mode、Pebble 语法见 [工作流](./workflow)。 - -``` -GET /api/v1/workflows # 列表 -GET /api/v1/workflows/{id} # 获取(含已发布 revision + 草稿) -POST /api/v1/workflows # 新建 -PUT /api/v1/workflows/{id} # 更新元数据(name / description / enabled) -PUT /api/v1/workflows/{id}/draft # 保存草稿(graph_json,不编译) -POST /api/v1/workflows/{id}/publish # 发布草稿为新 revision -DELETE /api/v1/workflows/{id} # 删除 - -POST /api/v1/workflows/{id}/compile # 编译已存草稿 + 诊断,不发布 -POST /api/v1/workflows/draft/generate # 自然语言生成 graph_json 草稿 -POST /api/v1/workflows/draft/preview-compile # 编译任意草稿 JSON(不入库,模板/生成器预览) -GET /api/v1/workflows/draft/templates # 生成器可直接套用的模板列表 - -GET /api/v1/workflows/{id}/runs # run 列表(limit,默认 50) -GET /api/v1/workflows/runs/paused # 当前 workspace 所有 paused run(运维入口) -GET /api/v1/workflows/runs/{runId} # run 详情 + 每步 input/output/token/duration -POST /api/v1/workflows/runs/{runId}/resume # await_approval 暂停后恢复 -``` - -> v0 没有「手动起 run / 取消 run」的端点——工作流的实际启动只能经[触发器](./triggers)或 `await_approval` 恢复(`/runs/{runId}/resume`);想试跑用 `/draft/preview-compile`(只编译、不入库、不真跑)。手动启动 run 已在规划中,后续版本提供。 - ---- - -## 触发器(1.3.0+) - -6 种 pattern type、事件治理、跨实例一致性见 [触发器](./triggers)。 - -``` -GET /api/v1/triggers # 列表 -GET /api/v1/triggers/{id} # 获取 -POST /api/v1/triggers # 新建 -PUT /api/v1/triggers/{id} # 更新(含 enabled 开关;改 cron 表达式会 bump pattern_version) -DELETE /api/v1/triggers/{id} # 删除 - -POST /api/v1/triggers/events # 通用事件入口(webhook / 桥接外部系统);按 X-Workspace-Id 投递 - # → 经去重 / 限流 / bot-self 后同步派发,返回每条 trigger 的 fire/drop 结果 -``` - ---- - -## 目标(1.4.0+) - -目标完成评分、自动跟进的行为细节见 [目标](./goals)。 - -``` -POST /api/v1/goals # 新建目标 -GET /api/v1/goals/{id} # 获取目标 -PATCH /api/v1/goals/{id} # 更新目标(部分) -GET /api/v1/goals/{id}/events # 该目标的评估事件历史 -``` - ---- - -## Token 用量 - -``` -GET /api/v1/token-usage?startDate=&endDate=&modelName=&providerId= -``` - ---- - -## 系统设置 - -``` -GET /api/v1/settings # 所有设置 -PUT /api/v1/settings # 更新多个 -GET /api/v1/settings/language # 当前语言 -PUT /api/v1/settings/language # 更新语言 -PUT /api/v1/settings/{key} # 更新单个 key -``` - ---- - -## 仪表盘 - -``` -GET /api/v1/dashboard/summary # 用量汇总卡片 -GET /api/v1/dashboard/trends # 趋势图(?range=7d|30d|90d) -GET /api/v1/dashboard/top-agents # 最常用 Agent -GET /api/v1/dashboard/top-tools # 最常用工具 -``` - ---- - -## 工作空间 - -``` -GET /api/v1/workspaces # 列表 -GET /api/v1/workspaces/{id} # 获取 -POST /api/v1/workspaces # 创建 -PUT /api/v1/workspaces/{id} # 更新 -DELETE /api/v1/workspaces/{id} # 删除(仅 owner) -GET /api/v1/workspaces/{id}/access # 当前用户访问信息(见下) -``` - -### 成员与 RBAC(1.4.0+) - -`/access` 返回调用者在该工作空间内的有效权限,前端据此渲染路由和菜单: - -```json -{ - "memberRole": "editor", - "isGlobalAdmin": false, - "effectiveRole": "editor", - "capabilities": ["workspace.read", "conversation.write", "..."] -} -``` - -``` -GET /api/v1/workspaces/{id}/members # 列成员 -POST /api/v1/workspaces/{id}/members # 添加成员 -PUT /api/v1/workspaces/{id}/members/{memberId} # 更新成员(角色等) -DELETE /api/v1/workspaces/{id}/members/{memberId} # 移除成员 -``` - ---- - -## Doctor(健康检查) - -``` -GET /api/v1/doctor/run # 运行所有检查 -GET /api/v1/doctor/checks # 缓存的检查结果 -``` - ---- - -## 错误响应 - -```json -{ - "code": 400, - "message": "Validation failed: name is required" -} -``` - -### 常见状态码 - -| 状态码 | 含义 | -|--------|------| -| 200 | 成功 | -| 400 | 错误请求 | -| 401 | 未授权 | -| 403 | 禁止 | -| 404 | 未找到 | -| 500 | 服务端错误 | - ---- - -## 分页 - -列表端点按一致的 shape 返回分页结果: - -```json -{ - "code": 200, - "data": { - "records": [ ], - "total": 42, - "current": 1, - "size": 20, - "pages": 3 - } -} -``` - -| 字段 | 用途 | -|------|------| -| `records` | 当前页的条目数组 | -| `total` | 总条数 | -| `current` | 当前页(从 1 开始) | -| `size` | 每页条数 | -| `pages` | 总页数 | - ---- - -## 下一步 - -- [快速开始](./quickstart)——让服务器跑起来 -- [安全与审批](./security)——JWT + 审批流程 -- [聊天与消息](./chat)——SSE 事件格式 -- [LLM Wiki](./wiki)——Wiki 端点行为 +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/conversations` | `获取会话列表` | +| `POST` | `/api/v1/conversations/batch-delete` | `批量删除会话` | +| `GET` | `/api/v1/conversations/page` | `分页查询会话列表` | +| `DELETE` | `/api/v1/conversations/{conversationId}` | `删除会话` | +| `DELETE` | `/api/v1/conversations/{conversationId}/messages` | `清空会话消息` | +| `GET` | `/api/v1/conversations/{conversationId}/messages` | `获取会话消息历史(支持分页)` | +| `PUT` | `/api/v1/conversations/{conversationId}/model` | `切换会话使用的模型 (provider + model name)` | +| `PUT` | `/api/v1/conversations/{conversationId}/pin` | `置顶或取消置顶会话` | +| `GET` | `/api/v1/conversations/{conversationId}/status` | `获取会话流状态` | +| `PUT` | `/api/v1/conversations/{conversationId}/title` | `重命名会话` | + +### Agent + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/agents` | `获取Agent列表` | +| `POST` | `/api/v1/agents` | `创建Agent` | +| `GET` | `/api/v1/agents/{agentId}/provider-preferences` | `获取 Agent 的偏好 Provider 顺序` | +| `PUT` | `/api/v1/agents/{agentId}/provider-preferences` | `批量设置 Agent 的偏好 Provider 顺序(替换模式)` | +| `GET` | `/api/v1/agents/{agentId}/skills` | `获取 Agent 已绑定的 Skills` | +| `PUT` | `/api/v1/agents/{agentId}/skills` | `批量设置 Agent 的 Skill 绑定` | +| `DELETE` | `/api/v1/agents/{agentId}/skills/{skillId}` | `解绑单个 Skill` | +| `POST` | `/api/v1/agents/{agentId}/skills/{skillId}` | `绑定单个 Skill` | +| `GET` | `/api/v1/agents/{agentId}/tools` | `获取 Agent 已绑定的 Tools` | +| `PUT` | `/api/v1/agents/{agentId}/tools` | `批量设置 Agent 的 Tool 绑定` | +| `GET` | `/api/v1/agents/{agentId}/workspace/files` | `列出工作区文件` | +| `DELETE` | `/api/v1/agents/{agentId}/workspace/files/**` | `删除工作区文件` | +| `GET` | `/api/v1/agents/{agentId}/workspace/files/**` | `读取工作区文件` | +| `PUT` | `/api/v1/agents/{agentId}/workspace/files/**` | `保存工作区文件` | +| `GET` | `/api/v1/agents/{agentId}/workspace/memory/export` | `导出 Agent 记忆快照(ZIP)` | +| `POST` | `/api/v1/agents/{agentId}/workspace/memory/import` | `导入 Agent 记忆快照(写入)` | +| `POST` | `/api/v1/agents/{agentId}/workspace/memory/import/preview` | `预览导入 Agent 记忆快照(不写入)` | +| `GET` | `/api/v1/agents/{agentId}/workspace/prompt-files` | `获取系统提示文件列表` | +| `PUT` | `/api/v1/agents/{agentId}/workspace/prompt-files` | `设置系统提示文件列表` | +| `DELETE` | `/api/v1/agents/{id}` | `删除Agent` | +| `GET` | `/api/v1/agents/{id}` | `获取Agent详情` | +| `PUT` | `/api/v1/agents/{id}` | `更新Agent` | +| `GET` | `/api/v1/agents/{id}/capabilities` | `获取Agent当前能力(modality 集合 + sidecar 配置),用于聊天页提示条` | +| `POST` | `/api/v1/agents/{id}/chat` | `同步对话` | +| `GET` | `/api/v1/agents/{id}/chat/stream` | `流式对话(SSE)` | +| `POST` | `/api/v1/agents/{id}/execute` | `执行复杂任务(Plan-Execute)` | +| `GET` | `/api/v1/agents/{id}/state` | `获取Agent运行状态` | + +### Agent 模板 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/templates` | `获取模板列表` | +| `POST` | `/api/v1/templates/{id}/apply` | `应用模板创建Agent` | + +### 子 Agent + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/subagents/active` | `List active sub-agents in a conversation's delegation tree` | +| `POST` | `/api/v1/subagents/spawn-pause` | `Set sub-agent spawn-pause for a conversation` | +| `POST` | `/api/v1/subagents/{subagentId}/interrupt` | `Interrupt a running sub-agent` | + +### 运行时管理 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `POST` | `/api/v1/admin/agent-runtime/runs/{conversationId}/recycle` | `Force recycle — dispose flux + drop RunState; use after friendly stop ignored` | +| `POST` | `/api/v1/admin/agent-runtime/runs/{conversationId}/stop` | `Friendly stop — request the run to wind down at its next checkpoint` | +| `GET` | `/api/v1/admin/agent-runtime/snapshot` | `Snapshot of every in-flight agent turn` | +| `POST` | `/api/v1/admin/agent-runtime/subagents/{subagentId}/interrupt` | `Interrupt one sub-agent (admin override of ownership check)` | +| `POST` | `/api/v1/admin/agent-runtime/sweep` | `Recycle every run currently flagged as stuck` | + +### 自动批准策略 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/approval/grants` | `列出当前 workspace 的自动批准策略(分页)` | +| `POST` | `/api/v1/approval/grants` | `创建自动批准策略` | +| `GET` | `/api/v1/approval/grants/active` | `当前 workspace 的活跃策略数量摘要` | +| `DELETE` | `/api/v1/approval/grants/{id}` | `撤销自动批准策略` | +| `GET` | `/api/v1/approval/resolutions` | `查询审批最终决策日志(按 grantId 或 conversationId 过滤)` | + +### 安全与 Tool Guard + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/security/approvals` | `审批记录(管理视角)` | +| `GET` | `/api/v1/security/audit/logs` | `审计日志` | +| `GET` | `/api/v1/security/audit/stats` | `审计统计` | +| `GET` | `/api/v1/security/guard/config` | `获取 Guard 配置` | +| `PUT` | `/api/v1/security/guard/config` | `更新 Guard 配置` | +| `GET` | `/api/v1/security/guard/config/file-guard` | `获取 File Guard 配置` | +| `PUT` | `/api/v1/security/guard/config/file-guard` | `更新 File Guard 配置` | +| `GET` | `/api/v1/security/guard/rules` | `规则列表` | +| `POST` | `/api/v1/security/guard/rules` | `新增自定义规则` | +| `GET` | `/api/v1/security/guard/rules/builtin` | `内置规则列表` | +| `DELETE` | `/api/v1/security/guard/rules/by-id/{id}` | `按主键 ID 删除自定义规则(兜底,rule_id 异常时使用)` | +| `GET` | `/api/v1/security/guard/rules/export` | `导出全部规则为 JSON` | +| `POST` | `/api/v1/security/guard/rules/import` | `从 JSON 批量导入规则(upsert 语义)` | +| `DELETE` | `/api/v1/security/guard/rules/{ruleId}` | `删除自定义规则` | +| `PUT` | `/api/v1/security/guard/rules/{ruleId}` | `更新规则` | +| `PUT` | `/api/v1/security/guard/rules/{ruleId}/toggle` | `启用/禁用规则` | + +### 审计 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/audit/events` | `分页查询审计事件` | + +### 活动流 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/activity/feed` | `Unified activity feed (audit + approval + tool calls)` | + +### 通知 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/notifications/summary` | `Aggregated counts for the sidebar attention badges` | + +### 工作空间 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/workspaces` | `获取当前用户的工作区列表(含 memberRole 与 effectiveRole)` | +| `POST` | `/api/v1/workspaces` | `创建工作区` | +| `DELETE` | `/api/v1/workspaces/{id}` | `删除工作区` | +| `GET` | `/api/v1/workspaces/{id}` | `获取工作区详情` | +| `PUT` | `/api/v1/workspaces/{id}` | `更新工作区` | +| `GET` | `/api/v1/workspaces/{id}/access` | `获取当前用户在指定工作区的访问能力(路由守卫消费)` | +| `GET` | `/api/v1/workspaces/{id}/members` | `获取工作区成员列表` | +| `POST` | `/api/v1/workspaces/{id}/members` | `添加工作区成员` | +| `DELETE` | `/api/v1/workspaces/{id}/members/{targetUserId}` | `移除工作区成员` | +| `PUT` | `/api/v1/workspaces/{id}/members/{targetUserId}` | `更新成员角色` | + +### 系统设置 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/settings` | `获取系统设置` | +| `PUT` | `/api/v1/settings` | `保存系统设置` | +| `GET` | `/api/v1/settings/language` | `获取当前语言` | +| `PUT` | `/api/v1/settings/language` | `更新当前语言` | +| `PUT` | `/api/v1/settings/sidecar` | `更新多模态 sidecar 配置` | + +### 首次初始化 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `POST` | `/api/v1/setup/init` | `Init` | +| `GET` | `/api/v1/setup/onboarding-status` | `Get Onboarding Status` | +| `GET` | `/api/v1/setup/status` | `Get Status` | + +### 系统健康 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/system/browser-health` | `Browser launch diagnostics` | +| `GET` | `/api/v1/system/health` | `System health check` | + +### 仪表盘 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/dashboard/cron-runs` | `获取最近执行记录(当前 workspace 关联的 CronJob)` | +| `GET` | `/api/v1/dashboard/cron-runs/{cronJobId}` | `获取 CronJob 执行历史` | +| `GET` | `/api/v1/dashboard/overview` | `获取概览统计` | +| `GET` | `/api/v1/dashboard/trend` | `获取日用量趋势` | + +### Token 用量 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/token-usage` | `获取 Token 使用统计` | + +### 模型 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/models` | `获取 Provider 列表(仅 enabled)` | +| `POST` | `/api/v1/models` | `创建模型` | +| `GET` | `/api/v1/models/active` | `获取当前激活模型` | +| `PUT` | `/api/v1/models/active` | `设置当前激活模型` | +| `GET` | `/api/v1/models/by-type` | `按类型筛选模型(chat / embedding),可选 modality 过滤` | +| `GET` | `/api/v1/models/catalog` | `RFC-074: 获取 Provider 全量目录(含未启用),供 Add Provider 抽屉使用` | +| `DELETE` | `/api/v1/models/custom-providers` | `删除自定义 Provider(查询参数变体,兼容含特殊字符的旧 ID)` | +| `POST` | `/api/v1/models/custom-providers` | `创建自定义 Provider` | +| `DELETE` | `/api/v1/models/custom-providers/{providerId}` | `删除自定义 Provider` | +| `GET` | `/api/v1/models/default` | `获取默认模型` | +| `GET` | `/api/v1/models/embedding/default` | `获取系统默认 Embedding 模型 ID` | +| `POST` | `/api/v1/models/embedding/default` | `设置系统默认 Embedding 模型` | +| `POST` | `/api/v1/models/embedding/{modelId}/test` | `测试 Embedding 模型连通性(嵌入一个短文本验证 API key)` | +| `GET` | `/api/v1/models/enabled` | `获取启用模型列表` | +| `DELETE` | `/api/v1/models/{id}` | `删除模型` | +| `GET` | `/api/v1/models/{id}` | `获取模型详情` | +| `PUT` | `/api/v1/models/{id}` | `更新模型` | +| `POST` | `/api/v1/models/{id}/default` | `设置默认模型` | +| `PUT` | `/api/v1/models/{providerId}/config` | `更新 Provider 配置` | +| `POST` | `/api/v1/models/{providerId}/disable` | `RFC-074: 禁用 Provider(如其下模型为当前默认会自动切换)` | +| `POST` | `/api/v1/models/{providerId}/discover` | `发现远端模型` | +| `POST` | `/api/v1/models/{providerId}/discover/apply` | `批量添加发现的模型` | +| `POST` | `/api/v1/models/{providerId}/enable` | `RFC-074: 启用 Provider` | +| `DELETE` | `/api/v1/models/{providerId}/models` | `从 Provider 删除模型` | +| `POST` | `/api/v1/models/{providerId}/models` | `向 Provider 添加模型` | +| `POST` | `/api/v1/models/{providerId}/models/test` | `测试单个模型可用性` | +| `POST` | `/api/v1/models/{providerId}/test-connection` | `测试供应商连接` | + +### OAuth + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `POST` | `/api/v1/oauth/anthropic/reload` | `Force re-detect credentials and refresh if near expiry` | +| `GET` | `/api/v1/oauth/anthropic/status` | `Read current Claude Code OAuth credential status from local disk` | +| `GET` | `/api/v1/oauth/openai/authorize` | `获取 OAuth 授权 URL(自动选 LOCAL / MANUAL_PASTE 模式)` | +| `POST` | `/api/v1/oauth/openai/callback-paste` | `MANUAL_PASTE 模式:用户粘贴浏览器回调 URL 完成 OAuth` | +| `POST` | `/api/v1/oauth/openai/device/cancel` | `Device flow: cancel a pending session` | +| `POST` | `/api/v1/oauth/openai/device/poll` | `Device flow: poll for completion` | +| `POST` | `/api/v1/oauth/openai/device/start` | `Device flow: start — request user_code` | +| `POST` | `/api/v1/oauth/openai/refresh` | `手动刷新 Token` | +| `DELETE` | `/api/v1/oauth/openai/revoke` | `清除 OAuth 凭证` | +| `GET` | `/api/v1/oauth/openai/status` | `获取 OAuth 连接状态` | + +### LLM 运行时 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/llm/provider-pool` | `查询所有 provider 的池状态 + 冷却信息` | +| `POST` | `/api/v1/llm/provider-pool/{providerId}/reprobe` | `手动重新探测某个 provider,立即更新池状态` | + +### 工具 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/tools` | `获取工具列表` | +| `POST` | `/api/v1/tools` | `创建工具(MCP)` | +| `GET` | `/api/v1/tools/available` | `获取员工可绑定的全部原子工具(含 MCP)` | +| `GET` | `/api/v1/tools/enabled` | `获取已启用工具列表` | +| `DELETE` | `/api/v1/tools/{id}` | `删除工具` | +| `GET` | `/api/v1/tools/{id}` | `获取工具详情` | +| `PUT` | `/api/v1/tools/{id}` | `更新工具` | +| `PUT` | `/api/v1/tools/{id}/disclosure-tier` | `设置工具披露分级(core / extension)` | +| `PUT` | `/api/v1/tools/{id}/toggle` | `启用/禁用工具` | + +### MCP 服务 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/mcp/servers` | `获取 MCP Server 列表` | +| `POST` | `/api/v1/mcp/servers` | `创建 MCP Server` | +| `POST` | `/api/v1/mcp/servers/refresh` | `刷新所有 MCP Server 连接` | +| `DELETE` | `/api/v1/mcp/servers/{id}` | `删除 MCP Server` | +| `GET` | `/api/v1/mcp/servers/{id}` | `获取 MCP Server 详情` | +| `PUT` | `/api/v1/mcp/servers/{id}` | `更新 MCP Server` | +| `PUT` | `/api/v1/mcp/servers/{id}/disclosure-tier` | `设置 MCP Server 披露分级(core / extension),整组工具跟随` | +| `POST` | `/api/v1/mcp/servers/{id}/test` | `测试 MCP Server 连接` | +| `PUT` | `/api/v1/mcp/servers/{id}/toggle` | `启用/禁用 MCP Server` | +| `GET` | `/api/v1/mcp/servers/{id}/tools` | `列出 MCP Server 已发现的工具` | + +### ACP 端点 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/acp/endpoints` | `List ACP endpoints` | +| `POST` | `/api/v1/acp/endpoints` | `Create a custom ACP endpoint` | +| `DELETE` | `/api/v1/acp/endpoints/{id}` | `Delete an ACP endpoint (builtins are protected)` | +| `GET` | `/api/v1/acp/endpoints/{id}` | `Get ACP endpoint by id` | +| `PUT` | `/api/v1/acp/endpoints/{id}` | `Update an ACP endpoint` | +| `POST` | `/api/v1/acp/endpoints/{id}/test` | `Test ACP endpoint connection (initialize handshake)` | +| `PUT` | `/api/v1/acp/endpoints/{id}/toggle` | `Enable / disable an ACP endpoint` | + +### 技能 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/skills` | `获取技能分页列表(RFC-042 §2.1)` | +| `POST` | `/api/v1/skills` | `创建技能` | +| `GET` | `/api/v1/skills/counts` | `获取各类型技能计数(tab 徽章用)` | +| `POST` | `/api/v1/skills/curator/activate` | `激活/取消激活 curator(真正归档 vs 仅预览)` | +| `POST` | `/api/v1/skills/curator/dry-run` | `立即运行一次 curator 预览(dry-run)` | +| `POST` | `/api/v1/skills/curator/pause` | `暂停 curator 定时扫描` | +| `GET` | `/api/v1/skills/curator/reports` | `列出最近的 curator 运行报告` | +| `GET` | `/api/v1/skills/curator/reports/{runId}` | `读取某次 curator 运行报告` | +| `POST` | `/api/v1/skills/curator/resume` | `恢复 curator 定时扫描` | +| `GET` | `/api/v1/skills/curator/status` | `curator 控制面状态` | +| `GET` | `/api/v1/skills/enabled` | `获取已启用技能列表` | +| `POST` | `/api/v1/skills/install/cancel/{taskId}` | `取消安装任务` | +| `GET` | `/api/v1/skills/install/hub/search` | `搜索 ClawHub 市场` | +| `POST` | `/api/v1/skills/install/start` | `开始异步安装 skill` | +| `GET` | `/api/v1/skills/install/status/{taskId}` | `查询安装任务状态` | +| `POST` | `/api/v1/skills/install/upload` | `上传 ZIP 安装 skill` | +| `DELETE` | `/api/v1/skills/install/{skillName}` | `卸载 skill` | +| `GET` | `/api/v1/skills/prompt-preview` | `预览技能 Prompt 增强效果(调试用,与 Agent 真实运行时一致)` | +| `GET` | `/api/v1/skills/runtime/active` | `获取 active skills 运行时视图` | +| `POST` | `/api/v1/skills/runtime/refresh` | `刷新 active skills 缓存,resync=true 时同步内置技能到 workspace` | +| `GET` | `/api/v1/skills/runtime/status` | `获取所有技能的运行时解析状态(管理页面使用)` | +| `GET` | `/api/v1/skills/summary` | `获取已启用技能摘要(按类型分组)` | +| `POST` | `/api/v1/skills/sync-files` | `Re-sync every skill's bundle files (admin)` | +| `POST` | `/api/v1/skills/synthesize-from-conversation` | `从对话历史合成 Skill(RFC-023)` | +| `GET` | `/api/v1/skills/type/{skillType}` | `按类型获取技能列表` | +| `DELETE` | `/api/v1/skills/{id}` | `硬删除技能 (admin only — 物理删除 + 工作区清空)` | +| `GET` | `/api/v1/skills/{id}` | `获取技能详情` | +| `PUT` | `/api/v1/skills/{id}` | `更新技能` | +| `POST` | `/api/v1/skills/{id}/archive` | `手动归档技能` | +| `GET` | `/api/v1/skills/{id}/employees` | `List agents that can use this skill (RFC-090 §14.2)` | +| `POST` | `/api/v1/skills/{id}/export-workspace` | `将 skill 导出到工作区目录` | +| `GET` | `/api/v1/skills/{id}/lessons` | `Read per-skill LESSONS.md (RFC-090 §11.4)` | +| `POST` | `/api/v1/skills/{id}/lessons/clear` | `Clear all lessons for a skill (RFC-090 §11.4)` | +| `POST` | `/api/v1/skills/{id}/pin` | `钉住/取消钉住技能(钉住的技能不会被自动归档)` | +| `GET` | `/api/v1/skills/{id}/requirements` | `Pre-flight requirement statuses for a skill (RFC-090)` | +| `POST` | `/api/v1/skills/{id}/rescan` | `重新扫描单个技能(RFC-042 §2.3.4)` | +| `POST` | `/api/v1/skills/{id}/restore` | `恢复已归档的技能` | +| `POST` | `/api/v1/skills/{id}/sync-files` | `Re-sync this skill's bundle files from DB → local workspace cache` | +| `PUT` | `/api/v1/skills/{id}/toggle` | `启用/禁用技能` | +| `GET` | `/api/v1/skills/{id}/workspace` | `获取 skill 工作区信息` | +| `GET` | `/api/v1/skills/{skillId}/secrets` | `List secret keys + masked previews for a skill` | +| `POST` | `/api/v1/skills/{skillId}/secrets` | `Upsert a secret value (empty value deletes it)` | +| `DELETE` | `/api/v1/skills/{skillId}/secrets/{key}` | `Delete a single secret by key` | + +### 技能模板 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/skill-templates` | `List skill templates (RFC-091)` | +| `GET` | `/api/v1/skill-templates/{id}` | `Get a single skill template` | +| `POST` | `/api/v1/skill-templates/{id}/instantiate` | `Instantiate a template into a skill` | + +### 插件 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/plugins` | `List all plugins` | +| `GET` | `/api/v1/plugins/{name}` | `Get plugin detail` | +| `PUT` | `/api/v1/plugins/{name}/config` | `Update plugin configuration` | +| `POST` | `/api/v1/plugins/{name}/disable` | `Disable a plugin` | +| `POST` | `/api/v1/plugins/{name}/enable` | `Enable a plugin` | + +### LLM Wiki + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `POST` | `/api/v1/wiki/admin/backfill-tokens` | `Force-run the token-count backfill batch now` | +| `POST` | `/api/v1/wiki/admin/kb/{kbId}/rebuild-overview` | `Ensure overview/log scaffold + rebuild overview stats now` | +| `GET` | `/api/v1/wiki/chunks/{chunkId}/pages` | `Pages By Chunk Id` | +| `DELETE` | `/api/v1/wiki/hot-cache/{kbId}` | `Soft-delete the hot cache row` | +| `GET` | `/api/v1/wiki/hot-cache/{kbId}` | `Get the current hot cache snapshot for a KB` | +| `POST` | `/api/v1/wiki/hot-cache/{kbId}/regenerate` | `Schedule a manual rebuild of the hot cache` | +| `GET` | `/api/v1/wiki/kb/{kbId}/jobs` | `Get Jobs` | +| `GET` | `/api/v1/wiki/kb/{kbId}/pages/{pageId}/citations` | `Page Citations` | +| `GET` | `/api/v1/wiki/kb/{kbId}/pages/{slugA}/relation/{slugB}` | `Explain Relation` | +| `POST` | `/api/v1/wiki/kb/{kbId}/pages/{slug}/enrich` | `Enrich Page` | +| `GET` | `/api/v1/wiki/kb/{kbId}/pages/{slug}/related` | `Related Pages` | +| `POST` | `/api/v1/wiki/kb/{kbId}/pages/{slug}/repair` | `Repair Page` | +| `POST` | `/api/v1/wiki/kb/{kbId}/search-preview` | `Search Preview` | +| `GET` | `/api/v1/wiki/kb/{kbId}/stats` | `Kb Stats` | +| `GET` | `/api/v1/wiki/knowledge-bases` | `获取所有知识库` | +| `POST` | `/api/v1/wiki/knowledge-bases` | `创建知识库` | +| `GET` | `/api/v1/wiki/knowledge-bases/agent/{agentId}` | `按 Agent 获取知识库` | +| `GET` | `/api/v1/wiki/knowledge-bases/bindable` | `列出可绑定到指定 Agent 的知识库` | +| `DELETE` | `/api/v1/wiki/knowledge-bases/{id}` | `删除知识库` | +| `GET` | `/api/v1/wiki/knowledge-bases/{id}` | `获取知识库详情` | +| `PUT` | `/api/v1/wiki/knowledge-bases/{id}` | `更新知识库` | +| `GET` | `/api/v1/wiki/knowledge-bases/{id}/config` | `获取知识库配置` | +| `PUT` | `/api/v1/wiki/knowledge-bases/{id}/config` | `更新知识库配置` | +| `GET` | `/api/v1/wiki/knowledge-bases/{id}/page-type-profile` | `获取知识库 pageType profile(未配置则返回内置默认)` | +| `PUT` | `/api/v1/wiki/knowledge-bases/{id}/page-type-profile` | `保存知识库 pageType profile` | +| `POST` | `/api/v1/wiki/knowledge-bases/{id}/page-type-profile/reset-default` | `重置 pageType profile 为内置默认` | +| `POST` | `/api/v1/wiki/knowledge-bases/{id}/page-type-profile/validate` | `校验 pageType profile JSON(不保存)` | +| `POST` | `/api/v1/wiki/knowledge-bases/{id}/scan` | `扫描关联目录导入文件` | +| `PUT` | `/api/v1/wiki/knowledge-bases/{id}/source-directory` | `设置知识库关联目录` | +| `GET` | `/api/v1/wiki/knowledge-bases/{id}/source-watcher` | `查看知识库源监听状态` | +| `POST` | `/api/v1/wiki/knowledge-bases/{id}/source-watcher/scan` | `手动触发一次源监听扫描` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/agents/{agentId}/page-type-permissions` | `列出某 Agent 在知识库下的 pageType 权限规则` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/agents/{agentId}/page-type-permissions` | `新增或更新 Agent 的 pageType 权限规则(按 agent+kb+pageType 去重)` | +| `DELETE` | `/api/v1/wiki/knowledge-bases/{kbId}/agents/{agentId}/page-type-permissions/{id}` | `删除一条 Agent pageType 权限规则` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/lint/broken-links` | `读取最近一次死链扫描的聚合结果` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/lint/broken-links` | `启动 Wiki 死链扫描 job(异步)` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/lint/broken-links/jobs/{jobId}` | `查询 Wiki 死链扫描 job 状态` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pages` | `获取 Wiki 页面列表(可按原始材料过滤)` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/archived` | `列出知识库中所有 archived=1 的页面(不含 content)` | +| `DELETE` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/batch` | `批量删除 Wiki 页面` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/refs` | `获取 Wiki 页面引用索引(slug/title/archived,供 wikilink 解析)` | +| `DELETE` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}` | `删除 Wiki 页面` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}` | `获取 Wiki 页面内容` | +| `PUT` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}` | `手动编辑 Wiki 页面` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}/archive` | `归档单个页面(软归档;可恢复)` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}/backlinks` | `获取反向链接` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}/rename` | `重命名 Wiki 页面,并级联更新所有引用方` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/pages/{slug}/unarchive` | `取消归档` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pipeline-runs/{runId}` | `查询单次 run 的步骤明细` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pipelines` | `列出知识库的 pipeline 定义` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/pipelines` | `保存(创建/更新)pipeline 定义(YAML/JSON)` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/pipelines/validate` | `校验 pipeline 配置(不保存)` | +| `DELETE` | `/api/v1/wiki/knowledge-bases/{kbId}/pipelines/{id}` | `删除 pipeline 定义` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/pipelines/{id}/runs` | `查询 pipeline 运行记录` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/process` | `触发知识库处理(异步);force=true 时清空所有 last_processed_hash 并重新入队全部材料` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/processing-status` | `获取处理状态` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/progress` | `订阅处理进度 SSE` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/raw` | `获取原始材料列表(含每条材料生成的页面数)` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/raw/text` | `添加文本材料` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/raw/upload` | `上传文件材料` | +| `DELETE` | `/api/v1/wiki/knowledge-bases/{kbId}/raw/{rawId}` | `删除原始材料` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/raw/{rawId}/cancel` | `请求取消正在进行的处理(仅在 processing 状态有效)` | +| `GET` | `/api/v1/wiki/knowledge-bases/{kbId}/raw/{rawId}/download` | `下载原始材料` | +| `POST` | `/api/v1/wiki/knowledge-bases/{kbId}/raw/{rawId}/reprocess` | `重新处理原始材料(force=true 时绕过 content_hash 短路)` | +| `GET` | `/api/v1/wiki/pages/lookup` | `跨 KB 按 title 或 slug 查找页面(chat 端 wikilink 跳转用)` | +| `GET` | `/api/v1/wiki/raw/{rawId}/pages` | `Pages By Raw Id` | +| `POST` | `/api/v1/wiki/research/start` | `启动 Deep Research,返回 SSE sessionId` | +| `GET` | `/api/v1/wiki/research/stream/{sessionId}` | `订阅 Deep Research SSE 事件流` | +| `GET` | `/api/v1/wiki/transformations` | `List transformations available to a KB` | +| `POST` | `/api/v1/wiki/transformations` | `Create` | +| `GET` | `/api/v1/wiki/transformations/runs` | `List Runs` | +| `DELETE` | `/api/v1/wiki/transformations/runs/{runId}` | `Delete Run` | +| `GET` | `/api/v1/wiki/transformations/runs/{runId}` | `Get Run` | +| `POST` | `/api/v1/wiki/transformations/runs/{runId}/cancel` | `Cancel a still-running transformation run` | +| `POST` | `/api/v1/wiki/transformations/runs/{runId}/save-as-page` | `Save a completed run's output as a synthesis wiki page` | +| `DELETE` | `/api/v1/wiki/transformations/{id}` | `Delete` | +| `GET` | `/api/v1/wiki/transformations/{id}` | `Get` | +| `PUT` | `/api/v1/wiki/transformations/{id}` | `Update` | +| `POST` | `/api/v1/wiki/transformations/{id}/aggregate` | `Aggregate all completed runs of a template into one KB-level synthesis page` | +| `POST` | `/api/v1/wiki/transformations/{id}/apply` | `Run a transformation against a raw material or wiki page` | + +### 记忆 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/memory/{agentId}/dream/events` | `Subscribe to dream events (SSE)` | +| `GET` | `/api/v1/memory/{agentId}/dream/morning-card` | `Get morning card for current user + agent` | +| `POST` | `/api/v1/memory/{agentId}/dream/morning-card/seen` | `Mark morning card as seen` | +| `GET` | `/api/v1/memory/{agentId}/dream/reports` | `List dream reports (paginated, newest first)` | +| `GET` | `/api/v1/memory/{agentId}/dream/reports/{reportId}` | `Get a single dream report by ID` | +| `POST` | `/api/v1/memory/{agentId}/dream/reports/{reportId}/entries/{key}/confirm` | `Confirm a memory entry (no-op acknowledgment)` | +| `POST` | `/api/v1/memory/{agentId}/dream/reports/{reportId}/entries/{key}/edit` | `Edit a memory entry — writes back to the target memory file with user-edited metadata` | +| `GET` | `/api/v1/memory/{agentId}/dreaming/candidates` | `查询召回候选列表(含评分详情)` | +| `GET` | `/api/v1/memory/{agentId}/dreaming/dreams` | `查询 DREAMS.md 整合日记` | +| `POST` | `/api/v1/memory/{agentId}/dreaming/focused` | `Focused Dream — 围绕指定主题触发记忆整合` | +| `GET` | `/api/v1/memory/{agentId}/dreaming/status` | `查询 Dreaming 状态(配置、统计、上次运行时间)` | +| `POST` | `/api/v1/memory/{agentId}/emergence` | `手动触发记忆整合(daily notes → MEMORY.md,NIGHTLY 模式)` | +| `GET` | `/api/v1/memory/{agentId}/facts` | `List facts for an agent` | +| `GET` | `/api/v1/memory/{agentId}/facts/contradictions` | `List unresolved contradictions` | +| `POST` | `/api/v1/memory/{agentId}/facts/contradictions/{contradictionId}/resolve` | `Resolve a contradiction (KEEP_A / KEEP_B / MERGE / IGNORE)` | +| `POST` | `/api/v1/memory/{agentId}/facts/{factId}/feedback` | `Submit feedback on a fact (HELPFUL/UNHELPFUL)` | +| `POST` | `/api/v1/memory/{agentId}/facts/{factId}/forget` | `Forget a fact — writes canonical metadata, rebuilds projection` | +| `POST` | `/api/v1/memory/{agentId}/summarize/{conversationId}` | `手动触发对话记忆提取` | + +### 目标 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/goals` | `List goals (optionally filtered by status)` | +| `POST` | `/api/v1/goals` | `Create a persistent goal for a conversation` | +| `GET` | `/api/v1/goals/by-conversation/{conversationId}` | `Get the active goal bound to a conversation (or null)` | +| `GET` | `/api/v1/goals/{id}` | `Get goal detail by id` | +| `PATCH` | `/api/v1/goals/{id}` | `Sparse update of a non-terminal goal` | +| `POST` | `/api/v1/goals/{id}/abandon` | `Abandon a goal (terminal)` | +| `POST` | `/api/v1/goals/{id}/criteria` | `Append a sub-criterion to an active goal` | +| `GET` | `/api/v1/goals/{id}/events` | `Get the event timeline for a goal` | +| `POST` | `/api/v1/goals/{id}/pause` | `Pause an active goal` | +| `POST` | `/api/v1/goals/{id}/resume` | `Resume a paused goal` | + +### 定时任务 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/cron-jobs` | `获取定时任务列表` | +| `POST` | `/api/v1/cron-jobs` | `创建定时任务` | +| `GET` | `/api/v1/cron-jobs/active-runs` | `查询会话下正在执行的定时任务运行` | +| `DELETE` | `/api/v1/cron-jobs/{id}` | `删除定时任务` | +| `GET` | `/api/v1/cron-jobs/{id}` | `获取定时任务详情` | +| `PUT` | `/api/v1/cron-jobs/{id}` | `更新定时任务` | +| `POST` | `/api/v1/cron-jobs/{id}/run` | `立即执行定时任务` | +| `PUT` | `/api/v1/cron-jobs/{id}/toggle` | `启用/禁用定时任务` | + +### 触发器 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/triggers` | `List triggers in the caller's workspace.` | +| `POST` | `/api/v1/triggers` | `Create a trigger; if enabled, registers it with the scheduler.` | +| `POST` | `/api/v1/triggers/events` | `Ingest one event envelope; returns per-trigger fire / drop summary.` | +| `DELETE` | `/api/v1/triggers/{id}` | `Delete a trigger and unregister its schedule.` | +| `GET` | `/api/v1/triggers/{id}` | `Get a trigger by id, scoped to the caller's workspace.` | +| `PUT` | `/api/v1/triggers/{id}` | `Update a trigger; pattern_version bumps when the cron expression changes.` | + +### 工作流 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/workflows` | `List workflows in the workspace` | +| `POST` | `/api/v1/workflows` | `Create a workflow row (draft starts empty).` | +| `POST` | `/api/v1/workflows/draft/generate` | `Generate a workflow draft from a natural-language description.` | +| `POST` | `/api/v1/workflows/draft/preview-compile` | `Compile arbitrary draft JSON without persisting — used by the template picker / generator preview to surface real ACL + schema diagnostics before a workflow row exists.` | +| `GET` | `/api/v1/workflows/draft/templates` | `List the canonical workflow templates the generator can apply directly.` | +| `GET` | `/api/v1/workflows/runs/paused` | `List paused runs across the workspace so operators can resume them.` | +| `GET` | `/api/v1/workflows/runs/{runId}` | `Inspect a single run with its step rows for replay / debugging.` | +| `POST` | `/api/v1/workflows/runs/{runId}/resume` | `Resume a paused workflow run with the given outcome.` | +| `DELETE` | `/api/v1/workflows/{id}` | `Soft-delete a workflow row.` | +| `GET` | `/api/v1/workflows/{id}` | `Get a workflow by id (includes inline draft + latest published graph).` | +| `PUT` | `/api/v1/workflows/{id}` | `Update workflow metadata (name / description / enabled).` | +| `POST` | `/api/v1/workflows/{id}/compile` | `Compile the draft and surface diagnostics without persisting a revision.` | +| `PUT` | `/api/v1/workflows/{id}/draft` | `Save the inline draft graph_json without compiling.` | +| `POST` | `/api/v1/workflows/{id}/publish` | `Compile the draft and persist a new revision pointed at by latest_revision_id.` | +| `GET` | `/api/v1/workflows/{id}/runs` | `List the most recent runs for a workflow.` | + +### 渠道 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/channels` | `获取渠道列表` | +| `POST` | `/api/v1/channels` | `创建渠道` | +| `GET` | `/api/v1/channels/health` | `批量获取所有渠道健康状态` | +| `POST` | `/api/v1/channels/preflight` | `Pre-flight: validate draft channel config without persisting` | +| `POST` | `/api/v1/channels/qrcode/{channelType}/begin` | `启动指定渠道的扫码授权流程` | +| `GET` | `/api/v1/channels/qrcode/{channelType}/status` | `查询指定渠道的扫码授权状态` | +| `GET` | `/api/v1/channels/status` | `获取渠道运行状态(全局系统视图,仅管理员可见)` | +| `GET` | `/api/v1/channels/type/{channelType}` | `按类型获取渠道列表` | +| `GET` | `/api/v1/channels/webchat/config` | `获取 WebChat 配置` | +| `POST` | `/api/v1/channels/webchat/stream` | `WebChat SSE 流式对话` | +| `POST` | `/api/v1/channels/webhook/dingtalk` | `钉钉消息回调` | +| `POST` | `/api/v1/channels/webhook/dingtalk/register/begin` | `启动钉钉扫码注册应用流程` | +| `GET` | `/api/v1/channels/webhook/dingtalk/register/status` | `查询钉钉扫码注册状态` | +| `POST` | `/api/v1/channels/webhook/discord` | `Discord 消息回调(已废弃:Discord 已切换为 Gateway WebSocket 模式)` | +| `POST` | `/api/v1/channels/webhook/feishu` | `飞书消息回调` | +| `POST` | `/api/v1/channels/webhook/feishu/register/begin` | `启动飞书扫码注册应用流程` | +| `GET` | `/api/v1/channels/webhook/feishu/register/status` | `查询飞书扫码注册状态` | +| `POST` | `/api/v1/channels/webhook/slack` | `Slack Events API 回调` | +| `GET` | `/api/v1/channels/webhook/status` | `获取渠道运行状态` | +| `POST` | `/api/v1/channels/webhook/telegram` | `Telegram 消息回调` | +| `POST` | `/api/v1/channels/webhook/wecom` | `企业微信消息回调(智能机器人模式不使用,保留兼容)` | +| `GET` | `/api/v1/channels/webhook/weixin/qrcode` | `获取微信登录二维码` | +| `GET` | `/api/v1/channels/webhook/weixin/qrcode/status` | `查询微信二维码扫码状态` | +| `DELETE` | `/api/v1/channels/{id}` | `删除渠道` | +| `GET` | `/api/v1/channels/{id}` | `获取渠道详情` | +| `PUT` | `/api/v1/channels/{id}` | `更新渠道` | +| `GET` | `/api/v1/channels/{id}/health` | `获取指定渠道的实时健康状态(真连接状态,前端绿点应该绑这个)` | +| `PUT` | `/api/v1/channels/{id}/toggle` | `启用/禁用渠道` | + +### 数据源 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/datasources` | `获取数据源列表` | +| `POST` | `/api/v1/datasources` | `创建数据源` | +| `DELETE` | `/api/v1/datasources/{id}` | `删除数据源` | +| `GET` | `/api/v1/datasources/{id}` | `获取数据源详情` | +| `PUT` | `/api/v1/datasources/{id}` | `更新数据源` | +| `POST` | `/api/v1/datasources/{id}/test` | `测试数据源连接` | +| `PUT` | `/api/v1/datasources/{id}/toggle` | `启用/禁用数据源` | + +### 语音转文本 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `POST` | `/api/v1/stt/transcribe` | `Transcribe` | + +### 文本转语音 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `POST` | `/api/v1/tts/synthesize` | `Synthesize` | +| `GET` | `/api/v1/tts/voices` | `List Voices` | + +### 生成文件 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/files/generated/{id}` | `Download a tool-generated file by its one-time id` | + +### 计划 + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/plans` | `获取 Agent 的计划列表` | +| `GET` | `/api/v1/plans/{id}` | `获取计划详情(含步骤)` | + +### Feature Flags + +| 方法 | 路径 | 用途 / handler | +|---|---|---| +| `GET` | `/api/v1/feature-flags` | `List` | +| `PUT` | `/api/v1/feature-flags/{flagKey}` | `Update` | diff --git a/mateclaw-server/src/main/resources/docs/zh/architecture.md b/mateclaw-server/src/main/resources/docs/zh/architecture.md index 8a7b0a9c..977f4fd8 100644 --- a/mateclaw-server/src/main/resources/docs/zh/architecture.md +++ b/mateclaw-server/src/main/resources/docs/zh/architecture.md @@ -151,7 +151,7 @@ mateclaw/ ### 目标评估节点(1.4.0+) -图(ReAct 和 Plan-Execute 都有)现在在 `FinalAnswerNode` 把最终答案流式输出之后再跑一个 `GoalEvaluationNode`:它给目标完成度打分,并可选地注入一条自动跟进消息,把没达成的目标继续推进。 +图(ReAct 和 Plan-Execute 都有)现在在 `FinalAnswerNode` 把最终答案流式输出之后再跑一个 `GoalEvaluationNode`:1.5.0 起它逐条裁决目标的 checklist 准则(bootstrap / verdict 两模式),**全部准则通过才算完成**,并可选地注入一条针对剩余准则的自动跟进消息,把没达成的目标继续推进。 ### 其他 1.4.0 运行时变化 diff --git a/mateclaw-server/src/main/resources/docs/zh/channels.md b/mateclaw-server/src/main/resources/docs/zh/channels.md index 1d8d8779..b3e3ab08 100644 --- a/mateclaw-server/src/main/resources/docs/zh/channels.md +++ b/mateclaw-server/src/main/resources/docs/zh/channels.md @@ -35,6 +35,11 @@ v1.4.0 把飞书做成了"一等公民"渠道——交互卡片、流式卡片 飞书的细节全部在下面[飞书](#飞书)一节里展开。 ::: +::: tip 1.5.0 渠道改进 +- **统一的入站媒体管线**:目前**微信和企业微信**已接入这层共用的入站媒体下载器 + 魔数(magic-byte)类型识别 + 指数退避重试(其它 IM 渠道后续接入)。文件类型从内容字节判定(不再硬编码 `image/*`),HEIC / WEBP / DOCX / XLSX 等都能正确识别,下载失败自动重试。 +- **飞书:跟进文本自动带上最近的文件(#201)**:飞书群里先发一个文件(哪怕没 @ 员工),再发一句文字,缓存的文件会自动作为内容片段塞给员工——每群 5 个文件、60 分钟 TTL。 +::: + --- ## 九个渠道 diff --git a/mateclaw-server/src/main/resources/docs/zh/chat.md b/mateclaw-server/src/main/resources/docs/zh/chat.md index b6a0d26d..f5ec2a05 100644 --- a/mateclaw-server/src/main/resources/docs/zh/chat.md +++ b/mateclaw-server/src/main/resources/docs/zh/chat.md @@ -50,6 +50,8 @@ MateClaw 的聊天 UI 在试着回答一个问题:**AI 刚刚告诉你的事 信任是靠"把过程摊开"挣来的。MateClaw 把过程摊开。 +**执行计划 & 工具调用详情查看器(1.5.0)。** 计划面板每个步骤、每个工具调用行,右侧多了一个"查看详情"图标。点开是一个带毛玻璃背景的弹窗,显示**完整的请求参数和响应输出**——内联预览会截断的部分这里都在,带请求/响应各自的复制按钮,状态徽标标"进行中 / 已完成 / 失败 / 等待中"。这些数据存在消息元数据里,所以刷新页面后计划步骤和工具调用照样可读。 + --- ## 多渠道实时同步 @@ -86,6 +88,10 @@ ChatConsole 不只是你自己聊天的地方。它是一个**运营控制台** 图片递交给支持视觉的模型做视觉理解。PDF 和 DOCX 走文本抽取(扫描件自动降级到 OCR)。Agent 在本回合读到的所有内容,都会进它的上下文。 +::: tip 工具生成的文件:下载链接扛得住重启(1.5.0,#243) +员工调工具生成的文件(文档 / 图片 / 音频…)现在**落盘**到 `data/generated-files/`,带 7 天保留窗口 + 6 小时定时清理,内存里再放一层 LRU——下载链接重启后依然有效,不再受原来 10 分钟内存窗口限制。前端用一个全局点击代理拦截 `/api/v1/files/generated/{id}` 下载:成功走鉴权 fetch → blob 下载,失败(404/410/过期)只弹一个 toast,**不再因为一个失效链接把整个页面卡死**。 +::: + ### 主模型不支持图片?走"多模态旁路" ::: tip 1.3.0 新增 diff --git a/mateclaw-server/src/main/resources/docs/zh/console.md b/mateclaw-server/src/main/resources/docs/zh/console.md index 8a68eb69..0e0261f9 100644 --- a/mateclaw-server/src/main/resources/docs/zh/console.md +++ b/mateclaw-server/src/main/resources/docs/zh/console.md @@ -111,7 +111,7 @@ - `POST /api/v1/chat/stream`——SSE 流式(原生 fetch) - `POST /api/v1/chat/upload` - `POST /api/v1/chat/{conversationId}/stop` -- `POST /api/v1/approvals/{id}/resolve` +- 审批结果通过 `POST /api/v1/chat/stream` 发送 `/approve` 或 `/deny` - `GET /api/v1/chat/{conversationId}/pending-approvals` - `GET /api/v1/conversations`——列表 - `GET /api/v1/conversations/{id}/messages` diff --git a/mateclaw-server/src/main/resources/docs/zh/desktop-ui-hot-update.md b/mateclaw-server/src/main/resources/docs/zh/desktop-ui-hot-update.md index 3d13d1e8..f7d2d7a4 100644 --- a/mateclaw-server/src/main/resources/docs/zh/desktop-ui-hot-update.md +++ b/mateclaw-server/src/main/resources/docs/zh/desktop-ui-hot-update.md @@ -262,7 +262,7 @@ Manifest 最好放在稳定的静态地址,不要依赖 GitHub API 动态查 建议机制: - `current.json` 记录当前版本、上一版本、状态 -- UI 启动成功后,前端调用 `/api/v1/system/ui/boot-ok` 或通过 preload IPC 上报“本次版本已健康启动” +- UI 启动成功后,通过 preload IPC 上报“本次版本已健康启动”;如果后续选择 REST 方案,可新增 `/api/v1/system/ui/boot-ok`(当前源码尚未提供该端点) - 若启动后短时间内崩溃或白屏,下次启动自动回滚上一版本 ### 7. 安全要求 @@ -437,9 +437,9 @@ UI 热更新本质上是在本地执行新的前端资源,必须做完整校 - `uiUpdater.download()` - `uiUpdater.applyOnRestart()` -后端接口建议新增: +后端接口建议新增(当前源码尚未提供): -- `GET /api/v1/runtime/version` +- 建议新增:`GET /api/v1/runtime/version`(当前源码尚未提供) 返回示例: diff --git a/mateclaw-server/src/main/resources/docs/zh/desktop.md b/mateclaw-server/src/main/resources/docs/zh/desktop.md index 93dc4a31..8ba6b142 100644 --- a/mateclaw-server/src/main/resources/docs/zh/desktop.md +++ b/mateclaw-server/src/main/resources/docs/zh/desktop.md @@ -147,7 +147,8 @@ cd ../mateclaw-server mvn clean package -DskipTests # 3. 把 JAR 拷到桌面项目 -cp target/mateclaw-server.jar ../mateclaw-desktop/resources/app.jar +JAR_FILE=$(ls -1 target/mateclaw-server-*.jar | grep -v sources | head -n 1) +cp "$JAR_FILE" ../mateclaw-desktop/resources/app.jar # 4. 下载平台特定的 JRE cd ../mateclaw-desktop diff --git a/mateclaw-server/src/main/resources/docs/zh/doctor.md b/mateclaw-server/src/main/resources/docs/zh/doctor.md index 96ad4abd..100d0c03 100644 --- a/mateclaw-server/src/main/resources/docs/zh/doctor.md +++ b/mateclaw-server/src/main/resources/docs/zh/doctor.md @@ -1,233 +1,71 @@ # Doctor -**Doctor 页面回答一个问题:这个东西现在是不是真的在正常工作?** +Doctor 是应用内的健康抽屉。它从后端健康服务读取当前本机实例状态;当前实现不是一个独立的定时诊断系统。 -MateClaw 有很多活动部件——后端、数据库、模型供应商、MCP 服务、IM 渠道、cron 任务、记忆整合、wiki 消化。出问题时,**症状**("我的 Agent 不响应")通常有一个**具体的原因**("DashScope API Key 昨天过期了")埋在离你能看到的地方好几层远的地方。Doctor 是一个单页,**一次性跑所有检查**,告诉你哪些是绿的、哪些是黄的、哪些是红的。 +可以从布局里的状态按钮 / 设置区域打开。抽屉每次打开或点击刷新时都会请求后端。 -通过 `设置 → Doctor` 打开,或者直接跳 `/doctor`。 +## 当前后端 API ---- - -## 它检查什么 - -每一项检查独立运行,报告三种状态之一: - -- **✅ OK**——一切按预期工作 -- **⚠️ 警告**——在工作但降级了(例如在用 fallback provider、接近配额、一个非关键的 cron 任务暂停了) -- **❌ 错误**——以一种你需要修的方式坏了 - -### 核心基础设施 - -| 检查 | 验证什么 | -|------|----------| -| **后端版本** | MateClaw 在跑,报告它的版本 | -| **数据库连接** | 配置的数据源可达,查询成功 | -| **数据库 schema** | 所有预期的 `mate_*` 表存在;迁移状态干净 | -| **磁盘使用** | 数据目录有足够空闲空间(低于 20% 警告,低于 5% 错误) | -| **H2 console 暴露** | 生产 profile 里启用了 H2 console 会警告 | -| **JWT secret 强度** | 还在用默认 JWT secret 会警告 | - -### 模型 - -| 检查 | 验证什么 | -|------|----------| -| **活跃模型** | 默认模型配置存在且启用 | -| **供应商连通性** | 每个启用的供应商最近通过了连接测试 | -| **API Key 存在** | 每个标记为启用的云供应商都配了 key | -| **Ollama 可达** | 如果配了 Ollama,本地实例可达 | - -### Agent 和工具 - -| 检查 | 验证什么 | -|------|----------| -| **工具注册表** | 内置工具和 MCP 工具加载无错 | -| **Tool Guard 配置** | 至少存在一条 Tool Guard 规则(用 `default-policy: allow` 会警告) | -| **默认 Agent** | 默认 Agent 存在且启用 | -| **Agent 模板** | 内置模板存在且可加载 | - -### 记忆和 Wiki - -| 检查 | 验证什么 | -|------|----------| -| **记忆整合 cron** | 每个 Agent 的整合 cron 任务存在且启用 | -| **上次整合运行** | 过去 7 天没有跑过整合会警告 | -| **Wiki 消化队列** | 没有卡住的 `pending` 或 `processing` 原始材料 | -| **Wiki schema** | `mate_wiki_*` 表存在且可查询 | - -### 渠道 - -| 检查 | 验证什么 | -|------|----------| -| **渠道健康监控** | 每个启用的渠道报告 `connected` 或正在主动重连 | -| **每渠道状态** | 每个 IM 渠道的连接状态和上次错误 | -| **Webhook URL 可达** | 生产环境下 webhook 模式的渠道没配公网 URL 会警告 | - -### MCP - -| 检查 | 验证什么 | -|------|----------| -| **启用的 MCP 服务** | 每个启用的 MCP 服务是 `connected` | -| **工具数** | 每个连接成功的服务报告至少一个工具 | -| **孤儿子进程** | 没有超过它父 client 存活的 stdio 子进程 | - -### Cron 和异步 - -| 检查 | 验证什么 | -|------|----------| -| **Cron 引擎** | 计划任务执行器在运行 | -| **过期任务** | 任何任务超时超过 24 小时会警告 | -| **异步任务队列** | `mate_async_task` 队列长度在正常范围 | - ---- - -## 检查怎么跑 - -Doctor 两种方式跑: - -### 按需 - -点 Doctor 页面上的**运行所有检查**。按钮并行触发所有检查;UI 在每项检查完成时流式返回结果。大多数检查在一秒内完成;最慢的(MCP 服务连接测试)可能要 10–30 秒。 - -### 按计划 - -Doctor 也在后台**每 15 分钟自动跑一次**。结果缓存在内存里并持久化到 `mate_doctor_check`,这样打开页面时它**立刻加载**——你看到的是上次缓存的状态,直到你点**运行所有检查**。 - -在 `application.yml` 里调整计划: - -```yaml -mateclaw: - doctor: - enabled: true - schedule-minutes: 15 - cache-ttl-minutes: 10 +```bash +curl http://localhost:18088/api/v1/system/health \ + -H "Authorization: Bearer " ``` ---- - -## 读结果 - -每个检查返回: +响应结构: ```json { - "name": "DashScope 供应商连通性", - "category": "Models", - "status": "ok", - "message": "连接测试成功(延迟:240ms)", - "lastChecked": "2026-04-11T14:30:22", - "details": { - "provider": "dashscope", - "baseUrl": "https://dashscope.aliyuncs.com", - "latencyMs": 240 - }, - "fixUrl": "/settings/models" + "code": 200, + "msg": "success", + "data": { + "overall": "healthy", + "checks": [ + { + "name": "default-model", + "status": "healthy", + "message": "Default model: qwen-plus", + "action": null + } + ] + } } ``` -UI 渲染: +`overall` 取值为 `healthy`、`warning`、`error`。每个检查项包含: -- 顶部的**分类 tab**——基础设施、模型、Agent、记忆、Wiki、渠道、MCP、Cron -- **状态计数器**——绿 / 黄 / 红 -- **检查列表**——名字、状态、消息、距上次检查的时间、"查看详情"展开、可选的"修复"按钮跳到相关设置页 -- **历史图**——(每个检查)最近 50 次运行的 sparkline,一眼看出抖动的检查 +| 字段 | 含义 | +|---|---| +| `name` | 稳定检查 key,例如 `default-model`、`database`、`browser`、`provider:`、`mcp:` | +| `status` | `healthy`、`warning` 或 `error` | +| `message` | 抽屉里展示的简短诊断信息 | +| `action` | 可选 `{ label, route }`,提示去哪里修 | ---- +## 当前检查项 -## 修复按钮 +当前 `SystemHealthService` 检查: -对可操作的检查,Doctor 行包含一个**修复**按钮,直接跳到相关的设置页面: +| 检查 | 验证什么 | 常见修复入口 | +|---|---|---| +| 默认模型 | 是否配置并能加载默认模型 | `/settings/models` | +| Provider 配置 | 需要 API key 的 provider 是否已配置 | `/settings/models` | +| 已启用 MCP 服务 | 已启用 MCP 服务是否有成功连接结果 | `/settings/mcp-servers` | +| 数据库初始化 | 首次启动 bootstrap 是否完成 | `/setup` | +| 浏览器诊断 | 浏览器工具启动前置条件 | `/api/v1/system/browser-health` | -- 模型供应商失败 → `设置 → 模型` -- Tool Guard `default-policy: allow` → `设置 → 安全与审批` -- 生产环境的 H2 console → `设置 → 系统`(或显示一个可复制的配置片段) -- JWT 默认 secret → `设置 → 系统`(或显示一个配置片段) -- MCP 服务断开 → `工具 → MCP 服务` -- 卡住的 wiki 消化 → `Wiki → [KB] → 原始材料` - -点修复带你到**你能解决问题的那个具体页面**。可能的话,目标页面会预过滤高亮失败的条目。 - ---- - -## Doctor API +浏览器诊断也有独立接口: ```bash -# 跑所有检查(同步) -curl http://localhost:18088/api/v1/doctor/run \ - -H "Authorization: Bearer " - -# 获取缓存的检查结果 -curl http://localhost:18088/api/v1/doctor/checks \ - -H "Authorization: Bearer " - -# 只跑特定分类 -curl http://localhost:18088/api/v1/doctor/run?category=models \ - -H "Authorization: Bearer " - -# 历史结果 -curl "http://localhost:18088/api/v1/doctor/history?check=dashscope-connectivity&limit=50" \ +curl http://localhost:18088/api/v1/system/browser-health \ -H "Authorization: Bearer " ``` ---- +## 当前源码没有实现的旧内容 -## 在运维中使用 Doctor +旧文档曾提到 `/api/v1/doctor/run`、`/api/v1/doctor/checks`、`/api/v1/doctor/history`、Doctor 定时后台运行、`mate_doctor_check`、`mate_doctor_check_history`。这些端点和表在当前后端源码中不存在。当前健康检查请使用 `/api/v1/system/health`。 -### 作为 uptime 监控的健康端点 +## 相关页面 -把你的外部 uptime 监控(UptimeRobot、Pingdom、内部 Prometheus)指向: - -``` -GET /api/v1/doctor/checks -``` - -端点返回 HTTP 200 带 JSON 汇总——聚合的通过/失败计数和按分类细分。你的监控应该在 `errorCount > 0` 时报警。 - -要更简单的健康检查,用: - -``` -GET /actuator/health -``` - -这遵循 Spring Boot 的标准格式。 - -### 升级时 - -部署新 MateClaw 版本之后跑 Doctor 验证没有回归: - -1. 打开 `/doctor` -2. 点**运行所有检查** -3. 看有没有之前没有的黄或红 -4. **特别注意数据库 schema**——升级后 schema 不匹配通常意味着某个迁移没跑 - -### 出问题时 - -用户报告"它不工作"时 Doctor 是第一个去看的地方。打开页面,看哪个检查是红的,点**修复**,解决问题。**如果没有检查是红的但用户仍然有问题**,大概率是 Doctor 还没覆盖的东西——开一个 [GitHub issue](https://github.com/matevip/mateclaw/issues) 让我们加一个检查。 - ---- - -## 数据模型 - -**`mate_doctor_check`** - -| 列 | 用途 | -|----|------| -| `id` | 主键 | -| `name` | 检查名字 | -| `category` | 检查分类 | -| `status` | `ok` / `warning` / `error` | -| `message` | 人类可读的消息 | -| `details` | 额外细节的 JSON | -| `last_checked` | 上次运行时间 | -| `run_duration_ms` | 检查耗时 | -| `workspace_id` | 范围(全局检查为 null) | - -历史结果进 `mate_doctor_check_history`,同样的列加上一个保留期清理任务。 - ---- - -## 下一步 - -- [控制台](./console)——Doctor 所在的 UI -- [配置说明](./config)——你可能基于 Doctor 警告配置的东西 -- [安全与审批](./security)——Doctor 在 Tool Guard 里检查什么 -- [贡献指南](./contributing)——缺了什么就加一个 Doctor 检查 +- [API 参考](./api) —— 源码对齐的路由索引 +- [模型配置](./models) —— 模型 / provider 设置 +- [MCP 协议](./mcp) —— MCP 服务配置 +- [安全与审批](./security) —— Tool Guard 和审批行为 diff --git a/mateclaw-server/src/main/resources/docs/zh/faq.md b/mateclaw-server/src/main/resources/docs/zh/faq.md index 68091fa1..d2edbce3 100644 --- a/mateclaw-server/src/main/resources/docs/zh/faq.md +++ b/mateclaw-server/src/main/resources/docs/zh/faq.md @@ -215,9 +215,10 @@ UI 里用 `工具 → MCP 服务`。三种传输模式:stdio、streamable_http ### 我批准了一个工具调用但 Agent 没恢复 1. `AWAITING_APPROVAL` 还是 true 吗?(`GET /api/v1/agents/{id}`) -2. 审批真的持久化了吗?(`GET /api/v1/approvals/{id}`) +2. 等待中的会话还有 pending 审批吗?(`GET /api/v1/chat/{conversationId}/pending-approvals`) 3. Agent 日志里 replay 尝试附近有错误吗? -4. Replay 失败的话,Agent 应该在聊天里暴露一个错误 +4. 批准/拒绝消息是否通过同一个会话的 `POST /api/v1/chat/stream` 发送? +5. Replay 失败的话,Agent 应该在聊天里暴露一个错误 ### 我想批量批准这个 Agent 未来的工具调用 diff --git a/mateclaw-server/src/main/resources/docs/zh/goals.md b/mateclaw-server/src/main/resources/docs/zh/goals.md index 553672f5..52de7e2a 100644 --- a/mateclaw-server/src/main/resources/docs/zh/goals.md +++ b/mateclaw-server/src/main/resources/docs/zh/goals.md @@ -65,8 +65,6 @@ Goal 把这件事翻过来。**你说一次,员工锁住目标,自己每轮 POST /api/v1/goals { "conversationId": "conv-xxx", - "agentId": "1000000001", - "workspaceId": 1, "title": "部署博客到 fly.io", "description": "...", "exitCriteria": "DNS+SSL+健康检查+测试通过", @@ -76,7 +74,7 @@ POST /api/v1/goals } ``` -完整接口列表见 [API 参考](./api)。 +> `agentId` / `workspaceId` 由 `conversationId` 在服务端派生,**请求体里不用传**(传了也会被忽略)。完整接口列表见 [API 参考](./api)。 --- @@ -114,13 +112,59 @@ POST /api/v1/goals 如果 `autoFollowupEnabled=true` 且这一轮 evaluator 判 "continue",后台会: 1. 写一条 `followup_injected` 事件到时间线 -2. 给对话末尾 APPEND 一条用户消息:"Continue working on the goal. Still missing: {gap}. Take the next concrete step." +2. 给对话末尾 APPEND 一条用户消息。**1.5.0 起,如果目标有清单,这条消息会明确列出还没通过的那几条准则**——"5/8 已完成,剩余:① …… ② ……,去做剩下的";没有清单时回退到笼统的 "Continue working on the goal. Still missing: {gap}." 3. 让员工再跑一轮 reasoning,**这一轮的回答就直接接在第一轮后面** 你的体感是:员工答完一段 → 停半拍 → **继续往下做** — 就像一个人做完一步停了一下想了想然后继续。 --- +## 目标是一份清单(checklist,1.5.0+) + +1.4.0 里 evaluator 每轮给一个完成度分数(0~1)和一句"还差什么"。问题是 **0.8 到底是什么意思**——哪几条做完了、哪几条没做,你看不清。 + +1.5.0 把它换成**清单**:目标 = 一组**可以逐条独立验证**的准则。 + +**evaluator 有两种模式:** + +| 模式 | 什么时候跑 | 干什么 | +|---|---|---| +| **bootstrap(拆解)** | 还没有准则时 | 把目标拆成清单,每条初始为"未通过" | +| **verdict(裁决)** | 已有准则时 | 逐条判:这条满足了吗?给出证据 | + +两种模式都用**结构化输出**——evaluator 必须返回带类型的对象(准则 `id` + `passed` + `evidence`),而不是一段自由文本让我们去猜。 + +**完成判定是确定性的。** 只有当**每一条准则都通过**,才判完成。20 条里过了 19 条(0.95 分)依然是"继续"——差一条就还差一条,没有模糊阈值。 + +**怎么给目标加清单——三种途径:** + +- **创建时直接带**——`setGoal` 工具传 `criteria: ["DNS 解析正确", "SSL 有效", "测试全绿"]`,或 `POST /api/v1/goals` 传 `criteria`。省去 bootstrap 那一轮。 +- **让 evaluator 自己拆**——不传 criteria,第一轮评估时 bootstrap 模式自动拆解。 +- **运行中追加**——`addGoalCriterion` 工具或 `POST /api/v1/goals/{id}/criteria`,往进行中的目标补一条,不用重开。 + +**一条准则长什么样:** + +```json +{ "id": "C1", "text": "DNS 解析指向 fly.io", "passed": false, "evidence": "" } +``` + +`id` 由服务端分配(C1、C2…),`text` 是人能看懂、LLM 能判的一句话,`passed` 是 evaluator 的裁决,`evidence` 是它给的依据(输出片段、文件摘录等)。清单存在 `mate_agent_goal.criteria` 列(JSON),通过 `GoalResponse.criteria` 解析后下发,从不以裸 JSON 字符串暴露。 + +### 头像旁的光,hover 出来是一张清单卡 + +- **没有清单**时——一句话 tooltip:标题 + evaluator 写的 gap 文本。 +- **有清单**时——一张卡片:标题 + `X/Y` 进度,下面每条准则前一个 `○`(未完成)或 `✓`(绿色已完成,文字带删除线)。 + +评估中头像周围是沙金色呼吸光晕;完成短暂显示绿色环后消失;预算耗尽变红橙色环。 + +### Evaluator SPI + +评估逻辑实现了 Spring AI 的 `Evaluator` 接口:既能做目标专用的 checklist 裁决(bootstrap / verdict),也能被当成通用评估器复用(把单个目标包成一条准则跑 verdict)。失败的 evaluator 调用**照样计入 LLM 预算**,所以预算账目是准的。 + +> 1.4.0 的目标是"员工记住它在干什么"。1.5.0 的目标是"员工知道**具体还差哪几条**"。从一个分数,到一份能逐条勾的清单。 + +--- + ## 4 个内置工具(员工可用) 员工的工具集里默认包含这 4 个(无需手动绑定,是 agent-wide 系统级工具): @@ -132,7 +176,7 @@ POST /api/v1/goals | **completeGoal** | 显式标记完成 | "所有事项已做完,请 completeGoal" | | **getGoalStatus** | 查询当前 goal 状态 | "我们现在进展到哪了?" | -完成时 (`completeGoal` 或 evaluator 判 score≥0.95),员工会把这个目标的总结同步到[长期记忆](./memory),后续对话能查得回来。 +完成时(`completeGoal`,或 evaluator 判定**每一条准则都通过**),员工会把这个目标的总结同步到[长期记忆](./memory),后续对话能查得回来。 --- @@ -168,7 +212,7 @@ turnsUsed >= turnBudget 或 (agentLlmCallsUsed + evalLlmCallsUsed) >= llmCallB ↓ ↑ paused - active ──evaluator score≥0.95 / completeGoal──→ completed (终态) + active ──evaluator 全部准则通过 / completeGoal──→ completed (终态) ↓ active ──turns_used/llm_calls 用完 ─────────→ exhausted (终态) ↓ @@ -188,7 +232,7 @@ turnsUsed >= turnBudget 或 (agentLlmCallsUsed + evalLlmCallsUsed) >= llmCallB - **不做嵌套目标 / 目标树** — 一个 conversation 一个目标,不堆 OKR - **不做"目标模板"** — 每个目标是手写的,不是从库里挑的 - **不做跨 conversation 迁移目标** — 想要那效果,请用[工作流](./workflow) -- **不暴露评估分数给用户** — 那个 `completionScore` 是工程内部协议,不是用户语言。UI 用一圈光说话,hover 显示 evaluator 写的 gap 文本(自然语言)。后端日志和 API 里仍可见数值,方便调试 +- **不暴露评估分数给用户** — 那个 `completionScore` 是工程内部协议,不是用户语言。UI 用一圈光说话,hover 出来:有清单时是逐条勾的清单卡,没清单时是 evaluator 写的 gap 文本(自然语言)。后端日志和 API 里仍可见数值,方便调试 --- @@ -219,12 +263,18 @@ mateclaw: goal: # 主开关;关闭后图节点对所有调用 pass-through enabled: true + # 创建目标时 autoFollowupEnabled 的默认值(调用方未指定时) + default-auto-followup: true + # 运行期总开关;关掉则无论 per-goal 标志如何,都不注入自动延续 + allow-auto-followup: true # 默认 turn 预算 default-turn-budget: 20 # 默认 LLM 调用预算(agent + evaluator 之和) default-llm-call-budget: 200 # 自动延续之间至少隔多久(秒) auto-followup-cooldown-seconds: 0 + # 单次 graph 运行内自动延续的硬上限(每条消息的安全网;总预算仍由 turnBudget 管) + max-followups-per-run: 8 # 评估器使用的模型;空字符串 = 沿用对话当前模型(便宜的小模型推荐:qwen-turbo / glm-4-flash) evaluator-model: "" # 评估 prompt 携带的历史消息条数上限 diff --git a/mateclaw-server/src/main/resources/docs/zh/mcp.md b/mateclaw-server/src/main/resources/docs/zh/mcp.md index 9274cb24..72a38b02 100644 --- a/mateclaw-server/src/main/resources/docs/zh/mcp.md +++ b/mateclaw-server/src/main/resources/docs/zh/mcp.md @@ -99,7 +99,7 @@ MateClaw ── HTTP POST ──► 远程 MCP 服务 - **URL**(streamable_http / sse)——服务端点 - **HTTP Headers**(streamable_http / sse)——JSON 对象 - **连接超时**——默认 30 秒 -- **读取超时**——默认 30 秒 +- **读取超时**——默认 **60 秒**(1.5.0 起从 30s 提到 60s,#247;单次 callTool 往返合法地跑久一点的工具不再被掐断。每台服务可单独调 5–300s) 保存。启用状态时 MateClaw 自动尝试连接并发现工具。 @@ -357,7 +357,7 @@ stdio 服务:禁用/删除、配置替换、应用关闭(`@PreDestroy`)、 | `cwd` | VARCHAR(512) | NULL | 工作目录 | | `enabled` | BOOLEAN | TRUE | 开关 | | `connect_timeout_seconds` | INT | 30 | HTTP 连接超时 | -| `read_timeout_seconds` | INT | 30 | 请求响应超时 | +| `read_timeout_seconds` | INT | 60 | 请求响应超时(1.5.0 起默认 60,旧为 30) | | `last_status` | VARCHAR(32) | `disconnected` | 上次连接状态 | | `last_error` | TEXT | NULL | 上次错误消息 | | `last_connected_time` | DATETIME | NULL | 上次成功连接时间 | diff --git a/mateclaw-server/src/main/resources/docs/zh/memory.md b/mateclaw-server/src/main/resources/docs/zh/memory.md index d0a10750..77ca4000 100644 --- a/mateclaw-server/src/main/resources/docs/zh/memory.md +++ b/mateclaw-server/src/main/resources/docs/zh/memory.md @@ -64,6 +64,55 @@ MateClaw 里其他所有东西,在你配置完之后就静止了。Agent、工 --- +## 记忆认人:per-owner 隔离(1.5.0) + +以前一个员工的记忆是**共享**的:不管是网页登录的你、还是飞书群里的同事、还是第三方 API 接进来的终端用户,聊出来的记忆都堆进同一个 `MEMORY.md`。一个员工服务多个人时,记忆会串台。 + +1.5.0 给每条记忆加了**主人(owner)**和**可见范围(scope)**。 + +### 统一的 owner_key + +不管身份从哪来,都归一成一个带前缀的字符串: + +| 来源 | owner_key | +|---|---| +| 网页控制台 | `user:<用户id>` | +| IM 渠道(飞书 / 钉钉 / 企微…) | `<渠道>:<发送者id>` | +| 第三方 API(带 endUserId) | `api:` | +| 系统 / cron | `system` | + +### 三档可见性 + +| scope | 谁能读 | 典型内容 | +|---|---|---| +| **PERSONAL(个人)** | 只有匹配的 owner | 对话里抽取出来的记忆默认进这档 | +| **TEAM(团队)** | 用这个员工的人都能读 | 员工配置文件(AGENTS.md / SOUL.md / PROFILE.md)、历史回填的数据 | +| **GLOBAL(全局)** | 跨员工 / 工作空间始终可见 | 预置事实、系统参考资料 | + +### 召回偏好个人记忆 + +system prompt 里只烤进 TEAM/GLOBAL 的共享记忆(可缓存);每轮再按当前 owner_key **预取**他个人的记忆注入。所以问"我的项目用什么栈"时,员工优先回忆**这个人**的私人记忆文件,而不是知识库里的泛泛资料。 + +> 关于结构化"事实"层:**事实召回查询本身支持 owner 可见性过滤**(PERSONAL 仅 owner 可见,TEAM/GLOBAL 共享)。但当前的**自动事实投影**主要从共享记忆文件构建、插入时不写 `ownerKey/scope`——也就是说个人化更多体现在个人记忆文件的预取上,事实层的 per-owner 化还在补齐中。 + +### 第三方 API 透传终端用户身份 + +`/api/v1/chat` 和 `/api/v1/chat/stream` 的请求体新增可选字段 **`endUserId`**(字符串,保大整数精度)。一个 PAT 认证的接入方代表一个 MateClaw 用户,但可以为每个终端用户传不同的 `endUserId`,记忆按终端用户自动隔离。 + +### 这是一个可开关的特性 + +总开关是 `mate.memory.lifecycle-mediator-enabled`。 + +::: warning 默认值要看清楚 +Java 属性的裸默认值是 `false`,但**随发行版打包的 `application.yml` 把它设成了 `true`**——也就是说**默认安装下 per-owner 隔离是开着的**。要回到旧的共享行为(所有写入走 TEAM),在你的配置里显式设为 `false`。 +::: + +打开后:对话抽取写入 owner 的 PERSONAL 记忆,召回按 owner_key 过滤;关闭后所有写入回退到共享 TEAM。多租户实例保持开启,单人部署可以关掉。 + +底层:迁移 `V137` 给 `mate_workspace_file` / `mate_memory_recall` / `mate_fact` 三张表加了 `owner_key` + `scope` 列,历史行回填为 `TEAM`(保证升级后没有记忆被藏起来)。`remember` 等记忆工具会按当前请求上下文解析 owner_key,开关打开时写进该 owner 的 PERSONAL 记忆,关闭时回退共享写入。 + +--- + ## 多层记忆 + 可插拔 Provider 记忆这一层不是一个硬编码的实现。它是一个**接口**——多层架构允许你**堆叠 provider**: @@ -414,6 +463,11 @@ mate: # --- 整合 / dreaming --- emergence-enabled: true emergence-day-range: 7 + + # --- per-owner 记忆隔离(1.5.0)--- + # 随发行版打包的默认值是 true(开):对话抽取写入 owner 的 PERSONAL 记忆,召回按 owner_key 过滤。 + # 设为 false 回到旧的共享行为(所有写入走 TEAM)。Java 属性裸默认值为 false。 + lifecycle-mediator-enabled: true ``` 配置前缀:`mate.memory`。 diff --git a/mateclaw-server/src/main/resources/docs/zh/models.md b/mateclaw-server/src/main/resources/docs/zh/models.md index db649f09..2202c769 100644 --- a/mateclaw-server/src/main/resources/docs/zh/models.md +++ b/mateclaw-server/src/main/resources/docs/zh/models.md @@ -17,8 +17,8 @@ MateClaw 不关心你用哪个 LLM。它通过五个协议适配器跟所有主 | **百炼 Token Plan** | 阿里百炼 token 包月套餐 | dashscope | 7 个种子模型;支持长 token | | **OpenAI** | GPT-4o、GPT-4o-mini、GPT-5.5、o1、o3、o4-mini | openai | 标准 OpenAI API | | **OpenAI OAuth(ChatGPT Plus/Pro)** | 通过订阅用 GPT-4o、o3、o4-mini | openai | 浏览器 OAuth,**不需要 API Key** | -| **Anthropic** | Claude 4.7、Claude 4.6 Sonnet、Claude 4.5 Haiku | anthropic | 原生 Messages API | -| **Anthropic Claude Code OAuth** | 通过 Claude Pro/Max/Team 订阅用 Claude 4.7 / 4.6 | anthropic | 浏览器 OAuth + 手动粘贴流,**不需要 API Key** | +| **Anthropic** | **Claude Opus 4.8 / 4.8 Fast**(1.5.0+)、Claude 4.7、Claude 4.6 Sonnet、Claude 4.5 Haiku | anthropic | 原生 Messages API;4.8 两个变体都支持 `xhigh` 思考档 | +| **Anthropic Claude Code OAuth** | 通过 Claude Pro/Max/Team 订阅用 Claude Opus 4.8 / 4.7 / 4.6 | anthropic | 浏览器 OAuth + 手动粘贴流,**不需要 API Key** | | **Google Gemini** _(原生)_ | gemini-2.5-flash、gemini-3-pro-image-preview、gemini-2.5-flash-image | gemini | 原生 `generateContent` API(非 OpenAI 兼容)——见下方"原生 Gemini" | | **xAI / Grok** | Grok 3、Grok 4 | openai | OpenAI 兼容(base URL + API Key);UI 带 xAI 品牌图标 | | **DeepSeek** | deepseek-chat、deepseek-coder、**DeepSeek V4 flash + pro**(支持思考模式) | openai | OpenAI 兼容 | @@ -162,13 +162,13 @@ Gemini 不再走 OpenAI 兼容层——MateClaw 直接对接 Google 的**原生 如果 `local` 模式起不来 loopback 端口(端口被占、沙箱拒绝),会自动降级到 `manual_paste`。 -**后端端点**(`/api/v1/oauth/openai/device`): +**后端端点:** | Method | Path | 用途 | |---|---|---| -| `POST` | `/start` | 开一个会话,返回 `deviceAuthId` / `userCode` / `verificationUrl` / `intervalSeconds` / `expiresInSeconds` | -| `POST` | `/poll` | 按 `deviceAuthId` 轮询,返回 `PENDING` / `COMPLETED` / `EXPIRED` | -| `POST` | `/cancel` | 丢弃会话(比如用户关了对话框) | +| `POST` | `/api/v1/oauth/openai/device/start` | 开一个会话,返回 `deviceAuthId` / `userCode` / `verificationUrl` / `intervalSeconds` / `expiresInSeconds` | +| `POST` | `/api/v1/oauth/openai/device/poll` | 按 `deviceAuthId` 轮询,返回 `PENDING` / `COMPLETED` / `EXPIRED` | +| `POST` | `/api/v1/oauth/openai/device/cancel` | 丢弃会话(比如用户关了对话框) | 前端按 OpenAI 返回的 `intervalSeconds`(一般 5 秒)轮询;服务端再设一个最小轮询间隔(默认 3 秒)兜底,避免被打。过期的会话每 5 分钟扫一次清掉。 @@ -393,6 +393,17 @@ MateClaw 用一个**活跃模型**作为全局默认。没有指定自己模型 - **出口 sanitizer** —— provider 专属选项(如 OpenAI 推理模型的 `reasoning_effort`)在 failover 到不支持的 provider 时被剥离,泄漏的选项不会让 fallback 报 400 - **UI 区分 401 与会话过期** —— provider 认证错误和用户会话过期现在显示不同消息、不同处置 +### 偏好提供商决定主模型(1.5.0) + +1.5.0 之前,"每个 agent 自定义优先级"只影响 **failover 顺序**——主模型仍是全局默认。1.5.0 让这个偏好**真的决定主模型选择**。完整优先级链是: + +1. **会话钉选模型最高优先**——聊天头部 ModelSelector 给这个会话单独绑了模型,就用它(见[按会话选模型](./chat#按会话选模型)) +2. **其次是 per-agent 的模型覆盖(`modelName`)**——员工自己钉死了某个模型 +3. **再次是全局默认模型** +4. **以上都没有时,才进入偏好提供商路由**——按偏好挑提供商的主模型 + +偏好提供商路由里有一道**能力门禁**:如果员工绑定的技能声明了 `requires-model: vision` 这类需求,路由会先挑能满足这些模态的提供商;满足不了再无约束回退。偏好存在 `mate_agent_provider_preference` 表(按 `sortOrder` 升序,越小优先级越高)。 + --- ## API 配置 diff --git a/mateclaw-server/src/main/resources/docs/zh/releases.md b/mateclaw-server/src/main/resources/docs/zh/releases.md index 0e63f1a7..7ceaf4dc 100644 --- a/mateclaw-server/src/main/resources/docs/zh/releases.md +++ b/mateclaw-server/src/main/resources/docs/zh/releases.md @@ -10,6 +10,7 @@ | 版本 | 日期 | 亮点 | |------|------|------| +| [v1.5.0](./releases/1.5.0) | 2026-06-04 | 目标长出清单——从"打个分"到"逐条勾"(checklist + Evaluator SPI + 确定性完成判定) · Wiki 学会自维护(`[[wikilink]]` 互联 + 改名/删页级联修链 + 坏链体检 · 事实/经验分层 + 失效传播 · pageType 档案与 per-agent 权限 · 处理流水线 · 本地目录知识源定时增量同步) · 记忆按主人隔离(owner_key + 个人/团队/全局可见性 + 第三方 endUserId 透传) · 每个员工绑主知识库 · 偏好提供商决定主模型 + Claude Opus 4.8 | | [v1.4.0](./releases/1.4.0) | 2026-05-23 | 持久化目标——员工锁住目标自己跟到完成 · 子员工委派变成一棵树(递归 3 层 + 异步 + 数字员工构建器) · 渐进式工具/技能披露(`enable_tool` + `load_skill`) · 工作空间 RBAC(四级角色 + 能力门禁) · 飞书做成一等公民(互动/审批/流式卡片 + 语音/文件音视频 + 渠道原生工具) | | [v1.3.0](./releases/1.3.0) | 2026-05-13 | 工作流元年——7 种 step mode 把员工组装成业务流程 · 触发器 6 种 pattern 让事件自动启动流程 · Wiki 从搜索索引升级为处理流水线(用户模板 + 跨材料聚合 + reverse-citation) · MCP per-agent 工具绑定 + 多模态旁路路由 · 4 个 JVM 原生文档生成工具 + 图像编辑 | | [v1.2.0](./releases/1.2.0) | 2026-05-05 | 智能体改名"数字员工"(角色 / 目标 / 背景故事 + 5 职业模板) · 技能成了骨架(manifest + 模板向导 + LESSONS 自我进化) · ACP 接入:Claude Code / Codex 变成你的员工 · Admin 运行时控制台让你看见每个员工正在干什么 | diff --git a/mateclaw-server/src/main/resources/docs/zh/security.md b/mateclaw-server/src/main/resources/docs/zh/security.md index bce775c2..97dd903f 100644 --- a/mateclaw-server/src/main/resources/docs/zh/security.md +++ b/mateclaw-server/src/main/resources/docs/zh/security.md @@ -88,8 +88,8 @@ mateclaw: | 状态码 | 含义 | 响应 | |--------|------|------| -| 401 | Token 缺失、过期或无效 | `{"code": 401, "message": "Unauthorized"}` | -| 403 | Token 有效但权限不足 | `{"code": 403, "message": "Forbidden"}` | +| 401 | Token 缺失、过期或无效 | `{"code":401,"msg":"Token expired or invalid","data":null}` | +| 403 | Token 有效但权限不足 | `{"code":403,"msg":"Forbidden","data":null}` | 前端统一处理——跳登录页、清空存储的 token。 @@ -100,8 +100,8 @@ MateClaw 出厂带 `admin` / `admin123`。**除了你自己笔记本之外的任 ### Spring Security 配置 - **无状态会话**——服务端不存 session;所有状态都在 JWT 里 -- **公共端点**——`/api/v1/auth/login`、`/h2-console/**`、`/swagger-ui/**` -- **受保护端点**——`/api/v1/**` 下的其他所有路径 +- **公共 API 端点**——`GET /api/v1/settings/language`、`/api/v1/auth/login`、`/api/v1/chat/stream`、`/api/v1/chat/*/stop`、`/api/v1/agents/*/chat/stream`、`/api/v1/setup/**`、`/api/v1/channels/webhook/**`、`/api/v1/channels/webchat/**`、`/api/v1/talk/ws`、`/api/v1/files/generated/**` +- **受保护端点**——`/api/**` 下的其他所有路径 - **CSRF 关闭**——无状态 JWT 不需要 --- @@ -247,7 +247,7 @@ Tool Guard:require_approval 用户点 Approve 或 Reject │ ▼ -POST /api/v1/approvals/{id}/resolve +POST /api/v1/chat/stream,消息为 /approve 或 /deny │ ├─ Approved → 重新加载 Agent,replay 工具调用,继续推理 └─ Rejected → 把拒绝作为 observation 返回,继续推理 @@ -255,6 +255,8 @@ POST /api/v1/approvals/{id}/resolve "replay" 机制很重要。Agent 恢复时**不会从头重新推理**——它直接跳到已经批准的工具调用、执行、从观察继续。**没有重复的 LLM 调用,没有浪费的 token。** +当前 Web 路径没有写入型 `POST /api/v1/approvals/{id}/resolve` 端点。批准和拒绝走普通聊天同一条 SSE 通道,这样 replay、持久化和取消都在同一个生命周期里。 + ### `mate_tool_approval` 表 | 列 | 用途 | @@ -283,24 +285,28 @@ Pending approval 在一个可配置的超时后过期(默认 10 分钟)。 MateClaw 可以通过 `channel/notification/` 适配器通知——邮件、应用内提醒、钉钉/飞书推送。在 `设置 → 安全与审批 → 通知` 里配置。 -### API 方式处理审批 +### 当前 API 表面 ```bash -# 列出 pending 审批 -curl http://localhost:18088/api/v1/approvals?status=pending \ +# 刷新页面后补水 pending 审批 +curl http://localhost:18088/api/v1/chat/{conversationId}/pending-approvals \ -H "Authorization: Bearer " -# 批准 -curl -X POST http://localhost:18088/api/v1/approvals/123/resolve \ +# 在等待中的会话里批准 +curl -N -X POST http://localhost:18088/api/v1/chat/stream \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ - -d '{"decision": "approved"}' + -d '{"agentId":"1","conversationId":"conv-abc123","message":"/approve"}' -# 拒绝并带原因 -curl -X POST http://localhost:18088/api/v1/approvals/123/resolve \ +# 在等待中的会话里拒绝 +curl -N -X POST http://localhost:18088/api/v1/chat/stream \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ - -d '{"decision": "rejected", "notes": "这个工作空间不适合"}' + -d '{"agentId":"1","conversationId":"conv-abc123","message":"/deny"}' + +# 管理自动批准授权 +curl http://localhost:18088/api/v1/approval/grants \ + -H "Authorization: Bearer " ``` --- diff --git a/mateclaw-server/src/main/resources/docs/zh/skills.md b/mateclaw-server/src/main/resources/docs/zh/skills.md index 88b8b992..2ebd57c7 100644 --- a/mateclaw-server/src/main/resources/docs/zh/skills.md +++ b/mateclaw-server/src/main/resources/docs/zh/skills.md @@ -531,6 +531,18 @@ mateclaw: --- +## 聊天里的 `/skill` 斜杠菜单(1.5.0 新增) + +不想用自然语言提示员工用哪个技能?在聊天输入框打一个 `/`,弹出一个**可搜索的技能选择器**: + +- ↑↓ 选、Enter/Tab 确认、Esc 关;打字实时过滤已启用的技能(最多显示 8 条)。 +- 列表来自 `GET /api/v1/skills/enabled`——包含真实技能 + MCP/ACP 派生的虚拟技能(同名时真实技能优先)。30 秒按工作空间缓存,避免每次重开都拉取。 +- 选中一个技能后,输入框里被插入一句指令:`Use the "技能名" skill: `,光标停在末尾,你接着补充上下文发出去。员工在消息历史里看到这条指令,就会调 `load_skill` 拉起这个技能。 + +这个菜单的显示条件只看**当前选中了员工、且该员工没有关闭技能**(前端 `currentAgent && !skillsDisabled`)——和全局的渐进式披露开关无关。全局把 `mateclaw.skill.disclosure.load-skill-tool.enabled` 设为 `false` 只会让后端不注册 `load_skill` 工具,菜单照样弹(员工会回退用 `readSkillFile` 之类的方式拉技能)。 + +--- + ## 技能生命周期管理员(v1.4 新增) 会合成技能的 Agent 会攒下垃圾——三周前的一次性技能还在目录里占着位子。**管理员(curator)** 是一个每日扫描,把闲置的、**Agent 创建的**技能沿 `active → stale → archived` 老化,让它们退场而不删除任何东西。 diff --git a/mateclaw-server/src/main/resources/docs/zh/tools.md b/mateclaw-server/src/main/resources/docs/zh/tools.md index 41461f7e..71beee72 100644 --- a/mateclaw-server/src/main/resources/docs/zh/tools.md +++ b/mateclaw-server/src/main/resources/docs/zh/tools.md @@ -319,14 +319,14 @@ curl -X PUT http://localhost:18088/api/v1/tools/1 \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -d '{"enabled": false}' -# 直接测试一个工具 -curl -X POST http://localhost:18088/api/v1/tools/WebSearchTool/test \ +# 设置内置或渠道工具的披露分级 +curl -X PUT http://localhost:18088/api/v1/tools/1/disclosure-tier \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ - -d '{"query": "Spring AI"}' + -d '{"tier": "core"}' ``` -每个依赖 provider 的工具在 Tools 页面都有测试按钮。 +当前 REST API 管理工具行、启用状态和披露分级。内置工具的直接执行走 Agent runtime,不存在 `/tools/{name}/test` 端点。 --- diff --git a/mateclaw-server/src/main/resources/docs/zh/wiki.md b/mateclaw-server/src/main/resources/docs/zh/wiki.md index 5312d06e..cba5cd99 100644 --- a/mateclaw-server/src/main/resources/docs/zh/wiki.md +++ b/mateclaw-server/src/main/resources/docs/zh/wiki.md @@ -258,6 +258,8 @@ UI 上能做: | `wiki_related_pages` | 关联页面(共享 chunk / 共享原文 / 双向链 / 语义近邻) | | `wiki_explain_relation` | 详细拆解两页之间的关联强度和原因 | | `wiki_create_page` / `wiki_delete_page` | 直接维护页面(删除受 locked / system 保护) | +| `wiki_update_page` | **1.5.0**:就地编辑一页(保留 slug),受 pageType "改" 权限门禁 | +| `wiki_stale_pages` | **1.5.0**:列出当前所有被标记"待复核(stale)"的页 | | `wiki_archive_page` / `wiki_unarchive_page` | 软归档:从默认 list/search/related 隐藏,但保留页面与引文,可恢复。系统页不能归档。 | | `wiki_list_transformations` | 列出当前 KB 可用的加工器模板(名称、用途、是否默认运行)| | `wiki_apply_transformation` | 对一份**原始材料**运行一个模板,返回输出(runId / output / 落页信息)| @@ -299,13 +301,11 @@ UI 上能做: #### 运维端点 -基础路径 `/api/v1/wiki/hot-cache`: - | Method | Path | 作用 | |---|---|---| -| `GET` | `/{kbId}` | 拿当前快照 + 元数据 | -| `POST` | `/{kbId}/regenerate` | 手动重建(异步,跳过去抖) | -| `DELETE` | `/{kbId}` | 软删除;下次事件触发重建 | +| `GET` | `/api/v1/wiki/hot-cache/{kbId}` | 拿当前快照 + 元数据 | +| `POST` | `/api/v1/wiki/hot-cache/{kbId}/regenerate` | 手动重建(异步,跳过去抖) | +| `DELETE` | `/api/v1/wiki/hot-cache/{kbId}` | 软删除;下次事件触发重建 | 热缓存数据落在 `mate_wiki_hot_cache`——具体列见下面的 **底层数据** 一节。 @@ -420,7 +420,85 @@ Chat 渲染 agent 回复时,content 里的 `[[slug]]` / `[[slug|alias]]` 会 | 4 | 删除 / 重命名级联清理,audit log,feature flag | | 5 | analyze 阶段输出 slug 白名单 `related_pages`(服务端二次校验),enrich applier 跳代码块 + slug 白名单 gate | -完整设计 + 实测见 `rfcs/202605/55-wiki-link-resolution-overhaul.md` + `mateclaw-server/src/test/resources/e2e/wiki-link-overhaul-verification.md`(6 个 e2e pass section、50+ 条 live 断言、3 个测试中发现并修复的 bug 完整记录)。 +完整设计与实测见仓库内对应的设计文档与端到端验证记录。 + +--- + +## 知识库会自维护(1.5.0) + +1.5.0 把 Wiki 从"一个能搜的知识库"推进成"一个会自己维护一致性、自己分层、自己跑流水线、能挂本地目录的知识引擎"。这一整块的管理入口在后台的 **Wiki 高级管理面板**(五个子页:页面类型档案 / 分层与失效 / 权限 / 知识源 watcher / 流水线)。 + +### 知识分层:事实 vs 经验 + +每页可以标一个**知识层**: + +- **`fact`(事实层)**——"是什么":基础事实页。不标的默认按事实处理。 +- **`experience`(经验层)**——"意味着什么":综合、分析、个人洞见,**依赖**一组事实页。 + +**失效会传播。** 经验页声明它依赖哪些事实页(按页面 **id** 存边,所以改名不断链)。当某个事实页在 ingest 时被更新,所有依赖它的经验页自动被标记 `stale`(待复核)+ 一段失效原因。`wiki_stale_pages` 工具列出当前所有待复核的页;搜索可以**按知识层过滤**(只搜事实 / 只搜经验 / 全部)。 + +底层:`mate_wiki_page` 加了 `knowledge_layer` / `depends_on_json` / `stale` / `stale_reason_json` 列(迁移 V135),依赖边存在 `mate_wiki_page_dependency` 表,带一个反向索引专门给失效传播用。 + +### 页面类型档案(pageType profile) + +为一个知识库定义有哪些**页面类型**(如"概念 / 教程 / 决策记录"),每种类型可以带: + +- 结构化字段 **schema**——新页落库时按它校验元数据,并记下校验状态(valid / invalid + 详情) +- **路由 / 创建 / 合并** 阶段的提示词——注入到对应阶段的 LLM 调用里 +- **Markdown 模板**——生成页面时的骨架 + +每个 KB 至多一个**启用的** profile;没配的 KB 用**内建默认档案**。profile 用 YAML 或 JSON 写,有"校验(不落库)"和"重置为默认"两个动作。存在 `mate_wiki_page_type_profile` 表(迁移 V134),页面元数据列也在同一迁移里加到 `mate_wiki_page`(`metadata_json` / `metadata_validation_status` / `template_key` / `profile_version`)。 + +### 页面类型权限(per-agent) + +可以为"**某个员工 + 某个 KB + 某种页面类型**"配读 / 增 / 改 / 删四个开关,外加**写策略**: + +| 写策略 | 含义 | +|---|---| +| `allow` | 立即写 | +| `approval_required` | 写入挂起,走[审批](./security)流程 | +| `deny` | 禁止 | + +`page_type='*'` 是 KB 级默认,**精确匹配优先于通配**。 + +**读和写的默认回退不一样**,这点要分清: + +- **读**——没匹配到规则时,回退到 **KB 级默认读策略** `defaultReadPolicy`(默认 `allow_all`,除非 KB 配成 `deny_all`)。所以升级后已有 KB 仍然全可读。读门禁过滤列表和搜索结果,不可读的类型直接当不存在(不泄露存在性)。 +- **写**——是 opt-in 收紧的。一个员工对某 KB **没配任何规则**时,写默认 `allow`(旧行为不变);一旦配了**任意一条**规则,这个 KB 就进入"锁定"模式——没匹配到规则的页面类型按 `deny` 处理(fail-safe)。 + +存在 `mate_wiki_agent_page_type_permission` 表(迁移 V133)。 + +### 处理流水线(Wiki Pipeline) + +给知识库定义一段处理流程,由**页面事件自动触发**: + +- **触发器**:`page_type_count`(某类页面数量达到阈值)、`page_created`(新建某类页面)、`stale_marked`(页面被标记失效) +- **步骤执行器**: + - `llm`——把输入过一遍模型,模型输出作为本步结果 + - `skill`——在**受限技能集**内跑一个技能,以 owner agent 身份执行 + +定义用 YAML 或 JSON 写,有 CRUD + 校验接口。每次运行(run)和每一步(step run)都有持久化记录可查,按 `(definition, trigger, subject, bucket)` 去重保证幂等。表:`mate_wiki_pipeline_definition` / `mate_wiki_pipeline_run` / `mate_wiki_pipeline_step_run`(迁移 V136)。 + +### 本地目录挂成知识源——可插拔 + 定时增量 + +知识源做成了**可插拔 SPI**(`WikiIngestSourceProvider`),内建一个文件系统实现:给 KB 配一个 `source_directory`,目录里的文件就被吸进知识库。 + +- **定时增量同步**——后台调度器(用分布式锁保证多节点只跑一份)周期扫描,**按内容哈希**检测变更,只重新吸入新增 / 改动的文件(文本和二进制都覆盖)。 +- **安全 fail-closed**——路径先规范化再解析软链(堵 TOCTOU),按允许根目录白名单校验;生产 profile 下空白名单默认拒绝一切。配 `mate.wiki.allowed-source-roots` 白名单。 +- **状态可查 + 手动触发**——`GET .../source-watcher` 看状态,`POST .../source-watcher/scan` 立即扫一次。 + +相关配置(`application.yml`): + +```yaml +mate: + wiki: + watcher-enabled: false # 知识源 watcher 总开关 + watcher-interval-ms: 300000 # 扫描间隔(默认 5 分钟) + allowed-source-roots: [] # 允许的源目录根(白名单) + require-allowed-roots: false # 生产建议设 true:空白名单则拒绝一切 +``` + +全部新增 REST 端点见 [API 参考](./api#llm-wiki)。 --- diff --git a/mateclaw-ui/package.json b/mateclaw-ui/package.json index cec79041..6b03914f 100644 --- a/mateclaw-ui/package.json +++ b/mateclaw-ui/package.json @@ -1,6 +1,6 @@ { "name": "mateclaw-ui", - "version": "1.5.0-SNAPSHOT", + "version": "1.5.0", "private": true, "type": "module", "description": "MateClaw - Personal AI Assistant Web Console", diff --git a/pom.xml b/pom.xml index 1d0ab590..abc6c83d 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ - 1.5.0-SNAPSHOT + 1.5.0 21