qwen-code WebShell 非主工作区会话归档加固:能力门控、身份对账与部分失败处理
2026/9/13 3:48:09 网站建设 项目流程

qwen-code WebShell 非主工作区会话归档加固:能力门控、身份对账与部分失败处理

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

本文基于 qwen-code 仓库的设计文档 web-shell-non-primary-session-archive-hardening.md,讲解 WebShell 在多工作区(daemon 多 workspace)场景下如何安全地暴露"归档/取消归档"操作:哪些能力(capability)与信任条件决定 UI 是否可用、跨工作区如何以(workspaceCwd, sessionId)二元组隔离同名会话,以及 HTTP 成功响应中携带的逐会话失败如何被前端对账(reconcile)。读完后,你能理解 qwen-code 会话归档链路从 UI 门控到 daemon 执行、再到状态收敛的完整契约。

一、变更定位:补齐既有 UI 路径,不动 API 契约

设计文档的 Summary 明确了这次加固的边界:

  • WebShell 已经在从已注册的 secondary(非主)工作区列出 active 与 archived 会话;
  • daemon 已经暴露了workspace-qualified 的 archive/unarchive 路由
  • 本次变更只是完成既有的 UI 路径,不修改:归档 REST API、SDK 类型定义、持久化格式、以及删除(delete)行为。

也就是说,这是一次典型的"契约不变、行为补齐"的加固:API 形状、协议字段、落盘格式全部保持稳定,变更集中在能力门控、身份隔离与错误对账三个层面。相关协议背景可参考 qwen-serve-protocol.md 与 会话生命周期说明。

二、能力与信任边界:三条件门控归档 UI

文档规定的门控规则是:

条件适用范围说明
session_archive能力所有会话归档 UI缺失时不暴露归档入口,也不查询 archived 目录
workspace_qualified_rest_core能力secondary 工作区非主工作区额外要求
受信任的运行时(trusted runtime)secondary 工作区非信任工作区不开放写操作

并且:受信任 secondary 工作区的 active 行只暴露 Archive 操作;这些行对 pin(置顶)、group(分组)、rename(重命名)、export(导出)、delete(删除)保持既有的load-only(仅加载)处理,本次加固不改变这一点。最后一个关键细节是:当所需能力缺失时,archived 目录根本不发起查询——门控发生在数据请求之前,而不是渲染阶段。

从源码可以印证这套门控。能力声明在 capabilities.ts 中:

session_archive: { since: 'v1' },

session_archive自 v1 能力集起可用;workspace_qualified_rest_core是另一条用于工作区限定 REST 通道的能力位,两者在 WebShell 客户端分别被消费。侧边栏 WebShellSidebar.tsx 中对能力的判断形如:

connection.capabilities?.features?.includes('session_archive'),

能力版本的演进规则可参考 能力与版本化文档。

三、身份模型:以 (workspaceCwd, sessionId) 作为会话唯一键

文档的 Identity and Reconciliation 部分规定:WebShell 中合并后的会话集合与瞬态行状态一律用(workspaceCwd, sessionId)二元组识别一个会话。这一规则覆盖:

  • 去重(deduplication);
  • React key 生成;
  • 当前选中态(current selection);
  • 忙碌态(busy state);
  • 未读完成标记(unread completion);
  • 导出进行中状态(export-in-flight state)。

目的是保证不同工作区中 sessionId 相同的两个会话完全独立——选中一个工作区的sess-abc,绝不能误伤另一个工作区同 id 的会话。

WebShellSidebar.tsx 中的会话键生成正是这个契约的落地:

return `${workspaceCwd ?? ''}\0${sessionId}`;

\0分隔 workspace 路径与 session id,避免纯字符串拼接产生歧义碰撞。secondary 工作区会话的查询与合并逻辑集中在 useOtherWorkspaceSessions.ts,其中每个查询以workspaceCwd为单位维护archiveState: 'active'等状态。

四、部分失败与响应后对账:成功 HTTP 响应里的逐会话错误

文档对错误语义的定义是核心设计点之一:

workspace-qualified 的 archive / unarchive 响应可能在 HTTP 2xx 成功响应中报告逐会话失败。WebShell 会把对应的errors[]条目呈现给用户,并且在操作落定后,总是重新对账(reconcile)主工作区的 active/archived 目录以及所选工作区的目录。幂等的alreadyArchivedalreadyActive结果被视为成功。

这段契约与 daemon 侧的结果结构严格对应。session-archive.ts 定义了批量结果类型:

export interface DaemonArchiveSessionsResult { archived: string[]; alreadyArchived: string[]; resolvedConflicts: string[]; notFound: string[]; errors: Array<{ sessionId: string; error: unknown }>; } export interface DaemonUnarchiveSessionsResult { unarchived: string[]; alreadyActive: string[]; resolvedConflicts: string[]; notFound: string[]; errors: Array<{ sessionId: string; error: unknown }>; }

几个实现细节值得注意:

  1. 批量部分成功是常态而非异常archiveDaemonSessions/unarchiveDaemonSessions对每个 session 独立执行并归桶(archived / alreadyArchived / notFound / error),整体请求仍是 200,失败信息走errors[]字段。因此客户端不能只看状态码,必须消费errors[]——这正是文档要求 WebShell "surface a matchingerrors[]entry" 的原因。
  2. 幂等语义内置。目标会话已处于目标状态时(已在归档目录 / 已在活跃目录),结果落入alreadyArchived/alreadyActive桶,不计入errors[],对调用方而言是成功。
  3. id 规范化后再去重。批量入口先sessionIds.map(normalizeSessionIdForLookup)Set去重(见 session-archive.ts),保证不同大小写写法不会重复执行。
  4. 冲突处理。当会话同时出现在 active 与 archived 目录时,默认拒绝并提示以resolveConflicts: true重试(见 sessionLocationError),显式开启后冲突结果会记入resolvedConflicts桶。
  5. 每次批量操作后写 stderr 审计行logSessionArchiveResult输出requested/archived(alreadyArchived)/notFound/errors的完整计数与 id 列表(见 session-archive.ts),便于运维侧核对前端呈现与后端实际变更是否一致。

WebShell 侧的写操作入口在 WebShellSidebar.tsx,分别调用archiveSessionsData([sessionId])unarchiveSessionsData([sessionId]);操作完成后触发对主工作区与会话目录的重查,即文档所说的 "always reconciles ... after the operation settles"。

五、daemon 侧的执行安全:协调锁与写入租约

归档/取消归档最终通过 SessionArchiveCoordinator 的互斥锁串行化到同一会话的并发维护操作:

  • runExclusiveMany对批内所有(规范化后的)session id 加互斥标记,若某 id 正处于排他迁移中则抛SessionArchivingError
  • 锁键经过normalizeSessionIdForLookup规范化——源码注释说明这是为了防止大小写不敏感文件系统中,不同拼写的调用者 id 绕过锁、在 restore 进行中误删 transcript 文件(session-archive.ts);
  • sealMaintenanceAndWait支持 daemon drain 阶段封存维护操作,封存后新请求抛DaemonDrainingError
  • 实际的存储变更在runWithDaemonWriterLease内完成:先获取 daemon 写入租约(processKind: 'daemon'takeoverPolicy: 'certified'),执行变更前用assertOwnedAndUnchanged断言存储未被外部修改,变更后再更新关联的 scheduled task 生命周期(归档会disableTasksForSessions,取消归档会enableTasksForSessions,见 updateScheduledTaskForMaintenance)。

这套机制保证了文档承诺的"删除行为不变":archive / unarchive / delete 共用同一套协调与租约原语,互不干扰。

六、验证矩阵:文档声明的测试覆盖

文档 Verification 一节声明了两层回归:

WebShell 层覆盖:成功与部分失败响应、能力缺失、非信任工作区、幂等结果、equal-id(同 id 不同工作区)场景下的 current / busy 状态隔离、以及操作后对账。对应的测试文件包括 SessionOverviewPanel.test.tsx、WebShellSidebar.collapse-persist.test.tsx、WebShellSidebar.workspace-removal.test.tsx 与 useOtherWorkspaceSessions.test.tsx。

daemon 层回归:归档并取消归档一个与主工作区 id 相同的 secondary 会话,同时断言主工作区的会话文件与 bridge 状态保持未变。daemon 侧归档逻辑的单测见 session-archive.test.ts,多工作区路由行为见 multi-workspace-sessions.test.ts 与 qwen-serve-routes.test.ts。

七、小结

这次加固可以用三句话概括其设计契约:

  1. 门控前置:没有session_archive(以及 secondary 工作区所需的workspace_qualified_rest_core+ 受信任运行时),归档 UI 不出现、archived 目录不查询;secondary 行仅开放 Archive,其余操作维持 load-only。
  2. 身份隔离:所有前端会话状态以(workspaceCwd, sessionId)为键,同名会话跨工作区互不可见。
  3. 失败可感知、状态最终一致:HTTP 200 中的errors[]逐条呈现,alreadyArchived/alreadyActive幂等成功,操作落定后强制对账所有相关目录。

对二次开发者的启示在于:多租户/多作用区的写操作 UI,能力位应同时作为"数据请求开关"而非仅"按钮开关";批量接口应以逐目标结果桶(changed / already / notFound / errors)为契约核心,客户端对账逻辑与结果桶一一对应,才能既容忍部分失败又不丢失状态一致性。

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

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

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

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

立即咨询