- ○載入中…
diff --git a/README.md b/README.md index 0ee58c0..0fae951 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Served via jsDelivr; fetched automatically during install — you never need to - `arcrun-wait/` — **arcrun-wait**(首裝) - `daemon/` — 桌面 App(Mac/Windows)安裝檔 -Built from `Arcrun@ad60863a8017` by `installer/scripts/ship.mjs`(arcrun-rag repo,release 1.4.58,built 2026-08-27)。 +Built from `Arcrun@f87260b1234f` by `installer/scripts/ship.mjs`(arcrun-rag repo,release 1.4.59,built 2026-08-27)。 ⚠️ 這份檔案由出貨管線每次自動重寫(`installer/scripts/render-bundles-readme.mjs`)—— 不要手動改這裡列的零件清單——它是算出來的:公庫=Arcrun 這一版編了什麼, diff --git a/arcrun-cypher-executor/worker.mjs b/arcrun-cypher-executor/worker.mjs index 560291b..5181faa 100644 --- a/arcrun-cypher-executor/worker.mjs +++ b/arcrun-cypher-executor/worker.mjs @@ -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"]) { diff --git a/arcrun-kbdb/worker.mjs b/arcrun-kbdb/worker.mjs index 26a9748..eb2ac5b 100644 --- a/arcrun-kbdb/worker.mjs +++ b/arcrun-kbdb/worker.mjs @@ -4241,11 +4241,15 @@ async function selectLibrariesForQuestion(db, question, opts = {}) { SELECT object AS name, ${libraryOf("library")} AS library FROM act WHERE object IS NOT NULL) SELECT name, library, COUNT(*) AS degree FROM ent - WHERE LENGTH(name) >= 2 AND instr(?, lower(name)) > 0 + WHERE LENGTH(name) >= 2 + AND ( + instr(?, lower(name)) > 0 + OR (LENGTH(?) >= 2 AND instr(lower(name), ?) > 0) + ) GROUP BY name, library ORDER BY degree DESC, name ASC LIMIT 200` - ).bind(...params, qNorm).all(); + ).bind(...params, qNorm, qNorm, qNorm).all(); for (const r of res.results ?? []) { const cur = hits.get(r.library) ?? { entities: /* @__PURE__ */ new Set(), score: 0 }; cur.entities.add(r.name); @@ -4545,6 +4549,109 @@ mapRoutes.get("/:library", async (c) => { return c.json({ success: true, map }); }); +// kbdb/src/actions/graph-query.ts +async function findTripletEdgesByNode(db, templateIdOrName, fields, nodeValue, owner_id) { + if (!nodeValue || fields.length === 0) return []; + const tpl = await getTemplate(db, templateIdOrName); + if (!tpl) return []; + const valueSql = owner_id ? `SELECT id FROM entries WHERE content = ? AND entry_type = 'value' AND (owner_id = ? OR owner_id IS NULL)` : `SELECT id FROM entries WHERE content = ? AND entry_type = 'value'`; + const valueParams = owner_id ? [nodeValue, owner_id] : [nodeValue]; + const valueRows = await db.prepare(valueSql).bind(...valueParams).all(); + const valueIds = (valueRows.results ?? []).map((r) => r.id); + if (valueIds.length === 0) return []; + const fieldIds = fields.map((f) => fieldEntryId(tpl.id, f)); + const dstPh = valueIds.map(() => "?").join(","); + const relPh = fieldIds.map(() => "?").join(","); + const relSql = owner_id ? `SELECT DISTINCT src_id AS record_id FROM entries + WHERE dst_id IN (${dstPh}) AND rel_id IN (${relPh}) AND owner_id = ?` : `SELECT DISTINCT src_id AS record_id FROM entries + WHERE dst_id IN (${dstPh}) AND rel_id IN (${relPh})`; + const relParams = owner_id ? [...valueIds, ...fieldIds, owner_id] : [...valueIds, ...fieldIds]; + const relRows = await db.prepare(relSql).bind(...relParams).all(); + const recordIds = (relRows.results ?? []).map((r) => r.record_id); + if (recordIds.length === 0) return []; + const recPh = recordIds.map(() => "?").join(","); + const pivotSql = ` + SELECT b.src_id AS record_id, + MAX(CASE WHEN r.rel_id = ? THEN v.content END) AS subject, + MAX(CASE WHEN r.rel_id = ? THEN v.content END) AS predicate, + MAX(CASE WHEN r.rel_id = ? THEN v.content END) AS object, + MAX(CASE WHEN r.rel_id = ? THEN v.content END) AS status + FROM entries b + LEFT JOIN entries r ON r.src_id = b.src_id AND r.rel_id != 'sys_belongs' + LEFT JOIN entries v ON v.id = r.dst_id + WHERE b.rel_id = 'sys_belongs' AND b.src_id IN (${recPh}) + GROUP BY b.src_id`; + const pivotParams = [ + fieldEntryId(tpl.id, "subject"), + fieldEntryId(tpl.id, "predicate"), + fieldEntryId(tpl.id, "object"), + fieldEntryId(tpl.id, "status"), + ...recordIds + ]; + const pivotRows = await db.prepare(pivotSql).bind(...pivotParams).all(); + return pivotRows.results ?? []; +} +async function graphNeighbors(db, start, opts = {}) { + const depth = Math.max(1, Math.min(Math.floor(opts.depth ?? 1) || 1, 10)); + const template = opts.template ?? "triplet"; + const directed = !!opts.directed; + const owner_id = opts.owner_id; + const visited = /* @__PURE__ */ new Set([start]); + let frontier = [start]; + const neighbors = []; + for (let d = 1; d <= depth; d++) { + if (frontier.length === 0) break; + const next = []; + for (const cur of frontier) { + const outgoing = await findTripletEdgesByNode(db, template, ["subject"], cur, owner_id); + for (const e of outgoing) { + if (e.status === "deprecated") continue; + const nb = e.object; + if (!nb || visited.has(nb)) continue; + visited.add(nb); + neighbors.push({ node: nb, predicate: e.predicate ?? "", from: cur, depth: d }); + next.push(nb); + } + if (!directed) { + const incoming = await findTripletEdgesByNode(db, template, ["object"], cur, owner_id); + for (const e of incoming) { + if (e.status === "deprecated") continue; + const nb = e.subject; + if (!nb || visited.has(nb)) continue; + visited.add(nb); + neighbors.push({ node: nb, predicate: e.predicate ?? "", from: cur, depth: d }); + next.push(nb); + } + } + } + frontier = next; + } + return { success: true, start, depth, directed, neighbors, count: neighbors.length }; +} + +// kbdb/src/routes/graph.ts +var graphRoutes = new Hono2(); +graphRoutes.get("/neighbors/:node", async (c) => { + const node = c.req.param("node"); + if (!node) return c.json({ success: false, error: "node required" }, 400); + const depthRaw = Number(c.req.query("depth")); + const depth = Number.isFinite(depthRaw) && depthRaw > 0 ? Math.floor(depthRaw) : void 0; + const directed = c.req.query("directed") === "true"; + const template = c.req.query("template") || void 0; + const owner_id = c.req.query("owner_id") || void 0; + try { + const result = await graphNeighbors(c.env.DB, node, { depth, template, directed, owner_id }); + const edges = result.neighbors.map((n) => ({ + subject: n.from, + predicate: n.predicate, + object: n.node + })); + return c.json({ ...result, edges }); + } catch (e) { + return c.json({ success: false, error: e instanceof Error ? e.message : String(e) }, 500); + } +}); + // kbdb/src/actions/execution-log.ts var SUCCESS_MESSAGE_MAX = 200; var FAILED_MESSAGE_MAX = 2e3; @@ -4832,6 +4939,12 @@ var GENERATIONS = [ // 🪦 entry_values 2026-08-15 廢除;「看到它=那台沒跟上,不是它還合法」。 { kind: "no_table", name: "entry_values" } ] + }, + { + n: 8, + file: "0008_entries_content_index.sql", + what: "entries.content \u7D22\u5F15\u2014\u2014graph \u9130\u5C45\u67E5\u8A62\u5F9E\u7BC0\u9EDE\u540D\u76F4\u63A5\u67E5\uFF08\u4E0D\u6488\u5168\u8868\uFF09\uFF0CArcrun#168 \u6839\u56E0\u4FEE\u5FA9\u7684\u5730\u57FA", + checks: [{ kind: "index", name: "idx_entries_content" }] } ]; var EXPECTED_GENERATION = GENERATIONS[GENERATIONS.length - 1].n; @@ -4990,6 +5103,7 @@ app.route("/recipe-stats", recipeStatRoutes); app.route("/execution-log", executionLogRoutes); app.route("/embed", embedRoutes); app.route("/map", mapRoutes); +app.route("/graph", graphRoutes); var index_default = app; export { index_default as default diff --git a/arcrun-mcp/worker.mjs b/arcrun-mcp/worker.mjs index 3102b6c..bfc0ec6 100644 --- a/arcrun-mcp/worker.mjs +++ b/arcrun-mcp/worker.mjs @@ -32025,7 +32025,7 @@ function registerAllKbdbGraphTools(server, env, orgNamespace, identity) { function registerGraphNeighbors(server, env, orgNamespace, identity) { server.tool( "kbdb_graph_neighbors", - "knowledge graph \u9130\u5C45\u67E5\u8A62\uFF081-hop/N-hop \u95DC\u4FC2\u904D\u6B77\uFF09\uFF1A\u7D66\u4E00\u500B\u7BC0\u9EDE\u540D\uFF0C\u6CBF KBDB triplet\uFF08subject-predicate-object\uFF09\u8A18\u9304\u505A BFS\uFF0C\u56DE\u50B3 depth \u8DF3\u5167\u7684\u9130\u5C45\u6E05\u55AE\uFF08[{node, predicate, from, depth}]\uFF09\u3002\u8207 kbdb_search\uFF08\u95DC\u9375\u5B57/\u8A9E\u7FA9\uFF09\u4E92\u88DC\uFF1A\u627E\u300C\u8DDF X \u6709\u95DC\u4FC2\u7684\u6771\u897F\u300D\u7528\u672C\u5DE5\u5177\uFF0C\u627E\u300C\u5167\u5BB9\u542B\u95DC\u9375\u5B57\u7684\u6771\u897F\u300D\u7528 kbdb_search\u3002\u9700\u8981 namespace \u88E1\u5DF2\u90E8\u7F72 graph_neighbors workflow\uFF08\u6C92\u88DD\u6703\u56DE\u5B89\u88DD\u6307\u5F15\uFF0C\u4E0D\u6703 crash\uFF09\u3002", + "knowledge graph \u9130\u5C45\u67E5\u8A62\uFF081-hop/N-hop \u95DC\u4FC2\u904D\u6B77\uFF09\uFF1A\u7D66\u4E00\u500B\u7BC0\u9EDE\u540D\uFF0C\u6CBF KBDB triplet\uFF08subject-predicate-object\uFF09\u8A18\u9304\u505A BFS\uFF0C\u56DE\u50B3 depth \u8DF3\u5167\u7684\u9130\u5C45\u6E05\u55AE\uFF08[{node, predicate, from, depth}]\uFF09\u3002\u8207 kbdb_search\uFF08\u95DC\u9375\u5B57/\u8A9E\u7FA9\uFF09\u4E92\u88DC\uFF1A\u627E\u300C\u8DDF X \u6709\u95DC\u4FC2\u7684\u6771\u897F\u300D\u7528\u672C\u5DE5\u5177\uFF0C\u627E\u300C\u5167\u5BB9\u542B\u95DC\u9375\u5B57\u7684\u6771\u897F\u300D\u7528 kbdb_search\u3002\u767B\u5165\u8EAB\u5206\uFF08\u5E33\u5BC6\uFF0FOAuth\uFF09\u76F4\u63A5\u8D70 KBDB \u7684\u5716 API\uFF0C\u4E0D\u9700\u8981\u88DD\u4EFB\u4F55 workflow\uFF1B\u670D\u52D9\u7D1A token \u9023\u7DDA\u624D\u9700\u8981 namespace \u88E1\u5DF2\u90E8\u7F72 graph_neighbors workflow\uFF08\u6C92\u88DD\u6703\u56DE\u5B89\u88DD\u6307\u5F15\uFF0C\u4E0D\u6703 crash\uFF09\u3002", { subject: external_exports.string().min(1).describe( "\u8D77\u9EDE\u7BC0\u9EDE\u540D\uFF08graph triplet \u7684 subject/object \u503C\uFF09\uFF0C\u5982 'Arcrun'" @@ -32331,6 +32331,20 @@ function registerGetMap(server, env, identity) { // mcp/src/tools/kbdb_index.ts var INDEX_CARD_NAME = "00-INDEX"; +function locationHints(location, hint) { + if (location) { + const where = [ + location.machine ? `\u8A2D\u5099\u300C${location.machine}\u300D` : null, + `\u5EAB\u300C${location.library}\u300D`, + location.full_path ? `\u8DEF\u5F91 ${location.full_path}` : location.source_path ? `\u5EAB\u5167\u8DEF\u5F91 ${location.source_path}\uFF08\u9019\u500B\u5EAB\u9084\u6C92\u767B\u8A18\u5BE6\u9AD4\u8CC7\u6599\u593E\uFF0C\u7B54\u4E0D\u51FA\u5B8C\u6574\u8DEF\u5F91\uFF09` : null + ].filter(Boolean); + return [ + `\u539F\u6587\u5728\u54EA\uFF1A${where.join("\u3001")}\u3002`, + "\u{1F534} \u56DE\u7B54\u300C\u539F\u6587\u5728\u54EA\u300D\u6642\u7528\u9019\u500B location \u6B04\u4F4D\uFF0C\u4E0D\u8981\u5F15\u7528\u5361\u7247\u5167\u6587\u88E1\u300C### \u51FA\u8655\u300D\u90A3\u4E00\u6BB5\u2014\u2014\u90A3\u5BEB\u7684\u662F\u5167\u90E8\u76F8\u5C0D\u8DEF\u5F91\uFF08`../\u6A94\u540D`\uFF09\uFF0C\u4F7F\u7528\u8005\u62FF\u8457\u5B83\u8D70\u4E0D\u5230\u4EFB\u4F55\u5730\u65B9\u3002" + ]; + } + return [hint ?? "\u9019\u5F35\u5361\u6C92\u6709 machine/source_path \u4E2D\u7E7C\u8CC7\u6599\uFF0C\u7B54\u4E0D\u51FA\u539F\u6587\u7684\u5BE6\u9AD4\u4F4D\u7F6E\u2014\u2014\u4E0D\u8981\u7528\u5167\u6587\u300C### \u51FA\u8655\u300D\u90A3\u6BB5\u4EE3\u7B54\u3002"]; +} function anchorOf(block) { try { const meta = typeof block.metadata_json === "string" ? JSON.parse(block.metadata_json) : block.metadata_json ?? {}; @@ -32464,11 +32478,13 @@ function registerGetCard(server, env, identity) { ); } const content = assembleCard(blocks); + const location = isPortal ? body.location ?? null : null; return successResponse( - { library, page_name, content, block_count: blocks.length }, + { library, page_name, content, block_count: blocks.length, location }, [ `\u9019\u662F\u300C${page_name}\u300D\u7684\u5B8C\u6574\u5167\u5BB9\uFF08${blocks.length} \u6BB5\uFF0C\u5DF2\u7167\u539F\u7A3F\u9806\u5E8F\u7D44\u597D\uFF09`, - "\u8981\u770B\u5B83\u9023\u5230\u54EA\u4E9B\u5361\uFF1Akbdb_graph_neighbors(name)" + "\u8981\u770B\u5B83\u9023\u5230\u54EA\u4E9B\u5361\uFF1Akbdb_graph_neighbors(name)", + ...locationHints(location, isPortal ? body.location_hint : void 0) ] ); } catch (e) { @@ -32567,6 +32583,11 @@ var KNOWLEDGE_FIRST = [ "3. **\u628A\u90A3\u5F35\u5361\u6574\u5F35\u8B80\u5B8C** \u2192 `kbdb_get_card({ library, page_name })`", " \u6458\u8981\uFF0B\u91CD\u9EDE\uFF0B\u5BE6\u9AD4\uFF0B\u95DC\u806F\u4E00\u6B21\u5230\u624B\uFF0C\u7167\u539F\u7A3F\u9806\u5E8F\u7D44\u597D\u3002**\u5F15\u7528\u5167\u5BB9\u6642\u4E00\u5F8B\u7528\u9019\u4E00\u652F\u3002**", "", + "\u{1F534} **\u6709\u4EBA\u554F\u300C\u539F\u6587\u5728\u54EA\uFF0F\u9019\u4EFD\u8CC7\u6599\u54EA\u4F86\u7684\u300D\u2014\u2014\u7B54\u6848\u8981\u8B80 `kbdb_get_card` \u56DE\u61C9\u7684 `location` \u6B04\u4F4D", + "\uFF08\u8A2D\u5099\uFF0F\u5EAB\uFF0F\u5EAB\u5167\u8DEF\u5F91\uFF09\uFF0C\u4E0D\u8981\u5F15\u7528\u5361\u7247\u5167\u6587\u88E1\u300C### \u51FA\u8655\u300D\u90A3\u4E00\u6BB5\u3002** \u90A3\u6BB5\u5BEB\u7684\u662F\u8403\u5361\u7576\u4E0B\u7684", + "\u5167\u90E8\u76F8\u5C0D\u8DEF\u5F91\uFF08\u5F62\u5982 `../\u6A94\u540D`\uFF09\uFF0C\u4F7F\u7528\u8005\u62FF\u8457\u5B83\u8D70\u4E0D\u5230\u4EFB\u4F55\u5730\u65B9\uFF08inkstone/Arcrun#167\uFF09\u3002", + "`location` \u662F null \u6642\uFF0C`kbdb_get_card` \u7684 hints \u6703\u8AA0\u5BE6\u8B1B\u7F3A\u4EC0\u9EBC\u2014\u2014\u7167\u8457\u8AAA\uFF0C\u4E0D\u8981\u7528\u5167\u6587\u90A3\u6BB5\u4EE3\u7B54\u3002", + "", "\u70BA\u4EC0\u9EBC\u662F\u9019\u4E09\u6B65\u3001\u800C\u4E0D\u662F\u76F4\u63A5\u5168\u6587\u641C\uFF1A\u4E00\u500B\u554F\u984C\u7684\u7B54\u6848\u901A\u5E38**\u6574\u5F35\u5361**\u624D\u8AAA\u5F97\u5B8C\u6574\uFF0C", "\u800C\u641C\u5C0B\u53EA\u6703\u56DE**\u547D\u4E2D\u95DC\u9375\u5B57\u7684\u90A3\u5E7E\u6BB5**\u2014\u2014\u4F60\u53EF\u80FD\u62FF\u5230\u6458\u8981\u537B\u6C92\u62FF\u5230\u6578\u5B57\u3001\u62FF\u5230\u95DC\u806F\u537B\u6C92\u62FF\u5230\u5BE6\u9AD4\uFF0C", "\u800C\u4E14**\u4F60\u4E0D\u6703\u77E5\u9053\u81EA\u5DF1\u6F0F\u4E86\u54EA\u5E7E\u6BB5**\u3002\u76EE\u9304\u7684\u5B58\u5728\u5C31\u662F\u70BA\u4E86\u8B93\u4F60\u5148\u770B\u5230\u300C\u6709\u4EC0\u9EBC\u300D\u518D\u6C7A\u5B9A\u8B80\u4EC0\u9EBC\u3002", diff --git a/daemon/Arcrun-0.18.41.dmg b/daemon/Arcrun-0.18.41.dmg new file mode 100644 index 0000000..ffdfa99 Binary files /dev/null and b/daemon/Arcrun-0.18.41.dmg differ diff --git a/daemon/Arcrun-0.18.41.msix b/daemon/Arcrun-0.18.41.msix new file mode 100644 index 0000000..a6e6fee Binary files /dev/null and b/daemon/Arcrun-0.18.41.msix differ diff --git a/daemon/Arcrun-win-0.18.41.exe b/daemon/Arcrun-win-0.18.41.exe new file mode 100755 index 0000000..990edb6 Binary files /dev/null and b/daemon/Arcrun-win-0.18.41.exe differ diff --git a/manifest.json b/manifest.json index 426c260..e7c0e98 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "schema": 2, "built": "2026-08-27", - "source": "Arcrun@ad60863a8017", + "source": "Arcrun@f87260b1234f", "core": [ { "name": "arcrun-array-ops", @@ -198,7 +198,7 @@ "name": "arcrun-cypher-executor", "main_module": "worker.mjs", "main_file": "arcrun-cypher-executor/worker.mjs", - "js_bytes": 694462, + "js_bytes": 696639, "modules": [], "compat_date": "2025-02-19", "compat_flags": [ @@ -239,10 +239,10 @@ "stripped": { "services": 13 }, - "source_commit": "af6f244b44b28a16a3578cf554d1e199a170a05e", - "source_content_sha256": "531937c2ebade75fe67676071ca5f658775c11aa855359330ae19117db1d1807", - "sha256": "531937c2ebade75fe67676071ca5f658775c11aa855359330ae19117db1d1807", - "bytes": 694462 + "source_commit": "f87260b1234f0c108c378caf474d7af78cdd6cb9", + "source_content_sha256": "2390294711ed529125559e505ed8f575093798480606253b31021b6faca4c758", + "sha256": "2390294711ed529125559e505ed8f575093798480606253b31021b6faca4c758", + "bytes": 696639 }, { "name": "arcrun-date-ops", @@ -396,7 +396,7 @@ "name": "arcrun-kbdb", "main_module": "worker.mjs", "main_file": "arcrun-kbdb/worker.mjs", - "js_bytes": 189149, + "js_bytes": 194366, "modules": [], "compat_date": "2025-02-19", "compat_flags": [ @@ -416,16 +416,16 @@ "ENVIRONMENT": "production" } }, - "source_commit": "af6f244b44b28a16a3578cf554d1e199a170a05e", - "source_content_sha256": "80d647b70a89f3d42398be621001ebfcdd08371b68ba523b6fa1f2048c06b64b", - "sha256": "80d647b70a89f3d42398be621001ebfcdd08371b68ba523b6fa1f2048c06b64b", - "bytes": 189149 + "source_commit": "f87260b1234f0c108c378caf474d7af78cdd6cb9", + "source_content_sha256": "97140b9d10337c0f995b5bd5a4a3403ff06403afb6a51394b3ede013293c5bf6", + "sha256": "97140b9d10337c0f995b5bd5a4a3403ff06403afb6a51394b3ede013293c5bf6", + "bytes": 194366 }, { "name": "arcrun-mcp", "main_module": "worker.mjs", "main_file": "arcrun-mcp/worker.mjs", - "js_bytes": 1205568, + "js_bytes": 1208080, "modules": [], "compat_date": "2024-11-27", "compat_flags": [ @@ -440,10 +440,10 @@ "ai": false, "vars": {} }, - "source_commit": "af6f244b44b28a16a3578cf554d1e199a170a05e", - "source_content_sha256": "03d2022d9d3134988fddfd52bd9aa6d1c7734f9d14870fbe1cbb39d9aa27a2f4", - "sha256": "03d2022d9d3134988fddfd52bd9aa6d1c7734f9d14870fbe1cbb39d9aa27a2f4", - "bytes": 1205568 + "source_commit": "f87260b1234f0c108c378caf474d7af78cdd6cb9", + "source_content_sha256": "fad73a5128b50f8f42317ea1c4844ea39eee3c32846ab061282e77b731d810d0", + "sha256": "fad73a5128b50f8f42317ea1c4844ea39eee3c32846ab061282e77b731d810d0", + "bytes": 1208080 }, { "name": "arcrun-merge", @@ -507,7 +507,7 @@ "name": "arcrun-rag-ui", "main_module": "index.js", "main_file": "tier2/ui/index.js", - "js_bytes": 673028, + "js_bytes": 677629, "modules": [], "compat_date": "2026-07-01", "compat_flags": [], @@ -518,10 +518,10 @@ "ai": false, "vars": {} }, - "source_commit": "790b41d10d48f8d6d117797c4b81f9884f909209", - "source_content_sha256": "8a6048f8ffe7125d0077688043a787849e8332d9613d6b693494583aef9de927", - "sha256": "8a6048f8ffe7125d0077688043a787849e8332d9613d6b693494583aef9de927", - "bytes": 673028 + "source_commit": "f87260b1234f0c108c378caf474d7af78cdd6cb9", + "source_content_sha256": "8f3bbc90f901a0f06f40f4fdaaf5ec4ed80a2012822596a78ae47a92014a9a42", + "sha256": "8f3bbc90f901a0f06f40f4fdaaf5ec4ed80a2012822596a78ae47a92014a9a42", + "bytes": 677629 }, { "name": "arcrun-set", @@ -698,12 +698,12 @@ "bytes": 67697 } ], - "release": "1.4.58", + "release": "1.4.59", "built_for": "oauth-installer-lazy-load", "notes": [ "rag_takedown_direct 用了 __CARDS_PREFIX__,但安裝器的代換表沒有它 ⇒ 這個佔位符會原封不動被推進使用者的工作流。" ], - "fingerprint": "0216b2185da93feb5878b09abb05c72eb25978a9eeeef0e2c06639b377d67be9", + "fingerprint": "6d5575253074f913fa548e13da8ce6c9cda4d9598a8bd5971642ab66ea43bf80", "library": [ { "name": "arcrun-array-ops", @@ -888,7 +888,7 @@ "name": "arcrun-cypher-executor", "main_module": "worker.mjs", "main_file": "arcrun-cypher-executor/worker.mjs", - "js_bytes": 694462, + "js_bytes": 696639, "modules": [], "compat_date": "2025-02-19", "compat_flags": [ @@ -929,8 +929,8 @@ "stripped": { "services": 13 }, - "source_commit": "af6f244b44b28a16a3578cf554d1e199a170a05e", - "source_content_sha256": "531937c2ebade75fe67676071ca5f658775c11aa855359330ae19117db1d1807", + "source_commit": "f87260b1234f0c108c378caf474d7af78cdd6cb9", + "source_content_sha256": "2390294711ed529125559e505ed8f575093798480606253b31021b6faca4c758", "first_install": true }, { @@ -1075,7 +1075,7 @@ "name": "arcrun-kbdb", "main_module": "worker.mjs", "main_file": "arcrun-kbdb/worker.mjs", - "js_bytes": 189149, + "js_bytes": 194366, "modules": [], "compat_date": "2025-02-19", "compat_flags": [ @@ -1095,15 +1095,15 @@ "ENVIRONMENT": "production" } }, - "source_commit": "af6f244b44b28a16a3578cf554d1e199a170a05e", - "source_content_sha256": "80d647b70a89f3d42398be621001ebfcdd08371b68ba523b6fa1f2048c06b64b", + "source_commit": "f87260b1234f0c108c378caf474d7af78cdd6cb9", + "source_content_sha256": "97140b9d10337c0f995b5bd5a4a3403ff06403afb6a51394b3ede013293c5bf6", "first_install": true }, { "name": "arcrun-mcp", "main_module": "worker.mjs", "main_file": "arcrun-mcp/worker.mjs", - "js_bytes": 1205568, + "js_bytes": 1208080, "modules": [], "compat_date": "2024-11-27", "compat_flags": [ @@ -1118,8 +1118,8 @@ "ai": false, "vars": {} }, - "source_commit": "af6f244b44b28a16a3578cf554d1e199a170a05e", - "source_content_sha256": "03d2022d9d3134988fddfd52bd9aa6d1c7734f9d14870fbe1cbb39d9aa27a2f4", + "source_commit": "f87260b1234f0c108c378caf474d7af78cdd6cb9", + "source_content_sha256": "fad73a5128b50f8f42317ea1c4844ea39eee3c32846ab061282e77b731d810d0", "first_install": true }, { @@ -1180,7 +1180,7 @@ "name": "arcrun-rag-ui", "main_module": "index.js", "main_file": "tier2/ui/index.js", - "js_bytes": 673028, + "js_bytes": 677629, "modules": [], "compat_date": "2026-07-01", "compat_flags": [], @@ -1191,8 +1191,8 @@ "ai": false, "vars": {} }, - "source_commit": "790b41d10d48f8d6d117797c4b81f9884f909209", - "source_content_sha256": "8a6048f8ffe7125d0077688043a787849e8332d9613d6b693494583aef9de927", + "source_commit": "f87260b1234f0c108c378caf474d7af78cdd6cb9", + "source_content_sha256": "8f3bbc90f901a0f06f40f4fdaaf5ec4ed80a2012822596a78ae47a92014a9a42", "first_install": true }, { @@ -1375,13 +1375,17 @@ "worker": "arcrun-mcp", "why": "leo 2026-08-10:「今天只要清單含有 MCP 即可」。產品承諾是「把自己的 AI 接上自己的知識庫」,MCP 就是那條線;缺它的話那句承諾從第一步就不成立(prod 1.4.30 曾整包沒有它)。" }, + { + "worker": "arcrun-code", + "why": "graph_neighbors/prep 用 __CODE_URL__" + }, { "worker": "arcrun-http-request", - "why": "graph_neighbors/fetch_triplets 用 __HTTP_REQ_URL__" + "why": "graph_neighbors/fetch_neighbors 用 __HTTP_REQ_URL__" }, { "worker": "arcrun-code", - "why": "graph_neighbors/bfs_neighbors 用 __CODE_URL__" + "why": "graph_neighbors/parse_neighbors 用 __CODE_URL__" }, { "worker": "arcrun-auth-static-key", @@ -1485,20 +1489,20 @@ } ], "daemon": { - "version": "0.18.40", + "version": "0.18.41", "mac": { - "file": "daemon/Arcrun-0.18.40.dmg", - "sha256": "5434b22742c9d1b449d22ab18fe48f2bb9f57c6ac4690e824e391135e7df0ab3" + "file": "daemon/Arcrun-0.18.41.dmg", + "sha256": "c37705824a139a2b0f2f8fb7d3d5aede686296e6c7820cb96b246d5e6638af61" }, "win": { - "file": "daemon/Arcrun-win-0.18.40.exe", - "sha256": "d84c3159379fd3a415bcab5192a376ae3753f9e12dd8624c5ffced59406b5139" + "file": "daemon/Arcrun-win-0.18.41.exe", + "sha256": "873ffe08176cfcd381d1ef02a9c88f52be12a8f918eaa83d2004d59db257c19f" }, "msix": { - "file": "daemon/Arcrun-0.18.40.msix", - "sha256": "809b0da36d43ce0b75780f91e34f9f4f0138ea075f3ac92c95155a4f0df8d804" + "file": "daemon/Arcrun-0.18.41.msix", + "sha256": "6806a58b6efd570e59502f7a5c4d7307fda2fcae39162f8089bab135110c4532" }, - "built": "20260827-1401", - "notes": "卡住的檔案不再擋住後面所有人" + "built": "20260827-1950", + "notes": "不叫「docs」的文件夾,現在也認得出來" } } diff --git a/tier2/ui/index.js b/tier2/ui/index.js index 8cfdf2b..1034f9e 100644 --- a/tier2/ui/index.js +++ b/tier2/ui/index.js @@ -1,6 +1,6 @@ // arcrun-rag-ui — 用戶實例的 GUI(console-ui/public 內嵌;由 Arcrun scripts/build-ui-worker.mjs 產生,勿手改) -const FILES = {"/apple-touch-icon.png":{"type":"image/png","b64":true,"data":"iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAIAAACyr5FlAAAABmJLR0QA/wD/AP+gvaeTAAATEklEQVR4nO3dd1xTV/8H8JOEERGMILKs4kDA+jjroyxBNipFwFV9LHsoD+JAcLXV2mr9dWhdVauCqAy1sgQnyKiKiKgMkSlLTVCCbDUB8vsjz8tS5ISMk4S+Xt/3X0DOPd8LfJKce++5JxRtHT0EQH+o8t4BMHhBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEQxA3N9ct4WEUCkWqVb7d+Y2To6NUS4iHpqqqJu99GKSmT5t26sRxCwtzY2PDtLRbXV1d0qgSGOC3ccN6FxdnCoWSk3NPGiXEBuHo3+jRn1w8H8tgMBBChhMn2trZpKXdam9vJ1vF2tp6/76fqVQqhUIxMzUZpTfqVkZmT08P2Spio2jr6Ml7HwYdVVXVpMT4ScZGvX/IZLG8vHyLiotJVTEyNExOildT+9uT8/6DB76+AWw2m1QVScCYoy8ajfbb4YN9koEQ0tXRib90wcHBnkgVbW2t6HNRfZKBEJo9a1ZyYoKBwQQiVSQEbyt97f5ul7u7W78PKSkpfe68kMPl5OU9kKQEnU6PPnfG0HBiv4+qqw93c1306HFBff1zSapIDsLxN76+3hs3rBPQgEqlWs610NHRycwUc3BAoVAOHdhvPc9KQBs6ne7u5spmswsKi8QoQcogHXMoKioaGxqOmzBeV0dbc4QmYziD/3Mej9fa0spuYjNZDc+qnpVXVLx//55UURsb66jIUzQaTZjGWdnZgauDWlvbRK2ybcvm4OAgIRsfPvzb3h9/ktcQdXCFQ09Pz9XVxdbG+rOZM5WUlAZsz+V2FRUXZ2Vlp6amljwtlaT0JGOjpMR4VVVV4TcpL6/w8PKuq6sXfpNly5b8uu8XkXYs9cqVkHUb3759K9JWRAyWcEwyNgoN3eDo4CDkE/djjx4X/PbbsStXr/J4PFG31dLSSklO+OSTT0TdkM1m+/j65z3IF6axqYlJXOw5RUVFUasUFBZ6+/ixWA2ibigh+YdjyJAhWzeHe3t7ih2L3vIe5IeFby4vrxB+Ezqd/sfF8zNnTBevIofD2RgaHp+QILjZ+PFjLyclqquri1eFyWR6evoUl5SIt7l45DwgNTI0vHA+1s7Olkolc1A9Sk/vi+XL6uqfl5YK9S5DoVCOHDowz8pS7Io0Gm3+fEcqlZpzD3t+k8FgXDwfp6cn/vNQTU3N3d2trLy8quqZ2J2ISp7hmDvXIi72nI6ODtluFRQUFsx3YjexCwoKB2wcHrbJ0+NLCStSKBRTUxODCePT0m91d3f3eVRRUeHM6chp06ZKWIV/IN3Z2ZGf/1DCroQkt3DMs7I6czpiyJAh0uicQqHYWM8rKSmtrKoS0GzJ4sU7d3xN6rqasbHR3LkWaWnpnZ2dvX/+0//tXbDAiUgJKpU6z8pKW1s7MytLBocw8hlzTJk8OT7+4tChQ6VapaW11drGDjeOGzt2TOatdGGOiURSX1/v6eVbWlbG/3axu/uhg/vJlkAI/fnn7YDANS2trcR77k0OrxzDhqldvBA3cqSmtAvRlZVHjtS8cvVav482N7e0tLbOs7IkNdzhYzAY7u6uT56U1NTUIISqqqpGjRo1+dNPCZZACOnrj3FydMzIzGhubiHbc29yCIe7u9uypUuEbMxkMp+UlBQWFZWVlbNYDVxuF4PBEP7faWRklJSU/OZNc7+PPn5c8PhxgYO9nbKyspAdCkNZWdl1kUtzS/PjxwXd3d3Xrl3v6uoyNzMjOy9EQ0PDzdU1/8HDFy9fEuy2N/m8raxftzZsU6iAP1Z1de2pyIirV68zmcw+D6mqqtrZ2a4O9J86ZYowtY4dP7Hru+8FNDA2Moo6fWr06NHC9CaSyMiob3Z+yx+iOjsvPPjrPjqdTrYEh8PZFLblj0uXyHbLJ58B6b3c+y+ZL21tbD5+Dejq6vpl3/6g4OD8/If9zp/gcDilpWUxsXEIUUxNTQaspa2tdfJUhIAGjWx2UvLl2bP/raerK9JvMaAZM6bPmD71Zlo6h8MpL6/Izv7T3s6W7EiLRqM5OTkoKirevZtDsNv/dS6vo5Xi4ieFRcVOjg69zxi2trZ5+fidv3BxwKE4j8e7m5MzQmPE9OnTBLdkMBiX4uMFvzd3dnbGxyeMG6tv/NGVegmNGzfO3t72VkZGa2sri9VwOeWKhYU52fEWhUIxmTPbyHDizbR0stPV5Hmeo7q6+vbtO46ODioqKgihltbW5ctXinQ1/F7u/ZUrlvM3F+B+Xn55ebngNt3d3VeuXqNQKCYmc8gODjQ1NV0XueTl5TGZrLa2tviExE8nTRo/fhzBEgghQ0NDS0vLtLT0jo4OUn3K+Qwpk8W6ceOGna2tisrQLz288h+KdnqHy+Vqao7896zPBDcrKSnJzb0vTId3c3Jqamrt7GyInMv/YOjQoYvd3Wpqa8vKyjgcTvLllGFqajNnziBYAiGkq6PzufPCO3fuvG5sJNKh/OdzvHnTnHw5pai4OD39lhib83i8pUsWC25TUV55KyNDyA6flpbevZNjb2834AuSSBQUFBYumI8Q717u/Z6enozMrMbGxnlWVmQPpIcNG+bu7lbytLS6ulry3uQfDoRQR0dHWdkAL/s4HM77wAB/wW0qq6quXbsufJ8vXr68cvWq5dy5I0aMEG+v+kWhUMzMTMeN0+efZS8oKHz48LGDvT3ZA2klJaVFLp+3tbc9fPhIwq7+8XNIm5reDNhGUUFB1G5ra+tcXN2ysrPF2ilB3N3cLlyI5ccuKzvbxdVNpBkhwqDRaLt27vhhz24F0X/x3v7x4fj4Qhcpra1tX3p4nzkbTbzn2bNmpSQn8ueQlpdXOLu4CjkjRCSeHqvOnokcNkz8d4Z/fDikqqura8vWbeGbtxK/o0lff0xKcqKtrQ1CqLGxcemyL6RxIsvK0vJKSsr48WPF2xzCMbBz0TEenj5tbSJPFxVMVVX1dMRJb29PhBCHw1m3PnT3D3uJX2vlTzIymTNHjG0lek+SHmVlZX39Mbo6upqamqqqKqof3d/xAdnRPk5mVpab+5Ko0xGjRo0i2C2NRtv93a6x+vq7vtvd3d195MjRFy9e7v/lJ7JDVHV19diYsxs3hSckJIq0ofynCX6gra1lbT3PzNRkxvQZY8fqEzzTkJiYFBQcInk/2tpaEadOzhjonKwY0tLSg4JD+JcLZn02M+LUCU1NwleteTzevv0H9u3/Vfg5tvIPB41Gmz/fyfPLVaamJlJ6GSAVDoQQnU4/dGD/woULiPTWW8nTUk8vnxcvXiCERo/+5ExUpJGhIfEqiYlJG0LDhLyfQ85jDltbm4z0m78f+83c3Ew2bxASevfuXcDqoIMHDxPv+dNJxqmXE/kvS/X1z10WuWdkZhKv4uq66OKFOCFfluT2/6DT6Qf2/3I2KnKQ3BcqPB6PdzY65uO5BJLT0tLy8vLkf93W1nb895PSOFCfOWO6vb2tMC3lEw41NbXzcTFLhZ7yM6jMmD4t9XKiLunr+wihgwcPr98Qyv96xYrlZ6NOk73EgxDq7Oz0D1gTG3temMZyOFqh0Wgnjh8d8GrZ4CS9OTth4Vsu/nEJIUSlUrdt2RwUtJpsCYQQi9Xg5e1bWCTs/bdyCEdAgJ+l5VzZ15VcSEjw5rBNxFeBampq8vULzL1/HyGkoqJy6OD++U5kZqv3VvTkiZenD5PFEn4TWYdDQ0NjwzoyBw6ypKSk9NOPewe8/CuGysoqT2/v6upahJCOjvbpyFNCTn8UyfXrN/67dl2feyYGJOsxx8qVX4h0s/JgoK6uHhcXLY1k3L595/NFbvxkTJk8OTUlWRrJOHrsd1//QFGTgWT/yuHi8rmMK0rIwGBCVGTkuHH6xHuOjondtv0rLrcLIeTo6HDk0AGyM0gQQlxu19bt22Ni4sTbXKbhGD58+ORJk2RZUUIWFuYnjh/lLxtHUE9Pz/e79xw7foL/7ZrVAdu3bSV+mqelpcUvYPWdO3fF7kGm4ZhkbCztNT0J+s/KFXt2f6+oSPhP1NHRERyy/vr1GwghRUWFPbu//8/KFWRLIISqq2s9vLwkvOtapuEge9VKeqhU6vZtW9esDiDeM5PJ9PLyLXryBCHEYDBOHD9qYWFOvMq93Fxfv8A3bwaeBiWYTMOhNuwfMBRVUVE5fOhXaSwp/KTkqaeXz8uXLxFC+vpjzpyOnDjRgHiVpKTk9Rs3EVkNS6bhGPzvKbo6OqejIqZMnky856vXrq0N2cA/ZJgze3bEqd/FXsgFh8fj7f3xp0OHjpDqUKaHsgRvqZCGqVOmpKYkSyMZR44c9Q9Yw0/G0iWLz8dFE0/G27dvA1YHEUwGkvErRwPrlSzLiWS+k9Ohg/ulcDDJ3bx1W1zcBYQQhULZHLYpJCSYbAmEUEPDK28fv8cFBWS7lWk4yitEWKqLIC0trblzLT58W1FR0WfRjqCg1du2bCZ+MNnc3OzrH8hf7p5Opx/8dZ+z80KyJdDfhzJkyXqyT17uXeLHLA0NrzQ0NIQ/5gwNC/9wWVJRUWHvnj0rViwnu0sIoWfPajy9vKqeVSOEtLS0IiOkNYVszX/XSun9Wtanz69fv0m8zx3f7npa+lSMDYcPHx4bfU4aybibc8/ZZRE/Gb1n8ZB14uQpb19/6Y3kZB2O6JgYMdYJFaC9vT09/ZaQt8L2pqurezk50czMlODO8J0/f3HFylXNzc0IIXNzs8SES8RfLPn3TOzYuUt6t+0g2YfjaWlZSkoqwQ7PRcd2dHRcSb0q6oZsNvt142uCe4IQ6unp2f3D3g2hm7hcLv8ndXV17969I1ulpbV1lYeXNO626kMOM8F2fvsd/1klOTabfejwEYRQbl7eo8eijdU5HI6fX2BNTR2RPUH8g8nAoCNHjvb+YX39cx9ff4ILtNfW1i1ydc/O/pNUhwLIIRxMFmtN0Fr+1UhJdHd3h6zb+OEk8ddffyPqfWlNTU0eXl5E1uRjsRrc3JdeudrPC9iD/IcbQsOIvJnef/DA2cVVpPWZJSGfu+xra2vLysqdHB3EvtO3q6srdFN46pW//hlMFqupqcnWxmbA87A3bt4sLn7C/7qp6U1hYZHrokU0mvjPk+KSkqXLVlTh1zwtLS2jUJCZqUTjm/iEBD//1cQ/SkwAuS3BUFlZmZmZZTJnjoaGhqjbPn/+3Nc/8PqNvgc+BQWF1TU1FhbmdIF3jPUOB0Korq6usbHR3t5O1N34X283bnp4+jQ1NQludu9e7vhxYydNMhajBI/H+/mXfdIefn5MnutzNDQ0nIuOYTexJxoYCDln4vXr1wcPHwlZt6G6uqbfBqWlpTExcc0tzfQh9GFqav3eV9gnHAihwqIiVVW1WZ/NFPVXOHb8RGhYOIfDEaZx+q0Mc3OzUSKugP7+/fvgkPVRUWdF3TfJyf+ON4QQlUqdM3u29TyrmZ/NNJxo0OeWm/b29orKqkcPH6VnZN6+/afkgxXcPkSc/F34j3Djcru2bf8qOiZWpCqampopyYljxgi7rOXr1699fP3zJV6GRTyDIhx9KCsrMxiMIUPoXG5XS0uLzC7XDR06NCHhj38JsdpwS0uLf+Ca27fviFHFyNAwKTFemGUzSsvKPDx9nj+X2ye9DcZwyJGOjnbq5STBNyzV1tZ5eHlXVFSKXcXK0vLsmUjBg3GxPyaMoEGxJtjg0d7ekZNzb7G7G+7zlPIe5H+xcpWEz+ba2tqGV68c7LFvYeeiY4LXrnv7lvDZM1FBOPp69epVZeUzZ+cFHx8SJydf9vULILKKS1FR8XDG8I9Xm+zu7t7x7a4ff/x5MHwuNYSjHxWVlRwud67FX1f5+YtbfPX1DoLrP2VlZ/9r8uQJE/66j7yjo8M/cM2lS/GkSkgIwtG/+/fztLW1p06dgvhrMm0IjYg8TbYEj8dLS79lY2OjNXIkQojJZK5Ysepebi7ZKpKAASmWoqJCzLmzEycaePv4iXrhRni6OjopKUlMJsvbx+/1a8IXAiUE4RBEXV1dRUWFv9qO9BgYTKivf07w4hwpEA6A9Q9YaQnIC4QDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOAAWhANgQTgAFoQDYEE4ABaEA2BBOADW/wPYvdSqRewFmQAAAABJRU5ErkJggg=="},"/console/dashboard/index.html":{"type":"text/html; charset=utf-8","b64":false,"data":"\n\n
\n\n\nMCP_TOKEN_TTL(mcp worker)設定。\n 正在前往搜尋頁… 沒有自動跳轉請點這裡
\n\n\n"},"/portal/app-mount.test.mjs":{"type":"application/javascript; charset=utf-8","b64":false,"data":"// App 掛載協定 v0.2 的結構自測(Arcrun#152/#82,leo 2026-08-24 拍板)\n// 跑法:node console-ui/public/portal/app-mount.test.mjs\n//\n// 這支守的是**協定的形狀**,不是長相——長相要用瀏覽器量(見 #152 的驗收留言)。\n// 之所以要有它:這一輪的病根是「出口與樣式被做成個案」——筆記 App 有返回鍵、總圖沒有;\n// CIS 表複製進 shadow 了、但 App 根本沒接上。個案會悄悄再長出來,所以把\n// 「不准再有個案」這件事寫成可執行的檢查。\nimport fs from 'node:fs';\n\nconst html = fs.readFileSync(new URL('./index.html', import.meta.url).pathname, 'utf8');\nlet pass = 0, fail = 0;\nconst chk = (label, cond, extra = '') => {\n if (cond) { console.log('PASS:', label); pass++; }\n else { console.log('FAIL:', label, extra); fail++; }\n};\n\n// ── ① 出口:每一個非首頁的 view 都必須有 .pagehead ────────────────────────────\n// ensureBackControls() 是往 `#v-\n \n —\n \n \n \n