qwen-code Session 存储生命周期修复:损坏、孤儿与 Active/Archive 冲突的确定性维护方案
2026/9/12 14:21:35 网站建设 项目流程

qwen-code Session 存储生命周期修复:损坏、孤儿与 Active/Archive 冲突的确定性维护方案

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

导读

本文讲解 qwen-code 项目中「会话存储生命周期修复」(Session storage lifecycle repair)的设计与实现:在会话 transcript 为空、头部撕裂损坏或属于父级已不存在的遗留子会话(legacy orphan)时,依然能够对物理文件执行确定性的 delete、archive、unarchive 操作,并为 active/archive 目录同时存在同名会话文件的冲突场景提供显式的resolveConflicts修复选项。读完本文,你将掌握该仓库生命周期分类器的判定规则、写入者锁与 generation 防护机制,以及 REST/ACP/SDK 三层 API 的兼容性演进方式。

问题背景:生命周期操作不应依赖「可加载的 transcript」

在 2026-08-22-session-storage-lifecycle-repair.md 描述的问题中,会话生命周期操作(删除、归档、取消归档)此前依赖「将 transcript 加载为可用会话」这一前置条件。这对于 delete、archive、unarchive 而言约束过强:

  • transcript 文件为空(empty);
  • 文件头部撕裂或格式损坏(torn / malformed head);
  • 属于遗留子会话(legacy child),其父会话已不存在。

上述文件虽然无法被加载为对话,但只要「选定的工作区(workspace)」与「归档状态」是明确的,物理文件仍然需要维护。否则这些物理文件会被遗留在磁盘上(stranded),既无法加载,也无法通过正常生命周期操作清理。

该变更的目标是:让 delete、archive、unarchive 直接作用于「所选工作区拥有的、拼写精确匹配的常规 transcript 文件」,并为 active/archive 冲突新增显式修复选项;同时不改变 transcript 加载、列举、导出、组织、Live 会话身份以及通用的大小写不敏感查找逻辑,正常生成的会话 UUID 仍保持规范的小写形式。

存储分类:生命周期分类器的判定规则

分类器(lifecycle classifier)会同时检查请求 UUID 文件在 active 与 archived 两个目录中的存在情况,其判定语义如下:

判定条件结果
路径不存在不贡献任何状态
常规的空文件或损坏文件贡献「物理状态」(physical state)
可读的首条记录必须标识出请求的会话与所选工作区
首条记录标识了其他工作区视为未找到(not found)
符号链接、目录或其他非常规条目失败关闭(fails closed)
active 与 archived 同时存在构成冲突(conflict)

分类器会捕获文件的身份信息(file identity),用于纯物理维护。在 unlink 或 rename 之前,Core 会再次验证「同一个常规文件仍占据相同路径」。

在源码层面,该逻辑位于 sessionService.ts 的resolveMaintainableSessionSnapshot

  • activearchived两种状态逐一调用readMaintainableSessionIdentity(sessionService.ts);
  • 打开文件时使用O_RDONLY,在非 Windows 平台额外叠加O_NOFOLLOWO_NONBLOCK,从而对符号链接、目录等非常规条目失败关闭:捕获ELOOP时抛出SessionStorageEntryError(sessionId, 'non_regular')
  • 首条可读记录若sessionIdcwd不是字符串、sessionId大小写不匹配、或cwd不属于当前项目,则分别抛出unknown_project/foreign_project错误(sessionService.ts);
  • 任一状态出现 foreign 且无任何有效身份时抛出foreign_project,出现 foreign 但同时存在其他身份时抛出ambiguous_project(拒绝歧义所有权);
  • 最终根据 active/archived 身份的有无归约为conflictactivearchivedundefined(未找到)四种位置。

SessionStorageEntryError定义在 sessionService.ts,归档/取消归档循环中捕获reason === 'foreign_project'并归入notFound,而不是返回错误。

路由规则:工作区限定与无工作区路由

路由层面分为两类:

  • 工作区限定路由(workspace-qualified):只变更所选受信任运行时的存储;
  • 无工作区普通路由(workspace-less):保留既有主工作区行为。Internal Conversations 运行时仅当请求批次具有唯一无歧义的所有者时才会被选中。

关键约束是:运行时选择在持有生命周期协调器锁(lifecycle coordinator lock)期间重复执行;如果 generation 已变化或不可用,返回既有的可重试运行时错误,而不是变更陈旧的存储(stale storage)。

Provenance(来源信息)对加载与列举仍是权威依据,但不再阻止对「归本工作区所有的常规 transcript」的维护——这就允许清理遗留孤儿子会话,同时不使其变为可加载。从代码看,resolveMaintainableSessionSnapshot的可读首条记录只要求 sessionId 与 cwd 归属当前项目,而非要求记录结构完整可重建对话,正是「物理所有权」与「可加载性」解耦的体现。

冲突修复:默认非破坏,显式 resolveConflicts

默认行为:非破坏性的冲突错误

archive 与 unarchive 保持默认的非破坏行为:active/archive 冲突以「按会话错误」(per-session error)返回,两个持久化副本都不会被移动、删除或覆盖。

两个细节值得注意:

  1. archive 仍会先严格关闭活动会话,再分类冲突:这样队列中待写入的记录有机会 flush 到 active transcript,避免分类在脏数据上进行;
  2. 批量生命周期路由(含工作区限定路由)以200响应返回该结果,取代此前工作区限定的409 session_conflict错误封套——冲突不再以整个请求失败的形式呈现,而是体现在批量结果中的 per-sessionerrors数组里。

显式修复:resolveConflicts: true

调用方可以发送resolveConflicts: true来显式修复冲突:

操作修复语义
archive +resolveConflicts: true保留 archived transcript,移除 active transcript
unarchive +resolveConflicts: true保留 active transcript,移除 archived transcript

响应新增resolvedConflicts数组,包含被该请求修复的会话 ID。delete 保持既有兼容行为,无需任何选项即移除两份副本。

在 sessionService.ts 的archiveSessions实现中,冲突分支(location === 'conflict')在resolveConflicts缺失时直接抛出Session archive conflict: ${sessionId};开启后则:

  1. 先执行assertStorageUnchangedassertCanMutate校验;
  2. 调用assertMaintainableSessionUnchanged(sessionService.ts)逐状态比对快照身份,任何不一致抛出SessionTranscriptChangedError
  3. 移除 active 文件,并把 PR sidecar、worktree 路径、prompt ledger 等副产物一并迁移或清理;
  4. 将 sessionId 同时计入archivedresolvedConflicts

unarchiveSessions(sessionService.ts)对称处理:保留 active、移除 archived,并额外通过retitleIfCollidesWithActive避免归档会话的存储标题与活动会话撞名。

写入者与 generation 安全

维护操作在 per-session 生命周期协调器与写入者锁(writer lock)下运行:

  • 损坏 transcript 使用同一把写入者锁,但携带维护哨兵(maintenance sentinel):因为常规写入者证明(writer proof)会刻意拒绝撕裂的 JSONL;
  • 哨兵不会绕过认证交接(certified handoff):如果写入者封存锁之后 transcript 字节发生变化,接管仍以SessionTranscriptChangedError失败关闭,需要操作者介入;
  • Core 在提交变更前仍会对实际 transcript 路径做快照与验证

Generation 防护共三层检查:获取协调器之后、文件系统提交之前、操作完成之后各检查一次所选运行时 generation。因此,即使运行时被替换,也不可能把进行中的变更静默重定向到另一个运行时。

测试侧,SessionTranscriptChangedError与 generation fencing 的覆盖见 sessionService.corruption.test.ts 以及 sessionService.test.ts(后者的resolveConflicts/resolvedConflicts用例覆盖了显式修复与替换检测)。

API 兼容性:REST、ACP 与 SDK

请求体与能力声明

REST 与 ACP 的 archive/unarchive 请求体接受可选的布尔字段resolveConflicts

  • 省略该字段保留非变更的冲突行为;
  • 工作区限定的 REST 传输层按上文所述在批量结果中报告冲突;
  • 非布尔值属于无效请求(invalid request)。

服务端通过session_storage_conflict_repair能力位(capability)通告对选项与响应字段的支持,声明位于 capabilities.ts(since: 'v1')。

响应字段与 SDK

archive 与 unarchive 响应新增resolvedConflicts

  • SDK 结果类型将该字段保持为可选(resolvedConflicts?: string[]),使旧客户端对旧 daemon 保持兼容,而新 daemon 始终返回数组——见 types.ts;
  • 既有 SDK client-ID 调用形式保持有效,选项通过额外的重载位置传入(见 DaemonClient.ts)。

REST 行为验证

server.test.ts 中的集成测试精确验证了契约:

  • resolveConflicts: 'yes'(字符串)返回400codeinvalid_request——证明非布尔值被拒绝;
  • resolveConflicts: true归档冲突会话返回200,响应体为{ archived: [sid], resolvedConflicts: [sid], errors: [] },且 active 文件被移除、archived 文件保留;
  • unarchive 方向对称验证。

能力位session_storage_conflict_repair也在该文件的 capability 清单(server.test.ts)中被断言。

验证矩阵:回归测试的覆盖维度

回归矩阵覆盖 delete、archive、unarchive 在以下三类 transcript 上的行为:

  1. empty(空文件);
  2. damaged(损坏文件,如撕裂的 JSON 头部);
  3. legacy-orphan(遗留孤儿,其记录的 cwd 属于当前项目但父会话已不存在)。

测试分别运行在 Core 层、工作区限定内部路由、以及无工作区的唯一所有者路由(unqualified unique-owner routing)。每个成功用例都会断言最终的 active/archive 文件状态,且移动操作保留原始字节。

在 sessionService.corruption.test.ts 中可以看到具体形态:

  • unreadableShapes数组定义了{ name: 'empty', content: '' }{ name: 'damaged', content: '{"uuid":"torn-head"' }两种不可读形态;
  • 对每种形态,测试断言:delete 后 active 文件不存在;archive 后 active 消失、archived 文件内容与写入时逐字节一致(fs.readFileSync(paths.archived, 'utf8')等于原 content),证明「不重写、原样移动」;
  • 冲突修复测试(sessionService.corruption.test.ts)分别以resolveConflicts: true验证 archive 保留 archived 内容('{"uuid":"torn-archived"')、unarchive 保留 active 内容('{"uuid":"torn-active"'),并断言resolvedConflicts数组。

兼容性测试则保留默认 archive/unarchive 冲突行为与 delete-both 行为,并补充覆盖:双向显式修复、外来/歧义所有权拒绝(foreign_project/ambiguous_project)、无效选项类型、SDK 请求体、generation fencing 与替换检测。

总结:设计要点一览

关注点设计决策
可维护性判定以「物理所有权」替代「可加载性」,空/损坏/孤儿文件仍可维护
所有权校验首条记录必须匹配会话 ID 且 cwd 属于当前工作区,外来/歧义一律拒绝
非常规条目符号链接、目录等 fail closed(non_regular
冲突默认行为非破坏:per-session 错误,两份副本均不动;批量路由返回200
显式修复resolveConflicts: true:archive 留归档删活动,unarchive 留活动删归档
写入安全维护哨兵 + writer lock,认证交接仍 fail closed;变更前快照验证路径身份
并发防护generation 在协调器获取后、提交前、完成后三处校验
API 演进resolvedConflicts可选字段保持旧客户端兼容,新 daemon 恒返回数组;capability 通告

通过「物理状态分类 + 显式冲突修复 + 写入者/ generation 双重防护」的组合,该设计让会话存储的维护从「依赖完整对话」变为「依赖确定的物理所有权」,为损坏与遗留数据提供了一条可安全执行的清理路径,值得在同类 AI 编码代理的会话存储层中借鉴。

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询