diff --git a/frontend-web/src/open-canvas/hooks/useAIWritingStream.ts b/frontend-web/src/open-canvas/hooks/useAIWritingStream.ts index ee956ad2..9c682bb3 100644 --- a/frontend-web/src/open-canvas/hooks/useAIWritingStream.ts +++ b/frontend-web/src/open-canvas/hooks/useAIWritingStream.ts @@ -568,6 +568,27 @@ export function customToProgressEvent(raw: unknown, sessionId: string): Progress const type = String(o.type ?? ''); // 后端 _save_draft_to_repo 等 helper 不走 custom 通道,type 必为节点 emit 的 chunk 类型 if (!type) return null; + + // 技能检索步骤:后端 _emit_skill_step 以 `writer({"type": "skill_search_step", **step})` + // 发出,但 **step 里也有 type(invoke_skill / list_skills / skill_result / select_skills / + // match_skills / skill_error / fallback / stop_early),会**覆盖**掉外层的 "skill_search_step"。 + // 所以 custom 事件实际 type 是这些内层值。这里按内层 step 类型识别,**实时**归一成 + // skill_search_step 事件(与素材包快照里的 step 同格式),让技能调用过程一步步进时间线, + // 而不是被这里的白名单丢弃、只在节点结束的快照里一次性出现。 + const SKILL_STEP_TYPES = [ + 'list_skills', 'match_skills', 'select_skills', 'invoke_skill', + 'skill_result', 'skill_error', 'fallback', 'stop_early', + ]; + if (SKILL_STEP_TYPES.includes(type)) { + return { + type: 'skill_search_step', + sessionId, + message: String(o.message ?? ''), + agentName: (o.agent_name ?? o.agentName) as string | undefined, + skillStep: normalizeSkillStep(o), + } as unknown as ProgressEvent; + } + const allowed: ProgressEventType[] = [ 'intent_chunk', 'intent_ready', 'research_progress', 'outline_chunk', 'draft_chunk',