@tool-bridge/server 0.20.0 → 0.21.1
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-LLZDFN7M.js} +19 -16
- package/dist/{alt_text_generator_ai-X7RMDDM5.js → alt_text_generator_ai-W7O34SNG.js} +4 -4
- package/dist/{amap-3VER75VH.js → amap-VOX4T2AW.js} +4 -4
- package/dist/{anthropic-N7NTB3UC.js → anthropic-G23NN3X5.js} +23 -25
- package/dist/{apify-JB5P5AJI.js → apify-Y3SS734K.js} +20 -19
- package/dist/{brave_search-USL5OOBS.js → brave_search-ZC7OAPSZ.js} +16 -14
- package/dist/{chunk-6ANLYNFB.js → chunk-ATK45BXV.js} +1 -1
- package/dist/{chunk-6X55ALDX.js → chunk-E6JCVKW6.js} +6 -5
- package/dist/{chunk-RVUERWUZ.js → chunk-KMTT3I6D.js} +768 -1048
- package/dist/chunk-R6P4XAE5.js +248 -0
- package/dist/{chunk-3WL6EXYX.js → chunk-U7B6DS2O.js} +1 -1
- package/dist/{chunk-WIGQCP6O.js → chunk-UZM2RXZH.js} +159 -182
- package/dist/chunk-XYBU3WZH.js +71 -0
- package/dist/{circleci-LBGB25LP.js → circleci-3BAX5FCG.js} +20 -24
- package/dist/{clerk-6RX37CNZ.js → clerk-6LEM6TNU.js} +22 -21
- package/dist/{cloudflare_dns-NQGW7FHV.js → cloudflare_dns-KINYTJUD.js} +21 -17
- package/dist/{cohere-XPC2NAEW.js → cohere-XCJ5AJ6B.js} +19 -17
- package/dist/{confluence-ZUG2LT5A.js → confluence-B3EWPZVE.js} +16 -11
- package/dist/{convertapi-ZLXKWV3L.js → convertapi-EUWSQYPF.js} +3 -3
- package/dist/{currencyapi-T3A3HSOD.js → currencyapi-KVTIK3GL.js} +23 -26
- package/dist/{deepseek-NJMOD23E.js → deepseek-PC5YGNC5.js} +27 -17
- package/dist/{docker_hub-D2MQCSJ7.js → docker_hub-C3MA5FU6.js} +4 -4
- package/dist/{dropbox-MI6U3SOT.js → dropbox-XBV7Z2IR.js} +9 -8
- package/dist/{dub-P2BYBYV4.js → dub-LDOFG3HC.js} +22 -19
- package/dist/{e2b-BGKIAE54.js → e2b-JMCPSVBW.js} +20 -20
- package/dist/{exa-EUY7VV4C.js → exa-RAK7O7RD.js} +22 -25
- package/dist/{fal_ai-AAOG5FYJ.js → fal_ai-23E7JPNT.js} +18 -17
- package/dist/feishu-7QRIDUV6.js +12019 -0
- package/dist/{feishu_custom_bot-BWGMNVXF.js → feishu_custom_bot-TJ4K7QNJ.js} +3 -3
- package/dist/{figma-SJSWYCGD.js → figma-E6IX5DTH.js} +21 -23
- package/dist/{firehydrant-MOXK2PST.js → firehydrant-ZBLPQREE.js} +15 -15
- package/dist/{fixer-ISIDUDZU.js → fixer-BWON27UO.js} +4 -4
- package/dist/{fly-U4KPSGBF.js → fly-ZH3HFCZJ.js} +20 -16
- package/dist/{front-ZHM2YX2E.js → front-QCGXYEDJ.js} +20 -23
- package/dist/{geocodio-SVCCAXAV.js → geocodio-YGNI7DA4.js} +4 -4
- package/dist/{ghost-INTR7FIY.js → ghost-EVSGKH2Q.js} +4 -4
- package/dist/{github-T275VVWR.js → github-MA2BGRCJ.js} +28 -27
- package/dist/{gmail-KSSPOE67.js → gmail-E5E6AADE.js} +17 -17
- package/dist/{googlecalendar-JB5RW27L.js → googlecalendar-BTLFPXTD.js} +22 -19
- package/dist/{googledocs-G522PUGW.js → googledocs-BUHL4J2H.js} +6 -5
- package/dist/{grafana-44ZPIV5P.js → grafana-GLU2DWF2.js} +24 -25
- package/dist/{graphhopper-2MORXLOW.js → graphhopper-WSMB3TCD.js} +4 -4
- package/dist/index.d.ts +31 -30
- package/dist/index.js +4 -3
- package/dist/{ip2proxy-R4K4KEMI.js → ip2proxy-EZ64Y4ZH.js} +4 -4
- package/dist/{ipgeolocation_io-H4J55OGI.js → ipgeolocation_io-AA7HAS22.js} +4 -4
- package/dist/{ipqualityscore-XGID4OHA.js → ipqualityscore-BHJXVTJT.js} +4 -4
- package/dist/{jina_ai-7ZVDOR3Z.js → jina_ai-EFBHTBXL.js} +20 -21
- package/dist/{jira-CTIYZTT3.js → jira-HQSHAA5K.js} +20 -16
- package/dist/{langsmith-ISP567FS.js → langsmith-5W6SCT6F.js} +25 -25
- package/dist/{lemlist-OGKK272F.js → lemlist-HJWH6UMK.js} +23 -24
- package/dist/{linear-6JX4E3JI.js → linear-K7RYFYAD.js} +6 -5
- package/dist/{logsnag-BVB36ZCL.js → logsnag-4KFOHL37.js} +20 -22
- package/dist/main.js +103 -102
- package/dist/{mattermost-UECVPZGY.js → mattermost-S2BRCWLQ.js} +21 -17
- package/dist/{mem0-EUA7S2OO.js → mem0-CAW43CIC.js} +15 -15
- package/dist/{memos-BYSBJRWZ.js → memos-TWEGPKFM.js} +39 -34
- package/dist/{metabase-4JXZH3YG.js → metabase-MMSEPCOF.js} +22 -18
- package/dist/{mistral_ai-2C5BTBKW.js → mistral_ai-64BGZQHP.js} +24 -21
- package/dist/{mother_duck-ZGNNNR3X.js → mother_duck-H7PCQNZ2.js} +23 -19
- package/dist/{ngrok-NTPGMRXI.js → ngrok-GQCUGZKJ.js} +17 -22
- package/dist/{notes-KGYXX6N6.js → notes-PFMKRO33.js} +1 -1
- package/dist/{notion-EX3X2G7G.js → notion-DQWZWP4N.js} +20 -17
- package/dist/{open_exchange_rates-2V2LIGPS.js → open_exchange_rates-QMMD735E.js} +4 -4
- package/dist/{openai-KQFQPK7Z.js → openai-W6PX32NZ.js} +22 -16
- package/dist/{opengraph_io-B63SEYJY.js → opengraph_io-HNDBXYNW.js} +4 -4
- package/dist/{openrouter-62A4H62Q.js → openrouter-JJYMEAUL.js} +19 -14
- package/dist/{outline-QC33XLCK.js → outline-MCBNQZYY.js} +18 -20
- package/dist/{paddle-HDZG35Y7.js → paddle-D2SKLUDN.js} +17 -15
- package/dist/{perplexity-M7LOMJWA.js → perplexity-XIFMKUAE.js} +19 -23
- package/dist/{pinecone-Y7YCJRZM.js → pinecone-HUFTALV2.js} +19 -24
- package/dist/{pipedrive-FLJA62UX.js → pipedrive-Y2O7QGFF.js} +22 -23
- package/dist/{polar-AE55Q425.js → polar-TYTOMOSR.js} +19 -17
- package/dist/{posthog-IAY5D3YW.js → posthog-43JAR3VW.js} +29 -24
- package/dist/{postmark-NMKAXDXZ.js → postmark-KL2QJYSG.js} +19 -17
- package/dist/{prerender-CDJM7I7N.js → prerender-MENYM2TY.js} +4 -4
- package/dist/{pubmed-N437ZJOR.js → pubmed-GOXUVVH5.js} +6 -5
- package/dist/{railway-NBWYXCUM.js → railway-V2SID4WV.js} +3 -3
- package/dist/{readwise-QRO3UC3R.js → readwise-CI4GIMMJ.js} +16 -15
- package/dist/{render-NEW7SROG.js → render-SUBJ6YRZ.js} +21 -20
- package/dist/{replicate-73YQLRTY.js → replicate-756MXPYX.js} +20 -21
- package/dist/{resend-R5BAZFHZ.js → resend-ZKH3BTBI.js} +15 -11
- package/dist/{rootly-5BLALPCS.js → rootly-CD2LJHPI.js} +19 -18
- package/dist/{scrapfly-EHFLNNUZ.js → scrapfly-NSRHFDIQ.js} +4 -4
- package/dist/{scrapingbee-UXLYS72S.js → scrapingbee-OQHLAVEY.js} +4 -4
- package/dist/{screenshot_fyi-KC3NLTJ6.js → screenshot_fyi-DVWG3AXC.js} +4 -4
- package/dist/{semantic_scholar-SA5OIUWA.js → semantic_scholar-T4A252KW.js} +21 -20
- package/dist/{sentry-BAE3XLJN.js → sentry-546MNDJG.js} +22 -19
- package/dist/{shodan-FJR6MKKW.js → shodan-C3OON3Z3.js} +4 -4
- package/dist/{shopify-7OA6SLN5.js → shopify-Y2HN2MCQ.js} +23 -19
- package/dist/{stripe-MUWDZGGF.js → stripe-VE3CD3AY.js} +3 -3
- package/dist/{supabase-SWWGNWYR.js → supabase-U2SL4D63.js} +18 -17
- package/dist/{tavily-XQNJ5EDI.js → tavily-KCZHK6HE.js} +20 -20
- package/dist/{telegram-RR6JPZOG.js → telegram-3URFBCMY.js} +4 -4
- package/dist/{telnyx-7PKXOWSO.js → telnyx-CXQ4EZUX.js} +19 -19
- package/dist/{todoist-63X5IUJE.js → todoist-HNPWNYTD.js} +18 -17
- package/dist/{trello-MOG63DAI.js → trello-3W2MIXRP.js} +4 -4
- package/dist/{turso-ENWTSHDL.js → turso-2EQF7C2Z.js} +23 -30
- package/dist/{twilio-FDXUN7VX.js → twilio-4PDBJKWX.js} +6 -5
- package/dist/{umami-TM7PROWF.js → umami-5REN7TGP.js} +23 -28
- package/dist/{unsplash-NWXLKSOW.js → unsplash-EIXL424F.js} +19 -18
- package/dist/{upstash_redis-CUTQ5L2N.js → upstash_redis-G4LQBYHA.js} +17 -13
- package/dist/{uptimerobot-NNO32M3I.js → uptimerobot-KNJ3EYLY.js} +3 -3
- package/dist/{v2ex-24KMSWS7.js → v2ex-RHJ234PZ.js} +35 -18
- package/dist/{vercel-B45LPF6I.js → vercel-AS6NCIC7.js} +21 -18
- package/dist/{wolfram_alpha_api-DKJWVR6G.js → wolfram_alpha_api-3G4PUJWB.js} +4 -4
- package/dist/{wordpress-XYFSDZXY.js → wordpress-KNDAAUV7.js} +21 -17
- package/dist/{workos-BEM7EPHM.js → workos-R6UITSJ7.js} +26 -27
- package/dist/{zhihu-CFHZ4NC7.js → zhihu-AGK4HILK.js} +21 -21
- package/package.json +5 -5
- package/dist/feishu-AN5IJYOW.js +0 -231
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EventSourceParserStream,
|
|
3
|
+
pkceChallenge
|
|
4
|
+
} from "./chunk-R6P4XAE5.js";
|
|
1
5
|
import {
|
|
2
6
|
createGuardedFetch
|
|
3
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-U7B6DS2O.js";
|
|
4
8
|
import {
|
|
5
9
|
AnnotationStore,
|
|
6
10
|
CONTEXT_CAPABILITIES,
|
|
@@ -55,6 +59,7 @@ import {
|
|
|
55
59
|
any,
|
|
56
60
|
appendVia,
|
|
57
61
|
array,
|
|
62
|
+
assertNoCollision,
|
|
58
63
|
assertPluginPayloadSize,
|
|
59
64
|
assertPresignTtlSec,
|
|
60
65
|
assertSecretRefUse,
|
|
@@ -171,7 +176,7 @@ import {
|
|
|
171
176
|
resolveIntegration,
|
|
172
177
|
resolveUpstreamTool,
|
|
173
178
|
rewriteRemotePath,
|
|
174
|
-
safeParse,
|
|
179
|
+
safeParse2 as safeParse,
|
|
175
180
|
selectFeedbackSearchText,
|
|
176
181
|
serializeToolSearchSnapshot,
|
|
177
182
|
sha256Hex,
|
|
@@ -196,7 +201,7 @@ import {
|
|
|
196
201
|
url,
|
|
197
202
|
validatePath,
|
|
198
203
|
virtualizeTools
|
|
199
|
-
} from "./chunk-
|
|
204
|
+
} from "./chunk-UZM2RXZH.js";
|
|
200
205
|
|
|
201
206
|
// src/config.ts
|
|
202
207
|
var DEFAULT_PORT = 8787;
|
|
@@ -821,300 +826,48 @@ function createS3ObjectStore(cfg, opts) {
|
|
|
821
826
|
};
|
|
822
827
|
}
|
|
823
828
|
|
|
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
|
-
}
|
|
829
|
+
// ../app/src/deps.ts
|
|
830
|
+
var TOOL_CACHE_TTL_DEFAULT = 300;
|
|
864
831
|
|
|
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);
|
|
832
|
+
// ../app/src/cursorCleanup.ts
|
|
833
|
+
function positiveInt(value, fallback, field) {
|
|
834
|
+
const actual = value ?? fallback;
|
|
835
|
+
if (!Number.isSafeInteger(actual) || actual < 1) {
|
|
836
|
+
throw new TBError("invalid_argument", `${field} must be a positive integer`);
|
|
902
837
|
}
|
|
903
|
-
return
|
|
904
|
-
}
|
|
905
|
-
function documentsFor(path, tools, feedback) {
|
|
906
|
-
return tools.map((tool) => ({ path, tool, ...feedback === "" ? {} : { feedback } }));
|
|
838
|
+
return actual;
|
|
907
839
|
}
|
|
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
|
-
}
|
|
840
|
+
async function runCursorCleanup(opts) {
|
|
841
|
+
const { state } = opts;
|
|
842
|
+
if (state.compareAndSwap === void 0) {
|
|
843
|
+
throw new TBError("unavailable", "cursor cleanup requires StateStore.compareAndSwap");
|
|
1100
844
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
await this.rebuildAll(marker === void 0 ? [] : [marker]);
|
|
845
|
+
const raw = await state.get(opts.progressKey);
|
|
846
|
+
let progress = raw === null ? null : opts.parseProgress(raw);
|
|
847
|
+
let cursor = progress?.cursor;
|
|
848
|
+
for (let pageNumber = 0; pageNumber < opts.maxPages; pageNumber++) {
|
|
849
|
+
const nextCursor = await opts.runPage(cursor, pageNumber);
|
|
850
|
+
if (nextCursor === void 0) {
|
|
851
|
+
if (progress !== null) {
|
|
852
|
+
await state.compareAndSwap(opts.progressKey, progress.revision, null);
|
|
1110
853
|
}
|
|
1111
|
-
|
|
854
|
+
return void 0;
|
|
1112
855
|
}
|
|
856
|
+
const next = {
|
|
857
|
+
cursor: nextCursor,
|
|
858
|
+
revision: (progress?.revision ?? 0) + 1
|
|
859
|
+
};
|
|
860
|
+
const advanced = await state.compareAndSwap(
|
|
861
|
+
opts.progressKey,
|
|
862
|
+
progress?.revision ?? null,
|
|
863
|
+
opts.encodeProgress(next)
|
|
864
|
+
);
|
|
865
|
+
if (!advanced) return void 0;
|
|
866
|
+
progress = next;
|
|
867
|
+
cursor = next.cursor;
|
|
1113
868
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
// ../app/src/deps.ts
|
|
1117
|
-
var TOOL_CACHE_TTL_DEFAULT = 300;
|
|
869
|
+
return cursor;
|
|
870
|
+
}
|
|
1118
871
|
|
|
1119
872
|
// ../app/src/storeRefToken.ts
|
|
1120
873
|
async function hmacKey(secret) {
|
|
@@ -1173,13 +926,6 @@ var STORE_READ_TTL_SEC_DEFAULT = 15 * 60;
|
|
|
1173
926
|
var STORE_CALL_MAX_OBJECTS_DEFAULT = 4;
|
|
1174
927
|
var STORE_CALL_MAX_OBJECT_BYTES_DEFAULT = 256 * 1024 * 1024;
|
|
1175
928
|
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
929
|
function randomSecret() {
|
|
1184
930
|
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
1185
931
|
let out = "";
|
|
@@ -1512,44 +1258,28 @@ async function cleanupDefaultStore(deps, opts = {}) {
|
|
|
1512
1258
|
const store = await defaultStoreRuntime(deps);
|
|
1513
1259
|
const maxPages = Math.min(positiveInt(opts.maxPages, 8, "maxPages"), 64);
|
|
1514
1260
|
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
1261
|
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
|
-
}
|
|
1262
|
+
const cursors = await runCursorCleanup({
|
|
1263
|
+
encodeProgress: (progress) => ({ cursors: progress.cursor, revision: progress.revision }),
|
|
1264
|
+
maxPages,
|
|
1265
|
+
parseProgress: (value) => {
|
|
1266
|
+
const progress = cleanupProgress(value);
|
|
1267
|
+
return { cursor: progress.cursors, revision: progress.revision };
|
|
1268
|
+
},
|
|
1269
|
+
progressKey: KEY_STORE_CLEANUP_PROGRESS,
|
|
1270
|
+
runPage: async (cursor, pageNumber) => {
|
|
1271
|
+
const page = await store.service.cleanup({
|
|
1272
|
+
...limit === void 0 ? {} : { limit },
|
|
1273
|
+
...cursor === void 0 ? {} : { cursors: cursor },
|
|
1274
|
+
// staging 不使用权威状态 cursor;同一 host tick 只运行一次,避免
|
|
1275
|
+
// maxPages 内重复扫描同一 driver maintenance 范围。
|
|
1276
|
+
runDriverMaintenance: pageNumber === 0
|
|
1277
|
+
});
|
|
1278
|
+
addCleanupResult(aggregate, page);
|
|
1279
|
+
return page.cursors;
|
|
1280
|
+
},
|
|
1281
|
+
state: deps.state
|
|
1282
|
+
});
|
|
1553
1283
|
return { ...aggregate, ...cursors === void 0 ? {} : { cursors } };
|
|
1554
1284
|
}
|
|
1555
1285
|
|
|
@@ -14330,250 +14060,6 @@ var Ajv = import_ajv.Ajv;
|
|
|
14330
14060
|
// ../../node_modules/.pnpm/@modelcontextprotocol+client@2.0.0/node_modules/@modelcontextprotocol/client/dist/shimsNode.mjs
|
|
14331
14061
|
var CORS_IS_POSSIBLE = false;
|
|
14332
14062
|
|
|
14333
|
-
// ../../node_modules/.pnpm/pkce-challenge@5.0.1/node_modules/pkce-challenge/dist/index.node.js
|
|
14334
|
-
var crypto2;
|
|
14335
|
-
crypto2 = globalThis.crypto?.webcrypto ?? // Node.js [18-16] REPL
|
|
14336
|
-
globalThis.crypto ?? // Node.js >18
|
|
14337
|
-
import("crypto").then((m) => m.webcrypto);
|
|
14338
|
-
async function getRandomValues(size) {
|
|
14339
|
-
return (await crypto2).getRandomValues(new Uint8Array(size));
|
|
14340
|
-
}
|
|
14341
|
-
async function random(size) {
|
|
14342
|
-
const mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
|
|
14343
|
-
const evenDistCutoff = Math.pow(2, 8) - Math.pow(2, 8) % mask.length;
|
|
14344
|
-
let result = "";
|
|
14345
|
-
while (result.length < size) {
|
|
14346
|
-
const randomBytes = await getRandomValues(size - result.length);
|
|
14347
|
-
for (const randomByte of randomBytes) {
|
|
14348
|
-
if (randomByte < evenDistCutoff) {
|
|
14349
|
-
result += mask[randomByte % mask.length];
|
|
14350
|
-
}
|
|
14351
|
-
}
|
|
14352
|
-
}
|
|
14353
|
-
return result;
|
|
14354
|
-
}
|
|
14355
|
-
async function generateVerifier(length) {
|
|
14356
|
-
return await random(length);
|
|
14357
|
-
}
|
|
14358
|
-
async function generateChallenge(code_verifier) {
|
|
14359
|
-
const buffer = await (await crypto2).subtle.digest("SHA-256", new TextEncoder().encode(code_verifier));
|
|
14360
|
-
return btoa(String.fromCharCode(...new Uint8Array(buffer))).replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "");
|
|
14361
|
-
}
|
|
14362
|
-
async function pkceChallenge(length) {
|
|
14363
|
-
if (!length)
|
|
14364
|
-
length = 43;
|
|
14365
|
-
if (length < 43 || length > 128) {
|
|
14366
|
-
throw `Expected a length between 43 and 128. Received ${length}.`;
|
|
14367
|
-
}
|
|
14368
|
-
const verifier = await generateVerifier(length);
|
|
14369
|
-
const challenge = await generateChallenge(verifier);
|
|
14370
|
-
return {
|
|
14371
|
-
code_verifier: verifier,
|
|
14372
|
-
code_challenge: challenge
|
|
14373
|
-
};
|
|
14374
|
-
}
|
|
14375
|
-
|
|
14376
|
-
// ../../node_modules/.pnpm/eventsource-parser@3.1.0/node_modules/eventsource-parser/dist/index.js
|
|
14377
|
-
var ParseError = class extends Error {
|
|
14378
|
-
constructor(message, options) {
|
|
14379
|
-
super(message), this.name = "ParseError", this.type = options.type, this.field = options.field, this.value = options.value, this.line = options.line;
|
|
14380
|
-
}
|
|
14381
|
-
};
|
|
14382
|
-
var LF = 10;
|
|
14383
|
-
var CR = 13;
|
|
14384
|
-
var SPACE = 32;
|
|
14385
|
-
function noop(_arg) {
|
|
14386
|
-
}
|
|
14387
|
-
function createParser(config) {
|
|
14388
|
-
if (typeof config == "function")
|
|
14389
|
-
throw new TypeError(
|
|
14390
|
-
"`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?"
|
|
14391
|
-
);
|
|
14392
|
-
const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];
|
|
14393
|
-
let pendingFragmentsLength = 0, isFirstChunk = true, id, data = "", dataLines = 0, eventType, terminated = false;
|
|
14394
|
-
function feed(chunk) {
|
|
14395
|
-
if (terminated)
|
|
14396
|
-
throw new Error(
|
|
14397
|
-
"Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."
|
|
14398
|
-
);
|
|
14399
|
-
if (isFirstChunk && (isFirstChunk = false, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {
|
|
14400
|
-
const trailing2 = processLines(chunk);
|
|
14401
|
-
trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();
|
|
14402
|
-
return;
|
|
14403
|
-
}
|
|
14404
|
-
if (chunk.indexOf(`
|
|
14405
|
-
`) === -1 && chunk.indexOf("\r") === -1) {
|
|
14406
|
-
pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();
|
|
14407
|
-
return;
|
|
14408
|
-
}
|
|
14409
|
-
pendingFragments.push(chunk);
|
|
14410
|
-
const input = pendingFragments.join("");
|
|
14411
|
-
pendingFragments.length = 0, pendingFragmentsLength = 0;
|
|
14412
|
-
const trailing = processLines(input);
|
|
14413
|
-
trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();
|
|
14414
|
-
}
|
|
14415
|
-
function checkBufferSize() {
|
|
14416
|
-
maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = true, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(
|
|
14417
|
-
new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
|
|
14418
|
-
type: "max-buffer-size-exceeded"
|
|
14419
|
-
})
|
|
14420
|
-
)));
|
|
14421
|
-
}
|
|
14422
|
-
function processLines(chunk) {
|
|
14423
|
-
let searchIndex = 0;
|
|
14424
|
-
if (chunk.indexOf("\r") === -1) {
|
|
14425
|
-
let lfIndex = chunk.indexOf(`
|
|
14426
|
-
`, searchIndex);
|
|
14427
|
-
for (; lfIndex !== -1; ) {
|
|
14428
|
-
if (searchIndex === lfIndex) {
|
|
14429
|
-
dataLines > 0 && onEvent({ id, event: eventType, data }), id = void 0, data = "", dataLines = 0, eventType = void 0, searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
|
|
14430
|
-
`, searchIndex);
|
|
14431
|
-
continue;
|
|
14432
|
-
}
|
|
14433
|
-
const firstCharCode = chunk.charCodeAt(searchIndex);
|
|
14434
|
-
if (isDataPrefix(chunk, searchIndex, firstCharCode)) {
|
|
14435
|
-
const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5, value = chunk.slice(valueStart, lfIndex);
|
|
14436
|
-
if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {
|
|
14437
|
-
onEvent({ id, event: eventType, data: value }), id = void 0, data = "", eventType = void 0, searchIndex = lfIndex + 2, lfIndex = chunk.indexOf(`
|
|
14438
|
-
`, searchIndex);
|
|
14439
|
-
continue;
|
|
14440
|
-
}
|
|
14441
|
-
data = dataLines === 0 ? value : `${data}
|
|
14442
|
-
${value}`, dataLines++;
|
|
14443
|
-
} else isEventPrefix(chunk, searchIndex, firstCharCode) ? eventType = chunk.slice(
|
|
14444
|
-
chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,
|
|
14445
|
-
lfIndex
|
|
14446
|
-
) || void 0 : parseLine(chunk, searchIndex, lfIndex);
|
|
14447
|
-
searchIndex = lfIndex + 1, lfIndex = chunk.indexOf(`
|
|
14448
|
-
`, searchIndex);
|
|
14449
|
-
}
|
|
14450
|
-
return chunk.slice(searchIndex);
|
|
14451
|
-
}
|
|
14452
|
-
for (; searchIndex < chunk.length; ) {
|
|
14453
|
-
const crIndex = chunk.indexOf("\r", searchIndex), lfIndex = chunk.indexOf(`
|
|
14454
|
-
`, searchIndex);
|
|
14455
|
-
let lineEnd = -1;
|
|
14456
|
-
if (crIndex !== -1 && lfIndex !== -1 ? lineEnd = crIndex < lfIndex ? crIndex : lfIndex : crIndex !== -1 ? crIndex === chunk.length - 1 ? lineEnd = -1 : lineEnd = crIndex : lfIndex !== -1 && (lineEnd = lfIndex), lineEnd === -1)
|
|
14457
|
-
break;
|
|
14458
|
-
parseLine(chunk, searchIndex, lineEnd), searchIndex = lineEnd + 1, chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF && searchIndex++;
|
|
14459
|
-
}
|
|
14460
|
-
return chunk.slice(searchIndex);
|
|
14461
|
-
}
|
|
14462
|
-
function parseLine(chunk, start, end) {
|
|
14463
|
-
if (start === end) {
|
|
14464
|
-
dispatchEvent();
|
|
14465
|
-
return;
|
|
14466
|
-
}
|
|
14467
|
-
const firstCharCode = chunk.charCodeAt(start);
|
|
14468
|
-
if (isDataPrefix(chunk, start, firstCharCode)) {
|
|
14469
|
-
const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5, value2 = chunk.slice(valueStart, end);
|
|
14470
|
-
data = dataLines === 0 ? value2 : `${data}
|
|
14471
|
-
${value2}`, dataLines++;
|
|
14472
|
-
return;
|
|
14473
|
-
}
|
|
14474
|
-
if (isEventPrefix(chunk, start, firstCharCode)) {
|
|
14475
|
-
eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;
|
|
14476
|
-
return;
|
|
14477
|
-
}
|
|
14478
|
-
if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
|
|
14479
|
-
const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
|
|
14480
|
-
id = value2.includes("\0") ? void 0 : value2;
|
|
14481
|
-
return;
|
|
14482
|
-
}
|
|
14483
|
-
if (firstCharCode === 58) {
|
|
14484
|
-
if (onComment) {
|
|
14485
|
-
const line2 = chunk.slice(start, end);
|
|
14486
|
-
onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));
|
|
14487
|
-
}
|
|
14488
|
-
return;
|
|
14489
|
-
}
|
|
14490
|
-
const line = chunk.slice(start, end), fieldSeparatorIndex = line.indexOf(":");
|
|
14491
|
-
if (fieldSeparatorIndex === -1) {
|
|
14492
|
-
processField(line, "", line);
|
|
14493
|
-
return;
|
|
14494
|
-
}
|
|
14495
|
-
const field = line.slice(0, fieldSeparatorIndex), offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1, value = line.slice(fieldSeparatorIndex + offset);
|
|
14496
|
-
processField(field, value, line);
|
|
14497
|
-
}
|
|
14498
|
-
function processField(field, value, line) {
|
|
14499
|
-
switch (field) {
|
|
14500
|
-
case "event":
|
|
14501
|
-
eventType = value || void 0;
|
|
14502
|
-
break;
|
|
14503
|
-
case "data":
|
|
14504
|
-
data = dataLines === 0 ? value : `${data}
|
|
14505
|
-
${value}`, dataLines++;
|
|
14506
|
-
break;
|
|
14507
|
-
case "id":
|
|
14508
|
-
id = value.includes("\0") ? void 0 : value;
|
|
14509
|
-
break;
|
|
14510
|
-
case "retry":
|
|
14511
|
-
/^\d+$/.test(value) ? onRetry(parseInt(value, 10)) : onError(
|
|
14512
|
-
new ParseError(`Invalid \`retry\` value: "${value}"`, {
|
|
14513
|
-
type: "invalid-retry",
|
|
14514
|
-
value,
|
|
14515
|
-
line
|
|
14516
|
-
})
|
|
14517
|
-
);
|
|
14518
|
-
break;
|
|
14519
|
-
default:
|
|
14520
|
-
onError(
|
|
14521
|
-
new ParseError(
|
|
14522
|
-
`Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`,
|
|
14523
|
-
{ type: "unknown-field", field, value, line }
|
|
14524
|
-
)
|
|
14525
|
-
);
|
|
14526
|
-
break;
|
|
14527
|
-
}
|
|
14528
|
-
}
|
|
14529
|
-
function dispatchEvent() {
|
|
14530
|
-
dataLines > 0 && onEvent({
|
|
14531
|
-
id,
|
|
14532
|
-
event: eventType,
|
|
14533
|
-
data
|
|
14534
|
-
}), id = void 0, data = "", dataLines = 0, eventType = void 0;
|
|
14535
|
-
}
|
|
14536
|
-
function reset(options = {}) {
|
|
14537
|
-
if (options.consume && pendingFragments.length > 0) {
|
|
14538
|
-
const incompleteLine = pendingFragments.join("");
|
|
14539
|
-
parseLine(incompleteLine, 0, incompleteLine.length);
|
|
14540
|
-
}
|
|
14541
|
-
isFirstChunk = true, id = void 0, data = "", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = false;
|
|
14542
|
-
}
|
|
14543
|
-
return { feed, reset };
|
|
14544
|
-
}
|
|
14545
|
-
function isDataPrefix(chunk, i, firstCharCode) {
|
|
14546
|
-
return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;
|
|
14547
|
-
}
|
|
14548
|
-
function isEventPrefix(chunk, i, firstCharCode) {
|
|
14549
|
-
return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58;
|
|
14550
|
-
}
|
|
14551
|
-
|
|
14552
|
-
// ../../node_modules/.pnpm/eventsource-parser@3.1.0/node_modules/eventsource-parser/dist/stream.js
|
|
14553
|
-
var EventSourceParserStream = class extends TransformStream {
|
|
14554
|
-
constructor({ onError, onRetry, onComment, maxBufferSize } = {}) {
|
|
14555
|
-
let parser;
|
|
14556
|
-
super({
|
|
14557
|
-
start(controller) {
|
|
14558
|
-
parser = createParser({
|
|
14559
|
-
onEvent: (event) => {
|
|
14560
|
-
controller.enqueue(event);
|
|
14561
|
-
},
|
|
14562
|
-
onError(error) {
|
|
14563
|
-
typeof onError == "function" && onError(error), (onError === "terminate" || error.type === "max-buffer-size-exceeded") && controller.error(error);
|
|
14564
|
-
},
|
|
14565
|
-
onRetry,
|
|
14566
|
-
onComment,
|
|
14567
|
-
maxBufferSize
|
|
14568
|
-
});
|
|
14569
|
-
},
|
|
14570
|
-
transform(chunk) {
|
|
14571
|
-
parser.feed(chunk);
|
|
14572
|
-
}
|
|
14573
|
-
});
|
|
14574
|
-
}
|
|
14575
|
-
};
|
|
14576
|
-
|
|
14577
14063
|
// ../../node_modules/.pnpm/@modelcontextprotocol+client@2.0.0/node_modules/@modelcontextprotocol/client/dist/index.mjs
|
|
14578
14064
|
var OAuthClientFlowError = class extends Error {
|
|
14579
14065
|
static {
|
|
@@ -19459,7 +18945,7 @@ async function guardOAuth(fn) {
|
|
|
19459
18945
|
try {
|
|
19460
18946
|
return await fn();
|
|
19461
18947
|
} catch (err) {
|
|
19462
|
-
if (err
|
|
18948
|
+
if (isTBError(err)) throw err;
|
|
19463
18949
|
if (err instanceof Error && /does not support dynamic client registration/i.test(err.message)) {
|
|
19464
18950
|
throw new TBError(
|
|
19465
18951
|
"invalid_argument",
|
|
@@ -19809,8 +19295,6 @@ function createPluginToolProvider(opts) {
|
|
|
19809
19295
|
|
|
19810
19296
|
// ../app/src/providerOAuth.ts
|
|
19811
19297
|
var KEY_TOKENS = "puoauth:token:";
|
|
19812
|
-
var STATE_TTL_SEC2 = 600;
|
|
19813
|
-
var PROVIDER_OAUTH_CALLBACK_PATH = "/~oauth/callback";
|
|
19814
19298
|
async function invalidateProviderOAuth(store, nodePath) {
|
|
19815
19299
|
await store.delete(KEY_TOKENS + nodePath);
|
|
19816
19300
|
}
|
|
@@ -19885,15 +19369,10 @@ async function requestTokens(opts) {
|
|
|
19885
19369
|
throw err;
|
|
19886
19370
|
}
|
|
19887
19371
|
}
|
|
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
19372
|
async function createPkce() {
|
|
19894
|
-
const verifier =
|
|
19373
|
+
const verifier = base64urlEncode(crypto.getRandomValues(new Uint8Array(32)));
|
|
19895
19374
|
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
19896
|
-
return { verifier, challenge:
|
|
19375
|
+
return { verifier, challenge: base64urlEncode(new Uint8Array(digest)) };
|
|
19897
19376
|
}
|
|
19898
19377
|
function requireProviderOAuthFetch(fetcher) {
|
|
19899
19378
|
if (fetcher === void 0) {
|
|
@@ -19929,7 +19408,7 @@ async function startProviderAuthorization(opts) {
|
|
|
19929
19408
|
{
|
|
19930
19409
|
p: opts.nodePath,
|
|
19931
19410
|
v: pkce?.verifier ?? "",
|
|
19932
|
-
exp: Math.floor(opts.now.getTime() / 1e3) +
|
|
19411
|
+
exp: Math.floor(opts.now.getTime() / 1e3) + STATE_TTL_SEC
|
|
19933
19412
|
},
|
|
19934
19413
|
opts.encryptionKey
|
|
19935
19414
|
);
|
|
@@ -19938,7 +19417,7 @@ async function startProviderAuthorization(opts) {
|
|
|
19938
19417
|
authorizationUrl: buildAuthorizationUrl({
|
|
19939
19418
|
config: opts.config,
|
|
19940
19419
|
clientId: client.clientId,
|
|
19941
|
-
redirectUri: opts.origin +
|
|
19420
|
+
redirectUri: opts.origin + OAUTH_CALLBACK_PATH,
|
|
19942
19421
|
state,
|
|
19943
19422
|
...pkce === void 0 ? {} : { codeChallenge: pkce.challenge }
|
|
19944
19423
|
})
|
|
@@ -19951,7 +19430,7 @@ async function finishProviderAuthorization(opts) {
|
|
|
19951
19430
|
config: opts.config,
|
|
19952
19431
|
fetcher,
|
|
19953
19432
|
now: opts.now,
|
|
19954
|
-
grant: { code: opts.code, redirectUri: opts.origin +
|
|
19433
|
+
grant: { code: opts.code, redirectUri: opts.origin + OAUTH_CALLBACK_PATH },
|
|
19955
19434
|
...opts.codeVerifier === "" ? {} : { codeVerifier: opts.codeVerifier },
|
|
19956
19435
|
...client
|
|
19957
19436
|
});
|
|
@@ -19981,21 +19460,61 @@ async function resolveProviderAccessToken(opts) {
|
|
|
19981
19460
|
return refreshed.accessToken;
|
|
19982
19461
|
}
|
|
19983
19462
|
|
|
19463
|
+
// ../app/src/providers/toolCache.ts
|
|
19464
|
+
var KEY_PREFIX = "toolcache:";
|
|
19465
|
+
function toolCacheKey(path) {
|
|
19466
|
+
return `${KEY_PREFIX}${path}`;
|
|
19467
|
+
}
|
|
19468
|
+
function isCached(v) {
|
|
19469
|
+
return typeof v === "object" && v !== null && Array.isArray(v.tools) && typeof v.fetchedAt === "string";
|
|
19470
|
+
}
|
|
19471
|
+
function isFresh(fetchedAt, ttl, now) {
|
|
19472
|
+
const age = (Date.parse(now) - Date.parse(fetchedAt)) / 1e3;
|
|
19473
|
+
return Number.isFinite(age) && age >= 0 && age < ttl;
|
|
19474
|
+
}
|
|
19475
|
+
async function invalidateToolCache(store, path) {
|
|
19476
|
+
await store.delete(toolCacheKey(path));
|
|
19477
|
+
}
|
|
19478
|
+
function cachedTools(value) {
|
|
19479
|
+
return isCached(value) ? value.tools : null;
|
|
19480
|
+
}
|
|
19481
|
+
async function peekToolCache(store, path) {
|
|
19482
|
+
return cachedTools(await store.get(toolCacheKey(path)));
|
|
19483
|
+
}
|
|
19484
|
+
async function getTools(store, path, fetchList, opts) {
|
|
19485
|
+
if (!opts.refresh) {
|
|
19486
|
+
const raw = await store.get(toolCacheKey(path));
|
|
19487
|
+
if (isCached(raw) && isFresh(raw.fetchedAt, opts.ttl, opts.now)) {
|
|
19488
|
+
return raw.tools;
|
|
19489
|
+
}
|
|
19490
|
+
}
|
|
19491
|
+
await opts.beforeFresh?.();
|
|
19492
|
+
try {
|
|
19493
|
+
const tools = await fetchList();
|
|
19494
|
+
const record2 = { tools, fetchedAt: opts.now };
|
|
19495
|
+
await store.put(toolCacheKey(path), record2);
|
|
19496
|
+
await opts.onFresh?.(tools);
|
|
19497
|
+
return tools;
|
|
19498
|
+
} catch (error) {
|
|
19499
|
+
await opts.onFreshError?.();
|
|
19500
|
+
throw error;
|
|
19501
|
+
}
|
|
19502
|
+
}
|
|
19503
|
+
|
|
19984
19504
|
// ../app/src/toolNodes.ts
|
|
19985
|
-
function upstreamTools(node, provider, deps, refresh, now) {
|
|
19505
|
+
function upstreamTools(node, provider, deps, refresh, now, searchSync) {
|
|
19986
19506
|
if (node.kind === "mcp" || node.kind === "tool") {
|
|
19987
|
-
const sync = isMutableSearchIndex(deps.search) ? new SearchSynchronizer(deps.state, deps.search) : void 0;
|
|
19988
19507
|
let marker;
|
|
19989
19508
|
return getTools(deps.state, node.path, () => provider.list(), {
|
|
19990
19509
|
refresh,
|
|
19991
19510
|
ttl: deps.toolCacheTtlSec ?? TOOL_CACHE_TTL_DEFAULT,
|
|
19992
19511
|
now,
|
|
19993
|
-
...
|
|
19512
|
+
...searchSync === void 0 ? {} : {
|
|
19994
19513
|
beforeFresh: async () => {
|
|
19995
|
-
marker = await
|
|
19514
|
+
marker = await searchSync.markNode(node.path);
|
|
19996
19515
|
},
|
|
19997
|
-
onFreshError: async () => await
|
|
19998
|
-
onFresh: async (tools) => await
|
|
19516
|
+
onFreshError: async () => await searchSync.abort(marker),
|
|
19517
|
+
onFresh: async (tools) => await searchSync.reconcileNodeQuietly(node.path, { marker, tools })
|
|
19999
19518
|
}
|
|
20000
19519
|
});
|
|
20001
19520
|
}
|
|
@@ -20130,11 +19649,11 @@ async function providerFor(node, ctx, deps) {
|
|
|
20130
19649
|
}
|
|
20131
19650
|
throw TBError.unimplemented(`kind '${node.kind}' has no tool provider`);
|
|
20132
19651
|
}
|
|
20133
|
-
async function refreshDynamicSearchNode(node, ctx, deps) {
|
|
19652
|
+
async function refreshDynamicSearchNode(node, ctx, deps, searchSync) {
|
|
20134
19653
|
if (node.kind !== "mcp" && node.kind !== "tool" || deviceToolMarker(node) !== null) return false;
|
|
20135
19654
|
try {
|
|
20136
19655
|
const provider = await providerFor(node, ctx, deps);
|
|
20137
|
-
await upstreamTools(node, provider, deps, true, (/* @__PURE__ */ new Date()).toISOString());
|
|
19656
|
+
await upstreamTools(node, provider, deps, true, (/* @__PURE__ */ new Date()).toISOString(), searchSync);
|
|
20138
19657
|
return true;
|
|
20139
19658
|
} catch {
|
|
20140
19659
|
return false;
|
|
@@ -20149,7 +19668,7 @@ async function refreshDynamicToolCache(node, ctx, deps) {
|
|
|
20149
19668
|
now: (/* @__PURE__ */ new Date()).toISOString()
|
|
20150
19669
|
});
|
|
20151
19670
|
}
|
|
20152
|
-
async function toolHelpModelFor(c, ctx, registry, path, deps) {
|
|
19671
|
+
async function toolHelpModelFor(c, ctx, registry, path, deps, searchSync) {
|
|
20153
19672
|
let resolved;
|
|
20154
19673
|
try {
|
|
20155
19674
|
resolved = await registry.resolve(path);
|
|
@@ -20170,7 +19689,7 @@ async function toolHelpModelFor(c, ctx, registry, path, deps) {
|
|
|
20170
19689
|
}
|
|
20171
19690
|
const provider = await providerFor(node, ctx, deps);
|
|
20172
19691
|
const refresh = c.req.query("refresh") === "1";
|
|
20173
|
-
const raw = await upstreamTools(node, provider, deps, refresh, (/* @__PURE__ */ new Date()).toISOString());
|
|
19692
|
+
const raw = await upstreamTools(node, provider, deps, refresh, (/* @__PURE__ */ new Date()).toISOString(), searchSync);
|
|
20174
19693
|
const { exposed } = virtualizeTools(node.virtualize, raw);
|
|
20175
19694
|
const tool = exposed.find((t) => t.name === rest);
|
|
20176
19695
|
if (tool === void 0) return null;
|
|
@@ -20323,9 +19842,6 @@ async function verifyRefToken(token, secret) {
|
|
|
20323
19842
|
function localContext(deps, node) {
|
|
20324
19843
|
return deps.locals?.context?.(node.path) ?? null;
|
|
20325
19844
|
}
|
|
20326
|
-
function localCapabilities(provider) {
|
|
20327
|
-
return contextCapabilitiesOf(provider);
|
|
20328
|
-
}
|
|
20329
19845
|
function parseS3Credentials(raw, refName) {
|
|
20330
19846
|
try {
|
|
20331
19847
|
const v = JSON.parse(raw);
|
|
@@ -20402,13 +19918,8 @@ async function createContextUploadGrant(node, cfg, deps, input) {
|
|
|
20402
19918
|
uploadGrantTtlSec
|
|
20403
19919
|
}, input);
|
|
20404
19920
|
}
|
|
20405
|
-
|
|
20406
|
-
const
|
|
20407
|
-
const opts = {
|
|
20408
|
-
nsPath: node.path,
|
|
20409
|
-
keyPrefix: contextKeyPrefix(cfg, node.path),
|
|
20410
|
-
readOnly: cfg.readOnly ?? false
|
|
20411
|
-
};
|
|
19921
|
+
function objectProviderOpts(node, keyPrefix, readOnly, deps, requestUrl) {
|
|
19922
|
+
const opts = { nsPath: node.path, keyPrefix, readOnly };
|
|
20412
19923
|
if (deps.refThresholdBytes !== void 0) opts.refThresholdBytes = deps.refThresholdBytes;
|
|
20413
19924
|
if (deps.refTtlSec !== void 0) opts.presignTtlSec = deps.refTtlSec;
|
|
20414
19925
|
const encKey = deps.encryptionKey;
|
|
@@ -20420,7 +19931,17 @@ async function contextProviderFor(node, cfg, deps, requestUrl) {
|
|
|
20420
19931
|
return `${origin}/~ref/${await signRefToken({ p: node.path, k: key, exp }, encKey)}`;
|
|
20421
19932
|
};
|
|
20422
19933
|
}
|
|
20423
|
-
return
|
|
19934
|
+
return opts;
|
|
19935
|
+
}
|
|
19936
|
+
async function contextProviderFor(node, cfg, deps, requestUrl) {
|
|
19937
|
+
const objects = await contextObjectStoreFor(cfg, deps);
|
|
19938
|
+
return createObjectContextProvider(objects, objectProviderOpts(
|
|
19939
|
+
node,
|
|
19940
|
+
contextKeyPrefix(cfg, node.path),
|
|
19941
|
+
cfg.readOnly ?? false,
|
|
19942
|
+
deps,
|
|
19943
|
+
requestUrl
|
|
19944
|
+
));
|
|
20424
19945
|
}
|
|
20425
19946
|
function skillhubKeyPrefix(cfg, nodePath) {
|
|
20426
19947
|
const prefix = cfg.providerConfig?.prefix;
|
|
@@ -20429,29 +19950,13 @@ function skillhubKeyPrefix(cfg, nodePath) {
|
|
|
20429
19950
|
}
|
|
20430
19951
|
async function skillhubProviderFor(node, cfg, deps, requestUrl) {
|
|
20431
19952
|
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);
|
|
19953
|
+
return createSkillhubProvider(objects, objectProviderOpts(
|
|
19954
|
+
node,
|
|
19955
|
+
skillhubKeyPrefix(cfg, node.path),
|
|
19956
|
+
cfg.readOnly ?? false,
|
|
19957
|
+
deps,
|
|
19958
|
+
requestUrl
|
|
19959
|
+
));
|
|
20455
19960
|
}
|
|
20456
19961
|
async function assertContextAlive(node, cfg, registry) {
|
|
20457
19962
|
if (!isContextExpired(node.createdAt, cfg.ttl, Date.now())) return;
|
|
@@ -20522,6 +20027,257 @@ async function assertSkillhubConfig(config, deps) {
|
|
|
20522
20027
|
}
|
|
20523
20028
|
}
|
|
20524
20029
|
|
|
20030
|
+
// ../app/src/search/synchronizer.ts
|
|
20031
|
+
var DIRTY_NODE_PREFIX = "searchdirty:node:";
|
|
20032
|
+
var DIRTY_SUBTREE_PREFIX = "searchdirty:subtree:";
|
|
20033
|
+
function isMutableSearchIndex(value) {
|
|
20034
|
+
if (typeof value !== "object" || value === null) return false;
|
|
20035
|
+
const index = value;
|
|
20036
|
+
return typeof index.replace === "function" && typeof index.remove === "function" && typeof index.removePrefix === "function" && typeof index.rebuild === "function" && typeof index.initialized === "function";
|
|
20037
|
+
}
|
|
20038
|
+
function toolNode(node) {
|
|
20039
|
+
return (node.kind === "mcp" || node.kind === "http" || node.kind === "tool") && node.config?.kind === node.kind;
|
|
20040
|
+
}
|
|
20041
|
+
function httpTools(node) {
|
|
20042
|
+
if (node.config?.kind !== "http") return null;
|
|
20043
|
+
return node.config.tools.map((tool) => {
|
|
20044
|
+
const spec = {
|
|
20045
|
+
description: tool.description,
|
|
20046
|
+
effect: effectFor(tool),
|
|
20047
|
+
name: tool.name
|
|
20048
|
+
};
|
|
20049
|
+
if (tool.inputSchema !== void 0) spec.inputSchema = tool.inputSchema;
|
|
20050
|
+
return spec;
|
|
20051
|
+
});
|
|
20052
|
+
}
|
|
20053
|
+
function deviceTools(node) {
|
|
20054
|
+
if (node.config?.kind !== "tool") return null;
|
|
20055
|
+
const cmds = node.config.providerConfig?.cmds;
|
|
20056
|
+
if (!Array.isArray(cmds)) return null;
|
|
20057
|
+
return cmds;
|
|
20058
|
+
}
|
|
20059
|
+
async function canonicalSearchTools(state, nodes) {
|
|
20060
|
+
const unique = new Map(nodes.map((node) => [node.path, node]));
|
|
20061
|
+
const dynamicPaths = [...unique.values()].filter((node) => node.kind !== "http" && deviceTools(node) === null).map((node) => node.path);
|
|
20062
|
+
const cached = dynamicPaths.length === 0 ? /* @__PURE__ */ new Map() : await state.getMany(dynamicPaths.map(toolCacheKey));
|
|
20063
|
+
const result = /* @__PURE__ */ new Map();
|
|
20064
|
+
for (const node of unique.values()) {
|
|
20065
|
+
const tools = node.kind === "http" ? httpTools(node) : deviceTools(node) ?? cachedTools(cached.get(toolCacheKey(node.path)));
|
|
20066
|
+
if (tools !== null) result.set(node.path, tools);
|
|
20067
|
+
}
|
|
20068
|
+
return result;
|
|
20069
|
+
}
|
|
20070
|
+
function documentsFor(path, tools, feedback) {
|
|
20071
|
+
return tools.map((tool) => ({ path, tool, ...feedback === "" ? {} : { feedback } }));
|
|
20072
|
+
}
|
|
20073
|
+
var SearchSynchronizer = class {
|
|
20074
|
+
constructor(state, search) {
|
|
20075
|
+
this.state = state;
|
|
20076
|
+
this.search = search;
|
|
20077
|
+
this.registry = new NodeRegistryStore(state);
|
|
20078
|
+
}
|
|
20079
|
+
state;
|
|
20080
|
+
search;
|
|
20081
|
+
registry;
|
|
20082
|
+
seedPromise;
|
|
20083
|
+
async mark(path, kind) {
|
|
20084
|
+
const canonical = normalizeToolSearchPath(path);
|
|
20085
|
+
const marker = {
|
|
20086
|
+
createdAt: Date.now(),
|
|
20087
|
+
// Marker 只是审计提示,正确性由每次 canonical audit 保证。每类固定一个 key,
|
|
20088
|
+
// 避免 overflow/device hello 用 UUID marker 把 KV 与扫描成本无界放大。
|
|
20089
|
+
key: `${kind === "node" ? DIRTY_NODE_PREFIX : DIRTY_SUBTREE_PREFIX}pending`,
|
|
20090
|
+
kind,
|
|
20091
|
+
path: canonical
|
|
20092
|
+
};
|
|
20093
|
+
await this.state.put(marker.key, marker);
|
|
20094
|
+
return marker;
|
|
20095
|
+
}
|
|
20096
|
+
async markNode(path) {
|
|
20097
|
+
return await this.mark(path, "node");
|
|
20098
|
+
}
|
|
20099
|
+
async markSubtree(path) {
|
|
20100
|
+
return await this.mark(path, "subtree");
|
|
20101
|
+
}
|
|
20102
|
+
async abort(marker) {
|
|
20103
|
+
if (marker !== void 0 && marker.expectedDigest === void 0) {
|
|
20104
|
+
await this.state.delete(marker.key);
|
|
20105
|
+
}
|
|
20106
|
+
}
|
|
20107
|
+
async dirtyMarkers() {
|
|
20108
|
+
const markers = [];
|
|
20109
|
+
for (const prefix of [DIRTY_NODE_PREFIX, DIRTY_SUBTREE_PREFIX]) {
|
|
20110
|
+
let cursor;
|
|
20111
|
+
do {
|
|
20112
|
+
const page = await this.state.list(prefix, {
|
|
20113
|
+
limit: 200,
|
|
20114
|
+
...cursor === void 0 ? {} : { cursor }
|
|
20115
|
+
});
|
|
20116
|
+
for (const item of page.items) {
|
|
20117
|
+
const marker = item.value;
|
|
20118
|
+
if (typeof marker.createdAt === "number" && marker.key === item.key && (marker.kind === "node" || marker.kind === "subtree") && typeof marker.path === "string") {
|
|
20119
|
+
markers.push(marker);
|
|
20120
|
+
}
|
|
20121
|
+
}
|
|
20122
|
+
cursor = page.cursor;
|
|
20123
|
+
} while (cursor !== void 0);
|
|
20124
|
+
}
|
|
20125
|
+
return markers;
|
|
20126
|
+
}
|
|
20127
|
+
async commitMarker(marker, expectedDigest) {
|
|
20128
|
+
if (marker === void 0) return;
|
|
20129
|
+
const committed = {
|
|
20130
|
+
...marker,
|
|
20131
|
+
committedAt: Date.now(),
|
|
20132
|
+
expectedDigest
|
|
20133
|
+
};
|
|
20134
|
+
await this.state.put(marker.key, committed);
|
|
20135
|
+
Object.assign(marker, committed);
|
|
20136
|
+
}
|
|
20137
|
+
async finishMarker(marker) {
|
|
20138
|
+
if (marker !== void 0) await this.state.delete(marker.key);
|
|
20139
|
+
}
|
|
20140
|
+
async feedbackText(path, snapshot) {
|
|
20141
|
+
if (snapshot !== void 0) return selectFeedbackSearchText(snapshot);
|
|
20142
|
+
return selectFeedbackSearchText(parseFeedbackEntries(await this.state.get(KEY_FEEDBACK + path)));
|
|
20143
|
+
}
|
|
20144
|
+
async rawTools(node, override) {
|
|
20145
|
+
if (override !== void 0) return [...override];
|
|
20146
|
+
if (node.kind === "http") return httpTools(node);
|
|
20147
|
+
const device = deviceTools(node);
|
|
20148
|
+
if (device !== null) return device;
|
|
20149
|
+
if (node.kind === "mcp" || node.kind === "tool") {
|
|
20150
|
+
return await peekToolCache(this.state, node.path);
|
|
20151
|
+
}
|
|
20152
|
+
return null;
|
|
20153
|
+
}
|
|
20154
|
+
indexableRecords(path, tools, feedback) {
|
|
20155
|
+
try {
|
|
20156
|
+
return serializeToolSearchSnapshot(path, tools, feedback);
|
|
20157
|
+
} catch (error) {
|
|
20158
|
+
if (isTBError(error)) return null;
|
|
20159
|
+
throw error;
|
|
20160
|
+
}
|
|
20161
|
+
}
|
|
20162
|
+
async reconcileNode(path, opts = {}) {
|
|
20163
|
+
const canonical = normalizeToolSearchPath(path);
|
|
20164
|
+
const node = await this.registry.get(canonical).catch(() => null);
|
|
20165
|
+
if (node === null || !toolNode(node)) {
|
|
20166
|
+
await this.commitMarker(opts.marker, null);
|
|
20167
|
+
await this.search.remove(canonical);
|
|
20168
|
+
await this.finishMarker(opts.marker);
|
|
20169
|
+
return;
|
|
20170
|
+
}
|
|
20171
|
+
const tools = await this.rawTools(node, opts.tools);
|
|
20172
|
+
if (tools === null) {
|
|
20173
|
+
await this.search.remove(canonical);
|
|
20174
|
+
await this.finishMarker(opts.marker);
|
|
20175
|
+
return;
|
|
20176
|
+
}
|
|
20177
|
+
if (tools.length === 0) {
|
|
20178
|
+
await this.commitMarker(opts.marker, null);
|
|
20179
|
+
await this.search.remove(canonical);
|
|
20180
|
+
await this.finishMarker(opts.marker);
|
|
20181
|
+
return;
|
|
20182
|
+
}
|
|
20183
|
+
const root = await this.registry.rootSnapshot(TOOL_SEARCH_AUDIT_NODE_LIMIT);
|
|
20184
|
+
if (root.truncated) {
|
|
20185
|
+
await this.finishMarker(opts.marker);
|
|
20186
|
+
return;
|
|
20187
|
+
}
|
|
20188
|
+
const feedback = await this.feedbackText(canonical, opts.feedback);
|
|
20189
|
+
const records = this.indexableRecords(canonical, tools, feedback);
|
|
20190
|
+
if (records === null) {
|
|
20191
|
+
await this.commitMarker(opts.marker, null);
|
|
20192
|
+
await this.search.remove(canonical);
|
|
20193
|
+
await this.finishMarker(opts.marker);
|
|
20194
|
+
return;
|
|
20195
|
+
}
|
|
20196
|
+
const digest = records.length === 0 ? null : toolSearchSnapshotDigest(records);
|
|
20197
|
+
await this.commitMarker(opts.marker, digest);
|
|
20198
|
+
await this.search.replace(canonical, tools, {
|
|
20199
|
+
feedback
|
|
20200
|
+
});
|
|
20201
|
+
await this.finishMarker(opts.marker);
|
|
20202
|
+
}
|
|
20203
|
+
/** mutation 热路径使用:canonical 已成功时,派生同步失败只保留 marker。 */
|
|
20204
|
+
async reconcileNodeQuietly(path, opts = {}) {
|
|
20205
|
+
try {
|
|
20206
|
+
await this.reconcileNode(path, opts);
|
|
20207
|
+
} catch {
|
|
20208
|
+
}
|
|
20209
|
+
}
|
|
20210
|
+
async rebuildAll(markers = [], opts = {}) {
|
|
20211
|
+
const snapshot = opts.canonicalNodes === void 0 ? await this.registry.rootSnapshot(TOOL_SEARCH_AUDIT_NODE_LIMIT) : { items: [...opts.canonicalNodes], truncated: false };
|
|
20212
|
+
if (snapshot.truncated) return false;
|
|
20213
|
+
const nodes = snapshot.items.filter(toolNode);
|
|
20214
|
+
const authoritativeEmpty = new Set(opts.authoritativeEmpty ?? []);
|
|
20215
|
+
const keys = nodes.flatMap((node) => [KEY_FEEDBACK + node.path, toolCacheKey(node.path)]);
|
|
20216
|
+
const values = await this.state.getMany(keys);
|
|
20217
|
+
const documents = [];
|
|
20218
|
+
for (const node of nodes) {
|
|
20219
|
+
if (authoritativeEmpty.has(node.path)) continue;
|
|
20220
|
+
let tools;
|
|
20221
|
+
if (node.kind === "http") {
|
|
20222
|
+
tools = httpTools(node);
|
|
20223
|
+
} else {
|
|
20224
|
+
tools = deviceTools(node) ?? cachedTools(values.get(toolCacheKey(node.path)));
|
|
20225
|
+
}
|
|
20226
|
+
if (tools === null) continue;
|
|
20227
|
+
const feedback = selectFeedbackSearchText(
|
|
20228
|
+
parseFeedbackEntries(values.get(KEY_FEEDBACK + node.path))
|
|
20229
|
+
);
|
|
20230
|
+
if (this.indexableRecords(node.path, tools, feedback) === null) continue;
|
|
20231
|
+
documents.push(...documentsFor(node.path, tools, feedback));
|
|
20232
|
+
}
|
|
20233
|
+
await this.search.rebuild(documents);
|
|
20234
|
+
await Promise.all(markers.map(async (marker) => {
|
|
20235
|
+
await this.state.delete(marker.key).catch(() => {
|
|
20236
|
+
});
|
|
20237
|
+
}));
|
|
20238
|
+
return true;
|
|
20239
|
+
}
|
|
20240
|
+
/** 每次搜索前审计 canonical 快照;同快照 rebuild 不 bump revision。 */
|
|
20241
|
+
async ensureReady() {
|
|
20242
|
+
const snapshot = await this.registry.rootSnapshot(TOOL_SEARCH_AUDIT_NODE_LIMIT);
|
|
20243
|
+
if (snapshot.truncated) {
|
|
20244
|
+
if (await this.search.initialized()) return;
|
|
20245
|
+
throw new TBError("rate_limited", "registry \u8D85\u51FA\u5DE5\u5177\u641C\u7D22 canonical audit \u5BB9\u91CF");
|
|
20246
|
+
}
|
|
20247
|
+
const dirty = await this.dirtyMarkers();
|
|
20248
|
+
await this.rebuildAll(dirty, { canonicalNodes: snapshot.items });
|
|
20249
|
+
}
|
|
20250
|
+
/** 首次 registry/device mutation 前建立 last-known-good,避免并发溢出抢在首次 seed 前。 */
|
|
20251
|
+
async ensureSeeded() {
|
|
20252
|
+
if (await this.search.initialized()) return;
|
|
20253
|
+
if (this.seedPromise === void 0) {
|
|
20254
|
+
this.seedPromise = (async () => {
|
|
20255
|
+
if (!await this.rebuildAll()) {
|
|
20256
|
+
throw new TBError("rate_limited", "registry \u8D85\u51FA\u5DE5\u5177\u641C\u7D22 canonical audit \u5BB9\u91CF");
|
|
20257
|
+
}
|
|
20258
|
+
})();
|
|
20259
|
+
}
|
|
20260
|
+
try {
|
|
20261
|
+
await this.seedPromise;
|
|
20262
|
+
} finally {
|
|
20263
|
+
this.seedPromise = void 0;
|
|
20264
|
+
}
|
|
20265
|
+
}
|
|
20266
|
+
async removeSubtreeQuietly(path, marker) {
|
|
20267
|
+
try {
|
|
20268
|
+
const canonical = normalizeToolSearchPath(path);
|
|
20269
|
+
if ((await this.registry.subtree(canonical)).length === 0) {
|
|
20270
|
+
await this.commitMarker(marker, null);
|
|
20271
|
+
await this.search.removePrefix(canonical);
|
|
20272
|
+
await this.finishMarker(marker);
|
|
20273
|
+
} else {
|
|
20274
|
+
await this.rebuildAll(marker === void 0 ? [] : [marker]);
|
|
20275
|
+
}
|
|
20276
|
+
} catch {
|
|
20277
|
+
}
|
|
20278
|
+
}
|
|
20279
|
+
};
|
|
20280
|
+
|
|
20525
20281
|
// ../app/src/deviceHello.ts
|
|
20526
20282
|
function assertDeviceId(deviceId) {
|
|
20527
20283
|
if (!/^[A-Za-z0-9._-]+$/.test(deviceId)) {
|
|
@@ -20561,6 +20317,7 @@ function customNodeInput(mountPath, deviceId, raw) {
|
|
|
20561
20317
|
return input;
|
|
20562
20318
|
}
|
|
20563
20319
|
function nodesForHello(mountPath, deviceId, expose) {
|
|
20320
|
+
const rawTargets = [mountPath];
|
|
20564
20321
|
const nodes = [
|
|
20565
20322
|
{
|
|
20566
20323
|
path: mountPath,
|
|
@@ -20569,6 +20326,7 @@ function nodesForHello(mountPath, deviceId, expose) {
|
|
|
20569
20326
|
}
|
|
20570
20327
|
];
|
|
20571
20328
|
if (expose.shell !== void 0) {
|
|
20329
|
+
rawTargets.push(joinTreePath(mountPath, "shell"));
|
|
20572
20330
|
nodes.push({
|
|
20573
20331
|
path: joinTreePath(mountPath, "shell"),
|
|
20574
20332
|
kind: "device",
|
|
@@ -20577,6 +20335,7 @@ function nodesForHello(mountPath, deviceId, expose) {
|
|
|
20577
20335
|
});
|
|
20578
20336
|
}
|
|
20579
20337
|
if (expose.fs !== void 0) {
|
|
20338
|
+
rawTargets.push(joinTreePath(mountPath, "fs"));
|
|
20580
20339
|
nodes.push({
|
|
20581
20340
|
path: joinTreePath(mountPath, "fs"),
|
|
20582
20341
|
kind: "context",
|
|
@@ -20590,11 +20349,13 @@ function nodesForHello(mountPath, deviceId, expose) {
|
|
|
20590
20349
|
});
|
|
20591
20350
|
}
|
|
20592
20351
|
for (const raw of expose.nodes ?? []) {
|
|
20352
|
+
rawTargets.push(joinTreePath(mountPath, raw.path));
|
|
20593
20353
|
nodes.push(customNodeInput(mountPath, deviceId, raw));
|
|
20594
20354
|
}
|
|
20355
|
+
assertNoCollision(rawTargets, `\u8BBE\u5907 '${deviceId}' \u7684 expose \u8DEF\u5F84`);
|
|
20595
20356
|
return nodes;
|
|
20596
20357
|
}
|
|
20597
|
-
async function assertRegisterPath(registry, ctx, targetPath) {
|
|
20358
|
+
async function assertRegisterPath(registry, ctx, targetPath, reservedRoots) {
|
|
20598
20359
|
if (!check(ctx, targetPath, "register").allow) {
|
|
20599
20360
|
throw new TBError("permission_denied", `no scope grants 'register' on '${targetPath}'`);
|
|
20600
20361
|
}
|
|
@@ -20612,7 +20373,8 @@ async function assertRegisterPath(registry, ctx, targetPath) {
|
|
|
20612
20373
|
},
|
|
20613
20374
|
targetPath,
|
|
20614
20375
|
action: "write",
|
|
20615
|
-
existing
|
|
20376
|
+
existing,
|
|
20377
|
+
...reservedRoots !== void 0 ? { reservedRoots } : {}
|
|
20616
20378
|
});
|
|
20617
20379
|
if (!res.allow) throw res.error;
|
|
20618
20380
|
}
|
|
@@ -20633,7 +20395,7 @@ async function processDeviceHello(opts) {
|
|
|
20633
20395
|
const inputs = nodesForHello(mountPath, hello.deviceId, hello.expose);
|
|
20634
20396
|
const registry = new NodeRegistryStore(store);
|
|
20635
20397
|
for (const input of inputs) {
|
|
20636
|
-
await assertRegisterPath(registry, authCtx, input.path);
|
|
20398
|
+
await assertRegisterPath(registry, authCtx, input.path, opts.reservedRoots);
|
|
20637
20399
|
assertSecretRefUse(authCtx.scopes, input.config);
|
|
20638
20400
|
}
|
|
20639
20401
|
const now = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -20662,15 +20424,43 @@ async function processDeviceHello(opts) {
|
|
|
20662
20424
|
return { mountPath, keyId: authCtx.keyId, searchIndexed };
|
|
20663
20425
|
}
|
|
20664
20426
|
|
|
20665
|
-
// ../app/src/
|
|
20666
|
-
|
|
20667
|
-
|
|
20668
|
-
|
|
20669
|
-
|
|
20670
|
-
|
|
20427
|
+
// ../app/src/deviceLifecycle.ts
|
|
20428
|
+
async function reverifyDeviceAuthority(opts) {
|
|
20429
|
+
const authCtx = await identify(opts.store, opts.authorization, (/* @__PURE__ */ new Date()).toISOString());
|
|
20430
|
+
if (authCtx === null || authCtx.keyId !== opts.keyId) return false;
|
|
20431
|
+
return checkRegisterPath({
|
|
20432
|
+
sk: {
|
|
20433
|
+
scopes: authCtx.scopes,
|
|
20434
|
+
id: authCtx.keyId,
|
|
20435
|
+
...authCtx.registerPaths !== void 0 ? { registerPaths: authCtx.registerPaths } : {}
|
|
20436
|
+
},
|
|
20437
|
+
targetPath: opts.mountPath,
|
|
20438
|
+
action: "write",
|
|
20439
|
+
existing: null,
|
|
20440
|
+
...opts.reservedRoots !== void 0 ? { reservedRoots: opts.reservedRoots } : {}
|
|
20441
|
+
}).allow;
|
|
20442
|
+
}
|
|
20443
|
+
async function markDeviceDisconnected(opts) {
|
|
20444
|
+
try {
|
|
20445
|
+
await opts.registry.setOnline(opts.mountPath, false, opts.now);
|
|
20446
|
+
} catch {
|
|
20671
20447
|
}
|
|
20672
|
-
return actual;
|
|
20673
20448
|
}
|
|
20449
|
+
async function reclaimDeviceSubtree(opts) {
|
|
20450
|
+
const searchSync = opts.search === void 0 ? void 0 : new SearchSynchronizer(opts.state, opts.search);
|
|
20451
|
+
const marker = await searchSync?.markSubtree(opts.mountPath);
|
|
20452
|
+
try {
|
|
20453
|
+
await opts.registry.deleteSubtree(opts.mountPath);
|
|
20454
|
+
} catch {
|
|
20455
|
+
}
|
|
20456
|
+
await searchSync?.removeSubtreeQuietly(opts.mountPath, marker);
|
|
20457
|
+
}
|
|
20458
|
+
function deviceSearchCapacityWarning(deviceId, mountPath) {
|
|
20459
|
+
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.`;
|
|
20460
|
+
}
|
|
20461
|
+
|
|
20462
|
+
// ../app/src/deviceMailbox.ts
|
|
20463
|
+
var KEY_DEVICE_MAILBOX_CLEANUP_PROGRESS = "sys:device-mailbox-cleanup-progress:v1";
|
|
20674
20464
|
function parseProgress(value) {
|
|
20675
20465
|
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
20466
|
return value;
|
|
@@ -20684,39 +20474,27 @@ async function cleanupDeviceMailbox(deps, opts = {}) {
|
|
|
20684
20474
|
throw new TBError("unavailable", "device mailbox cleanup requires StateStore.compareAndSwap");
|
|
20685
20475
|
}
|
|
20686
20476
|
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;
|
|
20477
|
+
const maxPages = Math.min(positiveInt(opts.maxPages, 8, "maxPages"), 64);
|
|
20478
|
+
const limit = Math.min(positiveInt(opts.limit, 200, "limit"), 200);
|
|
20692
20479
|
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
|
-
}
|
|
20480
|
+
const cursor = await runCursorCleanup({
|
|
20481
|
+
// {cursor,revision} 即持久形状,原样落库。
|
|
20482
|
+
encodeProgress: (progress) => progress,
|
|
20483
|
+
maxPages,
|
|
20484
|
+
parseProgress,
|
|
20485
|
+
progressKey: KEY_DEVICE_MAILBOX_CLEANUP_PROGRESS,
|
|
20486
|
+
runPage: async (pageCursor) => {
|
|
20487
|
+
const page = await mailbox.cleanup({
|
|
20488
|
+
limit,
|
|
20489
|
+
...pageCursor === void 0 ? {} : { cursor: pageCursor }
|
|
20490
|
+
});
|
|
20491
|
+
aggregate.deleted += page.deleted;
|
|
20492
|
+
aggregate.expired += page.expired;
|
|
20493
|
+
aggregate.scanned += page.scanned;
|
|
20494
|
+
return page.cursor;
|
|
20495
|
+
},
|
|
20496
|
+
state: deps.state
|
|
20497
|
+
});
|
|
20720
20498
|
return { ...aggregate, ...cursor === void 0 ? {} : { cursor } };
|
|
20721
20499
|
}
|
|
20722
20500
|
|
|
@@ -36622,7 +36400,7 @@ async function readRemoteResponseBody(response, maxBytes, signal) {
|
|
|
36622
36400
|
chunks.push(result.value);
|
|
36623
36401
|
}
|
|
36624
36402
|
} catch (error) {
|
|
36625
|
-
if (error
|
|
36403
|
+
if (isTBError(error)) throw error;
|
|
36626
36404
|
if (isAbortError(error, signal)) throw remoteAbortError();
|
|
36627
36405
|
throw normalizeUpstreamError({
|
|
36628
36406
|
kind: "network",
|
|
@@ -37320,6 +37098,99 @@ async function handleFeedbackDelete(c, env) {
|
|
|
37320
37098
|
return feedbackJson(feedbackRemoveResponseSchema, { ok: true });
|
|
37321
37099
|
}
|
|
37322
37100
|
|
|
37101
|
+
// ../app/src/responses.ts
|
|
37102
|
+
function tbErrorResponse(err) {
|
|
37103
|
+
return new Response(JSON.stringify(tbErrorBodySchema.parse(err.toJSON())), {
|
|
37104
|
+
status: err.httpStatus,
|
|
37105
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
37106
|
+
});
|
|
37107
|
+
}
|
|
37108
|
+
function withSecurityHeaders(res) {
|
|
37109
|
+
if (res.status === 101 || res.webSocket != null) return res;
|
|
37110
|
+
const apply = (headers) => {
|
|
37111
|
+
if (!headers.has("content-security-policy")) {
|
|
37112
|
+
headers.set(
|
|
37113
|
+
"content-security-policy",
|
|
37114
|
+
"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'"
|
|
37115
|
+
);
|
|
37116
|
+
}
|
|
37117
|
+
headers.set("x-content-type-options", "nosniff");
|
|
37118
|
+
headers.set("x-frame-options", "DENY");
|
|
37119
|
+
headers.set("referrer-policy", "no-referrer");
|
|
37120
|
+
};
|
|
37121
|
+
try {
|
|
37122
|
+
apply(res.headers);
|
|
37123
|
+
return res;
|
|
37124
|
+
} catch {
|
|
37125
|
+
const headers = new Headers(res.headers);
|
|
37126
|
+
apply(headers);
|
|
37127
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
|
|
37128
|
+
}
|
|
37129
|
+
}
|
|
37130
|
+
async function runHandler(fn) {
|
|
37131
|
+
try {
|
|
37132
|
+
return await fn();
|
|
37133
|
+
} catch (err) {
|
|
37134
|
+
if (isTBError(err)) return tbErrorResponse(err);
|
|
37135
|
+
return tbErrorResponse(new TBError("internal", "internal error"));
|
|
37136
|
+
}
|
|
37137
|
+
}
|
|
37138
|
+
function renderHelp(model, rep) {
|
|
37139
|
+
if (rep === "json") {
|
|
37140
|
+
return new Response(JSON.stringify(helpJsonSchema.parse(renderHelpJson(model))), {
|
|
37141
|
+
headers: { "content-type": contentTypeFor("json") }
|
|
37142
|
+
});
|
|
37143
|
+
}
|
|
37144
|
+
if (rep === "markdown") {
|
|
37145
|
+
return new Response(renderHelpMarkdown(model), {
|
|
37146
|
+
headers: { "content-type": contentTypeFor("markdown") }
|
|
37147
|
+
});
|
|
37148
|
+
}
|
|
37149
|
+
return new Response(renderHelpDsl(model), {
|
|
37150
|
+
headers: { "content-type": contentTypeFor("dsl") }
|
|
37151
|
+
});
|
|
37152
|
+
}
|
|
37153
|
+
async function enrichHelp(model, path, store) {
|
|
37154
|
+
try {
|
|
37155
|
+
const [annotation, feedback] = await Promise.all([
|
|
37156
|
+
new AnnotationStore(store).get(path),
|
|
37157
|
+
path === "" ? Promise.resolve([]) : new FeedbackStore(store).helpItems(path)
|
|
37158
|
+
]);
|
|
37159
|
+
return {
|
|
37160
|
+
...model,
|
|
37161
|
+
...annotation !== null ? { note: annotation.text } : {},
|
|
37162
|
+
...feedback.length > 0 ? { feedback } : {}
|
|
37163
|
+
};
|
|
37164
|
+
} catch {
|
|
37165
|
+
return model;
|
|
37166
|
+
}
|
|
37167
|
+
}
|
|
37168
|
+
function renderResult(value, rep) {
|
|
37169
|
+
const json = JSON.stringify(value ?? null);
|
|
37170
|
+
if (rep === "json") {
|
|
37171
|
+
return new Response(json, { headers: { "content-type": contentTypeFor("json") } });
|
|
37172
|
+
}
|
|
37173
|
+
return new Response(`\`\`\`json
|
|
37174
|
+
${json}
|
|
37175
|
+
\`\`\`
|
|
37176
|
+
`, {
|
|
37177
|
+
headers: { "content-type": contentTypeFor("markdown") }
|
|
37178
|
+
});
|
|
37179
|
+
}
|
|
37180
|
+
function renderTreeDsl(tree) {
|
|
37181
|
+
const lines = [];
|
|
37182
|
+
const walk = (n, depth) => {
|
|
37183
|
+
const indent = " ".repeat(depth);
|
|
37184
|
+
const label = n.path === "" ? "/" : n.path;
|
|
37185
|
+
const trunc = n.truncated ? " \u2026" : "";
|
|
37186
|
+
lines.push(`${indent}${label} [${n.kind}] ${n.description}${trunc}`);
|
|
37187
|
+
for (const child of n.children ?? []) walk(child, depth + 1);
|
|
37188
|
+
};
|
|
37189
|
+
walk(tree, 0);
|
|
37190
|
+
return `${lines.join("\n")}
|
|
37191
|
+
`;
|
|
37192
|
+
}
|
|
37193
|
+
|
|
37323
37194
|
// ../app/src/routes/deviceMailbox.ts
|
|
37324
37195
|
var DEVICE_MAILBOX_IDEMPOTENCY_HEADER = "x-tb-idempotency-key";
|
|
37325
37196
|
var DEVICE_MAILBOX_HTTP_BODY_BYTES = 272 * 1024;
|
|
@@ -37356,7 +37227,7 @@ async function mailboxJsonObject(c, opts = {}) {
|
|
|
37356
37227
|
}
|
|
37357
37228
|
raw += decoder.decode();
|
|
37358
37229
|
} catch (cause) {
|
|
37359
|
-
if (cause
|
|
37230
|
+
if (isTBError(cause)) throw cause;
|
|
37360
37231
|
throw invalidRequest("body must be valid UTF-8 JSON");
|
|
37361
37232
|
}
|
|
37362
37233
|
}
|
|
@@ -37395,7 +37266,7 @@ function canManage(ctx, operation) {
|
|
|
37395
37266
|
function assertCanManage(ctx, operation) {
|
|
37396
37267
|
if (!canManage(ctx, operation)) throw TBError.notFound("device operation not found");
|
|
37397
37268
|
}
|
|
37398
|
-
async function assertCurrentDeviceCredential(c, target) {
|
|
37269
|
+
async function assertCurrentDeviceCredential(c, target, reservedRoots) {
|
|
37399
37270
|
const ctx = c.get("ctx");
|
|
37400
37271
|
if (ctx.keyId !== target.deviceKeyId) {
|
|
37401
37272
|
throw new TBError("permission_denied", "device credential does not own this operation");
|
|
@@ -37414,7 +37285,8 @@ async function assertCurrentDeviceCredential(c, target) {
|
|
|
37414
37285
|
scopes: ctx.scopes,
|
|
37415
37286
|
...ctx.registerPaths === void 0 ? {} : { registerPaths: ctx.registerPaths }
|
|
37416
37287
|
},
|
|
37417
|
-
targetPath: target.mountPath
|
|
37288
|
+
targetPath: target.mountPath,
|
|
37289
|
+
...reservedRoots === void 0 ? {} : { reservedRoots }
|
|
37418
37290
|
});
|
|
37419
37291
|
if (!allowed.allow) throw allowed.error;
|
|
37420
37292
|
}
|
|
@@ -37438,212 +37310,118 @@ async function enqueueDeviceCommand(c, env, input) {
|
|
|
37438
37310
|
response.headers.set("x-tb-delivery", "mailbox");
|
|
37439
37311
|
return response;
|
|
37440
37312
|
}
|
|
37441
|
-
async function
|
|
37442
|
-
const
|
|
37443
|
-
const
|
|
37313
|
+
async function getOperation(c, env) {
|
|
37314
|
+
const input = parseBody(deviceOperationIdentityRequestSchema, await mailboxJsonObject(c));
|
|
37315
|
+
const result = await env.mailbox().get(input.deviceId, input.operationId);
|
|
37316
|
+
assertCanManage(c.get("ctx"), result);
|
|
37317
|
+
return c.json(deviceOperationDetailSchema.parse(result));
|
|
37318
|
+
}
|
|
37319
|
+
async function listOperations(c, env) {
|
|
37320
|
+
const input = parseBody(deviceOperationListRequestSchema, await mailboxJsonObject(c));
|
|
37444
37321
|
const mailbox = env.mailbox();
|
|
37445
37322
|
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) {
|
|
37323
|
+
const limit = Math.min(
|
|
37324
|
+
input.opts?.limit ?? DEVICE_MAILBOX_LIST_LIMIT_DEFAULT,
|
|
37325
|
+
DEVICE_MAILBOX_LIST_LIMIT_MAX
|
|
37326
|
+
);
|
|
37327
|
+
let cursor = input.opts?.cursor;
|
|
37328
|
+
const items = [];
|
|
37329
|
+
while (items.length < limit) {
|
|
37330
|
+
const previous = cursor;
|
|
37331
|
+
const page = await mailbox.list({
|
|
37332
|
+
deviceId: input.deviceId,
|
|
37333
|
+
limit: limit - items.length,
|
|
37334
|
+
...cursor === void 0 ? {} : { cursor },
|
|
37335
|
+
...input.opts?.states === void 0 ? {} : { states: input.opts.states }
|
|
37336
|
+
});
|
|
37337
|
+
items.push(...page.items.filter((item) => canManage(ctx, item)));
|
|
37338
|
+
cursor = page.cursor;
|
|
37339
|
+
if (cursor === void 0) {
|
|
37479
37340
|
return c.json(deviceOperationListResponseSchema.parse({ items }));
|
|
37480
37341
|
}
|
|
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
|
-
}
|
|
37342
|
+
if (cursor === previous) {
|
|
37343
|
+
throw new TBError("internal", "device mailbox list cursor did not advance");
|
|
37497
37344
|
}
|
|
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
37345
|
}
|
|
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
|
-
})));
|
|
37346
|
+
const resumeCursor = cursor;
|
|
37347
|
+
if (resumeCursor === void 0) {
|
|
37348
|
+
return c.json(deviceOperationListResponseSchema.parse({ items }));
|
|
37523
37349
|
}
|
|
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({
|
|
37350
|
+
let probeCursor = resumeCursor;
|
|
37351
|
+
while (probeCursor !== void 0) {
|
|
37352
|
+
const previous = probeCursor;
|
|
37353
|
+
const page = await mailbox.list({
|
|
37546
37354
|
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
|
-
);
|
|
37355
|
+
cursor: probeCursor,
|
|
37356
|
+
limit: DEVICE_MAILBOX_LIST_LIMIT_MAX,
|
|
37357
|
+
...input.opts?.states === void 0 ? {} : { states: input.opts.states }
|
|
37358
|
+
});
|
|
37359
|
+
if (page.items.some((item) => canManage(ctx, item))) {
|
|
37360
|
+
return c.json(deviceOperationListResponseSchema.parse({ items, cursor: resumeCursor }));
|
|
37361
|
+
}
|
|
37362
|
+
probeCursor = page.cursor;
|
|
37363
|
+
if (probeCursor === previous) {
|
|
37364
|
+
throw new TBError("internal", "device mailbox list cursor did not advance");
|
|
37571
37365
|
}
|
|
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
37366
|
}
|
|
37367
|
+
return c.json(deviceOperationListResponseSchema.parse({ items }));
|
|
37584
37368
|
}
|
|
37585
|
-
async function
|
|
37586
|
-
|
|
37587
|
-
|
|
37588
|
-
|
|
37589
|
-
|
|
37590
|
-
|
|
37591
|
-
|
|
37369
|
+
async function cancelOperation(c, env) {
|
|
37370
|
+
const input = parseBody(deviceOperationIdentityRequestSchema, await mailboxJsonObject(c));
|
|
37371
|
+
const mailbox = env.mailbox();
|
|
37372
|
+
const current = await mailbox.get(input.deviceId, input.operationId);
|
|
37373
|
+
assertCanManage(c.get("ctx"), current);
|
|
37374
|
+
return c.json(deviceOperationDetailSchema.parse(
|
|
37375
|
+
await mailbox.cancel(input.deviceId, input.operationId)
|
|
37376
|
+
));
|
|
37377
|
+
}
|
|
37378
|
+
function deviceAuthorize(c, env) {
|
|
37379
|
+
return async (target) => await assertCurrentDeviceCredential(c, target, env.deps.reservedRoots);
|
|
37380
|
+
}
|
|
37381
|
+
async function claimOperations(c, env) {
|
|
37382
|
+
const input = parseBody(deviceOperationClaimRequestSchema, await mailboxJsonObject(c));
|
|
37383
|
+
return c.json(deviceOperationClaimResponseSchema.parse(await env.mailbox().claim({
|
|
37384
|
+
...input,
|
|
37385
|
+
deviceKeyId: c.get("ctx").keyId,
|
|
37386
|
+
authorize: deviceAuthorize(c, env)
|
|
37387
|
+
})));
|
|
37592
37388
|
}
|
|
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
|
-
});
|
|
37389
|
+
async function renewLeases(c, env) {
|
|
37390
|
+
const input = parseBody(deviceOperationRenewRequestSchema, await mailboxJsonObject(c));
|
|
37391
|
+
return c.json(deviceOperationRenewResponseSchema.parse(await env.mailbox().renew({
|
|
37392
|
+
...input,
|
|
37393
|
+
deviceKeyId: c.get("ctx").keyId,
|
|
37394
|
+
authorize: deviceAuthorize(c, env)
|
|
37395
|
+
})));
|
|
37607
37396
|
}
|
|
37608
|
-
async function
|
|
37609
|
-
|
|
37610
|
-
|
|
37611
|
-
|
|
37612
|
-
|
|
37613
|
-
|
|
37614
|
-
|
|
37615
|
-
|
|
37616
|
-
...
|
|
37617
|
-
...feedback.length > 0 ? { feedback } : {}
|
|
37397
|
+
async function completeOperation(c, env) {
|
|
37398
|
+
const input = parseBody(deviceOperationCompleteRequestSchema, await mailboxJsonObject(c));
|
|
37399
|
+
let completion;
|
|
37400
|
+
if (input.outcome === "succeeded") {
|
|
37401
|
+
completion = { outcome: "succeeded", result: input.result };
|
|
37402
|
+
} else if (input.outcome === "result_unknown") {
|
|
37403
|
+
completion = {
|
|
37404
|
+
outcome: "result_unknown",
|
|
37405
|
+
...input.error === void 0 ? {} : { error: input.error }
|
|
37618
37406
|
};
|
|
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
|
-
`;
|
|
37407
|
+
} else {
|
|
37408
|
+
completion = { outcome: input.outcome, error: input.error };
|
|
37409
|
+
}
|
|
37410
|
+
return c.json(deviceOperationDetailSchema.parse(await env.mailbox().complete({
|
|
37411
|
+
deviceId: input.deviceId,
|
|
37412
|
+
operationId: input.operationId,
|
|
37413
|
+
leaseId: input.leaseId,
|
|
37414
|
+
deviceKeyId: c.get("ctx").keyId,
|
|
37415
|
+
authorize: deviceAuthorize(c, env)
|
|
37416
|
+
}, completion)));
|
|
37417
|
+
}
|
|
37418
|
+
function registerDeviceMailboxRoutes(app, env) {
|
|
37419
|
+
app.post("/~device/operations/get", (c) => runHandler(async () => await getOperation(c, env)));
|
|
37420
|
+
app.post("/~device/operations/list", (c) => runHandler(async () => await listOperations(c, env)));
|
|
37421
|
+
app.post("/~device/operations/cancel", (c) => runHandler(async () => await cancelOperation(c, env)));
|
|
37422
|
+
app.post("/~device/mailbox/claim", (c) => runHandler(async () => await claimOperations(c, env)));
|
|
37423
|
+
app.post("/~device/mailbox/renew", (c) => runHandler(async () => await renewLeases(c, env)));
|
|
37424
|
+
app.post("/~device/mailbox/complete", (c) => runHandler(async () => await completeOperation(c, env)));
|
|
37647
37425
|
}
|
|
37648
37426
|
|
|
37649
37427
|
// ../app/src/federation.ts
|
|
@@ -37867,6 +37645,24 @@ function assertRemoteConfigAllowed(config, settings) {
|
|
|
37867
37645
|
assertRemoteAllowed(baseUrl, settings);
|
|
37868
37646
|
}
|
|
37869
37647
|
|
|
37648
|
+
// ../app/src/registryMutation.ts
|
|
37649
|
+
async function assertNodeConfigMutation(opts) {
|
|
37650
|
+
const { action, config, ctx, deps, registry, targetPath } = opts;
|
|
37651
|
+
assertRemoteConfigAllowed(config, await resolveRemoteSettings(deps.state, deps.remote));
|
|
37652
|
+
await assertRegisterPath2(registry, ctx, targetPath, action, deps);
|
|
37653
|
+
assertSecretRefUse(ctx.scopes, config);
|
|
37654
|
+
await assertMcpOAuthConfig(config, deps.secrets);
|
|
37655
|
+
await assertContextConfig(config, deps);
|
|
37656
|
+
await assertSkillhubConfig(config, deps);
|
|
37657
|
+
await assertToolConfig(config, deps, ctx, targetPath);
|
|
37658
|
+
}
|
|
37659
|
+
async function invalidateNodeDerivedState(store, path) {
|
|
37660
|
+
await invalidateToolCache(store, path);
|
|
37661
|
+
await invalidateMcpEra(store, path);
|
|
37662
|
+
await invalidateMcpOAuth(store, path);
|
|
37663
|
+
await invalidateProviderOAuth(store, path);
|
|
37664
|
+
}
|
|
37665
|
+
|
|
37870
37666
|
// ../app/src/routes/register.ts
|
|
37871
37667
|
async function authorizeToolNode(c, env, node, encryptionKey) {
|
|
37872
37668
|
const { deps } = env;
|
|
@@ -37923,16 +37719,17 @@ async function handleRegister(c, env) {
|
|
|
37923
37719
|
);
|
|
37924
37720
|
}
|
|
37925
37721
|
const body = parseNodeInput(raw);
|
|
37926
|
-
assertRemoteConfigAllowed(body.config, await resolveRemoteSettings(store, deps.remote));
|
|
37927
37722
|
if (!check(ctx, path, "register").allow) {
|
|
37928
37723
|
throw new TBError("permission_denied", `no scope grants 'register' on '${path}'`);
|
|
37929
37724
|
}
|
|
37930
|
-
await
|
|
37931
|
-
|
|
37932
|
-
|
|
37933
|
-
|
|
37934
|
-
|
|
37935
|
-
|
|
37725
|
+
await assertNodeConfigMutation({
|
|
37726
|
+
action: "write",
|
|
37727
|
+
config: body.config,
|
|
37728
|
+
ctx,
|
|
37729
|
+
deps,
|
|
37730
|
+
registry,
|
|
37731
|
+
targetPath: body.path
|
|
37732
|
+
});
|
|
37936
37733
|
await searchSync?.ensureSeeded();
|
|
37937
37734
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
37938
37735
|
const marker = await searchSync?.markNode(body.path);
|
|
@@ -37943,12 +37740,9 @@ async function handleRegister(c, env) {
|
|
|
37943
37740
|
await searchSync?.abort(marker);
|
|
37944
37741
|
throw error;
|
|
37945
37742
|
}
|
|
37946
|
-
await
|
|
37947
|
-
await invalidateMcpEra(store, body.path);
|
|
37948
|
-
await invalidateMcpOAuth(store, body.path);
|
|
37949
|
-
await invalidateProviderOAuth(store, body.path);
|
|
37743
|
+
await invalidateNodeDerivedState(store, body.path);
|
|
37950
37744
|
await searchSync?.reconcileNodeQuietly(body.path, { marker });
|
|
37951
|
-
if (await refreshDynamicSearchNode(node, ctx, deps)) await searchSync?.abort(marker);
|
|
37745
|
+
if (await refreshDynamicSearchNode(node, ctx, deps, searchSync)) await searchSync?.abort(marker);
|
|
37952
37746
|
return new Response(JSON.stringify(registryNodeSchema.parse(node)), {
|
|
37953
37747
|
headers: { "content-type": contentTypeFor("json") }
|
|
37954
37748
|
});
|
|
@@ -38045,7 +37839,7 @@ async function handleDescribe(c, env) {
|
|
|
38045
37839
|
await assertContextAlive(node, node.config, registry);
|
|
38046
37840
|
const cfg = node.config;
|
|
38047
37841
|
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 ?
|
|
37842
|
+
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
37843
|
if ((cfg.provider === "r2" || cfg.provider === "s3") && cfg.readOnly !== true && await contextDirectUploadAvailable(cfg, deps)) {
|
|
38050
37844
|
capabilities = [...capabilities, CONTEXT_DIRECT_UPLOAD_CAPABILITY];
|
|
38051
37845
|
}
|
|
@@ -38497,7 +38291,7 @@ function parseRecord(value) {
|
|
|
38497
38291
|
topologyDigest: value.topologyDigest
|
|
38498
38292
|
});
|
|
38499
38293
|
} catch (error) {
|
|
38500
|
-
if (error
|
|
38294
|
+
if (isTBError(error)) throw error;
|
|
38501
38295
|
throw corruptSession();
|
|
38502
38296
|
}
|
|
38503
38297
|
}
|
|
@@ -38713,12 +38507,8 @@ var FederatedSearchSessionStore = class {
|
|
|
38713
38507
|
}
|
|
38714
38508
|
};
|
|
38715
38509
|
|
|
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;
|
|
38510
|
+
// ../app/src/search/compactTool.ts
|
|
38511
|
+
function compactSearchTool(tool) {
|
|
38722
38512
|
const compact = { ...tool };
|
|
38723
38513
|
delete compact.inputSchema;
|
|
38724
38514
|
delete compact.outputSchema;
|
|
@@ -38738,6 +38528,14 @@ function projectSearchTool(tool, detail) {
|
|
|
38738
38528
|
}
|
|
38739
38529
|
return compact;
|
|
38740
38530
|
}
|
|
38531
|
+
|
|
38532
|
+
// ../app/src/search/localSearch.ts
|
|
38533
|
+
function normalizedEffect(effect) {
|
|
38534
|
+
return effect === "read" || effect === "write" || effect === "destructive" ? effect : "unknown";
|
|
38535
|
+
}
|
|
38536
|
+
function projectSearchTool(tool, detail) {
|
|
38537
|
+
return detail === "full" ? tool : compactSearchTool(tool);
|
|
38538
|
+
}
|
|
38741
38539
|
async function executeLocalSearch(deps, request) {
|
|
38742
38540
|
const { ctx, search, searchSync, state } = deps;
|
|
38743
38541
|
const query = request.query;
|
|
@@ -38867,7 +38665,7 @@ function isPrefix(prefix, path) {
|
|
|
38867
38665
|
return prefix === "" || prefix === path || path.startsWith(`${prefix}/`);
|
|
38868
38666
|
}
|
|
38869
38667
|
function controlledStatus(error) {
|
|
38870
|
-
if (error
|
|
38668
|
+
if (isTBError(error)) {
|
|
38871
38669
|
if (error.message.includes("\u73AF")) return "cycle";
|
|
38872
38670
|
if (error.message.includes("\u8DF3\u6570")) return "hop_limit";
|
|
38873
38671
|
if (error.message.includes("aborted")) return "timed_out";
|
|
@@ -38886,31 +38684,14 @@ function invalidContinuation() {
|
|
|
38886
38684
|
});
|
|
38887
38685
|
}
|
|
38888
38686
|
function continuationSourceError(error) {
|
|
38889
|
-
if (error
|
|
38687
|
+
if (isTBError(error)) {
|
|
38890
38688
|
if (error.code === "invalid_argument") return error;
|
|
38891
38689
|
if (error.message.includes("source work budget exhausted")) return invalidContinuation();
|
|
38892
38690
|
}
|
|
38893
38691
|
return retryableContinuationError();
|
|
38894
38692
|
}
|
|
38895
38693
|
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 };
|
|
38694
|
+
return { ...item, tool: compactSearchTool(item.tool) };
|
|
38914
38695
|
}
|
|
38915
38696
|
function normalizedEffect2(effect) {
|
|
38916
38697
|
return effect === "read" || effect === "write" || effect === "destructive" ? effect : "unknown";
|
|
@@ -39439,7 +39220,7 @@ async function computePage(deps, input, sources, remoteNodes, deadline, expiresA
|
|
|
39439
39220
|
await assertFederatedCapability(deps, input, node, headers, deadline);
|
|
39440
39221
|
return null;
|
|
39441
39222
|
} catch (error) {
|
|
39442
|
-
const status = error
|
|
39223
|
+
const status = isTBError(error) && error.message.includes("unsupported") ? "unsupported" : controlledStatus(error);
|
|
39443
39224
|
return { error, source, status };
|
|
39444
39225
|
}
|
|
39445
39226
|
}
|
|
@@ -39447,7 +39228,7 @@ async function computePage(deps, input, sources, remoteNodes, deadline, expiresA
|
|
|
39447
39228
|
for (const failure of capabilityFailures) {
|
|
39448
39229
|
if (failure === null) continue;
|
|
39449
39230
|
if (!initial) {
|
|
39450
|
-
if (failure.error
|
|
39231
|
+
if (isTBError(failure.error) && failure.error.code === "invalid_argument") {
|
|
39451
39232
|
throw failure.error;
|
|
39452
39233
|
}
|
|
39453
39234
|
throw failure.status === "unsupported" ? invalidContinuation() : retryableContinuationError();
|
|
@@ -39610,9 +39391,6 @@ async function executeFederatedSearch(deps, input) {
|
|
|
39610
39391
|
"recursive search requires explicit TB_INSTANCE_ID and atomic StateStore CAS"
|
|
39611
39392
|
);
|
|
39612
39393
|
}
|
|
39613
|
-
if ((input.request.opts?.mode ?? "keyword") !== "keyword") {
|
|
39614
|
-
throw new TBError("invalid_argument", "semantic search cannot use recursive federation");
|
|
39615
|
-
}
|
|
39616
39394
|
const wireOpts = input.request.opts ?? {};
|
|
39617
39395
|
const constraints = normalizeToolSearchOptions({
|
|
39618
39396
|
...wireOpts.effects === void 0 ? {} : { effects: wireOpts.effects },
|
|
@@ -39716,7 +39494,7 @@ async function executeFederatedSearch(deps, input) {
|
|
|
39716
39494
|
requestDigest
|
|
39717
39495
|
});
|
|
39718
39496
|
} catch (error) {
|
|
39719
|
-
if (!(error
|
|
39497
|
+
if (!isTBError(error) || error.code !== "invalid_argument") throw error;
|
|
39720
39498
|
}
|
|
39721
39499
|
if (next !== void 0) assertNextRecord(current, next, digest);
|
|
39722
39500
|
const cachedNext = next !== void 0;
|
|
@@ -39925,13 +39703,6 @@ function registerSearchRoute(app, env) {
|
|
|
39925
39703
|
);
|
|
39926
39704
|
}
|
|
39927
39705
|
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
39706
|
const explicitFederation = request.opts?.federation;
|
|
39936
39707
|
const cursor = request.opts?.cursor;
|
|
39937
39708
|
const federation = explicitFederation ?? (cursor !== void 0 && !isFederatedSearchSessionHandle(cursor) ? "local" : capabilities.includes("search:federated") ? "recursive" : "local");
|
|
@@ -39941,9 +39712,6 @@ function registerSearchRoute(app, env) {
|
|
|
39941
39712
|
"recursive search requires capability 'search:federated'"
|
|
39942
39713
|
);
|
|
39943
39714
|
}
|
|
39944
|
-
if (federation === "recursive" && mode === "semantic") {
|
|
39945
|
-
throw new TBError("invalid_argument", "semantic search cannot use recursive federation");
|
|
39946
|
-
}
|
|
39947
39715
|
const normalizedRequest = {
|
|
39948
39716
|
...request,
|
|
39949
39717
|
opts: { ...request.opts ?? {}, federation }
|
|
@@ -40011,7 +39779,7 @@ async function helpModelFor(node, registry, ctx, builtins, deps, opts) {
|
|
|
40011
39779
|
}
|
|
40012
39780
|
if (node.kind === "mcp" || node.kind === "http" || node.kind === "tool") {
|
|
40013
39781
|
const provider = await providerFor(node, ctx, deps);
|
|
40014
|
-
const raw = await upstreamTools(node, provider, deps, opts.refresh, opts.now);
|
|
39782
|
+
const raw = await upstreamTools(node, provider, deps, opts.refresh, opts.now, opts.searchSync);
|
|
40015
39783
|
const { exposed } = virtualizeTools(node.virtualize, raw);
|
|
40016
39784
|
return toolsToHelpModel(
|
|
40017
39785
|
node.path,
|
|
@@ -40081,7 +39849,8 @@ async function commandHelpModelFor(registry, ctx, builtins, deps, path, opts) {
|
|
|
40081
39849
|
const full = await helpModelFor(node, registry, ctx, builtins, deps, {
|
|
40082
39850
|
refresh: opts.refresh,
|
|
40083
39851
|
schemas: true,
|
|
40084
|
-
now: (/* @__PURE__ */ new Date()).toISOString()
|
|
39852
|
+
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39853
|
+
searchSync: opts.searchSync
|
|
40085
39854
|
});
|
|
40086
39855
|
const cmd = full.cmds.find((c) => c.name === command);
|
|
40087
39856
|
if (cmd === void 0) return null;
|
|
@@ -40127,20 +39896,8 @@ var toolSpecCommand = (node, tool, providerBacked = false) => {
|
|
|
40127
39896
|
...tool.confirm === true ? { confirm: true } : {}
|
|
40128
39897
|
};
|
|
40129
39898
|
};
|
|
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
39899
|
function mcpBridgeFor(c, env, app) {
|
|
40143
|
-
const { builtinsOf, deps, globalSearchCapabilities } = env;
|
|
39900
|
+
const { builtinsOf, deps, globalSearchCapabilities, searchSync } = env;
|
|
40144
39901
|
const ctx = c.get("ctx");
|
|
40145
39902
|
const registry = new NodeRegistryStore(c.get("store"));
|
|
40146
39903
|
let remoteRequests = 0;
|
|
@@ -40159,7 +39916,7 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40159
39916
|
additionalProperties: false,
|
|
40160
39917
|
properties: {
|
|
40161
39918
|
query: { type: "string", minLength: 1 },
|
|
40162
|
-
mode: { type: "string", enum: ["keyword"
|
|
39919
|
+
mode: { type: "string", enum: ["keyword"] },
|
|
40163
39920
|
limit: { type: "integer", minimum: 1, maximum: 200 },
|
|
40164
39921
|
cursor: { type: "string", minLength: 1 },
|
|
40165
39922
|
detail: { type: "string", enum: ["compact", "full"], default: "compact" },
|
|
@@ -40227,6 +39984,10 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40227
39984
|
};
|
|
40228
39985
|
const remoteHelp = async (path) => {
|
|
40229
39986
|
takeRemoteRequest();
|
|
39987
|
+
const projector = await remotePathProjectorIfMatch(registry, path);
|
|
39988
|
+
if (projector === null) {
|
|
39989
|
+
throw remoteProtocolError(`remote ~help path '${path}' lost its mount owner`);
|
|
39990
|
+
}
|
|
40230
39991
|
const headers = new Headers(c.req.raw.headers);
|
|
40231
39992
|
headers.set("accept", "application/json");
|
|
40232
39993
|
const response = await remotePassthroughIfMatch(
|
|
@@ -40241,28 +40002,11 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40241
40002
|
if (response === null || !response.ok) {
|
|
40242
40003
|
throw new TBError("unavailable", `remote ~help failed for '${path}'`, { retryable: true });
|
|
40243
40004
|
}
|
|
40244
|
-
const
|
|
40245
|
-
if (
|
|
40005
|
+
const parsed = helpJsonSchema.safeParse(await response.json().catch(() => null));
|
|
40006
|
+
if (!parsed.success) {
|
|
40246
40007
|
throw new TBError("unavailable", `remote ~help returned invalid JSON for '${path}'`);
|
|
40247
40008
|
}
|
|
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;
|
|
40009
|
+
return projector.projectHelp(parsed.data, path, ctx);
|
|
40266
40010
|
};
|
|
40267
40011
|
const remotePaths = async (root) => {
|
|
40268
40012
|
const found = [];
|
|
@@ -40311,13 +40055,10 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40311
40055
|
if (check(ctx, path, command.scope).allow) {
|
|
40312
40056
|
let detailed = command;
|
|
40313
40057
|
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);
|
|
40058
|
+
const detail = await remoteHelp(command.path.replace(/^\/+/, ""));
|
|
40318
40059
|
detailed = detail.cmds.find((item) => item.name === command.name) ?? command;
|
|
40319
40060
|
}
|
|
40320
|
-
result.push(
|
|
40061
|
+
result.push(mcpCommand(path, model2.node.description, detailed));
|
|
40321
40062
|
}
|
|
40322
40063
|
}
|
|
40323
40064
|
}
|
|
@@ -40333,7 +40074,7 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40333
40074
|
if ((node.kind === "mcp" || node.kind === "http" || node.kind === "tool") && node.config !== void 0) {
|
|
40334
40075
|
if (!check(ctx, node.path, "call").allow) continue;
|
|
40335
40076
|
const provider = await providerFor(node, ctx, deps);
|
|
40336
|
-
const raw = await upstreamTools(node, provider, deps, false, now);
|
|
40077
|
+
const raw = await upstreamTools(node, provider, deps, false, now, searchSync);
|
|
40337
40078
|
const { exposed } = virtualizeTools(node.virtualize, raw);
|
|
40338
40079
|
result.push(...exposed.map((tool) => toolSpecCommand(node, tool, true)));
|
|
40339
40080
|
continue;
|
|
@@ -40341,7 +40082,8 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40341
40082
|
const model = await helpModelFor(node, registry, ctx, builtinsOf(c.get("store")), deps, {
|
|
40342
40083
|
includeDirectUpload: check(ctx, node.path, "write").allow,
|
|
40343
40084
|
refresh: false,
|
|
40344
|
-
now
|
|
40085
|
+
now,
|
|
40086
|
+
searchSync
|
|
40345
40087
|
});
|
|
40346
40088
|
for (const command of model.cmds) {
|
|
40347
40089
|
if (check(ctx, node.path, command.scope).allow) {
|
|
@@ -40444,7 +40186,7 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40444
40186
|
throw TBError.notFound("not found");
|
|
40445
40187
|
}
|
|
40446
40188
|
const provider = await providerFor(node, ctx, deps);
|
|
40447
|
-
const raw = await upstreamTools(node, provider, deps, false, (/* @__PURE__ */ new Date()).toISOString());
|
|
40189
|
+
const raw = await upstreamTools(node, provider, deps, false, (/* @__PURE__ */ new Date()).toISOString(), searchSync);
|
|
40448
40190
|
const upstreamName = resolveUpstreamTool(node.virtualize, raw, tool.toolName);
|
|
40449
40191
|
const result2 = await provider.call(upstreamName, args);
|
|
40450
40192
|
return {
|
|
@@ -40606,7 +40348,7 @@ async function handleInvoke(c, env) {
|
|
|
40606
40348
|
}
|
|
40607
40349
|
const args2 = await readInvokeBody();
|
|
40608
40350
|
const provider = await providerFor(node, ctx, deps);
|
|
40609
|
-
const tools = await upstreamTools(node, provider, deps, false, (/* @__PURE__ */ new Date()).toISOString());
|
|
40351
|
+
const tools = await upstreamTools(node, provider, deps, false, (/* @__PURE__ */ new Date()).toISOString(), searchSync);
|
|
40610
40352
|
const upstreamName = resolveUpstreamTool(node.virtualize, tools, command);
|
|
40611
40353
|
const result2 = await provider.call(upstreamName, args2);
|
|
40612
40354
|
return renderResult(result2.content, negotiate(c.req.header("accept")));
|
|
@@ -40670,7 +40412,7 @@ async function handleInvoke(c, env) {
|
|
|
40670
40412
|
if (cfg.provider !== "r2" && cfg.provider !== "s3") {
|
|
40671
40413
|
const local = localContext(deps, node);
|
|
40672
40414
|
if (local !== null) {
|
|
40673
|
-
const result4 = await
|
|
40415
|
+
const result4 = await dispatchContextCmd(local, command, args2);
|
|
40674
40416
|
return renderResult(result4, negotiate(c.req.header("accept")));
|
|
40675
40417
|
}
|
|
40676
40418
|
const { manifest, export: exported } = await requirePluginExport(
|
|
@@ -40690,11 +40432,11 @@ async function handleInvoke(c, env) {
|
|
|
40690
40432
|
...cfg.authRef !== void 0 ? { upstreamAuthRef: cfg.authRef } : {},
|
|
40691
40433
|
...deps.pluginBindings !== void 0 ? { bindings: deps.pluginBindings } : {}
|
|
40692
40434
|
});
|
|
40693
|
-
const result3 = await
|
|
40435
|
+
const result3 = await dispatchContextCmd(provider2, command, args2);
|
|
40694
40436
|
return renderResult(result3, negotiate(c.req.header("accept")));
|
|
40695
40437
|
}
|
|
40696
40438
|
const provider = await contextProviderFor(node, cfg, deps, c.req.url);
|
|
40697
|
-
const result2 = await
|
|
40439
|
+
const result2 = await dispatchContextCmd(provider, command, args2);
|
|
40698
40440
|
return renderResult(result2, negotiate(c.req.header("accept")));
|
|
40699
40441
|
}
|
|
40700
40442
|
if (node.kind === "skillhub" && node.config?.kind === "skillhub") {
|
|
@@ -40712,7 +40454,7 @@ async function handleInvoke(c, env) {
|
|
|
40712
40454
|
throw new TBError("permission_denied", `readOnly \u6302\u8F7D\u62D2\u7EDD '${command}'`);
|
|
40713
40455
|
}
|
|
40714
40456
|
const provider = await skillhubProviderFor(node, cfg, deps, c.req.url);
|
|
40715
|
-
const result2 = await
|
|
40457
|
+
const result2 = await dispatchSkillhubCmd(provider, command, args2);
|
|
40716
40458
|
return renderResult(result2, negotiate(c.req.header("accept")));
|
|
40717
40459
|
}
|
|
40718
40460
|
if (node.kind !== "builtin" || node.config?.kind !== "builtin") {
|
|
@@ -40736,19 +40478,14 @@ async function handleInvoke(c, env) {
|
|
|
40736
40478
|
throw new TBError("invalid_argument", "field 'path' must be a string");
|
|
40737
40479
|
}
|
|
40738
40480
|
const cfgPatch = cmd === "write" ? args.config : cmd === "update" ? args.patch?.config : void 0;
|
|
40739
|
-
|
|
40740
|
-
|
|
40741
|
-
|
|
40481
|
+
await assertNodeConfigMutation({
|
|
40482
|
+
action: cmd === "delete" ? "delete" : "write",
|
|
40483
|
+
config: cfgPatch,
|
|
40742
40484
|
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);
|
|
40485
|
+
deps,
|
|
40486
|
+
registry,
|
|
40487
|
+
targetPath
|
|
40488
|
+
});
|
|
40752
40489
|
registryTarget = targetPath;
|
|
40753
40490
|
}
|
|
40754
40491
|
if (registryTarget !== void 0 && (cmd === "write" || cmd === "update")) {
|
|
@@ -40778,22 +40515,16 @@ async function handleInvoke(c, env) {
|
|
|
40778
40515
|
return tbErrorResponse(new TBError("internal", "internal error"));
|
|
40779
40516
|
}
|
|
40780
40517
|
if (registryTarget !== void 0) {
|
|
40781
|
-
await
|
|
40782
|
-
await invalidateMcpEra(store, registryTarget);
|
|
40783
|
-
await invalidateMcpOAuth(store, registryTarget);
|
|
40784
|
-
await invalidateProviderOAuth(store, registryTarget);
|
|
40518
|
+
await invalidateNodeDerivedState(store, registryTarget);
|
|
40785
40519
|
await searchSync?.reconcileNodeQuietly(registryTarget, { marker: registryMarker });
|
|
40786
40520
|
const current = await registry.get(registryTarget).catch(() => null);
|
|
40787
|
-
if (current !== null && await refreshDynamicSearchNode(current, ctx, deps)) {
|
|
40521
|
+
if (current !== null && await refreshDynamicSearchNode(current, ctx, deps, searchSync)) {
|
|
40788
40522
|
await searchSync?.abort(registryMarker);
|
|
40789
40523
|
}
|
|
40790
40524
|
}
|
|
40791
40525
|
const pluginEmptyPaths = [];
|
|
40792
40526
|
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);
|
|
40527
|
+
await invalidateNodeDerivedState(store, mount.node.path);
|
|
40797
40528
|
const providerId = mount.node.config?.kind === "tool" ? mount.node.config.provider : "";
|
|
40798
40529
|
const manifest = await store.get(KEY_PLUGIN + providerId);
|
|
40799
40530
|
if (manifest !== null && manifest.enabled !== false) {
|
|
@@ -40841,8 +40572,7 @@ function createRouteEnv(deps) {
|
|
|
40841
40572
|
if (!declared.has("search")) return [];
|
|
40842
40573
|
return [
|
|
40843
40574
|
"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"] : []
|
|
40575
|
+
...deps.remote.instanceId !== void 0 && deps.state.compareAndSwap !== void 0 && deps.search?.revision !== void 0 ? ["search:federated"] : []
|
|
40846
40576
|
];
|
|
40847
40577
|
};
|
|
40848
40578
|
let catalogDigest;
|
|
@@ -40862,7 +40592,7 @@ function createRouteEnv(deps) {
|
|
|
40862
40592
|
|
|
40863
40593
|
// ../app/src/routes/help.ts
|
|
40864
40594
|
async function handleHelp(c, env) {
|
|
40865
|
-
const { builtinsOf, deps } = env;
|
|
40595
|
+
const { builtinsOf, deps, searchSync } = env;
|
|
40866
40596
|
const path = splitReserved(new URL(c.req.url).pathname, "~help");
|
|
40867
40597
|
if (path === null) throw TBError.notFound("no such path");
|
|
40868
40598
|
const ctx = c.get("ctx");
|
|
@@ -40901,11 +40631,12 @@ async function handleHelp(c, env) {
|
|
|
40901
40631
|
try {
|
|
40902
40632
|
node = await registry.get(path);
|
|
40903
40633
|
} catch {
|
|
40904
|
-
const toolModel = await toolHelpModelFor(c, ctx, registry, path, deps);
|
|
40634
|
+
const toolModel = await toolHelpModelFor(c, ctx, registry, path, deps, searchSync);
|
|
40905
40635
|
if (toolModel !== null) return renderHelp(await enrichHelp(toolModel, path, store), rep);
|
|
40906
40636
|
const cmdModel = await commandHelpModelFor(registry, ctx, builtins, deps, path, {
|
|
40907
40637
|
refresh,
|
|
40908
|
-
schemas: schemas2
|
|
40638
|
+
schemas: schemas2,
|
|
40639
|
+
searchSync
|
|
40909
40640
|
});
|
|
40910
40641
|
if (cmdModel !== null) return renderHelp(await enrichHelp(cmdModel, path, store), rep);
|
|
40911
40642
|
throw TBError.notFound("not found");
|
|
@@ -40913,7 +40644,8 @@ async function handleHelp(c, env) {
|
|
|
40913
40644
|
const model = await helpModelFor(node, registry, ctx, builtins, deps, {
|
|
40914
40645
|
refresh,
|
|
40915
40646
|
schemas: schemas2,
|
|
40916
|
-
now: (/* @__PURE__ */ new Date()).toISOString()
|
|
40647
|
+
now: (/* @__PURE__ */ new Date()).toISOString(),
|
|
40648
|
+
searchSync
|
|
40917
40649
|
});
|
|
40918
40650
|
return renderHelp(await enrichHelp(model, path, store), rep);
|
|
40919
40651
|
}
|
|
@@ -41021,6 +40753,7 @@ function createTbApp(deps) {
|
|
|
41021
40753
|
});
|
|
41022
40754
|
registerMcpRoute(app, env);
|
|
41023
40755
|
registerSearchRoute(app, env);
|
|
40756
|
+
registerDeviceMailboxRoutes(app, env);
|
|
41024
40757
|
app.get(
|
|
41025
40758
|
"/system/device/ws",
|
|
41026
40759
|
(c) => runHandler(async () => {
|
|
@@ -41061,13 +40794,6 @@ function createTbApp(deps) {
|
|
|
41061
40794
|
async (c) => await runHandler(async () => {
|
|
41062
40795
|
const segs = new URL(c.req.url).pathname.replace(/\/+$/, "").split("/");
|
|
41063
40796
|
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
40797
|
if (last === "~register") return await handleRegister(c, env);
|
|
41072
40798
|
if (last === "~authorize") return await handleAuthorize(c, env);
|
|
41073
40799
|
if (last === "~feedback" || segs[segs.length - 1] === "~feedback") {
|
|
@@ -41144,7 +40870,7 @@ var DeviceHub = class {
|
|
|
41144
40870
|
this.handleUpgrade(req, socket, head).catch((err) => {
|
|
41145
40871
|
rejectUpgrade(
|
|
41146
40872
|
socket,
|
|
41147
|
-
err
|
|
40873
|
+
isTBError(err) ? err : new TBError("internal", "upgrade failed")
|
|
41148
40874
|
);
|
|
41149
40875
|
});
|
|
41150
40876
|
});
|
|
@@ -41178,17 +40904,12 @@ var DeviceHub = class {
|
|
|
41178
40904
|
};
|
|
41179
40905
|
const conn = this.activeByDevice.get(deviceId);
|
|
41180
40906
|
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;
|
|
40907
|
+
const authorized = conn.keyId !== void 0 && conn.mountPath !== void 0 && await reverifyDeviceAuthority({
|
|
40908
|
+
store: this.store,
|
|
40909
|
+
authorization: conn.authorization,
|
|
40910
|
+
keyId: conn.keyId,
|
|
40911
|
+
mountPath: conn.mountPath
|
|
40912
|
+
});
|
|
41192
40913
|
if (!authorized) {
|
|
41193
40914
|
conn.session.reject(TBError.unauthenticated());
|
|
41194
40915
|
return offline;
|
|
@@ -41220,10 +40941,11 @@ var DeviceHub = class {
|
|
|
41220
40941
|
}
|
|
41221
40942
|
if (meta.disconnectedAt === void 0) {
|
|
41222
40943
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
41223
|
-
|
|
41224
|
-
|
|
41225
|
-
|
|
41226
|
-
|
|
40944
|
+
await markDeviceDisconnected({
|
|
40945
|
+
mountPath: meta.mountPath,
|
|
40946
|
+
now,
|
|
40947
|
+
registry: new NodeRegistryStore(this.store)
|
|
40948
|
+
});
|
|
41227
40949
|
await this.store.put(KEY_DEVICE_META + meta.deviceId, { ...meta, disconnectedAt: now });
|
|
41228
40950
|
this.scheduleReclaim(meta.deviceId, this.reclaimSec * 1e3);
|
|
41229
40951
|
} else {
|
|
@@ -41278,7 +41000,7 @@ var DeviceHub = class {
|
|
|
41278
41000
|
close: (code) => ws.close(code),
|
|
41279
41001
|
onHello: (hello) => {
|
|
41280
41002
|
this.acceptHello(conn, hello).catch((err) => {
|
|
41281
|
-
conn.session.reject(err
|
|
41003
|
+
conn.session.reject(isTBError(err) ? err : new TBError("internal", "hello \u5931\u8D25"));
|
|
41282
41004
|
});
|
|
41283
41005
|
}
|
|
41284
41006
|
},
|
|
@@ -41301,7 +41023,7 @@ var DeviceHub = class {
|
|
|
41301
41023
|
try {
|
|
41302
41024
|
conn.session.handleFrame(decodeDeviceFrame(data.toString()));
|
|
41303
41025
|
} catch (err) {
|
|
41304
|
-
conn.session.reject(err
|
|
41026
|
+
conn.session.reject(isTBError(err) ? err : new TBError("invalid_argument", "\u975E\u6CD5\u5E27"));
|
|
41305
41027
|
}
|
|
41306
41028
|
});
|
|
41307
41029
|
ws.on("close", () => {
|
|
@@ -41321,9 +41043,7 @@ var DeviceHub = class {
|
|
|
41321
41043
|
...this.search === void 0 ? {} : { search: this.search }
|
|
41322
41044
|
});
|
|
41323
41045
|
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
|
-
);
|
|
41046
|
+
console.warn(deviceSearchCapacityWarning(conn.deviceId, mountPath));
|
|
41327
41047
|
}
|
|
41328
41048
|
const meta = { deviceId: conn.deviceId, mountPath, keyId, connectedAt: now };
|
|
41329
41049
|
await this.store.put(KEY_DEVICE_META + conn.deviceId, meta);
|
|
@@ -41348,10 +41068,11 @@ var DeviceHub = class {
|
|
|
41348
41068
|
const raw = await this.store.get(KEY_DEVICE_META + conn.deviceId);
|
|
41349
41069
|
if (!isDeviceMeta(raw)) return;
|
|
41350
41070
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
41351
|
-
|
|
41352
|
-
|
|
41353
|
-
|
|
41354
|
-
|
|
41071
|
+
await markDeviceDisconnected({
|
|
41072
|
+
mountPath: raw.mountPath,
|
|
41073
|
+
now,
|
|
41074
|
+
registry: new NodeRegistryStore(this.store)
|
|
41075
|
+
});
|
|
41355
41076
|
await this.store.put(KEY_DEVICE_META + conn.deviceId, { ...raw, disconnectedAt: now });
|
|
41356
41077
|
this.scheduleReclaim(conn.deviceId, this.reclaimSec * 1e3);
|
|
41357
41078
|
}
|
|
@@ -41384,13 +41105,12 @@ var DeviceHub = class {
|
|
|
41384
41105
|
}
|
|
41385
41106
|
const raw = await this.store.get(KEY_DEVICE_META + deviceId);
|
|
41386
41107
|
if (!isDeviceMeta(raw)) return;
|
|
41387
|
-
|
|
41388
|
-
|
|
41389
|
-
|
|
41390
|
-
|
|
41391
|
-
|
|
41392
|
-
}
|
|
41393
|
-
await searchSync?.removeSubtreeQuietly(raw.mountPath, marker);
|
|
41108
|
+
await reclaimDeviceSubtree({
|
|
41109
|
+
mountPath: raw.mountPath,
|
|
41110
|
+
registry: new NodeRegistryStore(this.store),
|
|
41111
|
+
...this.search === void 0 ? {} : { search: this.search },
|
|
41112
|
+
state: this.store
|
|
41113
|
+
});
|
|
41394
41114
|
await this.store.delete(KEY_DEVICE_META + deviceId);
|
|
41395
41115
|
}
|
|
41396
41116
|
/** ws 协议层探活:上一轮未回 pong → terminate(触发 close → markDisconnected)。 */
|
|
@@ -41739,7 +41459,7 @@ var FsObjectStore = class {
|
|
|
41739
41459
|
if (!st.isFile()) continue;
|
|
41740
41460
|
files.push({ key, meta: this.metaOf(key, st) });
|
|
41741
41461
|
} catch (e) {
|
|
41742
|
-
if (e
|
|
41462
|
+
if (isTBError(e) || isErrnoCode(e, "ENOENT")) continue;
|
|
41743
41463
|
throw e;
|
|
41744
41464
|
}
|
|
41745
41465
|
}
|
|
@@ -41806,7 +41526,7 @@ var FsObjectStore = class {
|
|
|
41806
41526
|
if (real !== rootReal && !real.startsWith(rootReal + sep)) throw escapeError(key);
|
|
41807
41527
|
return;
|
|
41808
41528
|
} catch (e) {
|
|
41809
|
-
if (e
|
|
41529
|
+
if (isTBError(e)) throw e;
|
|
41810
41530
|
if (!isErrnoCode(e, "ENOENT")) throw e;
|
|
41811
41531
|
const parent = dirname(probe);
|
|
41812
41532
|
if (parent === probe) throw escapeError(key);
|
|
@@ -41948,7 +41668,7 @@ async function assertAncestorInRoot(root, full, key) {
|
|
|
41948
41668
|
}
|
|
41949
41669
|
return;
|
|
41950
41670
|
} catch (error) {
|
|
41951
|
-
if (error
|
|
41671
|
+
if (isTBError(error)) throw error;
|
|
41952
41672
|
if (!isErrnoCode2(error, "ENOENT")) throw error;
|
|
41953
41673
|
const parent = dirname2(probe);
|
|
41954
41674
|
if (parent === probe) {
|
|
@@ -42481,7 +42201,7 @@ import { join as join4 } from "path";
|
|
|
42481
42201
|
// package.json
|
|
42482
42202
|
var package_default = {
|
|
42483
42203
|
name: "@tool-bridge/server",
|
|
42484
|
-
version: "0.
|
|
42204
|
+
version: "0.21.1",
|
|
42485
42205
|
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
42206
|
type: "module",
|
|
42487
42207
|
license: "MIT",
|