- auth/refresh.go: RefreshStore(Issue/Rotate/Revoke/RevokeAll/RevokeByUser), SHA-256哈希256-bit随机串, 单次旋转事务(并发重放fail-closed) - 迁移0008_refresh_tokens(id/user_id/role/token_hash UNIQUE/expires_at/revoked_at + 索引) - gateway: POST /v1/auth/refresh(旋转/续期) + /v1/auth/logout(单/全设备); login响应增refreshToken - config: AUTH_REFRESH_TTL(720h)/AUTH_REFRESH_ROTATE_DISABLED; decode.go 支持time.Duration - rotate-jwt成功后RevokeAll(响应refreshTokensRevoked), revoke-keys不动refresh - 14测试+真实二进制e2e, build/vet/test绿+交叉编译 - Docs/api-client v0.25 + api-reference §2.3/2.4 同步
1168 lines
78 KiB
Markdown
1168 lines
78 KiB
Markdown
# Cyrene v2 后端 API 完整参考(api-reference.md)
|
||
|
||
> **用途**:本文档是 Cyrene v2 **全部后端 API(REST + WebSocket)的唯一详细参考**,供 Web 前端 / 移动端 / 原生客户端 / IM 桥接等**跨平台客户端开发者**使用。所有字段名、取值、鉴权规则、状态码均**以代码为准**(`internal/services/gateway/*.go`)。
|
||
> **主文档关系**:`Docs/api-client.md` 为面向使用者的**摘要**;`Docs/api-reference.md` 为**超详细主文档**。
|
||
> **维护铁律**:每实现/变更一个端点或 WS 事件必须同步更新本文档;契约变更版本化(REST 走 `/api/v1`,未来破坏性变更升 v2),不得破坏既有客户端。
|
||
> **当前版本**:以代码为准(gateway 已在 M0–M16 若干里程碑落地;详见各端点「里程碑」注记)。
|
||
|
||
---
|
||
|
||
## 0. 基础约定(全局)
|
||
|
||
- **Base URL**:`http://<host>:8080`。网关是唯一对外端口(单二进制、单端口 `:8080`、零跨进程 IPC 红线);生产一般经反向代理 + HTTPS。
|
||
- **版本**:REST 全部挂在 `/api/v1/{resource}`;WS 挂 `/ws/chat`;管理端点挂 `/api/v1/admin/*`。
|
||
- **响应统一信封**:
|
||
- 成功:`{"data": <payload>}`(HTTP 200 / 201 / 202 / 204)。
|
||
- 失败:`{"error": {"code": <string>, "message": <string>}}`(HTTP 400/401/403/404/409/500/501 等)。
|
||
- **错误码**:业务错误带稳定字符串 `code`(如 `unauthorized` / `not_found` / `bad_request` / `chat_busy` / `not_implemented`),便于客户端分支;`message` 面向人读。
|
||
- **CORS**:`Access-Control-Allow-Origin` 为配置的 `ALLOWED_ORIGINS` 白名单(`*` 时放开);`Allow-Methods: GET, POST, PUT, DELETE, OPTIONS`;`Allow-Headers: Authorization, Content-Type, X-API-Version`;`Access-Control-Expose-Headers: X-Voice-Provider`(跨域浏览器可读语音降级信号头);`Max-Age: 600`。`OPTIONS` 预检直接回 `204`。同源/无 Origin 请求放行。**注意:`X-API-Version` 仅作**advisory**惯例头(见 §0.2)——CORS 已放行但它**不参与服务端版本协商**,服务端当前不校验、不据此改变行为。**
|
||
- **请求日志**:每个请求由 `logging` 中间件记录一行(method/path/status/duration_ms),并计入可观测性指标。panic 由 `recover` 中间件兜底为 `500 {error:{code:internal,...}}`,不崩溃进程。
|
||
- **超时**:WS 连接 ping 周期 54s、pong 等待 60s、写超时 10s;读上限 4096 字节/帧。
|
||
|
||
### 0.1 鉴权模型(RBAC)
|
||
|
||
认证基于 **JWT Bearer**(`internal/auth`),角色层级 **owner > admin > viewer**(`owner` 满足 admin/viewer,`admin` 满足 viewer,`viewer` 仅满足 viewer)。
|
||
|
||
- **获取 token**:`POST /api/v1/auth/login`(见 §2.2)。token 有效期 **24 小时**(`tokenTTL`),签发角色恒为 `owner`。
|
||
- **REST 鉴权**:请求头 `Authorization: Bearer <JWT>`。`s.auth.Authorize(...)` 中间件:
|
||
- 缺失/无效/过期 token → `401 {error:{code:"unauthorized",...}}`。
|
||
- 有效 token 但角色不足 → `403 {error:{code:"forbidden",...}}`。
|
||
- `Authorize()`(无参)→ 任意**已认证**角色通过(only read metadata, e.g. GET /sessions、GET /personas)。
|
||
- `Authorize(owner)` → 仅 owner;`Authorize(owner, admin)` → owner 或 admin。
|
||
- **WS 鉴权**:`GET /ws/chat` 通过 `?token=<JWT>` 或 `Authorization: Bearer` 取 token(`wsToken`),`Verify` 通过即可升级;**不做角色门槛**(任何有效 token 均可连接),但 `history` 回放与 `proactive` 推送等按角色处理(见 §6)。
|
||
- **API Key(`X-API-Key`)**:⚠️ **当前实现未接入**。认证/授权中间件 `Authorize` 只校验 `Authorization: Bearer <JWT>`。`POST /api/v1/admin/users/apikey` 会**签发**一个明文 API key(仅返回一次),`GET /api/v1/admin/users` 会列出其掩码,但**网关的鉴权中间件目前不接受 `X-API-Key` 头**(`auth.APIKey.Verify/Lookup` 仅在单元测试中调用)。本文档按代码如实标注:**所有端点实际鉴权 = Bearer JWT**。若未来把 API key 接入中间件,需同步更新本档。
|
||
|
||
### 0.2 X-API-Version 头
|
||
|
||
`Docs/api-client.md` 建议客户端可带 `X-API-Version: 1`。**该头是 advisory 惯例头——服务端不校验、不据此协商版本,带不带行为完全不变**(`CORS Allow-Headers` 已放行它,跨域客户端发送不会预检失败)。若未来要真正版本化(`/api/v1` → 语义化协商),需另行实现服务端校验;在那之前客户端不应依赖它做任何行为分支。
|
||
|
||
### 0.3 上传 / 媒体(重要确认)
|
||
|
||
- **`POST /api/v1/upload`**(B1)上传图片 → 返回**同源 URL**(`/api/v1/files/<name>`),可直接作 `chat.image`;也可继续用 **base64 data: URI** 或 http(s) URL 走 `POST /api/v1/chat` / WS `chat` 的 `image` 字段(入视觉真推理,见 §6 流式与直连)。
|
||
- **REST 语音端点已存在**:`POST /api/v1/audio/transcribe`(STT)、`POST /api/v1/audio/speech`(TTS)、`POST /api/v1/audio/voice-clone`(声音复刻),均 owner-only + 审计,绕开 WS 4KB 帧限。语音也可走 WS `voice_input`(上行)/`voice_output`(下行)。
|
||
- 管理端导入知识文档(RAG)走 JSON body 文本(`POST /api/v1/admin/rag`),非文件上传。
|
||
|
||
---
|
||
|
||
## 1. REST 端点总览
|
||
|
||
> 下表为**全量登记**;下文 §2–§5 每端点给出字段级说明。`鉴权列`:`无` / `JWT`(任意已认证) / `owner|admin` / `owner`。`里程碑` 为代码/设计归属的大致里程碑(M8 网关、M9 前端、M10 补齐写、M16 会话持久化等)。
|
||
|
||
### 1.1 公开(无鉴权)
|
||
|
||
| # | 方法 | 路径 | 状态 | 说明 | 里程碑 |
|
||
|---|------|------|:---:|------|:---:|
|
||
| 1 | GET | `/api/v1/health` | 200 | 存活探针 `{status:"ok"}` | M8 |
|
||
| 2 | POST | `/api/v1/auth/login` | 200 | 换取 JWT(恒 owner) | M8 |
|
||
|
||
### 1.2 用户侧(已认证)
|
||
|
||
| # | 方法 | 路径 | 鉴权 | 状态 | 说明 | 里程碑 |
|
||
|---|------|------|------|:---:|------|:---:|
|
||
| 3 | POST | `/api/v1/chat` | owner\|admin | 200/202/409 | 发消息(同步回复或触发 WS 流式) | M8/M15 |
|
||
| 4 | GET | `/api/v1/sessions` | JWT | 200 | 会话列表(持久化会话 or 渠道会话回退) | M16 |
|
||
| 5 | GET | `/api/v1/personas` | JWT | 200 | 人格列表 | M8 |
|
||
| 6 | GET | `/api/v1/sessions/{id}/messages` | owner\|admin | 200 | 会话消息热分页 | M16 |
|
||
| 7 | POST | `/api/v1/sessions` | owner\|admin | 201 | 新建次级会话 | M16 |
|
||
| 8 | DELETE | `/api/v1/sessions/{id}` | owner\|admin | 200 | 删除次级会话 | M16 |
|
||
|
||
### 1.3 管理后台(`/api/v1/admin/*`)
|
||
|
||
读(`owner|admin`)+ 写(`owner`,全程审计)。下表按注册顺序,标注鉴权与状态。
|
||
|
||
| # | 方法 | 路径 | 鉴权 | 状态 | 说明 | 里程碑 |
|
||
|---|------|------|------|:---:|------|:---:|
|
||
| 9 | GET | `/api/v1/admin/personas` | owner\|admin | 200 | 人格列表 | M8 |
|
||
| 10 | GET | `/api/v1/admin/sessions` | owner\|admin | 200 | 会话列表 | M16 |
|
||
| 11 | GET | `/api/v1/admin/memories` | owner\|admin | 200 | 记忆列表 | M8 |
|
||
| 12 | GET | `/api/v1/admin/plugins` | owner\|admin | 200 | 插件/能力列表 | M8 |
|
||
| 13 | GET | `/api/v1/admin/channels` | owner\|admin | 200 | 渠道列表 + 已配账号(掩码+bot 身份+capabilities) | M8/M17 |
|
||
| 14 | GET | `/api/v1/admin/channels/messages` | owner\|admin | 200 | IM 适配器日志流(全量/平台/适配器三粒度) | M16 |
|
||
| 15 | GET | `/api/v1/admin/channels/{platform}/messages` | owner\|admin | 200 | 某平台 IM 适配器日志流 | M16 |
|
||
| 16 | GET | `/api/v1/admin/iot` | owner\|admin | 200 | IoT 设备列表 | M8 |
|
||
| 17 | GET | `/api/v1/admin/llm` | owner\|admin | 200 | LLM 多厂商多模型组(APIKey 不回显) | M8/M15 |
|
||
| 18 | GET | `/api/v1/admin/automations` | owner\|admin | 200 | 自动化/提醒列表 | M8 |
|
||
| 19 | GET | `/api/v1/admin/users` | owner\|admin | 200 | 账号/API-key 列表 | M8 |
|
||
| 20 | GET | `/api/v1/admin/backthink` | owner\|admin | 200 | 后台思考记录 | M9/M10 |
|
||
| 21 | GET | `/api/v1/admin/rag` | owner\|admin | 200 | 知识库文档列表(可带 `?query=` 并入命中) | M10 |
|
||
| 22 | POST | `/api/v1/admin/rag/query` | owner\|admin | 200 | 知识库检索 | M10 |
|
||
| 23 | POST | `/api/v1/admin/rag/delete` | owner\|admin | 501 | 删除文档(写为 M10,当前 501) | M10 |
|
||
| 24 | GET | `/api/v1/admin/observability/metrics` | owner\|admin | 200 | 网关请求指标 | M10 |
|
||
| 25 | GET | `/api/v1/admin/observability/trace` | owner\|admin | 200 | 最近 trace span | M10 |
|
||
| 26 | GET | `/api/v1/admin/observability/audit` | owner\|admin | 200 | 审计记录 | M10 |
|
||
| 27 | POST | `/api/v1/admin/plugins/policy` | **owner** | 200 | 写工具策略(审计) | M8 |
|
||
| 28 | GET | `/api/v1/admin/sessions/{id}/export` | owner\|admin | 200 | 导出会话元数据 | M8 |
|
||
| 29 | POST | `/api/v1/admin/personas` | **owner** | 201 | 新建人格 | M8 |
|
||
| 30 | PUT | `/api/v1/admin/personas/{id}` | **owner** | 200 | 编辑人格 | M8 |
|
||
| 31 | POST | `/api/v1/admin/users` | **owner** | 501 | 新建用户(M10) | M10 |
|
||
| 32 | POST | `/api/v1/admin/users/apikey` | **owner** | 201 | 签发 API key | M8 |
|
||
| 33 | DELETE | `/api/v1/admin/users/apikey/{id}` | **owner** | 200 | 吊销 API key | M8 |
|
||
| 34 | POST | `/api/v1/admin/iot/{id}/control` | **owner** | 200 | 设备控制 | M8 |
|
||
| 35 | POST | `/api/v1/admin/iot/scenarios` | **owner** | 501 | IoT 场景(M10) | M10 |
|
||
| 36 | POST | `/api/v1/admin/iot/rules` | **owner** | 501 | IoT 规则(M10) | M10 |
|
||
| 37 | POST | `/api/v1/admin/automations` | **owner** | 201 | 新建自动化/提醒 | M8 |
|
||
| 38 | POST | `/api/v1/admin/automations/{id}/run` | **owner** | 200 | 手动触发规则 | M8 |
|
||
| 39 | DELETE | `/api/v1/admin/automations/{id}` | **owner** | 200 | 删除规则 | M8 |
|
||
| 40 | POST | `/api/v1/admin/llm` | **owner** | 201 | 保存 LLM provider 组(落盘 + 热更新) | M8/M15 |
|
||
| 41 | POST | `/api/v1/admin/llm/test` | **owner** | 200 | 真实模型调用测试 | M8/M15 |
|
||
| 42 | POST | `/api/v1/admin/channels/open` | **owner** | 200 | 连接渠道 | M8 |
|
||
| 43 | POST | `/api/v1/admin/channels/close` | **owner** | 200 | 断开渠道 | M8 |
|
||
| 44 | POST | `/api/v1/admin/channels/test` | **owner** | 200 | 结构性身份识别测试 | M8/M16 |
|
||
| 45 | GET | `/api/v1/admin/channels/{platform}/login-qr` | **owner** | 200 | 微信 iLink 扫码绑定 QR | M16 |
|
||
| 46 | GET | `/api/v1/admin/channels/{platform}/login-status` | **owner** | 200 | 微信 iLink 扫码状态 | M16 |
|
||
| 47 | GET | `/api/v1/admin/channels/{platform}/view` | **owner** | 200 | KOOK 账号聚合视图 | M17 |
|
||
| 48 | GET | `/api/v1/admin/channels/{platform}/friends` | **owner** | 200 | KOOK 好友/申请/屏蔽 | M17 |
|
||
| 49 | POST | `/api/v1/admin/channels/{platform}/friend/handle` | **owner** | 200 | KOOK 处理好友申请 | M17 |
|
||
| 50 | POST | `/api/v1/admin/channels/{platform}/message/test` | **owner** | 200 | KOOK 发送测试消息 | M17 |
|
||
| 51 | PUT | `/api/v1/admin/channels/{platform}/config` | **owner** | 201 | 保存渠道账号配置(落盘 + 热更新) | M16 |
|
||
| 52 | DELETE | `/api/v1/admin/channels/{platform}/config` | **owner** | 200 | 删除渠道账号 | M16 |
|
||
| 53 | POST | `/api/v1/admin/plugins/install` | **owner** | 501 | 安装插件(需源码清单) | M10 |
|
||
| 54 | POST | `/api/v1/admin/plugins/uninstall` | **owner** | 200 | 移除插件 | M8 |
|
||
| 55 | POST | `/api/v1/admin/memories` | **owner** | 201 | 手动添加记忆 | M8 |
|
||
| 56 | POST | `/api/v1/admin/memories/search` | **owner** | 200 | 记忆检索 | M8 |
|
||
| 57 | POST | `/api/v1/admin/rag` | **owner** | 201 | 导入知识文档 | M10 |
|
||
| 58 | POST | `/api/v1/admin/system/revoke-keys` | **owner** | 200 | 吊销全部 API key | M10/M11 |
|
||
| 59 | POST | `/api/v1/admin/system/rotate-jwt` | **owner** | 200 | 轮换 JWT 密钥 | M10/M11 |
|
||
| 60 | POST | `/api/v1/admin/system/backup` | **owner** | 200 | 备份数据库 | M10/M11 |
|
||
| 61 | POST | `/api/v1/admin/system/restore` | **owner** | 200 | 恢复数据库 | M10/M11 |
|
||
|
||
**另**:外部 webhook 回调(如 `webhook` 渠道的 HMAC 回调)由 composition root 通过 `Deps.Webhooks` 动态挂载,**不走网关鉴权**(handler 自校验签名/授权);路径由调用方决定,不在上表枚举。
|
||
|
||
**WS**:`GET /ws/chat`(握手,JWT,见 §6)。
|
||
|
||
---
|
||
|
||
## 2. 公开端点
|
||
|
||
### 2.1 GET /api/v1/health
|
||
- **鉴权**:无(探针)。
|
||
- **响应**:`200` `{"data":{"status":"ok"}}`。
|
||
- **备注**:仅存活探针;不含依赖健康状态。示例:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/health
|
||
# => {"data":{"status":"ok"}}
|
||
```
|
||
|
||
### 2.2 POST /api/v1/auth/login
|
||
- **鉴权**:无(换取 JWT)。
|
||
- **请求 Body**(`loginRequest`):
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|:---:|------|
|
||
| `username` | string | 是 | 管理员用户名(对照 `cfg.Auth.AdminUser`) |
|
||
| `password` | string | 是 | 管理员密码(对照 `cfg.Auth.AdminPass`) |
|
||
|
||
```json
|
||
{"username":"admin","password":"s3cret"}
|
||
```
|
||
|
||
- **响应**:`200` `{"data":{"token":"<JWT>","expiresAt":<unix秒>,"role":"owner","refreshToken":"<opaque>"}}`(`loginResponse`)。
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `token` | string | 已签名 JWT(HS256),下文请求头 `Authorization: Bearer <token>` |
|
||
| `expiresAt` | int64 | 过期时间,Unix 秒 = `now + 24h`(`tokenTTL=24h`) |
|
||
| `role` | string | **恒为 `"owner"`**(代码硬编码 `auth.RoleOwner`) |
|
||
| `refreshToken` | string | **不透明 256-bit 随机串**(B2)——服务端**仅存其 SHA-256 哈希**,明文只在响应出现一次;用于 `POST /api/v1/auth/refresh` 静默续期,`AUTH_REFRESH_TTL` 默认 `720h` |
|
||
|
||
- **错误**:`400` `{"error":{"code":"bad_request","message":"invalid request body"}}`(JSON 解析失败);`401` `{"error":{"code":"unauthorized","message":"invalid username or password"}}`;`500` `{"error":{"code":"login_failed",...}}`(签名失败)。
|
||
- **备注**:登录响应**以代码为准** = `{token, expiresAt, role}`(**不是** `{token,jwt}`)。旧 `api-client.md` §1 表内一行写 `{token,jwt}` 为**历史笔误**,以本档为准。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/auth/login \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"username":"admin","password":"s3cret"}'
|
||
# => {"data":{"token":"eyJ...","expiresAt":1799...,"role":"owner","refreshToken":"...base64url..."}}
|
||
```
|
||
|
||
### 2.3 POST /api/v1/auth/refresh(B2)
|
||
- **鉴权**:公开,body 带 `refreshToken`。
|
||
- **请求 Body**:`{refreshToken:"<opaque>"}`。
|
||
- **响应**:`200` `{"data":{"token":"<新JWT>","expiresAt":<unix秒>,"refreshToken":"<新opaque?>"}}`——默认**单次使用旋转**:旧 refresh 立即作废并签发新的;`AUTH_REFRESH_ROTATE_DISABLED=true` 时旧 token 保持有效、不返回新 refresh(`refreshToken` 省略)。
|
||
- **错误**:`400 bad_request`(JSON 解析失败 / 空 token);`401` `{"error":{"code":"invalid_grant",...}}`(未知/已撤销/已过期,统一口径,防枚举);仅成功才审计。
|
||
- **curl**:`curl -s -X POST http://<host>:8080/api/v1/auth/refresh -H 'Content-Type: application/json' -d '{"refreshToken":"<opaque>"}'`
|
||
|
||
### 2.4 POST /api/v1/auth/logout(B2)
|
||
- **鉴权**:公开,两种方式二选一——body `{refreshToken}` 撤**单个** grant(幂等);或无 body 但带 `Authorization: Bearer <jwt>` 撤**该账号全部** grant(全设备登出)。
|
||
- **响应**:`200` `{"data":{"ok":true,"revokedCount":<n>}}`(单撤 `revokedCount` 为 0/1;全撤为该账号 refresh 行数)。
|
||
- **错误**:不存在的 token 为幂等 no-op(仍 `200`);非法 body `400`。
|
||
|
||
---
|
||
|
||
## 3. 用户侧端点(已认证)
|
||
|
||
### 3.1 POST /api/v1/chat
|
||
- **鉴权**:`Authorization: Bearer <JWT>`,`Authorize(owner, admin)`(owner 或 admin)。viewer 不能写。
|
||
- **请求 Body**(`chatRequest`):
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|:---:|------|
|
||
| `sessionId` | string | 否 | 目标会话 id;缺省 `"web"`(主会话)。子会话用 `web:<uuid>` |
|
||
| `text` | string | 是* | 用户文本。`text` 与 `image` 至少一项非空 |
|
||
| `image` | string | 否 | 视觉输入:base64 **data: URI** 或 http(s) URL;入 vision/OCR 或主模型多模态(design/06)。图文并存则为带图 + 文本的 turn |
|
||
| `deviceId` | string | 否 | 客户端生成的端标识(design/16 §4.5,如 `web:chrome:a1b2c3`),落库到消息,可选 |
|
||
| `stream` | bool | 是 | `true` = 经 WS 触发流式(`publishChatStream` 发 `ws.chat`),**立即返回**;`false` = 同步返回完整回复 |
|
||
|
||
```json
|
||
{"sessionId":"web","text":"你好","deviceId":"web:chrome:a1b2c3","stream":false}
|
||
```
|
||
|
||
- **响应**:
|
||
- 同步(`stream=false`,会话空闲)`200` `{"data":{"reply":"<助手文本>","steps":<int>,"stopReason":"<string>"}}`(`chatResponse`)。`steps` 为工具循环步数,`stopReason` 为停止原因(如 `stop`/`idle`/`error`)。
|
||
- 同步但会话**正忙**(`SubmitSync` 返回 `Pending`)`202` `{"data":{"queued":true,"queueDepth":<int>}}`(非阻塞 ack,稍后按上下文续接)。
|
||
- 流式(`stream=true`)`200` `{"data":{"stream":true}}` —— 实际文本经 WS 推送(见 §6)。
|
||
- **错误**:
|
||
- `400` `{"error":{"code":"bad_request","message":"text or image is required"}}`(空消息)。
|
||
- `409` `{"error":{"code":"chat_busy","message":"会话正忙,待处理消息已满"}}`(pending 队列满,`PendingFull`)。
|
||
- `500` `{"error":{"code":"chat_unavailable",...}}`(`runner` 未注入)/ `{"error":{"code":"chat_failed",...}}`(回合运行出错)。
|
||
- **备注**:
|
||
- **会话编排(design/15)**:同会话正忙时新消息**不阻塞**、入 pending(默认上限 5);满则 `409 chat_busy`。状态经 WS 状态事件通知(§6.4)。
|
||
- 主会话 `web` 绑定**全量访问 env**(`loop.OwnerEnv()`:记忆读写 + 行为不变)。
|
||
- 敏感字段(`image` 可能为 base64)不走日志明文。
|
||
- curl:
|
||
|
||
```bash
|
||
TOKEN=$(curl -s -X POST http://<host>:8080/api/v1/auth/login \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"username":"admin","password":"s3cret"}' | jq -r .data.token)
|
||
curl -s -X POST http://<host>:8080/api/v1/chat \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H 'Content-Type: application/json' \
|
||
-H 'X-API-Version: 1' \
|
||
-d '{"sessionId":"web","text":"讲个笑话","stream":false}'
|
||
# => {"data":{"reply":"...","steps":2,"stopReason":"stop"}}
|
||
```
|
||
|
||
### 3.2 GET /api/v1/sessions
|
||
- **鉴权**:`Authorize()`(任意已认证角色可读元数据)。
|
||
- **响应**:注入 `HistoryStore` 时返回**持久化会话**(`historySessionSummary`);否则回退渠道会话列表(`SessionSummary`,或 `[]`)。
|
||
|
||
**A) 持久化会话**(`history != nil`,主 + 子):
|
||
```json
|
||
{"data":[
|
||
{"id":"web","type":"main","title":"","createdAt":1753...},
|
||
{"id":"web:<uuid>","type":"sub","title":"临时小事","createdAt":1753...}
|
||
]}
|
||
```
|
||
- `id`:主会话恒 `"web"`;次级为 `web:<uuid>`。
|
||
- `type`:`"main"` | `"sub"`。
|
||
- `title`:可选(`omitempty`)。
|
||
- `createdAt`:int64(Unix 毫秒)。
|
||
|
||
**B) 渠道会话回退**(`history == nil`):
|
||
```json
|
||
{"data":[{"platform":"kook","channelId":"...","sessionId":"...","actorRole":"owner","owner":true,"isGroup":false,"memWritable":true}]}
|
||
```
|
||
`SessionSummary` 字段:`platform` / `channelId` / `sessionId` / `actorRole`(`owner|admin|viewer|...`)/ `owner`(bool) / `isGroup`(bool) / `memWritable`(bool)。
|
||
|
||
- **错误**:`500` `{"error":{"code":"sessions_failed",...}}`(列表失败)。
|
||
- **备注**:`HistoryStore` 未注入时后端**不报错**,退化为渠道视图(向后兼容)。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/sessions -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 3.3 GET /api/v1/personas
|
||
- **鉴权**:`Authorize()`(任意已认证角色)。
|
||
- **响应**:`200` `{"data":[<PersonaSummary>...]}`(无 store 时 `[]`)。
|
||
|
||
`PersonaSummary` 字段:
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `id` | string | 人格 id(`PersonaID`,回退 `ID`) |
|
||
| `name` | string | 显示名(`c.Name`) |
|
||
| `character` | string | 角色名(当前恒 = `name`) |
|
||
| `isDefault` | bool | 是否默认人格 |
|
||
| `mode` | string | 首个行为模式(`modes[0]`,未设置为 `""`) |
|
||
| `memoryNamespace` | string | 记忆命名空间(该人格自己的) |
|
||
| `avatar` | string | 视觉头像 URL(`omitempty`;该人格自己的,缺失则为空) |
|
||
| `chatBackground` | object | `omitempty`;`{landscape, portrait}` 双朝向壁纸,仅该人格自己的 |
|
||
|
||
```json
|
||
{"data":[{"id":"cyrene","name":"Cyrene","character":"Cyrene","isDefault":true,"mode":"normal","memoryNamespace":"cyrene","avatar":"/assets/cyrene.png","chatBackground":{"landscape":"/bg/b1.png","portrait":"/bg/b1p.png"}}]}
|
||
```
|
||
|
||
- **备注**:视觉资产按角色作用域读取,绝不返回别的角色的;缺失时前端回退中性默认。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/personas -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 3.4 GET /api/v1/sessions/{id}/messages
|
||
- **鉴权**:`Authorize(owner, admin)`(会话消息/thinking 属主人私密历史,viewer 不可读)。
|
||
- **Path**:`{id}` = 会话 id(`web` 或 `web:<uuid>`;IM 隔离会话 `sess-xxx` 亦可用)。
|
||
- **Query**:
|
||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||
|------|------|:---:|------|------|
|
||
| `limit` | int | 否 | 50 | 页大小,需 `>=1`,钳制上限 200(`maxPageLimit`);非法回 400 |
|
||
| `before` | int64 | 否 | 0 | 游标:上一页最旧消息的 `createdAt`(Unix 毫秒),向更旧翻页 |
|
||
| `platform` | string | 否 | - | 来源平台过滤(design/16 §4.4)。Web 端不传 = 全量共享主会话;IM 传本平台(如 `telegram`)只拉本渠道消息 |
|
||
- **响应**:`200` `{"data":[<historyMessage>...]}`,**按时间倒序**(newest first);需 `reverse()` 渲染。
|
||
|
||
`historyMessage` 全字段:
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `id` | string | 后端生成的消息 id(客户端据此去重) |
|
||
| `sessionId` | string | 会话 id |
|
||
| `role` | string | `user` / `assistant` |
|
||
| `content` | string | 正文(纯文本/图像消息文本;图像为 `image` 字段) |
|
||
| `mode` | string | 该条的模式(`omitempty`,供渲染气泡不用猜) |
|
||
| `mood` | string | 情绪标记(`omitempty`) |
|
||
| `image` | string | 图像引用(`omitempty`) |
|
||
| `thinking` | string | 思维链(`omitempty`;**本端点 owner/admin-only,故可达;公开回放/持久化广播中已剥离**) |
|
||
| `platform` | string | 来源平台(`web` / `telegram` / `weixin-ilink` / `qq-obv11`...,`omitempty`) |
|
||
| `deviceId` | string | 客户端端标识(`omitempty`,如 `web:chrome:a1b2c3`) |
|
||
| `senderName` | string | 第三方发送者昵称(`omitempty`;群聊/陌生人消息带,主人私聊为空) |
|
||
| `adapterId` | string | 产生该消息的适配器注册名(`omitempty`,如 `kook:3bef...`;Web 消息为空) |
|
||
| `createdAt` | int64 | Unix 毫秒(兼作分页游标) |
|
||
|
||
```json
|
||
{"data":[{"id":"m1","sessionId":"web","role":"assistant","content":"你好","mode":"normal","mood":"calm","platform":"web","deviceId":"web:chrome:a1b2c3","createdAt":1753...}]}
|
||
```
|
||
|
||
- **错误**:`400`(`limit`/`before` 非法、`id` 缺失);`501` `{"error":{"code":"not_implemented","message":"会话历史服务未配置(未注入 HistoryStore)"}}`;`500` `{"error":{"code":"messages_failed",...}}`。
|
||
- **备注**:热分页(design/16 §2.2,§5.1)。`thinking` 只在本 owner/admin 端点可见;WS 回放/广播已剥离。curl:
|
||
|
||
```bash
|
||
curl -s 'http://<host>:8080/api/v1/sessions/web/messages?limit=50&before=1753000000000&platform=web' \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 3.5 POST /api/v1/sessions
|
||
- **鉴权**:`Authorize(owner, admin)`(写操作,变更持久态)。
|
||
- **请求**:无 body(空)。可从 JWT claims 取 `ownerID` 作为子会话归属。
|
||
- **响应**:`201` `{"data":{"sessionId":"web:<uuid>"}}`。
|
||
- **错误**:`501` `{"error":{"code":"not_implemented","message":"会话历史服务未配置(未注入 HistoryStore)"}}`;`500` `{"error":{"code":"session_failed",...}}`。
|
||
- **备注**:新建次级会话(`NewSubSession`),独立上下文回溯。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/sessions \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1' -d '{}'
|
||
# => {"data":{"sessionId":"web:0123abcd..."}}
|
||
```
|
||
|
||
### 3.6 DELETE /api/v1/sessions/{id}
|
||
- **鉴权**:`Authorize(owner, admin)`。
|
||
- **Path**:`{id}` = 会话 id。
|
||
- **响应**:`200` `{"data":{"deleted":"<id>"}}`。
|
||
- **错误**:
|
||
- `403` `{"error":{"code":"forbidden","message":"the main session cannot be deleted"}}`(`history.ErrImmutableMain`,主会话 `web` 不可删)。
|
||
- `400`(id 缺失);`501`(无 HistoryStore);`500` `{"error":{"code":"session_failed",...}}`。
|
||
- **备注**:删除次级会话及其全部消息。curl:
|
||
|
||
```bash
|
||
curl -s -X DELETE http://<host>:8080/api/v1/sessions/web:0123abcd... \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
# => {"data":{"deleted":"web:0123abcd..."}}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 管理后台 · 读端点(`owner|admin`)
|
||
|
||
> 以下端点 `鉴权 = Authorize(owner, admin)`(除特别标注 owner)。所有管理**读**不审计;管理**写**(§5)全程审计(who/what/when/result,见 §4.15)。
|
||
|
||
### 4.1 GET /api/v1/admin/personas
|
||
- 同 `GET /api/v1/personas`(§3.3),返回 `PersonaSummary[]`。`adminListPersonas` 直接复用 `collectPersonas()`。
|
||
- `200 {"data":[<PersonaSummary>...]}`。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/admin/personas -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.2 GET /api/v1/admin/sessions
|
||
- 同 `GET /api/v1/sessions`(§3.2):注入 HistoryStore 返回持久化会话(主 + 子);否则回退渠道会话视图(`collectSessions`)。
|
||
- `200 {"data":[...]}`。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/admin/sessions -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.3 GET /api/v1/admin/memories
|
||
- **响应**:`200 {"data":[<MemorySummary>...]}`(无 store 时 `[]`)。`MemorySummary` = `{id, scope, text}`。`scope` ∈ `user|persona|project`。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/admin/memories -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.4 GET /api/v1/admin/plugins
|
||
- **响应**:`200 {"data":[<PluginSummary>...]}`(`PluginSummary` = `{name, kind, description, allowInGroup, allowInStranger}`)。`kind` 为能力类型(如 `plugin|channel|tool|mcp`);`allowInGroup/allowInStranger` 为可达性策略。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/admin/plugins -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.5 GET /api/v1/admin/channels
|
||
- **响应**:`200 {"data":[<ChannelSummary>...]}`。
|
||
|
||
`ChannelSummary` = `{name, connected, accounts?}`。`accounts` 为 `AdminChannelAccount[]`(掩码无 raw token),仅当 `s.channels` 注入且平台有已配账号时出现。
|
||
- `name`:平台名(如 `kook`、`telegram`、`weixin-ilink`)。
|
||
- `connected`:bool,该平台是否已连接。
|
||
- `accounts[]`(`AdminChannelAccount`)字段:`id` / `config`(掩码后的配置 map)/ `allowGroups` / `denyGroups` / `allowUsers` / `denyUsers` / `groupMsgPerMin` / `msgPerMin` / `maxMessages` / `botId?` / `botUsername?` / `botNickname?` / `capabilities?`(如 kook 的 `["friends","user_chats","channels","guilds","message_test"]`)。
|
||
- `botId/botUsername/botNickname` 为**已连接 bot 的真实身份**(如 KOOK Connect 时调 `/api/v3/user/me` 解析),非敏感来自 live adapter。
|
||
- `config` 值已脱敏(token 掩码 `****` + 尾 4);`bot_token` 等密钥**绝不回显**。
|
||
|
||
- **聚合逻辑**:`channels.json` 是账号存在性的真源;一个裸平台 + 多账号合并为**一行**;无注册 adapter 但已配账号的平台仍出现(供查看配置)。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/admin/channels -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.6 GET /api/v1/admin/channels/messages
|
||
- **鉴权**:`owner|admin`。
|
||
- **Query**:`limit`(默认50,上限200) / `before`(游标,Unix 毫秒) / `adapter`(适配器注册名,如 `kook:3bef...`) / `platform`。三粒度(互斥,`adapter` 优先,其次 `platform`,都不传=全量 IM 总览)。
|
||
- **响应**:`200 {"data":[<historyMessage>...]}`(newest first)。**本视图不过滤主会话**(因为它带 `adapter_id`);按 `adapter_id`/`platform` 过滤。`historyMessage` 见 §3.4。
|
||
- **错误**:`501`(无 HistoryStore)。
|
||
- **备注**:IM 适配器日志页全量数据源 + 轮询兜底;实时增量走 WS `channel_persisted`(§6.3)。curl:
|
||
|
||
```bash
|
||
curl -s 'http://<host>:8080/api/v1/admin/channels/messages?adapter=kook:3bef...&limit=50' \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.7 GET /api/v1/admin/channels/{platform}/messages
|
||
- **鉴权**:`owner|admin`。
|
||
- **Path**:`{platform}` = IM 平台(如 `telegram`)。
|
||
- **Query**:`limit`(默认50,上限200) / `before`(Unix 毫秒)。
|
||
- **响应**:`200 {"data":[<historyMessage>...]}`。**排除主会话 `web`**,只返回该平台非主人消息流(隔离 `sess-xxx`)。`senderName` 标发送者昵称;`adapterId` 标来源适配器。
|
||
- **错误**:`400`(platform 缺失);`501`(无 HistoryStore);`500`。
|
||
- curl:
|
||
|
||
```bash
|
||
curl -s 'http://<host>:8080/api/v1/admin/channels/telegram/messages?limit=50' \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.8 GET /api/v1/admin/iot
|
||
- **响应**:`200 {"data":[<iotDeviceSummary>...]}`(`iotDeviceSummary` = `{id, name, type, state, online}`;`state` 为 `map[string]any`)。无 store 时 `[]`。
|
||
- **错误**:`500` `{"error":{"code":"iot_failed",...}}`。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/admin/iot -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.9 GET /api/v1/admin/llm
|
||
- **响应**:`200 {"data":{"providers":[<AdminLlmProvider>...]}}`(`AdminLlmConfig`)。
|
||
- `AdminLlmProvider`:`{id, name, provider, baseUrl, model, multimodal, enabled, fallback?}`。
|
||
- `provider` 协议 ∈ `openai|anthropic`;`name` 为友好标签(OpenAI/Anthropic 或组 id)。
|
||
- `enabled`:index 0 的组为生效组 = `true`。
|
||
- **API key 永不返回**(wire 无 key 字段)。
|
||
- 数据源:`models.json` 各组(index 0 = 生效组);无 `models.json` 时按 `.env` 合成单组。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/admin/llm -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.10 GET /api/v1/admin/automations
|
||
- **响应**:`200 {"data":[<AdminAutomationRecord>...]}`。
|
||
- `AdminAutomationRecord`:`{id, name, kind, cron?, enabled, lastRun?, nextRun?}`。`kind` ∈ `cron|event`;`lastRun/nextRun` 为 RFC3339(未知省略)。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/admin/automations -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.11 GET /api/v1/admin/users
|
||
- **响应**:`200 {"data":[<AdminUserRecord>...]}`。
|
||
- `AdminUserRecord`:`{id, username, role, createdAt?}`(`createdAt` RFC3339)。无 UserStore 时仅有配置的 admin 账号(`id:"admin"`、`role:"owner"`);注入 UserStore 后追加 API key 摘要(`username:"apikey"`、`id` 为掩码)。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/admin/users -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.12 GET /api/v1/admin/backthink
|
||
- **响应**:`200 {"data":[<AdminBackthinkItem>...]}`(无 store 时 `[]`)。`AdminBackthinkItem`:`{id, trigger, conclusion?, shouldNotify, pushed, at}`(`at` 为 Unix 毫秒)。默认取最近 50 条。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/admin/backthink -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.13 GET /api/v1/admin/rag
|
||
- **Query**:`?query=<string>`(可选;带则并入检索命中)。
|
||
- **响应**:`200 {"data":{"documents":[<AdminRagDoc>...], "hits"?:[<AdminRagHit>...]}}`(`AdminRagResponse`)。
|
||
- `AdminRagDoc`:`{id, name, desc?, chunks, embedded}`(`embedded` 反映是否配置向量增强)。
|
||
- `AdminRagHit`:`{docId, docName, chapter?, text, score, semantic}`(`semantic` 布尔,语义/向量命中为 true)。
|
||
- **错误**:`500`。无 store 时返回空文档列表(不 500)。curl:
|
||
|
||
```bash
|
||
curl -s 'http://<host>:8080/api/v1/admin/rag?query=部署' -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.14 POST /api/v1/admin/rag/query
|
||
- **鉴权**:`owner|admin`。
|
||
- **Body**:`{"query":"<string>"}`(必填,非空)。
|
||
- **响应**:`200 {"data":{"query":"<string>","hits":[<AdminRagHit>...]}}`。**审计**:`rag.query`(成功 `ok` / 失败 `error`)。
|
||
- **错误**:`400`(query 缺失/空);`501`(未注入 RagStore);`500`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/rag/query \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"query":"部署要求是什么"}'
|
||
```
|
||
|
||
### 4.15 GET /api/v1/admin/observability/audit
|
||
- **Query**:`?limit=N`(默认 50,上限 500)。
|
||
- **响应**:`200 {"data":[<AuditRecord>...]}`(newest first)。`AuditRecord` = `{id(int64递增), who, what, when(RFC3339), result}`。`who` 形如 `admin:owner`;`what` 为审计动作(如 `system.backup`);`result` ∈ `ok|error`。
|
||
- 来源:SQLite `audit_logs`(`SQLiteAuditStore`);未注入时退化为进程内存。
|
||
- 示例:`{"data":[{"id":1,"who":"admin:owner","what":"system.backup","when":"2026-09-08T00:00:00Z","result":"ok"}]}`。curl:
|
||
|
||
```bash
|
||
curl -s 'http://<host>:8080/api/v1/admin/observability/audit?limit=100' -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.16 GET /api/v1/admin/observability/metrics
|
||
- **Query**:无(不接收 limit)。
|
||
- **响应**:`200 {"data":<AdminMetrics>}`。`AdminMetrics` = `{totalRequests, qps, avgLatencyMs, maxLatencyMs, perMethod{}, perPath{}, statusCounts{}, llmCalls, subagentEvents}`。
|
||
- `qps`:近 1 秒窗口计数;`avgLatencyMs/maxLatencyMs`:均值/最大耗时(毫秒)。
|
||
- `perMethod/perPath/statusCounts`:分布计数。
|
||
- `llmCalls/subagentEvents`:由实际观测的 trace span 按 `kind` 累计(Tracer 未注入为 0)。
|
||
- **Token/成本后端不统计、不返回**(不伪造指标)。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/admin/observability/metrics -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.17 GET /api/v1/admin/observability/trace
|
||
- **Query**:`?limit=N`(默认 50,上限 500)。
|
||
- **响应**:`200 {"data":[<AdminTraceSpan>...]}`(newest first)。`AdminTraceSpan` = `{id, parentId?, name, kind, status, startedAt, endedAt?, durationNs, props?, events?}`。`kind` ∈ `llm_call|tool_call|orchestration|state_transition|subagent|memory|guardrail`;`events` 为 `[{time,type,props?}]`;时间 RFC3339。
|
||
- 来源:环形缓冲(默认 512 条),由 `ObserveSpan`/`TraceSink` 注入。curl:
|
||
|
||
```bash
|
||
curl -s 'http://<host>:8080/api/v1/admin/observability/trace?limit=100' -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 4.18 GET /api/v1/admin/sessions/{id}/export
|
||
- **鉴权**:`owner|admin`(注册为 `adminAuth`)。
|
||
- **Path**:`{id}` = 会话 id。
|
||
- **响应**:`200 {"data":{"session":<SessionSummary>}}`(`summaryOfSession`:`{platform, channelId, sessionId, actorRole, owner, isGroup, memWritable}`)。
|
||
- **错误**:`404`(会话未找到);`500`。
|
||
- **备注**:导出会话身份元数据;完整消息转录由 M10 提供。curl:
|
||
|
||
```bash
|
||
curl -s http://<host>:8080/api/v1/admin/sessions/web:0123.../export -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 管理后台 · 写端点(`owner`,全程审计)
|
||
|
||
> 以下端点**鉴权 = `Authorize(owner)`**(仅主人)。每个写操作都会调用 `auditClaims(r, "<action>", "ok"|"error")` 记录 who/what/when/result(落 `audit_logs` + 网关日志)。**敏感值(token/密钥/新 JWT 密钥/DB 路径)不在响应/日志回显**。
|
||
|
||
### 5.1 POST /api/v1/admin/plugins/policy
|
||
- **Body**(`policyRequest`):`{"name":"<能力名>","allowInGroup":bool,"allowInStranger":bool}`。
|
||
- **响应**:`200 {"data":{"name":"<能力名>","allowInGroup":bool,"allowInStranger":bool}}`。
|
||
- **错误**:`400`(name 缺失);`500`(cap service 未配置);`404`(能力不存在);`500`。
|
||
- **审计**:`policy.set`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/plugins/policy \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"name":"web_search","allowInGroup":true,"allowInStranger":false}'
|
||
```
|
||
|
||
### 5.2 POST /api/v1/admin/personas
|
||
- **Body**(`personaUpsertRequest`):`{"id","name","characterRef?","role","systemPrompt?","personality?","speechStyle?","catchphrase?","background?","values?[]","modes?[]"/"mode?","memoryNamespace?"}`。`id/name/role` 必填;`systemPrompt` 空时派生默认;`characterRef` 空时 = id。
|
||
- **响应**:`201 {"data":<PersonaSummary>}`。注册进 persona registry(真实运行效果)+ 持久化到 `configs/personas/<id>.yaml`(重启存活)。
|
||
- **错误**:`400`(id/name/role 缺失);`409` `{"error":{"code":"duplicate","message":"人格已存在,请使用编辑"}}`;`500`(registry 未配置 / 持久化失败 / 注册失败)。
|
||
- **审计**:`persona.create`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/personas \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"id":"luna","name":"Luna","role":"女仆","modes":["normal"],"memoryNamespace":"luna"}'
|
||
# => {"data":{"id":"luna","name":"Luna","character":"Luna","isDefault":false,"mode":"normal","memoryNamespace":"luna"}}
|
||
```
|
||
|
||
### 5.3 PUT /api/v1/admin/personas/{id}
|
||
- **Path**:`{id}` = 人格 id。**Body** 同 5.2(不含 id;服务端以 path 覆盖 `req.ID`)。`name`/`role` 必填。
|
||
- **响应**:`200 {"data":<PersonaSummary>}`。
|
||
- **错误**:`400`(name/role 缺失);`404`(不存在);`500`(registry 未配置 / 持久化 / 更新失败)。
|
||
- **审计**:`persona.update`。curl:
|
||
|
||
```bash
|
||
curl -s -X PUT http://<host>:8080/api/v1/admin/personas/luna \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"name":"Luna","role":"女仆","modes":["gentle"]}'
|
||
```
|
||
|
||
### 5.4 POST /api/v1/admin/users
|
||
- **响应**:`501 {"error":{"code":"not_implemented","message":"新增用户/角色存储为 M10 提供(当前为单 admin + APIKey 现状)"}}`。**审计**:`user.create`(error)。
|
||
- **备注**:诚实不伪造(无持久用户表)。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/users \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' -d '{}'
|
||
```
|
||
|
||
### 5.5 POST /api/v1/admin/users/apikey
|
||
- **Body**(`apiKeyIssueRequest`):`{"role":"owner|admin|viewer"}`。
|
||
- **响应**:`201 {"data":{"id":"<掩码>","key":"<明文·仅此一次>","role":"<role>"}}`。`key` 为 base64 raw URL 编码的 32 字节随机串,**仅此响应可见一次**;`id` 为掩码(`xxxx....xxxx`),用于后续吊销。
|
||
- **错误**:`400`(invalid role/role 缺失);`500`(user service 未配置 / 生成失败)。
|
||
- **审计**:`apikey.issue`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/users/apikey \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"role":"admin"}'
|
||
# => {"data":{"id":"abcd****wxyz","key":"LQV...","role":"admin"}}
|
||
```
|
||
|
||
### 5.6 DELETE /api/v1/admin/users/apikey/{id}
|
||
- **Path**:`{id}` = 列表返回的掩码 id。
|
||
- **响应**:`200 {"data":{"id":"<掩码>","revoked":true}}`。
|
||
- **错误**:`404`(掩码未命中);`500`(user service 未配置)。
|
||
- **审计**:`apikey.revoke`。curl:
|
||
|
||
```bash
|
||
curl -s -X DELETE http://<host>:8080/api/v1/admin/users/apikey/abcd****wxyz \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 5.7 POST /api/v1/admin/iot/{id}/control
|
||
- **Path**:`{id}` = 设备 id。**Body**(`iotControlRequest`):`{"action":"<string>","params":{...}?}`。
|
||
- **响应**:`200 {"data":{<iotDeviceSummary>}}`(`{id,name,type,state,online}`)——真实下发到 IoT provider,并发布 `device_update` 事件。
|
||
- **错误**:`400`(action 缺失 / 动作不支持 / 参数错);`404`(设备不存在);`409`(设备离线);`500`(IoT 服务未配置 / 控制失败)。
|
||
- **审计**:`iot.control`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/iot/d1/control \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"action":"toggle","params":{"brightness":50}}'
|
||
```
|
||
|
||
### 5.8 POST /api/v1/admin/iot/scenarios
|
||
- **响应**:`501 {"error":{"code":"not_implemented","message":"IoT 场景存储为 M10 提供(当前 IoT 仅有设备列表/控制/状态)"}}`。**审计**:`iot.scenario`(error)。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/iot/scenarios -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{}'
|
||
```
|
||
|
||
### 5.9 POST /api/v1/admin/iot/rules
|
||
- **响应**:`501`(同 5.8,`iot.rule`)。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/iot/rules -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{}'
|
||
```
|
||
|
||
### 5.10 POST /api/v1/admin/automations
|
||
- **Body**(`automationCreateRequest`):`{"name","kind":"cron|event","expression"}`(均必填)。
|
||
- **响应**:`201 {"data":{"id":"<id>","name":"<name>","kind":"<kind>"}}`——真实注册规则。
|
||
- **错误**:`400`(字段缺失 / `invalid_cron` / `invalid_rule`);`500`(自动化服务未配置 / 添加失败)。
|
||
- **审计**:`automation.create`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/automations \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"name":"晨间问候","kind":"cron","expression":"0 8 * * *"}'
|
||
```
|
||
|
||
### 5.11 POST /api/v1/admin/automations/{id}/run
|
||
- **Path**:`{id}`。**Body**:无。
|
||
- **响应**:`200 {"data":{"id":"<id>","output":"<string>"}}`(手动触发一次)。
|
||
- **错误**:`404`;`500`。
|
||
- **审计**:`automation.run`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/automations/<id>/run -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 5.12 DELETE /api/v1/admin/automations/{id}
|
||
- **Path**:`{id}`。**响应**:`200 {"data":{"id":"<id>","deleted":true}}`。
|
||
- **错误**:`404`;`500`。**审计**:`automation.delete`。curl:
|
||
|
||
```bash
|
||
curl -s -X DELETE http://<host>:8080/api/v1/admin/automations/<id> -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 5.13 POST /api/v1/admin/llm
|
||
- **Body**(`llmProviderRequest`):`{"id?","name?","provider?","baseUrl","apiKey?","model","multimodal?","fallback?"}`。`baseUrl`+`model` 必填;`provider` 空默认 `openai`(仅允许 `openai|anthropic`)。
|
||
- **响应**:`201 {"data":{"provider":<AdminLlmProvider>,"persisted":true,"path":"<models.json 绝对路径>"}}`。**真实持久化**:读-改-写 `models.json`,保存组**提升为生效组**(index 0),并**热重载**运行中的 LLM stack(无需重启)。
|
||
- **错误**:`400`(缺 baseUrl/model / invalid provider);`500`(LLM 服务未注入 / 写盘失败 / 热重载失败)。**API key 写入文件但永不回显、不落日志**。
|
||
- **审计**:`llm.addProvider`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/llm \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"provider":"openai","baseUrl":"https://api.openai.com/v1","apiKey":"sk-...","model":"gpt-4o","multimodal":true}'
|
||
```
|
||
|
||
### 5.14 POST /api/v1/admin/llm/test
|
||
- **Body**(`llmTestRequest`):`{"provider?","baseUrl","model","apiKey?","prompt?"}`。`model`+`baseUrl` 必填;`provider` 空默认 `openai`;`prompt` 空用探针 `"Reply with the single word: OK"`。
|
||
- **响应**:`200 {"data":{"ok":true,"provider":"openai","model":"gpt-4o","reply":"<模型回复>","tokens":<int>}}`——**真实模型调用**(超时 30s)。
|
||
- **错误**:`400`(缺 model/baseUrl / invalid provider);`500`(调用失败)。
|
||
- **审计**:`llm.test`。**API key 仅本次请求内使用,不落库、不进 wire**。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/llm/test \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"provider":"openai","baseUrl":"https://api.openai.com/v1","model":"gpt-4o","apiKey":"sk-...","prompt":"你好"}'
|
||
```
|
||
|
||
### 5.15 POST /api/v1/admin/channels/open
|
||
- **Body**(`channelActionRequest`):`{"name":"<渠道名>"}`(如 `"kook"` / `"kook:conf0"` / 裸平台名解析到该平台首个账号)。
|
||
- **响应**:`200 {"data":{"name":"<name>","connected":true}}`——真实 Connect。
|
||
- **错误**:`400`(name 缺失);`404`(渠道未注册);`409` `{"error":{"code":"needs_login","message":"该渠道尚未登录:请先用「扫码登录」绑定,再连接"}}`(weixin-iLink 无 token);`500`(连接失败)。
|
||
- **审计**:`channel.open`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/channels/open \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"name":"kook"}'
|
||
```
|
||
|
||
### 5.16 POST /api/v1/admin/channels/close
|
||
- **Body**:`{"name":"<渠道名>"}`。**响应**:`200 {"data":{"name":"<name>","connected":false}}`——真实 Disconnect。
|
||
- **错误**:`400` / `404` / `500`。**审计**:`channel.close`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/channels/close \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' -d '{"name":"kook"}'
|
||
```
|
||
|
||
### 5.17 POST /api/v1/admin/channels/test
|
||
- **Body**(`channelTestRequest`):`{"platform?","accountId","isGroup"}`(`accountId` 必填)。
|
||
- **响应**:`200 {"data":{"role":"owner|member|...","owner":bool,"isGroup":bool,"env":"owner|...|","verdict":"<人读结论>"}}`。**结构性身份识别**:channel 层 actor 元数据 × `configs/users/owner.yaml` 白名单,**绝不靠提示词**。`verdict` 说明:群聊→隔离+记忆只读+仅允许工具;命中白名单→owner(全量访问);未命中→按陌生人(default deny)。
|
||
- **错误**:`400`(accountId 缺失)。**审计**:`channel.identity-test`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/channels/test \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"platform":"weixin-ilink","accountId":"wx_uid_123","isGroup":false}'
|
||
# => {"data":{"role":"owner","owner":true,"isGroup":false,"env":"owner","verdict":"匹配主人白名单 → owner(全量访问,记忆可读写)"}}
|
||
```
|
||
|
||
### 5.18 GET /api/v1/admin/channels/{platform}/login-qr
|
||
- **鉴权**:`owner`。**Path**:`{platform}` = `weixin-ilink`(其它平台 501)。**Query**:`?accountId=`(可选)。
|
||
- **响应**:`200 {"data":{"qrUrl?","qrCode?","status":"confirmed|<状态>"}}`。`qrUrl` 为微信二维码图 URL(前端据此渲染可扫 QR),`qrCode` 为原始扫码标识;已登录时 `status:"confirmed"`。
|
||
- **错误**:`501`(非 weixin-ilink);`404`(账号未找到,提示先保存账号);`500`(取码失败)。
|
||
- **审计**:`channel.weixin.login.qr`。curl:
|
||
|
||
```bash
|
||
curl -s 'http://<host>:8080/api/v1/admin/channels/weixin-ilink/login-qr?accountId=conf0' \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 5.19 GET /api/v1/admin/channels/{platform}/login-status
|
||
- **鉴权**:`owner`。**Path**:`{platform}` = `weixin-ilink`。**Query**:`?accountId=`(可选)。
|
||
- **响应**:`200 {"data":{"status":"confirmed|failed|...","botId?","botToken?","err?"}}`。`botToken` 为**掩码(`****`+尾4)**;`status==confirmed` 时后端已把刚取到的 `bot_token/bot_id/owner_id` **写入 channels.json** 并**热加载**该平台(bridge 连上已配置的 adapter,无需重启),且(owner_id 存在时)把扫码者绑定 id 登记进 owner 白名单(`owner.yaml`,热生效)。`status==failed` 时 `err` 带原因。
|
||
- **错误**:`501` / `404` / `500`。
|
||
- **审计**:`channel.weixin.login.status`(+ `channel.weixin.login.persist`(确认时))。curl:
|
||
|
||
```bash
|
||
curl -s 'http://<host>:8080/api/v1/admin/channels/weixin-ilink/login-status?accountId=conf0' \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
# 确认后 => {"data":{"status":"confirmed","botId":"wxid_xx","botToken":"****abcd"}}
|
||
```
|
||
|
||
### 5.20 GET /api/v1/admin/channels/{platform}/view
|
||
- **鉴权**:`owner`。**Path**:`{platform}` = `kook`(其它 501)。**Query**:`?accountId=`(可选)。
|
||
- **响应**:`200 {"data":{"platform":"kook","accountId?","friends?","userChats?","guilds?","channels?","members?","errors?"}}`。聚合该账号真实数据(好友/申请/屏蔽、私信会话、服务器 + 首个服务器频道与成员)。`errors` 为 `{子项:原因}` 记录失败子项(不伪造)。token/密钥不打印不落日志。
|
||
- **错误**:`501`(非 kook);`404`(账号未找到);`500`。
|
||
- **审计**:`channel.platform.view`。curl:
|
||
|
||
```bash
|
||
curl -s 'http://<host>:8080/api/v1/admin/channels/kook/view?accountId=3bef...' \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 5.21 GET /api/v1/admin/channels/{platform}/friends
|
||
- **鉴权**:`owner`。**Path**:`kook`。**Query**:`?type=friend|request|block&accountId=`(`type` 空=全部)。
|
||
- **响应**:`200 {"data":<KOOK FriendList>}`(含 friends/requests/blocked)。
|
||
- **错误**:`501` / `404` / `500`。**审计**:`channel.friends`。curl:
|
||
|
||
```bash
|
||
curl -s 'http://<host>:8080/api/v1/admin/channels/kook/friends?type=request&accountId=3bef...' \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 5.22 POST /api/v1/admin/channels/{platform}/friend/handle
|
||
- **鉴权**:`owner`。**Path**:`kook`。**Query**:`?accountId=`。**Body**(`kookFriendHandleRequest`):`{"id":<int64>,"accept":bool}`(id 必填)。
|
||
- **响应**:`200 {"data":{"handled":true,"id":<int64>,"accept":bool}}`。
|
||
- **错误**:`400`(id 缺失);`501` / `404` / `500`。**审计**:`channel.friend.handle`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST 'http://<host>:8080/api/v1/admin/channels/kook/friend/handle?accountId=3bef...' \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"id":12345,"accept":true}'
|
||
```
|
||
|
||
### 5.23 POST /api/v1/admin/channels/{platform}/message/test
|
||
- **鉴权**:`owner`。**Path**:`kook`。**Query**:`?accountId=`。**Body**(`kookMessageTestRequest`):`{"targetId":"<频道id>","content":"<文本>"}`(均必填)。
|
||
- **响应**:`200 {"data":{<KOOK TestMessageResult>}}`(如 `{msg_id,msg_timestamp}`)——真实向目标发一条 kmarkdown 测试消息。
|
||
- **错误**:`400`(字段缺失);`501` / `404` / `500`。**审计**:`channel.message.test`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST 'http://<host>:8080/api/v1/admin/channels/kook/message/test?accountId=3bef...' \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"targetId":"123456","content":"ping"}'
|
||
```
|
||
|
||
### 5.24 PUT /api/v1/admin/channels/{platform}/config
|
||
- **鉴权**:`owner`。**Path**:`{platform}`。**Body**(`channelConfigRequest`):
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `id` | string | 账号 id(可选;空则由服务端生成) |
|
||
| `token` | string | 快捷 token,并入 `config["bot_token"]`(若 config 未显式给) |
|
||
| `baseURL` | string | → `config["base_url"]` |
|
||
| `remoteURL` | string | → `config["remote_url"]` |
|
||
| `listenAddr` | string | → `config["listen_addr"]` |
|
||
| `mode` | string | → `config["mode"]` |
|
||
| `accessToken` | string | → `config["access_token"]` |
|
||
| `selfID` | string | → `config["self_id"]` |
|
||
| `config` | object | 平台专属键值(如 `bot_token`/`base_url`/`remote_url`/`endpointPath`/`receive_mode`/`webhook_verify_token`...) |
|
||
| `allowGroups`/`denyGroups` | string[] | 黑白名单(群) |
|
||
| `allowUsers`/`denyUsers` | string[] | 黑白名单(用户) |
|
||
| `groupMsgPerMin` | int | 群消息限流 |
|
||
| `msgPerMin` | int | 用户消息限流 |
|
||
| `maxMessages` | int | 单账号最大消息数 |
|
||
> 空配置时:`weixin-ilink` 豁免(先建账号再扫码),其它平台 `400`。
|
||
- **响应**:`201 {"data":{"persisted":true,"path":"<channels.json 路径>","platform":"<platform>","accountId":"<accountId>"}}`。**真实持久化 + 热更新**(读 channels.json→UpsertChannel→落盘→`Reload(platform)` 热替换 adapter,无需重启)。token 写入文件但**不回显**(GET 列表掩码)。
|
||
- **错误**:`400`(platform 缺失 / 空配置);`500`(渠道配置服务未注入 / 写盘失败 / 热重载失败)。
|
||
- **审计**:`channel.save`。curl:
|
||
|
||
```bash
|
||
curl -s -X PUT http://<host>:8080/api/v1/admin/channels/kook/config \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"id":"conf0","token":"<bot_token>","allowUsers":["user_i_like"],"groupMsgPerMin":20}'
|
||
```
|
||
|
||
### 5.25 DELETE /api/v1/admin/channels/{platform}/config
|
||
- **鉴权**:`owner`。**Path**:`{platform}`。**Query**:`?id=<accountId>`(必填)。
|
||
- **响应**:`200 {"data":{"persisted":true,"path":"<channels.json>","platform":"","accountId":"<id>"}}`。删除账号 → 落盘 → `Reload(platform)` 热更新(对应 adapter 优雅关停)。
|
||
- **错误**:`400`(platform/id 缺失);`500`(未注入 / 写盘 / 重载失败)。
|
||
- **审计**:`channel.delete`。curl:
|
||
|
||
```bash
|
||
curl -s -X DELETE 'http://<host>:8080/api/v1/admin/channels/kook/config?id=conf0' \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
|
||
```
|
||
|
||
### 5.26 POST /api/v1/admin/plugins/install
|
||
- **响应**:`501 {"error":{"code":"not_implemented","message":"插件安装需插件源码清单(编译二进制/YAML)为 M10 提供;卸载可直接调用 registry"}}`。**审计**:`plugin.install`(error)。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/plugins/install \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"name":"x"}'
|
||
```
|
||
|
||
### 5.27 POST /api/v1/admin/plugins/uninstall
|
||
- **Body**(`pluginActionRequest`):`{"name":"<能力名>"}`(必填)。
|
||
- **响应**:`200 {"data":{"name":"<name>","uninstalled":true}}`——真实从 registry 移除。
|
||
- **错误**:`400`(name 缺失);`404`(不存在);`500`。
|
||
- **审计**:`plugin.uninstall`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/plugins/uninstall \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' -d '{"name":"web_search"}'
|
||
```
|
||
|
||
### 5.28 POST /api/v1/admin/memories
|
||
- **Body**(`memoryAddRequest`):`{"personaId?","content","scope?"}`。`content` 必填;`personaId` 缺省 `"cyrene"`;`scope` ∈ `user|persona|project`(缺省 `persona`;无效非空回 400)。
|
||
- **响应**:`201 {"data":{"id":"<id>","scope":"<scope>","text":"<内容>"}}`——真实写入 memory store。
|
||
- **错误**:`400`(content 缺失 / scope 非法);`501`(memory store 未配置 / 未注入 `memory.Store`);`500`。
|
||
- **审计**:`memory.add`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/memories \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"personaId":"cyrene","content":"主人喜欢喝美式咖啡","scope":"user"}'
|
||
```
|
||
|
||
### 5.29 POST /api/v1/admin/memories/search
|
||
- **Body**(`memorySearchRequest`):`{"query","personaId?","topK?"}`。`query` 必填;`personaId` 缺省 `"cyrene"`;`topK` ≤0 取 10。
|
||
- **响应**:`200 {"data":{"query":"<query>","personaId":"<personaId>","semantic":false,"hits":[{"id","scope","text","score"}]}}`。真实检索(keyword×衰减);语义/向量路径需注入 vector index(`semantic` 当前恒 `false`,诚实反映未接向量器)。
|
||
- **错误**:`400`(query 缺失);`500`(memory store 未配置 / 检索失败)。
|
||
- **审计**:`memory.search`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/memories/search \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"query":"咖啡","personaId":"cyrene","topK":5}'
|
||
```
|
||
|
||
### 5.30 POST /api/v1/admin/rag
|
||
- **Body**(`ragIndexRequest`):`{"name","desc?","content"}`。`name`+`content` 必填。
|
||
- **响应**:`201 {"data":{"name":"<name>"}}`——真实导入:分块并落项目索引,立即可被关键词/行上下文检索。
|
||
- **错误**:`400`(name/content 缺失);`501`(未注入 RagStore);`500`。
|
||
- **审计**:`rag.index`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/rag \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"name":"部署说明","desc":"v2 部署要求","content":"单二进制...\n单端口 8080..."}'
|
||
```
|
||
|
||
### 5.31 POST /api/v1/admin/system/revoke-keys
|
||
- **Body**:无。
|
||
- **响应**:`200 {"data":{"revoked":<int>}}`——清空 API key 表,先前签发的所有 key 立即失效。
|
||
- **错误**:`500`(user service 未配置,`users_unavailable`)。
|
||
- **审计**:`system.revoke_keys`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/system/revoke-keys \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1' -d '{}'
|
||
```
|
||
|
||
### 5.32 POST /api/v1/admin/system/rotate-jwt
|
||
- **Body**(`rotateJWTRequest`):`{"newSecret":"<≥32 位>"}`(必填)。
|
||
- **响应**:`200 {"data":{"rotated":true,"note":"现有 token 已全部失效,需重新登录"}}`。
|
||
- **错误**:`400`(newSecret 缺失 / 长度 <32,`"新密钥长度不足 32 位"`);`500`(认证服务未配置 / 轮换失败)。
|
||
- **审计**:`system.rotate_jwt`。**新密钥永不回显、不落日志**。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/system/rotate-jwt \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"newSecret":"a-32-char-long-random-secret-12345678"}'
|
||
```
|
||
|
||
### 5.33 POST /api/v1/admin/system/backup
|
||
- **Body**:无。
|
||
- **响应**:`200 {"data":{"path":"<dataDir>/backup-<ts>.db","size":<字节数>}}`。经 SQLite `VACUUM INTO`(含已提交 WAL)快照到数据目录。
|
||
- **错误**:`500`(DB 路径未配置 `backup_unavailable` / 备份失败)。
|
||
- **审计**:`system.backup`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/system/backup \
|
||
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1' -d '{}'
|
||
```
|
||
|
||
### 5.34 POST /api/v1/admin/system/restore
|
||
- **Body**(`restoreRequest`):`{"path":"<dataDir 内的备份文件>"}`(必填)。
|
||
- **响应**:`200 {"data":{"restored":true,"backup":"<恢复前自动快照路径>","note":"已安排恢复,需重启服务后生效"}}`。**流程**:校验 path 在 dataDir 内(`filepath.Rel` 防路径穿越,`..` 逃逸回 400)→ 文件存在校验 → **先自动备份当前 DB** → 复制替换 DB 文件 → 返回(替换后需重启服务生效,因运行进程持有 DB 句柄)。
|
||
- **错误**:`400`(path 缺失 / 逃逸数据目录);`404`(备份文件不存在);`500`(DB 路径未配置 / 恢复前备份失败 / 替换失败)。
|
||
- **审计**:`system.restore`。curl:
|
||
|
||
```bash
|
||
curl -s -X POST http://<host>:8080/api/v1/admin/system/restore \
|
||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
|
||
-d '{"path":"/abs/path/data/backup-20260908T000000.000000000Z.db"}'
|
||
```
|
||
|
||
---
|
||
|
||
## 6. WebSocket 实时协议(`GET /ws/chat`)
|
||
|
||
> 唯一实时通道。**单二进制、单端口**:同一 `:8080` 上,REST/WS/admin 共用。生产者(loop/IoT/task/automation/reminder/subagent)经进程内 `bus` 发布,hub 订阅 `ws.>` 及桥接模式并规范化成 WS 帧广播给客户端。**WS 上行不鉴权角色门槛**,但特定下行按角色(`history` 回放对任何已认证连接,`proactive` 仅 owner/admin)。
|
||
|
||
### 6.1 握手与连接
|
||
|
||
- **URL**:`ws://<host>:8080/ws/chat?token=<JWT>`(或 `Authorization: Bearer <JWT>` 头)。
|
||
- **方法**:必须 `GET`(否则握手前回 `400 {"error":{"code":"bad_request","message":"websocket requires GET"}}`)。
|
||
- **鉴权**:`s.auth.Verify(wsToken(r))`。`wsToken` 先取 `?token=`,再取 `Authorization: Bearer`。失败 → **握手前**回 `401 {"error":{"code":"unauthorized",...}}`(浏览器/IM 客户端收到干净拒绝)。**不校验角色**(任意有效 JWT 可连)。
|
||
- **Origin 校验**:`CheckOrigin` 允许「同一 origin」+「同一 host」+ 白名单;`*` 全放行。同 host 放宽:production 内嵌前端页面 origin 即网关 Host,故 `PORT` 改变无需改 `.env`。**跨源客户端**(外部 IM / 另一 origin)需在 `ALLOWED_ORIGINS` 白名单。
|
||
- **升级后流程**:注册连接 → `replayHistory(c, "web")`(首帧推 `history` 回放)→ 启动 `writePump`(唯一写者,含 ping/pong)与 `readPump`(读客户端帧)。
|
||
- **心跳/限帧**:读上限 4096 字节/帧;写超时 10s;pong 等待 60s;ping 周期 54s。发送缓冲 32(慢客户端满则丢帧,生产者不阻塞)。
|
||
- **`history` 回放(首帧)**:`{type:"history", sessionId:"web", data:{messages:[historyMessage...]}}`——主会话最近 50 条(`wsReplayLimit=50`),**`thinking` 已剥离**;回放仅在 `history != nil` 时发生,失败静默跳过。
|
||
|
||
### 6.2 帧壳(Frame Shell)
|
||
|
||
- **下行(服务端→客户端)** `wsEvent`:
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|:---:|------|
|
||
| `type` | string | 是 | 事件类型(见 §6.3 表) |
|
||
| `turnId` | string | 否 | 一轮交换的关联 id(流式;`turn-<hex>`);OnStream 结构化流路径**不带** |
|
||
| `seq` | int | 否 | hub 级单调递增序号,供客户端排序关联流式突发 |
|
||
| `sessionId` | string | 否 | 能确定会话的事件带上,客户端按激活会话过滤 |
|
||
| `data` | any | 否 | 类型化负载(每个事件见 §6.3) |
|
||
|
||
- **上行(客户端→服务端)** `wsInbound`:
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|:---:|------|
|
||
| `type` | string | 是 | 事件类型(见 §6.3 上行表) |
|
||
| `reqId` | string | 否 | 请求关联 id(当前服务端**不**回填,预留) |
|
||
| `data` | object | 否 | 每类型负载(raw JSON) |
|
||
|
||
### 6.3 上行事件(客户端 → 服务端)
|
||
|
||
> 由 `wsConn.route(in)` 按 `in.Type` 分派。未知类型回 `{type:"error", data:{message:"unknown event type"}}`。
|
||
|
||
| type | 数据负载 | 服务端行为 | 结果 |
|
||
|------|----------|-----------|------|
|
||
| `mode_switch` | 任意(如 `{"mode":"chat"}`) | 发布 `ws.mode_switch` → hub 规范化广播 | 下行 `mode_switch`(广播) |
|
||
| `chat` | `{sessionId?, text, image?, deviceId?}` | 发布 `ws.chat` → `streamChat`(跑 loop 并流式) | 下行 `stream_chunk`/`stream_end` 或 `message.*/thinking.*/tool_call.*`(广播) |
|
||
| `interrupt` | `{sessionId}` | 会话编排器中断:取消在跑 turn + 清空 pending | 下行 `session_interrupted` |
|
||
| `voice_input` | `{audio?, format?, text?, sessionId?}` | STT→chat→TTS(缺省 sessionId=`web`) | 下行 `voice_output`(**定向回请求连接**) |
|
||
| `subagent_ask` | `{id, mode?(""/full/tail/keyword), n, query?}` | 审计子代理上下文(缺省 `mode`=full) | 下行 `subagent_event`(或 `error`) |
|
||
| `task_output_query` | `{taskId}` | 查询后台任务输出 | 下行 `task_output`(或 `error`) |
|
||
|
||
**上行各负载字段**
|
||
|
||
- **`chat`**(`chatStreamRequest`):`{sessionId`(缺省`"web"`), `text`, `image?`(base64 data: URI 或 URL), `deviceId?`}。text+image 至少一项;`runner` 未注入或两者皆空 → 直接广播 `stream_end{stopReason:"idle"}`。主会话 `web` 绑定全量 env(`loop.OwnerEnv()`)。`chat` 进入时若该会话正忙 → 由编排器入队(见 §6.4),非阻塞。
|
||
- **`interrupt`**:`{sessionId}`。命中空闲会话为 no-op。
|
||
- **`voice_input`**:`{audio`(base64 或 data: URI), `format`(`wav|mp3|ogg|webm|mpeg`,空由 mime 猜,默认 `wav`), `text`(已转写则跳过 STT), `sessionId?`}。无 `voice` 服务 → `{type:"error",data:{message:"voice unavailable"}}`。
|
||
- **`subagent_ask`**:`{id`(必填), `mode`(空/full/tail/keyword), `n`(tail 查询窗长), `query`(keyword 查询词)}。无 auditor → `error`;id 缺失 → `error`。
|
||
- **`task_output_query`**:`{taskId}`。无 task store → `error`;运行中任务返回截至目前输出;输出拼接 `stdout + "\n" + stderr`。
|
||
|
||
### 6.4 下行事件全表
|
||
|
||
> **来源标记**:`[direct]` 网关直接 emit;`[relay]` hub 从 bus `ws.>` 规范化(任意 `ws.X` 主题 → 类型 `X`);`[bridge]` 桥接 producer 主题;`[target]` 仅定向特定连接。
|
||
|
||
| type | 来源 | 数据(`data`) | 用途 |
|
||
|------|------|----------------|------|
|
||
| `history` | direct | `{messages:[historyMessage]} ` | 连接首帧回放主会话最近 50 条(thinking 剥离) |
|
||
| `message.start` | direct | `{index}` | 正文段开始 |
|
||
| `message.delta` | direct | `{delta,index}` | 正文 token 增量(累加到当前正文) |
|
||
| `message.end` | direct | `{index}` | 正文段结束(不结束整轮,由 `stream_end` 收尾) |
|
||
| `thinking.start` | direct | `{index}` | 思维链段开始(前端折叠块) |
|
||
| `thinking.delta` | direct | `{delta,index}` | 思维链 token 增量 |
|
||
| `thinking.end` | direct | `{index}` | 思维链段结束 |
|
||
| `tool_call.start` | direct | `{id,index}` | 工具调用中间态开始 |
|
||
| `tool_call.delta` | direct | `{id,index,delta}` | 工具调用参数增量(如 `input_json_delta`) |
|
||
| `tool_call.end` | direct | `{id,index}` | 工具调用中间态结束 |
|
||
| `tool_execution.update` | direct | `{id,index,delta}` | 工具执行进度/局部输出 |
|
||
| `stream_chunk` | direct | `{delta}` | **向后兼容整段文本**(仅非流式回退路径;流式路径不 emit) |
|
||
| `stream_end` | direct | `{stopReason, steps?}` / `{stopReason:"idle"}` / `{stopReason:"error", error}` | 一轮结束 |
|
||
| `session_busy` | direct(编排) | `{sessionId, mode}` | 会话进入忙碌态(mode∈idle/thinking/executing/subagent) |
|
||
| `pending_ack` | direct(编排) | `{sessionId, mode, queueDepth}` | 忙时新消息已入队「收到,稍后回」 |
|
||
| `pending_drain` | direct(编排) | `{sessionId, mode, queueDepth}` | 忙完开始消费下一待处理消息 |
|
||
| `session_idle` | direct(编排) | `{sessionId, mode}` | 会话回到空闲、队列清空 |
|
||
| `session_interrupted` | direct(编排) | `{sessionId, mode, dropped}` | 用户 interrupt,当前任务取消、pending 清空 |
|
||
| `pending_full` | direct(编排) | `{sessionId, mode, max}` | 单会话 pending 达上限(默认 5)拒绝入队 |
|
||
| `message_persisted` | direct | `{message:historyMessage}`(sessionId 设) | 每条 user/assistant 消息落库后广播(主会话 `web`/`web:<uuid>`),thinking 剥离 |
|
||
| `channel_persisted` | direct | `{message:historyMessage}`(sessionId=`sess-xxx`) | 群聊/陌生人(非主人)消息落库后广播,供 IM 适配器日志页 |
|
||
| `voice_output` | direct/target | `{audio, format, text, reply}` | 语音合成回复(定向回请求连接) |
|
||
| `subagent_event` | direct+relay | 审计结果 或 `{id,name,status,role,mode}` | 子代理上下文查询应答 / 子代理进度 |
|
||
| `task_output` | direct+bridge | `{taskId, output, status}` | 后台任务输出查询应答 / task.output 桥接 |
|
||
| `capacity_awaken` | direct | `{fromState, toState, tool?}` | 能力状态迁移(design/14 §5,`sessionId` 设) |
|
||
| `proactive_message` | direct | `{platform?, channelId?, channelType?, content, contentType?, sender?, timestamp?, messageId?}` | 自主主动推送(仅 owner/admin 连接收到) |
|
||
| `device_update` | bridge | producer payload | IoT 设备状态更新(`iot.device_update`) |
|
||
| `task_done` | bridge | producer payload | 后台任务完成(`task.done`) |
|
||
| `automation_result` | bridge | producer payload | 自动化规则结果(`automation.result`) |
|
||
| `reminder` | bridge | producer payload | 提醒触发(`reminder.trigger`) |
|
||
| `mode_switch` | direct | 上行原样(任意) | 行为模式切换回显(`ws.mode_switch`) |
|
||
| `mood`/`tool_used`/`graph_update`/… | relay | `ws.X` 主题负载(`ws.mood`→`mood`,`ws.tool_used`→`tool_used`,`ws.graph_update`→`graph_update`,…) | 生产者在 bus 上发 `ws.X` 即规范化成类型 `X` 广播 |
|
||
| `error` | direct | `{message}` | 非法 JSON / 未知类型 / 语音·子代理·任务错误等 |
|
||
|
||
> **`auth`**(旧 design/07 列表):当前**不**由网关直接 emit;若未来有 `ws.auth` 生产者主题,则按 `[relay]` 规则广播。本文档以代码为准。
|
||
|
||
**`message_persisted` / `channel_persisted` / `history` 的 `historyMessage` 全字段**(含 `thinking` 剥离规则):
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `id` | string | 后端生成消息 id(客户端去重键) |
|
||
| `sessionId` | string | 会话 id |
|
||
| `role` | string | `user` / `assistant` |
|
||
| `content` | string | 正文 |
|
||
| `mode` | string | `omitempty` |
|
||
| `mood` | string | `omitempty` |
|
||
| `image` | string | `omitempty` |
|
||
| `thinking` | string | **`omitempty`,但在这些实时帧中恒为空**(`BroadcastPersisted`/`replayHistory` 均置空;`/messages` owner/admin 端点才带) |
|
||
| `platform` | string | `omitempty`(`web`/`telegram`/`weixin-ilink`/`qq-obv11`...) |
|
||
| `deviceId` | string | `omitempty` |
|
||
| `senderName` | string | `omitempty`(第三方昵称,主人私聊为空) |
|
||
| `adapterId` | string | `omitempty`(仅 `channel_persisted` 常见,如 `kook:3bef...`) |
|
||
| `createdAt` | int64 | Unix 毫秒 |
|
||
|
||
**事件类型分流**(`BroadcastPersisted`):`sessionId == "web"` 或以 `web:` 前缀 → **`message_persisted`**(Web 聊天实时同步);其它(隔离 `sess-xxx`)→ **`channel_persisted`**(IM 适配器日志专用)。Web 聊天无 `channel_persisted` handler,天然忽略。
|
||
|
||
### 6.5 会话编排状态(design/15,`data.sessionId` 区分)
|
||
|
||
- **语义**:同一会话 `chat` 到达时若「她忙」(跑 loop/工具/子代理/长回复)→ 新消息**不阻塞**、入该会话 pending(上限默认 5);状态经上述 `session_busy`/`pending_ack`/`pending_drain`/`session_idle`/`session_interrupted`/`pending_full` 推给前端,供「正在做 xx… 有新消息稍后回」轻状态展示。
|
||
- **续接**:忙完重开时新回合**带上上一轮上下文**(`tail` 默认 20 条),非清空重来。
|
||
- **mode** 取值:`idle` / `thinking` / `executing` / `subagent`。
|
||
- REST 同步路径(§3.1)在忙时返回 `202 queued` / 满 `409 chat_busy`,与这些 WS 状态事件一致。
|
||
|
||
### 6.6 流式关联(`turnId` / `seq`)
|
||
|
||
- `turnId`:`runTurn` 每次生成 `turn-<hex>`(crypto/rand),用于关联一轮流式突发;`stream_end`/`stream_chunk`(回退路径)带 `turnId`。**OnStream 结构化流路径(`message.*` 等)由 `broadcastStream` 只填 `seq`、不带 `turnId`**(OnStream bus 负载未携带 turn 身份)。
|
||
- `seq`:hub 级单调递增 `atomic.Uint64`,保证一次突发有序,即便 loop 多 goroutine 触发。
|
||
|
||
### 6.7 WS 示例
|
||
|
||
**握手 + 收 `history` + 发 `chat` 收流式**(伪代码/curl-like):
|
||
|
||
```bash
|
||
# 1) 拿 token
|
||
TOKEN=$(curl -s -X POST http://<host>:8080/api/v1/auth/login \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"username":"admin","password":"s3cret"}' | jq -r .data.token)
|
||
|
||
# 2) 连接(浏览器用 ws = new WebSocket('ws://host:8080/ws/chat?token='+TOKEN))
|
||
# 首帧:{"type":"history","sessionId":"web","data":{"messages":[...]}}
|
||
|
||
# 3) 上行 chat
|
||
# {"type":"chat","data":{"sessionId":"web","text":"你好","deviceId":"web:chrome:a1b2c3"}}
|
||
# 下行(流式回退):{"type":"stream_chunk","turnId":"turn-abc123","seq":1,"sessionId":"web","data":{"delta":"你"}}
|
||
# ... {"type":"stream_chunk",...} ...
|
||
# {"type":"stream_end","turnId":"turn-abc123","seq":8,"sessionId":"web","data":{"stopReason":"stop","steps":2}}
|
||
|
||
# 4) 上行 interrupt
|
||
# {"type":"interrupt","data":{"sessionId":"web"}}
|
||
# 下行:{"type":"session_interrupted","data":{"sessionId":"web","mode":"idle","dropped":2}}
|
||
|
||
# 5) 上行 voice_input
|
||
# {"type":"voice_input","data":{"audio":"<base64>","format":"wav","sessionId":"web"}}
|
||
# 下行(定向):{"type":"voice_output","data":{"audio":"<base64>","format":"wav","text":"你好","reply":"你好呀"}}
|
||
|
||
# 6) 上行 subagent_ask
|
||
# {"type":"subagent_ask","data":{"id":"sub-1","mode":"full"}}
|
||
# 下行:{"type":"subagent_event","data":{...审计结果...}}
|
||
|
||
# 7) 上行 task_output_query
|
||
# {"type":"task_output_query","data":{"taskId":"task-1"}}
|
||
# 下行:{"type":"task_output","data":{"taskId":"task-1","output":"...","status":"done"}}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 上传 / 媒体说明(重要检查结论)
|
||
|
||
- **无 `/upload` 端点**:网关未注册任何文件上传路由。图片通过 `POST /api/v1/chat` 或 WS `chat` 的 `image` 字段以 **base64 data: URI** 或 **http(s) URL** 传入,入视觉真推理路径(vision/OCR 或主模型多模态,design/06)。文档导入走 `POST /api/v1/admin/rag` 的 JSON `content` 文本。
|
||
- **无 REST 语音端点**:语音只走 WS —— 上行 `voice_input`(base64 音频或已转写 `text`),下行 `voice_output`(合成音频 base64 + 识别文本 + 回复文本,定向回请求连接)。REST 侧没有语音流。
|
||
- **图片 + 文本并存**:`image` 非空时 turn 变为「带图 + 可选文本」;`text` 空 + `image` 有 → 纯图像 turn。
|
||
- **敏感面**:`image`(base64)与 `bot_token`/`apiKey`/`newSecret` 等概不作为日志明文输出;wire 响应对 token/key 做掩码或不返回。
|
||
|
||
---
|
||
|
||
## 8. 附录
|
||
|
||
### 8.1 通用错误码 / 状态码
|
||
|
||
| 状态码 | code(示例) | 说明 |
|
||
|:---:|------|------|
|
||
| 200 | - | 成功 |
|
||
| 201 | - | 创建成功(personas/子会话/apikey/automations/llm/channels config/memories/rag) |
|
||
| 202 | - | 已接受(chat 排队 `queued:true,queueDepth`) |
|
||
| 204 | - | CORS 预检 OPTIONS 成功无内容 |
|
||
| 400 | `bad_request` / `invalid_cron` / `invalid_rule` / `invalid_role` / `unsupported_action` / `invalid_param` / `invalid_provider` / `invalid_scope` | 请求体/参数非法 |
|
||
| 401 | `unauthorized` | 认证缺失/失效/过期(REST Bearer 与 WS) |
|
||
| 403 | `forbidden` | 角色不足(viewer 读管理/主会话删除/写操作);主会话不可删 |
|
||
| 404 | `not_found` | 资源不存在 |
|
||
| 409 | `chat_busy` / `duplicate` / `device_offline` / `needs_login` | 冲突:队列满 / 人格已存在 / 设备离线 / 渠道未登录 |
|
||
| 500 | `internal` / `chat_failed` / `sessions_failed` / … | 服务端错误(含未注入依赖) |
|
||
| 501 | `not_implemented` | 后端**确定未实现**(新增用户/IoT 场景·规则/插件安装/RAG 删除)——诚实返回,不伪造成功 |
|
||
|
||
### 8.2 依赖注入与「未配置」行为
|
||
|
||
网关依赖(`Deps`)大多为 **nil 容忍**,未注入时端点要么回退、要么**如实报错**,绝不伪造成功:
|
||
- `History` 未注入:`GET /sessions` 回退渠道会话视图;`/messages`、`POST /sessions`、`DELETE /sessions/{id}`、`/channels/messages` 回 `501 not_implemented`。
|
||
- `RAG` 未注入:`GET /admin/rag` 返回空文档列表;`POST /admin/rag`、`/admin/rag/delete` 回 `501`。
|
||
- `Runner(Loop)` 未注入:`/chat` 回 `500 chat_unavailable`;WS `chat` 直接广播 `stream_end{stopReason:"idle"}`。
|
||
- `Channels` 未注入:`/admin/channels/config` 回 `500 channels_unavailable`。
|
||
- `LLM` 未注入:`/admin/llm` 按 `.env` 合成单组;`POST /admin/llm` 回 `500 llm_unavailable`。
|
||
- `Voice` 未注入:WS `voice_input` 回 `error {message:"voice unavailable"}`。
|
||
- `Scheduler(auditor)` 未注入:WS `subagent_ask` 回 `error {message:"subagent audit unavailable"}`。
|
||
- `Tasks` 未注入:WS `task_output_query` 回 `error {message:"task output unavailable"}`。
|
||
- `AuditStore` 未注入:审计退化为进程内存(端点仍返回数据)。
|
||
- 管理员**只读**端点若依赖缺失,大多返回 `[]`,不 500。
|
||
|
||
### 8.3 审计动作清单(`what` 字段,落 `audit_logs`)
|
||
|
||
`policy.set` / `persona.create` / `persona.update` / `user.create` / `apikey.issue` / `apikey.revoke` / `iot.control` / `iot.scenario` / `iot.rule` / `automation.create` / `automation.run` / `automation.delete` / `llm.addProvider` / `llm.test` / `channel.open` / `channel.close` / `channel.identity-test` / `channel.weixin.login.qr` / `channel.weixin.login.status` / `channel.weixin.login.persist` / `channel.platform.view` / `channel.friends` / `channel.friend.handle` / `channel.message.test` / `channel.save` / `channel.delete` / `plugin.install` / `plugin.uninstall` / `memory.add` / `memory.search` / `rag.index` / `rag.query` / `rag.delete` / `session.export` / `system.revoke_keys` / `system.rotate_jwt` / `system.backup` / `system.restore`。
|
||
(`what` 后缀 `_failed/_persist_failed/_reload_failed` 等计入 `error` 分支。)
|
||
|
||
### 8.4 里程碑与未实现集合(诚实标注)
|
||
|
||
- **已真实实现**:登录/健康检查/聊天(同步+流式)/会话(列表/新建/删除/消息分页)/人格/管理读(personas/sessions/memories/plugins/channels/iot/llm/users/automations/backthink/rag/observability×3)/管理写(人格/API key/设备控制/自动化/LLM 保存·测试/渠道连接·断开·身份测试/渠道配置·删除/KOOK 面板/微信扫码/记忆添加·检索/插件卸载/RAG 导入/系统设置 4 项)。
|
||
- **仍为后置 M10(返回 `501 not_implemented`,不伪造)**:新增用户、IoT 场景与规则、插件安装(需源码清单)、RAG 删除、主人档案保存、编辑环境变量/重启服务/检查更新、完整会话转录导出(当前导出身份元数据)。
|
||
- **语义/向量增强**:`memory.search` 的 `semantic` 恒为 `false`(未接向量器时诚实反映);RAG `embedded` 反映是否配置向量增强;配置了增强必须真实走语义/向量路径(红线,非空壳)。
|
||
- **API key 认证**:`X-API-Key` 当前未被 `Authorize` 中间件校验(见 §0.1 注);key 的签发/吊销/列表已真实生效。
|
||
|
||
### 8.5 客户端接入快速索引
|
||
|
||
1. `POST /api/v1/auth/login` → `{token, expiresAt, role}`(恒 `owner`)。
|
||
2. 之后每个 REST 请求带 `Authorization: Bearer <token>`;管理写需 `owner`;管理读需 `owner|admin`;`GET /sessions`、`GET /personas` 任意已认证。
|
||
3. 实时:`ws://<host>:8080/ws/chat?token=<token>`;首帧 `history` 回放;上行 `chat`/`interrupt`/`voice_input`/`subagent_ask`/`task_output_query`/`mode_switch`;下行见 §6.4。
|
||
4. 身份判定永远来自后端 channel 层 actor 元数据 + 白名单(`configs/users/owner.yaml`),**客户端不要用提示词/文本判身份**;`thinking` 在公开回放/实时帧中已剥离,客户端不应依赖。
|
||
5. 发送消息带 `deviceId`(推荐)以便端标识落库;`platform` 用于 IM 渠道过滤。
|