qwen-code Daemon 归档会话导出:Workspace-Qualified Archived Session Export 协议与实现解析
发布时间:2026/9/13 9:47:59来源:尧图网络
qwen-code Daemon 归档会话导出Workspace-Qualified Archived Session Export 协议与实现解析【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读在 qwen-code 的守护进程daemon架构中活跃持久化会话active persisted session可以直接从指定已注册工作区导出但归档会话archived transcript此前必须先移动回活跃存储才能访问。本文围绕设计文档 daemon-archived-session-export.md 展开深入讲解新增的只读归档会话导出能力协议路由GET /workspaces/:workspace/session/:id/archive/export、无条件能力workspace_archived_session_export、SDK 方法WorkspaceDaemonClient.exportArchivedSession及其底层实现。读完本文你将掌握如何通过 REST 协议导出归档会话、理解其与活跃导出的差异、错误语义、并发租约约束与 256 MiB 大小限制以及它在源码中的完整调用链。背景与动机为什么归档会话不能直接导出在引入本特性之前daemon 只支持导出活跃持久化会话导出请求必须选中一个已注册工作区registered workspace并解析到该工作区的运行时runtime归档会话位于chats/archive/id.jsonl被视为不可访问状态若想导出必须先执行 unarchive 操作将其移回活跃存储这会破坏归档状态机的语义归档本意是让会话退出活跃视图、释放活跃存储占用为导出而临时反转状态既繁琐又有副作用例如可能触发状态迁移竞争、影响其他读取者。本设计的核心目标是在不改变活跃导出行为、不触碰归档状态机的前提下为归档会话提供一条只读导出通道。设计文档明确列出了新协议的三要素路由GET /workspaces/:workspace/session/:id/archive/export?formathtml|md|json|jsonl能力无条件unconditional宣告的workspace_archived_session_exportSDK 方法WorkspaceDaemonClient.exportArchivedSession其中最关键的设计约束是归档导出路由与能力必须与活跃导出保持独立。这样做的原因在 capabilities.ts 的源码注释中写得很清楚如果复用同一个能力标签老版本 daemon 可能忽略归档意图ignore archive intent返回同 id 的活跃 transcript造成数据混淆。// Workspace-qualified full session export from archived persisted storage. // This remains independent from active export so older daemons cannot ignore // archive intent and return an active transcript with the same session id. workspace_archived_session_export: { since: v1 },对应的活跃导出能力是workspace_session_export{ since: v1 }两者从 v1 起即同时存在、互不替代。协议契约选择器解析、信任检查与错误语义选择器解析与信任前置归档导出的选择器selector解析规则与活跃导出一致优先按精确的已注册工作区 id解析其次按URL 编码的规范化绝对 cwdcanonical absolute cwd解析被选中的运行时runtime必须处于**受信任trusted**状态。设计文档特别强调选择器解析与信任检查先于会话与格式校验。也就是说即使 session id 或 format 参数非法只要工作区选择器不可解析或运行时不受信任就会先被拒绝。这是先定边界、再查数据的安全顺序防止未信任运行时被当作数据源探测。在 routes/session.ts 中归档导出路由通过handleSessionExport处理并传入archiveState: archived与resolveQualifiedSessionRuntime(..., archived)app.get( /workspaces/:workspace/session/:id/archive/export, async (req, res) { const route GET /workspaces/:workspace/session/:id/archive/export; await handleSessionExport(req, res, { route, resolveRuntime: (sessionId) resolveQualifiedSessionRuntime( req, res, route, [sessionId], archived, ), workspaceQualified: true, archiveState: archived, }); }, );对比同文件中的活跃导出路由/workspaces/:workspace/session/:id/export见 routes/session.ts两者共用handleSessionExport处理器与resolveQualifiedSessionRuntime差异仅在于archiveState参数这正是复用现有导出收集器、格式化器与响应头设计的落地体现。数据源边界绝不越界查找归档导出只允许读取被选中工作区自己的chats/archive/id.jsonl。设计文档明确列出该路由不会做以下任何一件事不扫描活跃存储或其他工作区不回退到 primary 工作区不解析 live owner、不调用 bridge、不启动 ACP、不附加 client不加载 settings。也就是说归档导出是一条纯粹的本地只读文件投影路径完全绕开运行时生命周期管理。状态相关错误语义由于归档与活跃可能共存或处于迁移中路由对会话状态做了精确区分返回以下 HTTP 错误HTTP 状态错误码触发条件409session_not_archived会话仅存在于活跃存储active-only404session_not_found活跃与归档均不存在409session_conflict活跃与归档文件同时存在409session_archiving会话正处于归档/反归档状态迁移中这四类语义让客户端能根据错误码做出精确决策例如session_not_archived应引导客户端改用活跃导出路由session_archiving则应稍后重试。核心消费面SessionService.loadArchivedSession唯一的新核心入口归档导出的核心数据面只有一处新增SessionService.loadArchivedSession位于 sessionService.ts。源码注释明确说明其定位/** * Reads an archived session without changing its archive state. * Daemon load/resume paths must continue to use {link loadSession}. */ async loadArchivedSession( sessionId: string, options: { maxBytes: number }, ): PromiseResumedSessionData | undefined {它的工作流程分三步会话 id 合法性校验用SESSION_FILE_PATTERN正则校验${sessionId}.jsonl不合法直接返回undefined不抛错、不落盘路径解析与大小检查通过getSessionFilePath(sessionId, archived)定位归档文件fs.statSync拿到文件大小后若stats.size options.maxBytes则抛出SessionTranscriptTooLargeError否则继续只有ENOENT文件不存在被吞掉并返回undefined其他 I/O 错误照常抛出委托重建调用私有方法loadSessionFromState(sessionId, archived, stats)完成 transcript 重建。复用活跃加载的私有重建逻辑loadSessionFromStatesessionService.ts是活跃加载loadSession与归档加载共用的私有重建逻辑包含readAllRecords读取 JSONL 记录并跟踪sourceReadComplete首条记录校验sessionBelongsToCurrentProject会话必须属于当前项目防止跨工作区串读reconstructHistory重建线性历史并检测不可恢复的gaps缺失父节点等有 gap 时输出 warn 日志构造ConversationRecordsessionId、projectHash、startTime、lastUpdated 取自文件 mtime通过SessionFileHistoryAccumulator提取file_history_snapshot供/rewind使用通过includeActiveSideArtifactRecords与rebuildSessionArtifactSnapshot重建 artifact 快照附带lastCompletedUuid、sourcesUnavailable、historyGaps等字段。关键点在于现有 load/resume 调用方仍只走活跃路径loadArchivedSession是归档专属入口二者互不干扰——活跃会话的恢复语义例如 resume 到活跃分支不会因归档读取而改变。256 MiB 转录索引上限归档导出与活跃导出的一个显著差异是大小上限。设计文档指出Before reconstruction, the archived-only loader enforces the existing 256 MiB transcript indexing limit and returns413 transcript_too_largeabove it. Active export retains its shipped no-cap contract.即归档加载器在重建之前强制实施既有的256 MiBtranscript 索引限制超过即返回413 transcript_too_large而活跃导出保持其发布的无上限no-cap契约不变。实现上stats.size options.maxBytes的检查发生在任何记录读取与重建之前因此超大归档文件不会触发昂贵的 materialization物化过程。对应测试位于 sessionService.test.ts覆盖了合法加载、../outside这类路径穿越防护、以及超限场景。并发与租约SessionArchiveCoordinator 的双向互斥导出操作位置检查 transcript 重建 格式化全程持有既有的共享租约shared leaseSessionArchiveCoordinator。设计文档对并发语义的描述是归档、反归档、删除archive、unarchive、delete保持排他租约exclusive lease因此一次状态迁移要么在导出开始前发生并使导出被拒绝返回状态相关错误要么在导出持有的共享租约释放之后才开始不存在导出读到半迁移状态的窗口。此外协调器在跨工作区维度上保守地按 session id 加锁remains conservatively keyed by session id across workspaces——也就是说即使两个不同工作区出现同 id 会话锁仍按 id 串行化宁可保守也不引入跨工作区锁序复杂度。这一点与同 id 工作区隔离测试same-id workspace isolation相呼应。SDK 调用WorkspaceDaemonClient.exportArchivedSession方法签名与默认格式TypeScript SDK 在 DaemonClient.ts 中提供WorkspaceDaemonClient.exportArchivedSession/** Export an archived persisted session from this registered workspace. */ exportArchivedSession( sessionId: string, opts: { format?: DaemonSessionExportFormat; clientId?: string; } {}, ): PromiseDaemonSessionExportResult { return this.client.sessionExportRequest( /workspaces/${this.workspaceSelector}/session/${urlEncode(sessionId)}/archive/export, GET /workspaces/:workspace/session/:id/archive/export, opts, ); }与活跃导出exportSessionDaemonClient.ts相比二者结构完全一致仅 URL 路径多了/archive段。传输细节底层sessionExportRequestDaemonClient.ts的关键行为format默认值为html显式传入 format 时以查询参数?formatformat附加走原生 REST 传输rest模式并使用fetchWithTimeout响应非 2xx 时通过failOnError抛出带路由标签的 HTTP 错误解析content-type与content-disposition中的filename...取不到时回退为export.format返回{ content, filename, mimeType, format }四元组DaemonSessionExportResult。支持格式与活跃导出完全一致html | md | json | jsonl。兼容性老 daemon 上的行为设计文档明确活跃导出路由、workspace_session_export能力、legacy primary 导出、归档变更操作与持久化布局全部保持不变。对于直接调用 SDK 的客户端如果目标 daemon 是未实现本路由的老版本exportArchivedSession会收到正常的 HTTP 错误而不是静默返回活跃 transcript这正是能力与路由独立设计带来的兼容性保证。SDK 单测见 DaemonClient.test.ts覆盖了workspaceById/ cwd 选择器调用归档导出的路径。验证矩阵测试覆盖了哪些行为设计文档列出的测试覆盖范围可以作为完整的验收清单能力宣告capability advertisementworkspace_archived_session_export出现在能力列表中id 与 cwd 两种选择器注册工作区 id 与 URL 编码的规范化 cwd 均可选中工作区全部四种格式html / md / json / jsonl 导出一致attachment 元数据附件的元数据content-disposition filename、mime 等正确传递active / missing / conflict / transition 四态对应409 session_not_archived、404 session_not_found、409 session_conflict、409 session_archiving信任优先级选择器与信任检查先于会话/格式校验同 id 工作区隔离不同工作区同 id 会话互不串读无 bridge 活动导出过程不会触发 bridge / ACP / client 附加等副作用两种锁方向排他迁移先于共享导出拒绝与共享导出先于排他迁移等待两条路径核心归档重建loadArchivedSession的 transcript 重建正确性telemetry 归属导出事件的遥测属性正确归因原生 REST SDK 传输SDK 直连 daemon REST 端点的端到端行为大小测试恰好等于归档上限的文件被接受稀疏文件sparse file超出一字节即在 transcript 物化之前被拒绝。与其他文档的衔接协议层面的完整路由表可对照 qwen-serve-protocol.md能力版本化capability versioning的设计约束见 11-capabilities-versioning.mdSDK daemon client 的整体用法见 13-sdk-daemon-client.md活跃导出的用户侧说明见 qwen-serve.md。小结归档会话导出特性在 qwen-code daemon 中是一个小而完整的协议扩展一条独立路由 一个无条件能力 一个 SDK 方法背后是核心层loadArchivedSession对归档路径的只读重建、256 MiB 上限的前置校验、SessionArchiveCoordinator的共享/排他租约互斥以及覆盖状态机四态与安全边界的完整测试矩阵。它让归档会话在不改变归档状态、不启动任何运行时副作用的前提下获得与活跃导出一致的四种格式输出同时通过路由与能力的独立设计确保了与老 daemon 的兼容性。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网