会话生命周期与客户端身份
区分 create、attach、load、resume、detach 和会话终止。
This page has not been translated into English yet. The original Chinese version is shown below.
一个 daemon session 是绑定 ACP sessionId 的逻辑对话。client 是参与它的控制端;SSE subscriber 又只是某次事件订阅。三者生命周期不同,不能用“断开连接”统称所有关闭。
创建、附加与恢复
| 操作 | 含义 |
|---|---|
| POST /session,single scope | 已有 default session 时附加,否则创建 |
| POST /session,thread scope | 创建独立对话,仍受 maxSessions 约束 |
| POST /session/:id/load | 恢复并返回有界 replay snapshot |
| POST /session/:id/resume | 恢复但不请求同样的历史 replay |
| POST /session/:id/detach | 解除一个 client 的附着,返回 204;本身不是持久删除 |
恢复使用 pendingRestoreIds 避免同 ID 并发重复进行,并缓存 restoreState 供稍后附加者。session_resume 是稳定 daemon capability,ACP 内部方法仍可叫 unstable_resumeSession;客户端不应继续只检测旧别名。
Client ID 的选择
X-Qwen-Client-Id 由客户端自行选择,daemon 不代为生成。格式为 [A-Za-z0-9._:-]{1,128}。不同 controller 应使用各自稳定 ID;宿主与嵌入 Web Shell 只有确实作为同一逻辑控制端时才共用。
共用 ID 后,日志无法区分是谁发起请求。它也不是登录身份或 proof-of-possession;审批边界见多客户端审批。
关联事件可在 envelope 中带 originatorClientId。对自己的用户回显或 mid_turn_message_injected 去重前,必须比较实际 originator,不能因为自己也订阅该 session 就丢掉所有用户消息。
心跳与元数据
POST /session/:id/heartbeat 更新 sessionLastSeenAt;携带已登记 ID 时也更新对应 clientLastSeenAt。当前心跳提供可见性,不实施 v1 单客户端撤销/驱逐策略。
PATCH /session/:id/metadata 更新 displayName,长度最多 256,拒绝 U+0000–U+001F 和 U+007F 控制字符。成功后广播 session_metadata_updated。
终止信号
| 事件 | 范围 |
|---|---|
| session_closed | session 主动关闭或确认的 workspace runtime stop |
| session_died | child 异常退出、其他 kill 路径或 daemon shutdown |
| client_evicted | 仅当前慢 subscriber 被关闭,session 可继续 |
| stream_error | 当前订阅建立或传输失败 |
确认的 workspace stop 若仍有持久化不确定,会在 session_closed 带 persistenceUnconfirmed:true;客户端不能把所有 closed 都解释为历史已可靠落盘。
创建者断线不误杀附加者
创建请求因 TCP reset 无法交付时,route 可要求 requireZeroAttaches 清理新 session。如果其他客户端已经附加,则先保留 session,并记录 spawnOwnerWantedKill;后续 detach 使附着数归零时再完成延后清理。
同理,detach 与 subscriber 关闭的最终回收要结合剩余引用判断,不应把本客户端离开当作所有参与者都结束。持久归档和删除另见会话存储状态。