From c3856470ad5616b55aff846839581c15ed0975ed Mon Sep 17 00:00:00 2001 From: uncle6me-web Date: Thu, 13 Aug 2026 18:28:25 +0800 Subject: [PATCH] =?UTF-8?q?chore(builds):=20=E9=87=8D=E7=B7=A8=E6=88=90?= =?UTF-8?q?=E5=93=81=E2=80=94=E2=80=94=E6=8A=8A=20#117=20=E5=9C=B0?= =?UTF-8?q?=E5=9C=96=20entry=5Fcount=20=E8=88=87=20#118=20=E8=AE=80?= =?UTF-8?q?=E4=B8=8D=E5=88=B0=E2=89=A0=E4=B8=8D=E5=AD=98=E5=9C=A8=20?= =?UTF-8?q?=E6=94=BE=E9=80=B2=E5=9F=B7=E8=A1=8C=E6=AA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .worker-builds/arcrun-kbdb/worker.mjs | 38 ++- .worker-builds/arcrun-mcp/worker.mjs | 321 +++++++++++++++++--------- .worker-builds/manifest.json | 16 +- 3 files changed, 249 insertions(+), 126 deletions(-) diff --git a/.worker-builds/arcrun-kbdb/worker.mjs b/.worker-builds/arcrun-kbdb/worker.mjs index 1bc7231..78bfb50 100644 --- a/.worker-builds/arcrun-kbdb/worker.mjs +++ b/.worker-builds/arcrun-kbdb/worker.mjs @@ -3736,6 +3736,7 @@ async function recomputeLibraryMap(db, input) { await updateRecord(db, row.rid, { status: "superseded" }); superseded.push(row.rid); } + const entryCount = (await liveEntryCountsByLibrary(db, owner)).get(library) ?? 0; return { map: { record_id: blockEntry.id, @@ -3746,6 +3747,7 @@ async function recomputeLibraryMap(db, input) { relation_profile: relationProfile, bridges, triplet_count: tripletCount, + entry_count: entryCount, commit_hash: input.commit_hash ?? null, status: "active", updated_at: blockEntry.created_at @@ -3776,6 +3778,21 @@ async function liveTripletCountsByLibrary(db, tripletTemplateId, owner_id) { for (const r of res.results ?? []) m.set(r.library, r.n); return m; } +async function liveEntryCountsByLibrary(db, owner_id) { + const params = owner_id ? [owner_id] : []; + const res = await db.prepare( + // kbdb-sql-ok:牆內本體(kbdb/src/actions/),checkout 開在巢狀 worktree /private/tmp/wt-arcrun-library-map-honesty-87/(同 962d863/5919c6b 已記載的假警報成因:hook 逐字比對 matrix/arcrun/kbdb/src/ 吃不到中間多出的 worktree 目錄層,非繞牆) + `SELECT COALESCE(NULLIF(json_extract(metadata_json, '$.library'), ''), 'general') AS library, + COUNT(*) AS n + FROM entries + WHERE ${owner_id ? "owner_id = ? AND " : ""}entry_type != 'value' + AND NOT (entry_type = 'block' AND COALESCE(json_extract(metadata_json, '$.kind'), '') = 'library_map') + GROUP BY COALESCE(NULLIF(json_extract(metadata_json, '$.library'), ''), 'general')` + ).bind(...params).all(); + const m = /* @__PURE__ */ new Map(); + for (const r of res.results ?? []) m.set(r.library, r.n); + return m; +} async function knownLibraryNames(db, owner_id) { const names = /* @__PURE__ */ new Set(); const entryParams = owner_id ? [owner_id] : []; @@ -3825,11 +3842,15 @@ async function listLibraryMaps(db, owner_id) { const tpl = await getTemplate(db, LIBRARY_MAP_TEMPLATE_NAME); if (!tpl) return []; const params = owner_id ? [tpl.id, owner_id] : [tpl.id]; - const res = await db.prepare( - `WITH m AS (${mapPivotSql(!!owner_id)}) - SELECT * FROM m WHERE COALESCE(m.status, 'active') = 'active' AND m.library IS NOT NULL - ORDER BY m.ts DESC` - ).bind(...params).all(); + const [res, entryCounts] = await Promise.all([ + db.prepare( + // kbdb-sql-ok:牆內本體(kbdb/src/actions/),既有查詢(listLibraryMaps 原本就有)此次改包進 Promise.all 才重新觸發掃描,非新增違規;worktree 路徑假警報同上方 liveEntryCountsByLibrary 註解 + `WITH m AS (${mapPivotSql(!!owner_id)}) + SELECT * FROM m WHERE COALESCE(m.status, 'active') = 'active' AND m.library IS NOT NULL + ORDER BY m.ts DESC` + ).bind(...params).all(), + liveEntryCountsByLibrary(db, owner_id) + ]); const byLib = /* @__PURE__ */ new Map(); for (const r of res.results ?? []) { if (!r.library || byLib.has(r.library)) continue; @@ -3838,6 +3859,7 @@ async function listLibraryMaps(db, owner_id) { narrative: r.narrative || null, top_entities: parseJsonArray(r.top_entities).slice(0, 3).map((t) => t.name), triplet_count: Number(r.triplet_count ?? 0) || 0, + entry_count: entryCounts.get(r.library) ?? 0, updated_at: r.ts }); } @@ -3853,7 +3875,10 @@ async function getLibraryMapDetail(db, library, owner_id) { ORDER BY m.ts DESC LIMIT 1` ).bind(...params).first(); if (!row) return null; - const blockEntry = await getEntry(db, row.rid); + const [blockEntry, entryCounts] = await Promise.all([ + getEntry(db, row.rid), + liveEntryCountsByLibrary(db, owner_id) + ]); return { record_id: row.rid, library, @@ -3863,6 +3888,7 @@ async function getLibraryMapDetail(db, library, owner_id) { relation_profile: parseJsonArray(row.relation_profile), bridges: parseJsonArray(row.bridges), triplet_count: Number(row.triplet_count ?? 0) || 0, + entry_count: entryCounts.get(library) ?? 0, commit_hash: row.commit_hash || null, status: row.status ?? "active", updated_at: row.ts diff --git a/.worker-builds/arcrun-mcp/worker.mjs b/.worker-builds/arcrun-mcp/worker.mjs index 54dc80a..5f752e6 100644 --- a/.worker-builds/arcrun-mcp/worker.mjs +++ b/.worker-builds/arcrun-mcp/worker.mjs @@ -31262,16 +31262,109 @@ function registerAllWorkflowCrudTools(server, env) { registerRunWorkflow(server, env); } +// mcp/src/lib/portal-client.ts +async function portalFetch(env, session, path, opts = {}) { + if (!env.CYPHER_EXECUTOR) { + throw new Error("CYPHER_EXECUTOR service binding not configured"); + } + const url = new URL(`https://cypher${path}`); + for (const [k, v] of Object.entries(opts.query ?? {})) { + if (v !== void 0 && v !== "") url.searchParams.set(k, String(v)); + } + return env.CYPHER_EXECUTOR.fetch(url.toString(), { + method: opts.method ?? "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${session}` + }, + body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0 + }); +} +function resolveKnowledgeIdentity(authPath, portal) { + if (authPath !== "oauth") return { kind: "service" }; + return portal?.session ? { kind: "portal", portal } : { kind: "stale" }; +} +function staleIdentityError() { + return errorResponse( + "identity_missing", + "\u9019\u689D MCP \u9023\u7DDA\u662F\u820A\u7248\u7C3D\u767C\u7684 token\uFF0C\u88E1\u9762\u6C92\u6709\u767B\u5165\u8005\u8EAB\u5206\uFF0C\u56E0\u6B64\u67E5\u4E0D\u5230\u4EFB\u4F55\u77E5\u8B58\u5167\u5BB9\u3002\u91CD\u65B0\u9023\u7DDA\u4E00\u6B21\uFF08\u5728 claude.ai \u7684 connector \u8A2D\u5B9A\u88E1\u91CD\u65B0\u6388\u6B0A\u3001\u8F38\u5165\u4F60\u7684 Portal \u5E33\u5BC6\uFF09\u5373\u53EF\u2014\u2014\u4E0D\u9700\u8981\u53E6\u5916\u627E\u4EFB\u4F55 credential \u6216\u91D1\u9470\u3002", + [ + "\u5230 claude.ai \u2192 Settings \u2192 Connectors\uFF0C\u628A\u9019\u500B connector \u91CD\u65B0\u9023\u7DDA\u4E00\u6B21\uFF08\u6703\u8DF3\u51FA\u8F38\u5165 Portal \u5E33\u5BC6\u7684\u9801\u9762\uFF09", + "\u91CD\u9023\u5F8C kbdb_* \u5168\u90E8\u5DE5\u5177\u90FD\u6703\u7528\u4F60\u9019\u500B\u5E33\u865F\u7684\u6B0A\u9650\u67E5\u8A62" + ] + ); +} +async function portalError(res, what) { + const detail = await res.text().catch(() => ""); + if (res.status === 401) { + return errorResponse( + "session_expired", + `${what}\u5931\u6557\uFF1A\u767B\u5165\u968E\u6BB5\u5DF2\u904E\u671F\uFF08portal session \u5230\u671F\u6216\u5DF2\u767B\u51FA\uFF09\u3002`, + [ + "\u5230 claude.ai \u2192 Settings \u2192 Connectors \u91CD\u65B0\u9023\u7DDA\u9019\u500B connector\uFF08\u91CD\u65B0\u8F38\u5165 Portal \u5E33\u5BC6\uFF09", + "\u91CD\u9023\u5F8C\u6B0A\u9650\u8207\u4F60\u5728 portal \u7DB2\u9801\u4E0A\u770B\u5230\u7684\u4E00\u81F4" + ], + detail + ); + } + if (res.status === 403) { + return errorResponse( + "forbidden", + `${what}\u5931\u6557\uFF1A\u9019\u500B\u5E33\u865F\u6C92\u6709\u9019\u9805\u6B0A\u9650\uFF08\u5E33\u865F\u53EF\u80FD\u5DF2\u505C\u7528\uFF0C\u6216\u6C92\u6709\u88AB\u6388\u6B0A\u8A72\u77E5\u8B58\u5EAB\uFF09\u3002`, + ["\u8ACB\u77E5\u8B58\u5EAB\u7BA1\u7406\u54E1\u5728 portal \u7684\u5E33\u865F\u7BA1\u7406\u88E1\u78BA\u8A8D\u4F60\u7684\u72C0\u614B\u8207\u53EF\u7528\u77E5\u8B58\u5EAB"], + detail + ); + } + return errorResponse(`portal_${res.status}`, `${what}\u5931\u6557\uFF08HTTP ${res.status}\uFF09`, ["\u7A0D\u5F8C\u91CD\u8A66"], detail); +} + // mcp/src/tools/arcrun_skills_examples.ts +var KbdbAccessError = class extends Error { + constructor(status, what, detail) { + super(`KBDB ${what} HTTP ${status}`); + this.status = status; + this.what = what; + this.detail = detail; + } + status; + what; + detail; +}; +function kbdbFailure(e, searchHint, identity) { + const portalNote = identity.kind === "portal" ? "\u4F60\u9019\u689D\u662F\u5E33\u5BC6\u767B\u5165\u7684\u9023\u7DDA\uFF0C\u4F46 skill\uFF0Fexample \u9019\u6279\u5DE5\u5177\u76EE\u524D\u4ECD\u8D70**\u670D\u52D9\u5167\u90E8\u6191\u64DA**\uFF08\u9084\u6C92\u63A5\u4E0A\u767B\u5165\u8EAB\u5206\uFF09\u2014\u2014\u6240\u4EE5\u5B83\u8B80\u4E0D\u5230\uFF0C\u4E0D\u4EE3\u8868\u4F60\u7684\u5E33\u865F\u8B80\u4E0D\u5230\u3002" : ""; + if (e instanceof KbdbAccessError) { + const unauthorized = e.status === 401 || e.status === 403; + return errorResponse( + unauthorized ? "kbdb_unauthorized" : "kbdb_unreachable", + (unauthorized ? `\u8B80\u4E0D\u5230 KBDB\uFF08HTTP ${e.status}\uFF1A\u9019\u689D\u9023\u7DDA\u7684\u6191\u64DA\u88AB\u62D2\u6216\u6839\u672C\u6C92\u5E36\uFF09\u3002` : `\u8B80\u4E0D\u5230 KBDB\uFF08HTTP ${e.status}\uFF09\u3002`) + "\u{1F534} **\u9019\u662F\u300C\u8B80\u4E0D\u5230\u300D\uFF0C\u4E0D\u662F\u300C\u4E0D\u5B58\u5728\u300D**\u2014\u2014\u5167\u5BB9\u9084\u5728\u5EAB\u88E1\uFF0C\u53EA\u662F\u9019\u689D\u8DEF\u88AB\u64CB\u4F4F\u4E86\u3002" + (portalNote ? ` ${portalNote}` : ""), + [ + searchHint, + "kbdb_get_map() \u770B\u9019\u53F0\u5BE6\u4F8B\u6709\u54EA\u4E9B\u5EAB\uFF08\u90A3\u689D\u8D70\u5F97\u901A\u5C31\u66F4\u78BA\u5B9A\u662F\u9019\u6279\u5DE5\u5177\u7684\u8DEF\u58DE\u4E86\uFF0C\u4E0D\u662F\u5EAB\u7A7A\u4E86\uFF09", + "\u{1F534} \u4E0D\u51C6\u628A\u9019\u500B\u932F\u8AA4\u56DE\u5831\u6210\u300C\u627E\u4E0D\u5230\uFF0F\u6C92\u6709\u9019\u500B skill\u300D\u2014\u2014\u8ACB\u7167\u5BE6\u8AAA\u300CKBDB \u9019\u689D\u8DEF\u8B80\u4E0D\u5230\u300D", + "\u6301\u7E8C\u5931\u6557\uFF1A\u544A\u8A34 leo \u9019\u53F0\u5BE6\u4F8B\u7684 arcrun-mcp \u5C11\u4E86 secret KBDB_INTERNAL_TOKEN" + ], + e.detail + ); + } + return errorResponse( + "kbdb_unreachable", + `\u8B80\u4E0D\u5230 KBDB\uFF1A${e instanceof Error ? e.message : String(e)}\u3002\u{1F534} **\u9019\u662F\u300C\u8B80\u4E0D\u5230\u300D\uFF0C\u4E0D\u662F\u300C\u4E0D\u5B58\u5728\u300D**\u3002` + (portalNote ? ` ${portalNote}` : ""), + [searchHint, "\u7A0D\u5F8C\u91CD\u8A66", "\u{1F534} \u4E0D\u51C6\u628A\u5B83\u56DE\u5831\u6210\u300C\u6C92\u6709\u9019\u500B skill\uFF0Fexample\u300D"] + ); +} async function kbdbList(env, entryType, limit = 100) { const resp = await kbdbFetch(env, `/entries?entry_type=${encodeURIComponent(entryType)}&limit=${limit}`); - if (!resp.ok) throw new Error(`KBDB list entry_type=${entryType} HTTP ${resp.status}`); + if (!resp.ok) { + throw new KbdbAccessError(resp.status, `list entry_type=${entryType}`, await resp.text().catch(() => "")); + } const data = await resp.json(); return data.entries ?? []; } async function kbdbGetByPageName(env, pageName) { const resp = await kbdbFetch(env, `/entries?page_name=${encodeURIComponent(pageName)}&limit=1`); - if (!resp.ok) return null; + if (!resp.ok) { + throw new KbdbAccessError(resp.status, `get page_name=${pageName}`, await resp.text().catch(() => "")); + } const data = await resp.json(); return data.entries?.[0] ?? null; } @@ -31284,7 +31377,7 @@ function parseTags(tagsJson) { return []; } } -function registerListSkills(server, env) { +function registerListSkills(server, env, identity) { server.tool( toolName("list_skills"), "\u5217\u6240\u6709 agent-skill blocks\uFF08\u5F9E arcrun/registry/skills/ \u540C\u6B65\u9032 KBDB\uFF09\u3002\u6BCF\u500B skill \u662F\u500B markdown playbook\uFF0C\u63CF\u8FF0 AI \u9762\u5C0D X \u554F\u984C\u8A72\u600E\u9EBC\u60F3 + \u8A72\u7528\u54EA\u500B example\u3002\u56DE [{slug, title, tags}]\u3002call get_skill(slug) \u62FF\u5B8C\u6574\u5167\u6587\u3002", @@ -31292,6 +31385,7 @@ function registerListSkills(server, env) { tag: external_exports.string().optional().describe("optional \u6A19\u7C64\u904E\u6FFE\u3002\u5982 'rag' / 'watcher' / 'debug'") }, async ({ tag }) => { + if (identity.kind === "stale") return staleIdentityError(); try { const blocks = await kbdbList(env, "agent-skill", 100); const skills = blocks.map((b) => { @@ -31313,20 +31407,19 @@ function registerListSkills(server, env) { return successResponse( { count: skills.length, skills }, [ - skills.length === 0 ? "\u6C92\u6709 skill \u547D\u4E2D\u3002\u8A66 list_skills() \u4E0D\u5E36 tag \u770B\u5168\u90E8" : "call arcrun_get_skill(slug) \u62FF\u55AE\u500B skill \u5B8C\u6574 markdown" + skills.length === 0 ? "\u6C92\u6709 skill \u547D\u4E2D\u3002\u8A66 list_skills() \u4E0D\u5E36 tag \u770B\u5168\u90E8" : "call arcrun_get_skill(slug) \u62FF\u55AE\u500B skill \u5B8C\u6574 markdown", + // 誠實:這裡回的是**這台實例被 seed 進去的那幾支**,不是「全世界的 skill 目錄」。 + // 上面清單沒有的名字(例如 'INDEX')就是這台沒有——別照舊教材去猜一個 slug。 + "\u{1F534} \u53EA\u7528\u4E0A\u9762\u6E05\u55AE\u88E1\u771F\u7684\u6709\u7684 slug\uFF1B\u6E05\u55AE\u6C92\u6709\uFF1D\u9019\u53F0\u5BE6\u4F8B\u6C92 seed \u9032\u53BB\uFF0C\u4E0D\u8981\u786C\u731C\u540D\u5B57" ] ); } catch (e) { - return errorResponse( - "fetch_failed", - e instanceof Error ? e.message : String(e), - ["\u7A0D\u5F8C\u91CD\u8A66", "\u82E5\u6301\u7E8C\u5931\u6557\uFF0C\u544A\u8A34 leo"] - ); + return kbdbFailure(e, "\u6539\u7528 kbdb_search({ q: 'skill' }) \u76F4\u63A5\u5728\u77E5\u8B58\u5EAB\u88E1\u627E skill \u5361\u7247\uFF08\u90A3\u689D\u8DEF\u8D70\u7684\u662F\u53E6\u4E00\u7D44\u6191\u64DA\uFF09", identity); } } ); } -function registerGetSkill(server, env) { +function registerGetSkill(server, env, identity) { server.tool( toolName("get_skill"), "\u62FF\u55AE\u4E00 agent-skill \u5B8C\u6574 markdown playbook\u3002slug \u5F9E list_skills \u53D6\u5F97\u3002", @@ -31334,15 +31427,17 @@ function registerGetSkill(server, env) { slug: external_exports.string().describe("skill slug\uFF0C\u4F8B\u5982 'build_watcher_workflow' / 'rag_with_arcrun'") }, async ({ slug }) => { + if (identity.kind === "stale") return staleIdentityError(); try { const pageName = slug.startsWith("skill-") ? slug : `skill-${slug}`; const block = await kbdbGetByPageName(env, pageName); if (!block) { return errorResponse( "not_found", - `skill "${slug}" \u4E0D\u5B58\u5728`, + `KBDB \u6B63\u5E38\u56DE\u61C9\uFF0C\u4F46\u6C92\u6709 page_name="${pageName}" \u9019\u5F35\u5361\u2014\u2014\u9019\u53F0\u5BE6\u4F8B\u6C92\u6709 seed \u9019\u652F skill\u3002\uFF08\u4E0D\u540C\u5BE6\u4F8B seed \u7684 skill \u4E0D\u4E00\u6A23\uFF0C\u5225\u7167\u820A\u6559\u6750\u5047\u8A2D\u67D0\u500B\u540D\u5B57\u4E00\u5B9A\u5728\u3002\uFF09`, [ - "call arcrun_list_skills() \u770B\u53EF\u7528 slug", + "call arcrun_list_skills() \u770B**\u9019\u53F0\u5BE6\u4F8B\u771F\u7684\u6709**\u54EA\u5E7E\u652F", + `kbdb_search({ q: '${slug}' }) \u770B\u5167\u5BB9\u662F\u4E0D\u662F\u88AB\u5B58\u6210\u5225\u7684\u540D\u5B57`, "\u78BA\u8A8D\u62FC\u5B57\u6B63\u78BA\uFF08\u4E0D\u9700\u8981 'skill-' prefix\uFF09" ] ); @@ -31354,16 +31449,16 @@ function registerGetSkill(server, env) { tags: parseTags(block.tags_json) }); } catch (e) { - return errorResponse( - "fetch_failed", - e instanceof Error ? e.message : String(e), - ["\u7A0D\u5F8C\u91CD\u8A66"] + return kbdbFailure( + e, + `\u6539\u7528 kbdb_search({ q: 'skill-${slug}' }) \u6488\u540C\u4E00\u5F35\u5361\u7247\uFF08skill \u5C31\u4F4F\u5728\u77E5\u8B58\u5EAB\u7684 entries \u88E1\uFF0C\u90A3\u689D\u8DEF\u8D70\u53E6\u4E00\u7D44\u6191\u64DA\uFF09`, + identity ); } } ); } -function registerListExamples(server, env) { +function registerListExamples(server, env, identity) { server.tool( toolName("list_examples"), "\u5217\u6240\u6709 workflow-example blocks\uFF08\u5F9E arcrun/registry/examples/ \u540C\u6B65\u9032 KBDB\uFF09\u3002\u6BCF\u500B example \u662F\u53EF\u76F4\u63A5 push \u7684 workflow YAML \u7BC4\u672C + description\u3002\u56DE [{slug, tags}]\u3002call get_example / search_examples \u62FF\u7D30\u7BC0\u3002", @@ -31371,6 +31466,7 @@ function registerListExamples(server, env) { tag: external_exports.string().optional().describe("optional \u6A19\u7C64\u904E\u6FFE\u3002\u5982 'rag' / 'cron' / 'llm' / 'webhook'") }, async ({ tag }) => { + if (identity.kind === "stale") return staleIdentityError(); try { const blocks = await kbdbList(env, "workflow-example", 200); const examples = blocks.map((b) => { @@ -31389,16 +31485,12 @@ function registerListExamples(server, env) { ] ); } catch (e) { - return errorResponse( - "fetch_failed", - e instanceof Error ? e.message : String(e), - ["\u7A0D\u5F8C\u91CD\u8A66"] - ); + return kbdbFailure(e, "\u6539\u7528 kbdb_search({ q: 'example' }) \u76F4\u63A5\u5728\u77E5\u8B58\u5EAB\u88E1\u627E example \u5361\u7247\uFF08\u90A3\u689D\u8DEF\u8D70\u53E6\u4E00\u7D44\u6191\u64DA\uFF09", identity); } } ); } -function registerGetExample(server, env) { +function registerGetExample(server, env, identity) { server.tool( toolName("get_example"), "\u62FF\u55AE\u4E00 workflow-example \u5B8C\u6574 YAML + description\u3002slug \u5F9E list_examples / search_examples \u53D6\u5F97\u3002\u53EF\u76F4\u63A5\u62FF YAML \u6539\u6210\u4F60\u81EA\u5DF1\u7684 \u2192 push\u3002", @@ -31406,16 +31498,18 @@ function registerGetExample(server, env) { slug: external_exports.string().describe("example slug\uFF0C\u4F8B\u5982 'rag-search-answer' / 'cron-watcher'") }, async ({ slug }) => { + if (identity.kind === "stale") return staleIdentityError(); try { const pageName = slug.startsWith("example-") ? slug : `example-${slug}`; const block = await kbdbGetByPageName(env, pageName); if (!block) { return errorResponse( "not_found", - `example "${slug}" \u4E0D\u5B58\u5728`, + `KBDB \u6B63\u5E38\u56DE\u61C9\uFF0C\u4F46\u6C92\u6709 page_name="${pageName}" \u9019\u5F35\u5361\u2014\u2014\u9019\u53F0\u5BE6\u4F8B\u6C92 seed \u9019\u500B example\u3002`, [ - "call arcrun_list_examples() \u770B\u53EF\u7528 slug", - "\u6216 arcrun_search_examples(use_case) \u7528\u81EA\u7136\u8A9E\u8A00\u627E" + "call arcrun_list_examples() \u770B**\u9019\u53F0\u5BE6\u4F8B\u771F\u7684\u6709**\u54EA\u4E9B slug", + "\u6216 arcrun_search_examples(use_case) \u7528\u95DC\u9375\u5B57\u627E", + `kbdb_search({ q: '${slug}' }) \u770B\u5167\u5BB9\u662F\u4E0D\u662F\u88AB\u5B58\u6210\u5225\u7684\u540D\u5B57` ] ); } @@ -31436,16 +31530,16 @@ function registerGetExample(server, env) { "\u770B description_md \u4E86\u89E3\u8A2D\u8A08\u610F\u5716 / \u6539\u9020\u65B9\u5411" ]); } catch (e) { - return errorResponse( - "fetch_failed", - e instanceof Error ? e.message : String(e), - ["\u7A0D\u5F8C\u91CD\u8A66"] + return kbdbFailure( + e, + `\u6539\u7528 kbdb_search({ q: 'example-${slug}' }) \u6488\u540C\u4E00\u5F35\u5361\u7247\uFF08example \u5C31\u4F4F\u5728\u77E5\u8B58\u5EAB\u7684 entries \u88E1\uFF09`, + identity ); } } ); } -function registerSearchExamples(server, env) { +function registerSearchExamples(server, env, identity) { server.tool( toolName("search_examples"), "\u7528 use case \u95DC\u9375\u5B57\u641C workflow examples\uFF0C\u56DE\u6700\u76F8\u95DC N \u500B\u3002\u6CE8\u610F\uFF1A\u57FA\u672C\u76E4\u76EE\u524D\u662F D1 LIKE \u95DC\u9375\u5B57\u641C\u5C0B\uFF08\u975E\u8A9E\u7FA9 embedding\uFF1B\u8A9E\u7FA9\u662F kbdb-base Phase 1 \u7684 embed \u6A21\u7D44\uFF0C\u5C1A\u672A\u4E0A\uFF09\u3002\u2192 \u7528\u5177\u9AD4\u8A5E\uFF08'email'\u3001'cron'\u3001'rag'\uFF09\u6BD4\u6574\u53E5\u81EA\u7136\u8A9E\u8A00\u547D\u4E2D\u7387\u9AD8\u3002\u4E5F\u6703\u6BD4\u5C0D slug/tag\u3002", @@ -31454,6 +31548,7 @@ function registerSearchExamples(server, env) { top_k: external_exports.number().int().min(1).max(20).optional().describe("\u56DE\u5E7E\u500B\u7D50\u679C\uFF08\u9810\u8A2D 5\uFF09") }, async ({ query, top_k }) => { + if (identity.kind === "stale") return staleIdentityError(); try { const k = top_k ?? 5; const q = query.trim(); @@ -31498,21 +31593,17 @@ function registerSearchExamples(server, env) { ] ); } catch (e) { - return errorResponse( - "internal_error", - e instanceof Error ? e.message : String(e), - ["\u91CD\u8A66\u4E00\u6B21"] - ); + return kbdbFailure(e, `\u6539\u7528 kbdb_search({ q: '${query.trim()}' }) \u76F4\u63A5\u67E5\u77E5\u8B58\u5EAB\uFF08\u90A3\u689D\u8DEF\u8D70\u53E6\u4E00\u7D44\u6191\u64DA\uFF09`, identity); } } ); } -function registerAllSkillExampleTools(server, env) { - registerListSkills(server, env); - registerGetSkill(server, env); - registerListExamples(server, env); - registerGetExample(server, env); - registerSearchExamples(server, env); +function registerAllSkillExampleTools(server, env, identity) { + registerListSkills(server, env, identity); + registerGetSkill(server, env, identity); + registerListExamples(server, env, identity); + registerGetExample(server, env, identity); + registerSearchExamples(server, env, identity); } // mcp/src/tools/arcrun_recipe.ts @@ -31713,62 +31804,6 @@ function registerRecipeDelete(server, env) { ); } -// mcp/src/lib/portal-client.ts -async function portalFetch(env, session, path, opts = {}) { - if (!env.CYPHER_EXECUTOR) { - throw new Error("CYPHER_EXECUTOR service binding not configured"); - } - const url = new URL(`https://cypher${path}`); - for (const [k, v] of Object.entries(opts.query ?? {})) { - if (v !== void 0 && v !== "") url.searchParams.set(k, String(v)); - } - return env.CYPHER_EXECUTOR.fetch(url.toString(), { - method: opts.method ?? "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${session}` - }, - body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0 - }); -} -function resolveKnowledgeIdentity(authPath, portal) { - if (authPath !== "oauth") return { kind: "service" }; - return portal?.session ? { kind: "portal", portal } : { kind: "stale" }; -} -function staleIdentityError() { - return errorResponse( - "identity_missing", - "\u9019\u689D MCP \u9023\u7DDA\u662F\u820A\u7248\u7C3D\u767C\u7684 token\uFF0C\u88E1\u9762\u6C92\u6709\u767B\u5165\u8005\u8EAB\u5206\uFF0C\u56E0\u6B64\u67E5\u4E0D\u5230\u4EFB\u4F55\u77E5\u8B58\u5167\u5BB9\u3002\u91CD\u65B0\u9023\u7DDA\u4E00\u6B21\uFF08\u5728 claude.ai \u7684 connector \u8A2D\u5B9A\u88E1\u91CD\u65B0\u6388\u6B0A\u3001\u8F38\u5165\u4F60\u7684 Portal \u5E33\u5BC6\uFF09\u5373\u53EF\u2014\u2014\u4E0D\u9700\u8981\u53E6\u5916\u627E\u4EFB\u4F55 credential \u6216\u91D1\u9470\u3002", - [ - "\u5230 claude.ai \u2192 Settings \u2192 Connectors\uFF0C\u628A\u9019\u500B connector \u91CD\u65B0\u9023\u7DDA\u4E00\u6B21\uFF08\u6703\u8DF3\u51FA\u8F38\u5165 Portal \u5E33\u5BC6\u7684\u9801\u9762\uFF09", - "\u91CD\u9023\u5F8C kbdb_* \u5168\u90E8\u5DE5\u5177\u90FD\u6703\u7528\u4F60\u9019\u500B\u5E33\u865F\u7684\u6B0A\u9650\u67E5\u8A62" - ] - ); -} -async function portalError(res, what) { - const detail = await res.text().catch(() => ""); - if (res.status === 401) { - return errorResponse( - "session_expired", - `${what}\u5931\u6557\uFF1A\u767B\u5165\u968E\u6BB5\u5DF2\u904E\u671F\uFF08portal session \u5230\u671F\u6216\u5DF2\u767B\u51FA\uFF09\u3002`, - [ - "\u5230 claude.ai \u2192 Settings \u2192 Connectors \u91CD\u65B0\u9023\u7DDA\u9019\u500B connector\uFF08\u91CD\u65B0\u8F38\u5165 Portal \u5E33\u5BC6\uFF09", - "\u91CD\u9023\u5F8C\u6B0A\u9650\u8207\u4F60\u5728 portal \u7DB2\u9801\u4E0A\u770B\u5230\u7684\u4E00\u81F4" - ], - detail - ); - } - if (res.status === 403) { - return errorResponse( - "forbidden", - `${what}\u5931\u6557\uFF1A\u9019\u500B\u5E33\u865F\u6C92\u6709\u9019\u9805\u6B0A\u9650\uFF08\u5E33\u865F\u53EF\u80FD\u5DF2\u505C\u7528\uFF0C\u6216\u6C92\u6709\u88AB\u6388\u6B0A\u8A72\u77E5\u8B58\u5EAB\uFF09\u3002`, - ["\u8ACB\u77E5\u8B58\u5EAB\u7BA1\u7406\u54E1\u5728 portal \u7684\u5E33\u865F\u7BA1\u7406\u88E1\u78BA\u8A8D\u4F60\u7684\u72C0\u614B\u8207\u53EF\u7528\u77E5\u8B58\u5EAB"], - detail - ); - } - return errorResponse(`portal_${res.status}`, `${what}\u5931\u6557\uFF08HTTP ${res.status}\uFF09`, ["\u7A0D\u5F8C\u91CD\u8A66"], detail); -} - // mcp/src/tools/kbdb_data.ts var OWNER_IGNORED_HINT = "owner_id \u5728\u767B\u5165\u8EAB\u5206\u4E0B\u4E0D\u751F\u6548\uFF1A\u67E5\u8A62\u7BC4\u570D\u7531\u4F60\u7684\u5E33\u865F\u6B0A\u9650\u6C7A\u5B9A\uFF08\u8207\u4F60\u5728 portal \u7DB2\u9801\u770B\u5230\u7684\u4E00\u81F4\uFF09"; function registerAllKbdbDataTools(server, env, identity) { @@ -32142,8 +32177,10 @@ function renderLibraryMapLines(libraries) { const narrative = (l.narrative ?? "").trim() || "\uFF08narrative \u5F85\u88DC\uFF09"; const clipped = narrative.length > MAX_NARRATIVE_CHARS ? `${narrative.slice(0, MAX_NARRATIVE_CHARS)}\u2026` : narrative; const core = entityNames(l.top_entities, 3); - const count = Number(l.triplet_count ?? 0) || 0; - return `- ${l.library}\uFF1A${clipped}\uFF5C\u6838\u5FC3\uFF1A${core.length ? core.join("\u3001") : "\uFF08\u5C1A\u7121\uFF09"}\uFF5C${count} triplets`; + const tripletCount = Number(l.triplet_count ?? 0) || 0; + const entryCount = Number(l.entry_count ?? 0) || 0; + const countPart = tripletCount > 0 ? `${tripletCount} triplets` : entryCount > 0 ? `0 triplets\uFF0F${entryCount} \u7B46\u539F\u59CB\u5167\u5BB9\uFF08\u5C1A\u672A\u8403\u53D6\u95DC\u4FC2\uFF0Ckbdb_search \u67E5\u5F97\u5230\uFF09` : `0 triplets\uFF0F0 \u5167\u5BB9`; + return `- ${l.library}\uFF1A${clipped}\uFF5C\u6838\u5FC3\uFF1A${core.length ? core.join("\u3001") : "\uFF08\u5C1A\u7121\uFF09"}\uFF5C${countPart}`; }); const omitted = rows.length > MAX_LIBRARY_LINES ? ` \uFF08\u5176\u9918 ${rows.length - MAX_LIBRARY_LINES} \u5EAB\u7565\uFF0Ckbdb_get_map \u53EF\u770B\u5168\u90E8\uFF09` : ""; @@ -32195,6 +32232,13 @@ var RECOMPUTE_HINTS = [ function registerAllKbdbMapTools(server, env, identity) { registerGetMap(server, env, identity); } +function entryOnlyHint(tripletCount, entryCount, library) { + if (tripletCount > 0 || entryCount <= 0) return []; + const lib = library ? `\u300C${library}\u300D` : "\u9019\u500B\u5EAB"; + return [ + `${lib} triplet_count\uFF1D0\uFF0C\u4F46\u6709 ${entryCount} \u7B46\u539F\u59CB\u5167\u5BB9\uFF08entries\uFF09\u2014\u2014\u4E09\u5143\u7D44\u8403\u53D6\u9084\u6C92\u5C0D\u5B83\u8DD1\u904E\uFF0C\u4E0D\u4EE3\u8868\u6C92\u6709\u77E5\u8B58\u3002\u7528 kbdb_search\uFF08\u95DC\u9375\u5B57\u6216\u8A9E\u7FA9\uFF09\u76F4\u63A5\u67E5\u5F97\u5230\u5167\u5BB9\u3002` + ]; +} function registerGetMap(server, env, identity) { server.tool( "kbdb_get_map", @@ -32229,7 +32273,8 @@ function registerGetMap(server, env, identity) { ...l, // 防禦:top_entities 若是 JSON 字串形就 parse 成名字清單(失敗當空,誠實不 crash)。 top_entities: entityNames(l.top_entities, 3), - triplet_count: Number(l.triplet_count ?? 0) || 0 + triplet_count: Number(l.triplet_count ?? 0) || 0, + entry_count: Number(l.entry_count ?? 0) || 0 })); if (libraries.length === 0) { return successResponse({ libraries: [], count: 0 }, [ @@ -32238,9 +32283,15 @@ function registerGetMap(server, env, identity) { ...RECOMPUTE_HINTS ]); } + const entryOnlyLibs = libraries.filter( + (l) => (l.triplet_count ?? 0) === 0 && (l.entry_count ?? 0) > 0 + ); return successResponse({ libraries, count: libraries.length }, [ "\u8981\u770B\u67D0\u5EAB\u7D30\u7BC0\uFF1Akbdb_get_map(library='\u5EAB\u540D')", - "\u9032\u5EAB\u67E5\u5167\u5BB9\uFF1Akbdb_search\uFF08\u95DC\u9375\u5B57/\u8A9E\u7FA9\uFF09\uFF1B\u67E5\u95DC\u4FC2\uFF1Akbdb_graph_neighbors" + "\u9032\u5EAB\u67E5\u5167\u5BB9\uFF1Akbdb_search\uFF08\u95DC\u9375\u5B57/\u8A9E\u7FA9\uFF09\uFF1B\u67E5\u95DC\u4FC2\uFF1Akbdb_graph_neighbors", + ...entryOnlyLibs.length > 0 ? [ + `${entryOnlyLibs.map((l) => l.library).join("\u3001")} \u9019\u5E7E\u5EAB triplet_count\uFF1D0 \u4F46 entry_count\uFF1E0\uFF1A\u6709\u539F\u59CB\u5167\u5BB9\uFF0C\u53EA\u662F\u9084\u6C92\u8403\u53D6\u51FA\u4E09\u5143\u7D44\u95DC\u4FC2\u2014\u2014\u5225\u628A 0 triplets \u8B80\u6210\u300C\u6C92\u6709\u77E5\u8B58\u300D\uFF0C\u76F4\u63A5 kbdb_search \u9032\u53BB\u67E5\u3002` + ] : [] ]); } const res = await mapFetch(`/map/${encodeURIComponent(library)}${qs}`); @@ -32268,11 +32319,13 @@ function registerGetMap(server, env, identity) { top_entities: parseSlotArray(raw2.top_entities), relation_profile: parseSlotArray(raw2.relation_profile), bridges: parseSlotArray(raw2.bridges), - triplet_count: Number(raw2.triplet_count ?? 0) || 0 + triplet_count: Number(raw2.triplet_count ?? 0) || 0, + entry_count: Number(raw2.entry_count ?? 0) || 0 }; return successResponse({ map: map2 }, [ "bridges\uFF1D\u6B64\u5EAB entity \u540C\u6642\u51FA\u73FE\u5728\u54EA\u4E9B\u5176\u4ED6\u5EAB\uFF08\u53EA\u6709\u5169\u5074\u4E09\u5143\u7D44\u90FD\u6A19\u4E86 library \u503C\u624D\u6293\u5F97\u5230\uFF0C\u820A\u8CC7\u6599\u82E5\u6C92\u6A19\u6703\u504F\u7A00\u758F\uFF0C\u662F\u8AA0\u5BE6\u73FE\u6CC1\u4E0D\u662F bug\uFF09", - "\u6CBF\u6838\u5FC3 entity \u6316\u95DC\u4FC2\uFF1Akbdb_graph_neighbors(subject=entity \u540D)" + "\u6CBF\u6838\u5FC3 entity \u6316\u95DC\u4FC2\uFF1Akbdb_graph_neighbors(subject=entity \u540D)", + ...entryOnlyHint(map2.triplet_count, map2.entry_count, library) ]); } catch (e) { return errorResponse("internal_error", e instanceof Error ? e.message : String(e), ["\u7A0D\u5F8C\u91CD\u8A66"]); @@ -32340,7 +32393,7 @@ function registerAllTools(server, env, orgNamespace, partnerToken, identity) { registerReportFeedback(server, env, orgNamespace); registerAllIntrospectionTools(server, env); registerAllWorkflowCrudTools(server, env); - registerAllSkillExampleTools(server, env); + registerAllSkillExampleTools(server, env, identity); registerAllRecipeTools(server, env); registerAllKbdbDataTools(server, env, identity); registerAllKbdbGraphTools(server, env, orgNamespace, identity); @@ -32349,7 +32402,47 @@ function registerAllTools(server, env, orgNamespace, partnerToken, identity) { } // mcp/src/mcp-handler.ts -async function handleMcpRequest(request, env, orgNamespace, partnerToken, identity) { +var KNOWLEDGE_FIRST = [ + "\u3010\u9019\u689D\u9023\u7DDA\u4E0A\u6709\u4E3B\u4EBA\u7684\u77E5\u8B58\u5EAB\u2014\u2014\u5148\u67E5\u5B83\uFF0C\u518D\u67E5\u5225\u7684\u3011", + "", + "\u9019\u689D MCP \u9023\u7DDA\u5F8C\u9762\u63A5\u8457\u4E00\u500B **KBDB \u77E5\u8B58\u5EAB**\uFF1A\u9019\u53F0\u5BE6\u4F8B\u7684\u4E3B\u4EBA\u9577\u671F\u7D2F\u7A4D\u7684\u7B46\u8A18\u3001\u6C7A\u7B56\u3001", + "\u8E29\u904E\u7684\u5751\u3001\u5C08\u6848\u73FE\u6CC1\u3001skill \u8207\u5DE5\u4F5C\u6D41\u7D00\u9304\uFF0C\u90FD\u5728\u88E1\u9762\u3002**\u4F60\u4E0D\u662F\u5F9E\u96F6\u958B\u59CB\u7684**\u2014\u2014", + "\u4F60\u5C0D\u9019\u4E9B\u5C08\u6848\u7684\u5167\u5EFA\u5370\u8C61\u591A\u534A\u662F\u932F\u7684\u6216\u904E\u6642\u7684\uFF0C\u5EAB\u88E1\u90A3\u4EFD\u624D\u662F\u4E3B\u4EBA\u8A8D\u7684\u7248\u672C\u3002", + "", + "\u{1F534} **\u6709\u4EBA\u554F\u4F60\u300CX \u662F\u4EC0\u9EBC\uFF0F\u70BA\u4EC0\u9EBC\u9019\u6A23\u505A\uFF0F\u4E4B\u524D\u600E\u9EBC\u6C7A\u5B9A\u7684\uFF0F\u73FE\u5728\u505A\u5230\u54EA\u300D\u2014\u2014", + "\u4F60\u7684\u7B2C\u4E00\u500B\u52D5\u4F5C\u662F `kbdb_search`\uFF0C\u4E0D\u662F grep \u539F\u59CB\u78BC\u3001\u4E0D\u662F\u4E0A\u7DB2\u641C\u3001\u4E0D\u662F\u56DE\u7B54\u300C\u6211\u4E0D\u77E5\u9053\u300D\u3002**", + "", + "- `kbdb_search({ q: \"Arcrun \u662F\u4EC0\u9EBC\" })` \u2014 \u95DC\u9375\u5B57\u67E5\uFF08\u9810\u8A2D `mode:'keyword'`\uFF0C\u57FA\u672C\u76E4\u6C38\u9060\u53EF\u7528\uFF09\u3002", + " \u63DB\u5E7E\u7D44\u8B1B\u6CD5\u518D\u653E\u68C4\uFF1B\u60F3\u8981\u8A9E\u7FA9\u76F8\u4F3C\u5EA6\u7528 `mode:'semantic'`\u3002**\u9019\u4E00\u652F\u662F\u4F60\u7684\u7B2C\u4E00\u7AD9\u3002**", + "- `kbdb_get_map()` \u2014 \u4E0D\u77E5\u9053\u8A72\u9032\u54EA\u500B\u5EAB\u6642\u5148\u770B\u85CF\u66F8\u5730\u5716\uFF08\u4E0B\u9762\u82E5\u6709\u3010\u85CF\u66F8\u5730\u5716\u3011\u5C31\u662F\u5B83\u7684\u5FEB\u7167\uFF09\u3002", + '- `kbdb_graph_neighbors({ subject: "Arcrun" })` \u2014 \u67E5\u67D0\u500B\u6771\u897F\u8DDF\u8AB0\u6709\u95DC\u4FC2\uFF08\u4E09\u5143\u7D44\u904D\u6B77\uFF09\u3002', + "- `kbdb_list_templates` / `kbdb_query` \u2014 \u6309 template \u53D6\u6574\u6279\u7D50\u69CB\u5316\u8CC7\u6599\u3002", + "", + "\u{1F534} **\u9019\u4E09\u4EF6\u4E8B\u4E0D\u53EF\u4EE5\u8B1B\u6210\u540C\u4E00\u53E5**\uFF08\u8B1B\u6210\u540C\u4E00\u53E5\u5C31\u662F\u5728\u9A19\u4EBA\uFF09\uFF1A", + "\u2460 \u300C\u77E5\u8B58\u5EAB\u88E1\u6C92\u6709\u300D\u3000\u2461 \u300C\u6211\u6C92\u67E5\u300D\u3000\u2462 \u300C\u5730\u5716\u6C92\u53D6\u5230\uFF0F\u67D0\u5EAB\u986F\u793A 0\u300D\u3002", + "\u67E5\u904E\u771F\u7684\u6C92\u6709 \u2192 \u660E\u8AAA\u300C\u77E5\u8B58\u5EAB\u88E1\u67E5\u4E0D\u5230\uFF0C\u4EE5\u4E0B\u662F\u6211\u5F9E\u539F\u59CB\u78BC\uFF0F\u7DB2\u8DEF\u63A8\u7684\u300D\uFF0C\u518D\u53BB\u8B80 code \u6216\u4E0A\u7DB2\u3002", + "**\u6C92\u67E5\u5C31\u56DE\u7B54\uFF1D\u62FF\u4F60\u7684\u731C\u6E2C\u5192\u5145\u4E3B\u4EBA\u7684\u77E5\u8B58\uFF0C\u90A3\u662F\u9019\u689D\u9023\u7DDA\u4E0A\u6700\u56B4\u91CD\u7684\u932F\u3002**", + "", + "\u{1F534} **\u5730\u5716\u662F\u7D22\u5F15\uFF0C\u4E0D\u662F\u5EAB\u5B58\u6E05\u55AE**\uFF1A\u67D0\u5EAB\u986F\u793A `0 triplets`\u3001\u6216\u4E0B\u9762\u6574\u6BB5\u3010\u85CF\u66F8\u5730\u5716\u3011\u6C92\u51FA\u73FE\uFF0C", + "\u90FD**\u4E0D\u4EE3\u8868**\u6C92\u6709\u77E5\u8B58\uFF08\u53EF\u80FD\u53EA\u662F\u9084\u6C92\u91CD\u7B97\u3001\u6216\u9019\u6B21\u6C92\u6293\u5230\uFF09\u3002\u8981\u77E5\u9053\u6709\u6C92\u6709\uFF0C\u53EA\u6709\u4E00\u500B\u65B9\u6CD5\uFF1A`kbdb_search` \u67E5\u904E\u3002", + "\u540C\u7406\uFF0C\u4EFB\u4F55\u5DE5\u5177\u56DE 401\uFF0F\u9023\u4E0D\u4E0A\uFF0F\u6C92\u6B0A\u9650\uFF0C\u90A3\u662F**\u8B80\u4E0D\u5230**\uFF0C\u4E0D\u662F**\u4E0D\u5B58\u5728**\u2014\u2014\u7167\u5B83\u7D66\u7684 next_actions \u4FEE\uFF0C", + "\u5225\u628A\u5B83\u6539\u53E3\u8B1B\u6210\u300C\u9019\u88E1\u6C92\u6709\u300D\uFF08`arcrun_get_skill` \u66FE\u628A KBDB \u7684 401 \u8B1B\u6210\u300Cskill \u4E0D\u5B58\u5728\u300D\uFF0C\u5C31\u662F\u9019\u500B\u75C5\uFF09\u3002" +].join("\n"); +var MAP_UNAVAILABLE_NOTE = [ + "\u3010\u85CF\u66F8\u5730\u5716\uFF1A\u9019\u6B21\u6C92\u53D6\u5230\u3011", + "\u5730\u5716\u6293\u53D6\u903E\u6642\uFF0F\u56DE\u932F\uFF0F\u6216\u5B83\u56DE\u5831\u7684\u6E05\u55AE\u662F\u7A7A\u7684\uFF08\u4E5F\u53EF\u80FD\u53EA\u662F\u9084\u6C92\u91CD\u7B97\u904E\uFF09\u3002", + "\u{1F534} **\u9019\u662F\u300C\u5730\u5716\u6C92\u62FF\u5230\u300D\uFF0C\u4E0D\u662F\u300C\u9019\u88E1\u6C92\u6709\u77E5\u8B58\u300D\u3002** \u4E0A\u9762\u90A3\u689D\u898F\u5247\u7167\u820A\uFF1A", + "\u8981\u77E5\u9053\u5EAB\u88E1\u6709\u4EC0\u9EBC\uFF0C\u76F4\u63A5 `kbdb_search`\uFF1B\u60F3\u518D\u6293\u4E00\u6B21\u5730\u5716\u547C\u53EB `kbdb_get_map()`\uFF08\u5B83\u6703\u56DE\u5831\u771F\u6B63\u7684\u539F\u56E0\uFF09\u3002" +].join("\n"); +var MAP_STALE_NOTE = [ + "\u3010\u85CF\u66F8\u5730\u5716\uFF1A\u62FF\u4E0D\u5230\uFF0C\u56E0\u70BA\u9019\u689D\u9023\u7DDA\u662F\u820A\u7248\u7C3D\u767C\u7684 token\u3011", + "\u9019\u689D\u9023\u7DDA\u7684 token \u6C92\u5E36\u767B\u5165\u8005\u8EAB\u5206\uFF0C`kbdb_*` \u6703\u56DE `identity_missing`\u3002", + "\u{1F534} **\u9019\u4E0D\u4EE3\u8868\u77E5\u8B58\u5EAB\u662F\u7A7A\u7684**\u2014\u2014\u662F\u9019\u689D\u9023\u7DDA\u9084\u6C92\u8A8D\u5F97\u4F60\u3002", + "\u4ECD\u7136\u5148\u547C\u53EB\u4E00\u6B21 `kbdb_search` \u78BA\u8A8D\u932F\u8AA4\u78BC\uFF1B\u82E5\u771F\u7684\u662F `identity_missing`\uFF0C", + "\u8ACB\u4F7F\u7528\u8005\u5230 claude.ai \u2192 Settings \u2192 Connectors \u628A\u9019\u500B connector \u91CD\u65B0\u9023\u7DDA\u4E00\u6B21\uFF08\u91CD\u65B0\u8F38\u5165 Portal \u5E33\u5BC6\uFF09\uFF0C", + "**\u4E0D\u8981\u6539\u53E3\u8AAA\u300C\u67E5\u4E0D\u5230\u8CC7\u6599\u300D\u6216\u81EA\u5DF1\u53BB\u731C\u7B54\u6848\u3002**" +].join("\n"); +async function buildServerInstructions(env, identity) { const mapInstructions = await buildLibraryMapInstructions(env, identity); const startHere = [ "# Arcrun \u2014 \u4F60\u5DF2\u7D93\u914D\u5099\u4E86\u9019\u5957\u5DE5\u5177\uFF0C\u5225\u4E0A\u7DB2\u627E", @@ -32363,9 +32456,13 @@ async function handleMcpRequest(request, env, orgNamespace, partnerToken, identi "", "1. `arcrun_get_skill('write_intent_workflow')` \u2014 **\u5FC5\u8B80\u7B2C\u4E00\u652F**\u3002", " \u6559\u4F60\u7528 `>>` \u5BEB\u300C\u610F\u5716\u5DE5\u4F5C\u6D41\u300D\u3002\u4F60**\u4E0D\u9700\u8981\u5148\u77E5\u9053\u6709\u54EA\u4E9B\u96F6\u4EF6**\uFF0C\u5148\u5BEB\u610F\u5716\u3002", + " \u26A0\uFE0F \u9019\u652F\u82E5\u56DE\u932F\uFF08401\uFF0F\u9023\u4E0D\u4E0A\uFF0F`kbdb_unreachable`\uFF09\uFF0C\u90A3\u662F**\u9019\u689D\u9023\u7DDA\u8B80\u4E0D\u5230 KBDB**\uFF0C", + " **\u4E0D\u662F skill \u4E0D\u5B58\u5728**\u2014\u2014\u540C\u4E00\u4EFD\u5167\u5BB9\u7528 `kbdb_search({ q: 'skill-write_intent_workflow' })` \u6488\u5F97\u5230\u3002", "2. `arcrun_whoami()` \u2014 \u78BA\u8A8D\u9023\u5230\u54EA\u500B\u5E33\u865F\uFF08\u52FF\u81EA\u884C curl \u731C\u5E33\u865F URL\uFF09\u3002", "3. \u628A\u610F\u5716\u4E1F `POST /cypher/search` \u6216 `arcrun_validate_yaml` \u2014 \u7CFB\u7D71\u544A\u8A34\u4F60\u54EA\u4E9B\u96F6\u4EF6\u5B58\u5728\u3002", - "4. \u5361\u4F4F\uFF0F\u4E0D\u77E5\u9053\u8A72\u67E5\u4EC0\u9EBC \u2192 `arcrun_get_skill('INDEX')`\uFF08\u5168\u9928\u5C0E\u822A\uFF1A\u4EC0\u9EBC\u554F\u984C\u67E5\u54EA\u88E1\uFF0B\u5DF2\u77E5\u7684\u5751\uFF09\u3002", + "4. \u5361\u4F4F\uFF0F\u4E0D\u77E5\u9053\u8A72\u67E5\u4EC0\u9EBC \u2192 \u5148 `arcrun_list_skills()` **\u770B\u9019\u53F0\u5BE6\u4F8B\u771F\u7684\u6709\u54EA\u5E7E\u652F**\uFF0C\u518D\u6311\u4E00\u652F\u8B80\u3002", + " \uFF082026-08-13 \u5BE6\u6E2C\uFF1A\u4E0D\u540C\u5BE6\u4F8B seed \u7684 skill \u4E0D\u4E00\u6A23\uFF0C\u6709\u7684\u5BE6\u4F8B\u53EA\u6709\u5169\u652F\u3001\u9023 `INDEX` \u90FD\u6C92\u6709\u3002", + " **\u4E0D\u8981\u7167\u6559\u6750\u76F4\u63A5\u6307\u540D\u4E00\u500B slug** \u2014\u2014\u5148\u5217\u6E05\u55AE\uFF0C\u6216 `kbdb_search({ q: 'skill' })` \u76F4\u63A5\u5728\u5EAB\u88E1\u627E\u3002\uFF09", "5. \u7F3A\u96F6\u4EF6\u6642\uFF1A\u7F3A API \u2192 \u5BEB recipe\uFF08`arcrun_recipe_push`\uFF09\uFF1B\u7F3A\u80FD\u529B \u2192 \u6295\u7A3F\u96F6\u4EF6 PR\u3002", " \u{1F534} **\u4E0D\u8981\u56E0\u70BA\u67E5\u4E0D\u5230\u96F6\u4EF6\u5C31\u6539\u5BEB\u6210 `code` \u7BC0\u9EDE**\u2014\u2014\u90A3\u53EB\u300C\u8179\u8A9E\u8853\u300D\uFF08\u8868\u9762\u7528 Arcrun\u3001", " \u5BE6\u969B\u5168\u5BEB JS\uFF09\u3002`code` \u53EA\u7528\u65BC\u5C40\u90E8\u6574\u5F62\uFF08\u4F8B\uFF1A\u525D\u6389 LLM \u56DE\u61C9\u7684\u96DC\u8A0A\uFF09\u3002", @@ -32381,11 +32478,11 @@ async function handleMcpRequest(request, env, orgNamespace, partnerToken, identi "\u4E0D\u662F\u5931\u6557\u2014\u2014\u5225\u56E0\u70BA\u300C\u53EA\u6709\u4E00\u689D\u8DEF\u6709\u8F38\u51FA\u300D\u5C31\u4EE5\u70BA\u58DE\u6389\u800C\u6539\u5BEB\u6210 code\uFF082026-08-01 \u5BE6\u649E\uFF09\u3002", "\u7B2C\u4E00\u500B\u7BC0\u9EDE\u56FA\u5B9A\u662F `input`\u3002" ].join("\n"); - const instructions = mapInstructions ? `${startHere} - ---- - -${mapInstructions}` : startHere; + const mapSection = mapInstructions ?? (identity.kind === "stale" ? MAP_STALE_NOTE : MAP_UNAVAILABLE_NOTE); + return [KNOWLEDGE_FIRST, startHere, mapSection].join("\n\n---\n\n"); +} +async function handleMcpRequest(request, env, orgNamespace, partnerToken, identity) { + const instructions = await buildServerInstructions(env, identity); const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: void 0 }); const server = new McpServer( { name: "arcrun-mcp-server", version: "1.0.0" }, diff --git a/.worker-builds/manifest.json b/.worker-builds/manifest.json index 8a6a2f4..5ba07a6 100644 --- a/.worker-builds/manifest.json +++ b/.worker-builds/manifest.json @@ -1,8 +1,8 @@ { "schema": 1, "built_for": "arcrun-tier2-worker-artifacts", - "generated_at": "2026-08-12T16:25:19.210Z", - "repo_head": "2fcae722e7d7eab3c8b39744b87ae1344d23ba2a", + "generated_at": "2026-08-13T10:28:15.839Z", + "repo_head": "1ccee0055ebdb1f6d74094df3e807f56b849fa6f", "repo_dirty": false, "workers": [ { @@ -58,11 +58,11 @@ { "name": "arcrun-kbdb", "source_dir": "kbdb", - "source_commit": "f87d0e92f49690253e7c89c5badc82a08eb5d21b", + "source_commit": "614fe448124dbd25d8228e37723473f1704f3a7a", "main_module": "worker.mjs", "main_file": "arcrun-kbdb/worker.mjs", - "js_bytes": 149797, - "content_sha256": "8b23853cbc88aee0ca15ef20ca46e92bd8e75064cd311af2847f4d51811960b1", + "js_bytes": 151412, + "content_sha256": "f15e16cca6cefb0d8631759c27fec58cd36262abf6aed7853f3b35c6e8e1e09b", "modules": [], "compat_date": "2025-02-19", "compat_flags": [ @@ -148,11 +148,11 @@ { "name": "arcrun-mcp", "source_dir": "mcp", - "source_commit": "10d150ac2b4385af95a457f3c411430c4a146cf9", + "source_commit": "1ccee0055ebdb1f6d74094df3e807f56b849fa6f", "main_module": "worker.mjs", "main_file": "arcrun-mcp/worker.mjs", - "js_bytes": 1179487, - "content_sha256": "1cd4c4d079d72bf7cba7c490ba6a88476f70b3ea51af7e5c93f9a184ae3c0ce6", + "js_bytes": 1192612, + "content_sha256": "9bc8dffb34d0f36608a78b2cfeb8ee8b5c793f52b4497f5d4d5f69f70700972e", "modules": [], "compat_date": "2024-11-27", "compat_flags": [