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/ 知识文件,按口径入库)
This commit is contained in:
admin committed 2026-10-10 23:13:22 +08:00
1 parent 30b46dbd0c
commit c1b5e4d966
735 files changed
+153192 -2415

No files matched your search

@@ -0,0 +1,88 @@
# 「活会话」是什么 · 怎么往任意会话发消息
> 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)。