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

89 lines
4.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 「活会话」是什么 · 怎么往任意会话发消息
> 2026-09-29 · 工作区 `E:/ProgramData/AIProject/ai1net-dsh-server` · 结论全部来自**源码实读 + 真机实测**
---
## 一、「活会话」到底是什么
**它不是"正在运行的会话",而是「桌面当前打开的那一个会话」。**
源码依据(WorkBuddy 网关 `resolveLiveSessionFollow()` / `replySession()`):
```js
// 活会话 = 网关进程内存里的这个
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 回复稍后同步)。
---
## 四、⛔ 已知的运维限制(本轮踩到的)
**沙箱会杀掉跑太久的前台命令,并连带杀掉桥。**
实测 >~45~100 秒的前台命令会被终止,桥(后台任务)随之死掉 ⇒ 手机突然打不开。
⇒ 对策:命令切短步、长任务走异步;**桥需要"现场重开"**(我这边一条命令即可重启,口令自动取自进程环境)。
⚠️ 你自己开的 cmd 窗口**拿不到口令**(口令在 WorkBuddy 进程环境里),所以现在只能由我从 WorkBuddy 里拉起桥。
长期方案 = 把桥做成 **WorkBuddy 侧常驻组件**(即 棒 1)。