import type { Bindings, ExecutionGraph } from '../types'; import { ExecutionError, WorkflowPaused } from '../types'; import { GraphExecutor } from '../graph-executor'; import { graphSchema } from '../lib/schemas'; import { createComponentLoader } from '../lib/component-loader'; import { recordComponentStats } from './execution-evaluator'; import { parseTriplets } from './triplet-parser'; import { searchNodes } from './search-nodes'; import { buildExecutionGraph } from './graph-builder'; export async function handleCypherSearch( triplets: unknown[], env: Bindings, ): Promise<{ nodes: Record; cypher: unknown; missing: string[] }> { const parsed = parseTriplets(triplets); if (!parsed) { throw new Error('無法解析任何節點'); } // 2026-07-30:查 registry 判真實存在(workflow-discovery)。 // `missing` 以前寫死 [],等於告訴 AI「什麼都有」——那是「腹語術」的入口。 const { nodeResults, missingNodes } = await searchNodes(parsed, undefined, env); const graph = buildExecutionGraph(parsed, nodeResults, 'cypher-search-result', 'Cypher Search Result'); return { nodes: nodeResults, cypher: { nodes: graph.nodes, edges: graph.edges }, missing: missingNodes }; } export async function handleCypherExecute( triplets: unknown[], context: Record | undefined, graphId: string, graphName: string, config: Record> | undefined, env: Bindings, waitUntil: (promise: Promise) => void, apiKey?: string, ): Promise<{ success: boolean; data?: unknown; error?: string; trace?: unknown; duration_ms: number; graph?: ExecutionGraph; // resumable workflow: 節點 pending 時回 paused(不算 success 也不算 fail) paused?: boolean; task_id?: string; run_id?: string; paused_node_id?: string; }> { const parsed = parseTriplets(triplets as unknown[]); if (!parsed) { throw new Error('無法解析任何節點'); } const { nodeResults } = await searchNodes(parsed, config, env); const graph = buildExecutionGraph(parsed, nodeResults, graphId, graphName, config); const parseResult = graphSchema.safeParse(graph); if (!parseResult.success) { throw new Error('圖定義產生失敗'); } const loader = createComponentLoader(env); const executor = new GraphExecutor(loader, undefined, env, apiKey); const start = Date.now(); try { const result = await executor.execute(parseResult.data as ExecutionGraph, context ?? {}, env.EXEC_CONTEXT); const duration_ms = Date.now() - start; // 非同步回寫每顆零件的執行統計(design.md「執行統計設計」;fire-and-forget 不阻擋回應) waitUntil(recordComponentStats(env, graph.nodes, result.trace)); return { success: true, data: result.data, trace: result.trace, duration_ms, graph }; } catch (err) { const duration_ms = Date.now() - start; // Resumable workflow: 節點回 pending → 回 paused 結構,不算成功也不算失敗 // SDD: resumable-workflow/design.md if (err instanceof WorkflowPaused) { return { success: true, paused: true, task_id: err.task_id, run_id: err.run_id, paused_node_id: err.paused_node_id, trace: err.trace_so_far, duration_ms, graph, }; } const errMsg = err instanceof Error ? err.message : String(err); // 失敗路徑同樣回寫每顆零件統計:ExecutionError 帶完整 trace(失敗節點有 error、 // 之前成功的節點照記成功);非 ExecutionError 無 trace 可歸因 → 不記(誠實:不瞎猜)。 if (err instanceof ExecutionError) { waitUntil(recordComponentStats(env, graph.nodes, err.trace)); const traceFormatted = err.trace.map(s => ({ node: s.nodeId, status: s.error ? 'failed' : 'success', ...(s.error ? { error: s.error } : {}), })); throw new Error(JSON.stringify({ success: false, error: errMsg, failed_node: err.failed_node, failed_input: err.failed_input, trace: traceFormatted, duration_ms, })); } throw err; } }