Skip to content

feat: 多会话支持 — /sessions、/switch、/session 命令#174

Open
hahhforest wants to merge 2 commits into
xvirobotics:mainfrom
hahhforest:feat/multi-session-switch
Open

feat: 多会话支持 — /sessions、/switch、/session 命令#174
hahhforest wants to merge 2 commits into
xvirobotics:mainfrom
hahhforest:feat/multi-session-switch

Conversation

@hahhforest

@hahhforest hahhforest commented Apr 8, 2026

Copy link
Copy Markdown

概要

将 SessionManager 从扁平的 per-chatId 模型重构为 SessionGroup 分组模型,允许用户在一个聊天中维护多个对话线程。

  • /reset 不再销毁会话,而是创建新会话,旧会话保留可切换
  • /sessions 列出当前聊天的所有会话(编号、标题、ID 前缀、活跃标记)
  • /switch N 按编号切换会话
  • /session <prefix> 按 sessionId 前缀切换(按 [Feature] 能否实现通过指令列出和切换 Claude Code / Codex 历史会话? #239 统一命令设计)
  • 虚拟 chatId {chatId}::{N} 确保 SessionRegistry 记录隔离
  • 持久化格式向后兼容,自动迁移旧扁平格式为分组格式

对 reviewer 反馈的回应

  1. 文件迁移 — 已基于最新 upstream/maina6ce914)全新重写,代码位于 src/engines/claude/session-manager.ts,保留了所有上游新字段(engine, model, goal 等)
  2. refactor(session): replace SQLite registry with file-backed impl #243/feat(multi-process): per-bot process isolation via PM2 + BOT_NAME #244 依赖 — 两个 PR 自 5/9 以来无更新(作者 uestney 未回应 review 意见),先不等待。当前实现与现有 SessionRegistry(SQLite)兼容,后续 refactor(session): replace SQLite registry with file-backed impl #243/feat(multi-process): per-bot process isolation via PM2 + BOT_NAME #244 合并后会做适配 rebase
  3. 并发安全 — MessageBridge 通过 runningTasks Map 保证每个 chatId 同时只有一个任务在执行,activeIndex 读写不存在竞态条件。已在代码中注释说明此不变量
  4. 统一命令设计 — 按 [Feature] 能否实现通过指令列出和切换 Claude Code / Codex 历史会话? #239 讨论结果增加了 /session <prefix> 命令,命令空间与 feat: add /api and /resume commands for remote management #227/resume 正交

变更文件

文件 变更
src/engines/claude/session-manager.ts SessionGroup 分组模型 + 6 个新方法 + 持久化迁移
src/bridge/command-handler.ts /sessions/switch/session 命令 + /help 更新
src/bridge/message-bridge.ts 虚拟 chatId + 标题追踪
tests/session-manager.test.ts 24 个新测试覆盖所有新功能

测试计划

  • npm run build — 编译通过(预存的 kimi/marked 错误与本 PR 无关)
  • npm test — 24 个新测试 + 5 个原有测试全部通过
  • npm run lint — 无新错误
  • 飞书集成测试 — 完整多会话生命周期验证如下:

飞书集成测试记录

环境:bot xiaobai,persistent executor 模式

步骤 操作 飞书返回 内部状态
1 发送"中国的首都是哪里?" "北京啊" sessions=1, activeIndex=0, sid=59e35d2f, title="中国的首都是哪里?"
2 /reset "New session created. Previous sessions preserved" sessions=2, activeIndex=1, 旧会话完整保留
3 发送"法国的首都是哪里?" "巴黎啊" session[1] 获得独立 sid=e82ab932, title="法国的首都是哪里"
4 /switch 1 "Switched to session 1 (59e35d2f...)" activeIndex 从 1 切回 0
5 发送"我刚才问了什么问题?" "你问中国的首都是哪里" sid 保持 59e35d2f,上下文正确恢复

关键验证:切换回旧会话后,Claude 正确回忆了"中国的首都"而非"法国的首都",证明会话隔离和上下文恢复功能正常。

集成测试中发现并修复的 bug

  1. saveToDisk 过滤 bug:新创建的空会话(/reset 后尚无 sessionId)被持久化过滤器跳过,导致重启后丢失
  2. persistent executor 未释放/switch 未调用 releaseExecutor,导致旧 Claude 进程的 sessionId 覆盖切换后的会话

两个 bug 均已在第二个 commit 中修复。

@hahhforest hahhforest changed the title feat: add multi-session support with /sessions and /switch commands feat(feishu): multi-session support with /sessions and /switch commands Apr 8, 2026
@hahhforest hahhforest force-pushed the feat/multi-session-switch branch from cc18b3f to e193c4b Compare April 8, 2026 11:50
@floodsung

Copy link
Copy Markdown
Contributor

Triage 路过 — 这个 PR 目前与 main 有冲突(mergeable: CONFLICTING),自 2026-04-10 后无更新。多 session 的方案设计很完整,能否 rebase 到最新 main 解决冲突?另外注意 PR #227 也加入了 /resume 命令,可能需要协调命令空间和 SessionRegistry 的交互。

@floodsung

Copy link
Copy Markdown
Contributor

Thank you for this — the SessionGroup design is the right structural change for multi-session per-chat, and the migration from the old flat format is well-handled. A few items need addressing before this can land:

1. Stale base — SessionManager has moved

Since this PR was opened, the engine abstraction landed and src/claude/session-manager.ts was relocated to src/engines/claude/session-manager.ts. This is more than a textual conflict — the file's surrounding API has shifted (it's now constructed by the engine factory, with new env-var precedence: SESSION_STORE_DIR > METABOT_DATA_DIR > ~/.metabot). The patch needs to be re-derived against the new layout.

2. Coordination with #243 and #244

Concretely: please rebase after #243 + #244 land, not before — otherwise we'll be chasing three-way conflicts.

3. Concurrency on SessionGroup.activeIndex

/switch mutates activeIndex while recordSession may be writing to the same group from a concurrent message. There's no lock today. For a single user in a single chat the window is small, but worth either (a) serializing per-chatId writes or (b) at minimum documenting the edge case.

4. Unified command surface (cross-PR)

/sessions + /switch here overlaps semantically with /resume in #227 and the spec in #239. I've opened a design discussion on #239 to align — proposal is /sessions (your work, MetaBot multi-chat), /session <prefix> (a small extension to switch by sessionId prefix instead of just index), and /resume (PR #227's local CLI bridging). Could you weigh in there?

Once #243 and #244 are merged, please rebase, address (3), and align with the #239 design — happy to take it then.

hahhforest and others added 2 commits May 14, 2026 17:14
将 SessionManager 从扁平的 per-chatId 模型重构为 SessionGroup 分组模型,
允许用户在一个聊天中维护多个对话线程。

主要变更:
- resetSession() 创建新会话而非清除旧会话,历史会话保留可切换
- 新增 /sessions 列出所有会话、/switch N 按编号切换、/session <prefix> 按 ID 前缀切换
- 虚拟 chatId ({chatId}::{N}) 确保 SessionRegistry 中不同会话的记录隔离
- 自动记录会话标题(首条消息预览)
- 持久化格式向后兼容:自动将旧扁平格式迁移为分组格式
- 24 个测试覆盖所有新功能

设计参考 xvirobotics#239 统一命令规范。基于 upstream/main 全新重写(原 PR xvirobotics#174)。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. saveToDisk 过滤 bug:新创建的空会话(无 sessionId)被持久化过滤器
   跳过,导致 /reset 后重启服务会丢失新会话。改为保存 group 内所有
   session。

2. /switch 未释放 persistent executor:切换会话时旧的长驻 Claude 进程
   仍在运行,其 sessionId 会覆盖切换后的会话。现在 /switch 和
   /session 命令都会调用 releaseExecutor。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@hahhforest hahhforest force-pushed the feat/multi-session-switch branch from e193c4b to 372296a Compare May 14, 2026 10:04
@hahhforest hahhforest changed the title feat(feishu): multi-session support with /sessions and /switch commands feat: 多会话支持 — /sessions、/switch、/session 命令 May 14, 2026
@hahhforest

Copy link
Copy Markdown
Author

@floodsung 感谢详细的 review!已基于最新 upstream/maina6ce914)全新重写,逐条回应:

1. 文件迁移 ✅

已在 src/engines/claude/session-manager.ts 上重新实现,保留了所有上游新字段(sessionIdEngine, model, modelEngine, engine, activeGoal, goalSetAt),兼容引擎抽象层。

2. #243/#244 依赖

两个 PR 自 5/9 以来作者 uestney 未回应 review 意见,处于停滞状态。当前实现基于现有 SessionRegistry(SQLite)可正常工作,等 #243/#244 合并后会做一次适配 rebase。

3. 并发安全 ✅

分析后确认不需要加锁:MessageBridge 通过 runningTasks Map 强制保证每个 chatId 同时只有一个任务在执行,/switch/reset 作为命令在任务开始前处理,activeIndex 的读写不存在竞态。已在代码中注释说明此不变量。

4. 统一命令设计 ✅

#239 讨论结果增加了 /session <prefix> 命令(按 sessionId 前缀切换,8+ 字符)。命令空间:

飞书集成测试

在 persistent executor 模式下完成了完整的多会话生命周期验证:创建会话 → /reset 保留旧会话 → 新会话独立对话 → /switch 切换回旧会话 → 上下文正确恢复(详见 PR 描述)。

测试中发现并修复了两个 bug:saveToDisk 过滤掉空新会话、/switch 未释放 persistent executor。

@hahhforest

Copy link
Copy Markdown
Author

@floodsung 补充说明:关于你提到的 #243 依赖问题,我们已经在 PR #278 中独立解决了 —— 基于 @uestney 的设计将 SessionRegistry 从 SQLite 迁移为 JSON 文件持久化,并补全了你在 #243 review 中要求的 warn 日志、测试覆盖和迁移说明。

所以目前:

两个 PR 互不依赖,合并顺序随意。期待 review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants