let stepResult = '';
            let stepSuccess = true;
            // ⭐ 合并超时信号 + 用户停止信号，两者都能中断 AI 调用
            const merged = mergeAbort(signal, taskCtrl.signal);
            try {
              // 修复：fullPrompt 传到 customPrompt（第8参）而非 taskGoal（第6参）；
              // skipPersist 避免中间步骤污染聊天历史
              stepResult = await router.generateReply(
                chatId, agentId, taskMsgId,
                (chunk) => {
                  broadcast({ type: "agent.streaming.chunk", chatId, from: agentSenderId, msgId: taskMsgId, chunk });
                },
                // 推理过程（与主聊天路径一致，前端 .msg-think 区渲染）
                (thinkChunk: string) => {
                  broadcast({ type: "agent.streaming.think", chatId, from: agentSenderId, msgId: taskMsgId, think: thinkChunk });
                },
                undefined,
                (event: any) => {
                  broadcast({ ...event, chatId, from: agentSenderId, msgId: taskMsgId });
                },
                fullPrompt,
                merged.signal,
                { skipPersist: true }
              );
            } catch (stepErr: any) {
              stepSuccess = false;
              if (taskCtrl.signal.aborted) {
                stepResult = "步骤被用户停止中断";
              } else if (abortedByTimeout || signal?.aborted) {
                stepResult = "步骤执行超时（" + (task.timeout || ORCHESTRATOR_DEFAULT_TIMEOUT_MS) + "ms）被中断";
              } else {
                stepResult = "步骤执行失败：" + (stepErr.message || '未知错误');
              }
              console.error('[WS] step ' + task.id + ' failed:', stepErr);
            }

            if (signal) signal.removeEventListener('abort', onAbort);

            broadcast({
              type: "task.progress",
              chatId, from: agentSenderId, msgId: taskMsgId,
              phase: "step_done",
              message: (stepSuccess ? "步骤 " : "步骤失败 ") + (stepIdx + 1) + "：" + task.name,
              step: stepIdx + 1, total: steps.length,
              st