Qwen Code Channel Named Sessions(Part 2):为 Daemon 托管 Channel 构建多任务命名会话目录 Qwen Code Channel Named SessionsPart 2为 Daemon 托管 Channel 构建多任务命名会话目录【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读本文基于 Qwen Code 仓库的设计文档 docs/design/channel-named-sessions-part2.md系统讲解 Channel 命名会话Named Sessions系列设计的第一阶段落地如何在 daemon 托管的 Channel 中为一个用户在同一个聊天里维护最多 8 个有名字、可持久化的任务会话。读完本文你将掌握multiSession: true的启用前提、命名任务目录registry的数据结构与持久化细节、/sessions与/session系列命令的完整语义、busy 状态判定与失败顺序failure ordering以及 Part 2 与后续 Part 3/Part 4 的功能边界划分。文中同时给出仓库源码与配置文件的真实相对路径便于你继续深入阅读实现。Part 2 的目标与功能边界Part 2 是Channel 命名会话系列的第一个落地阶段它的目标可以概括为一句话为 daemon 托管的 Channel 增加第一个安全、可选的命名会话目录named-session catalog。在sessionScope: user且multiSession: true的前提下同一个发送者可以在同一个聊天中保留最多8 个命名任务选择一个空闲任务作为当前选中任务关闭一个任务而不删除它的 transcript之后重新打开reopen同一个daemon session恢复之前的精确会话。目录catalog的隔离维度是通道实例channel instance× 聊天chat× 发送者sender即不同 channel、不同聊天、不同用户之间的命名任务互不可见。Part 2 刻意保持同一时刻只选中一个任务的策略当当前任务仍在运行或存在挂起的权限请求pending permission request时切换任务、创建并选中新任务、或关闭任务都会被拒绝。这一运行中禁止切换的守卫busy guard将在 Part 3 完成命名投递、取消和权限关联之后移除Part 4 才会加入 worktree 创建能力。Reviewed boundaries运行期与兼容性约束Part 2 对功能可用范围做了严格的 fail-closed 界定场景Part 2 行为daemon 托管的 Channel worker sessionScope: user唯一支持 named-session 的模式独立运行的qwen channel start不支持配置了 webhook 的 Channel不支持channel 或 group 的groupHistoryLimit非零不支持Channel loop不支持含循环创建命令与 loop MCP 工具multiSession缺省或为 false行为与旧版本完全一致若某个 Channel 已经拥有一个启用的持久化 loopdaemon worker 会拒绝启动该多会话 Channel且不会向该 Channel 的会话暴露 loop 创建命令或 loop MCP 工具。在架构上Part 2 采用双指针设计legacy 路由文件route file保持原样继续持有普通分发normal dispatch所使用的选中会话新增的注册表registry存放在 daemon 的 workspace 与 channel 作用域状态目录之下注册表是目录的权威catalog authority路由只是兼容性指针compatibility pointer。另外Channel memory 仍然保持 chat 作用域它既不会被复制到每个任务也不会被当作所有权索引。Ownership 与命名规则一个 owner所有者是精确三元组(channelName, chatId, senderId)Thread ID 不参与所有权因为sessionScope: user本身就已经在同一个聊天内的多个 thread 之间路由每个操作都从经过认证的入站信封authenticated inbound envelope出发绝不接受命令文本中携带的 owner 或 daemon session ID兼容性路由不是所有权权威legacy 收养adoption会对照精确 owner 检查路由中存储的目标本地选中会话命令则通过目录解析而不是基于分隔符的路由键。命名规则在源码 packages/channels/base/src/named-session-manager.ts 中有直接对应const REGISTRY_VERSION 1; const MAX_OPEN_TASKS 8; const TASK_NAME_PATTERN /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;即任务名匹配[A-Za-z0-9][A-Za-z0-9_-]{0,31}132 位 ASCII 字母、数字、下划线或连字符且必须以字母或数字开头并在一个 owner 的打开与关闭任务全集内大小写不敏感地唯一。命令只按名字解析绝不接受 session ID。每个 owner 最多同时有 8 个打开任务关闭任务不占用打开任务配额。Registry版本 1 目录的数据结构与持久化数据结构版本 1 registry 是一个 owner 记录数组每个 owner 记录包含精确身份(channelName, chatId, senderId)可选的当前选中任务名activeTaskName若干任务记录每条任务记录包含字段含义name/sessionId显示名与精确 daemon session IDcwd/isolation工作目录与隔离模式shared/worktreestatusopen或closedcreatedAt/updatedAt/lastSelectedAt创建、更新与最近选中时间戳target原始的 Channel 投递目标这一结构在 packages/channels/base/src/named-session-manager.ts 中体现为StoredTask、StoredOwner、StoredRegistry三个接口例如interface StoredRegistry { version: 1; workspaceCwd: string; owners: StoredOwner[]; }为什么用数组而不是对象键设计文档明确使用数组而非攻击者可控制的对象键可以避免原型键prototype-key与分隔符冲突delimiter-collision两类安全隐患。这一点在 registry 校验逻辑isRegistry/isOwner/isTasknamed-session-manager.ts中也有体现owner 键与 session ID 都通过Set去重校验重复即判定 registry 无效。原子持久化owner 的变更在内存中按 owner 串行化per-owner serialization任务时间戳单调递增——即使在同一时钟 tick 内发生多次选中时间戳也不会相等。持久化采用唯一临时文件 原子 renameprivate writeRegistry(registry: StoredRegistry): void { const dir dirname(this.filePath); const tempPath ${this.filePath}.${process.pid}-${randomUUID()}.tmp; // mkdirSync(dir, { recursive: true, mode: 0o700 }) // writeFileSync(tempPath, JSON.stringify(registry, null, 2), { encoding: utf8, mode: 0o600 }) // renameSync(tempPath, this.filePath) }见 named-session-manager.ts。commitOwner的顺序是先构造并校验下一版 registry、构建对应的派生索引再原子写入最后才把内存中的registry与taskBySessionId索引一起发布named-session-manager.ts写入失败时两者都不会发布。对异常文件的处理策略畸形或不支持的 registry导致该已启用 Channel无法启动而不是静默替换所有权目录结构合法但来自旧 channel 工作目录的目录归档为 stale 并重置指向当前工作目录之外的 legacy 路由被遗忘forgotten而不是被收养。路由原语Router Primitives与 busy 状态为什么不用普通的 resolve()命名会话操作不会走 router 普通的懒加载resolve()路径。原因在于该路径在恢复的 legacy 路由无法加载时会主动创建替代会话这与精确任务选择的语义不兼容。router 因此暴露了四个窄化的 managed-session 原语create创建一个活的 daemon session但不改变当前选中路由load精确加载并校验一个会话不创建替代bind把该校验过的会话绑定为选中的兼容路由同时为其他活着的命名会话保留投递元数据detach/forget分离并遗忘一个已关闭会话不删除 daemon 数据。当 daemon bridge 崩溃并被替换时所有 managed client 会被标记为 dormant选中兼容路由由普通恢复流程恢复一次非活跃任务只在之后被选中时才按精确 ID 重新挂接reattach。如果精确加载失败registry 选中状态与兼容路由都不会改变如果创建 daemon session 之后 registry 持久化失败新 client 会被分离任务不会暴露给聊天。Busy 状态判定ChannelBase拥有 Part 2 的 busy 判定源码见 packages/channels/base/src/ChannelBase.tsNamedSessionManager构造时通过isBusy回调注入一个 session 在以下任一情况下被视为 busy仍有一个活跃的 Channel turnactive Channel turn存在挂起的权限请求pending permission requestdaemon bridge 报告存在活跃 promptactive prompt。取消期间以 Channel turn 为权威因为 daemon bridge 可能在 turn 的finally块完成之前就清除了自己的 active 标志。busy 守卫作用于/session new、/session use、/session close这三个会离开当前选中任务的操作。而既有的/clear、/new、/reset仍然是显式的破坏性重置它们只在同一个任务名下替换该选中任务的 daemon session并对被退役的 session 走既有的有界取消与清理路径。Part 2 命令全集Part 2 引入的命令语义如下与 ChannelBase.ts 中的handleNamedSessionsCommand/handleNamedSessionCommand实现一一对应命令语义/sessions列出当前聊天中该发送者拥有的打开任务/sessions all额外列出关闭任务/session current报告当前选中任务不暴露 session ID/session new name创建共享工作目录shared任务并选中它/session use name选中一个打开任务或通过加载其精确持久化会话重新打开一个关闭任务/session close name拒绝 busy 任务分离活 client保留目录记录与 transcript。关闭选中任务时自动选中最近选中过的剩余打开任务若没有剩余普通消息会引导用户创建或重新打开任务/session new name --worktree识别但拒绝返回一条指向 Part 4 的可操作提示/session cancel [name]识别但推迟到 Part 3。Telegram 既有/cancel继续取消选中任务其他适配器要求任务结束后才能切换命令实际输出的样式来自实现Open tasks: * feature-a (open, shared) - feature-b (closed, shared)Current task: feature-a (shared)Created and selected task feature-a (shared workspace).Selected task feature-b (shared workspace).Closed task feature-a. Selected feature-b.第一个目录操作或第一条普通消息会把既有 legacy 路由收养为default——不加载替代、不改变其 transcript若没有路由存在第一条普通消息会在分发前创建并持久化default。失败顺序Failure OrderingCreate / Reset / Select / ClosePart 2 为每个操作定义了严格的失败顺序保证任何一步失败都不会破坏既有状态Create / Reset先创建精确 daemon session → 再持久化所有权 → 最后绑定路由。持久化失败会分离新创建的 client 并保留旧选中。Select / Reopen先精确加载目标 → 再持久化新选中 → 最后绑定路由。加载失败保持旧选中不变持久化失败会分离仅为本次失败操作而加载的目标。Close先校验 idle 状态并加载任何替代选中然后才修改目录 → 提交 closed 状态 → 变更或移除选中路由 → 分离被关闭任务。分离失败会被暴露任务在可能的情况下恢复到 open/selected 状态绝不删除 transcript 或 worktree。在实现中close的失败回滚会在 detach 失败时用commitOwner恢复之前的 owner见 named-session-manager.ts。验证与测试覆盖设计文档列出的验证点覆盖了配置门禁configuration gatesowner 隔离大小写不敏感的名字唯一性8 个打开任务上限legacy 收养原子持久化create 回滚精确加载失败idle 守卫含取消收尾 cancel wind-downclose/reopen选中任务 reset重启恢复命令输出不含 session IDlegacy 行为不变。仓库中对应的单元测试位于 packages/channels/base/src/named-session-manager.test.tsdaemon-worker 测试验证运行时专属接线。daemon-backed E2E 计划覆盖同一群组中的两个用户与重启恢复两个场景并发运行任务切换与 worktree 创建被明确保留为后续部分Part 3 / Part 4的用例。与 Part 3 / Part 4 的衔接Part 2 是系列设计的安全第一步它为后续能力铺好了地基Part 3并发控制允许一个 owner 的多个命名任务并发运行并自由切换加入任务级取消/session cancel [name]、权限关联permission correlation与投递来源标签source label形如[feature-a]/[Alice · feature-a]。Part 3 不改变 version 1 registry、daemon session 协议与模型 transcript其详细设计见 docs/design/channel-named-sessions-part3.md 与已实现的 docs/design/channel-named-sessions-part3a.md。Part 4worktree加入 worktree 创建与文件系统隔离届时/session new name --worktree才会真正可用。配置示例与使用前提命名会话是可选功能通过 channel 配置中的multiSession: true开启配置说明见 docs/users/features/channels/overview.md{ channels: { my-channel: { type: telegram, sessionScope: user, multiSession: true } } }启用前提总结必须是daemon 托管的 Channel独立qwen channel start不支持sessionScope必须为user实现中违反此条件会直接抛错requires sessionScope user when multiSession is enabled见 ChannelBase.ts不能配置 webhookchannel/group 的groupHistoryLimit必须为零不能存在已启用的 Channel loop。sessionScope的完整取值user默认 /chat_thread/singlelegacythread仅兼容已有配置同样记录在 docs/users/features/channels/overview.md。小结Part 2 以安全、可选、精确为原则为 daemon 托管的 Channel 落地了命名会话目录精确的三元组所有权、132 位 ASCII 任务名与大小写不敏感唯一性、8 个打开任务上限、数组化 registry 与原子 rename 持久化、窄化 router 原语、权威性 busy 判定以及严格的失败顺序。它刻意保留单选中 禁止 busy 切换的保守策略把并发切换Part 3与 worktree 隔离Part 4留到安全前提就绪之后。对于希望在 IM 渠道中为同一用户维护多个并行编码任务的开发者来说这套设计提供了可验证、可恢复、与 transcript 解耦的完整方案相关实现可进一步阅读 named-session-manager.ts、ChannelBase.ts 与 named-session-manager.test.ts。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考