跳转到主要内容

概述

Daemon WebSocket API 为 Daemon 客户端提供持久的双向连接,用于实时接收和处理消息。与 REST API 的轮询模式不同,WebSocket 连接允许 Shannon 将传入消息(来自 Slack、LINE 或系统事件)直接推送到已连接的 Daemon。 该协议的核心是基于 Claim 的消息分发模型:Shannon 将消息广播到所有符合条件的连接,Daemon 竞争获取独占处理权后再进行回复。

Endpoint

GET /v1/ws/messages
使用标准认证 Header 升级为 WebSocket 连接。

认证

认证在 WebSocket 升级之前执行,使用与 REST Endpoint 相同的中间件。
方式Header
JWT BearerAuthorization: Bearer <token>
API KeyX-API-Key: <key>
websocat "ws://localhost:8080/v1/ws/messages" \
  -H "Authorization: Bearer <token>"

连接生命周期

1

HTTP 升级

客户端发送 GET /v1/ws/messages 并携带认证 Header。服务端验证凭据后进行协议升级。
2

WebSocket 建立

服务端升级为 WebSocket 连接(gorilla/websocket,4KB 读写缓冲区,CheckOrigin 允许所有来源)。
3

连接确认

服务端发送 connected 消息确认连接就绪。
{"type": "connected"}
4

双向消息通信

双方交换 JSON 消息。服务端分发传入消息;客户端执行 Claim、处理并回复。
5

心跳保活

服务端每 20 秒发送 WebSocket Ping。客户端必须在 60 秒内响应 Pong,否则连接将被关闭。

连接参数

参数
Ping 间隔20s
Pong 超时60s
最大消息大小64 KB
写入超时10s
读写缓冲区4 KB

消息信封

所有消息(双向)遵循统一的信封格式:
{
  "type": "<message_type>",
  "message_id": "<uuid>",
  "payload": {}
}
字段类型描述
typestring消息类型标识符
message_idstring (UUID)唯一消息标识符(connecteddisconnect 消息中省略)
payloadobject特定类型的数据

服务端到客户端消息

connected

WebSocket 连接建立后立即发送。
{
  "type": "connected"
}

message

分发给客户端处理的入站消息。这是主要的消息类型,携带来自 Channel Webhook(Slack、LINE)或系统事件的消息。
{
  "type": "message",
  "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "payload": {
    "channel": "slack",
    "thread_id": "C07ABCDEF-1234567890.123456",
    "sender": "user@example.com",
    "text": "Hello, can you help me?",
    "agent_name": "research-agent",
    "timestamp": "2026-03-10T10:00:00Z"
  }
}

MessagePayload 字段

字段类型描述
channelstring来源 Channel 类型:"slack""line"
thread_idstring会话线程标识符
senderstring发送者标识(邮箱、用户 ID 等)
textstring消息内容
agent_namestring处理消息的目标 Agent
timestampstring (ISO 8601)消息接收时间

system

来自 Shannon 的系统级通知。
{
  "type": "system",
  "message_id": "f7e8d9c0-b1a2-3456-7890-abcdef123456",
  "payload": {
    "text": "Agent research-agent is now available"
  }
}

claim_ack

对客户端 claim 请求的响应,指示 Claim 是否成功。
{
  "type": "claim_ack",
  "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "payload": {
    "granted": true
  }
}
字段类型描述
grantedbooleantrue 表示该客户端获得了独占处理权

客户端到服务端消息

claim

请求独占处理某条消息。同一消息只有一个客户端能成功 Claim。
{
  "type": "claim",
  "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

progress

在处理已 Claim 的消息时发送心跳/进度更新。这会延长 Claim 的有效期,防止超时。
{
  "type": "progress",
  "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "payload": {
    "status": "processing",
    "percent": 50
  }
}

reply

发送已 Claim 消息的处理结果。Shannon 会将其路由回来源 Channel(Slack、LINE 等)。
{
  "type": "reply",
  "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "payload": {
    "channel": "slack",
    "thread_id": "C07ABCDEF-1234567890.123456",
    "text": "Here is my response...",
    "format": "text"
  }
}

ReplyPayload 字段

字段类型描述
channelstring目标 Channel 类型
thread_idstring回复的会话线程
textstring响应内容
formatstring输出格式:"text""markdown"

disconnect

优雅地关闭连接。
{
  "type": "disconnect"
}

Claim 流程

Claim 流程是分布式消息处理的核心协议。它确保即使多个 Daemon 同时连接,每条消息也只由一个 Daemon 处理。
1

消息分发

当消息到达时(通过 Channel Webhook 或系统),Gateway 将其分发给按 tenant:user 索引的所有符合条件的 WebSocket 连接。
2

Claim 竞争

每个想要处理该消息的 Daemon 发送包含 message_idclaim 请求。
3

原子决议

Gateway 在 Redis 中原子地执行 Claim(SETNX)。第一个客户端获胜;其他客户端收到 {"granted": false}
4

消息处理

获胜的 Daemon 处理消息。可以选择发送 progress 消息来延长 Claim 有效期并报告处理进度。
5

发送回复

Daemon 发送包含处理结果的 reply。Shannon 将其路由回来源 Channel。

Claim 元数据

当消息被 Claim 时,Gateway 在 Redis 中存储元数据,TTL 为 60 秒
字段描述
conn_idWebSocket 连接标识符
channel_id来源 Channel ID
channel_typeChannel 类型(slackline 等)
thread_id会话线程 ID
reply_token平台特定的回复 Token(如适用)
timestampClaim 时间戳
workflow_id关联的 Temporal Workflow ID(如适用)
workflow_run_id关联的 Temporal Workflow Run ID(如适用)
待处理消息元数据的 TTL 为 90 秒。如果已 Claim 的消息在 60 秒内未回复,Claim 将过期,消息可被重新分发。

Hub 架构

WebSocket Hub 管理所有活跃连接,采用以下路由策略:
  • Tenant-User 索引 — 连接按 "tenant:user" 键索引,实现定向分发
  • 线程粘性路由 — 来自同一线程("channel_type:thread_id")的消息尽可能路由到同一连接
  • Redis 支持的 Claim — 分布式 Claim 决议确保多个 Gateway 实例间的一致性

回复路由

当 Gateway 收到 Daemon 的 reply 时,根据 Claim 元数据路由响应:
  1. Workflow 回复 — 如果 Claim 元数据中存在 workflow_id,Gateway 会通过 Signal 通知关联的 Temporal Workflow
  2. Channel 回复 — 否则,回复被路由回来源 Channel(Slack 消息、LINE Push 消息等)

错误处理

场景行为
升级时认证失败返回 HTTP 401,WebSocket 未建立
消息超过 64 KB连接关闭
Pong 超时(60s)服务端关闭连接
写入超时(10s)消息丢弃,连接可能关闭
Claim 过期(60s TTL)消息可被重新分发
无效 JSON消息被忽略

下一步