feat/step3 全量重生(Arcrun@7e87a33):查詢誠實化 cypher/registry+manifest 投影乾淨

- build-bundles.mjs(work/installer-batch-0725 版)+build-ui-bundle.mjs 全量建:
  tier1 19(18 TinyGo+code)+tier2 4(cypher/kbdb/registry/mcp)+ui=24 顆
- manifest built=2026-07-31 source=Arcrun@7e87a33(feat/step3-missing-guidance)
- 死零件 km_writer/claude_api/kbdb_upsert_block 消失(G13);claude_api 在
  .component-builds 仍殘留、建置時排除(上游 Arcrun 待刪,已落帳)
- daemon 段沿用 v0.15.0(zips 非本機可重建、非本次修復範圍)
- 含 feat/step3 修復:isVirtualIoName(Input 角色假 found)+registry input_schema
  透傳+not_found 分型指路 suggestion+recipe 兩庫查詢

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 12:59:09 +08:00
parent 3918ee629c
commit 0bfe37cffb
32 changed files with 1829 additions and 12496 deletions
+192 -7
View File
@@ -9950,6 +9950,11 @@ function parseTriplets(rawTriplets) {
__name(parseTriplets, "parseTriplets");
var INPUT_NAMES = /* @__PURE__ */ new Set(["input", "trigger", "webhook", "start"]);
var OUTPUT_NAMES = /* @__PURE__ */ new Set(["output", "result", "end", "done"]);
function isVirtualIoName(name) {
const lower = name.toLowerCase();
return INPUT_NAMES.has(lower) || OUTPUT_NAMES.has(lower);
}
__name(isVirtualIoName, "isVirtualIoName");
function resolveNodeRole(name, parsed) {
if (INPUT_NAMES.has(name.toLowerCase())) return "Input";
if (OUTPUT_NAMES.has(name.toLowerCase())) return "Output";
@@ -9969,13 +9974,15 @@ init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_process();
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_console();
init_performance2();
init_component_loader();
init_recipes();
async function searchNodes(parsed, config2, env2) {
const nodeResults = {};
const missingNodes = [];
const sub = env2?.WORKER_SUBDOMAIN;
const registryBase = env2?.REGISTRY_BASE_URL ?? (sub ? wasmWorkerUrl("registry", sub) : void 0);
for (const nodeName of parsed.nodeNames) {
const role = resolveNodeRole(nodeName, parsed);
if (role === "Input" || role === "Output") {
if ((role === "Input" || role === "Output") && isVirtualIoName(nodeName)) {
nodeResults[nodeName] = { status: "found", componentId: nodeName.toLowerCase(), type: role };
continue;
}
@@ -9985,11 +9992,11 @@ async function searchNodes(parsed, config2, env2) {
nodeResults[nodeName] = { status: "found", componentId, type: role };
continue;
}
if (!sub) {
if (!registryBase) {
nodeResults[nodeName] = { status: "unknown", componentId, type: role };
continue;
}
const q = await fetchComponent(sub, componentId);
const q = await fetchComponent(registryBase, componentId);
if (!q.ok) {
nodeResults[nodeName] = { status: "unknown", componentId, type: role };
continue;
@@ -9999,22 +10006,140 @@ async function searchNodes(parsed, config2, env2) {
status: "found",
componentId,
type: role,
source: "component",
input_schema: q.entry.input_schema,
success_rate: q.entry.success_rate,
stability: q.entry.stability
};
continue;
}
nodeResults[nodeName] = { status: "missing", componentId, type: role };
const recipe = env2?.RECIPES ? await resolveRecipe(componentId, env2.RECIPES) : null;
if (recipe) {
nodeResults[nodeName] = {
status: "found",
componentId: recipe.canonical_id,
type: role,
source: "recipe",
description: recipe.description,
endpoint: recipe.endpoint
};
continue;
}
const [similarComponents, similarRecipes] = await Promise.all([
searchSimilarComponents(registryBase, nodeName),
env2?.RECIPES ? searchSimilarRecipes(env2.RECIPES, nodeName) : Promise.resolve([])
]);
nodeResults[nodeName] = {
status: "not_found",
componentId,
type: role,
suggestion: buildSuggestion(componentId),
...similarComponents.length > 0 ? { similar_components: similarComponents } : {},
...similarRecipes.length > 0 ? { similar_recipes: similarRecipes } : {}
};
missingNodes.push(nodeName);
}
return { nodeResults, missingNodes };
}
__name(searchNodes, "searchNodes");
async function fetchComponent(subdomain, id) {
var SERVICE_HINTS = [
"google",
"gmail",
"sheets",
"slides",
"gdocs",
"drive",
"calendar",
"youtube",
"slack",
"telegram",
"discord",
"line",
"whatsapp",
"twilio",
"notion",
"airtable",
"trello",
"jira",
"asana",
"linear",
"github",
"gitea",
"gitlab",
"bitbucket",
"stripe",
"paypal",
"shopify",
"hubspot",
"salesforce",
"openai",
"anthropic",
"claude",
"gemini",
"groq",
"twitter",
"facebook",
"instagram",
"linkedin",
"dropbox",
"zoom",
"sendgrid",
"mailgun",
"kbdb"
];
var COMPUTE_HINTS = [
"encrypt",
"decrypt",
"cipher",
"aes",
"rsa",
"sha",
"md5",
"hmac",
"hash",
"sign",
"verify",
"encode",
"decode",
"base64",
"hex",
"compress",
"decompress",
"zip",
"gzip",
"uuid",
"random",
"regex",
"math",
"calc",
"sort",
"dedup",
"diff",
"template",
"render",
"convert",
"transform",
"parse",
"format",
"csv",
"xml"
];
function buildSuggestion(componentId) {
const lower = componentId.toLowerCase();
const serviceHit = SERVICE_HINTS.find((w) => lower.includes(w));
const computeHit = COMPUTE_HINTS.find((w) => lower.includes(w));
if (serviceHit) {
return `\u5169\u5EAB\u90FD\u67E5\u904E\uFF0C\u96F6\u4EF6 registry \u8207 recipe \u5EAB\u7686\u7121\u300C${componentId}\u300D\u3002\u540D\u5B57\u542B\u670D\u52D9\u8A5E\u300C${serviceHit}\u300D\uFF1D\u5916\u90E8 API \u6A23\u8C8C \u2192 \u6C92\u6709\u6B64 recipe\uFF0C\u53EF\u81EA\u5DF1\u5BEB\uFF1A\u5BEB\u6CD5\u770B skill\u300Cwrite_recipe\u300D\uFF08arcrun_get_skill('write_recipe')\uFF09\uFF0C\u5BEB\u597D\u7528 acr recipe push \u6216 POST /recipes \u88DD\u4E0A\u5373\u53EF\u7528\uFF0C\u4E0D\u7528\u6539\u5E73\u53F0\u3002`;
}
if (computeHit) {
return `\u5169\u5EAB\u90FD\u67E5\u904E\uFF0C\u96F6\u4EF6 registry \u8207 recipe \u5EAB\u7686\u7121\u300C${componentId}\u300D\u3002\u540D\u5B57\u542B\u8A08\u7B97\u8A5E\u300C${computeHit}\u300D\uFF1D\u8A08\u7B97\u539F\u8A9E\u6A23\u8C8C \u2192 \u6C92\u6709\u6B64\u96F6\u4EF6\uFF0C\u53EF\u6295\u7A3F PR \u65B0\u589E WASM component\uFF1A\u505A\u6CD5\u770B skill\u300Cadd_new_wasm_component\u300D\uFF08arcrun_get_skill('add_new_wasm_component')\uFF09\u3002`;
}
return `\u5169\u5EAB\u90FD\u67E5\u904E\uFF0C\u96F6\u4EF6 registry \u8207 recipe \u5EAB\u7686\u7121\u300C${componentId}\u300D\uFF0C\u4E14\u540D\u5B57\u5224\u4E0D\u51FA\u578B\u3002\u7F3A\u5916\u90E8 API \u2192 \u81EA\u5DF1\u5BEB recipe\uFF08skill\u300Cwrite_recipe\u300D\uFF09\uFF1B\u7F3A\u8A08\u7B97\u80FD\u529B \u2192 \u6295\u7A3F\u96F6\u4EF6 PR\uFF08skill\u300Cadd_new_wasm_component\u300D\uFF0Ccomponent \u9032 WASM \u6C99\u7BB1\uFF09\u3002`;
}
__name(buildSuggestion, "buildSuggestion");
async function fetchComponent(registryBase, id) {
try {
const base = wasmWorkerUrl("registry", subdomain);
const res = await fetch(`${base}/components/${encodeURIComponent(id)}`, {
const res = await fetch(`${registryBase}/components/${encodeURIComponent(id)}`, {
signal: AbortSignal.timeout(5e3)
});
if (res.status === 404) return { ok: true };
@@ -10035,6 +10160,66 @@ async function fetchComponent(subdomain, id) {
}
}
__name(fetchComponent, "fetchComponent");
function extractTokens(name) {
const tokens = [];
const ascii = name.toLowerCase().match(/[a-z0-9]{3,}/g) ?? [];
tokens.push(...ascii);
const cjkRuns = name.match(/[一-鿿]+/g) ?? [];
for (const run2 of cjkRuns) {
for (let i = 0; i + 2 <= run2.length; i++) tokens.push(run2.slice(i, i + 2));
}
return [...new Set(tokens)].slice(0, 8);
}
__name(extractTokens, "extractTokens");
async function searchRegistryIds(registryBase, q) {
try {
const res = await fetch(`${registryBase}/components/search?q=${encodeURIComponent(q)}`, {
signal: AbortSignal.timeout(5e3)
});
if (!res.ok) return [];
const body = await res.json();
return (body.data?.results ?? []).map((r) => r.canonical_id).filter((s) => !!s);
} catch {
return [];
}
}
__name(searchRegistryIds, "searchRegistryIds");
async function searchSimilarComponents(registryBase, nodeName) {
const direct = await searchRegistryIds(registryBase, nodeName);
if (direct.length > 0) return direct.slice(0, 3);
const tokens = extractTokens(nodeName);
if (tokens.length === 0) return [];
const hits = await Promise.all(tokens.map((t) => searchRegistryIds(registryBase, t)));
const count3 = /* @__PURE__ */ new Map();
for (const ids of hits) {
for (const id of ids) count3.set(id, (count3.get(id) ?? 0) + 1);
}
return [...count3.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([id]) => id);
}
__name(searchSimilarComponents, "searchSimilarComponents");
async function searchSimilarRecipes(kv, nodeName) {
try {
const list = await kv.list({ prefix: "recipe:" });
const all = (await Promise.all(
list.keys.map((k) => kv.get(k.name, "json"))
)).filter(Boolean);
const tokens = [nodeName.toLowerCase(), ...extractTokens(nodeName)];
const seen = /* @__PURE__ */ new Set();
const matched = [];
for (const r of all) {
if (seen.has(r.canonical_id)) continue;
const hay = `${r.canonical_id} ${r.display_name ?? ""} ${r.description ?? ""}`.toLowerCase();
if (tokens.some((t) => hay.includes(t))) {
seen.add(r.canonical_id);
matched.push(r.canonical_id);
}
}
return matched.slice(0, 3);
} catch {
return [];
}
}
__name(searchSimilarRecipes, "searchSimilarRecipes");
// src/actions/graph-builder.ts
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_process();