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
This commit is contained in:
Claude
2026-04-16 04:06:25 +00:00
commit 2707fca32b
155 changed files with 17413 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
{
"name": "@inkstone/u6u-builtins-worker",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@inkstone/u6u-builtins-worker",
"version": "1.0.0",
"dependencies": {
"hono": "^4.7.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250219.0",
"typescript": "^5.7.0"
}
},
"node_modules/@cloudflare/workers-types": {
"version": "4.20260329.1",
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260329.1.tgz",
"integrity": "sha512-LxBHrYYI/AZ6OCbUzRqRgg6Rt1qev2KxN2NNd3saye41AO2g52cYvHV+ohts5oPnrIUD7YRjbgN/J3NU7e7m5A==",
"dev": true,
"license": "MIT OR Apache-2.0"
},
"node_modules/hono": {
"version": "4.12.9",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz",
"integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"name": "arcrun-builtins",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy"
},
"dependencies": {
"hono": "^4.7.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250219.0",
"typescript": "^5.7.0"
}
}
+43
View File
@@ -0,0 +1,43 @@
// initComponents:把所有內建零件上架到 Component Registryvia Service Binding
import type { Bindings } from '../types';
import { buildComponentDefs } from '../types';
async function publishOne(
registry: Fetcher,
def: ReturnType<typeof buildComponentDefs>[number],
): Promise<{ id: string; status: number; ok: boolean; error?: unknown }> {
const payload = {
id: def.id,
name: def.name,
description: def.description,
url: def.url,
method: def.method,
tags: def.tags,
input_schema: JSON.stringify(def.input_schema),
output_schema: JSON.stringify(def.output_schema),
author: 'u6u-builtins',
version: '1.0.0',
};
const res = await registry.fetch('http://registry/components', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});
const body = await res.json().catch(() => ({ error: 'parse error' }));
// 201 = 新建成功;409 = 已存在(也算 ok)
const isOk = res.status === 201 || res.status === 409 || res.ok;
return { id: def.id, status: res.status, ok: isOk, ...(!isOk && { error: body }) };
}
export async function initComponents(
env: Bindings,
): Promise<{ ok: number; failed: number; results: unknown[] }> {
const defs = buildComponentDefs(env.WORKER_BASE_URL);
const results = await Promise.all(defs.map(def => publishOne(env.REGISTRY, def)));
const ok = results.filter(r => r.ok).length;
const failed = results.filter(r => !r.ok).length;
return { ok, failed, results };
}
+27
View File
@@ -0,0 +1,27 @@
// u6u-builtins Worker
// 所有零件已遷移至 u6u-core/registry/components/ 的 TinyGo .wasm 版本
// 此 Worker 保留 /init 端點供初始化 Component Registry 使用
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import type { Bindings } from './types';
import { initComponents } from './actions/initComponents';
const app = new Hono<{ Bindings: Bindings }>();
app.use('*', cors());
app.get('/', c => c.json({
service: 'u6u-builtins',
version: '2.0.0',
status: 'ok',
note: '所有零件已遷移至 WASM,請使用 Component Registry',
}));
// POST /init — 把所有零件上架到 Component Registry(冪等,可重複執行)
app.post('/init', async c => {
const result = await initComponents(c.env);
const allOk = result.failed === 0;
return c.json({ success: allOk, ...result }, allOk ? 200 : 207);
});
export default app;
+156
View File
@@ -0,0 +1,156 @@
// u6u-builtins Worker 型別定義
export type Bindings = {
REGISTRY: Fetcher; // Component Registry Service Binding
CYPHER: Fetcher; // Cypher Executor Service Binding(排程執行用)
U6U_STORE: KVNamespace; // KV Storecron: + ai-transform: 前綴)
AI: Ai; // Workers AIai-transform compile 用)
WORKER_BASE_URL: string; // 本 Worker 對外 URL(用於上架時填入 url 欄位)
ENVIRONMENT: string;
};
export type ActionResponse<T = unknown> =
| { success: true; data: T }
| { success: false; error: string };
// componentDefs:所有內建零件的定義清單(資料層,initComponents.ts 使用)
export interface ComponentDef {
id: string;
name: string;
description: string;
url: string;
method: 'POST';
tags: string;
input_schema: object;
output_schema: object;
}
// CronJob:排程定義(儲存在 U6U_STOREkey = cron:{id}
export interface CronJob {
id: string;
cron_expr: string; // 標準 5 欄位 cron expression
triplets?: string[]; // 三元組格式工作流(與 graph_token 二選一)
graph_token?: string; // 已存在的 webhook token
description: string;
created_at: string;
last_run?: string;
enabled: boolean;
}
// AiTransform:已編譯的 AI 轉換函式(儲存在 U6U_STOREkey = ai-transform:{id}
export interface AiTransform {
id: string;
description: string; // 自然語言描述
fn_body: string; // 產生的 JS 函式 body(可直接 new Function 執行)
created_at: string;
}
export function buildComponentDefs(baseUrl: string): ComponentDef[] {
return [
// === 既有零件 ===
{ id: 'http-request', name: 'http-request',
description: '發送 HTTP 請求(GET/POST/PUT/DELETE),回傳 status 和 response body。支援自訂 headers 和 body。',
url: `${baseUrl}/http-request`, method: 'POST', tags: 'builtin,http,request,api',
input_schema: { type: 'object', required: ['url'], properties: { url: { type: 'string' }, method: { type: 'string', enum: ['GET','POST','PUT','DELETE','PATCH'] }, headers: { type: 'object' }, body: {} } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { status: { type: 'number' }, body: {} } } } } },
{ id: 'set', name: 'set',
description: '設定變數(key-value),把結果傳遞到下一個節點。支援 assignments 陣列或 values 物件兩種格式。',
url: `${baseUrl}/set`, method: 'POST', tags: 'builtin,variable,set,transform',
input_schema: { type: 'object', properties: { assignments: { type: 'array', items: { type: 'object' } }, values: { type: 'object' }, context: { type: 'object' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object' } } } },
{ id: 'filter', name: 'filter',
description: '依條件過濾陣列,回傳符合條件的元素。',
url: `${baseUrl}/filter`, method: 'POST', tags: 'builtin,filter,array,condition',
input_schema: { type: 'object', required: ['items','condition'], properties: { items: { type: 'array' }, condition: { type: 'object' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { items: { type: 'array' }, count: { type: 'number' } } } } } },
{ id: 'switch', name: 'switch',
description: '依條件路由,多個出口分支。',
url: `${baseUrl}/switch`, method: 'POST', tags: 'builtin,switch,branch,route,condition',
input_schema: { type: 'object', required: ['value','cases'], properties: { value: {}, cases: { type: 'array' }, default_branch: { type: 'string' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { branch: { type: 'string' } } } } } },
{ id: 'merge', name: 'merge',
description: '合併多個輸入物件為一個。',
url: `${baseUrl}/merge`, method: 'POST', tags: 'builtin,merge,combine,object',
input_schema: { type: 'object', required: ['inputs'], properties: { inputs: { type: 'array', items: { type: 'object' } } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object' } } } },
{ id: 'wait', name: 'wait',
description: '等待指定毫秒數後繼續,最多 30 秒。',
url: `${baseUrl}/wait`, method: 'POST', tags: 'builtin,wait,delay,throttle',
input_schema: { type: 'object', required: ['ms'], properties: { ms: { type: 'number' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object' } } } },
{ id: 'google-sheets', name: 'google-sheets',
description: '讀取或寫入 Google 試算表。需要 Google OAuth access_token。',
url: `${baseUrl}/google-sheets`, method: 'POST', tags: 'integration,google,sheets,oauth',
input_schema: { type: 'object', required: ['spreadsheet_id','range','access_token'], properties: { spreadsheet_id: { type: 'string' }, range: { type: 'string' }, action: { type: 'string', enum: ['read','write'] }, values: { type: 'array' }, access_token: { type: 'string' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object' } } } },
{ id: 'gmail', name: 'gmail',
description: '透過 Gmail 發送 Email。需要 Google OAuth access_token。',
url: `${baseUrl}/gmail`, method: 'POST', tags: 'integration,google,gmail,email,oauth',
input_schema: { type: 'object', required: ['to','subject','body','access_token'], properties: { to: { type: 'string' }, subject: { type: 'string' }, body: { type: 'string' }, access_token: { type: 'string' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object' } } } },
{ id: 'line-notify', name: 'line-notify',
description: '發送 LINE Notify 訊息。需要 LINE Channel Access Token。',
url: `${baseUrl}/line-notify`, method: 'POST', tags: 'integration,line,notify,message',
input_schema: { type: 'object', required: ['message','token'], properties: { message: { type: 'string' }, token: { type: 'string' }, image_url: { type: 'string' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object' } } } },
{ id: 'telegram', name: 'telegram',
description: '透過 Telegram Bot 發送訊息。需要 bot_token 和 chat_id。',
url: `${baseUrl}/telegram`, method: 'POST', tags: 'integration,telegram,bot,message',
input_schema: { type: 'object', required: ['chat_id','text','bot_token'], properties: { chat_id: { type: 'string' }, text: { type: 'string' }, bot_token: { type: 'string' }, parse_mode: { type: 'string' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: {} } } },
// === P1 新增:Cron ===
{ id: 'cron', name: 'cron',
description: '建立定時排程工作流。指定 cron expression(如 0 9 * * *),到時間自動執行指定工作流。',
url: `${baseUrl}/cron`, method: 'POST', tags: 'builtin,cron,schedule,trigger,timer',
input_schema: { type: 'object', required: ['cron_expr'], properties: { cron_expr: { type: 'string', description: '標準 cron expression,如 0 9 * * *' }, triplets: { type: 'array', items: { type: 'string' } }, graph_token: { type: 'string' }, description: { type: 'string' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { cron_id: { type: 'string' }, cron_expr: { type: 'string' }, enabled: { type: 'boolean' } } } } } },
// === P2 新增:控制流 ===
{ id: 'if', name: 'if',
description: '單一條件判斷,true/false 兩個出口。condition 支援 JS 表達式(如 x > 5)。',
url: `${baseUrl}/if`, method: 'POST', tags: 'builtin,control,if,branch,condition',
input_schema: { type: 'object', required: ['condition'], properties: { condition: { type: 'string', description: 'JS 表達式,如 x > 5' }, input: { type: 'object', description: '提供給 condition 的變數' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { result: { type: 'boolean' }, branch: { type: 'string', enum: ['true', 'false'] } } } } } },
{ id: 'foreach', name: 'foreach',
description: '對輸入陣列的每個元素執行一次後續工作流(依序)。',
url: `${baseUrl}/foreach`, method: 'POST', tags: 'builtin,control,foreach,loop,iteration',
input_schema: { type: 'object', required: ['items'], properties: { items: { type: 'array', description: '要迭代的陣列' }, item_key: { type: 'string', description: '每個元素注入的變數名,預設 item' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { items: { type: 'array' }, count: { type: 'number' }, current_index: { type: 'number' }, current_item: {} } } } } },
{ id: 'try-catch', name: 'try-catch',
description: '錯誤處理分支:執行失敗時走 catch 出口繼續,不中斷整個工作流。',
url: `${baseUrl}/try-catch`, method: 'POST', tags: 'builtin,control,try,catch,error,handling',
input_schema: { type: 'object', required: ['action'], properties: { action: { type: 'object', description: '要嘗試執行的動作(url + body' }, fallback: { description: '失敗時的預設輸出' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { branch: { type: 'string', enum: ['try', 'catch'] }, result: {}, error: { type: 'string' } } } } } },
// === P3 新增:資料處理 ===
{ id: 'string-ops', name: 'string-ops',
description: '字串操作:capitalize, trim, replace, split, join, substring, upper, lower, includes, startsWith, endsWith, regex match/extract/replace。',
url: `${baseUrl}/string-ops`, method: 'POST', tags: 'builtin,data,string,transform,text',
input_schema: { type: 'object', required: ['operation','input'], properties: { operation: { type: 'string' }, input: { type: 'string' }, args: { description: '操作參數(依 operation 而定)' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { result: {}, operation: { type: 'string' } } } } } },
{ id: 'number-ops', name: 'number-ops',
description: '數字操作:round, floor, ceil, abs, format, add, subtract, multiply, divide, mod, min, max, clamp。',
url: `${baseUrl}/number-ops`, method: 'POST', tags: 'builtin,data,number,math,transform',
input_schema: { type: 'object', required: ['operation','input'], properties: { operation: { type: 'string' }, input: { type: 'number' }, args: { description: '操作參數(依 operation 而定)' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { result: {}, operation: { type: 'string' } } } } } },
{ id: 'array-ops', name: 'array-ops',
description: '陣列操作:map, sort, max, min, sum, average, count, first, last, flatten, unique, reverse, chunk。',
url: `${baseUrl}/array-ops`, method: 'POST', tags: 'builtin,data,array,list,transform',
input_schema: { type: 'object', required: ['operation','input'], properties: { operation: { type: 'string' }, input: { type: 'array' }, args: { description: '操作參數(依 operation 而定)' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { result: {}, operation: { type: 'string' } } } } } },
{ id: 'date-ops', name: 'date-ops',
description: '日期操作:now, format, add, subtract, diff, parse, startOf, endOf, isBefore, isAfter。',
url: `${baseUrl}/date-ops`, method: 'POST', tags: 'builtin,data,date,time,transform',
input_schema: { type: 'object', required: ['operation'], properties: { operation: { type: 'string' }, input: { type: 'string', description: 'ISO 日期字串(now 操作可省略)' }, args: { description: '操作參數(依 operation 而定)' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { result: {}, operation: { type: 'string' } } } } } },
// === P4 新增:AI Transform ===
{ id: 'ai-transform-compile', name: 'ai-transform-compile',
description: 'AI compile:輸入自然語言描述,AI 產生確定性 JS 轉換函式並儲存。返回 transform_id + 函式預覽。',
url: `${baseUrl}/ai-transform/compile`, method: 'POST', tags: 'ai,transform,compile,nlp,codegen',
input_schema: { type: 'object', required: ['description'], properties: { description: { type: 'string', description: '自然語言描述,如「把日期改成台灣格式 YYYY/MM/DD」' }, example_input: { description: '範例輸入(幫助 AI 理解)' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { transform_id: { type: 'string' }, fn_preview: { type: 'string' }, description: { type: 'string' } } } } } },
{ id: 'ai-transform-run', name: 'ai-transform-run',
description: 'AI run:使用已編譯的 transform_id 機械式執行轉換(不再呼叫 AI)。',
url: `${baseUrl}/ai-transform/run`, method: 'POST', tags: 'ai,transform,run,execute',
input_schema: { type: 'object', required: ['transform_id','input'], properties: { transform_id: { type: 'string', description: '由 compile 端點回傳的 ID' }, input: { description: '要轉換的資料' } } },
output_schema: { type: 'object', properties: { success: { type: 'boolean' }, data: { type: 'object', properties: { result: {}, transform_id: { type: 'string' } } } } } },
];
}
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"lib": ["ES2022"],
"types": ["@cloudflare/workers-types"],
"strict": true,
"noEmit": true
},
"include": ["src/**/*"]
}
+26
View File
@@ -0,0 +1,26 @@
name = "arcrun-builtins"
main = "src/index.ts"
compatibility_date = "2025-02-19"
compatibility_flags = ["nodejs_compat"]
workers_dev = true
# Service Binding:呼叫 Cypher Executor 執行排程工作流
[[services]]
binding = "CYPHER"
service = "arcrun-cypher-executor" # 填入你的 cypher-executor Worker 名稱
# KV Storecron: 前綴 = 排程定義,ai-transform: 前綴 = 編譯後轉換函式
[[kv_namespaces]]
binding = "U6U_STORE"
id = "" # 填入你的 KV Namespace ID
# Workers AIai-transform 零件的 compile 階段使用
[ai]
binding = "AI"
# Cron Trigger:每分鐘掃描並執行到期的排程工作流
[triggers]
crons = ["* * * * *"]
[vars]
ENVIRONMENT = "production"