ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

qwen-code 子代理执行模式投影(Agent Execution Mode Projection)解析:让 Web Shell 与运行时对前后台判定保持一致

qwen-code 子代理执行模式投影(Agent Execution Mode Projection)解析:让 Web Shell 与运行时对前后台判定保持一致 qwen-code 子代理执行模式投影Agent Execution Mode Projection解析让 Web Shell 与运行时对前后台判定保持一致【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读在 qwen-code 的运行时Runtime中一个agent/task子代理调用究竟以前台foreground还是后台background方式执行是由工具参数 已加载的子代理配置共同决定的而 Web 客户端此前只能看到工具参数无法复现完整判定规则导致同一调用在客户端与运行时可能出现前后台分类不一致。本文基于仓库设计文档 docs/design/2026-08-25-agent-execution-mode-projection.md深入讲解该问题的根因、executionMode投影方案的设计思路、运行时端的判定真相源实现、Web Shell 端的规范化与权威采纳逻辑以及针对旧会话与旧守护进程的兼容回退路径帮助你完整理解 qwen-code 中子代理前后台显示一致性的端到端链路。问题背景客户端与运行时的判定分歧前后台判定的真实规则并不只依赖参数在 qwen-code 的运行时中一次子代理调用是否进入后台执行由以下因素共同决定工具参数中的run_in_background已加载的子代理配置subagentConfig中的background标志是否为 forkfork调用会话是否为顶层会话top-level session是否携带working_dir、name等参数。也就是说客户端如果只拿到调用参数就无法准确复现运行时基于已加载子代理配置得出的最终结论。例如某个子代理配置自带background: true但客户端看不到这份配置仅凭参数推断就会得出与运行时相反的结论。这正是指定文档所描述的核心痛点The runtime decides whether an agent runs in the foreground or background using both tool arguments and loaded subagent configuration. Web clients only receive the arguments, so they can classify the same call differently from the runtime.为什么需要投影而不是让客户端自行复刻规则让每个客户端各自复刻一遍运行时的判定逻辑存在明显缺陷判定规则依赖SubagentConfig从磁盘加载的子代理配置客户端拿不到完整配置规则本身会随版本演进如嵌套限制、worktree 守卫、fork 语义变化客户端难以同步多个客户端Web Shell、Electron 桌面客户端等各自维护一份逻辑极易漂移。因此设计文档给出的思路是运行时在流式输出工具原始输出tool raw output时把已解析的最终执行模式直接投影到现有的 task-execution 显示帧上客户端只需读取并采纳这个权威值。设计核心把已解析的执行模式投影到 task-execution 帧投影时机与不变性保证设计文档明确了两个关键约束首次 running 更新即可用执行模式在第一条 running 状态的更新帧中就已经携带客户端无需等待生命周期状态推进值在生命周期推进期间保持不变executionMode在任务从 running 推进到 completed / failed / cancelled 等状态的过程中不会改变因此客户端可以在收到首帧后直接采纳并缓存不必担心后续帧覆盖。兼容性回退旧会话与旧守护进程投影字段是新增的因此历史录制会话recorded sessions中不包含该字段旧版本守护进程older daemons也不会输出该字段。对于这类帧客户端必须保留原有的参数 状态推断逻辑作为兼容回退即继续使用run_in_background、working_dir、name等参数以及rawOutput.status background来推断。这一回退逻辑在 Web Shell 中被称为冻结的兼容路径frozen compatibility path详见下文。非目标Non-goals明确的边界设计文档用 Non-goals 划定了本次改动的边界理解这些边界有助于避免误用不改变后台调度或生命周期状态的语义投影只是把既有结论广播给客户端不改变运行时后台任务注册表BackgroundTaskRegistry的调度行为也不改变 running / background / completed 等状态的含义不新增第二个顶层 ACP 字段executionMode复用了既有的 task-execution 显示帧该帧本就作为 tool raw output 流式输出不额外增加顶层 ACP 协议字段保持协议面最小化不更新独立的 Electron 客户端仓库内另一个消费不同结果格式的 Electron 客户端不在本次改动范围内事实上该客户端已被 fork 出本仓库独立维护见下文源码注释。运行时实现判定真相源在 agent.tsbackgroundRequested 与 shouldRunInBackground 的完整判定链运行时侧的真相源位于 packages/core/src/tools/agent/agent.ts 的AgentTool中。从源码结构看判定链可以概括为对应 agent.ts#L2700-L2711const backgroundRequested isFork !this.config.isInteractive() ? true : (this.params.run_in_background ?? subagentConfig.background ?? (!isForkRequested this.params.working_dir undefined this.params.name undefined)); const shouldRunInBackground backgroundRequested isTopLevelSession();逐项拆解判定输入说明isFork !this.config.isInteractive()headless 模式的 fork 调用强制走后台注册表fork 天然是分离的短命的非交互进程必须挂起直到继承的工作完成否则显式工具参数优先this.params.run_in_background显式工具参数优先级最高fork-headless 场景除外subagentConfig.background子代理配置文件中声明的后台标志客户端无法感知working_dir undefined name undefined兜底启发式既没有调用方自持 worktree、也没有命名队友的普通一次性启动默认进后台isTopLevelSession()后台委托在 v1 中仅限顶层会话——嵌套启动者拿不到它无法兑现的完成契约成功指引指向send_message与task_stop这两者都不在子代理工具集内两个重要细节值得注意隐式后台请求降级嵌套调用中隐式后台请求会降级为等待的前台运行Background request downgraded to a foreground run调试日志而不是把子代理结果孤儿化显式run_in_background: true的嵌套请求则被运行时 spawn 守卫直接拒绝buildSpawnBlockedResult。worktree 守卫当working_dir与后台执行冲突时shouldRunInBackground为 true 且携带working_dir运行时会在任何显示更新之前返回 blocked 结果——一个从未启动的调用绝不能发出带权威executionMode: background的 running 帧否则会与不带executionMode的 blocked 结果帧自相矛盾agent.ts#L2713-L2733。投影帧的组装判定完成后运行时组装 task-execution 显示帧agent.ts#L2735-L2746this.currentDisplay { type: task_execution as const, subagentName: subagentConfig.name, taskDescription: this.params.description, taskPrompt: this.params.prompt, executionMode: shouldRunInBackground ? background : foreground, subagentSessionReady: false, status: running as const, subagentColor: subagentConfig.color, }; if (shouldRunInBackground) this.setupEventListeners(updateOutput); updateOutput?.(this.currentDisplay);executionMode与status: running同帧出现正好满足设计文档首次 running 更新即可用的约束帧通过updateOutput回调以 tool raw output 的形式流式下发。桌面客户端的分叉与同步提示源码注释明确提到消费不同结果格式的桌面客户端已从本仓库 fork 出去基于 OpenWork不再在本仓库内 vendored它收不到这次投影因此在自己的副本里复刻了这条规则。仓库内的注释明确要求如果这条规则变化务必同步告知该 forkagent.ts#L2694-L2699。这印证了设计文档 Non-goals 中不更新 Electron 客户端的取舍——协议演进只面向仍在仓库内的 Web Shell。Web Shell 适配规范化与权威采纳传输层字段与类型定义Web Shell 通过守护进程daemon的转录消息接收工具调用数据。投影字段在传输模型中被定义为可选字段packages/web-shell/client/adapters/messageTypes.ts#L50-L54export interface DaemonMessageToolCall { callId: string; toolName: string; args?: Recordstring, unknown; executionMode?: foreground | background; subagentSessionReady?: boolean; status: DaemonMessageToolCallStatus; // ... }executionMode的可选性正是为兼容旧守护进程与历史录制会话而保留的。转录块到工具调用模型的规范化守护进程转录块transcript block在 packages/web-shell/client/adapters/transcriptToMessages.ts 中被转换为 Web Shell 的工具调用模型。关键转换逻辑transcriptToMessages.ts#L1264-L1303const executionMode safeToolProjection ? block.background true ? background : foreground : getRecord(rawOutput)?.[executionMode]; // ... executionMode: isTaskExecutionMode(executionMode) ? executionMode : undefined,在安全投影safeToolProjection路径下直接以转录块的background布尔值换算在常规路径下从工具原始输出rawOutput中读取executionMode并通过isTaskExecutionMode校验取值合法后才写入模型非法/缺失值统一归一为undefined从而走兼容回退。此外工具调用合并逻辑在 transcriptToMessages.ts#L1208 中遵循已存在的值优先策略target.executionMode source.executionMode ?? target.executionMode;这与设计文档值在生命周期推进期间保持不变的约束相互印证——后续帧即使不携带该字段也不会清空首帧已确立的权威值。权威采纳与冻结的兼容回退Web Shell 对前后台分类的入口是 packages/web-shell/client/adapters/toolClassification.ts 中的isBackgroundSubAgentToolCalltoolClassification.ts#L61-L91export function isBackgroundSubAgentToolCall(tool: ACPToolCall): boolean { if (!isSubAgentToolCall(tool)) return false; if (tool.executionMode) return tool.executionMode background; // Older daemon frames and recorded sessions do not include executionMode. const rawOutput getRecord(tool.rawOutput); const name tool.toolName.toLowerCase(); const args tool.args; const isTopLevelQwenAgent name agent tool.parentToolCallId undefined; const defaultsToBackground isTopLevelQwenAgent args ! undefined args?.run_in_background undefined args?.working_dir undefined args?.name undefined (typeof args?.subagent_type ! string || args.subagent_type.toLowerCase() ! fork); const explicitlyBackground args?.run_in_background true (name ! agent || isTopLevelQwenAgent); return ( rawOutput?.[status] background || explicitlyBackground || defaultsToBackground ); }这段代码精确实现了设计文档的三层语义权威采纳只要tool.executionMode存在就直接返回executionMode background不再参考任何参数或原始输出——运行时结论优先旧帧兼容回退executionMode缺失时旧守护进程帧、投影特性之前的录制会话、以及当前核心的 blocked-spawn 结果帧退回到参数 状态推断冻结不变式源码注释特别强调这条回退启发式是冻结的兼容路径核心侧判定规则变化时不得同步更新它toolClassification.ts#L52-L60——活规则在agent.ts的backgroundRequested/shouldRunInBackground桌面端镜像副本已被 fork 出仓库而这里已经存在一处有意为之的差异subagentConfig.background在回退路径中天然不可见正因如此才需要executionMode投影来补足。回退路径中还有两个值得注意的细节rawOutput.status background优先于参数推断即便参数写的是run_in_background: false只要运行状态帧表明进入了后台就按后台处理fork参数单独排除出缺省进后台启发式仅凭参数无法区分交互式分离 fork 与 headless 注册表支持的 fork因此这类帧信任rawOutput.status反映的真实运行模式。测试证据权威值压过参数packages/web-shell/client/adapters/toolClassification.test.ts 用一组针对性用例印证了权威采纳语义toolClassification.test.ts#L90-L115it(trusts the runtime background status when present, () { expect( isBackgroundSubAgentToolCall({ ...agentTool({ run_in_background: false }), rawOutput: { type: task_execution, status: background }, }), ).toBe(true); }); it(trusts the runtime background execution mode over foreground args, () { expect( isBackgroundSubAgentToolCall({ ...agentTool({ run_in_background: false }), executionMode: background, }), ).toBe(true); }); it(trusts the runtime foreground execution mode over background args, () { expect( isBackgroundSubAgentToolCall({ ...agentTool({ run_in_background: true }), executionMode: foreground, }), ).toBe(false); });也就是说参数写run_in_background: false但投影值executionMode: background→ 判定为后台配置中的background: true等客户端不可见因素生效参数写run_in_background: true但投影值executionMode: foreground→ 判定为前台嵌套降级、worktree 冲突等运行时规则生效。同文件中还有working_dir默认前台、命名队友不改变分类、嵌套调用无论标志如何都保持前台等用例toolClassification.test.ts#L60-L88共同覆盖了兼容回退路径的既有行为。端到端链路与兼容矩阵综合运行时与 Web Shell 两端一次子代理调用的执行模式投影链路如下模型发起agent/task工具调用携带run_in_background、fork、subagent_type、working_dir、name、description、prompt等参数运行时加载子代理配置SubagentConfig含background、executor、color等字段计算backgroundRequested与shouldRunInBackground必要时触发嵌套降级、worktree 守卫或 spawn 守卫运行时组装task_execution显示帧携带executionMode: foreground | background与status: running通过updateOutput流式下发为 tool raw outputWeb Shell 守护进程转录块经 transcriptToMessages.ts 规范化到DaemonMessageToolCall.executionMode再进入工具调用模型isBackgroundSubAgentToolCall优先采纳executionMode缺失时才走冻结的旧推断路径最终驱动 UI 渲染。各场景下的取值与路径可以归纳为下表数据来源是否含executionMode客户端行为新版本守护进程实时流当前核心是首帧即有生命周期内不变直接采纳为权威值旧版本守护进程实时流否走冻结的旧参数/状态推断投影特性之前的录制会话否同上blocked-spawn 结果帧从未启动否同上回退到冻结启发式避免与权威 running 帧冲突独立 Electron 客户端已 fork 出仓库不接收该投影在自己的副本中复刻规则结语executionMode投影是 qwen-code 在运行时决策、客户端呈现边界上的一次精准收敛它不改变任何调度与生命周期语义不新增顶层 ACP 字段而是复用既有的 task-execution 显示帧把依赖已加载子代理配置的复杂判定结果以最小代价广播给 Web Shell同时为旧会话与旧守护进程保留冻结的兼容回退。对于想要深入理解 qwen-code 子代理前后台机制或在此基础上构建客户端适配层的开发者建议沿着三条线索继续阅读运行时判定真相源 packages/core/src/tools/agent/agent.tsbackgroundRequested/shouldRunInBackground与 spawn 守卫、Web Shell 规范化入口 packages/web-shell/client/adapters/transcriptToMessages.tsdaemonToolBlockToToolCall与mergeToolCallInto、以及分类权威入口 packages/web-shell/client/adapters/toolClassification.tsisBackgroundSubAgentToolCall及其冻结回退再结合 toolClassification.test.ts 的用例理解边界行为。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表