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:
+192
-7
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user