docs: 更新协议文档并清理过时计划文件
This commit is contained in:
@@ -52,7 +52,7 @@ Base URL: `/api/v1/agent`
|
||||
|
||||
## 2) GET `/runs/{thread_id}/events`
|
||||
|
||||
订阅指定 thread 的实时事件流。
|
||||
订阅指定 thread 的实时事件流(按 `runId` 隔离当前 run)。
|
||||
|
||||
### Path
|
||||
|
||||
@@ -64,6 +64,7 @@ Base URL: `/api/v1/agent`
|
||||
|
||||
| 参数 | 类型 | 默认 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `runId` | string | - | 目标 run ID(必填)。SSE 仅输出该 run 事件 |
|
||||
| `idle_limit` | integer | `300` | 最大空闲轮询次数(1-3600) |
|
||||
|
||||
### Headers
|
||||
@@ -81,6 +82,11 @@ Base URL: `/api/v1/agent`
|
||||
- usage 审计与成本回退策略见 `docs/protocols/agent/sse-events.md`(5) Usage 审计协议)
|
||||
- 空闲时会发送 keep-alive 注释行 `: keep-alive`
|
||||
|
||||
run 过滤语义:
|
||||
|
||||
- 服务端会读取 thread 的事件流游标,但仅向客户端发送 `event.runId == query.runId` 的事件。
|
||||
- SSE 连接终止条件为“目标 run 收到 `RUN_FINISHED` 或 `RUN_ERROR`”,其他 run 的 terminal 事件不会终止当前连接。
|
||||
|
||||
当前阶段执行说明:
|
||||
|
||||
- `chat` 模式采用两阶段:`router` -> `worker`。
|
||||
@@ -91,6 +97,7 @@ Base URL: `/api/v1/agent`
|
||||
|
||||
- `401` 未认证
|
||||
- `403` 非会话所有者
|
||||
- `422` `runId` 非法
|
||||
- `422` `Last-Event-ID` 非法
|
||||
- `429` 超过 SSE 连接数限制
|
||||
|
||||
@@ -279,6 +286,7 @@ Agent 路由的错误同样遵循统一 HTTP 错误契约,详见:
|
||||
|
||||
- `AGENT_RUN_INPUT_INVALID`
|
||||
- `AGENT_RUN_MESSAGES_INVALID`
|
||||
- `AGENT_INVALID_RUN_ID`
|
||||
- `AGENT_INVALID_LAST_EVENT_ID`
|
||||
- `AGENT_SSE_CONNECTION_LIMIT`
|
||||
- `AGENT_ATTACHMENT_EMPTY`
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
本文档描述 `GET /api/v1/agent/runs/{thread_id}/events` 的事件协议。
|
||||
|
||||
> 当前协议要求 SSE 订阅显式携带 `runId`,事件输出按 run 隔离。
|
||||
|
||||
---
|
||||
|
||||
## 1) 事件管道
|
||||
@@ -13,7 +15,7 @@
|
||||
3. 事件同时:
|
||||
- 持久化到数据库(用于 history)
|
||||
- 发布到 Redis Stream(用于 SSE)
|
||||
4. `/runs/{thread_id}/events` 从 Redis Stream 读取并输出 SSE
|
||||
4. `/runs/{thread_id}/events` 从 Redis Stream 读取并输出 SSE(仅输出目标 `runId` 事件)
|
||||
|
||||
---
|
||||
|
||||
@@ -30,6 +32,7 @@ data: <json>
|
||||
|
||||
- `id` 可用于断点续流(`Last-Event-ID`)
|
||||
- `event` 与 JSON 内 `type` 一致(例如 `RUN_STARTED`)
|
||||
- 仅当 payload 中 `runId` 与 query `runId` 一致时才会对外发送
|
||||
- 空闲时可能出现 keep-alive 注释帧:
|
||||
|
||||
```text
|
||||
@@ -41,6 +44,15 @@ data: <json>
|
||||
|
||||
## 3) 事件类型(当前实现)
|
||||
|
||||
### 3.0 过滤与终止规则
|
||||
|
||||
- 请求参数 `runId` 为本次订阅目标 run。
|
||||
- 服务端只转发 `event.runId == runId` 的事件。
|
||||
- SSE 连接仅在目标 run 收到 terminal 事件后结束:
|
||||
- `RUN_FINISHED`
|
||||
- `RUN_ERROR`
|
||||
- 其他 run 的事件(包括 terminal)不会结束当前连接。
|
||||
|
||||
### 3.1 Run 生命周期
|
||||
|
||||
#### `RUN_STARTED`
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# 客户端缓存键作用域规范(Cache Key Scoping)
|
||||
|
||||
## 目标
|
||||
|
||||
防止同一设备多账号切换时出现缓存串读(A 账号数据在 B 账号展示)。
|
||||
|
||||
本规范适用于 `apps/lib/data/cache/**` 以及所有通过 `CachedRepository` 读写的业务缓存。
|
||||
|
||||
---
|
||||
|
||||
## 作用域模型
|
||||
|
||||
### 1) 作用域级别
|
||||
|
||||
- `anonymous`:未登录态(或未绑定用户上下文)
|
||||
- `user:<user_id>`:已登录用户态
|
||||
|
||||
### 2) 键格式
|
||||
|
||||
客户端持久缓存最终键必须按以下格式生成:
|
||||
|
||||
`cache:<scope>:<feature-key>`
|
||||
|
||||
其中 `<scope>` 可以附带会话代际后缀(推荐):
|
||||
|
||||
- `user:<user_id>:v<epoch>`
|
||||
- `anonymous:v<epoch>`
|
||||
|
||||
该后缀用于切号并发场景,确保旧会话异步回写落在旧命名空间。
|
||||
|
||||
示例:
|
||||
|
||||
- `cache:user:8ef4...:chat:history:first:default`
|
||||
- `cache:user:8ef4...:v12:chat:history:first:default`
|
||||
- `cache:user:8ef4...:v12:calendar:day:2026-03-29`
|
||||
- `cache:anonymous:v13:inbox:list:all`
|
||||
|
||||
---
|
||||
|
||||
## 责任边界
|
||||
|
||||
### 基础层(必须)
|
||||
|
||||
- `CachedRepository` 负责统一附加 `<scope>` 前缀。
|
||||
- Feature Repository 只声明业务键(`<feature-key>`),不得手工拼接 userId 前缀。
|
||||
|
||||
### 应用层(必须)
|
||||
|
||||
- 在认证状态变化时更新当前缓存作用域:
|
||||
- 登录成功 -> `user:<user_id>`
|
||||
- 登出/失效 -> `anonymous`
|
||||
|
||||
---
|
||||
|
||||
## 并发与切号安全
|
||||
|
||||
- 切号后,旧账号异步请求结果不得回写到新账号 UI 状态。
|
||||
- 推荐使用会话代际(epoch/token)保护异步回写。
|
||||
- 缓存分区与 UI 状态隔离必须同时存在:
|
||||
- 仅有分区,无代际保护:仍可能出现瞬时回流显示。
|
||||
- 仅有代际保护,无分区:仍可能读取到旧持久缓存。
|
||||
|
||||
---
|
||||
|
||||
## 兼容与迁移策略
|
||||
|
||||
### 向后兼容
|
||||
|
||||
- 旧无作用域键允许保留在本地存储中,不参与新读取路径。
|
||||
- 新版本只读取带 `cache:<scope>:` 前缀的键。
|
||||
|
||||
### 迁移方式
|
||||
|
||||
- 采用增量迁移(additive),不执行强制删除旧键。
|
||||
- 如需清理旧键,必须通过统一维护任务处理,不在功能逻辑中零散实现。
|
||||
|
||||
---
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. 同设备 A/B 账号来回切换,不出现跨账号历史/列表串读。
|
||||
2. 登录后首次读取命中当前用户作用域键;登出后读取命中匿名作用域键。
|
||||
3. Feature 仓库不再自行实现 userId 拼 key 逻辑。
|
||||
@@ -51,6 +51,7 @@ When creating/modifying/deprecating any code, this table must be updated in the
|
||||
|---|---|---:|---|
|
||||
| `AGENT_RUN_INPUT_INVALID` | agent | 422 | Run input payload invalid |
|
||||
| `AGENT_RUN_MESSAGES_INVALID` | agent | 422 | Run messages contract invalid |
|
||||
| `AGENT_INVALID_RUN_ID` | agent | 422 | SSE runId query invalid |
|
||||
| `AGENT_INVALID_LAST_EVENT_ID` | agent | 422 | SSE Last-Event-ID invalid |
|
||||
| `AGENT_SSE_CONNECTION_LIMIT` | agent | 429 | SSE connections exceed per-user limit |
|
||||
| `AGENT_ATTACHMENT_EMPTY` | agent | 422 | Attachment payload empty |
|
||||
@@ -114,6 +115,7 @@ When creating/modifying/deprecating any code, this table must be updated in the
|
||||
| `SCHEDULE_ITEM_PAGE_INVALID` | schedule_items | 400 | Pagination `page` must be greater than or equal to 1 |
|
||||
| `SCHEDULE_ITEM_PAGE_SIZE_INVALID` | schedule_items | 400 | Pagination `page_size` out of allowed range |
|
||||
| `SCHEDULE_ITEM_SHARE_FORBIDDEN` | schedule_items | 403 | Current user cannot share this schedule item |
|
||||
| `SCHEDULE_ITEM_FORBIDDEN` | schedule_items | 403 | Current user does not have permission to edit this schedule item |
|
||||
| `SCHEDULE_ITEM_SHARE_PERMISSION_EXCEEDED` | schedule_items | 403 | Requested share permission exceeds inviter permission |
|
||||
| `SCHEDULE_ITEM_SUBSCRIPTION_ALREADY_ACTIVE` | schedule_items | 400 | Recipient already has active subscription |
|
||||
| `SCHEDULE_ITEM_INVITE_ALREADY_SUBSCRIBED` | schedule_items | 400 | Recipient already accepted calendar invite |
|
||||
@@ -172,6 +174,7 @@ Exit code policy:
|
||||
|
||||
- `AGENT_RUN_INPUT_INVALID`
|
||||
- `AGENT_RUN_MESSAGES_INVALID`
|
||||
- `AGENT_INVALID_RUN_ID`
|
||||
- `AGENT_INVALID_LAST_EVENT_ID`
|
||||
- `AGENT_SSE_CONNECTION_LIMIT`
|
||||
- `AGENT_ATTACHMENT_EMPTY`
|
||||
|
||||
@@ -162,6 +162,12 @@ interface UiBadgeNode extends UiBaseNode {
|
||||
}
|
||||
```
|
||||
|
||||
`label` contract:
|
||||
- Backend SHOULD return stable i18n tokens for status badges: `ui.status.info|success|warning|error|pending`
|
||||
- Frontend is responsible for localizing these tokens by current locale
|
||||
- Backward compatibility: frontend SHOULD still tolerate legacy uppercase labels (`INFO/SUCCESS/...`) during migration
|
||||
- Unknown token fallback: frontend SHOULD keep original label (no semantic remap to other statuses)
|
||||
|
||||
#### 4. Button Node
|
||||
|
||||
```typescript
|
||||
@@ -455,7 +461,7 @@ interface UiIconSpec {
|
||||
},
|
||||
{
|
||||
"type": "badge",
|
||||
"label": "ERROR",
|
||||
"label": "ui.status.error",
|
||||
"status": "error"
|
||||
}
|
||||
]
|
||||
@@ -729,7 +735,7 @@ UiSchemaRenderer (for frontend rendering)
|
||||
"gap": 8,
|
||||
"children": [
|
||||
{"type": "text", "content": "日程已创建", "role": "title"},
|
||||
{"type": "badge", "label": "SUCCESS", "status": "success"}
|
||||
{"type": "badge", "label": "ui.status.success", "status": "success"}
|
||||
],
|
||||
"justify": "space-between",
|
||||
"align": "center"
|
||||
|
||||
Reference in New Issue
Block a user