AUN Mac App(Swift)与后端 Python daemon 之间的通信协议。
- 传输:daemon 以子进程方式启动,通过
stdin(请求)与stdout(响应 + 事件)通信 - 编码:UTF-8 NDJSON —— 每行一个 JSON 对象,以
\n结束 - 日志:daemon 运行时日志写
stderr,禁止混入 stdout - 阻塞:daemon 内部用 asyncio,不会阻塞 stdin;前端可并发发请求
python3 /path/to/aun_daemon.py [--data-dir /path/to/.aun]环境变量(可选):
AUN_CLI_DATA— 数据根目录(默认~/.aun)。与--data-dir等效,命令行优先
{"id": 1, "method": "initialize", "params": {}}id由前端递增,必须为整数params可省略
成功:
{"id": 1, "result": {"aid": "alice.agentid.pub", "target": null}}失败:
{"id": 1, "error": {"code": "NO_AID", "message": "No AID configured", "recoverable": true}}{"event": "message_received", "data": {"from": "bob.agentid.pub", "text": "hi", "ts": 1715000000000}}建立 AUNClient 连接、认证、订阅事件。Swift 端在 daemon 启动后必须先发这个。
Request
{"id": 1, "method": "initialize", "params": {"aid": "alice.agentid.pub"}}params.aid(可选):若省略则读~/.aun/aun-cli/config.json里的aid。两者都没有则返回NO_AID错误
Response
{"id": 1, "result": {
"aid": "alice.agentid.pub",
"target": {"type": "peer", "id": "bob.agentid.pub", "name": "bob"},
"recent_targets": [
{"type": "peer", "id": "bob.agentid.pub", "name": "bob"}
],
"gateway": "wss://gateway.agentid.pub:20001/aun",
"sdk_version": "0.2.12"
}行为
- 加载 AID → 认证 → 连接网关 → 注册
message.received等回调 - 成功后 daemon 会额外发一个
ready事件(见下) connect完成前返回失败则发error事件 + error 响应
切换当前对话目标。
Request
{"id": 2, "method": "set_target", "params": {"aid": "carol.agentid.pub"}}params.aid:peer AID- MVP 不支持 group target
Response
{"id": 2, "result": {"target": {"type": "peer", "id": "carol.agentid.pub", "name": "carol"}}}错误码
INVALID_AID:AID 格式不合法
向当前 target 发送文本消息。
Request
{"id": 3, "method": "send_text", "params": {"text": "Hello", "encrypt": true}}params.text(必选):消息正文params.encrypt(可选,默认true):是否 E2EE
Response
{"id": 3, "result": {
"message_id": "msg_abc",
"target": {"type": "peer", "id": "carol.agentid.pub", "name": "carol"},
"ts": 1715000000000
}}错误码
NO_TARGET:未设置 targetNOT_CONNECTED:网关断开且重连失败SEND_FAILED:SDK 返回错误(data字段含原始错误)
读历史消息(DB 里拉)。
Request
{"id": 4, "method": "list_messages", "params": {
"target_id": "carol.agentid.pub",
"limit": 50,
"before_id": null
}}target_id(必选):peer AID 或 group idlimit(可选,默认 50,最大 200)before_id(可选):分页游标。返回消息里最小的id传入即可取更早的一批
Response
{"id": 4, "result": {
"messages": [
{
"id": 101,
"message_id": "msg_abc",
"conversation_id": "carol.agentid.pub",
"conversation_type": "peer",
"direction": "sent",
"sender": "alice.agentid.pub",
"text": "Hello",
"seq": null,
"ts": 1715000000000,
"is_read": 1
}
]
}}消息按 ts 升序返回。
最近会话列表。
Request
{"id": 5, "method": "list_recent_targets"}Response
{"id": 5, "result": {
"targets": [
{"type": "peer", "id": "bob.agentid.pub", "name": "bob"},
{"type": "peer", "id": "carol.agentid.pub", "name": "carol"}
]
}}快照式查询连接状态。
Request
{"id": 6, "method": "get_status"}Response
{"id": 6, "result": {
"connected": true,
"aid": "alice.agentid.pub",
"target": {"type": "peer", "id": "carol.agentid.pub", "name": "carol"},
"gateway": "wss://gateway.agentid.pub:20001/aun"
}}优雅关闭 daemon。Response 返回后 daemon 会断开网关并退出进程。
Request
{"id": 7, "method": "shutdown"}Response
{"id": 7, "result": {"ok": true}}Swift 端收到响应后应等待进程真正退出(process.waitUntilExit())。
initialize 完成后触发,前端应在收到这个事件后才允许进入正常使用状态。
{"event": "ready", "data": {
"aid": "alice.agentid.pub",
"target": {"type": "peer", "id": "carol.agentid.pub", "name": "carol"},
"gateway": "wss://gateway.agentid.pub:20001/aun"
}}收到对端消息。
{"event": "message_received", "data": {
"message_id": "msg_xyz",
"from": "carol.agentid.pub",
"conversation_id": "carol.agentid.pub",
"conversation_type": "peer",
"text": "Hi there",
"seq": 42,
"ts": 1715000000000,
"e2ee": true
}}备注:
- 群消息 MVP 不发此事件(后续版本再加)
text是渲染友好的文本。结构化 payload(文件附件等)在 v0.2 通过独立事件/字段扩展
本端发送成功后推送(即使 send_text 响应返回也会发,便于多客户端同步)。
{"event": "message_sent", "data": {
"message_id": "msg_abc",
"target": {"type": "peer", "id": "carol.agentid.pub", "name": "carol"},
"text": "Hello",
"ts": 1715000000000
}}{"event": "connection_state", "data": {
"state": "connected",
"reason": null
}}state 取值:connecting | connected | disconnected | reconnecting
Daemon 层面的异步错误(非某次请求的响应)。
{"event": "error", "data": {
"code": "GATEWAY_DISCONNECTED",
"message": "WebSocket closed unexpectedly",
"recoverable": true
}}| code | 含义 | recoverable |
|---|---|---|
NO_AID |
未配置 AID,且 initialize 未传 |
false |
AID_NOT_FOUND |
AID 本地密钥库中不存在 | false |
AUTH_FAILED |
网关认证失败 | true |
GATEWAY_DISCONNECTED |
已连接的网关掉线 | true |
NOT_CONNECTED |
当前未连接到网关 | true |
NO_TARGET |
操作前未 set_target |
false |
INVALID_AID |
AID 格式不合法 | false |
SEND_FAILED |
SDK 发送失败(data 里有原始 error) |
视情况 |
INVALID_PARAMS |
请求参数 schema 不符 | false |
INTERNAL_ERROR |
daemon 内部异常 | false |
UNKNOWN_METHOD |
未识别的 method | false |
JSON-RPC 风格错误对象:
{"code": "SEND_FAILED", "message": "...", "recoverable": true, "data": {...}}Swift Python daemon
│ │
│── spawn process ────────>│
│ │
│ │ (import, setup, no output yet)
│ │
│── {"id":1,"method": │
│ "initialize",...} ───>│
│ │── 加载 AID
│ │── authenticate
│ │── connect gateway
│ │── 订阅事件
│ │
│<── {"id":1,"result":...} │
│<── {"event":"ready",...} │
│<── {"event":"connection │
│ _state","data": │
│ {"state":"connected" │
│ }} │ │
│ │
│── 正常使用期 ────────────│
│ │
│── {"id":99,"method": │
│ "shutdown"} ─────────>│
│<── {"id":99,"result":...}│
│ │── 断开网关,清理,exit(0)
│<── (stdout closed) ──────│
- 行解析:读取 stdout 按
\n分行;每行解析为 JSON,event字段存在则分发到事件流,否则按id匹配 pending request - 请求超时:建议 30s,超时则标记该
id失败,但不要杀进程(daemon 可能还在处理) - 进程崩溃:若 stdout EOF 或 process 退出码 ≠ 0,UI 显示"连接丢失"并提示重启 daemon
- 启动等待:spawn 后最多等 15s 收到
ready事件;超时则判定启动失败并 kill -9 - 关闭顺序:先发
shutdown→ 等 response →process.waitUntilExit(超过 5s 再 kill) - stderr:默认重定向到日志文件,不要丢,排查问题靠它
为了保持 v0.1 协议简洁,以下内容暂未实现,但已为扩展预留空间:
| 方向 | 方法/事件 |
|---|---|
| 群组 | list_groups, create_group, join_group, leave_group, list_group_members, group_event |
| 文件 | send_file, list_files, download_file, file_received |
| 身份 | list_aids, create_aid, switch_aid, delete_aid |
| 富文本 | 消息 payload 扩展为 {type, text, attachments, mentions} |
| @提及 | list_group_members + payload.mentions |
| 通知 | peer_presence, typing, read_receipt |
| 诊断 | set_debug, get_logs, log 事件 |
扩展原则:
- 新增 method / event 不影响旧字段
- 请求参数新增字段必须可选
- 响应新增字段必须可选
- 废弃字段保留至少两个小版本