From fb2f847c817e697cfde2d7c5e8750512c70a1f8b Mon Sep 17 00:00:00 2001 From: matevip Date: Sat, 16 May 2026 17:31:05 +0800 Subject: [PATCH] fix(docs): mirror root docs into classpath so readMateClawDoc serves them --- .../src/main/resources/docs/en/acp.md | 264 ++++++++ .../src/main/resources/docs/en/agents.md | 322 ++++++++++ .../src/main/resources/docs/en/ambient-ai.md | 171 ++++++ .../src/main/resources/docs/en/api.md | 509 ++++++++++++++++ .../main/resources/docs/en/architecture.md | 368 ++++++++++++ .../src/main/resources/docs/en/backstage.md | 135 +++++ .../src/main/resources/docs/en/channels.md | 527 ++++++++++++++++ .../src/main/resources/docs/en/chat.md | 281 +++++++++ .../src/main/resources/docs/en/config.md | 389 ++++++++++++ .../src/main/resources/docs/en/console.md | 562 +++++++++++++++++ .../main/resources/docs/en/contributing.md | 340 +++++++++++ .../src/main/resources/docs/en/desktop.md | 284 +++++++++ .../main/resources/docs/en/docker-deploy.md | 310 ++++++++++ .../src/main/resources/docs/en/doctor.md | 233 ++++++++ .../src/main/resources/docs/en/faq.md | 418 +++++++++++++ .../src/main/resources/docs/en/index.md | 44 ++ .../src/main/resources/docs/en/intro.md | 90 +++ .../src/main/resources/docs/en/mcp.md | 433 ++++++++++++++ .../src/main/resources/docs/en/memory.md | 407 +++++++++++++ .../src/main/resources/docs/en/model3d.md | 225 +++++++ .../src/main/resources/docs/en/models.md | 453 ++++++++++++++ .../src/main/resources/docs/en/multimodal.md | 192 ++++++ .../src/main/resources/docs/en/quickstart.md | 86 +++ .../src/main/resources/docs/en/releases.md | 29 + .../src/main/resources/docs/en/roadmap.md | 201 +++++++ .../src/main/resources/docs/en/security.md | 565 ++++++++++++++++++ .../src/main/resources/docs/en/skills.md | 528 ++++++++++++++++ .../src/main/resources/docs/en/tools.md | 322 ++++++++++ .../src/main/resources/docs/en/triggers.md | 259 ++++++++ .../src/main/resources/docs/en/user-guide.md | 201 +++++++ .../main/resources/docs/en/wecom-tuning.md | 419 +++++++++++++ .../src/main/resources/docs/en/wiki.md | 482 +++++++++++++++ .../src/main/resources/docs/en/workflow.md | 336 +++++++++++ .../src/main/resources/docs/en/workspaces.md | 258 ++++++++ .../src/main/resources/docs/zh/acp.md | 264 ++++++++ .../src/main/resources/docs/zh/agents.md | 322 ++++++++++ .../src/main/resources/docs/zh/ambient-ai.md | 171 ++++++ .../src/main/resources/docs/zh/api.md | 509 ++++++++++++++++ .../main/resources/docs/zh/architecture.md | 368 ++++++++++++ .../src/main/resources/docs/zh/backstage.md | 135 +++++ .../src/main/resources/docs/zh/channels.md | 527 ++++++++++++++++ .../src/main/resources/docs/zh/chat.md | 281 +++++++++ .../src/main/resources/docs/zh/config.md | 389 ++++++++++++ .../src/main/resources/docs/zh/console.md | 562 +++++++++++++++++ .../main/resources/docs/zh/contributing.md | 337 +++++++++++ .../docs/zh/desktop-ui-hot-update.md | 518 ++++++++++++++++ .../src/main/resources/docs/zh/desktop.md | 284 +++++++++ .../main/resources/docs/zh/docker-deploy.md | 310 ++++++++++ .../src/main/resources/docs/zh/doctor.md | 233 ++++++++ .../src/main/resources/docs/zh/faq.md | 418 +++++++++++++ .../src/main/resources/docs/zh/index.md | 44 ++ .../src/main/resources/docs/zh/intro.md | 90 +++ .../src/main/resources/docs/zh/mcp.md | 429 +++++++++++++ .../src/main/resources/docs/zh/memory.md | 402 +++++++++++++ .../src/main/resources/docs/zh/model3d.md | 227 +++++++ .../src/main/resources/docs/zh/models.md | 454 ++++++++++++++ .../src/main/resources/docs/zh/multimodal.md | 188 ++++++ .../src/main/resources/docs/zh/quickstart.md | 86 +++ .../src/main/resources/docs/zh/releases.md | 29 + .../src/main/resources/docs/zh/roadmap.md | 201 +++++++ .../src/main/resources/docs/zh/security.md | 565 ++++++++++++++++++ .../src/main/resources/docs/zh/skills.md | 527 ++++++++++++++++ .../src/main/resources/docs/zh/tools.md | 317 ++++++++++ .../src/main/resources/docs/zh/triggers.md | 259 ++++++++ .../src/main/resources/docs/zh/user-guide.md | 201 +++++++ .../main/resources/docs/zh/wecom-tuning.md | 419 +++++++++++++ .../src/main/resources/docs/zh/wiki.md | 482 +++++++++++++++ .../src/main/resources/docs/zh/workflow.md | 336 +++++++++++ .../src/main/resources/docs/zh/workspaces.md | 258 ++++++++ 69 files changed, 21785 insertions(+) create mode 100644 mateclaw-server/src/main/resources/docs/en/acp.md create mode 100644 mateclaw-server/src/main/resources/docs/en/agents.md create mode 100644 mateclaw-server/src/main/resources/docs/en/ambient-ai.md create mode 100644 mateclaw-server/src/main/resources/docs/en/api.md create mode 100644 mateclaw-server/src/main/resources/docs/en/architecture.md create mode 100644 mateclaw-server/src/main/resources/docs/en/backstage.md create mode 100644 mateclaw-server/src/main/resources/docs/en/channels.md create mode 100644 mateclaw-server/src/main/resources/docs/en/chat.md create mode 100644 mateclaw-server/src/main/resources/docs/en/config.md create mode 100644 mateclaw-server/src/main/resources/docs/en/console.md create mode 100644 mateclaw-server/src/main/resources/docs/en/contributing.md create mode 100644 mateclaw-server/src/main/resources/docs/en/desktop.md create mode 100644 mateclaw-server/src/main/resources/docs/en/docker-deploy.md create mode 100644 mateclaw-server/src/main/resources/docs/en/doctor.md create mode 100644 mateclaw-server/src/main/resources/docs/en/faq.md create mode 100644 mateclaw-server/src/main/resources/docs/en/index.md create mode 100644 mateclaw-server/src/main/resources/docs/en/intro.md create mode 100644 mateclaw-server/src/main/resources/docs/en/mcp.md create mode 100644 mateclaw-server/src/main/resources/docs/en/memory.md create mode 100644 mateclaw-server/src/main/resources/docs/en/model3d.md create mode 100644 mateclaw-server/src/main/resources/docs/en/models.md create mode 100644 mateclaw-server/src/main/resources/docs/en/multimodal.md create mode 100644 mateclaw-server/src/main/resources/docs/en/quickstart.md create mode 100644 mateclaw-server/src/main/resources/docs/en/releases.md create mode 100644 mateclaw-server/src/main/resources/docs/en/roadmap.md create mode 100644 mateclaw-server/src/main/resources/docs/en/security.md create mode 100644 mateclaw-server/src/main/resources/docs/en/skills.md create mode 100644 mateclaw-server/src/main/resources/docs/en/tools.md create mode 100644 mateclaw-server/src/main/resources/docs/en/triggers.md create mode 100644 mateclaw-server/src/main/resources/docs/en/user-guide.md create mode 100644 mateclaw-server/src/main/resources/docs/en/wecom-tuning.md create mode 100644 mateclaw-server/src/main/resources/docs/en/wiki.md create mode 100644 mateclaw-server/src/main/resources/docs/en/workflow.md create mode 100644 mateclaw-server/src/main/resources/docs/en/workspaces.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/acp.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/agents.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/ambient-ai.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/api.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/architecture.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/backstage.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/channels.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/chat.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/config.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/console.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/contributing.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/desktop-ui-hot-update.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/desktop.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/docker-deploy.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/doctor.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/faq.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/index.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/intro.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/mcp.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/memory.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/model3d.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/models.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/multimodal.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/quickstart.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/releases.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/roadmap.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/security.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/skills.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/tools.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/triggers.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/user-guide.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/wecom-tuning.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/wiki.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/workflow.md create mode 100644 mateclaw-server/src/main/resources/docs/zh/workspaces.md diff --git a/mateclaw-server/src/main/resources/docs/en/acp.md b/mateclaw-server/src/main/resources/docs/en/acp.md new file mode 100644 index 00000000..a0f925cf --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/acp.md @@ -0,0 +1,264 @@ +--- +title: ACP Integration — Plug External Coding Agents Into MateClaw +description: MateClaw acts as an ACP host. Delegate prompts to Claude Code, Codex, OpenCode, Qwen Code or any Agent Client Protocol agent over stdio. Built-in endpoints, visual env editor, auto-bridged skill cards, trust model, error translation. +head: + - - meta + - name: keywords + content: ACP,Agent Client Protocol,Claude Code,Codex,OpenCode,Qwen Code,external agent,stdio JSON-RPC,coding agent integration +--- + +# ACP — Agent Client Protocol + +**ACP is how MateClaw hands a prompt to an agent someone else built.** + +Agent Client Protocol is an open spec for agent clients to talk to agent servers over JSON-RPC. MateClaw acts as the **host**: it spawns an external CLI (Claude Code, Codex, OpenCode, Qwen Code, …), runs the `initialize` → `session/new` → `session/prompt` handshake on stdio, streams the response back into the conversation, and closes the process. + +If MCP is "plug in a tool", ACP is **"plug in a whole agent"**. From inside a MateClaw turn, calling Claude Code looks the same as calling any built-in tool — your agent just asks for `acp_claude-code_prompt` and reads the answer. + +--- + +## ACP vs MCP at a glance + +| | **MCP** | **ACP** | +|---|---|---| +| What you connect | A tool server | An agent | +| Granularity | Per-tool (`tools/list`) | Per-prompt (one shot) | +| Transport in MateClaw | stdio / streamable_http / sse | stdio | +| Session model | Long-lived, multi-call | Stateless: spawn → prompt → close | +| Typical use | Filesystem, search, custom data API | Delegate a coding task to Claude Code / Codex | +| Surface in MateClaw | Tool catalog | Skill catalog (auto-bridged) + tool wrapper | + +You can mix both in the same agent. + +--- + +## Built-in endpoints + +The Flyway migration that ships with MateClaw seeds four endpoints, all **disabled by default** — turn them on after you install the matching CLI. + +| Slug | Display name | Command | Notes | +|---|---|---|---| +| `claude-code` | Claude Code | `npx -y @zed-industries/claude-agent-acp` | Anthropic's Claude Code; reads `ANTHROPIC_API_KEY` | +| `codex` | OpenAI Codex CLI | `npx -y @zed-industries/codex-acp` | OpenAI's coding agent; reads `OPENAI_API_KEY` | +| `opencode` | OpenCode | `opencode acp` | Multi-model agent; binary must be on `PATH` | +| `qwen-code` | Qwen Code | `qwen --acp` | Alibaba's coding agent; reads `DASHSCOPE_API_KEY` | + +Built-in rows are write-protected — you can edit `args_json` / `env_json` / `description` / `trusted` / `enabled`, but you can't change the slug, replace the command, or delete the row. To run an unrelated agent, **add a custom endpoint** instead. + +--- + +## Configure via the admin console + +`Settings → ACP Endpoints` is the full CRUD surface. + +### Add or edit an endpoint + +- **Slug** — lowercase identifier (e.g. `claude-code`). Immutable after create. Skills reference endpoints by this slug. +- **Display name** — human label shown on the Skills page. +- **Description** — operator notes. +- **Command** — the executable (`npx`, `opencode`, …). Locked on built-in rows. +- **Args (JSON array)** — CLI arguments, e.g. `["-y","@zed-industries/claude-agent-acp"]`. +- **Env (JSON object)** — extra env vars merged into the child process. The visual editor masks values whose key matches `*API_KEY*`, `*TOKEN*`, `*SECRET*`, or `*PASS*`. +- **Tool parse mode** — `call_title` / `call_detail` / `update_detail`. Controls how upstream tool-call events render into the streamed transcript. +- **Trusted** — when ON, MateClaw auto-allows any `session/request_permission` the upstream agent asks for. When OFF, every permission request is denied (use this for non-interactive contexts). +- **Enabled** — gate flag. Disabled endpoints don't bridge into the skill catalog. + +### Test the connection + +Click **Test** to spawn the process, run `initialize` + `session/new`, and tear down. The result panel shows protocol version, agent capabilities, elapsed time, and — on failure — a translated error hint (see [Trust & error translation](#trust-error-translation)). Status persists on the row as `last_status` / `last_tested_at` / `last_error`. + +### Enable / disable / delete + +- **Toggle** — drops the endpoint from the catalog without deleting it. +- **Delete** — only available on custom rows. Built-in rows can't be deleted. + +Any change publishes an `AcpEndpointChangedEvent` and the skill catalog re-syncs immediately — no restart needed. + +--- + +## REST API + +Base path: `/api/v1/acp/endpoints`. JWT required. + +| Method | Path | What it does | +|---|---|---| +| `GET` | `/` | List all endpoints | +| `GET` | `/{id}` | Fetch one | +| `POST` | `/` | Create custom endpoint | +| `PUT` | `/{id}` | Patch fields (built-in `command` is locked) | +| `DELETE` | `/{id}` | Delete custom endpoint (built-ins refuse) | +| `PUT` | `/{id}/toggle?enabled=true\|false` | Enable / disable | +| `POST` | `/{id}/test` | Run connection test | + +### Create a custom endpoint + +```bash +curl -X POST http://localhost:18088/api/v1/acp/endpoints \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "my-coder", + "displayName": "My Custom Coder", + "description": "Internal coding agent", + "command": "npx", + "argsJson": "[\"-y\",\"@my-org/my-acp-agent\"]", + "envJson": "{\"MY_API_KEY\":\"sk-...\"}", + "toolParseMode": "call_detail", + "trusted": true, + "enabled": true + }' +``` + +### Test an endpoint + +```bash +curl -X POST http://localhost:18088/api/v1/acp/endpoints/9100002/test \ + -H "Authorization: Bearer " +``` + +Response shape: + +```json +{ + "name": "claude-code", + "command": "npx", + "args": ["-y", "@zed-industries/claude-agent-acp"], + "agentCapabilities": { "loadSession": false, "promptCapabilities": { "image": true } }, + "status": "OK", + "elapsedMs": 1842 +} +``` + +On failure `status` is `ERROR` and `error` carries the translated hint. + +--- + +## How endpoints reach your agents + +There are two paths: + +### 1. Auto-bridged virtual skill (zero config) + +For every enabled endpoint, MateClaw registers a virtual skill card and a wrapper tool named `acp__prompt`. The tool takes a single `prompt` string and returns the upstream agent's accumulated text reply. Any agent can call it the same way it calls a built-in tool — no skill manifest required. + +``` +Settings → ACP Endpoints (toggle on) + ↓ +AcpEndpointChangedEvent + ↓ +Skill catalog gains card "Claude Code" +Tool registry gains "acp_claude-code_prompt" + ↓ +Agent calls the tool → AcpDelegationService.prompt() + ↓ +spawn → initialize → session/new → session/prompt + ↓ +accumulate agent-message-chunk notifications + ↓ +return text to the agent's turn +``` + +### 2. Hand-authored skills (full control) + +A skill manifest can declare `type: acp` and pin to an endpoint. The skill gets its own wrapper tool (`acp___prompt`), can inject a `systemPrefix` ahead of every prompt, and can override `cwd` per session. + +```yaml +# SKILL.md frontmatter +type: acp +acp: + endpoint: claude-code + systemPrefix: | + You are working inside the MateClaw repo. Always run `mvn test` before reporting done. + cwd: /workspaces/mateclaw +``` + +This is how the `claude-code-helper` and `codex-helper` skill templates ship. + +--- + +## Trust & error translation {#trust-error-translation} + +### Trust flag + +ACP servers can pause and ask the host for permission (`session/request_permission`) before doing something sensitive — writing files, running shell commands, etc. MateClaw does **not** prompt the user mid-stream; instead, the per-endpoint `trusted` flag decides: + +- `trusted: true` — auto-allow the first option the agent offered. Best for installed CLIs you control. +- `trusted: false` — cancel every permission request. Use for sandboxed or untrusted endpoints; the upstream agent will gracefully back off. + +### Error translation + +Upstream errors from coding agents are notoriously cryptic. `AcpRuntimeSupport.translateAuthError()` recognises common 401 / 403 / "Request not allowed" patterns and rewrites them into something actionable: + +- Missing key → "Set `ANTHROPIC_API_KEY`" / `OPENAI_API_KEY` / `DASHSCOPE_API_KEY` / `GOOGLE_API_KEY`, picked per endpoint. +- Claude Code OAuth keychain hijack → suggests `claude logout` to clear a stale OAuth token from `~/.claude/` that's shadowing your env var. + +Hints surface in the test panel and in the streamed error message your agent receives. + +### Timeouts and limits + +- `initialize` handshake: 15s +- `session/new`: 10s +- Whole `session/prompt` round-trip: 5 min +- Stdio buffer cap: 50 MiB per call (configurable on the row via `stdio_buffer_limit_bytes`) + +--- + +## Database — `mate_acp_endpoint` + +| Column | Type | Default | Purpose | +|---|---|---|---| +| `id` | BIGINT | — | Primary key. Built-ins use `9100001`–`9100004` | +| `name` | VARCHAR(64) | — | Unique slug. Skills reference this | +| `display_name` | VARCHAR(128) | NULL | Label | +| `description` | TEXT | NULL | Operator notes | +| `command` | VARCHAR(256) | — | Process command | +| `args_json` | TEXT | NULL | CLI args (JSON array) | +| `env_json` | TEXT | NULL | Env overrides (JSON object) | +| `tool_parse_mode` | VARCHAR(32) | `call_title` | `call_title` / `call_detail` / `update_detail` | +| `builtin` | BOOLEAN | FALSE | Built-in rows are write-protected | +| `trusted` | BOOLEAN | TRUE | Auto-allow permission requests | +| `enabled` | BOOLEAN | FALSE | Off until you opt in | +| `stdio_buffer_limit_bytes` | BIGINT | 52428800 | 50 MiB cap on accumulated stdio | +| `last_status` | VARCHAR(32) | NULL | `OK` / `ERROR` | +| `last_tested_at` | DATETIME | NULL | Last test timestamp | +| `last_error` | TEXT | NULL | Last test error | +| `workspace_id` | BIGINT | 1 | Bound workspace | +| `create_time` / `update_time` | DATETIME | — | Timestamps | +| `deleted` | INT | 0 | Logical delete | + +Schema lives at `db/migration/{h2,mysql}/V68__add_acp_endpoints.sql`. + +--- + +## Troubleshooting + +### "Command not found" + +The `command` must be on the `PATH` of the user running MateClaw. Verify with `which npx` (or `which opencode`, `which qwen`). On Docker, install the CLI in the image. As a last resort, set `command` to the full absolute path. + +### "Request not allowed" / 403 from Claude Code + +You probably have an OAuth token cached in `~/.claude/` that's overriding the `ANTHROPIC_API_KEY` you set in the env editor. Run `claude logout`, then click **Test** again. The test panel will tell you when it detects this case. + +### Hangs on `session/new` + +Usually means the upstream CLI is downloading dependencies on first run (`npx -y` does this). Either pre-warm by running the CLI once outside MateClaw, or just retry — subsequent calls are fast. + +### "Subprocess output exceeded buffer" + +The agent emitted more than 50 MiB of stdio in a single call. Bump `stdioBufferLimitBytes` on the endpoint, or split the prompt into smaller turns. + +### Tool doesn't show up on the Skills page + +- Confirm `enabled: true`. +- Confirm the test passes (`last_status: OK`). +- Open the agent's tool binding — auto-bridged tools are available to every agent unless explicitly excluded. + +--- + +## Next + +- [Skills](./skills) — including hand-authored ACP-typed skills +- [Tools](./tools) — how the wrapper tool plugs into the registry +- [MCP](./mcp) — the sibling protocol for tool servers +- [Security & Approval](./security) — pairing the trust flag with Tool Guard diff --git a/mateclaw-server/src/main/resources/docs/en/agents.md b/mateclaw-server/src/main/resources/docs/en/agents.md new file mode 100644 index 00000000..fa2e74f9 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/agents.md @@ -0,0 +1,322 @@ +--- +title: Multi-Agent Engine — ReAct + Plan-and-Execute +description: MateClaw's multi-agent system runs in two modes — ReAct for real-time reasoning and Plan-and-Execute for complex task decomposition. Agents can delegate to one another for true multi-agent collaboration. +head: + - - meta + - name: keywords + content: multi-agent,ReAct,Plan-and-Execute,agent delegation,Spring AI Alibaba,AI agent +--- + +# Multi-Agent Engine + +> **They're called "digital employees" now.** The back office uses that term throughout. The runtime is still an Agent under the hood, but the UI, the mental model, and the templates treat each one as a coworker on your team. +> The renaming brings a worldview shift with it: you give an employee a **Role**, a **Goal**, and a **Backstory** — they know who they are and why they exist. You don't have to write a cold system prompt asking an "agent" to please understand the task. + +An employee is a personality with tools. Multiple employees form a team. + +That's the short version. The longer one: an employee is a name, a system prompt that defines how it thinks (built from role / goal / backstory), a model that actually thinks, a set of tools it's allowed to reach for, optional knowledge bases it can read, optional skills that extend what it can do, its own slice of memory, and a choice of how to approach hard problems — incrementally (ReAct) or with a plan (Plan-and-Execute). + +You can have many employees. Each one is specialized. You give them different jobs. + +--- + +## What a digital employee has + +| Piece | What it is | +|-------|-----------| +| **Name** | How you and your team find them | +| **Icon** | Pixel-art style, color coded by role | +| **Role** | One sentence — "I'm the product researcher" / "I'm customer support" | +| **Goal** | One sentence — "I help you see how the market is moving" | +| **Backstory** | Where they came from, why they exist, what they care about; auto-spliced into the final system prompt | +| **Employee-card tagline** | The "self-introduction" shown on the card | +| **System prompt** | Their personality, rules, style, priorities (role/goal/backstory inject automatically) | +| **Type** | `react` or `plan_execute` | +| **Tools** | Which tools they're allowed to call (built-in, MCP, skills, ACP-bridged) | +| **Knowledge bases** | LLM Wikis they can read from (KB hot cache auto-injects into the system prompt) | +| **Workspace memory** | Their own `PROFILE.md`, `MEMORY.md`, `SOUL.md`, `AGENTS.md`, and daily notes | +| **Max iterations** | How many reasoning loops are allowed before forced convergence | +| **Enabled flag** | Off switch | + +Notice what's *not* here: the model. A single global default model (set in `Settings → Models`) is used for every agent at runtime. The `model_name` field on the agent row is a legacy artifact — it's ignored. This is intentional: swapping models across your whole deployment is one click, not thirty. + +--- + +## Templates: hire a coworker who already knows the job + +You don't start from scratch. `Digital Employees → New` opens a two-tier template picker. + +### 5 career templates (recommended) + +Each one ships with a role, goal, backstory, the right toolset, a pixel-art avatar, and a color that belongs to the role. **Open one, it works:** + +- **Product Researcher** — competitive scans, market tracking, interview synthesis +- **Customer Support** — catch every question, look it up in the KB, escalate what they can't resolve +- **Knowledge Curator** — feed scattered material into the LLM Wiki, maintain bidirectional links, periodic consolidation +- **Data Analyst** — query datasources, run SQL, build charts, write conclusions +- **Executive Assistant** — calendar, email drafts, cross-tool coordination + +### Generic templates (blank or half-finished) + +- **General Assistant** — the default chat employee +- **Research / Code / Writing / Knowledge Curator / Data Analyst** — semi-finished, organized by purpose +- **Custom** — fully blank, if you know exactly what you want + +Pick one, give them a name, adjust the role and goal, save. **Working coworker in under a minute.** Every field is editable after creation. + +--- + +## Two ways of thinking + +### ReAct — think, act, observe, continue + +The default. An agent in ReAct mode runs a loop: **reason** about what to do next, **act** (maybe by calling a tool), **observe** the result, decide whether to loop again or answer. + +Use it for: +- simple Q&A that might need one or two tool calls +- conversational interaction where each user turn is small +- tasks where the agent needs to react to what it learns along the way + +Example: *"What's the weather in Beijing today?"* → reason (need current data), act (call web search), observe (15–26°C, sunny), answer. + +### Plan-and-Execute — plan first, execute second + +For larger tasks. The agent starts by generating a **plan** — an ordered list of 2 to 6 steps. Then it executes each step, one at a time. When done, it summarizes everything it did. + +Use it for: +- multi-step research ("investigate X, compare Y, write a brief") +- anything where the steps are knowable up front +- anything where you want to **watch progress** — the plan and each step's status show up in a persistent task list next to the conversation + +Example: *"Research Spring AI frameworks, compare the top three, write me a brief."* → plan (4 steps) → execute in order → summarize. + +### How to choose + +| Situation | Use | Why | +|-----------|-----|-----| +| Simple Q&A, single-tool calls | ReAct | No planning overhead | +| Information retrieval | ReAct | Usually done in 2–3 cycles | +| Multi-step ordered work | Plan-and-Execute | Explicit plan is easier to watch and debug | +| Research + comparison + writing | Plan-and-Execute | Each step feeds the next | +| "Read this file and tell me X" | ReAct | One tool, one answer | +| "Build me a structured report on X" | Plan-and-Execute | Multiple gathering + synthesis steps | + +Change an agent's type at any time. Same system prompt works reasonably in both modes. + +--- + +## Multi-agent parallel delegation + +An agent doesn't work alone. One agent can delegate to another — or to **three at once**. + +- **Single delegation** — hand a sub-task to a specific agent; it runs in an isolated session, results stream back +- **Parallel delegation** — fan out to multiple agents at once, each in its own session +- **Live child visibility** — see reasoning, tool calls, and progress for each child in the ChatConsole as it happens +- **Routing hints** — built into the system prompt, so agents know when to handle it themselves vs. when to delegate + +Example: coding agent takes the Jira ticket, research agent pulls competitor data, writing agent drafts the Slack reply. Three in parallel, results flow back to the orchestrator. + +--- + +## Deep thinking + +Not every question deserves deep reasoning, but some do. MateClaw lets you turn on deep thinking per agent, per conversation: + +- **`thinkingLevel`**: `off` / `low` / `medium` / `high` / `max` +- Supports Anthropic extended thinking, DashScope qwq reasoning, OpenAI o1 `reasoning_effort=high` +- The thinking block streams into the UI as a collapsible panel — you see the model reason, tokens don't get wasted on tasks that don't need it + +--- + +## Hiring a digital employee + +`Digital Employees → New`: + +1. Pick a template (one of the 5 career templates, a generic template, or Custom) +2. Name them, choose an avatar (pixel-art library, or upload your own) +3. Write a one-sentence **Role**, a one-sentence **Goal**, a few-sentence **Backstory** +4. Write a one-line **employee-card tagline** — the self-introduction shown on the card +5. Choose the type (`react` or `plan_execute`) +6. Write (or edit) the system prompt (role / goal / backstory get auto-appended — don't repeat them) +7. Pick which tools they can use, bind any knowledge bases they should read +8. Set `max_iterations` (default 10) +9. Save + +Live immediately. Call them from chat or via API. + +### Tool binding (per-agent tool picker) + +::: tip New in 1.3.0 +In v1.2.0 the employee's tool binding was a flat "check what you want" list. v1.3.0 reworks this into a **grouped + status-aware + namespace-aware** picker, specifically to handle MCP tool grime. +::: + +Open the digital-employee editor's Tools tab and you get: + +- **Grouped by source**: built-in tools / skill-injected tools / MCP tools (further grouped per server) / ACP tools +- **Status badges**: each tool carries a tag — + - `connected` — currently usable + - `stale` — this MCP server is currently unreachable, but the binding is preserved (it'll work as soon as the server is back) + - `unavailable` — server / skill has been disabled; binding is preserved but the runtime won't surface it to the employee + - `orphan` — references a tool that **no longer exists** (server removed, tool renamed); the save action **rejects** orphan references and forces cleanup +- **Namespace collisions**: when two different MCP servers expose the same tool name (e.g. both have `read_file`), the picker shows the fully prefixed names (`server-a__read_file` / `server-b__read_file`); the employee's system prompt maps them back to the originals so the LLM doesn't get confused +- **Validation on save**: every checked tool runs through `AgentBindingService.validate(...)` — any orphan reference fails save and must be cleared +- **MCP server rename**: bindings tied to a renamed server **follow automatically** (matched via persisted tool cache) — no need to re-tick + +UI: `Agents → pick employee → Tools`. + +Implementation details: see [MCP](./mcp#per-agent-tool-binding). + +### System prompt best practices + +The system prompt is the employee's voice, priorities, and constraints. **Role / Goal / Backstory**, skill instructions, and workspace memory all get automatically appended to the final prompt — you don't write those yourself. + +Your part should cover: + +1. **How they should speak** — tone, style, phrasing preferences ("professional but not stiff" / "stay cautious in customer-facing replies") +2. **What they're allowed and expected to do** — the task boundary +3. **How to behave when uncertain** — "search first, don't make things up" / "ask before running a dangerous command" +4. **Output format** — if you need structure, say so + +Leave out: + +- Tool descriptions — auto-injected +- Workspace memory instructions — they come from `AGENTS.md` +- Framework-specific behavior (tool call format, ReAct structure) — don't fight the runtime + +Example: + +> You are a professional technical documentation assistant. Your responsibilities: +> +> 1. Search and organize technical materials based on user needs +> 2. Answer questions using clear, structured formatting +> 3. Ensure code examples are syntactically correct +> 4. When unsure, search first rather than fabricating information +> +> Guidelines: +> - Cite sources when referencing external information +> - For time-sensitive questions, get the current date before searching + +--- + +## For developers: how the agent actually runs + +If you're just using agents, skip this section. If you're building on top of them — adding nodes, customizing routing, plugging in extensions — go straight to [Architecture](./architecture). The graph topologies, node lists, shared state keys, and extension points all live there. + +--- + +## Lifecycle states + +| State | Meaning | +|-------|---------| +| `IDLE` | Ready for input | +| `PLANNING` | Generating a plan (Plan-and-Execute mode) | +| `EXECUTING` | Running tool calls or sub-tasks | +| `RUNNING` | Active ReAct loop or Plan-Execute graph execution | +| `WAITING_USER_INPUT` | Paused for user response | +| `DONE` | Completed | +| `FAILED` | Execution failed | +| `ERROR` | Error state | + +Why the turn ended: + +| Value | Meaning | +|-------|---------| +| `NORMAL` | LLM gave a direct final answer | +| `SUMMARIZED` | Completed after a context-compression pass | +| `MAX_ITERATIONS_REACHED` | Forced convergence at iteration limit | +| `ERROR_FALLBACK` | Degraded answer after an error | + +--- + +## Reliability features + +These are things the runtime does so agents don't fail in ways you'd have to debug: + +- **Context pruning** — when the context window gets too full, earlier turns get summarized by the LLM and the summary replaces them. Cached for 30 minutes. Injected as a user message, not a system message, to prevent prompt injection from historical content. +- **Thinking recovery** — if a stream breaks mid-response, the partial thinking and content persist and show up when the conversation reloads. +- **Iteration limit handler** — instead of crashing when `max_iterations` is hit, the runtime forces a best-effort summary answer. +- **Stale stream cleanup** — every open SSE stream is tracked, abandoned ones are reaped automatically. +- **429 retry** — LLM rate-limit errors trigger automatic retries with backoff. +- **Repetition detection** — agents looping on the same tool call get forced out. +- **Configurable tool timeouts** — one slow tool can't freeze a turn. +- **Channel health monitor** — failing channel adapters restart with exponential backoff. + +None of these are user-facing buttons. They just happen. + +--- + +## Agent management API + +### Create + +```bash +curl -X POST http://localhost:18088/api/v1/agents \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Tech Assistant", + "description": "A professional technical documentation assistant", + "agentType": "react", + "systemPrompt": "You are a professional technical documentation assistant...", + "maxIterations": 10 + }' +``` + +### List / Get / Update / Delete + +```bash +curl http://localhost:18088/api/v1/agents -H "Authorization: Bearer YOUR_JWT_TOKEN" +curl http://localhost:18088/api/v1/agents/1 -H "Authorization: Bearer YOUR_JWT_TOKEN" + +curl -X PUT http://localhost:18088/api/v1/agents/1 \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -d '{"name":"Tech Assistant v2","maxIterations":15}' + +curl -X DELETE http://localhost:18088/api/v1/agents/1 \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" +``` + +### Streaming chat + +```bash +curl -N "http://localhost:18088/api/v1/agents/1/chat/stream?message=hello&conversationId=default" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" +``` + +--- + +## Debugging + +DEBUG logging in `application.yml`: + +```yaml +logging: + level: + vip.mate.agent: DEBUG + vip.mate.agent.graph: DEBUG +``` + +You'll see node-by-node execution: state transitions, dispatcher routing, iteration counts, tool call arguments and results, Tool Guard check results. + +### Common issues + +| Symptom | Likely cause | +|---------|--------------| +| Agent doesn't respond or times out | Model config wrong, API key invalid, quota exhausted | +| Agent stuck in a loop | `max_iterations` too low, or a tool returning errors repeatedly | +| `MAX_ITERATIONS_REACHED` happening often | Refine the system prompt or raise the limit | +| Tool calls silently failing | Tool Guard is blocking — check `mate_tool_guard_audit_log` | +| Approval-waiting graph won't resume | `toolCallPayload` format mismatch in `chatWithReplay` | + +--- + +## Next + +- [Tools](./tools) — what agents can call +- [Skills](./skills) — how to extend what agents can do +- [LLM Wiki](./wiki) — how knowledge gets read by agents +- [Memory](./memory) — how agents remember across conversations +- [Workflow](./workflow) (1.3.0+) — orchestrate multiple digital employees and system actions into a business process +- [Triggers](./triggers) (1.3.0+) — let events automatically start workflows or agent conversations +- [Architecture](./architecture) — the StateGraph runtime in depth diff --git a/mateclaw-server/src/main/resources/docs/en/ambient-ai.md b/mateclaw-server/src/main/resources/docs/en/ambient-ai.md new file mode 100644 index 00000000..891e2fa0 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/ambient-ai.md @@ -0,0 +1,171 @@ +--- +title: Ambient AI — It Finds You. You Don't Open It. +description: Cron jobs + multi-channel delivery = ambient AI. Push your morning briefing to Feishu, alert your DingTalk when a competitor ships, let AI show up where it's needed without you remembering to ask. +head: + - - meta + - name: keywords + content: ambient AI,proactive AI,cron jobs,multi-channel delivery,Feishu briefing,DingTalk push,Slack bot,scheduled agent,morning briefing +--- + +# Ambient AI + +**It finds you. You don't open it.** + +ChatGPT, Claude, Gemini — every AI assistant waits for you to open it. Open the browser, log in, click into the input box, type, wait. The AI is something you have to walk over to. + +MateClaw isn't. + +You can have any agent show up at any time, in any messenger you use, **and find you**. + +``` +Daily 9:00 AM: Briefing agent → Feishu engineering room +Weekly Mondays 10:00: Sales agent → Slack channel +4:00 AM job failure → Operations agent → DingTalk DM +``` + +This is the upgrade from **conversational AI** to **proactive AI**. We call it **Ambient AI** — it doesn't sit in a browser tab waiting for you. **It lives inside your workflow.** + +--- + +## What's actually happening + +Three things wired together: + +1. **Cron jobs** — a scheduler that knows how to run agents (`mate_cron_job`, `mate_cron_job_run` tables) +2. **The agent runs** — when the trigger fires, the scheduler spins up an agent context and runs the full tool chain (search, scrape, read Wiki, query database, …) +3. **Result is delivered through a channel** — agent output flows through `CronJobCompletedEvent` → `CronDeliveryListener` → `ChannelCronResultDelivery`, landing in the channel you set + +No one is watching. You set it once and the AI starts showing up to work on time. + +--- + +## Three patterns that just work + +### 1. Daily briefing + +> Every day at 9:00 AM, a competitive intelligence agent runs: search for releases shipped yesterday, read 5 target companies' blogs, diff the last 24 hours, distill the three most important things into Markdown, push to the engineering room on Feishu. + +`Cron 0 0 9 * * ?` · `Agent: CompIntel` · `Channel: Feishu — Engineering` + +You sit on the subway, your phone vibrates, you read the three things. Before you reach your desk you already know what the day is about. + +### 2. Weekly digest + +> Every Monday at 10:00 AM, the sales assistant agent queries last week's order database, extracts key metrics, writes a digest with tables, pushes to a Slack channel. + +Data comes through the [datasource tools](./config); the agent writes SQL and explains the result. If past digests live in your Wiki, they're auto-cited as comparison. + +### 3. Event-triggered alerts + +> A new email lands with the "Boss" label → an executive assistant agent → summarizes the email and drafts a reply → pushes to WeChat. + +Event triggers ride a high-frequency cron with a condition check, or wire an external webhook through an [MCP tool](./mcp). The agent runs through the same delivery chain to a channel. + +--- + +## Setting it up + +`Console → Cron Jobs → New` — three steps: + +1. **Cron expression** — when it runs (standard 6-field cron, with a UI builder) +2. **Agent** — pick an agent already configured with the right tools and system prompt +3. **Output channel** — pick a [channel](./channels) for the result (Feishu / DingTalk / Slack / WeCom / Telegram / any configured channel) + +Save. Next time the trigger fires, the agent goes to work. + +::: tip Don't configure 100 crons +Same rule as model management — you don't need 100 scheduled tasks, you need **one that actually helps**. Start with the 9 AM briefing. Run it for two weeks. See what you actually read and what's noise. Then add the second one. +::: + +--- + +## Why this matters + +The big chase across 2025–2026 in AI was the same thing — **make it so you don't have to open a screen.** + +- **Vision Pro** tried to put AI into your field of view. Didn't land. +- **Humane AI Pin** tried to put AI on your body. Folded. +- **Echo / Alexa** tried to put AI in your home. Stalled at "what's the weather." + +All of them tried to do it with new hardware. + +MateClaw's answer is the opposite: **the messengers your team already uses are the hardware.** + +Feishu, DingTalk, WeCom, Slack, Telegram, Discord, QQ — they're already open on your phone and your desktop. AI showing up where you're already looking is enough. **No new device. No new habit.** + +--- + +## What's different from other AI products + +| | Conversational AI | Ambient AI (MateClaw) | +|---|---|---| +| How it triggers | You open it | It shows up at the right time | +| Where it lives | A browser tab | The IM you already use | +| When it talks | Only when asked | When you need it | +| When you're offline | You miss it | Pushes when you come back | +| When something fails | Red error toast | Auto-reconnect, next push lands clean | + +Only MateClaw fills out the right column completely, because only MateClaw has all of these at once: + +- **Multi-agent runtime** (ReAct + Plan-Execute) +- **Cron scheduling + retry** +- **9 IM channel adapters** with exponential-backoff reconnect +- **Persistent memory** ([Memory](./memory) — Dreaming makes it know you better every day) +- **Wiki knowledge layer** ([LLM Wiki](./wiki) — gives the agent something to base research on) +- **Tool Guard** ([Security](./security) — sensitive ops still ask you first) + +Cron is the last piece that ties the rest together — **time-triggered access** to all of it. + +--- + +## Security considerations + +Proactive = stricter permissions needed. A cron-triggered agent runs unattended. Whatever tool permissions it has, it'll use — there's no "let me double-check" moment. + +So: + +- **Cron agents do not bypass Tool Guard.** Tool calls that need approval still pause and wait for you to approve in your IM. See [Approval workflow](./security#审批工作流-人在回路). +- If you don't want to be interrupted, set sensitive tools to `deny` instead of `require_approval` — the agent will stop hard rather than send you an approval request. +- **Every cron run is in the audit log** (`mate_audit_event`) — which task at what time used which tool to do what. + +--- + +## Underlying data (if you're curious) + +| Table | Purpose | +|---|---| +| `mate_cron_job` | One row per scheduled task — agent ID, cron expression, target channel, timeout, enabled flag | +| `mate_cron_job_run` | One row per execution — start/end time, status, output summary, error if any | + +Code: + +- `vip.mate.cron.service.CronJobLifecycleService` — task lifecycle management +- `vip.mate.cron.service.CronJobRunner` — single execution +- `vip.mate.cron.delivery.ChannelCronResultDelivery` — routes agent output to the channel +- `vip.mate.cron.delivery.CronDeliveryListener` — listens to `CronJobCompletedEvent` +- `vip.mate.cron.CronChatOriginFactory` — tags conversations with "this came from cron job X" so sessions stay traceable + +### API + +```bash +# List all cron jobs +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 \ + -H "Authorization: Bearer " + +# View execution history +curl http://localhost:18088/api/v1/cron-jobs/{id}/runs \ + -H "Authorization: Bearer " +``` + +--- + +## Next + +- [Channels](./channels) — where the AI output gets delivered +- [Agents](./agents) — cron schedules whatever agent you've configured +- [Security & Approval](./security) — keep cron from running sensitive ops unattended +- [LLM Wiki](./wiki) — give the cron agent a knowledge base to research with diff --git a/mateclaw-server/src/main/resources/docs/en/api.md b/mateclaw-server/src/main/resources/docs/en/api.md new file mode 100644 index 00000000..54c22ba8 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/api.md @@ -0,0 +1,509 @@ +# API Reference + +Every REST endpoint is prefixed `/api/v1/`. Every response follows the same envelope: + +```json +{ + "code": 200, + "message": "success", + "data": { } +} +``` + +Every endpoint except `/api/v1/auth/login` requires a JWT in the `Authorization` header: + +``` +Authorization: Bearer +``` + +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), [Doctor](./doctor). + +--- + +## 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 +``` + +**Login example:** + +```bash +curl -X POST http://localhost:18088/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"admin123"}' +``` + +Response: + +```json +{ + "code": 200, + "data": { + "token": "eyJhbGciOiJIUzI1NiJ9...", + "tokenType": "Bearer", + "expiresIn": 86400 + } +} +``` + +--- + +## Chat + +``` +POST /api/v1/chat/{agentId}/message # Send a message +GET /api/v1/chat/{agentId}/stream?conversationId= # SSE streaming +POST /api/v1/chat/{conversationId}/stop # Stop an in-flight stream +GET /api/v1/chat/{conversationId}/pending-approvals # List waiting approvals +``` + +**Send message:** + +```bash +curl -X POST http://localhost:18088/api/v1/chat/1/message \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"content":"Hello, what can you do?", "conversationId":"conv-abc123"}' +``` + +**SSE stream example:** + +```bash +curl -N http://localhost:18088/api/v1/chat/1/stream?conversationId=conv-abc123 \ + -H "Authorization: Bearer YOUR_TOKEN" +``` + +Event types and schema are documented in [Chat & Messaging](./chat). + +### Conversations + +``` +GET /api/v1/conversations # List (?page&size&agentId) +GET /api/v1/conversations/{id}/messages # Get messages +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/templates # List templates +POST /api/v1/agents/templates/{id} # Create from template +``` + +--- + +## Tools + +``` +GET /api/v1/tools # List +PUT /api/v1/tools/{id} # Update +PUT /api/v1/tools/{id}/toggle?enabled={bool} # Toggle +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. + +--- + +## 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 +``` + +### 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}/draft # Save draft (graph_json) +POST /api/v1/workflows/{id}/publish # Publish draft as a new revision +DELETE /api/v1/workflows/{id} # Delete + +POST /api/v1/workflows/draft/generate # Natural-language → graph_json draft +POST /api/v1/workflows/{id}/preview-compile # Static checks + Pebble validation, no publish + +POST /api/v1/workflows/{id}/runs # Start a run (async) +GET /api/v1/workflows/{id}/runs # Run list +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 +POST /api/v1/workflows/runs/{runId}/cancel # Cancel in-flight +``` + +--- + +## 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 +DELETE /api/v1/triggers/{id} # Delete +PUT /api/v1/triggers/{id}/toggle?enabled={bool} # Toggle + +POST /api/v1/triggers/events # Generic event ingress (webhook / external bridge) + # ACKs 200 immediately, dispatches asynchronously +GET /api/v1/triggers/{id}/events # Event history for this trigger +``` + +--- + +## 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}/members # List members +POST /api/v1/workspaces/{id}/members # Add member +DELETE /api/v1/workspaces/{id}/members/{userId} # Remove member +PUT /api/v1/workspaces/{id}/members/{userId}/role # Change role +``` + +--- + +## 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 diff --git a/mateclaw-server/src/main/resources/docs/en/architecture.md b/mateclaw-server/src/main/resources/docs/en/architecture.md new file mode 100644 index 00000000..05ae1483 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/architecture.md @@ -0,0 +1,368 @@ +# Architecture + +**How MateClaw is put together, in one page.** + +If you're using MateClaw, read [Introduction](./intro). If you're building on MateClaw — adding tools, new channels, custom memory providers, new agent graph nodes — read this page. + +--- + +## The product in one diagram + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ MateClaw │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐ │ +│ │ Web Console │ │ Desktop App │ │ IM Channels │ │ +│ │ Vue 3 SPA │ │ Electron │ │ DingTalk / Feishu / │ │ +│ │ (src/static) │ │ + Bundled │ │ WeCom / Telegram / │ │ +│ │ │ │ JRE 21 │ │ Discord / QQ / ... │ │ +│ └───────┬──────┘ └──────┬───────┘ └──────────┬──────────┘ │ +│ │ HTTP/SSE │ HTTP/SSE │ SPI │ +│ └────────┬────────┴───────────────────────┘ │ +│ │ │ +│ ┌────────────────▼──────────────────────────────────────────┐ │ +│ │ Spring Boot Backend (vip.mate.*) │ │ +│ │ │ │ +│ │ ┌────────────┐ ┌────────────┐ ┌────────────────────┐ │ │ +│ │ │ auth │ │ channel │ │ agent │ │ │ +│ │ │ (JWT) │ │ adapters │ │ (StateGraph │ │ │ +│ │ └────────────┘ └─────┬──────┘ │ runtime) │ │ │ +│ │ │ │ │ │ │ +│ │ ┌────────────┐ │ │ ┌──────────────┐ │ │ │ +│ │ │ workspace │ │ │ │ ReasoningN │ │ │ │ +│ │ │ isolation │ │ │ │ ActionN │ │ │ │ +│ │ └────────────┘ │ │ │ Observation │ │ │ │ +│ │ │ │ │ PlanGenN │ │ │ │ +│ │ ▼ │ │ StepExecN │ │ │ │ +│ │ ┌──────────────┐ │ │ FinalAnsN │ │ │ │ +│ │ │ Message │ │ └──────────────┘ │ │ │ +│ │ │ Router ├──▶ │ │ │ │ +│ │ └──────────────┘ └──────┼─────────────┘ │ │ +│ │ │ │ │ +│ │ ┌─────────────────────────────────────▼────────────────┐ │ │ +│ │ │ Tool Registry │ │ │ +│ │ │ │ │ │ +│ │ │ built-in @Tool + MCP clients + Skill scripts │ │ │ +│ │ └─────────┬───────────────────────────────────────────┬──┘ │ │ +│ │ │ │ │ │ +│ │ ▼ ▼ │ │ +│ │ ┌──────────────┐ ┌─────────────┐ ┌───────────────────┐ │ │ +│ │ │ Tool Guard │ │ Approval │ │ Audit Log │ │ │ +│ │ │ (rules) │ │ Workflow │ │ Pipeline │ │ │ +│ │ └──────────────┘ └─────────────┘ └───────────────────┘ │ │ +│ │ │ │ +│ │ ┌────────────┐ ┌────────────┐ ┌────────────────────┐ │ │ +│ │ │ memory │ │ wiki │ │ skill │ │ │ +│ │ │ multi-layer │ │ (layered) │ │ (SKILL.md runtime) │ │ │ +│ │ │ SPI │ │ digester │ │ │ │ │ +│ │ └────────────┘ └────────────┘ └────────────────────┘ │ │ +│ │ │ │ +│ │ ┌────────────────────────────────────────────────────┐ │ │ +│ │ │ MyBatis Plus / H2 or MySQL │ │ │ +│ │ │ │ │ │ +│ │ │ mate_agent / mate_message / mate_wiki_* / │ │ │ +│ │ │ mate_tool_guard_* / mate_workspace / ... │ │ │ +│ │ └────────────────────────────────────────────────────┘ │ │ +│ └───────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +One JAR. One process. The whole widget. + +--- + +## Repository layout + +``` +mateclaw/ +├── mateclaw-server/ # Spring Boot backend (the heart) +│ └── src/main/java/vip/mate/ +│ ├── MateClawApplication.java +│ ├── agent/ # StateGraph runtime, nodes, edges, state +│ ├── planning/ # Plan & SubPlan persistence +│ ├── workflow/ # Workflow engine (1.3.0+): DSL compiler, linear runtime, payload spill +│ ├── trigger/ # Trigger engine (1.3.0+): 6 pattern types, event governance, CronDelegationPort +│ ├── tool/ # ToolRegistry, @Tool beans, MCP (per-agent binding), guard +│ ├── approval/ # Approval workflow (also bridges the workflow await_approval pause/resume) +│ ├── skill/ # Dynamic skill packages +│ ├── wiki/ # LLM Wiki + Transformations engine (1.3.0+) +│ ├── memory/ # Memory layers + SPI +│ ├── workspace/ # Workspace entity + conversation + document +│ ├── channel/ # Channel SPI + adapters (1.3.0+: WeCom v2, reply queue, leader lease) +│ ├── llm/ # LLM provider configuration (1.3.0+: multimodal sidecar routing) +│ ├── auth/ # Spring Security + JWT +│ ├── audit/ # Audit event pipeline +│ ├── cron/ # Scheduled job engine (trigger.cron reuses via CronDelegationPort) +│ ├── task/ # Async task runtime +│ ├── dashboard/ # Metrics aggregation +│ ├── datasource/ # External DB connections +│ ├── stt/ # Speech-to-text +│ ├── tts/ # Text-to-speech +│ ├── system/ # System settings, bootstrap, onboarding +│ ├── config/ # Spring configuration +│ ├── common/ # Shared utilities +│ └── exception/ # Global exception handler +│ └── src/main/resources/ +│ ├── application.yml +│ ├── db/migration/ # Flyway migration scripts (h2/ + mysql/) +│ ├── db/data.sql # Seed data +│ ├── prompts/ # LLM prompt templates +│ ├── skills/ # Bundled skill packages +│ └── static/ # Frontend build output +├── mateclaw-ui/ # Vue 3 admin console +├── mateclaw-desktop/ # Electron desktop shell +├── mateclaw-webchat/ # Embeddable chat widget +├── matevip-sites/ # Marketing and docs sites (pnpm workspace) +├── docs/ # This documentation (VitePress) +├── deploy/ # Production deployment configs +├── docker-compose.yml +└── .env.example +``` + +The backend is a **single modular monolith**. Other projects are independent packages that don't share code with the backend. + +--- + +## The agent runtime is a StateGraph + +This is the most important thing to know if you're contributing to the backend. + +**MateClaw's agent runtime is not a class hierarchy.** There's no `BaseAgent` → `ReActAgent` → `MyCustomAgent` inheritance chain. The runtime is a **StateGraph** (from `spring-ai-alibaba-graph`) composed of nodes and conditional edges, assembled at runtime by `AgentGraphBuilder`. + +### Key pieces + +- `agent/graph/StateGraphReActAgent.java` — assembles the ReAct loop +- `agent/graph/plan/StateGraphPlanExecuteAgent.java` — assembles the Plan-and-Execute graph +- `agent/graph/node/` — `ReasoningNode`, `ActionNode`, `ObservationNode`, `FinalAnswerNode`, `SummarizingNode`, `LimitExceededNode` +- `agent/graph/plan/node/` — `PlanGenerationNode`, `StepExecutionNode`, `PlanSummaryNode`, `DirectAnswerNode` +- `agent/graph/edge/` + `plan/edge/` — dispatcher functions that decide the next node based on state +- `agent/graph/state/MateClawStateKeys.java` — the keys for the shared state object +- `agent/graph/state/MateClawStateAccessor.java` — typed accessor for the state map (don't touch the map directly) +- `agent/graph/lifecycle/ReActLifecycleListener.java` — node-level instrumentation hooks +- `agent/AgentGraphBuilder.java` — the builder that wires nodes and edges per agent config +- `agent/GraphEventPublisher.java` + `agent/graph/NodeStreamingChatHelper.java` — how streaming events escape the graph into the SSE stream + +### How to extend + +**Adding agent behavior** — create a new node in `agent/graph/node/` or a new edge dispatcher in `agent/graph/edge/`. Wire it into `AgentGraphBuilder`. Read and write state through `MateClawStateAccessor`. + +**Don't** create a new `XxxAgent` class. You'll be reimplementing what the graph already does. + +### Shared state keys + +| Key | Purpose | +|-----|---------| +| `USER_MESSAGE` | Current user input | +| `MESSAGES` | Conversation messages loaded from `mate_message` | +| `OBSERVATION_HISTORY` | Tool call results in this turn | +| `CURRENT_ITERATION` | How many loops have happened | +| `MAX_ITERATIONS` | The ceiling | +| `TOOL_CALLS` | Current tool call list | +| `AWAITING_APPROVAL` | Set true when a call needs human approval | +| `FINAL_ANSWER` | The agent's response | +| `FINISH_REASON` | Why the graph ended | + +--- + +## Data flow — a single turn + +``` +1. POST /api/v1/chat/{agentId}/message + ↓ +2. ChatController.sendMessage() + ↓ +3. ConversationManager.loadOrCreate(conversationId) + ↓ +4. AgentGraphBuilder.build(agentEntity) ← resolves the compiled graph + ↓ +5. graph.invoke(initialState) ← StateGraph execution begins + ↓ + ReasoningNode → Dispatcher → ActionNode → ObservationNode → (loop or finish) + ↓ +6. Tool calls go through: + ToolRegistry.resolve() → + Tool Guard rule evaluation → + (if approval needed) mate_tool_approval row + SSE event + AWAITING_APPROVAL=true + (if approved inline) ToolExecutionExecutor.execute() → observation + ↓ +7. Segments stream to the client via: + GraphEventPublisher → NodeStreamingChatHelper → SSE stream + ↓ +8. On completion: + FinalAnswerNode aggregates result + ConversationManager persists segments to mate_message + ConversationCompletedEvent published (async memory extraction kicks in) + ↓ +9. Response closes +``` + +--- + +## Extension points + +These are the SPIs and plugin points you can build on: + +### `@Tool`-annotated Spring beans + +Write a `@Component` with `@Tool` methods. It's picked up by `ToolRegistry` on startup. Every `@Tool` method becomes a callable tool. + +```java +@Component +public class MyCustomTool { + @Tool(description = "What the LLM sees") + public String doThing(@ToolParam(description = "...") String input) { + return "result"; + } +} +``` + +### `ChannelAdapter` SPI + +Implement `vip.mate.channel.ChannelAdapter` (or `StreamingChannelAdapter` for streaming). Register as a Spring bean. Add webhook endpoints via `ChannelWebhookController`. See [Channels](./channels). + +```java +public interface ChannelAdapter { + void onMessage(ChannelMessage message); + void sendMessage(String channelId, String content); + String getChannelType(); +} +``` + +### `MemoryProvider` SPI + +Implement `vip.mate.memory.spi.MemoryProvider` to plug in a custom memory backend (vector, graph, external service). Multiple providers can be stacked per agent. See [Memory](./memory). + +### MCP servers + +Connect external tool servers over stdio, streamable_http, or sse. Their tools appear in the tool registry automatically — Agent code doesn't know they're external. See [MCP](./mcp). + +### Skill packages + +Bundle instructions + tools + optional scripts in a `SKILL.md`. Upload via the UI or API. Agents can invoke them at runtime. See [Skills](./skills). + +### Agent graph nodes and edges + +For deeper customization, add a new node in `agent/graph/node/` or a new dispatcher in `agent/graph/edge/`. Wire it into `AgentGraphBuilder` behind a config flag. State access goes through `MateClawStateAccessor`. + +--- + +## Persistence — one schema, two databases + +MateClaw uses **MyBatis Plus** (not JPA) for database access. Conventions: + +- All tables prefixed `mate_` +- `snake_case` columns, `camelCase` Java fields, auto-mapped +- Every table has `create_time`, `update_time`, `deleted` (logical delete) +- **Flyway** manages schema migrations — `db/migration/h2/` and `db/migration/mysql/` hold dialect-specific scripts, auto-selected on startup +- `FlywayRepairConfig` runs `repair()` before `migrate()` on every boot, self-healing checksum drift and partially-failed migrations +- Seed data loaded by `DatabaseBootstrapRunner` from `db/data-*.sql`, idempotent + +### Table groups + +**Identity & config** — `mate_user`, `mate_system_setting`, `mate_model_config`, `mate_model_provider`, `mate_datasource`, `mate_mcp_server` + +**Agents & planning** — `mate_agent`, `mate_agent_skill`, `mate_agent_tool`, `mate_plan`, `mate_sub_plan` + +**Conversation** — `mate_conversation`, `mate_message`, `mate_channel`, `mate_channel_session` + +**Tools & approval** — `mate_tool`, `mate_tool_approval`, `mate_tool_guard_rule`, `mate_tool_guard_config`, `mate_tool_guard_audit_log` + +**Skills & workspace** — `mate_skill`, `mate_workspace`, `mate_workspace_member`, `mate_workspace_file` + +**Knowledge & memory** — `mate_wiki_knowledge_base`, `mate_wiki_raw_material`, `mate_wiki_page`, `mate_wiki_transformation`, `mate_wiki_transformation_run` (1.3.0+), `mate_memory_recall` + +**Workflow & triggers (1.3.0+)** — `mate_workflow`, `mate_workflow_revision`, `mate_workflow_run`, `mate_workflow_step_run`, `mate_workflow_payload`, `mate_trigger`, `mate_trigger_event` + +**Ops** — `mate_cron_job`, `mate_cron_job_run`, `mate_async_task`, `mate_usage_daily`, `mate_audit_event`, `mate_doctor_check` + +--- + +## Streaming — why SSE, not WebFlux + +MateClaw uses **Spring MVC**, not Spring WebFlux. WebFlux is explicitly excluded from the dependency graph. + +Why: Spring MVC + SSE is sufficient for streaming LLM responses to the frontend. It's simpler to reason about, easier to debug, and doesn't force the whole stack to become reactive. + +::: tip Virtual threads (JDK 21) +`spring.threads.virtual.enabled=true` is on. Tomcat request threads, `@Scheduled` tasks, and `@Async` methods all run on virtual threads. SSE long-connections no longer hold platform threads — concurrent connection count is no longer limited by thread pool size. +::: + +Streaming flow: + +1. Client opens `GET /api/v1/chat/{agentId}/stream` with `Accept: text/event-stream` +2. Controller returns `SseEmitter` +3. Agent graph runs on a worker thread; node execution emits events to `GraphEventPublisher` +4. Events serialize into SSE format and write to the emitter +5. `ChatStreamTracker` watches for abandoned streams and cleans them up + +The same SSE pattern is reused by channel adapters that support streaming (DingTalk AI Card, Web). + +--- + +## Frontend architecture + +**Vue 3 + TypeScript + Composition API + ` + + +``` + +--- + +## Testing + +### Backend tests + +```bash +cd mateclaw-server +mvn test # All tests +mvn test -Dtest=StateGraphReActAgentTest # Single class +mvn test -Dtest=StateGraphReActAgentTest#testChat # Single method +``` + +### Frontend type check and lint + +```bash +cd mateclaw-ui +pnpm build # vue-tsc type check + vite build +pnpm lint # ESLint with auto-fix +``` + +### Manual test checklist + +- [ ] Backend starts without errors +- [ ] Frontend builds without type errors (`pnpm build`) +- [ ] Login works with default credentials +- [ ] Model configured via UI +- [ ] Chat streams a response back +- [ ] New feature works as described +- [ ] No console errors +- [ ] Docs updated if user-facing behavior changed + +--- + +## Documentation changes + +If your PR changes user-facing behavior — a new feature, a renamed endpoint, a changed config key — **update the docs in the same PR**. + +The docs live in `docs/`. Pick the relevant page and update both `docs/en/` and `docs/zh/`. The Chinese and English versions are **independently written**, not translations — match tone and style with the existing page. + +```bash +cd docs +pnpm build +``` + +Build must succeed with zero errors before you open the PR. + +--- + +## Pull request process + +1. **Title** — conventional commit format +2. **Description** — what, why, how; link issues +3. **Screenshots** — for UI changes, before/after +4. **Testing** — describe how you tested +5. **Breaking changes** — note clearly at the top + +### PR template + +```markdown +## What + +Brief description of the change. + +## Why + +Why this change is needed (link issue). + +## How + +Technical approach. + +## Testing + +How this was tested. + +## Screenshots (if UI changes) + +Before / After. +``` + +--- + +## Reporting issues + +When filing a bug: + +- MateClaw version (or commit hash) +- Java version and OS +- Exact steps to reproduce +- Expected vs. actual behavior +- Relevant log output + +Good bug reports get good fixes. + +--- + +## Next + +- [Quick Start](./quickstart) — setup walkthrough +- [Introduction](./intro) — architecture overview +- [Architecture](./architecture) — StateGraph deep-dive for developers +- [Roadmap](./roadmap) — what we're working on next diff --git a/mateclaw-server/src/main/resources/docs/en/desktop.md b/mateclaw-server/src/main/resources/docs/en/desktop.md new file mode 100644 index 00000000..36565129 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/desktop.md @@ -0,0 +1,284 @@ +# Desktop App + +**Double-click. Wait thirty seconds. Log in. Use it.** + +That's the desktop app in four sentences. No Java to install. No browser to open. No docker compose file. No port to remember. MateClaw's desktop edition bundles Electron, a JRE 21 runtime, and the packaged Spring Boot server JAR into a single installer. **Your users never know Java is underneath.** + +This page is for people who want to run it, build it, or debug it. + +--- + +## Architecture + +``` +┌──────────────────────────────────────────┐ +│ Electron Shell │ +│ ┌────────────────────────────────────┐ │ +│ │ BrowserWindow (Chromium) │ │ +│ │ ┌──────────────────────────────┐ │ │ +│ │ │ Vue 3 Frontend (dist/) │ │ │ +│ │ │ Element Plus + Tailwind │ │ │ +│ │ └────────────┬─────────────────┘ │ │ +│ └───────────────┼────────────────────┘ │ +│ │ HTTP / SSE │ +│ ┌───────────────▼────────────────────┐ │ +│ │ Spring Boot Backend (child proc) │ │ +│ │ dynamic port on 127.0.0.1 │ │ +│ │ Bundled JRE 21 + H2 file DB │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ electron-updater Auto Update │ │ +│ └────────────────────────────────────┘ │ +└──────────────────────────────────────────┘ +``` + +Three things living inside one process tree: + +1. **Electron main process** — window, tray, IPC, backend lifecycle +2. **BrowserWindow (Chromium)** — renders the Vue 3 frontend (same code as the web version) +3. **Spring Boot backend** — spawned as a child process, listens on localhost only + +The backend picks a **free port dynamically** at startup so you don't collide with anything else on your machine. The frontend queries the main process for the actual port before the first API call. + +### Key features + +- Native window, no browser dependency +- System tray integration for background operation +- **Bundled JRE 21** — users never install Java +- **Auto update** via electron-updater (GitHub Releases) +- **Local-first data** — everything in a user directory +- **Dynamic backend port** — no port collisions +- **UI hot update** — frontend assets can be updated without repackaging the installer +- Cross-platform (macOS, Windows, Linux) + +--- + +## Supported platforms + +| Platform | Architecture | Status | +|----------|-------------|--------| +| macOS | Intel (x64) | Stable | +| macOS | Apple Silicon (ARM64) | Stable | +| Windows | x64 | Stable | +| Linux | x64 | Stable | + +--- + +## Prerequisites (for building, not for running) + +If you're **running** the app: download and install. Full stop. + +If you're **building** the app: + +| Tool | Version | Purpose | +|------|---------|---------| +| Node.js | 18+ | Frontend build + Electron | +| pnpm / npm | 8+ / 9+ | Package manager | +| Java | 21+ | Backend compilation + dev mode (production builds bundle the JRE) | +| Maven | 3.8+ | Backend build | + +--- + +## Module layout + +``` +mateclaw-desktop/ +├── electron/ +│ ├── main/index.ts # Main process — backend lifecycle, auto-update, tray +│ └── preload/index.ts # IPC bridge +├── src/ # Vue 3 renderer source +├── resources/ +│ ├── jre/ # Bundled JRE (per platform/arch) +│ └── app.jar # Packaged Spring Boot backend JAR +├── build/ # App icons +├── electron-builder.json # Packaging config +├── package.json +└── vite.config.ts +``` + +--- + +## Development mode + +```bash +cd mateclaw-desktop +pnpm install +pnpm dev +``` + +In dev mode: + +1. Vite starts the frontend dev server (HMR enabled) +2. Electron main process launches and loads the Vite dev URL +3. Main process spawns the Spring Boot JAR as a child process on a free port +4. Frontend talks to the backend via HTTP/SSE + +Frontend changes trigger HMR. Main-process changes restart Electron. + +--- + +## Production build + +```bash +cd mateclaw-desktop +pnpm build && npx electron-builder --mac # macOS +pnpm build && npx electron-builder --win # Windows +pnpm build && npx electron-builder --linux # Linux +``` + +Output lands in `release/`: + +| Platform | Artifact | Notes | +|----------|----------|-------| +| macOS | `.dmg` + `.zip` | Drag into Applications | +| Windows | `.exe` (NSIS) | Custom install dir | +| Linux | `.AppImage` | Add execute permission and run | + +### Build prerequisites — the full sequence + +```bash +# 1. Build frontend static assets +cd mateclaw-ui +pnpm install && pnpm build + +# 2. Build backend JAR (includes frontend assets in static/) +cd ../mateclaw-server +mvn clean package -DskipTests + +# 3. Copy JAR to desktop resources +cp target/mateclaw-server.jar ../mateclaw-desktop/resources/app.jar + +# 4. Download platform-specific JRE +cd ../mateclaw-desktop +bash scripts/download-jre.sh + +# 5. Build the installer +pnpm build && npx electron-builder +``` + +--- + +## Java backend lifecycle + +The Electron main process manages the Spring Boot backend through Node.js `child_process`: + +1. **Startup** — spawn the JAR using the bundled JRE, hand it a dynamic port, wait for ready +2. **Readiness check** — poll `http://127.0.0.1:{port}` until the backend responds, then load the frontend +3. **Runtime** — frontend communicates via REST + SSE +4. **Shutdown** — graceful shutdown signal (SIGTERM / taskkill), wait for exit, close the window + +If the backend crashes mid-session, the main process notices and shows an error dialog with the log tail. **No blank white window.** + +--- + +## Auto update + +electron-updater integration with GitHub Releases. + +### Flow + +1. On startup, checks GitHub Releases for a new version +2. When one is found, an in-UI notification shows version + changelog +3. On confirmation, downloads with a progress bar +4. Once downloaded, install now / install on next launch +5. App exits, replaces files, restarts + +### Configuration + +```json +{ + "publish": [ + { + "provider": "github", + "owner": "matevip", + "repo": "mateclaw" + } + ] +} +``` + +### UI hot update (no repackage) + +Frontend assets can be **hot-updated independently** — a frontend-only fix doesn't require a new installer. See `mateclaw-desktop/scripts/` and `desktop-ui-hot-update.md` for the hot-update build flow. + +--- + +## Data storage + +| OS | Path | +|-----|------| +| macOS | `~/Library/Application Support/MateClaw/data/` | +| Windows | `%APPDATA%/MateClaw/data/` | +| Linux | `~/.local/share/MateClaw/data/` | + +Logs, workspace files, skill scripts, wiki content all live alongside the database in the same user directory. Back it up before major changes. + +--- + +## `electron-builder.json` reference + +| Setting | Purpose | +|---------|---------| +| `appId` | `vip.mate.mateclaw` — system registration and code signing | +| `productName` | App name in title bar and installer | +| `publish` | Auto-update source (GitHub Releases) | +| `extraResources` | JRE and `app.jar` | +| `mac.target` | `dmg` + `zip`, `arm64` and `x64` | +| `win.target` | `nsis` installer | +| `linux.target` | `AppImage` | +| `mac.hardenedRuntime` | Required for signing + notarization | +| `nsis.oneClick` | `false` — lets users choose install directory | + +--- + +## Environment variables + +The desktop app reads env vars the same way the standalone backend does. But there's an easier way: **configure everything through the Settings page** after launch. API keys go into the encrypted `mate_model_provider` table and stay there. + +--- + +## Troubleshooting + +### Blank window + +1. Backend failed to start — check logs for crash details +2. Port conflict — dynamic port picker handles most cases, restrictive firewalls can break it +3. Bundled JRE corrupted — reinstall +4. Check logs below + +### Code signing warnings + +- **macOS** — right-click → **Open** to bypass Gatekeeper (first launch). Production: Apple Developer certificate + notarization. See `mateclaw-desktop/CODESIGNING.md`. +- **Windows** — SmartScreen warning → **More info → Run anyway**. Production: EV code signing certificate. + +### Desktop app won't start + +1. Installed app bundles JRE — you don't need Java. Dev build from source: verify `java -version` shows 21+. +2. Check logs: + - macOS: `~/Library/Logs/MateClaw/` + - Windows: `%APPDATA%/MateClaw/logs/` + - Linux: `~/.local/share/MateClaw/logs/` +3. Launch from terminal to see console output +4. Confirm backend port isn't blocked + +### WeCom auth popup + +The WeCom QR-code authorization flow **must open in an in-app popup** (not the system browser) so the `postMessage` callback works. MateClaw handles this in `setWindowOpenHandler` — `work.weixin.qq.com` domain opens as an in-app popup window. + +--- + +## Notes + +- First launch takes 10–30 seconds (database init) +- Closing the window doesn't stop the background service — use the system tray menu to fully quit +- Back up the user data directory regularly +- Bundled JRE makes the installer 80–120 MB + +--- + +## Next + +- [Quick Start](./quickstart) — fastest path through the desktop experience +- [Configuration](./config) — runtime settings +- [Admin Console](./console) — the UI inside the Electron window diff --git a/mateclaw-server/src/main/resources/docs/en/docker-deploy.md b/mateclaw-server/src/main/resources/docs/en/docker-deploy.md new file mode 100644 index 00000000..051ceae3 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/docker-deploy.md @@ -0,0 +1,310 @@ +# Docker Deployment + +The only recommended production deployment outside the desktop app. One `docker compose up -d` brings up three containers: MySQL, SearXNG, and mateclaw-server. + +This page covers **requirements, steps, verification, and common gotchas**. For the full environment variable reference, see [Configuration](./config). + +--- + +## Prerequisites + +| Item | Minimum | Recommended | Notes | +|---|---|---|---| +| Docker Engine | 24.0+ | latest stable | `docker --version` | +| Docker Compose | v2.20+ | v2.30+ | `docker compose version` (the v2 plugin, not the legacy `docker-compose`) | +| Host RAM | 4 GB | 8 GB+ | Chromium consumes 1-2 GB when the browser tool is active | +| Disk | 6 GB | 20 GB+ | ~2 GB image + MySQL data + workspace files | +| /dev/shm | default | compose sets 2 GB automatically | Chromium uses shared memory for rendering; the 64 MB default causes SIGBUS | +| Network | outbound | — | for pulling images and calling LLM APIs | + +**Not required on the host**: Java, Node, Maven, Chrome, or Python — all live inside the image. + +--- + +## The three containers + +| Service | Image | Role | Exposed port | +|---|---|---|---| +| `mysql` | `mysql:8.0` | Business data | `3306` | +| `searxng` | Built from `./docker/searxng/` | Keyless search fallback | `8088` | +| `mateclaw-server` | Built from `mateclaw-server/Dockerfile` | Spring Boot backend + embedded browser | `18080` | + +--- + +## SearXNG search service + +### Why we build a custom image + +`docker/searxng/Dockerfile` derives from upstream `searxng/searxng:latest` and **bakes our own `settings.yml` into `/etc/searxng/settings.yml`**. This isn't polish — it's mandatory: + +- **Upstream ships with only `html` output enabled**, while mateclaw calls `GET /search?q=...&format=json`. The default image responds to JSON requests with an HTML error page, `SearXNGSearchProvider` fails to parse it, returns empty results, and the UI shows "search temporarily unavailable". +- **Upstream enables the anti-bot Limiter plugin by default**, which rejects server-side calls (no JS, no cookies) with HTTP 429. + +Our `docker/searxng/settings.yml` changes three things: + +1. `search.formats: [html, json]` — enable JSON output +2. `server.limiter: false` — disable anti-bot rate limiting +3. Trim the engine list to a reliable subset (DuckDuckGo / Bing / Brave / Wikipedia / Google / Startpage), dropping the dozens of niche engines the upstream enables + +**Do not** switch this to a host bind-mount. An earlier version did, and deploys where the host directory didn't exist got an auto-created empty directory that shadowed the file — SearXNG started with no config at all. To tweak settings.yml, edit `docker/searxng/settings.yml` then: + +```sh +docker compose build searxng +docker compose up -d searxng +``` + +### Search provider fallback chain + +The backend `SearchProviderRegistry` picks a provider in this order: + +1. Whatever the user explicitly set under `Settings → Search` (the `searchProvider` setting) +2. Walk `autoDetectOrder`, **preferring paid providers whose API key is configured** (Serper order=1, Tavily order=2) +3. Fall back to keyless — SearXNG (order=50) wins over DuckDuckGo (order=100) + +On a fresh container with no API keys configured at all, **SearXNG handles every search call**. + +### Verifying the SearXNG path + +```sh +# 1. Hit the container directly +curl -s 'http://localhost:8088/search?q=test&format=json' | head -5 +# Expect: {"query": ..., "results": [...]} +# If you get HTML back, settings.yml didn't take effect. + +# 2. Hit it from inside the mateclaw-server container +docker exec mateclaw-server wget -qO- 'http://searxng:8080/search?q=test&format=json' | head -5 +# If this fails, compose networking is the problem. + +# 3. Ask an agent to search and tail backend logs +docker compose logs -f mateclaw-server | grep "搜索 provider" +# Expect: 搜索 provider 解析: searxng (source=keyless-fallback) +``` + +### Using an external SearXNG instance + +If you're already running SearXNG elsewhere, point mateclaw at it via `.env`: + +```properties +SEARXNG_BASE_URL=https://your-searxng.example.com +``` + +Then comment out the `searxng` service block in `docker-compose.yml`. Make sure **your external instance has the same JSON + Limiter settings** — otherwise you'll hit the same silent failure mode. + +--- + +## Browser automation + +### What the image actually contains + +The backend runtime stage (`mateclaw-server/Dockerfile` stage 3) is based on `mcr.microsoft.com/playwright:v1.52.0-noble` (Ubuntu Noble 24.04, glibc) and installs on top of it: + +- `openjdk-21-jre-headless` — runs the Spring Boot JAR +- `fonts-noto-cjk` — Chinese/Japanese/Korean rendering in screenshots +- `fonts-noto-color-emoji` — emoji glyphs +- `tzdata` — `Asia/Shanghai` timezone + +Microsoft's base image already ships all three browsers in `/ms-playwright/`: + +- `chromium-XXXX/chrome-linux/chrome` — the primary +- `firefox-XXXX/firefox/firefox` +- `webkit-XXXX/pw_run.sh` + +Plus every system library Chromium needs (`libnss3`, `libgbm1`, `libasound2`, `libx11-xcb1`, `libxkbcommon`, …). **No `playwright install` is required, and the Alpine-vs-musl incompatibility that blocks most Playwright deployments is sidestepped entirely.** + +The Dockerfile sets one environment variable explicitly: + +```dockerfile +ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright +``` + +This tells Playwright Java to use the pre-installed browsers and **not** try to download to `$HOME/.cache/ms-playwright` at runtime. + +### BrowserLauncher's 7-strategy fallback + +`vip.mate.tool.browser.BrowserLauncher` tries each strategy in order until one succeeds: + +1. `CONFIG_CDP` — if `MATECLAW_BROWSER_CDP_URL` is set, attach to that running Chrome +2. `CONFIG_PATH` — if `MATECLAW_BROWSER_CHROME_PATH` or `CHROME_PATH` env is set, use that exe +3. `CONFIG_CHANNEL` — if `MATECLAW_BROWSER_CHANNEL=chrome|msedge`, use the Playwright channel +4. `AUTO_CHANNEL` — try `chrome` then `msedge` channel (this step always wins inside the Docker image) +5. `AUTO_PATH` — scan standard install paths (`/usr/bin/google-chrome`, `chromium-browser`, `/snap/bin/chromium`, `microsoft-edge`, `brave-browser`) +6. `BUNDLED` — Playwright bundled Chromium (also guaranteed to work inside the image) +7. `EXTERNAL_CDP` — last resort: fork a system Chrome with `--remote-debugging-port=0`, parse stderr for the DevTools URL, attach via `connectOverCDP` (the openfang pattern) + +Inside the Docker image, **strategy 4 or 6 always hits** and no configuration is needed. If you need to attach to an external Chrome, use strategy 1. If you want a specific host-installed Chrome, use strategy 2. + +### `/dev/shm` must be 2 GB + +`docker-compose.yml` sets `shm_size: 2gb` for `mateclaw-server`. Docker defaults to 64 MB per container — Chromium uses shared memory for GPU compositing and page rendering, and three tabs is enough to SIGBUS the browser. Playwright surfaces this as `TargetClosedError: Target page, context or browser has been closed`. **Do not shrink this value.** + +### SSRF protection + +Before any `navigate` call, `BrowserUseTool` runs the URL through `UrlSafetyChecker`, which **hard-blocks** these hosts: + +- `localhost`, `127.0.0.1`, `::1`, `0.0.0.0` +- `169.254.169.254` (AWS / GCP / Azure IMDS), `100.100.100.200` (Alibaba Cloud IMDS), `192.0.0.192` (Azure IMDS alternative) +- All link-local / private / multicast IP ranges + +An LLM generating a malicious URL to dump cloud credentials is therefore a closed loop. If you genuinely need to scrape internal infrastructure from a specific host, either disable via `mateclaw.browser.ssrf-check-enabled` or edit the `UrlSafetyChecker` allowlist. **Think twice before doing this in production.** + +### Verifying the browser path + +```sh +# 1. Pre-flight diagnosis (doesn't actually launch a browser) +curl -s http://localhost:18080/api/v1/system/browser-health | jq . +# Expect: overall: "healthy", system.browsers found with chromium path + +# 2. Drive it from an agent +# browser_use(action="diagnose") # returns the strategy-chain trace +# browser_use(action="start") # actually launches +# browser_use(action="open", url="https://example.com") +# browser_use(action="screenshot") # returns a base64 PNG +``` + +--- + +## First deployment + +```sh +git clone https://github.com/matevip/mateclaw.git +cd mateclaw + +# 1. Fill in required values +cp .env.example .env +vi .env # see table below +``` + +**Required** (compose refuses to start without these, so you can't accidentally ship default passwords): + +| Variable | Notes | +|---|---| +| `DB_PASSWORD` | App DB password — 16+ chars, mixed case, digits, symbols | +| `DB_ROOT_PASSWORD` | MySQL root password — **must differ from the above** | + +**Strongly recommended** (not enforced, but startup logs WARN if missing): + +| Variable | Notes | +|---|---| +| `JWT_SECRET` | JWT signing key — generate with `openssl rand -base64 48` | +| `MATECLAW_CORS_ALLOWED_ORIGINS` | Production allowlist, e.g. `https://mateclaw.example.com` | + +Then bring the stack up: + +```sh +docker compose up -d --build # first build takes 3-10 minutes +docker compose logs -f mateclaw-server +``` + +First boot runs Flyway migrations (~5 s) and seeds default data (~3 s), then binds `0.0.0.0:18080`. + +Open `http://localhost:18080`, sign in as `admin / admin123`, and **change the password immediately** under `Settings → Security`. + +--- + +## Build-time performance + +### US / EU servers + +**Already optimal.** `mateclaw-server/pom.xml` lists repositories in the order `Maven Central → Google CDN → Aliyun`; Central direct is fastest over US/EU backbones. + +### China servers + +Flip to Aliyun-first. Either edit the `mvn` lines in `mateclaw-server/Dockerfile` to add `-Paliyun-first`, or (easier) expose it as a build arg: + +```dockerfile +# from +RUN mvn dependency:go-offline -q +RUN mvn package -DskipTests -q + +# to +ARG MAVEN_PROFILE= +RUN mvn dependency:go-offline -q ${MAVEN_PROFILE:+-P${MAVEN_PROFILE}} +RUN mvn package -DskipTests -q ${MAVEN_PROFILE:+-P${MAVEN_PROFILE}} +``` + +Then: + +```sh +docker compose build --build-arg MAVEN_PROFILE=aliyun-first mateclaw-server +``` + +Aliyun's public + Spring mirrors are promoted to the top of the lookup chain, keeping traffic inside China. + +--- + +## Optional overrides + +All can be set in `.env` and are read as environment variables. **Leave them empty to accept the container defaults.** + +| Variable | Default | Purpose | +|---|---|---| +| `SERPER_API_KEY` | — | Google Serper search API (paid, best quality) | +| `SEARXNG_SECRET` | built-in dev secret | Only fill when exposing port 8088 to the public internet | +| `SEARXNG_BASE_URL` | `http://searxng:8080` | Point at an external SearXNG instance | +| `MATECLAW_BROWSER_CDP_URL` | — | Attach to an external Chrome CDP sidecar | +| `MATECLAW_BROWSER_CHROME_PATH` | — | Override the bundled Chromium with a host-installed browser | +| `MATECLAW_BROWSER_CHANNEL` | — | Force a Playwright channel (`chrome`, `msedge`, ...) | + +**LLM API keys (DashScope, OpenAI, Anthropic, DeepSeek, Kimi, etc.) are not read from `.env`** — add them after startup in the UI under `Settings → Models → Add Provider`. Hot-reload supported. The container starts with **zero LLM keys configured**; just log in and add your first provider on the Models page. + +--- + +## Verification + +Run these in order after `docker compose up -d`: + +```sh +# 1. All three containers healthy +docker compose ps + +# 2. Base health check +curl -s http://localhost:18080/api/v1/system/health | jq . + +# 3. Browser tool self-diagnosis (the most common failure point on Linux hosts) +curl -s http://localhost:18080/api/v1/system/browser-health | jq . +# Expect overall: "healthy" + +# 4. SearXNG returns JSON (not an HTML error page) +curl -s 'http://localhost:8088/search?q=hello&format=json' | head -5 +``` + +If any of these fail, jump to the next section. + +--- + +## Common gotchas + +**Build stage `mvn dependency:go-offline` hangs** +US servers pulling through Aliyun is slow. The default `pom.xml` puts Maven Central first, so it should be fast. If it's still slow, the container has no outbound access — check your egress firewall. + +**`mateclaw-server` stays unhealthy at startup** +`docker compose logs mateclaw-server` and look for Flyway migration errors. Nine times out of ten, a special character in `DB_PASSWORD` got eaten by the shell — wrap the value in double quotes in `.env`. + +**Browser tool reports "Target page closed" or SIGBUS** +`shm_size: 2gb` didn't take effect. Check the actual value with `docker inspect mateclaw-server | grep ShmSize`. Upgrade Docker Engine to 24.0+ if it's still showing 64 MB. + +**Search returns "Search temporarily unavailable"** +SearXNG either isn't up or the image default settings disabled JSON output. Our own `./docker/searxng/` build patches this; if you're reusing an old named volume, reset it: `docker compose down -v searxng && docker compose up -d searxng`. + +**LLM responses show tofu boxes (□) for Chinese** +The image already installs `fonts-noto-cjk` and `fonts-noto-color-emoji`, so this isn't a server-side font issue. Check your frontend browser's locale / font settings. + +--- + +## Upgrading + +```sh +git pull +docker compose build mateclaw-server # only rebuild the backend +docker compose up -d mateclaw-server +``` + +The `mysql_data` volume persists across rebuilds. Flyway runs incremental migrations automatically and self-heals checksum changes on restart. **Version is pinned in `mateclaw-server/pom.xml` and the git tag** — prefer pinning to a tag in production, not tracking `dev`. + +--- + +## Next steps + +- [Configuration](./config) — every environment variable and runtime toggle +- [Doctor Health Check](./doctor) — the in-app diagnostics page +- [Security & Approval](./security) — pre-production hardening checklist diff --git a/mateclaw-server/src/main/resources/docs/en/doctor.md b/mateclaw-server/src/main/resources/docs/en/doctor.md new file mode 100644 index 00000000..1b91c503 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/doctor.md @@ -0,0 +1,233 @@ +# Doctor + +**The Doctor page answers one question: is this thing actually working right now?** + +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 with `Settings → Doctor` or just navigate to `/doctor`. + +--- + +## 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 +``` + +--- + +## Reading results + +Each check returns: + +```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" +} +``` + +The UI renders: + +- **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 + +--- + +## Fix buttons + +For actionable checks, the Doctor row includes a **Fix** button that navigates directly to the relevant settings page: + +- 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 + +```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" \ + -H "Authorization: Bearer " +``` + +--- + +## Using Doctor in operations + +### As a health endpoint for uptime monitoring + +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 diff --git a/mateclaw-server/src/main/resources/docs/en/faq.md b/mateclaw-server/src/main/resources/docs/en/faq.md new file mode 100644 index 00000000..1f09792f --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/faq.md @@ -0,0 +1,418 @@ +# FAQ + +Common questions and real answers. If your question isn't here, check the relevant feature page or open a [GitHub issue](https://github.com/matevip/mateclaw/issues). + +--- + +## Installation & setup + +### What Java version do I need? + +**Java 17 or higher.** MateClaw uses features introduced in Java 17 (sealed classes, text blocks, records, pattern matching). Verify with `java -version`. + +If you're using the desktop app, **you don't need Java installed at all** — the installer bundles JRE 21. + +### Do I need a cloud API key to start? + +No. Three keyless paths: + +- **Ollama** — local GPU inference; MateClaw auto-detects it on `localhost:11434` at startup +- **ChatGPT OAuth** — if you have a ChatGPT Plus or Pro subscription, log in through the browser flow — your subscription is used directly, no API key needed +- **OpenRouter free tier** — 200+ free models, one OpenRouter key gives you access + +**You also don't need to set any API key as an environment variable to start MateClaw.** All provider configuration is done through the UI at `Settings → Models` after startup. + +### How do I get a DashScope API key? + +1. Go to the [Alibaba Cloud DashScope console](https://dashscope.console.aliyun.com/) +2. Sign up or log in +3. Create an API key +4. In MateClaw, go to `Settings → Models → DashScope` and paste it + +### The backend won't start — port 18088 is in use + +Either stop the other process or change the port: + +```bash +mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=19090" +``` + +The **desktop app picks a free port dynamically**, so you don't see this error there. + +### H2 database lock error on startup + +```bash +rm -f data/mateclaw.mv.db.lock +``` + +Or wipe the data directory to start fresh: + +```bash +rm -rf data/ +``` + +--- + +## Authentication + +### What are the default credentials? + +Username `admin`, password `admin123`. **Change it immediately in any real deployment.** + +### My JWT token keeps expiring + +MateClaw implements **sliding-window renewal** — when a token is within 25% of expiry, the server issues a new one in the `X-New-Token` response header. The frontend handles this automatically. + +If you're calling the API manually (curl, Postman), read the `X-New-Token` header and use the new value for subsequent requests. + +### How do I change the admin password? + +Through `Settings → Security` in the UI is the easiest path. Or directly in the database (BCrypt-encoded): + +```sql +UPDATE mate_user SET password = '$2a$10$...' WHERE username = 'admin'; +``` + +--- + +## Models + +### How do I configure models? + +**All through the UI.** `Settings → Models → Add Provider`. Pick a provider, paste your API key (or OAuth in for ChatGPT Plus, or skip for Ollama), save, test. Model configuration is 100% UI-driven — no `spring.ai.*` YAML blocks to edit. + +LLM API keys are not read from environment variables — setting `DASHSCOPE_API_KEY` and friends has no effect. The container starts with zero providers; sign in and add the first one in the UI. + +### How do I use GPT-4 with MateClaw? + +`Settings → Models → Add Provider`. Either paste your OpenAI API key, or use **OpenAI OAuth** if you have ChatGPT Plus/Pro — a browser window opens for you to log in. After saving, pick `gpt-4o` (or whichever model) from the model picker. + +### Ollama models are slow + +Local model performance depends on hardware: + +- Use smaller models (7B instead of 14B) on less RAM +- Ensure Ollama has GPU access (`ollama ps` should show GPU) +- Increase Ollama's memory limit if available +- `qwen2.5:7b` or `qwen3:latest` is a good speed/quality balance + +### Can I use multiple providers at once? + +Yes. Configure multiple providers and assign different model configs to different agents. Each agent can use its own model — or inherit the global default. Switch the global active model at runtime without restart. + +### How do I pick a cheap model for some agents and a reasoning model for others? + +- Set the **globally active model** to your cheap general-purpose one (e.g., `qwen-plus`, `gpt-4o-mini`) +- Per-agent override: on a reasoning-heavy agent, bind it to `o3` or `qwen-max` specifically +- The grouped model picker in chat lets you switch per-conversation too + +--- + +## Tools & search + +### How do I switch the search provider? + +`Settings → System → Search Service`. Pick from Serper, Tavily, DuckDuckGo, or SearXNG. Enable **fallback** so failures fall through the chain. Takes effect immediately. + +Keyless options (DuckDuckGo, SearXNG) let you have working web search without any API keys. + +### How do I add a custom tool? + +Write a Spring `@Component` with `@Tool`-annotated methods: + +```java +@Component +public class MyCustomTool { + + @Tool(description = "Get weather information") + public String getWeather(@ToolParam(description = "City name") String city) { + return "Sunny, 25C"; + } +} +``` + +Auto-registered on startup. See [Tools](./tools). + +**If the tool does anything dangerous, add a Tool Guard rule for it.** + +### WebSearchTool returns empty results + +Configure a search provider in `Settings → System → Search Service`. Keyless options (DuckDuckGo, SearXNG) work without API keys. + +### Tool Guard keeps blocking my tool calls + +This is **by design** — dangerous tools require approval. Three ways to loosen it: + +1. **Add a specific allow rule** for the exact pattern you need (`Settings → Security & Approval → Tool Guard Rules`). Example: `ShellExecuteTool` with arg pattern `^(ls|cat|grep|find)\s` → `allow`. +2. **Lower the default policy** in `application.yml`: + ```yaml + mateclaw: + tool: + guard: + default-policy: allow # Not recommended in production + ``` +3. **Disable Tool Guard entirely** (only for dev): + ```yaml + mateclaw: + tool: + guard: + enabled: false + ``` + +**Production-safe:** keep `default-policy: require_approval` and add targeted allow rules for specific patterns you trust. + +### How do I configure MCP servers? + +`Tools → MCP Servers` in the UI. Three transport modes: stdio, streamable_http, sse. Config changes take effect without restart. See [MCP](./mcp). + +--- + +## LLM Wiki + +### What's the difference between Wiki and Memory? + +**Wiki is deliberate. Memory is passive.** + +- **Wiki** — you drop documents in, the system digests them into structured pages, agents read those pages. You build it. You edit it. You review it. +- **Memory** — built automatically as a byproduct of conversations. Agent extracts what seems memorable, consolidates patterns nightly. + +Wiki for **source material you want to make queryable** (product specs, design docs, past decisions). Memory for **context that accumulates** (your preferences, what you're working on). + +### Why does the agent still guess things when it has a knowledge base? + +Because you haven't bound the agent to the KB. `Agents → [your agent] → Knowledge` — bind the KB there. Until then, the wiki tools don't get injected. + +### Digestion is slow + +Tune `mate.wiki.digestion-concurrency` in `application.yml`. Default is 2 — bump to 4 or 8 if your LLM quota allows. + +--- + +## Memory + +### Memory is not working + +1. **Confirm auto-extraction is enabled** — check `mate.memory.auto-summarize-enabled` in config +2. **Verify conversation meets thresholds** — `min-messages-for-summarize` (default 4), `min-user-message-length` (default 10) +3. **Check cooldown** — same agent can't trigger extraction more than once every `cooldown-minutes` (default 5) +4. **Read the logs** — `vip.mate.memory` at DEBUG level shows every attempt + +### Memory consolidation tasks aren't running + +Consolidation is driven by seed data in `mate_cron_job`, scheduled for 2 AM daily per agent. Check: + +- Is `enabled` set to `1`? +- Are seed cron jobs present? (`SELECT * FROM mate_cron_job WHERE task_type = 'memory_emergence'`) + +### I don't like what the agent remembered about me + +Edit `PROFILE.md` or `MEMORY.md` directly in the agent workspace view. Lock pages you've edited. See [Memory](./memory). + +--- + +## Approvals + +### 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}`) +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 + +### I want to batch-approve future tool calls from this agent + +You want an **allow rule**, not a blanket approval. `Settings → Security & Approval → Tool Guard Rules → Add Rule`. + +### How long do pending approvals stay pending? + +Default 10 minutes, then they expire and become `rejected`. Configure with `mateclaw.tool.guard.approval-timeout-seconds`. + +--- + +## Agents + +### Agent stuck in RUNNING state + +Common causes: + +1. **Tool call timeout** — a tool is waiting for external service that's hung +2. **Max iterations exceeded** — `MAX_ITERATIONS_REACHED` handler forces a best-effort answer +3. **Awaiting approval** — Tool Guard paused execution +4. **Look at the logs**: + ```bash + mvn spring-boot:run -Dspring-boot.run.arguments="--logging.level.vip.mate.agent=DEBUG" + ``` + +### How do I tell if my agent is using the right tools? + +Expand the chat interface's **thinking panel**. You see every tool call, arguments, and result. If the agent is calling the wrong tool, tighten the system prompt. + +--- + +## Channels + +### DingTalk / Feishu webhook isn't receiving messages + +1. Server not publicly reachable +2. HTTPS required +3. Wrong verification token +4. Bot not added to group or missing permissions + +**Easier:** use **stream / long-connection / WebSocket mode** instead of webhook. DingTalk Stream, Feishu WebSocket, Telegram Long-Polling, Discord Gateway, Slack Socket mode — none need a public IP. + +### Can I use multiple channels at once? + +Yes. Each channel is independent and binds to one agent. Run a web console, DingTalk bot, and Telegram bot simultaneously, all with different agents (or the same one — your call). + +### Telegram / Discord can't reach the API (China network) + +Configure `http_proxy` in the channel config: + +```json +{ + "bot_token": "...", + "http_proxy": "http://127.0.0.1:7890" +} +``` + +--- + +## Data backup + +### How do I back up my data? + +**H2 (development / desktop):** stop, copy `./data/mateclaw.mv.db`: + +```bash +cp ./data/mateclaw.mv.db ./backup/mateclaw-$(date +%Y%m%d).mv.db +``` + +**MySQL (production):** + +```bash +mysqldump -u root -p mateclaw > mateclaw-backup-$(date +%Y%m%d).sql +``` + +**Docker:** + +```bash +docker exec mateclaw-mysql mysqldump -u root -p${MYSQL_ROOT_PASSWORD} mateclaw > backup.sql +``` + +**Desktop** data lives in the per-user directory: + +- macOS: `~/Library/Application Support/MateClaw/` +- Windows: `%APPDATA%/MateClaw/` +- Linux: `~/.local/share/MateClaw/` + +--- + +## Desktop app + +### Desktop app won't start + +The installer bundles JRE 21. Check the logs: + +- macOS: `~/Library/Logs/MateClaw/` +- Windows: `%APPDATA%/MateClaw/logs/` +- Linux: `~/.local/share/MateClaw/logs/` + +Try launching from a terminal. On Windows, right-click → Unblock. On macOS, allow the unsigned app in System Settings → Privacy. + +### How do I update the desktop app? + +**Auto-updates** via electron-updater. On startup, checks GitHub Releases and prompts you when a new version is available. Manual download also available from [Releases](https://github.com/matevip/mateclaw/releases). + +--- + +## Docker + +### Docker containers fail to start + +```bash +docker compose logs mateclaw-server +docker compose logs mateclaw-mysql +``` + +Common: + +- MySQL not ready yet +- Port conflicts (18080, 3306) +- Missing `.env` — copy from `.env.example` + +### How do I access the database in Docker? + +```bash +docker exec -it mateclaw-mysql mysql -u root -p mateclaw +``` + +--- + +## Debugging + +### How do I enable DEBUG logging? + +```yaml +logging: + level: + vip.mate: DEBUG + vip.mate.agent: DEBUG + vip.mate.agent.graph: DEBUG + org.springframework.ai: DEBUG +``` + +Or: + +```bash +mvn spring-boot:run -Dspring-boot.run.arguments="--logging.level.vip.mate=DEBUG" +``` + +### How do I access the H2 console? + +1. Visit `http://localhost:18088/h2-console` +2. JDBC URL: `jdbc:h2:file:./data/mateclaw` +3. Username: `sa` +4. Password: (empty) + +**Disable in production.** + +### How do I inspect SSE streaming events? + +Browser DevTools → Network → filter `EventStream`. Or: + +```bash +curl -N -H "Authorization: Bearer " \ + "http://localhost:18088/api/v1/chat/1/stream?conversationId=1" +``` + +--- + +## Frontend + +### Frontend shows a blank page after build + +```bash +cd mateclaw-ui +pnpm build +ls ../mateclaw-server/src/main/resources/static/ +# Should contain index.html and asset files +``` + +### Dark mode isn't persisting + +Stored in `localStorage`. Clearing browser data wipes it. + +### The UI feels sluggish + +- Turn logs back to INFO +- Check `java -Xmx` settings +- Click **Clear messages** on old conversations + +--- + +## Next + +- [Quick Start](./quickstart) — setup walkthrough +- [Configuration](./config) — full configuration reference +- [Contributing](./contributing) — how to report bugs and request features +- [GitHub Issues](https://github.com/matevip/mateclaw/issues) — when the docs don't answer your question diff --git a/mateclaw-server/src/main/resources/docs/en/index.md b/mateclaw-server/src/main/resources/docs/en/index.md new file mode 100644 index 00000000..cbd92c6d --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/index.md @@ -0,0 +1,44 @@ +--- +layout: home + +hero: + name: MateClaw + text: The personal AI your IT department can actually sign off on. + tagline: Other personal AI agents are built for one person. MateClaw is built for a team — multi-user workspaces, approval-gated sensitive actions, full audit trail, production-grade health monitoring. One JAR on your own machine. Zero data egress. + image: + src: /logo.png + alt: MateClaw + actions: + - theme: brand + text: Get Started → + link: /en/quickstart + - theme: alt + text: Read the Docs + link: /en/intro + - theme: alt + text: GitHub + link: https://github.com/matevip/mateclaw + +features: + - icon: 🧑‍💼 + title: Digital employees, not chatbots + details: You hire coworkers, not a chat box. Each one has a role, a goal, a backstory, a pixel-art avatar, and a color of their own — five career templates ship ready to use. ReAct + Plan-and-Execute, parallel delegation between employees. + - icon: 🧩 + title: Skills are the skeleton, not a plugin + details: One SKILL.md plus one LESSONS.md that grows with use. Eight starter templates, a five-step creation wizard, pre-flight checks before install. MCP and ACP bridges — even Claude Code and Codex show up as employees. + - icon: 📚 + title: Knowledge, shaped + details: The LLM Wiki digests raw files into structured pages with summaries and backlinks. A library you can read, not a vector store you query. The hot cache auto-injects into your employees' system prompts. + - icon: 🧬 + title: Memory that compounds + details: Session context, post-chat extraction, workspace files, and scheduled Dreaming consolidation. Tomorrow's conversation starts where today's ended. + - icon: 👀 + title: You see what every employee is doing + details: The Admin Runtime Console shows who's running, what step they're on, how many tokens they've used, with a one-click force-recycle when stuck. Streaming is staged honestly, multi-agent delegation no longer fights itself, long tasks demand evidence-grounded answers. + - icon: 🔀 + title: Business processes, not manual hand-offs + details: Workflows compose multiple employees plus system actions (approvals, channel dispatch, write-memory) into a publishable, triggerable, replayable linear DSL — seven step modes. Triggers wire system events to those flows — six pattern types cover cron, webhooks, channel messages, employee lifecycle, content match, and workflow completion. + - icon: 🌐 + title: Every surface that matters + details: Web console, desktop app with bundled JRE 21, and eight chat channels. Same brain, same memory, wherever your team works. +--- diff --git a/mateclaw-server/src/main/resources/docs/en/intro.md b/mateclaw-server/src/main/resources/docs/en/intro.md new file mode 100644 index 00000000..a4c32838 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/intro.md @@ -0,0 +1,90 @@ +--- +title: MateClaw Introduction — Self-hosted Multi-Agent AI Operating System +description: MateClaw is an open-source multi-agent AI OS built on Spring AI Alibaba. ReAct + Plan-and-Execute engines, LLM Wiki knowledge base, 4-layer memory lifecycle, MCP tool protocol, 8-channel integration. One JAR, zero data egress. +head: + - - meta + - name: keywords + content: MateClaw,multi-agent AI,self-hosted AI,AI operating system,Spring AI Alibaba,ReAct,Plan-and-Execute,MCP,LLM Wiki,memory lifecycle,Tool Guard,open source +--- + +# MateClaw — Self-hosted Multi-Agent AI Operating System + +**Your multi-agent AI. On your hardware. Under your rules.** + +MateClaw is a full AI operating system you deploy yourself. One JAR. One login. Your data never leaves the room. + +**Three things it does that other AI products can't:** + +**Proactive** — It shows up when it's needed. Push the morning briefing to Feishu at 9 AM. Alert your DingTalk when a competitor ships. **Not waiting in a browser tab.** → [Ambient AI](./ambient-ai) + +**It dreams** — While you sleep, a Dreaming pass consolidates the day's scattered conversations into a coherent understanding of you, writing it into `MEMORY.md`. The next morning **it picks up where yesterday ended** — not from scratch. → [Memory](./memory) + +**Asks before it acts** — When the agent wants to delete a file, send an email, or write to the database — Tool Guard rules **pause the turn mid-flight** and push an approval to your IM. You tap approve, the agent resumes. **Agentic, but not autonomous.** → [Security](./security) + +It lives on your desktop, in your browser, and inside the chat apps your team already uses — same brain, same memory, wherever you go. + +Bring any model. DashScope. OpenAI. Anthropic. Gemini. DeepSeek. Kimi. MiniMax. Zhipu. OpenRouter. Ollama for a local GPU. Log in to your ChatGPT Plus account via OAuth if you have one. Pick one. Add more later. + +--- + +## The problem MateClaw fights + +Most AI products stop at one layer. + +You get a chat box, but the memory resets every morning. You get a tool runtime, but no way to pause it when it's about to do something stupid. You get a knowledge base that retrieves fragments but can't tell you what it actually knows. You get a desktop app, but not the channels your team lives in. Or you get all of it — rented on someone else's cloud, with your data paying rent too. + +MateClaw fights a different fight. It's **all of it, under one roof, on hardware you control.** + +--- + +## What it actually does + +**It completes work.** Plan-and-Execute breaks complex tasks into ordered steps, executes them one at a time, and adapts mid-flight when something fails. ReAct handles the smaller loops — think, act, observe, continue. You see the plan update as the agent works. You see the tool calls. You see the thinking. You see it finish. + +**It remembers.** Session context, post-chat extraction, workspace memory files, scheduled consolidation, and a "dreaming" pass that connects yesterday's threads into today's understanding. Memory is not a feature bolted onto chat — it's how the system gets better at knowing you. + +**It shapes knowledge.** Drop a PDF. Drop a folder. Drop a thousand markdown notes. The LLM Wiki digests them into structured, linked pages with summaries and backlinks — not a vector store you query, a library you can read. Agents auto-inject page summaries and fetch full bodies on demand. + +**It holds real tools.** Built-in tools for search, file IO, time, shell, image, music, video, STT, and TTS. MCP servers for anything else. Skill packages you write in a `SKILL.md` and drop into a workspace. Everything gated by Tool Guard and optional human approval — strong hands, firm limits. + +**It shows up everywhere.** Web console, a desktop app that bundles JRE 21 so your users don't install Java, and eight chat channels: DingTalk, Feishu, WeCom, WeChat, Telegram, Discord, QQ, Slack. The same agent answers a Slack thread, a Feishu DM, and a web chat — same memory, same skills, same personality. + +--- + +## Why self-hosted changes the product + +Running MateClaw on your own hardware is not a compliance checkbox. It changes what the product **is**. + +**Your data stops paying rent.** Logs, conversations, documents, memory — none of it trains anyone else's model. None of it waits in a vendor's queue. None of it leaves your machines unless you point a channel at one. + +**You own the roadmap.** Don't like how the memory consolidator works? Change it. Need a tool your vendor won't build? Add it. MateClaw is Apache 2.0 — not source-available, not "open core", not waiting on a quarterly product review. + +**You pick the economics.** Start on DashScope. Swap to Ollama when your local GPU arrives. Put one agent on OpenAI and keep the rest cheap. Agent config and tool graphs don't care what's under the model interface. + +**Your deployment surface is real.** One JAR. One Spring Boot process. No Python runtime chain. No Node dependency hell. The desktop app bundles everything. The Docker compose file is eighteen lines. + +--- + +## What's under the hood + +- **Backend** — Spring Boot 3.5 + Spring AI Alibaba 1.1. Agent runtime built on a StateGraph with nodes for reasoning, action, observation, plan generation, and step execution. MyBatis Plus for persistence. SSE for streaming — WebFlux is explicitly excluded. +- **Frontend** — Vue 3 + TypeScript. Pinia for state, Element Plus + Tailwind for UI, full dark mode. Built into the backend JAR's `static/` so one process serves both. +- **Desktop** — Electron with bundled JRE 21 and the packaged server JAR. Launches, initializes, and your users never know Java is underneath. +- **Channels** — Each channel is a `ChannelAdapter` SPI implementation. Web streams over SSE. IM channels run on their platform's long-connection or webhook mode. +- **Storage** — H2 file DB for development, MySQL 8 for production. Flyway manages schema migrations with dialect-specific scripts for each. + +--- + +## Three ways to dive in + +You probably want one of three things. + +**Want to use it?** → [Quick Start](./quickstart) — 60 seconds to your first message on the desktop app. + +**Want to understand it?** → Read [Agents](./agents) → [LLM Wiki](./wiki) → [Memory](./memory) → [Multimodal](./multimodal). Those four pages are the product. + +**Want to build on it?** → [API Reference](./api) and [Contributing](./contributing). + +--- + +Nothing on this page is non-negotiable. If something doesn't make sense, the docs are at fault — not you. Tell us on GitHub. diff --git a/mateclaw-server/src/main/resources/docs/en/mcp.md b/mateclaw-server/src/main/resources/docs/en/mcp.md new file mode 100644 index 00000000..df8aff4a --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/mcp.md @@ -0,0 +1,433 @@ +--- +title: MCP Integration — Model Context Protocol Tool Extension +description: MateClaw acts as an MCP client, connecting to any external tool server via Model Context Protocol. JSON-RPC dynamic discovery, SSE/stdio dual transport, seamless unification with built-in tools. +head: + - - meta + - name: keywords + content: MCP,Model Context Protocol,MCP client,tool protocol,JSON-RPC,AI tool extension,Anthropic MCP +--- + +# MCP — Model Context Protocol + +**MCP is how MateClaw talks to tools someone else built.** + +Model Context Protocol is an open standard from Anthropic for connecting AI models to external tools and data. An MCP server is a process — local or remote — that advertises a set of tools over JSON-RPC. MateClaw acts as an MCP *client*: it connects, discovers tools via `tools/list`, and exposes them to your agents as if they were native. **From the agent's point of view, there's no difference between a built-in `@Tool` Spring bean and a tool coming from an MCP server.** + +This is the escape hatch. If you need a capability MateClaw doesn't ship with — filesystem access for a sandboxed directory, Tavily search, a custom internal data service, a browser automation suite — there's probably already an MCP server for it, and you can plug it in without writing a line of Java. + +--- + +## What MCP actually is + +``` +┌───────────────────────┐ ┌───────────────────────┐ +│ MateClaw │ │ MCP Server │ +│ (MCP Client) │ │ (Tool Provider) │ +│ │ JSON-RPC │ │ +│ Agent Engine ───────┼──────────────┼──► Tool A │ +│ │ │ Tool B │ +│ Tool Registry ◄──────┼──────────────┼─── Tool Discovery │ +│ │ │ (tools/list) │ +└───────────────────────┘ └───────────────────────┘ +``` + +Core concepts: + +- **MCP Client** — MateClaw, connecting to servers, discovering tools, forwarding invocations +- **MCP Server** — a third-party process declaring its available tools and executing calls +- **Tool Discovery** — the client sends `tools/list` to retrieve every tool and its parameter schema +- **Tool Invocation** — when the agent decides to call a tool, the client forwards the request to the right MCP server + +New tool capabilities become available to agents **without modifying code or restarting the service**. + +--- + +## Transport types + +Three transports for different deployment scenarios: + +### stdio (Standard I/O) + +MateClaw spawns a local child process and exchanges JSON-RPC messages via stdin/stdout. + +``` +MateClaw ── stdin ──► MCP Server subprocess + ◄─ stdout ── +``` + +**Use cases:** local Node.js/Python MCP packages (e.g., `@anthropic/mcp-filesystem`), command-line tool wrappers, development. +**Advantages:** no network configuration, works immediately, process isolation. +**Limitations:** local only. + +### streamable_http (Streamable HTTP) + +Standard HTTP POST for JSON-RPC, responses streamed back over HTTP. **Recommended for production.** + +``` +MateClaw ── HTTP POST ──► Remote MCP Server + ◄─ HTTP Stream ── +``` + +**Use cases:** cloud-deployed MCP servers, deployments behind load balancers. +**Advantages:** standard HTTP, CDN/firewall friendly, auth headers. + +### sse (Server-Sent Events) + +Earlier HTTP transport using SSE for server-to-client push. Legacy compatibility; new projects should prefer `streamable_http`. + +### Transport comparison + +| Feature | stdio | streamable_http | sse | +|---------|-------|-----------------|-----| +| Deployment | Local only | Local or remote | Local or remote | +| Network requirement | None | HTTP reachable | HTTP reachable | +| Authentication | Environment variables | HTTP Headers | HTTP Headers | +| Process management | MateClaw manages subprocess | External | External | +| Recommendation | Local tools | Remote services | Legacy compatibility | + +--- + +## Configuration via UI + +`Tools → MCP Servers → Add MCP Server`. Fill in: + +- **Name** — unique identifier (letters, numbers, `_`, `-`, `.`, spaces; 1–128 chars) +- **Description** — optional +- **Transport type** — `stdio`, `streamable_http`, or `sse` +- **Command** (stdio) — `npx`, `node`, `python`, etc. +- **Arguments** (stdio) — JSON array (e.g., `["-y", "@anthropic/mcp-filesystem", "/path"]`) +- **Working directory** (stdio) — optional +- **Environment variables** (stdio) — JSON object; supports `${ENV_VAR}` references +- **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 + +Save. If enabled, MateClaw auto-attempts to connect and discover tools. + +### Testing, enabling, status + +- **Test Connection** — sends `tools/list`, returns result, latency, tool list +- **Enable/Disable toggle** — drop connection without deleting config +- **Status** — `connected` / `disconnected` / `error` with error detail + +--- + +## Configuration via REST API + +Full CRUD at `/api/v1/mcp/servers`. + +### List all + +```bash +curl -s http://localhost:18088/api/v1/mcp/servers \ + -H "Authorization: Bearer " | jq +``` + +Response includes `headersJson` and `envJson` automatically **sanitized** (`sk-****abcd`). + +### Create — stdio + +```bash +curl -X POST http://localhost:18088/api/v1/mcp/servers \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "filesystem", + "transport": "stdio", + "command": "npx", + "argsJson": "[\"-y\", \"@anthropic/mcp-filesystem\", \"/home/user/workspace\"]", + "enabled": true + }' +``` + +### Create — streamable_http + +```bash +curl -X POST http://localhost:18088/api/v1/mcp/servers \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "remote-tools", + "transport": "streamable_http", + "url": "https://mcp.example.com/mcp", + "headersJson": "{\"Authorization\": \"Bearer your-api-key\"}", + "connectTimeoutSeconds": 15, + "readTimeoutSeconds": 60, + "enabled": true + }' +``` + +### Update (PATCH semantics) + +```bash +curl -X PUT http://localhost:18088/api/v1/mcp/servers/{id} \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"description": "Updated", "readTimeoutSeconds": 60}' +``` + +After update, enabled servers automatically reconnect. + +### Delete / Toggle / Test / Refresh + +```bash +curl -X DELETE http://localhost:18088/api/v1/mcp/servers/{id} \ + -H "Authorization: Bearer " + +curl -X PUT "http://localhost:18088/api/v1/mcp/servers/{id}/toggle?enabled=false" \ + -H "Authorization: Bearer " + +curl -X POST http://localhost:18088/api/v1/mcp/servers/{id}/test \ + -H "Authorization: Bearer " + +curl -X POST http://localhost:18088/api/v1/mcp/servers/refresh \ + -H "Authorization: Bearer " +``` + +**Built-in servers** (`builtin=true`) cannot be deleted. + +--- + +## Practical examples + +### Example 1 — Filesystem MCP (stdio) + +```bash +curl -X POST http://localhost:18088/api/v1/mcp/servers \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "filesystem", + "description": "Filesystem access (restricted to specified directory)", + "transport": "stdio", + "command": "npx", + "argsJson": "[\"-y\", \"@anthropic/mcp-filesystem\", \"/home/user/workspace\"]", + "enabled": true + }' +``` + +Discovered tools: `read_file`, `write_file`, `list_directory`, `search_files`, `get_file_info`. + +Security: `@anthropic/mcp-filesystem` only allows access to the specified directory and subdirectories. + +### Example 2 — Remote HTTP with auth + +```bash +curl -X POST http://localhost:18088/api/v1/mcp/servers \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "internal-data-service", + "transport": "streamable_http", + "url": "https://mcp-api.internal.example.com/mcp", + "headersJson": "{\"Authorization\": \"Bearer sk-your-api-key\", \"X-Team-Id\": \"engineering\"}", + "connectTimeoutSeconds": 10, + "readTimeoutSeconds": 120, + "enabled": true + }' +``` + +**Header values support environment variable references**: `{"Authorization": "Bearer ${MCP_API_KEY}"}` is replaced at runtime, **secrets don't land in the database**. + +### Example 3 — Tavily search (stdio + env vars) + +```bash +curl -X POST http://localhost:18088/api/v1/mcp/servers \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "tavily-search", + "transport": "stdio", + "command": "npx", + "argsJson": "[\"-y\", \"@anthropic/mcp-tavily\"]", + "envJson": "{\"TAVILY_API_KEY\": \"${TAVILY_API_KEY}\"}", + "enabled": true + }' +``` + +--- + +## How MCP tools become available to agents + +``` +Application startup + │ + ▼ +Iterate enabled MCP servers + │ + ▼ +Connect by transport → initialize → list tools → cache + │ + ▼ +Tool registry (aggregates built-in tools + MCP tools) + │ + ▼ +Agent tool set +``` + +**Key:** the agent fetches the **latest** active tool list on every invocation, so adding or removing MCP servers takes effect **without restarting**. From the agent's perspective, **MCP tools and built-in tools are identical** — no difference. + +--- + +## Per-agent tool binding + +::: tip New in 1.3.0 +Before v1.2.0, all employees could call every MCP tool by default — it was a global switch. v1.3.0 makes the binding **per-employee**, and adds dirty-state detection plus namespace collision handling. +::: + +### Three problems it solves + +**Problem 1: Tool namespace collisions.** +Two MCP servers both expose `read_file` — which one wins? v1.3.0 internally uses a **stable server-prefixed callback name** (`{serverName}__{toolName}`) and persists it to `mate_mcp_server.cached_tools`. The picker shows them as `serverA__read_file` and `serverB__read_file`; the agent's prompt maps them back to original names to save tokens and avoid LLM confusion. + +**Problem 2: MCP server / tool rename breaks bindings.** +In v1.2.0, renaming a server orphaned every employee bound to it. v1.3.0 introduces a **persistent tool cache**: every successful list-tools writes tool metadata to a `cached_tools` JSON column on `mate_mcp_server`. When validating bindings and the server is temporarily unreachable, the cache is consulted as fallback — bindings stay marked `stale` and become live again the moment the server reconnects. + +**Problem 3: Save silently accepted non-existent tool references.** +A typo'd `nonexistent-server.weird-tool` would save fine and blow up at runtime. v1.3.0 runs `AgentBindingService.validate(...)` on save: + +| Status | Meaning | Save behavior | +|---|---|---| +| `connected` | Server online, tool visible | ✅ Persist normally | +| `stale` | Server temporarily offline but in cache | ✅ Persist (marked stale) | +| `unavailable` | Server disabled | ✅ Persist (marked unavailable) | +| `orphan` | Server / tool no longer exists at all | ❌ Reject save, prompt user to clear | + +### Where to see tool status + +`Agents → pick employee → Tools` — see [Agent tool binding](./agents#tool-binding-per-agent-tool-picker). + +### Data contract + +- `mate_mcp_server.cached_tools` (new column in v1.3.0): JSON array, each element `{name, description, inputSchema, lastSeenAt}` +- `mate_agent_tool.tool_name`: stores the **prefixed callback name** `{serverName}__{toolName}` rather than the raw name, so a server rename surfaces immediately as an observable join miss +- `AgentBindingService.getEffectiveToolNames(agentId)` is the single source of truth for tool dispatch — runs every turn, ensuring the editor view and the runtime view always agree + +### Server-side rules + +- MCP servers bridged in via ACP **cannot** be edited from the MCP server list (they're owned by the ACP server's own lifecycle) +- A tool marked `unavailable` is **not listed** in the agent's system prompt — the LLM won't reach for it, but the binding row is preserved +- `returnDirect=true` tools (whose output replaces the assistant turn) go through the same ACL — they **do not bypass** binding + +--- + +## Connection management + +### Automatic connection on startup + +All `enabled=true` MCP servers connect automatically when the app starts. A single server's failure doesn't block other servers or application startup. + +### Thread safety + +The active-client map is concurrent, with an independent lock per server. + +### Connection replacement + +**"Connect new, then disconnect old"** strategy: build a new client, initialize it, swap it into the pool, close the old one. If the new client fails, the old one remains. + +### Subprocess cleanup + +For stdio servers, cleanup happens on: disable/delete, config replacement, application shutdown (`@PreDestroy`), connection failure. + +### Status monitoring + +After each connection operation, results persist: + +- `last_status` — `connected` / `disconnected` / `error` +- `last_error` — error message +- `last_connected_time` — timestamp of last success +- `tool_count` — currently discovered tools + +### Manual refresh + +`POST /api/v1/mcp/servers/refresh` drops all existing connections and reconnects every enabled server. Useful for troubleshooting. + +--- + +## Database storage — `mate_mcp_server` + +| Column | Type | Default | Purpose | +|--------|------|---------|---------| +| `id` | BIGINT | — | Primary key | +| `name` | VARCHAR(128) | — | Unique identifier | +| `description` | TEXT | NULL | Server description | +| `transport` | VARCHAR(32) | `stdio` | `stdio` / `streamable_http` / `sse` | +| `url` | VARCHAR(512) | NULL | Remote URL | +| `headers_json` | TEXT | NULL | HTTP headers JSON | +| `command` | VARCHAR(512) | NULL | Startup command | +| `args_json` | TEXT | NULL | Command arguments JSON array | +| `env_json` | TEXT | NULL | Environment variables JSON; supports `${VAR}` | +| `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 | +| `last_status` | VARCHAR(32) | `disconnected` | Last connection status | +| `last_error` | TEXT | NULL | Last error message | +| `last_connected_time` | DATETIME | NULL | Last successful connection | +| `tool_count` | INT | 0 | Discovered tool count | +| `builtin` | BOOLEAN | FALSE | Whether it's a built-in server | +| `create_time` / `update_time` | DATETIME | — | Timestamps | +| `deleted` | INT | 0 | Logical delete | + +### Sensitive data sanitization + +`headers_json` and `env_json` values are automatically masked in API responses. `args_json` is returned as-is. + +### Environment variable references + +- `${VAR_NAME}` — exact match and replacement +- `$VAR_NAME` — regex match + +Keeps secrets out of the database. + +--- + +## Troubleshooting + +### "Command not found" (stdio) + +1. Confirm the command is in PATH of the user running MateClaw +2. Verify: `which npx` or `npx --version` +3. Docker: confirm command is installed in the container +4. Use full path: `/usr/local/bin/npx` + +### Connection timeout + +1. HTTP/SSE: confirm URL reachable (`curl -v `) +2. Check firewall rules +3. Increase `connectTimeoutSeconds` / `readTimeoutSeconds` +4. stdio: first `npx -y` run may need to download packages + +### SSL/TLS errors + +1. Confirm remote SSL certificate is valid and not expired +2. Self-signed: add CA cert to JVM trust store +3. Confirm JDK supports required TLS version + +### Tools not showing up + +1. Check `tool_count > 0` +2. Use test connection, confirm `discoveredTools` non-empty +3. Verify MCP server implements `tools/list` +4. Check backend logs for MCP tool-discovery output + +### Tool invocation failures + +1. Check backend logs for specific errors +2. Confirm MCP server process is running (stdio) +3. Confirm remote server reachable (HTTP/SSE) +4. Check `readTimeoutSeconds` is sufficient +5. Try refresh connections + +### Orphaned subprocesses (stdio) + +Subprocesses are cleaned up on normal shutdown. If MateClaw was force-killed (`kill -9`), subprocesses may remain. `ps aux | grep mcp` and terminate. + +--- + +## Next + +- [Tools](./tools) — how MCP tools relate to built-in tools +- [Skills](./skills) — MCP-backed skills +- [Configuration](./config) — full configuration reference diff --git a/mateclaw-server/src/main/resources/docs/en/memory.md b/mateclaw-server/src/main/resources/docs/en/memory.md new file mode 100644 index 00000000..dc6f3f57 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/memory.md @@ -0,0 +1,407 @@ +--- +title: AI Memory System — 4-Layer Memory Lifecycle (Extract, Consolidate, Dream, Recall) +description: MateClaw's 4-layer memory lifecycle — in-conversation context, post-chat extraction, workspace persistence (PROFILE.md/MEMORY.md), and scheduled Dreaming consolidation. Your AI gets smarter every day. +head: + - - meta + - name: keywords + content: AI memory,memory system,Dreaming,PROFILE.md,MEMORY.md,memory lifecycle,long-term memory,memory extraction,memory consolidation +--- + +# AI Memory System + +**Memory is how the system gets better at knowing you.** + +Everything else in MateClaw is static the moment you configure it. Agents, tools, knowledge bases — they change when you change them. Memory is the one part that changes on its own, as a byproduct of actual use. That's the whole point. + +::: tip Your AI dreams about you while you sleep +That's not a marketing line. It's literal code in the `memory/dreaming/` package. + +Every night at 2 AM (default; configurable) a scheduled job runs — its name is **Dreaming**. It walks every agent's conversation trail from the day, consolidates scattered signals into a coherent understanding of you, filters out one-offs and contradictions and stale facts, promotes recurring patterns into `MEMORY.md`, and appends "what it saw, what it concluded, what it rewrote" to `DREAMS.md` — a human-readable audit trail of how memory got to where it is today. + +When you open MateClaw the next morning, it **picks up where yesterday left off** — not from zero. + +> Every other AI starts each day from scratch. MateClaw continues from where yesterday ended. +::: + +This page covers the four layers that make up memory, the files the system writes for each agent, and how agents themselves read and write those files during a conversation. + +--- + +## The four layers + +``` + ┌────────────────────────────────────────────────────────────┐ + │ 1. This turn │ + │ What you're saying, what was just said, auto-trimmed │ + │ to the model's token budget │ + │ Updated: every turn │ + └────────────────────────────────────────────────────────────┘ + │ + ▼ (after conversation completes) + ┌────────────────────────────────────────────────────────────┐ + │ 2. Post-chat extraction │ + │ Pulls the worth-keeping bits out of the conversation, │ + │ writes them into PROFILE.md / MEMORY.md / today's note │ + │ Updated: asynchronously, after each meaningful chat │ + └────────────────────────────────────────────────────────────┘ + │ + ▼ (daily at 2:00 AM, configurable) + ┌────────────────────────────────────────────────────────────┐ + │ 3. Nightly consolidation (Dreaming) │ + │ Scans recent daily notes, finds recurring patterns, │ + │ merges them into MEMORY.md, logs the run in DREAMS.md │ + │ Updated: scheduled; manual trigger available │ + └────────────────────────────────────────────────────────────┘ + │ + ▼ (next conversation picks up the latest) + ┌────────────────────────────────────────────────────────────┐ + │ 4. Workspace files as system prompt │ + │ The four markdown files are injected every turn │ + │ Updated: file changes take effect on the next turn │ + └────────────────────────────────────────────────────────────┘ +``` + +Each layer operates at a different timescale. Short-term is *this turn*. Extraction is *after each conversation*. Consolidation is *nightly*. Workspace file injection is *every turn uses whatever's current*. Together they form a loop — what you say becomes context, context becomes files, files become system prompt, system prompt becomes what the agent knows tomorrow. + +--- + +## 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: + +- The **default provider** is the workspace-file-based memory described in the rest of this page. It ships with MateClaw, and for most people it's all they'll ever need. +- **Custom providers** can be dropped in for specialized retrieval — vector-based long-term memory, graph memory, external memory services. +- **Layering** means a single agent can talk to multiple providers at once. A short-term provider returns recent context; a semantic provider returns related memories; a Wiki provider returns authoritative references. They compose at read time. + +For most agents, **default is enough** and you should ignore this section. If you're building something specialized — an agent that needs to remember thousands of facts with vector search, an agent that needs graph-structured memory — this is where you plug in. See [Architecture](./architecture). + +--- + +## The four files every agent has + +Every agent has its own workspace. Four markdown files form the backbone of long-term memory: + +``` +workspace/{agentId}/ +├── AGENTS.md # How the agent uses memory — behavior guide +├── SOUL.md # Who the agent is — core identity, personality, boundaries +├── PROFILE.md # Who you are — user profile, preferences, background +├── MEMORY.md # What matters — key decisions, project context, todos +└── memory/ + ├── 2026-04-09.md # Daily notes — what happened today, append-only + ├── 2026-04-10.md + └── 2026-04-11.md +``` + +The first four are **injected into the system prompt on every turn** (if `enabled=true`). Daily notes are not — they feed consolidation instead. + +### What each file is *for* + +- **AGENTS.md** — the agent's user manual for itself. When to write memory, what goes where, what tools are available. Seed: `enabled=true`, `sort_order=0`. +- **SOUL.md** — who the agent fundamentally is. Self-awareness, evolution guidance, privacy and boundary principles. Edit when you want to change the agent's character at a deep level. Seed: `enabled=true`, `sort_order=1`. +- **PROFILE.md** — what the agent has learned about you. Name, occupation, tech stack, communication preferences. Updated by the extractor when conversations reveal something durable. Full-replace writes. Seed: `enabled=true`, `sort_order=2`. +- **MEMORY.md** — what the agent has decided matters enough to keep. Active projects, unresolved decisions, open threads, things you asked it to remember. Updated by both the extractor and the consolidator. Seed: `enabled=true`, `sort_order=3`. + +::: tip New in 1.3.0: workflows can write memory +From v1.3.0, the [workflow](./workflow) `write_memory` step can write the run's output directly into an employee's `MEMORY.md` (or any enabled memory file) when the flow completes. Four merge strategies: `append` / `replace_section` / `upsert_kv` / `overwrite`. Memory is no longer written exclusively by the conversation extractor or the Dreaming consolidator — a business-process outcome can be persisted too. +::: + +### Daily notes + +Conversation highlights archived by date, in append mode — multiple conversations in one day concatenate into the same file. Not injected into the system prompt (`enabled=false`). They exist so the consolidator has something to scan at 2 AM. + +--- + +## Short-term: the context window + +Before every LLM call, MateClaw builds the prompt that actually gets sent: + +``` +[System Prompt] ← Always first +[Workspace file injection] ← AGENTS / SOUL / PROFILE / MEMORY +[Conversation context summary] ← Only if earlier turns got compressed +[Message 1: user] +[Message 2: assistant] +... +[Current user message] ← Always last +``` + +Workspace files are injected sorted by `sort_order`, formatted as: + +``` +--- AGENTS.md --- +(content) + +--- SOUL.md --- +(content) + +--- PROFILE.md --- +(content) + +--- MEMORY.md --- +(content) +``` + +Only files with `enabled=true` are included. + +### When context gets too big + +Three-stage defense: + +**Stage 1 — proactive compression.** When estimated total exceeds 75% of the budget (default window 128k tokens), the system calls the LLM to summarize earlier turns. The most recent 2 turns (4 messages) survive verbatim. The summary is cached for 30 minutes. + +**Stage 2 — emergency recovery.** If the LLM still returns context-too-large, the system stops calling the LLM. It discards older messages, keeps the last 2 turns, and retries once. + +**Stage 3 — hard trim.** If tokens are *still* over budget, messages drop from the front until the prompt fits. The last 2 messages are always preserved. + +> **Security design** — the summary is injected as a **user message**, not a system message. Deliberate: preventing compressed historical user input from being elevated into system-level instructions eliminates an injection vector. + +### Configuration + +```yaml +mate: + agent: + conversation: + window: + default-max-input-tokens: 128000 # Global max + compact-trigger-ratio: 0.75 # Compression trigger + preserve-recent-pairs: 2 # Turns preserved verbatim + summary-max-tokens: 300 # Compression budget +``` + +--- + +## Post-chat extraction + +After a conversation ends, the system asynchronously pulls out what's memorable and writes it to PROFILE.md, MEMORY.md, and the day's daily note. This happens off the user-response path — it never blocks the next turn. + +### What triggers it + +After a turn completes, the system handles extraction on a background thread. A few preconditions must pass before it actually runs: + +- Auto-summarize is on +- The conversation wasn't itself triggered by the consolidation cron job (avoids recursion) +- Message count meets the minimum (default 4) +- The last user message is long enough (default at least 10 chars) + +All pass — extraction begins. + +### Concurrency control + +- **Cooldown** — same agent won't extract twice within 5 minutes (default) +- **Per-agent lock** — if an extraction is already running for this agent, the new request is skipped + +### What the LLM actually does + +1. Load conversation messages +2. Read current PROFILE.md, MEMORY.md, today's daily note +3. Build a transcript: up to 30 messages, each truncated to 2000 chars +4. Call the LLM with the memory-summarize prompt templates +5. Parse the JSON response +6. Apply writes + +### LLM response schema + +| Field | Type | What it does | +|-------|------|--------------| +| `should_update` | boolean | Whether memory needs updating | +| `reason` | string | Why (for audit) | +| `daily_entry` | string | Content to append to today's daily note | +| `memory_update` | string | Full new content for MEMORY.md | +| `profile_update` | string | Full new content for PROFILE.md | + +### File write rules + +- **PROFILE.md** — full replace, only if `profile_update` is non-empty +- **MEMORY.md** — full replace, only if `memory_update` is non-empty +- **memory/YYYY-MM-DD.md** — append, created with date heading if missing + +--- + +## Consolidation and dreaming + +The third layer runs on a schedule. Its job is to watch daily notes pile up and periodically ask: *what's the pattern here, what should be promoted into core memory, what's stale and should be forgotten?* + +### What it does + +1. Lists the agent's `memory/*.md` files, takes the most recent 7 days +2. Reads those + the current MEMORY.md +3. Calls the LLM with the consolidation prompt templates +4. The LLM returns `{should_update, reason, memory_content}` +5. If `should_update` is true, MEMORY.md is fully replaced + +### Trigger methods + +- **Automatic** — every agent has a row in the system's scheduled jobs, set to run nightly at 2 AM +- **Manual** — `POST /api/v1/memory/{agentId}/emergence` + +### Why it's not recursive + +Consolidation triggers a "conversation" through the agent. Without protection, that conversation would re-trigger the post-chat extraction listener, which would trigger another conversation, ad infinitum. + +The event carries a trigger-source flag. The extraction listener sees that the conversation was started by the consolidation job and skips it. + +### DREAMS.md — the consolidation diary + +Each consolidation run appends a short entry to `workspace/{agentId}/DREAMS.md`: + +- what it looked at +- what patterns it found +- what changed in MEMORY.md +- the date + +Human-readable audit trail — open DREAMS.md and see *how* the memory got to its current state. Caps its own growth; old entries get summarized when the file exceeds a threshold. + +### Scored emergence and recall tracking + +Consolidation tracks: + +- **Which memory entries were actively recalled** in recent conversations — read patterns feed back into importance +- **Scored emergence** — candidate patterns ranked by frequency + recency + explicit recall, only high-scoring ones make it into MEMORY.md +- **Multi-gate filtering** — low-signal extractions (one-off mentions, contradictions, things the user later corrected) get filtered before becoming memory +- **Dreaming status API** — `GET /api/v1/memory/{agentId}/dreaming/status` + +### Full lifecycle (opt-in via flag) + +Memory grows from "dream nightly" to a complete turn-by-turn lifecycle. This behavior lands behind feature flags — default off in the open-source build, on in production builds. + +What it does: + +- **Every turn is bookkept** — the system takes notes at the start and end of every turn, not just at nightly consolidation +- **Fact projection** — conversations are projected into structured "fact" rows the agent can query. Trust scoring + decay built in. +- **Structured nightly report** — consolidation produces a full report; you can re-consolidate by topic on demand +- **Morning card** — the first conversation of the day surfaces yesterday's report; you Confirm / Edit / Forget each fact +- **Contradiction inbox** — when new facts conflict with old ones, you get a queue instead of silent overwrites +- **Explicit forget** — say "forget that," and it actually forgets, everywhere +- **Feedback scoring** — thumbs up/down on retrieved facts feeds back into trust +- **SOUL auto-evolution** — the agent's persona file rewrites itself from accumulated facts +- **Monthly archive** — old reports roll into a compressed monthly archive, browsable in the timeline +- **Memory Browser** — timeline, facts, contradictions, diff viewer, and a trust bar across the top + +Enable in `application.yml`: + +```yaml +mateclaw: + memory: + dream-v2: + enabled: true + fact-projection: true + contradictions: true + morning-card: true +``` + +--- + +## Agents reading and writing their own memory + +Memory isn't just something that *happens to* an agent. The agent itself can actively read and write its own files during a conversation, through a set of workspace memory tools: + +| Method | What it does | +|--------|--------------| +| `list_workspace_memory_files` | List files, optional filename prefix filter, sorted by `sort_order` | +| `read_workspace_memory_file` | Read a specific file's content | +| `write_workspace_memory_file` | Create or overwrite a file (full replace) | +| `edit_workspace_memory_file` | Find-and-replace edit (incremental, `replaceAll` supported) | + +### Examples + +**List:** + +```json +// in +{"agentId": 1, "filenamePrefix": "memory/"} +// out +{"agentId": 1, "count": 3, "files": [ + {"filename": "memory/2026-04-09.md", "enabled": false, "fileSize": 512}, + ... +]} +``` + +**Read:** + +```json +// in +{"agentId": 1, "filename": "MEMORY.md"} +// out +{"agentId": 1, "filename": "MEMORY.md", "enabled": true, "content": "..."} +``` + +**Edit:** + +```json +// in +{"agentId": 1, "filename": "MEMORY.md", "oldText": "old", "newText": "new"} +// out +{"agentId": 1, "filename": "MEMORY.md", "replacements": 1} +``` + +### Safety rules + +- `.md` files only +- No absolute paths, no `..` directory traversal +- `write` is a full overwrite — read first if you care about existing content +- Newly created files have `enabled=false` by default + +--- + +## Configuration reference + +### Memory extraction & consolidation + +```yaml +mate: + memory: + # --- Automatic extraction --- + auto-summarize-enabled: true + min-messages-for-summarize: 4 + min-user-message-length: 10 + skip-cron-conversations: true + summary-max-tokens: 1000 + max-transcript-messages: 30 + + # --- Concurrency --- + cooldown-minutes: 5 + + # --- Consolidation / dreaming --- + emergence-enabled: true + emergence-day-range: 7 +``` + +Prefix: `mate.memory`. + +### Context window + +```yaml +mate: + agent: + conversation: + window: + default-max-input-tokens: 128000 + compact-trigger-ratio: 0.75 + preserve-recent-pairs: 2 + summary-max-tokens: 300 +``` + +--- + +## API endpoints + +| Method | Path | Purpose | +|--------|------|---------| +| POST | `/api/v1/memory/{agentId}/emergence` | Manually trigger consolidation | +| POST | `/api/v1/memory/{agentId}/summarize/{conversationId}` | Manually trigger extraction | +| GET | `/api/v1/memory/{agentId}/dreaming/status` | Last run, next run, latest DREAMS.md entry | + +--- + +For developers extending the memory layer, see [Architecture](./architecture). + +--- + +## Next + +- [Agents](./agents) — how agents use memory during a turn +- [LLM Wiki](./wiki) — the *deliberate* knowledge layer, contrasted with passive memory +- [Tools](./tools) — the workspace memory tool is one of many +- [Configuration](./config) — full config reference +- [Architecture](./architecture) — backend code organization, SPI extension points diff --git a/mateclaw-server/src/main/resources/docs/en/model3d.md b/mateclaw-server/src/main/resources/docs/en/model3d.md new file mode 100644 index 00000000..cd5029c8 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/model3d.md @@ -0,0 +1,225 @@ +# 3D Model Generation + +Text-to-3D and image-to-3D in one tool call. Configure the credentials once and the agent can call `model3d_generate` to produce a `.glb` model that lands directly in the chat bubble — interactive preview, drag to rotate. + +--- + +## What's in the box + +| Aspect | Detail | +|---|---| +| **Current provider** | Tencent Hunyuan 3D (`ai3d.tencentcloudapi.com`, region `ap-guangzhou`) | +| **Available models** | `HY-3D-3.1` / `HY-3D-3.0` / `HY-3D-Express` | +| **Output format** | `.glb` (binary GLTF, single file with embedded textures, rendered inline by ``) | +| **Latency** | 1–3 minutes (Pro slower, Rapid faster) | +| **Auth scheme** | TC3-HMAC-SHA256 (SecretId + SecretKey) | + +Routing is automatic based on the `model` argument: + +| Model | Backend Action | Notes | +|---|---|---| +| **HY-3D-3.1** (default) | `SubmitHunyuanTo3DProJob` | Highest quality. Supports PBR materials, multi-view input, white-model (`GenerateType=Geometry`) | +| **HY-3D-3.0** | same | Older Pro variant, shares the call site | +| **HY-3D-Express** | `SubmitHunyuanTo3DRapidJob` | Fastest. Accepts only `Prompt` or `ImageUrl` | + +--- + +## 1. Get Tencent Cloud credentials + +The Hunyuan 3D API requires traditional CAM credentials (**not** the OpenAI-style `sk-xxx` Bearer keys). You need a SecretId + SecretKey pair. + +1. Open **[Cloud Access Management → API Keys](https://console.cloud.tencent.com/cam/capi)** +2. Click **Create Key**. Tencent gives you both: + - `SecretId` (starts with `AKID`, ~36 chars) + - `SecretKey` (~32 chars) +3. **Save both** — the SecretKey is shown only once and cannot be retrieved later. + +::: tip About `sk-xxx` keys from "API Key 管理" +The Tencent console has another page called "API Key Management" that issues single `sk-` prefixed Bearer tokens. **Those are scoped to TokenHub** (`tokenhub.tencentmaas.com`) for OpenAI-compatible chat completions and **cannot be used for Hunyuan 3D**. 3D requires the SecretId + SecretKey pair from CAM above. +::: + +## 2. Activate the Hunyuan 3D service + +Open **[Hunyuan 3D Console](https://console.cloud.tencent.com/ai3d)**. The first visit asks you to accept the service agreement / activate the free tier. + +Skipping this step results in: + +``` +[Hunyuan3D] SubmitHunyuanTo3DProJob failed: ResourceInsufficient +``` + +Some variants (notably `HY-3D-3.1`) may require a separate quota application or paid plan — check the console's quota dashboard. + +## 3. Configure credentials in MateClaw + +1. Open the **Models & Credentials** page and locate the **Tencent Hunyuan 3D** card (auto-registered by the V71 migration). +2. Click **Update** / **Configure**. +3. Paste the API Key as **`SecretId:SecretKey`** (single colon, no spaces): + ``` + AKIDxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:abcdefghijklmnopqrstuvwxyz123456 + ``` +4. Leave **Base URL** at the default `https://ai3d.tencentcloudapi.com` (the system handles regional routing automatically). +5. Save. + +::: warning Single-input compromise +The provider card currently exposes a single API Key field. A future improvement will split this into separate SecretId / SecretKey inputs that auto-join with `:` on save. +::: + +## 4. Enable the 3D feature + +Go to **Settings → 3D Generation**: + +- **Enable 3D model generation**: on +- **Preferred 3D provider**: pick `Tencent Hunyuan 3D` +- **Provider fallback**: leave on (only one provider exists today, but the toggle is forward-looking) + +Click **Save System Settings**. + +## 5. Try it in chat + +Speak naturally — the agent picks `model3d_generate` automatically: + +``` +Generate a 3D model: a cute cartoon dinosaur, green, with big round eyes +``` + +``` +Quickly generate a 3D model of a red apple ← LLM selects HY-3D-Express +``` + +``` +Generate a 3D model from this image: https://example.com/foo.png ← image-to-3D +``` + +``` +Generate a white-model 3D (no textures): a mechanical gear ← Geometry mode +``` + +Expected flow: + +1. **Tool returns immediately** (milliseconds) with a `taskId=xxx`. +2. **Backend worker polls Tencent every 8 seconds** asynchronously. +3. **1–3 minutes later** the `async_task_completed` SSE event lands in the conversation. +4. **`` renders the `.glb`** inline — drag, rotate, zoom. + +--- + +## Tool parameters (`model3d_generate`) + +| Param | Type | Required | Description | +|---|---|---|---| +| `prompt` | String | Yes\* | Text description, up to 1024 UTF-8 chars | +| `imageUrl` | String | Yes\* | Reference image URL (image-to-3D mode) | +| `model` | String | No | `HY-3D-3.1` (default) / `HY-3D-3.0` / `HY-3D-Express` | +| `enableTexture` | Boolean | No | `true` (default) / `false` (white-model, Pro only) | +| `enablePbr` | Boolean | No | `true` enables PBR materials (richer rendering, Pro only, default `false`) | + +\* Either `prompt` or `imageUrl` is required (XOR — Pro doesn't accept both, except in Sketch mode which is not exposed yet). + +--- + +## Troubleshooting + +### 1. `3D 模型生成功能未启用,请在系统设置中开启` / "3D generation is not enabled" + +→ The feature toggle is off. Go to **Settings → 3D Generation** and enable it. + +### 2. `Provider api_key must be "SecretId:SecretKey" (colon-joined)` + +→ Wrong credential format. You probably saved one of: +- A single SecretId (no SecretKey appended) +- A single SecretKey +- A single `sk-xxx` token (that's a TokenHub key, not for 3D) +- Used a space instead of `:` + +Correct: `AKIDxxxx...:zzzz...` — exactly one ASCII colon, no whitespace. + +### 3. `ResourceInsufficient` (资源不足) + +→ Tencent-side business error. Possible causes: +- Hunyuan 3D service not activated yet +- Free quota exhausted +- The selected model (especially `HY-3D-3.1`) requires approval / a paid plan + +Check the [Hunyuan 3D Console](https://console.cloud.tencent.com/ai3d) for quota status. + +### 4. `invalid params, first_frame_image` + +→ Tencent couldn't fetch your `imageUrl`. The URL must be reachable from the public internet — `localhost`, internal IPs, and signed URLs that have expired won't work. Confirm: +- The URL opens directly in an incognito browser window +- File type is `jpg/png/jpeg/webp`, resolution 128–5000px per side, ≤8MB + +### 5. Task hangs for 15 minutes + +→ The worker times out at 15 min by default. Check backend logs: + +```bash +grep '\[Hunyuan3D\]\|\[Model3dGen\]' logs/mateclaw.log | tail -10 +``` + +If polling is stuck on `RUN`/`WAIT`, restart the backend (in-flight tasks get marked failed on startup). + +### 6. Generation succeeds but the bubble shows only a download link, no interactive preview + +→ Tencent returned an OBJ bundle (.zip with OBJ + textures + MTL) instead of a single GLB. Our code prefers GLB (`pickBestResultFile`), but if Tencent only returns OBJ for that request, the frontend falls back to a download link. **Defaulting to `HY-3D-3.1` usually yields a GLB.** + +--- + +## Architecture (one-pager) + +``` +[ user ] ── natural language ─▶ [ agent ] ─▶ model3d_generate + │ + (route on model field) + │ + ┌───────────────────────────────┴──────────────┐ + ▼ ▼ + SubmitHunyuanTo3DProJob SubmitHunyuanTo3DRapidJob + (HY-3D-3.1 / HY-3D-3.0) (HY-3D-Express) + │ │ + └──────────────── ai3d.tencentcloudapi.com ────┘ + │ + returns JobId (24-h URL) + │ + AsyncTaskService polls Query{Pro,Rapid}HunyuanTo3DJob every 8 s + │ + status → DONE? ─▶ ResultFile3Ds[] + │ + pick best: GLB > FBX > OBJ + │ + download to data/chat-uploads/ + │ + write mate_message (type=model3d) + │ + broadcast SSE async_task_completed + │ + ▼ + frontend useChat detects modelUrl ─▶ MessageBubble bridges virtual attachment + │ + renders the .glb +``` + +--- + +## Log markers for a successful run + +``` +[ToolExecutor] Executing tool: model3d_generate +[Hunyuan3D] SubmitHunyuanTo3DProJob submitted job: 1441791994... (model=HY-3D-3.1) +[AsyncTask] Created task d73723f14c7c4167 (providerTaskId=pro:1441791994...) +[AsyncTask] Started polling for task d73723f14c7c4167 (interval=8s, timeout=15min) +[ToolExecutor] Tool model3d_generate returned 80 chars +…1–3 minutes… +[Model3dDownloader] Downloading 3D model from https://hunyuan-prod-….cos.../...glb to data/chat-uploads/.../model_d73723f14c7c4167.glb +[Model3dDownloader] Downloaded NNNN bytes +[Model3dGen] Task d73723f14c7c4167 completed, model saved: /api/v1/chat/files/.../model_d73723f14c7c4167.glb +``` + +--- + +## See also + +- [Multimodal Overview](./multimodal.md) +- [Models & Credentials](./models.md) +- [Tools System](./tools.md) +- Design RFC: `rfcs/202605/01-generative-async-pipeline.md` diff --git a/mateclaw-server/src/main/resources/docs/en/models.md b/mateclaw-server/src/main/resources/docs/en/models.md new file mode 100644 index 00000000..1bd4116b --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/models.md @@ -0,0 +1,453 @@ +# Models + +**Pick a model. Just one. Add more later.** + +MateClaw doesn't care which LLM you use. It talks to every mainstream provider through five protocol adapters, supports 15+ cloud providers and 4 local runtimes, and lets you swap models at runtime without touching agent configuration. The only opinion MateClaw has is that you should **start with one and add more when you need them** — not configure everything on day one. + +--- + +## What's supported + +### Cloud providers + +| Provider | Example models | Protocol | Notes | +|----------|---------------|----------|-------| +| **DashScope** (Alibaba) | Qwen-Max, Qwen-Plus, Qwen-Turbo, Qwen-VL, Qwen-Long | dashscope | Default out of the box | +| **DashScope (OpenAI-compatible)** | Qwen3.5-Plus, Qwen3.6-Plus, Qwen3 VL Plus, etc. (dot-versioned families) | openai | See "Two DashScope variants" below | +| **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 | +| **Google Gemini** | Gemini 2 Pro, Gemini 2 Flash | gemini | Google Generative AI API | +| **DeepSeek** | deepseek-chat, deepseek-coder, **DeepSeek V4 flash + pro** (thinking-mode) | openai | OpenAI-compatible | +| **Kimi (Moonshot)** | moonshot-v1-8k/32k/128k | openai | OpenAI-compatible | +| **Zhipu AI** | GLM-5-Turbo, GLM-5V-Turbo, GLM-5, GLM-5.1 | openai | OpenAI-compatible | +| **MiniMax** | abab6.5, abab5.5; expanded video catalog + CN endpoint | openai | OpenAI-compatible | +| **SiliconFlow CN/INTL** | Routed inference across hosted models | openai | Two endpoints, OpenAI-compatible | +| **OpenCode** | Code-tuned routing | openai | OpenAI-compatible | +| **OpenRouter** | 200+ models with free tier | openai | Routes to any upstream with one key | +| **Any OpenAI-compatible** | Your own vLLM, etc. | openai | Custom base URL | + +### Local runtimes + +| Runtime | Example models | Protocol | Notes | +|---------|---------------|----------|-------| +| **Ollama** | Gemma 3/4, Qwen 3, Llama 3.1, DeepSeek R1, Mistral | ollama | **Auto-detected at startup** on `localhost:11434` | +| **LM Studio** | Any GGUF model | openai | OpenAI-compatible server | +| **llama.cpp** | Any GGUF model | openai | Via llama-server | +| **MLX** | Apple Silicon via mlx-lm | openai | mlx-lm's OpenAI-compatible server | + +### Protocol adapters + +Five protocols cover everything: + +| Protocol | Used by | +|----------|---------| +| **OpenAI** | OpenAI, Kimi, DeepSeek, MiniMax, Zhipu, OpenRouter, LM Studio, llama.cpp, MLX | +| **Anthropic** | Claude family | +| **DashScope** | Qwen family | +| **Gemini** | Google Gemini family | +| **Ollama** | Locally hosted models via Ollama | + +Any OpenAI-compatible service works — just point `base-url` at it. + +--- + +## Two DashScope variants + +Same `sk-` API key, **two endpoints** that ship different model families: + +| Item | DashScope | DashScope (OpenAI-compatible) | +|---|---|---| +| Endpoint | `dashscope.aliyuncs.com/api/v1` (native) | `dashscope.aliyuncs.com/compatible-mode/v1` (OpenAI-compatible) | +| Protocol | DashScope native | OpenAI standard (same shape as GPT-4 / DeepSeek / Kimi) | +| Built-in web search (`enable_search`) | ✅ Supported | ❌ Not supported | +| Models | Qwen-Max / Plus / Turbo / Long, Qwen-VL, Qwen3-Max, DeepSeek-V3.2, etc. | **Dot-versioned** new families: Qwen3.5-Plus, Qwen3.6-Plus, Qwen3 VL-Plus, etc. | + +**Why two providers**: Alibaba publishes the dot-versioned families (`qwen3.5-*` / `qwen3.6-*` / `qwen3-vl-*`) only on the OpenAI-compatible endpoint; the native protocol returns `400 InvalidParameter` for them. The two providers **share the same sk- key** — paste it once, it works for both. + +**Which to pick**: +- Want Qwen-Max / Plus / Turbo + built-in search / DeepSeek-V3.2 → **DashScope** +- Want Qwen3.5-Plus / Qwen3.6-Plus / Qwen3 vision-language → **DashScope (OpenAI-compatible)** +- **Enable both** if you want — same key, models just appear under different cards + +--- + +## Adding a provider + +**A fresh MateClaw install has an empty provider list. That's deliberate.** + +You don't need to see 16 providers. You need **one that works.** + +`Settings → Models → Add Provider` opens a drawer with the full catalog. Local runtimes (Ollama, LM Studio, llama.cpp, MLX — no API key required) appear first; cloud providers (DashScope, OpenAI, Anthropic, DeepSeek, etc.) follow. + +Three steps: + +1. **Find the row you want and click Enable** — the provider joins your main list +2. **Fill in the base URL** (pre-filled for known providers) **and paste your API key** — encrypted at rest, masked in UI +3. **Save → Test Connection** — the system sends a lightweight request and reports success or error + +Close the drawer and the main list shows only the providers you've enabled. **Model picker, chat page, agent editor — every place that surfaces models, surfaces only the ones you opted in.** + +::: tip Existing installs (V55 migration) +Providers already in use are **not** turned off. V55 auto-marks a provider as enabled if any of these are true: +- Has a real API key configured +- Has an OAuth token +- Has been used by a chat session in the last 30 days +- Owns the current default model + +Untouched, never-used placeholder providers go back into the drawer — flip them on the next time you need them. +::: + +--- + +## Enabling / disabling a provider + +Every provider card in the main list has an **Enable / Disable** toggle. **You must enable a provider before you can use it** — that's the core product contract from v1.1.0 onward. + +- **Disable** — the provider disappears from the model picker, chat page, and agent editor immediately. **Configuration is preserved**; flip it back on and everything is exactly where you left it. +- **If you disable the provider that owns the current default model**, the system automatically promotes a model on a still-enabled provider as the new default — no broken next-message. +- **Enable** — the provider reappears everywhere. If it has never had an API key set, you'll be prompted to configure it. + +This separates "I have a key for this provider but I'm not using it today" from "I don't have this provider." Switching providers temporarily no longer means deleting configuration. + +### ChatGPT OAuth — no API key needed + +Have a ChatGPT Plus or Pro account? MateClaw can talk to OpenAI's chat endpoint through **browser-based OAuth** — log in the way you normally would, your subscription is used directly. GPT-4o, o3, and o4-mini become available immediately. + +`Settings → Models → Add Provider → OpenAI OAuth`. A browser window opens. Token exchange happens on the backend; **credentials never leave your machine**. + +### Device authorization grant — for remote / headless deployments + +Browser-callback OAuth needs the IDP's redirect to land back on a `localhost` port that *your* browser can reach. That's fine when MateClaw runs on your laptop and breaks the moment you put it on a server, in a container, or on a host that doesn't expose a loopback socket to your client. + +For those cases, OpenAI OAuth automatically switches to **Device Authorization Grant (RFC 8628)** — the same flow ChatGPT desktop and `gh auth login` use. No callback, no port mapping. + +`Settings → Models → Add Provider → OpenAI OAuth` on a non-localhost host pops a dialog showing: + +- A short **user code** (monospace, copyable) +- A **verification URL** at `auth.openai.com/codex/device` — open it in any browser on any device +- A live **countdown** until the device code expires (default 15 min) + +Enter the user code in your browser, authorize, and the dialog closes itself the moment the backend's poll loop sees `COMPLETED`. + +**How MateClaw decides which flow to use:** + +| `mateclaw.oauth.openai.deployment-mode` | Behaviour | +|---|---| +| `auto` *(default)* | `localhost` / `127.0.0.1` / `::1` → browser callback; everything else → device code | +| `local` | Force browser callback (loopback server) | +| `device_code` | Force device code | +| `manual_paste` | Force the legacy paste-the-callback-URL flow | + +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`): + +| 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) | + +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. + +Token persistence and refresh use the **same code path** as the browser-callback flow, so once the dialog closes there's no behavioural difference. + +### Anthropic Claude Code OAuth + +Same pattern, same outcome: have a Claude Pro / Max / Team subscription? Sign in with the **same OAuth flow Claude Code itself uses** — no `sk-ant-…` API key required. Claude 4.7 / 4.6 / 4.5 Haiku come online through your subscription. + +`Settings → Models → Add Provider → Anthropic Claude Code OAuth`. Two flows are supported: + +- **Browser callback** — local install, browser pops up, you click through, token lands in MateClaw +- **MANUAL_PASTE** — for remote-server deployments where the browser can't reach the backend, you complete the auth in your local browser and paste the token in + +Anti-abuse-gate compliant: Claude Code identity is injected into the system prompt, the request shape (UA / accept headers / `system` array form / `mcp_` tool-name prefixes) matches Claude Code's wire format exactly so the requests aren't rejected. + +--- + +## Model discovery + +Providers that expose a model list (OpenAI, Ollama, LM Studio, OpenRouter, etc.) support **Model Discovery** — one click and MateClaw fetches every model the provider offers. + +- `Settings → Models → [provider card] → Discover Models` +- System queries the provider's `/v1/models` endpoint +- Discovered models appear with name, context window, pricing +- Add them one by one or all at once + +For OpenRouter specifically, Model Discovery surfaces the **200+ free-tier models** — pick a free model and you have a working setup with zero cost. + +### Ollama auto-detection on startup + +No manual configuration needed. On startup: + +1. **Ping** `http://127.0.0.1:11434` +2. **Discover** — fetch pulled models via `/v1/models` +3. **Register** — add to `mate_model_config` +4. **Enable** — auto-enable matching pre-configured models +5. **Tag rewrite** — rewrites seed `:latest` tags to actual installed versions (`deepseek-r1:latest` → `deepseek-r1:7b`), no more `model not found` 404s + +If Ollama isn't running, silently skipped. + +::: tip Default behavior +- Models without tool support (`deepseek-r1`, `gemma*`, `phi3/4`, etc.) won't accidentally activate as default — they're blocklisted +- Models that are not callable on DashScope native protocol are auto-purged on startup; dot-versioned Qwen families now live on the DashScope (OpenAI-compatible) provider instead +- DashScope model discovery uses protocol-aware probing, skipping non-chat modalities +::: + +**Pre-configured Ollama models** (disabled until discovered, then auto-enabled): + +| Model | `model_name` | +|-------|-------------| +| Gemma 3 | `gemma3:latest` | +| Gemma 4 | `gemma4:latest` | +| Qwen 3 | `qwen3:latest` | +| Llama 3.1 | `llama3.1:latest` | +| DeepSeek R1 | `deepseek-r1:latest` | +| Mistral | `mistral:latest` | + +Setup: + +```bash +# Install Ollama from ollama.com, then: +ollama pull gemma3 +ollama pull qwen3 +``` + +Restart MateClaw. Auto-discovered, added, enabled. + +--- + +## Database schema + +### `mate_model_provider` + +| Column | Purpose | +|--------|---------| +| `id` | Primary key | +| `name` | Provider identifier | +| `display_name` | Human-readable name | +| `protocol` | `dashscope` / `openai` / `ollama` / `anthropic` / `gemini` | +| `base_url` | API base URL | +| `api_key` | Encrypted API key | +| `oauth_tokens` | OAuth tokens (ChatGPT Plus/Pro) | +| `is_local` | True for local runtimes | +| `enabled` | Provider master switch — when off, hidden from every model picker; configuration is preserved (v1.1.0+) | + +### `mate_model_config` + +| Column | Purpose | +|--------|---------| +| `id` | Primary key | +| `provider_id` | FK to `mate_model_provider` | +| `model_name` | Actual model identifier | +| `display_name` | Human-readable name | +| `temperature` | Default temperature (0.0 – 2.0) | +| `max_tokens` | Max output tokens | +| `top_p` | Top-p sampling | +| `group_name` | UI grouping (e.g., "Reasoning", "Fast", "Vision") | +| `enabled` | Whether the model is available | + +### Embedding models + +No `EMBEDDING_API_KEY` env vars. Embedding models are regular rows in `mate_model_config` with `model_type='embedding'`. They show up alongside chat models in `Settings → Models`. Knowledge bases pick their embedding model from a dropdown. + +### Anthropic prompt caching + +System prompts, agent personas, tool definitions — automatically marked with `cache_control: ephemeral` on Anthropic-compatible endpoints. First request warms the cache, every follow-up gets a cache hit. The Dashboard tracks `cache_read_tokens` / `cache_write_tokens` daily. + +### Thinking depth / `reasoning_effort` + +**Which models honor this parameter**: `reasoning_effort` is only valid for the OpenAI reasoning family (`gpt-5*` / `o1*` / `o3*` / `o4*`), and only when delivered through the OpenAI or Azure-OpenAI providers. Every other provider (DeepSeek, Kimi, DashScope, Ollama, self-hosted OpenAI-compatible gateways, etc.) will either error or behave oddly if this parameter reaches them. + +**Three product contracts**: + +1. **Chat models that don't support chain-of-thought** ignore the front-end "deep thinking = high" selector entirely — this is a capability property, not a UI setting. The thinking-depth selector automatically grays out when the current model is not reasoning-capable. +2. **`generateKwargs.reasoningEffort` at provider level** only takes effect on whitelisted providers. Setting it on DeepSeek / Kimi / other OpenAI-compatible providers is silently dropped with a WARN log; the parameter is never sent. +3. **Failover** re-checks at egress time: if the primary is GPT-5 and the fallback is DeepSeek, `reasoning_effort` is stripped before hitting DeepSeek, so leaked primary options can't 400 the fallback. + +**How to enable DeepSeek thinking**: DeepSeek's thinking mode does **not** use `reasoning_effort`. + +- `deepseek-reasoner`: thinking is on by default; no config needed. +- `deepseek-chat` with thinking: follow DeepSeek's official docs and set `{"thinking": {...}}` under the provider's `generateKwargs.extra_body`. **Do not** set `reasoningEffort`. + +**Kimi K2.5 thinking**: the model activates thinking natively; don't set `reasoning_effort`. + +**Multi-round tool calls + thinking**: thinking-capable models (DeepSeek-Reasoner / GPT-5 / Kimi K2.5) correctly round-trip historical `reasoning_content` during ReAct multi-round tool calls. Cross-user-turn history is cleared at the boundary, in-turn history is preserved — matching DeepSeek's "pass back within a turn, reset across turns" contract. + +--- + +## Grouped model selector + +When your deployment has a lot of models configured, the chat model picker groups them by provider and tag. Searchable dropdown lets you filter by name, provider, or group — "all Qwen", "all reasoning models", "everything under 7B". Groups are defined in the `group_name` column. + +Became a real thing when agents could be bound to different models per task — a reasoning model for Plan-Execute, a fast cheap model for Chat, a vision model for image understanding. + +--- + +## Active model switching at runtime + +MateClaw uses a single **active model** as the global default. Agents that don't specify their own use it. + +- **UI:** `Settings → Models → [model card] → Set as Active` +- **API:** `PUT /api/v1/models/active` + +Takes effect **immediately** — no restart. Next message uses the new model. In-flight conversations unaffected. + +Per-agent override supported: bind a specific agent to a specific model config. + +--- + +## Per-model testing + +Every model card has a **Test** button. Click it, system sends a simple prompt, shows: + +- Actual response text +- Latency +- Token usage +- Any error + +Use it whenever you add a new provider or suspect a stale key. + +--- + +## Multimodal sidecar (system-wide) + +::: tip Added in 1.3.0 +Lets a text-only primary model still answer questions about uploaded images. See [issue #87](https://github.com/matevip/mateclaw/issues/87). +::: + +Entry point: **Settings → Models → Multimodal sidecar**. Two independent cards: + +| Card | Purpose | Status | +|------|---------|--------| +| **Vision sidecar model** | Captions an uploaded image once, then hands the structured description to the primary chat model | Live | +| **Video sidecar model** | Same idea for video | Reserved (config persisted but not yet wired in v1) | + +The setting stores `mate_model_config.id` rather than `model_name` — the same `model_name` can exist under multiple providers (e.g. `qwen-vl-max` lives on both DashScope and an OpenAI-Compatible custom row), so a name-keyed setting would collide. Two setting keys: + +- `default.vision_model` +- `default.video_model` + +The dropdown only lists models that **actually support the relevant modality** — filtered by `ModelCapabilityService.supports(...)` on the backend; disabled providers or models without a declared vision capability never appear. Each card has its own Save button, independent of the other. + +When does it fire? `MultimodalRouter` ([source](https://github.com/matevip/mateclaw/blob/main/mateclaw-server/src/main/java/vip/mate/llm/routing/MultimodalRouter.java)) decides per turn: + +- Primary already supports vision → no routing (native multimodal path) +- Primary lacks vision + vision sidecar configured → SIDECAR strategy, captions to text +- Primary lacks vision + no sidecar → skip the attachment + tell the user to configure one + +For the end-user flow (badge, hint above the input box) see [Chat → Primary model can't see images? "Multimodal sidecar" routing](./chat#primary-model-cant-see-images-multimodal-sidecar-routing). + +--- + +## Multi-model failover + +::: tip OpenAI was down for 30 minutes. My AI didn't stop for a second. +During the last 30-minute DashScope rate-limit hiccup, our service uptime was 100%. + +Users saw their answers come through cleanly — no red error toast, no "service unavailable, please try again." **Mid-answer, mid-token**, the runtime quietly rolled to the next healthy provider. The next token after the cut landed normally. + +This isn't "automatic retry" in the engineering sense. It's **failover the user can't perceive**. +::: + +Every provider you add joins an `AvailableProviderPool` that's probed at startup and re-probed on config change. + +- **Automatic fallback** — if the primary provider returns an `AUTH_ERROR`, `BILLING`, `MODEL_NOT_FOUND`, `NETWORK`, or `5xx`, the runtime rolls forward to the next provider in the chain instead of bubbling up the error +- **Per-agent priority** — bind an agent to "OpenAI first, then Anthropic, then DashScope" via the drag-to-reorder editor in `Settings → Models` +- **Live pool state** — green / amber / red badges show each provider's health +- **4-protocol probe** — DashScope, OpenAI-compatible, Anthropic, Ollama-style +- **Manual reprobe + auto-reprobe on config change** — no restart after rotating a key +- **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 + +--- + +## Configuration via API + +```bash +# List enabled providers (what the main list shows) +curl http://localhost:18088/api/v1/models \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# List the full catalog (including disabled) — what the Add Provider drawer uses +curl http://localhost:18088/api/v1/models/catalog \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# Enable a provider +curl -X POST http://localhost:18088/api/v1/models/{providerId}/enable \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# Disable a provider (auto-switches default model if needed) +curl -X POST http://localhost:18088/api/v1/models/{providerId}/disable \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# Add a model configuration +curl -X POST http://localhost:18088/api/v1/models \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "providerId": 1, + "modelName": "qwen-plus", + "displayName": "Qwen Plus", + "temperature": 0.7, + "maxTokens": 4096, + "groupName": "Fast", + "enabled": true + }' + +# Set active model +curl -X PUT http://localhost:18088/api/v1/models/active \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"providerId": "openai", "model": "gpt-4o"}' + +# Discover models +curl -X POST http://localhost:18088/api/v1/models/{providerId}/discover \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# Test connection +curl -X POST http://localhost:18088/api/v1/models/{providerId}/test-connection \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" +``` + +--- + +## Everything goes through the UI + +::: tip +**Model configuration is 100% UI-driven.** There's no `spring.ai.*` YAML you need to touch. All providers, all API keys, all model configs, all switching — it all lives in `Settings → Models`, backed by the `mate_model_provider` and `mate_model_config` database tables. +::: + +The UI handles everything you'd otherwise do in YAML, plus several things YAML can't do: + +- **Add a provider** — pick a type, paste a key, save. Encrypted at rest, masked in the UI. +- **Test connection** — verify a provider before you trust it in production. +- **Discover models** — for providers that support `/v1/models`, one click pulls the whole list. +- **Per-model test** — send a test prompt and see the exact response, latency, and token usage. +- **Switch active model at runtime** — no restart, no config reload, takes effect on the next message. +- **Per-agent override** — bind a specific agent to a specific model config. + +LLM API keys are **no longer read from environment variables** — setting `DASHSCOPE_API_KEY` / `OPENAI_API_KEY` and similar has no effect. Every provider, key, and model lives in the UI. A fresh install starts with no providers configured; add your first one under `Settings → Models → Add Provider`. + +### Reference: which Qwen model to pick + +If you're on DashScope, here's the rough shape of the lineup: + +| Model | Context | Best for | +|-------|---------|----------| +| `qwen-max` | 32K | Complex reasoning, analysis | +| `qwen-plus` | 32K | General-purpose | +| `qwen-turbo` | 8K | Fast responses | +| `qwen-vl-max` | 32K | Vision + language | +| `qwen-long` | 1M | Very long documents | + +--- + +## Next + +- [Configuration](./config) — full config reference +- [Agents](./agents) — how agents use models +- [Admin Console](./console) — UI for model management diff --git a/mateclaw-server/src/main/resources/docs/en/multimodal.md b/mateclaw-server/src/main/resources/docs/en/multimodal.md new file mode 100644 index 00000000..17b048e0 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/multimodal.md @@ -0,0 +1,192 @@ +# Multimodal + +Speech, music, images, video — all first-class in MateClaw, not tacked on. + +Most AI products treat multimodal generation as a plugin you bolt on later. MateClaw ships with it as core infrastructure: **six image providers, four video providers, three TTS backends, three STT backends, and two music providers**, all unified behind a single tool interface so agents can call any of them without knowing which vendor is underneath. + +Configure once. Use everywhere. + +--- + +## What's in the box + +### Image generation — six providers + +| Provider | Model family | Notes | +|----------|--------------|-------| +| **DashScope** | Wanxiang | Alibaba's image model, default cloud option | +| **OpenAI** | DALL-E 3 | Standard DALL-E endpoint | +| **fal.ai** | Flux | Fast Flux inference via fal.ai | +| **Google Imagen** | Imagen 3 | Google Cloud credentials required | +| **Zhipu** | CogView | Native Chinese prompt support | +| **MiniMax** | — | Sync and async both supported | + +The image-generation tool auto-picks the provider configured as default, or you can force a specific one per call. Async generation returns a job id the agent polls; when the image lands, it attaches to the **original assistant message**, not a new one. + +::: tip New in 1.3.0 +DashScope Wanxiang plugged into the **unified multimodal-generation endpoint** (`multimodal-generation/generation`) in v1.3.0, adding 14 image models — 6 of which **support image editing**. See [Image edit](#image-edit) below. +::: + +#### Image edit + +::: tip New in 1.3.0 +Image editing (image-to-image) is supported from v1.3.0. In v1.2.0 and earlier, `image_generate` was text-to-image only. +::: + +The `image_generate` tool gains two parameters: `image` and `images`: + +| Parameter | Shape | Description | +|---|---|---| +| `image` | Single reference image | String: path / `file://` / `data:image/...` / `http(s)://` / `msg::` | +| `images` | Multiple reference images (up to 5) | Array of the same forms | + +The tool normalizes all five reference forms into in-memory buffers internally before forwarding to the provider. **Five reference forms**: + +1. **Local path** — `/abs/path.png` / `~/x.png` / `./rel.png` +2. **`file://` URL** — absolute-path variant +3. **`data:image/png;base64,...`** — inline base64 / percent-encoded body +4. **`http(s)://...`** — with SSRF guard (rejects internal hosts) +5. **`msg:[:]`** — references an image attachment from a message in the same conversation. **Works for non-vision models too** — the agent doesn't need to "see" the bytes; merely having seen the messageId in conversation history is enough + +```text +User: (uploads a sunset image, messageId=12345) Replace the background with a forest. +Agent: image_generate(prompt="replace background with forest", + image="msg:12345:0", + model="qwen-image-edit") +``` + +**Models that support image editing** (DashScope Wanxiang): +- `wan2.7-image` / `wan2.7-image-pro` (**T2I + edit**) +- `qwen-image-edit` / `qwen-image-edit-plus` / `qwen-image-edit-max` (**edit-only**) + +A fuller model catalog lives in [Models](./models#two-dashscope-variants). + +### Video generation — six providers + +- **DashScope** — Tongyi Wanxiang video +- **Runway** — Gen-2 / Gen-3 via API +- **MiniMax (Hailuo)** — text-to-video and image-to-video +- **Fal** — fast inference pipeline +- **CogVideo** — Zhipu CogVideoX +- **Kling** — Kuaishou Kling video generation + +Same async-attach model as image generation. Videos appear inline in the chat once rendering finishes — in the same bubble where the agent first said "working on it". + +### Music generation — two providers + +- **Google Lyria** — high-quality music generation +- **MiniMax** — music generation with lyrics + style prompts + +The music-generation tool takes a prompt, an optional style tag, and optional lyrics. Output is an MP3 attached to the message. + +### 3D model generation — one provider + +- **Tencent Hunyuan 3D** — `HY-3D-3.1` / `HY-3D-3.0` (Pro, supports PBR / multi-view / white-model) / `HY-3D-Express` (rapid) + +Text-to-3D and image-to-3D both work; output is a `.glb` rendered inline by `` for drag-to-rotate preview. Full setup walkthrough: **[3D Model Generation](./model3d.md)**. + +### Text-to-speech (TTS) — three providers + +- **DashScope CosyVoice** — Chinese + English, natural prosody +- **OpenAI TTS** — alloy, echo, fable, onyx, nova, shimmer +- **MiniMax T2A** — Chinese voices with emotion tags + +Click the speaker icon on any assistant message to read it aloud. The voice is whichever TTS provider is active in Settings. + +### Speech-to-text (STT) — two providers + +- **DashScope Paraformer** — Chinese-first, low latency +- **OpenAI Whisper** — the standard multilingual benchmark + +Hold the mic button in the chat input to speak. Release to transcribe. Edit the result before sending if you want to. + +--- + +## Configuration + +All multimodal providers live under `Settings → Models → [category]`. Add a provider once with its API key, then mark it as default for its category. + +```yaml +# application.yml — minimal example +mate: + image: + default-provider: dashscope + video: + default-provider: dashscope + tts: + default-provider: cosyvoice + stt: + default-provider: paraformer + music: + default-provider: dashscope +``` + +Per-agent overrides are available if you want a specific agent to always use, say, Flux for images and CosyVoice for voice. + +--- + +## How agents use it + +Every multimodal capability is exposed as a tool: + +| Tool | Signature | +|------|-----------| +| `image_generate` | `(prompt, style?, size?)` | +| `image_edit` | `(image_id, prompt)` — where the provider supports it | +| `video_generate` | `(prompt, duration?)` | +| `video_from_image` | `(image_id, prompt)` | +| `music_generate` | `(prompt, style?, lyrics?)` | +| `tts_synthesize` | `(text, voice?)` | +| `stt_transcribe` | `(audio_id, language?)` | + +Agents call them exactly like any other tool. The tool layer handles provider selection, retries, async polling, and attachment binding. + +--- + +## Async generation and message binding + +Image and video generation often takes longer than a normal agent turn. MateClaw handles this cleanly: + +1. Agent calls the generate tool. +2. Tool returns immediately with a job id and a placeholder attachment. +3. Backend polls the provider in the background. +4. When the result lands, it's attached to the **original assistant message** — not a new one. + +It works the way you'd expect: the image appears inside the same bubble where the agent first said "working on it" — not floating in a new message. + +--- + +## Where it shows up in the product + +- **Chat** — drag an image into the input for vision models; press-and-hold the mic to dictate; click the speaker on any response to read aloud; generated media appears inline. +- **Agents** — enable or disable specific multimodal tools per agent. +- **Tools page** — every provider has a test button so you can verify a key before using it in production. +- **Desktop app** — everything above, plus local filesystem access for batch operations. + +--- + +## When to use what + +- **Image** — documentation illustrations, slide graphics, concept visualization, marketing. Start with DashScope or Flux; DALL-E 3 when you need tight text rendering. +- **Video** — short-form demos, social content, product animations. Runway for quality, MiniMax for Chinese scenarios, DashScope for cloud-local. +- **Music** — background tracks, demo jingles, creative exploration. Two providers today; expect the surface to evolve. +- **TTS** — accessibility, audiobook-style reading, multilingual content. CosyVoice for Chinese, OpenAI for English variety. +- **STT** — voice-first input, meeting transcription, dictation workflows. Paraformer for Chinese, Whisper for everything else. + +--- + +## Multimodal input: primary doesn't speak it? Use a sidecar + +::: tip Added in 1.3.0 +This page is about **generation (output)**. The **input** side — uploading an image to a text-only primary model — runs through a separate "multimodal sidecar" path. See [Chat → Primary model can't see images?](./chat#primary-model-cant-see-images-multimodal-sidecar-routing) and [Models → Multimodal sidecar (system-wide)](./models#multimodal-sidecar-system-wide). +::: + +In short: configure a vision model under **Settings → Models → Multimodal sidecar**. When the primary model can't handle an uploaded image, the runtime captions it via the sidecar first and feeds the description to the primary chat. Primary stays cheap; the routing decision is fully visible in the chat UI (badge on the bubble, hint above the input box). + +--- + +## Next + +- [Chat & Messaging](./chat) — attachment input, multimodal sidecar routing, how generated media attaches to messages +- [Models](./models) — provider configuration UI, multimodal sidecar settings +- [Tools](./tools) — the tool system that hosts multimodal generation diff --git a/mateclaw-server/src/main/resources/docs/en/quickstart.md b/mateclaw-server/src/main/resources/docs/en/quickstart.md new file mode 100644 index 00000000..a2770476 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/quickstart.md @@ -0,0 +1,86 @@ +# Quick Start + +Sixty seconds to first message. **One path. Desktop app.** + +If you want Docker or local development instead, those live in [Configuration](./config) and [Contributing](./contributing). This page does one thing only — get you from zero to a working agent as fast as humanly possible. + +--- + +## 1. Download + +Grab the latest installer from [GitHub Releases](https://github.com/matevip/mateclaw/releases). + +- **Windows** — `MateClaw-Setup-x.y.z.exe` +- **macOS** — `MateClaw-x.y.z.dmg` +- **Linux** — `MateClaw-x.y.z.AppImage` + +No Java install. No Node install. No Maven. The desktop app bundles JRE 21 and the server JAR. + +## 2. Launch and log in + +Double-click. First launch takes 10 to 30 seconds while the backend boots. + +Log in. Username `admin`, password `admin123`. Change the password from `Settings → Security` the moment you're inside. Do it now. Your future self will thank you. + +## 3. Add one model + +`Settings → Models → Add Provider`. + +Pick one. Just one: + +- **DashScope** — the simplest cloud start; paste your key from Alibaba Cloud +- **OpenAI** or **Anthropic** — if you already have a key, drop it in +- **Ollama** — local GPU users; MateClaw auto-detects `localhost:11434` +- **ChatGPT OAuth** — if you have a Plus or Pro account, log in through the browser flow and use GPT-4o, o3, or o4-mini directly + +Save. The model appears in the chat screen's model picker. + +## 4. Say hello + +Click `Chat` in the left nav. Pick an agent. Pick the model you just configured. Type: + +> *Hi. What can you do right now?* + +Hit enter. Watch the tokens stream. + +If you saw an answer come back, **the system is alive and you're in the product.** Everything from here is about making it useful to you, not about making it work. + +--- + +## First useful moves + +You've got a working install. Now what? + +**Try a tool-using prompt.** Type *"Search the web for the latest Spring Boot release and summarize the breaking changes."* Watch the agent pick up a search tool, execute, observe the result, and come back with an answer. That's ReAct in action. + +**Create your first agent.** `Agents → New Agent`. Start from a template — the templates ship ready to work. Rename it, tighten the system prompt, choose which tools it can use, save. Agents are how you go from one chat window to a whole workforce. + +**Build your first knowledge base.** `Wiki → New Knowledge Base`. Drop in a PDF or point at a local folder. Wait for digestion (you'll see the progress bar on each raw material row). When it's done, bind the KB to an agent and ask a question about the content. See [LLM Wiki](./wiki) for what's happening under the hood. + +**Connect a chat channel.** `Channels` → pick Telegram, DingTalk, or any of the eight supported platforms. Paste the bot credentials. The same agent starts answering in that channel with the same memory it has on your desktop. + +Each of those has its own page in the sidebar when you're ready to go deeper. + +--- + +## Something broke? + +First run should Just Work. If it didn't: + +- **Installer won't launch** — On Windows, right-click → Properties → Unblock. On macOS, allow the unsigned app in System Settings → Privacy & Security. +- **Backend never boots** — Check `~/.mateclaw/logs/app.log` (Windows: `%USERPROFILE%\.mateclaw\logs\`). Nine times out of ten it's a port conflict on 18088. +- **Model call fails** — Wrong API key or network can't reach the provider. Go back to Settings, re-verify the key, or try a different provider. +- **UI is blank** — Hard-refresh with Ctrl/Cmd+Shift+R. Electron caches aggressively. +- **Still broken** — Open an issue on [GitHub](https://github.com/matevip/mateclaw/issues) with the tail of `app.log`. We read them. + +--- + +## Other ways to run MateClaw + +- **Docker** — `cp .env.example .env`, set the passwords, then `docker compose up -d --build`. Full prerequisites, Maven mirror selection (China vs US), browser-tool self-check, and upgrade flow live in [Docker Deployment](./docker-deploy). +- **From source** — `mvn spring-boot:run` in `mateclaw-server/` and `pnpm dev` in `mateclaw-ui/`. See [Contributing](./contributing). +- **Desktop internals** — packaging, code signing, auto-update. See [Desktop App](./desktop). + +--- + +Next: [Introduction](./intro) for the "why", or jump straight to [Agents](./agents) for the product itself. diff --git a/mateclaw-server/src/main/resources/docs/en/releases.md b/mateclaw-server/src/main/resources/docs/en/releases.md new file mode 100644 index 00000000..42b117ff --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/releases.md @@ -0,0 +1,29 @@ +# Changelog + +Release notes for every MateClaw version. The latest documentation always lives in `docs/en/` as the single source of truth — these notes are about what *changed* in each version. + +For historical diffs, check the corresponding git tag. For the "why" behind a feature, follow the link into the full release note. + +--- + +## Releases + +| Version | Date | Highlights | +|---------|------|------------| +| [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 | +| [v1.1.137](./releases/1.1.137) | 2026-04-29 | It learns from yesterday now · One bad model doesn't take the whole thing down · The "almost good" parts are good now · The knowledge base became a library you can open | +| [v1.1.0](./releases/1.1.0) | 2026-04-17 | Auto skill synthesis, multi-agent parallel delegation, Wiki semantic search + two-phase digest, deep thinking, Anthropic prompt caching, declarative hooks, plugin SDK, voice for every channel, ChatConsole multi-channel realtime sync, WeChat stability rebuild | +| [v1.0.418](./releases/1.0.418) | 2026-04-11 | Backend i18n, Flyway migration framework, WorkspacePathGuard sandbox, CronJobTool, Skill ZIP import, security hardening | +| [v1.0.314](./releases/1.0.314) | 2026-04-08 | LLM Wiki knowledge base, TTS/STT, music generation, image/video upgrades, search system with keyless fallback, ChatGPT OAuth login, agent runtime enhancements, database schema unification | +| [v1.0.108](./releases/1.0.108) | 2026-04-06 | Datasource SQL query, multimodal enhancements, desktop dynamic port, OpenRouter free models | +| [v1.0.101](./releases/1.0.101) | 2026-04-05 | Mobile layout, Ollama auto-detection on startup, model grouping, GitHub MCP, drag-and-drop file upload, multi-agent collaboration | +| [v1.0.0](./releases/1.0.0) | 2026-03-20 | Initial release — ReAct + Plan-Execute agents, 12 built-in tools, MCP protocol, 6 channel adapters, Vue 3 admin console | + +--- + +## What to read next + +- [Roadmap](./roadmap) — what's planned, what's in progress, what's done +- [Introduction](./intro) — why MateClaw exists +- [Contributing](./contributing) — how to help ship the next release diff --git a/mateclaw-server/src/main/resources/docs/en/roadmap.md b/mateclaw-server/src/main/resources/docs/en/roadmap.md new file mode 100644 index 00000000..2a1b8c55 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/roadmap.md @@ -0,0 +1,201 @@ +# Roadmap + +> "People don't know what they want until you show it to them." +> +> This isn't a feature list. It's a manifesto about **how your AI assistant should exist.** + +--- + +## What we believe + +Everyone deserves an AI assistant that actually understands them. + +Not a chat toy. Not a tech demo. A **digital counterpart** — one that knows how you work, connects to all your tools, thinks for you, executes for you, and remembers for you. + +**MateClaw is that thing.** + +--- + +## What we've shipped + +### v1.0 — It thinks and acts ✅ Released + +Make an AI assistant a coworker who uses tools, not a chat box. + +- ReAct engine: reason, act, observe, reason again +- Plan-and-Execute orchestration: plan first, then execute step by step +- StateGraph architecture: state-graph-based agent orchestration +- DynamicAgent: loaded from database at runtime, adjustable without restart +- 20 built-in tools: search / shell / file I/O / delegate / multimodal generation / cron / SQL +- Tool Guard + File Guard + Audit Log: every tool call has approval, control, and a record +- SKILL.md skill system: install new capabilities into your AI like apps + +### v1.1 — It's everywhere ✅ Released + +Move AI out of the chat box on a webpage and into every IM your team actually uses. + +- **8 channels**: Web / DingTalk / Feishu / WeCom / Telegram / Discord / QQ / WeChat Personal / Slack +- Session source tracking: every message knows which channel it came from +- 4-layer memory: session context + workspace memory + post-chat extraction + 2 AM consolidation +- DREAMS.md consolidation diary: human-readable audit of memory changes +- Workspace isolation: every agent / skill / wiki / conversation / memory belongs to a workspace +- ChatGPT OAuth + Anthropic Claude Code OAuth: log in with your subscription, no API key +- LLM Wiki + RAG: raw files become structured pages with bidirectional links and summaries + +### v1.2 — It's your coworker ✅ Released (2026-05-05) + +Renamed "agents" to **digital employees** — not vocabulary purism, a worldview shift. + +- **Digital employees** with Role / Goal / Backstory — not a cold system prompt +- **5 career templates**: product researcher / customer support / knowledge curator / data analyst / executive assistant — open one, it works +- **Skills are no longer aliases for tools** — each skill is a backbone with its own SKILL.md + LESSONS.md + workspace filesystem +- **ACP bridge**: Claude Code, Codex, Gemini CLI plug in as employees +- **Backstage runtime console**: for the first time you can **see what each employee is doing right now** — who's running, on which step, how many tokens, kill them in one click +- **Onboarding wizard**: first-login four-step flow from zero to first message +- **Dashboard**: daily usage trend + top agents/tools +- **Doctor**: system health checks + one-click fix + +Full story: [v1.2.0 release notes](./releases/1.2.0.md). + +--- + +## v1.3 — The workflow year ✅ Shipped (2026-05-13) + +> "Focus is about saying no to the hundred other good ideas that there are." + +Each digital employee being able to do work is just the beginning. **Real collaboration needs orchestration.** + +The v1.3 line is **graduating MateClaw from a chatbot framework to a business-process OS** — a flow is no longer the sum of several employees chatting separately, but a publishable, triggerable, replayable **linear-step DSL**. + +Full story: [v1.3.0 release notes](./releases/1.3.0.md). + +### Workflow + +- [x] **7 step modes**: sequential / fan_out / collect / conditional / await_approval / dispatch_channel / write_memory +- [x] **Pebble expression subset** for conditionals + variable references (no side effects, no code execution) +- [x] **JSON-first authoring**: Monaco + JSON-schema validation + static Pebble checking + template dropdown +- [x] **Natural language → workflow draft** (`POST /workflows/draft/generate`): a user describes the flow, an agent emits `graph_json` + compile diagnostics; never publishes directly — a human still reviews +- [x] **Integer revisions**: publish writes a new immutable row; draft is split from published version +- [x] **Run history**: every step's input / output / duration / token / failure chain is recorded +- [x] **Internal payload storage**: large I/O goes through `payload://` URIs — doesn't blow out the DB +- [x] **Cross-workspace ACL**: publish-time validation rejects agent / channel / employeeId references outside the workspace +- [x] **Persistent `await_approval` pause**: survives service restarts + +### Triggers + +- [x] **6 pattern types**: cron / webhook / channel_message / agent_lifecycle / content_match / workflow_completion +- [x] **Event governance on by default**: dedup (60s window), per-trigger rate limit, bot-self-msg filter, A→B→A recursion guard +- [x] **CronDelegationPort**: shares ShedLock + Spring TaskScheduler with the legacy cron module without writing into mate_cron_job +- [x] **Cross-instance consistency**: `pattern_version` self-cancellation + periodic syncFromDatabase +- [x] **Structured forms**: each of the 6 pattern types has its own field UI — no need to hand-write patternJson + +### Existing experience upgrades + +- [x] **Image editing** (issue #75): `image_generate` gains `image` / `images` parameters with 5 reference forms (including `msg::` for in-conversation attachments) +- [x] **DashScope OpenAI-compatible variant**: same sk- key, reaches the dot-versioned families (qwen3.5-plus / qwen3.6-plus / qwen3-vl-plus etc.) +- [x] **New Wanxiang / Qwen-Image families**: 14 new image models, 3 new video models (including happyhorse-1.0-t2v) +- [x] **4 document-generation tools**: DocxRenderTool / XlsxRenderTool / PptxRenderTool / PdfRenderTool — Markdown rendered directly into Office files, no subprocess fork, no npm dependency +- [x] **MCP per-agent tool binding**: every employee binds MCP tools individually + status badges (connected / stale / unavailable / orphan) + namespace collisions auto-prefixed + server renames auto-followed +- [x] **Xiaomi MiMo provider**: MiMo V2.5 Pro / V2.5 / V2 Pro / V2 Omni / V2 Flash +- [x] **Multimodal sidecar routing** (issue #87): when a text-only primary model meets an image attachment, the configured vision model captions it first so the primary chat stays cheap; the old "do not call any tools" hard ban is gone, so user-built tools are no longer suppressed; routing badge on the bubble and a hint above the input box make every decision visible + +### Still to do in v1.3 + +- [ ] **Canvas editor (v1)**: today's canvas is read-only chain rendering; the goal is `@vue-flow/core` drag-to-edit +- [ ] **Run replay view**: trace timeline + hover any node to diff input/output +- [ ] **`loop` mode**: iterate N times or per-item over an array +- [ ] **`invoke_skill` mode**: call a skill directly without going through an employee +- [ ] **Inter-trigger priority / dependency**: serial / parallel control when an event hits multiple triggers +- [ ] **Event replay**: a "redispatch" button on `mate_trigger_event` rows + +--- + +## Next: v1.4 — The scenario-application year + +> "When the tools are good enough, hide the tools and put the scenarios in front." + +v1.0 → v1.3 builds out the infrastructure: employees, memory, knowledge bases, tools, skills, workflows, triggers, multimodal, channels. **The next move isn't another bolt** — it's assembling these parts into **scenarios users can drop in and use**. + +The v1.4 keyword is **scenario applications**. Not "more features" — **letting normal users get value without learning 7 step modes and 6 trigger pattern types**. + +### Industry scenario templates (workflow + trigger combos) + +Each one is **a one-click-importable workflow template + trigger config + recommended employee bindings + recommended KB structure**: + +- [ ] **Customer ticket triage**: WeCom / Feishu entry → digital-employee classification → route / escalate / auto-reply → write to customer record +- [ ] **Morning / weekly report automation**: cron trigger → multi-employee parallel data collection → data analyst summarizes → generate PDF/PPTX → multi-channel dispatch +- [ ] **Contract approval flow**: contract upload → legal-employee first review → approval wait → legal-employee revision suggestions → write to archived memory +- [ ] **Market intel monitoring**: webhook trigger (site change) → content_match filtering → business analyst summary → Feishu bot push +- [ ] **New employee onboarding**: webhook (HRIS hire event) → executive assistant pulls doc checklist → training-KB onboarding → multi-day follow-up triggers +- [ ] **Code PR review**: GitHub webhook → code-reviewer employee runs review → comments back to PR → flag critical changes through await_approval + +### Scenario marketplace + +- [ ] **Scenario package format**: one scenario = `workflow.json` + `triggers.json` + `agents/*.md` + `knowledge/*.md` + `README.md`, shareable / installable +- [ ] **Scenario marketplace UI**: browse / try-run / one-click install / ratings + reviews +- [ ] **Scenario package versioning**: upgrade prompts + diff preview + rollback + +### Cross-scenario employee collaboration + +- [ ] **Employee directory profile**: each employee auto-gains "good at / weak at" tags (based on history + skills + tool set) +- [ ] **Scenario suggestions**: user describes "I want a flow that does X" → recommend the closest scenario template + existing employees +- [ ] **Cross-scenario memory sharing**: customer ticket triage and contract approval see the same customer record + +### Hide the infrastructure further + +- [ ] **Natural language → full scenario package**: v1.3 already does "NL → workflow draft"; v1.4 extends it to **the whole scenario** — one sentence yields a draft of workflow + triggers + recommended employees + recommended KB structure +- [ ] **Self-diagnosis wizards**: typical issues like "my workflow stuck waiting on approval" become self-serve diagnostics +- [ ] **Scenario-level dashboards**: not "tokens spent today" but "average customer-ticket handling time today" + +### Foundational capabilities advancing in parallel + +- [ ] **Scenario-level ACL**: installing a scenario package atomically configures the required channel / agent / KB / tool allowlists +- [ ] **Cross-workspace scenario sharing**: scenario templates reusable across workspaces (clone + override) +- [ ] **Scenario cost estimation**: see expected tokens / API calls / trigger frequency before installing + +--- + +## What we deliberately don't do + +> "I'm as proud of the things we haven't done as the things we have done." + +| Cut | Why | When it might return | +|-----|-----|---------------------| +| **Full RBAC permission model** | MateClaw is a digital-employee system, not an enterprise management platform. A single team doesn't need 100 permission combinations | When real multi-team SaaS customers need fine-grained permissions | +| **Multi-tenancy** | Same as above. Premature multi-tenancy is architectural cancer | When there's a clear SaaS commercialization path | +| **SSO / LDAP / SAML** | Enterprise integration is a bottomless pit | When paying enterprise customers explicitly ask | +| **30+ node visual workflow editor** | Most users won't reach for it. **v1.3's 7 step modes already cover 90% of real-world scenarios**; the rest is pushed to LLM natural-language generation | When a user case actually needs 30+ nodes (rare) | +| **Native mobile app** | 8 IM channels + desktop + Web already cover it. On your phone, you use MateClaw via DingTalk / Feishu / Telegram | When Web / IM channels can't deliver an irreplaceable mobile-only feature | +| **Replacing ReAct / Plan-Execute** | Workflow and those two engines **collaborate**, not replace — single-agent multi-turn reasoning still lives there | Never replaces | + +--- + +## Version milestones + +| Version | One line | User experience goal | Status | +|---------|----------|----------------------|--------| +| **v1.0** | It thinks and acts | An AI assistant that uses tools to solve problems | ✅ Released | +| **v1.1** | It's everywhere | 8 channels + 4-layer memory + workspaces + LLM Wiki | ✅ Released | +| **v1.2** | It's your coworker | Digital employees + 5 career templates + backbone-style skills + ACP bridge + Backstage runtime | ✅ Released | +| **v1.3** | It orchestrates business flows | Workflow + triggers + image editing + document generation + per-agent tool binding | ✅ Released | +| **v1.4** | **It lands real scenarios** | **Industry scenario templates + scenario marketplace + NL → workflow + cross-scenario employee profiling** | 📋 Planned | + +--- + +## One More Thing + +We're not building MateClaw to chase ChatGPT, not to be the next Dify, not to add another buzzword to a funding deck. + +We're building it because we believe one thing: + +**AI shouldn't be a chat box on a webpage. It should be your second brain.** + +It lives in your DingTalk, your Feishu, your Telegram. It's read every document you have. It remembers what you said three months ago. It uses your company's internal tools. It consolidates memory while you sleep. **It runs an entire business flow on your behalf.** + +Someday, you'll forget it's a program. + +**That's the day we win.** + +--- + +*Stay hungry. Stay foolish.* diff --git a/mateclaw-server/src/main/resources/docs/en/security.md b/mateclaw-server/src/main/resources/docs/en/security.md new file mode 100644 index 00000000..86124e4e --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/security.md @@ -0,0 +1,565 @@ +# Security & Approval + +**Strong hands, firm limits.** + +MateClaw gives agents real capability — shell access, file writes, browser automation, delegation to other agents, remote tools over MCP. That's the "strong hands" half. This page is about the other half: the limits that keep strong hands from doing stupid things. + +- **JWT auth** — who you are +- **Tool Guard (rule-based)** — what each agent is allowed to do +- **Approval workflow** — when a human needs to decide before execution +- **File Guard** — what the filesystem looks like to an agent +- **Workspace isolation** — what each team can see +- **Audit log** — what everybody did, in order, forever + +If you're running MateClaw in production, read this page top to bottom. + +::: tip Agentic, but not autonomous +Every IT department and CISO in 2025–2026 has the same question before buying AI: + +> **"What if the agent goes off the rails and deletes the wrong thing?"** + +Anyone who tells you "AI won't go off the rails" is lying. MateClaw's answer is different — **the agent asks you first when it matters.** + +When the agent wants to delete a file, send an email, run a write-side SQL, or hit a paid API — any tool call matched by a Tool Guard rule **pauses mid-turn**. An approval notification is pushed to your IM (Feishu / DingTalk / Slack / email). You tap approve, the agent resumes from where it stopped. Every action lands in `mate_tool_guard_audit_log` — append-only, retained as long as you want, CSV-exportable. + +**Agentic — it acts. Not autonomous — it doesn't act on its own initiative for the things that matter.** + +That's the line between "let AI do work for you" and "let AI make decisions for you." MateClaw stays on the left side of that line — which is also the side your CISO doesn't immediately say no to. +::: + +--- + +## JWT authentication + +### How it works + +1. The user posts credentials to `/api/v1/auth/login` +2. The server validates and returns a JWT +3. Every subsequent request includes the token in the `Authorization` header +4. The server validates the token on each request + +### Logging in + +```bash +curl -X POST http://localhost:18088/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username": "admin", "password": "admin123"}' +``` + +Response: + +```json +{ + "code": 200, + "data": { + "token": "eyJhbGciOiJIUzI1NiJ9...", + "tokenType": "Bearer", + "expiresIn": 86400 + } +} +``` + +### Password change + +Users can change their own password from the profile settings dialog. Admins can reset any member's password from member management. + +--- + +### Sliding window renewal + +MateClaw does sliding-window token renewal. When a token's remaining lifetime falls below the configurable `renewal-threshold` (default 2 hours / 7200000ms), the server issues a new token in the `X-New-Token` response header. The frontend picks it up and replaces the stored token transparently. Active users never get kicked out; idle sessions still expire on time. + +### Configuration + +```yaml +mateclaw: + auth: + jwt: + secret: your-secret-key-must-be-at-least-32-characters-long + expiration: 86400000 # 24h in milliseconds + sliding-window: true +``` + +::: warning +**Change the default JWT secret in production.** At least 32 characters. Set via env var (`JWT_SECRET=...`), never commit. +::: + +### Error codes + +| Code | Meaning | Response | +|------|---------|----------| +| 401 | Token missing, expired, or invalid | `{"code": 401, "message": "Unauthorized"}` | +| 403 | Valid token but insufficient permissions | `{"code": 403, "message": "Forbidden"}` | + +Frontend handles both uniformly — redirect to login, clear stored tokens. + +### Default credentials + +MateClaw ships with `admin` / `admin123`. **Change this immediately in any deployment other than your laptop.** + +### 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/**` +- **CSRF disabled** — not needed for stateless JWT + +--- + +## Tool Guard — rule-based permission engine + +Tool Guard is how MateClaw decides what a tool call is allowed to do. **It's not a flat dangerous-tools list.** It's a rule engine. Each rule specifies: *for this tool, optionally matching these arguments, in this workspace, do X* — where X is `allow`, `deny`, or `require_approval`. + +### The three tables + +| Table | Purpose | +|-------|---------| +| **`mate_tool_guard_config`** | Global config — enabled, default policy, approval timeout, notification channels | +| **`mate_tool_guard_rule`** | Individual rules — tool pattern, optional arg regex, workspace scope, action, priority | +| **`mate_tool_guard_audit_log`** | Every guarded call gets an entry — tool, args, rule matched, decision, user, timestamp | + +### How a rule is evaluated + +``` +Tool call arrives + │ + ▼ +Load rules for this workspace + global rules, sorted by priority + │ + ▼ +For each rule in priority order: + ┌─ Does the tool name match the pattern? + │ └─ No → next rule + ├─ Does the arg pattern match (if any)? + │ └─ No → next rule + └─ Yes on both → apply this rule's action and stop + │ + ▼ +No rules matched → apply default policy + │ + ▼ +Action: allow / deny / require_approval + │ + ▼ +Write audit log entry + │ + ▼ +Execute / reject / suspend for approval +``` + +Rules with higher priority run first. First matching rule wins. A rule can be scoped to a specific workspace or global. + +### Example rules + +``` +Rule 1 (priority 100): ShellExecuteTool, arg matches "^(ls|cat|grep|find)\\s" → allow +Rule 2 (priority 50): ShellExecuteTool → require_approval +Rule 3 (priority 50): WriteFileTool, arg.path starts with "/tmp" → allow +Rule 4 (priority 40): WriteFileTool → require_approval +Rule 5 (priority 30): * → allow (default) +``` + +Read-only shell commands execute immediately. Anything else needs approval. File writes under `/tmp` are free; elsewhere they need approval. Everything else runs. + +### Managing rules + +`Settings → Security & Approval → Tool Guard Rules`: list, create, edit, reorder, disable. Or via config: + +```yaml +mateclaw: + tool: + guard: + enabled: true + default-policy: require_approval + rules: + - tool: ShellExecuteTool + arg-pattern: "^(ls|cat|grep|find)\\s" + action: allow + priority: 100 + - tool: ShellExecuteTool + action: require_approval + priority: 50 + - tool: WriteFileTool + arg-pattern: "^/tmp/" + action: allow + priority: 50 + - tool: WriteFileTool + action: require_approval + priority: 40 +``` + +Or via API: + +```bash +curl -X POST http://localhost:18088/api/v1/security/guard/rules \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "tool": "ShellExecuteTool", + "argPattern": "^(ls|cat|grep|find)\\s", + "action": "allow", + "priority": 100 + }' +``` + +### Dangerous pattern detection + +In addition to user-defined rules, MateClaw's shell tool has built-in detection for patterns that are dangerous no matter what. `find -delete`, `rm -rf /`, piped downloads through `bash`, and similar patterns trigger elevated approval even if a rule would otherwise allow them. + +--- + +## Approval workflow — human in the loop + +When a rule evaluates to `require_approval`, MateClaw doesn't fail the call. It **suspends the agent mid-turn**, creates a pending approval, surfaces it to the user, and resumes exactly where it left off once the user decides. + +::: tip From 1.3.0: workflows ride the same approval rail +The v1.3.0 [workflow](./workflow) `await_approval` step suspends the entire workflow run on the same `mate_tool_approval` table — persisted across restarts. Approval requests fan out to the approver's channel (Feishu / DingTalk / Slack / WeCom); once resolved, the workflow runtime auto-resumes the next step. One audit log, one notification pipeline, one "pause / resume" semantic — covering both agent tool calls and workflow steps. +::: + +### How it flows + +``` +Agent calls tool + │ + ▼ +Tool Guard: require_approval + │ + ▼ +Create mate_tool_approval row (status=pending) + │ + ▼ +Set AWAITING_APPROVAL=true in graph state + │ + ▼ +Emit approval_required SSE event + │ + ▼ +Graph terminates cleanly + │ + ▼ +Frontend shows approval card + │ + ▼ +User clicks Approve or Reject + │ + ▼ +POST /api/v1/approvals/{id}/resolve + │ + ├─ Approved → reload agent, replay tool call, continue reasoning + └─ Rejected → send rejection as observation, continue reasoning +``` + +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 `mate_tool_approval` table + +| Column | Purpose | +|--------|---------| +| `id` | Primary key | +| `agent_id` | Which agent is waiting | +| `conversation_id` | Which conversation is suspended | +| `tool_name` | The tool being called | +| `tool_args` | JSON of the actual arguments | +| `rule_id` | Which rule triggered the approval | +| `status` | `pending` / `approved` / `rejected` / `expired` | +| `requested_at` | When the approval was created | +| `resolved_at` | When the user decided | +| `resolved_by` | Who decided | +| `notes` | Optional user notes on the decision | + +### Placeholder substitution + +Sometimes the agent's tool arguments contain placeholders — a computed file path, a templated command. The approval workflow **resolves placeholders before showing the dialog**, so users see the actual values they're approving. Approval returns the resolved values too, so what the agent executes is exactly what the user saw. + +### Timeouts + +Pending approvals expire after a configurable timeout (default: 10 minutes). Expired approvals become `rejected`, and the agent treats expiry the same as user rejection. + +### Notifications + +MateClaw can notify through `channel/notification/` adapters — email, in-app alert, DingTalk/Feishu push. Configure in `Settings → Security & Approval → Notifications`. + +### Resolving via API + +```bash +# List pending +curl http://localhost:18088/api/v1/approvals?status=pending \ + -H "Authorization: Bearer " + +# Approve +curl -X POST http://localhost:18088/api/v1/approvals/123/resolve \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"decision": "approved"}' + +# Reject with reason +curl -X POST http://localhost:18088/api/v1/approvals/123/resolve \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"decision": "rejected", "notes": "Not appropriate for this workspace"}' +``` + +--- + +## File Guard + +File Guard is filesystem-level access control. It sits underneath any tool or skill that reads or writes files, and decides what paths are in-bounds. + +### Evaluation pipeline + +``` +File access request + │ + ▼ +Path normalization (resolve .., symlinks, relative paths) + │ + ▼ +Allowlist check: is the path inside an allowed directory? + │ + ▼ +Denylist check: is the path inside a denied directory? + │ + ▼ +Symlink check: does following the path escape the sandbox? + │ + ▼ +Allow / Deny +``` + +### Rules built in + +| Rule | Description | +|------|-------------| +| Workspace isolation | Default access restricted to the workspace directory | +| System path denial | `/etc`, `/usr`, `/bin`, `/boot`, etc. blocked | +| Sensitive file protection | `.ssh`, `.config`, `.env` blocked | +| Path traversal prevention | `../` attacks detected and blocked | +| Symlink check | Symlink targets resolved and re-validated | + +### Configuration + +```yaml +mateclaw: + security: + file-guard: + enabled: true + allowed-paths: + - "${user.dir}/workspace" + - "${java.io.tmpdir}/mateclaw" + denied-paths: + - "/etc" + - "/usr" + - "${user.home}/.ssh" + - "${user.home}/.config" + - "${user.home}/.env" +``` + +Visual editor on `Settings → Security & Approval → File Guard`. + +--- + +## Workspace isolation + +Workspaces are how MateClaw keeps multiple teams' data separate. Every agent, skill, wiki, conversation, and memory file belongs to exactly one workspace. + +### Security primitives that follow workspace boundaries + +- **File Guard** — path allowlists default to `workspace/{workspaceId}/...` +- **Tool Guard rules** — can be scoped to a specific workspace +- **Wiki knowledge bases** — owned by a workspace, readable only by members +- **Memory files** — every agent's memory is under its workspace's directory +- **Channels** — each channel belongs to a workspace + +### Roles + +| Role | Can do | +|------|--------| +| **Owner** | Everything, including deleting the workspace | +| **Admin** | Everything except deleting/changing owner | +| **Member** | Use agents, read/write wiki, create conversations | +| **Viewer** | Read-only — see agents and KBs, can't create or modify | + +Full details in [Workspaces](./workspaces). + +### What isolation does NOT cover + +- **Shared global config** — JWT secret, model provider keys, MCP server definitions are global +- **Audit logs** — all workspaces' security events are in the same audit log; only admins with audit access read across workspaces + +--- + +## Audit log + +Every security-relevant action is recorded in `mate_audit_event`. **Append-only** — you can't modify an entry, and rows are retained for the configured window (default 90 days). + +### What gets logged + +| Event type | Captured data | +|------------|---------------| +| **Tool calls** | Tool name, args, result summary, duration, agent, workspace | +| **Tool Guard decisions** | Rule matched, action taken, rule ID | +| **Approvals** | Who approved/rejected, when, notes | +| **File Guard decisions** | Path, allow/deny, reason | +| **Skill executions** | Skill name, parameters, agent | +| **Login events** | User, IP, success/failure | +| **Configuration changes** | Old and new values for security-relevant settings | + +### Entry schema + +``` +timestamp When it happened +user_id Who did it (system for automated events) +action What they did +resource What it was done to +details JSON blob with the specifics +result success / failure / denied +ip_address Source IP when applicable +workspace_id Which workspace this belongs to +``` + +### Querying + +`Settings → Security & Approval → Audit Log`: filterable view by time range, event type, user, workspace, result. Export to CSV. + +Via API: + +```bash +curl "http://localhost:18088/api/v1/audit/events?from=2026-04-01&to=2026-04-11&action=tool_call" \ + -H "Authorization: Bearer " +``` + +--- + +## Skill security scanning + +Custom skills are scanned for dangerous patterns before they become active: + +| Check | What it looks for | +|-------|-------------------| +| **Prompt injection** | Attempts to override system prompts, hidden instructions | +| **Dangerous tool references** | Tools not in the allowlist, or tools requiring approval without declaration | +| **External URL references** | Links to untrusted external resources | +| **Script injection** | Embedded scripts or code execution attempts | + +### Severity levels + +| Level | Action | +|-------|--------| +| `CRITICAL` | Install blocked; must be fixed | +| `HIGH` | Warning + admin must confirm | +| `MEDIUM` | Warning displayed; install allowed | +| `LOW` | Logged only | +| `INFO` | Logged only | + +Scan reports live in `Settings → Security & Approval → Skill Scans`. + +--- + +## API key protection + +- API keys encrypted at rest in the database +- Keys **masked** (`sk-****abcd`) in every API response — never returned in full after creation +- MCP server `env_json` and `headers_json` values sanitized the same way +- Environment variable references (`${VAR}`) in MCP config resolve at runtime from the process environment + +--- + +## Network security + +### Production recommendations + +| Recommendation | Details | +|----------------|---------| +| **HTTPS** | Reverse proxy with TLS (Nginx or Caddy) | +| **Disable H2 console** | `spring.h2.console.enabled=false` in production | +| **Firewall** | Only expose the public port | +| **Rate limiting** | Configure at the reverse proxy level | +| **MySQL, not H2** | Use a dedicated MySQL 8 instance for production | + +### Nginx reverse proxy example + +```nginx +server { + listen 443 ssl; + server_name mateclaw.example.com; + + ssl_certificate /etc/ssl/certs/mateclaw.pem; + ssl_certificate_key /etc/ssl/private/mateclaw.key; + + location / { + proxy_pass http://localhost:18080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # SSE support + proxy_buffering off; + proxy_read_timeout 86400s; + } +} +``` + +--- + +## Security best practices + +1. **Change the default password.** Right now. On every deployment. +2. **Set a real JWT secret.** At least 32 characters, via environment variable, never committed. +3. **Least privilege.** Only enable the tools agents actually need. +4. **Default to `require_approval`.** Flip the Tool Guard default policy, then add `allow` rules for safe cases. Newly added tools default to safe. +5. **Configure File Guard.** Lock down allowed/denied paths before any agent touches the filesystem in anger. +6. **Review audit logs regularly.** Set a recurring reminder. Look for anomalies. +7. **Watch your skill scans.** CRITICAL findings shouldn't be bypassed lightly. +8. **Isolate networks.** Ollama, H2 console, internal MCP servers — none should be public. +9. **Don't skip approvals in production.** Auto-approve rules should be narrow and specific. `allow *` is a crisis waiting to happen. + +--- + +## Security configuration reference + +```yaml +mateclaw: + auth: + jwt: + secret: ${JWT_SECRET:your-secret-key-at-least-32-chars} + expiration: 86400 + sliding-window-ratio: 0.5 + + tool: + guard: + enabled: true + default-policy: require_approval + approval-timeout-seconds: 600 + notifications: + email-enabled: false + dingtalk-enabled: false + + security: + file-guard: + enabled: true + allowed-paths: + - "${user.dir}/workspace" + denied-paths: + - "/etc" + - "${user.home}/.ssh" + + audit-log: + enabled: true + retention-days: 90 + + skill: + security-scan: + enabled: true + block-critical: true +``` + +--- + +## Next + +- [Tools](./tools) — tool details and Tool Guard rule patterns +- [Skills](./skills) — skill security scanning details +- [Workspaces](./workspaces) — workspace isolation primitives +- [Agents](./agents) — how approval pauses and resumes an agent turn +- [Configuration](./config) — full configuration reference diff --git a/mateclaw-server/src/main/resources/docs/en/skills.md b/mateclaw-server/src/main/resources/docs/en/skills.md new file mode 100644 index 00000000..33b9c752 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/skills.md @@ -0,0 +1,528 @@ +# Skills + +**A skill is a tool that thinks in sentences.** + +Tools are atomic — read a file, send an HTTP request, run a command. Skills are compositions — "research this topic and write a brief", "review this code and comment on it", "turn my git log into a standup update". A skill is a `SKILL.md` file that combines instructions, parameters, prompt templates, optional scripts, and a list of tools the skill needs. The runtime loads it, renders it with your inputs, and hands the result to the agent. + +If tools are hands, skills are recipes. + +--- + +## Five kinds of skills + +| Type | Where it comes from | Who maintains it | +|------|--------------------|------------------| +| **`builtin`** | Ships with MateClaw under `skills/` in the classpath | The core team | +| **`custom`** | Created by you through the UI, API, or dropping a file into the workspace | You | +| **`dynamic`** | Auto-synthesized by agents during work | The agent + your approval | +| **`mcp`** | Backed by a tool exposed from an MCP server (a same-name `custom` skill shadows it) | The MCP server author | +| **`acp`** | Bridged from an external Agent Client Protocol endpoint (Claude Code, Codex, etc.) | The upstream agent service | + +All five flow through the same runtime pipeline. Only the source differs. + +--- + +## The SKILL.md protocol + +Every skill is one Markdown file with YAML frontmatter. The frontmatter is the contract. The body is the prompt. + +```markdown +--- +name: web-researcher +title: Web Researcher +description: Search the web and summarize findings on a given topic +version: 1.0.0 +type: custom +author: your-name +tools: + - WebSearchTool + - ReadFileTool +tags: + - research + - search +parameters: + - name: topic + type: string + required: true + description: The topic to research + - name: depth + type: string + required: false + default: brief + description: Level of detail (brief, detailed, comprehensive) +--- + +# Web Researcher + +You are a web research assistant. When given a topic, you should: + +1. Use WebSearchTool to find relevant information about {{topic}} +2. Evaluate source credibility +3. Compile findings into a {{depth}} summary +4. Include source URLs in your response + +## Output Format + +Present your findings as: +- **Summary**: 2-3 sentence overview +- **Key Facts**: Bullet-point list +- **Sources**: Numbered list of URLs +``` + +Two things to notice. First, the body is a prompt — not a description of one. It's what the skill will say to the agent at runtime, with `{{topic}}` and `{{depth}}` filled in. Second, the `tools:` list is a contract: the runtime guarantees those tools are available when the skill runs. If the agent doesn't have them, the skill call fails early with a clear error. + +### Frontmatter fields + +| Field | Required | Purpose | +|-------|----------|---------| +| `name` | ✅ | Unique identifier (kebab-case) | +| `title` | ✅ | Human-readable display name | +| `description` | ✅ | One-line summary | +| `version` | ✅ | Semantic version | +| `type` | ✅ | `builtin`, `custom`, `mcp` | +| `author` | — | Skill author | +| `tools` | — | List of tool names the skill requires | +| `tags` | — | Categorization | +| `parameters` | — | Typed input parameters | + +### Parameter schema + +| Field | Required | Purpose | +|-------|----------|---------| +| `name` | ✅ | Parameter name (used in `{{name}}` interpolation) | +| `type` | ✅ | `string`, `number`, `boolean`, `array` | +| `required` | — | Whether it must be provided (default: false) | +| `default` | — | Fallback value if caller omits | +| `description` | ✅ | What the parameter controls | + +--- + +## The runtime pipeline + +``` +1. RESOLVE Look up the skill by name in mate_skill + │ + ▼ +2. VALIDATE Check that required parameters are provided + │ + ▼ +3. RENDER Replace {{parameter}} placeholders in the SKILL.md body + │ + ▼ +4. INJECT Append the rendered instructions to the agent's system prompt + │ + ▼ +5. BIND TOOLS Verify required tools are available; fail fast if missing + │ + ▼ +6. EXECUTE The agent processes the enriched prompt with bound tools +``` + +Skills don't run scripts by default — they **shape the agent's behavior** for the duration of the call. The agent's next reasoning step sees the skill's rendered instructions as part of its system prompt. The exception is skills that ship with a script — `SkillScriptTool` can execute a skill's bundled script file, gated by Tool Guard. + +### Template rendering + +Skill bodies support `{{parameterName}}` placeholders. With `{topic: "quantum computing", depth: "detailed"}`: + +```markdown +Research the topic "{{topic}}" at a {{depth}} level of detail. +``` + +…renders to: + +```markdown +Research the topic "quantum computing" at a detailed level of detail. +``` + +Missing parameters fall back to defaults. Unknown placeholders are left intact. + +--- + +## Skill storage + +The database is the source of truth, the filesystem is a materialized cache. That's always been the rule for **SKILL.md**, and **as of v1.3 it applies to scripts/ and references/ too**. + +### Database: `mate_skill` + `mate_skill_file` + +`mate_skill` — skill identity and body: + +| Column | Purpose | +|--------|---------| +| `id` | Primary key | +| `name` | Unique name | +| `title` | Display title | +| `description` | One-line summary | +| `type` | `builtin` / `custom` / `mcp` | +| `content` | Full `SKILL.md` content | +| `version` | Semantic version | +| `enabled` | On/off | +| `tags` | JSON array | +| `create_time` / `update_time` | Timestamps | + +`mate_skill_file` (new in v1.3, migration `V112`) — the **canonical copy** of every bundle file: + +| Column | Purpose | +|--------|---------| +| `id` | Primary key | +| `skill_id` | FK to `mate_skill` | +| `file_path` | Relative path like `scripts/run.py` or `references/cfg.md` | +| `content` | UTF-8 text (≤1 MB per file, ≤50 MB per bundle) | +| `content_size` | Byte count (so listings don't have to load the blob) | +| `sha256` | Content fingerprint, drives the syncer's idempotent diff | + +### Filesystem: skill workspace + +``` +~/.mateclaw/skills/ +├── translate/ +│ ├── SKILL.md # Skill definition +│ ├── references/ # Reference materials +│ └── scripts/ # Optional executable scripts +├── code-review/ +│ ├── SKILL.md +│ └── ... +└── .archived/ # Archived old versions + └── translate-20260401-143000/ +``` + +Think of it as "Maven Local Repository, but for skills" — except the local repo can now hydrate itself from the database. + +### Auto-sync on startup + +Two sync passes run at boot, so every node has the latest bundle: + +1. `SkillWorkspaceBootstrapRunner` → `BundledSkillSyncer` scans the classpath `skills/` directory and syncs **bundled skills** into the workspace root. **Only syncs when the target directory doesn't exist**, so it never clobbers local modifications. +2. `SkillFileSyncer` diffs `mate_skill_file` (DB) against the local workspace (FS) by `sha256` and materializes anything missing or stale. + +**Why this matters for multi-instance deployments**: one node accepts the upload, the DB row + file rows are written, every other node either restarts or hits `POST /api/v1/skills/{id}/sync-files` to receive the full bundle. No NFS, no scp loop, even desktop clients can hand a skill off across machines. + +> Upgrade path: pre-v1.3 installs have files on disk but no `mate_skill_file` rows. The first time `SkillFileSyncer` runs on a freshly upgraded node, it **backfills from disk** into the canonical store; from then on the two stay in lockstep. + +### Robust zip install + +Third-party packagers package weirdly — some put `setup.sh` at the zip root, some emit `scripts/` entries before `SKILL.md`. As of v1.3, `ZipSkillFetcher`: + +- **Two-pass extraction** — the entire archive is buffered in memory first (cap-protected at 50 MB), `SKILL.md` is located and the wrapper-dir prefix computed, then entries are classified. **Zip entry order no longer affects the result.** +- **Root-level extension fallback** — files sitting next to `SKILL.md` that aren't already under a known bucket get classified by extension: `.sh / .py / .js / .rb / ...` → `scripts/`, `.md / .json / .yaml / .csv / ...` → `references/`. Unknown extensions are dropped with a `WARN` line so packaging mistakes surface instead of vanishing. +- **Write-then-prune + empty-bundle guard** — reinstalls **write new files first, then prune anything in the bucket that's not in the new bundle**. If the new bundle has zero entries for a bucket (`scripts/` or `references/`), the disk copies for that bucket are **left alone** — a malformed re-extract can no longer wipe your scripts. Pass `forcePrune=true` if you really want to clear a bucket via an intentionally empty bundle. + +> Real failure this catches: the official tencent-meeting-mcp zip puts `setup.sh` at the package root (not under `scripts/`). The old extractor silently dropped it; the new one auto-classifies it as `scripts/setup.sh` and the skill installs ready to run. + +### Configuration + +```yaml +mateclaw: + skill: + workspace: + root: ${user.home}/.mateclaw/skills + auto-init: true + delete-policy: archive # `archive` or `ignore` + bundled-skills-path: skills +``` + +--- + +## Skill Market (and ClawHub) + +The **Skill Market** page (`/skills`) is where you browse, install, edit, and manage skills. Three sources: + +- **Built-in** — skills that ship with MateClaw +- **Your custom skills** — the ones you created +- **ClawHub** — a community skill repository. Browse thousands of community skills, preview them, install with one click. Installed skills land as `custom` type. + +ClawHub is optional — if you're offline or don't want external skills, just don't touch that tab. + +--- + +## Skill Market API + +```bash +# List all skills +curl http://localhost:18088/api/v1/skills \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# Create a custom skill +curl -X POST http://localhost:18088/api/v1/skills \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "code-reviewer", + "title": "Code Reviewer", + "description": "Review code for bugs, style issues, and improvements", + "type": "custom", + "content": "---\nname: code-reviewer\n...", + "tags": ["development", "review"] + }' + +# Enable / disable +curl -X PUT http://localhost:18088/api/v1/skills/1 \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -d '{"enabled": true}' + +# Delete +curl -X DELETE http://localhost:18088/api/v1/skills/1 \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" +``` + +Delete policy is configurable — by default, deletion moves the skill workspace to `.archived/` rather than erasing it. + +--- + +## Writing a custom skill — step by step + +1. **Decide what the skill does.** One sentence. +2. **List the tools it needs.** Three or fewer is a good target. +3. **Write the parameters.** Required ones first, optional with defaults. +4. **Write the body.** Address the agent directly: *"You are X. When given Y, do Z."* +5. **Upload** via the Skill Market UI or API. +6. **Bind** the skill to one or more agents. +7. **Test** by sending a message that should trigger the skill. + +Example — "Daily Standup" skill: + +```markdown +--- +name: daily-standup +title: Daily Standup Generator +description: Generate a daily standup update based on recent git activity +version: 1.0.0 +type: custom +tools: + - ShellExecuteTool +parameters: + - name: repo_path + type: string + required: true + description: Path to the git repository +--- + +# Daily Standup Generator + +Generate a standup update by analyzing recent git activity. + +## Steps + +1. Run `git log --oneline --since="yesterday" --author=$(git config user.name)` + in the directory {{repo_path}} +2. Summarize completed work +3. Identify any work-in-progress branches +4. Format as a standup update: + - **Yesterday**: What was completed + - **Today**: What is planned based on open branches + - **Blockers**: Any merge conflicts or failing tests +``` + +--- + +## Workspace isolation + +Each workspace gets its own copy of skills. When you enable a skill for a workspace, its files are staged under that workspace's directory, the skill's tools are scoped to that workspace, and any file the skill writes stays inside the workspace boundary. See [Workspaces](./workspaces). + +--- + +## Auto Skill Synthesis + +Agents that work with you long enough start noticing patterns — a recurring database query, a particular report layout, the exact commands to SSH into your box. Agents can **turn those patterns into skills on their own**. + +The flow: + +1. The agent recognizes a reusable workflow during task execution +2. The agent proposes a new skill (create / edit / patch / delete) +3. You review in ChatConsole — check the content, rename if you want, approve or reject +4. On approval, the skill saves as `dynamic` type, ready for reuse + +**Security scan runs automatically before save** — dangerous patterns (prompt injection, script injection) are blocked. Skills can migrate between agents and export as ZIP. + +The agent's memory grows with you. No more repeating "remember I like tables sorted this way." + +--- + +## Template wizard: start from a starter + +Don't know how to write a SKILL.md? Open the wizard. + +`Skills → Create Wizard`: + +1. Pick a **starter template** (8 of them: researcher, code reviewer, writing assistant, customer-support script, data analysis, Claude Code helper, Codex helper, blank) +2. Fill in the variables — name, parameters, a few sentences of description +3. Upload any supporting files (scripts, references, prompt fragments) +4. Set secrets (API keys, etc.) — **secrets go to a vault, not into SKILL.md** +5. Save + +You don't get just a SKILL.md. You get a **multi-file bundle** — SKILL.md, references/, scripts/, secret references — packaged together. + +--- + +## Pre-flight check before installation + +A skill that's installed isn't necessarily a skill that runs — it might need an API key, a CLI tool, a MateClaw feature flag toggled on. + +Used to be: install, run, fail, debug. Now: + +**Pre-flight install dialog** — runs the prerequisite check automatically before the skill goes live: + +- Are the required tools present? +- Are the required API keys configured? +- Are the required feature flags on? +- Are the dependent MCP / ACP endpoints reachable? + +Whatever's missing is reported up front, with a one-click **`[Set Up]`** button that jumps to the right config page. **No more install-then-debug.** + +--- + +## LESSONS.md: skills that learn from experience + +Each skill can carry a `LESSONS.md` — what the skill learned during runs. + +- After a run, the skill can **proactively write a lesson**: "Last time the user didn't like that format, don't do it again" +- Next time the same skill is invoked, LESSONS get auto-injected into the prompt context +- The more it's used, the better it knows **when to step in and when to stay out** + +This is the first cut of skill self-evolution. Skills go from a list of instructions to something with playbooks, experience, and the capacity to grow. + +LESSONS are viewable and editable in the skill detail drawer's **Memory tab**. + +--- + +## Secrets: put the token in the right place + +Lots of skills need API credentials to function — tencent-meeting needs `TENCENT_MEETING_TOKEN`, Slack needs a bot token, Linear needs a personal API key. Those values **don't belong in SKILL.md** (it goes into the prompt and leaks to the LLM), don't belong in scripts (one git push and you're sorry), and editing `~/.zshrc` requires restarting the server and won't follow the skill across machines. + +As of v1.3, every skill has its own **per-skill secret store**. + +### Manage it in the UI + +Skill detail drawer → **Secrets** tab. One table plus a form: + +``` +Key Value Last updated Actions +TENCENT_MEETING_TOKEN sk••••ef 2026-05-12 [Edit] [Delete] + +[+ Add secret] +``` + +- **Plaintext never leaves the server** — listing returns only `preview` (`sk••••ef`-style mask); the add/edit dialog's value field starts blank, saving overwrites whatever was there. +- **Client-side validation** — keys must match `^[A-Za-z_][A-Za-z0-9_]{0,127}$`; bad keys are rejected in the browser before submission. +- **Value field is ``** — shoulder-surfers, screenshots, and password managers all stay out. + +### How it's stored / how it's injected + +| Stage | What happens | +|---|---| +| Write | `POST /api/v1/skills/{id}/secrets` `{key, value}` → AES-encrypted → `mate_skill_secret` | +| Read | Before subprocess launch, `SkillSecretService.getDecrypted(skillId)` AES-decrypts | +| Inject | `ProcessBuilder.environment().putAll(...)` — **overrides parent-process env vars of the same name** | + +The injection rule is **secret-store wins, `.zshrc` is the fallback**. For multi-user / multi-machine deployments, desktop clients, and corporate accounts that don't share databases, the secret store is the more reliable source of truth. + +### REST endpoints + +```bash +# List (masked) +GET /api/v1/skills/{id}/secrets +# Upsert (empty value deletes) +POST /api/v1/skills/{id}/secrets {"key":"...", "value":"..."} +# Delete +DELETE /api/v1/skills/{id}/secrets/{key} +``` + +### A full example: tencent-meeting + +``` +SkillMarket → tencent-meeting-mcp card → detail drawer → Secrets tab + → + Add secret → key=TENCENT_MEETING_TOKEN, value= + → Save + +Then when the agent runs setup.sh or scripts/tencent_meeting.py: + ProcessBuilder env carries $TENCENT_MEETING_TOKEN + → mcporter / Python script calls the Tencent API → meeting ID returned +``` + +No `~/.zshrc` edit, no mateclaw restart needed. + +--- + +## Discoverability: a skill installed should be a skill found + +Installing a new skill used to mean the agent often couldn't find it. Three causes, three fixes, all in v1.3. + +### 1) New skills are **boosted** in the prompt catalog + +The agent's system prompt carries a compact Skills table. Each model gets a row cap based on its max input tokens — qwen-turbo with 8192 tokens gets only **8 entries**. A brand-new skill has zero usage history, so the existing recent / frequent / RECOMMENDED sort buries it behind ~40 older skills, well below the cutoff. + +v1.3 inserts a "**installed in the last 7 days**" sort key at the front of the ranker. Install on Friday, the skill is still in the first frame on Monday — long enough to span a weekend, short enough not to occupy a slot indefinitely. Builtins and virtual MCP/ACP rows are excluded (you didn't "just install" them). + +### 2) `listAvailableSkills()` teaches the LLM how to search wider + +The tool description now explicitly says: + +- The default page is 20 entries; if you see `Showing: 20 of 47`, **retry with `keyword=` or `limit=50`** +- If the user mentions a specific skill name, **skip the catalog** — go straight to `readSkillFile(skillName="", filePath="SKILL.md")` to verify + +Truncated results carry a one-line hint at the end so even small models can see how to follow up. + +### 3) Calling a skill name as a tool **auto-redirects** + +LLMs occasionally call a skill name as if it were a tool (`tencent-meeting-mcp({...})`). The previous behavior was a textual hint telling them to call `readSkillFile` instead — which qwen-turbo-class models often can't act on. They reply "let me get that for you" and end the turn without any further tool call, producing a dead loop. + +As of v1.3, when `ToolExecutionExecutor` sees this case AND `readSkillFile` is bound to the agent, it **transparently invokes readSkillFile on the LLM's behalf** and returns the SKILL.md content (prefixed with `[auto-redirect]` and the original args echoed back) as the tool result. The model has runnable instructions in front of it on its very first attempt and goes straight to `runSkillScript`, no loop. + +> This fix helps small models a lot and doesn't hurt large models (they would have followed the textual hint anyway). + +--- + +## ACP bridge: plug in external coding agents + +ACP (Agent Client Protocol) is a protocol that lets external agent clients (Claude Code, Codex, other compatible clients) plug into MateClaw as skills. + +Once installed: + +- ACP endpoints **auto-bridge into skill cards** — they show up on the Skills page with a wrapper toolset +- **Visual env editor** — every endpoint's required key, URL, CWD, configurable in the UI +- **Per-session cwd** — every ACP session has its own working directory +- **Errors translated** — upstream messages like "Request not allowed" get translated into something actionable +- **OAuth keychain hijack detection** — if your OAuth token has been hijacked by another app, you're prompted to re-authenticate + +Templates: `claude-code-helper`, `codex-helper` — install and go. + +A digital employee calls an ACP skill the same way it calls a built-in tool. + +--- + +## Detail drawer: everything in one place + +Every skill card opens a drawer with eight tabs: + +- **Overview** — identity fields, manifest projection, source, version +- **Body** — `SKILL.md` editor (takes over the full drawer width) +- **Tools** — which tools this skill uses (with effective tool expansion) +- **Features** — capability matrix +- **Security** — content scan results, related Tool Guard rules +- **Lessons** — `LESSONS.md` content +- **Secrets** — env-var-style credentials (new in v1.3; see the "Secrets" section below) +- **Memory** — digital employees bound to this skill + +The card itself is slim — six fields and one status pill. **Clear beats comprehensive.** + +--- + +## Security + +Custom skills go through several checks before they become live: + +- **Content scanning** — `SKILL.md` scanned for prompt injection and script injection on upload +- **Tool requirement check** — `tools:` list must only reference tools that exist +- **Tool Guard compliance** — skills with dangerous tools inherit Tool Guard rules +- **MCP skill constraints** — MCP-backed skills inherit the security constraints of their MCP server + +Full review in [Security & Approval](./security). + +--- + +## Next + +- [Tools](./tools) — tools that skills can use +- [Agents](./agents) — how agents invoke skills during a turn +- [MCP](./mcp) — MCP-backed skills +- [Security & Approval](./security) — skill scanning details diff --git a/mateclaw-server/src/main/resources/docs/en/tools.md b/mateclaw-server/src/main/resources/docs/en/tools.md new file mode 100644 index 00000000..7c72b181 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/tools.md @@ -0,0 +1,322 @@ +# Tools + +**A tool is a hand the agent can reach out with.** + +Left to its own devices, a language model is a pattern-matcher wrapped in text. It doesn't know what time it is. It doesn't know what's in your files. It can't search the web, run a command, look at a PDF, delegate to another agent, or open a browser. It can only *talk about* doing those things. + +Tools are how MateClaw fixes this. Each tool is a concrete operation the agent is allowed to invoke — read a file, search the web, execute a shell command, extract text from a PDF, delegate to another agent. When the agent decides it needs one, it emits a **tool call**, the runtime executes it, and the result comes back as an **observation**. + +Fourteen tools ship built-in. Unlimited more can be added through MCP servers, custom skill scripts, or your own `@Tool`-annotated Spring beans. + +--- + +## How a tool call actually happens + +``` +Agent decides it needs a tool + │ + ▼ + Emits a tool call: {"name": "WebSearchTool", "args": {"query": "..."}} + │ + ▼ + ┌─────────────────────┐ + │ Tool registry │ ← look up the tool by name + └─────────────────────┘ + │ + ▼ + ┌─────────────────────┐ + │ Tool Guard │ ← rule-based check: allow / deny / approval + └─────────────────────┘ + │ + ┌────┴────┐ + │ │ + ▼ ▼ + allowed approval pending → user decides → allowed / rejected + │ + ▼ + ┌─────────────────────┐ + │ Execute (timeout) │ ← async, per-tool timeout + └─────────────────────┘ + │ + ▼ + Result → observation → agent's next reasoning step +``` + +Tool Guard is the gatekeeper. Timeouts are per-tool (so one slow tool can't freeze a turn). Execution can be concurrent inside a single Action phase — if the agent calls three independent tools at once, they run in parallel. + +None of this shows up in the agent's prompt. The agent just asks for a tool. The runtime handles everything in front of, during, and after the call. + +--- + +## Tool registration — three paths + +**1. Built-in tools.** The twenty tools that ship with MateClaw — registered into the tool table on startup. + +**2. MCP servers.** External processes speaking the Model Context Protocol expose tools dynamically. MateClaw discovers them via `tools/list` and they appear in the registry alongside built-in ones. See [MCP](./mcp). + +**3. Skill scripts.** Skill packages can ship executable scripts that get wrapped as tools at runtime. See [Skills](./skills). + +Tool discovery is **blacklist-style** — every discoverable tool is registered by default. Exclude specific tools explicitly. Newly added tools don't get silently missed. + +--- + +## The twenty built-in tools + +| Tool | What it does | Dangerous | +|------|--------------|-----------| +| `DateTimeTool` | Current date/time in any timezone | — | +| `WebSearchTool` | Search via the provider chain (Serper / Tavily / DuckDuckGo / SearXNG) | — | +| `ReadFileTool` | Read file contents | — | +| `WriteFileTool` | Write content to a file | ⚠️ | +| `EditFileTool` | Find-and-replace edit | ⚠️ | +| `ShellExecuteTool` | Execute a shell command | ⚠️ | +| `FileTypeDetectorTool` | Detect MIME type and encoding | — | +| `DocumentExtractTool` | Extract text from PDF, DOCX, XLSX | — | +| `WorkspaceMemoryTool` | Read/write the agent's workspace memory | — | +| `SkillFileTool` | Read and manage `SKILL.md` files | — | +| `SkillScriptTool` | Execute skill scripts | ⚠️ | +| `SkillManageTool` | Create / edit / delete skill packages | ⚠️ | +| `BrowserUseTool` | Drive a headless browser | ⚠️ | +| `DelegateAgentTool` | Delegate a task to another agent (parallel supported) | — | +| `MateClawDocTool` | Read built-in project documentation | — | +| `ImageGenerateTool` | Text-to-image / **image-to-image (1.3.0+)** | — | +| `VideoGenerateTool` | Text-to-video / image-to-video generation | — | +| `DocxRenderTool` | **1.3.0+** Markdown → .docx (Word document) | — | +| `XlsxRenderTool` | **1.3.0+** Markdown tables → .xlsx (Excel) | — | +| `PptxRenderTool` | **1.3.0+** Markdown (Marp-style `---` slide breaks) → .pptx | — | +| `PdfRenderTool` | **1.3.0+** Markdown → publication-grade PDF (CJK fonts embedded) | — | +| `CronJobTool` | Create and manage scheduled tasks | ⚠️ | +| `DatasourceTool` | Manage external datasource connections | ⚠️ | +| `SqlQueryTool` | Execute SQL queries on connected datasources | ⚠️ | + +Plus the `MusicGenerateTool` from [Multimodal](./multimodal). And the 14 Wiki tools from [LLM Wiki](./wiki): `wiki_read_page`, `wiki_read_many`, `wiki_list_pages`, `wiki_search_pages`, `wiki_semantic_search`, `wiki_compile_page`, `wiki_trace_source`, `wiki_create_page`, `wiki_delete_page`, `wiki_archive_page`, `wiki_unarchive_page`, `wiki_related_pages`, `wiki_explain_relation`, `wiki_enrich_page`. + +### DateTimeTool + +Returns the current date and time for a given timezone. Zero surprises. + +``` +Input: {"timezone": "America/New_York"} +Output: "2026-04-11T14:30:22" +``` + +### WebSearchTool + +Web search via a **provider chain** — DuckDuckGo and SearXNG as keyless fallbacks, Serper and Tavily when you have keys. Configured in `Settings → System → Search Service` and takes effect without restart. + +``` +Input: {"query": "Spring AI Alibaba latest version", "freshness": "month", "count": 5} +Output: "Spring AI Alibaba 1.1 was released..." +``` + +Features: + +- **Provider chain** — falls through to the next on failure. Keyless providers provide baseline coverage. +- **Advanced parameters** — `freshness` (day/week/month/year), `language`, `count`. +- **Result caching** — recent queries are cached. +- **Security wrapping** — results sanitized before return. +- **Provider-native + tool search coexistence** — models with their own search (ChatGPT, Gemini) can use that natively while tool search is available as fallback. + +### ShellExecuteTool + +Cross-platform shell execution. Linux/macOS uses `/bin/sh -c`; Windows uses `cmd.exe /D /S /C`. **Every call is gated by Tool Guard.** + +Safety design: + +- **Timeout** — 60s default, 300s hard cap +- **Output caps** — stdout and stderr capped at 10,000 bytes each +- **File-backed output** — stdout/stderr to temp file, not pipe +- **Structured result** — `{exitCode, stdout, stderr, timedOut}` +- **Dangerous-pattern detection** — `find -delete`, `rm -rf /`, piped bash downloads trigger elevated approval + +``` +Input: {"command": "ls -la /tmp"} +Output: "total 48\ndrwxrwxrwt 12 root root..." +``` + +### ReadFileTool / WriteFileTool / EditFileTool + +Read is safe. Write and Edit are both gated by Tool Guard. + +### DocumentExtractTool + +PDF, DOCX, XLSX, and friends become plain text. Scanned documents get OCR fallback where available. + +### Office document generation (1.3.0+) + +Four new tools that render Markdown directly into downloadable Office files — **no subprocess fork, no npm dependency**. Generated bytes are cached in memory and returned as a one-time download URL: + +| Tool | Use for | Key capabilities | +|---|---|---| +| `DocxRenderTool.renderDocx` | Reports / memos / contracts / resumes | Headings (# ## ###) / bold (**text**) / lists / tables / images (PNG/JPG/GIF/BMP/SVG → PNG) | +| `DocxRenderTool.renderDocxFromFile` | Same, but markdown is in a workspace file | Avoids the LLM having to repeat its own large markdown body as a tool argument | +| `XlsxRenderTool.renderXlsx` | Financial sheets / data exports / templates | Markdown table syntax → multiple sheets (split by `## SheetName`) | +| `PptxRenderTool.renderPptx` | Decks / project plans / briefings | Marp-style `---` slide breaks; `16:9` (default) / `4:3` aspect | +| `PptxRenderTool.renderPptxFromFile` | Same, but markdown in a file | Preferred when the deck body exceeds 5KB | +| `PdfRenderTool.renderPdf` | Publication-grade documents / weekly reports / templated docs | 1in margins / smart pagination / page numbers / cover page / mixed CJK + Latin (CJK fonts embedded) | + +::: tip Relationship with the existing `skills/docx` skill +The `skills/docx` skill **stays** — it's good at **editing existing .docx** (tracked changes, complex XML ops) and runs `npm install docx` on first use. The four new tools handle the "create-from-scratch" path with **no npm warm-up cost**. Agents prefer these RenderTools; fall back to the skill only when modifying an existing .docx. +::: + +### ImageGenerateTool — image edit support from 1.3.0 + +In v1.2.0 this tool was text-to-image only. v1.3.0 adds two parameters — `image` and `images` — for **multi-image input editing**. See [Multimodal](./multimodal#image-edit). + +### WorkspaceMemoryTool + +Lets an agent read, write, and edit its own workspace memory files — `MEMORY.md`, `PROFILE.md`, daily notes, anything under `workspace/{agentId}/`. Safety rules: `.md` only, no directory traversal. See [Memory](./memory). + +### BrowserUseTool + +Drives a headless browser. Navigate, click, type, extract. Every call gated by Tool Guard. + +### DelegateAgentTool — agents delegating to agents + +One agent can hand off a subtask to another: + +- **`delegateToAgent(agentName, task)`** — call a specific agent by name, run in isolated conversation, return the result +- **`listAvailableAgents()`** — list all available agents with name, type, description + +``` +User: Search for Spring AI news and have Writer summarize it +Agent A: [calls WebSearchTool] + [calls delegateToAgent(agentName="Writer", task="Summarize: ...")] + [receives Writer's response] + Replies with the combined result +``` + +Safety: + +- **Recursion cap** — maximum 3 delegation levels deep +- **Isolated sessions** — the delegated agent runs in its own conversation +- **Result truncation** — delegated results capped at 4000 characters + +### MateClawDocTool + +Reads the built-in MateClaw project documentation. Lets an agent answer "how does X work in MateClaw" questions by consulting actual docs rather than guessing. + +--- + +## Tool Guard — the permission layer + +Tool Guard is how MateClaw keeps strong tools from doing stupid things. It's **rule-based**, not a flat dangerous-tools list. Each rule says: *for this tool, with these arguments, in this context, do X* — where X is `allow`, `deny`, or `require_approval`. + +Core pieces: + +- **`mate_tool_guard_rule`** — individual rules with tool pattern, optional arg pattern, action +- **`mate_tool_guard_config`** — global config: enabled/disabled, default policy, approval timeout +- **`mate_tool_guard_audit_log`** — every guarded call leaves an entry + +Example rule: *allow `ShellExecuteTool` when the command starts with `ls`, `cat`, `grep`, or `find`. Require approval for anything else.* + +```yaml +mateclaw: + tool: + guard: + enabled: true + default-policy: require_approval + rules: + - tool: ShellExecuteTool + arg-pattern: "^(ls|cat|grep|find)\\s" + action: allow + - tool: WriteFileTool + action: require_approval +``` + +Or manage interactively on `Settings → Security & Approval`. When a rule requires approval, the runtime persists a row in `mate_tool_approval` and suspends the agent turn. When the user decides, the agent resumes where it paused. Full mechanism in [Security & Approval](./security). + +### Declarative hook system + +Tool Guard rules are a special case of a more general mechanism — the **declarative hook system**. Five lifecycle hooks cover every critical moment in tool and LLM execution: + +| Hook | Fires when | Typical use | +|------|-----------|-------------| +| `before_tool` | Before tool execution | Argument redaction, context injection, extra validation | +| `after_tool` | After tool execution | Result filtering, audit logging | +| `before_llm` | Before LLM call | Prompt enrichment, cache hit check | +| `after_llm` | After LLM returns | Output filtering, token accounting | +| `on_error` | On error | Alerting, fallback strategy | + +Hooks run in-process. They can transform arguments, transform results, mask sensitive fields, and add audit log entries. You can use hooks for things beyond Tool Guard — like injecting a security policy before every LLM call, or auto-redacting sensitive fields from tool returns. + +--- + +## Execution: concurrent, isolated, bounded + +- **Concurrent execution** — within a turn, independent tool calls run in parallel. Guard checks are sequential; execution is concurrent where safe. +- **Per-tool timeouts** — every tool has its own timeout. Defaults: fast tools 30s, shell/browser 60s, generation tools up to 300s. +- **Segment isolation** — when approvals are needed mid-turn, the segment splits at the approval boundary. +- **Observation truncation** — long tool results are automatically truncated before being added to observation history. +- **Error isolation** — one tool failure does not abort the turn. + +--- + +## Tool management via API + +```bash +# List all tools +curl http://localhost:18088/api/v1/tools \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# Enable / disable +curl -X PUT http://localhost:18088/api/v1/tools/1 \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -d '{"enabled": false}' + +# Test a tool directly +curl -X POST http://localhost:18088/api/v1/tools/WebSearchTool/test \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -d '{"query": "Spring AI"}' +``` + +Every provider-backed tool has a test button in the Tools page so you can verify API keys before shipping. + +--- + +## Creating a custom tool + +### Option 1: a `@Tool`-annotated Spring bean + +```java +@Component +public class FactorialTool { + + @Tool(description = "Calculate the factorial of a number") + public String factorial( + @ToolParam(description = "The number to compute factorial for") int n) { + long result = 1; + for (int i = 2; i <= n; i++) { + result *= i; + } + return String.valueOf(result); + } +} +``` + +- Spring `@Component` +- Every `@Tool` method becomes a callable tool +- Use `@ToolParam` on every parameter — that's the LLM description +- Return value is what the agent sees +- **If the tool is dangerous, add a Tool Guard rule for it** + +Restart and the tool is live. + +### Option 2: a skill script + +Don't want to write Java? Bundle behavior into a skill package with a `SKILL.md` and a script. See [Skills](./skills). + +### Option 3: an MCP server + +Capability already exists as an MCP server? Just add the server configuration. See [MCP](./mcp). + +--- + +## Next + +- [Skills](./skills) — higher-level capabilities built on tools +- [MCP](./mcp) — external tool providers +- [Security & Approval](./security) — Tool Guard rules, approval flow, audit log +- [Multimodal](./multimodal) — generation tools (image, video, music, TTS, STT) diff --git a/mateclaw-server/src/main/resources/docs/en/triggers.md b/mateclaw-server/src/main/resources/docs/en/triggers.md new file mode 100644 index 00000000..597db022 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/triggers.md @@ -0,0 +1,259 @@ +# Triggers + +::: tip New in 1.3.0 +The trigger system is available from v1.3.0. In v1.2.0 and earlier, workflows and agent conversations could only be invoked manually. +::: + +**What triggers are**: a connector between "events that happen in the system" and "actions to perform". Events can be a cron schedule, a webhook arriving, a channel message, an employee finishing a conversation, or another workflow completing. Actions are either starting a workflow or sending a message to an employee for processing. + +**What triggers are not**: +- Not a replacement cron-job manager — `mate_cron_job` still exists and runs independently; triggers **share** its ShedLock + scheduler base but **do not write into** `mate_cron_job` +- Not an IFTTT / n8n drag-to-edit automation builder — triggers only do "event → action" routing; complex logic belongs in [Workflow](./workflow.md) +- Not a full-feature webhook dispatcher — they handle dedup / rate-limit / bot-self filtering / pattern matching, not arbitrary business-payload parsing + +::: warning v1.3.0 scope +v0 = 6 pattern types + 2 dispatch targets (agent / workflow). Event governance (dedup, per-trigger rate limit, recursion guard, bot-self filtering) is on by default. +::: + +--- + +## One-minute overview + +```jsonc +// A trigger that runs a "morning report" workflow daily at 9 AM +{ + "name": "daily-morning-report", + "patternType": "cron", + "patternJson": { + "cronExpression": "0 0 9 * * *", + "timezone": "Asia/Shanghai" + }, + "targetType": "workflow", + "targetId": 12345, + "payloadTemplate": "{ \"date\": \"{{ now | date('yyyy-MM-dd') }}\" }", + "rateLimitPerMin": 10, + "dedupWindowSecs": 60, + "botSelfFilter": true, + "enabled": true +} +``` + +At 9 AM → backend grabs the ShedLock via `CronDelegationPort` → renders the payload → enqueues an async run of workflow `12345`. Other instances at the same moment are blocked by the lock; no double-fire. + +--- + +## Six pattern types + +Implemented in `TriggerPatternMatcher.java`. Each pattern matches its `pattern_json` block on the trigger row. **Fields not listed here are ignored** by v0's matcher. + +| Pattern | When it fires | `pattern_json` fields | Reuse constraint | +|---|---|---|---| +| `cron` | On a cron expression (**does not flow through ingest**; runs from the scheduler) | `cronExpression`, `timezone` | Reuses the `cron/` module's ShedLock + Spring TaskScheduler; **does NOT write into mate_cron_job, does NOT call CronJobService** | +| `webhook` | Generic event passthrough (**v0 does no further filtering** — secret check happens at the channel layer; the trigger itself just matches `patternType=webhook`) | (none in v0) | Through the unified `POST /api/v1/triggers/events` entry + envelope wrap | +| `channel_message` | Channel receives a message | `channelType` (optional, compared against envelope `data.channelType`), `senderEquals` (optional, exact sender id match) | Side-channel through `ChannelWebhookController`; original routing unaffected | +| `agent_lifecycle` | Agent lifecycle events | `agentId` (optional), `phase` (optional: `spawned` / `terminated` / `crashed`) | Hangs off `ReActLifecycleListener` | +| `content_match` | Substring must appear in the envelope content | `substring` (**required**, case-insensitive contains-match against envelope `data.content`) | Generic content filter; the event source is whatever fed the envelope | +| `workflow_completion` | A workflow run reaches a terminal state | `sourceWorkflowId` (optional), `stateFilter` (optional: `completed` / `failed` / `any`) | Listens to `WorkflowEngine` terminal events; recursion guard below | + +> **Unknown pattern types fail closed by default** — typo'd or future pattern types can't silently fire every trigger in the workspace. +> +> **Not in v1.3.0**: `schedule` (one-shot non-cron like "30 minutes from now"), external MQ listeners (Kafka / Pulsar / RocketMQ), metrics / threshold alerting triggers. + +--- + +## Event governance (on by default) + +### Bot self-msg filtering (default binding is no-op) + +Some channels (Feishu / DingTalk / WeCom) surface bot-emitted messages back as `channel_message` events. The framework wires this through `BotSelfFilter` SPI + each trigger's `bot_self_filter` field (default `true`). + +::: warning v0 default implementation is no-op +The default-bound `NoopBotSelfFilter` returns `false` from `isBotSelf(...)` for every sender. That means `bot_self_filter=true` on a trigger **doesn't actually filter anything in v0** until a channel adapter registers a real `BotSelfFilter` Spring Bean (which replaces the default). This is intentional — a wrong default would silently swallow all legitimate bot-to-bot messages. +::: + +To exempt a single trigger from the framework filter (rare — e.g. a bot emitting a special command to trigger cleanup), set that trigger's `bot_self_filter` to `false`. + +### Event dedup + +When `TriggerEventIngestService` dispatches an event, the engine queries `mate_trigger_event` for the `dedup_key` within the `dedupWindowSecs` window (default 60s). Already present → **dropped**, `fire_count` not incremented. + +Default `dedupWindowSecs = 60`. Raise it to absorb longer gateway re-deliveries; set to `0` to disable (**not recommended**). + +### Per-trigger rate limit + +Each trigger is rate-limited individually: at most `rateLimitPerMin` per minute (default 10). Events past the cap are dropped — **no retry**, **no row in `mate_trigger_event`**; instead `mate_trigger.last_error` is updated to `"rate-limited"` so ops can see it. + +`channel_message` triggers usually want this raised (group bursts); `workflow_completion` triggers usually want it lowered (to slow A→B→A chains). + +### Recursion guard + +A `workflow_completion` trigger fires a workflow which fires another `workflow_completion`… dispatch chain length > 5 → engine cuts + alerts. Intended to break "A writes a message that triggers B, B writes a message that triggers A" loops. + +### Webhook ACK timing + +The HTTP entry (`POST /api/v1/triggers/events`) → envelope wrap → dedup check → bot-self check → rate-limit check → **immediate 200 ACK** → async dispatch. Implications: + +- Upstream gateways (Feishu / DingTalk etc.) get 200 and stop re-delivering +- Dispatch failures → `mate_trigger.last_error` updates; same `dedup_key` on retry is still dedup'd (**no automatic retry**) + +"ACK only after dispatch succeeds" semantics — **not in v0** — fire-and-forget is intentional for surge handling. + +--- + +## Managing triggers from the UI + +### Entry point + +`Triggers` (sidebar) → list + **+ New** drawer. + +### Creating a trigger + +The drawer has structured forms per pattern type — no hand-written `pattern_json`: + +- `cron` → cron expression input + timezone dropdown + next-fire preview +- `channel_message` → channel type (optional) + sender id exact-match (optional) +- `agent_lifecycle` → agent (optional) + phase: `spawned` / `terminated` / `crashed` (optional) +- `content_match` → substring (**required**), matched case-insensitively against envelope `data.content` +- `workflow_completion` → upstream workflow (optional) + state filter: `completed` / `failed` / `any` (optional) +- `webhook` → no extra fields in v0 (transparent passthrough) + +Save → trigger persists; with `enabled=true` it's registered with the right engine immediately (cron → ShedLock; others → envelope router). + +### Payload template + +The `payload_template` field is a Pebble template string; the rendered output becomes the input to the dispatch target (agent conversation or workflow run). + +```jsonc +"payload_template": "{ + \"date\": \"{{ now | date('yyyy-MM-dd') }}\", + \"trigger\": \"{{ trigger.name }}\", + \"sourceEvent\": {{ event | toJson }} +}" +``` + +Variables in the template: +- `now` — current time +- `trigger.{name,id,workspaceId}` — the firing trigger +- `event` — the current event envelope (`workspaceId` / `senderId` / `data` JSON, etc.) + +### Inspecting fire history + +`mate_trigger_event` is **dedup metadata only** — one row per accepted event with `trigger_id` / `dedup_key` / `received_at` / `expires_at`, and **no copy of the envelope itself**. To audit the actual content of a particular event, look at channel-layer logs + the agent / workflow run records. + +`mate_trigger.fire_count` honestly records dispatch count (excluding dedup'd / rate-limited events); `mate_trigger.last_error` carries the most recent failure reason. + +--- + +## API reference + +All endpoints under `/api/v1/triggers/`. **What v1.3.0 actually exposes** — the `/webhook/{slug}` / `/test-fire` / `/{id}/events` entries from the RFC are not yet implemented. + +| Method | Path | Description | +|---|---|---| +| `GET` | `/api/v1/triggers` | List all triggers in the current workspace | +| `GET` | `/api/v1/triggers/{id}` | Get details | +| `POST` | `/api/v1/triggers` | Create a new trigger; with `enabled=true`, registers with scheduler / router immediately | +| `PUT` | `/api/v1/triggers/{id}` | Update (including enable / disable — flip the `enabled` field); on `pattern_json` change, `pattern_version++` and stale futures self-cancel cross-instance | +| `DELETE` | `/api/v1/triggers/{id}` | Soft delete (equivalent to disable) | +| `POST` | `/api/v1/triggers/events` | **Unified event entry** — any webhook / channel adapter / internal module hands an envelope to the engine; engine runs dedup / bot-self / rate limit / pattern match / dispatch and returns a per-trigger fire / drop summary | + +--- + +## Relationship with the existing cron module + +::: tip Reuse, not replace +Before v1.3.0 MateClaw already had a standalone cron subsystem (`mate_cron_job` table + `CronJobService`). The trigger system **does not replace it** — +- Legacy cron jobs (`task_type = text / agent / reminder`) remain on the `Cron Jobs` page +- New trigger crons live on the `Triggers` page +- Both **share** the underlying ShedLock lock table + Spring TaskScheduler thread pool +- The `mate_cron_job` list **does not show** trigger crons, and vice versa +::: + +Why not merge? Because `mate_cron_job`'s legacy schema (required `task_type` / `agentId`, etc.) doesn't fit a workflow target. Forcing extra columns would break existing product invariants. `CronDelegationPort` is the v0 minimal solution — share the scheduler base, split the persistence layer. Folding `mate_cron_job` into trigger entirely is a future iteration. + +--- + +## Cross-instance consistency (multi-replica deploy) + +`CronDelegationPort` methods are **process-local** — local `ScheduledFuture` lives only in this JVM, no persisted handle. Cross-instance consistency relies on: + +1. Every instance, on startup, calls `syncFromDatabase()` to scan all enabled cron triggers and register locally +2. When a trigger is updated, `pattern_version++` + cancel local future +3. Each fire re-reads the trigger row before executing; mismatched `patternVersion` → **local short-circuit + self-cancel** (means another instance modified it) +4. ShedLock key = `"mate-trigger-{triggerId}"`, mutually exclusive across instances +5. Periodic `@Scheduled(fixedDelay=60s) syncFromDatabase()` as a fallback reconciler + +Practical implication: rolling-deploy multiple replicas needs no extra steps — new instances pick up automatically; old instances finish their last cycle and stop. + +--- + +## Data model + +### `mate_trigger` — trigger configuration + +Key fields: + +| Field | Type | Purpose | +|---|---|---| +| `pattern_type` | varchar | One of the six patterns | +| `pattern_json` | TEXT | The pattern's filter parameters as JSON | +| `target_type` | varchar | `agent` or `workflow` | +| `target_id` | bigint | Foreign key to the agent / workflow | +| `payload_template` | TEXT | Pebble render template | +| `dedup_window_secs` | int | Dedup window in seconds | +| `rate_limit_per_min` | int | Max fires per minute | +| `bot_self_filter` | bool | Enable bot-self filter (default `true`, but the default impl is no-op) | +| `pattern_version` | bigint | Optimistic-concurrency Lamport counter; auto-bumps on every `pattern_json` change; cross-instance fires compare before executing and self-cancel on mismatch | +| `fire_count` | bigint | Effective dispatch count (excluding dedup'd / rate-limited drops) | +| `last_error` | varchar | Most recent failure reason (`"rate-limited"` / exception messages) | +| `enabled` | bool | Soft on/off | +| `deleted` | int | Soft delete | + +### `mate_trigger_event` — dedup metadata + +Used only for dedup decisions. **Does not store envelope copies**: + +| Field | Type | Purpose | +|---|---|---| +| `id` | bigint | Primary key | +| `trigger_id` | bigint | Trigger this row dedups against | +| `dedup_key` | varchar | **Unique index**; the engine consults this within `dedup_window_secs` | +| `received_at` | timestamp | Insertion time | +| `expires_at` | timestamp | Window expiry; the same key can re-enter after this point | + +::: tip Design tradeoff +v0 deliberately **does not persist envelopes inside `mate_trigger_event`** — full-volume channel events would crush the DB. Event-payload audit relies on channel-layer logs + the run records on the agent / workflow side. If "event replay" becomes a real need, an envelope column gets added later. +::: + +--- + +## Known limitations (v1.3.0) + +- **No visualization of trigger → workflow chains** — multiple triggers dispatching to the same workflow appear as two independent lists in the UI +- **No inter-trigger priority / dependency** — when an event hits multiple triggers, dispatches are serialized by ascending DB id +- **No dedicated webhook entry / IP allowlist** — there's no `/webhook/{slug}` route in v0; `/events` is the unified entry. Stricter IP control belongs at the front-door nginx / gateway +- **`agent_lifecycle` granularity is `spawned` / `terminated` / `crashed`** — not "started / completed / failed" per step +- **No event replay** — `mate_trigger_event` only persists dedup metadata, not envelopes; "redispatch this event" requires the upstream source to re-emit + +--- + +## Troubleshooting + +| Symptom | Investigate | +|---|---| +| Cron trigger doesn't fire | 1) `enabled=true`? 2) Does the cron expression + timezone parse to a next-fire time? The editor previews it. 3) Is the ShedLock held by another instance? Check the `shedlock` table. | +| `POST /events` returns 200 but no dispatch happens | The response body contains a per-trigger fire / drop summary — look for `BOT_SELF` / `RATE_LIMITED` / `DEDUPED` / `PATTERN_MISMATCH` | +| `channel_message` doesn't fire | 1) Does the envelope's `data.channelType` match this trigger's `pattern_json.channelType`? 2) `bot_self_filter=true` and a non-default `BotSelfFilter` is filtering it? 3) For `content_match`, the `substring` field must actually appear in `data.content` | +| `agent_lifecycle` doesn't fire | Confirm `pattern_json.phase` is `spawned` / `terminated` / `crashed` (not `started` / `completed` / `failed`) | +| Cron trigger stops firing after restart | Look at startup log for `syncFromDatabase()` errors; common cause is corrupted `pattern_json` failing deserialization | +| `mate_trigger.last_error` reads `"rate-limited"` | Raise `rate_limit_per_min`, or split the trigger into multiple ones partitioned by group | +| `bot_self_filter=true` doesn't seem to filter | Confirm a non-noop `BotSelfFilter` Spring Bean is registered — the default `NoopBotSelfFilter` always returns `false` | + +--- + +## Related + +- [Workflow](./workflow.md) — where dispatches go when `target_type=workflow` +- [Agents](./agents.md) — where dispatches go when `target_type=agent` +- [Channels](./channels.md) — the source of `channel_message` events +- [Security & Approval](./security.md) — webhook secret + ACL backstop diff --git a/mateclaw-server/src/main/resources/docs/en/user-guide.md b/mateclaw-server/src/main/resources/docs/en/user-guide.md new file mode 100644 index 00000000..c756d541 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/user-guide.md @@ -0,0 +1,201 @@ +# User Guide + +You opened MateClaw because you want AI to do work for you. Not because you want to learn new software. + +This guide does one thing: **get you from "installed" to "it's working for me" as fast as possible.** + +--- + +## 60-Second Launch + +| Step | What | Time | +|------|------|------| +| 1 | Double-click, log in with `admin` / `admin123` | 10s | +| 2 | Settings → Models → Add Provider, **enable one**, paste your key | 30s | +| 3 | Chat → pick an Agent → say "Hello" | 10s | +| 4 | Watch the reply stream in → **the system is alive** | — | + +The moment you see a response, you're in the product. Everything after this is about making it **useful to you**. + +--- + +## Models: connect one + +**A fresh MateClaw install has an empty provider list. That's deliberate — you don't need to see 16 providers, you need one that works.** + +`Settings → Models → Add Provider` opens a drawer with the full catalog. + +| Your situation | Recommendation | +|---------------|----------------| +| Nothing set up, want the fastest path | **DashScope** — paste your key from Alibaba Cloud | +| Already have an OpenAI / Anthropic key | Drop it in | +| Have a ChatGPT Plus / Pro account | **ChatGPT OAuth** — browser login, no API key needed | +| Want data to stay on your machine | **Ollama** — auto-detects `localhost:11434` | + +In the drawer, **click Enable** on the provider you want, then fill in the base URL (pre-filled for known providers) and paste your API key, and save. The model appears in the chat screen's model picker immediately. + +::: tip Enable / disable are separate from configure +**Enable** surfaces the provider everywhere; **disable** removes it from the picker but keeps the configuration — switching providers temporarily no longer means deleting the config. +::: + +**One is enough.** Don't spend time configuring five providers — get the system running first, add more later. + +--- + +## Chat: the heart of the product + +Click "Chat" in the sidebar. Pick an agent. Pick a model. Type. Hit enter. + +That's the entire interaction. There is no other entry point. + +### Three things to try right now + +**1. Ask a direct question** + +> Explain the difference between Java virtual threads and platform threads + +The agent answers directly — no tools involved. You're seeing pure reasoning. + +**2. Make it use tools** + +> Search the web for the latest Spring Boot release and summarize the breaking changes + +The agent picks up the search tool, reads results, composes an answer. You see the full "think → act → observe → answer" cycle — that's ReAct in action. + +**3. Give it a multi-step task** + +> First check our Wiki for auth design decisions, then compare against Spring Security 6 best practices, and give me a gap analysis + +The agent breaks this into steps, executes each one, then consolidates. You see the plan and progress on every step. + +If all three work, **you understand 90% of the product.** + +--- + +## Agents: how the AI behaves + +`Agents → New Agent` + +An agent defines exactly five things: + +| Config | One line | +|--------|----------| +| **System prompt** | Who it is, how it talks, what attitude | +| **Model** | Which model to use | +| **Tools** | Which tools it can call | +| **Skills** | Which skill packages it can invoke | +| **Wiki** | Which knowledge bases it can read | + +Start from a template. Templates ship ready to work — rename it, tighten the system prompt, check the tools you want, save. 30 seconds for a new agent. + +::: tip When to create a new agent +When you find yourself repeating the same setup instructions every conversation — that's the signal. Put those instructions in the system prompt so you never have to say them again. +::: + +--- + +## Memory: it remembers you + +MateClaw's memory doesn't require manual management. After each conversation, the system automatically extracts key information and writes it to memory. Next time, the agent works with that context. + +What you can shape: + +- **PROFILE.md** — who you are, your preferences, how you work +- **MEMORY.md** — long-term facts and notes that accumulate over time +- **Daily memory** — system-generated conversation summaries + +Memory is shared across all channels. What you discussed on desktop, the DingTalk agent remembers too. + +--- + +## Wiki: make it read your documents + +`Wiki → New Knowledge Base` + +Drop in PDFs, DOCX, TXT, or point at an entire folder. Wait for digestion — every raw material shows a progress bar, no guessing. + +Once digested: + +1. Bind the knowledge base to an agent +2. Ask about the content +3. The agent automatically retrieves relevant pages and answers with knowledge + +::: tip +Wiki isn't full-text search. It's **semantic retrieval** — ask "what did we decide about authentication" and get the decision, not every page containing the word "auth." +::: + +--- + +## Skills and MCP: extend the boundary + +**Skills** — `Agents → pick one → Skills`. Install from the skill marketplace, or write a `SKILL.md` by hand. + +**MCP** — `Settings → MCP Servers`. Connect external tool servers (filesystem, databases, custom APIs). MCP tools appear in the tool list automatically — the agent doesn't know and doesn't need to know they're external. + +When the 20 built-in tools aren't enough, these two doors open up. + +--- + +## Channels: find it where you already are + +`Channels → pick a platform → paste credentials` + +Eight channels: DingTalk, Feishu, WeCom, WeChat Personal, Telegram, Discord, QQ, Slack. + +::: tip DingTalk & Feishu: just scan a QR (v1.1.0+) +No more "go to the open platform → create app → copy ID and Secret" detour. In the new channel form, click **Bind via QR**, scan with the DingTalk / Feishu app, confirm — **client_id / app_id and the secret auto-fill**. Under 30 seconds end to end. +::: + +Same agent. Same memory. Every channel. + +--- + +## Security: powerful but not out of control + +The **Security** page gives you three controls: + +1. **Tool Guard** — which tools require your approval before execution (shell, SQL, file writes) +2. **File Guard** — which directories the agent can't touch +3. **Audit log** — see everything the agent has done + +The defaults are already safe. If you're using this in production, tighten the shell and SQL approval rules. + +--- + +## Three starter setups + +### A. Personal assistant (fastest) + +Configure one model → use the default agent → start chatting. Memory accumulates automatically. + +### B. Knowledge assistant + +Create an agent → create a Wiki KB → import your docs → bind Wiki to agent. + +### C. Automated worker + +Create a role-specific agent → install skills → connect MCP servers → configure approval rules in Security. + +--- + +## Something broke + +| Symptom | Most likely cause | +|---------|-------------------| +| Backend won't start | Port 18088 is taken. Check `~/.mateclaw/logs/app.log` | +| Model call fails | Wrong API key or network issue. Go back to Settings | +| UI is blank | Ctrl+Shift+R to hard-refresh | +| Ollama says "does not support tools" | Switch to a function-calling model (qwen3, llama3.1:8b+) | +| Still broken | [GitHub Issues](https://github.com/matevip/mateclaw/issues) with the tail of `app.log` | + +--- + +## What's next + +| You want to... | Go to... | +|----------------|----------| +| Understand why the product is built this way | [Introduction](./intro) | +| See the technical architecture | [Architecture](./architecture) | +| Configure more options | [Configuration](./config) | +| Connect more channels | [Channels](./channels) | +| Deep-dive into the agent engine | [Agents](./agents) | diff --git a/mateclaw-server/src/main/resources/docs/en/wecom-tuning.md b/mateclaw-server/src/main/resources/docs/en/wecom-tuning.md new file mode 100644 index 00000000..fb842cc1 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/wecom-tuning.md @@ -0,0 +1,419 @@ +# WeCom Deep Tuning + +**A bot that actually works for a group of 50 internal employees needs much more than "just connecting".** + +The [Channels → WeCom](./channels#wecom) section covers wiring up the channel; this document covers what MateClaw does **after** the channel is up — every non-obvious optimization, every platform corner the adapter handles, and why. + +Audience: + +- Operators who already have the WeCom channel running and want to understand "why is the group experience like this" +- Developers planning new features who need the platform constraints first +- Tech leads evaluating the bot for a real business team + +--- + +## Platform in one sentence + +**WeCom AI Bot is a "looks-like-a-chat-SDK, actually-an-event-callback" platform.** + +It gives you three primitives: + +1. **Receive events** — long-poll WebSocket or webhook delivers user @-mentions +2. **Reply** (within the same conversation) — `aibot_respond_msg` "attaches" your answer to a specific inbound frame +3. **Push proactively** (not in reply to anything) — `aibot_send_msg`, but **single chats only** + +**The hidden rule that matters most**: primitive #2 and #3 behave differently in groups vs. single chats. Every optimization below is scaffolded around that matrix. + +--- + +## Group multi-user collaboration + +### Default platform behavior + +When users A, B, C all @ the bot in one group, the platform delivers each as a separate frame, but all keyed to the **same chatId**. + +If you naively partition conversations by chatId (the obvious approach), you get: + +- Persisted history is just `user: ...` with no sender prefix — the model sees an unattributed wall when reading prior turns +- The debounce window (500ms / 2.5s adaptive) merges A's and B's rapid messages into one +- A asks "I want X", B follows with "I want Y", and the model thinks "user asked two unrelated things" + +### MateClaw's fix + +**Two layers**: + +**1. Sender-boundary debounce.** When two messages land in the same conversation back-to-back, check senderId first: + +- Same sender → merge (typical case: paste-split fragments) +- Different sender → flush the existing pending immediately, start a new window for the new sender + +The decision lives in [`ChannelMessageRouter.isSameSender`](https://github.com/anthropics/mateclaw/blob/main/mateclaw-server/src/main/java/vip/mate/channel/ChannelMessageRouter.java). Null-defensive: if either senderId is missing, refuse to merge — better to flush twice than to mis-attribute one fragment. + +**2. `[@sender]` prefix on persisted content + prompt.** Every group message (`chatId != null`) gets wrapped before save and before the LLM call: + +``` +[@XuZhanFu] @MateClawBot I want to query X +[@xuzf] @MateClawBot I want to query Y +``` + +So: + +- The 30th historical message still tells the model who said it +- The persisted timeline reads as `[@A] ...; [@B] ...; [@A] ...`, the model can disambiguate follow-ups, quote-replies, mutual corrections +- Single chats (`chatId == null`) are zero-overhead, behavior unchanged + +`senderName` takes priority over `senderId` (friendlier display); both null → return null (no `[@null]` garbage tag). + +### What you'll see in logs + +``` +[wecom] Sender boundary in conversation wecom:{chatId}: flushing pending from sender=A, accepting new sender=B +``` + +In the DB, `mate_message.content` literally has `[@xxx]` prefix. + +--- + +## Upload constraint matrix + +WeCom enforces **hard size limits** at the chunk-finish step (after all bytes are uploaded). UX without a pre-check: "uploaded for three minutes, nothing came out the other side". + +### Limits + +| Type | Max size | Format requirement | +|------|---------|---------| +| File | **20 MB** | any | +| Image | **10 MB** | any common format | +| Video | **10 MB** | any common format | +| Voice | **2 MB** | **must be AMR** (other formats rejected by platform) | +| Global | **20 MB** | absolute ceiling | + +### MateClaw's handling + +**Client-side pre-check** to avoid pointless uploads. `applyWeComUploadLimits(fileSize, mediaType, contentType)` returns: + +- File > 20 MB → reject, tell user "exceeds 20MB limit" +- Image > 10 MB → downgrade to file upload (still visible as attachment, just no thumbnail) +- Video > 10 MB → downgrade to file upload +- Voice > 2 MB **or** mime ≠ `audio/amr` → downgrade to file upload +- Anything > 20 MB → reject (absolute ceiling, no exception) + +The downgrade carries a friendly note ("image > 10MB, sent as file attachment"), so the user knows what just happened. + +### Magic-byte filename recovery + +WeCom-forwarded files often arrive **without a filename field**. Saving them as `file.bin` breaks every downstream tool that dispatches by extension (PDF readers, DOCX parsers, etc). + +Fix: magic-byte sniff: + +- `%PDF` → `.pdf` +- `PK\x03\x04` is a ZIP container; peek inside the first few entries to distinguish `.docx` / `.xlsx` / `.pptx` / `.odt` / `.epub` / `.jar` +- Other common formats (PNG / JPEG / MP4 / MP3 / WAV) all recognized +- Truly unknown → keep `.bin`, don't pretend it's something else + +Implemented in `WeComChannelAdapter.sniffMagic()` + `refineZipKind()`. + +--- + +## Quoted messages + +Users quoting a previous message (image, file, text, voice, miniprogram) and then asking a new question is **the most common group interaction pattern**. + +### Supported quote types + +| Quote type | What the bot sees | Further processing | +|----------|------------|------------------| +| Text | `[Quote: prior text]\nuser's new question` | ✅ text passed to model | +| Voice | `[Quote: [voice] ASR transcript]\nuser's new question` | ✅ ASR result as context | +| Image | `[Quote: [image]]\nuser's new question` + image attached part | ✅ vision sidecar reads it | +| File | `[Quote: [file: report.pdf]]\nuser's new question` + file attached part | ✅ file tool can read | +| Mixed | Each sub-type expanded by the rules above | ✅ | + +### Implementation notes + +- **Media is downloaded too**: a quoted image/file isn't just a marker string — it's actually downloaded, AES-256-CBC decrypted, persisted to `data/chat-uploads/{conversationId}/...`, and attached as a MessageContentPart for the agent +- **Path alignment**: the conversationId used for media must match the conversationId in `mate_conversation`, otherwise `/api/v1/chat/files/{convId}/{name}` 403s on `isConversationOwner` and the frontend `` shows broken-icon + +Historical bug: an early version's `inboundConversationId()` added a `wecom:group:` infix for groups, but the router persisted as `wecom:{chatId}` without the infix — every group-quoted image was broken until both sides aligned. Fixed. + +--- + +## appmsg message types + +`msgtype=appmsg` is WeCom's extension point for rich-media cards. Four common subtypes: + +| Variant | What it is | Bot handling | +|------|-----------|--------------| +| `appmsg.file` | Forwarded file (PDF / Word / Excel) | Full download pipeline, equivalent to `msgtype=file` | +| `appmsg.image` | Image card | Full download pipeline, equivalent to `msgtype=image` | +| `appmsg.url` | **Public-account article / external link** | See next section | +| `appmsg.miniprogram` | Mini-program | Title surfaced to model; payload not retrievable | + +Unknown subtypes fall back to `[appmsg: title]` so the model at least knows "user shared some kind of rich media". + +### Public-account articles + +mp.weixin.qq.com articles are served as **captcha-gated SSR** — no LLM tool can fetch the body. If the bot pretends it can read it, the model **invents content from the title** (production-observed: "the article makes three points..." — pure hallucination). + +When MateClaw detects `mp.weixin.qq.com` in the link branch, it appends a directive to the model: + +> (Hint: this link is a public-account article. The body needs to be opened in WeChat and pasted by the user. Please ask the user to paste the article text rather than guessing from the title.) + +Effect: the model stops fabricating and asks the user to paste the body. Other normal URLs (github, wikipedia, generic external links) **don't** trigger the hint, since their bodies are fetchable by ordinary tools. + +--- + +## Group proactive push (aibot_send_msg vs aibot_respond_msg) + +### Platform rules + +``` +Single chat: aibot_send_msg ✓ aibot_respond_msg ✓ +Group: aibot_send_msg ✗ aibot_respond_msg ✓ (must bind to a prior frame's reqId) +``` + +In groups, any proactive message from the bot (cron summaries, async-task completions, image-generation results) must **piggyback** on a prior user inbound's frameReqId. Otherwise the platform rejects it. + +### MateClaw's handling + +**LRU cache of recent inbound reqIds**. `lastChatReqIds: ConcurrentHashMap` is updated on every group inbound, capped at 1000 chats. + +**Unified outbound `sendOutboundFrame(chatId, body)`**: + +- Cache hit → `aibot_respond_msg` + cached reqId +- Cache miss → fall back to `aibot_send_msg` (single chat or new chat) + +This way: + +- Cron summaries → group has prior activity → respond succeeds; never any → degrade to send_msg, still fails but doesn't blanket-fail +- Async tasks (image / music / video generation) completing → `AsyncTaskMediaDispatcher` calls the unified outbound +- Multi-chunk LLM reply → same reqId reused + +### What you'll see in logs + +``` +[wecom] Group send via aibot_respond_msg: chatId=..., reqId=... +``` + +--- + +## Async-task forwarding + +Image generation (`image_generate`) / music generation (`music_generate`) / video generation (`video_generate`) / 3D model generation (`model3d_generate`) are all **async tasks** — the agent returns a task id immediately; the actual artifact arrives 30 seconds to several minutes later. + +Earlier bug: artifacts only showed up in the Web console's history view, **invisible in the WeCom group**. + +Fix: `AsyncTaskMediaDispatcher.forwardToImIfBound(conversationId, parts)`: + +- After task completion, look up the conversation's bound channel via `ChannelSessionStore` +- Skip `web` / `webchat` (SSE already covers them) +- Call the channel adapter's `sendContentParts(targetId, parts)` +- WeCom: image / audio / video / file all supported as native attachments +- Slack: via `filesUploadV2` (see [Slack channel](./channels#slack)) +- Channels without `sendContentParts` (QQ, etc.): catch UnsupportedOperationException + log; one unsupported channel doesn't block the rest + +Files live at `data/chat-uploads/{conversationId}/`, served at `/api/v1/chat/files/{conversationId}/{storedName}`. Frontend and channel attachment views all read by this URL. + +--- + +## Model behavior: faking tool calls + +Observation: **qwen3.6-plus** sometimes "lazes out" in long-context, tool-call-heavy scenarios — it produces a Markdown code block that **mimics** a tool call, but `toolCallCount=0`: + +```` +🎵 《Title》 generation task submitted! +⏳ ETA 1-2 minutes, audio will be pushed when ready... + +```json +{ "prompt": "...", "lyrics": "..." } +``` +```` + +Backend never sees a tool_call → music generation never starts → user never receives the song. + +**Current mitigation**: switch to a model that executes tool_calls reliably (kimi-for-coding, claude-sonnet-4.5, deepseek-r1). Change the agent's default model in [Models](./models). + +Possible future: server-side detection of "task submitted + toolCallCount=0" patterns, with a corrective system message and retry. + +--- + +## Model behavior: self-arguing loops + +Another sporadic failure: the model gets stuck in a "thinking-output" loop, repeating the same Chinese answer dozens of times until max_tokens (16384) runs out. Production-observed pattern: + +``` +"Wait, I should X." → write Chinese answer → "Done." → write same answer → "Wait, Y." → same again → ... +``` + +Users stare at "generating..." for tens of seconds to minutes, finally receive a wall of duplicates. + +### MateClaw's handling + +**Two-layer guard**: + +1. **Detection**: [`hasRepeatingSuffix`](https://github.com/anthropics/mateclaw/blob/main/mateclaw-server/src/main/java/vip/mate/agent/graph/NodeStreamingChatHelper.java) checks if the buffer ends with the same 24-240 character unit repeated 4+ times consecutively → immediately disposes the upstream subscription +2. **Dedup + flag**: `dedupTrailingRepeats` collapses N trailing copies to 1; ReasoningNode sets finishReason to `INCOMPLETE`; the frontend renders a truncation banner with a "regenerate" button + +Why not just emit a warning: the user already saw the duplicates in the SSE stream (one-way push, can't unsend), but the **DB-persisted finalAnswer** and **WeCom outbound** both use `finalAnswer` — so the IM group only sees one clean copy of the answer + an INCOMPLETE banner. + +The threshold is **deliberately narrow** (4 verbatim consecutive copies) to avoid false-positives on legitimate "TL;DR / body / TL;DR" three-stage outputs. + +--- + +## Network resilience + +### TLS / socket transient retry + +DashScope / OpenAI / various LLM gateways occasionally produce on the public internet: + +- `bad_record_mac` (TLS RFC 5246 §7.2.2 fatal alert 20) +- `SSLHandshakeException` +- `SocketException: Connection reset by peer` +- `Premature close` / `Broken pipe` + +Previously these would surface as `LLM call failed` red text with no retry. + +Fix: classify all of these as `SERVER_ERROR`, route through the existing exponential-backoff retry: 3s → 6s → 12s (with jitter) up to 5 attempts. See [Agent engine](./agents#error-recovery). + +### Keepalive + +Group replies via `aibot_respond_msg` have a **60-second TTL** per stream — no new data within 60s and the platform drops the slot, the eventual real reply is silently rejected. + +Agents handling complex tasks (multi-tool + LLM reasoning) often exceed 60s. `WeComKeepaliveScheduler` sends a noop "processing..." heartbeat every 30 seconds; the slot never expires. A 180-second hard cap force-finishes the stream so a genuinely-stuck task doesn't keep keepalive ticking forever. + +### Reconnect with exponential backoff + +When the WeCom long-connection drops (NAT timeout, network blip), the adapter reconnects: 2s → 4s → 8s → 16s → 30s cap. **Never gives up** — as long as the process is alive, it'll resume message reception when the network does. + +The control panel's health view shows current reconnect count, ops can read it directly. + +--- + +## Platform-level constraints (not bugs, just limits) + +These are **WeCom platform** constraints, can't be worked around in code, only in configuration: + +### Data permission lock + +API-mode bot ticking **any data permission** in the WeCom admin (e.g. "read messages", "get group info") **auto-restricts the bot to creator only**. Other members' messages get ignored. + +**Fix**: in the admin panel, **uncheck** all 7 data permissions. The bot becomes available to all authorized members. MateClaw uses webhooks for messages, doesn't need data permissions. + +### Visibility × data-permission matrix + +| Visibility | Data permission | Effective | +|---------|---------|---------| +| All staff | All checked | **Creator only** (data lock overrides visibility) | +| All staff | All unchecked | All staff (recommended) | +| Specific dept | All unchecked | Members in those depts | +| Specific people | All unchecked | Listed users | + +### Group requires @bot + +The bot in a WeCom group must be `@`-mentioned to receive a message. Direct messages (1:1) don't need `@`. Platform behavior, no workaround. MateClaw doesn't broadcast-listen to all group messages (and couldn't if it tried). + +--- + +## Debugging tips + +### Verify group attribution + +```sql +SELECT content FROM mate_message +WHERE conversation_id = 'wecom:{chatId}' AND role = 'user' +ORDER BY id DESC LIMIT 5; +``` + +Expect: every user message starts with `[@username]`. + +### Verify media path + +```bash +ls data/chat-uploads/wecom:{chatId}/ +``` + +There **should not** be any `wecom:group:{chatId}` directories with the `group:` infix (early-bug residue, manually clean up). + +### Verify group push routing + +In server logs: + +``` +[wecom] Group send via aibot_respond_msg: chatId=..., reqId=... +``` + +If the group doesn't see the bot's reply but logs show this line with a non-null reqId, the message reached the platform but was rejected (usually: reqId already consumed, or bot kicked from group). + +### Verify keepalive + +```bash +grep "wecom-keepalive" logs/mateclaw.log | tail +``` + +Expect periodic "Heartbeat sent" + "Heartbeat ACK received", with occasional "force-finished stream" hard-finishes. + +--- + +## Known corner cases + +| Scenario | Current behavior | Possible future | +|------|---------|---------| +| First group message is a cron push (no prior chat activity) | Cache empty, falls back to `aibot_send_msg`, platform rejects | Ring-buffer multi-reqId cache (limited gain, not implementing) | +| Model "lazes out" in long sessions | User retries / switch model | Server-side detection + corrective inject | +| 3 different senders concurrent in same group | Serial processing, each user gets own window (works) | — | +| User refuses to paste public-account body | Bot politely guides | — | +| OOXML magic-byte misclassification (very rare) | Falls back to `.zip` | ZIP entry peek covers 90% | + +--- + +## At-a-glance + +``` + ┌─────────────────────┐ + │ WeCom group user │ + └──────────┬──────────┘ + │ inbound (with chatId) + ▼ + ┌────────────────────────────────────────┐ + │ WeComChannelAdapter │ + │ ├─ chunk upload pre-check (4 categories)│ + │ ├─ magic-byte sniff (OOXML peek) │ + │ ├─ AES decrypt + chat-uploads/{convId}/ │ + │ ├─ quote parsing (5 sub-types) │ + │ ├─ appmsg parsing (4 sub-types + hint) │ + │ └─ cache lastChatReqIds[chatId] │ + └──────────────┬─────────────────────────┘ + │ ChannelMessage(content="[@xxx] ...") + ▼ + ┌────────────────────────────────────────┐ + │ ChannelMessageRouter │ + │ ├─ adaptive debounce (500ms / 2.5s) │ + │ ├─ sender boundary cut (group critical) │ + │ ├─ applyGroupTag → DB + LLM │ + │ └─ queue + sessionLock serialize │ + └──────────────┬─────────────────────────┘ + │ + ▼ + ┌──────────┐ + │ Agent │ ← StateGraph + ReAct + └─────┬────┘ + │ finalAnswer / tool_calls + ▼ + ┌────────────────────────────────────────┐ + │ sendOutboundFrame(chatId, body) │ + │ ├─ cache hit → aibot_respond_msg │ + │ ├─ cache miss → aibot_send_msg │ + │ ├─ keepalive (60s TTL extend) │ + │ └─ reconnect backoff (NAT/blip self-heal)│ + └────────────────────────────────────────┘ +``` + +--- + +## Related reading + +- [Channels](./channels) — overview of all 9 channels + setup +- [Agent engine](./agents) — TLS retry, error classification, self-loop detection +- [Models](./models) — switching default model, failover chain +- [Security & approval](./security) — approval flow for high-risk tools in groups +- [Doctor](./doctor) — diagnostic commands for channel troubleshooting diff --git a/mateclaw-server/src/main/resources/docs/en/wiki.md b/mateclaw-server/src/main/resources/docs/en/wiki.md new file mode 100644 index 00000000..83b997b3 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/wiki.md @@ -0,0 +1,482 @@ +--- +title: LLM Wiki — Structured Knowledge Engine, Not Vector Retrieval +description: LLM Wiki digests raw documents into structured knowledge pages with backlinks, summaries, and source tracing. Lazy ingest indexes uploads instantly and compiles pages on demand; eager ingest produces a full Wiki up front. Agents browse the library — they don't grep a vector store. +head: + - - meta + - name: keywords + content: LLM Wiki,knowledge base,knowledge engine,backlinks,structured knowledge,RAG alternative,knowledge graph,lazy ingest,on-demand compile,semantic search +--- + +# LLM Wiki + +A knowledge base isn't a place you search. It's a place you **read**. + +Most AI knowledge systems do one thing: chunk your files, embed them, hand back fragments at query time. You get pieces. You can't browse them. You can't tell what the system "knows" without asking. Nothing is ever *finished*. + +MateClaw's LLM Wiki does something different. Drop raw material into a knowledge base and the system reads it, digests it, and writes structured Wiki pages — each with a summary, backlinks, and provenance pointers back to the source passage. You can open any page and read it. You can edit it. Agents read summaries automatically and pull full pages on demand. + +**It's a library, not a vector store.** + +::: tip How it differs from the open-source "LLM Wiki" clones +In April 2026, Andrej Karpathy published a GitHub Gist that gave the idea a name: the material you feed an AI shouldn't be re-shredded into vector fragments at query time — it should be read once and written into a readable wiki. Within a month, at least nine `llm-wiki` single-file implementations had appeared on GitHub — useful, local, personal. + +MateClaw's LLM Wiki **is the same idea, raised into a product**: + +- Not one person's notebook — a **team-shared knowledge base** with multi-user access, permissions, audit, and archive +- Not a script that runs once — a capability **agents use continuously**, wired into memory, retrieval, and citation +- Not eager-only — **lazy mode compiles pages on demand**, saving 90%+ of LLM calls at scale +- Not raw markdown dumped on disk — a page layer with **provenance, bidirectional links, manual-edit protection, and reversible archive** +- Not an isolated tool — the **knowledge layer of MateClaw's agent operating system**, threaded through memory, agents, and channel delivery + +> They built a clone. We built a home. +::: + +--- + +## The three-layer model + +A knowledge base is three layers stacked on top of each other: + +1. **Raw material** — the files you dropped in. PDF, DOCX, plain text, markdown, or a whole local directory scanned in one go. The system keeps them intact; any claim in the Wiki traces back to the passage that produced it. +2. **Wiki pages** — structured articles the AI writes from the raw material. Each page has a title, a summary, a body, bidirectional links to related pages (`[[like this]]`, plus the alias form `[[target|display text]]`), and provenance pointers back into the raw layer. +3. **Agent surface** — when an agent calls a wiki tool, the system auto-injects the summaries of relevant pages into the prompt. Bodies are fetched on demand. Agents don't read raw files. They read the library. + +This matters because the agent's context window stops getting wasted on re-reading source material every turn. Tokens go to thinking, not to reading the same paragraph for the fifth time. + +--- + +## Creating a knowledge base + +`Wiki → New Knowledge Base`. Name it after what's inside, not who owns it. "Product specs" beats "Team Alpha's KB". + +Once it exists, add material: + +- **Upload files** — drag PDFs, DOCX, markdown, or plain text into the upload area. Each file becomes a raw material row. +- **Scan a local directory** — desktop only. Point at a folder and MateClaw walks it recursively, respecting `.gitignore`, importing everything that looks like text. +- **Paste text** — for short excerpts or conversation transcripts. + +The system starts indexing as soon as material arrives. You'll see a status indicator on each row: `pending → processing → completed`. If some chunks fail and others succeed, the row lands as `partial` — you keep what worked, instead of throwing the whole document away. + +--- + +## Two ways to ingest: do you need pages right now? + +`Wiki → Config → Ingest Mode` flips between: + +- **Eager (compile pages on upload)** — runs the full LLM pipeline to produce a finished, browsable Wiki. Pick this when you want pages ready to read the moment ingest finishes. The cost is real: many LLM calls per upload, slow, expensive. +- **Lazy (index now, compile later)** — extracts, normalizes, chunks, and embeds. **Zero page-generation LLM calls.** Search works immediately; pages are produced on demand when an agent or user actually needs one. + +Existing KBs default to eager so nothing changes underneath you. New KBs that don't need an instant Wiki should pick lazy — same retrieval quality, a fraction of the cost. The two modes coexist: pages an eager KB has already produced stay put; further uploads honor the current mode. + +> Under lazy, "0 pages" is **success**, not failure. This finally fixes the long-standing annoyance where any upload that produced no pages went red. + +--- + +## What ingestion actually does + +### Eager: the full pipeline + +For each raw material, in order: + +1. **Chunk** — split the source into overlapping passages, attaching structural metadata to each chunk: page number (PDF / PPTX), heading breadcrumb (`Intro / Setup / Linux`), section identifier, and a token-count estimate. +2. **Extract concepts** — ask the LLM to identify entities, decisions, facts, and open questions in each chunk. +3. **Cluster and draft** — group related extractions into candidate Wiki pages and generate structured drafts with summaries. +4. **Link** — find bidirectional references between pages (`[[concept]]` and `[[concept|display text]]`) and compute backlinks. +5. **Persist** — write pages to `mate_wiki_page` with citations pointing back to the raw passages they came from. + +Ingestion is idempotent. Re-run it on the same material and existing pages get updated rather than duplicated. Hand-edited content is protected — `locked` tells the digester to leave human prose alone, and you have to unlock explicitly to let the AI re-draft. + +#### Two-phase digest + +Eager ingest runs in two phases for an order-of-magnitude speedup: + +- **Phase A (route)** — extracts metadata and concept routing, deciding which pages each chunk feeds into. +- **Phase B (merge)** — generates pages in parallel, 60+ at a time. Each raw material gets its own **progress bar** — no more staring at "processing…" wondering what's happening. + +**Resumable**: interrupted mid-import? Hit "Reprocess" and only the unfinished pages re-run; everything already produced stays put. Documents larger than the embedding model's context get mean-pool sub-segmented automatically. + +### Lazy: index now, compile later + +The pipeline collapses to four steps: + +1. **Extract** — pull text out of the binary (PDF / DOCX / …). +2. **Normalize** — jsoup strips HTML noise (nav / footer / ads); markdown heading levels and PDF `--- Page N ---` markers are detected for downstream metadata. +3. **Chunk + metadata** — every chunk carries `page_number`, `header_breadcrumb`, `source_section`, `token_count`. +4. **Embed** — embeddings land asynchronously and the row is marked `completed`. + +When are pages produced? They aren't — until somebody asks. The system retrieves the relevant chunks, asks the LLM for one page, and binds the page's citations to the chunks it actually used. Nothing more. + +--- + +## System pages: overview and log + +Every KB ships with two **system pages**: + +- `slug=overview` — the front door of the knowledge base. Scope, recent updates, coverage stats live here. +- `slug=log` — an append-friendly audit trail of ingest / compile / edit activity. + +Both are flagged `page_type=system, locked=1`: + +- Delete (single, batch, or the cleanup pass during reprocessing) **refuses to remove them** — you'll get a clean error, not a silent drop. +- List, keyword search, semantic search, and related-pages **filter them out by default** so they don't pollute search results or the agent's context window. +- Reading by slug (`wiki_read_page("overview")`) still works — agents can opt in whenever they want. + +> The `locked=1` flag is also yours to set on any hand-curated page. The AI tool surface honors it the same way it honors `lastUpdatedBy="manual"` — they stack. + +--- + +## Transformations: making the KB programmable + +::: tip New in 1.3.0 +The Transformations engine shipped in v1.3.0. In v1.2.0 and earlier, Wiki was retrieval-only — chunk, embed, recall. v1.3.0 teaches the Wiki to **actively process**: user-defined templates, cross-material aggregation, reverse-citation extraction, JSON output, page-as-input transformations, cancel/re-run. Full release story in the [v1.3.0 release notes](./releases/1.3.0). +::: + +By default the Wiki digests raw materials into the pages **it** thinks matter — but "matter" is its judgment, not yours. **Transformations** flip that around: you author a prompt template that says "extract this shape from each source", and the engine runs it, persists the output, and keeps it in sync. + +Open `Wiki → [any KB] → Transformations`. Each template is composed of: + +- **Name** — short lowercase slug used by agent tools to address the template (e.g. `contract-risk-extract`) +- **Title / description** — human-readable +- **Prompt template** — the instruction text; supports `{input_text}` and `{title}` placeholders +- **Model** — defaults to the KB chat model; you can pin a single template to a specific model +- **Apply by default** — toggle on → every newly-ingested raw fires this template automatically +- **Output target** — `None` (stays in run history) or `Save as wiki page` (synthesis page auto-created) +- **Output format** — `Markdown` or `JSON` (with optional schema validation) + +### Seven enterprise templates shipped out of the box + +Available on every new KB; cover common enterprise jobs: + +| Template | What it produces | +|---|---| +| `contract-risk-extract` | Clause-level risk extraction (high / medium / low) with AI-suggested rewrites | +| `meeting-action-items` | Decisions + action items (owner / due date / acceptance criteria) | +| `customer-profile` | Customer emails / CRM records → structured account profile | +| `competitor-update` | Public signals → competitor digest | +| `resume-structured-extract` | Resume → standardized record (education / experience / skills / highlights) | +| `incident-postmortem` | Incident report → 5-Whys + remediation list + similar-incident keywords | +| `paper-imrad` | Paper / tech report → IMRaD summary + key terminology | + +### Four ways to trigger a run + +| Trigger | How it fires | Where it shines | +|---|---|---| +| **Manual** | Pick a source in the UI, click Run | Single-shot prompt iteration | +| **Apply default** | Toggle on the template; upload a new material | "Every new contract gets risk-extracted on arrival" | +| **Agent tool** | Agent calls `wiki_apply_transformation(name, rawId)` | Digital employee decides which template to run | +| **Aggregate** | "Aggregate all runs" button / `wiki_aggregate_transformation` | Map-reduce N per-source outputs into one KB-level synthesis page | + +### Input: raw materials or existing pages + +A template doesn't have to read a raw material — it can also run against an existing wiki page (agent tool: `wiki_apply_transformation_to_page(name, slug)`). This lets you chain templates: A turns a source into a synthesis page, then B reads that page and produces a different view. + +### Output target: run history, or a real wiki page + +- **None** — the result lives only in the run history under the template card. Good for one-off output. +- **Save as wiki page** — every successful run **upserts** to a fixed slug `-`. Re-running updates the same page rather than spawning duplicates. And: + - Page-level embedding is fired automatically so the synthesis page joins semantic search + - The output is parsed for citation hints like "第 N 题 / page X" and chunk-level citations are written linking back to the source chunks + - It joins the relation graph, the hot cache, and becomes directly readable by agents + +### JSON output + Schema validation + +When the format is JSON: + +1. A strict system prompt is injected ("return one JSON document, no prose, no fences") +2. On parse failure the executor retries once with a specific error reminder +3. A JSON Schema can be stored on the template; after parsing, the executor checks required fields and the top-level type +4. Still invalid → the run is failed with the specific reason in the error column + +Valid JSON is wrapped in a fenced ```json block so the existing markdown rendering and save-as-page contract continues to hold while downstream tools can still grep / parse the raw JSON. + +### Cross-material aggregation + +Have 10 contracts each processed by `contract-risk-extract`? Click "Aggregate all runs" on the card: + +- The system loads every completed run of this template within this KB +- Deduplicates by source (only the most recent run per raw contributes) +- Sends them to an LLM with a merge + dedupe system prompt — same clause types are merged, source attributions are preserved, disagreements are surfaced instead of smoothed over +- Upserts the result to a deterministic slug `-aggregate` +- Triggers page embedding so the aggregate joins semantic search + +This is what turns per-source extracts into a KB-level synthesis — no more diff-by-eye across N contracts. + +### Run history + observability + +Every run records: + +- Status: `pending / running / completed / failed / cancelled` +- Duration, model, trigger (manual / apply_default / agent_tool / aggregate) +- Input / output / total tokens reported by the provider (`8.2k↑ / 1.1k↓`), accumulated across retries +- Linked output page (when output_target=page) +- Full output / error message + +The UI lets you: + +- **Cancel** — flag a running run as cancelled. The LLM call still completes server-side because most providers don't support cancellation, but the executor drops the eventual output instead of overwriting that state. +- **Re-run** — one-click rerun against the same input. Works on completed, failed, and cancelled runs alike. +- **Compare** — tick two completed runs → the "Compare selected" button opens a side-by-side modal (older on the left, newer on the right) so prompt iteration finally has a real diff workflow. + +### Typical recipes + +| Scenario | Recipe | +|---|---| +| Legal automation | `contract-risk-extract` + apply default + save as page → every new contract auto-produces a risk report page | +| Sales intelligence | `customer-profile` + apply default → every account material becomes a profile page | +| Engineering memory | `meeting-action-items` + `incident-postmortem` together → decision history and incident lessons accumulate as KB pages | +| Research synthesis | Run `paper-imrad` across a batch, then aggregate → thematic survey page | +| Programmatic downstream | JSON format + schema → the wiki becomes a structured data source for dashboards / pipelines | + +### REST endpoints (base path `/api/v1/wiki/transformations`) + +| Method | Path | Purpose | +|---|---|---| +| `GET` / `POST` / `PUT` / `DELETE` | `/`, `/{id}` | Template CRUD | +| `POST` | `/{id}/apply?sync=true` | Run once (body carries either `rawId` or `pageId`) | +| `POST` | `/{id}/aggregate?kbId=X` | Cross-material aggregation | +| `GET` | `/runs?rawId=` or `?kbId=` or `?transformationId=` | Query run history | +| `POST` | `/runs/{runId}/save-as-page` | Promote a run manually into a wiki page | +| `POST` | `/runs/{runId}/cancel` | Mark a run cancelled | + +--- + +## How agents use the Wiki + +Bind an agent to a knowledge base from `Agents → [your agent] → Knowledge`. From that moment: + +- The agent's system prompt automatically includes a compressed summary of the KB's top-level pages. +- The agent's toolbox grows these wiki tools: + +| Tool | What it does | +|---|---| +| `wiki_search_pages` | Page-level hybrid retrieval (keyword + semantic). | +| `wiki_semantic_search` | Chunk-level semantic search. Hits include `pageNumber` and `section` when known, so the agent can cite "page 12, Setup / Linux" rather than a naked snippet. | +| `wiki_read_page` | Read a single page; trim by section heading or character cap. | +| `wiki_read_many` | **New.** Fetch multiple pages in one call (up to 10 slugs, with a per-page char cap). Replaces multi-turn `wiki_read_page` chains. | +| `wiki_compile_page` | **New.** On-demand page generation for a topic. Citations bind to the evidence chunks the prompt actually used — not to every chunk of the source raw. | +| `wiki_trace_source` | Trace a Wiki page back to its source raw materials. | +| `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_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. | +| `wiki_apply_transformation_to_page` | Run a template against an **existing wiki page** (takes a slug, not a numeric id). | +| `wiki_aggregate_transformation` | Map-reduce every completed run of a template across the KB into one synthesis wiki page. | + +The `kbId` parameter resolves automatically from the bound agent — agents never have to guess it. + +A typical agent turn: + +> **User:** "What did we decide about the retry policy last quarter?" +> +> **Agent:** *(reads injected summary, sees a "Retry Policy" page exists, opens that page directly, returns the decision with a source link.)* + +That isn't a vector query. It's literally opening the page — because the page exists. + +### Hot cache: a recent-activity snapshot in every system prompt + +The bound KB doesn't just contribute summaries — it also contributes a small, freshly-rebuilt **hot cache** that gets stitched into the system prompt. Think of it as the page the agent reads first, every turn: + +- **Last updated** — the most recent ingest / page edit +- **Key recent facts** — bullets the rebuilder considers high-signal +- **Recent changes** — page creations and compilations since the last rebuild +- **Active threads** — open questions and unresolved decisions + +The rebuilder fires asynchronously when a conversation ends (`ConversationCompletedEvent`), debounced inside a configurable window (default ~30 s) so a flurry of short turns doesn't churn LLM calls. An admin can also trigger a rebuild manually — that path bypasses the debounce. + +The injection is gated by the `wiki.hot_cache.enabled` feature flag (off → empty injection) and is capped at the **two highest-priority KBs** per agent so the system prompt stays small. + +#### Manage from the KB detail drawer + +`Wiki → [your KB] → Hot cache` shows: + +- **Regenerate** button — async manual rebuild; the panel polls a few seconds later and refreshes +- **Reset** button — soft-delete the row; the next `ConversationCompletedEvent` rebuilds it +- Meta grid: last updated, update reason (`AUTO` / `MANUAL` / `EVENT`), rebuild count, last duration in ms +- Error banner if the last rebuild failed +- The rendered Markdown content in a preview pane + +#### 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 | + +The hot cache lives in `mate_wiki_hot_cache` — see the **Data model** section below for the exact columns. + +### A typical lazy turn + +``` +User uploads product-manual.pdf (lazy mode: zero page-generation LLM calls) + ↓ +Agent: wiki_semantic_search("error code 500 retry") + → hit on chunk #1234, page=12, section "Error Handling / Retries" + ↓ +Agent: wiki_compile_page(topic="500 retry policy", maxEvidenceChunks=5) + → produces slug=500-retry-policy, citations bound to those 5 chunks only + ↓ +Agent: wiki_read_page("500-retry-policy") + → returns the structured page with its source-chunk list +``` + +The whole path spends one LLM call, scoped to the five chunks that actually matter. The other 200 chunks in the manual cost nothing extra. + +--- + +## Reading and editing pages + +Every generated page is a first-class document you can open in the Wiki view: + +- Markdown rendered with syntax highlighting. +- Backlinks in the sidebar — see what else references this page. +- A "source" button on every claim that jumps to the raw passage it came from. +- An edit mode where you can rewrite the page directly. +- The delete button is disabled on system / locked pages. + +Edit when the AI got it wrong. Your edits survive the next ingest — `locked` tells the digester to leave human prose alone. Unlock explicitly when you want the AI to re-draft from the source. + +--- + +## Search, source tracing, and semantic retrieval + +- **Semantic search** — ask "what did we decide about auth?" and get the decision, not pages containing "auth". Chunk-level embeddings with cosine retrieval — it understands what you mean. Hits now include `pageNumber` and `section`, so the agent can quote "page 12, Setup / Linux" instead of a free-floating snippet. +- **Hybrid retrieval** — full-text and semantic matching run together, RRF-fused, with a 1-hop relation boost on the top seeds. +- **Full-text search** — covers titles, summaries, bodies, and concept extractions. Works across every KB you have access to. +- **Source tracing** — any claim, any page, has a source link. Click it, you land on the raw passage. Agents have the same capability. +- **Backlinks** — every page shows what other pages link to it. The `[[concept|display text]]` alias form is now parsed correctly: only `concept` becomes a slug, `display text` is purely visual. +- **Related pages** — blends shared-chunk, shared-raw, direct-link, and semantic-neighbor signals. The 1-hop expansion **never seeds from system pages**, so overview/log don't drag every page in the KB into your "related" list. +- **Edit protection** — locked or hand-edited pages aren't overwritten on re-ingest; unlock explicitly to re-draft. + +--- + +## Vision pipeline: images become text + +A wiki that can't read images is half-blind. PDFs are the worst offenders — half the actual information often lives inside the figures. + +When the `wiki.ocr.enabled` feature flag is on, MateClaw runs every uploaded image — and every image *embedded in a PDF page* — through a vision pipeline that extracts a **caption** plus any **visible text** in the image. Those become first-class chunks alongside the surrounding prose, so retrieval finds them, agents quote them, and search results show inline thumbnails with a click-to-zoom lightbox. + +### How it works + +1. **Hash** the image bytes with SHA-256 — the cache is content-addressed, so re-uploading the same diagram in a different KB costs nothing. +2. **Probe `mate_wiki_image_caption_cache`** — on hit, reuse the caption immediately and increment `hit_count`. +3. On miss, walk the configured **vision providers in order** until one returns a non-null caption. +4. **Persist** the caption + visible text + provider id + model + duration into the cache (race-tolerant insert — concurrent uploads of the same bytes are fine). +5. The `VisionResult` flows back into the chunker as additional content for the page that contained the image. + +### Supported providers + +| Provider id | Model | Notes | +|---|---|---| +| `dashscope-vision` | `qwen-vl-max` | DashScope OpenAI-compatible endpoint; reuses the DashScope provider configured in the UI | +| `zhipu-vision` | `glm-5v-turbo` | Zhipu BigModel; OpenAI-compatible | +| `volcano-doubao-vision` | configurable | ByteDance Volcano Doubao vision | + +Providers are auto-detected by order. Configure their keys / base URLs in `Settings → Models` like any other provider — the vision pipeline picks up the credentials from there. + +### Toggling the pipeline + +`Settings → Feature Flags → wiki.ocr.enabled`. Off by default in lightweight installs; on once you've configured at least one vision provider. + +When the flag is **off**, the pipeline short-circuits — uploads still succeed, image chunks just don't carry captions. The `extracted_text` cache for those images is **deferred** rather than poisoned, so flipping the flag back on captures captions on the next upload without forcing a re-ingest. + +### What you see in the UI + +- Search hits that contain image evidence render the thumbnail inline; click to open a lightbox at full resolution. +- The raw-material detail drawer shows captions next to each extracted image so you can sanity-check what the model actually saw. + +--- + +## Health-aware LLM fallback + +Wiki ingest is LLM-heavy, and a single wedged provider used to mean a whole batch died. Now every wiki step (`route`, `create_page`, `merge_page`, `enrich`, …) hits the routing chain through a **health-aware fallback**: if the primary model errors or times out, the next model on the KB's `fallback` list is tried once. Health (success / error / latency) is tracked per provider, so a flapping provider gets demoted automatically until it recovers. + +Configure the fallback list under `Wiki → Config → Model Strategy` next to the per-step picker. + +--- + +## Per-step model selection actually works + +In `Wiki → Config → Model Strategy` you can pick a different model per step: + +```text +heavy_ingest.route → small, cheap model for routing +heavy_ingest.create_page → strong model for full-page authoring +heavy_ingest.merge_page → strong model for content merging +light_enrich.enrich → small, cheap model for wikilink annotation +``` + +Resolution order: + +```text +stepModels[step] → wikiDefaultModelId → system default model +``` + +This UI used to be cosmetic — the Java side dropped the config on the floor and ran every step on the system default. Every LLM call inside the eager pipeline now consults the routing chain: route, create, merge, retry-create, repair, document analysis, light enrich. + +--- + +## Data model (if you're curious) + +Nine tables: + +| Table | Purpose | +|---|---| +| `mate_wiki_knowledge_base` | One row per KB. Owner, name, description, config JSON (`ingestMode`, `wikiDefaultModelId`, `stepModels`, fallback chain). | +| `mate_wiki_raw_material` | One row per upload. Status, byte hash, source path, last successfully-processed hash. | +| `mate_wiki_page` | One row per generated page. Title, summary, body, `source_raw_ids` (provenance), `page_type`, `locked`, version, plus `embedding` / `embedding_model` / `embedding_text_version` so transformation synthesis pages enter semantic search directly. | +| `mate_wiki_chunk` | One row per chunk. content + hash + offsets + embedding, plus `page_number`, `header_breadcrumb`, `source_section`, `token_count`. | +| `mate_wiki_relation` | Cached page-to-page edges (shared chunks, shared raws, direct links, semantic neighbors) used to power the 1-hop retrieval boost and the related-pages tool. | +| `mate_wiki_hot_cache` | One row per KB. Rendered Markdown snapshot + `last_updated`, `update_reason`, `rebuild_count`, `last_rebuild_duration_ms`, `last_rebuild_error`. | +| `mate_wiki_image_caption_cache` | SHA-256 keyed cache of vision-extracted captions. `caption`, `visible_text`, `mime_type`, `capture_model`, `provider_id`, `duration_ms`, `hit_count`. | +| `mate_wiki_transformation` | One row per transformation template. `name`, `title`, `description`, `prompt_template`, `model_id`, `apply_default`, `output_target`, `output_format`, `output_schema`. `kb_id=NULL` = workspace-wide. | +| `mate_wiki_transformation_run` | One row per template execution. `status`, `output`, `error`, `duration_ms`, `model_id`, `triggered_by`, `input_tokens`, `output_tokens`, `total_tokens`, `output_page_id`. | + +`mate_wiki_page` also carries two protection flags: + +- `locked` (V40) — `1` blocks AI tools, batch ops, and re-ingest cleanup from modifying or deleting the page. The built-in `overview`/`log` system pages ship with `locked=1`; users can set it on any hand-curated page too. +- `archived` (V41) — `1` soft-archives the page: gone from default list/search/related results, but the page itself, its citations, and its backlinks are all preserved. Recoverable. + +### Operator endpoints + +For when you don't want to wait for the cron / event hooks to catch up: + +| Endpoint | What it does | +|---|---| +| `POST /api/v1/wiki/admin/kb/{kbId}/rebuild-overview` | Force-rewrite the overview marker region from current stats. | +| `POST /api/v1/wiki/admin/backfill-tokens` | Run one batch of the token-count backfill now; returns `pendingBefore` / `pendingAfter` / `filledThisBatch`. | + +The `mate.wiki` block in `application.yml` controls global knobs (chunk size, parallelism, auto-process-on-upload). Per-KB knobs (ingest mode, step models, fallback chain) live inside the KB's `configContent` JSON and are edited through the config UI. + +> The `token_count` column is nullable for legacy chunks; a low-frequency cron `WikiChunkTokenBackfillJob` fills them with `ceil(charCount / 4)` over time, never blocking ingest. + +--- + +## When to use it + +Reach for a Wiki KB when you have: + +- more than a handful of documents on the same topic +- material you want humans to read and edit, not just retrieve +- information that should outlive any single agent or conversation +- sources where "where did this come from" actually matters + +If you just want to drop one PDF into one conversation, attach it in chat. Wikis are for material that earns its own shelf. + +A short field guide to picking a mode: + +- You need a finished, browsable Wiki **right now** (sharing, presenting, onboarding): eager. +- You're seeding a corpus and want pages produced only when an agent or user reaches for them: lazy + on-demand compile. +- Big corpus, expensive model, unclear whether every document needs a full Wiki page: lazy is the cheaper default. + +--- + +## Next + +- [Agents](./agents) — binding an agent to a KB +- [Memory](./memory) — how Wiki and memory differ (hint: Wiki is deliberate, memory is passive) +- [API Reference](./api) — wiki REST endpoints diff --git a/mateclaw-server/src/main/resources/docs/en/workflow.md b/mateclaw-server/src/main/resources/docs/en/workflow.md new file mode 100644 index 00000000..e1d08a22 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/workflow.md @@ -0,0 +1,336 @@ +# Workflow + +::: tip New in 1.3.0 +Workflow orchestration is available from v1.3.0. Earlier releases (v1.2.0 and below) do not ship this capability. +::: + +**What workflow is**: a way to compose multiple digital employees plus system actions (approval / channel dispatch / memory write) into a linear-step business process. Each step can be gated by the previous step's output, fan out in parallel, wait for human approval, or persist results into an employee's `MEMORY.md`. + +**What workflow is not**: +- Not a replacement for ReAct / Plan-and-Execute — single-agent multi-turn reasoning still lives in those engines +- Not a low-code drag-and-drop if/else builder — v0 is **JSON-first** (canvas comes in v1) +- Not a 30-node Dify-style orchestrator — MateClaw workflows stay deliberately minimal: **a linear array of steps with one `mode` field expressing the control flow** + +::: warning v1.3.0 scope +v0 = internal alpha. **7 step modes + 6 trigger pattern types**. `loop` and `invoke_skill` are deferred. Run it on a flagship account / internal workspace before rolling out broadly. +::: + +--- + +## One-minute overview + +```json +{ + "schemaVersion": "1.0", + "inputs": [ + { "name": "customer", "type": "json" } + ], + "steps": [ + { + "name": "enrich", + "agentName": "data-analyst", + "promptTemplate": "Enrich and return strict JSON: {{ inputs.customer | toJson }}", + "mode": { "type": "sequential" }, + "outputVar": "enriched", + "outputContentType": "json" + }, + { + "name": "vip-route", + "agentName": "enterprise-sales", + "promptTemplate": "VIP onboarding for {{ outputs.enriched.name }}", + "mode": { + "type": "conditional", + "expression": "{{ outputs.enriched.tier == 'enterprise' }}" + } + }, + { + "name": "notify-feishu", + "agentName": "ops-bot", + "promptTemplate": "Notify feishu: {{ outputs.enriched }}", + "mode": { "type": "fan_out" } + }, + { + "name": "notify-email", + "agentName": "ops-bot", + "promptTemplate": "Notify email: {{ outputs.enriched }}", + "mode": { "type": "fan_out" } + }, + { + "name": "wait-acks", + "mode": { "type": "collect" } + }, + { + "name": "record", + "promptTemplate": "Onboarded {{ inputs.customer.name }}", + "mode": { + "type": "write_memory", + "employeeId": "{{ outputs.enriched.assignedEmployeeId }}", + "file": "MEMORY.md", + "mergeStrategy": "append" + } + } + ] +} +``` + +How it reads: +1. `enrich` asks the data analyst to structure the customer info as JSON +2. If `tier == enterprise`, route to the enterprise-sales employee for VIP onboarding +3. In parallel (fan_out), notify Feishu and notify email +4. `collect` waits for both notifications +5. Append the result to the employee's `MEMORY.md` + +--- + +## Core concepts + +### Seven step modes (v1.3.0) + +| Mode | Behavior | Required fields | Key semantics | +|---|---|---|---| +| `sequential` | Run after the previous step; previous output → `{{input}}` | — | Default mode | +| `fan_out` | Runs in parallel with consecutive `fan_out` steps; all receive the same `{{input}}` | — | Boundary detected at compile time: from this step onward, the first non-`fan_out` / non-`collect` step terminates the group | +| `collect` | Joins the most recent `fan_out` group's outputs with `\n\n---\n\n` into `{{input}}` | — | At least 2 consecutive `fan_out` steps must precede; compile-time check | +| `conditional` | Runs only if the Pebble expression is true | `expression` | When false, skipped; `{{input}}` is preserved (carries over previous step's) | +| `await_approval` | Pauses the run; sends an approval | `approvalKind`, `approverChannels[]` | Resumes to next step on approval; timeout follows workspace policy | +| `dispatch_channel` | Multi-channel delivery of `{{input}}` | `channels[]` | Per-channel failure follows `errorMode` | +| `write_memory` | Writes employee memory file | `employeeId`, `file`, `mergeStrategy` | Four strategies: `append` / `replace_section` / `upsert_kv` / `overwrite` | + +> **Not in v1.3.0**: `loop` (iterate N times or per-item over an array) and `invoke_skill` (call a skill without going through an employee). Coming based on user feedback. + +### Expressions: a Pebble subset + +Workflow does **not** use a full template engine — it supports the same Pebble subset as Kestra, just enough to gate conditionals and reference variables, with no code execution. + +| Category | Syntax | +|---|---| +| Variable references | `inputs.X` / `outputs.varname.field` / `vars.X` / `now` / `flow.id` | +| Operators | `==` `!=` `<` `<=` `>` `>=` `and` `or` `not` `+` `-` | +| Built-in filters | `length` / `lower` / `upper` / `default('x')` / `toJson` / `fromJson` / `date(format)` | +| JSONPath | `\| jq('.field.subfield')` | +| String tests | `\| contains('x')` / `\| startsWith('x')` / `\| matches('regex')` | + +**Not supported** (rejected at compile time): +- User-defined functions / macros +- `include` / `extends` +- File I/O / network I/O +- Any side-effecting operations + +### Output type: text vs json + +Each step's `outputContentType` decides how downstream steps can access it: + +| outputContentType | Default | Pebble access rules | +|---|---|---| +| `text` | ✅ | `outputs.X` is a string; `outputs.X.field` **fails at compile time**; `\| jq(...)` **fails at runtime** | +| `json` | — | Runtime `JSON.parse`; failure follows `errorMode`; field access / `jq(...)` are valid | + +**Agent steps default to `outputContentType=text`** — LLM natural-language output isn't structured JSON. To do conditionals or field access, you must: +1. **Explicitly** request strict JSON in the `promptTemplate` ("return strict JSON: {...}") +2. Set that step's `outputContentType` to `json` + +### Compile-time illegal combinations (publish rejects) + +| Combination | Reason | +|---|---| +| Multiple consecutive `fan_out` with no `collect` to terminate | `{{input}}` for the next step is ambiguous | +| `collect` without preceding `fan_out` | Nothing to collect | +| `await_approval` mixed inside a `fan_out` group | Multiple concurrent approvals fired with no aggregation UX | +| `agentName` references a non-existent / disabled / cross-workspace employee | ACL fail | +| Pebble expression references an undeclared variable | Compile-time | +| `outputs.X.field` but step X is `text` | Compile-time type error | +| `dispatch_channel` references a channel not in the workspace allowlist | ACL | +| `write_memory` references an employeeId outside the workspace | ACL | +| Step count > 200 (default cap) | Runaway-config guard | + +Publish runs `WorkflowCompiler.validate(graphJson) → List`. Each error points at a step name + field path; the Monaco editor highlights them inline. + +--- + +## Using workflow from the UI + +### Entry point + +`Workflows` (sidebar) → list → **+ New**. + +::: tip +The Workflows list is empty on a fresh install. That's intentional — v0 ships no built-in templates; flagship accounts co-author them. +::: + +### Editor (v1.3.0 = JSON only) + +- **Monaco editor**: JSON-schema validation, autocomplete, static Pebble checking +- **Template dropdown**: built-in skeletons fetched from `GET /api/v1/workflows/draft/templates` +- **Pre-compile**: `POST /api/v1/workflows/{id}/compile` returns compile diagnostics — **does not write a revision, does not actually run** +- **Publish**: compile → ACL validate → write a new `mate_workflow_revision` row (integer revision +1) + +::: warning Canvas comes in v1 +The `@vue-flow/core` canvas has a UI shell in v1.3.0, but it renders the step array as a node chain — **not** drag-to-edit. Double-click a node to open its field form; the primary edit path is still JSON. Full visual editing lands in v1.4+. +::: + +### Natural language → workflow draft (v1.3.0) + +`POST /api/v1/workflows/draft/generate` takes a free-form description ("I want a customer ticket triage flow with a Feishu entry, routing by tier — enterprise / pro / standard — to different handlers"), runs an internal agent to emit the corresponding `graph_json`, and **immediately compiles + returns** with diagnostics attached. + +Use cases: +- Authors who don't know the JSON DSL get a publishable first draft to refine in Monaco +- Bulk-feeding old SOP docs through the generator to get candidate workflow templates +- During customer co-creation, turn "how I want this to work" into something visualizable fast + +Response shape: +```json +{ + "graphJson": "...", // can be PUT directly into a draft + "compileErrors": [...], // same diagnostics as /compile + "modelUsed": "qwen-plus", + "tokenUsage": { ... } +} +``` + +::: tip Doesn't replace Monaco editing +The generator **never publishes directly** — it only emits a draft (via `saveDraft`); a human still has to review → compile → publish. The generated JSON may carry compile errors; the author cleans them up before publishing. +::: + +### Run history + +Every run persists as `mate_workflow_run` + `mate_workflow_run_step`. Detail view shows: +- Per-step input / output (payload URI references) +- Per-step duration + token usage +- Cross-step failure chain highlight +- For paused `await_approval` steps: who's approving, how long it's been waiting + +### Trigger sources + +A workflow run can only start through [Triggers](./triggers.md) or via `await_approval` resume — v0 has no "fire one now" endpoint. See API reference above for details. + +--- + +## API reference + +All endpoints live under `/api/v1/workflows/`. Requests must carry the `X-Workspace-Id` header. + +### CRUD + +| Method | Path | Description | +|---|---|---| +| `GET` | `/api/v1/workflows` | List all workflows in the current workspace | +| `POST` | `/api/v1/workflows` | Create a new workflow (draft starts empty) | +| `GET` | `/api/v1/workflows/{id}` | Fetch workflow metadata + inline draft | +| `PUT` | `/api/v1/workflows/{id}` | Update workflow metadata (name / description / enabled) | +| `PUT` | `/api/v1/workflows/{id}/draft` | Save the inline draft graph_json (does not compile) | +| `DELETE` | `/api/v1/workflows/{id}` | Soft-delete | + +### Compile / Publish + +| Method | Path | Description | +|---|---|---| +| `POST` | `/api/v1/workflows/{id}/compile` | Compile the current draft and return diagnostics — **does not persist a revision** | +| `POST` | `/api/v1/workflows/{id}/publish` | Compile + persist a new revision; updates `latest_revision_id` | + +### Draft generator (built-in in v1.3.0) + +| Method | Path | Description | +|---|---|---| +| `GET` | `/api/v1/workflows/draft/templates` | List built-in draft templates | +| `POST` | `/api/v1/workflows/draft/preview-compile` | Compile arbitrary graph_json — surfaces real diagnostics before a workflow row exists | +| `POST` | `/api/v1/workflows/draft/generate` | **Natural language → workflow draft** — describe the flow, an agent emits graph_json + compile diagnostics | + +### Run inspection / resume + +| Method | Path | Description | +|---|---|---| +| `GET` | `/api/v1/workflows/{id}/runs?limit=...` | Recent runs of a workflow (default 50) | +| `GET` | `/api/v1/workflows/runs/paused?limit=...` | All paused runs across the workspace (operator entry point) | +| `GET` | `/api/v1/workflows/runs/{runId}` | One run's detail + all step rows (input / output / duration) | +| `POST` | `/api/v1/workflows/runs/{runId}/resume` | Resume from `await_approval` pause (called automatically when an approval lands; not for manual use) | + +::: warning v0 has no standalone "start run" endpoint +There are only two paths to actually start a workflow run: + +1. **Via a trigger** — configure a trigger in [Triggers](./triggers.md) pointing at this workflow (`target_type=workflow`); when an event arrives the engine starts the run +2. **Via `await_approval` resume** — the resume endpoint pushes a paused run forward + +There is **no** `POST /api/v1/workflows/{id}/runs` "fire one now" endpoint in v0. For a dry run, use `/draft/preview-compile` to get compile output (**compile only — no persist, no real run**), or attach a temporary webhook trigger. A manual run-start endpoint is on the RFC but lands in a later release. +::: + +--- + +## Security model + +### Three-layer ACL + +| Role | Capabilities | +|---|---| +| `workflow:author` | Edit drafts, read own runs | +| `workflow:publisher` | Publish revisions; static ACL checks fire here | +| `workflow:operator` | Start/stop triggers, cancel runs, view other people's runs | + +### Per-step execution identity + +Every step carries in its ExecutionContext: +- `workspaceId`: must equal the workflow's workspace +- `actingAgentId`: for `sequential` and the three MateClaw modes → that step's agent; for other modes → publisher as fallback +- `triggeredBy` / `workflowId` / `revisionId` / `runId`: for audit traceability + +### Cross-workspace isolation + +At publish time `WorkflowAclValidator.checkAll(graphJson)` runs: +- `agentName` references must point to an employee in the current workspace +- `dispatch_channel` channels must be in the workspace allowlist +- `write_memory` employeeIds must be inside the current workspace + +Any failure → publish fails, transaction rolls back, **no revision row written, no `latest_revision_id` update**. + +### Relationship with [MCP per-agent tool binding](./mcp.md) + +Workflow **cannot** grant employees additional tools. When an agent step calls a tool, it goes through the same `AgentBindingService.getEffectiveToolNames(agentId)` ACL — what an employee can do inside a workflow is exactly what it can do in normal chat. + +--- + +## Internal storage URI for payloads + +Workflow inputs / outputs / intermediate artifacts above the 4KB default threshold are auto-spilled to the `mate_workflow_payload` table (v1.3.0: same-DB storage) or local filesystem fallback, and replaced inline with a `payload://` URI. This avoids large contexts blowing out the message column — see commit `9c81dba0 feat(workflow): payload fs fallback for medium-size payloads`. + +```text +payload://run/abc123/step/enrich/output → resolved by the backend at access time +``` + +The UI lazy-loads on demand. + +--- + +## Data model + +The workflow subsystem touches 8 tables: + +| Table | Purpose | +|---|---| +| `mate_workflow` | Workflow root (id / name / workspace) | +| `mate_workflow_revision` | Published revisions (integer revision; full graph_json snapshot; immutable) | +| `mate_workflow_run` | One execution (runId / triggerSource / status / startedAt / endedAt) | +| `mate_workflow_run_step` | Per-step input/output/duration inside a run | +| `mate_workflow_run_pause` | Persistent `await_approval` pause state (survives restart) | +| `mate_workflow_payload` | Large-payload internal storage (target for `payload://` URI) | +| `mate_trigger` | Trigger configurations (with cron `pattern_version`) | +| `mate_trigger_event` | Event dedup + rate-limit history | + +--- + +## Known limitations (v1.3.0) + +- **No drag-to-edit canvas** — the canvas is read-only chain rendering; primary edit path is JSON +- **No `loop` step** — can't iterate per-item or retry N times. Workaround: a fixed number of `fan_out` branches, or higher-level scheduling of multiple runs +- **No `invoke_skill` step** — skills must be attached to an agent and invoked through the agent +- **No cross-workspace sharing** — to reuse a workflow template across workspaces, copy it +- **No realtime collaborative editing** — concurrent edits to the same draft: **last write wins** +- **No per-step retry policy** — `errorMode.retry` is step-wide; finer-grained retry is deferred + +--- + +## Related + +- [Triggers](./triggers.md) — workflow's event entry point +- [Approval & security](./security.md) — what `await_approval` plugs into +- [Agents](./agents.md) — what `agentName` references +- [Channels](./channels.md) — what `dispatch_channel` can reach +- [Memory](./memory.md) — which file `write_memory` writes to diff --git a/mateclaw-server/src/main/resources/docs/en/workspaces.md b/mateclaw-server/src/main/resources/docs/en/workspaces.md new file mode 100644 index 00000000..9fa79947 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/en/workspaces.md @@ -0,0 +1,258 @@ +# Workspaces + +**A workspace is a box around one team's stuff.** + +MateClaw supports multiple teams in a single deployment by organizing every resource — agents, skills, wiki knowledge bases, conversations, memory files, tool guard rules, channels — into **workspaces**. When you're logged in, you see the workspaces you belong to and nothing else. When you switch workspace, the whole UI re-scopes: different agents, different skills, different knowledge, different channels. + +The point is that one MateClaw deployment can serve a product team, an engineering team, and a research team without their data, agents, or conversations bleeding into each other. + +--- + +## What belongs to a workspace + +Almost everything. The scoped resources: + +| Resource | Scoped how | +|----------|-----------| +| **Agents** | Every agent row has a `workspace_id` foreign key | +| **Skills** | Custom and MCP skills are scoped per workspace; builtin skills are global | +| **Wiki knowledge bases** | Every KB belongs to exactly one workspace | +| **Conversations and messages** | Scoped to the workspace the agent lives in | +| **Workspace memory files** | `workspace/{workspaceId}/{agentId}/...` | +| **Channels** | Each channel binds to one agent, so transitively to one workspace | +| **Tool Guard rules** | Rules can be global or scoped to a specific workspace | +| **File Guard paths** | Allowed/denied paths can be workspace-specific | +| **Cron jobs** | Scoped to the workspace of the agent they trigger | +| **Datasources** | External DB connections, scoped per workspace | +| **Audit events** | Every audit event records its `workspace_id` | + +What's **not** scoped (i.e., global): + +- JWT secret and auth config +- Model providers and API keys (global, with usage-tracked per workspace) +- MCP server definitions (global connections; workspace access is controlled by permissions) +- System-level settings in `mate_system_setting` +- Builtin skills + +--- + +## Workspace roles + +Each user is assigned to a workspace with one of four roles: + +| Role | Can do | +|------|--------| +| **Owner** | Everything, including deleting the workspace and managing members | +| **Admin** | Everything except deleting the workspace or changing the owner | +| **Member** | Use agents, read/write wiki, create conversations, invoke tools (subject to Tool Guard) | +| **Viewer** | Read-only — see agents and KBs, read conversations, can't create or modify | + +A user can belong to multiple workspaces with different roles. When they switch workspace, their effective permissions switch with them. + +### Role scope + +Roles control **UI visibility** and **API access**. The console hides menu items users don't have permission to use — a viewer role on a workspace doesn't see the Security menu or the workspace management page at all. The backend enforces the same rules on every API endpoint, so hitting a protected endpoint as a viewer returns `403 Forbidden`. + +--- + +## Creating a workspace + +`Settings → Workspaces → New Workspace`. + +1. Name it after what the team does, not what the team is called ("Product Research" over "Alpha Team") +2. Optional description +3. Save + +You become the owner of the workspace. You can now invite members. + +### Via API + +```bash +curl -X POST http://localhost:18088/api/v1/workspaces \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Product Research", + "description": "Competitive research and product specs" + }' +``` + +--- + +## Inviting members + +`Settings → Members → Add Member`. Enter an existing MateClaw user's username, pick a role, save. + +The member immediately sees the workspace in their workspace switcher on next page load. No invite email, no acceptance flow — the member's account already exists in MateClaw. + +### Via API + +```bash +curl -X POST http://localhost:18088/api/v1/workspaces/1/members \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "userId": 42, + "role": "member" + }' +``` + +--- + +## Switching workspace + +Top-left of the admin console. Click the workspace name to open the switcher; pick another one to switch. The entire UI re-scopes: + +- Sidebar menus re-render based on the new workspace's role +- The agent list refreshes to show agents in this workspace +- The Wiki list, skill list, channel list, etc. all change +- Active conversations stay open (they belong to their own workspace) + +Workspace selection is persisted per user — when you log back in, you land on the last workspace you used. + +--- + +## Security primitives that follow workspace boundaries + +This is where workspace isolation earns its keep. + +### File Guard + +The default allowed-path list for File Guard is `workspace/{workspaceId}/...`. A tool call from an agent in workspace A cannot read or write files that belong to workspace B, regardless of path traversal tricks — the symlink check and path normalization catch it. + +### Tool Guard rules + +Rules can be scoped to a specific workspace. You can have: + +- A **global** rule that says `ShellExecuteTool` needs approval +- A **workspace-specific** rule that says `ShellExecuteTool` is allowed if the command matches a narrow read-only pattern + +Only the second rule applies inside that workspace. Other workspaces see only the global rule. + +### Wiki knowledge bases + +A Wiki KB's data never leaves its workspace. An agent in workspace B cannot read a KB that belongs to workspace A, even if it tries. The Wiki search and read tools resolve `kbId` from the bound agent's workspace; cross-workspace reads are rejected at the API layer. + +### Memory files + +Workspace memory files (PROFILE.md, MEMORY.md, daily notes) live under `workspace/{workspaceId}/{agentId}/`. File Guard enforces the workspace boundary; the memory tools scope their list/read/write operations to the caller's workspace. + +### Channels + +Each channel binds to exactly one agent, so transitively to exactly one workspace. A DingTalk bot configured in workspace A is completely separate from a DingTalk bot configured in workspace B, even if they're configured to connect to the same DingTalk application (you probably don't want that, but it's technically allowed). + +--- + +## What isolation does NOT cover + +- **Shared global config** — JWT secret, model provider API keys, MCP server definitions are global. A workspace admin can't change them. +- **Audit log cross-workspace access** — security admins with the right permissions can query audit events across all workspaces. This is intentional — you want to see suspicious activity regardless of which workspace it happened in. +- **Token usage reporting** — aggregated globally, broken down per-workspace, per-agent, per-model in the Dashboard. +- **Model provider costs** — one billing relationship per provider at the global level; per-workspace quotas are on the [Roadmap](./roadmap). + +--- + +## Moving resources between workspaces + +Not supported directly. You have two options: + +1. **Export and import** — some resources have JSON export (agents via API, wiki KBs via API). Re-create them in the target workspace. +2. **Change ownership** — an admin or owner can directly update the `workspace_id` column in the database for simple resources. This is not officially supported; do it at your own risk and only with a backup. + +We'd like to support first-class moving in a future release. If you need this, leave a note on the [GitHub issue](https://github.com/matevip/mateclaw/issues). + +--- + +## Deleting a workspace + +**Only the owner can delete a workspace.** `Settings → Workspaces → [workspace] → Delete`. + +Deleting a workspace: + +- Soft-deletes every resource belonging to it — agents, skills, KBs, conversations, memory files, channels +- Removes all member associations +- Records an audit event + +Soft delete means the data isn't physically removed — it's marked `deleted = 1` and hidden from queries. If you delete by mistake, a database admin can restore it by flipping the flag. After the configured retention period, deleted data may be permanently purged by a cleanup job. + +--- + +## Workspace management API + +```bash +# List workspaces you belong to +curl http://localhost:18088/api/v1/workspaces \ + -H "Authorization: Bearer " + +# Get one workspace detail +curl http://localhost:18088/api/v1/workspaces/1 \ + -H "Authorization: Bearer " + +# Create +curl -X POST http://localhost:18088/api/v1/workspaces \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"name": "Product Research"}' + +# Update +curl -X PUT http://localhost:18088/api/v1/workspaces/1 \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"description": "Updated description"}' + +# Delete (owner only) +curl -X DELETE http://localhost:18088/api/v1/workspaces/1 \ + -H "Authorization: Bearer " + +# Member management +curl http://localhost:18088/api/v1/workspaces/1/members \ + -H "Authorization: Bearer " + +curl -X POST http://localhost:18088/api/v1/workspaces/1/members \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"userId": 42, "role": "member"}' + +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 \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"role": "admin"}' +``` + +--- + +## Data model + +**`mate_workspace`** + +| Column | Purpose | +|--------|---------| +| `id` | Primary key | +| `name` | Workspace name | +| `description` | Short description | +| `owner_id` | User ID of the owner | +| `create_time` / `update_time` | Timestamps | +| `deleted` | Logical delete flag | + +**`mate_workspace_member`** + +| Column | Purpose | +|--------|---------| +| `id` | Primary key | +| `workspace_id` | FK to `mate_workspace` | +| `user_id` | FK to `mate_user` | +| `role` | `owner` / `admin` / `member` / `viewer` | +| `joined_at` | When the user joined this workspace | +| `create_time` / `update_time` | Timestamps | + +--- + +## Next + +- [Admin Console](./console) — workspace switcher and UI +- [Security & Approval](./security) — how workspace isolation interacts with Tool Guard and File Guard +- [LLM Wiki](./wiki) — workspace-scoped knowledge bases +- [Memory](./memory) — workspace memory files diff --git a/mateclaw-server/src/main/resources/docs/zh/acp.md b/mateclaw-server/src/main/resources/docs/zh/acp.md new file mode 100644 index 00000000..a09246b7 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/acp.md @@ -0,0 +1,264 @@ +--- +title: ACP 接入 —— 把外部编码 Agent 接进 MateClaw +description: MateClaw 作为 ACP 宿主,通过 stdio 把 prompt 转交给 Claude Code、Codex、OpenCode、Qwen Code 等任意 Agent Client Protocol 端点。内置端点、可视化环境变量编辑、自动桥接技能卡、信任模型、错误翻译。 +head: + - - meta + - name: keywords + content: ACP,Agent Client Protocol,Claude Code,Codex,OpenCode,Qwen Code,外部 Agent,stdio JSON-RPC,编码 Agent 接入 +--- + +# ACP —— Agent Client Protocol + +**ACP 是 MateClaw 把 prompt 交给别人写的 Agent 的方式。** + +Agent Client Protocol 是一个开放规范,定义 Agent 客户端通过 JSON-RPC 调用 Agent 服务端的协议。MateClaw 扮演 **宿主**:拉起一个外部 CLI(Claude Code、Codex、OpenCode、Qwen Code …),通过 stdio 完成 `initialize` → `session/new` → `session/prompt` 三步握手,把流式响应回填到对话里,然后关闭进程。 + +如果说 MCP 是 "插一个工具",ACP 就是 **"插一整个 Agent"**。在 MateClaw 的一次轮次里,调用 Claude Code 和调用任何内置工具没有任何区别——你的 Agent 直接请求 `acp_claude-code_prompt` 然后读取结果即可。 + +--- + +## ACP vs MCP 一眼区分 + +| | **MCP** | **ACP** | +|---|---|---| +| 接什么 | 工具服务器 | Agent | +| 粒度 | 按工具(`tools/list`) | 按 prompt(一次性) | +| MateClaw 的传输 | stdio / streamable_http / sse | stdio | +| 会话模型 | 长连接、多次调用 | 无状态:拉起 → prompt → 关闭 | +| 典型用法 | 文件系统、搜索、自定义数据 API | 把编码任务转给 Claude Code / Codex | +| 在 MateClaw 的呈现 | 工具目录 | 技能目录(自动桥接)+ 工具包装 | + +同一个数字员工可以同时用两套。 + +--- + +## 内置端点 + +随 MateClaw 出厂的 Flyway 迁移会预置四个端点,**默认全部禁用**——你装好对应 CLI 之后再打开。 + +| 标识 | 显示名 | Command | 备注 | +|---|---|---|---| +| `claude-code` | Claude Code | `npx -y @zed-industries/claude-agent-acp` | Anthropic 的 Claude Code,读 `ANTHROPIC_API_KEY` | +| `codex` | OpenAI Codex CLI | `npx -y @zed-industries/codex-acp` | OpenAI 的编码 Agent,读 `OPENAI_API_KEY` | +| `opencode` | OpenCode | `opencode acp` | 多模型 Agent,二进制需在 `PATH` 中 | +| `qwen-code` | Qwen Code | `qwen --acp` | 阿里的编码 Agent,读 `DASHSCOPE_API_KEY` | + +内置行写保护——可以改 `args_json` / `env_json` / `description` / `trusted` / `enabled`,但不能改 slug、不能换 command、不能删除。要跑别的 Agent,**新建一个自定义端点**就行。 + +--- + +## 在控制台配置 + +`设置 → ACP 端点` 是完整的 CRUD 入口。 + +### 新建 / 编辑端点 + +- **Slug** —— 小写标识符(如 `claude-code`),创建后不可修改。技能通过 slug 引用端点。 +- **显示名** —— 技能页展示用的人类标签。 +- **描述** —— 运维备注。 +- **Command** —— 可执行文件(`npx`、`opencode` …),内置行锁定不可改。 +- **Args(JSON 数组)** —— CLI 参数,例如 `["-y","@zed-industries/claude-agent-acp"]`。 +- **Env(JSON 对象)** —— 注入子进程的额外环境变量。可视化编辑器会把 key 命中 `*API_KEY*` / `*TOKEN*` / `*SECRET*` / `*PASS*` 的值自动打码。 +- **Tool parse mode** —— `call_title` / `call_detail` / `update_detail`,决定上游工具调用事件渲染到流式抄本的方式。 +- **Trusted** —— 打开时,MateClaw 会自动同意上游 Agent 发来的 `session/request_permission`;关闭时,所有权限请求一律拒绝(适合非交互场景)。 +- **Enabled** —— 启停开关。禁用的端点不会进入技能目录。 + +### 测试连接 + +点击 **Test** 会拉起进程、跑一遍 `initialize` + `session/new`,再关掉。结果面板显示协议版本、Agent 能力、耗时,失败时附带翻译过的错误提示(见 [信任与错误翻译](#trust-error-translation))。状态会写回行上:`last_status` / `last_tested_at` / `last_error`。 + +### 启用 / 停用 / 删除 + +- **Toggle** —— 把端点从目录里摘掉但不删除。 +- **Delete** —— 仅自定义端点可删,内置端点拒绝删除。 + +任何变更都会发出 `AcpEndpointChangedEvent`,技能目录立刻重新同步——不需要重启服务。 + +--- + +## REST API + +基础路径:`/api/v1/acp/endpoints`,需要 JWT。 + +| Method | Path | 作用 | +|---|---|---| +| `GET` | `/` | 列出全部端点 | +| `GET` | `/{id}` | 取单条 | +| `POST` | `/` | 新建自定义端点 | +| `PUT` | `/{id}` | 局部更新(内置 `command` 锁定) | +| `DELETE` | `/{id}` | 删除自定义端点(内置拒绝) | +| `PUT` | `/{id}/toggle?enabled=true\|false` | 启用 / 停用 | +| `POST` | `/{id}/test` | 跑连接测试 | + +### 新建自定义端点 + +```bash +curl -X POST http://localhost:18088/api/v1/acp/endpoints \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "my-coder", + "displayName": "我的自研 Agent", + "description": "内部编码 Agent", + "command": "npx", + "argsJson": "[\"-y\",\"@my-org/my-acp-agent\"]", + "envJson": "{\"MY_API_KEY\":\"sk-...\"}", + "toolParseMode": "call_detail", + "trusted": true, + "enabled": true + }' +``` + +### 测试端点 + +```bash +curl -X POST http://localhost:18088/api/v1/acp/endpoints/9100002/test \ + -H "Authorization: Bearer " +``` + +返回示例: + +```json +{ + "name": "claude-code", + "command": "npx", + "args": ["-y", "@zed-industries/claude-agent-acp"], + "agentCapabilities": { "loadSession": false, "promptCapabilities": { "image": true } }, + "status": "OK", + "elapsedMs": 1842 +} +``` + +失败时 `status` 为 `ERROR`,`error` 字段是翻译后的提示。 + +--- + +## 端点是怎么被 Agent 用上的 + +两条路径: + +### 1. 自动桥接的虚拟技能(零配置) + +每个启用的端点都会被注册一张虚拟技能卡,并在工具注册表里多出一个名为 `acp__prompt` 的包装工具。它接收一个 `prompt` 字符串参数,返回上游 Agent 累积的文本回答。任何数字员工都可以像调内置工具一样调用它,不用写技能清单。 + +``` +设置 → ACP 端点(打开开关) + ↓ +AcpEndpointChangedEvent + ↓ +技能目录新增 "Claude Code" 卡片 +工具注册表新增 "acp_claude-code_prompt" + ↓ +Agent 调用工具 → AcpDelegationService.prompt() + ↓ +拉起 → initialize → session/new → session/prompt + ↓ +累积 agent-message-chunk 通知 + ↓ +把文本回填到 Agent 的轮次 +``` + +### 2. 手写技能(完全可控) + +技能清单可以声明 `type: acp` 并绑定到某个端点。技能会得到自己的包装工具(`acp___prompt`),可以在每次 prompt 前注入 `systemPrefix`,也可以按会话覆盖 `cwd`。 + +```yaml +# SKILL.md frontmatter +type: acp +acp: + endpoint: claude-code + systemPrefix: | + 你正在 MateClaw 仓库里工作。报完成前一定要先跑 `mvn test`。 + cwd: /workspaces/mateclaw +``` + +`claude-code-helper` 和 `codex-helper` 这两个出厂技能模板就是这么做的。 + +--- + +## 信任与错误翻译 {#trust-error-translation} + +### 信任开关 + +ACP 服务端可以在做敏感动作前(写文件、跑 shell 命令等)发 `session/request_permission` 请宿主放行。MateClaw **不会** 在流式响应中途打断用户去问,而是按端点的 `trusted` 标志决定: + +- `trusted: true` —— 自动选择 Agent 给的第一个选项放行。适合你自己装好的可信 CLI。 +- `trusted: false` —— 所有权限请求一律拒绝。适合沙盒或不可信端点;上游 Agent 会优雅退避。 + +### 错误翻译 + +编码 Agent 的报错出了名地难懂。`AcpRuntimeSupport.translateAuthError()` 识别常见 401 / 403 / "Request not allowed" 模式,把它们改写成可执行建议: + +- 缺密钥 → 提示 "请设置 `ANTHROPIC_API_KEY`" / `OPENAI_API_KEY` / `DASHSCOPE_API_KEY` / `GOOGLE_API_KEY`,按端点对应。 +- Claude Code OAuth 钥匙串劫持 → 建议跑 `claude logout`,把 `~/.claude/` 里盖住你环境变量的旧 OAuth token 清掉。 + +提示会在测试面板里弹出,也会带进 Agent 收到的流式错误信息里。 + +### 超时与限额 + +- `initialize` 握手:15 秒 +- `session/new`:10 秒 +- 整个 `session/prompt` 往返:5 分钟 +- stdio 缓冲上限:单次 50 MiB(行级 `stdio_buffer_limit_bytes` 可改) + +--- + +## 数据库 —— `mate_acp_endpoint` + +| 列 | 类型 | 默认 | 用途 | +|---|---|---|---| +| `id` | BIGINT | — | 主键,内置占用 `9100001`–`9100004` | +| `name` | VARCHAR(64) | — | 唯一 slug,技能引用此字段 | +| `display_name` | VARCHAR(128) | NULL | 显示名 | +| `description` | TEXT | NULL | 运维备注 | +| `command` | VARCHAR(256) | — | 进程命令 | +| `args_json` | TEXT | NULL | CLI 参数(JSON 数组) | +| `env_json` | TEXT | NULL | 环境变量覆盖(JSON 对象) | +| `tool_parse_mode` | VARCHAR(32) | `call_title` | `call_title` / `call_detail` / `update_detail` | +| `builtin` | BOOLEAN | FALSE | 内置行写保护 | +| `trusted` | BOOLEAN | TRUE | 自动放行权限请求 | +| `enabled` | BOOLEAN | FALSE | 默认关闭,按需打开 | +| `stdio_buffer_limit_bytes` | BIGINT | 52428800 | 单次 stdio 累积 50 MiB 上限 | +| `last_status` | VARCHAR(32) | NULL | `OK` / `ERROR` | +| `last_tested_at` | DATETIME | NULL | 上次测试时间 | +| `last_error` | TEXT | NULL | 上次测试错误 | +| `workspace_id` | BIGINT | 1 | 绑定的工作空间 | +| `create_time` / `update_time` | DATETIME | — | 时间戳 | +| `deleted` | INT | 0 | 逻辑删除 | + +DDL 在 `db/migration/{h2,mysql}/V68__add_acp_endpoints.sql`。 + +--- + +## 排查 + +### "Command not found" + +`command` 必须在跑 MateClaw 的用户的 `PATH` 里。`which npx`(或 `which opencode` / `which qwen`)确认一下。Docker 里要把 CLI 装进镜像。实在不行就把 `command` 写成绝对路径。 + +### Claude Code 报 "Request not allowed" / 403 + +多半是你 `~/.claude/` 里有个 OAuth token 缓存,盖住了你在环境变量编辑器里设的 `ANTHROPIC_API_KEY`。跑一次 `claude logout`,再点 **Test** 试试。测试面板检测到这种情况会主动提示。 + +### `session/new` 卡住 + +通常是上游 CLI 在首次启动时下载依赖(`npx -y` 会这样)。要么先在 MateClaw 之外手动跑一次 CLI 把依赖预热好,要么直接重试——后续调用都很快。 + +### "Subprocess output exceeded buffer" + +Agent 在一次调用里输出超过了 50 MiB 的 stdio。把端点行的 `stdioBufferLimitBytes` 调大,或者把 prompt 拆成多轮。 + +### 工具没出现在技能页 + +- 确认 `enabled: true`。 +- 确认测试通过(`last_status: OK`)。 +- 看一眼 Agent 的工具绑定——自动桥接的工具默认对所有数字员工可用,除非被明确排除。 + +--- + +## 下一步 + +- [技能系统](./skills) —— 包括手写的 `type: acp` 技能 +- [工具系统](./tools) —— 包装工具是怎么进注册表的 +- [MCP 协议](./mcp) —— 服务工具用的姊妹协议 +- [安全与审批](./security) —— 信任开关怎么和工具守卫配合 diff --git a/mateclaw-server/src/main/resources/docs/zh/agents.md b/mateclaw-server/src/main/resources/docs/zh/agents.md new file mode 100644 index 00000000..52400d5a --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/agents.md @@ -0,0 +1,322 @@ +--- +title: 多智能体引擎 — ReAct + Plan-and-Execute 双模式 +description: MateClaw 的多智能体系统支持 ReAct 推理循环和 Plan-and-Execute 任务拆解两种模式。Agent 之间可以互相委派,实现真正的多智能体协作。 +head: + - - meta + - name: keywords + content: 多智能体,ReAct,Plan-and-Execute,Agent委派,Spring AI Alibaba,AI Agent +--- + +# 多智能体引擎 + +> **它们现在叫"数字员工"了。** 后台所有出现"智能体"的地方都改了。底层运行时还是 Agent,但 UI 上、概念上、模板上——它们是你团队里的同事。 +> 命名变了,世界观也跟着变了:你给一个员工**角色(Role)**、**目标(Goal)**、**背景故事(Backstory)**,他知道自己是谁、为什么存在;你不用再写一段冷冰冰的 system prompt 让"agent"理解任务。 + +**一个数字员工就是一个带工具的人格。多个员工组成一支团队。** + +短版本是这个。长一点的版本:一个员工是一个名字,一段定义它怎么思考的 system prompt(包含角色 / 目标 / 背景故事),一个真正在思考的模型,一组它被允许调用的工具,可选的知识库,可选的技能包,它自己的一份记忆,以及它处理难题的方式——一步一步反应式(ReAct)还是先写计划再执行(Plan-and-Execute)。 + +你可以有很多个员工。每一个都专门做一类事。你把不同的活交给不同的员工。 + +--- + +## 一个数字员工有什么 + +| 部分 | 是什么 | +|------|--------| +| **名字** | 你和你的团队找它用的 | +| **图标** | 像素艺术风格、按角色配色,一眼认出 | +| **角色 (Role)** | 一句话——"我是产品研究员" / "我是客户支持" | +| **目标 (Goal)** | 一句话——"我帮你看清楚市场怎么动" | +| **背景故事 (Backstory)** | 它来自哪、为什么存在、它在意什么;这些会自动拼到最终 system prompt | +| **员工卡片标语 (Tagline)** | 卡片上展示的一句"自我介绍" | +| **System Prompt** | 它的人格、规则、风格、优先级(角色/目标/背景故事会自动注入) | +| **类型** | `react` 或 `plan_execute` | +| **工具** | 它被允许调用的工具(来自内置工具、MCP、技能包、ACP 桥接) | +| **知识库** | 它可以读的 LLM Wiki(KB 热点会自动注入到 system prompt) | +| **工作空间记忆** | 它自己那一份 `PROFILE.md`、`MEMORY.md`、`SOUL.md`、`AGENTS.md`,以及每日笔记 | +| **最大迭代次数** | 强制收敛前允许走多少轮推理循环 | +| **启用开关** | 关掉它 | + +注意一个**没有**的东西:模型。整个 MateClaw 部署里只有**一个全局默认模型**(在 `设置 → 模型` 里设置),所有 Agent 在运行时用的都是它。Agent 行上那个 `model_name` 字段是历史遗留——**被忽略**。这是刻意的:换模型是整个部署一次点击的事,不是三十次。 + +--- + +## 模板:从一个已经会工作的同事开始 + +你不是从一张白纸开始。`数字员工 → 新建` 打开一个模板选择器。里面有两层: + +### 5 个职业模板(推荐) + +每一个都自带角色、目标、背景故事、合适的工具集、像素艺术头像、专属配色——**打开就能用**: + +- **产品研究员**——竞品调研、市场动态追踪、用户访谈整理 +- **客户支持**——接住客户问的事、查知识库、把不能解决的升级出去 +- **知识管理员**——把零散材料整理进 LLM Wiki、维护双向链接、定期归纳 +- **数据分析师**——查数据源、跑 SQL、出图表、写结论 +- **行政助理**——日程、邮件草稿、跨工具协调 + +### 通用模板(白纸或半成品) + +- **通用助手**——默认的聊天员工 +- **研究 / 代码 / 写作 / 知识策展 / 数据分析**——按用途分类的半成品 +- **自定义**——彻底白纸一张,知道自己要什么就选这个 + +选一个,给它起名字、调一下角色和目标,保存。**一分钟以内就有一个能上岗的同事。** 创建之后每一项都能改。 + +--- + +## 两种思考方式 + +### ReAct —— 思考、行动、观察、继续 + +默认模式。ReAct 模式下的 Agent 跑一个循环:**推理**下一步该做什么,**行动**(可能调一个工具),**观察**结果,决定是再循环一次还是回答。 + +适合: +- 简单问答,需要一两次工具调用 +- 对话式交互,每一轮用户输入都不大 +- 需要 Agent "边学边反应"的任务 + +例子:*"北京今天什么天气?"* → 推理(需要实时数据)→ 行动(调 web search)→ 观察(15–26°C,晴)→ 回答。 + +### Plan-and-Execute —— 先计划、再执行 + +适合更大的任务。Agent 先生成一个**计划**——一个由 2 到 6 个步骤组成的有序列表,然后一步一步执行。完成之后,总结一下所有做过的事。 + +适合: +- 多步研究("调查 X,对比 Y,写一个简报") +- 步骤在一开始就能想清楚的任务 +- 你想**看着进度跑**的任务——计划和每一步的状态会出现在对话旁边的**持久任务清单**里 + +例子:*"研究一下 Spring AI 框架,对比前三个,给我写个简报。"* → 计划(4 步)→ 按顺序执行 → 最终汇总。 + +### 怎么选 + +| 情境 | 用哪个 | 为什么 | +|------|--------|--------| +| 简单问答、单工具调用 | ReAct | 没有计划开销 | +| 信息检索 | ReAct | 一般 2–3 轮就结束 | +| 多步有序任务 | Plan-and-Execute | 显式计划更好看、更好 debug | +| 研究 + 对比 + 写作 | Plan-and-Execute | 每一步的结果喂给下一步 | +| "读这份文件然后告诉我 X" | ReAct | 一个工具,一个答案 | +| "给我写一份关于 X 的结构化报告" | Plan-and-Execute | 多轮收集 + 综合 | + +类型可以随时改。同一份 system prompt 在两种模式下都能工作得不错。 + +--- + +## 多 Agent 并行委派 + +一个 Agent 不是孤军作战。一个 Agent 可以把任务委派给另一个——或者**同时委派给三个**。 + +- **单点委派** —— 把一个子任务交给指定 Agent,在独立会话中执行,结果流式回传 +- **并行委派** —— 同时委派给多个 Agent,每个在自己的隔离会话里跑 +- **子会话实时可见** —— 你在 ChatConsole 里能看到每个子任务的推理、工具调用和进度 +- **路由提示** —— 系统 prompt 里内置,Agent 知道什么时候该自己做、什么时候该委派 + +例子:让代码 Agent 处理 Jira 工单,同时让研究 Agent 拉竞品数据,同时让写作 Agent 起草 Slack 回复。三路并行,结果汇总给编排者。 + +--- + +## 深度思考 + +不是所有问题都值得深度推理,但有些问题需要。MateClaw 支持按 Agent、按对话打开深度思考模式: + +- **`thinkingLevel`**:`off` / `low` / `medium` / `high` / `max` +- 支持 Anthropic extended thinking、DashScope qwq 推理、OpenAI o1 `reasoning_effort=high` +- 思考块流式进入 UI,做成可折叠面板——你看得见模型怎么想,token 不会浪费在不需要推理的闲聊上 + +--- + +## 雇佣一个数字员工 + +`数字员工 → 新建`: + +1. 选一个模板(5 个职业模板之一,或通用模板,或 Custom) +2. 起名字,挑头像(像素艺术风格的库可选,或自己上传) +3. 写**角色 (Role)**——一句话;**目标 (Goal)**——一句话;**背景故事 (Backstory)**——几句话 +4. 写一句**员工卡片标语 (Tagline)**——卡片上展示的"自我介绍" +5. 选类型(`react` 或 `plan_execute`) +6. 写或改 system prompt(角色 / 目标 / 背景故事会自动拼接进来,不用重复写) +7. 勾选它能用的工具,绑定它该读的知识库 +8. 设置 `max_iterations`(默认 10) +9. 保存 + +立刻生效。从聊天 UI 或 API 开始用。 + +### 工具绑定(per-agent tool picker) + +::: tip 1.3.0 新增 +在 v1.2.0 时,员工的工具绑定是平铺的"勾哪个就能用哪个"列表。v1.3.0 把这块重做成**分组 + 状态感知 + 命名空间感知**的 picker,专门处理 MCP 工具的脏状态。 +::: + +打开数字员工编辑器的"工具"标签页: + +- **按来源分组**:内置工具 / 技能注入工具 / MCP 工具(按 server 名再分组) / ACP 工具 +- **状态徽标**:每个工具有一个标签—— + - `connected` —— 当前可用 + - `stale` —— 这个 MCP server 现在连不上,但绑定还保留(恢复连接后立即可用) + - `unavailable` —— server / skill 已被禁用,绑定保留但运行时不会下发给员工 + - `orphan` —— **不存在**的工具引用(server 删了或 tool 改名了);保存时会**拒绝**保留这种引用,强制清理 +- **命名空间冲突**:两个不同 MCP server 提供同名工具时(比如两个都有 `read_file`),picker 显示完整 prefixed name(`server-a__read_file` / `server-b__read_file`);员工的 system prompt 里把它们映射回原始名以避免混淆 +- **保存时校验**:勾选的每个工具会跑 `AgentBindingService.validate(...)`——任何 orphan 引用直接报错,必须先去掉 +- **MCP server 重命名**:原来挂在某 server 上的绑定**自动跟随**到新名字(按持久化的 tool cache 匹配),不需要手动重新勾 + +UI 入口:`Agents → 选员工 → 工具`。 + +技术细节见 [MCP](./mcp#per-agent-工具绑定)。 + +### System Prompt 最佳实践 + +System prompt 是数字员工的声音、优先级、约束的来源。**角色 / 目标 / 背景故事**和技能指令、工作空间记忆系统会自动拼接到最终 prompt 里——这些部分你不用自己写。 + +**你自己**的 prompt 应该包含: + +1. **它该怎么说话**——语气、风格、措辞偏好("专业但不死板" / "面向客户保持谨慎") +2. **它被允许做什么、被期望做什么**——任务边界 +3. **不确定时怎么办**——"先搜,不要编造" / "跑危险命令前问我" +4. **输出格式**——需要结构就明说 + +**不要**写的东西: + +- 工具描述——会自动注入 +- 工作空间记忆的使用说明——从 `AGENTS.md` 来 +- 框架层的行为(工具调用格式、ReAct 结构)——别跟运行时对着干 + +示例: + +> 你是一个专业的技术文档助手。你的职责: +> +> 1. 根据用户需求搜索并整理技术资料 +> 2. 用清晰、结构化的方式回答问题 +> 3. 确保代码示例语法正确 +> 4. 不确定的时候先搜索,不要捏造 +> +> 原则: +> - 引用外部信息时注明来源 +> - 涉及时效性问题,先获取当前日期再搜索 + +--- + +## 给开发者:Agent 内部是怎么转的 + +只是用 Agent 的话,这一节可以跳。在它上面写代码(加节点、改路由、做扩展)的话,请直接看 [架构说明](./architecture)——StateGraph 拓扑、节点列表、共享状态 key、扩展点都在那一页。 + +--- + +## 生命周期状态 + +| 状态 | 含义 | +|------|------| +| `IDLE` | 等待输入 | +| `PLANNING` | 正在生成计划(Plan-and-Execute 模式) | +| `EXECUTING` | 正在执行工具调用或子任务 | +| `RUNNING` | 活跃的 ReAct 循环或 Plan-Execute 图执行中 | +| `WAITING_USER_INPUT` | 暂停,等用户响应 | +| `DONE` | 完成 | +| `FAILED` | 执行失败 | +| `ERROR` | 错误状态 | + +回合的结束原因: + +| 值 | 含义 | +|----|------| +| `NORMAL` | LLM 给出了直接的最终答案 | +| `SUMMARIZED` | 上下文压缩之后正常完成 | +| `MAX_ITERATIONS_REACHED` | 到达迭代上限被强制收敛 | +| `ERROR_FALLBACK` | 出错后降级的答案 | + +--- + +## 可靠性机制 + +这些是运行时自己在做的事,目的是让 Agent 在你不想去 debug 的那种地方不脆弱: + +- **上下文修剪**——上下文窗口快满时,早期轮次由 LLM 总结、摘要替换原文。缓存 30 分钟。摘要以用户消息形式注入,不是系统消息——防止历史内容被提升成系统级指令的注入风险。 +- **思考恢复**——流式中途断了,已经写出的思考和内容会持久化,会话重载时还在。 +- **迭代上限处理**——到达 `max_iterations` 不会崩溃,而是强制让 LLM 用现有信息生成一个尽力而为的总结答案。 +- **僵尸流清理**——后台跟踪每一个打开的 SSE 流,被遗弃的会被自动回收。 +- **429 重试**——LLM 限流错误会触发带退避的自动重试。 +- **重复检测**——抓住那些反复在同一个工具调用上打转的 Agent,强行把它拉出循环。 +- **工具超时可配置**——一个慢工具不会冻结整个回合。 +- **渠道健康监控**——失败的渠道适配器走指数退避重启。 + +这些没有一个是用户面的按钮。它们就自己在发生。 + +--- + +## Agent 管理 API + +### 创建 + +```bash +curl -X POST http://localhost:18088/api/v1/agents \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "技术助手", + "description": "专业的技术文档助手", + "agentType": "react", + "systemPrompt": "你是一个专业的技术文档助手...", + "maxIterations": 10 + }' +``` + +### 列表 / 获取 / 更新 / 删除 + +```bash +curl http://localhost:18088/api/v1/agents -H "Authorization: Bearer YOUR_JWT_TOKEN" +curl http://localhost:18088/api/v1/agents/1 -H "Authorization: Bearer YOUR_JWT_TOKEN" + +curl -X PUT http://localhost:18088/api/v1/agents/1 \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -d '{"name":"技术助手 v2","maxIterations":15}' + +curl -X DELETE http://localhost:18088/api/v1/agents/1 \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" +``` + +### 流式对话 + +```bash +curl -N "http://localhost:18088/api/v1/agents/1/chat/stream?message=你好&conversationId=default" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" +``` + +--- + +## 调试 + +在 `application.yml` 里打开 DEBUG 日志: + +```yaml +logging: + level: + vip.mate.agent: DEBUG + vip.mate.agent.graph: DEBUG +``` + +你会看到节点一节点的执行过程:状态切换、dispatcher 路由决策、迭代数、工具调用参数和结果摘要、Tool Guard 检查结果。 + +### 常见问题 + +| 症状 | 可能原因 | +|------|----------| +| Agent 不响应或超时 | 模型配置错、API Key 无效、额度用光 | +| Agent 卡在循环里出不来 | `max_iterations` 太低,或者某个工具反复报错 | +| `MAX_ITERATIONS_REACHED` 经常触发 | 调 system prompt 或者把上限调高 | +| 工具调用悄悄失败 | Tool Guard 在拦——看 `mate_tool_guard_audit_log` | +| 等审批的图恢复不了 | `chatWithReplay` 里 `toolCallPayload` 格式对不上 | + +--- + +## 下一步 + +- [工具系统](./tools)——Agent 能调用什么 +- [技能系统](./skills)——怎么扩展 Agent 能做的事 +- [LLM Wiki](./wiki)——知识怎么被 Agent 读到 +- [记忆系统](./memory)——Agent 怎么跨会话记住东西 +- [工作流](./workflow)(1.3.0+)——把多个数字员工 + 系统动作编排成一条业务流程 +- [触发器](./triggers)(1.3.0+)——让事件自动启动工作流或员工对话 +- [架构说明](./architecture)——StateGraph 运行时深入 diff --git a/mateclaw-server/src/main/resources/docs/zh/ambient-ai.md b/mateclaw-server/src/main/resources/docs/zh/ambient-ai.md new file mode 100644 index 00000000..ec7cf952 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/ambient-ai.md @@ -0,0 +1,171 @@ +--- +title: 主动型 AI — AI 找你,不是你找 AI +description: 定时任务 + 多渠道交付 = 主动型 AI。每天早上九点把简报推送到飞书,竞品有大动作直接 ping 你的钉钉,AI 在该出现的时候自己出现,不需要你想起它。 +head: + - - meta + - name: keywords + content: 主动型AI,Ambient AI,定时任务,Cron,多渠道交付,飞书简报,钉钉推送,Slack机器人,环境式AI,主动汇报 +--- + +# 主动型 AI + +**AI 找你,不是你找 AI。** + +ChatGPT、Claude、Gemini——每个 AI 助手都在等你打开它。打开浏览器、登录、点开输入框、敲字、等回答。AI 是一个你必须主动走过去的东西。 + +MateClaw 不是。 + +你可以让任何一个 Agent 在任何一个时间,去任何一个聊天软件里,**主动找你**。 + +``` +每天早上 9:00:日报 Agent → 飞书研发群 +每周一 10:00:销售数据 Agent → Slack 频道 +凌晨 4:00 失败的任务 → 执行助理 Agent → DingTalk 私聊 +``` + +这是从「对话型 AI」到 **主动型 AI** 的升维。我们叫它 **Ambient AI**——环境式 AI。它不在某个浏览器标签页里等你,**它在你的工作流里**。 + +--- + +## 它在做什么 + +三件事串在一起: + +1. **Cron Job**——一个能跑 Agent 的调度器(`mate_cron_job`、`mate_cron_job_run` 两张表) +2. **Agent 跑一遍**——触发时间到了,调度器拉起 Agent 上下文,跑一整套工具链(搜索、抓取、读 Wiki、查数据库⋯) +3. **结果通过渠道交付**——Agent 的输出走 `CronJobCompletedEvent` → `CronDeliveryListener` → `ChannelCronResultDelivery`,落到你预设的渠道 + +整个过程**没有人值守**。你设置完,AI 就开始按时上班。 + +--- + +## 三个典型用法 + +### 一、每日简报 + +> 每天早上 9 点,竞品监控 Agent 跑一遍:搜索昨天发了什么 release、读 5 个目标公司的官博、对比 24 小时内的差异,把最重要的三件事写成 Markdown,推送到飞书研发群。 + +`Cron 表达式 0 0 9 * * ?` · `Agent: 竞品监控` · `渠道: 飞书 - 研发群` + +你坐进地铁,手机弹出消息,三件事看完。到工位之前你已经知道今天要追什么。 + +### 二、周报汇总 + +> 每周一上午 10 点,销售助理 Agent 查上周的订单数据库、抽出关键指标、写一份带表格的周报,推送到 Slack 频道。 + +数据来自 [数据源工具](./config),Agent 自动写 SQL 并解释结果。Wiki 里如果有过往周报,会被自动引用做对比。 + +### 三、事件触发 + +> 邮件来了带「老板」标签 → 执行助理 Agent → 总结邮件主旨 + 草拟回复 → 推送到微信。 + +事件触发用 cron 高频轮询 + 触发条件,或者用 [MCP 工具](./mcp) 接外部 webhook。Agent 跑完通过同一条交付链路落到渠道。 + +--- + +## 怎么配 + +`控制台 → 定时任务 → 新建` 三步: + +1. **Cron 表达式**——什么时候跑(标准 6 段 cron,UI 里有图形化编辑器) +2. **Agent**——选一个已经配好工具和系统指令的 Agent +3. **结果交付**——选一个 [渠道](./channels) 作为输出(飞书 / 钉钉 / Slack / 企业微信 / Telegram / 任意已配置的渠道) + +保存。下次触发时间一到,Agent 就开始上班。 + +::: tip 别配 100 个 cron +和模型管理一样——你不需要 100 个定时任务,你需要**一个真的有用的**。先做"早上 9 点的简报",跑两周,看一下哪些信息你真的会读,哪些是噪音,再加第二个。 +::: + +--- + +## 为什么这件事重要 + +整个 2025–2026 年,AI 圈在追同一个目标——**不需要你打开屏幕**。 + +- **Vision Pro** 想让 AI 在你视野里出现,没做成 +- **Humane AI Pin** 想让 AI 在你身上出现,崩了 +- **Echo / Alexa** 当年想让 AI 在你家里出现,停留在天气预报 + +它们都试图用一个新硬件去解决这件事。 + +MateClaw 的答案是:**你团队已经在用的所有聊天软件,就是那个"硬件"**。 + +飞书、钉钉、企业微信、Slack、Telegram、Discord、QQ——你早就开着。AI 出现在你已经看的地方,就足够了。**不需要新设备,不需要新习惯。** + +--- + +## 它和别的 AI 产品有什么不一样 + +| | 对话型 AI | 主动型 AI(MateClaw) | +|---|---|---| +| 触发方式 | 你打开它 | 它在该出现的时间出现 | +| 在哪 | 一个浏览器 tab | 你已经在用的 IM | +| 什么时候说话 | 你问它才说 | 你需要时它就说 | +| 离线时 | 错过 | 等你上线就推 | +| 失败时 | 红色 error | 自动重连,下一次正常推 | + +最右那一列只有 MateClaw 完整实现了——因为只有 MateClaw 同时有: + +- **多 Agent 引擎**(ReAct + Plan-Execute) +- **Cron 调度 + 失败重试** +- **9 个 IM 渠道适配器**(每个都有指数退避重连) +- **持久化记忆**([Memory](./memory),Dreaming 之后越用越懂你) +- **Wiki 知识层**([LLM Wiki](./wiki),让调研有依据) +- **Tool Guard**([Security](./security),敏感操作问你一句再执行) + +Cron 是最后一块拼图——把上面这堆能力**翻译成时间触发**。 + +--- + +## 安全考虑 + +主动 = 需要更严格的权限控制。一个 cron 触发的 Agent 跑得没人盯着,它能调什么工具就直接调,没有"我再确认一下"的机会。 + +所以: + +- **Cron Agent 不会绕过 Tool Guard**——需要审批的工具调用照样卡在那里,等你在 IM 里点一下批准,Agent 才继续。详见 [审批工作流](./security#审批工作流-人在回路) +- 不想被打断的,把敏感工具配成 `deny` 而不是 `require_approval`——让它在权限之外的地方直接停下,不发审批通知 +- **每次 cron 执行都进审计日志**(`mate_audit_event`)——哪个任务在什么时候、用什么工具做了什么,全有记录 + +--- + +## 底层数据(如果你好奇) + +| 表 | 用途 | +|---|---| +| `mate_cron_job` | 每个定时任务一行——Agent ID、cron 表达式、目标渠道、超时、启用开关 | +| `mate_cron_job_run` | 每次执行一行——开始/结束时间、状态、输出摘要、错误信息(如有) | + +代码组织: + +- `vip.mate.cron.service.CronJobLifecycleService` —— 任务生命周期管理 +- `vip.mate.cron.service.CronJobRunner` —— 单次执行 +- `vip.mate.cron.delivery.ChannelCronResultDelivery` —— 把 Agent 输出落到渠道 +- `vip.mate.cron.delivery.CronDeliveryListener` —— 监听 `CronJobCompletedEvent` +- `vip.mate.cron.CronChatOriginFactory` —— 构造 cron 来源标记,会话能查到这条对话来自哪个定时任务 + +### API + +```bash +# 列出所有定时任务 +curl http://localhost:18088/api/v1/cron-jobs \ + -H "Authorization: Bearer " + +# 立刻试跑一次(不影响下次定时触发) +curl -X POST http://localhost:18088/api/v1/cron-jobs/{id}/run-now \ + -H "Authorization: Bearer " + +# 看历史执行 +curl http://localhost:18088/api/v1/cron-jobs/{id}/runs \ + -H "Authorization: Bearer " +``` + +--- + +## 下一步 + +- [多渠道接入](./channels) —— 把 AI 输出送到哪一个 IM +- [Agent 引擎](./agents) —— cron 调度的就是你配好的 Agent +- [安全与审批](./security) —— 敏感操作不让 cron 自动跑 +- [LLM Wiki](./wiki) —— 给 cron Agent 一座知识库做调研 diff --git a/mateclaw-server/src/main/resources/docs/zh/api.md b/mateclaw-server/src/main/resources/docs/zh/api.md new file mode 100644 index 00000000..78d14858 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/api.md @@ -0,0 +1,509 @@ +# API 参考 + +所有 REST 端点前缀 `/api/v1/`。所有响应遵循同一个信封格式: + +```json +{ + "code": 200, + "message": "success", + "data": { } +} +``` + +除了 `/api/v1/auth/login`,所有端点都需要 `Authorization` header 里带 JWT: + +``` +Authorization: Bearer +``` + +深入的行为细节去读对应的功能页——[聊天与消息](./chat)、[Agent 引擎](./agents)、[工具系统](./tools)、[安全与审批](./security)、[LLM Wiki](./wiki)、[多模态创作](./multimodal)、[记忆系统](./memory)、[多渠道接入](./channels)、[模型配置](./models)、[工作空间](./workspaces)、[Doctor](./doctor)。 + +--- + +## 认证 + +``` +POST /api/v1/auth/login # 登录,获取 JWT +GET /api/v1/users/me # 获取当前用户 +PUT /api/v1/users/me # 更新个人资料 +PUT /api/v1/users/me/password # 修改密码 +``` + +**登录示例:** + +```bash +curl -X POST http://localhost:18088/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"admin123"}' +``` + +响应: + +```json +{ + "code": 200, + "data": { + "token": "eyJhbGciOiJIUzI1NiJ9...", + "tokenType": "Bearer", + "expiresIn": 86400 + } +} +``` + +--- + +## 聊天 + +``` +POST /api/v1/chat/{agentId}/message # 发送消息 +GET /api/v1/chat/{agentId}/stream?conversationId= # SSE 流式 +POST /api/v1/chat/{conversationId}/stop # 停止进行中的流 +GET /api/v1/chat/{conversationId}/pending-approvals # 列出等待的审批 +``` + +**发送消息:** + +```bash +curl -X POST http://localhost:18088/api/v1/chat/1/message \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"content":"你好,你能做什么?", "conversationId":"conv-abc123"}' +``` + +**SSE 流式示例:** + +```bash +curl -N http://localhost:18088/api/v1/chat/1/stream?conversationId=conv-abc123 \ + -H "Authorization: Bearer YOUR_TOKEN" +``` + +事件类型和 schema 在 [聊天与消息](./chat) 里。 + +### 会话 + +``` +GET /api/v1/conversations # 列表(?page&size&agentId) +GET /api/v1/conversations/{id}/messages # 取消息 +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/templates # 列出模板 +POST /api/v1/agents/templates/{id} # 从模板创建 +``` + +--- + +## 工具 + +``` +GET /api/v1/tools # 列表 +PUT /api/v1/tools/{id} # 更新 +PUT /api/v1/tools/{id}/toggle?enabled={bool} # 开关 +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`。 + +--- + +## 多模态 + +``` +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 # 扫码状态 +``` + +### 渠道 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}/draft # 保存草稿(graph_json) +POST /api/v1/workflows/{id}/publish # 发布草稿为新 revision +DELETE /api/v1/workflows/{id} # 删除 + +POST /api/v1/workflows/draft/generate # 自然语言生成 graph_json 草稿 +POST /api/v1/workflows/{id}/preview-compile # 静态检查 + Pebble 校验,不发布 + +POST /api/v1/workflows/{id}/runs # 起一个 run(异步) +GET /api/v1/workflows/{id}/runs # run 列表 +GET /api/v1/workflows/runs/{runId} # run 详情 + 每步 input/output/token/duration +POST /api/v1/workflows/runs/{runId}/resume # await_approval 后恢复 +POST /api/v1/workflows/runs/{runId}/cancel # 取消运行中 +``` + +--- + +## 触发器(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} # 更新 +DELETE /api/v1/triggers/{id} # 删除 +PUT /api/v1/triggers/{id}/toggle?enabled={bool} # 开关 + +POST /api/v1/triggers/events # 通用事件入口(webhook / 桥接外部系统) + # 立即 ACK 200,异步派发 +GET /api/v1/triggers/{id}/events # 该 trigger 的事件历史 +``` + +--- + +## 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}/members # 列成员 +POST /api/v1/workspaces/{id}/members # 添加成员 +DELETE /api/v1/workspaces/{id}/members/{userId} # 移除成员 +PUT /api/v1/workspaces/{id}/members/{userId}/role # 变更角色 +``` + +--- + +## 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 端点行为 diff --git a/mateclaw-server/src/main/resources/docs/zh/architecture.md b/mateclaw-server/src/main/resources/docs/zh/architecture.md new file mode 100644 index 00000000..33dc2c31 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/architecture.md @@ -0,0 +1,368 @@ +# 架构说明 + +**MateClaw 是怎么拼起来的,一页讲完。** + +**用** MateClaw 的人看 [项目介绍](./intro)。**在 MateClaw 上面建东西**的人——加工具、新渠道、自定义记忆 provider、新的 Agent 图节点——看这一页。 + +--- + +## 一张图的产品 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ MateClaw │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌─────────────────────┐ │ +│ │ Web 控制台 │ │ 桌面端 │ │ IM 渠道 │ │ +│ │ Vue 3 SPA │ │ Electron │ │ 钉钉 / 飞书 / │ │ +│ │ (src/static) │ │ + 内置 │ │ 企业微信 / Telegram/ │ │ +│ │ │ │ JRE 21 │ │ Discord / QQ / ... │ │ +│ └──────┬──────┘ └──────┬───────┘ └──────────┬──────────┘ │ +│ │ HTTP/SSE │ HTTP/SSE │ SPI │ +│ └────────┬────────┴───────────────────────┘ │ +│ │ │ +│ ┌───────────────▼──────────────────────────────────────────┐ │ +│ │ Spring Boot 后端(vip.mate.*) │ │ +│ │ │ │ +│ │ ┌────────────┐ ┌────────────┐ ┌────────────────────┐ │ │ +│ │ │ auth │ │ channel │ │ agent │ │ │ +│ │ │ (JWT) │ │ adapters │ │ (StateGraph │ │ │ +│ │ └────────────┘ └─────┬──────┘ │ runtime) │ │ │ +│ │ │ │ │ │ │ +│ │ ┌────────────┐ │ │ ┌──────────────┐ │ │ │ +│ │ │ workspace │ │ │ │ ReasoningN │ │ │ │ +│ │ │ isolation │ │ │ │ ActionN │ │ │ │ +│ │ └────────────┘ │ │ │ Observation │ │ │ │ +│ │ │ │ │ PlanGenN │ │ │ │ +│ │ ▼ │ │ StepExecN │ │ │ │ +│ │ ┌──────────────┐ │ │ FinalAnsN │ │ │ │ +│ │ │ Message │ │ └──────────────┘ │ │ │ +│ │ │ Router ├──▶ │ │ │ │ +│ │ └──────────────┘ └──────┼─────────────┘ │ │ +│ │ │ │ │ +│ │ ┌─────────────────────────────────────▼────────────────┐ │ │ +│ │ │ Tool Registry │ │ │ +│ │ │ │ │ │ +│ │ │ 内置 @Tool + MCP client + 技能脚本 │ │ │ +│ │ └─────────┬───────────────────────────────────────────┬──┘ │ │ +│ │ │ │ │ │ +│ │ ▼ ▼ │ │ +│ │ ┌──────────────┐ ┌─────────────┐ ┌───────────────────┐ │ │ +│ │ │ Tool Guard │ │ 审批 │ │ 审计日志 │ │ │ +│ │ │ (规则) │ │ 工作流 │ │ 管道 │ │ │ +│ │ └──────────────┘ └─────────────┘ └───────────────────┘ │ │ +│ │ │ │ +│ │ ┌────────────┐ ┌────────────┐ ┌────────────────────┐ │ │ +│ │ │ memory │ │ wiki │ │ skill │ │ │ +│ │ │ 多层 │ │ (分层) │ │ (SKILL.md 运行时) │ │ │ +│ │ │ SPI │ │ digester │ │ │ │ │ +│ │ └────────────┘ └────────────┘ └────────────────────┘ │ │ +│ │ │ │ +│ │ ┌────────────────────────────────────────────────────┐ │ │ +│ │ │ MyBatis Plus / H2 或 MySQL │ │ │ +│ │ │ │ │ │ +│ │ │ mate_agent / mate_message / mate_wiki_* / │ │ │ +│ │ │ mate_tool_guard_* / mate_workspace / ... │ │ │ +│ │ └────────────────────────────────────────────────────┘ │ │ +│ └───────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**一个 JAR。一个进程。The whole widget。** + +--- + +## 仓库布局 + +``` +mateclaw/ +├── mateclaw-server/ # Spring Boot 后端(心脏) +│ └── src/main/java/vip/mate/ +│ ├── MateClawApplication.java +│ ├── agent/ # StateGraph 运行时、节点、边、状态 +│ ├── planning/ # Plan 和 SubPlan 持久化 +│ ├── workflow/ # 工作流引擎(1.3.0+):DSL 编译、线性 runtime、payload spill +│ ├── trigger/ # 触发器引擎(1.3.0+):6 种 pattern、事件治理、CronDelegationPort +│ ├── tool/ # ToolRegistry、@Tool bean、MCP(per-agent 绑定)、guard +│ ├── approval/ # 审批工作流(兼任 workflow 的 await_approval 暂停/恢复桥接) +│ ├── skill/ # 动态技能包 +│ ├── wiki/ # LLM Wiki + Transformations 引擎(1.3.0+) +│ ├── memory/ # 记忆层 + SPI +│ ├── workspace/ # 工作空间实体 + 会话 + 文档 +│ ├── channel/ # 渠道 SPI + 适配器(1.3.0+: WeCom v2、reply queue、leader lease) +│ ├── llm/ # LLM provider 配置(1.3.0+: 多模态 sidecar 路由) +│ ├── auth/ # Spring Security + JWT +│ ├── audit/ # 审计事件管道 +│ ├── cron/ # 定时任务引擎(trigger.cron 通过 CronDelegationPort 复用) +│ ├── task/ # 异步任务运行时 +│ ├── dashboard/ # 指标聚合 +│ ├── datasource/ # 外部 DB 连接 +│ ├── stt/ # 语音转文字 +│ ├── tts/ # 文字转语音 +│ ├── system/ # 系统设置、bootstrap、引导 +│ ├── config/ # Spring 配置 +│ ├── common/ # 共享工具 +│ └── exception/ # 全局异常处理器 +│ └── src/main/resources/ +│ ├── application.yml +│ ├── db/migration/ # Flyway 迁移脚本(h2/ + mysql/) +│ ├── db/data.sql # 种子数据 +│ ├── prompts/ # LLM prompt 模板 +│ ├── skills/ # 捆绑的技能包 +│ └── static/ # 前端构建产物 +├── mateclaw-ui/ # Vue 3 管理控制台 +├── mateclaw-desktop/ # Electron 桌面壳 +├── mateclaw-webchat/ # 可嵌入的聊天小部件 +├── matevip-sites/ # 营销和文档站点(pnpm workspace) +├── docs/ # 这份文档(VitePress) +├── deploy/ # 生产部署配置 +├── docker-compose.yml +└── .env.example +``` + +后端是**一个模块化的单体**。其他项目是不和后端共享代码的独立包。 + +--- + +## Agent 运行时是一张 StateGraph + +**这是你给后端贡献代码时最重要的事。** + +**MateClaw 的 Agent 运行时不是一个类层次。** 没有 `BaseAgent` → `ReActAgent` → `MyCustomAgent` 的继承链。运行时是一张**由节点和条件边组成的 StateGraph**(来自 `spring-ai-alibaba-graph`),在运行时由 `AgentGraphBuilder` 装配。 + +### 关键文件 + +- `agent/graph/StateGraphReActAgent.java`——装配 ReAct 循环 +- `agent/graph/plan/StateGraphPlanExecuteAgent.java`——装配 Plan-and-Execute 图 +- `agent/graph/node/`——`ReasoningNode`、`ActionNode`、`ObservationNode`、`FinalAnswerNode`、`SummarizingNode`、`LimitExceededNode` +- `agent/graph/plan/node/`——`PlanGenerationNode`、`StepExecutionNode`、`PlanSummaryNode`、`DirectAnswerNode` +- `agent/graph/edge/` + `plan/edge/`——基于状态决定下一个节点的 dispatcher 函数 +- `agent/graph/state/MateClawStateKeys.java`——共享 state 对象的 key +- `agent/graph/state/MateClawStateAccessor.java`——state map 的类型化访问器(**别直接动 map**) +- `agent/graph/lifecycle/ReActLifecycleListener.java`——节点级插桩 hook +- `agent/AgentGraphBuilder.java`——按 Agent 配置拼装节点和边的 builder +- `agent/GraphEventPublisher.java` + `agent/graph/NodeStreamingChatHelper.java`——流式事件怎么从图里逃到 SSE 流里 + +### 怎么扩展 + +**加 Agent 行为**——在 `agent/graph/node/` 创建新节点,或在 `agent/graph/edge/` 创建新边 dispatcher。把它接进 `AgentGraphBuilder`。通过 `MateClawStateAccessor` 读写 state。 + +**不要**创建新的 `XxxAgent` 类。你会把图已经在做的事情重新实现一遍。 + +### 共享 state key + +| Key | 用途 | +|-----|------| +| `USER_MESSAGE` | 当前用户输入 | +| `MESSAGES` | 从 `mate_message` 加载的会话消息 | +| `OBSERVATION_HISTORY` | 本回合的工具调用结果 | +| `CURRENT_ITERATION` | 已经循环过多少次 | +| `MAX_ITERATIONS` | 上限 | +| `TOOL_CALLS` | 当前工具调用列表 | +| `AWAITING_APPROVAL` | 有调用需要人工审批时置 true | +| `FINAL_ANSWER` | Agent 的响应 | +| `FINISH_REASON` | 图为什么结束 | + +--- + +## 数据流 —— 单次回合 + +``` +1. POST /api/v1/chat/{agentId}/message + ↓ +2. ChatController.sendMessage() + ↓ +3. ConversationManager.loadOrCreate(conversationId) + ↓ +4. AgentGraphBuilder.build(agentEntity) ← 解析出编译好的图 + ↓ +5. graph.invoke(initialState) ← StateGraph 执行开始 + ↓ + ReasoningNode → Dispatcher → ActionNode → ObservationNode →(循环或结束) + ↓ +6. 工具调用走: + ToolRegistry.resolve() → + Tool Guard 规则评估 → + (需要审批时)mate_tool_approval 行 + SSE 事件 + AWAITING_APPROVAL=true + (内联允许时)ToolExecutionExecutor.execute() → observation + ↓ +7. Segment 通过以下方式流到客户端: + GraphEventPublisher → NodeStreamingChatHelper → SSE 流 + ↓ +8. 结束时: + FinalAnswerNode 聚合结果 + ConversationManager 把 segment 持久化到 mate_message + ConversationCompletedEvent 发出(异步记忆提取启动) + ↓ +9. 响应关闭 +``` + +--- + +## 扩展点 + +这些是你可以在上面建东西的 SPI 和插件点: + +### `@Tool` 标注的 Spring bean + +写一个带 `@Tool` 方法的 `@Component`。启动时被 `ToolRegistry` 捡起来。每个 `@Tool` 方法都成为一个可调用工具。 + +```java +@Component +public class MyCustomTool { + @Tool(description = "LLM 看到的描述") + public String doThing(@ToolParam(description = "...") String input) { + return "result"; + } +} +``` + +### `ChannelAdapter` SPI + +实现 `vip.mate.channel.ChannelAdapter`(或支持流式的 `StreamingChannelAdapter`)。注册成 Spring bean。通过 `ChannelWebhookController` 加 webhook 端点。见 [多渠道接入](./channels)。 + +```java +public interface ChannelAdapter { + void onMessage(ChannelMessage message); + void sendMessage(String channelId, String content); + String getChannelType(); +} +``` + +### `MemoryProvider` SPI + +实现 `vip.mate.memory.spi.MemoryProvider` 来插入自定义记忆后端(向量、图、外部服务)。**多个 provider 可以在 Agent 上堆叠**。见 [记忆系统](./memory)。 + +### MCP 服务 + +通过 stdio、streamable_http、sse 连接外部工具服务。它们的工具自动出现在工具注册表里——Agent 代码**不知道它们是外部的**。见 [MCP 协议](./mcp)。 + +### 技能包 + +把指令 + 工具 + 可选脚本打包进一个 `SKILL.md`。通过 UI 或 API 上传。Agent 在运行时可以调用它们。见 [技能系统](./skills)。 + +### Agent 图节点和边 + +更深的定制——在 `agent/graph/node/` 加新节点或在 `agent/graph/edge/` 加新 dispatcher。在配置 flag 后面接进 `AgentGraphBuilder`。State 访问走 `MateClawStateAccessor`。 + +--- + +## 持久化 —— 一份 schema,两种数据库 + +MateClaw 用 **MyBatis Plus**(不是 JPA)做数据库访问。约定: + +- 所有表前缀 `mate_` +- `snake_case` 列、`camelCase` Java 字段、自动映射 +- 每张表有 `create_time`、`update_time`、`deleted`(逻辑删除) +- **Flyway** 管理 schema 迁移——`db/migration/h2/` 和 `db/migration/mysql/` 各有一套方言脚本,启动时自动选择 +- `FlywayRepairConfig` 在每次启动时先 `repair()` 再 `migrate()`,checksum 变更和部分失败的迁移自动修复 +- 种子数据由 `DatabaseBootstrapRunner` 从 `db/data-*.sql` 加载,幂等执行 + +### 表分组 + +**身份和配置**——`mate_user`、`mate_system_setting`、`mate_model_config`、`mate_model_provider`、`mate_datasource`、`mate_mcp_server` + +**Agent 和计划**——`mate_agent`、`mate_agent_skill`、`mate_agent_tool`、`mate_plan`、`mate_sub_plan` + +**会话**——`mate_conversation`、`mate_message`、`mate_channel`、`mate_channel_session` + +**工具和审批**——`mate_tool`、`mate_tool_approval`、`mate_tool_guard_rule`、`mate_tool_guard_config`、`mate_tool_guard_audit_log` + +**技能和工作空间**——`mate_skill`、`mate_workspace`、`mate_workspace_member`、`mate_workspace_file` + +**知识和记忆**——`mate_wiki_knowledge_base`、`mate_wiki_raw_material`、`mate_wiki_page`、`mate_wiki_transformation`、`mate_wiki_transformation_run`(1.3.0+)、`mate_memory_recall` + +**工作流和触发器(1.3.0+)**——`mate_workflow`、`mate_workflow_revision`、`mate_workflow_run`、`mate_workflow_step_run`、`mate_workflow_payload`、`mate_trigger`、`mate_trigger_event` + +**运维**——`mate_cron_job`、`mate_cron_job_run`、`mate_async_task`、`mate_usage_daily`、`mate_audit_event`、`mate_doctor_check` + +--- + +## 流式 —— 为什么用 SSE 不用 WebFlux + +MateClaw 用 **Spring MVC**,不是 Spring WebFlux。**WebFlux 在依赖图里被明确排除。** + +为什么:Spring MVC + SSE 足以把 LLM 响应流式到前端。它更容易推理、更容易调试、不强迫整个栈变成响应式。 + +::: tip 虚拟线程(JDK 21) +`spring.threads.virtual.enabled=true` 已开启。Tomcat 请求线程、`@Scheduled` 任务和 `@Async` 方法全部运行在虚拟线程上。SSE 长连接不再占用平台线程——并发连接数不再受线程池大小约束。 +::: + +流式流程: + +1. 客户端打开 `GET /api/v1/chat/{agentId}/stream`,带 `Accept: text/event-stream` +2. Controller 返回 `SseEmitter` +3. Agent 图在工作线程上运行;节点执行把事件发给 `GraphEventPublisher` +4. 事件序列化成 SSE 格式写进 emitter +5. `ChatStreamTracker` 监视被遗弃的流并清理它们 + +同样的 SSE 模式被支持流式的渠道适配器复用(钉钉 AI Card、Web)。 + +--- + +## 前端架构 + +**Vue 3 + TypeScript + Composition API + ` + + +``` + +--- + +## 测试 + +### 后端测试 + +```bash +cd mateclaw-server +mvn test # 全部测试 +mvn test -Dtest=StateGraphReActAgentTest # 单个类 +mvn test -Dtest=StateGraphReActAgentTest#testChat # 单个方法 +``` + +### 前端类型检查和 lint + +```bash +cd mateclaw-ui +pnpm build # vue-tsc 类型检查 + vite build +pnpm lint # ESLint 自动修复 +``` + +### 手动测试清单 + +- [ ] 后端启动无错 +- [ ] 前端编译无类型错误(`pnpm build`) +- [ ] 用默认凭证能登录 +- [ ] 模型在 UI 里配好了 +- [ ] 对话能流式返回 +- [ ] 新功能按 PR 描述工作 +- [ ] 浏览器控制台没有错误 +- [ ] 如果改了用户面行为,**文档也更新了** + +--- + +## 文档变更 + +PR 改了用户面行为——新功能、重命名的端点、改过的配置 key——**在同一个 PR 里更新文档**。 + +文档在 `docs/`。挑相关页面更新 `docs/en/` 和 `docs/zh/`。中英版本**独立写作**,不是翻译——和已有页面的语气和风格保持一致。 + +```bash +cd docs +pnpm build +``` + +**PR 开出来之前 build 必须零错误通过。** + +--- + +## Pull request 流程 + +1. **标题**——conventional commit 格式 +2. **描述**——做了什么、为什么、怎么做的;链接 issue +3. **截图**——UI 改动带 before/after +4. **测试**——描述你怎么测的 +5. **破坏性变更**——在最上面清楚标注 + +### PR 模板 + +```markdown +## What + +改动的简短描述。 + +## Why + +为什么需要这个改动(链接 issue)。 + +## How + +技术方案。 + +## Testing + +怎么测的。 + +## Screenshots (if UI changes) + +Before / After。 +``` + +--- + +## 报告 bug + +- MateClaw 版本(或 commit hash) +- Java 版本和操作系统 +- **精确的**复现步骤 +- 预期 vs 实际行为 +- 相关日志输出 + +**好的 bug 报告得到好的修复。** + +--- + +## 下一步 + +- [快速开始](./quickstart)——搭建走一遍 +- [项目介绍](./intro)——架构概览 +- [架构说明](./architecture)——给开发者的 StateGraph 深入 +- [路线图](./roadmap)——我们接下来在做什么 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 new file mode 100644 index 00000000..3d13d1e8 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/desktop-ui-hot-update.md @@ -0,0 +1,518 @@ +# MateClaw Desktop UI 热更新设计 + +## 背景 + +当前 `mateclaw-desktop` 的运行链路不是“Electron 直接加载 `mateclaw-ui`”,而是: + +1. Electron 启动并显示本地 Splash。 +2. Electron 用内置 JRE 启动 `mateclaw-server.jar`。 +3. `mateclaw-ui` 已提前构建到 `mateclaw-server/src/main/resources/static`。 +4. `BrowserWindow` 最终加载 `http://localhost:18088`。 + +这意味着: + +- 现在的 UI 资源和后端 JAR 强绑定。 +- 任何 `mateclaw-ui` 改动,都要重新打包 `mateclaw-server.jar`,再跟着桌面安装包一起发布。 +- 现有 `electron-updater` 只能做“整包升级”,不能做“仅 UI 升级”。 + +因此这里要解决的问题,不是开发态 HMR,而是生产态 OTA:让 `mateclaw-ui` 可以脱离桌面安装包独立更新。 + +## 目标 + +- 支持 `mateclaw-ui` 独立于 `mateclaw-desktop` 发布。 +- UI 更新不要求用户下载新的桌面安装包。 +- 保持当前本地 `localhost` 架构,不把桌面应用直接改成远程网站壳子。 +- 更新失败可回滚到内置 UI。 +- 不影响现有 `electron-updater` 的整包升级能力。 + +## 非目标 + +- 第一阶段不做后端 JAR 热更新。 +- 第一阶段不允许 UI 任意突破当前后端 API 边界。 +- 第一阶段不替换现有 Electron Splash / 自动升级通道。 + +## 现状约束 + +### 1. UI 当前被打进 Spring Boot JAR + +`mateclaw-ui` 构建输出目录目前是: + +- `../mateclaw-server/src/main/resources/static` + +也就是 UI 构建产物直接进入后端静态资源目录,最终随 JAR 发布。 + +### 2. Desktop 最终加载的是后端地址 + +`mateclaw-desktop` 主窗口业务页当前加载: + +- `http://localhost:18088` + +所以 UI 热更新不能只改 Electron `dist`,必须让后端在运行时能切换静态资源来源。 + +### 3. 现有整包升级已存在 + +桌面端已经接入 `electron-updater`,并通过 GitHub Releases 发布整包升级。 + +因此新方案应当与它并存: + +- Shell / JRE / JAR 升级:继续走 `electron-updater` +- 纯前端升级:新增 UI OTA 通道 + +## 方案结论 + +推荐采用: + +**方案 A:外置 UI Bundle + 本地优先加载 + Manifest 驱动的 OTA 更新** + +核心思路: + +1. `mateclaw-ui` 产出独立的静态包(zip)。 +2. `mateclaw-desktop` 在启动时检查 UI 更新 Manifest。 +3. 下载并校验新的 UI 包后,解压到 `userData/ui-bundles//`。 +4. Electron 启动后端时,通过环境变量把“当前启用的 UI 目录”传给 Spring Boot。 +5. Spring Boot 优先从外部目录提供静态资源;若外部目录不存在或损坏,则回退到 JAR 内置 `classpath:/static/`。 +6. UI 更新完成后刷新窗口即可生效,无需安装新桌面包。 + +这是对当前架构改动最小、兼容性最强的路径。 + +## 为什么不选其他方案 + +### 方案 B:Electron 直接加载远程站点 + +不推荐作为主方案。 + +缺点: + +- 桌面应用退化成网站壳子,离线能力明显变差。 +- 安全面更大,远程页面注入风险更高。 +- 当前路由是 `history` 模式,本地与远程混用会增加协议、资源路径和鉴权处理复杂度。 +- 对现有 `localhost + Spring Boot` 架构破坏过大。 + +### 方案 C:把 UI 更新继续塞进 JAR 差分包 + +不满足目标。 + +原因: + +- UI 仍与后端耦合。 +- 每次 UI 变更都要重新发 JAR 和桌面安装包。 +- 无法做到真正的“仅 UI 热更新”。 + +## 目标架构 + +```text +Electron Shell +├── Splash UI(本地 dist) +├── UI Update Manager(新增) +├── Bundled JRE +├── mateclaw-server.jar +└── BrowserWindow → http://localhost:18088 + ├── 优先读取 userData/ui-bundles/current/ + └── fallback 到 classpath:/static/ +``` + +运行时目录建议: + +```text +~/Library/Application Support/MateClaw/ # macOS 示例 +├── data/ +├── ui-bundles/ +│ ├── current.json +│ ├── 1.0.3+20260405/ +│ │ ├── index.html +│ │ ├── assets/... +│ │ └── meta.json +│ └── 1.0.4+20260410/ +└── logs/ +``` + +## 关键设计 + +### 1. UI 包格式 + +每个 UI 发布产物建议包含: + +- `index.html` +- `assets/*` +- `logo/*` +- `icons/*` +- `meta.json` + +`meta.json` 示例: + +```json +{ + "uiVersion": "1.0.4", + "buildId": "20260410.1", + "minDesktopVersion": "1.0.0", + "minServerApiVersion": "1.0", + "maxServerApiVersion": "1.x", + "sha256": "..." +} +``` + +说明: + +- `uiVersion`:前端语义版本。 +- `buildId`:构建批次,便于排查。 +- `minDesktopVersion`:限制旧 Electron shell。 +- `minServerApiVersion` / `maxServerApiVersion`:约束 UI 与当前后端 API 兼容性。 + +### 2. 更新 Manifest + +桌面端不直接猜测最新版本,而是请求一个 Manifest。 + +建议格式: + +```json +{ + "channel": "stable", + "latest": { + "uiVersion": "1.0.4", + "buildId": "20260410.1", + "url": "https://download.example.com/mateclaw/ui/1.0.4/ui-bundle.zip", + "sha256": "..." + }, + "minimumDesktopVersion": "1.0.0", + "compatibleServerApi": "1.x", + "signature": "base64..." +} +``` + +Manifest 最好放在稳定的静态地址,不要依赖 GitHub API 动态查询 release 列表。 + +发布源建议优先级: + +1. 自有 CDN / OSS / COS / R2 +2. GitHub Releases 直链 + +如果主要用户在国内,建议不要把 GitHub 当唯一源。 + +### 3. Spring Boot 静态资源加载改造 + +需要在 `mateclaw-server` 中新增静态资源优先级: + +1. 外部目录 `file:${mateclaw.ui.dir}/` +2. 内置资源 `classpath:/static/` + +实现建议: + +- 新增配置项 `mateclaw.ui.dir` +- 在 `WebMvcConfigurer` 中注册资源处理器 +- 对 `/assets/**`、`/icons/**`、`/logo/**`、`/favicon.ico`、`/index.html` 和 SPA 路由统一转发 +- 当外部目录不存在时自动回退内置资源 + +这样 BrowserWindow 仍然访问 `http://localhost:18088`,但内容已经可由外置 UI 包覆盖。 + +### 4. Electron 侧 UI Update Manager + +新增一个独立的 UI 更新管理器,职责: + +1. 读取当前启用 UI 版本。 +2. 拉取远程 Manifest。 +3. 判定兼容性。 +4. 下载 zip。 +5. 校验 `sha256` 与签名。 +6. 解压到临时目录。 +7. 原子切换 `current.json` 或 `current` 软链接。 +8. 通知渲染层“有可用 UI 更新”或“更新已完成”。 + +建议时机: + +- 启动后 3 到 10 秒后台检查 +- 用户手动点击“检查前端更新” +- 设置页允许切换更新通道(stable / beta) + +### 5. 激活策略 + +建议采用“两阶段激活”: + +#### 启动前已下载完成 + +- Electron 在启动 Java 前先解析当前 UI 指针 +- 将 `MATECLAW_UI_DIR` 注入到 Java 进程环境变量 +- 本次启动直接加载新 UI + +#### 运行中下载完成 + +- 下载成功后先不杀后端 +- 标记“下次重启生效”是最稳妥方案 +- 若要做到即时生效,可尝试: + - 切换 `current` 指针 + - 通知前端 `window.location.reload()` + +第一阶段推荐: + +**下载后提示“重启应用以应用前端更新”** + +原因是: + +- 简化缓存一致性问题 +- 避免运行态资源引用一半新一半旧 +- 降低与长连接、SSE、登录态的耦合风险 + +### 6. 回滚策略 + +至少支持三层回退: + +1. 下载失败:保持当前 UI +2. 解压或校验失败:丢弃新包,保持当前 UI +3. 新 UI 启动异常:回退到上一版本 UI,最差回退到 JAR 内置 UI + +建议机制: + +- `current.json` 记录当前版本、上一版本、状态 +- UI 启动成功后,前端调用 `/api/v1/system/ui/boot-ok` 或通过 preload IPC 上报“本次版本已健康启动” +- 若启动后短时间内崩溃或白屏,下次启动自动回滚上一版本 + +### 7. 安全要求 + +UI 热更新本质上是在本地执行新的前端资源,必须做完整校验。 + +最低要求: + +- HTTPS 下载 +- `sha256` 校验 +- Manifest 签名校验 + +推荐增加: + +- 使用 Ed25519 公钥验签 +- 公钥随桌面端内置 +- 不允许跳过签名校验加载生产更新包 + +否则该通道会成为远程代码注入入口。 + +### 8. 缓存与资源路径 + +需要处理几个细节: + +- `index.html` 不应长缓存 +- `assets/*` 可使用内容 hash 长缓存 +- 外部 UI 包目录最好按版本隔离 +- `current` 只做版本指针,不直接覆盖旧目录 + +这和 Vite 的产物模式天然兼容。 + +### 9. 版本兼容规则 + +建议明确一条产品规则: + +- 只涉及 UI 表现、交互、文案、前端容错的变更,可以走 UI OTA +- 需要新增/修改后端 API、数据库结构、JRE 资源、Electron 权限能力的变更,必须走桌面整包升级 + +否则很容易出现: + +- UI 已升级 +- 本地 JAR 太旧 +- 页面调用了不存在的 API + +所以 Manifest 中必须带兼容约束。 + +## 发布链路设计 + +### 当前链路 + +1. `mateclaw-ui` 构建到后端 `static/` +2. Maven 打 JAR +3. Electron 打包安装包 +4. GitHub Releases 发布 + +### 新链路 + +#### 桌面整包发布 + +继续保持现状: + +1. 构建 UI +2. 打进 JAR +3. 打包桌面安装包 +4. 走 `electron-updater` + +#### UI 独立发布 + +新增: + +1. `mateclaw-ui` 单独构建到临时目录 +2. 生成 `meta.json` +3. 打 zip +4. 计算 `sha256` +5. 生成并发布 `ui-manifest.json` +6. 上传到 CDN / Release 资产 + +这样: + +- 新装用户依然有 JAR 内置 UI 可用 +- 老用户可在后续自动收到 UI OTA + +## 推荐实施阶段 + +### Phase 1:基础可用 + +目标: + +- 支持下载 UI 包 +- 支持启动时优先加载外部 UI +- 支持失败回退到内置 UI +- 下载完成后“下次重启生效” + +需要改动: + +- `mateclaw-server` + - 支持外部静态目录优先级 + - 提供 SPA fallback +- `mateclaw-desktop` + - 增加 UI Update Manager + - 增加 Manifest 拉取、下载、校验、解压、指针切换 + - 将 UI 版本信息暴露给 Splash / 设置页 +- `mateclaw-ui` + - 构建时生成 `meta.json` + - 设置页增加当前 UI 版本展示 + +这是最值得先落地的一版。 + +### Phase 2:产品化 + +目标: + +- 设置页支持“检查前端更新” +- 提示更新说明 +- 支持 stable / beta 通道 +- 支持启动失败自动回滚 + +### Phase 3:增强体验 + +目标: + +- 部分场景无需整应用重启即可刷新 UI +- 支持灰度发布 +- 支持按平台分发不同 UI 包 + +## 建议新增模块 + +### `mateclaw-desktop` + +建议新增: + +- `electron/main/ui-updater.ts` +- `electron/main/ui-runtime.ts` + +职责拆分: + +- `ui-updater.ts`:远程检查、下载、校验、解压、切换 +- `ui-runtime.ts`:读取当前 UI 指针、提供给 Java 进程环境变量 + +### `mateclaw-server` + +建议新增: + +- `vip.mate.config.ExternalUiProperties` +- `vip.mate.config.UiResourceConfig` +- `vip.mate.system.controller.DesktopRuntimeController` + +职责: + +- 配置外部 UI 目录 +- 注册静态资源与 SPA fallback +- 对前端暴露当前运行版本信息 + +### `mateclaw-ui` + +建议新增: + +- 构建脚本:生成 `meta.json` +- 设置页:显示 + - Desktop 版本 + - UI 版本 + - 后端版本 + - 更新通道 + - 最近检查时间 + +## 接口建议 + +桌面 preload / IPC 可以新增: + +- `uiUpdater.getState()` +- `uiUpdater.check()` +- `uiUpdater.download()` +- `uiUpdater.applyOnRestart()` + +后端接口建议新增: + +- `GET /api/v1/runtime/version` + +返回示例: + +```json +{ + "desktopVersion": "1.0.0", + "serverVersion": "1.0.0", + "serverApiVersion": "1.0", + "uiVersion": "1.0.4", + "uiSource": "external" +} +``` + +这样前端可以明确展示当前正在跑的是哪个 UI 包。 + +## 风险点 + +### 1. 前后端版本漂移 + +这是最大风险。 + +控制手段: + +- UI Manifest 增加兼容性约束 +- 约定 API 破坏性变更只能走整包升级 + +### 2. 白屏回滚不完善 + +如果只做下载和切换,不做启动成功确认,坏包可能导致用户持续白屏。 + +所以至少要有: + +- 上一版本指针 +- 启动健康上报 + +### 3. 更新源可达性 + +若继续完全依赖 GitHub Releases,国内网络环境下成功率可能不稳定。 + +建议尽快切到稳定 CDN。 + +### 4. 安全边界扩大 + +远程前端资源可执行,签名和 hash 校验是必须项,不是可选优化。 + +## 最小落地建议 + +如果现在就开始做,建议按下面顺序推进: + +1. 先改 `mateclaw-server`,让它支持“外部目录覆盖 classpath static”。 +2. 再改 `mateclaw-desktop`,把 UI 包下载到 `userData/ui-bundles/`,并通过环境变量传给 Java。 +3. 再补 Manifest、签名校验和版本展示。 +4. 最后再做“运行中更新提示”和“自动回滚”。 + +## 结论 + +对当前 MateClaw 架构,最合适的不是把桌面端改成远程站点壳,而是: + +**保留 `Electron + localhost Spring Boot` 架构,引入“外置 UI Bundle 覆盖内置 static 资源”的 OTA 机制。** + +这样可以: + +- 保持离线可用 +- 最大限度复用现有桌面端架构 +- 将 UI 发布频率从桌面整包中解耦 +- 把风险控制在“前端资源替换”这一层 + +第一阶段建议做到: + +- 启动时自动检查 UI 更新 +- 后台下载 +- 校验后写入外部目录 +- 下次重启生效 +- 失败自动回退到 JAR 内置 UI + +这版最稳,也最容易在现有代码上渐进落地。 diff --git a/mateclaw-server/src/main/resources/docs/zh/desktop.md b/mateclaw-server/src/main/resources/docs/zh/desktop.md new file mode 100644 index 00000000..93dc4a31 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/desktop.md @@ -0,0 +1,284 @@ +# 桌面应用 + +**双击。等 30 秒。登录。开始用。** + +四句话的桌面版。**不需要装 Java**,不需要打开浏览器,不需要 docker compose 文件,不需要记住端口号。MateClaw 桌面版把 Electron、JRE 21 运行时、打包的 Spring Boot 服务 JAR **全部装进一个安装包**。**你的用户永远不会知道下面跑的是 Java。** + +这一页给想**运行、构建、调试**桌面版的人看。 + +--- + +## 架构 + +``` +┌──────────────────────────────────────────┐ +│ Electron 外壳 │ +│ ┌────────────────────────────────────┐ │ +│ │ BrowserWindow (Chromium) │ │ +│ │ ┌──────────────────────────────┐ │ │ +│ │ │ Vue 3 前端 (dist/) │ │ │ +│ │ │ Element Plus + Tailwind │ │ │ +│ │ └────────────┬─────────────────┘ │ │ +│ └───────────────┼────────────────────┘ │ +│ │ HTTP / SSE │ +│ ┌───────────────▼────────────────────┐ │ +│ │ Spring Boot 后端 (子进程) │ │ +│ │ 127.0.0.1 上的动态端口 │ │ +│ │ 内置 JRE 21 + H2 文件数据库 │ │ +│ └────────────────────────────────────┘ │ +│ │ +│ ┌────────────────────────────────────┐ │ +│ │ electron-updater 自动更新 │ │ +│ └────────────────────────────────────┘ │ +└──────────────────────────────────────────┘ +``` + +一个进程树里住着三样东西: + +1. **Electron 主进程**——窗口、托盘、IPC、后端生命周期 +2. **BrowserWindow(Chromium)**——渲染 Vue 3 前端(和 Web 版同一份代码) +3. **Spring Boot 后端**——作为子进程被主进程启动,只监听 localhost + +后端在启动时**动态挑一个空闲端口**,这样就不会和你机器上别的东西冲突。 + +### 核心特性 + +- 原生窗口,不依赖浏览器 +- 系统托盘集成,后台运行 +- **内置 JRE 21**——用户**永远不用装 Java** +- **自动更新**通过 electron-updater +- **本地优先的数据** +- **动态后端端口** +- **UI 热更新**——前端资源可以独立更新,不用重新打包 +- 跨平台(macOS、Windows、Linux) + +--- + +## 支持的平台 + +| 平台 | 架构 | 状态 | +|------|------|------| +| macOS | Intel(x64) | 稳定 | +| macOS | Apple Silicon(ARM64) | 稳定 | +| Windows | x64 | 稳定 | +| Linux | x64 | 稳定 | + +--- + +## 前置要求(构建需要,运行不需要) + +**运行**这个 app?下载 + 安装。完。 + +**构建**这个 app? + +| 工具 | 版本 | 用途 | +|------|------|------| +| Node.js | 18+ | 前端构建 + Electron | +| pnpm / npm | 8+ / 9+ | 包管理器 | +| Java | 21+ | 后端编译 + 开发模式(生产构建自带 JRE) | +| Maven | 3.8+ | 后端构建 | + +--- + +## 模块布局 + +``` +mateclaw-desktop/ +├── electron/ +│ ├── main/index.ts # 主进程——后端生命周期、自动更新、托盘 +│ └── preload/index.ts # IPC 桥 +├── src/ # Vue 3 渲染进程源码 +├── resources/ +│ ├── jre/ # 内置 JRE +│ └── app.jar # 打包好的 Spring Boot 后端 JAR +├── build/ # 应用图标 +├── electron-builder.json # 打包配置 +├── package.json +└── vite.config.ts +``` + +--- + +## 开发模式 + +```bash +cd mateclaw-desktop +pnpm install +pnpm dev +``` + +开发模式下: + +1. Vite 起前端开发服务器(HMR) +2. Electron 主进程启动加载 Vite 开发 URL +3. 主进程在空闲端口启动 Spring Boot JAR 子进程 +4. 前端通过 HTTP/SSE 和后端对话 + +前端改动触发 HMR。主进程改动自动重启 Electron。 + +--- + +## 生产构建 + +```bash +cd mateclaw-desktop +pnpm build && npx electron-builder --mac # macOS +pnpm build && npx electron-builder --win # Windows +pnpm build && npx electron-builder --linux # Linux +``` + +产物落在 `release/`: + +| 平台 | 产物 | 说明 | +|------|------|------| +| macOS | `.dmg` + `.zip` | 拖进 Applications | +| Windows | `.exe`(NSIS) | 可自定义安装目录 | +| Linux | `.AppImage` | 加执行权限直接跑 | + +### 构建的完整前置流程 + +```bash +# 1. 构建前端静态资源 +cd mateclaw-ui +pnpm install && pnpm build + +# 2. 构建后端 JAR +cd ../mateclaw-server +mvn clean package -DskipTests + +# 3. 把 JAR 拷到桌面项目 +cp target/mateclaw-server.jar ../mateclaw-desktop/resources/app.jar + +# 4. 下载平台特定的 JRE +cd ../mateclaw-desktop +bash scripts/download-jre.sh + +# 5. 构建桌面安装包 +pnpm build && npx electron-builder +``` + +--- + +## Java 后端生命周期管理 + +Electron 主进程通过 Node.js `child_process` 管理 Spring Boot 后端: + +1. **启动**——用内置 JRE 把 JAR 作为子进程启动,传入一个动态端口,等就绪 +2. **就绪检查**——轮询 `http://127.0.0.1:{port}` 直到响应,然后加载前端 +3. **运行时**——前端通过 REST + SSE 通信 +4. **关闭**——发出优雅关机信号,等进程退出,关窗口 + +后端在会话中途崩了的话,主进程会发现并弹带日志尾巴的错误对话框。**不会白屏发呆。** + +--- + +## 自动更新 + +集成 electron-updater,从 GitHub Releases 自动检测和下载新版本。 + +### 流程 + +1. 启动时检查 GitHub Releases +2. 发现新版本时,UI 弹通知显示版本 + changelog +3. 用户确认后下载,带实时进度条 +4. 下载完成后选**立即安装**或**下次启动时安装** +5. App 退出、替换文件、重启 + +### 配置 + +```json +{ + "publish": [ + { + "provider": "github", + "owner": "matevip", + "repo": "mateclaw" + } + ] +} +``` + +### UI 热更新(不用重新打包) + +**前端资源可以独立热更新**——只改前端的修复不需要重发新安装器。看 `mateclaw-desktop/scripts/` 和 `desktop-ui-hot-update.md`。 + +--- + +## 数据存储 + +| OS | 路径 | +|----|------| +| macOS | `~/Library/Application Support/MateClaw/data/` | +| Windows | `%APPDATA%/MateClaw/data/` | +| Linux | `~/.local/share/MateClaw/data/` | + +日志、工作空间文件、技能脚本、Wiki 内容都在同一个用户目录下。做重大变更前**备份**。 + +--- + +## `electron-builder.json` 参考 + +| 设置 | 用途 | +|------|------| +| `appId` | `vip.mate.mateclaw`——系统注册和代码签名 | +| `productName` | 标题栏和安装器里显示的应用名 | +| `publish` | 自动更新源(GitHub Releases) | +| `extraResources` | JRE 和 `app.jar` | +| `mac.target` | `dmg` + `zip`,`arm64` 和 `x64` | +| `win.target` | `nsis` 安装器 | +| `linux.target` | `AppImage` | +| `mac.hardenedRuntime` | 签名和公证必需 | +| `nsis.oneClick` | `false`——让 Windows 用户选安装目录 | + +--- + +## 环境变量 + +桌面 app 读环境变量和独立后端一样。但有更简单的方式:**启动之后通过设置页面配置所有东西**。API key 进到加密的 `mate_model_provider` 表里。 + +--- + +## 故障排查 + +### 白屏 + +1. 后端没起来——看日志 +2. 端口冲突——动态端口选择器处理大部分情况,严格防火墙可能导致失败 +3. 内置 JRE 损坏——重装 app +4. 看日志(位置见下面) + +### 代码签名警告 + +- **macOS**——右键选**打开**绕过 Gatekeeper(首次启动)。生产分发:Apple Developer 证书做签名和公证。看 `mateclaw-desktop/CODESIGNING.md`。 +- **Windows**——SmartScreen 警告 → **更多信息 → 仍要运行**。生产分发:EV 代码签名证书。 + +### 桌面 app 启动不了 + +1. 安装版自带 JRE——不需要装 Java。开发版:确认 `java -version` 显示 21+。 +2. 看日志: + - macOS:`~/Library/Logs/MateClaw/` + - Windows:`%APPDATA%/MateClaw/logs/` + - Linux:`~/.local/share/MateClaw/logs/` +3. 从终端启动看控制台输出 +4. 确认后端选的端口没被防火墙挡 + +### 企业微信授权弹窗 + +企业微信二维码授权**必须在应用内弹窗打开**(不是系统浏览器),这样 `postMessage` 回调才能工作。MateClaw 在 `setWindowOpenHandler` 里对 `work.weixin.qq.com` 域名做了特殊处理——**自动作为应用内弹窗打开**。 + +--- + +## 注意 + +- 首次启动 10–30 秒(数据库初始化) +- 关窗口**不会**停后台服务——用系统托盘菜单完全退出 +- **定期备份用户数据目录** +- 内置 JRE 意味着安装包 80–120 MB + +--- + +## 下一步 + +- [快速开始](./quickstart)——最快走完桌面体验 +- [配置说明](./config)——运行时设置 +- [控制台](./console)——跑在 Electron 窗口里的 UI diff --git a/mateclaw-server/src/main/resources/docs/zh/docker-deploy.md b/mateclaw-server/src/main/resources/docs/zh/docker-deploy.md new file mode 100644 index 00000000..4bd5ae83 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/docker-deploy.md @@ -0,0 +1,310 @@ +# Docker 部署 + +桌面端之外的唯一推荐生产部署方式。一条 `docker compose up -d` 起三个容器:MySQL、SearXNG、mateclaw-server。 + +这一页覆盖**要求、步骤、验证、常见坑**。配置变量明细请看 [配置说明](./config)。 + +--- + +## 前置要求 + +| 项 | 最低 | 推荐 | 备注 | +|---|---|---|---| +| Docker Engine | 24.0+ | 最新稳定 | `docker --version` 确认 | +| Docker Compose | v2.20+ | v2.30+ | `docker compose version`(注意是 `compose` 不是 `compose`) | +| 宿主 RAM | 4 GB | 8 GB+ | 浏览器工具启动时 Chromium 会吃 1-2 GB | +| 磁盘空间 | 6 GB | 20 GB+ | 镜像约 2 GB + MySQL 数据 + 工作空间文件 | +| /dev/shm | 默认 | compose 已自动设 2 GB | Chromium 用共享内存做渲染,默认 64 MB 会 SIGBUS | +| 网络 | 出公网 | — | 拉镜像 + 调 LLM API | + +**不需要**:宿主装 Java / Node / Maven / Chrome / Python —— 全部在镜像里。 + +--- + +## 三个容器 + +| 服务 | 镜像 | 作用 | 暴露端口 | +|---|---|---|---| +| `mysql` | `mysql:8.0` | 业务数据存储 | `3306` | +| `searxng` | 本地构建 `./docker/searxng/` | 无 API Key 搜索兜底 | `8088` | +| `mateclaw-server` | 本地构建 `mateclaw-server/Dockerfile` | Spring Boot 后端 + 内置浏览器 | `18080` | + +--- + +## SearXNG 搜索服务 + +### 为什么要自己打镜像 + +`docker/searxng/Dockerfile` 从官方 `searxng/searxng:latest` 派生,**把自定义 `settings.yml` 打进 `/etc/searxng/settings.yml`**。这不是洁癖,是**必须**: + +- **上游默认只开 `html` 格式**,mateclaw 后端请求的是 `GET /search?q=...&format=json` —— 默认配置下直接返回 HTML 错误页,`SearXNGSearchProvider` 解析失败返回空列表,UI 显示"搜索暂时不可用" +- **上游默认启用反爬 Limiter 插件**,拦截没有 JS / Cookie 的服务端调用,回 HTTP 429 + +我们的 `docker/searxng/settings.yml` 做了三件事: + +1. `search.formats: [html, json]` —— 放开 JSON +2. `server.limiter: false` —— 关闭反爬限流 +3. 收敛引擎列表到可靠子集(DuckDuckGo / Bing / Brave / Wikipedia / Google / Startpage),裁掉默认那几十个不常用的 + +**不要**改成从宿主 bind-mount `settings.yml`,早期版本踩过坑 —— 宿主目录不存在时 Docker 会自动创建空目录把文件盖掉,容器起来就没配置了。如果要改 settings.yml,编辑 `docker/searxng/settings.yml` 然后: + +```sh +docker compose build searxng +docker compose up -d searxng +``` + +### 搜索 provider 降级链 + +后端 `SearchProviderRegistry` 按以下优先级选: + +1. 用户在「设置 → 搜索」里显式指定的 provider(`searchProvider` 配置项) +2. 按 `autoDetectOrder` 遍历,**优先选已配置 API Key 的付费 provider**(Serper order=1,Tavily order=2) +3. 回退到 keyless —— SearXNG(order=50)优先于 DuckDuckGo(order=100) + +一台全新容器、啥 API Key 都没配的情况下,默认就是 **SearXNG 接所有搜索流量**。 + +### 验证 SearXNG 通路 + +```sh +# 1. 直接打容器 +curl -s 'http://localhost:8088/search?q=test&format=json' | head -5 +# 期望:{"query": ..., "results": [...]} +# 如果拿到 HTML:settings.yml 没生效 + +# 2. 从 mateclaw-server 容器内部打 +docker exec mateclaw-server wget -qO- 'http://searxng:8080/search?q=test&format=json' | head -5 +# 如果不通:compose 网络有问题 + +# 3. 在 UI 聊天里让 agent 搜点东西,看后端日志 +docker compose logs -f mateclaw-server | grep "搜索 provider" +# 期望看到:搜索 provider 解析: searxng (source=keyless-fallback) +``` + +### 想用外部 SearXNG + +假如你已经在别处部署了 SearXNG 实例,可以在 `.env` 里: + +```properties +SEARXNG_BASE_URL=https://your-searxng.example.com +``` + +然后把 `docker-compose.yml` 里的 `searxng` 服务块注释掉。但记得**你那个实例也要满足同样的 JSON + Limiter 要求**。 + +--- + +## 浏览器自动化 + +### 镜像里到底装了什么 + +后端镜像以 `mcr.microsoft.com/playwright:v1.52.0-noble` 为基础(Ubuntu Noble 24.04,glibc),由 `mateclaw-server/Dockerfile` 的第三阶段拉起,额外装: + +- `openjdk-21-jre-headless` —— 跑 Spring Boot JAR +- `fonts-noto-cjk` —— 中文页面截图不出豆腐块 +- `fonts-noto-color-emoji` —— Emoji 渲染 +- `tzdata` —— 时区 `Asia/Shanghai` + +Playwright 官方镜像已经把三大浏览器预装在 `/ms-playwright/`: + +- `chromium-XXXX/chrome-linux/chrome` —— 主力 +- `firefox-XXXX/firefox/firefox` +- `webkit-XXXX/pw_run.sh` + +所有系统依赖(`libnss3` / `libgbm1` / `libasound2` / `libx11-xcb1` / `libxkbcommon` / …)随镜像装好了。**不需要 `playwright install`,也不怕 Alpine-musl 兼容性坑**。 + +Dockerfile 里显式设的环境变量: + +```dockerfile +ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright +``` + +—— Playwright Java 启动时按这个路径找预装浏览器,**不会**再去 `$HOME/.cache/ms-playwright` 联网下载。 + +### BrowserLauncher 的 7 级降级 + +`vip.mate.tool.browser.BrowserLauncher` 按如下优先级启动浏览器,任何一级命中即止: + +1. `CONFIG_CDP` —— 配了 `MATECLAW_BROWSER_CDP_URL` → 直接 attach 已运行的 Chrome +2. `CONFIG_PATH` —— 配了 `MATECLAW_BROWSER_CHROME_PATH` 或 `CHROME_PATH` env → 用指定 exe +3. `CONFIG_CHANNEL` —— 配了 `MATECLAW_BROWSER_CHANNEL=chrome|msedge` → 走 Playwright channel +4. `AUTO_CHANNEL` —— 自动试 `chrome` / `msedge` channel(Docker 镜像里这一步必然命中) +5. `AUTO_PATH` —— 扫标准安装路径(`/usr/bin/google-chrome` / `chromium-browser` / `snap/bin/chromium` / `microsoft-edge` / `brave-browser`) +6. `BUNDLED` —— Playwright bundled chromium(镜像里这一步也一定通) +7. `EXTERNAL_CDP` —— 最后兜底,自己 `fork` 系统 chrome 带 `--remote-debugging-port=0`,读 stderr 抠 DevTools URL,`connectOverCDP` 接回来(openfang 的套路) + +Docker 部署下 **默认走第 4 或第 6 级**,零配置可用。如果你的用例要接外部 Chrome,配第 1 级;要用宿主机装的某个特殊 Chrome,配第 2 级。 + +### `/dev/shm` 必须 2 GB + +`docker-compose.yml` 给 `mateclaw-server` 设了 `shm_size: 2gb`。Docker 默认给每个容器只 64 MB `/dev/shm`,Chromium 用共享内存做 GPU / 页面渲染,跑 3 个 tab 就会 SIGBUS 挂掉,表现为 Playwright `TargetClosedError: Target page, context or browser has been closed`。**不要改小这个值**。 + +### SSRF 防护 + +BrowserUseTool 在 `navigate` 前会过 `UrlSafetyChecker`,**硬阻断**以下 host: + +- `localhost` / `127.0.0.1` / `::1` / `0.0.0.0` +- 169.254.169.254(AWS / GCP / Azure IMDS)、100.100.100.200(阿里云 IMDS)、192.0.0.192(Azure IMDS alt) +- 所有 link-local / private / multicast IP 段 + +也就是说 LLM 生成一个恶意 URL 指向云元数据端点偷凭据这条路是封死的。如果你有内网抓取需求需要放行特定地址,关 `mateclaw.browser.ssrf-check-enabled` 或改 `UrlSafetyChecker` 的白名单。**生产环境谨慎**。 + +### 验证浏览器通路 + +```sh +# 1. 启动自检(不实际启浏览器,查环境齐不齐) +curl -s http://localhost:18080/api/v1/system/browser-health | jq . +# 期望:overall: "healthy",system.browsers 找到 chromium 路径 + +# 2. 在 agent 里让它调浏览器 +# browser_use(action="diagnose") # 返回策略链 trace +# browser_use(action="start") # 实际启动 +# browser_use(action="open", url="https://example.com") +# browser_use(action="screenshot") # 返回 base64 PNG +``` + +--- + +## 第一次部署 + +```sh +git clone https://github.com/matevip/mateclaw.git +cd mateclaw + +# 1. 必填项写到 .env +cp .env.example .env +vi .env # 见下方必填表 +``` + +**必填**(compose 启动会强制校验,缺项直接退出避免把默认值带进生产): + +| 变量 | 说明 | +|---|---| +| `DB_PASSWORD` | 业务库账号密码,建议 16+ 位 + 大小写 + 数字 + 符号 | +| `DB_ROOT_PASSWORD` | MySQL root 密码,**与上面不同** | + +**强烈建议**(不填不会报错,启动日志里 WARN): + +| 变量 | 说明 | +|---|---| +| `JWT_SECRET` | JWT 签名密钥,`openssl rand -base64 48` 生成 | +| `MATECLAW_CORS_ALLOWED_ORIGINS` | 生产白名单,如 `https://mateclaw.example.com` | + +然后起服务: + +```sh +docker compose up -d --build # 首次构建,约 3-10 分钟 +docker compose logs -f mateclaw-server +``` + +首次启动会跑 Flyway 迁移(~5 秒)+ 应用内种子数据(~3 秒),然后绑 `0.0.0.0:18080`。 + +浏览器打开 `http://localhost:18080`,`admin / admin123` 登录,**立刻在「设置 → 安全」改密码**。 + +--- + +## 构建加速 + +### 美国 / 欧洲服务器 + +**默认就是最快的**:`mateclaw-server/pom.xml` 里 `` 的优先级是 `Maven Central → Google CDN → Aliyun`,Central 直连最快。 + +### 中国服务器 + +切 Aliyun 优先:改 `mateclaw-server/Dockerfile` 的 `mvn` 命令加 `-Paliyun-first`,或者(更简单)在 `docker-compose.yml` 加一行 `build args` 传进去。 + +```dockerfile +# 原 +RUN mvn dependency:go-offline -q +RUN mvn package -DskipTests -q + +# 改 +ARG MAVEN_PROFILE= +RUN mvn dependency:go-offline -q ${MAVEN_PROFILE:+-P${MAVEN_PROFILE}} +RUN mvn package -DskipTests -q ${MAVEN_PROFILE:+-P${MAVEN_PROFILE}} +``` + +然后: + +```sh +docker compose build --build-arg MAVEN_PROFILE=aliyun-first mateclaw-server +``` + +Aliyun Spring 镜像和公共仓库会被推到最前,中国出口不用经美国骨干。 + +--- + +## 可选开关 + +全部支持在 `.env` 里通过环境变量 override,**不填即用容器内默认**: + +| 变量 | 默认 | 用途 | +|---|---|---| +| `SERPER_API_KEY` | — | Google 搜索 API(付费,质量高) | +| `SEARXNG_SECRET` | 内置开发 secret | 只在把 8088 端口暴露到公网时才填 | +| `SEARXNG_BASE_URL` | `http://searxng:8080` | 想接外部 SearXNG 实例时填 | +| `MATECLAW_BROWSER_CDP_URL` | — | 接外部 Chrome sidecar(CDP 端点) | +| `MATECLAW_BROWSER_CHROME_PATH` | — | 用宿主机 Chrome 覆盖镜像内置 | +| `MATECLAW_BROWSER_CHANNEL` | — | `chrome` / `msedge` 等,强制指定 Playwright channel | + +**LLM 的 API Key(DashScope / OpenAI / Anthropic / DeepSeek / Kimi / ...)不在 `.env` 里配** —— 启动后在 UI「设置 → 模型 → 添加供应商」里添加,支持热更新。容器**零 API Key 也能起来**,登录后到模型页配第一家供应商即可。 + +--- + +## 验证 + +起来之后按顺序跑: + +```sh +# 1. 三个容器都 healthy +docker compose ps + +# 2. 基础健康检查 +curl -s http://localhost:18080/api/v1/system/health | jq . + +# 3. 浏览器工具自检(Linux 上最容易挂的地方) +curl -s http://localhost:18080/api/v1/system/browser-health | jq . +# 期望 overall: "healthy" + +# 4. SearXNG 返回 JSON(不是 HTML 错误页) +curl -s 'http://localhost:8088/search?q=hello&format=json' | head -5 +``` + +任何一条不通再翻下一节。 + +--- + +## 常见坑 + +**构建阶段 `mvn dependency:go-offline` 卡死** +美国服务器拉 Aliyun 镜像慢。pom.xml 默认把 Maven Central 放最前,应该快。如果还是慢,网络不通——检查出站防火墙。 + +**`mateclaw-server` 启动前就 unhealthy** +`docker compose logs mateclaw-server` 看 Flyway 迁移是否成功。通常是 DB_PASSWORD 含特殊字符被 shell 吃了 —— 用双引号包住。 + +**浏览器工具报 "Target page closed" / SIGBUS** +`shm_size: 2gb` 没生效。`docker inspect mateclaw-server | grep ShmSize` 看实际值。老版本 Docker Engine 要升级到 24.0+。 + +**搜索返回 "搜索暂时不可用"** +SearXNG 容器没起来或 JSON 格式被镜像默认 settings 禁用。我们自己构建 `./docker/searxng/` 已经改好;如果用了旧的 volume 缓存要清:`docker compose down -v searxng && docker compose up -d searxng`。 + +**LLM 回答里出现乱码 / 豆腐块** +镜像已经装了 `fonts-noto-cjk` 和 `fonts-noto-color-emoji`,不是字体问题。检查前端浏览器 locale。 + +--- + +## 升级 + +```sh +git pull +docker compose build mateclaw-server # 只重建后端 +docker compose up -d mateclaw-server +``` + +MySQL 数据卷(`mysql_data`)不会动,Flyway 自动跑增量迁移 + 自愈 checksum 变化。**版本号写在 `mateclaw-server/pom.xml` 和 git tag**,生产环境建议钉 tag 而不是 `dev` 分支。 + +--- + +## 下一步 + +- [配置说明](./config) —— 所有环境变量和运行时开关 +- [Doctor 健康检查](./doctor) —— UI 里自带的启动体检 +- [安全与审批](./security) —— 生产部署前的加固清单 diff --git a/mateclaw-server/src/main/resources/docs/zh/doctor.md b/mateclaw-server/src/main/resources/docs/zh/doctor.md new file mode 100644 index 00000000..96ad4abd --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/doctor.md @@ -0,0 +1,233 @@ +# Doctor + +**Doctor 页面回答一个问题:这个东西现在是不是真的在正常工作?** + +MateClaw 有很多活动部件——后端、数据库、模型供应商、MCP 服务、IM 渠道、cron 任务、记忆整合、wiki 消化。出问题时,**症状**("我的 Agent 不响应")通常有一个**具体的原因**("DashScope API Key 昨天过期了")埋在离你能看到的地方好几层远的地方。Doctor 是一个单页,**一次性跑所有检查**,告诉你哪些是绿的、哪些是黄的、哪些是红的。 + +通过 `设置 → Doctor` 打开,或者直接跳 `/doctor`。 + +--- + +## 它检查什么 + +每一项检查独立运行,报告三种状态之一: + +- **✅ 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 +``` + +--- + +## 读结果 + +每个检查返回: + +```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" +} +``` + +UI 渲染: + +- 顶部的**分类 tab**——基础设施、模型、Agent、记忆、Wiki、渠道、MCP、Cron +- **状态计数器**——绿 / 黄 / 红 +- **检查列表**——名字、状态、消息、距上次检查的时间、"查看详情"展开、可选的"修复"按钮跳到相关设置页 +- **历史图**——(每个检查)最近 50 次运行的 sparkline,一眼看出抖动的检查 + +--- + +## 修复按钮 + +对可操作的检查,Doctor 行包含一个**修复**按钮,直接跳到相关的设置页面: + +- 模型供应商失败 → `设置 → 模型` +- 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" \ + -H "Authorization: Bearer " +``` + +--- + +## 在运维中使用 Doctor + +### 作为 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 检查 diff --git a/mateclaw-server/src/main/resources/docs/zh/faq.md b/mateclaw-server/src/main/resources/docs/zh/faq.md new file mode 100644 index 00000000..205c82aa --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/faq.md @@ -0,0 +1,418 @@ +# 常见问题(FAQ) + +常见问题 + 真答案。你的问题不在这里就看对应的功能页,或者去 [GitHub issue](https://github.com/matevip/mateclaw/issues) 开一个。 + +--- + +## 安装和搭建 + +### 需要什么 Java 版本? + +**Java 17 或更高。** MateClaw 用了 Java 17 引入的特性。用 `java -version` 验证。 + +用桌面端的话,**完全不需要装 Java**——安装器自带 JRE 21。 + +### 要一个云 API key 才能开始? + +不用。三条无 key 的路径: + +- **Ollama**——本地 GPU 推理;MateClaw 启动时在 `localhost:11434` 自动探测 +- **ChatGPT OAuth**——有 ChatGPT Plus 或 Pro 订阅的话走浏览器 OAuth 流程——**不需要 API Key** +- **OpenRouter 免费档**——200+ 免费模型,一个 key 就能访问 + +**启动 MateClaw 也不需要把任何 API Key 设成环境变量。** 所有供应商配置都在启动后通过 UI 的 `设置 → 模型` 来做。 + +### 怎么拿 DashScope API Key? + +1. 去[阿里云 DashScope 控制台](https://dashscope.console.aliyun.com/) +2. 注册或登录 +3. 创建一个 API Key +4. 在 MateClaw 里进 `设置 → 模型 → DashScope` 粘贴 + +### 后端起不来——18088 端口被占了 + +换端口: + +```bash +mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=19090" +``` + +**桌面端会动态挑一个空闲端口**,所以在那里看不到这个错误。 + +### 启动时 H2 数据库锁错误 + +```bash +rm -f data/mateclaw.mv.db.lock +``` + +或者清空数据目录重新开始: + +```bash +rm -rf data/ +``` + +--- + +## 认证 + +### 默认凭证是什么? + +用户名 `admin`,密码 `admin123`。**任何真实部署都要立刻改。** + +### 我的 JWT token 老过期 + +MateClaw 实现了**滑动窗口续签**——token 剩余 25% 时服务器在响应头 `X-New-Token` 里发一个新 token。前端自动处理。 + +手动调 API(curl、Postman)的话,读 `X-New-Token` header 用新值做后续请求。 + +### 怎么改 admin 密码? + +UI 里 `设置 → 安全` 最简单。或者直接改数据库(BCrypt 编码): + +```sql +UPDATE mate_user SET password = '$2a$10$...' WHERE username = 'admin'; +``` + +--- + +## 模型 + +### 怎么配置模型? + +**全部通过 UI。** `设置 → 模型 → 添加供应商`。选供应商、粘 API Key(或为 ChatGPT Plus OAuth、或 Ollama 跳过)、保存、测试。**模型配置 100% 通过 UI 管理**——没有 `spring.ai.*` YAML 需要改。 + +LLM API Key 不读环境变量——`DASHSCOPE_API_KEY` 之类的设置不会生效。容器零 Key 就能启动,登录后加供应商即可。 + +### 怎么在 MateClaw 里用 GPT-4? + +`设置 → 模型 → 添加供应商`。要么粘你的 OpenAI API Key,要么如果你有 ChatGPT Plus/Pro 就用 **OpenAI OAuth**——浏览器窗口弹出让你登录。保存后从模型选择器挑 `gpt-4o`(或任何模型)。 + +### Ollama 模型很慢 + +本地模型性能取决于硬件: + +- 内存小的机器用小模型(7B 而不是 14B) +- 确保 Ollama 有 GPU 访问(`ollama ps` 应该显示 GPU) +- 有条件调大 Ollama 内存上限 +- `qwen2.5:7b` 或 `qwen3:latest` 是好平衡 + +### 能同时用多个供应商吗? + +可以。配多个供应商,把不同的模型配置分给不同的 Agent。每个 Agent 用自己的模型——或者继承全局默认。 + +### 怎么给有些 Agent 挂便宜模型、给另一些挂推理模型? + +- **全局活跃模型**设成便宜通用的(`qwen-plus`、`gpt-4o-mini`) +- 按 Agent 覆盖:推理重的 Agent 单独绑 `o3` 或 `qwen-max` +- 聊天窗口里的分组模型选择器也能按会话切 + +--- + +## 工具和搜索 + +### 怎么切换搜索 provider? + +`设置 → 系统 → 搜索服务`。从 Serper、Tavily、DuckDuckGo、SearXNG 里挑。开启 **fallback**。立刻生效。 + +无 key 选项(DuckDuckGo、SearXNG)让你不需要 API Key 也能搜索。 + +### 怎么加一个自定义工具? + +写一个 Spring `@Component`: + +```java +@Component +public class MyCustomTool { + + @Tool(description = "获取天气信息") + public String getWeather(@ToolParam(description = "城市名") String city) { + return "晴,25°C"; + } +} +``` + +启动时自动注册。见 [工具系统](./tools)。 + +**工具做任何危险的事情时,给它加一条 Tool Guard 规则。** + +### WebSearchTool 返回空结果 + +在 `设置 → 系统 → 搜索服务` 里配一个搜索供应商。无 key 选项(DuckDuckGo、SearXNG)不需要 API Key。 + +### Tool Guard 一直在挡我的工具调用 + +**这是刻意设计的**——危险工具要审批。三种放宽方式: + +1. **给具体的命令模式加一条 allow 规则**(`设置 → 安全与审批 → Tool Guard 规则`)。例子:`ShellExecuteTool`,参数模式 `^(ls|cat|grep|find)\s` → `allow`。 +2. **把默认策略调成 `allow`**(`application.yml`): + ```yaml + mateclaw: + tool: + guard: + default-policy: allow # 生产不推荐 + ``` +3. **完全关掉 Tool Guard**(**只开发**): + ```yaml + mateclaw: + tool: + guard: + enabled: false + ``` + +**生产安全:** 保持 `default-policy: require_approval`,为你信任的具体模式加有针对性的 allow 规则。 + +### 怎么配 MCP 服务? + +UI 里用 `工具 → MCP 服务`。三种传输模式:stdio、streamable_http、sse。配置变更立刻生效。见 [MCP 协议](./mcp)。 + +--- + +## LLM Wiki + +### Wiki 和记忆有什么区别? + +**Wiki 是刻意的。记忆是被动的。** + +- **Wiki**——你扔文档进去,系统消化成结构化页面,Agent 读这些页面。**你建的**、**你编辑的**、**你审核的**。 +- **记忆**——作为对话副产品自动构建。Agent 提取看起来值得记住的东西,每夜整合模式。 + +**源材料可查询**用 Wiki(产品规格、设计文档、过去决策)。**累积的上下文**用记忆(偏好、在做什么)。 + +### Agent 有知识库为什么还在瞎猜? + +因为你没把 Agent 绑到 KB 上。`Agents → [某个 Agent] → 知识库`——在那里绑。**Agent 没显式绑定之前,wiki 工具不会被注入。** + +### 消化很慢 + +调 `application.yml` 里的 `mate.wiki.digestion-concurrency`。默认 2——LLM 额度允许就调到 4 或 8。 + +--- + +## 记忆 + +### 记忆不工作 + +1. **确认自动提取开着**——检查 `mate.memory.auto-summarize-enabled` +2. **确认对话达到阈值**——`min-messages-for-summarize`(默认 4)、`min-user-message-length`(默认 10) +3. **检查冷却**——同一个 Agent 在 `cooldown-minutes`(默认 5 分钟)内不能触发第二次 +4. **看日志**——`vip.mate.memory` 在 DEBUG 级别显示每一次尝试 + +### 记忆整合任务没跑 + +整合由 `mate_cron_job` 里的种子数据驱动,每个 Agent 每天凌晨 2 点。检查: + +- `enabled` 列是 `1` 吗? +- 种子 cron 任务在吗?(`SELECT * FROM mate_cron_job WHERE task_type = 'memory_emergence'`) + +### 我不喜欢 Agent 记住的关于我的东西 + +直接在 Agent 工作空间视图里编辑 `PROFILE.md` 或 `MEMORY.md`。**锁定**你编辑过的页面。见 [记忆系统](./memory)。 + +--- + +## 审批 + +### 我批准了一个工具调用但 Agent 没恢复 + +1. `AWAITING_APPROVAL` 还是 true 吗?(`GET /api/v1/agents/{id}`) +2. 审批真的持久化了吗?(`GET /api/v1/approvals/{id}`) +3. Agent 日志里 replay 尝试附近有错误吗? +4. Replay 失败的话,Agent 应该在聊天里暴露一个错误 + +### 我想批量批准这个 Agent 未来的工具调用 + +你想要的是**一条 allow 规则**,不是一次性全批准。`设置 → 安全与审批 → Tool Guard 规则 → 添加规则`。 + +### Pending 审批能放多久? + +默认 10 分钟,之后过期变成 `rejected`。用 `mateclaw.tool.guard.approval-timeout-seconds` 配置。 + +--- + +## Agent + +### Agent 卡在 RUNNING 状态 + +常见原因: + +1. **工具调用超时**——某个工具在等挂住的外部服务 +2. **超过迭代上限**——`MAX_ITERATIONS_REACHED` 处理器强制给尽力而为的答案 +3. **等审批**——Tool Guard 暂停了执行 +4. **看日志**: + ```bash + mvn spring-boot:run -Dspring-boot.run.arguments="--logging.level.vip.mate.agent=DEBUG" + ``` + +### 怎么判断我的 Agent 在用对的工具? + +展开聊天界面的**思考面板**。每次工具调用、参数、结果都看得见。Agent 在调错工具的话,**收紧 system prompt** 引导它。 + +--- + +## 渠道 + +### 钉钉 / 飞书 webhook 收不到消息 + +1. 服务器不是公网可达 +2. 大部分平台要求 HTTPS +3. 验证 token 错了 +4. Bot 没被加到群里或没有消息权限 + +**更简单:** 用 **stream / 长连接 / WebSocket 模式**而不是 webhook。钉钉 Stream、飞书 WebSocket、Telegram Long-Polling、Discord Gateway、Slack Socket mode——**都不需要公网 IP**。 + +### 能同时用多个渠道吗? + +可以。每个渠道独立、绑一个 Agent。可以同时跑 web 控制台、钉钉、Telegram,绑不同的 Agent(或同一个——你说了算)。 + +### Telegram / Discord 访问不到 API(国内网络) + +在渠道配置里配 `http_proxy`: + +```json +{ + "bot_token": "...", + "http_proxy": "http://127.0.0.1:7890" +} +``` + +--- + +## 数据备份 + +### 怎么备份数据? + +**H2(开发 / 桌面):** 停服务,拷贝 `./data/mateclaw.mv.db`: + +```bash +cp ./data/mateclaw.mv.db ./backup/mateclaw-$(date +%Y%m%d).mv.db +``` + +**MySQL(生产):** + +```bash +mysqldump -u root -p mateclaw > mateclaw-backup-$(date +%Y%m%d).sql +``` + +**Docker:** + +```bash +docker exec mateclaw-mysql mysqldump -u root -p${MYSQL_ROOT_PASSWORD} mateclaw > backup.sql +``` + +**桌面端**数据在每个用户目录下: + +- macOS:`~/Library/Application Support/MateClaw/` +- Windows:`%APPDATA%/MateClaw/` +- Linux:`~/.local/share/MateClaw/` + +--- + +## 桌面应用 + +### 桌面 app 启动不了 + +安装器自带 JRE 21。看日志: + +- macOS:`~/Library/Logs/MateClaw/` +- Windows:`%APPDATA%/MateClaw/logs/` +- Linux:`~/.local/share/MateClaw/logs/` + +从终端启动。Windows 右键 → 解除锁定。macOS "系统设置 → 隐私与安全性"允许未签名应用。 + +### 怎么更新桌面 app? + +**自动更新**通过 electron-updater。启动时检查 GitHub Releases 并弹提示。也可以手动从 [Releases](https://github.com/matevip/mateclaw/releases) 下载。 + +--- + +## Docker + +### Docker 容器起不来 + +```bash +docker compose logs mateclaw-server +docker compose logs mateclaw-mysql +``` + +常见: + +- MySQL 还没就绪 +- 端口冲突(18080、3306) +- 缺 `.env`——从 `.env.example` 拷一份 + +### 怎么在 Docker 里访问数据库? + +```bash +docker exec -it mateclaw-mysql mysql -u root -p mateclaw +``` + +--- + +## 调试 + +### 怎么开 DEBUG 日志? + +```yaml +logging: + level: + vip.mate: DEBUG + vip.mate.agent: DEBUG + vip.mate.agent.graph: DEBUG + org.springframework.ai: DEBUG +``` + +或: + +```bash +mvn spring-boot:run -Dspring-boot.run.arguments="--logging.level.vip.mate=DEBUG" +``` + +### 怎么访问 H2 console? + +1. 访问 `http://localhost:18088/h2-console` +2. JDBC URL:`jdbc:h2:file:./data/mateclaw` +3. 用户名:`sa` +4. 密码:(空) + +**生产环境关掉它。** + +### 怎么观察 SSE 流式事件? + +浏览器 DevTools → Network → 筛选 `EventStream`。或: + +```bash +curl -N -H "Authorization: Bearer " \ + "http://localhost:18088/api/v1/chat/1/stream?conversationId=1" +``` + +--- + +## 前端 + +### 构建后前端显示空白页 + +```bash +cd mateclaw-ui +pnpm build +ls ../mateclaw-server/src/main/resources/static/ +# 应该包含 index.html 和资源文件 +``` + +### 深色模式不持久化 + +存在 `localStorage`。清了浏览器数据会丢。 + +### UI 感觉卡顿 + +- 把日志调回 INFO +- 检查 `java -Xmx` 设置 +- 在老会话上点**清空消息** + +--- + +## 下一步 + +- [快速开始](./quickstart)——搭建 walkthrough +- [配置说明](./config)——完整配置参考 +- [贡献指南](./contributing)——怎么报 bug 和提功能请求 +- [GitHub Issues](https://github.com/matevip/mateclaw/issues)——文档没答案的时候去这里 diff --git a/mateclaw-server/src/main/resources/docs/zh/index.md b/mateclaw-server/src/main/resources/docs/zh/index.md new file mode 100644 index 00000000..6e4081e5 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/index.md @@ -0,0 +1,44 @@ +--- +layout: home + +hero: + name: MateClaw + text: 公司允许部署的那一个 AI。 + tagline: 别的 AI 助手是给一个人用的。MateClaw 是给一个团队用的——多用户工作空间、敏感操作走审批、完整审计日志、生产级健康监控。一个 JAR 包跑在自己机器上,数据不出门。 + image: + src: /logo.png + alt: MateClaw + actions: + - theme: brand + text: 开始使用 → + link: /zh/quickstart + - theme: alt + text: 阅读文档 + link: /zh/intro + - theme: alt + text: GitHub + link: https://github.com/matevip/mateclaw + +features: + - icon: 🧑‍💼 + title: 数字员工,不是聊天机器人 + details: 你雇佣同事,不是开聊天框。每位有角色 / 目标 / 背景故事、像素艺术头像、专属配色——5 个职业模板开箱可用。ReAct + Plan-and-Execute 双模式,员工之间并行委派。 + - icon: 🧩 + title: 技能是骨架,不是插件 + details: 一份 SKILL.md + 一份 LESSONS.md(用得越多越聪明)。8 个起步模板,向导 5 步出包,安装前自动 Pre-flight 检查。MCP / ACP 双桥接,连 Claude Code、Codex 都能进来当员工。 + - icon: 📚 + title: 被塑形的知识 + details: LLM Wiki 把原始文件消化成结构化、带双向链接和摘要的知识页面。是一本你能翻的书,不是一个你只能查的向量库。热点缓存自动注入到员工的 system prompt。 + - icon: 🧬 + title: 会越积越多的记忆 + details: 会话上下文、对话后的结构化提取、工作空间记忆文件、定时 Dreaming 整合。明天的对话从今天停下的地方继续。 + - icon: 👀 + title: 你看得见每位员工在干什么 + details: Admin 运行时控制台 — 谁在跑、跑到哪一步、占多少 token、卡住了一键回收。流式分阶段显示,多员工协作不打架,长任务必须有真实证据才回答。 + - icon: 🔀 + title: 业务流程,不再是手工接力 + details: 工作流把多位员工 + 系统动作(审批 / 渠道分发 / 写记忆)按 7 种 step mode 编排成一条可发布、可触发、可重放的业务流;触发器把"系统里发生的事"自动启动这条流——6 种 pattern 覆盖 cron、webhook、渠道消息、员工生命周期、内容匹配、工作流完成。 + - icon: 🌐 + title: 工作发生的每一个真实场所 + details: Web 控制台、内置 JRE 21 的桌面端,以及 8 个聊天渠道。同一个大脑、同一份记忆,跟着团队走到哪里就到哪里。 +--- diff --git a/mateclaw-server/src/main/resources/docs/zh/intro.md b/mateclaw-server/src/main/resources/docs/zh/intro.md new file mode 100644 index 00000000..c6391f02 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/intro.md @@ -0,0 +1,90 @@ +--- +title: MateClaw 项目介绍 — 自部署多智能体 AI 操作系统 +description: MateClaw 是基于 Spring AI Alibaba 的开源多智能体 AI 操作系统。ReAct + Plan-and-Execute 双引擎、LLM Wiki 知识库、四层记忆系统、MCP 工具协议、8 渠道统一接入。一个 JAR 包自部署,数据不出门。 +head: + - - meta + - name: keywords + content: MateClaw,多智能体,AI操作系统,自部署AI,Spring AI Alibaba,ReAct,Plan-and-Execute,MCP,LLM Wiki,记忆系统,Tool Guard,开源 +--- + +# MateClaw — 自部署多智能体 AI 操作系统 + +**你的多智能体 AI,跑在你自己的机器上,按你自己的规则。** + +MateClaw 是一整套可以自部署的 AI 操作系统。一个 JAR 包,一套登录,数据不出门。 + +**它和别的 AI 不一样的三件事——** + +**主动**:它在该出现的时候自己出现。每天早上 9 点把简报推送到你的飞书,竞品有大动作直接 ping 你的钉钉。**不在浏览器 tab 里等你。** → [主动型 AI](./ambient-ai) + +**会做梦**:你睡了它跑一次整合,把今天零散的对话整合成对你的理解,写进 `MEMORY.md`。第二天它从昨天结束的地方继续,**不是从零开始**。 → [记忆系统](./memory) + +**可审批**:Agent 想删文件、发邮件、写数据库——触发 Tool Guard 规则就**在回合中途暂停**,审批请求推到你的 IM,你点批准 Agent 才接着跑。**会动手,但不擅自动手。** → [安全与审批](./security) + +它同时活在你的桌面、浏览器,以及团队每天在用的聊天软件里——同一个大脑,同一份记忆,跟着团队走到哪里就到哪里。 + +模型自己选。DashScope、OpenAI、Anthropic、Gemini、DeepSeek、Kimi、MiniMax、智谱、OpenRouter。本地跑就上 Ollama,手头有 ChatGPT Plus 账号就 OAuth 登进去直接用。先配一个,后面随时加。 + +--- + +## 它在对抗什么 + +市面上大多数 AI 产品只做一层。 + +给你一个聊天框,但明天打开就又从零开始;给你一个工具执行器,但不给你按"暂停"的机会;给你一个知识库,只会检索碎片却说不清它到底知道什么;给你桌面端,却进不了团队在用的聊天软件。或者——所有东西都给了,但全都跑在别人家的云上,你的数据顺便给别人付房租。 + +MateClaw 换了一个打法:**所有东西放在一个屋檐下,跑在你自己能摸到的硬件上。** + +--- + +## 它实际在做什么 + +**它会把活儿干完。** Plan-and-Execute 会把复杂任务拆成有序的步骤,一步一步执行,中途哪一步炸了就重新调整。ReAct 管更小的循环——思考、行动、观察、继续。你会看到计划在滚,看到工具被调,看到思考过程,看到它最终收尾。 + +**它会记住。** 会话上下文、对话后的结构化提取、工作空间的记忆文件、定时整合,再加一轮"dreaming"——把昨天的线索串起来。记忆不是聊天功能上贴的一张贴纸,是系统越用越懂你的底层机制。 + +**它会把知识嚼碎。** 扔一份 PDF。扔一整个文件夹。扔一千篇 Markdown 笔记。LLM Wiki 会把它们消化成结构化的、带双向链接和摘要的知识页面——不是一个向量库,是一本你能翻的书。Agent 自动注入页面摘要,需要细节时再去取整页。 + +**它手上有真工具。** 内置工具:搜索、文件读写、shell、时间、图像、音乐、视频、语音识别、语音合成。任何别的东西都能接 MCP 服务。你自己的技能包只要写个 `SKILL.md` 就能装进工作空间。所有工具都过一层 Tool Guard,必要时还能走人工审批——手能伸得远,但边界清清楚楚。 + +**它会在每一个真实的工作面上出现。** Web 控制台、桌面端(内置 JRE 21,用户不需要装 Java),还有八个聊天渠道:钉钉、飞书、企业微信、微信、Telegram、Discord、QQ、Slack。Slack 里回复的 Agent 和浏览器里的 Agent 是同一个——同一份记忆、同一套技能、同一种性格。 + +--- + +## 为什么"自部署"这件事很重要 + +把 MateClaw 跑在你自己的机器上,不是合规打个勾那么简单。它改变的是这个产品**到底是什么**。 + +**你的数据不再给别人付房租。** 对话、日志、文档、记忆——没有一条拿去训练别人的模型,没有一条在别人家的队列里排队,没有一条离开你的机器,除非是你自己把某个渠道接了出去。 + +**路线图是你的。** 记忆整合的规则你不喜欢?自己改。需要一个厂商不给你做的工具?自己加。Apache 2.0,不是 "source available",不是 "open core",不用等别人的季度产品评审。 + +**账单是你自己算的。** 一开始上 DashScope,等本地 GPU 到了就切 Ollama,某个高价值 Agent 单独挂 OpenAI,其他的走便宜的。Agent 配置和工具图不关心底下的模型接口是什么。 + +**部署面是实打实的。** 一个 JAR 包。一个 Spring Boot 进程。不用装 Python,不用装 Node。桌面端自己带环境,Docker Compose 一共 18 行。 + +--- + +## 底下是什么 + +- **后端**——Spring Boot 3.5 + Spring AI Alibaba 1.1。Agent 运行时是一张 StateGraph,reasoning、action、observation、plan generation、step execution 都是图上的节点。MyBatis Plus 持久化。流式走 SSE(WebFlux 被明确拒之门外)。 +- **前端**——Vue 3 + TypeScript。Pinia 管状态,Element Plus + Tailwind 做 UI,支持深色模式。前端 build 的产物直接进后端 JAR 的 `static/`,一个进程服务两端。 +- **桌面端**——Electron 包 JRE 21 + 后端 JAR。双击启动,用户完全不需要知道底下跑的是 Java。 +- **渠道**——每个渠道是一个 `ChannelAdapter` SPI 实现。Web 走 SSE,IM 各自走平台的长连接或 webhook。 +- **存储**——开发用 H2 文件数据库,生产用 MySQL 8。Flyway 管理 schema 迁移,每种方言各有一套脚本。 + +--- + +## 三条进入方式 + +大概率你想做三件事之一。 + +**想用起来?** → [快速开始](./quickstart),桌面端 60 秒到第一条消息。 + +**想搞清楚它?** → 按这个顺序读:[Agent 引擎](./agents) → [LLM Wiki](./wiki) → [记忆系统](./memory) → [多模态创作](./multimodal)。这四页就是产品本身。 + +**想在它上面建东西?** → [API 参考](./api) 和 [贡献指南](./contributing)。 + +--- + +这页上写的所有东西都可以质疑。如果哪里读不通,是文档的问题,不是你的——去 GitHub 告诉我们。 diff --git a/mateclaw-server/src/main/resources/docs/zh/mcp.md b/mateclaw-server/src/main/resources/docs/zh/mcp.md new file mode 100644 index 00000000..9274cb24 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/mcp.md @@ -0,0 +1,429 @@ +--- +title: MCP 协议集成 — Model Context Protocol 工具扩展 +description: MateClaw 作为 MCP 客户端,通过 Model Context Protocol 接入任意外部工具服务器。JSON-RPC 动态发现、SSE/stdio 双传输、与内置工具无缝统一。 +head: + - - meta + - name: keywords + content: MCP,Model Context Protocol,MCP客户端,工具协议,JSON-RPC,AI工具扩展,Anthropic MCP +--- + +# MCP 协议 + +**MCP 是 MateClaw 跟"别人写的工具"对话的方式。** + +Model Context Protocol 是 Anthropic 提出的一个开放标准,用来把 AI 模型和外部工具/数据连起来。一个 MCP 服务是一个进程——本地或远程——通过 JSON-RPC 向外宣告一组工具。MateClaw 扮演 MCP **客户端**:连接上去、通过 `tools/list` 发现工具、把它们当原生工具暴露给你的 Agent。**从 Agent 的视角,一个内置的 `@Tool` Spring bean 和一个从 MCP 服务来的工具,没有任何区别。** + +这是 MateClaw 的**逃生口**。你需要一个 MateClaw 没自带的能力——沙盒目录的文件访问、Tavily 搜索、某个自定义的企业数据服务、一整套浏览器自动化——大概率已经有现成的 MCP 服务,你可以把它接进来,不用写一行 Java。 + +--- + +## MCP 到底是什么 + +``` +┌───────────────────────┐ ┌───────────────────────┐ +│ MateClaw │ │ MCP Server │ +│ (MCP Client) │ │ (工具提供方) │ +│ │ JSON-RPC │ │ +│ Agent Engine ───────┼──────────────┼──► Tool A │ +│ │ │ Tool B │ +│ Tool Registry ◄──────┼──────────────┼─── Tool Discovery │ +│ │ │ (tools/list) │ +└───────────────────────┘ └───────────────────────┘ +``` + +核心概念: + +- **MCP 客户端**——MateClaw,负责连接 MCP 服务、发现工具、转发工具调用 +- **MCP 服务**——第三方工具服务器 +- **工具发现**——客户端发送 `tools/list` 请求拿到服务器上所有工具 +- **工具调用**——Agent 决定调一个工具时,客户端转发给对应的 MCP 服务执行 + +新工具能力变成 Agent 可用的——**不改代码、不重启服务**。 + +--- + +## 三种传输类型 + +### stdio(标准 I/O) + +MateClaw 启动一个本地子进程,通过 stdin/stdout 交换 JSON-RPC 消息。 + +``` +MateClaw ── stdin ──► MCP Server 子进程 + ◄─ stdout ── +``` + +**适用:** 本地 Node.js/Python MCP 工具包、命令行工具封装、开发调试。 +**优势:** 不需要网络配置,开箱即用,进程隔离。 +**限制:** 仅本地。 + +### streamable_http(可流式 HTTP) + +标准 HTTP POST 发 JSON-RPC,响应通过 HTTP 流返回。**生产环境推荐。** + +``` +MateClaw ── HTTP POST ──► 远程 MCP 服务 + ◄─ HTTP Stream ── +``` + +**适用:** 云部署的 MCP 服务、前面有负载均衡的场景。 +**优势:** 标准 HTTP,CDN/防火墙友好,支持认证头。 + +### sse(Server-Sent Events) + +早期 HTTP 传输模式,用 SSE 做服务端到客户端推送。遗留兼容,新项目优先选 `streamable_http`。 + +### 传输对比 + +| 特性 | stdio | streamable_http | sse | +|------|-------|-----------------|-----| +| 部署 | 仅本地 | 本地或远程 | 本地或远程 | +| 网络要求 | 无 | HTTP 可达 | HTTP 可达 | +| 认证 | 环境变量 | HTTP Headers | HTTP Headers | +| 进程管理 | MateClaw 管理子进程 | 外部 | 外部 | +| 推荐 | 本地工具 | 远程服务 | 遗留兼容 | + +--- + +## UI 配置 + +`工具 → MCP 服务 → 添加 MCP 服务`。填: + +- **名称**——唯一标识符(字母、数字、`_`、`-`、`.`、空格;1–128 字符) +- **描述**——可选 +- **传输类型**——`stdio`、`streamable_http`、`sse` +- **命令**(stdio)——`npx`、`node`、`python` 等 +- **参数**(stdio)——JSON 数组(例如 `["-y", "@anthropic/mcp-filesystem", "/path"]`) +- **工作目录**(stdio)——可选 +- **环境变量**(stdio)——JSON 对象;支持 `${ENV_VAR}` 引用 +- **URL**(streamable_http / sse)——服务端点 +- **HTTP Headers**(streamable_http / sse)——JSON 对象 +- **连接超时**——默认 30 秒 +- **读取超时**——默认 30 秒 + +保存。启用状态时 MateClaw 自动尝试连接并发现工具。 + +### 测试、启用、状态 + +- **测试连接**——发送 `tools/list`,返回结果、延迟、工具列表 +- **启用/禁用开关**——断开连接但保留配置 +- **状态**——`connected` / `disconnected` / `error` 带错误详情 + +--- + +## REST API 配置 + +完整 CRUD 在 `/api/v1/mcp/servers`。 + +### 列表 + +```bash +curl -s http://localhost:18088/api/v1/mcp/servers \ + -H "Authorization: Bearer " | jq +``` + +响应里的 `headersJson` 和 `envJson` 字段自动**脱敏**(`sk-****abcd`)。 + +### 创建 —— stdio + +```bash +curl -X POST http://localhost:18088/api/v1/mcp/servers \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "filesystem", + "transport": "stdio", + "command": "npx", + "argsJson": "[\"-y\", \"@anthropic/mcp-filesystem\", \"/home/user/workspace\"]", + "enabled": true + }' +``` + +### 创建 —— streamable_http + +```bash +curl -X POST http://localhost:18088/api/v1/mcp/servers \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "remote-tools", + "transport": "streamable_http", + "url": "https://mcp.example.com/mcp", + "headersJson": "{\"Authorization\": \"Bearer your-api-key\"}", + "connectTimeoutSeconds": 15, + "readTimeoutSeconds": 60, + "enabled": true + }' +``` + +### 更新(PATCH 语义) + +```bash +curl -X PUT http://localhost:18088/api/v1/mcp/servers/{id} \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"description": "更新后的描述", "readTimeoutSeconds": 60}' +``` + +### 删除 / 开关 / 测试 / 刷新 + +```bash +curl -X DELETE http://localhost:18088/api/v1/mcp/servers/{id} \ + -H "Authorization: Bearer " + +curl -X PUT "http://localhost:18088/api/v1/mcp/servers/{id}/toggle?enabled=false" \ + -H "Authorization: Bearer " + +curl -X POST http://localhost:18088/api/v1/mcp/servers/{id}/test \ + -H "Authorization: Bearer " + +curl -X POST http://localhost:18088/api/v1/mcp/servers/refresh \ + -H "Authorization: Bearer " +``` + +**内置服务**(`builtin=true`)**不能删除**。 + +--- + +## 实战示例 + +### 示例 1 —— 文件系统 MCP(stdio) + +```bash +curl -X POST http://localhost:18088/api/v1/mcp/servers \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "filesystem", + "description": "文件系统读写(限制在指定目录内)", + "transport": "stdio", + "command": "npx", + "argsJson": "[\"-y\", \"@anthropic/mcp-filesystem\", \"/home/user/workspace\"]", + "enabled": true + }' +``` + +发现的工具:`read_file`、`write_file`、`list_directory`、`search_files`、`get_file_info`。 + +安全:`@anthropic/mcp-filesystem` **只允许访问启动参数里指定的目录及其子目录**。 + +### 示例 2 —— 带认证的远程 HTTP + +```bash +curl -X POST http://localhost:18088/api/v1/mcp/servers \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "internal-data-service", + "transport": "streamable_http", + "url": "https://mcp-api.internal.example.com/mcp", + "headersJson": "{\"Authorization\": \"Bearer sk-your-api-key\", \"X-Team-Id\": \"engineering\"}", + "connectTimeoutSeconds": 10, + "readTimeoutSeconds": 120, + "enabled": true + }' +``` + +**Header 值支持环境变量引用**:`{"Authorization": "Bearer ${MCP_API_KEY}"}` 在运行时被替换,**secret 不落库**。 + +### 示例 3 —— Tavily 搜索(stdio + 环境变量) + +```bash +curl -X POST http://localhost:18088/api/v1/mcp/servers \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{ + "name": "tavily-search", + "transport": "stdio", + "command": "npx", + "argsJson": "[\"-y\", \"@anthropic/mcp-tavily\"]", + "envJson": "{\"TAVILY_API_KEY\": \"${TAVILY_API_KEY}\"}", + "enabled": true + }' +``` + +--- + +## MCP 工具是怎么变成 Agent 可用的 + +``` +应用启动 + │ + ▼ +遍历启用的 MCP 服务 + │ + ▼ +按传输类型连接 → 调用 initialize → 列出工具 → 缓存 + │ + ▼ +工具注册表(聚合内置工具 + MCP 工具) + │ + ▼ +Agent 工具集 +``` + +**关键:** Agent 每次需要调工具时都拉**最新**的活跃工具列表,所以添加或删除 MCP 服务**不需要重启**。从 Agent 的视角看,**MCP 工具和内置工具完全一样**,没有差别。 + +--- + +## Per-agent 工具绑定 + +::: tip 1.3.0 新增 +v1.2.0 之前所有员工默认能用全部 MCP 工具——这是个全局开关。v1.3.0 把粒度拆细到**每个员工独立绑定哪些 MCP 工具**,并加了脏状态识别 + 命名空间防撞。 +::: + +### 三个解决的问题 + +**问题 1:工具命名空间冲突** +两个 MCP server 都暴露 `read_file`——agent 调用时哪个赢?v1.3.0 在内部使用**带 server 前缀的稳定 callback name**(`{serverName}__{toolName}`),并把它持久化到 `mate_mcp_server.cached_tools`。两个 read_file 在 picker 里显示为 `serverA__read_file` 和 `serverB__read_file`,agent 看到的 prompt 里映射回原始名以减少 token + 不让 LLM 困惑。 + +**问题 2:MCP server 改名 / 工具改名 → 员工绑定全部失效** +v1.2.0 时 server 一改名,绑这个 server 的员工全瞎了。v1.3.0 引入**持久化 tool cache**:每次成功 list-tools 后把工具元数据写到 `mate_mcp_server.cached_tools` JSON 列。agent binding 校验时如果 server 暂时连不上,就走 cache fallback——绑定保留为 `stale`,连接恢复后立即可用。 + +**问题 3:员工保存时静默接受不存在的工具引用** +v1.2.0 时员工配置里写了一个 `nonexistent-server.weird-tool`,保存成功,运行时报错。v1.3.0 在保存时跑 `AgentBindingService.validate(...)`: + +| 状态 | 含义 | 保存行为 | +|---|---|---| +| `connected` | server 在线,工具可见 | ✅ 正常保留 | +| `stale` | server 暂时离线但 cache 里有 | ✅ 保留(标记 stale) | +| `unavailable` | server 被禁用 | ✅ 保留(标记 unavailable) | +| `orphan` | server / tool 完全不存在了 | ❌ 拒绝保存,提示用户清理 | + +### 工具状态在哪里看 + +`Agents → 选员工 → 工具`——见 [数字员工的工具绑定](./agents#工具绑定per-agent-tool-picker)。 + +### 数据契约 + +- `mate_mcp_server.cached_tools`(v1.3.0 新列):JSON 数组,每个元素 `{name, description, inputSchema, lastSeenAt}` +- `mate_agent_tool.tool_name`:存的是**带前缀的 callback name** `{serverName}__{toolName}` 而不是原始名,这样 server 改名时 join 失败立刻可观测 +- `AgentBindingService.getEffectiveToolNames(agentId)` 是工具下发的唯一入口——agent 每个回合都跑一遍,确保运行时和编辑期看到的工具集一致 + +### 服务端规则 + +- MCP server 列表里**不可以编辑** ACP 桥接进来的 MCP 工具(它们是 ACP server 自己生命周期管的) +- 工具被 mark unavailable 后,agent system prompt 里**不再列出它**——LLM 不会想到调用它,但绑定数据保留 +- `returnDirect=true` 的工具(直接把工具输出当回答)走同一套 ACL,**不绕过** binding + +--- + +## 连接管理 + +### 启动时自动连接 + +所有 `enabled=true` 的 MCP 服务在应用启动时自动连接。单个服务失败不会阻塞其他或启动。 + +### 线程安全 + +活跃的 client 表是并发安全的,每个服务有独立的锁。 + +### 连接替换 + +**"先连新的,再断旧的"** 策略:建一个新 client、初始化、放进池、关闭旧的。新 client 失败时旧的保持不变。 + +### 子进程清理 + +stdio 服务:禁用/删除、配置替换、应用关闭(`@PreDestroy`)、连接失败时都会清理。 + +### 状态监控 + +每次连接操作后持久化: + +- `last_status`——`connected` / `disconnected` / `error` +- `last_error`——错误消息 +- `last_connected_time`——上次成功连接时间 +- `tool_count`——当前发现的工具数 + +### 手动刷新 + +`POST /api/v1/mcp/servers/refresh` 断开所有现有连接并重连所有启用的服务。用于排查连接问题。 + +--- + +## 数据库存储 —— `mate_mcp_server` + +| 列 | 类型 | 默认值 | 用途 | +|----|------|--------|------| +| `id` | BIGINT | — | 主键 | +| `name` | VARCHAR(128) | — | 唯一标识符 | +| `description` | TEXT | NULL | 服务描述 | +| `transport` | VARCHAR(32) | `stdio` | `stdio` / `streamable_http` / `sse` | +| `url` | VARCHAR(512) | NULL | 远程 URL | +| `headers_json` | TEXT | NULL | HTTP headers JSON | +| `command` | VARCHAR(512) | NULL | 启动命令 | +| `args_json` | TEXT | NULL | 命令参数 JSON 数组 | +| `env_json` | TEXT | NULL | 环境变量 JSON;支持 `${VAR}` | +| `cwd` | VARCHAR(512) | NULL | 工作目录 | +| `enabled` | BOOLEAN | TRUE | 开关 | +| `connect_timeout_seconds` | INT | 30 | HTTP 连接超时 | +| `read_timeout_seconds` | INT | 30 | 请求响应超时 | +| `last_status` | VARCHAR(32) | `disconnected` | 上次连接状态 | +| `last_error` | TEXT | NULL | 上次错误消息 | +| `last_connected_time` | DATETIME | NULL | 上次成功连接时间 | +| `tool_count` | INT | 0 | 发现的工具数 | +| `builtin` | BOOLEAN | FALSE | 是否内置 | +| `create_time` / `update_time` | DATETIME | — | 时间戳 | +| `deleted` | INT | 0 | 逻辑删除 | + +### 敏感数据脱敏 + +API 响应里 `headers_json` 和 `env_json` 的值自动**脱敏**。`args_json` 按原样返回。 + +### 环境变量引用 + +- `${VAR_NAME}`——精确匹配和替换 +- `$VAR_NAME`——正则匹配 + +**明文 secret 不进数据库。** + +--- + +## 故障排查 + +### "命令找不到"(stdio) + +1. 确认命令在运行 MateClaw 的用户的 PATH 里 +2. 验证:`which npx` 或 `npx --version` +3. Docker:确认命令在容器里装了 +4. 用完整路径:`/usr/local/bin/npx` + +### 连接超时 + +1. HTTP/SSE:确认 URL 可达(`curl -v `) +2. 检查防火墙规则 +3. 调大 `connectTimeoutSeconds` / `readTimeoutSeconds` +4. stdio:第一次 `npx -y` 可能要下包 + +### SSL/TLS 错误 + +1. 确认远程 SSL 证书有效 +2. 自签证书:把 CA 证书加进 JVM trust store +3. 确认 JDK 支持需要的 TLS 版本 + +### 工具没显示 + +1. 看 `tool_count > 0` +2. 用测试连接确认 `discoveredTools` 非空 +3. 确认 MCP 服务实现了 `tools/list` +4. 看后端日志里 MCP 工具发现相关的输出 + +### 工具调用失败 + +1. 看后端日志的具体错误 +2. 确认 MCP 服务进程还在跑(stdio) +3. 确认远程服务可达(HTTP/SSE) +4. 看 `readTimeoutSeconds` 够不够 +5. 试刷新连接 + +### 孤儿子进程(stdio) + +`@PreDestroy` 钩子正常会清理。MateClaw 被强杀(`kill -9`)的话子进程可能残留。`ps aux | grep mcp` 找到并杀掉。 + +--- + +## 下一步 + +- [工具系统](./tools)——MCP 工具和内置工具的关系 +- [技能系统](./skills)——MCP 支撑的技能 +- [配置说明](./config)——完整配置参考 diff --git a/mateclaw-server/src/main/resources/docs/zh/memory.md b/mateclaw-server/src/main/resources/docs/zh/memory.md new file mode 100644 index 00000000..10d18cff --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/memory.md @@ -0,0 +1,402 @@ +--- +title: AI 记忆系统 — 四层记忆生命周期(提取-整合-Dreaming-召回) +description: MateClaw 的四层记忆生命周期:即时上下文、对话后提取、工作空间持久化(PROFILE.md/MEMORY.md)、定时 Dreaming 整合。让 AI 越用越懂你,不再每天从零开始。 +head: + - - meta + - name: keywords + content: AI记忆,记忆系��,Dreaming,PROFILE.md,MEMORY.md,记忆生命周期,长期记忆,记忆提取,记忆整合 +--- + +# AI 记忆系统 + +**记忆是系统越用越懂你的机制。** + +MateClaw 里其他所有东西,在你配置完之后就静止了。Agent、工具、知识库——你改它们的时候才改。记忆是**唯一一个**会自己改变的部分,变化是实际使用过程的副产品。这就是整个设计的核心意图。 + +::: tip 它在你睡着的时候做了一个关于你的梦 +不是营销词。是 `memory/dreaming/` 包里真实跑的代码。 + +每天凌晨 2 点(默认时间,可改),系统跑一次调度任务,名字就叫 **Dreaming**:扫一遍今天和你聊天的每个 Agent 的对话痕迹,把零散的线索整合成对你的理解,过滤掉一次性的、矛盾的、过期的,把高频出现的提升进 `MEMORY.md`,整个"看见了什么、得出了什么、改写了什么"的过程追加进 `DREAMS.md`——一条人类可读的审计线。 + +第二天早上你打开它,它**从昨天结束的地方继续**,不是从零开始。 + +> 别的 AI 每天从零开始。MateClaw 从昨天结束的地方继续。 +::: + +这一页讲组成记忆的四个层、每个 Agent 的记忆文件、以及 Agent 自己怎么在对话中读写这些文件。 + +--- + +## 四个层 + +``` + ┌────────────────────────────────────────────────────────────┐ + │ 1. 当下这一回合 │ + │ 正在说的话、刚刚说过的话、按 token 预算自动裁剪 │ + │ 更新时机:每一回合 │ + └────────────────────────────────────────────────────────────┘ + │ + ▼(对话完成之后) + ┌────────────────────────────────────────────────────────────┐ + │ 2. 对话结束后的提取 │ + │ 从对话里挑出值得记住的事,写进 PROFILE.md / MEMORY.md │ + │ 和当天的日常笔记 │ + │ 更新时机:每次有意义的对话结束后异步跑 │ + └────────────────────────────────────────────────────────────┘ + │ + ▼(默认每天凌晨 2 点,可调) + ┌────────────────────────────────────────────────────────────┐ + │ 3. 夜里整合(Dreaming) │ + │ 扫一遍最近的日常笔记,找出反复出现的模式, │ + │ 合并进 MEMORY.md,把过程记到 DREAMS.md │ + │ 更新时机:定时触发,可手动 │ + └────────────────────────────────────────────────────────────┘ + │ + ▼(下一次对话直接用最新版本) + ┌────────────────────────────────────────────────────────────┐ + │ 4. 工作空间文件进入 system prompt │ + │ 四个 markdown 文件每一回合都被注入 │ + │ 更新时机:底下文件一变,下一回合就生效 │ + └────────────────────────────────────────────────────────────┘ +``` + +每一层跑在不同的时间尺度上。当下是**这一回合**。提取是**每一次对话之后**。整合是**每天夜里**。文件注入是**每一回合都用当前最新版本**。加在一起它们形成一个循环——你说的话变成上下文,上下文变成文件,文件变成 system prompt,system prompt 变成 Agent 明天知道的东西。 + +--- + +## 多层记忆 + 可插拔 Provider + +记忆这一层不是一个硬编码的实现。它是一个**接口**——多层架构允许你**堆叠 provider**: + +- **默认 Provider** 就是这页后面讲的基于工作空间文件的记忆。MateClaw 出厂就带这个,对大多数人来说这一个就够了。 +- **自定义 Provider** 可以插入用于专用检索——基于向量的长期记忆、图结构记忆、外部记忆服务。 +- **分层**意味着同一个 Agent 可以同时和多个 provider 对话。短期 provider 返回最近上下文;语义 provider 返回相关记忆;Wiki provider 返回权威引用。它们在读取时组合。 + +对大多数 Agent 来说,**默认的就够了**,这一节可以跳过。如果你在做某种专用的东西——需要记住上千条事实并用向量搜索、需要图结构记忆——这里就是插入点。开发细节看 [架构说明](./architecture)。 + +--- + +## 每个 Agent 都有的四个文件 + +每个 Agent 都有自己的工作空间。四个 markdown 文件是长期记忆的骨架: + +``` +workspace/{agentId}/ +├── AGENTS.md # Agent 怎么用记忆 —— 行为指南 +├── SOUL.md # Agent 是谁 —— 核心身份、人格、边界 +├── PROFILE.md # 你是谁 —— 用户画像、偏好、背景 +├── MEMORY.md # 什么重要 —— 关键决策、项目上下文、待办 +└── memory/ + ├── 2026-04-09.md # 日常笔记 —— 今天发生了什么,追加模式 + ├── 2026-04-10.md + └── 2026-04-11.md +``` + +前四个会在**每一回合注入到 system prompt**(只要 `enabled=true`)。每日笔记不会——它们喂给整合服务用。 + +### 每个文件是干什么的 + +- **AGENTS.md**——Agent 自己的使用说明书。什么时候该写记忆、每个地方放什么、有哪些工具可以操作记忆。种子:`enabled=true`,`sort_order=0`。 +- **SOUL.md**——Agent 从根上是谁。自我意识、演化指引、隐私与边界原则。想在深层修改 Agent 的性格时编辑它。种子:`enabled=true`,`sort_order=1`。 +- **PROFILE.md**——Agent 学到的关于你的东西。名字、职业、技术栈、沟通偏好。对话里出现值得保留的东西时记忆提取器会更新它。全覆盖写入。种子:`enabled=true`,`sort_order=2`。 +- **MEMORY.md**——Agent 认为重要到值得留下的东西。活跃项目、未决定的事、打开的线索、你让它记住的东西。提取器和整合器都会更新它。种子:`enabled=true`,`sort_order=3`。 + +::: tip 1.3.0 新增:工作流可以写记忆 +v1.3.0 起,[工作流](./workflow) 的 `write_memory` step 可以在流程跑完时把结果直接写进某位员工的 `MEMORY.md`(或任意启用的 memory 文件),支持 4 种合并策略:`append` / `replace_section` / `upsert_kv` / `overwrite`。这意味着记忆不再只能由对话提取或 Dreaming 写入——一条业务流程的产物也可以被沉淀。 +::: + +### 每日笔记 + +对话亮点按日期归档,**追加模式**——同一天里的多次对话全部累加到同一个文件。这些不会注入到 system prompt(`enabled=false`)。它们存在是为了让整合器凌晨两点跑的时候有东西可扫。 + +--- + +## 短期:上下文窗口 + +每一次 LLM 调用之前,MateClaw 都会构造真正送出去的那个 prompt: + +``` +[System Prompt] ← 永远在最前 +[工作空间文件注入] ← AGENTS / SOUL / PROFILE / MEMORY +[对话上下文摘要] ← 只有在早期轮次被压缩过时才有 +[Message 1: user] +[Message 2: assistant] +... +[当前用户消息] ← 永远在最后 +``` + +工作空间文件按 `sort_order` 排序拼进 system prompt,格式: + +``` +--- AGENTS.md --- +(内容) + +--- SOUL.md --- +(内容) + +--- PROFILE.md --- +(内容) + +--- MEMORY.md --- +(内容) +``` + +只注入 `enabled=true` 的文件。 + +### 上下文爆了怎么办 + +三层防御: + +**第一层:主动压缩。** 估算总 token 超过预算的 75%(默认窗口 12.8 万 token),系统让 LLM 总结早期轮次。最近 2 轮(4 条消息)保留原文。结果缓存 30 分钟。 + +**第二层:紧急恢复。** 如果 LLM 仍然返回上下文超限,系统不再调 LLM,直接丢掉更早的消息、保留最后 2 轮、重试一次。 + +**第三层:硬截断。** 总结之后还是超,从前往后继续丢消息直到 prompt 装得下。最近 2 条永远不动。 + +> **安全设计**——摘要以**用户消息**形式注入,**不是**系统消息。刻意的:防止早期用户输入的压缩版本被提升成系统级指令,关掉一条注入攻击路径。 + +### 配置 + +```yaml +mate: + agent: + conversation: + window: + default-max-input-tokens: 128000 + compact-trigger-ratio: 0.75 + preserve-recent-pairs: 2 + summary-max-tokens: 300 +``` + +--- + +## 对话后提取 + +一次对话结束之后,系统会异步地把值得记住的东西提取出来、写进 PROFILE.md、MEMORY.md、当天的日常笔记。发生在用户响应路径之外——**永远不会阻塞下一个回合**。 + +### 什么时候触发 + +一个回合完成之后,系统在后台线程处理这次对话。需要满足几个条件才会真的跑提取: + +- 自动总结开关打开 +- 不是定时任务自己触发的对话(防止递归) +- 消息数达到下限(默认 4 条) +- 最后一条用户消息够长(默认至少 10 字符) + +全部通过,开始提取。 + +### 并发控制 + +- **冷却**——同一个 Agent 在默认 5 分钟内不会重复提取 +- **按 Agent 加锁**——同一个 Agent 已经有一个提取任务在跑,新任务直接跳过 + +### LLM 实际在做什么 + +1. 从对话历史里加载消息 +2. 读当前的 PROFILE.md、MEMORY.md、今天的日常笔记 +3. 构造 transcript:最多 30 条消息,每条截断到 2000 字符 +4. 用记忆总结的 prompt 模板调 LLM +5. 解析 JSON 响应 +6. 执行写入 + +### LLM 响应 schema + +| 字段 | 类型 | 作用 | +|------|------|------| +| `should_update` | boolean | 记忆是否需要更新 | +| `reason` | string | 原因(用于审计) | +| `daily_entry` | string | 追加到今天日常笔记的内容 | +| `memory_update` | string | MEMORY.md 的全新全量内容 | +| `profile_update` | string | PROFILE.md 的全新全量内容 | + +### 文件写入规则 + +- **PROFILE.md**——全覆盖,只在 `profile_update` 非空时写 +- **MEMORY.md**——全覆盖,只在 `memory_update` 非空时写 +- **memory/YYYY-MM-DD.md**——追加,文件不存在时用日期标题新建 + +--- + +## 整合与 Dreaming + +第三层按计划跑。它的工作是看着日常笔记堆起来,周期性地问自己:*这里的模式是什么?哪些东西应该被提升进核心记忆?哪些东西过期了应该被遗忘?* + +### 它做什么 + +1. 列出 Agent 所有 `memory/*.md` 文件,取最近 7 天 +2. 读这些日常笔记 + 当前 MEMORY.md +3. 用整合 prompt 模板调 LLM +4. LLM 返回 `{should_update, reason, memory_content}` +5. 如果 `should_update` 为 true,MEMORY.md 被 `memory_content` 全覆盖 + +### 触发方式 + +- **自动**——每个 Agent 在系统定时任务里有一行,每天凌晨 2 点跑一次 +- **手动**——`POST /api/v1/memory/{agentId}/emergence` + +### 为什么不会递归 + +整合跑起来的时候会通过 Agent 触发一次"对话"。没有保护的话,那次对话会再触发对话后记忆提取监听器,循环下去。 + +事件上带触发源标记,提取监听器看到是定时任务触发的就直接跳过。 + +### DREAMS.md —— 整合日记 + +每次整合跑完会往 `workspace/{agentId}/DREAMS.md` 追加一条短记录: + +- 它看了什么 +- 它找到了什么模式 +- 因此 MEMORY.md 变了什么 +- 日期 + +这给你一条人类可读的审计线——你可以打开 DREAMS.md,看**记忆是怎么一步步走到当前状态的**。这个文件也有自我增长的上限,超过阈值会对旧记录做总结。 + +### 打分式 Emergence + 召回追踪 + +整合不是盲目地总结。它会追踪: + +- **哪些记忆条目在最近的对话里真的被主动召回**——Agent 的读取模式反过来影响整合对"什么重要"的判断 +- **打分式 emergence**——候选模式按频率 + 近期性 + 显式召回打分,只有高分的才能进 MEMORY.md +- **多闸门过滤**——低信号的提取(一次性提及、矛盾、用户后来主动纠正过的)会在变成记忆之前被过滤掉 +- **Dreaming 状态 API**——`GET /api/v1/memory/{agentId}/dreaming/status` + +### 完整生命周期(开关控制) + +记忆从"夜里梦一次"升级成完整的逐轮生命周期。这套行为落在开关后面——开源版默认关,生产构建打开。 + +它做的事: + +- **每一轮都被记账** —— 每一回合开始和结束时系统都在记笔记,不只是夜里整合的时候 +- **事实投影** —— 对话被拆成结构化的"事实"行,Agent 可以查询。带信任度评分 + 衰减。 +- **结构化的夜间报告** —— 整合产出一份完整报告,可以按主题手动重做 +- **晨报卡片** —— 第二天第一次对话浮出昨天的报告;逐条 Confirm / Edit / Forget +- **矛盾收件箱** —— 新事实和老事实冲突时给一个决策队列,而不是悄悄覆盖 +- **显式遗忘** —— 你说"忘掉",它就真的忘掉,从所有地方 +- **反馈打分** —— 检索到的事实点👍/👎,反馈进入信任度评分 +- **SOUL 自动演化** —— Agent 的人格档会从累积的事实里自我重写 +- **月度归档** —— 老报告滚进压缩的月度归档,时间线里能查 +- **记忆浏览器** —— 时间线、事实、矛盾、变更对比、信任度面板 + +`application.yml` 启用: + +```yaml +mateclaw: + memory: + dream-v2: + enabled: true + fact-projection: true + contradictions: true + morning-card: true +``` + +--- + +## Agent 自己读写自己的记忆 + +记忆不是单向地"发生在 Agent 身上"的事。Agent 自己在对话过程中可以主动读写自己的文件——通过一组工作空间记忆工具: + +| 方法 | 作用 | +|------|------| +| `list_workspace_memory_files` | 列出 Agent 的文件,可按文件名前缀过滤,按 `sort_order` 排序 | +| `read_workspace_memory_file` | 读某个文件的内容 | +| `write_workspace_memory_file` | 创建或覆盖一个文件(全覆盖) | +| `edit_workspace_memory_file` | 按精确查找替换编辑(增量更新,支持 `replaceAll`) | + +### 示例 + +**列表:** + +```json +// 输入 +{"agentId": 1, "filenamePrefix": "memory/"} +// 输出 +{"agentId": 1, "count": 3, "files": [ + {"filename": "memory/2026-04-09.md", "enabled": false, "fileSize": 512}, + ... +]} +``` + +**读取:** + +```json +// 输入 +{"agentId": 1, "filename": "MEMORY.md"} +// 输出 +{"agentId": 1, "filename": "MEMORY.md", "enabled": true, "content": "..."} +``` + +**编辑:** + +```json +// 输入 +{"agentId": 1, "filename": "MEMORY.md", "oldText": "旧内容", "newText": "新内容"} +// 输出 +{"agentId": 1, "filename": "MEMORY.md", "replacements": 1} +``` + +### 安全约束 + +- 只允许 `.md` 文件 +- 不允许绝对路径,不允许 `..` 目录穿越 +- `write` 是全覆盖——在乎已有内容就先 `read` +- 新建的文件默认 `enabled=false` + +--- + +## 配置参考 + +### 记忆提取 & 整合 + +```yaml +mate: + memory: + # --- 自动提取 --- + auto-summarize-enabled: true + min-messages-for-summarize: 4 + min-user-message-length: 10 + skip-cron-conversations: true + summary-max-tokens: 1000 + max-transcript-messages: 30 + + # --- 并发 --- + cooldown-minutes: 5 + + # --- 整合 / dreaming --- + emergence-enabled: true + emergence-day-range: 7 +``` + +配置前缀:`mate.memory`。 + +### 上下文窗口 + +```yaml +mate: + agent: + conversation: + window: + default-max-input-tokens: 128000 + compact-trigger-ratio: 0.75 + preserve-recent-pairs: 2 + summary-max-tokens: 300 +``` + +--- + +## API 接口 + +| 方法 | 路径 | 用途 | +|------|------|------| +| POST | `/api/v1/memory/{agentId}/emergence` | 手动触发整合 | +| POST | `/api/v1/memory/{agentId}/summarize/{conversationId}` | 对某次对话手动触发提取 | +| GET | `/api/v1/memory/{agentId}/dreaming/status` | 查询上次运行、下次计划、最新 DREAMS.md 条目 | + +--- + +## 下一步 + +- [Agent 引擎](./agents)——Agent 在一个回合里怎么用记忆 +- [LLM Wiki](./wiki)——**刻意的**知识层,和被动的记忆对照 +- [工具系统](./tools)——记忆读写工具是众多工具之一 +- [配置说明](./config)——完整配置参考 +- [架构说明](./architecture)——后端代码组织、SPI 扩展点 diff --git a/mateclaw-server/src/main/resources/docs/zh/model3d.md b/mateclaw-server/src/main/resources/docs/zh/model3d.md new file mode 100644 index 00000000..f1684185 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/model3d.md @@ -0,0 +1,227 @@ +# 3D 模型生成 + +文生 3D / 图生 3D 一键完成。配一次凭据,Agent 就可以通过 `model3d_generate` 工具生成 `.glb` 模型,结果直接挂回到对话气泡里,可拖拽旋转预览。 + +--- + +## 现状 + +| 维度 | 说明 | +|---|---| +| **当前 Provider** | 腾讯混元 3D(`ai3d.tencentcloudapi.com`,区域 `ap-guangzhou`)| +| **可用模型** | `HY-3D-3.1` / `HY-3D-3.0` / `HY-3D-Express` | +| **输出格式** | `.glb`(GLTF 二进制,单文件含贴图,前端 `` 直接渲染)| +| **生成耗时** | 1-3 分钟(Pro 慢 / Rapid 快)| +| **鉴权方式** | TC3-HMAC-SHA256(SecretId + SecretKey)| + +模型路由策略(自动按 model 字段决定接口): + +| 模型 | 调用 Action | 特性 | +|---|---|---| +| **HY-3D-3.1**(默认)| `SubmitHunyuanTo3DProJob` | 最高精度。支持 PBR 材质、多视角输入(多张图)、白模(GenerateType=Geometry)| +| **HY-3D-3.0** | 同上 | Pro 老一代,与 3.1 共享调用 | +| **HY-3D-Express** | `SubmitHunyuanTo3DRapidJob` | 极速版,仅支持 `Prompt` 或 `ImageUrl`,速度最快 | + +--- + +## 一、获取腾讯云凭据 + +混元 3D 接口走传统 CAM 鉴权(**不是 OpenAI 风格的 sk-xxx Bearer Key**),需要 SecretId + SecretKey 一对。 + +1. 登录腾讯云控制台,打开 **[访问管理 → 访问密钥 → API 密钥管理](https://console.cloud.tencent.com/cam/capi)** +2. 点「新建密钥」,腾讯云一次性给出: + - `SecretId`(`AKID` 开头,约 36 字符) + - `SecretKey`(约 32 字符) +3. **务必两个都保存好** —— SecretKey 关闭页面后无法再次查看。 + +::: tip 关于「API Key 管理」页面的 sk-xxx +腾讯云控制台还有另一个「API Key 管理」页面,给的是单个 `sk-` 前缀的 Bearer token。**那个 key 是 TokenHub(聊天补全)服务用的**,端点是 `tokenhub.tencentmaas.com`,**不能用于混元 3D**。3D 必须用上面 CAM 拿到的 SecretId + SecretKey。 +::: + +## 二、开通混元 3D 服务 + +打开 **[腾讯云混元 3D 控制台](https://console.cloud.tencent.com/ai3d)**,首次进入会要求同意服务协议 / 开通免费体验。 + +如果跳过这一步,工具会立即返回错误: + +``` +[Hunyuan3D] SubmitHunyuanTo3DProJob failed: 资源不足。 (ResourceInsufficient) +``` + +部分模型(特别是 `HY-3D-3.1`)可能需要单独申请白名单 / 购买配额,看控制台首页的额度说明。 + +## 三、在 MateClaw 里配置凭据 + +1. 进入 **「模型与凭据」** 页,找到 **「腾讯混元 3D」** 卡片(V71 迁移自动注册) +2. 点 **「更换」** / **「配置」** +3. **API Key** 字段填入 **`SecretId:SecretKey`**(中间一个英文冒号,**没有空格**): + ``` + AKIDxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:abcdefghijklmnopqrstuvwxyz123456 + ``` +4. **Base URL** 留默认 `https://ai3d.tencentcloudapi.com`(系统会自动附加 region 路由) +5. 保存 + +::: warning 单输入框的临时折中 +当前模型与凭据卡片是通用 UI,只暴露一个 API Key 输入框。后续会拆成 SecretId / SecretKey 两个独立输入框(自动拼 `:`)。 +::: + +## 四、启用 3D 生成功能 + +进入 **「设置 → 3D 生成」**: + +- **启用 3D 模型生成**:开启 +- **首选 3D 提供商**:选 `腾讯混元 3D` +- **提供商回退**:默认开启即可(目前只有一个 provider,回退暂时无意义,但保留以便未来扩展) + +点 **「保存系统设置」**。 + +## 五、在聊天里使用 + +直接说自然语言,Agent 会自动选用 `model3d_generate` 工具: + +``` +生成一个 3D 模型:可爱的卡通小恐龙,绿色,圆滚滚的眼睛 +``` + +``` +快速生成一个 3D 模型:一个红色苹果 ← LLM 会选 HY-3D-Express +``` + +``` +根据这张图生成 3D 模型:https://example.com/foo.png ← 图生 3D +``` + +``` +生成一个白模 3D:机械齿轮(不要贴图) ← Geometry 模式 +``` + +预期流程: + +1. **工具立即返回**(毫秒级),告诉你 `taskId=xxx` +2. **后端 worker 异步轮询腾讯云**,每 8 秒查一次状态 +3. **1-3 分钟后** SSE 事件 `async_task_completed` 自动推到对话 +4. **前端 ``** 渲染 `.glb`,可拖拽 / 旋转 / 缩放 + +--- + +## 工具参数(`model3d_generate`) + +| 参数 | 类型 | 必填 | 说明 | +|---|---|---|---| +| `prompt` | String | 是\* | 文本描述,最多 1024 字符 | +| `imageUrl` | String | 是\* | 参考图片 URL(图生 3D 模式)| +| `model` | String | 否 | `HY-3D-3.1` (默认) / `HY-3D-3.0` / `HY-3D-Express` | +| `enableTexture` | Boolean | 否 | `true`(默认)/ `false`(白模,仅 Pro 支持)| +| `enablePbr` | Boolean | 否 | `true` 启用 PBR 材质(更逼真,仅 Pro 支持,默认 `false`)| + +\* `prompt` 和 `imageUrl` 二选一,不能同时为空。Pro 接口下两者也不能同时给(除非走 Sketch 模式,当前未暴露)。 + +--- + +## 故障排查 + +### 1. `3D 模型生成功能未启用,请在系统设置中开启` + +→ 没启用功能开关。回到 **设置 → 3D 生成**,把「启用 3D 模型生成」打开并保存。 + +### 2. `Provider api_key must be "SecretId:SecretKey" (colon-joined)` + +→ 凭据格式不对。可能填了: +- 单个 SecretId(没拼 SecretKey) +- 单个 SecretKey +- 单个 sk-xxx(那是 TokenHub 的,不能用) +- 中间用了空格而不是 `:` + +正确格式:`AKIDxxxx...:zzzz...`(一个英文冒号)。 + +### 3. `资源不足。 (ResourceInsufficient)` + +→ 腾讯云端业务错误,**跟代码无关**。常见原因: +- 混元 3D 服务还没开通 +- 免费体验配额耗尽 +- 当前模型(特别是 HY-3D-3.1)需要审批 / 付费 + +去 [混元 3D 控制台](https://console.cloud.tencent.com/ai3d) 检查配额。 + +### 4. `invalid params, first_frame_image` + +→ 图生 3D 时 `imageUrl` 不可访问。腾讯需要从公网抓图,私有网络 / `localhost` URL 不行。检查: +- URL 在浏览器无痕模式能直接打开 +- 域名 + 文件后缀符合腾讯要求(`jpg/png/jpeg/webp`,128-5000px,≤8MB) + +### 5. 任务跑了 15 分钟还没回 + +→ Worker 默认 15 分钟超时。看后端日志: + +```bash +grep '\[Hunyuan3D\]\|\[Model3dGen\]' logs/mateclaw.log | tail -10 +``` + +查 polling 是不是被网络抖动卡死,或腾讯端长期 `RUN`/`WAIT` 没推进。可手动取消任务(重启后端 + 它会被标 failed)。 + +### 6. 模型生成出来了但前端只看到下载链接,不能拖拽 + +→ 腾讯返回的是 OBJ-zip 包(OBJ + 贴图 + MTL 多文件),不是单文件 GLB。代码已优先选 GLB 条目(`pickBestResultFile`),如果腾讯当次只返回 OBJ,前端会按 zip 显示。**切换到默认 `HY-3D-3.1` 通常会返回 GLB**。 + +--- + +## 架构(一图速览) + +``` +[ 用户 ] ─ 自然语言 ─▶ [ Agent ] ─▶ model3d_generate + │ + (model 字段路由) + │ + ┌─────────────────────────┴──────────────────┐ + ▼ ▼ + SubmitHunyuanTo3DProJob SubmitHunyuanTo3DRapidJob + (HY-3D-3.1 / HY-3D-3.0) (HY-3D-Express) + │ │ + └──────────── ai3d.tencentcloudapi.com ──────┘ + │ + 返回 JobId (24h 有效) + │ + AsyncTaskService 每 8s 轮询 Query{Pro,Rapid}HunyuanTo3DJob + │ + 状态 → DONE? ──▶ 拿 ResultFile3Ds[] + │ + 优先选 GLB > FBX > OBJ + │ + 下载到 data/chat-uploads/ + │ + 写 mate_message (type=model3d) + │ + 广播 SSE async_task_completed + │ + ▼ + 前端 useChat 识别 modelUrl ─▶ MessageBubble 桥接虚拟附件 + │ + 渲染 .glb +``` + +--- + +## 后台日志关键标记 + +正常一次完整流程的日志(按时间序): + +``` +[ToolExecutor] Executing tool: model3d_generate +[Hunyuan3D] SubmitHunyuanTo3DProJob submitted job: 1441791994... (model=HY-3D-3.1) +[AsyncTask] Created task d73723f14c7c4167 (providerTaskId=pro:1441791994...) +[AsyncTask] Started polling for task d73723f14c7c4167 (interval=8s, timeout=15min) +[ToolExecutor] Tool model3d_generate returned 80 chars +…等 1-3 分钟… +[Model3dDownloader] Downloading 3D model from https://hunyuan-prod-….cos.../...glb to data/chat-uploads/.../model_d73723f14c7c4167.glb +[Model3dDownloader] Downloaded NNNN bytes +[Model3dGen] Task d73723f14c7c4167 completed, model saved: /api/v1/chat/files/.../model_d73723f14c7c4167.glb +``` + +--- + +## 相关文档 + +- [多模态创作总览](./multimodal.md) +- [模型与凭据配置](./models.md) +- [工具系统](./tools.md) +- 设计稿:`rfcs/202605/01-generative-async-pipeline.md` diff --git a/mateclaw-server/src/main/resources/docs/zh/models.md b/mateclaw-server/src/main/resources/docs/zh/models.md new file mode 100644 index 00000000..541bdff5 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/models.md @@ -0,0 +1,454 @@ +# 模型配置 + +**先配一个。后面随时加。** + +MateClaw 不关心你用哪个 LLM。它通过五个协议适配器跟所有主流供应商对话,支持 15+ 个云端供应商和 4 个本地运行时,你可以在运行时**不动 Agent 配置**直接切模型。MateClaw 唯一的意见是——**从一个开始,需要再加**,不是第一天就把所有东西配好。 + +--- + +## 支持什么 + +### 云端供应商 + +| 供应商 | 示例模型 | 协议 | 说明 | +|--------|----------|------|------| +| **DashScope**(阿里云) | Qwen-Max、Qwen-Plus、Qwen-Turbo、Qwen-VL、Qwen-Long | dashscope | 默认开箱即用 | +| **DashScope(兼容模式)** | Qwen3.5-Plus、Qwen3.6-Plus、Qwen3 VL Plus 等点号版本号系列 | openai | 见下方"两个 DashScope 区别" | +| **百炼 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** | +| **Google Gemini** | Gemini 2 Pro、Gemini 2 Flash | gemini | Google Generative AI API | +| **DeepSeek** | deepseek-chat、deepseek-coder、**DeepSeek V4 flash + pro**(支持思考模式) | openai | OpenAI 兼容 | +| **Kimi(Moonshot)** | moonshot-v1-8k/32k/128k | openai | OpenAI 兼容 | +| **智谱 AI** | GLM-5-Turbo、GLM-5V-Turbo、GLM-5、GLM-5.1 | openai | OpenAI 兼容 | +| **MiniMax** | abab6.5、abab5.5;扩展视频模型目录 + 国内端点 | openai | OpenAI 兼容 | +| **SiliconFlow CN/INTL** | 托管路由推理 | openai | 双端点,OpenAI 兼容 | +| **OpenCode** | 代码场景路由 | openai | OpenAI 兼容 | +| **OpenRouter** | 200+ 模型含免费档 | openai | 一个 key 路由到任何上游 | +| **小米 MiMo** _(1.3.0+)_ | MiMo V2.5 Pro / V2.5 / V2 Pro / V2 Omni / V2 Flash | openai | 小米 MiMo 平台 | +| **任何 OpenAI 兼容服务** | 你自己的 vLLM 等 | openai | 自定义 base URL | + +### 本地运行时 + +| 运行时 | 示例模型 | 协议 | 说明 | +|--------|----------|------|------| +| **Ollama** | Gemma 3/4、Qwen 3、Llama 3.1、DeepSeek R1、Mistral | ollama | **启动时在 `localhost:11434` 自动检测** | +| **LM Studio** | 任何 GGUF 模型 | openai | OpenAI 兼容服务器 | +| **llama.cpp** | 任何 GGUF 模型 | openai | 通过 llama-server | +| **MLX** | Apple Silicon 上的 mlx-lm | openai | OpenAI 兼容服务器 | + +### 协议适配器 + +五个协议覆盖一切: + +| 协议 | 谁在用 | +|------|--------| +| **OpenAI** | OpenAI、Kimi、DeepSeek、MiniMax、智谱、OpenRouter、LM Studio、llama.cpp、MLX | +| **Anthropic** | Claude 家族 | +| **DashScope** | Qwen 家族 | +| **Gemini** | Google Gemini 家族 | +| **Ollama** | 通过 Ollama 跑的本地模型 | + +任何 OpenAI 兼容服务都能接——把 `base-url` 指过去就行。 + +--- + +## 两个 DashScope 区别 + +阿里云 DashScope 同一把 `sk-` API Key,**两个端点**面向不同模型族: + +| 项 | DashScope | DashScope(兼容模式) | +|---|---|---| +| 端点 | `dashscope.aliyuncs.com/api/v1`(native) | `dashscope.aliyuncs.com/compatible-mode/v1`(OpenAI-compatible) | +| 协议 | DashScope 原生协议 | OpenAI 协议(同 GPT-4 / DeepSeek / Kimi 一样) | +| 内置 web 搜索(`enable_search`) | ✅ 支持 | ❌ 不支持 | +| 适用模型 | Qwen-Max / Plus / Turbo / Long、Qwen-VL、Qwen3-Max、DeepSeek-V3.2 等 | **带点号版本号**的新模型族:Qwen3.5-Plus、Qwen3.6-Plus、Qwen3 VL-Plus 等 | + +**为什么分两个**:阿里把 dot-versioned 新模型族(`qwen3.5-*` / `qwen3.6-*` / `qwen3-vl-*`)只放在兼容模式端点上发布;用 native 协议调它们会返回 `400 InvalidParameter`。两个 provider 可以**共用同一把 sk- Key**,复制粘贴一次就好。 + +**怎么选**: +- 想用 Qwen-Max / Plus / Turbo + 内置搜索 / DeepSeek-V3.2 → **DashScope** +- 想用 Qwen3.5-Plus / Qwen3.6-Plus / Qwen3 视觉理解 → **DashScope(兼容模式)** +- **两个都启用**也可以——同一把 Key,只是模型出现在不同卡片下 + +--- + +## 添加一个供应商 + +**新装的 MateClaw 主列表是空的。这是故意的。** + +你不需要看见 16 个供应商,你需要**一个能跑的**。 + +`设置 → 模型 → 添加供应商`——按钮打开一个抽屉,里面是完整目录。本地运行时(Ollama、LM Studio、llama.cpp、MLX,**不需要 API Key**)排在前面,云端供应商(DashScope、OpenAI、Anthropic、DeepSeek 等)在后面。 + +三步: + +1. **找到要的那一行,点启用**——这个供应商进入主列表 +2. **填 base URL(已知供应商预填)+ 粘贴 API Key**——加密存储,UI 脱敏 +3. **保存 → 测试连接**——系统发一个轻量请求验证 + +抽屉关掉之后,主列表只显示你启用过的供应商。**模型选择器、聊天页、Agent 编辑器——所有看得到模型的地方,都只看得到你启用过的。** + +::: tip 老用户升级(V55 迁移) +已经在用的供应商不会被关掉。V55 把符合以下任意一种条件的供应商自动标记为启用: +- 配过真实 API Key +- 有 OAuth token +- 最近 30 天被聊天会话使用过 +- 是当前默认模型所在的供应商 + +没用过、留在数据库里占位的供应商,会回到抽屉里——你下次需要时再启用。 +::: + +--- + +## 启用 / 禁用一个供应商 + +主列表上每张供应商卡片都有**启用 / 禁用**开关。**先启用,才可用**——这是 v1.1.0 之后整个产品契约的核心。 + +- **禁用**——供应商从模型选择器、聊天页、Agent 编辑器里立刻消失。**配置不丢**,重新启用后原样恢复 +- **如果你禁用的是当前默认模型所在的供应商**,系统会自动把默认模型切到一个还启用着的供应商上的模型——不会让下一条消息直接报错 +- **启用**——供应商重新出现在所有看得到模型的地方。从未填过 API Key 的话,会提示你去配 + +这把"我有这个供应商的 Key 但今天不想用它"和"我没这个供应商"分开。临时切供应商不需要删配置。 + +### ChatGPT OAuth —— 不需要 API Key + +有 ChatGPT Plus 或 Pro 账号?MateClaw 可以通过**浏览器 OAuth** 对接 OpenAI 的 chat 端点——你按平常方式登录,你的订阅被直接使用。GPT-4o、o3、o4-mini 立刻可用。 + +`设置 → 模型 → 添加供应商 → OpenAI OAuth`。浏览器窗口弹出。Token 交换在后端完成,**凭证不离开你的机器**。 + +### 设备授权(Device Authorization Grant)—— 远程 / 无头部署专用 + +浏览器回调式 OAuth 要求 IDP 的重定向能落回 *你的浏览器* 能访问的某个 `localhost` 端口。这事儿在 MateClaw 跑你笔记本上时没问题,一旦你把它放到服务器、容器、或任何不向客户端暴露 loopback socket 的宿主上,就立刻坏掉。 + +针对这种情况,OpenAI OAuth 会自动切到 **设备授权(RFC 8628)**——和 ChatGPT 桌面端、`gh auth login` 用的是同一个流程。不需要回调,不需要端口映射。 + +非 localhost 宿主下,`设置 → 模型 → 添加供应商 → OpenAI OAuth` 会弹出一个对话框,里面有: + +- 一个短的**用户码**(等宽字体,可复制) +- 一个**验证 URL**:`auth.openai.com/codex/device`——任何设备的任何浏览器都能打开 +- 一个**实时倒计时**,显示设备码还剩多久过期(默认 15 分钟) + +把用户码填进浏览器、授权完成,对话框会在后端轮询拿到 `COMPLETED` 的瞬间自动关闭。 + +**MateClaw 怎么决定走哪个流:** + +| `mateclaw.oauth.openai.deployment-mode` | 行为 | +|---|---| +| `auto` *(默认)* | `localhost` / `127.0.0.1` / `::1` → 浏览器回调;其它 host → 设备授权 | +| `local` | 强制走浏览器回调(loopback 服务器) | +| `device_code` | 强制走设备授权 | +| `manual_paste` | 强制走旧的"复制回调 URL 粘回来"流 | + +如果 `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` | 丢弃会话(比如用户关了对话框) | + +前端按 OpenAI 返回的 `intervalSeconds`(一般 5 秒)轮询;服务端再设一个最小轮询间隔(默认 3 秒)兜底,避免被打。过期的会话每 5 分钟扫一次清掉。 + +token 持久化和刷新走的是和浏览器回调流**完全相同**的代码路径,所以对话框关了之后行为没有任何差别。 + +### Anthropic Claude Code OAuth + +同样的套路、同样的结果:有 Claude Pro / Max / Team 订阅?走 **Claude Code 自己用的那套 OAuth 流程** 登录——不需要 `sk-ant-…` 的 API Key。Claude 4.7 / 4.6 / 4.5 Haiku 通过订阅上线。 + +`设置 → 模型 → 添加供应商 → Anthropic Claude Code OAuth`。支持两种流程: + +- **浏览器回调** —— 本地安装,浏览器弹窗,点完授权 token 落到 MateClaw +- **MANUAL_PASTE** —— 远程服务器部署、浏览器到不了后端时,本地浏览器完成授权后把 token 粘回来 + +通过 anti-abuse 反滥用门:注入 Claude Code 身份到系统 prompt,请求形态(UA / accept 头 / `system` 数组形式 / `mcp_` 工具名前缀)与 Claude Code 在线协议完全对齐,请求不会被拒绝。 + +--- + +## 模型发现 + +提供模型列表的供应商(OpenAI、Ollama、LM Studio、OpenRouter 等)支持**模型发现**——一键让 MateClaw 拉取这个供应商下的所有模型。 + +- `设置 → 模型 → [供应商卡片] → 发现模型` +- 系统查询供应商的 `/v1/models` 端点 +- 发现的模型带名字、上下文窗口、价格 +- 逐个或批量添加 + +对 OpenRouter 特别有用——**让 200+ 免费档模型全都可见**。挑一个免费模型零成本有一套能用的环境。 + +### Ollama 启动时自动检测 + +不用手动配。启动时: + +1. **Ping** `http://127.0.0.1:11434` +2. **发现**——通过 `/v1/models` 拉取已拉的模型 +3. **注册**——加进 `mate_model_config` +4. **启用**——自动启用匹配的预配置模型 +5. **标签重写**——把种子里的 `:latest` 重写为实际安装的版本(`deepseek-r1:latest` → `deepseek-r1:7b`),不再因为 `model not found` 报 404 + +Ollama 没跑就**静默跳过**。 + +::: tip 默认行为 +- 无工具支持的模型(`deepseek-r1`、`gemma*`、`phi3/4` 等)不会被意外激活为默认——它们进入黑名单 +- 在 native DashScope 协议下不可用的模型在启动时自动清理;带点号版本号的 Qwen 系列改由 DashScope(兼容模式)provider 承载 +- DashScope 模型发现做协议感知探测,跳过非聊天模态 +::: + +**预配置的 Ollama 模型**(默认禁用,发现后自动启用): + +| 模型 | `model_name` | +|------|-------------| +| Gemma 3 | `gemma3:latest` | +| Gemma 4 | `gemma4:latest` | +| Qwen 3 | `qwen3:latest` | +| Llama 3.1 | `llama3.1:latest` | +| DeepSeek R1 | `deepseek-r1:latest` | +| Mistral | `mistral:latest` | + +配置: + +```bash +# 从 ollama.com 安装 Ollama,然后: +ollama pull gemma3 +ollama pull qwen3 +``` + +重启 MateClaw。自动发现、添加、启用。 + +--- + +## 数据库 schema + +### `mate_model_provider` + +| 列 | 用途 | +|----|------| +| `id` | 主键 | +| `name` | 供应商标识符 | +| `display_name` | 人类可读的名字 | +| `protocol` | `dashscope` / `openai` / `ollama` / `anthropic` / `gemini` | +| `base_url` | API 基础 URL | +| `api_key` | 加密的 API Key | +| `oauth_tokens` | OAuth tokens(ChatGPT Plus/Pro) | +| `is_local` | 本地运行时为 true | +| `enabled` | 供应商总开关——禁用后从所有模型选择器消失,配置保留(v1.1.0+) | + +### `mate_model_config` + +| 列 | 用途 | +|----|------| +| `id` | 主键 | +| `provider_id` | 外键到 `mate_model_provider` | +| `model_name` | 实际的模型标识符 | +| `display_name` | 人类可读的名字 | +| `temperature` | 默认温度(0.0–2.0) | +| `max_tokens` | 最大输出 token | +| `top_p` | top-p 采样 | +| `group_name` | UI 分组("Reasoning"、"Fast"、"Vision" 等) | +| `enabled` | 模型开关 | + +### 嵌入模型 + +不用配 `EMBEDDING_API_KEY` 环境变量。嵌入模型就是 `mate_model_config` 里 `model_type='embedding'` 的普通行。`设置 → 模型` 里和聊天模型列在一起。知识库从下拉里选它的嵌入模型。 + +### Anthropic prompt 缓存 + +系统 prompt、Agent 人格、工具定义——在 Anthropic 兼容端点上自动带 `cache_control: ephemeral`。第一次请求热身,之后每次缓存命中。Dashboard 里有 `cache_read_tokens` / `cache_write_tokens` 日维度统计。 + +### 思考深度 / `reasoning_effort` + +**哪些模型会看这个参数**:`reasoning_effort` 只对 OpenAI reasoning 族(`gpt-5*` / `o1*` / `o3*` / `o4*`)有效,且只通过 OpenAI / Azure-OpenAI 两家 provider 下发。任何别的 provider(DeepSeek、Kimi、DashScope、Ollama、自托管 OpenAI-兼容网关等)收到这个参数都会报错或触发异常行为。 + +**三点产品契约**: + +1. **Chat 类不带思维链的模型**,即使用户在前端 UI 选择"深度思考 = high",系统也**不执行** thinking——不是 UI 问题,是能力属性。模型选择器换到不支持的模型后"思考深度"选项自动灰掉。 +2. **Provider 的 `generateKwargs.reasoningEffort` 配置**只对白名单 provider 有效。在 DeepSeek / Kimi / 其他 OpenAI-兼容 provider 上配它会被**无条件丢弃**并打 WARN,不会实际下发。 +3. **Failover 切换**时会再次校验:如果 primary 是 GPT-5 而 fallback 是 DeepSeek,`reasoning_effort` 会在出站前被剥除,泄漏到 DeepSeek 的不会触发 400。 + +**DeepSeek thinking 的正确用法**:DeepSeek 的 thinking 模式**不接受** `reasoning_effort` 参数。 + +- `deepseek-reasoner`:模型本身自带 thinking,无需任何配置。 +- `deepseek-chat` 想开启 thinking:按 DeepSeek 官方文档在 provider 的 `generateKwargs.extra_body` 里加 `{"thinking": {...}}`,**不要**填 `reasoningEffort`。 + +**Kimi K2.5 thinking**:模型自带 thinking,也不接受 `reasoning_effort`。 + +**多轮 tool call + thinking**:带 thinking 的模型(DeepSeek-Reasoner / GPT-5 / Kimi K2.5)在 ReAct 多轮 tool call 场景下,历史消息的 `reasoning_content` 会正确回传给 provider;跨用户问题边界时自动清除,同一问题内的子轮次全部保留——符合 DeepSeek 的"同问题子轮必须回传、跨问题时清"契约。 + +--- + +## 分组模型选择器 + +当你部署里配了一堆模型之后,聊天界面上的模型选择器按供应商和标签分组。带搜索的下拉框允许你按名字、供应商、分组过滤——"所有 Qwen"、"所有 reasoning 模型"、"所有 7B 以下"。分组通过 `group_name` 列定义。 + +当 Agent 可以按任务绑定不同模型之后,这变成了**刚需**——Plan-Execute 用 reasoning 模型、Chat 用便宜快速的、图像理解用视觉模型。 + +--- + +## 运行时切换活跃模型 + +MateClaw 用一个**活跃模型**作为全局默认。没有指定自己模型的 Agent 都用它。 + +- **UI:** `设置 → 模型 → [模型卡片] → 设为活跃` +- **API:** `PUT /api/v1/models/active` + +**立刻生效**——不需要重启。下一条消息用新模型。进行中的对话不受影响。 + +也支持按 Agent 覆盖:把某个 Agent 绑定到特定模型配置。 + +--- + +## 单模型测试 + +每个模型卡片都有**测试**按钮。点一下,系统发一个简单 prompt,给你看: + +- 实际响应文本 +- 延迟 +- Token 用量 +- 错误 + +加了新供应商或怀疑 key 过期时用它。 + +--- + +## 多模态旁路(系统级) + +::: tip 1.3.0 新增 +让纯文本主模型也能"看图回答",参见 [issue #87](https://github.com/matevip/mateclaw/issues/87)。 +::: + +入口:**设置 → 模型 → 多模态旁路**。两个独立的卡片: + +| 卡片 | 用途 | 状态 | +|------|------|------| +| **视觉旁路模型** | 用户上传图片时调用一次,把图片转成结构化描述,再交给主对话模型 | 已上线 | +| **视频旁路模型** | 同样的思路用于视频 | 预留(v1 不接路由,仅持久化配置) | + +数据库存的是 `mate_model_config.id`(不是 modelName)——同一 `model_name` 在不同 provider 下都能存在(如 `qwen-vl-max` 同时挂 DashScope 和 OpenAI-Compatible),存名字会撞。两条 setting key: + +- `default.vision_model` +- `default.video_model` + +下拉只列**支持对应 modality 的模型**——筛选逻辑走后端 `ModelCapabilityService.supports(...)`,未启用 / 没声明 vision 能力的模型都不会出现在选项里。每张卡片有独立的"保存"按钮,互不干扰。 + +什么时候触发?运行时由 `MultimodalRouter` 决策([源码](https://github.com/matevip/mateclaw/blob/main/mateclaw-server/src/main/java/vip/mate/llm/routing/MultimodalRouter.java)): + +- 主模型已支持图片 → 不路由(走原 native multimodal 路径) +- 主模型不支持图片 + 配了视觉旁路 → SIDECAR 策略,视觉模型转描述 +- 主模型不支持图片 + 没配视觉旁路 → 跳过附件 + 文本提示让用户去配 + +具体的用户流程、徽章、提示条详见 [聊天与消息 → 主模型不支持图片?走"多模态旁路"](./chat#主模型不支持图片走多模态旁路)。 + +--- + +## 多模型 Failover + +::: tip OpenAI 挂了 30 分钟,我的 AI 没停过一秒 +上次 DashScope 限流抽风的 30 分钟里,我们的服务可用率是 100%。 + +用户看到的是回答正常说完——没有红色 error,没有"服务暂时不可用,请稍后再试"。**主 provider 在用户那一句话回答的中途**自动切到下一个健康的 provider,断点之后的 token 直接接上。 + +不是工程师讲的"自动重试"——是**用户感知不到的故障转移**。 +::: + +每个 provider 加进来都进入 `AvailableProviderPool`,启动时探活,配置变更自动重探。 + +- **自动 fallback** —— 主 provider 返回 `AUTH_ERROR` / `BILLING` / `MODEL_NOT_FOUND` / `NETWORK` / `5xx` 时,运行时滚到下一个 provider,而不是把错误抛到 UI +- **每个 agent 自定义优先级** —— 在 `设置 → 模型` 的拖拽编辑器里把某个 agent 锁成 "OpenAI 优先 → Anthropic → DashScope" +- **池子状态实时可见** —— 每个 provider 用绿/琥珀/红徽章标健康状态 +- **4 协议探活** —— DashScope、OpenAI 兼容、Anthropic、Ollama 风格 +- **手动重探 + 配置变更自动重探** —— 换 key 不用重启 +- **出口 sanitizer** —— provider 专属选项(如 OpenAI 推理模型的 `reasoning_effort`)在 failover 到不支持的 provider 时被剥离,泄漏的选项不会让 fallback 报 400 +- **UI 区分 401 与会话过期** —— provider 认证错误和用户会话过期现在显示不同消息、不同处置 + +--- + +## API 配置 + +```bash +# 列已启用的供应商(主列表看到的) +curl http://localhost:18088/api/v1/models \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# 列完整目录(含未启用项)——Add Provider 抽屉用的就是这个 +curl http://localhost:18088/api/v1/models/catalog \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# 启用一个供应商 +curl -X POST http://localhost:18088/api/v1/models/{providerId}/enable \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# 禁用一个供应商(如其下模型为当前默认会自动切换) +curl -X POST http://localhost:18088/api/v1/models/{providerId}/disable \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# 添加一个模型配置 +curl -X POST http://localhost:18088/api/v1/models \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "providerId": 1, + "modelName": "qwen-plus", + "displayName": "Qwen Plus", + "temperature": 0.7, + "maxTokens": 4096, + "groupName": "Fast", + "enabled": true + }' + +# 设置活跃模型 +curl -X PUT http://localhost:18088/api/v1/models/active \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"providerId": "openai", "model": "gpt-4o"}' + +# 发现模型 +curl -X POST http://localhost:18088/api/v1/models/{providerId}/discover \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# 测试连接 +curl -X POST http://localhost:18088/api/v1/models/{providerId}/test-connection \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" +``` + +--- + +## 所有配置都走 UI + +::: tip +**模型配置 100% 通过 UI 管理。** 没有任何 `spring.ai.*` 的 YAML 需要你手动改。所有供应商、所有 API Key、所有模型配置、所有切换——全部在 `设置 → 模型` 里,底层存在 `mate_model_provider` 和 `mate_model_config` 数据库表。 +::: + +UI 处理了你原本在 YAML 里会做的一切,外加几件 YAML 做不到的事: + +- **添加供应商**——选类型、粘 key、保存。数据库加密存储,UI 里脱敏显示。 +- **测试连接**——上线前先验证供应商。 +- **模型发现**——支持 `/v1/models` 的供应商一键拉取整个列表。 +- **单模型测试**——发一个测试 prompt,看真实响应、延迟、token 用量。 +- **运行时切换活跃模型**——不重启、不重载配置,下一条消息生效。 +- **按 Agent 覆盖**——把某个 Agent 绑定到特定的模型配置。 + +LLM API Key **不再读取环境变量**——`DASHSCOPE_API_KEY` / `OPENAI_API_KEY` 这类设置已经没有任何效果。所有供应商、Key、模型都住在 UI 里。新装的实例启动时数据库里没有供应商,到「设置 → 模型 → 添加供应商」加你的第一家即可。 + +### 参考:Qwen 模型怎么挑 + +如果你用 DashScope,大致阵容是这样: + +| 模型 | 上下文 | 适合 | +|------|--------|------| +| `qwen-max` | 32K | 复杂推理、分析 | +| `qwen-plus` | 32K | 通用 | +| `qwen-turbo` | 8K | 快速响应 | +| `qwen-vl-max` | 32K | 视觉 + 语言 | +| `qwen-long` | 1M | 超长文档 | + +--- + +## 下一步 + +- [配置说明](./config)——完整配置参考 +- [Agent 引擎](./agents)——Agent 怎么使用模型 +- [控制台](./console)——模型管理 UI diff --git a/mateclaw-server/src/main/resources/docs/zh/multimodal.md b/mateclaw-server/src/main/resources/docs/zh/multimodal.md new file mode 100644 index 00000000..31a3abf7 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/multimodal.md @@ -0,0 +1,188 @@ +# 多模态创作 + +语音、音乐、图像、视频——MateClaw 一开始就把它们当一等公民,不是事后贴上去的贴纸。 + +市面上大多数 AI 产品把多模态生成当插件处理:需要的时候装一个。MateClaw 反过来——它从第一天起就把多模态当作核心基础设施:**六个图像供应商、六个视频供应商、三个 TTS、两个 STT、两个音乐后端**,全部在同一套工具接口后面统一,Agent 调用时不用关心底下跑的是谁。 + +配一次,所有地方都能用。 + +--- + +## 盒子里有什么 + +### 图像生成 —— 六个供应商 + +| 供应商 | 模型家族 | 说明 | +|--------|----------|------| +| **DashScope** | 通义万相 | 阿里的图像模型,默认云端选项 | +| **OpenAI** | DALL-E 3 | 标准 DALL-E 端点 | +| **fal.ai** | Flux | 通过 fal.ai 跑 Flux,快 | +| **Google Imagen** | Imagen 3 | 需要 Google Cloud 凭证 | +| **智谱** | CogView | 对中文 prompt 原生支持 | +| **MiniMax** | —— | 同步异步都可以 | + +图像生成工具会自动挑默认供应商,也可以在调用时强制指定某一家。异步生成返回一个 job id,Agent 轮询;图片落地后会挂到**原来的那条消息上**,不是创建一条新的。 + +::: tip 1.3.0 新增 +DashScope 通义万相在 v1.3.0 起接入了**统一多模态生成端点**(`multimodal-generation/generation`),新增 14 个图像模型(含 6 个**支持图像编辑**的模型)。详见下方 [图像编辑](#image-edit) 一节。 +::: + +#### Image edit + +::: tip 1.3.0 新增 +图像编辑(图生图)自 v1.3.0 起支持。在 v1.2.0 及更早版本里,`image_generate` 工具只能做"文生图"。 +::: + +`image_generate` 工具新增 `image` / `images` 两个参数: + +| 参数 | 形态 | 说明 | +|---|---|---| +| `image` | 单张参考图 | 字符串:路径 / `file://` / `data:image/...` / `http(s)://` / `msg::` | +| `images` | 多张参考图(最多 5 张) | 数组:每个元素同上 | + +工具内部统一把这五种引用形式归一化为内存 buffer 后丢给 provider。**5 种引用形式**: + +1. **本地路径** —— `/abs/path.png` / `~/x.png` / `./rel.png` +2. **`file://` URL** —— 绝对路径变体 +3. **`data:image/png;base64,...`** —— 内联 base64 / 百分号编码 +4. **`http(s)://...`** —— 带 SSRF 校验,禁止内网地址 +5. **`msg:[:]`** —— 引用同会话内某条消息上的图片附件,**非视觉模型也能直接用**——agent 不需要"看见"图片字节,只要在对话历史里见过这个 messageId 即可 + +```text +用户:(上传一张日落图,messageId=12345)把背景改成森林 +Agent:image_generate(prompt="把背景改成森林", + image="msg:12345:0", + model="qwen-image-edit") +``` + +**支持图像编辑的模型**(DashScope 通义万相): +- `wan2.7-image` / `wan2.7-image-pro`(**T2I + 编辑**) +- `qwen-image-edit` / `qwen-image-edit-plus` / `qwen-image-edit-max`(**纯编辑**) + +在 [模型配置](./models#两个-dashscope-区别) 文档里有更全的模型清单。 + +### 视频生成 —— 六个供应商 + +- **DashScope**——通义万相视频 +- **Runway**——API 调 Gen-2 / Gen-3 +- **MiniMax(Hailuo)**——文生视频 + 图生视频 +- **Fal**——快速推理管线 +- **CogVideo**——智谱 CogVideoX +- **Kling**——快手可灵视频生成 + +异步挂载逻辑和图像一样。视频渲染完成后直接出现在 Agent 当初说"正在处理"的那个气泡里。 + +### 音乐生成 —— 两个供应商 + +- **Google Lyria**——高质量音乐生成 +- **MiniMax**——支持歌词 + 风格 prompt + +音乐生成工具接收 prompt、可选风格标签、可选歌词。输出是一条 MP3 挂在消息上。 + +### 3D 模型生成 —— 一个供应商 + +- **腾讯混元 3D**——`HY-3D-3.1` / `HY-3D-3.0`(Pro,支持 PBR / 多视角 / 白模)/ `HY-3D-Express`(极速版) + +文生 3D 与图生 3D 双模式,输出 `.glb`,前端 `` 直接渲染可拖拽预览。完整配置步骤见 **[3D 模型生成](./model3d.md)**。 + +### 语音合成(TTS)—— 三个供应商 + +- **DashScope CosyVoice**——中英文,韵律自然 +- **OpenAI TTS**——alloy、echo、fable、onyx、nova、shimmer 六种音色 +- **MiniMax T2A**——中文音色,带情感标签 + +任何 Assistant 消息上都有一个喇叭图标,点一下就朗读出来。用哪个声音取决于你在设置里激活的 TTS 供应商。 + +### 语音识别(STT)—— 两个供应商 + +- **DashScope Paraformer**——中文优先,低延迟 +- **OpenAI Whisper**——多语言行业基准 + +在聊天输入框按住麦克风图标讲话,松手转文本。识别结果可以在发送前再改一遍。 + +--- + +## 怎么配 + +所有多模态供应商都在 `设置 → 模型 → [类别]` 里。添加一次 API Key,然后把它标记为这个类别的默认。 + +```yaml +# application.yml —— 最小配置示例 +mate: + image: + default-provider: dashscope + video: + default-provider: dashscope + tts: + default-provider: cosyvoice + stt: + default-provider: paraformer + music: + default-provider: dashscope +``` + +如果你想让某个 Agent 总是用 Flux 出图、CosyVoice 发声,可以在 Agent 级别单独覆盖。 + +--- + +## Agent 怎么用 + +每一个多模态能力都是一个工具: + +| 工具 | 签名 | +|------|------| +| `image_generate` | `(prompt, style?, size?)` | +| `video_generate` | `(prompt, duration?)` | +| `music_generate` | `(prompt, style?, lyrics?)` | + +Agent 调用它们和调用任何其他工具一样。工具层负责供应商选择、重试、异步轮询、附件挂载。 + +--- + +## 异步生成 + 消息挂载 + +图像和视频生成往往比一个普通的 Agent 回合要慢。MateClaw 处理这件事的方式: + +1. Agent 调用生成工具。 +2. 工具立刻返回一个 job id 和占位附件。 +3. 后端在后台轮询供应商。 +4. 结果落地后,挂到**原来的那条 Assistant 消息上**,不是新建一条。 + +它工作得很干净:图片会出现在 Agent 当初说"正在处理"的那个气泡里,不是飘在一条新消息里。 + +--- + +## 产品里的哪些地方能看到 + +- **聊天**——拖图片进输入框给视觉模型用;按住麦克风语音输入;点任何回答上的喇叭朗读;生成的媒体直接内嵌。 +- **Agents**——可以单独开启或关闭某个 Agent 的多模态工具。 +- **工具页**——每个供应商都有一个测试按钮,方便在上线前验证 Key。 +- **桌面端**——上面所有功能,外加本地文件系统访问用于批处理。 + +--- + +## 什么时候用什么 + +- **图像**——文档配图、幻灯片、概念可视化、营销素材。起步用 DashScope 或 Flux;需要精确的文字渲染就用 DALL-E 3。 +- **视频**——短视频 demo、社交内容、产品动画。追求质量用 Runway,中文场景用 MiniMax,想本地云就 DashScope。 +- **音乐**——背景音乐、Demo 音效、创意尝试。目前两家,后面还会扩。 +- **TTS**——无障碍朗读、有声书式阅读、多语言内容。中文用 CosyVoice,英语要多样化就 OpenAI。 +- **STT**——语音输入、会议转写、口述工作流。中文用 Paraformer,其他语言用 Whisper。 + +--- + +## 多模态输入:主模型不支持?走旁路 + +::: tip 1.3.0 新增 +本页讲的是**生成(输出)**。**输入侧**的多模态——上传图片给纯文本主模型——走另一套路径:「多模态旁路」(sidecar)。详见 [聊天与消息 → 主模型不支持图片?走"多模态旁路"](./chat#主模型不支持图片走多模态旁路) 和 [模型配置 → 多模态旁路(系统级)](./models#多模态旁路-系统级)。 +::: + +简而言之:在「设置 → 模型 → 多模态旁路」配一个视觉模型,主模型不支持图片时系统会**自动把图片转描述**再喂给主对话模型,主模型保持便宜,路由全程在聊天 UI 可见(路由徽章 + 输入框上方提示条)。 + +--- + +## 下一步 + +- [聊天与消息](./chat)——附件输入、多模态旁路路由、生成的媒体如何挂载到消息上 +- [模型配置](./models)——供应商配置 UI、多模态旁路设置 +- [工具系统](./tools)——承载多模态生成的工具层 diff --git a/mateclaw-server/src/main/resources/docs/zh/quickstart.md b/mateclaw-server/src/main/resources/docs/zh/quickstart.md new file mode 100644 index 00000000..d3373b7d --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/quickstart.md @@ -0,0 +1,86 @@ +# 快速开始 + +60 秒到第一条消息。**只有一条路:桌面端。** + +Docker 和源码启动在 [配置说明](./config) 和 [贡献指南](./contributing) 里。这一页只做一件事——用尽可能快的速度,把你从"什么都没有"送到"一个能工作的 Agent"面前。 + +--- + +## 1. 下载 + +去 [GitHub Releases](https://github.com/matevip/mateclaw/releases) 拿最新安装包。 + +- **Windows**——`MateClaw-Setup-x.y.z.exe` +- **macOS**——`MateClaw-x.y.z.dmg` +- **Linux**——`MateClaw-x.y.z.AppImage` + +不用装 Java。不用装 Node。不用装 Maven。桌面端已经把 JRE 21 和后端 JAR 打包好了。 + +## 2. 启动、登录 + +双击。首次启动要 10 到 30 秒,后端在后台起来。 + +账号 `admin`,密码 `admin123`。**进去之后第一件事**:`设置 → 安全` 里改密码。现在就改。 + +## 3. 配一个模型 + +`设置 → 模型 → 添加供应商`。 + +挑一个就行,别贪多: + +- **DashScope**——云上最省事的起点,去阿里云控制台复制 Key 粘进来 +- **OpenAI / Anthropic**——手头有 Key 就直接填 +- **Ollama**——本地 GPU 用户,会自动识别 `localhost:11434` +- **ChatGPT OAuth**——有 Plus 或 Pro 账号,走浏览器登录一下,就能直接用 GPT-4o、o3、o4-mini + +保存。模型会立刻出现在 Chat 页面的模型选择器里。 + +## 4. 打个招呼 + +左侧导航点 `聊天`。选一个 Agent。选刚配好的模型。输入: + +> *你好。你现在能做什么?* + +回车。看 token 流出来。 + +看到回答了——**系统活了,你已经在产品里了**。接下来所有事情都是在让它**对你有用**,而不是在让它"能跑"。 + +--- + +## 接下来先试这几件事 + +系统装好了。然后呢? + +**试一次带工具的对话。** 输入:"帮我搜一下 Spring Boot 最新版本,总结一下里面的 breaking changes。" 看 Agent 自己去调搜索工具、执行、观察结果、返回答案——这就是 ReAct 在工作。 + +**建一个自己的 Agent。** `Agents → 新建 Agent`,从模板开始(模板是开箱即用的),重命名、改 system prompt、勾选允许的工具、保存。Agent 是你从"一个聊天窗口"升级到"一整支 AI 团队"的方法。 + +**建一个知识库。** `Wiki → 新建知识库`,扔一份 PDF 或者指一个本地文件夹。等它消化完(每条 raw material 上都有进度条)。消化完之后把这个 KB 绑到一个 Agent 上,问里面的内容。底下到底发生了什么看 [LLM Wiki](./wiki)。 + +**接一个聊天渠道。** `渠道` 里选钉钉、Telegram 或者八个支持的平台里随便一个,贴 Bot 凭证。同一个 Agent 会立刻开始在那个渠道里回复——**带着它在你桌面端的全部记忆**。 + +每一件事在侧边栏里都有独立的文档页,想深入时再去读。 + +--- + +## 出了问题? + +第一次跑通本应该很顺。如果没跑通—— + +- **安装器打不开**——Windows 下右键 → 属性 → 解除锁定;macOS 下去"系统设置 → 隐私与安全性"允许未签名应用。 +- **后端起不来**——看 `~/.mateclaw/logs/app.log`(Windows:`%USERPROFILE%\.mateclaw\logs\`)。十有八九是 18088 端口被占了。 +- **模型调用报错**——API Key 填错了,或者网络不通。回设置里检查,或者换一家试试。 +- **界面白屏**——Ctrl/Cmd + Shift + R 强刷。Electron 的缓存比较顽固。 +- **还是不行**——去 [GitHub Issues](https://github.com/matevip/mateclaw/issues) 开一个 Issue,把 `app.log` 的尾巴贴上。我们真的会看。 + +--- + +## 其他部署方式 + +- **Docker**——`cp .env.example .env` 填好密码,`docker compose up -d --build`。完整的前置要求、Maven 镜像选择(中国 / 美国)、浏览器工具自检、升级流程看 [Docker 部署](./docker-deploy)。 +- **从源码跑**——`mateclaw-server/` 里 `mvn spring-boot:run`,`mateclaw-ui/` 里 `pnpm dev`。细节在 [贡献指南](./contributing)。 +- **桌面端内部**——打包、签名、自动更新。看 [桌面应用](./desktop)。 + +--- + +下一步:去 [项目介绍](./intro) 看"为什么有这个东西",或者直接跳到 [Agent 引擎](./agents) 看产品本身。 diff --git a/mateclaw-server/src/main/resources/docs/zh/releases.md b/mateclaw-server/src/main/resources/docs/zh/releases.md new file mode 100644 index 00000000..ec028af4 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/releases.md @@ -0,0 +1,29 @@ +# 更新日志 + +每个 MateClaw 版本的发布说明。最新的文档始终以 `docs/zh/` 为单一事实源——这些说明讲的是每个版本里**改了什么**。 + +历史 diff 看对应的 git tag。功能背后的"为什么"点进完整的发布说明。 + +--- + +## 发布列表 + +| 版本 | 日期 | 亮点 | +|------|------|------| +| [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 运行时控制台让你看见每个员工正在干什么 | +| [v1.1.137](./releases/1.1.137) | 2026-04-29 | 它会从昨天学习了 · 一个模型坏了不会整体掉线 · "差一点就好"的地方现在好了 · 知识库变成了一座图书馆 | +| [v1.1.0](./releases/1.1.0) | 2026-04-17 | Agent 自动技能合成、多 agent 并行委派、Wiki 语义搜索 + 两阶段摘要、深度思考、Anthropic prompt 缓存、声明式 Hook、插件 SDK、全渠道语音、ChatConsole 多渠道实时同步、微信稳定性重建 | +| [v1.0.418](./releases/1.0.418) | 2026-04-11 | 后端国际化 (i18n)、Flyway 数据库迁移框架、WorkspacePathGuard 路径沙箱、CronJobTool 定时任务、Skill ZIP 导入、安全加固 | +| [v1.0.314](./releases/1.0.314) | 2026-04-08 | LLM Wiki 知识库、TTS/STT、音乐生成、图像/视频升级、带 keyless fallback 的搜索系统、ChatGPT OAuth 登录、Agent 运行时增强、数据库 schema 统一 | +| [v1.0.108](./releases/1.0.108) | 2026-04-06 | 数据源 SQL 查询、多模态增强、桌面动态端口、OpenRouter 免费模型 | +| [v1.0.101](./releases/1.0.101) | 2026-04-05 | 移动端布局、Ollama 启动时自动探测、模型分组、GitHub MCP、拖拽文件上传、多 Agent 协作 | +| [v1.0.0](./releases/1.0.0) | 2026-03-20 | 首次发布——ReAct + Plan-Execute Agent、12 个内置工具、MCP 协议、6 个渠道适配器、Vue 3 管理控制台 | + +--- + +## 接下来读什么 + +- [路线图](./roadmap)——计划中的、进行中的、已完成的 +- [项目介绍](./intro)——MateClaw 为什么存在 +- [贡献指南](./contributing)——怎么帮忙发布下一个版本 diff --git a/mateclaw-server/src/main/resources/docs/zh/roadmap.md b/mateclaw-server/src/main/resources/docs/zh/roadmap.md new file mode 100644 index 00000000..d7ff8007 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/roadmap.md @@ -0,0 +1,201 @@ +# 路线图 + +> "人们不知道自己想要什么,直到你把它摆在他们面前。" +> +> 这不是一份功能清单。这是一个关于**你的 AI 助手应该如何存在**的宣言。 + +--- + +## 我们的信念 + +每个人都值得拥有一个真正理解自己的 AI 助手。 + +不是一个聊天玩具。不是一个技术 demo。而是一个**数字分身**——它知道你的工作方式,连接你的所有工具,替你思考、替你执行、替你记住。 + +MateClaw 就是这个东西。 + +--- + +## 我们已经做到了什么 + +### v1.0 —— 它能思考和行动 ✅ 已发布 + +让一个 AI 助手成为"会用工具的同事",不是一个聊天框。 + +- ReAct 引擎:思考、行动、观察、再思考 +- Plan-and-Execute 编排:先制定计划,再逐步执行 +- StateGraph 架构:基于状态图的 Agent 编排 +- DynamicAgent:从数据库加载配置,运行时随时调整 +- 20 个内置工具:搜索 / Shell / 文件 / 委派 / 多模态生成 / 定时任务 / SQL 查询 +- ToolGuard + FileGuard + AuditLog:每一次工具调用有审批、有控制、有记录 +- SKILL.md 技能系统:像装 App 一样给 AI 装新能力 + +### v1.1 —— 它无处不在 ✅ 已发布 + +把 AI 从"网页上的对话框"搬进你团队真正在用的每一个 IM。 + +- **8 个渠道**:Web / 钉钉 / 飞书 / 企业微信 / Telegram / Discord / QQ / 微信个人 / Slack +- 会话来源追踪:每条消息都知道来自哪个渠道 +- 4 层记忆:会话上下文 + 工作空间记忆 + 对话后提取 + 每天凌晨 2:00 自动整合 +- DREAMS.md 整合日记:人类可读的记忆变更审计 +- 工作空间隔离:每个 agent / skill / wiki / conversation / memory 都属于一个工作空间 +- ChatGPT OAuth + Anthropic Claude Code OAuth:用订阅直接登录,不需要 API Key +- LLM Wiki 知识库 + RAG:把原始文件吃进去变成结构化、有双向链接、有摘要的知识页 + +### v1.2 —— 它是你的同事 ✅ 已发布(2026-05-05) + +把"智能体"换成**数字员工**——这不是术语洁癖,是世界观换了。 + +- **数字员工**:每位有角色(Role)、目标(Goal)、背景故事(Backstory),不是冰冷的 system prompt +- **5 个职业模板**:产品研究员 / 客户支持 / 知识管理员 / 数据分析师 / 行政助理——开箱即用 +- **技能不再是工具的别名,是骨架**:每个技能有自己的 SKILL.md + LESSONS.md + workspace 文件空间 +- **ACP 桥接**:Claude Code、Codex、Gemini CLI 这些顶级编码 Agent 以"员工"身份接入 +- **Backstage 运行时控制台**:你第一次能**看见每个员工正在干什么**——谁在跑、跑到哪一步、占多少 token、卡住了一键回收 +- **Onboarding wizard**:首次登录四步从零到第一条消息 +- **Dashboard**:日维度 usage 趋势 + 头部 agent / tool 排行 +- **Doctor**:系统健康检查 + 一键修复 + +完整故事:[v1.2.0 Release Notes](./releases/1.2.0.md)。 + +--- + +## v1.3 —— 工作流元年 ✅ 已发布(2026-05-13) + +> "聚焦不是对要关注的事情说 Yes。而是对其他一百个好点子说 No。" + +数字员工各自能干活只是起点。**真正的协作需要编排**。 + +v1.3 的主线是**让 MateClaw 从"chatbot 框架"升级为"业务流程 OS"**——一条业务流不再是几个员工各自聊天的总和,而是一份可发布、可触发、可重放的**线性 step DSL**。 + +完整故事:[v1.3.0 Release Notes](./releases/1.3.0.md)。 + +### 工作流(Workflow) + +- [x] **7 种 step mode**:sequential / fan_out / collect / conditional / await_approval / dispatch_channel / write_memory +- [x] **Pebble 表达式子集**作为条件判断 + 变量引用语言(不带副作用、不能跑代码) +- [x] **JSON-first 编辑**:Monaco + JSON schema 校验 + Pebble 静态检查 + 模板下拉 +- [x] **自然语言 → 工作流草稿**(`POST /workflows/draft/generate`):用户描述需求,agent 生成 graph_json + 编译诊断;不直接发布,仍要人工审阅 +- [x] **整数 revision**:发布写新行不可变;草稿与已发布版本分离 +- [x] **运行历史**:每个 step 的 input / output / 耗时 / token / 失败链路都被记录 +- [x] **payload 内置存储**:大输入输出走 `payload://` URI,不撑库 +- [x] **跨 workspace ACL**:发布期校验 agent / channel / employeeId 引用都在当前 workspace 内 +- [x] **`await_approval` 持久化暂停**:服务重启不丢 + +### 触发器(Trigger) + +- [x] **6 种 pattern type**:cron / webhook / channel_message / agent_lifecycle / content_match / workflow_completion +- [x] **事件治理默认开**:去重(60s 窗口)、per-trigger 限速、bot self-msg 过滤、A→B→A 递归切断 +- [x] **CronDelegationPort**:和老 cron 模块共享 ShedLock + Spring TaskScheduler,不写 mate_cron_job +- [x] **跨实例一致性**:`pattern_version` 自取消机制 + 周期 syncFromDatabase +- [x] **结构化表单**:6 种 pattern 各自有专属字段输入,不需要手写 patternJson + +### 升级现有体验 + +- [x] **图像编辑**(issue #75):`image_generate` 工具新增 `image` / `images` 参数,支持 5 种引用形式(含 `msg::` 引用会话内附件) +- [x] **DashScope 兼容模式**:复用同一把 sk- Key 接通点号版本号系列(qwen3.5-plus / qwen3.6-plus / qwen3-vl-plus 等) +- [x] **新万相 / qwen-image 系列**:14 个新图像模型,3 个新视频模型(含 happyhorse-1.0-t2v) +- [x] **4 个文档生成工具**:DocxRenderTool / XlsxRenderTool / PptxRenderTool / PdfRenderTool —— Markdown 直接渲染为 Office 文件,不 fork 子进程不依赖 npm +- [x] **MCP per-agent 工具绑定**:每个员工独立绑定 MCP 工具 + 状态徽标(connected / stale / unavailable / orphan)+ 命名空间冲突自动前缀化 + server 改名自动跟随 +- [x] **小米 MiMo provider**:MiMo V2.5 Pro / V2.5 / V2 Pro / V2 Omni / V2 Flash +- [x] **多模态旁路路由**(issue #87):纯文本主模型遇到图片附件时自动调用配置好的视觉模型转描述,主对话保持便宜;硬禁令拆掉后用户自定义工具不再被压制;路由徽章 + 输入框提示让决策全程可见 + +### v1.3 还要做的 + +- [ ] **画布编辑器(v1)**:当前画布是只读链式渲染,目标是 `@vue-flow/core` 的可拖拉编辑 +- [ ] **运行回放视图**:trace timeline + 任意节点 hover 看 input/output diff +- [ ] **`loop` mode**:迭代 N 次或对数组逐项处理 +- [ ] **`invoke_skill` mode**:直接调 skill 不经过员工 +- [ ] **trigger 间优先级 / 依赖**:同一事件命中多 trigger 时的串行 / 并行控制 +- [ ] **事件回放**:`mate_trigger_event` 加 "重新派发"按钮 + +--- + +## 下一站:v1.4 —— 场景应用元年 + +> "当工具足够好,就把工具藏起来,把场景推到前面。" + +v1.0 → v1.3 把基础设施做齐了:员工、记忆、知识库、工具、技能、工作流、触发器、多模态、多渠道。**下一步不是再造一颗螺丝**,是把这些零件组装成**用户一打开就能落地的场景**。 + +v1.4 的关键词是**场景应用**。不是"加更多功能",是**让普通用户不用学 7 种 step mode、6 种 trigger pattern 就能直接用**。 + +### 行业场景模板(Workflow + Trigger 联动) + +每一个都是一份**可一键导入的工作流模板 + 触发器配置 + 推荐员工绑定 + 推荐知识库结构**: + +- [ ] **客户工单分流**:企业微信 / 飞书入口 → 数字员工分类 → 路由 / 升级 / 自动回复 → 写进客户档案 +- [ ] **晨报 / 周报自动化**:cron trigger → 多员工并行采数 → 数据分析员工汇总 → 生成 PDF/PPTX → 多渠道分发 +- [ ] **合同审批流**:上传合同 → 法务员工初审 → 审批等待 → 法务员工修订建议 → 写归档记忆 +- [ ] **市场情报监控**:webhook trigger(站点变更)→ 内容判断(content_match)→ 商业分析员工总结 → 飞书机器人推送 +- [ ] **新员工 onboarding**:webhook(HRIS 入职事件)→ 行政助理拉文档清单 → 培训知识库引导 → 多日跟进 trigger +- [ ] **代码 PR 审查**:GitHub webhook → 代码审查员工跑 review → 评论回写 PR → 关键改动转 await_approval + +### 场景市场(Scenario Marketplace) + +- [ ] **场景包格式**:一个场景 = `workflow.json` + `triggers.json` + `agents/*.md` + `knowledge/*.md` + `README.md`,可分享 / 安装 +- [ ] **场景市场 UI**:浏览 / 试运行 / 一键安装 / 评分评论 +- [ ] **场景包版本管理**:升级提示 + diff 预览 + 回滚 + +### 让数字员工跨场景协作 + +- [ ] **员工目录画像**:每位员工自动生成"擅长 / 不擅长"标签(基于历史交互 + 技能 + 工具集) +- [ ] **场景智能推荐**:用户描述"我想要 X 流程" → 推荐最适合的场景模板 + 已有员工 +- [ ] **跨场景记忆共享**:客户工单分流和合同审批流见到的都是同一个客户档案 + +### 把基础设施进一步藏起来 + +- [ ] **自然语言 → 完整场景包**:v1.3 已有"自然语言 → 工作流草稿",v1.4 把它扩展到**整个场景**——一句话描述出 workflow + trigger + 推荐员工 + 推荐 KB 结构的完整草案 +- [ ] **典型问题向导**:把"我的工作流卡在审批没人审"这种问题做成自助诊断 +- [ ] **场景级仪表盘**:不是"今天 token 用了多少",是"今天客户工单平均处理多久" + +### 同步推进的基础能力 + +- [ ] **场景级 ACL**:场景包安装时一次性把所需的 channel / agent / KB / 工具的 allowlist 都配好 +- [ ] **跨 workspace 场景共享**:场景模板能在多个工作空间间复用(克隆 + 覆盖配置) +- [ ] **场景运行成本预估**:安装前看见预期 token / API 调用 / 触发频率 + +--- + +## 我们故意不做的事 + +> "我对我们没做过的事情和我们做过的事情一样感到自豪。" + +| 砍掉的功能 | 为什么 | 什么时候才该做 | +|-----------|--------|--------------| +| **完整 RBAC 权限模型** | MateClaw 是数字员工系统,不是企业管理平台。单团队不需要管理 100 种权限组合 | 当真正出现需要细粒度权限的多团队 SaaS 客户时 | +| **多租户** | 同上。过早的多租户是架构癌症 | 当有明确的 SaaS 商业化路径时 | +| **SSO / LDAP / SAML** | 企业集成是个无底洞 | 当付费企业客户明确要求时 | +| **30+ 节点的可视化工作流编辑器** | 用户大多用不上。**v1.3 的 7 种 step mode 已经覆盖 90% 实际场景**,剩下的复杂度推到 LLM 自然语言生成 | 真有用户场景需要 30+ 节点时(很少) | +| **移动端原生 App** | 8 个 IM 渠道 + 桌面端 + Web 已经覆盖。你在手机上用钉钉 / 飞书 / Telegram 就在用 MateClaw | 当 Web / IM 渠道有不可替代的移动专属能力时 | +| **替代 ReAct / Plan-Execute** | 工作流和这两条引擎**是协作关系**,不是替代——单 agent 多轮推理仍在那两条引擎里 | 永远不替代 | + +--- + +## 版本里程碑 + +| 版本 | 一句话 | 用户体验目标 | 状态 | +|------|--------|-------------|------| +| **v1.0** | 它能思考和行动 | 一个能用工具解决问题的 AI 助手 | ✅ 已发布 | +| **v1.1** | 它无处不在 | 8 个渠道 + 4 层记忆 + 工作空间 + LLM Wiki | ✅ 已发布 | +| **v1.2** | 它是你的同事 | 数字员工 + 5 个职业模板 + 骨架式技能 + ACP 桥接 + Backstage 运行时 | ✅ 已发布 | +| **v1.3** | 它能编排业务流 | 工作流 + 触发器 + 图像编辑 + 文档生成 + per-agent 工具绑定 | ✅ 已发布 | +| **v1.4** | **它能落地场景** | **行业场景模板 + 场景市场 + 自然语言生成工作流 + 跨场景员工画像** | 📋 规划中 | + +--- + +## One More Thing + +我们做 MateClaw,不是为了追赶 ChatGPT、不是为了做下一个 Dify、不是为了融资 PPT 上多一个 buzzword。 + +我们做它,是因为我们相信一件事: + +**AI 不应该是一个网页上的对话框。它应该是你的第二个大脑。** + +它住在你的钉钉里、你的飞书里、你的 Telegram 里。它读过你所有的文档。它记得你三个月前说过的话。它会用你公司的内部工具。它在你睡觉的时候整理记忆。**它能替你跑一整条业务流程**。 + +总有一天,你会忘记它是一个程序。 + +**那一天,就是我们成功的那一天。** + +--- + +*Stay hungry. Stay foolish.* diff --git a/mateclaw-server/src/main/resources/docs/zh/security.md b/mateclaw-server/src/main/resources/docs/zh/security.md new file mode 100644 index 00000000..6fe9a7c5 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/security.md @@ -0,0 +1,565 @@ +# 安全与审批 + +**手能伸得远,但边界清清楚楚。** + +MateClaw 给 Agent 真实的能力——shell 访问、文件写入、浏览器自动化、委托给其他 Agent、通过 MCP 调用远程工具。这是"手能伸得远"那一半。这一页讲的是另一半:**不让强有力的手干蠢事的边界**。 + +- **JWT 认证**——你是谁 +- **Tool Guard(基于规则)**——每个 Agent 被允许做什么 +- **审批工作流**——执行前什么时候需要人来决定 +- **File Guard**——Agent 眼里的文件系统长什么样 +- **工作空间隔离**——每个团队能看到什么 +- **审计日志**——所有人做过的所有事,按时间顺序,永远保留 + +生产环境跑 MateClaw 的话,从头到尾读完这页。 + +::: tip Agentic, but not autonomous +每个公司的 IT 部门和 CISO 在买 AI 之前问的同一个问题: + +> **"它会不会跑飞了,删了我不该删的东西?"** + +任何说"AI 不会跑飞"的人都在骗你。MateClaw 的答案不一样——**敏感操作问你一句再执行。** + +Agent 想删文件、发邮件、跑写入型 SQL、调付费 API——任何一条 Tool Guard 规则匹配上的调用,会**在回合中途暂停**,审批通知推到你的 IM(飞书 / 钉钉 / Slack / 邮件),你点批准,Agent 从暂停的地方接着跑。每一个动作进 `mate_tool_guard_audit_log`——按时间序、永远保留、可导出 CSV。 + +**会动手(agentic),但不擅自动手(not autonomous)。** + +这是「让 AI 替你干活」和「让 AI 替你做主」之间那条线。MateClaw 站在线的左边——也是你的 CISO 第一次不会一上来就否决的那一边。 +::: + +--- + +## JWT 认证 + +### 怎么工作的 + +1. 用户往 `/api/v1/auth/login` 提交凭证 +2. 服务器校验之后返回一个 JWT +3. 之后所有请求在 `Authorization` header 里带 token +4. 服务器在每个请求上校验 token + +### 登录 + +```bash +curl -X POST http://localhost:18088/api/v1/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username": "admin", "password": "admin123"}' +``` + +响应: + +```json +{ + "code": 200, + "data": { + "token": "eyJhbGciOiJIUzI1NiJ9...", + "tokenType": "Bearer", + "expiresIn": 86400 + } +} +``` + +### 修改密码 + +用户可以在个人设置里修改自己的密码。管理员可以在成员管理里重置任何成员的密码。 + +--- + +### 滑动窗口续签 + +MateClaw 实现了滑动窗口 token 续签。当 token 剩余有效期低于 `renewal-threshold`(默认 2 小时 / 7200000ms)时,服务器在响应头 `X-New-Token` 里发一个新 token。前端自动拿到新 token 替换旧的,**用户感知不到**。活跃用户不会被踢下线;空闲会话该过期还是过期。 + +### 配置 + +```yaml +mateclaw: + auth: + jwt: + secret: your-secret-key-must-be-at-least-32-characters-long + expiration: 86400000 # 24 小时,毫秒 + sliding-window: true +``` + +::: warning +**生产环境必须改默认 JWT secret。** 至少 32 字符。用环境变量(`JWT_SECRET=...`)设置,**不要 commit**。 +::: + +### 错误码 + +| 状态码 | 含义 | 响应 | +|--------|------|------| +| 401 | Token 缺失、过期或无效 | `{"code": 401, "message": "Unauthorized"}` | +| 403 | Token 有效但权限不足 | `{"code": 403, "message": "Forbidden"}` | + +前端统一处理——跳登录页、清空存储的 token。 + +### 默认凭证 + +MateClaw 出厂带 `admin` / `admin123`。**除了你自己笔记本之外的任何部署都必须立刻改。** + +### Spring Security 配置 + +- **无状态会话**——服务端不存 session;所有状态都在 JWT 里 +- **公共端点**——`/api/v1/auth/login`、`/h2-console/**`、`/swagger-ui/**` +- **受保护端点**——`/api/v1/**` 下的其他所有路径 +- **CSRF 关闭**——无状态 JWT 不需要 + +--- + +## Tool Guard —— 基于规则的权限引擎 + +Tool Guard 是 MateClaw 决定一次工具调用被允许做什么的机制。**它不是一个扁平的"危险工具清单"。** 它是一个规则引擎。每条规则说:*对这个工具,可选匹配这些参数,在这个工作空间里,做 X*——X 是 `allow`、`deny`、或 `require_approval`。 + +### 三张表 + +| 表 | 用途 | +|----|------| +| **`mate_tool_guard_config`** | 全局配置——开关、默认策略、审批超时、通知渠道 | +| **`mate_tool_guard_rule`** | 单条规则——工具模式、可选参数正则、工作空间范围、动作、优先级 | +| **`mate_tool_guard_audit_log`** | 每一次受守护的调用一条记录——工具、参数、匹配的规则、决定、用户、时间戳 | + +### 一条规则是怎么被评估的 + +``` +收到工具调用 + │ + ▼ +加载这个工作空间 + 全局的所有规则,按优先级排序 + │ + ▼ +按优先级遍历每条规则: + ┌─ 工具名匹配模式吗? + │ └─ 不 → 下一条 + ├─ 参数模式匹配吗(如果有)? + │ └─ 不 → 下一条 + └─ 都匹配 → 执行这条规则的动作,停 + │ + ▼ +没有规则匹配 → 执行默认策略 + │ + ▼ +动作:allow / deny / require_approval + │ + ▼ +写一条审计日志 + │ + ▼ +执行 / 拒绝 / 挂起等审批 +``` + +优先级更高的规则先执行。**第一个匹配的规则赢**。一条规则可以限定到特定的工作空间,也可以是全局的。 + +### 示例规则 + +``` +规则 1(优先级 100):ShellExecuteTool,参数匹配 "^(ls|cat|grep|find)\\s" → allow +规则 2(优先级 50): ShellExecuteTool → require_approval +规则 3(优先级 50): WriteFileTool,arg.path 以 "/tmp" 开头 → allow +规则 4(优先级 40): WriteFileTool → require_approval +规则 5(优先级 30): * → allow(默认) +``` + +只读的 shell 命令立刻执行。其他的需要审批。`/tmp` 下的文件写入自由;其他地方需要审批。其他所有工具放行。 + +### 管理规则 + +`设置 → 安全与审批 → Tool Guard 规则` 提供完整 UI。或者走配置文件: + +```yaml +mateclaw: + tool: + guard: + enabled: true + default-policy: require_approval + rules: + - tool: ShellExecuteTool + arg-pattern: "^(ls|cat|grep|find)\\s" + action: allow + priority: 100 + - tool: ShellExecuteTool + action: require_approval + priority: 50 + - tool: WriteFileTool + arg-pattern: "^/tmp/" + action: allow + priority: 50 + - tool: WriteFileTool + action: require_approval + priority: 40 +``` + +或者走 API: + +```bash +curl -X POST http://localhost:18088/api/v1/security/guard/rules \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "tool": "ShellExecuteTool", + "argPattern": "^(ls|cat|grep|find)\\s", + "action": "allow", + "priority": 100 + }' +``` + +### 危险模式检测 + +除了用户定义的规则之外,MateClaw 的 shell 工具内置了一套危险模式检测——不管你的规则怎么写,有些模式本身就是危险的。`find -delete`、`rm -rf /`、用管道把 `bash` 接到下载上之类的模式,**即使有规则本来会 allow,也会强制触发更高级别的审批**。 + +--- + +## 审批工作流 —— 人在回路 + +当一条规则评估为 `require_approval` 时,MateClaw 不会简单地让调用失败。它会**在回合中途挂起 Agent**,创建一条 pending approval,呈现给用户,等用户决定之后**从暂停的地方恢复执行**。 + +::: tip 1.3.0 起:工作流也走同一套审批 +v1.3.0 的 [工作流](./workflow) `await_approval` step 通过同一套 `mate_tool_approval` 表挂起整条 workflow run,跨服务重启不丢;审批结果通过 channel 通知(飞书 / 钉钉 / Slack / 企微)推回审批人,resolve 后 workflow runtime 自动 resume 下一 step。也就是说——同一份审计、同一份通知、同一种"暂停—恢复"语义,同时覆盖 Agent 工具调用和 workflow step。 +::: + +### 工作流 + +``` +Agent 调用工具 + │ + ▼ +Tool Guard:require_approval + │ + ▼ +创建 mate_tool_approval 行(status=pending) + │ + ▼ +图状态里 AWAITING_APPROVAL=true + │ + ▼ +发出 approval_required SSE 事件 + │ + ▼ +图干净地终止 + │ + ▼ +前端显示审批卡片 + │ + ▼ +用户点 Approve 或 Reject + │ + ▼ +POST /api/v1/approvals/{id}/resolve + │ + ├─ Approved → 重新加载 Agent,replay 工具调用,继续推理 + └─ Rejected → 把拒绝作为 observation 返回,继续推理 +``` + +"replay" 机制很重要。Agent 恢复时**不会从头重新推理**——它直接跳到已经批准的工具调用、执行、从观察继续。**没有重复的 LLM 调用,没有浪费的 token。** + +### `mate_tool_approval` 表 + +| 列 | 用途 | +|----|------| +| `id` | 主键 | +| `agent_id` | 哪个 Agent 在等 | +| `conversation_id` | 哪个会话被挂起 | +| `tool_name` | 要调的工具 | +| `tool_args` | 实际参数的 JSON | +| `rule_id` | 触发审批的规则 | +| `status` | `pending` / `approved` / `rejected` / `expired` | +| `requested_at` | 审批被创建的时间 | +| `resolved_at` | 用户决定的时间 | +| `resolved_by` | 谁决定的 | +| `notes` | 决定时可选的备注 | + +### 占位符替换 + +有时候 Agent 的工具参数里带占位符——一个被计算出来的文件路径、一条带模板的命令。审批工作流**在弹出对话框之前就替换完占位符**,用户看到的是**真正的值**。审批同样返回替换后的值,Agent 执行的**就是**用户看到的。 + +### 超时 + +Pending approval 在一个可配置的超时后过期(默认 10 分钟)。过期的审批变成 `rejected`,Agent 把这个过期当作用户的拒绝一样对待。 + +### 通知 + +MateClaw 可以通过 `channel/notification/` 适配器通知——邮件、应用内提醒、钉钉/飞书推送。在 `设置 → 安全与审批 → 通知` 里配置。 + +### API 方式处理审批 + +```bash +# 列出 pending 审批 +curl http://localhost:18088/api/v1/approvals?status=pending \ + -H "Authorization: Bearer " + +# 批准 +curl -X POST http://localhost:18088/api/v1/approvals/123/resolve \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"decision": "approved"}' + +# 拒绝并带原因 +curl -X POST http://localhost:18088/api/v1/approvals/123/resolve \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"decision": "rejected", "notes": "这个工作空间不适合"}' +``` + +--- + +## File Guard + +File Guard 是文件系统级的访问控制。它坐在读写文件的任何工具或技能下面,决定哪些路径在边界内。 + +### 评估管道 + +``` +文件访问请求 + │ + ▼ +路径规范化(解析 ..、符号链接、相对路径) + │ + ▼ +白名单检查:路径在允许的目录里吗? + │ + ▼ +黑名单检查:路径在被拒的目录里吗? + │ + ▼ +符号链接检查:顺着链接走会跳出沙盒吗? + │ + ▼ +允许 / 拒绝 +``` + +### 内置规则 + +| 规则 | 说明 | +|------|------| +| 工作空间隔离 | 默认访问限定在工作空间目录内 | +| 系统路径拒绝 | `/etc`、`/usr`、`/bin`、`/boot` 等默认拒绝 | +| 敏感文件保护 | `.ssh`、`.config`、`.env` 拒绝 | +| 路径穿越防护 | `../` 攻击被检测和阻止 | +| 符号链接检查 | 符号链接的目标被解析并重新校验 | + +### 配置 + +```yaml +mateclaw: + security: + file-guard: + enabled: true + allowed-paths: + - "${user.dir}/workspace" + - "${java.io.tmpdir}/mateclaw" + denied-paths: + - "/etc" + - "/usr" + - "${user.home}/.ssh" + - "${user.home}/.config" + - "${user.home}/.env" +``` + +可视化编辑器在 `设置 → 安全与审批 → File Guard`。 + +--- + +## 工作空间隔离 + +工作空间是 MateClaw 把多个团队的数据隔开的方式。每个 Agent、技能、Wiki、会话、记忆文件都**属于且只属于一个工作空间**。 + +### 沿工作空间边界生效的安全基元 + +- **File Guard**——路径白名单默认是 `workspace/{workspaceId}/...` +- **Tool Guard 规则**——可以限定到特定的工作空间 +- **Wiki 知识库**——归属于工作空间,只有成员能读 +- **记忆文件**——每个 Agent 的记忆在它工作空间的目录下面 +- **渠道**——每个渠道归属于一个工作空间 + +### 角色 + +| 角色 | 能做什么 | +|------|----------| +| **Owner** | 所有事,包括删除工作空间 | +| **Admin** | 除了删除工作空间或变更 owner 之外的所有事 | +| **Member** | 用 Agent、读写 wiki、创建会话 | +| **Viewer** | 只读——看得到 Agent 和 KB,不能创建或修改 | + +完整细节在 [工作空间](./workspaces)。 + +### 工作空间隔离**不**覆盖的 + +- **共享的全局配置**——JWT secret、模型 provider key、MCP 服务定义都是全局的 +- **审计日志的跨工作空间访问**——带权限的安全管理员可以跨所有工作空间查询审计事件 + +--- + +## 审计日志 + +每一个安全相关的动作都被记在 `mate_audit_event`。**仅追加**——你不能改一条已有的记录,按配置的窗口(默认 90 天)保留。 + +### 记什么 + +| 事件类型 | 捕获的数据 | +|----------|------------| +| **工具调用** | 工具名、参数、结果摘要、耗时、Agent、工作空间 | +| **Tool Guard 决定** | 匹配的规则、执行的动作、规则 ID | +| **审批** | 谁批准/拒绝、什么时候、备注 | +| **File Guard 决定** | 路径、允许/拒绝、原因 | +| **技能执行** | 技能名、参数、Agent | +| **登录事件** | 用户、IP、成功/失败 | +| **配置变更** | 安全相关设置的旧值和新值 | + +### 记录结构 + +``` +timestamp 什么时候发生 +user_id 谁做的(自动事件是 system) +action 做了什么 +resource 对什么做的 +details 具体细节的 JSON +result success / failure / denied +ip_address 源 IP(可用时) +workspace_id 属于哪个工作空间 +``` + +### 查询 + +`设置 → 安全与审批 → 审计日志`:按时间范围、事件类型、用户、工作空间、结果过滤。导出 CSV。 + +API: + +```bash +curl "http://localhost:18088/api/v1/audit/events?from=2026-04-01&to=2026-04-11&action=tool_call" \ + -H "Authorization: Bearer " +``` + +--- + +## 技能安全扫描 + +自定义技能在变活之前会被扫描危险模式: + +| 检查 | 找什么 | +|------|--------| +| **Prompt 注入** | 覆盖 system prompt 的企图、隐藏指令 | +| **危险工具引用** | 不在允许列表里的工具,或请求了需要审批却没声明的工具 | +| **外部 URL 引用** | 技能正文里指向不可信外部资源的链接 | +| **脚本注入** | 嵌入的脚本或代码执行企图 | + +### 严重级别 + +| 级别 | 动作 | +|------|------| +| `CRITICAL` | 安装被阻止;必须修好 | +| `HIGH` | 警告 + 管理员必须确认 | +| `MEDIUM` | 显示警告;允许安装 | +| `LOW` | 仅记录 | +| `INFO` | 仅记录 | + +扫描报告在 `设置 → 安全与审批 → 技能扫描` 里。 + +--- + +## API Key 保护 + +- API keys 在数据库里**加密存储** +- Keys 在所有 API 响应里**脱敏显示**(`sk-****abcd`)——创建之后永远不会完整返回给前端 +- MCP 服务的 `env_json` 和 `headers_json` 值按同样方式脱敏 +- MCP 配置里的环境变量引用(`${VAR}`)在运行时从进程环境解析 + +--- + +## 网络安全 + +### 生产建议 + +| 建议 | 细节 | +|------|------| +| **HTTPS** | 用反向代理 + TLS(Nginx 或 Caddy) | +| **关掉 H2 console** | 生产环境 `spring.h2.console.enabled=false` | +| **防火墙** | 只开放对外端口 | +| **限流** | 在反向代理层配置 | +| **MySQL,不是 H2** | 生产用独立的 MySQL 8 实例 | + +### Nginx 反向代理示例 + +```nginx +server { + listen 443 ssl; + server_name mateclaw.example.com; + + ssl_certificate /etc/ssl/certs/mateclaw.pem; + ssl_certificate_key /etc/ssl/private/mateclaw.key; + + location / { + proxy_pass http://localhost:18080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # SSE 支持 + proxy_buffering off; + proxy_read_timeout 86400s; + } +} +``` + +--- + +## 安全最佳实践 + +1. **改默认密码。** 现在就改。每一个部署都改。 +2. **设一个真正的 JWT secret。** 至少 32 字符,通过环境变量,永远不要 commit。 +3. **最小权限。** 只启用 Agent 真的需要的工具。 +4. **默认 `require_approval`。** 把 Tool Guard 的 `default-policy` 翻成 `require_approval`,然后为安全场景加 `allow` 规则。**新加的工具默认安全**。 +5. **配好 File Guard。** 在任何 Agent 真的碰文件系统之前把 allowed/denied 路径锁死。 +6. **定期看审计日志。** 设个定时提醒。找异常。 +7. **盯住技能扫描。** CRITICAL 发现不该被轻易绕过。 +8. **网络隔离。** Ollama、H2 console、内部 MCP 服务——这些都不该对公网可达。 +9. **生产环境别跳过审批。** 自动批准规则应该**窄而具体**。`allow *` 是定时炸弹。 + +--- + +## 安全配置参考 + +```yaml +mateclaw: + auth: + jwt: + secret: ${JWT_SECRET:your-secret-key-at-least-32-chars} + expiration: 86400 + sliding-window-ratio: 0.5 + + tool: + guard: + enabled: true + default-policy: require_approval + approval-timeout-seconds: 600 + notifications: + email-enabled: false + dingtalk-enabled: false + + security: + file-guard: + enabled: true + allowed-paths: + - "${user.dir}/workspace" + denied-paths: + - "/etc" + - "${user.home}/.ssh" + + audit-log: + enabled: true + retention-days: 90 + + skill: + security-scan: + enabled: true + block-critical: true +``` + +--- + +## 下一步 + +- [工具系统](./tools)——工具细节和 Tool Guard 规则模式 +- [技能系统](./skills)——技能安全扫描细节 +- [工作空间](./workspaces)——工作空间隔离基元 +- [Agent 引擎](./agents)——审批如何挂起并恢复 Agent 回合 +- [配置说明](./config)——完整配置参考 diff --git a/mateclaw-server/src/main/resources/docs/zh/skills.md b/mateclaw-server/src/main/resources/docs/zh/skills.md new file mode 100644 index 00000000..aa7773f7 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/skills.md @@ -0,0 +1,527 @@ +# 技能系统 + +**一个技能是一个"用句子思考"的工具。** + +工具是原子的——读一个文件、发一个 HTTP 请求、跑一个命令。技能是**组合**——"调研这个话题然后写一份简报"、"审这段代码并给出评论"、"把我的 git log 变成 standup 汇报"。一个技能是一份 `SKILL.md` 文件,把指令、参数、prompt 模板、可选脚本和需要的工具列表组合在一起。 + +如果工具是手,那技能就是**菜谱**。 + +--- + +## 五种技能 + +| 类型 | 来源 | 维护者 | +|------|------|--------| +| **`builtin`** | 跟着 MateClaw 一起发布 | 核心团队 | +| **`custom`** | 你通过 UI、API 或直接往工作空间扔文件创建 | 你自己 | +| **`dynamic`** | Agent 在工作中自动合成 | Agent + 你的审批 | +| **`mcp`** | 由 MCP 服务暴露的工具支撑(同名技能会被 `custom` 真技能覆盖) | MCP 服务作者 | +| **`acp`** | 桥接到外部 Agent 客户端协议端点(Claude Code、Codex 等) | 上游 Agent 服务 | + +全都走同一条运行时管道。唯一的区别是来源。 + +--- + +## SKILL.md 协议 + +每个技能是一份带 YAML frontmatter 的 Markdown 文件。**frontmatter 是契约,正文是 prompt。** + +```markdown +--- +name: web-researcher +title: 网页调研员 +description: 搜索网页并对给定话题做总结 +version: 1.0.0 +type: custom +author: your-name +tools: + - WebSearchTool + - ReadFileTool +tags: + - research + - search +parameters: + - name: topic + type: string + required: true + description: 调研的话题 + - name: depth + type: string + required: false + default: brief + description: 详细程度(brief, detailed, comprehensive) +--- + +# 网页调研员 + +你是一个网页调研助手。给你一个话题时,你应该: + +1. 用 WebSearchTool 搜索关于 {{topic}} 的相关信息 +2. 评估信息源的可信度 +3. 把发现整理成 {{depth}} 级别的总结 +4. 在回答里包含来源 URL + +## 输出格式 + +把你的发现按这个格式呈现: +- **总结**:2–3 句话概述 +- **关键事实**:要点列表 +- **来源**:带编号的 URL 列表 +``` + +注意两件事。第一,正文是一份 prompt——不是对 prompt 的**描述**。它是技能在运行时会对 Agent 说的话。第二,`tools:` 列表是一份契约:运行时保证这些工具在技能运行时可用。 + +### Frontmatter 字段 + +| 字段 | 必填 | 用途 | +|------|------|------| +| `name` | ✅ | 唯一标识符(kebab-case) | +| `title` | ✅ | 人类可读的显示名 | +| `description` | ✅ | 一句话总结 | +| `version` | ✅ | 语义化版本 | +| `type` | ✅ | `builtin`、`custom`、`mcp` | +| `author` | — | 作者 | +| `tools` | — | 技能需要的工具名列表 | +| `tags` | — | 分类标签 | +| `parameters` | — | 类型化的输入参数 | + +### 参数 schema + +| 字段 | 必填 | 用途 | +|------|------|------| +| `name` | ✅ | 参数名(`{{name}}` 插值时用) | +| `type` | ✅ | `string`、`number`、`boolean`、`array` | +| `required` | — | 是否必须提供(默认 false) | +| `default` | — | 调用者省略时的默认值 | +| `description` | ✅ | 这个参数控制什么 | + +--- + +## 运行时管道 + +``` +1. RESOLVE 在 mate_skill 里按名字查技能 + │ + ▼ +2. VALIDATE 检查必填参数都提供了 + │ + ▼ +3. RENDER 替换 SKILL.md 正文里的 {{parameter}} 占位符 + │ + ▼ +4. INJECT 把渲染后的指令追加到 Agent 的 system prompt + │ + ▼ +5. BIND TOOLS 校验技能要求的工具都可用;缺任何一个就早失败 + │ + ▼ +6. EXECUTE Agent 用增强后的 prompt 和绑定的工具处理任务 +``` + +技能默认**不跑脚本**——它在调用期间**塑形 Agent 的行为**。例外是自带脚本的技能——`SkillScriptTool` 可以执行技能捆绑的脚本文件,走 Tool Guard。 + +### 模板渲染 + +技能正文支持 `{{parameterName}}` 占位符。参数 `{topic: "量子计算", depth: "detailed"}` 下: + +```markdown +用 {{depth}} 级别的详细程度调研话题 "{{topic}}"。 +``` + +…渲染成: + +```markdown +用 detailed 级别的详细程度调研话题 "量子计算"。 +``` + +缺失参数回退到默认值。未知占位符原样保留。 + +--- + +## 技能存储 + +数据库是真相源,文件系统是物化缓存。这条规则对 **SKILL.md** 一直成立,**v1.3 起对 scripts/ 和 references/ 也成立**。 + +### 数据库:`mate_skill` + `mate_skill_file` + +`mate_skill`——技能身份与正文: + +| 列 | 用途 | +|----|------| +| `id` | 主键 | +| `name` | 唯一名 | +| `title` | 显示标题 | +| `description` | 一句话总结 | +| `type` | `builtin` / `custom` / `mcp` | +| `content` | 完整的 `SKILL.md` 内容 | +| `version` | 语义化版本 | +| `enabled` | 开关 | +| `tags` | JSON 数组 | +| `create_time` / `update_time` | 时间戳 | + +`mate_skill_file`(v1.3 新表,迁移 `V112`)——bundle 文件的**权威副本**: + +| 列 | 用途 | +|----|------| +| `id` | 主键 | +| `skill_id` | 外键到 `mate_skill` | +| `file_path` | `scripts/run.py` 或 `references/cfg.md` 这种相对路径 | +| `content` | UTF-8 文本(单文件 ≤1 MB,bundle ≤50 MB) | +| `content_size` | 字节数(不用拉 blob 就能列) | +| `sha256` | 内容指纹,给同步器做幂等 diff | + +### 文件系统:技能工作空间 + +``` +~/.mateclaw/skills/ +├── translate/ +│ ├── SKILL.md # 技能定义 +│ ├── references/ # 参考资料 +│ └── scripts/ # 可选的可执行脚本 +├── code-review/ +│ ├── SKILL.md +│ └── ... +└── .archived/ # 归档的旧版本 + └── translate-20260401-143000/ +``` + +把它想成"**Maven 本地仓库,但用来存技能**"——区别是"本地仓库"现在能从数据库 hydrate 出来。 + +### 启动时自动同步 + +启动时跑两遍同步,保证每个节点拿到的都是最新的: + +1. `SkillWorkspaceBootstrapRunner` → `BundledSkillSyncer` 扫描 classpath 的 `skills/` 目录,把**捆绑技能**同步到工作空间根。**只在目标目录不存在时同步**,不会覆盖本地修改。 +2. `SkillFileSyncer` 比对 `mate_skill_file`(DB)与本地工作空间(FS),按 `sha256` 增量物化缺失或过期的文件。 + +**多实例部署的意义**:一个节点上传 zip,DB row 与 file rows 写入;其他节点重启或调一次 `POST /api/v1/skills/{id}/sync-files` 就能拿到完整 bundle,不用 NFS、不用脚本拷贝、桌面端跨机也能接力。 + +> 升级路径:v1.3 之前的安装在 FS 有文件但 DB 没 row。`SkillFileSyncer` 第一次启动会**从磁盘回填**到 `mate_skill_file`,之后两边保持同步。 + +### 鲁棒的 zip 安装 + +第三方打包者千奇百怪——有人把 `setup.sh` 直接放 zip 根,有人 `scripts/` 排在 `SKILL.md` 之前。`ZipSkillFetcher` v1.3 起: + +- **两遍扫描**——先把所有条目缓存(受 50 MB 上限保护),定位 `SKILL.md` 算出 wrapper 前缀,再分类。**条目顺序不再影响结果**。 +- **根目录扩展名兜底**——SKILL.md 同级的非约定文件按扩展名归类:`.sh / .py / .js / .rb / ...` → `scripts/`,`.md / .json / .yaml / .csv / ...` → `references/`,未识别扩展名落 `WARN` 日志。 +- **写后裁剪 + 空 bundle 守卫**——重装时**先写新文件再裁剪不在新 bundle 里的旧文件**。如果新 bundle 某个桶(`scripts/` 或 `references/`)一个条目都没有,**保留磁盘上的旧文件**——一个解析失败的损坏 zip 不会再把你的 skill 擦干净。要强制清空就传 `forcePrune=true`。 + +> 这道门管得住的实际场景:上次实测中腾讯会议 zip 的 `setup.sh` 在根(不在 `scripts/` 子目录),旧 extractor 静默丢弃;新 extractor 自动归到 `scripts/setup.sh`,安装完直接可跑。 + +### 配置 + +```yaml +mateclaw: + skill: + workspace: + root: ${user.home}/.mateclaw/skills + auto-init: true + delete-policy: archive + bundled-skills-path: skills +``` + +--- + +## 技能市场(以及 ClawHub) + +**技能市场** 页面(`/skills`)是你浏览、安装、编辑、管理技能的地方。三个来源: + +- **内置**——MateClaw 出厂带的技能 +- **你的自定义技能**——你创建或上传的 +- **ClawHub**——一个社区技能库,浏览上千个社区技能、预览、一键安装 + +ClawHub 是**可选**的——离线或不想用外部技能,就别碰那个 tab。 + +--- + +## 技能市场 API + +```bash +# 列出所有技能 +curl http://localhost:18088/api/v1/skills \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# 创建一个自定义技能 +curl -X POST http://localhost:18088/api/v1/skills \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "code-reviewer", + "title": "代码审查员", + "description": "审查代码,找 bug、风格问题、改进点", + "type": "custom", + "content": "---\nname: code-reviewer\n...", + "tags": ["development", "review"] + }' + +# 启用 / 禁用 +curl -X PUT http://localhost:18088/api/v1/skills/1 \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -d '{"enabled": true}' + +# 删除 +curl -X DELETE http://localhost:18088/api/v1/skills/1 \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" +``` + +删除策略可配——默认把技能工作空间移到 `.archived/` 而不是清掉。 + +--- + +## 写一个自定义技能 —— 一步一步 + +1. **决定技能干什么。** 一句话。 +2. **列出它需要的工具。** 三个以内是个好目标。 +3. **写参数。** 必填的在前,可选的带默认值。 +4. **写正文。** 直接对 Agent 说话:*"你是 X。给你 Y 时,做 Z。"* +5. **上传**——通过技能市场 UI 或 API。 +6. **绑定**到一个或多个 Agent。 +7. **测试**——发一条应该触发这个技能的消息。 + +示例——"每日站会"技能: + +```markdown +--- +name: daily-standup +title: 每日站会生成器 +description: 根据最近的 git 活动生成站会汇报 +version: 1.0.0 +type: custom +tools: + - ShellExecuteTool +parameters: + - name: repo_path + type: string + required: true + description: git 仓库路径 +--- + +# 每日站会生成器 + +通过分析最近的 git 活动生成站会汇报。 + +## 步骤 + +1. 在 {{repo_path}} 目录下执行 `git log --oneline --since="yesterday" --author=$(git config user.name)` +2. 总结完成的工作 +3. 识别所有 work-in-progress 分支 +4. 按以下格式输出站会汇报: + - **昨天**:完成了什么 + - **今天**:根据未合并的分支计划做什么 + - **阻塞**:合并冲突或失败的测试 +``` + +--- + +## 工作空间隔离 + +每个工作空间都有自己的一份技能副本。给某个工作空间启用一个技能时,它的文件被 stage 到那个工作空间的目录下、技能的工具被 scope 到这个工作空间、技能写任何文件都在工作空间边界内。见 [工作空间](./workspaces)。 + +--- + +## 自动技能合成 + +Agent 用得多了,会发现自己反复在做同样的事——某种特定的数据库查询方式、某种报表格式、SSH 到你机器的命令序列。Agent 能**主动把这些套路变成技能**。 + +工作流程: + +1. Agent 在任务执行中识别出一个可复用的模式 +2. Agent 提议创建一个新技能(创建 / 编辑 / 修补 / 删除) +3. 你在 ChatConsole 里审批——看内容、改名字、确认或拒绝 +4. 审批通过后,技能自动保存为 `dynamic` 类型,下次直接复用 + +**安全扫描在保存前自动执行**——危险模式(prompt 注入、脚本注入)会被阻断。技能可以跨 Agent 迁移,也可以打包成 ZIP 分享。 + +Agent 的记忆和你一起长大。不用再反复说"记住我喜欢按这个格式排表格"。 + +--- + +## 模板向导:从起步模板开始 + +不会写 SKILL.md?打开向导。 + +`技能 → 创作向导`: + +1. 选一个**起步模板**(8 个:调研员、代码审查员、写作助手、客户支持脚本、数据分析、Claude Code helper、Codex helper、空白模板) +2. 填变量——名字、参数、几句描述 +3. 上传任何辅助文件(脚本、参考资料、prompt 片段) +4. 设置密钥(API key 之类的)——**密钥进密钥库,不进 SKILL.md** +5. 保存 + +你得到的不是一份 SKILL.md,是一个**多文件 bundle**——SKILL.md、references/、scripts/、密钥引用,全在一起。 + +--- + +## 安装前的 Preflight 检查 + +技能装进来不一定能用——它可能需要某个 API key、某个 CLI 工具、某个 MateClaw 开关被打开。 + +之前你装完一跑才发现少东西。现在: + +**Pre-flight install 对话框**——技能在变活之前自动跑一遍前置检查: + +- 需要的工具在不在 +- 需要的 API key 配了没 +- 需要的 feature flag 开了没 +- 依赖的 MCP / ACP 端点连得上吗 + +少什么直接告诉你,提供一键 **`[Set Up]`** 按钮跳到对应配置页。**不再装完才报错让你自己 debug。** + +--- + +## LESSONS.md:让技能从经验里学习 + +每个技能可以带一份 `LESSONS.md`——技能在执行中学到的经验。 + +- 技能跑完一次,可以**主动写一行 lesson**:"上次用户在这种格式下不满意,下次别这样" +- 下次同名技能再被调用,LESSONS 自动注入到 prompt 上下文 +- 用得越多,技能越知道**什么时候该出场、什么时候该闭嘴** + +这是技能"自我进化"的第一版。技能从一组指令,变成有套路、有经验、能成长的东西。 + +LESSONS 在技能详情抽屉的 **Memory tab** 里查看和编辑。 + +--- + +## 密钥(Secrets):把 token 放对地方 + +很多技能要 API 凭证才能跑——腾讯会议要 `TENCENT_MEETING_TOKEN`、Slack 要 bot token、Linear 要 personal API key。这些东西**不能写进 SKILL.md**(会进 prompt,泄露给 LLM)、不能写进 scripts(commit 到代码就完了)、改 `~/.zshrc` 又要重启服务器、桌面端跨机更带不走。 + +v1.3 起,每个技能自带一个 **per-skill 密钥仓**。 + +### 在 UI 里管 + +技能详情抽屉 → **密钥** tab。一张表加一个表单: + +``` +键名 值 最近更新 操作 +TENCENT_MEETING_TOKEN sk••••ef 2026-05-12 [改] [删] + +[+ 新增密钥] +``` + +- **明文永不出后端**——列表只回 `preview`(`sk••••ef` 这种 mask),新增/修改弹窗的值字段从空开始,提交即覆写。 +- **客户端预校验**——key 必须 `^[A-Za-z_][A-Za-z0-9_]{0,127}$`,错的 key 在浏览器就拦掉。 +- **value 字段是 password 型 input + autocomplete=off**——肩窥、截屏、密码管理器都不会沾。 + +### 怎么落盘 / 怎么注入 + +| 阶段 | 怎么做 | +|---|---| +| 写入 | `POST /api/v1/skills/{id}/secrets` `{key, value}` → AES 加密 → `mate_skill_secret` | +| 读出 | 子进程启动前 `SkillScriptService.getDecrypted(skillId)` AES 解密 | +| 注入 | `ProcessBuilder.environment().putAll(...)`——**覆盖父进程同名环境变量** | + +注入语义是**密钥仓里有的覆盖 `.zshrc` 里的;密钥仓里没有的沿用 `.zshrc`**。多人 / 多机部署、桌面端用户、公司域账户互相不串库时,密钥仓是更靠谱的事实源。 + +### REST 接口 + +```bash +# 列(masked) +GET /api/v1/skills/{id}/secrets +# upsert(value 为空等同删除) +POST /api/v1/skills/{id}/secrets {"key":"...", "value":"..."} +# 删 +DELETE /api/v1/skills/{id}/secrets/{key} +``` + +### 一个完整例子:腾讯会议 + +``` +SkillMarket → tencent-meeting-mcp 卡片 → 详情抽屉 → 密钥 tab + → +新增密钥 → key=TENCENT_MEETING_TOKEN, value= + → 保存 + +之后 agent 跑 setup.sh 或 scripts/tencent_meeting.py 时: + ProcessBuilder env 里就有 $TENCENT_MEETING_TOKEN + → mcporter / Python 脚本走腾讯 API → 会议号回来 +``` + +不再需要改 `~/.zshrc`、不需要重启 mateclaw。 + +--- + +## 技能发现:装完就能被用上 + +之前装一个新技能,agent 经常找不到。三个原因,三个修复,v1.3 都补了。 + +### 1) 新技能在 prompt catalog 里**优先** + +agent 的 system prompt 里有一个紧凑的 Skills 表。每个模型按 max input tokens 给一个上限——qwen-turbo 这种 8192 上限的模型只塞 **8 个**。一个全新的技能没有使用记录,原本的 RECOMMENDED 排序会把它埋在 ~40 个老技能后面,进不了 top-8。 + +v1.3 起在排序链最前加一档「**最近 7 天安装的优先**」。装完到周一回来都还在第一屏——足够长,又不会无限期占位。Builtin 与虚拟 MCP/ACP 行不算(你不是"刚装"它们)。 + +### 2) `listAvailableSkills()` 教会 LLM 怎么搜更多 + +工具描述里现在明确写了: + +- 默认页 20 条;如果看到 `Showing: 20 of 47` 这种字样,**用 `keyword=<部分名>` 或 `limit=50` 重试** +- 如果用户报上来一个具体技能名,**别走目录**,直接 `readSkillFile(skillName="", filePath="SKILL.md")` 验证 + +返回结果末尾被截断时也带一行截断提示,小模型也能看见怎么续搜。 + +### 3) skill 名误调成 tool 时**自动重定向** + +LLM 偶尔会把 skill 名当 tool 调(`tencent-meeting-mcp({...})`)。旧版本会返回一段文字提示叫它去 `readSkillFile`——qwen-turbo 这种小模型经常理解不了,回一句"好的我去查"就把回合结束了,进死循环。 + +v1.3 起,`ToolExecutionExecutor` 检测到这种情况且 `readSkillFile` 已绑定到 agent,**透明地代它跑 readSkillFile**,把 SKILL.md 全文(带 `[auto-redirect]` 前缀 + 原始参数回显)作为工具结果返回。模型一看就有 SKILL.md 里的可执行示例可以照抄,下一步直接 `runSkillScript`,不会再卡。 + +> 这条修复对所有小模型都有效,对大模型也无害(它们本来就会看完提示直接 retry)。 + +--- + +## ACP 桥接:把外部编码 Agent 接进来 + +ACP(Agent Client Protocol)是一种把外部 Agent 客户端(Claude Code、Codex、其他兼容客户端)以技能身份接入 MateClaw 的协议。 + +接入之后: + +- ACP 端点**自动桥接成技能卡**——出现在技能页,自带一组包装工具 +- **可视化环境编辑器**——每个 endpoint 需要的 key、URL、CWD 都在 UI 上配 +- **会话级 cwd**——每个 ACP 会话自己的工作目录 +- **错误翻译**——上游 "Request not allowed" 这种话翻成你看得懂的 +- **OAuth 钥匙串劫持检测**——发现 OAuth token 被其他应用占了,提示你重新登录 + +模板:`claude-code-helper`、`codex-helper`——开箱可用。 + +数字员工调用 ACP 技能的方式,和调内置工具没区别。 + +--- + +## 详情抽屉:所有信息一处看 + +每个技能卡片点开是一个抽屉,八个 tab: + +- **概览**——身份字段、manifest 投影、来源、版本 +- **正文**——`SKILL.md` 编辑器(接管整个抽屉宽度) +- **工具**——这个技能用哪些工具(含 effective tool 展开) +- **特性**——能力矩阵 +- **安全**——内容扫描结果、Tool Guard 关联规则 +- **经验**——`LESSONS.md` 内容 +- **密钥**——env-var 形态的凭证(v1.3 新增,详见下方"密钥(Secrets)"小节) +- **记忆**——绑定到这个技能的数字员工 + +卡片本身瘦身——只 6 个字段、一个状态徽章。**清楚比全面重要。** + +--- + +## 安全 + +自定义技能在变活之前会过几道检查: + +- **内容扫描**——上传时 `SKILL.md` 会被扫描 prompt 注入和脚本注入 +- **工具需求检查**——`tools:` 列表只能引用存在的工具 +- **Tool Guard 合规**——列出危险工具的技能继承那些工具的 Tool Guard 规则 +- **MCP 技能约束**——MCP 支撑的技能继承它背后 MCP 服务的安全约束 + +完整的技能安全审核在 [安全与审批](./security) 里。 + +--- + +## 下一步 + +- [工具系统](./tools)——技能能用的工具 +- [Agent 引擎](./agents)——Agent 怎么在回合中调用技能 +- [MCP 协议](./mcp)——MCP 支撑的技能 +- [安全与审批](./security)——技能扫描的细节 diff --git a/mateclaw-server/src/main/resources/docs/zh/tools.md b/mateclaw-server/src/main/resources/docs/zh/tools.md new file mode 100644 index 00000000..91435cc2 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/tools.md @@ -0,0 +1,317 @@ +# 工具系统 + +**一个工具就是 Agent 能伸出去的一只手。** + +把语言模型单独放在那里,它只是一个包在文本里的模式匹配器。它不知道现在几点。它不知道你的文件里写了什么。它不能搜索网页、执行命令、看一份 PDF、把任务交给另一个 Agent、打开一个浏览器。它只能**谈论**做这些事。 + +工具是 MateClaw 解决这件事的方式。每一个工具是一个 Agent 被允许调用的具体操作——读文件、搜网页、执行 shell 命令、从 PDF 抽文字、把任务委托给另一个 Agent。Agent 判断需要某个工具时,发出一次**工具调用**,运行时执行它,结果作为**观察**回到 Agent 下一步推理里。 + +**二十个内置工具**开箱即用。无限多个可以通过 MCP 服务、自定义技能脚本、或者你自己写的 `@Tool` Spring bean 加进来。 + +--- + +## 一次工具调用实际发生了什么 + +``` +Agent 判断需要一个工具 + │ + ▼ + 发出工具调用:{"name": "WebSearchTool", "args": {"query": "..."}} + │ + ▼ + ┌─────────────────────┐ + │ 工具注册表 │ ← 按名字查工具 + └─────────────────────┘ + │ + ▼ + ┌─────────────────────┐ + │ Tool Guard │ ← 基于规则的检查:allow / deny / 审批 + └─────────────────────┘ + │ + ┌────┴────┐ + │ │ + ▼ ▼ + 允许 审批挂起 → 用户决定 → 允许 / 拒绝 + │ + ▼ + ┌─────────────────────┐ + │ 执行(带超时) │ ← 异步,按工具单独设超时 + └─────────────────────┘ + │ + ▼ + 结果 → 观察 → Agent 下一步推理 +``` + +Tool Guard 是守门员。超时是**每个工具独立**的(这样一个慢工具冻不了整个回合)。在一次 Action 阶段里,Agent 同时调多个独立工具时它们可以**并发执行**。 + +这整套对 Agent 的 prompt 是**不可见**的。 + +--- + +## 工具注册的三条路 + +**1. 内置工具。** MateClaw 出厂带的二十个工具,启动时自动注册到工具表里。 + +**2. MCP 服务。** 说 Model Context Protocol 的外部进程动态暴露工具。MateClaw 通过 `tools/list` 发现它们。见 [MCP 协议](./mcp)。 + +**3. 技能脚本。** 技能包可以带可执行脚本,运行时被包装成工具。见 [技能系统](./skills)。 + +工具发现是**黑名单式**的——默认所有可发现的工具都会被注册,需要排除哪个就显式排除。这样新加进来的工具不会因为白名单遗漏被默默忽略。 + +--- + +## 二十个内置工具 + +| 工具 | 作用 | 危险 | +|------|------|------| +| `DateTimeTool` | 获取任意时区的当前日期时间 | — | +| `WebSearchTool` | 通过搜索引擎链搜索(Serper / Tavily / DuckDuckGo / SearXNG) | — | +| `ReadFileTool` | 读文件 | — | +| `WriteFileTool` | 写内容到文件 | ⚠️ | +| `EditFileTool` | 查找替换编辑 | ⚠️ | +| `ShellExecuteTool` | 执行 shell 命令 | ⚠️ | +| `FileTypeDetectorTool` | 检测 MIME 类型和编码 | — | +| `DocumentExtractTool` | 从 PDF / DOCX / XLSX 抽文字 | — | +| `WorkspaceMemoryTool` | 读写 Agent 的工作空间记忆 | — | +| `SkillFileTool` | 读取和管理 `SKILL.md` 文件 | — | +| `SkillScriptTool` | 执行技能脚本 | ⚠️ | +| `SkillManageTool` | 创建 / 编辑 / 删除技能包 | ⚠️ | +| `BrowserUseTool` | 驱动无头浏览器 | ⚠️ | +| `DelegateAgentTool` | 把任务委托给另一个 Agent(支持并行) | — | +| `MateClawDocTool` | 读取内置项目文档 | — | +| `ImageGenerateTool` | 文生图 / **图生图(1.3.0+)** | — | +| `VideoGenerateTool` | 文生视频 / 图生视频 | — | +| `DocxRenderTool` | **1.3.0+** Markdown → .docx(Word 文档) | — | +| `XlsxRenderTool` | **1.3.0+** Markdown 表格 → .xlsx(Excel) | — | +| `PptxRenderTool` | **1.3.0+** Markdown(Marp 风格 `---` 分页) → .pptx | — | +| `PdfRenderTool` | **1.3.0+** Markdown → 出版级 PDF(中文字体内嵌) | — | +| `CronJobTool` | 创建和管理定时任务 | ⚠️ | +| `DatasourceTool` | 管理外部数据源连接 | ⚠️ | +| `SqlQueryTool` | 对已连接数据源执行 SQL 查询 | ⚠️ | + +此外还有 [多模态创作](./multimodal) 的音乐生成工具 `MusicGenerateTool`。以及 [LLM Wiki](./wiki) 的 14 个 Wiki 工具:`wiki_read_page`、`wiki_read_many`、`wiki_list_pages`、`wiki_search_pages`、`wiki_semantic_search`、`wiki_compile_page`、`wiki_trace_source`、`wiki_create_page`、`wiki_delete_page`、`wiki_archive_page`、`wiki_unarchive_page`、`wiki_related_pages`、`wiki_explain_relation`、`wiki_enrich_page`。 + +### DateTimeTool + +返回给定时区的当前日期时间。没有意外。 + +``` +输入:{"timezone": "America/New_York"} +输出:"2026-04-11T14:30:22" +``` + +### WebSearchTool + +通过**供应商链**搜索——DuckDuckGo 和 SearXNG 作为无 Key 的 fallback,Serper 和 Tavily 在你有 Key 时启用。在 `设置 → 系统设置 → 搜索服务` 里配置,**改完即生效,不需要重启**。 + +``` +输入:{"query": "Spring AI Alibaba 最新版本", "freshness": "month", "count": 5} +输出:"Spring AI Alibaba 1.1 发布..." +``` + +特性: + +- **供应商链**——主 provider 失败时链式 fallback 到下一个 +- **高级参数**——`freshness`、`language`、`count` +- **结果缓存**——近期查询被缓存 +- **安全包装**——结果返回前先净化 +- **原生搜索 + 工具搜索共存**——自带搜索的模型用原生搜索,工具搜索作为 fallback + +### ShellExecuteTool + +跨平台 shell 执行。Linux/macOS 用 `/bin/sh -c`,Windows 用 `cmd.exe /D /S /C`。**每一次调用都过 Tool Guard。** + +安全设计: + +- **超时**——默认 60 秒,硬上限 300 秒 +- **输出上限**——stdout 和 stderr 各自上限 10,000 字节 +- **文件支撑输出**——写到临时文件,不是管道 +- **结构化结果**——`{exitCode, stdout, stderr, timedOut}` +- **危险模式检测**——`find -delete`、`rm -rf /`、管道 bash 下载触发更高级别审批 + +### ReadFileTool / WriteFileTool / EditFileTool + +读是安全的。写和编辑都过 Tool Guard。 + +### DocumentExtractTool + +PDF、DOCX、XLSX 之类变成纯文本。扫描件在可用的地方降级到 OCR。 + +### Office 文档生成(1.3.0+) + +四个新工具,把 Markdown 直接渲染为可下载的 Office 文件——**不 fork 子进程,不依赖 npm**。生成的字节缓存在内存里,返回一个一次性下载链接: + +| 工具 | 适用 | 关键能力 | +|---|---|---| +| `DocxRenderTool.renderDocx` | 报告 / 备忘录 / 合同 / 简历 | 标题(# ## ###) / 加粗(**text**) / 列表 / 表格 / 图片(PNG/JPG/GIF/BMP/SVG → PNG) | +| `DocxRenderTool.renderDocxFromFile` | 同上,但 markdown 在工作区文件里 | 用于 LLM 不想把已经写好的大段 markdown 当 tool 参数再发一次 | +| `XlsxRenderTool.renderXlsx` | 财务表 / 数据导出 / 模板 | Markdown 表格语法 → 多 sheet(用 `## SheetName` 切分) | +| `PptxRenderTool.renderPptx` | 演讲稿 / 项目方案 / 简报 | Marp 风格 `---` 分页;`16:9`(默认)/ `4:3` 比例 | +| `PptxRenderTool.renderPptxFromFile` | 同上,markdown 在文件里 | 内容大于 5KB 时优选 | +| `PdfRenderTool.renderPdf` | 出版级文档 / 周报 / 制式文件 | 1in 边距 / 智能分页 / 页码 / 封面页 / 中英混排(CJK 字体内嵌) | + +::: tip 跟原有 `skills/docx` 的关系 +现成的 `skills/docx` skill **保留**——它擅长的是**编辑已有 .docx**(tracked changes / 复杂 XML 操作)和首次安装时跑 `npm install docx`。新四个工具专门处理"从零创建"路径,**没有 npm 启动延迟**。Agent 首选这四个 RenderTool;要修已有 .docx 才转回 skill。 +::: + +### ImageGenerateTool —— 1.3.0 起支持图像编辑 + +v1.2.0 时这个工具只能"文生图"。v1.3.0 起新增 `image` / `images` 两个参数,支持**多图输入的图像编辑**。详见 [多模态创作](./multimodal#image-edit)。 + +### WorkspaceMemoryTool + +让 Agent 读、写、编辑自己的工作空间记忆文件——`workspace/{agentId}/` 下面的任何 `.md`。见 [记忆系统](./memory)。 + +### BrowserUseTool + +驱动一个无头浏览器。每次调用都过 Tool Guard。 + +### DelegateAgentTool —— Agent 之间委托 + +一个 Agent 可以把子任务交给另一个 Agent: + +- **`delegateToAgent(agentName, task)`**——按名字调用指定 Agent,在隔离会话里执行任务 +- **`listAvailableAgents()`**——列出所有可用 Agent + +``` +用户:搜一下 Spring AI 的新闻,让 Writer 总结一下 +Agent A:[调 WebSearchTool] + [调 delegateToAgent(agentName="Writer", task="总结:...")] + [收到 Writer 的回复] + 合并后回复用户 +``` + +安全: + +- **递归上限**——委托最多嵌套 3 层 +- **隔离会话**——被委托的 Agent 跑在自己的会话里 +- **结果截断**——委托结果上限 4000 字符 + +### MateClawDocTool + +读取内置的 MateClaw 项目文档。让 Agent 回答"MateClaw 里 X 是怎么工作的"这种问题时,**去查真文档**而不是猜。 + +--- + +## Tool Guard —— 权限层 + +Tool Guard 是 MateClaw 不让强工具干蠢事的机制。它是**基于规则的**,不是一个扁平的"危险 / 不危险"清单。每条规则说:*对这个工具,带这些参数,在这个上下文里,做 X*——X 是 `allow`、`deny`、或 `require_approval`。 + +核心几张表: + +- **`mate_tool_guard_rule`**——单条规则 +- **`mate_tool_guard_config`**——全局配置 +- **`mate_tool_guard_audit_log`**——每一次受守护的调用一条记录 + +示例规则:*`ShellExecuteTool`,命令以 `ls`、`cat`、`grep`、`find` 开头时允许。其他情况要求审批。* + +```yaml +mateclaw: + tool: + guard: + enabled: true + default-policy: require_approval + rules: + - tool: ShellExecuteTool + arg-pattern: "^(ls|cat|grep|find)\\s" + action: allow + - tool: WriteFileTool + action: require_approval +``` + +或者在 `设置 → 安全与审批` 里可视化管理。规则判定为需要审批时,运行时会在 `mate_tool_approval` 持久化一条记录并把 Agent 回合挂起。完整机制在 [安全与审批](./security)。 + +### 声明式 Hook 系统 + +Tool Guard 规则是一种更通用机制的特例——**声明式 Hook 系统**。5 个生命周期钩子覆盖工具调用和 LLM 调用的全部关键时刻: + +| Hook | 触发时机 | 典型用途 | +|------|----------|----------| +| `before_tool` | 工具执行前 | 参数脱敏、注入上下文、额外校验 | +| `after_tool` | 工具执行后 | 结果过滤、审计记录 | +| `before_llm` | LLM 调用前 | prompt 增强、缓存命中检查 | +| `after_llm` | LLM 返回后 | 输出过滤、token 统计 | +| `on_error` | 错误发生时 | 告警、降级策略 | + +Hook 在进程内执行,可以改参数、改结果、脱敏、加审计日志。你可以用 Hook 做 Tool Guard 之外的事——比如在每次 LLM 调用前注入安全策略,或者在工具返回后自动脱敏敏感字段。 + +--- + +## 执行:并发、隔离、有界 + +- **并发执行**——一个回合里独立的工具调用并发跑。Guard 检查是顺序的,执行在安全的地方并发。 +- **每工具超时**——每个工具有自己的超时。默认:快工具 30s,shell/browser 60s,生成类 300s。 +- **段隔离**——回合中间需要审批时,段在审批边界处分裂。 +- **观察截断**——结果超长会被自动截断。 +- **错误隔离**——单个工具失败不会中止整个回合。 + +--- + +## API 管理 + +```bash +# 列出所有工具 +curl http://localhost:18088/api/v1/tools \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" + +# 启用 / 禁用 +curl -X PUT http://localhost:18088/api/v1/tools/1 \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -d '{"enabled": false}' + +# 直接测试一个工具 +curl -X POST http://localhost:18088/api/v1/tools/WebSearchTool/test \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" \ + -d '{"query": "Spring AI"}' +``` + +每个依赖 provider 的工具在 Tools 页面都有测试按钮。 + +--- + +## 自定义工具 + +### 路线 1:`@Tool` Spring bean + +```java +@Component +public class FactorialTool { + + @Tool(description = "Calculate the factorial of a number") + public String factorial( + @ToolParam(description = "The number to compute factorial for") int n) { + long result = 1; + for (int i = 2; i <= n; i++) { + result *= i; + } + return String.valueOf(result); + } +} +``` + +- Spring `@Component` +- 每个 `@Tool` 方法变成一个可调用的工具 +- 每个参数上用 `@ToolParam`——这是 LLM 读的描述 +- 返回值就是 Agent 看到的观察 +- **工具做任何危险的事情时,为它加一条 Tool Guard 规则** + +重启后工具就活了。 + +### 路线 2:技能脚本 + +不想写 Java?把行为打包成一个技能包,带 `SKILL.md` 和脚本。见 [技能系统](./skills)。 + +### 路线 3:MCP 服务 + +能力已经以 MCP 服务形式存在?加个服务配置就行。见 [MCP 协议](./mcp)。 + +--- + +## 下一步 + +- [技能系统](./skills)——建立在工具之上的更高层能力 +- [MCP 协议](./mcp)——外部工具提供者 +- [安全与审批](./security)——Tool Guard 规则、审批流程、审计日志 +- [多模态创作](./multimodal)——生成类工具 diff --git a/mateclaw-server/src/main/resources/docs/zh/triggers.md b/mateclaw-server/src/main/resources/docs/zh/triggers.md new file mode 100644 index 00000000..174fdcd6 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/triggers.md @@ -0,0 +1,259 @@ +# 触发器(Triggers) + +::: tip 1.3.0 新增 +触发器系统自 v1.3.0 起提供。在 v1.2.0 及更早版本里,工作流和员工对话只能被手动调起。 +::: + +**触发器是什么**:把"系统里发生的事件"和"要执行的动作"连起来。事件可以是定时(cron)、是 webhook 来了、是某个渠道收到消息、是某个员工跑完了某次对话、是另一个工作流跑完了。动作可以是启动某个工作流,也可以是直接给某个员工发消息让它处理。 + +**触发器不是什么**: +- 不是 cron 任务管理器替代品——`mate_cron_job` 仍然存在并独立运作;触发器**复用**它的 ShedLock + 调度器底座,但**不写入** `mate_cron_job` +- 不是 IFTTT / n8n 风格的可拖拉自动化——触发器只负责"事件 → 动作"的路由;复杂逻辑放到 [工作流](./workflow.md) 里 +- 不是 webhook 的全功能 dispatcher——它只做去重 / 限流 / bot self-msg 过滤 / pattern 匹配,不替你解析复杂业务报文 + +::: warning v1.3.0 范围 +v0 = 6 种 pattern type + 2 种 dispatch target(agent / workflow)。安全治理(事件去重、per-trigger rate limit、循环保护、bot 自消息过滤)是默认开的。 +::: + +--- + +## 一分钟看懂 + +```jsonc +// 触发器:每天早上 9 点跑一次"晨报工作流" +{ + "name": "daily-morning-report", + "patternType": "cron", + "patternJson": { + "cronExpression": "0 0 9 * * *", + "timezone": "Asia/Shanghai" + }, + "targetType": "workflow", + "targetId": 12345, + "payloadTemplate": "{ \"date\": \"{{ now | date('yyyy-MM-dd') }}\" }", + "rateLimitPerMin": 10, + "dedupWindowSecs": 60, + "botSelfFilter": true, + "enabled": true +} +``` + +每天 9 点 → 后端通过 `CronDelegationPort` 抢到 ShedLock 锁 → 渲染 payload → 调起 workflow `12345` 异步运行。其它实例同一时刻被锁挡住,不会重复触发。 + +--- + +## 6 种 pattern type + +实现在 `TriggerPatternMatcher.java`。每个 pattern 对应 trigger 行的 `pattern_json` 列里一段 JSON。**未列出的字段表示 v0 不识别**——matcher 对未知字段直接忽略。 + +| Pattern | 触发时机 | `pattern_json` 字段 | 复用约束 | +|---|---|---|---| +| `cron` | 按 cron 表达式定时(**不进 ingest 管道**,由 scheduler 直跑) | `cronExpression`、`timezone` | 复用 `cron/` 模块的 ShedLock + Spring TaskScheduler;**不写 mate_cron_job 实体、不调 CronJobService** | +| `webhook` | 通用事件入口透传(**v0 不做更细过滤**——secret 校验在 channel 层;trigger 这边只看 `patternType=webhook` 命中) | (v0 无字段) | 通过 `POST /api/v1/triggers/events` 入口 + envelope wrap | +| `channel_message` | 渠道收到消息 | `channelType`(可选,按 envelope `data.channelType` 比对)、`senderEquals`(可选,按 sender id 精确比对) | 旁路 `ChannelWebhookController`,原路由不变 | +| `agent_lifecycle` | 员工生命周期事件 | `agentId`(可选)、`phase`(可选,取值 `spawned` / `terminated` / `crashed`) | 挂在 `ReActLifecycleListener` 上 | +| `content_match` | 内容包含 substring 才命中 | `substring`(**必填**,envelope 的 `data.content` 字段大小写不敏感包含匹配) | 通用过滤层,事件源由 envelope 决定 | +| `workflow_completion` | 工作流跑完进入终态 | `sourceWorkflowId`(可选)、`stateFilter`(可选,取值 `completed` / `failed` / `any`) | 监听 `WorkflowEngine` 终态事件;A→B→A 递归保护见下文 | + +> **未知 pattern type 默认 fail-closed**——typo 或将来加的 pattern 不会偷偷把 workspace 内所有 trigger 都点燃。 +> +> **不在 v1.3.0 里**:`schedule`(不带 cron 的定时如"30 分钟后")、外部 MQ 监听(Kafka / Pulsar / RocketMQ)、metrics / threshold 告警触发。 + +--- + +## 事件治理(默认开) + +### Bot self-msg 过滤(默认绑定为 noop) + +某些渠道(飞书 / 钉钉 / 企微)会把 bot 自己发的消息也回流为 `channel_message` 事件。**框架层**通过 trigger 行的 `bot_self_filter` 字段(默认 `true`)+ `BotSelfFilter` SPI 协作过滤。 + +::: warning v0 默认实现是 noop +开箱默认绑的是 `NoopBotSelfFilter`——`isBotSelf(...)` **永远返回 false**。这意味着 `bot_self_filter=true` 的 trigger 现在**不真过滤任何事件**。要让过滤真正生效,需要 channel 适配器侧注册一个真正能识别自己 bot id 的 `BotSelfFilter` Spring Bean(替换默认实现)。这是有意设计的——避免一个错误的 default 实现把所有合法的 bot 间通讯都误杀。 +::: + +要单独让一条 trigger 接受自己 bot 的消息(极少见,比如 bot 发特殊命令触发清理流程),把这条 trigger 的 `bot_self_filter` 设 `false`。 + +### 事件去重 + +事件经 `TriggerEventIngestService` 派发时,引擎在 `mate_trigger_event` 表上查 `dedup_key` 是否已经在 `dedupWindowSecs`(默认 60s)时窗内入过库。已经在 → **直接丢弃**,连 `fire_count` 都不++。 + +默认 `dedupWindowSecs = 60`。提高这个值可以扛更长时间的网关重投递;调到 `0` 关闭去重(**不推荐**)。 + +### Per-trigger rate limit + +每个 trigger 单独限速:1 分钟最多 `rateLimitPerMin` 次(默认 10)。命中限速的事件被丢弃,**不**重试,**不**写 `mate_trigger_event` 行;`mate_trigger.last_error` 字段会被刷成 `"rate-limited"` 便于运维查。 + +`channel_message` 类 trigger 通常要调高(瞬时群发);`workflow_completion` 类通常调低(防止 A→B→A 链路加速)。 + +### 递归循环保护 + +`workflow_completion` trigger 启动的 workflow 又触发另一个 `workflow_completion`……dispatch 链超过 5 层 → 引擎切断 + 告警。这是防止"A 写消息触发 B,B 写消息又触发 A"递归。 + +### Webhook ACK 时序 + +HTTP 入口(`POST /api/v1/triggers/events`)收到事件 → envelope wrap → dedup check → bot-self check → rate limit check → **立即 ACK 200** → 异步 dispatch。这意味着: + +- 上游网关(飞书 / 钉钉 等)拿到 200 就不再重投 +- 实际 dispatch 失败 → `mate_trigger.last_error` 被刷新;同 `dedup_key` 再来仍然被去重挡掉,**不重试** + +如果你需要"dispatch 成功才 ACK"语义,**目前没有**——v0 故意设计为 fire-and-forget 扛峰值。 + +--- + +## 在 UI 里管理触发器 + +### 入口 + +`Triggers`(侧栏)→ 列表 + **+ 新建** 抽屉。 + +### 创建 trigger + +抽屉里按 6 种 pattern type 各自结构化表单填字段——不需要手写 `pattern_json`: + +- 选 `cron` → 给 cron 表达式输入框 + 时区下拉 + 试运行下一次触发时间预览 +- 选 `channel_message` → 渠道类型可选 + (可选)按 sender id 精确匹配 +- 选 `agent_lifecycle` → agent 可选 + phase(spawned / terminated / crashed)可选 +- 选 `content_match` → substring 输入(**必填**),匹配 envelope 的 `data.content` +- 选 `workflow_completion` → 上游 workflow 可选 + state filter(completed / failed / any)可选 +- 选 `webhook` → v0 没有额外字段(透传一切) + +填完保存 → trigger 入库;`enabled=true` 时立即注册到对应引擎(cron 注册到 ShedLock;其它走 envelope 路由)。 + +### Payload template + +`payload_template` 字段是 Pebble 模板字符串,渲染后作为 dispatch target(agent 对话或 workflow run)的输入。 + +```jsonc +"payload_template": "{ + \"date\": \"{{ now | date('yyyy-MM-dd') }}\", + \"trigger\": \"{{ trigger.name }}\", + \"sourceEvent\": {{ event | toJson }} +}" +``` + +模板可访问的变量: +- `now` —— 当前时间 +- `trigger.{name,id,workspaceId}` —— 当前触发器 +- `event` —— 当前事件 envelope(`workspaceId` / `senderId` / `data` JSON 等) + +### 查看触发历史 + +`mate_trigger_event` 表存的是**去重元数据**——一行记录含 `trigger_id` / `dedup_key` / `received_at` / `expires_at`,不存 envelope 副本本身。要审计具体一次事件的内容,查 `mate_trigger.last_error` + dispatch 日志。 + +`mate_trigger.fire_count` 诚实记录有效 dispatch 次数(不计被去重 / 限速过滤掉的);`mate_trigger.last_error` 记录最近一次失败原因。 + +--- + +## API 参考 + +所有 endpoint 在 `/api/v1/triggers/` 下。`v1.3.0` 实际暴露的就这些——RFC 里规划的 `/webhook/{slug}` / `/test-fire` / `/{id}/events` 暂未实装。 + +| 方法 | 路径 | 说明 | +|---|---|---| +| `GET` | `/api/v1/triggers` | 列当前 workspace 所有 trigger | +| `GET` | `/api/v1/triggers/{id}` | 获取详情 | +| `POST` | `/api/v1/triggers` | 新建 trigger;若 `enabled=true` 立即注册到 scheduler / 路由 | +| `PUT` | `/api/v1/triggers/{id}` | 更新(包括启用 / 禁用——改 `enabled` 字段即可);`pattern_json` 改动时 `pattern_version++`,跨实例自取消旧 future | +| `DELETE` | `/api/v1/triggers/{id}` | 软删(等同禁用) | +| `POST` | `/api/v1/triggers/events` | **统一事件入口**——任何 webhook / channel adapter / 内部模块送一份 envelope 进来;引擎做 dedup / bot-self / rate limit / pattern match / dispatch;返回 per-trigger 命中 / 丢弃汇总 | + +--- + +## 跟现有 cron 模块的关系 + +::: tip 不取代,只复用 +v1.3.0 之前 MateClaw 已经有一个独立的 cron 子系统(`mate_cron_job` 表 + `CronJobService`)。Trigger 系统**不取代它**—— +- 老的 cron 任务(task_type = `text` / `agent` / `reminder`)仍然在 `Cron Jobs` 页面管理 +- 新的 trigger cron 在 `Triggers` 页面管理 +- 两者**共享**底层 ShedLock 锁表 + Spring TaskScheduler 线程池 +- `mate_cron_job` 列表**不会**显示 trigger cron;反过来也是 +::: + +为什么不合并?因为 `mate_cron_job` 老表的 `task_type` / `agentId` 必填等字段不适合 workflow target。强行扩列会破坏既有 product 约束。`CronDelegationPort` 是 v0 的最小化解——共享调度底座,分离持久层。`mate_cron_job` 整体收敛到 trigger 是后续版本的工作。 + +--- + +## 跨实例一致性(多副本部署) + +`CronDelegationPort` 的所有方法是**进程局部**的——本地 ScheduledFuture 只在本 JVM 注册,不持久化 handle。跨实例靠: + +1. 每个实例启动时调 `syncFromDatabase()` 扫所有 enabled cron trigger 注册本地 +2. 修改 trigger 时 `pattern_version++` + 取消本地 future +3. 每次 fire 前重新读 trigger 行,`patternVersion` 不匹配则**本地短路自取消**(说明被别的实例改过) +4. ShedLock 锁名 = `"mate-trigger-{triggerId}"`,跨实例互斥 +5. 周期 `@Scheduled(fixedDelay=60s) syncFromDatabase()` 兜底收敛 + +实战意义:你正常 rolling-deploy 多副本不需要做任何额外动作——新实例起来自动接管,老实例本地 future 走完最后一轮就停。 + +--- + +## 数据模型 + +### `mate_trigger` —— 触发器配置 + +主要字段: + +| 字段 | 类型 | 用途 | +|---|---|---| +| `pattern_type` | varchar | 6 种 pattern 之一 | +| `pattern_json` | TEXT | 该 pattern 的过滤参数 JSON | +| `target_type` | varchar | `agent` 或 `workflow` | +| `target_id` | bigint | 对应 agent / workflow 主键 | +| `payload_template` | TEXT | Pebble 渲染模板 | +| `dedup_window_secs` | int | 去重窗口(秒) | +| `rate_limit_per_min` | int | 每分钟最大 fire 次数 | +| `bot_self_filter` | bool | 是否启用 bot self 过滤(默认 true,但默认实现是 noop) | +| `pattern_version` | bigint | 乐观并发 lamport 计数器,**每次 `pattern_json` 改动 +1**;跨实例 fire 前比对自取消 | +| `fire_count` | bigint | 有效 dispatch 次数(不计去重 / 限速过滤掉的) | +| `last_error` | varchar | 最近一次失败原因(含 `"rate-limited"` / 异常 message) | +| `enabled` | bool | 软启停开关 | +| `deleted` | int | 软删标志 | + +### `mate_trigger_event` —— 去重元数据 + +仅用于去重判定,**不存 envelope 副本本身**: + +| 字段 | 类型 | 用途 | +|---|---|---| +| `id` | bigint | 主键 | +| `trigger_id` | bigint | 关联 trigger | +| `dedup_key` | varchar | **唯一索引**,引擎按此 key 在 `dedup_window_secs` 时窗内做去重判定 | +| `received_at` | timestamp | 入库时间 | +| `expires_at` | timestamp | 去重窗口过期时间,超过此点同 key 可以重新入库 | + +::: tip 设计取舍 +v0 故意**不把 envelope 全文写进 `mate_trigger_event`**——大体量渠道事件全量持久化撑不住库。事件正文的审计依赖 channel 层日志 + agent / workflow 层的 run 记录。如果未来需要"事件回放"等能力,再加 envelope 持久化列。 +::: + +--- + +## 已知限制(v1.3.0) + +- **没有可视化 trigger → workflow 串联图**——多 trigger 投递到同 workflow 在 UI 上看是两个独立列表 +- **没有 trigger 间优先级 / 依赖**——同一事件命中多 trigger 时按数据库 id 升序串行 dispatch +- **Webhook 入口没鉴权 IP allowlist**——只有 secret header;如果你需要更强的 IP 限制,前置 nginx / 网关 +- **`agent_lifecycle` 不区分会话级和 step 级**——员工一次对话内多次 step 失败只会触发一次 `failed` +- **没有事件回放**——`mate_trigger_event` 是只读历史,没有"重新派发这条事件"的按钮(v1 加) + +--- + +## 故障排查 + +| 现象 | 排查 | +|---|---| +| Cron trigger 没触发 | 1) `enabled=true`? 2) cron 表达式 + 时区是否解析为下次时间?UI 编辑器有预览; 3) ShedLock 锁是否被另一实例长持?查 `shedlock` 表 | +| 事件 `POST /events` 返回 200 但 dispatch 没发生 | 返回体里有 per-trigger fire / drop 汇总——看是否被 `BOT_SELF` / `RATE_LIMITED` / `DEDUPED` / `PATTERN_MISMATCH` 标了原因 | +| `channel_message` 触发不起来 | 1) envelope 的 `data.channelType` 拼写大小写是否和 trigger 的 `pattern_json.channelType` 匹配?2) `bot_self_filter=true` 但有自定义 `BotSelfFilter` 实现把它过掉了?3) `content_match` 的 `substring` 是否真的出现在 envelope 的 `data.content` 里 | +| `agent_lifecycle` 没触发 | 检查 `pattern_json.phase` 是 `spawned` / `terminated` / `crashed` 之一(不是 `started` / `completed` / `failed`) | +| 重启后 cron trigger 不再触发 | 看启动日志 `syncFromDatabase()` 是否报错;常见是表损坏 / `pattern_json` 反序列化失败 | +| `mate_trigger.last_error` 是 `"rate-limited"` | 调高 `rate_limit_per_min` 或者把 trigger 拆成多条按 group 分流 | +| `bot_self_filter=true` 没起作用 | 确认 `BotSelfFilter` 是否真有非 noop 实现——默认 `NoopBotSelfFilter` 永远返回 false | + +--- + +## 相关链接 + +- [工作流(Workflow)](./workflow.md) —— `target_type=workflow` 时 dispatch 到这里 +- [数字员工](./agents.md) —— `target_type=agent` 时 dispatch 到这里 +- [多渠道接入](./channels.md) —— `channel_message` pattern 监听的事件来源 +- [审批与安全](./security.md) —— webhook secret + ACL 兜底 diff --git a/mateclaw-server/src/main/resources/docs/zh/user-guide.md b/mateclaw-server/src/main/resources/docs/zh/user-guide.md new file mode 100644 index 00000000..e5eb7c4d --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/user-guide.md @@ -0,0 +1,201 @@ +# 用户手册 + +你打开 MateClaw,是因为你想让 AI 替你干活。不是因为你想学一套新软件。 + +这份手册只做一件事:**让你在最短时间内,从「装好了」走到「它在帮我做事了」。** + +--- + +## 60 秒起跑线 + +| 步骤 | 做什么 | 花多久 | +|------|--------|--------| +| 1 | 双击打开,`admin` / `admin123` 登录 | 10 秒 | +| 2 | 设置 → 模型 → 添加供应商,**启用一个**,粘贴 Key | 30 秒 | +| 3 | 聊天 → 选 Agent → 说「你好」 | 10 秒 | +| 4 | 看到回复流出来 → **系统活了** | — | + +看到回复的那一刻,你已经在产品里了。接下来的一切,是让它**对你有用**。 + +--- + +## 模型:先接通一个 + +**新装的 MateClaw 模型列表是空的。这是故意的——你不需要看 16 个供应商,你只需要一个能跑的。** + +`设置 → 模型 → 添加供应商`——按钮打开一个抽屉,里面是完整目录。 + +| 你的情况 | 推荐 | +|---------|------| +| 什么都没有,想最快跑通 | **DashScope** — 阿里云控制台复制 Key,粘进来 | +| 有 OpenAI / Anthropic Key | 直接填 | +| 有 ChatGPT Plus / Pro 账号 | **ChatGPT OAuth** — 浏览器登录即用,不需要 API Key | +| 想数据不出本机 | **Ollama** — 自动发现 `localhost:11434` | + +在抽屉里**点一下"启用"**,然后填 base URL(已知供应商预填)+ 粘贴 API Key,保存。模型立刻出现在聊天页的模型选择器里。 + +::: tip 启用 / 禁用是两件事 +**启用**让供应商出现在所有看得到模型的地方;**禁用**让它消失但保留配置——临时切供应商不需要删配置。 +::: + +**一个就够。** 别在这里花时间配五家——先让系统跑起来,后面随时加。 + +--- + +## 聊天:产品的心脏 + +左侧点「聊天」。选一个 Agent。选模型。打字。回车。 + +这就是整个交互。没有别的入口。 + +### 三件事值得立刻试 + +**1. 问一个直接的问题** + +> 帮我解释一下 Java 的虚拟线程和平台线程有什么区别 + +Agent 直接回答,不调工具。你看到的是纯推理能力。 + +**2. 让它用工具** + +> 搜一下 Spring Boot 最新版本,总结 breaking changes + +Agent 自己去调搜索工具、读结果、组织答案。你看到「思考 → 行动 → 观察 → 总结」的完整循环——这就是 ReAct。 + +**3. 让它做多步任务** + +> 先查一下我们的 Wiki 里关于认证的设计决策,再对比 Spring Security 6 的最佳实践,给我一份差异分析 + +Agent 会自动拆分成步骤、逐步执行、最后汇总。你能看到计划和每一步的进度。 + +如果三件事都跑通了,**你已经理解了 90% 的产品**。 + +--- + +## Agent:决定 AI 怎么工作 + +`Agent 管理 → 新建 Agent` + +一个 Agent 只定义五件事: + +| 配置 | 一句话 | +|------|--------| +| **系统指令** | 它是谁、怎么说话、什么态度 | +| **模型** | 用哪个模型 | +| **工具** | 能用哪些工具 | +| **技能** | 能调用哪些技能包 | +| **知识库** | 能读哪些 Wiki | + +从模板开始。模板是开箱即用的——改个名字、调调 system prompt、勾选工具,保存。30 秒一个新 Agent。 + +::: tip 什么时候该建新 Agent +当你发现自己每次都要重复同样的前提说明时——那就是该建一个专用 Agent 的信号。把那些前提放进 system prompt,以后不用再说。 +::: + +--- + +## 记忆:让它记住你 + +MateClaw 的记忆不需要你手动管理。聊完天,系统自动提取关键信息写入记忆。下次对话,Agent 会带着这些记忆工作。 + +你能做的: + +- **PROFILE.md** — 你是谁、你的偏好、你的工作方式 +- **MEMORY.md** — 长期积累的事实和笔记 +- **每日记忆** — 系统自动生成的对话摘要 + +记忆在所有渠道共享。你在桌面端聊的内容,钉钉里的 Agent 也记得。 + +--- + +## Wiki:让它读懂你的文档 + +`Wiki → 新建知识库` + +把 PDF、DOCX、TXT 或整个文件夹扔进去。等消化完——每条素材都有进度条,不用干等。 + +消化完之后: + +1. 把知识库绑到一个 Agent +2. 问里面的内容 +3. Agent 会自动检索相关页面,带着知识回答 + +::: tip +Wiki 不是全文搜索。它是**语义检索**——问「我们关于认证做了什么决定」,返回的是决策,不是包含「认证」两个字的所有页面。 +::: + +--- + +## 技能和 MCP:扩展能力边界 + +**技能** — `Agents → 选一个 Agent → 技能`。从技能市场安装,或手写 `SKILL.md`。 + +**MCP** — `设置 → MCP 服务`。接入外部工具服务器(文件系统、数据库、自定义 API)。MCP 工具自动出现在工具列表里,Agent 不知道也不需要知道它们是外部的。 + +当默认的 20 个内置工具不够时,从这两个入口扩展。 + +--- + +## 渠道:在你待的地方找到它 + +`渠道 → 选一个平台 → 贴凭证` + +支持 8 个渠道:钉钉、飞书、企业微信、微信个人、Telegram、Discord、QQ、Slack。 + +::: tip 钉钉和飞书:扫码即可(v1.1.0+) +不需要去开放平台"创建应用 → 复制 ID 和 Secret"。在新建渠道的表单里直接点**扫码绑定**,用钉钉 / 飞书 App 扫码授权,**client_id / app_id 和密钥自动回填**。整个过程不到 30 秒。 +::: + +同一个 Agent,同一份记忆,在所有渠道里工作。 + +--- + +## 安全:能力强,但不失控 + +`安全` 页面里你能做三件事: + +1. **工具守卫** — 哪些工具需要你批准才能执行(Shell、SQL、文件写入) +2. **文件守卫** — 哪些目录 Agent 不能碰 +3. **审计日志** — 看 Agent 做过什么 + +默认配置已经够安全。如果你在生产环境里用,建议进去收紧一下 Shell 和 SQL 的审批规则。 + +--- + +## 三种推荐配置 + +### A. 个人助手(最快起步) + +配一个模型 → 用默认 Agent → 开始聊。记忆系统自动积累。 + +### B. 知识助手 + +建一个 Agent → 建一个 Wiki 知识库 → 导入文档 → 把 Wiki 绑到 Agent。 + +### C. 自动化 Worker + +建一个专用 Agent → 安装技能 → 接 MCP 服务 → 在安全页配审批规则。 + +--- + +## 出了问题 + +| 症状 | 最可能的原因 | +|------|------------| +| 后端起不来 | 18088 端口被占。看 `~/.mateclaw/logs/app.log` | +| 模型调用报错 | API Key 错了,或者网络不通。回设置里检查 | +| 界面白屏 | Ctrl+Shift+R 强刷 | +| Ollama 报 "does not support tools" | 换一个支持 function calling 的模型(qwen3、llama3.1:8b+) | +| 还是不行 | [GitHub Issues](https://github.com/matevip/mateclaw/issues),贴 `app.log` 尾巴 | + +--- + +## 下一步 + +| 你想做什么 | 去哪里 | +|-----------|--------| +| 理解产品为什么这么设计 | [项目介绍](./intro) | +| 看技术架构 | [架构说明](./architecture) | +| 配置更多选项 | [配置参考](./config) | +| 接更多渠道 | [多渠道接入](./channels) | +| 深入 Agent 引擎 | [Agent 引擎](./agents) | diff --git a/mateclaw-server/src/main/resources/docs/zh/wecom-tuning.md b/mateclaw-server/src/main/resources/docs/zh/wecom-tuning.md new file mode 100644 index 00000000..e466b3ea --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/wecom-tuning.md @@ -0,0 +1,419 @@ +# 企业微信深度优化 + +**让一个真正能被企业内部群里几十号人用起来的 bot,远不止"接通就行"。** + +[多渠道接入 → 企业微信](./channels#企业微信) 那一节是把 bot 跑起来;这一篇是把 bot **跑稳**——所有 MateClaw 在企业微信适配层做过的非显然优化、踩过的平台边角,以及为什么这么处理。 + +阅读对象: + +- 已经把企业微信渠道连通、想理解"为什么我的群聊体验是这样"的运维 / 一线 +- 想加新功能但需要先知道平台限制的开发者 +- 想把 bot 推给真实业务团队前做技术评估的负责人 + +--- + +## 平台一句话总结 + +**企业微信 AI Bot 是个"看起来像聊天 SDK,本质是个事件回调"的平台。** + +它给你三种能力: + +1. **接收事件** —— 用户在群里 @ bot,平台通过长连接(WebSocket)或 webhook 把消息推过来 +2. **回复**(同一会话内)—— 用 `aibot_respond_msg` 把答案"贴"到对应的 frame 上 +3. **主动推**(不限于回复)—— 用 `aibot_send_msg` 但**仅限单聊** + +**最关键的隐藏规则**:第 2、3 条在群聊里是不一样的,单聊里也不一样。下面的所有优化都围绕这个矩阵展开。 + +--- + +## 群聊多用户协作 + +### 平台默认行为 + +群聊里 A、B、C 三个人都在 @ bot,平台会把每个人的消息当一条独立 frame 推过来,但都打到**同一个 chatId** 上。 + +如果你直接按 chatId 分会话(这是最自然的做法),后果是: + +- 持久化的对话历史里全是 `user: ...` 没有发送人前缀,模型读历史看到的是一锅粥 +- 防抖窗口(500ms / 2.5s 自适应)会把 A 和 B 的连发消息合并成一条 +- A 问"我想查 X",B 接着问"我想查 Y",bot 看到的是"用户问了 X 和 Y 两个不相关的事" + +### MateClaw 的处理 + +**两层修复**: + +**1. 防抖按 sender 切边界。** 同一会话内连续两条消息进来时,先看 senderId: + +- 同一个人 → 合并(典型场景:粘贴长文被 IM 客户端切片) +- 不同人 → 立即 flush 已有 pending,给新发送人开新窗口 + +代码层面是 [`ChannelMessageRouter.isSameSender`](https://github.com/anthropics/mateclaw/blob/main/mateclaw-server/src/main/java/vip/mate/channel/ChannelMessageRouter.java)。null 防御:任一 senderId 缺失都不合并,宁可多 flush 一次也不要错串归属。 + +**2. 持久化 + Prompt 都带 `[@sender]` 前缀。** 群聊(`chatId != null`)的每条 user 消息在落库时和送给 LLM 之前都会被 `applyGroupTag(message, content)` 包一层: + +``` +[@XuZhanFu] @迈特云的机器人 我想查 X +[@xuzf] @迈特云的机器人 我想查 Y +``` + +这样: + +- 第 30 条历史消息也能让模型知道是谁说的 +- 持久化的对话时间线读起来像 `[@A] ...; [@B] ...; [@A] ...`,模型能正确处理跟问、引用回复、互相纠错 +- 单聊(`chatId == null`)零开销,行为不变 + +senderName 优先于 senderId(友好),都没有时返回 null(避免 `[@null]` 这种垃圾标签)。 + +### 你能观察到什么 + +``` +[wecom] Sender boundary in conversation wecom:{chatId}: flushing pending from sender=A, accepting new sender=B +``` + +DB 里 `mate_message.content` 列直接看 `[@xxx]` 前缀。 + +--- + +## 上传约束矩阵 + +企业微信平台对 bot 上传的媒体有**硬性大小限制**,超限的请求在 **chunk-finish 阶段**被拒(已经传完所有字节才报错),用户体验是"传了三分钟然后什么都没发出来"。 + +### 限制 + +| 类型 | 大小上限 | 格式要求 | +|------|---------|---------| +| 文件 | **20 MB** | 任意 | +| 图片 | **10 MB** | 任意常见格式 | +| 视频 | **10 MB** | 任意常见格式 | +| 语音 | **2 MB** | **必须 AMR**(其他格式平台拒收) | +| 全局 | **20 MB** | 兜底硬上限 | + +### MateClaw 的处理 + +**客户端预检**,避免无效上传。`applyWeComUploadLimits(fileSize, mediaType, contentType)` 在上传前判定结果: + +- 文件 > 20 MB → 拒绝,告诉用户"超过 20MB 上限" +- 图片 > 10 MB → 降级为文件上传(用户在群里能看到附件,只是不再是缩略图) +- 视频 > 10 MB → 降级为文件上传 +- 语音 > 2 MB **或** mime 不是 `audio/amr` → 降级为文件上传 +- 文件 + 任何类型 > 20 MB → 直接拒绝(绝对硬上限) + +降级时附带一段说明文字("图片超过 10MB,已转为文件附件发送"),用户立刻知道发生了什么,不会以为 bot 抽风。 + +### 智能识别没有 filename 的文件 + +WeCom 群里转发的文件经常**没有 filename 字段**。落地存成 `file.bin` 的话,下游所有按扩展名 dispatch 的工具(PDF 阅读、DOCX 解析等)会全部失效。 + +修复:通过 magic-byte 嗅探还原扩展名: + +- `%PDF` → `.pdf` +- `PK\x03\x04` 是 ZIP 容器;进一步 peek 内部条目区分 `.docx` / `.xlsx` / `.pptx` / `.odt` / `.epub` / `.jar` +- 其他常见格式(PNG / JPEG / MP4 / MP3 / WAV)都能正确识别 +- 实在认不出 → 保留 `.bin`,至少不假装是其他格式 + +实现在 `WeComChannelAdapter.sniffMagic()` + `refineZipKind()`。 + +--- + +## 引用消息(quote) + +WeCom 用户引用前一条消息(图片、文件、文本、语音、小程序)然后追加问题,是**最常见的群聊交互模式**。 + +### 支持的引用类型 + +| 引用类型 | bot 看到的 | 是否能进一步处理 | +|----------|------------|------------------| +| 引用文本 | `[引用消息: 之前的文本内容]\n用户的新问题` | ✅ 文本一并送给模型 | +| 引用语音 | `[引用消息: [语音] ASR 转文字]\n用户的新问题` | ✅ 语音 ASR 结果作为上下文 | +| 引用图片 | `[引用消息: [图片]]\n用户的新问题` + 图片 attached part | ✅ 视觉模型 sidecar 看图 | +| 引用文件 | `[引用消息: [文件: report.pdf]]\n用户的新问题` + 文件 attached part | ✅ 文件 tool 可读 | +| 引用混合 | 各子类按上面规则展开 | ✅ | + +### 实现要点 + +- **媒体一并下载**:引用的图片 / 文件不只是个标记字符串,会真的下载、AES-256-CBC 解密、落到 `data/chat-uploads/{conversationId}/...`,然后作为 MessageContentPart 给 agent +- **路径一致**:媒体落盘的 conversationId **必须**等于 `mate_conversation` 表里的 conversationId,否则下游 `/api/v1/chat/files/{convId}/{name}` 会因 `isConversationOwner` 查不到行直接 403,前端 `` 显示图裂 + +历史 bug:早期版本 `inboundConversationId()` 给群聊路径加了 `wecom:group:` 中缀,但 router 持久化时是 `wecom:{chatId}` 没中缀,两边一对不上整批群聊引用图片全部图裂。已修。 + +--- + +## appmsg 消息类型 + +`msgtype=appmsg` 是 WeCom 给富媒体卡片留的扩展点,常见四种子变体: + +| 变体 | 实际是什么 | bot 怎么处理 | +|------|-----------|--------------| +| `appmsg.file` | 转发的文件(PDF / Word / Excel) | 走完整下载 pipeline,等同 `msgtype=file` | +| `appmsg.image` | 图片卡片 | 走完整下载 pipeline,等同 `msgtype=image` | +| `appmsg.url` | **公众号文章 / 外链** | 见下一节 | +| `appmsg.miniprogram` | 小程序 | 把 title 暴露给模型,附件无法获取 | + +未知子类型 fallback 成 `[appmsg: title]` 标记,至少模型知道"用户分享了某种富媒体"。 + +### 公众号文章 + +mp.weixin.qq.com 的文章页是**带 captcha-gated SSR 的**,任何 LLM 工具都抓不到正文。如果 bot 假装能读,模型会**凭标题瞎编内容**(生产里观察到:"本文讲了三个要点……" 完全是幻觉)。 + +MateClaw 在 link 分支检测到 `mp.weixin.qq.com` 后,会自动给模型追加一段提示: + +> (提示:该链接为公众号文章,正文需要用户在微信内打开后复制粘贴,请优先请用户粘贴正文,不要凭标题猜测内容。) + +效果:模型不再编造,主动让用户粘贴正文。其他正常网址(github、维基、随便一个外链)**不**触发提示,因为它们的 body 是普通工具能 fetch 的。 + +--- + +## 群聊主动推送(aibot_send_msg vs aibot_respond_msg) + +### 平台规则 + +``` +单聊:aibot_send_msg ✓ aibot_respond_msg ✓ +群聊:aibot_send_msg ✗ aibot_respond_msg ✓ (必须绑定一个 inbound frame 的 reqId) +``` + +群聊里 bot 任何主动消息(cron 推送、异步任务回推、图像生成完成)都必须**搭一辆顺风车**——绑到一个之前用户 inbound 的 frameReqId 上,否则平台拒收。 + +### MateClaw 的处理 + +**LRU 缓存最近 inbound reqId**。`lastChatReqIds: ConcurrentHashMap` 在每条群聊 inbound 进来时被更新,上限 1000 个 chat。 + +**统一出口 `sendOutboundFrame(chatId, body)`**: + +- 缓存命中 → `aibot_respond_msg` + 缓存的 reqId +- 缓存未命中 → 降级 `aibot_send_msg`(单聊或新 chat) + +这样: + +- cron 定时摘要 → 群聊有人说过话 → 走 respond 推送成功;从来没说过话 → 降级 send_msg 失败,但至少不会一刀切都失败 +- 异步任务(图像 / 音乐 / 视频生成)完成后 → `AsyncTaskMediaDispatcher` 调用统一出口 +- 同一条 LLM 回复跨多个 chunk → 同一个 reqId 复用 + +### 你能观察到什么 + +``` +[wecom] Group send via aibot_respond_msg: chatId=..., reqId=... +``` + +--- + +## 异步任务回推 + +图像生成 (`image_generate`) / 音乐生成 (`music_generate`) / 视频生成 (`video_generate`) / 3D 模型生成 (`model3d_generate`) 都是**异步任务**——agent 拿到 task id 立刻返回,真正的产物 30 秒~几分钟后才出来。 + +历史问题:产物只出现在 Web 控制台的会话历史里,**WeCom 群里看不到**。 + +修复:`AsyncTaskMediaDispatcher.forwardToImIfBound(conversationId, parts)`—— + +- 任务完成后,从 `ChannelSessionStore` 反查 conversationId 绑的渠道 +- 跳过 `web` / `webchat`(SSE 已经覆盖) +- 调对应渠道适配器的 `sendContentParts(targetId, parts)` +- WeCom:image / audio / video / file 全部支持,走原生附件 +- Slack:通过 `filesUploadV2` 直传(参考 [Slack channel](./channels#slack)) +- 不支持 `sendContentParts` 的渠道(QQ 等):catch UnsupportedOperationException + log,不让一个不支持的渠道卡住整批分发 + +文件路径在 `data/chat-uploads/{conversationId}/`,serve URL 是 `/api/v1/chat/files/{conversationId}/{storedName}`,前端 / 渠道附件视图都按这个 URL 读。 + +--- + +## 模型行为:假装调用工具 + +观察:**qwen3.6-plus** 在长上下文 + 工具调用密集的场景下偶发地"懒"——它会用 Markdown 代码块**伪装**自己调了工具,但实际 `toolCallCount=0`: + +```` +🎵 《在熟悉的路口》 重新创作任务已提交! +⏳ 生成约需 1-2 分钟,完成后音频会自动推送到对话中... + +```json +{ "prompt": "...", "lyrics": "..." } +``` +```` + +后端没拿到 tool_call → 永远不会真的发起音乐生成 → 用户永远收不到歌。 + +**目前的应对**:换更稳定执行 tool_calls 的模型(kimi-for-coding、claude-sonnet-4.5、deepseek-r1)。在 [模型配置](./models) 里把 agent 的默认模型改掉即可。 + +未来可能加:服务端检测"任务已提交 + toolCallCount=0"模式 → 自动注入纠正提示重试一次。 + +--- + +## 模型行为:自循环输出 + +另一种偶发故障:模型陷入"思考-输出"自循环,重复同一段中文回答几十次直到耗尽 max_tokens(16384)。生产上观察到的模式: + +``` +"Wait, I should X." → 写中文答案 → "Done." → 写同一份中文答案 → "Wait, Y." → 同一份答案 → ... +``` + +用户全程看 "生成中..." 等几十秒到几分钟,最后收到一坨重复文本。 + +### MateClaw 的处理 + +**两层守卫**: + +1. **检测**:[`hasRepeatingSuffix`](https://github.com/anthropics/mateclaw/blob/main/mateclaw-server/src/main/java/vip/mate/agent/graph/NodeStreamingChatHelper.java) 探测 buffer 尾部是否被同一个 24~240 字符的 unit 连续重复 4 次以上 → 立即 dispose 上游订阅 +2. **去重 + 标记**:`dedupTrailingRepeats` 把已累积的 buffer 尾部 N 份拷贝缩成 1 份;ReasoningNode 把 finishReason 设为 `INCOMPLETE`,前端展示截断卡 + "重新生成"按钮 + +为什么不无脑发警告就算了:用户已经在 SSE 流里看到那坨重复文本(SSE 单向 push 没法 unsend),但 **DB 持久化** 和 **WeCom 回推** 用的都是 `finalAnswer`——所以 IM 群里只看到一份干净的回答 + INCOMPLETE 提示。 + +阈值选得**特别窄**(4 次 verbatim 连续)就是为了不误伤合法的"TL;DR / body / TL;DR 三段式"输出。 + +--- + +## 网络层稳定性 + +### TLS / Socket 瞬时错误重试 + +DashScope / OpenAI / 各家 LLM 网关在公网传输中偶发产生: + +- `bad_record_mac`(TLS RFC 5246 §7.2.2 fatal alert 20) +- `SSLHandshakeException` +- `SocketException: Connection reset by peer` +- `Premature close` / `Broken pipe` + +之前这些一旦发生直接给 agent 抛 `LLM 调用失败` 红字,没有重试。 + +修复:把这些都归类成 `SERVER_ERROR`,走现有的指数退避重试链:3s → 6s → 12s(带 jitter)最多 5 次。详见 [agents 引擎](./agents#错误恢复)。 + +### keepalive + +群聊回复用 `aibot_respond_msg` 时,平台对**单条流**有 60 秒 TTL——超过 60 秒不发新数据,平台会丢弃这个 stream slot,后续真正的 reply 静默失败。 + +agent 处理复杂任务(多次工具调用 + LLM 推理)经常超过 60 秒。`WeComKeepaliveScheduler` 每 30 秒往 stream 上发一个 noop "正在处理..." 心跳,slot 永不过期。180 秒兜底强制 finish,避免任务真挂了 keepalive 一直续命。 + +### 重连 + 指数退避 + +WeCom 长连接断开时(NAT 超时、网络抖动),适配器自动重连:2s → 4s → 8s → 16s → 30s 封顶。**永远不会放弃**——只要进程还活着,下次能连上就立刻恢复消息接收。 + +控制台健康视图能看到当前重连次数,运维心里有数。 + +--- + +## 平台级约束(不是 bug,是限制) + +这些是**企业微信平台本身**的约束,没法在代码层绕过,只能配置层规避: + +### 数据权限锁 + +API 模式 bot 在企业微信管理后台勾选**任何一项数据使用权限**(如"读取消息"、"获取群信息"),bot 会**自动锁定为仅创建者可用**。其他成员发消息 bot 不响应。 + +**解决**:在管理后台**取消勾选**全部 7 项数据权限,bot 即可对所有授权成员可见。MateClaw 通过 webhook 拿消息,不需要这些数据权限。 + +### 可见范围 + 数据权限二维矩阵 + +| 可见范围 | 数据权限 | 实际效果 | +|---------|---------|---------| +| 全员 | 全部勾选 | **仅创建者**可用(数据权限锁覆盖可见范围) | +| 全员 | 全部取消 | 全员可用(推荐) | +| 指定部门 | 全部取消 | 指定部门成员可用 | +| 指定人员 | 全部取消 | 指定人列表内可用 | + +### 群聊里 @bot 才会触发 + +WeCom 群的 bot 必须被 `@` 才收到消息。私聊不需要 `@`。这是平台行为,没办法绕过。MateClaw 不会在群里 broadcast 监听所有消息(也做不到)。 + +--- + +## 调试技巧 + +### 看群聊归属是否生效 + +```sql +SELECT content FROM mate_message +WHERE conversation_id = 'wecom:{chatId}' AND role = 'user' +ORDER BY id DESC LIMIT 5; +``` + +期望:每条 user 消息都以 `[@username]` 开头。 + +### 看媒体落盘路径 + +```bash +ls data/chat-uploads/wecom:{chatId}/ +``` + +**不应该**有 `wecom:group:{chatId}` 这种带 `group:` 中缀的目录(早期 bug 残留可以手动清理)。 + +### 看群聊回推路径 + +后端日志里: + +``` +[wecom] Group send via aibot_respond_msg: chatId=..., reqId=... +``` + +如果群聊里 bot 没回复,但日志里看到这行 + reqId 不为空,说明回推到了平台但平台拒收(一般是 reqId 已被消费过、或 bot 已被踢出群)。 + +### 看 keepalive 状态 + +```bash +grep "wecom-keepalive" logs/mateclaw.log | tail +``` + +期望看到周期性的 "Heartbeat sent" + "Heartbeat ACK received" / 偶尔的 "force-finished stream" 强制完成。 + +--- + +## 已知 corner case + +| 场景 | 当前行为 | 后续可能 | +|------|---------|---------| +| 群里第一条消息就是 cron 推送(chat 还没人说过话) | 缓存里没有 reqId,降级 `aibot_send_msg` 被平台拒 | 加 ring buffer 缓存多条历史 reqId(仅修复有限场景,不上) | +| 模型在长会话里"懒"得调工具 | 用户重发 / 换模型 | 加服务端检测注入纠正提示 | +| 同一群同时来 3 条不同 sender 的消息 | 串行处理,每个用户独立窗口(生效) | — | +| 公众号文章用户拒绝粘贴正文 | bot 礼貌引导用户复制 | — | +| OOXML 文档 magic-byte 误判(极小概率) | 退回到 `.zip` | 已通过 ZIP 内部条目 peek 解决 90% 场景 | + +--- + +## 一图概括 + +``` + ┌─────────────────────┐ + │ 企业微信群里的用户 │ + └──────────┬──────────┘ + │ inbound (含 chatId) + ▼ + ┌────────────────────────────────────────┐ + │ WeComChannelAdapter │ + │ ├─ chunk upload pre-check (4 类限制) │ + │ ├─ magic-byte sniff (OOXML peek) │ + │ ├─ AES 解密 + 落 chat-uploads/{convId}/ │ + │ ├─ quote 引用解析(5 子类型) │ + │ ├─ appmsg 解析(4 子类型 + 公众号提示) │ + │ └─ 缓存 lastChatReqIds[chatId] │ + └──────────────┬─────────────────────────┘ + │ ChannelMessage(content="[@xxx] ...") + ▼ + ┌────────────────────────────────────────┐ + │ ChannelMessageRouter │ + │ ├─ 自适应 debounce (500ms / 2.5s) │ + │ ├─ sender boundary 切断(群聊关键) │ + │ ├─ applyGroupTag 落库 + 送 LLM │ + │ └─ 队列 + sessionLock 串行 │ + └──────────────┬─────────────────────────┘ + │ + ▼ + ┌──────────┐ + │ Agent │ ← StateGraph + ReAct + └─────┬────┘ + │ finalAnswer / tool_calls + ▼ + ┌────────────────────────────────────────┐ + │ sendOutboundFrame(chatId, body) │ + │ ├─ 缓存命中 → aibot_respond_msg │ + │ ├─ 缓存未命中 → aibot_send_msg │ + │ ├─ keepalive scheduler (60s TTL 续命) │ + │ └─ 重连退避(NAT / 抖动自愈) │ + └────────────────────────────────────────┘ +``` + +--- + +## 相关阅读 + +- [多渠道接入](./channels) — 9 个渠道的总览 + 设置 +- [Agent 引擎](./agents) — TLS 重试、错误分类、自循环检测 +- [模型配置](./models) — 怎么换默认模型、failover chain +- [安全与审批](./security) — 群里执行高风险工具的审批流 +- [Doctor 健康检查](./doctor) — 怎么用诊断命令排查渠道问题 diff --git a/mateclaw-server/src/main/resources/docs/zh/wiki.md b/mateclaw-server/src/main/resources/docs/zh/wiki.md new file mode 100644 index 00000000..7fba9844 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/wiki.md @@ -0,0 +1,482 @@ +--- +title: LLM Wiki 知识库 — 结构化知识引擎,不是向量检索 +description: LLM Wiki 把原始文档消化成结构化知识页面,带双向链接、摘要和溯源。Agent 自动注入知识。支持 lazy 入库(先入索引、按需出页面)和 eager 入库(上传即生成完整 Wiki)。 +head: + - - meta + - name: keywords + content: LLM Wiki,知识库,知识引擎,双向链接,结构化知识,RAG替代,知识图谱,lazy ingest,按需编译,语义搜索 +--- + +# LLM Wiki 知识库 + +知识库不是一个让你搜索的地方,是一个让你**读**的地方。 + +市面上大多数 AI 知识系统只做一件事:把文件切块、向量化、查询时返回片段。你拿到的是碎片。你看不到它的全貌。你问它之前,你永远不知道它到底"知道"什么。没有任何东西是**完成**的。 + +MateClaw 的 LLM Wiki 做的事不一样。你把原始材料扔进知识库,系统会把它读一遍,消化一遍,然后写出结构化的 Wiki 页面——每一页有摘要、反向链接、通往原文段落的来源指针。你可以打开任何一页直接读。你可以编辑。Agent 自动读摘要,按需取全文。 + +**是一本书,不是一个向量库。** + +::: tip 它和那些「LLM Wiki」开源仿品有什么不一样 +2026 年 4 月,Andrej Karpathy 用一个 GitHub Gist 把 "LLM Wiki" 这个想法推到台面:扔给 AI 的资料应该被读一遍、写成可读的 wiki,而不是当成查询时才翻一遍的向量碎片。一个月内,GitHub 上冒出至少 9 个 `llm-wiki` 单文件实现——好用、本地、个人级。 + +MateClaw 的 LLM Wiki **是同一个想法长成的产品**: + +- 不是一个人的笔记本,是**团队共享**的知识库——多用户、权限、审计、归档 +- 不是跑完就完的脚本,是 **Agent 一直在用**的能力——记忆、检索、引用全打通 +- 不是只有 eager 模式,**lazy 模式按需出页面**——量大时省 90%+ 的 LLM 调用 +- 不是裸文件输出,是**带溯源 / 双向链接 / 人工编辑保护 / 归档恢复**的页面层 +- 不是孤立工具,是 **MateClaw Agent 操作系统的知识层**——和记忆、Agent、渠道交付串成一根链 + +> 他们做了克隆。我们做了一个家。 +::: + +--- + +## 三层模型 + +一个知识库是三层结构叠起来的: + +1. **原始材料层**——你扔进去的文件。PDF、DOCX、纯文本、Markdown,或者桌面端扫描整个本地目录。系统保留原文不动;Wiki 里的任何一句话都能回溯到它出自哪段原文。 +2. **Wiki 页面层**——AI 从原始材料里写出的结构化文章。每一页有标题、摘要、正文、指向相关页面的双向链接(`[[像这样]]`,也支持 `[[target|展示文字]]` alias 形式)、以及通往原文的来源指针。 +3. **Agent 表层**——Agent 调用 wiki 工具时,系统会把相关页面的摘要自动注入 prompt,正文按需读取。Agent **不读原文**,它读这本书。 + +这事很重要,是因为 Agent 的上下文窗口再也不用浪费在反复读原文上了。Token 都花在思考上,不是重复阅读上。 + +--- + +## 建一个知识库 + +`Wiki → 新建知识库`。起名按"里面装的是什么",不是"属于谁"。"产品规格"比"Alpha 组的 KB"好。 + +建好之后加材料: + +- **上传文件**——把 PDF、DOCX、Markdown、纯文本拖进上传区。每个文件成为一条 raw material。 +- **扫描本地目录**——桌面端专属。指一个文件夹,MateClaw 递归走完整个树,尊重 `.gitignore`,把能读出文本的全都导入。 +- **粘贴文本**——适合短片段或对话记录。 + +材料一进来,系统就开始入库。每条 raw material 上都有状态:`pending → processing → completed`。如果中间几块失败了,状态会变 `partial`——你拿到的是"除了坏块之外都成功了",不是"一块坏了整份全挂"。 + +--- + +## 两种入库模式:要不要让 AI 立刻写页面 + +`Wiki → 配置 → 入库模式` 二选一: + +- **Eager(立即生成页面)**——上传后立刻跑完整流水线,把材料消化成结构化 Wiki 页面。适合"我就是要一份现成可读的 Wiki"。代价:每次上传烧 N 次 LLM 调用,慢、贵。 +- **Lazy(先入索引)**——上传只做抽取、清洗、切片、向量化。**0 次页面生成 LLM 调用**。立刻可搜,页面在有人真要读时按需生成。适合"先把资料倒进去,回头按需取"。 + +旧 KB 默认走 eager 不变,新建的 KB 想省钱就在配置页切到 lazy。两种模式可以混着用——同一个 KB 的旧页面继续在,再上传的材料按当前模式处理。 + +> Lazy 下"页面数 = 0"是**完成**态,不再被标记 failed。这条改动专门解决之前抽不出文本的材料一律变红的尴尬。 + +--- + +## 消化到底在干嘛 + +### Eager 模式:完整流水线 + +对每一份原始材料,按顺序: + +1. **切块**——把原文切成带重叠的段落,附带 chunk 级元数据:页码(PDF)、标题路径(Markdown / HTML 经过 jsoup 清洗)、章节标识、token 估算。 +2. **抽取概念**——让 LLM 在每一块里识别出实体、决策、事实、未解问题。 +3. **聚类成稿**——把相关的抽取聚在一起,生成一批候选 Wiki 页面的结构化草稿。 +4. **建链**——在页面之间找双向引用(`[[concept]]` 和 `[[concept|展示文字]]`),计算反向链接。 +5. **落库**——把页面写进 `mate_wiki_page`,引文(citation)指回原文段落。 + +入库是幂等的。同一份材料再跑一遍,系统更新已有页面而不是复制一份。人工编辑过的内容会被保护——`locked` 标记告诉 digester"这段是人写的,别动";要让 AI 重写就显式解锁。 + +#### 两阶段消化 + +eager 模式分两阶段,速度提了一个数量级: + +- **阶段 A(路由)**——抽取元信息和概念路由,决定每段原文会流向哪些页面。 +- **阶段 B(合并)**——按页并行生成,60+ 页同时跑。每条原始素材有自己的**独立进度条**——不再盯着"处理中…"猜进度。 + +**可恢复**:中途断了?点"重新处理",只重跑未完成的页面,已生成的不动。超过模型上下文限制的文档,系统自动做 mean-pool 子段切分——你不用管。 + +### Lazy 模式:先入索引,按需出页面 + +链路缩成四步: + +1. **抽取** → 拿到原始文本(PDF/DOCX/...)。 +2. **预处理** → jsoup 清掉 HTML 噪声,识别 markdown 标题层级和 PDF `--- Page N ---` 标记。 +3. **切片 + 元数据** → 每个 chunk 自带 `page_number`、`header_breadcrumb`(如 `Intro / Setup / Linux`)、`source_section`、`token_count`。 +4. **向量化** → embedding 异步入库,立刻可搜。 + +页面什么时候出?不出。等你或者 agent 真的需要的时候,按需编译一次——只取检索到的 evidence chunks,引文也只绑到这几个 chunk 上。 + +--- + +## 系统页:overview 和 log + +每个 KB 自动有两条**系统页**: + +- `slug=overview` —— 知识库的门面。范围、最近更新、覆盖率统计的摘要。 +- `slug=log` —— 入库 / 编译 / 编辑活动的可审计记录。 + +两者 `page_type=system, locked=1`: + +- 删除(单条 / 批量 / 重处理时的旧页清理)**删不掉**——会拿到清晰的拒绝理由。 +- 列表、关键词搜索、语义搜索、关联推荐**默认过滤掉**它们,避免污染检索结果和上下文窗口。 +- 但 agent 直接按 slug 读(`wiki_read_page("overview")`)仍然可以——想看就显式读。 + +> 普通用户也可以给手写的页面打上 `locked=1`,AI 工具就不会动它,逻辑跟 `lastUpdatedBy="manual"` 是叠加的。 + +--- + +## 加工器(Transformations):让知识库可编程 + +::: tip 1.3.0 新增 +Transformations 引擎自 v1.3.0 起提供。v1.2.0 及更早版本里,Wiki 只能被动检索——把原料切块、向量化、等召回;这一版起 Wiki **学会主动加工**:用户自定义模板、跨原料聚合、reverse-citation、JSON 输出、对页面跑模板、cancel/re-run 等能力全部到位。详细 release 故事见 [v1.3.0 release notes](./releases/1.3.0)。 +::: + +Wiki 默认把原始材料消化成它认为重要的页面 —— 但"重要"是它定义的,不是你定义的。**加工器**翻转了这件事:你写 prompt 模板,告诉系统"我想从材料里抽什么",引擎替你跑、落库、维护。 + +`Wiki → [任一知识库] → 加工器` 进入面板。每个模板由这几样组成: + +- **标识名** —— 短的小写 slug,Agent 调用时用它指名(如 `contract-risk-extract`) +- **显示名 / 描述** —— 给人看的 +- **提示词模板** —— 你的指令,支持 `{input_text}` 和 `{title}` 占位符 +- **模型** —— 默认走 KB 默认 chat 模型;也可以把单个模板钉到一个特定模型上 +- **默认运行** —— 勾上 → 每次新材料处理完,自动跑这个模板 +- **输出去向** —— `不保存`(只留运行历史) / `保存为 Wiki 页面`(自动产生 synthesis 页) +- **输出格式** —— `Markdown` 或 `JSON`(带可选 Schema 校验) + +### 开箱即用的 7 个企业模板 + +新建 KB 直接看到,覆盖典型企业场景: + +| 模板 | 用途 | +|---|---| +| `contract-risk-extract` | 合同条款级风险提取(高 / 中 / 低)+ AI 建议改写 | +| `meeting-action-items` | 会议纪要 → 决议 + 行动项(owner / 截止日 / 验收标准)| +| `customer-profile` | 客户邮件 / CRM 记录 → 结构化客户画像 | +| `competitor-update` | 公开信号 → 竞品动态简报 | +| `resume-structured-extract` | 简历 → 标准档案(教育 / 工作 / 技能 / 亮点)| +| `incident-postmortem` | 事故报告 → 5-Why 链 + 整改清单 + 相似事故 | +| `paper-imrad` | 论文 → IMRaD 摘要 + 关键术语 | + +### 四种触发方式 + +| 触发 | 怎么发起 | 用在哪 | +|---|---|---| +| **手动** | UI 选材料 + 点「运行」 | 调 prompt、单次试运行 | +| **默认运行** | 模板开关 + 上传新材料 | "每份新合同都自动跑一次" | +| **Agent 工具** | 数字员工调 `wiki_apply_transformation(name, rawId)` | Agent 自己决定要跑哪个 | +| **跨原料聚合** | 卡片上的「聚合所有运行」按钮 / `wiki_aggregate_transformation` | 把 N 份材料的 per-source 输出 map-reduce 成一份 KB 级合成页 | + +### 输入:原始材料 / 现有页面 + +模板不止能跑在 raw material 上,也能直接对现有 wiki 页面运行(Agent 端工具:`wiki_apply_transformation_to_page(name, slug)`)。这让你把模板串起来 —— 先用 A 把原料做成 synthesis 页,再用 B 对那个页面跑出新的视图。 + +### 输出去哪:留在历史还是变成 Wiki 页面 + +- **不保存** —— 运行结果只在「加工器」tab 的运行历史里可查,不进 Wiki。适合一次性临时输出。 +- **保存为 Wiki 页面** —— 每次成功运行 → 自动 upsert 到一个固定 slug 的 synthesis 页(`<模板名>-<材料标题>`)。重跑只更新这页,不复制。**而且**: + - 自动建 page-level embedding,进语义搜索 + - 反向解析输出里的「第 N 题 / 第 X 页」标记 → 回写 chunk-级 citation,绑回原文 + - 进关系图、热缓存、Agent 直接可读 + +### JSON 输出 + Schema 校验 + +输出格式选 JSON 时: + +1. 注入严格 system prompt("只返回 JSON 对象,前后无文字") +2. 解析失败 → 自动重试一次,retry 时附上具体错误提示 +3. 模板上可选填一个 JSON Schema,executor 在解析后检查 required 字段是否齐 +4. 仍然失败 → run 标记 failed,错误写进历史 + +成功时 JSON 以 fenced ```json 块的形式存进 page,下游程序可直接 grep + parse。 + +### 跨原料聚合 + +KB 里 10 份合同每份都跑了 `contract-risk-extract`,怎么看整体?卡片上点「聚合所有运行」: + +- 系统读取该模板对该 KB 的所有 completed run +- 按 source 去重(每份原料只取最新一次) +- LLM 做 merge + dedupe:去重相同条款类型、合并 source 引用、保留分歧 +- 产出 KB 级合成页 `<模板名>-aggregate` +- 自动嵌入语义搜索 + +这是把 "per-source extract" 升级成 "KB-level synthesis" 的关键 —— 不用一份份对比,AI 帮你看全局。 + +### 运行历史 + 可观测性 + +每条 run 都记录: + +- 状态:`pending / running / completed / failed / cancelled` +- 耗时、模型、触发方式(manual / apply_default / agent_tool / aggregate) +- 上行 / 下行 token 消耗(`8.2k↑ / 1.1k↓`),跨重试累加 +- 关联输出页面(如果 output_target=page) +- 完整输出 / 错误信息 + +UI 上能做: + +- **取消** —— 标记一个正在跑的 run 为 cancelled(LLM 调用仍在 provider 端走完,但执行器会丢弃结果) +- **重试** —— 失败 / 完成的 run 一键再跑一次(同输入) +- **对比** —— 勾两条 completed run → 「对比所选」→ side-by-side 模态框,左旧右新,调 prompt 时看差异最方便 + +### 典型场景 + +| 场景 | 配方 | +|---|---| +| 法务自动化 | `contract-risk-extract` + 默认运行 + 保存为页 → 每份合同自动生成风险报告页 | +| 销售情报 | `customer-profile` + 默认运行 → 每份客户材料合成画像页 | +| 研发记忆 | `meeting-action-items` + `incident-postmortem` 一起用 → 决策史和事故知识沉淀 | +| 研究综述 | `paper-imrad` 跑一批论文 → 「聚合所有运行」生成主题综述 | +| 程序化下游 | JSON 输出 + Schema → 把 wiki 当成结构化数据源 | + +### REST 端点(基础路径 `/api/v1/wiki/transformations`) + +| Method | Path | 作用 | +|---|---|---| +| `GET` / `POST` / `PUT` / `DELETE` | `/`、`/{id}` | 模板 CRUD | +| `POST` | `/{id}/apply?sync=true` | 跑一次(body 给 `rawId` 或 `pageId` 二选一)| +| `POST` | `/{id}/aggregate?kbId=X` | 跨原料聚合 | +| `GET` | `/runs?rawId=` 或 `?kbId=` 或 `?transformationId=` | 查运行历史 | +| `POST` | `/runs/{runId}/save-as-page` | 手动把一条 run 落成 wiki 页面 | +| `POST` | `/runs/{runId}/cancel` | 标记 cancelled | + +--- + +## Agent 怎么用 Wiki + +在 `Agents → 某个 Agent → 知识库` 里绑定一个知识库。从那一刻起: + +- Agent 的 system prompt 里自动注入这个 KB 顶层页面的压缩摘要。 +- Agent 的工具箱里多了这些 wiki 工具: + +| 工具 | 用途 | +|---|---| +| `wiki_search_pages` | 混合检索(关键词 + 语义),页面级 | +| `wiki_semantic_search` | chunk 级语义搜索,命中带 `pageNumber` 和 `section` 字段 | +| `wiki_read_page` | 读单页,可按 section 或字符上限截取 | +| `wiki_read_many` | **新**:一次拿多个 slug 的内容,最多 10 个,每页可设上限。替代多轮 `wiki_read_page` | +| `wiki_compile_page` | **新**:lazy 模式下按主题生成单页。引文只绑搜到的 evidence chunks | +| `wiki_trace_source` | 跟踪某个 wiki 页面来自哪些原文 | +| `wiki_related_pages` | 关联页面(共享 chunk / 共享原文 / 双向链 / 语义近邻) | +| `wiki_explain_relation` | 详细拆解两页之间的关联强度和原因 | +| `wiki_create_page` / `wiki_delete_page` | 直接维护页面(删除受 locked / system 保护) | +| `wiki_archive_page` / `wiki_unarchive_page` | 软归档:从默认 list/search/related 隐藏,但保留页面与引文,可恢复。系统页不能归档。 | +| `wiki_list_transformations` | 列出当前 KB 可用的加工器模板(名称、用途、是否默认运行)| +| `wiki_apply_transformation` | 对一份**原始材料**运行一个模板,返回输出(runId / output / 落页信息)| +| `wiki_apply_transformation_to_page` | 对一个**现有 wiki 页面**运行模板(接 slug,无需数字 ID)| +| `wiki_aggregate_transformation` | 跨 KB 内所有 raw 的同模板 run,合成一份 KB 级 synthesis 页 | + +`kbId` 参数根据绑定自动解析——Agent 不需要猜。 + +一个典型的 Agent 回合: + +> **用户**:"上个季度我们关于重试策略是怎么决定的?" +> +> **Agent**:*(读到注入的摘要里有"重试策略"页,直接打开"重试策略"页,返回决策内容和原文来源链接。)* + +那不是一次向量查询。是字面意义上的"打开这一页"——因为**这一页真实存在**。 + +### 热缓存:每次系统提示里都有一份"最近活跃"快照 + +绑定的 KB 不光给 Agent 注入摘要,还会注入一份**热缓存**——可以理解为 Agent 每一轮开局都先翻一遍的那一页: + +- **最近更新**——最近一次入库 / 页面编辑 +- **关键近期事实**——重建器筛出的高信号要点 +- **最近变更**——上次重建以来新生成 / 重新编译的页面 +- **悬而未决的话题**——开放问题和未结论的决策 + +重建在每次会话结束(`ConversationCompletedEvent`)异步触发,配合一个可配置的去抖窗口(默认约 30 秒),短轮次密集发生时不会把 LLM 打爆。Admin 也可以手动触发重建——手动路径会绕开去抖。 + +注入受 `wiki.hot_cache.enabled` 特性开关控制(关闭 → 注入空字符串),并按 KB 优先级最多挑前两个,避免系统提示被撑爆。 + +#### 在 KB 详情抽屉里管理 + +`Wiki → [你的 KB] → 热缓存` 面板里: + +- **重新生成**——异步手动重建;点完面板会在几秒后轮询并刷新 +- **重置**——软删除当前行,下一次 `ConversationCompletedEvent` 时重建 +- 元数据栅格:上次更新时间、更新原因(`AUTO` / `MANUAL` / `EVENT`)、重建次数、上次耗时(毫秒) +- 上次重建失败时显示错误条 +- 渲染后的 Markdown 内容预览 + +#### 运维端点 + +基础路径 `/api/v1/wiki/hot-cache`: + +| Method | Path | 作用 | +|---|---|---| +| `GET` | `/{kbId}` | 拿当前快照 + 元数据 | +| `POST` | `/{kbId}/regenerate` | 手动重建(异步,跳过去抖) | +| `DELETE` | `/{kbId}` | 软删除;下次事件触发重建 | + +热缓存数据落在 `mate_wiki_hot_cache`——具体列见下面的 **底层数据** 一节。 + +### Lazy 上传后的典型流程 + +``` +用户上传 product-manual.pdf (lazy 模式:0 次页面生成 LLM 调用) + ↓ +Agent: wiki_semantic_search("error code 500 retry") + → 命中 chunk #1234,page=12,section "Error Handling / Retries" + ↓ +Agent: wiki_compile_page(topic="500 retry policy", maxEvidenceChunks=5) + → 生成 slug=500-retry-policy,引文绑到这 5 个 chunk + ↓ +Agent: wiki_read_page("500-retry-policy") + → 返回结构化页面 + 来源 chunk 列表 +``` + +整条路径只在最后一步烧了一次 LLM 调用,且只针对真正相关的 5 个 chunk。 + +--- + +## 页面的阅读和编辑 + +每一个生成出来的页面都是一等公民的文档,你可以在 Wiki 视图里直接打开: + +- Markdown 渲染带语法高亮 +- 侧边栏列出反向链接——看看有哪些别的页面引用了它 +- 每个说法上都有"来源"按钮,点一下跳到 Wiki 所依据的原文段落 +- 编辑模式下可以直接重写页面的文字 +- system / locked 页面的删除按钮是禁用状态 + +AI 写错了就改。你的修改在下一次入库时会被保留——`locked` 标记告诉 digester 别碰这段人写的内容。要让 AI 重新起草就显式解锁。 + +--- + +## 搜索、来源追溯、语义检索 + +- **语义搜索**——问"我们关于 auth 决定了什么?",直接返回那个决策,不是一堆包含"auth"的页面。chunk 级嵌入 + cosine 检索,**理解你问的是什么意思**。命中现在自带 `pageNumber` 和 `section`,agent 可以引用 "page 12, Setup / Linux" 而不是粘一段没头没尾的片段。 +- **混合检索**——同时走全文匹配和语义匹配,取两者的交集优势。 +- **全文搜索**——搜标题、摘要、正文、概念抽取。横跨你有权访问的所有 KB。 +- **来源追溯**——任何页面的任何一句话都可以点回原文段落。Agent 也能做这件事。 +- **反向链接**——每一页都显示有哪些别的页面引用了它。`[[concept|展示文字]]` 形式的 alias 链接现在解析正确了——只把 `concept` 当 slug,`展示文字` 只用于显示。 +- **关联推荐**——跨四种信号(共享 chunk、共享原文、双向链、语义近邻)找相关页面,扩展时**不会**把 overview / log 拉进来当种子。 +- **人工编辑保护**——locked / 编辑过的页面不会在再次入库时被覆盖;要重写就显式解锁。 + +--- + +## 视觉管线:图片也能被读出来 + +读不了图的 wiki 是半瞎的。PDF 尤其严重——一半的真信息往往就在那些图里。 + +打开 `wiki.ocr.enabled` 特性开关之后,MateClaw 会把每一张上传图片——以及**嵌在 PDF 页面里**的每一张图——都过一遍视觉管线,提取**配字描述**和**图中可见文字**,作为一等公民的 chunk 和上下文文字一起入库。检索找得到、Agent 引用得到,搜索结果里图片也会作为缩略图就地显示,点一下放大成灯箱。 + +### 工作流程 + +1. **哈希**图像字节(SHA-256)——缓存按内容寻址,所以同一张图换个 KB 重传,零成本。 +2. **查 `mate_wiki_image_caption_cache`**——命中直接复用配字、`hit_count` 加 1。 +3. 未命中就**按顺序走配置好的视觉 provider**,第一个返回非空 caption 的获胜。 +4. **写回缓存**:caption + visible text + provider id + model + 耗时(race-tolerant insert,并发上传同样 OK)。 +5. `VisionResult` 回到 chunker,作为该图所在页的额外内容。 + +### 支持的视觉 Provider + +| Provider id | 模型 | 备注 | +|---|---|---| +| `dashscope-vision` | `qwen-vl-max` | DashScope 兼容模式,复用 UI 里配好的 DashScope provider | +| `zhipu-vision` | `glm-5v-turbo` | 智谱 BigModel,OpenAI 兼容 | +| `volcano-doubao-vision` | 可配置 | 字节跳动火山豆包视觉 | + +Provider 按 order 自动选用。Key / base URL 都在 `Settings → 模型` 里像普通 provider 那样配,视觉管线会从那里取凭证。 + +### 切换开关 + +`Settings → Feature Flags → wiki.ocr.enabled`。轻量部署默认关;至少配好一个视觉 provider 之后再打开。 + +开关**关闭**时管线会短路——上传仍然成功,只是图像 chunk 没有 caption。这些图的 `extracted_text` 缓存是**延后**而不是被污染,所以你下次重新打开开关,新上传的图会自动配字,老图也不用强制重建。 + +### UI 上看得到的变化 + +- 命中含图片证据的搜索结果会内联显示缩略图,点开就是全分辨率灯箱。 +- 原始材料详情抽屉里每张抽出来的图片旁边显示 caption,方便你对照模型到底"看到了"什么。 + +--- + +## 健康感知的 LLM 降级 + +Wiki 入库本身就很烧 LLM 调用,过去任何一个 provider 卡住都会拖死整批。现在每一个 wiki 步骤(`route` / `create_page` / `merge_page` / `enrich` …)都走**健康感知**的降级链:主模型报错或超时,就把 KB `fallback` 列表上的下一个模型试一次。每个 provider 的健康度(成功 / 失败 / 延迟)会被独立追踪,抖动严重的 provider 会被自动降权,等它恢复再回来。 + +降级链在 `Wiki → 配置 → 模型策略` 里、按步选模型旁边配。 + +--- + +## 模型策略真生效 + +`Wiki → 配置 → 模型策略` 里给每个步骤选不同模型: + +```text +heavy_ingest.route → 便宜的小模型,做路由 +heavy_ingest.create_page → 强模型,写完整页面 +heavy_ingest.merge_page → 强模型,合并已有页面 +light_enrich.enrich → 便宜的小模型,标 wikilink +``` + +回退顺序: + +```text +stepModels[step] → wikiDefaultModelId → 系统默认模型 +``` + +之前 UI 上的这套配置在 Java 端只是摆样子(字段没承接),现在每一次 LLM 调用真的按它选——route / create / merge / retry / repair / 文档分析 都是。 + +--- + +## 底层数据(如果你好奇) + +九张表: + +| 表名 | 用途 | +|------|------| +| `mate_wiki_knowledge_base` | 每个 KB 一行。owner、名字、描述、配置 JSON(含 `ingestMode` / `wikiDefaultModelId` / `stepModels` 等)。 | +| `mate_wiki_raw_material` | 每份上传一行。状态、byte hash、来源路径、上次成功处理时的 hash。 | +| `mate_wiki_page` | 每个生成页面一行。标题、摘要、正文、`source_raw_ids`(回指原文)、`page_type`、`locked`、版本号,外加 `embedding` / `embedding_model` / `embedding_text_version` 让 synthesis 页直接进语义搜索。 | +| `mate_wiki_chunk` | 每个 chunk 一行。content + hash + 偏移 + embedding,外加 `page_number` / `header_breadcrumb` / `source_section` / `token_count`。 | +| `mate_wiki_relation` | 缓存的页对页边(共享 chunk / 共享原文 / 直接链接 / 语义近邻),用于检索时的 1 跳关系 boost 和关联推荐工具。 | +| `mate_wiki_hot_cache` | 每个 KB 一行。渲染后的 Markdown 快照 + `last_updated` / `update_reason` / `rebuild_count` / `last_rebuild_duration_ms` / `last_rebuild_error`。 | +| `mate_wiki_image_caption_cache` | 视觉管线提取出的 caption 缓存,按 SHA-256 索引。`caption` / `visible_text` / `mime_type` / `capture_model` / `provider_id` / `duration_ms` / `hit_count`。 | +| `mate_wiki_transformation` | 每个加工器模板一行。`name` / `title` / `description` / `prompt_template` / `model_id` / `apply_default` / `output_target` / `output_format` / `output_schema`。`kb_id=NULL` = 工作区全局可用。 | +| `mate_wiki_transformation_run` | 每次模板运行一行。`status` / `output` / `error` / `duration_ms` / `model_id` / `triggered_by` / `input_tokens` / `output_tokens` / `total_tokens` / `output_page_id`。 | + +`mate_wiki_page` 还带两个保护字段: + +- `locked`(V40)—— 1 = 禁止 AI 工具 / 批量操作 / 重处理清理删改本页。系统页 `overview`/`log` 默认 `locked=1`,但用户也可以给任意手写页面打上。 +- `archived`(V41)—— 1 = 软归档,从默认 list / search / related 结果消失,但页面、引文、反向链全保留。可恢复。 + +### 几个运维 endpoint + +不靠 cron / 事件钩子兜底,想立刻刷一下的时候用: + +| Endpoint | 作用 | +|---|---| +| `POST /api/v1/wiki/admin/kb/{kbId}/rebuild-overview` | 立即按当前数据重写 overview marker 区域 | +| `POST /api/v1/wiki/admin/backfill-tokens` | 立即跑一批 token_count 回填,返回 `pendingBefore/pendingAfter/filledThisBatch` | + +`application.yml` 的 `mate.wiki` 配置块控制切块大小、并发度、auto-process 等全局参数;具体到每个 KB 的入库模式 / 模型策略 / 备选模型链,写在 KB 的 `configContent` JSON 里——前端配置页直接编辑。 + +> 旧 chunk 的 `token_count` 列允许 NULL;后台有个低频 cron `WikiChunkTokenBackfillJob` 用 `ceil(charCount / 4)` 按批回填,不影响主链路。 + +--- + +## 什么时候该用它 + +用 Wiki KB 当你有: + +- 同一主题下不止几份文档 +- 希望人也能读能改、不只是被检索的内容 +- 需要跨 Agent、跨会话持续存在的信息 +- "这个说法从哪来的"这件事很重要的资料 + +如果只是想把一份 PDF 扔进一次对话,在聊天里直接附件就好。Wiki 是给值得一个书架的材料准备的。 + +模式选择经验: + +- 你**马上**要拿 Wiki 来人读 / 演示 / 分享:eager。 +- 你只是先把资料倒进来,等 agent 用到才考虑要不要固化成页面:lazy + 按需编译。 +- 量大、模型贵、不确定每篇都需要完整页面:lazy 默认更省。 + +--- + +## 下一步 + +- [Agent 引擎](./agents) —— 把 Agent 绑定到 KB +- [记忆系统](./memory) —— Wiki 和记忆的区别(提示:Wiki 是刻意的,记忆是被动的) +- [API 参考](./api) —— Wiki 的 REST 接口 diff --git a/mateclaw-server/src/main/resources/docs/zh/workflow.md b/mateclaw-server/src/main/resources/docs/zh/workflow.md new file mode 100644 index 00000000..f1d78e47 --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/workflow.md @@ -0,0 +1,336 @@ +# 工作流(Workflow) + +::: tip 1.3.0 新增 +工作流编排自 v1.3.0 起提供。在 v1.2.0 及更早版本里没有这个能力。 +::: + +**工作流是什么**:把多个数字员工 + 系统操作(审批 / 渠道分发 / 写记忆)按线性 step 编排成一条业务流程。每一步可以被前一步的输出条件控制,可以并行扇出,可以等待人工审批,可以把结果写进员工的 MEMORY.md。 + +**工作流不是什么**: +- 不是 ReAct / Plan-and-Execute 的替代品——单 agent 的多轮推理仍然在那两条引擎里 +- 不是可视化拖拉框框生成 if/else 的低代码工具——v0 是 **JSON 优先**(v1 才上画布) +- 不是 30+ 节点的 Dify-style 编排——MateClaw 工作流刻意保持极简:**线性 step 数组 + 一个 mode 字段表达控制流** + +::: warning v1.3.0 范围 +v0 = internal alpha。**7 种 step mode + 6 种 trigger pattern**。`loop` / `invoke_skill` 留给后续版本。生产场景请先在标杆账号 / 内部 workspace 跑通再推广。 +::: + +--- + +## 一分钟看懂 + +```json +{ + "schemaVersion": "1.0", + "inputs": [ + { "name": "customer", "type": "json" } + ], + "steps": [ + { + "name": "enrich", + "agentName": "data-analyst", + "promptTemplate": "Enrich and return strict JSON: {{ inputs.customer | toJson }}", + "mode": { "type": "sequential" }, + "outputVar": "enriched", + "outputContentType": "json" + }, + { + "name": "vip-route", + "agentName": "enterprise-sales", + "promptTemplate": "VIP onboarding for {{ outputs.enriched.name }}", + "mode": { + "type": "conditional", + "expression": "{{ outputs.enriched.tier == 'enterprise' }}" + } + }, + { + "name": "notify-feishu", + "agentName": "ops-bot", + "promptTemplate": "Notify feishu: {{ outputs.enriched }}", + "mode": { "type": "fan_out" } + }, + { + "name": "notify-email", + "agentName": "ops-bot", + "promptTemplate": "Notify email: {{ outputs.enriched }}", + "mode": { "type": "fan_out" } + }, + { + "name": "wait-acks", + "mode": { "type": "collect" } + }, + { + "name": "record", + "promptTemplate": "Onboarded {{ inputs.customer.name }}", + "mode": { + "type": "write_memory", + "employeeId": "{{ outputs.enriched.assignedEmployeeId }}", + "file": "MEMORY.md", + "mergeStrategy": "append" + } + } + ] +} +``` + +读法: +1. `enrich` 让数据员工把客户信息结构化为 JSON +2. 如果客户层级是 `enterprise`,让企业销售员工跑 VIP onboarding +3. 同时(fan_out)扇出到飞书通知 + 邮件通知 step +4. `collect` 等两个通知都返回再继续 +5. 把结果追加写到员工的 `MEMORY.md` + +--- + +## 核心概念 + +### Step 七种 mode(v1.3.0) + +| Mode | 行为 | 必填字段 | 关键语义 | +|---|---|---|---| +| `sequential` | 顺序执行;上一步 output → `{{input}}` | — | 默认 mode | +| `fan_out` | 与连续后续 fan_out 并行;都收到同一 `{{input}}` | — | 边界由编译期检测:从此 step 开始,遇到第一个非 fan_out / 非 collect 的 step 即停 | +| `collect` | 把前面**最近一组** fan_out 的输出按 `\n\n---\n\n` 拼接为 `{{input}}` | — | 前面必须存在至少 2 个连续 fan_out;编译期校验 | +| `conditional` | Pebble 表达式 true 才执行 | `expression` | false 时跳过;`{{input}}` 不变(保留上一步) | +| `await_approval` | 暂停 run,发审批 | `approvalKind`、`approverChannels[]` | resume 后继续下一步;超时按 workspace 政策处理 | +| `dispatch_channel` | 把 `{{input}}` 多渠道分发 | `channels[]` | 单渠道失败按 errorMode 处理 | +| `write_memory` | 写员工记忆文件 | `employeeId`、`file`、`mergeStrategy` | 4 种策略:`append` / `replace_section` / `upsert_kv` / `overwrite` | + +> **不在 v1.3.0 里**:`loop`(迭代 N 次或对数组逐项处理)、`invoke_skill`(直接调用 skill 不经过员工)。等用户反馈再加。 + +### 表达式:Pebble 子集 + +工作流**不**用全功能模板引擎——它支持的是 Kestra 同款 Pebble 子集,只够做条件判断和变量引用,不能跑代码。 + +| 类别 | 语法 | +|---|---| +| 变量引用 | `inputs.X` / `outputs.varname.field` / `vars.X` / `now` / `flow.id` | +| 操作符 | `==` `!=` `<` `<=` `>` `>=` `and` `or` `not` `+` `-` | +| 内建过滤器 | `length` / `lower` / `upper` / `default('x')` / `toJson` / `fromJson` / `date(format)` | +| JSONPath | `\| jq('.field.subfield')` | +| 字符串测试 | `\| contains('x')` / `\| startsWith('x')` / `\| matches('regex')` | + +**不支持**(编译期拒): +- 自定义函数 / 宏定义 +- include / extends +- 文件 I/O / 网络 I/O +- 任何副作用操作 + +### 输出类型:text vs json + +每一步的 `outputContentType` 决定下游怎么访问它: + +| outputContentType | 默认 | Pebble 访问规则 | +|---|---|---| +| `text` | ✅ | `outputs.X` 是字符串;`outputs.X.field` **编译期错**;`\| jq(...)` **运行时错** | +| `json` | — | 运行时 `JSON.parse` 解析;失败按 `errorMode` 处理;字段访问 / `jq(...)` 合法 | + +**Agent step 默认 `outputContentType=text`**——LLM 自然语言输出本来就不是 JSON。要做条件分支或字段访问,必须: +1. 在 `promptTemplate` 里**明确**让 LLM 输出严格 JSON("return strict JSON: {...}") +2. 把这一步的 `outputContentType` 设为 `json` + +### 编译期非法组合(发布时拒) + +| 组合 | 拒原因 | +|---|---| +| 连续多个 `fan_out` 后没 `collect` | `{{input}}` 进入下一步歧义 | +| `collect` 没有前置 `fan_out` | 没东西可 collect | +| `fan_out` 组里混入 `await_approval` | 多审批并发同时触发,没法聚合 | +| `agentName` 指向员工不存在 / 已禁用 / 跨 workspace | ACL 失败 | +| Pebble 表达式引用未声明的变量 | 编译期 | +| `outputs.X.field` 但 step X 是 `text` 类型 | 编译期类型错 | +| `dispatch_channel` 引用的 channel 不在 workspace allowlist | ACL | +| `write_memory` 引用的 employeeId 跨 workspace | ACL | +| step 数 > 200(默认上限) | 防失控配置 | + +发布时跑 `WorkflowCompiler.validate(graphJson) → List`,每个错误指到 step name + 字段路径,UI 上 Monaco 编辑器直接标红。 + +--- + +## 在 UI 里用工作流 + +### 入口 + +`Workflows`(侧栏)→ 列表 → 点 **+ 新建**。 + +::: tip +新装的实例 Workflows 列表是空的。这是故意的——v0 不内置模板,由用户和标杆客户共建。 +::: + +### 编辑器(v1.3.0 = JSON only) + +- **Monaco 编辑器**:JSON schema 校验 + 自动补全 + Pebble 表达式静态检查 +- **模板下拉**:从 `GET /api/v1/workflows/draft/templates` 拉的内置骨架 +- **预编译**:`POST /api/v1/workflows/{id}/compile` 拿 compile diagnostics,**不写 revision、不真跑** +- **发布**:跑编译 → ACL 校验 → 写一个新的 `mate_workflow_revision` 行(整数版本号 +1) + +::: warning v1 才上画布 +`@vue-flow/core` 画布在 v1.3.0 里**已经有 UI 雏形**,但它把 step 数组渲染为节点链,**不是**可拖拉编辑——双击节点弹出对应字段表单,主编辑路径仍是 JSON。完整的可视化拖拉编辑推到 v1.4+。 +::: + +### 自然语言 → 工作流草稿(v1.3.0) + +`POST /api/v1/workflows/draft/generate` 接受一段自然语言描述("我要一条客户工单分流流程,飞书入口,按客户层级路由 enterprise / pro / standard 分别走不同处理人"),让一个内置 agent 生成对应 graph_json,并**立即编译返回**——附带 compile diagnostics。 + +适用场景: +- 不熟 JSON DSL 的用户先生成一稿可发布的草稿,再去 Monaco 里调 +- 老 SOP 文档批量灌进生成器,快速得到候选工作流模板 +- 客户共建时,把"做完这件事我希望它如何运转"的口语描述快速可视化 + +返回结构: +```json +{ + "graphJson": "...", // 可直接 PUT 进 draft 的 JSON + "compileErrors": [...], // 同 /compile 一致的诊断 + "modelUsed": "qwen-plus", + "tokenUsage": { ... } +} +``` + +::: tip 不替代 Monaco 编辑 +生成器**不会**直接发布——它只生成草稿(`saveDraft`),仍要走人工审阅 → 编译 → 发布。生成的 JSON 会带编译错误也不奇怪,作者修完再发布。 +::: + +### 运行历史 + +每条 run 都持久化为 `mate_workflow_run` + `mate_workflow_run_step`。详情页: +- 每个 step 的 input / output(payload URI 引用) +- 每步耗时 + token 消耗 +- 跨 step 失败链路高亮 +- await_approval 暂停时显示等谁审批 + 等了多久 + +### 触发方式 + +工作流的实际启动只能通过 [触发器(Triggers)](./triggers.md) 或 `await_approval` 恢复——v0 没有"立即手动跑一次"的 endpoint。详见上方 API 参考。 + +--- + +## API 参考 + +所有 endpoint 都在 `/api/v1/workflows/` 下,请求要带 `X-Workspace-Id` header。 + +### CRUD + +| 方法 | 路径 | 说明 | +|---|---|---| +| `GET` | `/api/v1/workflows` | 列出当前 workspace 的所有工作流 | +| `POST` | `/api/v1/workflows` | 新建工作流(草稿初始为空) | +| `GET` | `/api/v1/workflows/{id}` | 获取工作流元信息 + 内联草稿 | +| `PUT` | `/api/v1/workflows/{id}` | 更新工作流元数据(name / description / enabled) | +| `PUT` | `/api/v1/workflows/{id}/draft` | 保存内联草稿 graph_json(不编译) | +| `DELETE` | `/api/v1/workflows/{id}` | 软删工作流 | + +### 编译 / 发布 + +| 方法 | 路径 | 说明 | +|---|---|---| +| `POST` | `/api/v1/workflows/{id}/compile` | 编译当前草稿,返回 compile diagnostics,**不写 revision** | +| `POST` | `/api/v1/workflows/{id}/publish` | 编译 + 写新 revision;自动指向 `latest_revision_id` | + +### 草稿生成器(v1.3.0 内置) + +| 方法 | 路径 | 说明 | +|---|---|---| +| `GET` | `/api/v1/workflows/draft/templates` | 列出内置草稿模板 | +| `POST` | `/api/v1/workflows/draft/preview-compile` | 任意 graph_json 试编译——不需要先建 workflow 行就能拿到诊断 | +| `POST` | `/api/v1/workflows/draft/generate` | **自然语言 → 工作流草稿**——你描述需求,agent 生成 graph_json + 编译诊断 | + +### 运行查询 / 恢复 + +| 方法 | 路径 | 说明 | +|---|---|---| +| `GET` | `/api/v1/workflows/{id}/runs?limit=...` | 列某 workflow 最近 run(默认 50 条) | +| `GET` | `/api/v1/workflows/runs/paused?limit=...` | 列当前 workspace **所有 paused run**(运维入口) | +| `GET` | `/api/v1/workflows/runs/{runId}` | 单个 run 详情 + 全部 step 行(input / output / duration) | +| `POST` | `/api/v1/workflows/runs/{runId}/resume` | 从 `await_approval` 暂停恢复(系统在审批通过时自动调用,不需要前端手动调) | + +::: warning v0 没有"启动 run"的独立 endpoint +工作流 run 的实际启动路径只有两条: + +1. **通过 trigger**——在 [Triggers](./triggers.md) 配一条 trigger 指向这个 workflow(`target_type=workflow`),事件到了引擎自动启动 run +2. **通过 `await_approval` resume**——`/runs/{runId}/resume` 把 paused run 推下去 + +v0 **没有** `POST /api/v1/workflows/{id}/runs` 这种"立即手动跑一次"的 API。要做"试运行",用 `/draft/preview-compile` 拿编译结果(**只编译、不入库、不真跑**),或者临时挂一条 webhook trigger 触发。手动启动 run 在 RFC 里已规划,会在后续版本加。 +::: + +--- + +## 安全模型 + +### 三层 ACL 角色 + +| 角色 | 能干什么 | +|---|---| +| `workflow:author` | 编辑 draft、读自己的 run | +| `workflow:publisher` | 发布 revision;发布期跑静态 ACL 检查 | +| `workflow:operator` | 启停 trigger、cancel run、查看其它人的 run | + +### Step 执行身份 + +每个 step 在 ExecutionContext 携带: +- `workspaceId`:必须等于 workflow 的 workspace +- `actingAgentId`:sequential / 3 个 MateClaw mode → 该 step 的 agent;其它 mode → 发布者作为 fallback +- `triggeredBy` / `workflowId` / `revisionId` / `runId`:审计追溯 + +### 跨 workspace 隔离 + +发布期跑 `WorkflowAclValidator.checkAll(graphJson)`: +- `agentName` 引用的员工必须在当前 workspace 内 +- `dispatch_channel` 的 channel 必须在 workspace allowlist 内 +- `write_memory` 的 employeeId 必须在当前 workspace 内 + +任一不通过 → 发布失败,事务回滚,**不写 revision、不更新 latest_revision_id**。 + +### 与 [MCP 每 agent 工具绑定](./mcp.md) 的关系 + +工作流**不能**给员工额外的工具。Agent step 调工具时会跑 `AgentBindingService.getEffectiveToolNames(agentId)` 算出的同一套 ACL——员工在工作流里能用什么工具,跟它在普通对话里能用什么工具完全一致。 + +--- + +## 内置存储 URI(payload) + +工作流的输入 / 输出 / 中间产物如果超过 4KB 默认阈值,会被自动写入 `mate_workflow_payload` 表(v1.3.0:单库存储)或本地文件系统兜底,并在 graph 里以 `payload://` URI 替代。这避免了大上下文撑爆 message column 的问题——参考 commit `9c81dba0 feat(workflow): payload fs fallback for medium-size payloads`。 + +```text +payload://run/abc123/step/enrich/output → 实际存储位置由后端解析 +``` + +UI 渲染时按需 lazy-load。 + +--- + +## 数据模型 + +工作流系统涉及 8 张表: + +| 表 | 用途 | +|---|---| +| `mate_workflow` | 工作流主体(id / name / workspace) | +| `mate_workflow_revision` | 已发布版本(整数 revision;graph_json 整体快照;不可变) | +| `mate_workflow_run` | 一次执行(runId / triggerSource / status / startedAt / endedAt) | +| `mate_workflow_run_step` | run 内每一步的 input/output/duration | +| `mate_workflow_run_pause` | await_approval 持久化暂停状态(重启后能恢复) | +| `mate_workflow_payload` | 大 payload 内置存储(payload URI 解析目标) | +| `mate_trigger` | 触发器配置(含 cron pattern_version) | +| `mate_trigger_event` | 事件去重 + rate limit 历史 | + +--- + +## 已知限制(v1.3.0) + +- **没有可视化拖拉编辑**——画布是只读的链式渲染,主编辑路径是 JSON +- **没有 loop step**——做不了"对数组逐项处理"或"重试 N 次"。变通:用 `fan_out` 数个固定分支,或上层调度多 run +- **没有 invoke_skill step**——skill 必须挂在 agent 上由 agent 调用 +- **没有跨 workspace 共享**——同一份工作流模板要复用到多个 workspace 需要复制 +- **没有实时协作编辑**——同时间多人编辑同一草稿,**后写覆盖** +- **没有 step 级 retry policy**——`errorMode.retry` 是 step-wide 的;细粒度 retry 推到后续 + +--- + +## 相关链接 + +- [触发器(Triggers)](./triggers.md) —— 工作流的事件入口 +- [审批与安全](./security.md) —— `await_approval` 走的就是这条审批通道 +- [数字员工](./agents.md) —— Step 里 `agentName` 引用的就是它们 +- [多渠道接入](./channels.md) —— `dispatch_channel` 能投递到哪些渠道 +- [记忆系统](./memory.md) —— `write_memory` 写到员工的哪份文件 diff --git a/mateclaw-server/src/main/resources/docs/zh/workspaces.md b/mateclaw-server/src/main/resources/docs/zh/workspaces.md new file mode 100644 index 00000000..17fc596f --- /dev/null +++ b/mateclaw-server/src/main/resources/docs/zh/workspaces.md @@ -0,0 +1,258 @@ +# 工作空间 + +**一个工作空间就是一个团队所有东西外面的一个盒子。** + +MateClaw 在单次部署里支持多个团队的方式,是把每一种资源——Agent、技能、Wiki 知识库、会话、记忆文件、Tool Guard 规则、渠道——组织进**工作空间**。你登录时看到的是你所属的工作空间、其他什么都看不到。切换工作空间时,整个 UI 重新 scope:不同的 Agent、不同的技能、不同的知识、不同的渠道。 + +重点是**一个 MateClaw 部署可以同时服务一个产品组、一个工程组、一个研究组**,他们的数据、Agent、对话不会互相渗透。 + +--- + +## 什么属于工作空间 + +几乎所有东西。被 scope 的资源: + +| 资源 | 怎么 scope 的 | +|------|---------------| +| **Agent** | 每一行 Agent 都有 `workspace_id` 外键 | +| **技能** | 自定义和 MCP 技能按工作空间 scope;内置技能是全局的 | +| **Wiki 知识库** | 每个 KB 属于且只属于一个工作空间 | +| **会话和消息** | scope 到 Agent 所在的工作空间 | +| **工作空间记忆文件** | `workspace/{workspaceId}/{agentId}/...` | +| **渠道** | 每个渠道绑一个 Agent,所以传递地绑一个工作空间 | +| **Tool Guard 规则** | 规则可以是全局或 scope 到特定工作空间 | +| **File Guard 路径** | 允许/拒绝路径可以按工作空间 | +| **Cron 任务** | scope 到它触发的 Agent 所在的工作空间 | +| **数据源** | 外部 DB 连接,按工作空间 scope | +| **审计事件** | 每个审计事件记录它的 `workspace_id` | + +**不**被 scope 的(即全局的): + +- JWT secret 和认证配置 +- 模型供应商和 API Key(全局,但用量按工作空间追踪) +- MCP 服务定义(全局连接;工作空间访问由权限控制) +- `mate_system_setting` 里的系统级设置 +- 内置技能 + +--- + +## 工作空间角色 + +每个用户在工作空间里被分配四种角色之一: + +| 角色 | 能做什么 | +|------|----------| +| **Owner** | 所有事,包括删除工作空间和管理成员 | +| **Admin** | 除了删除工作空间或变更 owner 之外的所有事 | +| **Member** | 用 Agent、读写 wiki、创建会话、调用工具(受 Tool Guard 约束) | +| **Viewer** | 只读——看得到 Agent 和 KB、读会话、**不能创建或修改** | + +一个用户可以属于多个工作空间、**在不同工作空间有不同角色**。切换工作空间时,有效权限跟着切换。 + +### 角色的 scope + +角色控制 **UI 可见性**和 **API 访问**。控制台隐藏用户没权限用的菜单项——一个对某工作空间只有 viewer 角色的用户,**完全看不到**安全菜单或工作空间管理页面。后端在每个 API 端点上执行同样的规则,所以 viewer 打一个受保护的端点返回 `403 Forbidden`。 + +--- + +## 创建一个工作空间 + +`设置 → 工作空间 → 新建工作空间`。 + +1. 按"**团队在做什么**"起名,不是"团队叫什么"("产品调研"比"Alpha 组"好) +2. 可选描述 +3. 保存 + +你成为这个工作空间的 owner。现在可以邀请成员了。 + +### 走 API + +```bash +curl -X POST http://localhost:18088/api/v1/workspaces \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "name": "产品调研", + "description": "竞品调研和产品规格" + }' +``` + +--- + +## 邀请成员 + +`设置 → 成员 → 添加成员`。输入一个已存在的 MateClaw 用户名,选角色,保存。 + +成员**下次页面加载时**立刻在工作空间切换器里看到这个工作空间。没有邀请邮件,没有接受流程——成员的账号已经在 MateClaw 里了。 + +### 走 API + +```bash +curl -X POST http://localhost:18088/api/v1/workspaces/1/members \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "userId": 42, + "role": "member" + }' +``` + +--- + +## 切换工作空间 + +管理控制台左上角。点工作空间名字打开切换器,选另一个切。整个 UI 重新 scope: + +- 侧栏菜单按新工作空间的角色重新渲染 +- Agent 列表刷新显示这个工作空间里的 Agent +- Wiki 列表、技能列表、渠道列表等全部改变 +- 活跃的对话**保持打开**(它们属于自己的工作空间) + +工作空间选择**按用户持久化**——你再次登录时落到上次用的工作空间。 + +--- + +## 沿工作空间边界生效的安全基元 + +这是工作空间隔离真正起作用的地方。 + +### File Guard + +File Guard 的默认 allowed-path 列表是 `workspace/{workspaceId}/...`。工作空间 A 里一个 Agent 发出的工具调用**读不到也写不到**属于工作空间 B 的文件,不管它怎么用路径穿越的 trick——符号链接检查和路径规范化会逮住它。 + +### Tool Guard 规则 + +规则可以 scope 到特定工作空间。你可以有: + +- 一条**全局**规则说 `ShellExecuteTool` 需要审批 +- 一条**工作空间特定**的规则说命令匹配一个狭窄的只读模式时 `ShellExecuteTool` 被允许 + +**只有第二条规则在那个工作空间里生效。** 其他工作空间只看到全局规则。 + +### Wiki 知识库 + +Wiki KB 的数据**永远不会离开它的工作空间**。工作空间 B 里的 Agent 读不到属于工作空间 A 的 KB,**即使它尝试**。Wiki 检索和读取工具从绑定 Agent 的工作空间解析知识库 ID;跨工作空间读在 API 层被拒绝。 + +### 记忆文件 + +工作空间记忆文件(PROFILE.md、MEMORY.md、每日笔记)住在 `workspace/{workspaceId}/{agentId}/` 下面。File Guard 执行工作空间边界;记忆工具的 list/read/write 操作被限定到调用者的工作空间内。 + +### 渠道 + +每个渠道绑一个 Agent,传递地绑一个工作空间。工作空间 A 里配置的一个钉钉机器人和工作空间 B 里配置的一个钉钉机器人**完全独立**,即使它们被配置成连接同一个钉钉应用(你大概率不想这样,但技术上允许)。 + +--- + +## 工作空间隔离**不**覆盖的 + +- **共享的全局配置**——JWT secret、模型供应商 API Key、MCP 服务定义是全局的。工作空间管理员改不了。 +- **审计日志的跨工作空间访问**——带正确权限的安全管理员可以跨所有工作空间查询审计事件。这是**刻意的**——你想看到可疑活动,不管它发生在哪个工作空间。 +- **Token 用量报告**——全局聚合,在仪表盘里按工作空间、按 Agent、按模型细分。 +- **模型供应商成本**——全局层面每个 provider 一个计费关系;按工作空间的配额在[路线图](./roadmap)上。 + +--- + +## 在工作空间之间移动资源 + +**不直接支持。** 你有两个选项: + +1. **导出导入**——一些资源有 JSON 导出(Agent 走 API、Wiki KB 走 API)。在目标工作空间重新创建。 +2. **改所有权**——admin 或 owner 可以直接在数据库里更新简单资源的 `workspace_id` 列。这不是官方支持的;**自担风险而且一定要带备份**。 + +我们希望在未来版本里支持一等公民的移动。需要这个就在 [GitHub issue](https://github.com/matevip/mateclaw/issues) 上留言。 + +--- + +## 删除一个工作空间 + +**只有 owner 能删工作空间。** `设置 → 工作空间 → [工作空间] → 删除`。 + +删工作空间会: + +- 软删除它下面的每一个资源——Agent、技能、KB、会话、记忆文件、渠道 +- 移除所有成员关联 +- 记录一个审计事件 + +**软删除**意味着数据不被物理移除——它被标记为 `deleted = 1`、从查询里隐藏。误删的话数据库管理员可以通过翻转标记恢复。配置的保留期过后,删除的数据可能被清理任务永久清除。 + +--- + +## 工作空间管理 API + +```bash +# 列出你所属的工作空间 +curl http://localhost:18088/api/v1/workspaces \ + -H "Authorization: Bearer " + +# 获取单个工作空间详情 +curl http://localhost:18088/api/v1/workspaces/1 \ + -H "Authorization: Bearer " + +# 创建 +curl -X POST http://localhost:18088/api/v1/workspaces \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"name": "产品调研"}' + +# 更新 +curl -X PUT http://localhost:18088/api/v1/workspaces/1 \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"description": "更新后的描述"}' + +# 删除(仅 owner) +curl -X DELETE http://localhost:18088/api/v1/workspaces/1 \ + -H "Authorization: Bearer " + +# 成员管理 +curl http://localhost:18088/api/v1/workspaces/1/members \ + -H "Authorization: Bearer " + +curl -X POST http://localhost:18088/api/v1/workspaces/1/members \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"userId": 42, "role": "member"}' + +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 \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"role": "admin"}' +``` + +--- + +## 数据模型 + +**`mate_workspace`** + +| 列 | 用途 | +|----|------| +| `id` | 主键 | +| `name` | 工作空间名 | +| `description` | 简短描述 | +| `owner_id` | Owner 的用户 ID | +| `create_time` / `update_time` | 时间戳 | +| `deleted` | 逻辑删除标志 | + +**`mate_workspace_member`** + +| 列 | 用途 | +|----|------| +| `id` | 主键 | +| `workspace_id` | 外键到 `mate_workspace` | +| `user_id` | 外键到 `mate_user` | +| `role` | `owner` / `admin` / `member` / `viewer` | +| `joined_at` | 用户加入这个工作空间的时间 | +| `create_time` / `update_time` | 时间戳 | + +--- + +## 下一步 + +- [控制台](./console)——工作空间切换器和 UI +- [安全与审批](./security)——工作空间隔离和 Tool Guard、File Guard 的交互 +- [LLM Wiki](./wiki)——工作空间 scope 的知识库 +- [记忆系统](./memory)——工作空间记忆文件