Files
dsh_ai1net_server/交付物/活会话是什么-怎么往任意会话发消息-20260929.md
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

4.2 KiB
Raw Permalink Blame History

「活会话」是什么 · 怎么往任意会话发消息

2026-09-29 · 工作区 E:/ProgramData/AIProject/ai1net-dsh-server · 结论全部来自源码实读 + 真机实测


一、「活会话」到底是什么

它不是"正在运行的会话",而是「桌面当前打开的那一个会话」。

源码依据(WorkBuddy 网关 resolveLiveSessionFollow() / replySession()):

// 活会话 = 网关进程内存里的这个
let L = this.sessionManager.sessionSubject.value;
if (!L) return { sessionId: null, writerOccupied: false };
  • GET /api/v1/sessions/live 返回的 sessionId 就是它;为 null = 桌面当前没打开任何会话。
  • writerOccupied = 这个会话是否正被某个客户端持有写权(ACP 连接 / 桌面正在跑)。

为什么 reply 只认它? 上游给 reply 的定位就是"向当前活会话投递回复(不占用 ACP writer)" —— 即"手机跟随桌面正在聊的那一个",而不是"手机遥控任意会话"。所以:

目标 POST /sessions/{id}/reply
就是当前活会话 ✅ 200 {"delivered":true}
别的会话 ❌ 409 SESSION_FOLLOW_NOT_LIVE

二、怎么"往任意会话发消息"(已实现并实测通过)

用 ACP 借用:把一个会话切成活会话,发完再还回去。

connect → initialize
        → session/load {sessionId, cwd, mcpServers: []}   ← 把目标切成活会话
        → session/prompt {sessionId, prompt:[{type:"text",text}]}
        → (可选)session/load 回原活会话                   ← 归还

实测结果(对非活会话 b419f820-… 发一条):

步骤 结果
session/load 200,回放 404 事件,且 GET /sessions/live 切到了目标 ✅
session/prompt agent 真回复(agent_message_chunk 348 个)✅
目标会话 replay 出现 user_message_chunk,文本逐字一致 ✅

🔴 三条硬约束(⛔ 别踩)

  1. session/load / session/new 必须带 cwd 与 mcpServers —— 缺则 -32602 Invalid params。 cwd 取自 GET /api/v1/info(⚠️ 作用域列表 /api/v1/sessions 不返回 cwd)。
  2. 目标会话若已被外部 writer 占用 ⇒ 无法借用: -32000 "Persistent Session already has an external writer" {reason:"writer_occupied"} ⇒ 正被桌面打开/正在跑的会话,外部接管不了;空闲会话可以。 这正是「当前对话的主权在桌面」的体现。
  3. 只有与网关口同工作目录的会话可达。跨项目会话连 history/replay 都 404 SESSION_NOT_FOUND。 (本机实测:同目录 5 条可达;跨项目 19 条不可达。)

副作用(必须知道)

session/load 会把桌面的"当前对话"切到目标会话。所以实现里默认借用完归还; 但归还也可能失败(原活会话若被 writer 占用)—— 这时在桌面上点一下你要用的会话即可归位。


三、现在手机上的行为

你点的那条会话 手机发消息时
带绿色「活会话」标 走官方 reply —— 不切换桌面,立即投递
其他(同项目) 借用方式:短暂切桌面当前会话 → 发送 → 归还
标灰「不在当前项目」 ⛔ 不可发(网关读不到它)

页面「发送」已统一改走桥的 POST /__bridge/send {sessionId, text}, 它在内部自动选路(活会话→reply;否则→ACP 借用),默认异步(立刻返回,agent 回复稍后同步)。


四、⛔ 已知的运维限制(本轮踩到的)

沙箱会杀掉跑太久的前台命令,并连带杀掉桥。 实测 >45100 秒的前台命令会被终止,桥(后台任务)随之死掉 ⇒ 手机突然打不开。 ⇒ 对策:命令切短步、长任务走异步;桥需要"现场重开"(我这边一条命令即可重启,口令自动取自进程环境)。 ⚠️ 你自己开的 cmd 窗口拿不到口令(口令在 WorkBuddy 进程环境里),所以现在只能由我从 WorkBuddy 里拉起桥。 长期方案 = 把桥做成 WorkBuddy 侧常驻组件(即 棒 1)。