arcrun — AI workflow execution engine (clean history)

Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。

此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在
richblack/arcrun 與本地 backup 分支)。含:
- acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe)
- recipe push 把關(資料外流提醒 + 打通檢查)
- 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1)
- CLI / cypher-executor / registry / 完整 SDD

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-06-03 15:52:38 +08:00
commit 922a57fe34
485 changed files with 89356 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
/**
* POST /workflows/resume
* Webhook callback 進來時,從 paused state 撿起來繼續跑下游節點
* SDD: matrix/arcrun/.agents/specs/resumable-workflow/design.md Phase 3
*
* 安全:因為這是 daemon 主動 callback,沒有 partner keydaemon 不知道用戶 key
* 靠 task_id 為 nonce + 24h TTL + idempotent consume 保護
*/
import { Hono } from 'hono';
import type { Bindings } from '../types';
import { WorkflowPaused } from '../types';
import { GraphExecutor } from '../graph-executor';
import { createComponentLoader } from '../lib/component-loader';
import { consumePausedRun } from '../lib/paused-runs';
export const resumeRouter = new Hono<{ Bindings: Bindings }>();
resumeRouter.post('/workflows/resume', async (c) => {
let body: Record<string, unknown>;
try {
body = await c.req.json();
} catch {
return c.json({ error: 'request body 必須為 JSON' }, 400);
}
const taskId = typeof body.task_id === 'string' ? body.task_id : undefined;
if (!taskId) {
return c.json({ error: 'task_id 必填' }, 400);
}
// consume = load + deleteidempotent:重複 callback 第二次找不到 state,回 200
const state = await consumePausedRun(c.env.EXEC_CONTEXT, taskId);
if (!state) {
return c.json({
success: true,
noop: true,
reason: `paused state 不存在或已過期 (task_id=${taskId})`,
});
}
const callbackResult = {
success: body.success ?? true,
data: body.data,
error: body.error,
};
const loader = createComponentLoader(c.env);
const executor = new GraphExecutor(loader, undefined, c.env, state.api_key);
const start = Date.now();
try {
const result = await executor.resumeFromPaused({
graph: state.graph,
paused_node_id: state.paused_node_id,
paused_context: state.paused_context,
callback_result: callbackResult,
prior_trace: state.trace_so_far,
kvNamespace: c.env.EXEC_CONTEXT,
recipe_output_format: state.recipe_output_format,
recipe_output_required_fields: state.recipe_output_required_fields,
});
const duration_ms = Date.now() - start;
return c.json({
success: true,
resumed: true,
task_id: taskId,
run_id: state.run_id,
data: result.data,
trace: result.trace,
duration_ms,
});
} catch (err) {
if (err instanceof WorkflowPaused) {
// resume 後又遇到 pendingv2 nested 情境)— v1 仍持久化但回 paused-again
return c.json({
success: true,
paused_again: true,
task_id: err.task_id,
run_id: err.run_id,
paused_node_id: err.paused_node_id,
});
}
const errMsg = err instanceof Error ? err.message : String(err);
return c.json({ success: false, error: errMsg, task_id: taskId, run_id: state.run_id }, 500);
}
});