arm 候選 batch(Arcrun@341bcb1):step3 誠實化+step4 意圖替換+step6 統計回寫+t158 mode 分流+t159 daemon 登記+t160 現行 ui

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 14:57:54 +08:00
parent 8cc794c354
commit 0a043701d0
3 changed files with 374 additions and 106 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
{
"schema": "arcrun-rag-bundles/v1",
"built": "2026-07-31",
"source": "Arcrun@062757f",
"source": "Arcrun@341bcb1",
"core": [
{
"name": "arcrun-array-ops",
@@ -503,7 +503,7 @@
"tier": 2,
"main_file": "tier2/cypher/index.js",
"main_module": "index.js",
"sha256": "73c50e8bc75f13f6f0adcf39d08892d78ceb0ccc34067d3c6533a09c3138b1e0",
"sha256": "0761688567af20a0ccaca1ee07258e5500514703ae1b40d74e5bf6575aad452f",
"compat_date": "2025-02-19",
"compat_flags": [
"nodejs_compat",
@@ -585,7 +585,7 @@
"tier": 2,
"main_file": "tier2/registry/index.js",
"main_module": "index.js",
"sha256": "657f6016b2511eeb1f06222b7f8dc40ec2f7d5d7cd608e7c57d27b5186786f4d",
"sha256": "6e7c6239e03cedf1acd56cc5ef7c30acacab021c9833aa0c87e6909569b1785d",
"compat_date": "2025-02-19",
"compat_flags": [
"nodejs_compat"
+260 -90
View File
@@ -9447,6 +9447,61 @@ var init_schemas = __esm({
}
});
// src/actions/execution-evaluator.ts
function componentVerdictsFromTrace(nodes, trace3) {
const componentByNodeId = /* @__PURE__ */ new Map();
for (const n of nodes) {
if (n.type === "Component" && n.componentId) componentByNodeId.set(n.id, n.componentId);
}
const verdicts = [];
for (const step of trace3) {
const componentId = componentByNodeId.get(step.nodeId);
if (!componentId) continue;
const out = step.output;
const outputSaysFailed = typeof out === "object" && out !== null && !Array.isArray(out) && out.success === false;
verdicts.push({
component_id: componentId,
success: !step.error && !outputSaysFailed,
duration_ms: Math.max(0, Number(step.duration_ms) || 0)
});
}
return verdicts;
}
async function recordComponentStats(env2, nodes, trace3) {
try {
const base = (env2.REGISTRY_BASE_URL ?? (env2.WORKER_SUBDOMAIN ? wasmWorkerUrl("registry", env2.WORKER_SUBDOMAIN) : void 0))?.replace(/\/$/, "");
if (!base) return;
const verdicts = componentVerdictsFromTrace(nodes, trace3);
if (verdicts.length === 0) return;
await Promise.all(
verdicts.map(
(v) => fetch(`${base}/analytics/record`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
canonical_id: v.component_id,
success: v.success,
duration_ms: v.duration_ms
})
}).catch(() => void 0)
// 統計失敗不影響執行
)
);
} catch {
}
}
var init_execution_evaluator = __esm({
"src/actions/execution-evaluator.ts"() {
"use strict";
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();
__name(componentVerdictsFromTrace, "componentVerdictsFromTrace");
__name(recordComponentStats, "recordComponentStats");
}
});
// src/actions/webhook-handlers.ts
var webhook_handlers_exports = {};
__export(webhook_handlers_exports, {
@@ -9504,6 +9559,15 @@ async function executeWebhookGraph(env2, graph, triggerContext, token, apiKey, c
agent_user_agent: userAgent
}, ctx);
recordRecipeStats(env2, executor.usedRecipeKeys, true, Date.now(), ctx);
{
const statsPromise = recordComponentStats(
env2,
parsed.data.nodes,
result.trace
);
if (ctx?.waitUntil) ctx.waitUntil(statsPromise);
else void statsPromise;
}
return { success: true, data: result.data, duration_ms };
} catch (err) {
const duration_ms = Date.now() - start;
@@ -9519,6 +9583,15 @@ async function executeWebhookGraph(env2, graph, triggerContext, token, apiKey, c
if (!isPaused) {
recordRecipeStats(env2, executor.usedRecipeKeys, false, Date.now(), ctx);
}
if (!isPaused && err instanceof ExecutionError) {
const statsPromise = recordComponentStats(
env2,
parsed.data.nodes,
err.trace
);
if (ctx?.waitUntil) ctx.waitUntil(statsPromise);
else void statsPromise;
}
if (err instanceof ExecutionError) {
const traceFormatted = err.trace.map((s) => ({
node: s.nodeId,
@@ -9546,6 +9619,7 @@ var init_webhook_handlers = __esm({
init_schemas();
init_component_loader();
init_telemetry();
init_execution_evaluator();
__name(recordRecipeStats, "recordRecipeStats");
__name(generateToken, "generateToken");
__name(validateAndParseWebhook, "validateAndParseWebhook");
@@ -9909,17 +9983,7 @@ init_types();
init_graph_executor();
init_schemas();
init_component_loader();
// src/actions/execution-evaluator.ts
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_process();
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_console();
init_performance2();
async function writeEvaluation(_env, _record) {
}
__name(writeEvaluation, "writeEvaluation");
async function updateComponentStats(_env, _componentId, _verdict, _durationMs) {
}
__name(updateComponentStats, "updateComponentStats");
init_execution_evaluator();
// src/actions/triplet-parser.ts
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_process();
@@ -9975,7 +10039,7 @@ init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_console();
init_performance2();
init_component_loader();
init_recipes();
async function searchNodes(parsed, config2, env2, mode = "discover") {
async function searchNodes(parsed, config2, env2, mode = "discover", target) {
const nodeResults = {};
const missingNodes = [];
if (mode === "compile") {
@@ -9996,8 +10060,10 @@ async function searchNodes(parsed, config2, env2, mode = "discover") {
}
const sub = env2?.WORKER_SUBDOMAIN;
const registryBase = env2?.REGISTRY_BASE_URL ?? (sub ? wasmWorkerUrl("registry", sub) : void 0);
const catalog = registryBase ? await fetchCatalog(registryBase) : { status: "unreachable", entries: [] };
const recipes = env2?.RECIPES ? await listAllRecipes2(env2.RECIPES) : [];
const wantComponents = target !== "recipe";
const wantRecipes = target !== "component";
const catalog = !wantComponents ? { status: "ok", entries: [] } : registryBase ? await fetchCatalog(registryBase) : { status: "unreachable", entries: [] };
const recipes = wantRecipes && env2?.RECIPES ? await listAllRecipes2(env2.RECIPES) : [];
const byId = /* @__PURE__ */ new Map();
for (const e of catalog.entries) {
const prev = byId.get(e.canonical_id);
@@ -10051,6 +10117,11 @@ async function searchNodes(parsed, config2, env2, mode = "discover") {
};
continue;
}
const substituted = trySubstitution(nodeName, catalog.entries, recipes);
if (substituted) {
nodeResults[nodeName] = { ...substituted, type: role };
continue;
}
const similarComponents = similarFromCatalog(catalog.entries, nodeName);
const similarRecipes = similarFromRecipes(recipes, nodeName);
nodeResults[nodeName] = {
@@ -10166,6 +10237,64 @@ async function legacyPerNodeLookup(registryBase, componentId, nodeName, role, en
};
}
__name(legacyPerNodeLookup, "legacyPerNodeLookup");
function trySubstitution(nodeName, catalogEntries, recipes) {
const lower = nodeName.toLowerCase();
const serviceHits = SERVICE_HINTS.filter((w) => lower.includes(w));
if (serviceHits.length > 0) {
const matched = /* @__PURE__ */ new Map();
for (const r of recipes) {
const hay = `${r.canonical_id} ${r.display_name ?? ""} ${r.description ?? ""}`.toLowerCase();
if (serviceHits.every((h) => hay.includes(h))) matched.set(r.canonical_id, r);
}
if (matched.size !== 1) return null;
const recipe = [...matched.values()][0];
return {
status: "resolved",
componentId: recipe.canonical_id,
source: "recipe",
description: recipe.description,
endpoint: recipe.endpoint,
substitution: {
from: nodeName,
componentId: "http_request",
// recipehttp_request+參數模板的具名封裝
recipe: recipe.canonical_id,
reason: `\u670D\u52D9\u8A5E\u300C${serviceHits.join("\u3001")}\u300D\u552F\u4E00\u547D\u4E2D recipe\u300C${recipe.canonical_id}\u300D\uFF1Bworkflow config \u5BEB component: ${recipe.canonical_id}\uFF08\u5E95\u5C64\u96F6\u4EF6\uFF1Dhttp_request\uFF09\uFF0C\u53EA\u9700\u586B payload`
}
};
}
const tokens = extractTokens(nodeName);
if (tokens.length === 0) return null;
const byCanonical = /* @__PURE__ */ new Map();
for (const e of catalogEntries) {
const strongHay = [e.canonical_id, e.display_name ?? "", ...e.aliases ?? []].join(" ").toLowerCase();
const weakHay = [e.description ?? "", ...e.tags ?? []].join(" ").toLowerCase();
const strongHits = tokens.filter((t) => strongHay.includes(t));
const weakCount = tokens.filter((t) => weakHay.includes(t)).length;
const score = strongHits.length * 10 + weakCount;
if (score === 0) continue;
const prev = byCanonical.get(e.canonical_id);
if (!prev || score > prev.score) byCanonical.set(e.canonical_id, { entry: e, score, strongHits });
}
const ranked = [...byCanonical.values()].sort((a, b) => b.score - a.score);
const top = ranked[0];
if (!top || top.strongHits.length === 0) return null;
if (ranked[1] && ranked[1].score >= top.score) return null;
return {
status: "resolved",
componentId: top.entry.canonical_id,
source: "component",
input_schema: top.entry.input_schema,
success_rate: typeof top.entry.success_rate === "number" ? top.entry.success_rate : void 0,
stability: typeof top.entry.stability === "string" ? top.entry.stability : void 0,
substitution: {
from: nodeName,
componentId: top.entry.canonical_id,
reason: `\u65B7\u8A5E\u300C${top.strongHits.join("\u3001")}\u300D\u547D\u4E2D\u96F6\u4EF6\u300C${top.entry.canonical_id}\u300D\uFF08${top.entry.display_name ?? ""}\uFF09\u5F37\u6B04\u4F4D\u4E14\u5206\u6578\u552F\u4E00\u6700\u9AD8\uFF1B\u53EA\u9700\u7167 input_schema \u586B payload`
}
};
}
__name(trySubstitution, "trySubstitution");
var SERVICE_HINTS = [
"google",
"gmail",
@@ -10357,12 +10486,12 @@ function buildExecutionGraph(parsed, nodeResults, graphId, graphName, config2) {
__name(buildExecutionGraph, "buildExecutionGraph");
// src/actions/cypher-handlers.ts
async function handleCypherSearch(triplets, env2, mode = "discover") {
async function handleCypherSearch(triplets, env2, mode = "discover", target) {
const parsed = parseTriplets(triplets);
if (!parsed) {
throw new Error("\u7121\u6CD5\u89E3\u6790\u4EFB\u4F55\u7BC0\u9EDE");
}
const { nodeResults, missingNodes } = await searchNodes(parsed, void 0, env2, mode);
const { nodeResults, missingNodes } = await searchNodes(parsed, void 0, env2, mode, target);
const graph = buildExecutionGraph(parsed, nodeResults, "cypher-search-result", "Cypher Search Result");
return { nodes: nodeResults, cypher: { nodes: graph.nodes, edges: graph.edges }, missing: missingNodes };
}
@@ -10384,17 +10513,7 @@ async function handleCypherExecute(triplets, context2, graphId, graphName, confi
try {
const result = await executor.execute(parseResult.data, context2 ?? {}, env2.EXEC_CONTEXT);
const duration_ms = Date.now() - start;
const componentId = graph.nodes.find((n) => n.componentId)?.componentId ?? graphId;
const runId = `${graphId}-${Date.now()}`;
waitUntil(writeEvaluation(env2, {
run_id: runId,
workflow_id: graphId,
component_id: componentId,
verdict: "success",
duration_ms,
evaluated_at: Date.now()
}));
waitUntil(updateComponentStats(env2, componentId, "success", duration_ms));
waitUntil(recordComponentStats(env2, graph.nodes, result.trace));
return { success: true, data: result.data, trace: result.trace, duration_ms, graph };
} catch (err) {
const duration_ms = Date.now() - start;
@@ -10411,19 +10530,8 @@ async function handleCypherExecute(triplets, context2, graphId, graphName, confi
};
}
const errMsg = err instanceof Error ? err.message : String(err);
const componentId = graph.nodes.find((n) => n.componentId)?.componentId ?? graphId;
const runId = `${graphId}-${Date.now()}`;
waitUntil(writeEvaluation(env2, {
run_id: runId,
workflow_id: graphId,
component_id: componentId,
verdict: "failed",
duration_ms,
error_message: errMsg.slice(0, 200),
evaluated_at: Date.now()
}));
waitUntil(updateComponentStats(env2, componentId, "failed", duration_ms));
if (err instanceof ExecutionError) {
waitUntil(recordComponentStats(env2, graph.nodes, err.trace));
const traceFormatted = err.trace.map((s) => ({
node: s.nodeId,
status: s.error ? "failed" : "success",
@@ -10443,20 +10551,130 @@ async function handleCypherExecute(triplets, context2, graphId, graphName, confi
}
__name(handleCypherExecute, "handleCypherExecute");
// src/actions/target-search.ts
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();
// src/lib/workflow-search.ts
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_process();
init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_console();
init_performance2();
async function fetchTenantWorkflowSearch(env2, apiKey, q, mode = "semantic") {
const base = (env2.KBDB_BASE_URL ?? "https://arcrun-kbdb.uncle6-me.workers.dev").replace(/\/$/, "");
const headers = { "Content-Type": "application/json" };
if (env2.KBDB_INTERNAL_TOKEN) headers["Authorization"] = `Bearer ${env2.KBDB_INTERNAL_TOKEN}`;
const params = new URLSearchParams({
q,
owner_id: apiKey,
// 租戶隔離(只搜本租戶的 workflow)
entry_type: "workflow",
// base 通用 filterQ4),只回 workflow entry
mode
});
return fetch(`${base}/entries/search?${params.toString()}`, { headers });
}
__name(fetchTenantWorkflowSearch, "fetchTenantWorkflowSearch");
// src/actions/target-search.ts
async function searchByTarget(target, query, env2, apiKey) {
if (target === "component") {
const sub = env2.WORKER_SUBDOMAIN;
const registryBase = env2.REGISTRY_BASE_URL ?? (sub ? wasmWorkerUrl("registry", sub) : void 0);
if (!registryBase) return { ok: false, status: 502, error: "registry \u4F4D\u7F6E\u672A\u8A2D\u5B9A\uFF08WORKER_SUBDOMAIN\uFF0FREGISTRY_BASE_URL \u7686\u7F3A\uFF09" };
try {
const res2 = await fetch(
`${registryBase}/components/search?q=${encodeURIComponent(query)}`,
{ signal: AbortSignal.timeout(1e4) }
);
if (!res2.ok) return { ok: false, status: 502, error: `registry \u641C\u5C0B\u5931\u6557\uFF08HTTP ${res2.status}\uFF09` };
const body2 = await res2.json();
return {
ok: true,
body: {
target,
query,
results: body2.data?.results ?? [],
count: body2.data?.count ?? 0
}
};
} catch (e) {
return { ok: false, status: 502, error: `registry \u67E5\u4E0D\u901A\uFF1A${e instanceof Error ? e.message : String(e)}` };
}
}
if (target === "recipe") {
if (!env2.RECIPES) return { ok: false, status: 502, error: "RECIPES KV \u672A\u7D81\u5B9A" };
const all = await listAllRecipes2(env2.RECIPES);
const q = query.toLowerCase();
const seen = /* @__PURE__ */ new Set();
const results = [];
for (const r of all) {
if (seen.has(r.canonical_id)) continue;
const hay = `${r.canonical_id} ${r.display_name ?? ""} ${r.description ?? ""}`.toLowerCase();
if (!hay.includes(q)) continue;
seen.add(r.canonical_id);
results.push({
canonical_id: r.canonical_id,
display_name: r.display_name,
description: r.description,
endpoint: r.endpoint
});
}
return {
ok: true,
body: {
target,
query,
results,
count: results.length,
note: "\u641C\u7684\u662F\u672C\u90E8\u7F72\u79C1\u5EAB\uFF08workflow \u53EF\u76F4\u63A5 component: <canonical_id> \u5F15\u7528\uFF09\u3002\u516C\u5EAB\uFF08\u591A\u4F5C\u8005\u5E02\u5834\uFF09\u8D70 MCP arcrun_recipe_search\uFF0FGET /public-recipes\u3002"
}
};
}
if (!apiKey) return { ok: false, status: 401, error: "target=workflow \u9700\u8981 X-Arcrun-API-Key header\uFF08workflow \u641C\u5C0B\u9650\u672C\u79DF\u6236\uFF09" };
const res = await fetchTenantWorkflowSearch(env2, apiKey, query);
if (!res.ok) return { ok: false, status: 502, error: `workflow \u641C\u5C0B\u5931\u6557\uFF08KBDB HTTP ${res.status}\uFF09` };
const body = await res.json();
return { ok: true, body: { target, query, ...body } };
}
__name(searchByTarget, "searchByTarget");
// src/routes/cypher.ts
var cypherRouter = new Hono2();
var VALID_TARGETS = /* @__PURE__ */ new Set(["component", "recipe", "workflow"]);
cypherRouter.post("/cypher/search", async (c) => {
const body = await c.req.json();
const rawTriplets = body?.triplets;
const target = typeof body?.target === "string" ? body.target : void 0;
if (target !== void 0 && !VALID_TARGETS.has(target)) {
return c.json({ error: `target \u53EA\u63A5\u53D7 component\uFF0Frecipe\uFF0Fworkflow\uFF0C\u6536\u5230\u300C${target}\u300D` }, 400);
}
const query = typeof body?.query === "string" ? body.query.trim() : "";
if (query) {
if (!target) {
return c.json({ error: "\u7D66 query \u5FC5\u9808\u540C\u6642\u7D66 target\uFF08component\uFF0Frecipe\uFF0Fworkflow\uFF09\uFF0C\u6307\u660E\u8981\u641C\u54EA\u500B\u5EAB" }, 400);
}
const apiKey = c.req.header("X-Arcrun-API-Key") ?? void 0;
const r = await searchByTarget(target, query, c.env, apiKey);
if (!r.ok) return c.json({ error: r.error }, r.status);
return c.json(r.body);
}
if (!Array.isArray(rawTriplets) || rawTriplets.length === 0) {
return c.json({ error: "triplets \u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32\u9663\u5217" }, 400);
return c.json({ error: "triplets \u5FC5\u9808\u70BA\u975E\u7A7A\u5B57\u4E32\u9663\u5217\uFF08\u6216\u7D66 query + target \u505A\u540D\u5B57\u641C\u5C0B\uFF09" }, 400);
}
const mode = body?.mode === "compile" ? "compile" : "discover";
if (target && mode === "compile") {
return c.json({ error: "mode=compile\uFF08\u8907\u88FD\u8DEF\u5F91\uFF09\u4E0D\u67E5\u5EAB\uFF0C\u4E0D\u63A5\u53D7 target\uFF1B\u8981\u6307\u5B9A\u641C\u5C0B\u5C0D\u8C61\u8ACB\u7528 discover\uFF08\u9810\u8A2D\uFF09" }, 400);
}
if (target === "workflow") {
return c.json({ error: 'target=workflow \u662F\u540D\u5B57\u641C\u5C0B\uFF0C\u8ACB\u6539\u5E36 { target: "workflow", query: "..." }\uFF08\u4E0D\u5403 triplets\uFF09' }, 400);
}
try {
const now2 = /* @__PURE__ */ new Date();
const timestamp = now2.toISOString();
const versionId = `search-v1-${now2.getFullYear()}${String(now2.getMonth() + 1).padStart(2, "0")}${String(now2.getDate()).padStart(2, "0")}-${String(now2.getHours()).padStart(2, "0")}${String(now2.getMinutes()).padStart(2, "0")}${String(now2.getSeconds()).padStart(2, "0")}`;
const result = await handleCypherSearch(rawTriplets, c.env, mode);
const result = await handleCypherSearch(rawTriplets, c.env, mode, target);
const response = {
version: versionId,
timestamp,
@@ -11404,18 +11622,7 @@ webhooksNamedRouter.get("/workflows/search", async (c) => {
const q = c.req.query("q");
if (!q) return c.json({ error: "q \u5FC5\u586B\uFF1A\u7528\u81EA\u7136\u8A9E\u8A00\u63CF\u8FF0\u8981\u627E\u7684\u5DE5\u4F5C\u6D41\uFF08\u5982\u300C\u628A\u8CC7\u6599\u5BEB\u9032 Google Sheets\u300D\uFF09" }, 400);
const mode = c.req.query("mode") === "keyword" ? "keyword" : "semantic";
const base = (c.env.KBDB_BASE_URL ?? "https://arcrun-kbdb.uncle6-me.workers.dev").replace(/\/$/, "");
const headers = { "Content-Type": "application/json" };
if (c.env.KBDB_INTERNAL_TOKEN) headers["Authorization"] = `Bearer ${c.env.KBDB_INTERNAL_TOKEN}`;
const params = new URLSearchParams({
q,
owner_id: apiKey,
// 租戶隔離(只搜本租戶的 workflow)
entry_type: "workflow",
// base 通用 filterQ4),只回 workflow entry
mode
});
const res = await fetch(`${base}/entries/search?${params.toString()}`, { headers });
const res = await fetchTenantWorkflowSearch(c.env, apiKey, q, mode);
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
});
webhooksNamedRouter.post("/workflows/backfill-search-entries", async (c) => {
@@ -14003,43 +14210,6 @@ portalRouter.get(
return c.json({ success: true, libraries: libs.map(toPublicLibrary), count: libs.length });
})
);
portalRouter.post(
"/portal/admin/libraries",
(c) => run(c, async () => {
const auth = await requirePortalAdmin(c);
if (!auth.ok) return auth.res;
const body = await c.req.json().catch(() => null);
const name = String(body?.name ?? "").trim();
if (!isValidLibraryName(name) || name === "*") {
return c.json({ error: '\u5EAB\u540D\u9650 A-Za-z0-9_-\uFF081-64 \u5B57\u5143\uFF1B"*" \u662F\u4FDD\u7559\u503C\u4E0D\u53EF\u767B\u8A18\uFF09' }, 400);
}
const seeded = await ensurePortalTemplates(c.env);
if (seeded.errors.length > 0) {
return c.json({ error: `portal templates seed \u5931\u6557\uFF1A${seeded.errors.join("; ")}` }, 502);
}
const existing = await listRecordsByTemplate(c.env, LIBRARY_TEMPLATE);
if (existing.some((l) => (l.values.name ?? "") === name)) {
return c.json({ error: `\u5EAB ${name} \u5DF2\u767B\u8A18` }, 409);
}
const ns = portalNamespace(c.env);
const res = await kbdbFetch(c.env, "/records", {
method: "POST",
body: JSON.stringify({
template: LIBRARY_TEMPLATE,
owner_id: ns,
values: {
name,
display_name: String(body?.display_name ?? "").trim() || name,
description: String(body?.description ?? "").trim(),
status: "active"
}
})
});
if (!res.ok) throw new KbdbError(`POST /records\uFF08portal_library\uFF09\u2192 ${res.status}`);
const created = await res.json();
return c.json({ success: true, library: created.record ? toPublicLibrary(created.record) : { name } });
})
);
portalRouter.post(
"/portal/daemon/libraries",
(c) => run(c, async () => {
+111 -13
View File
@@ -2099,14 +2099,14 @@ var Hono = class _Hono {
* app.route("/api", app2) // GET /api/user
* ```
*/
route(path, app7) {
route(path, app8) {
const subApp = this.basePath(path);
app7.routes.map((r) => {
app8.routes.map((r) => {
let handler;
if (app7.errorHandler === errorHandler) {
if (app8.errorHandler === errorHandler) {
handler = r.handler;
} else {
handler = /* @__PURE__ */ __name(async (c, next) => (await compose([], app7.errorHandler)(c, () => r.handler(c, next))).res, "handler");
handler = /* @__PURE__ */ __name(async (c, next) => (await compose([], app8.errorHandler)(c, () => r.handler(c, next))).res, "handler");
handler[COMPOSED_HANDLER] = r.handler;
}
subApp.#addRoute(r.method, r.path, handler);
@@ -8195,16 +8195,114 @@ app5.post("/", async (c) => {
});
var init_default = app5;
// src/index.ts
// src/actions/recordAnalytics.ts
async function recordAnalytics(input, env2) {
const hashId = input.canonical_id.startsWith("cmp_") ? input.canonical_id : await env2.SUBMISSIONS_KV.get(`idx:${input.canonical_id}`);
if (!hashId) {
return { ok: false, error: `\u96F6\u4EF6 ${input.canonical_id} \u4E0D\u5728\u7D22\u5F15` };
}
const list = await env2.SUBMISSIONS_KV.list({ prefix: `comp:${hashId}:` });
let targetKey = null;
let targetRecord = null;
let bestScore = -Infinity;
for (const key of list.keys) {
const raw2 = await env2.SUBMISSIONS_KV.get(key.name);
if (!raw2) continue;
let v;
try {
v = JSON.parse(raw2);
} catch {
continue;
}
if (v.status === "tombstone") continue;
if (input.version) {
if (String(v.version) === input.version) {
targetKey = key.name;
targetRecord = v;
break;
}
continue;
}
const score = computeScore(v);
if (score > bestScore) {
bestScore = score;
targetKey = key.name;
targetRecord = v;
}
}
if (!targetKey || !targetRecord) {
return { ok: false, error: `\u96F6\u4EF6 ${input.canonical_id} \u7121\u53EF\u7528\u7248\u672C\u8A18\u9304` };
}
const version2 = String(targetRecord.version ?? "v1");
const canonicalId = String(targetRecord.canonical_id ?? input.canonical_id);
const statsKey = `stats:${hashId}:${version2}`;
let counters = { total_runs: 0, success_runs: 0, total_ms: 0 };
const rawStats = await env2.ANALYTICS_KV.get(statsKey);
if (rawStats) {
try {
const parsed = JSON.parse(rawStats);
counters = {
total_runs: Number(parsed.total_runs) || 0,
success_runs: Number(parsed.success_runs) || 0,
total_ms: Number(parsed.total_ms) || 0
};
} catch {
}
}
counters.total_runs += 1;
counters.success_runs += input.success ? 1 : 0;
counters.total_ms += Math.max(0, Number(input.duration_ms) || 0);
await env2.ANALYTICS_KV.put(statsKey, JSON.stringify(counters));
const successRate = counters.success_runs / counters.total_runs;
const avgDurationMs = Math.round(counters.total_ms / counters.total_runs);
targetRecord.success_rate = successRate;
targetRecord.avg_duration_ms = avgDurationMs;
targetRecord.call_count = counters.total_runs;
await env2.SUBMISSIONS_KV.put(targetKey, JSON.stringify(targetRecord));
return {
ok: true,
canonical_id: canonicalId,
version: version2,
total_runs: counters.total_runs,
success_runs: counters.success_runs,
success_rate: successRate,
avg_duration_ms: avgDurationMs
};
}
__name(recordAnalytics, "recordAnalytics");
// src/routes/analytics.ts
var app6 = new Hono2();
app6.use("*", cors());
app6.get("/", (c) => c.json({ service: "component-registry", version: "1.0.0", status: "ok" }));
app6.route("/components/guide", guide_default);
app6.route("/components/validate-contract", validateContract_default);
app6.route("/components", query_default);
app6.route("/components", components_default);
app6.route("/init", init_default);
var index_default = app6;
app6.post("/record", async (c) => {
const body = await c.req.json().catch(() => null);
if (!body || typeof body.canonical_id !== "string" || body.canonical_id.trim() === "") {
return c.json({ ok: false, error: "canonical_id \u5FC5\u586B" }, 400);
}
if (typeof body.success !== "boolean") {
return c.json({ ok: false, error: "success \u5FC5\u9808\u70BA boolean" }, 400);
}
const result = await recordAnalytics({
canonical_id: body.canonical_id.trim(),
version: typeof body.version === "string" && body.version !== "" ? body.version : void 0,
success: body.success,
duration_ms: typeof body.duration_ms === "number" ? body.duration_ms : 0
}, c.env);
if (!result.ok) return c.json(result, 404);
return c.json(result);
});
var analytics_default = app6;
// src/index.ts
var app7 = new Hono2();
app7.use("*", cors());
app7.get("/", (c) => c.json({ service: "component-registry", version: "1.0.0", status: "ok" }));
app7.route("/components/guide", guide_default);
app7.route("/components/validate-contract", validateContract_default);
app7.route("/components", query_default);
app7.route("/components", components_default);
app7.route("/init", init_default);
app7.route("/analytics", analytics_default);
var index_default = app7;
export {
index_default as default
};