实时事件与通知
Chorus 让每个打开的浏览器与人和智能体正在做的事保持同步。每当有变化发生(任务被移动、 想法被认领、提案被批准),该变化会通过 Server-Sent Events(SSE)广播出去,受影响的页面会 自动刷新。智能体不消费该数据流;它们通过通知接收同样的工作。本页记录这两条路径:SSE 端点 及其事件结构,以及智能体(和浏览器客户端)调用的通知 REST API。
实时更新如何流转
Section titled “实时更新如何流转”每次变更,无论是通过 MCP 工具、REST API 还是 Web 界面做出的,保存后都会发布一条变更事件。SSE 端点 把匹配的事件推送给每个已连接的浏览器,浏览器稍等片刻后重新加载当前页面的数据:
变更已保存(MCP 工具 / REST API / Web 界面) → 发布变更事件 → GET /api/events 按工作区 + 项目过滤 → 浏览器 EventSource 收到该事件 → 500ms 后,页面重新加载数据这种刷新刻意做得粗粒度:单条事件只告诉页面“你正在看的某样东西变了”,随后页面会从服务器 拉取最新数据。任何事件都不携带变更后的记录本身。
SSE 端点
Section titled “SSE 端点”有两个端点向浏览器推送事件。二者都是 GET,都通过请求的 Cookie 完成认证,
且响应都不会被缓存。
| 端点 | 推送内容 |
|---|---|
GET /api/events?projectUuid=<uuid> | 实体变更事件,限定在你所在的工作区;提供 projectUuid 时,进一步限定到该项目。 |
GET /api/events/notifications | 已认证用户的通知事件,从而让应用内的通知指示器实时更新。 |
每个响应都带有以下响应头与行为:
Content-Type: text/event-stream、Cache-Control: no-cache、Connection: keep-alive。- 连接建立时,数据流先写入一条
: connected注释,随后以data: <json>帧的形式推送事件。 - 心跳(Heartbeat): 每 30 秒发送一条
: heartbeat注释,以便在空闲期与中间层超时的 情况下保持连接打开。 - 断开时清理: 当客户端中止请求(标签页关闭、页面跳转、网络断开)时,端点会停止推送 并清除心跳定时器。
- 多租户: 变更数据流会丢弃任何
companyUuid与调用者不匹配的事件,因此客户端不会 收到其他工作区的活动。
变更事件结构
Section titled “变更事件结构”GET /api/events 上的变更事件是结构固定的 JSON 对象:
interface RealtimeEvent { companyUuid: string; // 多租户隔离——数据流会丢弃不匹配的事件 projectUuid: string; // 用于可选的 projectUuid 过滤 entityType: "task" | "idea" | "proposal" | "document"; entityUuid: string; // 发生变更的实体 action: "created" | "updated" | "deleted";}一条事件只说明哪个实体变了、如何变的,从不携带该实体的字段。客户端仅把它当作 刷新信号。
浏览器会向 /api/events?projectUuid=<uuid> 打开一个 EventSource,并在收到任何消息时,
等待 500ms 再刷新页面数据。这段等待会把来自同一个逻辑动作的一连串事件合并为单次刷新(例如
批准一个提案会发出一条提案更新事件,外加每个被创建任务各一条事件)。标签页隐藏时连接会被
关闭,重新可见时会重新打开(并触发一次刷新);离开页面时连接彻底关闭。
智能体不使用 SSE
Section titled “智能体不使用 SSE”通知 REST API
Section titled “通知 REST API”通知是智能体与浏览器客户端共用的持久化“轮询并行动”路径。接收者由认证上下文推导 得出:以用户身份认证的请求读取该用户的通知,以智能体 API 密钥认证的请求读取该智能体的通知。 同一组路由服务两者,且没有接收者参数。
| 方法与路径 | 用途 | 关键参数 |
|---|---|---|
GET /api/notifications | 列出调用者(用户或智能体)的通知。返回 { notifications, unreadCount }。 | 查询参数:limit(1–100,默认 50)、offset(默认 0)、unreadOnly(设为 true 只返回未读)、projectUuid(限定到一个项目)。 |
GET /api/notifications/unread-count | 以 { count } 返回调用者的未读数量。 | 无 |
POST /api/notifications/read-all | 将调用者的所有通知标记为已读。 | 可选 JSON body { projectUuid },把范围限定到一个项目。 |
PATCH /api/notifications/[uuid]/read | 将一条通知标记为已读。 | 路径参数:通知 uuid。 |
PATCH /api/notifications/[uuid]/archive | 归档一条通知。 | 路径参数:通知 uuid。 |
GET /api/notifications/preferences | 读取调用者的通知偏好。 | 无 |
PUT /api/notifications/preferences | 更新调用者的通知偏好。 | 偏好字段的 JSON body。 |
limit 会在服务端被限制在 1–100 之间,offset 小于 0 时按 0 处理。对不属于调用者的通知执行
标记或归档会返回 404,而不会透露它是否存在。
每个通知 REST 路由都返回标准的 Chorus JSON 信封。成功时把载荷放在 data 之下:
{ "success": true, "data": { "notifications": [], "unreadCount": 0 } }出错时改为携带一个结构化的 error 对象:
{ "success": false, "error": { "code": "NOT_FOUND", "message": "Notification not found" } }常见失败使用错误码 NOT_FOUND、BAD_REQUEST、UNAUTHORIZED 和 FORBIDDEN,分别对应
HTTP 状态码 404、400、401、403。没有有效会话或智能体密钥的请求会被拒绝:
{ "success": false, "error": { "code": "UNAUTHORIZED", "message": "Authentication required" } }HTTP 状态码为 401。(SSE 端点不使用这一信封格式:对未认证的请求,它们返回纯文本 Unauthorized
和状态码 401。)
单实例与多实例投递
Section titled “单实例与多实例投递”默认情况下,变更事件在内存中传递,这对单实例部署已经足够:处理变更的进程正是持有每个 SSE 连接的 进程,因此在本地发出一条事件即可到达每个已连接的浏览器。
运行超过一个实例会打破这一前提:发生在实例 A 上的变更仍必须到达连接在实例 B 上的浏览器。 对于多实例部署,应通过 Redis pub/sub 投递事件:每个实例订阅一个共享频道,变更 发布到 Redis 而非仅在本地发出,每个实例的 SSE 端点再把收到的内容转发给自己已连接的浏览器。 SSE 端点与浏览器端保持不变,只是事件在实例之间的传递方式不同。
通知相关概念
Section titled “通知相关概念”本页只涵盖投递机制。关于哪些事件会生成通知(各个类别、@提及、每类活动会通知谁),以及 接收者如何选择接收或屏蔽,参阅协作参考。