ship 1.4.59:Arcrun@f87260b1234f
This commit is contained in:
@@ -3195,15 +3195,6 @@ function kbdbBase(env) {
|
||||
function tenant(c) {
|
||||
return c.req.header("X-Arcrun-API-Key") ?? null;
|
||||
}
|
||||
function graphBase(env) {
|
||||
if (env.KBDB_GRAPH_URL) return env.KBDB_GRAPH_URL.replace(/\/$/, "");
|
||||
return `https://kbdb-graph-plugin.${env.WORKER_SUBDOMAIN}.workers.dev`;
|
||||
}
|
||||
function graphHeaders(env) {
|
||||
const headers = {};
|
||||
if (env.KBDB_INTERNAL_TOKEN) headers["Authorization"] = `Bearer ${env.KBDB_INTERNAL_TOKEN}`;
|
||||
return headers;
|
||||
}
|
||||
var kbdbProxyRouter, NEED_KEY;
|
||||
var init_kbdb_proxy = __esm({
|
||||
"cypher-executor/src/routes/kbdb-proxy.ts"() {
|
||||
@@ -3359,14 +3350,23 @@ var init_kbdb_proxy = __esm({
|
||||
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
|
||||
});
|
||||
kbdbProxyRouter.get("/kbdb/graph/neighbors/:name", async (c) => {
|
||||
if (!tenant(c)) return c.json(NEED_KEY, 401);
|
||||
const base = graphBase(c.env);
|
||||
const headers = graphHeaders(c.env);
|
||||
const owner = tenant(c);
|
||||
if (!owner) return c.json(NEED_KEY, 401);
|
||||
const { base, headers } = kbdbBase(c.env);
|
||||
const params = new URLSearchParams();
|
||||
params.set("owner_id", owner);
|
||||
for (const k of ["depth", "template", "directed"]) {
|
||||
const v = c.req.query(k);
|
||||
if (v) params.set(k, v);
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`${base}/graph/neighbors/${encodeURIComponent(c.req.param("name"))}`, { headers });
|
||||
const res = await fetch(
|
||||
`${base}/graph/neighbors/${encodeURIComponent(c.req.param("name"))}?${params.toString()}`,
|
||||
{ headers }
|
||||
);
|
||||
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
|
||||
} catch (e) {
|
||||
return c.json({ error: `kbdb-graph-plugin \u4E0D\u53EF\u9054\uFF08${base}\uFF09\uFF1A${e instanceof Error ? e.message : String(e)}` }, 502);
|
||||
return c.json({ error: `KBDB \u4E0D\u53EF\u9054\uFF08${base}\uFF09\uFF1A${e instanceof Error ? e.message : String(e)}` }, 502);
|
||||
}
|
||||
});
|
||||
kbdbProxyRouter.get("/kbdb/map", async (c) => {
|
||||
@@ -15047,7 +15047,6 @@ consoleDashboardRouter.get("/console/dashboard-data", async (c) => {
|
||||
const tenant2 = knowledgeOwner(c.env);
|
||||
const now2 = Date.now();
|
||||
const { base: kbdbUrl, headers: kbdbHeaders2 } = kbdbBase(c.env);
|
||||
const graphUrl = graphBase(c.env);
|
||||
const [
|
||||
beatEntries,
|
||||
taskEntries,
|
||||
@@ -15056,7 +15055,7 @@ consoleDashboardRouter.get("/console/dashboard-data", async (c) => {
|
||||
giteaSprint,
|
||||
kbdbHealth,
|
||||
embedStatus,
|
||||
graphStats,
|
||||
graphProbe,
|
||||
tripletTotal,
|
||||
entriesTotal,
|
||||
wikiCardTotal,
|
||||
@@ -15069,11 +15068,15 @@ consoleDashboardRouter.get("/console/dashboard-data", async (c) => {
|
||||
cachedGiteaSprint(c.env, now2, (p) => c.executionCtx.waitUntil(p)),
|
||||
fetchJson(`${kbdbUrl}/health`, kbdbHeaders2),
|
||||
fetchJson(`${kbdbUrl}/embed/backfill/status`, kbdbHeaders2),
|
||||
// graph-plugin 只拿來判「圖服務活著沒」(燈號)——數字不從這裡拿,見 fetchTripletTotal。
|
||||
// headers 一定要帶:plugin 的 /triplets 前綴掛 Bearer 閘,漏帶=永遠 401=永遠假紅燈(#100)。
|
||||
// 圖服務活著沒(燈號)——數字不從這裡拿,見 fetchTripletTotal。
|
||||
// 🔴 inkstone/Arcrun#168 收斂:原本探的是 kbdb-graph-plugin 的 /triplets/stats,
|
||||
// 但圖能力已收斂回 KBDB(GET /graph/neighbors/:node)⇒ 探那顆 plugin 等於在量一個
|
||||
// 沒有人走的服務:它沒裝就永遠紅燈、裝了也只證明一個不再被使用的東西活著。
|
||||
// 改探 KBDB 那支端點本身——用一個不存在的節點名,它會誠實回 count:0 的 200
|
||||
//(graph-query.ts:查不到就是空結果,不是錯誤),正好當「這條路通不通」的探針。
|
||||
fetchJson(
|
||||
`${graphUrl}/triplets/stats`,
|
||||
graphHeaders(c.env)
|
||||
`${kbdbUrl}/graph/neighbors/${encodeURIComponent("__arcrun_graph_probe__")}?depth=1`,
|
||||
kbdbHeaders2
|
||||
),
|
||||
fetchTripletTotal(c.env, tenant2),
|
||||
// owner_id 一律鎖本租戶:原本不帶 owner 會混到別租戶(實測 459,137 vs leo 的 458,732)
|
||||
@@ -15183,8 +15186,8 @@ consoleDashboardRouter.get("/console/dashboard-data", async (c) => {
|
||||
system: {
|
||||
kbdb_ok: kbdbHealth ? kbdbHealth.ok === true : false,
|
||||
embed: embedStatus ? { enabled: embedStatus.enabled === true, embedded: embedStatus.embedded ?? null, pending: embedStatus.pending ?? null } : null,
|
||||
// ok = plugin 通不通(graphStats 讀得到就是通);triplets = KBDB 真 COUNT(與 plugin 分頁長度無關)
|
||||
graph: { ok: graphStats !== null, triplets: tripletTotal },
|
||||
// ok = 圖查詢通不通(探針讀得到就是通);triplets = KBDB 真 COUNT(兩件事,不互相吞)
|
||||
graph: { ok: graphProbe !== null, triplets: tripletTotal },
|
||||
workflow_total: workflowTotal
|
||||
},
|
||||
kb: {
|
||||
@@ -15270,7 +15273,6 @@ consoleDashboardRouter.post("/console/triage-check", async (c) => {
|
||||
|
||||
// cypher-executor/src/routes/portal-data.ts
|
||||
init_dist();
|
||||
init_kbdb_proxy();
|
||||
init_webhook_handlers();
|
||||
|
||||
// cypher-executor/src/lib/app-system.ts
|
||||
@@ -17001,7 +17003,7 @@ function unwrapWorkflowData(data, key) {
|
||||
}
|
||||
return outer;
|
||||
}
|
||||
function mapGraphWorkflowOutput(data) {
|
||||
function mapGraphNeighborsResponse(data) {
|
||||
const layer = unwrapWorkflowData(data, "neighbors");
|
||||
const neighbors = Array.isArray(layer.neighbors) ? layer.neighbors : [];
|
||||
const edges = Array.isArray(layer.edges) ? layer.edges : [];
|
||||
@@ -17168,6 +17170,14 @@ portalDataRouter.get(
|
||||
return c.json({ success: true, entry });
|
||||
})
|
||||
);
|
||||
async function fetchNeighborsFromKbdb(env, tenant2, node, depth) {
|
||||
const qs = new URLSearchParams();
|
||||
qs.set("depth", String(depth));
|
||||
qs.set("template", "triplet");
|
||||
const res = await kbdbFetch(env, `/graph/neighbors/${encodeURIComponent(node)}?${qs.toString()}&${ownerQuery(tenant2)}`);
|
||||
const body = await res.json().catch(() => null);
|
||||
return { ok: res.ok, status: res.status, body };
|
||||
}
|
||||
portalDataRouter.get(
|
||||
"/portal/data/graph/neighbors/:name",
|
||||
(c) => run(c, async () => {
|
||||
@@ -17177,50 +17187,41 @@ portalDataRouter.get(
|
||||
if (!await hasGraphAccess(c.env, libraries)) {
|
||||
return c.json({ error: "\u7121\u77E5\u8B58\u5716\u8B5C\u6AA2\u8996\u6B0A\u9650" }, 403);
|
||||
}
|
||||
const nodeName = normalizeCjkQuery(c.req.param("name"));
|
||||
const tenant2 = knowledgeOwner(c.env);
|
||||
const wfGraph = await getTenantWorkflowGraph(c.env, "graph_neighbors");
|
||||
if (wfGraph) {
|
||||
const depthRaw = c.req.query("depth") ?? "";
|
||||
const depth = /^\d{1,2}$/.test(depthRaw) ? Number(depthRaw) : 2;
|
||||
const result = await executeWebhookGraph(
|
||||
c.env,
|
||||
wfGraph,
|
||||
// t116: 補傳 kbdb_base;t128: 補傳 template(workflow fetch_triplets.url 用 {{input.template}})
|
||||
{ node: nodeName, depth, namespace: tenant2, owner: tenant2, kbdb_base: c.env.KBDB_BASE_URL ?? "", template: "triplet" },
|
||||
"graph_neighbors",
|
||||
tenant2,
|
||||
c.executionCtx
|
||||
);
|
||||
if (!result.success) {
|
||||
return c.json({ error: `graph_neighbors workflow \u57F7\u884C\u5931\u6557\uFF1A${result.error ?? "\u672A\u77E5\u932F\u8AA4"}` }, 502);
|
||||
}
|
||||
return c.json(mapGraphWorkflowOutput(result.data));
|
||||
}
|
||||
const base = graphBase(c.env);
|
||||
const headers = graphHeaders(c.env);
|
||||
const rawName = c.req.param("name");
|
||||
const depthRaw = c.req.query("depth") ?? "";
|
||||
const depth = /^\d{1,2}$/.test(depthRaw) ? Number(depthRaw) : 2;
|
||||
const tryNames = [rawName];
|
||||
const normalized = normalizeCjkQuery(rawName);
|
||||
if (normalized !== rawName) tryNames.push(normalized);
|
||||
let first = null;
|
||||
try {
|
||||
const res = await fetch(`${base}/graph/neighbors/${encodeURIComponent(nodeName)}`, { headers });
|
||||
if (!res.ok) {
|
||||
return new Response(res.body, { status: res.status, headers: { "Content-Type": "application/json" } });
|
||||
for (const name of tryNames) {
|
||||
const r = await fetchNeighborsFromKbdb(c.env, tenant2, name, depth);
|
||||
if (!first) first = r;
|
||||
if (!r.ok) break;
|
||||
const mapped = mapGraphNeighborsResponse(r.body);
|
||||
if (mapped.count > 0) return c.json(mapped);
|
||||
}
|
||||
const resText = await res.text().catch(() => "");
|
||||
let data = null;
|
||||
try {
|
||||
data = JSON.parse(resText);
|
||||
} catch {
|
||||
}
|
||||
if (data && Array.isArray(data.neighbors) && data.neighbors.length === 0 && Array.isArray(data.edges) && data.edges.length === 0) {
|
||||
const fallbackName = await fuzzyFindNode(c.env, tenant2, nodeName);
|
||||
if (fallbackName && fallbackName !== nodeName) {
|
||||
const res2 = await fetch(`${base}/graph/neighbors/${encodeURIComponent(fallbackName)}`, { headers });
|
||||
return new Response(res2.body, { status: res2.status, headers: { "Content-Type": "application/json" } });
|
||||
if (first?.ok) {
|
||||
const fallbackName = await fuzzyFindNode(c.env, tenant2, rawName);
|
||||
if (fallbackName && !tryNames.includes(fallbackName)) {
|
||||
const r = await fetchNeighborsFromKbdb(c.env, tenant2, fallbackName, depth);
|
||||
if (r.ok) {
|
||||
const mapped = mapGraphNeighborsResponse(r.body);
|
||||
if (mapped.count > 0) return c.json(mapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new Response(resText, { status: res.status, headers: { "Content-Type": "application/json" } });
|
||||
} catch (e) {
|
||||
return c.json({ error: `kbdb-graph-plugin \u4E0D\u53EF\u9054\uFF1A${e instanceof Error ? e.message : String(e)}` }, 502);
|
||||
return c.json({ error: `KBDB \u5716\u8B5C\u67E5\u8A62\u4E0D\u53EF\u9054\uFF1A${e instanceof Error ? e.message : String(e)}` }, 502);
|
||||
}
|
||||
if (!first) return c.json({ error: "KBDB \u5716\u8B5C\u67E5\u8A62\u6C92\u6709\u56DE\u61C9" }, 502);
|
||||
if (!first.ok) {
|
||||
const err = first.body && typeof first.body === "object" ? first.body : { error: `KBDB \u5716\u8B5C\u67E5\u8A62\u5931\u6557\uFF08HTTP ${first.status}\uFF09` };
|
||||
return c.json(err, first.status);
|
||||
}
|
||||
return c.json(mapGraphNeighborsResponse(first.body));
|
||||
})
|
||||
);
|
||||
portalDataRouter.get(
|
||||
@@ -17538,6 +17539,29 @@ function canReadRecord(rec, tenant2, libraries) {
|
||||
const lib = recordLibrary(rec.values);
|
||||
return lib === null || canReadLibrary(libraries, lib);
|
||||
}
|
||||
function cardOriginalLocation(entries, library, libraryRoot) {
|
||||
for (const e of entries) {
|
||||
if (typeof e.metadata_json !== "string" || !e.metadata_json) continue;
|
||||
let meta;
|
||||
try {
|
||||
meta = JSON.parse(e.metadata_json);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
const sourcePath = typeof meta.source_path === "string" && meta.source_path.trim() ? meta.source_path.trim() : null;
|
||||
const machine = typeof meta.machine_label === "string" && meta.machine_label.trim() ? meta.machine_label.trim() : typeof meta.machine === "string" && meta.machine.trim() ? meta.machine.trim() : null;
|
||||
if (!sourcePath && !machine) continue;
|
||||
const root = libraryRoot && libraryRoot.trim() ? libraryRoot.trim() : null;
|
||||
return {
|
||||
machine,
|
||||
library,
|
||||
library_root: root,
|
||||
source_path: sourcePath,
|
||||
full_path: root && sourcePath ? `${root.replace(/\/+$/, "")}/${sourcePath.replace(/^\/+/, "")}` : null
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
portalDataRouter.get(
|
||||
"/portal/data/library-cards",
|
||||
(c) => run(c, async () => {
|
||||
@@ -17579,7 +17603,19 @@ portalDataRouter.get(
|
||||
}
|
||||
const entries = filterDeprecatedEntries(body.entries);
|
||||
if (entries.length === 0) return notFound(c);
|
||||
return c.json({ success: true, library, page_name: pageName, entries, count: entries.length });
|
||||
const libRecords = await listRecordsByTemplate(c.env, LIBRARY_TEMPLATE).catch(() => []);
|
||||
const libRootRaw = libRecords.find((r) => String(r.values.name ?? "") === library)?.values.root;
|
||||
const libraryRoot = typeof libRootRaw === "string" ? libRootRaw : null;
|
||||
const location = cardOriginalLocation(entries, library, libraryRoot);
|
||||
return c.json({
|
||||
success: true,
|
||||
library,
|
||||
page_name: pageName,
|
||||
entries,
|
||||
count: entries.length,
|
||||
location,
|
||||
...location ? {} : { location_hint: "\u9019\u5F35\u5361\u7684\u6BB5\u843D\u6C92\u6709 machine/source_path \u4E2D\u7E7C\u8CC7\u6599\uFF0C\u7B54\u4E0D\u51FA\u539F\u6587\u7684\u5BE6\u9AD4\u4F4D\u7F6E" }
|
||||
});
|
||||
})
|
||||
);
|
||||
portalDataRouter.get(
|
||||
@@ -17711,6 +17747,19 @@ portalDataRouter.get(
|
||||
if (!auth.ok) return auth.res;
|
||||
const libraries = parseLibraries(auth.user.values.libraries);
|
||||
if (libraries.length === 0) return c.json({ success: true, records: [], count: 0, total: 0 });
|
||||
const template = c.req.param("template");
|
||||
if (template === LIBRARY_TEMPLATE) {
|
||||
const all = await listRecordsByTemplate(c.env, LIBRARY_TEMPLATE);
|
||||
const records2 = libraries.includes("*") ? all : all.filter((r) => libraries.includes(String(r.values.name ?? "")));
|
||||
return c.json({
|
||||
success: true,
|
||||
records: records2,
|
||||
count: records2.length,
|
||||
page_size: all.length,
|
||||
filtered_out: all.length - records2.length,
|
||||
total: records2.length
|
||||
});
|
||||
}
|
||||
const tenant2 = knowledgeOwner(c.env);
|
||||
const params = new URLSearchParams(ownerQuery(tenant2));
|
||||
for (const k of ["limit", "offset"]) {
|
||||
|
||||
Reference in New Issue
Block a user