
qwen-code Daemon 会话生命周期与身份机制完全指南从创建、附加到恢复、终止【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-codeqwen-code 的 daemon 进程qwen serve把一次逻辑对话建模为一个会话session并由 ACP 桥接层统一管理其创建、附加、加载、恢复、关闭、死亡与回收。本文以docs/developers/daemon/08-session-lifecycle.md为骨架结合packages/acp-bridge、packages/cli/src/serve与packages/sdk-typescript的源码实现完整讲解会话状态机、X-Qwen-Client-Id客户端身份规则、心跳与断线回收守卫、全部生命周期端点及可调配置帮助你掌握如何在多客户端并发、重启恢复、worktree 隔离等场景下正确使用 daemon 的会话能力。会话Session与客户端Client两个核心概念daemon 中的session是一条逻辑对话严格绑定到唯一一个 ACPsessionId。桥接层为每个会话维护一个SessionEntry见 03-acp-bridge.md把 ACP 子进程连接与 HTTP 侧的簿记状态耦合在一起包括prompt FIFO 队列与 model-change FIFO 队列事件总线EventBus待处理的权限请求pending permissions已附加的客户端attached clients心跳状态heartbeats恢复状态restore state终端帧墓碑terminal-frame tombstones。而 daemonclient则由 HTTP 请求头X-Qwen-Client-Id标识——这是一个由 daemon 校验的、不透明的字符串由调用方在请求上盖章。桥接层跟踪哪些客户端附加到了哪些会话并利用发起者originator的 client id 驱动designated权限策略、审计追踪与事件归属。daemon 的核心职责可归纳为铸造mint、附加attach、恢复restore、回收reap会话校验并拒绝非法X-Qwen-Client-Id跟踪每个会话的多个附加客户端clientIds: Mapstring, count与attachCount在出站事件上盖章originatorClientId运行心跳供仪表盘判断客户端是否在线暴露可运维设置的会话元数据displayName通过PATCH /session/:id/metadata驱动session_died、session_closed、client_evicted、stream_error等终端帧。架构生命周期涉及的四个关键类型关注点源码位置说明SessionEntrypackages/acp-bridge/src/bridge.ts#L1123每个会话的内部结构体完整字段清单见下文State LifecycleBridgeSession公开类型packages/acp-bridge/src/bridgeTypes.ts#L190{ sessionId, workspaceCwd, currentCwd?, attached, clientId?, createdAt?, hasActivePrompt?, ... }返回给 HTTP 处理器BridgeSessionStatepackages/acp-bridge/src/bridgeTypes.ts#L520LoadSessionResponse \| ResumeSessionResponse缓存在 entry 的restoreState字段上DaemonSessionSDK 侧packages/sdk-typescript/src/daemon/types.ts#L1196{ sessionId, workspaceCwd, currentCwd?, attached, clientId?, createdAt?, ... }与BridgeSession保持字段同步另外两个生命周期关键实现点client-id 校验位于 packages/acp-bridge/src/bridge.ts 的spawnOrAttach附近非法时抛出InvalidClientIdError。会话断线回收器disconnect-reaper位于 packages/cli/src/serve/server.ts使用attachCount与spawnOwnerWantedKill跟踪 spawn 所有者的断线。状态机一次会话的完整流转Attach 与 Spawn 的区别在默认的sessionScope: single下桥接层的defaultEntry被所有接入的客户端共享当POST /session到达且defaultEntry已存在时返回attached: true不会重新 spawn 一个新的 ACP 子进程桥接层同步递增attachCount并把调用方的X-Qwen-Client-Id注册进clientIds。源码级佐证bridge.ts#L9842-L9866在effectiveScope single且defaultEntry存在时代码在任何 await 之前同步执行existing.attachCount并调用registerClient(existing, req.clientId)。注释明确说明这个同步递增是为了让 spawn 所有者的断线回收器requireZeroAttaches: true在桥接层让出yield时也能看到这次附加从而避免快速断线的 spawn 所有者每隔一次重连就摧毁一个健康会话。在sessionScope: thread下每个线程可以铸造成独立的会话调用方仍然受maxSessions上限约束。身份IdentityX-Qwen-Client-Id 的设计与校验X-Qwen-Client-Id可选但强烈推荐。daemon 不会替调用方生成该 id——客户端自己选定并在后续请求中复用它这样 daemon 才能归因投票votes、审计事件并检测重连。命名建议每个独立的控制器controller应使用一个不同且稳定的 id。Web Shell 出于兼容性保留历史webui_前缀。仅当一个宿主host与内嵌的 Web Shell 有意作为同一逻辑控制器时才应共享同一个 id一旦共享daemon 日志将无法区分是哪一方发起的请求。校验规则来自文档与源码一致字符集[A-Za-z0-9._:-]长度1–128超出该集合抛出InvalidClientIdErrorHTTP 400。源码侧的登记与校验机制registerClientbridge.ts#L4507若请求携带的 id 已在该会话的clientIds中则引用计数 1否则铸造一个新 idcreateClientId()生成client_${randomUUID()}bridge.ts#L4505。resolveTrustedClientIdbridge.ts#L4583会话级请求若携带clientId必须已登记在该会话的clientIds集合中否则抛出InvalidClientIdError。该错误类定义在 packages/acp-bridge/src/bridgeErrors.ts#L327错误消息为Client id ... is not registered for session ...。出站事件盖章originatorClientId的三个条件缺一不可触发该事件的请求携带了X-Qwen-Client-Id且该 id 当前已登记在会话的clientIds集合中且会话存在activePromptOriginatorClientId内联的sessionUpdate与permission_request会继承当前活动 prompt 的发起者。匿名调用方不带X-Qwen-Client-Id与权限策略的兼容矩阵first-responder策略匿名调用方可以正常投票designated策略拒绝匿名投票返回permission_forbidden{ reason: designated_mismatch }consensus策略同样拒绝原因相同——匿名者不在发起时刻的votersAtIssue快照中local-only策略唯一接受匿名回环loopback投票者的策略。工作流详解创建或附加Create or attachspawnOrAttachbridge.ts#L9780还会在req.sessionId提供时强制使用thread作用域并支持modelServiceId、approvalMode、worktree、branch、parentSessionId、sourceType/sourceId等扩展字段见 bridgeTypes.ts#L134 的BridgeSpawnRequest。附加时若携带modelServiceId且当前会话运行的是不同模型桥接层会调用setSessionModel对齐模型并发布model_switched事件让所有附加客户端可见。加载与恢复Load / resumePOST /session/:id/load恢复持久化会话并返回当前有界的回放快照窗口session/load通知或响应式回放在响应返回前就已播种。POST /session/:id/resume恢复但不带回放底层为connection.unstable_resumeSession对外以稳定的session_resumedaemon 能力暴露unstable_session_resume保留为废弃别名。两者共同的行为在 channel 上使用每会话的pendingRestoreIds集合使并发恢复调用合并并发者收到RestoreInProgressError在 entry 上缓存restoreState让迟到的附加者拿到与最初恢复者相同的载荷。Part 4A worktree 会话的完整性门控恢复这类持久化会话时sidecar 必须显式标识请求的 workspace 根checkout 必须规范地包含在对应的.qwen/worktrees/目录之下且其 marker 必须是包含确切恢复会话 id 的单链接常规文件。daemon 仅在上述检查通过后才迁移空闲的已恢复子进程活动子进程仅在报告 cwd 已等于 worktree 时才被接受。worktree 所有权转移路由POST /session/:id/worktree-reset能力标签session_worktree_reset_v1用于 Channel 任务重置daemon 在根 workspace 生成一个线程作用域的新替代会话将其迁移进已校验的 checkout链接 sidecar 对旧会话先写supersededBy再写替代会话的supersedes在每 checkout 路由锁与准入屏障下把 marker 翻转为替代会话随后切断被取代会话的客户端注册与内存 worktree 关联。若被取代会话的子进程仍持有后台工作而存活屏障保持武装并在响应中如实报告supersededSessionLive: true被准入屏障拒绝的写者收到409 worktree_reset_active。完整失败分类含409 worktree_session_superseded、409 worktree_reset_interrupted、409 worktree_marker_missing及各自的修复路径记录在 qwen-serve-protocol.md 的路由目录中。心跳HeartbeatPOST /session/:id/heartbeat无条件更新sessionLastSeenAt若请求携带已登记的X-Qwen-Client-Id则clientLastSeenAt.set(clientId, Date.now())也会更新。v1不实现按客户端驱逐per-client eviction撤销revocation策略计划在 F 系列 Wave 5 落地。当前心跳仅提供可观测性。源码佐证recordHeartbeat在 bridge.ts#L12403-L12405 同时更新两个时间戳测试 bridge.test.ts#L5782-L5878 验证了未登记的伪造 id 不会更新 lastSeenAt以及客户端注销unregisterClient会同步删除其clientLastSeenAt条目避免长期运行的 daemon 累积过期时间戳。元数据MetadataPATCH /session/:id/metadata接受{displayName?}。校验规则最大长度MAX_DISPLAY_NAME_LENGTH 256bridge.ts#L1483不得包含控制字符hasControlCharacterbridge.ts#L2473拒绝码点 ≤ 0x1f 或 0x7f 的字符违规时抛出InvalidSessionMetadataErrorHTTP 400具体校验逻辑见 bridge.ts#L12027-L12035。更新成功后向所有订阅者广播session_metadata_updated事件。终止Termination与终端帧终端帧触发条件session_closedDELETE /session/:idclient_close或程序化关闭session_diedchannel.exited因任何原因触发崩溃、子进程被杀使用 OS 退出路径时携带exitCode?signalCode?client_evicted每个订阅者的事件总线队列溢出见 10-event-bus.md。不是会话级终止——只关闭该订阅者stream_errorSubscriberLimitExceededError或其他路由级流失败每条终止路径都会通过mediator.forgetSession(sessionId)把待处理的权限解析为{kind:cancelled, reason:session_closed}这是 ACP 的要求被取消的 prompt 必须以outcome.cancelled解析其未决的requestPermission。断线回收守卫Disconnect-reaper guard当 spawn 所有者的 HTTP 响应无法写入如握手中途 TCP reset时路由调用killSession({ requireZeroAttaches: true })。若此时已有其他客户端附加attachCount 0守卫短路会话继续存活。设置spawnOwnerWantedKill true记住这一意图以便后续某个detachClient()把attachCount带回 0 时完成延迟回收。这一机制的目的正是防止快速断线的 spawn 所有者每隔一次重连就摧毁一个健康会话。源码佐证spawnOwnerWantedKill字段定义在 bridge.ts#L1416注释BkwQP详细描述了该墓碑语义实际调用点位于packages/cli/src/serve/acp-http/dispatch.ts如 #L1135 与 #L2128-L2131 的requireZeroAttaches: true。生命周期关键字段SessionEntry 全景以下字段直接驱动会话生命周期完整结构体见 bridge.ts#L1123字段类型含义clientIdsMapstring, number已登记 client id → 登记引用计数attachCountnumber该 entry 上spawnOrAttach返回attached: true的次数activePromptOriginatorClientIdstring?当前运行中 prompt 的发起者restoreStateBridgeSessionState?缓存的 load/resume 响应保证迟到的附加者看到一致载荷spawnOwnerWantedKillboolean延迟回收墓碑见上文断线回收守卫sessionLastSeenAtnumber?任意客户端最近一次心跳epoch msclientLastSeenAtMapstring, number每个客户端的最近心跳pendingPermissionIdsSetstring当前待处理的 ACP requestIds——取消/关闭时用于将其解析为 cancelled此外attachRefs: Mapstring, numberbridge.ts#L1403是每 clientId 的附加引用账本detachClient只能通过释放该账本中的引用递减attachCountspawn 所有者与恢复发起者等 owner 型登记刻意不进入账本因此带 owner clientId 的 detach、重复/未知/匿名的 detach 都无法窃取其他附加者的计数。扩展会话端点以下端点扩展了基础生命周期面多数带对应能力标签非阻塞 Promptnon_blocking_promptPOST /session/:id/prompt现在返回 HTTP202及{ promptId, lastEventId }而非阻塞到 prompt 完成。实际结果通过 SSE 的turn_complete/turn_error到达promptId字段把事件与 202 响应关联起来。DaemonSessionClient.prompt()在存在活动事件订阅时自动走非阻塞路径并从 SSE 流透明匹配结果。会话回顾session_recapPOST /session/:id/recap请快速模型生成一行我上次进行到哪里摘要返回{ sessionId, recap: string | null }null表示历史过短或模型临时失败。该端点尽力而为best-effort。会话旁问session_btwPOST /session/:id/btw在不打断主对话流的前提下基于会话上下文问一个一次性问题。实现在缓存路径上使用runForkedAgent做单轮、无工具的 LLM 调用返回{ sessionId, answer: string | null }实现强制执行BTW_MAX_INPUT_LENGTH、跨会话泄漏防护与超时处理。Shell 命令执行POST /session/:id/shell直接在 daemon 宿主上执行 shell 命令不经过 LLM。输出经会话 SSE 总线以user_shell_command/user_shell_result事件流式返回命令与结果同时注入 LLM 对话历史。响应为{ exitCode, output, aborted }。对活动的次级 workspace 会话该单一 REST 路由会解析会话所有者并在其 runtime 的桥接层执行使命令从所属 workspace 的 cwd 启动。该路由不提供路径沙箱workspace 限定的 ACP 客户端可继续在所属 workspace 连接上使用_qwen/session/shell。会话回退Session RewindGET /session/:id/rewind/snapshots与POST /session/:id/rewind解析所属的活动 workspace runtime。持久化会话必须先 load 或 resume 才能回退。Rewind 截断对话历史并可选地恢复edit与write_file跟踪的文件不撤销shell 命令、Git、脚本或手工修改。文件恢复是尽力而为的因此响应可能在历史已移动后报告rewound: false与filesFailed[]。SDK 的 rewind 调用始终使用 owner-aware REST即使客户端其他时候走 ACP 传输因为该变更必须保留严格的 REST 认证。会话分离Session DetachPOST /session/:id/detach通过递减attachCount显式分离一个客户端其本身不关闭会话。若分离后没有其他附加或订阅者残留会话被回收。端点返回 204。批量会话删除POST /sessions/delete接受{ sessionIds: string[] }最多 100 个 id关闭桥接会话并删除活动或已归档的 transcript 文件。若同一 id 的活动与归档 JSONL 都存在硬删除会同时移除两者以清除冲突。它清理活动与归档 worktree sidecar但保留 file-history 快照、subagent transcript 与 runtime sidecar。使用Promise.allSettled保证韧性返回{ removed, notFound, errors }。会话归档Session ArchivePOST /sessions/archive把非活动会话 JSONL 从chats/移动到chats/archive/。若目标会话存活daemon 先进入每会话归档门并执行严格关闭要求 ACP 子进程 flushChatRecordingService关闭或 flush 失败时归档保留 JSONL 原样。POST /sessions/unarchive把归档 JSONL 移回chats/。这只是存储状态迁移客户端之后必须调用session/load或session/resume。归档会话对 load/resume 返回409 session_archived与归档迁移竞争的变更返回409 session_archiving。空、损坏与孤儿常规 transcript 文件即便无法作为对话加载也仍可参与这些生命周期操作所有权安全检查可能故意 fail-closed 并需要运维介入。密封握手证明之后被修改的文件抛出SessionTranscriptChangedError首条超过有界所有权读取窗口的 JSON 形状记录抛出SessionTranscriptIdentityUnavailableError。广告session_storage_conflict_repair能力时archive/unarchive 接受resolveConflicts: true归档保留归档副本取消归档保留活动副本不传该选项时冲突双方都不会被移动、删除或覆盖而是出现在批量errors数组中。Workspace 限定的生命周期路由现在使用 HTTP 200 批量信封而非旧的409 session_conflict。上下文用量session_context_usageGET /session/:id/context-usage返回结构化的上下文窗口用量?detailtrue返回按工具、记忆、技能细分的更细粒度用量。会话统计session_statsGET /session/:id/stats返回用量统计模型指标输入/输出 token、缓存读写、总成本、每工具调用次数与延迟、文件编辑次数、本会话内每技能调用次数。skills块仅反映本会话内的技能体加载与技能斜杠命令不是跨会话的活动聚合。会话任务session_tasksGET /session/:id/tasks返回代理任务、shell 任务、监控任务及其生命周期状态的背景任务快照。由其他子代理派生的代理条目携带可选的血统字段parentAgentId、parentName、depth客户端可据此把嵌套子代理渲染成树负载示例见 qwen-serve-protocol.md。session_monitor_tool_correlation能力额外保证监控条目携带toolUseId使客户端能把 transcript 工具调用与其任务详情关联。会话 LSP 状态session_lspGET /session/:id/lsp为 daemon 客户端返回净化后的每会话 LSP 状态启停状态、服务器总数聚合、不可用/初始化状态以及每服务器的name、status、languages、transport、command、error。禁用或不可用的 LSP 以 HTTP 200 状态数据表示而非传输错误。压缩回放Compacted ReplayPOST /session/:id/load现在返回可包含compactedReplay?: BridgeEvent[]、liveJournal?: BridgeEvent[]、lastEventId?: number的BridgeRestoredSession类型见 bridgeTypes.ts#L528。这些字段是 daemon 对存活会话的有界内存回放窗口不是完整 transcript API。默认窗口上限为每个存活会话 4 MiB--compacted-replay-max-bytes启动时拒绝非法上限硬上限 256 MiB。常量定义在 packages/acp-bridge/src/replayWindowLimits.ts#L7-L8CLI 校验必须是 [1, 268435456] 的正安全整数见 packages/cli/src/serve/fast-path.ts#L212-L223。compactedReplay由TurnBoundaryCompactionEngine产出在回合边界把连续的文本/思考块折叠、把工具调用序列折叠到最终状态、丢弃瞬时信号产出 O(turns) 的回放日志而非 O(tokens) 日志通常 25–30 倍缩减。当旧回放条目被挤出字节窗口时compactedReplay[0]是合成的无 idhistory_truncated标记携带{reason: replay_window_exceeded, truncatedEvents, retainedEvents, maxBytes, truncatedTurns?, fullTranscriptAvailable: boolean}。fullTranscriptAvailable为 true 表示客户端可用GET /session/:id/transcript分页读取完整持久化 transcriptfalse 表示只有有界回放可用。客户端应将其渲染为状态并正常应用保留回放绝不能触发 resync 循环。回放引擎曾在某点失败时置replayDegraded: true此时客户端应优先完整 transcript。ACP 子进程预热Preheatbridge.preheat()仍对显式嵌入者开放qwen serve启动后也会为兼容性尝试预热受信任的主子进程。预热失败非致命下一条运行时命令或会话会重试受信任的次子进程首次使用时才启动。Workspace Runtime 在工作活动期间拥有子进程。在所有会话与管理租约排空后省略或为 0 的channelIdleTimeoutMs会立即回收子进程纯预热本身保留给首次使用且不会武装该回收器。正值配置延迟或活动 keepalive 会让子进程在更长的剩余窗口内保持可复用。公开的 Workspace Runtimeensure命令增加可续期的十分钟 workspace 租约每次成功调用都会重置该窗口即使 channel 已存活。配置项BridgeOptions.maxSessions默认 32—— 会话数上限。BridgeOptions.sessionScope默认single可选thread。BridgeOptions.initializeTimeoutMs默认 10s—— ACP 子进程启动截止时间Channel 工厂 initialize握手与默认请求超时。BridgeOptions.sessionRestoreTimeoutMs默认 60s—— ACPloadSession/unstable_resumeSession截止时间。默认 60s显式配置的初始化超时可抬高它但绝不会降低它。BridgeOptions.channelIdleTimeoutMs未设置或0在运行时工作排空后回收但纯预热保留给首次使用正值或活动 keepalive 延迟回收较长的窗口胜出。能力标签清单session_create、session_id_override、session_scope_override、session_load、session_resume、unstable_session_resume废弃别名、session_list、session_info、session_close、session_metadata、session_set_model、client_identity、client_heartbeat、session_recap、session_generation、session_btw、session_context_usage、session_tasks、session_monitor_tool_correlation、session_stats、session_lsp、session_resources、session_status、non_blocking_prompt。CLI 侧对应的 serve 快路径旗标映射见 packages/cli/src/serve/fast-path.ts#L48-L57--compacted-replay-max-bytes、--channel-idle-timeout-ms、--session-restore-timeout-ms等。无状态生成session_generationPOST /session/:id/generate接受{ prompt: string }返回请求作用域的 SSE 流事件为started、可选thinking、delta、done或error。请求不读取对话历史、不记录回合、不暴露工具。ACP 子进程在可用时使用配置的有效快速模型否则使用会话主模型。已知限制与注意点connection.unstable_resumeSession在 ACP 层可能仍不稳定但 daemon 以session_resume广告已承诺的 v1 路由契约unstable_session_resume仅保留为废弃兼容别名。v1没有按客户端驱逐只有按会话与按订阅者终止。撤销策略在 F 系列 Wave 5 / PR 24。client_evicted是每订阅者而非每会话的SSE 订阅者被驱逐的客户端可以重连。匿名客户端无X-Qwen-Client-Id在designated与consensus策略下不能投票。依赖与延伸阅读ACP 层connection.newSession、connection.unstable_resumeSession、connection.loadSession。03-acp-bridge.md桥接层整体架构。04-permission-mediation.mdoriginator 与身份如何驱动权限策略决策。10-event-bus.md终端帧投递。qwen-serve-protocol.md路由目录wire reference。参考源码packages/acp-bridge/src/bridge.tsSessionEntry定义与spawnOrAttach、registerClient、resolveTrustedClientId、killSession等核心实现。packages/acp-bridge/src/bridgeTypes.tsHttpAcpBridge、BridgeSession、BridgeSessionState、BridgeRestoredSession。packages/acp-bridge/src/bridgeErrors.tsInvalidClientIdError、InvalidSessionMetadataError等错误类型。packages/acp-bridge/src/replayWindowLimits.ts压缩回放窗口默认值与硬上限。packages/acp-bridge/src/bridge.test.ts心跳、元数据校验等生命周期行为测试。packages/sdk-typescript/src/daemon/types.tsDaemonSession、DaemonSessionSummary等 SDK 类型。packages/sdk-typescript/src/daemon/DaemonSessionClient.tsSDK 客户端prompt()的非阻塞路径等。packages/cli/src/serve/fast-path.tsserve 快路径参数解析与校验。packages/cli/src/serve/acp-http/dispatch.ts断线回收requireZeroAttaches等路由层逻辑。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考