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
@@ -0,0 +1,90 @@
/**
* prompt_recipe Zod schema
* SDD: matrix/arcrun/.agents/specs/recipe-system/design.md §2.1
*
* 平行於既有 auth_recipe / api_recipe,存 RECIPES KV (key: `prompt_recipe:{name}`)
* 容器 + recipe 模式:claude_api 是容器,recipe 是配方
*/
import { z } from 'zod';
// ── Transform 白名單 ──────────────────────────────────────────────────────────
// 限制 transform 種類避免變 mini-DSL;超過範圍請寫零件
export const TRANSFORM_NAMES = [
'json_array', // array → JSON.stringify 整體
'to_string', // 任意值 → String(x)
'join', // array → join(sep)sep 預設換行
'markdown_list', // array → "- a\n- b\n- c"
'extract_field', // array of object → 抽 field 後的 array(再可串其他 transform
'first', // array → first element(取單一)
'pluck_content', // KBDB blocks array → 抽 content 後 join 雙換行(草稿合併常用)
] as const;
/** transform 表示法:name 或 name:arg(如 extract_field:page_name */
export const TransformSchema = z.string().regex(/^[a-z_]+(:.+)?$/, 'transform 必須為 name 或 name:arg 格式');
// ── Fragment:從 KBDB / KV 抓固定資料 ──────────────────────────────────────────
export const KBDBBlockFragmentSchema = z.object({
var: z.string().min(1), // prompt template 內的變數名
source: z.literal('kbdb_block'),
block_id: z.string().optional(), // 二擇一
block_page_name: z.string().optional(), // 比 block_id 穩定
field: z.string().default('content'), // 抓 block 的哪個欄位
});
export const KVFragmentSchema = z.object({
var: z.string().min(1),
source: z.literal('kv'),
key: z.string().min(1),
});
// discriminatedUnion 對 refined zod object 不支援,故拆成驗證後 + 單獨檢查 block_id|page_name
export const FragmentSchema = z.discriminatedUnion('source', [
KBDBBlockFragmentSchema,
KVFragmentSchema,
]).superRefine((d, ctx) => {
if (d.source === 'kbdb_block' && !d.block_id && !d.block_page_name) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'block_id 或 block_page_name 必填其一',
});
}
});
// ── Input:從 workflow context 取值(含 transform) ────────────────────────────
export const InputSchema = z.object({
var: z.string().min(1),
from: z.string().min(1), // JSONPath-lite,如 "ctx.read_drafts.blocks"
transform: TransformSchema.optional(),
default: z.unknown().optional(), // from 取不到時的預設值(避免炸 prompt)
});
// ── Prompt 組裝 ──────────────────────────────────────────────────────────────
export const PromptAssemblySchema = z.object({
system: z.string().min(1), // 模板,可含 {{var}}
user: z.string().min(1),
});
// ── 輸出規格 ──────────────────────────────────────────────────────────────────
export const OutputSpecSchema = z.object({
format: z.enum(['text', 'json']).default('text'),
// 若 format=json,可選 schema 做 parse 後驗證(簡化版,列必填欄位即可)
required_fields: z.array(z.string()).optional(),
});
// ── 完整 prompt_recipe 定義 ────────────────────────────────────────────────────
export const PromptRecipeSchema = z.object({
kind: z.literal('prompt_recipe'),
name: z.string().min(1).regex(/^[a-z][a-z0-9_]*$/, 'name 為 lowercase + underscore'),
version: z.number().int().positive().default(1),
description: z.string().optional(),
model: z.enum(['haiku', 'sonnet', 'opus']).default('sonnet'),
fragments: z.array(FragmentSchema).default([]),
inputs: z.array(InputSchema).default([]),
prompt_assembly: PromptAssemblySchema,
output: OutputSpecSchema.default({ format: 'text' }),
});
export type PromptRecipe = z.infer<typeof PromptRecipeSchema>;
export type Fragment = z.infer<typeof FragmentSchema>;
export type RecipeInput = z.infer<typeof InputSchema>;