@tool-bridge/server 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{airtable-ZNWHIJMO.js → airtable-DMJEEGJP.js} +19 -16
- package/dist/{alt_text_generator_ai-X7RMDDM5.js → alt_text_generator_ai-RELFQ5FV.js} +4 -4
- package/dist/{amap-3VER75VH.js → amap-YSYEACKQ.js} +4 -4
- package/dist/{anthropic-N7NTB3UC.js → anthropic-D5X5GSYE.js} +23 -25
- package/dist/{apify-JB5P5AJI.js → apify-5NVFU3IZ.js} +20 -19
- package/dist/{brave_search-USL5OOBS.js → brave_search-X557FMGO.js} +16 -14
- package/dist/chunk-JEWFX6Z6.js +71 -0
- package/dist/{chunk-RVUERWUZ.js → chunk-MDY7K5CX.js} +748 -788
- package/dist/{chunk-WIGQCP6O.js → chunk-MZCIPUNQ.js} +141 -176
- package/dist/{chunk-6X55ALDX.js → chunk-Q74GLIDK.js} +3 -3
- package/dist/{chunk-6ANLYNFB.js → chunk-QR4LK3HF.js} +1 -1
- package/dist/{chunk-3WL6EXYX.js → chunk-ZTTN6AJG.js} +1 -1
- package/dist/{circleci-LBGB25LP.js → circleci-3P3QTUX2.js} +20 -24
- package/dist/{clerk-6RX37CNZ.js → clerk-GNMZQ2MY.js} +22 -21
- package/dist/{cloudflare_dns-NQGW7FHV.js → cloudflare_dns-VWZH3ZTR.js} +21 -17
- package/dist/{cohere-XPC2NAEW.js → cohere-C7QVIIMG.js} +19 -17
- package/dist/{confluence-ZUG2LT5A.js → confluence-52I37IUT.js} +16 -11
- package/dist/{convertapi-ZLXKWV3L.js → convertapi-3TRJ3WEO.js} +3 -3
- package/dist/{currencyapi-T3A3HSOD.js → currencyapi-LWEANWVI.js} +23 -26
- package/dist/{deepseek-NJMOD23E.js → deepseek-5U24YG4P.js} +27 -17
- package/dist/{docker_hub-D2MQCSJ7.js → docker_hub-CMISLRSE.js} +4 -4
- package/dist/{dropbox-MI6U3SOT.js → dropbox-TW4QN6FT.js} +9 -8
- package/dist/{dub-P2BYBYV4.js → dub-EFXGI5UH.js} +22 -19
- package/dist/{e2b-BGKIAE54.js → e2b-PHM63OVF.js} +20 -20
- package/dist/{exa-EUY7VV4C.js → exa-DYOQWURR.js} +22 -25
- package/dist/{fal_ai-AAOG5FYJ.js → fal_ai-VJPMCNRH.js} +18 -17
- package/dist/{feishu-AN5IJYOW.js → feishu-TFIZXPIN.js} +2 -2
- package/dist/{feishu_custom_bot-BWGMNVXF.js → feishu_custom_bot-ADSE23KU.js} +3 -3
- package/dist/{figma-SJSWYCGD.js → figma-4AK47AYY.js} +21 -23
- package/dist/{firehydrant-MOXK2PST.js → firehydrant-RGPTW4EA.js} +15 -15
- package/dist/{fixer-ISIDUDZU.js → fixer-OAMF4DKP.js} +4 -4
- package/dist/{fly-U4KPSGBF.js → fly-YB4AQD4O.js} +20 -16
- package/dist/{front-ZHM2YX2E.js → front-6GTVYKUM.js} +20 -23
- package/dist/{geocodio-SVCCAXAV.js → geocodio-YRT4K25M.js} +4 -4
- package/dist/{ghost-INTR7FIY.js → ghost-JCW6UIP2.js} +4 -4
- package/dist/{github-T275VVWR.js → github-CEGVRMNX.js} +28 -27
- package/dist/{gmail-KSSPOE67.js → gmail-JCMJD55P.js} +17 -17
- package/dist/{googlecalendar-JB5RW27L.js → googlecalendar-2EAW4MNM.js} +16 -14
- package/dist/{googledocs-G522PUGW.js → googledocs-YNKKBM6Q.js} +3 -3
- package/dist/{grafana-44ZPIV5P.js → grafana-353VSGXN.js} +24 -25
- package/dist/{graphhopper-2MORXLOW.js → graphhopper-H3IVJNDS.js} +4 -4
- package/dist/index.d.ts +31 -30
- package/dist/index.js +3 -3
- package/dist/{ip2proxy-R4K4KEMI.js → ip2proxy-DBGRWH7S.js} +4 -4
- package/dist/{ipgeolocation_io-H4J55OGI.js → ipgeolocation_io-5I2BDVMG.js} +4 -4
- package/dist/{ipqualityscore-XGID4OHA.js → ipqualityscore-ILV7TQCV.js} +4 -4
- package/dist/{jina_ai-7ZVDOR3Z.js → jina_ai-L5BH455V.js} +20 -21
- package/dist/{jira-CTIYZTT3.js → jira-IGTILUZA.js} +20 -16
- package/dist/{langsmith-ISP567FS.js → langsmith-BYRX4AOF.js} +25 -25
- package/dist/{lemlist-OGKK272F.js → lemlist-ZGNYTIQO.js} +23 -24
- package/dist/{linear-6JX4E3JI.js → linear-HLGOWISN.js} +3 -3
- package/dist/{logsnag-BVB36ZCL.js → logsnag-5RGTMTU2.js} +20 -22
- package/dist/main.js +102 -102
- package/dist/{mattermost-UECVPZGY.js → mattermost-BONGXVC7.js} +21 -17
- package/dist/{mem0-EUA7S2OO.js → mem0-RUVNUPTE.js} +15 -15
- package/dist/{memos-BYSBJRWZ.js → memos-TED4HI54.js} +36 -32
- package/dist/{metabase-4JXZH3YG.js → metabase-ZHXHRHZK.js} +22 -18
- package/dist/{mistral_ai-2C5BTBKW.js → mistral_ai-NGP5SHU7.js} +24 -21
- package/dist/{mother_duck-ZGNNNR3X.js → mother_duck-TKVQM35M.js} +23 -19
- package/dist/{ngrok-NTPGMRXI.js → ngrok-BYCUJ7QV.js} +17 -22
- package/dist/{notes-KGYXX6N6.js → notes-KXRNNYDW.js} +1 -1
- package/dist/{notion-EX3X2G7G.js → notion-2ILYKJOO.js} +20 -17
- package/dist/{open_exchange_rates-2V2LIGPS.js → open_exchange_rates-JU4DC6HZ.js} +4 -4
- package/dist/{openai-KQFQPK7Z.js → openai-6SOT26ZK.js} +22 -16
- package/dist/{opengraph_io-B63SEYJY.js → opengraph_io-ZEFHV2OL.js} +4 -4
- package/dist/{openrouter-62A4H62Q.js → openrouter-R4P7JL5L.js} +19 -14
- package/dist/{outline-QC33XLCK.js → outline-VCTQ5V2X.js} +18 -20
- package/dist/{paddle-HDZG35Y7.js → paddle-AZOAKBJS.js} +17 -15
- package/dist/{perplexity-M7LOMJWA.js → perplexity-OMRLGVAT.js} +19 -23
- package/dist/{pinecone-Y7YCJRZM.js → pinecone-DI62W3ZX.js} +19 -24
- package/dist/{pipedrive-FLJA62UX.js → pipedrive-CW7HONYW.js} +22 -23
- package/dist/{polar-AE55Q425.js → polar-5DRGTXNR.js} +19 -17
- package/dist/{posthog-IAY5D3YW.js → posthog-3DTHFCKE.js} +29 -24
- package/dist/{postmark-NMKAXDXZ.js → postmark-ME2V2XXI.js} +19 -17
- package/dist/{prerender-CDJM7I7N.js → prerender-5EAY7GDM.js} +4 -4
- package/dist/{pubmed-N437ZJOR.js → pubmed-GRQUBGVA.js} +3 -3
- package/dist/{railway-NBWYXCUM.js → railway-FTM75TMX.js} +3 -3
- package/dist/{readwise-QRO3UC3R.js → readwise-IJQGV6IQ.js} +16 -15
- package/dist/{render-NEW7SROG.js → render-TMSPHRTY.js} +21 -20
- package/dist/{replicate-73YQLRTY.js → replicate-RJCMDB7H.js} +20 -21
- package/dist/{resend-R5BAZFHZ.js → resend-YYN22NIL.js} +15 -11
- package/dist/{rootly-5BLALPCS.js → rootly-7RXE4K7L.js} +19 -18
- package/dist/{scrapfly-EHFLNNUZ.js → scrapfly-ZJJ6XM4Q.js} +4 -4
- package/dist/{scrapingbee-UXLYS72S.js → scrapingbee-QNGEOD6D.js} +4 -4
- package/dist/{screenshot_fyi-KC3NLTJ6.js → screenshot_fyi-SPZAABVD.js} +4 -4
- package/dist/{semantic_scholar-SA5OIUWA.js → semantic_scholar-SDPVGDDI.js} +21 -20
- package/dist/{sentry-BAE3XLJN.js → sentry-SJ5NN4C4.js} +22 -19
- package/dist/{shodan-FJR6MKKW.js → shodan-YMGOY7KW.js} +4 -4
- package/dist/{shopify-7OA6SLN5.js → shopify-SU5ABP54.js} +23 -19
- package/dist/{stripe-MUWDZGGF.js → stripe-JAC425KE.js} +3 -3
- package/dist/{supabase-SWWGNWYR.js → supabase-GQ2OFVIL.js} +18 -17
- package/dist/{tavily-XQNJ5EDI.js → tavily-Y7MDA5CI.js} +20 -20
- package/dist/{telegram-RR6JPZOG.js → telegram-OIDT3ZLH.js} +4 -4
- package/dist/{telnyx-7PKXOWSO.js → telnyx-4JGMC3K7.js} +19 -19
- package/dist/{todoist-63X5IUJE.js → todoist-O7S2OUGX.js} +18 -17
- package/dist/{trello-MOG63DAI.js → trello-3LDE4TAQ.js} +4 -4
- package/dist/{turso-ENWTSHDL.js → turso-MUQ44KCA.js} +23 -30
- package/dist/{twilio-FDXUN7VX.js → twilio-VYF3HUU6.js} +3 -3
- package/dist/{umami-TM7PROWF.js → umami-SVQIK26J.js} +23 -28
- package/dist/{unsplash-NWXLKSOW.js → unsplash-333XYDCJ.js} +19 -18
- package/dist/{upstash_redis-CUTQ5L2N.js → upstash_redis-IUM2HDLU.js} +17 -13
- package/dist/{uptimerobot-NNO32M3I.js → uptimerobot-7ZRU2NJV.js} +3 -3
- package/dist/{v2ex-24KMSWS7.js → v2ex-AVSJCQPS.js} +35 -18
- package/dist/{vercel-B45LPF6I.js → vercel-J7RGXNSA.js} +21 -18
- package/dist/{wolfram_alpha_api-DKJWVR6G.js → wolfram_alpha_api-6CRVUZOJ.js} +4 -4
- package/dist/{wordpress-XYFSDZXY.js → wordpress-THOJLPKA.js} +21 -17
- package/dist/{workos-BEM7EPHM.js → workos-ENTGRWF2.js} +26 -27
- package/dist/{zhihu-CFHZ4NC7.js → zhihu-W2BXICTX.js} +21 -21
- package/package.json +5 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createGuardedFetch
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZTTN6AJG.js";
|
|
4
4
|
import {
|
|
5
5
|
AnnotationStore,
|
|
6
6
|
CONTEXT_CAPABILITIES,
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
any,
|
|
56
56
|
appendVia,
|
|
57
57
|
array,
|
|
58
|
+
assertNoCollision,
|
|
58
59
|
assertPluginPayloadSize,
|
|
59
60
|
assertPresignTtlSec,
|
|
60
61
|
assertSecretRefUse,
|
|
@@ -196,7 +197,7 @@ import {
|
|
|
196
197
|
url,
|
|
197
198
|
validatePath,
|
|
198
199
|
virtualizeTools
|
|
199
|
-
} from "./chunk-
|
|
200
|
+
} from "./chunk-MZCIPUNQ.js";
|
|
200
201
|
|
|
201
202
|
// src/config.ts
|
|
202
203
|
var DEFAULT_PORT = 8787;
|
|
@@ -821,300 +822,48 @@ function createS3ObjectStore(cfg, opts) {
|
|
|
821
822
|
};
|
|
822
823
|
}
|
|
823
824
|
|
|
824
|
-
// ../app/src/
|
|
825
|
-
var
|
|
826
|
-
function toolCacheKey(path) {
|
|
827
|
-
return `${KEY_PREFIX}${path}`;
|
|
828
|
-
}
|
|
829
|
-
function isCached(v) {
|
|
830
|
-
return typeof v === "object" && v !== null && Array.isArray(v.tools) && typeof v.fetchedAt === "string";
|
|
831
|
-
}
|
|
832
|
-
function isFresh(fetchedAt, ttl, now) {
|
|
833
|
-
const age = (Date.parse(now) - Date.parse(fetchedAt)) / 1e3;
|
|
834
|
-
return Number.isFinite(age) && age >= 0 && age < ttl;
|
|
835
|
-
}
|
|
836
|
-
async function invalidateToolCache(store, path) {
|
|
837
|
-
await store.delete(toolCacheKey(path));
|
|
838
|
-
}
|
|
839
|
-
function cachedTools(value) {
|
|
840
|
-
return isCached(value) ? value.tools : null;
|
|
841
|
-
}
|
|
842
|
-
async function peekToolCache(store, path) {
|
|
843
|
-
return cachedTools(await store.get(toolCacheKey(path)));
|
|
844
|
-
}
|
|
845
|
-
async function getTools(store, path, fetchList, opts) {
|
|
846
|
-
if (!opts.refresh) {
|
|
847
|
-
const raw = await store.get(toolCacheKey(path));
|
|
848
|
-
if (isCached(raw) && isFresh(raw.fetchedAt, opts.ttl, opts.now)) {
|
|
849
|
-
return raw.tools;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
await opts.beforeFresh?.();
|
|
853
|
-
try {
|
|
854
|
-
const tools = await fetchList();
|
|
855
|
-
const record2 = { tools, fetchedAt: opts.now };
|
|
856
|
-
await store.put(toolCacheKey(path), record2);
|
|
857
|
-
await opts.onFresh?.(tools);
|
|
858
|
-
return tools;
|
|
859
|
-
} catch (error) {
|
|
860
|
-
await opts.onFreshError?.();
|
|
861
|
-
throw error;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
825
|
+
// ../app/src/deps.ts
|
|
826
|
+
var TOOL_CACHE_TTL_DEFAULT = 300;
|
|
864
827
|
|
|
865
|
-
// ../app/src/
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
const index = value;
|
|
871
|
-
return typeof index.replace === "function" && typeof index.remove === "function" && typeof index.removePrefix === "function" && typeof index.rebuild === "function" && typeof index.initialized === "function";
|
|
872
|
-
}
|
|
873
|
-
function toolNode(node) {
|
|
874
|
-
return (node.kind === "mcp" || node.kind === "http" || node.kind === "tool") && node.config?.kind === node.kind;
|
|
875
|
-
}
|
|
876
|
-
function httpTools(node) {
|
|
877
|
-
if (node.config?.kind !== "http") return null;
|
|
878
|
-
return node.config.tools.map((tool) => {
|
|
879
|
-
const spec = {
|
|
880
|
-
description: tool.description,
|
|
881
|
-
effect: effectFor(tool),
|
|
882
|
-
name: tool.name
|
|
883
|
-
};
|
|
884
|
-
if (tool.inputSchema !== void 0) spec.inputSchema = tool.inputSchema;
|
|
885
|
-
return spec;
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
function deviceTools(node) {
|
|
889
|
-
if (node.config?.kind !== "tool") return null;
|
|
890
|
-
const cmds = node.config.providerConfig?.cmds;
|
|
891
|
-
if (!Array.isArray(cmds)) return null;
|
|
892
|
-
return cmds;
|
|
893
|
-
}
|
|
894
|
-
async function canonicalSearchTools(state, nodes) {
|
|
895
|
-
const unique = new Map(nodes.map((node) => [node.path, node]));
|
|
896
|
-
const dynamicPaths = [...unique.values()].filter((node) => node.kind !== "http" && deviceTools(node) === null).map((node) => node.path);
|
|
897
|
-
const cached = dynamicPaths.length === 0 ? /* @__PURE__ */ new Map() : await state.getMany(dynamicPaths.map(toolCacheKey));
|
|
898
|
-
const result = /* @__PURE__ */ new Map();
|
|
899
|
-
for (const node of unique.values()) {
|
|
900
|
-
const tools = node.kind === "http" ? httpTools(node) : deviceTools(node) ?? cachedTools(cached.get(toolCacheKey(node.path)));
|
|
901
|
-
if (tools !== null) result.set(node.path, tools);
|
|
828
|
+
// ../app/src/cursorCleanup.ts
|
|
829
|
+
function positiveInt(value, fallback, field) {
|
|
830
|
+
const actual = value ?? fallback;
|
|
831
|
+
if (!Number.isSafeInteger(actual) || actual < 1) {
|
|
832
|
+
throw new TBError("invalid_argument", `${field} must be a positive integer`);
|
|
902
833
|
}
|
|
903
|
-
return
|
|
904
|
-
}
|
|
905
|
-
function documentsFor(path, tools, feedback) {
|
|
906
|
-
return tools.map((tool) => ({ path, tool, ...feedback === "" ? {} : { feedback } }));
|
|
834
|
+
return actual;
|
|
907
835
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
this.registry = new NodeRegistryStore(state);
|
|
913
|
-
}
|
|
914
|
-
state;
|
|
915
|
-
search;
|
|
916
|
-
registry;
|
|
917
|
-
seedPromise;
|
|
918
|
-
async mark(path, kind) {
|
|
919
|
-
const canonical = normalizeToolSearchPath(path);
|
|
920
|
-
const marker = {
|
|
921
|
-
createdAt: Date.now(),
|
|
922
|
-
// Marker 只是审计提示,正确性由每次 canonical audit 保证。每类固定一个 key,
|
|
923
|
-
// 避免 overflow/device hello 用 UUID marker 把 KV 与扫描成本无界放大。
|
|
924
|
-
key: `${kind === "node" ? DIRTY_NODE_PREFIX : DIRTY_SUBTREE_PREFIX}pending`,
|
|
925
|
-
kind,
|
|
926
|
-
path: canonical
|
|
927
|
-
};
|
|
928
|
-
await this.state.put(marker.key, marker);
|
|
929
|
-
return marker;
|
|
930
|
-
}
|
|
931
|
-
async markNode(path) {
|
|
932
|
-
return await this.mark(path, "node");
|
|
933
|
-
}
|
|
934
|
-
async markSubtree(path) {
|
|
935
|
-
return await this.mark(path, "subtree");
|
|
936
|
-
}
|
|
937
|
-
async abort(marker) {
|
|
938
|
-
if (marker !== void 0 && marker.expectedDigest === void 0) {
|
|
939
|
-
await this.state.delete(marker.key);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
async dirtyMarkers() {
|
|
943
|
-
const markers = [];
|
|
944
|
-
for (const prefix of [DIRTY_NODE_PREFIX, DIRTY_SUBTREE_PREFIX]) {
|
|
945
|
-
let cursor;
|
|
946
|
-
do {
|
|
947
|
-
const page = await this.state.list(prefix, {
|
|
948
|
-
limit: 200,
|
|
949
|
-
...cursor === void 0 ? {} : { cursor }
|
|
950
|
-
});
|
|
951
|
-
for (const item of page.items) {
|
|
952
|
-
const marker = item.value;
|
|
953
|
-
if (typeof marker.createdAt === "number" && marker.key === item.key && (marker.kind === "node" || marker.kind === "subtree") && typeof marker.path === "string") {
|
|
954
|
-
markers.push(marker);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
cursor = page.cursor;
|
|
958
|
-
} while (cursor !== void 0);
|
|
959
|
-
}
|
|
960
|
-
return markers;
|
|
961
|
-
}
|
|
962
|
-
async commitMarker(marker, expectedDigest) {
|
|
963
|
-
if (marker === void 0) return;
|
|
964
|
-
const committed = {
|
|
965
|
-
...marker,
|
|
966
|
-
committedAt: Date.now(),
|
|
967
|
-
expectedDigest
|
|
968
|
-
};
|
|
969
|
-
await this.state.put(marker.key, committed);
|
|
970
|
-
Object.assign(marker, committed);
|
|
971
|
-
}
|
|
972
|
-
async finishMarker(marker) {
|
|
973
|
-
if (marker !== void 0) await this.state.delete(marker.key);
|
|
974
|
-
}
|
|
975
|
-
async feedbackText(path, snapshot) {
|
|
976
|
-
if (snapshot !== void 0) return selectFeedbackSearchText(snapshot);
|
|
977
|
-
return selectFeedbackSearchText(parseFeedbackEntries(await this.state.get(KEY_FEEDBACK + path)));
|
|
978
|
-
}
|
|
979
|
-
async rawTools(node, override) {
|
|
980
|
-
if (override !== void 0) return [...override];
|
|
981
|
-
if (node.kind === "http") return httpTools(node);
|
|
982
|
-
const device = deviceTools(node);
|
|
983
|
-
if (device !== null) return device;
|
|
984
|
-
if (node.kind === "mcp" || node.kind === "tool") {
|
|
985
|
-
return await peekToolCache(this.state, node.path);
|
|
986
|
-
}
|
|
987
|
-
return null;
|
|
988
|
-
}
|
|
989
|
-
indexableRecords(path, tools, feedback) {
|
|
990
|
-
try {
|
|
991
|
-
return serializeToolSearchSnapshot(path, tools, feedback);
|
|
992
|
-
} catch (error) {
|
|
993
|
-
if (isTBError(error)) return null;
|
|
994
|
-
throw error;
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
async reconcileNode(path, opts = {}) {
|
|
998
|
-
const canonical = normalizeToolSearchPath(path);
|
|
999
|
-
const node = await this.registry.get(canonical).catch(() => null);
|
|
1000
|
-
if (node === null || !toolNode(node)) {
|
|
1001
|
-
await this.commitMarker(opts.marker, null);
|
|
1002
|
-
await this.search.remove(canonical);
|
|
1003
|
-
await this.finishMarker(opts.marker);
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
const tools = await this.rawTools(node, opts.tools);
|
|
1007
|
-
if (tools === null) {
|
|
1008
|
-
await this.search.remove(canonical);
|
|
1009
|
-
await this.finishMarker(opts.marker);
|
|
1010
|
-
return;
|
|
1011
|
-
}
|
|
1012
|
-
if (tools.length === 0) {
|
|
1013
|
-
await this.commitMarker(opts.marker, null);
|
|
1014
|
-
await this.search.remove(canonical);
|
|
1015
|
-
await this.finishMarker(opts.marker);
|
|
1016
|
-
return;
|
|
1017
|
-
}
|
|
1018
|
-
const root = await this.registry.rootSnapshot(TOOL_SEARCH_AUDIT_NODE_LIMIT);
|
|
1019
|
-
if (root.truncated) {
|
|
1020
|
-
await this.finishMarker(opts.marker);
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
1023
|
-
const feedback = await this.feedbackText(canonical, opts.feedback);
|
|
1024
|
-
const records = this.indexableRecords(canonical, tools, feedback);
|
|
1025
|
-
if (records === null) {
|
|
1026
|
-
await this.commitMarker(opts.marker, null);
|
|
1027
|
-
await this.search.remove(canonical);
|
|
1028
|
-
await this.finishMarker(opts.marker);
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
const digest = records.length === 0 ? null : toolSearchSnapshotDigest(records);
|
|
1032
|
-
await this.commitMarker(opts.marker, digest);
|
|
1033
|
-
await this.search.replace(canonical, tools, {
|
|
1034
|
-
feedback
|
|
1035
|
-
});
|
|
1036
|
-
await this.finishMarker(opts.marker);
|
|
1037
|
-
}
|
|
1038
|
-
/** mutation 热路径使用:canonical 已成功时,派生同步失败只保留 marker。 */
|
|
1039
|
-
async reconcileNodeQuietly(path, opts = {}) {
|
|
1040
|
-
try {
|
|
1041
|
-
await this.reconcileNode(path, opts);
|
|
1042
|
-
} catch {
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
async rebuildAll(markers = [], opts = {}) {
|
|
1046
|
-
const snapshot = opts.canonicalNodes === void 0 ? await this.registry.rootSnapshot(TOOL_SEARCH_AUDIT_NODE_LIMIT) : { items: [...opts.canonicalNodes], truncated: false };
|
|
1047
|
-
if (snapshot.truncated) return false;
|
|
1048
|
-
const nodes = snapshot.items.filter(toolNode);
|
|
1049
|
-
const authoritativeEmpty = new Set(opts.authoritativeEmpty ?? []);
|
|
1050
|
-
const keys = nodes.flatMap((node) => [KEY_FEEDBACK + node.path, toolCacheKey(node.path)]);
|
|
1051
|
-
const values = await this.state.getMany(keys);
|
|
1052
|
-
const documents = [];
|
|
1053
|
-
for (const node of nodes) {
|
|
1054
|
-
if (authoritativeEmpty.has(node.path)) continue;
|
|
1055
|
-
let tools;
|
|
1056
|
-
if (node.kind === "http") {
|
|
1057
|
-
tools = httpTools(node);
|
|
1058
|
-
} else {
|
|
1059
|
-
tools = deviceTools(node) ?? cachedTools(values.get(toolCacheKey(node.path)));
|
|
1060
|
-
}
|
|
1061
|
-
if (tools === null) continue;
|
|
1062
|
-
const feedback = selectFeedbackSearchText(
|
|
1063
|
-
parseFeedbackEntries(values.get(KEY_FEEDBACK + node.path))
|
|
1064
|
-
);
|
|
1065
|
-
if (this.indexableRecords(node.path, tools, feedback) === null) continue;
|
|
1066
|
-
documents.push(...documentsFor(node.path, tools, feedback));
|
|
1067
|
-
}
|
|
1068
|
-
await this.search.rebuild(documents);
|
|
1069
|
-
await Promise.all(markers.map(async (marker) => {
|
|
1070
|
-
await this.state.delete(marker.key).catch(() => {
|
|
1071
|
-
});
|
|
1072
|
-
}));
|
|
1073
|
-
return true;
|
|
1074
|
-
}
|
|
1075
|
-
/** 每次搜索前审计 canonical 快照;同快照 rebuild 不 bump revision。 */
|
|
1076
|
-
async ensureReady() {
|
|
1077
|
-
const snapshot = await this.registry.rootSnapshot(TOOL_SEARCH_AUDIT_NODE_LIMIT);
|
|
1078
|
-
if (snapshot.truncated) {
|
|
1079
|
-
if (await this.search.initialized()) return;
|
|
1080
|
-
throw new TBError("rate_limited", "registry \u8D85\u51FA\u5DE5\u5177\u641C\u7D22 canonical audit \u5BB9\u91CF");
|
|
1081
|
-
}
|
|
1082
|
-
const dirty = await this.dirtyMarkers();
|
|
1083
|
-
await this.rebuildAll(dirty, { canonicalNodes: snapshot.items });
|
|
1084
|
-
}
|
|
1085
|
-
/** 首次 registry/device mutation 前建立 last-known-good,避免并发溢出抢在首次 seed 前。 */
|
|
1086
|
-
async ensureSeeded() {
|
|
1087
|
-
if (await this.search.initialized()) return;
|
|
1088
|
-
if (this.seedPromise === void 0) {
|
|
1089
|
-
this.seedPromise = (async () => {
|
|
1090
|
-
if (!await this.rebuildAll()) {
|
|
1091
|
-
throw new TBError("rate_limited", "registry \u8D85\u51FA\u5DE5\u5177\u641C\u7D22 canonical audit \u5BB9\u91CF");
|
|
1092
|
-
}
|
|
1093
|
-
})();
|
|
1094
|
-
}
|
|
1095
|
-
try {
|
|
1096
|
-
await this.seedPromise;
|
|
1097
|
-
} finally {
|
|
1098
|
-
this.seedPromise = void 0;
|
|
1099
|
-
}
|
|
836
|
+
async function runCursorCleanup(opts) {
|
|
837
|
+
const { state } = opts;
|
|
838
|
+
if (state.compareAndSwap === void 0) {
|
|
839
|
+
throw new TBError("unavailable", "cursor cleanup requires StateStore.compareAndSwap");
|
|
1100
840
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
await this.rebuildAll(marker === void 0 ? [] : [marker]);
|
|
841
|
+
const raw = await state.get(opts.progressKey);
|
|
842
|
+
let progress = raw === null ? null : opts.parseProgress(raw);
|
|
843
|
+
let cursor = progress?.cursor;
|
|
844
|
+
for (let pageNumber = 0; pageNumber < opts.maxPages; pageNumber++) {
|
|
845
|
+
const nextCursor = await opts.runPage(cursor, pageNumber);
|
|
846
|
+
if (nextCursor === void 0) {
|
|
847
|
+
if (progress !== null) {
|
|
848
|
+
await state.compareAndSwap(opts.progressKey, progress.revision, null);
|
|
1110
849
|
}
|
|
1111
|
-
|
|
850
|
+
return void 0;
|
|
1112
851
|
}
|
|
852
|
+
const next = {
|
|
853
|
+
cursor: nextCursor,
|
|
854
|
+
revision: (progress?.revision ?? 0) + 1
|
|
855
|
+
};
|
|
856
|
+
const advanced = await state.compareAndSwap(
|
|
857
|
+
opts.progressKey,
|
|
858
|
+
progress?.revision ?? null,
|
|
859
|
+
opts.encodeProgress(next)
|
|
860
|
+
);
|
|
861
|
+
if (!advanced) return void 0;
|
|
862
|
+
progress = next;
|
|
863
|
+
cursor = next.cursor;
|
|
1113
864
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
// ../app/src/deps.ts
|
|
1117
|
-
var TOOL_CACHE_TTL_DEFAULT = 300;
|
|
865
|
+
return cursor;
|
|
866
|
+
}
|
|
1118
867
|
|
|
1119
868
|
// ../app/src/storeRefToken.ts
|
|
1120
869
|
async function hmacKey(secret) {
|
|
@@ -1173,13 +922,6 @@ var STORE_READ_TTL_SEC_DEFAULT = 15 * 60;
|
|
|
1173
922
|
var STORE_CALL_MAX_OBJECTS_DEFAULT = 4;
|
|
1174
923
|
var STORE_CALL_MAX_OBJECT_BYTES_DEFAULT = 256 * 1024 * 1024;
|
|
1175
924
|
var STORE_CALL_MAX_BYTES_DEFAULT = 512 * 1024 * 1024;
|
|
1176
|
-
function positiveInt(value, fallback, field) {
|
|
1177
|
-
const actual = value ?? fallback;
|
|
1178
|
-
if (!Number.isSafeInteger(actual) || actual < 1) {
|
|
1179
|
-
throw new TBError("invalid_argument", `${field} must be a positive integer`);
|
|
1180
|
-
}
|
|
1181
|
-
return actual;
|
|
1182
|
-
}
|
|
1183
925
|
function randomSecret() {
|
|
1184
926
|
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
1185
927
|
let out = "";
|
|
@@ -1512,44 +1254,28 @@ async function cleanupDefaultStore(deps, opts = {}) {
|
|
|
1512
1254
|
const store = await defaultStoreRuntime(deps);
|
|
1513
1255
|
const maxPages = Math.min(positiveInt(opts.maxPages, 8, "maxPages"), 64);
|
|
1514
1256
|
const limit = opts.limit === void 0 ? void 0 : positiveInt(opts.limit, 200, "limit");
|
|
1515
|
-
const rawProgress = await deps.state.get(KEY_STORE_CLEANUP_PROGRESS);
|
|
1516
|
-
let progress = rawProgress === null ? null : cleanupProgress(rawProgress);
|
|
1517
|
-
let cursors = progress?.cursors;
|
|
1518
1257
|
const aggregate = emptyCleanupResult();
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
return
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
revision: (progress?.revision ?? 0) + 1
|
|
1541
|
-
};
|
|
1542
|
-
const advanced = await deps.state.compareAndSwap(
|
|
1543
|
-
KEY_STORE_CLEANUP_PROGRESS,
|
|
1544
|
-
progress?.revision ?? null,
|
|
1545
|
-
next
|
|
1546
|
-
);
|
|
1547
|
-
if (!advanced) {
|
|
1548
|
-
return aggregate;
|
|
1549
|
-
}
|
|
1550
|
-
progress = next;
|
|
1551
|
-
cursors = next.cursors;
|
|
1552
|
-
}
|
|
1258
|
+
const cursors = await runCursorCleanup({
|
|
1259
|
+
encodeProgress: (progress) => ({ cursors: progress.cursor, revision: progress.revision }),
|
|
1260
|
+
maxPages,
|
|
1261
|
+
parseProgress: (value) => {
|
|
1262
|
+
const progress = cleanupProgress(value);
|
|
1263
|
+
return { cursor: progress.cursors, revision: progress.revision };
|
|
1264
|
+
},
|
|
1265
|
+
progressKey: KEY_STORE_CLEANUP_PROGRESS,
|
|
1266
|
+
runPage: async (cursor, pageNumber) => {
|
|
1267
|
+
const page = await store.service.cleanup({
|
|
1268
|
+
...limit === void 0 ? {} : { limit },
|
|
1269
|
+
...cursor === void 0 ? {} : { cursors: cursor },
|
|
1270
|
+
// staging 不使用权威状态 cursor;同一 host tick 只运行一次,避免
|
|
1271
|
+
// maxPages 内重复扫描同一 driver maintenance 范围。
|
|
1272
|
+
runDriverMaintenance: pageNumber === 0
|
|
1273
|
+
});
|
|
1274
|
+
addCleanupResult(aggregate, page);
|
|
1275
|
+
return page.cursors;
|
|
1276
|
+
},
|
|
1277
|
+
state: deps.state
|
|
1278
|
+
});
|
|
1553
1279
|
return { ...aggregate, ...cursors === void 0 ? {} : { cursors } };
|
|
1554
1280
|
}
|
|
1555
1281
|
|
|
@@ -19809,8 +19535,6 @@ function createPluginToolProvider(opts) {
|
|
|
19809
19535
|
|
|
19810
19536
|
// ../app/src/providerOAuth.ts
|
|
19811
19537
|
var KEY_TOKENS = "puoauth:token:";
|
|
19812
|
-
var STATE_TTL_SEC2 = 600;
|
|
19813
|
-
var PROVIDER_OAUTH_CALLBACK_PATH = "/~oauth/callback";
|
|
19814
19538
|
async function invalidateProviderOAuth(store, nodePath) {
|
|
19815
19539
|
await store.delete(KEY_TOKENS + nodePath);
|
|
19816
19540
|
}
|
|
@@ -19885,15 +19609,10 @@ async function requestTokens(opts) {
|
|
|
19885
19609
|
throw err;
|
|
19886
19610
|
}
|
|
19887
19611
|
}
|
|
19888
|
-
function base64url(bytes) {
|
|
19889
|
-
let binary = "";
|
|
19890
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
19891
|
-
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
19892
|
-
}
|
|
19893
19612
|
async function createPkce() {
|
|
19894
|
-
const verifier =
|
|
19613
|
+
const verifier = base64urlEncode(crypto.getRandomValues(new Uint8Array(32)));
|
|
19895
19614
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
19896
|
-
return { verifier, challenge:
|
|
19615
|
+
return { verifier, challenge: base64urlEncode(new Uint8Array(digest)) };
|
|
19897
19616
|
}
|
|
19898
19617
|
function requireProviderOAuthFetch(fetcher) {
|
|
19899
19618
|
if (fetcher === void 0) {
|
|
@@ -19929,7 +19648,7 @@ async function startProviderAuthorization(opts) {
|
|
|
19929
19648
|
{
|
|
19930
19649
|
p: opts.nodePath,
|
|
19931
19650
|
v: pkce?.verifier ?? "",
|
|
19932
|
-
exp: Math.floor(opts.now.getTime() / 1e3) +
|
|
19651
|
+
exp: Math.floor(opts.now.getTime() / 1e3) + STATE_TTL_SEC
|
|
19933
19652
|
},
|
|
19934
19653
|
opts.encryptionKey
|
|
19935
19654
|
);
|
|
@@ -19938,7 +19657,7 @@ async function startProviderAuthorization(opts) {
|
|
|
19938
19657
|
authorizationUrl: buildAuthorizationUrl({
|
|
19939
19658
|
config: opts.config,
|
|
19940
19659
|
clientId: client.clientId,
|
|
19941
|
-
redirectUri: opts.origin +
|
|
19660
|
+
redirectUri: opts.origin + OAUTH_CALLBACK_PATH,
|
|
19942
19661
|
state,
|
|
19943
19662
|
...pkce === void 0 ? {} : { codeChallenge: pkce.challenge }
|
|
19944
19663
|
})
|
|
@@ -19951,7 +19670,7 @@ async function finishProviderAuthorization(opts) {
|
|
|
19951
19670
|
config: opts.config,
|
|
19952
19671
|
fetcher,
|
|
19953
19672
|
now: opts.now,
|
|
19954
|
-
grant: { code: opts.code, redirectUri: opts.origin +
|
|
19673
|
+
grant: { code: opts.code, redirectUri: opts.origin + OAUTH_CALLBACK_PATH },
|
|
19955
19674
|
...opts.codeVerifier === "" ? {} : { codeVerifier: opts.codeVerifier },
|
|
19956
19675
|
...client
|
|
19957
19676
|
});
|
|
@@ -19981,21 +19700,61 @@ async function resolveProviderAccessToken(opts) {
|
|
|
19981
19700
|
return refreshed.accessToken;
|
|
19982
19701
|
}
|
|
19983
19702
|
|
|
19703
|
+
// ../app/src/providers/toolCache.ts
|
|
19704
|
+
var KEY_PREFIX = "toolcache:";
|
|
19705
|
+
function toolCacheKey(path) {
|
|
19706
|
+
return `${KEY_PREFIX}${path}`;
|
|
19707
|
+
}
|
|
19708
|
+
function isCached(v) {
|
|
19709
|
+
return typeof v === "object" && v !== null && Array.isArray(v.tools) && typeof v.fetchedAt === "string";
|
|
19710
|
+
}
|
|
19711
|
+
function isFresh(fetchedAt, ttl, now) {
|
|
19712
|
+
const age = (Date.parse(now) - Date.parse(fetchedAt)) / 1e3;
|
|
19713
|
+
return Number.isFinite(age) && age >= 0 && age < ttl;
|
|
19714
|
+
}
|
|
19715
|
+
async function invalidateToolCache(store, path) {
|
|
19716
|
+
await store.delete(toolCacheKey(path));
|
|
19717
|
+
}
|
|
19718
|
+
function cachedTools(value) {
|
|
19719
|
+
return isCached(value) ? value.tools : null;
|
|
19720
|
+
}
|
|
19721
|
+
async function peekToolCache(store, path) {
|
|
19722
|
+
return cachedTools(await store.get(toolCacheKey(path)));
|
|
19723
|
+
}
|
|
19724
|
+
async function getTools(store, path, fetchList, opts) {
|
|
19725
|
+
if (!opts.refresh) {
|
|
19726
|
+
const raw = await store.get(toolCacheKey(path));
|
|
19727
|
+
if (isCached(raw) && isFresh(raw.fetchedAt, opts.ttl, opts.now)) {
|
|
19728
|
+
return raw.tools;
|
|
19729
|
+
}
|
|
19730
|
+
}
|
|
19731
|
+
await opts.beforeFresh?.();
|
|
19732
|
+
try {
|
|
19733
|
+
const tools = await fetchList();
|
|
19734
|
+
const record2 = { tools, fetchedAt: opts.now };
|
|
19735
|
+
await store.put(toolCacheKey(path), record2);
|
|
19736
|
+
await opts.onFresh?.(tools);
|
|
19737
|
+
return tools;
|
|
19738
|
+
} catch (error) {
|
|
19739
|
+
await opts.onFreshError?.();
|
|
19740
|
+
throw error;
|
|
19741
|
+
}
|
|
19742
|
+
}
|
|
19743
|
+
|
|
19984
19744
|
// ../app/src/toolNodes.ts
|
|
19985
|
-
function upstreamTools(node, provider, deps, refresh, now) {
|
|
19745
|
+
function upstreamTools(node, provider, deps, refresh, now, searchSync) {
|
|
19986
19746
|
if (node.kind === "mcp" || node.kind === "tool") {
|
|
19987
|
-
const sync = isMutableSearchIndex(deps.search) ? new SearchSynchronizer(deps.state, deps.search) : void 0;
|
|
19988
19747
|
let marker;
|
|
19989
19748
|
return getTools(deps.state, node.path, () => provider.list(), {
|
|
19990
19749
|
refresh,
|
|
19991
19750
|
ttl: deps.toolCacheTtlSec ?? TOOL_CACHE_TTL_DEFAULT,
|
|
19992
19751
|
now,
|
|
19993
|
-
...
|
|
19752
|
+
...searchSync === void 0 ? {} : {
|
|
19994
19753
|
beforeFresh: async () => {
|
|
19995
|
-
marker = await
|
|
19754
|
+
marker = await searchSync.markNode(node.path);
|
|
19996
19755
|
},
|
|
19997
|
-
onFreshError: async () => await
|
|
19998
|
-
onFresh: async (tools) => await
|
|
19756
|
+
onFreshError: async () => await searchSync.abort(marker),
|
|
19757
|
+
onFresh: async (tools) => await searchSync.reconcileNodeQuietly(node.path, { marker, tools })
|
|
19999
19758
|
}
|
|
20000
19759
|
});
|
|
20001
19760
|
}
|
|
@@ -20130,11 +19889,11 @@ async function providerFor(node, ctx, deps) {
|
|
|
20130
19889
|
}
|
|
20131
19890
|
throw TBError.unimplemented(`kind '${node.kind}' has no tool provider`);
|
|
20132
19891
|
}
|
|
20133
|
-
async function refreshDynamicSearchNode(node, ctx, deps) {
|
|
19892
|
+
async function refreshDynamicSearchNode(node, ctx, deps, searchSync) {
|
|
20134
19893
|
if (node.kind !== "mcp" && node.kind !== "tool" || deviceToolMarker(node) !== null) return false;
|
|
20135
19894
|
try {
|
|
20136
19895
|
const provider = await providerFor(node, ctx, deps);
|
|
20137
|
-
await upstreamTools(node, provider, deps, true, (/* @__PURE__ */ new Date()).toISOString());
|
|
19896
|
+
await upstreamTools(node, provider, deps, true, (/* @__PURE__ */ new Date()).toISOString(), searchSync);
|
|
20138
19897
|
return true;
|
|
20139
19898
|
} catch {
|
|
20140
19899
|
return false;
|
|
@@ -20149,7 +19908,7 @@ async function refreshDynamicToolCache(node, ctx, deps) {
|
|
|
20149
19908
|
now: (/* @__PURE__ */ new Date()).toISOString()
|
|
20150
19909
|
});
|
|
20151
19910
|
}
|
|
20152
|
-
async function toolHelpModelFor(c, ctx, registry, path, deps) {
|
|
19911
|
+
async function toolHelpModelFor(c, ctx, registry, path, deps, searchSync) {
|
|
20153
19912
|
let resolved;
|
|
20154
19913
|
try {
|
|
20155
19914
|
resolved = await registry.resolve(path);
|
|
@@ -20170,7 +19929,7 @@ async function toolHelpModelFor(c, ctx, registry, path, deps) {
|
|
|
20170
19929
|
}
|
|
20171
19930
|
const provider = await providerFor(node, ctx, deps);
|
|
20172
19931
|
const refresh = c.req.query("refresh") === "1";
|
|
20173
|
-
const raw = await upstreamTools(node, provider, deps, refresh, (/* @__PURE__ */ new Date()).toISOString());
|
|
19932
|
+
const raw = await upstreamTools(node, provider, deps, refresh, (/* @__PURE__ */ new Date()).toISOString(), searchSync);
|
|
20174
19933
|
const { exposed } = virtualizeTools(node.virtualize, raw);
|
|
20175
19934
|
const tool = exposed.find((t) => t.name === rest);
|
|
20176
19935
|
if (tool === void 0) return null;
|
|
@@ -20323,9 +20082,6 @@ async function verifyRefToken(token, secret) {
|
|
|
20323
20082
|
function localContext(deps, node) {
|
|
20324
20083
|
return deps.locals?.context?.(node.path) ?? null;
|
|
20325
20084
|
}
|
|
20326
|
-
function localCapabilities(provider) {
|
|
20327
|
-
return contextCapabilitiesOf(provider);
|
|
20328
|
-
}
|
|
20329
20085
|
function parseS3Credentials(raw, refName) {
|
|
20330
20086
|
try {
|
|
20331
20087
|
const v = JSON.parse(raw);
|
|
@@ -20402,13 +20158,8 @@ async function createContextUploadGrant(node, cfg, deps, input) {
|
|
|
20402
20158
|
uploadGrantTtlSec
|
|
20403
20159
|
}, input);
|
|
20404
20160
|
}
|
|
20405
|
-
|
|
20406
|
-
const
|
|
20407
|
-
const opts = {
|
|
20408
|
-
nsPath: node.path,
|
|
20409
|
-
keyPrefix: contextKeyPrefix(cfg, node.path),
|
|
20410
|
-
readOnly: cfg.readOnly ?? false
|
|
20411
|
-
};
|
|
20161
|
+
function objectProviderOpts(node, keyPrefix, readOnly, deps, requestUrl) {
|
|
20162
|
+
const opts = { nsPath: node.path, keyPrefix, readOnly };
|
|
20412
20163
|
if (deps.refThresholdBytes !== void 0) opts.refThresholdBytes = deps.refThresholdBytes;
|
|
20413
20164
|
if (deps.refTtlSec !== void 0) opts.presignTtlSec = deps.refTtlSec;
|
|
20414
20165
|
const encKey = deps.encryptionKey;
|
|
@@ -20420,7 +20171,17 @@ async function contextProviderFor(node, cfg, deps, requestUrl) {
|
|
|
20420
20171
|
return `${origin}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
|
|
20421
20172
|
};
|
|
20422
20173
|
}
|
|
20423
|
-
return
|
|
20174
|
+
return opts;
|
|
20175
|
+
}
|
|
20176
|
+
async function contextProviderFor(node, cfg, deps, requestUrl) {
|
|
20177
|
+
const objects = await contextObjectStoreFor(cfg, deps);
|
|
20178
|
+
return createObjectContextProvider(objects, objectProviderOpts(
|
|
20179
|
+
node,
|
|
20180
|
+
contextKeyPrefix(cfg, node.path),
|
|
20181
|
+
cfg.readOnly ?? false,
|
|
20182
|
+
deps,
|
|
20183
|
+
requestUrl
|
|
20184
|
+
));
|
|
20424
20185
|
}
|
|
20425
20186
|
function skillhubKeyPrefix(cfg, nodePath) {
|
|
20426
20187
|
const prefix = cfg.providerConfig?.prefix;
|
|
@@ -20429,29 +20190,13 @@ function skillhubKeyPrefix(cfg, nodePath) {
|
|
|
20429
20190
|
}
|
|
20430
20191
|
async function skillhubProviderFor(node, cfg, deps, requestUrl) {
|
|
20431
20192
|
const objects = await contextObjectStoreFor(cfg, deps);
|
|
20432
|
-
|
|
20433
|
-
|
|
20434
|
-
|
|
20435
|
-
|
|
20436
|
-
|
|
20437
|
-
|
|
20438
|
-
|
|
20439
|
-
const encKey = deps.encryptionKey;
|
|
20440
|
-
if (encKey !== void 0) {
|
|
20441
|
-
const origin = new URL(requestUrl).origin;
|
|
20442
|
-
const relayTtlSec = deps.refTtlSec ?? PRESIGN_TTL_SEC_DEFAULT;
|
|
20443
|
-
opts.relayRefUrl = async (key) => {
|
|
20444
|
-
const exp = Math.floor(Date.now() / 1e3) + relayTtlSec;
|
|
20445
|
-
return `${origin}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
|
|
20446
|
-
};
|
|
20447
|
-
}
|
|
20448
|
-
return createSkillhubProvider(objects, opts);
|
|
20449
|
-
}
|
|
20450
|
-
async function dispatchSkillhubCmd2(provider, command, args) {
|
|
20451
|
-
return await dispatchSkillhubCmd(provider, command, args);
|
|
20452
|
-
}
|
|
20453
|
-
async function dispatchContextCmd2(provider, command, args) {
|
|
20454
|
-
return await dispatchContextCmd(provider, command, args);
|
|
20193
|
+
return createSkillhubProvider(objects, objectProviderOpts(
|
|
20194
|
+
node,
|
|
20195
|
+
skillhubKeyPrefix(cfg, node.path),
|
|
20196
|
+
cfg.readOnly ?? false,
|
|
20197
|
+
deps,
|
|
20198
|
+
requestUrl
|
|
20199
|
+
));
|
|
20455
20200
|
}
|
|
20456
20201
|
async function assertContextAlive(node, cfg, registry) {
|
|
20457
20202
|
if (!isContextExpired(node.createdAt, cfg.ttl, Date.now())) return;
|
|
@@ -20522,6 +20267,257 @@ async function assertSkillhubConfig(config, deps) {
|
|
|
20522
20267
|
}
|
|
20523
20268
|
}
|
|
20524
20269
|
|
|
20270
|
+
// ../app/src/search/synchronizer.ts
|
|
20271
|
+
var DIRTY_NODE_PREFIX = "searchdirty:node:";
|
|
20272
|
+
var DIRTY_SUBTREE_PREFIX = "searchdirty:subtree:";
|
|
20273
|
+
function isMutableSearchIndex(value) {
|
|
20274
|
+
if (typeof value !== "object" || value === null) return false;
|
|
20275
|
+
const index = value;
|
|
20276
|
+
return typeof index.replace === "function" && typeof index.remove === "function" && typeof index.removePrefix === "function" && typeof index.rebuild === "function" && typeof index.initialized === "function";
|
|
20277
|
+
}
|
|
20278
|
+
function toolNode(node) {
|
|
20279
|
+
return (node.kind === "mcp" || node.kind === "http" || node.kind === "tool") && node.config?.kind === node.kind;
|
|
20280
|
+
}
|
|
20281
|
+
function httpTools(node) {
|
|
20282
|
+
if (node.config?.kind !== "http") return null;
|
|
20283
|
+
return node.config.tools.map((tool) => {
|
|
20284
|
+
const spec = {
|
|
20285
|
+
description: tool.description,
|
|
20286
|
+
effect: effectFor(tool),
|
|
20287
|
+
name: tool.name
|
|
20288
|
+
};
|
|
20289
|
+
if (tool.inputSchema !== void 0) spec.inputSchema = tool.inputSchema;
|
|
20290
|
+
return spec;
|
|
20291
|
+
});
|
|
20292
|
+
}
|
|
20293
|
+
function deviceTools(node) {
|
|
20294
|
+
if (node.config?.kind !== "tool") return null;
|
|
20295
|
+
const cmds = node.config.providerConfig?.cmds;
|
|
20296
|
+
if (!Array.isArray(cmds)) return null;
|
|
20297
|
+
return cmds;
|
|
20298
|
+
}
|
|
20299
|
+
async function canonicalSearchTools(state, nodes) {
|
|
20300
|
+
const unique = new Map(nodes.map((node) => [node.path, node]));
|
|
20301
|
+
const dynamicPaths = [...unique.values()].filter((node) => node.kind !== "http" && deviceTools(node) === null).map((node) => node.path);
|
|
20302
|
+
const cached = dynamicPaths.length === 0 ? /* @__PURE__ */ new Map() : await state.getMany(dynamicPaths.map(toolCacheKey));
|
|
20303
|
+
const result = /* @__PURE__ */ new Map();
|
|
20304
|
+
for (const node of unique.values()) {
|
|
20305
|
+
const tools = node.kind === "http" ? httpTools(node) : deviceTools(node) ?? cachedTools(cached.get(toolCacheKey(node.path)));
|
|
20306
|
+
if (tools !== null) result.set(node.path, tools);
|
|
20307
|
+
}
|
|
20308
|
+
return result;
|
|
20309
|
+
}
|
|
20310
|
+
function documentsFor(path, tools, feedback) {
|
|
20311
|
+
return tools.map((tool) => ({ path, tool, ...feedback === "" ? {} : { feedback } }));
|
|
20312
|
+
}
|
|
20313
|
+
var SearchSynchronizer = class {
|
|
20314
|
+
constructor(state, search) {
|
|
20315
|
+
this.state = state;
|
|
20316
|
+
this.search = search;
|
|
20317
|
+
this.registry = new NodeRegistryStore(state);
|
|
20318
|
+
}
|
|
20319
|
+
state;
|
|
20320
|
+
search;
|
|
20321
|
+
registry;
|
|
20322
|
+
seedPromise;
|
|
20323
|
+
async mark(path, kind) {
|
|
20324
|
+
const canonical = normalizeToolSearchPath(path);
|
|
20325
|
+
const marker = {
|
|
20326
|
+
createdAt: Date.now(),
|
|
20327
|
+
// Marker 只是审计提示,正确性由每次 canonical audit 保证。每类固定一个 key,
|
|
20328
|
+
// 避免 overflow/device hello 用 UUID marker 把 KV 与扫描成本无界放大。
|
|
20329
|
+
key: `${kind === "node" ? DIRTY_NODE_PREFIX : DIRTY_SUBTREE_PREFIX}pending`,
|
|
20330
|
+
kind,
|
|
20331
|
+
path: canonical
|
|
20332
|
+
};
|
|
20333
|
+
await this.state.put(marker.key, marker);
|
|
20334
|
+
return marker;
|
|
20335
|
+
}
|
|
20336
|
+
async markNode(path) {
|
|
20337
|
+
return await this.mark(path, "node");
|
|
20338
|
+
}
|
|
20339
|
+
async markSubtree(path) {
|
|
20340
|
+
return await this.mark(path, "subtree");
|
|
20341
|
+
}
|
|
20342
|
+
async abort(marker) {
|
|
20343
|
+
if (marker !== void 0 && marker.expectedDigest === void 0) {
|
|
20344
|
+
await this.state.delete(marker.key);
|
|
20345
|
+
}
|
|
20346
|
+
}
|
|
20347
|
+
async dirtyMarkers() {
|
|
20348
|
+
const markers = [];
|
|
20349
|
+
for (const prefix of [DIRTY_NODE_PREFIX, DIRTY_SUBTREE_PREFIX]) {
|
|
20350
|
+
let cursor;
|
|
20351
|
+
do {
|
|
20352
|
+
const page = await this.state.list(prefix, {
|
|
20353
|
+
limit: 200,
|
|
20354
|
+
...cursor === void 0 ? {} : { cursor }
|
|
20355
|
+
});
|
|
20356
|
+
for (const item of page.items) {
|
|
20357
|
+
const marker = item.value;
|
|
20358
|
+
if (typeof marker.createdAt === "number" && marker.key === item.key && (marker.kind === "node" || marker.kind === "subtree") && typeof marker.path === "string") {
|
|
20359
|
+
markers.push(marker);
|
|
20360
|
+
}
|
|
20361
|
+
}
|
|
20362
|
+
cursor = page.cursor;
|
|
20363
|
+
} while (cursor !== void 0);
|
|
20364
|
+
}
|
|
20365
|
+
return markers;
|
|
20366
|
+
}
|
|
20367
|
+
async commitMarker(marker, expectedDigest) {
|
|
20368
|
+
if (marker === void 0) return;
|
|
20369
|
+
const committed = {
|
|
20370
|
+
...marker,
|
|
20371
|
+
committedAt: Date.now(),
|
|
20372
|
+
expectedDigest
|
|
20373
|
+
};
|
|
20374
|
+
await this.state.put(marker.key, committed);
|
|
20375
|
+
Object.assign(marker, committed);
|
|
20376
|
+
}
|
|
20377
|
+
async finishMarker(marker) {
|
|
20378
|
+
if (marker !== void 0) await this.state.delete(marker.key);
|
|
20379
|
+
}
|
|
20380
|
+
async feedbackText(path, snapshot) {
|
|
20381
|
+
if (snapshot !== void 0) return selectFeedbackSearchText(snapshot);
|
|
20382
|
+
return selectFeedbackSearchText(parseFeedbackEntries(await this.state.get(KEY_FEEDBACK + path)));
|
|
20383
|
+
}
|
|
20384
|
+
async rawTools(node, override) {
|
|
20385
|
+
if (override !== void 0) return [...override];
|
|
20386
|
+
if (node.kind === "http") return httpTools(node);
|
|
20387
|
+
const device = deviceTools(node);
|
|
20388
|
+
if (device !== null) return device;
|
|
20389
|
+
if (node.kind === "mcp" || node.kind === "tool") {
|
|
20390
|
+
return await peekToolCache(this.state, node.path);
|
|
20391
|
+
}
|
|
20392
|
+
return null;
|
|
20393
|
+
}
|
|
20394
|
+
indexableRecords(path, tools, feedback) {
|
|
20395
|
+
try {
|
|
20396
|
+
return serializeToolSearchSnapshot(path, tools, feedback);
|
|
20397
|
+
} catch (error) {
|
|
20398
|
+
if (isTBError(error)) return null;
|
|
20399
|
+
throw error;
|
|
20400
|
+
}
|
|
20401
|
+
}
|
|
20402
|
+
async reconcileNode(path, opts = {}) {
|
|
20403
|
+
const canonical = normalizeToolSearchPath(path);
|
|
20404
|
+
const node = await this.registry.get(canonical).catch(() => null);
|
|
20405
|
+
if (node === null || !toolNode(node)) {
|
|
20406
|
+
await this.commitMarker(opts.marker, null);
|
|
20407
|
+
await this.search.remove(canonical);
|
|
20408
|
+
await this.finishMarker(opts.marker);
|
|
20409
|
+
return;
|
|
20410
|
+
}
|
|
20411
|
+
const tools = await this.rawTools(node, opts.tools);
|
|
20412
|
+
if (tools === null) {
|
|
20413
|
+
await this.search.remove(canonical);
|
|
20414
|
+
await this.finishMarker(opts.marker);
|
|
20415
|
+
return;
|
|
20416
|
+
}
|
|
20417
|
+
if (tools.length === 0) {
|
|
20418
|
+
await this.commitMarker(opts.marker, null);
|
|
20419
|
+
await this.search.remove(canonical);
|
|
20420
|
+
await this.finishMarker(opts.marker);
|
|
20421
|
+
return;
|
|
20422
|
+
}
|
|
20423
|
+
const root = await this.registry.rootSnapshot(TOOL_SEARCH_AUDIT_NODE_LIMIT);
|
|
20424
|
+
if (root.truncated) {
|
|
20425
|
+
await this.finishMarker(opts.marker);
|
|
20426
|
+
return;
|
|
20427
|
+
}
|
|
20428
|
+
const feedback = await this.feedbackText(canonical, opts.feedback);
|
|
20429
|
+
const records = this.indexableRecords(canonical, tools, feedback);
|
|
20430
|
+
if (records === null) {
|
|
20431
|
+
await this.commitMarker(opts.marker, null);
|
|
20432
|
+
await this.search.remove(canonical);
|
|
20433
|
+
await this.finishMarker(opts.marker);
|
|
20434
|
+
return;
|
|
20435
|
+
}
|
|
20436
|
+
const digest = records.length === 0 ? null : toolSearchSnapshotDigest(records);
|
|
20437
|
+
await this.commitMarker(opts.marker, digest);
|
|
20438
|
+
await this.search.replace(canonical, tools, {
|
|
20439
|
+
feedback
|
|
20440
|
+
});
|
|
20441
|
+
await this.finishMarker(opts.marker);
|
|
20442
|
+
}
|
|
20443
|
+
/** mutation 热路径使用:canonical 已成功时,派生同步失败只保留 marker。 */
|
|
20444
|
+
async reconcileNodeQuietly(path, opts = {}) {
|
|
20445
|
+
try {
|
|
20446
|
+
await this.reconcileNode(path, opts);
|
|
20447
|
+
} catch {
|
|
20448
|
+
}
|
|
20449
|
+
}
|
|
20450
|
+
async rebuildAll(markers = [], opts = {}) {
|
|
20451
|
+
const snapshot = opts.canonicalNodes === void 0 ? await this.registry.rootSnapshot(TOOL_SEARCH_AUDIT_NODE_LIMIT) : { items: [...opts.canonicalNodes], truncated: false };
|
|
20452
|
+
if (snapshot.truncated) return false;
|
|
20453
|
+
const nodes = snapshot.items.filter(toolNode);
|
|
20454
|
+
const authoritativeEmpty = new Set(opts.authoritativeEmpty ?? []);
|
|
20455
|
+
const keys = nodes.flatMap((node) => [KEY_FEEDBACK + node.path, toolCacheKey(node.path)]);
|
|
20456
|
+
const values = await this.state.getMany(keys);
|
|
20457
|
+
const documents = [];
|
|
20458
|
+
for (const node of nodes) {
|
|
20459
|
+
if (authoritativeEmpty.has(node.path)) continue;
|
|
20460
|
+
let tools;
|
|
20461
|
+
if (node.kind === "http") {
|
|
20462
|
+
tools = httpTools(node);
|
|
20463
|
+
} else {
|
|
20464
|
+
tools = deviceTools(node) ?? cachedTools(values.get(toolCacheKey(node.path)));
|
|
20465
|
+
}
|
|
20466
|
+
if (tools === null) continue;
|
|
20467
|
+
const feedback = selectFeedbackSearchText(
|
|
20468
|
+
parseFeedbackEntries(values.get(KEY_FEEDBACK + node.path))
|
|
20469
|
+
);
|
|
20470
|
+
if (this.indexableRecords(node.path, tools, feedback) === null) continue;
|
|
20471
|
+
documents.push(...documentsFor(node.path, tools, feedback));
|
|
20472
|
+
}
|
|
20473
|
+
await this.search.rebuild(documents);
|
|
20474
|
+
await Promise.all(markers.map(async (marker) => {
|
|
20475
|
+
await this.state.delete(marker.key).catch(() => {
|
|
20476
|
+
});
|
|
20477
|
+
}));
|
|
20478
|
+
return true;
|
|
20479
|
+
}
|
|
20480
|
+
/** 每次搜索前审计 canonical 快照;同快照 rebuild 不 bump revision。 */
|
|
20481
|
+
async ensureReady() {
|
|
20482
|
+
const snapshot = await this.registry.rootSnapshot(TOOL_SEARCH_AUDIT_NODE_LIMIT);
|
|
20483
|
+
if (snapshot.truncated) {
|
|
20484
|
+
if (await this.search.initialized()) return;
|
|
20485
|
+
throw new TBError("rate_limited", "registry \u8D85\u51FA\u5DE5\u5177\u641C\u7D22 canonical audit \u5BB9\u91CF");
|
|
20486
|
+
}
|
|
20487
|
+
const dirty = await this.dirtyMarkers();
|
|
20488
|
+
await this.rebuildAll(dirty, { canonicalNodes: snapshot.items });
|
|
20489
|
+
}
|
|
20490
|
+
/** 首次 registry/device mutation 前建立 last-known-good,避免并发溢出抢在首次 seed 前。 */
|
|
20491
|
+
async ensureSeeded() {
|
|
20492
|
+
if (await this.search.initialized()) return;
|
|
20493
|
+
if (this.seedPromise === void 0) {
|
|
20494
|
+
this.seedPromise = (async () => {
|
|
20495
|
+
if (!await this.rebuildAll()) {
|
|
20496
|
+
throw new TBError("rate_limited", "registry \u8D85\u51FA\u5DE5\u5177\u641C\u7D22 canonical audit \u5BB9\u91CF");
|
|
20497
|
+
}
|
|
20498
|
+
})();
|
|
20499
|
+
}
|
|
20500
|
+
try {
|
|
20501
|
+
await this.seedPromise;
|
|
20502
|
+
} finally {
|
|
20503
|
+
this.seedPromise = void 0;
|
|
20504
|
+
}
|
|
20505
|
+
}
|
|
20506
|
+
async removeSubtreeQuietly(path, marker) {
|
|
20507
|
+
try {
|
|
20508
|
+
const canonical = normalizeToolSearchPath(path);
|
|
20509
|
+
if ((await this.registry.subtree(canonical)).length === 0) {
|
|
20510
|
+
await this.commitMarker(marker, null);
|
|
20511
|
+
await this.search.removePrefix(canonical);
|
|
20512
|
+
await this.finishMarker(marker);
|
|
20513
|
+
} else {
|
|
20514
|
+
await this.rebuildAll(marker === void 0 ? [] : [marker]);
|
|
20515
|
+
}
|
|
20516
|
+
} catch {
|
|
20517
|
+
}
|
|
20518
|
+
}
|
|
20519
|
+
};
|
|
20520
|
+
|
|
20525
20521
|
// ../app/src/deviceHello.ts
|
|
20526
20522
|
function assertDeviceId(deviceId) {
|
|
20527
20523
|
if (!/^[A-Za-z0-9._-]+$/.test(deviceId)) {
|
|
@@ -20561,6 +20557,7 @@ function customNodeInput(mountPath, deviceId, raw) {
|
|
|
20561
20557
|
return input;
|
|
20562
20558
|
}
|
|
20563
20559
|
function nodesForHello(mountPath, deviceId, expose) {
|
|
20560
|
+
const rawTargets = [mountPath];
|
|
20564
20561
|
const nodes = [
|
|
20565
20562
|
{
|
|
20566
20563
|
path: mountPath,
|
|
@@ -20569,6 +20566,7 @@ function nodesForHello(mountPath, deviceId, expose) {
|
|
|
20569
20566
|
}
|
|
20570
20567
|
];
|
|
20571
20568
|
if (expose.shell !== void 0) {
|
|
20569
|
+
rawTargets.push(joinTreePath(mountPath, "shell"));
|
|
20572
20570
|
nodes.push({
|
|
20573
20571
|
path: joinTreePath(mountPath, "shell"),
|
|
20574
20572
|
kind: "device",
|
|
@@ -20577,6 +20575,7 @@ function nodesForHello(mountPath, deviceId, expose) {
|
|
|
20577
20575
|
});
|
|
20578
20576
|
}
|
|
20579
20577
|
if (expose.fs !== void 0) {
|
|
20578
|
+
rawTargets.push(joinTreePath(mountPath, "fs"));
|
|
20580
20579
|
nodes.push({
|
|
20581
20580
|
path: joinTreePath(mountPath, "fs"),
|
|
20582
20581
|
kind: "context",
|
|
@@ -20590,11 +20589,13 @@ function nodesForHello(mountPath, deviceId, expose) {
|
|
|
20590
20589
|
});
|
|
20591
20590
|
}
|
|
20592
20591
|
for (const raw of expose.nodes ?? []) {
|
|
20592
|
+
rawTargets.push(joinTreePath(mountPath, raw.path));
|
|
20593
20593
|
nodes.push(customNodeInput(mountPath, deviceId, raw));
|
|
20594
20594
|
}
|
|
20595
|
+
assertNoCollision(rawTargets, `\u8BBE\u5907 '${deviceId}' \u7684 expose \u8DEF\u5F84`);
|
|
20595
20596
|
return nodes;
|
|
20596
20597
|
}
|
|
20597
|
-
async function assertRegisterPath(registry, ctx, targetPath) {
|
|
20598
|
+
async function assertRegisterPath(registry, ctx, targetPath, reservedRoots) {
|
|
20598
20599
|
if (!check(ctx, targetPath, "register").allow) {
|
|
20599
20600
|
throw new TBError("permission_denied", `no scope grants 'register' on '${targetPath}'`);
|
|
20600
20601
|
}
|
|
@@ -20612,7 +20613,8 @@ async function assertRegisterPath(registry, ctx, targetPath) {
|
|
|
20612
20613
|
},
|
|
20613
20614
|
targetPath,
|
|
20614
20615
|
action: "write",
|
|
20615
|
-
existing
|
|
20616
|
+
existing,
|
|
20617
|
+
...reservedRoots !== void 0 ? { reservedRoots } : {}
|
|
20616
20618
|
});
|
|
20617
20619
|
if (!res.allow) throw res.error;
|
|
20618
20620
|
}
|
|
@@ -20633,7 +20635,7 @@ async function processDeviceHello(opts) {
|
|
|
20633
20635
|
const inputs = nodesForHello(mountPath, hello.deviceId, hello.expose);
|
|
20634
20636
|
const registry = new NodeRegistryStore(store);
|
|
20635
20637
|
for (const input of inputs) {
|
|
20636
|
-
await assertRegisterPath(registry, authCtx, input.path);
|
|
20638
|
+
await assertRegisterPath(registry, authCtx, input.path, opts.reservedRoots);
|
|
20637
20639
|
assertSecretRefUse(authCtx.scopes, input.config);
|
|
20638
20640
|
}
|
|
20639
20641
|
const now = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -20662,15 +20664,43 @@ async function processDeviceHello(opts) {
|
|
|
20662
20664
|
return { mountPath, keyId: authCtx.keyId, searchIndexed };
|
|
20663
20665
|
}
|
|
20664
20666
|
|
|
20665
|
-
// ../app/src/
|
|
20666
|
-
|
|
20667
|
-
|
|
20668
|
-
|
|
20669
|
-
|
|
20670
|
-
|
|
20667
|
+
// ../app/src/deviceLifecycle.ts
|
|
20668
|
+
async function reverifyDeviceAuthority(opts) {
|
|
20669
|
+
const authCtx = await identify(opts.store, opts.authorization, (/* @__PURE__ */ new Date()).toISOString());
|
|
20670
|
+
if (authCtx === null || authCtx.keyId !== opts.keyId) return false;
|
|
20671
|
+
return checkRegisterPath({
|
|
20672
|
+
sk: {
|
|
20673
|
+
scopes: authCtx.scopes,
|
|
20674
|
+
id: authCtx.keyId,
|
|
20675
|
+
...authCtx.registerPaths !== void 0 ? { registerPaths: authCtx.registerPaths } : {}
|
|
20676
|
+
},
|
|
20677
|
+
targetPath: opts.mountPath,
|
|
20678
|
+
action: "write",
|
|
20679
|
+
existing: null,
|
|
20680
|
+
...opts.reservedRoots !== void 0 ? { reservedRoots: opts.reservedRoots } : {}
|
|
20681
|
+
}).allow;
|
|
20682
|
+
}
|
|
20683
|
+
async function markDeviceDisconnected(opts) {
|
|
20684
|
+
try {
|
|
20685
|
+
await opts.registry.setOnline(opts.mountPath, false, opts.now);
|
|
20686
|
+
} catch {
|
|
20671
20687
|
}
|
|
20672
|
-
return actual;
|
|
20673
20688
|
}
|
|
20689
|
+
async function reclaimDeviceSubtree(opts) {
|
|
20690
|
+
const searchSync = opts.search === void 0 ? void 0 : new SearchSynchronizer(opts.state, opts.search);
|
|
20691
|
+
const marker = await searchSync?.markSubtree(opts.mountPath);
|
|
20692
|
+
try {
|
|
20693
|
+
await opts.registry.deleteSubtree(opts.mountPath);
|
|
20694
|
+
} catch {
|
|
20695
|
+
}
|
|
20696
|
+
await searchSync?.removeSubtreeQuietly(opts.mountPath, marker);
|
|
20697
|
+
}
|
|
20698
|
+
function deviceSearchCapacityWarning(deviceId, mountPath) {
|
|
20699
|
+
return `[tool-bridge] device '${deviceId}' mounted at ${mountPath} but its tools are NOT in the search index: registry exceeds the tool-search capacity (TOOL_SEARCH_AUDIT_NODE_LIMIT). The device is callable but won't appear in tool search until capacity frees up.`;
|
|
20700
|
+
}
|
|
20701
|
+
|
|
20702
|
+
// ../app/src/deviceMailbox.ts
|
|
20703
|
+
var KEY_DEVICE_MAILBOX_CLEANUP_PROGRESS = "sys:device-mailbox-cleanup-progress:v1";
|
|
20674
20704
|
function parseProgress(value) {
|
|
20675
20705
|
if (typeof value !== "object" || value === null || Array.isArray(value) || typeof value.cursor !== "string" || !Number.isSafeInteger(value.revision) || value.revision < 1) throw new TBError("internal", "device mailbox cleanup progress is invalid");
|
|
20676
20706
|
return value;
|
|
@@ -20684,39 +20714,27 @@ async function cleanupDeviceMailbox(deps, opts = {}) {
|
|
|
20684
20714
|
throw new TBError("unavailable", "device mailbox cleanup requires StateStore.compareAndSwap");
|
|
20685
20715
|
}
|
|
20686
20716
|
const mailbox = createDeviceMailboxService(deps);
|
|
20687
|
-
const maxPages = Math.min(
|
|
20688
|
-
const limit = Math.min(
|
|
20689
|
-
const raw = await deps.state.get(KEY_DEVICE_MAILBOX_CLEANUP_PROGRESS);
|
|
20690
|
-
let progress = raw === null ? null : parseProgress(raw);
|
|
20691
|
-
let cursor = progress?.cursor;
|
|
20717
|
+
const maxPages = Math.min(positiveInt(opts.maxPages, 8, "maxPages"), 64);
|
|
20718
|
+
const limit = Math.min(positiveInt(opts.limit, 200, "limit"), 200);
|
|
20692
20719
|
const aggregate = { deleted: 0, expired: 0, scanned: 0 };
|
|
20693
|
-
|
|
20694
|
-
|
|
20695
|
-
|
|
20696
|
-
|
|
20697
|
-
|
|
20698
|
-
|
|
20699
|
-
|
|
20700
|
-
|
|
20701
|
-
|
|
20702
|
-
|
|
20703
|
-
|
|
20704
|
-
|
|
20705
|
-
|
|
20706
|
-
|
|
20707
|
-
|
|
20708
|
-
|
|
20709
|
-
|
|
20710
|
-
|
|
20711
|
-
};
|
|
20712
|
-
if (!await deps.state.compareAndSwap(
|
|
20713
|
-
KEY_DEVICE_MAILBOX_CLEANUP_PROGRESS,
|
|
20714
|
-
progress?.revision ?? null,
|
|
20715
|
-
next
|
|
20716
|
-
)) return aggregate;
|
|
20717
|
-
progress = next;
|
|
20718
|
-
cursor = next.cursor;
|
|
20719
|
-
}
|
|
20720
|
+
const cursor = await runCursorCleanup({
|
|
20721
|
+
// {cursor,revision} 即持久形状,原样落库。
|
|
20722
|
+
encodeProgress: (progress) => progress,
|
|
20723
|
+
maxPages,
|
|
20724
|
+
parseProgress,
|
|
20725
|
+
progressKey: KEY_DEVICE_MAILBOX_CLEANUP_PROGRESS,
|
|
20726
|
+
runPage: async (pageCursor) => {
|
|
20727
|
+
const page = await mailbox.cleanup({
|
|
20728
|
+
limit,
|
|
20729
|
+
...pageCursor === void 0 ? {} : { cursor: pageCursor }
|
|
20730
|
+
});
|
|
20731
|
+
aggregate.deleted += page.deleted;
|
|
20732
|
+
aggregate.expired += page.expired;
|
|
20733
|
+
aggregate.scanned += page.scanned;
|
|
20734
|
+
return page.cursor;
|
|
20735
|
+
},
|
|
20736
|
+
state: deps.state
|
|
20737
|
+
});
|
|
20720
20738
|
return { ...aggregate, ...cursor === void 0 ? {} : { cursor } };
|
|
20721
20739
|
}
|
|
20722
20740
|
|
|
@@ -37320,6 +37338,99 @@ async function handleFeedbackDelete(c, env) {
|
|
|
37320
37338
|
return feedbackJson(feedbackRemoveResponseSchema, { ok: true });
|
|
37321
37339
|
}
|
|
37322
37340
|
|
|
37341
|
+
// ../app/src/responses.ts
|
|
37342
|
+
function tbErrorResponse(err) {
|
|
37343
|
+
return new Response(JSON.stringify(tbErrorBodySchema.parse(err.toJSON())), {
|
|
37344
|
+
status: err.httpStatus,
|
|
37345
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
37346
|
+
});
|
|
37347
|
+
}
|
|
37348
|
+
function withSecurityHeaders(res) {
|
|
37349
|
+
if (res.status === 101 || res.webSocket != null) return res;
|
|
37350
|
+
const apply = (headers) => {
|
|
37351
|
+
if (!headers.has("content-security-policy")) {
|
|
37352
|
+
headers.set(
|
|
37353
|
+
"content-security-policy",
|
|
37354
|
+
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https: http:; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none'"
|
|
37355
|
+
);
|
|
37356
|
+
}
|
|
37357
|
+
headers.set("x-content-type-options", "nosniff");
|
|
37358
|
+
headers.set("x-frame-options", "DENY");
|
|
37359
|
+
headers.set("referrer-policy", "no-referrer");
|
|
37360
|
+
};
|
|
37361
|
+
try {
|
|
37362
|
+
apply(res.headers);
|
|
37363
|
+
return res;
|
|
37364
|
+
} catch {
|
|
37365
|
+
const headers = new Headers(res.headers);
|
|
37366
|
+
apply(headers);
|
|
37367
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
|
|
37368
|
+
}
|
|
37369
|
+
}
|
|
37370
|
+
async function runHandler(fn) {
|
|
37371
|
+
try {
|
|
37372
|
+
return await fn();
|
|
37373
|
+
} catch (err) {
|
|
37374
|
+
if (isTBError(err)) return tbErrorResponse(err);
|
|
37375
|
+
return tbErrorResponse(new TBError("internal", "internal error"));
|
|
37376
|
+
}
|
|
37377
|
+
}
|
|
37378
|
+
function renderHelp(model, rep) {
|
|
37379
|
+
if (rep === "json") {
|
|
37380
|
+
return new Response(JSON.stringify(helpJsonSchema.parse(renderHelpJson(model))), {
|
|
37381
|
+
headers: { "content-type": contentTypeFor("json") }
|
|
37382
|
+
});
|
|
37383
|
+
}
|
|
37384
|
+
if (rep === "markdown") {
|
|
37385
|
+
return new Response(renderHelpMarkdown(model), {
|
|
37386
|
+
headers: { "content-type": contentTypeFor("markdown") }
|
|
37387
|
+
});
|
|
37388
|
+
}
|
|
37389
|
+
return new Response(renderHelpDsl(model), {
|
|
37390
|
+
headers: { "content-type": contentTypeFor("dsl") }
|
|
37391
|
+
});
|
|
37392
|
+
}
|
|
37393
|
+
async function enrichHelp(model, path, store) {
|
|
37394
|
+
try {
|
|
37395
|
+
const [annotation, feedback] = await Promise.all([
|
|
37396
|
+
new AnnotationStore(store).get(path),
|
|
37397
|
+
path === "" ? Promise.resolve([]) : new FeedbackStore(store).helpItems(path)
|
|
37398
|
+
]);
|
|
37399
|
+
return {
|
|
37400
|
+
...model,
|
|
37401
|
+
...annotation !== null ? { note: annotation.text } : {},
|
|
37402
|
+
...feedback.length > 0 ? { feedback } : {}
|
|
37403
|
+
};
|
|
37404
|
+
} catch {
|
|
37405
|
+
return model;
|
|
37406
|
+
}
|
|
37407
|
+
}
|
|
37408
|
+
function renderResult(value, rep) {
|
|
37409
|
+
const json = JSON.stringify(value ?? null);
|
|
37410
|
+
if (rep === "json") {
|
|
37411
|
+
return new Response(json, { headers: { "content-type": contentTypeFor("json") } });
|
|
37412
|
+
}
|
|
37413
|
+
return new Response(`\`\`\`json
|
|
37414
|
+
${json}
|
|
37415
|
+
\`\`\`
|
|
37416
|
+
`, {
|
|
37417
|
+
headers: { "content-type": contentTypeFor("markdown") }
|
|
37418
|
+
});
|
|
37419
|
+
}
|
|
37420
|
+
function renderTreeDsl(tree) {
|
|
37421
|
+
const lines = [];
|
|
37422
|
+
const walk = (n, depth) => {
|
|
37423
|
+
const indent = " ".repeat(depth);
|
|
37424
|
+
const label = n.path === "" ? "/" : n.path;
|
|
37425
|
+
const trunc = n.truncated ? " \u2026" : "";
|
|
37426
|
+
lines.push(`${indent}${label} [${n.kind}] ${n.description}${trunc}`);
|
|
37427
|
+
for (const child of n.children ?? []) walk(child, depth + 1);
|
|
37428
|
+
};
|
|
37429
|
+
walk(tree, 0);
|
|
37430
|
+
return `${lines.join("\n")}
|
|
37431
|
+
`;
|
|
37432
|
+
}
|
|
37433
|
+
|
|
37323
37434
|
// ../app/src/routes/deviceMailbox.ts
|
|
37324
37435
|
var DEVICE_MAILBOX_IDEMPOTENCY_HEADER = "x-tb-idempotency-key";
|
|
37325
37436
|
var DEVICE_MAILBOX_HTTP_BODY_BYTES = 272 * 1024;
|
|
@@ -37395,7 +37506,7 @@ function canManage(ctx, operation) {
|
|
|
37395
37506
|
function assertCanManage(ctx, operation) {
|
|
37396
37507
|
if (!canManage(ctx, operation)) throw TBError.notFound("device operation not found");
|
|
37397
37508
|
}
|
|
37398
|
-
async function assertCurrentDeviceCredential(c, target) {
|
|
37509
|
+
async function assertCurrentDeviceCredential(c, target, reservedRoots) {
|
|
37399
37510
|
const ctx = c.get("ctx");
|
|
37400
37511
|
if (ctx.keyId !== target.deviceKeyId) {
|
|
37401
37512
|
throw new TBError("permission_denied", "device credential does not own this operation");
|
|
@@ -37414,7 +37525,8 @@ async function assertCurrentDeviceCredential(c, target) {
|
|
|
37414
37525
|
scopes: ctx.scopes,
|
|
37415
37526
|
...ctx.registerPaths === void 0 ? {} : { registerPaths: ctx.registerPaths }
|
|
37416
37527
|
},
|
|
37417
|
-
targetPath: target.mountPath
|
|
37528
|
+
targetPath: target.mountPath,
|
|
37529
|
+
...reservedRoots === void 0 ? {} : { reservedRoots }
|
|
37418
37530
|
});
|
|
37419
37531
|
if (!allowed.allow) throw allowed.error;
|
|
37420
37532
|
}
|
|
@@ -37438,212 +37550,118 @@ async function enqueueDeviceCommand(c, env, input) {
|
|
|
37438
37550
|
response.headers.set("x-tb-delivery", "mailbox");
|
|
37439
37551
|
return response;
|
|
37440
37552
|
}
|
|
37441
|
-
async function
|
|
37442
|
-
const
|
|
37443
|
-
const
|
|
37553
|
+
async function getOperation(c, env) {
|
|
37554
|
+
const input = parseBody(deviceOperationIdentityRequestSchema, await mailboxJsonObject(c));
|
|
37555
|
+
const result = await env.mailbox().get(input.deviceId, input.operationId);
|
|
37556
|
+
assertCanManage(c.get("ctx"), result);
|
|
37557
|
+
return c.json(deviceOperationDetailSchema.parse(result));
|
|
37558
|
+
}
|
|
37559
|
+
async function listOperations(c, env) {
|
|
37560
|
+
const input = parseBody(deviceOperationListRequestSchema, await mailboxJsonObject(c));
|
|
37444
37561
|
const mailbox = env.mailbox();
|
|
37445
37562
|
const ctx = c.get("ctx");
|
|
37446
|
-
|
|
37447
|
-
|
|
37448
|
-
|
|
37449
|
-
|
|
37450
|
-
|
|
37451
|
-
|
|
37452
|
-
|
|
37453
|
-
const
|
|
37454
|
-
const
|
|
37455
|
-
input.
|
|
37456
|
-
|
|
37457
|
-
|
|
37458
|
-
|
|
37459
|
-
|
|
37460
|
-
|
|
37461
|
-
|
|
37462
|
-
|
|
37463
|
-
deviceId: input.deviceId,
|
|
37464
|
-
limit: limit - items.length,
|
|
37465
|
-
...cursor === void 0 ? {} : { cursor },
|
|
37466
|
-
...input.opts?.states === void 0 ? {} : { states: input.opts.states }
|
|
37467
|
-
});
|
|
37468
|
-
items.push(...page.items.filter((item) => canManage(ctx, item)));
|
|
37469
|
-
cursor = page.cursor;
|
|
37470
|
-
if (cursor === void 0) {
|
|
37471
|
-
return c.json(deviceOperationListResponseSchema.parse({ items }));
|
|
37472
|
-
}
|
|
37473
|
-
if (cursor === previous) {
|
|
37474
|
-
throw new TBError("internal", "device mailbox list cursor did not advance");
|
|
37475
|
-
}
|
|
37476
|
-
}
|
|
37477
|
-
const resumeCursor = cursor;
|
|
37478
|
-
if (resumeCursor === void 0) {
|
|
37563
|
+
const limit = Math.min(
|
|
37564
|
+
input.opts?.limit ?? DEVICE_MAILBOX_LIST_LIMIT_DEFAULT,
|
|
37565
|
+
DEVICE_MAILBOX_LIST_LIMIT_MAX
|
|
37566
|
+
);
|
|
37567
|
+
let cursor = input.opts?.cursor;
|
|
37568
|
+
const items = [];
|
|
37569
|
+
while (items.length < limit) {
|
|
37570
|
+
const previous = cursor;
|
|
37571
|
+
const page = await mailbox.list({
|
|
37572
|
+
deviceId: input.deviceId,
|
|
37573
|
+
limit: limit - items.length,
|
|
37574
|
+
...cursor === void 0 ? {} : { cursor },
|
|
37575
|
+
...input.opts?.states === void 0 ? {} : { states: input.opts.states }
|
|
37576
|
+
});
|
|
37577
|
+
items.push(...page.items.filter((item) => canManage(ctx, item)));
|
|
37578
|
+
cursor = page.cursor;
|
|
37579
|
+
if (cursor === void 0) {
|
|
37479
37580
|
return c.json(deviceOperationListResponseSchema.parse({ items }));
|
|
37480
37581
|
}
|
|
37481
|
-
|
|
37482
|
-
|
|
37483
|
-
const previous = probeCursor;
|
|
37484
|
-
const page = await mailbox.list({
|
|
37485
|
-
deviceId: input.deviceId,
|
|
37486
|
-
cursor: probeCursor,
|
|
37487
|
-
limit: DEVICE_MAILBOX_LIST_LIMIT_MAX,
|
|
37488
|
-
...input.opts?.states === void 0 ? {} : { states: input.opts.states }
|
|
37489
|
-
});
|
|
37490
|
-
if (page.items.some((item) => canManage(ctx, item))) {
|
|
37491
|
-
return c.json(deviceOperationListResponseSchema.parse({ items, cursor: resumeCursor }));
|
|
37492
|
-
}
|
|
37493
|
-
probeCursor = page.cursor;
|
|
37494
|
-
if (probeCursor === previous) {
|
|
37495
|
-
throw new TBError("internal", "device mailbox list cursor did not advance");
|
|
37496
|
-
}
|
|
37582
|
+
if (cursor === previous) {
|
|
37583
|
+
throw new TBError("internal", "device mailbox list cursor did not advance");
|
|
37497
37584
|
}
|
|
37498
|
-
return c.json(deviceOperationListResponseSchema.parse({ items }));
|
|
37499
|
-
}
|
|
37500
|
-
if (path === "/~device/operations/cancel") {
|
|
37501
|
-
const input = parseBody(deviceOperationIdentityRequestSchema, body);
|
|
37502
|
-
const current = await mailbox.get(input.deviceId, input.operationId);
|
|
37503
|
-
assertCanManage(ctx, current);
|
|
37504
|
-
return c.json(deviceOperationDetailSchema.parse(
|
|
37505
|
-
await mailbox.cancel(input.deviceId, input.operationId)
|
|
37506
|
-
));
|
|
37507
37585
|
}
|
|
37508
|
-
|
|
37509
|
-
|
|
37510
|
-
|
|
37511
|
-
const path = new URL(c.req.url).pathname.replace(/\/+$/, "");
|
|
37512
|
-
const body = await mailboxJsonObject(c);
|
|
37513
|
-
const mailbox = env.mailbox();
|
|
37514
|
-
const ctx = c.get("ctx");
|
|
37515
|
-
const authorize = async (target) => await assertCurrentDeviceCredential(c, target);
|
|
37516
|
-
if (path === "/~device/mailbox/claim") {
|
|
37517
|
-
const input = parseBody(deviceOperationClaimRequestSchema, body);
|
|
37518
|
-
return c.json(deviceOperationClaimResponseSchema.parse(await mailbox.claim({
|
|
37519
|
-
...input,
|
|
37520
|
-
deviceKeyId: ctx.keyId,
|
|
37521
|
-
authorize
|
|
37522
|
-
})));
|
|
37586
|
+
const resumeCursor = cursor;
|
|
37587
|
+
if (resumeCursor === void 0) {
|
|
37588
|
+
return c.json(deviceOperationListResponseSchema.parse({ items }));
|
|
37523
37589
|
}
|
|
37524
|
-
|
|
37525
|
-
|
|
37526
|
-
|
|
37527
|
-
|
|
37528
|
-
deviceKeyId: ctx.keyId,
|
|
37529
|
-
authorize
|
|
37530
|
-
})));
|
|
37531
|
-
}
|
|
37532
|
-
if (path === "/~device/mailbox/complete") {
|
|
37533
|
-
const input = parseBody(deviceOperationCompleteRequestSchema, body);
|
|
37534
|
-
let completion;
|
|
37535
|
-
if (input.outcome === "succeeded") {
|
|
37536
|
-
completion = { outcome: "succeeded", result: input.result };
|
|
37537
|
-
} else if (input.outcome === "result_unknown") {
|
|
37538
|
-
completion = {
|
|
37539
|
-
outcome: "result_unknown",
|
|
37540
|
-
...input.error === void 0 ? {} : { error: input.error }
|
|
37541
|
-
};
|
|
37542
|
-
} else {
|
|
37543
|
-
completion = { outcome: input.outcome, error: input.error };
|
|
37544
|
-
}
|
|
37545
|
-
return c.json(deviceOperationDetailSchema.parse(await mailbox.complete({
|
|
37590
|
+
let probeCursor = resumeCursor;
|
|
37591
|
+
while (probeCursor !== void 0) {
|
|
37592
|
+
const previous = probeCursor;
|
|
37593
|
+
const page = await mailbox.list({
|
|
37546
37594
|
deviceId: input.deviceId,
|
|
37547
|
-
|
|
37548
|
-
|
|
37549
|
-
|
|
37550
|
-
|
|
37551
|
-
|
|
37552
|
-
|
|
37553
|
-
|
|
37554
|
-
|
|
37555
|
-
|
|
37556
|
-
|
|
37557
|
-
function tbErrorResponse(err) {
|
|
37558
|
-
return new Response(JSON.stringify(tbErrorBodySchema.parse(err.toJSON())), {
|
|
37559
|
-
status: err.httpStatus,
|
|
37560
|
-
headers: { "content-type": "application/json; charset=utf-8" }
|
|
37561
|
-
});
|
|
37562
|
-
}
|
|
37563
|
-
function withSecurityHeaders(res) {
|
|
37564
|
-
if (res.status === 101 || res.webSocket != null) return res;
|
|
37565
|
-
const apply = (headers) => {
|
|
37566
|
-
if (!headers.has("content-security-policy")) {
|
|
37567
|
-
headers.set(
|
|
37568
|
-
"content-security-policy",
|
|
37569
|
-
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https: http:; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none'"
|
|
37570
|
-
);
|
|
37595
|
+
cursor: probeCursor,
|
|
37596
|
+
limit: DEVICE_MAILBOX_LIST_LIMIT_MAX,
|
|
37597
|
+
...input.opts?.states === void 0 ? {} : { states: input.opts.states }
|
|
37598
|
+
});
|
|
37599
|
+
if (page.items.some((item) => canManage(ctx, item))) {
|
|
37600
|
+
return c.json(deviceOperationListResponseSchema.parse({ items, cursor: resumeCursor }));
|
|
37601
|
+
}
|
|
37602
|
+
probeCursor = page.cursor;
|
|
37603
|
+
if (probeCursor === previous) {
|
|
37604
|
+
throw new TBError("internal", "device mailbox list cursor did not advance");
|
|
37571
37605
|
}
|
|
37572
|
-
headers.set("x-content-type-options", "nosniff");
|
|
37573
|
-
headers.set("x-frame-options", "DENY");
|
|
37574
|
-
headers.set("referrer-policy", "no-referrer");
|
|
37575
|
-
};
|
|
37576
|
-
try {
|
|
37577
|
-
apply(res.headers);
|
|
37578
|
-
return res;
|
|
37579
|
-
} catch {
|
|
37580
|
-
const headers = new Headers(res.headers);
|
|
37581
|
-
apply(headers);
|
|
37582
|
-
return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
|
|
37583
37606
|
}
|
|
37607
|
+
return c.json(deviceOperationListResponseSchema.parse({ items }));
|
|
37584
37608
|
}
|
|
37585
|
-
async function
|
|
37586
|
-
|
|
37587
|
-
|
|
37588
|
-
|
|
37589
|
-
|
|
37590
|
-
|
|
37591
|
-
|
|
37609
|
+
async function cancelOperation(c, env) {
|
|
37610
|
+
const input = parseBody(deviceOperationIdentityRequestSchema, await mailboxJsonObject(c));
|
|
37611
|
+
const mailbox = env.mailbox();
|
|
37612
|
+
const current = await mailbox.get(input.deviceId, input.operationId);
|
|
37613
|
+
assertCanManage(c.get("ctx"), current);
|
|
37614
|
+
return c.json(deviceOperationDetailSchema.parse(
|
|
37615
|
+
await mailbox.cancel(input.deviceId, input.operationId)
|
|
37616
|
+
));
|
|
37617
|
+
}
|
|
37618
|
+
function deviceAuthorize(c, env) {
|
|
37619
|
+
return async (target) => await assertCurrentDeviceCredential(c, target, env.deps.reservedRoots);
|
|
37620
|
+
}
|
|
37621
|
+
async function claimOperations(c, env) {
|
|
37622
|
+
const input = parseBody(deviceOperationClaimRequestSchema, await mailboxJsonObject(c));
|
|
37623
|
+
return c.json(deviceOperationClaimResponseSchema.parse(await env.mailbox().claim({
|
|
37624
|
+
...input,
|
|
37625
|
+
deviceKeyId: c.get("ctx").keyId,
|
|
37626
|
+
authorize: deviceAuthorize(c, env)
|
|
37627
|
+
})));
|
|
37592
37628
|
}
|
|
37593
|
-
function
|
|
37594
|
-
|
|
37595
|
-
|
|
37596
|
-
|
|
37597
|
-
|
|
37598
|
-
|
|
37599
|
-
|
|
37600
|
-
return new Response(renderHelpMarkdown(model), {
|
|
37601
|
-
headers: { "content-type": contentTypeFor("markdown") }
|
|
37602
|
-
});
|
|
37603
|
-
}
|
|
37604
|
-
return new Response(renderHelpDsl(model), {
|
|
37605
|
-
headers: { "content-type": contentTypeFor("dsl") }
|
|
37606
|
-
});
|
|
37629
|
+
async function renewLeases(c, env) {
|
|
37630
|
+
const input = parseBody(deviceOperationRenewRequestSchema, await mailboxJsonObject(c));
|
|
37631
|
+
return c.json(deviceOperationRenewResponseSchema.parse(await env.mailbox().renew({
|
|
37632
|
+
...input,
|
|
37633
|
+
deviceKeyId: c.get("ctx").keyId,
|
|
37634
|
+
authorize: deviceAuthorize(c, env)
|
|
37635
|
+
})));
|
|
37607
37636
|
}
|
|
37608
|
-
async function
|
|
37609
|
-
|
|
37610
|
-
|
|
37611
|
-
|
|
37612
|
-
|
|
37613
|
-
|
|
37614
|
-
|
|
37615
|
-
|
|
37616
|
-
...
|
|
37617
|
-
...feedback.length > 0 ? { feedback } : {}
|
|
37637
|
+
async function completeOperation(c, env) {
|
|
37638
|
+
const input = parseBody(deviceOperationCompleteRequestSchema, await mailboxJsonObject(c));
|
|
37639
|
+
let completion;
|
|
37640
|
+
if (input.outcome === "succeeded") {
|
|
37641
|
+
completion = { outcome: "succeeded", result: input.result };
|
|
37642
|
+
} else if (input.outcome === "result_unknown") {
|
|
37643
|
+
completion = {
|
|
37644
|
+
outcome: "result_unknown",
|
|
37645
|
+
...input.error === void 0 ? {} : { error: input.error }
|
|
37618
37646
|
};
|
|
37619
|
-
}
|
|
37620
|
-
|
|
37621
|
-
}
|
|
37622
|
-
|
|
37623
|
-
|
|
37624
|
-
|
|
37625
|
-
|
|
37626
|
-
|
|
37627
|
-
|
|
37628
|
-
|
|
37629
|
-
|
|
37630
|
-
|
|
37631
|
-
|
|
37632
|
-
|
|
37633
|
-
|
|
37634
|
-
|
|
37635
|
-
|
|
37636
|
-
|
|
37637
|
-
const walk = (n, depth) => {
|
|
37638
|
-
const indent = " ".repeat(depth);
|
|
37639
|
-
const label = n.path === "" ? "/" : n.path;
|
|
37640
|
-
const trunc = n.truncated ? " \u2026" : "";
|
|
37641
|
-
lines.push(`${indent}${label} [${n.kind}] ${n.description}${trunc}`);
|
|
37642
|
-
for (const child of n.children ?? []) walk(child, depth + 1);
|
|
37643
|
-
};
|
|
37644
|
-
walk(tree, 0);
|
|
37645
|
-
return `${lines.join("\n")}
|
|
37646
|
-
`;
|
|
37647
|
+
} else {
|
|
37648
|
+
completion = { outcome: input.outcome, error: input.error };
|
|
37649
|
+
}
|
|
37650
|
+
return c.json(deviceOperationDetailSchema.parse(await env.mailbox().complete({
|
|
37651
|
+
deviceId: input.deviceId,
|
|
37652
|
+
operationId: input.operationId,
|
|
37653
|
+
leaseId: input.leaseId,
|
|
37654
|
+
deviceKeyId: c.get("ctx").keyId,
|
|
37655
|
+
authorize: deviceAuthorize(c, env)
|
|
37656
|
+
}, completion)));
|
|
37657
|
+
}
|
|
37658
|
+
function registerDeviceMailboxRoutes(app, env) {
|
|
37659
|
+
app.post("/~device/operations/get", (c) => runHandler(async () => await getOperation(c, env)));
|
|
37660
|
+
app.post("/~device/operations/list", (c) => runHandler(async () => await listOperations(c, env)));
|
|
37661
|
+
app.post("/~device/operations/cancel", (c) => runHandler(async () => await cancelOperation(c, env)));
|
|
37662
|
+
app.post("/~device/mailbox/claim", (c) => runHandler(async () => await claimOperations(c, env)));
|
|
37663
|
+
app.post("/~device/mailbox/renew", (c) => runHandler(async () => await renewLeases(c, env)));
|
|
37664
|
+
app.post("/~device/mailbox/complete", (c) => runHandler(async () => await completeOperation(c, env)));
|
|
37647
37665
|
}
|
|
37648
37666
|
|
|
37649
37667
|
// ../app/src/federation.ts
|
|
@@ -37867,6 +37885,24 @@ function assertRemoteConfigAllowed(config, settings) {
|
|
|
37867
37885
|
assertRemoteAllowed(baseUrl, settings);
|
|
37868
37886
|
}
|
|
37869
37887
|
|
|
37888
|
+
// ../app/src/registryMutation.ts
|
|
37889
|
+
async function assertNodeConfigMutation(opts) {
|
|
37890
|
+
const { action, config, ctx, deps, registry, targetPath } = opts;
|
|
37891
|
+
assertRemoteConfigAllowed(config, await resolveRemoteSettings(deps.state, deps.remote));
|
|
37892
|
+
await assertRegisterPath2(registry, ctx, targetPath, action, deps);
|
|
37893
|
+
assertSecretRefUse(ctx.scopes, config);
|
|
37894
|
+
await assertMcpOAuthConfig(config, deps.secrets);
|
|
37895
|
+
await assertContextConfig(config, deps);
|
|
37896
|
+
await assertSkillhubConfig(config, deps);
|
|
37897
|
+
await assertToolConfig(config, deps, ctx, targetPath);
|
|
37898
|
+
}
|
|
37899
|
+
async function invalidateNodeDerivedState(store, path) {
|
|
37900
|
+
await invalidateToolCache(store, path);
|
|
37901
|
+
await invalidateMcpEra(store, path);
|
|
37902
|
+
await invalidateMcpOAuth(store, path);
|
|
37903
|
+
await invalidateProviderOAuth(store, path);
|
|
37904
|
+
}
|
|
37905
|
+
|
|
37870
37906
|
// ../app/src/routes/register.ts
|
|
37871
37907
|
async function authorizeToolNode(c, env, node, encryptionKey) {
|
|
37872
37908
|
const { deps } = env;
|
|
@@ -37923,16 +37959,17 @@ async function handleRegister(c, env) {
|
|
|
37923
37959
|
);
|
|
37924
37960
|
}
|
|
37925
37961
|
const body = parseNodeInput(raw);
|
|
37926
|
-
assertRemoteConfigAllowed(body.config, await resolveRemoteSettings(store, deps.remote));
|
|
37927
37962
|
if (!check(ctx, path, "register").allow) {
|
|
37928
37963
|
throw new TBError("permission_denied", `no scope grants 'register' on '${path}'`);
|
|
37929
37964
|
}
|
|
37930
|
-
await
|
|
37931
|
-
|
|
37932
|
-
|
|
37933
|
-
|
|
37934
|
-
|
|
37935
|
-
|
|
37965
|
+
await assertNodeConfigMutation({
|
|
37966
|
+
action: "write",
|
|
37967
|
+
config: body.config,
|
|
37968
|
+
ctx,
|
|
37969
|
+
deps,
|
|
37970
|
+
registry,
|
|
37971
|
+
targetPath: body.path
|
|
37972
|
+
});
|
|
37936
37973
|
await searchSync?.ensureSeeded();
|
|
37937
37974
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
37938
37975
|
const marker = await searchSync?.markNode(body.path);
|
|
@@ -37943,12 +37980,9 @@ async function handleRegister(c, env) {
|
|
|
37943
37980
|
await searchSync?.abort(marker);
|
|
37944
37981
|
throw error;
|
|
37945
37982
|
}
|
|
37946
|
-
await
|
|
37947
|
-
await invalidateMcpEra(store, body.path);
|
|
37948
|
-
await invalidateMcpOAuth(store, body.path);
|
|
37949
|
-
await invalidateProviderOAuth(store, body.path);
|
|
37983
|
+
await invalidateNodeDerivedState(store, body.path);
|
|
37950
37984
|
await searchSync?.reconcileNodeQuietly(body.path, { marker });
|
|
37951
|
-
if (await refreshDynamicSearchNode(node, ctx, deps)) await searchSync?.abort(marker);
|
|
37985
|
+
if (await refreshDynamicSearchNode(node, ctx, deps, searchSync)) await searchSync?.abort(marker);
|
|
37952
37986
|
return new Response(JSON.stringify(registryNodeSchema.parse(node)), {
|
|
37953
37987
|
headers: { "content-type": contentTypeFor("json") }
|
|
37954
37988
|
});
|
|
@@ -38045,7 +38079,7 @@ async function handleDescribe(c, env) {
|
|
|
38045
38079
|
await assertContextAlive(node, node.config, registry);
|
|
38046
38080
|
const cfg = node.config;
|
|
38047
38081
|
const local = cfg.provider !== "r2" && cfg.provider !== "s3" ? localContext(deps, node) : null;
|
|
38048
|
-
let capabilities = cfg.provider === "r2" || cfg.provider === "s3" || cfg.provider === "device-fs" || deviceMarkerOf(cfg.providerConfig) !== null ? CONTEXT_CAPABILITIES : local !== null ?
|
|
38082
|
+
let capabilities = cfg.provider === "r2" || cfg.provider === "s3" || cfg.provider === "device-fs" || deviceMarkerOf(cfg.providerConfig) !== null ? CONTEXT_CAPABILITIES : local !== null ? contextCapabilitiesOf(local) : (await requirePluginExport(deps, cfg.provider, "context", "context", cfg.export)).export.capabilities ?? [];
|
|
38049
38083
|
if ((cfg.provider === "r2" || cfg.provider === "s3") && cfg.readOnly !== true && await contextDirectUploadAvailable(cfg, deps)) {
|
|
38050
38084
|
capabilities = [...capabilities, CONTEXT_DIRECT_UPLOAD_CAPABILITY];
|
|
38051
38085
|
}
|
|
@@ -38713,12 +38747,8 @@ var FederatedSearchSessionStore = class {
|
|
|
38713
38747
|
}
|
|
38714
38748
|
};
|
|
38715
38749
|
|
|
38716
|
-
// ../app/src/search/
|
|
38717
|
-
function
|
|
38718
|
-
return effect === "read" || effect === "write" || effect === "destructive" ? effect : "unknown";
|
|
38719
|
-
}
|
|
38720
|
-
function projectSearchTool(tool, detail) {
|
|
38721
|
-
if (detail === "full") return tool;
|
|
38750
|
+
// ../app/src/search/compactTool.ts
|
|
38751
|
+
function compactSearchTool(tool) {
|
|
38722
38752
|
const compact = { ...tool };
|
|
38723
38753
|
delete compact.inputSchema;
|
|
38724
38754
|
delete compact.outputSchema;
|
|
@@ -38738,6 +38768,14 @@ function projectSearchTool(tool, detail) {
|
|
|
38738
38768
|
}
|
|
38739
38769
|
return compact;
|
|
38740
38770
|
}
|
|
38771
|
+
|
|
38772
|
+
// ../app/src/search/localSearch.ts
|
|
38773
|
+
function normalizedEffect(effect) {
|
|
38774
|
+
return effect === "read" || effect === "write" || effect === "destructive" ? effect : "unknown";
|
|
38775
|
+
}
|
|
38776
|
+
function projectSearchTool(tool, detail) {
|
|
38777
|
+
return detail === "full" ? tool : compactSearchTool(tool);
|
|
38778
|
+
}
|
|
38741
38779
|
async function executeLocalSearch(deps, request) {
|
|
38742
38780
|
const { ctx, search, searchSync, state } = deps;
|
|
38743
38781
|
const query = request.query;
|
|
@@ -38893,24 +38931,7 @@ function continuationSourceError(error) {
|
|
|
38893
38931
|
return retryableContinuationError();
|
|
38894
38932
|
}
|
|
38895
38933
|
function compactItem(item) {
|
|
38896
|
-
|
|
38897
|
-
delete tool.inputSchema;
|
|
38898
|
-
delete tool.outputSchema;
|
|
38899
|
-
if (tool.description !== void 0) {
|
|
38900
|
-
const encoder = new TextEncoder();
|
|
38901
|
-
if (encoder.encode(tool.description).length > TOOL_SEARCH_DESCRIPTION_BYTES_MAX) {
|
|
38902
|
-
let description = "";
|
|
38903
|
-
let bytes = 0;
|
|
38904
|
-
for (const char of tool.description) {
|
|
38905
|
-
const size = encoder.encode(char).length;
|
|
38906
|
-
if (bytes + size > TOOL_SEARCH_DESCRIPTION_BYTES_MAX) break;
|
|
38907
|
-
description += char;
|
|
38908
|
-
bytes += size;
|
|
38909
|
-
}
|
|
38910
|
-
tool.description = description;
|
|
38911
|
-
}
|
|
38912
|
-
}
|
|
38913
|
-
return { ...item, tool };
|
|
38934
|
+
return { ...item, tool: compactSearchTool(item.tool) };
|
|
38914
38935
|
}
|
|
38915
38936
|
function normalizedEffect2(effect) {
|
|
38916
38937
|
return effect === "read" || effect === "write" || effect === "destructive" ? effect : "unknown";
|
|
@@ -39610,9 +39631,6 @@ async function executeFederatedSearch(deps, input) {
|
|
|
39610
39631
|
"recursive search requires explicit TB_INSTANCE_ID and atomic StateStore CAS"
|
|
39611
39632
|
);
|
|
39612
39633
|
}
|
|
39613
|
-
if ((input.request.opts?.mode ?? "keyword") !== "keyword") {
|
|
39614
|
-
throw new TBError("invalid_argument", "semantic search cannot use recursive federation");
|
|
39615
|
-
}
|
|
39616
39634
|
const wireOpts = input.request.opts ?? {};
|
|
39617
39635
|
const constraints = normalizeToolSearchOptions({
|
|
39618
39636
|
...wireOpts.effects === void 0 ? {} : { effects: wireOpts.effects },
|
|
@@ -39925,13 +39943,6 @@ function registerSearchRoute(app, env) {
|
|
|
39925
39943
|
);
|
|
39926
39944
|
}
|
|
39927
39945
|
const request = requestWithNormalizedLimit(parsed.data);
|
|
39928
|
-
const mode = request.opts?.mode ?? "keyword";
|
|
39929
|
-
if (mode === "semantic" && !capabilities.includes("search:semantic")) {
|
|
39930
|
-
throw new TBError(
|
|
39931
|
-
"invalid_argument",
|
|
39932
|
-
"search mode 'semantic' requires capability 'search:semantic'"
|
|
39933
|
-
);
|
|
39934
|
-
}
|
|
39935
39946
|
const explicitFederation = request.opts?.federation;
|
|
39936
39947
|
const cursor = request.opts?.cursor;
|
|
39937
39948
|
const federation = explicitFederation ?? (cursor !== void 0 && !isFederatedSearchSessionHandle(cursor) ? "local" : capabilities.includes("search:federated") ? "recursive" : "local");
|
|
@@ -39941,9 +39952,6 @@ function registerSearchRoute(app, env) {
|
|
|
39941
39952
|
"recursive search requires capability 'search:federated'"
|
|
39942
39953
|
);
|
|
39943
39954
|
}
|
|
39944
|
-
if (federation === "recursive" && mode === "semantic") {
|
|
39945
|
-
throw new TBError("invalid_argument", "semantic search cannot use recursive federation");
|
|
39946
|
-
}
|
|
39947
39955
|
const normalizedRequest = {
|
|
39948
39956
|
...request,
|
|
39949
39957
|
opts: { ...request.opts ?? {}, federation }
|
|
@@ -40011,7 +40019,7 @@ async function helpModelFor(node, registry, ctx, builtins, deps, opts) {
|
|
|
40011
40019
|
}
|
|
40012
40020
|
if (node.kind === "mcp" || node.kind === "http" || node.kind === "tool") {
|
|
40013
40021
|
const provider = await providerFor(node, ctx, deps);
|
|
40014
|
-
const raw = await upstreamTools(node, provider, deps, opts.refresh, opts.now);
|
|
40022
|
+
const raw = await upstreamTools(node, provider, deps, opts.refresh, opts.now, opts.searchSync);
|
|
40015
40023
|
const { exposed } = virtualizeTools(node.virtualize, raw);
|
|
40016
40024
|
return toolsToHelpModel(
|
|
40017
40025
|
node.path,
|
|
@@ -40081,7 +40089,8 @@ async function commandHelpModelFor(registry, ctx, builtins, deps, path, opts) {
|
|
|
40081
40089
|
const full = await helpModelFor(node, registry, ctx, builtins, deps, {
|
|
40082
40090
|
refresh: opts.refresh,
|
|
40083
40091
|
schemas: true,
|
|
40084
|
-
now: (/* @__PURE__ */ new Date()).toISOString()
|
|
40092
|
+
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
40093
|
+
searchSync: opts.searchSync
|
|
40085
40094
|
});
|
|
40086
40095
|
const cmd = full.cmds.find((c) => c.name === command);
|
|
40087
40096
|
if (cmd === void 0) return null;
|
|
@@ -40127,20 +40136,8 @@ var toolSpecCommand = (node, tool, providerBacked = false) => {
|
|
|
40127
40136
|
...tool.confirm === true ? { confirm: true } : {}
|
|
40128
40137
|
};
|
|
40129
40138
|
};
|
|
40130
|
-
var remoteCommand = (localNodePath, model, command) => {
|
|
40131
|
-
const remoteNodePath = model.node.path.replace(/^\/+|\/+$/g, "");
|
|
40132
|
-
const remoteCommandPath = command.path.replace(/^\/+|\/+$/g, "");
|
|
40133
|
-
if (remoteCommandPath !== remoteNodePath && !remoteCommandPath.startsWith(`${remoteNodePath}/`)) {
|
|
40134
|
-
throw new TBError("unavailable", "remote ~help returned a command outside its node");
|
|
40135
|
-
}
|
|
40136
|
-
const suffix = remoteCommandPath.slice(remoteNodePath.length);
|
|
40137
|
-
return mcpCommand(localNodePath, model.node.description, {
|
|
40138
|
-
...command,
|
|
40139
|
-
path: `/${localNodePath}${suffix}`
|
|
40140
|
-
});
|
|
40141
|
-
};
|
|
40142
40139
|
function mcpBridgeFor(c, env, app) {
|
|
40143
|
-
const { builtinsOf, deps, globalSearchCapabilities } = env;
|
|
40140
|
+
const { builtinsOf, deps, globalSearchCapabilities, searchSync } = env;
|
|
40144
40141
|
const ctx = c.get("ctx");
|
|
40145
40142
|
const registry = new NodeRegistryStore(c.get("store"));
|
|
40146
40143
|
let remoteRequests = 0;
|
|
@@ -40159,7 +40156,7 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40159
40156
|
additionalProperties: false,
|
|
40160
40157
|
properties: {
|
|
40161
40158
|
query: { type: "string", minLength: 1 },
|
|
40162
|
-
mode: { type: "string", enum: ["keyword"
|
|
40159
|
+
mode: { type: "string", enum: ["keyword"] },
|
|
40163
40160
|
limit: { type: "integer", minimum: 1, maximum: 200 },
|
|
40164
40161
|
cursor: { type: "string", minLength: 1 },
|
|
40165
40162
|
detail: { type: "string", enum: ["compact", "full"], default: "compact" },
|
|
@@ -40227,6 +40224,10 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40227
40224
|
};
|
|
40228
40225
|
const remoteHelp = async (path) => {
|
|
40229
40226
|
takeRemoteRequest();
|
|
40227
|
+
const projector = await remotePathProjectorIfMatch(registry, path);
|
|
40228
|
+
if (projector === null) {
|
|
40229
|
+
throw remoteProtocolError(`remote ~help path '${path}' lost its mount owner`);
|
|
40230
|
+
}
|
|
40230
40231
|
const headers = new Headers(c.req.raw.headers);
|
|
40231
40232
|
headers.set("accept", "application/json");
|
|
40232
40233
|
const response = await remotePassthroughIfMatch(
|
|
@@ -40241,28 +40242,11 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40241
40242
|
if (response === null || !response.ok) {
|
|
40242
40243
|
throw new TBError("unavailable", `remote ~help failed for '${path}'`, { retryable: true });
|
|
40243
40244
|
}
|
|
40244
|
-
const
|
|
40245
|
-
if (
|
|
40245
|
+
const parsed = helpJsonSchema.safeParse(await response.json().catch(() => null));
|
|
40246
|
+
if (!parsed.success) {
|
|
40246
40247
|
throw new TBError("unavailable", `remote ~help returned invalid JSON for '${path}'`);
|
|
40247
40248
|
}
|
|
40248
|
-
|
|
40249
|
-
if (owner?.node.kind !== "remote") {
|
|
40250
|
-
throw remoteProtocolError(`remote ~help path '${path}' lost its mount owner`);
|
|
40251
|
-
}
|
|
40252
|
-
const modelPath = canonicalRemotePath(model.node.path, true);
|
|
40253
|
-
if (modelPath !== owner.rest) {
|
|
40254
|
-
throw remoteProtocolError(`remote ~help path '${modelPath}' does not match request`);
|
|
40255
|
-
}
|
|
40256
|
-
for (const command of model.cmds) {
|
|
40257
|
-
if (!command.path.startsWith("/") || command.path.startsWith("//")) {
|
|
40258
|
-
throw remoteProtocolError(`remote ~help returned invalid command path '${command.path}'`);
|
|
40259
|
-
}
|
|
40260
|
-
const commandPath = canonicalRemotePath(command.path.slice(1), true);
|
|
40261
|
-
if (!remotePathWithin(modelPath, commandPath)) {
|
|
40262
|
-
throw remoteProtocolError(`remote ~help command '${command.path}' escapes its node`);
|
|
40263
|
-
}
|
|
40264
|
-
}
|
|
40265
|
-
return model;
|
|
40249
|
+
return projector.projectHelp(parsed.data, path, ctx);
|
|
40266
40250
|
};
|
|
40267
40251
|
const remotePaths = async (root) => {
|
|
40268
40252
|
const found = [];
|
|
@@ -40311,13 +40295,10 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40311
40295
|
if (check(ctx, path, command.scope).allow) {
|
|
40312
40296
|
let detailed = command;
|
|
40313
40297
|
if (command.inputSchema === void 0 && command.path !== `/${model2.node.path}`) {
|
|
40314
|
-
const
|
|
40315
|
-
const remoteCommandPath = command.path.replace(/^\/+|\/+$/g, "");
|
|
40316
|
-
const detailPath = `${path}${remoteCommandPath.slice(remoteNodePath.length)}`;
|
|
40317
|
-
const detail = await remoteHelp(detailPath);
|
|
40298
|
+
const detail = await remoteHelp(command.path.replace(/^\/+/, ""));
|
|
40318
40299
|
detailed = detail.cmds.find((item) => item.name === command.name) ?? command;
|
|
40319
40300
|
}
|
|
40320
|
-
result.push(
|
|
40301
|
+
result.push(mcpCommand(path, model2.node.description, detailed));
|
|
40321
40302
|
}
|
|
40322
40303
|
}
|
|
40323
40304
|
}
|
|
@@ -40333,7 +40314,7 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40333
40314
|
if ((node.kind === "mcp" || node.kind === "http" || node.kind === "tool") && node.config !== void 0) {
|
|
40334
40315
|
if (!check(ctx, node.path, "call").allow) continue;
|
|
40335
40316
|
const provider = await providerFor(node, ctx, deps);
|
|
40336
|
-
const raw = await upstreamTools(node, provider, deps, false, now);
|
|
40317
|
+
const raw = await upstreamTools(node, provider, deps, false, now, searchSync);
|
|
40337
40318
|
const { exposed } = virtualizeTools(node.virtualize, raw);
|
|
40338
40319
|
result.push(...exposed.map((tool) => toolSpecCommand(node, tool, true)));
|
|
40339
40320
|
continue;
|
|
@@ -40341,7 +40322,8 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40341
40322
|
const model = await helpModelFor(node, registry, ctx, builtinsOf(c.get("store")), deps, {
|
|
40342
40323
|
includeDirectUpload: check(ctx, node.path, "write").allow,
|
|
40343
40324
|
refresh: false,
|
|
40344
|
-
now
|
|
40325
|
+
now,
|
|
40326
|
+
searchSync
|
|
40345
40327
|
});
|
|
40346
40328
|
for (const command of model.cmds) {
|
|
40347
40329
|
if (check(ctx, node.path, command.scope).allow) {
|
|
@@ -40444,7 +40426,7 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40444
40426
|
throw TBError.notFound("not found");
|
|
40445
40427
|
}
|
|
40446
40428
|
const provider = await providerFor(node, ctx, deps);
|
|
40447
|
-
const raw = await upstreamTools(node, provider, deps, false, (/* @__PURE__ */ new Date()).toISOString());
|
|
40429
|
+
const raw = await upstreamTools(node, provider, deps, false, (/* @__PURE__ */ new Date()).toISOString(), searchSync);
|
|
40448
40430
|
const upstreamName = resolveUpstreamTool(node.virtualize, raw, tool.toolName);
|
|
40449
40431
|
const result2 = await provider.call(upstreamName, args);
|
|
40450
40432
|
return {
|
|
@@ -40606,7 +40588,7 @@ async function handleInvoke(c, env) {
|
|
|
40606
40588
|
}
|
|
40607
40589
|
const args2 = await readInvokeBody();
|
|
40608
40590
|
const provider = await providerFor(node, ctx, deps);
|
|
40609
|
-
const tools = await upstreamTools(node, provider, deps, false, (/* @__PURE__ */ new Date()).toISOString());
|
|
40591
|
+
const tools = await upstreamTools(node, provider, deps, false, (/* @__PURE__ */ new Date()).toISOString(), searchSync);
|
|
40610
40592
|
const upstreamName = resolveUpstreamTool(node.virtualize, tools, command);
|
|
40611
40593
|
const result2 = await provider.call(upstreamName, args2);
|
|
40612
40594
|
return renderResult(result2.content, negotiate(c.req.header("accept")));
|
|
@@ -40670,7 +40652,7 @@ async function handleInvoke(c, env) {
|
|
|
40670
40652
|
if (cfg.provider !== "r2" && cfg.provider !== "s3") {
|
|
40671
40653
|
const local = localContext(deps, node);
|
|
40672
40654
|
if (local !== null) {
|
|
40673
|
-
const result4 = await
|
|
40655
|
+
const result4 = await dispatchContextCmd(local, command, args2);
|
|
40674
40656
|
return renderResult(result4, negotiate(c.req.header("accept")));
|
|
40675
40657
|
}
|
|
40676
40658
|
const { manifest, export: exported } = await requirePluginExport(
|
|
@@ -40690,11 +40672,11 @@ async function handleInvoke(c, env) {
|
|
|
40690
40672
|
...cfg.authRef !== void 0 ? { upstreamAuthRef: cfg.authRef } : {},
|
|
40691
40673
|
...deps.pluginBindings !== void 0 ? { bindings: deps.pluginBindings } : {}
|
|
40692
40674
|
});
|
|
40693
|
-
const result3 = await
|
|
40675
|
+
const result3 = await dispatchContextCmd(provider2, command, args2);
|
|
40694
40676
|
return renderResult(result3, negotiate(c.req.header("accept")));
|
|
40695
40677
|
}
|
|
40696
40678
|
const provider = await contextProviderFor(node, cfg, deps, c.req.url);
|
|
40697
|
-
const result2 = await
|
|
40679
|
+
const result2 = await dispatchContextCmd(provider, command, args2);
|
|
40698
40680
|
return renderResult(result2, negotiate(c.req.header("accept")));
|
|
40699
40681
|
}
|
|
40700
40682
|
if (node.kind === "skillhub" && node.config?.kind === "skillhub") {
|
|
@@ -40712,7 +40694,7 @@ async function handleInvoke(c, env) {
|
|
|
40712
40694
|
throw new TBError("permission_denied", `readOnly \u6302\u8F7D\u62D2\u7EDD '${command}'`);
|
|
40713
40695
|
}
|
|
40714
40696
|
const provider = await skillhubProviderFor(node, cfg, deps, c.req.url);
|
|
40715
|
-
const result2 = await
|
|
40697
|
+
const result2 = await dispatchSkillhubCmd(provider, command, args2);
|
|
40716
40698
|
return renderResult(result2, negotiate(c.req.header("accept")));
|
|
40717
40699
|
}
|
|
40718
40700
|
if (node.kind !== "builtin" || node.config?.kind !== "builtin") {
|
|
@@ -40736,19 +40718,14 @@ async function handleInvoke(c, env) {
|
|
|
40736
40718
|
throw new TBError("invalid_argument", "field 'path' must be a string");
|
|
40737
40719
|
}
|
|
40738
40720
|
const cfgPatch = cmd === "write" ? args.config : cmd === "update" ? args.patch?.config : void 0;
|
|
40739
|
-
|
|
40740
|
-
|
|
40741
|
-
|
|
40721
|
+
await assertNodeConfigMutation({
|
|
40722
|
+
action: cmd === "delete" ? "delete" : "write",
|
|
40723
|
+
config: cfgPatch,
|
|
40742
40724
|
ctx,
|
|
40743
|
-
|
|
40744
|
-
|
|
40745
|
-
|
|
40746
|
-
);
|
|
40747
|
-
assertSecretRefUse(ctx.scopes, cfgPatch);
|
|
40748
|
-
await assertMcpOAuthConfig(cfgPatch, deps.secrets);
|
|
40749
|
-
await assertContextConfig(cfgPatch, deps);
|
|
40750
|
-
await assertSkillhubConfig(cfgPatch, deps);
|
|
40751
|
-
await assertToolConfig(cfgPatch, deps, ctx, targetPath);
|
|
40725
|
+
deps,
|
|
40726
|
+
registry,
|
|
40727
|
+
targetPath
|
|
40728
|
+
});
|
|
40752
40729
|
registryTarget = targetPath;
|
|
40753
40730
|
}
|
|
40754
40731
|
if (registryTarget !== void 0 && (cmd === "write" || cmd === "update")) {
|
|
@@ -40778,22 +40755,16 @@ async function handleInvoke(c, env) {
|
|
|
40778
40755
|
return tbErrorResponse(new TBError("internal", "internal error"));
|
|
40779
40756
|
}
|
|
40780
40757
|
if (registryTarget !== void 0) {
|
|
40781
|
-
await
|
|
40782
|
-
await invalidateMcpEra(store, registryTarget);
|
|
40783
|
-
await invalidateMcpOAuth(store, registryTarget);
|
|
40784
|
-
await invalidateProviderOAuth(store, registryTarget);
|
|
40758
|
+
await invalidateNodeDerivedState(store, registryTarget);
|
|
40785
40759
|
await searchSync?.reconcileNodeQuietly(registryTarget, { marker: registryMarker });
|
|
40786
40760
|
const current = await registry.get(registryTarget).catch(() => null);
|
|
40787
|
-
if (current !== null && await refreshDynamicSearchNode(current, ctx, deps)) {
|
|
40761
|
+
if (current !== null && await refreshDynamicSearchNode(current, ctx, deps, searchSync)) {
|
|
40788
40762
|
await searchSync?.abort(registryMarker);
|
|
40789
40763
|
}
|
|
40790
40764
|
}
|
|
40791
40765
|
const pluginEmptyPaths = [];
|
|
40792
40766
|
for (const mount of pluginMounts) {
|
|
40793
|
-
await
|
|
40794
|
-
await invalidateMcpEra(store, mount.node.path);
|
|
40795
|
-
await invalidateMcpOAuth(store, mount.node.path);
|
|
40796
|
-
await invalidateProviderOAuth(store, mount.node.path);
|
|
40767
|
+
await invalidateNodeDerivedState(store, mount.node.path);
|
|
40797
40768
|
const providerId = mount.node.config?.kind === "tool" ? mount.node.config.provider : "";
|
|
40798
40769
|
const manifest = await store.get(KEY_PLUGIN + providerId);
|
|
40799
40770
|
if (manifest !== null && manifest.enabled !== false) {
|
|
@@ -40841,8 +40812,7 @@ function createRouteEnv(deps) {
|
|
|
40841
40812
|
if (!declared.has("search")) return [];
|
|
40842
40813
|
return [
|
|
40843
40814
|
"search",
|
|
40844
|
-
...deps.remote.instanceId !== void 0 && deps.state.compareAndSwap !== void 0 && deps.search?.revision !== void 0 ? ["search:federated"] : []
|
|
40845
|
-
...declared.has("search:semantic") ? ["search:semantic"] : []
|
|
40815
|
+
...deps.remote.instanceId !== void 0 && deps.state.compareAndSwap !== void 0 && deps.search?.revision !== void 0 ? ["search:federated"] : []
|
|
40846
40816
|
];
|
|
40847
40817
|
};
|
|
40848
40818
|
let catalogDigest;
|
|
@@ -40862,7 +40832,7 @@ function createRouteEnv(deps) {
|
|
|
40862
40832
|
|
|
40863
40833
|
// ../app/src/routes/help.ts
|
|
40864
40834
|
async function handleHelp(c, env) {
|
|
40865
|
-
const { builtinsOf, deps } = env;
|
|
40835
|
+
const { builtinsOf, deps, searchSync } = env;
|
|
40866
40836
|
const path = splitReserved(new URL(c.req.url).pathname, "~help");
|
|
40867
40837
|
if (path === null) throw TBError.notFound("no such path");
|
|
40868
40838
|
const ctx = c.get("ctx");
|
|
@@ -40901,11 +40871,12 @@ async function handleHelp(c, env) {
|
|
|
40901
40871
|
try {
|
|
40902
40872
|
node = await registry.get(path);
|
|
40903
40873
|
} catch {
|
|
40904
|
-
const toolModel = await toolHelpModelFor(c, ctx, registry, path, deps);
|
|
40874
|
+
const toolModel = await toolHelpModelFor(c, ctx, registry, path, deps, searchSync);
|
|
40905
40875
|
if (toolModel !== null) return renderHelp(await enrichHelp(toolModel, path, store), rep);
|
|
40906
40876
|
const cmdModel = await commandHelpModelFor(registry, ctx, builtins, deps, path, {
|
|
40907
40877
|
refresh,
|
|
40908
|
-
schemas: schemas2
|
|
40878
|
+
schemas: schemas2,
|
|
40879
|
+
searchSync
|
|
40909
40880
|
});
|
|
40910
40881
|
if (cmdModel !== null) return renderHelp(await enrichHelp(cmdModel, path, store), rep);
|
|
40911
40882
|
throw TBError.notFound("not found");
|
|
@@ -40913,7 +40884,8 @@ async function handleHelp(c, env) {
|
|
|
40913
40884
|
const model = await helpModelFor(node, registry, ctx, builtins, deps, {
|
|
40914
40885
|
refresh,
|
|
40915
40886
|
schemas: schemas2,
|
|
40916
|
-
now: (/* @__PURE__ */ new Date()).toISOString()
|
|
40887
|
+
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
40888
|
+
searchSync
|
|
40917
40889
|
});
|
|
40918
40890
|
return renderHelp(await enrichHelp(model, path, store), rep);
|
|
40919
40891
|
}
|
|
@@ -41021,6 +40993,7 @@ function createTbApp(deps) {
|
|
|
41021
40993
|
});
|
|
41022
40994
|
registerMcpRoute(app, env);
|
|
41023
40995
|
registerSearchRoute(app, env);
|
|
40996
|
+
registerDeviceMailboxRoutes(app, env);
|
|
41024
40997
|
app.get(
|
|
41025
40998
|
"/system/device/ws",
|
|
41026
40999
|
(c) => runHandler(async () => {
|
|
@@ -41061,13 +41034,6 @@ function createTbApp(deps) {
|
|
|
41061
41034
|
async (c) => await runHandler(async () => {
|
|
41062
41035
|
const segs = new URL(c.req.url).pathname.replace(/\/+$/, "").split("/");
|
|
41063
41036
|
const last = segs.pop() ?? "";
|
|
41064
|
-
const pathname = new URL(c.req.url).pathname.replace(/\/+$/, "");
|
|
41065
|
-
if (pathname.startsWith("/~device/operations/")) {
|
|
41066
|
-
return await handleDeviceMailboxControl(c, env);
|
|
41067
|
-
}
|
|
41068
|
-
if (pathname.startsWith("/~device/mailbox/")) {
|
|
41069
|
-
return await handleDeviceMailboxData(c, env);
|
|
41070
|
-
}
|
|
41071
41037
|
if (last === "~register") return await handleRegister(c, env);
|
|
41072
41038
|
if (last === "~authorize") return await handleAuthorize(c, env);
|
|
41073
41039
|
if (last === "~feedback" || segs[segs.length - 1] === "~feedback") {
|
|
@@ -41178,17 +41144,12 @@ var DeviceHub = class {
|
|
|
41178
41144
|
};
|
|
41179
41145
|
const conn = this.activeByDevice.get(deviceId);
|
|
41180
41146
|
if (conn === void 0) return offline;
|
|
41181
|
-
const
|
|
41182
|
-
|
|
41183
|
-
|
|
41184
|
-
|
|
41185
|
-
|
|
41186
|
-
|
|
41187
|
-
},
|
|
41188
|
-
targetPath: conn.mountPath,
|
|
41189
|
-
action: "write",
|
|
41190
|
-
existing: null
|
|
41191
|
-
}).allow;
|
|
41147
|
+
const authorized = conn.keyId !== void 0 && conn.mountPath !== void 0 && await reverifyDeviceAuthority({
|
|
41148
|
+
store: this.store,
|
|
41149
|
+
authorization: conn.authorization,
|
|
41150
|
+
keyId: conn.keyId,
|
|
41151
|
+
mountPath: conn.mountPath
|
|
41152
|
+
});
|
|
41192
41153
|
if (!authorized) {
|
|
41193
41154
|
conn.session.reject(TBError.unauthenticated());
|
|
41194
41155
|
return offline;
|
|
@@ -41220,10 +41181,11 @@ var DeviceHub = class {
|
|
|
41220
41181
|
}
|
|
41221
41182
|
if (meta.disconnectedAt === void 0) {
|
|
41222
41183
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
41223
|
-
|
|
41224
|
-
|
|
41225
|
-
|
|
41226
|
-
|
|
41184
|
+
await markDeviceDisconnected({
|
|
41185
|
+
mountPath: meta.mountPath,
|
|
41186
|
+
now,
|
|
41187
|
+
registry: new NodeRegistryStore(this.store)
|
|
41188
|
+
});
|
|
41227
41189
|
await this.store.put(KEY_DEVICE_META + meta.deviceId, { ...meta, disconnectedAt: now });
|
|
41228
41190
|
this.scheduleReclaim(meta.deviceId, this.reclaimSec * 1e3);
|
|
41229
41191
|
} else {
|
|
@@ -41321,9 +41283,7 @@ var DeviceHub = class {
|
|
|
41321
41283
|
...this.search === void 0 ? {} : { search: this.search }
|
|
41322
41284
|
});
|
|
41323
41285
|
if (!searchIndexed) {
|
|
41324
|
-
console.warn(
|
|
41325
|
-
`[tool-bridge] device '${conn.deviceId}' mounted at ${mountPath} but its tools are NOT in the search index: registry exceeds the tool-search capacity (TOOL_SEARCH_AUDIT_NODE_LIMIT). The device is callable but won't appear in \`tb search\` until capacity frees up.`
|
|
41326
|
-
);
|
|
41286
|
+
console.warn(deviceSearchCapacityWarning(conn.deviceId, mountPath));
|
|
41327
41287
|
}
|
|
41328
41288
|
const meta = { deviceId: conn.deviceId, mountPath, keyId, connectedAt: now };
|
|
41329
41289
|
await this.store.put(KEY_DEVICE_META + conn.deviceId, meta);
|
|
@@ -41348,10 +41308,11 @@ var DeviceHub = class {
|
|
|
41348
41308
|
const raw = await this.store.get(KEY_DEVICE_META + conn.deviceId);
|
|
41349
41309
|
if (!isDeviceMeta(raw)) return;
|
|
41350
41310
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
41351
|
-
|
|
41352
|
-
|
|
41353
|
-
|
|
41354
|
-
|
|
41311
|
+
await markDeviceDisconnected({
|
|
41312
|
+
mountPath: raw.mountPath,
|
|
41313
|
+
now,
|
|
41314
|
+
registry: new NodeRegistryStore(this.store)
|
|
41315
|
+
});
|
|
41355
41316
|
await this.store.put(KEY_DEVICE_META + conn.deviceId, { ...raw, disconnectedAt: now });
|
|
41356
41317
|
this.scheduleReclaim(conn.deviceId, this.reclaimSec * 1e3);
|
|
41357
41318
|
}
|
|
@@ -41384,13 +41345,12 @@ var DeviceHub = class {
|
|
|
41384
41345
|
}
|
|
41385
41346
|
const raw = await this.store.get(KEY_DEVICE_META + deviceId);
|
|
41386
41347
|
if (!isDeviceMeta(raw)) return;
|
|
41387
|
-
|
|
41388
|
-
|
|
41389
|
-
|
|
41390
|
-
|
|
41391
|
-
|
|
41392
|
-
}
|
|
41393
|
-
await searchSync?.removeSubtreeQuietly(raw.mountPath, marker);
|
|
41348
|
+
await reclaimDeviceSubtree({
|
|
41349
|
+
mountPath: raw.mountPath,
|
|
41350
|
+
registry: new NodeRegistryStore(this.store),
|
|
41351
|
+
...this.search === void 0 ? {} : { search: this.search },
|
|
41352
|
+
state: this.store
|
|
41353
|
+
});
|
|
41394
41354
|
await this.store.delete(KEY_DEVICE_META + deviceId);
|
|
41395
41355
|
}
|
|
41396
41356
|
/** ws 协议层探活:上一轮未回 pong → terminate(触发 close → markDisconnected)。 */
|
|
@@ -42481,7 +42441,7 @@ import { join as join4 } from "path";
|
|
|
42481
42441
|
// package.json
|
|
42482
42442
|
var package_default = {
|
|
42483
42443
|
name: "@tool-bridge/server",
|
|
42484
|
-
version: "0.
|
|
42444
|
+
version: "0.21.0",
|
|
42485
42445
|
description: "tool-bridge self-hosted server: run the HTBP gateway on Node with SQLite or PostgreSQL persistence, filesystem object storage and WebSocket device channel \u2014 single container via docker run",
|
|
42486
42446
|
type: "module",
|
|
42487
42447
|
license: "MIT",
|