Module Structure
该节点负责围绕持久 transcript 构建用户可操作的会话生命周期。所有读取先验证 Session 与 workspace 身份,再从有效事件投影目录、分支、回退结果、搜索命中或导出内容。
Directory Layout
packages/cli/src/services/SessionService.ts:会话元数据聚合与生命周期总入口packages/cli/src/services/sessionCatalog.ts:查询规范化、稳定排序和 cursor 编解码packages/cli/src/services/sessionRewind.ts:append-only rewind materialization 与 checkpoint 规划packages/cli/src/services/TranscriptSearch.ts:SQLite FTS 快路径和 JSONL 兼容扫描packages/cli/src/services/SessionMarkdownExporter.ts:可移植 Markdown 安全投影packages/cli/src/services/SessionExportWriter.ts:本地 no-clobber 文件落盘packages/cli/src/slash-commands/:resume、fork、archive、rewind、search 与 export 交互入口
Key Entry Points
SessionService.listSessionPage()/listSessions()inpackages/cli/src/services/SessionService.ts:分页目录与任务过滤SessionService.forkSession()inpackages/cli/src/services/SessionService.ts:从稳定有效历史创建独立 childSessionService.rewindSession()inpackages/cli/src/services/SessionService.ts:原子提交回退 marker 并可选恢复 snapshotSessionService.archiveSession()/unarchiveSession()inpackages/cli/src/services/SessionService.ts:可恢复归档生命周期SessionService.exportSessionMarkdown()inpackages/cli/src/services/SessionService.ts:从 exact-workspace 稳定快照导出
Gotchas
- Catalog cursor 绑定
cwd、includeSubagents和archived查询域;把 active cursor 用于 archived 列表或改变 workspace/filter 会明确拒绝,而不是从近似位置继续 (packages/cli/src/services/sessionCatalog.ts) - 排序边界是
lastMessageTime DESC -> projectPath ASC -> sessionId ASC,cursor 还使用 UTF-16 code-unit sort key 对齐 SQLite;改一侧排序却不改另一侧会造成跨页重复或遗漏 (packages/cli/src/services/sessionCatalog.ts,packages/cli/src/services/SessionService.ts) - 无 workspace 的 ID 查询只在全局唯一时安全;
/resume、/fork和 archive 命令对同名跨工作区 Session 必须拒绝并要求用户选择 (packages/cli/src/slash-commands/resume.ts,packages/cli/src/slash-commands/fork.ts,packages/cli/src/slash-commands/archive.ts) - scoped load/delete/fork 不信任转义目录名,必须校验
session_created.data.sessionId和 committedcwd;目录碰撞不能让另一个 workspace 的 transcript 被读取或删除 (packages/cli/src/services/SessionService.ts,git:f764f682) - fork 必须留在源 workspace,并从最多三次 stat/read/stat 一致的稳定快照创建;源在三次读取中持续变化时不产生 child (
packages/cli/src/services/SessionService.ts,git:375287f5) - fork 复制 materialized 有效历史和 compaction checkpoint,但剥离 token handoff、inbox acknowledgement、pending interaction、review lifecycle 与 task 运行态;这些是父 Session 的恢复权限,不能继承到 child (
packages/cli/src/services/SessionService.ts,git:1cacc9eb) - fork 的 prompt artifact 必须按 transcript 实际引用复制且重新校验;artifact 复制失败会删除已创建 child transcript 和 child artifact,不能留下只有预览而缺少完整请求的分支 (
packages/cli/src/services/SessionService.ts,packages/cli/src/agent/runtime/UserPromptArtifactStore.ts) - rewind 不截断 JSONL;conversation/both 模式追加 marker 后由 projector 移除目标 user turn 及后续 conversation 生命周期,code-only 模式则保留 conversation (
packages/cli/src/services/sessionRewind.ts,packages/cli/src/services/SessionService.ts) - code/both rewind 在 append marker 的 validated 临界区内先检查 snapshot 连续性和写后 hash;任一文件被外部修改时整组拒绝且 transcript 不新增 marker (
packages/cli/src/services/SessionService.ts,packages/cli/tests/unit/services/session-service-rewind.test.ts) - 归档父 Session 只给根追加
archivedAt,后代状态由 lineage 投影继承;恢复父节点不会解除后代自己直接设置的归档 (packages/cli/src/services/SessionService.ts,docs/reference/session-archive.md) - 归档会先按稳定 ID 顺序获取根及全部 fork/subagent 后代的 lease,任一 queued/running 或被占用成员使整次操作零写入失败 (
packages/cli/src/services/SessionService.ts,packages/cli/src/agent/runtime/SessionLease.ts) - TranscriptSearch 的 SQLite 路径只做 FTS 候选缩减,最终仍执行原 substring/snippet 判断;自定义
storagePath、索引不可用或异常时走 JSONL 扫描 (packages/cli/src/services/TranscriptSearch.ts) - SQLite 搜索结果的
lineNumber为 0,而 JSONL 路径保留物理事件行号;调用方不能把该字段作为所有搜索路径都稳定的定位主键 (packages/cli/src/services/TranscriptSearch.ts) - Markdown 导出读取 stable JSONL 并应用 rewind,不从当前 Runtime、Web store 或 SQLite 拼接;否则 archived Session、冷导出和并发 append 会产生不同结果 (
packages/cli/src/services/SessionService.ts,packages/cli/src/services/SessionMarkdownExporter.ts) - 本地导出使用 exclusive create 和 0600 权限,目标已存在时明确失败;ACP 只允许内联 Markdown 且上限 1 MiB,不能让 remote client 指定宿主路径 (
packages/cli/src/services/SessionExportWriter.ts,packages/cli/src/slash-commands/export.ts) - exact-path 删除损坏 JSONL 仍保留 legacy 清理行为,当前无法从坏记录证明 committed cwd,且存储键注入性与跨进程锁仍是
storage-v2债务 (packages/cli/src/services/SessionService.ts)
Architecture
projectMetadataFromEntries()先折叠session_created/session_updateddurable metadata,再用 materialized history 计算消息数、错误、pending interaction 和 review 派生态;SQLite 复用同一 deriver 保持 parity (packages/cli/src/services/SessionService.ts)- 分页优先使用 SQLite recursive CTE 计算继承归档状态,失败时回退全量 JSONL 扫描并复用同一 comparator/cursor 逻辑 (
packages/cli/src/services/SessionService.ts,packages/cli/src/context/storage/sqlite/projection.ts) - rewind projector 是 catalog、resume、fork、search、export 和 context load 的共享依赖;新增 conversation event 时必须判断它是否属于 rewind 后缀,否则各读面会分叉 (
packages/cli/src/services/sessionRewind.ts) loadSession()返回完整 UI 历史,loadSessionModelContext()额外应用最新 compaction replacement;恢复界面和恢复 Provider 请求必须调用各自入口 (packages/cli/src/services/SessionService.ts)- 归档是 catalog projection,不是物理移动或删除;hard delete 才同步清除 transcript、inbox、goal、prompt artifact、browser artifact 和 SQLite 行 (
packages/cli/src/services/SessionService.ts)
Decisions
- Session catalog 从宽松目录扫描演进为 scope-bound cursor、严格 ID/workspace 验证和跨目录去重,优先防止错误恢复而不是猜测用户想要哪个同名会话 (
packages/cli/src/services/sessionCatalog.ts,git:be64af77,git:aaa50c79) - fork 采用完整 transcript exclusive create 而非共享父日志或 copy-on-write,使 child 可独立追加,同时通过
rootId/parentId/relationType保留 lineage (packages/cli/src/services/SessionService.ts,git:a141169b) - rewind 采用审计 marker 和纯 materializer,而不是破坏性删行,使后续 fork、搜索、导出和 SQLite rebuild 都能重现当时选择 (
packages/cli/src/services/sessionRewind.ts,git:6bd75ab1) - 导出正文 hash 刻意不覆盖可变化 metadata header;相同有效历史和 visibility 选项可得到稳定正文摘要 (
packages/cli/src/services/SessionMarkdownExporter.ts)
Security Considerations
- 导出器对普通文本清理 ANSI、控制/隐藏 Unicode、私钥、Bearer/key 模式;tool activity 还递归清理敏感键、data URL、host path 和 URL 凭证 (
packages/cli/src/services/SessionMarkdownExporter.ts) - 单项 activity 最多 64 KiB、完整导出最多 16 MiB;超出完整上限时整体失败而不是生成看似完整的静默截断文件 (
packages/cli/src/services/SessionMarkdownExporter.ts) - rewind 文件路径由 SnapshotManager 的 workspace/session 分区和 PathSecurity 边界约束,同名 Session 的不同 workspace 不得共享 snapshot (
packages/cli/src/services/SessionService.ts,packages/cli/src/slash-commands/rewind.ts)
Compatibility
- Cursor 接受带时区的非规范 ISO 时间并在解码后规范 workspace,但要求 canonical unpadded base64url 重编码一致,以兼容旧 cursor 又拒绝歧义编码 (
packages/cli/src/services/sessionCatalog.ts,git:2cd3c93d) - TranscriptSearch 仍识别没有
session_created的 legacymessageJSONL;当前事件格式则先 materialize rewind,再只聚合 user/assistant text part (packages/cli/src/services/TranscriptSearch.ts) - 缺少 task metadata 的 legacy transcript 默认投影为 completed,避免历史普通会话被误当失败任务 (
packages/cli/src/services/SessionService.ts)