@tool-bridge/server 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{airtable-23JBIM6X.js → airtable-ZNWHIJMO.js} +4 -4
- package/dist/{alt_text_generator_ai-ADWBTCIH.js → alt_text_generator_ai-X7RMDDM5.js} +4 -4
- package/dist/{amap-CYQCYW2Z.js → amap-3VER75VH.js} +4 -4
- package/dist/{anthropic-5P62HDNR.js → anthropic-N7NTB3UC.js} +4 -4
- package/dist/{apify-54RZUAGW.js → apify-JB5P5AJI.js} +4 -4
- package/dist/{brave_search-AHJ6FTD5.js → brave_search-USL5OOBS.js} +4 -4
- package/dist/{chunk-SWBNHIO4.js → chunk-3WL6EXYX.js} +1 -1
- package/dist/{chunk-B4P6YZFS.js → chunk-6ANLYNFB.js} +1 -1
- package/dist/{chunk-VLOIDU5U.js → chunk-6X55ALDX.js} +3 -3
- package/dist/{chunk-CLEZ6YQ6.js → chunk-RVUERWUZ.js} +584 -60
- package/dist/{chunk-OKRZPQ3R.js → chunk-WIGQCP6O.js} +869 -74
- package/dist/{circleci-UM3NSQJ3.js → circleci-LBGB25LP.js} +4 -4
- package/dist/{clerk-FE5LQ2CZ.js → clerk-6RX37CNZ.js} +4 -4
- package/dist/{cloudflare_dns-KEZABKUT.js → cloudflare_dns-NQGW7FHV.js} +4 -4
- package/dist/{cohere-4IPJADXP.js → cohere-XPC2NAEW.js} +4 -4
- package/dist/{confluence-56QEKNQI.js → confluence-ZUG2LT5A.js} +4 -4
- package/dist/{convertapi-3QRM5K5T.js → convertapi-ZLXKWV3L.js} +3 -3
- package/dist/{currencyapi-L6G4OND5.js → currencyapi-T3A3HSOD.js} +4 -4
- package/dist/{deepseek-LGC6HBFG.js → deepseek-NJMOD23E.js} +4 -4
- package/dist/{docker_hub-XDMK3BIU.js → docker_hub-D2MQCSJ7.js} +4 -4
- package/dist/{dropbox-4QARMFZU.js → dropbox-MI6U3SOT.js} +4 -4
- package/dist/{dub-C6JSJKOT.js → dub-P2BYBYV4.js} +4 -4
- package/dist/{e2b-6A6TVACJ.js → e2b-BGKIAE54.js} +4 -4
- package/dist/{exa-SIWWW6AA.js → exa-EUY7VV4C.js} +4 -4
- package/dist/{fal_ai-3IJXOKSP.js → fal_ai-AAOG5FYJ.js} +4 -4
- package/dist/{feishu-L4KDA4DC.js → feishu-AN5IJYOW.js} +2 -2
- package/dist/{feishu_custom_bot-SDM75H4P.js → feishu_custom_bot-BWGMNVXF.js} +3 -3
- package/dist/{figma-6DD436DN.js → figma-SJSWYCGD.js} +4 -4
- package/dist/{firehydrant-SYSGYB3R.js → firehydrant-MOXK2PST.js} +4 -4
- package/dist/{fixer-FZG7ASTF.js → fixer-ISIDUDZU.js} +4 -4
- package/dist/{fly-HMRCZBUC.js → fly-U4KPSGBF.js} +4 -4
- package/dist/{front-FAFSD7I3.js → front-ZHM2YX2E.js} +4 -4
- package/dist/{geocodio-4D3TWENM.js → geocodio-SVCCAXAV.js} +4 -4
- package/dist/{ghost-OSO2FOZS.js → ghost-INTR7FIY.js} +4 -4
- package/dist/{github-OR4I4RHE.js → github-T275VVWR.js} +4 -4
- package/dist/{gmail-5AOPJ63E.js → gmail-KSSPOE67.js} +4 -4
- package/dist/{googlecalendar-SOJIJUWH.js → googlecalendar-JB5RW27L.js} +4 -4
- package/dist/{googledocs-QSKN5TLN.js → googledocs-G522PUGW.js} +3 -3
- package/dist/{grafana-R3AIZM7M.js → grafana-44ZPIV5P.js} +4 -4
- package/dist/{graphhopper-4CR3QOAX.js → graphhopper-2MORXLOW.js} +4 -4
- package/dist/index.d.ts +21 -6
- package/dist/index.js +3 -3
- package/dist/{ip2proxy-OBCG6X6J.js → ip2proxy-R4K4KEMI.js} +4 -4
- package/dist/{ipgeolocation_io-NHH3DTQW.js → ipgeolocation_io-H4J55OGI.js} +4 -4
- package/dist/{ipqualityscore-7POMCBEC.js → ipqualityscore-XGID4OHA.js} +4 -4
- package/dist/{jina_ai-YXVKROV4.js → jina_ai-7ZVDOR3Z.js} +4 -4
- package/dist/{jira-LADO3VJU.js → jira-CTIYZTT3.js} +4 -4
- package/dist/{langsmith-XZ6UAQFU.js → langsmith-ISP567FS.js} +4 -4
- package/dist/{lemlist-P74GLNE7.js → lemlist-OGKK272F.js} +4 -4
- package/dist/{linear-CXJ7D37A.js → linear-6JX4E3JI.js} +3 -3
- package/dist/{logsnag-EVVHWFLK.js → logsnag-BVB36ZCL.js} +4 -4
- package/dist/main.js +102 -102
- package/dist/{mattermost-WG2MRGBD.js → mattermost-UECVPZGY.js} +4 -4
- package/dist/{mem0-HQWUKG6U.js → mem0-EUA7S2OO.js} +4 -4
- package/dist/{memos-SQMJIFOE.js → memos-BYSBJRWZ.js} +4 -4
- package/dist/{metabase-E27G2DZI.js → metabase-4JXZH3YG.js} +4 -4
- package/dist/{mistral_ai-MIL63NIT.js → mistral_ai-2C5BTBKW.js} +4 -4
- package/dist/{mother_duck-AIZVCBNR.js → mother_duck-ZGNNNR3X.js} +4 -4
- package/dist/{ngrok-XS4DVIQS.js → ngrok-NTPGMRXI.js} +4 -4
- package/dist/{notes-JEOUSL2Y.js → notes-KGYXX6N6.js} +1 -1
- package/dist/{notion-U4EZVDW2.js → notion-EX3X2G7G.js} +4 -4
- package/dist/{open_exchange_rates-AZSQEYEJ.js → open_exchange_rates-2V2LIGPS.js} +4 -4
- package/dist/{openai-HW3SYMTQ.js → openai-KQFQPK7Z.js} +4 -4
- package/dist/{opengraph_io-QX74MAEL.js → opengraph_io-B63SEYJY.js} +4 -4
- package/dist/{openrouter-OXSKYL6N.js → openrouter-62A4H62Q.js} +4 -4
- package/dist/{outline-CCQAPDSW.js → outline-QC33XLCK.js} +4 -4
- package/dist/{paddle-6KQPSSCK.js → paddle-HDZG35Y7.js} +4 -4
- package/dist/{perplexity-2TXO7IQE.js → perplexity-M7LOMJWA.js} +4 -4
- package/dist/{pinecone-YOO7NULI.js → pinecone-Y7YCJRZM.js} +4 -4
- package/dist/{pipedrive-Q6RIFIFP.js → pipedrive-FLJA62UX.js} +4 -4
- package/dist/{polar-KUMGCES3.js → polar-AE55Q425.js} +4 -4
- package/dist/{posthog-VXQHBSMB.js → posthog-IAY5D3YW.js} +4 -4
- package/dist/{postmark-YLIPF52S.js → postmark-NMKAXDXZ.js} +4 -4
- package/dist/{prerender-CRY7UJN5.js → prerender-CDJM7I7N.js} +4 -4
- package/dist/{pubmed-3Z4T5AM7.js → pubmed-N437ZJOR.js} +3 -3
- package/dist/{railway-BP37IGFL.js → railway-NBWYXCUM.js} +3 -3
- package/dist/{readwise-HTKICF4K.js → readwise-QRO3UC3R.js} +4 -4
- package/dist/{render-WIVTUXJH.js → render-NEW7SROG.js} +4 -4
- package/dist/{replicate-JJYPA43D.js → replicate-73YQLRTY.js} +4 -4
- package/dist/{resend-TKHZVG7A.js → resend-R5BAZFHZ.js} +4 -4
- package/dist/{rootly-FRSZLG5U.js → rootly-5BLALPCS.js} +4 -4
- package/dist/{scrapfly-M5WXGQWE.js → scrapfly-EHFLNNUZ.js} +4 -4
- package/dist/{scrapingbee-PMXNLLBK.js → scrapingbee-UXLYS72S.js} +4 -4
- package/dist/{screenshot_fyi-HFW4TAOH.js → screenshot_fyi-KC3NLTJ6.js} +4 -4
- package/dist/{semantic_scholar-KXVH2VZQ.js → semantic_scholar-SA5OIUWA.js} +4 -4
- package/dist/{sentry-WWOT2LRX.js → sentry-BAE3XLJN.js} +4 -4
- package/dist/{shodan-FY635H35.js → shodan-FJR6MKKW.js} +4 -4
- package/dist/{shopify-HQLBVSA5.js → shopify-7OA6SLN5.js} +4 -4
- package/dist/{stripe-GJN2GOHB.js → stripe-MUWDZGGF.js} +3 -3
- package/dist/{supabase-BBNZGQU6.js → supabase-SWWGNWYR.js} +4 -4
- package/dist/{tavily-DPENIBOG.js → tavily-XQNJ5EDI.js} +4 -4
- package/dist/{telegram-L4NONG6H.js → telegram-RR6JPZOG.js} +4 -4
- package/dist/{telnyx-H67MAEZS.js → telnyx-7PKXOWSO.js} +4 -4
- package/dist/{todoist-G2BSP4DX.js → todoist-63X5IUJE.js} +4 -4
- package/dist/{trello-7WMVFDG2.js → trello-MOG63DAI.js} +4 -4
- package/dist/{turso-ZUAESFC2.js → turso-ENWTSHDL.js} +4 -4
- package/dist/{twilio-EYFYIX3B.js → twilio-FDXUN7VX.js} +3 -3
- package/dist/{umami-AXAUDUVF.js → umami-TM7PROWF.js} +4 -4
- package/dist/{unsplash-UCQAWPLR.js → unsplash-NWXLKSOW.js} +4 -4
- package/dist/{upstash_redis-ZPZ4ASU6.js → upstash_redis-CUTQ5L2N.js} +4 -4
- package/dist/{uptimerobot-4QVKKUD5.js → uptimerobot-NNO32M3I.js} +3 -3
- package/dist/{v2ex-VGEFJTYC.js → v2ex-24KMSWS7.js} +4 -4
- package/dist/{vercel-JME4TSCN.js → vercel-B45LPF6I.js} +4 -4
- package/dist/{wolfram_alpha_api-TRGU2MDG.js → wolfram_alpha_api-DKJWVR6G.js} +4 -4
- package/dist/{wordpress-WRUKLCZR.js → wordpress-XYFSDZXY.js} +4 -4
- package/dist/{workos-ZDW4EHS4.js → workos-BEM7EPHM.js} +4 -4
- package/dist/{zhihu-4VI67CWU.js → zhihu-CFHZ4NC7.js} +4 -4
- package/package.json +5 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createGuardedFetch
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-3WL6EXYX.js";
|
|
4
4
|
import {
|
|
5
5
|
AnnotationStore,
|
|
6
6
|
CONTEXT_CAPABILITIES,
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
DEFAULT_STORE_DRIVER_KEY_ROOT,
|
|
10
10
|
DEVICE_CALL_TIMEOUT_MS,
|
|
11
11
|
DeviceGatewaySession,
|
|
12
|
+
DeviceMailboxService,
|
|
12
13
|
FEEDBACK_HIDE_SCORE,
|
|
13
14
|
FeedbackStore,
|
|
14
15
|
HEADER_TB_CONTEXT,
|
|
@@ -90,6 +91,16 @@ import {
|
|
|
90
91
|
derivePresence,
|
|
91
92
|
deviceDirectoryHelpModel,
|
|
92
93
|
deviceFsHelpModel,
|
|
94
|
+
deviceOperationClaimRequestSchema,
|
|
95
|
+
deviceOperationClaimResponseSchema,
|
|
96
|
+
deviceOperationCompleteRequestSchema,
|
|
97
|
+
deviceOperationDetailSchema,
|
|
98
|
+
deviceOperationIdentityRequestSchema,
|
|
99
|
+
deviceOperationListRequestSchema,
|
|
100
|
+
deviceOperationListResponseSchema,
|
|
101
|
+
deviceOperationRenewRequestSchema,
|
|
102
|
+
deviceOperationRenewResponseSchema,
|
|
103
|
+
deviceOperationSummarySchema,
|
|
93
104
|
deviceShellHelpModel,
|
|
94
105
|
discriminatedUnion,
|
|
95
106
|
dispatchContextCmd,
|
|
@@ -185,7 +196,7 @@ import {
|
|
|
185
196
|
url,
|
|
186
197
|
validatePath,
|
|
187
198
|
virtualizeTools
|
|
188
|
-
} from "./chunk-
|
|
199
|
+
} from "./chunk-WIGQCP6O.js";
|
|
189
200
|
|
|
190
201
|
// src/config.ts
|
|
191
202
|
var DEFAULT_PORT = 8787;
|
|
@@ -1552,12 +1563,12 @@ function requireDevice(deps) {
|
|
|
1552
1563
|
}
|
|
1553
1564
|
return deps.device;
|
|
1554
1565
|
}
|
|
1555
|
-
async function
|
|
1566
|
+
async function attemptDevice(deps, deviceId, req) {
|
|
1556
1567
|
const id = crypto.randomUUID();
|
|
1557
1568
|
const upload = req.context === void 0 ? null : await issueDeviceCallUpload(deps, deviceId, id, req.context);
|
|
1558
|
-
let
|
|
1569
|
+
let attempt2;
|
|
1559
1570
|
try {
|
|
1560
|
-
|
|
1571
|
+
attempt2 = await requireDevice(deps).invoke(deviceId, {
|
|
1561
1572
|
id,
|
|
1562
1573
|
path: req.path,
|
|
1563
1574
|
arguments: req.arguments,
|
|
@@ -1567,11 +1578,15 @@ async function invokeDevice(deps, deviceId, req) {
|
|
|
1567
1578
|
await upload?.revoke().catch(() => {
|
|
1568
1579
|
});
|
|
1569
1580
|
}
|
|
1570
|
-
if (!
|
|
1581
|
+
if (attempt2 === null || typeof attempt2 !== "object" || !["completed", "not_dispatched", "unknown"].includes(attempt2.disposition) || attempt2.result === null || typeof attempt2.result !== "object" || !("ok" in attempt2.result) || attempt2.disposition !== "completed" && attempt2.result.ok !== false) {
|
|
1571
1582
|
throw new TBError("unavailable", "device session returned invalid result");
|
|
1572
1583
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1584
|
+
return attempt2;
|
|
1585
|
+
}
|
|
1586
|
+
async function invokeDevice(deps, deviceId, req) {
|
|
1587
|
+
const attempt2 = await attemptDevice(deps, deviceId, req);
|
|
1588
|
+
if (attempt2.result.ok) return attempt2.result.value;
|
|
1589
|
+
throw tbErrorFromBody(attempt2.result.error);
|
|
1575
1590
|
}
|
|
1576
1591
|
function deviceCallContextFrom(ctx) {
|
|
1577
1592
|
const now = Date.now();
|
|
@@ -20633,7 +20648,7 @@ async function processDeviceHello(opts) {
|
|
|
20633
20648
|
input,
|
|
20634
20649
|
authCtx.keyId,
|
|
20635
20650
|
now,
|
|
20636
|
-
input.path === mountPath ? { online: true, lastSeenAt: now } : {}
|
|
20651
|
+
input.path === mountPath ? { deviceId: hello.deviceId, online: true, lastSeenAt: now } : {}
|
|
20637
20652
|
);
|
|
20638
20653
|
} catch (error) {
|
|
20639
20654
|
await searchSync?.abort(marker);
|
|
@@ -20647,6 +20662,64 @@ async function processDeviceHello(opts) {
|
|
|
20647
20662
|
return { mountPath, keyId: authCtx.keyId, searchIndexed };
|
|
20648
20663
|
}
|
|
20649
20664
|
|
|
20665
|
+
// ../app/src/deviceMailbox.ts
|
|
20666
|
+
var KEY_DEVICE_MAILBOX_CLEANUP_PROGRESS = "sys:device-mailbox-cleanup-progress:v1";
|
|
20667
|
+
function positiveInt2(value, fallback, field) {
|
|
20668
|
+
const actual = value ?? fallback;
|
|
20669
|
+
if (!Number.isSafeInteger(actual) || actual < 1) {
|
|
20670
|
+
throw new TBError("invalid_argument", `${field} must be a positive integer`);
|
|
20671
|
+
}
|
|
20672
|
+
return actual;
|
|
20673
|
+
}
|
|
20674
|
+
function parseProgress(value) {
|
|
20675
|
+
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
|
+
return value;
|
|
20677
|
+
}
|
|
20678
|
+
function createDeviceMailboxService(deps) {
|
|
20679
|
+
return new DeviceMailboxService(deps.state, deps.encryptionKey);
|
|
20680
|
+
}
|
|
20681
|
+
async function cleanupDeviceMailbox(deps, opts = {}) {
|
|
20682
|
+
await deps.ensureReady?.();
|
|
20683
|
+
if (deps.state.compareAndSwap === void 0) {
|
|
20684
|
+
throw new TBError("unavailable", "device mailbox cleanup requires StateStore.compareAndSwap");
|
|
20685
|
+
}
|
|
20686
|
+
const mailbox = createDeviceMailboxService(deps);
|
|
20687
|
+
const maxPages = Math.min(positiveInt2(opts.maxPages, 8, "maxPages"), 64);
|
|
20688
|
+
const limit = Math.min(positiveInt2(opts.limit, 200, "limit"), 200);
|
|
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;
|
|
20692
|
+
const aggregate = { deleted: 0, expired: 0, scanned: 0 };
|
|
20693
|
+
for (let pageNumber = 0; pageNumber < maxPages; pageNumber++) {
|
|
20694
|
+
const page = await mailbox.cleanup({ limit, ...cursor === void 0 ? {} : { cursor } });
|
|
20695
|
+
aggregate.deleted += page.deleted;
|
|
20696
|
+
aggregate.expired += page.expired;
|
|
20697
|
+
aggregate.scanned += page.scanned;
|
|
20698
|
+
if (page.cursor === void 0) {
|
|
20699
|
+
if (progress !== null) {
|
|
20700
|
+
await deps.state.compareAndSwap(
|
|
20701
|
+
KEY_DEVICE_MAILBOX_CLEANUP_PROGRESS,
|
|
20702
|
+
progress.revision,
|
|
20703
|
+
null
|
|
20704
|
+
);
|
|
20705
|
+
}
|
|
20706
|
+
return aggregate;
|
|
20707
|
+
}
|
|
20708
|
+
const next = {
|
|
20709
|
+
cursor: page.cursor,
|
|
20710
|
+
revision: (progress?.revision ?? 0) + 1
|
|
20711
|
+
};
|
|
20712
|
+
if (!await deps.state.compareAndSwap(
|
|
20713
|
+
KEY_DEVICE_MAILBOX_CLEANUP_PROGRESS,
|
|
20714
|
+
progress?.revision ?? null,
|
|
20715
|
+
next
|
|
20716
|
+
)) return aggregate;
|
|
20717
|
+
progress = next;
|
|
20718
|
+
cursor = next.cursor;
|
|
20719
|
+
}
|
|
20720
|
+
return { ...aggregate, ...cursor === void 0 ? {} : { cursor } };
|
|
20721
|
+
}
|
|
20722
|
+
|
|
20650
20723
|
// ../../node_modules/.pnpm/@modelcontextprotocol+server@2.0.0/node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
|
|
20651
20724
|
var __create2 = Object.create;
|
|
20652
20725
|
var __defProp2 = Object.defineProperty;
|
|
@@ -36292,17 +36365,34 @@ function assertSchemaShape(value, path, depth = 0) {
|
|
|
36292
36365
|
list.forEach((sub, i) => assertSchemaShape(sub, `${path}/${key}/${i}`, depth + 1));
|
|
36293
36366
|
}
|
|
36294
36367
|
}
|
|
36295
|
-
function inputSchemaOf(raw) {
|
|
36296
|
-
if (raw === void 0) return { type: "object", properties: {} };
|
|
36297
|
-
|
|
36368
|
+
function inputSchemaOf(raw, delivery) {
|
|
36369
|
+
if (raw === void 0 && delivery === void 0) return { type: "object", properties: {} };
|
|
36370
|
+
const source = raw ?? { type: "object", properties: {} };
|
|
36371
|
+
if (source === null || typeof source !== "object" || Array.isArray(source)) {
|
|
36298
36372
|
throw new Error("MCP tool inputSchema must be a JSON Schema object");
|
|
36299
36373
|
}
|
|
36300
|
-
const schema =
|
|
36374
|
+
const schema = source;
|
|
36301
36375
|
if (schema.type !== void 0 && schema.type !== "object") {
|
|
36302
36376
|
throw new Error("MCP tool inputSchema root type must be object");
|
|
36303
36377
|
}
|
|
36304
36378
|
assertSchemaShape(schema, "inputSchema");
|
|
36305
|
-
|
|
36379
|
+
if (delivery !== "mailbox" && delivery !== "both") {
|
|
36380
|
+
return { ...schema, type: "object" };
|
|
36381
|
+
}
|
|
36382
|
+
const properties = schema.properties;
|
|
36383
|
+
if (properties !== void 0 && (properties === null || typeof properties !== "object" || Array.isArray(properties))) throw new Error("MCP tool inputSchema properties must be an object");
|
|
36384
|
+
return {
|
|
36385
|
+
...schema,
|
|
36386
|
+
type: "object",
|
|
36387
|
+
properties: {
|
|
36388
|
+
...properties ?? {},
|
|
36389
|
+
"~delivery": {
|
|
36390
|
+
type: "string",
|
|
36391
|
+
enum: delivery === "both" ? ["realtime", "mailbox", "fallback"] : ["mailbox", "fallback"],
|
|
36392
|
+
description: "Tool Bridge delivery policy; fallback prefers realtime and queues only when dispatch definitely did not occur."
|
|
36393
|
+
}
|
|
36394
|
+
}
|
|
36395
|
+
};
|
|
36306
36396
|
}
|
|
36307
36397
|
function annotationsOf(tool) {
|
|
36308
36398
|
const annotations = {};
|
|
@@ -36328,7 +36418,7 @@ async function projectTools(source, validator) {
|
|
|
36328
36418
|
HTBP ${bridge.invokePath}`;
|
|
36329
36419
|
let inputSchema;
|
|
36330
36420
|
try {
|
|
36331
|
-
inputSchema = inputSchemaOf(bridge.inputSchema);
|
|
36421
|
+
inputSchema = inputSchemaOf(bridge.inputSchema, bridge.delivery);
|
|
36332
36422
|
} catch {
|
|
36333
36423
|
throw new ProtocolError3(
|
|
36334
36424
|
ProtocolErrorCode2.InternalError,
|
|
@@ -36706,6 +36796,16 @@ import { Hono } from "hono";
|
|
|
36706
36796
|
// ../core/src/protocol/openapi.ts
|
|
36707
36797
|
var schemaRegistry = external_exports.registry();
|
|
36708
36798
|
var schemas = {
|
|
36799
|
+
DeviceOperationClaimRequest: deviceOperationClaimRequestSchema,
|
|
36800
|
+
DeviceOperationClaimResponse: deviceOperationClaimResponseSchema,
|
|
36801
|
+
DeviceOperationCompleteRequest: deviceOperationCompleteRequestSchema,
|
|
36802
|
+
DeviceOperationDetail: deviceOperationDetailSchema,
|
|
36803
|
+
DeviceOperationIdentityRequest: deviceOperationIdentityRequestSchema,
|
|
36804
|
+
DeviceOperationListRequest: deviceOperationListRequestSchema,
|
|
36805
|
+
DeviceOperationListResponse: deviceOperationListResponseSchema,
|
|
36806
|
+
DeviceOperationRenewRequest: deviceOperationRenewRequestSchema,
|
|
36807
|
+
DeviceOperationRenewResponse: deviceOperationRenewResponseSchema,
|
|
36808
|
+
DeviceOperationSummary: deviceOperationSummarySchema,
|
|
36709
36809
|
Error: tbErrorBodySchema,
|
|
36710
36810
|
FeedbackDetail: feedbackDetailSchema,
|
|
36711
36811
|
FeedbackList: feedbackListSchema,
|
|
@@ -36941,20 +37041,82 @@ var fixedControlPlaneOpenApi = {
|
|
|
36941
37041
|
"x-tool-bridge-greedy-path": true
|
|
36942
37042
|
}
|
|
36943
37043
|
},
|
|
37044
|
+
"/~device/operations/get": {
|
|
37045
|
+
post: {
|
|
37046
|
+
operationId: "getDeviceOperation",
|
|
37047
|
+
security: bearerSecurity,
|
|
37048
|
+
requestBody: { required: true, content: jsonContent("DeviceOperationIdentityRequest") },
|
|
37049
|
+
responses: { 200: jsonResponse("Device operation detail", "DeviceOperationDetail"), ...errorResponses }
|
|
37050
|
+
}
|
|
37051
|
+
},
|
|
37052
|
+
"/~device/operations/list": {
|
|
37053
|
+
post: {
|
|
37054
|
+
operationId: "listDeviceOperations",
|
|
37055
|
+
security: bearerSecurity,
|
|
37056
|
+
requestBody: { required: true, content: jsonContent("DeviceOperationListRequest") },
|
|
37057
|
+
responses: { 200: jsonResponse("Visible device operation page", "DeviceOperationListResponse"), ...errorResponses }
|
|
37058
|
+
}
|
|
37059
|
+
},
|
|
37060
|
+
"/~device/operations/cancel": {
|
|
37061
|
+
post: {
|
|
37062
|
+
operationId: "cancelDeviceOperation",
|
|
37063
|
+
security: bearerSecurity,
|
|
37064
|
+
requestBody: { required: true, content: jsonContent("DeviceOperationIdentityRequest") },
|
|
37065
|
+
responses: { 200: jsonResponse("Cancelled or cancellation-requested operation", "DeviceOperationDetail"), ...errorResponses }
|
|
37066
|
+
}
|
|
37067
|
+
},
|
|
37068
|
+
"/~device/mailbox/claim": {
|
|
37069
|
+
post: {
|
|
37070
|
+
operationId: "claimDeviceOperation",
|
|
37071
|
+
security: bearerSecurity,
|
|
37072
|
+
requestBody: { required: true, content: jsonContent("DeviceOperationClaimRequest") },
|
|
37073
|
+
responses: { 200: jsonResponse("Claimed operation or next scan cursor", "DeviceOperationClaimResponse"), ...errorResponses }
|
|
37074
|
+
}
|
|
37075
|
+
},
|
|
37076
|
+
"/~device/mailbox/renew": {
|
|
37077
|
+
post: {
|
|
37078
|
+
operationId: "renewDeviceOperation",
|
|
37079
|
+
security: bearerSecurity,
|
|
37080
|
+
requestBody: { required: true, content: jsonContent("DeviceOperationRenewRequest") },
|
|
37081
|
+
responses: { 200: jsonResponse("Renewed operation lease", "DeviceOperationRenewResponse"), ...errorResponses }
|
|
37082
|
+
}
|
|
37083
|
+
},
|
|
37084
|
+
"/~device/mailbox/complete": {
|
|
37085
|
+
post: {
|
|
37086
|
+
operationId: "completeDeviceOperation",
|
|
37087
|
+
security: bearerSecurity,
|
|
37088
|
+
requestBody: { required: true, content: jsonContent("DeviceOperationCompleteRequest") },
|
|
37089
|
+
responses: { 200: jsonResponse("Terminal device operation", "DeviceOperationDetail"), ...errorResponses }
|
|
37090
|
+
}
|
|
37091
|
+
},
|
|
36944
37092
|
"/{commandPath}": {
|
|
36945
37093
|
post: {
|
|
36946
37094
|
"operationId": "invoke",
|
|
36947
37095
|
"security": bearerSecurity,
|
|
36948
|
-
"parameters": [
|
|
36949
|
-
|
|
36950
|
-
|
|
36951
|
-
|
|
36952
|
-
|
|
36953
|
-
|
|
36954
|
-
|
|
37096
|
+
"parameters": [
|
|
37097
|
+
{
|
|
37098
|
+
in: "path",
|
|
37099
|
+
name: "commandPath",
|
|
37100
|
+
required: true,
|
|
37101
|
+
schema: { type: "string" },
|
|
37102
|
+
description: "Complete HTBP command path, including the command/tool leaf."
|
|
37103
|
+
},
|
|
37104
|
+
{
|
|
37105
|
+
in: "query",
|
|
37106
|
+
name: "ttlSeconds",
|
|
37107
|
+
schema: { type: "integer", minimum: 1 },
|
|
37108
|
+
description: "Mailbox operation TTL; used only when ~delivery can enqueue."
|
|
37109
|
+
},
|
|
37110
|
+
{
|
|
37111
|
+
in: "header",
|
|
37112
|
+
name: "x-tb-idempotency-key",
|
|
37113
|
+
schema: { type: "string", minLength: 1, maxLength: 255 },
|
|
37114
|
+
description: "Mailbox retry key; used only when ~delivery can enqueue."
|
|
37115
|
+
}
|
|
37116
|
+
],
|
|
36955
37117
|
"requestBody": {
|
|
36956
37118
|
required: true,
|
|
36957
|
-
description: "Raw command arguments
|
|
37119
|
+
description: "Raw command arguments plus the reserved optional ~delivery control.",
|
|
36958
37120
|
content: { "application/json": { schema: {} } }
|
|
36959
37121
|
},
|
|
36960
37122
|
"responses": {
|
|
@@ -36965,6 +37127,7 @@ var fixedControlPlaneOpenApi = {
|
|
|
36965
37127
|
"text/markdown": { schema: { type: "string" } }
|
|
36966
37128
|
}
|
|
36967
37129
|
},
|
|
37130
|
+
202: jsonResponse("Durably accepted device operation", "DeviceOperationDetail"),
|
|
36968
37131
|
...errorResponses
|
|
36969
37132
|
},
|
|
36970
37133
|
"x-tool-bridge-greedy-path": true
|
|
@@ -37157,6 +37320,239 @@ async function handleFeedbackDelete(c, env) {
|
|
|
37157
37320
|
return feedbackJson(feedbackRemoveResponseSchema, { ok: true });
|
|
37158
37321
|
}
|
|
37159
37322
|
|
|
37323
|
+
// ../app/src/routes/deviceMailbox.ts
|
|
37324
|
+
var DEVICE_MAILBOX_IDEMPOTENCY_HEADER = "x-tb-idempotency-key";
|
|
37325
|
+
var DEVICE_MAILBOX_HTTP_BODY_BYTES = 272 * 1024;
|
|
37326
|
+
var DEVICE_MAILBOX_LIST_LIMIT_DEFAULT = 50;
|
|
37327
|
+
var DEVICE_MAILBOX_LIST_LIMIT_MAX = 200;
|
|
37328
|
+
function invalidRequest(message) {
|
|
37329
|
+
return new TBError("invalid_argument", message);
|
|
37330
|
+
}
|
|
37331
|
+
async function mailboxJsonObject(c, opts = {}) {
|
|
37332
|
+
const declaredLength = Number(c.req.header("content-length"));
|
|
37333
|
+
if (Number.isFinite(declaredLength) && declaredLength > DEVICE_MAILBOX_HTTP_BODY_BYTES) {
|
|
37334
|
+
throw new TBError("rate_limited", "device mailbox request body is too large", {
|
|
37335
|
+
retryable: false
|
|
37336
|
+
});
|
|
37337
|
+
}
|
|
37338
|
+
const reader = c.req.raw.body?.getReader();
|
|
37339
|
+
let raw = "";
|
|
37340
|
+
if (reader !== void 0) {
|
|
37341
|
+
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
|
|
37342
|
+
let received = 0;
|
|
37343
|
+
try {
|
|
37344
|
+
while (true) {
|
|
37345
|
+
const chunk = await reader.read();
|
|
37346
|
+
if (chunk.done) break;
|
|
37347
|
+
received += chunk.value.byteLength;
|
|
37348
|
+
if (received > DEVICE_MAILBOX_HTTP_BODY_BYTES) {
|
|
37349
|
+
await reader.cancel().catch(() => {
|
|
37350
|
+
});
|
|
37351
|
+
throw new TBError("rate_limited", "device mailbox request body is too large", {
|
|
37352
|
+
retryable: false
|
|
37353
|
+
});
|
|
37354
|
+
}
|
|
37355
|
+
raw += decoder.decode(chunk.value, { stream: true });
|
|
37356
|
+
}
|
|
37357
|
+
raw += decoder.decode();
|
|
37358
|
+
} catch (cause) {
|
|
37359
|
+
if (cause instanceof TBError) throw cause;
|
|
37360
|
+
throw invalidRequest("body must be valid UTF-8 JSON");
|
|
37361
|
+
}
|
|
37362
|
+
}
|
|
37363
|
+
let parsed = opts.allowEmpty === true && raw === "" ? {} : null;
|
|
37364
|
+
if (raw !== "") {
|
|
37365
|
+
try {
|
|
37366
|
+
parsed = JSON.parse(raw);
|
|
37367
|
+
} catch {
|
|
37368
|
+
parsed = null;
|
|
37369
|
+
}
|
|
37370
|
+
}
|
|
37371
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
37372
|
+
throw invalidRequest("body must be a JSON object");
|
|
37373
|
+
}
|
|
37374
|
+
return parsed;
|
|
37375
|
+
}
|
|
37376
|
+
function parseBody(schema, body) {
|
|
37377
|
+
const parsed = schema.safeParse(body);
|
|
37378
|
+
if (!parsed.success) throw invalidRequest(parsed.error.issues[0]?.message ?? "invalid request");
|
|
37379
|
+
return parsed.data;
|
|
37380
|
+
}
|
|
37381
|
+
function deviceOperationTtlSeconds(url2) {
|
|
37382
|
+
for (const key of url2.searchParams.keys()) {
|
|
37383
|
+
if (key !== "ttlSeconds") throw invalidRequest(`unknown enqueue query parameter '${key}'`);
|
|
37384
|
+
}
|
|
37385
|
+
const value = url2.searchParams.get("ttlSeconds");
|
|
37386
|
+
if (value === null) return void 0;
|
|
37387
|
+
if (!/^[1-9]\d*$/.test(value)) throw invalidRequest("ttlSeconds must be a positive integer");
|
|
37388
|
+
const parsed = Number(value);
|
|
37389
|
+
if (!Number.isSafeInteger(parsed)) throw invalidRequest("ttlSeconds must be a safe integer");
|
|
37390
|
+
return parsed;
|
|
37391
|
+
}
|
|
37392
|
+
function canManage(ctx, operation) {
|
|
37393
|
+
return ctx.owner === operation.caller.owner || check(ctx, operation.targetPath, "admin").allow;
|
|
37394
|
+
}
|
|
37395
|
+
function assertCanManage(ctx, operation) {
|
|
37396
|
+
if (!canManage(ctx, operation)) throw TBError.notFound("device operation not found");
|
|
37397
|
+
}
|
|
37398
|
+
async function assertCurrentDeviceCredential(c, target) {
|
|
37399
|
+
const ctx = c.get("ctx");
|
|
37400
|
+
if (ctx.keyId !== target.deviceKeyId) {
|
|
37401
|
+
throw new TBError("permission_denied", "device credential does not own this operation");
|
|
37402
|
+
}
|
|
37403
|
+
let existing = null;
|
|
37404
|
+
try {
|
|
37405
|
+
existing = await new NodeRegistryStore(c.get("store")).get(target.mountPath);
|
|
37406
|
+
} catch {
|
|
37407
|
+
existing = null;
|
|
37408
|
+
}
|
|
37409
|
+
const allowed = checkRegisterPath({
|
|
37410
|
+
action: "write",
|
|
37411
|
+
existing,
|
|
37412
|
+
sk: {
|
|
37413
|
+
id: ctx.keyId,
|
|
37414
|
+
scopes: ctx.scopes,
|
|
37415
|
+
...ctx.registerPaths === void 0 ? {} : { registerPaths: ctx.registerPaths }
|
|
37416
|
+
},
|
|
37417
|
+
targetPath: target.mountPath
|
|
37418
|
+
});
|
|
37419
|
+
if (!allowed.allow) throw allowed.error;
|
|
37420
|
+
}
|
|
37421
|
+
async function enqueueDeviceCommand(c, env, input) {
|
|
37422
|
+
const { arguments: args, command, marker, node } = input;
|
|
37423
|
+
const ctx = c.get("ctx");
|
|
37424
|
+
const result = await env.mailbox().enqueue({
|
|
37425
|
+
arguments: args,
|
|
37426
|
+
caller: { keyId: ctx.keyId, owner: ctx.owner },
|
|
37427
|
+
deviceId: marker.deviceId,
|
|
37428
|
+
deviceKeyId: node.registeredBy,
|
|
37429
|
+
mountPath: marker.mountPath,
|
|
37430
|
+
// 管理面与审计链必须能区分同一 tool 节点下的不同命令;权限仍按前缀 scope 判定。
|
|
37431
|
+
targetPath: `${node.path}/${command}`,
|
|
37432
|
+
path: `${relativeDevicePath(node.path, marker.mountPath)}/${command}`,
|
|
37433
|
+
traceId: ctx.traceId,
|
|
37434
|
+
...c.req.header(DEVICE_MAILBOX_IDEMPOTENCY_HEADER) === void 0 ? {} : { idempotencyKey: c.req.header(DEVICE_MAILBOX_IDEMPOTENCY_HEADER) },
|
|
37435
|
+
...input.ttlSeconds === void 0 ? {} : { ttlSeconds: input.ttlSeconds }
|
|
37436
|
+
});
|
|
37437
|
+
const response = c.json(deviceOperationDetailSchema.parse(result), 202);
|
|
37438
|
+
response.headers.set("x-tb-delivery", "mailbox");
|
|
37439
|
+
return response;
|
|
37440
|
+
}
|
|
37441
|
+
async function handleDeviceMailboxControl(c, env) {
|
|
37442
|
+
const path = new URL(c.req.url).pathname.replace(/\/+$/, "");
|
|
37443
|
+
const body = await mailboxJsonObject(c);
|
|
37444
|
+
const mailbox = env.mailbox();
|
|
37445
|
+
const ctx = c.get("ctx");
|
|
37446
|
+
if (path === "/~device/operations/get") {
|
|
37447
|
+
const input = parseBody(deviceOperationIdentityRequestSchema, body);
|
|
37448
|
+
const result = await mailbox.get(input.deviceId, input.operationId);
|
|
37449
|
+
assertCanManage(ctx, result);
|
|
37450
|
+
return c.json(deviceOperationDetailSchema.parse(result));
|
|
37451
|
+
}
|
|
37452
|
+
if (path === "/~device/operations/list") {
|
|
37453
|
+
const input = parseBody(deviceOperationListRequestSchema, body);
|
|
37454
|
+
const limit = Math.min(
|
|
37455
|
+
input.opts?.limit ?? DEVICE_MAILBOX_LIST_LIMIT_DEFAULT,
|
|
37456
|
+
DEVICE_MAILBOX_LIST_LIMIT_MAX
|
|
37457
|
+
);
|
|
37458
|
+
let cursor = input.opts?.cursor;
|
|
37459
|
+
const items = [];
|
|
37460
|
+
while (items.length < limit) {
|
|
37461
|
+
const previous = cursor;
|
|
37462
|
+
const page = await mailbox.list({
|
|
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) {
|
|
37479
|
+
return c.json(deviceOperationListResponseSchema.parse({ items }));
|
|
37480
|
+
}
|
|
37481
|
+
let probeCursor = resumeCursor;
|
|
37482
|
+
while (probeCursor !== void 0) {
|
|
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
|
+
}
|
|
37497
|
+
}
|
|
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
|
+
}
|
|
37508
|
+
throw TBError.notFound("no such path");
|
|
37509
|
+
}
|
|
37510
|
+
async function handleDeviceMailboxData(c, env) {
|
|
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
|
+
})));
|
|
37523
|
+
}
|
|
37524
|
+
if (path === "/~device/mailbox/renew") {
|
|
37525
|
+
const input = parseBody(deviceOperationRenewRequestSchema, body);
|
|
37526
|
+
return c.json(deviceOperationRenewResponseSchema.parse(await mailbox.renew({
|
|
37527
|
+
...input,
|
|
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({
|
|
37546
|
+
deviceId: input.deviceId,
|
|
37547
|
+
operationId: input.operationId,
|
|
37548
|
+
leaseId: input.leaseId,
|
|
37549
|
+
deviceKeyId: ctx.keyId,
|
|
37550
|
+
authorize
|
|
37551
|
+
}, completion)));
|
|
37552
|
+
}
|
|
37553
|
+
throw TBError.notFound("no such path");
|
|
37554
|
+
}
|
|
37555
|
+
|
|
37160
37556
|
// ../app/src/responses.ts
|
|
37161
37557
|
function tbErrorResponse(err) {
|
|
37162
37558
|
return new Response(JSON.stringify(tbErrorBodySchema.parse(err.toJSON())), {
|
|
@@ -39711,6 +40107,7 @@ var mcpCommand = (nodePath, nodeDescription, command) => {
|
|
|
39711
40107
|
invokePath,
|
|
39712
40108
|
description: command.h ?? nodeDescription,
|
|
39713
40109
|
...command.inputSchema !== void 0 ? { inputSchema: command.inputSchema } : {},
|
|
40110
|
+
...command.delivery !== void 0 ? { delivery: command.delivery } : {},
|
|
39714
40111
|
...command.effect !== void 0 ? { effect: command.effect } : {},
|
|
39715
40112
|
...command.confirm === true ? { confirm: true } : {}
|
|
39716
40113
|
};
|
|
@@ -39725,6 +40122,7 @@ var toolSpecCommand = (node, tool, providerBacked = false) => {
|
|
|
39725
40122
|
...providerBacked ? { providerBacked: true } : {},
|
|
39726
40123
|
description: tool.description ?? node.description,
|
|
39727
40124
|
...tool.inputSchema !== void 0 ? { inputSchema: tool.inputSchema } : {},
|
|
40125
|
+
...tool.delivery !== void 0 ? { delivery: tool.delivery } : {},
|
|
39728
40126
|
...tool.effect !== void 0 ? { effect: tool.effect } : {},
|
|
39729
40127
|
...tool.confirm === true ? { confirm: true } : {}
|
|
39730
40128
|
};
|
|
@@ -40048,11 +40446,11 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40048
40446
|
const provider = await providerFor(node, ctx, deps);
|
|
40049
40447
|
const raw = await upstreamTools(node, provider, deps, false, (/* @__PURE__ */ new Date()).toISOString());
|
|
40050
40448
|
const upstreamName = resolveUpstreamTool(node.virtualize, raw, tool.toolName);
|
|
40051
|
-
const
|
|
40449
|
+
const result2 = await provider.call(upstreamName, args);
|
|
40052
40450
|
return {
|
|
40053
|
-
content:
|
|
40054
|
-
...
|
|
40055
|
-
...
|
|
40451
|
+
content: result2.contentBlocks ?? result2.content,
|
|
40452
|
+
...result2.isError === true ? { isError: true } : {},
|
|
40453
|
+
...result2.structuredContent !== void 0 ? { structuredContent: result2.structuredContent } : {}
|
|
40056
40454
|
};
|
|
40057
40455
|
}
|
|
40058
40456
|
const url2 = new URL(tool.invokePath, c.req.url);
|
|
@@ -40064,7 +40462,13 @@ function mcpBridgeFor(c, env, app) {
|
|
|
40064
40462
|
const response = await app.request(
|
|
40065
40463
|
new Request(url2, { method: "POST", headers, body: JSON.stringify(args) })
|
|
40066
40464
|
);
|
|
40067
|
-
|
|
40465
|
+
const result = await resultFromResponse(response);
|
|
40466
|
+
if (args["~delivery"] !== void 0 && response.ok) {
|
|
40467
|
+
return {
|
|
40468
|
+
content: response.status === 202 ? { delivery: "mailbox", operation: result.content } : { delivery: "realtime", result: result.content }
|
|
40469
|
+
};
|
|
40470
|
+
}
|
|
40471
|
+
return result;
|
|
40068
40472
|
}
|
|
40069
40473
|
};
|
|
40070
40474
|
}
|
|
@@ -40099,26 +40503,102 @@ async function handleInvoke(c, env) {
|
|
|
40099
40503
|
const node = resolved.node;
|
|
40100
40504
|
const command = resolved.rest;
|
|
40101
40505
|
if (!check(ctx, node.path, "read").allow) throw TBError.notFound("not found");
|
|
40506
|
+
const toolMarker = deviceToolMarker(node);
|
|
40507
|
+
let invokeRequest;
|
|
40508
|
+
const readInvokeRequest = async () => {
|
|
40509
|
+
if (invokeRequest !== void 0) return await invokeRequest;
|
|
40510
|
+
invokeRequest = (async () => {
|
|
40511
|
+
const parsed = toolMarker === null ? await c.req.json().catch(() => null) : await mailboxJsonObject(c, { allowEmpty: true });
|
|
40512
|
+
if (parsed !== null && (typeof parsed !== "object" || Array.isArray(parsed))) {
|
|
40513
|
+
throw new TBError("invalid_argument", "body must be a JSON object (command arguments)");
|
|
40514
|
+
}
|
|
40515
|
+
const raw2 = { ...parsed ?? {} };
|
|
40516
|
+
const unknownControls = Object.keys(raw2).filter(
|
|
40517
|
+
(key) => key.startsWith("~") && key !== "~delivery"
|
|
40518
|
+
);
|
|
40519
|
+
if (unknownControls.length > 0) {
|
|
40520
|
+
throw new TBError(
|
|
40521
|
+
"invalid_argument",
|
|
40522
|
+
`unknown invocation control '${unknownControls[0]}'`
|
|
40523
|
+
);
|
|
40524
|
+
}
|
|
40525
|
+
const rawDelivery = raw2["~delivery"];
|
|
40526
|
+
delete raw2["~delivery"];
|
|
40527
|
+
if (rawDelivery !== void 0 && rawDelivery !== "realtime" && rawDelivery !== "mailbox" && rawDelivery !== "fallback") {
|
|
40528
|
+
throw new TBError(
|
|
40529
|
+
"invalid_argument",
|
|
40530
|
+
"'~delivery' must be one of realtime, mailbox, fallback"
|
|
40531
|
+
);
|
|
40532
|
+
}
|
|
40533
|
+
return {
|
|
40534
|
+
arguments: raw2,
|
|
40535
|
+
delivery: rawDelivery ?? "realtime",
|
|
40536
|
+
explicitDelivery: rawDelivery !== void 0
|
|
40537
|
+
};
|
|
40538
|
+
})();
|
|
40539
|
+
return await invokeRequest;
|
|
40540
|
+
};
|
|
40102
40541
|
const readInvokeBody = async () => {
|
|
40103
|
-
const
|
|
40104
|
-
if (
|
|
40105
|
-
throw new TBError(
|
|
40542
|
+
const request = await readInvokeRequest();
|
|
40543
|
+
if (request.explicitDelivery) {
|
|
40544
|
+
throw new TBError(
|
|
40545
|
+
"invalid_argument",
|
|
40546
|
+
"'~delivery' is only supported by device-backed tool commands"
|
|
40547
|
+
);
|
|
40106
40548
|
}
|
|
40107
|
-
return
|
|
40549
|
+
return request.arguments;
|
|
40108
40550
|
};
|
|
40109
|
-
const toolMarker = deviceToolMarker(node);
|
|
40110
40551
|
if (toolMarker !== null) {
|
|
40111
40552
|
if (!check(ctx, node.path, "call").allow) {
|
|
40112
40553
|
throw new TBError("permission_denied", `no scope grants 'call' on '${node.path}'`);
|
|
40113
40554
|
}
|
|
40114
|
-
const
|
|
40115
|
-
|
|
40555
|
+
const declared = toolMarker.cmds?.find((item) => item.name.toLowerCase() === command);
|
|
40556
|
+
if (toolMarker.cmds !== void 0 && declared === void 0) throw TBError.notFound("not found");
|
|
40557
|
+
const request = await readInvokeRequest();
|
|
40558
|
+
const capability = declared?.delivery ?? "realtime";
|
|
40559
|
+
const canRealtime = capability === "realtime" || capability === "both";
|
|
40560
|
+
const canMailbox = capability === "mailbox" || capability === "both";
|
|
40561
|
+
if (request.delivery === "realtime" && !canRealtime) {
|
|
40562
|
+
throw new TBError("invalid_argument", "device command does not support realtime delivery");
|
|
40563
|
+
}
|
|
40564
|
+
if (request.delivery === "mailbox" && !canMailbox) {
|
|
40565
|
+
throw new TBError("invalid_argument", "device command does not support mailbox delivery");
|
|
40566
|
+
}
|
|
40567
|
+
if (request.delivery === "fallback" && !canMailbox) {
|
|
40568
|
+
throw new TBError("invalid_argument", "device command does not support mailbox fallback");
|
|
40569
|
+
}
|
|
40570
|
+
const ttlSeconds = request.delivery === "realtime" ? void 0 : deviceOperationTtlSeconds(new URL(c.req.url));
|
|
40571
|
+
const enqueue = async () => await enqueueDeviceCommand(c, env, {
|
|
40572
|
+
arguments: request.arguments,
|
|
40573
|
+
command,
|
|
40574
|
+
marker: toolMarker,
|
|
40575
|
+
node,
|
|
40576
|
+
...ttlSeconds === void 0 ? {} : { ttlSeconds }
|
|
40577
|
+
});
|
|
40578
|
+
if (request.delivery === "mailbox" || !canRealtime) return await enqueue();
|
|
40579
|
+
if (request.delivery === "fallback" && deps.device === void 0) return await enqueue();
|
|
40580
|
+
const attempt2 = await attemptDevice(deps, toolMarker.deviceId, {
|
|
40116
40581
|
// 帧 path 含命令叶子段:<mount 相对路径>/<命令>。
|
|
40117
40582
|
path: `${relativeDevicePath(node.path, toolMarker.mountPath)}/${command}`,
|
|
40118
|
-
arguments:
|
|
40583
|
+
arguments: request.arguments,
|
|
40119
40584
|
context: deviceCallContextFrom(ctx)
|
|
40120
40585
|
});
|
|
40121
|
-
|
|
40586
|
+
if (attempt2.disposition === "not_dispatched" && request.delivery === "fallback") {
|
|
40587
|
+
return await enqueue();
|
|
40588
|
+
}
|
|
40589
|
+
if (attempt2.result.ok) {
|
|
40590
|
+
const response = renderResult(attempt2.result.value, negotiate(c.req.header("accept")));
|
|
40591
|
+
response.headers.set("x-tb-delivery", "realtime");
|
|
40592
|
+
return response;
|
|
40593
|
+
}
|
|
40594
|
+
if (attempt2.disposition === "unknown" && request.delivery === "fallback") {
|
|
40595
|
+
throw new TBError(
|
|
40596
|
+
attempt2.result.error.code,
|
|
40597
|
+
`${attempt2.result.error.message}; delivery outcome is unknown and was not enqueued`,
|
|
40598
|
+
{ retryable: false }
|
|
40599
|
+
);
|
|
40600
|
+
}
|
|
40601
|
+
throw tbErrorFromBody(attempt2.result.error);
|
|
40122
40602
|
}
|
|
40123
40603
|
if ((node.kind === "mcp" || node.kind === "http" || node.kind === "tool") && node.config !== void 0) {
|
|
40124
40604
|
if (!check(ctx, node.path, "call").allow) {
|
|
@@ -40372,7 +40852,12 @@ function createRouteEnv(deps) {
|
|
|
40372
40852
|
catalogDigest ??= catalogSetDigest(catalog);
|
|
40373
40853
|
return catalogDigest;
|
|
40374
40854
|
};
|
|
40375
|
-
|
|
40855
|
+
let mailboxInstance;
|
|
40856
|
+
const mailbox = () => {
|
|
40857
|
+
mailboxInstance ??= createDeviceMailboxService(deps);
|
|
40858
|
+
return mailboxInstance;
|
|
40859
|
+
};
|
|
40860
|
+
return { builtinsOf, deps, globalSearchCapabilities, mailbox, pluginCatalogDigest, searchSync };
|
|
40376
40861
|
}
|
|
40377
40862
|
|
|
40378
40863
|
// ../app/src/routes/help.ts
|
|
@@ -40576,6 +41061,13 @@ function createTbApp(deps) {
|
|
|
40576
41061
|
async (c) => await runHandler(async () => {
|
|
40577
41062
|
const segs = new URL(c.req.url).pathname.replace(/\/+$/, "").split("/");
|
|
40578
41063
|
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
|
+
}
|
|
40579
41071
|
if (last === "~register") return await handleRegister(c, env);
|
|
40580
41072
|
if (last === "~authorize") return await handleAuthorize(c, env);
|
|
40581
41073
|
if (last === "~feedback" || segs[segs.length - 1] === "~feedback") {
|
|
@@ -40666,7 +41158,10 @@ var DeviceHub = class {
|
|
|
40666
41158
|
*/
|
|
40667
41159
|
async invoke(deviceId, req) {
|
|
40668
41160
|
if (this.activeByDevice.has(deviceId)) return await this.invokeLocal(deviceId, req);
|
|
40669
|
-
const offline = {
|
|
41161
|
+
const offline = {
|
|
41162
|
+
disposition: "not_dispatched",
|
|
41163
|
+
result: { ok: false, error: TBError.deviceOffline().toJSON() }
|
|
41164
|
+
};
|
|
40670
41165
|
if (this.router === void 0) return offline;
|
|
40671
41166
|
return await this.router.forward(deviceId, req) ?? offline;
|
|
40672
41167
|
}
|
|
@@ -40677,7 +41172,10 @@ var DeviceHub = class {
|
|
|
40677
41172
|
* 那会再次查路由并转发,在两个副本间弹来弹去。这里没有连接就直接 offline。
|
|
40678
41173
|
*/
|
|
40679
41174
|
async invokeLocal(deviceId, req) {
|
|
40680
|
-
const offline = {
|
|
41175
|
+
const offline = {
|
|
41176
|
+
disposition: "not_dispatched",
|
|
41177
|
+
result: { ok: false, error: TBError.deviceOffline().toJSON() }
|
|
41178
|
+
};
|
|
40681
41179
|
const conn = this.activeByDevice.get(deviceId);
|
|
40682
41180
|
if (conn === void 0) return offline;
|
|
40683
41181
|
const authCtx = await identify(this.store, conn.authorization, (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -40696,7 +41194,10 @@ var DeviceHub = class {
|
|
|
40696
41194
|
return offline;
|
|
40697
41195
|
}
|
|
40698
41196
|
if (this.activeByDevice.get(deviceId) !== conn) return offline;
|
|
40699
|
-
return await new Promise((resolve3) => conn.session.call(
|
|
41197
|
+
return await new Promise((resolve3) => conn.session.call(
|
|
41198
|
+
req,
|
|
41199
|
+
(result, disposition) => resolve3({ disposition, result })
|
|
41200
|
+
));
|
|
40700
41201
|
}
|
|
40701
41202
|
/** DeviceChannel.ws:Node 宿主的升级在 http 层处理,永不应命中此路由。 */
|
|
40702
41203
|
async ws() {
|
|
@@ -40973,7 +41474,13 @@ var DeviceRouter = class _DeviceRouter {
|
|
|
40973
41474
|
return await new Promise((resolve3) => {
|
|
40974
41475
|
const timer = setTimeout(() => {
|
|
40975
41476
|
this.pending.delete(correlationId);
|
|
40976
|
-
resolve3({
|
|
41477
|
+
resolve3({
|
|
41478
|
+
disposition: "unknown",
|
|
41479
|
+
result: {
|
|
41480
|
+
ok: false,
|
|
41481
|
+
error: new TBError("unavailable", "\u8BBE\u5907\u8C03\u7528\u8F6C\u53D1\u8D85\u65F6").toJSON()
|
|
41482
|
+
}
|
|
41483
|
+
});
|
|
40977
41484
|
}, this.opts.forwardTimeoutMs ?? DEVICE_FORWARD_TIMEOUT_MS);
|
|
40978
41485
|
timer.unref?.();
|
|
40979
41486
|
this.pending.set(correlationId, { resolve: resolve3, timer });
|
|
@@ -40983,8 +41490,11 @@ var DeviceRouter = class _DeviceRouter {
|
|
|
40983
41490
|
this.pending.delete(correlationId);
|
|
40984
41491
|
clearTimeout(waiter.timer);
|
|
40985
41492
|
waiter.resolve({
|
|
40986
|
-
|
|
40987
|
-
|
|
41493
|
+
disposition: "unknown",
|
|
41494
|
+
result: {
|
|
41495
|
+
ok: false,
|
|
41496
|
+
error: new TBError("unavailable", "\u8BBE\u5907\u8C03\u7528\u8F6C\u53D1\u5931\u8D25").toJSON()
|
|
41497
|
+
}
|
|
40988
41498
|
});
|
|
40989
41499
|
});
|
|
40990
41500
|
});
|
|
@@ -40994,7 +41504,13 @@ var DeviceRouter = class _DeviceRouter {
|
|
|
40994
41504
|
this.renewTimer = void 0;
|
|
40995
41505
|
for (const [, waiter] of this.pending) {
|
|
40996
41506
|
clearTimeout(waiter.timer);
|
|
40997
|
-
waiter.resolve({
|
|
41507
|
+
waiter.resolve({
|
|
41508
|
+
disposition: "unknown",
|
|
41509
|
+
result: {
|
|
41510
|
+
ok: false,
|
|
41511
|
+
error: new TBError("unavailable", "\u7F51\u5173\u6B63\u5728\u5173\u95ED").toJSON()
|
|
41512
|
+
}
|
|
41513
|
+
});
|
|
40998
41514
|
}
|
|
40999
41515
|
this.pending.clear();
|
|
41000
41516
|
await Promise.all([...this.owned].map(async (id) => await this.release(id)));
|
|
@@ -41023,7 +41539,13 @@ var DeviceRouter = class _DeviceRouter {
|
|
|
41023
41539
|
try {
|
|
41024
41540
|
result = await this.opts.onLocalCall(call.deviceId, call.req);
|
|
41025
41541
|
} catch {
|
|
41026
|
-
result = {
|
|
41542
|
+
result = {
|
|
41543
|
+
disposition: "unknown",
|
|
41544
|
+
result: {
|
|
41545
|
+
ok: false,
|
|
41546
|
+
error: new TBError("internal", "\u8BBE\u5907\u8C03\u7528\u6267\u884C\u5931\u8D25").toJSON()
|
|
41547
|
+
}
|
|
41548
|
+
};
|
|
41027
41549
|
}
|
|
41028
41550
|
const reply = { correlationId: call.correlationId, result };
|
|
41029
41551
|
await this.backend.publish(deviceReplyChannel(call.replyTo), JSON.stringify(reply)).catch(() => {
|
|
@@ -41330,6 +41852,7 @@ var commandSchema = external_exports.strictObject({
|
|
|
41330
41852
|
argv: external_exports.array(external_exports.union([external_exports.string(), argumentSchema])).optional(),
|
|
41331
41853
|
cwd: external_exports.string().min(1).optional(),
|
|
41332
41854
|
effect: external_exports.enum(["read", "write", "destructive"]),
|
|
41855
|
+
delivery: external_exports.enum(["realtime", "mailbox", "both"]).optional(),
|
|
41333
41856
|
confirm: external_exports.boolean().optional(),
|
|
41334
41857
|
timeoutMs: external_exports.number().int().positive().max(SHELL_EXEC_DEFAULT_TIMEOUT_MS).optional(),
|
|
41335
41858
|
maxOutputBytes: external_exports.number().int().positive().max(STRUCTURED_COMMAND_MAX_OUTPUT_BYTES).optional(),
|
|
@@ -41958,7 +42481,7 @@ import { join as join4 } from "path";
|
|
|
41958
42481
|
// package.json
|
|
41959
42482
|
var package_default = {
|
|
41960
42483
|
name: "@tool-bridge/server",
|
|
41961
|
-
version: "0.
|
|
42484
|
+
version: "0.20.0",
|
|
41962
42485
|
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",
|
|
41963
42486
|
type: "module",
|
|
41964
42487
|
license: "MIT",
|
|
@@ -42297,26 +42820,27 @@ function createTbServer(config) {
|
|
|
42297
42820
|
}
|
|
42298
42821
|
const app = createTbApp(deps);
|
|
42299
42822
|
let server;
|
|
42300
|
-
let
|
|
42301
|
-
let
|
|
42302
|
-
const
|
|
42303
|
-
if (
|
|
42304
|
-
|
|
42823
|
+
let maintenanceTimer;
|
|
42824
|
+
let maintenanceInFlight = false;
|
|
42825
|
+
const runMaintenance = async () => {
|
|
42826
|
+
if (maintenanceInFlight) return;
|
|
42827
|
+
maintenanceInFlight = true;
|
|
42305
42828
|
try {
|
|
42306
42829
|
await cleanupDefaultStore(deps);
|
|
42830
|
+
if (deps.encryptionKey !== void 0) await cleanupDeviceMailbox(deps);
|
|
42307
42831
|
} finally {
|
|
42308
|
-
|
|
42832
|
+
maintenanceInFlight = false;
|
|
42309
42833
|
}
|
|
42310
42834
|
};
|
|
42311
42835
|
const reportStoreCleanupFailure = () => {
|
|
42312
42836
|
console.warn(JSON.stringify({ event: "tool_bridge_store_cleanup_failed" }));
|
|
42313
42837
|
};
|
|
42314
42838
|
const scheduleStoreCleanup = () => {
|
|
42315
|
-
void
|
|
42316
|
-
|
|
42317
|
-
void
|
|
42839
|
+
void runMaintenance().catch(reportStoreCleanupFailure);
|
|
42840
|
+
maintenanceTimer = setInterval(() => {
|
|
42841
|
+
void runMaintenance().catch(reportStoreCleanupFailure);
|
|
42318
42842
|
}, config.storeCleanupIntervalSec * 1e3);
|
|
42319
|
-
|
|
42843
|
+
maintenanceTimer.unref?.();
|
|
42320
42844
|
};
|
|
42321
42845
|
return {
|
|
42322
42846
|
app,
|
|
@@ -42345,9 +42869,9 @@ function createTbServer(config) {
|
|
|
42345
42869
|
},
|
|
42346
42870
|
async close() {
|
|
42347
42871
|
draining = true;
|
|
42348
|
-
if (
|
|
42349
|
-
clearInterval(
|
|
42350
|
-
|
|
42872
|
+
if (maintenanceTimer !== void 0) {
|
|
42873
|
+
clearInterval(maintenanceTimer);
|
|
42874
|
+
maintenanceTimer = void 0;
|
|
42351
42875
|
}
|
|
42352
42876
|
let closed;
|
|
42353
42877
|
if (server !== void 0) {
|