# 架构（ARCHITECTURE.md）

> 持久对话 MCP 插件的完整技术架构

---

## 1. 整体拓扑

```
┌────────────────────┐
│   Trae / Cursor    │  IDE（SOLO Agent 窗口）
│   (MCP client)     │
└─────────┬──────────┘
          │ stdio JSON-RPC
          │ 4 tools: init / select / wait / merge
          ▼
┌────────────────────┐
│  ensure-hub.sh     │  stdio → HTTP 桥接
│  (MCP server)      │
└─────────┬──────────┘
          │ HTTP (13458)
          ▼
┌────────────────────┐
│   hub.js (主)      │  ┌─ 远端: 192.168.110.101:13458
│   Node 22 + HTTP   │  └─ 本地: 127.0.0.1:13458
└─────────┬──────────┘
          │ 短轮询（10s）
          ▼
┌────────────────────┐
│   sessions.json    │  持久化层
│   bindings.json    │  - sessions: ct_xxx → 状态
│   plan-bindings    │  - bindings: workspace::title@ip → ct_
│   thread-bindings/ │  - plan: planName → ct_
└────────────────────┘  - thread: threadId → ct_

        ↓ 周期同步（可选）
┌────────────────────┐
│   MongoDB          │  远端数据库
│   karuo_site       │  (port 27017)
└────────────────────┘
```

---

## 2. 核心数据流

### 2.1 初始化

```
Agent → init_conversation(cursorTitle=标签, newPlan=true, planLabel=xxx)
  → ensure-hub.sh → POST /api/init
  → hub.js 创建 session（ct_xxx）
  → 写入 sessions.json
  → 写 binding：workspace::title@ip → ct_xxx
  → 返回 { token: ct_xxx, status: holding }
```

### 2.2 选择已有

```
Agent → select_conversation(token=ct_xxx, cursorTitle=标签)
  → ensure-hub.sh → POST /api/select
  → hub.js 验证垂直绑定（workspace::title@ip 三件指纹）
  → 不匹配 → 403 BINDING_REJECTED
  → 匹配 → 复用 session
```

### 2.3 真挂起

```
Agent → wait_for_user_input(token, message, timeout)
  → ensure-hub.sh → POST /api/wait/register
  → hub.js 设置 session.status = "waiting"
  → 注册到 pendingWaits[token]
  → 长连接（短轮询 10s 续约）
  → 收到 reply：
       a. 用户发消息 → deliverReply → 返回
       b. timedReply 触发（10s 间隔）→ deliverReply("保活续跑") → 返回
       c. POLL_TIMEOUT_MS=10s → renewal → 继续轮询
  → Agent 端按 status 决定：
       - "replied" → 返回 text
       - "renewal" → 继续 poll
       - "gone" → 重新 register
```

### 2.4 合并

```
Agent → merge_conversation(fromToken, toToken, cursorTitle)
  → ensure-hub.sh → POST /api/merge
  → hub.js 把 fromToken 的 messages 追加到 toToken
  → fromToken 标记 archived
  → 增量保存 sessions.json
```

---

## 3. 垂直绑定

### 3.1 绑定指纹

```
bindingKey = `${workspace}::${cursorTitle}@${hostIp}`
```

示例：
```
/Users/karuo/Documents/个人::持久对话永久续跑-20260626@192.168.3.1
```

### 3.2 验证时机

| 触发 | 验证 |
|:---|:---|
| `select_conversation` | 严校验（必须完全匹配） |
| `init_conversation` | 宽校验（可换 title 重新绑定） |
| `wait_for_user_input` | 中校验（容许 title 改名） |
| `merge_conversation` | 严校验（不匹配拒绝） |

### 3.3 改标签自动迁移

```javascript
// 用户在 Trae 改了标签 "A" → "B"
// wait 时传入新 title "B"
// hub.js 检测到旧 binding 有 token，旧 bindingKey 不匹配
// 允许迁移：把 bindingKey 改成新值，token 复用
```

---

## 4. session 状态机

```
   init
    │
    ▼
  holding ──────┐
    │            │ (每 10s 续约 / 接收新消息)
    │            │
    ▼            │
  waiting ───────┤
    │            │
    │ (用户发消息)│
    ▼            │
  replied ───────┘
    │
    │ (autoStreak 累加)
    ▼
  running
    │
    │ (merge / 结束持久对话)
    ▼
  merged / archived
```

---

## 5. autoStreak 与 keepAlive

### 5.1 概念

- `autoStreak`：连续"用户不发消息"的轮次计数
- `keepAlive`：Hub 在 autoStreak 超阈值时主动推"保活续跑"消息

### 5.2 真挂起模式（推荐）

```json
POST /api/session/config
{
  "sessionAutoContinue": false,
  "timedReplyEnabled": false,
  "timedReplyIntervalMs": 0
}
```

**效果**：
- 关闭 timedReply → 不会主动推"保活续跑"
- 关闭 autoContinue → 不会因为 idle 触发续跑
- wait 唯一返回源 = 用户真消息
- MCP 工具调用一直阻塞，agent turn 不会结束

### 5.3 自动模式（不推荐，会顶回 wait）

```json
{
  "sessionAutoContinue": true,
  "timedReplyEnabled": true,
  "timedReplyIntervalMs": 10000
}
```

**效果**：
- 每 10s 推"保活续跑"
- wait 立即返回
- agent turn 结束（UI 显示"任务完成"）

---

## 6. 短轮询 vs 长连接

| 方案 | 优点 | 缺点 | 采用 |
|:---|:---|:---|:---|
| 短轮询（10s） | 简单、跨网段友好、稳 | 续约频繁 | ✅ |
| WebSocket | 实时、省资源 | 需配 wss、断线重连复杂 | ❌ |
| Server-Sent Events | 单向实时 | 不支持双向 | ❌ |
| Long Polling | 接近实时 | 服务器维护成本 | ❌（短轮询已够） |

短轮询间隔：`POLL_TIMEOUT_MS = 10000`（10s），可在 `hub.js` 改。

---

## 7. 数据持久化

### 7.1 文件布局

```
~/.persistent-chat-local/
├── sessions.json              # 主 session 数据
├── bindings.json              # workspace::title@ip → ct_
├── plan-bindings.json         # planName → ct_
├── thread-bindings/           # threadId → ct_  (mkdir)
├── archive/                   # 归档 session
│   └── 2026-06/
│       └── ct_xxx.json
├── replies/                   # 历史用户回复缓存
├── logs/
│   ├── hub.log                # Hub 主日志
│   ├── launchd.log            # macOS launchd 日志
│   └── launchd.err
├── templates/                 # 面板模板
├── server.js                  # MCP server（41KB）
├── hub.js                     # Hub 主服务（89KB）
├── panel.html                 # 浏览器面板（84KB）
├── cursor-title.js            # 标签解析器（4KB）
├── ensure-hub.sh              # 启动器
└── mongo_sync.py              # → MongoDB 同步
```

### 7.2 sessions.json 格式

```json
{
  "ct_xxxx": {
    "token": "ct_xxxx",
    "title": "永久续跑三段式",
    "displayTitle": "",
    "status": "holding",
    "workspace": "/Users/karuo/Documents/个人",
    "createdAt": 1782448693854,
    "lastActiveAt": 1782449356228,
    "msgCount": 10,
    "autoStreak": 1,
    "bindingKey": "/Users/karuo/Documents/个人::标签@192.168.3.1",
    "hostIp": "192.168.3.1",
    "hub": "192.168.110.101:13458",
    "bindingCheckCount": 17,
    "continueMode": "optimize",
    "sessionAutoContinue": false,
    "timedReplyEnabled": false,
    "timedReplyIntervalMs": 0,
    "messages": [
      { "role": "user", "content": "...", "at": 1782448794716, "auto": false },
      { "role": "assistant", "content": "...", "at": 1782449116363, "auto": false }
    ],
    "lastReview": {
      "hasReview": false,
      "completionPct": null,
      "nextStep": "",
      "isIdleNext": true
    }
  }
}
```

### 7.3 周期保存

- 每次 register / deliverReply / merge 后 `saveSessions()`
- 写盘：原子 rename（先写 .tmp，再 rename）
- 加载：启动时一次 loadSessions()，之后只内存操作

---

## 8. 与 MongoDB 同步（可选）

`mongo_sync.py` 把 sessions.json 周期同步到 MongoDB `karuo_site.sessions`：

```python
# 触发
python3 mongo_sync.py
# 或 cron 每 5 分钟
*/5 * * * * cd ~/.persistent-chat-local && python3 mongo_sync.py
```

用途：
- 多机同步（局域网内多 Hub）
- 长期归档（超过 1000 个 session 不卡）
- 跨平台（Mac / NAS / Linux）

---

## 9. 安全性

| 维度 | 措施 |
|:---|:---|
| 端口 | 13458 默认仅 LAN 监听，不暴露公网 |
| 鉴权 | 垂直绑定（三件指纹），无 token → 拒绝 |
| 数据 | sessions.json 落在 ~/.persistent-chat-local/，权限 600 |
| 跨网段 | 靠 SSH 隧道 / VPN |
| 日志 | 不记录用户隐私内容（只记 metadata） |
| 卡密激活 | **已删除**（v1.6.37 不再有 license 校验） |

---

## 10. 性能

| 指标 | 值 |
|:---|:---|
| Hub 内存 | ~50MB（1000 个 session） |
| Hub CPU | < 1%（空闲） |
| 单 session 占内存 | ~5KB（仅 metadata） |
| 单消息占内存 | ~1KB |
| 短轮询带宽 | ~1KB/10s/session |
| 启动时间 | < 1s（Node 22） |
| sessions.json 加载 | 10000 个 session < 500ms |

---

## 11. 版本演进

| 版本 | 日期 | 关键变化 |
|:---|:---|:---|
| v1.1 | 2025-12 | 初版（仅 hub.js） |
| v1.5 | 2026-03 | 加 server.js / MCP 4 工具 |
| v1.6.30 | 2026-05 | autoStreak / timedReply |
| v1.6.37 | 2026-06-07 | **删除卡密激活** / 加 MongoDB 同步 |
| v1.6.68 | 2026-06-20 | 本地增强（VSIX 4.6 兼容） |

---

> 最后更新：2026-06-26 · 卡若AI
