用户希望各项目的 Agent(Claude Code、Codex 等)能实时收到 SynapBus 的新消息,不靠人提醒、也不靠定时轮询,并且做成后续通用的能力。
现状(代码基线 opc/main @ 352f9f6,上游 0d4a953):
opc/main
352f9f6
0d4a953
GET /api/events
internal/api/sse_handler.go
internal/api/router.go:206
No session cookie
SSEHub
clients map[int64]...
SSEBroadcaster.OnMessageSent
internal/api/broadcaster.go
BroadcastDM
BroadcastChannelMessage
agents.RequiredAuthMiddlewareWithOAuth
internal/agents/middleware.go:134
cmd/synapbus/main.go:825
agents.AgentFromContext
新增一个按 Agent 过滤、用 Agent API key 鉴权的 SSE 事件流,作为通用推送基础。各客户端(Claude Code 后台监视、Codex wrapper、脚本、通知)都在它之上对接,SynapBus 核心不绑定任何特定客户端。
/api/events
GET /api/agent-events
Authorization: Bearer <agent key>
text/event-stream
connected
heartbeat
to_agent
new_message
message_id
channel
from_agent
subject
id:
Last-Event-ID: <message_id>
resync_required
goauto
#goauto
erpgo
#erpgo
Last-Event-ID
go build ./cmd/synapbus
go test
internal/api
internal/agents
internal/messaging
bin/synapbus.exe
main
feat/1-agent-sse
docs/
状态:方案待用户确认,确认前不写代码。
分支与提交:feat/1-agent-sse(基于上游 main 0d4a953,已推送)
2e8a49c
def4bb5
5da3a5d
92a2e03
实现由 Sonnet 子 agent 完成,Claude Code(Opus)审核了三轮。
cmd/synapbus/main.go
messaging.ListEventMetaAfter
encoding/json
%q
go vet
go test ./internal/api/ ./internal/agents/ ./internal/messaging/ ./internal/mcp/ -count=1
-count=3
-race
buildSearchConditions
to_agent = ''
bin/synapbus.exe.bak-20261005-pre-issue1
dev-local
CGO_ENABLED=0
-ldflags "-s -w -X main.version=opc-92a2e03"
/health
/api/agent-events
opc-92a2e03
goauto_codex
{"message_id":20,"from_agent":"goauto_codex","to_agent":"goauto","subject":"SSE 实时推送测试"}
Last-Event-ID: 19
停止服务,把 bin/synapbus.exe.bak-20261005-pre-issue1 改回 bin/synapbus.exe 后启动。本次没有数据库迁移,旧版本可以直接使用现有的 data/。
data/
Obsidian《SynapBus新项目接入手册-Claude-Code与Codex》已新增「实时订阅(/api/agent-events)」一节。
状态:待用户验收。
No dependencies set.
The note is not visible to the blocked user.
来源与背景(2026-10-05)
用户希望各项目的 Agent(Claude Code、Codex 等)能实时收到 SynapBus 的新消息,不靠人提醒、也不靠定时轮询,并且做成后续通用的能力。
现状(代码基线
opc/main@352f9f6,上游0d4a953):GET /api/events(internal/api/sse_handler.go,路由internal/api/router.go:206),但只认网页登录的会话 Cookie。用 Agent API key 实测返回 401No session cookie。SSEHub按「人类所有者 ownerID」分发(clients map[int64]...)。当前所有 Agent 都属于 admin,订阅一次就能看到全部 Agent 的消息事件。SSEBroadcaster.OnMessageSent(internal/api/broadcaster.go),私信走BroadcastDM,频道消息走BroadcastChannelMessage(已经会取频道成员)。agents.RequiredAuthMiddlewareWithOAuth(internal/agents/middleware.go:134,挂载于cmd/synapbus/main.go:825),认证后可用agents.AgentFromContext取得 Agent。目标
新增一个按 Agent 过滤、用 Agent API key 鉴权的 SSE 事件流,作为通用推送基础。各客户端(Claude Code 后台监视、Codex wrapper、脚本、通知)都在它之上对接,SynapBus 核心不绑定任何特定客户端。
非目标
/api/events的行为和鉴权。方案(待用户确认)
GET /api/agent-eventsagents.RequiredAuthMiddlewareWithOAuth,Authorization: Bearer <agent key>;未认证返回 401。text/event-stream;连接建立时推送connected(含 agent 名),之后每 30 秒推送一次heartbeat。SSEHub增加按 agent 名索引的订阅集合,与现有按 owner 的集合并存。to_agent。new_message,字段为message_id、channel(频道消息)或from_agent/to_agent(私信)、subject(有就带);SSE 的id:字段设为message_id。Last-Event-ID: <message_id>,服务端先补发该 Agent 可见、id 更大的消息事件(限制最多 N 条,比如 200 条,超出时推送resync_required),再进入实时推送。补发查询复用现有「我的消息」可见范围(私信 + 已加入的频道),不另写权限逻辑。验收标准
goauto的 key 订阅:给goauto发私信、在#goauto发频道消息,都在 1 秒内收到new_message,事件里没有正文。goauto的 key 收不到erpgo的私信事件,也收不到自己未加入的频道(如#erpgo)的事件;自己发的消息不推给自己。/api/events网页推送行为不变。Last-Event-ID重连后,补发断线期间的事件,不重复、不遗漏;超过上限时推送resync_required。测试
go build ./cmd/synapbus,以及受影响包的go test(internal/api、internal/agents、internal/messaging)。bin/synapbus.exe并重启 8182 上的正式服务,须等用户确认。风险与回退
bin/synapbus.exe,或在opc/main上 revert 合并提交。分支与上游
main拉feat/1-agent-sse,完成并验收后合入opc/main。实现时尽量贴近上游风格,以后可以向上游提 PR。文档影响
docs/或 README 的 OPC 段落)。状态:方案待用户确认,确认前不写代码。
实施完成,待验收(2026-10-05)
分支与提交:
feat/1-agent-sse(基于上游main0d4a953,已推送)2e8a49cfeat(#1): add agent-key authenticated SSE stream /api/agent-eventsdef4bb5fix(#1): keep out-of-order live agent events, add store tests5da3a5dfix(#1): encode agent-events error bodies with encoding/json92a2e03merge(--no-ff) 合入opc/main,已推送。尚未替换正在运行的 8182 服务。实现由 Sonnet 子 agent 完成,Claude Code(Opus)审核了三轮。
实现要点
GET /api/agent-events,挂在cmd/synapbus/main.go,与 MCP 共用agents.RequiredAuthMiddlewareWithOAuth(Agent API key)。网页使用的/api/events行为不变。SSEHub新增按 agent 名索引的订阅集合:私信只推to_agent;频道消息推给发送时的频道成员;不推给发送者本人。事件只带message_id、channel或from_agent/to_agent、subject,不带正文;id:行等于 message_id。Last-Event-ID补发 id 更大的可见消息,最多 200 条,超出时只发resync_required。补发可见范围由messaging.ListEventMetaAfter决定(发给自己的私信 + 当前所在频道的消息,不含自己发的)。注意:补发按当前成员资格判断,加入频道前的消息也会补发(频道成员本来就能读历史)。encoding/json编码。审核发现并已修正
%q插值产生了未转义的引号)→ 改用encoding/json编码,并在测试里解析验证。验证(审核人亲自执行)
go vet:无报告。go test ./internal/api/ ./internal/agents/ ./internal/messaging/ ./internal/mcp/ -count=1:全部 ok。新测试-count=3稳定通过。合并后的opc/main编译并测试通过。/api/events无会话仍返回 401。-race(本机 ThreadSanitizer 无法启动),并发正确性依赖加锁设计、重复测试和端到端联调。待办
opc/main编译替换bin/synapbus.exe并重启 8182(所有 agent 会短暂断线后重连)。buildSearchConditions用to_agent = ''匹配频道消息,但频道消息的to_agent存的是 NULL,搜索可能漏掉频道消息。这是上游的潜在缺陷,不在本单范围,建议另行建单。已上线到本机正式服务(2026-10-05)
bin/synapbus.exe.bak-20261005-pre-issue1(10:32 编译,版本dev-local)。opc/main92a2e03编译新二进制(CGO_ENABLED=0、-ldflags "-s -w -X main.version=opc-92a2e03"),替换bin/synapbus.exe。未重新构建前端,沿用本地已构建的资源。/health正常,/api/agent-events不带 key 返回 401,网页首页返回 200;临时实例已清理。/health显示版本opc-92a2e03。正式服务实测
goauto的 key 订阅/api/agent-events,再由goauto_codex给goauto发一条测试私信(#20):订阅端约 0.4 秒内收到new_message,内容为{"message_id":20,"from_agent":"goauto_codex","to_agent":"goauto","subject":"SSE 实时推送测试"},正文在事件流中出现 0 次。Last-Event-ID: 19重连,正确补发 #20。回退方式
停止服务,把
bin/synapbus.exe.bak-20261005-pre-issue1改回bin/synapbus.exe后启动。本次没有数据库迁移,旧版本可以直接使用现有的data/。文档
Obsidian《SynapBus新项目接入手册-Claude-Code与Codex》已新增「实时订阅(/api/agent-events)」一节。
状态:待用户验收。