Files
Arcrun/cypher-executor/src/actions/cypher-handlers.ts
T
Claude 2707fca32b feat(arcrun): implement arcrun MVP — open-source AI workflow engine
Phase 1-5 complete per .agents/specs/u6u-core-mvp/:

**Phase 1 — Cherry-pick & cleanup**
- Create arcrun/ from cypher-executor, credentials, builtins, registry
- Remove 9 InkStone Service Bindings (KBDB, REGISTRY, CLINIC_*, AICEO, MINI_ME)
- Rewrite component-loader: 3-layer (builtin → WASM_BUCKET R2 → error)
- Remove autoPublishMissing.ts, proxy.ts (AICEO), execution-logger.ts (KBDB)
- Clean all KV namespace IDs and InkStone internal URLs from config files

**Phase 2 — contract.yaml completeness**
- Add credentials_required to gmail, google_sheets, telegram, line_notify
- Add config_example to all 21 components with annotated field descriptions

**Phase 3 — Credential injection**
- Add credential-injector.ts: AES-GCM decrypt from CREDENTIALS_KV
- Integrate into GraphExecutor before WASM execution
- Structured errors with repair instructions when credential missing

**Phase 4 — CLI (acr)**
- cli/package.json: arcrun package, bin: acr, deps: commander/js-yaml/chalk/ora
- 8 commands: init, creds push, push, run, validate, parts, list, logs
- Standard mode: writes directly to user's CF KV via CF REST API
- acr init: interactive setup with arcrun.dev API Key registration

**Phase 5 — Open source release prep**
- README.md: 5-minute quickstart, component table, workflow YAML syntax
- CONTRIBUTING.md: TinyGo dev env, component scaffolding, submission flow
- Security audit: no InkStone internal URLs/IDs in committed files
- .gitignore: exclude credentials.yaml, .wrangler, *.wasm

https://claude.ai/code/session_01BnCdSLVH8tUed9VrrPavgT
2026-04-16 04:06:25 +00:00

113 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Bindings, ExecutionGraph } from '../types';
import { ExecutionError } from '../types';
import { GraphExecutor } from '../graph-executor';
import { graphSchema } from '../lib/schemas';
import { createComponentLoader } from '../lib/component-loader';
import { writeEvaluation, updateComponentStats } 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<string, unknown>; cypher: unknown; missing: string[] }> {
const parsed = parseTriplets(triplets);
if (!parsed) {
throw new Error('無法解析任何節點');
}
const { nodeResults, missingNodes } = await searchNodes(parsed, env.WASM_BUCKET);
if (missingNodes.length > 0) {
return { nodes: nodeResults, cypher: null, missing: missingNodes };
}
const graph = buildExecutionGraph(parsed, nodeResults, 'cypher-search-result', 'Cypher Search Result');
return { nodes: nodeResults, cypher: { nodes: graph.nodes, edges: graph.edges }, missing: [] };
}
export async function handleCypherExecute(
triplets: unknown[],
context: Record<string, unknown> | undefined,
graphId: string,
graphName: string,
env: Bindings,
waitUntil: (promise: Promise<void>) => void,
): Promise<{ success: boolean; data?: unknown; error?: string; trace?: unknown; duration_ms: number; graph?: ExecutionGraph }> {
const parsed = parseTriplets(triplets as unknown[]);
if (!parsed) {
throw new Error('無法解析任何節點');
}
const { nodeResults, missingNodes } = await searchNodes(parsed, env.WASM_BUCKET);
if (missingNodes.length > 0) {
throw new Error(
`以下零件不存在於 WASM_BUCKET${missingNodes.join(', ')}\n` +
`修復:執行 acr parts 查看可用零件清單,或執行 acr validate <workflow.yaml> 進行完整驗證。`
);
}
const graph = buildExecutionGraph(parsed, nodeResults, graphId, graphName);
const parseResult = graphSchema.safeParse(graph);
if (!parseResult.success) {
throw new Error('圖定義產生失敗');
}
const loader = createComponentLoader(env);
const executor = new GraphExecutor(loader, undefined, env);
const start = Date.now();
try {
const result = await executor.execute(parseResult.data as ExecutionGraph, context ?? {}, env.EXEC_CONTEXT);
const duration_ms = Date.now() - start;
// 非同步記錄統計(Phase 7 補充 analytics,目前為 no-op
const componentId = graph.nodes.find(n => n.componentId)?.componentId ?? graphId;
const runId = `${graphId}-${Date.now()}`;
waitUntil(writeEvaluation(env, {
run_id: runId,
workflow_id: graphId,
component_id: componentId,
verdict: 'success',
duration_ms,
evaluated_at: Date.now(),
}));
waitUntil(updateComponentStats(env, componentId, 'success', duration_ms));
return { success: true, data: result.data, trace: result.trace, duration_ms, graph };
} catch (err) {
const duration_ms = Date.now() - start;
const errMsg = err instanceof Error ? err.message : String(err);
const componentId = graph.nodes.find(n => n.componentId)?.componentId ?? graphId;
const runId = `${graphId}-${Date.now()}`;
waitUntil(writeEvaluation(env, {
run_id: runId,
workflow_id: graphId,
component_id: componentId,
verdict: 'failed',
duration_ms,
error_message: errMsg.slice(0, 200),
evaluated_at: Date.now(),
}));
waitUntil(updateComponentStats(env, componentId, 'failed', duration_ms));
if (err instanceof ExecutionError) {
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;
}
}