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
+162
View File
@@ -0,0 +1,162 @@
// queryComponents — 查詢零件合約
// 支援兩種查詢 id
// component_hash_idcmp_xxxxxxxx)— 永久穩定,workflow 引用用
// canonical_id(小寫底線) — 可讀名稱,透過 idx: 反查索引解析
// Requirements: 12.2, 12.3
import type { Bindings } from '../types';
export interface ComponentRecord {
component_hash_id: string;
canonical_id: string;
display_name: string;
version: string;
category: string;
stability: string;
status: string;
description: string;
aliases: string[];
tags: string[];
success_rate: number;
avg_duration_ms: number;
call_count: number;
wasm_r2_key?: string;
score: number;
}
// ── id 解析:支援 hash_id 和 canonical_id 兩種格式 ──────────────────────────
async function resolveHashId(id: string, env: Bindings): Promise<string | null> {
// 已經是 hash_id 格式
if (id.startsWith('cmp_')) return id;
// canonical_id → 透過 idx: 反查索引
const hashId = await env.SUBMISSIONS_KV.get(`idx:${id}`);
return hashId;
}
// ── 取得零件的所有版本 ────────────────────────────────────────────────────────
async function listVersions(hashId: string, env: Bindings): Promise<ComponentRecord[]> {
const prefix = `comp:${hashId}:`;
const list = await env.SUBMISSIONS_KV.list({ prefix });
const records: ComponentRecord[] = [];
for (const key of list.keys) {
const raw = await env.SUBMISSIONS_KV.get(key.name);
if (!raw) continue;
try {
const v = JSON.parse(raw);
if (v.status === 'tombstone') continue;
records.push(toComponentRecord(v));
} catch {
continue;
}
}
return records;
}
// ── 公開 API ──────────────────────────────────────────────────────────────────
/** 取得零件最優版本(floating 策略:成功率 × 速度 × log(使用次數)) */
export async function getComponent(
id: string,
env: Bindings,
): Promise<ComponentRecord | null> {
const hashId = await resolveHashId(id, env);
if (!hashId) return null;
const versions = await listVersions(hashId, env);
if (versions.length === 0) return null;
versions.sort((a, b) => b.score - a.score);
return versions[0];
}
/** 取得零件所有版本清單(含評分排序) */
export async function getComponentVersions(
id: string,
env: Bindings,
): Promise<ComponentRecord[]> {
const hashId = await resolveHashId(id, env);
if (!hashId) return [];
const versions = await listVersions(hashId, env);
versions.sort((a, b) => b.score - a.score);
return versions.slice(0, 10);
}
/** 關鍵字搜尋(掃描 KV prefix comp:,比對 canonical_id / display_name / description / aliases
*
* 注意:這是 Phase 0 的純文字比對版本。
* Phase 2 接入 Cloudflare Vectorize 後改為語意搜尋,API 介面不變。
*/
export async function searchComponents(
query: string,
env: Bindings,
): Promise<ComponentRecord[]> {
const q = query.toLowerCase();
// 列出所有 comp: 前綴的 key(只取最新一頁,最多 1000 個)
const list = await env.SUBMISSIONS_KV.list({ prefix: 'comp:' });
const seen = new Set<string>(); // 每個 hash_id 只取最優版本
const candidates: ComponentRecord[] = [];
for (const key of list.keys) {
const raw = await env.SUBMISSIONS_KV.get(key.name);
if (!raw) continue;
let v: Record<string, unknown>;
try { v = JSON.parse(raw); } catch { continue; }
if (v.status === 'tombstone' || v.visibility !== 'public') continue;
// 比對:canonical_id / display_name / description / aliases
const searchable = [
String(v.canonical_id ?? ''),
String(v.display_name ?? ''),
String(v.description ?? ''),
...(Array.isArray(v.aliases) ? v.aliases.map(String) : []),
...(Array.isArray(v.tags) ? v.tags.map(String) : []),
].join(' ').toLowerCase();
if (!searchable.includes(q)) continue;
const hashId = String(v.component_hash_id ?? '');
if (seen.has(`${hashId}:${v.version}`)) continue;
seen.add(`${hashId}:${v.version}`);
candidates.push(toComponentRecord(v));
}
candidates.sort((a, b) => b.score - a.score);
return candidates.slice(0, 10);
}
// ── 內部工具函數 ──────────────────────────────────────────────────────────────
function computeScore(v: Record<string, unknown>): number {
const successRate = parseFloat(String(v.success_rate ?? '1'));
const avgDuration = parseFloat(String(v.avg_duration_ms ?? '10'));
const callCount = parseInt(String(v.call_count ?? '0'), 10);
const speedScore = Math.max(0, 1 - avgDuration / 1000);
return successRate * speedScore * Math.log(callCount + 2);
}
function toComponentRecord(v: Record<string, unknown>): ComponentRecord {
return {
component_hash_id: String(v.component_hash_id ?? ''),
canonical_id: String(v.canonical_id ?? ''),
display_name: String(v.display_name ?? ''),
version: String(v.version ?? 'v1'),
category: String(v.category ?? 'logic'),
stability: String(v.stability ?? 'floating'),
status: String(v.status ?? 'active'),
description: String(v.description ?? ''),
aliases: Array.isArray(v.aliases) ? v.aliases.map(String) : [],
tags: Array.isArray(v.tags) ? v.tags.map(String) : [],
success_rate: parseFloat(String(v.success_rate ?? '1')),
avg_duration_ms: parseFloat(String(v.avg_duration_ms ?? '0')),
call_count: parseInt(String(v.call_count ?? '0'), 10),
wasm_r2_key: v.wasm_r2_key ? String(v.wasm_r2_key) : undefined,
score: computeScore(v),
};
}